diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7f5dfc..2229d66f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed errors on production page when item settings need to be XML escaped (#667) - Fixed push button not appearing after commit (#654) - Fixed merge conflict resolution on stash popping (#531) - +- Fix "Max $ZF String" error when committing lots of files (#617) + ## [2.8.0] - 2024-12-06 ### Added diff --git a/cls/SourceControl/Git/Utils.cls b/cls/SourceControl/Git/Utils.cls index f8d751b7..a2dea3b5 100644 --- a/cls/SourceControl/Git/Utils.cls +++ b/cls/SourceControl/Git/Utils.cls @@ -3033,8 +3033,7 @@ ClassMethod SetConfiguredRemote(url) As %String quit output } -// Returns true if the current branch is the default merge branch and we are in basic mode - +/// Returns true if the current branch is the default merge branch and we are in basic mode ClassMethod InDefaultBranchBasicMode() As %Boolean { set basicMode = ..BasicMode() @@ -3077,4 +3076,100 @@ ClassMethod RunGitAndHandleMerge(command As %String, inFile As %String, Output r return $$$ERROR($$$GeneralError,"git reported failure") } +/// Runs the commit on the specified files (unstaging and restaging currently staged files) +ClassMethod RunGitCommandReStage(Output outStream, Output errStream, command As %String, ByRef fileList As %Library.DynamicArray, args...) As %Integer +{ + set sc = $$$OK + set tInitialTLevel = $TLevel + try { + LOCK +^GitCommit(42):5 + if '$Test { + $$$ThrowStatus($$$ERROR($$$GeneralError, "Couldn't grab a lock. Try again later")) + } + set tGitCommitLocked = 1 + + + TSTART + + // Unstage and save all currently staged files + set sc = ..GitUnstage(.unstaged) + + // Stage all files + set iterator = fileList.%GetIterator() + while iterator.%GetNext(.key, .value) { + do ..RunGitCommand("add",.err, .out, value) + } + + // Run the git command + set returnCode = ..RunGitWithArgs(.errStream, .outStream, command, args...) + + // Restage files + do ..GitStage(.unstaged) + + TCOMMIT + } catch e { + set sc = e.AsStatus() + } + + If tGitCommitLocked { + Lock -^GitCommit(42) + } + + While $TLevel > tInitialTLevel { + TROLLBACK 1 + } + + return returnCode +} + +ClassMethod GitStage(ByRef files As %Library.DynamicObject) As %Status +{ + set staged = files.%Get("staged") + set tracked = files.%Get("tracked") + + set iterator = staged.%GetIterator() + while iterator.%GetNext(.key, .value) { + do ..RunGitCommand("add",.errStream,.outStream, value) + } + + set iterator = tracked.%GetIterator() + while iterator.%GetNext(.key, .value) { + do ..RunGitCommand("add",.errStream, .outStream, value, "--intent-to-add") + } + return $$$OK +} + +/// Unstages all currently staged files and returns them in the array +ClassMethod GitUnstage(Output output As %Library.DynamicObject) As %Status +{ + set stagedList = [] + set trackedList = [] + + do ..RunGitCommandWithInput("status",, .errStream, .outStream, "--porcelain") + set line = outStream.ReadLine() + // Read through output + while (line'="") { + set staged = 0 + set tracked = 0 + set filename = $piece(line, " ", *-0) + if ($extract(line,1,1) '= " ") && ($extract(line,1,1) '= "?") { + set staged = 1 + } + elseif ($extract(line,2,2) = "A") { + set tracked = 1 + } + if staged { + do stagedList.%Push(filename) + do ..RunGitCommand("reset", .errStream, .out, filename) + } elseif tracked { + do trackedList.%Push(filename) + do ..RunGitCommand("reset",.errStream,.out, filename) + } + set line = outStream.ReadLine() + } + + set output = {"staged": (stagedList), "tracked": (trackedList)} + return $$$OK +} + } diff --git a/cls/SourceControl/Git/WebUIDriver.cls b/cls/SourceControl/Git/WebUIDriver.cls index d8c1d3ff..a7d808cc 100644 --- a/cls/SourceControl/Git/WebUIDriver.cls +++ b/cls/SourceControl/Git/WebUIDriver.cls @@ -102,7 +102,7 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out do %request.Set("EXPIRES",0) do ##class(%CSP.StreamServer).OnPreHTTP() // Need to call this to set headers properly set %stream = 1 // Leak this to webuidriver.csp - } elseif $match(pathStart,"git-command|git|dirname|hostname|viewonly|discarded-states|restore-discarded|contexts|create-branch") { + } elseif $match(pathStart,"git-command|git|dirname|hostname|viewonly|discarded-states|restore-discarded|contexts|create-branch|git-command-unstage") { if (%request.Method = "GET") { set %data = ##class(%Stream.TmpCharacter).%New() // Things not handled from Python backend: @@ -220,36 +220,15 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out } // Want to invoke merge conflict autoresolver in case of issues set returnCode = ##class(SourceControl.Git.Utils).RunGitCommandWithInput("-c",inFile,.errStream,.outStream,gitArgs...) - - set %data = ##class(%Stream.TmpCharacter).%New() - set changeTerminators = (%data.LineTerminator '= $char(13,10)) - set %data.LineTerminator = $char(13,10) // For the CSPGateway. - do outStream.Rewind() - while 'outStream.AtEnd { - do %data.WriteLine(outStream.ReadLine()) - } - set nLines = 0 - do errStream.Rewind() - while 'errStream.AtEnd { - do %data.WriteLine(errStream.ReadLine()) - set:changeTerminators nLines = nLines + 1 - } + do ..ConvertGitOutput(.outStream,.errStream,returnCode,.%data) - do %data.WriteLine("Git-Stderr-Length: " _ (errStream.Size + nLines)) - do %data.Write("Git-Return-Code: " _ returnCode) // No ending newline expected - do %data.Rewind() if '$listfind(readOnlyCommands,baseCommand) { do ##class(SourceControl.Git.Change).RefreshUncommitted(,,,1) } set handled = 1 } elseif (pathStart = "git-command") { - set stringBody = "" - while '%request.Content.AtEnd { - set stringBody = stringBody _ %request.Content.Read() - } - set stringBody = $zconvert(stringBody,"I","UTF8") - set requestBody = ##class(%Library.DynamicObject).%FromJSON(stringBody) + do ..UnpackRequest(.%request, .requestBody) set command = requestBody.command set gitCmd = command.%Get(0) @@ -309,22 +288,7 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out do %data.Rewind() } else { set returnCode = ##class(SourceControl.Git.Utils).RunGitCommandWithInput("-c", inFile, .errStream, .outStream, argsArr...) - set %data = ##class(%Stream.TmpCharacter).%New() - set changeTerminators = (%data.LineTerminator '= $char(13,10)) - set %data.LineTerminator = $char(13,10) // For the CSPGateway. - while 'outStream.AtEnd { - do %data.WriteLine(outStream.ReadLine()) - } - - set nLines = 0 - while 'errStream.AtEnd { - do %data.WriteLine(errStream.ReadLine()) - set:changeTerminators nLines = nLines + 1 - } - - do %data.WriteLine("Git-Stderr-Length: " _ (errStream.Size + nLines)) - do %data.Write("Git-Return-Code: " _ returnCode) // No ending newline expected - do %data.Rewind() + do ..ConvertGitOutput(.outStream,.errStream,returnCode,.%data) } set handled = 1 @@ -368,6 +332,41 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out } set handled = 1 } + } elseif (pathStart = "git-command-unstage") { + do ..UnpackRequest(.%request, .requestBody) + + set flags = requestBody.flags + if (flags '= "") { + set flaglist = $listfromstring(flags,",") + for i=1:1:$listlength(flaglist) { + set flag = $listget(flaglist,i) + set args($increment(args)) = flag + } + } + set message = requestBody.message + if (message '= "") { + set args($increment(args)) = "-m" + set args($increment(args)) = message + } + set details = requestBody.details + if (details '= "") { + set args($increment(args)) = "-m" + set args($increment(args)) = details + } + set command = requestBody.command + + // Extract Files + set fileList = [] + set files = requestBody.files + set iter = files.%GetIterator() + while iter.%GetNext(,.value) { + do fileList.%Push(value) + } + + set returnCode = ##class(SourceControl.Git.Utils).RunGitCommandReStage(.outStream,.errStream,command,.fileList,args...) + + do ..ConvertGitOutput(.outStream,.errStream,returnCode,.%data) + set handled = 1 } } } @@ -465,4 +464,38 @@ ClassMethod GetRemote() As %Library.DynamicObject quit {"remote": (remote)} } +ClassMethod UnpackRequest(ByRef %request As %CSP.Request, Output obj As %Library.DynamicObject) As %Status +{ + set stringBody = "" + while '%request.Content.AtEnd { + set stringBody = stringBody _ %request.Content.Read() + } + set stringBody = $zconvert(stringBody,"I","UTF8") + set obj = ##class(%Library.DynamicObject).%FromJSON(stringBody) + return $$$OK +} + +ClassMethod ConvertGitOutput(ByRef outStream, ByRef errStream, returnCode As %String, Output %data) As %Status +{ + set %data = ##class(%Stream.TmpCharacter).%New() + set changeTerminators = (%data.LineTerminator '= $char(13,10)) + set %data.LineTerminator = $char(13,10) // For the CSPGateway. + do outStream.Rewind() + while 'outStream.AtEnd { + do %data.WriteLine(outStream.ReadLine()) + } + + set nLines = 0 + do errStream.Rewind() + while 'errStream.AtEnd { + do %data.WriteLine(errStream.ReadLine()) + set:changeTerminators nLines = nLines + 1 + } + + do %data.WriteLine("Git-Stderr-Length: " _ (errStream.Size + nLines)) + do %data.Write("Git-Return-Code: " _ returnCode) // No ending newline expected + do %data.Rewind() + return $$$OK +} + } diff --git a/git-webui/release/share/git-webui/webui/js/git-webui.js b/git-webui/release/share/git-webui/webui/js/git-webui.js index 05876cec..7c321035 100644 --- a/git-webui/release/share/git-webui/webui/js/git-webui.js +++ b/git-webui/release/share/git-webui/webui/js/git-webui.js @@ -146,6 +146,45 @@ webui.parseGitResponse = function(data) { }; } +webui.processGitResponse = function(data, command, callback) { + var result = webui.parseGitResponse(data); + var rcode = result.rcode; + var output = result.output; + var message = result.message; + + if (rcode == 0) { + if (callback) { + callback(output); + } + // Return code is 0 but there is stderr output: this is a warning message + if (message.length > 0) { + if(warningCallback) { + warningCallback(message); + } else { + webui.showWarning(message); + } + } + } else { + var displayMessage = "" + if(output.length > 0){ + displayMessage += (output+"\n"); + } + if(message.length > 0){ + displayMessage += message; + } + if(displayMessage.length > 0){ + if(displayMessage.indexOf("self.document.Login") != -1){ + location.reload(); + return false; + } + webui.showError(displayMessage); + //} + } else { + webui.showError("The command
"+command.join(" ")+"
failed because of an unknown reason. Returned response: \n\n"+data) + } + } +} + webui.git_command = function(command, callback) { $.ajax({ url: "git-command", @@ -155,45 +194,7 @@ webui.git_command = function(command, callback) { command: command }), success: function(data) { - var result = webui.parseGitResponse(data); - var rcode = result.rcode; - var output = result.output; - var message = result.message; - - if (rcode == 0) { - if (callback) { - callback(output); - } - // Return code is 0 but there is stderr output: this is a warning message - if (message.length > 0) { - if(warningCallback) { - warningCallback(message); - } else { - webui.showWarning(message); - } - } - } else { - var displayMessage = "" - if(output.length > 0){ - displayMessage += (output+"\n"); - } - if(message.length > 0){ - displayMessage += message; - } - if(displayMessage.length > 0){ - // if(errorCallback) { - // errorCallback(displayMessage); - // } else{ - if(displayMessage.indexOf("self.document.Login") != -1){ - location.reload(); - return false; - } - webui.showError(displayMessage); - //} - } else { - webui.showError("The command
"+command.join(" ")+"
failed because of an unknown reason. Returned response: \n\n"+data) - } - } + webui.processGitResponse(data, command, callback); }, error: function(data) { var trimmedData = data.replace(/(\r\n)/gm, "\n"); @@ -3021,18 +3022,51 @@ webui.NewChangedFilesView = function(workspaceView) { } self.amend = function(message, details) { - if (self.commitMsgEmpty()) { - webui.git_command(["add"].concat(selectedItems)); - webui.git_command(['commit', '--amend', '--no-edit', '--'].concat(selectedItems), function(output) { - webui.showSuccess(output); - workspaceView.update(); + $.ajax({ + url: "git-command-unstage", + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + command: "commit", + message: message, + details: details, + flags: "--amend,--no-edit", + files: selectedItems + }), + success: function(data) { + webui.processGitResponse(data, ["commit", "--amend","--no-edit","-m",message,"-m",details], function(output) { + webui.showSuccess(output); + workspaceView.update(); + }); + }, + error: function(data) { + var trimmedData = data.replace(/(\r\n)/gm, "\n"); + webui.showError(trimmedData); + }, }); } else if (selectedItems.length != 0) { - webui.git_command(["add"].concat(selectedItems)); - webui.git_command(['commit', '--amend', '-m', message, '-m', details, '--'].concat(selectedItems), function(output) { - webui.showSuccess(output); - workspaceView.update(); + $.ajax({ + url: "git-command-unstage", + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + command: "commit", + message: message, + details: details, + flags: "--amend", + files:selectedItems + }), + success: function(data) { + webui.processGitResponse(data, ["commit","--amend","-m",message,"-m",details], function(output) { + webui.showSuccess(output); + workspaceView.update(); + }); + }, + error: function(data) { + var trimmedData = data.replace(/(\r\n)/gm, "\n"); + webui.showError(trimmedData); + }, }); } else { webui.git_command(['commit', '--amend', '--allow-empty', '-m', message, '-m', details], function(output) { @@ -3040,15 +3074,30 @@ webui.NewChangedFilesView = function(workspaceView) { workspaceView.update(); }); } - - } self.commit = function(message, details) { - webui.git_command(["add"].concat(selectedItems)); - webui.git_command(['commit', '-m', message, '-m', details, '--'].concat(selectedItems), function(output) { - webui.showSuccess(output); - workspaceView.update(); + $.ajax({ + url: "git-command-unstage", + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + command: "commit", + message: message, + details: details, + files: selectedItems + }), + success: function(data) { + webui.processGitResponse(data, ["commit","-m",message,"-m",details], function(output) { + webui.showSuccess(output); + workspaceView.update(); + }); + + }, + error: function(data) { + var trimmedData = data.replace(/(\r\n)/gm, "\n"); + webui.showError(trimmedData); + }, }); } diff --git a/git-webui/src/share/git-webui/webui/js/git-webui.js b/git-webui/src/share/git-webui/webui/js/git-webui.js index 05876cec..7c321035 100644 --- a/git-webui/src/share/git-webui/webui/js/git-webui.js +++ b/git-webui/src/share/git-webui/webui/js/git-webui.js @@ -146,6 +146,45 @@ webui.parseGitResponse = function(data) { }; } +webui.processGitResponse = function(data, command, callback) { + var result = webui.parseGitResponse(data); + var rcode = result.rcode; + var output = result.output; + var message = result.message; + + if (rcode == 0) { + if (callback) { + callback(output); + } + // Return code is 0 but there is stderr output: this is a warning message + if (message.length > 0) { + if(warningCallback) { + warningCallback(message); + } else { + webui.showWarning(message); + } + } + } else { + var displayMessage = "" + if(output.length > 0){ + displayMessage += (output+"\n"); + } + if(message.length > 0){ + displayMessage += message; + } + if(displayMessage.length > 0){ + if(displayMessage.indexOf("self.document.Login") != -1){ + location.reload(); + return false; + } + webui.showError(displayMessage); + //} + } else { + webui.showError("The command
"+command.join(" ")+"
failed because of an unknown reason. Returned response: \n\n"+data) + } + } +} + webui.git_command = function(command, callback) { $.ajax({ url: "git-command", @@ -155,45 +194,7 @@ webui.git_command = function(command, callback) { command: command }), success: function(data) { - var result = webui.parseGitResponse(data); - var rcode = result.rcode; - var output = result.output; - var message = result.message; - - if (rcode == 0) { - if (callback) { - callback(output); - } - // Return code is 0 but there is stderr output: this is a warning message - if (message.length > 0) { - if(warningCallback) { - warningCallback(message); - } else { - webui.showWarning(message); - } - } - } else { - var displayMessage = "" - if(output.length > 0){ - displayMessage += (output+"\n"); - } - if(message.length > 0){ - displayMessage += message; - } - if(displayMessage.length > 0){ - // if(errorCallback) { - // errorCallback(displayMessage); - // } else{ - if(displayMessage.indexOf("self.document.Login") != -1){ - location.reload(); - return false; - } - webui.showError(displayMessage); - //} - } else { - webui.showError("The command
"+command.join(" ")+"
failed because of an unknown reason. Returned response: \n\n"+data) - } - } + webui.processGitResponse(data, command, callback); }, error: function(data) { var trimmedData = data.replace(/(\r\n)/gm, "\n"); @@ -3021,18 +3022,51 @@ webui.NewChangedFilesView = function(workspaceView) { } self.amend = function(message, details) { - if (self.commitMsgEmpty()) { - webui.git_command(["add"].concat(selectedItems)); - webui.git_command(['commit', '--amend', '--no-edit', '--'].concat(selectedItems), function(output) { - webui.showSuccess(output); - workspaceView.update(); + $.ajax({ + url: "git-command-unstage", + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + command: "commit", + message: message, + details: details, + flags: "--amend,--no-edit", + files: selectedItems + }), + success: function(data) { + webui.processGitResponse(data, ["commit", "--amend","--no-edit","-m",message,"-m",details], function(output) { + webui.showSuccess(output); + workspaceView.update(); + }); + }, + error: function(data) { + var trimmedData = data.replace(/(\r\n)/gm, "\n"); + webui.showError(trimmedData); + }, }); } else if (selectedItems.length != 0) { - webui.git_command(["add"].concat(selectedItems)); - webui.git_command(['commit', '--amend', '-m', message, '-m', details, '--'].concat(selectedItems), function(output) { - webui.showSuccess(output); - workspaceView.update(); + $.ajax({ + url: "git-command-unstage", + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + command: "commit", + message: message, + details: details, + flags: "--amend", + files:selectedItems + }), + success: function(data) { + webui.processGitResponse(data, ["commit","--amend","-m",message,"-m",details], function(output) { + webui.showSuccess(output); + workspaceView.update(); + }); + }, + error: function(data) { + var trimmedData = data.replace(/(\r\n)/gm, "\n"); + webui.showError(trimmedData); + }, }); } else { webui.git_command(['commit', '--amend', '--allow-empty', '-m', message, '-m', details], function(output) { @@ -3040,15 +3074,30 @@ webui.NewChangedFilesView = function(workspaceView) { workspaceView.update(); }); } - - } self.commit = function(message, details) { - webui.git_command(["add"].concat(selectedItems)); - webui.git_command(['commit', '-m', message, '-m', details, '--'].concat(selectedItems), function(output) { - webui.showSuccess(output); - workspaceView.update(); + $.ajax({ + url: "git-command-unstage", + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + command: "commit", + message: message, + details: details, + files: selectedItems + }), + success: function(data) { + webui.processGitResponse(data, ["commit","-m",message,"-m",details], function(output) { + webui.showSuccess(output); + workspaceView.update(); + }); + + }, + error: function(data) { + var trimmedData = data.replace(/(\r\n)/gm, "\n"); + webui.showError(trimmedData); + }, }); }