diff --git a/.editorconfig b/.editorconfig index f3c9a7bf..720f49b7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,6 @@ indent_style = space indent_size = 4 dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 -end_of_line = crlf dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 12792cf6..2e265201 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,10 +7,10 @@ jobs: matrix: include: - name : Windows x64 - os: windows-2019 + os: windows-2022 runtime: win-x64 - name : Windows ARM64 - os: windows-2019 + os: windows-2022 runtime: win-arm64 - name : macOS (Intel) os: macos-13 @@ -31,7 +31,7 @@ jobs: container: ${{ matrix.container || '' }} steps: - name: Install common CLI tools - if: ${{ startsWith(matrix.runtime, 'linux-') }} + if: startsWith(matrix.runtime, 'linux-') run: | export DEBIAN_FRONTEND=noninteractive ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime @@ -45,7 +45,7 @@ jobs: with: dotnet-version: 9.0.x - name: Configure arm64 packages - if: ${{ matrix.runtime == 'linux-arm64' }} + if: matrix.runtime == 'linux-arm64' run: | sudo dpkg --add-architecture arm64 echo 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main restricted @@ -55,7 +55,7 @@ jobs: sudo sed -i -e 's/^deb http/deb [arch=amd64] http/g' /etc/apt/sources.list sudo sed -i -e 's/^deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list - name: Install cross-compiling dependencies - if: ${{ matrix.runtime == 'linux-arm64' }} + if: matrix.runtime == 'linux-arm64' run: | sudo apt-get update sudo apt-get install -y llvm gcc-aarch64-linux-gnu @@ -64,10 +64,10 @@ jobs: - name: Publish run: dotnet publish src/SourceGit.csproj -c Release -o publish -r ${{ matrix.runtime }} - name: Rename executable file - if: ${{ startsWith(matrix.runtime, 'linux-') }} + if: startsWith(matrix.runtime, 'linux-') run: mv publish/SourceGit publish/sourcegit - name: Tar artifact - if: ${{ startsWith(matrix.runtime, 'linux-') || startsWith(matrix.runtime, 'osx-') }} + if: startsWith(matrix.runtime, 'linux-') || startsWith(matrix.runtime, 'osx-') run: | tar -cvf "sourcegit.${{ matrix.runtime }}.tar" -C publish . rm -r publish/* diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml new file mode 100644 index 00000000..4d20b20e --- /dev/null +++ b/.github/workflows/format-check.yml @@ -0,0 +1,22 @@ +name: Format Check +on: + push: + branches: [ develop ] + workflow_dispatch: + workflow_call: + +jobs: + format-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Run formatting check + run: dotnet format --verify-no-changes diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 2dfc97fd..4fb5dca3 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -9,7 +9,7 @@ on: jobs: windows: name: Package Windows - runs-on: windows-2019 + runs-on: windows-2022 strategy: matrix: runtime: [ win-x64, win-arm64 ] diff --git a/README.md b/README.md index f9ba3072..ac3104a1 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ This app supports open repository in external tools listed in the table below. | Visual Studio Code | YES | YES | YES | | Visual Studio Code - Insiders | YES | YES | YES | | VSCodium | YES | YES | YES | +| Cursor | YES | YES | YES | | Fleet | YES | YES | YES | | Sublime Text | YES | YES | YES | | Zed | NO | YES | YES | diff --git a/SourceGit.sln b/SourceGit.sln index 624322f8..dad5a475 100644 --- a/SourceGit.sln +++ b/SourceGit.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\package.yml = .github\workflows\package.yml .github\workflows\release.yml = .github\workflows\release.yml .github\workflows\localization-check.yml = .github\workflows\localization-check.yml + .github\workflows\format-check.yml = .github\workflows\format-check.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}" diff --git a/TRANSLATION.md b/TRANSLATION.md index 97a4258c..a7cdbc1e 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,11 +6,62 @@ 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-%E2%88%9A-brightgreen) +### ![de__DE](https://img.shields.io/badge/de__DE-97.87%25-yellow) -### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen) +
+Missing keys in de_DE.axaml -### ![fr__FR](https://img.shields.io/badge/fr__FR-88.61%25-yellow) +- Text.ChangeSubmoduleUrl +- Text.ChangeSubmoduleUrl.Submodule +- Text.ChangeSubmoduleUrl.URL +- Text.CommitCM.CopyCommitMessage +- Text.MoveSubmodule +- Text.MoveSubmodule.MoveTo +- Text.MoveSubmodule.Submodule +- Text.SetSubmoduleBranch +- Text.SetSubmoduleBranch.Submodule +- Text.SetSubmoduleBranch.Current +- Text.SetSubmoduleBranch.New +- Text.SetSubmoduleBranch.New.Tip +- Text.Submodule.Branch +- Text.Submodule.Histories +- Text.Submodule.Move +- Text.Submodule.SetBranch +- Text.Submodule.SetURL +- Text.Submodule.Update + +
+ +### ![es__ES](https://img.shields.io/badge/es__ES-97.51%25-yellow) + +
+Missing keys in es_ES.axaml + +- Text.ChangeSubmoduleUrl +- Text.ChangeSubmoduleUrl.Submodule +- Text.ChangeSubmoduleUrl.URL +- Text.CommitCM.CopyCommitMessage +- Text.ConfigureCustomActionControls.Options +- Text.ConfigureCustomActionControls.Options.Tip +- Text.DirHistories +- Text.MoveSubmodule +- Text.MoveSubmodule.MoveTo +- Text.MoveSubmodule.Submodule +- Text.SetSubmoduleBranch +- Text.SetSubmoduleBranch.Submodule +- Text.SetSubmoduleBranch.Current +- Text.SetSubmoduleBranch.New +- Text.SetSubmoduleBranch.New.Tip +- Text.Submodule.Branch +- Text.Submodule.Histories +- Text.Submodule.Move +- Text.Submodule.SetBranch +- Text.Submodule.SetURL +- Text.Submodule.Update + +
+ +### ![fr__FR](https://img.shields.io/badge/fr__FR-86.26%25-yellow)
Missing keys in fr_FR.axaml @@ -27,11 +78,15 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.Skip - Text.Bisect.WaitingForRange - Text.BranchCM.ResetToSelectedCommit +- Text.ChangeSubmoduleUrl +- Text.ChangeSubmoduleUrl.Submodule +- Text.ChangeSubmoduleUrl.URL - Text.Checkout.RecurseSubmodules - Text.Checkout.WarnLostCommits - Text.Checkout.WithFastForward - Text.Checkout.WithFastForward.Upstream - Text.CommitCM.CopyAuthor +- Text.CommitCM.CopyCommitMessage - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject - Text.CommitCM.PushRevision @@ -47,10 +102,11 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.CheckedValue - Text.ConfigureCustomActionControls.CheckedValue.Tip - Text.ConfigureCustomActionControls.Description -- Text.ConfigureCustomActionControls.Description.Tip - Text.ConfigureCustomActionControls.DefaultValue - Text.ConfigureCustomActionControls.IsFolder - Text.ConfigureCustomActionControls.Label +- Text.ConfigureCustomActionControls.Options +- Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.Type - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges @@ -61,6 +117,7 @@ This document shows the translation status of each locale file in the repository - Text.DeinitSubmodule.Force - Text.DeinitSubmodule.Path - Text.Diff.Submodule.Deleted +- Text.DirHistories - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.GitFlow.FinishWithPush @@ -71,6 +128,9 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Merge.Edit +- Text.MoveSubmodule +- Text.MoveSubmodule.MoveTo +- Text.MoveSubmodule.Submodule - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Pull.RecurseSubmodules - Text.Push.Revision @@ -87,14 +147,27 @@ This document shows the translation status of each locale file in the repository - Text.ResetWithoutCheckout - Text.ResetWithoutCheckout.MoveTo - Text.ResetWithoutCheckout.Target +- Text.SetSubmoduleBranch +- Text.SetSubmoduleBranch.Submodule +- Text.SetSubmoduleBranch.Current +- Text.SetSubmoduleBranch.New +- Text.SetSubmoduleBranch.New.Tip - Text.Stash.Mode - Text.StashCM.CopyMessage +- Text.Submodule.Branch - Text.Submodule.Deinit +- Text.Submodule.Histories +- Text.Submodule.Move +- Text.Submodule.RelativePath +- Text.Submodule.RelativePath.Placeholder +- Text.Submodule.SetBranch +- Text.Submodule.SetURL - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited - Text.Submodule.Status.RevisionChanged - Text.Submodule.Status.Unmerged +- Text.Submodule.Update - Text.Submodule.URL - Text.TagCM.CustomAction - Text.ViewLogs @@ -112,7 +185,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-93.82%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-91.59%25-yellow)
Missing keys in it_IT.axaml @@ -122,9 +195,13 @@ This document shows the translation status of each locale file in the repository - Text.AddToIgnore.Storage - Text.Avatar.Load - Text.BranchCM.ResetToSelectedCommit +- Text.ChangeSubmoduleUrl +- Text.ChangeSubmoduleUrl.Submodule +- Text.ChangeSubmoduleUrl.URL - Text.Checkout.WarnLostCommits - Text.Checkout.WithFastForward - Text.Checkout.WithFastForward.Upstream +- Text.CommitCM.CopyCommitMessage - Text.CommitCM.PushRevision - Text.CommitDetail.Changes.Count - Text.Configure.CustomAction.Arguments.Tip @@ -136,16 +213,18 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.CheckedValue - Text.ConfigureCustomActionControls.CheckedValue.Tip - Text.ConfigureCustomActionControls.Description -- Text.ConfigureCustomActionControls.Description.Tip - Text.ConfigureCustomActionControls.DefaultValue - Text.ConfigureCustomActionControls.IsFolder - Text.ConfigureCustomActionControls.Label +- Text.ConfigureCustomActionControls.Options +- Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.Type - Text.CreateBranch.OverwriteExisting - Text.DeinitSubmodule - Text.DeinitSubmodule.Force - Text.DeinitSubmodule.Path - Text.Diff.Submodule.Deleted +- Text.DirHistories - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.Hotkeys.Global.SwitchWorkspace @@ -153,6 +232,9 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Merge.Edit +- Text.MoveSubmodule +- Text.MoveSubmodule.MoveTo +- Text.MoveSubmodule.Submodule - Text.Pull.RecurseSubmodules - Text.Push.Revision - Text.Push.Revision.Title @@ -161,9 +243,20 @@ This document shows the translation status of each locale file in the repository - Text.ResetWithoutCheckout - Text.ResetWithoutCheckout.MoveTo - Text.ResetWithoutCheckout.Target +- Text.SetSubmoduleBranch +- Text.SetSubmoduleBranch.Submodule +- Text.SetSubmoduleBranch.Current +- Text.SetSubmoduleBranch.New +- Text.SetSubmoduleBranch.New.Tip - Text.Stash.Mode - Text.StashCM.CopyMessage +- Text.Submodule.Branch - Text.Submodule.Deinit +- Text.Submodule.Histories +- Text.Submodule.Move +- Text.Submodule.SetBranch +- Text.Submodule.SetURL +- Text.Submodule.Update - Text.TagCM.CustomAction - Text.WorkingCopy.AddToGitIgnore.InFolder - Text.WorkingCopy.ConfirmCommitWithDetachedHead @@ -171,7 +264,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-88.36%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-86.26%25-yellow)
Missing keys in ja_JP.axaml @@ -189,11 +282,15 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.WaitingForRange - Text.BranchCM.CompareWithCurrent - Text.BranchCM.ResetToSelectedCommit +- Text.ChangeSubmoduleUrl +- Text.ChangeSubmoduleUrl.Submodule +- Text.ChangeSubmoduleUrl.URL - Text.Checkout.RecurseSubmodules - Text.Checkout.WarnLostCommits - Text.Checkout.WithFastForward - Text.Checkout.WithFastForward.Upstream - Text.CommitCM.CopyAuthor +- Text.CommitCM.CopyCommitMessage - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject - Text.CommitCM.PushRevision @@ -209,10 +306,11 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.CheckedValue - Text.ConfigureCustomActionControls.CheckedValue.Tip - Text.ConfigureCustomActionControls.Description -- Text.ConfigureCustomActionControls.Description.Tip - Text.ConfigureCustomActionControls.DefaultValue - Text.ConfigureCustomActionControls.IsFolder - Text.ConfigureCustomActionControls.Label +- Text.ConfigureCustomActionControls.Options +- Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.Type - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges @@ -223,6 +321,7 @@ This document shows the translation status of each locale file in the repository - Text.DeinitSubmodule.Force - Text.DeinitSubmodule.Path - Text.Diff.Submodule.Deleted +- Text.DirHistories - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.GitFlow.FinishWithPush @@ -233,6 +332,9 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Merge.Edit +- Text.MoveSubmodule +- Text.MoveSubmodule.MoveTo +- Text.MoveSubmodule.Submodule - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Pull.RecurseSubmodules - Text.Push.Revision @@ -250,14 +352,25 @@ This document shows the translation status of each locale file in the repository - Text.ResetWithoutCheckout - Text.ResetWithoutCheckout.MoveTo - Text.ResetWithoutCheckout.Target +- Text.SetSubmoduleBranch +- Text.SetSubmoduleBranch.Submodule +- Text.SetSubmoduleBranch.Current +- Text.SetSubmoduleBranch.New +- Text.SetSubmoduleBranch.New.Tip - Text.Stash.Mode - Text.StashCM.CopyMessage +- Text.Submodule.Branch - Text.Submodule.Deinit +- Text.Submodule.Histories +- Text.Submodule.Move +- Text.Submodule.SetBranch +- Text.Submodule.SetURL - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited - Text.Submodule.Status.RevisionChanged - Text.Submodule.Status.Unmerged +- Text.Submodule.Update - Text.Submodule.URL - Text.TagCM.CustomAction - Text.ViewLogs @@ -275,7 +388,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-80.85%25-yellow) +### ![pt__BR](https://img.shields.io/badge/pt__BR-78.91%25-yellow)
Missing keys in pt_BR.axaml @@ -301,12 +414,16 @@ This document shows the translation status of each locale file in the repository - Text.BranchCM.MergeMultiBranches - Text.BranchCM.ResetToSelectedCommit - Text.BranchUpstreamInvalid +- Text.ChangeSubmoduleUrl +- Text.ChangeSubmoduleUrl.Submodule +- Text.ChangeSubmoduleUrl.URL - Text.Checkout.RecurseSubmodules - Text.Checkout.WarnLostCommits - Text.Checkout.WithFastForward - Text.Checkout.WithFastForward.Upstream - Text.Clone.RecurseSubmodules - Text.CommitCM.CopyAuthor +- Text.CommitCM.CopyCommitMessage - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject - Text.CommitCM.Merge @@ -330,10 +447,11 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.CheckedValue - Text.ConfigureCustomActionControls.CheckedValue.Tip - Text.ConfigureCustomActionControls.Description -- Text.ConfigureCustomActionControls.Description.Tip - Text.ConfigureCustomActionControls.DefaultValue - Text.ConfigureCustomActionControls.IsFolder - Text.ConfigureCustomActionControls.Label +- Text.ConfigureCustomActionControls.Options +- Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.Type - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges @@ -352,6 +470,7 @@ This document shows the translation status of each locale file in the repository - Text.Diff.Last - Text.Diff.Submodule.Deleted - Text.Diff.UseBlockNavigation +- Text.DirHistories - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.Fetch.Force @@ -374,6 +493,9 @@ This document shows the translation status of each locale file in the repository - Text.MergeMultiple.CommitChanges - Text.MergeMultiple.Strategy - Text.MergeMultiple.Targets +- Text.MoveSubmodule +- Text.MoveSubmodule.MoveTo +- Text.MoveSubmodule.Submodule - Text.Preferences.AI.Streaming - Text.Preferences.Appearance.EditorTabWidth - Text.Preferences.General.DateFormat @@ -408,6 +530,11 @@ This document shows the translation status of each locale file in the repository - Text.ResetWithoutCheckout - Text.ResetWithoutCheckout.MoveTo - Text.ResetWithoutCheckout.Target +- Text.SetSubmoduleBranch +- Text.SetSubmoduleBranch.Submodule +- Text.SetSubmoduleBranch.Current +- Text.SetSubmoduleBranch.New +- Text.SetSubmoduleBranch.New.Tip - Text.SetUpstream - Text.SetUpstream.Local - Text.SetUpstream.Unset @@ -416,12 +543,18 @@ This document shows the translation status of each locale file in the repository - Text.Stash.Mode - Text.StashCM.CopyMessage - Text.StashCM.SaveAsPatch +- Text.Submodule.Branch - Text.Submodule.Deinit +- Text.Submodule.Histories +- Text.Submodule.Move +- Text.Submodule.SetBranch +- Text.Submodule.SetURL - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited - Text.Submodule.Status.RevisionChanged - Text.Submodule.Status.Unmerged +- Text.Submodule.Update - Text.Submodule.URL - Text.TagCM.CustomAction - Text.ViewLogs @@ -443,7 +576,7 @@ This document shows the translation status of each locale file in the repository ### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) -### ![ta__IN](https://img.shields.io/badge/ta__IN-88.48%25-yellow) +### ![ta__IN](https://img.shields.io/badge/ta__IN-86.37%25-yellow)
Missing keys in ta_IN.axaml @@ -461,11 +594,15 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.WaitingForRange - Text.BranchCM.CompareWithCurrent - Text.BranchCM.ResetToSelectedCommit +- Text.ChangeSubmoduleUrl +- Text.ChangeSubmoduleUrl.Submodule +- Text.ChangeSubmoduleUrl.URL - Text.Checkout.RecurseSubmodules - Text.Checkout.WarnLostCommits - Text.Checkout.WithFastForward - Text.Checkout.WithFastForward.Upstream - Text.CommitCM.CopyAuthor +- Text.CommitCM.CopyCommitMessage - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject - Text.CommitCM.PushRevision @@ -481,10 +618,11 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.CheckedValue - Text.ConfigureCustomActionControls.CheckedValue.Tip - Text.ConfigureCustomActionControls.Description -- Text.ConfigureCustomActionControls.Description.Tip - Text.ConfigureCustomActionControls.DefaultValue - Text.ConfigureCustomActionControls.IsFolder - Text.ConfigureCustomActionControls.Label +- Text.ConfigureCustomActionControls.Options +- Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.Type - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges @@ -495,6 +633,7 @@ This document shows the translation status of each locale file in the repository - Text.DeinitSubmodule.Force - Text.DeinitSubmodule.Path - Text.Diff.Submodule.Deleted +- Text.DirHistories - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.GitFlow.FinishWithPush @@ -505,6 +644,9 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Merge.Edit +- Text.MoveSubmodule +- Text.MoveSubmodule.MoveTo +- Text.MoveSubmodule.Submodule - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Pull.RecurseSubmodules - Text.Push.Revision @@ -521,14 +663,25 @@ This document shows the translation status of each locale file in the repository - Text.ResetWithoutCheckout - Text.ResetWithoutCheckout.MoveTo - Text.ResetWithoutCheckout.Target +- Text.SetSubmoduleBranch +- Text.SetSubmoduleBranch.Submodule +- Text.SetSubmoduleBranch.Current +- Text.SetSubmoduleBranch.New +- Text.SetSubmoduleBranch.New.Tip - Text.Stash.Mode - Text.StashCM.CopyMessage +- Text.Submodule.Branch - Text.Submodule.Deinit +- Text.Submodule.Histories +- Text.Submodule.Move +- Text.Submodule.SetBranch +- Text.Submodule.SetURL - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited - Text.Submodule.Status.RevisionChanged - Text.Submodule.Status.Unmerged +- Text.Submodule.Update - Text.Submodule.URL - Text.TagCM.CustomAction - Text.UpdateSubmodules.Target @@ -546,7 +699,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-89.70%25-yellow) +### ![uk__UA](https://img.shields.io/badge/uk__UA-87.56%25-yellow)
Missing keys in uk_UA.axaml @@ -563,11 +716,15 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.Skip - Text.Bisect.WaitingForRange - Text.BranchCM.ResetToSelectedCommit +- Text.ChangeSubmoduleUrl +- Text.ChangeSubmoduleUrl.Submodule +- Text.ChangeSubmoduleUrl.URL - Text.Checkout.RecurseSubmodules - Text.Checkout.WarnLostCommits - Text.Checkout.WithFastForward - Text.Checkout.WithFastForward.Upstream - Text.CommitCM.CopyAuthor +- Text.CommitCM.CopyCommitMessage - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject - Text.CommitCM.PushRevision @@ -582,10 +739,11 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.CheckedValue - Text.ConfigureCustomActionControls.CheckedValue.Tip - Text.ConfigureCustomActionControls.Description -- Text.ConfigureCustomActionControls.Description.Tip - Text.ConfigureCustomActionControls.DefaultValue - Text.ConfigureCustomActionControls.IsFolder - Text.ConfigureCustomActionControls.Label +- Text.ConfigureCustomActionControls.Options +- Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.Type - Text.ConfigureWorkspace.Name - Text.CreateBranch.OverwriteExisting @@ -593,6 +751,7 @@ This document shows the translation status of each locale file in the repository - Text.DeinitSubmodule.Force - Text.DeinitSubmodule.Path - Text.Diff.Submodule.Deleted +- Text.DirHistories - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.GitFlow.FinishWithPush @@ -603,6 +762,9 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Merge.Edit +- Text.MoveSubmodule +- Text.MoveSubmodule.MoveTo +- Text.MoveSubmodule.Submodule - Text.Preferences.Git.IgnoreCRAtEOLInDiff - Text.Pull.RecurseSubmodules - Text.Push.Revision @@ -619,14 +781,25 @@ This document shows the translation status of each locale file in the repository - Text.ResetWithoutCheckout - Text.ResetWithoutCheckout.MoveTo - Text.ResetWithoutCheckout.Target +- Text.SetSubmoduleBranch +- Text.SetSubmoduleBranch.Submodule +- Text.SetSubmoduleBranch.Current +- Text.SetSubmoduleBranch.New +- Text.SetSubmoduleBranch.New.Tip - Text.Stash.Mode - Text.StashCM.CopyMessage +- Text.Submodule.Branch - Text.Submodule.Deinit +- Text.Submodule.Histories +- Text.Submodule.Move +- Text.Submodule.SetBranch +- Text.Submodule.SetURL - Text.Submodule.Status - Text.Submodule.Status.Modified - Text.Submodule.Status.NotInited - Text.Submodule.Status.RevisionChanged - Text.Submodule.Status.Unmerged +- Text.Submodule.Update - Text.Submodule.URL - Text.TagCM.CustomAction - Text.ViewLogs diff --git a/VERSION b/VERSION index 56ea42f8..3918dc69 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2025.24 \ No newline at end of file +2025.25 \ No newline at end of file diff --git a/screenshots/theme_dark.png b/screenshots/theme_dark.png index 85e18481..6e8af07b 100644 Binary files a/screenshots/theme_dark.png and b/screenshots/theme_dark.png differ diff --git a/screenshots/theme_light.png b/screenshots/theme_light.png index 2e8cf6fc..10988efd 100644 Binary files a/screenshots/theme_light.png and b/screenshots/theme_light.png differ diff --git a/src/App.Commands.cs b/src/App.Commands.cs index 22e9fb51..f26919c7 100644 --- a/src/App.Commands.cs +++ b/src/App.Commands.cs @@ -37,22 +37,21 @@ namespace SourceGit } } - public static readonly Command OpenPreferencesCommand = new Command(_ => ShowWindow(new Views.Preferences(), false)); - public static readonly Command OpenHotkeysCommand = new Command(_ => ShowWindow(new Views.Hotkeys(), false)); + public static readonly Command OpenPreferencesCommand = new Command(async _ => await ShowDialog(new Views.Preferences())); + public static readonly Command OpenHotkeysCommand = new Command(async _ => await ShowDialog(new Views.Hotkeys())); public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir)); - public static readonly Command OpenAboutCommand = new Command(_ => ShowWindow(new Views.About(), false)); + public static readonly Command OpenAboutCommand = new Command(async _ => await ShowDialog(new Views.About())); public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true)); public static readonly Command QuitCommand = new Command(_ => Quit(0)); - public static readonly Command CopyTextBlockCommand = new Command(p => + public static readonly Command CopyTextBlockCommand = new Command(async p => { - var textBlock = p as TextBlock; - if (textBlock == null) + if (p is not TextBlock textBlock) return; if (textBlock.Inlines is { Count: > 0 } inlines) - CopyText(inlines.Text); + await CopyTextAsync(inlines.Text); else if (!string.IsNullOrEmpty(textBlock.Text)) - CopyText(textBlock.Text); + await CopyTextAsync(textBlock.Text); }); } } diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs index 9cad0792..a2e40200 100644 --- a/src/App.JsonCodeGen.cs +++ b/src/App.JsonCodeGen.cs @@ -34,6 +34,20 @@ namespace SourceGit } } + public class DataGridLengthConverter : JsonConverter + { + public override DataGridLength Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var size = reader.GetDouble(); + return new DataGridLength(size, DataGridLengthUnitType.Pixel, 0, size); + } + + public override void Write(Utf8JsonWriter writer, DataGridLength value, JsonSerializerOptions options) + { + writer.WriteNumberValue(value.DisplayValue); + } + } + [JsonSourceGenerationOptions( WriteIndented = true, IgnoreReadOnlyFields = true, @@ -41,6 +55,7 @@ namespace SourceGit Converters = [ typeof(ColorConverter), typeof(GridLengthConverter), + typeof(DataGridLengthConverter), ] )] [JsonSerializable(typeof(Models.ExternalToolPaths))] diff --git a/src/App.axaml b/src/App.axaml index 186022d5..f4dc3d89 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -28,6 +28,7 @@ + diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 935e2e19..53c25325 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -83,88 +83,112 @@ namespace SourceGit if (ex == null) return; - var builder = new StringBuilder(); - builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n"); - builder.Append("----------------------------\n"); - builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n"); - builder.Append($"OS: {Environment.OSVersion}\n"); - builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n"); - builder.Append($"Source: {ex.Source}\n"); - builder.Append($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}\n"); - builder.Append($"User: {Environment.UserName}\n"); - builder.Append($"App Start Time: {Process.GetCurrentProcess().StartTime}\n"); - builder.Append($"Exception Time: {DateTime.Now}\n"); - builder.Append($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB\n"); - builder.Append("---------------------------\n\n"); - builder.Append(ex); - var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log"); - File.WriteAllText(file, builder.ToString()); + using var writer = new StreamWriter(file); + writer.WriteLine($"Crash::: {ex.GetType().FullName}: {ex.Message}"); + writer.WriteLine(); + writer.WriteLine("----------------------------"); + writer.WriteLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version}"); + writer.WriteLine($"OS: {Environment.OSVersion}"); + writer.WriteLine($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}"); + writer.WriteLine($"Source: {ex.Source}"); + writer.WriteLine($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}"); + writer.WriteLine($"User: {Environment.UserName}"); + writer.WriteLine($"App Start Time: {Process.GetCurrentProcess().StartTime}"); + writer.WriteLine($"Exception Time: {DateTime.Now}"); + writer.WriteLine($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB"); + writer.WriteLine("----------------------------"); + writer.WriteLine(); + writer.WriteLine(ex); + writer.Flush(); } #endregion #region Utility Functions - public static void ShowWindow(object data, bool showAsDialog) + public static object CreateViewForViewModel(object data) { - var impl = (Views.ChromelessWindow target, bool isDialog) => - { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) - { - if (isDialog) - target.ShowDialog(owner); - else - target.Show(owner); - } - else - { - target.Show(); - } - }; - - if (data is Views.ChromelessWindow window) - { - impl(window, showAsDialog); - return; - } - var dataTypeName = data.GetType().FullName; if (string.IsNullOrEmpty(dataTypeName) || !dataTypeName.Contains(".ViewModels.", StringComparison.Ordinal)) - return; + return null; var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views."); var viewType = Type.GetType(viewTypeName); - if (viewType == null || !viewType.IsSubclassOf(typeof(Views.ChromelessWindow))) - return; + if (viewType != null) + return Activator.CreateInstance(viewType); - window = Activator.CreateInstance(viewType) as Views.ChromelessWindow; + return null; + } + + public static Task ShowDialog(object data, Window owner = null) + { + if (owner == null) + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } mainWindow }) + owner = mainWindow; + else + return null; + } + + if (data is Views.ChromelessWindow window) + return window.ShowDialog(owner); + + window = CreateViewForViewModel(data) as Views.ChromelessWindow; if (window != null) { window.DataContext = data; - impl(window, showAsDialog); + return window.ShowDialog(owner); } + + return null; + } + + public static void ShowWindow(object data) + { + if (data is Views.ChromelessWindow window) + { + window.Show(); + return; + } + + window = CreateViewForViewModel(data) as Views.ChromelessWindow; + if (window != null) + { + window.DataContext = data; + window.Show(); + } + } + + public static async Task AskConfirmAsync(string message, Action onSure) + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var confirm = new Views.Confirm(); + confirm.Message.Text = message; + confirm.OnSure = onSure; + return await confirm.ShowDialog(owner); + } + + return false; } public static void RaiseException(string context, string message) { - if (Current is App app && app._launcher != null) + if (Current is App { _launcher: not null } app) app._launcher.DispatchNotification(context, message, true); } public static void SendNotification(string context, string message) { - if (Current is App app && app._launcher != null) + if (Current is App { _launcher: not null } app) app._launcher.DispatchNotification(context, message, false); } public static void SetLocale(string localeKey) { - var app = Current as App; - if (app == null) - return; - - var targetLocale = app.Resources[localeKey] as ResourceDictionary; - if (targetLocale == null || targetLocale == app._activeLocale) + if (Current is not App app || + app.Resources[localeKey] is not ResourceDictionary targetLocale || + targetLocale == app._activeLocale) return; if (app._activeLocale != null) @@ -176,8 +200,7 @@ namespace SourceGit public static void SetTheme(string theme, string themeOverridesFile) { - var app = Current as App; - if (app == null) + if (Current is not App app) return; if (theme.Equals("Light", StringComparison.OrdinalIgnoreCase)) @@ -230,8 +253,7 @@ namespace SourceGit public static void SetFonts(string defaultFont, string monospaceFont, bool onlyUseMonospaceFontInEditor) { - var app = Current as App; - if (app == null) + if (Current is not App app) return; if (app._fontsOverrides != null) @@ -283,24 +305,16 @@ namespace SourceGit } } - public static async void CopyText(string data) + public static async Task CopyTextAsync(string data) { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - if (desktop.MainWindow?.Clipboard is { } clipboard) - await clipboard.SetTextAsync(data ?? ""); - } + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow.Clipboard: { } clipboard }) + await clipboard.SetTextAsync(data ?? ""); } public static async Task GetClipboardTextAsync() { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - if (desktop.MainWindow?.Clipboard is { } clipboard) - { - return await clipboard.GetTextAsync(); - } - } + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow.Clipboard: { } clipboard }) + return await clipboard.GetTextAsync(); return null; } @@ -436,33 +450,33 @@ namespace SourceGit return true; var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection); - var lines = new List(); + using var writer = new StreamWriter(file); foreach (var job in collection.Jobs) { switch (job.Action) { case Models.InteractiveRebaseAction.Pick: - lines.Add($"p {job.SHA}"); + writer.WriteLine($"p {job.SHA}"); break; case Models.InteractiveRebaseAction.Edit: - lines.Add($"e {job.SHA}"); + writer.WriteLine($"e {job.SHA}"); break; case Models.InteractiveRebaseAction.Reword: - lines.Add($"r {job.SHA}"); + writer.WriteLine($"r {job.SHA}"); break; case Models.InteractiveRebaseAction.Squash: - lines.Add($"s {job.SHA}"); + writer.WriteLine($"s {job.SHA}"); break; case Models.InteractiveRebaseAction.Fixup: - lines.Add($"f {job.SHA}"); + writer.WriteLine($"f {job.SHA}"); break; default: - lines.Add($"d {job.SHA}"); + writer.WriteLine($"d {job.SHA}"); break; } } - File.WriteAllLines(file, lines); + writer.Flush(); exitCode = 0; return true; @@ -561,7 +575,7 @@ namespace SourceGit Models.AvatarManager.Instance.Start(); string startupRepo = null; - if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0])) + if (desktop.Args is { Length: 1 } && Directory.Exists(desktop.Args[0])) startupRepo = desktop.Args[0]; var pref = ViewModels.Preferences.Instance; @@ -581,7 +595,7 @@ namespace SourceGit { if (!string.IsNullOrEmpty(repo) && Directory.Exists(repo)) { - var test = new Commands.QueryRepositoryRootPath(repo).ReadToEnd(); + var test = new Commands.QueryRepositoryRootPath(repo).GetResultAsync().Result; if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut)) { Dispatcher.UIThread.Invoke(() => @@ -648,9 +662,9 @@ namespace SourceGit private void ShowSelfUpdateResult(object data) { - Dispatcher.UIThread.Post(() => + Dispatcher.UIThread.Post(async () => { - ShowWindow(new ViewModels.SelfUpdate() { Data = data }, true); + await ShowDialog(new ViewModels.SelfUpdate { Data = data }); }); } diff --git a/src/Commands/Blame.cs b/src/Commands/Blame.cs index 1fc51fa4..0d55a051 100644 --- a/src/Commands/Blame.cs +++ b/src/Commands/Blame.cs @@ -1,6 +1,7 @@ using System; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -19,9 +20,9 @@ namespace SourceGit.Commands _result.File = file; } - public Models.BlameData Result() + public async Task ReadAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return _result; @@ -39,9 +40,7 @@ namespace SourceGit.Commands foreach (var line in _result.LineInfos) { if (line.CommitSHA.Length > _minSHALen) - { line.CommitSHA = line.CommitSHA.Substring(0, _minSHALen); - } } } diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs index f207e976..a5e3208e 100644 --- a/src/Commands/Branch.cs +++ b/src/Commands/Branch.cs @@ -1,19 +1,11 @@ using System.Text; +using System.Threading.Tasks; namespace SourceGit.Commands { public static class Branch { - public static string ShowCurrent(string repo) - { - var cmd = new Command(); - cmd.WorkingDirectory = repo; - cmd.Context = repo; - cmd.Args = "branch --show-current"; - return cmd.ReadToEnd().StdOut.Trim(); - } - - public static bool Create(string repo, string name, string basedOn, bool force, Models.ICommandLog log) + public static async Task CreateAsync(string repo, string name, string basedOn, bool force, Models.ICommandLog log) { var builder = new StringBuilder(); builder.Append("branch "); @@ -28,20 +20,20 @@ namespace SourceGit.Commands cmd.Context = repo; cmd.Args = builder.ToString(); cmd.Log = log; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } - public static bool Rename(string repo, string name, string to, Models.ICommandLog log) + public static async Task RenameAsync(string repo, string name, string to, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"branch -M {name} {to}"; cmd.Log = log; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } - public static bool SetUpstream(string repo, string name, string upstream, Models.ICommandLog log) + public static async Task SetUpstreamAsync(string repo, string name, string upstream, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; @@ -53,31 +45,31 @@ namespace SourceGit.Commands else cmd.Args = $"branch {name} -u {upstream}"; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } - public static bool DeleteLocal(string repo, string name, Models.ICommandLog log) + public static async Task DeleteLocalAsync(string repo, string name, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"branch -D {name}"; cmd.Log = log; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } - public static bool DeleteRemote(string repo, string remote, string name, Models.ICommandLog log) + public static async Task DeleteRemoteAsync(string repo, string remote, string name, Models.ICommandLog log) { - bool exists = new Remote(repo).HasBranch(remote, name); + bool exists = await new Remote(repo).HasBranchAsync(remote, name).ConfigureAwait(false); if (exists) - return new Push(repo, remote, $"refs/heads/{name}", true) { Log = log }.Exec(); + return await new Push(repo, remote, $"refs/heads/{name}", true) { Log = log }.RunAsync().ConfigureAwait(false); var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"branch -D -r {remote}/{name}"; cmd.Log = log; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } } } diff --git a/src/Commands/Checkout.cs b/src/Commands/Checkout.cs index d2876740..1aeff870 100644 --- a/src/Commands/Checkout.cs +++ b/src/Commands/Checkout.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -11,7 +12,7 @@ namespace SourceGit.Commands Context = repo; } - public bool Branch(string branch, bool force) + public async Task BranchAsync(string branch, bool force) { var builder = new StringBuilder(); builder.Append("checkout --progress "); @@ -20,10 +21,10 @@ namespace SourceGit.Commands builder.Append(branch); Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Branch(string branch, string basedOn, bool force, bool allowOverwrite) + public async Task BranchAsync(string branch, string basedOn, bool force, bool allowOverwrite) { var builder = new StringBuilder(); builder.Append("checkout --progress "); @@ -35,17 +36,17 @@ namespace SourceGit.Commands builder.Append(basedOn); Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Commit(string commitId, bool force) + public async Task CommitAsync(string commitId, bool force) { var option = force ? "--force" : string.Empty; Args = $"checkout {option} --detach --progress {commitId}"; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool UseTheirs(List files) + public async Task UseTheirsAsync(List files) { var builder = new StringBuilder(); builder.Append("checkout --theirs --"); @@ -56,10 +57,10 @@ namespace SourceGit.Commands builder.Append("\""); } Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool UseMine(List files) + public async Task UseMineAsync(List files) { var builder = new StringBuilder(); builder.Append("checkout --ours --"); @@ -69,14 +70,15 @@ namespace SourceGit.Commands builder.Append(f); builder.Append("\""); } + Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool FileWithRevision(string file, string revision) + public async Task FileWithRevisionAsync(string file, string revision) { Args = $"checkout --no-overlay {revision} -- \"{file}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } } } diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index a5ec0b56..12fcb72f 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -4,18 +4,19 @@ using System.Diagnostics; using System.Text; using System.Text.RegularExpressions; using System.Threading; - -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { public partial class Command { - public class ReadToEndResult + public class Result { public bool IsSuccess { get; set; } = false; - public string StdOut { get; set; } = ""; - public string StdErr { get; set; } = ""; + public string StdOut { get; set; } = string.Empty; + public string StdErr { get; set; } = string.Empty; + + public static Result Failed(string reason) => new Result() { StdErr = reason }; } public enum EditorType @@ -26,15 +27,17 @@ namespace SourceGit.Commands } public string Context { get; set; } = string.Empty; - public CancellationToken CancellationToken { get; set; } = CancellationToken.None; public string WorkingDirectory { get; set; } = null; - public EditorType Editor { get; set; } = EditorType.CoreEditor; // Only used in Exec() mode + public EditorType Editor { get; set; } = EditorType.CoreEditor; public string SSHKey { get; set; } = string.Empty; public string Args { get; set; } = string.Empty; + + // Only used in `ExecAsync` mode. + public CancellationToken CancellationToken { get; set; } = CancellationToken.None; public bool RaiseError { get; set; } = true; public Models.ICommandLog Log { get; set; } = null; - public bool Exec() + public async Task ExecAsync() { Log?.AppendLine($"$ git {Args}\n"); @@ -45,7 +48,7 @@ namespace SourceGit.Commands proc.OutputDataReceived += (_, e) => HandleOutput(e.Data, errs); proc.ErrorDataReceived += (_, e) => HandleOutput(e.Data, errs); - var dummy = null as Process; + Process dummy = null; var dummyProcLock = new object(); try { @@ -68,7 +71,7 @@ namespace SourceGit.Commands catch (Exception e) { if (RaiseError) - Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message)); + App.RaiseException(Context, e.Message); Log?.AppendLine(string.Empty); return false; @@ -76,7 +79,15 @@ namespace SourceGit.Commands proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); - proc.WaitForExit(); + + try + { + await proc.WaitForExitAsync(CancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + HandleOutput(e.Message, errs); + } if (dummy != null) { @@ -96,7 +107,7 @@ namespace SourceGit.Commands { var errMsg = string.Join("\n", errs).Trim(); if (!string.IsNullOrEmpty(errMsg)) - Dispatcher.UIThread.Post(() => App.RaiseException(Context, errMsg)); + App.RaiseException(Context, errMsg); } return false; @@ -105,7 +116,7 @@ namespace SourceGit.Commands return true; } - public ReadToEndResult ReadToEnd() + protected async Task ReadToEndAsync() { var start = CreateGitStartInfo(); var proc = new Process() { StartInfo = start }; @@ -116,24 +127,16 @@ namespace SourceGit.Commands } catch (Exception e) { - return new ReadToEndResult() - { - IsSuccess = false, - StdOut = string.Empty, - StdErr = e.Message, - }; + return Result.Failed(e.Message); } - var rs = new ReadToEndResult() - { - StdOut = proc.StandardOutput.ReadToEnd(), - StdErr = proc.StandardError.ReadToEnd(), - }; + var rs = new Result() { IsSuccess = true }; + rs.StdOut = await proc.StandardOutput.ReadToEndAsync(CancellationToken).ConfigureAwait(false); + rs.StdErr = await proc.StandardError.ReadToEndAsync(CancellationToken).ConfigureAwait(false); + await proc.WaitForExitAsync(CancellationToken).ConfigureAwait(false); - proc.WaitForExit(); rs.IsSuccess = proc.ExitCode == 0; proc.Close(); - return rs; } diff --git a/src/Commands/Commit.cs b/src/Commands/Commit.cs index 1585e7e3..02c4ba89 100644 --- a/src/Commands/Commit.cs +++ b/src/Commands/Commit.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -7,7 +8,7 @@ namespace SourceGit.Commands public Commit(string repo, string message, bool signOff, bool amend, bool resetAuthor) { _tmpFile = Path.GetTempFileName(); - File.WriteAllText(_tmpFile, message); + _message = message; WorkingDirectory = repo; Context = repo; @@ -18,22 +19,22 @@ namespace SourceGit.Commands Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit"; } - public bool Run() + public async Task RunAsync() { - var succ = Exec(); - try { + await File.WriteAllTextAsync(_tmpFile, _message).ConfigureAwait(false); + var succ = await ExecAsync().ConfigureAwait(false); File.Delete(_tmpFile); + return succ; } catch { - // Ignore + return false; } - - return succ; } - private readonly string _tmpFile; + private readonly string _tmpFile = string.Empty; + private readonly string _message = string.Empty; } } diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs index c88e087a..634d98c9 100644 --- a/src/Commands/CompareRevisions.cs +++ b/src/Commands/CompareRevisions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -29,21 +30,22 @@ namespace SourceGit.Commands Args = $"diff --name-status {based} {end} -- \"{path}\""; } - public List Result() + public async Task> ReadAsync() { - var rs = ReadToEnd(); + var changes = new List(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) - return _changes; + return changes; var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) - ParseLine(line); + ParseLine(changes, line); - _changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); - return _changes; + changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); + return changes; } - private void ParseLine(string line) + private void ParseLine(List outs, string line) { var match = REG_FORMAT().Match(line); if (!match.Success) @@ -53,7 +55,7 @@ namespace SourceGit.Commands { var renamed = new Models.Change() { Path = match.Groups[1].Value }; renamed.Set(Models.ChangeState.Renamed); - _changes.Add(renamed); + outs.Add(renamed); } return; @@ -66,23 +68,21 @@ namespace SourceGit.Commands { case 'M': change.Set(Models.ChangeState.Modified); - _changes.Add(change); + outs.Add(change); break; case 'A': change.Set(Models.ChangeState.Added); - _changes.Add(change); + outs.Add(change); break; case 'D': change.Set(Models.ChangeState.Deleted); - _changes.Add(change); + outs.Add(change); break; case 'C': change.Set(Models.ChangeState.Copied); - _changes.Add(change); + outs.Add(change); break; } } - - private readonly List _changes = new List(); } } diff --git a/src/Commands/Config.cs b/src/Commands/Config.cs index 49e8fcb7..78057b22 100644 --- a/src/Commands/Config.cs +++ b/src/Commands/Config.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -17,15 +18,13 @@ namespace SourceGit.Commands Context = repository; _isLocal = true; } - - RaiseError = false; } - public Dictionary ListAll() + public async Task> ReadAllAsync() { Args = "config -l"; - var output = ReadToEnd(); + var output = await ReadToEndAsync().ConfigureAwait(false); var rs = new Dictionary(); if (output.IsSuccess) { @@ -45,13 +44,15 @@ namespace SourceGit.Commands return rs; } - public string Get(string key) + public async Task GetAsync(string key) { Args = $"config {key}"; - return ReadToEnd().StdOut.Trim(); + + var rs = await ReadToEndAsync().ConfigureAwait(false); + return rs.StdOut.Trim(); } - public bool Set(string key, string value, bool allowEmpty = false) + public async Task SetAsync(string key, string value, bool allowEmpty = false) { var scope = _isLocal ? "--local" : "--global"; @@ -60,7 +61,7 @@ namespace SourceGit.Commands else Args = $"config {scope} {key} \"{value}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } private bool _isLocal = false; diff --git a/src/Commands/CountLocalChangesWithoutUntracked.cs b/src/Commands/CountLocalChangesWithoutUntracked.cs index a704f313..769d732e 100644 --- a/src/Commands/CountLocalChangesWithoutUntracked.cs +++ b/src/Commands/CountLocalChangesWithoutUntracked.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -11,9 +12,9 @@ namespace SourceGit.Commands Args = "--no-optional-locks status -uno --ignore-submodules=all --porcelain"; } - public int Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (rs.IsSuccess) { var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index 6af0a3cc..c4785e18 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -35,9 +36,9 @@ namespace SourceGit.Commands Args = $"diff --no-ext-diff --patch --unified={unified} {opt}"; } - public Models.DiffResult Result() + public async Task ReadAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); var start = 0; var end = rs.StdOut.IndexOf('\n', start); while (end > 0) @@ -248,14 +249,10 @@ namespace SourceGit.Commands foreach (var chunk in chunks) { if (chunk.DeletedCount > 0) - { left.Highlights.Add(new Models.TextInlineRange(chunk.DeletedStart, chunk.DeletedCount)); - } if (chunk.AddedCount > 0) - { right.Highlights.Add(new Models.TextInlineRange(chunk.AddedStart, chunk.AddedCount)); - } } } } diff --git a/src/Commands/Discard.cs b/src/Commands/Discard.cs index f36ca6c9..73899d52 100644 --- a/src/Commands/Discard.cs +++ b/src/Commands/Discard.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; using System.IO; - -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -14,9 +13,9 @@ namespace SourceGit.Commands /// /// /// - public static void All(string repo, bool includeIgnored, Models.ICommandLog log) + public static async Task AllAsync(string repo, bool includeIgnored, Models.ICommandLog log) { - var changes = new QueryLocalChanges(repo).Result(); + var changes = await new QueryLocalChanges(repo).GetResultAsync().ConfigureAwait(false); try { foreach (var c in changes) @@ -36,16 +35,13 @@ namespace SourceGit.Commands } catch (Exception e) { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}"); - }); + App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}"); } - new Reset(repo, "HEAD", "--hard") { Log = log }.Exec(); + await new Reset(repo, "HEAD", "--hard") { Log = log }.ExecAsync().ConfigureAwait(false); if (includeIgnored) - new Clean(repo) { Log = log }.Exec(); + await new Clean(repo) { Log = log }.ExecAsync().ConfigureAwait(false); } /// @@ -54,7 +50,7 @@ namespace SourceGit.Commands /// /// /// - public static void Changes(string repo, List changes, Models.ICommandLog log) + public static async Task ChangesAsync(string repo, List changes, Models.ICommandLog log) { var restores = new List(); @@ -78,17 +74,14 @@ namespace SourceGit.Commands } catch (Exception e) { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}"); - }); + App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}"); } if (restores.Count > 0) { var pathSpecFile = Path.GetTempFileName(); - File.WriteAllLines(pathSpecFile, restores); - new Restore(repo, pathSpecFile, false) { Log = log }.Exec(); + await File.WriteAllLinesAsync(pathSpecFile, restores).ConfigureAwait(false); + await new Restore(repo, pathSpecFile, false) { Log = log }.ExecAsync().ConfigureAwait(false); File.Delete(pathSpecFile); } } diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs index edf2a6dd..d25cc80c 100644 --- a/src/Commands/Fetch.cs +++ b/src/Commands/Fetch.cs @@ -1,12 +1,15 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class Fetch : Command { public Fetch(string repo, string remote, bool noTags, bool force) { + _remoteKey = $"remote.{remote}.sshkey"; + WorkingDirectory = repo; Context = repo; - SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); Args = "fetch --progress --verbose "; if (noTags) @@ -18,14 +21,24 @@ Args += "--force "; Args += remote; + } public Fetch(string repo, Models.Branch local, Models.Branch remote) { + _remoteKey = $"remote.{remote.Remote}.sshkey"; + WorkingDirectory = repo; Context = repo; - SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey"); Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}"; } + + public async Task RunAsync() + { + SSHKey = await new Config(WorkingDirectory).GetAsync(_remoteKey).ConfigureAwait(false); + return await ExecAsync().ConfigureAwait(false); + } + + private readonly string _remoteKey; } } diff --git a/src/Commands/GenerateCommitMessage.cs b/src/Commands/GenerateCommitMessage.cs index df61fdd2..9b6fc4ff 100644 --- a/src/Commands/GenerateCommitMessage.cs +++ b/src/Commands/GenerateCommitMessage.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; - -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -20,6 +19,11 @@ namespace SourceGit.Commands Context = repo; Args = $"diff --diff-algorithm=minimal {opt}"; } + + public async Task ReadAsync() + { + return await ReadToEndAsync().ConfigureAwait(false); + } } public GenerateCommitMessage(Models.OpenAIService service, string repo, List changes, CancellationToken cancelToken, Action onResponse) @@ -31,7 +35,7 @@ namespace SourceGit.Commands _onResponse = onResponse; } - public void Exec() + public async Task ExecAsync() { try { @@ -47,10 +51,10 @@ namespace SourceGit.Commands responseBuilder.Append("- "); summaryBuilder.Append("- "); - var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd(); + var rs = await new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadAsync(); if (rs.IsSuccess) { - _service.Chat( + await _service.ChatAsync( _service.AnalyzeDiffPrompt, $"Here is the `git diff` output: {rs.StdOut}", _cancelToken, @@ -74,7 +78,7 @@ namespace SourceGit.Commands var responseBody = responseBuilder.ToString(); var subjectBuilder = new StringBuilder(); - _service.Chat( + await _service.ChatAsync( _service.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summaryBuilder}", _cancelToken, @@ -86,7 +90,7 @@ namespace SourceGit.Commands } catch (Exception e) { - Dispatcher.UIThread.Post(() => App.RaiseException(_repo, $"Failed to generate commit message: {e}")); + App.RaiseException(_repo, $"Failed to generate commit message: {e}"); } } diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs index 1d33fa3a..1f492b89 100644 --- a/src/Commands/GitFlow.cs +++ b/src/Commands/GitFlow.cs @@ -1,31 +1,31 @@ using System.Text; -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { public static class GitFlow { - public static bool Init(string repo, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log) + public static async Task InitAsync(string repo, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log) { var config = new Config(repo); - config.Set("gitflow.branch.master", master); - config.Set("gitflow.branch.develop", develop); - config.Set("gitflow.prefix.feature", feature); - config.Set("gitflow.prefix.bugfix", "bugfix/"); - config.Set("gitflow.prefix.release", release); - config.Set("gitflow.prefix.hotfix", hotfix); - config.Set("gitflow.prefix.support", "support/"); - config.Set("gitflow.prefix.versiontag", version, true); + await config.SetAsync("gitflow.branch.master", master).ConfigureAwait(false); + await config.SetAsync("gitflow.branch.develop", develop).ConfigureAwait(false); + await config.SetAsync("gitflow.prefix.feature", feature).ConfigureAwait(false); + await config.SetAsync("gitflow.prefix.bugfix", "bugfix/").ConfigureAwait(false); + await config.SetAsync("gitflow.prefix.release", release).ConfigureAwait(false); + await config.SetAsync("gitflow.prefix.hotfix", hotfix).ConfigureAwait(false); + await config.SetAsync("gitflow.prefix.support", "support/").ConfigureAwait(false); + await config.SetAsync("gitflow.prefix.versiontag", version, true).ConfigureAwait(false); var init = new Command(); init.WorkingDirectory = repo; init.Context = repo; init.Args = "flow init -d"; init.Log = log; - return init.Exec(); + return await init.ExecAsync().ConfigureAwait(false); } - public static bool Start(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log) + public static async Task StartAsync(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log) { var start = new Command(); start.WorkingDirectory = repo; @@ -43,15 +43,15 @@ namespace SourceGit.Commands start.Args = $"flow hotfix start {name}"; break; default: - Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!")); + App.RaiseException(repo, "Bad git-flow branch type!!!"); return false; } start.Log = log; - return start.Exec(); + return await start.ExecAsync().ConfigureAwait(false); } - public static bool Finish(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log) + public static async Task FinishAsync(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log) { var builder = new StringBuilder(); builder.Append("flow "); @@ -68,7 +68,7 @@ namespace SourceGit.Commands builder.Append("hotfix"); break; default: - Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!")); + App.RaiseException(repo, "Bad git-flow branch type!!!"); return false; } @@ -86,7 +86,7 @@ namespace SourceGit.Commands finish.Context = repo; finish.Args = builder.ToString(); finish.Log = log; - return finish.Exec(); + return await finish.ExecAsync().ConfigureAwait(false); } } } diff --git a/src/Commands/IsBareRepository.cs b/src/Commands/IsBareRepository.cs index f92d0888..98b127ce 100644 --- a/src/Commands/IsBareRepository.cs +++ b/src/Commands/IsBareRepository.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -10,14 +11,14 @@ namespace SourceGit.Commands Args = "rev-parse --is-bare-repository"; } - public bool Result() + public async Task GetResultAsync() { if (!Directory.Exists(Path.Combine(WorkingDirectory, "refs")) || !Directory.Exists(Path.Combine(WorkingDirectory, "objects")) || !File.Exists(Path.Combine(WorkingDirectory, "HEAD"))) return false; - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); return rs.IsSuccess && rs.StdOut.Trim() == "true"; } } diff --git a/src/Commands/IsBinary.cs b/src/Commands/IsBinary.cs index af8f54bb..5c3e5673 100644 --- a/src/Commands/IsBinary.cs +++ b/src/Commands/IsBinary.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -15,9 +16,10 @@ namespace SourceGit.Commands RaiseError = false; } - public bool Result() + public async Task GetResultAsync() { - return REG_TEST().IsMatch(ReadToEnd().StdOut); + var rs = await ReadToEndAsync().ConfigureAwait(false); + return REG_TEST().IsMatch(rs.StdOut.Trim()); } } } diff --git a/src/Commands/IsCommitSHA.cs b/src/Commands/IsCommitSHA.cs index 1b0c50e3..dcf9b1a9 100644 --- a/src/Commands/IsCommitSHA.cs +++ b/src/Commands/IsCommitSHA.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class IsCommitSHA : Command { @@ -8,9 +10,9 @@ Args = $"cat-file -t {hash}"; } - public bool Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); return rs.IsSuccess && rs.StdOut.Trim().Equals("commit"); } } diff --git a/src/Commands/IsConflictResolved.cs b/src/Commands/IsConflictResolved.cs index 9b243451..2d53766a 100644 --- a/src/Commands/IsConflictResolved.cs +++ b/src/Commands/IsConflictResolved.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class IsConflictResolved : Command { @@ -11,9 +13,10 @@ Args = $"diff -a --ignore-cr-at-eol --check {opt}"; } - public bool Result() + public async Task GetResultAsync() { - return ReadToEnd().IsSuccess; + var rs = await ReadToEndAsync().ConfigureAwait(false); + return rs.IsSuccess; } } } diff --git a/src/Commands/IsLFSFiltered.cs b/src/Commands/IsLFSFiltered.cs index 2a7234bb..ee716761 100644 --- a/src/Commands/IsLFSFiltered.cs +++ b/src/Commands/IsLFSFiltered.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class IsLFSFiltered : Command { @@ -18,9 +20,9 @@ RaiseError = false; } - public bool Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); return rs.IsSuccess && rs.StdOut.Contains("filter\0lfs"); } } diff --git a/src/Commands/LFS.cs b/src/Commands/LFS.cs index 18d2ba93..30f06e5f 100644 --- a/src/Commands/LFS.cs +++ b/src/Commands/LFS.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -19,6 +20,11 @@ namespace SourceGit.Commands Args = args; Log = log; } + + public async Task ReadAsync() + { + return await ReadToEndAsync().ConfigureAwait(false); + } } public LFS(string repo) @@ -36,42 +42,42 @@ namespace SourceGit.Commands return content.Contains("git lfs pre-push"); } - public bool Install(Models.ICommandLog log) + public async Task InstallAsync(Models.ICommandLog log) { - return new SubCmd(_repo, "lfs install --local", log).Exec(); + return await new SubCmd(_repo, "lfs install --local", log).ExecAsync().ConfigureAwait(false); } - public bool Track(string pattern, bool isFilenameMode, Models.ICommandLog log) + public async Task TrackAsync(string pattern, bool isFilenameMode, Models.ICommandLog log) { var opt = isFilenameMode ? "--filename" : ""; - return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", log).Exec(); + return await new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", log).ExecAsync().ConfigureAwait(false); } - public void Fetch(string remote, Models.ICommandLog log) + public async Task FetchAsync(string remote, Models.ICommandLog log) { - new SubCmd(_repo, $"lfs fetch {remote}", log).Exec(); + await new SubCmd(_repo, $"lfs fetch {remote}", log).ExecAsync().ConfigureAwait(false); } - public void Pull(string remote, Models.ICommandLog log) + public async Task PullAsync(string remote, Models.ICommandLog log) { - new SubCmd(_repo, $"lfs pull {remote}", log).Exec(); + await new SubCmd(_repo, $"lfs pull {remote}", log).ExecAsync().ConfigureAwait(false); } - public void Push(string remote, Models.ICommandLog log) + public async Task PushAsync(string remote, Models.ICommandLog log) { - new SubCmd(_repo, $"lfs push {remote}", log).Exec(); + await new SubCmd(_repo, $"lfs push {remote}", log).ExecAsync().ConfigureAwait(false); } - public void Prune(Models.ICommandLog log) + public async Task PruneAsync(Models.ICommandLog log) { - new SubCmd(_repo, "lfs prune", log).Exec(); + await new SubCmd(_repo, "lfs prune", log).ExecAsync().ConfigureAwait(false); } - public List Locks(string remote) + public async Task> GetLocksAsync(string remote) { var locks = new List(); var cmd = new SubCmd(_repo, $"lfs locks --remote={remote}", null); - var rs = cmd.ReadToEnd(); + var rs = await cmd.ReadAsync().ConfigureAwait(false); if (rs.IsSuccess) { var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); @@ -93,21 +99,21 @@ namespace SourceGit.Commands return locks; } - public bool Lock(string remote, string file, Models.ICommandLog log) + public async Task LockAsync(string remote, string file, Models.ICommandLog log) { - return new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", log).Exec(); + return await new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", log).ExecAsync().ConfigureAwait(false); } - public bool Unlock(string remote, string file, bool force, Models.ICommandLog log) + public async Task UnlockAsync(string remote, string file, bool force, Models.ICommandLog log) { var opt = force ? "-f" : ""; - return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", log).Exec(); + return await new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", log).ExecAsync().ConfigureAwait(false); } - public bool Unlock(string remote, long id, bool force, Models.ICommandLog log) + public async Task UnlockAsync(string remote, long id, bool force, Models.ICommandLog log) { var opt = force ? "-f" : ""; - return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", log).Exec(); + return await new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", log).ExecAsync().ConfigureAwait(false); } private readonly string _repo; diff --git a/src/Commands/MergeTool.cs b/src/Commands/MergeTool.cs index fc6d0d75..7cf0a22c 100644 --- a/src/Commands/MergeTool.cs +++ b/src/Commands/MergeTool.cs @@ -1,12 +1,11 @@ using System.IO; - -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { public static class MergeTool { - public static bool OpenForMerge(string repo, int toolType, string toolPath, string file) + public static async Task OpenForMergeAsync(string repo, int toolType, string toolPath, string file) { var cmd = new Command(); cmd.WorkingDirectory = repo; @@ -19,27 +18,27 @@ namespace SourceGit.Commands if (toolType == 0) { cmd.Args = $"mergetool {fileArg}"; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } if (!File.Exists(toolPath)) { - Dispatcher.UIThread.Post(() => App.RaiseException(repo, $"Can NOT find external merge tool in '{toolPath}'!")); + App.RaiseException(repo, $"Can NOT find external merge tool in '{toolPath}'!"); return false; } var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType); if (supported == null) { - Dispatcher.UIThread.Post(() => App.RaiseException(repo, "Invalid merge tool in preference setting!")); + App.RaiseException(repo, "Invalid merge tool in preference setting!"); return false; } cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit {fileArg}"; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } - public static bool OpenForDiff(string repo, int toolType, string toolPath, Models.DiffOption option) + public static async Task OpenForDiffAsync(string repo, int toolType, string toolPath, Models.DiffOption option) { var cmd = new Command(); cmd.WorkingDirectory = repo; @@ -49,24 +48,24 @@ namespace SourceGit.Commands if (toolType == 0) { cmd.Args = $"difftool -g --no-prompt {option}"; - return cmd.Exec(); + return await cmd.ExecAsync(); } if (!File.Exists(toolPath)) { - Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT find external diff tool in '{toolPath}'!")); + App.RaiseException(repo, $"Can NOT find external diff tool in '{toolPath}'!"); return false; } var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType); if (supported == null) { - Dispatcher.UIThread.Post(() => App.RaiseException(repo, "Invalid merge tool in preference setting!")); + App.RaiseException(repo, "Invalid merge tool in preference setting!"); return false; } cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.DiffCmd}\" difftool --tool=sourcegit --no-prompt {option}"; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } } } diff --git a/src/Commands/Move.cs b/src/Commands/Move.cs new file mode 100644 index 00000000..ca719cd7 --- /dev/null +++ b/src/Commands/Move.cs @@ -0,0 +1,25 @@ +using System.Text; + +namespace SourceGit.Commands +{ + public class Move : Command + { + public Move(string repo, string oldPath, string newPath, bool force) + { + WorkingDirectory = repo; + Context = repo; + + var builder = new StringBuilder(); + builder.Append("mv -v "); + if (force) + builder.Append("-f "); + builder.Append('"'); + builder.Append(oldPath); + builder.Append("\" \""); + builder.Append(newPath); + builder.Append('"'); + + Args = builder.ToString(); + } + } +} diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs index 698fbfce..93896c75 100644 --- a/src/Commands/Pull.cs +++ b/src/Commands/Pull.cs @@ -1,12 +1,15 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class Pull : Command { public Pull(string repo, string remote, string branch, bool useRebase) { + _remote = remote; + WorkingDirectory = repo; Context = repo; - SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); Args = "pull --verbose --progress "; if (useRebase) @@ -14,5 +17,13 @@ Args += $"{remote} {branch}"; } + + public async Task RunAsync() + { + SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.sshkey").ConfigureAwait(false); + return await ExecAsync().ConfigureAwait(false); + } + + private readonly string _remote; } } diff --git a/src/Commands/Push.cs b/src/Commands/Push.cs index 8a5fe33c..b822af46 100644 --- a/src/Commands/Push.cs +++ b/src/Commands/Push.cs @@ -1,12 +1,15 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class Push : Command { public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force) { + _remote = remote; + WorkingDirectory = repo; Context = repo; - SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); Args = "push --progress --verbose "; if (withTags) @@ -23,9 +26,10 @@ public Push(string repo, string remote, string refname, bool isDelete) { + _remote = remote; + WorkingDirectory = repo; Context = repo; - SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); Args = "push "; if (isDelete) @@ -33,5 +37,13 @@ Args += $"{remote} {refname}"; } + + public async Task RunAsync() + { + SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.sshkey").ConfigureAwait(false); + return await ExecAsync().ConfigureAwait(false); + } + + private readonly string _remote; } } diff --git a/src/Commands/QueryAssumeUnchangedFiles.cs b/src/Commands/QueryAssumeUnchangedFiles.cs index b5c23b0b..0fa0f8d0 100644 --- a/src/Commands/QueryAssumeUnchangedFiles.cs +++ b/src/Commands/QueryAssumeUnchangedFiles.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -16,10 +17,10 @@ namespace SourceGit.Commands RaiseError = false; } - public List Result() + public async Task> GetResultAsync() { var outs = new List(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs index f268d709..e8a8372b 100644 --- a/src/Commands/QueryBranches.cs +++ b/src/Commands/QueryBranches.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -17,12 +18,10 @@ namespace SourceGit.Commands Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\""; } - public List Result(out int localBranchesCount) + public async Task> GetResultAsync() { - localBranchesCount = 0; - var branches = new List(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return branches; @@ -36,8 +35,6 @@ namespace SourceGit.Commands branches.Add(b); if (!b.IsLocal) remoteHeads.Add(b.FullName, b.Head); - else - localBranchesCount++; } } @@ -48,7 +45,7 @@ namespace SourceGit.Commands if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead)) { b.IsUpstreamGone = false; - b.TrackStatus ??= new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).Result(); + b.TrackStatus ??= await new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).GetResultAsync().ConfigureAwait(false); } else { diff --git a/src/Commands/QueryCommitChildren.cs b/src/Commands/QueryCommitChildren.cs index 4e99ce7a..6af0abb7 100644 --- a/src/Commands/QueryCommitChildren.cs +++ b/src/Commands/QueryCommitChildren.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -13,9 +14,9 @@ namespace SourceGit.Commands Args = $"rev-list -{max} --parents --branches --remotes --ancestry-path ^{commit}"; } - public List Result() + public async Task> GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); var outs = new List(); if (rs.IsSuccess) { diff --git a/src/Commands/QueryCommitFullMessage.cs b/src/Commands/QueryCommitFullMessage.cs index 36b6d1c7..07b77f2e 100644 --- a/src/Commands/QueryCommitFullMessage.cs +++ b/src/Commands/QueryCommitFullMessage.cs @@ -1,3 +1,5 @@ +using System.Threading.Tasks; + namespace SourceGit.Commands { public class QueryCommitFullMessage : Command @@ -9,12 +11,10 @@ namespace SourceGit.Commands Args = $"show --no-show-signature --format=%B -s {sha}"; } - public string Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); - if (rs.IsSuccess) - return rs.StdOut.TrimEnd(); - return string.Empty; + var rs = await ReadToEndAsync().ConfigureAwait(false); + return rs.IsSuccess ? rs.StdOut.TrimEnd() : string.Empty; } } } diff --git a/src/Commands/QueryCommitSignInfo.cs b/src/Commands/QueryCommitSignInfo.cs index 133949af..179e4cdd 100644 --- a/src/Commands/QueryCommitSignInfo.cs +++ b/src/Commands/QueryCommitSignInfo.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class QueryCommitSignInfo : Command { @@ -12,9 +14,9 @@ Args = $"{(useFakeSignersFile ? fakeSignersFileArg : string.Empty)} {baseArgs} {sha}"; } - public Models.CommitSignInfo Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return null; diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 8ac9cbc5..6391ff15 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -56,9 +57,9 @@ namespace SourceGit.Commands _findFirstMerged = false; } - public List Result() + public async Task> GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return _commits; @@ -110,7 +111,7 @@ namespace SourceGit.Commands _current.Subject = rs.StdOut.Substring(start); if (_findFirstMerged && !_isHeadFounded && _commits.Count > 0) - MarkFirstMerged(); + await MarkFirstMergedAsync().ConfigureAwait(false); return _commits; } @@ -120,21 +121,19 @@ namespace SourceGit.Commands if (data.Length < 8) return; - _current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries)); + _current.Parents.AddRange(data.Split(' ', StringSplitOptions.RemoveEmptyEntries)); } - private void MarkFirstMerged() + private async Task MarkFirstMergedAsync() { Args = $"log --since=\"{_commits[^1].CommitterTimeStr}\" --format=\"%H\""; - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); var shas = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); if (shas.Length == 0) return; - var set = new HashSet(); - foreach (var sha in shas) - set.Add(sha); + var set = new HashSet(shas); foreach (var c in _commits) { diff --git a/src/Commands/QueryCommitsForInteractiveRebase.cs b/src/Commands/QueryCommitsForInteractiveRebase.cs index 9f238319..81e28d4f 100644 --- a/src/Commands/QueryCommitsForInteractiveRebase.cs +++ b/src/Commands/QueryCommitsForInteractiveRebase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -14,9 +15,9 @@ namespace SourceGit.Commands Args = $"log --date-order --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD"; } - public List Result() + public async Task> GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return _commits; @@ -85,7 +86,7 @@ namespace SourceGit.Commands if (data.Length < 8) return; - _current.Commit.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries)); + _current.Commit.Parents.AddRange(data.Split(' ', StringSplitOptions.RemoveEmptyEntries)); } private List _commits = []; diff --git a/src/Commands/QueryCurrentBranch.cs b/src/Commands/QueryCurrentBranch.cs new file mode 100644 index 00000000..c721d13b --- /dev/null +++ b/src/Commands/QueryCurrentBranch.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; + +namespace SourceGit.Commands +{ + public class QueryCurrentBranch : Command + { + public QueryCurrentBranch(string repo) + { + WorkingDirectory = repo; + Context = repo; + Args = "branch --show-current"; + } + + public async Task GetResultAsync() + { + var rs = await ReadToEndAsync().ConfigureAwait(false); + return rs.StdOut.Trim(); + } + } +} diff --git a/src/Commands/QueryFileContent.cs b/src/Commands/QueryFileContent.cs index 648b9058..38ee6e4a 100644 --- a/src/Commands/QueryFileContent.cs +++ b/src/Commands/QueryFileContent.cs @@ -1,12 +1,13 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading.Tasks; namespace SourceGit.Commands { public static class QueryFileContent { - public static Stream Run(string repo, string revision, string file) + public static async Task RunAsync(string repo, string revision, string file) { var starter = new ProcessStartInfo(); starter.WorkingDirectory = repo; @@ -22,8 +23,8 @@ namespace SourceGit.Commands { var proc = new Process() { StartInfo = starter }; proc.Start(); - proc.StandardOutput.BaseStream.CopyTo(stream); - proc.WaitForExit(); + await proc.StandardOutput.BaseStream.CopyToAsync(stream).ConfigureAwait(false); + await proc.WaitForExitAsync().ConfigureAwait(false); proc.Close(); stream.Position = 0; @@ -36,7 +37,7 @@ namespace SourceGit.Commands return stream; } - public static Stream FromLFS(string repo, string oid, long size) + public static async Task FromLFSAsync(string repo, string oid, long size) { var starter = new ProcessStartInfo(); starter.WorkingDirectory = repo; @@ -53,11 +54,11 @@ namespace SourceGit.Commands { var proc = new Process() { StartInfo = starter }; proc.Start(); - proc.StandardInput.WriteLine("version https://git-lfs.github.com/spec/v1"); - proc.StandardInput.WriteLine($"oid sha256:{oid}"); - proc.StandardInput.WriteLine($"size {size}"); - proc.StandardOutput.BaseStream.CopyTo(stream); - proc.WaitForExit(); + await proc.StandardInput.WriteLineAsync("version https://git-lfs.github.com/spec/v1").ConfigureAwait(false); + await proc.StandardInput.WriteLineAsync($"oid sha256:{oid}").ConfigureAwait(false); + await proc.StandardInput.WriteLineAsync($"size {size}").ConfigureAwait(false); + await proc.StandardOutput.BaseStream.CopyToAsync(stream).ConfigureAwait(false); + await proc.WaitForExitAsync().ConfigureAwait(false); proc.Close(); stream.Position = 0; diff --git a/src/Commands/QueryFileSize.cs b/src/Commands/QueryFileSize.cs index 30af7715..1371df8b 100644 --- a/src/Commands/QueryFileSize.cs +++ b/src/Commands/QueryFileSize.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -14,9 +15,9 @@ namespace SourceGit.Commands Args = $"ls-tree {revision} -l -- \"{file}\""; } - public long Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (rs.IsSuccess) { var match = REG_FORMAT().Match(rs.StdOut); diff --git a/src/Commands/QueryGitCommonDir.cs b/src/Commands/QueryGitCommonDir.cs index 1076243e..c5b9339d 100644 --- a/src/Commands/QueryGitCommonDir.cs +++ b/src/Commands/QueryGitCommonDir.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -8,19 +9,19 @@ namespace SourceGit.Commands { WorkingDirectory = workDir; Args = "rev-parse --git-common-dir"; - RaiseError = false; } - public string Result() + public async Task GetResultAsync() { - var rs = ReadToEnd().StdOut; - if (string.IsNullOrEmpty(rs)) + var rs = await ReadToEndAsync().ConfigureAwait(false); + if (!rs.IsSuccess) return null; - rs = rs.Trim(); - if (Path.IsPathRooted(rs)) - return rs; - return Path.GetFullPath(Path.Combine(WorkingDirectory, rs)); + var stdout = rs.StdOut.Trim(); + if (string.IsNullOrEmpty(stdout)) + return null; + + return Path.IsPathRooted(stdout) ? stdout : Path.GetFullPath(Path.Combine(WorkingDirectory, stdout)); } } } diff --git a/src/Commands/QueryGitDir.cs b/src/Commands/QueryGitDir.cs index e3a94baf..ce8bfee6 100644 --- a/src/Commands/QueryGitDir.cs +++ b/src/Commands/QueryGitDir.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -8,19 +9,19 @@ namespace SourceGit.Commands { WorkingDirectory = workDir; Args = "rev-parse --git-dir"; - RaiseError = false; } - public string Result() + public async Task GetResultAsync() { - var rs = ReadToEnd().StdOut; - if (string.IsNullOrEmpty(rs)) + var rs = await ReadToEndAsync().ConfigureAwait(false); + if (!rs.IsSuccess) return null; - rs = rs.Trim(); - if (Path.IsPathRooted(rs)) - return rs; - return Path.GetFullPath(Path.Combine(WorkingDirectory, rs)); + var stdout = rs.StdOut.Trim(); + if (string.IsNullOrEmpty(stdout)) + return null; + + return Path.IsPathRooted(stdout) ? stdout : Path.GetFullPath(Path.Combine(WorkingDirectory, stdout)); } } } diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs index 788ed617..9605014d 100644 --- a/src/Commands/QueryLocalChanges.cs +++ b/src/Commands/QueryLocalChanges.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; - -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -19,13 +18,13 @@ namespace SourceGit.Commands Args = $"--no-optional-locks status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain"; } - public List Result() + public async Task> GetResultAsync() { var outs = new List(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) { - Dispatcher.UIThread.Post(() => App.RaiseException(Context, rs.StdErr)); + App.RaiseException(Context, rs.StdErr); return outs; } diff --git a/src/Commands/QueryRefsContainsCommit.cs b/src/Commands/QueryRefsContainsCommit.cs index cabe1b50..186b6a80 100644 --- a/src/Commands/QueryRefsContainsCommit.cs +++ b/src/Commands/QueryRefsContainsCommit.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -12,29 +13,28 @@ namespace SourceGit.Commands Args = $"for-each-ref --format=\"%(refname)\" --contains {commit}"; } - public List Result() + public async Task> GetResultAsync() { - var rs = new List(); + var outs = new List(); + var rs = await ReadToEndAsync().ConfigureAwait(false); + if (!rs.IsSuccess) + return outs; - var output = ReadToEnd(); - if (!output.IsSuccess) - return rs; - - var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { if (line.EndsWith("/HEAD", StringComparison.Ordinal)) continue; if (line.StartsWith("refs/heads/", StringComparison.Ordinal)) - rs.Add(new() { Name = line.Substring("refs/heads/".Length), Type = Models.DecoratorType.LocalBranchHead }); + outs.Add(new() { Name = line.Substring("refs/heads/".Length), Type = Models.DecoratorType.LocalBranchHead }); else if (line.StartsWith("refs/remotes/", StringComparison.Ordinal)) - rs.Add(new() { Name = line.Substring("refs/remotes/".Length), Type = Models.DecoratorType.RemoteBranchHead }); + outs.Add(new() { Name = line.Substring("refs/remotes/".Length), Type = Models.DecoratorType.RemoteBranchHead }); else if (line.StartsWith("refs/tags/", StringComparison.Ordinal)) - rs.Add(new() { Name = line.Substring("refs/tags/".Length), Type = Models.DecoratorType.Tag }); + outs.Add(new() { Name = line.Substring("refs/tags/".Length), Type = Models.DecoratorType.Tag }); } - return rs; + return outs; } } } diff --git a/src/Commands/QueryRemotes.cs b/src/Commands/QueryRemotes.cs index 7afec74d..bd42aabf 100644 --- a/src/Commands/QueryRemotes.cs +++ b/src/Commands/QueryRemotes.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -16,10 +17,10 @@ namespace SourceGit.Commands Args = "remote -v"; } - public List Result() + public async Task> GetResultAsync() { var outs = new List(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return outs; diff --git a/src/Commands/QueryRepositoryRootPath.cs b/src/Commands/QueryRepositoryRootPath.cs index 016621c8..f7e1eb63 100644 --- a/src/Commands/QueryRepositoryRootPath.cs +++ b/src/Commands/QueryRepositoryRootPath.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class QueryRepositoryRootPath : Command { @@ -7,5 +9,10 @@ WorkingDirectory = path; Args = "rev-parse --show-toplevel"; } + + public async Task GetResultAsync() + { + return await ReadToEndAsync().ConfigureAwait(false); + } } } diff --git a/src/Commands/QueryRevisionByRefName.cs b/src/Commands/QueryRevisionByRefName.cs index 7fb4ecfa..64a03e9d 100644 --- a/src/Commands/QueryRevisionByRefName.cs +++ b/src/Commands/QueryRevisionByRefName.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class QueryRevisionByRefName : Command { @@ -9,9 +11,9 @@ Args = $"rev-parse {refname}"; } - public string Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut)) return rs.StdOut.Trim(); diff --git a/src/Commands/QueryRevisionFileNames.cs b/src/Commands/QueryRevisionFileNames.cs index c6fd7373..74753412 100644 --- a/src/Commands/QueryRevisionFileNames.cs +++ b/src/Commands/QueryRevisionFileNames.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -11,17 +12,14 @@ namespace SourceGit.Commands Args = $"ls-tree -r -z --name-only {revision}"; } - public List Result() + public async Task> GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return []; var lines = rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries); - var outs = new List(); - foreach (var line in lines) - outs.Add(line); - return outs; + return [.. lines]; } } } diff --git a/src/Commands/QueryRevisionObjects.cs b/src/Commands/QueryRevisionObjects.cs index 5c582dcc..e991cd0c 100644 --- a/src/Commands/QueryRevisionObjects.cs +++ b/src/Commands/QueryRevisionObjects.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -18,9 +19,10 @@ namespace SourceGit.Commands Args += $" -- \"{parentFolder}\""; } - public List Result() + public async Task> GetResultAsync() { - var rs = ReadToEnd(); + var outs = new List(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (rs.IsSuccess) { var start = 0; @@ -28,19 +30,19 @@ namespace SourceGit.Commands while (end > 0) { var line = rs.StdOut.Substring(start, end - start); - Parse(line); + Parse(outs, line); start = end + 1; end = rs.StdOut.IndexOf('\0', start); } if (start < rs.StdOut.Length) - Parse(rs.StdOut.Substring(start)); + Parse(outs, rs.StdOut.Substring(start)); } - return _objects; + return outs; } - private void Parse(string line) + private void Parse(List outs, string line) { var match = REG_FORMAT().Match(line); if (!match.Success) @@ -60,9 +62,7 @@ namespace SourceGit.Commands _ => obj.Type, }; - _objects.Add(obj); + outs.Add(obj); } - - private List _objects = new List(); } } diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs index 35289ec5..897459f0 100644 --- a/src/Commands/QuerySingleCommit.cs +++ b/src/Commands/QuerySingleCommit.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -11,9 +12,9 @@ namespace SourceGit.Commands Args = $"show --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s -s {sha}"; } - public Models.Commit Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut)) { var commit = new Models.Commit(); diff --git a/src/Commands/QueryStagedChangesWithAmend.cs b/src/Commands/QueryStagedChangesWithAmend.cs index 78980401..bec033ff 100644 --- a/src/Commands/QueryStagedChangesWithAmend.cs +++ b/src/Commands/QueryStagedChangesWithAmend.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -19,9 +20,9 @@ namespace SourceGit.Commands _parent = parent; } - public List Result() + public async Task> GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return []; diff --git a/src/Commands/QueryStagedFileBlobGuid.cs b/src/Commands/QueryStagedFileBlobGuid.cs index 3f52a5f2..b6910034 100644 --- a/src/Commands/QueryStagedFileBlobGuid.cs +++ b/src/Commands/QueryStagedFileBlobGuid.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -14,16 +15,11 @@ namespace SourceGit.Commands Args = $"ls-files -s -- \"{file}\""; } - public string Result() + public async Task GetResultAsync() { - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); var match = REG_FORMAT().Match(rs.StdOut.Trim()); - if (match.Success) - { - return match.Groups[1].Value; - } - - return string.Empty; + return match.Success ? match.Groups[1].Value : string.Empty; } } } diff --git a/src/Commands/QueryStashes.cs b/src/Commands/QueryStashes.cs index 2a84b34a..2b292f71 100644 --- a/src/Commands/QueryStashes.cs +++ b/src/Commands/QueryStashes.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -12,10 +13,10 @@ namespace SourceGit.Commands Args = "stash list -z --no-show-signature --format=\"%H%n%P%n%ct%n%gd%n%B\""; } - public List Result() + public async Task> GetResultAsync() { var outs = new List(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return outs; diff --git a/src/Commands/QuerySubmodules.cs b/src/Commands/QuerySubmodules.cs index 663c0ea0..c6dac1f5 100644 --- a/src/Commands/QuerySubmodules.cs +++ b/src/Commands/QuerySubmodules.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -21,10 +22,10 @@ namespace SourceGit.Commands Args = "submodule status"; } - public List Result() + public async Task> GetResultAsync() { var submodules = new List(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); var map = new Dictionary(); @@ -64,11 +65,12 @@ namespace SourceGit.Commands if (submodules.Count > 0) { Args = "config --file .gitmodules --list"; - rs = ReadToEnd(); + rs = await ReadToEndAsync().ConfigureAwait(false); if (rs.IsSuccess) { var modules = new Dictionary(); lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) { var match = REG_FORMAT_MODULE_INFO().Match(line); @@ -80,21 +82,39 @@ namespace SourceGit.Commands if (!modules.TryGetValue(name, out var m)) { - m = new ModuleInfo(); - modules.Add(name, m); + // Find name alias. + foreach (var kv in modules) + { + if (kv.Value.Path.Equals(name, StringComparison.Ordinal)) + { + m = kv.Value; + break; + } + } + + if (m == null) + { + m = new ModuleInfo(); + modules.Add(name, m); + } } if (key.Equals("path", StringComparison.Ordinal)) m.Path = val; else if (key.Equals("url", StringComparison.Ordinal)) m.URL = val; + else if (key.Equals("branch", StringComparison.Ordinal)) + m.Branch = val; } } foreach (var kv in modules) { if (map.TryGetValue(kv.Value.Path, out var m)) + { m.URL = kv.Value.URL; + m.Branch = kv.Value.Branch; + } } } } @@ -113,7 +133,7 @@ namespace SourceGit.Commands } Args = $"--no-optional-locks status --porcelain -- {builder}"; - rs = ReadToEnd(); + rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return submodules; @@ -137,6 +157,7 @@ namespace SourceGit.Commands { public string Path { get; set; } = string.Empty; public string URL { get; set; } = string.Empty; + public string Branch { get; set; } = string.Empty; } } } diff --git a/src/Commands/QueryTags.cs b/src/Commands/QueryTags.cs index 896d555e..ba83cb18 100644 --- a/src/Commands/QueryTags.cs +++ b/src/Commands/QueryTags.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -14,10 +15,10 @@ namespace SourceGit.Commands Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objecttype)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\""; } - public List Result() + public async Task> GetResultAsync() { var tags = new List(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return tags; diff --git a/src/Commands/QueryTrackStatus.cs b/src/Commands/QueryTrackStatus.cs index e7e1f1c9..d687d274 100644 --- a/src/Commands/QueryTrackStatus.cs +++ b/src/Commands/QueryTrackStatus.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -11,11 +12,11 @@ namespace SourceGit.Commands Args = $"rev-list --left-right {local}...{upstream}"; } - public Models.BranchTrackStatus Result() + public async Task GetResultAsync() { var status = new Models.BranchTrackStatus(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return status; diff --git a/src/Commands/QueryUpdatableSubmodules.cs b/src/Commands/QueryUpdatableSubmodules.cs index 03f4a24d..05fcc053 100644 --- a/src/Commands/QueryUpdatableSubmodules.cs +++ b/src/Commands/QueryUpdatableSubmodules.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -16,10 +17,10 @@ namespace SourceGit.Commands Args = "submodule status"; } - public List Result() + public async Task> GetResultAsync() { var submodules = new List(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) diff --git a/src/Commands/Remote.cs b/src/Commands/Remote.cs index beaf412b..6a150377 100644 --- a/src/Commands/Remote.cs +++ b/src/Commands/Remote.cs @@ -1,4 +1,6 @@ -namespace SourceGit.Commands +using System.Threading.Tasks; + +namespace SourceGit.Commands { public class Remote : Command { @@ -8,50 +10,50 @@ Context = repo; } - public bool Add(string name, string url) + public async Task AddAsync(string name, string url) { Args = $"remote add {name} {url}"; - return Exec(); + return await ExecAsync(); } - public bool Delete(string name) + public async Task DeleteAsync(string name) { Args = $"remote remove {name}"; - return Exec(); + return await ExecAsync(); } - public bool Rename(string name, string to) + public async Task RenameAsync(string name, string to) { Args = $"remote rename {name} {to}"; - return Exec(); + return await ExecAsync(); } - public bool Prune(string name) + public async Task PruneAsync(string name) { Args = $"remote prune {name}"; - return Exec(); + return await ExecAsync(); } - public string GetURL(string name, bool isPush) + public async Task GetURLAsync(string name, bool isPush) { Args = "remote get-url" + (isPush ? " --push " : " ") + name; - var rs = ReadToEnd(); + var rs = await ReadToEndAsync(); return rs.IsSuccess ? rs.StdOut.Trim() : string.Empty; } - public bool SetURL(string name, string url, bool isPush) + public async Task SetURLAsync(string name, string url, bool isPush) { Args = "remote set-url" + (isPush ? " --push " : " ") + $"{name} {url}"; - return Exec(); + return await ExecAsync(); } - public bool HasBranch(string remote, string branch) + public async Task HasBranchAsync(string remote, string branch) { - SSHKey = new Config(WorkingDirectory).Get($"remote.{remote}.sshkey"); + SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{remote}.sshkey"); Args = $"ls-remote {remote} {branch}"; - var rs = ReadToEnd(); + var rs = await ReadToEndAsync(); return rs.IsSuccess && rs.StdOut.Trim().Length > 0; } } diff --git a/src/Commands/SaveChangesAsPatch.cs b/src/Commands/SaveChangesAsPatch.cs index b10037a1..659d5329 100644 --- a/src/Commands/SaveChangesAsPatch.cs +++ b/src/Commands/SaveChangesAsPatch.cs @@ -2,20 +2,19 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; - -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { public static class SaveChangesAsPatch { - public static bool ProcessLocalChanges(string repo, List changes, bool isUnstaged, string saveTo) + public static async Task ProcessLocalChangesAsync(string repo, List changes, bool isUnstaged, string saveTo) { - using (var sw = File.Create(saveTo)) + await using (var sw = File.Create(saveTo)) { foreach (var change in changes) { - if (!ProcessSingleChange(repo, new Models.DiffOption(change, isUnstaged), sw)) + if (!await ProcessSingleChangeAsync(repo, new Models.DiffOption(change, isUnstaged), sw)) return false; } } @@ -23,13 +22,13 @@ namespace SourceGit.Commands return true; } - public static bool ProcessRevisionCompareChanges(string repo, List changes, string baseRevision, string targetRevision, string saveTo) + public static async Task ProcessRevisionCompareChangesAsync(string repo, List changes, string baseRevision, string targetRevision, string saveTo) { - using (var sw = File.Create(saveTo)) + await using (var sw = File.Create(saveTo)) { foreach (var change in changes) { - if (!ProcessSingleChange(repo, new Models.DiffOption(baseRevision, targetRevision, change), sw)) + if (!await ProcessSingleChangeAsync(repo, new Models.DiffOption(baseRevision, targetRevision, change), sw)) return false; } } @@ -37,20 +36,20 @@ namespace SourceGit.Commands return true; } - public static bool ProcessStashChanges(string repo, List opts, string saveTo) + public static async Task ProcessStashChangesAsync(string repo, List opts, string saveTo) { - using (var sw = File.Create(saveTo)) + await using (var sw = File.Create(saveTo)) { foreach (var opt in opts) { - if (!ProcessSingleChange(repo, opt, sw)) + if (!await ProcessSingleChangeAsync(repo, opt, sw)) return false; } } return true; } - private static bool ProcessSingleChange(string repo, Models.DiffOption opt, FileStream writer) + private static async Task ProcessSingleChangeAsync(string repo, Models.DiffOption opt, FileStream writer) { var starter = new ProcessStartInfo(); starter.WorkingDirectory = repo; @@ -65,8 +64,8 @@ namespace SourceGit.Commands { var proc = new Process() { StartInfo = starter }; proc.Start(); - proc.StandardOutput.BaseStream.CopyTo(writer); - proc.WaitForExit(); + await proc.StandardOutput.BaseStream.CopyToAsync(writer).ConfigureAwait(false); + await proc.WaitForExitAsync().ConfigureAwait(false); var rs = proc.ExitCode == 0; proc.Close(); @@ -74,10 +73,7 @@ namespace SourceGit.Commands } catch (Exception e) { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, "Save change to patch failed: " + e.Message); - }); + App.RaiseException(repo, "Save change to patch failed: " + e.Message); return false; } } diff --git a/src/Commands/SaveRevisionFile.cs b/src/Commands/SaveRevisionFile.cs index b6127ea6..24d803df 100644 --- a/src/Commands/SaveRevisionFile.cs +++ b/src/Commands/SaveRevisionFile.cs @@ -1,32 +1,31 @@ using System; using System.Diagnostics; using System.IO; - -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { public static class SaveRevisionFile { - public static void Run(string repo, string revision, string file, string saveTo) + public static async Task RunAsync(string repo, string revision, string file, string saveTo) { var dir = Path.GetDirectoryName(saveTo); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); - var isLFSFiltered = new IsLFSFiltered(repo, revision, file).Result(); + var isLFSFiltered = await new IsLFSFiltered(repo, revision, file).GetResultAsync().ConfigureAwait(false); if (isLFSFiltered) { - var pointerStream = QueryFileContent.Run(repo, revision, file); - ExecCmd(repo, "lfs smudge", saveTo, pointerStream); + var pointerStream = await QueryFileContent.RunAsync(repo, revision, file).ConfigureAwait(false); + await ExecCmdAsync(repo, "lfs smudge", saveTo, pointerStream).ConfigureAwait(false); } else { - ExecCmd(repo, $"show {revision}:\"{file}\"", saveTo); + await ExecCmdAsync(repo, $"show {revision}:\"{file}\"", saveTo).ConfigureAwait(false); } } - private static void ExecCmd(string repo, string args, string outputFile, Stream input = null) + private static async Task ExecCmdAsync(string repo, string args, string outputFile, Stream input = null) { var starter = new ProcessStartInfo(); starter.WorkingDirectory = repo; @@ -39,24 +38,21 @@ namespace SourceGit.Commands starter.RedirectStandardOutput = true; starter.RedirectStandardError = true; - using (var sw = File.OpenWrite(outputFile)) + await using (var sw = File.OpenWrite(outputFile)) { try { var proc = new Process() { StartInfo = starter }; proc.Start(); if (input != null) - proc.StandardInput.Write(new StreamReader(input).ReadToEnd()); - proc.StandardOutput.BaseStream.CopyTo(sw); - proc.WaitForExit(); + await proc.StandardInput.WriteAsync(await new StreamReader(input).ReadToEndAsync()); + await proc.StandardOutput.BaseStream.CopyToAsync(sw); + await proc.WaitForExitAsync(); proc.Close(); } catch (Exception e) { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, "Save file failed: " + e.Message); - }); + App.RaiseException(repo, "Save file failed: " + e.Message); } } } diff --git a/src/Commands/Stash.cs b/src/Commands/Stash.cs index 7d1a269b..9774ed56 100644 --- a/src/Commands/Stash.cs +++ b/src/Commands/Stash.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -11,7 +12,7 @@ namespace SourceGit.Commands Context = repo; } - public bool Push(string message, bool includeUntracked = true, bool keepIndex = false) + public async Task PushAsync(string message, bool includeUntracked = true, bool keepIndex = false) { var builder = new StringBuilder(); builder.Append("stash push "); @@ -24,10 +25,10 @@ namespace SourceGit.Commands builder.Append("\""); Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Push(string message, List changes, bool keepIndex) + public async Task PushAsync(string message, List changes, bool keepIndex) { var builder = new StringBuilder(); builder.Append("stash push --include-untracked "); @@ -41,10 +42,10 @@ namespace SourceGit.Commands builder.Append($"\"{c.Path}\" "); Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Push(string message, string pathspecFromFile, bool keepIndex) + public async Task PushAsync(string message, string pathspecFromFile, bool keepIndex) { var builder = new StringBuilder(); builder.Append("stash push --include-untracked --pathspec-from-file=\""); @@ -57,10 +58,10 @@ namespace SourceGit.Commands builder.Append("\""); Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool PushOnlyStaged(string message, bool keepIndex) + public async Task PushOnlyStagedAsync(string message, bool keepIndex) { var builder = new StringBuilder(); builder.Append("stash push --staged "); @@ -70,32 +71,32 @@ namespace SourceGit.Commands builder.Append(message); builder.Append("\""); Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Apply(string name, bool restoreIndex) + public async Task ApplyAsync(string name, bool restoreIndex) { var opts = restoreIndex ? "--index" : string.Empty; Args = $"stash apply -q {opts} \"{name}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Pop(string name) + public async Task PopAsync(string name) { Args = $"stash pop -q --index \"{name}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Drop(string name) + public async Task DropAsync(string name) { Args = $"stash drop -q \"{name}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Clear() + public async Task ClearAsync() { Args = "stash clear"; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } } } diff --git a/src/Commands/Statistics.cs b/src/Commands/Statistics.cs index e11c1740..513b9640 100644 --- a/src/Commands/Statistics.cs +++ b/src/Commands/Statistics.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -11,10 +12,10 @@ namespace SourceGit.Commands Args = $"log --date-order --branches --remotes -{max} --format=%ct$%aN±%aE"; } - public Models.Statistics Result() + public async Task ReadAsync() { var statistics = new Models.Statistics(); - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); if (!rs.IsSuccess) return statistics; diff --git a/src/Commands/Submodule.cs b/src/Commands/Submodule.cs index 025d035a..ce93e017 100644 --- a/src/Commands/Submodule.cs +++ b/src/Commands/Submodule.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -11,25 +12,38 @@ namespace SourceGit.Commands Context = repo; } - public bool Add(string url, string relativePath, bool recursive) + public async Task AddAsync(string url, string relativePath, bool recursive) { Args = $"-c protocol.file.allow=always submodule add \"{url}\" \"{relativePath}\""; - if (!Exec()) + + var succ = await ExecAsync().ConfigureAwait(false); + if (!succ) return false; if (recursive) - { Args = $"submodule update --init --recursive -- \"{relativePath}\""; - return Exec(); - } else - { Args = $"submodule update --init -- \"{relativePath}\""; - return true; - } + return await ExecAsync().ConfigureAwait(false); } - public bool Update(List modules, bool init, bool recursive, bool useRemote = false) + public async Task SetURLAsync(string path, string url) + { + Args = $"submodule set-url -- \"{path}\" \"{url}\""; + return await ExecAsync().ConfigureAwait(false); + } + + public async Task SetBranchAsync(string path, string branch) + { + if (string.IsNullOrEmpty(branch)) + Args = $"submodule set-branch -d -- \"{path}\""; + else + Args = $"submodule set-branch -b \"{branch}\" -- \"{path}\""; + + return await ExecAsync().ConfigureAwait(false); + } + + public async Task UpdateAsync(List modules, bool init, bool recursive, bool useRemote = false) { var builder = new StringBuilder(); builder.Append("submodule update"); @@ -48,19 +62,19 @@ namespace SourceGit.Commands } Args = builder.ToString(); - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Deinit(string module, bool force) + public async Task DeinitAsync(string module, bool force) { Args = force ? $"submodule deinit -f -- \"{module}\"" : $"submodule deinit -- \"{module}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Delete(string module) + public async Task DeleteAsync(string module) { Args = $"rm -rf \"{module}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } } } diff --git a/src/Commands/Tag.cs b/src/Commands/Tag.cs index 017afea0..d372573a 100644 --- a/src/Commands/Tag.cs +++ b/src/Commands/Tag.cs @@ -1,20 +1,21 @@ using System.IO; +using System.Threading.Tasks; namespace SourceGit.Commands { public static class Tag { - public static bool Add(string repo, string name, string basedOn, Models.ICommandLog log) + public static async Task AddAsync(string repo, string name, string basedOn, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"tag --no-sign {name} {basedOn}"; cmd.Log = log; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } - public static bool Add(string repo, string name, string basedOn, string message, bool sign, Models.ICommandLog log) + public static async Task AddAsync(string repo, string name, string basedOn, string message, bool sign, Models.ICommandLog log) { var param = sign ? "--sign -a" : "--no-sign -a"; var cmd = new Command(); @@ -26,26 +27,26 @@ namespace SourceGit.Commands if (!string.IsNullOrEmpty(message)) { string tmp = Path.GetTempFileName(); - File.WriteAllText(tmp, message); + await File.WriteAllTextAsync(tmp, message); cmd.Args += $"-F \"{tmp}\""; - var succ = cmd.Exec(); + var succ = await cmd.ExecAsync().ConfigureAwait(false); File.Delete(tmp); return succ; } cmd.Args += $"-m {name}"; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } - public static bool Delete(string repo, string name, Models.ICommandLog log) + public static async Task DeleteAsync(string repo, string name, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"tag --delete {name}"; cmd.Log = log; - return cmd.Exec(); + return await cmd.ExecAsync().ConfigureAwait(false); } } } diff --git a/src/Commands/UnstageChangesForAmend.cs b/src/Commands/UnstageChangesForAmend.cs index 19def067..049811e5 100644 --- a/src/Commands/UnstageChangesForAmend.cs +++ b/src/Commands/UnstageChangesForAmend.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; - -using Avalonia.Threading; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -49,7 +48,7 @@ namespace SourceGit.Commands } } - public bool Exec() + public async Task ExecAsync() { var starter = new ProcessStartInfo(); starter.WorkingDirectory = _repo; @@ -66,25 +65,22 @@ namespace SourceGit.Commands { var proc = new Process() { StartInfo = starter }; proc.Start(); - proc.StandardInput.Write(_patchBuilder.ToString()); + await proc.StandardInput.WriteAsync(_patchBuilder.ToString()); proc.StandardInput.Close(); - var err = proc.StandardError.ReadToEnd(); - proc.WaitForExit(); + var err = await proc.StandardError.ReadToEndAsync().ConfigureAwait(false); + await proc.WaitForExitAsync().ConfigureAwait(false); var rs = proc.ExitCode == 0; proc.Close(); if (!rs) - Dispatcher.UIThread.Invoke(() => App.RaiseException(_repo, err)); + App.RaiseException(_repo, err); return rs; } catch (Exception e) { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(_repo, "Failed to unstage changes: " + e.Message); - }); + App.RaiseException(_repo, "Failed to unstage changes: " + e.Message); return false; } } diff --git a/src/Commands/Worktree.cs b/src/Commands/Worktree.cs index 1198a443..4e6bc4e4 100644 --- a/src/Commands/Worktree.cs +++ b/src/Commands/Worktree.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; namespace SourceGit.Commands { @@ -12,13 +13,13 @@ namespace SourceGit.Commands Context = repo; } - public List List() + public async Task> ReadAllAsync() { Args = "worktree list --porcelain"; - var rs = ReadToEnd(); + var rs = await ReadToEndAsync().ConfigureAwait(false); var worktrees = new List(); - var last = null as Models.Worktree; + Models.Worktree last = null; if (rs.IsSuccess) { var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); @@ -56,7 +57,7 @@ namespace SourceGit.Commands return worktrees; } - public bool Add(string fullpath, string name, bool createNew, string tracking) + public async Task AddAsync(string fullpath, string name, bool createNew, string tracking) { Args = "worktree add "; @@ -78,35 +79,35 @@ namespace SourceGit.Commands else if (!string.IsNullOrEmpty(name) && !createNew) Args += name; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Prune() + public async Task PruneAsync() { Args = "worktree prune -v"; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Lock(string fullpath) + public async Task LockAsync(string fullpath) { Args = $"worktree lock \"{fullpath}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Unlock(string fullpath) + public async Task UnlockAsync(string fullpath) { Args = $"worktree unlock \"{fullpath}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } - public bool Remove(string fullpath, bool force) + public async Task RemoveAsync(string fullpath, bool force) { if (force) Args = $"worktree remove -f \"{fullpath}\""; else Args = $"worktree remove \"{fullpath}\""; - return Exec(); + return await ExecAsync().ConfigureAwait(false); } } } diff --git a/src/Models/ApplyWhiteSpaceMode.cs b/src/Models/ApplyWhiteSpaceMode.cs index aad45f57..9bcd04b3 100644 --- a/src/Models/ApplyWhiteSpaceMode.cs +++ b/src/Models/ApplyWhiteSpaceMode.cs @@ -1,6 +1,6 @@ namespace SourceGit.Models { - public class ApplyWhiteSpaceMode + public class ApplyWhiteSpaceMode(string n, string d, string a) { public static readonly ApplyWhiteSpaceMode[] Supported = [ @@ -10,15 +10,8 @@ new ApplyWhiteSpaceMode("Error All", "Similar to 'error', but shows more", "error-all"), ]; - public string Name { get; set; } - public string Desc { get; set; } - public string Arg { get; set; } - - public ApplyWhiteSpaceMode(string n, string d, string a) - { - Name = n; - Desc = d; - Arg = a; - } + public string Name { get; set; } = n; + public string Desc { get; set; } = d; + public string Arg { get; set; } = a; } } diff --git a/src/Models/AvatarManager.cs b/src/Models/AvatarManager.cs index f0006652..06c205e4 100644 --- a/src/Models/AvatarManager.cs +++ b/src/Models/AvatarManager.cs @@ -55,7 +55,7 @@ namespace SourceGit.Models { while (true) { - var email = null as string; + string email = null; lock (_synclock) { @@ -79,7 +79,7 @@ namespace SourceGit.Models $"https://www.gravatar.com/avatar/{md5}?d=404"; var localFile = Path.Combine(_storePath, md5); - var img = null as Bitmap; + Bitmap img = null; try { var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) }; @@ -113,7 +113,7 @@ namespace SourceGit.Models _requesting.Remove(email); } - Dispatcher.UIThread.InvokeAsync(() => + Dispatcher.UIThread.Post(() => { _resources[email] = img; NotifyResourceChanged(email, img); @@ -197,7 +197,10 @@ namespace SourceGit.Models _resources[email] = image; - _requesting.Remove(email); + lock (_synclock) + { + _requesting.Remove(email); + } var store = Path.Combine(_storePath, GetEmailHash(email)); File.Copy(file, store, true); diff --git a/src/Models/Branch.cs b/src/Models/Branch.cs index 7146da3f..350bc5b5 100644 --- a/src/Models/Branch.cs +++ b/src/Models/Branch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text.RegularExpressions; namespace SourceGit.Models { @@ -29,7 +30,7 @@ namespace SourceGit.Models CommitterDate, } - public class Branch + public partial class Branch { public string Name { get; set; } public string FullName { get; set; } @@ -44,5 +45,13 @@ namespace SourceGit.Models public bool IsUpstreamGone { get; set; } public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}"; + + [GeneratedRegex(@"\s+")] + private static partial Regex REG_FIX_NAME(); + + public static string FixName(string name) + { + return REG_FIX_NAME().Replace(name, "-"); + } } } diff --git a/src/Models/CRLFMode.cs b/src/Models/CRLFMode.cs index 3f510f00..46f5442f 100644 --- a/src/Models/CRLFMode.cs +++ b/src/Models/CRLFMode.cs @@ -2,23 +2,16 @@ namespace SourceGit.Models { - public class CRLFMode + public class CRLFMode(string name, string value, string desc) { - public string Name { get; set; } - public string Value { get; set; } - public string Desc { get; set; } + public string Name { get; set; } = name; + public string Value { get; set; } = value; + public string Desc { get; set; } = desc; public static readonly List Supported = new List() { new CRLFMode("TRUE", "true", "Commit as LF, checkout as CRLF"), new CRLFMode("INPUT", "input", "Only convert for commit"), new CRLFMode("FALSE", "false", "Do NOT convert"), }; - - public CRLFMode(string name, string value, string desc) - { - Name = name; - Value = value; - Desc = desc; - } } } diff --git a/src/Models/CommitGraph.cs b/src/Models/CommitGraph.cs index 01488656..cb569610 100644 --- a/src/Models/CommitGraph.cs +++ b/src/Models/CommitGraph.cs @@ -6,6 +6,13 @@ using Avalonia.Media; namespace SourceGit.Models { + public record CommitGraphLayout(double startY, double clipWidth, double rowHeight) + { + public double StartY { get; set; } = startY; + public double ClipWidth { get; set; } = clipWidth; + public double RowHeight { get; set; } = rowHeight; + } + public class CommitGraph { public static List Pens { get; } = []; @@ -75,7 +82,7 @@ namespace SourceGit.Models foreach (var commit in commits) { - var major = null as PathHelper; + PathHelper major = null; var isMerged = commit.IsMerged; // Update current y offset diff --git a/src/Models/CommitLink.cs b/src/Models/CommitLink.cs index 2891e5d6..02308975 100644 --- a/src/Models/CommitLink.cs +++ b/src/Models/CommitLink.cs @@ -27,19 +27,19 @@ namespace SourceGit.Models trimmedUrl = url.AsSpan(0, url.Length - 4); if (url.StartsWith("https://github.com/", StringComparison.Ordinal)) - outs.Add(new($"Github ({trimmedUrl.Slice(19)})", $"{url}/commit/")); + outs.Add(new($"Github ({trimmedUrl[19..]})", $"{url}/commit/")); else if (url.StartsWith("https://gitlab.", StringComparison.Ordinal)) - outs.Add(new($"GitLab ({trimmedUrl.Slice(trimmedUrl.Slice(15).IndexOf('/') + 16)})", $"{url}/-/commit/")); + outs.Add(new($"GitLab ({trimmedUrl[(trimmedUrl[15..].IndexOf('/') + 16)..]})", $"{url}/-/commit/")); else if (url.StartsWith("https://gitee.com/", StringComparison.Ordinal)) - outs.Add(new($"Gitee ({trimmedUrl.Slice(18)})", $"{url}/commit/")); + outs.Add(new($"Gitee ({trimmedUrl[18..]})", $"{url}/commit/")); else if (url.StartsWith("https://bitbucket.org/", StringComparison.Ordinal)) - outs.Add(new($"BitBucket ({trimmedUrl.Slice(22)})", $"{url}/commits/")); + outs.Add(new($"BitBucket ({trimmedUrl[22..]})", $"{url}/commits/")); else if (url.StartsWith("https://codeberg.org/", StringComparison.Ordinal)) - outs.Add(new($"Codeberg ({trimmedUrl.Slice(21)})", $"{url}/commit/")); + outs.Add(new($"Codeberg ({trimmedUrl[21..]})", $"{url}/commit/")); else if (url.StartsWith("https://gitea.org/", StringComparison.Ordinal)) - outs.Add(new($"Gitea ({trimmedUrl.Slice(18)})", $"{url}/commit/")); + outs.Add(new($"Gitea ({trimmedUrl[18..]})", $"{url}/commit/")); else if (url.StartsWith("https://git.sr.ht/", StringComparison.Ordinal)) - outs.Add(new($"sourcehut ({trimmedUrl.Slice(18)})", $"{url}/commit/")); + outs.Add(new($"sourcehut ({trimmedUrl[18..]})", $"{url}/commit/")); } } diff --git a/src/Models/CustomAction.cs b/src/Models/CustomAction.cs index c6130fe0..1ed65b8b 100644 --- a/src/Models/CustomAction.cs +++ b/src/Models/CustomAction.cs @@ -16,6 +16,7 @@ namespace SourceGit.Models TextBox = 0, PathSelector, CheckBox, + ComboBox, } public class CustomActionControl : ObservableObject diff --git a/src/Models/DealWithChangesAfterStashing.cs b/src/Models/DealWithChangesAfterStashing.cs index 63889c96..9393ce74 100644 --- a/src/Models/DealWithChangesAfterStashing.cs +++ b/src/Models/DealWithChangesAfterStashing.cs @@ -2,21 +2,15 @@ namespace SourceGit.Models { - public class DealWithChangesAfterStashing + public class DealWithChangesAfterStashing(string label, string desc) { - public string Label { get; set; } - public string Desc { get; set; } + public string Label { get; set; } = label; + public string Desc { get; set; } = desc; public static readonly List Supported = [ new ("Discard", "All (or selected) changes will be discarded"), new ("Keep Index", "Staged changes are left intact"), new ("Keep All", "All (or selected) changes are left intact"), ]; - - public DealWithChangesAfterStashing(string label, string desc) - { - Label = label; - Desc = desc; - } } } diff --git a/src/Models/DiffOption.cs b/src/Models/DiffOption.cs index 69f93980..fe32528c 100644 --- a/src/Models/DiffOption.cs +++ b/src/Models/DiffOption.cs @@ -29,6 +29,8 @@ namespace SourceGit.Models { _workingCopyChange = change; _isUnstaged = isUnstaged; + _path = change.Path; + _orgPath = change.OriginalPath; if (isUnstaged) { @@ -37,13 +39,8 @@ namespace SourceGit.Models case ChangeState.Added: case ChangeState.Untracked: _extra = "--no-index"; - _path = change.Path; _orgPath = "/dev/null"; break; - default: - _path = change.Path; - _orgPath = change.OriginalPath; - break; } } else @@ -52,9 +49,6 @@ namespace SourceGit.Models _extra = $"--cached {change.DataForAmend.ParentSHA}"; else _extra = "--cached"; - - _path = change.Path; - _orgPath = change.OriginalPath; } } diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index 2f06c0cf..95ffa99f 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.Text; +using System.IO; using System.Text.RegularExpressions; using Avalonia; @@ -148,13 +148,13 @@ namespace SourceGit.Models var isTracked = !string.IsNullOrEmpty(fileBlobGuid); var fileGuid = isTracked ? fileBlobGuid : "00000000"; - var builder = new StringBuilder(); - builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n'); + using var writer = new StreamWriter(output); + writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); if (!revert && !isTracked) - builder.Append("new file mode 100644\n"); - builder.Append("index 00000000...").Append(fileGuid).Append('\n'); - builder.Append("--- ").Append((revert || isTracked) ? $"a/{change.Path}\n" : "/dev/null\n"); - builder.Append("+++ b/").Append(change.Path).Append('\n'); + writer.WriteLine("new file mode 100644"); + writer.WriteLine($"index 00000000...{fileGuid}"); + writer.WriteLine($"--- {(revert || isTracked ? $"a/{change.Path}" : "/dev/null")}"); + writer.WriteLine($"+++ b/{change.Path}"); var additions = selection.EndLine - selection.StartLine; if (selection.StartLine != 1) @@ -163,43 +163,43 @@ namespace SourceGit.Models if (revert) { var totalLines = Lines.Count - 1; - builder.Append("@@ -0,").Append(totalLines - additions).Append(" +0,").Append(totalLines).Append(" @@"); + writer.WriteLine($"@@ -0,{totalLines - additions} +0,{totalLines} @@"); for (int i = 1; i <= totalLines; i++) { var line = Lines[i]; if (line.Type != TextDiffLineType.Added) continue; - builder.Append(selection.IsInRange(i) ? "\n+" : "\n ").Append(line.Content); + writer.WriteLine($"{(selection.IsInRange(i) ? "+" : " ")}{line.Content}"); } } else { - builder.Append("@@ -0,0 +0,").Append(additions).Append(" @@"); + writer.WriteLine($"@@ -0,0 +0,{additions} @@"); for (int i = selection.StartLine - 1; i < selection.EndLine; i++) { var line = Lines[i]; if (line.Type != TextDiffLineType.Added) continue; - builder.Append("\n+").Append(line.Content); + writer.WriteLine($"+{line.Content}"); } } - builder.Append("\n\\ No newline at end of file\n"); - System.IO.File.WriteAllText(output, builder.ToString()); + writer.WriteLine("\\ No newline at end of file"); + writer.Flush(); } public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextDiffSelection selection, bool revert, string output) { var orgFile = !string.IsNullOrEmpty(change.OriginalPath) ? change.OriginalPath : change.Path; - var builder = new StringBuilder(); - builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n'); - builder.Append("index 00000000...").Append(fileTreeGuid).Append(" 100644\n"); - builder.Append("--- a/").Append(orgFile).Append('\n'); - builder.Append("+++ b/").Append(change.Path); + using var writer = new StreamWriter(output); + writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); + writer.WriteLine($"index 00000000...{fileTreeGuid} 100644"); + writer.WriteLine($"--- a/{orgFile}"); + writer.WriteLine($"+++ b/{change.Path}"); // If last line of selection is a change. Find one more line. - var tail = null as string; + string tail = null; if (selection.EndLine < Lines.Count) { var lastLine = Lines[selection.EndLine - 1]; @@ -210,21 +210,12 @@ namespace SourceGit.Models var line = Lines[i]; if (line.Type == TextDiffLineType.Indicator) break; - if (revert) + if (line.Type == TextDiffLineType.Normal || + (revert && line.Type == TextDiffLineType.Added) || + (!revert && line.Type == TextDiffLineType.Deleted)) { - if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Added) - { - tail = line.Content; - break; - } - } - else - { - if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Deleted) - { - tail = line.Content; - break; - } + tail = line.Content; + break; } } } @@ -264,21 +255,21 @@ namespace SourceGit.Models var line = Lines[i]; if (line.Type == TextDiffLineType.Indicator) { - ProcessIndicatorForPatch(builder, line, i, selection.StartLine, selection.EndLine, ignoreRemoves, ignoreAdds, revert, tail != null); + ProcessIndicatorForPatch(writer, line, i, selection.StartLine, selection.EndLine, ignoreRemoves, ignoreAdds, revert, tail != null); } else if (line.Type == TextDiffLineType.Added) { if (revert) - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } else if (line.Type == TextDiffLineType.Deleted) { if (!revert) - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } else if (line.Type == TextDiffLineType.Normal) { - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } } } @@ -289,42 +280,39 @@ namespace SourceGit.Models var line = Lines[i]; if (line.Type == TextDiffLineType.Indicator) { - if (!ProcessIndicatorForPatch(builder, line, i, selection.StartLine, selection.EndLine, selection.IgnoredDeletes, selection.IgnoredAdds, revert, tail != null)) - { + if (!ProcessIndicatorForPatch(writer, line, i, selection.StartLine, selection.EndLine, selection.IgnoredDeletes, selection.IgnoredAdds, revert, tail != null)) break; - } } else if (line.Type == TextDiffLineType.Normal) { - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } else if (line.Type == TextDiffLineType.Added) { - builder.Append("\n+").Append(line.Content); + writer.WriteLine($"+{line.Content}"); } else if (line.Type == TextDiffLineType.Deleted) { - builder.Append("\n-").Append(line.Content); + writer.WriteLine($"-{line.Content}"); } } - builder.Append("\n ").Append(tail); - builder.Append("\n"); - System.IO.File.WriteAllText(output, builder.ToString()); + writer.WriteLine($" {tail}"); + writer.Flush(); } public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeGuid, TextDiffSelection selection, bool revert, bool isOldSide, string output) { var orgFile = !string.IsNullOrEmpty(change.OriginalPath) ? change.OriginalPath : change.Path; - var builder = new StringBuilder(); - builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n'); - builder.Append("index 00000000...").Append(fileTreeGuid).Append(" 100644\n"); - builder.Append("--- a/").Append(orgFile).Append('\n'); - builder.Append("+++ b/").Append(change.Path); + using var writer = new StreamWriter(output); + writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); + writer.WriteLine($"index 00000000...{fileTreeGuid} 100644"); + writer.WriteLine($"--- a/{orgFile}"); + writer.WriteLine($"+++ b/{change.Path}"); // If last line of selection is a change. Find one more line. - var tail = null as string; + string tail = null; if (selection.EndLine < Lines.Count) { var lastLine = Lines[selection.EndLine - 1]; @@ -389,21 +377,21 @@ namespace SourceGit.Models var line = Lines[i]; if (line.Type == TextDiffLineType.Indicator) { - ProcessIndicatorForPatchSingleSide(builder, line, i, selection.StartLine, selection.EndLine, ignoreRemoves, ignoreAdds, revert, isOldSide, tail != null); + ProcessIndicatorForPatchSingleSide(writer, line, i, selection.StartLine, selection.EndLine, ignoreRemoves, ignoreAdds, revert, isOldSide, tail != null); } else if (line.Type == TextDiffLineType.Added) { if (revert) - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } else if (line.Type == TextDiffLineType.Deleted) { if (!revert) - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } else if (line.Type == TextDiffLineType.Normal) { - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } } } @@ -414,14 +402,12 @@ namespace SourceGit.Models var line = Lines[i]; if (line.Type == TextDiffLineType.Indicator) { - if (!ProcessIndicatorForPatchSingleSide(builder, line, i, selection.StartLine, selection.EndLine, selection.IgnoredDeletes, selection.IgnoredAdds, revert, isOldSide, tail != null)) - { + if (!ProcessIndicatorForPatchSingleSide(writer, line, i, selection.StartLine, selection.EndLine, selection.IgnoredDeletes, selection.IgnoredAdds, revert, isOldSide, tail != null)) break; - } } else if (line.Type == TextDiffLineType.Normal) { - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } else if (line.Type == TextDiffLineType.Added) { @@ -429,7 +415,7 @@ namespace SourceGit.Models { if (revert) { - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } else { @@ -438,20 +424,20 @@ namespace SourceGit.Models } else { - builder.Append("\n+").Append(line.Content); + writer.WriteLine($"+{line.Content}"); } } else if (line.Type == TextDiffLineType.Deleted) { if (isOldSide) { - builder.Append("\n-").Append(line.Content); + writer.WriteLine($"-{line.Content}"); } else { if (!revert) { - builder.Append("\n ").Append(line.Content); + writer.WriteLine($" {line.Content}"); } else { @@ -461,12 +447,11 @@ namespace SourceGit.Models } } - builder.Append("\n ").Append(tail); - builder.Append("\n"); - System.IO.File.WriteAllText(output, builder.ToString()); + writer.WriteLine($" {tail}"); + writer.Flush(); } - private bool ProcessIndicatorForPatch(StringBuilder builder, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool tailed) + private bool ProcessIndicatorForPatch(StreamWriter writer, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool tailed) { var match = REG_INDICATOR().Match(indicator.Content); var oldStart = int.Parse(match.Groups[1].Value); @@ -531,11 +516,11 @@ namespace SourceGit.Models if (oldCount == 0 && newCount == 0) return false; - builder.Append($"\n@@ -{oldStart},{oldCount} +{newStart},{newCount} @@"); + writer.WriteLine($"@@ -{oldStart},{oldCount} +{newStart},{newCount} @@"); return true; } - private bool ProcessIndicatorForPatchSingleSide(StringBuilder builder, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool isOldSide, bool tailed) + private bool ProcessIndicatorForPatchSingleSide(StreamWriter writer, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool isOldSide, bool tailed) { var match = REG_INDICATOR().Match(indicator.Content); var oldStart = int.Parse(match.Groups[1].Value); @@ -611,7 +596,7 @@ namespace SourceGit.Models if (oldCount == 0 && newCount == 0) return false; - builder.Append($"\n@@ -{oldStart},{oldCount} +{newStart},{newCount} @@"); + writer.WriteLine($"@@ -{oldStart},{oldCount} +{newStart},{newCount} @@"); return true; } diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs index fe67ad6a..ed9b9b08 100644 --- a/src/Models/ExternalMerger.cs +++ b/src/Models/ExternalMerger.cs @@ -44,6 +44,7 @@ namespace SourceGit.Models new ExternalMerger(9, "p4merge", "P4Merge", "p4merge.exe", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(10, "plastic_merge", "Plastic SCM", "mergetool.exe", "-s=\"$REMOTE\" -b=\"$BASE\" -d=\"$LOCAL\" -r=\"$MERGED\" --automatic", "-s=\"$LOCAL\" -d=\"$REMOTE\""), new ExternalMerger(11, "meld", "Meld", "Meld.exe", "\"$LOCAL\" \"$BASE\" \"$REMOTE\" --output \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(12, "cursor", "Cursor", "Cursor.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), }; } else if (OperatingSystem.IsMacOS()) @@ -57,6 +58,7 @@ namespace SourceGit.Models new ExternalMerger(5, "beyond_compare", "Beyond Compare", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(6, "codium", "VSCodium", "/Applications/VSCodium.app/Contents/Resources/app/bin/codium", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(7, "p4merge", "P4Merge", "/Applications/p4merge.app/Contents/Resources/launchp4merge", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(8, "cursor", "Cursor", "/Applications/Cursor.app/Contents/Resources/app/bin/cursor", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), }; } else if (OperatingSystem.IsLinux()) @@ -70,6 +72,7 @@ namespace SourceGit.Models new ExternalMerger(5, "meld", "Meld", "/usr/bin/meld", "\"$LOCAL\" \"$BASE\" \"$REMOTE\" --output \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(6, "codium", "VSCodium", "/usr/share/codium/bin/codium", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(7, "p4merge", "P4Merge", "/usr/local/bin/p4merge", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(8, "cursor", "Cursor", "cursor", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), }; } else @@ -93,19 +96,10 @@ namespace SourceGit.Models public string[] GetPatterns() { if (OperatingSystem.IsWindows()) - { return Exec.Split(';'); - } - else - { - var patterns = new List(); - var choices = Exec.Split(';', StringSplitOptions.RemoveEmptyEntries); - foreach (var c in choices) - { - patterns.Add(Path.GetFileName(c)); - } - return patterns.ToArray(); - } + + var choices = Exec.Split(';', StringSplitOptions.RemoveEmptyEntries); + return Array.ConvertAll(choices, Path.GetFileName); } } } diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs index 697c171a..f884fb4a 100644 --- a/src/Models/ExternalTool.cs +++ b/src/Models/ExternalTool.cs @@ -154,6 +154,11 @@ namespace SourceGit.Models TryAdd("Zed", "zed", platformFinder); } + public void Cursor(Func platformFinder) + { + TryAdd("Cursor", "cursor", platformFinder); + } + public void FindJetBrainsFromToolbox(Func platformFinder) { var exclude = new List { "fleet", "dotmemory", "dottrace", "resharper-u", "androidstudio" }; diff --git a/src/Models/MergeMode.cs b/src/Models/MergeMode.cs index 5dc70030..a217514e 100644 --- a/src/Models/MergeMode.cs +++ b/src/Models/MergeMode.cs @@ -1,25 +1,33 @@ namespace SourceGit.Models { - public class MergeMode + public class MergeMode(string n, string d, string a) { + public static readonly MergeMode Default = + new MergeMode("Default", "Fast-forward if possible", ""); + + public static readonly MergeMode FastForward = + new MergeMode("Fast-forward", "Refuse to merge when fast-forward is not possible", "--ff-only"); + + public static readonly MergeMode NoFastForward = + new MergeMode("No Fast-forward", "Always create a merge commit", "--no-ff"); + + public static readonly MergeMode Squash = + new MergeMode("Squash", "Squash merge", "--squash"); + + public static readonly MergeMode DontCommit + = new MergeMode("Don't commit", "Merge without commit", "--no-ff --no-commit"); + public static readonly MergeMode[] Supported = [ - new MergeMode("Default", "Fast-forward if possible", ""), - new MergeMode("Fast-forward", "Refuse to merge when fast-forward is not possible", "--ff-only"), - new MergeMode("No Fast-forward", "Always create a merge commit", "--no-ff"), - new MergeMode("Squash", "Squash merge", "--squash"), - new MergeMode("Don't commit", "Merge without commit", "--no-ff --no-commit"), + Default, + FastForward, + NoFastForward, + Squash, + DontCommit, ]; - public string Name { get; set; } - public string Desc { get; set; } - public string Arg { get; set; } - - public MergeMode(string n, string d, string a) - { - Name = n; - Desc = d; - Arg = a; - } + public string Name { get; set; } = n; + public string Desc { get; set; } = d; + public string Arg { get; set; } = a; } } diff --git a/src/Models/OpenAI.cs b/src/Models/OpenAI.cs index ab2a92b3..9ca3506e 100644 --- a/src/Models/OpenAI.cs +++ b/src/Models/OpenAI.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.Threading; +using System.Threading.Tasks; using Azure.AI.OpenAI; using CommunityToolkit.Mvvm.ComponentModel; using OpenAI; @@ -173,7 +174,7 @@ namespace SourceGit.Models """; } - public void Chat(string prompt, string question, CancellationToken cancellation, Action onUpdate) + public async Task ChatAsync(string prompt, string question, CancellationToken cancellation, Action onUpdate) { var server = new Uri(_server); var key = new ApiKeyCredential(_apiKey); @@ -191,9 +192,9 @@ namespace SourceGit.Models if (_streaming) { - var updates = client.CompleteChatStreaming(messages, null, cancellation); + var updates = client.CompleteChatStreamingAsync(messages, null, cancellation); - foreach (var update in updates) + await foreach (var update in updates) { if (update.ContentUpdate.Count > 0) rsp.Append(update.ContentUpdate[0].Text); @@ -201,7 +202,7 @@ namespace SourceGit.Models } else { - var completion = client.CompleteChat(messages, null, cancellation); + var completion = await client.CompleteChatAsync(messages, null, cancellation); if (completion.Value.Content.Count > 0) rsp.Append(completion.Value.Content[0].Text); diff --git a/src/Models/ResetMode.cs b/src/Models/ResetMode.cs index 827ccaa9..1c84dc8e 100644 --- a/src/Models/ResetMode.cs +++ b/src/Models/ResetMode.cs @@ -2,7 +2,7 @@ namespace SourceGit.Models { - public class ResetMode + public class ResetMode(string n, string d, string a, string k, IBrush b) { public static readonly ResetMode[] Supported = [ @@ -13,19 +13,10 @@ namespace SourceGit.Models 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, string k, IBrush b) - { - Name = n; - Desc = d; - Arg = a; - Key = k; - Color = b; - } + public string Name { get; set; } = n; + public string Desc { get; set; } = d; + public string Arg { get; set; } = a; + public string Key { get; set; } = k; + public IBrush Color { get; set; } = b; } } diff --git a/src/Models/Submodule.cs b/src/Models/Submodule.cs index ca73a8de..17080713 100644 --- a/src/Models/Submodule.cs +++ b/src/Models/Submodule.cs @@ -14,6 +14,7 @@ public string Path { get; set; } = string.Empty; public string SHA { get; set; } = string.Empty; public string URL { get; set; } = string.Empty; + public string Branch { get; set; } = string.Empty; public SubmoduleStatus Status { get; set; } = SubmoduleStatus.Normal; public bool IsDirty => Status > SubmoduleStatus.NotInited; } diff --git a/src/Models/TemplateEngine.cs b/src/Models/TemplateEngine.cs index 8f60bd74..c2ce28b2 100644 --- a/src/Models/TemplateEngine.cs +++ b/src/Models/TemplateEngine.cs @@ -88,9 +88,7 @@ namespace SourceGit.Models { var c = Peek(); if (c is not null) - { _pos++; - } return c; } @@ -129,7 +127,7 @@ namespace SourceGit.Models { case ESCAPE: // allow to escape only \ and $ - if (Peek() is { } nc && (nc == ESCAPE || nc == VARIABLE_ANCHOR)) + if (Peek() is ESCAPE or VARIABLE_ANCHOR) { esc = true; FlushText(tok, _pos - 1); @@ -320,9 +318,7 @@ namespace SourceGit.Models private static string EvalVariable(Context context, string name) { if (!s_variables.TryGetValue(name, out var getter)) - { return string.Empty; - } return getter(context); } @@ -334,9 +330,7 @@ namespace SourceGit.Models private static string EvalVariable(Context context, SlicedVariable variable) { if (!s_slicedVariables.TryGetValue(variable.name, out var getter)) - { return string.Empty; - } return getter(context, variable.count); } diff --git a/src/Models/TextInlineChange.cs b/src/Models/TextInlineChange.cs index afe5bec4..bc1873e2 100644 --- a/src/Models/TextInlineChange.cs +++ b/src/Models/TextInlineChange.cs @@ -50,7 +50,7 @@ namespace SourceGit.Models var ret = new List(); var posOld = 0; var posNew = 0; - var last = null as TextInlineChange; + TextInlineChange last = null; do { while (posOld < sizeOld && posNew < sizeNew && !chunksOld[posOld].Modified && !chunksNew[posNew].Modified) @@ -295,16 +295,12 @@ namespace SourceGit.Models private static void AddChunk(List chunks, Dictionary hashes, string data, int start) { - if (hashes.TryGetValue(data, out var hash)) - { - chunks.Add(new Chunk(hash, start, data.Length)); - } - else + if (!hashes.TryGetValue(data, out var hash)) { hash = hashes.Count; hashes.Add(data, hash); - chunks.Add(new Chunk(hash, start, data.Length)); } + chunks.Add(new Chunk(hash, start, data.Length)); } } } diff --git a/src/Models/TextMateHelper.cs b/src/Models/TextMateHelper.cs index b7efae72..e9903890 100644 --- a/src/Models/TextMateHelper.cs +++ b/src/Models/TextMateHelper.cs @@ -33,7 +33,7 @@ namespace SourceGit.Models var extension = Path.GetExtension(file); if (extension == ".h") extension = ".cpp"; - else if (extension == ".resx" || extension == ".plist" || extension == ".manifest") + else if (extension is ".resx" or ".plist" or ".manifest") extension = ".xml"; else if (extension == ".command") extension = ".sh"; diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index 4d6656e3..9ba7ee9c 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -256,6 +256,7 @@ namespace SourceGit.Models if (name.Equals(".gitmodules", StringComparison.Ordinal)) { _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); + _updateWC = DateTime.Now.AddSeconds(1).ToFileTime(); return; } diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index 3f6de903..03608649 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -52,6 +52,7 @@ namespace SourceGit.Native finder.VSCode(() => FindExecutable("code")); finder.VSCodeInsiders(() => FindExecutable("code-insiders")); finder.VSCodium(() => FindExecutable("codium")); + finder.Cursor(() => FindExecutable("cursor")); finder.Fleet(FindJetBrainsFleet); finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox"); finder.SublimeText(() => FindExecutable("subl")); diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index 8ee84cb5..1e7ac59e 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -74,6 +74,7 @@ namespace SourceGit.Native finder.VSCode(() => "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"); finder.VSCodeInsiders(() => "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"); finder.VSCodium(() => "/Applications/VSCodium.app/Contents/Resources/app/bin/codium"); + finder.Cursor(() => "/Applications/Cursor.app/Contents/Resources/app/bin/cursor"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet"); finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Library/Application Support/JetBrains/Toolbox"); finder.SublimeText(() => "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl"); diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index 02d8142b..e71bf782 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -67,7 +67,7 @@ namespace SourceGit.Native window.ExtendClientAreaToDecorationsHint = true; window.Classes.Add("fix_maximized_padding"); - Win32Properties.AddWndProcHookCallback(window, (IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled) => + Win32Properties.AddWndProcHookCallback(window, (IntPtr hWnd, uint msg, IntPtr _, IntPtr lParam, ref bool handled) => { // Custom WM_NCHITTEST if (msg == 0x0084) @@ -184,6 +184,7 @@ namespace SourceGit.Native finder.VSCode(FindVSCode); finder.VSCodeInsiders(FindVSCodeInsiders); finder.VSCodium(FindVSCodium); + finder.Cursor(FindCursor); finder.Fleet(() => $@"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\Programs\Fleet\Fleet.exe"); finder.FindJetBrainsFromToolbox(() => $@"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\JetBrains\Toolbox"); finder.SublimeText(FindSublimeText); @@ -257,7 +258,7 @@ namespace SourceGit.Native { // Schedule the DWM frame extension to run in the next render frame // to ensure proper timing with the window initialization sequence - Dispatcher.UIThread.InvokeAsync(() => + Dispatcher.UIThread.Post(() => { var platformHandle = w.TryGetPlatformHandle(); if (platformHandle == null) @@ -387,6 +388,20 @@ namespace SourceGit.Native return string.Empty; } + + private string FindCursor() + { + var cursorPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Programs", + "Cursor", + "Cursor.exe"); + + if (File.Exists(cursorPath)) + return cursorPath; + + return string.Empty; + } #endregion private void OpenFolderAndSelectFile(string folderPath) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index b1528c5a..6289ad18 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -89,7 +89,7 @@ M896 64H128C96 64 64 96 64 128v768c0 32 32 64 64 64h768c32 0 64-32 64-64V128c0-32-32-64-64-64z m-64 736c0 16-17 32-32 32H224c-18 0-32-12-32-32V224c0-16 16-32 32-32h576c15 0 32 16 32 32v576zM512 384c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z M0 512M1024 512M512 0M512 1024M813 448c-46 0-83 37-83 83 0 46 37 83 83 83 46 0 83-37 83-83 0-46-37-83-83-83zM211 448C165 448 128 485 128 531c0 46 37 83 83 83 46 0 83-37 83-83 0-46-37-83-83-83zM512 448c-46 0-83 37-83 83 0 46 37 83 83 83 46 0 83-37 83-83C595 485 558 448 512 448z M299 811 299 725 384 725 384 811 299 811M469 811 469 725 555 725 555 811 469 811M640 811 640 725 725 725 725 811 640 811M299 640 299 555 384 555 384 640 299 640M469 640 469 555 555 555 555 640 469 640M640 640 640 555 725 555 725 640 640 640M299 469 299 384 384 384 384 469 299 469M469 469 469 384 555 384 555 469 469 469M640 469 640 384 725 384 725 469 640 469M299 299 299 213 384 213 384 299 299 299M469 299 469 213 555 213 555 299 469 299M640 299 640 213 725 213 725 299 640 299Z - M64 363l0 204 265 0L329 460c0-11 6-18 14-20C349 437 355 437 362 441c93 60 226 149 226 149 33 22 34 60 0 82 0 0-133 89-226 149-14 9-32-3-32-18l-1-110L64 693l0 117c0 41 34 75 75 75l746 0c41 0 75-34 75-74L960 364c0-0 0-1 0-1L64 363zM64 214l0 75 650 0-33-80c-16-38-62-69-103-69l-440 0C97 139 64 173 64 214z + M64 363l0 204 265 0L329 460c0-11 6-18 14-20C349 437 355 437 362 441c93 60 226 149 226 149 33 22 34 60 0 82 0 0-133 89-226 149-14 9-32-3-32-18l-1-110L64 693l0 117c0 41 34 75 75 75l746 0c41 0 75-34 75-74L960 364c0-0 0-1 0-1L64 363zM64 214l0 75 650 0-33-80c-16-38-62-69-103-69l-440 0C97 139 64 173 64 214z M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z M841 627A43 43 0 00811 555h-299v85h196l-183 183A43 43 0 00555 896h299v-85h-196l183-183zM299 170H213v512H85l171 171 171-171H299zM725 128h-85c-18 0-34 11-40 28l-117 313h91L606 384h154l32 85h91l-117-313A43 43 0 00725 128zm-88 171 32-85h26l32 85h-90z M512 0a512 512 0 01512 512 57 57 0 01-114 0 398 398 0 10-398 398 57 57 0 010 114A512 512 0 01512 0zm367 600 121 120a57 57 0 01-80 81l-40-40V967a57 57 0 01-50 57l-7 0a57 57 0 01-57-57v-205l-40 40a57 57 0 01-75 5l-5-5a57 57 0 01-0-80l120-121a80 80 0 01113-0zM512 272a57 57 0 0157 57V499h114a57 57 0 0156 50L740 556a57 57 0 01-57 57H512a57 57 0 01-57-57v-228a57 57 0 0150-57L512 272z diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/CL.png b/src/Resources/Images/ExternalToolIcons/JetBrains/CL.png index 9d9cf6bd..01902a92 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/CL.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/CL.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/DB.png b/src/Resources/Images/ExternalToolIcons/JetBrains/DB.png index c7326287..694713cd 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/DB.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/DB.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/DL.png b/src/Resources/Images/ExternalToolIcons/JetBrains/DL.png index 2715c1dd..fc76eb93 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/DL.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/DL.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/DS.png b/src/Resources/Images/ExternalToolIcons/JetBrains/DS.png index 417f8337..f456aab9 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/DS.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/DS.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/GO.png b/src/Resources/Images/ExternalToolIcons/JetBrains/GO.png index 744938fc..72cea44c 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/GO.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/GO.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/JB.png b/src/Resources/Images/ExternalToolIcons/JetBrains/JB.png index e9a3d1c0..73a7b8a9 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/JB.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/JB.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/PC.png b/src/Resources/Images/ExternalToolIcons/JetBrains/PC.png index 38ae8274..f3e15a63 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/PC.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/PC.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/PS.png b/src/Resources/Images/ExternalToolIcons/JetBrains/PS.png index e5e2ceaf..e06fe661 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/PS.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/PS.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/PY.png b/src/Resources/Images/ExternalToolIcons/JetBrains/PY.png index 38ae8274..f3e15a63 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/PY.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/PY.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/QA.png b/src/Resources/Images/ExternalToolIcons/JetBrains/QA.png index 499b2eff..a962becf 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/QA.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/QA.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/QD.png b/src/Resources/Images/ExternalToolIcons/JetBrains/QD.png index 225f3652..d2b7c420 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/QD.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/QD.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/RD.png b/src/Resources/Images/ExternalToolIcons/JetBrains/RD.png index a2fa3d33..871988c3 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/RD.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/RD.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/RM.png b/src/Resources/Images/ExternalToolIcons/JetBrains/RM.png index 1adfa654..5fe13b62 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/RM.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/RM.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/RR.png b/src/Resources/Images/ExternalToolIcons/JetBrains/RR.png index ee27634a..5f72b09b 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/RR.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/RR.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/WRS.png b/src/Resources/Images/ExternalToolIcons/JetBrains/WRS.png index 2d8f9c36..5ea6cd56 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/WRS.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/WRS.png differ diff --git a/src/Resources/Images/ExternalToolIcons/JetBrains/WS.png b/src/Resources/Images/ExternalToolIcons/JetBrains/WS.png index 09dda1fc..6b5cb0f2 100644 Binary files a/src/Resources/Images/ExternalToolIcons/JetBrains/WS.png and b/src/Resources/Images/ExternalToolIcons/JetBrains/WS.png differ diff --git a/src/Resources/Images/ExternalToolIcons/beyond_compare.png b/src/Resources/Images/ExternalToolIcons/beyond_compare.png index c7aaf18b..dc05f32c 100644 Binary files a/src/Resources/Images/ExternalToolIcons/beyond_compare.png and b/src/Resources/Images/ExternalToolIcons/beyond_compare.png differ diff --git a/src/Resources/Images/ExternalToolIcons/codium.png b/src/Resources/Images/ExternalToolIcons/codium.png index 10abb719..58c49bcc 100644 Binary files a/src/Resources/Images/ExternalToolIcons/codium.png and b/src/Resources/Images/ExternalToolIcons/codium.png differ diff --git a/src/Resources/Images/ExternalToolIcons/cursor.png b/src/Resources/Images/ExternalToolIcons/cursor.png new file mode 100644 index 00000000..1e0092bb Binary files /dev/null and b/src/Resources/Images/ExternalToolIcons/cursor.png differ diff --git a/src/Resources/Images/ExternalToolIcons/fleet.png b/src/Resources/Images/ExternalToolIcons/fleet.png index 5e9d84f6..55232784 100644 Binary files a/src/Resources/Images/ExternalToolIcons/fleet.png and b/src/Resources/Images/ExternalToolIcons/fleet.png differ diff --git a/src/Resources/Images/ExternalToolIcons/git.png b/src/Resources/Images/ExternalToolIcons/git.png index a05a1322..034f3b9c 100644 Binary files a/src/Resources/Images/ExternalToolIcons/git.png and b/src/Resources/Images/ExternalToolIcons/git.png differ diff --git a/src/Resources/Images/ExternalToolIcons/kdiff3.png b/src/Resources/Images/ExternalToolIcons/kdiff3.png index 1e2a0bbb..640aacbe 100644 Binary files a/src/Resources/Images/ExternalToolIcons/kdiff3.png and b/src/Resources/Images/ExternalToolIcons/kdiff3.png differ diff --git a/src/Resources/Images/ExternalToolIcons/meld.png b/src/Resources/Images/ExternalToolIcons/meld.png index b9885f15..59cdc7c5 100644 Binary files a/src/Resources/Images/ExternalToolIcons/meld.png and b/src/Resources/Images/ExternalToolIcons/meld.png differ diff --git a/src/Resources/Images/ExternalToolIcons/p4merge.png b/src/Resources/Images/ExternalToolIcons/p4merge.png index 010d8f6f..352fc21d 100644 Binary files a/src/Resources/Images/ExternalToolIcons/p4merge.png and b/src/Resources/Images/ExternalToolIcons/p4merge.png differ diff --git a/src/Resources/Images/ExternalToolIcons/plastic_merge.png b/src/Resources/Images/ExternalToolIcons/plastic_merge.png index 0d82fc86..53d2e336 100644 Binary files a/src/Resources/Images/ExternalToolIcons/plastic_merge.png and b/src/Resources/Images/ExternalToolIcons/plastic_merge.png differ diff --git a/src/Resources/Images/ExternalToolIcons/rider.png b/src/Resources/Images/ExternalToolIcons/rider.png index 6ab3b8cb..411d7732 100644 Binary files a/src/Resources/Images/ExternalToolIcons/rider.png and b/src/Resources/Images/ExternalToolIcons/rider.png differ diff --git a/src/Resources/Images/ExternalToolIcons/sublime_text.png b/src/Resources/Images/ExternalToolIcons/sublime_text.png index 89e4a286..ca0cdebf 100644 Binary files a/src/Resources/Images/ExternalToolIcons/sublime_text.png and b/src/Resources/Images/ExternalToolIcons/sublime_text.png differ diff --git a/src/Resources/Images/ExternalToolIcons/tortoise_merge.png b/src/Resources/Images/ExternalToolIcons/tortoise_merge.png index eb5b1a8a..10ffe0ef 100644 Binary files a/src/Resources/Images/ExternalToolIcons/tortoise_merge.png and b/src/Resources/Images/ExternalToolIcons/tortoise_merge.png differ diff --git a/src/Resources/Images/ExternalToolIcons/vs.png b/src/Resources/Images/ExternalToolIcons/vs.png index d79fbcfd..072ee5cb 100644 Binary files a/src/Resources/Images/ExternalToolIcons/vs.png and b/src/Resources/Images/ExternalToolIcons/vs.png differ diff --git a/src/Resources/Images/ExternalToolIcons/vscode.png b/src/Resources/Images/ExternalToolIcons/vscode.png index 23b6af54..9c15c8d7 100644 Binary files a/src/Resources/Images/ExternalToolIcons/vscode.png and b/src/Resources/Images/ExternalToolIcons/vscode.png differ diff --git a/src/Resources/Images/ExternalToolIcons/vscode_insiders.png b/src/Resources/Images/ExternalToolIcons/vscode_insiders.png index 38fe8a1e..b48ed667 100644 Binary files a/src/Resources/Images/ExternalToolIcons/vscode_insiders.png and b/src/Resources/Images/ExternalToolIcons/vscode_insiders.png differ diff --git a/src/Resources/Images/ExternalToolIcons/win_merge.png b/src/Resources/Images/ExternalToolIcons/win_merge.png index 8f9f53d4..41077691 100644 Binary files a/src/Resources/Images/ExternalToolIcons/win_merge.png and b/src/Resources/Images/ExternalToolIcons/win_merge.png differ diff --git a/src/Resources/Images/ExternalToolIcons/xcode.png b/src/Resources/Images/ExternalToolIcons/xcode.png index faccf441..6c7d7202 100644 Binary files a/src/Resources/Images/ExternalToolIcons/xcode.png and b/src/Resources/Images/ExternalToolIcons/xcode.png differ diff --git a/src/Resources/Images/ExternalToolIcons/zed.png b/src/Resources/Images/ExternalToolIcons/zed.png index 07c4c50f..f2b5cead 100644 Binary files a/src/Resources/Images/ExternalToolIcons/zed.png and b/src/Resources/Images/ExternalToolIcons/zed.png differ diff --git a/src/Resources/Images/ShellIcons/cmd.png b/src/Resources/Images/ShellIcons/cmd.png index efa27dd4..aa118434 100644 Binary files a/src/Resources/Images/ShellIcons/cmd.png and b/src/Resources/Images/ShellIcons/cmd.png differ diff --git a/src/Resources/Images/ShellIcons/custom.png b/src/Resources/Images/ShellIcons/custom.png index 0175688f..6b11e3d3 100644 Binary files a/src/Resources/Images/ShellIcons/custom.png and b/src/Resources/Images/ShellIcons/custom.png differ diff --git a/src/Resources/Images/ShellIcons/deepin-terminal.png b/src/Resources/Images/ShellIcons/deepin-terminal.png index 78eef1b4..3bc708cd 100644 Binary files a/src/Resources/Images/ShellIcons/deepin-terminal.png and b/src/Resources/Images/ShellIcons/deepin-terminal.png differ diff --git a/src/Resources/Images/ShellIcons/foot.png b/src/Resources/Images/ShellIcons/foot.png index c5dbd0a5..a5ce6fdf 100644 Binary files a/src/Resources/Images/ShellIcons/foot.png and b/src/Resources/Images/ShellIcons/foot.png differ diff --git a/src/Resources/Images/ShellIcons/git-bash.png b/src/Resources/Images/ShellIcons/git-bash.png index 767e0a4e..e48485cb 100644 Binary files a/src/Resources/Images/ShellIcons/git-bash.png and b/src/Resources/Images/ShellIcons/git-bash.png differ diff --git a/src/Resources/Images/ShellIcons/gnome-terminal.png b/src/Resources/Images/ShellIcons/gnome-terminal.png index f9edd2e3..3fa56a81 100644 Binary files a/src/Resources/Images/ShellIcons/gnome-terminal.png and b/src/Resources/Images/ShellIcons/gnome-terminal.png differ diff --git a/src/Resources/Images/ShellIcons/iterm2.png b/src/Resources/Images/ShellIcons/iterm2.png index 16fbd3bd..c9410f56 100644 Binary files a/src/Resources/Images/ShellIcons/iterm2.png and b/src/Resources/Images/ShellIcons/iterm2.png differ diff --git a/src/Resources/Images/ShellIcons/kitty.png b/src/Resources/Images/ShellIcons/kitty.png index 465c2863..6ed70319 100644 Binary files a/src/Resources/Images/ShellIcons/kitty.png and b/src/Resources/Images/ShellIcons/kitty.png differ diff --git a/src/Resources/Images/ShellIcons/konsole.png b/src/Resources/Images/ShellIcons/konsole.png index e1dbcd49..f9fe06ea 100644 Binary files a/src/Resources/Images/ShellIcons/konsole.png and b/src/Resources/Images/ShellIcons/konsole.png differ diff --git a/src/Resources/Images/ShellIcons/lxterminal.png b/src/Resources/Images/ShellIcons/lxterminal.png index 99f62683..19f29475 100644 Binary files a/src/Resources/Images/ShellIcons/lxterminal.png and b/src/Resources/Images/ShellIcons/lxterminal.png differ diff --git a/src/Resources/Images/ShellIcons/mac-terminal.png b/src/Resources/Images/ShellIcons/mac-terminal.png index 569af756..62099f3f 100644 Binary files a/src/Resources/Images/ShellIcons/mac-terminal.png and b/src/Resources/Images/ShellIcons/mac-terminal.png differ diff --git a/src/Resources/Images/ShellIcons/mate-terminal.png b/src/Resources/Images/ShellIcons/mate-terminal.png index d48cedfa..7dc2945a 100644 Binary files a/src/Resources/Images/ShellIcons/mate-terminal.png and b/src/Resources/Images/ShellIcons/mate-terminal.png differ diff --git a/src/Resources/Images/ShellIcons/ptyxis.png b/src/Resources/Images/ShellIcons/ptyxis.png index 9202f6e1..71d62d5a 100644 Binary files a/src/Resources/Images/ShellIcons/ptyxis.png and b/src/Resources/Images/ShellIcons/ptyxis.png differ diff --git a/src/Resources/Images/ShellIcons/pwsh.png b/src/Resources/Images/ShellIcons/pwsh.png index 12edf757..ebb9e0da 100644 Binary files a/src/Resources/Images/ShellIcons/pwsh.png and b/src/Resources/Images/ShellIcons/pwsh.png differ diff --git a/src/Resources/Images/ShellIcons/warp.png b/src/Resources/Images/ShellIcons/warp.png index 7d604d8e..e053468d 100644 Binary files a/src/Resources/Images/ShellIcons/warp.png and b/src/Resources/Images/ShellIcons/warp.png differ diff --git a/src/Resources/Images/ShellIcons/wezterm.png b/src/Resources/Images/ShellIcons/wezterm.png index ed7a659f..9a80820b 100644 Binary files a/src/Resources/Images/ShellIcons/wezterm.png and b/src/Resources/Images/ShellIcons/wezterm.png differ diff --git a/src/Resources/Images/ShellIcons/wt.png b/src/Resources/Images/ShellIcons/wt.png index f2a874f4..01e48f0b 100644 Binary files a/src/Resources/Images/ShellIcons/wt.png and b/src/Resources/Images/ShellIcons/wt.png differ diff --git a/src/Resources/Images/ShellIcons/xfce4-terminal.png b/src/Resources/Images/ShellIcons/xfce4-terminal.png index 9eda3d00..eed9223e 100644 Binary files a/src/Resources/Images/ShellIcons/xfce4-terminal.png and b/src/Resources/Images/ShellIcons/xfce4-terminal.png differ diff --git a/src/Resources/Images/github.png b/src/Resources/Images/github.png index d3c211da..54658efa 100644 Binary files a/src/Resources/Images/github.png and b/src/Resources/Images/github.png differ diff --git a/src/Resources/Images/unreal.png b/src/Resources/Images/unreal.png index 01ceeb31..85d19999 100644 Binary files a/src/Resources/Images/unreal.png and b/src/Resources/Images/unreal.png differ diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 741ddcb6..99e5f12d 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -44,11 +44,11 @@ ENTFERNEN Bild laden... Aktualisieren - BINÄRE DATEI NICHT UNTERSTÜTZT!!! + BINÄRE DATEI WIRD NICHT UNTERSTÜTZT!!! Bisect Abbrechen Schlecht - Bisecting. Ist der aktuelle HEAD gut oder fehlerhaft? + Bisecting. Ist der aktuelle HEAD gut oder schlecht? Gut Überspringen Bisecting. Aktuellen Commit als gut oder schlecht markieren und einen anderen auschecken. @@ -74,7 +74,7 @@ Setze ${0}$ zurück auf ${1}$... Setze verfolgten Branch... Branch Vergleich - Ungültiger upstream! + Ungültiger Upstream! Bytes ABBRECHEN Auf Vorgänger-Revision zurücksetzen @@ -121,7 +121,6 @@ Mit Worktree vergleichen Author Committer - Information SHA Betreff Benutzerdefinierte Aktion @@ -185,12 +184,12 @@ Standard Remote Bevorzugter Merge Modus TICKETSYSTEM - Beispiel Azure DevOps Rule hinzufügen - Beispiel für Gitee Issue Regel einfügen - Beispiel für Gitee Pull Request Regel einfügen - Beispiel für Github-Regel hinzufügen - Beispiel für Gitlab Issue Regel einfügen - Beispiel für Gitlab Merge Request einfügen + Beispiel für Azure DevOps Regel hinzufügen + Beispiel für Gitee Issue Regel hinzufügen + Beispiel für Gitee Pull Request Regel hinzufügen + Beispiel für Github Regel hinzufügen + Beispiel für Gitlab Issue Regel hinzufügen + Beispiel für Gitlab Merge Request Regel hinzufügen Beispiel für Jira-Regel hinzufügen Neue Regel Ticketnummer Regex-Ausdruck: @@ -208,10 +207,11 @@ Wert bei Markierung: Wenn markiert, wird dieser Wert in Kommandozeilen-Argumenten benutzt Beschreibung: - Dient als Platzhalter in TextBox/Path Selector (Pfadeingabe) oder als Tooltip in CheckBox (Kontrollkästchen) Standardwert: - Ordner? + Auf Ordner einschränken Bezeichnung: + Einträge: + Nutze '|', um Einträge zu trennen Typ: Arbeitsplätze Farbe @@ -229,7 +229,7 @@ Kurzbeschreibung: Typ der Änderung: Kopieren - Kopiere gesamten Text + Gesamten Text kopieren Ganzen Pfad kopieren Pfad kopieren Branch erstellen... @@ -239,7 +239,7 @@ Verwerfen Stashen & wieder anwenden Neuer Branch-Name: - Branch-Namen eingeben. + Branch-Name Leerzeichen werden durch Bindestriche ersetzt. Lokalen Branch erstellen Überschreibe existierenden Branch @@ -307,6 +307,7 @@ Mehr Zeilen anzeigen WÄHLE EINE DATEI AUS UM ÄNDERUNGEN ANZUZEIGEN Öffne in Merge Tool + Verzeichnisverlauf Änderungen verwerfen Alle Änderungen in der Arbeitskopie. Änderungen: @@ -326,7 +327,7 @@ Ohne Tags fetchen Remote: Remote-Änderungen fetchen - Als unverändert annehmen + Als unverändert betrachten Verwerfen... Verwerfe {0} Dateien... Verwerfe Änderungen in ausgewählten Zeilen @@ -350,11 +351,11 @@ Development-Branch: Feature: Feature-Prefix: - FLOW - Finish Feature - FLOW - Finish Hotfix - FLOW - Finish Release + FLOW - Feature abschließen + FLOW - Hotfix abschließen + FLOW - Release abschließen Ziel: - Push zu Remote(s) nach Durchführung des Finish + Push zu Remote(s) nach Abschluss Squash beim Merge Hotfix: Hotfix-Prefix: @@ -388,7 +389,7 @@ Entsperren Erzwinge entsperren Prune - Führt `git lfs prune` aus um alte LFS Dateien von lokalem Speicher zu löschen + Führt `git lfs prune` aus um alte LFS Dateien vom lokalen Speicher zu löschen Pull Führt `git lfs pull` aus um alle Git LFS Dasteien für aktuellen Ref & Checkout herunterzuladen LFS Objekte pullen @@ -401,7 +402,7 @@ Verlauf AUTOR AUTOR ZEITPUNKT - GRAPH & COMMIT-NACHRICHT + VERLAUF & COMMIT-NACHRICHT SHA COMMIT ZEITPUNKT {0} COMMITS AUSGEWÄHLT @@ -447,13 +448,13 @@ Initialisiere Repository Pfad: Cherry-Pick wird durchgeführt. - Verarbeite commit - Merge request wird durchgeführt. + Verarbeite Commit + Merge Request wird durchgeführt. Verarbeite Rebase wird durchgeführt. Angehalten bei Revert wird durchgeführt. - Reverte commit + Reverte Commit Interaktiver Rebase Auf: Ziel Branch: @@ -472,7 +473,7 @@ Alle Änderungen committen Strategie: Ziele: - Bewege Repository Knoten + Verschiebe Repository Knoten Wähle Vorgänger-Knoten für: Name: Git wurde NICHT konfiguriert. Gehe bitte zuerst in die [Einstellungen] und konfiguriere Git. @@ -500,9 +501,9 @@ Verwende 'Shift+Enter' um eine neue Zeile einzufügen. 'Enter' ist das Kürzel für den OK Button Einstellungen OPEN AI - Analysierung des Diff Befehl + System-Prompt für Diff Analyse API Schlüssel - Generiere Nachricht Befehl + System-Prompt für Erstellung von Commit-Nachricht Modell Name Server @@ -521,7 +522,7 @@ Verwende nativen Fensterrahmen DIFF/MERGE TOOL Installationspfad - Installationspfad zum Diff/Merge Tool + Pfad zum Diff/Merge Tool Tool ALLGEMEIN Beim Starten nach Updates suchen @@ -530,15 +531,15 @@ Commit-Historie Zeige Autor Zeitpunkt anstatt Commit Zeitpunkt Zeige Nachfolger in den Commit Details - Zeige Tags im Commit Graph + Zeige Tags im Commit Verlauf Längenvorgabe für Commit-Nachrichten GIT Aktiviere Auto-CRLF - Klon Standardordner + Standard Klon-Ordner Benutzer Email Globale Git Benutzer Email Aktiviere --prune beim Fetchen - Aktiviere --ignore-cr-at-eol beim Unterschied + Aktiviere --ignore-cr-at-eol beim Diff Diese App setzt Git (>= 2.25.1) voraus Installationspfad Aktiviere HTTP SSL Verifizierung @@ -549,11 +550,11 @@ Commit-Signierung GPG Format GPG Installationspfad - Installationspfad zum GPG Programm + Pfad zum GPG Programm Tag-Signierung Benutzer Signierungsschlüssel GPG Benutzer Signierungsschlüssel - EINBINDUNGEN + INTEGRATIONEN SHELL/TERMINAL Pfad Shell/Terminal @@ -577,7 +578,7 @@ Lokaler Branch: Remote: Revision: - Push Revision zu Remote-Branch + Revision zu Remote-Branch pushen Push Remote-Branch: Remote-Branch verfolgen @@ -603,7 +604,7 @@ Fetch Im Browser öffnen Prune - Bestätige das entfernen des Worktrees + Bestätige das Entfernen des Worktrees Aktiviere `--force` Option Ziel: Branch umbenennen @@ -613,7 +614,7 @@ ABBRECHEN Änderungen automatisch von Remote fetchen... Sortieren - Nach Commit Datum + Nach Commit-Datum Nach Name Aufräumen (GC & Prune) Führt `git gc` auf diesem Repository aus. @@ -627,10 +628,10 @@ Aktiviere '--reflog' Option Öffne im Datei-Browser Suche Branches/Tags/Submodule - Sichtbarkeit im Graphen + Sichtbarkeit im Verlauf Aufheben - Im Graph ausblenden - Im Graph filtern + Im Verlauf ausblenden + Im Verlauf filtern Aktiviere '--first-parent' Option LAYOUT Horizontal @@ -642,10 +643,10 @@ Zum HEAD wechseln Erstelle Branch BENACHRICHTIGUNGEN LÖSCHEN - Nur aktuellen Branch im Graphen hervorheben + Nur aktuellen Branch im Verlauf hervorheben Öffne in {0} Öffne in externen Tools - Aktualisiern + Aktualisieren REMOTES REMOTE HINZUFÜGEN Commit suchen @@ -665,7 +666,7 @@ SUBMODUL AKTUALISIEREN TAGS NEUER TAG - Nach Erstellungsdatum + Nach Erstelldatum Nach Namen Sortiere Öffne im Terminal @@ -687,7 +688,7 @@ Commit rückgängig machen Commit: Commit Änderungen rückgängig machen - Commit Nachricht umformulieren + Commit-Nachricht umformulieren Bitte warten... SPEICHERN Speichern als... @@ -700,7 +701,7 @@ Download Diese Version überspringen Software Update - Es sind momentan kein Updates verfügbar. + Es sind momentan keine Updates verfügbar. Setze verfolgten Branch Branch: Upstream Verfolgung aufheben @@ -715,7 +716,7 @@ Stash Inklusive nicht-verfolgter Dateien Name: - Optional. Informationen zu dieses Stashes + Optional. Informationen zu diesem Stash Modus: Nur gestagte Änderungen Gestagte und unstagte Änderungen der ausgewähleten Datei(en) werden gestasht!!! @@ -723,7 +724,7 @@ Anwenden Kopiere Nachricht Entfernen - Als Path speichern... + Als Patch speichern... Stash entfernen Entfernen: Stashes @@ -739,12 +740,12 @@ COMMITS: SUBMODULE Submodul hinzufügen - Relativen Pfad kopieren + Relativen Pfad De-initialisiere Submodul Untergeordnete Submodule fetchen Öffne Submodul Repository Relativer Pfad: - Relativer Ordner um dieses Submodul zu speichern. + Relativer Pfad um dieses Submodul zu speichern. Submodul löschen STATUS geändert @@ -778,11 +779,11 @@ Lösche DRAG & DROP VON ORDNER UNTERSTÜTZT. BENUTZERDEFINIERTE GRUPPIERUNG UNTERSTÜTZT. Bearbeiten - Bewege in eine andere Gruppe + Verschiebe in eine andere Gruppe Öffne alle Repositories Öffne Repository Öffne Terminal - Klon Standardordner erneut nach Repositories durchsuchen + Standard Klon-Ordner erneut nach Repositories durchsuchen Suche Repositories... Sortieren Änderungen @@ -806,7 +807,7 @@ ALLE KONFLIKTE IN EXTERNEM MERGE-TOOL ÖFFNEN DATEI KONFLIKTE GELÖST MEINE VERSION VERWENDEN - DEREN VERSION VERWENDEN + IHRE VERSION VERWENDEN NICHT-VERFOLGTE DATEIEN INKLUDIEREN KEINE BISHERIGEN COMMIT-NACHRICHTEN KEINE COMMIT TEMPLATES @@ -819,7 +820,7 @@ UNSTAGED STAGEN ALLES STAGEN - ALS UNVERÄNDERT ANGENOMMENE ANZEIGEN + ALS UNVERÄNDERT BETRACHTETE ANZEIGEN Template: ${0}$ ARBEITSPLATZ: Arbeitsplätze konfigurieren... diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 2ce384c0..99c422f4 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -80,6 +80,9 @@ Show as File and Dir List Show as Path List Show as Filesystem Tree + Change Submodule's URL + Submodule: + URL: Checkout Branch Checkout Commit Commit: @@ -116,8 +119,8 @@ Compare with HEAD Compare with Worktree Author + Message Committer - Information SHA Subject Custom Action @@ -204,10 +207,11 @@ Checked Value: When checked, this value will be used in command-line arguments Description: - Used as placeholder in TextBox/PathSelector or tooltip in CheckBox Default: Is Folder: Label: + Options: + Use '|' as delimitter for options Type: Workspaces Color @@ -303,6 +307,7 @@ Increase Number of Visible Lines SELECT FILE TO VIEW CHANGES Open in Merge Tool + Dir Histories Discard Changes All local changes in working copy. Changes: @@ -468,6 +473,9 @@ Commit all changes Strategy: Targets: + Move Submodule + Move To: + Submodule: Move Repository Node Select parent node for: Name: @@ -697,6 +705,11 @@ Skip This Version Software Update There are currently no updates available. + Set Submodule's Branch + Submodule: + Current: + Change To: + Optional. Set to default when it is empty. Set Tracking Branch Branch: Unset upstream @@ -735,18 +748,24 @@ COMMITS: SUBMODULES Add Submodule - Copy Relative Path - De-initialize Submodule + Branch + Relative Path + De-initialize Fetch nested submodules - Open Submodule Repository + Histories + Move To + Open Repository Relative Path: Relative folder to store this module. - Delete Submodule + Delete + Set Branch + Change URL STATUS modified not initialized revision changed unmerged + Update URL OK Copy Tag Name diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 5d45798a..8654e5ed 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -121,7 +121,6 @@ Comparar con Worktree Autor Committer - Información SHA Asunto Acción personalizada @@ -208,7 +207,6 @@ Valor Comprobado: Cuando sea comprobado, este valor será usado en argumentos de la línea de comandos Descripción: - Utilizado como marcador de posición en CajaDeTexto/SelectorDeRuta ó tooltip en CheckBox Por defecto: Es Carpeta: Etiqueta: @@ -739,7 +737,7 @@ COMMITS: SUBMÓDULOS Añadir Submódulo - Copiar Ruta Relativa + Ruta Relativa Desinicializar Submódulo Fetch submódulos anidados Abrir Repositorio del Submódulo diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 10e9bc14..55de07a2 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -103,7 +103,6 @@ Cherry-Pick ... Comparer avec HEAD Comparer avec le worktree - Informations SHA Action personnalisée Rebase interactif de ${0}$ ici @@ -665,11 +664,9 @@ COMMITS: SOUS-MODULES Ajouter un sous-module - Copier le chemin relatif + Chemin relatif Fetch les sous-modules imbriqués Ouvrir le dépôt de sous-module - Relative Path: - Relative folder to store this module. Supprimer le sous-module OK Copier le nom du Tag diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index 57075725..9b2102b6 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -113,7 +113,6 @@ Confronta con Worktree Autore Committer - Informazioni SHA Oggetto Azione Personalizzata @@ -693,7 +692,7 @@ COMMIT: SOTTOMODULI Aggiungi Sottomodulo - Copia Percorso Relativo + Percorso Relativo Recupera sottomoduli annidati Apri Repository del Sottomodulo Percorso Relativo: diff --git a/src/Resources/Locales/ja_JP.axaml b/src/Resources/Locales/ja_JP.axaml index 166264c9..3780ffdc 100644 --- a/src/Resources/Locales/ja_JP.axaml +++ b/src/Resources/Locales/ja_JP.axaml @@ -102,7 +102,6 @@ チェリーピック... HEADと比較 ワークツリーと比較 - 情報 SHA カスタムアクション ${0}$ ブランチをここにインタラクティブリベース @@ -663,7 +662,7 @@ コミット: サブモジュール サブモジュールを追加 - 相対パスをコピー + 相対パス ネストされたサブモジュールを取得する サブモジュールのリポジトリを開く 相対パス: diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 7036ee6a..75da3552 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -93,7 +93,6 @@ Cherry-Pick ... Comparar com HEAD Comparar com Worktree - Informações SHA Ação customizada Rebase Interativo ${0}$ até Aqui @@ -603,7 +602,7 @@ COMMITS: SUBMÓDULOS Adicionar Submódulo - Copiar Caminho Relativo + Caminho Relativo Buscar submódulos aninhados Abrir Repositório do Submódulo Caminho Relativo: diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 4558f716..7d5b932b 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -84,6 +84,9 @@ Показывать в виде списка файлов и каталогов Показывать в виде списка путей Показывать в виде дерева файловой системы + Изменить URL-адрес подмодуля + Подмодуль: + URL-адрес: Переключить ветку Переключение ревизии Ревизия: @@ -120,8 +123,8 @@ Сравнить c ГОЛОВОЙ (HEAD) Сравнить с рабочим каталогом Автор + Сообщение Ревизор - Информацию SHA Субъект Пользовательское действие @@ -208,10 +211,11 @@ Проверяемое значение: Если установлено, то данное значение будет использоваться в аргументах командной строки Описание: - Используется в качестве заполнителя в текстовом поле/селекторе пути или всплывающей подсказки в флажке По умолчанию: Это каталог: Метка: + Опции: + Используйте разделитель «|» для опций Тип: Рабочие пространства Цвет @@ -307,6 +311,7 @@ Увеличить количество видимых строк ВЫБЕРИТЕ ФАЙЛ ДЛЯ ПРОСМОТРА ИЗМЕНЕНИЙ Открыть в инструменте слияния + Каталог историй Отклонить изменения Все локальные изменения в рабочей копии. Изменения: @@ -472,6 +477,9 @@ Зафиксировать все изменения Стратегия: Цели: + Переместить подмодуль + Переместить в: + Подмодуль: Переместить репозиторий в другую группу Выбрать группу для: Имя: @@ -701,6 +709,11 @@ Пропустить эту версию Обновление ПО В настоящее время обновления недоступны. + Установить ветку подмодуля + Подмодуль: + Текущий: + Изменить в: + Не обязательно. Установить по умолчанию, когда пусто. Отслеживать ветку Ветка: Снять основную ветку @@ -739,18 +752,24 @@ РЕВИЗИИ: ПОДМОДУЛИ Добавить подмодули - Копировать относительный путь + Ветка + Каталог Удалить подмодуль Извлечение вложенных подмодулей + Истории + Переместить в Открыть подмодуль репозитория Каталог: Относительный путь для хранения подмодуля. Удалить подмодуль + Установить ветку + Изменить URL-адрес СОСТОЯНИЕ изменён не создан ревизия изменена не слита + Обновить URL-адрес ОК Копировать имя метки diff --git a/src/Resources/Locales/ta_IN.axaml b/src/Resources/Locales/ta_IN.axaml index 0e9dd4d1..d6f2ff44 100644 --- a/src/Resources/Locales/ta_IN.axaml +++ b/src/Resources/Locales/ta_IN.axaml @@ -102,7 +102,6 @@ கனி-பறி ... தலையுடன் ஒப்பிடுக பணிமரத்துடன் ஒப்பிடுக - தகவலை பாகொவ-வை தனிப்பயன் செயல் இங்கே ${0}$ ஐ ஊடாடும் வகையில் மறுதளம் @@ -664,7 +663,7 @@ உறுதிமொழிகள்: துணைத் தொகுதி துணைத் தொகுதியைச் சேர் - உறவு பாதையை நகலெடு + உறவு பாதை உள்ளமைக்கப்பட்ட துணைத் தொகுதிகளை எடு துணைத் தொகுதி களஞ்சியத்தைத் திற உறவு பாதை: diff --git a/src/Resources/Locales/uk_UA.axaml b/src/Resources/Locales/uk_UA.axaml index 16799bb6..393eea74 100644 --- a/src/Resources/Locales/uk_UA.axaml +++ b/src/Resources/Locales/uk_UA.axaml @@ -103,7 +103,6 @@ Cherry-pick ... Порівняти з HEAD Порівняти з робочим деревом - Iнформацію SHA Спеціальна дія Інтерактивно перебазувати ${0}$ сюди @@ -669,7 +668,7 @@ КОМІТІВ: ПІДМОДУЛІ Додати підмодуль - Копіювати відносний шлях + відносний шлях Отримати вкладені підмодулі Відкрити сховище підмодуля Відносний шлях: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 8e2eeef0..7fc02833 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -84,6 +84,9 @@ 文件名+路径列表模式 全路径列表模式 文件目录树形结构模式 + 修改子模块远程地址 + 子模块 : + 远程地址 : 检出(checkout)分支 检出(checkout)提交 提交 : @@ -120,8 +123,8 @@ 与当前HEAD比较 与本地工作树比较 作者 + 提交信息 提交者 - 简要信息 提交指纹 主题 自定义操作 @@ -208,10 +211,11 @@ 启用时命令行参数 : 此CheckBox勾选后,该值会被应用于命令行参数 描述 : - TextBox及Path Selector中用作Placeholder,CheckBox中用作ToolTip 默认值 : 目标是否是目录 : 名称 : + 选项列表 : + 选项之间请使用英文 '|' 作为分隔符 类型 : 工作区 颜色 @@ -307,6 +311,7 @@ 增加可见的行数 请选择需要对比的文件 使用外部比对工具查看 + 目录内容变更历史 放弃更改确认 所有本地址未提交的修改。 变更 : @@ -472,6 +477,9 @@ 提交变化 合并策略 : 目标列表 : + 移动子模块 + 移动到 : + 子模块 : 调整仓库分组 请选择目标分组: 名称 : @@ -701,6 +709,11 @@ 忽略此版本 软件更新 当前已是最新版本。 + 修改子模块追踪分支 + 子模块 : + 当前追踪分支 : + 修改为 : + 可选。当不填写时,恢复默认追踪分支。 切换上游分支 本地分支 : 取消追踪 @@ -739,18 +752,24 @@ 提交次数: 子模块 添加子模块 - 复制路径 + 跟踪分支 + 相对路径 取消初始化 拉取子孙模块 + 变更历史 + 移动 打开仓库 相对仓库路径 : 本地存放的相对路径。 - 删除子模块 + 删除 + 修改跟踪分支 + 修改远程地址 状态 未提交修改 未初始化 SHA变更 未解决冲突 + 更新 仓库 确 定 复制标签名 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 732794fb..63273cb0 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -84,6 +84,9 @@ 檔案名稱 + 路徑列表模式 全路徑列表模式 檔案目錄樹狀結構模式 + 修改子模組的遠端網址 + 子模組: + 遠端網址: 簽出 (checkout) 分支 簽出 (checkout) 提交 提交: @@ -120,8 +123,8 @@ 與目前 HEAD 比較 與本機工作區比較 作者 + 提交訊息 提交者 - 摘要資訊 提交編號 標題 自訂動作 @@ -208,10 +211,11 @@ 啟用時的指令行參數: 勾選 CheckBox 後,此值將用於命令列參數中 描述: - 在 TextBox/PathSelector 中用作占位符,或在 CheckBox 中用作工具提示 預設值: 目標路徑是否為資料夾: 名稱: + 選項列表: + 請使用英文「|」符號分隔選項 類型: 工作區 顏色 @@ -307,6 +311,7 @@ 增加可見的行數 請選擇需要對比的檔案 使用外部比對工具檢視 + 目錄内容變更歷史 捨棄變更 所有本機未提交的變更。 變更: @@ -472,6 +477,9 @@ 提交變更 合併策略: 目標列表: + 移動子模組 + 移動到: + 子模組: 調整存放庫分組 請選擇目標分組: 名稱: @@ -701,6 +709,11 @@ 忽略此版本 軟體更新 目前已是最新版本。 + 設定子模組的追蹤分支 + 子模組: + 目前追蹤分支: + 變更為: + 選購。為空時設定為預設值。 切換上游分支 本機分支: 取消設定上游分支 @@ -739,18 +752,24 @@ 提交次數: 子模組 新增子模組 - 複製路徑 + 追蹤分支 + 相對路徑 取消初始化 提取子模組 + 變更歷史 + 移動 開啟存放庫 相對存放庫路徑: 本機存放的相對路徑。 - 刪除子模組 + 刪除 + 更改追蹤分支 + 更改遠端網址 狀態 未提交變更 未初始化 SHA 變更 未解決的衝突 + 更新 存放庫 確 定 複製標籤名稱 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index a20bec81..e328cf26 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -843,6 +843,15 @@ + + + + + + + + + diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj index 5205ae4f..8d76934c 100644 --- a/src/SourceGit.csproj +++ b/src/SourceGit.csproj @@ -40,6 +40,7 @@ + diff --git a/src/ViewModels/AIAssistant.cs b/src/ViewModels/AIAssistant.cs index 8756a30b..920fafcf 100644 --- a/src/ViewModels/AIAssistant.cs +++ b/src/ViewModels/AIAssistant.cs @@ -58,14 +58,14 @@ namespace SourceGit.ViewModels IsGenerating = true; _cancel = new CancellationTokenSource(); - Task.Run(() => + Task.Run(async () => { - new Commands.GenerateCommitMessage(_service, _repo.FullPath, _changes, _cancel.Token, message => + await new Commands.GenerateCommitMessage(_service, _repo.FullPath, _changes, _cancel.Token, message => { - Dispatcher.UIThread.Invoke(() => Text = message); - }).Exec(); + Dispatcher.UIThread.Post(() => Text = message); + }).ExecAsync().ConfigureAwait(false); - Dispatcher.UIThread.Invoke(() => IsGenerating = false); + Dispatcher.UIThread.Post(() => IsGenerating = false); }, _cancel.Token); } diff --git a/src/ViewModels/AddRemote.cs b/src/ViewModels/AddRemote.cs index 37bbc43a..5a6c019f 100644 --- a/src/ViewModels/AddRemote.cs +++ b/src/ViewModels/AddRemote.cs @@ -87,7 +87,7 @@ namespace SourceGit.ViewModels return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Adding remote ..."; @@ -95,24 +95,27 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Add Remote"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Remote(_repo.FullPath).Use(log).Add(_name, _url); - if (succ) - { - new Commands.Config(_repo.FullPath).Use(log).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); - new Commands.Fetch(_repo.FullPath, _name, false, false).Use(log).Exec(); - } + var succ = await new Commands.Remote(_repo.FullPath) + .Use(log) + .AddAsync(_name, _url); - log.Complete(); - CallUIThread(() => - { - _repo.MarkFetched(); - _repo.MarkBranchesDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - return succ; - }); + if (succ) + { + await new Commands.Config(_repo.FullPath) + .Use(log) + .SetAsync($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); + + await new Commands.Fetch(_repo.FullPath, _name, false, false) + .Use(log) + .RunAsync(); + } + + log.Complete(); + + _repo.MarkFetched(); + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/AddSubmodule.cs b/src/ViewModels/AddSubmodule.cs index 82a1f62a..313b2e8e 100644 --- a/src/ViewModels/AddSubmodule.cs +++ b/src/ViewModels/AddSubmodule.cs @@ -40,7 +40,7 @@ namespace SourceGit.ViewModels return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Adding submodule..."; @@ -59,14 +59,13 @@ namespace SourceGit.ViewModels relativePath = Path.GetFileName(_url); } - return Task.Run(() => - { - var succ = new Commands.Submodule(_repo.FullPath).Use(log).Add(_url, relativePath, Recursive); - log.Complete(); + var succ = await new Commands.Submodule(_repo.FullPath) + .Use(log) + .AddAsync(_url, relativePath, Recursive); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/AddToIgnore.cs b/src/ViewModels/AddToIgnore.cs index 74f7e3ec..d2a194f4 100644 --- a/src/ViewModels/AddToIgnore.cs +++ b/src/ViewModels/AddToIgnore.cs @@ -27,35 +27,28 @@ namespace SourceGit.ViewModels StorageFile = Models.GitIgnoreFile.Supported[0]; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Adding Ignored File(s) ..."; - return Task.Run(() => + var file = StorageFile.GetFullPath(_repo.FullPath, _repo.GitDir); + if (!File.Exists(file)) { - var file = StorageFile.GetFullPath(_repo.FullPath, _repo.GitDir); - if (!File.Exists(file)) - { - File.WriteAllLines(file, [_pattern]); - } + await File.WriteAllLinesAsync(file, [_pattern]); + } + else + { + var org = await File.ReadAllTextAsync(file); + if (!org.EndsWith('\n')) + await File.AppendAllLinesAsync(file, ["", _pattern]); else - { - var org = File.ReadAllText(file); - if (!org.EndsWith('\n')) - File.AppendAllLines(file, ["", _pattern]); - else - File.AppendAllLines(file, [_pattern]); - } + await File.AppendAllLinesAsync(file, [_pattern]); + } - CallUIThread(() => - { - _repo.MarkWorkingCopyDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - - return true; - }); + _repo.MarkWorkingCopyDirtyManually(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo; diff --git a/src/ViewModels/AddWorktree.cs b/src/ViewModels/AddWorktree.cs index a089a391..3c3d69ff 100644 --- a/src/ViewModels/AddWorktree.cs +++ b/src/ViewModels/AddWorktree.cs @@ -82,8 +82,7 @@ namespace SourceGit.ViewModels public static ValidationResult ValidateWorktreePath(string path, ValidationContext ctx) { - var creator = ctx.ObjectInstance as AddWorktree; - if (creator == null) + if (ctx.ObjectInstance is not AddWorktree creator) return new ValidationResult("Missing runtime context to create branch!"); if (string.IsNullOrEmpty(path)) @@ -105,7 +104,7 @@ namespace SourceGit.ViewModels return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Adding worktree ..."; @@ -116,14 +115,13 @@ namespace SourceGit.ViewModels Use(log); - return Task.Run(() => - { - var succ = new Commands.Worktree(_repo.FullPath).Use(log).Add(_path, branchName, _createNewBranch, tracking); - log.Complete(); + var succ = await new Commands.Worktree(_repo.FullPath) + .Use(log) + .AddAsync(_path, branchName, _createNewBranch, tracking); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private Repository _repo = null; diff --git a/src/ViewModels/Apply.cs b/src/ViewModels/Apply.cs index c7f3c185..709a66fd 100644 --- a/src/ViewModels/Apply.cs +++ b/src/ViewModels/Apply.cs @@ -41,20 +41,21 @@ namespace SourceGit.ViewModels return new ValidationResult($"File '{file}' can NOT be found!!!"); } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Apply patch..."; var log = _repo.CreateLog("Apply Patch"); - return Task.Run(() => - { - var succ = new Commands.Apply(_repo.FullPath, _patchFile, _ignoreWhiteSpace, SelectedWhiteSpaceMode.Arg, null).Use(log).Exec(); - log.Complete(); + Use(log); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.Apply(_repo.FullPath, _patchFile, _ignoreWhiteSpace, SelectedWhiteSpaceMode.Arg, null) + .Use(log) + .ExecAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/ApplyStash.cs b/src/ViewModels/ApplyStash.cs index e7cd64e9..b21d6fe8 100644 --- a/src/ViewModels/ApplyStash.cs +++ b/src/ViewModels/ApplyStash.cs @@ -28,20 +28,26 @@ namespace SourceGit.ViewModels Stash = stash; } - public override Task Sure() + public override async Task Sure() { + _repo.SetWatcherEnabled(false); ProgressDescription = $"Applying stash: {Stash.Name}"; var log = _repo.CreateLog("Apply Stash"); - return Task.Run(() => - { - var succ = new Commands.Stash(_repo.FullPath).Use(log).Apply(Stash.Name, RestoreIndex); - if (succ && DropAfterApply) - new Commands.Stash(_repo.FullPath).Use(log).Drop(Stash.Name); + Use(log); - log.Complete(); - return true; - }); + var succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .ApplyAsync(Stash.Name, RestoreIndex); + + if (succ && DropAfterApply) + await new Commands.Stash(_repo.FullPath) + .Use(log) + .DropAsync(Stash.Name); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo; diff --git a/src/ViewModels/Archive.cs b/src/ViewModels/Archive.cs index a4a4f6eb..6f86357d 100644 --- a/src/ViewModels/Archive.cs +++ b/src/ViewModels/Archive.cs @@ -44,7 +44,7 @@ namespace SourceGit.ViewModels BasedOn = tag; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Archiving ..."; @@ -52,20 +52,16 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Archive"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Archive(_repo.FullPath, _revision, _saveFile).Use(log).Exec(); - log.Complete(); + var succ = await new Commands.Archive(_repo.FullPath, _revision, _saveFile) + .Use(log) + .ExecAsync(); - CallUIThread(() => - { - _repo.SetWatcherEnabled(true); - if (succ) - App.SendNotification(_repo.FullPath, $"Save archive to : {_saveFile}"); - }); + log.Complete(); + _repo.SetWatcherEnabled(true); - return succ; - }); + if (succ) + App.SendNotification(_repo.FullPath, $"Save archive to : {_saveFile}"); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/AssumeUnchangedManager.cs b/src/ViewModels/AssumeUnchangedManager.cs index 68151448..8cd4a166 100644 --- a/src/ViewModels/AssumeUnchangedManager.cs +++ b/src/ViewModels/AssumeUnchangedManager.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; - using Avalonia.Collections; using Avalonia.Threading; @@ -14,19 +13,25 @@ namespace SourceGit.ViewModels _repo = repo; Files = new AvaloniaList(); - Task.Run(() => + Task.Run(async () => { - var collect = new Commands.QueryAssumeUnchangedFiles(_repo.FullPath).Result(); - Dispatcher.UIThread.Invoke(() => Files.AddRange(collect)); + var collect = await new Commands.QueryAssumeUnchangedFiles(_repo.FullPath) + .GetResultAsync() + .ConfigureAwait(false); + Dispatcher.UIThread.Post(() => Files.AddRange(collect)); }); } - public void Remove(string file) + public async Task RemoveAsync(string file) { if (!string.IsNullOrEmpty(file)) { var log = _repo.CreateLog("Remove Assume Unchanged File"); - new Commands.AssumeUnchanged(_repo.FullPath, file, false).Use(log).Exec(); + + await new Commands.AssumeUnchanged(_repo.FullPath, file, false) + .Use(log) + .ExecAsync(); + log.Complete(); Files.Remove(file); } diff --git a/src/ViewModels/Blame.cs b/src/ViewModels/Blame.cs index a189215a..affe40d9 100644 --- a/src/ViewModels/Blame.cs +++ b/src/ViewModels/Blame.cs @@ -2,9 +2,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Avalonia.Threading; - using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -34,7 +32,7 @@ namespace SourceGit.ViewModels public bool IsBinary { - get => _data != null && _data.IsBinary; + get => _data?.IsBinary ?? false; } public bool CanBack @@ -65,7 +63,10 @@ namespace SourceGit.ViewModels if (_commitMessages.TryGetValue(sha, out var msg)) return msg; - msg = new Commands.QueryCommitFullMessage(_repo, sha).Result(); + msg = new Commands.QueryCommitFullMessage(_repo, sha) + .GetResultAsync() + .Result; + _commitMessages[sha] = msg; return msg; } @@ -132,11 +133,13 @@ namespace SourceGit.ViewModels } else { - Task.Run(() => + Task.Run(async () => { - var result = new Commands.QuerySingleCommit(_repo, commitSHA).Result(); + var result = await new Commands.QuerySingleCommit(_repo, commitSHA) + .GetResultAsync() + .ConfigureAwait(false); - Dispatcher.UIThread.Invoke(() => + Dispatcher.UIThread.Post(() => { if (!token.IsCancellationRequested) { @@ -147,11 +150,13 @@ namespace SourceGit.ViewModels }, token); } - Task.Run(() => + Task.Run(async () => { - var result = new Commands.Blame(_repo, FilePath, commitSHA).Result(); + var result = await new Commands.Blame(_repo, FilePath, commitSHA) + .ReadAsync() + .ConfigureAwait(false); - Dispatcher.UIThread.Invoke(() => + Dispatcher.UIThread.Post(() => { if (!token.IsCancellationRequested) Data = result; diff --git a/src/ViewModels/BlockNavigation.cs b/src/ViewModels/BlockNavigation.cs index 9a5a926c..4a51244c 100644 --- a/src/ViewModels/BlockNavigation.cs +++ b/src/ViewModels/BlockNavigation.cs @@ -70,9 +70,7 @@ namespace SourceGit.ViewModels foreach (var line in lines) { lineIdx++; - if (line.Type == Models.TextDiffLineType.Added || - line.Type == Models.TextDiffLineType.Deleted || - line.Type == Models.TextDiffLineType.None) + if (line.Type is Models.TextDiffLineType.Added or Models.TextDiffLineType.Deleted or Models.TextDiffLineType.None) { if (isNewBlock) { diff --git a/src/ViewModels/BranchCompare.cs b/src/ViewModels/BranchCompare.cs index 64ceea1c..b2ecc3be 100644 --- a/src/ViewModels/BranchCompare.cs +++ b/src/ViewModels/BranchCompare.cs @@ -49,7 +49,7 @@ namespace SourceGit.ViewModels { if (SetProperty(ref _selectedChanges, value)) { - if (value != null && value.Count == 1) + if (value is { Count: 1 }) DiffContext = new DiffContext(_repo, new Models.DiffOption(_based.Head, _to.Head, value[0]), _diffContext); else DiffContext = null; @@ -63,9 +63,7 @@ namespace SourceGit.ViewModels set { if (SetProperty(ref _searchFilter, value)) - { RefreshVisible(); - } } } @@ -127,13 +125,13 @@ namespace SourceGit.ViewModels var diffWithMerger = new MenuItem(); diffWithMerger.Header = App.Text("DiffWithMerger"); diffWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); - diffWithMerger.Click += (_, ev) => + diffWithMerger.Click += (sender, ev) => { var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption(_based.Head, _to.Head, change); - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, toolType, toolPath, opt)); + _ = Commands.MergeTool.OpenForDiffAsync(_repo, toolType, toolPath, opt); ev.Handled = true; }; menu.Items.Add(diffWithMerger); @@ -156,9 +154,9 @@ namespace SourceGit.ViewModels var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, ev) => + copyPath.Click += async (_, ev) => { - App.CopyText(change.Path); + await App.CopyTextAsync(change.Path); ev.Handled = true; }; menu.Items.Add(copyPath); @@ -166,9 +164,9 @@ namespace SourceGit.ViewModels var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyFullPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - App.CopyText(Native.OS.GetAbsPath(_repo, change.Path)); + await App.CopyTextAsync(Native.OS.GetAbsPath(_repo, change.Path)); e.Handled = true; }; menu.Items.Add(copyFullPath); @@ -178,20 +176,28 @@ namespace SourceGit.ViewModels private void Refresh() { - Task.Run(() => + Task.Run(async () => { if (_baseHead == null) { - var baseHead = new Commands.QuerySingleCommit(_repo, _based.Head).Result(); - var toHead = new Commands.QuerySingleCommit(_repo, _to.Head).Result(); - Dispatcher.UIThread.Invoke(() => + var baseHead = await new Commands.QuerySingleCommit(_repo, _based.Head) + .GetResultAsync() + .ConfigureAwait(false); + + var toHead = await new Commands.QuerySingleCommit(_repo, _to.Head) + .GetResultAsync() + .ConfigureAwait(false); + + Dispatcher.UIThread.Post(() => { BaseHead = baseHead; ToHead = toHead; }); } - _changes = new Commands.CompareRevisions(_repo, _based.Head, _to.Head).Result(); + _changes = await new Commands.CompareRevisions(_repo, _based.Head, _to.Head) + .ReadAsync() + .ConfigureAwait(false); var visible = _changes; if (!string.IsNullOrWhiteSpace(_searchFilter)) @@ -204,7 +210,15 @@ namespace SourceGit.ViewModels } } - Dispatcher.UIThread.Invoke(() => VisibleChanges = visible); + Dispatcher.UIThread.Post(() => + { + VisibleChanges = visible; + + if (VisibleChanges.Count > 0) + SelectedChanges = [VisibleChanges[0]]; + else + SelectedChanges = []; + }); }); } diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs index dffbf671..1550e807 100644 --- a/src/ViewModels/BranchTreeNode.cs +++ b/src/ViewModels/BranchTreeNode.cs @@ -165,7 +165,7 @@ namespace SourceGit.ViewModels return; } - var lastFolder = null as BranchTreeNode; + BranchTreeNode lastFolder = null; var start = 0; while (sepIdx != -1) @@ -250,8 +250,7 @@ namespace SourceGit.ViewModels { if (r.Backend is Models.Branch) return r.TimeToSort == l.TimeToSort ? Models.NumericSort.Compare(l.Name, r.Name) : r.TimeToSort.CompareTo(l.TimeToSort); - else - return 1; + return 1; } if (r.Backend is Models.Branch) diff --git a/src/ViewModels/ChangeSubmoduleUrl.cs b/src/ViewModels/ChangeSubmoduleUrl.cs new file mode 100644 index 00000000..f356234b --- /dev/null +++ b/src/ViewModels/ChangeSubmoduleUrl.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class ChangeSubmoduleUrl : Popup + { + public Models.Submodule Submodule + { + get; + } + + [Required(ErrorMessage = "Url is required!!!")] + [CustomValidation(typeof(ChangeSubmoduleUrl), nameof(ValidateUrl))] + public string Url + { + get => _url; + set => SetProperty(ref _url, value, true); + } + + public ChangeSubmoduleUrl(Repository repo, Models.Submodule submodule) + { + _repo = repo; + _url = submodule.URL; + Submodule = submodule; + } + + public static ValidationResult ValidateUrl(string url, ValidationContext ctx) + { + if (!Models.Remote.IsValidURL(url)) + return new ValidationResult("Invalid repository URL format"); + + return ValidationResult.Success; + } + + public override async Task Sure() + { + if (_url.Equals(Submodule.URL, StringComparison.Ordinal)) + return true; + + _repo.SetWatcherEnabled(false); + ProgressDescription = "Change submodule's url..."; + + var log = _repo.CreateLog("Change Submodule's URL"); + Use(log); + + var succ = await new Commands.Submodule(_repo.FullPath) + .Use(log) + .SetURLAsync(Submodule.Path, _url); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; + } + + private readonly Repository _repo; + private string _url; + } +} diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs index 45dcd9a4..ce6195b8 100644 --- a/src/ViewModels/Checkout.cs +++ b/src/ViewModels/Checkout.cs @@ -33,7 +33,7 @@ namespace SourceGit.ViewModels DiscardLocalChanges = false; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Checkout '{Branch}' ..."; @@ -41,71 +41,76 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Checkout '{Branch}'"); Use(log); - var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules; - return Task.Run(() => + if (_repo.CurrentBranch is { IsDetachedHead: true }) { - bool succ; - var needPopStash = false; - - if (!_repo.ConfirmCheckoutBranch()) + var refs = await new Commands.QueryRefsContainsCommit(_repo.FullPath, _repo.CurrentBranch.Head).GetResultAsync(); + if (refs.Count == 0) { - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - } - - if (DiscardLocalChanges) - { - succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(Branch, true); - } - else - { - var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); - if (changes > 0) + var msg = App.Text("Checkout.WarnLostCommits"); + var shouldContinue = await App.AskConfirmAsync(msg, null); + if (!shouldContinue) { - succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH"); - if (!succ) - { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; - } + _repo.SetWatcherEnabled(true); + return true; + } + } + } - needPopStash = true; + var succ = false; + var needPopStash = false; + + if (!DiscardLocalChanges) + { + var changes = await new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).GetResultAsync(); + if (changes > 0) + { + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushAsync("CHECKOUT_AUTO_STASH"); + if (!succ) + { + log.Complete(); + _repo.SetWatcherEnabled(true); + return false; } - succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(Branch, false); + needPopStash = true; + } + } + + succ = await new Commands.Checkout(_repo.FullPath) + .Use(log) + .BranchAsync(Branch, DiscardLocalChanges); + + if (succ) + { + if (IsRecurseSubmoduleVisible && RecurseSubmodules) + { + var submodules = await new Commands.QueryUpdatableSubmodules(_repo.FullPath).GetResultAsync(); + if (submodules.Count > 0) + await new Commands.Submodule(_repo.FullPath) + .Use(log) + .UpdateAsync(submodules, true, true); } - if (succ) - { - if (updateSubmodules) - { - var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); - if (submodules.Count > 0) - new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); - } + if (needPopStash) + await new Commands.Stash(_repo.FullPath) + .Use(log) + .PopAsync("stash@{0}"); + } - if (needPopStash) - new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); - } + log.Complete(); - log.Complete(); + var b = _repo.Branches.Find(x => x.IsLocal && x.Name == Branch); + if (b != null && _repo.HistoriesFilterMode == Models.FilterMode.Included) + _repo.SetBranchFilterMode(b, Models.FilterMode.Included, true, false); - CallUIThread(() => - { - ProgressDescription = "Waiting for branch updated..."; + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); - var b = _repo.Branches.Find(x => x.IsLocal && x.Name == Branch); - if (b != null && _repo.HistoriesFilterMode == Models.FilterMode.Included) - _repo.SetBranchFilterMode(b, Models.FilterMode.Included, true, false); - - _repo.MarkBranchesDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - - Task.Delay(400).Wait(); - return succ; - }); + ProgressDescription = "Waiting for branch updated..."; + await Task.Delay(400); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/CheckoutAndFastForward.cs b/src/ViewModels/CheckoutAndFastForward.cs index 9ae931fe..205bbbd7 100644 --- a/src/ViewModels/CheckoutAndFastForward.cs +++ b/src/ViewModels/CheckoutAndFastForward.cs @@ -38,7 +38,7 @@ namespace SourceGit.ViewModels RemoteBranch = remoteBranch; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Checkout and Fast-Forward '{LocalBranch.Name}' ..."; @@ -46,70 +46,75 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Checkout and Fast-Forward '{LocalBranch.Name}' ..."); Use(log); - var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules; - return Task.Run(() => + if (_repo.CurrentBranch is { IsDetachedHead: true }) { - var succ = false; - var needPopStash = false; - - if (!_repo.ConfirmCheckoutBranch()) + var refs = await new Commands.QueryRefsContainsCommit(_repo.FullPath, _repo.CurrentBranch.Head).GetResultAsync(); + if (refs.Count == 0) { - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - } - - if (DiscardLocalChanges) - { - succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(LocalBranch.Name, RemoteBranch.Head, true, true); - } - else - { - var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); - if (changes > 0) + var msg = App.Text("Checkout.WarnLostCommits"); + var shouldContinue = await App.AskConfirmAsync(msg, null); + if (!shouldContinue) { - succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AND_FASTFORWARD_AUTO_STASH"); - if (!succ) - { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; - } + _repo.SetWatcherEnabled(true); + return true; + } + } + } - needPopStash = true; + var succ = false; + var needPopStash = false; + + if (!DiscardLocalChanges) + { + var changes = await new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).GetResultAsync(); + if (changes > 0) + { + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushAsync("CHECKOUT_AND_FASTFORWARD_AUTO_STASH"); + if (!succ) + { + log.Complete(); + _repo.SetWatcherEnabled(true); + return false; } - succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(LocalBranch.Name, RemoteBranch.Head, false, true); + needPopStash = true; + } + } + + succ = await new Commands.Checkout(_repo.FullPath) + .Use(log) + .BranchAsync(LocalBranch.Name, RemoteBranch.Head, DiscardLocalChanges, true); + + if (succ) + { + if (IsRecurseSubmoduleVisible && RecurseSubmodules) + { + var submodules = await new Commands.QueryUpdatableSubmodules(_repo.FullPath).GetResultAsync(); + if (submodules.Count > 0) + await new Commands.Submodule(_repo.FullPath) + .Use(log) + .UpdateAsync(submodules, true, true); } - if (succ) - { - if (updateSubmodules) - { - var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); - if (submodules.Count > 0) - new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); - } + if (needPopStash) + await new Commands.Stash(_repo.FullPath) + .Use(log) + .PopAsync("stash@{0}"); + } - if (needPopStash) - new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); - } + log.Complete(); - log.Complete(); + if (_repo.HistoriesFilterMode == Models.FilterMode.Included) + _repo.SetBranchFilterMode(LocalBranch, Models.FilterMode.Included, true, false); - CallUIThread(() => - { - ProgressDescription = "Waiting for branch updated..."; + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); - if (_repo.HistoriesFilterMode == Models.FilterMode.Included) - _repo.SetBranchFilterMode(LocalBranch, Models.FilterMode.Included, true, false); - - _repo.MarkBranchesDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - - Task.Delay(400).Wait(); - return succ; - }); + ProgressDescription = "Waiting for branch updated..."; + await Task.Delay(400); + return succ; } private Repository _repo; diff --git a/src/ViewModels/CheckoutCommit.cs b/src/ViewModels/CheckoutCommit.cs index f477538d..a77a7235 100644 --- a/src/ViewModels/CheckoutCommit.cs +++ b/src/ViewModels/CheckoutCommit.cs @@ -33,7 +33,7 @@ namespace SourceGit.ViewModels DiscardLocalChanges = false; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Checkout Commit '{Commit.SHA}' ..."; @@ -41,58 +41,67 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Checkout Commit"); Use(log); - var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules; - return Task.Run(() => + if (_repo.CurrentBranch is { IsDetachedHead: true }) { - bool succ; - var needPop = false; - - if (!_repo.ConfirmCheckoutBranch()) + var refs = await new Commands.QueryRefsContainsCommit(_repo.FullPath, _repo.CurrentBranch.Head).GetResultAsync(); + if (refs.Count == 0) { - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - } - - if (DiscardLocalChanges) - { - succ = new Commands.Checkout(_repo.FullPath).Use(log).Commit(Commit.SHA, true); - } - else - { - var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); - if (changes > 0) + var msg = App.Text("Checkout.WarnLostCommits"); + var shouldContinue = await App.AskConfirmAsync(msg, null); + if (!shouldContinue) { - succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH"); - if (!succ) - { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; - } + _repo.SetWatcherEnabled(true); + return true; + } + } + } - needPop = true; + var succ = false; + var needPop = false; + + if (!DiscardLocalChanges) + { + var changes = await new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).GetResultAsync(); + if (changes > 0) + { + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushAsync("CHECKOUT_AUTO_STASH"); + if (!succ) + { + log.Complete(); + _repo.SetWatcherEnabled(true); + return false; } - succ = new Commands.Checkout(_repo.FullPath).Use(log).Commit(Commit.SHA, false); + needPop = true; } + } - if (succ) + succ = await new Commands.Checkout(_repo.FullPath) + .Use(log) + .CommitAsync(Commit.SHA, DiscardLocalChanges); + + if (succ) + { + if (IsRecurseSubmoduleVisible && RecurseSubmodules) { - if (updateSubmodules) - { - var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); - if (submodules.Count > 0) - new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); - } - - if (needPop) - new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); + var submodules = await new Commands.QueryUpdatableSubmodules(_repo.FullPath).GetResultAsync(); + if (submodules.Count > 0) + await new Commands.Submodule(_repo.FullPath) + .Use(log) + .UpdateAsync(submodules, true, true); } - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + if (needPop) + await new Commands.Stash(_repo.FullPath) + .Use(log) + .PopAsync("stash@{0}"); + } + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/CherryPick.cs b/src/ViewModels/CherryPick.cs index 2d929f3a..ca525e0a 100644 --- a/src/ViewModels/CherryPick.cs +++ b/src/ViewModels/CherryPick.cs @@ -63,7 +63,7 @@ namespace SourceGit.ViewModels AutoCommit = true; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); _repo.ClearCommitMessage(); @@ -72,31 +72,32 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Cherry-Pick"); Use(log); - return Task.Run(() => + if (IsMergeCommit) { - if (IsMergeCommit) - { - new Commands.CherryPick( - _repo.FullPath, - Targets[0].SHA, - !AutoCommit, - AppendSourceToMessage, - $"-m {MainlineForMergeCommit + 1}").Use(log).Exec(); - } - else - { - new Commands.CherryPick( - _repo.FullPath, - string.Join(' ', Targets.ConvertAll(c => c.SHA)), - !AutoCommit, - AppendSourceToMessage, - string.Empty).Use(log).Exec(); - } + await new Commands.CherryPick( + _repo.FullPath, + Targets[0].SHA, + !AutoCommit, + AppendSourceToMessage, + $"-m {MainlineForMergeCommit + 1}") + .Use(log) + .ExecAsync(); + } + else + { + await new Commands.CherryPick( + _repo.FullPath, + string.Join(' ', Targets.ConvertAll(c => c.SHA)), + !AutoCommit, + AppendSourceToMessage, + string.Empty) + .Use(log) + .ExecAsync(); + } - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/Cleanup.cs b/src/ViewModels/Cleanup.cs index 1fc39cb5..c8788d68 100644 --- a/src/ViewModels/Cleanup.cs +++ b/src/ViewModels/Cleanup.cs @@ -9,7 +9,7 @@ namespace SourceGit.ViewModels _repo = repo; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Cleanup (GC & prune) ..."; @@ -17,13 +17,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Cleanup (GC & prune)"); Use(log); - return Task.Run(() => - { - new Commands.GC(_repo.FullPath).Use(log).Exec(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.GC(_repo.FullPath) + .Use(log) + .ExecAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/ClearStashes.cs b/src/ViewModels/ClearStashes.cs index d71bab31..6178d5ef 100644 --- a/src/ViewModels/ClearStashes.cs +++ b/src/ViewModels/ClearStashes.cs @@ -9,7 +9,7 @@ namespace SourceGit.ViewModels _repo = repo; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Clear all stashes..."; @@ -17,13 +17,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Clear Stashes"); Use(log); - return Task.Run(() => - { - new Commands.Stash(_repo.FullPath).Use(log).Clear(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.Stash(_repo.FullPath) + .Use(log) + .ClearAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs index c6f09d24..b3aadb58 100644 --- a/src/ViewModels/Clone.cs +++ b/src/ViewModels/Clone.cs @@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Threading.Tasks; - using Avalonia.Threading; namespace SourceGit.ViewModels @@ -74,11 +73,11 @@ namespace SourceGit.ViewModels { var text = await App.GetClipboardTextAsync(); if (Models.Remote.IsValidURL(text)) - Dispatcher.UIThread.Invoke(() => Remote = text); + Dispatcher.UIThread.Post(() => Remote = text); } catch { - // ignore + // Ignore } }); } @@ -97,79 +96,74 @@ namespace SourceGit.ViewModels return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { ProgressDescription = "Clone ..."; var log = new CommandLog("Clone"); Use(log); - return Task.Run(() => + var succ = await new Commands.Clone(_pageId, _parentFolder, _remote, _local, _useSSH ? _sshKey : "", _extraArgs) + .Use(log) + .ExecAsync(); + if (succ) + return false; + + var path = _parentFolder; + if (!string.IsNullOrEmpty(_local)) { - var cmd = new Commands.Clone(_pageId, _parentFolder, _remote, _local, _useSSH ? _sshKey : "", _extraArgs).Use(log); - if (!cmd.Exec()) - return false; + path = Path.GetFullPath(Path.Combine(path, _local)); + } + else + { + var name = Path.GetFileName(_remote)!; + if (name.EndsWith(".git", StringComparison.Ordinal)) + name = name.Substring(0, name.Length - 4); + else if (name.EndsWith(".bundle", StringComparison.Ordinal)) + name = name.Substring(0, name.Length - 7); - var path = _parentFolder; - if (!string.IsNullOrEmpty(_local)) + path = Path.GetFullPath(Path.Combine(path, name)); + } + + if (!Directory.Exists(path)) + { + App.RaiseException(_pageId, $"Folder '{path}' can NOT be found"); + return false; + } + + if (_useSSH && !string.IsNullOrEmpty(_sshKey)) + { + await new Commands.Config(path) + .Use(log) + .SetAsync("remote.origin.sshkey", _sshKey); + } + + if (InitAndUpdateSubmodules) + { + var submodules = await new Commands.QueryUpdatableSubmodules(path).GetResultAsync(); + if (submodules.Count > 0) + await new Commands.Submodule(path) + .Use(log) + .UpdateAsync(submodules, true, true); + } + + log.Complete(); + + var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true); + var launcher = App.GetLauncher(); + LauncherPage page = null; + foreach (var one in launcher.Pages) + { + if (one.Node.Id == _pageId) { - path = Path.GetFullPath(Path.Combine(path, _local)); + page = one; + break; } - else - { - var name = Path.GetFileName(_remote)!; - if (name.EndsWith(".git", StringComparison.Ordinal)) - name = name.Substring(0, name.Length - 4); - else if (name.EndsWith(".bundle", StringComparison.Ordinal)) - name = name.Substring(0, name.Length - 7); + } - path = Path.GetFullPath(Path.Combine(path, name)); - } - - if (!Directory.Exists(path)) - { - CallUIThread(() => - { - App.RaiseException(_pageId, $"Folder '{path}' can NOT be found"); - }); - return false; - } - - if (_useSSH && !string.IsNullOrEmpty(_sshKey)) - { - var config = new Commands.Config(path); - config.Set("remote.origin.sshkey", _sshKey); - } - - if (InitAndUpdateSubmodules) - { - var submodules = new Commands.QueryUpdatableSubmodules(path).Result(); - if (submodules.Count > 0) - new Commands.Submodule(path).Use(log).Update(submodules, true, true); - } - - log.Complete(); - - CallUIThread(() => - { - var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true); - var launcher = App.GetLauncher(); - var page = null as LauncherPage; - foreach (var one in launcher.Pages) - { - if (one.Node.Id == _pageId) - { - page = one; - break; - } - } - - Welcome.Instance.Refresh(); - launcher.OpenRepositoryInTab(node, page); - }); - - return true; - }); + Welcome.Instance.Refresh(); + launcher.OpenRepositoryInTab(node, page); + return true; } private string _pageId = string.Empty; diff --git a/src/ViewModels/CommandLog.cs b/src/ViewModels/CommandLog.cs index 896869cf..1accc226 100644 --- a/src/ViewModels/CommandLog.cs +++ b/src/ViewModels/CommandLog.cs @@ -51,36 +51,41 @@ namespace SourceGit.ViewModels public void AppendLine(string line = null) { - var newline = line ?? string.Empty; - - Dispatcher.UIThread.Invoke(() => + if (!Dispatcher.UIThread.CheckAccess()) { + Dispatcher.UIThread.Invoke(() => AppendLine(line)); + } + else + { + var newline = line ?? string.Empty; _builder.AppendLine(newline); _onNewLineReceived?.Invoke(newline); - }); + } } public void Complete() { - IsComplete = true; - - Dispatcher.UIThread.Invoke(() => + if (!Dispatcher.UIThread.CheckAccess()) { - _content = _builder.ToString(); - _builder.Clear(); - _builder = null; + Dispatcher.UIThread.Invoke(Complete); + return; + } - EndTime = DateTime.Now; + IsComplete = true; + EndTime = DateTime.Now; - OnPropertyChanged(nameof(IsComplete)); + _content = _builder.ToString(); + _builder.Clear(); + _builder = null; - if (_onNewLineReceived != null) - { - var dumpHandlers = _onNewLineReceived.GetInvocationList(); - foreach (var d in dumpHandlers) - _onNewLineReceived -= (Action)d; - } - }); + OnPropertyChanged(nameof(IsComplete)); + + if (_onNewLineReceived != null) + { + var dumpHandlers = _onNewLineReceived.GetInvocationList(); + foreach (var d in dumpHandlers) + _onNewLineReceived -= (Action)d; + } } private string _content = string.Empty; diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 54673ff2..7bdefe6b 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -17,14 +17,15 @@ namespace SourceGit.ViewModels { public int ActivePageIndex { - get => _repo.CommitDetailActivePageIndex; + get => _rememberActivePageIndex ? _repo.CommitDetailActivePageIndex : _activePageIndex; set { - if (_repo.CommitDetailActivePageIndex != value) - { + if (_rememberActivePageIndex) _repo.CommitDetailActivePageIndex = value; - OnPropertyChanged(); - } + else + _activePageIndex = value; + + OnPropertyChanged(); } } @@ -139,9 +140,10 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _canOpenRevisionFileWithDefaultEditor, value); } - public CommitDetail(Repository repo) + public CommitDetail(Repository repo, bool rememberActivePageIndex) { _repo = repo; + _rememberActivePageIndex = rememberActivePageIndex; WebLinks = Models.CommitLink.Get(repo.Remotes); } @@ -167,9 +169,11 @@ namespace SourceGit.ViewModels _repo?.NavigateToCommit(commitSHA); } - public List GetRefsContainsThisCommit() + public async Task> GetRefsContainsThisCommitAsync() { - return new Commands.QueryRefsContainsCommit(_repo.FullPath, _commit.SHA).Result(); + return await new Commands.QueryRefsContainsCommit(_repo.FullPath, _commit.SHA) + .GetResultAsync() + .ConfigureAwait(false); } public void ClearSearchChangeFilter() @@ -187,93 +191,34 @@ namespace SourceGit.ViewModels RevisionFileSearchSuggestion = null; } - public Models.Commit GetParent(string sha) + public async Task GetCommitAsync(string sha) { - return new Commands.QuerySingleCommit(_repo.FullPath, sha).Result(); + return await new Commands.QuerySingleCommit(_repo.FullPath, sha) + .GetResultAsync() + .ConfigureAwait(false); } - public List GetRevisionFilesUnderFolder(string parentFolder) + public async Task> GetRevisionFilesUnderFolderAsync(string parentFolder) { - return new Commands.QueryRevisionObjects(_repo.FullPath, _commit.SHA, parentFolder).Result(); + return await new Commands.QueryRevisionObjects(_repo.FullPath, _commit.SHA, parentFolder) + .GetResultAsync() + .ConfigureAwait(false); } - public void ViewRevisionFile(Models.Object file) + public async Task ViewRevisionFileAsync(Models.Object file) { - if (file == null) - { - ViewRevisionFilePath = string.Empty; - ViewRevisionFileContent = null; - CanOpenRevisionFileWithDefaultEditor = false; - return; - } + var obj = file ?? new Models.Object() { Path = string.Empty, Type = Models.ObjectType.None }; + ViewRevisionFilePath = obj.Path; - ViewRevisionFilePath = file.Path; - - switch (file.Type) + switch (obj.Type) { case Models.ObjectType.Blob: CanOpenRevisionFileWithDefaultEditor = true; - Task.Run(() => - { - var isBinary = new Commands.IsBinary(_repo.FullPath, _commit.SHA, file.Path).Result(); - if (isBinary) - { - var imgDecoder = ImageSource.GetDecoder(file.Path); - if (imgDecoder != Models.ImageDecoder.None) - { - var source = ImageSource.FromRevision(_repo.FullPath, _commit.SHA, file.Path, imgDecoder); - var image = new Models.RevisionImageFile(file.Path, source.Bitmap, source.Size); - Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = image); - } - else - { - var size = new Commands.QueryFileSize(_repo.FullPath, file.Path, _commit.SHA).Result(); - var binary = new Models.RevisionBinaryFile() { Size = size }; - Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = binary); - } - - return; - } - - var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _commit.SHA, file.Path); - var content = new StreamReader(contentStream).ReadToEnd(); - var lfs = Models.LFSObject.Parse(content); - if (lfs != null) - { - var imgDecoder = ImageSource.GetDecoder(file.Path); - if (imgDecoder != Models.ImageDecoder.None) - { - var combined = new RevisionLFSImage(_repo.FullPath, file.Path, lfs, imgDecoder); - Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = combined); - } - else - { - var rlfs = new Models.RevisionLFSObject() { Object = lfs }; - Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = rlfs); - } - } - else - { - var txt = new Models.RevisionTextFile() { FileName = file.Path, Content = content }; - Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = txt); - } - }); + await SetViewingBlobAsync(obj); break; case Models.ObjectType.Commit: CanOpenRevisionFileWithDefaultEditor = false; - Task.Run(() => - { - var submoduleRoot = Path.Combine(_repo.FullPath, file.Path).Replace('\\', '/').Trim('/'); - var commit = new Commands.QuerySingleCommit(submoduleRoot, file.SHA).Result(); - var message = commit != null ? new Commands.QueryCommitFullMessage(submoduleRoot, file.SHA).Result() : null; - var module = new Models.RevisionSubmodule() - { - Commit = commit ?? new Models.Commit() { SHA = _commit.SHA }, - FullMessage = new Models.CommitFullMessage { Message = message } - }; - - Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = module); - }); + await SetViewingCommitAsync(obj); break; default: CanOpenRevisionFileWithDefaultEditor = false; @@ -282,40 +227,27 @@ namespace SourceGit.ViewModels } } - public Task OpenRevisionFileWithDefaultEditor(string file) + public async Task OpenRevisionFileWithDefaultEditorAsync(string file) { - return Task.Run(() => - { - var fullPath = Native.OS.GetAbsPath(_repo.FullPath, file); - var fileName = Path.GetFileNameWithoutExtension(fullPath) ?? ""; - var fileExt = Path.GetExtension(fullPath) ?? ""; - var tmpFile = Path.Combine(Path.GetTempPath(), $"{fileName}~{_commit.SHA.Substring(0, 10)}{fileExt}"); + var fullPath = Native.OS.GetAbsPath(_repo.FullPath, file); + var fileName = Path.GetFileNameWithoutExtension(fullPath) ?? ""; + var fileExt = Path.GetExtension(fullPath) ?? ""; + var tmpFile = Path.Combine(Path.GetTempPath(), $"{fileName}~{_commit.SHA.Substring(0, 10)}{fileExt}"); - Commands.SaveRevisionFile.Run(_repo.FullPath, _commit.SHA, file, tmpFile); - Native.OS.OpenWithDefaultEditor(tmpFile); - }); + await Commands.SaveRevisionFile + .RunAsync(_repo.FullPath, _commit.SHA, file, tmpFile) + .ConfigureAwait(false); + + Native.OS.OpenWithDefaultEditor(tmpFile); } - public ContextMenu CreateChangeContextMenu(Models.Change change) + public ContextMenu CreateChangeContextMenuByFolder(ChangeTreeNode node, List changes) { - var diffWithMerger = new MenuItem(); - diffWithMerger.Header = App.Text("DiffWithMerger"); - diffWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); - diffWithMerger.Click += (_, ev) => - { - var toolType = Preferences.Instance.ExternalMergeToolType; - var toolPath = Preferences.Instance.ExternalMergeToolPath; - var opt = new Models.DiffOption(_commit, change); - - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo.FullPath, toolType, toolPath, opt)); - ev.Handled = true; - }; - - var fullPath = Native.OS.GetAbsPath(_repo.FullPath, change.Path); + var fullPath = Native.OS.GetAbsPath(_repo.FullPath, node.FullPath); var explore = new MenuItem(); explore.Header = App.Text("RevealFile"); explore.Icon = App.CreateMenuIcon("Icons.Explore"); - explore.IsEnabled = File.Exists(fullPath); + explore.IsEnabled = Directory.Exists(fullPath); explore.Click += (_, ev) => { Native.OS.OpenInFileManager(fullPath, true); @@ -323,21 +255,11 @@ namespace SourceGit.ViewModels }; var history = new MenuItem(); - history.Header = App.Text("FileHistory"); + history.Header = App.Text("DirHistories"); history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new FileHistories(_repo, change.Path, _commit.SHA), false); - ev.Handled = true; - }; - - var blame = new MenuItem(); - blame.Header = App.Text("Blame"); - blame.Icon = App.CreateMenuIcon("Icons.Blame"); - blame.IsEnabled = change.Index != Models.ChangeState.Deleted; - blame.Click += (_, ev) => - { - App.ShowWindow(new Blame(_repo.FullPath, change.Path, _commit), false); + App.ShowWindow(new DirHistories(_repo, node.FullPath, _commit.SHA)); ev.Handled = true; }; @@ -360,7 +282,109 @@ namespace SourceGit.ViewModels if (storageFile != null) { var saveTo = storageFile.Path.LocalPath; - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessRevisionCompareChanges(_repo.FullPath, [change], baseRevision, _commit.SHA, saveTo)); + var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo.FullPath, changes, baseRevision, _commit.SHA, saveTo); + if (succ) + App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); + } + + e.Handled = true; + }; + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); + copyPath.Click += async (_, ev) => + { + await App.CopyTextAsync(node.FullPath); + ev.Handled = true; + }; + + var copyFullPath = new MenuItem(); + copyFullPath.Header = App.Text("CopyFullPath"); + copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); + copyFullPath.Click += async (_, e) => + { + await App.CopyTextAsync(fullPath); + e.Handled = true; + }; + + var menu = new ContextMenu(); + menu.Items.Add(explore); + menu.Items.Add(new MenuItem { Header = "-" }); + menu.Items.Add(history); + menu.Items.Add(patch); + menu.Items.Add(new MenuItem { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFullPath); + + return menu; + } + + public ContextMenu CreateChangeContextMenu(Models.Change change) + { + var diffWithMerger = new MenuItem(); + diffWithMerger.Header = App.Text("DiffWithMerger"); + diffWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); + diffWithMerger.Click += (sender, ev) => + { + var toolType = Preferences.Instance.ExternalMergeToolType; + var toolPath = Preferences.Instance.ExternalMergeToolPath; + var opt = new Models.DiffOption(_commit, change); + + _ = Commands.MergeTool.OpenForDiffAsync(_repo.FullPath, toolType, toolPath, opt); + ev.Handled = true; + }; + + var fullPath = Native.OS.GetAbsPath(_repo.FullPath, change.Path); + var explore = new MenuItem(); + explore.Header = App.Text("RevealFile"); + explore.Icon = App.CreateMenuIcon("Icons.Explore"); + explore.IsEnabled = File.Exists(fullPath); + explore.Click += (_, ev) => + { + Native.OS.OpenInFileManager(fullPath, true); + ev.Handled = true; + }; + + var history = new MenuItem(); + history.Header = App.Text("FileHistory"); + history.Icon = App.CreateMenuIcon("Icons.Histories"); + history.Click += (_, ev) => + { + App.ShowWindow(new FileHistories(_repo, change.Path, _commit.SHA)); + ev.Handled = true; + }; + + var blame = new MenuItem(); + blame.Header = App.Text("Blame"); + blame.Icon = App.CreateMenuIcon("Icons.Blame"); + blame.IsEnabled = change.Index != Models.ChangeState.Deleted; + blame.Click += (_, ev) => + { + App.ShowWindow(new Blame(_repo.FullPath, change.Path, _commit)); + ev.Handled = true; + }; + + var patch = new MenuItem(); + patch.Header = App.Text("FileCM.SaveAsPatch"); + patch.Icon = App.CreateMenuIcon("Icons.Diff"); + patch.Click += async (_, e) => + { + var storageProvider = App.GetStorageProvider(); + if (storageProvider == null) + return; + + var options = new FilePickerSaveOptions(); + options.Title = App.Text("FileCM.SaveAsPatch"); + options.DefaultExtension = ".patch"; + options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }]; + + var baseRevision = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0]; + var storageFile = await storageProvider.SaveFilePickerAsync(options); + if (storageFile != null) + { + var saveTo = storageFile.Path.LocalPath; + var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo.FullPath, [change], baseRevision, _commit.SHA, saveTo); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -384,7 +408,7 @@ namespace SourceGit.ViewModels resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout"); resetToThisRevision.Click += async (_, ev) => { - await ResetToThisRevision(change.Path); + await ResetToThisRevisionAsync(change.Path); ev.Handled = true; }; @@ -394,7 +418,7 @@ namespace SourceGit.ViewModels resetToFirstParent.IsEnabled = _commit.Parents.Count > 0; resetToFirstParent.Click += async (_, ev) => { - await ResetToParentRevision(change); + await ResetToParentRevisionAsync(change); ev.Handled = true; }; @@ -408,18 +432,18 @@ namespace SourceGit.ViewModels var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, ev) => + copyPath.Click += async (_, ev) => { - App.CopyText(change.Path); + await App.CopyTextAsync(change.Path); ev.Handled = true; }; var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyFullPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - App.CopyText(fullPath); + await App.CopyTextAsync(fullPath); e.Handled = true; }; @@ -428,8 +452,61 @@ namespace SourceGit.ViewModels return menu; } + public ContextMenu CreateRevisionFileContextMenuByFolder(string path) + { + var fullPath = Native.OS.GetAbsPath(_repo.FullPath, path); + var explore = new MenuItem(); + explore.Header = App.Text("RevealFile"); + explore.Icon = App.CreateMenuIcon("Icons.Explore"); + explore.IsEnabled = Directory.Exists(fullPath); + explore.Click += (_, ev) => + { + Native.OS.OpenInFileManager(fullPath, true); + ev.Handled = true; + }; + + var history = new MenuItem(); + history.Header = App.Text("DirHistories"); + history.Icon = App.CreateMenuIcon("Icons.Histories"); + history.Click += (_, ev) => + { + App.ShowWindow(new DirHistories(_repo, path, _commit.SHA)); + ev.Handled = true; + }; + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); + copyPath.Click += async (_, ev) => + { + await App.CopyTextAsync(path); + ev.Handled = true; + }; + + var copyFullPath = new MenuItem(); + copyFullPath.Header = App.Text("CopyFullPath"); + copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); + copyFullPath.Click += async (_, e) => + { + await App.CopyTextAsync(fullPath); + e.Handled = true; + }; + + var menu = new ContextMenu(); + menu.Items.Add(explore); + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(history); + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFullPath); + return menu; + } + public ContextMenu CreateRevisionFileContextMenu(Models.Object file) { + if (file.Type == Models.ObjectType.Tree) + return CreateRevisionFileContextMenuByFolder(file.Path); + var menu = new ContextMenu(); var fullPath = Native.OS.GetAbsPath(_repo.FullPath, file.Path); var explore = new MenuItem(); @@ -448,7 +525,7 @@ namespace SourceGit.ViewModels openWith.IsEnabled = file.Type == Models.ObjectType.Blob; openWith.Click += async (_, ev) => { - await OpenRevisionFileWithDefaultEditor(file.Path); + await OpenRevisionFileWithDefaultEditorAsync(file.Path); ev.Handled = true; }; @@ -471,7 +548,10 @@ namespace SourceGit.ViewModels var folder = selected[0]; var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder.Path.ToString(); var saveTo = Path.Combine(folderPath, Path.GetFileName(file.Path)!); - await Task.Run(() => Commands.SaveRevisionFile.Run(_repo.FullPath, _commit.SHA, file.Path, saveTo)); + + await Commands.SaveRevisionFile + .RunAsync(_repo.FullPath, _commit.SHA, file.Path, saveTo) + .ConfigureAwait(false); } } catch (Exception e) @@ -492,7 +572,7 @@ namespace SourceGit.ViewModels history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new FileHistories(_repo, file.Path, _commit.SHA), false); + App.ShowWindow(new FileHistories(_repo, file.Path, _commit.SHA)); ev.Handled = true; }; @@ -502,7 +582,7 @@ namespace SourceGit.ViewModels blame.IsEnabled = file.Type == Models.ObjectType.Blob; blame.Click += (_, ev) => { - App.ShowWindow(new Blame(_repo.FullPath, file.Path, _commit), false); + App.ShowWindow(new Blame(_repo.FullPath, file.Path, _commit)); ev.Handled = true; }; @@ -517,7 +597,7 @@ namespace SourceGit.ViewModels resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout"); resetToThisRevision.Click += async (_, ev) => { - await ResetToThisRevision(file.Path); + await ResetToThisRevisionAsync(file.Path); ev.Handled = true; }; @@ -528,7 +608,7 @@ namespace SourceGit.ViewModels resetToFirstParent.IsEnabled = _commit.Parents.Count > 0; resetToFirstParent.Click += async (_, ev) => { - await ResetToParentRevision(change); + await ResetToParentRevisionAsync(change); ev.Handled = true; }; @@ -542,18 +622,18 @@ namespace SourceGit.ViewModels var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, ev) => + copyPath.Click += async (_, ev) => { - App.CopyText(file.Path); + await App.CopyTextAsync(file.Path); ev.Handled = true; }; var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyFullPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - App.CopyText(fullPath); + await App.CopyTextAsync(fullPath); e.Handled = true; }; @@ -585,39 +665,51 @@ namespace SourceGit.ViewModels _cancellationSource = new CancellationTokenSource(); var token = _cancellationSource.Token; - Task.Run(() => + Task.Run(async () => { - var message = new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA).Result(); - var inlines = ParseInlinesInMessage(message); + var message = await new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA) + .GetResultAsync() + .ConfigureAwait(false); + var inlines = await ParseInlinesInMessageAsync(message); if (!token.IsCancellationRequested) - Dispatcher.UIThread.Invoke(() => FullMessage = new Models.CommitFullMessage { Message = message, Inlines = inlines }); + Dispatcher.UIThread.Post(() => + { + FullMessage = new Models.CommitFullMessage + { + Message = message, + Inlines = inlines + }; + }); }, token); - Task.Run(() => + Task.Run(async () => { - var signInfo = new Commands.QueryCommitSignInfo(_repo.FullPath, _commit.SHA, !_repo.HasAllowedSignersFile).Result(); + var signInfo = await new Commands.QueryCommitSignInfo(_repo.FullPath, _commit.SHA, !_repo.HasAllowedSignersFile) + .GetResultAsync() + .ConfigureAwait(false); + if (!token.IsCancellationRequested) - Dispatcher.UIThread.Invoke(() => SignInfo = signInfo); + Dispatcher.UIThread.Post(() => SignInfo = signInfo); }, token); if (Preferences.Instance.ShowChildren) { - Task.Run(() => + Task.Run(async () => { var max = Preferences.Instance.MaxHistoryCommits; var cmd = new Commands.QueryCommitChildren(_repo.FullPath, _commit.SHA, max) { CancellationToken = token }; - var children = cmd.Result(); + var children = await cmd.GetResultAsync().ConfigureAwait(false); if (!token.IsCancellationRequested) Dispatcher.UIThread.Post(() => Children = children); }, token); } - Task.Run(() => + Task.Run(async () => { var parent = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0]; var cmd = new Commands.CompareRevisions(_repo.FullPath, parent, _commit.SHA) { CancellationToken = token }; - var changes = cmd.Result(); + var changes = await cmd.ReadAsync().ConfigureAwait(false); var visible = changes; if (!string.IsNullOrWhiteSpace(_searchChangeFilter)) { @@ -638,12 +730,14 @@ namespace SourceGit.ViewModels if (visible.Count == 0) SelectedChanges = null; + else + SelectedChanges = [VisibleChanges[0]]; }); } }, token); } - private Models.InlineElementCollector ParseInlinesInMessage(string message) + private async Task ParseInlinesInMessageAsync(string message) { var inlines = new Models.InlineElementCollector(); if (_repo.Settings.IssueTrackerRules is { Count: > 0 } rules) @@ -682,7 +776,7 @@ namespace SourceGit.ViewModels continue; var sha = match.Groups[1].Value; - var isCommitSHA = new Commands.IsCommitSHA(_repo.FullPath, sha).Result(); + var isCommitSHA = await new Commands.IsCommitSHA(_repo.FullPath, sha).GetResultAsync().ConfigureAwait(false); if (isCommitSHA) inlines.Add(new Models.InlineElement(Models.InlineElementType.CommitSHA, start, len, sha)); } @@ -734,7 +828,7 @@ namespace SourceGit.ViewModels lfsLock.Click += async (_, e) => { var log = _repo.CreateLog("Lock LFS file"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(_repo.Remotes[0].Name, path, log)); + var succ = await new Commands.LFS(_repo.FullPath).LockAsync(_repo.Remotes[0].Name, path, log); if (succ) App.SendNotification(_repo.FullPath, $"Lock file \"{path}\" successfully!"); @@ -752,7 +846,7 @@ namespace SourceGit.ViewModels lockRemote.Click += async (_, e) => { var log = _repo.CreateLog("Lock LFS file"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(remoteName, path, log)); + var succ = await new Commands.LFS(_repo.FullPath).LockAsync(remoteName, path, log); if (succ) App.SendNotification(_repo.FullPath, $"Lock file \"{path}\" successfully!"); @@ -772,7 +866,7 @@ namespace SourceGit.ViewModels lfsUnlock.Click += async (_, e) => { var log = _repo.CreateLog("Unlock LFS file"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(_repo.Remotes[0].Name, path, false, log)); + var succ = await new Commands.LFS(_repo.FullPath).UnlockAsync(_repo.Remotes[0].Name, path, false, log); if (succ) App.SendNotification(_repo.FullPath, $"Unlock file \"{path}\" successfully!"); @@ -790,7 +884,7 @@ namespace SourceGit.ViewModels unlockRemote.Click += async (_, e) => { var log = _repo.CreateLog("Unlock LFS file"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(remoteName, path, false, log)); + var succ = await new Commands.LFS(_repo.FullPath).UnlockAsync(remoteName, path, false, log); if (succ) App.SendNotification(_repo.FullPath, $"Unlock file \"{path}\" successfully!"); @@ -818,10 +912,13 @@ namespace SourceGit.ViewModels var sha = Commit.SHA; _requestingRevisionFiles = true; - Task.Run(() => + Task.Run(async () => { - var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result(); - Dispatcher.UIThread.Invoke(() => + var files = await new Commands.QueryRevisionFileNames(_repo.FullPath, sha) + .GetResultAsync() + .ConfigureAwait(false); + + Dispatcher.UIThread.Post(() => { if (sha == Commit.SHA && _requestingRevisionFiles) { @@ -862,29 +959,83 @@ namespace SourceGit.ViewModels RevisionFileSearchSuggestion = suggestion; } - private Task ResetToThisRevision(string path) + private async Task SetViewingBlobAsync(Models.Object file) + { + var isBinary = await new Commands.IsBinary(_repo.FullPath, _commit.SHA, file.Path).GetResultAsync(); + if (isBinary) + { + var imgDecoder = ImageSource.GetDecoder(file.Path); + if (imgDecoder != Models.ImageDecoder.None) + { + var source = await ImageSource.FromRevisionAsync(_repo.FullPath, _commit.SHA, file.Path, imgDecoder); + ViewRevisionFileContent = new Models.RevisionImageFile(file.Path, source.Bitmap, source.Size); + } + else + { + var size = await new Commands.QueryFileSize(_repo.FullPath, file.Path, _commit.SHA).GetResultAsync(); + ViewRevisionFileContent = new Models.RevisionBinaryFile() { Size = size }; + } + + return; + } + + var contentStream = await Commands.QueryFileContent.RunAsync(_repo.FullPath, _commit.SHA, file.Path); + var content = await new StreamReader(contentStream).ReadToEndAsync(); + var lfs = Models.LFSObject.Parse(content); + if (lfs != null) + { + var imgDecoder = ImageSource.GetDecoder(file.Path); + if (imgDecoder != Models.ImageDecoder.None) + ViewRevisionFileContent = new RevisionLFSImage(_repo.FullPath, file.Path, lfs, imgDecoder); + else + ViewRevisionFileContent = new Models.RevisionLFSObject() { Object = lfs }; + } + else + { + ViewRevisionFileContent = new Models.RevisionTextFile() { FileName = file.Path, Content = content }; + } + } + + private async Task SetViewingCommitAsync(Models.Object file) + { + var submoduleRoot = Path.Combine(_repo.FullPath, file.Path).Replace('\\', '/').Trim('/'); + var commit = await new Commands.QuerySingleCommit(submoduleRoot, file.SHA).GetResultAsync(); + if (commit == null) + { + ViewRevisionFileContent = new Models.RevisionSubmodule() + { + Commit = new Models.Commit() { SHA = file.SHA }, + FullMessage = new Models.CommitFullMessage() + }; + } + else + { + var message = await new Commands.QueryCommitFullMessage(submoduleRoot, file.SHA).GetResultAsync(); + ViewRevisionFileContent = new Models.RevisionSubmodule() + { + Commit = commit, + FullMessage = new Models.CommitFullMessage { Message = message } + }; + } + } + + private async Task ResetToThisRevisionAsync(string path) { var log = _repo.CreateLog($"Reset File to '{_commit.SHA}'"); - return Task.Run(() => - { - new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(path, $"{_commit.SHA}"); - log.Complete(); - }); + await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(path, $"{_commit.SHA}"); + log.Complete(); } - private Task ResetToParentRevision(Models.Change change) + private async Task ResetToParentRevisionAsync(Models.Change change) { var log = _repo.CreateLog($"Reset File to '{_commit.SHA}~1'"); - return Task.Run(() => - { - if (change.Index == Models.ChangeState.Renamed) - new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(change.OriginalPath, $"{_commit.SHA}~1"); + if (change.Index == Models.ChangeState.Renamed) + await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.OriginalPath, $"{_commit.SHA}~1"); - new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevision(change.Path, $"{_commit.SHA}~1"); - log.Complete(); - }); + await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.Path, $"{_commit.SHA}~1"); + log.Complete(); } [GeneratedRegex(@"\b(https?://|ftp://)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]")] @@ -894,6 +1045,8 @@ namespace SourceGit.ViewModels private static partial Regex REG_SHA_FORMAT(); private Repository _repo = null; + private bool _rememberActivePageIndex = true; + private int _activePageIndex = 0; private Models.Commit _commit = null; private Models.CommitFullMessage _fullMessage = null; private Models.CommitSignInfo _signInfo = null; diff --git a/src/ViewModels/ConfigureCustomActionControls.cs b/src/ViewModels/ConfigureCustomActionControls.cs index f0588af7..abe992ea 100644 --- a/src/ViewModels/ConfigureCustomActionControls.cs +++ b/src/ViewModels/ConfigureCustomActionControls.cs @@ -23,7 +23,12 @@ namespace SourceGit.ViewModels public void Add() { - var added = new Models.CustomActionControl() { Type = Models.CustomActionControlType.TextBox }; + var added = new Models.CustomActionControl() + { + Label = "Unnamed", + Type = Models.CustomActionControlType.TextBox + }; + Controls.Add(added); Edit = added; } diff --git a/src/ViewModels/ConfigureWorkspace.cs b/src/ViewModels/ConfigureWorkspace.cs index 5be066ae..638caf5d 100644 --- a/src/ViewModels/ConfigureWorkspace.cs +++ b/src/ViewModels/ConfigureWorkspace.cs @@ -17,7 +17,7 @@ namespace SourceGit.ViewModels set { if (SetProperty(ref _selected, value)) - CanDeleteSelected = value != null && !value.IsActive; + CanDeleteSelected = value is { IsActive: false }; } } diff --git a/src/ViewModels/Confirm.cs b/src/ViewModels/Confirm.cs deleted file mode 100644 index ecbda72c..00000000 --- a/src/ViewModels/Confirm.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace SourceGit.ViewModels -{ - public class Confirm - { - public string Message - { - get; - private set; - } - - public Confirm(string message, Action onSure, Action onCancel = null) - { - Message = message; - _onSure = onSure; - _onCancel = onCancel; - } - - public void Done(bool isSure) - { - if (isSure) - _onSure?.Invoke(); - else - _onCancel?.Invoke(); - } - - private Action _onSure; - private Action _onCancel; - } -} diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs index bf93b5bc..585fee0b 100644 --- a/src/ViewModels/Conflict.cs +++ b/src/ViewModels/Conflict.cs @@ -19,7 +19,7 @@ namespace SourceGit.ViewModels { Name = branch.Name; Head = branch.Head; - Revision = new Commands.QuerySingleCommit(repo.FullPath, branch.Head).Result() ?? new Models.Commit() { SHA = branch.Head }; + Revision = new Commands.QuerySingleCommit(repo.FullPath, branch.Head).GetResultAsync().Result ?? new Models.Commit() { SHA = branch.Head }; } } @@ -65,42 +65,39 @@ namespace SourceGit.ViewModels _change = change; var isSubmodule = repo.Submodules.Find(x => x.Path.Equals(change.Path, StringComparison.Ordinal)) != null; - if (!isSubmodule && (_change.ConflictReason == Models.ConflictReason.BothAdded || _change.ConflictReason == Models.ConflictReason.BothModified)) + if (!isSubmodule && (_change.ConflictReason is Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified)) { CanUseExternalMergeTool = true; - IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).Result(); + IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).GetResultAsync().Result; } - var context = wc.InProgressContext; - if (context is CherryPickInProgress cherryPick) + switch (wc.InProgressContext) { - Theirs = cherryPick.Head; - Mine = new ConflictSourceBranch(repo, repo.CurrentBranch); - } - else if (context is RebaseInProgress rebase) - { - var b = repo.Branches.Find(x => x.IsLocal && x.Name == rebase.HeadName); - if (b != null) - Theirs = new ConflictSourceBranch(b.Name, b.Head, rebase.StoppedAt); - else - Theirs = new ConflictSourceBranch(rebase.HeadName, rebase.StoppedAt?.SHA ?? "----------", rebase.StoppedAt); + case CherryPickInProgress cherryPick: + Theirs = cherryPick.Head; + Mine = new ConflictSourceBranch(repo, repo.CurrentBranch); + break; + case RebaseInProgress rebase: + var b = repo.Branches.Find(x => x.IsLocal && x.Name == rebase.HeadName); + if (b != null) + Theirs = new ConflictSourceBranch(b.Name, b.Head, rebase.StoppedAt); + else + Theirs = new ConflictSourceBranch(rebase.HeadName, rebase.StoppedAt?.SHA ?? "----------", rebase.StoppedAt); - Mine = rebase.Onto; - } - else if (context is RevertInProgress revert) - { - Theirs = revert.Head; - Mine = new ConflictSourceBranch(repo, repo.CurrentBranch); - } - else if (context is MergeInProgress merge) - { - Theirs = merge.Source; - Mine = new ConflictSourceBranch(repo, repo.CurrentBranch); - } - else - { - Theirs = "Stash or Patch"; - Mine = new ConflictSourceBranch(repo, repo.CurrentBranch); + Mine = rebase.Onto; + break; + case RevertInProgress revert: + Theirs = revert.Head; + Mine = new ConflictSourceBranch(repo, repo.CurrentBranch); + break; + case MergeInProgress merge: + Theirs = merge.Source; + Mine = new ConflictSourceBranch(repo, repo.CurrentBranch); + break; + default: + Theirs = "Stash or Patch"; + Mine = new ConflictSourceBranch(repo, repo.CurrentBranch); + break; } } diff --git a/src/ViewModels/ConventionalCommitMessageBuilder.cs b/src/ViewModels/ConventionalCommitMessageBuilder.cs index 8faabe86..5eb9494c 100644 --- a/src/ViewModels/ConventionalCommitMessageBuilder.cs +++ b/src/ViewModels/ConventionalCommitMessageBuilder.cs @@ -72,10 +72,9 @@ namespace SourceGit.ViewModels builder.Append(")"); } - if (string.IsNullOrEmpty(_breakingChanges)) - builder.Append(": "); - else - builder.Append("!: "); + if (!string.IsNullOrEmpty(_breakingChanges)) + builder.Append("!"); + builder.Append(": "); builder.Append(_description); builder.Append("\n\n"); diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs index 6e01eb39..07080d7e 100644 --- a/src/ViewModels/CreateBranch.cs +++ b/src/ViewModels/CreateBranch.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; namespace SourceGit.ViewModels @@ -67,12 +68,10 @@ namespace SourceGit.ViewModels public CreateBranch(Repository repo, Models.Branch branch) { _repo = repo; - _baseOnRevision = branch.IsDetachedHead ? branch.Head : branch.FullName; + _baseOnRevision = branch.Head; if (!branch.IsLocal && repo.Branches.Find(x => x.IsLocal && x.Name == branch.Name) == null) - { Name = branch.Name; - } BasedOn = branch; DiscardLocalChanges = false; @@ -102,123 +101,122 @@ namespace SourceGit.ViewModels { if (!creator._allowOverwrite) { - var fixedName = creator.FixName(name); + var fixedName = Models.Branch.FixName(name); foreach (var b in creator._repo.Branches) { - if (b.FriendlyName == fixedName) + if (b.FriendlyName.Equals(fixedName, StringComparison.Ordinal)) return new ValidationResult("A branch with same name already exists!"); } } return ValidationResult.Success; } - else - { - return new ValidationResult("Missing runtime context to create branch!"); - } + + return new ValidationResult("Missing runtime context to create branch!"); } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); - var fixedName = FixName(_name); + var fixedName = Models.Branch.FixName(_name); var log = _repo.CreateLog($"Create Branch '{fixedName}'"); Use(log); - var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules; - return Task.Run(() => + if (CheckoutAfterCreated) { - bool succ; - - if (CheckoutAfterCreated && !_repo.ConfirmCheckoutBranch()) + if (_repo.CurrentBranch is { IsDetachedHead: true } && !_repo.CurrentBranch.Head.Equals(_baseOnRevision, StringComparison.Ordinal)) { - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - } - - if (CheckoutAfterCreated && !_repo.IsBare) - { - var needPopStash = false; - if (DiscardLocalChanges) + var refs = await new Commands.QueryRefsContainsCommit(_repo.FullPath, _repo.CurrentBranch.Head).GetResultAsync(); + if (refs.Count == 0) { - succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision, true, _allowOverwrite); - } - else - { - var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); - if (changes > 0) + var msg = App.Text("Checkout.WarnLostCommits"); + var shouldContinue = await App.AskConfirmAsync(msg, null); + if (!shouldContinue) { - succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CREATE_BRANCH_AUTO_STASH"); - if (!succ) - { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; - } + _repo.SetWatcherEnabled(true); + return true; + } + } + } + } - needPopStash = true; + bool succ; + if (CheckoutAfterCreated && !_repo.IsBare) + { + var needPopStash = false; + if (!DiscardLocalChanges) + { + var changes = await new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).GetResultAsync(); + if (changes > 0) + { + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushAsync("CREATE_BRANCH_AUTO_STASH"); + if (!succ) + { + log.Complete(); + _repo.SetWatcherEnabled(true); + return false; } - succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision, false, _allowOverwrite); - } - - if (succ) - { - if (updateSubmodules) - { - var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); - if (submodules.Count > 0) - new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); - } - - if (needPopStash) - new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); + needPopStash = true; } } - else - { - succ = Commands.Branch.Create(_repo.FullPath, fixedName, _baseOnRevision, _allowOverwrite, log); - } - log.Complete(); + succ = await new Commands.Checkout(_repo.FullPath) + .Use(log) + .BranchAsync(fixedName, _baseOnRevision, DiscardLocalChanges, _allowOverwrite); - CallUIThread(() => + if (succ) { - if (succ && CheckoutAfterCreated) + if (IsRecurseSubmoduleVisible && RecurseSubmodules) { - var fake = new Models.Branch() { IsLocal = true, FullName = $"refs/heads/{fixedName}" }; - if (BasedOn is Models.Branch based && !based.IsLocal) - fake.Upstream = based.FullName; - - var folderEndIdx = fake.FullName.LastIndexOf('/'); - if (folderEndIdx > 10) - _repo.Settings.ExpandedBranchNodesInSideBar.Add(fake.FullName.Substring(0, folderEndIdx)); - - if (_repo.HistoriesFilterMode == Models.FilterMode.Included) - _repo.SetBranchFilterMode(fake, Models.FilterMode.Included, true, false); - - ProgressDescription = "Waiting for branch updated..."; + var submodules = await new Commands.QueryUpdatableSubmodules(_repo.FullPath).GetResultAsync(); + if (submodules.Count > 0) + await new Commands.Submodule(_repo.FullPath) + .Use(log) + .UpdateAsync(submodules, true, true); } - _repo.MarkBranchesDirtyManually(); - _repo.SetWatcherEnabled(true); - }); + if (needPopStash) + await new Commands.Stash(_repo.FullPath) + .Use(log) + .PopAsync("stash@{0}"); + } + } + else + { + succ = await Commands.Branch.CreateAsync(_repo.FullPath, fixedName, _baseOnRevision, _allowOverwrite, log); + } - if (CheckoutAfterCreated) - Task.Delay(400).Wait(); + log.Complete(); - return true; - }); - } + if (succ && CheckoutAfterCreated) + { + var fake = new Models.Branch() { IsLocal = true, FullName = $"refs/heads/{fixedName}" }; + if (BasedOn is Models.Branch { IsLocal: false } based) + fake.Upstream = based.FullName; - private string FixName(string name) - { - if (!name.Contains(' ')) - return name; + var folderEndIdx = fake.FullName.LastIndexOf('/'); + if (folderEndIdx > 10) + _repo.Settings.ExpandedBranchNodesInSideBar.Add(fake.FullName.Substring(0, folderEndIdx)); - var parts = name.Split(' ', System.StringSplitOptions.RemoveEmptyEntries); - return string.Join("-", parts); + if (_repo.HistoriesFilterMode == Models.FilterMode.Included) + _repo.SetBranchFilterMode(fake, Models.FilterMode.Included, true, false); + + } + + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); + + if (CheckoutAfterCreated) + { + ProgressDescription = "Waiting for branch updated..."; + await Task.Delay(400); + } + + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/CreateGroup.cs b/src/ViewModels/CreateGroup.cs index fb0218f0..eb8ef2b5 100644 --- a/src/ViewModels/CreateGroup.cs +++ b/src/ViewModels/CreateGroup.cs @@ -29,7 +29,7 @@ namespace SourceGit.ViewModels }, _parent, true); Welcome.Instance.Refresh(); - return null; + return Task.FromResult(true); } private readonly RepositoryNode _parent = null; diff --git a/src/ViewModels/CreateTag.cs b/src/ViewModels/CreateTag.cs index d3cd512b..85e2b973 100644 --- a/src/ViewModels/CreateTag.cs +++ b/src/ViewModels/CreateTag.cs @@ -51,7 +51,7 @@ namespace SourceGit.ViewModels _basedOn = branch.Head; BasedOn = branch; - SignTag = new Commands.Config(repo.FullPath).Get("tag.gpgsign").Equals("true", StringComparison.OrdinalIgnoreCase); + SignTag = new Commands.Config(repo.FullPath).GetAsync("tag.gpgsign").Result.Equals("true", StringComparison.OrdinalIgnoreCase); } public CreateTag(Repository repo, Models.Commit commit) @@ -60,7 +60,7 @@ namespace SourceGit.ViewModels _basedOn = commit.SHA; BasedOn = commit; - SignTag = new Commands.Config(repo.FullPath).Get("tag.gpgsign").Equals("true", StringComparison.OrdinalIgnoreCase); + SignTag = new Commands.Config(repo.FullPath).GetAsync("tag.gpgsign").Result.Equals("true", StringComparison.OrdinalIgnoreCase); } public static ValidationResult ValidateTagName(string name, ValidationContext ctx) @@ -74,7 +74,7 @@ namespace SourceGit.ViewModels return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Create tag..."; @@ -83,24 +83,23 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Create Tag"); Use(log); - return Task.Run(() => + bool succ; + if (_annotated) + succ = await Commands.Tag.AddAsync(_repo.FullPath, _tagName, _basedOn, Message, SignTag, log); + else + succ = await Commands.Tag.AddAsync(_repo.FullPath, _tagName, _basedOn, log); + + if (succ && remotes != null) { - bool succ; - if (_annotated) - succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, Message, SignTag, log); - else - succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, log); + foreach (var remote in remotes) + await new Commands.Push(_repo.FullPath, remote.Name, $"refs/tags/{_tagName}", false) + .Use(log) + .RunAsync(); + } - if (succ && remotes != null) - { - foreach (var remote in remotes) - new Commands.Push(_repo.FullPath, remote.Name, $"refs/tags/{_tagName}", false).Use(log).Exec(); - } - - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/CustomActionContextMenuLabel.cs b/src/ViewModels/CustomActionContextMenuLabel.cs new file mode 100644 index 00000000..82804f09 --- /dev/null +++ b/src/ViewModels/CustomActionContextMenuLabel.cs @@ -0,0 +1,8 @@ +namespace SourceGit.ViewModels +{ + public class CustomActionContextMenuLabel(string name, bool isGlobal) + { + public string Name { get; set; } = name; + public bool IsGlobal { get; set; } = isGlobal; + } +} diff --git a/src/ViewModels/DeinitSubmodule.cs b/src/ViewModels/DeinitSubmodule.cs index a96a65d0..1769ef80 100644 --- a/src/ViewModels/DeinitSubmodule.cs +++ b/src/ViewModels/DeinitSubmodule.cs @@ -23,7 +23,7 @@ namespace SourceGit.ViewModels Force = false; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "De-initialize Submodule"; @@ -31,13 +31,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("De-initialize Submodule"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Submodule(_repo.FullPath).Use(log).Deinit(Submodule, false); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.Submodule(_repo.FullPath) + .Use(log) + .DeinitAsync(Submodule, false); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private Repository _repo; diff --git a/src/ViewModels/DeleteBranch.cs b/src/ViewModels/DeleteBranch.cs index 4decdb40..0c8ee154 100644 --- a/src/ViewModels/DeleteBranch.cs +++ b/src/ViewModels/DeleteBranch.cs @@ -39,7 +39,7 @@ namespace SourceGit.ViewModels } } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Deleting branch..."; @@ -47,29 +47,22 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Delete Branch"); Use(log); - return Task.Run(() => + if (Target.IsLocal) { - if (Target.IsLocal) - { - Commands.Branch.DeleteLocal(_repo.FullPath, Target.Name, log); + await Commands.Branch.DeleteLocalAsync(_repo.FullPath, Target.Name, log); + if (_alsoDeleteTrackingRemote && TrackingRemoteBranch != null) + await Commands.Branch.DeleteRemoteAsync(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name, log); + } + else + { + await Commands.Branch.DeleteRemoteAsync(_repo.FullPath, Target.Remote, Target.Name, log); + } - if (_alsoDeleteTrackingRemote && TrackingRemoteBranch != null) - Commands.Branch.DeleteRemote(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name, log); - } - else - { - Commands.Branch.DeleteRemote(_repo.FullPath, Target.Remote, Target.Name, log); - } + log.Complete(); - log.Complete(); - - CallUIThread(() => - { - _repo.MarkBranchesDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - return true; - }); + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/DeleteMultipleBranches.cs b/src/ViewModels/DeleteMultipleBranches.cs index b40ff223..e8ca45ab 100644 --- a/src/ViewModels/DeleteMultipleBranches.cs +++ b/src/ViewModels/DeleteMultipleBranches.cs @@ -17,7 +17,7 @@ namespace SourceGit.ViewModels Targets = branches; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Deleting multiple branches..."; @@ -25,29 +25,21 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Delete Multiple Branches"); Use(log); - return Task.Run(() => + if (_isLocal) { - if (_isLocal) - { - foreach (var target in Targets) - Commands.Branch.DeleteLocal(_repo.FullPath, target.Name, log); - } - else - { - foreach (var target in Targets) - Commands.Branch.DeleteRemote(_repo.FullPath, target.Remote, target.Name, log); - } + foreach (var target in Targets) + await Commands.Branch.DeleteLocalAsync(_repo.FullPath, target.Name, log); + } + else + { + foreach (var target in Targets) + await Commands.Branch.DeleteRemoteAsync(_repo.FullPath, target.Remote, target.Name, log); + } - log.Complete(); - - CallUIThread(() => - { - _repo.MarkBranchesDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - - return true; - }); + log.Complete(); + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); + return true; } private Repository _repo = null; diff --git a/src/ViewModels/DeleteRemote.cs b/src/ViewModels/DeleteRemote.cs index faf7c8a9..595a7d52 100644 --- a/src/ViewModels/DeleteRemote.cs +++ b/src/ViewModels/DeleteRemote.cs @@ -16,7 +16,7 @@ namespace SourceGit.ViewModels Remote = remote; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Deleting remote ..."; @@ -24,18 +24,14 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Delete Remote"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Remote(_repo.FullPath).Use(log).Delete(Remote.Name); - log.Complete(); + var succ = await new Commands.Remote(_repo.FullPath) + .Use(log) + .DeleteAsync(Remote.Name); - CallUIThread(() => - { - _repo.MarkBranchesDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - return succ; - }); + log.Complete(); + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/DeleteRepositoryNode.cs b/src/ViewModels/DeleteRepositoryNode.cs index 38e03d9f..59ef60ea 100644 --- a/src/ViewModels/DeleteRepositoryNode.cs +++ b/src/ViewModels/DeleteRepositoryNode.cs @@ -18,7 +18,7 @@ namespace SourceGit.ViewModels { Preferences.Instance.RemoveNode(Node, true); Welcome.Instance.Refresh(); - return null; + return Task.FromResult(true); } } } diff --git a/src/ViewModels/DeleteSubmodule.cs b/src/ViewModels/DeleteSubmodule.cs index 239c7d31..34a77c6e 100644 --- a/src/ViewModels/DeleteSubmodule.cs +++ b/src/ViewModels/DeleteSubmodule.cs @@ -16,7 +16,7 @@ namespace SourceGit.ViewModels Submodule = submodule; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Deleting submodule ..."; @@ -24,13 +24,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Delete Submodule"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Submodule(_repo.FullPath).Use(log).Delete(Submodule); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.Submodule(_repo.FullPath) + .Use(log) + .DeleteAsync(Submodule); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/DeleteTag.cs b/src/ViewModels/DeleteTag.cs index f7de6341..b283a837 100644 --- a/src/ViewModels/DeleteTag.cs +++ b/src/ViewModels/DeleteTag.cs @@ -22,7 +22,7 @@ namespace SourceGit.ViewModels Target = tag; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Deleting tag '{Target.Name}' ..."; @@ -31,24 +31,19 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Delete Tag"); Use(log); - return Task.Run(() => + var succ = await Commands.Tag.DeleteAsync(_repo.FullPath, Target.Name, log); + if (succ) { - var succ = Commands.Tag.Delete(_repo.FullPath, Target.Name, log); - if (succ) - { - foreach (var r in remotes) - new Commands.Push(_repo.FullPath, r.Name, $"refs/tags/{Target.Name}", true).Use(log).Exec(); - } + foreach (var r in remotes) + await new Commands.Push(_repo.FullPath, r.Name, $"refs/tags/{Target.Name}", true) + .Use(log) + .RunAsync(); + } - log.Complete(); - - CallUIThread(() => - { - _repo.MarkTagsDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - return succ; - }); + log.Complete(); + _repo.MarkTagsDirtyManually(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 9bb3c710..5b772fc8 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -97,7 +97,7 @@ namespace SourceGit.ViewModels { var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, toolType, toolPath, _option)); + Task.Run(() => Commands.MergeTool.OpenForDiffAsync(_repo, toolType, toolPath, _option)); } private void LoadDiffContent() @@ -109,18 +109,22 @@ namespace SourceGit.ViewModels return; } - Task.Run(() => + Task.Run(async () => { var numLines = Preferences.Instance.UseFullTextDiff ? 999999999 : _unifiedLines; var ignoreWhitespace = Preferences.Instance.IgnoreWhitespaceChangesInDiff; - var latest = new Commands.Diff(_repo, _option, numLines, ignoreWhitespace).Result(); + + var latest = await new Commands.Diff(_repo, _option, numLines, ignoreWhitespace) + .ReadAsync() + .ConfigureAwait(false); + var info = new Info(_option, numLines, ignoreWhitespace, latest); if (_info != null && info.IsSame(_info)) return; _info = info; - var rs = null as object; + object rs = null; if (latest.TextDiff != null) { var count = latest.TextDiff.Lines.Count; @@ -141,9 +145,9 @@ namespace SourceGit.ViewModels var sha = line.Content.Substring(18); if (line.Type == Models.TextDiffLineType.Added) - submoduleDiff.New = QuerySubmoduleRevision(submoduleRoot, sha); + submoduleDiff.New = await QuerySubmoduleRevisionAsync(submoduleRoot, sha).ConfigureAwait(false); else if (line.Type == Models.TextDiffLineType.Deleted) - submoduleDiff.Old = QuerySubmoduleRevision(submoduleRoot, sha); + submoduleDiff.Old = await QuerySubmoduleRevisionAsync(submoduleRoot, sha).ConfigureAwait(false); } if (isSubmodule) @@ -167,8 +171,8 @@ namespace SourceGit.ViewModels if (_option.Revisions.Count == 2) { - var oldImage = ImageSource.FromRevision(_repo, _option.Revisions[0], oldPath, imgDecoder); - var newImage = ImageSource.FromRevision(_repo, _option.Revisions[1], _option.Path, imgDecoder); + var oldImage = await ImageSource.FromRevisionAsync(_repo, _option.Revisions[0], oldPath, imgDecoder).ConfigureAwait(false); + var newImage = await ImageSource.FromRevisionAsync(_repo, _option.Revisions[1], _option.Path, imgDecoder).ConfigureAwait(false); imgDiff.Old = oldImage.Bitmap; imgDiff.OldFileSize = oldImage.Size; imgDiff.New = newImage.Bitmap; @@ -178,7 +182,7 @@ namespace SourceGit.ViewModels { if (!oldPath.Equals("/dev/null", StringComparison.Ordinal)) { - var oldImage = ImageSource.FromRevision(_repo, "HEAD", oldPath, imgDecoder); + var oldImage = await ImageSource.FromRevisionAsync(_repo, "HEAD", oldPath, imgDecoder).ConfigureAwait(false); imgDiff.Old = oldImage.Bitmap; imgDiff.OldFileSize = oldImage.Size; } @@ -186,7 +190,7 @@ namespace SourceGit.ViewModels var fullPath = Path.Combine(_repo, _option.Path); if (File.Exists(fullPath)) { - var newImage = ImageSource.FromFile(fullPath, imgDecoder); + var newImage = await ImageSource.FromFileAsync(fullPath, imgDecoder).ConfigureAwait(false); imgDiff.New = newImage.Bitmap; imgDiff.NewFileSize = newImage.Size; } @@ -199,13 +203,13 @@ namespace SourceGit.ViewModels var binaryDiff = new Models.BinaryDiff(); if (_option.Revisions.Count == 2) { - binaryDiff.OldSize = new Commands.QueryFileSize(_repo, oldPath, _option.Revisions[0]).Result(); - binaryDiff.NewSize = new Commands.QueryFileSize(_repo, _option.Path, _option.Revisions[1]).Result(); + binaryDiff.OldSize = await new Commands.QueryFileSize(_repo, oldPath, _option.Revisions[0]).GetResultAsync().ConfigureAwait(false); + binaryDiff.NewSize = await new Commands.QueryFileSize(_repo, _option.Path, _option.Revisions[1]).GetResultAsync().ConfigureAwait(false); } else { var fullPath = Path.Combine(_repo, _option.Path); - binaryDiff.OldSize = new Commands.QueryFileSize(_repo, oldPath, "HEAD").Result(); + binaryDiff.OldSize = await new Commands.QueryFileSize(_repo, oldPath, "HEAD").GetResultAsync().ConfigureAwait(false); binaryDiff.NewSize = File.Exists(fullPath) ? new FileInfo(fullPath).Length : 0; } rs = binaryDiff; @@ -236,13 +240,13 @@ namespace SourceGit.ViewModels }); } - private Models.RevisionSubmodule QuerySubmoduleRevision(string repo, string sha) + private async Task QuerySubmoduleRevisionAsync(string repo, string sha) { - var commit = new Commands.QuerySingleCommit(repo, sha).Result(); + var commit = await new Commands.QuerySingleCommit(repo, sha).GetResultAsync().ConfigureAwait(false); if (commit == null) return new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = sha } }; - var body = new Commands.QueryCommitFullMessage(repo, sha).Result(); + var body = await new Commands.QueryCommitFullMessage(repo, sha).GetResultAsync().ConfigureAwait(false); return new Models.RevisionSubmodule() { Commit = commit, diff --git a/src/ViewModels/DirHistories.cs b/src/ViewModels/DirHistories.cs new file mode 100644 index 00000000..6b4aa039 --- /dev/null +++ b/src/ViewModels/DirHistories.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class DirHistories : ObservableObject + { + public string Title + { + get; + } + + public bool IsLoading + { + get => _isLoading; + private set => SetProperty(ref _isLoading, value); + } + + public List Commits + { + get => _commits; + private set => SetProperty(ref _commits, value); + } + + public Models.Commit SelectedCommit + { + get => _selectedCommit; + set + { + if (SetProperty(ref _selectedCommit, value)) + Detail.Commit = value; + } + } + + public CommitDetail Detail + { + get => _detail; + private set => SetProperty(ref _detail, value); + } + + public DirHistories(Repository repo, string dir, string revision = null) + { + if (!string.IsNullOrEmpty(revision)) + Title = $"{dir} @ {revision}"; + else + Title = dir; + + _repo = repo; + _detail = new CommitDetail(repo, false); + _detail.SearchChangeFilter = dir; + + Task.Run(async () => + { + var commits = await new Commands.QueryCommits(_repo.FullPath, $"--date-order -n 10000 {revision ?? string.Empty} -- \"{dir}\"", false) + .GetResultAsync() + .ConfigureAwait(false); + + Dispatcher.UIThread.Post(() => + { + Commits = commits; + IsLoading = false; + + if (commits.Count > 0) + SelectedCommit = commits[0]; + }); + }); + } + + public void NavigateToCommit(Models.Commit commit) + { + _repo.NavigateToCommit(commit.SHA); + } + + public string GetCommitFullMessage(Models.Commit commit) + { + var sha = commit.SHA; + if (_cachedCommitFullMessage.TryGetValue(sha, out var msg)) + return msg; + + msg = new Commands.QueryCommitFullMessage(_repo.FullPath, sha).GetResultAsync().Result; + _cachedCommitFullMessage[sha] = msg; + return msg; + } + + private Repository _repo = null; + private bool _isLoading = true; + private List _commits = []; + private Models.Commit _selectedCommit = null; + private CommitDetail _detail = null; + private Dictionary _cachedCommitFullMessage = new(); + } +} diff --git a/src/ViewModels/Discard.cs b/src/ViewModels/Discard.cs index 7619635c..1fcda904 100644 --- a/src/ViewModels/Discard.cs +++ b/src/ViewModels/Discard.cs @@ -56,7 +56,7 @@ namespace SourceGit.ViewModels Mode = new DiscardMultipleFiles() { Count = _changes.Count }; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = _changes == null ? "Discard all local changes ..." : $"Discard total {_changes.Count} changes ..."; @@ -64,23 +64,15 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Discard all"); Use(log); - return Task.Run(() => - { - if (Mode is DiscardAllMode all) - Commands.Discard.All(_repo.FullPath, all.IncludeIgnored, log); - else - Commands.Discard.Changes(_repo.FullPath, _changes, log); + if (Mode is DiscardAllMode all) + await Commands.Discard.AllAsync(_repo.FullPath, all.IncludeIgnored, log); + else + await Commands.Discard.ChangesAsync(_repo.FullPath, _changes, log); - log.Complete(); - - CallUIThread(() => - { - _repo.MarkWorkingCopyDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - - return true; - }); + log.Complete(); + _repo.MarkWorkingCopyDirtyManually(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/DropStash.cs b/src/ViewModels/DropStash.cs index 545da010..8c826c2c 100644 --- a/src/ViewModels/DropStash.cs +++ b/src/ViewModels/DropStash.cs @@ -12,19 +12,19 @@ namespace SourceGit.ViewModels Stash = stash; } - public override Task Sure() + public override async Task Sure() { ProgressDescription = $"Dropping stash: {Stash.Name}"; var log = _repo.CreateLog("Drop Stash"); Use(log); - return Task.Run(() => - { - new Commands.Stash(_repo.FullPath).Use(log).Drop(Stash.Name); - log.Complete(); - return true; - }); + await new Commands.Stash(_repo.FullPath) + .Use(log) + .DropAsync(Stash.Name); + + log.Complete(); + return true; } private readonly Repository _repo; diff --git a/src/ViewModels/EditRemote.cs b/src/ViewModels/EditRemote.cs index 763c8ce1..4c7e3237 100644 --- a/src/ViewModels/EditRemote.cs +++ b/src/ViewModels/EditRemote.cs @@ -53,9 +53,7 @@ namespace SourceGit.ViewModels _useSSH = Models.Remote.IsSSH(remote.URL); if (_useSSH) - { - SSHKey = new Commands.Config(repo.FullPath).Get($"remote.{remote.Name}.sshkey"); - } + SSHKey = new Commands.Config(repo.FullPath).GetAsync($"remote.{remote.Name}.sshkey").Result; } public static ValidationResult ValidateRemoteName(string name, ValidationContext ctx) @@ -100,36 +98,33 @@ namespace SourceGit.ViewModels return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Editing remote '{_remote.Name}' ..."; - return Task.Run(() => + if (_remote.Name != _name) { - if (_remote.Name != _name) - { - var succ = new Commands.Remote(_repo.FullPath).Rename(_remote.Name, _name); - if (succ) - _remote.Name = _name; - } + var succ = await new Commands.Remote(_repo.FullPath).RenameAsync(_remote.Name, _name); + if (succ) + _remote.Name = _name; + } - if (_remote.URL != _url) - { - var succ = new Commands.Remote(_repo.FullPath).SetURL(_name, _url, false); - if (succ) - _remote.URL = _url; - } + if (_remote.URL != _url) + { + var succ = await new Commands.Remote(_repo.FullPath).SetURLAsync(_name, _url, false); + if (succ) + _remote.URL = _url; + } - var pushURL = new Commands.Remote(_repo.FullPath).GetURL(_name, true); - if (pushURL != _url) - new Commands.Remote(_repo.FullPath).SetURL(_name, _url, true); + var pushURL = await new Commands.Remote(_repo.FullPath).GetURLAsync(_name, true); + if (pushURL != _url) + await new Commands.Remote(_repo.FullPath).SetURLAsync(_name, _url, true); - new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); + await new Commands.Config(_repo.FullPath).SetAsync($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/EditRepositoryNode.cs b/src/ViewModels/EditRepositoryNode.cs index 599b7f63..38d072a1 100644 --- a/src/ViewModels/EditRepositoryNode.cs +++ b/src/ViewModels/EditRepositoryNode.cs @@ -51,7 +51,7 @@ namespace SourceGit.ViewModels Welcome.Instance.Refresh(); } - return null; + return Task.FromResult(true); } private RepositoryNode _node = null; diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index b46df5b0..daffdc52 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -72,6 +72,36 @@ namespace SourceGit.ViewModels public string GetValue() => IsChecked ? CheckedValue : string.Empty; } + public class CustomActionControlComboBox : ObservableObject, ICustomActionControlParameter + { + public string Label { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public List Options { get; set; } = []; + + public string Value + { + get => _value; + set => SetProperty(ref _value, value); + } + + public CustomActionControlComboBox(string label, string description, string options) + { + Label = label; + Description = description; + + var parts = options.Split('|', StringSplitOptions.TrimEntries); + if (parts.Length > 0) + { + Options.AddRange(parts); + _value = parts[0]; + } + } + + public string GetValue() => _value; + + private string _value = string.Empty; + } + public class ExecuteCustomAction : Popup { public Models.CustomAction CustomAction @@ -121,7 +151,7 @@ namespace SourceGit.ViewModels PrepareControlParameters(); } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Run custom action ..."; @@ -136,19 +166,16 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog(CustomAction.Name); Use(log); - return Task.Run(() => - { - log.AppendLine($"$ {CustomAction.Executable} {cmdline}\n"); + log.AppendLine($"$ {CustomAction.Executable} {cmdline}\n"); - if (CustomAction.WaitForExit) - RunAndWait(cmdline, log); - else - Run(cmdline); + if (CustomAction.WaitForExit) + await RunAsync(cmdline, log); + else + _ = Task.Run(() => Run(cmdline)); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private void PrepareControlParameters() @@ -160,11 +187,14 @@ namespace SourceGit.ViewModels case Models.CustomActionControlType.TextBox: ControlParameters.Add(new CustomActionControlTextBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); break; + case Models.CustomActionControlType.PathSelector: + ControlParameters.Add(new CustomActionControlPathSelector(ctl.Label, ctl.Description, ctl.BoolValue, PrepareStringByTarget(ctl.StringValue))); + break; case Models.CustomActionControlType.CheckBox: ControlParameters.Add(new CustomActionControlCheckBox(ctl.Label, ctl.Description, ctl.StringValue, ctl.BoolValue)); break; - case Models.CustomActionControlType.PathSelector: - ControlParameters.Add(new CustomActionControlPathSelector(ctl.Label, ctl.Description, ctl.BoolValue, PrepareStringByTarget(ctl.StringValue))); + case Models.CustomActionControlType.ComboBox: + ControlParameters.Add(new CustomActionControlComboBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); break; } } @@ -174,14 +204,13 @@ namespace SourceGit.ViewModels { org = org.Replace("${REPO}", GetWorkdir()); - if (Target is Models.Branch b) - return org.Replace("${BRANCH}", b.FriendlyName); - else if (Target is Models.Commit c) - return org.Replace("${SHA}", c.SHA); - else if (Target is Models.Tag t) - return org.Replace("${TAG}", t.Name); - - return org; + return Target switch + { + Models.Branch b => org.Replace("${BRANCH}", b.FriendlyName), + Models.Commit c => org.Replace("${SHA}", c.SHA), + Models.Tag t => org.Replace("${TAG}", t.Name), + _ => org + }; } private string GetWorkdir() @@ -204,11 +233,11 @@ namespace SourceGit.ViewModels } catch (Exception e) { - CallUIThread(() => App.RaiseException(_repo.FullPath, e.Message)); + App.RaiseException(_repo.FullPath, e.Message); } } - private void RunAndWait(string args, Models.ICommandLog log) + private async Task RunAsync(string args, Models.ICommandLog log) { var start = new ProcessStartInfo(); start.FileName = CustomAction.Executable; @@ -244,19 +273,19 @@ namespace SourceGit.ViewModels proc.Start(); proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); - proc.WaitForExit(); + await proc.WaitForExitAsync().ConfigureAwait(false); var exitCode = proc.ExitCode; if (exitCode != 0) { var errMsg = builder.ToString().Trim(); if (!string.IsNullOrEmpty(errMsg)) - CallUIThread(() => App.RaiseException(_repo.FullPath, errMsg)); + App.RaiseException(_repo.FullPath, errMsg); } } catch (Exception e) { - CallUIThread(() => App.RaiseException(_repo.FullPath, e.Message)); + App.RaiseException(_repo.FullPath, e.Message); } proc.Close(); diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs index 3225f9e2..0930e777 100644 --- a/src/ViewModels/Fetch.cs +++ b/src/ViewModels/Fetch.cs @@ -54,7 +54,7 @@ namespace SourceGit.ViewModels } } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); @@ -63,36 +63,32 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Fetch"); Use(log); - return Task.Run(() => + if (FetchAllRemotes) { - if (FetchAllRemotes) - { - foreach (var remote in _repo.Remotes) - new Commands.Fetch(_repo.FullPath, remote.Name, notags, force).Use(log).Exec(); - } - else - { - new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, force).Use(log).Exec(); - } + foreach (var remote in _repo.Remotes) + await new Commands.Fetch(_repo.FullPath, remote.Name, notags, force) + .Use(log) + .RunAsync(); + } + else + { + await new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, force) + .Use(log) + .RunAsync(); + } - log.Complete(); + log.Complete(); - var upstream = _repo.CurrentBranch?.Upstream; - var upstreamHead = string.Empty; - if (!string.IsNullOrEmpty(upstream)) - upstreamHead = new Commands.QueryRevisionByRefName(_repo.FullPath, upstream.Substring(13)).Result(); + var upstream = _repo.CurrentBranch?.Upstream; + if (!string.IsNullOrEmpty(upstream)) + { + var upstreamHead = await new Commands.QueryRevisionByRefName(_repo.FullPath, upstream.Substring(13)).GetResultAsync(); + _repo.NavigateToCommit(upstreamHead, true); + } - CallUIThread(() => - { - if (!string.IsNullOrEmpty(upstreamHead)) - _repo.NavigateToCommit(upstreamHead, true); - - _repo.MarkFetched(); - _repo.SetWatcherEnabled(true); - }); - - return true; - }); + _repo.MarkFetched(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/FetchInto.cs b/src/ViewModels/FetchInto.cs index c681b637..3a0879b9 100644 --- a/src/ViewModels/FetchInto.cs +++ b/src/ViewModels/FetchInto.cs @@ -21,7 +21,7 @@ namespace SourceGit.ViewModels Upstream = upstream; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Fast-Forward ..."; @@ -29,20 +29,16 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Fetch Into '{Local.FriendlyName}'"); Use(log); - return Task.Run(() => - { - new Commands.Fetch(_repo.FullPath, Local, Upstream).Use(log).Exec(); - log.Complete(); + await new Commands.Fetch(_repo.FullPath, Local, Upstream) + .Use(log) + .RunAsync(); - var changedLocalBranchHead = new Commands.QueryRevisionByRefName(_repo.FullPath, Local.Name).Result(); - CallUIThread(() => - { - _repo.NavigateToCommit(changedLocalBranchHead, true); - _repo.SetWatcherEnabled(true); - }); + log.Complete(); - return true; - }); + var newHead = await new Commands.QueryRevisionByRefName(_repo.FullPath, Local.Name).GetResultAsync(); + _repo.NavigateToCommit(newHead, true); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs index 45468e6b..ad4ce461 100644 --- a/src/ViewModels/FileHistories.cs +++ b/src/ViewModels/FileHistories.cs @@ -45,26 +45,28 @@ namespace SourceGit.ViewModels RefreshViewContent(); } - public Task ResetToSelectedRevision() + public async Task ResetToSelectedRevisionAsync() { - return Task.Run(() => new Commands.Checkout(_repo.FullPath).FileWithRevision(_file, $"{_revision.SHA}")); + return await new Commands.Checkout(_repo.FullPath) + .FileWithRevisionAsync(_file, $"{_revision.SHA}") + .ConfigureAwait(false); } - public Task OpenWithDefaultEditor() + public async Task OpenWithDefaultEditorAsync() { if (_viewContent is not FileHistoriesRevisionFile { CanOpenWithDefaultEditor: true }) - return null; + return; - return Task.Run(() => - { - var fullPath = Native.OS.GetAbsPath(_repo.FullPath, _file); - var fileName = Path.GetFileNameWithoutExtension(fullPath) ?? ""; - var fileExt = Path.GetExtension(fullPath) ?? ""; - var tmpFile = Path.Combine(Path.GetTempPath(), $"{fileName}~{_revision.SHA.Substring(0, 10)}{fileExt}"); + var fullPath = Native.OS.GetAbsPath(_repo.FullPath, _file); + var fileName = Path.GetFileNameWithoutExtension(fullPath) ?? ""; + var fileExt = Path.GetExtension(fullPath) ?? ""; + var tmpFile = Path.Combine(Path.GetTempPath(), $"{fileName}~{_revision.SHA.Substring(0, 10)}{fileExt}"); - Commands.SaveRevisionFile.Run(_repo.FullPath, _revision.SHA, _file, tmpFile); - Native.OS.OpenWithDefaultEditor(tmpFile); - }); + await Commands.SaveRevisionFile + .RunAsync(_repo.FullPath, _revision.SHA, _file, tmpFile) + .ConfigureAwait(false); + + Native.OS.OpenWithDefaultEditor(tmpFile); } private void RefreshViewContent() @@ -77,7 +79,7 @@ namespace SourceGit.ViewModels private void SetViewContentAsRevisionFile() { - var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, _file).Result(); + var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, _file).GetResultAsync().Result; if (objs.Count == 0) { ViewContent = new FileHistoriesRevisionFile(_file); @@ -88,30 +90,30 @@ namespace SourceGit.ViewModels switch (obj.Type) { case Models.ObjectType.Blob: - Task.Run(() => + Task.Run(async () => { - var isBinary = new Commands.IsBinary(_repo.FullPath, _revision.SHA, _file).Result(); + var isBinary = await new Commands.IsBinary(_repo.FullPath, _revision.SHA, _file).GetResultAsync().ConfigureAwait(false); if (isBinary) { var imgDecoder = ImageSource.GetDecoder(_file); if (imgDecoder != Models.ImageDecoder.None) { - var source = ImageSource.FromRevision(_repo.FullPath, _revision.SHA, _file, imgDecoder); + var source = await ImageSource.FromRevisionAsync(_repo.FullPath, _revision.SHA, _file, imgDecoder).ConfigureAwait(false); var image = new Models.RevisionImageFile(_file, source.Bitmap, source.Size); - Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, image, true)); + Dispatcher.UIThread.Post(() => ViewContent = new FileHistoriesRevisionFile(_file, image, true)); } else { - var size = new Commands.QueryFileSize(_repo.FullPath, _file, _revision.SHA).Result(); + var size = await new Commands.QueryFileSize(_repo.FullPath, _file, _revision.SHA).GetResultAsync().ConfigureAwait(false); var binaryFile = new Models.RevisionBinaryFile() { Size = size }; - Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, binaryFile, true)); + Dispatcher.UIThread.Post(() => ViewContent = new FileHistoriesRevisionFile(_file, binaryFile, true)); } return; } - var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, _file); - var content = new StreamReader(contentStream).ReadToEnd(); + var contentStream = await Commands.QueryFileContent.RunAsync(_repo.FullPath, _revision.SHA, _file).ConfigureAwait(false); + var content = await new StreamReader(contentStream).ReadToEndAsync(); var lfs = Models.LFSObject.Parse(content); if (lfs != null) { @@ -119,34 +121,34 @@ namespace SourceGit.ViewModels if (imgDecoder != Models.ImageDecoder.None) { var combined = new RevisionLFSImage(_repo.FullPath, _file, lfs, imgDecoder); - Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, combined, true)); + Dispatcher.UIThread.Post(() => ViewContent = new FileHistoriesRevisionFile(_file, combined, true)); } else { var rlfs = new Models.RevisionLFSObject() { Object = lfs }; - Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, rlfs, true)); + Dispatcher.UIThread.Post(() => ViewContent = new FileHistoriesRevisionFile(_file, rlfs, true)); } } else { var txt = new Models.RevisionTextFile() { FileName = obj.Path, Content = content }; - Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, txt, true)); + Dispatcher.UIThread.Post(() => ViewContent = new FileHistoriesRevisionFile(_file, txt, true)); } }); break; case Models.ObjectType.Commit: - Task.Run(() => + Task.Run(async () => { var submoduleRoot = Path.Combine(_repo.FullPath, _file); - var commit = new Commands.QuerySingleCommit(submoduleRoot, obj.SHA).Result(); - var message = commit != null ? new Commands.QueryCommitFullMessage(submoduleRoot, obj.SHA).Result() : null; + var commit = await new Commands.QuerySingleCommit(submoduleRoot, obj.SHA).GetResultAsync().ConfigureAwait(false); + var message = commit != null ? await new Commands.QueryCommitFullMessage(submoduleRoot, obj.SHA).GetResultAsync().ConfigureAwait(false) : null; var module = new Models.RevisionSubmodule() { Commit = commit ?? new Models.Commit() { SHA = obj.SHA }, FullMessage = new Models.CommitFullMessage { Message = message } }; - Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module)); + Dispatcher.UIThread.Post(() => ViewContent = new FileHistoriesRevisionFile(_file, module)); }); break; default: @@ -203,28 +205,27 @@ namespace SourceGit.ViewModels RefreshViewContent(); } - public Task SaveAsPatch(string saveTo) + public async Task SaveAsPatch(string saveTo) { - return Task.Run(() => - { - Commands.SaveChangesAsPatch.ProcessRevisionCompareChanges(_repo.FullPath, _changes, _startPoint.SHA, _endPoint.SHA, saveTo); - return true; - }); + return await Commands.SaveChangesAsPatch + .ProcessRevisionCompareChangesAsync(_repo.FullPath, _changes, _startPoint.SHA, _endPoint.SHA, saveTo) + .ConfigureAwait(false); } private void RefreshViewContent() { - Task.Run(() => + Task.Run(async () => { - _changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).Result(); + _changes = await new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).ReadAsync().ConfigureAwait(false); if (_changes.Count == 0) { - Dispatcher.UIThread.Invoke(() => ViewContent = null); - return; + Dispatcher.UIThread.Post(() => ViewContent = null); + } + else + { + var option = new Models.DiffOption(_startPoint.SHA, _endPoint.SHA, _changes[0]); + Dispatcher.UIThread.Post(() => ViewContent = new DiffContext(_repo.FullPath, option, _viewContent)); } - - var option = new Models.DiffOption(_startPoint.SHA, _endPoint.SHA, _changes[0]); - Dispatcher.UIThread.Invoke(() => ViewContent = new DiffContext(_repo.FullPath, option, _viewContent)); }); } @@ -238,6 +239,11 @@ namespace SourceGit.ViewModels public class FileHistories : ObservableObject { + public string Title + { + get; + } + public bool IsLoading { get => _isLoading; @@ -264,13 +270,20 @@ namespace SourceGit.ViewModels public FileHistories(Repository repo, string file, string commit = null) { + if (!string.IsNullOrEmpty(commit)) + Title = $"{file} @ {commit}"; + else + Title = file; + _repo = repo; - Task.Run(() => + Task.Run(async () => { - var based = commit ?? string.Empty; - var commits = new Commands.QueryCommits(_repo.FullPath, $"--date-order -n 10000 {based} -- \"{file}\"", false).Result(); - Dispatcher.UIThread.Invoke(() => + var commits = await new Commands.QueryCommits(_repo.FullPath, $"--date-order -n 10000 {commit ?? string.Empty} -- \"{file}\"", false) + .GetResultAsync() + .ConfigureAwait(false); + + Dispatcher.UIThread.Post(() => { IsLoading = false; Commits = commits; @@ -304,7 +317,7 @@ namespace SourceGit.ViewModels if (_fullCommitMessages.TryGetValue(sha, out var msg)) return msg; - msg = new Commands.QueryCommitFullMessage(_repo.FullPath, sha).Result(); + msg = new Commands.QueryCommitFullMessage(_repo.FullPath, sha).GetResultAsync().Result; _fullCommitMessages[sha] = msg; return msg; } diff --git a/src/ViewModels/GitFlowFinish.cs b/src/ViewModels/GitFlowFinish.cs index bef4c2d9..959fa10e 100644 --- a/src/ViewModels/GitFlowFinish.cs +++ b/src/ViewModels/GitFlowFinish.cs @@ -40,7 +40,7 @@ namespace SourceGit.ViewModels Type = type; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Git Flow - Finish {Branch.Name} ..."; @@ -50,14 +50,11 @@ namespace SourceGit.ViewModels var prefix = _repo.GitFlow.GetPrefix(Type); var name = Branch.Name.StartsWith(prefix) ? Branch.Name.Substring(prefix.Length) : Branch.Name; + var succ = await Commands.GitFlow.FinishAsync(_repo.FullPath, Type, name, Squash, AutoPush, KeepBranch, log); - return Task.Run(() => - { - var succ = Commands.GitFlow.Finish(_repo.FullPath, Type, name, Squash, AutoPush, KeepBranch, log); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo; diff --git a/src/ViewModels/GitFlowStart.cs b/src/ViewModels/GitFlowStart.cs index 3ecba883..e6055f8e 100644 --- a/src/ViewModels/GitFlowStart.cs +++ b/src/ViewModels/GitFlowStart.cs @@ -49,7 +49,7 @@ namespace SourceGit.ViewModels return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Git Flow - Start {Prefix}{_name} ..."; @@ -57,13 +57,10 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("GitFlow - Start"); Use(log); - return Task.Run(() => - { - var succ = Commands.GitFlow.Start(_repo.FullPath, Type, _name, log); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await Commands.GitFlow.StartAsync(_repo.FullPath, Type, _name, log); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo; diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 75c5861d..6291bfe9 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; -using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Platform.Storage; @@ -140,7 +139,7 @@ namespace SourceGit.ViewModels if (commit == null) { AutoSelectedCommit = null; - commit = new Commands.QuerySingleCommit(_repo.FullPath, commitSHA).Result(); + commit = new Commands.QuerySingleCommit(_repo.FullPath, commitSHA).GetResultAsync().Result; } else { @@ -156,7 +155,7 @@ namespace SourceGit.ViewModels } else { - var commitDetail = new CommitDetail(_repo); + var commitDetail = new CommitDetail(_repo, true); commitDetail.Commit = commit; DetailContext = commitDetail; } @@ -189,7 +188,7 @@ namespace SourceGit.ViewModels } else { - var commitDetail = new CommitDetail(_repo); + var commitDetail = new CommitDetail(_repo, true); commitDetail.Commit = commit; DetailContext = commitDetail; } @@ -261,7 +260,7 @@ namespace SourceGit.ViewModels if (commit.IsCurrentHead) return; - var firstRemoteBranch = null as Models.Branch; + Models.Branch firstRemoteBranch = null; foreach (var d in commit.Decorators) { if (d.Type == Models.DecoratorType.LocalBranchHead) @@ -300,7 +299,7 @@ namespace SourceGit.ViewModels } } - public ContextMenu MakeContextMenu(ListBox list) + public ContextMenu MakeContextMenu(DataGrid list) { var current = _repo.CurrentBranch; if (current == null || list.SelectedItems == null) @@ -379,7 +378,7 @@ namespace SourceGit.ViewModels return; var options = new FolderPickerOpenOptions() { AllowMultiple = false }; - var log = null as CommandLog; + CommandLog log = null; try { var picker = await storageProvider.OpenFolderPickerAsync(options); @@ -393,7 +392,7 @@ namespace SourceGit.ViewModels for (var i = 0; i < selected.Count; i++) { var saveTo = GetPatchFileName(folderPath, selected[i], i); - succ = await Task.Run(() => new Commands.FormatPatch(_repo.FullPath, selected[i].SHA, saveTo).Use(log).Exec()); + succ = await new Commands.FormatPatch(_repo.FullPath, selected[i].SHA, saveTo).Use(log).ExecAsync(); if (!succ) break; } @@ -416,26 +415,26 @@ namespace SourceGit.ViewModels var copyMultipleSHAs = new MenuItem(); copyMultipleSHAs.Header = App.Text("CommitCM.CopySHA"); copyMultipleSHAs.Icon = App.CreateMenuIcon("Icons.Fingerprint"); - copyMultipleSHAs.Click += (_, e) => + copyMultipleSHAs.Click += async (_, e) => { var builder = new StringBuilder(); foreach (var c in selected) builder.AppendLine(c.SHA); - App.CopyText(builder.ToString()); + await App.CopyTextAsync(builder.ToString()); e.Handled = true; }; var copyMultipleInfo = new MenuItem(); - copyMultipleInfo.Header = App.Text("CommitCM.CopyInfo"); + copyMultipleInfo.Header = App.Text("CommitCM.CopySHA") + " - " + App.Text("CommitCM.CopySubject"); copyMultipleInfo.Icon = App.CreateMenuIcon("Icons.Info"); - copyMultipleInfo.Click += (_, e) => + copyMultipleInfo.Click += async (_, e) => { var builder = new StringBuilder(); foreach (var c in selected) builder.AppendLine($"{c.SHA.AsSpan(0, 10)} - {c.Subject}"); - App.CopyText(builder.ToString()); + await App.CopyTextAsync(builder.ToString()); e.Handled = true; }; @@ -457,25 +456,24 @@ namespace SourceGit.ViewModels { foreach (var d in commit.Decorators) { - if (d.Type == Models.DecoratorType.CurrentBranchHead) + switch (d.Type) { - FillCurrentBranchMenu(menu, current); - } - else if (d.Type == Models.DecoratorType.LocalBranchHead) - { - var b = _repo.Branches.Find(x => x.IsLocal && d.Name == x.Name); - FillOtherLocalBranchMenu(menu, b, current, commit.IsMerged); - } - else if (d.Type == Models.DecoratorType.RemoteBranchHead) - { - var b = _repo.Branches.Find(x => !x.IsLocal && d.Name == x.FriendlyName); - FillRemoteBranchMenu(menu, b, current, commit.IsMerged); - } - else if (d.Type == Models.DecoratorType.Tag) - { - var t = _repo.Tags.Find(x => x.Name == d.Name); - if (t != null) - tags.Add(t); + case Models.DecoratorType.CurrentBranchHead: + FillCurrentBranchMenu(menu, current); + break; + case Models.DecoratorType.LocalBranchHead: + var lb = _repo.Branches.Find(x => x.IsLocal && d.Name == x.Name); + FillOtherLocalBranchMenu(menu, lb, current, commit.IsMerged); + break; + case Models.DecoratorType.RemoteBranchHead: + var rb = _repo.Branches.Find(x => !x.IsLocal && d.Name == x.FriendlyName); + FillRemoteBranchMenu(menu, rb, current, commit.IsMerged); + break; + case Models.DecoratorType.Tag: + var t = _repo.Tags.Find(x => x.Name == d.Name); + if (t != null) + tags.Add(t); + break; } } @@ -588,7 +586,7 @@ namespace SourceGit.ViewModels var cherryPick = new MenuItem(); cherryPick.Header = App.Text("CommitCM.CherryPick"); cherryPick.Icon = App.CreateMenuIcon("Icons.CherryPick"); - cherryPick.Click += (_, e) => + cherryPick.Click += async (_, e) => { if (_repo.CanCreatePopup()) { @@ -601,7 +599,10 @@ namespace SourceGit.ViewModels var parents = new List(); foreach (var sha in commit.Parents) { - var parent = _commits.Find(x => x.SHA == sha) ?? new Commands.QuerySingleCommit(_repo.FullPath, sha).Result(); + var parent = _commits.Find(x => x.SHA == sha); + if (parent == null) + parent = await new Commands.QuerySingleCommit(_repo.FullPath, sha) + .GetResultAsync(); if (parent != null) parents.Add(parent); @@ -644,7 +645,7 @@ namespace SourceGit.ViewModels var interactiveRebase = new MenuItem(); interactiveRebase.Header = App.Text("CommitCM.InteractiveRebase", current.Name); interactiveRebase.Icon = App.CreateMenuIcon("Icons.InteractiveRebase"); - interactiveRebase.Click += (_, e) => + interactiveRebase.Click += async (_, e) => { if (_repo.LocalChangesCount > 0) { @@ -652,7 +653,7 @@ namespace SourceGit.ViewModels return; } - App.ShowWindow(new InteractiveRebase(_repo, current, commit), true); + await App.ShowDialog(new InteractiveRebase(_repo, current, commit)); e.Handled = true; }; @@ -685,13 +686,13 @@ namespace SourceGit.ViewModels var compareWithHead = new MenuItem(); compareWithHead.Header = App.Text("CommitCM.CompareWithHead"); compareWithHead.Icon = App.CreateMenuIcon("Icons.Compare"); - compareWithHead.Click += (_, e) => + compareWithHead.Click += async (_, e) => { var head = _commits.Find(x => x.SHA == current.Head); if (head == null) { _repo.SelectedSearchedCommit = null; - head = new Commands.QuerySingleCommit(_repo.FullPath, current.Head).Result(); + head = await new Commands.QuerySingleCommit(_repo.FullPath, current.Head).GetResultAsync(); if (head != null) DetailContext = new RevisionCompare(_repo.FullPath, commit, head); } @@ -753,7 +754,7 @@ namespace SourceGit.ViewModels return; var options = new FolderPickerOpenOptions() { AllowMultiple = false }; - var log = null as CommandLog; + CommandLog log = null; try { var selected = await storageProvider.OpenFolderPickerAsync(options); @@ -764,7 +765,7 @@ namespace SourceGit.ViewModels var folder = selected[0]; var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder.Path.ToString(); var saveTo = GetPatchFileName(folderPath, commit); - var succ = await Task.Run(() => new Commands.FormatPatch(_repo.FullPath, commit.SHA, saveTo).Use(log).Exec()); + var succ = await new Commands.FormatPatch(_repo.FullPath, commit.SHA, saveTo).Use(log).ExecAsync(); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -800,10 +801,10 @@ namespace SourceGit.ViewModels foreach (var action in actions) { - var dup = action; + var (dup, label) = action; var item = new MenuItem(); item.Icon = App.CreateMenuIcon("Icons.Action"); - item.Header = dup.Name; + item.Header = label; item.Click += (_, e) => { _repo.ExecCustomAction(dup, commit); @@ -820,45 +821,55 @@ namespace SourceGit.ViewModels var copySHA = new MenuItem(); copySHA.Header = App.Text("CommitCM.CopySHA"); copySHA.Icon = App.CreateMenuIcon("Icons.Fingerprint"); - copySHA.Click += (_, e) => + copySHA.Click += async (_, e) => { - App.CopyText(commit.SHA); + await App.CopyTextAsync(commit.SHA); e.Handled = true; }; var copySubject = new MenuItem(); copySubject.Header = App.Text("CommitCM.CopySubject"); copySubject.Icon = App.CreateMenuIcon("Icons.Subject"); - copySubject.Click += (_, e) => + copySubject.Click += async (_, e) => { - App.CopyText(commit.Subject); + await App.CopyTextAsync(commit.Subject); e.Handled = true; }; var copyInfo = new MenuItem(); - copyInfo.Header = App.Text("CommitCM.CopyInfo"); + copyInfo.Header = App.Text("CommitCM.CopySHA") + " - " + App.Text("CommitCM.CopySubject"); copyInfo.Icon = App.CreateMenuIcon("Icons.Info"); - copyInfo.Click += (_, e) => + copyInfo.Click += async (_, e) => { - App.CopyText($"{commit.SHA.AsSpan(0, 10)} - {commit.Subject}"); + await App.CopyTextAsync($"{commit.SHA.AsSpan(0, 10)} - {commit.Subject}"); + e.Handled = true; + }; + + var copyMessage = new MenuItem(); + copyMessage.Header = App.Text("CommitCM.CopyCommitMessage"); + copyMessage.Icon = App.CreateMenuIcon("Icons.Info"); + copyMessage.Click += async (_, e) => + { + var message = await new Commands.QueryCommitFullMessage(_repo.FullPath, commit.SHA).GetResultAsync(); + await App.CopyTextAsync(message); e.Handled = true; }; var copyAuthor = new MenuItem(); copyAuthor.Header = App.Text("CommitCM.CopyAuthor"); copyAuthor.Icon = App.CreateMenuIcon("Icons.User"); - copyAuthor.Click += (_, e) => + copyAuthor.Click += async (_, e) => { - App.CopyText(commit.Author.ToString()); + await App.CopyTextAsync(commit.Author.ToString()); e.Handled = true; }; var copyCommitter = new MenuItem(); copyCommitter.Header = App.Text("CommitCM.CopyCommitter"); copyCommitter.Icon = App.CreateMenuIcon("Icons.User"); - copyCommitter.Click += (_, e) => + copyCommitter.Click += async (_, e) => { - App.CopyText(commit.Committer.ToString()); + await App.CopyTextAsync(commit.Committer.ToString()); e.Handled = true; }; @@ -868,6 +879,7 @@ namespace SourceGit.ViewModels copy.Items.Add(copySHA); copy.Items.Add(copySubject); copy.Items.Add(copyInfo); + copy.Items.Add(copyMessage); copy.Items.Add(copyAuthor); copy.Items.Add(copyCommitter); menu.Items.Add(copy); @@ -966,9 +978,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("BranchCM.CopyName"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(current.Name); + await App.CopyTextAsync(current.Name); e.Handled = true; }; submenu.Items.Add(copy); @@ -1058,9 +1070,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("BranchCM.CopyName"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(branch.Name); + await App.CopyTextAsync(branch.Name); e.Handled = true; }; submenu.Items.Add(copy); @@ -1120,9 +1132,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("BranchCM.CopyName"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(name); + await App.CopyTextAsync(name); e.Handled = true; }; submenu.Items.Add(copy); @@ -1184,9 +1196,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("TagCM.Copy"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(tag.Name); + await App.CopyTextAsync(tag.Name); e.Handled = true; }; submenu.Items.Add(copy); diff --git a/src/ViewModels/ImageSource.cs b/src/ViewModels/ImageSource.cs index 039403f4..3095e4c6 100644 --- a/src/ViewModels/ImageSource.cs +++ b/src/ViewModels/ImageSource.cs @@ -2,11 +2,10 @@ using System.Globalization; using System.IO; using System.Runtime.InteropServices; - +using System.Threading.Tasks; using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; - using BitMiracle.LibTiff.Classic; using Pfim; @@ -36,25 +35,25 @@ namespace SourceGit.ViewModels }; } - public static ImageSource FromFile(string fullpath, Models.ImageDecoder decoder) + public static async Task FromFileAsync(string fullpath, Models.ImageDecoder decoder) { - using (var stream = File.OpenRead(fullpath)) - return LoadFromStream(stream, decoder); + await using var stream = File.OpenRead(fullpath); + return await Task.Run(() => LoadFromStream(stream, decoder)).ConfigureAwait(false); } - public static ImageSource FromRevision(string repo, string revision, string file, Models.ImageDecoder decoder) + public static async Task FromRevisionAsync(string repo, string revision, string file, Models.ImageDecoder decoder) { - var stream = Commands.QueryFileContent.Run(repo, revision, file); - return LoadFromStream(stream, decoder); + await using var stream = await Commands.QueryFileContent.RunAsync(repo, revision, file).ConfigureAwait(false); + return await Task.Run(() => LoadFromStream(stream, decoder)).ConfigureAwait(false); } - public static ImageSource FromLFSObject(string repo, Models.LFSObject lfs, Models.ImageDecoder decoder) + public static async Task FromLFSObjectAsync(string repo, Models.LFSObject lfs, Models.ImageDecoder decoder) { if (string.IsNullOrEmpty(lfs.Oid) || lfs.Size == 0) return new ImageSource(null, 0); - var stream = Commands.QueryFileContent.FromLFS(repo, lfs.Oid, lfs.Size); - return LoadFromStream(stream, decoder); + var stream = await Commands.QueryFileContent.FromLFSAsync(repo, lfs.Oid, lfs.Size).ConfigureAwait(false); + return await Task.Run(() => LoadFromStream(stream, decoder)).ConfigureAwait(false); } private static ImageSource LoadFromStream(Stream stream, Models.ImageDecoder decoder) diff --git a/src/ViewModels/InProgressContexts.cs b/src/ViewModels/InProgressContexts.cs index d26e50c9..a1e7448c 100644 --- a/src/ViewModels/InProgressContexts.cs +++ b/src/ViewModels/InProgressContexts.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; namespace SourceGit.ViewModels { @@ -10,27 +11,27 @@ namespace SourceGit.ViewModels _cmd = cmd; } - public bool Abort() + public Task AbortAsync() { return new Commands.Command() { WorkingDirectory = _repo, Context = _repo, Args = $"{_cmd} --abort", - }.Exec(); + }.ExecAsync(); } - public virtual bool Skip() + public virtual Task SkipAsync() { return new Commands.Command() { WorkingDirectory = _repo, Context = _repo, Args = $"{_cmd} --skip", - }.Exec(); + }.ExecAsync(); } - public virtual bool Continue() + public virtual Task ContinueAsync() { return new Commands.Command() { @@ -38,7 +39,7 @@ namespace SourceGit.ViewModels Context = _repo, Editor = Commands.Command.EditorType.None, Args = $"{_cmd} --continue", - }.Exec(); + }.ExecAsync(); } protected string GetFriendlyNameOfCommit(Models.Commit commit) @@ -74,7 +75,7 @@ namespace SourceGit.ViewModels public CherryPickInProgress(Repository repo) : base(repo.FullPath, "cherry-pick") { var headSHA = File.ReadAllText(Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD")).Trim(); - Head = new Commands.QuerySingleCommit(repo.FullPath, headSHA).Result() ?? new Models.Commit() { SHA = headSHA }; + Head = new Commands.QuerySingleCommit(repo.FullPath, headSHA).GetResultAsync().Result ?? new Models.Commit() { SHA = headSHA }; } } @@ -114,16 +115,16 @@ namespace SourceGit.ViewModels var stoppedSHAPath = Path.Combine(repo.GitDir, "rebase-merge", "stopped-sha"); var stoppedSHA = File.Exists(stoppedSHAPath) ? File.ReadAllText(stoppedSHAPath).Trim() - : new Commands.QueryRevisionByRefName(repo.FullPath, HeadName).Result(); + : new Commands.QueryRevisionByRefName(repo.FullPath, HeadName).GetResultAsync().Result; if (!string.IsNullOrEmpty(stoppedSHA)) - StoppedAt = new Commands.QuerySingleCommit(repo.FullPath, stoppedSHA).Result() ?? new Models.Commit() { SHA = stoppedSHA }; + StoppedAt = new Commands.QuerySingleCommit(repo.FullPath, stoppedSHA).GetResultAsync().Result ?? new Models.Commit() { SHA = stoppedSHA }; var ontoSHA = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "onto")).Trim(); - Onto = new Commands.QuerySingleCommit(repo.FullPath, ontoSHA).Result() ?? new Models.Commit() { SHA = ontoSHA }; + Onto = new Commands.QuerySingleCommit(repo.FullPath, ontoSHA).GetResultAsync().Result ?? new Models.Commit() { SHA = ontoSHA }; } - public override bool Continue() + public override Task ContinueAsync() { return new Commands.Command() { @@ -131,7 +132,7 @@ namespace SourceGit.ViewModels Context = _repo, Editor = Commands.Command.EditorType.RebaseEditor, Args = "rebase --continue", - }.Exec(); + }.ExecAsync(); } } @@ -146,7 +147,7 @@ namespace SourceGit.ViewModels public RevertInProgress(Repository repo) : base(repo.FullPath, "revert") { var headSHA = File.ReadAllText(Path.Combine(repo.GitDir, "REVERT_HEAD")).Trim(); - Head = new Commands.QuerySingleCommit(repo.FullPath, headSHA).Result() ?? new Models.Commit() { SHA = headSHA }; + Head = new Commands.QuerySingleCommit(repo.FullPath, headSHA).GetResultAsync().Result ?? new Models.Commit() { SHA = headSHA }; } } @@ -171,15 +172,15 @@ namespace SourceGit.ViewModels public MergeInProgress(Repository repo) : base(repo.FullPath, "merge") { - Current = Commands.Branch.ShowCurrent(repo.FullPath); + Current = new Commands.QueryCurrentBranch(repo.FullPath).GetResultAsync().Result; var sourceSHA = File.ReadAllText(Path.Combine(repo.GitDir, "MERGE_HEAD")).Trim(); - Source = new Commands.QuerySingleCommit(repo.FullPath, sourceSHA).Result() ?? new Models.Commit() { SHA = sourceSHA }; + Source = new Commands.QuerySingleCommit(repo.FullPath, sourceSHA).GetResultAsync().Result ?? new Models.Commit() { SHA = sourceSHA }; } - public override bool Skip() + public override Task SkipAsync() { - return true; + return Task.FromResult(true); } } } diff --git a/src/ViewModels/Init.cs b/src/ViewModels/Init.cs index b47ba663..7f349917 100644 --- a/src/ViewModels/Init.cs +++ b/src/ViewModels/Init.cs @@ -24,29 +24,25 @@ namespace SourceGit.ViewModels Reason = string.IsNullOrEmpty(reason) ? "Invalid repository detected!" : reason; } - public override Task Sure() + public override async Task Sure() { ProgressDescription = $"Initialize git repository at: '{_targetPath}'"; var log = new CommandLog("Initialize"); Use(log); - return Task.Run(() => + var succ = await new Commands.Init(_pageId, _targetPath) + .Use(log) + .ExecAsync(); + + log.Complete(); + + if (succ) { - var succ = new Commands.Init(_pageId, _targetPath).Use(log).Exec(); - log.Complete(); - - if (succ) - { - CallUIThread(() => - { - Preferences.Instance.FindOrAddNodeByRepositoryPath(_targetPath, _parentNode, true); - Welcome.Instance.Refresh(); - }); - } - - return succ; - }); + Preferences.Instance.FindOrAddNodeByRepositoryPath(_targetPath, _parentNode, true); + Welcome.Instance.Refresh(); + } + return succ; } private readonly string _pageId = null; diff --git a/src/ViewModels/InitGitFlow.cs b/src/ViewModels/InitGitFlow.cs index c5aeceec..e315fcf1 100644 --- a/src/ViewModels/InitGitFlow.cs +++ b/src/ViewModels/InitGitFlow.cs @@ -100,7 +100,7 @@ namespace SourceGit.ViewModels return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Init git-flow ..."; @@ -108,65 +108,58 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Gitflow - Init"); Use(log); - return Task.Run(() => + bool succ; + var current = _repo.CurrentBranch; + + var masterBranch = _repo.Branches.Find(x => x.IsLocal && x.Name.Equals(_master, StringComparison.Ordinal)); + if (masterBranch == null) { - bool succ; - var current = _repo.CurrentBranch; - - var masterBranch = _repo.Branches.Find(x => x.IsLocal && x.Name.Equals(_master, StringComparison.Ordinal)); - if (masterBranch == null) + succ = await Commands.Branch.CreateAsync(_repo.FullPath, _master, current.Head, true, log); + if (!succ) { - succ = Commands.Branch.Create(_repo.FullPath, _master, current.Head, true, log); - if (!succ) - { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; - } - } - - var developBranch = _repo.Branches.Find(x => x.IsLocal && x.Name.Equals(_develop, StringComparison.Ordinal)); - if (developBranch == null) - { - succ = Commands.Branch.Create(_repo.FullPath, _develop, current.Head, true, log); - if (!succ) - { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; - } - } - - succ = Commands.GitFlow.Init( - _repo.FullPath, - _master, - _develop, - _featurePrefix, - _releasePrefix, - _hotfixPrefix, - _tagPrefix, - log); - - log.Complete(); - - CallUIThread(() => - { - if (succ) - { - var gitflow = new Models.GitFlow(); - gitflow.Master = _master; - gitflow.Develop = _develop; - gitflow.FeaturePrefix = _featurePrefix; - gitflow.ReleasePrefix = _releasePrefix; - gitflow.HotfixPrefix = _hotfixPrefix; - _repo.GitFlow = gitflow; - } - + log.Complete(); _repo.SetWatcherEnabled(true); - }); + return false; + } + } - return succ; - }); + var developBranch = _repo.Branches.Find(x => x.IsLocal && x.Name.Equals(_develop, StringComparison.Ordinal)); + if (developBranch == null) + { + succ = await Commands.Branch.CreateAsync(_repo.FullPath, _develop, current.Head, true, log); + if (!succ) + { + log.Complete(); + _repo.SetWatcherEnabled(true); + return false; + } + } + + succ = await Commands.GitFlow.InitAsync( + _repo.FullPath, + _master, + _develop, + _featurePrefix, + _releasePrefix, + _hotfixPrefix, + _tagPrefix, + log); + + log.Complete(); + + if (succ) + { + var gitflow = new Models.GitFlow(); + gitflow.Master = _master; + gitflow.Develop = _develop; + gitflow.FeaturePrefix = _featurePrefix; + gitflow.ReleasePrefix = _releasePrefix; + gitflow.HotfixPrefix = _hotfixPrefix; + _repo.GitFlow = gitflow; + } + + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo; diff --git a/src/ViewModels/InteractiveRebase.cs b/src/ViewModels/InteractiveRebase.cs index e22d0d73..230d217f 100644 --- a/src/ViewModels/InteractiveRebase.cs +++ b/src/ViewModels/InteractiveRebase.cs @@ -129,11 +129,11 @@ namespace SourceGit.ViewModels Current = current; On = on; IsLoading = true; - DetailContext = new CommitDetail(repo); + DetailContext = new CommitDetail(repo, false); - Task.Run(() => + Task.Run(async () => { - var commits = new Commands.QueryCommitsForInteractiveRebase(repoPath, on.SHA).Result(); + var commits = await new Commands.QueryCommitsForInteractiveRebase(repoPath, on.SHA).GetResultAsync().ConfigureAwait(false); var list = new List(); for (var i = 0; i < commits.Count; i++) @@ -142,7 +142,7 @@ namespace SourceGit.ViewModels list.Add(new InteractiveRebaseItem(c.Commit, c.Message, i < commits.Count - 1)); } - Dispatcher.UIThread.Invoke(() => + Dispatcher.UIThread.Post(() => { Items.AddRange(list); IsLoading = false; @@ -188,7 +188,7 @@ namespace SourceGit.ViewModels UpdateItems(); } - public Task Start() + public async Task Start() { _repo.SetWatcherEnabled(false); @@ -206,16 +206,19 @@ namespace SourceGit.ViewModels Message = item.FullMessage, }); } - File.WriteAllText(saveFile, JsonSerializer.Serialize(collection, JsonCodeGen.Default.InteractiveRebaseJobCollection)); + await using (var stream = File.Create(saveFile)) + { + await JsonSerializer.SerializeAsync(stream, collection, JsonCodeGen.Default.InteractiveRebaseJobCollection); + } var log = _repo.CreateLog("Interactive Rebase"); - return Task.Run(() => - { - var succ = new Commands.InteractiveRebase(_repo.FullPath, On.SHA).Use(log).Exec(); - log.Complete(); - Dispatcher.UIThread.Invoke(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.InteractiveRebase(_repo.FullPath, On.SHA) + .Use(log) + .ExecAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private void UpdateItems() diff --git a/src/ViewModels/LFSFetch.cs b/src/ViewModels/LFSFetch.cs index 99d9babb..c73f2aa8 100644 --- a/src/ViewModels/LFSFetch.cs +++ b/src/ViewModels/LFSFetch.cs @@ -19,7 +19,7 @@ namespace SourceGit.ViewModels SelectedRemote = _repo.Remotes[0]; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Fetching LFS objects from remote ..."; @@ -27,13 +27,11 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("LFS Fetch"); Use(log); - return Task.Run(() => - { - new Commands.LFS(_repo.FullPath).Fetch(SelectedRemote.Name, log); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.LFS(_repo.FullPath).FetchAsync(SelectedRemote.Name, log); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/LFSImageDiff.cs b/src/ViewModels/LFSImageDiff.cs index bb69fd65..c554f178 100644 --- a/src/ViewModels/LFSImageDiff.cs +++ b/src/ViewModels/LFSImageDiff.cs @@ -21,10 +21,10 @@ namespace SourceGit.ViewModels { LFS = lfs; - Task.Run(() => + Task.Run(async () => { - var oldImage = ImageSource.FromLFSObject(repo, lfs.Old, decoder); - var newImage = ImageSource.FromLFSObject(repo, lfs.New, decoder); + var oldImage = await ImageSource.FromLFSObjectAsync(repo, lfs.Old, decoder).ConfigureAwait(false); + var newImage = await ImageSource.FromLFSObjectAsync(repo, lfs.New, decoder).ConfigureAwait(false); var img = new Models.ImageDiff() { @@ -34,7 +34,7 @@ namespace SourceGit.ViewModels NewFileSize = newImage.Size }; - Dispatcher.UIThread.Invoke(() => Image = img); + Dispatcher.UIThread.Post(() => Image = img); }); } diff --git a/src/ViewModels/LFSLocks.cs b/src/ViewModels/LFSLocks.cs index 0ee149d0..800ad820 100644 --- a/src/ViewModels/LFSLocks.cs +++ b/src/ViewModels/LFSLocks.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; - using Avalonia.Threading; - using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -41,22 +40,22 @@ namespace SourceGit.ViewModels { _repo = repo; _remote = remote; - _userName = new Commands.Config(repo.FullPath).Get("user.name"); - HasValidUserName = !string.IsNullOrEmpty(_userName); - - Task.Run(() => + Task.Run(async () => { - _cachedLocks = new Commands.LFS(_repo.FullPath).Locks(_remote); - Dispatcher.UIThread.Invoke(() => + _userName = await new Commands.Config(repo.FullPath).GetAsync("user.name").ConfigureAwait(false); + _cachedLocks = await new Commands.LFS(_repo.FullPath).GetLocksAsync(_remote).ConfigureAwait(false); + + Dispatcher.UIThread.Post(() => { UpdateVisibleLocks(); IsLoading = false; + HasValidUserName = !string.IsNullOrEmpty(_userName); }); }); } - public void Unlock(Models.LFSLock lfsLock, bool force) + public async Task UnlockAsync(Models.LFSLock lfsLock, bool force) { if (_isLoading) return; @@ -64,22 +63,16 @@ namespace SourceGit.ViewModels IsLoading = true; var log = _repo.CreateLog("Unlock LFS File"); - Task.Run(() => + var succ = await new Commands.LFS(_repo.FullPath).UnlockAsync(_remote, lfsLock.ID, force, log); + log.Complete(); + + if (succ) { - var succ = new Commands.LFS(_repo.FullPath).Unlock(_remote, lfsLock.ID, force, log); - log.Complete(); + _cachedLocks.Remove(lfsLock); + UpdateVisibleLocks(); + } - Dispatcher.UIThread.Invoke(() => - { - if (succ) - { - _cachedLocks.Remove(lfsLock); - UpdateVisibleLocks(); - } - - IsLoading = false; - }); - }); + IsLoading = false; } private void UpdateVisibleLocks() @@ -88,14 +81,13 @@ namespace SourceGit.ViewModels if (!_showOnlyMyLocks) { - foreach (var lfsLock in _cachedLocks) - visible.Add(lfsLock); + visible.AddRange(_cachedLocks); } else { foreach (var lfsLock in _cachedLocks) { - if (lfsLock.User == _userName) + if (lfsLock.User.Equals(_userName, StringComparison.Ordinal)) visible.Add(lfsLock); } } diff --git a/src/ViewModels/LFSPrune.cs b/src/ViewModels/LFSPrune.cs index 9a6bd8a7..c5337b6c 100644 --- a/src/ViewModels/LFSPrune.cs +++ b/src/ViewModels/LFSPrune.cs @@ -9,7 +9,7 @@ namespace SourceGit.ViewModels _repo = repo; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "LFS prune ..."; @@ -17,13 +17,11 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("LFS Prune"); Use(log); - return Task.Run(() => - { - new Commands.LFS(_repo.FullPath).Prune(log); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.LFS(_repo.FullPath).PruneAsync(log); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/LFSPull.cs b/src/ViewModels/LFSPull.cs index 1943c02e..8c034b8f 100644 --- a/src/ViewModels/LFSPull.cs +++ b/src/ViewModels/LFSPull.cs @@ -19,7 +19,7 @@ namespace SourceGit.ViewModels SelectedRemote = _repo.Remotes[0]; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Pull LFS objects from remote ..."; @@ -27,13 +27,11 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("LFS Pull"); Use(log); - return Task.Run(() => - { - new Commands.LFS(_repo.FullPath).Pull(SelectedRemote.Name, log); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.LFS(_repo.FullPath).PullAsync(SelectedRemote.Name, log); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/LFSPush.cs b/src/ViewModels/LFSPush.cs index ea111eff..cd69b508 100644 --- a/src/ViewModels/LFSPush.cs +++ b/src/ViewModels/LFSPush.cs @@ -19,7 +19,7 @@ namespace SourceGit.ViewModels SelectedRemote = _repo.Remotes[0]; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Push LFS objects to remote ..."; @@ -27,13 +27,11 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("LFS Push"); Use(log); - return Task.Run(() => - { - new Commands.LFS(_repo.FullPath).Push(SelectedRemote.Name, log); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.LFS(_repo.FullPath).PushAsync(SelectedRemote.Name, log); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/LFSTrackCustomPattern.cs b/src/ViewModels/LFSTrackCustomPattern.cs index b69733e8..f9fa4754 100644 --- a/src/ViewModels/LFSTrackCustomPattern.cs +++ b/src/ViewModels/LFSTrackCustomPattern.cs @@ -23,7 +23,7 @@ namespace SourceGit.ViewModels _repo = repo; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Adding custom LFS tracking pattern ..."; @@ -31,13 +31,11 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("LFS Add Custom Pattern"); Use(log); - return Task.Run(() => - { - var succ = new Commands.LFS(_repo.FullPath).Track(_pattern, IsFilename, log); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.LFS(_repo.FullPath).TrackAsync(_pattern, IsFilename, log); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index ede6d84c..98c4ae8a 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -4,6 +4,7 @@ using System.IO; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -95,7 +96,7 @@ namespace SourceGit.ViewModels foreach (var w in pref.Workspaces) w.IsActive = false; - var test = new Commands.QueryRepositoryRootPath(startupRepo).ReadToEnd(); + var test = new Commands.QueryRepositoryRootPath(startupRepo).GetResultAsync().Result; if (!test.IsSuccess || string.IsNullOrEmpty(test.StdOut)) { Pages[0].Notifications.Add(new Models.Notification @@ -345,7 +346,7 @@ namespace SourceGit.ViewModels return; } - var isBare = new Commands.IsBareRepository(node.Id).Result(); + var isBare = new Commands.IsBareRepository(node.Id).GetResultAsync().Result; var gitDir = isBare ? node.Id : GetRepositoryGitDir(node.Id); if (string.IsNullOrEmpty(gitDir)) { @@ -394,6 +395,12 @@ namespace SourceGit.ViewModels public void DispatchNotification(string pageId, string message, bool isError) { + if (!Dispatcher.UIThread.CheckAccess()) + { + Dispatcher.UIThread.Invoke(() => DispatchNotification(pageId, message, isError)); + return; + } + var notification = new Models.Notification() { IsError = isError, @@ -410,8 +417,7 @@ namespace SourceGit.ViewModels } } - if (_activePage != null) - _activePage.Notifications.Add(notification); + _activePage?.Notifications.Add(notification); } public ContextMenu CreateContextForWorkspace() @@ -444,9 +450,9 @@ namespace SourceGit.ViewModels var configure = new MenuItem(); configure.Header = App.Text("Workspace.Configure"); - configure.Click += (_, e) => + configure.Click += async (_, e) => { - App.ShowWindow(new ConfigureWorkspace(), true); + await App.ShowDialog(new ConfigureWorkspace()); e.Handled = true; }; menu.Items.Add(configure); @@ -517,9 +523,9 @@ namespace SourceGit.ViewModels var copyPath = new MenuItem(); copyPath.Header = App.Text("PageTabBar.Tab.CopyPath"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, e) => + copyPath.Click += async (_, e) => { - page.CopyPath(); + await page.CopyPathAsync(); e.Handled = true; }; menu.Items.Add(new MenuItem() { Header = "-" }); @@ -557,7 +563,7 @@ namespace SourceGit.ViewModels return null; } - return new Commands.QueryGitDir(repo).Result(); + return new Commands.QueryGitDir(repo).GetResultAsync().Result; } private void CloseRepositoryInTab(LauncherPage page, bool removeFromWorkspace = true) diff --git a/src/ViewModels/LauncherPage.cs b/src/ViewModels/LauncherPage.cs index 191a8163..0b4b3d8a 100644 --- a/src/ViewModels/LauncherPage.cs +++ b/src/ViewModels/LauncherPage.cs @@ -1,8 +1,7 @@ using System; - +using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Media; - using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -59,10 +58,10 @@ namespace SourceGit.ViewModels Notifications.Clear(); } - public void CopyPath() + public async Task CopyPathAsync() { if (_node.IsRepository) - App.CopyText(_node.Id); + await App.CopyTextAsync(_node.Id); } public void ChangeDirtyState(Models.DirtyState flag, bool remove) @@ -106,28 +105,19 @@ namespace SourceGit.ViewModels return; dump.InProgress = true; - var task = dump.Sure(); - var finished = false; - if (task != null) - { - try - { - finished = await task; - } - catch (Exception e) - { - App.LogException(e); - } - dump.InProgress = false; + try + { + var finished = await dump.Sure(); if (finished) Popup = null; } - else + catch (Exception e) { - dump.InProgress = false; - Popup = null; + App.LogException(e); } + + dump.InProgress = false; } } diff --git a/src/ViewModels/LayoutInfo.cs b/src/ViewModels/LayoutInfo.cs index 37d6e784..26ae128d 100644 --- a/src/ViewModels/LayoutInfo.cs +++ b/src/ViewModels/LayoutInfo.cs @@ -41,12 +41,6 @@ namespace SourceGit.ViewModels set => SetProperty(ref _repositorySidebarWidth, value); } - public GridLength HistoriesAuthorColumnWidth - { - get => _historiesAuthorColumnWidth; - set => SetProperty(ref _historiesAuthorColumnWidth, value); - } - public GridLength WorkingCopyLeftWidth { get => _workingCopyLeftWidth; @@ -71,11 +65,17 @@ namespace SourceGit.ViewModels set => SetProperty(ref _commitDetailFilesLeftWidth, value); } + public DataGridLength AuthorColumnWidth + { + get => _authorColumnWidth; + set => SetProperty(ref _authorColumnWidth, value); + } + private GridLength _repositorySidebarWidth = new GridLength(250, GridUnitType.Pixel); - private GridLength _historiesAuthorColumnWidth = new GridLength(120, GridUnitType.Pixel); private GridLength _workingCopyLeftWidth = new GridLength(300, GridUnitType.Pixel); private GridLength _stashesLeftWidth = new GridLength(300, GridUnitType.Pixel); private GridLength _commitDetailChangesLeftWidth = new GridLength(256, GridUnitType.Pixel); private GridLength _commitDetailFilesLeftWidth = new GridLength(256, GridUnitType.Pixel); + private DataGridLength _authorColumnWidth = new DataGridLength(120, DataGridLengthUnitType.Pixel, 120, 120); } } diff --git a/src/ViewModels/Merge.cs b/src/ViewModels/Merge.cs index d7b6a29c..e6679fb1 100644 --- a/src/ViewModels/Merge.cs +++ b/src/ViewModels/Merge.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace SourceGit.ViewModels { @@ -34,7 +33,7 @@ namespace SourceGit.ViewModels Source = source; Into = into; - Mode = forceFastForward ? Models.MergeMode.Supported[1] : AutoSelectMergeMode(); + Mode = forceFastForward ? Models.MergeMode.FastForward : AutoSelectMergeMode(); } public Merge(Repository repo, Models.Commit source, string into) @@ -57,7 +56,7 @@ namespace SourceGit.ViewModels Mode = AutoSelectMergeMode(); } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); _repo.ClearCommitMessage(); @@ -66,41 +65,58 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Merging '{_sourceName}' into '{Into}'"); Use(log); - return Task.Run(() => - { - new Commands.Merge(_repo.FullPath, _sourceName, Mode.Arg, Edit).Use(log).Exec(); - log.Complete(); + await new Commands.Merge(_repo.FullPath, _sourceName, Mode.Arg, Edit) + .Use(log) + .ExecAsync(); - var head = new Commands.QueryRevisionByRefName(_repo.FullPath, "HEAD").Result(); - CallUIThread(() => - { - _repo.NavigateToCommit(head, true); - _repo.SetWatcherEnabled(true); - }); - return true; - }); + log.Complete(); + + var head = await new Commands.QueryRevisionByRefName(_repo.FullPath, "HEAD").GetResultAsync(); + _repo.NavigateToCommit(head, true); + _repo.SetWatcherEnabled(true); + return true; } private Models.MergeMode AutoSelectMergeMode() + { + return + GetGitConfigBranchMergeOptions() // Branch + ?? GetSettingsPreferredMergeMode() // Repository + ?? GetGitConfigMergeFF(); // Global + } + + private Models.MergeMode GetGitConfigBranchMergeOptions() + { + var config = new Commands.Config(_repo.FullPath).GetAsync($"branch.{Into}.mergeoptions").Result; + return config switch + { + null or "" => null, + "--ff-only" => Models.MergeMode.FastForward, + "--no-ff" => Models.MergeMode.NoFastForward, + "--squash" => Models.MergeMode.Squash, + "--no-commit" or "--no-ff --no-commit" => Models.MergeMode.DontCommit, + _ => null + }; + } + + private Models.MergeMode GetSettingsPreferredMergeMode() { var preferredMergeModeIdx = _repo.Settings.PreferredMergeMode; if (preferredMergeModeIdx < 0 || preferredMergeModeIdx > Models.MergeMode.Supported.Length) - preferredMergeModeIdx = 0; + return null; - var defaultMergeMode = Models.MergeMode.Supported[preferredMergeModeIdx]; - var config = new Commands.Config(_repo.FullPath).Get($"branch.{Into}.mergeoptions"); - if (string.IsNullOrEmpty(config)) - return defaultMergeMode; - if (config.Equals("--ff-only", StringComparison.Ordinal)) - return Models.MergeMode.Supported[1]; - if (config.Equals("--no-ff", StringComparison.Ordinal)) - return Models.MergeMode.Supported[2]; - if (config.Equals("--squash", StringComparison.Ordinal)) - return Models.MergeMode.Supported[3]; - if (config.Equals("--no-commit", StringComparison.Ordinal) || config.Equals("--no-ff --no-commit", StringComparison.Ordinal)) - return Models.MergeMode.Supported[4]; + return Models.MergeMode.Supported[preferredMergeModeIdx]; + } - return defaultMergeMode; + private Models.MergeMode GetGitConfigMergeFF() + { + var config = new Commands.Config(_repo.FullPath).GetAsync("merge.ff").Result; + return config switch + { + "false" => Models.MergeMode.NoFastForward, + "only" => Models.MergeMode.FastForward, + _ => Models.MergeMode.Default + }; } private readonly Repository _repo = null; diff --git a/src/ViewModels/MergeMultiple.cs b/src/ViewModels/MergeMultiple.cs index aaa21cce..03f199a8 100644 --- a/src/ViewModels/MergeMultiple.cs +++ b/src/ViewModels/MergeMultiple.cs @@ -38,7 +38,7 @@ namespace SourceGit.ViewModels Strategy = Models.MergeStrategy.ForMultiple[0]; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); _repo.ClearCommitMessage(); @@ -47,18 +47,17 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Merge Multiple Heads"); Use(log); - return Task.Run(() => - { - new Commands.Merge( - _repo.FullPath, - ConvertTargetToMergeSources(), - AutoCommit, - Strategy.Arg).Use(log).Exec(); + await new Commands.Merge( + _repo.FullPath, + ConvertTargetToMergeSources(), + AutoCommit, + Strategy.Arg) + .Use(log) + .ExecAsync(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private List ConvertTargetToMergeSources() diff --git a/src/ViewModels/MoveRepositoryNode.cs b/src/ViewModels/MoveRepositoryNode.cs index 51f2e974..0c213058 100644 --- a/src/ViewModels/MoveRepositoryNode.cs +++ b/src/ViewModels/MoveRepositoryNode.cs @@ -43,7 +43,7 @@ namespace SourceGit.ViewModels Welcome.Instance.Refresh(); } - return null; + return Task.FromResult(true); } private void MakeRows(List collection, int depth) diff --git a/src/ViewModels/MoveSubmodule.cs b/src/ViewModels/MoveSubmodule.cs new file mode 100644 index 00000000..52210f41 --- /dev/null +++ b/src/ViewModels/MoveSubmodule.cs @@ -0,0 +1,53 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class MoveSubmodule : Popup + { + public Models.Submodule Submodule + { + get; + } + + [Required(ErrorMessage = "Path is required!!!")] + public string MoveTo + { + get => _moveTo; + set => SetProperty(ref _moveTo, value, true); + } + + public MoveSubmodule(Repository repo, Models.Submodule submodule) + { + _repo = repo; + _moveTo = submodule.Path; + Submodule = submodule; + } + + public override async Task Sure() + { + ProgressDescription = "Moving submodule ..."; + + var oldPath = Native.OS.GetAbsPath(_repo.FullPath, Submodule.Path); + var newPath = Native.OS.GetAbsPath(_repo.FullPath, _moveTo); + if (oldPath.Equals(newPath, StringComparison.Ordinal)) + return true; + + var log = _repo.CreateLog("Move Submodule"); + _repo.SetWatcherEnabled(false); + Use(log); + + var succ = await new Commands.Move(_repo.FullPath, oldPath, newPath, false) + .Use(log) + .ExecAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; + } + + private Repository _repo; + private string _moveTo; + } +} diff --git a/src/ViewModels/Popup.cs b/src/ViewModels/Popup.cs index b3fe7e23..942d33f9 100644 --- a/src/ViewModels/Popup.cs +++ b/src/ViewModels/Popup.cs @@ -1,9 +1,5 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; - -using Avalonia.Threading; - using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -41,11 +37,6 @@ namespace SourceGit.ViewModels return null; } - protected void CallUIThread(Action action) - { - Dispatcher.UIThread.Invoke(action); - } - protected void Use(CommandLog log) { log.Register(SetDescription); diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 665b7604..d7fe73f9 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -285,6 +285,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _commitChangeViewMode, value); } + public Models.ChangeViewMode StashChangeViewMode + { + get => _stashChangeViewMode; + set => SetProperty(ref _stashChangeViewMode, value); + } + public string GitInstallPath { get => Native.OS.GitExecutable; @@ -523,8 +529,8 @@ namespace SourceGit.ViewModels return; var file = Path.Combine(Native.OS.DataDir, "preference.json"); - var data = JsonSerializer.Serialize(this, JsonCodeGen.Default.Preferences); - File.WriteAllText(file, data); + using var stream = File.Create(file); + JsonSerializer.Serialize(stream, this, JsonCodeGen.Default.Preferences); } private static Preferences Load() @@ -584,13 +590,6 @@ namespace SourceGit.ViewModels } } - private void SortNodesRecursive(List collection) - { - SortNodes(collection); - foreach (var node in collection) - SortNodesRecursive(node.SubNodes); - } - private RepositoryNode FindNodeRecursive(string id, List collection) { foreach (var node in collection) @@ -699,6 +698,7 @@ namespace SourceGit.ViewModels private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _stagedChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _commitChangeViewMode = Models.ChangeViewMode.List; + private Models.ChangeViewMode _stashChangeViewMode = Models.ChangeViewMode.List; private string _gitDefaultCloneDir = string.Empty; diff --git a/src/ViewModels/PruneRemote.cs b/src/ViewModels/PruneRemote.cs index 007bcab8..56ba598d 100644 --- a/src/ViewModels/PruneRemote.cs +++ b/src/ViewModels/PruneRemote.cs @@ -15,7 +15,7 @@ namespace SourceGit.ViewModels Remote = remote; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Run `prune` on remote ..."; @@ -23,13 +23,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Prune Remote '{Remote.Name}'"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Remote(_repo.FullPath).Use(log).Prune(Remote.Name); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.Remote(_repo.FullPath) + .Use(log) + .PruneAsync(Remote.Name); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/PruneWorktrees.cs b/src/ViewModels/PruneWorktrees.cs index 3cb884dc..cce33527 100644 --- a/src/ViewModels/PruneWorktrees.cs +++ b/src/ViewModels/PruneWorktrees.cs @@ -9,7 +9,7 @@ namespace SourceGit.ViewModels _repo = repo; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Prune worktrees ..."; @@ -17,13 +17,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Prune Worktrees"); Use(log); - return Task.Run(() => - { - new Commands.Worktree(_repo.FullPath).Use(log).Prune(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.Worktree(_repo.FullPath) + .Use(log) + .PruneAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs index 1ddd6d99..9372f309 100644 --- a/src/ViewModels/Pull.cs +++ b/src/ViewModels/Pull.cs @@ -83,7 +83,7 @@ namespace SourceGit.ViewModels } else { - var autoSelectedRemote = null as Models.Remote; + Models.Remote autoSelectedRemote = null; if (!string.IsNullOrEmpty(Current.Upstream)) { var remoteNameEndIdx = Current.Upstream.IndexOf('/', 13); @@ -96,7 +96,7 @@ namespace SourceGit.ViewModels if (autoSelectedRemote == null) { - var remote = null as Models.Remote; + Models.Remote remote = null; if (!string.IsNullOrEmpty(_repo.Settings.DefaultRemote)) remote = _repo.Remotes.Find(x => x.Name == _repo.Settings.DefaultRemote); _selectedRemote = remote ?? _repo.Remotes[0]; @@ -111,7 +111,7 @@ namespace SourceGit.ViewModels } } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); @@ -119,60 +119,52 @@ namespace SourceGit.ViewModels Use(log); var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules; - return Task.Run(() => + var changes = await new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).GetResultAsync(); + var needPopStash = false; + if (changes > 0) { - var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); - var needPopStash = false; - if (changes > 0) + if (DiscardLocalChanges) { - if (DiscardLocalChanges) + await Commands.Discard.AllAsync(_repo.FullPath, false, log); + } + else + { + var succ = await new Commands.Stash(_repo.FullPath).Use(log).PushAsync("PULL_AUTO_STASH"); + if (!succ) { - Commands.Discard.All(_repo.FullPath, false, log); + log.Complete(); + _repo.SetWatcherEnabled(true); + return false; } - else - { - var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("PULL_AUTO_STASH"); - if (!succ) - { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; - } - needPopStash = true; - } + needPopStash = true; + } + } + + bool rs = await new Commands.Pull( + _repo.FullPath, + _selectedRemote.Name, + !string.IsNullOrEmpty(Current.Upstream) && Current.Upstream.Equals(_selectedBranch.FullName) ? string.Empty : _selectedBranch.Name, + UseRebase).Use(log).RunAsync(); + if (rs) + { + if (updateSubmodules) + { + var submodules = await new Commands.QueryUpdatableSubmodules(_repo.FullPath).GetResultAsync(); + if (submodules.Count > 0) + await new Commands.Submodule(_repo.FullPath).Use(log).UpdateAsync(submodules, true, true); } - bool rs = new Commands.Pull( - _repo.FullPath, - _selectedRemote.Name, - !string.IsNullOrEmpty(Current.Upstream) && Current.Upstream.Equals(_selectedBranch.FullName) ? string.Empty : _selectedBranch.Name, - UseRebase).Use(log).Exec(); + if (needPopStash) + await new Commands.Stash(_repo.FullPath).Use(log).PopAsync("stash@{0}"); + } - if (rs) - { - if (updateSubmodules) - { - var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); - if (submodules.Count > 0) - new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); - } + log.Complete(); - if (needPopStash) - new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); - } - - log.Complete(); - - var head = new Commands.QueryRevisionByRefName(_repo.FullPath, "HEAD").Result(); - CallUIThread(() => - { - _repo.NavigateToCommit(head, true); - _repo.SetWatcherEnabled(true); - }); - - return rs; - }); + var head = await new Commands.QueryRevisionByRefName(_repo.FullPath, "HEAD").GetResultAsync(); + _repo.NavigateToCommit(head, true); + _repo.SetWatcherEnabled(true); + return rs; } private void PostRemoteSelected() diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs index 917935b0..85969ee5 100644 --- a/src/ViewModels/Push.cs +++ b/src/ViewModels/Push.cs @@ -102,7 +102,7 @@ namespace SourceGit.ViewModels // Gather all local branches and find current branch. LocalBranches = new List(); - var current = null as Models.Branch; + Models.Branch current = null; foreach (var branch in _repo.Branches) { if (branch.IsLocal) @@ -142,7 +142,7 @@ namespace SourceGit.ViewModels // Set default remote to the first if it has not been set. if (_selectedRemote == null) { - var remote = null as Models.Remote; + Models.Remote remote = null; if (!string.IsNullOrEmpty(_repo.Settings.DefaultRemote)) remote = repo.Remotes.Find(x => x.Name == _repo.Settings.DefaultRemote); @@ -158,7 +158,7 @@ namespace SourceGit.ViewModels return !string.IsNullOrEmpty(_selectedRemoteBranch?.Head); } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); @@ -168,22 +168,19 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Push"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Push( - _repo.FullPath, - _selectedLocalBranch.Name, - _selectedRemote.Name, - remoteBranchName, - PushAllTags, - _repo.Submodules.Count > 0 && CheckSubmodules, - _isSetTrackOptionVisible && Tracking, - ForcePush).Use(log).Exec(); + var succ = await new Commands.Push( + _repo.FullPath, + _selectedLocalBranch.Name, + _selectedRemote.Name, + remoteBranchName, + PushAllTags, + _repo.Submodules.Count > 0 && CheckSubmodules, + _isSetTrackOptionVisible && Tracking, + ForcePush).Use(log).RunAsync(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private void AutoSelectBranchByRemote() diff --git a/src/ViewModels/PushRevision.cs b/src/ViewModels/PushRevision.cs index 9322db2c..a257aea8 100644 --- a/src/ViewModels/PushRevision.cs +++ b/src/ViewModels/PushRevision.cs @@ -28,7 +28,7 @@ namespace SourceGit.ViewModels Force = false; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Push {Revision.SHA.Substring(0, 10)} -> {RemoteBranch.FriendlyName} ..."; @@ -36,22 +36,19 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Push Revision"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Push( - _repo.FullPath, - Revision.SHA, - RemoteBranch.Remote, - RemoteBranch.Name, - false, - false, - false, - Force).Use(log).Exec(); + var succ = await new Commands.Push( + _repo.FullPath, + Revision.SHA, + RemoteBranch.Remote, + RemoteBranch.Name, + false, + false, + false, + Force).Use(log).RunAsync(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo; diff --git a/src/ViewModels/PushTag.cs b/src/ViewModels/PushTag.cs index 82a3af00..f2bb4500 100644 --- a/src/ViewModels/PushTag.cs +++ b/src/ViewModels/PushTag.cs @@ -34,7 +34,7 @@ namespace SourceGit.ViewModels SelectedRemote = _repo.Remotes[0]; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Pushing tag ..."; @@ -42,28 +42,29 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Push Tag"); Use(log); - return Task.Run(() => + var succ = true; + var tag = $"refs/tags/{Target.Name}"; + if (_pushAllRemotes) { - var succ = true; - var tag = $"refs/tags/{Target.Name}"; - if (_pushAllRemotes) + foreach (var remote in _repo.Remotes) { - foreach (var remote in _repo.Remotes) - { - succ = new Commands.Push(_repo.FullPath, remote.Name, tag, false).Use(log).Exec(); - if (!succ) - break; - } - } - else - { - succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, tag, false).Use(log).Exec(); + succ = await new Commands.Push(_repo.FullPath, remote.Name, tag, false) + .Use(log) + .RunAsync(); + if (!succ) + break; } + } + else + { + succ = await new Commands.Push(_repo.FullPath, SelectedRemote.Name, tag, false) + .Use(log) + .RunAsync(); + } - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/Rebase.cs b/src/ViewModels/Rebase.cs index 0fc8ec89..1727bc86 100644 --- a/src/ViewModels/Rebase.cs +++ b/src/ViewModels/Rebase.cs @@ -40,7 +40,7 @@ namespace SourceGit.ViewModels AutoStash = true; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); _repo.ClearCommitMessage(); @@ -49,13 +49,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Rebase"); Use(log); - return Task.Run(() => - { - new Commands.Rebase(_repo.FullPath, _revision, AutoStash).Use(log).Exec(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.Rebase(_repo.FullPath, _revision, AutoStash) + .Use(log) + .ExecAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo; diff --git a/src/ViewModels/RemoveWorktree.cs b/src/ViewModels/RemoveWorktree.cs index d5de8533..a78cedc2 100644 --- a/src/ViewModels/RemoveWorktree.cs +++ b/src/ViewModels/RemoveWorktree.cs @@ -21,7 +21,7 @@ namespace SourceGit.ViewModels Target = target; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Remove worktree ..."; @@ -29,13 +29,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Remove worktree"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Worktree(_repo.FullPath).Use(log).Remove(Target.FullPath, Force); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.Worktree(_repo.FullPath) + .Use(log) + .RemoveAsync(Target.FullPath, Force); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/RenameBranch.cs b/src/ViewModels/RenameBranch.cs index 22cd2688..655a2d0c 100644 --- a/src/ViewModels/RenameBranch.cs +++ b/src/ViewModels/RenameBranch.cs @@ -31,24 +31,22 @@ namespace SourceGit.ViewModels { if (ctx.ObjectInstance is RenameBranch rename) { - var fixedName = rename.FixName(name); + var fixedName = Models.Branch.FixName(name); foreach (var b in rename._repo.Branches) { - if (b.IsLocal && b != rename.Target && b.Name == fixedName) - { + if (b.IsLocal && b != rename.Target && b.Name.Equals(fixedName, StringComparison.Ordinal)) return new ValidationResult("A branch with same name already exists!!!"); - } } } return ValidationResult.Success; } - public override Task Sure() + public override async Task Sure() { - var fixedName = FixName(_name); - if (fixedName == Target.Name) - return null; + var fixedName = Models.Branch.FixName(_name); + if (fixedName.Equals(Target.Name, StringComparison.Ordinal)) + return true; _repo.SetWatcherEnabled(false); ProgressDescription = $"Rename '{Target.Name}'"; @@ -56,48 +54,34 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Rename Branch '{Target.Name}'"); Use(log); - return Task.Run(() => + var isCurrent = Target.IsCurrent; + var oldName = Target.FullName; + + var succ = await Commands.Branch.RenameAsync(_repo.FullPath, Target.Name, fixedName, log); + if (succ) { - var isCurrent = Target.IsCurrent; - var oldName = Target.FullName; - var succ = Commands.Branch.Rename(_repo.FullPath, Target.Name, fixedName, log); - log.Complete(); - - CallUIThread(() => + foreach (var filter in _repo.Settings.HistoriesFilters) { - ProgressDescription = "Waiting for branch updated..."; - - if (succ) + if (filter.Type == Models.FilterType.LocalBranch && + filter.Pattern.Equals(oldName, StringComparison.Ordinal)) { - foreach (var filter in _repo.Settings.HistoriesFilters) - { - if (filter.Type == Models.FilterType.LocalBranch && - filter.Pattern.Equals(oldName, StringComparison.Ordinal)) - { - filter.Pattern = $"refs/heads/{fixedName}"; - break; - } - } + filter.Pattern = $"refs/heads/{fixedName}"; + break; } + } + } - _repo.MarkBranchesDirtyManually(); - _repo.SetWatcherEnabled(true); - }); + log.Complete(); + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); - if (isCurrent) - Task.Delay(400).Wait(); + if (isCurrent) + { + ProgressDescription = "Waiting for branch updated..."; + await Task.Delay(400); + } - return succ; - }); - } - - private string FixName(string name) - { - if (!name.Contains(' ')) - return name; - - var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries); - return string.Join("-", parts); + return succ; } private readonly Repository _repo; diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 4a057398..908da1c0 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -510,7 +510,7 @@ namespace SourceGit.ViewModels var gitDirForWatcher = _gitDir; if (_gitDir.Replace('\\', '/').IndexOf("/worktrees/", StringComparison.Ordinal) > 0) { - var commonDir = new Commands.QueryGitCommonDir(_fullpath).Result(); + var commonDir = new Commands.QueryGitCommonDir(_fullpath).GetResultAsync().Result; if (!string.IsNullOrEmpty(commonDir)) gitDirForWatcher = commonDir; } @@ -649,9 +649,9 @@ namespace SourceGit.ViewModels Task.Run(RefreshWorkingCopyChanges); Task.Run(RefreshStashes); - Task.Run(() => + Task.Run(async () => { - var config = new Commands.Config(_fullpath).ListAll(); + var config = await new Commands.Config(_fullpath).ReadAllAsync().ConfigureAwait(false); _hasAllowedSignersFile = config.TryGetValue("gpg.ssh.allowedSignersFile", out var allowedSignersFile) && !string.IsNullOrEmpty(allowedSignersFile); if (config.TryGetValue("gitflow.branch.master", out var masterName)) @@ -823,15 +823,13 @@ namespace SourceGit.ViewModels if (!CanCreatePopup()) return; - ExecuteCustomAction popup; - if (scope is Models.Branch b) - popup = new ExecuteCustomAction(this, action, b); - else if (scope is Models.Commit c) - popup = new ExecuteCustomAction(this, action, c); - else if (scope is Models.Tag t) - popup = new ExecuteCustomAction(this, action, t); - else - popup = new ExecuteCustomAction(this, action); + var popup = scope switch + { + Models.Branch b => new ExecuteCustomAction(this, action, b), + Models.Commit c => new ExecuteCustomAction(this, action, c), + Models.Tag t => new ExecuteCustomAction(this, action, t), + _ => new ExecuteCustomAction(this, action) + }; if (action.Controls.Count == 0) ShowAndStartPopup(popup); @@ -869,26 +867,33 @@ namespace SourceGit.ViewModels SelectedSearchedCommit = null; MatchedFilesForSearching = null; - Task.Run(() => + Task.Run(async () => { - List visible = []; + var visible = new List(); var method = (Models.CommitSearchMethod)_searchCommitFilterType; if (method == Models.CommitSearchMethod.BySHA) { - var isCommitSHA = new Commands.IsCommitSHA(_fullpath, _searchCommitFilter).Result(); + var isCommitSHA = await new Commands.IsCommitSHA(_fullpath, _searchCommitFilter) + .GetResultAsync() + .ConfigureAwait(false); + if (isCommitSHA) { - var commit = new Commands.QuerySingleCommit(_fullpath, _searchCommitFilter).Result(); - visible = [commit]; - } + var commit = await new Commands.QuerySingleCommit(_fullpath, _searchCommitFilter) + .GetResultAsync() + .ConfigureAwait(false); + visible.Add(commit); + } } else { - visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, method, _onlySearchCommitsInCurrentBranch).Result(); + visible = await new Commands.QueryCommits(_fullpath, _searchCommitFilter, method, _onlySearchCommitsInCurrentBranch) + .GetResultAsync() + .ConfigureAwait(false); } - Dispatcher.UIThread.Invoke(() => + Dispatcher.UIThread.Post(() => { SearchedCommits = visible; IsSearchLoadingVisible = false; @@ -1077,50 +1082,45 @@ namespace SourceGit.ViewModels _workingCopy?.AbortMerge(); } - public List GetCustomActions(Models.CustomActionScope scope) + public List<(Models.CustomAction, CustomActionContextMenuLabel)> GetCustomActions(Models.CustomActionScope scope) { - var actions = new List(); + var actions = new List<(Models.CustomAction, CustomActionContextMenuLabel)>(); foreach (var act in Preferences.Instance.CustomActions) { if (act.Scope == scope) - actions.Add(act); + actions.Add((act, new CustomActionContextMenuLabel(act.Name, true))); } foreach (var act in _settings.CustomActions) { if (act.Scope == scope) - actions.Add(act); + actions.Add((act, new CustomActionContextMenuLabel(act.Name, false))); } return actions; } - public void Bisect(string subcmd) + public async Task ExecBisectCommandAsync(string subcmd) { IsBisectCommandRunning = true; SetWatcherEnabled(false); var log = CreateLog($"Bisect({subcmd})"); - Task.Run(() => - { - var succ = new Commands.Bisect(_fullpath, subcmd).Use(log).Exec(); - log.Complete(); - var head = new Commands.QueryRevisionByRefName(_fullpath, "HEAD").Result(); - Dispatcher.UIThread.Invoke(() => - { - if (!succ) - App.RaiseException(_fullpath, log.Content.Substring(log.Content.IndexOf('\n')).Trim()); - else if (log.Content.Contains("is the first bad commit")) - App.SendNotification(_fullpath, log.Content.Substring(log.Content.IndexOf('\n')).Trim()); + var succ = await new Commands.Bisect(_fullpath, subcmd).Use(log).ExecAsync(); + log.Complete(); - MarkBranchesDirtyManually(); - NavigateToCommit(head, true); - SetWatcherEnabled(true); - IsBisectCommandRunning = false; - }); - }); + var head = await new Commands.QueryRevisionByRefName(_fullpath, "HEAD").GetResultAsync(); + if (!succ) + App.RaiseException(_fullpath, log.Content.Substring(log.Content.IndexOf('\n')).Trim()); + else if (log.Content.Contains("is the first bad commit")) + App.SendNotification(_fullpath, log.Content.Substring(log.Content.IndexOf('\n')).Trim()); + + MarkBranchesDirtyManually(); + NavigateToCommit(head, true); + SetWatcherEnabled(true); + IsBisectCommandRunning = false; } public bool MayHaveSubmodules() @@ -1132,8 +1132,8 @@ namespace SourceGit.ViewModels public void RefreshBranches() { - var branches = new Commands.QueryBranches(_fullpath).Result(out var localBranchesCount); - var remotes = new Commands.QueryRemotes(_fullpath).Result(); + var branches = new Commands.QueryBranches(_fullpath).GetResultAsync().Result; + var remotes = new Commands.QueryRemotes(_fullpath).GetResultAsync().Result; var builder = BuildBranchTree(branches, remotes); Dispatcher.UIThread.Invoke(() => @@ -1145,6 +1145,13 @@ namespace SourceGit.ViewModels CurrentBranch = branches.Find(x => x.IsCurrent); LocalBranchTrees = builder.Locals; RemoteBranchTrees = builder.Remotes; + + var localBranchesCount = 0; + foreach (var b in branches) + { + if (b.IsLocal && !b.IsDetachedHead) + localBranchesCount++; + } LocalBranchesCount = localBranchesCount; if (_workingCopy != null) @@ -1157,7 +1164,7 @@ namespace SourceGit.ViewModels public void RefreshWorktrees() { - var worktrees = new Commands.Worktree(_fullpath).List(); + var worktrees = new Commands.Worktree(_fullpath).ReadAllAsync().Result; var cleaned = new List(); foreach (var worktree in worktrees) @@ -1176,7 +1183,7 @@ namespace SourceGit.ViewModels public void RefreshTags() { - var tags = new Commands.QueryTags(_fullpath).Result(); + var tags = new Commands.QueryTags(_fullpath).GetResultAsync().Result; Dispatcher.UIThread.Invoke(() => { Tags = tags; @@ -1207,7 +1214,7 @@ namespace SourceGit.ViewModels else builder.Append(filters); - var commits = new Commands.QueryCommits(_fullpath, builder.ToString()).Result(); + var commits = new Commands.QueryCommits(_fullpath, builder.ToString()).GetResultAsync().Result; var graph = Models.CommitGraph.Parse(commits, _settings.EnableFirstParentInHistories); Dispatcher.UIThread.Invoke(() => @@ -1244,7 +1251,7 @@ namespace SourceGit.ViewModels return; } - var submodules = new Commands.QuerySubmodules(_fullpath).Result(); + var submodules = new Commands.QuerySubmodules(_fullpath).GetResultAsync().Result; _watcher?.SetSubmodules(submodules); Dispatcher.UIThread.Invoke(() => @@ -1265,8 +1272,9 @@ namespace SourceGit.ViewModels } hasChanged = !exist.SHA.Equals(module.SHA, StringComparison.Ordinal) || - !exist.URL.Equals(module.URL, StringComparison.Ordinal) || - exist.Status != module.Status; + !exist.Branch.Equals(module.Branch, StringComparison.Ordinal) || + !exist.URL.Equals(module.URL, StringComparison.Ordinal) || + exist.Status != module.Status; if (hasChanged) break; @@ -1286,7 +1294,7 @@ namespace SourceGit.ViewModels if (IsBare) return; - var changes = new Commands.QueryLocalChanges(_fullpath, _settings.IncludeUntrackedInLocalChanges).Result(); + var changes = new Commands.QueryLocalChanges(_fullpath, _settings.IncludeUntrackedInLocalChanges).GetResultAsync().Result; if (_workingCopy == null) return; @@ -1306,7 +1314,7 @@ namespace SourceGit.ViewModels if (IsBare) return; - var stashes = new Commands.QueryStashes(_fullpath).Result(); + var stashes = new Commands.QueryStashes(_fullpath).GetResultAsync().Result; Dispatcher.UIThread.Invoke(() => { if (_stashesPage != null) @@ -1328,41 +1336,6 @@ namespace SourceGit.ViewModels ShowPopup(new CreateBranch(this, _currentBranch)); } - public bool ConfirmCheckoutBranch() - { - if (Dispatcher.UIThread.CheckAccess()) - return true; - - if (_currentBranch is not { IsDetachedHead: true }) - return true; - - var refs = new Commands.QueryRefsContainsCommit(_fullpath, _currentBranch.Head).Result(); - if (refs.Count == 0) - { - var confirmCheckout = false; - var resetEvent = new AutoResetEvent(false); - - Dispatcher.UIThread.Invoke(() => - { - var msg = App.Text("Checkout.WarnLostCommits"); - App.ShowWindow(new Confirm(msg, () => - { - confirmCheckout = true; - resetEvent.Set(); - }, () => - { - confirmCheckout = false; - resetEvent.Set(); - }), true); - }); - - resetEvent.WaitOne(); - return confirmCheckout; - } - - return true; - } - public void CheckoutBranch(Models.Branch branch) { if (branch.IsLocal) @@ -1466,7 +1439,7 @@ namespace SourceGit.ViewModels public void UpdateSubmodules() { if (CanCreatePopup()) - ShowPopup(new UpdateSubmodules(this)); + ShowPopup(new UpdateSubmodules(this, null)); } public void OpenSubmodule(string submodule) @@ -1587,13 +1560,9 @@ namespace SourceGit.ViewModels init.Click += (_, e) => { if (_currentBranch == null) - { App.RaiseException(_fullpath, "Git flow init failed: No branch found!!!"); - } else if (CanCreatePopup()) - { ShowPopup(new InitGitFlow(this)); - } e.Handled = true; }; @@ -1696,9 +1665,9 @@ namespace SourceGit.ViewModels locks.IsEnabled = _remotes.Count > 0; if (_remotes.Count == 1) { - locks.Click += (_, e) => + locks.Click += async (_, e) => { - App.ShowWindow(new LFSLocks(this, _remotes[0].Name), true); + await App.ShowDialog(new LFSLocks(this, _remotes[0].Name)); e.Handled = true; }; } @@ -1709,9 +1678,9 @@ namespace SourceGit.ViewModels var remoteName = remote.Name; var lockRemote = new MenuItem(); lockRemote.Header = remoteName; - lockRemote.Click += (_, e) => + lockRemote.Click += async (_, e) => { - App.ShowWindow(new LFSLocks(this, remoteName), true); + await App.ShowDialog(new LFSLocks(this, remoteName)); e.Handled = true; }; locks.Items.Add(lockRemote); @@ -1726,10 +1695,10 @@ namespace SourceGit.ViewModels var install = new MenuItem(); install.Header = App.Text("GitLFS.Install"); install.Icon = App.CreateMenuIcon("Icons.Init"); - install.Click += (_, e) => + install.Click += async (_, e) => { var log = CreateLog("Install LFS"); - var succ = new Commands.LFS(_fullpath).Install(log); + var succ = await new Commands.LFS(_fullpath).InstallAsync(log); if (succ) App.SendNotification(_fullpath, "LFS enabled successfully!"); @@ -1752,10 +1721,10 @@ namespace SourceGit.ViewModels { foreach (var action in actions) { - var dup = action; + var (dup, label) = action; var item = new MenuItem(); item.Icon = App.CreateMenuIcon("Icons.Action"); - item.Header = dup.Name; + item.Header = label; item.Click += (_, e) => { ExecCustomAction(dup, null); @@ -2014,7 +1983,7 @@ namespace SourceGit.ViewModels compareWithCurrent.Icon = App.CreateMenuIcon("Icons.Compare"); compareWithCurrent.Click += (_, _) => { - App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch), false); + App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch)); }; menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(compareWithCurrent); @@ -2024,13 +1993,13 @@ namespace SourceGit.ViewModels var compareWithWorktree = new MenuItem(); compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree"); compareWithWorktree.Icon = App.CreateMenuIcon("Icons.Compare"); - compareWithWorktree.Click += (_, _) => + compareWithWorktree.Click += async (_, _) => { SelectedSearchedCommit = null; if (_histories != null) { - var target = new Commands.QuerySingleCommit(_fullpath, branch.Head).Result(); + var target = await new Commands.QuerySingleCommit(_fullpath, branch.Head).GetResultAsync(); _histories.AutoSelectedCommit = null; _histories.DetailContext = new RevisionCompare(_fullpath, target, null); } @@ -2147,9 +2116,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("BranchCM.CopyName"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(branch.Name); + await App.CopyTextAsync(branch.Name); e.Handled = true; }; menu.Items.Add(copy); @@ -2219,9 +2188,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("RemoteCM.CopyURL"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(remote.URL); + await App.CopyTextAsync(remote.URL); e.Handled = true; }; @@ -2294,7 +2263,7 @@ namespace SourceGit.ViewModels compareWithHead.Icon = App.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch), false); + App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch)); }; menu.Items.Add(compareWithHead); @@ -2303,13 +2272,13 @@ namespace SourceGit.ViewModels var compareWithWorktree = new MenuItem(); compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree"); compareWithWorktree.Icon = App.CreateMenuIcon("Icons.Compare"); - compareWithWorktree.Click += (_, _) => + compareWithWorktree.Click += async (_, _) => { SelectedSearchedCommit = null; if (_histories != null) { - var target = new Commands.QuerySingleCommit(_fullpath, branch.Head).Result(); + var target = await new Commands.QuerySingleCommit(_fullpath, branch.Head).GetResultAsync(); _histories.AutoSelectedCommit = null; _histories.DetailContext = new RevisionCompare(_fullpath, target, null); } @@ -2361,9 +2330,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("BranchCM.CopyName"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(name); + await App.CopyTextAsync(name); e.Handled = true; }; @@ -2441,10 +2410,10 @@ namespace SourceGit.ViewModels foreach (var action in actions) { - var dup = action; + var (dup, label) = action; var item = new MenuItem(); item.Icon = App.CreateMenuIcon("Icons.Action"); - item.Header = dup.Name; + item.Header = label; item.Click += (_, e) => { ExecCustomAction(dup, tag); @@ -2461,9 +2430,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("TagCM.Copy"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, ev) => + copy.Click += async (_, ev) => { - App.CopyText(tag.Name); + await App.CopyTextAsync(tag.Name); ev.Handled = true; }; @@ -2471,9 +2440,9 @@ namespace SourceGit.ViewModels copyMessage.Header = App.Text("TagCM.CopyMessage"); copyMessage.Icon = App.CreateMenuIcon("Icons.Copy"); copyMessage.IsEnabled = !string.IsNullOrEmpty(tag.Message); - copyMessage.Click += (_, ev) => + copyMessage.Click += async (_, ev) => { - App.CopyText(tag.Message); + await App.CopyTextAsync(tag.Message); ev.Handled = true; }; @@ -2586,6 +2555,46 @@ namespace SourceGit.ViewModels ev.Handled = true; }; + var update = new MenuItem(); + update.Header = App.Text("Submodule.Update"); + update.Icon = App.CreateMenuIcon("Icons.Loading"); + update.Click += (_, ev) => + { + if (CanCreatePopup()) + ShowPopup(new UpdateSubmodules(this, submodule)); + ev.Handled = true; + }; + + var move = new MenuItem(); + move.Header = App.Text("Submodule.Move"); + move.Icon = App.CreateMenuIcon("Icons.MoveTo"); + move.Click += (_, ev) => + { + if (CanCreatePopup()) + ShowPopup(new MoveSubmodule(this, submodule)); + ev.Handled = true; + }; + + var setURL = new MenuItem(); + setURL.Header = App.Text("Submodule.SetURL"); + setURL.Icon = App.CreateMenuIcon("Icons.Edit"); + setURL.Click += (_, ev) => + { + if (CanCreatePopup()) + ShowPopup(new ChangeSubmoduleUrl(this, submodule)); + ev.Handled = true; + }; + + var setBranch = new MenuItem(); + setBranch.Header = App.Text("Submodule.SetBranch"); + setBranch.Icon = App.CreateMenuIcon("Icons.Track"); + setBranch.Click += (_, ev) => + { + if (CanCreatePopup()) + ShowPopup(new SetSubmoduleBranch(this, submodule)); + ev.Handled = true; + }; + var deinit = new MenuItem(); deinit.Header = App.Text("Submodule.Deinit"); deinit.Icon = App.CreateMenuIcon("Icons.Undo"); @@ -2607,21 +2616,71 @@ namespace SourceGit.ViewModels ev.Handled = true; }; - var copy = new MenuItem(); - copy.Header = App.Text("Submodule.CopyPath"); - copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, ev) => + var histories = new MenuItem(); + histories.Header = App.Text("Submodule.Histories"); + histories.Icon = App.CreateMenuIcon("Icons.Histories"); + histories.Click += (_, ev) => { - App.CopyText(submodule.Path); + App.ShowWindow(new FileHistories(this, submodule.Path)); ev.Handled = true; }; + var copySHA = new MenuItem(); + copySHA.Header = App.Text("CommitDetail.Info.SHA"); + copySHA.Icon = App.CreateMenuIcon("Icons.Fingerprint"); + copySHA.Click += async (_, ev) => + { + await App.CopyTextAsync(submodule.SHA); + ev.Handled = true; + }; + + var copyRelativePath = new MenuItem(); + copyRelativePath.Header = App.Text("Submodule.CopyPath"); + copyRelativePath.Icon = App.CreateMenuIcon("Icons.Folder"); + copyRelativePath.Click += async (_, ev) => + { + await App.CopyTextAsync(submodule.Path); + ev.Handled = true; + }; + + var copyURL = new MenuItem(); + copyURL.Header = App.Text("Submodule.URL"); + copyURL.Icon = App.CreateMenuIcon("Icons.Link"); + copyURL.Click += async (_, ev) => + { + await App.CopyTextAsync(submodule.URL); + ev.Handled = true; + }; + + var copyBranch = new MenuItem(); + copyBranch.Header = App.Text("Submodule.Branch"); + copyBranch.Icon = App.CreateMenuIcon("Icons.Branch"); + copyBranch.Click += async (_, ev) => + { + await App.CopyTextAsync(submodule.Branch); + ev.Handled = true; + }; + + var copy = new MenuItem(); + copy.Header = App.Text("Copy"); + copy.Icon = App.CreateMenuIcon("Icons.Copy"); + copy.Items.Add(copySHA); + copy.Items.Add(copyBranch); + copy.Items.Add(copyRelativePath); + copy.Items.Add(copyURL); + var menu = new ContextMenu(); menu.Items.Add(open); menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(update); + menu.Items.Add(setURL); + menu.Items.Add(setBranch); + menu.Items.Add(move); menu.Items.Add(deinit); menu.Items.Add(rm); menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(histories); + menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(copy); return menu; } @@ -2635,11 +2694,11 @@ namespace SourceGit.ViewModels var unlock = new MenuItem(); unlock.Header = App.Text("Worktree.Unlock"); unlock.Icon = App.CreateMenuIcon("Icons.Unlock"); - unlock.Click += (_, ev) => + unlock.Click += async (_, ev) => { SetWatcherEnabled(false); var log = CreateLog("Unlock Worktree"); - var succ = new Commands.Worktree(_fullpath).Use(log).Unlock(worktree.FullPath); + var succ = await new Commands.Worktree(_fullpath).Use(log).UnlockAsync(worktree.FullPath); if (succ) worktree.IsLocked = false; log.Complete(); @@ -2653,11 +2712,11 @@ namespace SourceGit.ViewModels var loc = new MenuItem(); loc.Header = App.Text("Worktree.Lock"); loc.Icon = App.CreateMenuIcon("Icons.Lock"); - loc.Click += (_, ev) => + loc.Click += async (_, ev) => { SetWatcherEnabled(false); var log = CreateLog("Lock Worktree"); - var succ = new Commands.Worktree(_fullpath).Use(log).Lock(worktree.FullPath); + var succ = await new Commands.Worktree(_fullpath).Use(log).LockAsync(worktree.FullPath); if (succ) worktree.IsLocked = true; log.Complete(); @@ -2681,9 +2740,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("Worktree.CopyPath"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(worktree.FullPath); + await App.CopyTextAsync(worktree.FullPath); e.Handled = true; }; menu.Items.Add(new MenuItem() { Header = "-" }); @@ -2876,10 +2935,10 @@ namespace SourceGit.ViewModels foreach (var action in actions) { - var dup = action; + var (dup, label) = action; var item = new MenuItem(); item.Icon = App.CreateMenuIcon("Icons.Action"); - item.Header = dup.Name; + item.Header = label; item.Click += (_, e) => { ExecCustomAction(dup, branch); @@ -2914,10 +2973,13 @@ namespace SourceGit.ViewModels _requestingWorktreeFiles = true; - Task.Run(() => + Task.Run(async () => { - _worktreeFiles = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result(); - Dispatcher.UIThread.Invoke(() => + _worktreeFiles = await new Commands.QueryRevisionFileNames(_fullpath, "HEAD") + .GetResultAsync() + .ConfigureAwait(false); + + Dispatcher.UIThread.Post(() => { if (IsSearchingCommitsByFilePath() && _requestingWorktreeFiles) CalcMatchedFilesForSearching(); @@ -2949,7 +3011,7 @@ namespace SourceGit.ViewModels MatchedFilesForSearching = matched; } - private void AutoFetchImpl(object sender) + private async void AutoFetchImpl(object sender) { try { @@ -2974,7 +3036,7 @@ namespace SourceGit.ViewModels Dispatcher.UIThread.Invoke(() => IsAutoFetching = true); foreach (var remote in remotes) - new Commands.Fetch(_fullpath, remote, false, false) { RaiseError = false }.Exec(); + await new Commands.Fetch(_fullpath, remote, false, false) { RaiseError = false }.RunAsync(); _lastFetchTime = DateTime.Now; Dispatcher.UIThread.Invoke(() => IsAutoFetching = false); } diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs index d69ff711..10bf1928 100644 --- a/src/ViewModels/RepositoryConfigure.cs +++ b/src/ViewModels/RepositoryConfigure.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Threading.Tasks; + using Avalonia.Collections; using CommunityToolkit.Mvvm.ComponentModel; @@ -159,7 +161,7 @@ namespace SourceGit.ViewModels if (!AvailableOpenAIServices.Contains(PreferredOpenAIService)) PreferredOpenAIService = "---"; - _cached = new Commands.Config(repo.FullPath).ListAll(); + _cached = new Commands.Config(repo.FullPath).ReadAllAsync().Result; if (_cached.TryGetValue("user.name", out var name)) UserName = name; if (_cached.TryGetValue("user.email", out var email)) @@ -316,33 +318,21 @@ namespace SourceGit.ViewModels _repo.Settings.MoveCustomActionDown(_selectedCustomAction); } - public void Save() + public async Task SaveAsync() { - SetIfChanged("user.name", UserName, ""); - SetIfChanged("user.email", UserEmail, ""); - SetIfChanged("commit.gpgsign", GPGCommitSigningEnabled ? "true" : "false", "false"); - SetIfChanged("tag.gpgsign", GPGTagSigningEnabled ? "true" : "false", "false"); - SetIfChanged("user.signingkey", GPGUserSigningKey, ""); - SetIfChanged("http.proxy", HttpProxy, ""); - SetIfChanged("fetch.prune", EnablePruneOnFetch ? "true" : "false", "false"); + await SetIfChangedAsync("user.name", UserName, ""); + await SetIfChangedAsync("user.email", UserEmail, ""); + await SetIfChangedAsync("commit.gpgsign", GPGCommitSigningEnabled ? "true" : "false", "false"); + await SetIfChangedAsync("tag.gpgsign", GPGTagSigningEnabled ? "true" : "false", "false"); + await SetIfChangedAsync("user.signingkey", GPGUserSigningKey, ""); + await SetIfChangedAsync("http.proxy", HttpProxy, ""); + await SetIfChangedAsync("fetch.prune", EnablePruneOnFetch ? "true" : "false", "false"); } - private void SetIfChanged(string key, string value, string defValue) + private async Task SetIfChangedAsync(string key, string value, string defValue) { - bool changed = false; - if (_cached.TryGetValue(key, out var old)) - { - changed = old != value; - } - else if (!string.IsNullOrEmpty(value) && value != defValue) - { - changed = true; - } - - if (changed) - { - new Commands.Config(_repo.FullPath).Set(key, value); - } + if (value != _cached.GetValueOrDefault(key, defValue)) + await new Commands.Config(_repo.FullPath).SetAsync(key, value); } private readonly Repository _repo = null; diff --git a/src/ViewModels/Reset.cs b/src/ViewModels/Reset.cs index d3377a99..5a628b1e 100644 --- a/src/ViewModels/Reset.cs +++ b/src/ViewModels/Reset.cs @@ -28,7 +28,7 @@ namespace SourceGit.ViewModels SelectedMode = Models.ResetMode.Supported[1]; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Reset current branch to {To.SHA} ..."; @@ -36,13 +36,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Reset HEAD to '{To.SHA}'"); Use(log); - return Task.Run(() => - { - var succ = new Commands.Reset(_repo.FullPath, To.SHA, SelectedMode.Arg).Use(log).Exec(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.Reset(_repo.FullPath, To.SHA, SelectedMode.Arg) + .Use(log) + .ExecAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/ResetWithoutCheckout.cs b/src/ViewModels/ResetWithoutCheckout.cs index 9ec3f6b3..2c6403d1 100644 --- a/src/ViewModels/ResetWithoutCheckout.cs +++ b/src/ViewModels/ResetWithoutCheckout.cs @@ -30,7 +30,7 @@ namespace SourceGit.ViewModels To = to; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = $"Reset {Target.Name} to {_revision} ..."; @@ -38,13 +38,10 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Reset '{Target.Name}' to '{_revision}'"); Use(log); - return Task.Run(() => - { - var succ = Commands.Branch.Create(_repo.FullPath, Target.Name, _revision, true, log); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await Commands.Branch.CreateAsync(_repo.FullPath, Target.Name, _revision, true, log); + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo = null; diff --git a/src/ViewModels/Revert.cs b/src/ViewModels/Revert.cs index bb07e2ff..b39076a0 100644 --- a/src/ViewModels/Revert.cs +++ b/src/ViewModels/Revert.cs @@ -22,7 +22,7 @@ namespace SourceGit.ViewModels AutoCommit = true; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); _repo.ClearCommitMessage(); @@ -31,13 +31,13 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog($"Revert '{Target.SHA}'"); Use(log); - return Task.Run(() => - { - new Commands.Revert(_repo.FullPath, Target.SHA, AutoCommit).Use(log).Exec(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + await new Commands.Revert(_repo.FullPath, Target.SHA, AutoCommit) + .Use(log) + .ExecAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index f04d1e30..383786e3 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -39,7 +39,7 @@ namespace SourceGit.ViewModels { if (SetProperty(ref _selectedChanges, value)) { - if (value != null && value.Count == 1) + if (value is { Count: 1 }) { var option = new Models.DiffOption(GetSHA(_startPoint), GetSHA(_endPoint), value[0]); DiffContext = new DiffContext(_repo, option, _diffContext); @@ -58,9 +58,7 @@ namespace SourceGit.ViewModels set { if (SetProperty(ref _searchFilter, value)) - { RefreshVisible(); - } } } @@ -85,12 +83,9 @@ namespace SourceGit.ViewModels _repo = null; _startPoint = null; _endPoint = null; - if (_changes != null) - _changes.Clear(); - if (_visibleChanges != null) - _visibleChanges.Clear(); - if (_selectedChanges != null) - _selectedChanges.Clear(); + _changes?.Clear(); + _visibleChanges?.Clear(); + _selectedChanges?.Clear(); _searchFilter = null; _diffContext = null; } @@ -120,11 +115,11 @@ namespace SourceGit.ViewModels public void SaveAsPatch(string saveTo) { - Task.Run(() => + Task.Run(async () => { - var succ = Commands.SaveChangesAsPatch.ProcessRevisionCompareChanges(_repo, _changes, GetSHA(_startPoint), GetSHA(_endPoint), saveTo); + var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo, _changes, GetSHA(_startPoint), GetSHA(_endPoint), saveTo); if (succ) - Dispatcher.UIThread.Invoke(() => App.SendNotification(_repo, App.Text("SaveAsPatchSuccess"))); + App.SendNotification(_repo, App.Text("SaveAsPatchSuccess")); }); } @@ -150,7 +145,7 @@ namespace SourceGit.ViewModels var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, toolType, toolPath, opt)); + Task.Run(() => Commands.MergeTool.OpenForDiffAsync(_repo, toolType, toolPath, opt)); ev.Handled = true; }; menu.Items.Add(diffWithMerger); @@ -173,9 +168,9 @@ namespace SourceGit.ViewModels var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, ev) => + copyPath.Click += async (_, ev) => { - App.CopyText(change.Path); + await App.CopyTextAsync(change.Path); ev.Handled = true; }; menu.Items.Add(copyPath); @@ -183,9 +178,9 @@ namespace SourceGit.ViewModels var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyFullPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - App.CopyText(Native.OS.GetAbsPath(_repo, change.Path)); + await App.CopyTextAsync(Native.OS.GetAbsPath(_repo, change.Path)); e.Handled = true; }; menu.Items.Add(copyFullPath); @@ -217,7 +212,7 @@ namespace SourceGit.ViewModels private void Refresh() { - _changes = new Commands.CompareRevisions(_repo, GetSHA(_startPoint), GetSHA(_endPoint)).Result(); + _changes = new Commands.CompareRevisions(_repo, GetSHA(_startPoint), GetSHA(_endPoint)).ReadAsync().Result; var visible = _changes; if (!string.IsNullOrWhiteSpace(_searchFilter)) @@ -230,7 +225,15 @@ namespace SourceGit.ViewModels } } - Dispatcher.UIThread.Invoke(() => VisibleChanges = visible); + Dispatcher.UIThread.Post(() => + { + VisibleChanges = visible; + + if (VisibleChanges.Count > 0) + SelectedChanges = [VisibleChanges[0]]; + else + SelectedChanges = []; + }); } private string GetSHA(object obj) diff --git a/src/ViewModels/RevisionFileTreeNode.cs b/src/ViewModels/RevisionFileTreeNode.cs index 083e9d33..2f94d40d 100644 --- a/src/ViewModels/RevisionFileTreeNode.cs +++ b/src/ViewModels/RevisionFileTreeNode.cs @@ -18,7 +18,7 @@ namespace SourceGit.ViewModels public bool IsFolder { - get => Backend != null && Backend.Type == Models.ObjectType.Tree; + get => Backend?.Type == Models.ObjectType.Tree; } public bool IsExpanded diff --git a/src/ViewModels/RevisionLFSImage.cs b/src/ViewModels/RevisionLFSImage.cs index e0b9a348..2cdd8e66 100644 --- a/src/ViewModels/RevisionLFSImage.cs +++ b/src/ViewModels/RevisionLFSImage.cs @@ -21,9 +21,9 @@ namespace SourceGit.ViewModels { LFS = new Models.RevisionLFSObject() { Object = lfs }; - Task.Run(() => + Task.Run(async () => { - var source = ImageSource.FromLFSObject(repo, lfs, decoder); + var source = await ImageSource.FromLFSObjectAsync(repo, lfs, decoder).ConfigureAwait(false); var img = new Models.RevisionImageFile(file, source.Bitmap, source.Size); Dispatcher.UIThread.Invoke(() => Image = img); }); diff --git a/src/ViewModels/Reword.cs b/src/ViewModels/Reword.cs index 8517c1bf..f1a5b2bb 100644 --- a/src/ViewModels/Reword.cs +++ b/src/ViewModels/Reword.cs @@ -21,15 +21,15 @@ namespace SourceGit.ViewModels public Reword(Repository repo, Models.Commit head) { _repo = repo; - _oldMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, head.SHA).Result(); + _oldMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, head.SHA).GetResultAsync().Result; _message = _oldMessage; Head = head; } - public override Task Sure() + public override async Task Sure() { if (string.Compare(_message, _oldMessage, StringComparison.Ordinal) == 0) - return null; + return true; _repo.SetWatcherEnabled(false); ProgressDescription = "Editing head commit message ..."; @@ -38,14 +38,13 @@ namespace SourceGit.ViewModels Use(log); var signOff = _repo.Settings.EnableSignOffForCommit; - return Task.Run(() => - { - // For reword (only changes the commit message), disable `--reset-author` - var succ = new Commands.Commit(_repo.FullPath, _message, signOff, true, false).Use(log).Run(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + var succ = await new Commands.Commit(_repo.FullPath, _message, signOff, true, false) + .Use(log) + .RunAsync(); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo; diff --git a/src/ViewModels/ScanRepositories.cs b/src/ViewModels/ScanRepositories.cs index 6df2843b..e90e711f 100644 --- a/src/ViewModels/ScanRepositories.cs +++ b/src/ViewModels/ScanRepositories.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Diagnostics; using System.IO; using System.Threading.Tasks; -using Avalonia.Threading; - namespace SourceGit.ViewModels { public class ScanRepositories : Popup @@ -40,56 +37,43 @@ namespace SourceGit.ViewModels GetManagedRepositories(Preferences.Instance.RepositoryNodes, _managed); } - public override Task Sure() + public override async Task Sure() { ProgressDescription = $"Scan repositories under '{_selected.Path}' ..."; - return Task.Run(() => + var minDelay = Task.Delay(500); + var rootDir = new DirectoryInfo(_selected.Path); + var found = new List(); + + await GetUnmanagedRepositoriesAsync(rootDir, found, new EnumerationOptions() { - var watch = new Stopwatch(); - watch.Start(); - - var rootDir = new DirectoryInfo(_selected.Path); - var found = new List(); - GetUnmanagedRepositories(rootDir, found, new EnumerationOptions() - { - AttributesToSkip = FileAttributes.Hidden | FileAttributes.System, - IgnoreInaccessible = true, - }); - - // Make sure this task takes at least 0.5s to avoid that the popup panel do not disappear very quickly. - var remain = 500 - (int)watch.Elapsed.TotalMilliseconds; - watch.Stop(); - if (remain > 0) - Task.Delay(remain).Wait(); - - Dispatcher.UIThread.Invoke(() => - { - var normalizedRoot = rootDir.FullName.Replace('\\', '/').TrimEnd('/'); - - foreach (var f in found) - { - var parent = new DirectoryInfo(f).Parent!.FullName.Replace('\\', '/').TrimEnd('/'); - if (parent.Equals(normalizedRoot, StringComparison.Ordinal)) - { - Preferences.Instance.FindOrAddNodeByRepositoryPath(f, null, false, false); - } - else if (parent.StartsWith(normalizedRoot, StringComparison.Ordinal)) - { - var relative = parent.Substring(normalizedRoot.Length).TrimStart('/'); - var group = FindOrCreateGroupRecursive(Preferences.Instance.RepositoryNodes, relative); - Preferences.Instance.FindOrAddNodeByRepositoryPath(f, group, false, false); - } - } - - Preferences.Instance.AutoRemoveInvalidNode(); - Preferences.Instance.Save(); - - Welcome.Instance.Refresh(); - }); - - return true; + AttributesToSkip = FileAttributes.Hidden | FileAttributes.System, + IgnoreInaccessible = true, }); + + // Make sure this task takes at least 0.5s to avoid that the popup panel do not disappear very quickly. + await minDelay; + + var normalizedRoot = rootDir.FullName.Replace('\\', '/').TrimEnd('/'); + foreach (var f in found) + { + var parent = new DirectoryInfo(f).Parent!.FullName.Replace('\\', '/').TrimEnd('/'); + if (parent.Equals(normalizedRoot, StringComparison.Ordinal)) + { + Preferences.Instance.FindOrAddNodeByRepositoryPath(f, null, false, false); + } + else if (parent.StartsWith(normalizedRoot, StringComparison.Ordinal)) + { + var relative = parent.Substring(normalizedRoot.Length).TrimStart('/'); + var group = FindOrCreateGroupRecursive(Preferences.Instance.RepositoryNodes, relative); + Preferences.Instance.FindOrAddNodeByRepositoryPath(f, group, false, false); + } + } + + Preferences.Instance.AutoRemoveInvalidNode(); + Preferences.Instance.Save(); + Welcome.Instance.Refresh(); + return true; } private void GetManagedRepositories(List group, HashSet repos) @@ -103,7 +87,7 @@ namespace SourceGit.ViewModels } } - private void GetUnmanagedRepositories(DirectoryInfo dir, List outs, EnumerationOptions opts, int depth = 0) + private async Task GetUnmanagedRepositoriesAsync(DirectoryInfo dir, List outs, EnumerationOptions opts, int depth = 0) { var subdirs = dir.GetDirectories("*", opts); foreach (var subdir in subdirs) @@ -112,7 +96,7 @@ namespace SourceGit.ViewModels subdir.Name.Equals("node_modules", StringComparison.Ordinal)) continue; - CallUIThread(() => ProgressDescription = $"Scanning {subdir.FullName}..."); + ProgressDescription = $"Scanning {subdir.FullName}..."; var normalizedSelf = subdir.FullName.Replace('\\', '/').TrimEnd('/'); if (_managed.Contains(normalizedSelf)) @@ -121,7 +105,7 @@ namespace SourceGit.ViewModels var gitDir = Path.Combine(subdir.FullName, ".git"); if (Directory.Exists(gitDir) || File.Exists(gitDir)) { - var test = new Commands.QueryRepositoryRootPath(subdir.FullName).ReadToEnd(); + var test = await new Commands.QueryRepositoryRootPath(subdir.FullName).GetResultAsync().ConfigureAwait(false); if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut)) { var normalized = test.StdOut.Trim().Replace('\\', '/').TrimEnd('/'); @@ -132,7 +116,7 @@ namespace SourceGit.ViewModels continue; } - var isBare = new Commands.IsBareRepository(subdir.FullName).Result(); + var isBare = await new Commands.IsBareRepository(subdir.FullName).GetResultAsync().ConfigureAwait(false); if (isBare) { outs.Add(normalizedSelf); @@ -140,7 +124,7 @@ namespace SourceGit.ViewModels } if (depth < 5) - GetUnmanagedRepositories(subdir, outs, opts, depth + 1); + await GetUnmanagedRepositoriesAsync(subdir, outs, opts, depth + 1); } } diff --git a/src/ViewModels/SetSubmoduleBranch.cs b/src/ViewModels/SetSubmoduleBranch.cs new file mode 100644 index 00000000..5abeec87 --- /dev/null +++ b/src/ViewModels/SetSubmoduleBranch.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class SetSubmoduleBranch : Popup + { + public Models.Submodule Submodule + { + get; + } + + public string ChangeTo + { + get => _changeTo; + set => SetProperty(ref _changeTo, value); + } + + public SetSubmoduleBranch(Repository repo, Models.Submodule submodule) + { + _repo = repo; + _changeTo = submodule.Branch; + Submodule = submodule; + } + + public override async Task Sure() + { + ProgressDescription = "Set submodule's branch ..."; + + if (_changeTo.Equals(Submodule.Branch, StringComparison.Ordinal)) + return true; + + var log = _repo.CreateLog("Set Submodule's Branch"); + _repo.SetWatcherEnabled(false); + Use(log); + + var succ = await new Commands.Submodule(_repo.FullPath) + .Use(log) + .SetBranchAsync(Submodule.Path, _changeTo); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; + } + + private readonly Repository _repo; + private string _changeTo; + } +} diff --git a/src/ViewModels/SetUpstream.cs b/src/ViewModels/SetUpstream.cs index a51586e6..c6208dbd 100644 --- a/src/ViewModels/SetUpstream.cs +++ b/src/ViewModels/SetUpstream.cs @@ -50,26 +50,23 @@ namespace SourceGit.ViewModels } } - public override Task Sure() + public override async Task Sure() { ProgressDescription = "Setting upstream..."; var upstream = (_unset || SelectedRemoteBranch == null) ? string.Empty : SelectedRemoteBranch.FullName; if (upstream == Local.Upstream) - return null; + return true; var log = _repo.CreateLog("Set Upstream"); Use(log); - return Task.Run(() => - { - var succ = Commands.Branch.SetUpstream(_repo.FullPath, Local.Name, upstream.Replace("refs/remotes/", ""), log); - if (succ) - _repo.RefreshBranches(); + var succ = await Commands.Branch.SetUpstreamAsync(_repo.FullPath, Local.Name, upstream.Replace("refs/remotes/", ""), log); + log.Complete(); - log.Complete(); - return true; - }); + if (succ) + _repo.MarkBranchesDirtyManually(); + return true; } private readonly Repository _repo; diff --git a/src/ViewModels/Squash.cs b/src/ViewModels/Squash.cs index 9574beb0..515290b8 100644 --- a/src/ViewModels/Squash.cs +++ b/src/ViewModels/Squash.cs @@ -20,11 +20,11 @@ namespace SourceGit.ViewModels public Squash(Repository repo, Models.Commit target, string shaToGetPreferMessage) { _repo = repo; - _message = new Commands.QueryCommitFullMessage(_repo.FullPath, shaToGetPreferMessage).Result(); + _message = new Commands.QueryCommitFullMessage(_repo.FullPath, shaToGetPreferMessage).GetResultAsync().Result; Target = target; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Squashing ..."; @@ -32,36 +32,43 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Squash"); Use(log); - return Task.Run(() => + var signOff = _repo.Settings.EnableSignOffForCommit; + var autoStashed = false; + bool succ; + + if (_repo.LocalChangesCount > 0) { - var signOff = _repo.Settings.EnableSignOffForCommit; - var autoStashed = false; - bool succ; + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushAsync("SQUASH_AUTO_STASH"); - if (_repo.LocalChangesCount > 0) + if (!succ) { - succ = new Commands.Stash(_repo.FullPath).Use(log).Push("SQUASH_AUTO_STASH"); - if (!succ) - { - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return false; - } - - autoStashed = true; + log.Complete(); + _repo.SetWatcherEnabled(true); + return false; } - succ = new Commands.Reset(_repo.FullPath, Target.SHA, "--soft").Use(log).Exec(); - if (succ) - succ = new Commands.Commit(_repo.FullPath, _message, signOff, true, false).Use(log).Run(); + autoStashed = true; + } - if (succ && autoStashed) - new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); + succ = await new Commands.Reset(_repo.FullPath, Target.SHA, "--soft") + .Use(log) + .ExecAsync(); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; - }); + if (succ) + succ = await new Commands.Commit(_repo.FullPath, _message, signOff, true, false) + .Use(log) + .RunAsync(); + + if (succ && autoStashed) + await new Commands.Stash(_repo.FullPath) + .Use(log) + .PopAsync("stash@{0}"); + + log.Complete(); + _repo.SetWatcherEnabled(true); + return succ; } private readonly Repository _repo; diff --git a/src/ViewModels/StashChanges.cs b/src/ViewModels/StashChanges.cs index 0fb243f5..62e942f7 100644 --- a/src/ViewModels/StashChanges.cs +++ b/src/ViewModels/StashChanges.cs @@ -50,7 +50,7 @@ namespace SourceGit.ViewModels HasSelectedFiles = hasSelectedFiles; } - public override Task Sure() + public override async Task Sure() { _repo.SetWatcherEnabled(false); ProgressDescription = "Stash changes ..."; @@ -58,57 +58,56 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Stash Local Changes"); Use(log); - return Task.Run(() => + var mode = (DealWithChangesAfterStashing)ChangesAfterStashing; + var keepIndex = mode == DealWithChangesAfterStashing.KeepIndex; + bool succ; + + if (!HasSelectedFiles) { - var mode = (DealWithChangesAfterStashing)ChangesAfterStashing; - var keepIndex = mode == DealWithChangesAfterStashing.KeepIndex; - bool succ; - - if (!HasSelectedFiles) + if (OnlyStaged) { - if (OnlyStaged) + if (Native.OS.GitVersion >= Models.GitVersions.STASH_PUSH_ONLY_STAGED) { - if (Native.OS.GitVersion >= Models.GitVersions.STASH_PUSH_ONLY_STAGED) - { - succ = new Commands.Stash(_repo.FullPath).Use(log).PushOnlyStaged(Message, keepIndex); - } - else - { - var staged = new List(); - foreach (var c in _changes) - { - if (c.Index != Models.ChangeState.None && c.Index != Models.ChangeState.Untracked) - staged.Add(c); - } - - succ = StashWithChanges(staged, keepIndex, log); - } + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushOnlyStagedAsync(Message, keepIndex); } else { - succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, IncludeUntracked, keepIndex); + var staged = new List(); + foreach (var c in _changes) + { + if (c.Index != Models.ChangeState.None && c.Index != Models.ChangeState.Untracked) + staged.Add(c); + } + + succ = await StashWithChangesAsync(staged, keepIndex, log); } } else { - succ = StashWithChanges(_changes, keepIndex, log); + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushAsync(Message, IncludeUntracked, keepIndex); } + } + else + { + succ = await StashWithChangesAsync(_changes, keepIndex, log); + } - if (mode == DealWithChangesAfterStashing.KeepAll && succ) - succ = new Commands.Stash(_repo.FullPath).Use(log).Apply("stash@{0}", true); + if (mode == DealWithChangesAfterStashing.KeepAll && succ) + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .ApplyAsync("stash@{0}", true); - log.Complete(); - CallUIThread(() => - { - _repo.MarkWorkingCopyDirtyManually(); - _repo.SetWatcherEnabled(true); - }); - - return succ; - }); + log.Complete(); + _repo.MarkWorkingCopyDirtyManually(); + _repo.SetWatcherEnabled(true); + return succ; } - private bool StashWithChanges(List changes, bool keepIndex, CommandLog log) + private async Task StashWithChangesAsync(List changes, bool keepIndex, CommandLog log) { if (changes.Count == 0) return true; @@ -121,8 +120,11 @@ namespace SourceGit.ViewModels paths.Add(c.Path); var pathSpecFile = Path.GetTempFileName(); - File.WriteAllLines(pathSpecFile, paths); - succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, pathSpecFile, keepIndex); + await File.WriteAllLinesAsync(pathSpecFile, paths); + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushAsync(Message, pathSpecFile, keepIndex) + .ConfigureAwait(false); File.Delete(pathSpecFile); } else @@ -131,7 +133,10 @@ namespace SourceGit.ViewModels { var count = Math.Min(32, changes.Count - i); var step = changes.GetRange(i, count); - succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, step, keepIndex); + succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .PushAsync(Message, step, keepIndex) + .ConfigureAwait(false); if (!succ) break; } diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index 431f61e6..365c788e 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -57,24 +57,26 @@ namespace SourceGit.ViewModels } else { - Task.Run(() => + Task.Run(async () => { - var changes = new Commands.CompareRevisions(_repo.FullPath, $"{value.SHA}^", value.SHA).Result(); - var untracked = new List(); + var changes = await new Commands.CompareRevisions(_repo.FullPath, $"{value.SHA}^", value.SHA) + .ReadAsync() + .ConfigureAwait(false); + var untracked = new List(); if (value.Parents.Count == 3) { - untracked = new Commands.CompareRevisions(_repo.FullPath, Models.Commit.EmptyTreeSHA1, value.Parents[2]).Result(); + untracked = await new Commands.CompareRevisions(_repo.FullPath, Models.Commit.EmptyTreeSHA1, value.Parents[2]) + .ReadAsync() + .ConfigureAwait(false); + var needSort = changes.Count > 0 && untracked.Count > 0; - - foreach (var c in untracked) - changes.Add(c); - + changes.AddRange(untracked); if (needSort) changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); } - Dispatcher.UIThread.Invoke(() => + Dispatcher.UIThread.Post(() => { _untracked = untracked; Changes = changes; @@ -91,23 +93,23 @@ namespace SourceGit.ViewModels private set { if (SetProperty(ref _changes, value)) - SelectedChange = value is { Count: > 0 } ? value[0] : null; + SelectedChanges = value is { Count: > 0 } ? [value[0]] : []; } } - public Models.Change SelectedChange + public List SelectedChanges { - get => _selectedChange; + get => _selectedChanges; set { - if (SetProperty(ref _selectedChange, value)) + if (SetProperty(ref _selectedChanges, value)) { - if (value == null) + if (value is not { Count: 1 }) DiffContext = null; - else if (_untracked.Contains(value)) - DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], value), _diffContext); + else if (_untracked.Contains(value[0])) + DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], value[0]), _diffContext); else - DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, value), _diffContext); + DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, value[0]), _diffContext); } } } @@ -127,11 +129,11 @@ namespace SourceGit.ViewModels { _stashes?.Clear(); _changes?.Clear(); + _selectedChanges?.Clear(); _untracked.Clear(); _repo = null; _selectedStash = null; - _selectedChange = null; _diffContext = null; } @@ -188,7 +190,7 @@ namespace SourceGit.ViewModels opts.Add(new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, c)); } - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessStashChanges(_repo.FullPath, opts, storageFile.Path.LocalPath)); + var succ = await Commands.SaveChangesAsPatch.ProcessStashChangesAsync(_repo.FullPath, opts, storageFile.Path.LocalPath); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -199,9 +201,9 @@ namespace SourceGit.ViewModels var copy = new MenuItem(); copy.Header = App.Text("StashCM.CopyMessage"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, ev) => + copy.Click += async (_, ev) => { - App.CopyText(stash.Message); + await App.CopyTextAsync(stash.Message); ev.Handled = true; }; @@ -215,11 +217,12 @@ namespace SourceGit.ViewModels return menu; } - public ContextMenu MakeContextMenuForChange(Models.Change change) + public ContextMenu MakeContextMenuForChange() { - if (change == null) + if (_selectedChanges is not { Count: 1 }) return null; + var change = _selectedChanges[0]; var diffWithMerger = new MenuItem(); diffWithMerger.Header = App.Text("DiffWithMerger"); diffWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); @@ -229,7 +232,7 @@ namespace SourceGit.ViewModels var toolPath = Preferences.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption($"{_selectedStash.SHA}^", _selectedStash.SHA, change); - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo.FullPath, toolType, toolPath, opt)); + Task.Run(() => Commands.MergeTool.OpenForDiffAsync(_repo.FullPath, toolType, toolPath, opt)); ev.Handled = true; }; @@ -251,23 +254,20 @@ namespace SourceGit.ViewModels { var log = _repo.CreateLog($"Reset File to '{_selectedStash.SHA}'"); - await Task.Run(() => + if (_untracked.Contains(change)) { - if (_untracked.Contains(change)) - { - Commands.SaveRevisionFile.Run(_repo.FullPath, _selectedStash.Parents[2], change.Path, fullPath); - } - else if (change.Index == Models.ChangeState.Added) - { - Commands.SaveRevisionFile.Run(_repo.FullPath, _selectedStash.SHA, change.Path, fullPath); - } - else - { - new Commands.Checkout(_repo.FullPath) - .Use(log) - .FileWithRevision(change.Path, $"{_selectedStash.SHA}"); - } - }); + await Commands.SaveRevisionFile.RunAsync(_repo.FullPath, _selectedStash.Parents[2], change.Path, fullPath); + } + else if (change.Index == Models.ChangeState.Added) + { + await Commands.SaveRevisionFile.RunAsync(_repo.FullPath, _selectedStash.SHA, change.Path, fullPath); + } + else + { + await new Commands.Checkout(_repo.FullPath) + .Use(log) + .FileWithRevisionAsync(change.Path, $"{_selectedStash.SHA}"); + } log.Complete(); ev.Handled = true; @@ -276,18 +276,18 @@ namespace SourceGit.ViewModels var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, ev) => + copyPath.Click += async (_, ev) => { - App.CopyText(change.Path); + await App.CopyTextAsync(change.Path); ev.Handled = true; }; var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyFullPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, change.Path)); + await App.CopyTextAsync(Native.OS.GetAbsPath(_repo.FullPath, change.Path)); e.Handled = true; }; @@ -340,7 +340,7 @@ namespace SourceGit.ViewModels private Models.Stash _selectedStash = null; private List _changes = null; private List _untracked = []; - private Models.Change _selectedChange = null; + private List _selectedChanges = []; private DiffContext _diffContext = null; } } diff --git a/src/ViewModels/Statistics.cs b/src/ViewModels/Statistics.cs index 3a87607e..a16c94ec 100644 --- a/src/ViewModels/Statistics.cs +++ b/src/ViewModels/Statistics.cs @@ -56,10 +56,10 @@ namespace SourceGit.ViewModels public Statistics(string repo) { - Task.Run(() => + Task.Run(async () => { - var result = new Commands.Statistics(repo, Preferences.Instance.MaxHistoryCommits).Result(); - Dispatcher.UIThread.Invoke(() => + var result = await new Commands.Statistics(repo, Preferences.Instance.MaxHistoryCommits).ReadAsync().ConfigureAwait(false); + Dispatcher.UIThread.Post(() => { _data = result; RefreshReport(); diff --git a/src/ViewModels/UpdateSubmodules.cs b/src/ViewModels/UpdateSubmodules.cs index df2d5565..78bb1feb 100644 --- a/src/ViewModels/UpdateSubmodules.cs +++ b/src/ViewModels/UpdateSubmodules.cs @@ -5,12 +5,17 @@ namespace SourceGit.ViewModels { public class UpdateSubmodules : Popup { - public List Submodules + public bool HasPreSelectedSubmodule { get; - } = []; + } - public string SelectedSubmodule + public List Submodules + { + get => _repo.Submodules; + } + + public Models.Submodule SelectedSubmodule { get; set; @@ -40,39 +45,51 @@ namespace SourceGit.ViewModels set; } = false; - public UpdateSubmodules(Repository repo) + public UpdateSubmodules(Repository repo, Models.Submodule selected) { _repo = repo; - foreach (var submodule in _repo.Submodules) - Submodules.Add(submodule.Path); - - SelectedSubmodule = Submodules.Count > 0 ? Submodules[0] : string.Empty; + if (selected != null) + { + _updateAll = false; + SelectedSubmodule = selected; + EnableInit = selected.Status == Models.SubmoduleStatus.NotInited; + HasPreSelectedSubmodule = true; + } + else if (repo.Submodules.Count > 0) + { + SelectedSubmodule = repo.Submodules[0]; + HasPreSelectedSubmodule = false; + } } - public override Task Sure() + public override async Task Sure() { - _repo.SetWatcherEnabled(false); - - List targets; + var targets = new List(); if (_updateAll) - targets = Submodules; - else - targets = [SelectedSubmodule]; + { + foreach (var submodule in Submodules) + targets.Add(submodule.Path); + } + else if (SelectedSubmodule != null) + { + targets.Add(SelectedSubmodule.Path); + } + + if (targets.Count == 0) + return true; var log = _repo.CreateLog("Update Submodule"); + _repo.SetWatcherEnabled(false); Use(log); - return Task.Run(() => - { - new Commands.Submodule(_repo.FullPath) - .Use(log) - .Update(targets, EnableInit, EnableRecursive, EnableRemote); + await new Commands.Submodule(_repo.FullPath) + .Use(log) + .UpdateAsync(targets, EnableInit, EnableRecursive, EnableRemote); - log.Complete(); - CallUIThread(() => _repo.SetWatcherEnabled(true)); - return true; - }); + log.Complete(); + _repo.SetWatcherEnabled(true); + return true; } private readonly Repository _repo = null; diff --git a/src/ViewModels/Welcome.cs b/src/ViewModels/Welcome.cs index 4b6dc7d4..7ee2c80c 100644 --- a/src/ViewModels/Welcome.cs +++ b/src/ViewModels/Welcome.cs @@ -93,11 +93,11 @@ namespace SourceGit.ViewModels return; } - var isBare = new Commands.IsBareRepository(path).Result(); + var isBare = new Commands.IsBareRepository(path).GetResultAsync().Result; var repoRoot = path; if (!isBare) { - var test = new Commands.QueryRepositoryRootPath(path).ReadToEnd(); + var test = new Commands.QueryRepositoryRootPath(path).GetResultAsync().Result; if (!test.IsSuccess || string.IsNullOrEmpty(test.StdOut)) { InitRepository(path, parent, test.StdErr); @@ -276,7 +276,7 @@ namespace SourceGit.ViewModels var move = new MenuItem(); move.Header = App.Text("Welcome.Move"); - move.Icon = App.CreateMenuIcon("Icons.MoveToAnotherGroup"); + move.Icon = App.CreateMenuIcon("Icons.MoveTo"); move.Click += (_, e) => { var activePage = App.GetLauncher().ActivePage; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 1b56e213..0f10f3fa 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -91,7 +91,9 @@ namespace SourceGit.ViewModels return; } - CommitMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, currentBranch.Head).Result(); + CommitMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, currentBranch.Head) + .GetResultAsync() + .Result; } else { @@ -167,7 +169,7 @@ namespace SourceGit.ViewModels } else { - if (_selectedStaged != null && _selectedStaged.Count > 0) + if (_selectedStaged is { Count: > 0 }) SelectedStaged = []; if (value.Count == 1) @@ -193,7 +195,7 @@ namespace SourceGit.ViewModels } else { - if (_selectedUnstaged != null && _selectedUnstaged.Count > 0) + if (_selectedUnstaged is { Count: > 0 }) SelectedUnstaged = []; if (value.Count == 1) @@ -269,12 +271,12 @@ namespace SourceGit.ViewModels var lastSelectedUnstaged = new HashSet(); var lastSelectedStaged = new HashSet(); - if (_selectedUnstaged != null && _selectedUnstaged.Count > 0) + if (_selectedUnstaged is { Count: > 0 }) { foreach (var c in _selectedUnstaged) lastSelectedUnstaged.Add(c.Path); } - else if (_selectedStaged != null && _selectedStaged.Count > 0) + else if (_selectedStaged is { Count: > 0 }) { foreach (var c in _selectedStaged) lastSelectedStaged.Add(c.Path); @@ -332,11 +334,6 @@ namespace SourceGit.ViewModels UseExternalMergeTool(null); } - public void OpenAssumeUnchanged() - { - App.ShowWindow(new AssumeUnchangedManager(_repo), true); - } - public void StashAll(bool autoStart) { if (!_repo.CanCreatePopup()) @@ -392,9 +389,7 @@ namespace SourceGit.ViewModels if (!change.IsConflicted) continue; - if (change.ConflictReason == Models.ConflictReason.BothDeleted || - change.ConflictReason == Models.ConflictReason.DeletedByThem || - change.ConflictReason == Models.ConflictReason.AddedByUs) + if (change.ConflictReason is Models.ConflictReason.BothDeleted or Models.ConflictReason.DeletedByThem or Models.ConflictReason.AddedByUs) { var fullpath = Path.Combine(_repo.FullPath, change.Path); if (File.Exists(fullpath)) @@ -410,7 +405,7 @@ namespace SourceGit.ViewModels if (files.Count > 0) { - var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).Use(log).UseTheirs(files)); + var succ = await new Commands.Checkout(_repo.FullPath).Use(log).UseTheirsAsync(files); if (succ) needStage.AddRange(files); } @@ -419,7 +414,7 @@ namespace SourceGit.ViewModels { var pathSpecFile = Path.GetTempFileName(); await File.WriteAllLinesAsync(pathSpecFile, needStage); - await Task.Run(() => new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).Exec()); + await new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).ExecAsync(); File.Delete(pathSpecFile); } @@ -441,9 +436,7 @@ namespace SourceGit.ViewModels if (!change.IsConflicted) continue; - if (change.ConflictReason == Models.ConflictReason.BothDeleted || - change.ConflictReason == Models.ConflictReason.DeletedByUs || - change.ConflictReason == Models.ConflictReason.AddedByThem) + if (change.ConflictReason is Models.ConflictReason.BothDeleted or Models.ConflictReason.DeletedByUs or Models.ConflictReason.AddedByThem) { var fullpath = Path.Combine(_repo.FullPath, change.Path); if (File.Exists(fullpath)) @@ -459,7 +452,7 @@ namespace SourceGit.ViewModels if (files.Count > 0) { - var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).Use(log).UseMine(files)); + var succ = await new Commands.Checkout(_repo.FullPath).Use(log).UseMineAsync(files); if (succ) needStage.AddRange(files); } @@ -468,7 +461,7 @@ namespace SourceGit.ViewModels { var pathSpecFile = Path.GetTempFileName(); await File.WriteAllLinesAsync(pathSpecFile, needStage); - await Task.Run(() => new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).Exec()); + await new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).ExecAsync(); File.Delete(pathSpecFile); } @@ -482,7 +475,7 @@ namespace SourceGit.ViewModels var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; var file = change?.Path; // NOTE: With no arg, mergetool runs on every file with merge conflicts! - await Task.Run(() => Commands.MergeTool.OpenForMerge(_repo.FullPath, toolType, toolPath, file)); + await Commands.MergeTool.OpenForMergeAsync(_repo.FullPath, toolType, toolPath, file); } public void ContinueMerge() @@ -492,14 +485,14 @@ namespace SourceGit.ViewModels if (_inProgressContext != null) { _repo.SetWatcherEnabled(false); - Task.Run(() => + Task.Run(async () => { var mergeMsgFile = Path.Combine(_repo.GitDir, "MERGE_MSG"); if (File.Exists(mergeMsgFile) && !string.IsNullOrWhiteSpace(_commitMessage)) - File.WriteAllText(mergeMsgFile, _commitMessage); + await File.WriteAllTextAsync(mergeMsgFile, _commitMessage); - var succ = _inProgressContext.Continue(); - Dispatcher.UIThread.Invoke(() => + var succ = await _inProgressContext.ContinueAsync(); + await Dispatcher.UIThread.InvokeAsync(() => { if (succ) CommitMessage = string.Empty; @@ -523,10 +516,10 @@ namespace SourceGit.ViewModels if (_inProgressContext != null) { _repo.SetWatcherEnabled(false); - Task.Run(() => + Task.Run(async () => { - var succ = _inProgressContext.Skip(); - Dispatcher.UIThread.Invoke(() => + var succ = await _inProgressContext.SkipAsync(); + await Dispatcher.UIThread.InvokeAsync(() => { if (succ) CommitMessage = string.Empty; @@ -550,10 +543,10 @@ namespace SourceGit.ViewModels if (_inProgressContext != null) { _repo.SetWatcherEnabled(false); - Task.Run(() => + Task.Run(async () => { - var succ = _inProgressContext.Abort(); - Dispatcher.UIThread.Invoke(() => + var succ = await _inProgressContext.AbortAsync(); + await Dispatcher.UIThread.InvokeAsync(() => { if (succ) CommitMessage = string.Empty; @@ -590,6 +583,7 @@ namespace SourceGit.ViewModels if (_selectedUnstaged == null || _selectedUnstaged.Count == 0) return null; + var hasSelectedFolder = !string.IsNullOrEmpty(selectedSingleFolder); var menu = new ContextMenu(); if (_selectedUnstaged.Count == 1) { @@ -602,11 +596,8 @@ namespace SourceGit.ViewModels explore.IsEnabled = File.Exists(path) || Directory.Exists(path); explore.Click += (_, e) => { - if (string.IsNullOrEmpty(selectedSingleFolder)) - Native.OS.OpenInFileManager(path, true); - else - Native.OS.OpenInFileManager(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder), true); - + var target = hasSelectedFolder ? Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder) : path; + Native.OS.OpenInFileManager(target, true); e.Handled = true; }; menu.Items.Add(explore); @@ -627,7 +618,6 @@ namespace SourceGit.ViewModels { var useTheirs = new MenuItem(); useTheirs.Icon = App.CreateMenuIcon("Icons.Incoming"); - useTheirs.Header = App.Text("FileCM.UseTheirs"); useTheirs.Click += (_, e) => { UseTheirs(_selectedUnstaged); @@ -636,7 +626,6 @@ namespace SourceGit.ViewModels var useMine = new MenuItem(); useMine.Icon = App.CreateMenuIcon("Icons.Local"); - useMine.Header = App.Text("FileCM.UseMine"); useMine.Click += (_, e) => { UseMine(_selectedUnstaged); @@ -652,25 +641,28 @@ namespace SourceGit.ViewModels e.Handled = true; }; - if (_inProgressContext is CherryPickInProgress cherryPick) + switch (_inProgressContext) { - useTheirs.Header = App.Text("FileCM.ResolveUsing", cherryPick.HeadName); - useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); - } - else if (_inProgressContext is RebaseInProgress rebase) - { - useTheirs.Header = App.Text("FileCM.ResolveUsing", rebase.HeadName); - useMine.Header = App.Text("FileCM.ResolveUsing", rebase.BaseName); - } - else if (_inProgressContext is RevertInProgress revert) - { - useTheirs.Header = App.Text("FileCM.ResolveUsing", $"{revert.Head.SHA.AsSpan(0, 10)} (revert)"); - useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); - } - else if (_inProgressContext is MergeInProgress merge) - { - useTheirs.Header = App.Text("FileCM.ResolveUsing", merge.SourceName); - useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + case CherryPickInProgress cherryPick: + useTheirs.Header = App.Text("FileCM.ResolveUsing", cherryPick.HeadName); + useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + break; + case RebaseInProgress rebase: + useTheirs.Header = App.Text("FileCM.ResolveUsing", rebase.HeadName); + useMine.Header = App.Text("FileCM.ResolveUsing", rebase.BaseName); + break; + case RevertInProgress revert: + useTheirs.Header = App.Text("FileCM.ResolveUsing", $"{revert.Head.SHA.AsSpan(0, 10)} (revert)"); + useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + break; + case MergeInProgress merge: + useTheirs.Header = App.Text("FileCM.ResolveUsing", merge.SourceName); + useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + break; + default: + useTheirs.Header = App.Text("FileCM.UseTheirs"); + useMine.Header = App.Text("FileCM.UseMine"); + break; } menu.Items.Add(useTheirs); @@ -727,7 +719,7 @@ namespace SourceGit.ViewModels var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath)); + var succ = await Commands.SaveChangesAsPatch.ProcessLocalChangesAsync(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -739,31 +731,20 @@ namespace SourceGit.ViewModels assumeUnchanged.Header = App.Text("FileCM.AssumeUnchanged"); assumeUnchanged.Icon = App.CreateMenuIcon("Icons.File.Ignore"); assumeUnchanged.IsVisible = change.WorkTree != Models.ChangeState.Untracked; - assumeUnchanged.Click += (_, e) => + assumeUnchanged.Click += async (_, e) => { var log = _repo.CreateLog("Assume File Unchanged"); - new Commands.AssumeUnchanged(_repo.FullPath, change.Path, true).Use(log).Exec(); + await new Commands.AssumeUnchanged(_repo.FullPath, change.Path, true).Use(log).ExecAsync(); log.Complete(); e.Handled = true; }; - var history = new MenuItem(); - history.Header = App.Text("FileHistory"); - history.Icon = App.CreateMenuIcon("Icons.Histories"); - history.Click += (_, e) => - { - App.ShowWindow(new FileHistories(_repo, change.Path), false); - e.Handled = true; - }; - menu.Items.Add(stage); menu.Items.Add(discard); menu.Items.Add(stash); menu.Items.Add(patch); menu.Items.Add(assumeUnchanged); menu.Items.Add(new MenuItem() { Header = "-" }); - menu.Items.Add(history); - menu.Items.Add(new MenuItem() { Header = "-" }); var extension = Path.GetExtension(change.Path); var hasExtra = false; @@ -773,7 +754,7 @@ namespace SourceGit.ViewModels addToIgnore.Header = App.Text("WorkingCopy.AddToGitIgnore"); addToIgnore.Icon = App.CreateMenuIcon("Icons.GitIgnore"); - if (!string.IsNullOrEmpty(selectedSingleFolder)) + if (hasSelectedFolder) { var ignoreFolder = new MenuItem(); ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder"); @@ -827,24 +808,21 @@ namespace SourceGit.ViewModels menu.Items.Add(addToIgnore); hasExtra = true; } - else if (!string.IsNullOrEmpty(selectedSingleFolder)) + else if (hasSelectedFolder) { var addToIgnore = new MenuItem(); addToIgnore.Header = App.Text("WorkingCopy.AddToGitIgnore"); addToIgnore.Icon = App.CreateMenuIcon("Icons.GitIgnore"); - if (!string.IsNullOrEmpty(selectedSingleFolder)) + var ignoreFolder = new MenuItem(); + ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder"); + ignoreFolder.Click += (_, e) => { - var ignoreFolder = new MenuItem(); - ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder"); - ignoreFolder.Click += (_, e) => - { - if (_repo.CanCreatePopup()) - _repo.ShowPopup(new AddToIgnore(_repo, $"{selectedSingleFolder}/")); - e.Handled = true; - }; - addToIgnore.Items.Add(ignoreFolder); - } + if (_repo.CanCreatePopup()) + _repo.ShowPopup(new AddToIgnore(_repo, $"{selectedSingleFolder}/")); + e.Handled = true; + }; + addToIgnore.Items.Add(ignoreFolder); menu.Items.Add(addToIgnore); hasExtra = true; @@ -857,7 +835,7 @@ namespace SourceGit.ViewModels lfs.Header = App.Text("GitLFS"); lfs.Icon = App.CreateMenuIcon("Icons.LFS"); - var isLFSFiltered = new Commands.IsLFSFiltered(_repo.FullPath, change.Path).Result(); + var isLFSFiltered = new Commands.IsLFSFiltered(_repo.FullPath, change.Path).GetResultAsync().Result; if (!isLFSFiltered) { var filename = Path.GetFileName(change.Path); @@ -866,7 +844,7 @@ namespace SourceGit.ViewModels lfsTrackThisFile.Click += async (_, e) => { var log = _repo.CreateLog("Track LFS"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track(filename, true, log)); + var succ = await new Commands.LFS(_repo.FullPath).TrackAsync(filename, true, log); if (succ) App.SendNotification(_repo.FullPath, $"Tracking file named {filename} successfully!"); @@ -882,7 +860,7 @@ namespace SourceGit.ViewModels lfsTrackByExtension.Click += async (_, e) => { var log = _repo.CreateLog("Track LFS"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track($"*{extension}", false, log)); + var succ = await new Commands.LFS(_repo.FullPath).TrackAsync($"*{extension}", false, log); if (succ) App.SendNotification(_repo.FullPath, $"Tracking all *{extension} files successfully!"); @@ -904,7 +882,7 @@ namespace SourceGit.ViewModels lfsLock.Click += async (_, e) => { var log = _repo.CreateLog("Lock LFS File"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(_repo.Remotes[0].Name, change.Path, log)); + var succ = await new Commands.LFS(_repo.FullPath).LockAsync(_repo.Remotes[0].Name, change.Path, log); if (succ) App.SendNotification(_repo.FullPath, $"Lock file \"{change.Path}\" successfully!"); @@ -922,7 +900,7 @@ namespace SourceGit.ViewModels lockRemote.Click += async (_, e) => { var log = _repo.CreateLog("Lock LFS File"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(remoteName, change.Path, log)); + var succ = await new Commands.LFS(_repo.FullPath).LockAsync(remoteName, change.Path, log); if (succ) App.SendNotification(_repo.FullPath, $"Lock file \"{change.Path}\" successfully!"); @@ -943,7 +921,7 @@ namespace SourceGit.ViewModels lfsUnlock.Click += async (_, e) => { var log = _repo.CreateLog("Unlock LFS File"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(_repo.Remotes[0].Name, change.Path, false, log)); + var succ = await new Commands.LFS(_repo.FullPath).UnlockAsync(_repo.Remotes[0].Name, change.Path, false, log); if (succ) App.SendNotification(_repo.FullPath, $"Unlock file \"{change.Path}\" successfully!"); @@ -961,7 +939,7 @@ namespace SourceGit.ViewModels unlockRemote.Click += async (_, e) => { var log = _repo.CreateLog("Unlock LFS File"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(remoteName, change.Path, false, log)); + var succ = await new Commands.LFS(_repo.FullPath).UnlockAsync(remoteName, change.Path, false, log); if (succ) App.SendNotification(_repo.FullPath, $"Unlock file \"{change.Path}\" successfully!"); @@ -981,32 +959,40 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); } - var copy = new MenuItem(); - copy.Header = App.Text("CopyPath"); - copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + var history = new MenuItem(); + history.Header = App.Text(hasSelectedFolder ? "DirHistories" : "FileHistory"); + history.Icon = App.CreateMenuIcon("Icons.Histories"); + history.Click += (_, e) => { - if (string.IsNullOrEmpty(selectedSingleFolder)) - App.CopyText(change.Path); + if (hasSelectedFolder) + App.ShowWindow(new DirHistories(_repo, selectedSingleFolder)); else - App.CopyText(selectedSingleFolder); + App.ShowWindow(new FileHistories(_repo, change.Path)); e.Handled = true; }; - menu.Items.Add(copy); + + var copy = new MenuItem(); + copy.Header = App.Text("CopyPath"); + copy.Icon = App.CreateMenuIcon("Icons.Copy"); + copy.Click += async (_, e) => + { + await App.CopyTextAsync(hasSelectedFolder ? selectedSingleFolder : change.Path); + e.Handled = true; + }; var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyFullPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - if (string.IsNullOrEmpty(selectedSingleFolder)) - App.CopyText(path); - else - App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder)); - + await App.CopyTextAsync(hasSelectedFolder ? Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder) : path); e.Handled = true; }; + + menu.Items.Add(history); + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copy); menu.Items.Add(copyFullPath); } else @@ -1031,7 +1017,6 @@ namespace SourceGit.ViewModels var useTheirs = new MenuItem(); useTheirs.Icon = App.CreateMenuIcon("Icons.Incoming"); - useTheirs.Header = App.Text("FileCM.UseTheirs"); useTheirs.Click += (_, e) => { UseTheirs(_selectedUnstaged); @@ -1040,32 +1025,34 @@ namespace SourceGit.ViewModels var useMine = new MenuItem(); useMine.Icon = App.CreateMenuIcon("Icons.Local"); - useMine.Header = App.Text("FileCM.UseMine"); useMine.Click += (_, e) => { UseMine(_selectedUnstaged); e.Handled = true; }; - if (_inProgressContext is CherryPickInProgress cherryPick) + switch (_inProgressContext) { - useTheirs.Header = App.Text("FileCM.ResolveUsing", cherryPick.HeadName); - useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); - } - else if (_inProgressContext is RebaseInProgress rebase) - { - useTheirs.Header = App.Text("FileCM.ResolveUsing", rebase.HeadName); - useMine.Header = App.Text("FileCM.ResolveUsing", rebase.BaseName); - } - else if (_inProgressContext is RevertInProgress revert) - { - useTheirs.Header = App.Text("FileCM.ResolveUsing", $"{revert.Head.SHA.AsSpan(0, 10)} (revert)"); - useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); - } - else if (_inProgressContext is MergeInProgress merge) - { - useTheirs.Header = App.Text("FileCM.ResolveUsing", merge.SourceName); - useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + case CherryPickInProgress cherryPick: + useTheirs.Header = App.Text("FileCM.ResolveUsing", cherryPick.HeadName); + useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + break; + case RebaseInProgress rebase: + useTheirs.Header = App.Text("FileCM.ResolveUsing", rebase.HeadName); + useMine.Header = App.Text("FileCM.ResolveUsing", rebase.BaseName); + break; + case RevertInProgress revert: + useTheirs.Header = App.Text("FileCM.ResolveUsing", $"{revert.Head.SHA.AsSpan(0, 10)} (revert)"); + useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + break; + case MergeInProgress merge: + useTheirs.Header = App.Text("FileCM.ResolveUsing", merge.SourceName); + useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + break; + default: + useTheirs.Header = App.Text("FileCM.UseTheirs"); + useMine.Header = App.Text("FileCM.UseMine"); + break; } menu.Items.Add(useTheirs); @@ -1073,7 +1060,7 @@ namespace SourceGit.ViewModels return menu; } - if (!string.IsNullOrEmpty(selectedSingleFolder)) + if (hasSelectedFolder) { var dir = Path.Combine(_repo.FullPath, selectedSingleFolder); var explore = new MenuItem(); @@ -1135,7 +1122,7 @@ namespace SourceGit.ViewModels var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath)); + var succ = await Commands.SaveChangesAsPatch.ProcessLocalChangesAsync(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -1148,7 +1135,7 @@ namespace SourceGit.ViewModels menu.Items.Add(stash); menu.Items.Add(patch); - if (!string.IsNullOrEmpty(selectedSingleFolder)) + if (hasSelectedFolder) { var ignoreFolder = new MenuItem(); ignoreFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.InFolder"); @@ -1167,21 +1154,32 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(addToIgnore); + var history = new MenuItem(); + history.Header = App.Text("DirHistories"); + history.Icon = App.CreateMenuIcon("Icons.Histories"); + history.Click += (_, e) => + { + App.ShowWindow(new DirHistories(_repo, selectedSingleFolder)); + e.Handled = true; + }; + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(history); + var copy = new MenuItem(); copy.Header = App.Text("CopyPath"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, e) => + copy.Click += async (_, e) => { - App.CopyText(selectedSingleFolder); + await App.CopyTextAsync(selectedSingleFolder); e.Handled = true; }; var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder)); + await App.CopyTextAsync(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder)); e.Handled = true; }; menu.Items.Add(new MenuItem() { Header = "-" }); @@ -1200,7 +1198,7 @@ namespace SourceGit.ViewModels var menu = new ContextMenu(); - var ai = null as MenuItem; + MenuItem ai = null; var services = _repo.GetPreferredOpenAIServices(); if (services.Count > 0) { @@ -1210,9 +1208,9 @@ namespace SourceGit.ViewModels if (services.Count == 1) { - ai.Click += (_, e) => + ai.Click += async (_, e) => { - App.ShowWindow(new AIAssistant(_repo, services[0], _selectedStaged, t => CommitMessage = t), true); + await App.ShowDialog(new AIAssistant(_repo, services[0], _selectedStaged, t => CommitMessage = t)); e.Handled = true; }; } @@ -1224,9 +1222,9 @@ namespace SourceGit.ViewModels var item = new MenuItem(); item.Header = service.Name; - item.Click += (_, e) => + item.Click += async (_, e) => { - App.ShowWindow(new AIAssistant(_repo, dup, _selectedStaged, t => CommitMessage = t), true); + await App.ShowDialog(new AIAssistant(_repo, dup, _selectedStaged, t => CommitMessage = t)); e.Handled = true; }; @@ -1235,6 +1233,7 @@ namespace SourceGit.ViewModels } } + var hasSelectedFolder = !string.IsNullOrEmpty(selectedSingleFolder); if (_selectedStaged.Count == 1) { var change = _selectedStaged[0]; @@ -1246,11 +1245,8 @@ namespace SourceGit.ViewModels explore.Icon = App.CreateMenuIcon("Icons.Explore"); explore.Click += (_, e) => { - if (string.IsNullOrEmpty(selectedSingleFolder)) - Native.OS.OpenInFileManager(path, true); - else - Native.OS.OpenInFileManager(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder), true); - + var target = hasSelectedFolder ? Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder) : path; + Native.OS.OpenInFileManager(target, true); e.Handled = true; }; @@ -1301,7 +1297,7 @@ namespace SourceGit.ViewModels var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath)); + var succ = await Commands.SaveChangesAsPatch.ProcessLocalChangesAsync(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -1309,15 +1305,6 @@ namespace SourceGit.ViewModels e.Handled = true; }; - var history = new MenuItem(); - history.Header = App.Text("FileHistory"); - history.Icon = App.CreateMenuIcon("Icons.Histories"); - history.Click += (_, e) => - { - App.ShowWindow(new FileHistories(_repo, change.Path), false); - e.Handled = true; - }; - menu.Items.Add(explore); menu.Items.Add(openWith); menu.Items.Add(new MenuItem() { Header = "-" }); @@ -1325,8 +1312,6 @@ namespace SourceGit.ViewModels menu.Items.Add(stash); menu.Items.Add(patch); menu.Items.Add(new MenuItem() { Header = "-" }); - menu.Items.Add(history); - menu.Items.Add(new MenuItem() { Header = "-" }); var lfsEnabled = new Commands.LFS(_repo.FullPath).IsEnabled(); if (lfsEnabled) @@ -1344,7 +1329,7 @@ namespace SourceGit.ViewModels lfsLock.Click += async (_, e) => { var log = _repo.CreateLog("Lock LFS File"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(_repo.Remotes[0].Name, change.Path, log)); + var succ = await new Commands.LFS(_repo.FullPath).LockAsync(_repo.Remotes[0].Name, change.Path, log); if (succ) App.SendNotification(_repo.FullPath, $"Lock file \"{change.Path}\" successfully!"); @@ -1362,7 +1347,7 @@ namespace SourceGit.ViewModels lockRemote.Click += async (_, e) => { var log = _repo.CreateLog("Lock LFS File"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(remoteName, change.Path, log)); + var succ = await new Commands.LFS(_repo.FullPath).LockAsync(remoteName, change.Path, log); if (succ) App.SendNotification(_repo.FullPath, $"Lock file \"{change.Path}\" successfully!"); @@ -1383,7 +1368,7 @@ namespace SourceGit.ViewModels lfsUnlock.Click += async (_, e) => { var log = _repo.CreateLog("Unlock LFS File"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(_repo.Remotes[0].Name, change.Path, false, log)); + var succ = await new Commands.LFS(_repo.FullPath).UnlockAsync(_repo.Remotes[0].Name, change.Path, false, log); if (succ) App.SendNotification(_repo.FullPath, $"Unlock file \"{change.Path}\" successfully!"); @@ -1401,7 +1386,7 @@ namespace SourceGit.ViewModels unlockRemote.Click += async (_, e) => { var log = _repo.CreateLog("Unlock LFS File"); - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(remoteName, change.Path, false, log)); + var succ = await new Commands.LFS(_repo.FullPath).UnlockAsync(remoteName, change.Path, false, log); if (succ) App.SendNotification(_repo.FullPath, $"Unlock file \"{change.Path}\" successfully!"); @@ -1423,38 +1408,45 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); } + var history = new MenuItem(); + history.Header = App.Text(hasSelectedFolder ? "DirHistories" : "FileHistory"); + history.Icon = App.CreateMenuIcon("Icons.Histories"); + history.Click += (_, e) => + { + if (hasSelectedFolder) + App.ShowWindow(new DirHistories(_repo, selectedSingleFolder)); + else + App.ShowWindow(new FileHistories(_repo, change.Path)); + e.Handled = true; + }; + var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, e) => + copyPath.Click += async (_, e) => { - if (string.IsNullOrEmpty(selectedSingleFolder)) - App.CopyText(change.Path); - else - App.CopyText(selectedSingleFolder); - + await App.CopyTextAsync(hasSelectedFolder ? selectedSingleFolder : change.Path); e.Handled = true; }; var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyFullPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - if (string.IsNullOrEmpty(selectedSingleFolder)) - App.CopyText(path); - else - App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder)); - + var target = hasSelectedFolder ? Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder) : path; + await App.CopyTextAsync(target); e.Handled = true; }; + menu.Items.Add(history); + menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(copyPath); menu.Items.Add(copyFullPath); } else { - if (!string.IsNullOrEmpty(selectedSingleFolder)) + if (hasSelectedFolder) { var dir = Path.Combine(_repo.FullPath, selectedSingleFolder); var explore = new MenuItem(); @@ -1508,7 +1500,7 @@ namespace SourceGit.ViewModels var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath)); + var succ = await Commands.SaveChangesAsPatch.ProcessLocalChangesAsync(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -1526,26 +1518,37 @@ namespace SourceGit.ViewModels menu.Items.Add(ai); } - if (!string.IsNullOrEmpty(selectedSingleFolder)) + if (hasSelectedFolder) { + var history = new MenuItem(); + history.Header = App.Text(hasSelectedFolder ? "DirHistories" : "FileHistory"); + history.Icon = App.CreateMenuIcon("Icons.Histories"); + history.Click += (_, e) => + { + App.ShowWindow(new DirHistories(_repo, selectedSingleFolder)); + e.Handled = true; + }; + var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyPath.Click += (_, e) => + copyPath.Click += async (_, e) => { - App.CopyText(selectedSingleFolder); + await App.CopyTextAsync(selectedSingleFolder); e.Handled = true; }; var copyFullPath = new MenuItem(); copyFullPath.Header = App.Text("CopyFullPath"); copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy"); - copyFullPath.Click += (_, e) => + copyFullPath.Click += async (_, e) => { - App.CopyText(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder)); + await App.CopyTextAsync(Native.OS.GetAbsPath(_repo.FullPath, selectedSingleFolder)); e.Handled = true; }; + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(history); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(copyPath); menu.Items.Add(copyFullPath); @@ -1559,7 +1562,7 @@ namespace SourceGit.ViewModels { var menu = new ContextMenu(); - var gitTemplate = new Commands.Config(_repo.FullPath).Get("commit.template"); + var gitTemplate = new Commands.Config(_repo.FullPath).GetAsync("commit.template").Result; var templateCount = _repo.Settings.CommitTemplates.Count; if (templateCount == 0 && string.IsNullOrEmpty(gitTemplate)) { @@ -1662,7 +1665,7 @@ namespace SourceGit.ViewModels if (services.Count == 1) { - App.ShowWindow(new AIAssistant(_repo, services[0], _staged, t => CommitMessage = t), true); + _ = App.ShowDialog(new AIAssistant(_repo, services[0], _staged, t => CommitMessage = t)); return null; } @@ -1672,9 +1675,9 @@ namespace SourceGit.ViewModels var dup = service; var item = new MenuItem(); item.Header = service.Name; - item.Click += (_, e) => + item.Click += async (_, e) => { - App.ShowWindow(new AIAssistant(_repo, dup, _staged, t => CommitMessage = t), true); + await App.ShowDialog(new AIAssistant(_repo, dup, _staged, t => CommitMessage = t)); e.Handled = true; }; @@ -1704,8 +1707,13 @@ namespace SourceGit.ViewModels { if (_useAmend) { - var head = new Commands.QuerySingleCommit(_repo.FullPath, "HEAD").Result(); - return new Commands.QueryStagedChangesWithAmend(_repo.FullPath, head.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : $"{head.SHA}^").Result(); + var head = new Commands.QuerySingleCommit(_repo.FullPath, "HEAD") + .GetResultAsync() + .Result; + + return new Commands.QueryStagedChangesWithAmend(_repo.FullPath, head.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : $"{head.SHA}^") + .GetResultAsync() + .Result; } var rs = new List(); @@ -1751,7 +1759,7 @@ namespace SourceGit.ViewModels if (File.Exists(rebaseMsgFile)) CommitMessage = File.ReadAllText(rebaseMsgFile); else if (rebasing.StoppedAt != null) - CommitMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, rebasing.StoppedAt.SHA).Result(); + CommitMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, rebasing.StoppedAt.SHA).GetResultAsync().Result; } } else if (File.Exists(Path.Combine(_repo.GitDir, "REVERT_HEAD"))) @@ -1783,17 +1791,18 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Stage"); if (count == _unstaged.Count) { - await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).Exec()); + await new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).ExecAsync(); } else { - var paths = new List(); - foreach (var c in changes) - paths.Add(c.Path); - var pathSpecFile = Path.GetTempFileName(); - await File.WriteAllLinesAsync(pathSpecFile, paths); - await Task.Run(() => new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).Exec()); + await using (var writer = new StreamWriter(pathSpecFile)) + { + foreach (var c in changes) + await writer.WriteLineAsync(c.Path); + } + + await new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).ExecAsync(); File.Delete(pathSpecFile); } log.Complete(); @@ -1819,21 +1828,22 @@ namespace SourceGit.ViewModels if (_useAmend) { log.AppendLine("$ git update-index --index-info "); - await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec()); + await new Commands.UnstageChangesForAmend(_repo.FullPath, changes).ExecAsync(); } else { - var paths = new List(); - foreach (var c in changes) + var pathSpecFile = Path.GetTempFileName(); + await using (var writer = new StreamWriter(pathSpecFile)) { - paths.Add(c.Path); - if (c.Index == Models.ChangeState.Renamed) - paths.Add(c.OriginalPath); + foreach (var c in changes) + { + await writer.WriteLineAsync(c.Path); + if (c.Index == Models.ChangeState.Renamed) + await writer.WriteLineAsync(c.OriginalPath); + } } - var pathSpecFile = Path.GetTempFileName(); - await File.WriteAllLinesAsync(pathSpecFile, paths); - await Task.Run(() => new Commands.Restore(_repo.FullPath, pathSpecFile, true).Use(log).Exec()); + await new Commands.Restore(_repo.FullPath, pathSpecFile, true).Use(log).ExecAsync(); File.Delete(pathSpecFile); } log.Complete(); @@ -1870,14 +1880,14 @@ namespace SourceGit.ViewModels if (_repo.CurrentBranch is { IsDetachedHead: true } && checkPassed < CommitCheckPassed.DetachedHead) { var msg = App.Text("WorkingCopy.ConfirmCommitWithDetachedHead"); - App.ShowWindow(new Confirm(msg, () => DoCommit(autoStage, autoPush, CommitCheckPassed.DetachedHead)), true); + _ = App.AskConfirmAsync(msg, () => DoCommit(autoStage, autoPush, CommitCheckPassed.DetachedHead)); return; } if (!string.IsNullOrEmpty(_filter) && _staged.Count > _visibleStaged.Count && checkPassed < CommitCheckPassed.Filter) { var msg = App.Text("WorkingCopy.ConfirmCommitWithFilter", _staged.Count, _visibleStaged.Count, _staged.Count - _visibleStaged.Count); - App.ShowWindow(new Confirm(msg, () => DoCommit(autoStage, autoPush, CommitCheckPassed.Filter)), true); + _ = App.AskConfirmAsync(msg, () => DoCommit(autoStage, autoPush, CommitCheckPassed.Filter)); return; } @@ -1885,7 +1895,7 @@ namespace SourceGit.ViewModels { if ((!autoStage && _staged.Count == 0) || (autoStage && _cached.Count == 0)) { - App.ShowWindow(new ConfirmEmptyCommit(_cached.Count > 0, stageAll => DoCommit(stageAll, autoPush, CommitCheckPassed.FileCount)), true); + _ = App.ShowDialog(new ConfirmEmptyCommit(_cached.Count > 0, stageAll => DoCommit(stageAll, autoPush, CommitCheckPassed.FileCount))); return; } } @@ -1896,18 +1906,18 @@ namespace SourceGit.ViewModels var signOff = _repo.Settings.EnableSignOffForCommit; var log = _repo.CreateLog("Commit"); - Task.Run(() => + Task.Run(async () => { var succ = true; if (autoStage && _unstaged.Count > 0) - succ = new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).Exec(); + succ = await new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).ExecAsync().ConfigureAwait(false); if (succ) - succ = new Commands.Commit(_repo.FullPath, _commitMessage, signOff, _useAmend, _resetAuthor).Use(log).Run(); + succ = await new Commands.Commit(_repo.FullPath, _commitMessage, signOff, _useAmend, _resetAuthor).Use(log).RunAsync().ConfigureAwait(false); log.Complete(); - Dispatcher.UIThread.Post(() => + Dispatcher.UIThread.Post(async () => { if (succ) { @@ -1918,7 +1928,7 @@ namespace SourceGit.ViewModels { if (_repo.CurrentBranch == null) { - var currentBranchName = Commands.Branch.ShowCurrent(_repo.FullPath); + var currentBranchName = await new Commands.QueryCurrentBranch(_repo.FullPath).GetResultAsync(); var tmp = new Models.Branch() { Name = currentBranchName }; _repo.ShowAndStartPopup(new Push(_repo, tmp)); } diff --git a/src/Views/AIAssistant.axaml.cs b/src/Views/AIAssistant.axaml.cs index f865cabe..48dbb900 100644 --- a/src/Views/AIAssistant.axaml.cs +++ b/src/Views/AIAssistant.axaml.cs @@ -72,9 +72,7 @@ namespace SourceGit.Views base.OnPropertyChanged(change); if (change.Property == ContentProperty) - { Text = Content; - } } private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) @@ -84,9 +82,9 @@ namespace SourceGit.Views return; var copy = new MenuItem() { Header = App.Text("Copy") }; - copy.Click += (_, ev) => + copy.Click += async (_, ev) => { - App.CopyText(selected); + await App.CopyTextAsync(selected); ev.Handled = true; }; @@ -115,6 +113,7 @@ namespace SourceGit.Views { public AIAssistant() { + CloseOnESC = true; InitializeComponent(); } diff --git a/src/Views/About.axaml b/src/Views/About.axaml index e6839617..e1a668e4 100644 --- a/src/Views/About.axaml +++ b/src/Views/About.axaml @@ -45,7 +45,12 @@ - + diff --git a/src/Views/About.axaml.cs b/src/Views/About.axaml.cs index d393f94c..e43e9aeb 100644 --- a/src/Views/About.axaml.cs +++ b/src/Views/About.axaml.cs @@ -7,6 +7,7 @@ namespace SourceGit.Views { public About() { + CloseOnESC = true; InitializeComponent(); var assembly = Assembly.GetExecutingAssembly(); @@ -19,6 +20,12 @@ namespace SourceGit.Views TxtCopyright.Text = copyright.Copyright; } + private void OnVisitReleaseNotes(object _, RoutedEventArgs e) + { + Native.OS.OpenBrowser($"https://github.com/sourcegit-scm/sourcegit/releases/tag/v{TxtVersion.Text}"); + e.Handled = true; + } + private void OnVisitWebsite(object _, RoutedEventArgs e) { Native.OS.OpenBrowser("https://sourcegit-scm.github.io/"); diff --git a/src/Views/AddWorktree.axaml.cs b/src/Views/AddWorktree.axaml.cs index c39c8928..4ac2d85f 100644 --- a/src/Views/AddWorktree.axaml.cs +++ b/src/Views/AddWorktree.axaml.cs @@ -25,7 +25,7 @@ namespace SourceGit.Views if (selected.Count == 1) { var folder = selected[0]; - var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder.Path.ToString(); TxtLocation.Text = folderPath.TrimEnd('\\', '/'); } } diff --git a/src/Views/Askpass.axaml.cs b/src/Views/Askpass.axaml.cs index bbe26e98..ab1505fe 100644 --- a/src/Views/Askpass.axaml.cs +++ b/src/Views/Askpass.axaml.cs @@ -19,7 +19,7 @@ namespace SourceGit.Views private void EnterPassword(object _1, RoutedEventArgs _2) { var passphrase = TxtPassphrase.Text ?? string.Empty; - Console.Out.Write($"{passphrase}\n"); + Console.Out.WriteLine(passphrase); App.Quit(0); } } diff --git a/src/Views/AssumeUnchangedManager.axaml.cs b/src/Views/AssumeUnchangedManager.axaml.cs index a0a5a352..914e643e 100644 --- a/src/Views/AssumeUnchangedManager.axaml.cs +++ b/src/Views/AssumeUnchangedManager.axaml.cs @@ -7,13 +7,14 @@ namespace SourceGit.Views { public AssumeUnchangedManager() { + CloseOnESC = true; InitializeComponent(); } - private void OnRemoveButtonClicked(object sender, RoutedEventArgs e) + private async void OnRemoveButtonClicked(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.AssumeUnchangedManager vm && sender is Button button) - vm.Remove(button.DataContext as string); + await vm.RemoveAsync(button.DataContext as string); e.Handled = true; } diff --git a/src/Views/Blame.axaml.cs b/src/Views/Blame.axaml.cs index 5f49fa34..362dc024 100644 --- a/src/Views/Blame.axaml.cs +++ b/src/Views/Blame.axaml.cs @@ -90,7 +90,7 @@ namespace SourceGit.Views { var view = TextView; var maxWidth = 0.0; - if (view != null && view.VisualLinesValid && _editor.BlameData != null) + if (view is { VisualLinesValid: true } && _editor.BlameData != null) { var typeface = view.CreateTypeface(); var calculated = new HashSet(); @@ -394,9 +394,9 @@ namespace SourceGit.Views var copy = new MenuItem(); copy.Header = App.Text("Copy"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, ev) => + copy.Click += async (_, ev) => { - App.CopyText(selected); + await App.CopyTextAsync(selected); ev.Handled = true; }; diff --git a/src/Views/BranchCompare.axaml b/src/Views/BranchCompare.axaml index 3d3859a9..2787ee87 100644 --- a/src/Views/BranchCompare.axaml +++ b/src/Views/BranchCompare.axaml @@ -11,8 +11,7 @@ x:Name="ThisControl" Icon="/App.ico" Title="{DynamicResource Text.BranchCompare}" - MinWidth="1280" MinHeight="720" - WindowStartupLocation="CenterOwner"> + MinWidth="1280" MinHeight="720"> @@ -128,7 +127,6 @@ diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index 57cfa88d..891efa4f 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -70,8 +70,7 @@ namespace SourceGit.Views private void CreateContent(Thickness margin, string iconKey, bool highlight) { - var geo = this.FindResource(iconKey) as StreamGeometry; - if (geo == null) + if (this.FindResource(iconKey) is not StreamGeometry geo) return; var path = new Path() @@ -401,7 +400,7 @@ namespace SourceGit.Views if (selected == null || selected.Count == 0) return; - var prev = null as ViewModels.BranchTreeNode; + ViewModels.BranchTreeNode prev = null; foreach (var row in Rows) { if (row.IsSelected) @@ -481,7 +480,7 @@ namespace SourceGit.Views }; menu.Items.Add(deleteMulti); - menu?.Open(this); + menu.Open(this); } } diff --git a/src/Views/CaptionButtons.axaml.cs b/src/Views/CaptionButtons.axaml.cs index 650ccef0..806d9cb5 100644 --- a/src/Views/CaptionButtons.axaml.cs +++ b/src/Views/CaptionButtons.axaml.cs @@ -42,8 +42,7 @@ namespace SourceGit.Views private void CloseWindow(object _, RoutedEventArgs e) { var window = this.FindAncestorOfType(); - if (window != null) - window.Close(); + window?.Close(); e.Handled = true; } diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 6623a60b..d5c69986 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -87,15 +87,6 @@ namespace SourceGit.Views set => SetValue(ChangesProperty, value); } - public static readonly StyledProperty AutoSelectFirstChangeProperty = - AvaloniaProperty.Register(nameof(AutoSelectFirstChange)); - - public bool AutoSelectFirstChange - { - get => GetValue(AutoSelectFirstChangeProperty); - set => SetValue(AutoSelectFirstChangeProperty, value); - } - public static readonly StyledProperty> SelectedChangesProperty = AvaloniaProperty.Register>(nameof(SelectedChanges)); @@ -365,24 +356,9 @@ namespace SourceGit.Views MakeTreeRows(rows, tree.Tree); tree.Rows.AddRange(rows); - if (!onlyViewModeChange && AutoSelectFirstChange) + if (selected.Count > 0) { - foreach (var row in tree.Rows) - { - if (row.Change != null) - { - tree.SelectedRows.Add(row); - SetCurrentValue(SelectedChangesProperty, [row.Change]); - break; - } - } - } - else if (selected.Count > 0) - { - var sets = new HashSet(); - foreach (var c in selected) - sets.Add(c); - + var sets = new HashSet(selected); var nodes = new List(); foreach (var row in tree.Rows) { @@ -399,16 +375,8 @@ namespace SourceGit.Views { var grid = new ViewModels.ChangeCollectionAsGrid(); grid.Changes.AddRange(changes); - - if (!onlyViewModeChange && AutoSelectFirstChange) - { - grid.SelectedChanges.Add(changes[0]); - SetCurrentValue(SelectedChangesProperty, [changes[0]]); - } - else if (selected.Count > 0) - { + if (selected.Count > 0) grid.SelectedChanges.AddRange(selected); - } Content = grid; } @@ -416,16 +384,8 @@ namespace SourceGit.Views { var list = new ViewModels.ChangeCollectionAsList(); list.Changes.AddRange(changes); - - if (!onlyViewModeChange && AutoSelectFirstChange) - { - list.SelectedChanges.Add(changes[0]); - SetCurrentValue(SelectedChangesProperty, [changes[0]]); - } - else if (selected.Count > 0) - { + if (selected.Count > 0) list.SelectedChanges.AddRange(selected); - } Content = list; } @@ -447,9 +407,7 @@ namespace SourceGit.Views if (selected.Count > 0) { - var sets = new HashSet(); - foreach (var c in selected) - sets.Add(c); + var sets = new HashSet(selected); var nodes = new List(); foreach (var row in tree.Rows) diff --git a/src/Views/ChangeSubmoduleUrl.axaml b/src/Views/ChangeSubmoduleUrl.axaml new file mode 100644 index 00000000..ad458133 --- /dev/null +++ b/src/Views/ChangeSubmoduleUrl.axaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/src/Views/ChangeSubmoduleUrl.axaml.cs b/src/Views/ChangeSubmoduleUrl.axaml.cs new file mode 100644 index 00000000..287c20d8 --- /dev/null +++ b/src/Views/ChangeSubmoduleUrl.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class ChangeSubmoduleUrl : UserControl + { + public ChangeSubmoduleUrl() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/ChromelessWindow.cs b/src/Views/ChromelessWindow.cs index 1662bcd7..24c652dc 100644 --- a/src/Views/ChromelessWindow.cs +++ b/src/Views/ChromelessWindow.cs @@ -13,6 +13,12 @@ namespace SourceGit.Views get => Native.OS.UseSystemWindowFrame; } + public bool CloseOnESC + { + get; + set; + } = false; + protected override Type StyleKeyOverride => typeof(Window); public ChromelessWindow() @@ -68,9 +74,20 @@ namespace SourceGit.Views } } + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (e is { Handled: false, Key: Key.Escape, KeyModifiers: KeyModifiers.None } && CloseOnESC) + { + Close(); + e.Handled = true; + } + } + private void OnWindowBorderPointerPressed(object sender, PointerPressedEventArgs e) { - if (sender is Border border && border.Tag is WindowEdge edge && CanResize) + if (sender is Border { Tag: WindowEdge edge } && CanResize) BeginResizeDrag(edge, e); } } diff --git a/src/Views/CommitBaseInfo.axaml.cs b/src/Views/CommitBaseInfo.axaml.cs index ac9b53cc..01e96c14 100644 --- a/src/Views/CommitBaseInfo.axaml.cs +++ b/src/Views/CommitBaseInfo.axaml.cs @@ -1,11 +1,10 @@ +using System; using System.Collections.Generic; -using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Threading; namespace SourceGit.Views { @@ -61,10 +60,10 @@ namespace SourceGit.Views InitializeComponent(); } - private void OnCopyCommitSHA(object sender, RoutedEventArgs e) + private async void OnCopyCommitSHA(object sender, RoutedEventArgs e) { if (sender is Button { DataContext: Models.Commit commit }) - App.CopyText(commit.SHA); + await App.CopyTextAsync(commit.SHA); e.Handled = true; } @@ -103,39 +102,32 @@ namespace SourceGit.Views e.Handled = true; } - private void OnOpenContainsIn(object sender, RoutedEventArgs e) + private async void OnOpenContainsIn(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.CommitDetail detail && sender is Button button) { - var tracking = new CommitRelationTracking(detail); + var tracking = new CommitRelationTracking(); var flyout = new Flyout(); flyout.Content = tracking; flyout.ShowAt(button); + + await tracking.SetDataAsync(detail); } e.Handled = true; } - private void OnSHAPointerEntered(object sender, PointerEventArgs e) + 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) + if (tooltip is Models.Commit commit && commit.SHA.Equals(sha, StringComparison.Ordinal)) return; - Task.Run(() => - { - var c = detail.GetParent(sha); - if (c == null) - return; - - Dispatcher.UIThread.Invoke(() => - { - if (ctl.IsEffectivelyVisible && ctl.DataContext is string newSHA && newSHA == sha) - ToolTip.SetTip(ctl, c); - }); - }); + var c = await detail.GetCommitAsync(sha); + if (c is not null && ctl is { IsEffectivelyVisible: true, DataContext: string newSHA } && sha.Equals(newSHA, StringComparison.Ordinal)) + ToolTip.SetTip(ctl, c); } e.Handled = true; @@ -144,11 +136,10 @@ namespace SourceGit.Views private void OnSHAPressed(object sender, PointerPressedEventArgs e) { var point = e.GetCurrentPoint(this); - - if (point.Properties.IsLeftButtonPressed && DataContext is ViewModels.CommitDetail detail && sender is Control { DataContext: string sha }) - { + if (point.Properties.IsLeftButtonPressed && + DataContext is ViewModels.CommitDetail detail && + sender is Control { DataContext: string sha }) detail.NavigateTo(sha); - } e.Handled = true; } diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index 4dafee37..4a1d95bc 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -50,7 +50,6 @@ ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeViewMode}" Changes="{Binding VisibleChanges}" SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}" - AutoSelectFirstChange="True" ContextRequested="OnChangeContextRequested"/> diff --git a/src/Views/CommitChanges.axaml.cs b/src/Views/CommitChanges.axaml.cs index c3d30018..8bd868fb 100644 --- a/src/Views/CommitChanges.axaml.cs +++ b/src/Views/CommitChanges.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia.VisualTree; namespace SourceGit.Views { @@ -11,15 +12,25 @@ namespace SourceGit.Views private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) { - if (sender is ChangeCollectionView { SelectedChanges: { } selected } view && - selected.Count == 1 && - DataContext is ViewModels.CommitDetail vm) + e.Handled = true; + + if (sender is not ChangeCollectionView view || DataContext is not ViewModels.CommitDetail vm) + return; + + var changes = view.SelectedChanges ?? []; + var container = view.FindDescendantOfType(); + if (container is { SelectedItems.Count: 1, SelectedItem: ViewModels.ChangeTreeNode { IsFolder: true } node }) { - var menu = vm.CreateChangeContextMenu(selected[0]); - menu?.Open(view); + var menu = vm.CreateChangeContextMenuByFolder(node, changes); + menu.Open(view); + return; } - e.Handled = true; + if (changes.Count == 1) + { + var menu = vm.CreateChangeContextMenu(changes[0]); + menu.Open(view); + } } } } diff --git a/src/Views/CommitDetail.axaml.cs b/src/Views/CommitDetail.axaml.cs index f0599c66..23c6bd42 100644 --- a/src/Views/CommitDetail.axaml.cs +++ b/src/Views/CommitDetail.axaml.cs @@ -12,7 +12,7 @@ namespace SourceGit.Views private void OnChangeDoubleTapped(object sender, TappedEventArgs e) { - if (DataContext is ViewModels.CommitDetail detail && sender is Grid grid && grid.DataContext is Models.Change change) + if (DataContext is ViewModels.CommitDetail detail && sender is Grid { DataContext: Models.Change change }) { detail.ActivePageIndex = 1; detail.SelectedChanges = new() { change }; @@ -23,7 +23,7 @@ namespace SourceGit.Views private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) { - if (DataContext is ViewModels.CommitDetail detail && sender is Grid grid && grid.DataContext is Models.Change change) + if (DataContext is ViewModels.CommitDetail detail && sender is Grid { DataContext: Models.Change change } grid) { var menu = detail.CreateChangeContextMenu(change); menu?.Open(grid); diff --git a/src/Views/CommitGraph.cs b/src/Views/CommitGraph.cs index 5db39300..858ff3a0 100644 --- a/src/Views/CommitGraph.cs +++ b/src/Views/CommitGraph.cs @@ -1,7 +1,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media; -using Avalonia.VisualTree; namespace SourceGit.Views { @@ -34,42 +33,38 @@ namespace SourceGit.Views set => SetValue(OnlyHighlightCurrentBranchProperty, value); } + public static readonly StyledProperty LayoutProperty = + AvaloniaProperty.Register(nameof(Layout)); + + public Models.CommitGraphLayout Layout + { + get => GetValue(LayoutProperty); + set => SetValue(LayoutProperty, value); + } + static CommitGraph() { - AffectsRender(GraphProperty, DotBrushProperty, OnlyHighlightCurrentBranchProperty); + AffectsRender( + GraphProperty, + DotBrushProperty, + OnlyHighlightCurrentBranchProperty, + LayoutProperty); } public override void Render(DrawingContext context) { base.Render(context); - var graph = Graph; - if (graph == null) + if (Graph is not { } graph || Layout is not { } layout) return; - var histories = this.FindAncestorOfType(); - if (histories == null) - return; + var startY = layout.StartY; + var clipWidth = layout.ClipWidth; + var clipHeight = Bounds.Height; + var rowHeight = layout.RowHeight; + var endY = startY + clipHeight + 28; - var list = histories.CommitListContainer; - if (list == null) - return; - - var container = list.ItemsPanelRoot as VirtualizingStackPanel; - if (container == null) - return; - - var item = list.ContainerFromIndex(container.FirstRealizedIndex); - if (item == null) - return; - - var width = histories.CommitListHeader.ColumnDefinitions[0].ActualWidth; - var height = Bounds.Height; - var rowHeight = item.Bounds.Height; - var startY = container.FirstRealizedIndex * rowHeight - item.TranslatePoint(new Point(0, 0), list).Value!.Y; - var endY = startY + height + 28; - - using (context.PushClip(new Rect(0, 0, width, height))) + using (context.PushClip(new Rect(0, 0, clipWidth, clipHeight))) using (context.PushTransform(Matrix.CreateTranslation(0, -startY))) { DrawCurves(context, graph, startY, endY, rowHeight); diff --git a/src/Views/CommitMessageEditor.axaml.cs b/src/Views/CommitMessageEditor.axaml.cs index 7a23f85c..a3d89407 100644 --- a/src/Views/CommitMessageEditor.axaml.cs +++ b/src/Views/CommitMessageEditor.axaml.cs @@ -9,6 +9,7 @@ namespace SourceGit.Views { public CommitMessageEditor() { + CloseOnESC = true; InitializeComponent(); } diff --git a/src/Views/CommitMessagePresenter.cs b/src/Views/CommitMessagePresenter.cs index 61119991..ea1b98f2 100644 --- a/src/Views/CommitMessagePresenter.cs +++ b/src/Views/CommitMessagePresenter.cs @@ -135,9 +135,9 @@ namespace SourceGit.Views var copy = new MenuItem(); copy.Header = App.Text("SHALinkCM.CopySHA"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, ev) => + copy.Click += async (_, ev) => { - App.CopyText(link); + await App.CopyTextAsync(link); ev.Handled = true; }; @@ -168,9 +168,9 @@ namespace SourceGit.Views var copy = new MenuItem(); copy.Header = App.Text("IssueLinkCM.CopyLink"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, ev) => + copy.Click += async (_, ev) => { - App.CopyText(link); + await App.CopyTextAsync(link); ev.Handled = true; }; @@ -256,7 +256,7 @@ namespace SourceGit.Views var lastDetailCommit = detail.Commit.SHA; Task.Run(() => { - var c = detail.GetParent(sha); + var c = detail.GetCommitAsync(sha).Result; Dispatcher.UIThread.Invoke(() => { // Make sure the DataContext of CommitBaseInfo is not changed. diff --git a/src/Views/CommitMessageTextBox.axaml.cs b/src/Views/CommitMessageTextBox.axaml.cs index 5330852c..0fbf3ab4 100644 --- a/src/Views/CommitMessageTextBox.axaml.cs +++ b/src/Views/CommitMessageTextBox.axaml.cs @@ -212,9 +212,9 @@ namespace SourceGit.Views e.Handled = true; } - private void CopyAllText(object sender, RoutedEventArgs e) + private async void CopyAllText(object sender, RoutedEventArgs e) { - App.CopyText(Text); + await App.CopyTextAsync(Text); e.Handled = true; } diff --git a/src/Views/CommitRefsPresenter.cs b/src/Views/CommitRefsPresenter.cs index 7a473f8b..d221c155 100644 --- a/src/Views/CommitRefsPresenter.cs +++ b/src/Views/CommitRefsPresenter.cs @@ -174,12 +174,11 @@ namespace SourceGit.Views { _items.Clear(); - var commit = DataContext as Models.Commit; - if (commit == null) + if (DataContext is not Models.Commit commit) return new Size(0, 0); var refs = commit.Decorators; - if (refs != null && refs.Count > 0) + if (refs is { Count: > 0 }) { var typeface = new Typeface(FontFamily); var typefaceBold = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Bold); @@ -196,8 +195,7 @@ namespace SourceGit.Views if (!showTags && decorator.Type == Models.DecoratorType.Tag) continue; - var isHead = decorator.Type == Models.DecoratorType.CurrentBranchHead || - decorator.Type == Models.DecoratorType.CurrentCommitHead; + var isHead = decorator.Type is Models.DecoratorType.CurrentBranchHead or Models.DecoratorType.CurrentCommitHead; var label = new FormattedText( decorator.Name, diff --git a/src/Views/CommitRelationTracking.axaml.cs b/src/Views/CommitRelationTracking.axaml.cs index 1e436552..ff3e8546 100644 --- a/src/Views/CommitRelationTracking.axaml.cs +++ b/src/Views/CommitRelationTracking.axaml.cs @@ -1,7 +1,5 @@ using System.Threading.Tasks; - using Avalonia.Controls; -using Avalonia.Threading; namespace SourceGit.Views { @@ -12,21 +10,12 @@ namespace SourceGit.Views InitializeComponent(); } - public CommitRelationTracking(ViewModels.CommitDetail detail) + public async Task SetDataAsync(ViewModels.CommitDetail detail) { - InitializeComponent(); - LoadingIcon.IsVisible = true; - - Task.Run(() => - { - var containsIn = detail.GetRefsContainsThisCommit(); - Dispatcher.UIThread.Invoke(() => - { - Container.ItemsSource = containsIn; - LoadingIcon.IsVisible = false; - }); - }); + var containsIn = await detail.GetRefsContainsThisCommitAsync(); + Container.ItemsSource = containsIn; + LoadingIcon.IsVisible = false; } } } diff --git a/src/Views/CommitTimeTextBlock.cs b/src/Views/CommitTimeTextBlock.cs index e6a03834..f8374177 100644 --- a/src/Views/CommitTimeTextBlock.cs +++ b/src/Views/CommitTimeTextBlock.cs @@ -118,8 +118,7 @@ namespace SourceGit.Views private string GetDisplayText() { - var commit = DataContext as Models.Commit; - if (commit == null) + if (DataContext is not Models.Commit commit) return string.Empty; if (ShowAsDateTime) diff --git a/src/Views/ConfigureCustomActionControls.axaml b/src/Views/ConfigureCustomActionControls.axaml index 2a38610f..21b6b8f1 100644 --- a/src/Views/ConfigureCustomActionControls.axaml +++ b/src/Views/ConfigureCustomActionControls.axaml @@ -129,8 +129,9 @@ - + + @@ -139,51 +140,72 @@ CornerRadius="3" Height="28" Text="{Binding Description, Mode=TwoWay}"/> - - - - - - - - - - - - + + + + + + + + + + + Text="{Binding StringValue, Mode=TwoWay}"/> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/ConfigureCustomActionControls.axaml.cs b/src/Views/ConfigureCustomActionControls.axaml.cs index 9e2104cf..e28366d7 100644 --- a/src/Views/ConfigureCustomActionControls.axaml.cs +++ b/src/Views/ConfigureCustomActionControls.axaml.cs @@ -4,6 +4,7 @@ namespace SourceGit.Views { public ConfigureCustomActionControls() { + CloseOnESC = true; InitializeComponent(); } } diff --git a/src/Views/ConfigureWorkspace.axaml.cs b/src/Views/ConfigureWorkspace.axaml.cs index 06294caf..19104f81 100644 --- a/src/Views/ConfigureWorkspace.axaml.cs +++ b/src/Views/ConfigureWorkspace.axaml.cs @@ -9,6 +9,7 @@ namespace SourceGit.Views { public ConfigureWorkspace() { + CloseOnESC = true; InitializeComponent(); } diff --git a/src/Views/Confirm.axaml b/src/Views/Confirm.axaml index 93361533..6f2dbd6d 100644 --- a/src/Views/Confirm.axaml +++ b/src/Views/Confirm.axaml @@ -3,10 +3,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:v="using:SourceGit.Views" - xmlns:vm="using:SourceGit.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.Confirm" - x:DataType="vm:Confirm" x:Name="ThisControl" Icon="/App.ico" Title="{DynamicResource Text.Warn}" @@ -38,7 +36,7 @@ - + diff --git a/src/Views/Confirm.axaml.cs b/src/Views/Confirm.axaml.cs index 13e488ee..f214da23 100644 --- a/src/Views/Confirm.axaml.cs +++ b/src/Views/Confirm.axaml.cs @@ -5,28 +5,26 @@ namespace SourceGit.Views { public partial class Confirm : ChromelessWindow { + public Action OnSure + { + get; + set; + } + public Confirm() { InitializeComponent(); } - protected override void OnClosed(EventArgs e) - { - (DataContext as ViewModels.Confirm)?.Done(_isOkPressed); - base.OnClosed(e); - } - private void Sure(object _1, RoutedEventArgs _2) { - _isOkPressed = true; - Close(); + OnSure?.Invoke(); + Close(true); } private void CloseWindow(object _1, RoutedEventArgs _2) { - Close(); + Close(false); } - - private bool _isOkPressed = false; } } diff --git a/src/Views/ConfirmRestart.axaml.cs b/src/Views/ConfirmRestart.axaml.cs index ea49bea1..f77a0dc1 100644 --- a/src/Views/ConfirmRestart.axaml.cs +++ b/src/Views/ConfirmRestart.axaml.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics; using Avalonia.Interactivity; @@ -12,12 +11,6 @@ namespace SourceGit.Views InitializeComponent(); } - private void CloseWindow(object _1, RoutedEventArgs _2) - { - Console.Out.WriteLine("No passphrase entered."); - App.Quit(-1); - } - private void Restart(object _1, RoutedEventArgs _2) { var selfExecFile = Process.GetCurrentProcess().MainModule!.FileName; diff --git a/src/Views/ConventionalCommitMessageBuilder.axaml.cs b/src/Views/ConventionalCommitMessageBuilder.axaml.cs index 955450ed..3f2c7f39 100644 --- a/src/Views/ConventionalCommitMessageBuilder.axaml.cs +++ b/src/Views/ConventionalCommitMessageBuilder.axaml.cs @@ -6,6 +6,7 @@ namespace SourceGit.Views { public ConventionalCommitMessageBuilder() { + CloseOnESC = true; InitializeComponent(); } diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 01cca875..ad29b1a4 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -37,7 +37,7 @@ diff --git a/src/Views/Preferences.axaml.cs b/src/Views/Preferences.axaml.cs index f0f4617a..a6b4b722 100644 --- a/src/Views/Preferences.axaml.cs +++ b/src/Views/Preferences.axaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -116,10 +117,11 @@ namespace SourceGit.Views { var pref = ViewModels.Preferences.Instance; DataContext = pref; + CloseOnESC = true; if (pref.IsGitConfigured()) { - var config = new Commands.Config(null).ListAll(); + var config = new Commands.Config(null).ReadAllAsync().Result; if (config.TryGetValue("user.name", out var name)) DefaultUser = name; @@ -153,13 +155,13 @@ namespace SourceGit.Views InitializeComponent(); } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + protected override async void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == GPGFormatProperty) { - var config = new Commands.Config(null).ListAll(); + var config = await new Commands.Config(null).ReadAllAsync(); if (GPGFormat.Value == "openpgp" && config.TryGetValue("gpg.program", out var openpgp)) GPGExecutableFile = openpgp; else if (config.TryGetValue($"gpg.{GPGFormat.Value}.program", out var gpgProgram)) @@ -167,23 +169,23 @@ namespace SourceGit.Views } } - protected override void OnClosing(WindowClosingEventArgs e) + protected override async void OnClosing(WindowClosingEventArgs e) { base.OnClosing(e); if (Design.IsDesignMode) return; - var config = new Commands.Config(null).ListAll(); - SetIfChanged(config, "user.name", DefaultUser, ""); - SetIfChanged(config, "user.email", DefaultEmail, ""); - SetIfChanged(config, "user.signingkey", GPGUserKey, ""); - SetIfChanged(config, "core.autocrlf", CRLFMode?.Value, null); - SetIfChanged(config, "fetch.prune", EnablePruneOnFetch ? "true" : "false", "false"); - SetIfChanged(config, "commit.gpgsign", EnableGPGCommitSigning ? "true" : "false", "false"); - SetIfChanged(config, "tag.gpgsign", EnableGPGTagSigning ? "true" : "false", "false"); - SetIfChanged(config, "http.sslverify", EnableHTTPSSLVerify ? "" : "false", ""); - SetIfChanged(config, "gpg.format", GPGFormat.Value, "openpgp"); + var config = await new Commands.Config(null).ReadAllAsync(); + await SetIfChangedAsync(config, "user.name", DefaultUser, ""); + await SetIfChangedAsync(config, "user.email", DefaultEmail, ""); + await SetIfChangedAsync(config, "user.signingkey", GPGUserKey, ""); + await SetIfChangedAsync(config, "core.autocrlf", CRLFMode?.Value, null); + await SetIfChangedAsync(config, "fetch.prune", EnablePruneOnFetch ? "true" : "false", "false"); + await SetIfChangedAsync(config, "commit.gpgsign", EnableGPGCommitSigning ? "true" : "false", "false"); + await SetIfChangedAsync(config, "tag.gpgsign", EnableGPGTagSigning ? "true" : "false", "false"); + await SetIfChangedAsync(config, "http.sslverify", EnableHTTPSSLVerify ? "" : "false", ""); + await SetIfChangedAsync(config, "gpg.format", GPGFormat.Value, "openpgp"); if (!GPGFormat.Value.Equals("ssh", StringComparison.Ordinal)) { @@ -200,7 +202,7 @@ namespace SourceGit.Views changed = true; if (changed) - new Commands.Config(null).Set($"gpg.{GPGFormat.Value}.program", GPGExecutableFile); + await new Commands.Config(null).SetAsync($"gpg.{GPGFormat.Value}.program", GPGExecutableFile); } ViewModels.Preferences.Instance.Save(); @@ -214,10 +216,15 @@ namespace SourceGit.Views AllowMultiple = false, }; - var selected = await StorageProvider.OpenFilePickerAsync(options); - if (selected.Count == 1) + try { - ViewModels.Preferences.Instance.ThemeOverrides = selected[0].Path.LocalPath; + var selected = await StorageProvider.OpenFilePickerAsync(options); + if (selected is { Count: 1 }) + ViewModels.Preferences.Instance.ThemeOverrides = selected[0].Path.LocalPath; + } + catch (Exception ex) + { + App.RaiseException(string.Empty, $"Failed to select theme: {ex.Message}"); } e.Handled = true; @@ -232,11 +239,18 @@ namespace SourceGit.Views AllowMultiple = false, }; - var selected = await StorageProvider.OpenFilePickerAsync(options); - if (selected.Count == 1) + try { - ViewModels.Preferences.Instance.GitInstallPath = selected[0].Path.LocalPath; - UpdateGitVersion(); + var selected = await StorageProvider.OpenFilePickerAsync(options); + if (selected is { Count: 1 }) + { + ViewModels.Preferences.Instance.GitInstallPath = selected[0].Path.LocalPath; + UpdateGitVersion(); + } + } + catch (Exception ex) + { + App.RaiseException(string.Empty, $"Failed to select git executable: {ex.Message}"); } e.Handled = true; @@ -277,10 +291,15 @@ namespace SourceGit.Views AllowMultiple = false, }; - var selected = await StorageProvider.OpenFilePickerAsync(options); - if (selected.Count == 1) + try { - GPGExecutableFile = selected[0].Path.LocalPath; + var selected = await StorageProvider.OpenFilePickerAsync(options); + if (selected is { Count: 1 }) + GPGExecutableFile = selected[0].Path.LocalPath; + } + catch (Exception ex) + { + App.RaiseException(string.Empty, $"Failed to select gpg program: {ex.Message}"); } e.Handled = true; @@ -293,16 +312,25 @@ namespace SourceGit.Views return; var shell = Models.ShellOrTerminal.Supported[type]; - var options = new FilePickerOpenOptions() + var options = new FilePickerOpenOptions() { AllowMultiple = false }; + if (shell.Type != "custom") { - FileTypeFilter = [new FilePickerFileType(shell.Name) { Patterns = [shell.Exec] }], - AllowMultiple = false, - }; + options = new FilePickerOpenOptions() + { + FileTypeFilter = [new FilePickerFileType(shell.Name) { Patterns = [shell.Exec] }], + AllowMultiple = false, + }; + } - var selected = await StorageProvider.OpenFilePickerAsync(options); - if (selected.Count == 1) + try { - ViewModels.Preferences.Instance.ShellOrTerminalPath = selected[0].Path.LocalPath; + var selected = await StorageProvider.OpenFilePickerAsync(options); + if (selected is { Count: 1 }) + ViewModels.Preferences.Instance.ShellOrTerminalPath = selected[0].Path.LocalPath; + } + catch (Exception ex) + { + App.RaiseException(string.Empty, $"Failed to select shell/terminal: {ex.Message}"); } e.Handled = true; @@ -311,7 +339,7 @@ namespace SourceGit.Views private async void SelectExternalMergeTool(object _, RoutedEventArgs e) { var type = ViewModels.Preferences.Instance.ExternalMergeToolType; - if (type < 0 || type >= Models.ExternalMerger.Supported.Count) + if (type <= 0 || type >= Models.ExternalMerger.Supported.Count) { ViewModels.Preferences.Instance.ExternalMergeToolType = 0; e.Handled = true; @@ -325,16 +353,21 @@ namespace SourceGit.Views AllowMultiple = false, }; - var selected = await StorageProvider.OpenFilePickerAsync(options); - if (selected.Count == 1) + try { - ViewModels.Preferences.Instance.ExternalMergeToolPath = selected[0].Path.LocalPath; + var selected = await StorageProvider.OpenFilePickerAsync(options); + if (selected is { Count: 1 }) + ViewModels.Preferences.Instance.ExternalMergeToolPath = selected[0].Path.LocalPath; + } + catch (Exception ex) + { + App.RaiseException(string.Empty, $"Failed to select merge tool: {ex.Message}"); } e.Handled = true; } - private void SetIfChanged(Dictionary cached, string key, string value, string defValue) + private static async Task SetIfChangedAsync(Dictionary cached, string key, string value, string defValue) { bool changed = false; if (cached.TryGetValue(key, out var old)) @@ -343,15 +376,15 @@ namespace SourceGit.Views changed = true; if (changed) - new Commands.Config(null).Set(key, value); + await new Commands.Config(null).SetAsync(key, value); } - private void OnUseNativeWindowFrameChanged(object sender, RoutedEventArgs e) + private async void OnUseNativeWindowFrameChanged(object sender, RoutedEventArgs e) { if (sender is CheckBox box) { ViewModels.Preferences.Instance.UseSystemWindowFrame = box.IsChecked == true; - App.ShowWindow(new ConfirmRestart(), true); + await App.ShowDialog(new ConfirmRestart()); } e.Handled = true; @@ -398,9 +431,16 @@ namespace SourceGit.Views AllowMultiple = false, }; - var selected = await StorageProvider.OpenFilePickerAsync(options); - if (selected.Count == 1 && sender is Button { DataContext: Models.CustomAction action }) - action.Executable = selected[0].Path.LocalPath; + try + { + var selected = await StorageProvider.OpenFilePickerAsync(options); + if (selected is { Count: 1 } && sender is Button { DataContext: Models.CustomAction action }) + action.Executable = selected[0].Path.LocalPath; + } + catch (Exception ex) + { + App.RaiseException(string.Empty, $"Failed to select program for custom action: {ex.Message}"); + } e.Handled = true; } diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index 472f552a..94149325 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -121,8 +121,7 @@ namespace SourceGit.Views private void OnSearchKeyDown(object _, KeyEventArgs e) { - var repo = DataContext as ViewModels.Repository; - if (repo == null) + if (DataContext is not ViewModels.Repository repo) return; if (e.Key == Key.Enter) @@ -199,9 +198,7 @@ namespace SourceGit.Views private void OnDoubleTappedWorktree(object sender, TappedEventArgs e) { if (sender is ListBox { SelectedItem: Models.Worktree worktree } && DataContext is ViewModels.Repository repo) - { repo.OpenWorktree(worktree); - } e.Handled = true; } @@ -343,8 +340,7 @@ namespace SourceGit.Views private void OnSearchSuggestionBoxKeyDown(object _, KeyEventArgs e) { - var repo = DataContext as ViewModels.Repository; - if (repo == null) + if (DataContext is not ViewModels.Repository repo) return; if (e.Key == Key.Escape) @@ -363,8 +359,7 @@ namespace SourceGit.Views private void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e) { - var repo = DataContext as ViewModels.Repository; - if (repo == null) + if (DataContext is not ViewModels.Repository repo) return; var content = (sender as StackPanel)?.DataContext as string; @@ -437,12 +432,12 @@ namespace SourceGit.Views e.Handled = true; } - private void OnBisectCommand(object sender, RoutedEventArgs e) + private async void OnBisectCommand(object sender, RoutedEventArgs e) { if (sender is Button button && DataContext is ViewModels.Repository { IsBisectCommandRunning: false } repo && repo.CanCreatePopup()) - repo.Bisect(button.Tag as string); + await repo.ExecBisectCommandAsync(button.Tag as string); e.Handled = true; } diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml index 75cccd71..2cc935dd 100644 --- a/src/Views/RepositoryConfigure.axaml +++ b/src/Views/RepositoryConfigure.axaml @@ -492,11 +492,14 @@ - + diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 47895ba1..4f897038 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Platform.Storage; @@ -9,23 +8,16 @@ namespace SourceGit.Views { public RepositoryConfigure() { + CloseOnESC = true; InitializeComponent(); } - protected override void OnKeyDown(KeyEventArgs e) - { - base.OnKeyDown(e); - - if (!e.Handled && e.Key == Key.Escape) - Close(); - } - - protected override void OnClosing(WindowClosingEventArgs e) + protected override async void OnClosing(WindowClosingEventArgs e) { base.OnClosing(e); if (!Design.IsDesignMode && DataContext is ViewModels.RepositoryConfigure configure) - configure.Save(); + await configure.SaveAsync(); } private async void SelectExecutableForCustomAction(object sender, RoutedEventArgs e) diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index e18b0e81..bac3372f 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -22,20 +22,20 @@ namespace SourceGit.Views } } - private void OpenStatistics(object _, RoutedEventArgs e) + private async void OpenStatistics(object _, RoutedEventArgs e) { if (DataContext is ViewModels.Repository repo) { - App.ShowWindow(new ViewModels.Statistics(repo.FullPath), true); + await App.ShowDialog(new ViewModels.Statistics(repo.FullPath)); e.Handled = true; } } - private void OpenConfigure(object sender, RoutedEventArgs e) + private async void OpenConfigure(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.Repository repo) { - App.ShowWindow(new ViewModels.RepositoryConfigure(repo), true); + await App.ShowDialog(new ViewModels.RepositoryConfigure(repo)); e.Handled = true; } } @@ -116,10 +116,9 @@ namespace SourceGit.Views e.Handled = true; } - private void StartBisect(object sender, RoutedEventArgs e) + private async void StartBisect(object sender, RoutedEventArgs e) { - if (DataContext is ViewModels.Repository { IsBisectCommandRunning: false } repo && - repo.InProgressContext == null && + if (DataContext is ViewModels.Repository { IsBisectCommandRunning: false, InProgressContext: null } repo && repo.CanCreatePopup()) { if (repo.LocalChangesCount > 0) @@ -127,7 +126,7 @@ namespace SourceGit.Views else if (repo.IsBisectCommandRunning || repo.BisectState != Models.BisectState.None) App.RaiseException(repo.FullPath, "Bisect is running! Please abort it before starting a new one."); else - repo.Bisect("start"); + await repo.ExecBisectCommandAsync("start"); } e.Handled = true; @@ -144,11 +143,11 @@ namespace SourceGit.Views e.Handled = true; } - private void OpenGitLogs(object sender, RoutedEventArgs e) + private async void OpenGitLogs(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.Repository repo) { - App.ShowWindow(new ViewModels.ViewLogs(repo), true); + await App.ShowDialog(new ViewModels.ViewLogs(repo)); e.Handled = true; } } diff --git a/src/Views/RevisionCompare.axaml b/src/Views/RevisionCompare.axaml index 6367c866..8d20f104 100644 --- a/src/Views/RevisionCompare.axaml +++ b/src/Views/RevisionCompare.axaml @@ -99,7 +99,6 @@ diff --git a/src/Views/RevisionCompare.axaml.cs b/src/Views/RevisionCompare.axaml.cs index 2c548240..1f0e5fc1 100644 --- a/src/Views/RevisionCompare.axaml.cs +++ b/src/Views/RevisionCompare.axaml.cs @@ -37,8 +37,7 @@ namespace SourceGit.Views if (topLevel == null) return; - var vm = DataContext as ViewModels.RevisionCompare; - if (vm == null) + if (DataContext is not ViewModels.RevisionCompare vm) return; var options = new FilePickerSaveOptions(); diff --git a/src/Views/RevisionFileContentViewer.axaml.cs b/src/Views/RevisionFileContentViewer.axaml.cs index cfbb6e3d..acb2017f 100644 --- a/src/Views/RevisionFileContentViewer.axaml.cs +++ b/src/Views/RevisionFileContentViewer.axaml.cs @@ -108,9 +108,9 @@ namespace SourceGit.Views return; var copy = new MenuItem() { Header = App.Text("Copy") }; - copy.Click += (_, ev) => + copy.Click += async (_, ev) => { - App.CopyText(selected); + await App.CopyTextAsync(selected); ev.Handled = true; }; diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index de396b1a..a7ca36e0 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; - +using System.Threading.Tasks; using Avalonia; using Avalonia.Collections; using Avalonia.Controls; @@ -16,13 +16,13 @@ namespace SourceGit.Views { protected override Type StyleKeyOverride => typeof(ToggleButton); - protected override void OnPointerPressed(PointerPressedEventArgs e) + protected override async void OnPointerPressed(PointerPressedEventArgs e) { if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && DataContext is ViewModels.RevisionFileTreeNode { IsFolder: true } node) { var tree = this.FindAncestorOfType(); - tree?.ToggleNodeIsExpanded(node); + await tree?.ToggleNodeIsExpandedAsync(node); } e.Handled = true; @@ -81,8 +81,7 @@ namespace SourceGit.Views private void CreateContent(string iconKey, Thickness margin, IBrush fill = null) { - var geo = this.FindResource(iconKey) as StreamGeometry; - if (geo == null) + if (this.FindResource(iconKey) is not StreamGeometry geo) return; var icon = new Avalonia.Controls.Shapes.Path() @@ -107,13 +106,14 @@ namespace SourceGit.Views { protected override Type StyleKeyOverride => typeof(ListBox); - protected override void OnKeyDown(KeyEventArgs e) + protected override async void OnKeyDown(KeyEventArgs e) { if (SelectedItem is ViewModels.RevisionFileTreeNode { IsFolder: true } node && e.KeyModifiers == KeyModifiers.None) { if ((node.IsExpanded && e.Key == Key.Left) || (!node.IsExpanded && e.Key == Key.Right)) { - this.FindAncestorOfType()?.ToggleNodeIsExpanded(node); + var tree = this.FindAncestorOfType(); + await tree?.ToggleNodeIsExpandedAsync(node); e.Handled = true; } } @@ -141,7 +141,7 @@ namespace SourceGit.Views InitializeComponent(); } - public void SetSearchResult(string file) + public async Task SetSearchResultAsync(string file) { Rows.Clear(); _searchResult.Clear(); @@ -157,7 +157,7 @@ namespace SourceGit.Views if (vm?.Commit == null) return; - var objects = vm.GetRevisionFilesUnderFolder(file); + var objects = await vm.GetRevisionFilesUnderFolderAsync(file); if (objects is not { Count: 1 }) return; @@ -203,7 +203,7 @@ namespace SourceGit.Views GC.Collect(); } - public void ToggleNodeIsExpanded(ViewModels.RevisionFileTreeNode node) + public async Task ToggleNodeIsExpandedAsync(ViewModels.RevisionFileTreeNode node) { _disableSelectionChangingEvent = true; node.IsExpanded = !node.IsExpanded; @@ -215,8 +215,8 @@ namespace SourceGit.Views if (node.IsExpanded) { - var subtree = GetChildrenOfTreeNode(node); - if (subtree != null && subtree.Count > 0) + var subtree = await GetChildrenOfTreeNodeAsync(node); + if (subtree is { Count: > 0 }) { var subrows = new List(); MakeRows(subrows, subtree, depth + 1); @@ -240,7 +240,7 @@ namespace SourceGit.Views _disableSelectionChangingEvent = false; } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + protected override async void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -257,7 +257,7 @@ namespace SourceGit.Views return; } - var objects = vm.GetRevisionFilesUnderFolder(null); + var objects = await vm.GetRevisionFilesUnderFolderAsync(null); if (objects == null || objects.Count == 0) { GC.Collect(); @@ -281,17 +281,14 @@ namespace SourceGit.Views if (DataContext is ViewModels.CommitDetail vm && sender is Grid { DataContext: ViewModels.RevisionFileTreeNode { Backend: { } obj } } grid) { - if (obj.Type != Models.ObjectType.Tree) - { - var menu = vm.CreateRevisionFileContextMenu(obj); - menu?.Open(grid); - } + var menu = vm.CreateRevisionFileContextMenu(obj); + menu.Open(grid); } e.Handled = true; } - private void OnTreeNodeDoubleTapped(object sender, TappedEventArgs e) + private async void OnTreeNodeDoubleTapped(object sender, TappedEventArgs e) { if (sender is Grid { DataContext: ViewModels.RevisionFileTreeNode { IsFolder: true } node }) { @@ -299,22 +296,22 @@ namespace SourceGit.Views if (posX < node.Depth * 16 + 16) return; - ToggleNodeIsExpanded(node); + await ToggleNodeIsExpandedAsync(node); } } - private void OnRowsSelectionChanged(object sender, SelectionChangedEventArgs _) + private async void OnRowsSelectionChanged(object sender, SelectionChangedEventArgs _) { if (_disableSelectionChangingEvent || DataContext is not ViewModels.CommitDetail vm) return; if (sender is ListBox { SelectedItem: ViewModels.RevisionFileTreeNode { IsFolder: false } node }) - vm.ViewRevisionFile(node.Backend); + await vm.ViewRevisionFileAsync(node.Backend); else - vm.ViewRevisionFile(null); + await vm.ViewRevisionFileAsync(null); } - private List GetChildrenOfTreeNode(ViewModels.RevisionFileTreeNode node) + private async Task> GetChildrenOfTreeNodeAsync(ViewModels.RevisionFileTreeNode node) { if (!node.IsFolder) return null; @@ -322,11 +319,10 @@ namespace SourceGit.Views if (node.Children.Count > 0) return node.Children; - var vm = DataContext as ViewModels.CommitDetail; - if (vm == null) + if (DataContext is not ViewModels.CommitDetail vm) return null; - var objects = vm.GetRevisionFilesUnderFolder(node.Backend.Path + "/"); + var objects = await vm.GetRevisionFilesUnderFolderAsync(node.Backend.Path + "/"); if (objects == null || objects.Count == 0) return null; diff --git a/src/Views/RevisionFiles.axaml.cs b/src/Views/RevisionFiles.axaml.cs index 4894d1d4..dd51943c 100644 --- a/src/Views/RevisionFiles.axaml.cs +++ b/src/Views/RevisionFiles.axaml.cs @@ -11,15 +11,14 @@ namespace SourceGit.Views InitializeComponent(); } - private void OnSearchBoxKeyDown(object _, KeyEventArgs e) + private async void OnSearchBoxKeyDown(object _, KeyEventArgs e) { - var vm = DataContext as ViewModels.CommitDetail; - if (vm == null) + if (DataContext is not ViewModels.CommitDetail vm) return; if (e.Key == Key.Enter) { - FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + await FileTree.SetSearchResultAsync(vm.RevisionFileSearchFilter); e.Handled = true; } else if (e.Key == Key.Down || e.Key == Key.Up) @@ -39,16 +38,15 @@ namespace SourceGit.Views } } - private void OnSearchBoxTextChanged(object _, TextChangedEventArgs e) + private async void OnSearchBoxTextChanged(object _, TextChangedEventArgs e) { if (string.IsNullOrEmpty(TxtSearchRevisionFiles.Text)) - FileTree.SetSearchResult(null); + await FileTree.SetSearchResultAsync(null); } - private void OnSearchSuggestionBoxKeyDown(object _, KeyEventArgs e) + private async void OnSearchSuggestionBoxKeyDown(object _, KeyEventArgs e) { - var vm = DataContext as ViewModels.CommitDetail; - if (vm == null) + if (DataContext is not ViewModels.CommitDetail vm) return; if (e.Key == Key.Escape) @@ -60,15 +58,14 @@ namespace SourceGit.Views { vm.RevisionFileSearchFilter = content; TxtSearchRevisionFiles.CaretIndex = content.Length; - FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + await FileTree.SetSearchResultAsync(vm.RevisionFileSearchFilter); e.Handled = true; } } - private void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e) + private async void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e) { - var vm = DataContext as ViewModels.CommitDetail; - if (vm == null) + if (DataContext is not ViewModels.CommitDetail vm) return; var content = (sender as StackPanel)?.DataContext as string; @@ -76,7 +73,7 @@ namespace SourceGit.Views { vm.RevisionFileSearchFilter = content; TxtSearchRevisionFiles.CaretIndex = content.Length; - FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + await FileTree.SetSearchResultAsync(vm.RevisionFileSearchFilter); } e.Handled = true; @@ -85,7 +82,7 @@ namespace SourceGit.Views private async void OnOpenFileWithDefaultEditor(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.CommitDetail { CanOpenRevisionFileWithDefaultEditor: true } vm) - await vm.OpenRevisionFileWithDefaultEditor(vm.ViewRevisionFilePath); + await vm.OpenRevisionFileWithDefaultEditorAsync(vm.ViewRevisionFilePath); e.Handled = true; } diff --git a/src/Views/SelfUpdate.axaml.cs b/src/Views/SelfUpdate.axaml.cs index 6f4ab94c..97f1bc77 100644 --- a/src/Views/SelfUpdate.axaml.cs +++ b/src/Views/SelfUpdate.axaml.cs @@ -66,6 +66,7 @@ namespace SourceGit.Views { public SelfUpdate() { + CloseOnESC = true; InitializeComponent(); } diff --git a/src/Views/SetSubmoduleBranch.axaml b/src/Views/SetSubmoduleBranch.axaml new file mode 100644 index 00000000..5fb0281f --- /dev/null +++ b/src/Views/SetSubmoduleBranch.axaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/SetSubmoduleBranch.axaml.cs b/src/Views/SetSubmoduleBranch.axaml.cs new file mode 100644 index 00000000..ecc06b2a --- /dev/null +++ b/src/Views/SetSubmoduleBranch.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class SetSubmoduleBranch : UserControl + { + public SetSubmoduleBranch() + { + InitializeComponent(); + } + } +} + diff --git a/src/Views/StashChanges.axaml b/src/Views/StashChanges.axaml index 8ddc3adf..21225941 100644 --- a/src/Views/StashChanges.axaml +++ b/src/Views/StashChanges.axaml @@ -32,7 +32,7 @@ - + + Foreground="{DynamicResource Brush.FG2}"/> - + + + - - - - - - - - - - - - - - - - - - - - + + + - + + Text="{DynamicResource Text.Submodule.Branch}"/> + + + + + + + + @@ -190,12 +207,12 @@ - - - - diff --git a/src/Views/SubmodulesView.axaml.cs b/src/Views/SubmodulesView.axaml.cs index 81ccdc5d..52419445 100644 --- a/src/Views/SubmodulesView.axaml.cs +++ b/src/Views/SubmodulesView.axaml.cs @@ -71,8 +71,7 @@ namespace SourceGit.Views private void CreateContent(Thickness margin, string iconKey) { - var geo = this.FindResource(iconKey) as StreamGeometry; - if (geo == null) + if (this.FindResource(iconKey) is not StreamGeometry geo) return; Content = new Avalonia.Controls.Shapes.Path() @@ -164,7 +163,7 @@ namespace SourceGit.Views { if (sender is Control control && DataContext is ViewModels.Repository repo) { - if (control.DataContext is ViewModels.SubmoduleTreeNode node && node.Module != null) + if (control.DataContext is ViewModels.SubmoduleTreeNode { Module: not null } node) { var menu = repo.CreateContextMenuForSubmodule(node.Module); menu?.Open(control); diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index 1b384262..b73a5b5e 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -71,8 +71,7 @@ namespace SourceGit.Views private void CreateContent(Thickness margin, string iconKey) { - var geo = this.FindResource(iconKey) as StreamGeometry; - if (geo == null) + if (this.FindResource(iconKey) is not StreamGeometry geo) return; Content = new Avalonia.Controls.Shapes.Path() @@ -181,8 +180,7 @@ namespace SourceGit.Views private void OnItemContextRequested(object sender, ContextRequestedEventArgs e) { - var control = sender as Control; - if (control == null) + if (sender is not Control control) return; Models.Tag selected; @@ -205,7 +203,7 @@ namespace SourceGit.Views private void OnSelectionChanged(object sender, SelectionChangedEventArgs _) { var selected = (sender as ListBox)?.SelectedItem; - var selectedTag = null as Models.Tag; + Models.Tag selectedTag = null; if (selected is ViewModels.TagTreeNode node) selectedTag = node.Tag; else if (selected is Models.Tag tag) @@ -217,6 +215,9 @@ namespace SourceGit.Views private void OnKeyDown(object sender, KeyEventArgs e) { + if (e.Key is not (Key.Delete or Key.Back)) + return; + if (DataContext is not ViewModels.Repository repo) return; diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index b84a6423..8c785554 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -4,7 +4,7 @@ using System.ComponentModel; using System.Globalization; using System.IO; using System.Text; - +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Data; @@ -101,7 +101,7 @@ namespace SourceGit.Views var lines = presenter.GetLines(); var view = TextView; - if (view != null && view.VisualLinesValid) + if (view is { VisualLinesValid: true }) { var typeface = view.CreateTypeface(); foreach (var line in view.VisualLines) @@ -175,7 +175,7 @@ namespace SourceGit.Views var lines = presenter.GetLines(); var view = TextView; - if (view != null && view.VisualLinesValid) + if (view is { VisualLinesValid: true }) { var typeface = view.CreateTypeface(); foreach (var line in view.VisualLines) @@ -189,7 +189,7 @@ namespace SourceGit.Views var info = lines[index - 1]; var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineMiddle) - view.VerticalOffset; - var indicator = null as FormattedText; + FormattedText indicator = null; if (info.Type == Models.TextDiffLineType.Added) { indicator = new FormattedText( @@ -545,14 +545,11 @@ namespace SourceGit.Views public virtual void GotoFirstChange() { var blockNavigation = BlockNavigation; - if (blockNavigation != null) + var prev = blockNavigation?.GotoFirst(); + if (prev != null) { - var prev = blockNavigation.GotoFirst(); - if (prev != null) - { - TextArea.Caret.Line = prev.Start; - ScrollToLine(prev.Start); - } + TextArea.Caret.Line = prev.Start; + ScrollToLine(prev.Start); } } @@ -637,9 +634,7 @@ namespace SourceGit.Views 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 (nextType is Models.TextDiffLineType.None or Models.TextDiffLineType.Added or Models.TextDiffLineType.Deleted) { if (findNormalLine) { @@ -657,14 +652,11 @@ namespace SourceGit.Views public virtual void GotoLastChange() { var blockNavigation = BlockNavigation; - if (blockNavigation != null) + var next = blockNavigation?.GotoLast(); + if (next != null) { - var next = blockNavigation.GotoLast(); - if (next != null) - { - TextArea.Caret.Line = next.Start; - ScrollToLine(next.Start); - } + TextArea.Caret.Line = next.Start; + ScrollToLine(next.Start); } } @@ -764,13 +756,13 @@ namespace SourceGit.Views } } - private void OnTextAreaKeyDown(object sender, KeyEventArgs e) + private async void OnTextAreaKeyDown(object sender, KeyEventArgs e) { if (e.KeyModifiers.Equals(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control)) { if (e.Key == Key.C) { - CopyWithoutIndicators(); + await CopyWithoutIndicatorsAsync(); e.Handled = true; } } @@ -794,9 +786,9 @@ namespace SourceGit.Views var copy = new MenuItem(); copy.Header = App.Text("Copy"); copy.Icon = App.CreateMenuIcon("Icons.Copy"); - copy.Click += (_, ev) => + copy.Click += async (_, ev) => { - CopyWithoutIndicators(); + await CopyWithoutIndicatorsAsync(); ev.Handled = true; }; @@ -989,12 +981,12 @@ namespace SourceGit.Views } } - private void CopyWithoutIndicators() + private async Task CopyWithoutIndicatorsAsync() { var selection = TextArea.Selection; if (selection.IsEmpty) { - App.CopyText(string.Empty); + await App.CopyTextAsync(string.Empty); return; } @@ -1011,15 +1003,10 @@ namespace SourceGit.Views if (startIdx == endIdx) { - var line = lines[startIdx]; - if (line.Type == Models.TextDiffLineType.Indicator || - line.Type == Models.TextDiffLineType.None) - { - App.CopyText(string.Empty); - return; - } - - App.CopyText(SelectedText); + if (lines[startIdx].Type is Models.TextDiffLineType.Indicator or Models.TextDiffLineType.None) + await App.CopyTextAsync(string.Empty); + else + await App.CopyTextAsync(SelectedText); return; } @@ -1027,8 +1014,7 @@ namespace SourceGit.Views for (var i = startIdx; i <= endIdx && i <= lines.Count - 1; i++) { var line = lines[i]; - if (line.Type == Models.TextDiffLineType.Indicator || - line.Type == Models.TextDiffLineType.None) + if (line.Type is Models.TextDiffLineType.Indicator or Models.TextDiffLineType.None) continue; // The first selected line (partial selection) @@ -1064,7 +1050,7 @@ namespace SourceGit.Views builder.AppendLine(line.Content); } - App.CopyText(builder.ToString()); + await App.CopyTextAsync(builder.ToString()); } private TextMate.Installation _textMate = null; @@ -1100,8 +1086,7 @@ namespace SourceGit.Views public override void UpdateSelectedChunk(double y) { - var diff = DataContext as Models.TextDiff; - if (diff == null) + if (DataContext is not Models.TextDiff diff) return; var view = TextArea.TextView; @@ -1327,8 +1312,7 @@ namespace SourceGit.Views public override void UpdateSelectedChunk(double y) { - var diff = DataContext as ViewModels.TwoSideTextDiff; - if (diff == null) + if (DataContext is not ViewModels.TwoSideTextDiff diff) return; var parent = this.FindAncestorOfType(); @@ -1823,17 +1807,15 @@ namespace SourceGit.Views TryRaiseBlockNavigationChanged(); } - private void OnStageChunk(object _1, RoutedEventArgs _2) + private async void OnStageChunk(object _1, RoutedEventArgs _2) { var chunk = SelectedChunk; if (chunk == null) return; var diff = DataContext as Models.TextDiff; - if (diff == null) - return; - var change = diff.Option.WorkingCopyChange; + var change = diff?.Option.WorkingCopyChange; if (change == null) return; @@ -1842,18 +1824,15 @@ namespace SourceGit.Views return; var repoView = this.FindAncestorOfType(); - if (repoView == null) - return; - var repo = repoView.DataContext as ViewModels.Repository; - if (repo == null) + if (repoView?.DataContext is not ViewModels.Repository repo) return; repo.SetWatcherEnabled(false); if (!selection.HasLeftChanges) { - new Commands.Add(repo.FullPath, change).Exec(); + await new Commands.Add(repo.FullPath, change).ExecAsync(); } else { @@ -1864,16 +1843,16 @@ namespace SourceGit.Views } else if (chunk.Combined) { - var treeGuid = new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).Result(); + var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync(); diff.GeneratePatchFromSelection(change, treeGuid, selection, false, tmpFile); } else { - var treeGuid = new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).Result(); + var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync(); diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, false, chunk.IsOldSide, tmpFile); } - new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index").Exec(); + await new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index").ExecAsync(); File.Delete(tmpFile); } @@ -1881,17 +1860,15 @@ namespace SourceGit.Views repo.SetWatcherEnabled(true); } - private void OnUnstageChunk(object _1, RoutedEventArgs _2) + private async void OnUnstageChunk(object _1, RoutedEventArgs _2) { var chunk = SelectedChunk; if (chunk == null) return; var diff = DataContext as Models.TextDiff; - if (diff == null) - return; - var change = diff.Option.WorkingCopyChange; + var change = diff?.Option.WorkingCopyChange; if (change == null) return; @@ -1900,11 +1877,8 @@ namespace SourceGit.Views return; var repoView = this.FindAncestorOfType(); - if (repoView == null) - return; - var repo = repoView.DataContext as ViewModels.Repository; - if (repo == null) + if (repoView?.DataContext is not ViewModels.Repository repo) return; repo.SetWatcherEnabled(false); @@ -1912,13 +1886,13 @@ namespace SourceGit.Views if (!selection.HasLeftChanges) { if (change.DataForAmend != null) - new Commands.UnstageChangesForAmend(repo.FullPath, [change]).Exec(); + await new Commands.UnstageChangesForAmend(repo.FullPath, [change]).ExecAsync(); else - new Commands.Restore(repo.FullPath, change).Exec(); + await new Commands.Restore(repo.FullPath, change).ExecAsync(); } else { - var treeGuid = new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).Result(); + var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync(); var tmpFile = Path.GetTempFileName(); if (change.Index == Models.ChangeState.Added) diff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile); @@ -1927,7 +1901,7 @@ namespace SourceGit.Views else diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile); - new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index --reverse").Exec(); + await new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index --reverse").ExecAsync(); File.Delete(tmpFile); } @@ -1935,17 +1909,15 @@ namespace SourceGit.Views repo.SetWatcherEnabled(true); } - private void OnDiscardChunk(object _1, RoutedEventArgs _2) + private async void OnDiscardChunk(object _1, RoutedEventArgs _2) { var chunk = SelectedChunk; if (chunk == null) return; var diff = DataContext as Models.TextDiff; - if (diff == null) - return; - var change = diff.Option.WorkingCopyChange; + var change = diff?.Option.WorkingCopyChange; if (change == null) return; @@ -1954,18 +1926,15 @@ namespace SourceGit.Views return; var repoView = this.FindAncestorOfType(); - if (repoView == null) - return; - var repo = repoView.DataContext as ViewModels.Repository; - if (repo == null) + if (repoView?.DataContext is not ViewModels.Repository repo) return; repo.SetWatcherEnabled(false); if (!selection.HasLeftChanges) { - Commands.Discard.Changes(repo.FullPath, [change], null); + await Commands.Discard.ChangesAsync(repo.FullPath, [change], null); } else { @@ -1976,16 +1945,16 @@ namespace SourceGit.Views } else if (chunk.Combined) { - var treeGuid = new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).Result(); + var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync(); diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile); } else { - var treeGuid = new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).Result(); + var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync(); diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile); } - new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--reverse").Exec(); + await new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--reverse").ExecAsync(); File.Delete(tmpFile); } diff --git a/src/Views/UpdateSubmodules.axaml b/src/Views/UpdateSubmodules.axaml index f189b80d..2b732036 100644 --- a/src/Views/UpdateSubmodules.axaml +++ b/src/Views/UpdateSubmodules.axaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.UpdateSubmodules" @@ -10,7 +11,7 @@ - + + SelectedItem="{Binding SelectedSubmodule, Mode=TwoWay}"> + + + + + + + - + - + + IsChecked="{Binding UpdateAll, Mode=TwoWay}" + IsVisible="{Binding !HasPreSelectedSubmodule, Mode=OneWay}"/> + copy.Click += async (_, ev) => { - App.CopyText(log.Content); + await App.CopyTextAsync(log.Content); ev.Handled = true; }; diff --git a/src/Views/Welcome.axaml.cs b/src/Views/Welcome.axaml.cs index 75ac4079..712e674d 100644 --- a/src/Views/Welcome.axaml.cs +++ b/src/Views/Welcome.axaml.cs @@ -208,11 +208,8 @@ namespace SourceGit.Views if (e.Data.Contains("MovedRepositoryTreeNode") || e.Data.Contains(DataFormats.Files)) { var grid = sender as Grid; - if (grid == null) - return; - var to = grid.DataContext as ViewModels.RepositoryNode; - if (to == null) + if (grid?.DataContext is not ViewModels.RepositoryNode) return; e.DragEffects = DragDropEffects.Move; @@ -225,8 +222,7 @@ namespace SourceGit.Views if (sender is not Grid grid) return; - var to = grid.DataContext as ViewModels.RepositoryNode; - if (to == null) + if (grid.DataContext is not ViewModels.RepositoryNode to) { e.Handled = true; return; diff --git a/src/Views/WelcomeToolbar.axaml.cs b/src/Views/WelcomeToolbar.axaml.cs index 2918a570..8e25f34a 100644 --- a/src/Views/WelcomeToolbar.axaml.cs +++ b/src/Views/WelcomeToolbar.axaml.cs @@ -24,10 +24,16 @@ namespace SourceGit.Views if (topLevel == null) return; + var preference = ViewModels.Preferences.Instance; + var workspace = preference.GetActiveWorkspace(); + var initDir = workspace.DefaultCloneDir; + if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir)) + initDir = preference.GitDefaultCloneDir; + var options = new FolderPickerOpenOptions() { AllowMultiple = false }; - if (Directory.Exists(ViewModels.Preferences.Instance.GitDefaultCloneDir)) + if (Directory.Exists(initDir)) { - var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(ViewModels.Preferences.Instance.GitDefaultCloneDir); + var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir); options.SuggestedStartLocation = folder; } diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 39ae1a7e..6e5e6229 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -73,7 +73,7 @@ Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Unstaged.ViewAssumeUnchanged}" - Command="{Binding OpenAssumeUnchanged}"> + Click="OnOpenAssumeUnchanged"> (); + if (repoView is { DataContext: ViewModels.Repository repo }) + await App.ShowDialog(new ViewModels.AssumeUnchangedManager(repo)); + + e.Handled = true; + } + private void OnUnstagedContextRequested(object sender, ContextRequestedEventArgs e) { if (DataContext is ViewModels.WorkingCopy vm && sender is Control control)