diff --git a/.gitignore b/.gitignore
index f348333a..1742b876 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,4 @@
.vscode
bin
obj
-publish
*.user
diff --git a/README.md b/README.md
index ca35f786..878c1abe 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,10 @@
# SourceGit
-Opensouce Git GUI client for Windows.
+Opensouce Git GUI client.
## High-lights
* Opensource/Free
-* Light-weight
* Fast
* English/简体中文
* Build-in light/dark themes
@@ -29,8 +28,6 @@ Opensouce Git GUI client for Windows.
Pre-build Binaries:[Releases](https://github.com/sourcegit-scm/sourcegit/releases)
-> NOTE: You need install Git first.
-
## Screen Shots
* Drak Theme
diff --git a/src/App.axaml b/src/App.axaml
new file mode 100644
index 00000000..b9124294
--- /dev/null
+++ b/src/App.axaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
new file mode 100644
index 00000000..924d79b5
--- /dev/null
+++ b/src/App.axaml.cs
@@ -0,0 +1,158 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Data.Core.Plugins;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Styling;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Text;
+
+namespace SourceGit {
+ public partial class App : Application {
+
+ [STAThread]
+ public static void Main(string[] args) {
+ try {
+ BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+ } catch (Exception ex) {
+ var builder = new StringBuilder();
+ builder.Append("Crash: ");
+ builder.Append(ex.Message);
+ builder.Append("\n\n");
+ builder.Append("----------------------------\n");
+ builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
+ builder.Append($"OS: {Environment.OSVersion.ToString()}\n");
+ builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
+ builder.Append($"Source: {ex.Source}\n");
+ builder.Append($"---------------------------\n\n");
+ builder.Append(ex.StackTrace);
+
+ var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
+ var file = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
+ file = Path.Combine(file, $"crash_{time}.log");
+ File.WriteAllText(file, builder.ToString());
+ }
+ }
+
+ public static AppBuilder BuildAvaloniaApp() {
+ var builder = AppBuilder.Configure();
+ builder.UsePlatformDetect();
+
+ if (OperatingSystem.IsWindows()) {
+ builder.With(new FontManagerOptions() {
+ FontFallbacks = [
+ new FontFallback { FontFamily = new FontFamily("Microsoft YaHei UI") }
+ ]
+ });
+ } else if (OperatingSystem.IsMacOS()) {
+ builder.With(new FontManagerOptions() {
+ FontFallbacks = [
+ new FontFallback { FontFamily = new FontFamily("PingFang SC") }
+ ]
+ });
+ builder.With(new MacOSPlatformOptions() {
+ DisableDefaultApplicationMenuItems = true,
+ DisableNativeMenus = true,
+ });
+ }
+
+ builder.LogToTrace();
+ return builder;
+ }
+
+ public static void RaiseException(string context, string message) {
+ if (Current is App app && app._notificationReceiver != null) {
+ var ctx = context.Replace('\\', '/');
+ var notice = new Models.Notification() { IsError = true, Message = message };
+ app._notificationReceiver.OnReceiveNotification(ctx, notice);
+ }
+ }
+
+ public static void SendNotification(string context, string message) {
+ if (Current is App app && app._notificationReceiver != null) {
+ var ctx = context.Replace('\\', '/');
+ var notice = new Models.Notification() { IsError = false, Message = message };
+ app._notificationReceiver.OnReceiveNotification(ctx, notice);
+ }
+ }
+
+ public static void SetLocale(string localeKey) {
+ var app = Current as App;
+ var targetLocale = app.Resources[localeKey] as ResourceDictionary;
+ if (targetLocale == null || targetLocale == app._activeLocale) {
+ return;
+ }
+
+ if (app._activeLocale != null) {
+ app.Resources.MergedDictionaries.Remove(app._activeLocale);
+ }
+
+ app.Resources.MergedDictionaries.Add(targetLocale);
+ app._activeLocale = targetLocale;
+ }
+
+ public static void SetTheme(string theme) {
+ if (theme.Equals("Light", StringComparison.OrdinalIgnoreCase)) {
+ App.Current.RequestedThemeVariant = ThemeVariant.Light;
+ } else if (theme.Equals("Dark", StringComparison.OrdinalIgnoreCase)) {
+ App.Current.RequestedThemeVariant = ThemeVariant.Dark;
+ } else {
+ App.Current.RequestedThemeVariant = ThemeVariant.Default;
+ }
+ }
+
+ public static async void CopyText(string data) {
+ if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
+ if (desktop.MainWindow.Clipboard is { } clipbord) {
+ await clipbord.SetTextAsync(data);
+ }
+ }
+ }
+
+ public static string Text(string key, params object[] args) {
+ var fmt = Current.FindResource($"Text.{key}") as string;
+ if (string.IsNullOrWhiteSpace(fmt)) return $"Text.{key}";
+ return string.Format(fmt, args);
+ }
+
+ public static TopLevel GetTopLevel() {
+ if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
+ return desktop.MainWindow;
+ }
+ return null;
+ }
+
+ public static void Quit() {
+ if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
+ desktop.MainWindow.Close();
+ desktop.Shutdown();
+ }
+ }
+
+ public override void Initialize() {
+ AvaloniaXamlLoader.Load(this);
+
+ SetLocale(ViewModels.Preference.Instance.Locale);
+ SetTheme(ViewModels.Preference.Instance.Theme);
+ }
+
+ public override void OnFrameworkInitializationCompleted() {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
+ BindingPlugins.DataValidators.RemoveAt(0);
+
+ var launcher = new Views.Launcher();
+ _notificationReceiver = launcher;
+ desktop.MainWindow = launcher;
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+
+ private ResourceDictionary _activeLocale = null;
+ private Models.INotificationReceiver _notificationReceiver = null;
+ }
+}
\ No newline at end of file
diff --git a/src/App.manifest b/src/App.manifest
index 5a6db8f3..b3bc3bdf 100644
--- a/src/App.manifest
+++ b/src/App.manifest
@@ -1,14 +1,18 @@
-
-
-
-
-
-
-
- PerMonitorV2
- true
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App.xaml b/src/App.xaml
deleted file mode 100644
index 1f48956b..00000000
--- a/src/App.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/App.xaml.cs b/src/App.xaml.cs
deleted file mode 100644
index 16637cd1..00000000
--- a/src/App.xaml.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using System;
-using System.IO;
-using System.Windows;
-
-namespace SourceGit {
-
- ///
- /// 程序入口.
- ///
- public partial class App : Application {
- public static event Action ExceptionRaised;
-
- ///
- /// 读取本地化字串
- ///
- /// 本地化字串的Key
- /// 可选格式化参数
- /// 本地化字串
- public static string Text(string key, params object[] args) {
- var data = Current.FindResource($"Text.{key}") as string;
- if (string.IsNullOrEmpty(data)) return $"Text.{key}";
- return string.Format(data, args);
- }
-
- ///
- /// 触发错误
- ///
- /// 错误上下文
- /// 错误内容
- public static void Exception(string ctx, string detail) {
- ExceptionRaised?.Invoke(ctx, detail);
- }
-
- ///
- /// 启动.
- ///
- ///
- ///
- protected override void OnStartup(StartupEventArgs e) {
- base.OnStartup(e);
-
- // 崩溃文件生成
- AppDomain.CurrentDomain.UnhandledException += (_, ev) => Models.CrashInfo.Create(ev.ExceptionObject as Exception);
-
- // 创建必要目录
- if (!Directory.Exists(Views.Controls.Avatar.CACHE_PATH)) {
- Directory.CreateDirectory(Views.Controls.Avatar.CACHE_PATH);
- }
-
- Models.Theme.Change();
- Models.Locale.Change();
-
- // 如果启动命令中指定了路径,打开指定目录的仓库
- var launcher = new Views.Launcher();
- if (Models.Preference.Instance.IsReady) {
- if (e.Args.Length > 0) {
- var repo = Models.Preference.Instance.FindRepository(e.Args[0]);
- if (repo == null) {
- var path = new Commands.GetRepositoryRootPath(e.Args[0]).Result();
- if (path != null) {
- var gitDir = new Commands.QueryGitDir(path).Result();
- repo = Models.Preference.Instance.AddRepository(path, gitDir);
- }
- }
-
- if (repo != null) Models.Watcher.Open(repo);
- } else if (Models.Preference.Instance.Restore.IsEnabled) {
- var restore = Models.Preference.Instance.Restore;
- var actived = null as Models.Repository;
- if (restore.Opened.Count > 0) {
- foreach (var path in restore.Opened) {
- if (!Directory.Exists(path)) continue;
- var repo = Models.Preference.Instance.FindRepository(path);
- if (repo != null) Models.Watcher.Open(repo);
- if (path == restore.Actived) actived = repo;
- }
-
- if (actived != null) Models.Watcher.Open(actived);
- }
- }
- }
-
- // 主界面显示
- MainWindow = launcher;
- MainWindow.Show();
- }
-
- ///
- /// 后台运行
- ///
- ///
- ///
- protected override void OnDeactivated(EventArgs e) {
- base.OnDeactivated(e);
- GC.Collect();
- Models.Preference.Save();
- }
- }
-}
diff --git a/src/Avalonia.Controls.DataGrid.patch b/src/Avalonia.Controls.DataGrid.patch
new file mode 100644
index 00000000..415c537f
--- /dev/null
+++ b/src/Avalonia.Controls.DataGrid.patch
@@ -0,0 +1,81 @@
+diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
+index e4573c3759a8a1eeece45c8bacb4fa853201f8e7..aa29066173e03092b61477985aed73beb08ec8fc 100644
+--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
++++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
+@@ -716,6 +716,16 @@ public DataGridRowDetailsVisibilityMode RowDetailsVisibilityMode
+ set { SetValue(RowDetailsVisibilityModeProperty, value); }
+ }
+
++ public static readonly RoutedEvent DisplayRegionChangedEvent = RoutedEvent.Register(
++ nameof(DisplayRegionChanged),
++ RoutingStrategies.Bubble);
++
++ public event EventHandler DisplayRegionChanged
++ {
++ add => AddHandler(DisplayRegionChangedEvent, value);
++ remove => RemoveHandler(DisplayRegionChangedEvent, value);
++ }
++
+ static DataGrid()
+ {
+ AffectsMeasure(
+@@ -2428,6 +2438,11 @@ protected virtual void OnUnloadingRow(DataGridRowEventArgs e)
+ }
+ }
+
++ protected virtual void OnDisplayRegionChanged()
++ {
++ RaiseEvent(new RoutedEventArgs(DisplayRegionChangedEvent));
++ }
++
+ ///
+ /// Comparator class so we can sort list by the display index
+ ///
+@@ -3879,6 +3894,7 @@ private void InvalidateColumnHeadersMeasure()
+ {
+ EnsureColumnHeadersVisibility();
+ _columnHeadersPresenter.InvalidateMeasure();
++ OnDisplayRegionChanged();
+ }
+ }
+
+@@ -3903,6 +3919,8 @@ private void InvalidateRowsMeasure(bool invalidateIndividualElements)
+ element.InvalidateMeasure();
+ }
+ }
++
++ OnDisplayRegionChanged();
+ }
+ }
+
+@@ -6211,5 +6229,30 @@ protected virtual void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventA
+ {
+ AutoGeneratingColumn?.Invoke(this, e);
+ }
++
++ public Vector GetDisplayOffset()
++ {
++ // Has bug when using arrow keys via keyboard.
++ // return new Vector(_horizontalOffset, _verticalOffset);
++
++ double startX = 0;
++ double startY = 0;
++
++ foreach (var child in _rowsPresenter.Children)
++ {
++ var row = child as DataGridRow;
++ if (row.Slot >= 0 && row.Bounds.Top <= 0 && row.Bounds.Top > -RowHeight)
++ {
++ var testY = RowHeight * row.Index - row.Bounds.Top;
++ if (startY < testY)
++ {
++ startY = testY;
++ startX = row.Bounds.Left;
++ }
++ }
++ }
++
++ return new Vector(startX, startY);
++ }
+ }
+ }
diff --git a/src/BuildWindows.bat b/src/BuildWindows.bat
new file mode 100644
index 00000000..8ec72502
--- /dev/null
+++ b/src/BuildWindows.bat
@@ -0,0 +1 @@
+dotnet publish -c Release -r win-x64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained
\ No newline at end of file
diff --git a/src/Commands/Add.cs b/src/Commands/Add.cs
index fb98cf87..c83c330d 100644
--- a/src/Commands/Add.cs
+++ b/src/Commands/Add.cs
@@ -1,27 +1,24 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Text;
namespace SourceGit.Commands {
- ///
- /// `git add`命令
- ///
public class Add : Command {
- public Add(string repo) {
- Cwd = repo;
- Args = "add .";
- }
+ public Add(string repo, List changes = null) {
+ WorkingDirectory = repo;
+ Context = repo;
- public Add(string repo, List paths) {
- StringBuilder builder = new StringBuilder();
- builder.Append("add --");
- foreach (var p in paths) {
- builder.Append(" \"");
- builder.Append(p);
- builder.Append("\"");
+ if (changes == null || changes.Count == 0) {
+ Args = "add .";
+ } else {
+ var builder = new StringBuilder();
+ builder.Append("add --");
+ foreach (var c in changes) {
+ builder.Append(" \"");
+ builder.Append(c.Path);
+ builder.Append("\"");
+ }
+ Args = builder.ToString();
}
-
- Cwd = repo;
- Args = builder.ToString();
}
}
}
diff --git a/src/Commands/Apply.cs b/src/Commands/Apply.cs
index f62f113c..3619d963 100644
--- a/src/Commands/Apply.cs
+++ b/src/Commands/Apply.cs
@@ -1,11 +1,8 @@
-namespace SourceGit.Commands {
- ///
- /// 应用Patch
- ///
+namespace SourceGit.Commands {
public class Apply : Command {
-
public Apply(string repo, string file, bool ignoreWhitespace, string whitespaceMode) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = "apply ";
if (ignoreWhitespace) Args += "--ignore-whitespace ";
else Args += $"--whitespace={whitespaceMode} ";
diff --git a/src/Commands/Archive.cs b/src/Commands/Archive.cs
index b4153e34..ac748653 100644
--- a/src/Commands/Archive.cs
+++ b/src/Commands/Archive.cs
@@ -1,22 +1,19 @@
-using System;
+using System;
namespace SourceGit.Commands {
-
- ///
- /// 存档命令
- ///
public class Archive : Command {
- private Action handler;
-
- public Archive(string repo, string revision, string to, Action onProgress) {
- Cwd = repo;
- Args = $"archive --format=zip --verbose --output=\"{to}\" {revision}";
+ public Archive(string repo, string revision, string saveTo, Action outputHandler) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}";
TraitErrorAsOutput = true;
- handler = onProgress;
+ _outputHandler = outputHandler;
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
+
+ private Action _outputHandler;
}
}
diff --git a/src/Commands/AssumeUnchanged.cs b/src/Commands/AssumeUnchanged.cs
index 9a0af3d9..d7dc4c06 100644
--- a/src/Commands/AssumeUnchanged.cs
+++ b/src/Commands/AssumeUnchanged.cs
@@ -2,59 +2,59 @@
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
- ///
- /// 查看、添加或移除忽略变更文件
- ///
public class AssumeUnchanged {
- private string repo;
-
class ViewCommand : Command {
private static readonly Regex REG = new Regex(@"^(\w)\s+(.+)$");
- private List outs = new List();
public ViewCommand(string repo) {
- Cwd = repo;
+ WorkingDirectory = repo;
Args = "ls-files -v";
+ RaiseError = false;
}
public List Result() {
Exec();
- return outs;
+ return _outs;
}
- public override void OnReadline(string line) {
+ protected override void OnReadline(string line) {
var match = REG.Match(line);
if (!match.Success) return;
if (match.Groups[1].Value == "h") {
- outs.Add(match.Groups[2].Value);
+ _outs.Add(match.Groups[2].Value);
}
}
+
+ private List _outs = new List();
}
class ModCommand : Command {
public ModCommand(string repo, string file, bool bAdd) {
var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged";
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = $"update-index {mode} -- \"{file}\"";
}
}
public AssumeUnchanged(string repo) {
- this.repo = repo;
+ _repo = repo;
}
public List View() {
- return new ViewCommand(repo).Result();
+ return new ViewCommand(_repo).Result();
}
public void Add(string file) {
- new ModCommand(repo, file, true).Exec();
+ new ModCommand(_repo, file, true).Exec();
}
public void Remove(string file) {
- new ModCommand(repo, file, false).Exec();
+ new ModCommand(_repo, file, false).Exec();
}
+
+ private string _repo;
}
}
diff --git a/src/Commands/Blame.cs b/src/Commands/Blame.cs
index 571ff17e..908019f9 100644
--- a/src/Commands/Blame.cs
+++ b/src/Commands/Blame.cs
@@ -1,77 +1,90 @@
-using System;
-using System.Collections.Generic;
+using System;
+using System.IO;
+using System.Text;
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
- ///
- /// 逐行追溯
- ///
public class Blame : Command {
private static readonly Regex REG_FORMAT = new Regex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)");
private static readonly DateTime UTC_START = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime();
- private Data data = new Data();
- private bool needUnifyCommitSHA = false;
- private int minSHALen = 0;
-
- public class Data {
- public List Lines = new List();
- public bool IsBinary = false;
- }
-
public Blame(string repo, string file, string revision) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = $"blame -t {revision} -- \"{file}\"";
+ RaiseError = false;
+
+ _result.File = file;
}
- public Data Result() {
- Exec();
+ public Models.BlameData Result() {
+ var succ = Exec();
+ if (!succ) {
+ return new Models.BlameData();
+ }
- if (needUnifyCommitSHA) {
- foreach (var line in data.Lines) {
- if (line.CommitSHA.Length > minSHALen) {
- line.CommitSHA = line.CommitSHA.Substring(0, minSHALen);
+ if (_needUnifyCommitSHA) {
+ foreach (var line in _result.LineInfos) {
+ if (line.CommitSHA.Length > _minSHALen) {
+ line.CommitSHA = line.CommitSHA.Substring(0, _minSHALen);
}
}
}
- return data;
+ _result.Content = _content.ToString();
+ return _result;
}
- public override void OnReadline(string line) {
- if (data.IsBinary) return;
+ protected override void OnReadline(string line) {
+ if (_result.IsBinary) return;
if (string.IsNullOrEmpty(line)) return;
if (line.IndexOf('\0') >= 0) {
- data.IsBinary = true;
- data.Lines.Clear();
+ _result.IsBinary = true;
+ _result.LineInfos.Clear();
return;
}
var match = REG_FORMAT.Match(line);
if (!match.Success) return;
+ _content.AppendLine(match.Groups[4].Value);
+
var commit = match.Groups[1].Value;
- var author = match.Groups[2].Value;
- var timestamp = int.Parse(match.Groups[3].Value);
- var content = match.Groups[4].Value;
- var when = UTC_START.AddSeconds(timestamp).ToString("yyyy/MM/dd");
+ if (commit == _lastSHA) {
+ var info = new Models.BlameLineInfo() {
+ CommitSHA = commit,
+ Author = string.Empty,
+ Time = string.Empty,
+ };
- var blameLine = new Models.BlameLine() {
- LineNumber = $"{data.Lines.Count + 1}",
- CommitSHA = commit,
- Author = author,
- Time = when,
- Content = content,
- };
+ _result.LineInfos.Add(info);
+ } else {
+ var author = match.Groups[2].Value;
+ var timestamp = int.Parse(match.Groups[3].Value);
+ var when = UTC_START.AddSeconds(timestamp).ToString("yyyy/MM/dd");
- if (line[0] == '^') {
- needUnifyCommitSHA = true;
- if (minSHALen == 0) minSHALen = commit.Length;
- else if (commit.Length < minSHALen) minSHALen = commit.Length;
+ var blameLine = new Models.BlameLineInfo() {
+ IsFirstInGroup = true,
+ CommitSHA = commit,
+ Author = author,
+ Time = when,
+ };
+
+ _lastSHA = commit;
+ _result.LineInfos.Add(blameLine);
}
- data.Lines.Add(blameLine);
+ if (line[0] == '^') {
+ _needUnifyCommitSHA = true;
+ _minSHALen = Math.Min(_minSHALen, commit.Length);
+ }
}
+
+ private Models.BlameData _result = new Models.BlameData();
+ private StringBuilder _content = new StringBuilder();
+ private string _lastSHA = string.Empty;
+ private bool _needUnifyCommitSHA = false;
+ private int _minSHALen = 64;
}
}
diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs
index 387892ba..f109d1e4 100644
--- a/src/Commands/Branch.cs
+++ b/src/Commands/Branch.cs
@@ -1,38 +1,39 @@
-namespace SourceGit.Commands {
- ///
- /// 分支相关操作
- ///
- class Branch : Command {
- private string target = null;
-
- public Branch(string repo, string branch) {
- Cwd = repo;
- target = branch;
+namespace SourceGit.Commands {
+ public static class Branch {
+ public static bool Create(string repo, string name, string basedOn) {
+ var cmd = new Command();
+ cmd.WorkingDirectory = repo;
+ cmd.Context = repo;
+ cmd.Args = $"branch {name} {basedOn}";
+ return cmd.Exec();
}
- public void Create(string basedOn) {
- Args = $"branch {target} {basedOn}";
- Exec();
+ public static bool Rename(string repo, string name, string to) {
+ var cmd = new Command();
+ cmd.WorkingDirectory = repo;
+ cmd.Context = repo;
+ cmd.Args = $"branch -M {name} {to}";
+ return cmd.Exec();
}
- public void Rename(string to) {
- Args = $"branch -M {target} {to}";
- Exec();
- }
-
- public void SetUpstream(string upstream) {
- Args = $"branch {target} ";
+ public static bool SetUpstream(string repo, string name, string upstream) {
+ var cmd = new Command();
+ cmd.WorkingDirectory = repo;
+ cmd.Context = repo;
if (string.IsNullOrEmpty(upstream)) {
- Args += "--unset-upstream";
+ cmd.Args = $"branch {name} --unset-upstream";
} else {
- Args += $"-u {upstream}";
+ cmd.Args = $"branch {name} -u {upstream}";
}
- Exec();
+ return cmd.Exec();
}
- public void Delete() {
- Args = $"branch -D {target}";
- Exec();
+ public static bool Delete(string repo, string name) {
+ var cmd = new Command();
+ cmd.WorkingDirectory = repo;
+ cmd.Context = repo;
+ cmd.Args = $"branch -D {name}";
+ return cmd.Exec();
}
}
}
diff --git a/src/Commands/Checkout.cs b/src/Commands/Checkout.cs
index 1f169992..14121fab 100644
--- a/src/Commands/Checkout.cs
+++ b/src/Commands/Checkout.cs
@@ -1,29 +1,25 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Text;
namespace SourceGit.Commands {
- ///
- /// 检出
- ///
public class Checkout : Command {
- private Action handler = null;
-
public Checkout(string repo) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
}
public bool Branch(string branch, Action onProgress) {
Args = $"checkout --progress {branch}";
TraitErrorAsOutput = true;
- handler = onProgress;
+ _outputHandler = onProgress;
return Exec();
}
public bool Branch(string branch, string basedOn, Action onProgress) {
Args = $"checkout --progress -b {branch} {basedOn}";
TraitErrorAsOutput = true;
- handler = onProgress;
+ _outputHandler = onProgress;
return Exec();
}
@@ -54,8 +50,10 @@ namespace SourceGit.Commands {
return Exec();
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
+
+ private Action _outputHandler;
}
}
diff --git a/src/Commands/CherryPick.cs b/src/Commands/CherryPick.cs
index ca939e76..6160ebac 100644
--- a/src/Commands/CherryPick.cs
+++ b/src/Commands/CherryPick.cs
@@ -1,12 +1,9 @@
-namespace SourceGit.Commands {
- ///
- /// 遴选命令
- ///
+namespace SourceGit.Commands {
public class CherryPick : Command {
-
public CherryPick(string repo, string commit, bool noCommit) {
var mode = noCommit ? "-n" : "--ff";
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = $"cherry-pick {mode} {commit}";
}
}
diff --git a/src/Commands/Clean.cs b/src/Commands/Clean.cs
index 38a9a477..56a56a6c 100644
--- a/src/Commands/Clean.cs
+++ b/src/Commands/Clean.cs
@@ -1,14 +1,11 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Text;
namespace SourceGit.Commands {
- ///
- /// 清理指令
- ///
public class Clean : Command {
-
public Clean(string repo) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = "clean -qfd";
}
@@ -21,7 +18,8 @@ namespace SourceGit.Commands {
builder.Append("\"");
}
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = builder.ToString();
}
}
diff --git a/src/Commands/Clone.cs b/src/Commands/Clone.cs
index fefc0f3e..a6228e20 100644
--- a/src/Commands/Clone.cs
+++ b/src/Commands/Clone.cs
@@ -1,24 +1,18 @@
-using System;
+using System;
namespace SourceGit.Commands {
-
- ///
- /// 克隆
- ///
public class Clone : Command {
- private Action handler = null;
- private Action onError = null;
+ private Action _notifyProgress;
- public Clone(string path, string url, string localName, string sshKey, string extraArgs, Action outputHandler, Action errHandler) {
- Cwd = path;
+ public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs, Action ouputHandler) {
+ Context = ctx;
+ WorkingDirectory = path;
TraitErrorAsOutput = true;
- handler = outputHandler;
- onError = errHandler;
if (string.IsNullOrEmpty(sshKey)) {
- Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
- } else {
Args = "-c credential.helper=manager ";
+ } else {
+ Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
}
Args += "clone --progress --verbose --recurse-submodules ";
@@ -26,14 +20,12 @@ namespace SourceGit.Commands {
if (!string.IsNullOrEmpty(extraArgs)) Args += $"{extraArgs} ";
Args += $"{url} ";
if (!string.IsNullOrEmpty(localName)) Args += localName;
+
+ _notifyProgress = ouputHandler;
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
- }
-
- public override void OnException(string message) {
- onError?.Invoke(message);
+ protected override void OnReadline(string line) {
+ _notifyProgress?.Invoke(line);
}
}
}
diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs
index aac9bdff..9e55a581 100644
--- a/src/Commands/Command.cs
+++ b/src/Commands/Command.cs
@@ -1,3 +1,4 @@
+using Avalonia.Threading;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -5,60 +6,27 @@ using System.Text;
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
-
- ///
- /// 用于取消命令执行的上下文对象
- ///
- public class Context {
- public bool IsCancelRequested { get; set; } = false;
- }
-
- ///
- /// 命令接口
- ///
public class Command {
- private static readonly Regex PROGRESS_REG = new Regex(@"\d+%");
-
- ///
- /// 读取全部输出时的结果
- ///
- public class ReadToEndResult {
- public bool IsSuccess { get; set; }
- public string Output { get; set; }
- public string Error { get; set; }
+ public class CancelToken {
+ public bool Requested { get; set; } = false;
}
- ///
- /// 上下文
- ///
- public Context Ctx { get; set; } = null;
+ public class ReadToEndResult {
+ public bool IsSuccess { get; set; }
+ public string StdOut { get; set; }
+ public string StdErr { get; set; }
+ }
- ///
- /// 运行路径
- ///
- public string Cwd { get; set; } = "";
-
- ///
- /// 参数
- ///
- public string Args { get; set; } = "";
-
- ///
- /// 是否忽略错误
- ///
- public bool DontRaiseError { get; set; } = false;
-
- ///
- /// 使用标准错误输出
- ///
+ 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;
public bool TraitErrorAsOutput { get; set; } = false;
- ///
- /// 运行
- ///
public bool Exec() {
var start = new ProcessStartInfo();
- start.FileName = Models.Preference.Instance.Git.Path;
+ start.FileName = Native.OS.GitExecutableFile;
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
start.UseShellExecute = false;
start.CreateNoWindow = true;
@@ -67,49 +35,53 @@ namespace SourceGit.Commands {
start.StandardOutputEncoding = Encoding.UTF8;
start.StandardErrorEncoding = Encoding.UTF8;
- if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
+ if (!string.IsNullOrEmpty(WorkingDirectory)) start.WorkingDirectory = WorkingDirectory;
var errs = new List();
var proc = new Process() { StartInfo = start };
var isCancelled = false;
- proc.OutputDataReceived += (o, e) => {
- if (Ctx != null && Ctx.IsCancelRequested) {
+ proc.OutputDataReceived += (_, e) => {
+ if (Cancel != null && Cancel.Requested) {
isCancelled = true;
proc.CancelErrorRead();
proc.CancelOutputRead();
- if (!proc.HasExited) proc.Kill();
+ if (!proc.HasExited) proc.Kill(true);
return;
}
- if (e.Data == null) return;
- OnReadline(e.Data);
+ if (e.Data != null) OnReadline(e.Data);
};
- proc.ErrorDataReceived += (o, e) => {
- if (Ctx != null && Ctx.IsCancelRequested) {
+
+ proc.ErrorDataReceived += (_, e) => {
+ if (Cancel != null && Cancel.Requested) {
isCancelled = true;
proc.CancelErrorRead();
proc.CancelOutputRead();
- if (!proc.HasExited) proc.Kill();
+ if (!proc.HasExited) proc.Kill(true);
return;
}
if (string.IsNullOrEmpty(e.Data)) return;
if (TraitErrorAsOutput) OnReadline(e.Data);
- // 错误信息中忽略进度相关的输出
+ // Ignore progress messages
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;
- if (PROGRESS_REG.IsMatch(e.Data)) return;
+ if (_progressRegex.IsMatch(e.Data)) return;
errs.Add(e.Data);
};
try {
proc.Start();
} catch (Exception e) {
- if (!DontRaiseError) OnException(e.Message);
+ if (RaiseError) {
+ Dispatcher.UIThread.Invoke(() => {
+ App.RaiseException(Context, e.Message);
+ });
+ }
return false;
}
@@ -121,19 +93,20 @@ namespace SourceGit.Commands {
proc.Close();
if (!isCancelled && exitCode != 0 && errs.Count > 0) {
- if (!DontRaiseError) OnException(string.Join("\n", errs));
+ if (RaiseError) {
+ Dispatcher.UIThread.Invoke(() => {
+ App.RaiseException(Context, string.Join("\n", errs));
+ });
+ }
return false;
} else {
return true;
}
}
- ///
- /// 直接读取全部标准输出
- ///
public ReadToEndResult ReadToEnd() {
var start = new ProcessStartInfo();
- start.FileName = Models.Preference.Instance.Git.Path;
+ start.FileName = Native.OS.GitExecutableFile;
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
start.UseShellExecute = false;
start.CreateNoWindow = true;
@@ -142,22 +115,23 @@ namespace SourceGit.Commands {
start.StandardOutputEncoding = Encoding.UTF8;
start.StandardErrorEncoding = Encoding.UTF8;
- if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
+ if (!string.IsNullOrEmpty(WorkingDirectory)) start.WorkingDirectory = WorkingDirectory;
var proc = new Process() { StartInfo = start };
try {
proc.Start();
} catch (Exception e) {
return new ReadToEndResult() {
- Output = string.Empty,
- Error = e.Message,
IsSuccess = false,
+ StdOut = string.Empty,
+ StdErr = e.Message,
};
}
- var rs = new ReadToEndResult();
- rs.Output = proc.StandardOutput.ReadToEnd();
- rs.Error = proc.StandardError.ReadToEnd();
+ var rs = new ReadToEndResult() {
+ StdOut = proc.StandardOutput.ReadToEnd(),
+ StdErr = proc.StandardError.ReadToEnd(),
+ };
proc.WaitForExit();
rs.IsSuccess = proc.ExitCode == 0;
@@ -166,19 +140,8 @@ namespace SourceGit.Commands {
return rs;
}
- ///
- /// 调用Exec时的读取函数
- ///
- ///
- public virtual void OnReadline(string line) {
- }
+ protected virtual void OnReadline(string line) { }
- ///
- /// 默认异常处理函数
- ///
- ///
- public virtual void OnException(string message) {
- App.Exception(Cwd, message);
- }
+ private static readonly Regex _progressRegex = new Regex(@"\d+%");
}
-}
+}
\ No newline at end of file
diff --git a/src/Commands/Commit.cs b/src/Commands/Commit.cs
index a3dde9a9..2733590c 100644
--- a/src/Commands/Commit.cs
+++ b/src/Commands/Commit.cs
@@ -1,17 +1,16 @@
-using System.IO;
+using System.IO;
namespace SourceGit.Commands {
- ///
- /// `git commit`命令
- ///
public class Commit : Command {
- public Commit(string repo, string message, bool amend) {
+ public Commit(string repo, string message, bool amend, bool allowEmpty = false) {
var file = Path.GetTempFileName();
File.WriteAllText(file, message);
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = $"commit --file=\"{file}\"";
if (amend) Args += " --amend --no-edit";
+ if (allowEmpty) Args += " --allow-empty";
}
}
}
diff --git a/src/Commands/CommitChanges.cs b/src/Commands/CommitChanges.cs
deleted file mode 100644
index defcbff2..00000000
--- a/src/Commands/CommitChanges.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-
-namespace SourceGit.Commands {
-
- ///
- /// 取得一个提交的变更列表
- ///
- public class CommitChanges : Command {
- private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
- private List changes = new List();
-
- public CommitChanges(string cwd, string commit) {
- Cwd = cwd;
- Args = $"show --name-status {commit}";
- }
-
- public List Result() {
- Exec();
- return changes;
- }
-
- public override void OnReadline(string line) {
- var match = REG_FORMAT.Match(line);
- if (!match.Success) return;
-
- var change = new Models.Change() { Path = match.Groups[2].Value };
- var status = match.Groups[1].Value;
-
- switch (status[0]) {
- case 'M': change.Set(Models.Change.Status.Modified); changes.Add(change); break;
- case 'A': change.Set(Models.Change.Status.Added); changes.Add(change); break;
- case 'D': change.Set(Models.Change.Status.Deleted); changes.Add(change); break;
- case 'R': change.Set(Models.Change.Status.Renamed); changes.Add(change); break;
- case 'C': change.Set(Models.Change.Status.Copied); changes.Add(change); break;
- }
- }
- }
-}
diff --git a/src/Commands/CommitRangeChanges.cs b/src/Commands/CommitRangeChanges.cs
deleted file mode 100644
index 05cc778e..00000000
--- a/src/Commands/CommitRangeChanges.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-
-namespace SourceGit.Commands {
-
- ///
- /// 对比两个提交间的变更
- ///
- public class CommitRangeChanges : Command {
- private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
- private List changes = new List();
-
- public CommitRangeChanges(string cwd, string start, string end) {
- Cwd = cwd;
- Args = $"diff --name-status {start} {end}";
- }
-
- public List Result() {
- Exec();
- return changes;
- }
-
- public override void OnReadline(string line) {
- var match = REG_FORMAT.Match(line);
- if (!match.Success) return;
-
- var change = new Models.Change() { Path = match.Groups[2].Value };
- var status = match.Groups[1].Value;
-
- switch (status[0]) {
- case 'M': change.Set(Models.Change.Status.Modified); changes.Add(change); break;
- case 'A': change.Set(Models.Change.Status.Added); changes.Add(change); break;
- case 'D': change.Set(Models.Change.Status.Deleted); changes.Add(change); break;
- case 'R': change.Set(Models.Change.Status.Renamed); changes.Add(change); break;
- case 'C': change.Set(Models.Change.Status.Copied); changes.Add(change); break;
- }
- }
- }
-}
diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs
new file mode 100644
index 00000000..2b10adab
--- /dev/null
+++ b/src/Commands/CompareRevisions.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+ public class CompareRevisions : Command {
+ private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
+
+ public CompareRevisions(string repo, string start, string end) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"diff --name-status {start} {end}";
+ }
+
+ public List Result() {
+ Exec();
+ _changes.Sort((l, r) => l.Path.CompareTo(r.Path));
+ return _changes;
+ }
+
+ protected override void OnReadline(string line) {
+ var match = REG_FORMAT.Match(line);
+ if (!match.Success) return;
+
+ var change = new Models.Change() { Path = match.Groups[2].Value };
+ var status = match.Groups[1].Value;
+
+ switch (status[0]) {
+ case 'M': change.Set(Models.ChangeState.Modified); _changes.Add(change); break;
+ case 'A': change.Set(Models.ChangeState.Added); _changes.Add(change); break;
+ case 'D': change.Set(Models.ChangeState.Deleted); _changes.Add(change); break;
+ case 'R': change.Set(Models.ChangeState.Renamed); _changes.Add(change); break;
+ case 'C': change.Set(Models.ChangeState.Copied); _changes.Add(change); break;
+ }
+ }
+
+ private List _changes = new List();
+ }
+}
diff --git a/src/Commands/Config.cs b/src/Commands/Config.cs
index f77216dd..4a0ebb30 100644
--- a/src/Commands/Config.cs
+++ b/src/Commands/Config.cs
@@ -1,32 +1,55 @@
+using System;
+using System.Collections.Generic;
+
namespace SourceGit.Commands {
- ///
- /// config命令
- ///
- public class Config : Command {
+ public class Config : Command {
+ public Config(string repository) {
+ WorkingDirectory = repository;
+ Context = repository;
+ RaiseError = false;
+ }
- public Config() { }
+ public Dictionary ListAll() {
+ Args = "config -l";
- public Config(string repo) {
- Cwd = repo;
+ var output = ReadToEnd();
+ var rs = new Dictionary();
+ if (output.IsSuccess) {
+ var lines = output.StdOut.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var line in lines) {
+ var idx = line.IndexOf('=');
+ if (idx != -1) {
+ var key = line.Substring(0, idx).Trim();
+ var val = line.Substring(idx+1).Trim();
+ if (rs.ContainsKey(key)) {
+ rs[key] = val;
+ } else {
+ rs.Add(key, val);
+ }
+ }
+ }
+ }
+
+ return rs;
}
public string Get(string key) {
Args = $"config {key}";
- return ReadToEnd().Output.Trim();
+ return ReadToEnd().StdOut.Trim();
}
- public bool Set(string key, string val, bool allowEmpty = false) {
- if (!allowEmpty && string.IsNullOrEmpty(val)) {
- if (string.IsNullOrEmpty(Cwd)) {
+ public bool Set(string key, string value, bool allowEmpty = false) {
+ if (!allowEmpty && string.IsNullOrWhiteSpace(value)) {
+ if (string.IsNullOrEmpty(WorkingDirectory)) {
Args = $"config --global --unset {key}";
} else {
Args = $"config --unset {key}";
}
} else {
- if (string.IsNullOrEmpty(Cwd)) {
- Args = $"config --global {key} \"{val}\"";
+ if (string.IsNullOrWhiteSpace(WorkingDirectory)) {
+ Args = $"config --global {key} \"{value}\"";
} else {
- Args = $"config {key} \"{val}\"";
+ Args = $"config {key} \"{value}\"";
}
}
diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs
index 4e4e641a..983b60c1 100644
--- a/src/Commands/Diff.cs
+++ b/src/Commands/Diff.cs
@@ -1,111 +1,147 @@
-using System;
+using System;
using System.Collections.Generic;
-using System.Linq;
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
- ///
- /// Diff命令(用于文件文件比对)
- ///
public class Diff : Command {
private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@");
- private Models.TextChanges changes = new Models.TextChanges();
- private List deleted = new List();
- private List added = new List();
- private int oldLine = 0;
- private int newLine = 0;
- private int lineIndex = 0;
+ private static readonly string PREFIX_LFS = " version https://git-lfs.github.com/spec/";
- public Diff(string repo, string args) {
- Cwd = repo;
- Args = $"diff --ignore-cr-at-eol --unified=4 {args}";
+ public Diff(string repo, Models.DiffOption opt) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"diff --ignore-cr-at-eol --unified=4 {opt}";
}
- public Models.TextChanges Result() {
+ public Models.DiffResult Result() {
Exec();
- ProcessChanges();
- if (changes.IsBinary) changes.Lines.Clear();
- lineIndex = 0;
- return changes;
+
+ if (_result.IsBinary || _result.IsLFS) {
+ _result.TextDiff = null;
+ } else {
+ ProcessInlineHighlights();
+
+ if (_result.TextDiff.Lines.Count == 0) {
+ _result.TextDiff = null;
+ } else {
+ _result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
+ }
+ }
+
+ return _result;
}
- public override void OnReadline(string line) {
- if (changes.IsBinary) return;
+ protected override void OnReadline(string line) {
+ if (_result.IsBinary) return;
- if (changes.Lines.Count == 0) {
+ if (_result.IsLFS) {
+ var ch = line[0];
+ if (ch == '-') {
+ line = line.Substring(1);
+ if (line.StartsWith("oid sha256:")) {
+ _result.LFSDiff.Old.Oid = line.Substring(11);
+ } else if (line.StartsWith("size ")) {
+ _result.LFSDiff.Old.Size = long.Parse(line.Substring(5));
+ }
+ } else if (ch == '+') {
+ line = line.Substring(1);
+ if (line.StartsWith("oid sha256:")) {
+ _result.LFSDiff.New.Oid = line.Substring(11);
+ } else if (line.StartsWith("size ")) {
+ _result.LFSDiff.New.Size = long.Parse(line.Substring(5));
+ }
+ } else if (line.StartsWith(" size ")) {
+ _result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
+ }
+ return;
+ }
+
+ if (_result.TextDiff.Lines.Count == 0) {
var match = REG_INDICATOR.Match(line);
if (!match.Success) {
- if (line.StartsWith("Binary", StringComparison.Ordinal)) changes.IsBinary = true;
+ if (line.StartsWith("Binary", StringComparison.Ordinal)) _result.IsBinary = true;
return;
}
- oldLine = int.Parse(match.Groups[1].Value);
- newLine = int.Parse(match.Groups[2].Value);
- changes.Lines.Add(new Models.TextChanges.Line(lineIndex++, Models.TextChanges.LineMode.Indicator, line, "", ""));
+ _oldLine = int.Parse(match.Groups[1].Value);
+ _newLine = int.Parse(match.Groups[2].Value);
+ _result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, "", ""));
} else {
if (line.Length == 0) {
- ProcessChanges();
- changes.Lines.Add(new Models.TextChanges.Line(lineIndex++, Models.TextChanges.LineMode.Normal, "", $"{oldLine}", $"{newLine}"));
- oldLine++;
- newLine++;
+ ProcessInlineHighlights();
+ _result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", $"{_oldLine}", $"{_newLine}"));
+ _oldLine++;
+ _newLine++;
return;
}
var ch = line[0];
if (ch == '-') {
- deleted.Add(new Models.TextChanges.Line(lineIndex++, Models.TextChanges.LineMode.Deleted, line.Substring(1), $"{oldLine}", ""));
- oldLine++;
+ _deleted.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), $"{_oldLine}", ""));
+ _oldLine++;
} else if (ch == '+') {
- added.Add(new Models.TextChanges.Line(lineIndex++, Models.TextChanges.LineMode.Added, line.Substring(1), "", $"{newLine}"));
- newLine++;
+ _added.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), "", $"{_newLine}"));
+ _newLine++;
} else if (ch != '\\') {
- ProcessChanges();
+ ProcessInlineHighlights();
var match = REG_INDICATOR.Match(line);
if (match.Success) {
- oldLine = int.Parse(match.Groups[1].Value);
- newLine = int.Parse(match.Groups[2].Value);
- changes.Lines.Add(new Models.TextChanges.Line(lineIndex++, Models.TextChanges.LineMode.Indicator, line, "", ""));
+ _oldLine = int.Parse(match.Groups[1].Value);
+ _newLine = int.Parse(match.Groups[2].Value);
+ _result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, "", ""));
} else {
- changes.Lines.Add(new Models.TextChanges.Line(lineIndex++, Models.TextChanges.LineMode.Normal, line.Substring(1), $"{oldLine}", $"{newLine}"));
- oldLine++;
- newLine++;
+ if (line.StartsWith(PREFIX_LFS)) {
+ _result.IsLFS = true;
+ _result.LFSDiff = new Models.LFSDiff();
+ return;
+ }
+
+ _result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), $"{_oldLine}", $"{_newLine}"));
+ _oldLine++;
+ _newLine++;
}
}
}
}
- private void ProcessChanges() {
- if (deleted.Any()) {
- if (added.Count == deleted.Count) {
- for (int i = added.Count - 1; i >= 0; i--) {
- var left = deleted[i];
- var right = added[i];
+ private void ProcessInlineHighlights() {
+ if (_deleted.Count > 0) {
+ if (_added.Count == _deleted.Count) {
+ for (int i = _added.Count - 1; i >= 0; i--) {
+ var left = _deleted[i];
+ var right = _added[i];
if (left.Content.Length > 1024 || right.Content.Length > 1024) continue;
- var chunks = Models.TextCompare.Process(left.Content, right.Content);
+ var chunks = Models.TextInlineChange.Compare(left.Content, right.Content);
if (chunks.Count > 4) continue;
foreach (var chunk in chunks) {
if (chunk.DeletedCount > 0) {
- left.Highlights.Add(new Models.TextChanges.HighlightRange(chunk.DeletedStart, chunk.DeletedCount));
+ left.Highlights.Add(new Models.TextInlineRange(chunk.DeletedStart, chunk.DeletedCount));
}
if (chunk.AddedCount > 0) {
- right.Highlights.Add(new Models.TextChanges.HighlightRange(chunk.AddedStart, chunk.AddedCount));
+ right.Highlights.Add(new Models.TextInlineRange(chunk.AddedStart, chunk.AddedCount));
}
}
}
}
- changes.Lines.AddRange(deleted);
- deleted.Clear();
+ _result.TextDiff.Lines.AddRange(_deleted);
+ _deleted.Clear();
}
- if (added.Any()) {
- changes.Lines.AddRange(added);
- added.Clear();
+ if (_added.Count > 0) {
+ _result.TextDiff.Lines.AddRange(_added);
+ _added.Clear();
}
}
+
+ private Models.DiffResult _result = new Models.DiffResult() { TextDiff = new Models.TextDiff() };
+ private List _deleted = new List();
+ private List _added = new List();
+ private int _oldLine = 0;
+ private int _newLine = 0;
}
}
diff --git a/src/Commands/Discard.cs b/src/Commands/Discard.cs
index dd35d0ca..0480c7e6 100644
--- a/src/Commands/Discard.cs
+++ b/src/Commands/Discard.cs
@@ -1,28 +1,19 @@
-using System;
+using System;
using System.Collections.Generic;
namespace SourceGit.Commands {
- ///
- /// 忽略变更
- ///
- public class Discard {
- private string repo = null;
-
- public Discard(string repo) {
- this.repo = repo;
- }
-
- public void Whole() {
+ public static class Discard {
+ public static void All(string repo) {
new Reset(repo, "HEAD", "--hard").Exec();
new Clean(repo).Exec();
}
- public void Changes(List changes) {
+ public static void Changes(string repo, List changes) {
var needClean = new List();
var needCheckout = new List();
foreach (var c in changes) {
- if (c.WorkTree == Models.Change.Status.Untracked || c.WorkTree == Models.Change.Status.Added) {
+ if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added) {
needClean.Add(c.Path);
} else {
needCheckout.Add(c.Path);
diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs
index ad3925cc..ad36051f 100644
--- a/src/Commands/Fetch.cs
+++ b/src/Commands/Fetch.cs
@@ -1,17 +1,11 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
+using System;
namespace SourceGit.Commands {
-
- ///
- /// 拉取
- ///
public class Fetch : Command {
- private Action handler = null;
-
public Fetch(string repo, string remote, bool prune, Action outputHandler) {
- Cwd = repo;
+ _outputHandler = outputHandler;
+ WorkingDirectory = repo;
+ Context = repo;
TraitErrorAsOutput = true;
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
@@ -24,12 +18,12 @@ namespace SourceGit.Commands {
Args += "fetch --progress --verbose ";
if (prune) Args += "--prune ";
Args += remote;
- handler = outputHandler;
- AutoFetch.MarkFetched(repo);
}
public Fetch(string repo, string remote, string localBranch, string remoteBranch, Action outputHandler) {
- Cwd = repo;
+ _outputHandler = outputHandler;
+ WorkingDirectory = repo;
+ Context = repo;
TraitErrorAsOutput = true;
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
@@ -40,63 +34,12 @@ namespace SourceGit.Commands {
}
Args += $"fetch --progress --verbose {remote} {remoteBranch}:{localBranch}";
- handler = outputHandler;
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
- }
- }
-
- ///
- /// 自动拉取(每隔10分钟)
- ///
- public class AutoFetch {
- private static Dictionary jobs = new Dictionary();
-
- private Fetch cmd = null;
- private long nextFetchPoint = 0;
- private Timer timer = null;
-
- public static void Start(string repo) {
- if (!Models.Preference.Instance.Git.AutoFetchRemotes) return;
-
- // 只自动更新加入管理列表中的仓库(子模块等不自动更新)
- var exists = Models.Preference.Instance.FindRepository(repo);
- if (exists == null) return;
-
- var job = new AutoFetch(repo);
- jobs.Add(repo, job);
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
- public static void MarkFetched(string repo) {
- if (!jobs.ContainsKey(repo)) return;
- jobs[repo].nextFetchPoint = DateTime.Now.AddMinutes(10).ToFileTime();
- }
-
- public static void Stop(string repo) {
- if (!jobs.ContainsKey(repo)) return;
-
- jobs[repo].timer.Dispose();
- jobs.Remove(repo);
- }
-
- public AutoFetch(string repo) {
- cmd = new Fetch(repo, "--all", true, null);
- cmd.DontRaiseError = true;
-
- nextFetchPoint = DateTime.Now.AddMinutes(10).ToFileTime();
- timer = new Timer(OnTick, null, 60000, 10000);
- }
-
- private void OnTick(object o) {
- var now = DateTime.Now.ToFileTime();
- if (nextFetchPoint > now) return;
-
- Models.Watcher.SetEnabled(cmd.Cwd, false);
- cmd.Exec();
- nextFetchPoint = DateTime.Now.AddMinutes(10).ToFileTime();
- Models.Watcher.SetEnabled(cmd.Cwd, true);
- }
+ private Action _outputHandler;
}
}
diff --git a/src/Commands/FormatPatch.cs b/src/Commands/FormatPatch.cs
index af5fb624..c139c477 100644
--- a/src/Commands/FormatPatch.cs
+++ b/src/Commands/FormatPatch.cs
@@ -1,12 +1,9 @@
-namespace SourceGit.Commands {
- ///
- /// 将Commit另存为Patch文件
- ///
+namespace SourceGit.Commands {
public class FormatPatch : Command {
-
- public FormatPatch(string repo, string commit, string path) {
- Cwd = repo;
- Args = $"format-patch {commit} -1 -o \"{path}\"";
+ public FormatPatch(string repo, string commit, string saveTo) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"format-patch {commit} -1 -o \"{saveTo}\"";
}
}
}
diff --git a/src/Commands/GC.cs b/src/Commands/GC.cs
index ceadb0b7..0c9d5761 100644
--- a/src/Commands/GC.cs
+++ b/src/Commands/GC.cs
@@ -1,21 +1,19 @@
using System;
namespace SourceGit.Commands {
- ///
- /// GC
- ///
public class GC : Command {
- private Action handler;
-
- public GC(string repo, Action onProgress) {
- Cwd = repo;
- Args = "gc";
+ public GC(string repo, Action outputHandler) {
+ _outputHandler = outputHandler;
+ WorkingDirectory = repo;
+ Context = repo;
TraitErrorAsOutput = true;
- handler = onProgress;
+ Args = "gc";
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
+
+ private Action _outputHandler;
}
}
diff --git a/src/Commands/GetRepositoryRootPath.cs b/src/Commands/GetRepositoryRootPath.cs
deleted file mode 100644
index c4dc6777..00000000
--- a/src/Commands/GetRepositoryRootPath.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace SourceGit.Commands {
- ///
- /// 取得一个库的根路径
- ///
- public class GetRepositoryRootPath : Command {
- public GetRepositoryRootPath(string path) {
- Cwd = path;
- Args = "rev-parse --show-toplevel";
- }
-
- public string Result() {
- var rs = ReadToEnd().Output;
- if (string.IsNullOrEmpty(rs)) return null;
- return rs.Trim();
- }
- }
-}
diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs
index b6707b06..8aaa45b6 100644
--- a/src/Commands/GitFlow.cs
+++ b/src/Commands/GitFlow.cs
@@ -1,24 +1,22 @@
-namespace SourceGit.Commands {
- ///
- /// Git-Flow命令
- ///
- public class GitFlow : Command {
+using System.Collections.Generic;
+namespace SourceGit.Commands {
+ public class GitFlow : Command {
public GitFlow(string repo) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
}
- public bool Init(string master, string develop, string feature, string release, string hotfix, string version) {
- var branches = new Branches(Cwd).Result();
+ public bool Init(List branches, string master, string develop, string feature, string release, string hotfix, string version) {
var current = branches.Find(x => x.IsCurrent);
var masterBranch = branches.Find(x => x.Name == master);
- if (masterBranch == null && current != null) new Branch(Cwd, develop).Create(current.Head);
+ if (masterBranch == null && current != null) Branch.Create(WorkingDirectory, master, current.Head);
var devBranch = branches.Find(x => x.Name == develop);
- if (devBranch == null && current != null) new Branch(Cwd, develop).Create(current.Head);
+ if (devBranch == null && current != null) Branch.Create(WorkingDirectory, develop, current.Head);
- var cmd = new Config(Cwd);
+ var cmd = new Config(WorkingDirectory);
cmd.Set("gitflow.branch.master", master);
cmd.Set("gitflow.branch.develop", develop);
cmd.Set("gitflow.prefix.feature", feature);
@@ -32,7 +30,7 @@ namespace SourceGit.Commands {
return Exec();
}
- public void Start(Models.GitFlowBranchType type, string name) {
+ public bool Start(Models.GitFlowBranchType type, string name) {
switch (type) {
case Models.GitFlowBranchType.Feature:
Args = $"flow feature start {name}";
@@ -44,13 +42,14 @@ namespace SourceGit.Commands {
Args = $"flow hotfix start {name}";
break;
default:
- return;
+ App.RaiseException(Context, "Bad branch type!!!");
+ return false;
}
- Exec();
+ return Exec();
}
- public void Finish(Models.GitFlowBranchType type, string name, bool keepBranch) {
+ public bool Finish(Models.GitFlowBranchType type, string name, bool keepBranch) {
var option = keepBranch ? "-k" : string.Empty;
switch (type) {
case Models.GitFlowBranchType.Feature:
@@ -63,10 +62,11 @@ namespace SourceGit.Commands {
Args = $"flow hotfix finish {option} {name} -m \"HOTFIX_DONE\"";
break;
default:
- return;
+ App.RaiseException(Context, "Bad branch type!!!");
+ return false;
}
- Exec();
+ return Exec();
}
}
}
diff --git a/src/Commands/Init.cs b/src/Commands/Init.cs
index 35dde5a2..f009ebe0 100644
--- a/src/Commands/Init.cs
+++ b/src/Commands/Init.cs
@@ -1,12 +1,8 @@
-namespace SourceGit.Commands {
-
- ///
- /// 初始化Git仓库
- ///
+namespace SourceGit.Commands {
public class Init : Command {
-
- public Init(string workDir) {
- Cwd = workDir;
+ public Init(string ctx, string dir) {
+ Context = ctx;
+ WorkingDirectory = dir;
Args = "init -q";
}
}
diff --git a/src/Commands/IsBinary.cs b/src/Commands/IsBinary.cs
new file mode 100644
index 00000000..d9096aa2
--- /dev/null
+++ b/src/Commands/IsBinary.cs
@@ -0,0 +1,18 @@
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+ public class IsBinary : Command {
+ private static readonly Regex REG_TEST = new Regex(@"^\-\s+\-\s+.*$");
+
+ public IsBinary(string repo, string commit, string path) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\"";
+ RaiseError = false;
+ }
+
+ public bool Result() {
+ return REG_TEST.IsMatch(ReadToEnd().StdOut);
+ }
+ }
+}
diff --git a/src/Commands/IsBinaryFile.cs b/src/Commands/IsBinaryFile.cs
deleted file mode 100644
index 68cbff62..00000000
--- a/src/Commands/IsBinaryFile.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Text.RegularExpressions;
-
-namespace SourceGit.Commands {
- ///
- /// 查询指定版本下的某文件是否是二进制文件
- ///
- public class IsBinaryFile : Command {
- private static readonly Regex REG_TEST = new Regex(@"^\-\s+\-\s+.*$");
- public IsBinaryFile(string repo, string commit, string path) {
- Cwd = repo;
- Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\"";
- }
-
- public bool Result() {
- return REG_TEST.IsMatch(ReadToEnd().Output);
- }
- }
-}
diff --git a/src/Commands/IsLFSFiltered.cs b/src/Commands/IsLFSFiltered.cs
new file mode 100644
index 00000000..39e24654
--- /dev/null
+++ b/src/Commands/IsLFSFiltered.cs
@@ -0,0 +1,15 @@
+namespace SourceGit.Commands {
+ public class IsLFSFiltered : Command {
+ public IsLFSFiltered(string repo, string path) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"check-attr -a -z \"{path}\"";
+ RaiseError = false;
+ }
+
+ public bool Result() {
+ var rs = ReadToEnd();
+ return rs.IsSuccess && rs.StdOut.Contains("filter\0lfs");
+ }
+ }
+}
diff --git a/src/Commands/LFS.cs b/src/Commands/LFS.cs
index 87d3e378..d632a25b 100644
--- a/src/Commands/LFS.cs
+++ b/src/Commands/LFS.cs
@@ -1,51 +1,40 @@
-using System;
+using System;
using System.IO;
namespace SourceGit.Commands {
- ///
- /// LFS相关
- ///
public class LFS {
- private string repo;
-
- private class PruneCmd : Command {
- private Action handler;
-
+ class PruneCmd : Command {
public PruneCmd(string repo, Action onProgress) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = "lfs prune";
TraitErrorAsOutput = true;
- handler = onProgress;
+ _outputHandler = onProgress;
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
+
+ private Action _outputHandler;
}
public LFS(string repo) {
- this.repo = repo;
+ _repo = repo;
}
public bool IsEnabled() {
- var path = Path.Combine(repo, ".git", "hooks", "pre-push");
+ var path = Path.Combine(_repo, ".git", "hooks", "pre-push");
if (!File.Exists(path)) return false;
var content = File.ReadAllText(path);
return content.Contains("git lfs pre-push");
}
- public bool IsFiltered(string path) {
- var cmd = new Command();
- cmd.Cwd = repo;
- cmd.Args = $"check-attr -a -z \"{path}\"";
-
- var rs = cmd.ReadToEnd();
- return rs.Output.Contains("filter\0lfs");
+ public void Prune(Action outputHandler) {
+ new PruneCmd(_repo, outputHandler).Exec();
}
- public void Prune(Action onProgress) {
- new PruneCmd(repo, onProgress).Exec();
- }
+ private string _repo;
}
}
diff --git a/src/Commands/LocalChanges.cs b/src/Commands/LocalChanges.cs
deleted file mode 100644
index edb156db..00000000
--- a/src/Commands/LocalChanges.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-
-namespace SourceGit.Commands {
- ///
- /// 取得本地工作副本变更
- ///
- public class LocalChanges : Command {
- private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
- private static readonly string[] UNTRACKED = new string[] { "no", "all" };
- private List changes = new List();
-
- public LocalChanges(string path, bool includeUntracked = true) {
- Cwd = path;
- Args = $"status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
- }
-
- public List Result() {
- Exec();
- return changes;
- }
-
- public override void OnReadline(string line) {
- var match = REG_FORMAT.Match(line);
- if (!match.Success) return;
- if (line.EndsWith("/", StringComparison.Ordinal)) return; // Ignore changes with git-worktree
-
- var change = new Models.Change() { Path = match.Groups[2].Value };
- var status = match.Groups[1].Value;
-
- switch (status) {
- case " M": change.Set(Models.Change.Status.None, Models.Change.Status.Modified); break;
- case " A": change.Set(Models.Change.Status.None, Models.Change.Status.Added); break;
- case " D": change.Set(Models.Change.Status.None, Models.Change.Status.Deleted); break;
- case " R": change.Set(Models.Change.Status.None, Models.Change.Status.Renamed); break;
- case " C": change.Set(Models.Change.Status.None, Models.Change.Status.Copied); break;
- case "M": change.Set(Models.Change.Status.Modified, Models.Change.Status.None); break;
- case "MM": change.Set(Models.Change.Status.Modified, Models.Change.Status.Modified); break;
- case "MD": change.Set(Models.Change.Status.Modified, Models.Change.Status.Deleted); break;
- case "A": change.Set(Models.Change.Status.Added, Models.Change.Status.None); break;
- case "AM": change.Set(Models.Change.Status.Added, Models.Change.Status.Modified); break;
- case "AD": change.Set(Models.Change.Status.Added, Models.Change.Status.Deleted); break;
- case "D": change.Set(Models.Change.Status.Deleted, Models.Change.Status.None); break;
- case "R": change.Set(Models.Change.Status.Renamed, Models.Change.Status.None); break;
- case "RM": change.Set(Models.Change.Status.Renamed, Models.Change.Status.Modified); break;
- case "RD": change.Set(Models.Change.Status.Renamed, Models.Change.Status.Deleted); break;
- case "C": change.Set(Models.Change.Status.Copied, Models.Change.Status.None); break;
- case "CM": change.Set(Models.Change.Status.Copied, Models.Change.Status.Modified); break;
- case "CD": change.Set(Models.Change.Status.Copied, Models.Change.Status.Deleted); break;
- case "DR": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Renamed); break;
- case "DC": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Copied); break;
- case "DD": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Deleted); break;
- case "AU": change.Set(Models.Change.Status.Added, Models.Change.Status.Unmerged); break;
- case "UD": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Deleted); break;
- case "UA": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Added); break;
- case "DU": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Unmerged); break;
- case "AA": change.Set(Models.Change.Status.Added, Models.Change.Status.Added); break;
- case "UU": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Unmerged); break;
- case "??": change.Set(Models.Change.Status.Untracked, Models.Change.Status.Untracked); break;
- default: return;
- }
-
- changes.Add(change);
- }
- }
-}
diff --git a/src/Commands/Merge.cs b/src/Commands/Merge.cs
index a44c72a1..9854e8c5 100644
--- a/src/Commands/Merge.cs
+++ b/src/Commands/Merge.cs
@@ -1,21 +1,19 @@
-using System;
+using System;
namespace SourceGit.Commands {
- ///
- /// 合并分支
- ///
public class Merge : Command {
- private Action handler = null;
-
- public Merge(string repo, string source, string mode, Action onProgress) {
- Cwd = repo;
- Args = $"merge --progress {source} {mode}";
+ public Merge(string repo, string source, string mode, Action outputHandler) {
+ _outputHandler = outputHandler;
+ WorkingDirectory = repo;
+ Context = repo;
TraitErrorAsOutput = true;
- handler = onProgress;
+ Args = $"merge --progress {source} {mode}";
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
+
+ private Action _outputHandler = null;
}
}
diff --git a/src/Commands/MergeTool.cs b/src/Commands/MergeTool.cs
new file mode 100644
index 00000000..4d02f029
--- /dev/null
+++ b/src/Commands/MergeTool.cs
@@ -0,0 +1,41 @@
+using System.IO;
+
+namespace SourceGit.Commands {
+ public static class MergeTool {
+ public static bool OpenForMerge(string repo, string tool, string mergeCmd, string file) {
+ if (string.IsNullOrWhiteSpace(tool) || string.IsNullOrWhiteSpace(mergeCmd)) {
+ App.RaiseException(repo, "Invalid external merge tool settings!");
+ return false;
+ }
+
+ if (!File.Exists(tool)) {
+ App.RaiseException(repo, $"Can NOT found external merge tool in '{tool}'!");
+ return false;
+ }
+
+ var cmd = new Command();
+ cmd.WorkingDirectory = repo;
+ cmd.RaiseError = false;
+ cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{tool}\\\" {mergeCmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit \"{file}\"";
+ return cmd.Exec();
+ }
+
+ public static bool OpenForDiff(string repo, string tool, string diffCmd, Models.DiffOption option) {
+ if (string.IsNullOrWhiteSpace(tool) || string.IsNullOrWhiteSpace(diffCmd)) {
+ App.RaiseException(repo, "Invalid external merge tool settings!");
+ return false;
+ }
+
+ if (!File.Exists(tool)) {
+ App.RaiseException(repo, $"Can NOT found external merge tool in '{tool}'!");
+ return false;
+ }
+
+ var cmd = new Command();
+ cmd.WorkingDirectory = repo;
+ cmd.RaiseError = false;
+ cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{tool}\\\" {diffCmd}\" difftool --tool=sourcegit --no-prompt {option}";
+ return cmd.Exec();
+ }
+ }
+}
diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs
index e2ab522a..f9112206 100644
--- a/src/Commands/Pull.cs
+++ b/src/Commands/Pull.cs
@@ -1,19 +1,12 @@
-using System;
+using System;
namespace SourceGit.Commands {
-
- ///
- /// 拉回
- ///
public class Pull : Command {
- private Action handler = null;
- private bool needStash = false;
-
- public Pull(string repo, string remote, string branch, bool useRebase, bool autoStash, Action onProgress) {
- Cwd = repo;
+ public Pull(string repo, string remote, string branch, bool useRebase, Action outputHandler) {
+ _outputHandler = outputHandler;
+ WorkingDirectory = repo;
+ Context = repo;
TraitErrorAsOutput = true;
- handler = onProgress;
- needStash = autoStash;
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
if (!string.IsNullOrEmpty(sshKey)) {
@@ -27,25 +20,10 @@ namespace SourceGit.Commands {
Args += $"{remote} {branch}";
}
- public bool Run() {
- if (needStash) {
- var changes = new LocalChanges(Cwd).Result();
- if (changes.Count > 0) {
- if (!new Stash(Cwd).Push(changes, "PULL_AUTO_STASH", true)) {
- return false;
- }
- } else {
- needStash = false;
- }
- }
-
- var succ = Exec();
- if (succ && needStash) new Stash(Cwd).Pop("stash@{0}");
- return succ;
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
- }
+ private Action _outputHandler;
}
}
diff --git a/src/Commands/Push.cs b/src/Commands/Push.cs
index 7784c581..8839666b 100644
--- a/src/Commands/Push.cs
+++ b/src/Commands/Push.cs
@@ -1,16 +1,12 @@
-using System;
+using System;
namespace SourceGit.Commands {
- ///
- /// 推送
- ///
public class Push : Command {
- private Action handler = null;
-
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool force, bool track, Action onProgress) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
TraitErrorAsOutput = true;
- handler = onProgress;
+ _outputHandler = onProgress;
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
if (!string.IsNullOrEmpty(sshKey)) {
@@ -28,8 +24,16 @@ namespace SourceGit.Commands {
Args += $"{remote} {local}:{remoteBranch}";
}
+ ///
+ /// Only used to delete a remote branch!!!!!!
+ ///
+ ///
+ ///
+ ///
public Push(string repo, string remote, string branch) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
+ TraitErrorAsOutput = true;
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
if (!string.IsNullOrEmpty(sshKey)) {
@@ -42,7 +46,8 @@ namespace SourceGit.Commands {
}
public Push(string repo, string remote, string tag, bool isDelete) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
if (!string.IsNullOrEmpty(sshKey)) {
@@ -56,8 +61,10 @@ namespace SourceGit.Commands {
Args += $"{remote} refs/tags/{tag}";
}
- public override void OnReadline(string line) {
- handler?.Invoke(line);
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
+
+ private Action _outputHandler = null;
}
}
diff --git a/src/Commands/Branches.cs b/src/Commands/QueryBranches.cs
similarity index 80%
rename from src/Commands/Branches.cs
rename to src/Commands/QueryBranches.cs
index 8dfdfae1..8c2db9a2 100644
--- a/src/Commands/Branches.cs
+++ b/src/Commands/QueryBranches.cs
@@ -1,31 +1,26 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
- ///
- /// 解析所有的分支
- ///
- public class Branches : Command {
+ public class QueryBranches : Command {
private static readonly string PREFIX_LOCAL = "refs/heads/";
private static readonly string PREFIX_REMOTE = "refs/remotes/";
- private static readonly string CMD = "branch -l --all -v --format=\"%(refname)$%(objectname)$%(HEAD)$%(upstream)$%(upstream:track)\"";
private static readonly Regex REG_AHEAD = new Regex(@"ahead (\d+)");
private static readonly Regex REG_BEHIND = new Regex(@"behind (\d+)");
- private List loaded = new List();
-
- public Branches(string path) {
- Cwd = path;
- Args = CMD;
+ public QueryBranches(string repo) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = "branch -l --all -v --format=\"%(refname)$%(objectname)$%(HEAD)$%(upstream)$%(upstream:track)\"";
}
public List Result() {
Exec();
- return loaded;
+ return _branches;
}
- public override void OnReadline(string line) {
+ protected override void OnReadline(string line) {
var parts = line.Split('$');
if (parts.Length != 5) return;
@@ -55,7 +50,7 @@ namespace SourceGit.Commands {
branch.Upstream = parts[3];
branch.UpstreamTrackStatus = ParseTrackStatus(parts[4]);
- loaded.Add(branch);
+ _branches.Add(branch);
}
private string ParseTrackStatus(string data) {
@@ -75,5 +70,7 @@ namespace SourceGit.Commands {
return track.Trim();
}
+
+ private List _branches = new List();
}
}
diff --git a/src/Commands/QueryCommitChanges.cs b/src/Commands/QueryCommitChanges.cs
new file mode 100644
index 00000000..7b252efd
--- /dev/null
+++ b/src/Commands/QueryCommitChanges.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+ public class QueryCommitChanges : Command {
+ private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
+
+ public QueryCommitChanges(string repo, string commitSHA) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"show --name-status {commitSHA}";
+ }
+
+ public List Result() {
+ Exec();
+ _changes.Sort((l, r) => l.Path.CompareTo(r.Path));
+ return _changes;
+ }
+
+ protected override void OnReadline(string line) {
+ var match = REG_FORMAT.Match(line);
+ if (!match.Success) return;
+
+ var change = new Models.Change() { Path = match.Groups[2].Value };
+ var status = match.Groups[1].Value;
+
+ switch (status[0]) {
+ case 'M': change.Set(Models.ChangeState.Modified); _changes.Add(change); break;
+ case 'A': change.Set(Models.ChangeState.Added); _changes.Add(change); break;
+ case 'D': change.Set(Models.ChangeState.Deleted); _changes.Add(change); break;
+ case 'R': change.Set(Models.ChangeState.Renamed); _changes.Add(change); break;
+ case 'C': change.Set(Models.ChangeState.Copied); _changes.Add(change); break;
+ }
+ }
+
+ private List _changes = new List();
+ }
+}
diff --git a/src/Commands/Commits.cs b/src/Commands/QueryCommits.cs
similarity index 92%
rename from src/Commands/Commits.cs
rename to src/Commands/QueryCommits.cs
index f70528db..0d6c31e2 100644
--- a/src/Commands/Commits.cs
+++ b/src/Commands/QueryCommits.cs
@@ -1,13 +1,8 @@
-using System;
+using System;
using System.Collections.Generic;
-using System.Linq;
namespace SourceGit.Commands {
-
- ///
- /// 取得提交列表
- ///
- public class Commits : Command {
+ public class QueryCommits : Command {
private static readonly string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
private static readonly string GPGSIG_END = " -----END PGP SIGNATURE-----";
@@ -17,8 +12,8 @@ namespace SourceGit.Commands {
private bool isHeadFounded = false;
private bool findFirstMerged = true;
- public Commits(string path, string limits, bool needFindHead = true) {
- Cwd = path;
+ public QueryCommits(string repo, string limits, bool needFindHead = true) {
+ WorkingDirectory = repo;
Args = "log --date-order --decorate=full --pretty=raw " + limits;
findFirstMerged = needFindHead;
}
@@ -38,7 +33,7 @@ namespace SourceGit.Commands {
return commits;
}
- public override void OnReadline(string line) {
+ protected override void OnReadline(string line) {
if (isSkipingGpgsig) {
if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal)) isSkipingGpgsig = false;
return;
@@ -137,10 +132,10 @@ namespace SourceGit.Commands {
}
private void MarkFirstMerged() {
- Args = $"log --since=\"{commits.Last().CommitterTimeStr}\" --format=\"%H\"";
+ Args = $"log --since=\"{commits[commits.Count - 1].CommitterTimeStr}\" --format=\"%H\"";
var rs = ReadToEnd();
- var shas = rs.Output.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
+ var shas = rs.StdOut.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (shas.Length == 0) return;
var set = new HashSet();
diff --git a/src/Commands/QueryFileContent.cs b/src/Commands/QueryFileContent.cs
index 04eab006..172134c5 100644
--- a/src/Commands/QueryFileContent.cs
+++ b/src/Commands/QueryFileContent.cs
@@ -1,26 +1,23 @@
-using System.Collections.Generic;
+using System.Text;
namespace SourceGit.Commands {
- ///
- /// 取得指定提交下的某文件内容
- ///
public class QueryFileContent : Command {
- private List lines = new List();
- private int added = 0;
-
- public QueryFileContent(string repo, string commit, string path) {
- Cwd = repo;
- Args = $"show {commit}:\"{path}\"";
+ public QueryFileContent(string repo, string revision, string file) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"show {revision}:\"{file}\"";
}
- public List Result() {
+ public string Result() {
Exec();
- return lines;
+ return _builder.ToString();
}
- public override void OnReadline(string line) {
- added++;
- lines.Add(new Models.TextLine() { Number = added, Data = line });
+ protected override void OnReadline(string line) {
+ _builder.Append(line);
+ _builder.Append('\n');
}
+
+ private StringBuilder _builder = new StringBuilder();
}
}
diff --git a/src/Commands/QueryFileSize.cs b/src/Commands/QueryFileSize.cs
new file mode 100644
index 00000000..e16bf9e6
--- /dev/null
+++ b/src/Commands/QueryFileSize.cs
@@ -0,0 +1,29 @@
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+ public class QueryFileSize : Command {
+ private static readonly Regex REG_FORMAT = new Regex(@"^\d+\s+\w+\s+[0-9a-f]+\s+(\d+)\s+.*$");
+
+ public QueryFileSize(string repo, string file, string revision) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"ls-tree {revision} -l -- {file}";
+ }
+
+ public long Result() {
+ if (_result != 0) return _result;
+
+ var rs = ReadToEnd();
+ if (rs.IsSuccess) {
+ var match = REG_FORMAT.Match(rs.StdOut);
+ if (match.Success) {
+ return long.Parse(match.Groups[1].Value);
+ }
+ }
+
+ return 0;
+ }
+
+ private long _result = 0;
+ }
+}
diff --git a/src/Commands/QueryFileSizeChange.cs b/src/Commands/QueryFileSizeChange.cs
deleted file mode 100644
index 70792e2a..00000000
--- a/src/Commands/QueryFileSizeChange.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System.IO;
-
-namespace SourceGit.Commands {
- ///
- /// 查询文件大小变化
- ///
- public class QueryFileSizeChange {
-
- class QuerySizeCmd : Command {
- public QuerySizeCmd(string repo, string path, string revision) {
- Cwd = repo;
- Args = $"cat-file -s {revision}:\"{path}\"";
- }
-
- public long Result() {
- string data = ReadToEnd().Output;
- long size;
- if (!long.TryParse(data, out size)) size = 0;
- return size;
- }
- }
-
- private Models.FileSizeChange change = new Models.FileSizeChange();
-
- public QueryFileSizeChange(string repo, string[] revisions, string path, string orgPath) {
- if (revisions.Length == 0) {
- change.NewSize = new FileInfo(Path.Combine(repo, path)).Length;
- change.OldSize = new QuerySizeCmd(repo, path, "HEAD").Result();
- } else if (revisions.Length == 1) {
- change.NewSize = new QuerySizeCmd(repo, path, "HEAD").Result();
- if (string.IsNullOrEmpty(orgPath)) {
- change.OldSize = new QuerySizeCmd(repo, path, revisions[0]).Result();
- } else {
- change.OldSize = new QuerySizeCmd(repo, orgPath, revisions[0]).Result();
- }
- } else {
- change.NewSize = new QuerySizeCmd(repo, path, revisions[1]).Result();
- if (string.IsNullOrEmpty(orgPath)) {
- change.OldSize = new QuerySizeCmd(repo, path, revisions[0]).Result();
- } else {
- change.OldSize = new QuerySizeCmd(repo, orgPath, revisions[0]).Result();
- }
- }
- }
-
- public Models.FileSizeChange Result() {
- return change;
- }
- }
-}
diff --git a/src/Commands/QueryGitDir.cs b/src/Commands/QueryGitDir.cs
index af45273d..dcdc216b 100644
--- a/src/Commands/QueryGitDir.cs
+++ b/src/Commands/QueryGitDir.cs
@@ -1,23 +1,20 @@
-using System.IO;
+using System.IO;
namespace SourceGit.Commands {
-
- ///
- /// 取得GitDir
- ///
public class QueryGitDir : Command {
public QueryGitDir(string workDir) {
- Cwd = workDir;
+ WorkingDirectory = workDir;
Args = "rev-parse --git-dir";
+ RaiseError = false;
}
public string Result() {
- var rs = ReadToEnd().Output;
+ var rs = ReadToEnd().StdOut;
if (string.IsNullOrEmpty(rs)) return null;
rs = rs.Trim();
if (Path.IsPathRooted(rs)) return rs;
- return Path.GetFullPath(Path.Combine(Cwd, rs));
+ return Path.GetFullPath(Path.Combine(WorkingDirectory, rs));
}
}
}
diff --git a/src/Commands/QueryLFSObject.cs b/src/Commands/QueryLFSObject.cs
deleted file mode 100644
index 8db8bbe0..00000000
--- a/src/Commands/QueryLFSObject.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-
-namespace SourceGit.Commands {
- ///
- /// 取得一个LFS对象的信息
- ///
- public class QueryLFSObject : Command {
- private Models.LFSObject obj = new Models.LFSObject();
-
- public QueryLFSObject(string repo, string commit, string path) {
- Cwd = repo;
- Args = $"show {commit}:\"{path}\"";
- }
-
- public Models.LFSObject Result() {
- Exec();
- return obj;
- }
-
- public override void OnReadline(string line) {
- if (line.StartsWith("oid sha256:", StringComparison.Ordinal)) {
- obj.OID = line.Substring(11).Trim();
- } else if (line.StartsWith("size")) {
- obj.Size = int.Parse(line.Substring(4).Trim());
- }
- }
- }
-}
diff --git a/src/Commands/QueryLFSObjectChange.cs b/src/Commands/QueryLFSObjectChange.cs
deleted file mode 100644
index 5f9e1631..00000000
--- a/src/Commands/QueryLFSObjectChange.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-namespace SourceGit.Commands {
- ///
- /// 查询LFS对象变更
- ///
- public class QueryLFSObjectChange : Command {
- private Models.LFSChange change = new Models.LFSChange();
-
- public QueryLFSObjectChange(string repo, string args) {
- Cwd = repo;
- Args = $"diff --ignore-cr-at-eol {args}";
- }
-
- public Models.LFSChange Result() {
- Exec();
- return change;
- }
-
- public override void OnReadline(string line) {
- var ch = line[0];
- if (ch == '-') {
- if (change.Old == null) change.Old = new Models.LFSObject();
- line = line.Substring(1);
- if (line.StartsWith("oid sha256:")) {
- change.Old.OID = line.Substring(11);
- } else if (line.StartsWith("size ")) {
- change.Old.Size = int.Parse(line.Substring(5));
- }
- } else if (ch == '+') {
- if (change.New == null) change.New = new Models.LFSObject();
- line = line.Substring(1);
- if (line.StartsWith("oid sha256:")) {
- change.New.OID = line.Substring(11);
- } else if (line.StartsWith("size ")) {
- change.New.Size = int.Parse(line.Substring(5));
- }
- } else if (line.StartsWith(" size ")) {
- change.New.Size = change.Old.Size = int.Parse(line.Substring(6));
- }
- }
- }
-}
diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs
new file mode 100644
index 00000000..260885d5
--- /dev/null
+++ b/src/Commands/QueryLocalChanges.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+ public class QueryLocalChanges : Command {
+ private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
+ private static readonly string[] UNTRACKED = [ "no", "all" ];
+
+ public QueryLocalChanges(string repo, bool includeUntracked = true) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
+ }
+
+ public List Result() {
+ Exec();
+ return _changes;
+ }
+
+ protected override void OnReadline(string line) {
+ var match = REG_FORMAT.Match(line);
+ if (!match.Success) return;
+ if (line.EndsWith("/", StringComparison.Ordinal)) return; // Ignore changes with git-worktree
+
+ var change = new Models.Change() { Path = match.Groups[2].Value };
+ var status = match.Groups[1].Value;
+
+ switch (status) {
+ case " M": change.Set(Models.ChangeState.None, Models.ChangeState.Modified); break;
+ case " A": change.Set(Models.ChangeState.None, Models.ChangeState.Added); break;
+ case " D": change.Set(Models.ChangeState.None, Models.ChangeState.Deleted); break;
+ case " R": change.Set(Models.ChangeState.None, Models.ChangeState.Renamed); break;
+ case " C": change.Set(Models.ChangeState.None, Models.ChangeState.Copied); break;
+ case "M": change.Set(Models.ChangeState.Modified, Models.ChangeState.None); break;
+ case "MM": change.Set(Models.ChangeState.Modified, Models.ChangeState.Modified); break;
+ case "MD": change.Set(Models.ChangeState.Modified, Models.ChangeState.Deleted); break;
+ case "A": change.Set(Models.ChangeState.Added, Models.ChangeState.None); break;
+ case "AM": change.Set(Models.ChangeState.Added, Models.ChangeState.Modified); break;
+ case "AD": change.Set(Models.ChangeState.Added, Models.ChangeState.Deleted); break;
+ case "D": change.Set(Models.ChangeState.Deleted, Models.ChangeState.None); break;
+ case "R": change.Set(Models.ChangeState.Renamed, Models.ChangeState.None); break;
+ case "RM": change.Set(Models.ChangeState.Renamed, Models.ChangeState.Modified); break;
+ case "RD": change.Set(Models.ChangeState.Renamed, Models.ChangeState.Deleted); break;
+ case "C": change.Set(Models.ChangeState.Copied, Models.ChangeState.None); break;
+ case "CM": change.Set(Models.ChangeState.Copied, Models.ChangeState.Modified); break;
+ case "CD": change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted); break;
+ case "DR": change.Set(Models.ChangeState.Deleted, Models.ChangeState.Renamed); break;
+ case "DC": change.Set(Models.ChangeState.Deleted, Models.ChangeState.Copied); break;
+ case "DD": change.Set(Models.ChangeState.Deleted, Models.ChangeState.Deleted); break;
+ case "AU": change.Set(Models.ChangeState.Added, Models.ChangeState.Unmerged); break;
+ case "UD": change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Deleted); break;
+ case "UA": change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Added); break;
+ case "DU": change.Set(Models.ChangeState.Deleted, Models.ChangeState.Unmerged); break;
+ case "AA": change.Set(Models.ChangeState.Added, Models.ChangeState.Added); break;
+ case "UU": change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Unmerged); break;
+ case "??": change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked); break;
+ default: return;
+ }
+
+ _changes.Add(change);
+ }
+
+ private List _changes = new List();
+ }
+}
diff --git a/src/Commands/Remotes.cs b/src/Commands/QueryRemotes.cs
similarity index 54%
rename from src/Commands/Remotes.cs
rename to src/Commands/QueryRemotes.cs
index 1866b7f2..e8e21b4a 100644
--- a/src/Commands/Remotes.cs
+++ b/src/Commands/QueryRemotes.cs
@@ -1,25 +1,22 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
- ///
- /// 获取远程列表
- ///
- public class Remotes : Command {
+ public class QueryRemotes : Command {
private static readonly Regex REG_REMOTE = new Regex(@"^([\w\.\-]+)\s*(\S+).*$");
- private List loaded = new List();
- public Remotes(string repo) {
- Cwd = repo;
+ public QueryRemotes(string repo) {
+ WorkingDirectory = repo;
+ Context = repo;
Args = "remote -v";
}
public List Result() {
Exec();
- return loaded;
+ return _loaded;
}
- public override void OnReadline(string line) {
+ protected override void OnReadline(string line) {
var match = REG_REMOTE.Match(line);
if (!match.Success) return;
@@ -28,8 +25,10 @@ namespace SourceGit.Commands {
URL = match.Groups[2].Value,
};
- if (loaded.Find(x => x.Name == remote.Name) != null) return;
- loaded.Add(remote);
+ if (_loaded.Find(x => x.Name == remote.Name) != null) return;
+ _loaded.Add(remote);
}
+
+ private List _loaded = new List();
}
}
diff --git a/src/Commands/QueryRepositoryRootPath.cs b/src/Commands/QueryRepositoryRootPath.cs
new file mode 100644
index 00000000..51a1a15a
--- /dev/null
+++ b/src/Commands/QueryRepositoryRootPath.cs
@@ -0,0 +1,15 @@
+namespace SourceGit.Commands {
+ public class QueryRepositoryRootPath : Command {
+ public QueryRepositoryRootPath(string path) {
+ WorkingDirectory = path;
+ Args = "rev-parse --show-toplevel";
+ RaiseError = false;
+ }
+
+ public string Result() {
+ var rs = ReadToEnd().StdOut;
+ if (string.IsNullOrEmpty(rs)) return null;
+ return rs.Trim();
+ }
+ }
+}
diff --git a/src/Commands/RevisionObjects.cs b/src/Commands/QueryRevisionObjects.cs
similarity index 77%
rename from src/Commands/RevisionObjects.cs
rename to src/Commands/QueryRevisionObjects.cs
index 25a190ec..b0c48f2a 100644
--- a/src/Commands/RevisionObjects.cs
+++ b/src/Commands/QueryRevisionObjects.cs
@@ -1,16 +1,14 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
- ///
- /// 取出指定Revision下的文件列表
- ///
- public class RevisionObjects : Command {
+ public class QueryRevisionObjects : Command {
private static readonly Regex REG_FORMAT = new Regex(@"^\d+\s+(\w+)\s+([0-9a-f]+)\s+(.*)$");
private List objects = new List();
- public RevisionObjects(string cwd, string sha) {
- Cwd = cwd;
+ public QueryRevisionObjects(string repo, string sha) {
+ WorkingDirectory = repo;
+ Context = repo;
Args = $"ls-tree -r {sha}";
}
@@ -19,7 +17,7 @@ namespace SourceGit.Commands {
return objects;
}
- public override void OnReadline(string line) {
+ protected override void OnReadline(string line) {
var match = REG_FORMAT.Match(line);
if (!match.Success) return;
diff --git a/src/Commands/QueryStashChanges.cs b/src/Commands/QueryStashChanges.cs
new file mode 100644
index 00000000..c2d50d45
--- /dev/null
+++ b/src/Commands/QueryStashChanges.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+ public class QueryStashChanges : Command {
+ private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
+
+ public QueryStashChanges(string repo, string sha) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"diff --name-status --pretty=format: {sha}^ {sha}";
+ }
+
+ public List Result() {
+ Exec();
+ return _changes;
+ }
+
+ protected override void OnReadline(string line) {
+ var match = REG_FORMAT.Match(line);
+ if (!match.Success) return;
+
+ var change = new Models.Change() { Path = match.Groups[2].Value };
+ var status = match.Groups[1].Value;
+
+ switch (status[0]) {
+ case 'M': change.Set(Models.ChangeState.Modified); _changes.Add(change); break;
+ case 'A': change.Set(Models.ChangeState.Added); _changes.Add(change); break;
+ case 'D': change.Set(Models.ChangeState.Deleted); _changes.Add(change); break;
+ case 'R': change.Set(Models.ChangeState.Renamed); _changes.Add(change); break;
+ case 'C': change.Set(Models.ChangeState.Copied); _changes.Add(change); break;
+ }
+ }
+
+ private List _changes = new List();
+ }
+}
diff --git a/src/Commands/Stashes.cs b/src/Commands/QueryStashes.cs
similarity index 52%
rename from src/Commands/Stashes.cs
rename to src/Commands/QueryStashes.cs
index 5668ff64..7e159500 100644
--- a/src/Commands/Stashes.cs
+++ b/src/Commands/QueryStashes.cs
@@ -1,48 +1,47 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
- ///
- /// 解析当前仓库中的贮藏
- ///
- public class Stashes : Command {
+ public class QueryStashes : Command {
private static readonly Regex REG_STASH = new Regex(@"^Reflog: refs/(stash@\{\d+\}).*$");
- private List parsed = new List();
- private Models.Stash current = null;
-
- public Stashes(string path) {
- Cwd = path;
+
+ public QueryStashes(string repo) {
+ WorkingDirectory = repo;
+ Context = repo;
Args = "stash list --pretty=raw";
}
public List Result() {
Exec();
- if (current != null) parsed.Add(current);
- return parsed;
+ if (_current != null) _stashes.Add(_current);
+ return _stashes;
}
- public override void OnReadline(string line) {
+ protected override void OnReadline(string line) {
if (line.StartsWith("commit ", StringComparison.Ordinal)) {
- if (current != null && !string.IsNullOrEmpty(current.Name)) parsed.Add(current);
- current = new Models.Stash() { SHA = line.Substring(7, 8) };
+ if (_current != null && !string.IsNullOrEmpty(_current.Name)) _stashes.Add(_current);
+ _current = new Models.Stash() { SHA = line.Substring(7, 8) };
return;
}
- if (current == null) return;
+ if (_current == null) return;
if (line.StartsWith("Reflog: refs/stash@", StringComparison.Ordinal)) {
var match = REG_STASH.Match(line);
- if (match.Success) current.Name = match.Groups[1].Value;
+ if (match.Success) _current.Name = match.Groups[1].Value;
} else if (line.StartsWith("Reflog message: ", StringComparison.Ordinal)) {
- current.Message = line.Substring(16);
+ _current.Message = line.Substring(16);
} else if (line.StartsWith("author ", StringComparison.Ordinal)) {
Models.User user = Models.User.Invalid;
ulong time = 0;
Models.Commit.ParseUserAndTime(line.Substring(7), ref user, ref time);
- current.Author = user;
- current.Time = time;
+ _current.Author = user;
+ _current.Time = time;
}
}
+
+ private List _stashes = new List();
+ private Models.Stash _current = null;
}
}
diff --git a/src/Commands/QuerySubmodules.cs b/src/Commands/QuerySubmodules.cs
new file mode 100644
index 00000000..0013ff49
--- /dev/null
+++ b/src/Commands/QuerySubmodules.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+ public class QuerySubmodules : Command {
+ private readonly Regex REG_FORMAT = new Regex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$");
+
+ public QuerySubmodules(string repo) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = "submodule status";
+ }
+
+ public List Result() {
+ Exec();
+ return _submodules;
+ }
+
+ protected override void OnReadline(string line) {
+ var match = REG_FORMAT.Match(line);
+ if (!match.Success) return;
+ _submodules.Add(match.Groups[1].Value);
+ }
+
+ private List _submodules = new List();
+ }
+}
diff --git a/src/Commands/QueryTags.cs b/src/Commands/QueryTags.cs
new file mode 100644
index 00000000..470301a3
--- /dev/null
+++ b/src/Commands/QueryTags.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+
+namespace SourceGit.Commands {
+ public class QueryTags : Command {
+ public QueryTags(string repo) {
+ Context = repo;
+ WorkingDirectory = repo;
+ Args = "for-each-ref --sort=-creatordate --format=\"$%(refname:short)$%(objectname)$%(*objectname)\" refs/tags";
+ }
+
+ public List Result() {
+ Exec();
+ return _loaded;
+ }
+
+ protected override void OnReadline(string line) {
+ var subs = line.Split(new char[] { '$' }, StringSplitOptions.RemoveEmptyEntries);
+ if (subs.Length == 2) {
+ _loaded.Add(new Models.Tag() {
+ Name = subs[0],
+ SHA = subs[1],
+ });
+ } else if (subs.Length == 3) {
+ _loaded.Add(new Models.Tag() {
+ Name = subs[0],
+ SHA = subs[2],
+ });
+ }
+ }
+
+ private List _loaded = new List();
+ }
+}
diff --git a/src/Commands/Rebase.cs b/src/Commands/Rebase.cs
index 791233af..88d304aa 100644
--- a/src/Commands/Rebase.cs
+++ b/src/Commands/Rebase.cs
@@ -1,11 +1,8 @@
-namespace SourceGit.Commands {
- ///
- /// 变基命令
- ///
+namespace SourceGit.Commands {
public class Rebase : Command {
-
public Rebase(string repo, string basedOn, bool autoStash) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = "rebase ";
if (autoStash) Args += "--autostash ";
Args += basedOn;
diff --git a/src/Commands/Remote.cs b/src/Commands/Remote.cs
index 09326afd..1a526045 100644
--- a/src/Commands/Remote.cs
+++ b/src/Commands/Remote.cs
@@ -1,11 +1,8 @@
-namespace SourceGit.Commands {
- ///
- /// 远程操作
- ///
+namespace SourceGit.Commands {
public class Remote : Command {
-
public Remote(string repo) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
}
public bool Add(string name, string url) {
diff --git a/src/Commands/Reset.cs b/src/Commands/Reset.cs
index b60ca174..445890bd 100644
--- a/src/Commands/Reset.cs
+++ b/src/Commands/Reset.cs
@@ -1,33 +1,32 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Text;
namespace SourceGit.Commands {
- ///
- /// 重置命令
- ///
public class Reset : Command {
-
public Reset(string repo) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = "reset";
}
- public Reset(string repo, string revision, string mode) {
- Cwd = repo;
- Args = $"reset {mode} {revision}";
- }
+ public Reset(string repo, List changes) {
+ WorkingDirectory = repo;
+ Context = repo;
- public Reset(string repo, List files) {
- Cwd = repo;
-
- StringBuilder builder = new StringBuilder();
+ var builder = new StringBuilder();
builder.Append("reset --");
- foreach (var f in files) {
+ foreach (var c in changes) {
builder.Append(" \"");
- builder.Append(f);
+ builder.Append(c.Path);
builder.Append("\"");
}
Args = builder.ToString();
}
+
+ public Reset(string repo, string revision, string mode) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"reset {mode} {revision}";
+ }
}
}
diff --git a/src/Commands/Revert.cs b/src/Commands/Revert.cs
index 2a656fc8..6b9549f5 100644
--- a/src/Commands/Revert.cs
+++ b/src/Commands/Revert.cs
@@ -1,11 +1,8 @@
-namespace SourceGit.Commands {
- ///
- /// 撤销提交
- ///
+namespace SourceGit.Commands {
public class Revert : Command {
-
public Revert(string repo, string commit, bool autoCommit) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
Args = $"revert {commit} --no-edit";
if (!autoCommit) Args += " --no-commit";
}
diff --git a/src/Commands/Reword.cs b/src/Commands/Reword.cs
deleted file mode 100644
index 3296c37c..00000000
--- a/src/Commands/Reword.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.IO;
-
-namespace SourceGit.Commands {
- ///
- /// 编辑HEAD的提交信息
- ///
- public class Reword : Command {
- public Reword(string repo, string msg) {
- var tmp = Path.GetTempFileName();
- File.WriteAllText(tmp, msg);
-
- Cwd = repo;
- Args = $"commit --amend --allow-empty --file=\"{tmp}\"";
- }
- }
-}
diff --git a/src/Commands/SaveChangesAsPatch.cs b/src/Commands/SaveChangesAsPatch.cs
new file mode 100644
index 00000000..fa8203ac
--- /dev/null
+++ b/src/Commands/SaveChangesAsPatch.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+
+namespace SourceGit.Commands {
+ public static class SaveChangesAsPatch {
+ public static bool Exec(string repo, List changes, bool isUnstaged, string saveTo) {
+ using (var sw = File.Create(saveTo)) {
+ foreach (var change in changes) {
+ if (!ProcessSingleChange(repo, new Models.DiffOption(change, isUnstaged), sw)) return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool ProcessSingleChange(string repo, Models.DiffOption opt, FileStream writer) {
+ var starter = new ProcessStartInfo();
+ starter.WorkingDirectory = repo;
+ starter.FileName = Native.OS.GitExecutableFile;
+ starter.Arguments = $"diff --ignore-cr-at-eol --unified=4 {opt}";
+ starter.UseShellExecute = false;
+ starter.CreateNoWindow = true;
+ starter.WindowStyle = ProcessWindowStyle.Hidden;
+ starter.RedirectStandardOutput = true;
+
+ try {
+ var proc = new Process() { StartInfo = starter };
+ proc.Start();
+ proc.StandardOutput.BaseStream.CopyTo(writer);
+ proc.WaitForExit();
+ var rs = proc.ExitCode == 0;
+ proc.Close();
+
+ return rs;
+ } catch (Exception e) {
+ App.RaiseException(repo, "Save change to patch failed: " + e.Message);
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Commands/SaveChangesToPatch.cs b/src/Commands/SaveChangesToPatch.cs
deleted file mode 100644
index b40b9bee..00000000
--- a/src/Commands/SaveChangesToPatch.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.IO;
-
-namespace SourceGit.Commands {
- ///
- /// 将Changes保存到文件流中
- ///
- public class SaveChangeToStream : Command {
- private StreamWriter writer = null;
-
- public SaveChangeToStream(string repo, Models.Change change, StreamWriter to) {
- Cwd = repo;
- if (change.WorkTree == Models.Change.Status.Added || change.WorkTree == Models.Change.Status.Untracked) {
- Args = $"diff --no-index --no-ext-diff --find-renames -- /dev/null \"{change.Path}\"";
- } else {
- var pathspec = $"\"{change.Path}\"";
- if (!string.IsNullOrEmpty(change.OriginalPath)) pathspec = $"\"{change.OriginalPath}\" \"{change.Path}\"";
- Args = $"diff --binary --no-ext-diff --find-renames --full-index -- {pathspec}";
- }
- writer = to;
- }
-
- public override void OnReadline(string line) {
- writer.WriteLine(line);
- }
- }
-}
diff --git a/src/Commands/SaveRevisionFile.cs b/src/Commands/SaveRevisionFile.cs
index a83fc618..61f49df1 100644
--- a/src/Commands/SaveRevisionFile.cs
+++ b/src/Commands/SaveRevisionFile.cs
@@ -1,44 +1,60 @@
+using System;
using System.Diagnostics;
using System.IO;
namespace SourceGit.Commands {
- ///
- /// 保存指定版本的文件
- ///
- public class SaveRevisionFile {
- private string cwd = "";
- private string bat = "";
-
- public SaveRevisionFile(string repo, string path, string sha, string saveTo) {
- var tmp = Path.GetTempFileName();
- var cmd = $"\"{Models.Preference.Instance.Git.Path}\" --no-pager ";
-
- var isLFS = new LFS(repo).IsFiltered(path);
- if (isLFS) {
- cmd += $"show {sha}:\"{path}\" > {tmp}.lfs\n";
- cmd += $"\"{Models.Preference.Instance.Git.Path}\" --no-pager lfs smudge < {tmp}.lfs > \"{saveTo}\"\n";
+ public static class SaveRevisionFile {
+ public static void Run(string repo, string revision, string file, string saveTo) {
+ var isLFSFiltered = new IsLFSFiltered(repo, file).Result();
+ if (isLFSFiltered) {
+ var tmpFile = saveTo + ".tmp";
+ if (ExecCmd(repo, $"show {revision}:\"{file}\"", tmpFile)) {
+ ExecCmd(repo, $"lfs smudge", saveTo, tmpFile);
+ }
+ File.Delete(tmpFile);
} else {
- cmd += $"show {sha}:\"{path}\" > \"{saveTo}\"\n";
+ ExecCmd(repo, $"show {revision}:\"{file}\"", saveTo);
}
-
- cwd = repo;
- bat = tmp + ".bat";
-
- File.WriteAllText(bat, cmd);
}
-
- public void Exec() {
+
+ private static bool ExecCmd(string repo, string args, string outputFile, string inputFile = null) {
var starter = new ProcessStartInfo();
- starter.FileName = bat;
- starter.WorkingDirectory = cwd;
+ starter.WorkingDirectory = repo;
+ starter.FileName = Native.OS.GitExecutableFile;
+ starter.Arguments = args;
+ starter.UseShellExecute = false;
starter.CreateNoWindow = true;
starter.WindowStyle = ProcessWindowStyle.Hidden;
+ starter.RedirectStandardInput = true;
+ starter.RedirectStandardOutput = true;
+ starter.RedirectStandardError = true;
- var proc = Process.Start(starter);
- proc.WaitForExit();
- proc.Close();
+ using (var sw = File.OpenWrite(outputFile)) {
+ try {
+ var proc = new Process() { StartInfo = starter };
+ proc.Start();
- File.Delete(bat);
+ if (inputFile != null) {
+ using (StreamReader sr = new StreamReader(inputFile)) {
+ while (true) {
+ var line = sr.ReadLine();
+ if (line == null) break;
+ proc.StandardInput.WriteLine(line);
+ }
+ }
+ }
+
+ proc.StandardOutput.BaseStream.CopyTo(sw);
+ proc.WaitForExit();
+ var rs = proc.ExitCode == 0;
+ proc.Close();
+
+ return rs;
+ } catch (Exception e) {
+ App.RaiseException(repo, "Save file failed: " + e.Message);
+ return false;
+ }
+ }
}
}
}
diff --git a/src/Commands/Stash.cs b/src/Commands/Stash.cs
index 0a923f2c..720129a7 100644
--- a/src/Commands/Stash.cs
+++ b/src/Commands/Stash.cs
@@ -1,68 +1,49 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.IO;
namespace SourceGit.Commands {
- ///
- /// 单个贮藏相关操作
- ///
public class Stash : Command {
-
public Stash(string repo) {
- Cwd = repo;
+ WorkingDirectory = repo;
+ Context = repo;
}
- public bool Push(List changes, string message, bool bFull) {
- if (bFull) {
- var needAdd = new List();
- foreach (var c in changes) {
- if (c.WorkTree == Models.Change.Status.Added || c.WorkTree == Models.Change.Status.Untracked) {
- needAdd.Add(c.Path);
- if (needAdd.Count > 10) {
- new Add(Cwd, needAdd).Exec();
- needAdd.Clear();
- }
+ public bool Push(string message) {
+ Args = $"stash push -m \"{message}\"";
+ return Exec();
+ }
+
+ public bool Push(List changes, string message) {
+ var temp = Path.GetTempFileName();
+ var stream = new FileStream(temp, FileMode.Create);
+ var writer = new StreamWriter(stream);
+
+ var needAdd = new List();
+ foreach (var c in changes) {
+ writer.WriteLine(c.Path);
+
+ if (c.WorkTree == Models.ChangeState.Added || c.WorkTree == Models.ChangeState.Untracked) {
+ needAdd.Add(c);
+ if (needAdd.Count > 10) {
+ new Add(WorkingDirectory, needAdd).Exec();
+ needAdd.Clear();
}
}
+ }
+ if (needAdd.Count > 0) {
+ new Add(WorkingDirectory, needAdd).Exec();
+ needAdd.Clear();
+ }
- if (needAdd.Count > 0) {
- new Add(Cwd, needAdd).Exec();
- needAdd.Clear();
- }
+ writer.Flush();
+ stream.Flush();
+ writer.Close();
+ stream.Close();
- Args = $"stash push -m \"{message}\"";
- return Exec();
- } else {
- var temp = Path.GetTempFileName();
- var stream = new FileStream(temp, FileMode.Create);
- var writer = new StreamWriter(stream);
-
- var needAdd = new List();
- foreach (var c in changes) {
- writer.WriteLine(c.Path);
-
- if (c.WorkTree == Models.Change.Status.Added || c.WorkTree == Models.Change.Status.Untracked) {
- needAdd.Add(c.Path);
- if (needAdd.Count > 10) {
- new Add(Cwd, needAdd).Exec();
- needAdd.Clear();
- }
- }
- }
- if (needAdd.Count > 0) {
- new Add(Cwd, needAdd).Exec();
- needAdd.Clear();
- }
-
- writer.Flush();
- stream.Flush();
- writer.Close();
- stream.Close();
-
- Args = $"stash push -m \"{message}\" --pathspec-from-file=\"{temp}\"";
- var succ = Exec();
- File.Delete(temp);
- return succ;
- }
+ Args = $"stash push -m \"{message}\" --pathspec-from-file=\"{temp}\"";
+ var succ = Exec();
+ File.Delete(temp);
+ return succ;
}
public bool Apply(string name) {
@@ -79,5 +60,10 @@ namespace SourceGit.Commands {
Args = $"stash drop -q {name}";
return Exec();
}
+
+ public bool Clear() {
+ Args = "stash clear";
+ return Exec();
+ }
}
}
diff --git a/src/Commands/StashChanges.cs b/src/Commands/StashChanges.cs
deleted file mode 100644
index 459a3776..00000000
--- a/src/Commands/StashChanges.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-
-namespace SourceGit.Commands {
- ///
- /// 查看Stash中的修改
- ///
- public class StashChanges : Command {
- private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
- private List changes = new List();
-
- public StashChanges(string repo, string sha) {
- Cwd = repo;
- Args = $"diff --name-status --pretty=format: {sha}^ {sha}";
- }
-
- public List Result() {
- Exec();
- return changes;
- }
-
- public override void OnReadline(string line) {
- var match = REG_FORMAT.Match(line);
- if (!match.Success) return;
-
- var change = new Models.Change() { Path = match.Groups[2].Value };
- var status = match.Groups[1].Value;
-
- switch (status[0]) {
- case 'M': change.Set(Models.Change.Status.Modified); changes.Add(change); break;
- case 'A': change.Set(Models.Change.Status.Added); changes.Add(change); break;
- case 'D': change.Set(Models.Change.Status.Deleted); changes.Add(change); break;
- case 'R': change.Set(Models.Change.Status.Renamed); changes.Add(change); break;
- case 'C': change.Set(Models.Change.Status.Copied); changes.Add(change); break;
- }
- }
- }
-}
diff --git a/src/Commands/SubTree.cs b/src/Commands/SubTree.cs
deleted file mode 100644
index 62f632fe..00000000
--- a/src/Commands/SubTree.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.IO;
-
-namespace SourceGit.Commands {
- ///
- /// 子树相关操作
- ///
- public class SubTree : Command {
- private Action handler = null;
-
- public SubTree(string repo) {
- Cwd = repo;
- TraitErrorAsOutput = true;
- }
-
- public override void OnReadline(string line) {
- handler?.Invoke(line);
- }
-
- public bool Add(string prefix, string source, string revision, bool squash, Action onProgress) {
- var path = Path.Combine(Cwd, prefix);
- if (Directory.Exists(path)) return true;
-
- handler = onProgress;
- Args = $"subtree add --prefix=\"{prefix}\" {source} {revision}";
- if (squash) Args += " --squash";
- return Exec();
- }
-
- public void Pull(string prefix, string source, string branch, bool squash, Action onProgress) {
- handler = onProgress;
- Args = $"subtree pull --prefix=\"{prefix}\" {source} {branch}";
- if (squash) Args += " --squash";
- Exec();
- }
-
- public void Push(string prefix, string source, string branch, Action onProgress) {
- handler = onProgress;
- Args = $"subtree push --prefix=\"{prefix}\" {source} {branch}";
- Exec();
- }
- }
-}
diff --git a/src/Commands/Submodule.cs b/src/Commands/Submodule.cs
index bf3ba87d..a6aeee83 100644
--- a/src/Commands/Submodule.cs
+++ b/src/Commands/Submodule.cs
@@ -1,25 +1,22 @@
-using System;
+using System;
namespace SourceGit.Commands {
- ///
- /// 子模块
- ///
public class Submodule : Command {
- private Action onProgress = null;
-
- public Submodule(string cwd) {
- Cwd = cwd;
+ public Submodule(string repo) {
+ WorkingDirectory = repo;
+ Context = repo;
}
- public bool Add(string url, string path, bool recursive, Action handler) {
- Args = $"submodule add {url} {path}";
- onProgress = handler;
+ public bool Add(string url, string relativePath, bool recursive, Action outputHandler) {
+ _outputHandler = outputHandler;
+ Args = $"submodule add {url} {relativePath}";
if (!Exec()) return false;
if (recursive) {
- Args = $"submodule update --init --recursive -- {path}";
+ Args = $"submodule update --init --recursive -- {relativePath}";
return Exec();
} else {
+ Args = $"submodule update --init -- {relativePath}";
return true;
}
}
@@ -29,16 +26,18 @@ namespace SourceGit.Commands {
return Exec();
}
- public bool Delete(string path) {
- Args = $"submodule deinit -f {path}";
+ public bool Delete(string relativePath) {
+ Args = $"submodule deinit -f {relativePath}";
if (!Exec()) return false;
- Args = $"rm -rf {path}";
+ Args = $"rm -rf {relativePath}";
return Exec();
}
- public override void OnReadline(string line) {
- onProgress?.Invoke(line);
+ protected override void OnReadline(string line) {
+ _outputHandler?.Invoke(line);
}
+
+ private Action _outputHandler;
}
}
diff --git a/src/Commands/Submodules.cs b/src/Commands/Submodules.cs
deleted file mode 100644
index 4daf69f6..00000000
--- a/src/Commands/Submodules.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-
-namespace SourceGit.Commands {
- ///
- /// 获取子模块列表
- ///
- public class Submodules : Command {
- private readonly Regex REG_FORMAT = new Regex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$");
- private List modules = new List();
-
- public Submodules(string repo) {
- Cwd = repo;
- Args = "submodule status";
- }
-
- public List Result() {
- Exec();
- return modules;
- }
-
- public override void OnReadline(string line) {
- var match = REG_FORMAT.Match(line);
- if (!match.Success) return;
- modules.Add(match.Groups[1].Value);
- }
- }
-}
diff --git a/src/Commands/Tag.cs b/src/Commands/Tag.cs
index 88942878..d0ab3bcb 100644
--- a/src/Commands/Tag.cs
+++ b/src/Commands/Tag.cs
@@ -1,38 +1,35 @@
+using System.Collections.Generic;
using System.IO;
namespace SourceGit.Commands {
-
- ///
- /// 标签相关指令
- ///
- public class Tag : Command {
-
- public Tag(string repo) {
- Cwd = repo;
- }
-
- public bool Add(string name, string basedOn, string message) {
- Args = $"tag -a {name} {basedOn} ";
+ public static class Tag {
+ public static bool Add(string repo, string name, string basedOn, string message) {
+ var cmd = new Command();
+ cmd.WorkingDirectory = repo;
+ cmd.Context = repo;
+ cmd.Args = $"tag -a {name} {basedOn} ";
if (!string.IsNullOrEmpty(message)) {
string tmp = Path.GetTempFileName();
File.WriteAllText(tmp, message);
- Args += $"-F \"{tmp}\"";
+ cmd.Args += $"-F \"{tmp}\"";
} else {
- Args += $"-m {name}";
+ cmd.Args += $"-m {name}";
}
- return Exec();
+ return cmd.Exec();
}
- public bool Delete(string name, bool push) {
- Args = $"tag --delete {name}";
- if (!Exec()) return false;
+ public static bool Delete(string repo, string name, List remotes) {
+ var cmd = new Command();
+ cmd.WorkingDirectory = repo;
+ cmd.Context = repo;
+ cmd.Args = $"tag --delete {name}";
+ if (!cmd.Exec()) return false;
- if (push) {
- var remotes = new Remotes(Cwd).Result();
+ if (remotes != null) {
foreach (var r in remotes) {
- new Push(Cwd, r.Name, name, true).Exec();
+ new Push(repo, r.Name, name, true).Exec();
}
}
diff --git a/src/Commands/Tags.cs b/src/Commands/Tags.cs
deleted file mode 100644
index 738c86db..00000000
--- a/src/Commands/Tags.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace SourceGit.Commands {
- ///
- /// 解析所有的Tags
- ///
- public class Tags : Command {
- public static readonly string CMD = "for-each-ref --sort=-creatordate --format=\"$%(refname:short)$%(objectname)$%(*objectname)\" refs/tags";
- private List loaded = new List();
-
- public Tags(string path) {
- Cwd = path;
- Args = CMD;
- }
-
- public List Result() {
- Exec();
- return loaded;
- }
-
- public override void OnReadline(string line) {
- var subs = line.Split(new char[] { '$' }, StringSplitOptions.RemoveEmptyEntries);
- if (subs.Length == 2) {
- loaded.Add(new Models.Tag() {
- Name = subs[0],
- SHA = subs[1],
- });
- } else if (subs.Length == 3) {
- loaded.Add(new Models.Tag() {
- Name = subs[0],
- SHA = subs[2],
- });
- }
- }
- }
-}
diff --git a/src/Commands/Version.cs b/src/Commands/Version.cs
index 1911f34a..f4da1767 100644
--- a/src/Commands/Version.cs
+++ b/src/Commands/Version.cs
@@ -1,18 +1,14 @@
-using System;
-
-namespace SourceGit.Commands {
- ///
- /// 检测git是否可用,并获取git版本信息
- ///
+namespace SourceGit.Commands {
public class Version : Command {
- const string GitVersionPrefix = "git version ";
+ public Version() {
+ Args = "-v";
+ RaiseError = false;
+ }
+
public string Query() {
- Args = $"--version";
- var result = ReadToEnd();
- if (!result.IsSuccess || string.IsNullOrEmpty(result.Output)) return null;
- var version = result.Output.Trim();
- if (!version.StartsWith(GitVersionPrefix, StringComparison.Ordinal)) return null;
- return version.Substring(GitVersionPrefix.Length);
+ var rs = ReadToEnd();
+ if (!rs.IsSuccess || string.IsNullOrWhiteSpace(rs.StdOut)) return string.Empty;
+ return rs.StdOut.Trim().Substring("git version ".Length);
}
}
}
diff --git a/src/Converters/BookmarkConverters.cs b/src/Converters/BookmarkConverters.cs
new file mode 100644
index 00000000..1eca567b
--- /dev/null
+++ b/src/Converters/BookmarkConverters.cs
@@ -0,0 +1,12 @@
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+
+namespace SourceGit.Converters {
+ public static class BookmarkConverters {
+ public static FuncValueConverter ToBrush =
+ new FuncValueConverter(bookmark => Models.Bookmarks.Brushes[bookmark]);
+
+ public static FuncValueConverter ToStrokeThickness =
+ new FuncValueConverter(bookmark => bookmark == 0 ? 1.0 : 0);
+ }
+}
diff --git a/src/Converters/BoolConverters.cs b/src/Converters/BoolConverters.cs
new file mode 100644
index 00000000..65593f8f
--- /dev/null
+++ b/src/Converters/BoolConverters.cs
@@ -0,0 +1,8 @@
+using Avalonia.Data.Converters;
+
+namespace SourceGit.Converters {
+ public static class BoolConverters {
+ public static FuncValueConverter ToCommitOpacity =
+ new FuncValueConverter(x => x ? 1 : 0.5);
+ }
+}
diff --git a/src/Converters/BranchConverters.cs b/src/Converters/BranchConverters.cs
new file mode 100644
index 00000000..c067a481
--- /dev/null
+++ b/src/Converters/BranchConverters.cs
@@ -0,0 +1,8 @@
+using Avalonia.Data.Converters;
+
+namespace SourceGit.Converters {
+ public static class BranchConverters {
+ public static FuncValueConverter ToName =
+ new FuncValueConverter(v => v.IsLocal ? v.Name : $"{v.Remote}/{v.Name}");
+ }
+}
diff --git a/src/Converters/ChangeViewModeConverters.cs b/src/Converters/ChangeViewModeConverters.cs
new file mode 100644
index 00000000..8bfb0898
--- /dev/null
+++ b/src/Converters/ChangeViewModeConverters.cs
@@ -0,0 +1,28 @@
+using Avalonia.Controls;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+
+namespace SourceGit.Converters {
+ public static class ChangeViewModeConverters {
+ public static FuncValueConverter ToIcon =
+ new FuncValueConverter(v => {
+ switch (v) {
+ case Models.ChangeViewMode.List:
+ return App.Current?.FindResource("Icons.List") as StreamGeometry;
+ case Models.ChangeViewMode.Grid:
+ return App.Current?.FindResource("Icons.Grid") as StreamGeometry;
+ default:
+ return App.Current?.FindResource("Icons.Tree") as StreamGeometry;
+ }
+ });
+
+ public static FuncValueConverter IsList =
+ new FuncValueConverter(v => v == Models.ChangeViewMode.List);
+
+ public static FuncValueConverter IsGrid =
+ new FuncValueConverter(v => v == Models.ChangeViewMode.Grid);
+
+ public static FuncValueConverter IsTree =
+ new FuncValueConverter(v => v == Models.ChangeViewMode.Tree);
+ }
+}
diff --git a/src/Converters/DecoratorTypeConverters.cs b/src/Converters/DecoratorTypeConverters.cs
new file mode 100644
index 00000000..2e9460c9
--- /dev/null
+++ b/src/Converters/DecoratorTypeConverters.cs
@@ -0,0 +1,34 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+
+namespace SourceGit.Converters {
+ public static class DecoratorTypeConverters {
+ public static FuncValueConverter ToBackground =
+ new FuncValueConverter(v => {
+ if (v == Models.DecoratorType.Tag) return Models.DecoratorResources.Backgrounds[0];
+ return Models.DecoratorResources.Backgrounds[1];
+ });
+
+ public static FuncValueConverter ToIcon =
+ new FuncValueConverter(v => {
+ var key = "Icons.Tag";
+ switch (v) {
+ case Models.DecoratorType.CurrentBranchHead:
+ key = "Icons.Check";
+ break;
+ case Models.DecoratorType.RemoteBranchHead:
+ key = "Icons.Remote";
+ break;
+ case Models.DecoratorType.LocalBranchHead:
+ key = "Icons.Branch";
+ break;
+ default:
+ break;
+ }
+
+ return Application.Current?.FindResource(key) as StreamGeometry;
+ });
+ }
+}
diff --git a/src/Converters/IntConverters.cs b/src/Converters/IntConverters.cs
new file mode 100644
index 00000000..95f4a57a
--- /dev/null
+++ b/src/Converters/IntConverters.cs
@@ -0,0 +1,27 @@
+using Avalonia.Data.Converters;
+using System;
+using System.Globalization;
+
+namespace SourceGit.Converters {
+ public static class IntConverters {
+ public static FuncValueConverter IsGreaterThanZero =
+ new FuncValueConverter(v => v > 0);
+
+ public static FuncValueConverter IsZero =
+ new FuncValueConverter(v => v == 0);
+
+ public class NotEqualConverter : IValueConverter {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
+ int v = (int)value;
+ int target = (int)parameter;
+ return v != target;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
+ throw new NotImplementedException();
+ }
+ }
+
+ public static NotEqualConverter NotEqual = new NotEqualConverter();
+ }
+}
diff --git a/src/Converters/LauncherPageConverters.cs b/src/Converters/LauncherPageConverters.cs
new file mode 100644
index 00000000..6ba2a0a4
--- /dev/null
+++ b/src/Converters/LauncherPageConverters.cs
@@ -0,0 +1,28 @@
+using Avalonia.Collections;
+using Avalonia.Data.Converters;
+using System.Collections.Generic;
+
+namespace SourceGit.Converters {
+ public static class LauncherPageConverters {
+ public static FuncMultiValueConverter