diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e856ec0..204c1afb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: - name: Publish run: dotnet publish src/SourceGit.csproj -c Release -o publish -r osx-x64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained - name: Packing Program - run: tar -cvf sourcegit.osx-x64.tar publish/ + run: tar -cvf sourcegit.osx-x64.tar -C publish/ . - name: Upload Artifact uses: actions/upload-artifact@v4 with: @@ -68,7 +68,7 @@ jobs: - name: Publish run: dotnet publish src/SourceGit.csproj -c Release -o publish -r osx-arm64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained - name: Packing Program - run: tar -cvf sourcegit.osx-arm64.tar publish/ + run: tar -cvf sourcegit.osx-arm64.tar -C publish/ . - name: Upload Artifact uses: actions/upload-artifact@v4 with: @@ -93,7 +93,7 @@ jobs: - name: Rename Executable File run: mv publish/SourceGit publish/sourcegit - name: Packing Program - run: tar -cvf sourcegit.linux-x64.tar publish/ + run: tar -cvf sourcegit.linux-x64.tar -C publish/ . - name: Upload Artifact uses: actions/upload-artifact@v4 with: diff --git a/README.md b/README.md index 9d923107..b8523562 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Opensource Git GUI client. * Supports SSH access with each remote * GIT commands with GUI * Clone/Fetch/Pull/Push... + * Merge/Rebase/Reset/Revert/Amend/Cherry-pick... + * Interactive rebase (Basic) * Branches * Remotes * Tags @@ -26,10 +28,11 @@ Opensource Git GUI client. * Blame * Revision Diffs * Branch Diff - * Image Diff + * Image Diff - Side-By-Side/Swipe/Blend * GitFlow support +* Git LFS support -> **Linux** only tested on **Ubuntu 22.04** on **X11**. +> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**. ## How to Use @@ -110,8 +113,6 @@ This app supports open repository in external tools listed in the table below. | Color.Border2 | Border color used in visual lines, like seperators, Rectange, etc. | | Color.FlatButton.Background | Flat button background color, like `Cancel`, `Commit & Push` button | | Color.FlatButton.BackgroundHovered | Flat button background color when hovered, like `Cancel` button | -| Color.FlatButton.PrimaryBackground | Primary flat button background color, like `Ok`, `Commit` button | -| Color.FlatButton.PrimaryBackgroundHovered | Primary flat button background color when hovered, like `Ok`, `Commit` button | | Color.FG1 | Primary foreground color for all text elements | | Color.FG2 | Secondary foreground color for all text elements | | Color.Diff.EmptyBG | Background color used in empty lines in diff viewer | diff --git a/VERSION b/VERSION index 19a7efe8..8162fd55 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.17 \ No newline at end of file +8.18 \ No newline at end of file diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs index af2e9913..61f00074 100644 --- a/src/App.JsonCodeGen.cs +++ b/src/App.JsonCodeGen.cs @@ -6,6 +6,7 @@ namespace SourceGit [JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)] [JsonSerializable(typeof(Models.Version))] [JsonSerializable(typeof(Models.JetBrainsState))] + [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(ViewModels.Preference))] internal partial class JsonCodeGen : JsonSerializerContext { } diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 69917120..e99f88b6 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -45,14 +45,15 @@ namespace SourceGit { try { - BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + if (args.Length > 1 && args[0].Equals("--rebase-editor", StringComparison.Ordinal)) + Environment.Exit(Models.InteractiveRebaseEditor.Process(args[1])); + else + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } catch (Exception ex) { var builder = new StringBuilder(); - builder.Append("Crash: "); - builder.Append(ex.Message); - builder.Append("\n\n"); + 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.ToString()}\n"); @@ -191,6 +192,10 @@ namespace SourceGit var fmt = Current.FindResource($"Text.{key}") as string; if (string.IsNullOrWhiteSpace(fmt)) return $"Text.{key}"; + + if (args == null || args.Length == 0) + return fmt; + return string.Format(fmt, args); } @@ -313,6 +318,18 @@ namespace SourceGit }); } + public static async Task GetClipboardTextAsync() + { + if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (desktop.MainWindow.Clipboard is { } clipboard) + { + return await clipboard.GetTextAsync(); + } + } + return default; + } + private ResourceDictionary _activeLocale = null; private ResourceDictionary _colorOverrides = null; private Models.INotificationReceiver _notificationReceiver = null; diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index c30a3d45..00f36c85 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -43,9 +43,7 @@ namespace SourceGit.Commands // Force using en_US.UTF-8 locale to avoid GCM crash if (OperatingSystem.IsLinux()) - { start.Environment.Add("LANG", "en_US.UTF-8"); - } if (!string.IsNullOrEmpty(WorkingDirectory)) start.WorkingDirectory = WorkingDirectory; diff --git a/src/Commands/Config.cs b/src/Commands/Config.cs index 62340aa3..e1016f1e 100644 --- a/src/Commands/Config.cs +++ b/src/Commands/Config.cs @@ -14,7 +14,10 @@ namespace SourceGit.Commands public Dictionary ListAll() { - Args = "config -l"; + if (string.IsNullOrEmpty(WorkingDirectory)) + Args = "config --global -l"; + else + Args = "config -l"; var output = ReadToEnd(); var rs = new Dictionary(); diff --git a/src/Commands/GitIgnore.cs b/src/Commands/GitIgnore.cs index 44bb268b..e666eba6 100644 --- a/src/Commands/GitIgnore.cs +++ b/src/Commands/GitIgnore.cs @@ -8,9 +8,9 @@ namespace SourceGit.Commands { var file = Path.Combine(repo, ".gitignore"); if (!File.Exists(file)) - File.WriteAllLines(file, [ pattern ]); + File.WriteAllLines(file, [pattern]); else - File.AppendAllLines(file, [ pattern ]); + File.AppendAllLines(file, [pattern]); } } } diff --git a/src/Commands/LFS.cs b/src/Commands/LFS.cs index 3b8a1cc2..068e8ac5 100644 --- a/src/Commands/LFS.cs +++ b/src/Commands/LFS.cs @@ -1,17 +1,22 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; namespace SourceGit.Commands { - public class LFS + public partial class LFS { - class PruneCmd : Command + [GeneratedRegex(@"^(.+)\s+(\w+)\s+\w+:(\d+)$")] + private static partial Regex REG_LOCK(); + + class SubCmd : Command { - public PruneCmd(string repo, Action onProgress) + public SubCmd(string repo, string args, Action onProgress) { WorkingDirectory = repo; Context = repo; - Args = "lfs prune"; + Args = args; TraitErrorAsOutput = true; _outputHandler = onProgress; } @@ -39,9 +44,73 @@ namespace SourceGit.Commands return content.Contains("git lfs pre-push"); } + public bool Install() + { + return new SubCmd(_repo, $"lfs install", null).Exec(); + } + + public bool Track(string pattern, bool isFilenameMode = false) + { + var opt = isFilenameMode ? "--filename" : ""; + return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", null).Exec(); + } + + public void Fetch(Action outputHandler) + { + new SubCmd(_repo, $"lfs fetch", outputHandler).Exec(); + } + + public void Pull(Action outputHandler) + { + new SubCmd(_repo, $"lfs pull", outputHandler).Exec(); + } + public void Prune(Action outputHandler) { - new PruneCmd(_repo, outputHandler).Exec(); + new SubCmd(_repo, "lfs prune", outputHandler).Exec(); + } + + public List Locks() + { + var locks = new List(); + var cmd = new SubCmd(_repo, "lfs locks", null); + var rs = cmd.ReadToEnd(); + if (rs.IsSuccess) + { + var lines = rs.StdOut.Split(new char[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + var match = REG_LOCK().Match(line); + if (match.Success) + { + locks.Add(new Models.LFSLock() + { + File = match.Groups[1].Value, + User = match.Groups[2].Value, + ID = long.Parse(match.Groups[3].Value), + }); + } + } + } + + return locks; + } + + public bool Lock(string file) + { + return new SubCmd(_repo, $"lfs lock \"{file}\"", null).Exec(); + } + + public bool Unlock(string file, bool force) + { + var opt = force ? "-f" : ""; + return new SubCmd(_repo, $"lfs unlock {opt} \"{file}\"", null).Exec(); + } + + public bool Unlock(long id, bool force) + { + var opt = force ? "-f" : ""; + return new SubCmd(_repo, $"lfs unlock {opt} --id={id}", null).Exec(); } private readonly string _repo; diff --git a/src/Commands/MergeTool.cs b/src/Commands/MergeTool.cs index 75b88bc9..d3478d59 100644 --- a/src/Commands/MergeTool.cs +++ b/src/Commands/MergeTool.cs @@ -6,57 +6,63 @@ namespace SourceGit.Commands { public static class MergeTool { - public static bool OpenForMerge(string repo, string tool, string mergeCmd, string file) + public static bool OpenForMerge(string repo, int toolType, string toolPath, string file) { - if (string.IsNullOrWhiteSpace(tool) || string.IsNullOrWhiteSpace(mergeCmd)) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, "Invalid external merge tool settings!"); - }); - return false; - } - - if (!File.Exists(tool)) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, $"Can NOT found external merge tool in '{tool}'!"); - }); - return false; - } - var cmd = new Command(); cmd.WorkingDirectory = repo; - cmd.RaiseError = false; - cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{tool}\\\" {mergeCmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit \"{file}\""; + cmd.Context = repo; + cmd.RaiseError = true; + + if (toolType == 0) + { + cmd.Args = $"mergetool \"{file}\""; + return cmd.Exec(); + } + + if (!File.Exists(toolPath)) + { + Dispatcher.UIThread.Post(() => App.RaiseException(repo, $"Can NOT found external merge tool in '{toolPath}'!")); + return false; + } + + var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType); + if (supported == null) + { + Dispatcher.UIThread.Post(() => App.RaiseException(repo, "Invalid merge tool in preference setting!")); + return false; + } + + cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit \"{file}\""; return cmd.Exec(); } - public static bool OpenForDiff(string repo, string tool, string diffCmd, Models.DiffOption option) + public static bool OpenForDiff(string repo, int toolType, string toolPath, Models.DiffOption option) { - if (string.IsNullOrWhiteSpace(tool) || string.IsNullOrWhiteSpace(diffCmd)) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, "Invalid external merge tool settings!"); - }); - return false; - } - - if (!File.Exists(tool)) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, $"Can NOT found external merge tool in '{tool}'!"); - }); - return false; - } - var cmd = new Command(); cmd.WorkingDirectory = repo; - cmd.RaiseError = false; - cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{tool}\\\" {diffCmd}\" difftool --tool=sourcegit --no-prompt {option}"; + cmd.Context = repo; + cmd.RaiseError = true; + + if (toolType == 0) + { + cmd.Args = $"difftool -g --no-prompt {option}"; + return cmd.Exec(); + } + + if (!File.Exists(toolPath)) + { + Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT found external diff tool in '{toolPath}'!")); + return false; + } + + var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType); + if (supported == null) + { + Dispatcher.UIThread.Post(() => App.RaiseException(repo, "Invalid merge tool in preference setting!")); + return false; + } + + cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.DiffCmd}\" difftool --tool=sourcegit --no-prompt {option}"; return cmd.Exec(); } } diff --git a/src/Commands/Rebase.cs b/src/Commands/Rebase.cs index d08d55ad..2576d0e6 100644 --- a/src/Commands/Rebase.cs +++ b/src/Commands/Rebase.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Diagnostics; + +namespace SourceGit.Commands { public class Rebase : Command { @@ -12,4 +14,17 @@ Args += basedOn; } } + + public class InteractiveRebase : Command + { + public InteractiveRebase(string repo, string basedOn) + { + var exec = Process.GetCurrentProcess().MainModule.FileName; + var editor = $"\\\"{exec}\\\" --rebase-editor"; + + WorkingDirectory = repo; + Context = repo; + Args = $"-c core.editor=\"{editor}\" -c sequence.editor=\"{editor}\" -c rebase.abbreviateCommands=true rebase -i --autosquash {basedOn}"; + } + } } diff --git a/src/Converters/DoubleConverters.cs b/src/Converters/DoubleConverters.cs new file mode 100644 index 00000000..5b7c0a03 --- /dev/null +++ b/src/Converters/DoubleConverters.cs @@ -0,0 +1,19 @@ +using Avalonia.Data.Converters; + +namespace SourceGit.Converters +{ + public static class DoubleConverters + { + public static readonly FuncValueConverter Increase = + new FuncValueConverter(v => v + 1.0); + + public static readonly FuncValueConverter Decrease = + new FuncValueConverter(v => v - 1.0); + + public static readonly FuncValueConverter ToPercentage = + new FuncValueConverter(v => (v * 100).ToString("F3") + "%"); + + public static readonly FuncValueConverter OneMinusToPercentage = + new FuncValueConverter(v => ((1.0 - v) * 100).ToString("F3") + "%"); + } +} diff --git a/src/Converters/FontSizeModifyConverters.cs b/src/Converters/FontSizeModifyConverters.cs deleted file mode 100644 index 4c885e38..00000000 --- a/src/Converters/FontSizeModifyConverters.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Avalonia.Data.Converters; - -namespace SourceGit.Converters -{ - public static class FontSizeModifyConverters - { - public static readonly FuncValueConverter Increase = - new FuncValueConverter(v => v + 1.0); - - public static readonly FuncValueConverter Decrease = - new FuncValueConverter(v => v - 1.0); - } -} diff --git a/src/Converters/IntConverters.cs b/src/Converters/IntConverters.cs index 8235a3ef..64f9b357 100644 --- a/src/Converters/IntConverters.cs +++ b/src/Converters/IntConverters.cs @@ -18,5 +18,11 @@ namespace SourceGit.Converters public static readonly FuncValueConverter IsNotOne = new FuncValueConverter(v => v != 1); + + public static readonly FuncValueConverter IsSubjectLengthBad = + new FuncValueConverter(v => v > ViewModels.Preference.Instance.SubjectGuideLength); + + public static readonly FuncValueConverter IsSubjectLengthGood = + new FuncValueConverter(v => v <= ViewModels.Preference.Instance.SubjectGuideLength); } } diff --git a/src/Converters/InteractiveRebaseActionConverters.cs b/src/Converters/InteractiveRebaseActionConverters.cs new file mode 100644 index 00000000..dbd183bd --- /dev/null +++ b/src/Converters/InteractiveRebaseActionConverters.cs @@ -0,0 +1,51 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace SourceGit.Converters +{ + public static class InteractiveRebaseActionConverters + { + public static readonly FuncValueConverter ToIconBrush = + new FuncValueConverter(v => + { + switch (v) + { + case Models.InteractiveRebaseAction.Pick: + return Brushes.Green; + case Models.InteractiveRebaseAction.Edit: + return Brushes.Orange; + case Models.InteractiveRebaseAction.Reword: + return Brushes.Orange; + case Models.InteractiveRebaseAction.Squash: + return Brushes.LightGray; + case Models.InteractiveRebaseAction.Fixup: + return Brushes.LightGray; + default: + return Brushes.Red; + } + }); + + public static readonly FuncValueConverter ToName = + new FuncValueConverter(v => + { + switch (v) + { + case Models.InteractiveRebaseAction.Pick: + return "Pick"; + case Models.InteractiveRebaseAction.Edit: + return "Edit"; + case Models.InteractiveRebaseAction.Reword: + return "Reword"; + case Models.InteractiveRebaseAction.Squash: + return "Squash"; + case Models.InteractiveRebaseAction.Fixup: + return "Fixup"; + default: + return "Drop"; + } + }); + + public static readonly FuncValueConverter CanEditMessage = + new FuncValueConverter(v => v == Models.InteractiveRebaseAction.Reword || v == Models.InteractiveRebaseAction.Squash); + } +} diff --git a/src/Converters/StringConverters.cs b/src/Converters/StringConverters.cs index ac1785c0..42eaa164 100644 --- a/src/Converters/StringConverters.cs +++ b/src/Converters/StringConverters.cs @@ -81,6 +81,16 @@ namespace SourceGit.Converters return true; }); + public static readonly FuncValueConverter TrimRefsPrefix = + new FuncValueConverter(v => + { + if (v.StartsWith("refs/heads/", StringComparison.Ordinal)) + return v.Substring(11); + if (v.StartsWith("refs/remotes/", StringComparison.Ordinal)) + return v.Substring(13); + return v; + }); + [GeneratedRegex(@"^[\s\w]*(\d+)\.(\d+)[\.\-](\d+).*$")] private static partial Regex REG_GIT_VERSION(); diff --git a/src/Models/DealWithLocalChanges.cs b/src/Models/DealWithLocalChanges.cs index 82609642..f308a90c 100644 --- a/src/Models/DealWithLocalChanges.cs +++ b/src/Models/DealWithLocalChanges.cs @@ -2,8 +2,8 @@ { public enum DealWithLocalChanges { + DoNothing, StashAndReaply, Discard, - DoNothing, } } diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index a0a02c6f..a625fc7b 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -587,16 +587,10 @@ namespace SourceGit.Models public string New { get; set; } = string.Empty; } - public class SubmoduleRevision - { - public Commit Commit { get; set; } = null; - public string FullMessage { get; set; } = string.Empty; - } - public class SubmoduleDiff { - public SubmoduleRevision Old { get; set; } = null; - public SubmoduleRevision New { get; set; } = null; + public RevisionSubmodule Old { get; set; } = null; + public RevisionSubmodule New { get; set; } = null; } public class DiffResult diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs index d61ce055..9a27db0b 100644 --- a/src/Models/ExternalMerger.cs +++ b/src/Models/ExternalMerger.cs @@ -32,7 +32,7 @@ namespace SourceGit.Models if (OperatingSystem.IsWindows()) { Supported = new List() { - new ExternalMerger(0, "custom_diff", "Custom", "", "", ""), + new ExternalMerger(0, "git", "Use Git Settings", "", "", ""), new ExternalMerger(1, "vscode", "Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(2, "vscode_insiders", "Visual Studio Code - Insiders", "Code - Insiders.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(3, "vs", "Visual Studio", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" /m", "\"$LOCAL\" \"$REMOTE\""), @@ -41,36 +41,39 @@ namespace SourceGit.Models new ExternalMerger(6, "beyond_compare", "Beyond Compare", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(7, "win_merge", "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(8, "codium", "VSCodium", "VSCodium.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(9, "p4merge", "P4Merge", "p4merge.exe", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""), }; } else if (OperatingSystem.IsMacOS()) { Supported = new List() { - new ExternalMerger(0, "custom_diff", "Custom", "", "", ""), + new ExternalMerger(0, "git", "Use Git Settings", "", "", ""), new ExternalMerger(1, "xcode", "FileMerge", "/usr/bin/opendiff", "\"$BASE\" \"$LOCAL\" \"$REMOTE\" -ancestor \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(2, "vscode", "Visual Studio Code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(3, "vscode_insiders", "Visual Studio Code - Insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(4, "kdiff3", "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(5, "beyond_compare", "Beyond Compare", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(6, "codium", "VSCodium", "/Applications/VSCodium.app/Contents/Resources/app/bin/codium", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(7, "p4merge", "P4Merge", "/Applications/p4merge.app/Contents/Resources/launchp4merge", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""), }; } else if (OperatingSystem.IsLinux()) { Supported = new List() { - new ExternalMerger(0, "custom_diff", "Custom", "", "", ""), + new ExternalMerger(0, "git", "Use Git Settings", "", "", ""), new ExternalMerger(1, "vscode", "Visual Studio Code", "/usr/share/code/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(2, "vscode_insiders", "Visual Studio Code - Insiders", "/usr/share/code-insiders/code-insiders", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(3, "kdiff3", "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(4, "beyond_compare", "Beyond Compare", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(5, "meld", "Meld", "/usr/bin/meld", "\"$LOCAL\" \"$BASE\" \"$REMOTE\" -output \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(6, "codium", "VSCodium", "/usr/share/codium/bin/codium", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(7, "p4merge", "P4Merge", "/usr/local/bin/p4merge", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""), }; } else { Supported = new List() { - new ExternalMerger(0, "custom_diff", "Custom", "", "", ""), + new ExternalMerger(0, "git", "Use Git Settings", "", "", ""), }; } } diff --git a/src/Models/InteractiveRebaseEditor.cs b/src/Models/InteractiveRebaseEditor.cs new file mode 100644 index 00000000..911258d4 --- /dev/null +++ b/src/Models/InteractiveRebaseEditor.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; + +namespace SourceGit.Models +{ + public enum InteractiveRebaseAction + { + Pick, + Edit, + Reword, + Squash, + Fixup, + Drop, + } + + public class InteractiveRebaseJob + { + public string SHA { get; set; } = string.Empty; + public InteractiveRebaseAction Action { get; set; } = InteractiveRebaseAction.Pick; + public string Message { get; set; } = string.Empty; + } + + public static class InteractiveRebaseEditor + { + public static int Process(string file) + { + try + { + var filename = Path.GetFileName(file); + if (filename.Equals("git-rebase-todo", StringComparison.OrdinalIgnoreCase)) + { + var dirInfo = new DirectoryInfo(Path.GetDirectoryName(file)); + if (!dirInfo.Exists || !dirInfo.Name.Equals("rebase-merge", StringComparison.Ordinal)) + return -1; + + var jobsFile = Path.Combine(dirInfo.Parent.FullName, "sourcegit_rebase_jobs.json"); + if (!File.Exists(jobsFile)) + return -1; + + var jobs = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.ListInteractiveRebaseJob); + var lines = new List(); + foreach (var job in jobs) + { + switch (job.Action) + { + case InteractiveRebaseAction.Pick: + lines.Add($"p {job.SHA}"); + break; + case InteractiveRebaseAction.Edit: + lines.Add($"e {job.SHA}"); + break; + case InteractiveRebaseAction.Reword: + lines.Add($"r {job.SHA}"); + break; + case InteractiveRebaseAction.Squash: + lines.Add($"s {job.SHA}"); + break; + case InteractiveRebaseAction.Fixup: + lines.Add($"f {job.SHA}"); + break; + default: + lines.Add($"d {job.SHA}"); + break; + } + } + + File.WriteAllLines(file, lines); + } + else if (filename.Equals("COMMIT_EDITMSG", StringComparison.OrdinalIgnoreCase)) + { + var jobsFile = Path.Combine(Path.GetDirectoryName(file), "sourcegit_rebase_jobs.json"); + if (!File.Exists(jobsFile)) + return 0; + + var jobs = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.ListInteractiveRebaseJob); + var doneFile = Path.Combine(Path.GetDirectoryName(file), "rebase-merge", "done"); + if (!File.Exists(doneFile)) + return -1; + + var done = File.ReadAllText(doneFile).Split(new char[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries); + if (done.Length > jobs.Count) + return -1; + + var job = jobs[done.Length - 1]; + File.WriteAllText(file, job.Message); + } + + return 0; + } + catch + { + return -1; + } + } + } +} diff --git a/src/Models/LFSLock.cs b/src/Models/LFSLock.cs new file mode 100644 index 00000000..0a328cfb --- /dev/null +++ b/src/Models/LFSLock.cs @@ -0,0 +1,9 @@ +namespace SourceGit.Models +{ + public class LFSLock + { + public string File { get; set; } = string.Empty; + public string User { get; set; } = string.Empty; + public long ID { get; set; } = 0; + } +} diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs index 75ca961f..29c10d51 100644 --- a/src/Models/Remote.cs +++ b/src/Models/Remote.cs @@ -1,16 +1,20 @@ -using System.Text.RegularExpressions; +using System; +using System.Text.RegularExpressions; namespace SourceGit.Models { public partial class Remote { - [GeneratedRegex(@"^http[s]?://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/]+/[\w\-\.]+(\.git)?$")] + [GeneratedRegex(@"^http[s]?://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/~]+/[\w\-\.]+(\.git)?$")] private static partial Regex REG_HTTPS(); - [GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:[\w\-/]+/[\w\-\.]+(\.git)?$")] + [GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:[\w\-/~]+/[\w\-\.]+(\.git)?$")] private static partial Regex REG_SSH1(); - [GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/]+/[\w\-\.]+(\.git)?$")] + [GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/~]+/[\w\-\.]+(\.git)?$")] private static partial Regex REG_SSH2(); + [GeneratedRegex(@"^git@([\w\.\-]+):([\w\-/~]+/[\w\-\.]+)\.git$")] + private static partial Regex REG_TO_VISIT_URL_CAPTURE(); + private static readonly Regex[] URL_FORMATS = [ REG_HTTPS(), REG_SSH1(), @@ -43,5 +47,29 @@ namespace SourceGit.Models } return false; } + + public bool TryGetVisitURL(out string url) + { + url = null; + + if (URL.StartsWith("http", StringComparison.Ordinal)) + { + if (URL.EndsWith(".git")) + url = URL.Substring(0, URL.Length - 4); + else + url = URL; + + return true; + } + + var match = REG_TO_VISIT_URL_CAPTURE().Match(URL); + if (match.Success) + { + url = $"https://{match.Groups[1].Value}/{match.Groups[2].Value}"; + return true; + } + + return false; + } } } diff --git a/src/Models/RevisionFile.cs b/src/Models/RevisionFile.cs index b918e2e7..59868fcc 100644 --- a/src/Models/RevisionFile.cs +++ b/src/Models/RevisionFile.cs @@ -25,6 +25,7 @@ namespace SourceGit.Models public class RevisionSubmodule { - public string SHA { get; set; } + public Commit Commit { get; set; } = null; + public string FullMessage { get; set; } = string.Empty; } } diff --git a/src/Resources/ExternalToolIcons/custom_diff.png b/src/Resources/ExternalToolIcons/custom_diff.png deleted file mode 100644 index 8d046a2f..00000000 Binary files a/src/Resources/ExternalToolIcons/custom_diff.png and /dev/null differ diff --git a/src/Resources/ExternalToolIcons/git.png b/src/Resources/ExternalToolIcons/git.png new file mode 100644 index 00000000..cbb908b9 Binary files /dev/null and b/src/Resources/ExternalToolIcons/git.png differ diff --git a/src/Resources/ExternalToolIcons/p4merge.png b/src/Resources/ExternalToolIcons/p4merge.png new file mode 100644 index 00000000..010d8f6f Binary files /dev/null and b/src/Resources/ExternalToolIcons/p4merge.png differ diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 32124080..8574c130 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -12,12 +12,10 @@ M576 832C576 867 547 896 512 896 477 896 448 867 448 832 448 797 477 768 512 768 547 768 576 797 576 832ZM512 256C477 256 448 285 448 320L448 640C448 675 477 704 512 704 547 704 576 675 576 640L576 320C576 285 547 256 512 256ZM1024 896C1024 967 967 1024 896 1024L128 1024C57 1024 0 967 0 896 0 875 5 855 14 837L14 837 398 69 398 69C420 28 462 0 512 0 562 0 604 28 626 69L1008 835C1018 853 1024 874 1024 896ZM960 896C960 885 957 875 952 865L952 864 951 863 569 98C557 77 536 64 512 64 488 64 466 77 455 99L452 105 92 825 93 825 71 867C66 876 64 886 64 896 64 931 93 960 128 960L896 960C931 960 960 931 960 896Z M608 0q48 0 88 23t63 63 23 87v70h55q35 0 67 14t57 38 38 57 14 67V831q0 34-14 66t-38 57-57 38-67 13H426q-34 0-66-13t-57-38-38-57-14-66v-70h-56q-34 0-66-14t-57-38-38-57-13-67V174q0-47 23-87T109 23 196 0h412m175 244H426q-46 0-86 22T278 328t-26 85v348H608q47 0 86-22t63-62 25-85l1-348m-269 318q18 0 31 13t13 31-13 31-31 13-31-13-13-31 13-31 31-13m0-212q13 0 22 9t11 22v125q0 14-9 23t-22 10-23-7-11-22l-1-126q0-13 10-23t23-10z m186 532 287 0 0 287c0 11 9 20 20 20s20-9 20-20l0-287 287 0c11 0 20-9 20-20s-9-20-20-20l-287 0 0-287c0-11-9-20-20-20s-20 9-20 20l0 287-287 0c-11 0-20 9-20 20s9 20 20 20z - M716.3 383.1c0 38.4-6.5 76-19.4 111.8l-10.7 29.7 229.6 229.5c44.5 44.6 44.5 117.1 0 161.6a113.6 113.6 0 01-80.8 33.5a113.6 113.6 0 01-80.8-33.5L529 694l-32 13a331.6 331.6 0 01-111.9 19.4A333.5 333.5 0 0150 383.1c0-39 6.8-77.2 20-113.6L285 482l194-195-214-210A331 331 0 01383.1 50A333.5 333.5 0 01716.3 383.1zM231.6 31.6l-22.9 9.9a22.2 22.2 0 00-5.9 4.2a19.5 19.5 0 000 27.5l215 215.2L288.4 417.8 77.8 207.1a26 26 0 00-17.2-7.1a22.8 22.8 0 00-21.5 15a400.5 400.5 0 00-7.5 16.6A381.6 381.6 0 000 384c0 211.7 172.2 384 384 384c44.3 0 87.6-7.5 129-22.3L743.1 975.8A163.5 163.5 0 00859.5 1024c43.9 0 85.3-17.1 116.4-48.2a164.8 164.8 0 000-233L745.5 513C760.5 471.5 768 428 768 384C768 172 596 0 384 0c-53 0-104 10.5-152.5 31.5z - M928 500a21 21 0 00-19-20L858 472a11 11 0 01-9-9c-1-6-2-13-3-19a11 11 0 015-12l46-25a21 21 0 0010-26l-8-22a21 21 0 00-24-13l-51 10a11 11 0 01-12-6c-3-6-6-11-10-17a11 11 0 011-13l34-39a21 21 0 001-28l-15-18a20 20 0 00-27-4l-45 27a11 11 0 01-13-1c-5-4-10-9-15-12a11 11 0 01-3-12l19-49a21 21 0 00-9-26l-20-12a21 21 0 00-27 6L650 193a9 9 0 01-11 3c-1-1-12-5-20-7a11 11 0 01-7-10l1-52a21 21 0 00-17-22l-23-4a21 21 0 00-24 14L532 164a11 11 0 01-11 7h-20a11 11 0 01-11-7l-17-49a21 21 0 00-24-15l-23 4a21 21 0 00-17 22l1 52a11 11 0 01-8 11c-5 2-15 6-19 7c-4 1-8 0-12-4l-33-40A21 21 0 00313 146l-20 12A21 21 0 00285 184l19 49a11 11 0 01-3 12c-5 4-10 8-15 12a11 11 0 01-13 1L228 231a21 21 0 00-27 4L186 253a21 21 0 001 28L221 320a11 11 0 011 13c-3 5-7 11-10 17a11 11 0 01-12 6l-51-10a21 21 0 00-24 13l-8 22a21 21 0 0010 26l46 25a11 11 0 015 12l0 3c-1 6-2 11-3 16a11 11 0 01-9 9l-51 8A21 21 0 0096 500v23A21 21 0 00114 544l51 8a11 11 0 019 9c1 6 2 13 3 19a11 11 0 01-5 12l-46 25a21 21 0 00-10 26l8 22a21 21 0 0024 13l51-10a11 11 0 0112 6c3 6 6 11 10 17a11 11 0 01-1 13l-34 39a21 21 0 00-1 28l15 18a20 20 0 0027 4l45-27a11 11 0 0113 1c5 4 10 9 15 12a11 11 0 013 12l-19 49a21 21 0 009 26l20 12a21 21 0 0027-6L374 832c3-3 7-5 10-4c7 3 12 5 20 7a11 11 0 018 10l-1 52a21 21 0 0017 22l23 4a21 21 0 0024-14l17-50a11 11 0 0111-7h20a11 11 0 0111 7l17 49a21 21 0 0020 15a19 19 0 004 0l23-4a21 21 0 0017-22l-1-52a11 11 0 018-10c8-3 13-5 18-7l1 0c6-2 9 0 11 3l34 41A21 21 0 00710 878l20-12a21 21 0 009-26l-18-49a11 11 0 013-12c5-4 10-8 15-12a11 11 0 0113-1l45 27a21 21 0 0027-4l15-18a21 21 0 00-1-28l-34-39a11 11 0 01-1-13c3-5 7-11 10-17a11 11 0 0112-6l51 10a21 21 0 0024-13l8-22a21 21 0 00-10-26l-46-25a11 11 0 01-5-12l0-3c1-6 2-11 3-16a11 11 0 019-9l51-8a21 21 0 0018-21v-23zm-565 188a32 32 0 01-51 5a270 270 0 011-363a32 32 0 0151 6l91 161a32 32 0 010 31zM512 782a270 270 0 01-57-6a32 32 0 01-20-47l92-160a32 32 0 0127-16h184a32 32 0 0130 41c-35 109-137 188-257 188zm15-328L436 294a32 32 0 0121-47a268 268 0 0155-6c120 0 222 79 257 188a32 32 0 01-30 41h-184a32 32 0 01-28-16z + M928 500a21 21 0 00-19-20L858 472a11 11 0 01-9-9c-1-6-2-13-3-19a11 11 0 015-12l46-25a21 21 0 0010-26l-8-22a21 21 0 00-24-13l-51 10a11 11 0 01-12-6c-3-6-6-11-10-17a11 11 0 011-13l34-39a21 21 0 001-28l-15-18a20 20 0 00-27-4l-45 27a11 11 0 01-13-1c-5-4-10-9-15-12a11 11 0 01-3-12l19-49a21 21 0 00-9-26l-20-12a21 21 0 00-27 6L650 193a9 9 0 01-11 3c-1-1-12-5-20-7a11 11 0 01-7-10l1-52a21 21 0 00-17-22l-23-4a21 21 0 00-24 14L532 164a11 11 0 01-11 7h-20a11 11 0 01-11-7l-17-49a21 21 0 00-24-15l-23 4a21 21 0 00-17 22l1 52a11 11 0 01-8 11c-5 2-15 6-19 7c-4 1-8 0-12-4l-33-40A21 21 0 00313 146l-20 12A21 21 0 00285 184l19 49a11 11 0 01-3 12c-5 4-10 8-15 12a11 11 0 01-13 1L228 231a21 21 0 00-27 4L186 253a21 21 0 001 28L221 320a11 11 0 011 13c-3 5-7 11-10 17a11 11 0 01-12 6l-51-10a21 21 0 00-24 13l-8 22a21 21 0 0010 26l46 25a11 11 0 015 12l0 3c-1 6-2 11-3 16a11 11 0 01-9 9l-51 8A21 21 0 0096 500v23A21 21 0 00114 544l51 8a11 11 0 019 9c1 6 2 13 3 19a11 11 0 01-5 12l-46 25a21 21 0 00-10 26l8 22a21 21 0 0024 13l51-10a11 11 0 0112 6c3 6 6 11 10 17a11 11 0 01-1 13l-34 39a21 21 0 00-1 28l15 18a20 20 0 0027 4l45-27a11 11 0 0113 1c5 4 10 9 15 12a11 11 0 013 12l-19 49a21 21 0 009 26l20 12a21 21 0 0027-6L374 832c3-3 7-5 10-4c7 3 12 5 20 7a11 11 0 018 10l-1 52a21 21 0 0017 22l23 4a21 21 0 0024-14l17-50a11 11 0 0111-7h20a11 11 0 0111 7l17 49a21 21 0 0020 15a19 19 0 004 0l23-4a21 21 0 0017-22l-1-52a11 11 0 018-10c8-3 13-5 18-7l1 0c6-2 9 0 11 3l34 41A21 21 0 00710 878l20-12a21 21 0 009-26l-18-49a11 11 0 013-12c5-4 10-8 15-12a11 11 0 0113-1l45 27a21 21 0 0027-4l15-18a21 21 0 00-1-28l-34-39a11 11 0 01-1-13c3-5 7-11 10-17a11 11 0 0112-6l51 10a21 21 0 0024-13l8-22a21 21 0 00-10-26l-46-25a11 11 0 01-5-12l0-3c1-6 2-11 3-16a11 11 0 019-9l51-8a21 21 0 0018-21v-23zm-565 188a32 32 0 01-51 5a270 270 0 011-363a32 32 0 0151 6l91 161a32 32 0 010 31zM512 782a270 270 0 01-57-6a32 32 0 01-20-47l92-160a32 32 0 0127-16h184a32 32 0 0130 41c-35 109-137 188-257 188zm15-328L436 294a32 32 0 0121-47a268 268 0 0155-6c120 0 222 79 257 188a32 32 0 01-30 41h-184a32 32 0 01-28-16z M512 0C229 0 0 229 0 512s229 512 512 512 512-229 512-512S795 0 512 0zM512 928c-230 0-416-186-416-416S282 96 512 96s416 186 416 416S742 928 512 928zM538 343c47 0 83-38 83-78 0-32-21-61-62-61-55 0-82 45-82 77C475 320 498 343 538 343zM533 729c-8 0-11-10-3-40l43-166c16-61 11-100-22-100-39 0-131 40-211 108l16 27c25-17 68-35 78-35 8 0 7 10 0 36l-38 158c-23 89 1 110 34 110 33 0 118-30 196-110l-19-25C575 717 543 729 533 729z M702 677 590 565a148 148 0 10-25 27L676 703zm-346-200a115 115 0 11115 115A115 115 0 01355 478z M512 57c251 0 455 204 455 455S763 967 512 967 57 763 57 512 261 57 512 57zm181 274c-11-11-29-11-40 0L512 472 371 331c-11-11-29-11-40 0-11 11-11 29 0 40L471 512 331 653c-11 11-11 29 0 40 11 11 29 11 40 0l141-141 141 141c11 11 29 11 40 0 11-11 11-29 0-40L552 512l141-141c11-11 11-29 0-40z - M899 870l-53-306H864c14 0 26-12 26-26V346c0-14-12-26-26-26H618V138c0-14-12-26-26-26H432c-14 0-26 12-26 26v182H160c-14 0-26 12-26 26v192c0 14 12 26 26 26h18l-53 306c0 2 0 3 0 4c0 14 12 26 26 26h723c2 0 3 0 4 0c14-2 24-16 21-30zM204 390h272V182h72v208h272v104H204V390zm468 440V674c0-4-4-8-8-8h-48c-4 0-8 4-8 8v156H416V674c0-4-4-8-8-8h-48c-4 0-8 4-8 8v156H203l45-260H776l45 260H672z M960 146v91C960 318 759 384 512 384S64 318 64 238V146C64 66 265 0 512 0s448 66 448 146zM960 352v206C960 638 759 704 512 704S64 638 64 558V352c96 66 272 97 448 97S864 418 960 352zm0 320v206C960 958 759 1024 512 1024S64 958 64 878V672c96 66 272 97 448 97S864 738 960 672z M800 928l-512 0 0-704 224 0 0 292 113-86 111 86 0-292 128 0 0 640c0 35-29 64-64 64zM625 388l-81 64 0-260 160 0 0 260-79-64zM192 160l0 32c0 18 14 32 32 32l32 0 0 704-32 0c-35 0-64-29-64-64l0-704c0-35 29-64 64-64l576 0c24 0 44 13 55 32l-631 0c-18 0-32 14-32 32z M64 864h896V288h-396a64 64 0 01-57-35L460 160H64v704zm-64 32V128a32 32 0 0132-32h448a32 32 0 0129 18L564 224H992a32 32 0 0132 32v640a32 32 0 01-32 32H32a32 32 0 01-32-32z @@ -61,7 +59,6 @@ M683 537h-144v-142h-142V283H239a44 44 0 00-41 41v171a56 56 0 0014 34l321 321a41 41 0 0058 0l174-174a41 41 0 000-58zm-341-109a41 41 0 110-58a41 41 0 010 58zM649 284V142h-69v142h-142v68h142v142h69v-142h142v-68h-142z M557.7 545.3 789.9 402.7c24-15 31.3-46.5 16.4-70.5c-14.8-23.8-46-31.2-70-16.7L506.5 456.6 277.1 315.4c-24.1-14.8-55.6-7.3-70.5 16.8c-14.8 24.1-7.3 55.6 16.8 70.5l231.8 142.6V819.1c0 28.3 22.9 51.2 51.2 51.2c28.3 0 51.2-22.9 51.2-51.2V545.3h.1zM506.5 0l443.4 256v511.9L506.5 1023.9 63.1 767.9v-511.9L506.5 0z M770 320a41 41 0 00-56-14l-252 153L207 306a41 41 0 10-43 70l255 153 2 296a41 41 0 0082 0l-2-295 255-155a41 41 0 0014-56zM481 935a42 42 0 01-42 0L105 741a42 42 0 01-21-36v-386a42 42 0 0121-36L439 89a42 42 0 0142 0l335 193a42 42 0 0121 36v87h84v-87a126 126 0 00-63-109L523 17a126 126 0 00-126 0L63 210a126 126 0 00-63 109v386a126 126 0 0063 109l335 193a126 126 0 00126 0l94-54-42-72zM1029 700h-126v-125a42 42 0 00-84 0v126h-126a42 42 0 000 84h126v126a42 42 0 1084 0v-126h126a42 42 0 000-84z - M170 470l0 84 86 0 0-84-86 0zM86 598l0-172 852 0 0 172-852 0zM256 298l0-84-86 0 0 84 86 0zM86 170l852 0 0 172-852 0 0-172zM170 726l0 84 86 0 0-84-86 0zM86 854l0-172 852 0 0 172-852 0z M812 864h-29V654c0-21-11-40-28-52l-133-88 134-89c18-12 28-31 28-52V164h28c18 0 32-14 32-32s-14-32-32-32H212c-18 0-32 14-32 32s14 32 32 32h30v210c0 21 11 40 28 52l133 88-134 89c-18 12-28 31-28 52V864H212c-18 0-32 14-32 32s14 32 32 32h600c18 0 32-14 32-32s-14-32-32-32zM441 566c18-12 28-31 28-52s-11-40-28-52L306 373V164h414v209l-136 90c-18 12-28 31-28 52 0 21 11 40 28 52l135 89V695c-9-7-20-13-32-19-30-15-93-41-176-41-63 0-125 14-175 38-12 6-22 12-31 18v-36l136-90z M512 0C233 0 7 223 0 500C6 258 190 64 416 64c230 0 416 200 416 448c0 53 43 96 96 96s96-43 96-96c0-283-229-512-512-512zm0 1023c279 0 505-223 512-500c-6 242-190 436-416 436c-230 0-416-200-416-448c0-53-43-96-96-96s-96 43-96 96c0 283 229 512 512 512z M888.8 0H135.2c-32.3 0-58.9 26.1-58.9 58.9v906.2c0 32.3 26.1 58.9 58.9 58.9h753.2c32.3 0 58.9-26.1 58.9-58.9v-906.2c.5-32.8-26.1-58.9-58.4-58.9zm-164.9 176.6c30.7 0 55.8 25.1 55.8 55.8s-25.1 55.8-55.8 55.8s-55.8-25.1-55.8-55.8s24.6-55.8 55.8-55.8zm-212 0c30.7 0 55.8 25.1 55.8 55.8S542.7 288.3 512 288.3s-55.8-25.1-55.8-55.8S481.3 176.6 512 176.6zm-212 0c30.7 0 55.8 25.1 55.8 55.8s-25.1 55.8-55.8 55.8s-55.8-25.1-55.8-55.8s25.1-55.8 55.8-55.8zm208.9 606.2H285.2c-24.6 0-44-20-44-44c0-24.6 20-44 44-44h223.7c24.6 0 44 20 44 44c0 24.1-19.5 44-44 44zm229.9-212H285.2c-24.6 0-44-20-44-44c0-24.6 20-44 44-44h453.1c24.6 0 44 20 44 44c.5 24.1-19.5 44-43.5 44z @@ -90,7 +87,7 @@ M765 118 629 239l-16 137-186 160 54 59 183-168 144 4 136-129 47-43-175-12L827 67zM489 404c-66 0-124 55-124 125s54 121 124 121c66 0 120-55 120-121H489l23-121c-8-4-16-4-23-4zM695 525c0 114-93 207-206 207s-206-94-206-207 93-207 206-207c16 0 27 0 43 4l43-207c-27-4-54-8-85-8-229 0-416 188-416 419s187 419 416 419c225 0 408-180 416-403v-12l-210-4z M973 358a51 51 0 0151 51v563a51 51 0 01-51 51H51a51 51 0 01-51-51V410a51 51 0 0151-51h256a51 51 0 110 102H102v461h819V461h-205a51 51 0 110-102h256zM51 102a51 51 0 110-102h256c141 0 256 115 256 256v388l66-66a51 51 0 1172 72l-154 154a51 51 0 01-72 0l-154-154a51 51 0 1172-72L461 644V256c0-85-69-154-154-154H51z M976 0h-928A48 48 0 000 48v652a48 48 0 0048 48h416V928H200a48 48 0 000 96h624a48 48 0 000-96H560v-180h416a48 48 0 0048-48V48A48 48 0 00976 0zM928 652H96V96h832v556z - M412 66C326 132 271 233 271 347c0 17 1 34 4 50-41-48-98-79-162-83a444 444 0 00-46 196c0 207 142 382 337 439h2c19 0 34 15 34 33 0 11-6 21-14 26l1 14C183 973 0 763 0 511 0 272 166 70 393 7A35 35 0 01414 0c19 0 34 15 34 33a33 33 0 01-36 33zm200 893c86-66 141-168 141-282 0-17-1-34-4-50 41 48 98 79 162 83a444 444 0 0046-196c0-207-142-382-337-439h-2a33 33 0 01-34-33c0-11 6-21 14-26L596 0C841 51 1024 261 1024 513c0 239-166 441-393 504A35 35 0 01610 1024a33 33 0 01-34-33 33 33 0 0136-33zM512 704a192 192 0 110-384 192 192 0 010 384z + M412 66C326 132 271 233 271 347c0 17 1 34 4 50-41-48-98-79-162-83a444 444 0 00-46 196c0 207 142 382 337 439h2c19 0 34 15 34 33 0 11-6 21-14 26l1 14C183 973 0 763 0 511 0 272 166 70 393 7A35 35 0 01414 0c19 0 34 15 34 33a33 33 0 01-36 33zm200 893c86-66 141-168 141-282 0-17-1-34-4-50 41 48 98 79 162 83a444 444 0 0046-196c0-207-142-382-337-439h-2a33 33 0 01-34-33c0-11 6-21 14-26L596 0C841 51 1024 261 1024 513c0 239-166 441-393 504A35 35 0 01610 1024a33 33 0 01-34-33 33 33 0 0136-33zM512 704a192 192 0 110-384 192 192 0 010 384z M939 94v710L512 998 85 805V94h-64A21 21 0 010 73v-0C0 61 10 51 21 51h981c12 0 21 10 21 21v0c0 12-10 21-21 21h-64zm-536 588L512 624l109 58c6 3 13 4 20 3a32 32 0 0026-37l-21-122 88-87c5-5 8-11 9-18a32 32 0 00-27-37l-122-18-54-111a32 32 0 00-57 0l-54 111-122 18c-7 1-13 4-18 9a33 33 0 001 46l88 87-21 122c-1 7-0 14 3 20a32 32 0 0043 14z M236 542a32 32 0 109 63l86-12a180 180 0 0022 78l-71 47a32 32 0 1035 53l75-50a176 176 0 00166 40L326 529zM512 16C238 16 16 238 16 512s222 496 496 496 496-222 496-496S786 16 512 16zm0 896c-221 0-400-179-400-400a398 398 0 0186-247l561 561A398 398 0 01512 912zm314-154L690 622a179 179 0 004-29l85 12a32 32 0 109-63l-94-13v-49l94-13a32 32 0 10-9-63l-87 12a180 180 0 00-20-62l71-47A32 32 0 10708 252l-75 50a181 181 0 00-252 10l-115-115A398 398 0 01512 112c221 0 400 179 400 400a398 398 0 01-86 247z M884 159l-18-18a43 43 0 00-38-12l-235 43a166 166 0 00-101 60L400 349a128 128 0 00-148 47l-120 171a21 21 0 005 29l17 12a128 128 0 00178-32l27-38 124 124-38 27a128 128 0 00-32 178l12 17a21 21 0 0029 5l171-120a128 128 0 0047-148l117-92A166 166 0 00853 431l43-235a43 43 0 00-12-38zm-177 249a64 64 0 110-90 64 64 0 010 90zm-373 312a21 21 0 010 30l-139 139a21 21 0 01-30 0l-30-30a21 21 0 010-30l139-139a21 21 0 0130 0z @@ -100,4 +97,10 @@ M248 221a77 77 0 00-30-21c-18-7-40-10-68-5a224 224 0 00-45 13c-5 2-10 5-15 8l-3 2v68l11-9c10-8 21-14 34-19 13-5 26-7 39-7 12 0 21 3 28 10 6 6 9 16 9 29l-62 9c-14 2-26 6-36 11a80 80 0 00-25 20c-7 8-12 17-15 27-6 21-6 44 1 65a70 70 0 0041 43c10 4 21 6 34 6a80 80 0 0063-28v22h64V298c0-16-2-31-6-44a91 91 0 00-18-33zm-41 121v15c0 8-1 15-4 22a48 48 0 01-24 29 44 44 0 01-33 2 29 29 0 01-10-6 25 25 0 01-6-9 30 30 0 01-2-12c0-5 1-9 2-14a21 21 0 015-9 28 28 0 0110-7 83 83 0 0120-5l42-6zm323-68a144 144 0 00-16-42 87 87 0 00-28-29 75 75 0 00-41-11 73 73 0 00-44 14c-6 5-12 11-17 17V64H326v398h59v-18c8 10 18 17 30 21 6 2 13 3 21 3 16 0 31-4 43-11 12-7 23-18 31-31a147 147 0 0019-46 248 248 0 006-57c0-17-2-33-5-49zm-55 49c0 15-1 28-4 39-2 11-6 20-10 27a41 41 0 01-15 15 37 37 0 01-36 1 44 44 0 01-13-12 59 59 0 01-9-18A76 76 0 01384 352v-33c0-10 1-20 4-29 2-8 6-15 10-22a43 43 0 0115-13 37 37 0 0119-5 35 35 0 0132 18c4 6 7 14 9 23 2 9 3 20 3 31zM154 634a58 58 0 0120-15c14-6 35-7 49-1 7 3 13 6 20 12l21 17V572l-6-4a124 124 0 00-58-14c-20 0-38 4-54 11-16 7-30 17-41 30-12 13-20 29-26 46-6 17-9 36-9 57 0 18 3 36 8 52 6 16 14 30 24 42 10 12 23 21 38 28 15 7 32 10 50 10 15 0 28-2 39-5 11-3 21-8 30-14l5-4v-57l-13 6a26 26 0 01-5 2c-3 1-6 2-8 3-2 1-15 6-15 6-4 2-9 3-14 4a63 63 0 01-38-4 53 53 0 01-20-14 70 70 0 01-13-24 111 111 0 01-5-34c0-13 2-26 5-36 3-10 8-19 14-26zM896 384h-256V320h288c21 1 32 12 32 32v384c0 18-12 32-32 32H504l132 133-45 45-185-185c-16-21-16-25 0-45l185-185L637 576l-128 128H896V384z M128 183C128 154 154 128 183 128h521c30 0 55 26 55 55v38c0 17-17 34-34 34s-34-17-34-34v-26H196v495h26c17 0 34 17 34 34s-17 34-34 34h-38c-30 0-55-26-55-55V183zM380 896h-34c-26 0-47-21-47-47v-90h68V828h64V896H380c4 0 0 0 0 0zM759 828V896h90c26 0 47-21 47-47v-90h-68V828h-68zM828 435H896V346c0-26-21-47-47-47h-90v68H828v68zM435 299v68H367V439H299V346C299 320 320 299 346 299h90zM367 649H299v-107h68v107zM546 367V299h107v68h-107zM828 546H896v107h-68v-107zM649 828V896h-107v-68h107zM730 508v188c0 17-17 34-34 34h-188c-17 0-34-17-34-34s17-34 34-34h102l-124-124c-13-13-13-34 0-47 13-13 34-13 47 0l124 124V512c0-17 17-34 34-34 21-4 38 9 38 30z M590 74 859 342V876c0 38-31 68-68 68H233c-38 0-68-31-68-68V142c0-38 31-68 68-68h357zm-12 28H233a40 40 0 00-40 38L193 142v734a40 40 0 0038 40L233 916h558a40 40 0 0040-38L831 876V354L578 102zM855 371h-215c-46 0-83-36-84-82l0-2V74h28v213c0 30 24 54 54 55l2 0h215v28zM57 489m28 0 853 0q28 0 28 28l0 284q0 28-28 28l-853 0q-28 0-28-28l0-284q0-28 28-28ZM157 717c15 0 29-6 37-13v-51h-41v22h17v18c-2 2-6 3-10 3-21 0-30-13-30-34 0-21 12-34 28-34 9 0 15 4 20 9l14-17C184 610 172 603 156 603c-29 0-54 21-54 57 0 37 24 56 54 56zM245 711v-108h-34v108h34zm69 0v-86H341V603H262v22h28V711h24zM393 711v-108h-34v108h34zm66 6c15 0 29-6 37-13v-51h-41v22h17v18c-2 2-6 3-10 3-21 0-30-13-30-34 0-21 12-34 28-34 9 0 15 4 20 9l14-17C485 610 474 603 458 603c-29 0-54 21-54 57 0 37 24 56 54 56zm88-6v-36c0-13-2-28-3-40h1l10 24 25 52H603v-108h-23v36c0 13 2 28 3 40h-1l-10-24L548 603H523v108h23zM677 717c30 0 51-22 51-57 0-36-21-56-51-56-30 0-51 20-51 56 0 36 21 57 51 57zm3-23c-16 0-26-12-26-32 0-19 10-31 26-31 16 0 26 11 26 31S696 694 680 694zm93 17v-38h13l21 38H836l-25-43c12-5 19-15 19-31 0-26-20-34-44-34H745v108h27zm16-51H774v-34h15c16 0 25 4 25 16s-9 18-25 18zM922 711v-22h-43v-23h35v-22h-35V625h41V603H853v108h68z + M40 9 15 23 15 31 9 28 9 20 34 5 24 0 0 14 0 34 25 48 25 28 49 14zM26 29 26 48 49 34 49 15z + M832 464h-68V240a128 128 0 00-128-128h-248a128 128 0 00-128 128v224H192c-18 0-32 14-32 32v384c0 18 14 32 32 32h640c18 0 32-14 32-32v-384c0-18-14-32-32-32zm-292 237v53a8 8 0 01-8 8h-40a8 8 0 01-8-8v-53a48 48 0 1156 0zm152-237H332V240a56 56 0 0156-56h248a56 56 0 0156 56v224z + M832 464H332V240c0-31 25-56 56-56h248c31 0 56 25 56 56v68c0 4 4 8 8 8h56c4 0 8-4 8-8v-68c0-71-57-128-128-128H388c-71 0-128 57-128 128v224h-68c-18 0-32 14-32 32v384c0 18 14 32 32 32h640c18 0 32-14 32-32V496c0-18-14-32-32-32zM540 701v53c0 4-4 8-8 8h-40c-4 0-8-4-8-8v-53c-12-9-20-23-20-39 0-27 22-48 48-48s48 22 48 48c0 16-8 30-20 39z + M897 673v13c0 51-42 93-93 93h-10c-1 0-2 0-2 0H220c-23 0-42 19-42 42v13c0 23 19 42 42 42h552c14 0 26 12 26 26 0 14-12 26-26 26H220c-51 0-93-42-93-93v-13c0-51 42-93 93-93h20c1-0 2-0 2-0h562c23 0 42-19 42-42v-13c0-11-5-22-13-29-8-7-17-11-28-10H660c-14 0-26-12-26-26 0-14 12-26 26-26h144c24-1 47 7 65 24 18 17 29 42 29 67zM479 98c-112 0-203 91-203 203 0 44 14 85 38 118l132 208c15 24 50 24 66 0l133-209c23-33 37-73 37-117 0-112-91-203-203-203zm0 327c-68 0-122-55-122-122s55-122 122-122 122 55 122 122-55 122-122 122z + M416 64H768v64h-64v704h64v64H448v-64h64V512H416a224 224 0 1 1 0-448zM576 832h64V128H576v704zM416 128H512v320H416a160 160 0 0 1 0-320z + M512 64A447 447 0 0064 512c0 248 200 448 448 448s448-200 448-448S760 64 512 64zM218 295h31c54 0 105 19 145 55 13 12 13 31 3 43a35 35 0 01-22 10 36 36 0 01-21-7 155 155 0 00-103-39h-31a32 32 0 01-31-31c0-18 13-31 30-31zm31 433h-31a32 32 0 01-31-31c0-16 13-31 31-31h31A154 154 0 00403 512 217 217 0 01620 295h75l-93-67a33 33 0 01-7-43 33 33 0 0143-7l205 148-205 148a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67H620a154 154 0 00-154 154c0 122-97 220-217 220zm390 118a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67h-75c-52 0-103-19-143-54-12-12-13-31-1-43a30 30 0 0142-3 151 151 0 00102 39h75L602 599a33 33 0 01-7-43 33 33 0 0143-7l205 148-203 151z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index a1b03bf0..abda0adc 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -12,7 +12,7 @@ Raise errors and refuses to apply the patch Error All Similar to 'error', but shows more - Patch File : + Patch File: Select .patch file to apply Ignore whitespace changes No Warn @@ -20,11 +20,11 @@ Apply Patch Warn Outputs warnings for a few such errors, but applies - Whitespace : - Archive ... - Save Archive To : + Whitespace: + Archive... + Save Archive To: Select archive file path - Revision : + Revision: Archive FILES ASSUME UNCHANGED NO FILES ASSUMED AS UNCHANGED @@ -32,23 +32,23 @@ BINARY FILE NOT SUPPORTED!!! Blame BLAME ON THIS FILE IS NOT SUPPORTED!!! - Checkout${0}$ + Checkout ${0}$... Compare with Branch Compare with HEAD Compare with Worktree Copy Branch Name - Delete${0}$ + Delete ${0}$... Delete selected {0} branches Discard all changes - Fast-Forward to${0}$ - Git Flow - Finish${0}$ - Merge${0}$into${1}$ - Pull${0}$ - Pull${0}$into${1}$ - Push${0}$ - Rebase${0}$on${1}$ - Rename${0}$ - Tracking ... + Fast-Forward to ${0}$ + Git Flow - Finish ${0}$ + Merge ${0}$ into ${1}$... + Pull ${0}$ + Pull ${0}$ into ${1}$... + Push ${0}$ + Rebase ${0}$ on ${1}$... + Rename ${0}$... + Set Tracking Branch Unset Upstream Branch Compare Bytes @@ -60,39 +60,40 @@ Checkout Branch Checkout Commit Warning: By doing a commit checkout, your Head will be detached - Commit : - Branch : - Local Changes : + Commit: + Branch: + Local Changes: Discard Do Nothing Stash & Reapply Cherry-Pick This Commit - Commit : + Commit: Commit all changes Cherry Pick Clear Stashes You are trying to clear all stashes. Are you sure to continue? Clone Remote Repository - Extra Parameters : + Extra Parameters: Additional arguments to clone repository. Optional. - Local Name : + Local Name: Repository name. Optional. - Parent Folder : - Repository URL : + Parent Folder: + Repository URL: CLOSE Cherry-Pick This Commit Checkout Commit Compare with HEAD Compare with Worktree Copy SHA - Rebase${0}$to Here - Reset${0}$to Here + Interactive Rebase ${0}$ to Here + Rebase ${0}$ to Here + Reset ${0}$ to Here Revert Commit Reword - Save as Patch ... + Save as Patch... Squash Into Parent CHANGES - Search Changes ... + Search Changes... FILES LFS File Submodule @@ -104,6 +105,8 @@ PARENTS REFS SHA + Enter commit subject & message + Git uses an empty line to separate the subject and extra message body. Repository Configure Email Address Email address @@ -114,43 +117,44 @@ Copy Copy Path Copy File Name - Create Branch - Based On : - Check out after created - Local Changes : + Create Branch... + Based On: + Check out the created branch + Local Changes: Discard Do Nothing Stash & Reapply - New Branch Name : + New Branch Name: Enter branch name. Create Local Branch - Create Tag - New Tag At : + Create Tag... + New Tag At: GPG signing - Tag Message : + Tag Message: Optional. - Tag Name : - Recommended format :v1.0.0-alpha + Tag Name: + Recommended format: v1.0.0-alpha Push to all remotes after created - Kind : + Create New Tag + Kind: annotated lightweight Cut Delete Branch - Branch : + Branch: You are about to delete a remote branch!!! - Also delete remote branch${0}$ + Also delete remote branch ${0}$ Delete Multiple Branches You are trying to delete multiple branches at one time. Be sure to double-check before taking action! Delete Remote - Remote : - Target : + Remote: + Target: Confirm Deleting Group Confirm Deleting Repository Delete Submodule - Submodule Path : + Submodule Path: Delete Tag - Tag : + Tag: Delete from remote repositories BINARY DIFF NEW @@ -170,22 +174,23 @@ Decrease Number of Visible Lines Increase Number of Visible Lines SELECT FILE TO VIEW CHANGES + Show hidden symbols Open In Merge Tool Discard Changes All local changes in working copy. - Changes : + Changes: Total {0} changes will be discard You can't undo this action!!! - Bookmark : - New Name : - Target : + Bookmark: + New Name: + Target: Edit Selected Group Edit Selected Repository Fast-Forward (without checkout) Fetch Fetch all remotes Prune remote dead branches - Remote : + Remote: Fetch Remote Changes Assume unchanged Discard... @@ -193,8 +198,8 @@ Discard Changes in Selected Line(s) Open External Merge Tool Save As Patch... - Stage... - Stage {0} files... + Stage + Stage {0} files Stage Changes in Selected Line(s) Stash... Stash {0} files... @@ -206,28 +211,50 @@ File History FILTER Git-Flow - Development Branch : - Feature : - Feature Prefix : + Development Branch: + Feature: + Feature Prefix: FLOW - Finish Feature FLOW - Finish Hotfix FLOW - Finish Release - Target : - Hotfix : - Hotfix Prefix : + Target: + Hotfix: + Hotfix Prefix: Initialize Git-Flow Keep branch - Production Branch : - Release : - Release Prefix : - Start Feature ... + Production Branch: + Release: + Release Prefix: + Start Feature... FLOW - Start Feature - Start Hotfix ... + Start Hotfix... FLOW - Start Hotfix Enter name - Start Release ... + Start Release... FLOW - Start Release - Version Tag Prefix : + Version Tag Prefix: + Git LFS + Add Track Pattern... + Pattern is file name + Custom Pattern: + Add Track Pattern to Git LFS + Fetch + Fetch LFS Objects + Run `git lfs fetch` to download Git LFS objects. This does not update the working copy. + Install Git LFS hooks + Show Locks + No Locked Files + Lock + LFS Locks + Unlock + Force Unlock + Prune + Run `git lfs prune` to delete old LFS files from local storage + Pull + Pull LFS Objects + Run `git lfs pull` to download all Git LFS files for current ref & checkout + Track files named '{0}' + Track all *{0} files Histories Switch Horizontal/Vertical Layout Switch Curve/Polyline Graph Mode @@ -257,25 +284,30 @@ Find previous match Open search panel Initialize Repository - Path : + Path: Invalid repository detected. Run `git init` under this path? Cherry-Pick in progress. Press 'Abort' to restore original HEAD. Merge request in progress. Press 'Abort' to restore original HEAD. Rebase in progress. Press 'Abort' to restore original HEAD. Revert in progress. Press 'Abort' to restore original HEAD. + Interactive Rebase + Target Branch: + On: + Move Up + Move Down Source Git ERROR NOTICE Open Main Menu Merge Branch - Into : - Merge Option : - Source Branch : - Name : + Into: + Merge Option: + Source Branch: + Name: Git has NOT been configured. Please to go [Preference] and configure it first. NOTICE SELECT FOLDER - Open With ... + Open With... Optional. Create New Page Bookmark @@ -298,6 +330,7 @@ Language History Commits Restore windows + Subject Guide Length Use fixed tab width in titlebar GIT Fetch remotes automatically @@ -321,63 +354,66 @@ Input path for installed gpg program User Signing Key User's gpg signing key - EXTERNAL MERGE TOOL - Diff Command - Merge Command - Install Path - Input path for merge tool - Merger + DIFF/MERGE TOOL + Install Path + Input path for diff/merge tool + Tool + Prune Remote + Target: Pull - Branch : - Into : - Local Changes : + Branch: + Into: + Local Changes: Discard Do Nothing Stash & Reapply - Remote : + Remote: Pull (Fetch & Merge) Use rebase instead of merge Push Force push - Local Branch : - Remote : + Local Branch: + Remote: Push Changes To Remote - Remote Branch : - Tracking remote branch + Remote Branch: + Set as tracking branch Push all tags Push Tag To Remote Push to all remotes - Remote : - Tag : + Remote: + Tag: Quit Rebase Current Branch Stash & reapply local changes - On : - Rebase : + On: + Rebase: Refresh Add Remote Edit Remote - Name : + Name: Remote name - Repository URL : + Repository URL: Remote git repository URL Copy URL - Delete ... - Edit ... - Fetch ... + Delete... + Edit... + Fetch + Open In Browser Prune - Target : + Target: Rename Branch - New Name : + New Name: Unique name for this branch - Branch : + Branch: ABORT Cleanup(GC & Prune) - Run `gc` command and do `lfs prune` if LFS is installed. + Run `git gc` command for this repository. + Clear all Configure this repository CONTINUE Open In File Browser Filter Branches + FILTERED BY: LOCAL BRANCHES Navigate To HEAD Create Branch @@ -401,21 +437,21 @@ WORKSPACE Git Repository URL Reset Current Branch To Revision - Reset Mode : - Move To : - Current Branch : + Reset Mode: + Move To: + Current Branch: Reveal in File Explorer Revert Commit - Commit : + Commit: Commit revert changes Reword Commit Message - Message : - On : - Running. Please wait ... + Message: + On: + Running. Please wait... SAVE - Save As ... + Save As... Patch has been saved successfully! - Check for Updates ... + Check for Updates... New version of this software is available: Check for updates failed! Download @@ -423,22 +459,22 @@ Software Update There are currently no updates available. Squash HEAD Into Parent - HEAD : - Reword : - To : - SSH Private Key : + HEAD: + Reword: + To: + SSH Private Key: Private SSH key store path START Stash Include untracked files - Message : + Message: Optional. Name of this stash Stash Local Changes Apply Drop Pop Drop Stash - Drop : + Drop: Stashes CHANGES STASHES @@ -455,14 +491,14 @@ Copy Relative Path Fetch nested submodules Open Submodule Repository - Relative Path : + Relative Path: Relative folder to store this module. Delete Submodule OK Copy Tag Name - Delete${0}$ - Push${0}$ - URL : + Delete ${0}$... + Push ${0}$... + URL: Update Submodules Run `submodule update` command for this repository. Warning @@ -472,12 +508,13 @@ Delete DRAG & DROP FOLDER SUPPORTED. CUSTOM GROUPING SUPPORTED. Edit + Open All Repositories Open Repository Open Terminal - Search Repositories ... + Search Repositories... Sort Changes - Add To .gitignore ... + Git Ignore Ignore all *{0} files Ignore *{0} files in the same folder Ignore files in the same folder @@ -486,7 +523,6 @@ You can stage this file now. COMMIT COMMIT & PUSH - Enter commit message CTRL + Enter CONFLICTS DETECTED FILE CONFLICTS ARE RESOLVED diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 1de41e04..56f0edce 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -35,23 +35,23 @@ 二进制文件不支持该操作!!! 逐行追溯(blame) 选中文件不支持该操作!!! - 检出(checkout)${0}$ + 检出(checkout) ${0}$... 与其他分支对比 与当前HEAD比较 与本地工作树比较 复制分支名 - 删除${0}$ + 删除 ${0}$... 删除选中的 {0} 个分支 放弃所有更改 - 快进(fast-forward)到${0}$ - GIT工作流 - 完成${0}$ - 合并${0}$到${1}$ - 拉回(pull)${0}$ - 拉回(pull)${0}$内容至${1}$ + 快进(fast-forward)到 ${0}$ + GIT工作流 - 完成 ${0}$ + 合并 ${0}$ 到 ${1}$... + 拉回(pull) ${0}$ + 拉回(pull) ${0}$ 内容至 ${1}$... 推送(push)${0}$ - 变基(rebase)${0}$分支至${1}$ - 重命名${0}$ - 切换上游分支... + 变基(rebase) ${0}$ 分支至 ${1}$... + 重命名 ${0}$... + 切换上游分支 取消追踪 分支比较 字节 @@ -88,8 +88,9 @@ 与当前HEAD比较 与本地工作树比较 复制提交指纹 - 变基(rebase)${0}$到此处 - 重置(reset)${0}$到此处 + 交互式变基(rebase -i) ${0}$ 到此处 + 变基(rebase) ${0}$ 到此处 + 重置(reset) ${0}$ 到此处 回滚此提交 编辑提交信息 另存为补丁 ... @@ -107,6 +108,8 @@ 父提交 相关引用 提交指纹 + 填写提交信息 + Git使用空白行来划分提交信息中的主题与内容 仓库配置 电子邮箱 邮箱地址 @@ -117,7 +120,7 @@ 复制 复制路径 复制文件名 - 新建分支 + 新建分支 ... 新分支基于 : 完成后切换到新分支 未提交更改 : @@ -127,7 +130,7 @@ 新分支名 : 填写分支名称。 创建本地分支 - 新建标签 + 新建标签 ... 标签位于 : 使用GPG签名 标签描述 : @@ -135,6 +138,7 @@ 标签名 : 推荐格式 :v1.0.0-alpha 推送到所有远程仓库 + 新建标签 类型 : 附注标签 轻量标签 @@ -142,7 +146,7 @@ 删除分支确认 分支名 : 您正在删除远程上的分支,请务必小心!!! - 同时删除远程分支${0}$ + 同时删除远程分支 ${0}$ 删除多个分支 您正在尝试一次性删除多个分支,请务必仔细检查后再执行操作! 删除远程确认 @@ -173,6 +177,7 @@ 减少可见的行数 增加可见的行数 请选择需要对比的文件 + 显示隐藏符号 使用外部比对工具查看 放弃更改确认 所有本地址未提交的修改。 @@ -196,8 +201,8 @@ 放弃选中的更改 使用外部合并工具打开 另存为补丁... - 暂存(add)... - 暂存(add){0} 个文件... + 暂存(add) + 暂存(add){0} 个文件 暂存选中的更改 贮藏(stash)... 贮藏(stash)选中的 {0} 个文件... @@ -231,6 +236,28 @@ 开始版本分支... 开始版本分支 版本标签前缀 : + Git LFS + 添加追踪文件规则... + 匹配完整文件名 + 规则 : + 添加LFS追踪文件规则 + 拉取LFS对象 (fetch) + 拉取LFS对象 + 执行`git lfs prune`命令,下载远程LFS对象,但不会更新工作副本。 + 启用Git LFS支持 + 显示LFS对象锁 + 没有锁定的LFS文件 + 锁定 + LFS对象锁状态 + 解锁 + 强制解锁 + 精简本地LFS对象存储 + 运行`git lfs prune`命令,从本地存储中精简当前版本不需要的LFS对象 + 拉回LFS对象 (pull) + 拉回LFS对象 + 运行`git lfs pull`命令,下载远程LFS对象并更新工作副本。 + 跟踪名为'{0}'的文件 + 跟踪所有 *{0} 文件 历史记录 切换横向/纵向显示 切换曲线/折线显示 @@ -266,6 +293,11 @@ 合并操作进行中。点击【终止】回滚到操作前的状态。 变基(Rebase)操作进行中。点击【终止】回滚到操作前的状态。 回滚提交操作进行中。点击【终止】回滚到操作前的状态。 + 交互式变基 + 目标分支 : + 起始提交 : + 向上移动 + 向下移动 Source Git 出错了 系统提示 @@ -301,6 +333,7 @@ 显示语言 最大历史提交数 启动时恢复上次打开的仓库 + SUBJECT字数检测 使用固定宽度的标题栏标签 GIT配置 启用定时自动拉取远程更新 @@ -324,12 +357,12 @@ 签名程序所在路径 用户签名KEY 输入签名提交所使用的KEY - 外部合并工具 - 对比模式启动参数 - 合并模式启动参数 - 安装路径 - 填写工具可执行文件所在位置 - 工具 + 对比/合并工具 + 安装路径 + 填写工具可执行文件所在位置 + 工具 + 清理远程已删除分支 + 目标 : 拉回(pull) 拉取分支 : 本地分支 : @@ -367,20 +400,22 @@ 复制远程地址 删除 ... 编辑 ... - 拉取(fetch)更新 ... + 拉取(fetch)更新 + 在浏览器中打开 清理远程已删除分支 - 目标 : 分支重命名 新的名称 : 新的分支名不能与现有分支名相同 分支 : 终止合并 清理本仓库(GC) - 本操作将执行`gc`,对于启用LFS的仓库也会执行`lfs prune`。 + 本操作将执行`git gc`命令。 + 清空过滤规则 配置本仓库 下一步 在文件浏览器中打开 过滤显示分支 + 过滤规则 : 本地分支 定位HEAD 新建分支 @@ -463,8 +498,8 @@ 删除子模块 确 定 复制标签名 - 删除${0}$ - 推送${0}$ + 删除 ${0}$... + 推送 ${0}$... 仓库地址 : 更新子模块 为此仓库执行`submodule update`命令,更新所有的子模块。 @@ -475,12 +510,13 @@ 删除 支持拖放目录添加。支持自定义分组。 编辑 + 打开所有包含仓库 打开本地仓库 打开终端 快速查找仓库... 排序 本地更改 - 添加至 .gitignore 忽略列表 ... + 添加至 .gitignore 忽略列表 忽略所有 *{0} 文件 忽略同目录下所有 *{0} 文件 忽略同目录下所有文件 @@ -489,7 +525,6 @@ 现在您已可将其加入暂存区中 提交 提交并推送 - 填写提交信息 CTRL + Enter 检测到冲突 文件冲突已解决 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 79c4be87..80152a37 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -35,23 +35,23 @@ 二進位制檔案不支援該操作!!! 逐行追溯(blame) 選中檔案不支援該操作!!! - 檢出(checkout)${0}$ + 檢出(checkout) ${0}$... 與其他分支比較 與當前HEAD比較 與本地工作樹比較 複製分支名 - 刪除${0}$ + 刪除 ${0}$... 刪除選中的 {0} 個分支 放棄所有更改 - 快進(fast-forward)到${0}$ - GIT工作流 - 完成${0}$ - 合併${0}$到${1}$ - 拉回(pull)${0}$ - 拉回(pull)${0}$內容至${1}$ + 快進(fast-forward)到 ${0}$ + GIT工作流 - 完成 ${0}$ + 合併 ${0}$ 到 ${1}$... + 拉回(pull) ${0}$ + 拉回(pull) ${0}$ 內容至 ${1}$... 推送(push)${0}$ - 變基(rebase)${0}$分支至${1}$ - 重新命名${0}$ - 切換上游分支... + 變基(rebase) ${0}$ 分支至 ${1}$... + 重新命名 ${0}$... + 切換上游分支 取消追蹤 分支比較 位元組 @@ -88,8 +88,9 @@ 與當前HEAD比較 與本地工作樹比較 複製提交指紋 - 變基(rebase)${0}$到此處 - 重置(reset)${0}$到此處 + 互動式變基(rebase -i) ${0}$ 到此處 + 變基(rebase) ${0}$ 到此處 + 重置(reset) ${0}$ 到此處 回滾此提交 編輯提交資訊 另存為補丁 ... @@ -107,6 +108,8 @@ 父提交 相關引用 提交指紋 + 填寫提交資訊 + Git使用空白行來劃分提交資訊中的主題與內容 倉庫配置 電子郵箱 郵箱地址 @@ -117,7 +120,7 @@ 複製 複製路徑 複製檔名 - 新建分支 + 新建分支 ... 新分支基於 : 完成後切換到新分支 未提交更改 : @@ -127,7 +130,7 @@ 新分支名 : 填寫分支名稱。 建立本地分支 - 新建標籤 + 新建標籤 ... 標籤位於 : 使用GPG簽名 標籤描述 : @@ -135,6 +138,7 @@ 標籤名 : 推薦格式 :v1.0.0-alpha 推送到所有遠端倉庫 + 新建標籤 型別 : 附註標籤 輕量標籤 @@ -142,7 +146,7 @@ 刪除分支確認 分支名 : 您正在刪除遠端上的分支,請務必小心!!! - 同時刪除遠端分支${0}$ + 同時刪除遠端分支 ${0}$ 刪除多個分支 您正在嘗試一次性刪除多個分支,請務必仔細檢查後再執行操作! 刪除遠端確認 @@ -173,6 +177,7 @@ 減少可見的行數 增加可見的行數 請選擇需要對比的檔案 + 顯示隱藏符號 使用外部比對工具檢視 放棄更改確認 所有本地址未提交的修改。 @@ -196,8 +201,8 @@ 放棄選中的更改 使用外部合併工具開啟 另存為補丁... - 暫存(add)... - 暫存(add){0} 個檔案... + 暫存(add) + 暫存(add){0} 個檔案 暫存選中的更改 儲藏(stash)... 儲藏(stash)選中的 {0} 個檔案... @@ -231,6 +236,28 @@ 開始版本分支... 開始版本分支 版本標籤字首 : + Git LFS + 添加追蹤檔案規則... + 匹配完整檔案名 + 規則 : + 添加LFS追蹤檔案規則 + 拉取LFS物件 (fetch) + 拉取LFS物件 + 執行`git lfs fetch`命令,下載遠端LFS物件,但不會更新工作副本。 + 啟用Git LFS支援 + 顯示LFS物件鎖 + 沒有鎖定的LFS物件 + 鎖定 + LFS物件鎖 + 解鎖 + 強制解鎖 + 精簡本地LFS物件存儲 + 執行`git lfs prune`命令,從本地存儲中精簡當前版本不需要的LFS物件 + 拉回LFS物件 (pull) + 拉回LFS物件 + 執行`git lfs pull`命令,下載遠端LFS物件并更新工作副本。 + 跟蹤名為'{0}'的檔案 + 跟蹤所有 *{0} 檔案 歷史記錄 切換橫向/縱向顯示 切換曲線/折線顯示 @@ -266,6 +293,11 @@ 合併操作進行中。點選【終止】回滾到操作前的狀態。 變基(Rebase)操作進行中。點選【終止】回滾到操作前的狀態。 回滾提交操作進行中。點選【終止】回滾到操作前的狀態。 + 互動式變基 + 目標分支 : + 起始提交 : + 向上移動 + 向下移動 Source Git 出錯了 系統提示 @@ -301,6 +333,7 @@ 顯示語言 最大歷史提交數 啟動時恢復上次開啟的倉庫 + SUBJECT字數檢測 使用固定寬度的標題欄標籤 GIT配置 啟用定時自動拉取遠端更新 @@ -324,12 +357,12 @@ gpg.exe所在路徑 使用者簽名KEY 輸入簽名提交所使用的KEY - 外部合併工具 - 對比模式啟動引數 - 合併模式啟動引數 - 安裝路徑 - 填寫工具可執行檔案所在位置 - 工具 + 對比/合併工具 + 安裝路徑 + 填寫工具可執行檔案所在位置 + 工具 + 清理遠端已刪除分支 + 目標 : 拉回(pull) 拉取分支 : 本地分支 : @@ -367,20 +400,22 @@ 複製遠端地址 刪除 ... 編輯 ... - 拉取(fetch)更新 ... + 拉取(fetch)更新 + 在瀏覽器中訪問網址 清理遠端已刪除分支 - 目標 : 分支重新命名 新的名稱 : 新的分支名不能與現有分支名相同 分支 : 終止合併 清理本倉庫(GC) - 本操作將執行`gc`,對於啟用LFS的倉庫也會執行`lfs prune`。 + 本操作將執行`git gc`命令。 + 清空過濾規則 配置本倉庫 下一步 在檔案瀏覽器中開啟 過濾顯示分支 + 過濾規則 : 本地分支 定位HEAD 新建分支 @@ -463,8 +498,8 @@ 刪除子模組 確 定 複製標籤名 - 刪除${0}$ - 推送${0}$ + 刪除 ${0}$... + 推送 ${0}$... 倉庫地址 : 更新子模組 本操作將執行 `submodule update` 。 @@ -475,12 +510,13 @@ 刪除 支援拖放目錄新增。支援自定義分組。 編輯 + 打開所有包含倉庫 開啟本地倉庫 開啟終端 快速查詢倉庫... 排序 本地更改 - 添加至 .gitignore 忽略清單 ... + 添加至 .gitignore 忽略清單 忽略所有 *{0} 檔案 忽略同路徑下所有 *{0} 檔案 忽略同路徑下所有檔案 @@ -489,7 +525,6 @@ 現在您已可將其加入暫存區中 提交 提交併推送 - 填寫提交資訊 CTRL + Enter 檢測到衝突 檔案衝突已解決 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index fdf3ec3e..a646cc46 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -1,7 +1,6 @@ @@ -160,6 +159,11 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0,0,0,4 + 0 + 0 + 8 + 16 + 16 + + + + + + + + + + + + diff --git a/src/Views/ImageDiffView.axaml.cs b/src/Views/ImageDiffView.axaml.cs new file mode 100644 index 00000000..bdeda323 --- /dev/null +++ b/src/Views/ImageDiffView.axaml.cs @@ -0,0 +1,318 @@ +using System; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Styling; + +namespace SourceGit.Views +{ + public class ImageContainer : Control + { + public override void Render(DrawingContext context) + { + if (_bgBrush == null) + { + var maskBrush = new SolidColorBrush(ActualThemeVariant == ThemeVariant.Dark ? 0xFF404040 : 0xFFBBBBBB); + var bg = new DrawingGroup() + { + Children = + { + new GeometryDrawing() { Brush = maskBrush, Geometry = new RectangleGeometry(new Rect(0, 0, 12, 12)) }, + new GeometryDrawing() { Brush = maskBrush, Geometry = new RectangleGeometry(new Rect(12, 12, 12, 12)) }, + } + }; + + _bgBrush = new DrawingBrush(bg) + { + AlignmentX = AlignmentX.Left, + AlignmentY = AlignmentY.Top, + DestinationRect = new RelativeRect(new Size(24, 24), RelativeUnit.Absolute), + Stretch = Stretch.None, + TileMode = TileMode.Tile, + }; + } + + context.FillRectangle(_bgBrush, new Rect(Bounds.Size)); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property.Name == "ActualThemeVariant") + { + _bgBrush = null; + InvalidateVisual(); + } + } + + private DrawingBrush _bgBrush = null; + } + + public class ImagesSwipeControl : ImageContainer + { + public static readonly StyledProperty AlphaProperty = + AvaloniaProperty.Register(nameof(Alpha), 0.5); + + public double Alpha + { + get => GetValue(AlphaProperty); + set => SetValue(AlphaProperty, value); + } + + public static readonly StyledProperty OldImageProperty = + AvaloniaProperty.Register(nameof(OldImage), null); + + public Bitmap OldImage + { + get => GetValue(OldImageProperty); + set => SetValue(OldImageProperty, value); + } + + public static readonly StyledProperty NewImageProperty = + AvaloniaProperty.Register(nameof(NewImage), null); + + public Bitmap NewImage + { + get => GetValue(NewImageProperty); + set => SetValue(NewImageProperty, value); + } + + static ImagesSwipeControl() + { + AffectsMeasure(OldImageProperty, NewImageProperty); + AffectsRender(AlphaProperty); + } + + public override void Render(DrawingContext context) + { + base.Render(context); + + var alpha = Alpha; + var w = Bounds.Width; + var h = Bounds.Height; + var x = w * alpha; + var left = OldImage; + if (left != null && alpha > 0) + { + var src = new Rect(0, 0, left.Size.Width * alpha, left.Size.Height); + var dst = new Rect(0, 0, x, h); + context.DrawImage(left, src, dst); + } + + var right = NewImage; + if (right != null && alpha < 1) + { + var src = new Rect(right.Size.Width * alpha, 0, right.Size.Width * (1 - alpha), right.Size.Height); + var dst = new Rect(x, 0, w - x, h); + context.DrawImage(right, src, dst); + } + + context.DrawLine(new Pen(Brushes.DarkGreen, 2), new Point(x, 0), new Point(x, Bounds.Height)); + } + + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + base.OnPointerPressed(e); + + var p = e.GetPosition(this); + var hitbox = new Rect(Math.Max(Bounds.Width * Alpha - 2, 0), 0, 4, Bounds.Height); + var pointer = e.GetCurrentPoint(this); + if (pointer.Properties.IsLeftButtonPressed && hitbox.Contains(p)) + { + _pressedOnSlider = true; + Cursor = new Cursor(StandardCursorType.SizeWestEast); + e.Pointer.Capture(this); + e.Handled = true; + } + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + _pressedOnSlider = false; + } + + protected override void OnPointerMoved(PointerEventArgs e) + { + var w = Bounds.Width; + var p = e.GetPosition(this); + + if (_pressedOnSlider) + { + SetCurrentValue(AlphaProperty, Math.Clamp(p.X, 0, w) / w); + } + else + { + var hitbox = new Rect(Math.Max(w * Alpha - 2, 0), 0, 4, Bounds.Height); + if (hitbox.Contains(p)) + { + if (!_lastInSlider) + { + _lastInSlider = true; + Cursor = new Cursor(StandardCursorType.SizeWestEast); + } + } + else + { + if (_lastInSlider) + { + _lastInSlider = false; + Cursor = null; + } + } + } + } + + protected override Size MeasureOverride(Size availableSize) + { + var left = OldImage; + var right = NewImage; + + if (left == null) + return right == null ? availableSize : GetDesiredSize(right.Size, availableSize); + + if (right == null) + return GetDesiredSize(left.Size, availableSize); + + var ls = GetDesiredSize(left.Size, availableSize); + var rs = GetDesiredSize(right.Size, availableSize); + return ls.Width > rs.Width ? ls : rs; + } + + private Size GetDesiredSize(Size img, Size available) + { + var w = available.Width; + var h = available.Height; + + var sw = available.Width / img.Width; + var sh = available.Height / img.Height; + var scale = Math.Min(sw, sh); + + return new Size(scale * img.Width, scale * img.Height); + } + + private bool _pressedOnSlider = false; + private bool _lastInSlider = false; + } + + public class ImageBlendControl : ImageContainer + { + public static readonly StyledProperty AlphaProperty = + AvaloniaProperty.Register(nameof(Alpha), 1.0); + + public double Alpha + { + get => GetValue(AlphaProperty); + set => SetValue(AlphaProperty, value); + } + + public static readonly StyledProperty OldImageProperty = + AvaloniaProperty.Register(nameof(OldImage), null); + + public Bitmap OldImage + { + get => GetValue(OldImageProperty); + set => SetValue(OldImageProperty, value); + } + + public static readonly StyledProperty NewImageProperty = + AvaloniaProperty.Register(nameof(NewImage), null); + + public Bitmap NewImage + { + get => GetValue(NewImageProperty); + set => SetValue(NewImageProperty, value); + } + + static ImageBlendControl() + { + AffectsMeasure(OldImageProperty, NewImageProperty); + AffectsRender(AlphaProperty); + } + + public override void Render(DrawingContext context) + { + base.Render(context); + + var rect = new Rect(0, 0, Bounds.Width, Bounds.Height); + var alpha = Alpha; + var left = OldImage; + var right = NewImage; + var drawLeft = left != null && alpha < 1.0; + var drawRight = right != null && alpha > 0; + + if (drawLeft && drawRight) + { + using (var rt = new RenderTargetBitmap(right.PixelSize, right.Dpi)) + { + var rtRect = new Rect(rt.Size); + using (var dc = rt.CreateDrawingContext()) + { + using (dc.PushRenderOptions(RO_SRC)) + using (dc.PushOpacity(1 - alpha)) + dc.DrawImage(left, rtRect); + + using (dc.PushRenderOptions(RO_DST)) + using (dc.PushOpacity(alpha)) + dc.DrawImage(right, rtRect); + } + + context.DrawImage(rt, rtRect, rect); + } + } + else if (drawLeft) + { + using (context.PushOpacity(1 - alpha)) + context.DrawImage(left, rect); + } + else if (drawRight) + { + using (context.PushOpacity(alpha)) + context.DrawImage(right, rect); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + var left = OldImage; + var right = NewImage; + + if (left == null) + return right == null ? availableSize : GetDesiredSize(right.Size, availableSize); + + if (right == null) + return GetDesiredSize(left.Size, availableSize); + + var ls = GetDesiredSize(left.Size, availableSize); + var rs = GetDesiredSize(right.Size, availableSize); + return ls.Width > rs.Width ? ls : rs; + } + + private Size GetDesiredSize(Size img, Size available) + { + var w = available.Width; + var h = available.Height; + + var sw = available.Width / img.Width; + var sh = available.Height / img.Height; + var scale = Math.Min(sw, sh); + + return new Size(scale * img.Width, scale * img.Height); + } + + private static readonly RenderOptions RO_SRC = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; + private static readonly RenderOptions RO_DST = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Plus, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; + } + + public partial class ImageDiffView : UserControl + { + public ImageDiffView() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/InteractiveRebase.axaml b/src/Views/InteractiveRebase.axaml new file mode 100644 index 00000000..8e966509 --- /dev/null +++ b/src/Views/InteractiveRebase.axaml @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/LFSLocks.axaml.cs b/src/Views/LFSLocks.axaml.cs new file mode 100644 index 00000000..a46674af --- /dev/null +++ b/src/Views/LFSLocks.axaml.cs @@ -0,0 +1,40 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; + +namespace SourceGit.Views +{ + public partial class LFSLocks : ChromelessWindow + { + public LFSLocks() + { + InitializeComponent(); + } + + private void BeginMoveWindow(object sender, PointerPressedEventArgs e) + { + BeginMoveDrag(e); + } + + private void CloseWindow(object sender, RoutedEventArgs e) + { + Close(); + } + + private void OnUnlockButtonClicked(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.LFSLocks vm && sender is Button button) + vm.Unlock(button.DataContext as Models.LFSLock, false); + + e.Handled = true; + } + + private void OnForceUnlockButtonClicked(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.LFSLocks vm && sender is Button button) + vm.Unlock(button.DataContext as Models.LFSLock, true); + + e.Handled = true; + } + } +} diff --git a/src/Views/LFSPrune.axaml b/src/Views/LFSPrune.axaml new file mode 100644 index 00000000..a8ada710 --- /dev/null +++ b/src/Views/LFSPrune.axaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/src/Views/LFSPrune.axaml.cs b/src/Views/LFSPrune.axaml.cs new file mode 100644 index 00000000..dbb4a376 --- /dev/null +++ b/src/Views/LFSPrune.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class LFSPrune : UserControl + { + public LFSPrune() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/LFSPull.axaml b/src/Views/LFSPull.axaml new file mode 100644 index 00000000..f472f567 --- /dev/null +++ b/src/Views/LFSPull.axaml @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/Views/LFSPull.axaml.cs b/src/Views/LFSPull.axaml.cs new file mode 100644 index 00000000..db71afe6 --- /dev/null +++ b/src/Views/LFSPull.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class LFSPull : UserControl + { + public LFSPull() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/LFSTrackCustomPattern.axaml b/src/Views/LFSTrackCustomPattern.axaml new file mode 100644 index 00000000..6333dcd2 --- /dev/null +++ b/src/Views/LFSTrackCustomPattern.axaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/src/Views/LFSTrackCustomPattern.axaml.cs b/src/Views/LFSTrackCustomPattern.axaml.cs new file mode 100644 index 00000000..2e66f55a --- /dev/null +++ b/src/Views/LFSTrackCustomPattern.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class LFSTrackCustomPattern : UserControl + { + public LFSTrackCustomPattern() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml index 255d57bd..84ecdd3a 100644 --- a/src/Views/Launcher.axaml +++ b/src/Views/Launcher.axaml @@ -50,7 +50,7 @@ - + diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index e22a5696..3fd3b662 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -258,6 +258,16 @@ namespace SourceGit.Views private void OnPointerPressedTab(object sender, PointerPressedEventArgs e) { + var border = sender as Border; + var point = e.GetCurrentPoint(border); + if (point.Properties.IsMiddleButtonPressed) + { + var vm = DataContext as ViewModels.Launcher; + vm.CloseTab(border.DataContext as ViewModels.LauncherPage); + e.Handled = true; + return; + } + _pressedTab = true; _startDragTab = false; _pressedTabPosition = e.GetPosition(sender as Border); diff --git a/src/Views/NameHighlightedTextBlock.cs b/src/Views/NameHighlightedTextBlock.cs index 3be81186..c0583223 100644 --- a/src/Views/NameHighlightedTextBlock.cs +++ b/src/Views/NameHighlightedTextBlock.cs @@ -81,7 +81,6 @@ namespace SourceGit.Views return; var normalTypeface = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal); - //var highlightTypeface = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Bold, FontStretch.Normal); var underlinePen = new Pen(Foreground, 1); var offsetX = 0.0; @@ -99,7 +98,6 @@ namespace SourceGit.Views part, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, - //isName ? highlightTypeface : normalTypeface, normalTypeface, FontSize, Foreground); @@ -107,15 +105,14 @@ namespace SourceGit.Views if (isName) { var lineY = formatted.Baseline + 2; - offsetX += 4; context.DrawText(formatted, new Point(offsetX, 0)); context.DrawLine(underlinePen, new Point(offsetX, lineY), new Point(offsetX + formatted.Width, lineY)); - offsetX += formatted.Width + 4; + offsetX += formatted.WidthIncludingTrailingWhitespace; } else { context.DrawText(formatted, new Point(offsetX, 0)); - offsetX += formatted.Width; + offsetX += formatted.WidthIncludingTrailingWhitespace; } isName = !isName; diff --git a/src/Views/PopupRunningStatus.axaml b/src/Views/PopupRunningStatus.axaml index 5cc9a4bd..c1451a1e 100644 --- a/src/Views/PopupRunningStatus.axaml +++ b/src/Views/PopupRunningStatus.axaml @@ -18,7 +18,7 @@ diff --git a/src/Views/Preference.axaml b/src/Views/Preference.axaml index 037b6353..2e8a04a3 100644 --- a/src/Views/Preference.axaml +++ b/src/Views/Preference.axaml @@ -27,7 +27,7 @@ @@ -58,7 +58,7 @@ - + + + + - + - - - @@ -449,12 +462,12 @@ - + - + + Margin="0,0,16,0" + IsVisible="{Binding ExternalMergeToolType, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/> + Text="{Binding ExternalMergeToolPath, Mode=TwoWay}" + Watermark="{DynamicResource Text.Preference.DiffMerge.Path.Placeholder}" + IsVisible="{Binding ExternalMergeToolType, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"> - - - - - - diff --git a/src/Views/Preference.axaml.cs b/src/Views/Preference.axaml.cs index cccbd522..0bc477f3 100644 --- a/src/Views/Preference.axaml.cs +++ b/src/Views/Preference.axaml.cs @@ -43,7 +43,7 @@ namespace SourceGit.Views { get; set; - } + } = Models.CRLFMode.Supported[0]; public static readonly StyledProperty GitVersionProperty = AvaloniaProperty.Register(nameof(GitVersion)); @@ -157,13 +157,13 @@ namespace SourceGit.Views CRLFMode = Models.CRLFMode.Supported.Find(x => x.Value == crlf); if (config.TryGetValue("commit.gpgsign", out var gpgCommitSign)) EnableGPGCommitSigning = (gpgCommitSign == "true"); - if (config.TryGetValue("tag.gpgSign", out var gpgTagSign)) + if (config.TryGetValue("tag.gpgsign", out var gpgTagSign)) EnableGPGTagSigning = (gpgTagSign == "true"); if (config.TryGetValue("gpg.format", out var gpgFormat)) GPGFormat = Models.GPGFormat.Supported.Find(x => x.Value == gpgFormat) ?? Models.GPGFormat.Supported[0]; - if (GPGFormat.Value == "opengpg" && config.TryGetValue("gpg.program", out var opengpg)) - GPGExecutableFile = opengpg; + if (GPGFormat.Value == "openpgp" && config.TryGetValue("gpg.program", out var openpgp)) + GPGExecutableFile = openpgp; else if (config.TryGetValue($"gpg.{GPGFormat.Value}.program", out var gpgProgram)) GPGExecutableFile = gpgProgram; @@ -174,6 +174,20 @@ namespace SourceGit.Views GitVersion = ver; } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == GPGFormatProperty) + { + var config = new Commands.Config(null).ListAll(); + if (GPGFormat.Value == "openpgp" && config.TryGetValue("gpg.program", out var openpgp)) + GPGExecutableFile = openpgp; + else if (config.TryGetValue($"gpg.{GPGFormat.Value}.program", out var gpgProgram)) + GPGExecutableFile = gpgProgram; + } + } + private void BeginMoveWindow(object sender, PointerPressedEventArgs e) { BeginMoveDrag(e); @@ -181,34 +195,17 @@ namespace SourceGit.Views private void CloseWindow(object sender, RoutedEventArgs e) { - var cmd = new Commands.Config(null); + var config = new Commands.Config(null).ListAll(); + SetIfChanged(config, "user.name", DefaultUser); + SetIfChanged(config, "user.email", DefaultEmail); + SetIfChanged(config, "user.signingkey", GPGUserKey); + SetIfChanged(config, "core.autocrlf", CRLFMode.Value); + SetIfChanged(config, "commit.gpgsign", EnableGPGCommitSigning ? "true" : "false"); + SetIfChanged(config, "tag.gpgsign", EnableGPGTagSigning ? "true" : "false"); + SetIfChanged(config, "gpg.format", GPGFormat.Value); - var config = cmd.ListAll(); - var oldUser = config.TryGetValue("user.name", out var user) ? user : string.Empty; - var oldEmail = config.TryGetValue("user.email", out var email) ? email : string.Empty; - var oldGPGSignKey = config.TryGetValue("user.signingkey", out var signingKey) ? signingKey : string.Empty; - var oldCRLF = config.TryGetValue("core.autocrlf", out var crlf) ? crlf : string.Empty; - var oldGPGFormat = config.TryGetValue("gpg.format", out var gpgFormat) ? gpgFormat : "opengpg"; - var oldGPGCommitSignEnable = config.TryGetValue("commit.gpgsign", out var gpgCommitSign) ? gpgCommitSign : "false"; - var oldGPGTagSignEnable = config.TryGetValue("tag.gpgSign", out var gpgTagSign) ? gpgTagSign : "false"; - var oldGPGExec = config.TryGetValue("gpg.program", out var program) ? program : string.Empty; - - if (DefaultUser != oldUser) - cmd.Set("user.name", DefaultUser); - if (DefaultEmail != oldEmail) - cmd.Set("user.email", DefaultEmail); - if (GPGUserKey != oldGPGSignKey) - cmd.Set("user.signingkey", GPGUserKey); - if (CRLFMode != null && CRLFMode.Value != oldCRLF) - cmd.Set("core.autocrlf", CRLFMode.Value); - if (EnableGPGCommitSigning != (oldGPGCommitSignEnable == "true")) - cmd.Set("commit.gpgsign", EnableGPGCommitSigning ? "true" : "false"); - if (EnableGPGTagSigning != (oldGPGTagSignEnable == "true")) - cmd.Set("tag.gpgSign", EnableGPGTagSigning ? "true" : "false"); - if (GPGFormat.Value != oldGPGFormat) - cmd.Set("gpg.format", GPGFormat.Value); - if (GPGExecutableFile != oldGPGExec) - cmd.Set($"gpg.{GPGFormat.Value}.program", GPGExecutableFile); + if (!GPGFormat.Value.Equals("ssh", StringComparison.Ordinal)) + SetIfChanged(config, $"gpg.{GPGFormat.Value}.program", GPGExecutableFile); Close(); } @@ -287,6 +284,7 @@ namespace SourceGit.Views { ViewModels.Preference.Instance.ExternalMergeToolType = 0; type = 0; + return; } var tool = Models.ExternalMerger.Supported[type]; @@ -302,5 +300,17 @@ namespace SourceGit.Views ViewModels.Preference.Instance.ExternalMergeToolPath = selected[0].Path.LocalPath; } } + + private void SetIfChanged(Dictionary cached, string key, string value) + { + bool changed = false; + if (cached.TryGetValue(key, out var old)) + changed = old != value; + else if (!string.IsNullOrEmpty(value)) + changed = true; + + if (changed) + new Commands.Config(null).Set(key, value); + } } } diff --git a/src/Views/PruneRemote.axaml b/src/Views/PruneRemote.axaml index 2b3dbcf0..72b09d26 100644 --- a/src/Views/PruneRemote.axaml +++ b/src/Views/PruneRemote.axaml @@ -9,9 +9,9 @@ + Text="{DynamicResource Text.PruneRemote}"/> - + diff --git a/src/Views/Pull.axaml b/src/Views/Pull.axaml index 21a5c7f7..d2d1bebb 100644 --- a/src/Views/Pull.axaml +++ b/src/Views/Pull.axaml @@ -72,17 +72,17 @@ + - - + + IsChecked="{Binding Tracking, Mode=TwoWay}" + IsVisible="{Binding IsSetTrackOptionVisible}"/> + + @@ -94,7 +98,7 @@ @@ -453,6 +457,7 @@ HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" ContextRequested="OnSubmoduleContextRequested" + DoubleTapped="OnDoubleTappedSubmodule" IsVisible="{Binding IsSubmoduleGroupExpanded, Mode=OneWay}"> + + - + - + - + @@ -125,12 +130,32 @@ HeadersVisibility="Column" GridLinesVisibility="All" BorderThickness="1" - BorderBrush="{DynamicResource Brush.Border1}" + BorderBrush="{DynamicResource Brush.Border2}" Background="{DynamicResource Brush.Contents}" + HorizontalGridLinesBrush="{DynamicResource Brush.Border2}" + VerticalGridLinesBrush="{DynamicResource Brush.Border2}" IsReadOnly="True" RowHeight="26" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> + + + + + + + @@ -156,7 +181,8 @@ diff --git a/src/Views/Statistics.axaml.cs b/src/Views/Statistics.axaml.cs index 0b88a29d..a6e13b4e 100644 --- a/src/Views/Statistics.axaml.cs +++ b/src/Views/Statistics.axaml.cs @@ -12,6 +12,15 @@ namespace SourceGit.Views { public class Chart : Control { + public static readonly StyledProperty LabelBrushProperty = + AvaloniaProperty.Register(nameof(LabelBrush), Brushes.Black); + + public IBrush LabelBrush + { + get => GetValue(LabelBrushProperty); + set => SetValue(LabelBrushProperty, value); + } + public static readonly StyledProperty LineBrushProperty = AvaloniaProperty.Register(nameof(LineBrush), Brushes.Gray); @@ -63,33 +72,19 @@ namespace SourceGit.Views } if (maxV < 5) - { maxV = 5; - } else if (maxV < 10) - { maxV = 10; - } else if (maxV < 50) - { maxV = 50; - } else if (maxV < 100) - { maxV = 100; - } else if (maxV < 200) - { maxV = 200; - } else if (maxV < 500) - { maxV = 500; - } else - { maxV = (int)Math.Ceiling(maxV / 500.0) * 500; - } var typeface = new Typeface("fonts:SourceGit#JetBrains Mono"); var pen = new Pen(LineBrush, 1); @@ -100,7 +95,7 @@ namespace SourceGit.Views context.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, width, height)); // Draw coordinate - var maxLabel = new FormattedText($"{maxV}", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12.0, LineBrush); + var maxLabel = new FormattedText($"{maxV}", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12.0, LabelBrush); var horizonStart = maxLabel.Width + 8; var labelHeight = maxLabel.Height; context.DrawText(maxLabel, new Point(0, -maxLabel.Height * 0.5)); @@ -122,7 +117,7 @@ namespace SourceGit.Views FlowDirection.LeftToRight, typeface, 12.0, - LineBrush); + LabelBrush); var dashHeight = i * stepV; var vy = Math.Max(0, dashHeight - vLabel.Height * 0.5); @@ -155,7 +150,7 @@ namespace SourceGit.Views FlowDirection.LeftToRight, typeface, 10.0, - LineBrush); + LabelBrush); var rect = _hitBoxes[i]; var xLabel = rect.X - (hLabel.Width - rect.Width) * 0.5; var yLabel = height - labelHeight + 4; @@ -188,7 +183,7 @@ namespace SourceGit.Views FlowDirection.LeftToRight, typeface, 12.0, - LineBrush); + LabelBrush); var tx = rect.X - (tooltip.Width - rect.Width) * 0.5; var ty = rect.Y - tooltip.Height - 4; diff --git a/src/Views/TextDiffView.axaml b/src/Views/TextDiffView.axaml index 538a0a6e..738924b6 100644 --- a/src/Views/TextDiffView.axaml +++ b/src/Views/TextDiffView.axaml @@ -22,7 +22,9 @@ IndicatorForeground="{DynamicResource Brush.FG2}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" - WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/> + WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}" + ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" + /> @@ -40,7 +42,9 @@ IndicatorForeground="{DynamicResource Brush.FG2}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" - WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/> + WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}" + ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" + /> @@ -57,7 +61,9 @@ IndicatorForeground="{DynamicResource Brush.FG2}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" - WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/> + WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}" + ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" + /> diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 45fc7c2a..22ee8123 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -105,6 +105,15 @@ namespace SourceGit.Views set => SetValue(UseSyntaxHighlightingProperty, value); } + public static readonly StyledProperty ShowHiddenSymbolsProperty = + AvaloniaProperty.Register(nameof(ShowHiddenSymbols), false); + + public bool ShowHiddenSymbols + { + get => GetValue(ShowHiddenSymbolsProperty); + set => SetValue(ShowHiddenSymbolsProperty, value); + } + protected override Type StyleKeyOverride => typeof(TextEditor); public IThemedTextDiffPresenter(TextArea area, TextDocument doc) : base(area, doc) @@ -140,11 +149,23 @@ namespace SourceGit.Views base.OnPropertyChanged(change); if (change.Property == UseSyntaxHighlightingProperty) + { UpdateTextMate(); + } + else if (change.Property == ShowHiddenSymbolsProperty) + { + var val = change.NewValue is true; + Options.ShowTabs = val; + Options.ShowSpaces = val; + } else if (change.Property == FileNameProperty) + { Models.TextMateHelper.SetGrammarByFileName(_textMate, FileName); + } else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null) + { Models.TextMateHelper.SetThemeByApp(_textMate); + } } protected void UpdateTextMate() diff --git a/src/Views/Welcome.axaml b/src/Views/Welcome.axaml index ceccc0ce..fb74205f 100644 --- a/src/Views/Welcome.axaml +++ b/src/Views/Welcome.axaml @@ -117,7 +117,7 @@ Classes="italic" Margin="0,0,8,0" HorizontalAlignment="Center" VerticalAlignment="Center" - FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Decrease}}" + FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}" Text="{DynamicResource Text.Welcome.DragDropTip}" Foreground="{DynamicResource Brush.FG2}"/> diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index d05236d5..ff9d5dda 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -117,7 +117,7 @@ BorderBrush="{DynamicResource Brush.Border0}"/> - + @@ -158,23 +158,16 @@ + + + - + - +