mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-23 05:05:00 +00:00
Merge branch 'release/v2025.09'
This commit is contained in:
commit
6fd6bbb6b5
74 changed files with 1582 additions and 1473 deletions
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
### de_DE.axaml: 99.08%
|
||||
### de_DE.axaml: 99.07%
|
||||
|
||||
|
||||
<details>
|
||||
|
@ -24,7 +24,7 @@
|
|||
|
||||
</details>
|
||||
|
||||
### fr_FR.axaml: 91.68%
|
||||
### fr_FR.axaml: 91.66%
|
||||
|
||||
|
||||
<details>
|
||||
|
@ -106,7 +106,7 @@
|
|||
|
||||
</details>
|
||||
|
||||
### pt_BR.axaml: 91.41%
|
||||
### pt_BR.axaml: 91.39%
|
||||
|
||||
|
||||
<details>
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
2025.08
|
||||
2025.09
|
|
@ -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
|
||||
fi
|
||||
|
|
|
@ -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<string>();
|
||||
|
||||
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;
|
||||
|
|
|
@ -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}";
|
||||
}
|
||||
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string>();
|
||||
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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<string> Result()
|
||||
|
|
|
@ -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<Models.Change> Result()
|
||||
|
|
|
@ -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<string> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
return rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
var lines = rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var outs = new List<string>();
|
||||
foreach (var line in lines)
|
||||
outs.Add(line);
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public enum DealWithLocalChanges
|
||||
{
|
||||
DoNothing,
|
||||
StashAndReaply,
|
||||
Discard,
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<string, User> _caches = new ConcurrentDictionary<string, User>();
|
||||
private readonly int _hash;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Text;
|
|||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Native
|
||||
{
|
||||
|
@ -213,6 +214,10 @@ namespace SourceGit.Native
|
|||
}
|
||||
|
||||
private void FixWindowFrameOnWin10(Window w)
|
||||
{
|
||||
// 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)
|
||||
|
@ -220,6 +225,7 @@ namespace SourceGit.Native
|
|||
|
||||
var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
|
||||
DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins);
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
#region EXTERNAL_EDITOR_FINDER
|
||||
|
|
|
@ -90,7 +90,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branch:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Lokale Änderungen:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Verwerfen</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Nichts tun</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stashen & wieder anwenden</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry Pick</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Quelle an Commit-Nachricht anhängen</x:String>
|
||||
|
@ -206,7 +205,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Erstellten Branch auschecken</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Lokale Änderungen:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Verwerfen</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">Nichts tun</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stashen & wieder anwenden</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Neuer Branch-Name:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Branch-Namen eingeben.</x:String>
|
||||
|
@ -445,6 +443,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">Einfügen</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">Gerade eben</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">Vor {0} Minuten</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">Vor 1 Stunde</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">Vor {0} Stunden</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">Gestern</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">Vor {0} Tagen</x:String>
|
||||
|
@ -517,7 +516,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">Lokaler Branch:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Lokale Änderungen:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Verwerfen</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">Nichts tun</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Stashen & wieder anwenden</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Ohne Tags fetchen</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Remote:</x:String>
|
||||
|
|
|
@ -88,7 +88,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branch:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Local Changes:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Discard</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Do Nothing</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stash & Reapply</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry Pick</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Append source to commit message</x:String>
|
||||
|
@ -205,7 +204,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Check out the created branch</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Local Changes:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Discard</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">Do Nothing</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stash & Reapply</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">New Branch Name:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Enter branch name.</x:String>
|
||||
|
@ -446,6 +444,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">Paste</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">Just now</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">{0} minutes ago</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">1 hour ago</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">{0} hours ago</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">Yesterday</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} days ago</x:String>
|
||||
|
@ -520,7 +519,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">Into:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Local Changes:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Discard</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">Do Nothing</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Stash & Reapply</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Fetch without tags</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Remote:</x:String>
|
||||
|
|
|
@ -91,7 +91,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Rama:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Cambios Locales:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Descartar</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">No Hacer Nada</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stash & Reaplicar</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry Pick</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Añadir fuente al mensaje de commit</x:String>
|
||||
|
@ -208,7 +207,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Checkout de la rama creada</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Cambios Locales:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Descartar</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">No Hacer Nada</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stash & Reaplicar</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Nombre de la Nueva Rama:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Introduzca el nombre de la rama.</x:String>
|
||||
|
@ -449,6 +447,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">Pegar</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">Justo ahora</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">Hace {0} minutos</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">Hace 1 hora</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">Hace {0} horas</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">Ayer</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">Hace {0} días</x:String>
|
||||
|
@ -524,7 +523,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">En:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Cambios Locales:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Descartar</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">No Hacer Nada</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Stash & Reaplicar</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Fetch sin etiquetas</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Remoto:</x:String>
|
||||
|
|
|
@ -83,7 +83,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branche :</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Changements locaux :</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Annuler</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Ne rien faire</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Mettre en stash et réappliquer</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry-Pick de ce commit</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Ajouter la source au message de commit</x:String>
|
||||
|
@ -198,7 +197,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Récupérer la branche créée</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Changements locaux :</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Rejeter</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">Ne rien faire</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stash & Réappliquer</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Nom de la nouvelle branche :</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Entrez le nom de la branche.</x:String>
|
||||
|
@ -424,6 +422,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">Coller</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">A l'instant</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">il y a {0} minutes</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">il y a 1 heure</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">il y a {0} heures</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">Hier</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">il y a {0} jours</x:String>
|
||||
|
@ -492,7 +491,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">Dans :</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Changements locaux :</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Rejeter</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">Ne rien faire</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Stash & Réappliquer</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Fetch sans les tags</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Dépôt distant :</x:String>
|
||||
|
|
|
@ -91,7 +91,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branch:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Modifiche Locali:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Scarta</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Non fare nulla</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stasha e Ripristina</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry Pick</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Aggiungi sorgente al messaggio di commit</x:String>
|
||||
|
@ -208,7 +207,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Checkout del Branch Creato</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Modifiche Locali:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Scarta</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">Non Fare Nulla</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stasha e Ripristina</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Nome Nuovo Branch:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Inserisci il nome del branch.</x:String>
|
||||
|
@ -450,6 +448,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">Incolla</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">Proprio ora</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">{0} minuti fa</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">1 ora fa</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">{0} ore fa</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">Ieri</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} giorni fa</x:String>
|
||||
|
@ -523,7 +522,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">In:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Modifiche Locali:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Scarta</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">Non fare nulla</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Stasha e Riapplica</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Recupera senza tag</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Remoto:</x:String>
|
||||
|
|
|
@ -107,7 +107,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branch:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Alterações Locais:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Descartar</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Nada</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stash & Reaplicar</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry-Pick</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Adicionar origem à mensagem de commit</x:String>
|
||||
|
@ -215,7 +214,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Checar o branch criado</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Alterações Locais:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Descartar</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">Não Fazer Nada</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Guardar & Reaplicar</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Nome do Novo Branch:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Insira o nome do branch.</x:String>
|
||||
|
@ -437,6 +435,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">Colar</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">Agora mesmo</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">{0} minutos atrás</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">1 hora atrás</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">{0} horas atrás</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">Ontem</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} dias atrás</x:String>
|
||||
|
@ -506,7 +505,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">Para:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Alterações Locais:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Descartar</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">Não Fazer Nada</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Guardar & Reaplicar</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Buscar sem tags</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Remoto:</x:String>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• Исходный код можно найти по адресу </x:String>
|
||||
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">Бесплатный графический клиент Git с исходным кодом</x:String>
|
||||
<x:String x:Key="Text.AddWorktree" xml:space="preserve">Добавить рабочий каталог</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">Что проверить:</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Существующую ветку</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Создать новую ветку</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">Переключиться на:</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">ветку из списка</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">создать новую ветку</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Location" xml:space="preserve">Расположение:</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Location.Placeholder" xml:space="preserve">Путь к рабочему каталогу (поддерживается относительный путь)</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Name" xml:space="preserve">Имя ветки:</x:String>
|
||||
|
@ -91,7 +91,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Ветка:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Локальные изменения:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Отклонить</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Ничего не делать</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Отложить и примненить повторно</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve"> Частичный выбор</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Добавить источник для ревизии сообщения</x:String>
|
||||
|
@ -126,7 +125,7 @@
|
|||
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Сбросить ${0}$ сюда</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">Отменить ревизию</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Reword" xml:space="preserve">Изменить комментарий</x:String>
|
||||
<x:String x:Key="Text.CommitCM.SaveAsPatch" xml:space="preserve">Сохранить как patch-файл...</x:String>
|
||||
<x:String x:Key="Text.CommitCM.SaveAsPatch" xml:space="preserve">Сохранить как заплатки...</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Squash" xml:space="preserve">Объединить с предыдущей ревизией</x:String>
|
||||
<x:String x:Key="Text.CommitCM.SquashCommitsSinceThis" xml:space="preserve">Объединить все следующие ревизии с этим</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Changes" xml:space="preserve">ИЗМЕНЕНИЯ</x:String>
|
||||
|
@ -167,7 +166,7 @@
|
|||
<x:String x:Key="Text.Configure.Email" xml:space="preserve">Адрес электронной почты</x:String>
|
||||
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">Адрес электронной почты</x:String>
|
||||
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.AutoFetch" xml:space="preserve">Автоматическая загрузка изменений</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.AutoFetch" xml:space="preserve">Автозагрузка изменений</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.AutoFetchIntervalSuffix" xml:space="preserve">Минут(а/ы)</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.DefaultRemote" xml:space="preserve">Внешний репозиторий по умолчанию</x:String>
|
||||
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">ОТСЛЕЖИВАНИЕ ПРОБЛЕМ</x:String>
|
||||
|
@ -209,7 +208,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Проверить созданную ветку</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Локальные изменения:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Отклонить</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">Ничего не делать</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Отложить и применить повторно</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Имя новой ветки:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Введите имя ветки.</x:String>
|
||||
|
@ -218,15 +216,15 @@
|
|||
<x:String x:Key="Text.CreateTag" xml:space="preserve">Создать метку...</x:String>
|
||||
<x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">Новая метка у:</x:String>
|
||||
<x:String x:Key="Text.CreateTag.GPGSign" xml:space="preserve">GPG подпись</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Message" xml:space="preserve">Сообщение с меткой:</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Message" xml:space="preserve">Сообщение с меткой:</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Message.Placeholder" xml:space="preserve">Необязательно.</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Name" xml:space="preserve">Имя метки:</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Name.Placeholder" xml:space="preserve">Рекомендуемый формат: v1.0.0-alpha</x:String>
|
||||
<x:String x:Key="Text.CreateTag.PushToAllRemotes" xml:space="preserve">Выложить на все внешние репозитории после создания</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Title" xml:space="preserve">Создать новую метку</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Type" xml:space="preserve">Вид:</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">Аннотированный</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">Лёгкий</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">С примечаниями</x:String>
|
||||
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">Простой</x:String>
|
||||
<x:String x:Key="Text.CtrlClickTip" xml:space="preserve">Удерживайте Ctrl, чтобы начать сразу</x:String>
|
||||
<x:String x:Key="Text.Cut" xml:space="preserve">Вырезать</x:String>
|
||||
<x:String x:Key="Text.DeleteBranch" xml:space="preserve">Удалить ветку</x:String>
|
||||
|
@ -237,7 +235,7 @@
|
|||
<x:String x:Key="Text.DeleteMultiBranch.Tip" xml:space="preserve">Вы пытаетесь удалить несколько веток одновременно. Обязательно перепроверьте, прежде чем предпринимать какие-либо действия!</x:String>
|
||||
<x:String x:Key="Text.DeleteRemote" xml:space="preserve">Удалить внешний репозиторий</x:String>
|
||||
<x:String x:Key="Text.DeleteRemote.Remote" xml:space="preserve">Внешний репозиторий:</x:String>
|
||||
<x:String x:Key="Text.DeleteRepositoryNode.Path" xml:space="preserve">Path:</x:String>
|
||||
<x:String x:Key="Text.DeleteRepositoryNode.Path" xml:space="preserve">Путь:</x:String>
|
||||
<x:String x:Key="Text.DeleteRepositoryNode.Target" xml:space="preserve">Цель:</x:String>
|
||||
<x:String x:Key="Text.DeleteRepositoryNode.TipForGroup" xml:space="preserve">Все дочерние элементы будут удалены из списка.</x:String>
|
||||
<x:String x:Key="Text.DeleteRepositoryNode.TitleForGroup" xml:space="preserve">Подтвердите удаление группы</x:String>
|
||||
|
@ -262,7 +260,7 @@
|
|||
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">НИКАКИХ ИЗМЕНЕНИЙ ИЛИ МЕНЯЕТСЯ ТОЛЬКО EOL</x:String>
|
||||
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">Предыдущее различие</x:String>
|
||||
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">Сохранить как заплатку</x:String>
|
||||
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">Различие бок о бок</x:String>
|
||||
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">Различие рядом</x:String>
|
||||
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">ПОДМОДУЛЬ</x:String>
|
||||
<x:String x:Key="Text.Diff.Submodule.New" xml:space="preserve">НОВЫЙ</x:String>
|
||||
<x:String x:Key="Text.Diff.SwapCommits" xml:space="preserve">Обмен</x:String>
|
||||
|
@ -450,6 +448,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">Вставить</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">Сейчас</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">{0} минут назад</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">1 час назад</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">{0} часов назад</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">Вчера</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} дней назад</x:String>
|
||||
|
@ -524,7 +523,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">В:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Локальные изменения:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Отклонить</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">Ничего не делать</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Отложить и применить повторно</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Забрать без меток</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Внешний репозиторий:</x:String>
|
||||
|
@ -553,7 +551,7 @@
|
|||
<x:String x:Key="Text.Remote.EditTitle" xml:space="preserve">Редактировать внешний репозиторий</x:String>
|
||||
<x:String x:Key="Text.Remote.Name" xml:space="preserve">Имя:</x:String>
|
||||
<x:String x:Key="Text.Remote.Name.Placeholder" xml:space="preserve">Имя внешнего репозитория</x:String>
|
||||
<x:String x:Key="Text.Remote.URL" xml:space="preserve">Адрес репозитория:</x:String>
|
||||
<x:String x:Key="Text.Remote.URL" xml:space="preserve">Адрес:</x:String>
|
||||
<x:String x:Key="Text.Remote.URL.Placeholder" xml:space="preserve">Адрес внешнего репозитория git</x:String>
|
||||
<x:String x:Key="Text.RemoteCM.CopyURL" xml:space="preserve">Копировать адрес</x:String>
|
||||
<x:String x:Key="Text.RemoteCM.Delete" xml:space="preserve">Удалить...</x:String>
|
||||
|
@ -693,8 +691,8 @@
|
|||
<x:String x:Key="Text.Submodule.CopyPath" xml:space="preserve">Копировать относительный путь</x:String>
|
||||
<x:String x:Key="Text.Submodule.FetchNested" xml:space="preserve">Извлечение вложенных подмодулей</x:String>
|
||||
<x:String x:Key="Text.Submodule.Open" xml:space="preserve">Открыть подмодуль репозитория</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath" xml:space="preserve">Относительный путь:</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath.Placeholder" xml:space="preserve">Относительный каталог для хранения подмодуля.</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath" xml:space="preserve">Каталог:</x:String>
|
||||
<x:String x:Key="Text.Submodule.RelativePath.Placeholder" xml:space="preserve">Относительный путь для хранения подмодуля.</x:String>
|
||||
<x:String x:Key="Text.Submodule.Remove" xml:space="preserve">Удалить подмодуль</x:String>
|
||||
<x:String x:Key="Text.Sure" xml:space="preserve">ОК</x:String>
|
||||
<x:String x:Key="Text.TagCM.Copy" xml:space="preserve">Копировать имя метки</x:String>
|
||||
|
|
|
@ -91,7 +91,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">目标分支 :</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">未提交更改 :</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">丢弃更改</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">不做处理</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">贮藏并自动恢复</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">挑选提交</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">提交信息中追加来源信息</x:String>
|
||||
|
@ -208,7 +207,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">完成后切换到新分支</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">未提交更改 :</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">丢弃更改</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">不做处理</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">贮藏并自动恢复</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">新分支名 :</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">填写分支名称。</x:String>
|
||||
|
@ -449,6 +447,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">粘贴</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">刚刚</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">{0}分钟前</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">1小时前</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">{0}小时前</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">昨天</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0}天前</x:String>
|
||||
|
@ -524,7 +523,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">本地分支 :</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">未提交更改 :</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">丢弃更改</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">不做处理</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">贮藏并自动恢复</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">不拉取远程标签</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">远程 :</x:String>
|
||||
|
|
|
@ -91,7 +91,6 @@
|
|||
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">目標分支:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">未提交變更:</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">捨棄變更</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">不做處理</x:String>
|
||||
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">擱置變更並自動復原</x:String>
|
||||
<x:String x:Key="Text.CherryPick" xml:space="preserve">揀選提交</x:String>
|
||||
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">提交資訊中追加來源資訊</x:String>
|
||||
|
@ -208,7 +207,6 @@
|
|||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">完成後切換到新分支</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">未提交變更:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">捨棄變更</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">不做處理</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">擱置變更並自動復原</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">新分支名稱:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">輸入分支名稱。</x:String>
|
||||
|
@ -449,6 +447,7 @@
|
|||
<x:String x:Key="Text.Paste" xml:space="preserve">貼上</x:String>
|
||||
<x:String x:Key="Text.Period.JustNow" xml:space="preserve">剛剛</x:String>
|
||||
<x:String x:Key="Text.Period.MinutesAgo" xml:space="preserve">{0} 分鐘前</x:String>
|
||||
<x:String x:Key="Text.Period.HourAgo" xml:space="preserve">1 小時前</x:String>
|
||||
<x:String x:Key="Text.Period.HoursAgo" xml:space="preserve">{0} 小時前</x:String>
|
||||
<x:String x:Key="Text.Period.Yesterday" xml:space="preserve">昨天</x:String>
|
||||
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} 天前</x:String>
|
||||
|
@ -523,7 +522,6 @@
|
|||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">本機分支:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">未提交變更:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">捨棄變更</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">不做處理</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">擱置變更並自動復原</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">不拉取遠端標籤</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">遠端:</x:String>
|
||||
|
|
|
@ -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 ...");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
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();
|
||||
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<string>();
|
||||
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<string> _revisionFiles = null;
|
||||
private string _revisionFileSearchFilter = string.Empty;
|
||||
private List<string> _revisionFileSearchSuggestion = null;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}'");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<Models.CustomAction>();
|
||||
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();
|
||||
|
|
|
@ -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<Models.CustomAction> CustomActions
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
public AvaloniaList<Models.OpenAIService> 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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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<bool> 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;
|
||||
}
|
||||
|
|
|
@ -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<Models.Commit>();
|
||||
SearchCommitFilter = string.Empty;
|
||||
SearchCommitFilterSuggestion.Clear();
|
||||
IsSearchCommitSuggestionOpen = false;
|
||||
_revisionFiles.Clear();
|
||||
|
||||
if (value)
|
||||
{
|
||||
SelectedViewIndex = 0;
|
||||
UpdateCurrentRevisionFilesForSearchSuggestion();
|
||||
CalcWorktreeFilesForSearching();
|
||||
}
|
||||
else
|
||||
{
|
||||
SearchedCommits = new List<Models.Commit>();
|
||||
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,46 +320,16 @@ 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<string>();
|
||||
foreach (var file in _revisionFiles)
|
||||
{
|
||||
if (file.Contains(value, StringComparison.OrdinalIgnoreCase) && file.Length != value.Length)
|
||||
{
|
||||
suggestion.Add(file);
|
||||
if (suggestion.Count > 100)
|
||||
break;
|
||||
if (SetProperty(ref _searchCommitFilter, value) && IsSearchingCommitsByFilePath())
|
||||
CalcMatchedFilesForSearching();
|
||||
}
|
||||
}
|
||||
|
||||
SearchCommitFilterSuggestion.Clear();
|
||||
SearchCommitFilterSuggestion.AddRange(suggestion);
|
||||
IsSearchCommitSuggestionOpen = SearchCommitFilterSuggestion.Count > 0;
|
||||
}
|
||||
else if (SearchCommitFilterSuggestion.Count > 0)
|
||||
public List<string> MatchedFilesForSearching
|
||||
{
|
||||
SearchCommitFilterSuggestion.Clear();
|
||||
IsSearchCommitSuggestionOpen = false;
|
||||
get => _matchedFilesForSearching;
|
||||
private set => SetProperty(ref _matchedFilesForSearching, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSearchCommitSuggestionOpen
|
||||
{
|
||||
get => _isSearchCommitSuggestionOpen;
|
||||
set => SetProperty(ref _isSearchCommitSuggestionOpen, value);
|
||||
}
|
||||
|
||||
public AvaloniaList<string> SearchCommitFilterSuggestion
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new AvaloniaList<string>();
|
||||
|
||||
public List<Models.Commit> SearchedCommits
|
||||
{
|
||||
|
@ -367,6 +337,16 @@ namespace SourceGit.ViewModels
|
|||
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<Models.Commit>();
|
||||
var visible = null as List<Models.Commit>;
|
||||
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;
|
||||
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<Models.CustomAction> GetCustomActions(Models.CustomActionScope scope)
|
||||
{
|
||||
var actions = new List<Models.CustomAction>();
|
||||
|
||||
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<Models.OpenAIService> GetPreferedOpenAIServices()
|
||||
public List<Models.OpenAIService> 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<Models.OpenAIService>();
|
||||
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<Models.CustomAction>();
|
||||
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<Models.CustomAction>();
|
||||
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())
|
||||
{
|
||||
_worktreeFiles = null;
|
||||
MatchedFilesForSearching = null;
|
||||
GC.Collect();
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
var files = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result();
|
||||
_worktreeFiles = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
if (_searchCommitFilterType != 3)
|
||||
return;
|
||||
if (IsSearchingCommitsByFilePath())
|
||||
CalcMatchedFilesForSearching();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_revisionFiles.AddRange(files);
|
||||
|
||||
if (!string.IsNullOrEmpty(_searchCommitFilter) && _searchCommitFilter.Length > 2 && _revisionFiles.Count > 0)
|
||||
private void CalcMatchedFilesForSearching()
|
||||
{
|
||||
var suggestion = new List<string>();
|
||||
foreach (var file in _revisionFiles)
|
||||
if (_worktreeFiles == null || _worktreeFiles.Count == 0 || _searchCommitFilter.Length < 3)
|
||||
{
|
||||
MatchedFilesForSearching = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var matched = new List<string>();
|
||||
foreach (var file in _worktreeFiles)
|
||||
{
|
||||
if (file.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) && file.Length != _searchCommitFilter.Length)
|
||||
{
|
||||
suggestion.Add(file);
|
||||
if (suggestion.Count > 100)
|
||||
matched.Add(file);
|
||||
if (matched.Count > 100)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SearchCommitFilterSuggestion.Clear();
|
||||
SearchCommitFilterSuggestion.AddRange(suggestion);
|
||||
IsSearchCommitSuggestionOpen = SearchCommitFilterSuggestion.Count > 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
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<Models.Commit> _searchedCommits = new List<Models.Commit>();
|
||||
private Models.Commit _searchResultSelectedCommit = null;
|
||||
private List<string> _revisionFiles = new List<string>();
|
||||
private Models.Commit _selectedSearchedCommit = null;
|
||||
private List<string> _worktreeFiles = null;
|
||||
private List<string> _matchedFilesForSearching = null;
|
||||
|
||||
private string _filter = string.Empty;
|
||||
private object _lockRemotes = new object();
|
||||
|
|
|
@ -1452,14 +1452,11 @@ namespace SourceGit.ViewModels
|
|||
App.OpenDialog(dialog);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var menu = new ContextMenu() { Placement = PlacementMode.TopEdgeAlignedLeft };
|
||||
|
||||
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) =>
|
||||
|
@ -1474,7 +1471,6 @@ namespace SourceGit.ViewModels
|
|||
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Models.Change> GetVisibleUnstagedChanges(List<Models.Change> unstaged)
|
||||
{
|
||||
|
|
|
@ -39,7 +39,8 @@
|
|||
<Grid ColumnDefinitions="16,Auto,Auto,*"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
Background="Transparent"
|
||||
DoubleTapped="OnRowDoubleTapped">
|
||||
DoubleTapped="OnRowDoubleTapped"
|
||||
ToolTip.Tip="{Binding FullPath}">
|
||||
<v:ChangeTreeNodeToggleButton Grid.Column="0"
|
||||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
|
@ -71,7 +72,10 @@
|
|||
SelectionChanged="OnRowSelectionChanged">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Change">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,*" Background="Transparent" DoubleTapped="OnRowDoubleTapped">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,*"
|
||||
Background="Transparent"
|
||||
DoubleTapped="OnRowDoubleTapped"
|
||||
ToolTip.Tip="{Binding Path}">
|
||||
<v:ChangeStatusIcon Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
Margin="4,0,0,0"
|
||||
|
@ -100,7 +104,10 @@
|
|||
SelectionChanged="OnRowSelectionChanged">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Change">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*" Background="Transparent" DoubleTapped="OnRowDoubleTapped">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*"
|
||||
Background="Transparent"
|
||||
DoubleTapped="OnRowDoubleTapped"
|
||||
ToolTip.Tip="{Binding Path}">
|
||||
<v:ChangeStatusIcon Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
Margin="4,0,0,0"
|
||||
|
|
|
@ -33,20 +33,12 @@
|
|||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Checkout.LocalChanges}"/>
|
||||
<WrapPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<RadioButton Content="{DynamicResource Text.CreateBranch.LocalChanges.DoNothing}"
|
||||
x:Name="RadioDoNothing"
|
||||
GroupName="LocalChanges"
|
||||
<RadioButton GroupName="LocalChanges"
|
||||
Margin="0,0,8,0"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
<RadioButton Content="{DynamicResource Text.CreateBranch.LocalChanges.StashAndReply}"
|
||||
x:Name="RadioStashAndReply"
|
||||
GroupName="LocalChanges"
|
||||
Margin="0,0,8,0"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
<RadioButton Content="{DynamicResource Text.CreateBranch.LocalChanges.Discard}"
|
||||
x:Name="RadioDiscard"
|
||||
GroupName="LocalChanges"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
Content="{DynamicResource Text.CreateBranch.LocalChanges.StashAndReply}"
|
||||
IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/>
|
||||
<RadioButton GroupName="LocalChanges"
|
||||
Content="{DynamicResource Text.CreateBranch.LocalChanges.Discard}"/>
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,10 @@
|
|||
<StackPanel Grid.Row="1" Grid.Column="1" Height="32" Orientation="Horizontal">
|
||||
<RadioButton Content="{DynamicResource Text.Checkout.LocalChanges.StashAndReply}"
|
||||
GroupName="LocalChanges"
|
||||
IsChecked="{Binding AutoStash, Mode=TwoWay}" />
|
||||
Margin="0,0,8,0"
|
||||
IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/>
|
||||
<RadioButton Content="{DynamicResource Text.Checkout.LocalChanges.Discard}"
|
||||
GroupName="LocalChanges"
|
||||
Margin="8,0,0,0" />
|
||||
GroupName="LocalChanges"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="2" Grid.Column="1" ColumnDefinitions="Auto,*" Margin="0,6,0,0">
|
||||
|
|
|
@ -80,10 +80,10 @@
|
|||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Margin="0,0,0,12" Text="{Binding ToolTip}"/>
|
||||
<Grid ColumnDefinitions="Auto,8,Auto" RowDefinitions="Auto,Auto">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Signer:" IsVisible="{Binding HasSigner}"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Signer}" IsVisible="{Binding HasSigner}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Key:"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Key}"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Signer:" IsVisible="{Binding HasSigner}" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Signer}" IsVisible="{Binding HasSigner}" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Key:" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Key}" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>
|
||||
|
|
228
src/Views/CommitGraph.cs
Normal file
228
src/Views/CommitGraph.cs
Normal file
|
@ -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<Models.CommitGraph> GraphProperty =
|
||||
AvaloniaProperty.Register<CommitGraph, Models.CommitGraph>(nameof(Graph));
|
||||
|
||||
public Models.CommitGraph Graph
|
||||
{
|
||||
get => GetValue(GraphProperty);
|
||||
set => SetValue(GraphProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> DotBrushProperty =
|
||||
AvaloniaProperty.Register<CommitGraph, IBrush>(nameof(DotBrush), Brushes.Transparent);
|
||||
|
||||
public IBrush DotBrush
|
||||
{
|
||||
get => GetValue(DotBrushProperty);
|
||||
set => SetValue(DotBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> OnlyHighlightCurrentBranchProperty =
|
||||
AvaloniaProperty.Register<CommitGraph, bool>(nameof(OnlyHighlightCurrentBranch), true);
|
||||
|
||||
public bool OnlyHighlightCurrentBranch
|
||||
{
|
||||
get => GetValue(OnlyHighlightCurrentBranchProperty);
|
||||
set => SetValue(OnlyHighlightCurrentBranchProperty, value);
|
||||
}
|
||||
|
||||
static CommitGraph()
|
||||
{
|
||||
AffectsRender<CommitGraph>(GraphProperty, DotBrushProperty, OnlyHighlightCurrentBranchProperty);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
base.Render(context);
|
||||
|
||||
var graph = Graph;
|
||||
if (graph == null)
|
||||
return;
|
||||
|
||||
var histories = this.FindAncestorOfType<Histories>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
src/Views/CommitStatusIndicator.cs
Normal file
90
src/Views/CommitStatusIndicator.cs
Normal file
|
@ -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<Models.Branch> CurrentBranchProperty =
|
||||
AvaloniaProperty.Register<CommitStatusIndicator, Models.Branch>(nameof(CurrentBranch));
|
||||
|
||||
public Models.Branch CurrentBranch
|
||||
{
|
||||
get => GetValue(CurrentBranchProperty);
|
||||
set => SetValue(CurrentBranchProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> AheadBrushProperty =
|
||||
AvaloniaProperty.Register<CommitStatusIndicator, IBrush>(nameof(AheadBrush));
|
||||
|
||||
public IBrush AheadBrush
|
||||
{
|
||||
get => GetValue(AheadBrushProperty);
|
||||
set => SetValue(AheadBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> BehindBrushProperty =
|
||||
AvaloniaProperty.Register<CommitStatusIndicator, IBrush>(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;
|
||||
}
|
||||
}
|
189
src/Views/CommitSubjectPresenter.cs
Normal file
189
src/Views/CommitSubjectPresenter.cs
Normal file
|
@ -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<string> SubjectProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, string>(nameof(Subject));
|
||||
|
||||
public string Subject
|
||||
{
|
||||
get => GetValue(SubjectProperty);
|
||||
set => SetValue(SubjectProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
|
||||
|
||||
public AvaloniaList<Models.IssueTrackerRule> 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<Models.Hyperlink>();
|
||||
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<Inline>();
|
||||
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<Models.Hyperlink> _matches = null;
|
||||
private Models.Hyperlink _lastHover = null;
|
||||
}
|
||||
}
|
166
src/Views/CommitTimeTextBlock.cs
Normal file
166
src/Views/CommitTimeTextBlock.cs
Normal file
|
@ -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<bool> ShowAsDateTimeProperty =
|
||||
AvaloniaProperty.Register<CommitTimeTextBlock, bool>(nameof(ShowAsDateTime), true);
|
||||
|
||||
public bool ShowAsDateTime
|
||||
{
|
||||
get => GetValue(ShowAsDateTimeProperty);
|
||||
set => SetValue(ShowAsDateTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<int> DateTimeFormatProperty =
|
||||
AvaloniaProperty.Register<CommitTimeTextBlock, int>(nameof(DateTimeFormat), 0);
|
||||
|
||||
public int DateTimeFormat
|
||||
{
|
||||
get => GetValue(DateTimeFormatProperty);
|
||||
set => SetValue(DateTimeFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> UseAuthorTimeProperty =
|
||||
AvaloniaProperty.Register<CommitTimeTextBlock, bool>(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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
122
src/Views/Conflict.axaml
Normal file
122
src/Views/Conflict.axaml
Normal file
|
@ -0,0 +1,122 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:m="using:SourceGit.Models"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.Conflict"
|
||||
x:DataType="vm:Conflict">
|
||||
<Border Background="{DynamicResource Brush.Window}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||
<Grid VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Vertical" IsVisible="{Binding !IsResolved}">
|
||||
<StackPanel.DataTemplates>
|
||||
<DataTemplate DataType="vm:ConflictSourceBranch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding Name}"/>
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Text="{Binding Head, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
Foreground="DarkOrange"
|
||||
TextDecorations="Underline"
|
||||
Cursor="Hand"
|
||||
PointerPressed="OnPressedSHA"
|
||||
ToolTip.Tip="{Binding Revision}"
|
||||
ToolTip.ShowDelay="0">
|
||||
<TextBlock.DataTemplates>
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<StackPanel MinWidth="400" Orientation="Vertical">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
||||
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0"/>
|
||||
<TextBlock Grid.Column="2" Classes="primary" Text="{Binding CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Classes="primary" Margin="0,8,0,0" Text="{Binding Subject}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</TextBlock.DataTemplates>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Commit}"/>
|
||||
<v:CommitRefsPresenter Margin="8,0,0,0"
|
||||
TagBackground="{DynamicResource Brush.DecoratorTag}"
|
||||
Foreground="{DynamicResource Brush.FG1}"
|
||||
FontFamily="{DynamicResource Fonts.Primary}"
|
||||
FontSize="11"
|
||||
VerticalAlignment="Center"
|
||||
UseGraphColor="False"/>
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
Foreground="DarkOrange"
|
||||
TextDecorations="Underline"
|
||||
Cursor="Hand"
|
||||
PointerPressed="OnPressedSHA"
|
||||
ToolTip.Tip="{Binding}"
|
||||
ToolTip.ShowDelay="0">
|
||||
<TextBlock.DataTemplates>
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<StackPanel MinWidth="400" Orientation="Vertical">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
||||
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0"/>
|
||||
<TextBlock Grid.Column="2" Classes="primary" Text="{Binding CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Classes="primary" Margin="0,8,0,0" Text="{Binding Subject}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</TextBlock.DataTemplates>
|
||||
</TextBlock>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding Subject}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="x:String">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Changes}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</StackPanel.DataTemplates>
|
||||
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
<TextBlock Margin="0,16" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
|
||||
<Border Margin="16,0" Padding="8" CornerRadius="4" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||
<Border.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="Theirs" Converter="{x:Static ObjectConverters.IsNotNull}"/>
|
||||
<Binding Path="Mine" Converter="{x:Static ObjectConverters.IsNotNull}"/>
|
||||
</MultiBinding>
|
||||
</Border.IsVisible>
|
||||
|
||||
<Grid Margin="8,0,0,0" RowDefinitions="32,32" ColumnDefinitions="Auto,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" Text="THEIRS"/>
|
||||
<ContentControl Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Content="{Binding Theirs}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" Text="MINE"/>
|
||||
<ContentControl Grid.Row="1" Grid.Column="1" Margin="16,0,0,0" Content="{Binding Mine}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel Margin="0,8,0,0" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Classes="flat" Content="USE THEIRS" Command="{Binding UseTheirs}"/>
|
||||
<Button Classes="flat" Margin="8,0,0,0" Content="USE MINE" Command="{Binding UseMine}"/>
|
||||
<Button Classes="flat" Margin="8,0,0,0" Content="OPEN EXTERNAL MERGETOOL" Command="{Binding OpenExternalMergeTool}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Vertical" IsVisible="{Binding IsResolved}">
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icons.Check}" Fill="Green"/>
|
||||
<TextBlock Margin="0,16,0,8" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts.Resolved}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="{DynamicResource Text.WorkingCopy.CanStageTip}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
23
src/Views/Conflict.axaml.cs
Normal file
23
src/Views/Conflict.axaml.cs
Normal file
|
@ -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<Repository>();
|
||||
if (repoView is { DataContext: ViewModels.Repository repo } && sender is TextBlock text)
|
||||
repo.NavigateToCommit(text.Text);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,15 +14,7 @@
|
|||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.CreateBranch.Title}"/>
|
||||
<Grid Margin="0,16,0,0" ColumnDefinitions="140,*">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,Auto,Auto,Auto" ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
|
@ -78,20 +70,12 @@
|
|||
IsVisible="{Binding !IsBareRepository}"/>
|
||||
<Border Grid.Row="3" Grid.Column="1" MinHeight="32" IsVisible="{Binding !IsBareRepository}">
|
||||
<WrapPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<RadioButton Content="{DynamicResource Text.CreateBranch.LocalChanges.DoNothing}"
|
||||
x:Name="RadioDoNothing"
|
||||
GroupName="LocalChanges"
|
||||
<RadioButton GroupName="LocalChanges"
|
||||
Margin="0,0,8,0"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
<RadioButton Content="{DynamicResource Text.CreateBranch.LocalChanges.StashAndReply}"
|
||||
x:Name="RadioStashAndReply"
|
||||
GroupName="LocalChanges"
|
||||
Margin="0,0,8,0"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
<RadioButton Content="{DynamicResource Text.CreateBranch.LocalChanges.Discard}"
|
||||
x:Name="RadioDiscard"
|
||||
GroupName="LocalChanges"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
Content="{DynamicResource Text.CreateBranch.LocalChanges.StashAndReply}"
|
||||
IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/>
|
||||
<RadioButton GroupName="LocalChanges"
|
||||
Content="{DynamicResource Text.CreateBranch.LocalChanges.Discard}"/>
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
|
||||
|
|
|
@ -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.CreateBranch;
|
||||
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.CreateBranch;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
</Border>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="2" Classes="primary" Margin="4,0,0,0" Text="{Binding Title}" FontSize="11" TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Grid.Column="2" Classes="primary" Margin="4,0,0,0" Text="{Binding Title}" ToolTip.Tip="{Binding Title}" FontSize="11" TextTrimming="CharacterEllipsis"/>
|
||||
|
||||
<!-- Toolbar Buttons -->
|
||||
<StackPanel Grid.Column="3" Margin="8,0,0,0" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
|
|
|
@ -10,18 +10,18 @@
|
|||
x:Class="SourceGit.Views.Histories"
|
||||
x:DataType="vm:Histories"
|
||||
x:Name="ThisControl">
|
||||
<v:LayoutableGrid UseHorizontal="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseTwoColumnsLayoutInHistories}">
|
||||
<v:LayoutableGrid.RowDefinitions>
|
||||
<v:HistoriesLayout UseHorizontal="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseTwoColumnsLayoutInHistories}">
|
||||
<v:HistoriesLayout.RowDefinitions>
|
||||
<RowDefinition Height="{Binding TopArea, Mode=TwoWay}" MinHeight="100"/>
|
||||
<RowDefinition Height="3"/>
|
||||
<RowDefinition Height="{Binding BottomArea, Mode=TwoWay}" MinHeight="200"/>
|
||||
</v:LayoutableGrid.RowDefinitions>
|
||||
</v:HistoriesLayout.RowDefinitions>
|
||||
|
||||
<v:LayoutableGrid.ColumnDefinitions>
|
||||
<v:HistoriesLayout.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding LeftArea, Mode=TwoWay}" MinWidth="100"/>
|
||||
<ColumnDefinition Width="3"/>
|
||||
<ColumnDefinition Width="{Binding RightArea, Mode=TwoWay}" MinWidth="100"/>
|
||||
</v:LayoutableGrid.ColumnDefinitions>
|
||||
</v:HistoriesLayout.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
|
||||
<Grid RowDefinitions="24,*" Grid.IsSharedSizeScope="True">
|
||||
|
@ -126,7 +126,11 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Subject & REFS -->
|
||||
<Border Grid.Column="0" Padding="{Binding Margin}" ClipToBounds="True">
|
||||
<Border Grid.Column="0"
|
||||
Padding="{Binding Margin}"
|
||||
ClipToBounds="True"
|
||||
Background="Transparent"
|
||||
ToolTip.Tip="{Binding Subject}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*" Margin="2,0,4,0" ClipToBounds="True">
|
||||
<v:CommitStatusIndicator Grid.Column="0"
|
||||
CurrentBranch="{Binding $parent[v:Histories].CurrentBranch}"
|
||||
|
@ -159,7 +163,10 @@
|
|||
</Border>
|
||||
|
||||
<!-- Author -->
|
||||
<Grid Grid.Column="1" ColumnDefinitions="20,*" IsHitTestVisible="False">
|
||||
<Grid Grid.Column="1"
|
||||
ColumnDefinitions="20,*"
|
||||
Background="Transparent"
|
||||
ToolTip.Tip="{Binding Author}">
|
||||
<v:Avatar Grid.Column="0"
|
||||
Width="16" Height="16"
|
||||
Margin="4,0,0,0"
|
||||
|
@ -264,5 +271,5 @@
|
|||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</v:LayoutableGrid>
|
||||
</v:HistoriesLayout>
|
||||
</UserControl>
|
||||
|
|
|
@ -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<bool> UseHorizontalProperty =
|
||||
AvaloniaProperty.Register<LayoutableGrid, bool>(nameof(UseHorizontal));
|
||||
AvaloniaProperty.Register<HistoriesLayout, bool>(nameof(UseHorizontal));
|
||||
|
||||
public bool UseHorizontal
|
||||
{
|
||||
|
@ -28,14 +22,16 @@ namespace SourceGit.Views
|
|||
|
||||
protected override Type StyleKeyOverride => typeof(Grid);
|
||||
|
||||
static LayoutableGrid()
|
||||
public HistoriesLayout()
|
||||
{
|
||||
UseHorizontalProperty.Changed.AddClassHandler<LayoutableGrid>((o, _) => o.RefreshLayout());
|
||||
RefreshLayout();
|
||||
}
|
||||
|
||||
public override void ApplyTemplate()
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.ApplyTemplate();
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == UseHorizontalProperty)
|
||||
RefreshLayout();
|
||||
}
|
||||
|
||||
|
@ -74,639 +70,6 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
public class CommitStatusIndicator : Control
|
||||
{
|
||||
public static readonly StyledProperty<Models.Branch> CurrentBranchProperty =
|
||||
AvaloniaProperty.Register<CommitStatusIndicator, Models.Branch>(nameof(CurrentBranch));
|
||||
|
||||
public Models.Branch CurrentBranch
|
||||
{
|
||||
get => GetValue(CurrentBranchProperty);
|
||||
set => SetValue(CurrentBranchProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> AheadBrushProperty =
|
||||
AvaloniaProperty.Register<CommitStatusIndicator, IBrush>(nameof(AheadBrush));
|
||||
|
||||
public IBrush AheadBrush
|
||||
{
|
||||
get => GetValue(AheadBrushProperty);
|
||||
set => SetValue(AheadBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> BehindBrushProperty =
|
||||
AvaloniaProperty.Register<CommitStatusIndicator, IBrush>(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<string> SubjectProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, string>(nameof(Subject));
|
||||
|
||||
public string Subject
|
||||
{
|
||||
get => GetValue(SubjectProperty);
|
||||
set => SetValue(SubjectProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
|
||||
|
||||
public AvaloniaList<Models.IssueTrackerRule> 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<Models.Hyperlink>();
|
||||
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<Inline>();
|
||||
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<Models.Hyperlink> _matches = null;
|
||||
private Models.Hyperlink _lastHover = null;
|
||||
}
|
||||
|
||||
public class CommitTimeTextBlock : TextBlock
|
||||
{
|
||||
public static readonly StyledProperty<bool> ShowAsDateTimeProperty =
|
||||
AvaloniaProperty.Register<CommitTimeTextBlock, bool>(nameof(ShowAsDateTime), true);
|
||||
|
||||
public bool ShowAsDateTime
|
||||
{
|
||||
get => GetValue(ShowAsDateTimeProperty);
|
||||
set => SetValue(ShowAsDateTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<int> DateTimeFormatProperty =
|
||||
AvaloniaProperty.Register<CommitTimeTextBlock, int>(nameof(DateTimeFormat), 0);
|
||||
|
||||
public int DateTimeFormat
|
||||
{
|
||||
get => GetValue(DateTimeFormatProperty);
|
||||
set => SetValue(DateTimeFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> UseAuthorTimeProperty =
|
||||
AvaloniaProperty.Register<CommitTimeTextBlock, bool>(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<Models.CommitGraph> GraphProperty =
|
||||
AvaloniaProperty.Register<CommitGraph, Models.CommitGraph>(nameof(Graph));
|
||||
|
||||
public Models.CommitGraph Graph
|
||||
{
|
||||
get => GetValue(GraphProperty);
|
||||
set => SetValue(GraphProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> DotBrushProperty =
|
||||
AvaloniaProperty.Register<CommitGraph, IBrush>(nameof(DotBrush), Brushes.Transparent);
|
||||
|
||||
public IBrush DotBrush
|
||||
{
|
||||
get => GetValue(DotBrushProperty);
|
||||
set => SetValue(DotBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> OnlyHighlightCurrentBranchProperty =
|
||||
AvaloniaProperty.Register<CommitGraph, bool>(nameof(OnlyHighlightCurrentBranch), true);
|
||||
|
||||
public bool OnlyHighlightCurrentBranch
|
||||
{
|
||||
get => GetValue(OnlyHighlightCurrentBranchProperty);
|
||||
set => SetValue(OnlyHighlightCurrentBranchProperty, value);
|
||||
}
|
||||
|
||||
static CommitGraph()
|
||||
{
|
||||
AffectsRender<CommitGraph>(GraphProperty, DotBrushProperty, OnlyHighlightCurrentBranchProperty);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
base.Render(context);
|
||||
|
||||
var graph = Graph;
|
||||
if (graph == null)
|
||||
return;
|
||||
|
||||
var histories = this.FindAncestorOfType<Histories>();
|
||||
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<GridLength> AuthorNameColumnWidthProperty =
|
||||
|
@ -754,36 +117,34 @@ namespace SourceGit.Views
|
|||
set => SetValue(NavigationIdProperty, value);
|
||||
}
|
||||
|
||||
static Histories()
|
||||
{
|
||||
NavigationIdProperty.Changed.AddClassHandler<Histories>((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<Histories>((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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -422,7 +422,7 @@
|
|||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preferences.Integration}"/>
|
||||
</TabItem.Header>
|
||||
|
||||
<StackPanel Margin="8" MaxWidth="580" Orientation="Vertical" Grid.IsSharedSizeScope="True">
|
||||
<StackPanel Margin="8" Orientation="Vertical" Grid.IsSharedSizeScope="True">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Terminal}"/>
|
||||
<TextBlock Classes="bold" Margin="4,0,0,0" Text="{DynamicResource Text.Preferences.Shell}"/>
|
||||
|
@ -527,12 +527,126 @@
|
|||
</StackPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.CustomAction}"/>
|
||||
</TabItem.Header>
|
||||
|
||||
<Grid MinHeight="340" Margin="0,8,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200"/>
|
||||
<ColumnDefinition Width="*" MaxWidth="400"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
|
||||
Background="{DynamicResource Brush.Contents}">
|
||||
<Grid RowDefinitions="*,1,Auto">
|
||||
<ListBox Grid.Row="0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding CustomActions}"
|
||||
SelectedItem="{Binding #ThisControl.SelectedCustomAction, Mode=TwoWay}"
|
||||
SelectionMode="Single">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="MinHeight" Value="0"/>
|
||||
<Setter Property="Height" Value="26"/>
|
||||
<Setter Property="Padding" Value="4,2"/>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:CustomAction">
|
||||
<Grid Margin="4,0" ColumnDefinitions="Auto,*">
|
||||
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Action}" Fill="{DynamicResource Brush.FG1}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="6,0,0,0" TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<Rectangle Grid.Row="1" Height="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="{DynamicResource Brush.ToolBar}">
|
||||
<Button Classes="icon_button" Click="OnAddCustomAction">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Plus}"/>
|
||||
</Button>
|
||||
|
||||
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
|
||||
|
||||
<Button Classes="icon_button" Click="OnRemoveSelectedCustomAction">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Window.Minimize}"/>
|
||||
</Button>
|
||||
|
||||
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ContentControl Grid.Column="1" Margin="16,0,0,0">
|
||||
<ContentControl.Content>
|
||||
<Binding Path="SelectedCustomAction" ElementName="ThisControl">
|
||||
<Binding.TargetNullValue>
|
||||
<Path Width="64" Height="64"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Fill="{DynamicResource Brush.FG2}"
|
||||
Data="{StaticResource Icons.Empty}"/>
|
||||
</Binding.TargetNullValue>
|
||||
</Binding>
|
||||
</ContentControl.Content>
|
||||
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="m:CustomAction">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Text="{DynamicResource Text.Configure.CustomAction.Name}"/>
|
||||
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Name, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Configure.CustomAction.Scope}"/>
|
||||
<ComboBox Margin="0,4,0,0" Height="28" HorizontalAlignment="Stretch" SelectedIndex="{Binding Scope, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="{DynamicResource Text.Configure.CustomAction.Scope.Repository}"/>
|
||||
<ComboBoxItem Content="{DynamicResource Text.Configure.CustomAction.Scope.Commit}"/>
|
||||
<ComboBoxItem Content="{DynamicResource Text.Configure.CustomAction.Scope.Branch}"/>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Configure.CustomAction.Executable}"/>
|
||||
<TextBox Margin="0,4,0,0" Height="28" CornerRadius="3" Text="{Binding Executable, Mode=TwoWay}">
|
||||
<TextBox.InnerRightContent>
|
||||
<Button Classes="icon_button" Width="30" Height="30" Click="SelectExecutableForCustomAction">
|
||||
<Path Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
|
||||
</Button>
|
||||
</TextBox.InnerRightContent>
|
||||
</TextBox>
|
||||
|
||||
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Configure.CustomAction.Arguments}"/>
|
||||
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Arguments, Mode=TwoWay}"/>
|
||||
<TextBlock Margin="0,2,0,0" TextWrapping="Wrap" Text="{DynamicResource Text.Configure.CustomAction.Arguments.Tip}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
|
||||
<CheckBox Margin="0,8,0,0" Content="{DynamicResource Text.Configure.CustomAction.WaitForExit}" IsChecked="{Binding WaitForExit, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preferences.AI}"/>
|
||||
</TabItem.Header>
|
||||
|
||||
<Grid ColumnDefinitions="200,*" Margin="0,8,0,16" MinHeight="400">
|
||||
<Grid Margin="0,8,0,16" MinHeight="400">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200"/>
|
||||
<ColumnDefinition Width="*" MaxWidth="400"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0"
|
||||
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
|
||||
Background="{DynamicResource Brush.Contents}">
|
||||
|
|
|
@ -103,6 +103,15 @@ namespace SourceGit.Views
|
|||
set => SetValue(SelectedOpenAIServiceProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<Models.CustomAction> SelectedCustomActionProperty =
|
||||
AvaloniaProperty.Register<Preferences, Models.CustomAction>(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;
|
||||
|
|
|
@ -84,20 +84,12 @@
|
|||
Margin="0,0,8,0"
|
||||
Text="{DynamicResource Text.Pull.LocalChanges}"/>
|
||||
<WrapPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<RadioButton Content="{DynamicResource Text.Pull.LocalChanges.DoNothing}"
|
||||
x:Name="RadioDoNothing"
|
||||
<RadioButton GroupName="LocalChanges"
|
||||
Margin="0,0,8,0"
|
||||
GroupName="LocalChanges"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
<RadioButton Content="{DynamicResource Text.Pull.LocalChanges.StashAndReply}"
|
||||
x:Name="RadioStashAndReply"
|
||||
Margin="0,0,8,0"
|
||||
GroupName="LocalChanges"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
<RadioButton Content="{DynamicResource Text.Pull.LocalChanges.Discard}"
|
||||
x:Name="RadioDiscard"
|
||||
GroupName="LocalChanges"
|
||||
IsCheckedChanged="OnLocalChangeActionIsCheckedChanged"/>
|
||||
Content="{DynamicResource Text.Pull.LocalChanges.StashAndReply}"
|
||||
IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/>
|
||||
<RadioButton GroupName="LocalChanges"
|
||||
Content="{DynamicResource Text.Pull.LocalChanges.Discard}"/>
|
||||
</WrapPanel>
|
||||
|
||||
<CheckBox Grid.Row="4" Grid.Column="1"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 @@
|
|||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.RenameBranch}"/>
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,Auto" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
|
@ -32,6 +33,13 @@
|
|||
Text="{Binding Name, Mode=TwoWay}"
|
||||
Watermark="{DynamicResource Text.RenameBranch.Name.Placeholder}"
|
||||
v:AutoFocusBehaviour.IsEnabled="True"/>
|
||||
|
||||
<StackPanel Grid.Row="2" Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
IsVisible="{Binding Name, Converter={x:Static c:StringConverters.ContainsSpaces}}">
|
||||
<Path Width="10" Height="10" Data="{StaticResource Icons.Error}" Fill="DarkOrange"/>
|
||||
<TextBlock Classes="small" Text="{DynamicResource Text.CreateBranch.Name.WarnSpace}" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
|
|
@ -421,14 +421,20 @@
|
|||
|
||||
<Popup PlacementTarget="{Binding #TxtSearchCommitsBox}"
|
||||
Placement="BottomEdgeAlignedLeft"
|
||||
HorizontalOffset="-8" VerticalAlignment="-8"
|
||||
IsOpen="{Binding IsSearchCommitSuggestionOpen}">
|
||||
HorizontalOffset="-8" VerticalAlignment="-8">
|
||||
<Popup.IsOpen>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="MatchedFilesForSearching" Converter="{x:Static c:ListConverters.IsNotNullOrEmpty}"/>
|
||||
<Binding Path="$parent[Window].IsActive"/>
|
||||
</MultiBinding>
|
||||
</Popup.IsOpen>
|
||||
|
||||
<Border Margin="8" VerticalAlignment="Top" Effect="drop-shadow(0 0 8 #80000000)">
|
||||
<Border Background="{DynamicResource Brush.Popup}" CornerRadius="4" Padding="4" BorderThickness="0.65" BorderBrush="{DynamicResource Brush.Accent}">
|
||||
<ListBox x:Name="SearchSuggestionBox"
|
||||
Background="Transparent"
|
||||
SelectionMode="Single"
|
||||
ItemsSource="{Binding SearchCommitFilterSuggestion}"
|
||||
ItemsSource="{Binding MatchedFilesForSearching}"
|
||||
MaxHeight="400"
|
||||
Focusable="True"
|
||||
KeyDown="OnSearchSuggestionBoxKeyDown">
|
||||
|
@ -478,11 +484,11 @@
|
|||
BorderThickness="0"
|
||||
SelectedIndex="{Binding SearchCommitFilterType, Mode=TwoWay}">
|
||||
<ComboBox.Items>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.BySHA}" FontSize="12"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.ByAuthor}" FontSize="12"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.ByCommitter}" FontSize="12"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.ByMessage}" FontSize="12"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.ByFile}" FontSize="12"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.BySHA}"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.ByAuthor}"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.ByCommitter}"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.ByMessage}"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.ByFile}"/>
|
||||
</ComboBox.Items>
|
||||
</ComboBox>
|
||||
|
||||
|
@ -490,26 +496,29 @@
|
|||
Margin="4,0,0,0"
|
||||
IsChecked="{Binding OnlySearchCommitsInCurrentBranch, Mode=TwoWay}"
|
||||
IsVisible="{Binding SearchCommitFilterType, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.InCurrentBranch}" FontSize="12"/>
|
||||
<TextBlock Text="{DynamicResource Text.Repository.Search.InCurrentBranch}"/>
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
<ListBox Grid.Row="2"
|
||||
Margin="0,8,0,0"
|
||||
Padding="4"
|
||||
ItemsSource="{Binding SearchedCommits}"
|
||||
SelectionMode="Single"
|
||||
SelectedItem="{Binding SearchResultSelectedCommit, Mode=TwoWay}"
|
||||
SelectedItem="{Binding SelectedSearchedCommit, Mode=TwoWay}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
CornerRadius="4"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto">
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Grid.IsSharedSizeScope="True">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Height" Value="50"/>
|
||||
<Setter Property="Height" Value="28"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
|
@ -521,18 +530,22 @@
|
|||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<Border BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="0,0,0,1" Padding="4" Background="Transparent">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
||||
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0" ClipToBounds="True"/>
|
||||
<TextBlock Grid.Column="2" Classes="primary" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
|
||||
<TextBlock Grid.Column="3" Classes="primary" Text="{Binding AuthorTimeShortStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="24"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="SearchCommitTimeColumn"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<v:Avatar Grid.Column="0" Width="16" Height="16" HorizontalAlignment="Center" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
||||
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Subject}" Margin="2,0,0,0" VerticalAlignment="Center" TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Grid.Column="2"
|
||||
Classes="primary"
|
||||
Margin="4,0"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
Text="{Binding CommitterTimeShortStr}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="1" Classes="primary" Text="{Binding Subject}" VerticalAlignment="Bottom"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -43,8 +43,14 @@
|
|||
|
||||
<Popup PlacementTarget="{Binding #TxtSearchRevisionFiles}"
|
||||
Placement="BottomEdgeAlignedLeft"
|
||||
HorizontalOffset="-8" VerticalAlignment="-8"
|
||||
IsOpen="{Binding RevisionFileSearchSuggestion, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
|
||||
HorizontalOffset="-8" VerticalAlignment="-8">
|
||||
<Popup.IsOpen>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="RevisionFileSearchSuggestion" Converter="{x:Static c:ListConverters.IsNotNullOrEmpty}"/>
|
||||
<Binding Path="$parent[Window].IsActive"/>
|
||||
</MultiBinding>
|
||||
</Popup.IsOpen>
|
||||
|
||||
<Border Margin="8" VerticalAlignment="Top" Effect="drop-shadow(0 0 8 #80000000)">
|
||||
<Border Background="{DynamicResource Brush.Popup}" CornerRadius="4" Padding="4" BorderThickness="0.65" BorderBrush="{DynamicResource Brush.Accent}">
|
||||
<ListBox x:Name="SearchSuggestionBox"
|
||||
|
|
|
@ -203,84 +203,7 @@
|
|||
<ContentControl Content="{Binding DetailContext}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:Conflict">
|
||||
<Border Background="{DynamicResource Brush.Window}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||
<Grid VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Vertical" IsVisible="{Binding !IsResolved}">
|
||||
<StackPanel.DataTemplates>
|
||||
<DataTemplate DataType="m:Branch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding FriendlyName}"/>
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Text="{Binding Head, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
Foreground="DarkOrange"
|
||||
TextDecorations="Underline"
|
||||
Cursor="Hand"
|
||||
PointerPressed="OnPressedSHA"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="m:Commit">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Commit}"/>
|
||||
<v:CommitRefsPresenter Margin="8,0,0,0"
|
||||
TagBackground="{DynamicResource Brush.DecoratorTag}"
|
||||
Foreground="{DynamicResource Brush.FG1}"
|
||||
FontFamily="{DynamicResource Fonts.Primary}"
|
||||
FontSize="11"
|
||||
VerticalAlignment="Center"
|
||||
UseGraphColor="False"/>
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
Foreground="DarkOrange"
|
||||
TextDecorations="Underline"
|
||||
Cursor="Hand"
|
||||
PointerPressed="OnPressedSHA"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding Subject}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="x:String">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Changes}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</StackPanel.DataTemplates>
|
||||
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
<TextBlock Margin="0,16" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
|
||||
<Border Margin="16,0" Padding="8" CornerRadius="4" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
|
||||
<Border.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="Theirs" Converter="{x:Static ObjectConverters.IsNotNull}"/>
|
||||
<Binding Path="Mine" Converter="{x:Static ObjectConverters.IsNotNull}"/>
|
||||
</MultiBinding>
|
||||
</Border.IsVisible>
|
||||
|
||||
<Grid Margin="8,0,0,0" RowDefinitions="32,32" ColumnDefinitions="Auto,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" Text="THEIRS"/>
|
||||
<ContentControl Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Content="{Binding Theirs}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" Text="MINE"/>
|
||||
<ContentControl Grid.Row="1" Grid.Column="1" Margin="16,0,0,0" Content="{Binding Mine}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel Margin="0,8,0,0" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Classes="flat" Content="USE THEIRS" Command="{Binding UseTheirs}"/>
|
||||
<Button Classes="flat" Margin="8,0,0,0" Content="USE MINE" Command="{Binding UseMine}"/>
|
||||
<Button Classes="flat" Margin="8,0,0,0" Content="OPEN EXTERNAL MERGETOOL" Command="{Binding OpenExternalMergeTool}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Vertical" IsVisible="{Binding IsResolved}">
|
||||
<Path Width="64" Height="64" Data="{StaticResource Icons.Check}" Fill="Green"/>
|
||||
<TextBlock Margin="0,16,0,8" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts.Resolved}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="{DynamicResource Text.WorkingCopy.CanStageTip}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<v:Conflict/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="vm:DiffContext">
|
||||
|
|
|
@ -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<Repository>();
|
||||
if (repoView is { DataContext: ViewModels.Repository repo } && sender is TextBlock text)
|
||||
repo.NavigateToCommit(text.Text);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue