Skip to content

Commit ba13a10

Browse files
committed
wip: smarter behavior on rebase conflicts on production
1 parent 6f39ea8 commit ba13a10

File tree

3 files changed

+199
-19
lines changed

3 files changed

+199
-19
lines changed

cls/SourceControl/Git/Extension.cls

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,10 @@ Method OnSourceMenuItem(name As %String, ByRef Enabled As %String, ByRef Display
159159
} else {
160160
set Enabled = -1
161161
}
162-
if (name '= "") {
162+
163+
if (name = "Status") {
164+
set DisplayName = ..LocalizeName(name)_" (branch: "_##class(SourceControl.Git.Utils).GetCurrentBranch()_")"
165+
} if (name '= "") {
163166
set DisplayName = ..LocalizeName(name)
164167
}
165168
quit $$$OK
@@ -395,4 +398,3 @@ Method AddToSourceControl(InternalName As %String, Description As %String = "")
395398
}
396399

397400
}
398-
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
Include (%occInclude, %occErrors, %occKeyword, %occReference, %occSAX)
2+
3+
Class SourceControl.Git.Util.ProductionConflictResolver Extends %RegisteredObject
4+
{
5+
6+
Property logStream As %Stream.Object [ Private ];
7+
8+
Property productionFile As %String [ Private ];
9+
10+
Property productionClassname As %Dictionary.CacheClassname [ Private ];
11+
12+
Property errorStatus As %Status [ InitialExpression = 1, Private ];
13+
14+
/// API property: whether or not the conflict was resolved
15+
Property resolved As %Boolean [ InitialExpression = 0 ];
16+
17+
/// API property: error message if resolved is false
18+
Property errorMessage As %String [ Calculated ];
19+
20+
Method errorMessageGet() As %String
21+
{
22+
If $$$ISERR(..errorStatus) {
23+
Do $System.Status.DecomposeStatus(..errorStatus,.components)
24+
If $Get(components(1,"code")) = $$$GeneralError {
25+
Quit $Get(components(1,"param",1))
26+
} Else {
27+
Set ex = ##class(%Exception.StatusException).CreateFromStatus(..errorStatus)
28+
Do ex.Log()
29+
Quit "an internal error occurred and has been logged."
30+
}
31+
} Else {
32+
Quit ""
33+
}
34+
}
35+
36+
ClassMethod FromLog(pOutStream As %Stream.Object) As SourceControl.Git.Util.ProductionConflictResolver
37+
{
38+
Set inst = ..%New()
39+
Try {
40+
Set inst.logStream = pOutStream
41+
Do inst.ConsumeStream()
42+
Do inst.Resolve()
43+
} Catch e {
44+
Set inst.resolved = 0
45+
Set inst.errorStatus = e.AsStatus()
46+
}
47+
Do inst.logStream.Rewind() // Finally
48+
Quit inst
49+
}
50+
51+
Method ConsumeStream() [ Private ]
52+
{
53+
Do ..logStream.Rewind()
54+
Do ..logStream.ReadLine()
55+
Set productionLine = ..logStream.ReadLine()
56+
Set ..productionFile = $Piece(productionLine,"Merge conflict in ",2)
57+
If ..productionFile = "" {
58+
$$$ThrowStatus($$$ERROR($$$GeneralError,"Message did not reflect merge conflict on a single file."))
59+
}
60+
If '..logStream.AtEnd {
61+
$$$ThrowStatus($$$ERROR($$$GeneralError,"Multiple files had merge conflicts; cannot resolve intelligently."))
62+
}
63+
Set internalName = ##class(SourceControl.Git.Utils).NameToInternalName(..productionFile)
64+
If ($Piece(internalName,".",*) '= "CLS") {
65+
$$$ThrowStatus($$$ERROR($$$GeneralError,"File with conflict is not a class."))
66+
}
67+
Set ..productionClassname = $Piece(internalName,".",1,*-1)
68+
If '$$$comClassDefined(..productionClassname) && '$ClassMethod(..productionClassname,"%Extends","Ens.Production") {
69+
$$$ThrowStatus($$$ERROR($$$GeneralError,"File with conflict is not an interoperability production."))
70+
}
71+
}
72+
73+
Method Resolve() [ Private ]
74+
{
75+
// If we got this far, ..productionClassname is a subclass of Ens.Production and ..productionFile is its file within the git root.
76+
Set code = 0
77+
Set code = code + ##class(SourceControl.Git.Utils).RunGitCommand("show",,.base,":1:"_..productionFile)
78+
Set code = code + ##class(SourceControl.Git.Utils).RunGitCommand("show",,.ours,":2:"_..productionFile)
79+
Set code = code + ##class(SourceControl.Git.Utils).RunGitCommand("show",,.theirs,":3:"_..productionFile)
80+
81+
If code > 0 {
82+
$$$ThrowStatus($$$ERROR($$$GeneralError,"Unable to load base/ours/theirs for "_..productionFile))
83+
}
84+
85+
Do ..ClassToTree(base,.baseTree)
86+
Do ..ClassToTree(ours,.oursTree)
87+
Do ..ClassToTree(theirs,.theirsTree)
88+
89+
zw baseTree,oursTree,theirsTree
90+
91+
Do ..ThreeWayMerge(.baseTree,.theirsTree,.oursTree,.resolved)
92+
93+
Do ..TreeToClass(.resolved)
94+
}
95+
96+
Method ClassToTree(source As %Stream.Object, Output tree)
97+
{
98+
Set sc = $$$OK
99+
Kill ^||%oddDEF
100+
Set ^||%oddDEF(..productionClassname)=""
101+
Try {
102+
Do source.Rewind()
103+
While 'source.AtEnd {
104+
Set tTextArray($Increment(tTextArray(0))) = source.ReadLine(,.sc)
105+
$$$ThrowOnError(sc)
106+
}
107+
$$$ThrowOnError(##class(%Atelier.v1.Utils.TextServices).SetTextFromArray(.tTextArray,,..productionClassname,"CLS"))
108+
Merge classDef = ^||%oddDEF(..productionClassname)
109+
110+
Set xmlStream = ##class(%Stream.TmpCharacter).%New()
111+
For i=1:1:$Get(classDef($$$cCLASSxdata,"ProductionDefinition",$$$cXDATAdata)) {
112+
Do xmlStream.WriteLine($Get(classDef($$$cCLASSxdata,"ProductionDefinition",$$$cXDATAdata,i)))
113+
}
114+
Set handler=##class(%XML.ImportHandler).%New("IRIS.Temp",$$$IntHandler)
115+
$$$ThrowOnError(##Class(%XML.SAX.Parser).ParseStream(xmlStream,handler))
116+
117+
Merge tree = @handler.DOMName@(handler.Tree)
118+
} Catch e {
119+
Set sc = e.AsStatus()
120+
}
121+
Do source.Rewind()
122+
Kill ^||%oddDEF(..productionClassname)
123+
$$$ThrowOnError(sc)
124+
}
125+
126+
Method ThreeWayMerge(ByRef base, ByRef theirs, ByRef ours, Output resolved)
127+
{
128+
}
129+
130+
Method TreeToClass(ByRef tree)
131+
{
132+
}
133+
134+
}

cls/SourceControl/Git/Utils.cls

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@ ClassMethod AfterUserAction(Type As %Integer, Name As %String, InternalName As %
303303
do ..Sync(Msg)
304304
set Reload = 1
305305
}
306+
} elseif (menuItemName = "GitWebUI") {
307+
// Always force reload as many things could have possibly changed.
308+
set Reload = 1
306309
}
307310
quit $$$OK
308311
}
@@ -395,14 +398,49 @@ ClassMethod StageAddedFiles()
395398
}
396399

397400
/// Merges the files from the configured branch as part of the Sync operation
398-
ClassMethod MergeDefaultRemoteBranch()
401+
/// Returns true if this resulted in durable changes to the local git repo
402+
ClassMethod MergeDefaultRemoteBranch(Output alert As %String = "") As %Boolean
399403
{
404+
set rebased = 0
400405
set settings = ##class(SourceControl.Git.Settings).%New()
401406
set defaultMergeBranch = settings.defaultMergeBranch
402407
if defaultMergeBranch '= "" {
403-
do ..RunGitWithArgs(.errStream, .outStream, "rebase", defaultMergeBranch)
408+
do ..RunGitWithArgs(.errStream, .outStream, "fetch", "origin", defaultMergeBranch_":"_defaultMergeBranch)
404409
do ..PrintStreams(errStream, outStream)
410+
411+
// Start a transaction so code changes can be rolled back
412+
set initTLevel = $TLevel
413+
try {
414+
TSTART
415+
set code = ..RunGitWithArgs(.errStream, .outStream, "rebase", defaultMergeBranch)
416+
if (code '= 0) {
417+
$$$ThrowStatus($$$ERROR($$$GeneralError,"git rebase reported failure"))
418+
}
419+
set rebased = 1
420+
TCOMMIT
421+
} catch e {
422+
// "rebase" may throw an exception due to errors syncing to IRIS. In that case, roll back and keep going to abort the rebase.
423+
write !,"Attempting to resolve differences in production definition..."
424+
set resolver = ##class(SourceControl.Git.Util.ProductionConflictResolver).FromLog(outStream)
425+
write !! zw resolver write !!
426+
if resolver.resolved {
427+
set rebased = 1
428+
write " success!"
429+
} else {
430+
write " unable to resolve - "_resolver.errorMessage
431+
while $TLevel > initTLevel {
432+
TROLLBACK 1
433+
}
434+
}
435+
}
436+
if 'rebased {
437+
do ..RunGitCommand("rebase",.errStream, .outStream,"--abort")
438+
do ..PrintStreams(errStream, outStream)
439+
set alert = "WARNING: Remote branch '"_defaultMergeBranch_"' could not be merged due to conflicts. Changes have been pushed to '"_..GetCurrentBranch()_"' and must be resolved in your git remote."
440+
write !,alert,!
441+
}
405442
}
443+
quit rebased
406444
}
407445

408446
/// Converts the DynamicArray into a list and calls the SourceControl.Git.Change RemoveUncommitted method on the newly created list
@@ -419,7 +457,7 @@ ClassMethod ClearUncommitted(filesWithActions) As %Status
419457
quit $$$OK
420458
}
421459

422-
ClassMethod Sync(Msg As %String) As %Status
460+
ClassMethod Sync(Msg As %String, Output alert As %String) As %Status
423461
{
424462
write !, "Syncing local repository...", !
425463
do ..StageAddedFiles()
@@ -430,20 +468,24 @@ ClassMethod Sync(Msg As %String) As %Status
430468
do ..Fetch()
431469
do ..Pull()
432470
do ..SyncCommit(Msg)
433-
do ..Push()
434-
do ..MergeDefaultRemoteBranch()
435-
do ..Push()
436-
471+
do ..Push(,1)
472+
if ..MergeDefaultRemoteBranch(.alert) {
473+
do ..Push(,1)
474+
}
437475
}
438-
439476
quit $$$OK
440477
}
441478

442-
ClassMethod Push(remote As %String = "origin") As %Status
479+
ClassMethod Push(remote As %String = "origin", force As %Boolean = 0) As %Status
443480
{
444481
do ##class(SourceControl.Git.Utils).RunGitCommandWithInput("branch",,.errStream,.outstream,"--show-current")
445482
set branchName = outstream.ReadLine(outstream.Size)
446-
do ..RunGitWithArgs(.errStream, .outStream, "push", remote, branchName)
483+
if (force) {
484+
set args($i(args)) = "--force"
485+
}
486+
set args($i(args)) = remote
487+
set args($i(args)) = branchName
488+
do ..RunGitWithArgs(.errStream, .outStream, "push", args...)
447489
do ..PrintStreams(errStream, outStream)
448490
quit $$$OK
449491
}
@@ -475,7 +517,7 @@ ClassMethod Pull(remote As %String = "origin") As %Status
475517
set branchName = outStream.ReadLine(outStream.Size)
476518
write !, "Pulling from branch: ", branchName
477519
kill errStream, outStream
478-
set returnCode = ..RunGitWithArgs(.errStream, .outStream, "pull", remote _ "/" _ branchName)
520+
set returnCode = ..RunGitWithArgs(.errStream, .outStream, "pull", remote, branchName)
479521

480522
w !, "Pull ran with return code: " _ returnCode
481523
quit $$$OK
@@ -1226,6 +1268,7 @@ ClassMethod ImportCSPFile(InternalName As %String) As %Status
12261268
ClassMethod ListItemsInFiles(ByRef itemList, ByRef err) As %Status
12271269
{
12281270
#define DoNotLoad 1
1271+
set res = $$$OK
12291272

12301273
set mappingFileType = $order($$$SourceMapping(""))
12311274
while (mappingFileType '= "") {
@@ -1536,7 +1579,7 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O
15361579
{
15371580
set pullArg = ""
15381581
if command = "pull" {
1539-
set pullArg = args(1)
1582+
set pullArg = $Get(args(1))
15401583
}
15411584
// Special case: git --version is used internally even when the settings incorporated here may be invalid/unspecified.
15421585
if (command '= "--version") {
@@ -1570,12 +1613,13 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O
15701613
set diffBase = ""
15711614
set diffCompare = ""
15721615
set pullOriginIndex = ""
1573-
if (command = "checkout") || (command = "merge") || (command = "rebase") || (command = "pull"){
1616+
if (command = "checkout") || (command = "merge") || (command = "rebase") || (command = "pull") {
15741617
set syncIris = 1
1575-
set diffCompare = args(args)
1618+
if $data(args) && $data(args(args),diffCompare) {
1619+
// no-op
1620+
}
15761621
}
15771622

1578-
15791623
for i=1:1:$get(args) {
15801624
if ($data(args(i))) {
15811625
set newArgs($increment(newArgs)) = args(i)
@@ -1607,7 +1651,7 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O
16071651
}
16081652
do ..RunGitCommand("fetch", .errorStream, .outputStream)
16091653
kill errorStream, outputStream
1610-
do ##class(SourceControl.Git.Utils).RunGitCommandWithInput("diff",,.errorStream,.outputStream, diffBase_".."_diffCompare, "--name-status")
1654+
do ##class(SourceControl.Git.Utils).RunGitCommandWithInput("diff",,.errorStream,.outputStream, diffBase_$Case(diffCompare,"":"",:"..")_diffCompare, "--name-status")
16111655
while (outputStream.AtEnd = 0) {
16121656
set file = outputStream.ReadLine()
16131657
set modification = ##class(SourceControl.Git.Modification).%New()
@@ -1656,8 +1700,8 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O
16561700
for stream=errStream,outStream {
16571701
set stream.RemoveOnClose = 1
16581702
}
1659-
do ..PrintStreams(errStream, outStream)
16601703
if syncIris {
1704+
do ..PrintStreams(errStream, outStream)
16611705
$$$ThrowOnError(..SyncIrisWithRepo(.files))
16621706
}
16631707
quit returnCode

0 commit comments

Comments
 (0)