Skip to content

Commit

Permalink
Provide ability to sort/order branches
Browse files Browse the repository at this point in the history
Restore the functionality (albeit improved) removed in 9c7c76f.

Allow to either defer sorting to git (i.e. sort by Default) which is
either implicit, or configured via `git config branch.sort`.

Alternatively expose the following sort by options:
- authordate
- committerdate
- creatordate
- taggerdate
- refname
- objectsize
- upstream

For user-specified sort, allow to select ascending or descending order (default: desc).

Git v2.19+ is required to facilitate the above, hence bump in the min version.

The sort configuration applies to both the left panel and the branches
dropdown in the toolstrip.

Fixes #6310
  • Loading branch information
RussKie committed Sep 10, 2020
1 parent 4b80245 commit 93831ba
Show file tree
Hide file tree
Showing 21 changed files with 272 additions and 137 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,4 @@ tree.txt
*.binlog
artifacts/
.tools/vswhere/
.dotnet/
66 changes: 40 additions & 26 deletions GitCommands/Git/Commands/GitCommandHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GitCommands.Git;
using GitExtUtils;
using GitUIPluginInterfaces;
using JetBrains.Annotations;
Expand Down Expand Up @@ -81,39 +80,54 @@ public static ArgumentString AddSubmoduleCmd(string remotePath, string localPath
};
}

public static ArgumentString GetRefsCmd(bool tags, bool branches, bool noLocks)
public static ArgumentString GetRefsCmd(bool tags, bool branches, bool noLocks, GitRefsSortBy sortBy, GitRefsSortOrder sortOrder)
{
GitArgumentBuilder cmd;

if (tags)
string format;
if (!tags)
{
cmd = new GitArgumentBuilder("show-ref", gitOptions:
noLocks && GitVersion.Current.SupportNoOptionalLocks
? (ArgumentString)"--no-optional-locks"
: default)
{
{ branches, "--dereference", "--tags" },
};
}
else if (branches)
{
// branches only
cmd = new GitArgumentBuilder("for-each-ref", gitOptions:
noLocks && GitVersion.Current.SupportNoOptionalLocks
? (ArgumentString)"--no-optional-locks"
: default)
{
"--sort=-committerdate",
@"refs/heads/",
@"--format=""%(objectname) %(refname)"""
};
// If we don't need tags, it is easy.
format = @"--format=""%(objectname) %(refname)""";
}
else
{
throw new ArgumentException("GetRefs: Neither branches nor tags requested");
// ...however, if we're interested in tags, tags may be simple (in which case they are point to commits directly),
// or "dereferences" (i.e. commits that contian metadata and point to other commits, "^{}").
// Derefence commits do not contain date information, so we need to find information from the referenced commits (those with '*').
// So the following format is as follows:
// If (there is a 'authordate' information, then this is a simple tag/direct commit)
// Then
// format = %(objectname) %(refname)
// Else
// format = %(*objectname) %(*refname) // i.e. info from a referenced commit
// End
format = @"--format=""%(if)%(authordate)%(then)%(objectname) %(refname)%(else)%(*objectname) %(*refname)%(end)""";
}

GitArgumentBuilder cmd = new GitArgumentBuilder("for-each-ref",
gitOptions: noLocks && GitVersion.Current.SupportNoOptionalLocks
? (ArgumentString)"--no-optional-locks"
: default)
{
{ sortBy != GitRefsSortBy.Default, GetSortCriteria(tags, sortBy, sortOrder), string.Empty },
format,
{ branches, "refs/heads/", string.Empty },
{ branches && tags, "refs/remotes/", string.Empty },
{ tags, "refs/tags/", string.Empty },
};

return cmd;

static string GetSortCriteria(bool needTags, GitRefsSortBy sortBy, GitRefsSortOrder sortOrder)
{
string order = sortOrder == GitRefsSortOrder.Ascending ? string.Empty : "-";
if (!needTags)
{
return $"--sort={order}{sortBy}";
}

// Sort by dereferenced data
return $"--sort={order}{sortBy} --sort={order}*{sortBy}";
}
}

public static ArgumentString RevertCmd(ObjectId commitId, bool autoCommit, int parentIndex)
Expand Down
46 changes: 2 additions & 44 deletions GitCommands/Git/GitModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2895,53 +2895,11 @@ public RemoteActionResult<IReadOnlyList<IGitRef>> GetRemoteServerRefs(string rem

public IReadOnlyList<IGitRef> GetRefs(bool tags = true, bool branches = true)
{
return GetRefs(tags, branches, false);
}

public IReadOnlyList<IGitRef> GetRefs(bool tags, bool branches, bool noLocks)
{
var refList = _gitExecutable.GetOutput(GitCommandHelpers.GetRefsCmd(tags: tags, branches: branches, noLocks: noLocks));

string cmd = GitCommandHelpers.GetRefsCmd(tags: tags, branches: branches, noLocks: true, AppSettings.RefsSortBy, AppSettings.RefsSortOrder);
var refList = _gitExecutable.GetOutput(cmd);
return ParseRefs(refList);
}

/// <param name="option">Order by date is slower.</param>
public IReadOnlyList<IGitRef> GetTagRefs(GetTagRefsSortOrder option)
{
var list = GetRefs(true, false);

switch (option)
{
case GetTagRefsSortOrder.ByCommitDateAscending:
return list.OrderBy(GetDate).ToList();
case GetTagRefsSortOrder.ByCommitDateDescending:
return list.OrderByDescending(GetDate).ToList();
default:
return list;
}

// BUG this sorting logic has no effect as CommitDate is not set by the GitRevision constructor
DateTime GetDate(IGitRef head) => new GitRevision(head.ObjectId).CommitDate;
}

public enum GetTagRefsSortOrder
{
/// <summary>
/// default
/// </summary>
ByName,

/// <summary>
/// slower than ByName
/// </summary>
ByCommitDateAscending,

/// <summary>
/// slower than ByName
/// </summary>
ByCommitDateDescending
}

public async Task<string[]> GetMergedBranchesAsync(bool includeRemote = false, bool fullRefname = false, string commit = null)
=> (await _gitExecutable
.GetOutputAsync(GitCommandHelpers.MergedBranchesCmd(includeRemote, fullRefname, commit))
Expand Down
4 changes: 2 additions & 2 deletions GitCommands/Git/GitVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class GitVersion : IComparable<GitVersion>
private static readonly GitVersion v2_19_0 = new GitVersion("2.19.0");
private static readonly GitVersion v2_20_0 = new GitVersion("2.20.0");

public static readonly GitVersion LastSupportedVersion = v2_11_0;
public static readonly GitVersion LastRecommendedVersion = new GitVersion("2.25.1");
public static readonly GitVersion LastSupportedVersion = v2_19_0;
public static readonly GitVersion LastRecommendedVersion = new GitVersion("2.28.0");

private static GitVersion _current;

Expand Down
12 changes: 12 additions & 0 deletions GitCommands/Settings/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,18 @@ public static bool UseConsoleEmulatorForCommands
set => SetBool("UseConsoleEmulatorForCommands", value);
}

public static GitRefsSortBy RefsSortBy
{
get => GetEnum("RefsSortBy", GitRefsSortBy.Default);
set => SetEnum("RefsSortBy", value);
}

public static GitRefsSortOrder RefsSortOrder
{
get => GetEnum("RefsSortOrder", GitRefsSortOrder.Descending);
set => SetEnum("RefsSortOrder", value);
}

public static DiffListSortType DiffListSorting
{
get => GetEnum("DiffListSortType", DiffListSortType.FilePath);
Expand Down
5 changes: 2 additions & 3 deletions GitUI/BranchTreePanel/RepoObjectsTree.Nodes.Branches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using GitUI.Properties;
using JetBrains.Annotations;
using Microsoft.VisualStudio.Threading;
using ResourceManager;

namespace GitUI.BranchTreePanel
{
Expand Down Expand Up @@ -259,7 +258,7 @@ private sealed class BranchTree : Tree
{
private readonly IAheadBehindDataProvider _aheadBehindDataProvider;

public BranchTree(TreeNode treeNode, IGitUICommandsSource uiCommands, [CanBeNull]IAheadBehindDataProvider aheadBehindDataProvider)
public BranchTree(TreeNode treeNode, IGitUICommandsSource uiCommands, [CanBeNull] IAheadBehindDataProvider aheadBehindDataProvider)
: base(treeNode, uiCommands)
{
_aheadBehindDataProvider = aheadBehindDataProvider;
Expand All @@ -280,7 +279,7 @@ private async Task<Nodes> LoadNodesAsync(CancellationToken token)
await TaskScheduler.Default;
token.ThrowIfCancellationRequested();

var branchNames = Module.GetRefs(tags: false, branches: true, noLocks: true).Select(b => b.Name);
var branchNames = Module.GetRefs(tags: false, branches: true).Select(b => b.Name);
return FillBranchTree(branchNames, token);
}

Expand Down
2 changes: 1 addition & 1 deletion GitUI/BranchTreePanel/RepoObjectsTree.Nodes.Remotes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private async Task<Nodes> LoadNodesAsync(CancellationToken token)
var nodes = new Nodes(this);
var pathToNodes = new Dictionary<string, BaseBranchNode>();

var branches = Module.GetRefs(tags: true, branches: true, noLocks: true)
var branches = Module.GetRefs(tags: true, branches: true)
.Where(branch => branch.IsRemote && !branch.IsTag)
.OrderBy(branch => branch.Name)
.Select(branch => branch.Name);
Expand Down
4 changes: 1 addition & 3 deletions GitUI/BranchTreePanel/RepoObjectsTree.Nodes.Tags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using GitCommands;
using GitUI.BranchTreePanel.Interfaces;
using GitUI.CommandsDialogs;
using GitUI.Properties;
using GitUIPluginInterfaces;
using Microsoft.VisualStudio.Threading;
using ResourceManager;

namespace GitUI.BranchTreePanel
{
Expand Down Expand Up @@ -91,7 +89,7 @@ private async Task<Nodes> LoadNodesAsync(CancellationToken token)
{
await TaskScheduler.Default;
token.ThrowIfCancellationRequested();
return FillTagTree(Module.GetTagRefs(GitModule.GetTagRefsSortOrder.ByName), token);
return FillTagTree(Module.GetRefs(tags: true, branches: false), token);
}

private Nodes FillTagTree(IEnumerable<IGitRef> tags, CancellationToken token)
Expand Down
4 changes: 2 additions & 2 deletions GitUI/CommandsDialogs/BrowseDialog/FormGoToCommit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private Task LoadTagsAsync()
{
comboBoxTags.Text = Strings.LoadingData;
return _tagsLoader.LoadAsync(
() => Module.GetTagRefs(GitModule.GetTagRefsSortOrder.ByCommitDateDescending).ToList(),
() => Module.GetRefs(tags: true, branches: false).ToList(),
list =>
{
comboBoxTags.Text = string.Empty;
Expand All @@ -92,7 +92,7 @@ private Task LoadBranchesAsync()
{
comboBoxBranches.Text = Strings.LoadingData;
return _branchesLoader.LoadAsync(
() => Module.GetRefs(false).ToList(),
() => Module.GetRefs(tags: false, branches: true).ToList(),
list =>
{
comboBoxBranches.Text = string.Empty;
Expand Down
2 changes: 1 addition & 1 deletion GitUI/CommandsDialogs/FormBrowse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2025,7 +2025,7 @@ IEnumerable<string> GetBranchNames()
// Make sure there are never more than a 100 branches added to the menu
// Git Extensions will hang when the drop down is too large...
return Module
.GetRefs(tags: false, branches: true, noLocks: true)
.GetRefs(tags: false, branches: true)
.Select(b => b.Name)
.Take(100);
}
Expand Down
4 changes: 2 additions & 2 deletions GitUI/CommandsDialogs/FormCheckoutBranch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ private IEnumerable<IGitRef> GetLocalBranches()
{
if (_localBranches == null)
{
_localBranches = Module.GetRefs(false);
_localBranches = Module.GetRefs(tags: false, branches: true);
}

return _localBranches;
Expand All @@ -534,7 +534,7 @@ private IEnumerable<IGitRef> GetRemoteBranches()
{
if (_remoteBranches == null)
{
_remoteBranches = Module.GetRefs(true, true).Where(h => h.IsRemote && !h.IsTag).ToList();
_remoteBranches = Module.GetRefs(tags: true, branches: true).Where(h => h.IsRemote && !h.IsTag).ToList();
}

return _remoteBranches;
Expand Down
2 changes: 1 addition & 1 deletion GitUI/CommandsDialogs/FormCommit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ private async Task UpdateBranchNameDisplayAsync()
remoteNameLabel.Click -= _branchNameLabelOnClick;
}

var currentBranch = Module.GetRefs(false, true).FirstOrDefault(r => r.LocalName == currentBranchName);
var currentBranch = Module.GetRefs(tags: false, branches: true).FirstOrDefault(r => r.LocalName == currentBranchName);
if (currentBranch == null)
{
branchNameLabel.Text = currentBranchName;
Expand Down
2 changes: 1 addition & 1 deletion GitUI/CommandsDialogs/FormDeleteBranch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public FormDeleteBranch(GitUICommands commands, IEnumerable<string> defaultBranc

private void FormDeleteBranchLoad(object sender, EventArgs e)
{
Branches.BranchesToSelect = Module.GetRefs(true, true).Where(h => h.IsHead && !h.IsRemote).ToList();
Branches.BranchesToSelect = Module.GetRefs(tags: true, branches: true).Where(h => h.IsHead && !h.IsRemote).ToList();
foreach (var branch in Module.GetMergedBranches())
{
if (!branch.StartsWith("* "))
Expand Down
2 changes: 1 addition & 1 deletion GitUI/CommandsDialogs/FormDeleteTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public FormDeleteTag(GitUICommands commands, string tag)
private void FormDeleteTagLoad(object sender, EventArgs e)
{
Tags.DisplayMember = nameof(IGitRef.LocalName);
Tags.DataSource = Module.GetRefs(true, false);
Tags.DataSource = Module.GetRefs(tags: true, branches: false);
Tags.Text = Tag as string;
remotesComboboxControl1.SelectedRemote = Module.GetCurrentRemote();
EnableOrDisableRemotesCombobox();
Expand Down
5 changes: 3 additions & 2 deletions GitUI/CommandsDialogs/FormPush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -907,8 +907,9 @@ private void RemotesValidated(object sender, EventArgs e)
private void FillTagDropDown()
{
// var tags = Module.GetTagHeads(GitModule.GetTagHeadsOption.OrderByCommitDateDescending); // comment out to sort by commit date
var tags = Module.GetTagRefs(GitModule.GetTagRefsSortOrder.ByName)
.Select(tag => tag.Name).ToList();
List<string> tags = Module.GetRefs(tags: true, branches: false)
.Select(tag => tag.Name)
.ToList();
tags.Insert(0, AllRefs);
TagComboBox.DataSource = tags;

Expand Down
Loading

0 comments on commit 93831ba

Please sign in to comment.