diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs
index 4a553817..a3b13929 100644
--- a/src/Commands/QuerySingleCommit.cs
+++ b/src/Commands/QuerySingleCommit.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Text;
namespace SourceGit.Commands
{
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index 856577d7..fd962463 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -33,6 +33,7 @@
Blame
BLAME ON THIS FILE IS NOT SUPPORTED!!!
Checkout${0}$
+ Compare with Branch
Compare with HEAD
Compare with Worktree
Copy Branch Name
@@ -49,6 +50,7 @@
Rename${0}$
Tracking ...
Unset Upstream
+ Branch Compare
Bytes
CANCEL
CHANGE DISPLAY MODE
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index e5b46759..60868f16 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -36,6 +36,7 @@
逐行追溯(blame)
选中文件不支持该操作!!!
检出(checkout)${0}$
+ 与其他分支对比
与当前HEAD比较
与本地工作树比较
复制分支名
@@ -52,6 +53,7 @@
重命名${0}$
切换上游分支...
取消追踪
+ 分支比较
字节
取 消
切换变更显示模式
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index ae7a6d47..4ad5108d 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -36,6 +36,7 @@
逐行追溯(blame)
選中檔案不支援該操作!!!
檢出(checkout)${0}$
+ 與其他分支比較
與當前HEAD比較
與本地工作樹比較
複製分支名
@@ -52,6 +53,7 @@
重新命名${0}$
切換上游分支...
取消追蹤
+ 分支比較
位元組
取 消
切換變更顯示模式
diff --git a/src/ViewModels/BranchCompare.cs b/src/ViewModels/BranchCompare.cs
new file mode 100644
index 00000000..6b19d249
--- /dev/null
+++ b/src/ViewModels/BranchCompare.cs
@@ -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 VisibleChanges
+ {
+ get => _visibleChanges;
+ private set => SetProperty(ref _visibleChanges, value);
+ }
+
+ public List 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();
+ 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();
+ 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 _changes = null;
+ private List _visibleChanges = null;
+ private List _selectedChanges = null;
+ private string _searchFilter = string.Empty;
+ private DiffContext _diffContext = null;
+ }
+}
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index bbb0d6be..d564c15a 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -909,6 +909,13 @@ namespace SourceGit.ViewModels
}
menu.Items.Add(push);
+
+ var compareWithBranch = CreateMenuItemToCompareBranches(branch);
+ if (compareWithBranch != null)
+ {
+ menu.Items.Add(new MenuItem() { Header = "-" });
+ menu.Items.Add(compareWithBranch);
+ }
}
else
{
@@ -968,24 +975,6 @@ 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;
- };
-
if (WorkingCopyChangesCount > 0)
{
var compareWithWorktree = new MenuItem();
@@ -1002,11 +991,18 @@ namespace SourceGit.ViewModels
_histories.DetailContext = new RevisionCompare(FullPath, target, null);
}
};
+ menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(compareWithWorktree);
}
- menu.Items.Add(new MenuItem() { Header = "-" });
- menu.Items.Add(compare);
+ var compareWithBranch = CreateMenuItemToCompareBranches(branch);
+ if (compareWithBranch != null)
+ {
+ if (WorkingCopyChangesCount == 0)
+ menu.Items.Add(new MenuItem() { Header = "-" });
+
+ menu.Items.Add(compareWithBranch);
+ }
}
var type = GitFlow.GetBranchType(branch.Name);
@@ -1263,51 +1259,39 @@ 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);
-
- if (WorkingCopyChangesCount > 0)
- {
- var compareWithWorktree = new MenuItem();
- compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree");
- compareWithWorktree.Icon = App.CreateMenuIcon("Icons.Compare");
- compareWithWorktree.Click += (o, e) =>
- {
- SearchResultSelectedCommit = null;
-
- if (_histories != null)
- {
- var target = new Commands.QuerySingleCommit(FullPath, branch.Head).Result();
- _histories.AutoSelectedCommit = null;
- _histories.DetailContext = new RevisionCompare(FullPath, target, null);
- }
- };
- menu.Items.Add(compareWithWorktree);
- }
-
- menu.Items.Add(new MenuItem() { Header = "-" });
- }
}
+ var hasCompare = false;
+ if (WorkingCopyChangesCount > 0)
+ {
+ var compareWithWorktree = new MenuItem();
+ compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree");
+ compareWithWorktree.Icon = App.CreateMenuIcon("Icons.Compare");
+ compareWithWorktree.Click += (o, e) =>
+ {
+ SearchResultSelectedCommit = null;
+
+ if (_histories != null)
+ {
+ var target = new Commands.QuerySingleCommit(FullPath, branch.Head).Result();
+ _histories.AutoSelectedCommit = null;
+ _histories.DetailContext = new RevisionCompare(FullPath, target, null);
+ }
+ };
+ 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 = "-" });
+
var delete = new MenuItem();
delete.Header = new Views.NameHighlightedTextBlock("BranchCM.Delete", $"{branch.Remote}/{branch.Name}");
delete.Icon = App.CreateMenuIcon("Icons.Clear");
@@ -1485,6 +1469,41 @@ namespace SourceGit.ViewModels
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 branches, List remotes)
{
var builder = new BranchTreeNode.Builder();
diff --git a/src/Views/BranchCompare.axaml b/src/Views/BranchCompare.axaml
new file mode 100644
index 00000000..faaf26a3
--- /dev/null
+++ b/src/Views/BranchCompare.axaml
@@ -0,0 +1,240 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/BranchCompare.axaml.cs b/src/Views/BranchCompare.axaml.cs
new file mode 100644
index 00000000..7888caf5
--- /dev/null
+++ b/src/Views/BranchCompare.axaml.cs
@@ -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;
+ }
+ }
+}