From cbe4c365259e6ef57bd6a4a3196a99ea97ae46ff Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 8 Jul 2024 22:07:00 +0800 Subject: [PATCH] feature: support git.core.askpass (#239) --- src/App.axaml.cs | 43 +++++++++++++--- src/Commands/Branch.cs | 10 ++-- src/Commands/Clone.cs | 6 +-- src/Commands/Command.cs | 20 +++++++- src/Commands/Fetch.cs | 20 +++----- src/Commands/Pull.cs | 10 ++-- src/Commands/Push.cs | 20 +++----- src/Resources/Icons.axaml | 1 + src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/ViewModels/Launcher.cs | 5 +- src/Views/Askpass.axaml | 81 +++++++++++++++++++++++++++++++ src/Views/Askpass.axaml.cs | 52 ++++++++++++++++++++ 14 files changed, 211 insertions(+), 60 deletions(-) create mode 100644 src/Views/Askpass.axaml create mode 100644 src/Views/Askpass.axaml.cs diff --git a/src/App.axaml.cs b/src/App.axaml.cs index b03e4f03..b21953a2 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Reflection; using System.Text; using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Input; @@ -324,16 +325,25 @@ namespace SourceGit if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { BindingPlugins.DataValidators.RemoveAt(0); - Native.OS.SetupEnternalTools(); - _launcher = new ViewModels.Launcher(); - desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; - - var pref = ViewModels.Preference.Instance; - if (pref.ShouldCheck4UpdateOnStartup) + var commandlines = Environment.GetCommandLineArgs(); + if (TryParseAskpass(commandlines, out var keyname)) { - pref.Save(); - Check4Update(); + desktop.MainWindow = new Views.Askpass(Path.GetFileName(keyname)); + } + else + { + Native.OS.SetupEnternalTools(); + + _launcher = new ViewModels.Launcher(commandlines); + desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; + + var pref = ViewModels.Preference.Instance; + if (pref.ShouldCheck4UpdateOnStartup) + { + pref.Save(); + Check4Update(); + } } } @@ -359,6 +369,23 @@ namespace SourceGit }); } + private static bool TryParseAskpass(string[] args, out string keyname) + { + keyname = string.Empty; + + if (args.Length != 2) + return false; + + var match = REG_ASKPASS().Match(args[1]); + if (match.Success) + keyname = match.Groups[1].Value; + + return match.Success; + } + + [GeneratedRegex(@"Enter\s+passphrase\s*for\s*key\s*['""]([^'""]+)['""]\:\s*", RegexOptions.IgnoreCase)] + private static partial Regex REG_ASKPASS(); + private ViewModels.Launcher _launcher = null; private ResourceDictionary _activeLocale = null; private ResourceDictionary _themeOverrides = null; diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs index 660a5daa..cd4ec599 100644 --- a/src/Commands/Branch.cs +++ b/src/Commands/Branch.cs @@ -52,14 +52,10 @@ cmd.Context = repo; var sshKey = new Config(repo).Get($"remote.{remote}.sshkey"); - if (!string.IsNullOrEmpty(sshKey)) - { - cmd.Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" "; - } - else - { + if (string.IsNullOrEmpty(sshKey)) cmd.Args = "-c credential.helper=manager "; - } + else + cmd.UseSSHKey(sshKey); cmd.Args += $"push {remote} --delete {name}"; return cmd.Exec(); diff --git a/src/Commands/Clone.cs b/src/Commands/Clone.cs index 80e0df50..cdefbb23 100644 --- a/src/Commands/Clone.cs +++ b/src/Commands/Clone.cs @@ -13,13 +13,9 @@ namespace SourceGit.Commands TraitErrorAsOutput = true; if (string.IsNullOrEmpty(sshKey)) - { Args = "-c credential.helper=manager "; - } else - { - Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" "; - } + UseSSHKey(sshKey); Args += "clone --progress --verbose --recurse-submodules "; diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 00f36c85..3e440a13 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -28,6 +28,15 @@ namespace SourceGit.Commands public string Args { get; set; } = string.Empty; public bool RaiseError { get; set; } = true; public bool TraitErrorAsOutput { get; set; } = false; + public Dictionary Envs { get; set; } = new Dictionary(); + + public void UseSSHKey(string key) + { + Envs.Add("DISPLAY", "required"); + Envs.Add("SSH_ASKPASS", Process.GetCurrentProcess().MainModule.FileName); + Envs.Add("SSH_ASKPASS_REQUIRE", "prefer"); + Envs.Add("GIT_SSH_COMMAND", $"ssh -i '{key}'"); + } public bool Exec() { @@ -41,6 +50,10 @@ namespace SourceGit.Commands start.StandardOutputEncoding = Encoding.UTF8; start.StandardErrorEncoding = Encoding.UTF8; + // User environment overrides. + foreach (var kv in Envs) + start.Environment.Add(kv.Key, kv.Value); + // Force using en_US.UTF-8 locale to avoid GCM crash if (OperatingSystem.IsLinux()) start.Environment.Add("LANG", "en_US.UTF-8"); @@ -94,7 +107,7 @@ namespace SourceGit.Commands return; if (e.Data.StartsWith("Filtering content:", StringComparison.Ordinal)) return; - if (_progressRegex().IsMatch(e.Data)) + if (REG_PROGRESS().IsMatch(e.Data)) return; errs.Add(e.Data); }; @@ -185,6 +198,9 @@ namespace SourceGit.Commands protected virtual void OnReadline(string line) { } [GeneratedRegex(@"\d+%")] - private static partial Regex _progressRegex(); + private static partial Regex REG_PROGRESS(); + + [GeneratedRegex(@"Enter\s+passphrase\s*for\s*key\s*['""]([^'""]+)['""]\:\s*", RegexOptions.IgnoreCase)] + private static partial Regex REG_ASKPASS(); } } diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs index ca1d83d6..c169645d 100644 --- a/src/Commands/Fetch.cs +++ b/src/Commands/Fetch.cs @@ -15,14 +15,10 @@ namespace SourceGit.Commands TraitErrorAsOutput = true; var sshKey = new Config(repo).Get($"remote.{remote}.sshkey"); - if (!string.IsNullOrEmpty(sshKey)) - { - Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" "; - } - else - { + if (string.IsNullOrEmpty(sshKey)) Args = "-c credential.helper=manager "; - } + else + UseSSHKey(sshKey); Args += "fetch --progress --verbose "; if (prune) @@ -46,14 +42,10 @@ namespace SourceGit.Commands TraitErrorAsOutput = true; var sshKey = new Config(repo).Get($"remote.{remote}.sshkey"); - if (!string.IsNullOrEmpty(sshKey)) - { - Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" "; - } - else - { + if (string.IsNullOrEmpty(sshKey)) Args = "-c credential.helper=manager "; - } + else + UseSSHKey(sshKey); Args += $"fetch --progress --verbose {remote} {remoteBranch}:{localBranch}"; } diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs index 43418825..5ed81603 100644 --- a/src/Commands/Pull.cs +++ b/src/Commands/Pull.cs @@ -12,14 +12,10 @@ namespace SourceGit.Commands TraitErrorAsOutput = true; var sshKey = new Config(repo).Get($"remote.{remote}.sshkey"); - if (!string.IsNullOrEmpty(sshKey)) - { - Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" "; - } - else - { + if (string.IsNullOrEmpty(sshKey)) Args = "-c credential.helper=manager "; - } + else + UseSSHKey(sshKey); Args += "pull --verbose --progress --tags "; if (useRebase) diff --git a/src/Commands/Push.cs b/src/Commands/Push.cs index 0aac37a5..80ad10e6 100644 --- a/src/Commands/Push.cs +++ b/src/Commands/Push.cs @@ -12,14 +12,10 @@ namespace SourceGit.Commands _outputHandler = onProgress; var sshKey = new Config(repo).Get($"remote.{remote}.sshkey"); - if (!string.IsNullOrEmpty(sshKey)) - { - Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" "; - } - else - { + if (string.IsNullOrEmpty(sshKey)) Args = "-c credential.helper=manager "; - } + else + UseSSHKey(sshKey); Args += "push --progress --verbose "; @@ -39,14 +35,10 @@ namespace SourceGit.Commands Context = repo; var sshKey = new Config(repo).Get($"remote.{remote}.sshkey"); - if (!string.IsNullOrEmpty(sshKey)) - { - Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" "; - } - else - { + if (string.IsNullOrEmpty(sshKey)) Args = "-c credential.helper=manager "; - } + else + UseSSHKey(sshKey); Args += "push "; if (isDelete) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 1af75c64..00f7fc48 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -50,6 +50,7 @@ M512 0C229 0 0 229 0 512s229 512 512 512 512-229 512-512S795 0 512 0zM512 928c-230 0-416-186-416-416S282 96 512 96s416 186 416 416S742 928 512 928zM538 343c47 0 83-38 83-78 0-32-21-61-62-61-55 0-82 45-82 77C475 320 498 343 538 343zM533 729c-8 0-11-10-3-40l43-166c16-61 11-100-22-100-39 0-131 40-211 108l16 27c25-17 68-35 78-35 8 0 7 10 0 36l-38 158c-23 89 1 110 34 110 33 0 118-30 196-110l-19-25C575 717 543 729 533 729z M412 66C326 132 271 233 271 347c0 17 1 34 4 50-41-48-98-79-162-83a444 444 0 00-46 196c0 207 142 382 337 439h2c19 0 34 15 34 33 0 11-6 21-14 26l1 14C183 973 0 763 0 511 0 272 166 70 393 7A35 35 0 01414 0c19 0 34 15 34 33a33 33 0 01-36 33zm200 893c86-66 141-168 141-282 0-17-1-34-4-50 41 48 98 79 162 83a444 444 0 0046-196c0-207-142-382-337-439h-2a33 33 0 01-34-33c0-11 6-21 14-26L596 0C841 51 1024 261 1024 513c0 239-166 441-393 504A35 35 0 01610 1024a33 33 0 01-34-33 33 33 0 0136-33zM512 704a192 192 0 110-384 192 192 0 010 384z M512 64A447 447 0 0064 512c0 248 200 448 448 448s448-200 448-448S760 64 512 64zM218 295h31c54 0 105 19 145 55 13 12 13 31 3 43a35 35 0 01-22 10 36 36 0 01-21-7 155 155 0 00-103-39h-31a32 32 0 01-31-31c0-18 13-31 30-31zm31 433h-31a32 32 0 01-31-31c0-16 13-31 31-31h31A154 154 0 00403 512 217 217 0 01620 295h75l-93-67a33 33 0 01-7-43 33 33 0 0143-7l205 148-205 148a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67H620a154 154 0 00-154 154c0 122-97 220-217 220zm390 118a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67h-75c-52 0-103-19-143-54-12-12-13-31-1-43a30 30 0 0142-3 151 151 0 00102 39h75L602 599a33 33 0 01-7-43 33 33 0 0143-7l205 148-203 151z + M640 96c-158 0-288 130-288 288 0 17 3 31 5 46L105 681 96 691V928h224v-96h96v-96h96v-95c38 18 82 31 128 31 158 0 288-130 288-288s-130-288-288-288zm0 64c123 0 224 101 224 224s-101 224-224 224a235 235 0 01-109-28l-8-4H448v96h-96v96H256v96H160v-146l253-254 12-11-3-17C419 417 416 400 416 384c0-123 101-224 224-224zm64 96a64 64 0 100 128 64 64 0 100-128z M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zM139 832V192c0-6 4-11 11-11h331v661H149c-6 0-11-4-11-11zm747 0c0 6-4 11-11 11H544v-661H875c6 0 11 4 11 11v640z M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zm-725 64h725c6 0 11 4 11 11v288h-747V192c0-6 4-11 11-11zm725 661H149c-6 0-11-4-11-11V544h747V832c0 6-4 11-11 11z M40 9 15 23 15 31 9 28 9 20 34 5 24 0 0 14 0 34 25 48 25 28 49 14zM26 29 26 48 49 34 49 15z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 271a52dd..3a6ef2bd 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -36,6 +36,7 @@ Select archive file path Revision: Archive + SourceGit Askpass FILES ASSUME UNCHANGED NO FILES ASSUMED AS UNCHANGED REMOVE diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index adf03079..51ece14d 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -39,6 +39,7 @@ 选择存档文件的存放路径 指定的提交: 存档 + SourceGit Askpass 不跟踪更改的文件 没有不跟踪更改的文件 移除 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index d8d22032..bb5bd996 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -39,6 +39,7 @@ 選擇存檔檔案的存放路徑 指定的提交: 存檔 + SourceGit Askpass 不跟蹤更改的檔案 沒有不跟蹤更改的檔案 移除 diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index df5cf641..c0b376ba 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -28,12 +28,11 @@ namespace SourceGit.ViewModels } } - public Launcher() + public Launcher(string[] commandlines) { Pages = new AvaloniaList(); AddNewTab(); - - var commandlines = Environment.GetCommandLineArgs(); + if (commandlines.Length == 2) { var path = commandlines[1]; diff --git a/src/Views/Askpass.axaml b/src/Views/Askpass.axaml new file mode 100644 index 00000000..11d87a45 --- /dev/null +++ b/src/Views/Askpass.axaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +