Skip to content

Commit 327369a

Browse files
committed
feat: automatically resolve common production conflict case
1 parent 724d118 commit 327369a

File tree

4 files changed

+212
-170
lines changed

4 files changed

+212
-170
lines changed

cls/SourceControl/Git/Util/ProductionConflictResolver.cls

Lines changed: 67 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -72,89 +72,84 @@ Method ConsumeStream() [ Private ]
7272

7373
Method Resolve() [ Private ]
7474
{
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-
}
75+
Set filePath = ##class(SourceControl.Git.Utils).TempFolder()_..productionFile
76+
Set file = ##class(%Stream.FileCharacter).%OpenId(filePath,,.sc)
77+
$$$ThrowOnError(sc)
8478

85-
Do ..ClassToXMLDoc(base,.baseDoc)
86-
Do ..ClassToXMLDoc(ours,.oursDoc)
87-
Do ..ClassToXMLDoc(theirs,.theirsDoc)
79+
Do ..ResolveStream(file) // Throws exception on failure
8880

89-
Do ..ThreeWayMerge(baseDoc,oursDoc,theirsDoc,.resolved)
81+
$$$ThrowOnError(##class(SourceControl.Git.Utils).ImportItem(..productionClassname_".CLS",1))
82+
$$$ThrowOnError($System.OBJ.Compile(..productionClassname,"ck"))
9083

91-
Do ..XMLDocToClass(resolved)
84+
// TODO: if we add multiple resolvers, move this to the end.
85+
set code = ##class(SourceControl.Git.Utils).RunGitWithArgs(.errStream, .outStream, "add", ..productionFile)
86+
if (code '= 0) {
87+
$$$ThrowStatus($$$ERROR($$$GeneralError,"git add reported failure"))
88+
}
89+
set code = ##class(SourceControl.Git.Utils).RunGitWithArgs(.errStream, .outStream, "commit", "-m", "Auto-resolved conflict on production class")
90+
if (code '= 0) {
91+
$$$ThrowStatus($$$ERROR($$$GeneralError,"git commit reported failure"))
92+
}
93+
set code = ##class(SourceControl.Git.Utils).RunGitWithArgs(.errStream, .outStream, "rebase", "--continue")
94+
if (code '= 0) {
95+
$$$ThrowStatus($$$ERROR($$$GeneralError,"git rebase --continue reported failure"))
96+
}
97+
98+
Set ..resolved = 1
9299
}
93100

94-
Method ClassToXMLDoc(source As %Stream.Object, Output doc As %XML.Document)
101+
/// Non-private to support unit testing
102+
ClassMethod ResolveStream(stream As %Stream.Object)
95103
{
96-
Set sc = $$$OK
97-
Kill ^||%oddDEF
98-
Set ^||%oddDEF(..productionClassname)=""
99-
Try {
100-
Do source.Rewind()
101-
While 'source.AtEnd {
102-
Set tTextArray($Increment(tTextArray(0))) = source.ReadLine(,.sc)
103-
$$$ThrowOnError(sc)
104+
// File may have:
105+
/*
106+
<<<<<<< HEAD
107+
<Item Name="Demo7" Category="" ClassName="EnsLib.CloudStorage.BusinessOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
108+
=======
109+
<Item Name="Demo5" Category="" ClassName="EnsLib.AmazonCloudWatch.MetricAlarmOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
110+
>>>>>>> 607d1f6 (modified src/HCC/Connect/Production.cls add Demo5)
111+
</Item>
112+
*/
113+
114+
// If:
115+
// * We have one such marker (<<<<<<< / ======= / >>>>>>>)
116+
// * The line after >>>>>> is "</Item>"
117+
// Then:
118+
// * We can replace ======= with "</Item>"
119+
120+
Set copy = ##class(%Stream.TmpCharacter).%New()
121+
Set markerCount = 0
122+
Set postCloseMarker = 0
123+
While 'stream.AtEnd {
124+
Set line = stream.ReadLine()
125+
Set start = $Extract(line,1,7)
126+
If start = "<<<<<<<" {
127+
Set markerCount = markerCount + 1
128+
Continue
129+
} ElseIf (start = ">>>>>>>") {
130+
Set postCloseMarker = 1
131+
Continue
132+
} ElseIf (start = "=======") {
133+
Do copy.WriteLine(" </Item>")
134+
Continue
135+
} ElseIf postCloseMarker {
136+
If $ZStrip(line,"<>W") '= "</Item>" {
137+
$$$ThrowStatus($$$ERROR($$$GeneralError,"The type of conflict encountered is not handled; user must resolve manually."))
138+
}
139+
Set postCloseMarker = 0
104140
}
105-
$$$ThrowOnError(##class(%Atelier.v1.Utils.TextServices).SetTextFromArray(.tTextArray,,..productionClassname,"CLS"))
106-
Merge classDef = ^||%oddDEF(..productionClassname)
107-
108-
Set xmlStream = ##class(%Stream.TmpCharacter).%New()
109-
For i=1:1:$Get(classDef($$$cCLASSxdata,"ProductionDefinition",$$$cXDATAdata)) {
110-
Do xmlStream.WriteLine($Get(classDef($$$cCLASSxdata,"ProductionDefinition",$$$cXDATAdata,i)))
111-
}
112-
Set reader = ##class(%XML.Reader).%New()
113-
$$$ThrowOnError(reader.OpenStream(xmlStream))
114-
Set doc = reader.Document
115-
} Catch e {
116-
Set sc = e.AsStatus()
141+
Do copy.WriteLine(line)
117142
}
118-
Do source.Rewind()
119-
Kill ^||%oddDEF
120-
$$$ThrowOnError(sc)
121-
}
122143

123-
ClassMethod ThreeWayMerge(base As %XML.Document, theirs As %XML.Document, ours As %XML.Document, Output resolved)
124-
{
125-
// TODO: actually do three-way merge
126-
Merge resolved = ours
127-
}
128-
129-
ClassMethod BuildElementPathMap(doc As %XML.Document, Output map)
130-
{
131-
}
132-
133-
ClassMethod XMLDocToStream(document As %XML.Document, ByRef outStream)
134-
{
135-
Set writer = ##class(%XML.Writer).%New()
136-
Set export = ##class(%Stream.TmpCharacter).%New()
137-
$$$ThrowOnError(writer.OutputToStream(export))
138-
$$$ThrowOnError(writer.Document(document))
139-
140-
Set reader = ##class(%XML.Reader).%New()
141-
$$$ThrowOnError(reader.OpenStream(export))
142-
Do reader.CorrelateRoot("Ens.Config.Production")
143-
If 'reader.Next(.production,.sc) {
144-
$$$ThrowStatus($$$ERROR($$$GeneralError,"Could not reimport production from resolved XML."))
144+
If markerCount > 1 {
145+
$$$ThrowStatus($$$ERROR($$$GeneralError,"Multiple conflicts found, cannot resolve automatically."))
146+
} ElseIf markerCount = 0 {
147+
$$$ThrowStatus($$$ERROR($$$GeneralError,"No conflict markers found in file"))
145148
}
146-
$$$ThrowOnError(sc)
147-
$$$ThrowOnError(production.XMLExportToStream(.outStream,,"literal,indent"))
148-
}
149149

150-
Method XMLDocToClass(resolved As %XML.Document)
151-
{
152-
Set xData = ##class(%Dictionary.XDataDefinition).IDKEYOpen(..productionClassname,"ProductionDefinition",,.sc)
153-
$$$ThrowOnError(sc)
154-
Do xData.Implementation.Clear()
155-
Do ..XMLDocToStream(resolved,xData.Implementation)
156-
Do xData.Implementation.Rewind()
157-
Write !!,$ZConvert(xData.Implementation.Read(100000),"O","HTML"),!!
150+
$$$ThrowOnError(stream.CopyFromAndSave(copy))
151+
152+
Quit 1
158153
}
159154

160155
}

cls/SourceControl/Git/Utils.cls

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -422,17 +422,17 @@ ClassMethod MergeDefaultRemoteBranch(Output alert As %String = "") As %Boolean
422422
// "rebase" may throw an exception due to errors syncing to IRIS. In that case, roll back and keep going to abort the rebase.
423423
write !,"Attempting to resolve differences in production definition..."
424424
set resolver = ##class(SourceControl.Git.Util.ProductionConflictResolver).FromLog(outStream)
425-
write !! zw resolver write !!
426425
if resolver.resolved {
427426
set rebased = 1
427+
TCOMMIT
428428
write " success!"
429429
} else {
430430
write " unable to resolve - "_resolver.errorMessage
431-
while $TLevel > initTLevel {
432-
TROLLBACK 1
433-
}
434431
}
435432
}
433+
while $TLevel > initTLevel {
434+
TROLLBACK 1
435+
}
436436
if 'rebased {
437437
do ..RunGitCommand("rebase",.errStream, .outStream,"--abort")
438438
do ..PrintStreams(errStream, outStream)
@@ -2392,4 +2392,3 @@ ClassMethod BaselineExport(pCommitMessage = "", pPushToRemote = "") As %Status
23922392
}
23932393

23942394
}
2395-
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
Class UnitTest.SourceControl.Git.ProductionConflictResolve Extends %UnitTest.TestCase
2+
{
3+
4+
Method TestResolve()
5+
{
6+
Set file = ##class(%Stream.FileCharacter).%New()
7+
Set file.RemoveOnClose = 1
8+
Set xdata = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(),"SampleFile1",,.sc)
9+
While 'xdata.Data.AtEnd {
10+
Do file.WriteLine(xdata.Data.ReadLine())
11+
}
12+
$$$ThrowOnError(file.%Save())
13+
14+
Set resolved = ##class(%Stream.FileCharacter).%New()
15+
Set resolved.RemoveOnClose = 1
16+
Set xdata = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(),"ResolvedFile1",,.sc)
17+
While 'xdata.Data.AtEnd {
18+
Do resolved.WriteLine(xdata.Data.ReadLine())
19+
}
20+
$$$ThrowOnError(resolved.%Save())
21+
22+
Do ##class(SourceControl.Git.Util.ProductionConflictResolver).ResolveStream(file)
23+
24+
Do $$$AssertFilesSame(file.Filename,resolved.Filename)
25+
}
26+
27+
XData SampleFile1 [ MimeType = text/plain ]
28+
{
29+
Class HCC.Connect.Production Extends Ens.Production
30+
{
31+
32+
XData ProductionDefinition
33+
{
34+
<Production Name="HCC.Connect.Production" LogGeneralTraceEvents="false">
35+
<Description>Health Connect Cloud Base Production</Description>
36+
<ActorPoolSize>1</ActorPoolSize>
37+
<Item Name="Ens.Activity.Operation.Local" Category="" ClassName="Ens.Activity.Operation.Local" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
38+
</Item>
39+
<Item Name="Ens.Enterprise.MsgBankOperation" Category="" ClassName="Ens.Enterprise.MsgBankOperation" PoolSize="1" Enabled="true" Foreground="false" Comment="Health Connect Cloud Message Bank" LogTraceEvents="false" Schedule="">
40+
<Setting Target="Adapter" Name="IPAddress">bank</Setting>
41+
</Item>
42+
<Item Name="FeatureA Service" Category="" ClassName="EnsLib.HL7.Service.FTPService" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
43+
</Item>
44+
<Item Name="FeatureA Process" Category="" ClassName="EnsLib.HL7.MsgRouter.RoutingEngine" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
45+
<Setting Target="Host" Name="BusinessRuleName">HCC.Connect.FeatureAProcessRoutingRule</Setting>
46+
</Item>
47+
<Item Name="FeatureA Operation" Category="" ClassName="EnsLib.HL7.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
48+
</Item>
49+
<Item Name="FeatureB" Category="" ClassName="EnsLib.HL7.Service.TCPService" PoolSize="1" Enabled="false" Foreground="false" Comment="Comment" LogTraceEvents="false" Schedule="">
50+
</Item>
51+
<Item Name="FeatureBProcess" Category="" ClassName="EnsLib.HL7.MsgRouter.RoutingEngine" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
52+
<Setting Target="Host" Name="BusinessRuleName">HCC.Connect.FeatureBProcessRoutingRule</Setting>
53+
</Item>
54+
<Item Name="FeatureB Operation" Category="" ClassName="EnsLib.HL7.Operation.FileOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="Comment 12" LogTraceEvents="false" Schedule="">
55+
</Item>
56+
<Item Name="FeatureC Operation" Category="" ClassName="EnsLib.HL7.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
57+
<Setting Target="Adapter" Name="IPAddress">127.0.0.1</Setting>
58+
<Setting Target="Adapter" Name="Port">8080</Setting>
59+
</Item>
60+
<Item Name="FeatureG" Category="" ClassName="EnsLib.HL7.Service.TCPService" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
61+
</Item>
62+
<Item Name="FeatureD" Category="" ClassName="EnsLib.FTP.PassthroughService" PoolSize="1" Enabled="false" Foreground="false" Comment="." LogTraceEvents="false" Schedule="">
63+
</Item>
64+
<Item Name="FeatureE" Category="" ClassName="EnsLib.EDI.X12.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
65+
<Setting Target="Adapter" Name="IPAddress">1.4.3.5</Setting>
66+
</Item>
67+
<Item Name="FeatureH" Category="" ClassName="Ens.Activity.Operation.REST" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
68+
<Setting Target="Adapter" Name="HTTPPort">12345</Setting>
69+
<Setting Target="Adapter" Name="HTTPServer">localhost</Setting>
70+
</Item>
71+
<Item Name="Baz" Category="" ClassName="EnsLib.DICOM.Duplex.TCP" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
72+
</Item>
73+
<<<<<<< HEAD
74+
<Item Name="Demo7" Category="" ClassName="EnsLib.CloudStorage.BusinessOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
75+
=======
76+
<Item Name="Demo5" Category="" ClassName="EnsLib.AmazonCloudWatch.MetricAlarmOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
77+
>>>>>>> 607d1f6 (modified src/HCC/Connect/Production.cls add Demo5)
78+
</Item>
79+
</Production>
80+
}
81+
82+
}
83+
}
84+
85+
XData ResolvedFile1 [ MimeType = text/plain ]
86+
{
87+
Class HCC.Connect.Production Extends Ens.Production
88+
{
89+
90+
XData ProductionDefinition
91+
{
92+
<Production Name="HCC.Connect.Production" LogGeneralTraceEvents="false">
93+
<Description>Health Connect Cloud Base Production</Description>
94+
<ActorPoolSize>1</ActorPoolSize>
95+
<Item Name="Ens.Activity.Operation.Local" Category="" ClassName="Ens.Activity.Operation.Local" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
96+
</Item>
97+
<Item Name="Ens.Enterprise.MsgBankOperation" Category="" ClassName="Ens.Enterprise.MsgBankOperation" PoolSize="1" Enabled="true" Foreground="false" Comment="Health Connect Cloud Message Bank" LogTraceEvents="false" Schedule="">
98+
<Setting Target="Adapter" Name="IPAddress">bank</Setting>
99+
</Item>
100+
<Item Name="FeatureA Service" Category="" ClassName="EnsLib.HL7.Service.FTPService" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
101+
</Item>
102+
<Item Name="FeatureA Process" Category="" ClassName="EnsLib.HL7.MsgRouter.RoutingEngine" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
103+
<Setting Target="Host" Name="BusinessRuleName">HCC.Connect.FeatureAProcessRoutingRule</Setting>
104+
</Item>
105+
<Item Name="FeatureA Operation" Category="" ClassName="EnsLib.HL7.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
106+
</Item>
107+
<Item Name="FeatureB" Category="" ClassName="EnsLib.HL7.Service.TCPService" PoolSize="1" Enabled="false" Foreground="false" Comment="Comment" LogTraceEvents="false" Schedule="">
108+
</Item>
109+
<Item Name="FeatureBProcess" Category="" ClassName="EnsLib.HL7.MsgRouter.RoutingEngine" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
110+
<Setting Target="Host" Name="BusinessRuleName">HCC.Connect.FeatureBProcessRoutingRule</Setting>
111+
</Item>
112+
<Item Name="FeatureB Operation" Category="" ClassName="EnsLib.HL7.Operation.FileOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="Comment 12" LogTraceEvents="false" Schedule="">
113+
</Item>
114+
<Item Name="FeatureC Operation" Category="" ClassName="EnsLib.HL7.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
115+
<Setting Target="Adapter" Name="IPAddress">127.0.0.1</Setting>
116+
<Setting Target="Adapter" Name="Port">8080</Setting>
117+
</Item>
118+
<Item Name="FeatureG" Category="" ClassName="EnsLib.HL7.Service.TCPService" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
119+
</Item>
120+
<Item Name="FeatureD" Category="" ClassName="EnsLib.FTP.PassthroughService" PoolSize="1" Enabled="false" Foreground="false" Comment="." LogTraceEvents="false" Schedule="">
121+
</Item>
122+
<Item Name="FeatureE" Category="" ClassName="EnsLib.EDI.X12.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
123+
<Setting Target="Adapter" Name="IPAddress">1.4.3.5</Setting>
124+
</Item>
125+
<Item Name="FeatureH" Category="" ClassName="Ens.Activity.Operation.REST" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
126+
<Setting Target="Adapter" Name="HTTPPort">12345</Setting>
127+
<Setting Target="Adapter" Name="HTTPServer">localhost</Setting>
128+
</Item>
129+
<Item Name="Baz" Category="" ClassName="EnsLib.DICOM.Duplex.TCP" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
130+
</Item>
131+
<Item Name="Demo7" Category="" ClassName="EnsLib.CloudStorage.BusinessOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
132+
</Item>
133+
<Item Name="Demo5" Category="" ClassName="EnsLib.AmazonCloudWatch.MetricAlarmOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
134+
</Item>
135+
</Production>
136+
}
137+
138+
}
139+
}
140+
141+
}

0 commit comments

Comments
 (0)