From b2aac68600e7db92b6d8b0bff93cbea93cb771f4 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 7 Jul 2025 17:14:59 +0800 Subject: [PATCH] refactor: rewrite integration for `git difftool` and `git mergetool` command Signed-off-by: leo --- src/Commands/Command.cs | 31 ++++++++++--- src/Commands/DiffTool.cs | 46 +++++++++++++++++++ src/Commands/MergeTool.cs | 74 +++++++++++-------------------- src/ViewModels/BranchCompare.cs | 4 +- src/ViewModels/CommitDetail.cs | 5 +-- src/ViewModels/DiffContext.cs | 2 +- src/ViewModels/RevisionCompare.cs | 3 +- src/ViewModels/StashesPage.cs | 3 +- src/ViewModels/WorkingCopy.cs | 11 +++-- 9 files changed, 107 insertions(+), 72 deletions(-) create mode 100644 src/Commands/DiffTool.cs diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 12fcb72f..53d0b643 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -37,11 +37,24 @@ namespace SourceGit.Commands public bool RaiseError { get; set; } = true; public Models.ICommandLog Log { get; set; } = null; + public void Exec() + { + try + { + var start = CreateGitStartInfo(false); + Process.Start(start); + } + catch (Exception ex) + { + App.RaiseException(Context, ex.Message); + } + } + public async Task ExecAsync() { Log?.AppendLine($"$ git {Args}\n"); - var start = CreateGitStartInfo(); + var start = CreateGitStartInfo(true); var errs = new List(); var proc = new Process() { StartInfo = start }; @@ -118,7 +131,7 @@ namespace SourceGit.Commands protected async Task ReadToEndAsync() { - var start = CreateGitStartInfo(); + var start = CreateGitStartInfo(true); var proc = new Process() { StartInfo = start }; try @@ -140,17 +153,21 @@ namespace SourceGit.Commands return rs; } - private ProcessStartInfo CreateGitStartInfo() + private ProcessStartInfo CreateGitStartInfo(bool redirect) { var start = new ProcessStartInfo(); start.FileName = Native.OS.GitExecutable; start.Arguments = "--no-pager -c core.quotepath=off -c credential.helper=manager "; start.UseShellExecute = false; start.CreateNoWindow = true; - start.RedirectStandardOutput = true; - start.RedirectStandardError = true; - start.StandardOutputEncoding = Encoding.UTF8; - start.StandardErrorEncoding = Encoding.UTF8; + + if (redirect) + { + start.RedirectStandardOutput = true; + start.RedirectStandardError = true; + start.StandardOutputEncoding = Encoding.UTF8; + start.StandardErrorEncoding = Encoding.UTF8; + } // Force using this app as SSH askpass program var selfExecFile = Process.GetCurrentProcess().MainModule!.FileName; diff --git a/src/Commands/DiffTool.cs b/src/Commands/DiffTool.cs new file mode 100644 index 00000000..abed00b0 --- /dev/null +++ b/src/Commands/DiffTool.cs @@ -0,0 +1,46 @@ +using System.IO; + +namespace SourceGit.Commands +{ + public class DiffTool : Command + { + public DiffTool(string repo, int type, string exec, Models.DiffOption option) + { + WorkingDirectory = repo; + Context = repo; + + _merger = Models.ExternalMerger.Supported.Find(x => x.Type == type); + _exec = exec; + _option = option; + } + + public void Open() + { + if (_merger == null) + { + App.RaiseException(Context, "Invalid merge tool in preference setting!"); + return; + } + + if (_merger.Type == 0) + { + Args = $"difftool -g --no-prompt {_option}"; + } + else if (File.Exists(_exec)) + { + Args = $"-c difftool.sourcegit.cmd=\"\\\"{_exec}\\\" {_merger.DiffCmd}\" difftool --tool=sourcegit --no-prompt {_option}"; + } + else + { + App.RaiseException(Context, $"Can NOT find external diff tool in '{_exec}'!"); + return; + } + + Exec(); + } + + private Models.ExternalMerger _merger; + private string _exec; + private Models.DiffOption _option; + } +} diff --git a/src/Commands/MergeTool.cs b/src/Commands/MergeTool.cs index 7cf0a22c..f4688317 100644 --- a/src/Commands/MergeTool.cs +++ b/src/Commands/MergeTool.cs @@ -3,69 +3,45 @@ using System.Threading.Tasks; namespace SourceGit.Commands { - public static class MergeTool + public class MergeTool : Command { - public static async Task OpenForMergeAsync(string repo, int toolType, string toolPath, string file) + public MergeTool(string repo, int type, string exec, string file) { - var cmd = new Command(); - cmd.WorkingDirectory = repo; - cmd.Context = repo; - cmd.RaiseError = true; + WorkingDirectory = repo; + Context = exec; - // NOTE: If no names are specified, 'git mergetool' will run the merge tool program on every file with merge conflicts. - var fileArg = string.IsNullOrEmpty(file) ? "" : $"\"{file}\""; - - if (toolType == 0) - { - cmd.Args = $"mergetool {fileArg}"; - return await cmd.ExecAsync().ConfigureAwait(false); - } - - if (!File.Exists(toolPath)) - { - App.RaiseException(repo, $"Can NOT find external merge tool in '{toolPath}'!"); - return false; - } - - var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType); - if (supported == null) - { - App.RaiseException(repo, "Invalid merge tool in preference setting!"); - return false; - } - - cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit {fileArg}"; - return await cmd.ExecAsync().ConfigureAwait(false); + _merger = Models.ExternalMerger.Supported.Find(x => x.Type == type); + _exec = exec; + _file = string.IsNullOrEmpty(file) ? "" : $"\"{file}\""; } - public static async Task OpenForDiffAsync(string repo, int toolType, string toolPath, Models.DiffOption option) + public async Task OpenAsync() { - var cmd = new Command(); - cmd.WorkingDirectory = repo; - cmd.Context = repo; - cmd.RaiseError = true; - - if (toolType == 0) + if (_merger == null) { - cmd.Args = $"difftool -g --no-prompt {option}"; - return await cmd.ExecAsync(); - } - - if (!File.Exists(toolPath)) - { - App.RaiseException(repo, $"Can NOT find external diff tool in '{toolPath}'!"); + App.RaiseException(Context, "Invalid merge tool in preference setting!"); return false; } - var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType); - if (supported == null) + if (_merger.Type == 0) { - App.RaiseException(repo, "Invalid merge tool in preference setting!"); + Args = $"mergetool {_file}"; + } + else if (File.Exists(_exec)) + { + Args = $"-c mergetool.sourcegit.cmd=\"\\\"{_exec}\\\" {_merger.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit {_file}"; + } + else + { + App.RaiseException(Context, $"Can NOT find external merge tool in '{_exec}'!"); return false; } - cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.DiffCmd}\" difftool --tool=sourcegit --no-prompt {option}"; - return await cmd.ExecAsync().ConfigureAwait(false); + return await ExecAsync().ConfigureAwait(false); } + + private Models.ExternalMerger _merger; + private string _exec; + private string _file; } } diff --git a/src/ViewModels/BranchCompare.cs b/src/ViewModels/BranchCompare.cs index 590cff96..914d1e0c 100644 --- a/src/ViewModels/BranchCompare.cs +++ b/src/ViewModels/BranchCompare.cs @@ -125,13 +125,13 @@ namespace SourceGit.ViewModels var openWithMerger = new MenuItem(); openWithMerger.Header = App.Text("OpenInExternalMergeTool"); openWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); - openWithMerger.Click += (sender, ev) => + openWithMerger.Click += (_, ev) => { var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption(_based.Head, _to.Head, change); - _ = Commands.MergeTool.OpenForDiffAsync(_repo, toolType, toolPath, opt); + new Commands.DiffTool(_repo, toolType, toolPath, opt).Open(); ev.Handled = true; }; menu.Items.Add(openWithMerger); diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index b9c46a1b..73959b0d 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -325,13 +325,12 @@ namespace SourceGit.ViewModels var openWithMerger = new MenuItem(); openWithMerger.Header = App.Text("OpenInExternalMergeTool"); openWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); - openWithMerger.Click += (sender, ev) => + openWithMerger.Click += (_, ev) => { var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption(_commit, change); - - _ = Commands.MergeTool.OpenForDiffAsync(_repo.FullPath, toolType, toolPath, opt); + new Commands.DiffTool(_repo.FullPath, toolType, toolPath, opt).Open(); ev.Handled = true; }; diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 5b772fc8..025ceddd 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -97,7 +97,7 @@ namespace SourceGit.ViewModels { var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; - Task.Run(() => Commands.MergeTool.OpenForDiffAsync(_repo, toolType, toolPath, _option)); + new Commands.DiffTool(_repo, toolType, toolPath, _option).Open(); } private void LoadDiffContent() diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index 65958a17..6de99d88 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -144,8 +144,7 @@ namespace SourceGit.ViewModels var opt = new Models.DiffOption(GetSHA(_startPoint), GetSHA(_endPoint), change); var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; - - Task.Run(() => Commands.MergeTool.OpenForDiffAsync(_repo, toolType, toolPath, opt)); + new Commands.DiffTool(_repo, toolType, toolPath, opt).Open(); ev.Handled = true; }; menu.Items.Add(openWithMerger); diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index 4c907cc0..9fbb5582 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -231,8 +231,7 @@ namespace SourceGit.ViewModels var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption($"{_selectedStash.SHA}^", _selectedStash.SHA, change); - - Task.Run(() => Commands.MergeTool.OpenForDiffAsync(_repo.FullPath, toolType, toolPath, opt)); + new Commands.DiffTool(_repo.FullPath, toolType, toolPath, opt).Open(); ev.Handled = true; }; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 6ab8f8d7..12b6dd33 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -464,12 +464,12 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(true); } - public async Task UseExternalMergeTool(Models.Change change) + public async Task UseExternalMergeTool(Models.Change change) { var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; var file = change?.Path; - await Commands.MergeTool.OpenForMergeAsync(_repo.FullPath, toolType, toolPath, file); + return await new Commands.MergeTool(_repo.FullPath, toolType, toolPath, file).OpenAsync(); } public void ContinueMerge() @@ -598,7 +598,7 @@ namespace SourceGit.ViewModels var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption(change, true); - _ = Commands.MergeTool.OpenForDiffAsync(_repo.FullPath, toolType, toolPath, opt); + new Commands.DiffTool(_repo.FullPath, toolType, toolPath, opt).Open(); } e.Handled = true; @@ -1250,13 +1250,12 @@ namespace SourceGit.ViewModels var openWithMerger = new MenuItem(); openWithMerger.Header = App.Text("OpenInExternalMergeTool"); openWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); - openWithMerger.Click += (sender, ev) => + openWithMerger.Click += (_, ev) => { var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption(change, false); - - _ = Commands.MergeTool.OpenForDiffAsync(_repo.FullPath, toolType, toolPath, opt); + new Commands.DiffTool(_repo.FullPath, toolType, toolPath, opt).Open(); ev.Handled = true; };