diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e97d0b1b6..b3dd9d5ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,11 +39,11 @@ jobs: apt-get install -y sudo sudo apt-get install -y curl wget git unzip zip libicu66 tzdata clang - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: true - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: 10.0.x - name: Configure arm64 packages @@ -75,7 +75,7 @@ jobs: rm -r publish/* mv "sourcegit.${{ matrix.runtime }}.tar" publish - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: sourcegit.${{ matrix.runtime }} path: publish/* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50e02dc95..acd7dcde1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,9 @@ name: Continuous Integration on: push: - branches: [develop] + branches: [develop, master] pull_request: - branches: [develop] + branches: [develop, master] workflow_dispatch: workflow_call: jobs: @@ -17,7 +17,7 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Output version string id: version run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index adbeab527..43d5edf91 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -1,9 +1,9 @@ name: Format Check on: push: - branches: [develop] + branches: [develop, master] pull_request: - branches: [develop] + branches: [develop, master] workflow_dispatch: workflow_call: @@ -13,12 +13,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: true - name: Set up .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: 10.0.x diff --git a/.github/workflows/localization-check.yml b/.github/workflows/localization-check.yml index c5970870b..84274977a 100644 --- a/.github/workflows/localization-check.yml +++ b/.github/workflows/localization-check.yml @@ -1,7 +1,7 @@ name: Localization Check on: push: - branches: [develop] + branches: [develop, master] paths: - 'src/Resources/Locales/**' workflow_dispatch: @@ -13,12 +13,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20.x' + node-version: '24.x' - name: Install dependencies run: npm install fs-extra@11.2.0 path@0.12.7 xml2js@0.6.2 diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index d203dd2e2..0845774fb 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -15,9 +15,9 @@ jobs: runtime: [win-x64, win-arm64] steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: sourcegit.${{ matrix.runtime }} path: build/SourceGit @@ -28,12 +28,12 @@ jobs: RUNTIME: ${{ matrix.runtime }} run: ./build/scripts/package.win.ps1 - name: Upload package artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: package.${{ matrix.runtime }} path: build/sourcegit_*.zip - name: Delete temp artifacts - uses: geekyeggo/delete-artifact@v5 + uses: geekyeggo/delete-artifact@v6 with: name: sourcegit.${{ matrix.runtime }} osx-app: @@ -44,9 +44,9 @@ jobs: runtime: [osx-x64, osx-arm64] steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: sourcegit.${{ matrix.runtime }} path: build @@ -59,12 +59,12 @@ jobs: tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit ./build/scripts/package.osx-app.sh - name: Upload package artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: package.${{ matrix.runtime }} path: build/sourcegit_*.zip - name: Delete temp artifacts - uses: geekyeggo/delete-artifact@v5 + uses: geekyeggo/delete-artifact@v6 with: name: sourcegit.${{ matrix.runtime }} linux: @@ -76,7 +76,7 @@ jobs: runtime: [linux-x64, linux-arm64] steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Download package dependencies run: | export DEBIAN_FRONTEND=noninteractive @@ -84,7 +84,7 @@ jobs: apt-get update apt-get install -y curl wget git dpkg-dev fakeroot tzdata zip unzip desktop-file-utils rpm libfuse2 file build-essential binutils - name: Download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: sourcegit.${{ matrix.runtime }} path: build @@ -98,7 +98,7 @@ jobs: tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit ./build/scripts/package.linux.sh - name: Upload package artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: package.${{ matrix.runtime }} path: | @@ -106,6 +106,6 @@ jobs: build/sourcegit_*.deb build/sourcegit-*.rpm - name: Delete temp artifacts - uses: geekyeggo/delete-artifact@v5 + uses: geekyeggo/delete-artifact@v6 with: name: sourcegit.${{ matrix.runtime }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e61e608b0..816870a02 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: contents: write steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Create release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -40,7 +40,7 @@ jobs: VERSION: ${{ needs.version.outputs.version }} run: gh release create "$TAG" -t "$VERSION" --notes-from-tag - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: package.* path: packages diff --git a/TRANSLATION.md b/TRANSLATION.md index 994f52a3e..fd1e3672a 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,11 +6,12 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-98.46%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-98.25%25-yellow)
Missing keys in de_DE.axaml +- Text.Apply.3Way - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -18,6 +19,7 @@ This document shows the translation status of each locale file in the repository - Text.CommitMessageTextBox.Column - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.GotoRevisionSelector +- Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Init.CommandTip - Text.Init.ErrorMessageTip @@ -29,21 +31,24 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-99.90%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-99.69%25-yellow)
Missing keys in es_ES.axaml +- Text.Apply.3Way +- Text.Hotkeys.Repo.CreateBranch - Text.Preferences.General.Use24Hours
-### ![fr__FR](https://img.shields.io/badge/fr__FR-92.28%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-92.09%25-yellow)
Missing keys in fr_FR.axaml - Text.About.ReleaseDate +- Text.Apply.3Way - Text.Blame.IgnoreWhitespace - Text.BranchCM.CompareTwo - Text.BranchCM.CompareWith @@ -70,6 +75,7 @@ This document shows the translation status of each locale file in the repository - Text.Histories.ShowColumns - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom +- Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent - Text.Init.CommandTip @@ -121,13 +127,14 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-90.22%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-90.03%25-yellow)
Missing keys in id_ID.axaml - Text.About.ReleaseDate - Text.About.ReleaseNotes +- Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.Blame.IgnoreWhitespace - Text.BranchCM.CompareTwo @@ -166,6 +173,7 @@ This document shows the translation status of each locale file in the repository - Text.Histories.ShowColumns - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom +- Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent - Text.Hotkeys.Repo.OpenCommandPalette @@ -224,11 +232,12 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-97.84%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-97.64%25-yellow)
Missing keys in it_IT.axaml +- Text.Apply.3Way - Text.ChangeCM.ResetFileTo - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags @@ -239,6 +248,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent - Text.Init.CommandTip @@ -253,16 +263,18 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-98.87%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-98.66%25-yellow)
Missing keys in ja_JP.axaml +- Text.Apply.3Way - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles - Text.ConfirmEmptyCommit.StageSelectedThenCommit +- Text.Hotkeys.Repo.CreateBranch - Text.Init.CommandTip - Text.Init.ErrorMessageTip - Text.Preferences.General.Use24Hours @@ -272,12 +284,13 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-90.53%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-90.34%25-yellow)
Missing keys in ko_KR.axaml - Text.About.ReleaseDate +- Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.Blame.IgnoreWhitespace - Text.Blame.TypeNotSupported @@ -312,6 +325,7 @@ This document shows the translation status of each locale file in the repository - Text.Histories.ShowColumns - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom +- Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent - Text.Hotkeys.Repo.OpenCommandPalette @@ -372,11 +386,12 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-68.49%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-68.35%25-red)
Missing keys in pt_BR.axaml +- Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.BranchCM.InteractiveRebase.Manually - Text.BranchTree.AheadBehind @@ -509,6 +524,7 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom +- Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent - Text.Hotkeys.Repo.OpenCommandPalette @@ -686,23 +702,9 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-99.18%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) -
-Missing keys in ru_RU.axaml - -- Text.CommandPalette.Branches -- Text.CommandPalette.BranchesAndTags -- Text.CommandPalette.RepositoryActions -- Text.CommandPalette.RevisionFiles -- Text.ConfirmEmptyCommit.StageSelectedThenCommit -- Text.Init.CommandTip -- Text.Init.ErrorMessageTip -- Text.Preferences.General.Use24Hours - -
- -### ![ta__IN](https://img.shields.io/badge/ta__IN-70.75%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-70.61%25-red)
Missing keys in ta_IN.axaml @@ -714,6 +716,7 @@ This document shows the translation status of each locale file in the repository - Text.AddToIgnore.Storage - Text.App.Hide - Text.App.ShowAll +- Text.Apply.3Way - Text.Askpass.Passphrase - Text.Avatar.Load - Text.Bisect @@ -850,6 +853,7 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom +- Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent - Text.Hotkeys.Repo.OpenCommandPalette @@ -994,7 +998,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-71.58%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-71.43%25-red)
Missing keys in uk_UA.axaml @@ -1006,6 +1010,7 @@ This document shows the translation status of each locale file in the repository - Text.AddToIgnore.Storage - Text.App.Hide - Text.App.ShowAll +- Text.Apply.3Way - Text.Askpass.Passphrase - Text.Avatar.Load - Text.Bisect @@ -1138,6 +1143,7 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom +- Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent - Text.Hotkeys.Repo.OpenCommandPalette diff --git a/src/App.axaml.cs b/src/App.axaml.cs index a4c25193d..f9c6117cc 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -6,7 +6,6 @@ using System.Reflection; using System.Text; using System.Text.Json; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -286,6 +285,8 @@ public static void SetTheme(string theme, string themeOverridesFile) else Models.CommitGraph.SetDefaultPens(overrides.GraphPenThickness); + Native.OS.UseMicaOnWindows11 = overrides.UseMicaOnWindows11; + app.Resources.MergedDictionaries.Add(resDic); app._themeOverrides = resDic; } @@ -486,23 +487,7 @@ private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode) using var stream = File.OpenRead(jobsFile); var collection = JsonSerializer.Deserialize(stream, JsonCodeGen.Default.InteractiveRebaseJobCollection); - using var writer = new StreamWriter(file); - foreach (var job in collection.Jobs) - { - var code = job.Action switch - { - Models.InteractiveRebaseAction.Pick => 'p', - Models.InteractiveRebaseAction.Edit => 'e', - Models.InteractiveRebaseAction.Reword => 'r', - Models.InteractiveRebaseAction.Squash => 's', - Models.InteractiveRebaseAction.Fixup => 'f', - _ => 'd' - }; - writer.WriteLine($"{code} {job.SHA}"); - } - - writer.Flush(); - + collection.WriteTodoList(file); exitCode = 0; return true; } @@ -533,27 +518,8 @@ private static bool TryLaunchAsRebaseMessageEditor(string[] args, out int exitCo var onto = File.ReadAllText(ontoFile).Trim(); using var stream = File.OpenRead(jobsFile); var collection = JsonSerializer.Deserialize(stream, JsonCodeGen.Default.InteractiveRebaseJobCollection); - if (!collection.Onto.Equals(onto) || !collection.OrigHead.Equals(origHead)) - return true; - - var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); - if (done.Length == 0) - return true; - - var current = done[^1].Trim(); - var match = REG_REBASE_TODO().Match(current); - if (!match.Success) - return true; - - var sha = match.Groups[1].Value; - foreach (var job in collection.Jobs) - { - if (job.SHA.StartsWith(sha)) - { - File.WriteAllText(file, job.Message); - break; - } - } + if (collection.Onto.StartsWith(onto, StringComparison.OrdinalIgnoreCase) && collection.OrigHead.StartsWith(origHead, StringComparison.OrdinalIgnoreCase)) + collection.WriteCommitMessage(doneFile, file); return true; } @@ -804,9 +770,6 @@ private string FixFontFamilyName(string input) return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty; } - [GeneratedRegex(@"^[a-z]+\s+([a-fA-F0-9]{4,40})(\s+.*)?$")] - private static partial Regex REG_REBASE_TODO(); - private Models.IpcChannel _ipcChannel = null; private ViewModels.Launcher _launcher = null; private ResourceDictionary _activeLocale = null; diff --git a/src/Commands/Clone.cs b/src/Commands/Clone.cs index b0323528c..ffd11bda9 100644 --- a/src/Commands/Clone.cs +++ b/src/Commands/Clone.cs @@ -14,7 +14,7 @@ public Clone(string ctx, string path, string url, string localName, string sshKe builder.Append("clone --progress --verbose "); if (!string.IsNullOrEmpty(extraArgs)) builder.Append(extraArgs).Append(' '); - builder.Append(url).Append(' '); + builder.Append(url.Quoted()).Append(' '); if (!string.IsNullOrEmpty(localName)) builder.Append(localName.Quoted()); diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index 680aff63d..4d0cc72ac 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -12,7 +12,7 @@ public partial class Diff : Command [GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")] private static partial Regex REG_INDICATOR(); - [GeneratedRegex(@"^index\s([0-9a-f]{6,40})\.\.([0-9a-f]{6,40})(\s[1-9]{6})?")] + [GeneratedRegex(@"^index\s([0-9a-f]{6,64})\.\.([0-9a-f]{6,64})(\s[1-9]{6})?")] private static partial Regex REG_HASH_CHANGE(); private const string PREFIX_LFS_NEW = "+version https://git-lfs.github.com/spec/"; @@ -194,6 +194,7 @@ private void ParseLine(string line) return; } + _result.TextDiff.DeletedLines++; _last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0); _deleted.Add(_last); _oldLine++; @@ -207,6 +208,7 @@ private void ParseLine(string line) return; } + _result.TextDiff.AddedLines++; _last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine); _added.Add(_last); _newLine++; diff --git a/src/Commands/IsBinary.cs b/src/Commands/IsBinary.cs index 087e71c7b..9dbe05459 100644 --- a/src/Commands/IsBinary.cs +++ b/src/Commands/IsBinary.cs @@ -8,11 +8,11 @@ public partial class IsBinary : Command [GeneratedRegex(@"^\-\s+\-\s+.*$")] private static partial Regex REG_TEST(); - public IsBinary(string repo, string commit, string path) + public IsBinary(string repo, string revision, string path) { WorkingDirectory = repo; Context = repo; - Args = $"diff --no-color --no-ext-diff --numstat {Models.Commit.EmptyTreeSHA1} {commit} -- {path.Quoted()}"; + Args = $"diff --no-color --no-ext-diff --numstat {Models.EmptyTreeHash.Guess(revision)} {revision} -- {path.Quoted()}"; RaiseError = false; } diff --git a/src/Commands/QueryCommitChildren.cs b/src/Commands/QueryCommitChildren.cs index 6af0abb73..7e7e88877 100644 --- a/src/Commands/QueryCommitChildren.cs +++ b/src/Commands/QueryCommitChildren.cs @@ -24,7 +24,7 @@ public async Task> GetResultAsync() foreach (var line in lines) { if (line.Contains(_commit)) - outs.Add(line.Substring(0, 40)); + outs.Add(line.Substring(0, _commit.Length)); } } diff --git a/src/Commands/QueryStagedChangesWithAmend.cs b/src/Commands/QueryStagedChangesWithAmend.cs index cff939e2e..78109ce6b 100644 --- a/src/Commands/QueryStagedChangesWithAmend.cs +++ b/src/Commands/QueryStagedChangesWithAmend.cs @@ -6,9 +6,9 @@ namespace SourceGit.Commands { public partial class QueryStagedChangesWithAmend : Command { - [GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ADMT])\d{0,6}\t(.*)$")] + [GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{4,64}) [0-9a-f]{4,64} ([ADMT])\d{0,6}\t(.*)$")] private static partial Regex REG_FORMAT1(); - [GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([RC])\d{0,6}\t(.*\t.*)$")] + [GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{4,64}) [0-9a-f]{4,64} ([RC])\d{0,6}\t(.*\t.*)$")] private static partial Regex REG_FORMAT2(); public QueryStagedChangesWithAmend(string repo, string parent) diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index 60501ba4a..7f55e31f8 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -15,8 +15,6 @@ public enum CommitSearchMethod public class Commit { - public const string EmptyTreeSHA1 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; - public string SHA { get; set; } = string.Empty; public User Author { get; set; } = User.Invalid; public ulong AuthorTime { get; set; } = 0; @@ -33,6 +31,7 @@ public class Commit public bool IsCommitterVisible => !Author.Equals(Committer) || AuthorTime != CommitterTime; public bool IsCurrentHead => Decorators.Find(x => x.Type is DecoratorType.CurrentBranchHead or DecoratorType.CurrentCommitHead) != null; public bool HasDecorators => Decorators.Count > 0; + public string FirstParentToCompare => Parents.Count > 0 ? $"{SHA}^" : EmptyTreeHash.Guess(SHA); public string GetFriendlyName() { diff --git a/src/Models/DiffOption.cs b/src/Models/DiffOption.cs index 0dc8bc31f..def59bedd 100644 --- a/src/Models/DiffOption.cs +++ b/src/Models/DiffOption.cs @@ -60,8 +60,7 @@ public DiffOption(Change change, bool isUnstaged) /// public DiffOption(Commit commit, Change change) { - var baseRevision = commit.Parents.Count == 0 ? Commit.EmptyTreeSHA1 : $"{commit.SHA}^"; - _revisions.Add(baseRevision); + _revisions.Add(commit.FirstParentToCompare); _revisions.Add(commit.SHA); _path = change.Path; _orgPath = change.OriginalPath; @@ -74,8 +73,7 @@ public DiffOption(Commit commit, Change change) /// public DiffOption(Commit commit, string file) { - var baseRevision = commit.Parents.Count == 0 ? Commit.EmptyTreeSHA1 : $"{commit.SHA}^"; - _revisions.Add(baseRevision); + _revisions.Add(commit.FirstParentToCompare); _revisions.Add(commit.SHA); _path = file; } @@ -88,7 +86,7 @@ public DiffOption(FileVersion ver) { if (string.IsNullOrEmpty(ver.OriginalPath)) { - _revisions.Add(ver.HasParent ? $"{ver.SHA}^" : Commit.EmptyTreeSHA1); + _revisions.Add(ver.HasParent ? $"{ver.SHA}^" : EmptyTreeHash.Guess(ver.SHA)); _revisions.Add(ver.SHA); _path = ver.Path; } @@ -111,14 +109,14 @@ public DiffOption(FileVersion start, FileVersion end) { if (start.Change.Index == ChangeState.Deleted) { - _revisions.Add(Commit.EmptyTreeSHA1); + _revisions.Add(EmptyTreeHash.Guess(end.SHA)); _revisions.Add(end.SHA); _path = end.Path; } else if (end.Change.Index == ChangeState.Deleted) { _revisions.Add(start.SHA); - _revisions.Add(Commit.EmptyTreeSHA1); + _revisions.Add(EmptyTreeHash.Guess(start.SHA)); _path = start.Path; } else if (!end.Path.Equals(start.Path, StringComparison.Ordinal)) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index df8f204c0..32fff76ce 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -55,6 +55,8 @@ public partial class TextDiff { public List Lines { get; set; } = new List(); public int MaxLineNumber = 0; + public int AddedLines { get; set; } = 0; + public int DeletedLines { get; set; } = 0; public TextDiffSelection MakeSelection(int startLine, int endLine, bool isCombined, bool isOldSide) { diff --git a/src/Models/EmptyTreeHash.cs b/src/Models/EmptyTreeHash.cs new file mode 100644 index 000000000..bf1445a0f --- /dev/null +++ b/src/Models/EmptyTreeHash.cs @@ -0,0 +1,13 @@ +namespace SourceGit.Models +{ + public static class EmptyTreeHash + { + public static string Guess(string revision) + { + return revision.Length == 40 ? SHA1 : SHA256; + } + + private const string SHA1 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; + private const string SHA256 = "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321"; + } +} diff --git a/src/Models/InteractiveRebase.cs b/src/Models/InteractiveRebase.cs index bae99ac52..ac7e29d4f 100644 --- a/src/Models/InteractiveRebase.cs +++ b/src/Models/InteractiveRebase.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; namespace SourceGit.Models { @@ -34,10 +37,55 @@ public class InteractiveRebaseJob public string Message { get; set; } = string.Empty; } - public class InteractiveRebaseJobCollection + public partial class InteractiveRebaseJobCollection { public string OrigHead { get; set; } = string.Empty; public string Onto { get; set; } = string.Empty; public List Jobs { get; set; } = new List(); + + public void WriteTodoList(string todoFile) + { + using var writer = new StreamWriter(todoFile); + foreach (var job in Jobs) + { + var code = job.Action switch + { + InteractiveRebaseAction.Pick => 'p', + InteractiveRebaseAction.Edit => 'e', + InteractiveRebaseAction.Reword => 'r', + InteractiveRebaseAction.Squash => 's', + InteractiveRebaseAction.Fixup => 'f', + _ => 'd' + }; + writer.WriteLine($"{code} {job.SHA}"); + } + + writer.Flush(); + } + + public void WriteCommitMessage(string doneFile, string msgFile) + { + var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + if (done.Length == 0) + return; + + var current = done[^1].Trim(); + var match = REG_REBASE_TODO().Match(current); + if (!match.Success) + return; + + var sha = match.Groups[1].Value; + foreach (var job in Jobs) + { + if (job.SHA.StartsWith(sha)) + { + File.WriteAllText(msgFile, job.Message); + return; + } + } + } + + [GeneratedRegex(@"^[a-z]+\s+([a-fA-F0-9]{4,64})(\s+.*)?$")] + private static partial Regex REG_REBASE_TODO(); } } diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs index 1ef697052..18b57c414 100644 --- a/src/Models/Remote.cs +++ b/src/Models/Remote.cs @@ -64,14 +64,9 @@ public bool TryGetVisitURL(out string url) { url = null; - if (URL.StartsWith("http", StringComparison.Ordinal)) + if (URL.StartsWith("http://", StringComparison.Ordinal) || URL.StartsWith("https://", StringComparison.Ordinal)) { - var uri = new Uri(URL.EndsWith(".git", StringComparison.Ordinal) ? URL.Substring(0, URL.Length - 4) : URL); - if (uri.Port != 80 && uri.Port != 443) - url = $"{uri.Scheme}://{uri.Host}:{uri.Port}{uri.LocalPath}"; - else - url = $"{uri.Scheme}://{uri.Host}{uri.LocalPath}"; - + url = URL.EndsWith(".git", StringComparison.Ordinal) ? URL.Substring(0, URL.Length - 4) : URL; return true; } @@ -97,7 +92,6 @@ public bool TryGetCreatePullRequestURL(out string url, string mergeBranch) var uri = new Uri(baseURL); var host = uri.Host; - var route = uri.AbsolutePath.TrimStart('/'); var encodedBranch = HttpUtility.UrlEncode(mergeBranch); if (host.Contains("github.com", StringComparison.Ordinal)) diff --git a/src/Models/Stash.cs b/src/Models/Stash.cs index 4f5ad6922..93439a40a 100644 --- a/src/Models/Stash.cs +++ b/src/Models/Stash.cs @@ -10,5 +10,6 @@ public class Stash public ulong Time { get; set; } = 0; public string Message { get; set; } = ""; public string Subject => Message.Split('\n', 2)[0].Trim(); + public string UntrackedParent => EmptyTreeHash.Guess(SHA); } } diff --git a/src/Models/ThemeOverrides.cs b/src/Models/ThemeOverrides.cs index ccd9f57e8..531cbccdd 100644 --- a/src/Models/ThemeOverrides.cs +++ b/src/Models/ThemeOverrides.cs @@ -9,6 +9,7 @@ public class ThemeOverrides public Dictionary BasicColors { get; set; } = new Dictionary(); public double GraphPenThickness { get; set; } = 2; public double OpacityForNotMergedCommits { get; set; } = 0.5; + public bool UseMicaOnWindows11 { get; set; } = true; public List GraphColors { get; set; } = new List(); } } diff --git a/src/Native/OS.cs b/src/Native/OS.cs index 159656f67..1044887a2 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -107,6 +107,12 @@ public static string ExternalDiffArgs set; } = string.Empty; + public static bool UseMicaOnWindows11 + { + get => OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) && _enableMicaOnWindows11; + set => _enableMicaOnWindows11 = value; + } + public static bool UseSystemWindowFrame { get => OperatingSystem.IsLinux() && _enableSystemWindowFrame; @@ -294,5 +300,6 @@ private static void UpdateGitVersion() private static IBackend _backend = null; private static string _gitExecutable = string.Empty; private static bool _enableSystemWindowFrame = false; + private static bool _enableMicaOnWindows11 = true; } } diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 0866c1a0b..3514fbef6 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -24,6 +24,7 @@ Hide SourceGit Show All Patch + 3-Way Merge Patch File: Select .patch file to apply Ignore whitespace changes @@ -502,6 +503,7 @@ Commit staged changes Commit and push staged changes Stage all changes and commit + Create new branch Fetch, starts directly Dashboard mode (Default) Goto child of selected commit diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 0a566ac3b..629ac59dd 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -28,6 +28,7 @@ Скрыть SourceGit Показать все Исправить + Трехстороннее слияние Файл заплатки: Выберите файл .patch для применения Игнорировать изменения пробелов @@ -138,6 +139,10 @@ Адрес репозитория: ЗАКРЫТЬ Редактор + Ветки + Ветки и метки + Пользовательские действия репозитория + Ревизия файлов Переключиться на эту ревизию Применить эту ревизию (cherry-pick) Применить несколько ревизий ... @@ -281,6 +286,7 @@ ПРОДОЛЖИТЬ Обнаружена пустая ревизия! Вы хотите продолжить (--allow-empty)? Сформировать всё и зафиксировать ревизию + Сформировать выбранные и зафиксировать Обнаружена пустая ревизия! Вы хотите продолжить (--allow-empty) или отложить всё, а затем зафиксировать ревизию? Требуется перезапуск Вы должны перезапустить приложение после применения изменений. @@ -501,6 +507,7 @@ Зафиксировать сформированные изменения Зафиксировать и выложить сформированные изменения Сформировать все изменения и зафиксировать + Создать новую ветку Извлечь (fetch), запускается сразу Режим доски (по умолчанию) К дочернему выбранной ревизии @@ -523,6 +530,8 @@ Сформировать Расформировать Создать репозиторий + Вы действительно хотите запуститб команду «git init» по этому пути? + Не удалось открыть репозиторий. Причина: Путь: Выполняется частичный перенос ревизий (cherry-pick). Обрабтка ревизии. @@ -646,6 +655,7 @@ Показать наследника в деталях комментария Показывать метки на графике Длина темы ревизии + 24-часовой Создать Github-подобный аватар по умолчанию GIT Включить автозавершение CRLF diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 4dc0d07a4..3f1b8c55b 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -28,6 +28,7 @@ 隐藏 SourceGit 显示所有窗口 应用补丁(apply) + 尝试三路合并 补丁文件 : 选择补丁文件 忽略空白符号 @@ -506,6 +507,7 @@ 提交暂存区更改 提交暂存区更改并推送 自动暂存全部变更并提交 + 新建分支 拉取 (fetch) 远程变更 切换左边栏为分支/标签等显示模式(默认) 前往选中提交的子提交 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 8d3659b0d..fa4e3d2bd 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -28,6 +28,7 @@ 隱藏 SourceGit 顯示所有 套用修補檔 (apply patch) + 嘗試三路合併 修補檔: 選擇修補檔 忽略空白符號 @@ -506,6 +507,7 @@ 提交暫存區變更 提交暫存區變更並推送 自動暫存全部變更並提交 + 新增分支 提取 (fetch) 遠端的變更 切換左邊欄為分支/標籤等顯示模式 (預設) 前往所選提交的子提交 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index 3167a8c26..366325cdc 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -1472,7 +1472,7 @@ Name="PART_searchTextBox" Width="265" Height="28" Margin="0,0,0,0" - CornerRadius="2" + CornerRadius="3" Text="{Binding SearchPattern, RelativeSource={RelativeSource TemplatedParent}}"> diff --git a/src/ViewModels/Apply.cs b/src/ViewModels/Apply.cs index 3eab5ef7a..3578c12f2 100644 --- a/src/ViewModels/Apply.cs +++ b/src/ViewModels/Apply.cs @@ -26,6 +26,12 @@ public Models.ApplyWhiteSpaceMode SelectedWhiteSpaceMode set; } + public bool ThreeWayMerge + { + get; + set; + } + public Apply(Repository repo) { _repo = repo; @@ -49,7 +55,8 @@ public override async Task Sure() var log = _repo.CreateLog("Apply Patch"); Use(log); - var succ = await new Commands.Apply(_repo.FullPath, _patchFile, _ignoreWhiteSpace, SelectedWhiteSpaceMode.Arg, null) + var extra = ThreeWayMerge ? "--3way" : string.Empty; + var succ = await new Commands.Apply(_repo.FullPath, _patchFile, _ignoreWhiteSpace, SelectedWhiteSpaceMode.Arg, extra) .Use(log) .ExecAsync(); diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs index ee760331f..aca5cb1ef 100644 --- a/src/ViewModels/Checkout.cs +++ b/src/ViewModels/Checkout.cs @@ -4,9 +4,9 @@ namespace SourceGit.ViewModels { public class Checkout : Popup { - public string Branch + public string BranchName { - get; + get => _branch.Name; } public bool DiscardLocalChanges @@ -15,24 +15,20 @@ public bool DiscardLocalChanges set; } - public Checkout(Repository repo, string branch) + public Checkout(Repository repo, Models.Branch branch) { _repo = repo; - Branch = branch; + _branch = branch; DiscardLocalChanges = false; } - public override bool CanStartDirectly() - { - return _repo.LocalChangesCount == 0; - } - public override async Task Sure() { using var lockWatcher = _repo.LockWatcher(); - ProgressDescription = $"Checkout '{Branch}' ..."; + var branchName = BranchName; + ProgressDescription = $"Checkout '{branchName}' ..."; - var log = _repo.CreateLog($"Checkout '{Branch}'"); + var log = _repo.CreateLog($"Checkout '{branchName}'"); Use(log); if (_repo.CurrentBranch is { IsDetachedHead: true }) @@ -70,7 +66,7 @@ public override async Task Sure() succ = await new Commands.Checkout(_repo.FullPath) .Use(log) - .BranchAsync(Branch, DiscardLocalChanges); + .BranchAsync(branchName, DiscardLocalChanges); if (succ) { @@ -80,21 +76,19 @@ public override async Task Sure() await new Commands.Stash(_repo.FullPath) .Use(log) .PopAsync("stash@{0}"); + + _repo.RefreshAfterCheckoutBranch(_branch); + } + else + { + _repo.MarkWorkingCopyDirtyManually(); } log.Complete(); - - var b = _repo.Branches.Find(x => x.IsLocal && x.Name == Branch); - if (b != null && _repo.HistoryFilterMode == Models.FilterMode.Included) - _repo.SetBranchFilterMode(b, Models.FilterMode.Included, false, false); - - _repo.MarkBranchesDirtyManually(); - - ProgressDescription = "Waiting for branch updated..."; - await Task.Delay(400); return succ; } private readonly Repository _repo = null; + private readonly Models.Branch _branch = null; } } diff --git a/src/ViewModels/CheckoutAndFastForward.cs b/src/ViewModels/CheckoutAndFastForward.cs index 197701c33..120d6c4ad 100644 --- a/src/ViewModels/CheckoutAndFastForward.cs +++ b/src/ViewModels/CheckoutAndFastForward.cs @@ -80,17 +80,19 @@ public override async Task Sure() await new Commands.Stash(_repo.FullPath) .Use(log) .PopAsync("stash@{0}"); - } - - log.Complete(); - if (_repo.HistoryFilterMode == Models.FilterMode.Included) - _repo.SetBranchFilterMode(LocalBranch, Models.FilterMode.Included, false, false); + LocalBranch.Behind.Clear(); + LocalBranch.Head = RemoteBranch.Head; + LocalBranch.CommitterDate = RemoteBranch.CommitterDate; - _repo.MarkBranchesDirtyManually(); + _repo.RefreshAfterCheckoutBranch(LocalBranch); + } + else + { + _repo.MarkWorkingCopyDirtyManually(); + } - ProgressDescription = "Waiting for branch updated..."; - await Task.Delay(400); + log.Complete(); return succ; } diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 8cffb0ae9..6ccfba0ae 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -240,8 +240,13 @@ public async Task SaveChangesAsPatchAsync(List changes, string sa if (_commit == null) return; - var baseRevision = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0]; - var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo.FullPath, changes, baseRevision, _commit.SHA, saveTo); + var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync( + _repo.FullPath, + changes, + _commit.FirstParentToCompare, + _commit.SHA, + saveTo); + if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -535,8 +540,7 @@ private void Refresh() Task.Run(async () => { - var parent = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : $"{_commit.SHA}^"; - var cmd = new Commands.CompareRevisions(_repo.FullPath, parent, _commit.SHA) { CancellationToken = token }; + var cmd = new Commands.CompareRevisions(_repo.FullPath, _commit.FirstParentToCompare, _commit.SHA) { CancellationToken = token }; var changes = await cmd.ReadAsync().ConfigureAwait(false); var visible = changes; if (!string.IsNullOrWhiteSpace(_searchChangeFilter)) @@ -757,7 +761,7 @@ private async Task SetViewingCommitAsync(Models.Object file) [GeneratedRegex(@"\b(https?://|ftp://)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]")] private static partial Regex REG_URL_FORMAT(); - [GeneratedRegex(@"\b([0-9a-fA-F]{6,40})\b")] + [GeneratedRegex(@"\b([0-9a-fA-F]{6,64})\b")] private static partial Regex REG_SHA_FORMAT(); [GeneratedRegex(@"`.*?`")] diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs index b5d4d7125..832f56268 100644 --- a/src/ViewModels/CreateBranch.cs +++ b/src/ViewModels/CreateBranch.cs @@ -58,6 +58,8 @@ public CreateBranch(Repository repo, Models.Branch branch) { _repo = repo; _baseOnRevision = branch.Head; + _committerDate = branch.CommitterDate; + _head = branch.Head; if (!branch.IsLocal) Name = branch.Name; @@ -70,6 +72,8 @@ public CreateBranch(Repository repo, Models.Commit commit) { _repo = repo; _baseOnRevision = commit.SHA; + _committerDate = commit.CommitterTime; + _head = commit.SHA; BasedOn = commit; DiscardLocalChanges = false; @@ -79,6 +83,8 @@ public CreateBranch(Repository repo, Models.Tag tag) { _repo = repo; _baseOnRevision = tag.SHA; + _committerDate = tag.CreatorDate; + _head = tag.SHA; BasedOn = tag; DiscardLocalChanges = false; @@ -125,6 +131,15 @@ public override async Task Sure() } } + Models.Branch created = new() + { + Name = _name, + FullName = $"refs/heads/{_name}", + CommitterDate = _committerDate, + Head = _head, + IsLocal = true, + }; + bool succ; if (CheckoutAfterCreated && !_repo.IsBare) { @@ -168,43 +183,33 @@ public override async Task Sure() .CreateAsync(_baseOnRevision, _allowOverwrite); } - if (succ && BasedOn is Models.Branch { IsLocal: false } basedOn && _name.Equals(basedOn.Name, StringComparison.Ordinal)) + if (succ) { - await new Commands.Branch(_repo.FullPath, _name) + if (BasedOn is Models.Branch { IsLocal: false } basedOn && _name.Equals(basedOn.Name, StringComparison.Ordinal)) + { + await new Commands.Branch(_repo.FullPath, _name) .Use(log) .SetUpstreamAsync(basedOn); - } - - log.Complete(); - - if (succ && CheckoutAfterCreated) - { - var fake = new Models.Branch() { IsLocal = true, FullName = $"refs/heads/{_name}" }; - if (BasedOn is Models.Branch { IsLocal: false } based) - fake.Upstream = based.FullName; - var folderEndIdx = fake.FullName.LastIndexOf('/'); - if (folderEndIdx > 10) - _repo.UIStates.ExpandedBranchNodesInSideBar.Add(fake.FullName.Substring(0, folderEndIdx)); + created.Upstream = basedOn.FullName; + } - if (_repo.HistoryFilterMode == Models.FilterMode.Included) - _repo.SetBranchFilterMode(fake, Models.FilterMode.Included, false, false); + _repo.RefreshAfterCreateBranch(created, CheckoutAfterCreated); } - - _repo.MarkBranchesDirtyManually(); - - if (CheckoutAfterCreated) + else { - ProgressDescription = "Waiting for branch updated..."; - await Task.Delay(400); + _repo.MarkWorkingCopyDirtyManually(); } + log.Complete(); return true; } private readonly Repository _repo = null; - private string _name = null; private readonly string _baseOnRevision = null; + private readonly ulong _committerDate = 0; + private readonly string _head = string.Empty; + private string _name = null; private bool _allowOverwrite = false; } } diff --git a/src/ViewModels/EditRepositoryNode.cs b/src/ViewModels/EditRepositoryNode.cs index d7176c786..cb83668e7 100644 --- a/src/ViewModels/EditRepositoryNode.cs +++ b/src/ViewModels/EditRepositoryNode.cs @@ -6,17 +6,14 @@ namespace SourceGit.ViewModels { public class EditRepositoryNode : Popup { - public string Id + public string Target { - get => _id; - set => SetProperty(ref _id, value); + get; } - [Required(ErrorMessage = "Name is required!")] - public string Name + public bool IsRepository { - get => _name; - set => SetProperty(ref _name, value, true); + get; } public List Bookmarks @@ -24,26 +21,27 @@ public List Bookmarks get; } - public int Bookmark + [Required(ErrorMessage = "Name is required!")] + public string Name { - get => _bookmark; - set => SetProperty(ref _bookmark, value); + get => _name; + set => SetProperty(ref _name, value, true); } - public bool IsRepository + public int Bookmark { - get => _isRepository; - set => SetProperty(ref _isRepository, value); + get => _bookmark; + set => SetProperty(ref _bookmark, value); } public EditRepositoryNode(RepositoryNode node) { _node = node; - _id = node.Id; _name = node.Name; - _isRepository = node.IsRepository; _bookmark = node.Bookmark; + Target = node.IsRepository ? node.Id : node.Name; + IsRepository = node.IsRepository; Bookmarks = new List(); for (var i = 0; i < Models.Bookmarks.Brushes.Length; i++) Bookmarks.Add(i); @@ -65,9 +63,7 @@ public override Task Sure() } private RepositoryNode _node = null; - private string _id = null; private string _name = null; - private bool _isRepository = false; private int _bookmark = 0; } } diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs index 722e450f4..17d1fc05d 100644 --- a/src/ViewModels/FileHistories.cs +++ b/src/ViewModels/FileHistories.cs @@ -2,10 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; - -using Avalonia.Collections; using Avalonia.Threading; - using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -17,15 +14,27 @@ public class FileHistoriesRevisionFile(string path, object content = null, bool public bool CanOpenWithDefaultEditor { get; set; } = canOpenWithDefaultEditor; } + public class FileHistoriesSingleRevisionViewMode + { + public bool IsDiff + { + get; + set; + } = true; + } + public class FileHistoriesSingleRevision : ObservableObject { public bool IsDiffMode { - get => _isDiffMode; + get => _viewMode.IsDiff; set { - if (SetProperty(ref _isDiffMode, value)) + if (_viewMode.IsDiff != value) + { + _viewMode.IsDiff = value; RefreshViewContent(); + } } } @@ -35,17 +44,24 @@ public object ViewContent set => SetProperty(ref _viewContent, value); } - public FileHistoriesSingleRevision(string repo, Models.FileVersion revision, bool prevIsDiffMode) + public FileHistoriesSingleRevision(string repo, Models.FileVersion revision, FileHistoriesSingleRevisionViewMode viewMode) { _repo = repo; _file = revision.Path; _revision = revision; - _isDiffMode = prevIsDiffMode; + _viewMode = viewMode; _viewContent = null; RefreshViewContent(); } + public void SetRevision(Models.FileVersion revision) + { + _file = revision.Path; + _revision = revision; + RefreshViewContent(); + } + public async Task ResetToSelectedRevisionAsync() { return await new Commands.Checkout(_repo) @@ -72,7 +88,7 @@ await Commands.SaveRevisionFile private void RefreshViewContent() { - if (_isDiffMode) + if (_viewMode.IsDiff) { ViewContent = new DiffContext(_repo, new(_revision), _viewContent as DiffContext); return; @@ -155,7 +171,7 @@ private async Task GetRevisionFileContentAsync(Models.Object obj) private string _repo = null; private string _file = null; private Models.FileVersion _revision = null; - private bool _isDiffMode = false; + private FileHistoriesSingleRevisionViewMode _viewMode = null; private object _viewContent = null; } @@ -226,11 +242,15 @@ public List Revisions set => SetProperty(ref _revisions, value); } - public AvaloniaList SelectedRevisions + public List SelectedRevisions { - get; - set; - } = []; + get => _selectedRevisions; + set + { + if (SetProperty(ref _selectedRevisions, value)) + RefreshViewContent(); + } + } public object ViewContent { @@ -257,23 +277,8 @@ public FileHistories(string repo, string file, string commit = null) { IsLoading = false; Revisions = revisions; - if (revisions.Count > 0) - SelectedRevisions.Add(revisions[0]); }); }); - - SelectedRevisions.CollectionChanged += (_, _) => - { - if (_viewContent is FileHistoriesSingleRevision singleRevision) - _prevIsDiffMode = singleRevision.IsDiffMode; - - ViewContent = SelectedRevisions.Count switch - { - 1 => new FileHistoriesSingleRevision(_repo, SelectedRevisions[0], _prevIsDiffMode), - 2 => new FileHistoriesCompareRevisions(_repo, SelectedRevisions[0], SelectedRevisions[1]), - _ => SelectedRevisions.Count, - }; - }; } public void NavigateToCommit(Models.FileVersion revision) @@ -303,10 +308,35 @@ public string GetCommitFullMessage(Models.FileVersion revision) return msg; } + private void RefreshViewContent() + { + var count = _selectedRevisions?.Count ?? 0; + if (count == 0) + { + ViewContent = null; + } + else if (count == 1) + { + if (_viewContent is FileHistoriesSingleRevision single) + single.SetRevision(_selectedRevisions[0]); + else + ViewContent = new FileHistoriesSingleRevision(_repo, _selectedRevisions[0], _viewMode); + } + else if (count == 2) + { + ViewContent = new FileHistoriesCompareRevisions(_repo, _selectedRevisions[0], _selectedRevisions[1]); + } + else + { + ViewContent = _selectedRevisions.Count; + } + } + private readonly string _repo = null; private bool _isLoading = true; - private bool _prevIsDiffMode = true; + private FileHistoriesSingleRevisionViewMode _viewMode = new(); private List _revisions = null; + private List _selectedRevisions = []; private Dictionary _fullCommitMessages = new(); private object _viewContent = null; } diff --git a/src/ViewModels/RenameBranch.cs b/src/ViewModels/RenameBranch.cs index e15a96b90..dbca651e7 100644 --- a/src/ViewModels/RenameBranch.cs +++ b/src/ViewModels/RenameBranch.cs @@ -60,17 +60,9 @@ public override async Task Sure() .RenameAsync(_name); if (succ) - _repo.UIStates.RenameBranchFilter(Target.FullName, _name); + _repo.RefreshAfterRenameBranch(Target, _name); log.Complete(); - _repo.MarkBranchesDirtyManually(); - - if (isCurrent) - { - ProgressDescription = "Waiting for branch updated..."; - await Task.Delay(400); - } - return succ; } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 7123a2e0c..4588647ca 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -788,6 +788,113 @@ public IDisposable LockWatcher() return _watcher?.Lock(); } + public void RefreshAfterCreateBranch(Models.Branch created, bool checkout) + { + _watcher?.MarkBranchUpdated(); + _watcher?.MarkWorkingCopyUpdated(); + + _branches.Add(created); + + if (checkout) + { + if (_currentBranch.IsDetachedHead) + { + _branches.Remove(_currentBranch); + } + else + { + _currentBranch.IsCurrent = false; + _currentBranch.WorktreePath = null; + } + + created.IsCurrent = true; + created.WorktreePath = FullPath; + + var folderEndIdx = created.FullName.LastIndexOf('/'); + if (folderEndIdx > 10) + _uiStates.ExpandedBranchNodesInSideBar.Add(created.FullName.Substring(0, folderEndIdx)); + + if (_historyFilterMode == Models.FilterMode.Included) + SetBranchFilterMode(created, Models.FilterMode.Included, false, false); + + CurrentBranch = created; + } + + List locals = []; + foreach (var b in _branches) + { + if (b.IsLocal) + locals.Add(b); + } + + var builder = BuildBranchTree(locals, []); + LocalBranchTrees = builder.Locals; + + RefreshCommits(); + RefreshWorkingCopyChanges(); + RefreshWorktrees(); + } + + public void RefreshAfterCheckoutBranch(Models.Branch checkouted) + { + _watcher?.MarkBranchUpdated(); + _watcher?.MarkWorkingCopyUpdated(); + + if (_currentBranch.IsDetachedHead) + { + _branches.Remove(_currentBranch); + } + else + { + _currentBranch.IsCurrent = false; + _currentBranch.WorktreePath = null; + } + + checkouted.IsCurrent = true; + checkouted.WorktreePath = FullPath; + if (_historyFilterMode == Models.FilterMode.Included) + SetBranchFilterMode(checkouted, Models.FilterMode.Included, false, false); + + List locals = []; + foreach (var b in _branches) + { + if (b.IsLocal) + locals.Add(b); + } + + var builder = BuildBranchTree(locals, []); + LocalBranchTrees = builder.Locals; + CurrentBranch = checkouted; + + RefreshCommits(); + RefreshWorkingCopyChanges(); + RefreshWorktrees(); + } + + public void RefreshAfterRenameBranch(Models.Branch b, string newName) + { + _watcher?.MarkBranchUpdated(); + + var newFullName = $"refs/heads/{newName}"; + _uiStates.RenameBranchFilter(b.FullName, newFullName); + + b.Name = newName; + b.FullName = newFullName; + + List locals = []; + foreach (var branch in _branches) + { + if (branch.IsLocal) + locals.Add(branch); + } + + var builder = BuildBranchTree(locals, []); + LocalBranchTrees = builder.Locals; + + RefreshCommits(); + RefreshWorktrees(); + } + public void MarkBranchesDirtyManually() { _watcher?.MarkBranchUpdated(); @@ -1219,17 +1326,14 @@ public void RefreshWorkingCopyChanges() .GetResultAsync() .ConfigureAwait(false); - if (_workingCopy == null || token.IsCancellationRequested) - return; - changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); - _workingCopy.SetData(changes, token); Dispatcher.UIThread.Invoke(() => { if (token.IsCancellationRequested) return; + _workingCopy.SetData(changes); LocalChangesCount = changes.Count; OnPropertyChanged(nameof(InProgressContext)); GetOwnerPage()?.ChangeDirtyState(Models.DirtyState.HasLocalChanges, changes.Count == 0); @@ -1304,7 +1408,10 @@ public async Task CheckoutBranchAsync(Models.Branch branch) if (branch.IsLocal) { - await ShowAndStartPopupAsync(new Checkout(this, branch.Name)); + if (_workingCopy is { CanSwitchBranchDirectly: true }) + await ShowAndStartPopupAsync(new Checkout(this, branch)); + else + ShowPopup(new Checkout(this, branch)); } else { diff --git a/src/ViewModels/SearchCommitContext.cs b/src/ViewModels/SearchCommitContext.cs index 374336e5e..da4514e91 100644 --- a/src/ViewModels/SearchCommitContext.cs +++ b/src/ViewModels/SearchCommitContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -105,6 +106,12 @@ public void StartSearch() IsQuerying = true; + if (_cancellation is { IsCancellationRequested: false }) + _cancellation.Cancel(); + + _cancellation = new(); + var token = _cancellation.Token; + Task.Run(async () => { var result = new List(); @@ -158,17 +165,23 @@ public void StartSearch() Dispatcher.UIThread.Post(() => { - IsQuerying = false; + if (token.IsCancellationRequested) + return; + IsQuerying = false; if (_repo.IsSearchingCommits) Results = result; }); - }); + }, token); } public void EndSearch() { + if (_cancellation is { IsCancellationRequested: false }) + _cancellation.Cancel(); + _worktreeFiles = null; + IsQuerying = false; Suggestions = null; Results = null; GC.Collect(); @@ -228,6 +241,7 @@ private void UpdateSuggestions() } private Repository _repo = null; + private CancellationTokenSource _cancellation = null; private int _method = (int)Models.CommitSearchMethod.ByMessage; private string _filter = string.Empty; private bool _onlySearchCurrentBranch = false; diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index a2a47c274..a484b0225 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -63,7 +63,7 @@ public Models.Stash SelectedStash var untracked = new List(); if (value.Parents.Count == 3) { - untracked = await new Commands.CompareRevisions(_repo.FullPath, Models.Commit.EmptyTreeSHA1, value.Parents[2]) + untracked = await new Commands.CompareRevisions(_repo.FullPath, value.UntrackedParent, value.Parents[2]) .ReadAsync() .ConfigureAwait(false); @@ -107,7 +107,7 @@ public List SelectedChanges if (value is not { Count: 1 }) DiffContext = null; else if (_untracked.Contains(value[0])) - DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], value[0]), _diffContext); + DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(_selectedStash.UntrackedParent, _selectedStash.Parents[2], value[0]), _diffContext); else DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, value[0]), _diffContext); } @@ -167,16 +167,16 @@ public async Task SaveStashAsPatchAsync(Models.Stash stash, string saveTo) .ConfigureAwait(false); foreach (var c in changes) - opts.Add(new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, c)); + opts.Add(new Models.DiffOption(stash.Parents[0], stash.SHA, c)); if (stash.Parents.Count == 3) { - var untracked = await new Commands.CompareRevisions(_repo.FullPath, Models.Commit.EmptyTreeSHA1, stash.Parents[2]) + var untracked = await new Commands.CompareRevisions(_repo.FullPath, stash.UntrackedParent, stash.Parents[2]) .ReadAsync() .ConfigureAwait(false); foreach (var c in untracked) - opts.Add(new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], c)); + opts.Add(new Models.DiffOption(stash.UntrackedParent, stash.Parents[2], c)); changes.AddRange(untracked); } @@ -190,7 +190,7 @@ public void OpenChangeWithExternalDiffTool(Models.Change change) { Models.DiffOption opt; if (_untracked.Contains(change)) - opt = new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], change); + opt = new Models.DiffOption(_selectedStash.UntrackedParent, _selectedStash.Parents[2], change); else opt = new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, change); @@ -242,7 +242,7 @@ public async Task ApplySelectedChanges(List changes) foreach (var c in changes) { if (_untracked.Contains(c) && _selectedStash.Parents.Count == 3) - opts.Add(new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], c)); + opts.Add(new Models.DiffOption(_selectedStash.UntrackedParent, _selectedStash.Parents[2], c)); else opts.Add(new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, c)); } diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 6b68a22dd..c45b79e15 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading; using System.Threading.Tasks; - -using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -41,6 +38,12 @@ public bool HasUnsolvedConflicts set => SetProperty(ref _hasUnsolvedConflicts, value); } + public bool CanSwitchBranchDirectly + { + get; + set; + } = false; + public InProgressContext InProgressContext { get => _inProgressContext; @@ -256,64 +259,67 @@ public void Dispose() _commitMessage = string.Empty; } - public void SetData(List changes, CancellationToken cancellationToken) + public void SetData(List changes) { if (!IsChanged(_cached, changes)) { - // Just force refresh selected changes. - Dispatcher.UIThread.Invoke(() => - { - if (cancellationToken.IsCancellationRequested) - return; - - HasUnsolvedConflicts = _cached.Find(x => x.IsConflicted) != null; - UpdateInProgressState(); - UpdateDetail(); - }); - + HasUnsolvedConflicts = _cached.Find(x => x.IsConflicted) != null; + UpdateInProgressState(); + UpdateDetail(); return; } var lastSelectedUnstaged = new HashSet(); - var lastSelectedStaged = new HashSet(); if (_selectedUnstaged is { Count: > 0 }) { foreach (var c in _selectedUnstaged) lastSelectedUnstaged.Add(c.Path); } - else if (_selectedStaged is { Count: > 0 }) - { - foreach (var c in _selectedStaged) - lastSelectedStaged.Add(c.Path); - } var unstaged = new List(); + var visibleUnstaged = new List(); + var selectedUnstaged = new List(); + var noFilter = string.IsNullOrEmpty(_filter); var hasConflict = false; + var canSwitchDirectly = true; foreach (var c in changes) { if (c.WorkTree != Models.ChangeState.None) { unstaged.Add(c); hasConflict |= c.IsConflicted; + + if (noFilter || c.Path.Contains(_filter, StringComparison.OrdinalIgnoreCase)) + { + visibleUnstaged.Add(c); + if (lastSelectedUnstaged.Contains(c.Path)) + selectedUnstaged.Add(c); + } } - } - var visibleUnstaged = GetVisibleChanges(unstaged); - var selectedUnstaged = new List(); - foreach (var c in visibleUnstaged) - { - if (lastSelectedUnstaged.Contains(c.Path)) - selectedUnstaged.Add(c); + if (!canSwitchDirectly) + continue; + + if (c.WorkTree == Models.ChangeState.Untracked || c.Index == Models.ChangeState.Added) + continue; + + canSwitchDirectly = false; } var staged = GetStagedChanges(changes); - var visibleStaged = GetVisibleChanges(staged); var selectedStaged = new List(); - foreach (var c in visibleStaged) + if (_selectedStaged is { Count: > 0 }) { - if (lastSelectedStaged.Contains(c.Path)) - selectedStaged.Add(c); + var set = new HashSet(); + foreach (var c in _selectedStaged) + set.Add(c.Path); + + foreach (var c in visibleStaged) + { + if (set.Contains(c.Path)) + selectedStaged.Add(c); + } } if (selectedUnstaged.Count == 0 && selectedStaged.Count == 0 && hasConflict) @@ -322,25 +328,20 @@ public void SetData(List changes, CancellationToken cancellationT selectedUnstaged.Add(firstConflict); } - Dispatcher.UIThread.Invoke(() => - { - if (cancellationToken.IsCancellationRequested) - return; - - _isLoadingData = true; - _cached = changes; - HasUnsolvedConflicts = hasConflict; - VisibleUnstaged = visibleUnstaged; - VisibleStaged = visibleStaged; - Unstaged = unstaged; - Staged = staged; - SelectedUnstaged = selectedUnstaged; - SelectedStaged = selectedStaged; - _isLoadingData = false; + _isLoadingData = true; + _cached = changes; + HasUnsolvedConflicts = hasConflict; + CanSwitchBranchDirectly = canSwitchDirectly; + VisibleUnstaged = visibleUnstaged; + VisibleStaged = visibleStaged; + Unstaged = unstaged; + Staged = staged; + SelectedUnstaged = selectedUnstaged; + SelectedStaged = selectedStaged; + _isLoadingData = false; - UpdateInProgressState(); - UpdateDetail(); - }); + UpdateInProgressState(); + UpdateDetail(); } public async Task StageChangesAsync(List changes, Models.Change next) @@ -671,8 +672,11 @@ public async Task CommitAsync(bool autoStage, bool autoPush) if (succ) { + // Do not use property `UseAmend` but manually trigger property changed to avoid refreshing staged changes here. + _useAmend = false; + OnPropertyChanged(nameof(UseAmend)); + CommitMessage = string.Empty; - UseAmend = false; if (autoPush && _repo.Remotes.Count > 0) { Models.Branch pushBranch = null; @@ -739,7 +743,7 @@ public async Task CommitAsync(bool autoStage, bool autoPush) if (_useAmend) { var head = new Commands.QuerySingleCommit(_repo.FullPath, "HEAD").GetResult(); - return new Commands.QueryStagedChangesWithAmend(_repo.FullPath, head.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : $"{head.SHA}^").GetResult(); + return new Commands.QueryStagedChangesWithAmend(_repo.FullPath, head.FirstParentToCompare).GetResult(); } var rs = new List(); diff --git a/src/Views/AddRemote.axaml b/src/Views/AddRemote.axaml index eb8d8b9f2..a53cfbe1f 100644 --- a/src/Views/AddRemote.axaml +++ b/src/Views/AddRemote.axaml @@ -27,9 +27,16 @@ + Text="{Binding Name, Mode=TwoWay}"> + + + + + Text="{Binding Url, Mode=TwoWay}"> + + + + + + + + + + + + diff --git a/src/Views/RemoteProtocolSwitcher.axaml.cs b/src/Views/RemoteProtocolSwitcher.axaml.cs new file mode 100644 index 000000000..343dbc133 --- /dev/null +++ b/src/Views/RemoteProtocolSwitcher.axaml.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; + +namespace SourceGit.Views +{ + public partial class RemoteProtocolSwitcher : UserControl + { + public static readonly StyledProperty UrlProperty = + AvaloniaProperty.Register(nameof(Url)); + + public string Url + { + get => GetValue(UrlProperty); + set => SetValue(UrlProperty, value); + } + + public static readonly StyledProperty ActiveProtocolProperty = + AvaloniaProperty.Register(nameof(ActiveProtocol)); + + public string ActiveProtocol + { + get => GetValue(ActiveProtocolProperty); + set => SetValue(ActiveProtocolProperty, value); + } + + public RemoteProtocolSwitcher() + { + InitializeComponent(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == UrlProperty) + { + _protocols.Clear(); + + var url = Url ?? string.Empty; + if (url.StartsWith("https://", StringComparison.Ordinal) && Uri.TryCreate(url, UriKind.Absolute, out var uri)) + { + var host = uri.Host; + var route = uri.AbsolutePath.TrimStart('/'); + + _protocols.Add(url); + _protocols.Add($"git@{host}:{route}"); + + SetCurrentValue(ActiveProtocolProperty, "HTTPS"); + SetCurrentValue(IsVisibleProperty, true); + return; + } + + var match = REG_SSH_FORMAT().Match(url); + if (match.Success) + { + var host = match.Groups[1].Value; + var repo = match.Groups[2].Value; + + _protocols.Add($"https://{host}/{repo}"); + _protocols.Add(url); + + SetCurrentValue(ActiveProtocolProperty, "SSH"); + SetCurrentValue(IsVisibleProperty, true); + return; + } + + SetCurrentValue(IsVisibleProperty, false); + } + } + + private void OnOpenDropdownMenu(object sender, RoutedEventArgs e) + { + if (sender is Button btn && _protocols.Count > 0) + { + var menu = new ContextMenu(); + menu.Placement = PlacementMode.BottomEdgeAlignedLeft; + + foreach (var protocol in _protocols) + { + var dup = protocol; + var item = new MenuItem() { Header = dup }; + item.Click += (_, _) => Url = protocol; + menu.Items.Add(item); + } + + menu.Open(btn); + } + + e.Handled = true; + } + + [GeneratedRegex(@"^git@([\w\.\-]+):(.+)$")] + private static partial Regex REG_SSH_FORMAT(); + private List _protocols = []; + } +} diff --git a/src/Views/RenameBranch.axaml b/src/Views/RenameBranch.axaml index d193a5fd4..bcc405346 100644 --- a/src/Views/RenameBranch.axaml +++ b/src/Views/RenameBranch.axaml @@ -35,7 +35,7 @@ diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 2bbc938fc..8151765df 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -238,7 +238,14 @@ - diff --git a/src/Views/RepositoryToolbar.axaml b/src/Views/RepositoryToolbar.axaml index f857ffee6..72ebb9dc5 100644 --- a/src/Views/RepositoryToolbar.axaml +++ b/src/Views/RepositoryToolbar.axaml @@ -80,11 +80,7 @@ VerticalAlignment="Center" Fill="{DynamicResource Brush.Border2}"/> - - - diff --git a/src/Views/SetSubmoduleBranch.axaml b/src/Views/SetSubmoduleBranch.axaml index 2d5260e76..718fad7fd 100644 --- a/src/Views/SetSubmoduleBranch.axaml +++ b/src/Views/SetSubmoduleBranch.axaml @@ -43,7 +43,7 @@ diff --git a/src/Views/StashSubjectPresenter.cs b/src/Views/StashSubjectPresenter.cs index 8f47a350e..e9c1bac91 100644 --- a/src/Views/StashSubjectPresenter.cs +++ b/src/Views/StashSubjectPresenter.cs @@ -127,7 +127,7 @@ protected override Size MeasureOverride(Size availableSize) [GeneratedRegex(@"^On ([^\s]+)\: ")] private static partial Regex REG_KEYWORD_ON(); - [GeneratedRegex(@"^WIP on ([^\s]+)\: ([a-f0-9]{6,40}) ")] + [GeneratedRegex(@"^WIP on ([^\s]+)\: ([a-f0-9]{6,64}) ")] private static partial Regex REG_KEYWORD_WIP(); } }