mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-11-01 13:13:21 -07:00
feature: support git.core.askpass (#239)
This commit is contained in:
parent
8fa19ecd0c
commit
cbe4c36525
14 changed files with 211 additions and 60 deletions
|
@ -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,9 +325,17 @@ namespace SourceGit
|
|||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
var commandlines = Environment.GetCommandLineArgs();
|
||||
if (TryParseAskpass(commandlines, out var keyname))
|
||||
{
|
||||
desktop.MainWindow = new Views.Askpass(Path.GetFileName(keyname));
|
||||
}
|
||||
else
|
||||
{
|
||||
Native.OS.SetupEnternalTools();
|
||||
|
||||
_launcher = new ViewModels.Launcher();
|
||||
_launcher = new ViewModels.Launcher(commandlines);
|
||||
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
||||
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
|
@ -336,6 +345,7 @@ namespace SourceGit
|
|||
Check4Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 ";
|
||||
|
||||
|
|
|
@ -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<string, string> Envs { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}";
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
<StreamGeometry x:Key="Icons.Info">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</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Init">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</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.InteractiveRebase">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</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Password">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</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.LayoutHorizontal">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</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.LayoutVertical">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</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.LFS">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</StreamGeometry>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<x:String x:Key="Text.Archive.File.Placeholder" xml:space="preserve">Select archive file path</x:String>
|
||||
<x:String x:Key="Text.Archive.Revision" xml:space="preserve">Revision:</x:String>
|
||||
<x:String x:Key="Text.Archive.Title" xml:space="preserve">Archive</x:String>
|
||||
<x:String x:Key="Text.Askpass" xml:space="preserve">SourceGit Askpass</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged" xml:space="preserve">FILES ASSUME UNCHANGED</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">NO FILES ASSUMED AS UNCHANGED</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">REMOVE</x:String>
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
<x:String x:Key="Text.Archive.File.Placeholder" xml:space="preserve">选择存档文件的存放路径</x:String>
|
||||
<x:String x:Key="Text.Archive.Revision" xml:space="preserve">指定的提交:</x:String>
|
||||
<x:String x:Key="Text.Archive.Title" xml:space="preserve">存档</x:String>
|
||||
<x:String x:Key="Text.Askpass" xml:space="preserve">SourceGit Askpass</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged" xml:space="preserve">不跟踪更改的文件</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">没有不跟踪更改的文件</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">移除</x:String>
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
<x:String x:Key="Text.Archive.File.Placeholder" xml:space="preserve">選擇存檔檔案的存放路徑</x:String>
|
||||
<x:String x:Key="Text.Archive.Revision" xml:space="preserve">指定的提交:</x:String>
|
||||
<x:String x:Key="Text.Archive.Title" xml:space="preserve">存檔</x:String>
|
||||
<x:String x:Key="Text.Askpass" xml:space="preserve">SourceGit Askpass</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged" xml:space="preserve">不跟蹤更改的檔案</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">沒有不跟蹤更改的檔案</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">移除</x:String>
|
||||
|
|
|
@ -28,12 +28,11 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public Launcher()
|
||||
public Launcher(string[] commandlines)
|
||||
{
|
||||
Pages = new AvaloniaList<LauncherPage>();
|
||||
AddNewTab();
|
||||
|
||||
var commandlines = Environment.GetCommandLineArgs();
|
||||
if (commandlines.Length == 2)
|
||||
{
|
||||
var path = commandlines[1];
|
||||
|
|
81
src/Views/Askpass.axaml
Normal file
81
src/Views/Askpass.axaml
Normal file
|
@ -0,0 +1,81 @@
|
|||
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Askpass"
|
||||
x:DataType="v:Askpass"
|
||||
Icon="/App.ico"
|
||||
Title="{DynamicResource Text.Askpass}"
|
||||
SizeToContent="WidthAndHeight"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<!-- TitleBar -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30">
|
||||
<Border Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Background="{DynamicResource Brush.TitleBar}"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
|
||||
PointerPressed="BeginMoveWindow"/>
|
||||
|
||||
<Path Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
Margin="10,0,0,0"
|
||||
Data="{StaticResource Icons.Password}"
|
||||
IsVisible="{OnPlatform True, macOS=False}"/>
|
||||
|
||||
<Grid Grid.Column="0" Classes="caption_button_box" Margin="2,4,0,0" IsVisible="{OnPlatform False, macOS=True}">
|
||||
<Button Classes="caption_button_macos" Click="CloseWindow">
|
||||
<Grid>
|
||||
<Ellipse Fill="{DynamicResource Brush.MacOS.Close}"/>
|
||||
<Path Height="6" Width="6" Stretch="Fill" Fill="#404040" Stroke="#404040" StrokeThickness="1" Data="{StaticResource Icons.Window.Close}"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Askpass}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"/>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Classes="caption_button"
|
||||
Click="CloseWindow"
|
||||
IsVisible="{OnPlatform True, macOS=False}">
|
||||
<Path Data="{StaticResource Icons.Window.Close}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="1" Margin="0,16" Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal" Margin="16,0">
|
||||
<TextBlock Text="Enter passphrase for key: "/>
|
||||
<Border Background="{DynamicResource Brush.Accent}" CornerRadius="4">
|
||||
<TextBlock Text="{Binding KeyName}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Margin="16"
|
||||
MinWidth="300"
|
||||
Height="32"
|
||||
Text="{Binding Passphrase, Mode=TwoWay}"
|
||||
PasswordChar="*"
|
||||
HorizontalAlignment="Stretch"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Classes="flat primary"
|
||||
Width="80"
|
||||
Content="{DynamicResource Text.Sure}"
|
||||
Click="EnterPassword"
|
||||
HotKey="Enter"/>
|
||||
<Button Classes="flat"
|
||||
Width="80"
|
||||
Margin="16,0,0,0"
|
||||
Content="{DynamicResource Text.Cancel}"
|
||||
Click="CloseWindow"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</v:ChromelessWindow>
|
52
src/Views/Askpass.axaml.cs
Normal file
52
src/Views/Askpass.axaml.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class Askpass : ChromelessWindow
|
||||
{
|
||||
public string KeyName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = string.Empty;
|
||||
|
||||
public string Passphrase
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = string.Empty;
|
||||
|
||||
public Askpass()
|
||||
{
|
||||
DataContext = this;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public Askpass(string keyname)
|
||||
{
|
||||
KeyName = keyname;
|
||||
DataContext = this;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void BeginMoveWindow(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
|
||||
private void CloseWindow(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Console.Out.WriteLine("No passphrase entered.");
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
private void EnterPassword(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Console.Out.Write($"{Passphrase}\n");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue