From 5592c652f79664e08a46266a4af09722fb500940 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 25 Sep 2020 17:24:43 +0800 Subject: [PATCH] feature(Histories): supports to diff selected 2 commits --- src/UI/CommitViewer.xaml.cs | 3 +- src/UI/Histories.xaml | 11 +- src/UI/Histories.xaml.cs | 18 +- src/UI/TwoCommitsDiff.xaml | 146 ++++++++++++++++ src/UI/TwoCommitsDiff.xaml.cs | 314 ++++++++++++++++++++++++++++++++++ 5 files changed, 479 insertions(+), 13 deletions(-) create mode 100644 src/UI/TwoCommitsDiff.xaml create mode 100644 src/UI/TwoCommitsDiff.xaml.cs diff --git a/src/UI/CommitViewer.xaml.cs b/src/UI/CommitViewer.xaml.cs index 40a3e222..42783b4e 100644 --- a/src/UI/CommitViewer.xaml.cs +++ b/src/UI/CommitViewer.xaml.cs @@ -282,8 +282,7 @@ namespace SourceGit.UI { e.Handled = true; } - private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e) - { + private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e) { var row = sender as DataGridRow; if (row == null) return; diff --git a/src/UI/Histories.xaml b/src/UI/Histories.xaml index 999df59f..a5cd84b9 100644 --- a/src/UI/Histories.xaml +++ b/src/UI/Histories.xaml @@ -161,13 +161,16 @@ - - + + + + + - + - diff --git a/src/UI/Histories.xaml.cs b/src/UI/Histories.xaml.cs index 7e3d1fad..91cbcbd6 100644 --- a/src/UI/Histories.xaml.cs +++ b/src/UI/Histories.xaml.cs @@ -41,10 +41,6 @@ namespace SourceGit.UI { /// public Histories() { InitializeComponent(); - - mask4MultiSelection.Visibility = Visibility.Visible; - txtTotalSelected.Content = "SELECT COMMIT TO VIEW DETAIL"; - ChangeOrientation(null, null); } @@ -271,14 +267,22 @@ namespace SourceGit.UI { private void CommitSelectChanged(object sender, SelectionChangedEventArgs e) { mask4MultiSelection.Visibility = Visibility.Collapsed; + commitViewer.Visibility = Visibility.Collapsed; + twoCommitDiff.Visibility = Visibility.Collapsed; var selected = commitList.SelectedItems; if (selected.Count == 1) { - var commit = selected[0] as Git.Commit; - if (commit != null) commitViewer.SetData(Repo, commit); - } else if (selected.Count > 1) { + commitViewer.Visibility = Visibility.Visible; + commitViewer.SetData(Repo, selected[0] as Git.Commit); + } else if (selected.Count == 2) { + twoCommitDiff.Visibility = Visibility.Visible; + twoCommitDiff.SetData(Repo, (selected[0] as Git.Commit).ShortSHA, (selected[1] as Git.Commit).ShortSHA); + } else if (selected.Count > 2) { mask4MultiSelection.Visibility = Visibility.Visible; txtTotalSelected.Content = $"SELECTED {selected.Count} COMMITS"; + } else { + mask4MultiSelection.Visibility = Visibility.Visible; + txtTotalSelected.Content = $"SELECT COMMIT TO VIEW DETAIL"; } } diff --git a/src/UI/TwoCommitsDiff.xaml b/src/UI/TwoCommitsDiff.xaml new file mode 100644 index 00000000..4e28bc8d --- /dev/null +++ b/src/UI/TwoCommitsDiff.xaml @@ -0,0 +1,146 @@ + + + + + + + + + diff --git a/src/UI/TwoCommitsDiff.xaml.cs b/src/UI/TwoCommitsDiff.xaml.cs new file mode 100644 index 00000000..6cca7799 --- /dev/null +++ b/src/UI/TwoCommitsDiff.xaml.cs @@ -0,0 +1,314 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace SourceGit.UI { + + /// + /// Diff with selected 2 commits. + /// + public partial class TwoCommitsDiff : UserControl { + private Git.Repository repo = null; + private string sha1 = null; + private string sha2 = null; + private List cachedChanges = new List(); + private List displayChanges = new List(); + private string changeFilter = null; + + /// + /// Node for file tree. + /// + public class Node { + public string FilePath { get; set; } = ""; + public string OriginalPath { get; set; } = ""; + public string Name { get; set; } = ""; + public bool IsFile { get; set; } = false; + public bool IsNodeExpanded { get; set; } = true; + public Git.Change Change { get; set; } = null; + public Git.Commit.Object CommitObject { get; set; } = null; + public List Children { get; set; } = new List(); + } + + /// + /// Constructor. + /// + public TwoCommitsDiff() { + InitializeComponent(); + } + + /// + /// Show. + /// + /// + /// + /// + public void SetData(Git.Repository repo, string sha1, string sha2) { + this.repo = repo; + this.sha1 = sha1; + this.sha2 = sha2; + + txtTitle.Content = $"COMMIT: {sha1} -> {sha2}"; + Task.Run(() => LoadChanges(true)); + } + + /// + /// Cleanup. + /// + public void Cleanup() { + repo = null; + cachedChanges.Clear(); + displayChanges.Clear(); + } + + private void LoadChanges(bool reload = false) { + if (reload) { + cachedChanges.Clear(); + + repo.RunCommand($"diff --name-status {sha1} {sha2}", line => { + var c = Git.Change.Parse(line, true); + if (c != null) cachedChanges.Add(c); + }); + } + + displayChanges.Clear(); + + if (string.IsNullOrEmpty(changeFilter)) { + displayChanges.AddRange(cachedChanges); + } else { + foreach (var c in cachedChanges) { + if (c.Path.ToUpper().Contains(changeFilter)) displayChanges.Add(c); + } + } + + List changeTreeSource = new List(); + Dictionary folders = new Dictionary(); + bool isDefaultExpanded = displayChanges.Count < 50; + + foreach (var c in displayChanges) { + var sepIdx = c.Path.IndexOf('/'); + if (sepIdx == -1) { + Node node = new Node(); + node.FilePath = c.Path; + node.IsFile = true; + node.Name = c.Path; + node.Change = c; + node.IsNodeExpanded = isDefaultExpanded; + if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath; + changeTreeSource.Add(node); + } else { + Node lastFolder = null; + var start = 0; + + while (sepIdx != -1) { + var folder = c.Path.Substring(0, sepIdx); + if (folders.ContainsKey(folder)) { + lastFolder = folders[folder]; + } else if (lastFolder == null) { + lastFolder = new Node(); + lastFolder.FilePath = folder; + lastFolder.Name = folder.Substring(start); + lastFolder.IsNodeExpanded = isDefaultExpanded; + changeTreeSource.Add(lastFolder); + folders.Add(folder, lastFolder); + } else { + var folderNode = new Node(); + folderNode.FilePath = folder; + folderNode.Name = folder.Substring(start); + folderNode.IsNodeExpanded = isDefaultExpanded; + folders.Add(folder, folderNode); + lastFolder.Children.Add(folderNode); + lastFolder = folderNode; + } + + start = sepIdx + 1; + sepIdx = c.Path.IndexOf('/', start); + } + + Node node = new Node(); + node.FilePath = c.Path; + node.Name = c.Path.Substring(start); + node.IsFile = true; + node.Change = c; + if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath; + lastFolder.Children.Add(node); + } + } + + folders.Clear(); + SortTreeNodes(changeTreeSource); + + Dispatcher.Invoke(() => { + changeList2.ItemsSource = null; + changeList2.ItemsSource = displayChanges; + changeTree.ItemsSource = changeTreeSource; + diffViewer.Reset(); + }); + } + + private void SearchChangeFileTextChanged(object sender, TextChangedEventArgs e) { + changeFilter = txtChangeFilter.Text.ToUpper(); + Task.Run(() => LoadChanges()); + } + + private void ChangeTreeItemSelected(object sender, RoutedPropertyChangedEventArgs e) { + diffViewer.Reset(); + + var node = e.NewValue as Node; + if (node == null || !node.IsFile) return; + + diffViewer.Diff(repo, new DiffViewer.Option() { + RevisionRange = new string[] { sha1, sha2 }, + Path = node.FilePath, + OrgPath = node.OriginalPath + }); + } + + private void ChangeListSelectionChanged(object sender, SelectionChangedEventArgs e) { + if (e.AddedItems.Count != 1) return; + + var change = e.AddedItems[0] as Git.Change; + if (change == null) return; + + diffViewer.Diff(repo, new DiffViewer.Option() { + RevisionRange = new string[] { sha1, sha2 }, + Path = change.Path, + OrgPath = change.OriginalPath + }); + } + + private void ChangeListContextMenuOpening(object sender, ContextMenuEventArgs e) { + var row = sender as DataGridRow; + if (row == null) return; + + var change = row.DataContext as Git.Change; + if (change == null) return; + + var path = change.Path; + var menu = new ContextMenu(); + if (change.Index != Git.Change.Status.Deleted) { + MenuItem history = new MenuItem(); + history.Header = "File History"; + history.Click += (o, ev) => { + var viewer = new FileHistories(repo, path); + viewer.Show(); + }; + menu.Items.Add(history); + + MenuItem explore = new MenuItem(); + explore.Header = "Reveal in File Explorer"; + explore.Click += (o, ev) => { + var absPath = Path.GetFullPath(repo.Path + "\\" + path); + Process.Start("explorer", $"/select,{absPath}"); + e.Handled = true; + }; + menu.Items.Add(explore); + } + + MenuItem copyPath = new MenuItem(); + copyPath.Header = "Copy Path"; + copyPath.Click += (obj, ev) => { + Clipboard.SetText(path); + }; + menu.Items.Add(copyPath); + menu.IsOpen = true; + e.Handled = true; + } + + private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e) { + var row = sender as DataGridRow; + if (row == null) return; + + var change = row.DataContext as Git.Change; + if (change == null) return; + + var viewer = new FileHistories(repo, change.Path); + viewer.Show(); + } + + private void SortTreeNodes(List list) { + list.Sort((l, r) => { + if (l.IsFile) { + return r.IsFile ? l.Name.CompareTo(r.Name) : 1; + } else { + return r.IsFile ? -1 : l.Name.CompareTo(r.Name); + } + }); + + foreach (var sub in list) { + if (sub.Children.Count > 0) SortTreeNodes(sub.Children); + } + } + + private ScrollViewer GetScrollViewer(FrameworkElement owner) { + if (owner == null) return null; + if (owner is ScrollViewer) return owner as ScrollViewer; + + int n = VisualTreeHelper.GetChildrenCount(owner); + for (int i = 0; i < n; i++) { + var child = VisualTreeHelper.GetChild(owner, i) as FrameworkElement; + var deep = GetScrollViewer(child); + if (deep != null) return deep; + } + + return null; + } + + private void TreeMouseWheel(object sender, MouseWheelEventArgs e) { + var scroll = GetScrollViewer(sender as TreeView); + if (scroll == null) return; + + if (e.Delta > 0) { + scroll.LineUp(); + } else { + scroll.LineDown(); + } + + e.Handled = true; + } + + private void TreeContextMenuOpening(object sender, ContextMenuEventArgs e) { + var item = sender as TreeViewItem; + if (item == null) return; + + var node = item.DataContext as Node; + if (node == null || !node.IsFile) return; + + item.IsSelected = true; + + ContextMenu menu = new ContextMenu(); + if (node.Change == null || node.Change.Index != Git.Change.Status.Deleted) { + MenuItem history = new MenuItem(); + history.Header = "File History"; + history.Click += (o, ev) => { + var viewer = new FileHistories(repo, node.FilePath); + viewer.Show(); + }; + menu.Items.Add(history); + + MenuItem explore = new MenuItem(); + explore.Header = "Reveal in File Explorer"; + explore.Click += (o, ev) => { + var path = Path.GetFullPath(repo.Path + "\\" + node.FilePath); + Process.Start("explorer", $"/select,{path}"); + e.Handled = true; + }; + menu.Items.Add(explore); + } + + MenuItem copyPath = new MenuItem(); + copyPath.Header = "Copy Path"; + copyPath.Click += (obj, ev) => { + Clipboard.SetText(node.FilePath); + }; + menu.Items.Add(copyPath); + + menu.IsOpen = true; + e.Handled = true; + } + } +}