diff --git a/.editorconfig b/.editorconfig index 3ad9d05b..83b15884 100644 --- a/.editorconfig +++ b/.editorconfig @@ -293,6 +293,10 @@ end_of_line = lf [*.{cmd,bat}] end_of_line = crlf +# Package manifests +[{*.spec,control}] +end_of_line = lf + # YAML files [*.{yml,yaml}] indent_size = 2 diff --git a/.gitattributes b/.gitattributes index 7410eb08..bd1dfea9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,10 +3,12 @@ *.png binary *.ico binary *.sh text eol=lf +*.spec text eol=lf +control text eol=lf *.bat text eol=crlf *.cmd text eol=crlf *.ps1 text eol=crlf *.json text .gitattributes export-ignore -.gitignore export-ignore \ No newline at end of file +.gitignore export-ignore diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad62efb6..d4117364 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Configure arm64 packages if: ${{ matrix.runtime == 'linux-arm64' }} run: | diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 53affe3d..e973c1ab 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -3,7 +3,7 @@ on: workflow_call: inputs: version: - description: Source Git package version + description: SourceGit package version required: true type: string jobs: diff --git a/README.md b/README.md index e464f74c..d49fb1e4 100644 --- a/README.md +++ b/README.md @@ -47,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.70%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-100.00%25-brightgreen)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-86.58%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-100.00%25-brightgreen)](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) +[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-100.00%25-brightgreen)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.14%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-98.42%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.14%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 diff --git a/SourceGit.sln b/SourceGit.sln index 9799a09e..9c5fcdb1 100644 --- a/SourceGit.sln +++ b/SourceGit.sln @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{F45A EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{67B6D05F-A000-40BA-ADB4-C9065F880D7B}" ProjectSection(SolutionItems) = preProject + .github\workflows\build.yml = .github\workflows\build.yml .github\workflows\ci.yml = .github\workflows\ci.yml .github\workflows\package.yml = .github\workflows\package.yml .github\workflows\release.yml = .github\workflows\release.yml diff --git a/TRANSLATION.md b/TRANSLATION.md index 587ba99a..5967d21e 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -1,22 +1,4 @@ -### de_DE.axaml: 98.70% - - -
-Missing Keys - -- Text.Diff.SaveAsPatch -- Text.Diff.VisualLines.All -- Text.Hotkeys.Repo.CreateBranchOnCommit -- Text.Hotkeys.Repo.Fetch -- Text.Hotkeys.Repo.Pull -- Text.Hotkeys.Repo.Push -- Text.IssueLinkCM.OpenInBrowser -- Text.IssueLinkCM.CopyLink -- Text.Preference.Appearance.EditorFontSize - -
- -### es_ES.axaml: 100.00% +### de_DE.axaml: 100.00%
@@ -26,115 +8,53 @@
-### fr_FR.axaml: 86.58% +### es_ES.axaml: 99.14% + + +
+Missing Keys + +- Text.Preference.Appearance.FontSize +- Text.Preference.Appearance.FontSize.Default +- Text.Preference.Appearance.FontSize.Editor +- Text.Repository.FilterCommits.Default +- Text.Repository.FilterCommits.Exclude +- Text.Repository.FilterCommits.Include + +
+ +### fr_FR.axaml: 98.42%
Missing Keys -- 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 -- Text.ConventionalCommit -- Text.ConventionalCommit.BreakingChanges -- Text.ConventionalCommit.ClosedIssue -- Text.ConventionalCommit.Detail -- Text.ConventionalCommit.Scope -- Text.ConventionalCommit.ShortDescription -- Text.ConventionalCommit.Type -- Text.Diff.IgnoreWhitespace -- Text.Diff.SaveAsPatch -- Text.Diff.VisualLines.All -- Text.Discard.IncludeIgnored -- Text.ExecuteCustomAction -- Text.ExecuteCustomAction.Name -- Text.FileHistory.FileChange -- Text.GitLFS.Locks.OnlyMine -- Text.Histories.Header.AuthorTime -- Text.Histories.Tips -- Text.Histories.Tips.MacOS -- Text.Histories.Tips.Prefix -- Text.Hotkeys.Repo.CommitWithAutoStage -- Text.Hotkeys.Repo.CreateBranchOnCommit -- Text.Hotkeys.Repo.DiscardSelected -- Text.Hotkeys.Repo.Fetch -- Text.Hotkeys.Repo.Pull -- Text.Hotkeys.Repo.Push -- Text.IssueLinkCM.OpenInBrowser -- Text.IssueLinkCM.CopyLink -- Text.MoveRepositoryNode -- Text.MoveRepositoryNode.Target -- 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.Appearance.EditorFontSize -- Text.Preference.General.ShowAuthorTime -- Text.Preference.Integration -- Text.Preference.Shell -- Text.Preference.Shell.Type -- Text.Preference.Shell.Path -- Text.Repository.AutoFetching +- Text.Preference.Appearance.FontSize +- Text.Preference.Appearance.FontSize.Default +- Text.Preference.Appearance.FontSize.Editor - Text.Repository.CustomActions -- Text.Repository.CustomActions.Empty -- Text.Repository.EnableReflog -- Text.Repository.Search.InCurrentBranch +- Text.Repository.FilterCommits.Default +- Text.Repository.FilterCommits.Exclude +- Text.Repository.FilterCommits.Include - Text.ScanRepositories -- Text.ScanRepositories.RootDir -- Text.Squash.Into -- Text.Stash.KeepIndex -- Text.Stash.OnlyStagedChanges -- Text.Stash.TipForSelectedFiles -- Text.Statistics.Overview -- Text.TagCM.CopyMessage -- Text.Welcome.Move -- Text.Welcome.ScanDefaultCloneDir -- Text.WorkingCopy.CommitTip -- Text.WorkingCopy.CommitWithAutoStage -- Text.WorkingCopy.ConfirmCommitWithoutFiles -- Text.Workspace -- Text.Workspace.Configure
-### pt_BR.axaml: 100.00% +### pt_BR.axaml: 99.14%
Missing Keys - +- Text.Preference.Appearance.FontSize +- Text.Preference.Appearance.FontSize.Default +- Text.Preference.Appearance.FontSize.Editor +- Text.Repository.FilterCommits.Default +- Text.Repository.FilterCommits.Exclude +- Text.Repository.FilterCommits.Include
diff --git a/VERSION b/VERSION index 833d11c8..fb6559a3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.38 \ No newline at end of file +8.39 \ No newline at end of file diff --git a/build/resources/_common/applications/sourcegit.desktop b/build/resources/_common/applications/sourcegit.desktop index ff7ef135..bcf9c813 100644 --- a/build/resources/_common/applications/sourcegit.desktop +++ b/build/resources/_common/applications/sourcegit.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Name=Source Git +Name=SourceGit Comment=Open-source & Free Git GUI Client Exec=/opt/sourcegit/sourcegit Icon=/usr/share/icons/sourcegit.png diff --git a/build/resources/rpm/SPECS/build.spec b/build/resources/rpm/SPECS/build.spec index 9dda5f96..d31b6587 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 or libX11-6) -Requires: (libSM or libSM6) +Requires: libX11.so.6()(%{__isa_bits}bit) +Requires: libSM.so.6()(%{__isa_bits}bit) %define _build_id_links none diff --git a/build/scripts/package.linux.sh b/build/scripts/package.linux.sh index 5ffd5a27..5abb058b 100755 --- a/build/scripts/package.linux.sh +++ b/build/scripts/package.linux.sh @@ -5,16 +5,6 @@ set -o set -u set pipefail -if [[ -z "$VERSION" ]]; then - echo "Provide the version as environment variable VERSION" - exit 1 -fi - -if [[ -z "$RUNTIME" ]]; then - echo "Provide the runtime as environment variable RUNTIME" - exit 1 -fi - arch= appimage_arch= target= diff --git a/build/scripts/package.osx-app.sh b/build/scripts/package.osx-app.sh index 4a50a860..2d43e24a 100755 --- a/build/scripts/package.osx-app.sh +++ b/build/scripts/package.osx-app.sh @@ -5,16 +5,6 @@ set -o set -u set pipefail -if [[ -z "$VERSION" ]]; then - echo "Provide the version as environment variable VERSION" - exit 1 -fi - -if [[ -z "$RUNTIME" ]]; then - echo "Provide the runtime as environment variable RUNTIME" - exit 1 -fi - cd build mkdir -p SourceGit.app/Contents/Resources diff --git a/build/scripts/package.windows-portable.sh b/build/scripts/package.windows-portable.sh index 9ba29216..6bd3879b 100755 --- a/build/scripts/package.windows-portable.sh +++ b/build/scripts/package.windows-portable.sh @@ -5,16 +5,6 @@ set -o set -u set pipefail -if [[ -z "$VERSION" ]]; then - echo "Provide the version as environment variable VERSION" - exit 1 -fi - -if [[ -z "$RUNTIME" ]]; then - echo "Provide the runtime as environment variable RUNTIME" - exit 1 -fi - cd build rm -rf SourceGit/*.pdb diff --git a/global.json b/global.json index b5b37b60..a27a2b82 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "9.0.0", "rollForward": "latestMajor", "allowPrerelease": false } diff --git a/src/App.axaml.cs b/src/App.axaml.cs index dfec763b..0615724a 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -478,17 +478,20 @@ namespace SourceGit if (args.Length <= 1 || !args[0].Equals("--rebase-message-editor", StringComparison.Ordinal)) return false; + exitCode = 0; + var file = args[1]; var filename = Path.GetFileName(file); if (!filename.Equals("COMMIT_EDITMSG", StringComparison.OrdinalIgnoreCase)) return true; - var jobsFile = Path.Combine(Path.GetDirectoryName(file)!, "sourcegit_rebase_jobs.json"); + var gitDir = Path.GetDirectoryName(file)!; + var jobsFile = Path.Combine(gitDir, "sourcegit_rebase_jobs.json"); if (!File.Exists(jobsFile)) return true; var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection); - var doneFile = Path.Combine(Path.GetDirectoryName(file)!, "rebase-merge", "done"); + var doneFile = Path.Combine(gitDir, "rebase-merge", "done"); if (!File.Exists(doneFile)) return true; @@ -499,7 +502,6 @@ namespace SourceGit var job = collection.Jobs[done.Length - 1]; File.WriteAllText(file, job.Message); - exitCode = 0; return true; } diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs index 890b54ee..4ec8da82 100644 --- a/src/Commands/Branch.cs +++ b/src/Commands/Branch.cs @@ -48,8 +48,18 @@ var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; - cmd.SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); - cmd.Args = $"push {remote} --delete {name}"; + + bool exists = new Remote(repo).HasBranch(remote, name); + if (exists) + { + cmd.SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); + cmd.Args = $"push {remote} --delete {name}"; + } + else + { + cmd.Args = $"branch -D -r {remote}/{name}"; + } + return cmd.Exec(); } } diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index 04103d68..dea15592 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -28,9 +28,9 @@ namespace SourceGit.Commands Context = repo; if (ignoreWhitespace) - Args = $"diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}"; + Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}"; else - Args = $"diff --patch --ignore-cr-at-eol --unified={unified} {opt}"; + Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}"; } public Models.DiffResult Result() @@ -129,7 +129,7 @@ namespace SourceGit.Commands _oldLine = int.Parse(match.Groups[1].Value); _newLine = int.Parse(match.Groups[2].Value); _result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0)); - } + } } else { diff --git a/src/Commands/ExecuteCustomAction.cs b/src/Commands/ExecuteCustomAction.cs index f5fec82c..4981b2f9 100644 --- a/src/Commands/ExecuteCustomAction.cs +++ b/src/Commands/ExecuteCustomAction.cs @@ -44,7 +44,7 @@ namespace SourceGit.Commands { outputHandler?.Invoke(e.Data); builder.AppendLine(e.Data); - } + } }; try diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs index 08d2d1c6..834cd7fc 100644 --- a/src/Commands/Fetch.cs +++ b/src/Commands/Fetch.cs @@ -16,7 +16,7 @@ namespace SourceGit.Commands if (noTags) Args += "--no-tags "; else - Args += "--force "; + Args += "--tags "; if (prune) Args += "--prune "; diff --git a/src/Commands/Remote.cs b/src/Commands/Remote.cs index f2e8d09e..beaf412b 100644 --- a/src/Commands/Remote.cs +++ b/src/Commands/Remote.cs @@ -45,5 +45,14 @@ Args = "remote set-url" + (isPush ? " --push " : " ") + $"{name} {url}"; return Exec(); } + + public bool HasBranch(string remote, string branch) + { + SSHKey = new Config(WorkingDirectory).Get($"remote.{remote}.sshkey"); + Args = $"ls-remote {remote} {branch}"; + + var rs = ReadToEnd(); + return rs.IsSuccess && rs.StdOut.Trim().Length > 0; + } } } diff --git a/src/Commands/Statistics.cs b/src/Commands/Statistics.cs index adfcc574..511c43e8 100644 --- a/src/Commands/Statistics.cs +++ b/src/Commands/Statistics.cs @@ -4,11 +4,11 @@ namespace SourceGit.Commands { public class Statistics : Command { - public Statistics(string repo) + public Statistics(string repo, int max) { WorkingDirectory = repo; Context = repo; - Args = $"log --date-order --branches --remotes -40000 --pretty=format:\"%ct$%aN\""; + Args = $"log --date-order --branches --remotes -{max} --pretty=format:\"%ct$%aN\""; } public Models.Statistics Result() diff --git a/src/Converters/FilterModeConverters.cs b/src/Converters/FilterModeConverters.cs new file mode 100644 index 00000000..c486af5e --- /dev/null +++ b/src/Converters/FilterModeConverters.cs @@ -0,0 +1,22 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace SourceGit.Converters +{ + public static class FilterModeConverters + { + public static readonly FuncValueConverter ToBorderBrush = + new FuncValueConverter(v => + { + switch (v) + { + case Models.FilterMode.Included: + return Brushes.Green; + case Models.FilterMode.Excluded: + return Brushes.Red; + default: + return Brushes.Transparent; + } + }); + } +} diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs index b5398835..a09f808c 100644 --- a/src/Models/ExternalMerger.cs +++ b/src/Models/ExternalMerger.cs @@ -39,7 +39,7 @@ namespace SourceGit.Models new ExternalMerger(4, "tortoise_merge", "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""), new ExternalMerger(5, "kdiff3", "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(6, "beyond_compare", "Beyond Compare", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), - new ExternalMerger(7, "win_merge", "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(7, "win_merge", "WinMerge", "WinMergeU.exe", "\"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(8, "codium", "VSCodium", "VSCodium.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(9, "p4merge", "P4Merge", "p4merge.exe", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""), }; diff --git a/src/Models/Filter.cs b/src/Models/Filter.cs new file mode 100644 index 00000000..af4569fa --- /dev/null +++ b/src/Models/Filter.cs @@ -0,0 +1,60 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.Models +{ + public enum FilterType + { + LocalBranch = 0, + LocalBranchFolder, + RemoteBranch, + RemoteBranchFolder, + Tag, + } + + public enum FilterMode + { + None = 0, + Included, + Excluded, + } + + public class Filter : ObservableObject + { + public string Pattern + { + get => _pattern; + set => SetProperty(ref _pattern, value); + } + + public FilterType Type + { + get; + set; + } = FilterType.LocalBranch; + + public FilterMode Mode + { + get => _mode; + set => SetProperty(ref _mode, value); + } + + public bool IsBranch + { + get => Type != FilterType.Tag; + } + + public Filter() + { + } + + public Filter(string pattern, FilterType type, FilterMode mode) + { + _pattern = pattern; + _mode = mode; + Type = type; + } + + private string _pattern = string.Empty; + private FilterMode _mode = FilterMode.None; + } +} diff --git a/src/Models/OpenAI.cs b/src/Models/OpenAI.cs index e9c7b5ed..c5ca7449 100644 --- a/src/Models/OpenAI.cs +++ b/src/Models/OpenAI.cs @@ -155,7 +155,12 @@ namespace SourceGit.Models var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(60) }; if (!string.IsNullOrEmpty(ApiKey)) - client.DefaultRequestHeaders.Add("Authorization", $"Bearer {ApiKey}"); + { + if (Server.Contains("openai.azure.com/", StringComparison.Ordinal)) + client.DefaultRequestHeaders.Add("api-key", ApiKey); + else + client.DefaultRequestHeaders.Add("Authorization", $"Bearer {ApiKey}"); + } var req = new StringContent(JsonSerializer.Serialize(chat, JsonCodeGen.Default.OpenAIChatRequest), Encoding.UTF8, "application/json"); try diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs index 77c58ee7..f6796198 100644 --- a/src/Models/RepositorySettings.cs +++ b/src/Models/RepositorySettings.cs @@ -1,4 +1,8 @@ -using Avalonia.Collections; +using System; +using System.Collections.Generic; +using System.Text; + +using Avalonia.Collections; namespace SourceGit.Models { @@ -76,11 +80,11 @@ namespace SourceGit.Models set; } = true; - public AvaloniaList Filters + public AvaloniaList HistoriesFilters { get; set; - } = new AvaloniaList(); + } = new AvaloniaList(); public AvaloniaList CommitTemplates { @@ -148,6 +152,208 @@ namespace SourceGit.Models set; } = "---"; + public Dictionary CollectHistoriesFilters() + { + var map = new Dictionary(); + foreach (var filter in HistoriesFilters) + map.Add(filter.Pattern, filter.Mode); + return map; + } + + public bool UpdateHistoriesFilter(string pattern, FilterType type, FilterMode mode) + { + // Clear all filters when there's a filter that has different mode. + if (mode != FilterMode.None) + { + var clear = false; + foreach (var filter in HistoriesFilters) + { + if (filter.Mode != mode) + { + clear = true; + break; + } + } + + if (clear) + { + HistoriesFilters.Clear(); + HistoriesFilters.Add(new Filter(pattern, type, mode)); + return true; + } + } + else + { + for (int i = 0; i < HistoriesFilters.Count; i++) + { + var filter = HistoriesFilters[i]; + if (filter.Type == type && filter.Pattern.Equals(pattern, StringComparison.Ordinal)) + { + HistoriesFilters.RemoveAt(i); + return true; + } + } + + return false; + } + + for (int i = 0; i < HistoriesFilters.Count; i++) + { + var filter = HistoriesFilters[i]; + if (filter.Type != type) + continue; + + if (filter.Pattern.Equals(pattern, StringComparison.Ordinal)) + return false; + } + + HistoriesFilters.Add(new Filter(pattern, type, mode)); + return true; + } + + public void RemoveChildrenBranchFilters(string pattern) + { + var dirty = new List(); + var prefix = $"{pattern}/"; + + foreach (var filter in HistoriesFilters) + { + if (filter.Type == FilterType.Tag) + continue; + + if (filter.Pattern.StartsWith(prefix, StringComparison.Ordinal)) + dirty.Add(filter); + } + + foreach (var filter in dirty) + HistoriesFilters.Remove(filter); + } + + public string BuildHistoriesFilter() + { + var excludedBranches = new List(); + var excludedRemotes = new List(); + var excludedTags = new List(); + var includedBranches = new List(); + var includedRemotes = new List(); + var includedTags = new List(); + foreach (var filter in HistoriesFilters) + { + if (filter.Type == FilterType.LocalBranch) + { + var name = filter.Pattern.Substring(11); + var b = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]"; + + if (filter.Mode == FilterMode.Included) + includedBranches.Add(b); + else if (filter.Mode == FilterMode.Excluded) + excludedBranches.Add(b); + } + else if (filter.Type == FilterType.LocalBranchFolder) + { + if (filter.Mode == FilterMode.Included) + includedBranches.Add($"{filter.Pattern.Substring(11)}/*"); + else if (filter.Mode == FilterMode.Excluded) + excludedBranches.Add($"{filter.Pattern.Substring(11)}/*"); + } + else if (filter.Type == FilterType.RemoteBranch) + { + var name = filter.Pattern.Substring(13); + var r = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]"; + + if (filter.Mode == FilterMode.Included) + includedRemotes.Add(r); + else if (filter.Mode == FilterMode.Excluded) + excludedRemotes.Add(r); + } + else if (filter.Type == FilterType.RemoteBranchFolder) + { + if (filter.Mode == FilterMode.Included) + includedRemotes.Add($"{filter.Pattern.Substring(13)}/*"); + else if (filter.Mode == FilterMode.Excluded) + excludedRemotes.Add($"{filter.Pattern.Substring(13)}/*"); + } + else if (filter.Type == FilterType.Tag) + { + var name = filter.Pattern; + var t = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]"; + + if (filter.Mode == FilterMode.Included) + includedTags.Add(t); + else if (filter.Mode == FilterMode.Excluded) + excludedTags.Add(t); + } + } + + bool hasIncluded = includedBranches.Count > 0 || includedRemotes.Count > 0 || includedTags.Count > 0; + bool hasExcluded = excludedBranches.Count > 0 || excludedRemotes.Count > 0 || excludedTags.Count > 0; + + var builder = new StringBuilder(); + if (hasIncluded) + { + foreach (var b in includedBranches) + { + builder.Append("--branches="); + builder.Append(b); + builder.Append(' '); + } + + foreach (var r in includedRemotes) + { + builder.Append("--remotes="); + builder.Append(r); + builder.Append(' '); + } + + foreach (var t in includedTags) + { + builder.Append("--tags="); + builder.Append(t); + builder.Append(' '); + } + } + else if (hasExcluded) + { + if (excludedBranches.Count > 0) + { + foreach (var b in excludedBranches) + { + builder.Append("--exclude="); + builder.Append(b); + builder.Append(' '); + } + } + + builder.Append("--exclude=HEA[D] --branches "); + + if (excludedRemotes.Count > 0) + { + foreach (var r in excludedRemotes) + { + builder.Append("--exclude="); + builder.Append(r); + builder.Append(' '); + } + } + + builder.Append("--exclude=origin/HEA[D] --remotes "); + + if (excludedTags.Count > 0) + { + foreach (var t in excludedTags) + { + builder.Append("--exclude="); + builder.Append(t); + builder.Append(' '); + } + } + + builder.Append("--tags "); + } + + return builder.ToString(); + } + public void PushCommitMessage(string message) { var existIdx = CommitMessages.IndexOf(message); diff --git a/src/Models/ResetMode.cs b/src/Models/ResetMode.cs index 735533c2..827ccaa9 100644 --- a/src/Models/ResetMode.cs +++ b/src/Models/ResetMode.cs @@ -6,23 +6,25 @@ namespace SourceGit.Models { public static readonly ResetMode[] Supported = [ - new ResetMode("Soft", "Keep all changes. Stage differences", "--soft", Brushes.Green), - new ResetMode("Mixed", "Keep all changes. Unstage differences", "--mixed", Brushes.Orange), - new ResetMode("Merge", "Reset while keeping unmerged changes", "--merge", Brushes.Purple), - new ResetMode("Keep", "Reset while keeping local modifications", "--keep", Brushes.Purple), - new ResetMode("Hard", "Discard all changes", "--hard", Brushes.Red), + new ResetMode("Soft", "Keep all changes. Stage differences", "--soft", "S", Brushes.Green), + new ResetMode("Mixed", "Keep all changes. Unstage differences", "--mixed", "M", Brushes.Orange), + new ResetMode("Merge", "Reset while keeping unmerged changes", "--merge", "G", Brushes.Purple), + new ResetMode("Keep", "Reset while keeping local modifications", "--keep", "K", Brushes.Purple), + new ResetMode("Hard", "Discard all changes", "--hard", "H", Brushes.Red), ]; public string Name { get; set; } public string Desc { get; set; } public string Arg { get; set; } + public string Key { get; set; } public IBrush Color { get; set; } - public ResetMode(string n, string d, string a, IBrush b) + public ResetMode(string n, string d, string a, string k, IBrush b) { Name = n; Desc = d; Arg = a; + Key = k; Color = b; } } diff --git a/src/Models/Statistics.cs b/src/Models/Statistics.cs index b669eb55..969d3945 100644 --- a/src/Models/Statistics.cs +++ b/src/Models/Statistics.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using LiveChartsCore; using LiveChartsCore.Defaults; @@ -138,7 +139,8 @@ namespace SourceGit.Models public Statistics() { _today = DateTime.Now.ToLocalTime().Date; - _thisWeekStart = _today.AddSeconds(-(int)_today.DayOfWeek * 3600 * 24); + var weekOffset = (7 + (int)_today.DayOfWeek - (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek) % 7; + _thisWeekStart = _today.AddDays(-weekOffset); _thisMonthStart = _today.AddDays(1 - _today.Day); All = new StatisticsReport(StaticsticsMode.All, DateTime.MinValue); diff --git a/src/Models/Tag.cs b/src/Models/Tag.cs index 2ec9e093..2e8f2c8e 100644 --- a/src/Models/Tag.cs +++ b/src/Models/Tag.cs @@ -1,10 +1,19 @@ -namespace SourceGit.Models +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.Models { - public class Tag + public class Tag : ObservableObject { - public string Name { get; set; } - public string SHA { get; set; } - public string Message { get; set; } - public bool IsFiltered { get; set; } + public string Name { get; set; } = string.Empty; + public string SHA { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + + public FilterMode FilterMode + { + get => _filterMode; + set => SetProperty(ref _filterMode, value); + } + + private FilterMode _filterMode = FilterMode.None; } } diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index 6665250c..710b307d 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -113,22 +113,11 @@ namespace SourceGit.Models if (_updateTags > 0) { _updateTags = 0; - Task.Run(() => - { - _repo.RefreshTags(); - _repo.RefreshBranches(); - _repo.RefreshCommits(); - }); - } - else - { - Task.Run(() => - { - _repo.RefreshBranches(); - _repo.RefreshCommits(); - }); + Task.Run(_repo.RefreshTags); } + Task.Run(_repo.RefreshBranches); + Task.Run(_repo.RefreshCommits); Task.Run(_repo.RefreshWorkingCopyChanges); Task.Run(_repo.RefreshWorktrees); } diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index d1f43695..200d3638 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -58,7 +58,7 @@ Lösche alle ausgewählten {0} Branches Alle Änderungen verwerfen Fast-Forward zu ${0}$ - Fetche ${0}$ nach ${1}$... + Fetche ${0}$ in ${1}$ hinein... Git Flow - Abschließen ${0}$ Merge ${0}$ in ${1}$ hinein... Pull ${0}$ @@ -73,7 +73,7 @@ ABBRECHEN Auf diese Revision zurücksetzen Auf Vorgänger-Revision zurücksetzen - Generiere Commit-Nachricht + Generiere Commit-Nachricht ANZEIGE MODUS ÄNDERN Zeige als Datei- und Ordnerliste Zeige als Pfadliste @@ -162,8 +162,8 @@ TICKETSYSTEM Beispiel für Github-Regel hinzufügen Beispiel für Jira-Regel hinzufügen - Beispiel für eine Gitlab Issue Regel einfügen - Beispiel für einen Gitlab Merge Request einfügen + Beispiel für Gitlab Issue Regel einfügen + Beispiel für Gitlab Merge Request einfügen Neue Regel Ticketnummer Regex-Ausdruck: Name: @@ -171,7 +171,7 @@ Verwende bitte $1, $2 um auf Regex-Gruppenwerte zuzugreifen. OPEN AI Bevorzugter Service: - Wenn der 'Bevorzugte Service' aktiviert ist, wird SourceGit nur dieses Repository nutzen. Ansonsten wird, wenn mehrere Services verfügbar sind, eine Kontextmenü zur Auswahl angezeigt. + Der ausgewählte 'Bevorzugte Service' wird nur in diesem Repository gesetzt und verwendet. Wenn keiner gesetzt ist und mehrere Servies verfügbar sind wird ein Kontextmenü zur Auswahl angezeigt. HTTP Proxy HTTP Proxy für dieses Repository Benutzername @@ -240,6 +240,7 @@ Nächste Änderung KEINE ÄNDERUNG ODER NUR ZEILEN-ENDE ÄNDERUNGEN Vorherige Änderung + Als Patch speichern Zeige versteckte Symbole Nebeneinander SUBMODUL @@ -248,6 +249,7 @@ Syntax Hervorhebung Zeilenumbruch Öffne in Merge Tool + Alle Zeilen anzeigen Weniger Zeilen anzeigen Mehr Zeilen anzeigen WÄHLE EINE DATEI AUS UM ÄNDERUNGEN ANZUZEIGEN @@ -263,7 +265,7 @@ Ziel: Ausgewählte Gruppe bearbeiten Ausgewähltes Repository bearbeiten - Führe benutzerte Aktion aus + Führe benutzerdefinierte Aktion aus Name der Aktion: Fast-Forward (ohne Auschecken) Fetch @@ -290,7 +292,6 @@ Datei Historie INHALT ÄNDERUNGEN - FILTER Git-Flow Development-Branch: Feature: @@ -364,8 +365,12 @@ Gestagte Änderungen committen Gestagte Änderungen committen und pushen Alle Änderungen stagen und committen + Neuen Branch basierend auf ausgewählten Commit erstellen Ausgewählte Änderungen verwerfen + Fetch, wird direkt ausgeführt Dashboard Modus (Standard) + Pull, wird direkt ausgeführt + Push, wird direkt ausgeführt Erzwinge Neuladen des Repositorys Ausgewählte Änderungen stagen/unstagen Commit-Suchmodus @@ -389,6 +394,8 @@ Interaktiver Rebase Ziel Branch: Auf: + In Browser öffnen + Link kopieren FEHLER INFO Branch mergen @@ -429,7 +436,9 @@ Modell DARSTELLUNG Standardschriftart - Standardschriftgröße + Schriftgröße + Standard + Texteditor Monospace-Schriftart Verwende die Monospace-Schriftart nur im Texteditor Design @@ -526,7 +535,7 @@ Änderungen automatisch von Remote fetchen... Aufräumen (GC & Prune) Führt `git gc` auf diesem Repository aus. - Alles löschen + Filter aufheben Repository Einstellungen WEITER Benutzerdefinierte Aktionen @@ -534,7 +543,9 @@ Aktiviere '--reflog' Option Öffne im Datei-Browser Suche Branches/Tags/Submodule - GEFILTERT: + Aufheben + Im Graph ausblenden + Im Graph filtern LOKALE BRANCHES Zum HEAD wechseln Aktiviere '--first-parent' Option @@ -593,7 +604,7 @@ START Stash Inklusive nicht-verfolgter Dateien - Behalte Dateien des Stages + Behalte gestagte Dateien Name: Optional. Name dieses Stashes Nur gestagte Änderungen diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 3803bd39..55fb04d8 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -289,7 +289,6 @@ File History CONTENT CHANGE - FILTER Git-Flow Development Branch: Feature: @@ -434,8 +433,9 @@ Server APPEARANCE Default Font - Default Font Size - Editor Font Size + Font Size + Default + Editor Monospace Font Only use monospace font in text editor Theme @@ -540,7 +540,9 @@ Enable '--reflog' Option Open in File Browser Search Branches/Tags/Submodules - FILTERED BY: + Unset + Hide in commit graph + Filter in commit graph LOCAL BRANCHES Navigate to HEAD Enable '--first-parent' Option diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 6bc96dd1..f69fc934 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -294,7 +294,6 @@ Historial de Archivos CONTENIDO CAMBIO - FILTRO Git-Flow Rama de Desarrollo: Feature: @@ -438,8 +437,6 @@ Servidor APARIENCIA Fuente por defecto - Tamaño de fuente por defecto - Tamaño de fuente del editor Fuente Monospace Usar solo fuente monospace en el editor de texto Tema @@ -544,7 +541,6 @@ 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' diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 043e8f97..75cd4d58 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -3,23 +3,26 @@ À propos + À propos de SourceGit • Compilé avec + • Le graphique est rendu par © 2024 sourcegit-scm • TextEditor de • Les polices Monospace proviennent de - À propos de SourceGit • Le code source est disponible sur Client Git Open Source et Gratuit Ajouter un Worktree + What to Checkout: + Créer une nouvelle branche + Branche existante Emplacement : Chemin vers ce worktree. Relatif supporté. Nom de branche: Optionnel. Nom du dossier de destination par défaut. Suivre la branche : Suivi de la branche distante - What to Checkout: - Créer une nouvelle branche - Branche existante + Assistant IA + Utiliser l'IA pour générer un message de commit Appliquer Erreur Soulever les erreurs et refuser d'appliquer le patch @@ -55,6 +58,7 @@ Supprimer {0} branches sélectionnées Rejeter tous les changements Fast-Forward vers ${0}$ + Fetch ${0}$ vers ${1}$... Git Flow - Terminer ${0}$ Fusionner ${0}$ dans ${1}$... Tirer ${0}$ @@ -69,6 +73,7 @@ ANNULER Réinitialiser à la révision parente Réinitialiser à cette révision + Générer un message de commit CHANGER LE MODE D'AFFICHAGE Afficher comme liste de dossiers/fichiers Afficher comme liste de chemins @@ -77,24 +82,25 @@ Checkout ce commit Commit : Avertissement: un checkout vers un commit aboutiera vers un HEAD détaché + Branche : Changements locaux : Annuler Ne rien faire Mettre en stash et réappliquer - Branche : Cherry-Pick de ce commit Commit : Commit tous les changements + Ligne principale : Cherry Pick Supprimer les stashes Vous essayez de supprimer tous les stashes. Êtes-vous sûr de vouloir continuer ? + Cloner repository distant Paramètres supplémentaires : Arguments additionnels au clônage. Optionnel. Nom local : Nom de dépôt. Optionnel. Dossier parent : URL du dépôt : - Cloner le dépôt distant FERMER Éditeur Changer de commit @@ -103,6 +109,7 @@ Comparer avec le worktree Copier les informations Copier le SHA + Action personnalisée Rebase interactif de ${0}$ ici Rebaser ${0}$ ici Réinitialiser ${0}$ ici @@ -110,6 +117,7 @@ Reformuler Enregistrer en tant que patch... Squash dans le parent + Squash les commits enfants ici CHANGEMENTS Rechercher les changements... FICHIERS @@ -126,29 +134,56 @@ PARENTS REFS SHA - Description + Ouvrir dans le navigateur Entrez le message du commit + Description Configurer le dépôt MODÈLE DE COMMIT - Contenu de modèle: Nom de modèle: + Contenu de modèle: + ACTION PERSONNALISÉE + Arguments : + ${REPO} - Chemin du repository; ${SHA} - SHA du commit sélectionné + Fichier exécutable : + Nom : + Portée : + Commit + Repository Adresse e-mail Adresse e-mail GIT Fetch les dépôts distants automatiquement minute(s) + Dépôt par défaut + Activer --prune pour fetch + Activer --signoff pour commit SUIVI DES PROBLÈMES Ajouter une règle d'exemple Github Ajouter une règle d'exemple Jira + Ajouter une règle d'exemple pour Incidents GitLab + Ajouter une règle d'exemple pour Merge Request GitLab Nouvelle règle Issue Regex Expression: Nom de règle : - Veuillez utiliser $1, $2 pour accéder aux valeurs des groupes regex. URL résultant: + Veuillez utiliser $1, $2 pour accéder aux valeurs des groupes regex. + IA + Service préféré: + Si le 'Service préféré' est défini, SourceGit l'utilisera seulement dans ce repository. Sinon, si plus d'un service est disponible, un menu contextuel permettant de choisir l'un d'eux sera affiché. Proxy HTTP Proxy HTTP utilisé par ce dépôt Nom d'utilisateur Nom d'utilisateur pour ce dépôt + Espaces de travail + Couleur + Restaurer les onglets au démarrage + Assistant Commits Conventionnels + Changement Radical : + Incident Clos : + Détail des Modifications : + Portée : + Courte Description : + Type de Changement : Copier Copier tout le texte Copier le nom de fichier @@ -198,10 +233,12 @@ ANCIEN Copier Mode de fichier changé + Ignorer les changements d'espaces CHANGEMENT D'OBJET LFS Différence suivante PAS DE CHANGEMENT OU SEULEMENT EN FIN DE LIGNE Différence précédente + Enregistrer en tant que patch Afficher les caractères invisibles Diff côte-à-côte SOUS-MODULE @@ -210,6 +247,7 @@ Coloration syntaxique Retour à la ligne Ouvrir dans l'outil de fusion + Voir toutes les lignes Réduit le nombre de ligne visibles Augmente le nombre de ligne visibles SÉLECTIONNEZ UN FICHIER POUR VOIR LES CHANGEMENTS @@ -217,6 +255,7 @@ Rejeter les changements Tous les changements dans la copie de travail. Changements : + Inclure les fichiers ignorés {0} changements seront rejetés Vous ne pouvez pas annuler cette action !!! Signet : @@ -224,6 +263,8 @@ Cible : Éditer le groupe sélectionné Éditer le dépôt sélectionné + Lancer action personnalisée + Nom de l'action : Fast-Forward (sans checkout) Fetch Fetch toutes les branches distantes @@ -248,63 +289,68 @@ Utiliser les leurs (checkout --theirs) Historique du fichier CONTENU - FILTRER + MODIFICATION Git-Flow - Development Branch: + Branche de développement : Feature: Feature Prefix: - FLOW - Finish Feature - FLOW - Finish Hotfix - FLOW - Finish Release - Target: + FLOW - Terminer Feature + FLOW - Terminer Hotfix + FLOW - Terminer Release + Cible: Hotfix: Hotfix Prefix: - Initialize Git-Flow - Keep branch - Production Branch: - Release: - Release Prefix: - Start Feature... - FLOW - Start Feature - Start Hotfix... - FLOW - Start Hotfix - Enter name - Start Release... - FLOW - Start Release - Version Tag Prefix: + Initialiser Git-Flow + Garder la branche + Branche de production : + Release : + Release Prefix : + Commencer Feature... + FLOW - Commencer Feature + Commencer Hotfix... + FLOW - Commencer Hotfix + Saisir le nom + Commencer Release... + FLOW - Commencer Release + Préfixe Tag de Version : Git LFS - Add Track Pattern... - Pattern is file name - Custom Pattern: - Add Track Pattern to Git LFS + Ajouter un pattern de suivi... + Le pattern est un nom de fichier + Pattern personnalisé : + Ajouter un pattern de suivi à Git LFS Fetch - Fetch LFS Objects - Run `git lfs fetch` to download Git LFS objects. This does not update the working copy. - Install Git LFS hooks - Show Locks - No Locked Files - Lock - LFS Locks - Unlock - Force Unlock + Fetch les objets LFS + Lancer `git lfs fetch` pour télécharger les objets Git LFS. Cela ne met pas à jour la copie de travail. + Installer les hooks Git LFS + Afficher les verrous + Pas de fichiers verrouillés + Verrouiller + Afficher seulement mes verrous + Verrous LFS + Déverouiller + Forcer le déverouillage Prune - Run `git lfs prune` to delete old LFS files from local storage + Lancer `git lfs prune` pour supprimer les anciens fichier LFS du stockage local Pull - Pull LFS Objects - Run `git lfs pull` to download all Git LFS files for current ref & checkout + Pull les objets LFS + Lancer `git lfs pull` pour télécharger tous les fichier Git LFS de la référence actuelle & checkout Push - Push LFS Objects - Push queued large files to the Git LFS endpoint - Remote: - Track files named '{0}' - Track all *{0} files + Push les objets LFS + Transférer les fichiers volumineux en file d'attente vers le point de terminaison Git LFS + Dépôt : + Suivre les fichiers appelés '{0}' + Suivre tous les fichiers *{0} Historique Basculer entre dispositions Horizontal/Vertical AUTEUR + HEURE DE L'AUTEUR GRAPHE & SUJET SHA HEURE DE COMMIT {0} COMMITS SÉLECTIONNÉS + Maintenir 'Ctrl' ou 'Shift' enfoncée pour sélectionner plusieurs commits. + Maintenir ⌘ ou ⇧ enfoncée pour sélectionner plusieurs commits. + CONSEILS: Référence des raccourcis clavier GLOBAL Annuler le popup en cours @@ -316,7 +362,13 @@ DÉPÔT Commit les changements de l'index Commit et pousser les changements de l'index + Ajouter tous les changements et commit + Créer une nouvelle branche basée sur le commit actuel + Rejeter les changements sélectionnés + Fetch, démarre directement Mode tableau de bord (Défaut) + Pull, démarre directement + Push, démarre directement Forcer le rechargement du dépôt Ajouter/Retirer les changements sélectionnés de l'index Recherche de commit @@ -331,26 +383,30 @@ Stage Retirer de l'index Rejeter - Initialize Repository - Path: - Cherry-Pick in progress. Press 'Abort' to restore original HEAD. - Merge request in progress. Press 'Abort' to restore original HEAD. - Rebase in progress. Press 'Abort' to restore original HEAD. - Revert in progress. Press 'Abort' to restore original HEAD. - Interactive Rebase - Target Branch: - On: - ERROR + Initialiser le repository + Chemin : + Cherry-Pick en cours. Appuyer sur 'Abort' pour restaurer le HEAD d'origine. + Merge request in progress. Appuyer sur 'Abort' pour restaurer le HEAD d'origine. + Rebase in progress. Appuyer sur 'Abort' pour restaurer le HEAD d'origine. + Revert in progress. Appuyer sur 'Abort' pour restaurer le HEAD d'origine. + Rebase interactif + Branche cible : + Sur : + Ouvrir dans le navigateur + Copier le lien + ERREUR NOTICE - Merge Branch - Into: - Merge Option: - Source Branch: + Merger la branche + Dans : + Option de merge: + Branche source : + Déplacer le noeud du repository + Sélectionnier le noeud parent pour : Nom : Git n'a PAS été configuré. Veuillez d'abord le faire dans le menu Préférence. Ouvrir le dossier AppData - Open With... - Optional. + Ouvrir avec... + Optionnel. Créer un nouvel onglet Bookmark Fermer l'onglet @@ -369,9 +425,17 @@ L'an dernier il y a {0} ans Préférences + IA + Analyser Diff Prompt + Clé d'API + Générer le sujet de Prompt + Modèle + Nom + Serveur APPARENCE Police par défaut Taille de police par défaut + Taille de police de l'éditeur Police monospace N'utiliser que des polices monospace pour l'éditeur de texte Thème @@ -386,6 +450,7 @@ Vérifier les mises à jour au démarrage Language Historique de commits + Afficher l'heure de l'auteur au lieu de l'heure de validation dans le graphique Guide de longueur du sujet GIT Activer auto CRLF @@ -405,6 +470,10 @@ Saisir le chemin d'installation vers le programme GPG Clé de signature de l'utilisateur Clé de signature GPG de l'utilisateur + INTEGRATION + SHELL/TERMINAL + Shell/Terminal + Chemin Élaguer une branche distant Cible : Élaguer les Worktrees @@ -418,87 +487,90 @@ Ne rien faire Stash & Réappliquer Fetch sans les tags - Distant : + Dépôt distant : Pull (Fetch & Merge) - Use rebase instead of merge + Utiliser rebase au lieu de merge Push - Make sure submodules have been pushed + Assurez-vous que les submodules ont été poussés Force push - Local Branch: - Remote: - Push Changes To Remote - Remote Branch: - Set as tracking branch - Push all tags + Branche locale : + Dépôt distant : + Pousser les changements vers le dépôt distant + Branche distante : + Définir comme branche de suivi + Push tous les tags Push Tag To Remote - Push to all remotes - Remote: - Tag: - Quit - Rebase Current Branch - Stash & reapply local changes - On: - Rebase: - Refresh - Add Remote - Edit Remote - Name: - Remote name - Repository URL: - Remote git repository URL - Copy URL - Delete... - Edit... + Push tous les dépôts distants + Dépôt distant : + Tag : + Quitter + Rebase la branche actuelle + Stash & réappliquer changements locaux + Sur : + Rebase : + Rafraîchir + Ajouter dépôt distant + Modifier dépôt distant + Nom : + Nom du dépôt distant + URL du repository : + URL du dépôt distant + Copier l'URL + Supprimer... + Editer... Fetch - Open In Browser + Ouvrir dans le navigateur Prune - Confirm to Remove Worktree - Enable `--force` Option - Target: - Rename Branch - New Name: - Unique name for this branch - Branch: + Confirmer la suppression du Worktree + Activer l'option `--force` + Cible : + la branche + Nouveau nom : + Nom unique pour cette branche + Branche : ABORT - Cleanup(GC & Prune) - Run `git gc` command for this repository. - Clear all - Configure this repository - CONTINUE - Ouvrir dans l'explorateur Windows - Search Branches/Tags/Submodules - FILTERED BY: - LOCAL BRANCHES - Navigate To HEAD - Enable '--first-parent' Option - Create Branch - Open In {0} - Open In External Tools - Refresh - REMOTES - ADD REMOTE - RESOLVE - Search Commit - File + Fetch automatique des changements depuis les dépôts... + Nettoyage(GC & Prune) + Lancer `git gc` pour ce repository. + Tout effacer + Configurer ce repository + CONTINUER + Pas d'actions personnalisées + Activer l'option '--reflog' + Ouvrir dans l'explorateur de fichiers + Rechercher Branches/Tags/Submodules + BRANCHES LOCALES + Naviguer vers le HEAD + Activer l'option '--first-parent' + Créer une branche + Ouvrir dans {0} + Ouvrir dans un outil externe + Rafraîchir + DEPOTS DISTANTS + AJOUTER DEPOT DISTANT + RESOUDRE + Rechercher un commit + Fichier Message SHA - Author & Committer - Show Tags as Tree - Statistics + Auteur & Committer + Branche actuelle + Voir les Tags en tant qu'arbre + Statistiques SUBMODULES - ADD SUBMODULE - UPDATE SUBMODULE + AJOUTER SUBMODULE + METTRE A JOUR SUBMODULE TAGS - NEW TAG + NOUVEAU TAG Ouvrir dans un terminal WORKTREES - ADD WORKTREE + AJOUTER WORKTREE PRUNE - Git Repository URL - Reset Current Branch To Revision + URL du repository Git + Reset branche actuelle à la révision Reset Mode: - Move To: - Current Branch: + Déplacer vers : + Branche actuelle : Ouvrir dans l'explorateur de fichier Revert le Commit Commit : @@ -509,7 +581,8 @@ SAUVEGARDER Sauvegarder en tant que... Le patch a été sauvegardé ! - Vérifier les mises à jour... + Dossier racine : + Rechercher des mises à jour... Une nouvelle version du logiciel est disponible : La vérification de mise à jour à échouée ! Télécharger @@ -517,13 +590,17 @@ Mise à jour du logiciel Il n'y a pas de mise à jour pour le moment. Squash Commits + Dans : SSH Private Key: Private SSH key store path START Stash Include untracked files - Message: + Garder les fichiers staged + Message : Optionnel. Nom de ce stash + Seulement les changements staged + Les modifications staged et unstaged des fichiers sélectionnés seront stockées!!! Stash les changements locaux Appliquer Effacer @@ -540,6 +617,7 @@ WEEK COMMITS: AUTHORS: + APERCU SUBMODULES Add Submodule Copy Relative Path @@ -550,6 +628,7 @@ Delete Submodule OK Copy Tag Name + Copier le message du tag Delete ${0}$... Fusionner ${0}$ dans ${1}$... Push ${0}$... @@ -568,9 +647,11 @@ Supprimer GLISSER / DEPOSER DE DOSSIER SUPPORTÉ. GROUPAGE PERSONNALISÉ SUPPORTÉ. Éditer + Déplacer vers un autre groupe Ouvrir tous les dépôts Ouvrir un dépôt Ouvrir le terminal + Réanalyser les repositories dans le dossier de clonage par défaut Chercher des dépôts... Trier Changements @@ -584,6 +665,9 @@ COMMIT COMMIT & PUSH Modèles/Historiques + Trigger click event + Stage tous les changements et commit + Un commit vide a été détecté ! Voulez-vous continuer (--allow-empty) ? CONFLITS DÉTECTÉS LES CONFLITS DE FICHIER SONT RÉSOLUS INCLURE LES FICHIERS NON-SUIVIS @@ -598,6 +682,8 @@ VOIR LES FICHIERS PRÉSUMÉS INCHANGÉS Modèle: ${0}$ Faites un clique droit sur les fichiers sélectionnés et faites vos choix pour la résoluion des conflits. + ESPACE DE TRAVAIL : + Configurer les espaces de travail... WORKTREE Copier le chemin Verrouiller diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 61ded334..a4f20310 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -176,7 +176,6 @@ Repositório Endereço de email Endereço de email - Endereço de Email Buscar remotos automaticamente Minuto(s) Remoto padrão @@ -317,7 +316,6 @@ Histórico de Arquivos CONTEUDO MUDANÇA - FILTRO Branch de Desenvolvimento: Feature: Prefixo da Feature: @@ -460,8 +458,6 @@ Servidor INTELIGÊNCIA ARTIFICIAL Fonte Padrão - Tamanho da fonte padrão - Tamanho da fonte do editor Fonte Monoespaçada Usar fonte monoespaçada apenas no editor de texto Tema @@ -568,7 +564,6 @@ Habilitar opção '--reflog' Abrir no Navegador de Arquivos Pesquisar Branches/Tags/Submódulos - FILTRADO POR: Habilitar opção '--first-parent' BRANCHES LOCAIS Navegar para HEAD diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 3ea6a922..3d03e850 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -62,7 +62,7 @@ Поток Git - Завершение ${0}$ Слить ${0}$ в ${1}$... Забрать ${0}$ - Перетащить ${0}$ в ${1}$... + Забрать ${0}$ в ${1}$... Выложить ${0}$ Переместить ${0}$ на ${1}$... Переименовать ${0}$... @@ -213,7 +213,7 @@ Вид: Аннотированный Лёгкий - Удерживайте Ctrl, чтобы начать непосредственно + Удерживайте Ctrl, чтобы начать сразу Вырезать Удалить ветку Ветка: @@ -293,7 +293,6 @@ История файлов СОДЕРЖИМОЕ ИЗМЕНИТЬ - ФИЛЬТР Git-поток Ветка разработчика: Свойство: @@ -369,11 +368,11 @@ Подготовить все изменения и зафиксировать Создать новую ветку на основе выбранной ветки Отклонить выбранные изменения - Извлечение, запускается непосредственно + Извлечение, запускается сразу Режим доски (по-умолчанию) Принудительно перезагрузить этот хранилище - Забрать, запускается непосредственно - Выложить, запускается непосредственно + Забрать, запускается сразу + Выложить, запускается сразу Подготовленные/Неподготовленные выбранные изменения Режим поиска фиксаций Переключить на «Изменения» @@ -438,8 +437,9 @@ Сервер ВИД Шрифт по-умолчанию - Размер шрифта по-умолчанию - Размер шрифта редактора + Размер шрифта + По-умолчанию + Редактор Моноширный шрифт В текстовом редакторе используется только моноширный шрифт Тема @@ -544,7 +544,10 @@ Разрешить опцию --reflog Открыть в файловом менеджере Поиск веток, меток и подмодулей - ОТФИЛЬТРОВАНО: + Не установлен (По-умолчанию) + Скрыть в графе фиксации + Фильтр в графе фиксации + ОТФИЛЬТРОВАНО: ЛОКАЛЬНЫЕ ВЕТКИ Навигация по заголовку Включить опцию --first-parent diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 3df54899..1ab661be 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -292,7 +292,6 @@ 文件历史 文件内容 文件变更 - 过滤 GIT工作流 开发分支 : 特性分支 : @@ -437,7 +436,9 @@ 服务地址 外观配置 缺省字体 - 默认字体大小 + 字体大小 + 默认 + 代码编辑器 代码字体大小 等宽字体 仅在文本编辑器中使用等宽字体 @@ -543,7 +544,9 @@ 启用 --reflog 选项 在文件浏览器中打开 快速查找分支/标签/子模块 - 过滤规则 : + 不指定 + 在提交列表中隐藏 + 使用其对提交列表过滤 本地分支 定位HEAD 启用 --first-parent 过滤选项 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 4dcb31aa..5452fef6 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -292,7 +292,6 @@ 檔案歷史 檔案内容 檔案變更 - 篩選 Git 工作流 開發分支: 功能分支: @@ -437,8 +436,9 @@ 產生提交訊息提示詞 外觀設定 預設字型 - 預設字型大小 - 程式碼字型大小 + 字型大小 + 預設 + 程式碼 等寬字型 僅在文字編輯器中使用等寬字型 佈景主題 @@ -543,7 +543,9 @@ 啟用 [--reflog] 選項 在檔案瀏覽器中開啟 快速搜尋分支/標籤/子模組 - 篩選規則: + 不指定 + 在提交清單中隱藏 + 使用其來篩選提交清單 本機分支 回到 HEAD 啟用 [--first-parent] 選項 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index 50f4d830..82970549 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -168,12 +168,13 @@ - + @@ -511,6 +512,12 @@ + + - - - - + + @@ -67,15 +71,10 @@ Foreground="{DynamicResource Brush.BadgeFG}" Background="{DynamicResource Brush.Badge}"/> - - + + diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index e96b2594..92c2b043 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -428,28 +428,6 @@ namespace SourceGit.Views } } - private void OnToggleFilterClicked(object sender, RoutedEventArgs e) - { - if (DataContext is ViewModels.Repository repo && - sender is ToggleButton toggle && - toggle.DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch } node) - { - bool filtered = toggle.IsChecked == true; - List filters = [branch.FullName]; - if (branch.IsLocal && !string.IsNullOrEmpty(branch.Upstream)) - { - filters.Add(branch.Upstream); - - node.IsFiltered = filtered; - UpdateUpstreamFilterState(repo.RemoteBranchTrees, branch.Upstream, filtered); - } - - repo.UpdateFilters(filters, filtered); - } - - e.Handled = true; - } - private void MakeRows(List rows, List nodes, int depth) { foreach (var node in nodes) @@ -477,23 +455,6 @@ namespace SourceGit.Views CollectBranchesInNode(outs, sub); } - private bool UpdateUpstreamFilterState(List collection, string upstream, bool isFiltered) - { - foreach (var node in collection) - { - if (node.Backend is Models.Branch b && b.FullName == upstream) - { - node.IsFiltered = isFiltered; - return true; - } - - if (node.Backend is Models.Remote r && upstream.StartsWith($"refs/remotes/{r.Name}/", StringComparison.Ordinal)) - return UpdateUpstreamFilterState(node.Children, upstream, isFiltered); - } - - return false; - } - private bool _disableSelectionChangingEvent = false; } } diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 76ea0227..d8b77a18 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -117,7 +117,28 @@ TextDecorations="Underline" Cursor="Hand" Margin="0,0,16,0" - PointerPressed="OnSHAPressed"/> + PointerEntered="OnSHAPointerEntered" + PointerPressed="OnSHAPressed"> + + + + + + + + + + + + + + + + + + diff --git a/src/Views/CommitBaseInfo.axaml.cs b/src/Views/CommitBaseInfo.axaml.cs index 228fbe8e..8f480745 100644 --- a/src/Views/CommitBaseInfo.axaml.cs +++ b/src/Views/CommitBaseInfo.axaml.cs @@ -1,3 +1,5 @@ +using System.Threading.Tasks; + using Avalonia; using Avalonia.Collections; using Avalonia.Controls; @@ -113,6 +115,29 @@ namespace SourceGit.Views e.Handled = true; } + private async void OnSHAPointerEntered(object sender, PointerEventArgs e) + { + if (DataContext is ViewModels.CommitDetail detail && sender is Control { DataContext: string sha } ctl) + { + var tooltip = ToolTip.GetTip(ctl); + if (tooltip is Models.Commit commit && commit.SHA == sha) + { + ToolTip.SetIsOpen(ctl, true); + } + else + { + var c = await Task.Run(() => detail.GetParent(sha)); + if (c != null) + { + ToolTip.SetTip(ctl, c); + ToolTip.SetIsOpen(ctl, ctl.IsPointerOver); + } + } + } + + e.Handled = true; + } + private void OnSHAPressed(object sender, PointerPressedEventArgs e) { if (DataContext is ViewModels.CommitDetail detail && sender is Control { DataContext: string sha }) diff --git a/src/Views/CommitRefsPresenter.cs b/src/Views/CommitRefsPresenter.cs index fc3233a5..e8a66da0 100644 --- a/src/Views/CommitRefsPresenter.cs +++ b/src/Views/CommitRefsPresenter.cs @@ -38,7 +38,7 @@ namespace SourceGit.Views } public static readonly StyledProperty BackgroundProperty = - AvaloniaProperty.Register(nameof(Background), null); + AvaloniaProperty.Register(nameof(Background), Brushes.Transparent); public IBrush Background { @@ -56,7 +56,7 @@ namespace SourceGit.Views } public static readonly StyledProperty UseGraphColorProperty = - AvaloniaProperty.Register(nameof(UseGraphColor), false); + AvaloniaProperty.Register(nameof(UseGraphColor)); public bool UseGraphColor { @@ -96,7 +96,6 @@ namespace SourceGit.Views var x = 1.0; foreach (var item in _items) { - var iconRect = new RoundedRect(new Rect(x, 0, 16, 16), new CornerRadius(2, 0, 0, 2)); var entireRect = new RoundedRect(new Rect(x, 0, item.Width, 16), new CornerRadius(2)); if (item.IsHead) diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 06525abd..e0627ad8 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -34,8 +34,24 @@ + + + + @@ -81,9 +94,7 @@ @@ -97,14 +108,14 @@ @@ -112,16 +123,14 @@ - diff --git a/src/Views/DiffView.axaml.cs b/src/Views/DiffView.axaml.cs index 860627d3..7184ec44 100644 --- a/src/Views/DiffView.axaml.cs +++ b/src/Views/DiffView.axaml.cs @@ -1,4 +1,6 @@ using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.VisualTree; namespace SourceGit.Views { @@ -8,5 +10,31 @@ namespace SourceGit.Views { InitializeComponent(); } + + private void OnGotoPrevChange(object _, RoutedEventArgs e) + { + var textDiff = this.FindDescendantOfType(); + if (textDiff == null) + return; + + textDiff.GotoPrevChange(); + if (textDiff is SingleSideTextDiffPresenter presenter) + presenter.ForceSyncScrollOffset(); + + e.Handled = true; + } + + private void OnGotoNextChange(object _, RoutedEventArgs e) + { + var textDiff = this.FindDescendantOfType(); + if (textDiff == null) + return; + + textDiff.GotoNextChange(); + if (textDiff is SingleSideTextDiffPresenter presenter) + presenter.ForceSyncScrollOffset(); + + e.Handled = true; + } } } diff --git a/src/Views/FilterModeSwitchButton.axaml b/src/Views/FilterModeSwitchButton.axaml new file mode 100644 index 00000000..0fbbbf8e --- /dev/null +++ b/src/Views/FilterModeSwitchButton.axaml @@ -0,0 +1,32 @@ + + + diff --git a/src/Views/FilterModeSwitchButton.axaml.cs b/src/Views/FilterModeSwitchButton.axaml.cs new file mode 100644 index 00000000..f68e9166 --- /dev/null +++ b/src/Views/FilterModeSwitchButton.axaml.cs @@ -0,0 +1,167 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace SourceGit.Views +{ + public partial class FilterModeSwitchButton : UserControl + { + public static readonly StyledProperty ModeProperty = + AvaloniaProperty.Register(nameof(Mode)); + + public Models.FilterMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + public static readonly StyledProperty IsNoneVisibleProperty = + AvaloniaProperty.Register(nameof(IsNoneVisible)); + + public bool IsNoneVisible + { + get => GetValue(IsNoneVisibleProperty); + set => SetValue(IsNoneVisibleProperty, value); + } + + public static readonly StyledProperty IsContextMenuOpeningProperty = + AvaloniaProperty.Register(nameof(IsContextMenuOpening)); + + public bool IsContextMenuOpening + { + get => GetValue(IsContextMenuOpeningProperty); + set => SetValue(IsContextMenuOpeningProperty, value); + } + + public FilterModeSwitchButton() + { + IsVisible = false; + InitializeComponent(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ModeProperty || + change.Property == IsNoneVisibleProperty || + change.Property == IsContextMenuOpeningProperty) + { + var visible = (Mode != Models.FilterMode.None || IsNoneVisible || IsContextMenuOpening); + SetCurrentValue(IsVisibleProperty, visible); + } + } + + private void OnChangeFilterModeButtonClicked(object sender, RoutedEventArgs e) + { + var repoView = this.FindAncestorOfType(); + if (repoView == null) + return; + + var repo = repoView.DataContext as ViewModels.Repository; + if (repo == null) + return; + + var button = sender as Button; + if (button == null) + return; + + var menu = new ContextMenu(); + var mode = Models.FilterMode.None; + if (DataContext is Models.Tag tag) + { + mode = tag.FilterMode; + + if (mode != Models.FilterMode.None) + { + var unset = new MenuItem(); + unset.Header = App.Text("Repository.FilterCommits.Default"); + unset.Click += (_, ev) => + { + repo.SetTagFilterMode(tag, Models.FilterMode.None); + ev.Handled = true; + }; + + menu.Items.Add(unset); + menu.Items.Add(new MenuItem() { Header = "-" }); + } + + var include = new MenuItem(); + include.Icon = App.CreateMenuIcon("Icons.Filter"); + include.Header = App.Text("Repository.FilterCommits.Include"); + include.IsEnabled = mode != Models.FilterMode.Included; + include.Click += (_, ev) => + { + repo.SetTagFilterMode(tag, Models.FilterMode.Included); + ev.Handled = true; + }; + + var exclude = new MenuItem(); + exclude.Icon = App.CreateMenuIcon("Icons.EyeClose"); + exclude.Header = App.Text("Repository.FilterCommits.Exclude"); + exclude.IsEnabled = mode != Models.FilterMode.Excluded; + exclude.Click += (_, ev) => + { + repo.SetTagFilterMode(tag, Models.FilterMode.Excluded); + ev.Handled = true; + }; + + menu.Items.Add(include); + menu.Items.Add(exclude); + } + else if (DataContext is ViewModels.BranchTreeNode node) + { + mode = node.FilterMode; + + if (mode != Models.FilterMode.None) + { + var unset = new MenuItem(); + unset.Header = App.Text("Repository.FilterCommits.Default"); + unset.Click += (_, ev) => + { + repo.SetBranchFilterMode(node, Models.FilterMode.None); + ev.Handled = true; + }; + + menu.Items.Add(unset); + menu.Items.Add(new MenuItem() { Header = "-" }); + } + + var include = new MenuItem(); + include.Icon = App.CreateMenuIcon("Icons.Filter"); + include.Header = App.Text("Repository.FilterCommits.Include"); + include.IsEnabled = mode != Models.FilterMode.Included; + include.Click += (_, ev) => + { + repo.SetBranchFilterMode(node, Models.FilterMode.Included); + ev.Handled = true; + }; + + var exclude = new MenuItem(); + exclude.Icon = App.CreateMenuIcon("Icons.EyeClose"); + exclude.Header = App.Text("Repository.FilterCommits.Exclude"); + exclude.IsEnabled = mode != Models.FilterMode.Excluded; + exclude.Click += (_, ev) => + { + repo.SetBranchFilterMode(node, Models.FilterMode.Excluded); + ev.Handled = true; + }; + + menu.Items.Add(include); + menu.Items.Add(exclude); + } + + if (mode == Models.FilterMode.None) + { + IsContextMenuOpening = true; + menu.Closed += (_, _) => IsContextMenuOpening = false; + } + + menu.Open(button); + e.Handled = true; + } + } +} + + diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs index 9f436346..7edcb295 100644 --- a/src/Views/Histories.axaml.cs +++ b/src/Views/Histories.axaml.cs @@ -192,35 +192,26 @@ namespace SourceGit.Views if (string.IsNullOrEmpty(subject)) return; - var offset = 0; var keywordMatch = REG_KEYWORD_FORMAT1().Match(subject); if (!keywordMatch.Success) keywordMatch = REG_KEYWORD_FORMAT2().Match(subject); - if (keywordMatch.Success) - { - var keyword = new Run(subject.Substring(0, keywordMatch.Length)); - keyword.FontWeight = FontWeight.Bold; - Inlines.Add(keyword); - - offset = keywordMatch.Length; - subject = subject.Substring(offset); - } - - var rules = IssueTrackerRules; - if (rules == null || rules.Count == 0) - { - Inlines.Add(new Run(subject)); - return; - } - + var rules = IssueTrackerRules ?? []; var matches = new List(); foreach (var rule in rules) rule.Matches(matches, subject); if (matches.Count == 0) { - Inlines.Add(new Run(subject)); + if (keywordMatch.Success) + { + Inlines.Add(new Run(subject.Substring(0, keywordMatch.Length)) { FontWeight = FontWeight.Bold }); + Inlines.Add(new Run(subject.Substring(keywordMatch.Length))); + } + else + { + Inlines.Add(new Run(subject)); + } return; } @@ -232,18 +223,44 @@ namespace SourceGit.Views foreach (var match in matches) { if (match.Start > pos) - inlines.Add(new Run(subject.Substring(pos, match.Start - pos))); + { + if (keywordMatch.Success && pos < keywordMatch.Length) + { + if (keywordMatch.Length < match.Start) + { + inlines.Add(new Run(subject.Substring(pos, keywordMatch.Length - pos)) { FontWeight = FontWeight.Bold }); + inlines.Add(new Run(subject.Substring(keywordMatch.Length, match.Start - keywordMatch.Length))); + } + else + { + inlines.Add(new Run(subject.Substring(pos, match.Start - pos)) { FontWeight = FontWeight.Bold }); + } + } + else + { + inlines.Add(new Run(subject.Substring(pos, match.Start - pos))); + } + } var link = new Run(subject.Substring(match.Start, match.Length)); link.Classes.Add("issue_link"); inlines.Add(link); pos = match.Start + match.Length; - match.Start += offset; // Because we use this index of whole subject to detect mouse event. } if (pos < subject.Length) - inlines.Add(new Run(subject.Substring(pos))); + { + if (keywordMatch.Success && pos < keywordMatch.Length) + { + inlines.Add(new Run(subject.Substring(pos, keywordMatch.Length - pos)) { FontWeight = FontWeight.Bold }); + inlines.Add(new Run(subject.Substring(keywordMatch.Length))); + } + else + { + inlines.Add(new Run(subject.Substring(pos))); + } + } Inlines.AddRange(inlines); } @@ -713,9 +730,12 @@ namespace SourceGit.Views private void OnCommitListDoubleTapped(object sender, TappedEventArgs e) { - if (DataContext is ViewModels.Histories histories && sender is ListBox { SelectedItems: { Count: 1 } selected }) + if (DataContext is ViewModels.Histories histories && sender is ListBox { SelectedItems: { Count: 1 } }) { - histories.DoubleTapped(selected[0] as Models.Commit); + var source = e.Source as Control; + var item = source.FindAncestorOfType(); + if (item is { DataContext: Models.Commit commit }) + histories.DoubleTapped(commit); } e.Handled = true; } diff --git a/src/Views/LauncherPage.axaml b/src/Views/LauncherPage.axaml index e75fd936..be2b339f 100644 --- a/src/Views/LauncherPage.axaml +++ b/src/Views/LauncherPage.axaml @@ -109,7 +109,7 @@ - + diff --git a/src/Views/Preference.axaml b/src/Views/Preference.axaml index 73be0f7c..9b84604a 100644 --- a/src/Views/Preference.axaml +++ b/src/Views/Preference.axaml @@ -121,7 +121,7 @@ - + - + + Value="{Binding DefaultFontSize, Mode=TwoWay}"> + + + + + + + + + + + + + + - - - - @@ -196,16 +205,16 @@ - - - - + @@ -557,14 +557,6 @@ diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index e2c7df8c..afc8ac5a 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -52,7 +52,7 @@ namespace SourceGit.Views var startDirectly = launcher.HasKeyModifier(KeyModifiers.Control); if (!startDirectly && OperatingSystem.IsMacOS()) startDirectly = launcher.HasKeyModifier(KeyModifiers.Meta); - + repo.Fetch(startDirectly); e.Handled = true; } @@ -66,7 +66,7 @@ namespace SourceGit.Views var startDirectly = launcher.HasKeyModifier(KeyModifiers.Control); if (!startDirectly && OperatingSystem.IsMacOS()) startDirectly = launcher.HasKeyModifier(KeyModifiers.Meta); - + repo.Pull(startDirectly); e.Handled = true; } @@ -80,7 +80,7 @@ namespace SourceGit.Views var startDirectly = launcher.HasKeyModifier(KeyModifiers.Control); if (!startDirectly && OperatingSystem.IsMacOS()) startDirectly = launcher.HasKeyModifier(KeyModifiers.Meta); - + repo.Push(startDirectly); e.Handled = true; } diff --git a/src/Views/Reset.axaml b/src/Views/Reset.axaml index c703ac1d..b3a18e49 100644 --- a/src/Views/Reset.axaml +++ b/src/Views/Reset.axaml @@ -37,16 +37,19 @@ Margin="0,0,8,0" Text="{DynamicResource Text.Reset.Mode}"/> + SelectedItem="{Binding SelectedMode, Mode=TwoWay}" + KeyDown="OnResetModeKeyDown"> - - + + + diff --git a/src/Views/Reset.axaml.cs b/src/Views/Reset.axaml.cs index cc4b9b58..8c380538 100644 --- a/src/Views/Reset.axaml.cs +++ b/src/Views/Reset.axaml.cs @@ -1,4 +1,6 @@ using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; namespace SourceGit.Views { @@ -8,5 +10,29 @@ namespace SourceGit.Views { InitializeComponent(); } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + + ResetMode.Focus(); + } + + private void OnResetModeKeyDown(object sender, KeyEventArgs e) + { + if (sender is ComboBox comboBox) + { + var key = e.Key.ToString(); + for (int i = 0; i < Models.ResetMode.Supported.Length; i++) + { + if (key.Equals(Models.ResetMode.Supported[i].Key, System.StringComparison.OrdinalIgnoreCase)) + { + comboBox.SelectedIndex = i; + e.Handled = true; + return; + } + } + } + } } } diff --git a/src/Views/TagsView.axaml b/src/Views/TagsView.axaml index a30f63de..5dedb661 100644 --- a/src/Views/TagsView.axaml +++ b/src/Views/TagsView.axaml @@ -12,6 +12,10 @@ + + @@ -43,15 +47,14 @@ Classes="primary" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" Margin="8,0,0,0"/> - - + + + + + + + + @@ -60,32 +63,28 @@ - + Margin="8,0,0,0" + TextTrimming="CharacterEllipsis"/> - + diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index 8d4168b2..c83cfd28 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -65,7 +65,7 @@ namespace SourceGit.Views } if (node.Tag != null) - CreateContent(new Thickness(0, 2, 0, 0), "Icons.Tag"); + CreateContent(new Thickness(0, 0, 0, 0), "Icons.Tag"); else if (node.IsExpanded) CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder.Open"); else @@ -247,23 +247,6 @@ namespace SourceGit.Views } } - private void OnToggleFilterClicked(object sender, RoutedEventArgs e) - { - if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo) - { - var target = null as Models.Tag; - if (toggle.DataContext is ViewModels.TagTreeNode node) - target = node.Tag; - else if (toggle.DataContext is Models.Tag tag) - target = tag; - - if (target != null) - repo.UpdateFilters([target.Name], toggle.IsChecked == true); - } - - e.Handled = true; - } - private void MakeTreeRows(List rows, List nodes) { foreach (var node in nodes) diff --git a/src/Views/TextDiffView.axaml b/src/Views/TextDiffView.axaml index 57427321..f2a5beaf 100644 --- a/src/Views/TextDiffView.axaml +++ b/src/Views/TextDiffView.axaml @@ -13,27 +13,39 @@ - + + + + + + + - + + + + + diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 913a6340..242f8c4c 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -45,6 +45,18 @@ namespace SourceGit.Views } } + public record TextDiffViewRange + { + public int StartIdx { get; set; } = 0; + public int EndIdx { get; set; } = 0; + + public TextDiffViewRange(int startIdx, int endIdx) + { + StartIdx = startIdx; + EndIdx = endIdx; + } + } + public class ThemedTextDiffPresenter : TextEditor { public class VerticalSeperatorMargin : AbstractMargin @@ -210,7 +222,6 @@ namespace SourceGit.Views if (presenter == null) return new Size(0, 0); - var maxLineNumber = presenter.GetMaxLineNumber(); var typeface = TextView.CreateTypeface(); var test = new FormattedText( $"-", @@ -465,6 +476,15 @@ namespace SourceGit.Views get => GetValue(SelectedChunkProperty); set => SetValue(SelectedChunkProperty, value); } + + public static readonly StyledProperty DisplayRangeProperty = + AvaloniaProperty.Register(nameof(DisplayRange), new TextDiffViewRange(0, 0)); + + public TextDiffViewRange DisplayRange + { + get => GetValue(DisplayRangeProperty); + set => SetValue(DisplayRangeProperty, value); + } protected override Type StyleKeyOverride => typeof(TextEditor); @@ -498,6 +518,78 @@ namespace SourceGit.Views { } + public void GotoPrevChange() + { + var firstLineIdx = DisplayRange.StartIdx; + if (firstLineIdx <= 1) + return; + + var lines = GetLines(); + var firstLineType = lines[firstLineIdx].Type; + var prevLineType = lines[firstLineIdx - 1].Type; + var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator; + var isChangePrevLine = prevLineType != Models.TextDiffLineType.Normal && prevLineType != Models.TextDiffLineType.Indicator; + if (isChangeFirstLine && isChangePrevLine) + { + for (var i = firstLineIdx - 2; i >= 0; i--) + { + var prevType = lines[i].Type; + if (prevType == Models.TextDiffLineType.Normal || prevType == Models.TextDiffLineType.Indicator) + { + ScrollToLine(i + 2); + return; + } + } + } + + var findChange = false; + for (var i = firstLineIdx - 1; i >= 0; i--) + { + var prevType = lines[i].Type; + if (prevType == Models.TextDiffLineType.Normal || prevType == Models.TextDiffLineType.Indicator) + { + if (findChange) + { + ScrollToLine(i + 2); + return; + } + } + else if (!findChange) + { + findChange = true; + } + } + } + + public void GotoNextChange() + { + var lines = GetLines(); + var lastLineIdx = DisplayRange.EndIdx; + if (lastLineIdx >= lines.Count - 1) + return; + + var lastLineType = lines[lastLineIdx].Type; + var findNormalLine = lastLineType == Models.TextDiffLineType.Normal || lastLineType == Models.TextDiffLineType.Indicator; + for (var idx = lastLineIdx + 1; idx < lines.Count; idx++) + { + var nextType = lines[idx].Type; + if (nextType == Models.TextDiffLineType.None || + nextType == Models.TextDiffLineType.Added || + nextType == Models.TextDiffLineType.Deleted) + { + if (findNormalLine) + { + ScrollToLine(idx + 1); + return; + } + } + else if (!findNormalLine) + { + findNormalLine = true; + } + } + } + public override void Render(DrawingContext context) { base.Render(context); @@ -524,6 +616,7 @@ namespace SourceGit.Views TextArea.TextView.PointerEntered += OnTextViewPointerChanged; TextArea.TextView.PointerMoved += OnTextViewPointerChanged; TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged; + TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged; UpdateTextMate(); } @@ -536,6 +629,7 @@ namespace SourceGit.Views TextArea.TextView.PointerEntered -= OnTextViewPointerChanged; TextArea.TextView.PointerMoved -= OnTextViewPointerChanged; TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged; + TextArea.TextView.VisualLinesChanged -= OnTextViewVisualLinesChanged; if (_textMate != null) { @@ -643,6 +737,34 @@ namespace SourceGit.Views } } + private void OnTextViewVisualLinesChanged(object sender, EventArgs e) + { + if (!TextArea.TextView.VisualLinesValid) + { + SetCurrentValue(DisplayRangeProperty, new TextDiffViewRange(0, 0)); + return; + } + + var lines = GetLines(); + var start = int.MaxValue; + var count = 0; + foreach (var line in TextArea.TextView.VisualLines) + { + if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted) + continue; + + var index = line.FirstDocumentLine.LineNumber - 1; + if (index >= lines.Count) + continue; + + count++; + if (start > index) + start = index; + } + + SetCurrentValue(DisplayRangeProperty, new TextDiffViewRange(start, start + count)); + } + protected void TrySetChunk(TextDiffViewChunk chunk) { var old = SelectedChunk; @@ -950,12 +1072,8 @@ namespace SourceGit.Views private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e) { - if (EnableChunkSelection && sender is ScrollViewer viewer) - { - var area = viewer.FindDescendantOfType