2024-02-05 23:08:37 -08:00
|
|
|
using Avalonia.Threading;
|
2022-05-20 01:00:25 -07:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.Text;
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
|
|
namespace SourceGit.Commands {
|
|
|
|
public class Command {
|
2024-02-05 23:08:37 -08:00
|
|
|
public class CancelToken {
|
|
|
|
public bool Requested { get; set; } = false;
|
|
|
|
}
|
2022-05-20 01:00:25 -07:00
|
|
|
|
|
|
|
public class ReadToEndResult {
|
|
|
|
public bool IsSuccess { get; set; }
|
2024-02-05 23:08:37 -08:00
|
|
|
public string StdOut { get; set; }
|
|
|
|
public string StdErr { get; set; }
|
2022-05-20 01:00:25 -07:00
|
|
|
}
|
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
public string Context { get; set; } = string.Empty;
|
|
|
|
public CancelToken Cancel { get; set; } = null;
|
|
|
|
public string WorkingDirectory { get; set; } = null;
|
|
|
|
public string Args { get; set; } = string.Empty;
|
|
|
|
public bool RaiseError { get; set; } = true;
|
2022-05-20 01:00:25 -07:00
|
|
|
public bool TraitErrorAsOutput { get; set; } = false;
|
|
|
|
|
|
|
|
public bool Exec() {
|
|
|
|
var start = new ProcessStartInfo();
|
2024-02-20 19:29:28 -08:00
|
|
|
start.FileName = Native.OS.GitInstallPath;
|
2022-05-20 01:00:25 -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-02-05 23:08:37 -08:00
|
|
|
if (!string.IsNullOrEmpty(WorkingDirectory)) start.WorkingDirectory = WorkingDirectory;
|
2022-05-20 01:00:25 -07:00
|
|
|
|
|
|
|
var errs = new List<string>();
|
|
|
|
var proc = new Process() { StartInfo = start };
|
|
|
|
var isCancelled = false;
|
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
proc.OutputDataReceived += (_, e) => {
|
|
|
|
if (Cancel != null && Cancel.Requested) {
|
2022-05-20 01:00:25 -07:00
|
|
|
isCancelled = true;
|
|
|
|
proc.CancelErrorRead();
|
|
|
|
proc.CancelOutputRead();
|
2024-02-05 23:08:37 -08:00
|
|
|
if (!proc.HasExited) proc.Kill(true);
|
2022-05-20 01:00:25 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
if (e.Data != null) OnReadline(e.Data);
|
2022-05-20 01:00:25 -07:00
|
|
|
};
|
2024-02-05 23:08:37 -08:00
|
|
|
|
|
|
|
proc.ErrorDataReceived += (_, e) => {
|
|
|
|
if (Cancel != null && Cancel.Requested) {
|
2022-05-20 01:00:25 -07:00
|
|
|
isCancelled = true;
|
|
|
|
proc.CancelErrorRead();
|
|
|
|
proc.CancelOutputRead();
|
2024-02-05 23:08:37 -08:00
|
|
|
if (!proc.HasExited) proc.Kill(true);
|
2022-05-20 01:00:25 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(e.Data)) return;
|
|
|
|
if (TraitErrorAsOutput) OnReadline(e.Data);
|
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
// Ignore progress messages
|
2023-10-11 21:02:41 -07:00
|
|
|
if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal)) return;
|
2022-05-20 01:00:25 -07:00
|
|
|
if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return;
|
2023-10-11 21:02:41 -07:00
|
|
|
if (e.Data.StartsWith("remote: Compressing objects:", StringComparison.Ordinal)) return;
|
|
|
|
if (e.Data.StartsWith("Filtering content:", StringComparison.Ordinal)) return;
|
2024-02-05 23:08:37 -08:00
|
|
|
if (_progressRegex.IsMatch(e.Data)) return;
|
2022-05-20 01:00:25 -07:00
|
|
|
errs.Add(e.Data);
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
proc.Start();
|
|
|
|
} catch (Exception e) {
|
2024-02-05 23:08:37 -08:00
|
|
|
if (RaiseError) {
|
|
|
|
Dispatcher.UIThread.Invoke(() => {
|
|
|
|
App.RaiseException(Context, e.Message);
|
|
|
|
});
|
|
|
|
}
|
2022-05-20 01:00:25 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
proc.BeginOutputReadLine();
|
|
|
|
proc.BeginErrorReadLine();
|
|
|
|
proc.WaitForExit();
|
|
|
|
|
|
|
|
int exitCode = proc.ExitCode;
|
|
|
|
proc.Close();
|
|
|
|
|
|
|
|
if (!isCancelled && exitCode != 0 && errs.Count > 0) {
|
2024-02-05 23:08:37 -08:00
|
|
|
if (RaiseError) {
|
|
|
|
Dispatcher.UIThread.Invoke(() => {
|
|
|
|
App.RaiseException(Context, string.Join("\n", errs));
|
|
|
|
});
|
|
|
|
}
|
2022-05-20 01:00:25 -07:00
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public ReadToEndResult ReadToEnd() {
|
|
|
|
var start = new ProcessStartInfo();
|
2024-02-20 19:29:28 -08:00
|
|
|
start.FileName = Native.OS.GitInstallPath;
|
2022-05-20 01:00:25 -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-02-05 23:08:37 -08:00
|
|
|
if (!string.IsNullOrEmpty(WorkingDirectory)) start.WorkingDirectory = WorkingDirectory;
|
2022-05-20 01:00:25 -07:00
|
|
|
|
|
|
|
var proc = new Process() { StartInfo = start };
|
|
|
|
try {
|
|
|
|
proc.Start();
|
|
|
|
} catch (Exception e) {
|
|
|
|
return new ReadToEndResult() {
|
|
|
|
IsSuccess = false,
|
2024-02-05 23:08:37 -08:00
|
|
|
StdOut = string.Empty,
|
|
|
|
StdErr = e.Message,
|
2022-05-20 01:00:25 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
var rs = new ReadToEndResult() {
|
|
|
|
StdOut = proc.StandardOutput.ReadToEnd(),
|
|
|
|
StdErr = proc.StandardError.ReadToEnd(),
|
|
|
|
};
|
2022-05-20 01:00:25 -07:00
|
|
|
|
|
|
|
proc.WaitForExit();
|
|
|
|
rs.IsSuccess = proc.ExitCode == 0;
|
|
|
|
proc.Close();
|
|
|
|
|
|
|
|
return rs;
|
|
|
|
}
|
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
protected virtual void OnReadline(string line) { }
|
2022-10-19 00:20:58 -07:00
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
private static readonly Regex _progressRegex = new Regex(@"\d+%");
|
2022-05-20 01:00:25 -07:00
|
|
|
}
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|