sourcegit/SourceGit/UI/Dashboard.xaml.cs

1070 lines
40 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace SourceGit.UI {
/// <summary>
/// Branch node in tree.
/// </summary>
public class BranchNode {
public string Name { get; set; }
public Git.Branch Branch { get; set; }
public bool IsExpanded { get; set; }
public bool IsCurrent => Branch != null ? Branch.IsCurrent : false;
public bool IsFiltered => Branch != null ? Branch.IsFiltered : false;
public string Track => Branch != null ? Branch.UpstreamTrack : "";
public Visibility FilterVisibility => Branch == null ? Visibility.Collapsed : Visibility.Visible;
public Visibility TrackVisibility => (Branch != null && !Branch.IsSameWithUpstream) ? Visibility.Visible : Visibility.Collapsed;
public List<BranchNode> Children { get; set; }
}
/// <summary>
/// Remote node in tree.
/// </summary>
public class RemoteNode {
public string Name { get; set; }
public bool IsExpanded { get; set; }
public List<BranchNode> Children { get; set; }
}
/// <summary>
/// Dashboard for opened repository.
/// </summary>
public partial class Dashboard : UserControl {
private Git.Repository repo = null;
private List<BranchNode> cachedLocalBranches = new List<BranchNode>();
private List<RemoteNode> cachedRemotes = new List<RemoteNode>();
private string abortCommand = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repo">Opened repository.</param>
public Dashboard(Git.Repository opened) {
opened.OnWorkingCopyChanged = UpdateLocalChanges;
opened.OnTagChanged = UpdateTags;
opened.OnStashChanged = UpdateStashes;
opened.OnBranchChanged = () => UpdateBranches(false);
opened.OnCommitsChanged = UpdateHistories;
opened.OnSubmoduleChanged = UpdateSubmodules;
opened.OnNavigateCommit = commit => {
Dispatcher.Invoke(() => {
workspace.SelectedItem = historiesSwitch;
histories.Navigate(commit);
});
};
InitializeComponent();
repo = opened;
histories.Repo = opened;
commits.Repo = opened;
UpdateBranches();
UpdateHistories();
UpdateLocalChanges();
UpdateStashes();
UpdateTags();
UpdateSubmodules();
}
/// <summary>
/// Cleanup all items
/// </summary>
public void Cleanup() {
localBranchTree.ItemsSource = null;
remoteBranchTree.ItemsSource = null;
tagList.ItemsSource = null;
cachedLocalBranches.Clear();
cachedRemotes.Clear();
}
#region DATA_UPDATE
private void UpdateHistories() {
Dispatcher.Invoke(() => {
histories.SetLoadingEnabled(true);
});
Task.Run(() => {
var args = "-8000 ";
if (repo.LogFilters.Count > 0) {
args = args + string.Join(" ", repo.LogFilters);
} else {
args = args + "--branches --remotes --tags";
}
var commits = repo.Commits(args);
histories.SetCommits(commits);
});
}
private void UpdateLocalChanges() {
Task.Run(() => {
var changes = repo.LocalChanges();
var conflicts = commits.SetData(changes);
Dispatcher.Invoke(() => {
localChangesBadge.Visibility = changes.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
localChangesCount.Content = changes.Count;
btnContinue.Visibility = conflicts ? Visibility.Collapsed : Visibility.Visible;
DetectMergeState();
});
});
}
private void UpdateStashes() {
Task.Run(() => {
var data = repo.Stashes();
Dispatcher.Invoke(() => {
stashBadge.Visibility = data.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
stashCount.Content = data.Count;
stashes.SetData(repo, data);
});
});
}
private void BackupBranchNodeExpandState(Dictionary<string, bool> states, List<BranchNode> nodes, string prefix) {
foreach (var node in nodes) {
var path = prefix + "/" + node.Name;
states.Add(path, node.IsExpanded);
BackupBranchNodeExpandState(states, node.Children, path);
}
}
private void MakeBranchNode(Git.Branch branch, List<BranchNode> collection, Dictionary<string, BranchNode> folders, Dictionary<string, bool> expandStates, string prefix) {
var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (!branch.IsLocal) {
if (subs.Length < 2) return;
subs = subs.Skip(1).ToArray();
}
branch.IsFiltered = repo.LogFilters.Contains(branch.FullName);
if (subs.Length == 1) {
var node = new BranchNode() {
Name = subs[0],
Branch = branch,
Children = new List<BranchNode>(),
};
collection.Add(node);
} else {
BranchNode lastFolder = null;
string path = prefix;
for (int i = 0; i < subs.Length - 1; i++) {
path = path + "/" + subs[i];
if (folders.ContainsKey(path)) {
lastFolder = folders[path];
} else if (lastFolder == null) {
lastFolder = new BranchNode() {
Name = subs[i],
IsExpanded = expandStates.ContainsKey(path) ? expandStates[path] : false,
Children = new List<BranchNode>(),
};
collection.Add(lastFolder);
folders.Add(path, lastFolder);
} else {
var folder = new BranchNode() {
Name = subs[i],
IsExpanded = expandStates.ContainsKey(path) ? expandStates[path] : false,
Children = new List<BranchNode>(),
};
lastFolder.Children.Add(folder);
folders.Add(path, folder);
lastFolder = folder;
}
}
BranchNode node = new BranchNode();
node.Name = subs[subs.Length - 1];
node.Branch = branch;
node.Children = new List<BranchNode>();
lastFolder.Children.Add(node);
}
}
private void SortBranchNodes(List<BranchNode> collection) {
collection.Sort((l, r) => {
if (l.Branch != null) {
return r.Branch != null ? l.Branch.Name.CompareTo(r.Branch.Name) : -1;
} else {
return r.Branch == null ? l.Name.CompareTo(r.Name) : 1;
}
});
foreach (var sub in collection) {
if (sub.Children.Count > 0) SortBranchNodes(sub.Children);
}
}
private void UpdateBranches(bool force = true) {
bool IsDetached = false;
Git.Branch branch = null;
Task.Run(() => {
var branches = repo.Branches(force);
var remotes = repo.Remotes(true);
var localBranchNodes = new List<BranchNode>();
var remoteNodes = new List<RemoteNode>();
var remoteMap = new Dictionary<string, RemoteNode>();
var folders = new Dictionary<string, BranchNode>();
var states = new Dictionary<string, bool>();
BackupBranchNodeExpandState(states, cachedLocalBranches, "locals");
foreach (var r in cachedRemotes) {
var prefix = $"remotes/{r.Name}";
states.Add(prefix, r.IsExpanded);
BackupBranchNodeExpandState(states, r.Children, prefix);
}
foreach (var b in branches) {
if (b.IsLocal) {
MakeBranchNode(b, localBranchNodes, folders, states, "locals");
branch = b;
} else if (!string.IsNullOrEmpty(b.Remote)) {
RemoteNode remote = null;
if (!remoteMap.ContainsKey(b.Remote)) {
var key = "remotes/" + b.Remote;
remote = new RemoteNode() {
Name = b.Remote,
IsExpanded = states.ContainsKey(key) ? states[key] : false,
Children = new List<BranchNode>(),
};
remoteNodes.Add(remote);
remoteMap.Add(b.Remote, remote);
} else {
remote = remoteMap[b.Remote];
}
MakeBranchNode(b, remote.Children, folders, states, "remotes");
} else {
/// 对于 SUBMODULE HEAD 出于游离状态detached on commit id
/// 此时,分支既不是 本地分支,也不是远程分支
IsDetached = b.IsCurrent;
}
}
foreach (var r in remotes) {
if (!remoteMap.ContainsKey(r.Name)) {
var remote = new RemoteNode() {
Name = r.Name,
IsExpanded = false,
Children = new List<BranchNode>(),
};
remoteNodes.Add(remote);
}
}
SortBranchNodes(localBranchNodes);
foreach (var r in remoteNodes) SortBranchNodes(r.Children);
cachedLocalBranches = localBranchNodes;
cachedRemotes = remoteNodes;
Dispatcher.Invoke(() => {
localBranchTree.ItemsSource = localBranchNodes;
remoteBranchTree.ItemsSource = remoteNodes;
});
if (IsDetached && branch != null) repo.Checkout(branch.Name);
});
}
private void UpdateTags() {
Task.Run(() => {
var tags = repo.Tags(true);
foreach (var t in tags) t.IsFiltered = repo.LogFilters.Contains(t.Name);
Dispatcher.Invoke(() => {
tagCount.Content = $"TAGS ({tags.Count})";
tagList.ItemsSource = tags;
});
});
}
private void UpdateSubmodules() {
Task.Run(() => {
var submodules = repo.Submodules();
Dispatcher.Invoke(() => {
submoduleCount.Content = $"SUBMODULES ({submodules.Count})";
submoduleList.ItemsSource = submodules;
});
});
}
#endregion
#region TOOLBAR
private void OpenFetch(object sender, RoutedEventArgs e) {
Fetch.Show(repo);
}
private void OpenPull(object sender, RoutedEventArgs e) {
Pull.Show(repo);
}
private void OpenPush(object sender, RoutedEventArgs e) {
Push.Show(repo);
}
private void OpenStash(object sender, RoutedEventArgs e) {
Stash.Show(repo, new List<string>());
}
private void OpenApply(object sender, RoutedEventArgs e) {
Apply.Show(repo);
}
private void OpenSearch(object sender, RoutedEventArgs e) {
if (popupManager.IsLocked()) return;
workspace.SelectedItem = historiesSwitch;
if (histories.searchBar.Margin.Top == 0) {
histories.HideSearchBar();
} else {
histories.OpenSearchBar();
}
}
private void OpenConfigure(object sender, RoutedEventArgs e) {
Configure.Show(repo);
}
private void OpenExplorer(object sender, RoutedEventArgs e) {
Process.Start(repo.Path);
}
private void OpenTerminal(object sender, RoutedEventArgs e) {
var bash = Path.Combine(App.Preference.GitExecutable, "..", "bash.exe");
if (!File.Exists(bash)) {
App.RaiseError("Can NOT locate bash.exe. Make sure bash.exe exists under the same folder with git.exe");
return;
}
var start = new ProcessStartInfo();
start.WorkingDirectory = repo.Path;
start.FileName = bash;
Process.Start(start);
}
#endregion
#region HOT_KEYS
public void OpenSearchBar(object sender, ExecutedRoutedEventArgs e) {
workspace.SelectedItem = historiesSwitch;
histories.OpenSearchBar();
}
public void HideSearchBar(object sender, ExecutedRoutedEventArgs e) {
if (histories.Visibility == Visibility.Visible) {
histories.HideSearchBar();
}
}
#endregion
#region MERGE_ABORTS
public void DetectMergeState() {
var cherryPickMerge = Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD");
var rebaseMerge = Path.Combine(repo.GitDir, "REBASE_HEAD");
var revertMerge = Path.Combine(repo.GitDir, "REVERT_HEAD");
var otherMerge = Path.Combine(repo.GitDir, "MERGE_HEAD");
if (File.Exists(cherryPickMerge)) {
abortCommand = "cherry-pick";
txtMergeProcessing.Content = "Cherry-Pick merge request detected! Press 'Abort' to restore original HEAD";
} else if (File.Exists(rebaseMerge)) {
abortCommand = "rebase";
txtMergeProcessing.Content = "Rebase merge request detected! Press 'Abort' to restore original HEAD";
} else if (File.Exists(revertMerge)) {
abortCommand = "revert";
txtMergeProcessing.Content = "Revert merge request detected! Press 'Abort' to restore original HEAD";
} else if (File.Exists(otherMerge)) {
abortCommand = "merge";
txtMergeProcessing.Content = "Merge request detected! Press 'Abort' to restore original HEAD";
} else {
abortCommand = null;
}
if (abortCommand != null) {
abortPanel.Visibility = Visibility.Visible;
if (commits.Visibility == Visibility.Visible) {
btnResolve.Visibility = Visibility.Collapsed;
} else {
btnResolve.Visibility = Visibility.Visible;
}
commits.LoadMergeMessage();
} else {
abortPanel.Visibility = Visibility.Collapsed;
}
}
private void Resolve(object sender, RoutedEventArgs e) {
workspace.SelectedItem = workingCopySwitch;
}
private async void Continue(object sender, RoutedEventArgs e) {
if (abortCommand == null) return;
await Task.Run(() => {
repo.SetWatcherEnabled(false);
var errs = repo.RunCommand($"-c core.editor=true {abortCommand} --continue", null);
repo.AssertCommand(errs);
});
commits.ClearMessage();
}
private async void Abort(object sender, RoutedEventArgs e) {
if (abortCommand == null) return;
await Task.Run(() => {
repo.SetWatcherEnabled(false);
var errs = repo.RunCommand($"{abortCommand} --abort", null);
repo.AssertCommand(errs);
});
commits.ClearMessage();
}
#endregion
#region WORKSPACE
private void SwitchWorkingCopy(object sender, RoutedEventArgs e) {
if (commits == null || histories == null || stashes == null) return;
commits.Visibility = Visibility.Visible;
histories.Visibility = Visibility.Collapsed;
stashes.Visibility = Visibility.Collapsed;
if (abortPanel.Visibility == Visibility.Visible) {
btnResolve.Visibility = Visibility.Collapsed;
}
}
private void SwitchHistories(object sender, RoutedEventArgs e) {
if (commits == null || histories == null || stashes == null) return;
commits.Visibility = Visibility.Collapsed;
histories.Visibility = Visibility.Visible;
stashes.Visibility = Visibility.Collapsed;
if (abortPanel.Visibility == Visibility.Visible) {
btnResolve.Visibility = Visibility.Visible;
}
}
private void SwitchStashes(object sender, RoutedEventArgs e) {
if (commits == null || histories == null || stashes == null) return;
commits.Visibility = Visibility.Collapsed;
histories.Visibility = Visibility.Collapsed;
stashes.Visibility = Visibility.Visible;
if (abortPanel.Visibility == Visibility.Visible) {
btnResolve.Visibility = Visibility.Visible;
}
}
#endregion
#region LOCAL_BRANCHES
private void OpenNewBranch(object sender, RoutedEventArgs e) {
CreateBranch.Show(repo);
}
private void OpenGitFlow(object sender, RoutedEventArgs ev) {
var button = sender as Button;
if (button.ContextMenu == null) {
button.ContextMenu = new ContextMenu();
button.ContextMenu.PlacementTarget = button;
button.ContextMenu.Placement = PlacementMode.Bottom;
button.ContextMenu.StaysOpen = false;
button.ContextMenu.Focusable = true;
} else {
button.ContextMenu.Items.Clear();
}
if (repo.IsGitFlowEnabled()) {
var startFeature = new MenuItem();
startFeature.Header = "Start Feature ...";
startFeature.Click += (o, e) => {
GitFlowStartBranch.Show(repo, Git.Branch.Type.Feature);
e.Handled = true;
};
var startRelease = new MenuItem();
startRelease.Header = "Start Release ...";
startRelease.Click += (o, e) => {
GitFlowStartBranch.Show(repo, Git.Branch.Type.Release);
e.Handled = true;
};
var startHotfix = new MenuItem();
startHotfix.Header = "Start Hotfix ...";
startHotfix.Click += (o, e) => {
GitFlowStartBranch.Show(repo, Git.Branch.Type.Hotfix);
e.Handled = true;
};
button.ContextMenu.Items.Add(startFeature);
button.ContextMenu.Items.Add(startRelease);
button.ContextMenu.Items.Add(startHotfix);
} else {
var init = new MenuItem();
init.Header = "Initialize Git-Flow";
init.Click += (o, e) => {
GitFlowSetup.Show(repo);
e.Handled = true;
};
button.ContextMenu.Items.Add(init);
}
button.ContextMenu.IsOpen = true;
ev.Handled = true;
}
private void LocalBranchSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
var node = e.NewValue as BranchNode;
if (node == null || node.Branch == null) return;
repo.OnNavigateCommit?.Invoke(node.Branch.Head);
}
private void LocalBranchMouseDoubleClick(object sender, MouseButtonEventArgs e) {
var node = (sender as TreeViewItem).DataContext as BranchNode;
if (node == null || node.Branch == null) return;
Task.Run(() => repo.Checkout(node.Branch.Name));
}
private void LocalBranchContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var node = (sender as TreeViewItem).DataContext as BranchNode;
if (node == null || node.Branch == null) return;
var menu = new ContextMenu();
var branch = node.Branch;
var push = new MenuItem();
push.Header = $"Push '{branch.Name}'";
push.Click += (o, e) => {
Push.Show(repo, branch);
e.Handled = true;
};
if (branch.IsCurrent) {
var discard = new MenuItem();
discard.Header = "Discard all changes";
discard.Click += (o, e) => {
Discard.Show(repo, null);
e.Handled = true;
};
menu.Items.Add(discard);
menu.Items.Add(new Separator());
if (!string.IsNullOrEmpty(branch.Upstream)) {
var upstream = branch.Upstream.Substring(13);
var fastForward = new MenuItem();
fastForward.Header = $"Fast-Forward to '{upstream}'";
fastForward.Click += (o, e) => {
Merge.StartDirectly(repo, upstream, branch.Name);
e.Handled = true;
};
var pull = new MenuItem();
pull.Header = $"Pull '{upstream}'";
pull.Click += (o, e) => {
Pull.Show(repo);
e.Handled = true;
};
menu.Items.Add(fastForward);
menu.Items.Add(pull);
}
menu.Items.Add(push);
} else {
var current = repo.CurrentBranch();
var checkout = new MenuItem();
checkout.Header = $"Checkout {branch.Name}";
checkout.Click += (o, e) => {
Task.Run(() => repo.Checkout(node.Branch.Name));
e.Handled = true;
};
menu.Items.Add(checkout);
menu.Items.Add(new Separator());
menu.Items.Add(push);
var merge = new MenuItem();
merge.Header = $"Merge '{branch.Name}' into '{current.Name}'";
merge.Click += (o, e) => {
Merge.Show(repo, branch.Name, current.Name);
e.Handled = true;
};
menu.Items.Add(merge);
var rebase = new MenuItem();
rebase.Header = $"Rebase '{current.Name}' on '{branch.Name}'";
rebase.Click += (o, e) => {
Rebase.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(rebase);
}
if (branch.Kind != Git.Branch.Type.Normal) {
menu.Items.Add(new Separator());
var icon = new System.Windows.Shapes.Path();
icon.Style = FindResource("Style.Icon") as Style;
icon.Data = FindResource("Icon.Flow") as Geometry;
icon.Width = 10;
var finish = new MenuItem();
finish.Header = $"Git Flow - Finish '{branch.Name}'";
finish.Icon = icon;
finish.Click += (o, e) => {
GitFlowFinishBranch.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(finish);
}
var rename = new MenuItem();
rename.Header = $"Rename '{branch.Name}'";
rename.Click += (o, e) => {
RenameBranch.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(new Separator());
menu.Items.Add(rename);
var delete = new MenuItem();
delete.Header = $"Delete '{branch.Name}'";
delete.IsEnabled = !branch.IsCurrent;
delete.Click += (o, e) => {
DeleteBranch.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(delete);
menu.Items.Add(new Separator());
var createBranch = new MenuItem();
createBranch.Header = "Create Branch";
createBranch.Click += (o, e) => {
CreateBranch.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(createBranch);
var createTag = new MenuItem();
createTag.Header = "Create Tag";
createTag.Click += (o, e) => {
CreateTag.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(createTag);
menu.Items.Add(new Separator());
var copy = new MenuItem();
copy.Header = "Copy Branch Name";
copy.Click += (o, e) => {
Clipboard.SetText(branch.Name);
e.Handled = true;
};
menu.Items.Add(copy);
menu.IsOpen = true;
ev.Handled = true;
}
#endregion
#region REMOTE_BRANCHES
private void OpenRemote(object sender, RoutedEventArgs e) {
Remote.Show(repo);
}
private void OpenRemoteContextMenu(RemoteNode node) {
var fetch = new MenuItem();
fetch.Header = $"Fetch '{node.Name}'";
fetch.Click += (o, e) => {
Fetch.Show(repo, node.Name);
e.Handled = true;
};
var edit = new MenuItem();
edit.Header = $"Edit '{node.Name}'";
edit.Click += (o, e) => {
var remotes = repo.Remotes();
var found = remotes.Find(r => r.Name == node.Name);
if (found != null) Remote.Show(repo, found);
e.Handled = true;
};
var delete = new MenuItem();
delete.Header = $"Delete '{node.Name}'";
delete.Click += (o, e) => {
DeleteRemote.Show(repo, node.Name);
e.Handled = true;
};
var copy = new MenuItem();
copy.Header = "Copy Remote URL";
copy.Click += (o, e) => {
var remotes = repo.Remotes();
var found = remotes.Find(r => r.Name == node.Name);
if (found != null) Clipboard.SetText(found.URL);
e.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(fetch);
menu.Items.Add(new Separator());
menu.Items.Add(edit);
menu.Items.Add(delete);
menu.Items.Add(new Separator());
menu.Items.Add(copy);
menu.IsOpen = true;
}
private void OpenRemoteBranchContextMenu(BranchNode node) {
var branch = node.Branch;
var current = repo.CurrentBranch();
if (current == null) return;
var checkout = new MenuItem();
checkout.Header = $"Checkout '{branch.Name}'";
checkout.Click += (o, e) => {
var branches = repo.Branches();
var tracked = null as Git.Branch;
var upstream = $"refs/remotes/{branch.Name}";
foreach (var b in branches) {
if (b.IsLocal && b.Upstream == upstream) {
tracked = b;
break;
}
}
if (tracked == null) {
CreateBranch.Show(repo, branch);
} else if (!tracked.IsCurrent) {
Task.Run(() => repo.Checkout(tracked.Name));
}
e.Handled = true;
};
var pull = new MenuItem();
pull.Header = $"Pull '{branch.Name}' into '{current.Name}'";
pull.Click += (o, e) => {
Pull.Show(repo, branch.Name);
e.Handled = true;
};
var merge = new MenuItem();
merge.Header = $"Merge '{branch.Name}' into '{current.Name}'";
merge.Click += (o, e) => {
Merge.Show(repo, branch.Name, current.Name);
e.Handled = true;
};
var rebase = new MenuItem();
rebase.Header = $"Rebase '{current.Name}' on '{branch.Name}'";
rebase.Click += (o, e) => {
Rebase.Show(repo, branch);
e.Handled = true;
};
var delete = new MenuItem();
delete.Header = $"Delete '{branch.Name}'";
delete.Click += (o, e) => {
DeleteBranch.Show(repo, branch);
e.Handled = true;
};
var createBranch = new MenuItem();
createBranch.Header = "Create New Branch";
createBranch.Click += (o, e) => {
CreateBranch.Show(repo, branch);
e.Handled = true;
};
var createTag = new MenuItem();
createTag.Header = "Create New Tag";
createTag.Click += (o, e) => {
CreateTag.Show(repo, branch);
e.Handled = true;
};
var copy = new MenuItem();
copy.Header = "Copy Branch Name";
copy.Click += (o, e) => {
Clipboard.SetText(branch.Name);
e.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(checkout);
menu.Items.Add(new Separator());
menu.Items.Add(pull);
menu.Items.Add(merge);
menu.Items.Add(rebase);
menu.Items.Add(new Separator());
menu.Items.Add(delete);
menu.Items.Add(new Separator());
menu.Items.Add(createBranch);
menu.Items.Add(createTag);
menu.Items.Add(new Separator());
menu.Items.Add(copy);
menu.IsOpen = true;
}
private void RemoteBranchSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
var node = e.NewValue as BranchNode;
if (node == null || node.Branch == null) return;
repo.OnNavigateCommit?.Invoke(node.Branch.Head);
}
private void RemoteContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var remoteNode = (sender as TreeViewItem).DataContext as RemoteNode;
if (remoteNode != null) {
OpenRemoteContextMenu(remoteNode);
ev.Handled = true;
return;
}
var branchNode = (sender as TreeViewItem).DataContext as BranchNode;
if (branchNode != null && branchNode.Branch != null) {
OpenRemoteBranchContextMenu(branchNode);
ev.Handled = true;
return;
}
}
#endregion
#region TAGS
private void OpenNewTag(object sender, RoutedEventArgs e) {
CreateTag.Show(repo);
}
private void TagLostFocus(object sender, RoutedEventArgs e) {
(sender as DataGrid).UnselectAll();
}
private void TagSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count == 1) {
var item = e.AddedItems[0] as Git.Tag;
repo.OnNavigateCommit?.Invoke(item.SHA);
}
}
private void TagContextMenuOpening(object sender, ContextMenuEventArgs e) {
var tag = (sender as DataGrid).SelectedItem as Git.Tag;
if (tag == null) return;
var createBranch = new MenuItem();
createBranch.Header = "Create New Branch";
createBranch.Click += (o, ev) => {
CreateBranch.Show(repo, tag);
ev.Handled = true;
};
var pushTag = new MenuItem();
pushTag.Header = $"Push '{tag.Name}'";
pushTag.Click += (o, ev) => {
PushTag.Show(repo, tag);
ev.Handled = true;
};
var deleteTag = new MenuItem();
deleteTag.Header = $"Delete '{tag.Name}'";
deleteTag.Click += (o, ev) => {
DeleteTag.Show(repo, tag);
ev.Handled = true;
};
var copy = new MenuItem();
copy.Header = "Copy Name";
copy.Click += (o, ev) => {
Clipboard.SetText(tag.Name);
ev.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(createBranch);
menu.Items.Add(new Separator());
menu.Items.Add(pushTag);
menu.Items.Add(deleteTag);
menu.Items.Add(new Separator());
menu.Items.Add(copy);
menu.IsOpen = true;
e.Handled = true;
}
#endregion
#region SUBMODULES
private void OpenAddSubmodule(object sender, RoutedEventArgs e) {
AddSubmodule.Show(repo);
}
private void UpdateSubmodule(object sender, RoutedEventArgs e) {
Waiting.Show(repo, () => repo.UpdateSubmodule());
}
private void SubmoduleLostFocus(object sender, RoutedEventArgs e) {
(sender as DataGrid).UnselectAll();
}
private void SubmoduleContextMenuOpening(object sender, ContextMenuEventArgs e) {
var path = (sender as DataGrid).SelectedItem as string;
if (path == null) return;
var open = new MenuItem();
open.Header = "Open Submodule Repository";
open.Click += (o, ev) => {
var sub = new Git.Repository();
sub.Path = Path.Combine(repo.Path, path);
sub.Name = Path.GetFileName(path);
sub.Parent = repo;
sub.Open();
ev.Handled = true;
};
var copy = new MenuItem();
copy.Header = "Copy Relative Path";
copy.Click += (o, ev) => {
Clipboard.SetText(path);
ev.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(open);
menu.Items.Add(copy);
menu.IsOpen = true;
e.Handled = true;
}
private void SubmoduleMouseDoubleClick(object sender, MouseButtonEventArgs e) {
var path = (sender as DataGridRow).DataContext as string;
if (path == null) return;
var sub = new Git.Repository();
sub.Path = Path.Combine(repo.Path, path);
sub.Name = Path.GetFileName(path);
sub.Parent = repo;
sub.Open();
}
#endregion
#region TREES
private TreeViewItem FindTreeViewItem(ItemsControl item, BranchNode node) {
if (item == null) return null;
var data = item.DataContext as BranchNode;
if (data == node) return item as TreeViewItem;
for (int i = 0; i < item.Items.Count; i++) {
var childContainer = item.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
var child = FindTreeViewItem(childContainer, node);
if (child != null) return child;
}
return null;
}
private void TreeLostFocus(object sender, RoutedEventArgs e) {
var tree = sender as TreeView;
var remote = tree.SelectedItem as RemoteNode;
if (remote != null) {
var remoteItem = tree.ItemContainerGenerator.ContainerFromItem(remote) as TreeViewItem;
if (remoteItem != null) remoteItem.IsSelected = false;
return;
}
var node = tree.SelectedItem as BranchNode;
if (node == null) return;
var item = FindTreeViewItem(tree, node);
if (item != null) item.IsSelected = false;
}
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;
}
#endregion
#region FILETER
private void FilterChanged(object sender, RoutedEventArgs e) {
var toggle = sender as ToggleButton;
if (toggle == null) return;
if (toggle.DataContext is BranchNode) {
var branch = (toggle.DataContext as BranchNode).Branch;
if (branch == null) return;
if (toggle.IsChecked == true) {
if (!repo.LogFilters.Contains(branch.FullName)) {
repo.LogFilters.Add(branch.FullName);
}
if (!string.IsNullOrEmpty(branch.Upstream) && !repo.LogFilters.Contains(branch.Upstream)) {
repo.LogFilters.Add(branch.Upstream);
UpdateBranches(false);
}
} else {
repo.LogFilters.Remove(branch.FullName);
if (!string.IsNullOrEmpty(branch.Upstream)) {
repo.LogFilters.Remove(branch.Upstream);
UpdateBranches(false);
}
}
}
if (toggle.DataContext is Git.Tag) {
var tag = toggle.DataContext as Git.Tag;
if (toggle.IsChecked == true) {
if (!repo.LogFilters.Contains(tag.Name)) {
repo.LogFilters.Add(tag.Name);
}
} else {
repo.LogFilters.Remove(tag.Name);
}
}
UpdateHistories();
}
#endregion
}
}