refactor: rewrite git-flow integration

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2025-05-20 21:08:00 +08:00
parent 6fa454ace8
commit 4d5be9f280
No known key found for this signature in database
9 changed files with 197 additions and 166 deletions

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Text;
using Avalonia.Threading;
@ -8,34 +7,6 @@ namespace SourceGit.Commands
{
public static class GitFlow
{
public class BranchDetectResult
{
public bool IsGitFlowBranch { get; set; } = false;
public string Type { get; set; } = string.Empty;
public string Prefix { get; set; } = string.Empty;
}
public static bool IsEnabled(string repo, List<Models.Branch> branches)
{
var localBrancheNames = new HashSet<string>();
foreach (var branch in branches)
{
if (branch.IsLocal)
localBrancheNames.Add(branch.Name);
}
var config = new Config(repo).ListAll();
if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master))
return false;
if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop))
return false;
return config.ContainsKey("gitflow.prefix.feature") &&
config.ContainsKey("gitflow.prefix.release") &&
config.ContainsKey("gitflow.prefix.hotfix");
}
public static bool Init(string repo, List<Models.Branch> branches, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log)
{
var current = branches.Find(x => x.IsCurrent);
@ -66,90 +37,53 @@ namespace SourceGit.Commands
return init.Exec();
}
public static string GetPrefix(string repo, string type)
public static bool Start(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log)
{
return new Config(repo).Get($"gitflow.prefix.{type}");
}
public static BranchDetectResult DetectType(string repo, List<Models.Branch> branches, string branch)
{
var rs = new BranchDetectResult();
var localBrancheNames = new HashSet<string>();
foreach (var b in branches)
{
if (b.IsLocal)
localBrancheNames.Add(b.Name);
}
var config = new Config(repo).ListAll();
if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master))
return rs;
if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop))
return rs;
if (!config.TryGetValue("gitflow.prefix.feature", out var feature) ||
!config.TryGetValue("gitflow.prefix.release", out var release) ||
!config.TryGetValue("gitflow.prefix.hotfix", out var hotfix))
return rs;
if (branch.StartsWith(feature, StringComparison.Ordinal))
{
rs.IsGitFlowBranch = true;
rs.Type = "feature";
rs.Prefix = feature;
}
else if (branch.StartsWith(release, StringComparison.Ordinal))
{
rs.IsGitFlowBranch = true;
rs.Type = "release";
rs.Prefix = release;
}
else if (branch.StartsWith(hotfix, StringComparison.Ordinal))
{
rs.IsGitFlowBranch = true;
rs.Type = "hotfix";
rs.Prefix = hotfix;
}
return rs;
}
public static bool Start(string repo, string type, string name, Models.ICommandLog log)
{
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
{
Dispatcher.UIThread.Post(() =>
{
App.RaiseException(repo, "Bad branch type!!!");
});
return false;
}
var start = new Command();
start.WorkingDirectory = repo;
start.Context = repo;
start.Args = $"flow {type} start {name}";
switch (type)
{
case Models.GitFlowBranchType.Feature:
start.Args = $"flow feature start {name}";
break;
case Models.GitFlowBranchType.Release:
start.Args = $"flow release start {name}";
break;
case Models.GitFlowBranchType.Hotfix:
start.Args = $"flow hotfix start {name}";
break;
default:
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!"));
return false;
}
start.Log = log;
return start.Exec();
}
public static bool Finish(string repo, string type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log)
public static bool Finish(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log)
{
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
{
Dispatcher.UIThread.Post(() =>
{
App.RaiseException(repo, "Bad branch type!!!");
});
return false;
}
var builder = new StringBuilder();
builder.Append("flow ");
builder.Append(type);
switch (type)
{
case Models.GitFlowBranchType.Feature:
builder.Append("feature");
break;
case Models.GitFlowBranchType.Release:
builder.Append("release");
break;
case Models.GitFlowBranchType.Hotfix:
builder.Append("hotfix");
break;
default:
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!"));
return false;
}
builder.Append(" finish ");
if (squash)
builder.Append("--squash ");
@ -166,14 +100,5 @@ namespace SourceGit.Commands
finish.Log = log;
return finish.Exec();
}
private static readonly List<string> SUPPORTED_BRANCH_TYPES = new List<string>()
{
"feature",
"release",
"bugfix",
"hotfix",
"support",
};
}
}

46
src/Models/GitFlow.cs Normal file
View file

@ -0,0 +1,46 @@
namespace SourceGit.Models
{
public enum GitFlowBranchType
{
None = 0,
Feature,
Release,
Hotfix,
}
public class GitFlow
{
public string Master { get; set; } = string.Empty;
public string Develop { get; set; } = string.Empty;
public string FeaturePrefix { get; set; } = string.Empty;
public string ReleasePrefix { get; set; } = string.Empty;
public string HotfixPrefix { get; set; } = string.Empty;
public bool IsValid
{
get
{
return !string.IsNullOrEmpty(Master) &&
!string.IsNullOrEmpty(Develop) &&
!string.IsNullOrEmpty(FeaturePrefix) &&
!string.IsNullOrEmpty(ReleasePrefix) &&
!string.IsNullOrEmpty(HotfixPrefix);
}
}
public string GetPrefix(GitFlowBranchType type)
{
switch (type)
{
case GitFlowBranchType.Feature:
return FeaturePrefix;
case GitFlowBranchType.Release:
return ReleasePrefix;
case GitFlowBranchType.Hotfix:
return HotfixPrefix;
default:
return string.Empty;
}
}
}
}

View file

@ -9,9 +9,11 @@ namespace SourceGit.ViewModels
get;
}
public bool IsFeature => _type == "feature";
public bool IsRelease => _type == "release";
public bool IsHotfix => _type == "hotfix";
public Models.GitFlowBranchType Type
{
get;
private set;
}
public bool Squash
{
@ -31,27 +33,27 @@ namespace SourceGit.ViewModels
set;
} = false;
public GitFlowFinish(Repository repo, Models.Branch branch, string type, string prefix)
public GitFlowFinish(Repository repo, Models.Branch branch, Models.GitFlowBranchType type)
{
_repo = repo;
_type = type;
_prefix = prefix;
Branch = branch;
Type = type;
}
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = $"Git Flow - Finish {Branch.Name} ...";
var name = Branch.Name.StartsWith(_prefix) ? Branch.Name.Substring(_prefix.Length) : Branch.Name;
ProgressDescription = $"Git Flow - finishing {_type} {name} ...";
var log = _repo.CreateLog("Gitflow - Finish");
var log = _repo.CreateLog("GitFlow - Finish");
Use(log);
var prefix = _repo.GitFlow.GetPrefix(Type);
var name = Branch.Name.StartsWith(prefix) ? Branch.Name.Substring(prefix.Length) : Branch.Name;
return Task.Run(() =>
{
var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, Squash, AutoPush, KeepBranch, log);
var succ = Commands.GitFlow.Finish(_repo.FullPath, Type, name, Squash, AutoPush, KeepBranch, log);
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
@ -59,7 +61,5 @@ namespace SourceGit.ViewModels
}
private readonly Repository _repo;
private readonly string _type;
private readonly string _prefix;
}
}

View file

@ -5,6 +5,18 @@ namespace SourceGit.ViewModels
{
public class GitFlowStart : Popup
{
public Models.GitFlowBranchType Type
{
get;
private set;
}
public string Prefix
{
get;
private set;
}
[Required(ErrorMessage = "Name is required!!!")]
[RegularExpression(@"^[\w\-/\.#]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(GitFlowStart), nameof(ValidateBranchName))]
@ -14,27 +26,19 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _name, value, true);
}
public string Prefix
{
get => _prefix;
}
public bool IsFeature => _type == "feature";
public bool IsRelease => _type == "release";
public bool IsHotfix => _type == "hotfix";
public GitFlowStart(Repository repo, string type)
public GitFlowStart(Repository repo, Models.GitFlowBranchType type)
{
_repo = repo;
_type = type;
_prefix = Commands.GitFlow.GetPrefix(repo.FullPath, type);
Type = type;
Prefix = _repo.GitFlow.GetPrefix(type);
}
public static ValidationResult ValidateBranchName(string name, ValidationContext ctx)
{
if (ctx.ObjectInstance is GitFlowStart starter)
{
var check = $"{starter._prefix}{name}";
var check = $"{starter.Prefix}{name}";
foreach (var b in starter._repo.Branches)
{
if (b.FriendlyName == check)
@ -48,14 +52,14 @@ namespace SourceGit.ViewModels
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = $"Git Flow - starting {_type} {_name} ...";
ProgressDescription = $"Git Flow - Start {Prefix}{_name} ...";
var log = _repo.CreateLog("Gitflow - Start");
var log = _repo.CreateLog("GitFlow - Start");
Use(log);
return Task.Run(() =>
{
var succ = Commands.GitFlow.Start(_repo.FullPath, _type, _name, log);
var succ = Commands.GitFlow.Start(_repo.FullPath, Type, _name, log);
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
@ -63,8 +67,6 @@ namespace SourceGit.ViewModels
}
private readonly Repository _repo;
private readonly string _type;
private readonly string _prefix;
private string _name = null;
}
}

View file

@ -975,8 +975,8 @@ namespace SourceGit.ViewModels
if (!_repo.IsBare)
{
var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, current.Name);
if (detect.IsGitFlowBranch)
var type = _repo.GetGitFlowType(current);
if (type != Models.GitFlowBranchType.None)
{
var finish = new MenuItem();
finish.Header = App.Text("BranchCM.Finish", current.Name);
@ -984,7 +984,7 @@ namespace SourceGit.ViewModels
finish.Click += (_, e) =>
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new GitFlowFinish(_repo, current, detect.Type, detect.Prefix));
_repo.ShowPopup(new GitFlowFinish(_repo, current, type));
e.Handled = true;
};
submenu.Items.Add(finish);
@ -1063,8 +1063,8 @@ namespace SourceGit.ViewModels
if (!_repo.IsBare)
{
var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, branch.Name);
if (detect.IsGitFlowBranch)
var type = _repo.GetGitFlowType(branch);
if (type != Models.GitFlowBranchType.None)
{
var finish = new MenuItem();
finish.Header = App.Text("BranchCM.Finish", branch.Name);
@ -1072,7 +1072,7 @@ namespace SourceGit.ViewModels
finish.Click += (_, e) =>
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new GitFlowFinish(_repo, branch, detect.Type, detect.Prefix));
_repo.ShowPopup(new GitFlowFinish(_repo, branch, type));
e.Handled = true;
};
submenu.Items.Add(finish);

View file

@ -121,7 +121,23 @@ namespace SourceGit.ViewModels
log);
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true));
CallUIThread(() =>
{
if (succ)
{
var gitflow = new Models.GitFlow();
gitflow.Master = _master;
gitflow.Develop = _develop;
gitflow.FeaturePrefix = _featurePrefix;
gitflow.ReleasePrefix = _releasePrefix;
gitflow.HotfixPrefix = _hotfixPrefix;
_repo.GitFlow = gitflow;
}
_repo.SetWatcherEnabled(true);
});
return succ;
});
}

View file

@ -51,6 +51,12 @@ namespace SourceGit.ViewModels
get => _settings;
}
public Models.GitFlow GitFlow
{
get;
set;
} = new Models.GitFlow();
public Models.FilterMode HistoriesFilterMode
{
get => _historiesFilterMode;
@ -595,6 +601,28 @@ namespace SourceGit.ViewModels
GetOwnerPage()?.StartPopup(popup);
}
public bool IsGitFlowEnabled()
{
return GitFlow is { IsValid: true } &&
_branches.Find(x => x.IsLocal && x.Name.Equals(GitFlow.Master, StringComparison.Ordinal)) != null &&
_branches.Find(x => x.IsLocal && x.Name.Equals(GitFlow.Develop, StringComparison.Ordinal)) != null;
}
public Models.GitFlowBranchType GetGitFlowType(Models.Branch b)
{
if (!IsGitFlowEnabled())
return Models.GitFlowBranchType.None;
var name = b.Name;
if (name.StartsWith(GitFlow.FeaturePrefix, StringComparison.Ordinal))
return Models.GitFlowBranchType.Feature;
if (name.StartsWith(GitFlow.ReleasePrefix, StringComparison.Ordinal))
return Models.GitFlowBranchType.Release;
if (name.StartsWith(GitFlow.HotfixPrefix, StringComparison.Ordinal))
return Models.GitFlowBranchType.Hotfix;
return Models.GitFlowBranchType.None;
}
public CommandLog CreateLog(string name)
{
var log = new CommandLog(name);
@ -606,8 +634,19 @@ namespace SourceGit.ViewModels
{
Task.Run(() =>
{
var allowedSignersFile = new Commands.Config(_fullpath).Get("gpg.ssh.allowedSignersFile");
_hasAllowedSignersFile = !string.IsNullOrEmpty(allowedSignersFile);
var config = new Commands.Config(_fullpath).ListAll();
_hasAllowedSignersFile = config.TryGetValue("gpg.ssh.allowedSignersFile", out var allowedSignersFile) && !string.IsNullOrEmpty(allowedSignersFile);
if (config.TryGetValue("gitflow.branch.master", out var masterName))
GitFlow.Master = masterName;
if (config.TryGetValue("gitflow.branch.develop", out var developName))
GitFlow.Develop = developName;
if (config.TryGetValue("gitflow.prefix.feature", out var featurePrefix))
GitFlow.FeaturePrefix = featurePrefix;
if (config.TryGetValue("gitflow.prefix.release", out var releasePrefix))
GitFlow.ReleasePrefix = releasePrefix;
if (config.TryGetValue("gitflow.prefix.hotfix", out var hotfixPrefix))
GitFlow.HotfixPrefix = hotfixPrefix;
});
Task.Run(RefreshBranches);
@ -1377,8 +1416,7 @@ namespace SourceGit.ViewModels
var menu = new ContextMenu();
menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
var isGitFlowEnabled = Commands.GitFlow.IsEnabled(_fullpath, _branches);
if (isGitFlowEnabled)
if (IsGitFlowEnabled())
{
var startFeature = new MenuItem();
startFeature.Header = App.Text("GitFlow.StartFeature");
@ -1386,7 +1424,7 @@ namespace SourceGit.ViewModels
startFeature.Click += (_, e) =>
{
if (CanCreatePopup())
ShowPopup(new GitFlowStart(this, "feature"));
ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Feature));
e.Handled = true;
};
@ -1396,7 +1434,7 @@ namespace SourceGit.ViewModels
startRelease.Click += (_, e) =>
{
if (CanCreatePopup())
ShowPopup(new GitFlowStart(this, "release"));
ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Release));
e.Handled = true;
};
@ -1406,7 +1444,7 @@ namespace SourceGit.ViewModels
startHotfix.Click += (_, e) =>
{
if (CanCreatePopup())
ShowPopup(new GitFlowStart(this, "hotfix"));
ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Hotfix));
e.Handled = true;
};
@ -1867,8 +1905,8 @@ namespace SourceGit.ViewModels
if (!IsBare)
{
var detect = Commands.GitFlow.DetectType(_fullpath, _branches, branch.Name);
if (detect.IsGitFlowBranch)
var type = GetGitFlowType(branch);
if (type != Models.GitFlowBranchType.None)
{
var finish = new MenuItem();
finish.Header = App.Text("BranchCM.Finish", branch.Name);
@ -1876,7 +1914,7 @@ namespace SourceGit.ViewModels
finish.Click += (_, e) =>
{
if (CanCreatePopup())
ShowPopup(new GitFlowFinish(this, branch, detect.Type, detect.Prefix));
ShowPopup(new GitFlowFinish(this, branch, type));
e.Handled = true;
};
menu.Items.Add(new MenuItem() { Header = "-" });

View file

@ -2,7 +2,9 @@
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:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="SourceGit.Views.GitFlowFinish"
x:DataType="vm:GitFlowFinish">
@ -10,15 +12,15 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.GitFlow.FinishFeature}"
IsVisible="{Binding IsFeature}"/>
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:GitFlowBranchType.Feature}}"/>
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.GitFlow.FinishRelease}"
IsVisible="{Binding IsRelease}"/>
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:GitFlowBranchType.Release}}"/>
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.GitFlow.FinishHotfix}"
IsVisible="{Binding IsHotfix}"/>
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:GitFlowBranchType.Hotfix}}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,32" ColumnDefinitions="150,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"

View file

@ -2,8 +2,10 @@
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:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="SourceGit.Views.GitFlowStart"
x:DataType="vm:GitFlowStart">
@ -11,15 +13,15 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.GitFlow.StartFeatureTitle}"
IsVisible="{Binding IsFeature}"/>
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:GitFlowBranchType.Feature}}"/>
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.GitFlow.StartReleaseTitle}"
IsVisible="{Binding IsRelease}"/>
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:GitFlowBranchType.Release}}"/>
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.GitFlow.StartHotfixTitle}"
IsVisible="{Binding IsHotfix}"/>
IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:GitFlowBranchType.Hotfix}}"/>
<Grid Margin="0,16,0,0" ColumnDefinitions="120,*">
<TextBlock Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"