diff --git a/CHANGELOG.md b/CHANGELOG.md index b5dde105..689bf6bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Items mapped from database other than namespace's default routine database are now ignored by default when exporting or adding files - New setting to configure whether mapped items should be should be treated as read-only - Added a basic mode to automatically perform functionality expected in basic use cases (#349) -- New sync operation for basic mode that fetches, pulls, commits and then pushes (#349) +- New sync operation for basic mode that fetches, pulls, commits, pushes, rebases, and pushes again (#349) +- "Sync" operation in basic mode automatically resolves the class of merge conflict common in production classes where multiple independent items are added in different feature branches - Now skips files belonging to other git enabled packages in `##class(SourceControl.Git.Change).RefreshUncommitted()` (#347) - Added a new "Branch" parameter to `##class(SourceControl.Git.PullEventHandler)` (#351) - Command-line utility to do a baseline export of items in a namespace @@ -26,7 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Menu items names are properly translated from internal name in VSCode, Management Portal (#372) - Now has proper locking behavior in `##class(SourceControl.Git.WebUIDriver).HandleRequest()`(#385) - Git operations from the WebUI now don't unlock the session if they aren't read-only +- Syncing only prompts users for a commit message if there are uncommitted files (#390) - WebUI works properly for users with %Developer without needing to add further SQL privileges (#365) +- Fixed `<UNDEFINED>` error running Import All (#380) +- Discarding changes now recompiles - critical for productions and some other cases (#387) ## [2.3.1] - 2024-04-30 diff --git a/cls/SourceControl/Git/Extension.cls b/cls/SourceControl/Git/Extension.cls index 1e466710..6cfb2305 100644 --- a/cls/SourceControl/Git/Extension.cls +++ b/cls/SourceControl/Git/Extension.cls @@ -159,7 +159,10 @@ Method OnSourceMenuItem(name As %String, ByRef Enabled As %String, ByRef Display } else { set Enabled = -1 } - if (name '= "") { + + if (name = "Status") { + set DisplayName = ..LocalizeName(name)_" (branch: "_##class(SourceControl.Git.Utils).GetCurrentBranch()_")" + } if (name '= "") { set DisplayName = ..LocalizeName(name) } quit $$$OK diff --git a/cls/SourceControl/Git/PullEventHandler.cls b/cls/SourceControl/Git/PullEventHandler.cls index 3dfb3a4c..66728728 100644 --- a/cls/SourceControl/Git/PullEventHandler.cls +++ b/cls/SourceControl/Git/PullEventHandler.cls @@ -20,5 +20,28 @@ Method OnPull() As %Status [ Abstract ] { } +/// <var>files</var> is an integer-subscripted array of <class>SourceControl.Git.Modification</class> objects. +ClassMethod ForModifications(ByRef files) As %Status +{ + set event = $classmethod(##class(SourceControl.Git.Utils).PullEventClass(),"%New") + set event.LocalRoot = ##class(SourceControl.Git.Utils).TempFolder() + merge event.ModifiedFiles = files + quit event.OnPull() } +/// <var>InternalName</var> may be a comma-delimited string or $ListBuild list +ClassMethod ForInternalNames(InternalName As %String) As %Status +{ + set list = $select($listvalid(InternalName):InternalName,1:$ListFromString(InternalName)) + set pointer = 0 + while $listnext(list,pointer,InternalName) { + set mod = ##class(SourceControl.Git.Modification).%New() + set mod.internalName = InternalName + set mod.externalName = ##class(SourceControl.Git.Utils).FullExternalName(InternalName) + set mod.changeType = "M" + set files($i(files)) = mod + } + quit ..ForModifications(.files) +} + +} diff --git a/cls/SourceControl/Git/PullEventHandler/IncrementalLoad.cls b/cls/SourceControl/Git/PullEventHandler/IncrementalLoad.cls index 5f3a8b11..8dd64d3b 100644 --- a/cls/SourceControl/Git/PullEventHandler/IncrementalLoad.cls +++ b/cls/SourceControl/Git/PullEventHandler/IncrementalLoad.cls @@ -35,7 +35,7 @@ Method OnPull() As %Status write !, "Nothing to compile." quit $$$OK } - quit $system.OBJ.CompileList(.compilelist, "cukb") + quit $system.OBJ.CompileList(.compilelist, "ck") } Method DeleteFile(item As %String) As %Status diff --git a/cls/SourceControl/Git/Util/ProductionConflictResolver.cls b/cls/SourceControl/Git/Util/ProductionConflictResolver.cls new file mode 100644 index 00000000..ec29a1a9 --- /dev/null +++ b/cls/SourceControl/Git/Util/ProductionConflictResolver.cls @@ -0,0 +1,156 @@ +Include (%occInclude, %occErrors, %occKeyword, %occReference, %occSAX) + +Class SourceControl.Git.Util.ProductionConflictResolver Extends %RegisteredObject +{ + +Property logStream As %Stream.Object [ Private ]; + +Property productionFile As %String [ Private ]; + +Property productionClassname As %Dictionary.CacheClassname [ Private ]; + +Property errorStatus As %Status [ InitialExpression = 1, Private ]; + +/// API property: whether or not the conflict was resolved +Property resolved As %Boolean [ InitialExpression = 0 ]; + +/// API property: error message if resolved is false +Property errorMessage As %String [ Calculated ]; + +Method errorMessageGet() As %String +{ + If $$$ISERR(..errorStatus) { + Do $System.Status.DecomposeStatus(..errorStatus,.components) + If $Get(components(1,"code")) = $$$GeneralError { + Quit $Get(components(1,"param",1)) + } Else { + Set ex = ##class(%Exception.StatusException).CreateFromStatus(..errorStatus) + Do ex.Log() + Quit "an internal error occurred and has been logged." + } + } Else { + Quit "" + } +} + +ClassMethod FromLog(pOutStream As %Stream.Object) As SourceControl.Git.Util.ProductionConflictResolver +{ + Set inst = ..%New() + Try { + Set inst.logStream = pOutStream + Do inst.ConsumeStream() + Do inst.Resolve() + } Catch e { + Set inst.resolved = 0 + Set inst.errorStatus = e.AsStatus() + } + Do inst.logStream.Rewind() // Finally + Quit inst +} + +Method ConsumeStream() [ Private ] +{ + Do ..logStream.Rewind() + Do ..logStream.ReadLine() + Set productionLine = ..logStream.ReadLine() + Set ..productionFile = $Piece(productionLine,"Merge conflict in ",2) + If ..productionFile = "" { + $$$ThrowStatus($$$ERROR($$$GeneralError,"Message did not reflect merge conflict on a single file.")) + } + If '..logStream.AtEnd { + $$$ThrowStatus($$$ERROR($$$GeneralError,"Multiple files had merge conflicts; cannot resolve intelligently.")) + } + Set internalName = ##class(SourceControl.Git.Utils).NameToInternalName(..productionFile) + If ($Piece(internalName,".",*) '= "CLS") { + $$$ThrowStatus($$$ERROR($$$GeneralError,"File with conflict is not a class.")) + } + Set ..productionClassname = $Piece(internalName,".",1,*-1) + If '($$$comClassDefined(..productionClassname) && $ClassMethod(..productionClassname,"%Extends","Ens.Production")) { + $$$ThrowStatus($$$ERROR($$$GeneralError,"File with conflict is not an interoperability production.")) + } +} + +Method Resolve() [ Private ] +{ + Set filePath = ##class(SourceControl.Git.Utils).TempFolder()_..productionFile + Set file = ##class(%Stream.FileCharacter).%OpenId(filePath,,.sc) + $$$ThrowOnError(sc) + + Do ..ResolveStream(file) // Throws exception on failure + + $$$ThrowOnError(##class(SourceControl.Git.Utils).ImportItem(..productionClassname_".CLS",1)) + $$$ThrowOnError($System.OBJ.Compile(..productionClassname,"ck")) + + // TODO: if we add multiple resolvers, move this to the end. + set code = ##class(SourceControl.Git.Utils).RunGitWithArgs(.errStream, .outStream, "add", ..productionFile) + if (code '= 0) { + $$$ThrowStatus($$$ERROR($$$GeneralError,"git add reported failure")) + } + set code = ##class(SourceControl.Git.Utils).RunGitWithArgs(.errStream, .outStream, "commit", "--no-edit") + if (code '= 0) { + $$$ThrowStatus($$$ERROR($$$GeneralError,"git commit reported failure")) + } + + set code = ##class(SourceControl.Git.Utils).RunGitWithArgs(.errStream, .outStream, "rebase", "--continue") + if (code '= 0) { + $$$ThrowStatus($$$ERROR($$$GeneralError,"git rebase --continue reported failure")) + } + + set ..resolved = 1 +} + +/// Non-private to support unit testing +ClassMethod ResolveStream(stream As %Stream.Object) +{ + // File may have: + /* + <<<<<<< HEAD + <Item Name="Demo7" Category="" ClassName="EnsLib.CloudStorage.BusinessOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + ======= + <Item Name="Demo5" Category="" ClassName="EnsLib.AmazonCloudWatch.MetricAlarmOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + >>>>>>> 607d1f6 (modified src/HCC/Connect/Production.cls add Demo5) + </Item> + */ + + // If: + // * We have one such marker (<<<<<<< / ======= / >>>>>>>) + // * The line after >>>>>> is "</Item>" + // Then: + // * We can replace ======= with "</Item>" + + Set copy = ##class(%Stream.TmpCharacter).%New() + Set markerCount = 0 + Set postCloseMarker = 0 + While 'stream.AtEnd { + Set line = stream.ReadLine() + Set start = $Extract(line,1,7) + If start = "<<<<<<<" { + Set markerCount = markerCount + 1 + Continue + } ElseIf (start = ">>>>>>>") { + Set postCloseMarker = 1 + Continue + } ElseIf (start = "=======") { + Do copy.WriteLine(" </Item>") + Continue + } ElseIf postCloseMarker { + If $ZStrip(line,"<>W") '= "</Item>" { + $$$ThrowStatus($$$ERROR($$$GeneralError,"The type of conflict encountered is not handled; user must resolve manually.")) + } + Set postCloseMarker = 0 + } + Do copy.WriteLine(line) + } + + If markerCount > 1 { + $$$ThrowStatus($$$ERROR($$$GeneralError,"Multiple conflicts found, cannot resolve automatically.")) + } ElseIf markerCount = 0 { + $$$ThrowStatus($$$ERROR($$$GeneralError,"No conflict markers found in file")) + } + + $$$ThrowOnError(stream.CopyFromAndSave(copy)) + + Quit 1 +} + +} diff --git a/cls/SourceControl/Git/Utils.cls b/cls/SourceControl/Git/Utils.cls index 02eceb85..e7977155 100644 --- a/cls/SourceControl/Git/Utils.cls +++ b/cls/SourceControl/Git/Utils.cls @@ -253,9 +253,18 @@ ClassMethod UserAction(InternalName As %String, MenuName As %String, ByRef Targe set Action = 7 quit $$$OK } elseif (menuItemName = "Sync") { - set Target = "Enter a commit message for the sync operation" - set Action = 7 - set Msg = ..PreSync() + if ..CheckForUncommittedFiles() { + set Target = "Enter a commit message for the sync operation" + set Action = 7 + set Msg = ..PreSync() + } else { + set Target = "" + do ..Sync("",.Target) + if (Target '= "") { + set Action = 6 + } + } + quit $$$OK } elseif (menuItemName = "Push") { quit ..Push() @@ -308,6 +317,9 @@ ClassMethod AfterUserAction(Type As %Integer, Name As %String, InternalName As % do ..Sync(Msg) set Reload = 1 } + } elseif (menuItemName = "GitWebUI") { + // Always force reload as many things could have possibly changed. + set Reload = 1 } quit $$$OK } @@ -326,8 +338,7 @@ ClassMethod Revert(InternalName As %String) As %Status do ..RunGitCommand("checkout", .errStream, .outStream, "--", filename) $$$QuitOnError(##class(SourceControl.Git.Change).RemoveUncommitted(filename,0,1)) $$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(0,1,,1)) - $$$QuitOnError(..ImportItem(InternalName,1)) - quit $$$OK + quit ##class(SourceControl.Git.PullEventHandler).ForInternalNames(InternalName) } ClassMethod Commit(InternalName As %String, Message As %String = "example commit message") As %Status @@ -376,17 +387,32 @@ ClassMethod PreSync() As %String /// Commits all the files as needed by the Sync operation ClassMethod SyncCommit(Msg As %String) As %Status { - set uncommittedFilesWithAction = ##class(SourceControl.Git.Utils).UncommittedWithAction().%Get("user") - set username = ..GitUserName() - set email = ..GitUserEmail() - set author = username_" <"_email_">" - do ..RunGitWithArgs(.errStream, .outStream, "commit", "--author", author, "-m", Msg) - do ..PrintStreams(errStream, outStream) - $$$QuitOnError(..ClearUncommitted(uncommittedFilesWithAction)) - $$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(,,,1)) + + if ..CheckForUncommittedFiles() { + set uncommittedFilesWithAction = ##class(SourceControl.Git.Utils).UncommittedWithAction().%Get("user") + set username = ..GitUserName() + set email = ..GitUserEmail() + set author = username_" <"_email_">" + do ..RunGitWithArgs(.errStream, .outStream, "commit", "--author", author, "-m", Msg) + do ..PrintStreams(errStream, outStream) + $$$QuitOnError(..ClearUncommitted(uncommittedFilesWithAction)) + $$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(,,,1)) + } + quit $$$OK } +ClassMethod CheckForUncommittedFiles() As %Boolean +{ + set uncommittedFilesWithAction = ##class(SourceControl.Git.Utils).UncommittedWithAction().%Get("user") + set valInArr = uncommittedFilesWithAction.%Pop() + if valInArr = "" { + return 0 + } else { + quit 1 + } +} + /// Goes through all the added files and stages them ClassMethod StageAddedFiles() { @@ -400,14 +426,56 @@ ClassMethod StageAddedFiles() } /// Merges the files from the configured branch as part of the Sync operation -ClassMethod MergeDefaultRemoteBranch() +/// Returns true if this resulted in durable changes to the local git repo +ClassMethod MergeDefaultRemoteBranch(Output alert As %String = "") As %Boolean { + set rebased = 0 set settings = ##class(SourceControl.Git.Settings).%New() set defaultMergeBranch = settings.defaultMergeBranch if defaultMergeBranch '= "" { - do ..RunGitWithArgs(.errStream, .outStream, "rebase", defaultMergeBranch) + do ..RunGitWithArgs(.errStream, .outStream, "fetch", "origin", defaultMergeBranch_":"_defaultMergeBranch) do ..PrintStreams(errStream, outStream) + + do ..RunGitWithArgs(,.outStream, "rev-parse", defaultMergeBranch) + set startSha = outStream.ReadLine() + + // Start a transaction so code changes can be rolled back + set initTLevel = $TLevel + try { + TSTART + set code = ..RunGitWithArgs(.errStream, .outStream, "rebase", defaultMergeBranch) + if (code '= 0) { + $$$ThrowStatus($$$ERROR($$$GeneralError,"git rebase reported failure")) + } + set rebased = 1 + TCOMMIT + } catch e { + // "rebase" may throw an exception due to errors syncing to IRIS. In that case, roll back and keep going to abort the rebase. + write !,"Attempting to resolve differences in production definition..." + set resolver = ##class(SourceControl.Git.Util.ProductionConflictResolver).FromLog(outStream) + if resolver.resolved { + set rebased = 1 + TCOMMIT + write " success!" + } else { + write " unable to resolve - "_resolver.errorMessage + } + } + while $TLevel > initTLevel { + TROLLBACK 1 + } + if rebased { + do ##class(SourceControl.Git.Utils).RunGitWithArgs(.errStream, .outStream, "diff", startSha, "HEAD", "--name-status") + do ##class(SourceControl.Git.Utils).ParseDiffStream(outStream,,.finalFileSet) + do ##class(SourceControl.Git.Utils).SyncIrisWithRepoThroughDiff(.finalFileSet) + } else { + do ..RunGitCommand("rebase",.errStream, .outStream,"--abort") + do ..PrintStreams(errStream, outStream) + 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. See log for more details." + write !,alert,! + } } + quit rebased } /// Converts the DynamicArray into a list and calls the SourceControl.Git.Change RemoveUncommitted method on the newly created list @@ -424,7 +492,7 @@ ClassMethod ClearUncommitted(filesWithActions) As %Status quit $$$OK } -ClassMethod Sync(Msg As %String) As %Status +ClassMethod Sync(Msg As %String, Output alert As %String) As %Status { write !, "Syncing local repository...", ! do ..StageAddedFiles() @@ -435,20 +503,24 @@ ClassMethod Sync(Msg As %String) As %Status do ..Fetch() do ..Pull() do ..SyncCommit(Msg) - do ..Push() - do ..MergeDefaultRemoteBranch() - do ..Push() - + do ..Push(,1) + if ..MergeDefaultRemoteBranch(.alert) { + do ..Push(,1) + } } - quit $$$OK } -ClassMethod Push(remote As %String = "origin") As %Status +ClassMethod Push(remote As %String = "origin", force As %Boolean = 0) As %Status { do ##class(SourceControl.Git.Utils).RunGitCommandWithInput("branch",,.errStream,.outstream,"--show-current") set branchName = outstream.ReadLine(outstream.Size) - do ..RunGitWithArgs(.errStream, .outStream, "push", remote, branchName) + if (force) { + set args($i(args)) = "--force" + } + set args($i(args)) = remote + set args($i(args)) = branchName + do ..RunGitWithArgs(.errStream, .outStream, "push", args...) do ..PrintStreams(errStream, outStream) quit $$$OK } @@ -1231,6 +1303,7 @@ ClassMethod ImportCSPFile(InternalName As %String) As %Status ClassMethod ListItemsInFiles(ByRef itemList, ByRef err) As %Status { #define DoNotLoad 1 + set res = $$$OK set mappingFileType = $order($$$SourceMapping("")) while (mappingFileType '= "") { @@ -1348,10 +1421,7 @@ ClassMethod ImportRoutines(force As %Boolean = 0) As %Status } } - set eventHandler = $classmethod(..PullEventClass(),"%New") - set eventHandler.LocalRoot = ..TempFolder() - merge eventHandler.ModifiedFiles = files - set sc = eventHandler.OnPull() + set sc = ##class(SourceControl.Git.PullEventHandler).ForModifications(.files) if $$$ISERR(sc) { set ec = $$$ADDSC(ec,sc) } @@ -1565,13 +1635,16 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O if (command = "checkout"){ set syncIrisWithDiff = 1 - set diffCompare = args(args) + if $data(args) && $data(args(args),diffCompare) { + // no-op + } } elseif (command = "merge") || (command = "rebase") || (command = "pull"){ set syncIrisWithCommand = 1 - set diffCompare = args(args) + if $data(args) && $data(args(args),diffCompare) { + // no-op + } } - for i=1:1:$get(args) { if ($data(args(i))) { set newArgs($increment(newArgs)) = args(i) @@ -1601,23 +1674,8 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O } do ..RunGitCommand("fetch", .errorStream, .outputStream) kill errorStream, outputStream - do ##class(SourceControl.Git.Utils).RunGitCommandWithInput("diff",,.errorStream,.outputStream, diffBase_".."_diffCompare, "--name-status") - while (outputStream.AtEnd = 0) { - set file = outputStream.ReadLine() - set modification = ##class(SourceControl.Git.Modification).%New() - set modification.changeType = $piece(file, $c(9), 1) - - set modification.externalName = $zstrip($piece(file, $c(9),2),"<W") - if (modification.changeType '= "A"){ - set modification.internalName = ##class(SourceControl.Git.Utils).NameToInternalName(modification.externalName,,0) - } - else { - set modification.internalName = "" - } - set files($increment(files)) = modification - write !, ?4, modification.changeType, ?4, modification.internalName, ?4 , modification.externalName - } - + do ##class(SourceControl.Git.Utils).RunGitCommandWithInput("diff",,.errorStream,.outputStream, diffBase_$Case(diffCompare,"":"",:"..")_diffCompare, "--name-status") + do ..ParseDiffStream(outputStream,,.files) } set outLog = ##class(%Library.File).TempFilename() @@ -1646,10 +1704,12 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O for stream=errStream,outStream { set stream.RemoveOnClose = 1 } - do ..PrintStreams(errStream, outStream) + if syncIrisWithDiff { + do ..PrintStreams(errStream, outStream) $$$ThrowOnError(..SyncIrisWithRepoThroughDiff(.files)) } elseif syncIrisWithCommand { + do ..PrintStreams(errStream, outStream) $$$ThrowOnError(..SyncIrisWithRepoThroughCommand(.outStream)) } quit returnCode @@ -1660,8 +1720,8 @@ ClassMethod SyncIrisWithRepoThroughCommand(ByRef outStream) As %Status set deletedFiles = "" set addedFiles = "" set files = "" + do outStream.Rewind() while (outStream.AtEnd = 0) { - set line = outStream.ReadLine() set lineStart = $piece(line, " ", 2) if (lineStart = "delete") || (lineStart = "create") { @@ -1684,7 +1744,6 @@ ClassMethod SyncIrisWithRepoThroughCommand(ByRef outStream) As %Status set deletedFiles = $extract(deletedFiles, 2, *) set addedFiles = $extract(addedFiles, 2, *) - if (deletedFiles '= ""){ set sc = ##class(SourceControl.Git.Utils).RemoveFromServerSideSourceControl(deletedFiles) } @@ -1693,10 +1752,36 @@ ClassMethod SyncIrisWithRepoThroughCommand(ByRef outStream) As %Status } do outStream.Rewind() - set event = $classmethod(..PullEventClass(),"%New") - set event.LocalRoot = ..TempFolder() - merge event.ModifiedFiles = files - quit event.OnPull() + quit ##class(SourceControl.Git.PullEventHandler).ForModifications(.files) +} + +ClassMethod ParseDiffStream(stream As %Stream.Object, verbose As %Boolean = 1, Output files) +{ + kill files + while (stream.AtEnd = 0) { + set file = stream.ReadLine() + set modification = ##class(SourceControl.Git.Modification).%New() + set modification.changeType = $piece(file, $c(9), 1) + + set modification.externalName = $zstrip($piece(file, $c(9), 2),"<W") + if $extract(modification.changeType) = "R" { + set modification.changeType = "D" + set modification.internalName = ##class(SourceControl.Git.Utils).NameToInternalName(modification.externalName,,0) + set files($increment(files)) = modification + set modification = ##class(SourceControl.Git.Modification).%New() + set modification.changeType = "A" + set modification.internalName = "" + set modification.externalName = $zstrip($piece(file, $c(9), 3),"<W") + } elseif (modification.changeType '= "A"){ + set modification.internalName = ##class(SourceControl.Git.Utils).NameToInternalName(modification.externalName,,0) + } else { + set modification.internalName = "" + } + set files($increment(files)) = modification + if verbose { + write !, " ", modification.changeType, " ", modification.internalName, " ", modification.externalName + } + } } ClassMethod SyncIrisWithRepoThroughDiff(ByRef files) As %Status @@ -1704,7 +1789,7 @@ ClassMethod SyncIrisWithRepoThroughDiff(ByRef files) As %Status set key = $order(files("")) set deletedFiles = "" set addedFiles = "" - while(key '= "") { + while (key '= "") { set modification = files(key) if (modification.changeType = "D"){ if (modification.internalName '= "") { @@ -1715,7 +1800,6 @@ ClassMethod SyncIrisWithRepoThroughDiff(ByRef files) As %Status if (modification.internalName '= "") { set addedFiles = addedFiles_","_modification.internalName set files(key) = modification - } } set key = $order(files(key)) @@ -1731,10 +1815,7 @@ ClassMethod SyncIrisWithRepoThroughDiff(ByRef files) As %Status set sc = ##class(SourceControl.Git.Utils).AddToServerSideSourceControl(addedFiles) } - set event = $classmethod(..PullEventClass(),"%New") - set event.LocalRoot = ..TempFolder() - merge event.ModifiedFiles = files - quit event.OnPull() + quit ##class(SourceControl.Git.PullEventHandler).ForModifications(.files) } ClassMethod GenerateCommitMessageFromFiles(filesWithActions) As %String @@ -2383,4 +2464,3 @@ ClassMethod BaselineExport(pCommitMessage = "", pPushToRemote = "") As %Status } } - diff --git a/cls/_zpkg/isc/sc/git/Socket.cls b/cls/_zpkg/isc/sc/git/Socket.cls index 2bc20cdf..cbeb99f3 100644 --- a/cls/_zpkg/isc/sc/git/Socket.cls +++ b/cls/_zpkg/isc/sc/git/Socket.cls @@ -194,3 +194,4 @@ Method SendJSON(pObject As %DynamicAbstractObject) } } + diff --git a/test/UnitTest/SourceControl/Git/ProductionConflictResolve.cls b/test/UnitTest/SourceControl/Git/ProductionConflictResolve.cls new file mode 100644 index 00000000..5e34ae4c --- /dev/null +++ b/test/UnitTest/SourceControl/Git/ProductionConflictResolve.cls @@ -0,0 +1,141 @@ +Class UnitTest.SourceControl.Git.ProductionConflictResolve Extends %UnitTest.TestCase +{ + +Method TestResolve() +{ + Set file = ##class(%Stream.FileCharacter).%New() + Set file.RemoveOnClose = 1 + Set xdata = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(),"SampleFile1",,.sc) + While 'xdata.Data.AtEnd { + Do file.WriteLine(xdata.Data.ReadLine()) + } + $$$ThrowOnError(file.%Save()) + + Set resolved = ##class(%Stream.FileCharacter).%New() + Set resolved.RemoveOnClose = 1 + Set xdata = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(),"ResolvedFile1",,.sc) + While 'xdata.Data.AtEnd { + Do resolved.WriteLine(xdata.Data.ReadLine()) + } + $$$ThrowOnError(resolved.%Save()) + + Do ##class(SourceControl.Git.Util.ProductionConflictResolver).ResolveStream(file) + + Do $$$AssertFilesSame(file.Filename,resolved.Filename) +} + +XData SampleFile1 [ MimeType = text/plain ] +{ +Class HCC.Connect.Production Extends Ens.Production +{ + +XData ProductionDefinition +{ +<Production Name="HCC.Connect.Production" LogGeneralTraceEvents="false"> + <Description>Health Connect Cloud Base Production</Description> + <ActorPoolSize>1</ActorPoolSize> + <Item Name="Ens.Activity.Operation.Local" Category="" ClassName="Ens.Activity.Operation.Local" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <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=""> + <Setting Target="Adapter" Name="IPAddress">bank</Setting> + </Item> + <Item Name="FeatureA Service" Category="" ClassName="EnsLib.HL7.Service.FTPService" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureA Process" Category="" ClassName="EnsLib.HL7.MsgRouter.RoutingEngine" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Host" Name="BusinessRuleName">HCC.Connect.FeatureAProcessRoutingRule</Setting> + </Item> + <Item Name="FeatureA Operation" Category="" ClassName="EnsLib.HL7.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureB" Category="" ClassName="EnsLib.HL7.Service.TCPService" PoolSize="1" Enabled="false" Foreground="false" Comment="Comment" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureBProcess" Category="" ClassName="EnsLib.HL7.MsgRouter.RoutingEngine" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Host" Name="BusinessRuleName">HCC.Connect.FeatureBProcessRoutingRule</Setting> + </Item> + <Item Name="FeatureB Operation" Category="" ClassName="EnsLib.HL7.Operation.FileOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="Comment 12" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureC Operation" Category="" ClassName="EnsLib.HL7.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Adapter" Name="IPAddress">127.0.0.1</Setting> + <Setting Target="Adapter" Name="Port">8080</Setting> + </Item> + <Item Name="FeatureG" Category="" ClassName="EnsLib.HL7.Service.TCPService" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureD" Category="" ClassName="EnsLib.FTP.PassthroughService" PoolSize="1" Enabled="false" Foreground="false" Comment="." LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureE" Category="" ClassName="EnsLib.EDI.X12.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Adapter" Name="IPAddress">1.4.3.5</Setting> + </Item> + <Item Name="FeatureH" Category="" ClassName="Ens.Activity.Operation.REST" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Adapter" Name="HTTPPort">12345</Setting> + <Setting Target="Adapter" Name="HTTPServer">localhost</Setting> + </Item> + <Item Name="Baz" Category="" ClassName="EnsLib.DICOM.Duplex.TCP" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> +<<<<<<< HEAD + <Item Name="Demo7" Category="" ClassName="EnsLib.CloudStorage.BusinessOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> +======= + <Item Name="Demo5" Category="" ClassName="EnsLib.AmazonCloudWatch.MetricAlarmOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> +>>>>>>> 607d1f6 (modified src/HCC/Connect/Production.cls add Demo5) + </Item> +</Production> +} + +} +} + +XData ResolvedFile1 [ MimeType = text/plain ] +{ +Class HCC.Connect.Production Extends Ens.Production +{ + +XData ProductionDefinition +{ +<Production Name="HCC.Connect.Production" LogGeneralTraceEvents="false"> + <Description>Health Connect Cloud Base Production</Description> + <ActorPoolSize>1</ActorPoolSize> + <Item Name="Ens.Activity.Operation.Local" Category="" ClassName="Ens.Activity.Operation.Local" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <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=""> + <Setting Target="Adapter" Name="IPAddress">bank</Setting> + </Item> + <Item Name="FeatureA Service" Category="" ClassName="EnsLib.HL7.Service.FTPService" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureA Process" Category="" ClassName="EnsLib.HL7.MsgRouter.RoutingEngine" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Host" Name="BusinessRuleName">HCC.Connect.FeatureAProcessRoutingRule</Setting> + </Item> + <Item Name="FeatureA Operation" Category="" ClassName="EnsLib.HL7.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureB" Category="" ClassName="EnsLib.HL7.Service.TCPService" PoolSize="1" Enabled="false" Foreground="false" Comment="Comment" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureBProcess" Category="" ClassName="EnsLib.HL7.MsgRouter.RoutingEngine" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Host" Name="BusinessRuleName">HCC.Connect.FeatureBProcessRoutingRule</Setting> + </Item> + <Item Name="FeatureB Operation" Category="" ClassName="EnsLib.HL7.Operation.FileOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="Comment 12" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureC Operation" Category="" ClassName="EnsLib.HL7.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Adapter" Name="IPAddress">127.0.0.1</Setting> + <Setting Target="Adapter" Name="Port">8080</Setting> + </Item> + <Item Name="FeatureG" Category="" ClassName="EnsLib.HL7.Service.TCPService" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureD" Category="" ClassName="EnsLib.FTP.PassthroughService" PoolSize="1" Enabled="false" Foreground="false" Comment="." LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="FeatureE" Category="" ClassName="EnsLib.EDI.X12.Operation.TCPOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Adapter" Name="IPAddress">1.4.3.5</Setting> + </Item> + <Item Name="FeatureH" Category="" ClassName="Ens.Activity.Operation.REST" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + <Setting Target="Adapter" Name="HTTPPort">12345</Setting> + <Setting Target="Adapter" Name="HTTPServer">localhost</Setting> + </Item> + <Item Name="Baz" Category="" ClassName="EnsLib.DICOM.Duplex.TCP" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="Demo7" Category="" ClassName="EnsLib.CloudStorage.BusinessOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> + <Item Name="Demo5" Category="" ClassName="EnsLib.AmazonCloudWatch.MetricAlarmOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule=""> + </Item> +</Production> +} + +} +} + +}