Merge branch 'release/v2025.25'

This commit is contained in:
leo
2025-07-07 11:11:05 +08:00
345 changed files with 5051 additions and 3822 deletions

View File

@@ -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

View File

@@ -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/*

22
.github/workflows/format-check.yml vendored Normal file
View File

@@ -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

View File

@@ -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 ]

View File

@@ -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 |

View File

@@ -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}"

View File

@@ -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)
<details>
<summary>Missing keys in de_DE.axaml</summary>
### ![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
</details>
### ![es__ES](https://img.shields.io/badge/es__ES-97.51%25-yellow)
<details>
<summary>Missing keys in es_ES.axaml</summary>
- 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
</details>
### ![fr__FR](https://img.shields.io/badge/fr__FR-86.26%25-yellow)
<details>
<summary>Missing keys in fr_FR.axaml</summary>
@@ -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
</details>
### ![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)
<details>
<summary>Missing keys in it_IT.axaml</summary>
@@ -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
</details>
### ![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)
<details>
<summary>Missing keys in ja_JP.axaml</summary>
@@ -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
</details>
### ![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)
<details>
<summary>Missing keys in pt_BR.axaml</summary>
@@ -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)
<details>
<summary>Missing keys in ta_IN.axaml</summary>
@@ -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
</details>
### ![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)
<details>
<summary>Missing keys in uk_UA.axaml</summary>
@@ -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

View File

@@ -1 +1 @@
2025.24
2025.25

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 KiB

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 KiB

After

Width:  |  Height:  |  Size: 471 KiB

View File

@@ -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);
});
}
}

View File

@@ -34,6 +34,20 @@ namespace SourceGit
}
}
public class DataGridLengthConverter : JsonConverter<DataGridLength>
{
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))]

View File

@@ -28,6 +28,7 @@
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
<StyleInclude Source="/Resources/Styles.axaml"/>
</Application.Styles>

View File

@@ -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<bool> 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<bool>(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<string> 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<string>();
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 });
});
}

View File

@@ -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<Models.BlameData> 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);
}
}
}

View File

@@ -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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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);
}
}
}

View File

@@ -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<bool> 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<bool> 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<bool> 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<string> files)
public async Task<bool> UseTheirsAsync(List<string> 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<string> files)
public async Task<bool> UseMineAsync(List<string> 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<bool> FileWithRevisionAsync(string file, string revision)
{
Args = $"checkout --no-overlay {revision} -- \"{file}\"";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
}
}

View File

@@ -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<bool> 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<Result> 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;
}

View File

@@ -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<bool> 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;
}
}

View File

@@ -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<Models.Change> Result()
public async Task<List<Models.Change>> ReadAsync()
{
var rs = ReadToEnd();
var changes = new List<Models.Change>();
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<Models.Change> 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<Models.Change> _changes = new List<Models.Change>();
}
}

View File

@@ -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<string, string> ListAll()
public async Task<Dictionary<string, string>> ReadAllAsync()
{
Args = "config -l";
var output = ReadToEnd();
var output = await ReadToEndAsync().ConfigureAwait(false);
var rs = new Dictionary<string, string>();
if (output.IsSuccess)
{
@@ -45,13 +44,15 @@ namespace SourceGit.Commands
return rs;
}
public string Get(string key)
public async Task<string> 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<bool> 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;

View File

@@ -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<int> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (rs.IsSuccess)
{
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);

View File

@@ -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<Models.DiffResult> 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));
}
}
}
}

View File

@@ -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
/// <param name="repo"></param>
/// <param name="includeIgnored"></param>
/// <param name="log"></param>
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);
}
/// <summary>
@@ -54,7 +50,7 @@ namespace SourceGit.Commands
/// <param name="repo"></param>
/// <param name="changes"></param>
/// <param name="log"></param>
public static void Changes(string repo, List<Models.Change> changes, Models.ICommandLog log)
public static async Task ChangesAsync(string repo, List<Models.Change> changes, Models.ICommandLog log)
{
var restores = new List<string>();
@@ -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);
}
}

View File

@@ -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<bool> RunAsync()
{
SSHKey = await new Config(WorkingDirectory).GetAsync(_remoteKey).ConfigureAwait(false);
return await ExecAsync().ConfigureAwait(false);
}
private readonly string _remoteKey;
}
}

View File

@@ -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<Result> ReadAsync()
{
return await ReadToEndAsync().ConfigureAwait(false);
}
}
public GenerateCommitMessage(Models.OpenAIService service, string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> 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}");
}
}

View File

@@ -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<bool> 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<bool> 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<bool> 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);
}
}
}

View File

@@ -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<bool> 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";
}
}

View File

@@ -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<bool> GetResultAsync()
{
return REG_TEST().IsMatch(ReadToEnd().StdOut);
var rs = await ReadToEndAsync().ConfigureAwait(false);
return REG_TEST().IsMatch(rs.StdOut.Trim());
}
}
}

View File

@@ -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<bool> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
return rs.IsSuccess && rs.StdOut.Trim().Equals("commit");
}
}

View File

@@ -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<bool> GetResultAsync()
{
return ReadToEnd().IsSuccess;
var rs = await ReadToEndAsync().ConfigureAwait(false);
return rs.IsSuccess;
}
}
}

View File

@@ -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<bool> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
return rs.IsSuccess && rs.StdOut.Contains("filter\0lfs");
}
}

View File

@@ -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<Result> 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<bool> 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<bool> 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<Models.LFSLock> Locks(string remote)
public async Task<List<Models.LFSLock>> GetLocksAsync(string remote)
{
var locks = new List<Models.LFSLock>();
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<bool> 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<bool> 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<bool> 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;

View File

@@ -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<bool> 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<bool> 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);
}
}
}

25
src/Commands/Move.cs Normal file
View File

@@ -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();
}
}
}

View File

@@ -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<bool> RunAsync()
{
SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.sshkey").ConfigureAwait(false);
return await ExecAsync().ConfigureAwait(false);
}
private readonly string _remote;
}
}

View File

@@ -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<bool> RunAsync()
{
SSHKey = await new Config(WorkingDirectory).GetAsync($"remote.{_remote}.sshkey").ConfigureAwait(false);
return await ExecAsync().ConfigureAwait(false);
}
private readonly string _remote;
}
}

View File

@@ -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<string> Result()
public async Task<List<string>> GetResultAsync()
{
var outs = new List<string>();
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{

View File

@@ -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<Models.Branch> Result(out int localBranchesCount)
public async Task<List<Models.Branch>> GetResultAsync()
{
localBranchesCount = 0;
var branches = new List<Models.Branch>();
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
{

View File

@@ -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<string> Result()
public async Task<List<string>> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
var outs = new List<string>();
if (rs.IsSuccess)
{

View File

@@ -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<string> 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;
}
}
}

View File

@@ -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<Models.CommitSignInfo> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return null;

View File

@@ -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<Models.Commit> Result()
public async Task<List<Models.Commit>> 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<string>();
foreach (var sha in shas)
set.Add(sha);
var set = new HashSet<string>(shas);
foreach (var c in _commits)
{

View File

@@ -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<Models.InteractiveCommit> Result()
public async Task<List<Models.InteractiveCommit>> 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<Models.InteractiveCommit> _commits = [];

View File

@@ -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<string> GetResultAsync()
{
var rs = await ReadToEndAsync().ConfigureAwait(false);
return rs.StdOut.Trim();
}
}
}

View File

@@ -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<Stream> 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<Stream> 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;

View File

@@ -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<long> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (rs.IsSuccess)
{
var match = REG_FORMAT().Match(rs.StdOut);

View File

@@ -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<string> 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));
}
}
}

View File

@@ -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<string> 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));
}
}
}

View File

@@ -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<Models.Change> Result()
public async Task<List<Models.Change>> GetResultAsync()
{
var outs = new List<Models.Change>();
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;
}

View File

@@ -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<Models.Decorator> Result()
public async Task<List<Models.Decorator>> GetResultAsync()
{
var rs = new List<Models.Decorator>();
var outs = new List<Models.Decorator>();
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;
}
}
}

View File

@@ -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<Models.Remote> Result()
public async Task<List<Models.Remote>> GetResultAsync()
{
var outs = new List<Models.Remote>();
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return outs;

View File

@@ -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<Result> GetResultAsync()
{
return await ReadToEndAsync().ConfigureAwait(false);
}
}
}

View File

@@ -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<string> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut))
return rs.StdOut.Trim();

View File

@@ -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<string> Result()
public async Task<List<string>> 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<string>();
foreach (var line in lines)
outs.Add(line);
return outs;
return [.. lines];
}
}
}

View File

@@ -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<Models.Object> Result()
public async Task<List<Models.Object>> GetResultAsync()
{
var rs = ReadToEnd();
var outs = new List<Models.Object>();
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<Models.Object> 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<Models.Object> _objects = new List<Models.Object>();
}
}

View File

@@ -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<Models.Commit> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut))
{
var commit = new Models.Commit();

View File

@@ -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<Models.Change> Result()
public async Task<List<Models.Change>> GetResultAsync()
{
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return [];

View File

@@ -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<string> 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;
}
}
}

View File

@@ -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<Models.Stash> Result()
public async Task<List<Models.Stash>> GetResultAsync()
{
var outs = new List<Models.Stash>();
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return outs;

View File

@@ -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<Models.Submodule> Result()
public async Task<List<Models.Submodule>> GetResultAsync()
{
var submodules = new List<Models.Submodule>();
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
var map = new Dictionary<string, Models.Submodule>();
@@ -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<string, ModuleInfo>();
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;
}
}
}

View File

@@ -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<Models.Tag> Result()
public async Task<List<Models.Tag>> GetResultAsync()
{
var tags = new List<Models.Tag>();
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return tags;

View File

@@ -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<Models.BranchTrackStatus> GetResultAsync()
{
var status = new Models.BranchTrackStatus();
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return status;

View File

@@ -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<string> Result()
public async Task<List<string>> GetResultAsync()
{
var submodules = new List<string>();
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)

View File

@@ -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<bool> AddAsync(string name, string url)
{
Args = $"remote add {name} {url}";
return Exec();
return await ExecAsync();
}
public bool Delete(string name)
public async Task<bool> DeleteAsync(string name)
{
Args = $"remote remove {name}";
return Exec();
return await ExecAsync();
}
public bool Rename(string name, string to)
public async Task<bool> RenameAsync(string name, string to)
{
Args = $"remote rename {name} {to}";
return Exec();
return await ExecAsync();
}
public bool Prune(string name)
public async Task<bool> PruneAsync(string name)
{
Args = $"remote prune {name}";
return Exec();
return await ExecAsync();
}
public string GetURL(string name, bool isPush)
public async Task<string> 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<bool> 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<bool> 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;
}
}

View File

@@ -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<Models.Change> changes, bool isUnstaged, string saveTo)
public static async Task<bool> ProcessLocalChangesAsync(string repo, List<Models.Change> 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<Models.Change> changes, string baseRevision, string targetRevision, string saveTo)
public static async Task<bool> ProcessRevisionCompareChangesAsync(string repo, List<Models.Change> 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<Models.DiffOption> opts, string saveTo)
public static async Task<bool> ProcessStashChangesAsync(string repo, List<Models.DiffOption> 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<bool> 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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<bool> 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<Models.Change> changes, bool keepIndex)
public async Task<bool> PushAsync(string message, List<Models.Change> 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<bool> 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<bool> 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<bool> 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<bool> PopAsync(string name)
{
Args = $"stash pop -q --index \"{name}\"";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
public bool Drop(string name)
public async Task<bool> DropAsync(string name)
{
Args = $"stash drop -q \"{name}\"";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
public bool Clear()
public async Task<bool> ClearAsync()
{
Args = "stash clear";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
}
}

View File

@@ -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<Models.Statistics> ReadAsync()
{
var statistics = new Models.Statistics();
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return statistics;

View File

@@ -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<bool> 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<string> modules, bool init, bool recursive, bool useRemote = false)
public async Task<bool> SetURLAsync(string path, string url)
{
Args = $"submodule set-url -- \"{path}\" \"{url}\"";
return await ExecAsync().ConfigureAwait(false);
}
public async Task<bool> 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<bool> UpdateAsync(List<string> 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<bool> 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<bool> DeleteAsync(string module)
{
Args = $"rm -rf \"{module}\"";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
}
}

View File

@@ -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<bool> 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<bool> 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<bool> 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);
}
}
}

View File

@@ -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<bool> 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;
}
}

View File

@@ -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<Models.Worktree> List()
public async Task<List<Models.Worktree>> ReadAllAsync()
{
Args = "worktree list --porcelain";
var rs = ReadToEnd();
var rs = await ReadToEndAsync().ConfigureAwait(false);
var worktrees = new List<Models.Worktree>();
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<bool> 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<bool> PruneAsync()
{
Args = "worktree prune -v";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
public bool Lock(string fullpath)
public async Task<bool> LockAsync(string fullpath)
{
Args = $"worktree lock \"{fullpath}\"";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
public bool Unlock(string fullpath)
public async Task<bool> UnlockAsync(string fullpath)
{
Args = $"worktree unlock \"{fullpath}\"";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
public bool Remove(string fullpath, bool force)
public async Task<bool> RemoveAsync(string fullpath, bool force)
{
if (force)
Args = $"worktree remove -f \"{fullpath}\"";
else
Args = $"worktree remove \"{fullpath}\"";
return Exec();
return await ExecAsync().ConfigureAwait(false);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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, "-");
}
}
}

View File

@@ -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<CRLFMode> Supported = new List<CRLFMode>() {
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;
}
}
}

View File

@@ -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<Pen> 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

View File

@@ -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/"));
}
}

View File

@@ -16,6 +16,7 @@ namespace SourceGit.Models
TextBox = 0,
PathSelector,
CheckBox,
ComboBox,
}
public class CustomActionControl : ObservableObject

View File

@@ -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<DealWithChangesAfterStashing> 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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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<string>();
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);
}
}
}

View File

@@ -154,6 +154,11 @@ namespace SourceGit.Models
TryAdd("Zed", "zed", platformFinder);
}
public void Cursor(Func<string> platformFinder)
{
TryAdd("Cursor", "cursor", platformFinder);
}
public void FindJetBrainsFromToolbox(Func<string> platformFinder)
{
var exclude = new List<string> { "fleet", "dotmemory", "dottrace", "resharper-u", "androidstudio" };

View File

@@ -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;
}
}

View File

@@ -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<string> onUpdate)
public async Task ChatAsync(string prompt, string question, CancellationToken cancellation, Action<string> 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);

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -50,7 +50,7 @@ namespace SourceGit.Models
var ret = new List<TextInlineChange>();
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<Chunk> chunks, Dictionary<string, int> 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));
}
}
}

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -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"));

View File

@@ -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");

View File

@@ -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)

View File

@@ -89,7 +89,7 @@
<StreamGeometry x:Key="Icons.Modified">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</StreamGeometry>
<StreamGeometry x:Key="Icons.More">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Move">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</StreamGeometry>
<StreamGeometry x:Key="Icons.MoveToAnotherGroup">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</StreamGeometry>
<StreamGeometry x:Key="Icons.MoveTo">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</StreamGeometry>
<StreamGeometry x:Key="Icons.OpenWith">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</StreamGeometry>
<StreamGeometry x:Key="Icons.OrderByName">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</StreamGeometry>
<StreamGeometry x:Key="Icons.OrderByTime">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</StreamGeometry>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Some files were not shown because too many files have changed in this diff Show More