refactor: merge BranchTrackStatus to Branch

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2025-08-29 17:32:22 +08:00
parent 9bbb858ee3
commit 01ed9caea4
12 changed files with 74 additions and 79 deletions

View File

@@ -26,15 +26,16 @@ namespace SourceGit.Commands
return branches;
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
var remoteHeads = new Dictionary<string, string>();
var mismatched = new HashSet<string>();
var remotes = new Dictionary<string, Models.Branch>();
foreach (var line in lines)
{
var b = ParseLine(line);
var b = ParseLine(line, mismatched);
if (b != null)
{
branches.Add(b);
if (!b.IsLocal)
remoteHeads.Add(b.FullName, b.Head);
remotes.Add(b.FullName, b);
}
}
@@ -42,15 +43,16 @@ namespace SourceGit.Commands
{
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
{
if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead))
if (remotes.TryGetValue(b.Upstream, out var upstream))
{
b.IsUpstreamGone = false;
b.TrackStatus ??= await new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).GetResultAsync().ConfigureAwait(false);
if (mismatched.Contains(b.FullName))
await new QueryTrackStatus(WorkingDirectory).GetResultAsync(b, upstream).ConfigureAwait(false);
}
else
{
b.IsUpstreamGone = true;
b.TrackStatus ??= new Models.BranchTrackStatus();
}
}
}
@@ -58,7 +60,7 @@ namespace SourceGit.Commands
return branches;
}
private Models.Branch ParseLine(string line)
private Models.Branch ParseLine(string line, HashSet<string> mismatched)
{
var parts = line.Split('\0');
if (parts.Length != 7)
@@ -103,11 +105,11 @@ namespace SourceGit.Commands
branch.Upstream = parts[4];
branch.IsUpstreamGone = false;
if (!branch.IsLocal ||
string.IsNullOrEmpty(branch.Upstream) ||
string.IsNullOrEmpty(parts[5]) ||
parts[5].Equals("=", StringComparison.Ordinal))
branch.TrackStatus = new Models.BranchTrackStatus();
if (branch.IsLocal &&
!string.IsNullOrEmpty(branch.Upstream) &&
!string.IsNullOrEmpty(parts[5]) &&
!parts[5].Equals("=", StringComparison.Ordinal))
mismatched.Add(branch.FullName);
branch.WorktreePath = parts[6];
return branch;

View File

@@ -5,31 +5,28 @@ namespace SourceGit.Commands
{
public class QueryTrackStatus : Command
{
public QueryTrackStatus(string repo, string local, string upstream)
public QueryTrackStatus(string repo)
{
WorkingDirectory = repo;
Context = repo;
Args = $"rev-list --left-right {local}...{upstream}";
}
public async Task<Models.BranchTrackStatus> GetResultAsync()
public async Task GetResultAsync(Models.Branch local, Models.Branch remote)
{
var status = new Models.BranchTrackStatus();
Args = $"rev-list --left-right {local.Head}...{remote.Head}";
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return status;
return;
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (line[0] == '>')
status.Behind.Add(line.Substring(1));
local.Behind.Add(line.Substring(1));
else
status.Ahead.Add(line.Substring(1));
local.Ahead.Add(line.Substring(1));
}
return status;
}
}
}

View File

@@ -2,27 +2,6 @@
namespace SourceGit.Models
{
public class BranchTrackStatus
{
public List<string> Ahead { get; set; } = new List<string>();
public List<string> Behind { get; set; } = new List<string>();
public bool IsVisible => Ahead.Count > 0 || Behind.Count > 0;
public override string ToString()
{
if (Ahead.Count == 0 && Behind.Count == 0)
return string.Empty;
var track = "";
if (Ahead.Count > 0)
track += $"{Ahead.Count}↑";
if (Behind.Count > 0)
track += $" {Behind.Count}↓";
return track.Trim();
}
}
public enum BranchSortMode
{
Name = 0,
@@ -39,11 +18,33 @@ namespace SourceGit.Models
public bool IsCurrent { get; set; }
public bool IsDetachedHead { get; set; }
public string Upstream { get; set; }
public BranchTrackStatus TrackStatus { get; set; }
public List<string> Ahead { get; set; } = [];
public List<string> Behind { get; set; } = [];
public string Remote { get; set; }
public bool IsUpstreamGone { get; set; }
public string WorktreePath { get; set; }
public bool IsTrackStatusVisible
{
get
{
return Ahead.Count + Behind.Count > 0;
}
}
public string TrackStatusDescription
{
get
{
var ahead = Ahead.Count;
var behind = Behind.Count;
if (ahead > 0)
return behind > 0 ? $"{ahead}↑ {behind}↓" : $"{ahead}↑";
return behind > 0 ? $"{behind}↓" : string.Empty;
}
}
public bool HasWorktree => !IsCurrent && !string.IsNullOrEmpty(WorktreePath);
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
}

View File

@@ -219,12 +219,12 @@ namespace SourceGit.ViewModels
return false;
var lb = _repo.Branches.Find(x => x.IsLocal && x.Upstream == rb.FullName);
if (lb == null || lb.TrackStatus.Ahead.Count > 0)
if (lb == null || lb.Ahead.Count > 0)
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new CreateBranch(_repo, rb));
}
else if (lb.TrackStatus.Behind.Count > 0)
else if (lb.Behind.Count > 0)
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new CheckoutAndFastForward(_repo, lb, rb));
@@ -265,7 +265,7 @@ namespace SourceGit.ViewModels
continue;
var lb = _repo.Branches.Find(x => x.IsLocal && x.Upstream == rb.FullName);
if (lb is { TrackStatus.Ahead.Count: 0 })
if (lb.Ahead.Count == 0)
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new CheckoutAndFastForward(_repo, lb, rb));

View File

@@ -1182,7 +1182,7 @@ namespace SourceGit.ViewModels
if (_workingCopy != null)
_workingCopy.HasRemotes = remotes.Count > 0;
var hasPendingPullOrPush = CurrentBranch?.TrackStatus.IsVisible ?? false;
var hasPendingPullOrPush = CurrentBranch?.IsTrackStatusVisible ?? false;
GetOwnerPage()?.ChangeDirtyState(Models.DirtyState.HasPendingPullOrPush, !hasPendingPullOrPush);
});
}, token);
@@ -1465,9 +1465,9 @@ namespace SourceGit.ViewModels
{
if (b.IsLocal &&
b.Upstream.Equals(branch.FullName, StringComparison.Ordinal) &&
b.TrackStatus.Ahead.Count == 0)
b.Ahead.Count == 0)
{
if (b.TrackStatus.Behind.Count > 0)
if (b.Behind.Count > 0)
ShowPopup(new CheckoutAndFastForward(this, b, branch));
else if (!b.IsCurrent)
await CheckoutBranchAsync(b);

View File

@@ -74,7 +74,7 @@
Margin="0,4,0,0"
HorizontalAlignment="Left" VerticalAlignment="Center"
Text="{DynamicResource Text.BranchTree.Status}"
IsVisible="{Binding TrackStatus.IsVisible, Mode=OneWay}"/>
IsVisible="{Binding IsTrackStatusVisible, Mode=OneWay}"/>
<v:BranchTreeNodeTrackStatusTooltip Grid.Row="1" Grid.Column="1"
Margin="8,4,0,0"/>

View File

@@ -183,11 +183,11 @@ namespace SourceGit.Views
if (DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
{
var status = branch.TrackStatus.ToString();
if (!string.IsNullOrEmpty(status))
var desc = branch.TrackStatusDescription;
if (!string.IsNullOrEmpty(desc))
{
_label = new FormattedText(
status,
desc,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily),
@@ -212,22 +212,18 @@ namespace SourceGit.Views
Text = string.Empty;
if (DataContext is not Models.Branch { TrackStatus: { IsVisible: true } track })
if (DataContext is not Models.Branch { IsTrackStatusVisible: true } branch)
{
SetCurrentValue(IsVisibleProperty, false);
return;
}
if (track.Ahead.Count > 0)
{
Text = track.Behind.Count > 0 ?
App.Text("BranchTree.AheadBehind", track.Ahead.Count, track.Behind.Count) :
App.Text("BranchTree.Ahead", track.Ahead.Count);
}
else if (track.Behind.Count > 0)
{
Text = App.Text("BranchTree.Behind", track.Behind.Count);
}
var ahead = branch.Ahead.Count;
var behind = branch.Behind.Count;
if (ahead > 0)
Text = behind > 0 ? App.Text("BranchTree.AheadBehind", ahead, behind) : App.Text("BranchTree.Ahead", ahead);
else
Text = App.Text("BranchTree.Behind", behind);
SetCurrentValue(IsVisibleProperty, true);
}
@@ -638,7 +634,7 @@ namespace SourceGit.Views
var fastForward = new MenuItem();
fastForward.Header = App.Text("BranchCM.FastForward", upstream.FriendlyName);
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
fastForward.IsEnabled = branch.TrackStatus.Ahead.Count == 0 && branch.TrackStatus.Behind.Count > 0;
fastForward.IsEnabled = branch.Ahead.Count == 0 && branch.Behind.Count > 0;
fastForward.Click += async (_, e) =>
{
if (repo.CanCreatePopup())
@@ -685,7 +681,7 @@ namespace SourceGit.Views
var fastForward = new MenuItem();
fastForward.Header = App.Text("BranchCM.FastForward", upstream.FriendlyName);
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
fastForward.IsEnabled = branch.TrackStatus.Ahead.Count == 0 && branch.TrackStatus.Behind.Count > 0;
fastForward.IsEnabled = branch.Ahead.Count == 0 && branch.Behind.Count > 0;
fastForward.Click += async (_, e) =>
{
if (repo.CanCreatePopup())
@@ -697,7 +693,7 @@ namespace SourceGit.Views
var fetchInto = new MenuItem();
fetchInto.Header = App.Text("BranchCM.FetchInto", upstream.FriendlyName, branch.Name);
fetchInto.Icon = App.CreateMenuIcon("Icons.Fetch");
fetchInto.IsEnabled = branch.TrackStatus.Ahead.Count == 0;
fetchInto.IsEnabled = branch.Ahead.Count == 0;
fetchInto.Click += async (_, e) =>
{
if (repo.CanCreatePopup())

View File

@@ -32,11 +32,11 @@
VerticalAlignment="Center"
CornerRadius="9"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding LocalBranch.TrackStatus.IsVisible}">
IsVisible="{Binding LocalBranch.IsTrackStatusVisible, Mode=OneWay}">
<TextBlock Foreground="{DynamicResource Brush.BadgeFG}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="10"
Text="{Binding LocalBranch.TrackStatus}"/>
Text="{Binding LocalBranch.TrackStatusDescription, Mode=OneWay}"/>
</Border>
</StackPanel>

View File

@@ -52,14 +52,13 @@ namespace SourceGit.Views
protected override Size MeasureOverride(Size availableSize)
{
if (DataContext is Models.Commit commit && CurrentBranch is not null)
if (DataContext is Models.Commit commit && CurrentBranch is { } b)
{
var sha = commit.SHA;
var track = CurrentBranch.TrackStatus;
if (track.Ahead.Contains(sha))
if (b.Ahead.Contains(sha))
_status = Status.Ahead;
else if (track.Behind.Contains(sha))
else if (b.Behind.Contains(sha))
_status = Status.Behind;
else
_status = Status.Normal;

View File

@@ -23,11 +23,11 @@
VerticalAlignment="Center"
CornerRadius="9"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding Target.TrackStatus.IsVisible}">
IsVisible="{Binding Target.IsTrackStatusVisible}">
<TextBlock Foreground="{DynamicResource Brush.BadgeFG}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="10"
Text="{Binding Target.TrackStatus}"/>
Text="{Binding Target.TrackStatusDescription}"/>
</Border>
</StackPanel>

View File

@@ -52,11 +52,11 @@
VerticalAlignment="Center"
CornerRadius="9"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding TrackStatus.IsVisible}">
IsVisible="{Binding IsTrackStatusVisible}">
<TextBlock Foreground="{DynamicResource Brush.BadgeFG}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="10"
Text="{Binding TrackStatus}"/>
Text="{Binding TrackStatusDescription, Mode=OneWay}"/>
</Border>
</Grid>
</DataTemplate>

View File

@@ -711,7 +711,7 @@ namespace SourceGit.Views
if (!isHead)
{
if (current.TrackStatus.Ahead.Contains(commit.SHA))
if (current.Ahead.Contains(commit.SHA))
{
var upstream = repo.Branches.Find(x => x.FullName.Equals(current.Upstream, StringComparison.Ordinal));
var pushRevision = new MenuItem();
@@ -911,7 +911,7 @@ namespace SourceGit.Views
var fastForward = new MenuItem();
fastForward.Header = App.Text("BranchCM.FastForward", upstream);
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
fastForward.IsEnabled = current.TrackStatus.Ahead.Count == 0 && current.TrackStatus.Behind.Count > 0;
fastForward.IsEnabled = current.Ahead.Count == 0 && current.Behind.Count > 0;
fastForward.Click += async (_, e) =>
{
var b = repo.Branches.Find(x => x.FriendlyName == upstream);