enhance: tag creation & pushing (#141)

* supports creating lightweight tags
* supports GPG signed tags
* add option to push selected tag to all remotes
This commit is contained in:
leo 2024-05-24 10:31:20 +08:00
parent 0dea7ed0e2
commit b556feb3d3
11 changed files with 122 additions and 42 deletions

View file

@ -5,12 +5,22 @@ namespace SourceGit.Commands
{ {
public static class Tag public static class Tag
{ {
public static bool Add(string repo, string name, string basedOn, string message) public static bool Add(string repo, string name, string basedOn)
{ {
var cmd = new Command(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Args = $"tag -a {name} {basedOn} "; cmd.Args = $"tag {name} {basedOn}";
return cmd.Exec();
}
public static bool Add(string repo, string name, string basedOn, string message, bool sign)
{
var param = sign ? "-s -a" : "-a";
var cmd = new Command();
cmd.WorkingDirectory = repo;
cmd.Context = repo;
cmd.Args = $"tag {param} {name} {basedOn} ";
if (!string.IsNullOrEmpty(message)) if (!string.IsNullOrEmpty(message))
{ {

View file

@ -114,10 +114,14 @@
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Create Local Branch</x:String> <x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Create Local Branch</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">Create Tag</x:String> <x:String x:Key="Text.CreateTag" xml:space="preserve">Create Tag</x:String>
<x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">New Tag At :</x:String> <x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">New Tag At :</x:String>
<x:String x:Key="Text.CreateTag.GPGSign" xml:space="preserve">Enable GPG signing</x:String>
<x:String x:Key="Text.CreateTag.Message" xml:space="preserve">Tag Message :</x:String> <x:String x:Key="Text.CreateTag.Message" xml:space="preserve">Tag Message :</x:String>
<x:String x:Key="Text.CreateTag.Message.Placeholder" xml:space="preserve">Optional.</x:String> <x:String x:Key="Text.CreateTag.Message.Placeholder" xml:space="preserve">Optional.</x:String>
<x:String x:Key="Text.CreateTag.Name" xml:space="preserve">Tag Name :</x:String> <x:String x:Key="Text.CreateTag.Name" xml:space="preserve">Tag Name :</x:String>
<x:String x:Key="Text.CreateTag.Name.Placeholder" xml:space="preserve">Recommended format v1.0.0-alpha</x:String> <x:String x:Key="Text.CreateTag.Name.Placeholder" xml:space="preserve">Recommended format v1.0.0-alpha</x:String>
<x:String x:Key="Text.CreateTag.Type" xml:space="preserve">Kind </x:String>
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">annotated</x:String>
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">lightweight</x:String>
<x:String x:Key="Text.Cut" xml:space="preserve">Cut</x:String> <x:String x:Key="Text.Cut" xml:space="preserve">Cut</x:String>
<x:String x:Key="Text.DeleteBranch" xml:space="preserve">Delete Branch</x:String> <x:String x:Key="Text.DeleteBranch" xml:space="preserve">Delete Branch</x:String>
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch :</x:String> <x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch :</x:String>
@ -312,6 +316,7 @@
<x:String x:Key="Text.Push.To" xml:space="preserve">Remote Branch :</x:String> <x:String x:Key="Text.Push.To" xml:space="preserve">Remote Branch :</x:String>
<x:String x:Key="Text.Push.WithAllTags" xml:space="preserve">Push all tags</x:String> <x:String x:Key="Text.Push.WithAllTags" xml:space="preserve">Push all tags</x:String>
<x:String x:Key="Text.PushTag" xml:space="preserve">Push Tag To Remote</x:String> <x:String x:Key="Text.PushTag" xml:space="preserve">Push Tag To Remote</x:String>
<x:String x:Key="Text.PushTag.PushAllRemotes" xml:space="preserve">Push to all remotes</x:String>
<x:String x:Key="Text.PushTag.Remote" xml:space="preserve">Remote :</x:String> <x:String x:Key="Text.PushTag.Remote" xml:space="preserve">Remote :</x:String>
<x:String x:Key="Text.PushTag.Tag" xml:space="preserve">Tag :</x:String> <x:String x:Key="Text.PushTag.Tag" xml:space="preserve">Tag :</x:String>
<x:String x:Key="Text.Quit" xml:space="preserve">Quit</x:String> <x:String x:Key="Text.Quit" xml:space="preserve">Quit</x:String>

View file

@ -114,10 +114,14 @@
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">创建本地分支</x:String> <x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">创建本地分支</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">新建标签</x:String> <x:String x:Key="Text.CreateTag" xml:space="preserve">新建标签</x:String>
<x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">标签位于 </x:String> <x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">标签位于 </x:String>
<x:String x:Key="Text.CreateTag.GPGSign" xml:space="preserve">使用GPG签名</x:String>
<x:String x:Key="Text.CreateTag.Message" xml:space="preserve">标签描述 </x:String> <x:String x:Key="Text.CreateTag.Message" xml:space="preserve">标签描述 </x:String>
<x:String x:Key="Text.CreateTag.Message.Placeholder" xml:space="preserve">选填。</x:String> <x:String x:Key="Text.CreateTag.Message.Placeholder" xml:space="preserve">选填。</x:String>
<x:String x:Key="Text.CreateTag.Name" xml:space="preserve">标签名 </x:String> <x:String x:Key="Text.CreateTag.Name" xml:space="preserve">标签名 </x:String>
<x:String x:Key="Text.CreateTag.Name.Placeholder" xml:space="preserve">推荐格式 v1.0.0-alpha</x:String> <x:String x:Key="Text.CreateTag.Name.Placeholder" xml:space="preserve">推荐格式 v1.0.0-alpha</x:String>
<x:String x:Key="Text.CreateTag.Type" xml:space="preserve">类型 </x:String>
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">附注标签</x:String>
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">轻量标签</x:String>
<x:String x:Key="Text.Cut" xml:space="preserve">剪切</x:String> <x:String x:Key="Text.Cut" xml:space="preserve">剪切</x:String>
<x:String x:Key="Text.DeleteBranch" xml:space="preserve">删除分支确认</x:String> <x:String x:Key="Text.DeleteBranch" xml:space="preserve">删除分支确认</x:String>
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">分支名 </x:String> <x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">分支名 </x:String>
@ -312,6 +316,7 @@
<x:String x:Key="Text.Push.To" xml:space="preserve">远程分支 </x:String> <x:String x:Key="Text.Push.To" xml:space="preserve">远程分支 </x:String>
<x:String x:Key="Text.Push.WithAllTags" xml:space="preserve">同时推送标签</x:String> <x:String x:Key="Text.Push.WithAllTags" xml:space="preserve">同时推送标签</x:String>
<x:String x:Key="Text.PushTag" xml:space="preserve">推送标签到远程仓库</x:String> <x:String x:Key="Text.PushTag" xml:space="preserve">推送标签到远程仓库</x:String>
<x:String x:Key="Text.PushTag.PushAllRemotes" xml:space="preserve">推送到所有远程仓库</x:String>
<x:String x:Key="Text.PushTag.Remote" xml:space="preserve">远程仓库 </x:String> <x:String x:Key="Text.PushTag.Remote" xml:space="preserve">远程仓库 </x:String>
<x:String x:Key="Text.PushTag.Tag" xml:space="preserve">标签 </x:String> <x:String x:Key="Text.PushTag.Tag" xml:space="preserve">标签 </x:String>
<x:String x:Key="Text.Quit" xml:space="preserve">退出</x:String> <x:String x:Key="Text.Quit" xml:space="preserve">退出</x:String>

View file

@ -1,12 +1,16 @@
using System; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SourceGit.ViewModels namespace SourceGit.ViewModels
{ {
public class CreateTag : Popup public class CreateTag : Popup
{ {
public object BasedOn
{
get;
private set;
}
[Required(ErrorMessage = "Tag name is required!")] [Required(ErrorMessage = "Tag name is required!")]
[RegularExpression(@"^[\w\-\.]+$", ErrorMessage = "Bad tag name format!")] [RegularExpression(@"^[\w\-\.]+$", ErrorMessage = "Bad tag name format!")]
[CustomValidation(typeof(CreateTag), nameof(ValidateTagName))] [CustomValidation(typeof(CreateTag), nameof(ValidateTagName))]
@ -22,11 +26,17 @@ namespace SourceGit.ViewModels
set; set;
} }
public object BasedOn public bool Annotated
{
get => _annotated;
set => SetProperty(ref _annotated, value);
}
public bool SignTag
{ {
get; get;
private set; set;
} } = false;
public CreateTag(Repository repo, Models.Branch branch) public CreateTag(Repository repo, Models.Branch branch)
{ {
@ -65,7 +75,11 @@ namespace SourceGit.ViewModels
return Task.Run(() => return Task.Run(() =>
{ {
Commands.Tag.Add(_repo.FullPath, TagName, _basedOn, Message); if (_annotated)
Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, Message, SignTag);
else
Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn);
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });
@ -73,6 +87,7 @@ namespace SourceGit.ViewModels
private readonly Repository _repo = null; private readonly Repository _repo = null;
private string _tagName = string.Empty; private string _tagName = string.Empty;
private bool _annotated = true;
private readonly string _basedOn = string.Empty; private readonly string _basedOn = string.Empty;
} }
} }

View file

@ -22,6 +22,12 @@ namespace SourceGit.ViewModels
set; set;
} }
public bool PushAllRemotes
{
get => _pushAllRemotes;
set => SetProperty(ref _pushAllRemotes, value);
}
public PushTag(Repository repo, Models.Tag target) public PushTag(Repository repo, Models.Tag target)
{ {
_repo = repo; _repo = repo;
@ -37,12 +43,27 @@ namespace SourceGit.ViewModels
return Task.Run(() => return Task.Run(() =>
{ {
var succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, Target.Name, false).Exec(); bool succ = true;
if (_pushAllRemotes)
{
foreach (var remote in _repo.Remotes)
{
succ = new Commands.Push(_repo.FullPath, remote.Name, Target.Name, false).Exec();
if (!succ)
break;
}
}
else
{
succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, Target.Name, false).Exec();
}
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });
} }
private readonly Repository _repo = null; private readonly Repository _repo = null;
private bool _pushAllRemotes = false;
} }
} }

View file

@ -1,5 +1,4 @@
using System; using System.ComponentModel;
using System.ComponentModel;
using Avalonia.Controls; using Avalonia.Controls;

View file

@ -13,7 +13,7 @@
<TextBlock FontSize="18" <TextBlock FontSize="18"
Classes="bold" Classes="bold"
Text="{DynamicResource Text.CreateTag}"/> Text="{DynamicResource Text.CreateTag}"/>
<Grid Margin="0,16,8,0" RowDefinitions="32,32,64" ColumnDefinitions="150,*"> <Grid Margin="0,16,8,0" RowDefinitions="32,32,32,Auto,Auto" ColumnDefinitions="150,*">
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0" Margin="0,0,8,0"
@ -49,16 +49,37 @@
v:AutoFocusBehaviour.IsEnabled="True"/> v:AutoFocusBehaviour.IsEnabled="True"/>
<TextBlock Grid.Row="2" Grid.Column="0" <TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CreateTag.Type}"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<RadioButton Content="{DynamicResource Text.CreateTag.Type.Annotated}"
GroupName="TagKind"
IsChecked="{Binding Annotated, Mode=TwoWay}"/>
<RadioButton Content="{DynamicResource Text.CreateTag.Type.Lightweight}"
GroupName="TagKind"
Margin="8,0,0,0"/>
</StackPanel>
<TextBlock Grid.Row="3" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Top" HorizontalAlignment="Right" VerticalAlignment="Top"
Margin="0,6,8,0" Margin="0,6,8,0"
Text="{DynamicResource Text.CreateTag.Message}"/> Text="{DynamicResource Text.CreateTag.Message}"
<TextBox Grid.Row="2" Grid.Column="1" IsVisible="{Binding Annotated}"/>
Height="56" <TextBox Grid.Row="3" Grid.Column="1"
Height="64"
AcceptsReturn="True" AcceptsTab="False" AcceptsReturn="True" AcceptsTab="False"
VerticalAlignment="Center" VerticalContentAlignment="Top" VerticalAlignment="Center" VerticalContentAlignment="Top"
CornerRadius="2" CornerRadius="2"
Watermark="{DynamicResource Text.CreateTag.Message.Placeholder}" Watermark="{DynamicResource Text.CreateTag.Message.Placeholder}"
Text="{Binding Message, Mode=TwoWay}"/> Text="{Binding Message, Mode=TwoWay}"
IsVisible="{Binding Annotated}"/>
<CheckBox Grid.Row="4" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.CreateTag.GPGSign}"
IsChecked="{Binding SignTag, Mode=TwoWay}"
IsVisible="{Binding Annotated}"/>
</Grid> </Grid>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View file

@ -396,17 +396,10 @@
<Grid Margin="8" RowDefinitions="32,32,32" ColumnDefinitions="Auto,*"> <Grid Margin="8" RowDefinitions="32,32,32" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0" <TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preference.GPG.Enabled}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<CheckBox Grid.Row="0" Grid.Column="1"
IsChecked="{Binding #me.EnableGPGSigning, Mode=TwoWay}"/>
<TextBlock Grid.Row="1" Grid.Column="0"
Text="{DynamicResource Text.Preference.GPG.Path}" Text="{DynamicResource Text.Preference.GPG.Path}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<TextBox Grid.Row="1" Grid.Column="1" <TextBox Grid.Row="0" Grid.Column="1"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding #me.GPGExecutableFile, Mode=TwoWay}"> Text="{Binding #me.GPGExecutableFile, Mode=TwoWay}">
@ -417,15 +410,19 @@
</TextBox.InnerRightContent> </TextBox.InnerRightContent>
</TextBox> </TextBox>
<TextBlock Grid.Row="2" Grid.Column="0" <TextBlock Grid.Row="1" Grid.Column="0"
Text="{DynamicResource Text.Preference.GPG.UserKey}" Text="{DynamicResource Text.Preference.GPG.UserKey}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<TextBox Grid.Row="2" Grid.Column="1" <TextBox Grid.Row="1" Grid.Column="1"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding #me.GPGUserKey, Mode=TwoWay}" Text="{Binding #me.GPGUserKey, Mode=TwoWay}"
Watermark="{DynamicResource Text.Preference.GPG.UserKey.Placeholder}"/> Watermark="{DynamicResource Text.Preference.GPG.UserKey.Placeholder}"/>
<CheckBox Grid.Row="2" Grid.Column="1"
Content="{DynamicResource Text.Preference.GPG.Enabled}"
IsChecked="{Binding #me.EnableGPGSigning, Mode=TwoWay}"/>
</Grid> </Grid>
</TabItem> </TabItem>

View file

@ -207,10 +207,17 @@ namespace SourceGit.Views
private async void SelectGPGExecutable(object sender, RoutedEventArgs e) private async void SelectGPGExecutable(object sender, RoutedEventArgs e)
{ {
var pattern = OperatingSystem.IsWindows() ? "gpg.exe" : "gpg"; var patterns = new List<string>();
if (OperatingSystem.IsWindows())
patterns.Add("gpg.exe");
else if (OperatingSystem.IsLinux())
patterns.AddRange(new string[] { "gpg", "gpg2" });
else
patterns.Add("gpg");
var options = new FilePickerOpenOptions() var options = new FilePickerOpenOptions()
{ {
FileTypeFilter = [new FilePickerFileType("GPG Executable") { Patterns = [pattern] }], FileTypeFilter = [new FilePickerFileType("GPG Executable") { Patterns = patterns }],
AllowMultiple = false, AllowMultiple = false,
}; };

View file

@ -12,7 +12,7 @@
<TextBlock FontSize="18" <TextBlock FontSize="18"
Classes="bold" Classes="bold"
Text="{DynamicResource Text.PushTag}"/> Text="{DynamicResource Text.PushTag}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="150,*"> <Grid Margin="0,16,0,0" RowDefinitions="32,32,32" ColumnDefinitions="150,*">
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0" Margin="0,0,8,0"
@ -31,7 +31,8 @@
Height="28" Padding="8,0" Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding Remotes}" ItemsSource="{Binding Remotes}"
SelectedItem="{Binding SelectedRemote, Mode=TwoWay}"> SelectedItem="{Binding SelectedRemote, Mode=TwoWay}"
IsEnabled="{Binding !PushAllRemotes}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate x:DataType="{x:Type m:Remote}"> <DataTemplate x:DataType="{x:Type m:Remote}">
<StackPanel Orientation="Horizontal" Height="20" VerticalAlignment="Center"> <StackPanel Orientation="Horizontal" Height="20" VerticalAlignment="Center">
@ -41,6 +42,10 @@
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<CheckBox Grid.Row="2" Grid.Column="1"
Content="{DynamicResource Text.PushTag.PushAllRemotes}"
IsChecked="{Binding PushAllRemotes, Mode=TwoWay}"/>
</Grid> </Grid>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View file

@ -47,23 +47,18 @@
Text="{Binding HttpProxy, Mode=TwoWay}"/> Text="{Binding HttpProxy, Mode=TwoWay}"/>
<TextBlock Grid.Row="3" Grid.Column="0" <TextBlock Grid.Row="3" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Preference.GPG.Enabled}"/>
<CheckBox Grid.Row="3" Grid.Column="1"
x:Name="chkGPGSigningEnabled"
IsChecked="{Binding GPGSigningEnabled, Mode=TwoWay}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0" Margin="0,0,8,0"
Text="{DynamicResource Text.Preference.GPG.UserKey}"/> Text="{DynamicResource Text.Preference.GPG.UserKey}"/>
<TextBox Grid.Row="4" Grid.Column="1" <TextBox Grid.Row="3" Grid.Column="1"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Watermark="{DynamicResource Text.Preference.GPG.UserKey.Placeholder}" Watermark="{DynamicResource Text.Preference.GPG.UserKey.Placeholder}"
Text="{Binding GPGUserSigningKey, Mode=TwoWay}" Text="{Binding GPGUserSigningKey, Mode=TwoWay}"/>
IsEnabled="{Binding #chkGPGSigningEnabled.IsChecked}"/>
<CheckBox Grid.Row="4" Grid.Column="1"
Content="{DynamicResource Text.Preference.GPG.Enabled}"
IsChecked="{Binding GPGSigningEnabled, Mode=TwoWay}"/>
</Grid> </Grid>
</StackPanel> </StackPanel>
</UserControl> </UserControl>