diff --git a/README.md b/README.md index 8ebbbb98..6a688d99 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ # SourceGit - Opensource Git GUI client. -![stars](https://img.shields.io/github/stars/sourcegit-scm/sourcegit.svg) ![forks](https://img.shields.io/github/forks/sourcegit-scm/sourcegit.svg) ![license](https://img.shields.io/github/license/sourcegit-scm/sourcegit.svg) ![latest](https://img.shields.io/github/v/release/sourcegit-scm/sourcegit.svg) ![downloads](https://img.shields.io/github/downloads/sourcegit-scm/sourcegit/total) +[![stars](https://img.shields.io/github/stars/sourcegit-scm/sourcegit.svg)](https://github.com/sourcegit-scm/sourcegit/stargazers) +[![forks](https://img.shields.io/github/forks/sourcegit-scm/sourcegit.svg)](https://github.com/sourcegit-scm/sourcegit/forks) +[![license](https://img.shields.io/github/license/sourcegit-scm/sourcegit.svg)](LICENSE) +[![latest](https://img.shields.io/github/v/release/sourcegit-scm/sourcegit.svg)](https://github.com/sourcegit-scm/sourcegit/releases/latest) +[![downloads](https://img.shields.io/github/downloads/sourcegit-scm/sourcegit/total)](https://github.com/sourcegit-scm/sourcegit/releases) ## Highlights * Supports Windows/macOS/Linux * Opensource/Free * Fast -* English/Français/Deutsch/Português/Русский/简体中文/繁體中文 +* Deutsch/English/Español/Français/Português/Русский/简体中文/繁體中文 * Built-in light/dark themes * Customize theme * Visual commit graph @@ -43,7 +47,7 @@ ## Translation Status -[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-98.95%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-90.36%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-93.52%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-99.10%25-yellow)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) +[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-96.05%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.08%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-87.72%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-90.79%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) ## How to Use @@ -80,11 +84,16 @@ For **Windows** users: For **macOS** users: -* Download `sourcegit_x.y.osx-x64.zip` or `sourcegit_x.y.osx-arm64.zip` from Releases. `x64` for Intel and `arm64` for Apple Silicon. -* Move `SourceGit.app` to `Applications` folder. -* Make sure your mac trusts all software from anywhere. For more information, search `spctl --master-disable`. +* Thanks [@ybeapps](https://github.com/ybeapps) for making `SourceGit` available on `Homebrew`. You can simply install it with following command: + ```shell + brew tap ybeapps/homebrew-sourcegit + brew install --cask --no-quarantine sourcegit + ``` +* If you want to install `SourceGit.app` from Github Release manually, you need run following command to make sure it works: + ```shell + sudo xattr -cr /Applications/SourceGit.app + ``` * Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your mac. -* You may need to run `sudo xattr -cr /Applications/SourceGit.app` to make sure the software works. * You can run `echo $PATH > ~/Library/Application\ Support/SourceGit/PATH` to generate a custom PATH env file to introduce `PATH` env to SourceGit. For **Linux** users: @@ -110,14 +119,15 @@ For other AI service: This app supports open repository in external tools listed in the table below. -| Tool | Windows | macOS | Linux | KEY IN `external_editors.json` | -|-------------------------------|---------|-------|-------|--------------------------------| -| Visual Studio Code | YES | YES | YES | VSCODE | -| Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS | -| VSCodium | YES | YES | YES | VSCODIUM | -| JetBrains Fleet | YES | YES | YES | FLEET | -| Sublime Text | YES | YES | YES | SUBLIME_TEXT | -| Zed | NO | YES | YES | ZED | +| Tool | Windows | macOS | Linux | +|-------------------------------|---------|-------|-------| +| Visual Studio Code | YES | YES | YES | +| Visual Studio Code - Insiders | YES | YES | YES | +| VSCodium | YES | YES | YES | +| Fleet | YES | YES | YES | +| Sublime Text | YES | YES | YES | +| Zed | NO | YES | YES | +| Visual Studio | YES | NO | NO | > [!NOTE] > This app will try to find those tools based on some pre-defined or expected locations automatically. If you are using one portable version of these tools, it will not be detected by this app. @@ -125,7 +135,7 @@ This app supports open repository in external tools listed in the table below. ```json { "tools": { - "VSCODE": "D:\\VSCode\\Code.exe" + "Visual Studio Code": "D:\\VSCode\\Code.exe" } } ``` @@ -153,4 +163,4 @@ Everyone is welcome to submit a PR. Please make sure your PR is based on the lat Thanks to all the people who contribute. -[![Contributors](https://contrib.rocks/image?repo=sourcegit-scm/sourcegit&columns=10)](https://github.com/sourcegit-scm/sourcegit/graphs/contributors) +[![Contributors](https://contrib.rocks/image?repo=sourcegit-scm/sourcegit&columns=20)](https://github.com/sourcegit-scm/sourcegit/graphs/contributors) diff --git a/SourceGit.sln b/SourceGit.sln index abd42aee..9799a09e 100644 --- a/SourceGit.sln +++ b/SourceGit.sln @@ -76,6 +76,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "appimage", "appimage", "{5D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{C54D4001-9940-477C-A0B6-E795ED0A3209}" ProjectSection(SolutionItems) = preProject + build\scripts\localization-check.js = build\scripts\localization-check.js build\scripts\package.linux.sh = build\scripts\package.linux.sh build\scripts\package.osx-app.sh = build\scripts\package.osx-app.sh build\scripts\package.windows-portable.sh = build\scripts\package.windows-portable.sh diff --git a/TRANSLATION.md b/TRANSLATION.md index ed06afef..b6c255a1 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -1,20 +1,69 @@ -### de_DE.axaml: 98.95% +### de_DE.axaml: 96.05%
Missing Keys +- Text.BranchCM.FetchInto +- Text.ChangeCM.GenerateCommitMessage +- Text.CommitCM.CustomAction +- Text.Configure.CustomAction +- Text.Configure.CustomAction.Arguments +- Text.Configure.CustomAction.Arguments.Tip +- Text.Configure.CustomAction.Executable +- Text.Configure.CustomAction.Name +- Text.Configure.CustomAction.Scope +- Text.Configure.CustomAction.Scope.Commit +- Text.Configure.CustomAction.Scope.Repository +- Text.Configure.Git.EnablePruneOnFetch - Text.Configure.Git.EnableSignOff - Text.Configure.IssueTracker.AddSampleGitLabIssue - Text.Configure.IssueTracker.AddSampleGitLabMergeRequest -- Text.Preference.Advanced +- Text.Configure.OpenAI +- Text.Configure.OpenAI.Prefered +- Text.Configure.OpenAI.Prefered.Tip +- Text.ExecuteCustomAction +- Text.ExecuteCustomAction.Name - Text.Preference.AI.AnalyzeDiffPrompt - Text.Preference.AI.GenerateSubjectPrompt +- Text.Preference.AI.Name +- Text.Repository.CustomActions +- Text.Repository.CustomActions.Empty +- Text.Stash.KeepIndex - Text.WorkingCopy.ConfirmCommitWithoutFiles
-### fr_FR.axaml: 90.36% +### es_ES.axaml: 97.08% + + +
+Missing Keys + +- Text.ChangeCM.GenerateCommitMessage +- Text.CommitCM.CustomAction +- Text.Configure.CustomAction +- Text.Configure.CustomAction.Arguments +- Text.Configure.CustomAction.Arguments.Tip +- Text.Configure.CustomAction.Executable +- Text.Configure.CustomAction.Name +- Text.Configure.CustomAction.Scope +- Text.Configure.CustomAction.Scope.Commit +- Text.Configure.CustomAction.Scope.Repository +- Text.Configure.Git.EnablePruneOnFetch +- Text.Configure.OpenAI +- Text.Configure.OpenAI.Prefered +- Text.Configure.OpenAI.Prefered.Tip +- Text.ExecuteCustomAction +- Text.ExecuteCustomAction.Name +- Text.Preference.AI.Name +- Text.Repository.CustomActions +- Text.Repository.CustomActions.Empty +- Text.Stash.KeepIndex + +
+ +### fr_FR.axaml: 87.72%
@@ -23,16 +72,31 @@ - Text.About.Chart - Text.AIAssistant - Text.AIAssistant.Tip +- Text.BranchCM.FetchInto +- Text.ChangeCM.GenerateCommitMessage - Text.CherryPick.AppendSourceToMessage - Text.CherryPick.Mainline - Text.CherryPick.Mainline.Tips - Text.CommitCM.CherryPickMultiple +- Text.CommitCM.CustomAction - Text.CommitCM.SquashCommitsSinceThis - Text.CommitDetail.Info.WebLinks +- Text.Configure.CustomAction +- Text.Configure.CustomAction.Arguments +- Text.Configure.CustomAction.Arguments.Tip +- Text.Configure.CustomAction.Executable +- Text.Configure.CustomAction.Name +- Text.Configure.CustomAction.Scope +- Text.Configure.CustomAction.Scope.Commit +- Text.Configure.CustomAction.Scope.Repository - Text.Configure.Git.DefaultRemote +- Text.Configure.Git.EnablePruneOnFetch - Text.Configure.Git.EnableSignOff - Text.Configure.IssueTracker.AddSampleGitLabIssue - Text.Configure.IssueTracker.AddSampleGitLabMergeRequest +- Text.Configure.OpenAI +- Text.Configure.OpenAI.Prefered +- Text.Configure.OpenAI.Prefered.Tip - Text.ConfigureWorkspace - Text.ConfigureWorkspace.Color - Text.ConfigureWorkspace.Restore @@ -45,6 +109,8 @@ - Text.ConventionalCommit.Type - Text.Diff.IgnoreWhitespace - Text.Discard.IncludeIgnored +- Text.ExecuteCustomAction +- Text.ExecuteCustomAction.Name - Text.FileHistory.FileChange - Text.GitLFS.Locks.OnlyMine - Text.Histories.Header.AuthorTime @@ -55,12 +121,12 @@ - Text.Hotkeys.Repo.DiscardSelected - Text.MoveRepositoryNode - Text.MoveRepositoryNode.Target -- Text.Preference.Advanced - Text.Preference.AI - Text.Preference.AI.AnalyzeDiffPrompt - Text.Preference.AI.ApiKey - Text.Preference.AI.GenerateSubjectPrompt - Text.Preference.AI.Model +- Text.Preference.AI.Name - Text.Preference.AI.Server - Text.Preference.General.ShowAuthorTime - Text.Preference.Integration @@ -68,11 +134,14 @@ - Text.Preference.Shell.Type - Text.Preference.Shell.Path - Text.Repository.AutoFetching +- Text.Repository.CustomActions +- Text.Repository.CustomActions.Empty - Text.Repository.EnableReflog - Text.Repository.Search.InCurrentBranch - Text.ScanRepositories - Text.ScanRepositories.RootDir - Text.Squash.Into +- Text.Stash.KeepIndex - Text.Stash.OnlyStagedChanges - Text.Stash.TipForSelectedFiles - Text.Statistics.Overview @@ -87,7 +156,7 @@
-### pt_BR.axaml: 93.52% +### pt_BR.axaml: 90.79%
@@ -96,18 +165,33 @@ - Text.About.Chart - Text.AIAssistant - Text.AIAssistant.Tip +- Text.BranchCM.FetchInto +- Text.ChangeCM.GenerateCommitMessage - Text.CherryPick.AppendSourceToMessage - Text.CherryPick.Mainline - Text.CherryPick.Mainline.Tips - Text.CommitCM.CherryPickMultiple +- Text.CommitCM.CustomAction - Text.CommitCM.SquashCommitsSinceThis - Text.CommitDetail.Info.ContainsIn - Text.CommitDetail.Info.ContainsIn.Title - Text.CommitDetail.Info.WebLinks +- Text.Configure.CustomAction +- Text.Configure.CustomAction.Arguments +- Text.Configure.CustomAction.Arguments.Tip +- Text.Configure.CustomAction.Executable +- Text.Configure.CustomAction.Name +- Text.Configure.CustomAction.Scope +- Text.Configure.CustomAction.Scope.Commit +- Text.Configure.CustomAction.Scope.Repository - Text.Configure.Git.DefaultRemote +- Text.Configure.Git.EnablePruneOnFetch - Text.Configure.Git.EnableSignOff - Text.Configure.IssueTracker.AddSampleGitLabIssue - Text.Configure.IssueTracker.AddSampleGitLabMergeRequest +- Text.Configure.OpenAI +- Text.Configure.OpenAI.Prefered +- Text.Configure.OpenAI.Prefered.Tip - Text.ConfigureWorkspace - Text.ConfigureWorkspace.Color - Text.ConfigureWorkspace.Restore @@ -120,14 +204,19 @@ - Text.ConventionalCommit.Type - Text.CopyAllText - Text.Discard.IncludeIgnored +- Text.ExecuteCustomAction +- Text.ExecuteCustomAction.Name - Text.FileHistory.FileContent - Text.FileHistory.FileChange - Text.GitLFS.Locks.OnlyMine - Text.MoveRepositoryNode - Text.MoveRepositoryNode.Target -- Text.Preference.Advanced +- Text.Preference.AI.Name - Text.Push.CheckSubmodules +- Text.Repository.CustomActions +- Text.Repository.CustomActions.Empty - Text.Squash.Into +- Text.Stash.KeepIndex - Text.Stash.OnlyStagedChanges - Text.Stash.TipForSelectedFiles - Text.Statistics.Overview @@ -149,18 +238,13 @@
-### zh_CN.axaml: 99.10% +### zh_CN.axaml: 100.00%
Missing Keys -- Text.Preference.AI -- Text.Preference.AI.AnalyzeDiffPrompt -- Text.Preference.AI.ApiKey -- Text.Preference.AI.GenerateSubjectPrompt -- Text.Preference.AI.Model -- Text.Preference.AI.Server +
diff --git a/VERSION b/VERSION index f42a1bc8..a5e93156 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.36 \ No newline at end of file +8.37 \ No newline at end of file diff --git a/build/resources/rpm/SPECS/build.spec b/build/resources/rpm/SPECS/build.spec index 289cbe39..9dda5f96 100644 --- a/build/resources/rpm/SPECS/build.spec +++ b/build/resources/rpm/SPECS/build.spec @@ -5,8 +5,8 @@ Summary: Open-source & Free Git Gui Client License: MIT URL: https://sourcegit-scm.github.io/ Source: https://github.com/sourcegit-scm/sourcegit/archive/refs/tags/v%_version.tar.gz -Requires: libX11 -Requires: libSM +Requires: (libX11 or libX11-6) +Requires: (libSM or libSM6) %define _build_id_links none diff --git a/src/App.axaml b/src/App.axaml index b1fe303b..fc55776e 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -18,6 +18,7 @@ + diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 682ec5fc..dfec763b 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -59,6 +59,10 @@ namespace SourceGit builder.UsePlatformDetect(); builder.LogToTrace(); builder.WithInterFont(); + builder.With(new FontManagerOptions() + { + DefaultFamilyName = "fonts:Inter#Inter" + }); builder.ConfigureFonts(manager => { var monospace = new EmbeddedFontCollection( @@ -223,7 +227,7 @@ namespace SourceGit if (onlyUseMonospaceFontInEditor) { if (string.IsNullOrEmpty(defaultFont)) - resDic.Add("Fonts.Primary", new FontFamily("fonts:Inter#Inter, $Default")); + resDic.Add("Fonts.Primary", new FontFamily("fonts:Inter#Inter")); else resDic.Add("Fonts.Primary", new FontFamily(defaultFont)); } diff --git a/src/Commands/ExecuteCustomAction.cs b/src/Commands/ExecuteCustomAction.cs new file mode 100644 index 00000000..253c6b43 --- /dev/null +++ b/src/Commands/ExecuteCustomAction.cs @@ -0,0 +1,63 @@ +using System; +using System.Diagnostics; +using System.Text; + +using Avalonia.Threading; + +namespace SourceGit.Commands +{ + public static class ExecuteCustomAction + { + public static void Run(string repo, string file, string args, Action outputHandler) + { + var start = new ProcessStartInfo(); + start.FileName = file; + start.Arguments = args; + start.UseShellExecute = false; + start.CreateNoWindow = true; + start.RedirectStandardOutput = true; + start.RedirectStandardError = true; + start.StandardOutputEncoding = Encoding.UTF8; + 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 }; + proc.OutputDataReceived += (_, e) => + { + if (e.Data != null) + outputHandler?.Invoke(e.Data); + }; + + proc.ErrorDataReceived += (_, e) => + { + if (e.Data != null) + outputHandler?.Invoke(e.Data); + }; + + try + { + proc.Start(); + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + proc.WaitForExit(); + } + catch (Exception e) + { + Dispatcher.UIThread.Invoke(() => + { + App.RaiseException(repo, e.Message); + }); + } + + proc.Close(); + } + } +} diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs index ea4e54bf..08d2d1c6 100644 --- a/src/Commands/Fetch.cs +++ b/src/Commands/Fetch.cs @@ -4,7 +4,7 @@ namespace SourceGit.Commands { public class Fetch : Command { - public Fetch(string repo, string remote, bool noTags, Action outputHandler) + public Fetch(string repo, string remote, bool noTags, bool prune, Action outputHandler) { _outputHandler = outputHandler; WorkingDirectory = repo; @@ -18,9 +18,22 @@ namespace SourceGit.Commands else Args += "--force "; + if (prune) + Args += "--prune "; + Args += remote; } + public Fetch(string repo, Models.Branch local, Models.Branch remote, Action outputHandler) + { + _outputHandler = outputHandler; + WorkingDirectory = repo; + Context = repo; + TraitErrorAsOutput = true; + SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey"); + Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}"; + } + protected override void OnReadline(string line) { _outputHandler?.Invoke(line); diff --git a/src/Commands/GenerateCommitMessage.cs b/src/Commands/GenerateCommitMessage.cs index 3bcf7010..e4f25f38 100644 --- a/src/Commands/GenerateCommitMessage.cs +++ b/src/Commands/GenerateCommitMessage.cs @@ -20,8 +20,9 @@ namespace SourceGit.Commands } } - public GenerateCommitMessage(string repo, List changes, CancellationToken cancelToken, Action onProgress) + public GenerateCommitMessage(Models.OpenAIService service, string repo, List changes, CancellationToken cancelToken, Action onProgress) { + _service = service; _repo = repo; _changes = changes; _cancelToken = cancelToken; @@ -75,7 +76,7 @@ namespace SourceGit.Commands var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd(); var diff = rs.IsSuccess ? rs.StdOut : "unknown change"; - var rsp = Models.OpenAI.Chat(Models.OpenAI.AnalyzeDiffPrompt, $"Here is the `git diff` output: {diff}", _cancelToken); + var rsp = _service.Chat(_service.AnalyzeDiffPrompt, $"Here is the `git diff` output: {diff}", _cancelToken); if (rsp != null && rsp.Choices.Count > 0) return rsp.Choices[0].Message.Content; @@ -84,13 +85,14 @@ namespace SourceGit.Commands private string GenerateSubject(string summary) { - var rsp = Models.OpenAI.Chat(Models.OpenAI.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summary}", _cancelToken); + var rsp = _service.Chat(_service.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summary}", _cancelToken); if (rsp != null && rsp.Choices.Count > 0) return rsp.Choices[0].Message.Content; return string.Empty; } + private Models.OpenAIService _service; private string _repo; private List _changes; private CancellationToken _cancelToken; diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs index a4efa4b6..732530f5 100644 --- a/src/Commands/Pull.cs +++ b/src/Commands/Pull.cs @@ -4,7 +4,7 @@ namespace SourceGit.Commands { public class Pull : Command { - public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action outputHandler) + public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, bool prune, Action outputHandler) { _outputHandler = outputHandler; WorkingDirectory = repo; @@ -17,6 +17,8 @@ namespace SourceGit.Commands Args += "--rebase "; if (noTags) Args += "--no-tags "; + if (prune) + Args += "--prune "; Args += $"{remote} {branch}"; } diff --git a/src/Commands/QueryCommitSignInfo.cs b/src/Commands/QueryCommitSignInfo.cs new file mode 100644 index 00000000..5c81cf57 --- /dev/null +++ b/src/Commands/QueryCommitSignInfo.cs @@ -0,0 +1,35 @@ +namespace SourceGit.Commands +{ + public class QueryCommitSignInfo : Command + { + public QueryCommitSignInfo(string repo, string sha, bool useFakeSignersFile) + { + WorkingDirectory = repo; + Context = repo; + + const string baseArgs = "show --no-show-signature --pretty=format:\"%G?%n%GS%n%GK\" -s"; + const string fakeSignersFileArg = "-c gpg.ssh.allowedSignersFile=/dev/null"; + Args = $"{(useFakeSignersFile ? fakeSignersFileArg : string.Empty)} {baseArgs} {sha}"; + } + + public Models.CommitSignInfo Result() + { + var rs = ReadToEnd(); + if (!rs.IsSuccess) + return null; + + var raw = rs.StdOut.Trim(); + if (raw.Length <= 1) + return null; + + var lines = raw.Split('\n'); + return new Models.CommitSignInfo() + { + VerifyResult = lines[0][0], + Signer = lines[1], + Key = lines[2] + }; + + } + } +} diff --git a/src/Commands/QueryFileSize.cs b/src/Commands/QueryFileSize.cs index c36984dd..9016d826 100644 --- a/src/Commands/QueryFileSize.cs +++ b/src/Commands/QueryFileSize.cs @@ -11,7 +11,7 @@ namespace SourceGit.Commands { WorkingDirectory = repo; Context = repo; - Args = $"ls-tree {revision} -l -- {file}"; + Args = $"ls-tree {revision} -l -- \"{file}\""; } public long Result() diff --git a/src/Commands/Stash.cs b/src/Commands/Stash.cs index fbdb6bd4..77f1af53 100644 --- a/src/Commands/Stash.cs +++ b/src/Commands/Stash.cs @@ -17,24 +17,29 @@ namespace SourceGit.Commands return Exec(); } - public bool Push(List changes, string message, bool onlyStaged) + public bool Push(List changes, string message, bool onlyStaged, bool keepIndex) { - var pathsBuilder = new StringBuilder(); + var builder = new StringBuilder(); + builder.Append("stash push "); + if (onlyStaged) + builder.Append("--staged "); + if (keepIndex) + builder.Append("--keep-index "); + builder.Append("-m \""); + builder.Append(message); + builder.Append("\" -- "); if (onlyStaged) { foreach (var c in changes) - pathsBuilder.Append($"\"{c.Path}\" "); - - var paths = pathsBuilder.ToString(); - Args = $"stash push --staged -m \"{message}\" -- {paths}"; + builder.Append($"\"{c.Path}\" "); } else { var needAdd = new List(); foreach (var c in changes) { - pathsBuilder.Append($"\"{c.Path}\" "); + builder.Append($"\"{c.Path}\" "); if (c.WorkTree == Models.ChangeState.Added || c.WorkTree == Models.ChangeState.Untracked) { @@ -51,11 +56,9 @@ namespace SourceGit.Commands new Add(WorkingDirectory, needAdd).Exec(); needAdd.Clear(); } - - var paths = pathsBuilder.ToString(); - Args = $"stash push -m \"{message}\" -- {paths}"; } + Args = builder.ToString(); return Exec(); } diff --git a/src/Models/CommitSignInfo.cs b/src/Models/CommitSignInfo.cs new file mode 100644 index 00000000..44b95e61 --- /dev/null +++ b/src/Models/CommitSignInfo.cs @@ -0,0 +1,60 @@ +using Avalonia.Media; + +namespace SourceGit.Models +{ + public class CommitSignInfo + { + public char VerifyResult { get; init; } = 'N'; + public string Signer { get; init; } = string.Empty; + public string Key { get; init; } = string.Empty; + public bool HasSigner => !string.IsNullOrEmpty(Signer); + + public IBrush Brush + { + get + { + switch (VerifyResult) + { + case 'G': + case 'U': + return Brushes.Green; + case 'X': + case 'Y': + case 'R': + return Brushes.DarkOrange; + case 'B': + case 'E': + return Brushes.Red; + default: + return Brushes.Transparent; + } + } + } + + public string ToolTip + { + get + { + switch (VerifyResult) + { + case 'G': + return "Good signature."; + case 'U': + return "Good signature with unknown validity."; + case 'X': + return "Good signature but has expired."; + case 'Y': + return "Good signature made by expired key."; + case 'R': + return "Good signature made by a revoked key."; + case 'B': + return "Bad signature."; + case 'E': + return "Signature cannot be checked."; + default: + return "No signature."; + } + } + } + } +} diff --git a/src/Models/CustomAction.cs b/src/Models/CustomAction.cs new file mode 100644 index 00000000..2a400b02 --- /dev/null +++ b/src/Models/CustomAction.cs @@ -0,0 +1,42 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.Models +{ + public enum CustomActionScope + { + Repository, + Commit, + } + + public class CustomAction : ObservableObject + { + public string Name + { + get => _name; + set => SetProperty(ref _name, value); + } + + public CustomActionScope Scope + { + get => _scope; + set => SetProperty(ref _scope, value); + } + + public string Executable + { + get => _executable; + set => SetProperty(ref _executable, value); + } + + public string Arguments + { + get => _arguments; + set => SetProperty(ref _arguments, value); + } + + private string _name = string.Empty; + private CustomActionScope _scope = CustomActionScope.Repository; + private string _executable = string.Empty; + private string _arguments = string.Empty; + } +} diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs index b26a9a90..103e91bc 100644 --- a/src/Models/ExternalTool.cs +++ b/src/Models/ExternalTool.cs @@ -13,15 +13,13 @@ namespace SourceGit.Models public class ExternalTool { public string Name { get; private set; } - public string Executable { get; private set; } - public string OpenCmdArgs { get; private set; } public Bitmap IconImage { get; private set; } = null; - public ExternalTool(string name, string icon, string executable, string openCmdArgs) + public ExternalTool(string name, string icon, string execFile, Func execArgsGenerator = null) { Name = name; - Executable = executable; - OpenCmdArgs = openCmdArgs; + _execFile = execFile; + _execArgsGenerator = execArgsGenerator ?? (repo => $"\"{repo}\""); try { @@ -40,11 +38,14 @@ namespace SourceGit.Models Process.Start(new ProcessStartInfo() { WorkingDirectory = repo, - FileName = Executable, - Arguments = string.Format(OpenCmdArgs, repo), + FileName = _execFile, + Arguments = _execArgsGenerator.Invoke(repo), UseShellExecute = false, }); } + + private string _execFile = string.Empty; + private Func _execArgsGenerator = null; } public class JetBrainsState @@ -110,48 +111,48 @@ namespace SourceGit.Models _customPaths = new ExternalToolPaths(); } - public void TryAdd(string name, string icon, string args, string key, Func finder) + public void TryAdd(string name, string icon, Func finder, Func execArgsGenerator = null) { - if (_customPaths.Tools.TryGetValue(key, out var customPath) && File.Exists(customPath)) + if (_customPaths.Tools.TryGetValue(name, out var customPath) && File.Exists(customPath)) { - Founded.Add(new ExternalTool(name, icon, customPath, args)); + Founded.Add(new ExternalTool(name, icon, customPath, execArgsGenerator)); } else { var path = finder(); if (!string.IsNullOrEmpty(path) && File.Exists(path)) - Founded.Add(new ExternalTool(name, icon, path, args)); + Founded.Add(new ExternalTool(name, icon, path, execArgsGenerator)); } } public void VSCode(Func platformFinder) { - TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE", platformFinder); + TryAdd("Visual Studio Code", "vscode", platformFinder); } public void VSCodeInsiders(Func platformFinder) { - TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS", platformFinder); + TryAdd("Visual Studio Code - Insiders", "vscode_insiders", platformFinder); } public void VSCodium(Func platformFinder) { - TryAdd("VSCodium", "codium", "\"{0}\"", "VSCODIUM", platformFinder); + TryAdd("VSCodium", "codium", platformFinder); } public void Fleet(Func platformFinder) { - TryAdd("Fleet", "fleet", "\"{0}\"", "FLEET", platformFinder); + TryAdd("Fleet", "fleet", platformFinder); } public void SublimeText(Func platformFinder) { - TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT", platformFinder); + TryAdd("Sublime Text", "sublime_text", platformFinder); } public void Zed(Func platformFinder) { - TryAdd("Zed", "zed", "\"{0}\"", "ZED", platformFinder); + TryAdd("Zed", "zed", platformFinder); } public void FindJetBrainsFromToolbox(Func platformFinder) @@ -170,8 +171,7 @@ namespace SourceGit.Models Founded.Add(new ExternalTool( $"{tool.DisplayName} {tool.DisplayVersion}", supported_icons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : "JetBrains/JB", - Path.Combine(tool.InstallLocation, tool.LaunchCommand), - "\"{0}\"")); + Path.Combine(tool.InstallLocation, tool.LaunchCommand))); } } } diff --git a/src/Models/Locales.cs b/src/Models/Locales.cs index 0d9e5f69..9d24f491 100644 --- a/src/Models/Locales.cs +++ b/src/Models/Locales.cs @@ -8,8 +8,9 @@ namespace SourceGit.Models public string Key { get; set; } public static readonly List Supported = new List() { - new Locale("English", "en_US"), new Locale("Deutsch", "de_DE"), + new Locale("English", "en_US"), + new Locale("Español", "es_ES"), new Locale("Français", "fr_FR"), new Locale("Português (Brasil)", "pt_BR"), new Locale("Русский", "ru_RU"), diff --git a/src/Models/NumericSort.cs b/src/Models/NumericSort.cs index bdff95e6..ed5002e6 100644 --- a/src/Models/NumericSort.cs +++ b/src/Models/NumericSort.cs @@ -21,6 +21,10 @@ int loc2 = 0; bool isDigit1 = char.IsDigit(c1); + bool isDigit2 = char.IsDigit(c2); + if (isDigit1 != isDigit2) + return c1.CompareTo(c2); + do { tmp1[loc1] = c1; @@ -33,7 +37,6 @@ break; } while (char.IsDigit(c1) == isDigit1); - bool isDigit2 = char.IsDigit(c2); do { tmp2[loc2] = c2; @@ -49,16 +52,10 @@ string sub1 = new string(tmp1, 0, loc1); string sub2 = new string(tmp2, 0, loc2); int result; - if (isDigit1 && isDigit2) - { - int num1 = int.Parse(sub1); - int num2 = int.Parse(sub2); - result = num1 - num2; - } + if (isDigit1) + result = loc1 == loc2 ? string.CompareOrdinal(sub1, sub2) : loc1 - loc2; else - { - result = string.Compare(sub1, sub2, System.StringComparison.Ordinal); - } + result = string.CompareOrdinal(sub1, sub2); if (result != 0) return result; diff --git a/src/Models/OpenAI.cs b/src/Models/OpenAI.cs index b1bb9465..e9c7b5ed 100644 --- a/src/Models/OpenAI.cs +++ b/src/Models/OpenAI.cs @@ -6,6 +6,8 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; +using CommunityToolkit.Mvvm.ComponentModel; + namespace SourceGit.Models { public class OpenAIChatMessage @@ -74,44 +76,78 @@ namespace SourceGit.Models } } - public static class OpenAI + public class OpenAIService : ObservableObject { - public static string Server + public string Name { - get; - set; + get => _name; + set => SetProperty(ref _name, value); } - public static string ApiKey + public string Server { - get; - set; + get => _server; + set => SetProperty(ref _server, value); } - public static string Model + public string ApiKey { - get; - set; + get => _apiKey; + set => SetProperty(ref _apiKey, value); } - public static string AnalyzeDiffPrompt + public string Model { - get; - set; + get => _model; + set => SetProperty(ref _model, value); } - public static string GenerateSubjectPrompt + public string AnalyzeDiffPrompt { - get; - set; + get => _analyzeDiffPrompt; + set => SetProperty(ref _analyzeDiffPrompt, value); } - public static bool IsValid + public string GenerateSubjectPrompt { - get => !string.IsNullOrEmpty(Server) && !string.IsNullOrEmpty(Model); + get => _generateSubjectPrompt; + set => SetProperty(ref _generateSubjectPrompt, value); } - public static OpenAIChatResponse Chat(string prompt, string question, CancellationToken cancellation) + public OpenAIService() + { + AnalyzeDiffPrompt = """ + You are an expert developer specialist in creating commits. + Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules: + - Do not use any code snippets, imports, file routes or bullets points. + - Do not mention the route of file that has been change. + - Write clear, concise, and descriptive messages that explain the MAIN GOAL made of the changes. + - Use the present tense and active voice in the message, for example, "Fix bug" instead of "Fixed bug.". + - Use the imperative mood, which gives the message a sense of command, e.g. "Add feature" instead of "Added feature". + - Avoid using general terms like "update" or "change", be specific about what was updated or changed. + - Avoid using terms like "The main goal of", just output directly the summary in plain text + """; + + GenerateSubjectPrompt = """ + You are an expert developer specialist in creating commits messages. + Your only goal is to retrieve a single commit message. + Based on the provided user changes, combine them in ONE SINGLE commit message retrieving the global idea, following strictly the next rules: + - Assign the commit {type} according to the next conditions: + feat: Only when adding a new feature. + fix: When fixing a bug. + docs: When updating documentation. + style: When changing elements styles or design and/or making changes to the code style (formatting, missing semicolons, etc.) without changing the code logic. + test: When adding or updating tests. + chore: When making changes to the build process or auxiliary tools and libraries. + revert: When undoing a previous commit. + refactor: When restructuring code without changing its external behavior, or is any of the other refactor types. + - Do not add any issues numeration, explain your output nor introduce your answer. + - Output directly only one commit message in plain text with the next format: {type}: {commit_message}. + - Be as concise as possible, keep the message under 50 characters. + """; + } + + public OpenAIChatResponse Chat(string prompt, string question, CancellationToken cancellation) { var chat = new OpenAIChatRequest() { Model = Model }; chat.AddMessage("system", prompt); @@ -144,5 +180,12 @@ namespace SourceGit.Models throw; } } + + private string _name; + private string _server; + private string _apiKey; + private string _model; + private string _analyzeDiffPrompt; + private string _generateSubjectPrompt; } } diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs index 40c00bc9..77c58ee7 100644 --- a/src/Models/RepositorySettings.cs +++ b/src/Models/RepositorySettings.cs @@ -16,6 +16,12 @@ namespace SourceGit.Models set; } = DealWithLocalChanges.DoNothing; + public bool EnablePruneOnFetch + { + get; + set; + } = false; + public bool FetchWithoutTags { get; @@ -94,6 +100,12 @@ namespace SourceGit.Models set; } = new AvaloniaList(); + public AvaloniaList CustomActions + { + get; + set; + } = new AvaloniaList(); + public bool EnableAutoFetch { get; @@ -112,6 +124,30 @@ namespace SourceGit.Models set; } = false; + public bool IncludeUntrackedWhenStash + { + get; + set; + } = true; + + public bool OnlyStagedWhenStash + { + get; + set; + } = false; + + public bool KeepIndexWhenStash + { + get; + set; + } = false; + + public string PreferedOpenAIService + { + get; + set; + } = "---"; + public void PushCommitMessage(string message) { var existIdx = CommitMessages.IndexOf(message); @@ -200,5 +236,22 @@ namespace SourceGit.Models if (rule != null) IssueTrackerRules.Remove(rule); } + + public CustomAction AddNewCustomAction() + { + var act = new CustomAction() + { + Name = "Unnamed Custom Action", + }; + + CustomActions.Add(act); + return act; + } + + public void RemoveCustomAction(CustomAction act) + { + if (act != null) + CustomActions.Remove(act); + } } } diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index 6ca0bbb0..a57d26d2 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -134,6 +134,7 @@ namespace SourceGit.Native finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe"); finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\JetBrains\\Toolbox"); finder.SublimeText(FindSublimeText); + finder.TryAdd("Visual Studio", "vs", FindVisualStudio, GenerateCommandlineArgsForVisualStudio); return finder.Founded; } @@ -313,6 +314,27 @@ namespace SourceGit.Native return string.Empty; } + + private string FindVisualStudio() + { + var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey( + Microsoft.Win32.RegistryHive.LocalMachine, + Microsoft.Win32.RegistryView.Registry64); + + // Get default class for VisualStudio.Launcher.sln - the handler for *.sln files + if (localMachine.OpenSubKey(@"SOFTWARE\Classes\VisualStudio.Launcher.sln\CLSID") is Microsoft.Win32.RegistryKey launcher) + { + // Get actual path to the executable + if (launcher.GetValue(string.Empty) is string CLSID && + localMachine.OpenSubKey(@$"SOFTWARE\Classes\CLSID\{CLSID}\LocalServer32") is Microsoft.Win32.RegistryKey devenv && + devenv.GetValue(string.Empty) is string localServer32) + { + return localServer32!.Trim('\"'); + } + } + + return string.Empty; + } #endregion private void OpenFolderAndSelectFile(string folderPath) @@ -328,5 +350,31 @@ namespace SourceGit.Native ILFree(pidl); } } + + private string GenerateCommandlineArgsForVisualStudio(string repo) + { + var sln = FindVSSolutionFile(repo, 4); + return string.IsNullOrEmpty(sln) ? $"\"{repo}\"" : $"\"{sln}\""; + } + + private string FindVSSolutionFile(string path, int leftDepth) + { + var found = Directory.GetFiles(path, "*.sln", SearchOption.TopDirectoryOnly); + if (found != null && found.Length > 0) + return Path.GetFullPath(found[0]); + + if (leftDepth <= 0) + return null; + + var subfolders = Directory.GetDirectories(path); + foreach (var subfolder in subfolders) + { + var first = FindVSSolutionFile(subfolder, leftDepth - 1); + if (!string.IsNullOrEmpty(first)) + return first; + } + + return null; + } } } diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 1a84597e..6cf53d96 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -1,4 +1,5 @@ + M41 512c0-128 46-241 138-333C271 87 384 41 512 41s241 46 333 138c92 92 138 205 138 333s-46 241-138 333c-92 92-205 138-333 138s-241-46-333-138C87 753 41 640 41 512zm87 0c0 108 36 195 113 271s164 113 271 113c108 0 195-36 271-113s113-164 113-271-36-195-113-271c-77-77-164-113-271-113-108 0-195 36-271 113C164 317 128 404 128 512zm256 148V292l195 113L768 512l-195 113-195 113v-77zm148-113-61 36V440l61 36 61 36-61 36z M951 419a255 255 0 00-22-209 258 258 0 00-278-124A259 259 0 00213 178a255 255 0 00-171 124 258 258 0 0032 303 255 255 0 0022 210 258 258 0 00278 124A255 255 0 00566 1024a258 258 0 00246-179 256 256 0 00171-124 258 258 0 00-32-302zM566 957a191 191 0 01-123-44l6-3 204-118a34 34 0 0017-29v-287l86 50a3 3 0 012 2v238a192 192 0 01-192 192zM154 781a191 191 0 01-23-129l6 4 204 118a33 33 0 0033 0l249-144v99a3 3 0 01-1 3L416 851a192 192 0 01-262-70zM100 337a191 191 0 01101-84V495a33 33 0 0017 29l248 143-86 50a3 3 0 01-3 0l-206-119A192 192 0 01100 336zm708 164-249-145L645 307a3 3 0 013 0l206 119a192 192 0 01-29 346v-242a34 34 0 00-17-28zm86-129-6-4-204-119a33 33 0 00-33 0L401 394V294a3 3 0 011-3l206-119a192 192 0 01285 199zm-539 176-86-50a3 3 0 01-2-2V259a192 192 0 01315-147l-6 3-204 118a34 34 0 00-17 29zm47-101 111-64 111 64v128l-111 64-111-64z M296 392h64v64h-64zM296 582v160h128V582h-64v-62h-64v62zm80 48v64h-32v-64h32zM360 328h64v64h-64zM296 264h64v64h-64zM360 456h64v64h-64zM360 200h64v64h-64zM855 289 639 73c-6-6-14-9-23-9H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V311c0-9-3-17-9-23zM790 326H602V138L790 326zm2 562H232V136h64v64h64v-64h174v216c0 23 19 42 42 42h216v494z M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z @@ -71,9 +72,6 @@ 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 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 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 - M887 774 625 511l263-260c11-11 11-28 0-39l-75-75c-5-5-12-8-20-8-7 0-14 3-20 8L512 396 250 137c-5-5-12-8-20-8-7 0-14 3-20 8L136 212c-11 11-11 28 0 39l263 260L137 774c-5 5-8 12-8 20 0 7 3 14 8 20l75 75c5 5 12 8 20 8 7 0 14-3 20-8L512 626l261 262c5 5 12 8 20 8 7 0 14-3 20-8l75-75c5-5 8-12 8-20C895 786 892 779 887 774z - M1024 750v110c0 50-41 91-91 91h-841A92 92 0 010 859v-110C0 699 41 658 91 658h841c50 0 91 41 91 91z - M0 4 0 20 16 20 0 4M4 0 20 0 20 16 4 0z M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z M824 645V307c0-56-46-102-102-102h-102V102l-154 154 154 154V307h102v338c-46 20-82 67-82 123 0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123zm-51 195c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72zM384 256c0-72-61-133-133-133-72 0-133 61-133 133 0 56 36 102 82 123v266C154 666 118 712 118 768c0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123V379C348 358 384 312 384 256zM323 768c0 41-31 72-72 72-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72zM251 328c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72z M896 64H128C96 64 64 96 64 128v768c0 32 32 64 64 64h768c32 0 64-32 64-64V128c0-32-32-64-64-64z m-64 736c0 16-17 32-32 32H224c-18 0-32-12-32-32V224c0-16 16-32 32-32h576c15 0 32 16 32 32v576zM512 384c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z @@ -121,6 +119,7 @@ M762 1024C876 818 895 504 448 514V768L64 384l384-384v248c535-14 595 472 314 776z 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 M170 831l343-342L855 831l105-105-448-448L64 726 170 831z + M880 128A722 722 0 01555 13a77 77 0 00-85 0 719 719 0 01-325 115c-40 4-71 38-71 80v369c0 246 329 446 439 446 110 0 439-200 439-446V207c0-41-31-76-71-80zM465 692a36 36 0 01-53 0L305 579a42 42 0 010-57 36 36 0 0153 0l80 85L678 353a36 36 0 0153 0 42 42 0 01-0 57L465 692z 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 M0 512M1024 512M512 0M512 1024M762 412v100h-500v-100h-150v200h800v-200h-150z M519 459 222 162a37 37 0 10-52 52l297 297L169 809a37 37 0 1052 52l297-297 297 297a37 37 0 1052-52l-297-297 297-297a37 37 0 10-52-52L519 459z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index ff846035..2e96e391 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -55,6 +55,7 @@ Delete selected {0} branches Discard all changes Fast-Forward to ${0}$ + Fetch ${0}$ into ${1}$... Git Flow - Finish ${0}$ Merge ${0}$ into ${1}$... Pull ${0}$ @@ -69,6 +70,7 @@ CANCEL Reset to This Revision Reset to Parent Revision + Generate commit message CHANGE DISPLAY MODE Show as File and Dir List Show as Path List @@ -106,6 +108,7 @@ Compare with Worktree Copy Info Copy SHA + Custom Action Interactive Rebase ${0}$ to Here Rebase ${0}$ to Here Reset ${0}$ to Here @@ -137,12 +140,21 @@ COMMIT TEMPLATE Template Name: Template Content: + CUSTOM ACTION + Arguments: + ${REPO} - Repository's path; ${SHA} - Selected commit's SHA + Executable File: + Name: + Scope: + Commit + Repository Email Address Email address GIT Fetch remotes automatically Minute(s) Default Remote + Enable --prune on fetch Enable --signoff for commit ISSUE TRACKER Add Sample Github Rule @@ -154,6 +166,9 @@ Rule Name: Result URL: Please use $1, $2 to access regex groups values. + OPEN AI + Prefered Service: + If the 'Prefered Service' is set, SourceGit will only use it in this repository. Otherwise, if there is more than one service available, a context menu to choose one of them will be shown. HTTP Proxy HTTP proxy used by this repository User Name @@ -246,6 +261,8 @@ Target: Edit Selected Group Edit Selected Repository + Run Custom Action + Action Name: Fast-Forward (without checkout) Fetch Fetch all remotes @@ -401,12 +418,12 @@ Last year {0} years ago Preference - Advanced Options OPEN AI Analyze Diff Prompt API Key Generate Subject Prompt Model + Name Server APPEARANCE Default Font @@ -510,6 +527,8 @@ Clear all Configure this repository CONTINUE + Custom Actions + No Custom Actions Enable '--reflog' Option Open In File Browser Search Branches/Tags/Submodules @@ -572,6 +591,7 @@ START Stash Include untracked files + Keep staged files Message: Optional. Name of this stash Only staged changes diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml new file mode 100644 index 00000000..f8a2fa68 --- /dev/null +++ b/src/Resources/Locales/es_ES.axaml @@ -0,0 +1,670 @@ + + + + + Acerca de + Acerca de SourceGit + • Construido con + • El gráfico es renderizado por + © 2024 sourcegit-scm + • Editor de texto de + • Las fuentes monoespaciadas provienen de + • El código fuente se puede encontrar en + Cliente Git GUI de código abierto y gratuito + Agregar Worktree + Qué Checkout: + Rama Existente + Crear Nueva Rama + Ubicación: + Ruta para este worktree. Se admite ruta relativa. + Nombre de la Rama: + Opcional. Por defecto es el nombre de la carpeta de destino. + Rama de Seguimiento: + Seguimiento de rama remota + Asistente OpenAI + Usar OpenAI para generar mensaje de commit + Aplicar Patch + Error + Genera errores y se niega a aplicar el patch + Error Todo + Similar a 'error', pero muestra más + Archivo Patch: + Seleccionar archivo .patch para aplicar + Ignorar cambios de espacios en blanco + Sin Advertencia + Desactiva la advertencia de espacios en blanco al final + Aplicar Patch + Advertencia + Genera advertencias para algunos de estos errores, pero aplica + Espacios en Blanco: + Archivar... + Guardar Archivo en: + Seleccionar ruta del archivo + Revisión: + Archivar + SourceGit Askpass + ARCHIVOS ASUMIDOS COMO SIN CAMBIOS + NO HAY ARCHIVOS ASUMIDOS COMO SIN CAMBIOS + REMOVER + ¡ARCHIVO BINARIO NO SOPORTADO! + Blame + ¡BLAME EN ESTE ARCHIVO NO SOPORTADO! + Checkout ${0}$... + Comparar con Rama + Comparar con HEAD + Comparar con Worktree + Copiar Nombre de Rama + Eliminar ${0}$... + Eliminar {0} ramas seleccionadas + Descartar todos los cambios + Fast-Forward a ${0}$ + Fetch ${0}$ en ${1}$... + Git Flow - Finalizar ${0}$ + Merge ${0}$ en ${1}$... + Pull ${0}$ + Pull ${0}$ en ${1}$... + Push ${0}$ + Rebase ${0}$ en ${1}$... + Renombrar ${0}$... + Establecer Rama de Seguimiento + Desestablecer Upstream + Comparar Ramas + Bytes + CANCELAR + Resetear a Esta Revisión + Resetear a Revisión Padre + CAMBIAR MODO DE VISUALIZACIÓN + Mostrar como Lista de Archivos y Directorios + Mostrar como Lista de Rutas + Mostrar como Árbol de Sistema de Archivos + Checkout Rama + Checkout Commit + Advertencia: Al hacer un checkout de commit, tu Head se separará + Commit: + Rama: + Cambios Locales: + Descartar + No Hacer Nada + Stash & Reaplicar + Cherry Pick + Añadir fuente al mensaje de commit + Commit(s): + Commit todos los cambios + Mainline: + Normalmente no puedes cherry-pick un merge porque no sabes qué lado del merge debe considerarse la línea principal. Esta opción permite que cherry-pick reproduzca el cambio en relación con el padre especificado. + Limpiar Stashes + Estás intentando limpiar todos los stashes. ¿Estás seguro de continuar? + Clonar Repositorio Remoto + Parámetros Adicionales: + Argumentos adicionales para clonar el repositorio. Opcional. + Nombre Local: + Nombre del repositorio. Opcional. + Carpeta Padre: + URL del Repositorio: + CERRAR + Editor + Cherry-Pick Este Commit + Cherry-Pick ... + Checkout Commit + Comparar con HEAD + Comparar con Worktree + Copiar Información + Copiar SHA + Rebase Interactivo ${0}$ hasta Aquí + Rebase ${0}$ hasta Aquí + Reset ${0}$ hasta Aquí + Revertir Commit + Reescribir + Guardar como Patch... + Squash en Parent + Squash Commits Hijos hasta Aquí + CAMBIOS + Buscar Cambios... + ARCHIVOS + Archivo LFS + Submódulo + INFORMACIÓN + AUTOR + CAMBIADO + COMMITTER + Ver refs que contienen este commit + COMMIT ESTÁ CONTENIDO EN + Muestra solo los primeros 100 cambios. Ver todos los cambios en la pestaña CAMBIOS. + MENSAJE + PADRES + REFS + SHA + Abrir en Navegador + Introducir asunto del commit + Descripción + Configurar Repositorio + PLANTILLA DE COMMIT + Nombre de la Plantilla: + Contenido de la Plantilla: + Dirección de Email + Dirección de email + GIT + Fetch remotos automáticamente + Minuto(s) + Remoto por Defecto + Habilitar --signoff para commit + SEGUIMIENTO DE INCIDENCIAS + Añadir Regla de Ejemplo para Github + Añadir Regla de Ejemplo para Jira + Añadir Regla de Ejemplo para Incidencias de GitLab + Añadir Regla de Ejemplo para Merge Requests de GitLab + Nueva Regla + Expresión Regex para Incidencias: + Nombre de la Regla: + URL Resultante: + Por favor, use $1, $2 para acceder a los valores de los grupos regex. + Proxy HTTP + Proxy HTTP utilizado por este repositorio + Nombre de Usuario + Nombre de usuario para este repositorio + Espacios de Trabajo + Color + Restaurar pestañas al iniciar + Asistente de Commit Convencional + Cambio Importante: + Incidencia Cerrada: + Detalles del Cambio: + Alcance: + Descripción Corta: + Tipo de Cambio: + Copiar + Copiar Todo el Texto + COPIAR MENSAJE + Copiar Ruta + Copiar Nombre del Archivo + Crear Rama... + Basado En: + Checkout de la rama creada + Cambios Locales: + Descartar + No Hacer Nada + Stash & Reaplicar + Nombre de la Nueva Rama: + Introduzca el nombre de la rama. + Crear Rama Local + Crear Etiqueta... + Nueva Etiqueta En: + Firma GPG + Mensaje de la Etiqueta: + Opcional. + Nombre de la Etiqueta: + Formato recomendado: v1.0.0-alpha + Push a todos los remotos después de crear + Crear Nueva Etiqueta + Tipo: + anotada + ligera + Mantenga Ctrl para iniciar directamente + Cortar + Eliminar Rama + Rama: + ¡Estás a punto de eliminar una rama remota! + También eliminar la rama remota ${0}$ + Eliminar Múltiples Ramas + Estás intentando eliminar múltiples ramas a la vez. ¡Asegúrate de revisar antes de tomar acción! + Eliminar Remoto + Remoto: + Destino: + Confirmar Eliminación de Grupo + Confirmar Eliminación de Repositorio + Eliminar Submódulo + Ruta del Submódulo: + Eliminar Etiqueta + Etiqueta: + Eliminar de los repositorios remotos + DIFERENCIA BINARIA + NUEVO + ANTIGUO + Copiar + Modo de Archivo Cambiado + Ignorar Cambio de Espacios en Blanco + CAMBIO DE OBJETO LFS + Siguiente Diferencia + SIN CAMBIOS O SOLO CAMBIOS DE EOL + Diferencia Anterior + Mostrar símbolos ocultos + Diferencia Lado a Lado + SUBMÓDULO + NUEVO + Intercambiar + Resaltado de Sintaxis + Ajuste de Línea + Abrir en Herramienta de Merge + Disminuir Número de Líneas Visibles + Aumentar Número de Líneas Visibles + SELECCIONA ARCHIVO PARA VER CAMBIOS + Abrir en Herramienta de Merge + Descartar Cambios + Todos los cambios locales en la copia de trabajo. + Cambios: + Incluir archivos ignorados + Total {0} cambios serán descartados + ¡No puedes deshacer esta acción! + Marcador: + Nuevo Nombre: + Destino: + Editar Grupo Seleccionado + Editar Repositorio Seleccionado + Fast-Forward (sin checkout) + Fetch + Fetch todos los remotos + Fetch sin etiquetas + Remoto: + Fetch Cambios Remotos + Asumir sin cambios + Descartar... + Descartar {0} archivos... + Descartar Cambios en Línea(s) Seleccionada(s) + Abrir Herramienta de Merge Externa + Guardar Como Patch... + Stage + Stage {0} archivos + Stage Cambios en Línea(s) Seleccionada(s) + Stash... + Stash {0} archivos... + Unstage + Unstage {0} archivos + Unstage Cambios en Línea(s) Seleccionada(s) + Usar Suyos (checkout --theirs) + Usar Míos (checkout --ours) + Historial de Archivos + CONTENIDO + CAMBIO + FILTRO + Git-Flow + Rama de Desarrollo: + Feature: + Prefijo de Feature: + FLOW - Finalizar Feature + FLOW - Finalizar Hotfix + FLOW - Finalizar Release + Destino: + Hotfix: + Prefijo de Hotfix: + Inicializar Git-Flow + Mantener rama + Rama de Producción: + Release: + Prefijo de Release: + Iniciar Feature... + FLOW - Iniciar Feature + Iniciar Hotfix... + FLOW - Iniciar Hotfix + Introducir nombre + Iniciar Release... + FLOW - Iniciar Release + Prefijo de Etiqueta de Versión: + Git LFS + Añadir Patrón de Seguimiento... + El patrón es el nombre del archivo + Patrón Personalizado: + Añadir Patrón de Seguimiento a Git LFS + Fetch + Fetch Objetos LFS + Ejecuta `git lfs fetch` para descargar objetos Git LFS. Esto no actualiza la copia de trabajo. + Instalar hooks de Git LFS + Mostrar Bloqueos + No hay archivos bloqueados + Bloquear + Mostrar solo mis bloqueos + Bloqueos LFS + Desbloquear + Forzar Desbloqueo + Prune + Ejecuta `git lfs prune` para eliminar archivos LFS antiguos del almacenamiento local + Pull + Pull Objetos LFS + Ejecuta `git lfs pull` para descargar todos los archivos Git LFS para la referencia actual y hacer checkout + Push + Push Objetos LFS + Push archivos grandes en cola al endpoint de Git LFS + Remoto: + Seguir archivos llamados '{0}' + Seguir todos los archivos *{0} + Historias + Cambiar a Disposición Horizontal/Vertical + AUTOR + HORA DEL AUTOR + GRÁFICO & ASUNTO + SHA + FECHA DE COMMIT + {0} COMMITS SELECCIONADOS + Mantén 'Ctrl' o 'Shift' para seleccionar múltiples commits. + Mantén ⌘ o ⇧ para seleccionar múltiples commits. + CONSEJOS: + Referencia de Atajos de Teclado + GLOBAL + Cancelar popup actual + Cerrar página actual + Ir a la página anterior + Ir a la siguiente página + Crear nueva página + Abrir diálogo de preferencias + REPOSITORIO + Commit cambios staged + Commit y push cambios staged + Stage todos los cambios y commit + Descartar cambios seleccionados + Modo Dashboard (Por Defecto) + Forzar a recargar este repositorio + Stage/Unstage cambios seleccionados + Modo de búsqueda de commits + Cambiar a 'Cambios' + Cambiar a 'Historias' + Cambiar a 'Stashes' + EDITOR DE TEXTO + Cerrar panel de búsqueda + Buscar siguiente coincidencia + Buscar coincidencia anterior + Abrir panel de búsqueda + Stage + Unstage + Descartar + Inicializar Repositorio + Ruta: + Cherry-Pick en progreso. Presiona 'Abort' para restaurar el HEAD original. + Merge en progreso. Presiona 'Abort' para restaurar el HEAD original. + Rebase en progreso. Presiona 'Abort' para restaurar el HEAD original. + Revert en progreso. Presiona 'Abort' para restaurar el HEAD original. + Rebase Interactivo + Rama Objetivo: + En: + ERROR + AVISO + Merge Rama + En: + Opción de Merge: + Rama Fuente: + Mover Nodo del Repositorio + Seleccionar nodo padre para: + Nombre: + Git NO ha sido configurado. Por favor, ve a [Preferencias] y configúralo primero. + Abrir Directorio de Datos de la App + Abrir Con... + Opcional. + Crear Nueva Página + Marcador + Cerrar Pestaña + Cerrar Otras Pestañas + Cerrar Pestañas a la Derecha + Copiar Ruta del Repositorio + Repositorios + Pegar + Justo ahora + Hace {0} minutos + Hace {0} horas + Ayer + Hace {0} días + Último mes + Hace {0} meses + Último año + Hace {0} años + Preferencias + Opciones Avanzadas + OPEN AI + Analizar Diff Prompt + Clave API + Generar Subject Prompt + Modelo + Servidor + APARIENCIA + Fuente por defecto + Tamaño de fuente por defecto + Fuente Monospace + Usar solo fuente monospace en el editor de texto + Tema + Sobreescritura de temas + Usar ancho de pestaña fijo en la barra de título + Usar marco de ventana nativo + HERRAMIENTA DIFF/MERGE + Ruta de instalación + Introducir ruta para la herramienta diff/merge + Herramienta + GENERAL + Buscar actualizaciones al iniciar + Idioma + Commits en el historial + Mostrar hora del autor en lugar de la hora del commit en el gráfico + Longitud de la guía del asunto + GIT + Habilitar Auto CRLF + Directorio de clonado por defecto + Email de usuario + Email global del usuario git + Ruta de instalación + Nombre de usuario + Nombre global del usuario git + Versión de Git + Se requiere Git (>= 2.23.0) para esta aplicación + FIRMA GPG + Firma GPG en commit + Firma GPG en etiqueta + Formato GPG + Ruta de instalación del programa + Introducir ruta para el programa gpg instalado + Clave de firma del usuario + Clave de firma gpg del usuario + INTEGRACIÓN + SHELL/TERMINAL + Shell/Terminal + Ruta + Podar Remoto + Destino: + Podar Worktrees + Podar información de worktree en `$GIT_DIR/worktrees` + Pull + Rama: + Fetch todas las ramas + En: + Cambios Locales: + Descartar + No Hacer Nada + Stash & Reaplicar + Fetch sin etiquetas + Remoto: + Pull (Fetch & Merge) + Usar rebase en lugar de merge + Push + Asegurarse de que los submódulos se hayan hecho push + Forzar push + Rama Local: + Remoto: + Push Cambios al Remoto + Rama Remota: + Establecer como rama de seguimiento + Push todas las etiquetas + Push Etiqueta al Remoto + Push a todos los remotos + Remoto: + Etiqueta: + Salir + Rebase Rama Actual + Stash & reaplicar cambios locales + En: + Rebase: + Refrescar + Añadir Remoto + Editar Remoto + Nombre: + Nombre remoto + URL del Repositorio: + URL del repositorio git remoto + Copiar URL + Borrar... + Editar... + Fetch + Abrir En Navegador + Podar (Prune) + Confirmar para Eliminar Worktree + Utilizar Opción `--force` + Destino: + Renombrar Rama + Nuevo Nombre: + Nombre único para esta rama + Rama: + ABORTAR + Auto fetching cambios desde remotos... + Limpiar (GC & Prune) + Ejecutar comando `git gc` para este repositorio. + Limpiar todo + Configurar este repositorio + CONTINUAR + Habilitar Opción '--reflog' + Abrir en el Explorador + Buscar Ramas/Etiquetas/Submódulos + FILTRAR POR: + RAMAS LOCALES + Navegar a HEAD + Habilitar Opción '--first-parent' + Crear Rama + Abrir en {0} + Abrir en Herramientas Externas + Refrescar + REMOTOS + AÑADIR REMOTO + RESOLVER + Buscar Commit + Archivo + Mensaje + SHA + Autor & Committer + Rama Actual + Mostrar Etiquetas como Árbol + Estadísticas + SUBMÓDULOS + AÑADIR SUBMÓDULO + ACTUALIZAR SUBMÓDULO + ETIQUETAS + NUEVA ETIQUETA + Abrir en Terminal + WORKTREES + AÑADIR WORKTREE + PRUNE + URL del Repositorio Git + Resetear Rama Actual a Revisión + Modo de Reset: + Mover a: + Rama Actual: + Revelar en el Explorador de Archivos + Revertir Commit + Commit: + Commit revertir cambios + Reescribir Mensaje de Commit + Usa 'Shift+Enter' para introducir una nueva línea. 'Enter' es el atajo del botón OK + Ejecutando. Por favor espera... + GUARDAR + Guardar Como... + ¡El patch se ha guardado exitosamente! + Escanear Repositorios + Directorio Raíz: + Buscar Actualizaciones... + Nueva versión de este software disponible: + ¡Error al buscar actualizaciones! + Descargar + Omitir Esta Versión + Actualización de Software + Actualmente no hay actualizaciones disponibles. + Squash Commits + En: + Clave Privada SSH: + Ruta de almacenamiento de la clave privada SSH + INICIAR + Stash + Incluir archivos no rastreados + Mensaje: + Opcional. Nombre de este stash + Solo cambios staged + ¡Tanto los cambios staged como los no staged de los archivos seleccionados serán stashed! + Stash Cambios Locales + Aplicar + Eliminar + Pop + Eliminar Stash + Eliminar: + Stashes + CAMBIOS + STASHES + Estadísticas + COMMITS + COMMITTER + MES + SEMANA + COMMITS: + AUTORES: + GENERAL + SUBMÓDULOS + Añadir Submódulo + Copiar Ruta Relativa + Fetch submódulos anidados + Abrir Repositorio del Submódulo + Ruta Relativa: + Carpeta relativa para almacenar este módulo. + Eliminar Submódulo + OK + Copiar Nombre de la Etiqueta + Copiar Mensaje de la Etiqueta + Eliminar ${0}$... + Push ${0}$... + URL: + Actualizar Submódulos + Todos los submódulos + Inicializar según sea necesario + Recursivamente + Submódulo: + Usar opción --remote + Advertencia + Página de Bienvenida + Crear Grupo + Crear Sub-Grupo + Clonar Repositorio + Eliminar + SOPORTA ARRASTRAR Y SOLTAR CARPETAS. SOPORTA AGRUPACIÓN PERSONALIZADA. + Editar + Mover a Otro Grupo + Abrir Todos Los Repositorios + Abrir Repositorio + Abrir Terminal + Reescanear Repositorios en el Directorio de Clonado por Defecto + Buscar Repositorios... + Ordenar + Cambios + Git Ignore + Ignorar todos los archivos *{0} + Ignorar archivos *{0} en la misma carpeta + Ignorar archivos en la misma carpeta + Ignorar solo este archivo + Enmendar (Amend) + Puedes stagear este archivo ahora. + COMMIT + COMMIT & PUSH + Plantilla/Historias + Activar evento de clic + Stagear todos los cambios y commit + ¡Commit vacío detectado! ¿Quieres continuar (--allow-empty)? + CONFLICTOS DETECTADOS + LOS CONFLICTOS DE ARCHIVOS ESTÁN RESUELTOS + INCLUIR ARCHIVOS NO RASTREADOS + NO HAY MENSAJES DE ENTRADA RECIENTES + NO HAY PLANTILLAS DE COMMIT + STAGED + UNSTAGE + UNSTAGE TODO + UNSTAGED + STAGE + STAGE TODO + VER ASSUME UNCHANGED + Plantilla: ${0}$ + Haz clic derecho en el(los) archivo(s) seleccionado(s) y elige tu opción para resolver conflictos. + ESPACIO DE TRABAJO: + Configura Espacios de Trabajo... + WORKTREE + Copiar Ruta + Bloquear + Eliminar + Desbloquear + diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 0d7c55fb..d2076099 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -16,7 +16,7 @@ Существующую ветку Создать новую ветку Расположение: - Путь к этому рабочему дереву. Поддерживается относительный путью + Путь к этому рабочему дереву. Поддерживается относительный путь. Имя ветки: Необязательно. По умолчанию используется имя целевой папки. Отслеживание ветки: @@ -27,7 +27,7 @@ Ошибка Выдает ошибки и отказывается применять исправление Все ошибки - Аналогично "ошибке", но показывает больше + Аналогично «ошибке», но показывает больше Файл исправлений: Выберите файл .patch для применения Игнорировать изменения пробелов @@ -58,6 +58,7 @@ Удалить выбранные {0} ветки Отклонить все изменения. Перемотать вперёд к ${0}$ + Извлечь ${0}$ в ${1}$... Поток Git - Завершение ${0}$ Слить ${0}$ в ${1}$... Забрать ${0}$ @@ -72,6 +73,7 @@ ОТМЕНА Сбросить эту ревизию Сбросить родительскую ревизию + Произвести сообщение о фиксации ИЗМЕНИТЬ РЕЖИМ ОТОБРАЖЕНИЯ Показывать в виде списка файлов и каталогов Показывать в виде списка путей @@ -109,14 +111,15 @@ Сравнить с рабочим деревом Копировать информацию Копировать SHA + Пользовательское действие Интерактивное перемещение ${0}$ сюда Переместить ${0}$ сюда Сбросить ${0}$ сюда Вернуть фиксацию Переформулировать Сохранить как исправление... - Уплотнить в родительскую - Уплотнить дочерную фиксацию сюда + Втиснуть в родительскую + Втиснуть дочерную фиксацию сюда ИЗМЕНЕНИЯ Найти изменения.... ФАЙЛЫ @@ -140,13 +143,22 @@ ШАБЛОН ФИКСАЦИИ Имя шаблона: Шаблон содержания: + ПОЛЬЗОВАТЕЛЬСКОЕ ДЕЙСТВИЕ + Аргументы: + ${REPO} - Путь хранилища; ${SHA} - Выбранные фиксации SHA + Исполняемый фалй: + Имя: + Диапазон: + Фиксация + Хранилище Адрес электронной почты Адрес электронной почты GIT Автоматическое извлечение внешних хранилищ Минут(а/ы) - Удалённое хранилище по-умолчанию Разрешить --signoff для фиксации + Удалённое хранилище по-умолчанию + Разрешить --prune при извлечении ОТСЛЕЖИВАНИЕ ПРОБЛЕМ Добавить пример правила для Git Добавить пример правила Jira @@ -157,9 +169,12 @@ Имя правила: Адрес результата: Пожалуйста, используйте $1, $2 для доступа к значениям групп регулярных выражений. + ОТКРЫТЬ ИИ + Предпочитаемый сервис: + Если «Предпочитаемый сервис» установлен, SourceGit будет использовать только этот хранилище. В противном случае, если доступно более одной услуги, будет отображено контекстное меню для выбора одной из них. HTTP-прокси HTTP-прокси, используемый этим хранилищем - Имя пользовтаеля + Имя пользователя Имя пользователя для этого хранилища Рабочие пространства Имя @@ -250,6 +265,8 @@ Цель: Редактировать выбранную группу Редактировать выбранное хранилище + Выполнить пользовательское действие + Имя действия: Быстрая перемотка вперёд (без проверки) Извлечь Извлечь все внешние хранилища @@ -306,7 +323,7 @@ Добавить шаблон отслеживания в ХБФ Git Извлечь Извлечь объекты ХБФ - Запустить `git lfs fetch", чтобы загрузить объекты ХБФ Git. При этом рабочая копия не обновляется. + Запустить «git lfs fetch», чтобы загрузить объекты ХБФ Git. При этом рабочая копия не обновляется. Установить перехват ХБФ Git Показывать блокировки Нет заблокированных файлов @@ -316,15 +333,15 @@ Разблокировать Принудительно разблокировать Обрезать - Запустите `git lfs prune", чтобы удалить старые файлы ХБФ из локального хранилища + Запустить «git lfs prune», чтобы удалить старые файлы ХБФ из локального хранилища Забрать Забрать объекты ХБФ - Запустите `git lfs pull", чтобы загрузить все файлы ХБФ Git для текущей ссылки и проверить + Запустить «git lfs pull», чтобы загрузить все файлы ХБФ Git для текущей ссылки и проверить Выложить Выложить объекты ХБФ Отправляйте большие файлы, помещенные в очередь, в конечную точку ХБФ Git Внешнее хранилище: - Отслеживать файлы с именем '{0}' + Отслеживать файлы с именем «{0}» Отслеживать все *{0} файлов Истории Переключение горизонтального/вертикального расположения @@ -334,7 +351,7 @@ SHA ВРЕМЯ ФИКСАЦИИ ВЫБРАННЫЕ {0} ФИКСАЦИИ - Удерживайте 'Ctrl' или 'Shift', чтобы выбрать несколько фиксаций. + Удерживайте Ctrl или Shift, чтобы выбрать несколько фиксаций. Удерживайте ⌘ или ⇧, чтобы выбрать несколько фиксаций. ПОДСКАЗКИ: Ссылка на сочетания клавиш @@ -354,9 +371,9 @@ Принудительно перезагрузить этот хранилище Подготовленные/Неподготовленные выбранные изменения Режим поиска фиксаций - Переключить на 'Изменения' - Переключить на 'Истории' - Переключить на 'Отложенные' + Переключить на «Изменения» + Переключить на «Истории» + Переключить на «Отложенные» ТЕКСТОВЫЙ РЕДАКТОР Закрыть панель поиска Найти следующее совпадение @@ -367,10 +384,10 @@ Отклонить Инициализировать хранилище Путь: - Выполняется частичный забор. Нажмите 'Отказ' для восстановления заголовка. - Выполняет запрос слияния. Нажмите 'Отказ' для восстановления заголовка. - Выполняется перенос. Нажмите 'Отказ' для восстановления заголовка. - Выполняется возврат. Нажмите 'Отказ' для восстановления заголовка. + Выполняется частичный забор. Нажмите «Отказ» для восстановления заголовка. + Выполняет запрос слияния. Нажмите «Отказ» для восстановления заголовка. + Выполняется перенос. Нажмите «Отказ» для восстановления заголовка. + Выполняется возврат. Нажмите «Отказ» для восстановления заголовка. Интерактивное перемещение Целевая ветка: На: @@ -405,12 +422,12 @@ В пролому году {0} лет назад Параметры - Расширенные опции ОТКРЫТЬ ИИ Ключ API Запрос на анализ различий - Сгенерировать запрос на тему + Произвести запрос на тему Модель + Имя: Сервер ВИД Шрифт по-умолчанию @@ -456,7 +473,7 @@ Удалить внешнее хранилище Цель: Удалить рабочее дерево - Информация об обрезке рабочего дерева в `$GIT_DIR/worktrees` + Информация об обрезке рабочего дерева в «$GIT_DIR/worktrees» Забрать Ветка: Извлечь все ветки @@ -501,7 +518,7 @@ Открыть в браузере Удалить Подтвердить удаление рабочего дерева - Включить опцию `--force` + Включить опцию --force Цель: Переименовать ветку Новое имя: @@ -510,17 +527,19 @@ Отказ Автоматическое извлечение изменений с внешних хранилищ... Очистить (Сбор мусора и удаление) - Запустить команду `git gc` для данного хранилища. + Запустить команду «git gc» для данного хранилища. Очистить всё Настройка этого хранилища ПРОДОЛЖИТЬ - Разрешить опцию '--reflog' + Изменить действия + Не изменять действия + Разрешить опцию --reflog Открыть в файловом менеджере Поиск веток, меток и подмодулей - ОТФИЛЬТРОВАНО ОТ: + ОТФИЛЬТРОВАНО: ЛОКАЛЬНЫЕ ВЕТКИ Навигация по заголовку - Включить опцию '--first-parent' + Включить опцию --first-parent Создать ветку Открыть в {0} Открыть в расширенном инструменте @@ -550,12 +569,12 @@ Режим сброса: Переместить в: Текущая ветка: - Раскрыть в файловом менеджере + Открыть в файловом менеджере Отменить фиксацию Фиксация: Отмена изменений фиксации Переформулировать сообщение фиксации - Использовать "Shift+Enter" для ввода новой строки. "Enter" - это горячая клавиша кнопки OK + Использовать «Shift+Enter» для ввода новой строки. «Enter» - это горячая клавиша кнопки OK Запуск. Подождите пожалуйста... СОХРАНИТЬ Сохранить как... @@ -569,7 +588,7 @@ Пропустить эту версию Обновление ПО В настоящее время обновления недоступны. - Уплотнить фиксации + Втиснуть фиксации В: Частный ключ SSH: Путь хранения частного ключа SSH @@ -578,6 +597,7 @@ ЗАПУСК Отложить Включить неотслеживаемые файлы + Хранить отложенные файлы Сообщение: Необязательно. Имя этого тайника Отложить локальные изменения diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index f4027111..469a88bb 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -58,6 +58,7 @@ 删除选中的 {0} 个分支 放弃所有更改 快进(fast-forward)到 ${0}$ + 拉取(fetch) ${0}$ 至 ${1}$... GIT工作流 - 完成 ${0}$ 合并 ${0}$ 到 ${1}$... 拉回(pull) ${0}$ @@ -72,6 +73,7 @@ 取 消 重置文件到该版本 重置文件到上一版本 + 生成提交信息 切换变更显示模式 文件名+路径列表模式 全路径列表模式 @@ -109,6 +111,7 @@ 与本地工作树比较 复制简要信息 复制提交指纹 + 自定义操作 交互式变基(rebase -i) ${0}$ 到此处 变基(rebase) ${0}$ 到此处 重置(reset) ${0}$ 到此处 @@ -140,6 +143,14 @@ 提交信息模板 模板名 : 模板内容 : + 自定义操作 + 命令行参数 : + 请使用${REPO}代替仓库路径,${SHA}代替提交哈希 + 可执行文件路径 : + 名称 : + 作用目标 : + 选中的提交 + 仓库 电子邮箱 邮箱地址 GIT配置 @@ -147,6 +158,7 @@ 分钟 默认远程 提交信息追加署名 (--signoff) + 拉取更新时启用修剪(--prune) ISSUE追踪 新增匹配Github Issue规则 新增匹配Jira规则 @@ -157,6 +169,9 @@ 规则名 : 为ISSUE生成的URL链接 : 可在URL中使用$1,$2等变量填入正则表达式匹配的内容 + OPEN AI + 启用特定服务 : + 当【启用特定服务】被设置时,SourceGit将在本仓库中仅使用该服务。否则将弹出可用的OpenAI服务列表供用户选择。 HTTP代理 HTTP网络代理 用户名 @@ -249,6 +264,8 @@ 目标 : 编辑分组 编辑仓库 + 执行自定义操作 + 自定义操作 : 快进(fast-forward,无需checkout) 拉取(fetch) 拉取所有的远程仓库 @@ -404,7 +421,13 @@ 一年前 {0}年前 偏好设置 - 高级设置 + OPEN AI + Analyze Diff Prompt + API密钥 + Generate Subject Prompt + 模型 + 配置名称 + 服务地址 外观配置 缺省字体 默认字体大小 @@ -507,6 +530,8 @@ 清空过滤规则 配置本仓库 下一步 + 自定义操作 + 自定义操作未设置 启用 --reflog 选项 在文件浏览器中打开 快速查找分支/标签/子模块 @@ -569,6 +594,7 @@ 开 始 贮藏(stash) 包含未跟踪的文件 + 保留暂存区文件 信息 : 选填,用于命名此贮藏 仅贮藏暂存区的变更 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index a72914f7..77a832ff 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -58,6 +58,7 @@ 刪除所選的 {0} 個分支 捨棄所有變更 快轉 (fast-forward) 到 ${0}$ + 提取 (fetch) ${0}$ 到 ${1}$... Git 工作流 - 完成 ${0}$ 合併 ${0}$ 到 ${1}$... 拉取 (pull) ${0}$ @@ -72,6 +73,7 @@ 取 消 重設檔案為此版本 重設檔案到上一版本 + 產生提交訊息 切換變更顯示模式 檔案名稱 + 路徑列表模式 全路徑列表模式 @@ -109,6 +111,7 @@ 與本機工作區比較 複製摘要資訊 複製提交編號 + 自訂動作 互動式重定基底 (rebase -i) ${0}$ 到此處 重定基底 (rebase) ${0}$ 到此處 重設 (reset) ${0}$ 到此處 @@ -140,6 +143,14 @@ 提交訊息範本 範本名稱: 範本內容: + 自訂動作 + 指令參數: + 使用 ${REPO} 表示存放庫路徑、${SHA} 表示所選的提交編號 + 可執行檔案路徑: + 名稱: + 執行範圍: + 選取的提交 + 存放庫 電子郵件 電子郵件地址 Git 設定 @@ -147,16 +158,20 @@ 分鐘 預設遠端存放庫 提交訊息追加署名 (--signoff) + 拉取變更時進行清理 (--prune) Issue 追蹤 新增符合 GitHub Issue 規則 新增符合 Jira 規則 新增符合 GitLab 議題規則 新增符合 GitLab 合併請求規則 新增自訂規則 - 符合 Issue 的正則表達式: + 符合 Issue 的正規表達式: 規則名稱: 為 Issue 產生的網址連結: - 可在網址中使用 $1、$2 等變數填入正則表示式相符的內容 + 可在網址中使用 $1、$2 等變數填入正規表達式相符的內容 + OpenAI + 偏好服務: + 設定 [偏好服務] 後,SourceGit 將於此存放庫中使用該服務,否則會顯示 OpenAI 服務列表供使用者選擇。 HTTP 代理 HTTP 網路代理 使用者名稱 @@ -249,6 +264,8 @@ 目標: 編輯群組 編輯存放庫 + 執行自訂動作 + 自訂動作: 快進 (fast-forward,無需 checkout) 提取 (fetch) 提取所有的遠端存放庫 @@ -404,11 +421,11 @@ 一年前 {0} 年前 偏好設定 - 進階設定 OpenAI 伺服器 API 金鑰 模型 + 名稱 分析變更差異提示詞 產生提交訊息提示詞 外觀設定 @@ -513,6 +530,8 @@ 清空篩選規則 設定本存放庫 下一步 + 自訂動作 + 沒有自訂的動作 啟用 [--reflog] 選項 在檔案瀏覽器中開啟 快速搜尋分支/標籤/子模組 @@ -575,6 +594,7 @@ 開 始 擱置變更 (stash) 包含未追蹤的檔案 + 保留已暫存的變更 擱置變更訊息: 選填,用於命名此擱置變更 僅擱置已暫存的變更 diff --git a/src/Resources/Themes.axaml b/src/Resources/Themes.axaml index aa0cbbb8..6326023a 100644 --- a/src/Resources/Themes.axaml +++ b/src/Resources/Themes.axaml @@ -83,7 +83,7 @@ - fonts:Inter#Inter, $Default + fonts:Inter#Inter fonts:SourceGit#JetBrains Mono fonts:SourceGit#JetBrains Mono diff --git a/src/ViewModels/AddRemote.cs b/src/ViewModels/AddRemote.cs index d2a7729a..d6424572 100644 --- a/src/ViewModels/AddRemote.cs +++ b/src/ViewModels/AddRemote.cs @@ -100,7 +100,7 @@ namespace SourceGit.ViewModels { SetProgressDescription("Fetching from added remote ..."); new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); - new Commands.Fetch(_repo.FullPath, _name, false, SetProgressDescription).Exec(); + new Commands.Fetch(_repo.FullPath, _name, false, false, SetProgressDescription).Exec(); } CallUIThread(() => { diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 3692be10..1600c4cd 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -45,6 +45,12 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _fullMessage, value); } + public Models.CommitSignInfo SignInfo + { + get => _signInfo; + private set => SetProperty(ref _signInfo, value); + } + public List Changes { get => _changes; @@ -131,6 +137,7 @@ namespace SourceGit.ViewModels _visibleChanges.Clear(); if (_selectedChanges != null) _selectedChanges.Clear(); + _signInfo = null; _searchChangeFilter = null; _diffContext = null; _viewRevisionFileContent = null; @@ -474,6 +481,7 @@ namespace SourceGit.ViewModels { _changes = null; FullMessage = string.Empty; + SignInfo = null; Changes = []; VisibleChanges = null; SelectedChanges = null; @@ -488,6 +496,12 @@ namespace SourceGit.ViewModels Dispatcher.UIThread.Invoke(() => FullMessage = fullMessage); }); + Task.Run(() => + { + var signInfo = new Commands.QueryCommitSignInfo(_repo.FullPath, _commit.SHA, !_repo.HasAllowedSignersFile).Result(); + Dispatcher.UIThread.Invoke(() => SignInfo = signInfo); + }); + if (_cancelToken != null) _cancelToken.Requested = true; @@ -637,6 +651,7 @@ namespace SourceGit.ViewModels private int _activePageIndex = 0; private Models.Commit _commit = null; private string _fullMessage = string.Empty; + private Models.CommitSignInfo _signInfo = null; private List _changes = null; private List _visibleChanges = null; private List _selectedChanges = null; diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs new file mode 100644 index 00000000..920b9f43 --- /dev/null +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class ExecuteCustomAction : Popup + { + public Models.CustomAction CustomAction + { + get; + private set; + } + + public ExecuteCustomAction(Repository repo, Models.CustomAction action, Models.Commit commit) + { + _repo = repo; + _args = action.Arguments.Replace("${REPO}", _repo.FullPath); + if (commit != null) + _args = _args.Replace("${SHA}", commit.SHA); + + CustomAction = action; + View = new Views.ExecuteCustomAction() { DataContext = this }; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = "Run custom action ..."; + + return Task.Run(() => + { + Commands.ExecuteCustomAction.Run(_repo.FullPath, CustomAction.Executable, _args, SetProgressDescription); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return true; + }); + } + + private readonly Repository _repo = null; + private string _args = string.Empty; + } +} diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs index 3fe92a5f..7f54680d 100644 --- a/src/ViewModels/Fetch.cs +++ b/src/ViewModels/Fetch.cs @@ -40,6 +40,8 @@ namespace SourceGit.ViewModels { _repo.SetWatcherEnabled(false); + var notags = _repo.Settings.FetchWithoutTags; + var prune = _repo.Settings.EnablePruneOnFetch; return Task.Run(() => { if (FetchAllRemotes) @@ -47,13 +49,13 @@ namespace SourceGit.ViewModels foreach (var remote in _repo.Remotes) { SetProgressDescription($"Fetching remote: {remote.Name}"); - new Commands.Fetch(_repo.FullPath, remote.Name, NoTags, SetProgressDescription).Exec(); + new Commands.Fetch(_repo.FullPath, remote.Name, notags, prune, SetProgressDescription).Exec(); } } else { SetProgressDescription($"Fetching remote: {SelectedRemote.Name}"); - new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, NoTags, SetProgressDescription).Exec(); + new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, prune, SetProgressDescription).Exec(); } CallUIThread(() => diff --git a/src/ViewModels/FetchInto.cs b/src/ViewModels/FetchInto.cs new file mode 100644 index 00000000..a52b1cd6 --- /dev/null +++ b/src/ViewModels/FetchInto.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class FetchInto : Popup + { + public Models.Branch Local + { + get; + private set; + } + + public Models.Branch Upstream + { + get; + private set; + } + + public FetchInto(Repository repo, Models.Branch local, Models.Branch upstream) + { + _repo = repo; + Local = local; + Upstream = upstream; + View = new Views.FetchInto() { DataContext = this }; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = "Fast-Forward ..."; + + return Task.Run(() => + { + new Commands.Fetch(_repo.FullPath, Local, Upstream, SetProgressDescription).Exec(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return true; + }); + } + + private readonly Repository _repo = null; + } +} diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 179f6d97..713e1635 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -605,6 +605,39 @@ namespace SourceGit.ViewModels menu.Items.Add(archive); menu.Items.Add(new MenuItem() { Header = "-" }); + var actions = new List(); + foreach (var action in _repo.Settings.CustomActions) + { + if (action.Scope == Models.CustomActionScope.Commit) + actions.Add(action); + } + if (actions.Count > 0) + { + var custom = new MenuItem(); + custom.Header = App.Text("CommitCM.CustomAction"); + custom.Icon = App.CreateMenuIcon("Icons.Action"); + + foreach (var action in actions) + { + var dup = action; + var item = new MenuItem(); + item.Icon = App.CreateMenuIcon("Icons.Action"); + item.Header = dup.Name; + item.Click += (_, e) => + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowAndStartPopup(new ExecuteCustomAction(_repo, dup, commit)); + + e.Handled = true; + }; + + custom.Items.Add(item); + } + + menu.Items.Add(custom); + menu.Items.Add(new MenuItem() { Header = "-" }); + } + var copySHA = new MenuItem(); copySHA.Header = App.Text("CommitCM.CopySHA"); copySHA.Icon = App.CreateMenuIcon("Icons.Copy"); diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index dd618e41..8e4f7775 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -204,12 +204,7 @@ namespace SourceGit.ViewModels var activeIdx = Pages.IndexOf(_activePage); if (removeIdx == activeIdx) { - ActivePage = Pages[removeIdx == Pages.Count - 1 ? removeIdx - 1 : removeIdx + 1]; - CloseRepositoryInTab(page); - Pages.RemoveAt(removeIdx); - } - else if (removeIdx + 1 == activeIdx) - { + ActivePage = Pages[removeIdx > 0 ? removeIdx - 1 : removeIdx + 1]; CloseRepositoryInTab(page); Pages.RemoveAt(removeIdx); } diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index 72667f54..2741650c 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; - +using Avalonia.Collections; using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -25,7 +25,6 @@ namespace SourceGit.ViewModels _instance.PrepareGit(); _instance.PrepareShellOrTerminal(); _instance.PrepareWorkspaces(); - _instance.PrepareOpenAIPrompt(); return _instance; } @@ -277,71 +276,6 @@ namespace SourceGit.ViewModels set => SetProperty(ref _externalMergeToolPath, value); } - public string OpenAIServer - { - get => Models.OpenAI.Server; - set - { - if (value != Models.OpenAI.Server) - { - Models.OpenAI.Server = value; - OnPropertyChanged(); - } - } - } - - public string OpenAIApiKey - { - get => Models.OpenAI.ApiKey; - set - { - if (value != Models.OpenAI.ApiKey) - { - Models.OpenAI.ApiKey = value; - OnPropertyChanged(); - } - } - } - - public string OpenAIModel - { - get => Models.OpenAI.Model; - set - { - if (value != Models.OpenAI.Model) - { - Models.OpenAI.Model = value; - OnPropertyChanged(); - } - } - } - - public string OpenAIAnalyzeDiffPrompt - { - get => Models.OpenAI.AnalyzeDiffPrompt; - set - { - if (value != Models.OpenAI.AnalyzeDiffPrompt) - { - Models.OpenAI.AnalyzeDiffPrompt = value; - OnPropertyChanged(); - } - } - } - - public string OpenAIGenerateSubjectPrompt - { - get => Models.OpenAI.GenerateSubjectPrompt; - set - { - if (value != Models.OpenAI.GenerateSubjectPrompt) - { - Models.OpenAI.GenerateSubjectPrompt = value; - OnPropertyChanged(); - } - } - } - public uint StatisticsSampleColor { get => _statisticsSampleColor; @@ -360,6 +294,12 @@ namespace SourceGit.ViewModels set; } = []; + public AvaloniaList OpenAIServices + { + get; + set; + } = []; + public double LastCheckUpdateTime { get => _lastCheckUpdateTime; @@ -554,45 +494,6 @@ namespace SourceGit.ViewModels } } - private void PrepareOpenAIPrompt() - { - if (string.IsNullOrEmpty(Models.OpenAI.AnalyzeDiffPrompt)) - { - Models.OpenAI.AnalyzeDiffPrompt = """ - You are an expert developer specialist in creating commits. - Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules: - - Do not use any code snippets, imports, file routes or bullets points. - - Do not mention the route of file that has been change. - - Write clear, concise, and descriptive messages that explain the MAIN GOAL made of the changes. - - Use the present tense and active voice in the message, for example, "Fix bug" instead of "Fixed bug.". - - Use the imperative mood, which gives the message a sense of command, e.g. "Add feature" instead of "Added feature". - - Avoid using general terms like "update" or "change", be specific about what was updated or changed. - - Avoid using terms like "The main goal of", just output directly the summary in plain text - """; - } - - if (string.IsNullOrEmpty(Models.OpenAI.GenerateSubjectPrompt)) - { - Models.OpenAI.GenerateSubjectPrompt = """ - You are an expert developer specialist in creating commits messages. - Your only goal is to retrieve a single commit message. - Based on the provided user changes, combine them in ONE SINGLE commit message retrieving the global idea, following strictly the next rules: - - Assign the commit {type} according to the next conditions: - feat: Only when adding a new feature. - fix: When fixing a bug. - docs: When updating documentation. - style: When changing elements styles or design and/or making changes to the code style (formatting, missing semicolons, etc.) without changing the code logic. - test: When adding or updating tests. - chore: When making changes to the build process or auxiliary tools and libraries. - revert: When undoing a previous commit. - refactor: When restructuring code without changing its external behavior, or is any of the other refactor types. - - Do not add any issues numeration, explain your output nor introduce your answer. - - Output directly only one commit message in plain text with the next format: {type}: {commit_message}. - - Be as concise as possible, keep the message under 50 characters. - """; - } - } - private RepositoryNode FindNodeRecursive(string id, List collection) { foreach (var node in collection) diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs index fdaad920..6c493449 100644 --- a/src/ViewModels/Pull.cs +++ b/src/ViewModels/Pull.cs @@ -147,9 +147,18 @@ namespace SourceGit.ViewModels if (FetchAllBranches) { SetProgressDescription($"Fetching remote: {_selectedRemote.Name}..."); - rs = new Commands.Fetch(_repo.FullPath, _selectedRemote.Name, NoTags, SetProgressDescription).Exec(); + rs = new Commands.Fetch( + _repo.FullPath, + _selectedRemote.Name, + NoTags, + _repo.Settings.EnablePruneOnFetch, + SetProgressDescription).Exec(); + if (!rs) + { + CallUIThread(() => _repo.SetWatcherEnabled(true)); return false; + } _repo.MarkFetched(); @@ -168,7 +177,14 @@ namespace SourceGit.ViewModels else { SetProgressDescription($"Pull {_selectedRemote.Name}/{_selectedBranch.Name}..."); - rs = new Commands.Pull(_repo.FullPath, _selectedRemote.Name, _selectedBranch.Name, UseRebase, NoTags, SetProgressDescription).Exec(); + rs = new Commands.Pull( + _repo.FullPath, + _selectedRemote.Name, + _selectedBranch.Name, + UseRebase, + NoTags, + _repo.Settings.EnablePruneOnFetch, + SetProgressDescription).Exec(); } if (rs && needPopStash) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 8f6e1f88..9dd5ab9d 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -45,6 +45,11 @@ namespace SourceGit.ViewModels get => _settings; } + public bool HasAllowedSignersFile + { + get => _hasAllowedSignersFile; + } + public int SelectedViewIndex { get => _selectedViewIndex; @@ -444,6 +449,12 @@ namespace SourceGit.ViewModels public void RefreshAll() { + Task.Run(() => + { + var allowedSignersFile = new Commands.Config(_fullpath).Get("gpg.ssh.allowedSignersFile"); + _hasAllowedSignersFile = !string.IsNullOrEmpty(allowedSignersFile); + }); + Task.Run(() => { RefreshBranches(); @@ -1276,6 +1287,45 @@ namespace SourceGit.ViewModels return menu; } + public ContextMenu CreateContextMenuForCustomAction() + { + var actions = new List(); + foreach (var action in _settings.CustomActions) + { + if (action.Scope == Models.CustomActionScope.Repository) + actions.Add(action); + } + + var menu = new ContextMenu(); + menu.Placement = PlacementMode.BottomEdgeAlignedLeft; + + if (actions.Count > 0) + { + foreach (var action in actions) + { + var dup = action; + var item = new MenuItem(); + item.Icon = App.CreateMenuIcon("Icons.Action"); + item.Header = dup.Name; + item.Click += (_, e) => + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowAndStartPopup(new ExecuteCustomAction(this, dup, null)); + + e.Handled = true; + }; + + menu.Items.Add(item); + } + } + else + { + menu.Items.Add(new MenuItem() { Header = App.Text("Repository.CustomActions.Empty") }); + } + + return menu; + } + public ContextMenu CreateContextMenuForLocalBranch(Models.Branch branch) { var menu = new ContextMenu(); @@ -1354,6 +1404,7 @@ namespace SourceGit.ViewModels e.Handled = true; }; menu.Items.Add(checkout); + menu.Items.Add(new MenuItem() { Header = "-" }); var worktree = _worktrees.Find(x => x.Branch == branch.FullName); var upstream = _branches.Find(x => x.FullName == branch.Upstream); @@ -1370,11 +1421,22 @@ namespace SourceGit.ViewModels e.Handled = true; }; - menu.Items.Add(new MenuItem() { Header = "-" }); + var fetchInto = new MenuItem(); + fetchInto.Header = new Views.NameHighlightedTextBlock("BranchCM.FetchInto", upstream.FriendlyName, branch.Name); + fetchInto.Icon = App.CreateMenuIcon("Icons.Fetch"); + fetchInto.IsEnabled = branch.TrackStatus.Ahead.Count == 0; + fetchInto.Click += (_, e) => + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowAndStartPopup(new FetchInto(this, branch, upstream)); + e.Handled = true; + }; + menu.Items.Add(fastForward); + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(fetchInto); } - menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(push); var merge = new MenuItem(); @@ -2114,7 +2176,7 @@ namespace SourceGit.ViewModels IsAutoFetching = true; Dispatcher.UIThread.Invoke(() => OnPropertyChanged(nameof(IsAutoFetching))); - new Commands.Fetch(_fullpath, "--all", false, null) { RaiseError = false }.Exec(); + new Commands.Fetch(_fullpath, "--all", false, _settings.EnablePruneOnFetch, null) { RaiseError = false }.Exec(); _lastFetchTime = DateTime.Now; IsAutoFetching = false; Dispatcher.UIThread.Invoke(() => OnPropertyChanged(nameof(IsAutoFetching))); @@ -2123,6 +2185,7 @@ namespace SourceGit.ViewModels private string _fullpath = string.Empty; private string _gitDir = string.Empty; private Models.RepositorySettings _settings = null; + private bool _hasAllowedSignersFile = false; private Models.Watcher _watcher = null; private Histories _histories = null; diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs index 3c969c4d..620db074 100644 --- a/src/ViewModels/RepositoryConfigure.cs +++ b/src/ViewModels/RepositoryConfigure.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Collections; using CommunityToolkit.Mvvm.ComponentModel; @@ -66,6 +67,12 @@ namespace SourceGit.ViewModels set => _repo.Settings.EnableSignOffForCommit = value; } + public bool EnablePruneOnFetch + { + get => _repo.Settings.EnablePruneOnFetch; + set => _repo.Settings.EnablePruneOnFetch = value; + } + public bool EnableAutoFetch { get => _repo.Settings.EnableAutoFetch; @@ -108,6 +115,29 @@ namespace SourceGit.ViewModels set => SetProperty(ref _selectedIssueTrackerRule, value); } + public List AvailableOpenAIServices + { + get; + private set; + } + + public string PreferedOpenAIService + { + get => _repo.Settings.PreferedOpenAIService; + set => _repo.Settings.PreferedOpenAIService = value; + } + + public AvaloniaList CustomActions + { + get => _repo.Settings.CustomActions; + } + + public Models.CustomAction SelectedCustomAction + { + get => _selectedCustomAction; + set => SetProperty(ref _selectedCustomAction, value); + } + public RepositoryConfigure(Repository repo) { _repo = repo; @@ -116,6 +146,13 @@ namespace SourceGit.ViewModels foreach (var remote in _repo.Remotes) Remotes.Add(remote.Name); + AvailableOpenAIServices = new List() { "---" }; + foreach (var service in Preference.Instance.OpenAIServices) + AvailableOpenAIServices.Add(service.Name); + + if (AvailableOpenAIServices.IndexOf(PreferedOpenAIService) == -1) + PreferedOpenAIService = "---"; + _cached = new Commands.Config(repo.FullPath).ListAll(); if (_cached.TryGetValue("user.name", out var name)) UserName = name; @@ -207,11 +244,21 @@ namespace SourceGit.ViewModels public void RemoveSelectedIssueTracker() { - if (_selectedIssueTrackerRule != null) - _repo.Settings.RemoveIssueTracker(_selectedIssueTrackerRule); + _repo.Settings.RemoveIssueTracker(_selectedIssueTrackerRule); SelectedIssueTrackerRule = null; } + public void AddNewCustomAction() + { + SelectedCustomAction = _repo.Settings.AddNewCustomAction(); + } + + public void RemoveSelectedCustomAction() + { + _repo.Settings.RemoveCustomAction(_selectedCustomAction); + SelectedCustomAction = null; + } + public void Save() { SetIfChanged("user.name", UserName, ""); @@ -245,5 +292,6 @@ namespace SourceGit.ViewModels private string _httpProxy; private Models.CommitTemplate _selectedCommitTemplate = null; private Models.IssueTrackerRule _selectedIssueTrackerRule = null; + private Models.CustomAction _selectedCustomAction = null; } } diff --git a/src/ViewModels/StashChanges.cs b/src/ViewModels/StashChanges.cs index ed3d2bfd..1ad3a0da 100644 --- a/src/ViewModels/StashChanges.cs +++ b/src/ViewModels/StashChanges.cs @@ -18,24 +18,27 @@ namespace SourceGit.ViewModels public bool IncludeUntracked { - get; - set; + get => _repo.Settings.IncludeUntrackedWhenStash; + set => _repo.Settings.IncludeUntrackedWhenStash = value; } public bool OnlyStaged { - get; - set; + get => _repo.Settings.OnlyStagedWhenStash; + set => _repo.Settings.OnlyStagedWhenStash = value; + } + + public bool KeepIndex + { + get => _repo.Settings.KeepIndexWhenStash; + set => _repo.Settings.KeepIndexWhenStash = value; } public StashChanges(Repository repo, List changes, bool hasSelectedFiles) { _repo = repo; _changes = changes; - HasSelectedFiles = hasSelectedFiles; - IncludeUntracked = true; - OnlyStaged = false; View = new Views.StashChanges() { DataContext = this }; } @@ -63,7 +66,7 @@ namespace SourceGit.ViewModels return Task.Run(() => { - var succ = new Commands.Stash(_repo.FullPath).Push(jobs, Message, !HasSelectedFiles && OnlyStaged); + var succ = new Commands.Stash(_repo.FullPath).Push(jobs, Message, !HasSelectedFiles && OnlyStaged, KeepIndex); CallUIThread(() => { _repo.MarkWorkingCopyDirtyManually(); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 6fcbedd9..020053f9 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -403,25 +403,6 @@ namespace SourceGit.ViewModels } } - public void GenerateCommitMessageByAI() - { - if (!Models.OpenAI.IsValid) - { - App.RaiseException(_repo.FullPath, "Bad configuration for OpenAI"); - return; - } - - if (_staged is { Count: > 0 }) - { - var dialog = new Views.AIAssistant(_repo.FullPath, _staged, generated => CommitMessage = generated); - App.OpenDialog(dialog); - } - else - { - App.RaiseException(_repo.FullPath, "No files added to commit!"); - } - } - public void Commit() { DoCommit(false, false, false); @@ -900,6 +881,44 @@ namespace SourceGit.ViewModels return null; var menu = new ContextMenu(); + + var ai = null as MenuItem; + var services = GetPreferedOpenAIServices(); + if (services.Count > 0) + { + ai = new MenuItem(); + ai.Icon = App.CreateMenuIcon("Icons.AIAssist"); + ai.Header = App.Text("ChangeCM.GenerateCommitMessage"); + + if (services.Count == 1) + { + ai.Click += (_, e) => + { + var dialog = new Views.AIAssistant(services[0], _repo.FullPath, _selectedStaged, generated => CommitMessage = generated); + App.OpenDialog(dialog); + e.Handled = true; + }; + } + else + { + foreach (var service in services) + { + var dup = service; + + var item = new MenuItem(); + item.Header = service.Name; + item.Click += (_, e) => + { + var dialog = new Views.AIAssistant(dup, _repo.FullPath, _selectedStaged, generated => CommitMessage = generated); + App.OpenDialog(dialog); + e.Handled = true; + }; + + ai.Items.Add(item); + } + } + } + if (_selectedStaged.Count == 1) { var change = _selectedStaged[0]; @@ -980,24 +999,6 @@ namespace SourceGit.ViewModels e.Handled = true; }; - var copyPath = new MenuItem(); - copyPath.Header = App.Text("CopyPath"); - copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, e) => - { - App.CopyText(change.Path); - e.Handled = true; - }; - - var copyFileName = new MenuItem(); - copyFileName.Header = App.Text("CopyFileName"); - copyFileName.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFileName.Click += (_, e) => - { - App.CopyText(Path.GetFileName(change.Path)); - e.Handled = true; - }; - menu.Items.Add(explore); menu.Items.Add(openWith); menu.Items.Add(new MenuItem() { Header = "-" }); @@ -1089,6 +1090,30 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); } + if (ai != null) + { + menu.Items.Add(ai); + menu.Items.Add(new MenuItem() { Header = "-" }); + } + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); + copyPath.Click += (_, e) => + { + App.CopyText(change.Path); + e.Handled = true; + }; + + var copyFileName = new MenuItem(); + copyFileName.Header = App.Text("CopyFileName"); + copyFileName.Icon = App.CreateMenuIcon("Icons.Copy"); + copyFileName.Click += (_, e) => + { + App.CopyText(Path.GetFileName(change.Path)); + e.Handled = true; + }; + menu.Items.Add(copyPath); menu.Items.Add(copyFileName); } @@ -1142,6 +1167,12 @@ namespace SourceGit.ViewModels menu.Items.Add(unstage); menu.Items.Add(stash); menu.Items.Add(patch); + + if (ai != null) + { + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(ai); + } } return menu; @@ -1211,6 +1242,51 @@ namespace SourceGit.ViewModels return menu; } + public ContextMenu CreateContextForOpenAI() + { + if (_staged == null || _staged.Count == 0) + { + App.RaiseException(_repo.FullPath, "No files added to commit!"); + return null; + } + + var services = GetPreferedOpenAIServices(); + if (services.Count == 0) + { + App.RaiseException(_repo.FullPath, "Bad configuration for OpenAI"); + return null; + } + + if (services.Count == 1) + { + var dialog = new Views.AIAssistant(services[0], _repo.FullPath, _staged, generated => CommitMessage = generated); + App.OpenDialog(dialog); + return null; + } + else + { + var menu = new ContextMenu() { Placement = PlacementMode.TopEdgeAlignedLeft }; + + foreach (var service in services) + { + var dup = service; + + var item = new MenuItem(); + item.Header = service.Name; + item.Click += (_, e) => + { + var dialog = new Views.AIAssistant(dup, _repo.FullPath, _staged, generated => CommitMessage = generated); + App.OpenDialog(dialog); + e.Handled = true; + }; + + menu.Items.Add(item); + } + + return menu; + } + } + private List GetStagedChanges() { if (_useAmend) @@ -1363,6 +1439,25 @@ namespace SourceGit.ViewModels return false; } + private IList GetPreferedOpenAIServices() + { + var services = Preference.Instance.OpenAIServices; + if (services == null || services.Count == 0) + return []; + + if (services.Count == 1) + return services; + + var prefered = _repo.Settings.PreferedOpenAIService; + foreach (var service in services) + { + if (service.Name.Equals(prefered, StringComparison.Ordinal)) + return [service]; + } + + return services; + } + private Repository _repo = null; private bool _isLoadingData = false; private bool _isStaging = false; diff --git a/src/Views/AIAssistant.axaml b/src/Views/AIAssistant.axaml index 1c266715..528d0e5b 100644 --- a/src/Views/AIAssistant.axaml +++ b/src/Views/AIAssistant.axaml @@ -15,30 +15,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/AIAssistant.axaml.cs b/src/Views/AIAssistant.axaml.cs index 6ceb5610..d81335eb 100644 --- a/src/Views/AIAssistant.axaml.cs +++ b/src/Views/AIAssistant.axaml.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Threading; namespace SourceGit.Views @@ -17,12 +16,14 @@ namespace SourceGit.Views InitializeComponent(); } - public AIAssistant(string repo, List changes, Action onDone) + public AIAssistant(Models.OpenAIService service, string repo, List changes, Action onDone) { + _service = service; _repo = repo; _changes = changes; _onDone = onDone; _cancel = new CancellationTokenSource(); + InitializeComponent(); } @@ -35,7 +36,7 @@ namespace SourceGit.Views Task.Run(() => { - var message = new Commands.GenerateCommitMessage(_repo, _changes, _cancel.Token, SetDescription).Result(); + var message = new Commands.GenerateCommitMessage(_service, _repo, _changes, _cancel.Token, SetDescription).Result(); if (_cancel.IsCancellationRequested) return; @@ -53,16 +54,12 @@ namespace SourceGit.Views _cancel.Cancel(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void SetDescription(string message) { Dispatcher.UIThread.Invoke(() => ProgressMessage.Text = message); } + private Models.OpenAIService _service; private string _repo; private List _changes; private Action _onDone; diff --git a/src/Views/About.axaml b/src/Views/About.axaml index 159a34a9..d781ee9e 100644 --- a/src/Views/About.axaml +++ b/src/Views/About.axaml @@ -13,30 +13,23 @@ WindowStartupLocation="CenterScreen"> - - + - - - - - diff --git a/src/Views/About.axaml.cs b/src/Views/About.axaml.cs index 4f7f953f..b02cd13f 100644 --- a/src/Views/About.axaml.cs +++ b/src/Views/About.axaml.cs @@ -21,11 +21,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void OnVisitAvaloniaUI(object _, PointerPressedEventArgs e) { Native.OS.OpenBrowser("https://www.avaloniaui.net/"); diff --git a/src/Views/Askpass.axaml b/src/Views/Askpass.axaml index e3e9a499..f2308805 100644 --- a/src/Views/Askpass.axaml +++ b/src/Views/Askpass.axaml @@ -13,30 +13,23 @@ WindowStartupLocation="CenterScreen"> - - + - - - - - @@ -49,10 +42,12 @@ + HorizontalAlignment="Stretch" + v:AutoFocusBehaviour.IsEnabled="True"> - - + - - - - - diff --git a/src/Views/AssumeUnchangedManager.axaml.cs b/src/Views/AssumeUnchangedManager.axaml.cs index ae9c7fe6..a0a5a352 100644 --- a/src/Views/AssumeUnchangedManager.axaml.cs +++ b/src/Views/AssumeUnchangedManager.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views @@ -11,11 +10,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void OnRemoveButtonClicked(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.AssumeUnchangedManager vm && sender is Button button) diff --git a/src/Views/Blame.axaml b/src/Views/Blame.axaml index 9cf8b78a..98e1c4f2 100644 --- a/src/Views/Blame.axaml +++ b/src/Views/Blame.axaml @@ -19,29 +19,26 @@ - + - - - - - - - - - - - + + + - - - + diff --git a/src/Views/Blame.axaml.cs b/src/Views/Blame.axaml.cs index f96870e0..164b89de 100644 --- a/src/Views/Blame.axaml.cs +++ b/src/Views/Blame.axaml.cs @@ -435,24 +435,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void MaximizeOrRestoreWindow(object _, TappedEventArgs e) - { - if (WindowState == WindowState.Maximized) - WindowState = WindowState.Normal; - else - WindowState = WindowState.Maximized; - - e.Handled = true; - } - - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - if (e.ClickCount == 1) - BeginMoveDrag(e); - - e.Handled = true; - } - protected override void OnClosed(EventArgs e) { base.OnClosed(e); diff --git a/src/Views/BranchCompare.axaml b/src/Views/BranchCompare.axaml index a8bbc4af..b0e227c2 100644 --- a/src/Views/BranchCompare.axaml +++ b/src/Views/BranchCompare.axaml @@ -21,29 +21,26 @@ - + - - - - - - - - - - - + + + - - - + diff --git a/src/Views/BranchCompare.axaml.cs b/src/Views/BranchCompare.axaml.cs index 844a1b6b..90ec1af5 100644 --- a/src/Views/BranchCompare.axaml.cs +++ b/src/Views/BranchCompare.axaml.cs @@ -10,24 +10,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void MaximizeOrRestoreWindow(object _, TappedEventArgs e) - { - if (WindowState == WindowState.Maximized) - WindowState = WindowState.Normal; - else - WindowState = WindowState.Maximized; - - e.Handled = true; - } - - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - if (e.ClickCount == 1) - BeginMoveDrag(e); - - e.Handled = true; - } - private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) { if (DataContext is ViewModels.BranchCompare vm && sender is ChangeCollectionView view) diff --git a/src/Views/CaptionButtonsMacOS.axaml b/src/Views/CaptionButtonsMacOS.axaml deleted file mode 100644 index 59c12e8d..00000000 --- a/src/Views/CaptionButtonsMacOS.axaml +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - #FFED6A5E - #FF69110A - #FFB24F46 - #FF2E0402 - #FFF4BF4F - #FF8F591D - #FFB78F3A - #FF522A0A - #FF61C554 - #FF296017 - #FF48943F - #FF102F07 - - - #FFED6A5E - #FF8C1A10 - #FFF09389 - #FF69120A - #FFF4BF4F - #FF8F591D - #FFFBEB74 - #FF705F1B - #FF61C554 - #FF296017 - #FF86F37F - #FF2C681A - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Views/CaptionButtonsMacOS.axaml.cs b/src/Views/CaptionButtonsMacOS.axaml.cs deleted file mode 100644 index 98bbb88f..00000000 --- a/src/Views/CaptionButtonsMacOS.axaml.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.VisualTree; - -namespace SourceGit.Views -{ - public partial class CaptionButtonsMacOS : UserControl - { - public static readonly StyledProperty IsCloseButtonOnlyProperty = - AvaloniaProperty.Register(nameof(IsCloseButtonOnly)); - - public bool IsCloseButtonOnly - { - get => GetValue(IsCloseButtonOnlyProperty); - set => SetValue(IsCloseButtonOnlyProperty, value); - } - - public CaptionButtonsMacOS() - { - InitializeComponent(); - } - - private void MinimizeWindow(object _, RoutedEventArgs e) - { - var window = this.FindAncestorOfType(); - if (window != null) - window.WindowState = WindowState.Minimized; - - e.Handled = true; - } - - private void MaximizeOrRestoreWindow(object _, RoutedEventArgs e) - { - var window = this.FindAncestorOfType(); - if (window != null) - window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; - - e.Handled = true; - } - - private void CloseWindow(object _, RoutedEventArgs e) - { - var window = this.FindAncestorOfType(); - window?.Close(); - e.Handled = true; - } - } -} diff --git a/src/Views/ChromelessWindow.cs b/src/Views/ChromelessWindow.cs index a9b9f259..107a7ba3 100644 --- a/src/Views/ChromelessWindow.cs +++ b/src/Views/ChromelessWindow.cs @@ -32,14 +32,35 @@ namespace SourceGit.Views Classes.Add("custom_window_frame"); } } - else + else if (OperatingSystem.IsWindows()) { ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome; ExtendClientAreaToDecorationsHint = true; - - if (OperatingSystem.IsWindows()) - Classes.Add("fix_maximized_padding"); + Classes.Add("fix_maximized_padding"); } + else + { + ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome; + ExtendClientAreaToDecorationsHint = true; + } + } + + public void BeginMoveWindow(object _, PointerPressedEventArgs e) + { + if (e.ClickCount == 1) + BeginMoveDrag(e); + + e.Handled = true; + } + + public void MaximizeOrRestoreWindow(object _, TappedEventArgs e) + { + if (WindowState == WindowState.Maximized) + WindowState = WindowState.Normal; + else + WindowState = WindowState.Maximized; + + e.Handled = true; } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 623332c4..62117480 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -71,6 +71,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -105,7 +132,7 @@ VerticalAlignment="Center" UseGraphColor="False"/> - + SetValue(MessageProperty, value); } + public static readonly StyledProperty SignInfoProperty = + AvaloniaProperty.Register(nameof(SignInfo)); + + public Models.CommitSignInfo SignInfo + { + get => GetValue(SignInfoProperty); + set => SetValue(SignInfoProperty, value); + } + public static readonly StyledProperty SupportsContainsInProperty = AvaloniaProperty.Register(nameof(SupportsContainsIn)); diff --git a/src/Views/CommitDetail.axaml b/src/Views/CommitDetail.axaml index 8307d650..cb99b3d9 100644 --- a/src/Views/CommitDetail.axaml +++ b/src/Views/CommitDetail.axaml @@ -21,6 +21,7 @@ diff --git a/src/Views/ConfigureWorkspace.axaml b/src/Views/ConfigureWorkspace.axaml index c7ed900b..9c18ad04 100644 --- a/src/Views/ConfigureWorkspace.axaml +++ b/src/Views/ConfigureWorkspace.axaml @@ -2,10 +2,8 @@ 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:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" - xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450" x:Class="SourceGit.Views.ConfigureWorkspace" x:DataType="vm:ConfigureWorkspace" @@ -17,30 +15,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/ConfigureWorkspace.axaml.cs b/src/Views/ConfigureWorkspace.axaml.cs index 82d8cd30..e2cc1cb2 100644 --- a/src/Views/ConfigureWorkspace.axaml.cs +++ b/src/Views/ConfigureWorkspace.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Input; namespace SourceGit.Views { @@ -15,10 +14,5 @@ namespace SourceGit.Views ViewModels.Preference.Instance.Save(); base.OnClosing(e); } - - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } } } diff --git a/src/Views/ConfirmCommitWithoutFiles.axaml b/src/Views/ConfirmCommitWithoutFiles.axaml index 0b457531..a056f016 100644 --- a/src/Views/ConfirmCommitWithoutFiles.axaml +++ b/src/Views/ConfirmCommitWithoutFiles.axaml @@ -15,30 +15,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/ConfirmCommitWithoutFiles.axaml.cs b/src/Views/ConfirmCommitWithoutFiles.axaml.cs index 0be18902..342600fc 100644 --- a/src/Views/ConfirmCommitWithoutFiles.axaml.cs +++ b/src/Views/ConfirmCommitWithoutFiles.axaml.cs @@ -1,4 +1,3 @@ -using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views @@ -10,11 +9,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void Sure(object _1, RoutedEventArgs _2) { if (DataContext is ViewModels.ConfirmCommitWithoutFiles vm) diff --git a/src/Views/ConfirmRestart.axaml b/src/Views/ConfirmRestart.axaml index 9aafda7e..de2ddd4f 100644 --- a/src/Views/ConfirmRestart.axaml +++ b/src/Views/ConfirmRestart.axaml @@ -13,30 +13,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/ConfirmRestart.axaml.cs b/src/Views/ConfirmRestart.axaml.cs index d0647731..ea49bea1 100644 --- a/src/Views/ConfirmRestart.axaml.cs +++ b/src/Views/ConfirmRestart.axaml.cs @@ -1,8 +1,6 @@ using System; using System.Diagnostics; -using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views @@ -14,11 +12,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void CloseWindow(object _1, RoutedEventArgs _2) { Console.Out.WriteLine("No passphrase entered."); diff --git a/src/Views/ConventionalCommitMessageBuilder.axaml b/src/Views/ConventionalCommitMessageBuilder.axaml index ab2ae37f..4fe8b8f9 100644 --- a/src/Views/ConventionalCommitMessageBuilder.axaml +++ b/src/Views/ConventionalCommitMessageBuilder.axaml @@ -5,7 +5,6 @@ xmlns:m="using:SourceGit.Models" 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.ConventionalCommitMessageBuilder" x:DataType="vm:ConventionalCommitMessageBuilder" @@ -18,30 +17,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/ConventionalCommitMessageBuilder.axaml.cs b/src/Views/ConventionalCommitMessageBuilder.axaml.cs index dfe56f0d..955450ed 100644 --- a/src/Views/ConventionalCommitMessageBuilder.axaml.cs +++ b/src/Views/ConventionalCommitMessageBuilder.axaml.cs @@ -1,4 +1,3 @@ -using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views @@ -10,11 +9,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void OnApplyClicked(object _, RoutedEventArgs e) { if (DataContext is ViewModels.ConventionalCommitMessageBuilder builder) diff --git a/src/Views/ExecuteCustomAction.axaml b/src/Views/ExecuteCustomAction.axaml new file mode 100644 index 00000000..9ee2b55d --- /dev/null +++ b/src/Views/ExecuteCustomAction.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/src/Views/ExecuteCustomAction.axaml.cs b/src/Views/ExecuteCustomAction.axaml.cs new file mode 100644 index 00000000..e4f9cecf --- /dev/null +++ b/src/Views/ExecuteCustomAction.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class ExecuteCustomAction : UserControl + { + public ExecuteCustomAction() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/FetchInto.axaml b/src/Views/FetchInto.axaml new file mode 100644 index 00000000..4a0c0966 --- /dev/null +++ b/src/Views/FetchInto.axaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/src/Views/FetchInto.axaml.cs b/src/Views/FetchInto.axaml.cs new file mode 100644 index 00000000..c61c052e --- /dev/null +++ b/src/Views/FetchInto.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class FetchInto : UserControl + { + public FetchInto() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/FileHistories.axaml b/src/Views/FileHistories.axaml index bc048706..703957b8 100644 --- a/src/Views/FileHistories.axaml +++ b/src/Views/FileHistories.axaml @@ -20,29 +20,26 @@ - + - - - - - - - - - - - + + + - - - + diff --git a/src/Views/FileHistories.axaml.cs b/src/Views/FileHistories.axaml.cs index 6a2ee7c9..9d74892b 100644 --- a/src/Views/FileHistories.axaml.cs +++ b/src/Views/FileHistories.axaml.cs @@ -11,24 +11,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void MaximizeOrRestoreWindow(object _, TappedEventArgs e) - { - if (WindowState == WindowState.Maximized) - WindowState = WindowState.Normal; - else - WindowState = WindowState.Maximized; - - e.Handled = true; - } - - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - if (e.ClickCount == 1) - BeginMoveDrag(e); - - e.Handled = true; - } - private void OnPressCommitSHA(object sender, PointerPressedEventArgs e) { if (sender is TextBlock { DataContext: Models.Commit commit } && diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index f372d5d5..20f49d90 100644 --- a/src/Views/Histories.axaml +++ b/src/Views/Histories.axaml @@ -12,15 +12,15 @@ x:Name="ThisControl"> - + - + - + - + diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml index 5d98238d..a28bc566 100644 --- a/src/Views/Hotkeys.axaml +++ b/src/Views/Hotkeys.axaml @@ -15,30 +15,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/Hotkeys.axaml.cs b/src/Views/Hotkeys.axaml.cs index 2a21fef9..d8b5e1a8 100644 --- a/src/Views/Hotkeys.axaml.cs +++ b/src/Views/Hotkeys.axaml.cs @@ -1,5 +1,3 @@ -using Avalonia.Input; - namespace SourceGit.Views { public partial class Hotkeys : ChromelessWindow @@ -8,10 +6,5 @@ namespace SourceGit.Views { InitializeComponent(); } - - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } } } diff --git a/src/Views/InteractiveRebase.axaml b/src/Views/InteractiveRebase.axaml index e1161a2b..c008193b 100644 --- a/src/Views/InteractiveRebase.axaml +++ b/src/Views/InteractiveRebase.axaml @@ -16,30 +16,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/InteractiveRebase.axaml.cs b/src/Views/InteractiveRebase.axaml.cs index 8f75cc26..a31b8a23 100644 --- a/src/Views/InteractiveRebase.axaml.cs +++ b/src/Views/InteractiveRebase.axaml.cs @@ -81,11 +81,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void CloseWindow(object _1, RoutedEventArgs _2) { Close(); diff --git a/src/Views/LFSLocks.axaml b/src/Views/LFSLocks.axaml index ccbe9fe2..ac495bf1 100644 --- a/src/Views/LFSLocks.axaml +++ b/src/Views/LFSLocks.axaml @@ -16,30 +16,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/LFSLocks.axaml.cs b/src/Views/LFSLocks.axaml.cs index ee4b6ff1..695341f4 100644 --- a/src/Views/LFSLocks.axaml.cs +++ b/src/Views/LFSLocks.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views @@ -11,11 +10,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void OnUnlockButtonClicked(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.LFSLocks vm && sender is Button button) diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml index 285ea72c..ad5a7f34 100644 --- a/src/Views/Launcher.axaml +++ b/src/Views/Launcher.axaml @@ -25,13 +25,11 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/Preference.axaml.cs b/src/Views/Preference.axaml.cs index 2f08e0db..e54d4c25 100644 --- a/src/Views/Preference.axaml.cs +++ b/src/Views/Preference.axaml.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Avalonia; using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Platform.Storage; @@ -74,6 +73,15 @@ namespace SourceGit.Views set; } + public static readonly StyledProperty SelectedOpenAIServiceProperty = + AvaloniaProperty.Register(nameof(SelectedOpenAIService)); + + public Models.OpenAIService SelectedOpenAIService + { + get => GetValue(SelectedOpenAIServiceProperty); + set => SetValue(SelectedOpenAIServiceProperty, value); + } + public Preference() { var pref = ViewModels.Preference.Instance; @@ -157,11 +165,6 @@ namespace SourceGit.Views base.OnClosing(e); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private async void SelectThemeOverrideFile(object _, RoutedEventArgs e) { var options = new FilePickerOpenOptions() @@ -312,5 +315,24 @@ namespace SourceGit.Views e.Handled = true; } + + private void OnAddOpenAIService(object sender, RoutedEventArgs e) + { + var service = new Models.OpenAIService() { Name = "Unnamed Service" }; + ViewModels.Preference.Instance.OpenAIServices.Add(service); + SelectedOpenAIService = service; + + e.Handled = true; + } + + private void OnRemoveSelectedOpenAIService(object sender, RoutedEventArgs e) + { + if (SelectedOpenAIService == null) + return; + + ViewModels.Preference.Instance.OpenAIServices.Remove(SelectedOpenAIService); + SelectedOpenAIService = null; + e.Handled = true; + } } } diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml index 6383d904..f1deca3a 100644 --- a/src/Views/RepositoryConfigure.axaml +++ b/src/Views/RepositoryConfigure.axaml @@ -5,6 +5,7 @@ xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:ac="using:Avalonia.Controls.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.RepositoryConfigure" x:DataType="vm:RepositoryConfigure" @@ -16,30 +17,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - @@ -51,7 +45,7 @@ - + - + + + @@ -340,6 +338,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 7e559cc2..3faba5ee 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; -using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; namespace SourceGit.Views { @@ -16,9 +17,19 @@ namespace SourceGit.Views base.OnClosing(e); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) + private async void SelectExecutableForCustomAction(object sender, RoutedEventArgs e) { - BeginMoveDrag(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; } } } diff --git a/src/Views/RepositoryToolbar.axaml b/src/Views/RepositoryToolbar.axaml index b76cfd63..c1eec786 100644 --- a/src/Views/RepositoryToolbar.axaml +++ b/src/Views/RepositoryToolbar.axaml @@ -96,6 +96,10 @@ + + diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index 27ac43cd..55132620 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -91,6 +91,17 @@ namespace SourceGit.Views e.Handled = true; } + + private void OpenCustomActionMenu(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.Repository repo) + { + var menu = repo.CreateContextMenuForCustomAction(); + (sender as Control)?.OpenContextMenu(menu); + } + + e.Handled = true; + } } } diff --git a/src/Views/SelfUpdate.axaml b/src/Views/SelfUpdate.axaml index a8eb152c..6fd2c415 100644 --- a/src/Views/SelfUpdate.axaml +++ b/src/Views/SelfUpdate.axaml @@ -17,30 +17,23 @@ WindowStartupLocation="CenterOwner"> - - + - - - - - diff --git a/src/Views/SelfUpdate.axaml.cs b/src/Views/SelfUpdate.axaml.cs index 800d9295..cdaac940 100644 --- a/src/Views/SelfUpdate.axaml.cs +++ b/src/Views/SelfUpdate.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views @@ -11,11 +10,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void CloseWindow(object _1, RoutedEventArgs _2) { Close(); diff --git a/src/Views/StandaloneCommitMessageEditor.axaml b/src/Views/StandaloneCommitMessageEditor.axaml index f59d3e84..8a0dc91a 100644 --- a/src/Views/StandaloneCommitMessageEditor.axaml +++ b/src/Views/StandaloneCommitMessageEditor.axaml @@ -14,30 +14,23 @@ WindowStartupLocation="CenterScreen"> - - + - - - - - diff --git a/src/Views/StandaloneCommitMessageEditor.axaml.cs b/src/Views/StandaloneCommitMessageEditor.axaml.cs index 08b0d6f4..6833fb33 100644 --- a/src/Views/StandaloneCommitMessageEditor.axaml.cs +++ b/src/Views/StandaloneCommitMessageEditor.axaml.cs @@ -1,7 +1,6 @@ using System; using System.IO; -using Avalonia.Input; using Avalonia.Interactivity; namespace SourceGit.Views @@ -40,11 +39,6 @@ namespace SourceGit.Views App.Quit(_exitCode); } - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } - private void SaveAndClose(object _1, RoutedEventArgs _2) { File.WriteAllText(_file, Editor.Text); diff --git a/src/Views/StashChanges.axaml b/src/Views/StashChanges.axaml index 5396da4d..b3e6254a 100644 --- a/src/Views/StashChanges.axaml +++ b/src/Views/StashChanges.axaml @@ -11,7 +11,7 @@ - + - - + + + + diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index b3e52771..87847c06 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -9,11 +9,11 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.StashesPage" x:DataType="vm:StashesPage"> - + - + diff --git a/src/Views/StashesPage.axaml.cs b/src/Views/StashesPage.axaml.cs index 60e8f0c1..f3048889 100644 --- a/src/Views/StashesPage.axaml.cs +++ b/src/Views/StashesPage.axaml.cs @@ -9,6 +9,20 @@ namespace SourceGit.Views InitializeComponent(); } + private void OnMainLayoutSizeChanged(object sender, SizeChangedEventArgs e) + { + var grid = sender as Grid; + if (grid == null) + return; + + var layout = ViewModels.Preference.Instance.Layout; + var width = grid.Bounds.Width; + var maxLeft = width - 304; + + if (layout.StashesLeftWidth.Value - maxLeft > 1.0) + layout.StashesLeftWidth = new GridLength(maxLeft, GridUnitType.Pixel); + } + private void OnStashContextRequested(object sender, ContextRequestedEventArgs e) { if (DataContext is ViewModels.StashesPage vm && sender is Border border) diff --git a/src/Views/Statistics.axaml b/src/Views/Statistics.axaml index 6e2a00dd..0c002407 100644 --- a/src/Views/Statistics.axaml +++ b/src/Views/Statistics.axaml @@ -16,30 +16,23 @@ CanResize="False"> - - + - - - - - diff --git a/src/Views/Statistics.axaml.cs b/src/Views/Statistics.axaml.cs index 3c5e70b6..4ebf9016 100644 --- a/src/Views/Statistics.axaml.cs +++ b/src/Views/Statistics.axaml.cs @@ -1,5 +1,3 @@ -using Avalonia.Input; - namespace SourceGit.Views { public partial class Statistics : ChromelessWindow @@ -8,10 +6,5 @@ namespace SourceGit.Views { InitializeComponent(); } - - private void BeginMoveWindow(object _, PointerPressedEventArgs e) - { - BeginMoveDrag(e); - } } } diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 3d080fc6..4550e46a 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -8,116 +8,134 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600" x:Class="SourceGit.Views.WorkingCopy" x:DataType="vm:WorkingCopy"> - + - + - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + + diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index 2b01b861..f64e1a30 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -11,6 +11,20 @@ namespace SourceGit.Views InitializeComponent(); } + private void OnMainLayoutSizeChanged(object sender, SizeChangedEventArgs e) + { + var grid = sender as Grid; + if (grid == null) + return; + + var layout = ViewModels.Preference.Instance.Layout; + var width = grid.Bounds.Width; + var maxLeft = width - 304; + + if (layout.WorkingCopyLeftWidth.Value - maxLeft > 1.0) + layout.WorkingCopyLeftWidth = new GridLength(maxLeft, GridUnitType.Pixel); + } + private void OnOpenCommitMessagePicker(object sender, RoutedEventArgs e) { if (sender is Button button && DataContext is ViewModels.WorkingCopy vm) @@ -120,6 +134,17 @@ namespace SourceGit.Views e.Handled = true; } + private void OnOpenOpenAIHelper(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.WorkingCopy vm) + { + var menu = vm.CreateContextForOpenAI(); + (sender as Button)?.OpenContextMenu(menu); + } + + e.Handled = true; + } + private void OnOpenConventionalCommitHelper(object _, RoutedEventArgs e) { if (DataContext is ViewModels.WorkingCopy vm)