diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs index d279dc89..b741fb5c 100644 --- a/src/Commands/QueryBranches.cs +++ b/src/Commands/QueryBranches.cs @@ -26,15 +26,16 @@ namespace SourceGit.Commands return branches; var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); - var remoteHeads = new Dictionary(); + var mismatched = new HashSet(); + var remotes = new Dictionary(); 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 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; diff --git a/src/Commands/QueryTrackStatus.cs b/src/Commands/QueryTrackStatus.cs index d687d274..f00074f8 100644 --- a/src/Commands/QueryTrackStatus.cs +++ b/src/Commands/QueryTrackStatus.cs @@ -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 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; } } } diff --git a/src/Models/Branch.cs b/src/Models/Branch.cs index ecb6d516..d012a735 100644 --- a/src/Models/Branch.cs +++ b/src/Models/Branch.cs @@ -2,27 +2,6 @@ namespace SourceGit.Models { - public class BranchTrackStatus - { - public List Ahead { get; set; } = new List(); - public List Behind { get; set; } = new List(); - - 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 Ahead { get; set; } = []; + public List 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}"; } diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index da627e00..5c9d67a7 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -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)); diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index bd258b9d..c4c43ad4 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -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); diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml index bcfee37a..a48bc6c8 100644 --- a/src/Views/BranchTree.axaml +++ b/src/Views/BranchTree.axaml @@ -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}"/> diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index 785c5de7..ffa21c17 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -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()) diff --git a/src/Views/CheckoutAndFastForward.axaml b/src/Views/CheckoutAndFastForward.axaml index ad0422b3..e010544f 100644 --- a/src/Views/CheckoutAndFastForward.axaml +++ b/src/Views/CheckoutAndFastForward.axaml @@ -32,11 +32,11 @@ VerticalAlignment="Center" CornerRadius="9" Background="{DynamicResource Brush.Badge}" - IsVisible="{Binding LocalBranch.TrackStatus.IsVisible}"> + IsVisible="{Binding LocalBranch.IsTrackStatusVisible, Mode=OneWay}"> + Text="{Binding LocalBranch.TrackStatusDescription, Mode=OneWay}"/> diff --git a/src/Views/CommitStatusIndicator.cs b/src/Views/CommitStatusIndicator.cs index 7073011a..49d74c98 100644 --- a/src/Views/CommitStatusIndicator.cs +++ b/src/Views/CommitStatusIndicator.cs @@ -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; diff --git a/src/Views/DeleteBranch.axaml b/src/Views/DeleteBranch.axaml index 3439fc9e..fc07b145 100644 --- a/src/Views/DeleteBranch.axaml +++ b/src/Views/DeleteBranch.axaml @@ -23,11 +23,11 @@ VerticalAlignment="Center" CornerRadius="9" Background="{DynamicResource Brush.Badge}" - IsVisible="{Binding Target.TrackStatus.IsVisible}"> + IsVisible="{Binding Target.IsTrackStatusVisible}"> + Text="{Binding Target.TrackStatusDescription}"/> diff --git a/src/Views/DeleteMultipleBranches.axaml b/src/Views/DeleteMultipleBranches.axaml index cf084e14..46b992b0 100644 --- a/src/Views/DeleteMultipleBranches.axaml +++ b/src/Views/DeleteMultipleBranches.axaml @@ -52,11 +52,11 @@ VerticalAlignment="Center" CornerRadius="9" Background="{DynamicResource Brush.Badge}" - IsVisible="{Binding TrackStatus.IsVisible}"> + IsVisible="{Binding IsTrackStatusVisible}"> + Text="{Binding TrackStatusDescription, Mode=OneWay}"/> diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs index c37c900e..b8e5a097 100644 --- a/src/Views/Histories.axaml.cs +++ b/src/Views/Histories.axaml.cs @@ -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);