diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf0d744..3e84ad42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.4.1] - Unreleased +### Added +- New API endpoint that accepts git commands as array instead of string (#437) + ### Fixed - Fixed JS errors in Studio on certain operations (#416) - Add menu option disabled for unsaved files (#420) diff --git a/cls/SourceControl/Git/WebUIDriver.cls b/cls/SourceControl/Git/WebUIDriver.cls index e271c9bf..f6c29214 100644 --- a/cls/SourceControl/Git/WebUIDriver.cls +++ b/cls/SourceControl/Git/WebUIDriver.cls @@ -27,7 +27,7 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out if $isobject($get(responseJSON)) { do responseJSON.%ToJSON(%data) } - } elseif $match(pathStart,"git|dirname|hostname|viewonly") { + } elseif $match(pathStart,"git-command|git|dirname|hostname|viewonly") { if (%request.Method = "GET") { set %data = ##class(%Stream.TmpCharacter).%New() // Things not handled from Python backend: @@ -159,6 +159,40 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out do ##class(SourceControl.Git.Change).RefreshUncommitted(,,,1) } set handled = 1 + } elseif (pathStart = "git-command") { + set requestBody = ##class(%Library.DynamicObject).%FromJSON(%request.Content) + set command = requestBody.command + + set argsArr = "" + set argsArr($increment(argsArr)) = "color.ui=true" + set iterator = command.%GetIterator() + while iterator.%GetNext(,.value) { + set argsArr($increment(argsArr)) = value + } + + set inFile = "" + + 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 + } + + // Need to write out two lines or we get an infinite loop in JavaScript... + do %data.WriteLine() + do %data.WriteLine() + do %data.WriteLine("Git-Stderr-Length: " _ (errStream.Size + nLines)) + do %data.Write("Git-Return-Code: " _ returnCode) // No ending newline expected + do %data.Rewind() + set handled = 1 } } } 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 e36f87b6..bb0f1531 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 @@ -110,6 +110,89 @@ webui.showWarning = function(message) { '').appendTo(messageBox); } +webui.git_command = function(command, callback) { + $.ajax({ + url: "git-command", + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + command: command + }), + success: function(data) { + // Convention : last lines are footer meta data like headers. An empty line marks the start if the footers + var footers = {}; + var fIndex = data.length; + while (true) { + var oldFIndex = fIndex; + fIndex = data.lastIndexOf("\r\n", fIndex - 1); + var line = data.substring(fIndex + 2, oldFIndex); + if (line.length > 0) { + var footer = line.split(": "); + footers[footer[0]] = footer[1]; + } else { + break; + } + } + // Trims the the data variable to remove the footers extracted in the loop. + // Windows adds \r\n for every line break but the Git-Stderr-Length variable, + // counts it as only one character, throwing off the message length. + var trimmedData = data.substring(0, fIndex).replace(/(\r\n)/gm, "\n"); + var fIndex = trimmedData.length + + var messageLength = parseInt(footers["Git-Stderr-Length"]); + var messageStartIndex = fIndex-messageLength; + var message = trimmedData.substring(messageStartIndex, fIndex); + + var output = trimmedData.substring(0, messageStartIndex); + var rcode = parseInt(footers["Git-Return-Code"]); + + 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) + } + } + }, + error: function(data) { + var trimmedData = data.substring(0, fIndex).replace(/(\r\n)/gm, "\n"); + var fIndex = trimmedData.length + + var messageLength = parseInt(footers["Git-Stderr-Length"]); + var messageStartIndex = fIndex-messageLength; + var message = trimmedData.substring(messageStartIndex, fIndex); + webui.showError(message); + }, + }); +} + webui.git = function(cmd, arg1, arg2, arg3, arg4) { // cmd = git command line arguments // other arguments = optional stdin content and a callback function. @@ -2551,42 +2634,33 @@ webui.NewChangedFilesView = function(workspaceView) { self.amend = function(message, details) { var selectedFilesAsString = selectedItems.join(" "); - message = self.doubleQuotesToSingleQuotes(message); - details = self.doubleQuotesToSingleQuotes(details); if (self.commitMsgEmpty()) { webui.git("add " + selectedFilesAsString); - webui.git("commit --amend --no-edit -- " + selectedFilesAsString, function(output) { + webui.git_command(['commit', '--amend', '--no-edit', '--'].concat(selectedItems), function(output) { webui.showSuccess(output); workspaceView.update(); - }) + }); } else if (selectedItems.length != 0) { webui.git("add " + selectedFilesAsString); - webui.git('commit --amend -m "' + message + '" -m "' + details + '" -- ' + selectedFilesAsString, function(output) { + webui.git_command(['commit', '--amend', '-m', message, '-m', 'details', '--'].concat(selectedItems), function(output) { webui.showSuccess(output); workspaceView.update(); - }) + }); } else { - webui.git('commit --amend --allow-empty -m "' + message + '" -m "' + details + '"', function(output) { + webui.git_command(['commit', '--amend', '--allow-empty', '-m', message, '-m', details], function(output) { webui.showSuccess(output); workspaceView.update(); - }) + }); } } - self.doubleQuotesToSingleQuotes = function(string) { - return string.replace(/"/g, "'"); - } - self.commit = function(message, details) { - var selectedFilesAsString = selectedItems.join(" "); - message = self.doubleQuotesToSingleQuotes(message); - details = self.doubleQuotesToSingleQuotes(details); - - webui.git("add " + selectedFilesAsString); - webui.git('commit -m "' + message + '" -m "' + details + '" -- ' + selectedFilesAsString, function(output) { + // var selectedFilesAsString = selectedItems.join(" "); + webui.git_command(["add"].concat(selectedItems)); + webui.git_command(['commit', '-m', message, '-m', details, '--'].concat(selectedItems), function(output) { webui.showSuccess(output); workspaceView.update(); }); 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 e36f87b6..a3df96a5 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 @@ -110,6 +110,89 @@ webui.showWarning = function(message) { '').appendTo(messageBox); } +webui.git_command = function(command, callback) { + $.ajax({ + url: "git-command", + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + command: command + }), + success: function(data) { + // Convention : last lines are footer meta data like headers. An empty line marks the start if the footers + var footers = {}; + var fIndex = data.length; + while (true) { + var oldFIndex = fIndex; + fIndex = data.lastIndexOf("\r\n", fIndex - 1); + var line = data.substring(fIndex + 2, oldFIndex); + if (line.length > 0) { + var footer = line.split(": "); + footers[footer[0]] = footer[1]; + } else { + break; + } + } + // Trims the the data variable to remove the footers extracted in the loop. + // Windows adds \r\n for every line break but the Git-Stderr-Length variable, + // counts it as only one character, throwing off the message length. + var trimmedData = data.substring(0, fIndex).replace(/(\r\n)/gm, "\n"); + var fIndex = trimmedData.length + + var messageLength = parseInt(footers["Git-Stderr-Length"]); + var messageStartIndex = fIndex-messageLength; + var message = trimmedData.substring(messageStartIndex, fIndex); + + var output = trimmedData.substring(0, messageStartIndex); + var rcode = parseInt(footers["Git-Return-Code"]); + + 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) + } + } + }, + error: function(data) { + var trimmedData = data.substring(0, fIndex).replace(/(\r\n)/gm, "\n"); + var fIndex = trimmedData.length + + var messageLength = parseInt(footers["Git-Stderr-Length"]); + var messageStartIndex = fIndex-messageLength; + var message = trimmedData.substring(messageStartIndex, fIndex); + webui.showError(message); + }, + }); +} + webui.git = function(cmd, arg1, arg2, arg3, arg4) { // cmd = git command line arguments // other arguments = optional stdin content and a callback function. @@ -2550,43 +2633,32 @@ webui.NewChangedFilesView = function(workspaceView) { } self.amend = function(message, details) { - var selectedFilesAsString = selectedItems.join(" "); - message = self.doubleQuotesToSingleQuotes(message); - details = self.doubleQuotesToSingleQuotes(details); if (self.commitMsgEmpty()) { - webui.git("add " + selectedFilesAsString); - webui.git("commit --amend --no-edit -- " + selectedFilesAsString, function(output) { + webui.git_command(["add"].concat(selectedItems)); + webui.git_command(['commit', '--amend', '--no-edit', '--'].concat(selectedItems), function(output) { webui.showSuccess(output); workspaceView.update(); - }) + }); } else if (selectedItems.length != 0) { - webui.git("add " + selectedFilesAsString); - webui.git('commit --amend -m "' + message + '" -m "' + details + '" -- ' + selectedFilesAsString, function(output) { + webui.git_command(["add"].concat(selectedItems)); + webui.git_command(['commit', '--amend', '-m', message, '-m', 'details', '--'].concat(selectedItems), function(output) { webui.showSuccess(output); workspaceView.update(); - }) + }); } else { - webui.git('commit --amend --allow-empty -m "' + message + '" -m "' + details + '"', function(output) { + webui.git_command(['commit', '--amend', '--allow-empty', '-m', message, '-m', details], function(output) { webui.showSuccess(output); workspaceView.update(); - }) + }); } } - self.doubleQuotesToSingleQuotes = function(string) { - return string.replace(/"/g, "'"); - } - self.commit = function(message, details) { - var selectedFilesAsString = selectedItems.join(" "); - message = self.doubleQuotesToSingleQuotes(message); - details = self.doubleQuotesToSingleQuotes(details); - - webui.git("add " + selectedFilesAsString); - webui.git('commit -m "' + message + '" -m "' + details + '" -- ' + selectedFilesAsString, function(output) { + webui.git_command(["add"].concat(selectedItems)); + webui.git_command(['commit', '-m', message, '-m', details, '--'].concat(selectedItems), function(output) { webui.showSuccess(output); workspaceView.update(); });