mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -08:00
998 lines
37 KiB
C#
998 lines
37 KiB
C#
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.OnNavigateCommit = commit => {
|
|
Dispatcher.Invoke(() => {
|
|
workspace.SelectedItem = historiesSwitch;
|
|
histories.Navigate(commit);
|
|
});
|
|
};
|
|
|
|
InitializeComponent();
|
|
|
|
repo = opened;
|
|
repoName.Content = repo.Name;
|
|
histories.Repo = opened;
|
|
commits.Repo = opened;
|
|
|
|
UpdateBranches();
|
|
UpdateHistories();
|
|
UpdateLocalChanges();
|
|
UpdateStashes();
|
|
UpdateTags();
|
|
}
|
|
|
|
#region DATA_UPDATE
|
|
private void UpdateHistories() {
|
|
Dispatcher.Invoke(() => {
|
|
histories.SetLoadingEnabled(true);
|
|
});
|
|
|
|
Task.Run(() => {
|
|
var args = "-5000 ";
|
|
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) {
|
|
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");
|
|
} else {
|
|
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");
|
|
}
|
|
}
|
|
|
|
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;
|
|
});
|
|
});
|
|
}
|
|
|
|
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 Cleanup(object sender, RoutedEventArgs e) {
|
|
localBranchTree.ItemsSource = null;
|
|
remoteBranchTree.ItemsSource = null;
|
|
tagList.ItemsSource = null;
|
|
cachedLocalBranches.Clear();
|
|
cachedRemotes.Clear();
|
|
}
|
|
#endregion
|
|
|
|
#region TOOLBAR
|
|
private void Close(object sender, RoutedEventArgs e) {
|
|
if (PopupManager.IsLocked()) return;
|
|
PopupManager.Close();
|
|
|
|
cachedLocalBranches.Clear();
|
|
cachedRemotes.Clear();
|
|
|
|
repo.Close();
|
|
}
|
|
|
|
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 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 gitDir = Path.Combine(repo.Path, ".git");
|
|
var cherryPickMerge = Path.Combine(gitDir, "CHERRY_PICK_HEAD");
|
|
var rebaseMerge = Path.Combine(gitDir, "REBASE_HEAD");
|
|
var revertMerge = Path.Combine(gitDir, "REVERT_HEAD");
|
|
var otherMerge = Path.Combine(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 ListView).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 ListView).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 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
|
|
}
|
|
}
|