diff --git a/TRANSLATION.md b/TRANSLATION.md index 1a9d00d1..08594b66 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,11 +6,18 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-98.23%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.75%25-yellow)
Missing keys in de_DE.axaml +- Text.BranchCM.ResetToSelectedCommit +- Text.CommitDetail.Changes.Count +- Text.CreateBranch.OverwriteExisting +- Text.DeinitSubmodule +- Text.DeinitSubmodule.Force +- Text.DeinitSubmodule.Path +- Text.Diff.Submodule.Deleted - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace @@ -18,7 +25,12 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.TextEditor.OpenExternalMergeTool - Text.Launcher.Workspaces - Text.Launcher.Pages +- Text.Pull.RecurseSubmodules - Text.Repository.ShowSubmodulesAsTree +- Text.ResetWithoutCheckout +- Text.ResetWithoutCheckout.MoveTo +- Text.ResetWithoutCheckout.Target +- Text.Submodule.Deinit - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -28,19 +40,16 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-99.49%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-99.87%25-yellow)
Missing keys in es_ES.axaml -- Text.Hotkeys.Global.SwitchWorkspace -- Text.Hotkeys.Global.SwitchTab -- Text.Launcher.Workspaces -- Text.Launcher.Pages +- Text.CreateBranch.OverwriteExisting
-### ![fr__FR](https://img.shields.io/badge/fr__FR-94.05%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-92.62%25-yellow)
Missing keys in fr_FR.axaml @@ -52,16 +61,23 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.Good - Text.Bisect.Skip - Text.Bisect.WaitingForRange +- Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject +- Text.CommitDetail.Changes.Count - Text.CommitMessageTextBox.SubjectCount - Text.Configure.Git.PreferredMergeMode - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges - Text.ConfirmEmptyCommit.StageAllThenCommit - Text.ConfirmEmptyCommit.WithLocalChanges +- Text.CreateBranch.OverwriteExisting +- Text.DeinitSubmodule +- Text.DeinitSubmodule.Force +- Text.DeinitSubmodule.Path +- Text.Diff.Submodule.Deleted - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace @@ -70,6 +86,7 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Preferences.Git.IgnoreCRAtEOLInDiff +- Text.Pull.RecurseSubmodules - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName @@ -77,6 +94,10 @@ This document shows the translation status of each locale file in the repository - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs - Text.Repository.Visit +- Text.ResetWithoutCheckout +- Text.ResetWithoutCheckout.MoveTo +- Text.ResetWithoutCheckout.Target +- Text.Submodule.Deinit - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -95,19 +116,31 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-99.49%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-98.00%25-yellow)
Missing keys in it_IT.axaml +- Text.BranchCM.ResetToSelectedCommit +- Text.CommitDetail.Changes.Count +- Text.CreateBranch.OverwriteExisting +- Text.DeinitSubmodule +- Text.DeinitSubmodule.Force +- Text.DeinitSubmodule.Path +- Text.Diff.Submodule.Deleted - Text.Hotkeys.Global.SwitchWorkspace - Text.Hotkeys.Global.SwitchTab - Text.Launcher.Workspaces - Text.Launcher.Pages +- Text.Pull.RecurseSubmodules +- Text.ResetWithoutCheckout +- Text.ResetWithoutCheckout.MoveTo +- Text.ResetWithoutCheckout.Target +- Text.Submodule.Deinit
-### ![ja__JP](https://img.shields.io/badge/ja__JP-93.80%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-92.37%25-yellow)
Missing keys in ja_JP.axaml @@ -119,16 +152,23 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.Good - Text.Bisect.Skip - Text.Bisect.WaitingForRange +- Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject +- Text.CommitDetail.Changes.Count - Text.CommitMessageTextBox.SubjectCount - Text.Configure.Git.PreferredMergeMode - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges - Text.ConfirmEmptyCommit.StageAllThenCommit - Text.ConfirmEmptyCommit.WithLocalChanges +- Text.CreateBranch.OverwriteExisting +- Text.DeinitSubmodule +- Text.DeinitSubmodule.Force +- Text.DeinitSubmodule.Path +- Text.Diff.Submodule.Deleted - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace @@ -137,6 +177,7 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Preferences.Git.IgnoreCRAtEOLInDiff +- Text.Pull.RecurseSubmodules - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName @@ -146,6 +187,10 @@ This document shows the translation status of each locale file in the repository - Text.Repository.Tags.OrderByNameDes - Text.Repository.ViewLogs - Text.Repository.Visit +- Text.ResetWithoutCheckout +- Text.ResetWithoutCheckout.MoveTo +- Text.ResetWithoutCheckout.Target +- Text.Submodule.Deinit - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -164,7 +209,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-85.57%25-yellow) +### ![pt__BR](https://img.shields.io/badge/pt__BR-84.23%25-yellow)
Missing keys in pt_BR.axaml @@ -184,6 +229,7 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.WaitingForRange - Text.BranchCM.CustomAction - Text.BranchCM.MergeMultiBranches +- Text.BranchCM.ResetToSelectedCommit - Text.BranchUpstreamInvalid - Text.Checkout.RecurseSubmodules - Text.Clone.RecurseSubmodules @@ -192,6 +238,7 @@ This document shows the translation status of each locale file in the repository - Text.CommitCM.CopySubject - Text.CommitCM.Merge - Text.CommitCM.MergeMultiple +- Text.CommitDetail.Changes.Count - Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children - Text.CommitMessageTextBox.SubjectCount @@ -206,11 +253,16 @@ This document shows the translation status of each locale file in the repository - Text.ConfirmEmptyCommit.WithLocalChanges - Text.CopyFullPath - Text.CreateBranch.Name.WarnSpace +- Text.CreateBranch.OverwriteExisting +- Text.DeinitSubmodule +- Text.DeinitSubmodule.Force +- Text.DeinitSubmodule.Path - Text.DeleteRepositoryNode.Path - Text.DeleteRepositoryNode.TipForGroup - Text.DeleteRepositoryNode.TipForRepository - Text.Diff.First - Text.Diff.Last +- Text.Diff.Submodule.Deleted - Text.Diff.UseBlockNavigation - Text.Fetch.Force - Text.FileCM.ResolveUsing @@ -238,6 +290,7 @@ This document shows the translation status of each locale file in the repository - Text.Preferences.General.ShowTagsInGraph - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Preferences.Git.SSLVerify +- Text.Pull.RecurseSubmodules - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName @@ -258,6 +311,9 @@ This document shows the translation status of each locale file in the repository - Text.Repository.UseRelativeTimeInHistories - Text.Repository.ViewLogs - Text.Repository.Visit +- Text.ResetWithoutCheckout +- Text.ResetWithoutCheckout.MoveTo +- Text.ResetWithoutCheckout.Target - Text.SetUpstream - Text.SetUpstream.Local - Text.SetUpstream.Unset @@ -266,6 +322,7 @@ This document shows the translation status of each locale file in the repository - Text.Stash.AutoRestore - Text.Stash.AutoRestore.Tip - Text.StashCM.SaveAsPatch +- Text.Submodule.Deinit - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -286,17 +343,9 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-99.75%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) -
-Missing keys in ru_RU.axaml - -- Text.Hotkeys.Global.SwitchTab -- Text.Launcher.Pages - -
- -### ![ta__IN](https://img.shields.io/badge/ta__IN-94.05%25-yellow) +### ![ta__IN](https://img.shields.io/badge/ta__IN-92.62%25-yellow)
Missing keys in ta_IN.axaml @@ -308,16 +357,23 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.Good - Text.Bisect.Skip - Text.Bisect.WaitingForRange +- Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject +- Text.CommitDetail.Changes.Count - Text.CommitMessageTextBox.SubjectCount - Text.Configure.Git.PreferredMergeMode - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges - Text.ConfirmEmptyCommit.StageAllThenCommit - Text.ConfirmEmptyCommit.WithLocalChanges +- Text.CreateBranch.OverwriteExisting +- Text.DeinitSubmodule +- Text.DeinitSubmodule.Force +- Text.DeinitSubmodule.Path +- Text.Diff.Submodule.Deleted - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace @@ -326,6 +382,7 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Preferences.Git.IgnoreCRAtEOLInDiff +- Text.Pull.RecurseSubmodules - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName @@ -333,6 +390,10 @@ This document shows the translation status of each locale file in the repository - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs - Text.Repository.Visit +- Text.ResetWithoutCheckout +- Text.ResetWithoutCheckout.MoveTo +- Text.ResetWithoutCheckout.Target +- Text.Submodule.Deinit - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited @@ -351,7 +412,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-95.19%25-yellow) +### ![uk__UA](https://img.shields.io/badge/uk__UA-93.74%25-yellow)
Missing keys in uk_UA.axaml @@ -363,12 +424,19 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.Good - Text.Bisect.Skip - Text.Bisect.WaitingForRange +- Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject +- Text.CommitDetail.Changes.Count - Text.CommitMessageTextBox.SubjectCount - Text.ConfigureWorkspace.Name +- Text.CreateBranch.OverwriteExisting +- Text.DeinitSubmodule +- Text.DeinitSubmodule.Force +- Text.DeinitSubmodule.Path +- Text.Diff.Submodule.Deleted - Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.Hotkeys.Global.SwitchWorkspace @@ -377,6 +445,7 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Preferences.Git.IgnoreCRAtEOLInDiff +- Text.Pull.RecurseSubmodules - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName @@ -384,6 +453,10 @@ This document shows the translation status of each locale file in the repository - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs - Text.Repository.Visit +- Text.ResetWithoutCheckout +- Text.ResetWithoutCheckout.MoveTo +- Text.ResetWithoutCheckout.Target +- Text.Submodule.Deinit - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited diff --git a/VERSION b/VERSION index 60bea9b2..ac98de3d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2025.18 \ No newline at end of file +2025.19 \ No newline at end of file diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 45ab0b8c..0664ee25 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -548,7 +548,7 @@ namespace SourceGit private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop) { - Native.OS.SetupEnternalTools(); + Native.OS.SetupExternalTools(); Models.AvatarManager.Instance.Start(); string startupRepo = null; @@ -671,7 +671,16 @@ namespace SourceGit prevChar = c; } - trimmed.Add(sb.ToString()); + var name = sb.ToString(); + var idx = name.IndexOf('#'); + if (idx >= 0) + { + if (!name.Equals("fonts:Inter#Inter", StringComparison.Ordinal) && + !name.Equals("fonts:SourceGit#JetBrains Mono", StringComparison.Ordinal)) + continue; + } + + trimmed.Add(name); } return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty; diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs index 9c396215..0d1b1f8f 100644 --- a/src/Commands/Branch.cs +++ b/src/Commands/Branch.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Text; + +namespace SourceGit.Commands { public static class Branch { @@ -11,12 +13,20 @@ return cmd.ReadToEnd().StdOut.Trim(); } - public static bool Create(string repo, string name, string basedOn, Models.ICommandLog log) + public static bool Create(string repo, string name, string basedOn, bool force, Models.ICommandLog log) { + var builder = new StringBuilder(); + builder.Append("branch "); + if (force) + builder.Append("-f "); + builder.Append(name); + builder.Append(" "); + builder.Append(basedOn); + var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; - cmd.Args = $"branch {name} {basedOn}"; + cmd.Args = builder.ToString(); cmd.Log = log; return cmd.Exec(); } diff --git a/src/Commands/Checkout.cs b/src/Commands/Checkout.cs index 6f63ae60..aa386c2f 100644 --- a/src/Commands/Checkout.cs +++ b/src/Commands/Checkout.cs @@ -13,15 +13,28 @@ namespace SourceGit.Commands public bool Branch(string branch, bool force) { - var option = force ? "--force" : string.Empty; - Args = $"checkout {option} --progress {branch}"; + var builder = new StringBuilder(); + builder.Append("checkout --progress "); + if (force) + builder.Append("--force "); + builder.Append(branch); + + Args = builder.ToString(); return Exec(); } - public bool Branch(string branch, string basedOn, bool force) + public bool Branch(string branch, string basedOn, bool force, bool allowOverwrite) { - var option = force ? "--force" : string.Empty; - Args = $"checkout --progress -b {branch} {basedOn}"; + var builder = new StringBuilder(); + builder.Append("checkout --progress "); + builder.Append(allowOverwrite ? "-B " : "-b "); + if (force) + builder.Append("--force "); + builder.Append(branch); + builder.Append(" "); + builder.Append(basedOn); + + Args = builder.ToString(); return Exec(); } diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 699cc120..9bfa1c15 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -36,44 +36,14 @@ namespace SourceGit.Commands public bool Exec() { + Log?.AppendLine($"$ git {Args}\n"); + var start = CreateGitStartInfo(); var errs = new List(); var proc = new Process() { StartInfo = start }; - Log?.AppendLine($"$ git {Args}\n"); - - proc.OutputDataReceived += (_, e) => - { - if (e.Data == null) - return; - - Log?.AppendLine(e.Data); - }; - - proc.ErrorDataReceived += (_, e) => - { - if (string.IsNullOrEmpty(e.Data)) - { - errs.Add(string.Empty); - return; - } - - Log?.AppendLine(e.Data); - - // Ignore progress messages - if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal)) - return; - if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) - return; - if (e.Data.StartsWith("remote: Compressing objects:", StringComparison.Ordinal)) - return; - if (e.Data.StartsWith("Filtering content:", StringComparison.Ordinal)) - return; - if (REG_PROGRESS().IsMatch(e.Data)) - return; - - errs.Add(e.Data); - }; + proc.OutputDataReceived += (_, e) => HandleOutput(e.Data, errs); + proc.ErrorDataReceived += (_, e) => HandleOutput(e.Data, errs); var dummy = null as Process; var dummyProcLock = new object(); @@ -222,6 +192,28 @@ namespace SourceGit.Commands return start; } + private void HandleOutput(string line, List errs) + { + line = line ?? string.Empty; + Log?.AppendLine(line); + + // Lines to hide in error message. + if (line.Length > 0) + { + if (line.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal) || + line.StartsWith("remote: Counting objects:", StringComparison.Ordinal) || + line.StartsWith("remote: Compressing objects:", StringComparison.Ordinal) || + line.StartsWith("Filtering content:", StringComparison.Ordinal) || + line.StartsWith("hint:", StringComparison.Ordinal)) + return; + + if (REG_PROGRESS().IsMatch(line)) + return; + } + + errs.Add(line); + } + [GeneratedRegex(@"\d+%")] private static partial Regex REG_PROGRESS(); } diff --git a/src/Commands/ExecuteCustomAction.cs b/src/Commands/ExecuteCustomAction.cs index 000c8fd1..e59bc068 100644 --- a/src/Commands/ExecuteCustomAction.cs +++ b/src/Commands/ExecuteCustomAction.cs @@ -27,7 +27,7 @@ namespace SourceGit.Commands } } - public static void RunAndWait(string repo, string file, string args, Action outputHandler) + public static void RunAndWait(string repo, string file, string args, Models.ICommandLog log) { var start = new ProcessStartInfo(); start.FileName = file; @@ -40,20 +40,22 @@ namespace SourceGit.Commands start.StandardErrorEncoding = Encoding.UTF8; start.WorkingDirectory = repo; + log?.AppendLine($"$ {file} {args}\n"); + var proc = new Process() { StartInfo = start }; var builder = new StringBuilder(); proc.OutputDataReceived += (_, e) => { if (e.Data != null) - outputHandler?.Invoke(e.Data); + log?.AppendLine(e.Data); }; proc.ErrorDataReceived += (_, e) => { if (e.Data != null) { - outputHandler?.Invoke(e.Data); + log?.AppendLine(e.Data); builder.AppendLine(e.Data); } }; diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs index e4fab235..1d33fa3a 100644 --- a/src/Commands/GitFlow.cs +++ b/src/Commands/GitFlow.cs @@ -1,53 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Text; - +using System.Text; using Avalonia.Threading; namespace SourceGit.Commands { public static class GitFlow { - public class BranchDetectResult + public static bool Init(string repo, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log) { - public bool IsGitFlowBranch { get; set; } = false; - public string Type { get; set; } = string.Empty; - public string Prefix { get; set; } = string.Empty; - } - - public static bool IsEnabled(string repo, List branches) - { - var localBrancheNames = new HashSet(); - foreach (var branch in branches) - { - if (branch.IsLocal) - localBrancheNames.Add(branch.Name); - } - - var config = new Config(repo).ListAll(); - if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master)) - return false; - - if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop)) - return false; - - return config.ContainsKey("gitflow.prefix.feature") && - config.ContainsKey("gitflow.prefix.release") && - config.ContainsKey("gitflow.prefix.hotfix"); - } - - public static bool Init(string repo, List branches, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log) - { - var current = branches.Find(x => x.IsCurrent); - - var masterBranch = branches.Find(x => x.Name == master); - if (masterBranch == null && current != null) - Branch.Create(repo, master, current.Head, log); - - var devBranch = branches.Find(x => x.Name == develop); - if (devBranch == null && current != null) - Branch.Create(repo, develop, current.Head, log); - var config = new Config(repo); config.Set("gitflow.branch.master", master); config.Set("gitflow.branch.develop", develop); @@ -66,90 +25,53 @@ namespace SourceGit.Commands return init.Exec(); } - public static string GetPrefix(string repo, string type) + public static bool Start(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log) { - return new Config(repo).Get($"gitflow.prefix.{type}"); - } - - public static BranchDetectResult DetectType(string repo, List branches, string branch) - { - var rs = new BranchDetectResult(); - var localBrancheNames = new HashSet(); - foreach (var b in branches) - { - if (b.IsLocal) - localBrancheNames.Add(b.Name); - } - - var config = new Config(repo).ListAll(); - if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master)) - return rs; - - if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop)) - return rs; - - if (!config.TryGetValue("gitflow.prefix.feature", out var feature) || - !config.TryGetValue("gitflow.prefix.release", out var release) || - !config.TryGetValue("gitflow.prefix.hotfix", out var hotfix)) - return rs; - - if (branch.StartsWith(feature, StringComparison.Ordinal)) - { - rs.IsGitFlowBranch = true; - rs.Type = "feature"; - rs.Prefix = feature; - } - else if (branch.StartsWith(release, StringComparison.Ordinal)) - { - rs.IsGitFlowBranch = true; - rs.Type = "release"; - rs.Prefix = release; - } - else if (branch.StartsWith(hotfix, StringComparison.Ordinal)) - { - rs.IsGitFlowBranch = true; - rs.Type = "hotfix"; - rs.Prefix = hotfix; - } - - return rs; - } - - public static bool Start(string repo, string type, string name, Models.ICommandLog log) - { - if (!SUPPORTED_BRANCH_TYPES.Contains(type)) - { - Dispatcher.UIThread.Post(() => - { - App.RaiseException(repo, "Bad branch type!!!"); - }); - - return false; - } - var start = new Command(); start.WorkingDirectory = repo; start.Context = repo; - start.Args = $"flow {type} start {name}"; + + switch (type) + { + case Models.GitFlowBranchType.Feature: + start.Args = $"flow feature start {name}"; + break; + case Models.GitFlowBranchType.Release: + start.Args = $"flow release start {name}"; + break; + case Models.GitFlowBranchType.Hotfix: + start.Args = $"flow hotfix start {name}"; + break; + default: + Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!")); + return false; + } + start.Log = log; return start.Exec(); } - public static bool Finish(string repo, string type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log) + public static bool Finish(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log) { - if (!SUPPORTED_BRANCH_TYPES.Contains(type)) - { - Dispatcher.UIThread.Post(() => - { - App.RaiseException(repo, "Bad branch type!!!"); - }); - - return false; - } - var builder = new StringBuilder(); builder.Append("flow "); - builder.Append(type); + + switch (type) + { + case Models.GitFlowBranchType.Feature: + builder.Append("feature"); + break; + case Models.GitFlowBranchType.Release: + builder.Append("release"); + break; + case Models.GitFlowBranchType.Hotfix: + builder.Append("hotfix"); + break; + default: + Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!")); + return false; + } + builder.Append(" finish "); if (squash) builder.Append("--squash "); @@ -166,14 +88,5 @@ namespace SourceGit.Commands finish.Log = log; return finish.Exec(); } - - private static readonly List SUPPORTED_BRANCH_TYPES = new List() - { - "feature", - "release", - "bugfix", - "hotfix", - "support", - }; } } diff --git a/src/Commands/IsBinary.cs b/src/Commands/IsBinary.cs index de59b5a4..af8f54bb 100644 --- a/src/Commands/IsBinary.cs +++ b/src/Commands/IsBinary.cs @@ -11,7 +11,7 @@ namespace SourceGit.Commands { WorkingDirectory = repo; Context = repo; - Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\""; + Args = $"diff {Models.Commit.EmptyTreeSHA1} {commit} --numstat -- \"{path}\""; RaiseError = false; } diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs index 2695b16b..698fbfce 100644 --- a/src/Commands/Pull.cs +++ b/src/Commands/Pull.cs @@ -2,7 +2,7 @@ { public class Pull : Command { - public Pull(string repo, string remote, string branch, bool useRebase, bool noTags) + public Pull(string repo, string remote, string branch, bool useRebase) { WorkingDirectory = repo; Context = repo; @@ -12,9 +12,6 @@ if (useRebase) Args += "--rebase=true "; - if (noTags) - Args += "--no-tags "; - Args += $"{remote} {branch}"; } } diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs index 39794090..910e5fd2 100644 --- a/src/Commands/QueryBranches.cs +++ b/src/Commands/QueryBranches.cs @@ -27,7 +27,7 @@ namespace SourceGit.Commands return branches; var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); - var remoteBranches = new HashSet(); + var remoteHeads = new Dictionary(); foreach (var line in lines) { var b = ParseLine(line); @@ -35,7 +35,7 @@ namespace SourceGit.Commands { branches.Add(b); if (!b.IsLocal) - remoteBranches.Add(b.FullName); + remoteHeads.Add(b.FullName, b.Head); else localBranchesCount++; } @@ -44,7 +44,22 @@ namespace SourceGit.Commands foreach (var b in branches) { if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream)) - b.IsUpstreamGone = !remoteBranches.Contains(b.Upstream); + { + if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead)) + { + b.IsUpstreamGone = false; + + if (b.TrackStatus == null) + b.TrackStatus = new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).Result(); + } + else + { + b.IsUpstreamGone = true; + + if (b.TrackStatus == null) + b.TrackStatus = new Models.BranchTrackStatus(); + } + } } return branches; @@ -93,9 +108,10 @@ namespace SourceGit.Commands branch.Upstream = parts[4]; branch.IsUpstreamGone = false; - if (branch.IsLocal && !string.IsNullOrEmpty(parts[5]) && !parts[5].Equals("=", StringComparison.Ordinal)) - branch.TrackStatus = new QueryTrackStatus(WorkingDirectory, branch.Name, branch.Upstream).Result(); - else + if (!branch.IsLocal || + string.IsNullOrEmpty(branch.Upstream) || + string.IsNullOrEmpty(parts[5]) || + parts[5].Equals("=", StringComparison.Ordinal)) branch.TrackStatus = new Models.BranchTrackStatus(); return branch; diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs index 404f5be6..9abf433e 100644 --- a/src/Commands/QueryLocalChanges.cs +++ b/src/Commands/QueryLocalChanges.cs @@ -128,30 +128,16 @@ namespace SourceGit.Commands change.Set(Models.ChangeState.Deleted, Models.ChangeState.Copied); break; case "DD": - change.Set(Models.ChangeState.Deleted, Models.ChangeState.Deleted); - break; case "AU": - change.Set(Models.ChangeState.Added, Models.ChangeState.Unmerged); - break; case "UD": - change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Deleted); - break; case "UA": - change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Added); - break; case "DU": - change.Set(Models.ChangeState.Deleted, Models.ChangeState.Unmerged); - break; case "AA": - change.Set(Models.ChangeState.Added, Models.ChangeState.Added); - break; case "UU": - change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Unmerged); + change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted); break; case "??": - change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked); - break; - default: + change.Set(Models.ChangeState.None, Models.ChangeState.Untracked); break; } diff --git a/src/Commands/QueryStagedChangesWithAmend.cs b/src/Commands/QueryStagedChangesWithAmend.cs index 8f8456c9..b20c20dc 100644 --- a/src/Commands/QueryStagedChangesWithAmend.cs +++ b/src/Commands/QueryStagedChangesWithAmend.cs @@ -6,7 +6,7 @@ namespace SourceGit.Commands { public partial class QueryStagedChangesWithAmend : Command { - [GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMTUX])\d{0,6}\t(.*)$")] + [GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMT])\d{0,6}\t(.*)$")] private static partial Regex REG_FORMAT1(); [GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} R\d{0,6}\t(.*\t.*)$")] private static partial Regex REG_FORMAT2(); @@ -22,76 +22,71 @@ namespace SourceGit.Commands public List Result() { var rs = ReadToEnd(); - if (rs.IsSuccess) + if (!rs.IsSuccess) + return []; + + var changes = new List(); + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) { - var changes = new List(); - var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); - foreach (var line in lines) + var match = REG_FORMAT2().Match(line); + if (match.Success) { - var match = REG_FORMAT2().Match(line); - if (match.Success) + var change = new Models.Change() { - var change = new Models.Change() + Path = match.Groups[3].Value, + DataForAmend = new Models.ChangeDataForAmend() { - Path = match.Groups[3].Value, - DataForAmend = new Models.ChangeDataForAmend() - { - FileMode = match.Groups[1].Value, - ObjectHash = match.Groups[2].Value, - ParentSHA = _parent, - }, - }; - change.Set(Models.ChangeState.Renamed); - changes.Add(change); - continue; - } - - match = REG_FORMAT1().Match(line); - if (match.Success) - { - var change = new Models.Change() - { - Path = match.Groups[4].Value, - DataForAmend = new Models.ChangeDataForAmend() - { - FileMode = match.Groups[1].Value, - ObjectHash = match.Groups[2].Value, - ParentSHA = _parent, - }, - }; - - var type = match.Groups[3].Value; - switch (type) - { - case "A": - change.Set(Models.ChangeState.Added); - break; - case "C": - change.Set(Models.ChangeState.Copied); - break; - case "D": - change.Set(Models.ChangeState.Deleted); - break; - case "M": - change.Set(Models.ChangeState.Modified); - break; - case "T": - change.Set(Models.ChangeState.TypeChanged); - break; - case "U": - change.Set(Models.ChangeState.Unmerged); - break; - } - changes.Add(change); - } + FileMode = match.Groups[1].Value, + ObjectHash = match.Groups[2].Value, + ParentSHA = _parent, + }, + }; + change.Set(Models.ChangeState.Renamed); + changes.Add(change); + continue; } - return changes; + match = REG_FORMAT1().Match(line); + if (match.Success) + { + var change = new Models.Change() + { + Path = match.Groups[4].Value, + DataForAmend = new Models.ChangeDataForAmend() + { + FileMode = match.Groups[1].Value, + ObjectHash = match.Groups[2].Value, + ParentSHA = _parent, + }, + }; + + var type = match.Groups[3].Value; + switch (type) + { + case "A": + change.Set(Models.ChangeState.Added); + break; + case "C": + change.Set(Models.ChangeState.Copied); + break; + case "D": + change.Set(Models.ChangeState.Deleted); + break; + case "M": + change.Set(Models.ChangeState.Modified); + break; + case "T": + change.Set(Models.ChangeState.TypeChanged); + break; + } + changes.Add(change); + } } - return []; + return changes; } - private string _parent = string.Empty; + private readonly string _parent; } } diff --git a/src/Commands/QuerySubmodules.cs b/src/Commands/QuerySubmodules.cs index 86147f97..663c0ea0 100644 --- a/src/Commands/QuerySubmodules.cs +++ b/src/Commands/QuerySubmodules.cs @@ -112,7 +112,7 @@ namespace SourceGit.Commands } } - Args = $"--no-optional-locks status -uno --porcelain -- {builder}"; + Args = $"--no-optional-locks status --porcelain -- {builder}"; rs = ReadToEnd(); if (!rs.IsSuccess) return submodules; diff --git a/src/Commands/QueryUpdatableSubmodules.cs b/src/Commands/QueryUpdatableSubmodules.cs new file mode 100644 index 00000000..03f4a24d --- /dev/null +++ b/src/Commands/QueryUpdatableSubmodules.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace SourceGit.Commands +{ + public partial class QueryUpdatableSubmodules : Command + { + [GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")] + private static partial Regex REG_FORMAT_STATUS(); + + public QueryUpdatableSubmodules(string repo) + { + WorkingDirectory = repo; + Context = repo; + Args = "submodule status"; + } + + public List Result() + { + var submodules = new List(); + var rs = ReadToEnd(); + + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + var match = REG_FORMAT_STATUS().Match(line); + if (match.Success) + { + var stat = match.Groups[1].Value; + var path = match.Groups[3].Value; + if (!stat.StartsWith(' ')) + submodules.Add(path); + } + } + + return submodules; + } + } +} diff --git a/src/Commands/Submodule.cs b/src/Commands/Submodule.cs index c8b676ea..025d035a 100644 --- a/src/Commands/Submodule.cs +++ b/src/Commands/Submodule.cs @@ -13,7 +13,7 @@ namespace SourceGit.Commands public bool Add(string url, string relativePath, bool recursive) { - Args = $"submodule add {url} \"{relativePath}\""; + Args = $"-c protocol.file.allow=always submodule add \"{url}\" \"{relativePath}\""; if (!Exec()) return false; @@ -29,23 +29,7 @@ namespace SourceGit.Commands } } - public bool Update(string module, bool init, bool recursive, bool useRemote) - { - Args = "submodule update"; - - if (init) - Args += " --init"; - if (recursive) - Args += " --recursive"; - if (useRemote) - Args += " --remote"; - if (!string.IsNullOrEmpty(module)) - Args += $" -- \"{module}\""; - - return Exec(); - } - - public bool Update(List modules, bool init, bool recursive, bool useRemote) + public bool Update(List modules, bool init, bool recursive, bool useRemote = false) { var builder = new StringBuilder(); builder.Append("submodule update"); @@ -60,20 +44,22 @@ namespace SourceGit.Commands { builder.Append(" --"); foreach (var module in modules) - builder.Append($" \"{module.Path}\""); + builder.Append($" \"{module}\""); } Args = builder.ToString(); return Exec(); } - public bool Delete(string relativePath) + public bool Deinit(string module, bool force) { - Args = $"submodule deinit -f \"{relativePath}\""; - if (!Exec()) - return false; + Args = force ? $"submodule deinit -f -- \"{module}\"" : $"submodule deinit -- \"{module}\""; + return Exec(); + } - Args = $"rm -rf \"{relativePath}\""; + public bool Delete(string module) + { + Args = $"rm -rf \"{module}\""; return Exec(); } } diff --git a/src/Commands/UpdateRef.cs b/src/Commands/UpdateRef.cs deleted file mode 100644 index 1e7bb239..00000000 --- a/src/Commands/UpdateRef.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace SourceGit.Commands -{ - public class UpdateRef : Command - { - public UpdateRef(string repo, string refName, string toRevision) - { - WorkingDirectory = repo; - Context = repo; - Args = $"update-ref {refName} {toRevision}"; - } - } -} diff --git a/src/Converters/ListConverters.cs b/src/Converters/ListConverters.cs index e0c5967e..6f3ae98b 100644 --- a/src/Converters/ListConverters.cs +++ b/src/Converters/ListConverters.cs @@ -7,6 +7,9 @@ namespace SourceGit.Converters { public static class ListConverters { + public static readonly FuncValueConverter Count = + new FuncValueConverter(v => v == null ? "0" : $"{v.Count}"); + public static readonly FuncValueConverter ToCount = new FuncValueConverter(v => v == null ? "(0)" : $"({v.Count})"); diff --git a/src/Models/Change.cs b/src/Models/Change.cs index 0c96ec95..7d772d3e 100644 --- a/src/Models/Change.cs +++ b/src/Models/Change.cs @@ -18,8 +18,8 @@ namespace SourceGit.Models Deleted, Renamed, Copied, - Unmerged, - Untracked + Untracked, + Conflicted, } public class ChangeDataForAmend @@ -36,20 +36,7 @@ namespace SourceGit.Models public string Path { get; set; } = ""; public string OriginalPath { get; set; } = ""; public ChangeDataForAmend DataForAmend { get; set; } = null; - - public bool IsConflict - { - get - { - if (Index == ChangeState.Unmerged || WorkTree == ChangeState.Unmerged) - return true; - if (Index == ChangeState.Added && WorkTree == ChangeState.Added) - return true; - if (Index == ChangeState.Deleted && WorkTree == ChangeState.Deleted) - return true; - return false; - } - } + public bool IsConflicted => WorkTree == ChangeState.Conflicted; public void Set(ChangeState index, ChangeState workTree = ChangeState.None) { @@ -77,6 +64,7 @@ namespace SourceGit.Models if (Path[0] == '"') Path = Path.Substring(1, Path.Length - 2); + if (!string.IsNullOrEmpty(OriginalPath) && OriginalPath[0] == '"') OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2); } diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index ef5a34bc..d8eb8ab6 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -18,6 +18,9 @@ namespace SourceGit.Models public class Commit { + // As retrieved by: git mktree public DiffOption(Commit commit, Change change) { - var baseRevision = commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : $"{commit.SHA}^"; + var baseRevision = commit.Parents.Count == 0 ? Commit.EmptyTreeSHA1 : $"{commit.SHA}^"; _revisions.Add(baseRevision); _revisions.Add(commit.SHA); _path = change.Path; @@ -79,7 +79,7 @@ namespace SourceGit.Models /// public DiffOption(Commit commit, string file) { - var baseRevision = commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : $"{commit.SHA}^"; + var baseRevision = commit.Parents.Count == 0 ? Commit.EmptyTreeSHA1 : $"{commit.SHA}^"; _revisions.Add(baseRevision); _revisions.Add(commit.SHA); _path = file; @@ -124,6 +124,6 @@ namespace SourceGit.Models private readonly string _path; private readonly string _orgPath = string.Empty; private readonly string _extra = string.Empty; - private readonly List _revisions = new List(); + private readonly List _revisions = []; } } diff --git a/src/Models/GitFlow.cs b/src/Models/GitFlow.cs new file mode 100644 index 00000000..5d26072b --- /dev/null +++ b/src/Models/GitFlow.cs @@ -0,0 +1,46 @@ +namespace SourceGit.Models +{ + public enum GitFlowBranchType + { + None = 0, + Feature, + Release, + Hotfix, + } + + public class GitFlow + { + public string Master { get; set; } = string.Empty; + public string Develop { get; set; } = string.Empty; + public string FeaturePrefix { get; set; } = string.Empty; + public string ReleasePrefix { get; set; } = string.Empty; + public string HotfixPrefix { get; set; } = string.Empty; + + public bool IsValid + { + get + { + return !string.IsNullOrEmpty(Master) && + !string.IsNullOrEmpty(Develop) && + !string.IsNullOrEmpty(FeaturePrefix) && + !string.IsNullOrEmpty(ReleasePrefix) && + !string.IsNullOrEmpty(HotfixPrefix); + } + } + + public string GetPrefix(GitFlowBranchType type) + { + switch (type) + { + case GitFlowBranchType.Feature: + return FeaturePrefix; + case GitFlowBranchType.Release: + return ReleasePrefix; + case GitFlowBranchType.Hotfix: + return HotfixPrefix; + default: + return string.Empty; + } + } + } +} diff --git a/src/Models/IRepository.cs b/src/Models/IRepository.cs index 0224d81f..2fc7c612 100644 --- a/src/Models/IRepository.cs +++ b/src/Models/IRepository.cs @@ -2,6 +2,8 @@ { public interface IRepository { + bool MayHaveSubmodules(); + void RefreshBranches(); void RefreshWorktrees(); void RefreshTags(); diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs index ec9b8f20..6e36cfb9 100644 --- a/src/Models/Remote.cs +++ b/src/Models/Remote.cs @@ -6,8 +6,10 @@ namespace SourceGit.Models { public partial class Remote { - [GeneratedRegex(@"^https?://([-a-zA-Z0-9:%._\+~#=]+@)?[-a-zA-Z0-9:%._\+~#=]{1,256}(\.[a-zA-Z0-9()]{1,6})?(:[0-9]{1,5})?\b(/[-a-zA-Z0-9()@:%_\+.~#?&=]+)+(\.git)?$")] + [GeneratedRegex(@"^https?://[^/]+/.+[^/\.]$")] private static partial Regex REG_HTTPS(); + [GeneratedRegex(@"^git://[^/]+/.+[^/\.]$")] + private static partial Regex REG_GIT(); [GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")] private static partial Regex REG_SSH1(); [GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")] @@ -18,6 +20,7 @@ namespace SourceGit.Models private static readonly Regex[] URL_FORMATS = [ REG_HTTPS(), + REG_GIT(), REG_SSH1(), REG_SSH2(), ]; @@ -30,13 +33,10 @@ namespace SourceGit.Models if (string.IsNullOrWhiteSpace(url)) return false; - for (int i = 1; i < URL_FORMATS.Length; i++) - { - if (URL_FORMATS[i].IsMatch(url)) - return true; - } + if (REG_SSH1().IsMatch(url)) + return true; - return false; + return REG_SSH2().IsMatch(url); } public static bool IsValidURL(string url) @@ -50,7 +50,10 @@ namespace SourceGit.Models return true; } - return url.EndsWith(".git", StringComparison.Ordinal) && Directory.Exists(url); + return url.StartsWith("file://", StringComparison.Ordinal) || + url.StartsWith("./", StringComparison.Ordinal) || + url.StartsWith("../", StringComparison.Ordinal) || + Directory.Exists(url); } public bool TryGetVisitURL(out string url) diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs index 9af032bb..34189073 100644 --- a/src/Models/RepositorySettings.cs +++ b/src/Models/RepositorySettings.cs @@ -80,18 +80,6 @@ namespace SourceGit.Models set; } = true; - public bool FetchWithoutTagsOnPull - { - get; - set; - } = false; - - public bool FetchAllBranchesOnPull - { - get; - set; - } = true; - public bool CheckSubmodulesOnPush { get; @@ -417,6 +405,7 @@ namespace SourceGit.Models public void PushCommitMessage(string message) { + message = message.Trim().ReplaceLineEndings("\n"); var existIdx = CommitMessages.IndexOf(message); if (existIdx == 0) return; diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index e930f412..928951ca 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -107,7 +107,6 @@ namespace SourceGit.Models { _updateBranch = 0; _updateWC = 0; - _updateSubmodules = 0; if (_updateTags > 0) { @@ -115,10 +114,15 @@ namespace SourceGit.Models Task.Run(_repo.RefreshTags); } + if (_updateSubmodules > 0 || _repo.MayHaveSubmodules()) + { + _updateSubmodules = 0; + Task.Run(_repo.RefreshSubmodules); + } + Task.Run(_repo.RefreshBranches); Task.Run(_repo.RefreshCommits); Task.Run(_repo.RefreshWorkingCopyChanges); - Task.Run(_repo.RefreshSubmodules); Task.Run(_repo.RefreshWorktrees); } @@ -150,14 +154,29 @@ namespace SourceGit.Models private void OnRepositoryChanged(object o, FileSystemEventArgs e) { - if (string.IsNullOrEmpty(e.Name) || e.Name.EndsWith(".lock", StringComparison.Ordinal)) + if (string.IsNullOrEmpty(e.Name)) return; var name = e.Name.Replace("\\", "/"); - if (name.StartsWith("modules", StringComparison.Ordinal) && name.EndsWith("HEAD", StringComparison.Ordinal)) + if (name.Contains("fsmonitor--daemon/", StringComparison.Ordinal) || + name.EndsWith(".lock", StringComparison.Ordinal) || + name.StartsWith("lfs/", StringComparison.Ordinal)) + return; + + if (name.StartsWith("modules", StringComparison.Ordinal)) { - _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); - _updateWC = DateTime.Now.AddSeconds(1).ToFileTime(); + if (name.EndsWith("/HEAD", StringComparison.Ordinal) || + name.EndsWith("/ORIG_HEAD", StringComparison.Ordinal)) + { + _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); + _updateWC = DateTime.Now.AddSeconds(1).ToFileTime(); + } + } + else if (name.Equals("MERGE_HEAD", StringComparison.Ordinal) || + name.Equals("AUTO_MERGE", StringComparison.Ordinal)) + { + if (_repo.MayHaveSubmodules()) + _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); } else if (name.StartsWith("refs/tags", StringComparison.Ordinal)) { @@ -187,9 +206,20 @@ namespace SourceGit.Models return; var name = e.Name.Replace("\\", "/"); - if (name == ".git" || name.StartsWith(".git/", StringComparison.Ordinal)) + if (name.Equals(".git", StringComparison.Ordinal) || + name.StartsWith(".git/", StringComparison.Ordinal) || + name.EndsWith("/.git", StringComparison.Ordinal)) return; + if (name.StartsWith(".vs/", StringComparison.Ordinal)) + return; + + if (name.Equals(".gitmodules", StringComparison.Ordinal)) + { + _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); + return; + } + lock (_lockSubmodule) { foreach (var submodule in _submodules) diff --git a/src/Native/OS.cs b/src/Native/OS.cs index 4c891283..ad6f8104 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -124,7 +124,7 @@ namespace SourceGit.Native Directory.CreateDirectory(DataDir); } - public static void SetupEnternalTools() + public static void SetupExternalTools() { ExternalTools = _backend.FindExternalTools(); } diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 30750931..886a07cf 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -292,7 +292,6 @@ Ausgewähltes Repository bearbeiten Führe benutzerdefinierte Aktion aus Name der Aktion: - Fast-Forward (ohne Auschecken) Fetch Alle Remotes fetchen Aktiviere '--force' Option @@ -527,12 +526,10 @@ Worktree Informationen in `$GIT_COMMON_DIR/worktrees` löschen Pull Remote-Branch: - Alle Branches fetchen Lokaler Branch: Lokale Änderungen: Verwerfen Stashen & wieder anwenden - Ohne Tags fetchen Remote: Pull (Fetch & Merge) Rebase anstatt Merge verwenden diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index bb259272..c12396ec 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -63,6 +63,7 @@ Push ${0}$ Rebase ${0}$ on ${1}$... Rename ${0}$... + Reset ${0}$ to ${1}$... Set Tracking Branch... Branch Compare Invalid upstream! @@ -124,6 +125,7 @@ Squash into Parent Squash Children into Here CHANGES + changed file(s) Search Changes... FILES LFS File @@ -215,6 +217,7 @@ Enter branch name. Spaces will be replaced with dashes. Create Local Branch + Overwrite existing branch Create Tag... New Tag At: GPG signing @@ -229,6 +232,9 @@ lightweight Hold Ctrl to start directly Cut + De-initialize Submodule + Force de-init even if it contains local changes. + Submodule: Delete Branch Branch: You are about to delete a remote branch!!! @@ -264,6 +270,7 @@ Show hidden symbols Side-By-Side Diff SUBMODULE + DELETED NEW Swap Syntax Highlighting @@ -288,7 +295,6 @@ Edit Selected Repository Run Custom Action Action Name: - Fast-Forward (without checkout) Fetch Fetch all remotes Force override local refs @@ -530,12 +536,11 @@ Prune worktree information in `$GIT_COMMON_DIR/worktrees` Pull Remote Branch: - Fetch all branches Into: Local Changes: Discard Stash & Reapply - Fetch without tags + Update all submodules Remote: Pull (Fetch & Merge) Use rebase instead of merge @@ -646,6 +651,9 @@ Reset Mode: Move To: Current Branch: + Reset Branch (Without Checkout) + Move To: + Branch: Reveal in File Explorer Revert Commit Commit: @@ -705,6 +713,7 @@ SUBMODULES Add Submodule Copy Relative Path + De-initialize Submodule Fetch nested submodules Open Submodule Repository Relative Path: diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index e2b16329..513eeb06 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -67,6 +67,7 @@ Push ${0}$ Rebase ${0}$ en ${1}$... Renombrar ${0}$... + Resetear ${0}$ a ${1}$... Establecer Rama de Seguimiento... Comparar Ramas ¡Upstream inválido! @@ -128,6 +129,7 @@ Squash en Parent Squash Commits Hijos hasta Aquí CAMBIOS + archivo(s) modificado(s) Buscar Cambios... ARCHIVOS Archivo LFS @@ -233,6 +235,9 @@ ligera Mantenga Ctrl para iniciar directamente Cortar + Desinicializar Submódulo + Forzar desinicialización incluso si contiene cambios locales. + Submódulo: Eliminar Rama Rama: ¡Estás a punto de eliminar una rama remota! @@ -268,6 +273,7 @@ Mostrar símbolos ocultos Diferencia Lado a Lado SUBMÓDULO + BORRADO NUEVO Intercambiar Resaltado de Sintaxis @@ -292,7 +298,6 @@ Editar Repositorio Seleccionado Ejecutar Acción Personalizada Nombre de la Acción: - Fast-Forward (sin checkout) Fetch Fetch todos los remotos Utilizar opción '--force' @@ -390,6 +395,8 @@ Ir a la página anterior Crear nueva página Abrir diálogo de preferencias + Cambiar espacio de trabajo activo + Cambiar página activa REPOSITORIO Commit cambios staged Commit y push cambios staged @@ -432,6 +439,8 @@ Abrir en el Navegador ERROR AVISO + Espacios de trabajo + Páginas Merge Rama En: Opción de Merge: @@ -530,12 +539,11 @@ Podar información de worktree en `$GIT_COMMON_DIR/worktrees` Pull Rama Remota: - Fetch todas las ramas En: Cambios Locales: Descartar Stash & Reaplicar - Fetch sin etiquetas + Actualizar todos los submódulos Remoto: Pull (Fetch & Merge) Usar rebase en lugar de merge @@ -646,6 +654,9 @@ Modo de Reset: Mover a: Rama Actual: + Resetear Rama (Sin hacer Checkout) + Mover A: + Rama: Revelar en el Explorador de Archivos Revertir Commit Commit: @@ -705,6 +716,7 @@ SUBMÓDULOS Añadir Submódulo Copiar Ruta Relativa + Desinicializar Submódulo Fetch submódulos anidados Abrir Repositorio del Submódulo Ruta Relativa: diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 0529702d..0c3ab91b 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -275,7 +275,6 @@ Éditer le dépôt sélectionné Lancer action personnalisée Nom de l'action : - Fast-Forward (sans récupération) Fetch Fetch toutes les branches distantes Outrepasser les vérifications de refs @@ -509,12 +508,10 @@ Élaguer les information de worktree dans `$GIT_COMMON_DIR/worktrees` Pull Branche distante : - Fetch toutes les branches Dans : Changements locaux : Rejeter Stash & Réappliquer - Fetch sans les tags Dépôt distant : Pull (Fetch & Merge) Utiliser rebase au lieu de merge diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index 05fee42b..23c31c6e 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -292,7 +292,6 @@ Modifica Repository Selezionato Esegui Azione Personalizzata Nome Azione: - Avanzamento Veloce (senza verifica) Recupera Recupera da tutti i remoti Forza la sovrascrittura dei riferimenti locali @@ -530,12 +529,10 @@ Potatura delle informazioni di worktree in `$GIT_COMMON_DIR/worktrees` Scarica Branch Remoto: - Recupera tutti i branch In: Modifiche Locali: Scarta Stasha e Riapplica - Recupera senza tag Remoto: Scarica (Recupera e Unisci) Riallineare anziché unire diff --git a/src/Resources/Locales/ja_JP.axaml b/src/Resources/Locales/ja_JP.axaml index 48d55500..ca29b985 100644 --- a/src/Resources/Locales/ja_JP.axaml +++ b/src/Resources/Locales/ja_JP.axaml @@ -275,7 +275,6 @@ 選択中のリポジトリを編集 カスタムアクションを実行 アクション名: - (チェックアウトせずに)ブランチを早送りする フェッチ すべてのリモートをフェッチ ローカル参照を強制的に上書き @@ -509,12 +508,10 @@ `$GIT_DIR/worktrees` の作業ツリー情報を削除 プル ブランチ: - すべてのブランチをフェッチ 宛先: ローカルの変更: 破棄 スタッシュして再適用 - タグなしでフェッチ リモート: プル (フェッチ & マージ) マージの代わりにリベースを使用 diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 2a693151..7ce861e9 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -249,7 +249,6 @@ Editar Repositório Selecionado Executar ação customizada Nome da ação: - Fast-Forward (sem checkout) Buscar Buscar todos os remotos Buscar sem tags @@ -465,12 +464,10 @@ Podar informações de worktree em `$GIT_COMMON_DIR/worktrees` Puxar Branch Remoto: - Buscar todos os branches Para: Alterações Locais: Descartar Guardar & Reaplicar - Buscar sem tags Remoto: Puxar (Buscar & Mesclar) Usar rebase em vez de merge diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 77a7ba4f..bc72fd65 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -67,6 +67,7 @@ Выложить ${0}$ Переместить ${0}$ на ${1}$... Переименовать ${0}$... + Сбросить ${0}$ к ${1}$... Отслеживать ветку... Сравнение веток Недопустимая основная ветка! @@ -128,6 +129,7 @@ Объединить с предыдущей ревизией Объединить все следующие ревизии с этим ИЗМЕНЕНИЯ + изменённый(х) файл(ов) Найти изменения.... ФАЙЛЫ Файл LFS @@ -219,6 +221,7 @@ Введите имя ветки. Пробелы будут заменены на тире. Создать локальную ветку + Перезаписать существующую ветку Создать метку... Новая метка у: GPG подпись @@ -233,6 +236,9 @@ Простой Удерживайте Ctrl, чтобы сразу начать Вырезать + Удалить подмодуль + Принудительно удалить даже если содержит локальные изменения. + Подмодуль: Удалить ветку Ветка: Вы собираетесь удалить внешнюю ветку!!! @@ -268,6 +274,7 @@ Показывать скрытые символы Сравнение рядом ПОДМОДУЛЬ + УДАЛЁН НОВЫЙ Обмен Подсветка синтаксиса @@ -292,7 +299,6 @@ Редактировать выбранный репозиторий Выполнить пользовательское действие Имя действия: - Быстрая перемотка вперёд (без проверки) Извлечь Извлечь все внешние репозитории Разрешить опцию (--force) @@ -391,6 +397,7 @@ Создать новую вкладку Открыть диалоговое окно настроек Переключить активное рабочее место + Переключить активную страницу РЕПОЗИТОРИЙ Зафиксировать сформированные изменения Зафиксировать и выложить сформированные изменения @@ -434,6 +441,7 @@ ОШИБКА УВЕДОМЛЕНИЕ Рабочие места + Страницы Влить ветку В: Опции слияния: @@ -532,12 +540,11 @@ Информация об обрезке рабочего каталога в «$GIT_COMMON_DIR/worktrees» Загрузить Ветка внешнего репозитория: - Извлечь все ветки В: Локальные изменения: Отклонить Отложить и применить повторно - Загрузить без меток + Обновить все подмодули Внешний репозиторий: Загрузить (Получить и слить) Использовать перемещение вместо слияния @@ -648,6 +655,9 @@ Режим сброса: Переместить в: Текущая ветка: + Сброс ветки (без переключения) + Переместить в: + Ветка: Открыть в файловом менеджере Отменить ревизию Ревизия: @@ -707,6 +717,7 @@ ПОДМОДУЛИ Добавить подмодули Копировать относительный путь + Удалить подмодуль Извлечение вложенных подмодулей Открыть подмодуль репозитория Каталог: diff --git a/src/Resources/Locales/ta_IN.axaml b/src/Resources/Locales/ta_IN.axaml index 02c138c6..71df97c0 100644 --- a/src/Resources/Locales/ta_IN.axaml +++ b/src/Resources/Locales/ta_IN.axaml @@ -275,7 +275,6 @@ தேர்ந்தெடுக்கப்பட்ட களஞ்சியத்தைத் திருத்து தனிப்பயன் செயலை இயக்கு செயல் பெயர்: - வேகமாக முன்னோக்கி (சரிபார்க்காமல்) பெறு எல்லா தொலைகளையும் பெறு உள்ளக குறிப்புகளை கட்டாயமாக மீறு @@ -509,12 +508,10 @@ `$GIT_COMMON_DIR/பணிமரங்கள்` இதில் பணிமரம் தகவலை கத்தரி இழு தொலை கிளை: - எல்லா கிளைகளையும் எடு இதனுள்: உள்ளக மாற்றங்கள்: நிராகரி பதுக்கிவை & மீண்டும் இடு - குறிச்சொற்கள் இல்லாமல் பெறு தொலை: இழு (எடுத்து ஒன்றிணை) ஒன்றிணை என்பதற்குப் பதிலாக மறுதளத்தைப் பயன்படுத்து diff --git a/src/Resources/Locales/uk_UA.axaml b/src/Resources/Locales/uk_UA.axaml index a3b63bde..297878a6 100644 --- a/src/Resources/Locales/uk_UA.axaml +++ b/src/Resources/Locales/uk_UA.axaml @@ -279,7 +279,6 @@ Редагувати вибраний репозиторій Виконати спеціальну дію Ім'я дії: - Перемотати (без перемкнуття) Витягти Витягти всі віддалені сховища Примусово перезаписати локальні refs @@ -513,12 +512,10 @@ Видалити застарілу інформацію про робочі дерева в `$GIT_COMMON_DIR/worktrees` Pull (Витягти) Віддалена гілка: - Отримати всі гілки В: Локальні зміни: Скасувати Сховати та Застосувати - Отримати без тегів Віддалене сховище: Pull (Fetch & Merge) Використовувати rebase замість merge diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 18569a14..00939656 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -65,8 +65,9 @@ 拉回(pull) ${0}$ 拉回(pull) ${0}$ 内容至 ${1}$... 推送(push)${0}$ - 变基(rebase) ${0}$ 分支至 ${1}$... + 变基(rebase) ${0}$ 至 ${1}$... 重命名 ${0}$... + 重置 ${0}$ 到 ${1}$... 切换上游分支 ... 分支比较 跟踪的上游分支不存在或已删除! @@ -128,6 +129,7 @@ 合并此提交到上一个提交 合并之后的提交到此处 变更对比 + 个文件发生变更 查找变更... 文件列表 LFS文件 @@ -219,6 +221,7 @@ 填写分支名称。 空格将被替换为'-'符号 创建本地分支 + 允许重置已存在的分支 新建标签 ... 标签位于 : 使用GPG签名 @@ -233,6 +236,9 @@ 轻量标签 按住Ctrl键点击将以默认参数运行 剪切 + 取消初始化子模块 + 强制取消,即使包含本地变更 + 子模块 : 删除分支确认 分支名 : 您正在删除远程上的分支,请务必小心!!! @@ -268,6 +274,7 @@ 显示隐藏符号 分列对比 子模块 + 删除 新增 交换比对双方 语法高亮 @@ -292,7 +299,6 @@ 编辑仓库 执行自定义操作 自定义操作 : - 快进(fast-forward,无需checkout) 拉取(fetch) 拉取所有的远程仓库 强制覆盖本地REFs @@ -534,12 +540,11 @@ 清理在`$GIT_COMMON_DIR/worktrees`中的无效工作树信息 拉回(pull) 拉取分支 : - 拉取远程中的所有分支变更 本地分支 : 未提交更改 : 丢弃更改 贮藏并自动恢复 - 不拉取远程标签 + 同时更新所有子模块 远程 : 拉回(拉取并合并) 使用变基方式合并分支 @@ -650,6 +655,9 @@ 重置模式 : 提交 : 当前分支 : + 重置所选分支(非当前分支) + 重置点 : + 操作分支 : 在文件浏览器中查看 回滚操作确认 目标提交 : @@ -709,6 +717,7 @@ 子模块 添加子模块 复制路径 + 取消初始化 拉取子孙模块 打开仓库 相对仓库路径 : diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index ded99a14..026a41ed 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -67,6 +67,7 @@ 推送 (push) ${0}$ 重定基底 (rebase) ${0}$ 分支至 ${1}$... 重新命名 ${0}$... + 重設 ${0}$ 至 ${1}$... 切換上游分支... 分支比較 追蹤上游分支不存在或已刪除! @@ -128,6 +129,7 @@ 合併此提交到上一個提交 合併之後的提交到此處 變更對比 + 個檔案已變更 搜尋變更... 檔案列表 LFS 檔案 @@ -219,6 +221,7 @@ 輸入分支名稱。 空格將以英文破折號取代 建立本機分支 + 允許覆寫現有分支 新增標籤... 標籤位於: 使用 GPG 簽章 @@ -233,6 +236,9 @@ 輕量標籤 按住 Ctrl 鍵將直接以預設參數執行 剪下 + 取消初始化子模組 + 強制取消,即使它包含本地變更 + 子模組 : 刪除分支確認 分支名稱: 您正在刪除遠端上的分支,請務必小心! @@ -268,6 +274,7 @@ 顯示隱藏符號 並排對比 子模組 + 已刪除 新增 交換比對雙方 語法上色 @@ -292,7 +299,6 @@ 編輯存放庫 執行自訂動作 自訂動作: - 快進 (fast-forward,無需 checkout) 提取 (fetch) 提取所有的遠端存放庫 強制覆寫本機 REFs @@ -534,12 +540,11 @@ 清理在 `$GIT_COMMON_DIR/worktrees` 中的無效工作區資訊 拉取 (pull) 拉取分支: - 拉取遠端中的所有分支 本機分支: 未提交變更: 捨棄變更 擱置變更並自動復原 - 不拉取遠端標籤 + 同時更新所有子模組 遠端: 拉取 (提取並合併) 使用重定基底 (rebase) 合併分支 @@ -650,6 +655,9 @@ 重設模式: 移至提交: 目前分支: + 重設選取的分支(非目前分支) + 重設位置 : + 選取分支 : 在檔案瀏覽器中檢視 復原操作確認 目標提交: @@ -709,6 +717,7 @@ 子模組 新增子模組 複製路徑 + 取消初始化 提取子模組 開啟存放庫 相對存放庫路徑: diff --git a/src/ViewModels/AddSubmodule.cs b/src/ViewModels/AddSubmodule.cs index cf1bd8a3..82a1f62a 100644 --- a/src/ViewModels/AddSubmodule.cs +++ b/src/ViewModels/AddSubmodule.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Threading.Tasks; @@ -14,12 +15,10 @@ namespace SourceGit.ViewModels set => SetProperty(ref _url, value, true); } - [Required(ErrorMessage = "Reletive path is required!!!")] - [CustomValidation(typeof(AddSubmodule), nameof(ValidateRelativePath))] public string RelativePath { get => _relativePath; - set => SetProperty(ref _relativePath, value, true); + set => SetProperty(ref _relativePath, value); } public bool Recursive @@ -37,20 +36,6 @@ namespace SourceGit.ViewModels { if (!Models.Remote.IsValidURL(url)) return new ValidationResult("Invalid repository URL format"); - return ValidationResult.Success; - } - - public static ValidationResult ValidateRelativePath(string path, ValidationContext ctx) - { - if (Path.Exists(path)) - { - return new ValidationResult("Give path is exists already!"); - } - - if (Path.IsPathRooted(path)) - { - return new ValidationResult("Path must be relative to this repository!"); - } return ValidationResult.Success; } @@ -63,9 +48,20 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Add Submodule"); Use(log); + var relativePath = _relativePath; + if (string.IsNullOrEmpty(relativePath)) + { + if (_url.EndsWith("/.git", StringComparison.Ordinal)) + relativePath = Path.GetFileName(Path.GetDirectoryName(_url)); + else if (_url.EndsWith(".git", StringComparison.Ordinal)) + relativePath = Path.GetFileNameWithoutExtension(_url); + else + relativePath = Path.GetFileName(_url); + } + return Task.Run(() => { - var succ = new Commands.Submodule(_repo.FullPath).Use(log).Add(_url, _relativePath, Recursive); + var succ = new Commands.Submodule(_repo.FullPath).Use(log).Add(_url, relativePath, Recursive); log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs index d8e9b38a..630a97a5 100644 --- a/src/ViewModels/Checkout.cs +++ b/src/ViewModels/Checkout.cs @@ -17,8 +17,7 @@ namespace SourceGit.ViewModels public bool IsRecurseSubmoduleVisible { - get; - private set; + get => _repo.Submodules.Count > 0; } public bool RecurseSubmodules @@ -32,7 +31,6 @@ namespace SourceGit.ViewModels _repo = repo; Branch = branch; DiscardLocalChanges = false; - IsRecurseSubmoduleVisible = repo.Submodules.Count > 0; } public override Task Sure() @@ -76,9 +74,9 @@ namespace SourceGit.ViewModels { if (updateSubmodules) { - var submodules = new Commands.QuerySubmodules(_repo.FullPath).Result(); + var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); if (submodules.Count > 0) - new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true, false); + new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); } if (needPopStash) diff --git a/src/ViewModels/CheckoutCommit.cs b/src/ViewModels/CheckoutCommit.cs index a8c5b8cf..c41fd2ce 100644 --- a/src/ViewModels/CheckoutCommit.cs +++ b/src/ViewModels/CheckoutCommit.cs @@ -17,8 +17,7 @@ namespace SourceGit.ViewModels public bool IsRecurseSubmoduleVisible { - get; - private set; + get => _repo.Submodules.Count > 0; } public bool RecurseSubmodules @@ -32,7 +31,6 @@ namespace SourceGit.ViewModels _repo = repo; Commit = commit; DiscardLocalChanges = false; - IsRecurseSubmoduleVisible = repo.Submodules.Count > 0; } public override Task Sure() @@ -76,9 +74,9 @@ namespace SourceGit.ViewModels { if (updateSubmodules) { - var submodules = new Commands.QuerySubmodules(_repo.FullPath).Result(); + var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); if (submodules.Count > 0) - new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true, false); + new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); } if (needPop) diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs index dd9cf86b..032551a2 100644 --- a/src/ViewModels/Clone.cs +++ b/src/ViewModels/Clone.cs @@ -140,9 +140,9 @@ namespace SourceGit.ViewModels if (InitAndUpdateSubmodules) { - var submodules = new Commands.QuerySubmodules(path).Result(); + var submodules = new Commands.QueryUpdatableSubmodules(path).Result(); if (submodules.Count > 0) - new Commands.Submodule(path).Use(log).Update(submodules, true, true, false); + new Commands.Submodule(path).Use(log).Update(submodules, true, true); } log.Complete(); diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 3b411056..bd4f2284 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -15,7 +15,7 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public partial class CommitDetail : ObservableObject + public partial class CommitDetail : ObservableObject, IDisposable { public int ActivePageIndex { @@ -137,7 +137,7 @@ namespace SourceGit.ViewModels WebLinks = Models.CommitLink.Get(repo.Remotes); } - public void Cleanup() + public void Dispose() { _repo = null; _commit = null; @@ -149,6 +149,7 @@ namespace SourceGit.ViewModels _diffContext = null; _viewRevisionFileContent = null; _cancellationSource = null; + _requestingRevisionFiles = false; _revisionFiles = null; _revisionFileSearchSuggestion = null; } @@ -335,7 +336,7 @@ namespace SourceGit.ViewModels options.DefaultExtension = ".patch"; options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }]; - var baseRevision = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0]; + var baseRevision = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0]; var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { @@ -546,6 +547,7 @@ namespace SourceGit.ViewModels private void Refresh() { _changes = null; + _requestingRevisionFiles = false; _revisionFiles = null; SignInfo = null; @@ -593,7 +595,7 @@ namespace SourceGit.ViewModels Task.Run(() => { - var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0]; + var parent = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0]; var cmd = new Commands.CompareRevisions(_repo.FullPath, parent, _commit.SHA) { CancellationToken = token }; var changes = cmd.Result(); var visible = changes; @@ -812,16 +814,22 @@ namespace SourceGit.ViewModels { if (_revisionFiles == null) { + if (_requestingRevisionFiles) + return; + var sha = Commit.SHA; + _requestingRevisionFiles = true; Task.Run(() => { var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result(); Dispatcher.UIThread.Invoke(() => { - if (sha == Commit.SHA) + if (sha == Commit.SHA && _requestingRevisionFiles) { _revisionFiles = files; + _requestingRevisionFiles = false; + if (!string.IsNullOrEmpty(_revisionFileSearchFilter)) CalcRevisionFileSearchSuggestion(); } @@ -907,6 +915,7 @@ namespace SourceGit.ViewModels private DiffContext _diffContext = null; private object _viewRevisionFileContent = null; private CancellationTokenSource _cancellationSource = null; + private bool _requestingRevisionFiles = false; private List _revisionFiles = null; private string _revisionFileSearchFilter = string.Empty; private List _revisionFileSearchSuggestion = null; diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs index 8c825081..add365a3 100644 --- a/src/ViewModels/Conflict.cs +++ b/src/ViewModels/Conflict.cs @@ -1,4 +1,6 @@ -namespace SourceGit.ViewModels +using System; + +namespace SourceGit.ViewModels { public class ConflictSourceBranch { @@ -46,7 +48,8 @@ _wc = wc; _change = change; - IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).Result(); + var isSubmodule = repo.Submodules.Find(x => x.Path.Equals(change.Path, StringComparison.Ordinal)) != null; + IsResolved = !isSubmodule && new Commands.IsConflictResolved(repo.FullPath, change).Result(); var context = wc.InProgressContext; if (context is CherryPickInProgress cherryPick) diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs index 252802f4..e518e977 100644 --- a/src/ViewModels/CreateBranch.cs +++ b/src/ViewModels/CreateBranch.cs @@ -43,10 +43,19 @@ namespace SourceGit.ViewModels get => _repo.IsBare; } + public bool AllowOverwrite + { + get => _allowOverwrite; + set + { + if (SetProperty(ref _allowOverwrite, value)) + ValidateProperty(_name, nameof(Name)); + } + } + public bool IsRecurseSubmoduleVisible { - get; - private set; + get => _repo.Submodules.Count > 0; } public bool RecurseSubmodules @@ -67,7 +76,6 @@ namespace SourceGit.ViewModels BasedOn = branch; DiscardLocalChanges = false; - IsRecurseSubmoduleVisible = repo.Submodules.Count > 0; } public CreateBranch(Repository repo, Models.Commit commit) @@ -77,7 +85,6 @@ namespace SourceGit.ViewModels BasedOn = commit; DiscardLocalChanges = false; - IsRecurseSubmoduleVisible = repo.Submodules.Count > 0; } public CreateBranch(Repository repo, Models.Tag tag) @@ -87,23 +94,28 @@ namespace SourceGit.ViewModels BasedOn = tag; DiscardLocalChanges = false; - IsRecurseSubmoduleVisible = repo.Submodules.Count > 0; } public static ValidationResult ValidateBranchName(string name, ValidationContext ctx) { - var creator = ctx.ObjectInstance as CreateBranch; - if (creator == null) - return new ValidationResult("Missing runtime context to create branch!"); - - var fixedName = creator.FixName(name); - foreach (var b in creator._repo.Branches) + if (ctx.ObjectInstance is CreateBranch creator) { - if (b.FriendlyName == fixedName) - return new ValidationResult("A branch with same name already exists!"); - } + if (!creator._allowOverwrite) + { + var fixedName = creator.FixName(name); + foreach (var b in creator._repo.Branches) + { + if (b.FriendlyName == fixedName) + return new ValidationResult("A branch with same name already exists!"); + } + } - return ValidationResult.Success; + return ValidationResult.Success; + } + else + { + return new ValidationResult("Missing runtime context to create branch!"); + } } public override Task Sure() @@ -123,7 +135,7 @@ namespace SourceGit.ViewModels var needPopStash = false; if (DiscardLocalChanges) { - succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision, true); + succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision, true, _allowOverwrite); } else { @@ -141,16 +153,16 @@ namespace SourceGit.ViewModels needPopStash = true; } - succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision, false); + succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision, false, _allowOverwrite); } if (succ) { if (updateSubmodules) { - var submodules = new Commands.QuerySubmodules(_repo.FullPath).Result(); + var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); if (submodules.Count > 0) - new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true, false); + new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); } if (needPopStash) @@ -159,7 +171,7 @@ namespace SourceGit.ViewModels } else { - succ = Commands.Branch.Create(_repo.FullPath, fixedName, _baseOnRevision, log); + succ = Commands.Branch.Create(_repo.FullPath, fixedName, _baseOnRevision, _allowOverwrite, log); } log.Complete(); @@ -205,5 +217,6 @@ namespace SourceGit.ViewModels private readonly Repository _repo = null; private string _name = null; private readonly string _baseOnRevision = null; + private bool _allowOverwrite = false; } } diff --git a/src/ViewModels/DeinitSubmodule.cs b/src/ViewModels/DeinitSubmodule.cs new file mode 100644 index 00000000..a96a65d0 --- /dev/null +++ b/src/ViewModels/DeinitSubmodule.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class DeinitSubmodule : Popup + { + public string Submodule + { + get; + private set; + } + + public bool Force + { + get; + set; + } + + public DeinitSubmodule(Repository repo, string submodule) + { + _repo = repo; + Submodule = submodule; + Force = false; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = "De-initialize Submodule"; + + var log = _repo.CreateLog("De-initialize Submodule"); + Use(log); + + return Task.Run(() => + { + var succ = new Commands.Submodule(_repo.FullPath).Use(log).Deinit(Submodule, false); + log.Complete(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return succ; + }); + } + + private Repository _repo; + } +} diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index 52729e56..72570bf0 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -36,13 +36,17 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Run custom action ..."; + var log = _repo.CreateLog(CustomAction.Name); + Use(log); + return Task.Run(() => { if (CustomAction.WaitForExit) - Commands.ExecuteCustomAction.RunAndWait(_repo.FullPath, CustomAction.Executable, _args, output => CallUIThread(() => ProgressDescription = output)); + Commands.ExecuteCustomAction.RunAndWait(_repo.FullPath, CustomAction.Executable, _args, log); else Commands.ExecuteCustomAction.Run(_repo.FullPath, CustomAction.Executable, _args); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/FastForwardWithoutCheckout.cs b/src/ViewModels/FastForwardWithoutCheckout.cs deleted file mode 100644 index 53d69816..00000000 --- a/src/ViewModels/FastForwardWithoutCheckout.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Threading.Tasks; - -namespace SourceGit.ViewModels -{ - public class FastForwardWithoutCheckout : Popup - { - public Models.Branch Local - { - get; - } - - public Models.Branch To - { - get; - } - - public FastForwardWithoutCheckout(Repository repo, Models.Branch local, Models.Branch upstream) - { - _repo = repo; - Local = local; - To = upstream; - } - - public override Task Sure() - { - _repo.SetWatcherEnabled(false); - ProgressDescription = "Fast-Forward ..."; - - var log = _repo.CreateLog("Fast-Forward (No checkout)"); - Use(log); - - return Task.Run(() => - { - new Commands.UpdateRef(_repo.FullPath, Local.FullName, To.FullName).Use(log).Exec(); - log.Complete(); - CallUIThread(() => - { - _repo.NavigateToCommit(To.Head); - _repo.SetWatcherEnabled(true); - }); - return true; - }); - } - - private readonly Repository _repo = null; - } -} diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs index fc92be81..b85b2b53 100644 --- a/src/ViewModels/Fetch.cs +++ b/src/ViewModels/Fetch.cs @@ -80,9 +80,16 @@ namespace SourceGit.ViewModels log.Complete(); + var upstream = _repo.CurrentBranch?.Upstream; + var upstreamHead = string.Empty; + if (!string.IsNullOrEmpty(upstream)) + upstreamHead = new Commands.QueryRevisionByRefName(_repo.FullPath, upstream.Substring(13)).Result(); + CallUIThread(() => { - _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.Upstream); + if (!string.IsNullOrEmpty(upstreamHead)) + _repo.NavigateToCommitDelayed(upstreamHead); + _repo.MarkFetched(); _repo.SetWatcherEnabled(true); }); diff --git a/src/ViewModels/FetchInto.cs b/src/ViewModels/FetchInto.cs index eee87979..730c08b6 100644 --- a/src/ViewModels/FetchInto.cs +++ b/src/ViewModels/FetchInto.cs @@ -33,11 +33,14 @@ namespace SourceGit.ViewModels { new Commands.Fetch(_repo.FullPath, Local, Upstream).Use(log).Exec(); log.Complete(); + + var changedLocalBranchHead = new Commands.QueryRevisionByRefName(_repo.FullPath, Local.Name).Result(); CallUIThread(() => { - _repo.NavigateToBranchDelayed(Upstream.FullName); + _repo.NavigateToCommitDelayed(changedLocalBranchHead); _repo.SetWatcherEnabled(true); }); + return true; }); } diff --git a/src/ViewModels/GitFlowFinish.cs b/src/ViewModels/GitFlowFinish.cs index d7ad5d31..bef4c2d9 100644 --- a/src/ViewModels/GitFlowFinish.cs +++ b/src/ViewModels/GitFlowFinish.cs @@ -9,9 +9,11 @@ namespace SourceGit.ViewModels get; } - public bool IsFeature => _type == "feature"; - public bool IsRelease => _type == "release"; - public bool IsHotfix => _type == "hotfix"; + public Models.GitFlowBranchType Type + { + get; + private set; + } public bool Squash { @@ -31,27 +33,27 @@ namespace SourceGit.ViewModels set; } = false; - public GitFlowFinish(Repository repo, Models.Branch branch, string type, string prefix) + public GitFlowFinish(Repository repo, Models.Branch branch, Models.GitFlowBranchType type) { _repo = repo; - _type = type; - _prefix = prefix; Branch = branch; + Type = type; } public override Task Sure() { _repo.SetWatcherEnabled(false); + ProgressDescription = $"Git Flow - Finish {Branch.Name} ..."; - var name = Branch.Name.StartsWith(_prefix) ? Branch.Name.Substring(_prefix.Length) : Branch.Name; - ProgressDescription = $"Git Flow - finishing {_type} {name} ..."; - - var log = _repo.CreateLog("Gitflow - Finish"); + var log = _repo.CreateLog("GitFlow - Finish"); Use(log); + var prefix = _repo.GitFlow.GetPrefix(Type); + var name = Branch.Name.StartsWith(prefix) ? Branch.Name.Substring(prefix.Length) : Branch.Name; + return Task.Run(() => { - var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, Squash, AutoPush, KeepBranch, log); + var succ = Commands.GitFlow.Finish(_repo.FullPath, Type, name, Squash, AutoPush, KeepBranch, log); log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; @@ -59,7 +61,5 @@ namespace SourceGit.ViewModels } private readonly Repository _repo; - private readonly string _type; - private readonly string _prefix; } } diff --git a/src/ViewModels/GitFlowStart.cs b/src/ViewModels/GitFlowStart.cs index 8e44984a..3ecba883 100644 --- a/src/ViewModels/GitFlowStart.cs +++ b/src/ViewModels/GitFlowStart.cs @@ -5,6 +5,18 @@ namespace SourceGit.ViewModels { public class GitFlowStart : Popup { + public Models.GitFlowBranchType Type + { + get; + private set; + } + + public string Prefix + { + get; + private set; + } + [Required(ErrorMessage = "Name is required!!!")] [RegularExpression(@"^[\w\-/\.#]+$", ErrorMessage = "Bad branch name format!")] [CustomValidation(typeof(GitFlowStart), nameof(ValidateBranchName))] @@ -14,27 +26,19 @@ namespace SourceGit.ViewModels set => SetProperty(ref _name, value, true); } - public string Prefix - { - get => _prefix; - } - - public bool IsFeature => _type == "feature"; - public bool IsRelease => _type == "release"; - public bool IsHotfix => _type == "hotfix"; - - public GitFlowStart(Repository repo, string type) + public GitFlowStart(Repository repo, Models.GitFlowBranchType type) { _repo = repo; - _type = type; - _prefix = Commands.GitFlow.GetPrefix(repo.FullPath, type); + + Type = type; + Prefix = _repo.GitFlow.GetPrefix(type); } public static ValidationResult ValidateBranchName(string name, ValidationContext ctx) { if (ctx.ObjectInstance is GitFlowStart starter) { - var check = $"{starter._prefix}{name}"; + var check = $"{starter.Prefix}{name}"; foreach (var b in starter._repo.Branches) { if (b.FriendlyName == check) @@ -48,14 +52,14 @@ namespace SourceGit.ViewModels public override Task Sure() { _repo.SetWatcherEnabled(false); - ProgressDescription = $"Git Flow - starting {_type} {_name} ..."; + ProgressDescription = $"Git Flow - Start {Prefix}{_name} ..."; - var log = _repo.CreateLog("Gitflow - Start"); + var log = _repo.CreateLog("GitFlow - Start"); Use(log); return Task.Run(() => { - var succ = Commands.GitFlow.Start(_repo.FullPath, _type, _name, log); + var succ = Commands.GitFlow.Start(_repo.FullPath, Type, _name, log); log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; @@ -63,8 +67,6 @@ namespace SourceGit.ViewModels } private readonly Repository _repo; - private readonly string _type; - private readonly string _prefix; private string _name = null; } } diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index f21d2636..2057a13f 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -12,7 +12,7 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public class Histories : ObservableObject + public class Histories : ObservableObject, IDisposable { public Repository Repo { @@ -57,7 +57,7 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _navigationId, value); } - public object DetailContext + public IDisposable DetailContext { get => _detailContext; set => SetProperty(ref _detailContext, value); @@ -98,23 +98,13 @@ namespace SourceGit.ViewModels _repo = repo; } - public void Cleanup() + public void Dispose() { - Commits = new List(); - + Commits = []; _repo = null; _graph = null; _autoSelectedCommit = null; - - if (_detailContext is CommitDetail cd) - { - cd.Cleanup(); - } - else if (_detailContext is RevisionCompare rc) - { - rc.Cleanup(); - } - + _detailContext?.Dispose(); _detailContext = null; } @@ -220,7 +210,7 @@ namespace SourceGit.ViewModels else { _repo.SelectedSearchedCommit = null; - DetailContext = commits.Count; + DetailContext = new Models.Count(commits.Count); } } @@ -985,8 +975,8 @@ namespace SourceGit.ViewModels if (!_repo.IsBare) { - var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, current.Name); - if (detect.IsGitFlowBranch) + var type = _repo.GetGitFlowType(current); + if (type != Models.GitFlowBranchType.None) { var finish = new MenuItem(); finish.Header = App.Text("BranchCM.Finish", current.Name); @@ -994,7 +984,7 @@ namespace SourceGit.ViewModels finish.Click += (_, e) => { if (_repo.CanCreatePopup()) - _repo.ShowPopup(new GitFlowFinish(_repo, current, detect.Type, detect.Prefix)); + _repo.ShowPopup(new GitFlowFinish(_repo, current, type)); e.Handled = true; }; submenu.Items.Add(finish); @@ -1073,8 +1063,8 @@ namespace SourceGit.ViewModels if (!_repo.IsBare) { - var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, branch.Name); - if (detect.IsGitFlowBranch) + var type = _repo.GetGitFlowType(branch); + if (type != Models.GitFlowBranchType.None) { var finish = new MenuItem(); finish.Header = App.Text("BranchCM.Finish", branch.Name); @@ -1082,7 +1072,7 @@ namespace SourceGit.ViewModels finish.Click += (_, e) => { if (_repo.CanCreatePopup()) - _repo.ShowPopup(new GitFlowFinish(_repo, branch, detect.Type, detect.Prefix)); + _repo.ShowPopup(new GitFlowFinish(_repo, branch, type)); e.Handled = true; }; submenu.Items.Add(finish); @@ -1256,7 +1246,7 @@ namespace SourceGit.ViewModels private Models.CommitGraph _graph = null; private Models.Commit _autoSelectedCommit = null; private long _navigationId = 0; - private object _detailContext = null; + private IDisposable _detailContext = null; private Models.Bisect _bisect = null; diff --git a/src/ViewModels/InitGitFlow.cs b/src/ViewModels/InitGitFlow.cs index 1672ef8e..dec587b0 100644 --- a/src/ViewModels/InitGitFlow.cs +++ b/src/ViewModels/InitGitFlow.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -109,9 +110,35 @@ namespace SourceGit.ViewModels return Task.Run(() => { - var succ = Commands.GitFlow.Init( + var succ = false; + var current = _repo.CurrentBranch; + + var masterBranch = _repo.Branches.Find(x => x.IsLocal && x.Name.Equals(_master, StringComparison.Ordinal)); + if (masterBranch == null) + { + succ = Commands.Branch.Create(_repo.FullPath, _master, current.Head, true, log); + if (!succ) + { + log.Complete(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return false; + } + } + + var developBranch = _repo.Branches.Find(x => x.IsLocal && x.Name.Equals(_develop, StringComparison.Ordinal)); + if (developBranch == null) + { + succ = Commands.Branch.Create(_repo.FullPath, _develop, current.Head, true, log); + if (!succ) + { + log.Complete(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return false; + } + } + + succ = Commands.GitFlow.Init( _repo.FullPath, - _repo.Branches, _master, _develop, _featurePrefix, @@ -121,7 +148,23 @@ namespace SourceGit.ViewModels log); log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); + + CallUIThread(() => + { + if (succ) + { + var gitflow = new Models.GitFlow(); + gitflow.Master = _master; + gitflow.Develop = _develop; + gitflow.FeaturePrefix = _featurePrefix; + gitflow.ReleasePrefix = _releasePrefix; + gitflow.HotfixPrefix = _hotfixPrefix; + _repo.GitFlow = gitflow; + } + + _repo.SetWatcherEnabled(true); + }); + return succ; }); } diff --git a/src/ViewModels/InteractiveRebase.cs b/src/ViewModels/InteractiveRebase.cs index 0e77eecf..1c4d4480 100644 --- a/src/ViewModels/InteractiveRebase.cs +++ b/src/ViewModels/InteractiveRebase.cs @@ -19,10 +19,23 @@ namespace SourceGit.ViewModels private set; } + public bool CanSquashOrFixup + { + get => _canSquashOrFixup; + set + { + if (SetProperty(ref _canSquashOrFixup, value)) + { + if (_action == Models.InteractiveRebaseAction.Squash || _action == Models.InteractiveRebaseAction.Fixup) + Action = Models.InteractiveRebaseAction.Pick; + } + } + } + public Models.InteractiveRebaseAction Action { get => _action; - private set => SetProperty(ref _action, value); + set => SetProperty(ref _action, value); } public string Subject @@ -48,20 +61,17 @@ namespace SourceGit.ViewModels } } - public InteractiveRebaseItem(Models.Commit c, string message) + public InteractiveRebaseItem(Models.Commit c, string message, bool canSquashOrFixup) { Commit = c; FullMessage = message; - } - - public void SetAction(object param) - { - Action = (Models.InteractiveRebaseAction)param; + CanSquashOrFixup = canSquashOrFixup; } private Models.InteractiveRebaseAction _action = Models.InteractiveRebaseAction.Pick; private string _subject; private string _fullMessage; + private bool _canSquashOrFixup = true; } public class InteractiveRebase : ObservableObject @@ -88,7 +98,7 @@ namespace SourceGit.ViewModels { get; private set; - } = new AvaloniaList(); + } = []; public InteractiveRebaseItem SelectedItem { @@ -121,8 +131,11 @@ namespace SourceGit.ViewModels var commits = new Commands.QueryCommitsForInteractiveRebase(repoPath, on.SHA).Result(); var list = new List(); - foreach (var c in commits) - list.Add(new InteractiveRebaseItem(c.Commit, c.Message)); + for (var i = 0; i < commits.Count; i++) + { + var c = commits[i]; + list.Add(new InteractiveRebaseItem(c.Commit, c.Message, i < commits.Count - 1)); + } Dispatcher.UIThread.Invoke(() => { @@ -141,6 +154,7 @@ namespace SourceGit.ViewModels Items.RemoveAt(idx - 1); Items.Insert(idx, prev); SelectedItem = item; + UpdateItems(); } } @@ -153,9 +167,22 @@ namespace SourceGit.ViewModels Items.RemoveAt(idx + 1); Items.Insert(idx, next); SelectedItem = item; + UpdateItems(); } } + public void ChangeAction(InteractiveRebaseItem item, Models.InteractiveRebaseAction action) + { + if (!item.CanSquashOrFixup) + { + if (action == Models.InteractiveRebaseAction.Squash || action == Models.InteractiveRebaseAction.Fixup) + return; + } + + item.Action = action; + UpdateItems(); + } + public Task Start() { _repo.SetWatcherEnabled(false); @@ -186,6 +213,27 @@ namespace SourceGit.ViewModels }); } + private void UpdateItems() + { + if (Items.Count == 0) + return; + + var hasValidParent = false; + for (var i = Items.Count - 1; i >= 0; i--) + { + var item = Items[i]; + if (hasValidParent) + { + item.CanSquashOrFixup = true; + } + else + { + item.CanSquashOrFixup = false; + hasValidParent = item.Action != Models.InteractiveRebaseAction.Drop; + } + } + } + private Repository _repo = null; private bool _isLoading = false; private InteractiveRebaseItem _selectedItem = null; diff --git a/src/ViewModels/Merge.cs b/src/ViewModels/Merge.cs index d3d83836..2e276708 100644 --- a/src/ViewModels/Merge.cs +++ b/src/ViewModels/Merge.cs @@ -64,9 +64,10 @@ namespace SourceGit.ViewModels new Commands.Merge(_repo.FullPath, _sourceName, Mode.Arg).Use(log).Exec(); log.Complete(); + var head = new Commands.QueryRevisionByRefName(_repo.FullPath, "HEAD").Result(); CallUIThread(() => { - _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.FullName); + _repo.NavigateToCommitDelayed(head); _repo.SetWatcherEnabled(true); }); return true; diff --git a/src/ViewModels/Popup.cs b/src/ViewModels/Popup.cs index ec1e007c..b3fe7e23 100644 --- a/src/ViewModels/Popup.cs +++ b/src/ViewModels/Popup.cs @@ -48,7 +48,14 @@ namespace SourceGit.ViewModels protected void Use(CommandLog log) { - log.Register(newline => ProgressDescription = newline.Trim()); + log.Register(SetDescription); + } + + private void SetDescription(string data) + { + var desc = data.Trim(); + if (!string.IsNullOrEmpty(desc)) + ProgressDescription = desc; } private bool _inProgress = false; diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs index e13dad96..1b53949b 100644 --- a/src/ViewModels/Pull.cs +++ b/src/ViewModels/Pull.cs @@ -50,16 +50,15 @@ namespace SourceGit.ViewModels set => _repo.Settings.PreferRebaseInsteadOfMerge = value; } - public bool FetchAllBranches + public bool IsRecurseSubmoduleVisible { - get => _repo.Settings.FetchAllBranchesOnPull; - set => _repo.Settings.FetchAllBranchesOnPull = value; + get => _repo.Submodules.Count > 0; } - public bool NoTags + public bool RecurseSubmodules { - get => _repo.Settings.FetchWithoutTagsOnPull; - set => _repo.Settings.FetchWithoutTagsOnPull = value; + get => _repo.Settings.UpdateSubmodulesOnCheckoutBranch; + set => _repo.Settings.UpdateSubmodulesOnCheckoutBranch = value; } public Pull(Repository repo, Models.Branch specifiedRemoteBranch) @@ -119,6 +118,7 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Pull"); Use(log); + var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules; return Task.Run(() => { var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); @@ -143,47 +143,31 @@ namespace SourceGit.ViewModels } } - bool rs; - if (FetchAllBranches) + bool rs = new Commands.Pull( + _repo.FullPath, + _selectedRemote.Name, + !string.IsNullOrEmpty(_current.Upstream) && _current.Upstream.Equals(_selectedBranch.FullName) ? string.Empty : _selectedBranch.Name, + UseRebase).Use(log).Exec(); + + if (rs) { - rs = new Commands.Fetch( - _repo.FullPath, - _selectedRemote.Name, - NoTags, - false).Use(log).Exec(); - if (!rs) + if (updateSubmodules) { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; + var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); + if (submodules.Count > 0) + new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); } - _repo.MarkFetched(); - - // Use merge/rebase instead of pull as fetch is done manually. - if (UseRebase) - rs = new Commands.Rebase(_repo.FullPath, _selectedBranch.FriendlyName, false).Use(log).Exec(); - else - rs = new Commands.Merge(_repo.FullPath, _selectedBranch.FriendlyName, "").Use(log).Exec(); + if (needPopStash) + new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); } - else - { - rs = new Commands.Pull( - _repo.FullPath, - _selectedRemote.Name, - _selectedBranch.Name, - UseRebase, - NoTags).Use(log).Exec(); - } - - if (rs && needPopStash) - rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); log.Complete(); + var head = new Commands.QueryRevisionByRefName(_repo.FullPath, "HEAD").Result(); CallUIThread(() => { - _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.FullName); + _repo.NavigateToCommitDelayed(head); _repo.SetWatcherEnabled(true); }); diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index a3f63251..b02cf36a 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -51,6 +51,12 @@ namespace SourceGit.ViewModels get => _settings; } + public Models.GitFlow GitFlow + { + get; + set; + } = new Models.GitFlow(); + public Models.FilterMode HistoriesFilterMode { get => _historiesFilterMode; @@ -294,6 +300,7 @@ namespace SourceGit.ViewModels SelectedSearchedCommit = null; SearchCommitFilter = string.Empty; MatchedFilesForSearching = null; + _requestingWorktreeFiles = false; _worktreeFiles = null; } } @@ -545,9 +552,9 @@ namespace SourceGit.ViewModels _historiesFilterMode = Models.FilterMode.None; _watcher?.Dispose(); - _histories.Cleanup(); - _workingCopy.Cleanup(); - _stashesPage.Cleanup(); + _histories.Dispose(); + _workingCopy.Dispose(); + _stashesPage.Dispose(); _watcher = null; _histories = null; @@ -568,6 +575,7 @@ namespace SourceGit.ViewModels _searchedCommits.Clear(); _selectedSearchedCommit = null; + _requestingWorktreeFiles = false; _worktreeFiles = null; _matchedFilesForSearching = null; } @@ -593,6 +601,28 @@ namespace SourceGit.ViewModels GetOwnerPage()?.StartPopup(popup); } + public bool IsGitFlowEnabled() + { + return GitFlow is { IsValid: true } && + _branches.Find(x => x.IsLocal && x.Name.Equals(GitFlow.Master, StringComparison.Ordinal)) != null && + _branches.Find(x => x.IsLocal && x.Name.Equals(GitFlow.Develop, StringComparison.Ordinal)) != null; + } + + public Models.GitFlowBranchType GetGitFlowType(Models.Branch b) + { + if (!IsGitFlowEnabled()) + return Models.GitFlowBranchType.None; + + var name = b.Name; + if (name.StartsWith(GitFlow.FeaturePrefix, StringComparison.Ordinal)) + return Models.GitFlowBranchType.Feature; + if (name.StartsWith(GitFlow.ReleasePrefix, StringComparison.Ordinal)) + return Models.GitFlowBranchType.Release; + if (name.StartsWith(GitFlow.HotfixPrefix, StringComparison.Ordinal)) + return Models.GitFlowBranchType.Hotfix; + return Models.GitFlowBranchType.None; + } + public CommandLog CreateLog(string name) { var log = new CommandLog(name); @@ -602,19 +632,30 @@ namespace SourceGit.ViewModels public void RefreshAll() { - Task.Run(() => - { - var allowedSignersFile = new Commands.Config(_fullpath).Get("gpg.ssh.allowedSignersFile"); - _hasAllowedSignersFile = !string.IsNullOrEmpty(allowedSignersFile); - }); - + Task.Run(RefreshCommits); Task.Run(RefreshBranches); Task.Run(RefreshTags); - Task.Run(RefreshCommits); Task.Run(RefreshSubmodules); Task.Run(RefreshWorktrees); Task.Run(RefreshWorkingCopyChanges); Task.Run(RefreshStashes); + + Task.Run(() => + { + var config = new Commands.Config(_fullpath).ListAll(); + _hasAllowedSignersFile = config.TryGetValue("gpg.ssh.allowedSignersFile", out var allowedSignersFile) && !string.IsNullOrEmpty(allowedSignersFile); + + if (config.TryGetValue("gitflow.branch.master", out var masterName)) + GitFlow.Master = masterName; + if (config.TryGetValue("gitflow.branch.develop", out var developName)) + GitFlow.Develop = developName; + if (config.TryGetValue("gitflow.prefix.feature", out var featurePrefix)) + GitFlow.FeaturePrefix = featurePrefix; + if (config.TryGetValue("gitflow.prefix.release", out var releasePrefix)) + GitFlow.ReleasePrefix = releasePrefix; + if (config.TryGetValue("gitflow.prefix.hotfix", out var hotfixPrefix)) + GitFlow.HotfixPrefix = hotfixPrefix; + }); } public ContextMenu CreateContextMenuForExternalTools() @@ -881,17 +922,17 @@ namespace SourceGit.ViewModels } } + public void NavigateToCommitDelayed(string sha) + { + _navigateToCommitDelayed = sha; + } + public void NavigateToCurrentHead() { if (_currentBranch != null) NavigateToCommit(_currentBranch.Head); } - public void NavigateToBranchDelayed(string branch) - { - _navigateToBranchDelayed = branch; - } - public void ClearHistoriesFilter() { _settings.HistoriesFilters.Clear(); @@ -1052,6 +1093,13 @@ namespace SourceGit.ViewModels }); } + public bool MayHaveSubmodules() + { + var modulesFile = Path.Combine(_fullpath, ".gitmodules"); + var info = new FileInfo(modulesFile); + return info.Exists && info.Length > 20; + } + public void RefreshBranches() { var branches = new Commands.QueryBranches(_fullpath).Result(out var localBranchesCount); @@ -1141,27 +1189,64 @@ namespace SourceGit.ViewModels BisectState = _histories.UpdateBisectInfo(); - if (!string.IsNullOrEmpty(_navigateToBranchDelayed)) - { - var branch = _branches.Find(x => x.FullName == _navigateToBranchDelayed); - if (branch != null) - NavigateToCommit(branch.Head); - } + if (!string.IsNullOrEmpty(_navigateToCommitDelayed)) + NavigateToCommit(_navigateToCommitDelayed); } - _navigateToBranchDelayed = string.Empty; + _navigateToCommitDelayed = string.Empty; }); } public void RefreshSubmodules() { + if (!MayHaveSubmodules()) + { + if (_submodules.Count > 0) + { + Dispatcher.UIThread.Invoke(() => + { + Submodules = []; + VisibleSubmodules = BuildVisibleSubmodules(); + }); + } + + return; + } + var submodules = new Commands.QuerySubmodules(_fullpath).Result(); _watcher?.SetSubmodules(submodules); Dispatcher.UIThread.Invoke(() => { - Submodules = submodules; - VisibleSubmodules = BuildVisibleSubmodules(); + bool hasChanged = _submodules.Count != submodules.Count; + if (!hasChanged) + { + var old = new Dictionary(); + foreach (var module in _submodules) + old.Add(module.Path, module); + + foreach (var module in submodules) + { + if (!old.TryGetValue(module.Path, out var exist)) + { + hasChanged = true; + break; + } + + hasChanged = !exist.SHA.Equals(module.SHA, StringComparison.Ordinal) || + !exist.URL.Equals(module.URL, StringComparison.Ordinal) || + exist.Status != module.Status; + + if (hasChanged) + break; + } + } + + if (hasChanged) + { + Submodules = submodules; + VisibleSubmodules = BuildVisibleSubmodules(); + } }); } @@ -1375,8 +1460,7 @@ namespace SourceGit.ViewModels var menu = new ContextMenu(); menu.Placement = PlacementMode.BottomEdgeAlignedLeft; - var isGitFlowEnabled = Commands.GitFlow.IsEnabled(_fullpath, _branches); - if (isGitFlowEnabled) + if (IsGitFlowEnabled()) { var startFeature = new MenuItem(); startFeature.Header = App.Text("GitFlow.StartFeature"); @@ -1384,7 +1468,7 @@ namespace SourceGit.ViewModels startFeature.Click += (_, e) => { if (CanCreatePopup()) - ShowPopup(new GitFlowStart(this, "feature")); + ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Feature)); e.Handled = true; }; @@ -1394,7 +1478,7 @@ namespace SourceGit.ViewModels startRelease.Click += (_, e) => { if (CanCreatePopup()) - ShowPopup(new GitFlowStart(this, "release")); + ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Release)); e.Handled = true; }; @@ -1404,7 +1488,7 @@ namespace SourceGit.ViewModels startHotfix.Click += (_, e) => { if (CanCreatePopup()) - ShowPopup(new GitFlowStart(this, "hotfix")); + ShowPopup(new GitFlowStart(this, Models.GitFlowBranchType.Hotfix)); e.Handled = true; }; @@ -1419,8 +1503,15 @@ namespace SourceGit.ViewModels init.Icon = App.CreateMenuIcon("Icons.Init"); init.Click += (_, e) => { - if (CanCreatePopup()) + if (_currentBranch == null) + { + App.RaiseException(_fullpath, "Git flow init failed: No branch found!!!"); + } + else if (CanCreatePopup()) + { ShowPopup(new InitGitFlow(this)); + } + e.Handled = true; }; menu.Items.Add(init); @@ -1770,9 +1861,25 @@ namespace SourceGit.ViewModels fastForward.Click += (_, e) => { if (CanCreatePopup()) - ShowAndStartPopup(new FastForwardWithoutCheckout(this, branch, upstream)); + ShowAndStartPopup(new ResetWithoutCheckout(this, branch, upstream)); e.Handled = true; }; + menu.Items.Add(fastForward); + + var selectedCommit = (_histories?.DetailContext as CommitDetail)?.Commit; + if (selectedCommit != null && !selectedCommit.SHA.Equals(branch.Head, StringComparison.Ordinal)) + { + var move = new MenuItem(); + move.Header = App.Text("BranchCM.ResetToSelectedCommit", branch.Name, selectedCommit.SHA.Substring(0, 10)); + move.Icon = App.CreateMenuIcon("Icons.Reset"); + move.Click += (_, e) => + { + if (CanCreatePopup()) + ShowPopup(new ResetWithoutCheckout(this, branch, selectedCommit)); + e.Handled = true; + }; + menu.Items.Add(move); + } var fetchInto = new MenuItem(); fetchInto.Header = App.Text("BranchCM.FetchInto", upstream.FriendlyName, branch.Name); @@ -1785,7 +1892,6 @@ namespace SourceGit.ViewModels e.Handled = true; }; - menu.Items.Add(fastForward); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(fetchInto); } @@ -1850,8 +1956,8 @@ namespace SourceGit.ViewModels if (!IsBare) { - var detect = Commands.GitFlow.DetectType(_fullpath, _branches, branch.Name); - if (detect.IsGitFlowBranch) + var type = GetGitFlowType(branch); + if (type != Models.GitFlowBranchType.None) { var finish = new MenuItem(); finish.Header = App.Text("BranchCM.Finish", branch.Name); @@ -1859,7 +1965,7 @@ namespace SourceGit.ViewModels finish.Click += (_, e) => { if (CanCreatePopup()) - ShowPopup(new GitFlowFinish(this, branch, detect.Type, detect.Prefix)); + ShowPopup(new GitFlowFinish(this, branch, type)); e.Handled = true; }; menu.Items.Add(new MenuItem() { Header = "-" }); @@ -2370,12 +2476,14 @@ namespace SourceGit.ViewModels ev.Handled = true; }; - var copy = new MenuItem(); - copy.Header = App.Text("Submodule.CopyPath"); - copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, ev) => + var deinit = new MenuItem(); + deinit.Header = App.Text("Submodule.Deinit"); + deinit.Icon = App.CreateMenuIcon("Icons.Undo"); + deinit.IsEnabled = submodule.Status != Models.SubmoduleStatus.NotInited; + deinit.Click += (_, ev) => { - App.CopyText(submodule.Path); + if (CanCreatePopup()) + ShowPopup(new DeinitSubmodule(this, submodule.Path)); ev.Handled = true; }; @@ -2389,10 +2497,22 @@ namespace SourceGit.ViewModels ev.Handled = true; }; + var copy = new MenuItem(); + copy.Header = App.Text("Submodule.CopyPath"); + copy.Icon = App.CreateMenuIcon("Icons.Copy"); + copy.Click += (_, ev) => + { + App.CopyText(submodule.Path); + ev.Handled = true; + }; + var menu = new ContextMenu(); menu.Items.Add(open); - menu.Items.Add(copy); + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(deinit); menu.Items.Add(rm); + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copy); return menu; } @@ -2683,19 +2803,27 @@ namespace SourceGit.ViewModels { if (!IsSearchingCommitsByFilePath()) { + _requestingWorktreeFiles = false; _worktreeFiles = null; MatchedFilesForSearching = null; GC.Collect(); return; } + if (_requestingWorktreeFiles) + return; + + _requestingWorktreeFiles = true; + Task.Run(() => { _worktreeFiles = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result(); Dispatcher.UIThread.Invoke(() => { - if (IsSearchingCommitsByFilePath()) + if (IsSearchingCommitsByFilePath() && _requestingWorktreeFiles) CalcMatchedFilesForSearching(); + + _requestingWorktreeFiles = false; }); }); } @@ -2781,6 +2909,7 @@ namespace SourceGit.ViewModels private string _searchCommitFilter = string.Empty; private List _searchedCommits = new List(); private Models.Commit _selectedSearchedCommit = null; + private bool _requestingWorktreeFiles = false; private List _worktreeFiles = null; private List _matchedFilesForSearching = null; @@ -2804,6 +2933,6 @@ namespace SourceGit.ViewModels private Models.BisectState _bisectState = Models.BisectState.None; private bool _isBisectCommandRunning = false; - private string _navigateToBranchDelayed = string.Empty; + private string _navigateToCommitDelayed = string.Empty; } } diff --git a/src/ViewModels/ResetWithoutCheckout.cs b/src/ViewModels/ResetWithoutCheckout.cs new file mode 100644 index 00000000..3a9582ef --- /dev/null +++ b/src/ViewModels/ResetWithoutCheckout.cs @@ -0,0 +1,53 @@ +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class ResetWithoutCheckout : Popup + { + public Models.Branch Target + { + get; + } + + public object To + { + get; + } + + public ResetWithoutCheckout(Repository repo, Models.Branch target, Models.Branch to) + { + _repo = repo; + _revision = to.Head; + Target = target; + To = to; + } + + public ResetWithoutCheckout(Repository repo, Models.Branch target, Models.Commit to) + { + _repo = repo; + _revision = to.SHA; + Target = target; + To = to; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = $"Reset {Target.Name} to {_revision} ..."; + + var log = _repo.CreateLog($"Reset '{Target.Name}' to '{_revision}'"); + Use(log); + + return Task.Run(() => + { + var succ = Commands.Branch.Create(_repo.FullPath, Target.Name, _revision, true, log); + log.Complete(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return succ; + }); + } + + private readonly Repository _repo = null; + private readonly string _revision = string.Empty; + } +} diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index 3b5717a6..1dad7593 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -10,7 +10,7 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public class RevisionCompare : ObservableObject + public class RevisionCompare : ObservableObject, IDisposable { public object StartPoint { @@ -83,7 +83,7 @@ namespace SourceGit.ViewModels Task.Run(Refresh); } - public void Cleanup() + public void Dispose() { _repo = null; _startPoint = null; diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index 87e36b86..b974d427 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -11,7 +11,7 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public class StashesPage : ObservableObject + public class StashesPage : ObservableObject, IDisposable { public List Stashes { @@ -69,7 +69,7 @@ namespace SourceGit.ViewModels changes = new Commands.CompareRevisions(_repo.FullPath, $"{value.SHA}^", value.SHA).Result(); if (value.Parents.Count == 3) { - var untracked = new Commands.CompareRevisions(_repo.FullPath, "4b825dc642cb6eb9a060e54bf8d69288fbee4904", value.Parents[2]).Result(); + var untracked = new Commands.CompareRevisions(_repo.FullPath, Models.Commit.EmptyTreeSHA1, value.Parents[2]).Result(); var needSort = changes.Count > 0; foreach (var c in untracked) @@ -107,7 +107,7 @@ namespace SourceGit.ViewModels if (value == null) DiffContext = null; else if (value.Index == Models.ChangeState.Added && _selectedStash.Parents.Count == 3) - DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption("4b825dc642cb6eb9a060e54bf8d69288fbee4904", _selectedStash.Parents[2], value), _diffContext); + DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], value), _diffContext); else DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, value), _diffContext); } @@ -125,14 +125,13 @@ namespace SourceGit.ViewModels _repo = repo; } - public void Cleanup() + public void Dispose() { + _stashes?.Clear(); + _changes?.Clear(); + _repo = null; - if (_stashes != null) - _stashes.Clear(); _selectedStash = null; - if (_changes != null) - _changes.Clear(); _selectedChange = null; _diffContext = null; } @@ -183,7 +182,7 @@ namespace SourceGit.ViewModels foreach (var c in _changes) { if (c.Index == Models.ChangeState.Added && _selectedStash.Parents.Count == 3) - opts.Add(new Models.DiffOption("4b825dc642cb6eb9a060e54bf8d69288fbee4904", _selectedStash.Parents[2], c)); + opts.Add(new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], c)); else opts.Add(new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, c)); } diff --git a/src/ViewModels/SubmoduleCollection.cs b/src/ViewModels/SubmoduleCollection.cs index 4600496e..7def3ced 100644 --- a/src/ViewModels/SubmoduleCollection.cs +++ b/src/ViewModels/SubmoduleCollection.cs @@ -9,7 +9,7 @@ namespace SourceGit.ViewModels { public class SubmoduleTreeNode : ObservableObject { - public string FullPath { get; set; } = string.Empty; + public string FullPath { get; private set; } = string.Empty; public int Depth { get; private set; } = 0; public Models.Submodule Module { get; private set; } = null; public List Children { get; private set; } = []; diff --git a/src/ViewModels/TagCollection.cs b/src/ViewModels/TagCollection.cs index ce9c9508..b6be1a0a 100644 --- a/src/ViewModels/TagCollection.cs +++ b/src/ViewModels/TagCollection.cs @@ -23,7 +23,7 @@ namespace SourceGit.ViewModels public class TagTreeNode : ObservableObject { - public string FullPath { get; set; } + public string FullPath { get; private set; } public int Depth { get; private set; } = 0; public Models.Tag Tag { get; private set; } = null; public TagTreeNodeToolTip ToolTip { get; private set; } = null; diff --git a/src/ViewModels/UpdateSubmodules.cs b/src/ViewModels/UpdateSubmodules.cs index c2f23133..df2d5565 100644 --- a/src/ViewModels/UpdateSubmodules.cs +++ b/src/ViewModels/UpdateSubmodules.cs @@ -65,14 +65,9 @@ namespace SourceGit.ViewModels return Task.Run(() => { - foreach (var submodule in targets) - { - new Commands.Submodule(_repo.FullPath).Use(log).Update( - submodule, - EnableInit, - EnableRecursive, - EnableRemote); - } + new Commands.Submodule(_repo.FullPath) + .Use(log) + .Update(targets, EnableInit, EnableRecursive, EnableRemote); log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 762f1176..7c2d95f9 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -11,7 +11,7 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public class WorkingCopy : ObservableObject + public class WorkingCopy : ObservableObject, IDisposable { public bool IncludeUntracked { @@ -210,7 +210,7 @@ namespace SourceGit.ViewModels _repo = repo; } - public void Cleanup() + public void Dispose() { _repo = null; _inProgressContext = null; @@ -244,7 +244,7 @@ namespace SourceGit.ViewModels // Just force refresh selected changes. Dispatcher.UIThread.Invoke(() => { - HasUnsolvedConflicts = _cached.Find(x => x.IsConflict) != null; + HasUnsolvedConflicts = _cached.Find(x => x.IsConflicted) != null; UpdateDetail(); UpdateInProgressState(); @@ -276,7 +276,7 @@ namespace SourceGit.ViewModels if (c.WorkTree != Models.ChangeState.None) { unstaged.Add(c); - hasConflict |= c.IsConflict; + hasConflict |= c.IsConflicted; } } @@ -378,7 +378,7 @@ namespace SourceGit.ViewModels foreach (var change in changes) { - if (!change.IsConflict) + if (!change.IsConflicted) continue; if (change.WorkTree == Models.ChangeState.Deleted) @@ -420,7 +420,7 @@ namespace SourceGit.ViewModels foreach (var change in changes) { - if (!change.IsConflict) + if (!change.IsConflicted) continue; if (change.Index == Models.ChangeState.Deleted) @@ -547,17 +547,17 @@ namespace SourceGit.ViewModels public void Commit() { - DoCommit(false, false, false); + DoCommit(false, false); } public void CommitWithAutoStage() { - DoCommit(true, false, false); + DoCommit(true, false); } public void CommitWithPush() { - DoCommit(false, true, false); + DoCommit(false, true); } public ContextMenu CreateContextMenuForUnstagedChanges() @@ -594,7 +594,7 @@ namespace SourceGit.ViewModels menu.Items.Add(openWith); menu.Items.Add(new MenuItem() { Header = "-" }); - if (change.IsConflict) + if (change.IsConflicted) { var useTheirs = new MenuItem(); useTheirs.Icon = App.CreateMenuIcon("Icons.Incoming"); @@ -949,7 +949,7 @@ namespace SourceGit.ViewModels var hasNonConflicts = false; foreach (var change in _selectedUnstaged) { - if (change.IsConflict) + if (change.IsConflicted) hasConflicts = true; else hasNonConflicts = true; @@ -1528,14 +1528,13 @@ namespace SourceGit.ViewModels if (_useAmend) { var head = new Commands.QuerySingleCommit(_repo.FullPath, "HEAD").Result(); - return new Commands.QueryStagedChangesWithAmend(_repo.FullPath, head.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : $"{head.SHA}^").Result(); + return new Commands.QueryStagedChangesWithAmend(_repo.FullPath, head.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : $"{head.SHA}^").Result(); } var rs = new List(); foreach (var c in _cached) { - if (c.Index != Models.ChangeState.None && - c.Index != Models.ChangeState.Untracked) + if (c.Index != Models.ChangeState.None) rs.Add(c); } return rs; @@ -1681,7 +1680,7 @@ namespace SourceGit.ViewModels if (change == null) DetailContext = null; - else if (change.IsConflict && isUnstaged) + else if (change.IsConflicted) DetailContext = new Conflict(_repo, this, change); else DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged), _detailContext as DiffContext); @@ -1763,14 +1762,17 @@ namespace SourceGit.ViewModels { if (old.Count != cur.Count) return true; - - var oldSet = new HashSet(); + + var oldMap = new Dictionary(); foreach (var c in old) - oldSet.Add($"{c.Path}\n{c.WorkTree}\n{c.Index}"); + oldMap.Add(c.Path, c); foreach (var c in cur) { - if (!oldSet.Contains($"{c.Path}\n{c.WorkTree}\n{c.Index}")) + if (!oldMap.TryGetValue(c.Path, out var o)) + return true; + + if (o.Index != c.Index || o.WorkTree != c.WorkTree) return true; } diff --git a/src/Views/Blame.axaml.cs b/src/Views/Blame.axaml.cs index 4342651e..00342b0b 100644 --- a/src/Views/Blame.axaml.cs +++ b/src/Views/Blame.axaml.cs @@ -296,8 +296,6 @@ namespace SourceGit.Views TextArea.LeftMargins.Add(new CommitInfoMargin(this) { Margin = new Thickness(8, 0) }); TextArea.LeftMargins.Add(new VerticalSeparatorMargin(this)); TextArea.Caret.PositionChanged += OnTextAreaCaretPositionChanged; - TextArea.LayoutUpdated += OnTextAreaLayoutUpdated; - TextArea.PointerWheelChanged += OnTextAreaPointerWheelChanged; TextArea.TextView.ContextRequested += OnTextViewContextRequested; TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged; TextArea.TextView.Margin = new Thickness(4, 0); @@ -341,8 +339,6 @@ namespace SourceGit.Views TextArea.LeftMargins.Clear(); TextArea.Caret.PositionChanged -= OnTextAreaCaretPositionChanged; - TextArea.LayoutUpdated -= OnTextAreaLayoutUpdated; - TextArea.PointerWheelChanged -= OnTextAreaPointerWheelChanged; TextArea.TextView.ContextRequested -= OnTextViewContextRequested; TextArea.TextView.VisualLinesChanged -= OnTextViewVisualLinesChanged; @@ -392,42 +388,21 @@ namespace SourceGit.Views InvalidateVisual(); } - private void OnTextAreaLayoutUpdated(object sender, EventArgs e) - { - if (TextArea.IsFocused) - InvalidateVisual(); - } - - private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e) - { - if (!TextArea.IsFocused && !string.IsNullOrEmpty(_highlight)) - Focus(); - } - private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) { var selected = SelectedText; if (string.IsNullOrEmpty(selected)) return; - var copy = new MenuItem() { Header = App.Text("Copy") }; + var copy = new MenuItem(); + copy.Header = App.Text("Copy"); + copy.Icon = App.CreateMenuIcon("Icons.Copy"); copy.Click += (_, ev) => { App.CopyText(selected); ev.Handled = true; }; - if (this.FindResource("Icons.Copy") is StreamGeometry geo) - { - copy.Icon = new Avalonia.Controls.Shapes.Path() - { - Width = 10, - Height = 10, - Stretch = Stretch.Fill, - Data = geo, - }; - } - var menu = new ContextMenu(); menu.Items.Add(copy); menu.Open(TextArea.TextView); @@ -445,6 +420,8 @@ namespace SourceGit.Views break; } } + + InvalidateVisual(); } private TextMate.Installation _textMate = null; diff --git a/src/Views/ChangeCollectionView.axaml b/src/Views/ChangeCollectionView.axaml index 805028d0..36de5685 100644 --- a/src/Views/ChangeCollectionView.axaml +++ b/src/Views/ChangeCollectionView.axaml @@ -30,7 +30,8 @@ - @@ -66,7 +67,8 @@ - @@ -98,7 +100,8 @@ - diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 6ef79861..00499fce 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -145,6 +145,7 @@ namespace SourceGit.Views removeCount++; } + tree.Rows.RemoveRange(idx + 1, removeCount); } } @@ -209,6 +210,13 @@ namespace SourceGit.Views return null; } + public void TakeFocus() + { + var container = this.FindDescendantOfType(); + if (container is { IsFocused: false }) + container.Focus(); + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); diff --git a/src/Views/ChangeStatusIcon.cs b/src/Views/ChangeStatusIcon.cs index 7dbd0beb..c1185174 100644 --- a/src/Views/ChangeStatusIcon.cs +++ b/src/Views/ChangeStatusIcon.cs @@ -48,21 +48,16 @@ namespace SourceGit.Views EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), }, new LinearGradientBrush - { - GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) }, - StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), - EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), - }, - new LinearGradientBrush { GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) }, StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), }, + Brushes.OrangeRed, ]; - private static readonly string[] INDICATOR = ["?", "±", "T", "+", "−", "➜", "❏", "U", "★"]; - private static readonly string[] TIPS = ["Unknown", "Modified", "Type Changed", "Added", "Deleted", "Renamed", "Copied", "Unmerged", "Untracked"]; + private static readonly string[] INDICATOR = ["?", "±", "T", "+", "−", "➜", "❏", "★", "!"]; + private static readonly string[] TIPS = ["Unknown", "Modified", "Type Changed", "Added", "Deleted", "Renamed", "Copied", "Untracked", "Conflict"]; public static readonly StyledProperty IsUnstagedChangeProperty = AvaloniaProperty.Register(nameof(IsUnstagedChange)); @@ -93,16 +88,8 @@ namespace SourceGit.Views string indicator; if (IsUnstagedChange) { - if (Change.IsConflict) - { - background = Brushes.OrangeRed; - indicator = "!"; - } - else - { - background = BACKGROUNDS[(int)Change.WorkTree]; - indicator = INDICATOR[(int)Change.WorkTree]; - } + background = BACKGROUNDS[(int)Change.WorkTree]; + indicator = INDICATOR[(int)Change.WorkTree]; } else { @@ -139,7 +126,7 @@ namespace SourceGit.Views } if (isUnstaged) - ToolTip.SetTip(this, c.IsConflict ? "Conflict" : TIPS[(int)c.WorkTree]); + ToolTip.SetTip(this, TIPS[(int)c.WorkTree]); else ToolTip.SetTip(this, TIPS[(int)c.Index]); diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index 80f96c48..4dafee37 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.CommitChanges" x:DataType="vm:CommitDetail"> @@ -14,7 +15,7 @@ - + + + + + + + + + - + + + - diff --git a/src/Views/DeinitSubmodule.axaml b/src/Views/DeinitSubmodule.axaml new file mode 100644 index 00000000..d3e0b641 --- /dev/null +++ b/src/Views/DeinitSubmodule.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/src/Views/FastForwardWithoutCheckout.axaml.cs b/src/Views/DeinitSubmodule.axaml.cs similarity index 52% rename from src/Views/FastForwardWithoutCheckout.axaml.cs rename to src/Views/DeinitSubmodule.axaml.cs index 0e3ba20d..18dd9944 100644 --- a/src/Views/FastForwardWithoutCheckout.axaml.cs +++ b/src/Views/DeinitSubmodule.axaml.cs @@ -2,9 +2,9 @@ using Avalonia.Controls; namespace SourceGit.Views { - public partial class FastForwardWithoutCheckout : UserControl + public partial class DeinitSubmodule : UserControl { - public FastForwardWithoutCheckout() + public DeinitSubmodule() { InitializeComponent(); } diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index b6d1fd65..85da69d1 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -178,12 +178,12 @@