mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-22 20:37:19 -08:00
feature: add context menu for both branch and commit to compare selected with current HEAD
This commit is contained in:
parent
de6375da7c
commit
4249653ed6
8 changed files with 254 additions and 10 deletions
|
@ -17,6 +17,7 @@ namespace SourceGit.Commands
|
|||
public QueryCommits(string repo, string limits, bool needFindHead = true)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "log --date-order --decorate=full --pretty=raw " + limits;
|
||||
findFirstMerged = needFindHead;
|
||||
}
|
||||
|
|
167
src/Commands/QuerySingleCommit.cs
Normal file
167
src/Commands/QuerySingleCommit.cs
Normal file
|
@ -0,0 +1,167 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QuerySingleCommit : Command
|
||||
{
|
||||
private const string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
|
||||
private const string GPGSIG_END = " -----END PGP SIGNATURE-----";
|
||||
|
||||
public QuerySingleCommit(string repo, string sha) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --pretty=raw --decorate=full -s {sha}";
|
||||
}
|
||||
|
||||
public Models.Commit Result()
|
||||
{
|
||||
var succ = Exec();
|
||||
if (!succ)
|
||||
return null;
|
||||
|
||||
_commit.Message.Trim();
|
||||
return _commit;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
if (isSkipingGpgsig)
|
||||
{
|
||||
if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal))
|
||||
isSkipingGpgsig = false;
|
||||
return;
|
||||
}
|
||||
else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal))
|
||||
{
|
||||
isSkipingGpgsig = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("commit ", StringComparison.Ordinal))
|
||||
{
|
||||
line = line.Substring(7);
|
||||
|
||||
var decoratorStart = line.IndexOf('(', StringComparison.Ordinal);
|
||||
if (decoratorStart < 0)
|
||||
{
|
||||
_commit.SHA = line.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
_commit.SHA = line.Substring(0, decoratorStart).Trim();
|
||||
ParseDecorators(_commit.Decorators, line.Substring(decoratorStart + 1));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("tree ", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (line.StartsWith("parent ", StringComparison.Ordinal))
|
||||
{
|
||||
_commit.Parents.Add(line.Substring("parent ".Length));
|
||||
}
|
||||
else if (line.StartsWith("author ", StringComparison.Ordinal))
|
||||
{
|
||||
Models.User user = Models.User.Invalid;
|
||||
ulong time = 0;
|
||||
Models.Commit.ParseUserAndTime(line.Substring(7), ref user, ref time);
|
||||
_commit.Author = user;
|
||||
_commit.AuthorTime = time;
|
||||
}
|
||||
else if (line.StartsWith("committer ", StringComparison.Ordinal))
|
||||
{
|
||||
Models.User user = Models.User.Invalid;
|
||||
ulong time = 0;
|
||||
Models.Commit.ParseUserAndTime(line.Substring(10), ref user, ref time);
|
||||
_commit.Committer = user;
|
||||
_commit.CommitterTime = time;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(_commit.Subject))
|
||||
{
|
||||
_commit.Subject = line.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
_commit.Message += (line.Trim() + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
private bool ParseDecorators(List<Models.Decorator> decorators, string data)
|
||||
{
|
||||
bool isHeadOfCurrent = false;
|
||||
|
||||
var subs = data.Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var sub in subs)
|
||||
{
|
||||
var d = sub.Trim();
|
||||
if (d.StartsWith("tag: refs/tags/", StringComparison.Ordinal))
|
||||
{
|
||||
decorators.Add(new Models.Decorator()
|
||||
{
|
||||
Type = Models.DecoratorType.Tag,
|
||||
Name = d.Substring(15).Trim(),
|
||||
});
|
||||
}
|
||||
else if (d.EndsWith("/HEAD", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (d.StartsWith("HEAD -> refs/heads/", StringComparison.Ordinal))
|
||||
{
|
||||
isHeadOfCurrent = true;
|
||||
decorators.Add(new Models.Decorator()
|
||||
{
|
||||
Type = Models.DecoratorType.CurrentBranchHead,
|
||||
Name = d.Substring(19).Trim(),
|
||||
});
|
||||
}
|
||||
else if (d.Equals("HEAD"))
|
||||
{
|
||||
isHeadOfCurrent = true;
|
||||
decorators.Add(new Models.Decorator()
|
||||
{
|
||||
Type = Models.DecoratorType.CurrentCommitHead,
|
||||
Name = d.Trim(),
|
||||
});
|
||||
}
|
||||
else if (d.StartsWith("refs/heads/", StringComparison.Ordinal))
|
||||
{
|
||||
decorators.Add(new Models.Decorator()
|
||||
{
|
||||
Type = Models.DecoratorType.LocalBranchHead,
|
||||
Name = d.Substring(11).Trim(),
|
||||
});
|
||||
}
|
||||
else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal))
|
||||
{
|
||||
decorators.Add(new Models.Decorator()
|
||||
{
|
||||
Type = Models.DecoratorType.RemoteBranchHead,
|
||||
Name = d.Substring(13).Trim(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
decorators.Sort((l, r) =>
|
||||
{
|
||||
if (l.Type != r.Type)
|
||||
{
|
||||
return (int)l.Type - (int)r.Type;
|
||||
}
|
||||
else
|
||||
{
|
||||
return l.Name.CompareTo(r.Name);
|
||||
}
|
||||
});
|
||||
|
||||
return isHeadOfCurrent;
|
||||
}
|
||||
|
||||
private Models.Commit _commit = new Models.Commit();
|
||||
private bool isSkipingGpgsig = false;
|
||||
}
|
||||
}
|
|
@ -97,4 +97,5 @@
|
|||
<StreamGeometry x:Key="Icons.GitFlow.Release">M884 159l-18-18a43 43 0 00-38-12l-235 43a166 166 0 00-101 60L400 349a128 128 0 00-148 47l-120 171a21 21 0 005 29l17 12a128 128 0 00178-32l27-38 124 124-38 27a128 128 0 00-32 178l12 17a21 21 0 0029 5l171-120a128 128 0 0047-148l117-92A166 166 0 00853 431l43-235a43 43 0 00-12-38zm-177 249a64 64 0 110-90 64 64 0 010 90zm-373 312a21 21 0 010 30l-139 139a21 21 0 01-30 0l-30-30a21 21 0 010-30l139-139a21 21 0 0130 0z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Lines.Incr">M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l132 0 0-128 64 0 0 128 132 0 0 64-132 0 0 128-64 0 0-128-132 0Z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Lines.Decr">M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l328 0 0 64-328 0Z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Compare">M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z</StreamGeometry>
|
||||
</ResourceDictionary>
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
|
||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">BLAME ON THIS FILE IS NOT SUPPORTED!!!</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Compare with HEAD</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">Copy Branch Name</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Delete" xml:space="preserve">Delete${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.DeleteMultiBranches" xml:space="preserve">Delete selected {0} branches</x:String>
|
||||
|
@ -76,8 +77,9 @@
|
|||
<x:String x:Key="Text.Clone.RemoteURL" xml:space="preserve">Repository URL :</x:String>
|
||||
<x:String x:Key="Text.Close" xml:space="preserve">CLOSE</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CherryPick" xml:space="preserve">Cherry-Pick This Commit</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">Copy SHA</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Checkout" xml:space="preserve">Checkout Commit</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">Compare with HEAD</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">Copy SHA</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">Rebase${0}$to Here</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Reset${0}$to Here</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">Revert Commit</x:String>
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
<x:String x:Key="Text.Blame" xml:space="preserve">逐行追溯(blame)</x:String>
|
||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">选中文件不支持该操作!!!</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">检出(checkout)${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">与当前HEAD比较</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">复制分支名</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Delete" xml:space="preserve">删除${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.DeleteMultiBranches" xml:space="preserve">删除选中的 {0} 个分支</x:String>
|
||||
|
@ -77,6 +78,7 @@
|
|||
<x:String x:Key="Text.Close" xml:space="preserve">关闭</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CherryPick" xml:space="preserve">挑选(cherry-pick)此提交</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Checkout" xml:space="preserve">检出此提交</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">与当前HEAD比较</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">复制提交指纹</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">变基(rebase)${0}$到此处</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">重置(reset)${0}$到此处</x:String>
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace SourceGit.ViewModels
|
|||
public Models.Commit AutoSelectedCommit
|
||||
{
|
||||
get => _autoSelectedCommit;
|
||||
private set => SetProperty(ref _autoSelectedCommit, value);
|
||||
set => SetProperty(ref _autoSelectedCommit, value);
|
||||
}
|
||||
|
||||
public long NavigationId
|
||||
|
@ -81,7 +81,7 @@ namespace SourceGit.ViewModels
|
|||
public object DetailContext
|
||||
{
|
||||
get => _detailContext;
|
||||
private set => SetProperty(ref _detailContext, value);
|
||||
set => SetProperty(ref _detailContext, value);
|
||||
}
|
||||
|
||||
public Histories(Repository repo)
|
||||
|
@ -171,17 +171,16 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public ContextMenu MakeContextMenu()
|
||||
public ContextMenu MakeContextMenu(DataGrid datagrid)
|
||||
{
|
||||
var detail = _detailContext as CommitDetail;
|
||||
if (detail == null)
|
||||
if (datagrid.SelectedItems.Count != 1)
|
||||
return null;
|
||||
|
||||
var current = _repo.Branches.Find(x => x.IsCurrent);
|
||||
if (current == null)
|
||||
return null;
|
||||
|
||||
var commit = detail.Commit;
|
||||
var commit = datagrid.SelectedItem as Models.Commit;
|
||||
var menu = new ContextMenu();
|
||||
var tags = new List<Models.Tag>();
|
||||
|
||||
|
@ -317,6 +316,33 @@ namespace SourceGit.ViewModels
|
|||
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
if (current.Head != commit.SHA)
|
||||
{
|
||||
var compare = new MenuItem();
|
||||
compare.Header = App.Text("CommitCM.CompareWithHead");
|
||||
compare.Icon = App.CreateMenuIcon("Icons.Compare");
|
||||
compare.Click += (o, e) =>
|
||||
{
|
||||
var head = _commits.Find(x => x.SHA == current.Head);
|
||||
if (head == null)
|
||||
{
|
||||
_repo.SearchResultSelectedCommit = null;
|
||||
head = new Commands.QuerySingleCommit(_repo.FullPath, current.Head).Result();
|
||||
if (head != null)
|
||||
DetailContext = new RevisionCompare(_repo.FullPath, commit, head);
|
||||
}
|
||||
else
|
||||
{
|
||||
datagrid.SelectedItems.Add(head);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(compare);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
|
||||
var createBranch = new MenuItem();
|
||||
createBranch.Icon = App.CreateMenuIcon("Icons.Branch.Add");
|
||||
createBranch.Header = App.Text("CreateBranch");
|
||||
|
|
|
@ -928,6 +928,27 @@ namespace SourceGit.ViewModels
|
|||
|
||||
menu.Items.Add(merge);
|
||||
menu.Items.Add(rebase);
|
||||
|
||||
var compare = new MenuItem();
|
||||
compare.Header = App.Text("BranchCM.CompareWithHead");
|
||||
compare.Icon = App.CreateMenuIcon("Icons.Compare");
|
||||
compare.Click += (o, e) =>
|
||||
{
|
||||
SearchResultSelectedCommit = null;
|
||||
|
||||
if (_histories != null)
|
||||
{
|
||||
var target = new Commands.QuerySingleCommit(FullPath, branch.Head).Result();
|
||||
var head = new Commands.QuerySingleCommit(FullPath, current.Head).Result();
|
||||
_histories.AutoSelectedCommit = null;
|
||||
_histories.DetailContext = new RevisionCompare(FullPath, target, head);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
menu.Items.Add(compare);
|
||||
}
|
||||
|
||||
var type = GitFlow.GetBranchType(branch.Name);
|
||||
|
@ -1197,6 +1218,30 @@ namespace SourceGit.ViewModels
|
|||
menu.Items.Add(merge);
|
||||
menu.Items.Add(rebase);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
if (current.Head != branch.Head)
|
||||
{
|
||||
var compare = new MenuItem();
|
||||
compare.Header = App.Text("BranchCM.CompareWithHead");
|
||||
compare.Icon = App.CreateMenuIcon("Icons.Compare");
|
||||
compare.Click += (o, e) =>
|
||||
{
|
||||
SearchResultSelectedCommit = null;
|
||||
|
||||
if (_histories != null)
|
||||
{
|
||||
var target = new Commands.QuerySingleCommit(FullPath, branch.Head).Result();
|
||||
var head = new Commands.QuerySingleCommit(FullPath, current.Head).Result();
|
||||
_histories.AutoSelectedCommit = null;
|
||||
_histories.DetailContext = new RevisionCompare(FullPath, target, head);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(compare);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
}
|
||||
|
||||
var delete = new MenuItem();
|
||||
|
|
|
@ -297,10 +297,10 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnCommitDataGridContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Histories histories)
|
||||
if (DataContext is ViewModels.Histories histories && sender is DataGrid datagrid)
|
||||
{
|
||||
var menu = histories.MakeContextMenu();
|
||||
(sender as Control)?.OpenContextMenu(menu);
|
||||
var menu = histories.MakeContextMenu(datagrid);
|
||||
datagrid.OpenContextMenu(menu);
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue