diff --git a/TRANSLATION.md b/TRANSLATION.md
index facab0ab2..479f5188d 100644
--- a/TRANSLATION.md
+++ b/TRANSLATION.md
@@ -6,55 +6,19 @@ This document shows the translation status of each locale file in the repository
### 
-### 
+### 
Missing keys in de_DE.axaml
-- Text.Bisect
-- Text.Bisect.Abort
-- Text.Bisect.Bad
-- Text.Bisect.Detecting
-- Text.Bisect.Good
-- Text.Bisect.Skip
-- Text.Bisect.WaitingForRange
-- Text.BranchUpstreamInvalid
-- Text.CommitCM.CopyAuthor
-- Text.CommitCM.CopyCommitter
-- Text.CommitCM.CopySubject
-- Text.CommitMessageTextBox.SubjectCount
-- Text.Configure.CustomAction.WaitForExit
-- Text.Configure.Git.PreferredMergeMode
-- Text.Configure.IssueTracker.AddSampleAzure
-- Text.ConfirmEmptyCommit.Continue
-- Text.ConfirmEmptyCommit.NoLocalChanges
-- Text.ConfirmEmptyCommit.StageAllThenCommit
-- Text.ConfirmEmptyCommit.WithLocalChanges
-- Text.CopyFullPath
-- Text.Diff.First
-- Text.Diff.Last
-- Text.Preferences.AI.Streaming
-- Text.Preferences.Appearance.EditorTabWidth
-- Text.Preferences.General.ShowTagsInGraph
-- Text.Preferences.Git.IgnoreCRAtEOLInDiff
-- Text.Repository.Search.ByContent
-- Text.Repository.ViewLogs
-- Text.StashCM.SaveAsPatch
-- Text.ViewLogs
-- Text.ViewLogs.Clear
-- Text.ViewLogs.CopyLog
-- Text.ViewLogs.Delete
-- Text.WorkingCopy.ConfirmCommitWithFilter
-- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
-- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
-- Text.WorkingCopy.Conflicts.UseMine
-- Text.WorkingCopy.Conflicts.UseTheirs
+- Text.GitFlow.FinishWithPush
+- Text.GitFlow.FinishWithSquash
### 
-### 
+### 
Missing keys in fr_FR.axaml
@@ -66,6 +30,7 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.Good
- Text.Bisect.Skip
- Text.Bisect.WaitingForRange
+- Text.Checkout.RecurseSubmodules
- Text.CommitCM.CopyAuthor
- Text.CommitCM.CopyCommitter
- Text.CommitCM.CopySubject
@@ -75,9 +40,15 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.NoLocalChanges
- Text.ConfirmEmptyCommit.StageAllThenCommit
- Text.ConfirmEmptyCommit.WithLocalChanges
+- Text.GitFlow.FinishWithPush
+- Text.GitFlow.FinishWithSquash
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
+- Text.Repository.BranchSort
+- Text.Repository.BranchSort.ByCommitterDate
+- Text.Repository.BranchSort.ByName
- Text.Repository.Search.ByContent
- Text.Repository.ViewLogs
+- Text.Repository.Visit
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
@@ -90,7 +61,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in it_IT.axaml
@@ -102,6 +73,7 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.Good
- Text.Bisect.Skip
- Text.Bisect.WaitingForRange
+- Text.Checkout.RecurseSubmodules
- Text.CommitCM.CopyAuthor
- Text.CommitCM.CopyCommitter
- Text.CommitCM.CopySubject
@@ -112,10 +84,16 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.StageAllThenCommit
- Text.ConfirmEmptyCommit.WithLocalChanges
- Text.CopyFullPath
+- Text.GitFlow.FinishWithPush
+- Text.GitFlow.FinishWithSquash
- Text.Preferences.General.ShowTagsInGraph
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
+- Text.Repository.BranchSort
+- Text.Repository.BranchSort.ByCommitterDate
+- Text.Repository.BranchSort.ByName
- Text.Repository.Search.ByContent
- Text.Repository.ViewLogs
+- Text.Repository.Visit
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
@@ -128,7 +106,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in ja_JP.axaml
@@ -140,6 +118,7 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.Good
- Text.Bisect.Skip
- Text.Bisect.WaitingForRange
+- Text.Checkout.RecurseSubmodules
- Text.CommitCM.CopyAuthor
- Text.CommitCM.CopyCommitter
- Text.CommitCM.CopySubject
@@ -149,11 +128,17 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.NoLocalChanges
- Text.ConfirmEmptyCommit.StageAllThenCommit
- Text.ConfirmEmptyCommit.WithLocalChanges
+- Text.GitFlow.FinishWithPush
+- Text.GitFlow.FinishWithSquash
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
+- Text.Repository.BranchSort
+- Text.Repository.BranchSort.ByCommitterDate
+- Text.Repository.BranchSort.ByName
- Text.Repository.FilterCommits
- Text.Repository.Search.ByContent
- Text.Repository.Tags.OrderByNameDes
- Text.Repository.ViewLogs
+- Text.Repository.Visit
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
@@ -166,7 +151,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in pt_BR.axaml
@@ -187,6 +172,7 @@ This document shows the translation status of each locale file in the repository
- Text.BranchCM.CustomAction
- Text.BranchCM.MergeMultiBranches
- Text.BranchUpstreamInvalid
+- Text.Checkout.RecurseSubmodules
- Text.Clone.RecurseSubmodules
- Text.CommitCM.CopyAuthor
- Text.CommitCM.CopyCommitter
@@ -215,6 +201,8 @@ This document shows the translation status of each locale file in the repository
- Text.Diff.UseBlockNavigation
- Text.Fetch.Force
- Text.FileCM.ResolveUsing
+- Text.GitFlow.FinishWithPush
+- Text.GitFlow.FinishWithSquash
- Text.Hotkeys.Global.Clone
- Text.InProgress.CherryPick.Head
- Text.InProgress.Merge.Operating
@@ -232,6 +220,9 @@ 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.Repository.BranchSort
+- Text.Repository.BranchSort.ByCommitterDate
+- Text.Repository.BranchSort.ByName
- Text.Repository.FilterCommits
- Text.Repository.HistoriesLayout
- Text.Repository.HistoriesLayout.Horizontal
@@ -247,6 +238,7 @@ This document shows the translation status of each locale file in the repository
- Text.Repository.Tags.Sort
- Text.Repository.UseRelativeTimeInHistories
- Text.Repository.ViewLogs
+- Text.Repository.Visit
- Text.SetUpstream
- Text.SetUpstream.Local
- Text.SetUpstream.Unset
@@ -271,7 +263,7 @@ This document shows the translation status of each locale file in the repository
### 
-### 
+### 
Missing keys in ta_IN.axaml
@@ -283,6 +275,7 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.Good
- Text.Bisect.Skip
- Text.Bisect.WaitingForRange
+- Text.Checkout.RecurseSubmodules
- Text.CommitCM.CopyAuthor
- Text.CommitCM.CopyCommitter
- Text.CommitCM.CopySubject
@@ -292,9 +285,15 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.NoLocalChanges
- Text.ConfirmEmptyCommit.StageAllThenCommit
- Text.ConfirmEmptyCommit.WithLocalChanges
+- Text.GitFlow.FinishWithPush
+- Text.GitFlow.FinishWithSquash
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
+- Text.Repository.BranchSort
+- Text.Repository.BranchSort.ByCommitterDate
+- Text.Repository.BranchSort.ByName
- Text.Repository.Search.ByContent
- Text.Repository.ViewLogs
+- Text.Repository.Visit
- Text.UpdateSubmodules.Target
- Text.ViewLogs
- Text.ViewLogs.Clear
@@ -307,7 +306,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in uk_UA.axaml
@@ -319,14 +318,21 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.Good
- Text.Bisect.Skip
- Text.Bisect.WaitingForRange
+- Text.Checkout.RecurseSubmodules
- Text.CommitCM.CopyAuthor
- Text.CommitCM.CopyCommitter
- Text.CommitCM.CopySubject
- Text.CommitMessageTextBox.SubjectCount
- Text.ConfigureWorkspace.Name
+- Text.GitFlow.FinishWithPush
+- Text.GitFlow.FinishWithSquash
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
+- Text.Repository.BranchSort
+- Text.Repository.BranchSort.ByCommitterDate
+- Text.Repository.BranchSort.ByName
- Text.Repository.Search.ByContent
- Text.Repository.ViewLogs
+- Text.Repository.Visit
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
diff --git a/VERSION b/VERSION
index fe39b2a2b..52dc0d52e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2025.16
\ No newline at end of file
+2025.17
\ No newline at end of file
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index c54b616db..6e45164dc 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -78,7 +78,7 @@ public static AppBuilder BuildAvaloniaApp()
return builder;
}
- private static void LogException(Exception ex)
+ public static void LogException(Exception ex)
{
if (ex == null)
return;
diff --git a/src/Commands/Checkout.cs b/src/Commands/Checkout.cs
index c39c28aee..6f63ae603 100644
--- a/src/Commands/Checkout.cs
+++ b/src/Commands/Checkout.cs
@@ -11,15 +11,24 @@ public Checkout(string repo)
Context = repo;
}
- public bool Branch(string branch)
+ public bool Branch(string branch, bool force)
{
- Args = $"checkout --recurse-submodules --progress {branch}";
+ var option = force ? "--force" : string.Empty;
+ Args = $"checkout {option} --progress {branch}";
return Exec();
}
- public bool Branch(string branch, string basedOn)
+ public bool Branch(string branch, string basedOn, bool force)
{
- Args = $"checkout --recurse-submodules --progress -b {branch} {basedOn}";
+ var option = force ? "--force" : string.Empty;
+ Args = $"checkout --progress -b {branch} {basedOn}";
+ return Exec();
+ }
+
+ public bool Commit(string commitId, bool force)
+ {
+ var option = force ? "--force" : string.Empty;
+ Args = $"checkout {option} --detach --progress {commitId}";
return Exec();
}
@@ -56,11 +65,5 @@ public bool FileWithRevision(string file, string revision)
Args = $"checkout --no-overlay {revision} -- \"{file}\"";
return Exec();
}
-
- public bool Commit(string commitId)
- {
- Args = $"checkout --detach --progress {commitId}";
- return Exec();
- }
}
}
diff --git a/src/Commands/Clean.cs b/src/Commands/Clean.cs
index a10e58731..6ed749995 100644
--- a/src/Commands/Clean.cs
+++ b/src/Commands/Clean.cs
@@ -1,31 +1,12 @@
-using System.Collections.Generic;
-using System.Text;
-
-namespace SourceGit.Commands
+namespace SourceGit.Commands
{
public class Clean : Command
{
- public Clean(string repo, bool includeIgnored)
+ public Clean(string repo)
{
WorkingDirectory = repo;
Context = repo;
- Args = includeIgnored ? "clean -qfdx" : "clean -qfd";
- }
-
- public Clean(string repo, List files)
- {
- var builder = new StringBuilder();
- builder.Append("clean -qfd --");
- foreach (var f in files)
- {
- builder.Append(" \"");
- builder.Append(f);
- builder.Append("\"");
- }
-
- WorkingDirectory = repo;
- Context = repo;
- Args = builder.ToString();
+ Args = "clean -qfdx";
}
}
}
diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs
index a354b8162..6af0a3cc6 100644
--- a/src/Commands/Diff.cs
+++ b/src/Commands/Diff.cs
@@ -28,11 +28,11 @@ public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespa
Context = repo;
if (ignoreWhitespace)
- Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-all-space --unified={unified} {opt}";
+ Args = $"diff --no-ext-diff --patch --ignore-all-space --unified={unified} {opt}";
else if (Models.DiffOption.IgnoreCRAtEOL)
- Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
+ Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
else
- Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --unified={unified} {opt}";
+ Args = $"diff --no-ext-diff --patch --unified={unified} {opt}";
}
public Models.DiffResult Result()
diff --git a/src/Commands/Discard.cs b/src/Commands/Discard.cs
index f837df528..fcbe85915 100644
--- a/src/Commands/Discard.cs
+++ b/src/Commands/Discard.cs
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
+using System.IO;
+
+using Avalonia.Threading;
namespace SourceGit.Commands
{
@@ -7,33 +10,71 @@ public static class Discard
{
public static void All(string repo, bool includeIgnored, Models.ICommandLog log)
{
+ var changes = new QueryLocalChanges(repo).Result();
+ try
+ {
+ foreach (var c in changes)
+ {
+ if (c.WorkTree == Models.ChangeState.Untracked ||
+ c.WorkTree == Models.ChangeState.Added ||
+ c.Index == Models.ChangeState.Added ||
+ c.Index == Models.ChangeState.Renamed)
+ {
+ var fullPath = Path.Combine(repo, c.Path);
+ if (Directory.Exists(fullPath))
+ Directory.Delete(fullPath, true);
+ else
+ File.Delete(fullPath);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
+ });
+ }
+
new Restore(repo) { Log = log }.Exec();
- new Clean(repo, includeIgnored) { Log = log }.Exec();
+ if (includeIgnored)
+ new Clean(repo) { Log = log }.Exec();
}
public static void Changes(string repo, List changes, Models.ICommandLog log)
{
- var needClean = new List();
- var needCheckout = new List();
+ var restores = new List();
- foreach (var c in changes)
+ try
{
- if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
- needClean.Add(c.Path);
- else
- needCheckout.Add(c.Path);
+ foreach (var c in changes)
+ {
+ if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
+ {
+ var fullPath = Path.Combine(repo, c.Path);
+ if (Directory.Exists(fullPath))
+ Directory.Delete(fullPath, true);
+ else
+ File.Delete(fullPath);
+ }
+ else
+ {
+ restores.Add(c.Path);
+ }
+ }
}
-
- for (int i = 0; i < needClean.Count; i += 10)
+ catch (Exception e)
{
- var count = Math.Min(10, needClean.Count - i);
- new Clean(repo, needClean.GetRange(i, count)) { Log = log }.Exec();
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
+ });
}
- for (int i = 0; i < needCheckout.Count; i += 10)
+ for (int i = 0; i < restores.Count; i += 10)
{
- var count = Math.Min(10, needCheckout.Count - i);
- new Restore(repo, needCheckout.GetRange(i, count), "--worktree --recurse-submodules") { Log = log }.Exec();
+ var count = Math.Min(10, restores.Count - i);
+ new Restore(repo, restores.GetRange(i, count), "--worktree --recurse-submodules") { Log = log }.Exec();
}
}
}
diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs
index 833d268d0..e4fab2355 100644
--- a/src/Commands/GitFlow.cs
+++ b/src/Commands/GitFlow.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Text;
using Avalonia.Threading;
@@ -134,7 +135,7 @@ public static bool Start(string repo, string type, string name, Models.ICommandL
return start.Exec();
}
- public static bool Finish(string repo, string type, string name, bool keepBranch, Models.ICommandLog log)
+ public static bool Finish(string repo, string type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log)
{
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
{
@@ -146,11 +147,22 @@ public static bool Finish(string repo, string type, string name, bool keepBranch
return false;
}
- var option = keepBranch ? "-k" : string.Empty;
+ var builder = new StringBuilder();
+ builder.Append("flow ");
+ builder.Append(type);
+ builder.Append(" finish ");
+ if (squash)
+ builder.Append("--squash ");
+ if (push)
+ builder.Append("--push ");
+ if (keepBranch)
+ builder.Append("-k ");
+ builder.Append(name);
+
var finish = new Command();
finish.WorkingDirectory = repo;
finish.Context = repo;
- finish.Args = $"flow {type} finish {option} {name}";
+ finish.Args = builder.ToString();
finish.Log = log;
return finish.Exec();
}
diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs
index 39b771892..19514954e 100644
--- a/src/Commands/QueryBranches.cs
+++ b/src/Commands/QueryBranches.cs
@@ -14,7 +14,7 @@ public QueryBranches(string repo)
{
WorkingDirectory = repo;
Context = repo;
- Args = "branch -l --all -v --format=\"%(refname)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
+ Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
}
public List Result()
@@ -49,7 +49,7 @@ public QueryBranches(string repo)
private Models.Branch ParseLine(string line)
{
var parts = line.Split('\0');
- if (parts.Length != 5)
+ if (parts.Length != 6)
return null;
var branch = new Models.Branch();
@@ -83,12 +83,13 @@ private Models.Branch ParseLine(string line)
}
branch.FullName = refName;
- branch.Head = parts[1];
- branch.IsCurrent = parts[2] == "*";
- branch.Upstream = parts[3];
+ branch.CommitterDate = ulong.Parse(parts[1]);
+ branch.Head = parts[2];
+ branch.IsCurrent = parts[3] == "*";
+ branch.Upstream = parts[4];
branch.IsUpstreamGone = false;
- if (branch.IsLocal && !string.IsNullOrEmpty(parts[4]) && !parts[4].Equals("=", StringComparison.Ordinal))
+ if (branch.IsLocal && !string.IsNullOrEmpty(parts[5]) && !parts[5].Equals("=", StringComparison.Ordinal))
branch.TrackStatus = new QueryTrackStatus(WorkingDirectory, branch.Name, branch.Upstream).Result();
else
branch.TrackStatus = new Models.BranchTrackStatus();
diff --git a/src/Commands/Submodule.cs b/src/Commands/Submodule.cs
index e4f35fca8..c8b676ea5 100644
--- a/src/Commands/Submodule.cs
+++ b/src/Commands/Submodule.cs
@@ -1,4 +1,7 @@
-namespace SourceGit.Commands
+using System.Collections.Generic;
+using System.Text;
+
+namespace SourceGit.Commands
{
public class Submodule : Command
{
@@ -42,6 +45,28 @@ public bool Update(string module, bool init, bool recursive, bool useRemote)
return Exec();
}
+ public bool Update(List modules, bool init, bool recursive, bool useRemote)
+ {
+ var builder = new StringBuilder();
+ builder.Append("submodule update");
+
+ if (init)
+ builder.Append(" --init");
+ if (recursive)
+ builder.Append(" --recursive");
+ if (useRemote)
+ builder.Append(" --remote");
+ if (modules.Count > 0)
+ {
+ builder.Append(" --");
+ foreach (var module in modules)
+ builder.Append($" \"{module.Path}\"");
+ }
+
+ Args = builder.ToString();
+ return Exec();
+ }
+
public bool Delete(string relativePath)
{
Args = $"submodule deinit -f \"{relativePath}\"";
diff --git a/src/Models/Branch.cs b/src/Models/Branch.cs
index d0ac19907..7146da3fa 100644
--- a/src/Models/Branch.cs
+++ b/src/Models/Branch.cs
@@ -23,10 +23,17 @@ public override string ToString()
}
}
+ public enum BranchSortMode
+ {
+ Name = 0,
+ CommitterDate,
+ }
+
public class Branch
{
public string Name { get; set; }
public string FullName { get; set; }
+ public ulong CommitterDate { get; set; }
public string Head { get; set; }
public bool IsLocal { get; set; }
public bool IsCurrent { get; set; }
diff --git a/src/Models/CommitGraph.cs b/src/Models/CommitGraph.cs
index 772097513..01488656a 100644
--- a/src/Models/CommitGraph.cs
+++ b/src/Models/CommitGraph.cs
@@ -64,8 +64,8 @@ public static CommitGraph Parse(List commits, bool firstParentOnlyEnable
{
const double unitWidth = 12;
const double halfWidth = 6;
- const double unitHeight = 28;
- const double halfHeight = 14;
+ const double unitHeight = 1;
+ const double halfHeight = 0.5;
var temp = new CommitGraph();
var unsolved = new List();
diff --git a/src/Models/CommitLink.cs b/src/Models/CommitLink.cs
index 544ee3c33..2891e5d65 100644
--- a/src/Models/CommitLink.cs
+++ b/src/Models/CommitLink.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
namespace SourceGit.Models
@@ -22,24 +22,24 @@ public static List Get(List remotes)
{
if (remote.TryGetVisitURL(out var url))
{
- var trimmedUrl = url;
+ var trimmedUrl = url.AsSpan();
if (url.EndsWith(".git"))
- trimmedUrl = url.Substring(0, url.Length - 4);
+ trimmedUrl = url.AsSpan(0, url.Length - 4);
if (url.StartsWith("https://github.com/", StringComparison.Ordinal))
- outs.Add(new($"Github ({trimmedUrl.Substring(19)})", $"{url}/commit/"));
+ outs.Add(new($"Github ({trimmedUrl.Slice(19)})", $"{url}/commit/"));
else if (url.StartsWith("https://gitlab.", StringComparison.Ordinal))
- outs.Add(new($"GitLab ({trimmedUrl.Substring(trimmedUrl.Substring(15).IndexOf('/') + 16)})", $"{url}/-/commit/"));
+ outs.Add(new($"GitLab ({trimmedUrl.Slice(trimmedUrl.Slice(15).IndexOf('/') + 16)})", $"{url}/-/commit/"));
else if (url.StartsWith("https://gitee.com/", StringComparison.Ordinal))
- outs.Add(new($"Gitee ({trimmedUrl.Substring(18)})", $"{url}/commit/"));
+ outs.Add(new($"Gitee ({trimmedUrl.Slice(18)})", $"{url}/commit/"));
else if (url.StartsWith("https://bitbucket.org/", StringComparison.Ordinal))
- outs.Add(new($"BitBucket ({trimmedUrl.Substring(22)})", $"{url}/commits/"));
+ outs.Add(new($"BitBucket ({trimmedUrl.Slice(22)})", $"{url}/commits/"));
else if (url.StartsWith("https://codeberg.org/", StringComparison.Ordinal))
- outs.Add(new($"Codeberg ({trimmedUrl.Substring(21)})", $"{url}/commit/"));
+ outs.Add(new($"Codeberg ({trimmedUrl.Slice(21)})", $"{url}/commit/"));
else if (url.StartsWith("https://gitea.org/", StringComparison.Ordinal))
- outs.Add(new($"Gitea ({trimmedUrl.Substring(18)})", $"{url}/commit/"));
+ outs.Add(new($"Gitea ({trimmedUrl.Slice(18)})", $"{url}/commit/"));
else if (url.StartsWith("https://git.sr.ht/", StringComparison.Ordinal))
- outs.Add(new($"sourcehut ({trimmedUrl.Substring(18)})", $"{url}/commit/"));
+ outs.Add(new($"sourcehut ({trimmedUrl.Slice(18)})", $"{url}/commit/"));
}
}
diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs
index 34a720333..9af032bb1 100644
--- a/src/Models/RepositorySettings.cs
+++ b/src/Models/RepositorySettings.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Text;
@@ -38,6 +38,18 @@ public bool OnlyHighlighCurrentBranchInHistories
set;
} = false;
+ public BranchSortMode LocalBranchSortMode
+ {
+ get;
+ set;
+ } = BranchSortMode.Name;
+
+ public BranchSortMode RemoteBranchSortMode
+ {
+ get;
+ set;
+ } = BranchSortMode.Name;
+
public TagSortMode TagSortMode
{
get;
@@ -110,6 +122,12 @@ public bool CheckoutBranchOnCreateBranch
set;
} = true;
+ public bool UpdateSubmodulesOnCheckoutBranch
+ {
+ get;
+ set;
+ } = true;
+
public AvaloniaList HistoriesFilters
{
get;
@@ -326,28 +344,28 @@ public string BuildHistoriesFilter()
if (filter.Mode == FilterMode.Included)
includedRefs.Add(filter.Pattern);
else if (filter.Mode == FilterMode.Excluded)
- excludedBranches.Add($"--exclude=\"{filter.Pattern.Substring(11)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
+ excludedBranches.Add($"--exclude=\"{filter.Pattern.AsSpan(11)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
}
else if (filter.Type == FilterType.LocalBranchFolder)
{
if (filter.Mode == FilterMode.Included)
- includedRefs.Add($"--branches={filter.Pattern.Substring(11)}/*");
+ includedRefs.Add($"--branches={filter.Pattern.AsSpan(11)}/*");
else if (filter.Mode == FilterMode.Excluded)
- excludedBranches.Add($"--exclude=\"{filter.Pattern.Substring(11)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
+ excludedBranches.Add($"--exclude=\"{filter.Pattern.AsSpan(11)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
}
else if (filter.Type == FilterType.RemoteBranch)
{
if (filter.Mode == FilterMode.Included)
includedRefs.Add(filter.Pattern);
else if (filter.Mode == FilterMode.Excluded)
- excludedRemotes.Add($"--exclude=\"{filter.Pattern.Substring(13)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
+ excludedRemotes.Add($"--exclude=\"{filter.Pattern.AsSpan(13)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
}
else if (filter.Type == FilterType.RemoteBranchFolder)
{
if (filter.Mode == FilterMode.Included)
- includedRefs.Add($"--remotes={filter.Pattern.Substring(13)}/*");
+ includedRefs.Add($"--remotes={filter.Pattern.AsSpan(13)}/*");
else if (filter.Mode == FilterMode.Excluded)
- excludedRemotes.Add($"--exclude=\"{filter.Pattern.Substring(13)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
+ excludedRemotes.Add($"--exclude=\"{filter.Pattern.AsSpan(13)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
}
else if (filter.Type == FilterType.Tag)
{
diff --git a/src/Models/ShellOrTerminal.cs b/src/Models/ShellOrTerminal.cs
index 3ada2cf99..7dfb22373 100644
--- a/src/Models/ShellOrTerminal.cs
+++ b/src/Models/ShellOrTerminal.cs
@@ -42,7 +42,8 @@ static ShellOrTerminal()
new ShellOrTerminal("mac-terminal", "Terminal", ""),
new ShellOrTerminal("iterm2", "iTerm", ""),
new ShellOrTerminal("warp", "Warp", ""),
- new ShellOrTerminal("ghostty", "Ghostty", "")
+ new ShellOrTerminal("ghostty", "Ghostty", ""),
+ new ShellOrTerminal("kitty", "kitty", "")
};
}
else
@@ -58,6 +59,7 @@ static ShellOrTerminal()
new ShellOrTerminal("foot", "Foot", "foot"),
new ShellOrTerminal("wezterm", "WezTerm", "wezterm"),
new ShellOrTerminal("ptyxis", "Ptyxis", "ptyxis"),
+ new ShellOrTerminal("kitty", "kitty", "kitty"),
new ShellOrTerminal("custom", "Custom", ""),
};
}
diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs
index d7ef47014..0966233f6 100644
--- a/src/Native/MacOS.cs
+++ b/src/Native/MacOS.cs
@@ -59,6 +59,8 @@ public string FindTerminal(Models.ShellOrTerminal shell)
return "Warp";
case "ghostty":
return "Ghostty";
+ case "kitty":
+ return "kitty";
}
return string.Empty;
diff --git a/src/Resources/Images/ShellIcons/kitty.png b/src/Resources/Images/ShellIcons/kitty.png
new file mode 100644
index 000000000..465c2863d
Binary files /dev/null and b/src/Resources/Images/ShellIcons/kitty.png differ
diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml
index 8eb49a3ec..30750931a 100644
--- a/src/Resources/Locales/de_DE.axaml
+++ b/src/Resources/Locales/de_DE.axaml
@@ -40,6 +40,13 @@
KEINE ALS UNVERÄNDERT ANGENOMMENEN DATEIEN
ENTFERNEN
BINÄRE DATEI NICHT UNTERSTÜTZT!!!
+ Bisect
+ Abbrechen
+ Schlecht
+ Bisecting. Ist der aktuelle HEAD gut oder fehlerhaft?
+ Gut
+ Überspringen
+ Bisecting. Aktuellen Commit als gut oder schlecht markieren und einen anderen auschecken.
Blame
BLAME WIRD BEI DIESER DATEI NICHT UNTERSTÜTZT!!!
Auschecken von ${0}$...
@@ -62,6 +69,7 @@
Benenne ${0}$ um...
Setze verfolgten Branch...
Branch Vergleich
+ Ungültiger upstream!
Bytes
ABBRECHEN
Auf Vorgänger-Revision zurücksetzen
@@ -78,6 +86,7 @@
Lokale Änderungen:
Verwerfen
Stashen & wieder anwenden
+ Alle Submodule updaten
Branch:
Cherry Pick
Quelle an Commit-Nachricht anhängen
@@ -102,8 +111,11 @@
Mehrere cherry-picken
Mit HEAD vergleichen
Mit Worktree vergleichen
+ Author
+ Committer
Information
SHA
+ Betreff
Benutzerdefinierte Aktion
Interactives Rebase von ${0}$ auf diesen Commit
Merge in ${0}$ hinein
@@ -135,6 +147,7 @@
SHA
Im Browser öffnen
Details
+ Betreff
Commit-Nachricht
Repository Einstellungen
COMMIT TEMPLATE
@@ -149,13 +162,16 @@
Branch
Commit
Repository
+ Auf Beenden der Aktion warten
Email Adresse
Email Adresse
GIT
Remotes automatisch fetchen
Minute(n)
Standard Remote
+ Bevorzugter Merge Modus
TICKETSYSTEM
+ Beispiel Azure DevOps Rule hinzufügen
Beispiel für Gitee Issue Regel einfügen
Beispiel für Gitee Pull Request Regel einfügen
Beispiel für Github-Regel hinzufügen
@@ -178,6 +194,10 @@
Farbe
Name
Zuletzt geöffnete Tabs beim Starten wiederherstellen
+ WEITER
+ Leerer Commit erkannt! Möchtest du trotzdem fortfahren (--allow-empty)?
+ ALLES STAGEN & COMMITTEN
+ Leerer Commit erkannt! Möchtest du trotzdem fortfahren (--allow-empty) oder alle Änderungen stagen und dann committen?
Konventionelle Commit-Hilfe
Breaking Change:
Geschlossenes Ticket:
@@ -187,6 +207,7 @@
Typ der Änderung:
Kopieren
Kopiere gesamten Text
+ Ganzen Pfad kopieren
Pfad kopieren
Branch erstellen...
Basierend auf:
@@ -236,7 +257,9 @@
ALT
Kopieren
Dateimodus geändert
+ Erste Differenz
Ignoriere Leerzeichenänderungen
+ Letzte Differenz
LFS OBJEKT ÄNDERUNG
Nächste Änderung
KEINE ÄNDERUNG ODER NUR ZEILEN-ENDE ÄNDERUNGEN
@@ -447,8 +470,10 @@
Modell
Name
Server
+ Streaming aktivieren
DARSTELLUNG
Standardschriftart
+ Editor Tab Breite
Schriftgröße
Standard
Texteditor
@@ -469,6 +494,7 @@
Commit-Historie
Zeige Autor Zeitpunkt anstatt Commit Zeitpunkt
Zeige Nachfolger in den Commit Details
+ Zeige Tags im Commit Graph
Längenvorgabe für Commit-Nachrichten
GIT
Aktiviere Auto-CRLF
@@ -476,6 +502,7 @@
Benutzer Email
Globale Git Benutzer Email
Aktivere --prune beim fetchen
+ Aktiviere --ignore-cr-at-eol beim Unterschied
Diese App setzt Git (>= 2.23.0) voraus
Installationspfad
Aktiviere HTTP SSL Verifizierung
@@ -549,6 +576,9 @@
Branch:
ABBRECHEN
Änderungen automatisch von Remote fetchen...
+ Sortieren
+ Nach Commit Datum
+ Nach Name
Aufräumen (GC & Prune)
Führt `git gc` auf diesem Repository aus.
Filter aufheben
@@ -583,6 +613,7 @@
Commit suchen
Autor
Committer
+ Inhalt
Dateiname
Commit-Nachricht
SHA
@@ -601,6 +632,8 @@
Sortiere
Öffne im Terminal
Verwende relative Zeitangaben in Verlauf
+ Logs ansehen
+ Öffne '{0}' im Browser
WORKTREES
WORKTREE HINZUFÜGEN
PRUNE
@@ -651,6 +684,7 @@
Lokale Änderungen stashen
Anwenden
Entfernen
+ Als Path speichern...
Stash entfernen
Entfernen:
Stashes
@@ -685,6 +719,10 @@
Submodul:
Verwende `--remote` Option
URL:
+ Logs
+ ALLES LÖSCHEN
+ Kopieren
+ Löschen
Warnung
Willkommensseite
Erstelle Gruppe
@@ -714,8 +752,13 @@
Klick-Ereignis auslösen
Commit (Bearbeitung)
Alle Änderungen stagen und committen
+ Du hast {0} Datei(en) gestaged, aber nur {1} werden angezeigt ({2} sind herausgefiltert). Willst du trotzdem fortfahren?
KONFLIKTE ERKANNT
+ EXTERNES MERGE-TOOL ÖFFNEN
+ ALLE KONFLIKTE IN EXTERNEM MERGE-TOOL ÖFFNEN
DATEI KONFLIKTE GELÖST
+ MEINE VERSION VERWENDEN
+ DEREN VERSION VERWENDEN
NICHT-VERFOLGTE DATEIEN INKLUDIEREN
KEINE BISHERIGEN COMMIT-NACHRICHTEN
KEINE COMMIT TEMPLATES
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index f4c62a90f..e2281a865 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -82,6 +82,7 @@
Local Changes:
Discard
Stash & Reapply
+ Update all submodules
Branch:
Cherry Pick
Append source to commit message
@@ -322,6 +323,8 @@
FLOW - Finish Hotfix
FLOW - Finish Release
Target:
+ Push to remote(s) after performing finish
+ Squash during merge
Hotfix:
Hotfix Prefix:
Initialize Git-Flow
@@ -571,6 +574,9 @@
Branch:
ABORT
Auto fetching changes from remotes...
+ Sort
+ By Committer Date
+ By Name
Cleanup(GC & Prune)
Run `git gc` command for this repository.
Clear all
@@ -601,7 +607,7 @@
Open in External Tools
Refresh
REMOTES
- ADD REMOTE
+ Add Remote
Search Commit
Author
Committer
@@ -614,10 +620,10 @@
SKIP
Statistics
SUBMODULES
- ADD SUBMODULE
- UPDATE SUBMODULE
+ Add Submodule
+ Update Submodule
TAGS
- NEW TAG
+ New Tag
By Creator Date
By Name (Ascending)
By Name (Descending)
@@ -625,9 +631,10 @@
Open in Terminal
Use relative time in histories
View Logs
+ Visit '{0}' in Browser
WORKTREES
- ADD WORKTREE
- PRUNE
+ Add Worktree
+ Prune
Git Repository URL
Reset Current Branch To Revision
Reset Mode:
diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml
index c9978b2a7..b11d0e98a 100644
--- a/src/Resources/Locales/es_ES.axaml
+++ b/src/Resources/Locales/es_ES.axaml
@@ -86,6 +86,7 @@
Cambios Locales:
Descartar
Stash & Reaplicar
+ Actualizar todos los submódulos
Rama:
Cherry Pick
Añadir fuente al mensaje de commit
@@ -101,7 +102,7 @@
Nombre Local:
Nombre del repositorio. Opcional.
Carpeta Padre:
- Inicializar y actualizar submodulos
+ Inicializar y actualizar submódulos
URL del Repositorio:
CERRAR
Editor
@@ -326,6 +327,8 @@
FLOW - Finalizar Hotfix
FLOW - Finalizar Release
Destino:
+ Push al/los remoto(s) después de Finalizar
+ Squash durante el merge
Hotfix:
Prefijo de Hotfix:
Inicializar Git-Flow
@@ -575,6 +578,9 @@
Rama:
ABORTAR
Auto fetching cambios desde remotos...
+ Ordenar
+ Por Fecha de Committer
+ Por Nombre
Limpiar (GC & Prune)
Ejecutar comando `git gc` para este repositorio.
Limpiar todo
@@ -629,6 +635,7 @@
Abrir en Terminal
Usar tiempo relativo en las historias
Ver Logs
+ Visitar '{0}' en el Navegador
WORKTREES
AÑADIR WORKTREE
PRUNE
diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml
index 4c8ea4bfc..1c45f22f1 100644
--- a/src/Resources/Locales/ru_RU.axaml
+++ b/src/Resources/Locales/ru_RU.axaml
@@ -86,6 +86,7 @@
Локальные изменения:
Отклонить
Отложить и примненить повторно
+ Обновить все подкаталоги
Ветка:
Частичный выбор
Добавить источник для ревизии сообщения
@@ -326,6 +327,8 @@
ПРОЦЕСС - Закончить исправление
ПРОЦЕСС - Завершить выпуск
Цель:
+ Выложить на удалённый(ые) после завершения
+ Втиснуть при слиянии
Исправление:
Префикс исправлений:
Создать Git-процесс
@@ -575,6 +578,9 @@
Ветка:
Отказ
Автоматическое извлечение изменений с внешних репозиторий...
+ Сортировать
+ По дате ревизора (исполнителя)
+ По имени
Очистить (Сбор мусора и удаление)
Запустить команду (git gc) для данного репозитория.
Очистить всё
@@ -628,7 +634,8 @@
Сортировать
Открыть в терминале
Использовать относительное время в историях
- Просмотр логов
+ Просмотр журналов
+ Посетить '{0}' в браузере
РАБОЧИЕ КАТАЛОГИ
ДОБАВИТЬ РАБОЧИЙ КАТАЛОГ
ОБРЕЗАТЬ
@@ -714,7 +721,7 @@
Подмодуль:
Использовать опцию (--remote)
Сетевой адрес:
- Логи
+ Журналы
ОЧИСТИТЬ ВСЁ
Копировать
Удалить
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index 8ee210e95..d08c6f703 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -86,6 +86,7 @@
未提交更改 :
丢弃更改
贮藏并自动恢复
+ 同时更新所有子模块
目标分支 :
挑选提交
提交信息中追加来源信息
@@ -326,6 +327,8 @@
结束修复分支
结束版本分支
目标分支 :
+ 完成后自动推送
+ 压缩变更为单一提交后合并分支
修复分支 :
修复分支名前缀 :
初始化GIT工作流
@@ -575,6 +578,9 @@
分支 :
终止合并
自动拉取远端变更中...
+ 排序方式
+ 按提交时间
+ 按名称
清理本仓库(GC)
本操作将执行`git gc`命令。
清空过滤规则
@@ -629,6 +635,7 @@
在终端中打开
在提交列表中使用相对时间
查看命令日志
+ 访问远程仓库 '{0}'
工作树列表
新增工作树
清理
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index 7430d609f..4cd68cadb 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -86,6 +86,7 @@
未提交變更:
捨棄變更
擱置變更並自動復原
+ 同時更新所有子模組
目標分支:
揀選提交
提交資訊中追加來源資訊
@@ -326,6 +327,8 @@
完成修復分支
完成發行分支
目標分支:
+ 完成後自動推送
+ 壓縮為單一提交後合併
修復分支:
修復分支前置詞:
初始化 Git 工作流
@@ -575,6 +578,9 @@
分支:
中止
自動提取遠端變更中...
+ 排序
+ 依建立時間
+ 依名稱升序
清理本存放庫 (GC)
本操作將執行 `git gc` 命令。
清空篩選規則
@@ -629,6 +635,7 @@
在終端機中開啟
在提交列表中使用相對時間
檢視 Git 指令記錄
+ 檢視遠端存放庫 '{0}'
工作區列表
新增工作區
清理
diff --git a/src/Resources/Themes.axaml b/src/Resources/Themes.axaml
index f33e71d2a..3194c8c3d 100644
--- a/src/Resources/Themes.axaml
+++ b/src/Resources/Themes.axaml
@@ -25,7 +25,7 @@
#A7E1A7
#F19B9D
#0000EE
- #FFE5E5E5
+ #FFE4E4E4
@@ -52,7 +52,7 @@
#A0308D3C
#A09F4247
#4DAAFC
- #FF2E2E2E
+ #FF383838
diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs
index 0148844a0..d0289f6a3 100644
--- a/src/ViewModels/BranchTreeNode.cs
+++ b/src/ViewModels/BranchTreeNode.cs
@@ -10,6 +10,7 @@ public class BranchTreeNode : ObservableObject
public string Name { get; private set; } = string.Empty;
public string Path { get; private set; } = string.Empty;
public object Backend { get; private set; } = null;
+ public ulong TimeToSort { get; private set; } = 0;
public int Depth { get; set; } = 0;
public bool IsSelected { get; set; } = false;
public List Children { get; private set; } = new List();
@@ -62,6 +63,12 @@ public class Builder
public List Remotes => _remotes;
public List InvalidExpandedNodes => _invalidExpandedNodes;
+ public Builder(Models.BranchSortMode localSortMode, Models.BranchSortMode remoteSortMode)
+ {
+ _localSortMode = localSortMode;
+ _remoteSortMode = remoteSortMode;
+ }
+
public void SetExpandedNodes(List expanded)
{
foreach (var node in expanded)
@@ -72,6 +79,7 @@ public void Run(List branches, List remotes, bool
{
var folders = new Dictionary();
+ var fakeRemoteTime = (ulong)remotes.Count;
foreach (var remote in remotes)
{
var path = $"refs/remotes/{remote.Name}";
@@ -81,8 +89,10 @@ public void Run(List branches, List remotes, bool
Path = path,
Backend = remote,
IsExpanded = bForceExpanded || _expanded.Contains(path),
+ TimeToSort = fakeRemoteTime,
};
+ fakeRemoteTime--;
folders.Add(path, node);
_remotes.Add(node);
}
@@ -108,12 +118,21 @@ public void Run(List branches, List remotes, bool
}
folders.Clear();
- SortNodes(_locals);
- SortNodes(_remotes);
+
+ if (_localSortMode == Models.BranchSortMode.Name)
+ SortNodesByName(_locals);
+ else
+ SortNodesByTime(_locals);
+
+ if (_remoteSortMode == Models.BranchSortMode.Name)
+ SortNodesByName(_remotes);
+ else
+ SortNodesByTime(_remotes);
}
private void MakeBranchNode(Models.Branch branch, List roots, Dictionary folders, string prefix, bool bForceExpanded)
{
+ var time = branch.CommitterDate;
var fullpath = $"{prefix}/{branch.Name}";
var sepIdx = branch.Name.IndexOf('/', StringComparison.Ordinal);
if (sepIdx == -1 || branch.IsDetachedHead)
@@ -124,6 +143,7 @@ private void MakeBranchNode(Models.Branch branch, List roots, Di
Path = fullpath,
Backend = branch,
IsExpanded = false,
+ TimeToSort = time,
});
return;
}
@@ -138,6 +158,7 @@ private void MakeBranchNode(Models.Branch branch, List roots, Di
if (folders.TryGetValue(folder, out var val))
{
lastFolder = val;
+ lastFolder.TimeToSort = Math.Max(lastFolder.TimeToSort, time);
if (!lastFolder.IsExpanded)
lastFolder.IsExpanded |= (branch.IsCurrent || _expanded.Contains(folder));
}
@@ -148,6 +169,7 @@ private void MakeBranchNode(Models.Branch branch, List roots, Di
Name = name,
Path = folder,
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
+ TimeToSort = time,
};
roots.Add(lastFolder);
folders.Add(folder, lastFolder);
@@ -159,6 +181,7 @@ private void MakeBranchNode(Models.Branch branch, List roots, Di
Name = name,
Path = folder,
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
+ TimeToSort = time,
};
lastFolder.Children.Add(cur);
folders.Add(folder, cur);
@@ -175,10 +198,11 @@ private void MakeBranchNode(Models.Branch branch, List roots, Di
Path = fullpath,
Backend = branch,
IsExpanded = false,
+ TimeToSort = time,
});
}
- private void SortNodes(List nodes)
+ private void SortNodesByName(List nodes)
{
nodes.Sort((l, r) =>
{
@@ -192,9 +216,39 @@ private void SortNodes(List nodes)
});
foreach (var node in nodes)
- SortNodes(node.Children);
+ SortNodesByName(node.Children);
+ }
+
+ private void SortNodesByTime(List nodes)
+ {
+ nodes.Sort((l, r) =>
+ {
+ if (l.Backend is Models.Branch { IsDetachedHead: true })
+ return -1;
+
+ if (l.Backend is Models.Branch)
+ {
+ if (r.Backend is Models.Branch)
+ return r.TimeToSort == l.TimeToSort ? Models.NumericSort.Compare(l.Name, r.Name) : r.TimeToSort.CompareTo(l.TimeToSort);
+ else
+ return 1;
+ }
+
+ if (r.Backend is Models.Branch)
+ return -1;
+
+ if (r.TimeToSort == l.TimeToSort)
+ return Models.NumericSort.Compare(l.Name, r.Name);
+
+ return r.TimeToSort.CompareTo(l.TimeToSort);
+ });
+
+ foreach (var node in nodes)
+ SortNodesByTime(node.Children);
}
+ private readonly Models.BranchSortMode _localSortMode = Models.BranchSortMode.Name;
+ private readonly Models.BranchSortMode _remoteSortMode = Models.BranchSortMode.Name;
private readonly List _locals = new List();
private readonly List _remotes = new List();
private readonly List _invalidExpandedNodes = new List();
diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs
index a96ac79a9..d8e9b38aa 100644
--- a/src/ViewModels/Checkout.cs
+++ b/src/ViewModels/Checkout.cs
@@ -15,11 +15,24 @@ public bool DiscardLocalChanges
set;
}
+ public bool IsRecurseSubmoduleVisible
+ {
+ get;
+ private set;
+ }
+
+ public bool RecurseSubmodules
+ {
+ get => _repo.Settings.UpdateSubmodulesOnCheckoutBranch;
+ set => _repo.Settings.UpdateSubmodulesOnCheckoutBranch = value;
+ }
+
public Checkout(Repository repo, string branch)
{
_repo = repo;
Branch = branch;
DiscardLocalChanges = false;
+ IsRecurseSubmoduleVisible = repo.Submodules.Count > 0;
}
public override Task Sure()
@@ -30,19 +43,22 @@ public override Task Sure()
var log = _repo.CreateLog($"Checkout '{Branch}'");
Use(log);
+ var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules;
return Task.Run(() =>
{
- var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
+ var succ = false;
var needPopStash = false;
- if (changes > 0)
+
+ if (DiscardLocalChanges)
{
- if (DiscardLocalChanges)
- {
- Commands.Discard.All(_repo.FullPath, false, log);
- }
- else
+ succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(Branch, true);
+ }
+ else
+ {
+ var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
+ if (changes > 0)
{
- var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH");
+ succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH");
if (!succ)
{
log.Complete();
@@ -52,11 +68,22 @@ public override Task Sure()
needPopStash = true;
}
+
+ succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(Branch, false);
}
- var rs = new Commands.Checkout(_repo.FullPath).Use(log).Branch(Branch);
- if (needPopStash)
- rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
+ if (succ)
+ {
+ if (updateSubmodules)
+ {
+ var submodules = new Commands.QuerySubmodules(_repo.FullPath).Result();
+ if (submodules.Count > 0)
+ new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true, false);
+ }
+
+ if (needPopStash)
+ new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
+ }
log.Complete();
@@ -73,7 +100,7 @@ public override Task Sure()
});
Task.Delay(400).Wait();
- return rs;
+ return succ;
});
}
diff --git a/src/ViewModels/CheckoutCommit.cs b/src/ViewModels/CheckoutCommit.cs
index 7bd6976de..a8c5b8cfe 100644
--- a/src/ViewModels/CheckoutCommit.cs
+++ b/src/ViewModels/CheckoutCommit.cs
@@ -15,11 +15,24 @@ public bool DiscardLocalChanges
set;
}
+ public bool IsRecurseSubmoduleVisible
+ {
+ get;
+ private set;
+ }
+
+ public bool RecurseSubmodules
+ {
+ get => _repo.Settings.UpdateSubmodulesOnCheckoutBranch;
+ set => _repo.Settings.UpdateSubmodulesOnCheckoutBranch = value;
+ }
+
public CheckoutCommit(Repository repo, Models.Commit commit)
{
_repo = repo;
Commit = commit;
DiscardLocalChanges = false;
+ IsRecurseSubmoduleVisible = repo.Submodules.Count > 0;
}
public override Task Sure()
@@ -30,19 +43,22 @@ public override Task Sure()
var log = _repo.CreateLog("Checkout Commit");
Use(log);
+ var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules;
return Task.Run(() =>
{
- var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
- var needPopStash = false;
- if (changes > 0)
+ var succ = false;
+ var needPop = false;
+
+ if (DiscardLocalChanges)
{
- if (DiscardLocalChanges)
- {
- Commands.Discard.All(_repo.FullPath, false, log);
- }
- else
+ succ = new Commands.Checkout(_repo.FullPath).Use(log).Commit(Commit.SHA, true);
+ }
+ else
+ {
+ var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
+ if (changes > 0)
{
- var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH");
+ succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH");
if (!succ)
{
log.Complete();
@@ -50,17 +66,28 @@ public override Task Sure()
return false;
}
- needPopStash = true;
+ needPop = true;
}
+
+ succ = new Commands.Checkout(_repo.FullPath).Use(log).Commit(Commit.SHA, false);
}
- var rs = new Commands.Checkout(_repo.FullPath).Use(log).Commit(Commit.SHA);
- if (needPopStash)
- rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
+ if (succ)
+ {
+ if (updateSubmodules)
+ {
+ var submodules = new Commands.QuerySubmodules(_repo.FullPath).Result();
+ if (submodules.Count > 0)
+ new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true, false);
+ }
+
+ if (needPop)
+ new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
+ }
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true));
- return rs;
+ return succ;
});
}
diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs
index ee5da69e9..dd9cf86b2 100644
--- a/src/ViewModels/Clone.cs
+++ b/src/ViewModels/Clone.cs
@@ -101,7 +101,6 @@ public override Task Sure()
{
ProgressDescription = "Clone ...";
- // Create a temp log.
var log = new CommandLog("Clone");
Use(log);
@@ -139,12 +138,11 @@ public override Task Sure()
config.Set("remote.origin.sshkey", _sshKey);
}
- // individually update submodule (if any)
if (InitAndUpdateSubmodules)
{
- var submoduleList = new Commands.QuerySubmodules(path).Result();
- foreach (var submodule in submoduleList)
- new Commands.Submodule(path).Use(log).Update(submodule.Path, true, true, false);
+ var submodules = new Commands.QuerySubmodules(path).Result();
+ if (submodules.Count > 0)
+ new Commands.Submodule(path).Use(log).Update(submodules, true, true, false);
}
log.Complete();
diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs
index fdaa29f12..3b411056a 100644
--- a/src/ViewModels/CommitDetail.cs
+++ b/src/ViewModels/CommitDetail.cs
@@ -291,7 +291,7 @@ public ContextMenu CreateChangeContextMenu(Models.Change change)
ev.Handled = true;
};
- var fullPath = Path.Combine(_repo.FullPath, change.Path);
+ var fullPath = Native.OS.GetAbsPath(_repo.FullPath, change.Path);
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
@@ -362,11 +362,9 @@ public ContextMenu CreateChangeContextMenu(Models.Change change)
var resetToThisRevision = new MenuItem();
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
- resetToThisRevision.Click += (_, ev) =>
+ resetToThisRevision.Click += async (_, ev) =>
{
- var log = _repo.CreateLog($"Reset File to '{_commit.SHA}'");
- new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(change.Path, $"{_commit.SHA}");
- log.Complete();
+ await ResetToThisRevision(change.Path);
ev.Handled = true;
};
@@ -374,14 +372,9 @@ public ContextMenu CreateChangeContextMenu(Models.Change change)
resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToFirstParent.IsEnabled = _commit.Parents.Count > 0;
- resetToFirstParent.Click += (_, ev) =>
+ resetToFirstParent.Click += async (_, ev) =>
{
- var log = _repo.CreateLog($"Reset File to '{_commit.SHA}~1'");
- if (change.Index == Models.ChangeState.Renamed)
- new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(change.OriginalPath, $"{_commit.SHA}~1");
-
- new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(change.Path, $"{_commit.SHA}~1");
- log.Complete();
+ await ResetToParentRevision(change);
ev.Handled = true;
};
@@ -389,8 +382,7 @@ public ContextMenu CreateChangeContextMenu(Models.Change change)
menu.Items.Add(resetToFirstParent);
menu.Items.Add(new MenuItem { Header = "-" });
- if (File.Exists(Path.Combine(fullPath)))
- TryToAddContextMenuItemsForGitLFS(menu, change.Path);
+ TryToAddContextMenuItemsForGitLFS(menu, fullPath, change.Path);
}
var copyPath = new MenuItem();
@@ -407,7 +399,7 @@ public ContextMenu CreateChangeContextMenu(Models.Change change)
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Click += (_, e) =>
{
- App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, change.Path));
+ App.CopyText(fullPath);
e.Handled = true;
};
@@ -419,7 +411,7 @@ public ContextMenu CreateChangeContextMenu(Models.Change change)
public ContextMenu CreateRevisionFileContextMenu(Models.Object file)
{
var menu = new ContextMenu();
- var fullPath = Path.Combine(_repo.FullPath, file.Path);
+ var fullPath = Native.OS.GetAbsPath(_repo.FullPath, file.Path);
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
@@ -499,38 +491,34 @@ public ContextMenu CreateRevisionFileContextMenu(Models.Object file)
menu.Items.Add(blame);
menu.Items.Add(new MenuItem() { Header = "-" });
- var resetToThisRevision = new MenuItem();
- resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
- resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
- resetToThisRevision.IsEnabled = File.Exists(fullPath);
- resetToThisRevision.Click += (_, ev) =>
+ if (!_repo.IsBare)
{
- var log = _repo.CreateLog($"Reset File to '{_commit.SHA}'");
- new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(file.Path, $"{_commit.SHA}");
- log.Complete();
- ev.Handled = true;
- };
+ var resetToThisRevision = new MenuItem();
+ resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
+ resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
+ resetToThisRevision.Click += async (_, ev) =>
+ {
+ await ResetToThisRevision(file.Path);
+ ev.Handled = true;
+ };
- var resetToFirstParent = new MenuItem();
- resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
- resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
- var fileInChanges = _changes.Find(x => x.Path == file.Path);
- var fileIndex = fileInChanges?.Index;
- resetToFirstParent.IsEnabled = _commit.Parents.Count > 0 && fileIndex != Models.ChangeState.Renamed;
- resetToFirstParent.Click += (_, ev) =>
- {
- var log = _repo.CreateLog($"Reset File to '{_commit.SHA}~1'");
- new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(file.Path, $"{_commit.SHA}~1");
- log.Complete();
- ev.Handled = true;
- };
+ var change = _changes.Find(x => x.Path == file.Path) ?? new Models.Change() { Index = Models.ChangeState.None, Path = file.Path };
+ var resetToFirstParent = new MenuItem();
+ resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
+ resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
+ resetToFirstParent.IsEnabled = _commit.Parents.Count > 0;
+ resetToFirstParent.Click += async (_, ev) =>
+ {
+ await ResetToParentRevision(change);
+ ev.Handled = true;
+ };
- menu.Items.Add(resetToThisRevision);
- menu.Items.Add(resetToFirstParent);
- menu.Items.Add(new MenuItem() { Header = "-" });
+ menu.Items.Add(resetToThisRevision);
+ menu.Items.Add(resetToFirstParent);
+ menu.Items.Add(new MenuItem() { Header = "-" });
- if (File.Exists(Path.Combine(fullPath)))
- TryToAddContextMenuItemsForGitLFS(menu, file.Path);
+ TryToAddContextMenuItemsForGitLFS(menu, fullPath, file.Path);
+ }
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
@@ -546,7 +534,7 @@ public ContextMenu CreateRevisionFileContextMenu(Models.Object file)
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Click += (_, e) =>
{
- App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, file.Path));
+ App.CopyText(fullPath);
e.Handled = true;
};
@@ -725,8 +713,11 @@ private void RefreshVisibleChanges()
}
}
- private void TryToAddContextMenuItemsForGitLFS(ContextMenu menu, string path)
+ private void TryToAddContextMenuItemsForGitLFS(ContextMenu menu, string fullPath, string path)
{
+ if (_repo.Remotes.Count == 0 || !File.Exists(fullPath))
+ return;
+
var lfsEnabled = new Commands.LFS(_repo.FullPath).IsEnabled();
if (!lfsEnabled)
return;
@@ -738,7 +729,6 @@ private void TryToAddContextMenuItemsForGitLFS(ContextMenu menu, string path)
var lfsLock = new MenuItem();
lfsLock.Header = App.Text("GitLFS.Locks.Lock");
lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
- lfsLock.IsEnabled = _repo.Remotes.Count > 0;
if (_repo.Remotes.Count == 1)
{
lfsLock.Click += async (_, e) =>
@@ -777,7 +767,6 @@ private void TryToAddContextMenuItemsForGitLFS(ContextMenu menu, string path)
var lfsUnlock = new MenuItem();
lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
- lfsUnlock.IsEnabled = _repo.Remotes.Count > 0;
if (_repo.Remotes.Count == 1)
{
lfsUnlock.Click += async (_, e) =>
@@ -867,6 +856,31 @@ private void CalcRevisionFileSearchSuggestion()
RevisionFileSearchSuggestion = suggestion;
}
+ private Task ResetToThisRevision(string path)
+ {
+ var log = _repo.CreateLog($"Reset File to '{_commit.SHA}'");
+
+ return Task.Run(() =>
+ {
+ new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(path, $"{_commit.SHA}");
+ log.Complete();
+ });
+ }
+
+ private Task ResetToParentRevision(Models.Change change)
+ {
+ var log = _repo.CreateLog($"Reset File to '{_commit.SHA}~1'");
+
+ return Task.Run(() =>
+ {
+ if (change.Index == Models.ChangeState.Renamed)
+ new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(change.OriginalPath, $"{_commit.SHA}~1");
+
+ new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(change.Path, $"{_commit.SHA}~1");
+ log.Complete();
+ });
+ }
+
[GeneratedRegex(@"\b(https?://|ftp://)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]")]
private static partial Regex REG_URL_FORMAT();
diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs
index dd190e18b..252802f43 100644
--- a/src/ViewModels/CreateBranch.cs
+++ b/src/ViewModels/CreateBranch.cs
@@ -28,7 +28,14 @@ public bool DiscardLocalChanges
public bool CheckoutAfterCreated
{
get => _repo.Settings.CheckoutBranchOnCreateBranch;
- set => _repo.Settings.CheckoutBranchOnCreateBranch = value;
+ set
+ {
+ if (_repo.Settings.CheckoutBranchOnCreateBranch != value)
+ {
+ _repo.Settings.CheckoutBranchOnCreateBranch = value;
+ OnPropertyChanged();
+ }
+ }
}
public bool IsBareRepository
@@ -36,6 +43,18 @@ public bool IsBareRepository
get => _repo.IsBare;
}
+ public bool IsRecurseSubmoduleVisible
+ {
+ get;
+ private set;
+ }
+
+ public bool RecurseSubmodules
+ {
+ get => _repo.Settings.UpdateSubmodulesOnCheckoutBranch;
+ set => _repo.Settings.UpdateSubmodulesOnCheckoutBranch = value;
+ }
+
public CreateBranch(Repository repo, Models.Branch branch)
{
_repo = repo;
@@ -48,6 +67,7 @@ public CreateBranch(Repository repo, Models.Branch branch)
BasedOn = branch;
DiscardLocalChanges = false;
+ IsRecurseSubmoduleVisible = repo.Submodules.Count > 0;
}
public CreateBranch(Repository repo, Models.Commit commit)
@@ -57,6 +77,7 @@ public CreateBranch(Repository repo, Models.Commit commit)
BasedOn = commit;
DiscardLocalChanges = false;
+ IsRecurseSubmoduleVisible = repo.Submodules.Count > 0;
}
public CreateBranch(Repository repo, Models.Tag tag)
@@ -66,6 +87,7 @@ public CreateBranch(Repository repo, Models.Tag tag)
BasedOn = tag;
DiscardLocalChanges = false;
+ IsRecurseSubmoduleVisible = repo.Submodules.Count > 0;
}
public static ValidationResult ValidateBranchName(string name, ValidationContext ctx)
@@ -92,20 +114,21 @@ public override Task Sure()
var log = _repo.CreateLog($"Create Branch '{fixedName}'");
Use(log);
+ var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules;
return Task.Run(() =>
{
- bool succ;
+ bool succ = false;
if (CheckoutAfterCreated && !_repo.IsBare)
{
- var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
var needPopStash = false;
- if (changes > 0)
+ if (DiscardLocalChanges)
{
- if (DiscardLocalChanges)
- {
- Commands.Discard.All(_repo.FullPath, false, log);
- }
- else
+ succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision, true);
+ }
+ else
+ {
+ var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
+ if (changes > 0)
{
succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CREATE_BRANCH_AUTO_STASH");
if (!succ)
@@ -117,11 +140,22 @@ public override Task Sure()
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);
- if (needPopStash)
- new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
+ if (succ)
+ {
+ if (updateSubmodules)
+ {
+ var submodules = new Commands.QuerySubmodules(_repo.FullPath).Result();
+ if (submodules.Count > 0)
+ new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true, false);
+ }
+
+ if (needPopStash)
+ new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
+ }
}
else
{
@@ -144,6 +178,8 @@ public override Task Sure()
if (_repo.HistoriesFilterMode == Models.FilterMode.Included)
_repo.SetBranchFilterMode(fake, Models.FilterMode.Included, true, false);
+
+ ProgressDescription = "Waiting for branch updated...";
}
_repo.MarkBranchesDirtyManually();
@@ -151,10 +187,7 @@ public override Task Sure()
});
if (CheckoutAfterCreated)
- {
- CallUIThread(() => ProgressDescription = "Waiting for branch updated...");
Task.Delay(400).Wait();
- }
return true;
});
diff --git a/src/ViewModels/GitFlowFinish.cs b/src/ViewModels/GitFlowFinish.cs
index efc28db16..d7ad5d315 100644
--- a/src/ViewModels/GitFlowFinish.cs
+++ b/src/ViewModels/GitFlowFinish.cs
@@ -13,6 +13,18 @@ public Models.Branch Branch
public bool IsRelease => _type == "release";
public bool IsHotfix => _type == "hotfix";
+ public bool Squash
+ {
+ get;
+ set;
+ } = false;
+
+ public bool AutoPush
+ {
+ get;
+ set;
+ } = false;
+
public bool KeepBranch
{
get;
@@ -39,7 +51,7 @@ public override Task Sure()
return Task.Run(() =>
{
- var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, KeepBranch, log);
+ var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, Squash, AutoPush, KeepBranch, log);
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
diff --git a/src/ViewModels/LauncherPage.cs b/src/ViewModels/LauncherPage.cs
index b97ef6f71..8a59d2460 100644
--- a/src/ViewModels/LauncherPage.cs
+++ b/src/ViewModels/LauncherPage.cs
@@ -100,23 +100,32 @@ public void StartPopup(Popup popup)
public async void ProcessPopup()
{
- if (_popup is { InProgress: false })
+ if (_popup is { InProgress: false } dump)
{
- if (!_popup.Check())
+ if (!dump.Check())
return;
- _popup.InProgress = true;
- var task = _popup.Sure();
+ dump.InProgress = true;
+ var task = dump.Sure();
+ var finished = false;
if (task != null)
{
- var finished = await task;
- _popup.InProgress = false;
+ try
+ {
+ finished = await task;
+ }
+ catch (Exception e)
+ {
+ App.LogException(e);
+ }
+
+ dump.InProgress = false;
if (finished)
Popup = null;
}
else
{
- _popup.InProgress = false;
+ dump.InProgress = false;
Popup = null;
}
}
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index 289d890f3..8661cae26 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -583,43 +583,85 @@ public void RefreshAll()
Task.Run(RefreshStashes);
}
- public void OpenInFileManager()
+ public ContextMenu CreateContextMenuForExternalTools()
{
- Native.OS.OpenInFileManager(_fullpath);
- }
+ var menu = new ContextMenu();
+ menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
- public void OpenInTerminal()
- {
- Native.OS.OpenTerminal(_fullpath);
- }
+ RenderOptions.SetBitmapInterpolationMode(menu, BitmapInterpolationMode.HighQuality);
+ RenderOptions.SetEdgeMode(menu, EdgeMode.Antialias);
+ RenderOptions.SetTextRenderingMode(menu, TextRenderingMode.Antialias);
+
+ var explore = new MenuItem();
+ explore.Header = App.Text("Repository.Explore");
+ explore.Icon = App.CreateMenuIcon("Icons.Explore");
+ explore.Click += (_, e) =>
+ {
+ Native.OS.OpenInFileManager(_fullpath);
+ e.Handled = true;
+ };
+
+ var terminal = new MenuItem();
+ terminal.Header = App.Text("Repository.Terminal");
+ terminal.Icon = App.CreateMenuIcon("Icons.Terminal");
+ terminal.Click += (_, e) =>
+ {
+ Native.OS.OpenTerminal(_fullpath);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(explore);
+ menu.Items.Add(terminal);
- public ContextMenu CreateContextMenuForExternalTools()
- {
var tools = Native.OS.ExternalTools;
- if (tools.Count == 0)
+ if (tools.Count > 0)
{
- App.RaiseException(_fullpath, "No available external editors found!");
- return null;
+ menu.Items.Add(new MenuItem() { Header = "-" });
+
+ foreach (var tool in Native.OS.ExternalTools)
+ {
+ var dupTool = tool;
+
+ var item = new MenuItem();
+ item.Header = App.Text("Repository.OpenIn", dupTool.Name);
+ item.Icon = new Image { Width = 16, Height = 16, Source = dupTool.IconImage };
+ item.Click += (_, e) =>
+ {
+ dupTool.Open(_fullpath);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(item);
+ }
}
- var menu = new ContextMenu();
- menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
- RenderOptions.SetBitmapInterpolationMode(menu, BitmapInterpolationMode.HighQuality);
+ var urls = new Dictionary();
+ foreach (var r in _remotes)
+ {
+ if (r.TryGetVisitURL(out var visit))
+ urls.Add(r.Name, visit);
+ }
- foreach (var tool in tools)
+ if (urls.Count > 0)
{
- var dupTool = tool;
+ menu.Items.Add(new MenuItem() { Header = "-" });
- var item = new MenuItem();
- item.Header = App.Text("Repository.OpenIn", dupTool.Name);
- item.Icon = new Image { Width = 16, Height = 16, Source = dupTool.IconImage };
- item.Click += (_, e) =>
+ foreach (var url in urls)
{
- dupTool.Open(_fullpath);
- e.Handled = true;
- };
+ var name = url.Key;
+ var addr = url.Value;
+
+ var item = new MenuItem();
+ item.Header = App.Text("Repository.Visit", name);
+ item.Icon = App.CreateMenuIcon("Icons.Remotes");
+ item.Click += (_, e) =>
+ {
+ Native.OS.OpenBrowser(addr);
+ e.Handled = true;
+ };
- menu.Items.Add(item);
+ menu.Items.Add(item);
+ }
}
return menu;
@@ -1154,7 +1196,7 @@ public void CheckoutBranch(Models.Branch branch)
if (branch.IsLocal)
{
- if (_localChangesCount > 0)
+ if (_localChangesCount > 0 || _submodules.Count > 0)
ShowPopup(new Checkout(this, branch.Name));
else
ShowAndStartPopup(new Checkout(this, branch.Name));
@@ -2187,6 +2229,51 @@ public ContextMenu CreateContextMenuForTag(Models.Tag tag)
return menu;
}
+ public ContextMenu CreateContextMenuForBranchSortMode(bool local)
+ {
+ var mode = local ? _settings.LocalBranchSortMode : _settings.RemoteBranchSortMode;
+ var changeMode = new Action(m =>
+ {
+ if (local)
+ _settings.LocalBranchSortMode = m;
+ else
+ _settings.RemoteBranchSortMode = m;
+
+ var builder = BuildBranchTree(_branches, _remotes);
+ LocalBranchTrees = builder.Locals;
+ RemoteBranchTrees = builder.Remotes;
+ });
+
+ var byNameAsc = new MenuItem();
+ byNameAsc.Header = App.Text("Repository.BranchSort.ByName");
+ if (mode == Models.BranchSortMode.Name)
+ byNameAsc.Icon = App.CreateMenuIcon("Icons.Check");
+ byNameAsc.Click += (_, ev) =>
+ {
+ if (mode != Models.BranchSortMode.Name)
+ changeMode(Models.BranchSortMode.Name);
+
+ ev.Handled = true;
+ };
+
+ var byCommitterDate = new MenuItem();
+ byCommitterDate.Header = App.Text("Repository.BranchSort.ByCommitterDate");
+ if (mode == Models.BranchSortMode.CommitterDate)
+ byCommitterDate.Icon = App.CreateMenuIcon("Icons.Check");
+ byCommitterDate.Click += (_, ev) =>
+ {
+ if (mode != Models.BranchSortMode.CommitterDate)
+ changeMode(Models.BranchSortMode.CommitterDate);
+
+ ev.Handled = true;
+ };
+
+ var menu = new ContextMenu();
+ menu.Items.Add(byNameAsc);
+ menu.Items.Add(byCommitterDate);
+ return menu;
+ }
+
public ContextMenu CreateContextMenuForTagSortMode()
{
var mode = _settings.TagSortMode;
@@ -2356,7 +2443,7 @@ private LauncherPage GetOwnerPage()
private BranchTreeNode.Builder BuildBranchTree(List branches, List remotes)
{
- var builder = new BranchTreeNode.Builder();
+ var builder = new BranchTreeNode.Builder(_settings.LocalBranchSortMode, _settings.RemoteBranchSortMode);
if (string.IsNullOrEmpty(_filter))
{
builder.SetExpandedNodes(_settings.ExpandedBranchNodesInSideBar);
diff --git a/src/Views/BranchCompare.axaml b/src/Views/BranchCompare.axaml
index 4f6f312e6..1fe66e9b9 100644
--- a/src/Views/BranchCompare.axaml
+++ b/src/Views/BranchCompare.axaml
@@ -46,7 +46,7 @@
-
+
-
+
+
+ Content="{DynamicResource Text.Checkout.LocalChanges.Discard}"/>
+
+
diff --git a/src/Views/CheckoutCommit.axaml b/src/Views/CheckoutCommit.axaml
index 9b4188231..11b4b5d04 100644
--- a/src/Views/CheckoutCommit.axaml
+++ b/src/Views/CheckoutCommit.axaml
@@ -12,7 +12,7 @@
Classes="bold"
Text="{DynamicResource Text.Checkout.Commit}" />
-
+
-
+
+
+
diff --git a/src/Views/CommitGraph.cs b/src/Views/CommitGraph.cs
index 015eaca5f..5db393009 100644
--- a/src/Views/CommitGraph.cs
+++ b/src/Views/CommitGraph.cs
@@ -55,23 +55,29 @@ public override void Render(DrawingContext context)
if (list == null)
return;
- // Calculate drawing area.
- double width = Bounds.Width - 273 - histories.AuthorNameColumnWidth.Value;
- double height = Bounds.Height;
- double startY = list.Scroll?.Offset.Y ?? 0;
- double endY = startY + height + 28;
+ var container = list.ItemsPanelRoot as VirtualizingStackPanel;
+ if (container == null)
+ return;
+
+ var item = list.ContainerFromIndex(container.FirstRealizedIndex);
+ if (item == null)
+ return;
+
+ var width = histories.CommitListHeader.ColumnDefinitions[0].ActualWidth;
+ var height = Bounds.Height;
+ var rowHeight = item.Bounds.Height;
+ var startY = container.FirstRealizedIndex * rowHeight - item.TranslatePoint(new Point(0, 0), list).Value!.Y;
+ var endY = startY + height + 28;
- // Apply scroll offset and clip.
using (context.PushClip(new Rect(0, 0, width, height)))
using (context.PushTransform(Matrix.CreateTranslation(0, -startY)))
{
- // Draw contents
- DrawCurves(context, graph, startY, endY);
- DrawAnchors(context, graph, startY, endY);
+ DrawCurves(context, graph, startY, endY, rowHeight);
+ DrawAnchors(context, graph, startY, endY, rowHeight);
}
}
- private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double top, double bottom)
+ private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double top, double bottom, double rowHeight)
{
var grayedPen = new Pen(new SolidColorBrush(Colors.Gray, 0.4), Models.CommitGraph.Pens[0].Thickness);
var onlyHighlightCurrentBranch = OnlyHighlightCurrentBranch;
@@ -82,16 +88,20 @@ private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double
{
if (link.IsMerged)
continue;
- if (link.End.Y < top)
+
+ var startY = link.Start.Y * rowHeight;
+ var endY = link.End.Y * rowHeight;
+
+ if (endY < top)
continue;
- if (link.Start.Y > bottom)
+ if (startY > bottom)
break;
var geo = new StreamGeometry();
using (var ctx = geo.Open())
{
- ctx.BeginFigure(link.Start, false);
- ctx.QuadraticBezierTo(link.Control, link.End);
+ ctx.BeginFigure(new Point(link.Start.X, startY), false);
+ ctx.QuadraticBezierTo(new Point(link.Control.X, link.Control.Y * rowHeight), new Point(link.End.X, endY));
}
context.DrawGeometry(null, grayedPen, geo);
@@ -100,10 +110,11 @@ private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double
foreach (var line in graph.Paths)
{
- var last = line.Points[0];
+ var last = new Point(line.Points[0].X, line.Points[0].Y * rowHeight);
var size = line.Points.Count;
+ var endY = line.Points[size - 1].Y * rowHeight;
- if (line.Points[size - 1].Y < top)
+ if (endY < top)
continue;
if (last.Y > bottom)
break;
@@ -117,7 +128,7 @@ private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double
var ended = false;
for (int i = 1; i < size; i++)
{
- var cur = line.Points[i];
+ var cur = new Point(line.Points[i].X, line.Points[i].Y * rowHeight);
if (cur.Y < top)
{
last = cur;
@@ -173,23 +184,27 @@ private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double
{
if (onlyHighlightCurrentBranch && !link.IsMerged)
continue;
- if (link.End.Y < top)
+
+ var startY = link.Start.Y * rowHeight;
+ var endY = link.End.Y * rowHeight;
+
+ if (endY < top)
continue;
- if (link.Start.Y > bottom)
+ if (startY > bottom)
break;
var geo = new StreamGeometry();
using (var ctx = geo.Open())
{
- ctx.BeginFigure(link.Start, false);
- ctx.QuadraticBezierTo(link.Control, link.End);
+ ctx.BeginFigure(new Point(link.Start.X, startY), false);
+ ctx.QuadraticBezierTo(new Point(link.Control.X, link.Control.Y * rowHeight), new Point(link.End.X, endY));
}
context.DrawGeometry(null, Models.CommitGraph.Pens[link.Color], geo);
}
}
- private void DrawAnchors(DrawingContext context, Models.CommitGraph graph, double top, double bottom)
+ private void DrawAnchors(DrawingContext context, Models.CommitGraph graph, double top, double bottom, double rowHeight)
{
var dotFill = DotBrush;
var dotFillPen = new Pen(dotFill, 2);
@@ -198,9 +213,11 @@ private void DrawAnchors(DrawingContext context, Models.CommitGraph graph, doubl
foreach (var dot in graph.Dots)
{
- if (dot.Center.Y < top)
+ var center = new Point(dot.Center.X, dot.Center.Y * rowHeight);
+
+ if (center.Y < top)
continue;
- if (dot.Center.Y > bottom)
+ if (center.Y > bottom)
break;
var pen = Models.CommitGraph.Pens[dot.Color];
@@ -210,16 +227,16 @@ private void DrawAnchors(DrawingContext context, Models.CommitGraph graph, doubl
switch (dot.Type)
{
case Models.CommitGraph.DotType.Head:
- context.DrawEllipse(dotFill, pen, dot.Center, 6, 6);
- context.DrawEllipse(pen.Brush, null, dot.Center, 3, 3);
+ context.DrawEllipse(dotFill, pen, center, 6, 6);
+ context.DrawEllipse(pen.Brush, null, center, 3, 3);
break;
case Models.CommitGraph.DotType.Merge:
- context.DrawEllipse(pen.Brush, null, dot.Center, 6, 6);
- context.DrawLine(dotFillPen, new Point(dot.Center.X, dot.Center.Y - 3), new Point(dot.Center.X, dot.Center.Y + 3));
- context.DrawLine(dotFillPen, new Point(dot.Center.X - 3, dot.Center.Y), new Point(dot.Center.X + 3, dot.Center.Y));
+ context.DrawEllipse(pen.Brush, null, center, 6, 6);
+ context.DrawLine(dotFillPen, new Point(center.X, center.Y - 3), new Point(center.X, center.Y + 3));
+ context.DrawLine(dotFillPen, new Point(center.X - 3, center.Y), new Point(center.X + 3, center.Y));
break;
default:
- context.DrawEllipse(dotFill, pen, dot.Center, 3, 3);
+ context.DrawEllipse(dotFill, pen, center, 3, 3);
break;
}
}
diff --git a/src/Views/CommitSubjectPresenter.cs b/src/Views/CommitSubjectPresenter.cs
index 86e0fd54b..18902462f 100644
--- a/src/Views/CommitSubjectPresenter.cs
+++ b/src/Views/CommitSubjectPresenter.cs
@@ -104,23 +104,32 @@ public override void Render(DrawingContext context)
if (_inlines.Count == 0)
return;
- var height = Bounds.Height;
- var width = Bounds.Width;
- foreach (var inline in _inlines)
+ var ro = new RenderOptions()
{
- if (inline.X > width)
- return;
+ TextRenderingMode = TextRenderingMode.SubpixelAntialias,
+ EdgeMode = EdgeMode.Antialias
+ };
- if (inline.Element is { Type: Models.InlineElementType.Code })
- {
- var rect = new Rect(inline.X, (height - inline.Text.Height - 2) * 0.5, inline.Text.WidthIncludingTrailingWhitespace + 8, inline.Text.Height + 2);
- var roundedRect = new RoundedRect(rect, new CornerRadius(4));
- context.DrawRectangle(InlineCodeBackground, null, roundedRect);
- context.DrawText(inline.Text, new Point(inline.X + 4, (height - inline.Text.Height) * 0.5));
- }
- else
+ using (context.PushRenderOptions(ro))
+ {
+ var height = Bounds.Height;
+ var width = Bounds.Width;
+ foreach (var inline in _inlines)
{
- context.DrawText(inline.Text, new Point(inline.X, (height - inline.Text.Height) * 0.5));
+ if (inline.X > width)
+ return;
+
+ if (inline.Element is { Type: Models.InlineElementType.Code })
+ {
+ var rect = new Rect(inline.X, (height - inline.Text.Height - 2) * 0.5, inline.Text.WidthIncludingTrailingWhitespace + 8, inline.Text.Height + 2);
+ var roundedRect = new RoundedRect(rect, new CornerRadius(4));
+ context.DrawRectangle(InlineCodeBackground, null, roundedRect);
+ context.DrawText(inline.Text, new Point(inline.X + 4, (height - inline.Text.Height) * 0.5));
+ }
+ else
+ {
+ context.DrawText(inline.Text, new Point(inline.X, (height - inline.Text.Height) * 0.5));
+ }
}
}
}
@@ -299,7 +308,7 @@ private void GenerateFormattedTextElements()
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
codeTypeface,
- fontSize,
+ fontSize - 0.5,
foreground);
_inlines.Add(new Inline(x, link, elem));
x += link.WidthIncludingTrailingWhitespace + 8;
diff --git a/src/Views/Conflict.axaml b/src/Views/Conflict.axaml
index cf2e25a07..6e8aceb3f 100644
--- a/src/Views/Conflict.axaml
+++ b/src/Views/Conflict.axaml
@@ -52,7 +52,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/GitFlowFinish.axaml b/src/Views/GitFlowFinish.axaml
index 7af46fd98..6a16e95cd 100644
--- a/src/Views/GitFlowFinish.axaml
+++ b/src/Views/GitFlowFinish.axaml
@@ -19,7 +19,7 @@
Classes="bold"
Text="{DynamicResource Text.GitFlow.FinishHotfix}"
IsVisible="{Binding IsHotfix}"/>
-
+
+
+
+
+
+ IsChecked="{Binding KeepBranch, Mode=TwoWay}"
+ ToolTip.Tip="-k"/>
diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml
index 86a965289..c9a8ba5e3 100644
--- a/src/Views/Histories.axaml
+++ b/src/Views/Histories.axaml
@@ -30,7 +30,7 @@
Background="{DynamicResource Brush.Window}"
BorderThickness="0,0,0,1"
BorderBrush="{DynamicResource Brush.Border0}">
-
+
@@ -76,7 +76,7 @@