diff --git a/README.md b/README.md
index 4086a641..c932ec8a 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@
## Translation Status
-[](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md)
+[](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md)
> [!NOTE]
> You can find the missing keys in [TRANSLATION.md](TRANSLATION.md)
@@ -79,7 +79,7 @@ For **Windows** users:
```
> [!NOTE]
> `winget` will install this software as a commandline tool. You need run `SourceGit` from console or `Win+R` at the first time. Then you can add it to the taskbar.
-* You can install the latest stable by `scoope` with follow commands:
+* You can install the latest stable by `scoop` with follow commands:
```shell
scoop bucket add extras
scoop install sourcegit
diff --git a/TRANSLATION.md b/TRANSLATION.md
index a5abbbba..e3f9d2a1 100644
--- a/TRANSLATION.md
+++ b/TRANSLATION.md
@@ -1,4 +1,4 @@
-### de_DE.axaml: 99.08%
+### de_DE.axaml: 99.07%
@@ -24,7 +24,7 @@
-### fr_FR.axaml: 91.68%
+### fr_FR.axaml: 91.66%
@@ -106,7 +106,7 @@
-### pt_BR.axaml: 91.41%
+### pt_BR.axaml: 91.39%
diff --git a/VERSION b/VERSION
index a75bd422..23993bfb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2025.08
\ No newline at end of file
+2025.09
\ No newline at end of file
diff --git a/build/scripts/package.windows.sh b/build/scripts/package.windows.sh
index 1a8f99c1..c22a9d35 100755
--- a/build/scripts/package.windows.sh
+++ b/build/scripts/package.windows.sh
@@ -10,7 +10,7 @@ cd build
rm -rf SourceGit/*.pdb
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
- powershell -Command "Compress-Archive -Path SourceGit\\* -DestinationPath \"sourcegit_$VERSION.$RUNTIME.zip\" -Force"
+ powershell -Command "Compress-Archive -Path SourceGit -DestinationPath \"sourcegit_$VERSION.$RUNTIME.zip\" -Force"
else
- zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
+ zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
fi
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index 25e32323..f59d35db 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -77,6 +77,31 @@ namespace SourceGit
Native.OS.SetupApp(builder);
return builder;
}
+
+ private static void LogException(Exception ex)
+ {
+ if (ex == null)
+ return;
+
+ var builder = new StringBuilder();
+ builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n");
+ builder.Append("----------------------------\n");
+ builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
+ builder.Append($"OS: {Environment.OSVersion}\n");
+ builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
+ builder.Append($"Source: {ex.Source}\n");
+ builder.Append($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}\n");
+ builder.Append($"User: {Environment.UserName}\n");
+ builder.Append($"App Start Time: {Process.GetCurrentProcess().StartTime}\n");
+ builder.Append($"Exception Time: {DateTime.Now}\n");
+ builder.Append($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB\n");
+ builder.Append($"---------------------------\n\n");
+ builder.Append(ex);
+
+ var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
+ var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
+ File.WriteAllText(file, builder.ToString());
+ }
#endregion
#region Utility Functions
@@ -181,6 +206,9 @@ namespace SourceGit
app._fontsOverrides = null;
}
+ defaultFont = app.FixFontFamilyName(defaultFont);
+ monospaceFont = app.FixFontFamilyName(monospaceFont);
+
var resDic = new ResourceDictionary();
if (!string.IsNullOrEmpty(defaultFont))
resDic.Add("Fonts.Default", new FontFamily(defaultFont));
@@ -325,31 +353,6 @@ namespace SourceGit
}
#endregion
- private static void LogException(Exception ex)
- {
- if (ex == null)
- return;
-
- var builder = new StringBuilder();
- builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n");
- builder.Append("----------------------------\n");
- builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
- builder.Append($"OS: {Environment.OSVersion}\n");
- builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
- builder.Append($"Source: {ex.Source}\n");
- builder.Append($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}\n");
- builder.Append($"User: {Environment.UserName}\n");
- builder.Append($"App Start Time: {Process.GetCurrentProcess().StartTime}\n");
- builder.Append($"Exception Time: {DateTime.Now}\n");
- builder.Append($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB\n");
- builder.Append($"---------------------------\n\n");
- builder.Append(ex);
-
- var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
- var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
- File.WriteAllText(file, builder.ToString());
- }
-
private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
{
exitCode = -1;
@@ -546,6 +549,24 @@ namespace SourceGit
});
}
+ private string FixFontFamilyName(string input)
+ {
+ if (string.IsNullOrEmpty(input))
+ return string.Empty;
+
+ var parts = input.Split(',');
+ var trimmed = new List();
+
+ foreach (var part in parts)
+ {
+ var t = part.Trim();
+ if (!string.IsNullOrEmpty(t))
+ trimmed.Add(t);
+ }
+
+ return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty;
+ }
+
private ViewModels.Launcher _launcher = null;
private ResourceDictionary _activeLocale = null;
private ResourceDictionary _themeOverrides = null;
diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs
index 391aeeb2..2dc8a98d 100644
--- a/src/Commands/Branch.cs
+++ b/src/Commands/Branch.cs
@@ -54,21 +54,14 @@
public static bool DeleteRemote(string repo, string remote, string name)
{
+ bool exists = new Remote(repo).HasBranch(remote, name);
+ if (exists)
+ return new Push(repo, remote, $"refs/heads/{name}", true).Exec();
+
var cmd = new Command();
cmd.WorkingDirectory = repo;
cmd.Context = repo;
-
- bool exists = new Remote(repo).HasBranch(remote, name);
- if (exists)
- {
- cmd.SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
- cmd.Args = $"push {remote} --delete {name}";
- }
- else
- {
- cmd.Args = $"branch -D -r {remote}/{name}";
- }
-
+ cmd.Args = $"branch -D -r {remote}/{name}";
return cmd.Exec();
}
}
diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs
index 3f61de17..0fef1235 100644
--- a/src/Commands/Command.cs
+++ b/src/Commands/Command.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
+using System.Threading;
using Avalonia.Threading;
@@ -10,11 +11,6 @@ namespace SourceGit.Commands
{
public partial class Command
{
- public class CancelToken
- {
- public bool Requested { get; set; } = false;
- }
-
public class ReadToEndResult
{
public bool IsSuccess { get; set; } = false;
@@ -30,7 +26,7 @@ namespace SourceGit.Commands
}
public string Context { get; set; } = string.Empty;
- public CancelToken Cancel { get; set; } = null;
+ public CancellationToken CancellationToken { get; set; } = CancellationToken.None;
public string WorkingDirectory { get; set; } = null;
public EditorType Editor { get; set; } = EditorType.CoreEditor; // Only used in Exec() mode
public string SSHKey { get; set; } = string.Empty;
@@ -43,36 +39,15 @@ namespace SourceGit.Commands
var start = CreateGitStartInfo();
var errs = new List();
var proc = new Process() { StartInfo = start };
- var isCancelled = false;
proc.OutputDataReceived += (_, e) =>
{
- if (Cancel != null && Cancel.Requested)
- {
- isCancelled = true;
- proc.CancelErrorRead();
- proc.CancelOutputRead();
- if (!proc.HasExited)
- proc.Kill(true);
- return;
- }
-
if (e.Data != null)
OnReadline(e.Data);
};
proc.ErrorDataReceived += (_, e) =>
{
- if (Cancel != null && Cancel.Requested)
- {
- isCancelled = true;
- proc.CancelErrorRead();
- proc.CancelOutputRead();
- if (!proc.HasExited)
- proc.Kill(true);
- return;
- }
-
if (string.IsNullOrEmpty(e.Data))
{
errs.Add(string.Empty);
@@ -97,9 +72,25 @@ namespace SourceGit.Commands
errs.Add(e.Data);
};
+ var dummy = null as Process;
+ var dummyProcLock = new object();
try
{
proc.Start();
+
+ // It not safe, please only use `CancellationToken` in readonly commands.
+ if (CancellationToken.CanBeCanceled)
+ {
+ dummy = proc;
+ CancellationToken.Register(() =>
+ {
+ lock (dummyProcLock)
+ {
+ if (dummy is { HasExited: false })
+ dummy.Kill();
+ }
+ });
+ }
}
catch (Exception e)
{
@@ -113,10 +104,18 @@ namespace SourceGit.Commands
proc.BeginErrorReadLine();
proc.WaitForExit();
+ if (dummy != null)
+ {
+ lock (dummyProcLock)
+ {
+ dummy = null;
+ }
+ }
+
int exitCode = proc.ExitCode;
proc.Close();
- if (!isCancelled && exitCode != 0)
+ if (!CancellationToken.IsCancellationRequested && exitCode != 0)
{
if (RaiseError)
{
@@ -192,13 +191,12 @@ namespace SourceGit.Commands
if (!start.Environment.ContainsKey("GIT_SSH_COMMAND") && !string.IsNullOrEmpty(SSHKey))
start.Environment.Add("GIT_SSH_COMMAND", $"ssh -i '{SSHKey}'");
- // Force using en_US.UTF-8 locale to avoid GCM crash
+ // Force using en_US.UTF-8 locale
if (OperatingSystem.IsLinux())
- start.Environment.Add("LANG", "en_US.UTF-8");
-
- // Fix macOS `PATH` env
- if (OperatingSystem.IsMacOS() && !string.IsNullOrEmpty(Native.OS.CustomPathEnv))
- start.Environment.Add("PATH", Native.OS.CustomPathEnv);
+ {
+ start.Environment.Add("LANG", "C");
+ start.Environment.Add("LC_ALL", "C");
+ }
// Force using this app as git editor.
switch (Editor)
diff --git a/src/Commands/CountLocalChangesWithoutUntracked.cs b/src/Commands/CountLocalChangesWithoutUntracked.cs
index afb62840..7ab9a54a 100644
--- a/src/Commands/CountLocalChangesWithoutUntracked.cs
+++ b/src/Commands/CountLocalChangesWithoutUntracked.cs
@@ -8,7 +8,7 @@ namespace SourceGit.Commands
{
WorkingDirectory = repo;
Context = repo;
- Args = "status -uno --ignore-submodules=dirty --porcelain";
+ Args = "--no-optional-locks status -uno --ignore-submodules=dirty --porcelain";
}
public int Result()
diff --git a/src/Commands/ExecuteCustomAction.cs b/src/Commands/ExecuteCustomAction.cs
index 7775da34..000c8fd1 100644
--- a/src/Commands/ExecuteCustomAction.cs
+++ b/src/Commands/ExecuteCustomAction.cs
@@ -17,14 +17,6 @@ namespace SourceGit.Commands
start.CreateNoWindow = true;
start.WorkingDirectory = repo;
- // Force using en_US.UTF-8 locale to avoid GCM crash
- if (OperatingSystem.IsLinux())
- start.Environment.Add("LANG", "en_US.UTF-8");
-
- // Fix macOS `PATH` env
- if (OperatingSystem.IsMacOS() && !string.IsNullOrEmpty(Native.OS.CustomPathEnv))
- start.Environment.Add("PATH", Native.OS.CustomPathEnv);
-
try
{
Process.Start(start);
@@ -48,14 +40,6 @@ namespace SourceGit.Commands
start.StandardErrorEncoding = Encoding.UTF8;
start.WorkingDirectory = repo;
- // Force using en_US.UTF-8 locale to avoid GCM crash
- if (OperatingSystem.IsLinux())
- start.Environment.Add("LANG", "en_US.UTF-8");
-
- // Fix macOS `PATH` env
- if (OperatingSystem.IsMacOS() && !string.IsNullOrEmpty(Native.OS.CustomPathEnv))
- start.Environment.Add("PATH", Native.OS.CustomPathEnv);
-
var proc = new Process() { StartInfo = start };
var builder = new StringBuilder();
diff --git a/src/Commands/Push.cs b/src/Commands/Push.cs
index 69b859ab..dc81f606 100644
--- a/src/Commands/Push.cs
+++ b/src/Commands/Push.cs
@@ -26,7 +26,7 @@ namespace SourceGit.Commands
Args += $"{remote} {local}:{remoteBranch}";
}
- public Push(string repo, string remote, string tag, bool isDelete)
+ public Push(string repo, string remote, string refname, bool isDelete)
{
WorkingDirectory = repo;
Context = repo;
@@ -36,7 +36,7 @@ namespace SourceGit.Commands
if (isDelete)
Args += "--delete ";
- Args += $"{remote} refs/tags/{tag}";
+ Args += $"{remote} {refname}";
}
protected override void OnReadline(string line)
diff --git a/src/Commands/QueryCommitChildren.cs b/src/Commands/QueryCommitChildren.cs
index d1bced52..6a6ed909 100644
--- a/src/Commands/QueryCommitChildren.cs
+++ b/src/Commands/QueryCommitChildren.cs
@@ -9,7 +9,7 @@ namespace SourceGit.Commands
WorkingDirectory = repo;
Context = repo;
_commit = commit;
- Args = $"rev-list -{max} --parents --branches --remotes ^{commit}";
+ Args = $"rev-list -{max} --parents --branches --remotes --ancestry-path={commit} ^{commit}";
}
public List Result()
diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs
index ea422215..9458e5f5 100644
--- a/src/Commands/QueryLocalChanges.cs
+++ b/src/Commands/QueryLocalChanges.cs
@@ -13,7 +13,7 @@ namespace SourceGit.Commands
{
WorkingDirectory = repo;
Context = repo;
- Args = $"status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
+ Args = $"--no-optional-locks status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
}
public List Result()
diff --git a/src/Commands/QueryRevisionFileNames.cs b/src/Commands/QueryRevisionFileNames.cs
index d2d69614..c6fd7373 100644
--- a/src/Commands/QueryRevisionFileNames.cs
+++ b/src/Commands/QueryRevisionFileNames.cs
@@ -1,4 +1,6 @@
-namespace SourceGit.Commands
+using System.Collections.Generic;
+
+namespace SourceGit.Commands
{
public class QueryRevisionFileNames : Command
{
@@ -9,13 +11,17 @@
Args = $"ls-tree -r -z --name-only {revision}";
}
- public string[] Result()
+ public List Result()
{
var rs = ReadToEnd();
- if (rs.IsSuccess)
- return rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
+ if (!rs.IsSuccess)
+ return [];
- return [];
+ var lines = rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
+ var outs = new List();
+ foreach (var line in lines)
+ outs.Add(line);
+ return outs;
}
}
}
diff --git a/src/Commands/QuerySubmodules.cs b/src/Commands/QuerySubmodules.cs
index 6ebfa8b1..1ceccf78 100644
--- a/src/Commands/QuerySubmodules.cs
+++ b/src/Commands/QuerySubmodules.cs
@@ -49,7 +49,7 @@ namespace SourceGit.Commands
if (submodules.Count > 0)
{
- Args = $"status -uno --porcelain -- {builder}";
+ Args = $"--no-optional-locks status -uno --porcelain -- {builder}";
rs = ReadToEnd();
if (!rs.IsSuccess)
return submodules;
diff --git a/src/Commands/Tag.cs b/src/Commands/Tag.cs
index fa11e366..23dbb11c 100644
--- a/src/Commands/Tag.cs
+++ b/src/Commands/Tag.cs
@@ -48,9 +48,7 @@ namespace SourceGit.Commands
if (remotes != null)
{
foreach (var r in remotes)
- {
- new Push(repo, r.Name, name, true).Exec();
- }
+ new Push(repo, r.Name, $"refs/tags/{name}", true).Exec();
}
return true;
diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs
index 5c48b0c0..0bad8376 100644
--- a/src/Models/Commit.cs
+++ b/src/Models/Commit.cs
@@ -8,6 +8,7 @@ namespace SourceGit.Models
{
public enum CommitSearchMethod
{
+ BySHA = 0,
ByAuthor,
ByCommitter,
ByMessage,
@@ -35,6 +36,7 @@ namespace SourceGit.Models
public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateOnly);
+ public string CommitterTimeShortStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateOnly);
public bool IsMerged { get; set; } = false;
public bool IsCommitterVisible => !Author.Equals(Committer) || AuthorTime != CommitterTime;
diff --git a/src/Models/DealWithLocalChanges.cs b/src/Models/DealWithLocalChanges.cs
deleted file mode 100644
index f308a90c..00000000
--- a/src/Models/DealWithLocalChanges.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace SourceGit.Models
-{
- public enum DealWithLocalChanges
- {
- DoNothing,
- StashAndReaply,
- Discard,
- }
-}
diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs
index 08bf48ca..76a7f160 100644
--- a/src/Models/RepositorySettings.cs
+++ b/src/Models/RepositorySettings.cs
@@ -471,11 +471,7 @@ namespace SourceGit.Models
public CustomAction AddNewCustomAction()
{
- var act = new CustomAction()
- {
- Name = "Unnamed Custom Action",
- };
-
+ var act = new CustomAction() { Name = "Unnamed Action" };
CustomActions.Add(act);
return act;
}
diff --git a/src/Models/User.cs b/src/Models/User.cs
index 850bcf2f..066ab747 100644
--- a/src/Models/User.cs
+++ b/src/Models/User.cs
@@ -43,6 +43,11 @@ namespace SourceGit.Models
return _caches.GetOrAdd(data, key => new User(key));
}
+ public override string ToString()
+ {
+ return $"{Name} <{Email}>";
+ }
+
private static ConcurrentDictionary _caches = new ConcurrentDictionary();
private readonly int _hash;
}
diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs
index 633ef5eb..123b160b 100644
--- a/src/Native/MacOS.cs
+++ b/src/Native/MacOS.cs
@@ -18,9 +18,22 @@ namespace SourceGit.Native
DisableDefaultApplicationMenuItems = true,
});
+ // Fix `PATH` env on macOS.
+ var path = Environment.GetEnvironmentVariable("PATH");
+ if (string.IsNullOrEmpty(path))
+ path = "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
+ else if (!path.Contains("/opt/homebrew/", StringComparison.Ordinal))
+ path = "/opt/homebrew/bin:/opt/homebrew/sbin:" + path;
+
var customPathFile = Path.Combine(OS.DataDir, "PATH");
if (File.Exists(customPathFile))
- OS.CustomPathEnv = File.ReadAllText(customPathFile).Trim();
+ {
+ var env = File.ReadAllText(customPathFile).Trim();
+ if (!string.IsNullOrEmpty(env))
+ path = env;
+ }
+
+ Environment.SetEnvironmentVariable("PATH", path);
}
public string FindGitExecutable()
diff --git a/src/Native/OS.cs b/src/Native/OS.cs
index 3a688654..f11d1e7f 100644
--- a/src/Native/OS.cs
+++ b/src/Native/OS.cs
@@ -31,12 +31,6 @@ namespace SourceGit.Native
private set;
} = string.Empty;
- public static string CustomPathEnv
- {
- get;
- set;
- } = string.Empty;
-
public static string GitExecutable
{
get => _gitExecutable;
diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs
index 11b6bd13..eb354f10 100644
--- a/src/Native/Windows.cs
+++ b/src/Native/Windows.cs
@@ -8,6 +8,7 @@ using System.Text;
using Avalonia;
using Avalonia.Controls;
+using Avalonia.Threading;
namespace SourceGit.Native
{
@@ -214,12 +215,17 @@ namespace SourceGit.Native
private void FixWindowFrameOnWin10(Window w)
{
- var platformHandle = w.TryGetPlatformHandle();
- if (platformHandle == null)
- return;
+ // Schedule the DWM frame extension to run in the next render frame
+ // to ensure proper timing with the window initialization sequence
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ var platformHandle = w.TryGetPlatformHandle();
+ if (platformHandle == null)
+ return;
- var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
- DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins);
+ var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
+ DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins);
+ }, DispatcherPriority.Render);
}
#region EXTERNAL_EDITOR_FINDER
diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml
index 1d0ca895..aff8ffc5 100644
--- a/src/Resources/Locales/de_DE.axaml
+++ b/src/Resources/Locales/de_DE.axaml
@@ -90,7 +90,6 @@
Branch:
Lokale Änderungen:
Verwerfen
- Nichts tun
Stashen & wieder anwenden
Cherry Pick
Quelle an Commit-Nachricht anhängen
@@ -206,7 +205,6 @@
Erstellten Branch auschecken
Lokale Änderungen:
Verwerfen
- Nichts tun
Stashen & wieder anwenden
Neuer Branch-Name:
Branch-Namen eingeben.
@@ -445,6 +443,7 @@
Einfügen
Gerade eben
Vor {0} Minuten
+ Vor 1 Stunde
Vor {0} Stunden
Gestern
Vor {0} Tagen
@@ -517,7 +516,6 @@
Lokaler Branch:
Lokale Änderungen:
Verwerfen
- Nichts tun
Stashen & wieder anwenden
Ohne Tags fetchen
Remote:
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index 5df3ca71..818bd9bb 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -88,7 +88,6 @@
Branch:
Local Changes:
Discard
- Do Nothing
Stash & Reapply
Cherry Pick
Append source to commit message
@@ -205,7 +204,6 @@
Check out the created branch
Local Changes:
Discard
- Do Nothing
Stash & Reapply
New Branch Name:
Enter branch name.
@@ -446,6 +444,7 @@
Paste
Just now
{0} minutes ago
+ 1 hour ago
{0} hours ago
Yesterday
{0} days ago
@@ -520,7 +519,6 @@
Into:
Local Changes:
Discard
- Do Nothing
Stash & Reapply
Fetch without tags
Remote:
diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml
index 4073f512..e909a14e 100644
--- a/src/Resources/Locales/es_ES.axaml
+++ b/src/Resources/Locales/es_ES.axaml
@@ -91,7 +91,6 @@
Rama:
Cambios Locales:
Descartar
- No Hacer Nada
Stash & Reaplicar
Cherry Pick
Añadir fuente al mensaje de commit
@@ -208,7 +207,6 @@
Checkout de la rama creada
Cambios Locales:
Descartar
- No Hacer Nada
Stash & Reaplicar
Nombre de la Nueva Rama:
Introduzca el nombre de la rama.
@@ -449,6 +447,7 @@
Pegar
Justo ahora
Hace {0} minutos
+ Hace 1 hora
Hace {0} horas
Ayer
Hace {0} días
@@ -524,7 +523,6 @@
En:
Cambios Locales:
Descartar
- No Hacer Nada
Stash & Reaplicar
Fetch sin etiquetas
Remoto:
diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml
index 59fe00a8..aecea9ad 100644
--- a/src/Resources/Locales/fr_FR.axaml
+++ b/src/Resources/Locales/fr_FR.axaml
@@ -83,7 +83,6 @@
Branche :
Changements locaux :
Annuler
- Ne rien faire
Mettre en stash et réappliquer
Cherry-Pick de ce commit
Ajouter la source au message de commit
@@ -198,7 +197,6 @@
Récupérer la branche créée
Changements locaux :
Rejeter
- Ne rien faire
Stash & Réappliquer
Nom de la nouvelle branche :
Entrez le nom de la branche.
@@ -424,6 +422,7 @@
Coller
A l'instant
il y a {0} minutes
+ il y a 1 heure
il y a {0} heures
Hier
il y a {0} jours
@@ -492,7 +491,6 @@
Dans :
Changements locaux :
Rejeter
- Ne rien faire
Stash & Réappliquer
Fetch sans les tags
Dépôt distant :
diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml
index 6e99decf..4dcc8771 100644
--- a/src/Resources/Locales/it_IT.axaml
+++ b/src/Resources/Locales/it_IT.axaml
@@ -91,7 +91,6 @@
Branch:
Modifiche Locali:
Scarta
- Non fare nulla
Stasha e Ripristina
Cherry Pick
Aggiungi sorgente al messaggio di commit
@@ -208,7 +207,6 @@
Checkout del Branch Creato
Modifiche Locali:
Scarta
- Non Fare Nulla
Stasha e Ripristina
Nome Nuovo Branch:
Inserisci il nome del branch.
@@ -450,6 +448,7 @@
Incolla
Proprio ora
{0} minuti fa
+ 1 ora fa
{0} ore fa
Ieri
{0} giorni fa
@@ -523,7 +522,6 @@
In:
Modifiche Locali:
Scarta
- Non fare nulla
Stasha e Riapplica
Recupera senza tag
Remoto:
diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml
index 78445bfb..b146bf0e 100644
--- a/src/Resources/Locales/pt_BR.axaml
+++ b/src/Resources/Locales/pt_BR.axaml
@@ -107,7 +107,6 @@
Branch:
Alterações Locais:
Descartar
- Nada
Stash & Reaplicar
Cherry-Pick
Adicionar origem à mensagem de commit
@@ -215,7 +214,6 @@
Checar o branch criado
Alterações Locais:
Descartar
- Não Fazer Nada
Guardar & Reaplicar
Nome do Novo Branch:
Insira o nome do branch.
@@ -437,6 +435,7 @@
Colar
Agora mesmo
{0} minutos atrás
+ 1 hora atrás
{0} horas atrás
Ontem
{0} dias atrás
@@ -506,7 +505,6 @@
Para:
Alterações Locais:
Descartar
- Não Fazer Nada
Guardar & Reaplicar
Buscar sem tags
Remoto:
diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml
index b15a769b..07fb7c94 100644
--- a/src/Resources/Locales/ru_RU.axaml
+++ b/src/Resources/Locales/ru_RU.axaml
@@ -11,9 +11,9 @@
• Исходный код можно найти по адресу
Бесплатный графический клиент Git с исходным кодом
Добавить рабочий каталог
- Что проверить:
- Существующую ветку
- Создать новую ветку
+ Переключиться на:
+ ветку из списка
+ создать новую ветку
Расположение:
Путь к рабочему каталогу (поддерживается относительный путь)
Имя ветки:
@@ -91,7 +91,6 @@
Ветка:
Локальные изменения:
Отклонить
- Ничего не делать
Отложить и примненить повторно
Частичный выбор
Добавить источник для ревизии сообщения
@@ -126,7 +125,7 @@
Сбросить ${0}$ сюда
Отменить ревизию
Изменить комментарий
- Сохранить как patch-файл...
+ Сохранить как заплатки...
Объединить с предыдущей ревизией
Объединить все следующие ревизии с этим
ИЗМЕНЕНИЯ
@@ -167,7 +166,7 @@
Адрес электронной почты
Адрес электронной почты
GIT
- Автоматическая загрузка изменений
+ Автозагрузка изменений
Минут(а/ы)
Внешний репозиторий по умолчанию
ОТСЛЕЖИВАНИЕ ПРОБЛЕМ
@@ -209,7 +208,6 @@
Проверить созданную ветку
Локальные изменения:
Отклонить
- Ничего не делать
Отложить и применить повторно
Имя новой ветки:
Введите имя ветки.
@@ -218,15 +216,15 @@
Создать метку...
Новая метка у:
GPG подпись
- Сообщение с меткой:
+ Сообщение с
меткой:
Необязательно.
Имя метки:
Рекомендуемый формат: v1.0.0-alpha
Выложить на все внешние репозитории после создания
Создать новую метку
Вид:
- Аннотированный
- Лёгкий
+ С примечаниями
+ Простой
Удерживайте Ctrl, чтобы начать сразу
Вырезать
Удалить ветку
@@ -237,7 +235,7 @@
Вы пытаетесь удалить несколько веток одновременно. Обязательно перепроверьте, прежде чем предпринимать какие-либо действия!
Удалить внешний репозиторий
Внешний репозиторий:
- Path:
+ Путь:
Цель:
Все дочерние элементы будут удалены из списка.
Подтвердите удаление группы
@@ -262,7 +260,7 @@
НИКАКИХ ИЗМЕНЕНИЙ ИЛИ МЕНЯЕТСЯ ТОЛЬКО EOL
Предыдущее различие
Сохранить как заплатку
- Различие бок о бок
+ Различие рядом
ПОДМОДУЛЬ
НОВЫЙ
Обмен
@@ -450,6 +448,7 @@
Вставить
Сейчас
{0} минут назад
+ 1 час назад
{0} часов назад
Вчера
{0} дней назад
@@ -524,7 +523,6 @@
В:
Локальные изменения:
Отклонить
- Ничего не делать
Отложить и применить повторно
Забрать без меток
Внешний репозиторий:
@@ -553,7 +551,7 @@
Редактировать внешний репозиторий
Имя:
Имя внешнего репозитория
- Адрес репозитория:
+ Адрес:
Адрес внешнего репозитория git
Копировать адрес
Удалить...
@@ -693,8 +691,8 @@
Копировать относительный путь
Извлечение вложенных подмодулей
Открыть подмодуль репозитория
- Относительный путь:
- Относительный каталог для хранения подмодуля.
+ Каталог:
+ Относительный путь для хранения подмодуля.
Удалить подмодуль
ОК
Копировать имя метки
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index 72b434f9..2d160ad2 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -91,7 +91,6 @@
目标分支 :
未提交更改 :
丢弃更改
- 不做处理
贮藏并自动恢复
挑选提交
提交信息中追加来源信息
@@ -208,7 +207,6 @@
完成后切换到新分支
未提交更改 :
丢弃更改
- 不做处理
贮藏并自动恢复
新分支名 :
填写分支名称。
@@ -449,6 +447,7 @@
粘贴
刚刚
{0}分钟前
+ 1小时前
{0}小时前
昨天
{0}天前
@@ -524,7 +523,6 @@
本地分支 :
未提交更改 :
丢弃更改
- 不做处理
贮藏并自动恢复
不拉取远程标签
远程 :
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index bc9991f6..e50a600d 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -91,7 +91,6 @@
目標分支:
未提交變更:
捨棄變更
- 不做處理
擱置變更並自動復原
揀選提交
提交資訊中追加來源資訊
@@ -208,7 +207,6 @@
完成後切換到新分支
未提交變更:
捨棄變更
- 不做處理
擱置變更並自動復原
新分支名稱:
輸入分支名稱。
@@ -449,6 +447,7 @@
貼上
剛剛
{0} 分鐘前
+ 1 小時前
{0} 小時前
昨天
{0} 天前
@@ -523,7 +522,6 @@
本機分支:
未提交變更:
捨棄變更
- 不做處理
擱置變更並自動復原
不拉取遠端標籤
遠端:
diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs
index 9376741d..3334eba4 100644
--- a/src/ViewModels/Checkout.cs
+++ b/src/ViewModels/Checkout.cs
@@ -9,16 +9,17 @@ namespace SourceGit.ViewModels
get;
}
- public Models.DealWithLocalChanges PreAction
+ public bool DiscardLocalChanges
{
get;
set;
- } = Models.DealWithLocalChanges.DoNothing;
+ }
public Checkout(Repository repo, string branch)
{
_repo = repo;
Branch = branch;
+ DiscardLocalChanges = false;
View = new Views.Checkout() { DataContext = this };
}
@@ -33,7 +34,12 @@ namespace SourceGit.ViewModels
var needPopStash = false;
if (changes > 0)
{
- if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
+ if (DiscardLocalChanges)
+ {
+ SetProgressDescription("Discard local changes ...");
+ Commands.Discard.All(_repo.FullPath, false);
+ }
+ else
{
SetProgressDescription("Stash local changes ...");
var succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH");
@@ -45,11 +51,6 @@ namespace SourceGit.ViewModels
needPopStash = true;
}
- else if (PreAction == Models.DealWithLocalChanges.Discard)
- {
- SetProgressDescription("Discard local changes ...");
- Commands.Discard.All(_repo.FullPath, false);
- }
}
SetProgressDescription("Checkout branch ...");
diff --git a/src/ViewModels/CheckoutCommit.cs b/src/ViewModels/CheckoutCommit.cs
index ddc0a0c6..1876a425 100644
--- a/src/ViewModels/CheckoutCommit.cs
+++ b/src/ViewModels/CheckoutCommit.cs
@@ -9,16 +9,17 @@ namespace SourceGit.ViewModels
get;
}
- public bool AutoStash
+ public bool DiscardLocalChanges
{
- get => _autoStash;
- set => SetProperty(ref _autoStash, value);
+ get;
+ set;
}
public CheckoutCommit(Repository repo, Models.Commit commit)
{
_repo = repo;
Commit = commit;
+ DiscardLocalChanges = false;
View = new Views.CheckoutCommit() { DataContext = this };
}
@@ -33,7 +34,12 @@ namespace SourceGit.ViewModels
var needPopStash = false;
if (changes > 0)
{
- if (AutoStash)
+ if (DiscardLocalChanges)
+ {
+ SetProgressDescription("Discard local changes ...");
+ Commands.Discard.All(_repo.FullPath, false);
+ }
+ else
{
SetProgressDescription("Stash local changes ...");
var succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH");
@@ -45,11 +51,6 @@ namespace SourceGit.ViewModels
needPopStash = true;
}
- else
- {
- SetProgressDescription("Discard local changes ...");
- Commands.Discard.All(_repo.FullPath, false);
- }
}
SetProgressDescription("Checkout commit ...");
@@ -67,6 +68,5 @@ namespace SourceGit.ViewModels
}
private readonly Repository _repo = null;
- private bool _autoStash = true;
}
}
diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs
index 456e99f8..34ac8308 100644
--- a/src/ViewModels/CommitDetail.cs
+++ b/src/ViewModels/CommitDetail.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
+using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
@@ -171,7 +172,7 @@ namespace SourceGit.ViewModels
_searchChangeFilter = null;
_diffContext = null;
_viewRevisionFileContent = null;
- _cancelToken = null;
+ _cancellationSource = null;
WebLinks.Clear();
_revisionFiles = null;
_revisionFileSearchSuggestion = null;
@@ -589,32 +590,36 @@ namespace SourceGit.ViewModels
if (_commit == null)
return;
+ if (_cancellationSource is { IsCancellationRequested: false })
+ _cancellationSource.Cancel();
+
+ _cancellationSource = new CancellationTokenSource();
+ var token = _cancellationSource.Token;
+
Task.Run(() =>
{
var message = new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA).Result();
var links = ParseLinksInMessage(message);
- Dispatcher.UIThread.Invoke(() => FullMessage = new Models.CommitFullMessage { Message = message, Links = links });
+
+ if (!token.IsCancellationRequested)
+ Dispatcher.UIThread.Invoke(() => FullMessage = new Models.CommitFullMessage { Message = message, Links = links });
});
Task.Run(() =>
{
var signInfo = new Commands.QueryCommitSignInfo(_repo.FullPath, _commit.SHA, !_repo.HasAllowedSignersFile).Result();
- Dispatcher.UIThread.Invoke(() => SignInfo = signInfo);
+ if (!token.IsCancellationRequested)
+ Dispatcher.UIThread.Invoke(() => SignInfo = signInfo);
});
- if (_cancelToken != null)
- _cancelToken.Requested = true;
-
- _cancelToken = new Commands.Command.CancelToken();
-
if (Preferences.Instance.ShowChildren)
{
Task.Run(() =>
{
var max = Preferences.Instance.MaxHistoryCommits;
- var cmdChildren = new Commands.QueryCommitChildren(_repo.FullPath, _commit.SHA, max) { Cancel = _cancelToken };
- var children = cmdChildren.Result();
- if (!cmdChildren.Cancel.Requested)
+ var cmd = new Commands.QueryCommitChildren(_repo.FullPath, _commit.SHA, max) { CancellationToken = token };
+ var children = cmd.Result();
+ if (!token.IsCancellationRequested)
Dispatcher.UIThread.Post(() => Children = children);
});
}
@@ -622,8 +627,8 @@ namespace SourceGit.ViewModels
Task.Run(() =>
{
var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0];
- var cmdChanges = new Commands.CompareRevisions(_repo.FullPath, parent, _commit.SHA) { Cancel = _cancelToken };
- var changes = cmdChanges.Result();
+ var cmd = new Commands.CompareRevisions(_repo.FullPath, parent, _commit.SHA) { CancellationToken = token };
+ var changes = cmd.Result();
var visible = changes;
if (!string.IsNullOrWhiteSpace(_searchChangeFilter))
{
@@ -635,7 +640,7 @@ namespace SourceGit.ViewModels
}
}
- if (!cmdChanges.Cancel.Requested)
+ if (!token.IsCancellationRequested)
{
Dispatcher.UIThread.Post(() =>
{
@@ -809,14 +814,11 @@ namespace SourceGit.ViewModels
Task.Run(() =>
{
var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result();
- var filesList = new List();
- filesList.AddRange(files);
-
Dispatcher.UIThread.Invoke(() =>
{
if (sha == Commit.SHA)
{
- _revisionFiles = filesList;
+ _revisionFiles = files;
if (!string.IsNullOrEmpty(_revisionFileSearchFilter))
CalcRevisionFileSearchSuggestion();
}
@@ -873,7 +875,7 @@ namespace SourceGit.ViewModels
private string _searchChangeFilter = string.Empty;
private DiffContext _diffContext = null;
private object _viewRevisionFileContent = null;
- private Commands.Command.CancelToken _cancelToken = null;
+ private CancellationTokenSource _cancellationSource = null;
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 03c09e8a..1ccf4a33 100644
--- a/src/ViewModels/Conflict.cs
+++ b/src/ViewModels/Conflict.cs
@@ -1,5 +1,26 @@
namespace SourceGit.ViewModels
{
+ public class ConflictSourceBranch
+ {
+ public string Name { get; private set; }
+ public string Head { get; private set; }
+ public Models.Commit Revision { get; private set; }
+
+ public ConflictSourceBranch(string name, string head, Models.Commit revision)
+ {
+ Name = name;
+ Head = head;
+ Revision = revision;
+ }
+
+ public ConflictSourceBranch(Repository repo, Models.Branch branch)
+ {
+ Name = branch.Name;
+ Head = branch.Head;
+ Revision = new Commands.QuerySingleCommit(repo.FullPath, branch.Head).Result() ?? new Models.Commit() { SHA = branch.Head };
+ }
+ }
+
public class Conflict
{
public object Theirs
@@ -31,28 +52,32 @@
if (context is CherryPickInProgress cherryPick)
{
Theirs = cherryPick.Head;
- Mine = repo.CurrentBranch;
+ Mine = new ConflictSourceBranch(repo, repo.CurrentBranch);
}
else if (context is RebaseInProgress rebase)
{
var b = repo.Branches.Find(x => x.IsLocal && x.Name == rebase.HeadName);
- Theirs = (object)b ?? rebase.StoppedAt;
+ if (b != null)
+ Theirs = new ConflictSourceBranch(b.Name, b.Head, rebase.StoppedAt);
+ else
+ Theirs = new ConflictSourceBranch(rebase.HeadName, rebase.StoppedAt?.SHA ?? "----------", rebase.StoppedAt);
+
Mine = rebase.Onto;
}
else if (context is RevertInProgress revert)
{
Theirs = revert.Head;
- Mine = repo.CurrentBranch;
+ Mine = new ConflictSourceBranch(repo, repo.CurrentBranch);
}
else if (context is MergeInProgress merge)
{
Theirs = merge.Source;
- Mine = repo.CurrentBranch;
+ Mine = new ConflictSourceBranch(repo, repo.CurrentBranch);
}
else
{
Theirs = "Stash or Patch";
- Mine = repo.CurrentBranch;
+ Mine = new ConflictSourceBranch(repo, repo.CurrentBranch);
}
}
diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs
index 01bff031..37db0065 100644
--- a/src/ViewModels/CreateBranch.cs
+++ b/src/ViewModels/CreateBranch.cs
@@ -19,11 +19,11 @@ namespace SourceGit.ViewModels
get;
}
- public Models.DealWithLocalChanges PreAction
+ public bool DiscardLocalChanges
{
get;
set;
- } = Models.DealWithLocalChanges.DoNothing;
+ }
public bool CheckoutAfterCreated
{
@@ -47,6 +47,7 @@ namespace SourceGit.ViewModels
}
BasedOn = branch;
+ DiscardLocalChanges = false;
View = new Views.CreateBranch() { DataContext = this };
}
@@ -56,6 +57,7 @@ namespace SourceGit.ViewModels
_baseOnRevision = commit.SHA;
BasedOn = commit;
+ DiscardLocalChanges = false;
View = new Views.CreateBranch() { DataContext = this };
}
@@ -65,6 +67,7 @@ namespace SourceGit.ViewModels
_baseOnRevision = tag.SHA;
BasedOn = tag;
+ DiscardLocalChanges = false;
View = new Views.CreateBranch() { DataContext = this };
}
@@ -98,7 +101,12 @@ namespace SourceGit.ViewModels
var needPopStash = false;
if (changes > 0)
{
- if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
+ if (DiscardLocalChanges)
+ {
+ SetProgressDescription("Discard local changes...");
+ Commands.Discard.All(_repo.FullPath, false);
+ }
+ else
{
SetProgressDescription("Stash local changes");
succ = new Commands.Stash(_repo.FullPath).Push("CREATE_BRANCH_AUTO_STASH");
@@ -110,11 +118,6 @@ namespace SourceGit.ViewModels
needPopStash = true;
}
- else if (PreAction == Models.DealWithLocalChanges.Discard)
- {
- SetProgressDescription("Discard local changes...");
- Commands.Discard.All(_repo.FullPath, false);
- }
}
SetProgressDescription($"Create new branch '{fixedName}'");
diff --git a/src/ViewModels/CreateTag.cs b/src/ViewModels/CreateTag.cs
index a6d7255b..86ae7118 100644
--- a/src/ViewModels/CreateTag.cs
+++ b/src/ViewModels/CreateTag.cs
@@ -96,7 +96,7 @@ namespace SourceGit.ViewModels
foreach (var remote in remotes)
{
SetProgressDescription($"Pushing tag to remote {remote.Name} ...");
- new Commands.Push(_repo.FullPath, remote.Name, _tagName, false).Exec();
+ new Commands.Push(_repo.FullPath, remote.Name, $"refs/tags/{_tagName}", false).Exec();
}
}
diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs
index 8e34379f..16a6410c 100644
--- a/src/ViewModels/ExecuteCustomAction.cs
+++ b/src/ViewModels/ExecuteCustomAction.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
@@ -13,7 +14,7 @@ namespace SourceGit.ViewModels
public ExecuteCustomAction(Repository repo, Models.CustomAction action)
{
_repo = repo;
- _args = action.Arguments.Replace("${REPO}", _repo.FullPath);
+ _args = action.Arguments.Replace("${REPO}", GetWorkdir());
CustomAction = action;
View = new Views.ExecuteCustomAction() { DataContext = this };
}
@@ -21,7 +22,7 @@ namespace SourceGit.ViewModels
public ExecuteCustomAction(Repository repo, Models.CustomAction action, Models.Branch branch)
{
_repo = repo;
- _args = action.Arguments.Replace("${REPO}", _repo.FullPath).Replace("${BRANCH}", branch.FriendlyName);
+ _args = action.Arguments.Replace("${REPO}", GetWorkdir()).Replace("${BRANCH}", branch.FriendlyName);
CustomAction = action;
View = new Views.ExecuteCustomAction() { DataContext = this };
}
@@ -29,7 +30,7 @@ namespace SourceGit.ViewModels
public ExecuteCustomAction(Repository repo, Models.CustomAction action, Models.Commit commit)
{
_repo = repo;
- _args = action.Arguments.Replace("${REPO}", _repo.FullPath).Replace("${SHA}", commit.SHA);
+ _args = action.Arguments.Replace("${REPO}", GetWorkdir()).Replace("${SHA}", commit.SHA);
CustomAction = action;
View = new Views.ExecuteCustomAction() { DataContext = this };
}
@@ -51,6 +52,11 @@ namespace SourceGit.ViewModels
});
}
+ private string GetWorkdir()
+ {
+ return OperatingSystem.IsWindows() ? _repo.FullPath.Replace("/", "\\") : _repo.FullPath;
+ }
+
private readonly Repository _repo = null;
private string _args = string.Empty;
}
diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs
index 48ce114d..0e67915c 100644
--- a/src/ViewModels/Histories.cs
+++ b/src/ViewModels/Histories.cs
@@ -148,14 +148,14 @@ namespace SourceGit.ViewModels
{
if (commits.Count == 0)
{
- _repo.SearchResultSelectedCommit = null;
+ _repo.SelectedSearchedCommit = null;
DetailContext = null;
}
else if (commits.Count == 1)
{
var commit = (commits[0] as Models.Commit)!;
- if (_repo.SearchResultSelectedCommit == null || _repo.SearchResultSelectedCommit.SHA != commit.SHA)
- _repo.SearchResultSelectedCommit = _repo.SearchedCommits.Find(x => x.SHA == commit.SHA);
+ if (_repo.SelectedSearchedCommit == null || _repo.SelectedSearchedCommit.SHA != commit.SHA)
+ _repo.SelectedSearchedCommit = _repo.SearchedCommits.Find(x => x.SHA == commit.SHA);
AutoSelectedCommit = commit;
NavigationId = _navigationId + 1;
@@ -173,7 +173,7 @@ namespace SourceGit.ViewModels
}
else if (commits.Count == 2)
{
- _repo.SearchResultSelectedCommit = null;
+ _repo.SelectedSearchedCommit = null;
var end = commits[0] as Models.Commit;
var start = commits[1] as Models.Commit;
@@ -181,7 +181,7 @@ namespace SourceGit.ViewModels
}
else
{
- _repo.SearchResultSelectedCommit = null;
+ _repo.SelectedSearchedCommit = null;
DetailContext = commits.Count;
}
}
@@ -599,7 +599,7 @@ namespace SourceGit.ViewModels
var head = _commits.Find(x => x.SHA == current.Head);
if (head == null)
{
- _repo.SearchResultSelectedCommit = null;
+ _repo.SelectedSearchedCommit = null;
head = new Commands.QuerySingleCommit(_repo.FullPath, current.Head).Result();
if (head != null)
DetailContext = new RevisionCompare(_repo.FullPath, commit, head);
@@ -694,12 +694,7 @@ namespace SourceGit.ViewModels
menu.Items.Add(archive);
menu.Items.Add(new MenuItem() { Header = "-" });
- var actions = new List();
- foreach (var action in _repo.Settings.CustomActions)
- {
- if (action.Scope == Models.CustomActionScope.Commit)
- actions.Add(action);
- }
+ var actions = _repo.GetCustomActions(Models.CustomActionScope.Commit);
if (actions.Count > 0)
{
var custom = new MenuItem();
diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs
index 016fd4c4..0b1d841e 100644
--- a/src/ViewModels/Preferences.cs
+++ b/src/ViewModels/Preferences.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Avalonia.Collections;
@@ -66,9 +65,8 @@ namespace SourceGit.ViewModels
get => _defaultFontFamily;
set
{
- var name = FixFontFamilyName(value);
- if (SetProperty(ref _defaultFontFamily, name) && !_isLoading)
- App.SetFonts(_defaultFontFamily, _monospaceFontFamily, _onlyUseMonoFontInEditor);
+ if (SetProperty(ref _defaultFontFamily, value) && !_isLoading)
+ App.SetFonts(value, _monospaceFontFamily, _onlyUseMonoFontInEditor);
}
}
@@ -77,9 +75,8 @@ namespace SourceGit.ViewModels
get => _monospaceFontFamily;
set
{
- var name = FixFontFamilyName(value);
- if (SetProperty(ref _monospaceFontFamily, name) && !_isLoading)
- App.SetFonts(_defaultFontFamily, _monospaceFontFamily, _onlyUseMonoFontInEditor);
+ if (SetProperty(ref _monospaceFontFamily, value) && !_isLoading)
+ App.SetFonts(_defaultFontFamily, value, _onlyUseMonoFontInEditor);
}
}
@@ -342,6 +339,12 @@ namespace SourceGit.ViewModels
set;
} = [];
+ public AvaloniaList CustomActions
+ {
+ get;
+ set;
+ } = [];
+
public AvaloniaList OpenAIServices
{
get;
@@ -614,35 +617,6 @@ namespace SourceGit.ViewModels
return changed;
}
- private string FixFontFamilyName(string name)
- {
- var trimmed = name.Trim();
- if (string.IsNullOrEmpty(trimmed))
- return string.Empty;
-
- var builder = new StringBuilder();
- var lastIsSpace = false;
- for (int i = 0; i < trimmed.Length; i++)
- {
- var c = trimmed[i];
- if (char.IsWhiteSpace(c))
- {
- if (lastIsSpace)
- continue;
-
- lastIsSpace = true;
- }
- else
- {
- lastIsSpace = false;
- }
-
- builder.Append(c);
- }
-
- return builder.ToString();
- }
-
private static Preferences _instance = null;
private static bool _isLoading = false;
diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs
index ff557792..62d68834 100644
--- a/src/ViewModels/Pull.cs
+++ b/src/ViewModels/Pull.cs
@@ -38,11 +38,11 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _selectedBranch, value, true);
}
- public Models.DealWithLocalChanges PreAction
+ public bool DiscardLocalChanges
{
get;
set;
- } = Models.DealWithLocalChanges.DoNothing;
+ }
public bool UseRebase
{
@@ -124,7 +124,12 @@ namespace SourceGit.ViewModels
var needPopStash = false;
if (changes > 0)
{
- if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
+ if (DiscardLocalChanges)
+ {
+ SetProgressDescription("Discard local changes ...");
+ Commands.Discard.All(_repo.FullPath, false);
+ }
+ else
{
SetProgressDescription("Stash local changes...");
var succ = new Commands.Stash(_repo.FullPath).Push("PULL_AUTO_STASH");
@@ -136,11 +141,6 @@ namespace SourceGit.ViewModels
needPopStash = true;
}
- else if (PreAction == Models.DealWithLocalChanges.Discard)
- {
- SetProgressDescription("Discard local changes ...");
- Commands.Discard.All(_repo.FullPath, false);
- }
}
bool rs;
diff --git a/src/ViewModels/PushTag.cs b/src/ViewModels/PushTag.cs
index 54673fbe..de2941d2 100644
--- a/src/ViewModels/PushTag.cs
+++ b/src/ViewModels/PushTag.cs
@@ -43,13 +43,14 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
- bool succ = true;
+ var succ = true;
+ var tag = $"refs/tags/{Target.Name}";
if (_pushAllRemotes)
{
foreach (var remote in _repo.Remotes)
{
SetProgressDescription($"Pushing tag to remote {remote.Name} ...");
- succ = new Commands.Push(_repo.FullPath, remote.Name, Target.Name, false).Exec();
+ succ = new Commands.Push(_repo.FullPath, remote.Name, tag, false).Exec();
if (!succ)
break;
}
@@ -57,7 +58,7 @@ namespace SourceGit.ViewModels
else
{
SetProgressDescription($"Pushing tag to remote {SelectedRemote.Name} ...");
- succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, Target.Name, false).Exec();
+ succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, tag, false).Exec();
}
CallUIThread(() => _repo.SetWatcherEnabled(true));
diff --git a/src/ViewModels/RenameBranch.cs b/src/ViewModels/RenameBranch.cs
index bd0b4664..0679a5b5 100644
--- a/src/ViewModels/RenameBranch.cs
+++ b/src/ViewModels/RenameBranch.cs
@@ -12,7 +12,7 @@ namespace SourceGit.ViewModels
}
[Required(ErrorMessage = "Branch name is required!!!")]
- [RegularExpression(@"^[\w\-/\.#]+$", ErrorMessage = "Bad branch name format!")]
+ [RegularExpression(@"^[\w \-/\.#]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(RenameBranch), nameof(ValidateBranchName))]
public string Name
{
@@ -32,9 +32,10 @@ namespace SourceGit.ViewModels
{
if (ctx.ObjectInstance is RenameBranch rename)
{
+ var fixedName = rename.FixName(name);
foreach (var b in rename._repo.Branches)
{
- if (b.IsLocal && b != rename.Target && b.Name == name)
+ if (b.IsLocal && b != rename.Target && b.Name == fixedName)
{
return new ValidationResult("A branch with same name already exists!!!");
}
@@ -46,7 +47,8 @@ namespace SourceGit.ViewModels
public override Task Sure()
{
- if (_name == Target.Name)
+ var fixedName = FixName(_name);
+ if (fixedName == Target.Name)
return null;
_repo.SetWatcherEnabled(false);
@@ -55,7 +57,7 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
var oldName = Target.FullName;
- var succ = Commands.Branch.Rename(_repo.FullPath, Target.Name, _name);
+ var succ = Commands.Branch.Rename(_repo.FullPath, Target.Name, fixedName);
CallUIThread(() =>
{
if (succ)
@@ -65,7 +67,7 @@ namespace SourceGit.ViewModels
if (filter.Type == Models.FilterType.LocalBranch &&
filter.Pattern.Equals(oldName, StringComparison.Ordinal))
{
- filter.Pattern = $"refs/heads/{_name}";
+ filter.Pattern = $"refs/heads/{fixedName}";
break;
}
}
@@ -78,6 +80,15 @@ namespace SourceGit.ViewModels
});
}
+ private string FixName(string name)
+ {
+ if (!name.Contains(' '))
+ return name;
+
+ var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
+ return string.Join("-", parts);
+ }
+
private readonly Repository _repo;
private string _name;
}
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index 2294fdde..97c52d8e 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -6,7 +6,6 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
@@ -269,16 +268,18 @@ namespace SourceGit.ViewModels
{
if (SetProperty(ref _isSearching, value))
{
- SearchedCommits = new List();
- SearchCommitFilter = string.Empty;
- SearchCommitFilterSuggestion.Clear();
- IsSearchCommitSuggestionOpen = false;
- _revisionFiles.Clear();
-
if (value)
{
SelectedViewIndex = 0;
- UpdateCurrentRevisionFilesForSearchSuggestion();
+ CalcWorktreeFilesForSearching();
+ }
+ else
+ {
+ SearchedCommits = new List();
+ SelectedSearchedCommit = null;
+ SearchCommitFilter = string.Empty;
+ MatchedFilesForSearching = null;
+ _worktreeFiles = null;
}
}
}
@@ -307,8 +308,7 @@ namespace SourceGit.ViewModels
{
if (SetProperty(ref _searchCommitFilterType, value))
{
- UpdateCurrentRevisionFilesForSearchSuggestion();
-
+ CalcWorktreeFilesForSearching();
if (!string.IsNullOrEmpty(_searchCommitFilter))
StartSearchCommits();
}
@@ -320,53 +320,33 @@ namespace SourceGit.ViewModels
get => _searchCommitFilter;
set
{
- if (SetProperty(ref _searchCommitFilter, value) &&
- _searchCommitFilterType == 3 &&
- !string.IsNullOrEmpty(value) &&
- value.Length >= 2 &&
- _revisionFiles.Count > 0)
- {
- var suggestion = new List();
- foreach (var file in _revisionFiles)
- {
- if (file.Contains(value, StringComparison.OrdinalIgnoreCase) && file.Length != value.Length)
- {
- suggestion.Add(file);
- if (suggestion.Count > 100)
- break;
- }
- }
-
- SearchCommitFilterSuggestion.Clear();
- SearchCommitFilterSuggestion.AddRange(suggestion);
- IsSearchCommitSuggestionOpen = SearchCommitFilterSuggestion.Count > 0;
- }
- else if (SearchCommitFilterSuggestion.Count > 0)
- {
- SearchCommitFilterSuggestion.Clear();
- IsSearchCommitSuggestionOpen = false;
- }
+ if (SetProperty(ref _searchCommitFilter, value) && IsSearchingCommitsByFilePath())
+ CalcMatchedFilesForSearching();
}
}
- public bool IsSearchCommitSuggestionOpen
+ public List MatchedFilesForSearching
{
- get => _isSearchCommitSuggestionOpen;
- set => SetProperty(ref _isSearchCommitSuggestionOpen, value);
+ get => _matchedFilesForSearching;
+ private set => SetProperty(ref _matchedFilesForSearching, value);
}
- public AvaloniaList SearchCommitFilterSuggestion
- {
- get;
- private set;
- } = new AvaloniaList();
-
public List SearchedCommits
{
get => _searchedCommits;
set => SetProperty(ref _searchedCommits, value);
}
+ public Models.Commit SelectedSearchedCommit
+ {
+ get => _selectedSearchedCommit;
+ set
+ {
+ if (SetProperty(ref _selectedSearchedCommit, value) && value != null)
+ NavigateToCommit(value.SHA);
+ }
+ }
+
public bool IsLocalBranchGroupExpanded
{
get => _settings.IsLocalBranchesExpandedInSideBar;
@@ -437,16 +417,6 @@ namespace SourceGit.ViewModels
get => _workingCopy?.InProgressContext;
}
- public Models.Commit SearchResultSelectedCommit
- {
- get => _searchResultSelectedCommit;
- set
- {
- if (SetProperty(ref _searchResultSelectedCommit, value) && value != null)
- NavigateToCommit(value.SHA);
- }
- }
-
public bool IsAutoFetching
{
get => _isAutoFetching;
@@ -518,7 +488,7 @@ namespace SourceGit.ViewModels
{
File.WriteAllText(Path.Combine(_gitDir, "sourcegit.settings"), settingsSerialized);
}
- catch (DirectoryNotFoundException)
+ catch
{
// Ignore
}
@@ -550,9 +520,10 @@ namespace SourceGit.ViewModels
_submodules.Clear();
_visibleSubmodules.Clear();
_searchedCommits.Clear();
+ _selectedSearchedCommit = null;
- _revisionFiles.Clear();
- SearchCommitFilterSuggestion.Clear();
+ _worktreeFiles = null;
+ _matchedFilesForSearching = null;
}
public bool CanCreatePopup()
@@ -723,39 +694,33 @@ namespace SourceGit.ViewModels
SearchCommitFilter = string.Empty;
}
+ public void ClearMatchedFilesForSearching()
+ {
+ MatchedFilesForSearching = null;
+ }
+
public void StartSearchCommits()
{
if (_histories == null)
return;
IsSearchLoadingVisible = true;
- SearchResultSelectedCommit = null;
- IsSearchCommitSuggestionOpen = false;
- SearchCommitFilterSuggestion.Clear();
+ SelectedSearchedCommit = null;
+ MatchedFilesForSearching = null;
Task.Run(() =>
{
- var visible = new List();
+ var visible = null as List;
+ var method = (Models.CommitSearchMethod)_searchCommitFilterType;
- switch (_searchCommitFilterType)
+ if (method == Models.CommitSearchMethod.BySHA)
{
- case 0:
- var commit = new Commands.QuerySingleCommit(_fullpath, _searchCommitFilter).Result();
- if (commit != null)
- visible.Add(commit);
- break;
- case 1:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByAuthor, _onlySearchCommitsInCurrentBranch).Result();
- break;
- case 2:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByCommitter, _onlySearchCommitsInCurrentBranch).Result();
- break;
- case 3:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByMessage, _onlySearchCommitsInCurrentBranch).Result();
- break;
- case 4:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByFile, _onlySearchCommitsInCurrentBranch).Result();
- break;
+ var commit = new Commands.QuerySingleCommit(_fullpath, _searchCommitFilter).Result();
+ visible = commit == null ? [] : [commit];
+ }
+ else
+ {
+ visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, method, _onlySearchCommitsInCurrentBranch).Result();
}
Dispatcher.UIThread.Invoke(() =>
@@ -943,6 +908,25 @@ namespace SourceGit.ViewModels
_workingCopy?.AbortMerge();
}
+ public List GetCustomActions(Models.CustomActionScope scope)
+ {
+ var actions = new List();
+
+ foreach (var act in Preferences.Instance.CustomActions)
+ {
+ if (act.Scope == scope)
+ actions.Add(act);
+ }
+
+ foreach (var act in _settings.CustomActions)
+ {
+ if (act.Scope == scope)
+ actions.Add(act);
+ }
+
+ return actions;
+ }
+
public void RefreshBranches()
{
var branches = new Commands.QueryBranches(_fullpath).Result();
@@ -1224,23 +1208,26 @@ namespace SourceGit.ViewModels
App.GetLauncer()?.OpenRepositoryInTab(node, null);
}
- public AvaloniaList GetPreferedOpenAIServices()
+ public List GetPreferedOpenAIServices()
{
var services = Preferences.Instance.OpenAIServices;
if (services == null || services.Count == 0)
return [];
if (services.Count == 1)
- return services;
+ return [services[0]];
var prefered = _settings.PreferedOpenAIService;
+ var all = new List();
foreach (var service in services)
{
if (service.Name.Equals(prefered, StringComparison.Ordinal))
return [service];
+
+ all.Add(service);
}
- return services;
+ return all;
}
public ContextMenu CreateContextMenuForGitFlow()
@@ -1443,16 +1430,10 @@ namespace SourceGit.ViewModels
public ContextMenu CreateContextMenuForCustomAction()
{
- var actions = new List();
- foreach (var action in _settings.CustomActions)
- {
- if (action.Scope == Models.CustomActionScope.Repository)
- actions.Add(action);
- }
-
var menu = new ContextMenu();
menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
+ var actions = GetCustomActions(Models.CustomActionScope.Repository);
if (actions.Count > 0)
{
foreach (var action in actions)
@@ -1643,7 +1624,7 @@ namespace SourceGit.ViewModels
compareWithWorktree.Icon = App.CreateMenuIcon("Icons.Compare");
compareWithWorktree.Click += (_, _) =>
{
- SearchResultSelectedCommit = null;
+ SelectedSearchedCommit = null;
if (_histories != null)
{
@@ -1925,7 +1906,7 @@ namespace SourceGit.ViewModels
compareWithWorktree.Icon = App.CreateMenuIcon("Icons.Compare");
compareWithWorktree.Click += (_, _) =>
{
- SearchResultSelectedCommit = null;
+ SelectedSearchedCommit = null;
if (_histories != null)
{
@@ -2349,13 +2330,7 @@ namespace SourceGit.ViewModels
private void TryToAddCustomActionsToBranchContextMenu(ContextMenu menu, Models.Branch branch)
{
- var actions = new List();
- foreach (var action in Settings.CustomActions)
- {
- if (action.Scope == Models.CustomActionScope.Branch)
- actions.Add(action);
- }
-
+ var actions = GetCustomActions(Models.CustomActionScope.Branch);
if (actions.Count == 0)
return;
@@ -2384,42 +2359,52 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" });
}
- private void UpdateCurrentRevisionFilesForSearchSuggestion()
+ private bool IsSearchingCommitsByFilePath()
{
- _revisionFiles.Clear();
+ return _isSearching && _searchCommitFilterType == (int)Models.CommitSearchMethod.ByFile;
+ }
- if (_searchCommitFilterType == 3)
+ private void CalcWorktreeFilesForSearching()
+ {
+ if (!IsSearchingCommitsByFilePath())
{
- Task.Run(() =>
- {
- var files = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result();
- Dispatcher.UIThread.Invoke(() =>
- {
- if (_searchCommitFilterType != 3)
- return;
-
- _revisionFiles.AddRange(files);
-
- if (!string.IsNullOrEmpty(_searchCommitFilter) && _searchCommitFilter.Length > 2 && _revisionFiles.Count > 0)
- {
- var suggestion = new List();
- foreach (var file in _revisionFiles)
- {
- if (file.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) && file.Length != _searchCommitFilter.Length)
- {
- suggestion.Add(file);
- if (suggestion.Count > 100)
- break;
- }
- }
-
- SearchCommitFilterSuggestion.Clear();
- SearchCommitFilterSuggestion.AddRange(suggestion);
- IsSearchCommitSuggestionOpen = SearchCommitFilterSuggestion.Count > 0;
- }
- });
- });
+ _worktreeFiles = null;
+ MatchedFilesForSearching = null;
+ GC.Collect();
+ return;
}
+
+ Task.Run(() =>
+ {
+ _worktreeFiles = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result();
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ if (IsSearchingCommitsByFilePath())
+ CalcMatchedFilesForSearching();
+ });
+ });
+ }
+
+ private void CalcMatchedFilesForSearching()
+ {
+ if (_worktreeFiles == null || _worktreeFiles.Count == 0 || _searchCommitFilter.Length < 3)
+ {
+ MatchedFilesForSearching = null;
+ return;
+ }
+
+ var matched = new List();
+ foreach (var file in _worktreeFiles)
+ {
+ if (file.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) && file.Length != _searchCommitFilter.Length)
+ {
+ matched.Add(file);
+ if (matched.Count > 100)
+ break;
+ }
+ }
+
+ MatchedFilesForSearching = matched;
}
private void AutoFetchImpl(object sender)
@@ -2468,13 +2453,13 @@ namespace SourceGit.ViewModels
private bool _isSearching = false;
private bool _isSearchLoadingVisible = false;
- private bool _isSearchCommitSuggestionOpen = false;
- private int _searchCommitFilterType = 3;
+ private int _searchCommitFilterType = (int)Models.CommitSearchMethod.ByMessage;
private bool _onlySearchCommitsInCurrentBranch = false;
private string _searchCommitFilter = string.Empty;
private List _searchedCommits = new List();
- private Models.Commit _searchResultSelectedCommit = null;
- private List _revisionFiles = new List();
+ private Models.Commit _selectedSearchedCommit = null;
+ private List _worktreeFiles = null;
+ private List _matchedFilesForSearching = null;
private string _filter = string.Empty;
private object _lockRemotes = new object();
diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs
index 35db11b9..f9ddb288 100644
--- a/src/ViewModels/WorkingCopy.cs
+++ b/src/ViewModels/WorkingCopy.cs
@@ -1452,28 +1452,24 @@ namespace SourceGit.ViewModels
App.OpenDialog(dialog);
return null;
}
- else
+
+ var menu = new ContextMenu() { Placement = PlacementMode.TopEdgeAlignedLeft };
+ foreach (var service in services)
{
- var menu = new ContextMenu() { Placement = PlacementMode.TopEdgeAlignedLeft };
-
- foreach (var service in services)
+ var dup = service;
+ var item = new MenuItem();
+ item.Header = service.Name;
+ item.Click += (_, e) =>
{
- var dup = service;
+ var dialog = new Views.AIAssistant(dup, _repo.FullPath, this, _staged);
+ App.OpenDialog(dialog);
+ e.Handled = true;
+ };
- var item = new MenuItem();
- item.Header = service.Name;
- item.Click += (_, e) =>
- {
- var dialog = new Views.AIAssistant(dup, _repo.FullPath, this, _staged);
- App.OpenDialog(dialog);
- e.Handled = true;
- };
-
- menu.Items.Add(item);
- }
-
- return menu;
+ menu.Items.Add(item);
}
+
+ return menu;
}
private List GetVisibleUnstagedChanges(List unstaged)
diff --git a/src/Views/ChangeCollectionView.axaml b/src/Views/ChangeCollectionView.axaml
index 6ce3d033..2b0f5bfa 100644
--- a/src/Views/ChangeCollectionView.axaml
+++ b/src/Views/ChangeCollectionView.axaml
@@ -39,7 +39,8 @@
+ DoubleTapped="OnRowDoubleTapped"
+ ToolTip.Tip="{Binding FullPath}">
-
+
-
+
-
+
-
-
+
-
-
+ Content="{DynamicResource Text.CreateBranch.LocalChanges.StashAndReply}"
+ IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/>
+
diff --git a/src/Views/Checkout.axaml.cs b/src/Views/Checkout.axaml.cs
index da6e6b31..f8398a1d 100644
--- a/src/Views/Checkout.axaml.cs
+++ b/src/Views/Checkout.axaml.cs
@@ -1,5 +1,4 @@
using Avalonia.Controls;
-using Avalonia.Interactivity;
namespace SourceGit.Views
{
@@ -9,51 +8,5 @@ namespace SourceGit.Views
{
InitializeComponent();
}
-
- protected override void OnLoaded(RoutedEventArgs e)
- {
- base.OnLoaded(e);
-
- var vm = DataContext as ViewModels.Checkout;
- if (vm == null)
- return;
-
- switch (vm.PreAction)
- {
- case Models.DealWithLocalChanges.DoNothing:
- RadioDoNothing.IsChecked = true;
- break;
- case Models.DealWithLocalChanges.StashAndReaply:
- RadioStashAndReply.IsChecked = true;
- break;
- default:
- RadioDiscard.IsChecked = true;
- break;
- }
- }
-
- private void OnLocalChangeActionIsCheckedChanged(object sender, RoutedEventArgs e)
- {
- var vm = DataContext as ViewModels.Checkout;
- if (vm == null)
- return;
-
- if (RadioDoNothing.IsChecked == true)
- {
- if (vm.PreAction != Models.DealWithLocalChanges.DoNothing)
- vm.PreAction = Models.DealWithLocalChanges.DoNothing;
- return;
- }
-
- if (RadioStashAndReply.IsChecked == true)
- {
- if (vm.PreAction != Models.DealWithLocalChanges.StashAndReaply)
- vm.PreAction = Models.DealWithLocalChanges.StashAndReaply;
- return;
- }
-
- if (vm.PreAction != Models.DealWithLocalChanges.Discard)
- vm.PreAction = Models.DealWithLocalChanges.Discard;
- }
}
}
diff --git a/src/Views/CheckoutCommit.axaml b/src/Views/CheckoutCommit.axaml
index 3ee3943f..9b418823 100644
--- a/src/Views/CheckoutCommit.axaml
+++ b/src/Views/CheckoutCommit.axaml
@@ -30,16 +30,16 @@
+ Margin="0,0,8,0"
+ IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/>
+ GroupName="LocalChanges"/>
-
-
-
-
-
+
+
+
+
diff --git a/src/Views/CommitGraph.cs b/src/Views/CommitGraph.cs
new file mode 100644
index 00000000..015eaca5
--- /dev/null
+++ b/src/Views/CommitGraph.cs
@@ -0,0 +1,228 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.VisualTree;
+
+namespace SourceGit.Views
+{
+ public class CommitGraph : Control
+ {
+ public static readonly StyledProperty GraphProperty =
+ AvaloniaProperty.Register(nameof(Graph));
+
+ public Models.CommitGraph Graph
+ {
+ get => GetValue(GraphProperty);
+ set => SetValue(GraphProperty, value);
+ }
+
+ public static readonly StyledProperty DotBrushProperty =
+ AvaloniaProperty.Register(nameof(DotBrush), Brushes.Transparent);
+
+ public IBrush DotBrush
+ {
+ get => GetValue(DotBrushProperty);
+ set => SetValue(DotBrushProperty, value);
+ }
+
+ public static readonly StyledProperty OnlyHighlightCurrentBranchProperty =
+ AvaloniaProperty.Register(nameof(OnlyHighlightCurrentBranch), true);
+
+ public bool OnlyHighlightCurrentBranch
+ {
+ get => GetValue(OnlyHighlightCurrentBranchProperty);
+ set => SetValue(OnlyHighlightCurrentBranchProperty, value);
+ }
+
+ static CommitGraph()
+ {
+ AffectsRender(GraphProperty, DotBrushProperty, OnlyHighlightCurrentBranchProperty);
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ base.Render(context);
+
+ var graph = Graph;
+ if (graph == null)
+ return;
+
+ var histories = this.FindAncestorOfType();
+ if (histories == null)
+ return;
+
+ var list = histories.CommitListContainer;
+ 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;
+
+ // 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);
+ }
+ }
+
+ private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double top, double bottom)
+ {
+ var grayedPen = new Pen(new SolidColorBrush(Colors.Gray, 0.4), Models.CommitGraph.Pens[0].Thickness);
+ var onlyHighlightCurrentBranch = OnlyHighlightCurrentBranch;
+
+ if (onlyHighlightCurrentBranch)
+ {
+ foreach (var link in graph.Links)
+ {
+ if (link.IsMerged)
+ continue;
+ if (link.End.Y < top)
+ continue;
+ if (link.Start.Y > bottom)
+ break;
+
+ var geo = new StreamGeometry();
+ using (var ctx = geo.Open())
+ {
+ ctx.BeginFigure(link.Start, false);
+ ctx.QuadraticBezierTo(link.Control, link.End);
+ }
+
+ context.DrawGeometry(null, grayedPen, geo);
+ }
+ }
+
+ foreach (var line in graph.Paths)
+ {
+ var last = line.Points[0];
+ var size = line.Points.Count;
+
+ if (line.Points[size - 1].Y < top)
+ continue;
+ if (last.Y > bottom)
+ break;
+
+ var geo = new StreamGeometry();
+ var pen = Models.CommitGraph.Pens[line.Color];
+
+ using (var ctx = geo.Open())
+ {
+ var started = false;
+ var ended = false;
+ for (int i = 1; i < size; i++)
+ {
+ var cur = line.Points[i];
+ if (cur.Y < top)
+ {
+ last = cur;
+ continue;
+ }
+
+ if (!started)
+ {
+ ctx.BeginFigure(last, false);
+ started = true;
+ }
+
+ if (cur.Y > bottom)
+ {
+ cur = new Point(cur.X, bottom);
+ ended = true;
+ }
+
+ if (cur.X > last.X)
+ {
+ ctx.QuadraticBezierTo(new Point(cur.X, last.Y), cur);
+ }
+ else if (cur.X < last.X)
+ {
+ if (i < size - 1)
+ {
+ var midY = (last.Y + cur.Y) / 2;
+ ctx.CubicBezierTo(new Point(last.X, midY + 4), new Point(cur.X, midY - 4), cur);
+ }
+ else
+ {
+ ctx.QuadraticBezierTo(new Point(last.X, cur.Y), cur);
+ }
+ }
+ else
+ {
+ ctx.LineTo(cur);
+ }
+
+ if (ended)
+ break;
+ last = cur;
+ }
+ }
+
+ if (!line.IsMerged && onlyHighlightCurrentBranch)
+ context.DrawGeometry(null, grayedPen, geo);
+ else
+ context.DrawGeometry(null, pen, geo);
+ }
+
+ foreach (var link in graph.Links)
+ {
+ if (onlyHighlightCurrentBranch && !link.IsMerged)
+ continue;
+ if (link.End.Y < top)
+ continue;
+ if (link.Start.Y > bottom)
+ break;
+
+ var geo = new StreamGeometry();
+ using (var ctx = geo.Open())
+ {
+ ctx.BeginFigure(link.Start, false);
+ ctx.QuadraticBezierTo(link.Control, link.End);
+ }
+
+ context.DrawGeometry(null, Models.CommitGraph.Pens[link.Color], geo);
+ }
+ }
+
+ private void DrawAnchors(DrawingContext context, Models.CommitGraph graph, double top, double bottom)
+ {
+ var dotFill = DotBrush;
+ var dotFillPen = new Pen(dotFill, 2);
+ var grayedPen = new Pen(Brushes.Gray, Models.CommitGraph.Pens[0].Thickness);
+ var onlyHighlightCurrentBranch = OnlyHighlightCurrentBranch;
+
+ foreach (var dot in graph.Dots)
+ {
+ if (dot.Center.Y < top)
+ continue;
+ if (dot.Center.Y > bottom)
+ break;
+
+ var pen = Models.CommitGraph.Pens[dot.Color];
+ if (!dot.IsMerged && onlyHighlightCurrentBranch)
+ pen = grayedPen;
+
+ 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);
+ 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));
+ break;
+ default:
+ context.DrawEllipse(dotFill, pen, dot.Center, 3, 3);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Views/CommitStatusIndicator.cs b/src/Views/CommitStatusIndicator.cs
new file mode 100644
index 00000000..c2f4184e
--- /dev/null
+++ b/src/Views/CommitStatusIndicator.cs
@@ -0,0 +1,90 @@
+using System;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+
+namespace SourceGit.Views
+{
+ public class CommitStatusIndicator : Control
+ {
+ public static readonly StyledProperty CurrentBranchProperty =
+ AvaloniaProperty.Register(nameof(CurrentBranch));
+
+ public Models.Branch CurrentBranch
+ {
+ get => GetValue(CurrentBranchProperty);
+ set => SetValue(CurrentBranchProperty, value);
+ }
+
+ public static readonly StyledProperty AheadBrushProperty =
+ AvaloniaProperty.Register(nameof(AheadBrush));
+
+ public IBrush AheadBrush
+ {
+ get => GetValue(AheadBrushProperty);
+ set => SetValue(AheadBrushProperty, value);
+ }
+
+ public static readonly StyledProperty BehindBrushProperty =
+ AvaloniaProperty.Register(nameof(BehindBrush));
+
+ public IBrush BehindBrush
+ {
+ get => GetValue(BehindBrushProperty);
+ set => SetValue(BehindBrushProperty, value);
+ }
+
+ enum Status
+ {
+ Normal,
+ Ahead,
+ Behind,
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ if (_status == Status.Normal)
+ return;
+
+ context.DrawEllipse(_status == Status.Ahead ? AheadBrush : BehindBrush, null, new Rect(0, 0, 5, 5));
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ if (DataContext is Models.Commit commit && CurrentBranch is not null)
+ {
+ var sha = commit.SHA;
+ var track = CurrentBranch.TrackStatus;
+
+ if (track.Ahead.Contains(sha))
+ _status = Status.Ahead;
+ else if (track.Behind.Contains(sha))
+ _status = Status.Behind;
+ else
+ _status = Status.Normal;
+ }
+ else
+ {
+ _status = Status.Normal;
+ }
+
+ return _status == Status.Normal ? new Size(0, 0) : new Size(9, 5);
+ }
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+ InvalidateMeasure();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ if (change.Property == CurrentBranchProperty)
+ InvalidateMeasure();
+ }
+
+ private Status _status = Status.Normal;
+ }
+}
diff --git a/src/Views/CommitSubjectPresenter.cs b/src/Views/CommitSubjectPresenter.cs
new file mode 100644
index 00000000..32f6838d
--- /dev/null
+++ b/src/Views/CommitSubjectPresenter.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using Avalonia;
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Controls.Documents;
+using Avalonia.Input;
+using Avalonia.Media;
+using Avalonia.Media.TextFormatting;
+
+namespace SourceGit.Views
+{
+ public partial class CommitSubjectPresenter : TextBlock
+ {
+ public static readonly StyledProperty SubjectProperty =
+ AvaloniaProperty.Register(nameof(Subject));
+
+ public string Subject
+ {
+ get => GetValue(SubjectProperty);
+ set => SetValue(SubjectProperty, value);
+ }
+
+ public static readonly StyledProperty> IssueTrackerRulesProperty =
+ AvaloniaProperty.Register>(nameof(IssueTrackerRules));
+
+ public AvaloniaList IssueTrackerRules
+ {
+ get => GetValue(IssueTrackerRulesProperty);
+ set => SetValue(IssueTrackerRulesProperty, value);
+ }
+
+ protected override Type StyleKeyOverride => typeof(TextBlock);
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == SubjectProperty || change.Property == IssueTrackerRulesProperty)
+ {
+ Inlines!.Clear();
+ _matches = null;
+ ClearHoveredIssueLink();
+
+ var subject = Subject;
+ if (string.IsNullOrEmpty(subject))
+ return;
+
+ var keywordMatch = REG_KEYWORD_FORMAT1().Match(subject);
+ if (!keywordMatch.Success)
+ keywordMatch = REG_KEYWORD_FORMAT2().Match(subject);
+
+ var rules = IssueTrackerRules ?? [];
+ var matches = new List();
+ foreach (var rule in rules)
+ rule.Matches(matches, subject);
+
+ if (matches.Count == 0)
+ {
+ if (keywordMatch.Success)
+ {
+ Inlines.Add(new Run(subject.Substring(0, keywordMatch.Length)) { FontWeight = FontWeight.Bold });
+ Inlines.Add(new Run(subject.Substring(keywordMatch.Length)));
+ }
+ else
+ {
+ Inlines.Add(new Run(subject));
+ }
+ return;
+ }
+
+ matches.Sort((l, r) => l.Start - r.Start);
+ _matches = matches;
+
+ var inlines = new List();
+ var pos = 0;
+ foreach (var match in matches)
+ {
+ if (match.Start > pos)
+ {
+ if (keywordMatch.Success && pos < keywordMatch.Length)
+ {
+ if (keywordMatch.Length < match.Start)
+ {
+ inlines.Add(new Run(subject.Substring(pos, keywordMatch.Length - pos)) { FontWeight = FontWeight.Bold });
+ inlines.Add(new Run(subject.Substring(keywordMatch.Length, match.Start - keywordMatch.Length)));
+ }
+ else
+ {
+ inlines.Add(new Run(subject.Substring(pos, match.Start - pos)) { FontWeight = FontWeight.Bold });
+ }
+ }
+ else
+ {
+ inlines.Add(new Run(subject.Substring(pos, match.Start - pos)));
+ }
+ }
+
+ var link = new Run(subject.Substring(match.Start, match.Length));
+ link.Classes.Add("issue_link");
+ inlines.Add(link);
+
+ pos = match.Start + match.Length;
+ }
+
+ if (pos < subject.Length)
+ {
+ if (keywordMatch.Success && pos < keywordMatch.Length)
+ {
+ inlines.Add(new Run(subject.Substring(pos, keywordMatch.Length - pos)) { FontWeight = FontWeight.Bold });
+ inlines.Add(new Run(subject.Substring(keywordMatch.Length)));
+ }
+ else
+ {
+ inlines.Add(new Run(subject.Substring(pos)));
+ }
+ }
+
+ Inlines.AddRange(inlines);
+ }
+ }
+
+ protected override void OnPointerMoved(PointerEventArgs e)
+ {
+ base.OnPointerMoved(e);
+
+ if (_matches != null)
+ {
+ var point = e.GetPosition(this) - new Point(Padding.Left, Padding.Top);
+ var x = Math.Min(Math.Max(point.X, 0), Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0));
+ var y = Math.Min(Math.Max(point.Y, 0), Math.Max(TextLayout.Height, 0));
+ point = new Point(x, y);
+
+ var textPosition = TextLayout.HitTestPoint(point).TextPosition;
+ foreach (var match in _matches)
+ {
+ if (!match.Intersect(textPosition, 1))
+ continue;
+
+ if (match == _lastHover)
+ return;
+
+ _lastHover = match;
+ SetCurrentValue(CursorProperty, Cursor.Parse("Hand"));
+ ToolTip.SetTip(this, match.Link);
+ ToolTip.SetIsOpen(this, true);
+ e.Handled = true;
+ return;
+ }
+
+ ClearHoveredIssueLink();
+ }
+ }
+
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
+ {
+ base.OnPointerPressed(e);
+
+ if (_lastHover != null)
+ Native.OS.OpenBrowser(_lastHover.Link);
+ }
+
+ protected override void OnPointerExited(PointerEventArgs e)
+ {
+ base.OnPointerExited(e);
+ ClearHoveredIssueLink();
+ }
+
+ private void ClearHoveredIssueLink()
+ {
+ if (_lastHover != null)
+ {
+ ToolTip.SetTip(this, null);
+ SetCurrentValue(CursorProperty, Cursor.Parse("Arrow"));
+ _lastHover = null;
+ }
+ }
+
+ [GeneratedRegex(@"^\[[\w\s]+\]")]
+ private static partial Regex REG_KEYWORD_FORMAT1();
+
+ [GeneratedRegex(@"^\S+([\<\(][\w\s_\-\*,]+[\>\)])?\!?\s?:\s")]
+ private static partial Regex REG_KEYWORD_FORMAT2();
+
+ private List _matches = null;
+ private Models.Hyperlink _lastHover = null;
+ }
+}
diff --git a/src/Views/CommitTimeTextBlock.cs b/src/Views/CommitTimeTextBlock.cs
new file mode 100644
index 00000000..db63e8a6
--- /dev/null
+++ b/src/Views/CommitTimeTextBlock.cs
@@ -0,0 +1,166 @@
+using System;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+
+namespace SourceGit.Views
+{
+ public class CommitTimeTextBlock : TextBlock
+ {
+ public static readonly StyledProperty ShowAsDateTimeProperty =
+ AvaloniaProperty.Register(nameof(ShowAsDateTime), true);
+
+ public bool ShowAsDateTime
+ {
+ get => GetValue(ShowAsDateTimeProperty);
+ set => SetValue(ShowAsDateTimeProperty, value);
+ }
+
+ public static readonly StyledProperty DateTimeFormatProperty =
+ AvaloniaProperty.Register(nameof(DateTimeFormat), 0);
+
+ public int DateTimeFormat
+ {
+ get => GetValue(DateTimeFormatProperty);
+ set => SetValue(DateTimeFormatProperty, value);
+ }
+
+ public static readonly StyledProperty UseAuthorTimeProperty =
+ AvaloniaProperty.Register(nameof(UseAuthorTime), true);
+
+ public bool UseAuthorTime
+ {
+ get => GetValue(UseAuthorTimeProperty);
+ set => SetValue(UseAuthorTimeProperty, value);
+ }
+
+ protected override Type StyleKeyOverride => typeof(TextBlock);
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == UseAuthorTimeProperty)
+ {
+ SetCurrentValue(TextProperty, GetDisplayText());
+ }
+ else if (change.Property == ShowAsDateTimeProperty)
+ {
+ SetCurrentValue(TextProperty, GetDisplayText());
+
+ if (ShowAsDateTime)
+ StopTimer();
+ else
+ StartTimer();
+ }
+ else if (change.Property == DateTimeFormatProperty)
+ {
+ if (ShowAsDateTime)
+ SetCurrentValue(TextProperty, GetDisplayText());
+ }
+ }
+
+ protected override void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+
+ if (!ShowAsDateTime)
+ StartTimer();
+ }
+
+ protected override void OnUnloaded(RoutedEventArgs e)
+ {
+ base.OnUnloaded(e);
+ StopTimer();
+ }
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+ SetCurrentValue(TextProperty, GetDisplayText());
+ }
+
+ private void StartTimer()
+ {
+ if (_refreshTimer != null)
+ return;
+
+ _refreshTimer = DispatcherTimer.Run(() =>
+ {
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ var text = GetDisplayText();
+ if (!text.Equals(Text, StringComparison.Ordinal))
+ Text = text;
+ });
+
+ return true;
+ }, TimeSpan.FromSeconds(10));
+ }
+
+ private void StopTimer()
+ {
+ if (_refreshTimer != null)
+ {
+ _refreshTimer.Dispose();
+ _refreshTimer = null;
+ }
+ }
+
+ private string GetDisplayText()
+ {
+ var commit = DataContext as Models.Commit;
+ if (commit == null)
+ return string.Empty;
+
+ if (ShowAsDateTime)
+ return UseAuthorTime ? commit.AuthorTimeStr : commit.CommitterTimeStr;
+
+ var timestamp = UseAuthorTime ? commit.AuthorTime : commit.CommitterTime;
+ var now = DateTime.Now;
+ var localTime = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime();
+ var span = now - localTime;
+ if (span.TotalMinutes < 1)
+ return App.Text("Period.JustNow");
+
+ if (span.TotalHours < 1)
+ return App.Text("Period.MinutesAgo", (int)span.TotalMinutes);
+
+ if (span.TotalDays < 1)
+ {
+ var hours = (int)span.TotalHours;
+ return hours == 1 ? App.Text("Period.HourAgo") : App.Text("Period.HoursAgo", hours);
+ }
+
+ var lastDay = now.AddDays(-1).Date;
+ if (localTime >= lastDay)
+ return App.Text("Period.Yesterday");
+
+ if ((localTime.Year == now.Year && localTime.Month == now.Month) || span.TotalDays < 28)
+ {
+ var diffDay = now.Date - localTime.Date;
+ return App.Text("Period.DaysAgo", (int)diffDay.TotalDays);
+ }
+
+ var lastMonth = now.AddMonths(-1).Date;
+ if (localTime.Year == lastMonth.Year && localTime.Month == lastMonth.Month)
+ return App.Text("Period.LastMonth");
+
+ if (localTime.Year == now.Year || localTime > now.AddMonths(-11))
+ {
+ var diffMonth = (12 + now.Month - localTime.Month) % 12;
+ return App.Text("Period.MonthsAgo", diffMonth);
+ }
+
+ var diffYear = now.Year - localTime.Year;
+ if (diffYear == 1)
+ return App.Text("Period.LastYear");
+
+ return App.Text("Period.YearsAgo", diffYear);
+ }
+
+ private IDisposable _refreshTimer = null;
+ }
+}
diff --git a/src/Views/ConfigureWorkspace.axaml.cs b/src/Views/ConfigureWorkspace.axaml.cs
index 012c2e85..9e458f6f 100644
--- a/src/Views/ConfigureWorkspace.axaml.cs
+++ b/src/Views/ConfigureWorkspace.axaml.cs
@@ -11,8 +11,10 @@ namespace SourceGit.Views
protected override void OnClosing(WindowClosingEventArgs e)
{
- ViewModels.Preferences.Instance.Save();
base.OnClosing(e);
+
+ if (!Design.IsDesignMode)
+ ViewModels.Preferences.Instance.Save();
}
}
}
diff --git a/src/Views/Conflict.axaml b/src/Views/Conflict.axaml
new file mode 100644
index 00000000..9a056f9e
--- /dev/null
+++ b/src/Views/Conflict.axaml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/Conflict.axaml.cs b/src/Views/Conflict.axaml.cs
new file mode 100644
index 00000000..6121b5c8
--- /dev/null
+++ b/src/Views/Conflict.axaml.cs
@@ -0,0 +1,23 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.VisualTree;
+
+namespace SourceGit.Views
+{
+ public partial class Conflict : UserControl
+ {
+ public Conflict()
+ {
+ InitializeComponent();
+ }
+
+ private void OnPressedSHA(object sender, PointerPressedEventArgs e)
+ {
+ var repoView = this.FindAncestorOfType();
+ if (repoView is { DataContext: ViewModels.Repository repo } && sender is TextBlock text)
+ repo.NavigateToCommit(text.Text);
+
+ e.Handled = true;
+ }
+ }
+}
diff --git a/src/Views/CreateBranch.axaml b/src/Views/CreateBranch.axaml
index ec56ff20..a5f1f212 100644
--- a/src/Views/CreateBranch.axaml
+++ b/src/Views/CreateBranch.axaml
@@ -14,15 +14,7 @@
-
-
-
-
-
-
-
-
-
+
-
-
-
+ Content="{DynamicResource Text.CreateBranch.LocalChanges.StashAndReply}"
+ IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/>
+
-
+
-
+
diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml
index 40b2636f..afe2c1b7 100644
--- a/src/Views/Histories.axaml
+++ b/src/Views/Histories.axaml
@@ -10,18 +10,18 @@
x:Class="SourceGit.Views.Histories"
x:DataType="vm:Histories"
x:Name="ThisControl">
-
-
+
+
-
+
-
+
-
+
@@ -126,7 +126,11 @@
-
+
-
+
-
+
diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs
index 8b5142ad..f7055dfa 100644
--- a/src/Views/Histories.axaml.cs
+++ b/src/Views/Histories.axaml.cs
@@ -1,24 +1,18 @@
using System;
-using System.Collections.Generic;
using System.Text;
-using System.Text.RegularExpressions;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
-using Avalonia.Controls.Documents;
using Avalonia.Input;
-using Avalonia.Interactivity;
-using Avalonia.Media;
-using Avalonia.Threading;
using Avalonia.VisualTree;
namespace SourceGit.Views
{
- public class LayoutableGrid : Grid
+ public class HistoriesLayout : Grid
{
public static readonly StyledProperty UseHorizontalProperty =
- AvaloniaProperty.Register(nameof(UseHorizontal));
+ AvaloniaProperty.Register(nameof(UseHorizontal));
public bool UseHorizontal
{
@@ -28,15 +22,17 @@ namespace SourceGit.Views
protected override Type StyleKeyOverride => typeof(Grid);
- static LayoutableGrid()
+ public HistoriesLayout()
{
- UseHorizontalProperty.Changed.AddClassHandler((o, _) => o.RefreshLayout());
+ RefreshLayout();
}
- public override void ApplyTemplate()
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
- base.ApplyTemplate();
- RefreshLayout();
+ base.OnPropertyChanged(change);
+
+ if (change.Property == UseHorizontalProperty)
+ RefreshLayout();
}
private void RefreshLayout()
@@ -74,639 +70,6 @@ namespace SourceGit.Views
}
}
- public class CommitStatusIndicator : Control
- {
- public static readonly StyledProperty CurrentBranchProperty =
- AvaloniaProperty.Register(nameof(CurrentBranch));
-
- public Models.Branch CurrentBranch
- {
- get => GetValue(CurrentBranchProperty);
- set => SetValue(CurrentBranchProperty, value);
- }
-
- public static readonly StyledProperty AheadBrushProperty =
- AvaloniaProperty.Register(nameof(AheadBrush));
-
- public IBrush AheadBrush
- {
- get => GetValue(AheadBrushProperty);
- set => SetValue(AheadBrushProperty, value);
- }
-
- public static readonly StyledProperty BehindBrushProperty =
- AvaloniaProperty.Register(nameof(BehindBrush));
-
- public IBrush BehindBrush
- {
- get => GetValue(BehindBrushProperty);
- set => SetValue(BehindBrushProperty, value);
- }
-
- enum Status
- {
- Normal,
- Ahead,
- Behind,
- }
-
- public override void Render(DrawingContext context)
- {
- if (_status == Status.Normal)
- return;
-
- context.DrawEllipse(_status == Status.Ahead ? AheadBrush : BehindBrush, null, new Rect(0, 0, 5, 5));
- }
-
- protected override Size MeasureOverride(Size availableSize)
- {
- if (DataContext is Models.Commit commit && CurrentBranch is not null)
- {
- var sha = commit.SHA;
- var track = CurrentBranch.TrackStatus;
-
- if (track.Ahead.Contains(sha))
- _status = Status.Ahead;
- else if (track.Behind.Contains(sha))
- _status = Status.Behind;
- else
- _status = Status.Normal;
- }
- else
- {
- _status = Status.Normal;
- }
-
- return _status == Status.Normal ? new Size(0, 0) : new Size(9, 5);
- }
-
- protected override void OnDataContextChanged(EventArgs e)
- {
- base.OnDataContextChanged(e);
- InvalidateMeasure();
- }
-
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
- {
- base.OnPropertyChanged(change);
- if (change.Property == CurrentBranchProperty)
- InvalidateMeasure();
- }
-
- private Status _status = Status.Normal;
- }
-
- public partial class CommitSubjectPresenter : TextBlock
- {
- public static readonly StyledProperty SubjectProperty =
- AvaloniaProperty.Register(nameof(Subject));
-
- public string Subject
- {
- get => GetValue(SubjectProperty);
- set => SetValue(SubjectProperty, value);
- }
-
- public static readonly StyledProperty> IssueTrackerRulesProperty =
- AvaloniaProperty.Register>(nameof(IssueTrackerRules));
-
- public AvaloniaList IssueTrackerRules
- {
- get => GetValue(IssueTrackerRulesProperty);
- set => SetValue(IssueTrackerRulesProperty, value);
- }
-
- protected override Type StyleKeyOverride => typeof(TextBlock);
-
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
- {
- base.OnPropertyChanged(change);
-
- if (change.Property == SubjectProperty || change.Property == IssueTrackerRulesProperty)
- {
- Inlines!.Clear();
- _matches = null;
- ClearHoveredIssueLink();
-
- var subject = Subject;
- if (string.IsNullOrEmpty(subject))
- return;
-
- var keywordMatch = REG_KEYWORD_FORMAT1().Match(subject);
- if (!keywordMatch.Success)
- keywordMatch = REG_KEYWORD_FORMAT2().Match(subject);
-
- var rules = IssueTrackerRules ?? [];
- var matches = new List();
- foreach (var rule in rules)
- rule.Matches(matches, subject);
-
- if (matches.Count == 0)
- {
- if (keywordMatch.Success)
- {
- Inlines.Add(new Run(subject.Substring(0, keywordMatch.Length)) { FontWeight = FontWeight.Bold });
- Inlines.Add(new Run(subject.Substring(keywordMatch.Length)));
- }
- else
- {
- Inlines.Add(new Run(subject));
- }
- return;
- }
-
- matches.Sort((l, r) => l.Start - r.Start);
- _matches = matches;
-
- var inlines = new List();
- var pos = 0;
- foreach (var match in matches)
- {
- if (match.Start > pos)
- {
- if (keywordMatch.Success && pos < keywordMatch.Length)
- {
- if (keywordMatch.Length < match.Start)
- {
- inlines.Add(new Run(subject.Substring(pos, keywordMatch.Length - pos)) { FontWeight = FontWeight.Bold });
- inlines.Add(new Run(subject.Substring(keywordMatch.Length, match.Start - keywordMatch.Length)));
- }
- else
- {
- inlines.Add(new Run(subject.Substring(pos, match.Start - pos)) { FontWeight = FontWeight.Bold });
- }
- }
- else
- {
- inlines.Add(new Run(subject.Substring(pos, match.Start - pos)));
- }
- }
-
- var link = new Run(subject.Substring(match.Start, match.Length));
- link.Classes.Add("issue_link");
- inlines.Add(link);
-
- pos = match.Start + match.Length;
- }
-
- if (pos < subject.Length)
- {
- if (keywordMatch.Success && pos < keywordMatch.Length)
- {
- inlines.Add(new Run(subject.Substring(pos, keywordMatch.Length - pos)) { FontWeight = FontWeight.Bold });
- inlines.Add(new Run(subject.Substring(keywordMatch.Length)));
- }
- else
- {
- inlines.Add(new Run(subject.Substring(pos)));
- }
- }
-
- Inlines.AddRange(inlines);
- }
- }
-
- protected override void OnPointerMoved(PointerEventArgs e)
- {
- base.OnPointerMoved(e);
-
- if (_matches != null)
- {
- var point = e.GetPosition(this) - new Point(Padding.Left, Padding.Top);
- var x = Math.Min(Math.Max(point.X, 0), Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0));
- var y = Math.Min(Math.Max(point.Y, 0), Math.Max(TextLayout.Height, 0));
- point = new Point(x, y);
-
- var textPosition = TextLayout.HitTestPoint(point).TextPosition;
- foreach (var match in _matches)
- {
- if (!match.Intersect(textPosition, 1))
- continue;
-
- if (match == _lastHover)
- return;
-
- _lastHover = match;
- SetCurrentValue(CursorProperty, Cursor.Parse("Hand"));
- ToolTip.SetTip(this, match.Link);
- ToolTip.SetIsOpen(this, true);
- e.Handled = true;
- return;
- }
-
- ClearHoveredIssueLink();
- }
- }
-
- protected override void OnPointerPressed(PointerPressedEventArgs e)
- {
- base.OnPointerPressed(e);
-
- if (_lastHover != null)
- Native.OS.OpenBrowser(_lastHover.Link);
- }
-
- protected override void OnPointerExited(PointerEventArgs e)
- {
- base.OnPointerExited(e);
- ClearHoveredIssueLink();
- }
-
- private void ClearHoveredIssueLink()
- {
- if (_lastHover != null)
- {
- ToolTip.SetTip(this, null);
- SetCurrentValue(CursorProperty, Cursor.Parse("Arrow"));
- _lastHover = null;
- }
- }
-
- [GeneratedRegex(@"^\[[\w\s]+\]")]
- private static partial Regex REG_KEYWORD_FORMAT1();
-
- [GeneratedRegex(@"^\S+([\<\(][\w\s_\-\*,]+[\>\)])?\!?\s?:\s")]
- private static partial Regex REG_KEYWORD_FORMAT2();
-
- private List _matches = null;
- private Models.Hyperlink _lastHover = null;
- }
-
- public class CommitTimeTextBlock : TextBlock
- {
- public static readonly StyledProperty ShowAsDateTimeProperty =
- AvaloniaProperty.Register(nameof(ShowAsDateTime), true);
-
- public bool ShowAsDateTime
- {
- get => GetValue(ShowAsDateTimeProperty);
- set => SetValue(ShowAsDateTimeProperty, value);
- }
-
- public static readonly StyledProperty DateTimeFormatProperty =
- AvaloniaProperty.Register(nameof(DateTimeFormat), 0);
-
- public int DateTimeFormat
- {
- get => GetValue(DateTimeFormatProperty);
- set => SetValue(DateTimeFormatProperty, value);
- }
-
- public static readonly StyledProperty UseAuthorTimeProperty =
- AvaloniaProperty.Register(nameof(UseAuthorTime), true);
-
- public bool UseAuthorTime
- {
- get => GetValue(UseAuthorTimeProperty);
- set => SetValue(UseAuthorTimeProperty, value);
- }
-
- protected override Type StyleKeyOverride => typeof(TextBlock);
-
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
- {
- base.OnPropertyChanged(change);
-
- if (change.Property == UseAuthorTimeProperty)
- {
- SetCurrentValue(TextProperty, GetDisplayText());
- }
- else if (change.Property == ShowAsDateTimeProperty)
- {
- SetCurrentValue(TextProperty, GetDisplayText());
-
- if (ShowAsDateTime)
- StopTimer();
- else
- StartTimer();
- }
- else if (change.Property == DateTimeFormatProperty)
- {
- if (ShowAsDateTime)
- SetCurrentValue(TextProperty, GetDisplayText());
- }
- }
-
- protected override void OnLoaded(RoutedEventArgs e)
- {
- base.OnLoaded(e);
-
- if (!ShowAsDateTime)
- StartTimer();
- }
-
- protected override void OnUnloaded(RoutedEventArgs e)
- {
- base.OnUnloaded(e);
- StopTimer();
- }
-
- protected override void OnDataContextChanged(EventArgs e)
- {
- base.OnDataContextChanged(e);
- SetCurrentValue(TextProperty, GetDisplayText());
- }
-
- private void StartTimer()
- {
- if (_refreshTimer != null)
- return;
-
- _refreshTimer = DispatcherTimer.Run(() =>
- {
- Dispatcher.UIThread.Invoke(() =>
- {
- var text = GetDisplayText();
- if (!text.Equals(Text, StringComparison.Ordinal))
- Text = text;
- });
-
- return true;
- }, TimeSpan.FromSeconds(10));
- }
-
- private void StopTimer()
- {
- if (_refreshTimer != null)
- {
- _refreshTimer.Dispose();
- _refreshTimer = null;
- }
- }
-
- private string GetDisplayText()
- {
- var commit = DataContext as Models.Commit;
- if (commit == null)
- return string.Empty;
-
- if (ShowAsDateTime)
- return UseAuthorTime ? commit.AuthorTimeStr : commit.CommitterTimeStr;
-
- var timestamp = UseAuthorTime ? commit.AuthorTime : commit.CommitterTime;
- var now = DateTime.Now;
- var localTime = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime();
- var span = now - localTime;
- if (span.TotalMinutes < 1)
- return App.Text("Period.JustNow");
-
- if (span.TotalHours < 1)
- return App.Text("Period.MinutesAgo", (int)span.TotalMinutes);
-
- if (span.TotalDays < 1)
- return App.Text("Period.HoursAgo", (int)span.TotalHours);
-
- var lastDay = now.AddDays(-1).Date;
- if (localTime >= lastDay)
- return App.Text("Period.Yesterday");
-
- if ((localTime.Year == now.Year && localTime.Month == now.Month) || span.TotalDays < 28)
- {
- var diffDay = now.Date - localTime.Date;
- return App.Text("Period.DaysAgo", (int)diffDay.TotalDays);
- }
-
- var lastMonth = now.AddMonths(-1).Date;
- if (localTime.Year == lastMonth.Year && localTime.Month == lastMonth.Month)
- return App.Text("Period.LastMonth");
-
- if (localTime.Year == now.Year || localTime > now.AddMonths(-11))
- {
- var diffMonth = (12 + now.Month - localTime.Month) % 12;
- return App.Text("Period.MonthsAgo", diffMonth);
- }
-
- var diffYear = now.Year - localTime.Year;
- if (diffYear == 1)
- return App.Text("Period.LastYear");
-
- return App.Text("Period.YearsAgo", diffYear);
- }
-
- private IDisposable _refreshTimer = null;
- }
-
- public class CommitGraph : Control
- {
- public static readonly StyledProperty GraphProperty =
- AvaloniaProperty.Register(nameof(Graph));
-
- public Models.CommitGraph Graph
- {
- get => GetValue(GraphProperty);
- set => SetValue(GraphProperty, value);
- }
-
- public static readonly StyledProperty DotBrushProperty =
- AvaloniaProperty.Register(nameof(DotBrush), Brushes.Transparent);
-
- public IBrush DotBrush
- {
- get => GetValue(DotBrushProperty);
- set => SetValue(DotBrushProperty, value);
- }
-
- public static readonly StyledProperty OnlyHighlightCurrentBranchProperty =
- AvaloniaProperty.Register(nameof(OnlyHighlightCurrentBranch), true);
-
- public bool OnlyHighlightCurrentBranch
- {
- get => GetValue(OnlyHighlightCurrentBranchProperty);
- set => SetValue(OnlyHighlightCurrentBranchProperty, value);
- }
-
- static CommitGraph()
- {
- AffectsRender(GraphProperty, DotBrushProperty, OnlyHighlightCurrentBranchProperty);
- }
-
- public override void Render(DrawingContext context)
- {
- base.Render(context);
-
- var graph = Graph;
- if (graph == null)
- return;
-
- var histories = this.FindAncestorOfType();
- if (histories == null)
- return;
-
- var list = histories.CommitListContainer;
- 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;
-
- // 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);
- }
- }
-
- private void DrawCurves(DrawingContext context, Models.CommitGraph graph, double top, double bottom)
- {
- var grayedPen = new Pen(new SolidColorBrush(Colors.Gray, 0.4), Models.CommitGraph.Pens[0].Thickness);
- var onlyHighlightCurrentBranch = OnlyHighlightCurrentBranch;
-
- if (onlyHighlightCurrentBranch)
- {
- foreach (var link in graph.Links)
- {
- if (link.IsMerged)
- continue;
- if (link.End.Y < top)
- continue;
- if (link.Start.Y > bottom)
- break;
-
- var geo = new StreamGeometry();
- using (var ctx = geo.Open())
- {
- ctx.BeginFigure(link.Start, false);
- ctx.QuadraticBezierTo(link.Control, link.End);
- }
-
- context.DrawGeometry(null, grayedPen, geo);
- }
- }
-
- foreach (var line in graph.Paths)
- {
- var last = line.Points[0];
- var size = line.Points.Count;
-
- if (line.Points[size - 1].Y < top)
- continue;
- if (last.Y > bottom)
- break;
-
- var geo = new StreamGeometry();
- var pen = Models.CommitGraph.Pens[line.Color];
-
- using (var ctx = geo.Open())
- {
- var started = false;
- var ended = false;
- for (int i = 1; i < size; i++)
- {
- var cur = line.Points[i];
- if (cur.Y < top)
- {
- last = cur;
- continue;
- }
-
- if (!started)
- {
- ctx.BeginFigure(last, false);
- started = true;
- }
-
- if (cur.Y > bottom)
- {
- cur = new Point(cur.X, bottom);
- ended = true;
- }
-
- if (cur.X > last.X)
- {
- ctx.QuadraticBezierTo(new Point(cur.X, last.Y), cur);
- }
- else if (cur.X < last.X)
- {
- if (i < size - 1)
- {
- var midY = (last.Y + cur.Y) / 2;
- ctx.CubicBezierTo(new Point(last.X, midY + 4), new Point(cur.X, midY - 4), cur);
- }
- else
- {
- ctx.QuadraticBezierTo(new Point(last.X, cur.Y), cur);
- }
- }
- else
- {
- ctx.LineTo(cur);
- }
-
- if (ended)
- break;
- last = cur;
- }
- }
-
- if (!line.IsMerged && onlyHighlightCurrentBranch)
- context.DrawGeometry(null, grayedPen, geo);
- else
- context.DrawGeometry(null, pen, geo);
- }
-
- foreach (var link in graph.Links)
- {
- if (onlyHighlightCurrentBranch && !link.IsMerged)
- continue;
- if (link.End.Y < top)
- continue;
- if (link.Start.Y > bottom)
- break;
-
- var geo = new StreamGeometry();
- using (var ctx = geo.Open())
- {
- ctx.BeginFigure(link.Start, false);
- ctx.QuadraticBezierTo(link.Control, link.End);
- }
-
- context.DrawGeometry(null, Models.CommitGraph.Pens[link.Color], geo);
- }
- }
-
- private void DrawAnchors(DrawingContext context, Models.CommitGraph graph, double top, double bottom)
- {
- var dotFill = DotBrush;
- var dotFillPen = new Pen(dotFill, 2);
- var grayedPen = new Pen(Brushes.Gray, Models.CommitGraph.Pens[0].Thickness);
- var onlyHighlightCurrentBranch = OnlyHighlightCurrentBranch;
-
- foreach (var dot in graph.Dots)
- {
- if (dot.Center.Y < top)
- continue;
- if (dot.Center.Y > bottom)
- break;
-
- var pen = Models.CommitGraph.Pens[dot.Color];
- if (!dot.IsMerged && onlyHighlightCurrentBranch)
- pen = grayedPen;
-
- 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);
- 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));
- break;
- default:
- context.DrawEllipse(dotFill, pen, dot.Center, 3, 3);
- break;
- }
- }
- }
- }
-
public partial class Histories : UserControl
{
public static readonly StyledProperty AuthorNameColumnWidthProperty =
@@ -754,36 +117,34 @@ namespace SourceGit.Views
set => SetValue(NavigationIdProperty, value);
}
- static Histories()
- {
- NavigationIdProperty.Changed.AddClassHandler((h, _) =>
- {
- if (h.DataContext == null)
- return;
-
- // Force scroll selected item (current head) into view. see issue #58
- var list = h.CommitListContainer;
- if (list != null && list.SelectedItems.Count == 1)
- list.ScrollIntoView(list.SelectedIndex);
- });
-
- AuthorNameColumnWidthProperty.Changed.AddClassHandler((h, _) =>
- {
- h.CommitGraph.InvalidateVisual();
- });
- }
-
public Histories()
{
InitializeComponent();
}
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == NavigationIdProperty)
+ {
+ if (DataContext is ViewModels.Histories)
+ {
+ var list = CommitListContainer;
+ if (list != null && list.SelectedItems.Count == 1)
+ list.ScrollIntoView(list.SelectedIndex);
+ }
+ }
+ }
+
private void OnCommitListLayoutUpdated(object _1, EventArgs _2)
{
var y = CommitListContainer.Scroll?.Offset.Y ?? 0;
- if (y != _lastScrollY)
+ var authorNameColumnWidth = AuthorNameColumnWidth.Value;
+ if (y != _lastScrollY || authorNameColumnWidth != _lastAuthorNameColumnWidth)
{
_lastScrollY = y;
+ _lastAuthorNameColumnWidth = authorNameColumnWidth;
CommitGraph.InvalidateVisual();
}
}
@@ -863,5 +224,6 @@ namespace SourceGit.Views
}
private double _lastScrollY = 0;
+ private double _lastAuthorNameColumnWidth = 0;
}
}
diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs
index b8c09b35..9ccec78c 100644
--- a/src/Views/Launcher.axaml.cs
+++ b/src/Views/Launcher.axaml.cs
@@ -273,8 +273,10 @@ namespace SourceGit.Views
protected override void OnClosing(WindowClosingEventArgs e)
{
- (DataContext as ViewModels.Launcher)?.Quit(Width, Height);
base.OnClosing(e);
+
+ if (!Design.IsDesignMode && DataContext is ViewModels.Launcher launcher)
+ launcher.Quit(Width, Height);
}
private void OnOpenWorkspaceMenu(object sender, RoutedEventArgs e)
diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml
index 12679e50..7bc83b10 100644
--- a/src/Views/Preferences.axaml
+++ b/src/Views/Preferences.axaml
@@ -422,7 +422,7 @@
-
+
@@ -527,12 +527,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
diff --git a/src/Views/Preferences.axaml.cs b/src/Views/Preferences.axaml.cs
index 8f9917be..73b2e995 100644
--- a/src/Views/Preferences.axaml.cs
+++ b/src/Views/Preferences.axaml.cs
@@ -103,6 +103,15 @@ namespace SourceGit.Views
set => SetValue(SelectedOpenAIServiceProperty, value);
}
+ public static readonly StyledProperty SelectedCustomActionProperty =
+ AvaloniaProperty.Register(nameof(SelectedCustomAction));
+
+ public Models.CustomAction SelectedCustomAction
+ {
+ get => GetValue(SelectedCustomActionProperty);
+ set => SetValue(SelectedCustomActionProperty, value);
+ }
+
public Preferences()
{
var pref = ViewModels.Preferences.Instance;
@@ -160,6 +169,11 @@ namespace SourceGit.Views
protected override void OnClosing(WindowClosingEventArgs e)
{
+ base.OnClosing(e);
+
+ if (Design.IsDesignMode)
+ return;
+
var config = new Commands.Config(null).ListAll();
SetIfChanged(config, "user.name", DefaultUser, "");
SetIfChanged(config, "user.email", DefaultEmail, "");
@@ -190,7 +204,6 @@ namespace SourceGit.Views
}
ViewModels.Preferences.Instance.Save();
- base.OnClosing(e);
}
private async void SelectThemeOverrideFile(object _, RoutedEventArgs e)
@@ -368,6 +381,40 @@ namespace SourceGit.Views
e.Handled = true;
}
+ private void OnAddCustomAction(object sender, RoutedEventArgs e)
+ {
+ var action = new Models.CustomAction() { Name = "Unnamed Action (Global)" };
+ ViewModels.Preferences.Instance.CustomActions.Add(action);
+ SelectedCustomAction = action;
+
+ e.Handled = true;
+ }
+
+ private async void SelectExecutableForCustomAction(object sender, RoutedEventArgs e)
+ {
+ var options = new FilePickerOpenOptions()
+ {
+ FileTypeFilter = [new FilePickerFileType("Executable file(script)") { Patterns = ["*.*"] }],
+ AllowMultiple = false,
+ };
+
+ var selected = await StorageProvider.OpenFilePickerAsync(options);
+ if (selected.Count == 1 && sender is Button { DataContext: Models.CustomAction action })
+ action.Executable = selected[0].Path.LocalPath;
+
+ e.Handled = true;
+ }
+
+ private void OnRemoveSelectedCustomAction(object sender, RoutedEventArgs e)
+ {
+ if (SelectedCustomAction == null)
+ return;
+
+ ViewModels.Preferences.Instance.CustomActions.Remove(SelectedCustomAction);
+ SelectedCustomAction = null;
+ e.Handled = true;
+ }
+
private void UpdateGitVersion()
{
GitVersion = Native.OS.GitVersionString;
diff --git a/src/Views/Pull.axaml b/src/Views/Pull.axaml
index 3e1f96d9..67121826 100644
--- a/src/Views/Pull.axaml
+++ b/src/Views/Pull.axaml
@@ -22,7 +22,7 @@
-
+
-
-
-
+ Content="{DynamicResource Text.Pull.LocalChanges.StashAndReply}"
+ IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/>
+
-
+
diff --git a/src/Views/Pull.axaml.cs b/src/Views/Pull.axaml.cs
index 3003f02c..c6b4923e 100644
--- a/src/Views/Pull.axaml.cs
+++ b/src/Views/Pull.axaml.cs
@@ -1,5 +1,4 @@
using Avalonia.Controls;
-using Avalonia.Interactivity;
namespace SourceGit.Views
{
@@ -9,51 +8,5 @@ namespace SourceGit.Views
{
InitializeComponent();
}
-
- protected override void OnLoaded(RoutedEventArgs e)
- {
- base.OnLoaded(e);
-
- var vm = DataContext as ViewModels.Pull;
- if (vm == null)
- return;
-
- switch (vm.PreAction)
- {
- case Models.DealWithLocalChanges.DoNothing:
- RadioDoNothing.IsChecked = true;
- break;
- case Models.DealWithLocalChanges.StashAndReaply:
- RadioStashAndReply.IsChecked = true;
- break;
- default:
- RadioDiscard.IsChecked = true;
- break;
- }
- }
-
- private void OnLocalChangeActionIsCheckedChanged(object sender, RoutedEventArgs e)
- {
- var vm = DataContext as ViewModels.Pull;
- if (vm == null)
- return;
-
- if (RadioDoNothing.IsChecked == true)
- {
- if (vm.PreAction != Models.DealWithLocalChanges.DoNothing)
- vm.PreAction = Models.DealWithLocalChanges.DoNothing;
- return;
- }
-
- if (RadioStashAndReply.IsChecked == true)
- {
- if (vm.PreAction != Models.DealWithLocalChanges.StashAndReaply)
- vm.PreAction = Models.DealWithLocalChanges.StashAndReaply;
- return;
- }
-
- if (vm.PreAction != Models.DealWithLocalChanges.Discard)
- vm.PreAction = Models.DealWithLocalChanges.Discard;
- }
}
}
diff --git a/src/Views/RenameBranch.axaml b/src/Views/RenameBranch.axaml
index 59a849fe..efbbf323 100644
--- a/src/Views/RenameBranch.axaml
+++ b/src/Views/RenameBranch.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.RenameBranch"
x:DataType="vm:RenameBranch">
@@ -11,7 +12,7 @@
-
+
+
+
+
+
+
diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml
index 30180f7d..b16447fa 100644
--- a/src/Views/Repository.axaml
+++ b/src/Views/Repository.axaml
@@ -86,7 +86,7 @@
-
+
-
+
+ VerticalContentAlignment="Center">
-
@@ -421,14 +421,20 @@
+ HorizontalOffset="-8" VerticalAlignment="-8">
+
+
+
+
+
+
+
@@ -478,11 +484,11 @@
BorderThickness="0"
SelectedIndex="{Binding SearchCommitFilterType, Mode=TwoWay}">
-
-
-
-
-
+
+
+
+
+
@@ -490,26 +496,29 @@
Margin="4,0,0,0"
IsChecked="{Binding OnlySearchCommitsInCurrentBranch, Mode=TwoWay}"
IsVisible="{Binding SearchCommitFilterType, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
-
+
+ ScrollViewer.VerticalScrollBarVisibility="Auto"
+ Grid.IsSharedSizeScope="True">
@@ -521,18 +530,22 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -549,7 +562,7 @@
-
+
-
+
@@ -636,7 +649,7 @@
-
+
-
+
-
+
-
+
diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs
index 2b3b7c30..00218a85 100644
--- a/src/Views/Repository.axaml.cs
+++ b/src/Views/Repository.axaml.cs
@@ -134,7 +134,7 @@ namespace SourceGit.Views
}
else if (e.Key == Key.Down)
{
- if (repo.IsSearchCommitSuggestionOpen)
+ if (repo.MatchedFilesForSearching is { Count: > 0 })
{
SearchSuggestionBox.Focus(NavigationMethod.Tab);
SearchSuggestionBox.SelectedIndex = 0;
@@ -144,12 +144,7 @@ namespace SourceGit.Views
}
else if (e.Key == Key.Escape)
{
- if (repo.IsSearchCommitSuggestionOpen)
- {
- repo.SearchCommitFilterSuggestion.Clear();
- repo.IsSearchCommitSuggestionOpen = false;
- }
-
+ repo.ClearMatchedFilesForSearching();
e.Handled = true;
}
}
@@ -369,9 +364,7 @@ namespace SourceGit.Views
if (e.Key == Key.Escape)
{
- repo.IsSearchCommitSuggestionOpen = false;
- repo.SearchCommitFilterSuggestion.Clear();
-
+ repo.ClearMatchedFilesForSearching();
e.Handled = true;
}
else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content)
diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs
index 3faba5ee..455731aa 100644
--- a/src/Views/RepositoryConfigure.axaml.cs
+++ b/src/Views/RepositoryConfigure.axaml.cs
@@ -13,8 +13,10 @@ namespace SourceGit.Views
protected override void OnClosing(WindowClosingEventArgs e)
{
- (DataContext as ViewModels.RepositoryConfigure)?.Save();
base.OnClosing(e);
+
+ if (!Design.IsDesignMode && DataContext is ViewModels.RepositoryConfigure configure)
+ configure.Save();
}
private async void SelectExecutableForCustomAction(object sender, RoutedEventArgs e)
diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml
index 6847b14b..e0c6577d 100644
--- a/src/Views/RevisionFiles.axaml
+++ b/src/Views/RevisionFiles.axaml
@@ -43,8 +43,14 @@
+ HorizontalOffset="-8" VerticalAlignment="-8">
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs
index 04674f86..4ae4d779 100644
--- a/src/Views/WorkingCopy.axaml.cs
+++ b/src/Views/WorkingCopy.axaml.cs
@@ -1,7 +1,6 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
-using Avalonia.VisualTree;
namespace SourceGit.Views
{
@@ -160,14 +159,5 @@ namespace SourceGit.Views
e.Handled = true;
}
-
- private void OnPressedSHA(object sender, PointerPressedEventArgs e)
- {
- var repoView = this.FindAncestorOfType();
- if (repoView is { DataContext: ViewModels.Repository repo } && sender is TextBlock text)
- repo.NavigateToCommit(text.Text);
-
- e.Handled = true;
- }
}
}