2020-11-18 01:14:32 -08:00
|
|
|
using System;
|
2020-08-06 01:01:10 -07:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.IO;
|
2020-11-18 01:14:32 -08:00
|
|
|
using System.Security.Cryptography;
|
|
|
|
using System.Text;
|
2020-08-06 01:01:10 -07:00
|
|
|
using System.Threading.Tasks;
|
|
|
|
using System.Windows;
|
|
|
|
using System.Windows.Controls;
|
|
|
|
using System.Windows.Input;
|
|
|
|
using System.Windows.Media;
|
2020-11-18 01:14:32 -08:00
|
|
|
using System.Windows.Media.Imaging;
|
2020-08-06 01:01:10 -07:00
|
|
|
using System.Windows.Navigation;
|
|
|
|
|
|
|
|
namespace SourceGit.UI {
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Commit detail viewer
|
|
|
|
/// </summary>
|
|
|
|
public partial class CommitViewer : UserControl {
|
2020-11-18 01:14:32 -08:00
|
|
|
private static readonly string AVATAR_PATH = Path.Combine(
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
|
|
"SourceGit",
|
|
|
|
"avatars");
|
|
|
|
|
2020-08-06 01:01:10 -07:00
|
|
|
private Git.Repository repo = null;
|
|
|
|
private Git.Commit commit = null;
|
|
|
|
private List<Git.Change> cachedChanges = new List<Git.Change>();
|
|
|
|
private List<Git.Change> displayChanges = new List<Git.Change>();
|
|
|
|
private string changeFilter = null;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Node for file tree.
|
|
|
|
/// </summary>
|
|
|
|
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<Node> Children { get; set; } = new List<Node>();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Constructor.
|
|
|
|
/// </summary>
|
|
|
|
public CommitViewer() {
|
|
|
|
InitializeComponent();
|
|
|
|
}
|
|
|
|
|
|
|
|
#region DATA
|
|
|
|
public void SetData(Git.Repository opened, Git.Commit selected) {
|
|
|
|
repo = opened;
|
|
|
|
commit = selected;
|
|
|
|
|
|
|
|
SetBaseInfo(commit);
|
|
|
|
|
|
|
|
Task.Run(() => {
|
|
|
|
cachedChanges.Clear();
|
|
|
|
cachedChanges = commit.GetChanges(repo);
|
|
|
|
|
|
|
|
Dispatcher.Invoke(() => {
|
|
|
|
changeList1.ItemsSource = null;
|
|
|
|
changeList1.ItemsSource = cachedChanges;
|
|
|
|
});
|
|
|
|
|
|
|
|
LayoutChanges();
|
|
|
|
SetRevisionFiles(commit.GetFiles(repo));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private void Cleanup(object sender, RoutedEventArgs e) {
|
|
|
|
fileTree.ItemsSource = null;
|
|
|
|
changeList1.ItemsSource = null;
|
|
|
|
changeList2.ItemsSource = null;
|
|
|
|
displayChanges.Clear();
|
|
|
|
cachedChanges.Clear();
|
|
|
|
diffViewer.Reset();
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region BASE_INFO
|
|
|
|
private void SetBaseInfo(Git.Commit commit) {
|
|
|
|
var parentIds = new List<string>();
|
|
|
|
foreach (var p in commit.Parents) parentIds.Add(p.Substring(0, 8));
|
|
|
|
|
|
|
|
SHA.Text = commit.SHA;
|
|
|
|
refs.ItemsSource = commit.Decorators;
|
|
|
|
parents.ItemsSource = parentIds;
|
|
|
|
subject.Text = commit.Subject;
|
|
|
|
|
2020-11-23 03:00:14 -08:00
|
|
|
var commitMsg = commit.Message.Trim();
|
|
|
|
if (string.IsNullOrEmpty(commitMsg)) {
|
|
|
|
descRow.Height = new GridLength(0);
|
|
|
|
} else {
|
|
|
|
descRow.Height = GridLength.Auto;
|
|
|
|
message.Text = commitMsg;
|
|
|
|
}
|
|
|
|
|
|
|
|
authorName.Text = commit.Author.Name;
|
|
|
|
authorEmail.Text = commit.Author.Email;
|
|
|
|
authorTime.Text = commit.Author.Time;
|
|
|
|
|
|
|
|
if (commit.Committer.Email == commit.Author.Email) {
|
|
|
|
if (commit.Committer.Time == commit.Author.Time) {
|
|
|
|
committerPanel.Visibility = Visibility.Hidden;
|
|
|
|
|
|
|
|
SetAvatar(authorAvatar, commit.Author.Email);
|
|
|
|
} else {
|
|
|
|
committerPanel.Visibility = Visibility.Visible;
|
|
|
|
|
|
|
|
committerName.Text = commit.Committer.Name;
|
|
|
|
committerEmail.Text = commit.Committer.Email;
|
|
|
|
committerTime.Text = commit.Committer.Time;
|
|
|
|
|
|
|
|
SetAvatar(authorAvatar, commit.Author.Email);
|
|
|
|
SetAvatar(committerAvatar, commit.Committer.Email, false);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
committerPanel.Visibility = Visibility.Visible;
|
|
|
|
|
|
|
|
committerName.Text = commit.Committer.Name;
|
|
|
|
committerEmail.Text = commit.Committer.Email;
|
|
|
|
committerTime.Text = commit.Committer.Time;
|
|
|
|
|
|
|
|
SetAvatar(authorAvatar, commit.Author.Email);
|
|
|
|
SetAvatar(committerAvatar, commit.Committer.Email);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (commit.Decorators.Count == 0) {
|
|
|
|
refRow.Height = new GridLength(0);
|
|
|
|
} else {
|
|
|
|
refRow.Height = GridLength.Auto;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void SetAvatar(Image img, string email, bool save = true) {
|
|
|
|
byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(email.ToLower().Trim()));
|
2020-11-18 01:14:32 -08:00
|
|
|
string md5 = "";
|
|
|
|
for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2");
|
|
|
|
md5 = md5.ToLower();
|
|
|
|
|
|
|
|
if (!Directory.Exists(AVATAR_PATH)) Directory.CreateDirectory(AVATAR_PATH);
|
2020-08-06 01:01:10 -07:00
|
|
|
|
2020-11-18 01:14:32 -08:00
|
|
|
string filePath = Path.Combine(AVATAR_PATH, md5);
|
|
|
|
if (File.Exists(filePath)) {
|
2020-11-23 03:00:14 -08:00
|
|
|
img.Source = new BitmapImage(new Uri(filePath));
|
2020-08-06 01:01:10 -07:00
|
|
|
} else {
|
2020-11-25 19:52:50 -08:00
|
|
|
var bitmap = new BitmapImage(new Uri("https://www.gravatar.com/avatar/" + md5 + "?d=404"));
|
2020-11-23 03:00:14 -08:00
|
|
|
if (save) {
|
|
|
|
bitmap.DownloadCompleted += (o, e) => {
|
|
|
|
var owner = o as BitmapImage;
|
|
|
|
if (owner != null) {
|
|
|
|
var encoder = new PngBitmapEncoder();
|
|
|
|
encoder.Frames.Add(BitmapFrame.Create(owner));
|
|
|
|
using (var fs = new FileStream(filePath, FileMode.Create)) {
|
|
|
|
encoder.Save(fs);
|
|
|
|
}
|
2020-11-18 01:14:32 -08:00
|
|
|
}
|
2020-11-23 03:00:14 -08:00
|
|
|
};
|
2020-11-18 01:14:32 -08:00
|
|
|
}
|
2020-11-23 03:00:14 -08:00
|
|
|
img.Source = bitmap;
|
|
|
|
|
2020-08-06 01:01:10 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void NavigateParent(object sender, RequestNavigateEventArgs e) {
|
|
|
|
repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString);
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region CHANGES
|
|
|
|
private void LayoutChanges() {
|
|
|
|
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<Node> changeTreeSource = new List<Node>();
|
|
|
|
Dictionary<string, Node> folders = new Dictionary<string, Node>();
|
|
|
|
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(() => LayoutChanges());
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ChangeTreeItemSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
|
|
|
|
diffViewer.Reset();
|
|
|
|
|
|
|
|
var node = e.NewValue as Node;
|
|
|
|
if (node == null || !node.IsFile) return;
|
|
|
|
|
|
|
|
var start = $"{commit.SHA}^";
|
|
|
|
if (commit.Parents.Count == 0) {
|
|
|
|
start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
|
|
}
|
|
|
|
|
|
|
|
diffViewer.Diff(repo, new DiffViewer.Option() {
|
|
|
|
RevisionRange = new string[] { start, commit.SHA },
|
|
|
|
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;
|
|
|
|
|
|
|
|
var start = $"{commit.SHA}^";
|
|
|
|
if (commit.Parents.Count == 0) {
|
|
|
|
start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
|
|
}
|
|
|
|
|
|
|
|
diffViewer.Diff(repo, new DiffViewer.Option() {
|
|
|
|
RevisionRange = new string[] { start, commit.SHA },
|
|
|
|
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 blame = new MenuItem();
|
|
|
|
blame.Header = "Blame";
|
|
|
|
blame.Click += (obj, ev) => {
|
|
|
|
Blame viewer = new Blame(repo, path, commit.SHA);
|
|
|
|
viewer.Show();
|
|
|
|
};
|
|
|
|
menu.Items.Add(blame);
|
|
|
|
|
|
|
|
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 saveAs = new MenuItem();
|
|
|
|
saveAs.Header = "Save As ...";
|
|
|
|
saveAs.Click += (obj, ev) => {
|
2020-11-22 22:02:13 -08:00
|
|
|
FolderDailog.Open("Save file to ...", saveTo => {
|
2020-11-22 21:41:13 -08:00
|
|
|
var savePath = Path.Combine(saveTo, Path.GetFileName(path));
|
2020-11-18 04:05:41 -08:00
|
|
|
commit.SaveFileTo(repo, path, savePath);
|
2020-11-22 21:41:13 -08:00
|
|
|
});
|
2020-08-06 01:01:10 -07:00
|
|
|
};
|
|
|
|
menu.Items.Add(saveAs);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-09-25 02:24:43 -07:00
|
|
|
private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e) {
|
2020-08-06 01:01:10 -07:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region FILES
|
|
|
|
private void SetRevisionFiles(List<Git.Commit.Object> files) {
|
|
|
|
List<Node> fileTreeSource = new List<Node>();
|
|
|
|
Dictionary<string, Node> folders = new Dictionary<string, Node>();
|
|
|
|
|
|
|
|
foreach (var obj in files) {
|
|
|
|
var sepIdx = obj.Path.IndexOf("/");
|
|
|
|
if (sepIdx == -1) {
|
|
|
|
Node node = new Node();
|
|
|
|
node.FilePath = obj.Path;
|
|
|
|
node.Name = obj.Path;
|
|
|
|
node.IsFile = true;
|
|
|
|
node.IsNodeExpanded = false;
|
|
|
|
node.CommitObject = obj;
|
|
|
|
fileTreeSource.Add(node);
|
|
|
|
} else {
|
|
|
|
Node lastFolder = null;
|
|
|
|
var start = 0;
|
|
|
|
|
|
|
|
while (sepIdx != -1) {
|
|
|
|
var folder = obj.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 = false;
|
|
|
|
fileTreeSource.Add(lastFolder);
|
|
|
|
folders.Add(folder, lastFolder);
|
|
|
|
} else {
|
|
|
|
var folderNode = new Node();
|
|
|
|
folderNode.FilePath = folder;
|
|
|
|
folderNode.Name = folder.Substring(start);
|
|
|
|
folderNode.IsNodeExpanded = false;
|
|
|
|
folders.Add(folder, folderNode);
|
|
|
|
lastFolder.Children.Add(folderNode);
|
|
|
|
lastFolder = folderNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
start = sepIdx + 1;
|
|
|
|
sepIdx = obj.Path.IndexOf('/', start);
|
|
|
|
}
|
|
|
|
|
|
|
|
Node node = new Node();
|
|
|
|
node.FilePath = obj.Path;
|
|
|
|
node.Name = obj.Path.Substring(start);
|
|
|
|
node.IsFile = true;
|
|
|
|
node.IsNodeExpanded = false;
|
|
|
|
node.CommitObject = obj;
|
|
|
|
lastFolder.Children.Add(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
folders.Clear();
|
|
|
|
SortTreeNodes(fileTreeSource);
|
|
|
|
|
|
|
|
Dispatcher.Invoke(() => {
|
|
|
|
fileTree.ItemsSource = fileTreeSource;
|
|
|
|
filePreview.Text = "";
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private async void FileTreeItemSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
|
|
|
|
filePreview.Text = "";
|
|
|
|
maskPreviewNotSupported.Visibility = Visibility.Collapsed;
|
|
|
|
maskRevision.Visibility = Visibility.Collapsed;
|
|
|
|
|
|
|
|
var node = e.NewValue as Node;
|
|
|
|
if (node == null || !node.IsFile || node.CommitObject == null) return;
|
|
|
|
|
|
|
|
switch (node.CommitObject.Kind) {
|
|
|
|
case Git.Commit.Object.Type.Blob:
|
2020-09-25 01:30:28 -07:00
|
|
|
if (repo.IsLFSFiltered(node.FilePath)) {
|
|
|
|
var obj = repo.GetLFSObject(commit.SHA, node.FilePath);
|
|
|
|
maskRevision.Visibility = Visibility.Visible;
|
|
|
|
iconPreviewRevision.Data = FindResource("Icon.LFS") as Geometry;
|
|
|
|
txtPreviewRevision.Content = $"LFS SIZE: {obj.Size} Bytes";
|
|
|
|
} else {
|
|
|
|
await Task.Run(() => {
|
|
|
|
var isBinary = false;
|
|
|
|
var data = commit.GetTextFileContent(repo, node.FilePath, out isBinary);
|
|
|
|
|
|
|
|
if (isBinary) {
|
|
|
|
Dispatcher.Invoke(() => maskPreviewNotSupported.Visibility = Visibility.Visible);
|
|
|
|
} else {
|
|
|
|
Dispatcher.Invoke(() => filePreview.Text = data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-08-06 01:01:10 -07:00
|
|
|
break;
|
|
|
|
case Git.Commit.Object.Type.Tag:
|
|
|
|
maskRevision.Visibility = Visibility.Visible;
|
|
|
|
iconPreviewRevision.Data = FindResource("Icon.Tag") as Geometry;
|
|
|
|
txtPreviewRevision.Content = "TAG: " + node.CommitObject.SHA;
|
|
|
|
break;
|
|
|
|
case Git.Commit.Object.Type.Commit:
|
|
|
|
maskRevision.Visibility = Visibility.Visible;
|
|
|
|
iconPreviewRevision.Data = FindResource("Icon.Submodule") as Geometry;
|
|
|
|
txtPreviewRevision.Content = "SUBMODULE: " + node.CommitObject.SHA;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region TREE_COMMON
|
|
|
|
private void SortTreeNodes(List<Node> 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 void TreeMouseWheel(object sender, MouseWheelEventArgs e) {
|
2020-11-22 21:41:13 -08:00
|
|
|
var scroll = Helpers.TreeViewHelper.GetScrollViewer(sender as TreeView);
|
2020-08-06 01:01:10 -07:00
|
|
|
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 blame = new MenuItem();
|
|
|
|
blame.Header = "Blame";
|
|
|
|
blame.Click += (obj, ev) => {
|
|
|
|
Blame viewer = new Blame(repo, node.FilePath, commit.SHA);
|
|
|
|
viewer.Show();
|
|
|
|
};
|
|
|
|
menu.Items.Add(blame);
|
|
|
|
|
|
|
|
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 saveAs = new MenuItem();
|
|
|
|
saveAs.Header = "Save As ...";
|
|
|
|
saveAs.IsEnabled = node.CommitObject == null || node.CommitObject.Kind == Git.Commit.Object.Type.Blob;
|
|
|
|
saveAs.Click += (obj, ev) => {
|
2020-11-22 22:02:13 -08:00
|
|
|
FolderDailog.Open("Save file to ...", saveTo => {
|
2020-11-22 21:41:13 -08:00
|
|
|
var path = Path.Combine(saveTo, node.Name);
|
2020-11-18 04:05:41 -08:00
|
|
|
commit.SaveFileTo(repo, node.FilePath, path);
|
2020-11-22 21:41:13 -08:00
|
|
|
});
|
2020-08-06 01:01:10 -07:00
|
|
|
};
|
|
|
|
menu.Items.Add(saveAs);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-11-24 01:14:44 -08:00
|
|
|
private void TreeRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
|
|
|
|
e.Handled = true;
|
|
|
|
}
|
|
|
|
#endregion
|
2020-08-06 01:01:10 -07:00
|
|
|
}
|
|
|
|
}
|