2024-03-20 00:36:10 -07:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.Text;
|
|
|
|
using System.Text.RegularExpressions;
|
2024-07-09 02:56:23 -07:00
|
|
|
|
2024-03-20 00:36:10 -07:00
|
|
|
using Avalonia.Threading;
|
|
|
|
|
|
|
|
namespace SourceGit.Commands
|
|
|
|
{
|
|
|
|
public partial class Command
|
|
|
|
{
|
|
|
|
public class CancelToken
|
|
|
|
{
|
|
|
|
public bool Requested { get; set; } = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public class ReadToEndResult
|
|
|
|
{
|
2024-08-27 00:35:10 -07:00
|
|
|
public bool IsSuccess { get; set; } = false;
|
|
|
|
public string StdOut { get; set; } = "";
|
|
|
|
public string StdErr { get; set; } = "";
|
2024-03-20 00:36:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 19:16:15 -07:00
|
|
|
public enum EditorType
|
|
|
|
{
|
|
|
|
None,
|
|
|
|
CoreEditor,
|
|
|
|
RebaseEditor,
|
|
|
|
}
|
|
|
|
|
2024-03-20 00:36:10 -07:00
|
|
|
public string Context { get; set; } = string.Empty;
|
|
|
|
public CancelToken Cancel { get; set; } = null;
|
|
|
|
public string WorkingDirectory { get; set; } = null;
|
2024-07-08 19:16:15 -07:00
|
|
|
public EditorType Editor { get; set; } = EditorType.CoreEditor; // Only used in Exec() mode
|
2024-07-09 03:13:15 -07:00
|
|
|
public string SSHKey { get; set; } = string.Empty;
|
2024-03-20 00:36:10 -07:00
|
|
|
public string Args { get; set; } = string.Empty;
|
|
|
|
public bool RaiseError { get; set; } = true;
|
|
|
|
public bool TraitErrorAsOutput { get; set; } = false;
|
|
|
|
|
|
|
|
public bool Exec()
|
|
|
|
{
|
|
|
|
var start = new ProcessStartInfo();
|
2024-04-05 22:14:22 -07:00
|
|
|
start.FileName = Native.OS.GitExecutable;
|
2024-09-24 00:53:36 -07:00
|
|
|
start.Arguments = "--no-pager -c core.quotepath=off -c credential.helper=manager ";
|
2024-03-20 00:36:10 -07:00
|
|
|
start.UseShellExecute = false;
|
|
|
|
start.CreateNoWindow = true;
|
|
|
|
start.RedirectStandardOutput = true;
|
|
|
|
start.RedirectStandardError = true;
|
|
|
|
start.StandardOutputEncoding = Encoding.UTF8;
|
|
|
|
start.StandardErrorEncoding = Encoding.UTF8;
|
|
|
|
|
2024-07-09 03:13:15 -07:00
|
|
|
// Force using this app as SSH askpass program
|
2024-07-14 09:30:31 -07:00
|
|
|
var selfExecFile = Process.GetCurrentProcess().MainModule!.FileName;
|
2024-07-09 20:57:02 -07:00
|
|
|
if (!OperatingSystem.IsLinux())
|
|
|
|
start.Environment.Add("DISPLAY", "required");
|
2024-07-09 19:47:43 -07:00
|
|
|
start.Environment.Add("SSH_ASKPASS", selfExecFile); // Can not use parameter here, because it invoked by SSH with `exec`
|
2024-07-09 03:13:15 -07:00
|
|
|
start.Environment.Add("SSH_ASKPASS_REQUIRE", "prefer");
|
2024-09-24 00:53:36 -07:00
|
|
|
start.Environment.Add("SOURCEGIT_LAUNCH_AS_ASKPASS", "TRUE");
|
2024-07-09 03:13:15 -07:00
|
|
|
|
|
|
|
// If an SSH private key was provided, sets the environment.
|
|
|
|
if (!string.IsNullOrEmpty(SSHKey))
|
2024-07-21 20:33:34 -07:00
|
|
|
start.Environment.Add("GIT_SSH_COMMAND", $"ssh -o StrictHostKeyChecking=accept-new -i '{SSHKey}'");
|
2024-07-09 03:13:15 -07:00
|
|
|
else
|
2024-09-18 02:19:55 -07:00
|
|
|
start.Environment.Add("GIT_SSH_COMMAND", $"ssh -o StrictHostKeyChecking=accept-new");
|
2024-07-09 03:13:15 -07:00
|
|
|
|
|
|
|
// Force using en_US.UTF-8 locale to avoid GCM crash
|
|
|
|
if (OperatingSystem.IsLinux())
|
|
|
|
start.Environment.Add("LANG", "en_US.UTF-8");
|
|
|
|
|
2024-09-24 19:35:07 -07:00
|
|
|
// Fix sometimes `LSEnvironment` not working on macOS
|
|
|
|
if (OperatingSystem.IsMacOS())
|
|
|
|
start.Environment.Add("PATH", "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin");
|
|
|
|
|
2024-07-09 03:13:15 -07:00
|
|
|
// Force using this app as git editor.
|
2024-07-08 19:16:15 -07:00
|
|
|
switch (Editor)
|
|
|
|
{
|
|
|
|
case EditorType.CoreEditor:
|
2024-07-09 03:13:15 -07:00
|
|
|
start.Arguments += $"-c core.editor=\"\\\"{selfExecFile}\\\" --core-editor\" ";
|
2024-07-08 19:16:15 -07:00
|
|
|
break;
|
|
|
|
case EditorType.RebaseEditor:
|
2024-07-09 03:13:15 -07:00
|
|
|
start.Arguments += $"-c core.editor=\"\\\"{selfExecFile}\\\" --rebase-message-editor\" -c sequence.editor=\"\\\"{selfExecFile}\\\" --rebase-todo-editor\" -c rebase.abbreviateCommands=true ";
|
2024-07-08 19:16:15 -07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
start.Arguments += "-c core.editor=true ";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append command args
|
|
|
|
start.Arguments += Args;
|
|
|
|
|
2024-07-09 03:13:15 -07:00
|
|
|
// Working directory
|
2024-03-31 01:54:29 -07:00
|
|
|
if (!string.IsNullOrEmpty(WorkingDirectory))
|
|
|
|
start.WorkingDirectory = WorkingDirectory;
|
2024-03-20 00:36:10 -07:00
|
|
|
|
|
|
|
var errs = new List<string>();
|
|
|
|
var proc = new Process() { StartInfo = start };
|
|
|
|
var isCancelled = false;
|
|
|
|
|
|
|
|
proc.OutputDataReceived += (_, e) =>
|
|
|
|
{
|
|
|
|
if (Cancel != null && Cancel.Requested)
|
|
|
|
{
|
|
|
|
isCancelled = true;
|
|
|
|
proc.CancelErrorRead();
|
|
|
|
proc.CancelOutputRead();
|
2024-03-31 01:54:29 -07:00
|
|
|
if (!proc.HasExited)
|
|
|
|
proc.Kill(true);
|
2024-03-20 00:36:10 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-31 01:54:29 -07:00
|
|
|
if (e.Data != null)
|
|
|
|
OnReadline(e.Data);
|
2024-03-20 00:36:10 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
proc.ErrorDataReceived += (_, e) =>
|
|
|
|
{
|
|
|
|
if (Cancel != null && Cancel.Requested)
|
|
|
|
{
|
|
|
|
isCancelled = true;
|
|
|
|
proc.CancelErrorRead();
|
|
|
|
proc.CancelOutputRead();
|
2024-03-31 01:54:29 -07:00
|
|
|
if (!proc.HasExited)
|
|
|
|
proc.Kill(true);
|
2024-03-20 00:36:10 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-31 01:54:29 -07:00
|
|
|
if (string.IsNullOrEmpty(e.Data))
|
|
|
|
return;
|
|
|
|
if (TraitErrorAsOutput)
|
|
|
|
OnReadline(e.Data);
|
2024-03-20 00:36:10 -07:00
|
|
|
|
|
|
|
// Ignore progress messages
|
2024-03-31 01:54:29 -07:00
|
|
|
if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal))
|
|
|
|
return;
|
|
|
|
if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal))
|
|
|
|
return;
|
|
|
|
if (e.Data.StartsWith("remote: Compressing objects:", StringComparison.Ordinal))
|
|
|
|
return;
|
|
|
|
if (e.Data.StartsWith("Filtering content:", StringComparison.Ordinal))
|
|
|
|
return;
|
2024-07-08 07:07:00 -07:00
|
|
|
if (REG_PROGRESS().IsMatch(e.Data))
|
2024-03-31 01:54:29 -07:00
|
|
|
return;
|
2024-03-20 00:36:10 -07:00
|
|
|
errs.Add(e.Data);
|
|
|
|
};
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
proc.Start();
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
if (RaiseError)
|
|
|
|
{
|
|
|
|
Dispatcher.UIThread.Invoke(() =>
|
|
|
|
{
|
|
|
|
App.RaiseException(Context, e.Message);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
proc.BeginOutputReadLine();
|
|
|
|
proc.BeginErrorReadLine();
|
|
|
|
proc.WaitForExit();
|
|
|
|
|
|
|
|
int exitCode = proc.ExitCode;
|
|
|
|
proc.Close();
|
|
|
|
|
|
|
|
if (!isCancelled && exitCode != 0 && errs.Count > 0)
|
|
|
|
{
|
|
|
|
if (RaiseError)
|
|
|
|
{
|
|
|
|
Dispatcher.UIThread.Invoke(() =>
|
|
|
|
{
|
|
|
|
App.RaiseException(Context, string.Join("\n", errs));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public ReadToEndResult ReadToEnd()
|
|
|
|
{
|
|
|
|
var start = new ProcessStartInfo();
|
2024-04-05 22:14:22 -07:00
|
|
|
start.FileName = Native.OS.GitExecutable;
|
2024-03-20 00:36:10 -07:00
|
|
|
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
|
|
|
|
start.UseShellExecute = false;
|
|
|
|
start.CreateNoWindow = true;
|
|
|
|
start.RedirectStandardOutput = true;
|
|
|
|
start.RedirectStandardError = true;
|
|
|
|
start.StandardOutputEncoding = Encoding.UTF8;
|
|
|
|
start.StandardErrorEncoding = Encoding.UTF8;
|
|
|
|
|
2024-03-31 01:54:29 -07:00
|
|
|
if (!string.IsNullOrEmpty(WorkingDirectory))
|
|
|
|
start.WorkingDirectory = WorkingDirectory;
|
2024-03-20 00:36:10 -07:00
|
|
|
|
|
|
|
var proc = new Process() { StartInfo = start };
|
|
|
|
try
|
|
|
|
{
|
|
|
|
proc.Start();
|
|
|
|
}
|
2024-08-27 00:35:10 -07:00
|
|
|
catch (Exception e)
|
2024-03-20 00:36:10 -07:00
|
|
|
{
|
|
|
|
return new ReadToEndResult()
|
|
|
|
{
|
|
|
|
IsSuccess = false,
|
|
|
|
StdOut = string.Empty,
|
2024-08-27 00:35:10 -07:00
|
|
|
StdErr = e.Message,
|
2024-03-20 00:36:10 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var rs = new ReadToEndResult()
|
|
|
|
{
|
|
|
|
StdOut = proc.StandardOutput.ReadToEnd(),
|
2024-08-27 00:35:10 -07:00
|
|
|
StdErr = proc.StandardError.ReadToEnd(),
|
2024-03-20 00:36:10 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
proc.WaitForExit();
|
|
|
|
rs.IsSuccess = proc.ExitCode == 0;
|
|
|
|
proc.Close();
|
|
|
|
|
|
|
|
return rs;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void OnReadline(string line) { }
|
|
|
|
|
|
|
|
[GeneratedRegex(@"\d+%")]
|
2024-07-08 07:07:00 -07:00
|
|
|
private static partial Regex REG_PROGRESS();
|
2024-03-20 00:36:10 -07:00
|
|
|
}
|
2024-03-31 01:54:29 -07:00
|
|
|
}
|