using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.Generic; using System.IO; using System.Text.Json.Serialization; using System.Threading.Tasks; namespace SourceGit.ViewModels { public class Repository : ObservableObject, Models.IRepository { public string FullPath { get => _fullpath; set => SetProperty(ref _fullpath, value); } public string GitDir { get => _gitDir; set => SetProperty(ref _gitDir, value); } public AvaloniaList Filters { get; set; } = new AvaloniaList(); public AvaloniaList CommitMessages { get; set; } = new AvaloniaList(); [JsonIgnore] public bool IsVSCodeFound { get => !string.IsNullOrEmpty(Native.OS.VSCodeExecutableFile); } [JsonIgnore] public Models.GitFlow GitFlow { get => _gitflow; set => SetProperty(ref _gitflow, value); } [JsonIgnore] public int SelectedViewIndex { get => _selectedViewIndex; set { if (SetProperty(ref _selectedViewIndex, value)) { switch (value) { case 1: SelectedView = _workingCopy; break; case 2: SelectedView = _stashesPage; break; default: SelectedView = _histories; break; } } } } [JsonIgnore] public object SelectedView { get => _selectedView; set => SetProperty(ref _selectedView, value); } [JsonIgnore] public List Remotes { get => _remotes; private set => SetProperty(ref _remotes, value); } [JsonIgnore] public List Branches { get => _branches; private set => SetProperty(ref _branches, value); } [JsonIgnore] public List LocalBranchTrees { get => _localBranchTrees; private set => SetProperty(ref _localBranchTrees, value); } [JsonIgnore] public List RemoteBranchTrees { get => _remoteBranchTrees; private set => SetProperty(ref _remoteBranchTrees, value); } [JsonIgnore] public List Tags { get => _tags; private set => SetProperty(ref _tags, value); } [JsonIgnore] public List Submodules { get => _submodules; private set => SetProperty(ref _submodules, value); } [JsonIgnore] public int WorkingCopyChangesCount { get => _workingCopy.Count; } [JsonIgnore] public int StashesCount { get => _stashesPage.Count; } [JsonIgnore] public bool IsConflictBarVisible { get => _isConflictBarVisible; private set => SetProperty(ref _isConflictBarVisible, value); } [JsonIgnore] public bool HasUnsolvedConflict { get => _hasUnsolvedConflict; private set => SetProperty(ref _hasUnsolvedConflict, value); } [JsonIgnore] public bool CanCommitWithPush { get => _canCommitWithPush; private set => SetProperty(ref _canCommitWithPush, value); } [JsonIgnore] public bool IncludeUntracked { get => _includeUntracked; set { if (SetProperty(ref _includeUntracked, value)) { Task.Run(RefreshWorkingCopyChanges); } } } [JsonIgnore] public bool IsSearching { get => _isSearching; set { if (SetProperty(ref _isSearching, value)) { SearchedCommits = new List(); SearchCommitFilter = string.Empty; if (value) SelectedViewIndex = 0; } } } [JsonIgnore] public string SearchCommitFilter { get => _searchCommitFilter; set => SetProperty(ref _searchCommitFilter, value); } [JsonIgnore] public List SearchedCommits { get => _searchedCommits; set => SetProperty(ref _searchedCommits, value); } public void Open() { _watcher = new Models.Watcher(this); _histories = new Histories(this); _workingCopy = new WorkingCopy(this); _stashesPage = new StashesPage(this); _selectedView = _histories; _selectedViewIndex = 0; _isConflictBarVisible = false; _hasUnsolvedConflict = false; Task.Run(() => { RefreshBranches(); RefreshTags(); RefreshCommits(); }); Task.Run(RefreshSubmodules); Task.Run(RefreshWorkingCopyChanges); Task.Run(RefreshStashes); Task.Run(RefreshGitFlow); } public void Close() { _watcher.Dispose(); _watcher = null; _histories = null; _workingCopy = null; _stashesPage = null; _selectedView = null; _isSearching = false; _searchCommitFilter = string.Empty; _remotes.Clear(); _branches.Clear(); _localBranchTrees.Clear(); _remoteBranchTrees.Clear(); _tags.Clear(); _submodules.Clear(); _searchedCommits.Clear(); GC.Collect(); } public void OpenInFileManager() { Native.OS.OpenInFileManager(_fullpath); } public void OpenInVSCode() { Native.OS.OpenInVSCode(_fullpath); } public void OpenInTerminal() { Native.OS.OpenTerminal(_fullpath); } public void Fetch() { if (!PopupHost.CanCreatePopup()) return; if (Remotes.Count == 0) { App.RaiseException(_fullpath, "No remotes added to this repository!!!"); return; } PopupHost.ShowPopup(new Fetch(this)); } public void Pull() { if (!PopupHost.CanCreatePopup()) return; if (Remotes.Count == 0) { App.RaiseException(_fullpath, "No remotes added to this repository!!!"); return; } PopupHost.ShowPopup(new Pull(this, null)); } public void Push() { if (!PopupHost.CanCreatePopup()) return; if (Remotes.Count == 0) { App.RaiseException(_fullpath, "No remotes added to this repository!!!"); return; } if (Branches.Find(x => x.IsCurrent) == null) App.RaiseException(_fullpath, "Can NOT found current branch!!!"); PopupHost.ShowPopup(new Push(this, null)); } public void ApplyPatch() { if (!PopupHost.CanCreatePopup()) return; PopupHost.ShowPopup(new Apply(this)); } public void Cleanup() { if (!PopupHost.CanCreatePopup()) return; PopupHost.ShowAndStartPopup(new Cleanup(this)); } public void OpenConfigure() { if (!PopupHost.CanCreatePopup()) return; PopupHost.ShowPopup(new RepositoryConfigure(this)); } public void ClearSearchCommitFilter() { SearchCommitFilter = string.Empty; } public void StartSearchCommits() { if (_histories == null) return; var visible = new List(); foreach (var c in _histories.Commits) { if (c.SHA.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Subject.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Message.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Author.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Committer.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase)) { visible.Add(c); } } SearchedCommits = visible; } public void ExitSearchMode() { IsSearching = false; } public void SetWatcherEnabled(bool enabled) { if (_watcher != null) _watcher.SetEnabled(enabled); } public void NavigateToCommit(string sha) { if (_histories != null) { SelectedViewIndex = 0; _histories.NavigateTo(sha); } } public void UpdateFilter(string filter, bool toggle) { var changed = false; if (toggle) { if (!Filters.Contains(filter)) { Filters.Add(filter); changed = true; } } else { changed = Filters.Remove(filter); } if (changed) Task.Run(RefreshCommits); } public void StashAll() { if (PopupHost.CanCreatePopup()) { var changes = new List(); changes.AddRange(_workingCopy.Unstaged); changes.AddRange(_workingCopy.Staged); PopupHost.ShowPopup(new StashChanges(this, changes, true)); } } public void GotoResolve() { if (_workingCopy != null) SelectedViewIndex = 1; } public async void ContinueMerge() { var cherryPickMerge = Path.Combine(_gitDir, "CHERRY_PICK_HEAD"); var rebaseMerge = Path.Combine(_gitDir, "REBASE_HEAD"); var rebaseMergeFolder = Path.Combine(_gitDir, "rebase-merge"); var revertMerge = Path.Combine(_gitDir, "REVERT_HEAD"); var otherMerge = Path.Combine(_gitDir, "MERGE_HEAD"); var mode = ""; if (File.Exists(cherryPickMerge)) { mode = "cherry-pick"; } else if (File.Exists(rebaseMerge) && Directory.Exists(rebaseMergeFolder)) { mode = "rebase"; } else if (File.Exists(revertMerge)) { mode = "revert"; } else if (File.Exists(otherMerge)) { mode = "merge"; } else { await Task.Run(RefreshWorkingCopyChanges); return; } var cmd = new Commands.Command(); cmd.WorkingDirectory = _fullpath; cmd.Context = _fullpath; cmd.Args = $"-c core.editor=true {mode} --continue"; SetWatcherEnabled(false); var succ = await Task.Run(cmd.Exec); SetWatcherEnabled(true); if (succ) { if (_workingCopy != null) _workingCopy.CommitMessage = string.Empty; if (mode == "rebase") { if (File.Exists(rebaseMerge)) File.Delete(rebaseMerge); if (Directory.Exists(rebaseMergeFolder)) Directory.Delete(rebaseMergeFolder); } } } public async void AbortMerge() { var cmd = new Commands.Command(); cmd.WorkingDirectory = _fullpath; cmd.Context = _fullpath; if (File.Exists(Path.Combine(_gitDir, "CHERRY_PICK_HEAD"))) { cmd.Args = "cherry-pick --abort"; } else if (File.Exists(Path.Combine(_gitDir, "REBASE_HEAD"))) { cmd.Args = "rebase --abort"; } else if (File.Exists(Path.Combine(_gitDir, "REVERT_HEAD"))) { cmd.Args = "revert --abort"; } else if (File.Exists(Path.Combine(_gitDir, "MERGE_HEAD"))) { cmd.Args = "merge --abort"; } else { await Task.Run(RefreshWorkingCopyChanges); return; } SetWatcherEnabled(false); await Task.Run(cmd.Exec); SetWatcherEnabled(true); } public void RefreshBranches() { var branches = new Commands.QueryBranches(FullPath).Result(); var remotes = new Commands.QueryRemotes(FullPath).Result(); var builder = new Models.BranchTreeNode.Builder(); builder.SetFilters(Filters); builder.CollectExpandedNodes(_localBranchTrees, true); builder.CollectExpandedNodes(_remoteBranchTrees, false); builder.Run(branches, remotes); Dispatcher.UIThread.Invoke(() => { Remotes = remotes; Branches = branches; LocalBranchTrees = builder.Locals; RemoteBranchTrees = builder.Remotes; var cur = Branches.Find(x => x.IsCurrent); CanCommitWithPush = cur != null && !string.IsNullOrEmpty(cur.Upstream); }); } public void RefreshTags() { var tags = new Commands.QueryTags(FullPath).Result(); foreach (var tag in tags) tag.IsFiltered = Filters.Contains(tag.Name); Dispatcher.UIThread.Invoke(() => { Tags = tags; }); } public void RefreshCommits() { Dispatcher.UIThread.Invoke(() => _histories.IsLoading = true); var limits = $"-{Preference.Instance.MaxHistoryCommits} "; var validFilters = new List(); foreach (var filter in Filters) { if (filter.StartsWith("refs/")) { if (_branches.FindIndex(x => x.FullName == filter) >= 0) validFilters.Add(filter); } else { if (_tags.FindIndex(t => t.Name == filter) >= 0) validFilters.Add(filter); } } if (validFilters.Count > 0) { limits += string.Join(" ", validFilters); } else { limits += "--branches --remotes --tags"; } var commits = new Commands.QueryCommits(FullPath, limits).Result(); Dispatcher.UIThread.Invoke(() => { _histories.IsLoading = false; _histories.Commits = commits; }); } public void RefreshSubmodules() { var submodules = new Commands.QuerySubmodules(FullPath).Result(); Dispatcher.UIThread.Invoke(() => { Submodules = submodules; }); } public void RefreshWorkingCopyChanges() { _watcher.MarkWorkingCopyRefreshed(); var changes = new Commands.QueryLocalChanges(FullPath, _includeUntracked).Result(); var hasUnsolvedConflict = _workingCopy.SetData(changes); var cherryPickMerge = Path.Combine(_gitDir, "CHERRY_PICK_HEAD"); var rebaseMerge = Path.Combine(_gitDir, "REBASE_HEAD"); var rebaseMergeFolder = Path.Combine(_gitDir, "rebase-merge"); var revertMerge = Path.Combine(_gitDir, "REVERT_HEAD"); var otherMerge = Path.Combine(_gitDir, "MERGE_HEAD"); var runningMerge = (File.Exists(cherryPickMerge) || (File.Exists(rebaseMerge) && Directory.Exists(rebaseMergeFolder)) || File.Exists(revertMerge) || File.Exists(otherMerge)); if (!runningMerge) { if (Directory.Exists(rebaseMergeFolder)) Directory.Delete(rebaseMergeFolder, true); var applyFolder = Path.Combine(_gitDir, "rebase-apply"); if (Directory.Exists(applyFolder)) Directory.Delete(applyFolder, true); } Dispatcher.UIThread.Invoke(() => { IsConflictBarVisible = runningMerge; HasUnsolvedConflict = hasUnsolvedConflict; OnPropertyChanged(nameof(WorkingCopyChangesCount)); }); } public void RefreshStashes() { var stashes = new Commands.QueryStashes(FullPath).Result(); Dispatcher.UIThread.Invoke(() => { _stashesPage.Stashes = stashes; OnPropertyChanged(nameof(StashesCount)); }); } public void RefreshGitFlow() { var config = new Commands.Config(_fullpath).ListAll(); var gitFlow = new Models.GitFlow(); if (config.ContainsKey("gitflow.prefix.feature")) gitFlow.Feature = config["gitflow.prefix.feature"]; if (config.ContainsKey("gitflow.prefix.release")) gitFlow.Release = config["gitflow.prefix.release"]; if (config.ContainsKey("gitflow.prefix.hotfix")) gitFlow.Hotfix = config["gitflow.prefix.hotfix"]; Dispatcher.UIThread.Invoke(() => { GitFlow = gitFlow; }); } public void CreateNewBranch() { var current = Branches.Find(x => x.IsCurrent); if (current == null) { App.RaiseException(_fullpath, "Git do not hold any branch until you do first commit."); return; } if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateBranch(this, current)); } public void CreateNewTag() { var current = Branches.Find(x => x.IsCurrent); if (current == null) { App.RaiseException(_fullpath, "Git do not hold any branch until you do first commit."); return; } if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateTag(this, current)); } public void AddRemote() { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new AddRemote(this)); } public void AddSubmodule() { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new AddSubmodule(this)); } public ContextMenu CreateContextMenuForGitFlow() { var menu = new ContextMenu(); menu.Placement = PlacementMode.BottomEdgeAlignedLeft; if (GitFlow.IsEnabled) { var startFeature = new MenuItem(); startFeature.Header = App.Text("GitFlow.StartFeature"); startFeature.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Feature)); e.Handled = true; }; var startRelease = new MenuItem(); startRelease.Header = App.Text("GitFlow.StartRelease"); startRelease.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Release)); e.Handled = true; }; var startHotfix = new MenuItem(); startHotfix.Header = App.Text("GitFlow.StartHotfix"); startHotfix.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Hotfix)); e.Handled = true; }; menu.Items.Add(startFeature); menu.Items.Add(startRelease); menu.Items.Add(startHotfix); } else { var init = new MenuItem(); init.Header = App.Text("GitFlow.Init"); init.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new InitGitFlow(this)); e.Handled = true; }; menu.Items.Add(init); } return menu; } public ContextMenu CreateContextMenuForLocalBranch(Models.Branch branch) { var menu = new ContextMenu(); var push = new MenuItem(); push.Header = CreateHighlightLabel("BranchCM.Push", branch.Name); push.Icon = CreateMenuIcon("Icons.Push"); push.IsEnabled = Remotes.Count > 0; push.Click += (_, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Push(this, branch)); e.Handled = true; }; if (branch.IsCurrent) { var discard = new MenuItem(); discard.Header = App.Text("BranchCM.DiscardAll"); discard.Icon = CreateMenuIcon("Icons.Undo"); discard.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Discard(this)); e.Handled = true; }; menu.Items.Add(discard); menu.Items.Add(new MenuItem() { Header = "-" }); if (!string.IsNullOrEmpty(branch.Upstream)) { var upstream = branch.Upstream.Substring(13); var fastForward = new MenuItem(); fastForward.Header = CreateHighlightLabel("BranchCM.FastForward", upstream); fastForward.Icon = CreateMenuIcon("Icons.FastForward"); fastForward.IsEnabled = !string.IsNullOrEmpty(branch.UpstreamTrackStatus) && branch.UpstreamTrackStatus.IndexOf('↑') < 0; fastForward.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowAndStartPopup(new Merge(this, upstream, branch.Name)); e.Handled = true; }; var pull = new MenuItem(); pull.Header = CreateHighlightLabel("BranchCM.Pull", upstream); pull.Icon = CreateMenuIcon("Icons.Pull"); pull.IsEnabled = !string.IsNullOrEmpty(branch.UpstreamTrackStatus); pull.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Pull(this, null)); e.Handled = true; }; menu.Items.Add(fastForward); menu.Items.Add(pull); } menu.Items.Add(push); } else { var current = Branches.Find(x => x.IsCurrent); var checkout = new MenuItem(); checkout.Header = CreateHighlightLabel("BranchCM.Checkout", branch.Name); checkout.Icon = CreateMenuIcon("Icons.Check"); checkout.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowAndStartPopup(new Checkout(this, branch.Name)); e.Handled = true; }; menu.Items.Add(checkout); var upstream = Branches.Find(x => x.FullName == branch.Upstream); if (upstream != null) { var fastForward = new MenuItem(); fastForward.Header = CreateHighlightLabel("BranchCM.FastForward", $"{upstream.Remote}/{upstream.Name}"); fastForward.Icon = CreateMenuIcon("Icons.FastForward"); fastForward.IsEnabled = !string.IsNullOrEmpty(branch.UpstreamTrackStatus) && branch.UpstreamTrackStatus.IndexOf('↑') < 0; fastForward.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowAndStartPopup(new FastForwardWithoutCheckout(this, branch, upstream)); e.Handled = true; }; menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(fastForward); } menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(push); var merge = new MenuItem(); merge.Header = CreateHighlightLabel("BranchCM.Merge", branch.Name, current.Name); merge.Icon = CreateMenuIcon("Icons.Merge"); merge.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Merge(this, branch.Name, current.Name)); e.Handled = true; }; var rebase = new MenuItem(); rebase.Header = CreateHighlightLabel("BranchCM.Rebase", current.Name, branch.Name); rebase.Icon = CreateMenuIcon("Icons.Rebase"); rebase.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Rebase(this, current, branch)); e.Handled = true; }; menu.Items.Add(merge); menu.Items.Add(rebase); } var type = GitFlow.GetBranchType(branch.Name); if (type != Models.GitFlowBranchType.None) { var finish = new MenuItem(); finish.Header = CreateHighlightLabel("BranchCM.Finish", branch.Name); finish.Icon = CreateMenuIcon("Icons.Flow"); finish.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new GitFlowFinish(this, branch, type)); e.Handled = true; }; menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(finish); } var rename = new MenuItem(); rename.Header = CreateHighlightLabel("BranchCM.Rename", branch.Name); rename.Icon = CreateMenuIcon("Icons.Rename"); rename.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new RenameBranch(this, branch)); e.Handled = true; }; var delete = new MenuItem(); delete.Header = CreateHighlightLabel("BranchCM.Delete", branch.Name); delete.Icon = CreateMenuIcon("Icons.Clear"); delete.IsEnabled = !branch.IsCurrent; delete.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new DeleteBranch(this, branch)); e.Handled = true; }; var createBranch = new MenuItem(); createBranch.Icon = CreateMenuIcon("Icons.Branch.Add"); createBranch.Header = App.Text("CreateBranch"); createBranch.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateBranch(this, branch)); e.Handled = true; }; var createTag = new MenuItem(); createTag.Icon = CreateMenuIcon("Icons.Tag.Add"); createTag.Header = App.Text("CreateTag"); createTag.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateTag(this, branch)); e.Handled = true; }; menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(rename); menu.Items.Add(delete); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(createBranch); menu.Items.Add(createTag); menu.Items.Add(new MenuItem() { Header = "-" }); var remoteBranches = new List(); foreach (var b in Branches) { if (!b.IsLocal) remoteBranches.Add(b); } if (remoteBranches.Count > 0) { var tracking = new MenuItem(); tracking.Header = App.Text("BranchCM.Tracking"); tracking.Icon = CreateMenuIcon("Icons.Branch"); foreach (var b in remoteBranches) { var upstream = b.FullName.Replace("refs/remotes/", ""); var target = new MenuItem(); target.Header = upstream; if (branch.Upstream == b.FullName) target.Icon = CreateMenuIcon("Icons.Check"); target.Click += (o, e) => { if (Commands.Branch.SetUpstream(_fullpath, branch.Name, upstream)) { Task.Run(RefreshBranches); } e.Handled = true; }; tracking.Items.Add(target); } var unsetUpstream = new MenuItem(); unsetUpstream.Header = App.Text("BranchCM.UnsetUpstream"); unsetUpstream.Click += (_, e) => { if (Commands.Branch.SetUpstream(_fullpath, branch.Name, string.Empty)) { Task.Run(RefreshBranches); } e.Handled = true; }; tracking.Items.Add(new MenuItem() { Header = "-" }); tracking.Items.Add(unsetUpstream); menu.Items.Add(tracking); } var archive = new MenuItem(); archive.Icon = CreateMenuIcon("Icons.Archive"); archive.Header = App.Text("Archive"); archive.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Archive(this, branch)); e.Handled = true; }; menu.Items.Add(archive); menu.Items.Add(new MenuItem() { Header = "-" }); var copy = new MenuItem(); copy.Header = App.Text("BranchCM.CopyName"); copy.Icon = CreateMenuIcon("Icons.Copy"); copy.Click += (o, e) => { App.CopyText(branch.Name); e.Handled = true; }; menu.Items.Add(copy); return menu; } public ContextMenu CreateContextMenuForRemote(Models.Remote remote) { var menu = new ContextMenu(); var fetch = new MenuItem(); fetch.Header = App.Text("RemoteCM.Fetch"); fetch.Icon = CreateMenuIcon("Icons.Fetch"); fetch.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowAndStartPopup(new Fetch(this, remote)); e.Handled = true; }; var prune = new MenuItem(); prune.Header = App.Text("RemoteCM.Prune"); prune.Icon = CreateMenuIcon("Icons.Clear2"); prune.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowAndStartPopup(new PruneRemote(this, remote)); e.Handled = true; }; var edit = new MenuItem(); edit.Header = App.Text("RemoteCM.Edit"); edit.Icon = CreateMenuIcon("Icons.Edit"); edit.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new EditRemote(this, remote)); e.Handled = true; }; var delete = new MenuItem(); delete.Header = App.Text("RemoteCM.Delete"); delete.Icon = CreateMenuIcon("Icons.Clear"); delete.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new DeleteRemote(this, remote)); e.Handled = true; }; var copy = new MenuItem(); copy.Header = App.Text("RemoteCM.CopyURL"); copy.Icon = CreateMenuIcon("Icons.Copy"); copy.Click += (o, e) => { App.CopyText(remote.URL); e.Handled = true; }; menu.Items.Add(fetch); menu.Items.Add(prune); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(edit); menu.Items.Add(delete); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(copy); return menu; } public ContextMenu CreateContextMenuForRemoteBranch(Models.Branch branch) { var menu = new ContextMenu(); var current = Branches.Find(x => x.IsCurrent); var checkout = new MenuItem(); checkout.Header = CreateHighlightLabel("BranchCM.Checkout", $"{branch.Remote}/{branch.Name}"); checkout.Icon = CreateMenuIcon("Icons.Check"); checkout.Click += (o, e) => { foreach (var b in Branches) { if (b.IsLocal && b.Upstream == branch.FullName) { if (b.IsCurrent) return; if (PopupHost.CanCreatePopup()) PopupHost.ShowAndStartPopup(new Checkout(this, b.Name)); return; } } if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateBranch(this, branch)); e.Handled = true; }; menu.Items.Add(checkout); menu.Items.Add(new MenuItem() { Header = "-" }); if (current != null) { var pull = new MenuItem(); pull.Header = CreateHighlightLabel("BranchCM.PullInto", $"{branch.Remote}/{branch.Name}", current.Name); pull.Icon = CreateMenuIcon("Icons.Pull"); pull.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Pull(this, branch)); e.Handled = true; }; var merge = new MenuItem(); merge.Header = CreateHighlightLabel("BranchCM.Merge", $"{branch.Remote}/{branch.Name}", current.Name); merge.Icon = CreateMenuIcon("Icons.Merge"); merge.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Merge(this, $"{branch.Remote}/{branch.Name}", current.Name)); e.Handled = true; }; var rebase = new MenuItem(); rebase.Header = CreateHighlightLabel("BranchCM.Rebase", current.Name, $"{branch.Remote}/{branch.Name}"); rebase.Icon = CreateMenuIcon("Icons.Rebase"); rebase.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Rebase(this, current, branch)); e.Handled = true; }; menu.Items.Add(pull); menu.Items.Add(merge); menu.Items.Add(rebase); menu.Items.Add(new MenuItem() { Header = "-" }); } var delete = new MenuItem(); delete.Header = CreateHighlightLabel("BranchCM.Delete", $"{branch.Remote}/{branch.Name}"); delete.Icon = CreateMenuIcon("Icons.Clear"); delete.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new DeleteBranch(this, branch)); e.Handled = true; }; var createBranch = new MenuItem(); createBranch.Icon = CreateMenuIcon("Icons.Branch.Add"); createBranch.Header = App.Text("CreateBranch"); createBranch.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateBranch(this, branch)); e.Handled = true; }; var createTag = new MenuItem(); createTag.Icon = CreateMenuIcon("Icons.Tag.Add"); createTag.Header = App.Text("CreateTag"); createTag.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateTag(this, branch)); e.Handled = true; }; var archive = new MenuItem(); archive.Icon = CreateMenuIcon("Icons.Archive"); archive.Header = App.Text("Archive"); archive.Click += (o, e) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Archive(this, branch)); e.Handled = true; }; var copy = new MenuItem(); copy.Header = App.Text("BranchCM.CopyName"); copy.Icon = CreateMenuIcon("Icons.Copy"); copy.Click += (o, e) => { App.CopyText(branch.Remote + "/" + branch.Name); e.Handled = true; }; menu.Items.Add(delete); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(createBranch); menu.Items.Add(createTag); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(archive); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(copy); return menu; } public ContextMenu CreateContextMenuForTag(Models.Tag tag) { var createBranch = new MenuItem(); createBranch.Icon = CreateMenuIcon("Icons.Branch.Add"); createBranch.Header = App.Text("CreateBranch"); createBranch.Click += (o, ev) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateBranch(this, tag)); ev.Handled = true; }; var pushTag = new MenuItem(); pushTag.Header = CreateHighlightLabel("TagCM.Push", tag.Name); pushTag.Icon = CreateMenuIcon("Icons.Push"); pushTag.IsEnabled = Remotes.Count > 0; pushTag.Click += (o, ev) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new PushTag(this, tag)); ev.Handled = true; }; var deleteTag = new MenuItem(); deleteTag.Header = CreateHighlightLabel("TagCM.Delete", tag.Name); deleteTag.Icon = CreateMenuIcon("Icons.Clear"); deleteTag.Click += (o, ev) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new DeleteTag(this, tag)); ev.Handled = true; }; var archive = new MenuItem(); archive.Icon = CreateMenuIcon("Icons.Archive"); archive.Header = App.Text("Archive"); archive.Click += (o, ev) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new Archive(this, tag)); ev.Handled = true; }; var copy = new MenuItem(); copy.Header = App.Text("TagCM.Copy"); copy.Icon = CreateMenuIcon("Icons.Copy"); copy.Click += (o, ev) => { App.CopyText(tag.Name); ev.Handled = true; }; var menu = new ContextMenu(); menu.Items.Add(createBranch); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(pushTag); menu.Items.Add(deleteTag); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(archive); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(copy); return menu; } public ContextMenu CreateContextMenuForSubmodule(string submodule) { var open = new MenuItem(); open.Header = App.Text("Submodule.Open"); open.Icon = CreateMenuIcon("Icons.Folder.Open"); open.Click += (o, ev) => { var root = Path.GetFullPath(Path.Combine(_fullpath, submodule)); var gitDir = new Commands.QueryGitDir(root).Result(); var repo = Preference.AddRepository(root, gitDir); var node = new RepositoryNode() { Id = root, Name = Path.GetFileName(root), Bookmark = 0, IsRepository = true, }; var launcher = App.GetTopLevel().DataContext as Launcher; if (launcher != null) { launcher.OpenRepositoryInTab(node, null); } ev.Handled = true; }; var copy = new MenuItem(); copy.Header = App.Text("Submodule.CopyPath"); copy.Icon = CreateMenuIcon("Icons.Copy"); copy.Click += (o, ev) => { App.CopyText(submodule); ev.Handled = true; }; var rm = new MenuItem(); rm.Header = App.Text("Submodule.Remove"); rm.Icon = CreateMenuIcon("Icons.Clear"); rm.Click += (o, ev) => { if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new DeleteSubmodule(this, submodule)); ev.Handled = true; }; var menu = new ContextMenu(); menu.Items.Add(open); menu.Items.Add(copy); menu.Items.Add(rm); return menu; } private object CreateHighlightLabel(string key, params object[] args) { var label = new Views.NameHighlightedTextBlock(); label.Text = App.Text(key, args); label.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center; return label; } private Avalonia.Controls.Shapes.Path CreateMenuIcon(string key) { var icon = new Avalonia.Controls.Shapes.Path(); icon.Width = 12; icon.Height = 12; icon.Stretch = Stretch.Uniform; icon.Data = App.Current?.FindResource(key) as StreamGeometry; return icon; } private string _fullpath = string.Empty; private string _gitDir = string.Empty; private Models.GitFlow _gitflow = new Models.GitFlow(); private Models.Watcher _watcher = null; private Histories _histories = null; private WorkingCopy _workingCopy = null; private StashesPage _stashesPage = null; private int _selectedViewIndex = 0; private object _selectedView = null; private bool _isSearching = false; private string _searchCommitFilter = string.Empty; private List _searchedCommits = new List(); private List _remotes = new List(); private List _branches = new List(); private List _localBranchTrees = new List(); private List _remoteBranchTrees = new List(); private List _tags = new List(); private List _submodules = new List(); private bool _isConflictBarVisible = false; private bool _hasUnsolvedConflict = false; private bool _canCommitWithPush = false; private bool _includeUntracked = true; } }