mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -08:00
feature: supports branch compare (#174)
This commit is contained in:
parent
8bcce5f723
commit
7f2e22def6
8 changed files with 608 additions and 64 deletions
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
|
<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.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.Checkout" xml:space="preserve">Checkout${0}$</x:String>
|
||||||
|
<x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">Compare with Branch</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Compare with HEAD</x:String>
|
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Compare with HEAD</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">Compare with Worktree</x:String>
|
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">Compare with Worktree</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">Copy Branch Name</x:String>
|
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">Copy Branch Name</x:String>
|
||||||
|
@ -49,6 +50,7 @@
|
||||||
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">Rename${0}$</x:String>
|
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">Rename${0}$</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">Tracking ...</x:String>
|
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">Tracking ...</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">Unset Upstream</x:String>
|
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">Unset Upstream</x:String>
|
||||||
|
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Branch Compare</x:String>
|
||||||
<x:String x:Key="Text.Bytes" xml:space="preserve">Bytes</x:String>
|
<x:String x:Key="Text.Bytes" xml:space="preserve">Bytes</x:String>
|
||||||
<x:String x:Key="Text.Cancel" xml:space="preserve">CANCEL</x:String>
|
<x:String x:Key="Text.Cancel" xml:space="preserve">CANCEL</x:String>
|
||||||
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">CHANGE DISPLAY MODE</x:String>
|
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">CHANGE DISPLAY MODE</x:String>
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
<x:String x:Key="Text.Blame" xml:space="preserve">逐行追溯(blame)</x:String>
|
<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.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.Checkout" xml:space="preserve">检出(checkout)${0}$</x:String>
|
||||||
|
<x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">与其他分支对比</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">与当前HEAD比较</x:String>
|
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">与当前HEAD比较</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">与本地工作树比较</x:String>
|
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">与本地工作树比较</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">复制分支名</x:String>
|
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">复制分支名</x:String>
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">重命名${0}$</x:String>
|
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">重命名${0}$</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">切换上游分支...</x:String>
|
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">切换上游分支...</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">取消追踪</x:String>
|
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">取消追踪</x:String>
|
||||||
|
<x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比较</x:String>
|
||||||
<x:String x:Key="Text.Bytes" xml:space="preserve">字节</x:String>
|
<x:String x:Key="Text.Bytes" xml:space="preserve">字节</x:String>
|
||||||
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
|
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
|
||||||
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切换变更显示模式</x:String>
|
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切换变更显示模式</x:String>
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
<x:String x:Key="Text.Blame" xml:space="preserve">逐行追溯(blame)</x:String>
|
<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.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.Checkout" xml:space="preserve">檢出(checkout)${0}$</x:String>
|
||||||
|
<x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">與其他分支比較</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">與當前HEAD比較</x:String>
|
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">與當前HEAD比較</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">與本地工作樹比較</x:String>
|
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">與本地工作樹比較</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">複製分支名</x:String>
|
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">複製分支名</x:String>
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">重新命名${0}$</x:String>
|
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">重新命名${0}$</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">切換上游分支...</x:String>
|
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">切換上游分支...</x:String>
|
||||||
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">取消追蹤</x:String>
|
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">取消追蹤</x:String>
|
||||||
|
<x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比較</x:String>
|
||||||
<x:String x:Key="Text.Bytes" xml:space="preserve">位元組</x:String>
|
<x:String x:Key="Text.Bytes" xml:space="preserve">位元組</x:String>
|
||||||
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
|
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
|
||||||
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切換變更顯示模式</x:String>
|
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切換變更顯示模式</x:String>
|
||||||
|
|
222
src/ViewModels/BranchCompare.cs
Normal file
222
src/ViewModels/BranchCompare.cs
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace SourceGit.ViewModels
|
||||||
|
{
|
||||||
|
public class BranchCompare : ObservableObject
|
||||||
|
{
|
||||||
|
public Models.Branch Base
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.Branch To
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.Commit BaseHead
|
||||||
|
{
|
||||||
|
get => _baseHead;
|
||||||
|
private set => SetProperty(ref _baseHead, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.Commit ToHead
|
||||||
|
{
|
||||||
|
get => _toHead;
|
||||||
|
private set => SetProperty(ref _toHead, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Models.Change> VisibleChanges
|
||||||
|
{
|
||||||
|
get => _visibleChanges;
|
||||||
|
private set => SetProperty(ref _visibleChanges, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Models.Change> SelectedChanges
|
||||||
|
{
|
||||||
|
get => _selectedChanges;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedChanges, value))
|
||||||
|
{
|
||||||
|
if (value != null && value.Count == 1)
|
||||||
|
DiffContext = new DiffContext(_repo, new Models.DiffOption(Base.Head, To.Head, value[0]), _diffContext);
|
||||||
|
else
|
||||||
|
DiffContext = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SearchFilter
|
||||||
|
{
|
||||||
|
get => _searchFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _searchFilter, value))
|
||||||
|
{
|
||||||
|
RefreshVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiffContext DiffContext
|
||||||
|
{
|
||||||
|
get => _diffContext;
|
||||||
|
private set => SetProperty(ref _diffContext, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BranchCompare(string repo, Models.Branch baseBranch, Models.Branch toBranch)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
|
||||||
|
Base = baseBranch;
|
||||||
|
To = toBranch;
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var baseHead = new Commands.QuerySingleCommit(_repo, Base.Head).Result();
|
||||||
|
var toHead = new Commands.QuerySingleCommit(_repo, To.Head).Result();
|
||||||
|
_changes = new Commands.CompareRevisions(_repo, Base.Head, To.Head).Result();
|
||||||
|
|
||||||
|
var visible = _changes;
|
||||||
|
if (!string.IsNullOrWhiteSpace(_searchFilter))
|
||||||
|
{
|
||||||
|
visible = new List<Models.Change>();
|
||||||
|
foreach (var c in _changes)
|
||||||
|
{
|
||||||
|
if (c.Path.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
visible.Add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
|
{
|
||||||
|
BaseHead = baseHead;
|
||||||
|
ToHead = toHead;
|
||||||
|
VisibleChanges = visible;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NavigateTo(string commitSHA)
|
||||||
|
{
|
||||||
|
var repo = Preference.FindRepository(_repo);
|
||||||
|
if (repo != null)
|
||||||
|
repo.NavigateToCommit(commitSHA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearSearchFilter()
|
||||||
|
{
|
||||||
|
SearchFilter = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextMenu CreateChangeContextMenu()
|
||||||
|
{
|
||||||
|
if (_selectedChanges == null || _selectedChanges.Count != 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var change = _selectedChanges[0];
|
||||||
|
var menu = new ContextMenu();
|
||||||
|
|
||||||
|
var diffWithMerger = new MenuItem();
|
||||||
|
diffWithMerger.Header = App.Text("DiffWithMerger");
|
||||||
|
diffWithMerger.Icon = App.CreateMenuIcon("Icons.Diff");
|
||||||
|
diffWithMerger.Click += (_, ev) =>
|
||||||
|
{
|
||||||
|
var opt = new Models.DiffOption(Base.Head, To.Head, change);
|
||||||
|
var type = Preference.Instance.ExternalMergeToolType;
|
||||||
|
var exec = Preference.Instance.ExternalMergeToolPath;
|
||||||
|
|
||||||
|
var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
|
||||||
|
if (tool == null || !File.Exists(exec))
|
||||||
|
{
|
||||||
|
App.RaiseException(_repo, "Invalid merge tool in preference setting!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = tool.Type != 0 ? tool.DiffCmd : Preference.Instance.ExternalMergeToolDiffCmd;
|
||||||
|
Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, exec, args, opt));
|
||||||
|
ev.Handled = true;
|
||||||
|
};
|
||||||
|
menu.Items.Add(diffWithMerger);
|
||||||
|
|
||||||
|
if (change.Index != Models.ChangeState.Deleted)
|
||||||
|
{
|
||||||
|
var full = Path.GetFullPath(Path.Combine(_repo, change.Path));
|
||||||
|
var explore = new MenuItem();
|
||||||
|
explore.Header = App.Text("RevealFile");
|
||||||
|
explore.Icon = App.CreateMenuIcon("Icons.Folder.Open");
|
||||||
|
explore.IsEnabled = File.Exists(full);
|
||||||
|
explore.Click += (_, ev) =>
|
||||||
|
{
|
||||||
|
Native.OS.OpenInFileManager(full, true);
|
||||||
|
ev.Handled = true;
|
||||||
|
};
|
||||||
|
menu.Items.Add(explore);
|
||||||
|
}
|
||||||
|
|
||||||
|
var copyPath = new MenuItem();
|
||||||
|
copyPath.Header = App.Text("CopyPath");
|
||||||
|
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||||
|
copyPath.Click += (_, ev) =>
|
||||||
|
{
|
||||||
|
App.CopyText(change.Path);
|
||||||
|
ev.Handled = true;
|
||||||
|
};
|
||||||
|
menu.Items.Add(copyPath);
|
||||||
|
|
||||||
|
var copyFileName = new MenuItem();
|
||||||
|
copyFileName.Header = App.Text("CopyFileName");
|
||||||
|
copyFileName.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||||
|
copyFileName.Click += (_, e) =>
|
||||||
|
{
|
||||||
|
App.CopyText(Path.GetFileName(change.Path));
|
||||||
|
e.Handled = true;
|
||||||
|
};
|
||||||
|
menu.Items.Add(copyFileName);
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshVisible()
|
||||||
|
{
|
||||||
|
if (_changes == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_searchFilter))
|
||||||
|
{
|
||||||
|
VisibleChanges = _changes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var visible = new List<Models.Change>();
|
||||||
|
foreach (var c in _changes)
|
||||||
|
{
|
||||||
|
if (c.Path.Contains(_searchFilter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
visible.Add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
VisibleChanges = visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _repo = string.Empty;
|
||||||
|
private Models.Commit _baseHead = null;
|
||||||
|
private Models.Commit _toHead = null;
|
||||||
|
private List<Models.Change> _changes = null;
|
||||||
|
private List<Models.Change> _visibleChanges = null;
|
||||||
|
private List<Models.Change> _selectedChanges = null;
|
||||||
|
private string _searchFilter = string.Empty;
|
||||||
|
private DiffContext _diffContext = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -909,6 +909,13 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.Items.Add(push);
|
menu.Items.Add(push);
|
||||||
|
|
||||||
|
var compareWithBranch = CreateMenuItemToCompareBranches(branch);
|
||||||
|
if (compareWithBranch != null)
|
||||||
|
{
|
||||||
|
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||||
|
menu.Items.Add(compareWithBranch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -968,24 +975,6 @@ namespace SourceGit.ViewModels
|
||||||
menu.Items.Add(merge);
|
menu.Items.Add(merge);
|
||||||
menu.Items.Add(rebase);
|
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (WorkingCopyChangesCount > 0)
|
if (WorkingCopyChangesCount > 0)
|
||||||
{
|
{
|
||||||
var compareWithWorktree = new MenuItem();
|
var compareWithWorktree = new MenuItem();
|
||||||
|
@ -1002,11 +991,18 @@ namespace SourceGit.ViewModels
|
||||||
_histories.DetailContext = new RevisionCompare(FullPath, target, null);
|
_histories.DetailContext = new RevisionCompare(FullPath, target, null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||||
menu.Items.Add(compareWithWorktree);
|
menu.Items.Add(compareWithWorktree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var compareWithBranch = CreateMenuItemToCompareBranches(branch);
|
||||||
|
if (compareWithBranch != null)
|
||||||
|
{
|
||||||
|
if (WorkingCopyChangesCount == 0)
|
||||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||||
menu.Items.Add(compare);
|
|
||||||
|
menu.Items.Add(compareWithBranch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var type = GitFlow.GetBranchType(branch.Name);
|
var type = GitFlow.GetBranchType(branch.Name);
|
||||||
|
@ -1263,28 +1259,9 @@ namespace SourceGit.ViewModels
|
||||||
menu.Items.Add(merge);
|
menu.Items.Add(merge);
|
||||||
menu.Items.Add(rebase);
|
menu.Items.Add(rebase);
|
||||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
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;
|
var hasCompare = false;
|
||||||
};
|
|
||||||
menu.Items.Add(compare);
|
|
||||||
|
|
||||||
if (WorkingCopyChangesCount > 0)
|
if (WorkingCopyChangesCount > 0)
|
||||||
{
|
{
|
||||||
var compareWithWorktree = new MenuItem();
|
var compareWithWorktree = new MenuItem();
|
||||||
|
@ -1302,11 +1279,18 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
menu.Items.Add(compareWithWorktree);
|
menu.Items.Add(compareWithWorktree);
|
||||||
|
hasCompare = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var compareWithBranch = CreateMenuItemToCompareBranches(branch);
|
||||||
|
if (compareWithBranch != null)
|
||||||
|
{
|
||||||
|
menu.Items.Add(compareWithBranch);
|
||||||
|
hasCompare = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCompare)
|
||||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var delete = new MenuItem();
|
var delete = new MenuItem();
|
||||||
delete.Header = new Views.NameHighlightedTextBlock("BranchCM.Delete", $"{branch.Remote}/{branch.Name}");
|
delete.Header = new Views.NameHighlightedTextBlock("BranchCM.Delete", $"{branch.Remote}/{branch.Name}");
|
||||||
|
@ -1485,6 +1469,41 @@ namespace SourceGit.ViewModels
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MenuItem CreateMenuItemToCompareBranches(Models.Branch branch)
|
||||||
|
{
|
||||||
|
if (Branches.Count == 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var compare = new MenuItem();
|
||||||
|
compare.Header = App.Text("BranchCM.CompareWithBranch");
|
||||||
|
compare.Icon = App.CreateMenuIcon("Icons.Compare");
|
||||||
|
|
||||||
|
foreach (var b in Branches)
|
||||||
|
{
|
||||||
|
if (b.FullName != branch.FullName)
|
||||||
|
{
|
||||||
|
var dup = b;
|
||||||
|
var target = new MenuItem();
|
||||||
|
target.Header = b.IsLocal ? b.Name : $"{b.Remote}/{b.Name}";
|
||||||
|
target.Icon = App.CreateMenuIcon(b.IsCurrent ? "Icons.Check" : "Icons.Branch");
|
||||||
|
target.Click += (_, e) =>
|
||||||
|
{
|
||||||
|
var wnd = new Views.BranchCompare()
|
||||||
|
{
|
||||||
|
DataContext = new BranchCompare(FullPath, branch, dup)
|
||||||
|
};
|
||||||
|
|
||||||
|
wnd.Show(App.GetTopLevel() as Window);
|
||||||
|
e.Handled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
compare.Items.Add(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compare;
|
||||||
|
}
|
||||||
|
|
||||||
private BranchTreeNode.Builder BuildBranchTree(List<Models.Branch> branches, List<Models.Remote> remotes)
|
private BranchTreeNode.Builder BuildBranchTree(List<Models.Branch> branches, List<Models.Remote> remotes)
|
||||||
{
|
{
|
||||||
var builder = new BranchTreeNode.Builder();
|
var builder = new BranchTreeNode.Builder();
|
||||||
|
|
240
src/Views/BranchCompare.axaml
Normal file
240
src/Views/BranchCompare.axaml
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
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="800" d:DesignHeight="450"
|
||||||
|
x:Class="SourceGit.Views.BranchCompare"
|
||||||
|
x:DataType="vm:BranchCompare"
|
||||||
|
x:Name="me"
|
||||||
|
Icon="/App.ico"
|
||||||
|
Title="{DynamicResource Text.BranchCompare}"
|
||||||
|
Background="Transparent"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
MinWidth="1280" MinHeight="720"
|
||||||
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaChromeHints="NoChrome"
|
||||||
|
SystemDecorations="{OnPlatform Full, Linux=None}">
|
||||||
|
<Grid Margin="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.ToContentMargin}}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="30"/>
|
||||||
|
<RowDefinition Height="64"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Custom window shadow for Linux -->
|
||||||
|
<Border Grid.Row="0" Grid.RowSpan="3"
|
||||||
|
Background="{DynamicResource Brush.Window}"
|
||||||
|
Effect="drop-shadow(0 0 6 #A0000000)"
|
||||||
|
IsVisible="{OnPlatform False, Linux=True}"/>
|
||||||
|
|
||||||
|
<!-- TitleBar -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,*,Auto">
|
||||||
|
<!-- Bottom border -->
|
||||||
|
<Border Grid.Column="0" Grid.ColumnSpan="4"
|
||||||
|
Background="{DynamicResource Brush.TitleBar}"
|
||||||
|
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}"
|
||||||
|
DoubleTapped="MaximizeOrRestoreWindow"
|
||||||
|
PointerPressed="BeginMoveWindow"/>
|
||||||
|
|
||||||
|
<!-- Caption Buttons (macOS) -->
|
||||||
|
<Border Grid.Column="0" IsVisible="{OnPlatform False, macOS=True}">
|
||||||
|
<v:CaptionButtonsMacOS/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Icon -->
|
||||||
|
<Path Grid.Column="1" Margin="8,0,0,0" Width="12" Height="12" Data="{StaticResource Icons.Compare}"/>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<TextBlock Grid.Column="2" Margin="8,0,0,0" Text="{DynamicResource Text.BranchCompare}" FontWeight="Bold" IsHitTestVisible="False" VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- Caption Buttons (Windows/Linux) -->
|
||||||
|
<Border Grid.Column="3" IsVisible="{OnPlatform True, macOS=False}">
|
||||||
|
<v:CaptionButtons/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Compare Targets -->
|
||||||
|
<Border Grid.Row="1" Background="{DynamicResource Brush.Window}">
|
||||||
|
<Grid Margin="48,8,48,8" ColumnDefinitions="*,48,*">
|
||||||
|
<Border Grid.Column="0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
|
||||||
|
<v:Avatar Width="16" Height="16"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
User="{Binding BaseHead.Author}"/>
|
||||||
|
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding BaseHead.Author.Name}" Margin="8,0,0,0"/>
|
||||||
|
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
|
||||||
|
<TextBlock Text="{Binding Base, Converter={x:Static c:BranchConverters.ToName}}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Grid.Column="3" Classes="monospace" Text="{Binding BaseHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA"/>
|
||||||
|
<TextBlock Grid.Column="4" Classes="monospace" Text="{Binding BaseHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Classes="monospace" Text="{Binding BaseHead.Subject}" VerticalAlignment="Bottom"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Path Grid.Column="1" Width="16" Height="16" Fill="{DynamicResource Brush.FG2}" Data="{DynamicResource Icons.Down}" RenderTransformOrigin="50%,50%" RenderTransform="rotate(270deg)"/>
|
||||||
|
|
||||||
|
<Border Grid.Column="2" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
|
||||||
|
<v:Avatar Width="16" Height="16"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
User="{Binding ToHead.Author}"/>
|
||||||
|
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding ToHead.Author.Name}" Margin="8,0,0,0"/>
|
||||||
|
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
|
||||||
|
<TextBlock Text="{Binding To, Converter={x:Static c:BranchConverters.ToName}}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Grid.Column="3" Classes="monospace" Text="{Binding ToHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA"/>
|
||||||
|
<TextBlock Grid.Column="4" Classes="monospace" Text="{Binding ToHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Classes="monospace" Text="{Binding ToHead.Subject}" VerticalAlignment="Bottom"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Changes -->
|
||||||
|
<Border Grid.Row="2" Background="{DynamicResource Brush.Window}">
|
||||||
|
<Grid Margin="8,0,8,8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="256" MinWidth="200" MaxWidth="480"/>
|
||||||
|
<ColumnDefinition Width="4"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Column="0" RowDefinitions="26,*">
|
||||||
|
<!-- Search & Display Mode -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="*,18">
|
||||||
|
<TextBox Grid.Column="0"
|
||||||
|
Height="26"
|
||||||
|
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
|
||||||
|
Background="Transparent"
|
||||||
|
CornerRadius="4"
|
||||||
|
Watermark="{DynamicResource Text.CommitDetail.Changes.Search}"
|
||||||
|
Text="{Binding SearchFilter, Mode=TwoWay}">
|
||||||
|
<TextBox.InnerLeftContent>
|
||||||
|
<Path Width="14" Height="14" Margin="4,0,0,0" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Search}"/>
|
||||||
|
</TextBox.InnerLeftContent>
|
||||||
|
|
||||||
|
<TextBox.InnerRightContent>
|
||||||
|
<Button Classes="icon_button"
|
||||||
|
IsVisible="{Binding SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||||
|
Command="{Binding ClearSearchFilter}">
|
||||||
|
<Path Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Clear}"/>
|
||||||
|
</Button>
|
||||||
|
</TextBox.InnerRightContent>
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<v:ChangeViewModeSwitcher Grid.Column="1"
|
||||||
|
Width="14" Height="14"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Changes -->
|
||||||
|
<Border Grid.Row="1" Margin="0,4,0,0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}">
|
||||||
|
<v:ChangeCollectionView IsWorkingCopyChange="False"
|
||||||
|
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=CommitChangeViewMode}"
|
||||||
|
Changes="{Binding VisibleChanges}"
|
||||||
|
SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}"
|
||||||
|
ContextRequested="OnChangeContextRequested"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<GridSplitter Grid.Column="1"
|
||||||
|
MinWidth="1"
|
||||||
|
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||||
|
Background="Transparent"/>
|
||||||
|
|
||||||
|
<Grid Grid.Column="2">
|
||||||
|
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||||
|
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||||
|
<Path Width="64" Height="64" Data="{StaticResource Icons.Diff}" Fill="{DynamicResource Brush.FG2}"/>
|
||||||
|
<TextBlock Margin="0,16,0,0"
|
||||||
|
Text="{DynamicResource Text.Diff.Welcome}"
|
||||||
|
FontSize="18" FontWeight="Bold"
|
||||||
|
Foreground="{DynamicResource Brush.FG2}"
|
||||||
|
HorizontalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ContentControl Content="{Binding DiffContext}">
|
||||||
|
<ContentControl.DataTemplates>
|
||||||
|
<DataTemplate DataType="vm:DiffContext">
|
||||||
|
<v:DiffView/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ContentControl.DataTemplates>
|
||||||
|
</ContentControl>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Custom window sizer for Linux -->
|
||||||
|
<Grid Grid.Row="0" Grid.RowSpan="3" IsVisible="{OnPlatform False, Linux=True}" IsHitTestVisible="{Binding #me.WindowState, Converter={x:Static c:WindowStateConverters.IsNormal}}">
|
||||||
|
<Border Width="4" Height="4"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||||
|
Cursor="TopLeftCorner"
|
||||||
|
Tag="{x:Static WindowEdge.NorthWest}"
|
||||||
|
PointerPressed="CustomResizeWindow"/>
|
||||||
|
|
||||||
|
<Border Height="4" Margin="4,0"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Stretch" VerticalAlignment="Top"
|
||||||
|
Cursor="TopSide"
|
||||||
|
Tag="{x:Static WindowEdge.North}"
|
||||||
|
PointerPressed="CustomResizeWindow"/>
|
||||||
|
|
||||||
|
<Border Width="4" Height="4"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||||
|
Cursor="TopRightCorner"
|
||||||
|
Tag="{x:Static WindowEdge.NorthEast}"
|
||||||
|
PointerPressed="CustomResizeWindow"/>
|
||||||
|
|
||||||
|
<Border Width="4" Margin="0,4"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Stretch"
|
||||||
|
Cursor="LeftSide"
|
||||||
|
Tag="{x:Static WindowEdge.West}"
|
||||||
|
PointerPressed="CustomResizeWindow"/>
|
||||||
|
|
||||||
|
<Border Width="4" Margin="0,4"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Right" VerticalAlignment="Stretch"
|
||||||
|
Cursor="RightSide"
|
||||||
|
Tag="{x:Static WindowEdge.East}"
|
||||||
|
PointerPressed="CustomResizeWindow"/>
|
||||||
|
|
||||||
|
<Border Width="4" Height="4"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Bottom"
|
||||||
|
Cursor="BottomLeftCorner"
|
||||||
|
Tag="{x:Static WindowEdge.SouthWest}"
|
||||||
|
PointerPressed="CustomResizeWindow"/>
|
||||||
|
|
||||||
|
<Border Height="4" Margin="4,0"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
|
||||||
|
Cursor="BottomSide"
|
||||||
|
Tag="{x:Static WindowEdge.South}"
|
||||||
|
PointerPressed="CustomResizeWindow"/>
|
||||||
|
|
||||||
|
<Border Width="4" Height="4"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||||
|
Cursor="BottomRightCorner"
|
||||||
|
Tag="{x:Static WindowEdge.SouthEast}"
|
||||||
|
PointerPressed="CustomResizeWindow"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
58
src/Views/BranchCompare.axaml.cs
Normal file
58
src/Views/BranchCompare.axaml.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
|
||||||
|
namespace SourceGit.Views
|
||||||
|
{
|
||||||
|
public partial class BranchCompare : Window
|
||||||
|
{
|
||||||
|
public BranchCompare()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MaximizeOrRestoreWindow(object sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
if (WindowState == WindowState.Maximized)
|
||||||
|
WindowState = WindowState.Normal;
|
||||||
|
else
|
||||||
|
WindowState = WindowState.Maximized;
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CustomResizeWindow(object sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Border border)
|
||||||
|
{
|
||||||
|
if (border.Tag is WindowEdge edge)
|
||||||
|
{
|
||||||
|
BeginResizeDrag(edge, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginMoveWindow(object sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
BeginMoveDrag(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ViewModels.BranchCompare vm && sender is ChangeCollectionView view)
|
||||||
|
{
|
||||||
|
var menu = vm.CreateChangeContextMenu();
|
||||||
|
view.OpenContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPressedSHA(object sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ViewModels.BranchCompare vm && sender is TextBlock block)
|
||||||
|
vm.NavigateTo(block.Text);
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue