mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-21 18:35:00 +00:00
Merge
This commit is contained in:
commit
75b15c455e
284 changed files with 10116 additions and 5764 deletions
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
|
@ -19,14 +19,25 @@ jobs:
|
|||
os: macos-latest
|
||||
runtime: osx-arm64
|
||||
- name : Linux
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
runtime: linux-x64
|
||||
container: ubuntu:20.04
|
||||
- name : Linux (arm64)
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
runtime: linux-arm64
|
||||
container: ubuntu:20.04
|
||||
name: Build ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container || '' }}
|
||||
steps:
|
||||
- name: Install common CLI tools
|
||||
if: ${{ startsWith(matrix.runtime, 'linux-') }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
||||
apt-get update
|
||||
apt-get install -y sudo
|
||||
sudo apt-get install -y curl wget git unzip zip libicu66 tzdata clang
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
|
@ -47,7 +58,7 @@ jobs:
|
|||
if: ${{ matrix.runtime == 'linux-arm64' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
|
||||
sudo apt-get install -y llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
|
||||
- name: Build
|
||||
run: dotnet build -c Release
|
||||
- name: Publish
|
||||
|
|
14
.github/workflows/package.yml
vendored
14
.github/workflows/package.yml
vendored
|
@ -9,10 +9,10 @@ on:
|
|||
jobs:
|
||||
windows:
|
||||
name: Package Windows
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: windows-2019
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: [win-x64, win-arm64]
|
||||
runtime: [ win-x64, win-arm64 ]
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
@ -22,6 +22,7 @@ jobs:
|
|||
name: sourcegit.${{ matrix.runtime }}
|
||||
path: build/SourceGit
|
||||
- name: Package
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
RUNTIME: ${{ matrix.runtime }}
|
||||
|
@ -69,6 +70,7 @@ jobs:
|
|||
linux:
|
||||
name: Package Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:20.04
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: [linux-x64, linux-arm64]
|
||||
|
@ -77,9 +79,10 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
- name: Download package dependencies
|
||||
run: |
|
||||
sudo add-apt-repository universe
|
||||
sudo apt-get update
|
||||
sudo apt-get install desktop-file-utils rpm libfuse2
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
||||
apt-get update
|
||||
apt-get install -y curl wget git dpkg-dev fakeroot tzdata zip unzip desktop-file-utils rpm libfuse2 file build-essential binutils
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
@ -89,6 +92,7 @@ jobs:
|
|||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
RUNTIME: ${{ matrix.runtime }}
|
||||
APPIMAGE_EXTRACT_AND_RUN: 1
|
||||
run: |
|
||||
mkdir build/SourceGit
|
||||
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit
|
||||
|
|
4
LICENSE
4
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 sourcegit
|
||||
Copyright (c) 2025 sourcegit
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
30
README.md
30
README.md
|
@ -11,16 +11,16 @@
|
|||
* Supports Windows/macOS/Linux
|
||||
* Opensource/Free
|
||||
* Fast
|
||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/简体中文/繁體中文
|
||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
||||
* Built-in light/dark themes
|
||||
* Customize theme
|
||||
* Visual commit graph
|
||||
* Supports SSH access with each remote
|
||||
* GIT commands with GUI
|
||||
* Clone/Fetch/Pull/Push...
|
||||
* Merge/Rebase/Reset/Revert/Amend/Cherry-pick...
|
||||
* Amend/Reword
|
||||
* Interactive rebase (Basic)
|
||||
* Merge/Rebase/Reset/Revert/Cherry-pick...
|
||||
* Amend/Reword/Squash
|
||||
* Interactive rebase
|
||||
* Branches
|
||||
* Remotes
|
||||
* Tags
|
||||
|
@ -40,6 +40,7 @@
|
|||
* Git LFS
|
||||
* Issue Link
|
||||
* Workspace
|
||||
* Custom Action
|
||||
* Using AI to generate commit message (C# port of [anjerodev/commitollama](https://github.com/anjerodev/commitollama))
|
||||
|
||||
> [!WARNING]
|
||||
|
@ -47,10 +48,7 @@
|
|||
|
||||
## Translation Status
|
||||
|
||||
[](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md)
|
||||
|
||||
> [!NOTE]
|
||||
> You can find the missing keys in [TRANSLATION.md](TRANSLATION.md)
|
||||
You can find the current translation status in [TRANSLATION.md](https://github.com/sourcegit-scm/sourcegit/blob/develop/TRANSLATION.md)
|
||||
|
||||
## How to Use
|
||||
|
||||
|
@ -62,7 +60,7 @@ This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationD
|
|||
|
||||
| OS | PATH |
|
||||
|---------|-----------------------------------------------------|
|
||||
| Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` |
|
||||
| Windows | `%APPDATA%\SourceGit` |
|
||||
| Linux | `${HOME}/.config/SourceGit` or `${HOME}/.sourcegit` |
|
||||
| macOS | `${HOME}/Library/Application Support/SourceGit` |
|
||||
|
||||
|
@ -79,7 +77,7 @@ For **Windows** users:
|
|||
```
|
||||
> [!NOTE]
|
||||
> `winget` will install this software as a commandline tool. You need run `SourceGit` from console or `Win+R` at the first time. Then you can add it to the taskbar.
|
||||
* You can install the latest stable by `scoope` with follow commands:
|
||||
* You can install the latest stable by `scoop` with follow commands:
|
||||
```shell
|
||||
scoop bucket add extras
|
||||
scoop install sourcegit
|
||||
|
@ -107,7 +105,7 @@ For **Linux** users:
|
|||
`deb` how to:
|
||||
```shell
|
||||
curl https://codeberg.org/api/packages/yataro/debian/repository.key | sudo tee /etc/apt/keyrings/sourcegit.asc
|
||||
echo "deb [signed-by=/etc/apt/keyrings/sourcegit.asc] https://codeberg.org/api/packages/yataro/debian generic main" | sudo tee /etc/apt/sources.list.d/sourcegit.list
|
||||
echo "deb [signed-by=/etc/apt/keyrings/sourcegit.asc, arch=amd64,arm64] https://codeberg.org/api/packages/yataro/debian generic main" | sudo tee /etc/apt/sources.list.d/sourcegit.list
|
||||
sudo apt update
|
||||
sudo apt install sourcegit
|
||||
```
|
||||
|
@ -132,15 +130,15 @@ For **Linux** users:
|
|||
|
||||
## OpenAI
|
||||
|
||||
This software supports using OpenAI or other AI service that has an OpenAI comaptible HTTP API to generate commit message. You need configurate the service in `Preference` window.
|
||||
This software supports using OpenAI or other AI service that has an OpenAI compatible HTTP API to generate commit message. You need configurate the service in `Preference` window.
|
||||
|
||||
For `OpenAI`:
|
||||
|
||||
* `Server` must be `https://api.openai.com/v1/chat/completions`
|
||||
* `Server` must be `https://api.openai.com/v1`
|
||||
|
||||
For other AI service:
|
||||
|
||||
* The `Server` should fill in a URL equivalent to OpenAI's `https://api.openai.com/v1/chat/completions`. For example, when using `Ollama`, it should be `http://localhost:11434/v1/chat/completions` instead of `http://localhost:11434/api/generate`
|
||||
* The `Server` should fill in a URL equivalent to OpenAI's `https://api.openai.com/v1`. For example, when using `Ollama`, it should be `http://localhost:11434/v1` instead of `http://localhost:11434/api/generate`
|
||||
* The `API Key` is optional that depends on the service
|
||||
|
||||
## External Tools
|
||||
|
@ -201,3 +199,7 @@ dotnet run --project src/SourceGit.csproj
|
|||
Thanks to all the people who contribute.
|
||||
|
||||
[](https://github.com/sourcegit-scm/sourcegit/graphs/contributors)
|
||||
|
||||
## Third-Party Components
|
||||
|
||||
For detailed license information, see [THIRD-PARTY-LICENSES.md](THIRD-PARTY-LICENSES.md).
|
||||
|
|
|
@ -60,6 +60,8 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DEBIAN", "DEBIAN", "{F101849D-BDB7-40D4-A516-751150C3CCFC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\resources\deb\DEBIAN\control = build\resources\deb\DEBIAN\control
|
||||
build\resources\deb\DEBIAN\preinst = build\resources\deb\DEBIAN\preinst
|
||||
build\resources\deb\DEBIAN\prerm = build\resources\deb\DEBIAN\prerm
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rpm", "rpm", "{9BA0B044-0CC9-46F8-B551-204F149BF45D}"
|
||||
|
|
86
THIRD-PARTY-LICENSES.md
Normal file
86
THIRD-PARTY-LICENSES.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Third-Party Licenses
|
||||
|
||||
This project incorporates components from the following third parties:
|
||||
|
||||
## Packages
|
||||
|
||||
### AvaloniaUI
|
||||
|
||||
- **Source**: https://github.com/AvaloniaUI/Avalonia
|
||||
- **Version**: 11.2.5
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md
|
||||
|
||||
### AvaloniaEdit
|
||||
|
||||
- **Source**: https://github.com/AvaloniaUI/AvaloniaEdit
|
||||
- **Version**: 11.2.0
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/AvaloniaUI/AvaloniaEdit/blob/master/LICENSE
|
||||
|
||||
### LiveChartsCore.SkiaSharpView.Avalonia
|
||||
|
||||
- **Source**: https://github.com/beto-rodriguez/LiveCharts2
|
||||
- **Version**: 2.0.0-rc5.4
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/beto-rodriguez/LiveCharts2/blob/master/LICENSE
|
||||
|
||||
### TextMateSharp
|
||||
|
||||
- **Source**: https://github.com/danipen/TextMateSharp
|
||||
- **Version**: 1.0.66
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/danipen/TextMateSharp/blob/master/LICENSE.md
|
||||
|
||||
### OpenAI .NET SDK
|
||||
|
||||
- **Source**: https://github.com/openai/openai-dotnet
|
||||
- **Version**: 2.2.0-beta2
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/openai/openai-dotnet/blob/main/LICENSE
|
||||
|
||||
### Azure.AI.OpenAI
|
||||
|
||||
- **Source**: https://github.com/Azure/azure-sdk-for-net
|
||||
- **Version**: 2.2.0-beta2
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/Azure/azure-sdk-for-net/blob/main/LICENSE.txt
|
||||
|
||||
## Fonts
|
||||
|
||||
### JetBrainsMono
|
||||
|
||||
- **Source**: https://github.com/JetBrains/JetBrainsMono
|
||||
- **Commit**: v2.304
|
||||
- **License**: SIL Open Font License, Version 1.1
|
||||
- **License Link**: https://github.com/JetBrains/JetBrainsMono/blob/v2.304/OFL.txt
|
||||
|
||||
## Grammar Files
|
||||
|
||||
### haxe-TmLanguage
|
||||
|
||||
- **Source**: https://github.com/vshaxe/haxe-TmLanguage
|
||||
- **Commit**: ddad8b4c6d0781ac20be0481174ec1be772c5da5
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/vshaxe/haxe-TmLanguage/blob/ddad8b4c6d0781ac20be0481174ec1be772c5da5/LICENSE.md
|
||||
|
||||
### coc-toml
|
||||
|
||||
- **Source**: https://github.com/kkiyama117/coc-toml
|
||||
- **Commit**: aac3e0c65955c03314b2733041b19f903b7cc447
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/kkiyama117/coc-toml/blob/aac3e0c65955c03314b2733041b19f903b7cc447/LICENSE
|
||||
|
||||
### eclipse-buildship
|
||||
|
||||
- **Source**: https://github.com/eclipse/buildship
|
||||
- **Commit**: 6bb773e7692f913dec27105129ebe388de34e68b
|
||||
- **License**: Eclipse Public License 1.0
|
||||
- **License Link**: https://github.com/eclipse-buildship/buildship/blob/6bb773e7692f913dec27105129ebe388de34e68b/README.md
|
||||
|
||||
### vscode-jsp-lang
|
||||
|
||||
- **Source**: https://github.com/samuel-weinhardt/vscode-jsp-lang
|
||||
- **Commit**: 0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/samuel-weinhardt/vscode-jsp-lang/blob/0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355/LICENSE
|
344
TRANSLATION.md
344
TRANSLATION.md
|
@ -1,55 +1,150 @@
|
|||
### de_DE.axaml: 97.55%
|
||||
# Translation Status
|
||||
|
||||
This document shows the translation status of each locale file in the repository.
|
||||
|
||||
## Details
|
||||
|
||||
### 
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
<summary>Missing keys in de_DE.axaml</summary>
|
||||
|
||||
- Text.Configure.IssueTracker.AddSampleGiteeIssue
|
||||
- Text.Configure.IssueTracker.AddSampleGiteePullRequest
|
||||
- Text.Preference.General.DateFormat
|
||||
- Text.Preference.Git.SSLVerify
|
||||
- Text.Repository.HistoriesLayout
|
||||
- Text.Repository.HistoriesLayout.Horizontal
|
||||
- Text.Repository.HistoriesLayout.Vertical
|
||||
- Text.Repository.HistoriesOrder
|
||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||
- Text.Repository.Tags.OrderByCreatorDate
|
||||
- Text.Repository.Tags.OrderByNameAsc
|
||||
- Text.BranchUpstreamInvalid
|
||||
- Text.Configure.CustomAction.WaitForExit
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.Configure.IssueTracker.AddSampleAzure
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.Diff.First
|
||||
- Text.Diff.Last
|
||||
- Text.Preferences.AI.Streaming
|
||||
- Text.Preferences.Appearance.EditorTabWidth
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.StashCM.SaveAsPatch
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in es_ES.axaml</summary>
|
||||
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in fr_FR.axaml</summary>
|
||||
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in it_IT.axaml</summary>
|
||||
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ja_JP.axaml</summary>
|
||||
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.Tags.OrderByNameDes
|
||||
- Text.Repository.Tags.Sort
|
||||
- Text.Repository.UseRelativeTimeInHistories
|
||||
- Text.SetUpstream
|
||||
- Text.SetUpstream.Local
|
||||
- Text.SetUpstream.Unset
|
||||
- Text.SetUpstream.Upstream
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
|
||||
</details>
|
||||
|
||||
### es_ES.axaml: 100.00%
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
### fr_FR.axaml: 92.79%
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
<summary>Missing keys in pt_BR.axaml</summary>
|
||||
|
||||
- Text.AIAssistant.Regen
|
||||
- Text.AIAssistant.Use
|
||||
- Text.ApplyStash
|
||||
- Text.ApplyStash.DropAfterApply
|
||||
- Text.ApplyStash.RestoreIndex
|
||||
- Text.ApplyStash.Stash
|
||||
- Text.BranchCM.CustomAction
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.CherryPick.AppendSourceToMessage
|
||||
- Text.CherryPick.Mainline.Tips
|
||||
- Text.CommitCM.CherryPickMultiple
|
||||
- Text.BranchUpstreamInvalid
|
||||
- Text.Clone.RecurseSubmodules
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.CommitDetail.Info.Children
|
||||
- Text.Configure.CustomAction.Scope.Branch
|
||||
- Text.Configure.CustomAction.WaitForExit
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.Configure.IssueTracker.AddSampleGiteeIssue
|
||||
- Text.Configure.IssueTracker.AddSampleGiteePullRequest
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.CreateBranch.Name.WarnSpace
|
||||
- Text.DeleteRepositoryNode.Path
|
||||
- Text.DeleteRepositoryNode.TipForGroup
|
||||
- Text.DeleteRepositoryNode.TipForRepository
|
||||
- Text.Diff.First
|
||||
- Text.Diff.Last
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.Fetch.Force
|
||||
- Text.FileCM.ResolveUsing
|
||||
|
@ -63,132 +158,18 @@
|
|||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Preference.Appearance.FontSize
|
||||
- Text.Preference.Appearance.FontSize.Default
|
||||
- Text.Preference.Appearance.FontSize.Editor
|
||||
- Text.Preference.General.DateFormat
|
||||
- Text.Preference.General.ShowChildren
|
||||
- Text.Preference.Git.SSLVerify
|
||||
- Text.Repository.CustomActions
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.FilterCommits.Default
|
||||
- Text.Repository.FilterCommits.Exclude
|
||||
- Text.Repository.FilterCommits.Include
|
||||
- Text.Repository.HistoriesLayout
|
||||
- Text.Repository.HistoriesLayout.Horizontal
|
||||
- Text.Repository.HistoriesLayout.Vertical
|
||||
- Text.Repository.HistoriesOrder
|
||||
- Text.Repository.HistoriesOrder.ByDate
|
||||
- Text.Repository.HistoriesOrder.Topo
|
||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||
- Text.Repository.Skip
|
||||
- Text.Repository.Tags.OrderByCreatorDate
|
||||
- Text.Repository.Tags.OrderByNameAsc
|
||||
- Text.Repository.Tags.OrderByNameDes
|
||||
- Text.Repository.Tags.Sort
|
||||
- Text.Repository.UseRelativeTimeInHistories
|
||||
- Text.ScanRepositories
|
||||
- Text.SetUpstream
|
||||
- Text.SetUpstream.Local
|
||||
- Text.SetUpstream.Unset
|
||||
- Text.SetUpstream.Upstream
|
||||
- Text.SHALinkCM.NavigateTo
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
|
||||
</details>
|
||||
|
||||
### it_IT.axaml: 93.33%
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.CommitDetail.Info.Children
|
||||
- Text.Configure.IssueTracker.AddSampleGiteeIssue
|
||||
- Text.Configure.IssueTracker.AddSampleGiteePullRequest
|
||||
- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
|
||||
- Text.Configure.OpenAI.Preferred
|
||||
- Text.Configure.OpenAI.Preferred.Tip
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.Fetch.Force
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Preference.General.DateFormat
|
||||
- Text.Preference.General.ShowChildren
|
||||
- Text.Preference.Git.SSLVerify
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.FilterCommits.Default
|
||||
- Text.Repository.FilterCommits.Exclude
|
||||
- Text.Repository.FilterCommits.Include
|
||||
- Text.Repository.HistoriesLayout
|
||||
- Text.Repository.HistoriesLayout.Horizontal
|
||||
- Text.Repository.HistoriesLayout.Vertical
|
||||
- Text.Repository.HistoriesOrder
|
||||
- Text.Repository.HistoriesOrder.ByDate
|
||||
- Text.Repository.HistoriesOrder.Topo
|
||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||
- Text.Repository.Skip
|
||||
- Text.Repository.Tags.OrderByCreatorDate
|
||||
- Text.Repository.Tags.OrderByNameAsc
|
||||
- Text.Repository.Tags.OrderByNameDes
|
||||
- Text.Repository.Tags.Sort
|
||||
- Text.Repository.UseRelativeTimeInHistories
|
||||
- Text.SetUpstream
|
||||
- Text.SetUpstream.Local
|
||||
- Text.SetUpstream.Unset
|
||||
- Text.SetUpstream.Upstream
|
||||
- Text.SHALinkCM.CopySHA
|
||||
- Text.SHALinkCM.NavigateTo
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
|
||||
</details>
|
||||
|
||||
### pt_BR.axaml: 94.42%
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.CommitDetail.Info.Children
|
||||
- Text.Configure.IssueTracker.AddSampleGiteeIssue
|
||||
- Text.Configure.IssueTracker.AddSampleGiteePullRequest
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.Fetch.Force
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Preference.General.DateFormat
|
||||
- Text.Preference.General.ShowChildren
|
||||
- Text.Preference.Git.SSLVerify
|
||||
- Text.Preferences.AI.Streaming
|
||||
- Text.Preferences.Appearance.EditorTabWidth
|
||||
- Text.Preferences.General.DateFormat
|
||||
- Text.Preferences.General.ShowChildren
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.Preferences.Git.SSLVerify
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.HistoriesLayout
|
||||
- Text.Repository.HistoriesLayout.Horizontal
|
||||
- Text.Repository.HistoriesLayout.Vertical
|
||||
- Text.Repository.HistoriesOrder
|
||||
- Text.Repository.Notifications.Clear
|
||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||
- Text.Repository.Skip
|
||||
- Text.Repository.Tags.OrderByCreatorDate
|
||||
|
@ -201,36 +182,55 @@
|
|||
- Text.SetUpstream.Unset
|
||||
- Text.SetUpstream.Upstream
|
||||
- Text.SHALinkCM.NavigateTo
|
||||
- Text.Stash.AutoRestore
|
||||
- Text.Stash.AutoRestore.Tip
|
||||
- Text.StashCM.SaveAsPatch
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.SignOff
|
||||
|
||||
</details>
|
||||
|
||||
### ru_RU.axaml: 100.00%
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
|
||||
<summary>Missing keys in ru_RU.axaml</summary>
|
||||
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
|
||||
</details>
|
||||
|
||||
### zh_CN.axaml: 100.00%
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
|
||||
<summary>Missing keys in ta_IN.axaml</summary>
|
||||
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.UpdateSubmodules.Target
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
|
||||
</details>
|
||||
|
||||
### zh_TW.axaml: 100.00%
|
||||
### 
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
### 
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
2025.01
|
||||
2025.12
|
|
@ -1,7 +1,8 @@
|
|||
Package: sourcegit
|
||||
Version: 8.23
|
||||
Version: 2025.10
|
||||
Priority: optional
|
||||
Depends: libx11-6, libice6, libsm6, libicu | libicu76 | libicu74 | libicu72 | libicu71 | libicu70 | libicu69 | libicu68 | libicu67 | libicu66 | libicu65 | libicu63 | libicu60 | libicu57 | libicu55 | libicu52, xdg-utils
|
||||
Architecture: amd64
|
||||
Installed-Size: 60440
|
||||
Maintainer: longshuang@msn.cn
|
||||
Description: Open-source & Free Git GUI Client
|
||||
|
|
32
build/resources/deb/DEBIAN/preinst
Executable file
32
build/resources/deb/DEBIAN/preinst
Executable file
|
@ -0,0 +1,32 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <new-preinst> `install'
|
||||
# * <new-preinst> `install' <old-version>
|
||||
# * <new-preinst> `upgrade' <old-version>
|
||||
# * <old-preinst> `abort-upgrade' <new-version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/
|
||||
|
||||
case "$1" in
|
||||
install|upgrade)
|
||||
# Check if SourceGit is running and stop it
|
||||
if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then
|
||||
echo "Stopping running SourceGit instance..."
|
||||
pkill -f '/opt/sourcegit/sourcegit' || true
|
||||
# Give the process a moment to terminate
|
||||
sleep 1
|
||||
fi
|
||||
;;
|
||||
|
||||
abort-upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "preinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
35
build/resources/deb/DEBIAN/prerm
Executable file
35
build/resources/deb/DEBIAN/prerm
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <prerm> `remove'
|
||||
# * <old-prerm> `upgrade' <new-version>
|
||||
# * <new-prerm> `failed-upgrade' <old-version>
|
||||
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
|
||||
# * <deconfigured's-prerm> `deconfigure' `in-favour'
|
||||
# <package-being-installed> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
case "$1" in
|
||||
remove|upgrade|deconfigure)
|
||||
if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then
|
||||
echo "Stopping running SourceGit instance..."
|
||||
pkill -f '/opt/sourcegit/sourcegit' || true
|
||||
# Give the process a moment to terminate
|
||||
sleep 1
|
||||
fi
|
||||
;;
|
||||
|
||||
failed-upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "prerm called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
|
@ -6,7 +6,6 @@ const repoRoot = path.join(__dirname, '../../');
|
|||
const localesDir = path.join(repoRoot, 'src/Resources/Locales');
|
||||
const enUSFile = path.join(localesDir, 'en_US.axaml');
|
||||
const outputFile = path.join(repoRoot, 'TRANSLATION.md');
|
||||
const readmeFile = path.join(repoRoot, 'README.md');
|
||||
|
||||
const parser = new xml2js.Parser();
|
||||
|
||||
|
@ -18,42 +17,36 @@ async function parseXml(filePath) {
|
|||
async function calculateTranslationRate() {
|
||||
const enUSData = await parseXml(enUSFile);
|
||||
const enUSKeys = new Set(enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||
|
||||
const translationRates = [];
|
||||
const badges = [];
|
||||
|
||||
const files = (await fs.readdir(localesDir)).filter(file => file !== 'en_US.axaml' && file.endsWith('.axaml'));
|
||||
|
||||
// Add en_US badge first
|
||||
badges.push(`[](TRANSLATION.md)`);
|
||||
const lines = [];
|
||||
|
||||
lines.push('# Translation Status');
|
||||
lines.push('This document shows the translation status of each locale file in the repository.');
|
||||
lines.push(`## Details`);
|
||||
lines.push(`### `);
|
||||
|
||||
for (const file of files) {
|
||||
const locale = file.replace('.axaml', '').replace('_', '__');
|
||||
const filePath = path.join(localesDir, file);
|
||||
const localeData = await parseXml(filePath);
|
||||
const localeKeys = new Set(localeData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||
|
||||
const missingKeys = [...enUSKeys].filter(key => !localeKeys.has(key));
|
||||
const translationRate = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
|
||||
|
||||
translationRates.push(`### ${file}: ${translationRate.toFixed(2)}%\n`);
|
||||
translationRates.push(`<details>\n<summary>Missing Keys</summary>\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n</details>`);
|
||||
if (missingKeys.length > 0) {
|
||||
const progress = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
|
||||
const badgeColor = progress >= 75 ? 'yellow' : 'red';
|
||||
|
||||
// Add badges
|
||||
const locale = file.replace('.axaml', '').replace('_', '__');
|
||||
const badgeColor = translationRate === 100 ? 'brightgreen' : translationRate >= 75 ? 'yellow' : 'red';
|
||||
badges.push(`[}%25-${badgeColor})](TRANSLATION.md)`);
|
||||
lines.push(`### }%25-${badgeColor})`);
|
||||
lines.push(`<details>\n<summary>Missing keys in ${file}</summary>\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n</details>`)
|
||||
} else {
|
||||
lines.push(`### `);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(translationRates.join('\n\n'));
|
||||
|
||||
await fs.writeFile(outputFile, translationRates.join('\n\n') + '\n', 'utf8');
|
||||
|
||||
// Update README.md
|
||||
let readmeContent = await fs.readFile(readmeFile, 'utf8');
|
||||
const badgeSection = `## Translation Status\n\n${badges.join(' ')}`;
|
||||
console.log(badgeSection);
|
||||
readmeContent = readmeContent.replace(/## Translation Status\n\n.*\n\n/, badgeSection + '\n\n');
|
||||
await fs.writeFile(readmeFile, readmeContent, 'utf8');
|
||||
const content = lines.join('\n\n');
|
||||
console.log(content);
|
||||
await fs.writeFile(outputFile, content, 'utf8');
|
||||
}
|
||||
|
||||
calculateTranslationRate().catch(err => console.error(err));
|
||||
|
|
|
@ -56,8 +56,15 @@ cp -f SourceGit/* resources/deb/opt/sourcegit
|
|||
ln -rsf resources/deb/opt/sourcegit/sourcegit resources/deb/usr/bin
|
||||
cp -r resources/_common/applications resources/deb/usr/share
|
||||
cp -r resources/_common/icons resources/deb/usr/share
|
||||
sed -i -e "s/^Version:.*/Version: $VERSION/" -e "s/^Architecture:.*/Architecture: $arch/" resources/deb/DEBIAN/control
|
||||
dpkg-deb --root-owner-group --build resources/deb "sourcegit_$VERSION-1_$arch.deb"
|
||||
# Calculate installed size in KB
|
||||
installed_size=$(du -sk resources/deb | cut -f1)
|
||||
# Update the control file
|
||||
sed -i -e "s/^Version:.*/Version: $VERSION/" \
|
||||
-e "s/^Architecture:.*/Architecture: $arch/" \
|
||||
-e "s/^Installed-Size:.*/Installed-Size: $installed_size/" \
|
||||
resources/deb/DEBIAN/control
|
||||
# Build deb package with gzip compression
|
||||
dpkg-deb -Zgzip --root-owner-group --build resources/deb "sourcegit_$VERSION-1_$arch.deb"
|
||||
|
||||
rpmbuild -bb --target="$target" resources/rpm/SPECS/build.spec --define "_topdir $(pwd)/resources/rpm" --define "_version $VERSION"
|
||||
mv "resources/rpm/RPMS/$target/sourcegit-$VERSION-1.$target.rpm" ./
|
||||
|
|
|
@ -9,4 +9,8 @@ cd build
|
|||
|
||||
rm -rf SourceGit/*.pdb
|
||||
|
||||
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
|
||||
powershell -Command "Compress-Archive -Path SourceGit -DestinationPath \"sourcegit_$VERSION.$RUNTIME.zip\" -Force"
|
||||
else
|
||||
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
|
||||
fi
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace SourceGit
|
|||
}
|
||||
}
|
||||
|
||||
public static readonly Command OpenPreferenceCommand = new Command(_ => OpenDialog(new Views.Preference()));
|
||||
public static readonly Command OpenPreferencesCommand = new Command(_ => OpenDialog(new Views.Preferences()));
|
||||
public static readonly Command OpenHotkeysCommand = new Command(_ => OpenDialog(new Views.Hotkeys()));
|
||||
public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir));
|
||||
public static readonly Command OpenAboutCommand = new Command(_ => OpenDialog(new Views.About()));
|
||||
|
|
|
@ -46,11 +46,9 @@ namespace SourceGit
|
|||
[JsonSerializable(typeof(Models.ExternalToolPaths))]
|
||||
[JsonSerializable(typeof(Models.InteractiveRebaseJobCollection))]
|
||||
[JsonSerializable(typeof(Models.JetBrainsState))]
|
||||
[JsonSerializable(typeof(Models.OpenAIChatRequest))]
|
||||
[JsonSerializable(typeof(Models.OpenAIChatResponse))]
|
||||
[JsonSerializable(typeof(Models.ThemeOverrides))]
|
||||
[JsonSerializable(typeof(Models.Version))]
|
||||
[JsonSerializable(typeof(Models.RepositorySettings))]
|
||||
[JsonSerializable(typeof(ViewModels.Preference))]
|
||||
[JsonSerializable(typeof(ViewModels.Preferences))]
|
||||
internal partial class JsonCodeGen : JsonSerializerContext { }
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
<ResourceInclude x:Key="zh_CN" Source="/Resources/Locales/zh_CN.axaml"/>
|
||||
<ResourceInclude x:Key="zh_TW" Source="/Resources/Locales/zh_TW.axaml"/>
|
||||
<ResourceInclude x:Key="es_ES" Source="/Resources/Locales/es_ES.axaml"/>
|
||||
<ResourceInclude x:Key="ja_JP" Source="/Resources/Locales/ja_JP.axaml"/>
|
||||
<ResourceInclude x:Key="ta_IN" Source="/Resources/Locales/ta_IN.axaml"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
|
@ -35,7 +37,7 @@
|
|||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}" IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" Gesture="⌘+,"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" Gesture="⌘+,"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.OpenAppDataDir}" Command="{x:Static s:App.OpenAppDataDirCommand}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}" Gesture="⌘+Q"/>
|
||||
|
|
129
src/App.axaml.cs
129
src/App.axaml.cs
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia;
|
||||
|
@ -35,15 +37,14 @@ namespace SourceGit
|
|||
|
||||
TaskScheduler.UnobservedTaskException += (_, e) =>
|
||||
{
|
||||
LogException(e.Exception);
|
||||
e.SetObserved();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (TryLaunchedAsRebaseTodoEditor(args, out int exitTodo))
|
||||
if (TryLaunchAsRebaseTodoEditor(args, out int exitTodo))
|
||||
Environment.Exit(exitTodo);
|
||||
else if (TryLaunchedAsRebaseMessageEditor(args, out int exitMessage))
|
||||
else if (TryLaunchAsRebaseMessageEditor(args, out int exitMessage))
|
||||
Environment.Exit(exitMessage);
|
||||
else
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
@ -75,6 +76,31 @@ namespace SourceGit
|
|||
Native.OS.SetupApp(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void LogException(Exception ex)
|
||||
{
|
||||
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());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Utility Functions
|
||||
|
@ -179,6 +205,9 @@ namespace SourceGit
|
|||
app._fontsOverrides = null;
|
||||
}
|
||||
|
||||
defaultFont = app.FixFontFamilyName(defaultFont);
|
||||
monospaceFont = app.FixFontFamilyName(monospaceFont);
|
||||
|
||||
var resDic = new ResourceDictionary();
|
||||
if (!string.IsNullOrEmpty(defaultFont))
|
||||
resDic.Add("Fonts.Default", new FontFamily(defaultFont));
|
||||
|
@ -298,7 +327,7 @@ namespace SourceGit
|
|||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
pref.PropertyChanged += (_, _) => pref.Save();
|
||||
|
||||
SetLocale(pref.Locale);
|
||||
|
@ -312,44 +341,18 @@ namespace SourceGit
|
|||
{
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
if (TryLaunchedAsCoreEditor(desktop))
|
||||
if (TryLaunchAsCoreEditor(desktop))
|
||||
return;
|
||||
|
||||
if (TryLaunchedAsAskpass(desktop))
|
||||
if (TryLaunchAsAskpass(desktop))
|
||||
return;
|
||||
|
||||
TryLaunchedAsNormal(desktop);
|
||||
TryLaunchAsNormal(desktop);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static void LogException(Exception ex)
|
||||
{
|
||||
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.ToString()}\n");
|
||||
builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
|
||||
builder.Append($"Source: {ex.Source}\n");
|
||||
builder.Append($"---------------------------\n\n");
|
||||
builder.Append(ex.StackTrace);
|
||||
while (ex.InnerException != null)
|
||||
{
|
||||
ex = ex.InnerException;
|
||||
builder.Append($"\n\nInnerException::: {ex.GetType().FullName}: {ex.Message}\n");
|
||||
builder.Append(ex.StackTrace);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
private static bool TryLaunchedAsRebaseTodoEditor(string[] args, out int exitCode)
|
||||
private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
|
||||
{
|
||||
exitCode = -1;
|
||||
|
||||
|
@ -402,7 +405,7 @@ namespace SourceGit
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool TryLaunchedAsRebaseMessageEditor(string[] args, out int exitCode)
|
||||
private static bool TryLaunchAsRebaseMessageEditor(string[] args, out int exitCode)
|
||||
{
|
||||
exitCode = -1;
|
||||
|
||||
|
@ -436,7 +439,7 @@ namespace SourceGit
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchedAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private bool TryLaunchAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var args = desktop.Args;
|
||||
if (args == null || args.Length <= 1 || !args[0].Equals("--core-editor", StringComparison.Ordinal))
|
||||
|
@ -444,14 +447,18 @@ namespace SourceGit
|
|||
|
||||
var file = args[1];
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
desktop.Shutdown(-1);
|
||||
else
|
||||
desktop.MainWindow = new Views.StandaloneCommitMessageEditor(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
var editor = new Views.StandaloneCommitMessageEditor();
|
||||
editor.SetFile(file);
|
||||
desktop.MainWindow = editor;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchedAsAskpass(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private bool TryLaunchAsAskpass(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var launchAsAskpass = Environment.GetEnvironmentVariable("SOURCEGIT_LAUNCH_AS_ASKPASS");
|
||||
if (launchAsAskpass is not "TRUE")
|
||||
|
@ -460,14 +467,16 @@ namespace SourceGit
|
|||
var args = desktop.Args;
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
desktop.MainWindow = new Views.Askpass(args[0]);
|
||||
var askpass = new Views.Askpass();
|
||||
askpass.TxtDescription.Text = args[0];
|
||||
desktop.MainWindow = askpass;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TryLaunchedAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
Native.OS.SetupEnternalTools();
|
||||
Models.AvatarManager.Instance.Start();
|
||||
|
@ -480,7 +489,7 @@ namespace SourceGit
|
|||
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
||||
|
||||
#if !DISABLE_UPDATE_DETECTION
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
if (pref.ShouldCheck4UpdateOnStartup())
|
||||
Check4Update();
|
||||
#endif
|
||||
|
@ -512,7 +521,7 @@ namespace SourceGit
|
|||
// Should not check ignored tag if this is called manually.
|
||||
if (!manually)
|
||||
{
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
if (ver.TagName == pref.IgnoreUpdateTag)
|
||||
return;
|
||||
}
|
||||
|
@ -522,7 +531,7 @@ namespace SourceGit
|
|||
catch (Exception e)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(e);
|
||||
ShowSelfUpdateResult(new Models.SelfUpdateFailed(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -539,6 +548,38 @@ namespace SourceGit
|
|||
});
|
||||
}
|
||||
|
||||
private string FixFontFamilyName(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return string.Empty;
|
||||
|
||||
var parts = input.Split(',');
|
||||
var trimmed = new List<string>();
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var t = part.Trim();
|
||||
if (string.IsNullOrEmpty(t))
|
||||
continue;
|
||||
|
||||
// Collapse multiple spaces into single space
|
||||
var prevChar = '\0';
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var c in t)
|
||||
{
|
||||
if (c == ' ' && prevChar == ' ')
|
||||
continue;
|
||||
sb.Append(c);
|
||||
prevChar = c;
|
||||
}
|
||||
|
||||
trimmed.Add(sb.ToString());
|
||||
}
|
||||
|
||||
return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty;
|
||||
}
|
||||
|
||||
private ViewModels.Launcher _launcher = null;
|
||||
private ResourceDictionary _activeLocale = null;
|
||||
private ResourceDictionary _themeOverrides = null;
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace SourceGit.Commands
|
|||
Args = includeUntracked ? "add ." : "add -u .";
|
||||
}
|
||||
|
||||
public Add(string repo, List<Models.Change> changes)
|
||||
public Add(string repo, List<string> changes)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
@ -22,7 +22,7 @@ namespace SourceGit.Commands
|
|||
foreach (var c in changes)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(c.Path);
|
||||
builder.Append(c);
|
||||
builder.Append("\"");
|
||||
}
|
||||
Args = builder.ToString();
|
||||
|
|
|
@ -54,21 +54,14 @@
|
|||
|
||||
public static bool DeleteRemote(string repo, string remote, string name)
|
||||
{
|
||||
bool exists = new Remote(repo).HasBranch(remote, name);
|
||||
if (exists)
|
||||
return new Push(repo, remote, $"refs/heads/{name}", true).Exec();
|
||||
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
|
||||
bool exists = new Remote(repo).HasBranch(remote, name);
|
||||
if (exists)
|
||||
{
|
||||
cmd.SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
cmd.Args = $"push {remote} --delete {name}";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.Args = $"branch -D -r {remote}/{name}";
|
||||
}
|
||||
|
||||
cmd.Args = $"branch -D -r {remote}/{name}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace SourceGit.Commands
|
|||
WorkingDirectory = path;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = sshKey;
|
||||
Args = "clone --progress --verbose --recurse-submodules ";
|
||||
Args = "clone --progress --verbose ";
|
||||
|
||||
if (!string.IsNullOrEmpty(extraArgs))
|
||||
Args += $"{extraArgs} ";
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
|
@ -10,11 +11,6 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class Command
|
||||
{
|
||||
public class CancelToken
|
||||
{
|
||||
public bool Requested { get; set; } = false;
|
||||
}
|
||||
|
||||
public class ReadToEndResult
|
||||
{
|
||||
public bool IsSuccess { get; set; } = false;
|
||||
|
@ -30,7 +26,7 @@ namespace SourceGit.Commands
|
|||
}
|
||||
|
||||
public string Context { get; set; } = string.Empty;
|
||||
public CancelToken Cancel { get; set; } = null;
|
||||
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 string SSHKey { get; set; } = string.Empty;
|
||||
|
@ -43,36 +39,15 @@ namespace SourceGit.Commands
|
|||
var start = CreateGitStartInfo();
|
||||
var errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var isCancelled = false;
|
||||
|
||||
proc.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (Cancel != null && Cancel.Requested)
|
||||
{
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited)
|
||||
proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Data != null)
|
||||
OnReadline(e.Data);
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (Cancel != null && Cancel.Requested)
|
||||
{
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited)
|
||||
proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
errs.Add(string.Empty);
|
||||
|
@ -97,9 +72,25 @@ namespace SourceGit.Commands
|
|||
errs.Add(e.Data);
|
||||
};
|
||||
|
||||
var dummy = null as Process;
|
||||
var dummyProcLock = new object();
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
|
||||
// It not safe, please only use `CancellationToken` in readonly commands.
|
||||
if (CancellationToken.CanBeCanceled)
|
||||
{
|
||||
dummy = proc;
|
||||
CancellationToken.Register(() =>
|
||||
{
|
||||
lock (dummyProcLock)
|
||||
{
|
||||
if (dummy is { HasExited: false })
|
||||
dummy.Kill();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -113,10 +104,18 @@ namespace SourceGit.Commands
|
|||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
if (dummy != null)
|
||||
{
|
||||
lock (dummyProcLock)
|
||||
{
|
||||
dummy = null;
|
||||
}
|
||||
}
|
||||
|
||||
int exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
|
||||
if (!isCancelled && exitCode != 0)
|
||||
if (!CancellationToken.IsCancellationRequested && exitCode != 0)
|
||||
{
|
||||
if (RaiseError)
|
||||
{
|
||||
|
@ -192,13 +191,12 @@ namespace SourceGit.Commands
|
|||
if (!start.Environment.ContainsKey("GIT_SSH_COMMAND") && !string.IsNullOrEmpty(SSHKey))
|
||||
start.Environment.Add("GIT_SSH_COMMAND", $"ssh -i '{SSHKey}'");
|
||||
|
||||
// Force using en_US.UTF-8 locale to avoid GCM crash
|
||||
// Force using en_US.UTF-8 locale
|
||||
if (OperatingSystem.IsLinux())
|
||||
start.Environment.Add("LANG", "en_US.UTF-8");
|
||||
|
||||
// Fix macOS `PATH` env
|
||||
if (OperatingSystem.IsMacOS() && !string.IsNullOrEmpty(Native.OS.CustomPathEnv))
|
||||
start.Environment.Add("PATH", Native.OS.CustomPathEnv);
|
||||
{
|
||||
start.Environment.Add("LANG", "C");
|
||||
start.Environment.Add("LC_ALL", "C");
|
||||
}
|
||||
|
||||
// Force using this app as git editor.
|
||||
switch (Editor)
|
||||
|
|
|
@ -6,8 +6,10 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class CompareRevisions : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")]
|
||||
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
|
||||
private static partial Regex REG_RENAME_FORMAT();
|
||||
|
||||
public CompareRevisions(string repo, string start, string end)
|
||||
{
|
||||
|
@ -18,6 +20,15 @@ namespace SourceGit.Commands
|
|||
Args = $"diff --name-status {based} {end}";
|
||||
}
|
||||
|
||||
public CompareRevisions(string repo, string start, string end, string path)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var based = string.IsNullOrEmpty(start) ? "-R" : start;
|
||||
Args = $"diff --name-status {based} {end} -- \"{path}\"";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
Exec();
|
||||
|
@ -29,7 +40,17 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
match = REG_RENAME_FORMAT().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var renamed = new Models.Change() { Path = match.Groups[1].Value };
|
||||
renamed.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(renamed);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
@ -48,10 +69,6 @@ namespace SourceGit.Commands
|
|||
change.Set(Models.ChangeState.Deleted);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'R':
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
_changes.Add(change);
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace SourceGit.Commands
|
|||
var rs = new Dictionary<string, string>();
|
||||
if (output.IsSuccess)
|
||||
{
|
||||
var lines = output.StdOut.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var idx = line.IndexOf('=', StringComparison.Ordinal);
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "status -uno --ignore-submodules=dirty --porcelain";
|
||||
Args = "--no-optional-locks status -uno --ignore-submodules=dirty --porcelain";
|
||||
}
|
||||
|
||||
public int Result()
|
||||
|
@ -16,7 +16,7 @@ namespace SourceGit.Commands
|
|||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
return lines.Length;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,18 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("deleted file mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.OldMode = line.Substring(18);
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("new file mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.NewMode = line.Substring(14);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
|
||||
|
|
|
@ -8,7 +8,26 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public static class ExecuteCustomAction
|
||||
{
|
||||
public static void Run(string repo, string file, string args, Action<string> outputHandler)
|
||||
public static void Run(string repo, string file, string args)
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = file;
|
||||
start.Arguments = args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.WorkingDirectory = repo;
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(start);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunAndWait(string repo, string file, string args, Action<string> outputHandler)
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = file;
|
||||
|
@ -21,14 +40,6 @@ namespace SourceGit.Commands
|
|||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
start.WorkingDirectory = repo;
|
||||
|
||||
// Force using en_US.UTF-8 locale to avoid GCM crash
|
||||
if (OperatingSystem.IsLinux())
|
||||
start.Environment.Add("LANG", "en_US.UTF-8");
|
||||
|
||||
// Fix macOS `PATH` env
|
||||
if (OperatingSystem.IsMacOS() && !string.IsNullOrEmpty(Native.OS.CustomPathEnv))
|
||||
start.Environment.Add("PATH", Native.OS.CustomPathEnv);
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var builder = new StringBuilder();
|
||||
|
||||
|
@ -57,19 +68,14 @@ namespace SourceGit.Commands
|
|||
var exitCode = proc.ExitCode;
|
||||
if (exitCode != 0)
|
||||
{
|
||||
var errMsg = builder.ToString();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, errMsg);
|
||||
});
|
||||
var errMsg = builder.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(errMsg))
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, errMsg));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, e.Message);
|
||||
});
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, e.Message));
|
||||
}
|
||||
|
||||
proc.Close();
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class Fetch : Command
|
||||
{
|
||||
public Fetch(string repo, string remote, bool noTags, bool prune, bool force, Action<string> outputHandler)
|
||||
public Fetch(string repo, string remote, bool noTags, bool force, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
|
@ -21,9 +21,6 @@ namespace SourceGit.Commands
|
|||
if (force)
|
||||
Args += "--force ";
|
||||
|
||||
if (prune)
|
||||
Args += "--prune ";
|
||||
|
||||
Args += remote;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -20,82 +22,78 @@ namespace SourceGit.Commands
|
|||
}
|
||||
}
|
||||
|
||||
public GenerateCommitMessage(Models.OpenAIService service, string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> onProgress)
|
||||
public GenerateCommitMessage(Models.OpenAIService service, string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> onResponse)
|
||||
{
|
||||
_service = service;
|
||||
_repo = repo;
|
||||
_changes = changes;
|
||||
_cancelToken = cancelToken;
|
||||
_onProgress = onProgress;
|
||||
_onResponse = onResponse;
|
||||
}
|
||||
|
||||
public string Result()
|
||||
public void Exec()
|
||||
{
|
||||
try
|
||||
{
|
||||
var summarybuilder = new StringBuilder();
|
||||
var bodyBuilder = new StringBuilder();
|
||||
_onResponse?.Invoke("Waiting for pre-file analyzing to completed...\n\n");
|
||||
|
||||
var responseBuilder = new StringBuilder();
|
||||
var summaryBuilder = new StringBuilder();
|
||||
foreach (var change in _changes)
|
||||
{
|
||||
if (_cancelToken.IsCancellationRequested)
|
||||
return "";
|
||||
return;
|
||||
|
||||
_onProgress?.Invoke($"Analyzing {change.Path}...");
|
||||
responseBuilder.Append("- ");
|
||||
summaryBuilder.Append("- ");
|
||||
|
||||
var summary = GenerateChangeSummary(change);
|
||||
summarybuilder.Append("- ");
|
||||
summarybuilder.Append(summary);
|
||||
summarybuilder.Append("(file: ");
|
||||
summarybuilder.Append(change.Path);
|
||||
summarybuilder.Append(")");
|
||||
summarybuilder.AppendLine();
|
||||
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
_service.Chat(
|
||||
_service.AnalyzeDiffPrompt,
|
||||
$"Here is the `git diff` output: {rs.StdOut}",
|
||||
_cancelToken,
|
||||
update =>
|
||||
{
|
||||
responseBuilder.Append(update);
|
||||
summaryBuilder.Append(update);
|
||||
|
||||
bodyBuilder.Append("- ");
|
||||
bodyBuilder.Append(summary);
|
||||
bodyBuilder.AppendLine();
|
||||
_onResponse?.Invoke($"Waiting for pre-file analyzing to completed...\n\n{responseBuilder}");
|
||||
});
|
||||
}
|
||||
|
||||
responseBuilder.Append("\n");
|
||||
summaryBuilder.Append("(file: ");
|
||||
summaryBuilder.Append(change.Path);
|
||||
summaryBuilder.Append(")\n");
|
||||
}
|
||||
|
||||
if (_cancelToken.IsCancellationRequested)
|
||||
return "";
|
||||
return;
|
||||
|
||||
_onProgress?.Invoke($"Generating commit message...");
|
||||
|
||||
var body = bodyBuilder.ToString();
|
||||
var subject = GenerateSubject(summarybuilder.ToString());
|
||||
return string.Format("{0}\n\n{1}", subject, body);
|
||||
var responseBody = responseBuilder.ToString();
|
||||
var subjectBuilder = new StringBuilder();
|
||||
_service.Chat(
|
||||
_service.GenerateSubjectPrompt,
|
||||
$"Here are the summaries changes:\n{summaryBuilder}",
|
||||
_cancelToken,
|
||||
update =>
|
||||
{
|
||||
subjectBuilder.Append(update);
|
||||
_onResponse?.Invoke($"{subjectBuilder}\n\n{responseBody}");
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.RaiseException(_repo, $"Failed to generate commit message: {e}");
|
||||
return "";
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(_repo, $"Failed to generate commit message: {e}"));
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateChangeSummary(Models.Change change)
|
||||
{
|
||||
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
|
||||
var diff = rs.IsSuccess ? rs.StdOut : "unknown change";
|
||||
|
||||
var rsp = _service.Chat(_service.AnalyzeDiffPrompt, $"Here is the `git diff` output: {diff}", _cancelToken);
|
||||
if (rsp != null && rsp.Choices.Count > 0)
|
||||
return rsp.Choices[0].Message.Content;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string GenerateSubject(string summary)
|
||||
{
|
||||
var rsp = _service.Chat(_service.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summary}", _cancelToken);
|
||||
if (rsp != null && rsp.Choices.Count > 0)
|
||||
return rsp.Choices[0].Message.Content;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private Models.OpenAIService _service;
|
||||
private string _repo;
|
||||
private List<Models.Change> _changes;
|
||||
private CancellationToken _cancelToken;
|
||||
private Action<string> _onProgress;
|
||||
private Action<string> _onResponse;
|
||||
}
|
||||
}
|
||||
|
|
24
src/Commands/IsBareRepository.cs
Normal file
24
src/Commands/IsBareRepository.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class IsBareRepository : Command
|
||||
{
|
||||
public IsBareRepository(string path)
|
||||
{
|
||||
WorkingDirectory = path;
|
||||
Args = "rev-parse --is-bare-repository";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(WorkingDirectory, "refs")) ||
|
||||
!Directory.Exists(Path.Combine(WorkingDirectory, "objects")) ||
|
||||
!File.Exists(Path.Combine(WorkingDirectory, "HEAD")))
|
||||
return false;
|
||||
|
||||
var rs = ReadToEnd();
|
||||
return rs.IsSuccess && rs.StdOut.Trim() == "true";
|
||||
}
|
||||
}
|
||||
}
|
17
src/Commands/IsCommitSHA.cs
Normal file
17
src/Commands/IsCommitSHA.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class IsCommitSHA : Command
|
||||
{
|
||||
public IsCommitSHA(string repo, string hash)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = $"cat-file -t {hash}";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
return rs.IsSuccess && rs.StdOut.Trim().Equals("commit");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class LFS
|
||||
{
|
||||
[GeneratedRegex(@"^(.+)\s+(\w+)\s+\w+:(\d+)$")]
|
||||
[GeneratedRegex(@"^(.+)\s+([\w.]+)\s+\w+:(\d+)$")]
|
||||
private static partial Regex REG_LOCK();
|
||||
|
||||
class SubCmd : Command
|
||||
|
@ -82,7 +82,7 @@ namespace SourceGit.Commands
|
|||
var rs = cmd.ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_LOCK().Match(line);
|
||||
|
|
|
@ -13,9 +13,12 @@ namespace SourceGit.Commands
|
|||
cmd.Context = repo;
|
||||
cmd.RaiseError = true;
|
||||
|
||||
// NOTE: If no <file> names are specified, 'git mergetool' will run the merge tool program on every file with merge conflicts.
|
||||
var fileArg = string.IsNullOrEmpty(file) ? "" : $"\"{file}\"";
|
||||
|
||||
if (toolType == 0)
|
||||
{
|
||||
cmd.Args = $"mergetool \"{file}\"";
|
||||
cmd.Args = $"mergetool {fileArg}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
|
@ -32,7 +35,7 @@ namespace SourceGit.Commands
|
|||
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 \"{file}\"";
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,21 +4,20 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class Pull : Command
|
||||
{
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, bool prune, Action<string> outputHandler)
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "pull --verbose --progress --tags ";
|
||||
Args = "pull --verbose --progress ";
|
||||
|
||||
if (useRebase)
|
||||
Args += "--rebase ";
|
||||
Args += "--rebase=true ";
|
||||
|
||||
if (noTags)
|
||||
Args += "--no-tags ";
|
||||
if (prune)
|
||||
Args += "--prune ";
|
||||
|
||||
Args += $"{remote} {branch}";
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace SourceGit.Commands
|
|||
Args += $"{remote} {local}:{remoteBranch}";
|
||||
}
|
||||
|
||||
public Push(string repo, string remote, string tag, bool isDelete)
|
||||
public Push(string repo, string remote, string refname, bool isDelete)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
@ -36,7 +36,7 @@ namespace SourceGit.Commands
|
|||
if (isDelete)
|
||||
Args += "--delete ";
|
||||
|
||||
Args += $"{remote} refs/tags/{tag}";
|
||||
Args += $"{remote} {refname}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
|
|
|
@ -24,12 +24,23 @@ namespace SourceGit.Commands
|
|||
if (!rs.IsSuccess)
|
||||
return branches;
|
||||
|
||||
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var remoteBranches = new HashSet<string>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var b = ParseLine(line);
|
||||
if (b != null)
|
||||
{
|
||||
branches.Add(b);
|
||||
if (!b.IsLocal)
|
||||
remoteBranches.Add(b.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var b in branches)
|
||||
{
|
||||
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
|
||||
b.IsUpsteamGone = !remoteBranches.Contains(b.Upstream);
|
||||
}
|
||||
|
||||
return branches;
|
||||
|
@ -75,6 +86,7 @@ namespace SourceGit.Commands
|
|||
branch.Head = parts[1];
|
||||
branch.IsCurrent = parts[2] == "*";
|
||||
branch.Upstream = parts[3];
|
||||
branch.IsUpsteamGone = false;
|
||||
|
||||
if (branch.IsLocal && !string.IsNullOrEmpty(parts[4]) && !parts[4].Equals("=", StringComparison.Ordinal))
|
||||
branch.TrackStatus = new QueryTrackStatus(WorkingDirectory, branch.Name, branch.Upstream).Result();
|
||||
|
|
|
@ -9,10 +9,10 @@ namespace SourceGit.Commands
|
|||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
_commit = commit;
|
||||
Args = $"rev-list -{max} --parents --branches --remotes ^{commit}";
|
||||
Args = $"rev-list -{max} --parents --branches --remotes --ancestry-path ^{commit}";
|
||||
}
|
||||
|
||||
public IEnumerable<string> Result()
|
||||
public List<string> Result()
|
||||
{
|
||||
Exec();
|
||||
return _lines;
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --no-show-signature --pretty=format:%B -s {sha}";
|
||||
Args = $"show --no-show-signature --format=%B -s {sha}";
|
||||
}
|
||||
|
||||
public string Result()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
const string baseArgs = "show --no-show-signature --pretty=format:\"%G?%n%GS%n%GK\" -s";
|
||||
const string baseArgs = "show --no-show-signature --format=%G?%n%GS%n%GK -s";
|
||||
const string fakeSignersFileArg = "-c gpg.ssh.allowedSignersFile=/dev/null";
|
||||
Args = $"{(useFakeSignersFile ? fakeSignersFileArg : string.Empty)} {baseArgs} {sha}";
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
|||
if (!rs.IsSuccess)
|
||||
return null;
|
||||
|
||||
var raw = rs.StdOut.Trim();
|
||||
var raw = rs.StdOut.Trim().ReplaceLineEndings("\n");
|
||||
if (raw.Length <= 1)
|
||||
return null;
|
||||
|
||||
|
@ -29,7 +29,6 @@
|
|||
Signer = lines[1],
|
||||
Key = lines[2]
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {limits}";
|
||||
Args = $"log --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {limits}";
|
||||
_findFirstMerged = needFindHead;
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,13 @@ namespace SourceGit.Commands
|
|||
{
|
||||
string search = onlyCurrentBranch ? string.Empty : "--branches --remotes ";
|
||||
|
||||
if (method == Models.CommitSearchMethod.ByUser)
|
||||
if (method == Models.CommitSearchMethod.ByAuthor)
|
||||
{
|
||||
search += $"-i --author=\"{filter}\" --committer=\"{filter}\"";
|
||||
search += $"-i --author=\"{filter}\"";
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByCommitter)
|
||||
{
|
||||
search += $"-i --committer=\"{filter}\"";
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByFile)
|
||||
{
|
||||
|
@ -31,7 +35,7 @@ namespace SourceGit.Commands
|
|||
var argsBuilder = new StringBuilder();
|
||||
argsBuilder.Append(search);
|
||||
|
||||
var words = filter.Split(new[] { ' ', '\t', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var words = filter.Split([' ', '\t', '\r'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var word in words)
|
||||
{
|
||||
var escaped = word.Trim().Replace("\"", "\\\"", StringComparison.Ordinal);
|
||||
|
@ -44,7 +48,7 @@ namespace SourceGit.Commands
|
|||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log -1000 --date-order --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s " + search;
|
||||
Args = $"log -1000 --date-order --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s " + search;
|
||||
_findFirstMerged = false;
|
||||
}
|
||||
|
||||
|
@ -120,7 +124,7 @@ namespace SourceGit.Commands
|
|||
Args = $"log --since=\"{_commits[^1].CommitterTimeStr}\" --format=\"%H\"";
|
||||
|
||||
var rs = ReadToEnd();
|
||||
var shas = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var shas = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (shas.Length == 0)
|
||||
return;
|
||||
|
||||
|
|
|
@ -3,18 +3,18 @@ using System.Collections.Generic;
|
|||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCommitsWithFullMessage : Command
|
||||
public class QueryCommitsForInteractiveRebase : Command
|
||||
{
|
||||
public QueryCommitsWithFullMessage(string repo, string args)
|
||||
public QueryCommitsForInteractiveRebase(string repo, string on)
|
||||
{
|
||||
_boundary = $"----- BOUNDARY OF COMMIT {Guid.NewGuid()} -----";
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log --date-order --no-show-signature --decorate=full --pretty=format:\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {args}";
|
||||
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.CommitWithMessage> Result()
|
||||
public List<Models.InteractiveCommit> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
|
@ -29,7 +29,7 @@ namespace SourceGit.Commands
|
|||
switch (nextPartIdx)
|
||||
{
|
||||
case 0:
|
||||
_current = new Models.CommitWithMessage();
|
||||
_current = new Models.InteractiveCommit();
|
||||
_current.Commit.SHA = line;
|
||||
_commits.Add(_current);
|
||||
break;
|
||||
|
@ -52,7 +52,7 @@ namespace SourceGit.Commands
|
|||
_current.Commit.CommitterTime = ulong.Parse(line);
|
||||
break;
|
||||
default:
|
||||
var boundary = rs.StdOut.IndexOf(_boundary, end + 1);
|
||||
var boundary = rs.StdOut.IndexOf(_boundary, end + 1, StringComparison.Ordinal);
|
||||
if (boundary > end)
|
||||
{
|
||||
_current.Message = rs.StdOut.Substring(start, boundary - start - 1);
|
||||
|
@ -88,8 +88,8 @@ namespace SourceGit.Commands
|
|||
_current.Commit.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
|
||||
private List<Models.CommitWithMessage> _commits = new List<Models.CommitWithMessage>();
|
||||
private Models.CommitWithMessage _current = null;
|
||||
private List<Models.InteractiveCommit> _commits = [];
|
||||
private Models.InteractiveCommit _current = null;
|
||||
private string _boundary = "";
|
||||
}
|
||||
}
|
26
src/Commands/QueryGitCommonDir.cs
Normal file
26
src/Commands/QueryGitCommonDir.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryGitCommonDir : Command
|
||||
{
|
||||
public QueryGitCommonDir(string workDir)
|
||||
{
|
||||
WorkingDirectory = workDir;
|
||||
Args = "rev-parse --git-common-dir";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd().StdOut;
|
||||
if (string.IsNullOrEmpty(rs))
|
||||
return null;
|
||||
|
||||
rs = rs.Trim();
|
||||
if (Path.IsPathRooted(rs))
|
||||
return rs;
|
||||
return Path.GetFullPath(Path.Combine(WorkingDirectory, rs));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
|
||||
Args = $"--no-optional-locks status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace SourceGit.Commands
|
|||
if (!output.IsSuccess)
|
||||
return rs;
|
||||
|
||||
var lines = output.StdOut.Split('\n');
|
||||
var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.EndsWith("/HEAD", StringComparison.Ordinal))
|
||||
|
|
21
src/Commands/QueryRevisionByRefName.cs
Normal file
21
src/Commands/QueryRevisionByRefName.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryRevisionByRefName : Command
|
||||
{
|
||||
public QueryRevisionByRefName(string repo, string refname)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"rev-parse {refname}";
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut))
|
||||
return rs.StdOut.Trim();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
namespace SourceGit.Commands
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryRevisionFileNames : Command
|
||||
{
|
||||
|
@ -9,13 +11,17 @@
|
|||
Args = $"ls-tree -r -z --name-only {revision}";
|
||||
}
|
||||
|
||||
public string[] Result()
|
||||
public List<string> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
return rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s -s {sha}";
|
||||
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()
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace SourceGit.Commands
|
|||
if (rs.IsSuccess)
|
||||
{
|
||||
var changes = new List<Models.Change>();
|
||||
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT2().Match(line);
|
||||
|
|
|
@ -1,60 +1,76 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Query stash changes. Requires git >= 2.32.0
|
||||
/// </summary>
|
||||
public partial class QueryStashChanges : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")]
|
||||
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
|
||||
private static partial Regex REG_RENAME_FORMAT();
|
||||
|
||||
public QueryStashChanges(string repo, string sha)
|
||||
public QueryStashChanges(string repo, string stash)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff --name-status --pretty=format: {sha}^ {sha}";
|
||||
Args = $"stash show -u --name-status \"{stash}\"";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
Exec();
|
||||
return _changes;
|
||||
}
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status[0])
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var outs = new List<Models.Change>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
case 'M':
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'A':
|
||||
change.Set(Models.ChangeState.Added);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'D':
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'R':
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
match = REG_RENAME_FORMAT().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var renamed = new Models.Change() { Path = match.Groups[1].Value };
|
||||
renamed.Set(Models.ChangeState.Renamed);
|
||||
outs.Add(renamed);
|
||||
}
|
||||
|
||||
private readonly List<Models.Change> _changes = new List<Models.Change>();
|
||||
continue;
|
||||
}
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status[0])
|
||||
{
|
||||
case 'M':
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
outs.Add(change);
|
||||
break;
|
||||
case 'A':
|
||||
change.Set(Models.ChangeState.Added);
|
||||
outs.Add(change);
|
||||
break;
|
||||
case 'D':
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
outs.Add(change);
|
||||
break;
|
||||
case 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
outs.Add(change);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
outs.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -8,7 +9,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "stash list --pretty=format:%H%n%ct%n%gd%n%s";
|
||||
Args = "stash list --format=%H%n%P%n%ct%n%gd%n%s";
|
||||
}
|
||||
|
||||
public List<Models.Stash> Result()
|
||||
|
@ -26,21 +27,32 @@ namespace SourceGit.Commands
|
|||
_stashes.Add(_current);
|
||||
break;
|
||||
case 1:
|
||||
_current.Time = ulong.Parse(line);
|
||||
ParseParent(line);
|
||||
break;
|
||||
case 2:
|
||||
_current.Name = line;
|
||||
_current.Time = ulong.Parse(line);
|
||||
break;
|
||||
case 3:
|
||||
_current.Name = line;
|
||||
break;
|
||||
case 4:
|
||||
_current.Message = line;
|
||||
break;
|
||||
}
|
||||
|
||||
_nextLineIdx++;
|
||||
if (_nextLineIdx > 3)
|
||||
if (_nextLineIdx > 4)
|
||||
_nextLineIdx = 0;
|
||||
}
|
||||
|
||||
private void ParseParent(string data)
|
||||
{
|
||||
if (data.Length < 8)
|
||||
return;
|
||||
|
||||
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
|
||||
private readonly List<Models.Stash> _stashes = new List<Models.Stash>();
|
||||
private Models.Stash _current = null;
|
||||
private int _nextLineIdx = 0;
|
||||
|
|
|
@ -24,11 +24,9 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var submodules = new List<Models.Submodule>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var lines = rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT1().Match(line);
|
||||
|
@ -51,13 +49,13 @@ namespace SourceGit.Commands
|
|||
|
||||
if (submodules.Count > 0)
|
||||
{
|
||||
Args = $"status -uno --porcelain -- {builder}";
|
||||
Args = $"--no-optional-locks status -uno --porcelain -- {builder}";
|
||||
rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
var dirty = new HashSet<string>();
|
||||
lines = rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_STATUS().Match(line);
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace SourceGit.Commands
|
|||
if (!rs.IsSuccess)
|
||||
return status;
|
||||
|
||||
var lines = rs.StdOut.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line[0] == '>')
|
||||
|
|
|
@ -37,6 +37,19 @@ namespace SourceGit.Commands
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool ProcessStashChanges(string repo, List<Models.DiffOption> opts, string saveTo)
|
||||
{
|
||||
using (var sw = File.Create(saveTo))
|
||||
{
|
||||
foreach (var opt in opts)
|
||||
{
|
||||
if (!ProcessSingleChange(repo, opt, sw))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ProcessSingleChange(string repo, Models.DiffOption opt, FileStream writer)
|
||||
{
|
||||
var starter = new ProcessStartInfo();
|
||||
|
|
|
@ -11,16 +11,26 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
}
|
||||
|
||||
public bool Push(string message)
|
||||
public bool Push(string message, bool includeUntracked = true, bool keepIndex = false)
|
||||
{
|
||||
Args = $"stash push -m \"{message}\"";
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push ");
|
||||
if (includeUntracked)
|
||||
builder.Append("--include-untracked ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\"");
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Push(string message, List<Models.Change> changes, bool keepIndex)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push ");
|
||||
builder.Append("stash push --include-untracked ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
|
@ -37,7 +47,7 @@ namespace SourceGit.Commands
|
|||
public bool Push(string message, string pathspecFromFile, bool keepIndex)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push --pathspec-from-file=\"");
|
||||
builder.Append("stash push --include-untracked --pathspec-from-file=\"");
|
||||
builder.Append(pathspecFromFile);
|
||||
builder.Append("\" ");
|
||||
if (keepIndex)
|
||||
|
@ -63,21 +73,22 @@ namespace SourceGit.Commands
|
|||
return Exec();
|
||||
}
|
||||
|
||||
public bool Apply(string name)
|
||||
public bool Apply(string name, bool restoreIndex)
|
||||
{
|
||||
Args = $"stash apply -q {name}";
|
||||
var opts = restoreIndex ? "--index" : string.Empty;
|
||||
Args = $"stash apply -q {opts} \"{name}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Pop(string name)
|
||||
{
|
||||
Args = $"stash pop -q {name}";
|
||||
Args = $"stash pop -q --index \"{name}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Drop(string name)
|
||||
{
|
||||
Args = $"stash drop -q {name}";
|
||||
Args = $"stash drop -q \"{name}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log --date-order --branches --remotes -{max} --pretty=format:\"%ct$%aN\"";
|
||||
Args = $"log --date-order --branches --remotes -{max} --format=%ct$%aN±%aE";
|
||||
}
|
||||
|
||||
public Models.Statistics Result()
|
||||
|
|
|
@ -48,9 +48,7 @@ namespace SourceGit.Commands
|
|||
if (remotes != null)
|
||||
{
|
||||
foreach (var r in remotes)
|
||||
{
|
||||
new Push(repo, r.Name, name, true).Exec();
|
||||
}
|
||||
new Push(repo, r.Name, $"refs/tags/{name}", true).Exec();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -20,12 +21,13 @@ namespace SourceGit.Commands
|
|||
var last = null as Models.Worktree;
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("worktree ", StringComparison.Ordinal))
|
||||
{
|
||||
last = new Models.Worktree() { FullPath = line.Substring(9).Trim() };
|
||||
last.RelativePath = Path.GetRelativePath(WorkingDirectory, last.FullPath);
|
||||
worktrees.Add(last);
|
||||
}
|
||||
else if (line.StartsWith("bare", StringComparison.Ordinal))
|
||||
|
@ -73,6 +75,8 @@ namespace SourceGit.Commands
|
|||
|
||||
if (!string.IsNullOrEmpty(tracking))
|
||||
Args += tracking;
|
||||
else if (!string.IsNullOrEmpty(name) && !createNew)
|
||||
Args += name;
|
||||
|
||||
_outputHandler = outputHandler;
|
||||
return Exec();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
|
@ -6,5 +7,8 @@ namespace SourceGit.Converters
|
|||
{
|
||||
public static readonly FuncValueConverter<bool, double> ToPageTabWidth =
|
||||
new FuncValueConverter<bool, double>(x => x ? 200 : double.NaN);
|
||||
|
||||
public static readonly FuncValueConverter<bool, FontWeight> IsBoldToFontWeight =
|
||||
new FuncValueConverter<bool, FontWeight>(x => x ? FontWeight.Bold : FontWeight.Normal);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ namespace SourceGit.Converters
|
|||
new FuncValueConverter<int, bool>(v => v != 1);
|
||||
|
||||
public static readonly FuncValueConverter<int, bool> IsSubjectLengthBad =
|
||||
new FuncValueConverter<int, bool>(v => v > ViewModels.Preference.Instance.SubjectGuideLength);
|
||||
new FuncValueConverter<int, bool>(v => v > ViewModels.Preferences.Instance.SubjectGuideLength);
|
||||
|
||||
public static readonly FuncValueConverter<int, bool> IsSubjectLengthGood =
|
||||
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preference.Instance.SubjectGuideLength);
|
||||
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preferences.Instance.SubjectGuideLength);
|
||||
|
||||
public static readonly FuncValueConverter<int, Thickness> ToTreeMargin =
|
||||
new FuncValueConverter<int, Thickness>(v => new Thickness(v * 16, 0, 0, 0));
|
||||
|
|
|
@ -78,5 +78,11 @@ namespace SourceGit.Converters
|
|||
return v.Substring(13);
|
||||
return v;
|
||||
});
|
||||
|
||||
public static readonly FuncValueConverter<string, bool> ContainsSpaces =
|
||||
new FuncValueConverter<string, bool>(v => v != null && v.Contains(' '));
|
||||
|
||||
public static readonly FuncValueConverter<string, bool> IsNotNullOrWhitespace =
|
||||
new FuncValueConverter<string, bool>(v => v != null && v.Trim().Length > 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,22 @@
|
|||
{
|
||||
public class ApplyWhiteSpaceMode
|
||||
{
|
||||
public static readonly ApplyWhiteSpaceMode[] Supported =
|
||||
[
|
||||
new ApplyWhiteSpaceMode("No Warn", "Turns off the trailing whitespace warning", "nowarn"),
|
||||
new ApplyWhiteSpaceMode("Warn", "Outputs warnings for a few such errors, but applies", "warn"),
|
||||
new ApplyWhiteSpaceMode("Error", "Raise errors and refuses to apply the patch", "error"),
|
||||
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 = App.Text(n);
|
||||
Desc = App.Text(d);
|
||||
Name = n;
|
||||
Desc = d;
|
||||
Arg = a;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
@ -196,8 +196,8 @@ namespace SourceGit.Models
|
|||
private string GetEmailHash(string email)
|
||||
{
|
||||
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered));
|
||||
var builder = new StringBuilder();
|
||||
var hash = MD5.HashData(Encoding.Default.GetBytes(lowered).AsSpan());
|
||||
var builder = new StringBuilder(hash.Length * 2);
|
||||
foreach (var c in hash)
|
||||
builder.Append(c.ToString("x2"));
|
||||
return builder.ToString();
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace SourceGit.Models
|
|||
public string Upstream { get; set; }
|
||||
public BranchTrackStatus TrackStatus { get; set; }
|
||||
public string Remote { get; set; }
|
||||
public bool IsUpsteamGone { get; set; }
|
||||
|
||||
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace SourceGit.Models
|
|||
public string OriginalPath { get; set; } = "";
|
||||
public ChangeDataForAmend DataForAmend { get; set; } = null;
|
||||
|
||||
public bool IsConflit
|
||||
public bool IsConflict
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
|
@ -10,7 +10,9 @@ namespace SourceGit.Models
|
|||
{
|
||||
public enum CommitSearchMethod
|
||||
{
|
||||
ByUser,
|
||||
BySHA = 0,
|
||||
ByAuthor,
|
||||
ByCommitter,
|
||||
ByMessage,
|
||||
ByFile,
|
||||
}
|
||||
|
@ -43,6 +45,7 @@ namespace SourceGit.Models
|
|||
public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
|
||||
public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
|
||||
public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateOnly);
|
||||
public string CommitterTimeShortStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateOnly);
|
||||
|
||||
public bool IsMerged { get; set; } = false;
|
||||
public bool IsCommitterVisible => !Author.Equals(Committer) || AuthorTime != CommitterTime;
|
||||
|
@ -149,9 +152,9 @@ namespace SourceGit.Models
|
|||
}
|
||||
}
|
||||
|
||||
public class CommitWithMessage
|
||||
public class CommitFullMessage
|
||||
{
|
||||
public Commit Commit { get; set; } = new Commit();
|
||||
public string Message { get; set; } = "";
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public List<Hyperlink> Links { get; set; } = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
Repository,
|
||||
Commit,
|
||||
Branch,
|
||||
}
|
||||
|
||||
public class CustomAction : ObservableObject
|
||||
|
@ -34,9 +35,16 @@ namespace SourceGit.Models
|
|||
set => SetProperty(ref _arguments, value);
|
||||
}
|
||||
|
||||
public bool WaitForExit
|
||||
{
|
||||
get => _waitForExit;
|
||||
set => SetProperty(ref _waitForExit, value);
|
||||
}
|
||||
|
||||
private string _name = string.Empty;
|
||||
private CustomActionScope _scope = CustomActionScope.Repository;
|
||||
private string _executable = string.Empty;
|
||||
private string _arguments = string.Empty;
|
||||
private bool _waitForExit = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,17 +32,17 @@ namespace SourceGit.Models
|
|||
|
||||
public static readonly List<DateTimeFormat> Supported = new List<DateTimeFormat>
|
||||
{
|
||||
new DateTimeFormat("yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss"),
|
||||
new DateTimeFormat("yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss"),
|
||||
new DateTimeFormat("yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss"),
|
||||
new DateTimeFormat("MM/dd/yyyy", "MM/dd/yyyy HH:mm:ss"),
|
||||
new DateTimeFormat("MM.dd.yyyy", "MM.dd.yyyy HH:mm:ss"),
|
||||
new DateTimeFormat("MM-dd-yyyy", "MM-dd-yyyy HH:mm:ss"),
|
||||
new DateTimeFormat("dd/MM/yyyy", "dd/MM/yyyy HH:mm:ss"),
|
||||
new DateTimeFormat("dd.MM.yyyy", "dd.MM.yyyy HH:mm:ss"),
|
||||
new DateTimeFormat("dd-MM-yyyy", "dd-MM-yyyy HH:mm:ss"),
|
||||
new DateTimeFormat("MMM d yyyy", "MMM d yyyy HH:mm:ss"),
|
||||
new DateTimeFormat("d MMM yyyy", "d MMM yyyy HH:mm:ss"),
|
||||
new DateTimeFormat("yyyy/MM/dd", "yyyy/MM/dd, HH:mm:ss"),
|
||||
new DateTimeFormat("yyyy.MM.dd", "yyyy.MM.dd, HH:mm:ss"),
|
||||
new DateTimeFormat("yyyy-MM-dd", "yyyy-MM-dd, HH:mm:ss"),
|
||||
new DateTimeFormat("MM/dd/yyyy", "MM/dd/yyyy, HH:mm:ss"),
|
||||
new DateTimeFormat("MM.dd.yyyy", "MM.dd.yyyy, HH:mm:ss"),
|
||||
new DateTimeFormat("MM-dd-yyyy", "MM-dd-yyyy, HH:mm:ss"),
|
||||
new DateTimeFormat("dd/MM/yyyy", "dd/MM/yyyy, HH:mm:ss"),
|
||||
new DateTimeFormat("dd.MM.yyyy", "dd.MM.yyyy, HH:mm:ss"),
|
||||
new DateTimeFormat("dd-MM-yyyy", "dd-MM-yyyy, HH:mm:ss"),
|
||||
new DateTimeFormat("MMM d yyyy", "MMM d yyyy, HH:mm:ss"),
|
||||
new DateTimeFormat("d MMM yyyy", "d MMM yyyy, HH:mm:ss"),
|
||||
};
|
||||
|
||||
private static readonly DateTime _example = new DateTime(2025, 1, 31, 8, 0, 0, DateTimeKind.Local);
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public enum DealWithLocalChanges
|
||||
{
|
||||
DoNothing,
|
||||
StashAndReaply,
|
||||
Discard,
|
||||
}
|
||||
}
|
|
@ -681,6 +681,18 @@ namespace SourceGit.Models
|
|||
public TextDiff TextDiff { get; set; } = null;
|
||||
public LFSDiff LFSDiff { get; set; } = null;
|
||||
|
||||
public string FileModeChange => string.IsNullOrEmpty(OldMode) ? string.Empty : $"{OldMode} → {NewMode}";
|
||||
public string FileModeChange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(OldMode) && string.IsNullOrEmpty(NewMode))
|
||||
return string.Empty;
|
||||
|
||||
var oldDisplay = string.IsNullOrEmpty(OldMode) ? "0" : OldMode;
|
||||
var newDisplay = string.IsNullOrEmpty(NewMode) ? "0" : NewMode;
|
||||
|
||||
return $"{oldDisplay} → {newDisplay}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ namespace SourceGit.Models
|
|||
new ExternalMerger(7, "win_merge", "WinMerge", "WinMergeU.exe", "\"$MERGED\"", "-u -e -sw \"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMerger(8, "codium", "VSCodium", "VSCodium.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMerger(9, "p4merge", "P4Merge", "p4merge.exe", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMerger(10, "plastic_merge", "Plastic SCM", "mergetool.exe", "-s=\"$REMOTE\" -b=\"$BASE\" -d=\"$LOCAL\" -r=\"$MERGED\" --automatic", "-s=\"$LOCAL\" -d=\"$REMOTE\""),
|
||||
};
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
|
|
|
@ -13,13 +13,18 @@
|
|||
public static readonly System.Version ADD_WITH_PATHSPECFILE = new System.Version(2, 25, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The minimal version of Git that supports the `stash` command with the `--pathspec-from-file` option.
|
||||
/// The minimal version of Git that supports the `stash push` command with the `--pathspec-from-file` option.
|
||||
/// </summary>
|
||||
public static readonly System.Version STASH_WITH_PATHSPECFILE = new System.Version(2, 26, 0);
|
||||
public static readonly System.Version STASH_PUSH_WITH_PATHSPECFILE = new System.Version(2, 26, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The minimal version of Git that supports the `stash` command with the `--staged` option.
|
||||
/// The minimal version of Git that supports the `stash push` command with the `--staged` option.
|
||||
/// </summary>
|
||||
public static readonly System.Version STASH_ONLY_STAGED = new System.Version(2, 35, 0);
|
||||
public static readonly System.Version STASH_PUSH_ONLY_STAGED = new System.Version(2, 35, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The minimal version of Git that supports the `stash show` command with the `-u` option.
|
||||
/// </summary>
|
||||
public static readonly System.Version STASH_SHOW_WITH_UNTRACKED = new System.Version(2, 32, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
{
|
||||
public interface IRepository
|
||||
{
|
||||
string FullPath { get; set; }
|
||||
string GitDir { get; set; }
|
||||
|
||||
void RefreshBranches();
|
||||
void RefreshWorktrees();
|
||||
void RefreshTags();
|
||||
|
|
|
@ -12,6 +12,12 @@ namespace SourceGit.Models
|
|||
Drop,
|
||||
}
|
||||
|
||||
public class InteractiveCommit
|
||||
{
|
||||
public Commit Commit { get; set; } = new Commit();
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class InteractiveRebaseJob
|
||||
{
|
||||
public string SHA { get; set; } = string.Empty;
|
||||
|
|
|
@ -17,6 +17,8 @@ namespace SourceGit.Models
|
|||
new Locale("Русский", "ru_RU"),
|
||||
new Locale("简体中文", "zh_CN"),
|
||||
new Locale("繁體中文", "zh_TW"),
|
||||
new Locale("日本語", "ja_JP"),
|
||||
new Locale("தமிழ் (Tamil)", "ta_IN"),
|
||||
};
|
||||
|
||||
public Locale(string name, string key)
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
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", "Use '--squash'", "--squash"),
|
||||
new MergeMode("Squash", "Squash merge", "--squash"),
|
||||
new MergeMode("Don't commit", "Merge without commit", "--no-ff --no-commit"),
|
||||
];
|
||||
|
||||
|
|
|
@ -1,79 +1,99 @@
|
|||
using System;
|
||||
using System.ClientModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class OpenAIChatMessage
|
||||
public partial class OpenAIResponse
|
||||
{
|
||||
[JsonPropertyName("role")]
|
||||
public string Role
|
||||
public OpenAIResponse(Action<string> onUpdate)
|
||||
{
|
||||
get;
|
||||
set;
|
||||
_onUpdate = onUpdate;
|
||||
}
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string Content
|
||||
public void Append(string text)
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
var buffer = text;
|
||||
|
||||
public class OpenAIChatChoice
|
||||
{
|
||||
[JsonPropertyName("index")]
|
||||
public int Index
|
||||
{
|
||||
get;
|
||||
set;
|
||||
if (_thinkTail.Length > 0)
|
||||
{
|
||||
_thinkTail.Append(buffer);
|
||||
buffer = _thinkTail.ToString();
|
||||
_thinkTail.Clear();
|
||||
}
|
||||
|
||||
buffer = REG_COT().Replace(buffer, "");
|
||||
|
||||
var startIdx = buffer.IndexOf('<', StringComparison.Ordinal);
|
||||
if (startIdx >= 0)
|
||||
{
|
||||
if (startIdx > 0)
|
||||
OnReceive(buffer.Substring(0, startIdx));
|
||||
|
||||
var endIdx = buffer.IndexOf(">", startIdx + 1, StringComparison.Ordinal);
|
||||
if (endIdx <= startIdx)
|
||||
{
|
||||
if (buffer.Length - startIdx <= 15)
|
||||
_thinkTail.Append(buffer.Substring(startIdx));
|
||||
else
|
||||
OnReceive(buffer.Substring(startIdx));
|
||||
}
|
||||
else if (endIdx < startIdx + 15)
|
||||
{
|
||||
var tag = buffer.Substring(startIdx + 1, endIdx - startIdx - 1);
|
||||
if (_thinkTags.Contains(tag))
|
||||
_thinkTail.Append(buffer.Substring(startIdx));
|
||||
else
|
||||
OnReceive(buffer.Substring(startIdx));
|
||||
}
|
||||
else
|
||||
{
|
||||
OnReceive(buffer.Substring(startIdx));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OnReceive(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public OpenAIChatMessage Message
|
||||
public void End()
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public class OpenAIChatResponse
|
||||
{
|
||||
[JsonPropertyName("choices")]
|
||||
public List<OpenAIChatChoice> Choices
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
}
|
||||
|
||||
public class OpenAIChatRequest
|
||||
{
|
||||
[JsonPropertyName("model")]
|
||||
public string Model
|
||||
{
|
||||
get;
|
||||
set;
|
||||
if (_thinkTail.Length > 0)
|
||||
{
|
||||
OnReceive(_thinkTail.ToString());
|
||||
_thinkTail.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("messages")]
|
||||
public List<OpenAIChatMessage> Messages
|
||||
private void OnReceive(string text)
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
if (!_hasTrimmedStart)
|
||||
{
|
||||
text = text.TrimStart();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
public void AddMessage(string role, string content)
|
||||
{
|
||||
Messages.Add(new OpenAIChatMessage { Role = role, Content = content });
|
||||
_hasTrimmedStart = true;
|
||||
}
|
||||
|
||||
_onUpdate.Invoke(text);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"<(think|thought|thinking|thought_chain)>.*?</\1>", RegexOptions.Singleline)]
|
||||
private static partial Regex REG_COT();
|
||||
|
||||
private Action<string> _onUpdate = null;
|
||||
private StringBuilder _thinkTail = new StringBuilder();
|
||||
private HashSet<string> _thinkTags = ["think", "thought", "thinking", "thought_chain"];
|
||||
private bool _hasTrimmedStart = false;
|
||||
}
|
||||
|
||||
public class OpenAIService : ObservableObject
|
||||
|
@ -102,6 +122,12 @@ namespace SourceGit.Models
|
|||
set => SetProperty(ref _model, value);
|
||||
}
|
||||
|
||||
public bool Streaming
|
||||
{
|
||||
get => _streaming;
|
||||
set => SetProperty(ref _streaming, value);
|
||||
}
|
||||
|
||||
public string AnalyzeDiffPrompt
|
||||
{
|
||||
get => _analyzeDiffPrompt;
|
||||
|
@ -147,45 +173,54 @@ namespace SourceGit.Models
|
|||
""";
|
||||
}
|
||||
|
||||
public OpenAIChatResponse Chat(string prompt, string question, CancellationToken cancellation)
|
||||
public void Chat(string prompt, string question, CancellationToken cancellation, Action<string> onUpdate)
|
||||
{
|
||||
var chat = new OpenAIChatRequest() { Model = Model };
|
||||
chat.AddMessage("user", prompt);
|
||||
chat.AddMessage("user", question);
|
||||
|
||||
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(60) };
|
||||
if (!string.IsNullOrEmpty(ApiKey))
|
||||
var server = new Uri(_server);
|
||||
var key = new ApiKeyCredential(_apiKey);
|
||||
var client = null as ChatClient;
|
||||
if (_server.Contains("openai.azure.com/", StringComparison.Ordinal))
|
||||
{
|
||||
if (Server.Contains("openai.azure.com/", StringComparison.Ordinal))
|
||||
client.DefaultRequestHeaders.Add("api-key", ApiKey);
|
||||
else
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {ApiKey}");
|
||||
var azure = new AzureOpenAIClient(server, key);
|
||||
client = azure.GetChatClient(_model);
|
||||
}
|
||||
else
|
||||
{
|
||||
var openai = new OpenAIClient(key, new() { Endpoint = server });
|
||||
client = openai.GetChatClient(_model);
|
||||
}
|
||||
|
||||
var req = new StringContent(JsonSerializer.Serialize(chat, JsonCodeGen.Default.OpenAIChatRequest), Encoding.UTF8, "application/json");
|
||||
var messages = new List<ChatMessage>();
|
||||
messages.Add(_model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt));
|
||||
messages.Add(new UserChatMessage(question));
|
||||
|
||||
try
|
||||
{
|
||||
var task = client.PostAsync(Server, req, cancellation);
|
||||
task.Wait(cancellation);
|
||||
var rsp = new OpenAIResponse(onUpdate);
|
||||
|
||||
var rsp = task.Result;
|
||||
var reader = rsp.Content.ReadAsStringAsync(cancellation);
|
||||
reader.Wait(cancellation);
|
||||
|
||||
var body = reader.Result;
|
||||
if (!rsp.IsSuccessStatusCode)
|
||||
if (_streaming)
|
||||
{
|
||||
throw new Exception($"AI service returns error code {rsp.StatusCode}. Body: {body ?? string.Empty}");
|
||||
var updates = client.CompleteChatStreaming(messages, null, cancellation);
|
||||
|
||||
foreach (var update in updates)
|
||||
{
|
||||
if (update.ContentUpdate.Count > 0)
|
||||
rsp.Append(update.ContentUpdate[0].Text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var completion = client.CompleteChat(messages, null, cancellation);
|
||||
|
||||
if (completion.Value.Content.Count > 0)
|
||||
rsp.Append(completion.Value.Content[0].Text);
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize(reader.Result, JsonCodeGen.Default.OpenAIChatResponse);
|
||||
rsp.End();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (cancellation.IsCancellationRequested)
|
||||
return null;
|
||||
|
||||
throw;
|
||||
if (!cancellation.IsCancellationRequested)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,6 +228,7 @@ namespace SourceGit.Models
|
|||
private string _server;
|
||||
private string _apiKey;
|
||||
private string _model;
|
||||
private bool _streaming = true;
|
||||
private string _analyzeDiffPrompt;
|
||||
private string _generateSubjectPrompt;
|
||||
}
|
||||
|
|
|
@ -8,12 +8,12 @@ namespace SourceGit.Models
|
|||
{
|
||||
[GeneratedRegex(@"^https?://([-a-zA-Z0-9:%._\+~#=]+@)?[-a-zA-Z0-9:%._\+~#=]{1,256}(\.[a-zA-Z0-9()]{1,6})?(:[0-9]{1,5})?\b(/[-a-zA-Z0-9()@:%_\+.~#?&=]+)+(\.git)?$")]
|
||||
private static partial Regex REG_HTTPS();
|
||||
[GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:[\w\-/~%]+/[\w\-\.%]+(\.git)?$")]
|
||||
[GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")]
|
||||
private static partial Regex REG_SSH1();
|
||||
[GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/~]+/[\w\-\.]+(\.git)?$")]
|
||||
[GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")]
|
||||
private static partial Regex REG_SSH2();
|
||||
|
||||
[GeneratedRegex(@"^git@([\w\.\-]+):([\w\-/~]+/[\w\-\.]+)\.git$")]
|
||||
[GeneratedRegex(@"^git@([\w\.\-]+):([\w\-/~%]+/[\w\-\.%]+)\.git$")]
|
||||
private static partial Regex REG_TO_VISIT_URL_CAPTURE();
|
||||
|
||||
private static readonly Regex[] URL_FORMATS = [
|
||||
|
|
|
@ -50,18 +50,6 @@ namespace SourceGit.Models
|
|||
set;
|
||||
} = true;
|
||||
|
||||
public DealWithLocalChanges DealWithLocalChangesOnCheckoutBranch
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = DealWithLocalChanges.DoNothing;
|
||||
|
||||
public bool EnablePruneOnFetch
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public bool EnableForceOnFetch
|
||||
{
|
||||
get;
|
||||
|
@ -74,12 +62,6 @@ namespace SourceGit.Models
|
|||
set;
|
||||
} = false;
|
||||
|
||||
public DealWithLocalChanges DealWithLocalChangesOnPull
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = DealWithLocalChanges.DoNothing;
|
||||
|
||||
public bool PreferRebaseInsteadOfMerge
|
||||
{
|
||||
get;
|
||||
|
@ -110,11 +92,17 @@ namespace SourceGit.Models
|
|||
set;
|
||||
} = false;
|
||||
|
||||
public DealWithLocalChanges DealWithLocalChangesOnCreateBranch
|
||||
public bool PushToRemoteWhenCreateTag
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = DealWithLocalChanges.DoNothing;
|
||||
} = true;
|
||||
|
||||
public bool PushToRemoteWhenDeleteTag
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public bool CheckoutBranchOnCreateBranch
|
||||
{
|
||||
|
@ -188,6 +176,12 @@ namespace SourceGit.Models
|
|||
set;
|
||||
} = false;
|
||||
|
||||
public bool AutoRestoreAfterStash
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public string PreferedOpenAIService
|
||||
{
|
||||
get;
|
||||
|
@ -230,6 +224,12 @@ namespace SourceGit.Models
|
|||
set;
|
||||
} = [];
|
||||
|
||||
public int PreferredMergeMode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = 0;
|
||||
|
||||
public Dictionary<string, FilterMode> CollectHistoriesFilters()
|
||||
{
|
||||
var map = new Dictionary<string, FilterMode>();
|
||||
|
@ -309,128 +309,81 @@ namespace SourceGit.Models
|
|||
|
||||
public string BuildHistoriesFilter()
|
||||
{
|
||||
var includedRefs = new List<string>();
|
||||
var excludedBranches = new List<string>();
|
||||
var excludedRemotes = new List<string>();
|
||||
var excludedTags = new List<string>();
|
||||
var includedBranches = new List<string>();
|
||||
var includedRemotes = new List<string>();
|
||||
var includedTags = new List<string>();
|
||||
foreach (var filter in HistoriesFilters)
|
||||
{
|
||||
if (filter.Type == FilterType.LocalBranch)
|
||||
{
|
||||
var name = filter.Pattern.Substring(11);
|
||||
var b = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]";
|
||||
|
||||
if (filter.Mode == FilterMode.Included)
|
||||
includedBranches.Add(b);
|
||||
includedRefs.Add(filter.Pattern);
|
||||
else if (filter.Mode == FilterMode.Excluded)
|
||||
excludedBranches.Add(b);
|
||||
excludedBranches.Add($"--exclude=\"{filter.Pattern.Substring(11)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
|
||||
}
|
||||
else if (filter.Type == FilterType.LocalBranchFolder)
|
||||
{
|
||||
if (filter.Mode == FilterMode.Included)
|
||||
includedBranches.Add($"{filter.Pattern.Substring(11)}/*");
|
||||
includedRefs.Add($"--branches={filter.Pattern.Substring(11)}/*");
|
||||
else if (filter.Mode == FilterMode.Excluded)
|
||||
excludedBranches.Add($"{filter.Pattern.Substring(11)}/*");
|
||||
excludedBranches.Add($"--exclude=\"{filter.Pattern.Substring(11)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
|
||||
}
|
||||
else if (filter.Type == FilterType.RemoteBranch)
|
||||
{
|
||||
var name = filter.Pattern.Substring(13);
|
||||
var r = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]";
|
||||
|
||||
if (filter.Mode == FilterMode.Included)
|
||||
includedRemotes.Add(r);
|
||||
includedRefs.Add(filter.Pattern);
|
||||
else if (filter.Mode == FilterMode.Excluded)
|
||||
excludedRemotes.Add(r);
|
||||
excludedRemotes.Add($"--exclude=\"{filter.Pattern.Substring(13)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
|
||||
}
|
||||
else if (filter.Type == FilterType.RemoteBranchFolder)
|
||||
{
|
||||
if (filter.Mode == FilterMode.Included)
|
||||
includedRemotes.Add($"{filter.Pattern.Substring(13)}/*");
|
||||
includedRefs.Add($"--remotes={filter.Pattern.Substring(13)}/*");
|
||||
else if (filter.Mode == FilterMode.Excluded)
|
||||
excludedRemotes.Add($"{filter.Pattern.Substring(13)}/*");
|
||||
excludedRemotes.Add($"--exclude=\"{filter.Pattern.Substring(13)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
|
||||
}
|
||||
else if (filter.Type == FilterType.Tag)
|
||||
{
|
||||
var name = filter.Pattern;
|
||||
var t = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]";
|
||||
|
||||
if (filter.Mode == FilterMode.Included)
|
||||
includedTags.Add(t);
|
||||
includedRefs.Add($"refs/tags/{filter.Pattern}");
|
||||
else if (filter.Mode == FilterMode.Excluded)
|
||||
excludedTags.Add(t);
|
||||
excludedTags.Add($"--exclude=\"{filter.Pattern}\" --decorate-refs-exclude=\"refs/tags/{filter.Pattern}\"");
|
||||
}
|
||||
}
|
||||
|
||||
bool hasIncluded = includedBranches.Count > 0 || includedRemotes.Count > 0 || includedTags.Count > 0;
|
||||
bool hasExcluded = excludedBranches.Count > 0 || excludedRemotes.Count > 0 || excludedTags.Count > 0;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
if (hasIncluded)
|
||||
if (includedRefs.Count > 0)
|
||||
{
|
||||
foreach (var b in includedBranches)
|
||||
foreach (var r in includedRefs)
|
||||
{
|
||||
builder.Append(r);
|
||||
builder.Append(' ');
|
||||
}
|
||||
}
|
||||
else if (excludedBranches.Count + excludedRemotes.Count + excludedTags.Count > 0)
|
||||
{
|
||||
foreach (var b in excludedBranches)
|
||||
{
|
||||
builder.Append("--branches=");
|
||||
builder.Append(b);
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
foreach (var r in includedRemotes)
|
||||
builder.Append("--exclude=HEAD --branches ");
|
||||
|
||||
foreach (var r in excludedRemotes)
|
||||
{
|
||||
builder.Append("--remotes=");
|
||||
builder.Append(r);
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
foreach (var t in includedTags)
|
||||
builder.Append("--exclude=origin/HEAD --remotes ");
|
||||
|
||||
foreach (var t in excludedTags)
|
||||
{
|
||||
builder.Append("--tags=");
|
||||
builder.Append(t);
|
||||
builder.Append(' ');
|
||||
}
|
||||
}
|
||||
else if (hasExcluded)
|
||||
{
|
||||
if (excludedBranches.Count > 0)
|
||||
{
|
||||
foreach (var b in excludedBranches)
|
||||
{
|
||||
builder.Append("--exclude=");
|
||||
builder.Append(b);
|
||||
builder.Append(" --decorate-refs-exclude=refs/heads/");
|
||||
builder.Append(b);
|
||||
builder.Append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append("--exclude=HEA[D] --branches ");
|
||||
|
||||
if (excludedRemotes.Count > 0)
|
||||
{
|
||||
foreach (var r in excludedRemotes)
|
||||
{
|
||||
builder.Append("--exclude=");
|
||||
builder.Append(r);
|
||||
builder.Append(" --decorate-refs-exclude=refs/remotes/");
|
||||
builder.Append(r);
|
||||
builder.Append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append("--exclude=origin/HEA[D] --remotes ");
|
||||
|
||||
if (excludedTags.Count > 0)
|
||||
{
|
||||
foreach (var t in excludedTags)
|
||||
{
|
||||
builder.Append("--exclude=");
|
||||
builder.Append(t);
|
||||
builder.Append(" --decorate-refs-exclude=refs/tags/");
|
||||
builder.Append(t);
|
||||
builder.Append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append("--tags ");
|
||||
}
|
||||
|
@ -477,11 +430,7 @@ namespace SourceGit.Models
|
|||
|
||||
public CustomAction AddNewCustomAction()
|
||||
{
|
||||
var act = new CustomAction()
|
||||
{
|
||||
Name = "Unnamed Custom Action",
|
||||
};
|
||||
|
||||
var act = new CustomAction() { Name = "Unnamed Action" };
|
||||
CustomActions.Add(act);
|
||||
return act;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,6 @@ namespace SourceGit.Models
|
|||
public class RevisionSubmodule
|
||||
{
|
||||
public Commit Commit { get; set; } = null;
|
||||
public string FullMessage { get; set; } = string.Empty;
|
||||
public CommitFullMessage FullMessage { get; set; } = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Reflection;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SourceGit.Models
|
||||
|
@ -32,5 +33,24 @@ namespace SourceGit.Models
|
|||
}
|
||||
}
|
||||
|
||||
public class AlreadyUpToDate { }
|
||||
public class AlreadyUpToDate
|
||||
{
|
||||
}
|
||||
|
||||
public class SelfUpdateFailed
|
||||
{
|
||||
public string Reason
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public SelfUpdateFailed(Exception e)
|
||||
{
|
||||
if (e.InnerException is { } inner)
|
||||
Reason = inner.Message;
|
||||
else
|
||||
Reason = e.Message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ namespace SourceGit.Models
|
|||
new ShellOrTerminal("mac-terminal", "Terminal", ""),
|
||||
new ShellOrTerminal("iterm2", "iTerm", ""),
|
||||
new ShellOrTerminal("warp", "Warp", ""),
|
||||
new ShellOrTerminal("ghostty", "Ghostty", "")
|
||||
};
|
||||
}
|
||||
else
|
||||
|
@ -56,6 +57,7 @@ namespace SourceGit.Models
|
|||
new ShellOrTerminal("mate-terminal", "MATE Terminal", "mate-terminal"),
|
||||
new ShellOrTerminal("foot", "Foot", "foot"),
|
||||
new ShellOrTerminal("wezterm", "WezTerm", "wezterm"),
|
||||
new ShellOrTerminal("ptyxis", "Ptyxis", "ptyxis"),
|
||||
new ShellOrTerminal("custom", "Custom", ""),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
|
@ -6,6 +7,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string SHA { get; set; } = "";
|
||||
public List<string> Parents { get; set; } = [];
|
||||
public ulong Time { get; set; } = 0;
|
||||
public string Message { get; set; } = "";
|
||||
|
||||
|
|
|
@ -18,9 +18,9 @@ namespace SourceGit.Models
|
|||
ThisWeek,
|
||||
}
|
||||
|
||||
public class StaticsticsAuthor(string name, int count)
|
||||
public class StaticsticsAuthor(User user, int count)
|
||||
{
|
||||
public string Name { get; set; } = name;
|
||||
public User User { get; set; } = user;
|
||||
public int Count { get; set; } = count;
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ namespace SourceGit.Models
|
|||
}
|
||||
}
|
||||
|
||||
public void AddCommit(DateTime time, string author)
|
||||
public void AddCommit(DateTime time, User author)
|
||||
{
|
||||
Total++;
|
||||
|
||||
|
@ -126,7 +126,7 @@ namespace SourceGit.Models
|
|||
}
|
||||
|
||||
private StaticsticsMode _mode = StaticsticsMode.All;
|
||||
private Dictionary<string, int> _mapUsers = new Dictionary<string, int>();
|
||||
private Dictionary<User, int> _mapUsers = new Dictionary<User, int>();
|
||||
private Dictionary<DateTime, int> _mapSamples = new Dictionary<DateTime, int>();
|
||||
}
|
||||
|
||||
|
@ -150,14 +150,16 @@ namespace SourceGit.Models
|
|||
|
||||
public void AddCommit(string author, double timestamp)
|
||||
{
|
||||
var user = User.FindOrAdd(author);
|
||||
|
||||
var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime();
|
||||
if (time >= _thisWeekStart)
|
||||
Week.AddCommit(time, author);
|
||||
Week.AddCommit(time, user);
|
||||
|
||||
if (time >= _thisMonthStart)
|
||||
Month.AddCommit(time, author);
|
||||
Month.AddCommit(time, user);
|
||||
|
||||
All.AddCommit(time, author);
|
||||
All.AddCommit(time, user);
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
|
|
|
@ -313,7 +313,7 @@ namespace SourceGit.Models
|
|||
|
||||
private static bool IsNameChar(char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
|
||||
}
|
||||
|
||||
// (?) notice or log if variable is not found
|
||||
|
|
|
@ -21,10 +21,11 @@ namespace SourceGit.Models
|
|||
{
|
||||
private static readonly ExtraGrammar[] s_extraGrammars =
|
||||
[
|
||||
new ExtraGrammar("source.toml", ".toml", "toml.json"),
|
||||
new ExtraGrammar("source.kotlin", ".kotlin", "kotlin.json"),
|
||||
new ExtraGrammar("source.hx", ".hx", "haxe.json"),
|
||||
new ExtraGrammar("source.hxml", ".hxml", "hxml.json"),
|
||||
new ExtraGrammar("source.toml", [".toml"], "toml.json"),
|
||||
new ExtraGrammar("source.kotlin", [".kotlin", ".kt", ".kts"], "kotlin.json"),
|
||||
new ExtraGrammar("source.hx", [".hx"], "haxe.json"),
|
||||
new ExtraGrammar("source.hxml", [".hxml"], "hxml.json"),
|
||||
new ExtraGrammar("text.html.jsp", [".jsp", ".jspf", ".tag"], "jsp.json"),
|
||||
];
|
||||
|
||||
public static string GetScope(string file, RegistryOptions reg)
|
||||
|
@ -36,13 +37,14 @@ namespace SourceGit.Models
|
|||
extension = ".xml";
|
||||
else if (extension == ".command")
|
||||
extension = ".sh";
|
||||
else if (extension == ".kt" || extension == ".kts")
|
||||
extension = ".kotlin";
|
||||
|
||||
foreach (var grammar in s_extraGrammars)
|
||||
{
|
||||
if (grammar.Extension.Equals(extension, StringComparison.OrdinalIgnoreCase))
|
||||
return grammar.Scope;
|
||||
foreach (var ext in grammar.Extensions)
|
||||
{
|
||||
if (ext.Equals(extension, StringComparison.OrdinalIgnoreCase))
|
||||
return grammar.Scope;
|
||||
}
|
||||
}
|
||||
|
||||
return reg.GetScopeByExtension(extension);
|
||||
|
@ -71,10 +73,10 @@ namespace SourceGit.Models
|
|||
return reg.GetGrammar(scopeName);
|
||||
}
|
||||
|
||||
private record ExtraGrammar(string Scope, string Extension, string File)
|
||||
private record ExtraGrammar(string Scope, List<string> Extensions, string File)
|
||||
{
|
||||
public readonly string Scope = Scope;
|
||||
public readonly string Extension = Extension;
|
||||
public readonly List<string> Extensions = Extensions;
|
||||
public readonly string File = File;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,11 @@ namespace SourceGit.Models
|
|||
return _caches.GetOrAdd(data, key => new User(key));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} <{Email}>";
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<string, User> _caches = new ConcurrentDictionary<string, User>();
|
||||
private readonly int _hash;
|
||||
}
|
||||
|
|
|
@ -8,12 +8,12 @@ namespace SourceGit.Models
|
|||
{
|
||||
public class Watcher : IDisposable
|
||||
{
|
||||
public Watcher(IRepository repo)
|
||||
public Watcher(IRepository repo, string fullpath, string gitDir)
|
||||
{
|
||||
_repo = repo;
|
||||
|
||||
_wcWatcher = new FileSystemWatcher();
|
||||
_wcWatcher.Path = _repo.FullPath;
|
||||
_wcWatcher.Path = fullpath;
|
||||
_wcWatcher.Filter = "*";
|
||||
_wcWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.CreationTime;
|
||||
_wcWatcher.IncludeSubdirectories = true;
|
||||
|
@ -23,15 +23,8 @@ namespace SourceGit.Models
|
|||
_wcWatcher.Deleted += OnWorkingCopyChanged;
|
||||
_wcWatcher.EnableRaisingEvents = true;
|
||||
|
||||
// If this repository is a worktree repository, just watch the main repository's gitdir.
|
||||
var gitDirNormalized = _repo.GitDir.Replace("\\", "/");
|
||||
var worktreeIdx = gitDirNormalized.IndexOf(".git/worktrees/", StringComparison.Ordinal);
|
||||
var repoWatchDir = _repo.GitDir;
|
||||
if (worktreeIdx > 0)
|
||||
repoWatchDir = _repo.GitDir.Substring(0, worktreeIdx + 4);
|
||||
|
||||
_repoWatcher = new FileSystemWatcher();
|
||||
_repoWatcher.Path = repoWatchDir;
|
||||
_repoWatcher.Path = gitDir;
|
||||
_repoWatcher.Filter = "*";
|
||||
_repoWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName;
|
||||
_repoWatcher.IncludeSubdirectories = true;
|
||||
|
@ -72,6 +65,11 @@ namespace SourceGit.Models
|
|||
_updateBranch = DateTime.Now.ToFileTime() - 1;
|
||||
}
|
||||
|
||||
public void MarkTagDirtyManually()
|
||||
{
|
||||
_updateTags = DateTime.Now.ToFileTime() - 1;
|
||||
}
|
||||
|
||||
public void MarkWorkingCopyDirtyManually()
|
||||
{
|
||||
_updateWC = DateTime.Now.ToFileTime() - 1;
|
||||
|
@ -109,6 +107,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
_updateBranch = 0;
|
||||
_updateWC = 0;
|
||||
_updateSubmodules = 0;
|
||||
|
||||
if (_updateTags > 0)
|
||||
{
|
||||
|
@ -119,6 +118,7 @@ namespace SourceGit.Models
|
|||
Task.Run(_repo.RefreshBranches);
|
||||
Task.Run(_repo.RefreshCommits);
|
||||
Task.Run(_repo.RefreshWorkingCopyChanges);
|
||||
Task.Run(_repo.RefreshSubmodules);
|
||||
Task.Run(_repo.RefreshWorktrees);
|
||||
}
|
||||
|
||||
|
@ -131,20 +131,20 @@ namespace SourceGit.Models
|
|||
if (_updateSubmodules > 0 && now > _updateSubmodules)
|
||||
{
|
||||
_updateSubmodules = 0;
|
||||
_repo.RefreshSubmodules();
|
||||
Task.Run(_repo.RefreshSubmodules);
|
||||
}
|
||||
|
||||
if (_updateStashes > 0 && now > _updateStashes)
|
||||
{
|
||||
_updateStashes = 0;
|
||||
_repo.RefreshStashes();
|
||||
Task.Run(_repo.RefreshStashes);
|
||||
}
|
||||
|
||||
if (_updateTags > 0 && now > _updateTags)
|
||||
{
|
||||
_updateTags = 0;
|
||||
_repo.RefreshTags();
|
||||
_repo.RefreshCommits();
|
||||
Task.Run(_repo.RefreshTags);
|
||||
Task.Run(_repo.RefreshCommits);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,12 +173,6 @@ namespace SourceGit.Models
|
|||
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
|
||||
{
|
||||
_updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
|
||||
|
||||
lock (_lockSubmodule)
|
||||
{
|
||||
if (_submodules.Count > 0)
|
||||
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
|
||||
}
|
||||
}
|
||||
else if (name.StartsWith("objects/", StringComparison.Ordinal) || name.Equals("index", StringComparison.Ordinal))
|
||||
{
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
public string Branch { get; set; } = string.Empty;
|
||||
public string FullPath { get; set; } = string.Empty;
|
||||
public string RelativePath { get; set; } = string.Empty;
|
||||
public string Head { get; set; } = string.Empty;
|
||||
public bool IsBare { get; set; } = false;
|
||||
public bool IsDetached { get; set; } = false;
|
||||
|
@ -21,15 +22,15 @@ namespace SourceGit.Models
|
|||
get
|
||||
{
|
||||
if (IsDetached)
|
||||
return $"(deteched HEAD at {Head.Substring(10)})";
|
||||
return $"deteched HEAD at {Head.Substring(10)}";
|
||||
|
||||
if (Branch.StartsWith("refs/heads/", System.StringComparison.Ordinal))
|
||||
return $"({Branch.Substring(11)})";
|
||||
return Branch.Substring(11);
|
||||
|
||||
if (Branch.StartsWith("refs/remotes/", System.StringComparison.Ordinal))
|
||||
return $"({Branch.Substring(13)})";
|
||||
return Branch.Substring(13);
|
||||
|
||||
return $"({Branch})";
|
||||
return Branch;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,13 +65,16 @@ namespace SourceGit.Native
|
|||
{
|
||||
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var cwd = string.IsNullOrEmpty(workdir) ? home : workdir;
|
||||
var terminal = OS.ShellOrTerminal;
|
||||
|
||||
var startInfo = new ProcessStartInfo();
|
||||
startInfo.WorkingDirectory = cwd;
|
||||
startInfo.FileName = OS.ShellOrTerminal;
|
||||
startInfo.FileName = terminal;
|
||||
|
||||
if (OS.ShellOrTerminal.EndsWith("wezterm", StringComparison.OrdinalIgnoreCase))
|
||||
if (terminal.EndsWith("wezterm", StringComparison.OrdinalIgnoreCase))
|
||||
startInfo.Arguments = $"start --cwd \"{cwd}\"";
|
||||
else if (terminal.EndsWith("ptyxis", StringComparison.OrdinalIgnoreCase))
|
||||
startInfo.Arguments = $"--new-window --working-directory=\"{cwd}\"";
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
@ -18,9 +18,22 @@ namespace SourceGit.Native
|
|||
DisableDefaultApplicationMenuItems = true,
|
||||
});
|
||||
|
||||
// Fix `PATH` env on macOS.
|
||||
var path = Environment.GetEnvironmentVariable("PATH");
|
||||
if (string.IsNullOrEmpty(path))
|
||||
path = "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
||||
else if (!path.Contains("/opt/homebrew/", StringComparison.Ordinal))
|
||||
path = "/opt/homebrew/bin:/opt/homebrew/sbin:" + path;
|
||||
|
||||
var customPathFile = Path.Combine(OS.DataDir, "PATH");
|
||||
if (File.Exists(customPathFile))
|
||||
OS.CustomPathEnv = File.ReadAllText(customPathFile).Trim();
|
||||
{
|
||||
var env = File.ReadAllText(customPathFile).Trim();
|
||||
if (!string.IsNullOrEmpty(env))
|
||||
path = env;
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable("PATH", path);
|
||||
}
|
||||
|
||||
public string FindGitExecutable()
|
||||
|
@ -44,6 +57,8 @@ namespace SourceGit.Native
|
|||
return "iTerm";
|
||||
case "warp":
|
||||
return "Warp";
|
||||
case "ghostty":
|
||||
return "Ghostty";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
|
|
@ -25,15 +25,10 @@ namespace SourceGit.Native
|
|||
void OpenWithDefaultEditor(string file);
|
||||
}
|
||||
|
||||
public static string DataDir {
|
||||
get;
|
||||
private set;
|
||||
} = string.Empty;
|
||||
|
||||
public static string CustomPathEnv
|
||||
public static string DataDir
|
||||
{
|
||||
get;
|
||||
set;
|
||||
private set;
|
||||
} = string.Empty;
|
||||
|
||||
public static string GitExecutable
|
||||
|
@ -61,12 +56,14 @@ namespace SourceGit.Native
|
|||
private set;
|
||||
} = new Version(0, 0, 0);
|
||||
|
||||
public static string ShellOrTerminal {
|
||||
public static string ShellOrTerminal
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = string.Empty;
|
||||
|
||||
public static List<Models.ExternalTool> ExternalTools {
|
||||
public static List<Models.ExternalTool> ExternalTools
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
@ -165,6 +162,15 @@ namespace SourceGit.Native
|
|||
_backend.OpenWithDefaultEditor(file);
|
||||
}
|
||||
|
||||
public static string GetAbsPath(string root, string sub)
|
||||
{
|
||||
var fullpath = Path.Combine(root, sub);
|
||||
if (OperatingSystem.IsWindows())
|
||||
return fullpath.Replace('/', '\\');
|
||||
|
||||
return fullpath;
|
||||
}
|
||||
|
||||
private static void UpdateGitVersion()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_gitExecutable) || !File.Exists(_gitExecutable))
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Text;
|
|||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Native
|
||||
{
|
||||
|
@ -152,7 +153,7 @@ namespace SourceGit.Native
|
|||
|
||||
public void OpenBrowser(string url)
|
||||
{
|
||||
var info = new ProcessStartInfo("cmd", $"/c start {url}");
|
||||
var info = new ProcessStartInfo("cmd", $"/c start \"\" \"{url}\"");
|
||||
info.CreateNoWindow = true;
|
||||
Process.Start(info);
|
||||
}
|
||||
|
@ -214,12 +215,17 @@ namespace SourceGit.Native
|
|||
|
||||
private void FixWindowFrameOnWin10(Window w)
|
||||
{
|
||||
var platformHandle = w.TryGetPlatformHandle();
|
||||
if (platformHandle == null)
|
||||
return;
|
||||
// Schedule the DWM frame extension to run in the next render frame
|
||||
// to ensure proper timing with the window initialization sequence
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
var platformHandle = w.TryGetPlatformHandle();
|
||||
if (platformHandle == null)
|
||||
return;
|
||||
|
||||
var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
|
||||
DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins);
|
||||
var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
|
||||
DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins);
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
#region EXTERNAL_EDITOR_FINDER
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"information_for_contributors": [
|
||||
"This file has been copied from https://github.com/vshaxe/haxe-TmLanguage/blob/ddad8b4c6d0781ac20be0481174ec1be772c5da5/haxe.tmLanguage",
|
||||
"and converted to JSON using https://marketplace.visualstudio.com/items?itemName=pedro-w.tmlanguage"
|
||||
"and converted to JSON using https://marketplace.visualstudio.com/items?itemName=pedro-w.tmlanguage",
|
||||
"The original file was licensed under the MIT License",
|
||||
"https://github.com/vshaxe/haxe-TmLanguage/blob/ddad8b4c6d0781ac20be0481174ec1be772c5da5/LICENSE.md"
|
||||
],
|
||||
"fileTypes": [
|
||||
"hx",
|
||||
|
@ -2485,4 +2487,4 @@
|
|||
"name": "variable.other.hx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"information_for_contributors": [
|
||||
"This file has been copied from https://github.com/vshaxe/haxe-TmLanguage/blob/ddad8b4c6d0781ac20be0481174ec1be772c5da5/hxml.tmLanguage",
|
||||
"and converted to JSON using https://marketplace.visualstudio.com/items?itemName=pedro-w.tmlanguage"
|
||||
"and converted to JSON using https://marketplace.visualstudio.com/items?itemName=pedro-w.tmlanguage",
|
||||
"The original file was licensed under the MIT License",
|
||||
"https://github.com/vshaxe/haxe-TmLanguage/blob/ddad8b4c6d0781ac20be0481174ec1be772c5da5/LICENSE.md"
|
||||
],
|
||||
"fileTypes": [
|
||||
"hxml"
|
||||
|
@ -67,4 +69,4 @@
|
|||
],
|
||||
"scopeName": "source.hxml",
|
||||
"uuid": "CB1B853A-C4C8-42C3-BA70-1B1605BE51C1"
|
||||
}
|
||||
}
|
||||
|
|
100
src/Resources/Grammars/jsp.json
Normal file
100
src/Resources/Grammars/jsp.json
Normal file
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"information_for_contributors": [
|
||||
"This file has been copied from https://github.com/samuel-weinhardt/vscode-jsp-lang/blob/0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355/syntaxes/jsp.tmLanguage.json",
|
||||
"The original file was licensed under the MIT License",
|
||||
"https://github.com/samuel-weinhardt/vscode-jsp-lang/blob/0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355/LICENSE"
|
||||
],
|
||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||
"name": "Jakarta Server Pages",
|
||||
"fileTypes": ["jsp", "jspf", "tag"],
|
||||
"scopeName": "text.html.jsp",
|
||||
"patterns": [
|
||||
{ "include": "#comment" },
|
||||
{ "include": "#directive" },
|
||||
{ "include": "#expression" },
|
||||
{ "include": "text.html.derivative" }
|
||||
],
|
||||
"injections": {
|
||||
"L:text.html.jsp -comment -meta.tag.directive.jsp -meta.tag.scriptlet.jsp": {
|
||||
"patterns": [
|
||||
{ "include": "#scriptlet" }
|
||||
],
|
||||
"comment": "allow scriptlets anywhere except comments and nested"
|
||||
},
|
||||
"L:meta.attribute (string.quoted.single.html | string.quoted.double.html) -string.template.expression.jsp": {
|
||||
"patterns": [
|
||||
{ "include": "#expression" },
|
||||
{ "include": "text.html.derivative" }
|
||||
],
|
||||
"comment": "allow expressions and tags within HTML attributes (not nested)"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"comment": {
|
||||
"name": "comment.block.jsp",
|
||||
"begin": "<%--",
|
||||
"end": "--%>"
|
||||
},
|
||||
"directive": {
|
||||
"name": "meta.tag.directive.jsp",
|
||||
"begin": "(<)(%@)",
|
||||
"end": "(%)(>)",
|
||||
"beginCaptures": {
|
||||
"1": { "name": "punctuation.definition.tag.jsp" },
|
||||
"2": { "name": "entity.name.tag.jsp" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"1": { "name": "entity.name.tag.jsp" },
|
||||
"2": { "name": "punctuation.definition.tag.jsp" }
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\b(attribute|include|page|tag|taglib|variable)\\b(?!\\s*=)",
|
||||
"name": "keyword.control.directive.jsp"
|
||||
},
|
||||
{ "include": "text.html.basic#attribute" }
|
||||
]
|
||||
},
|
||||
"scriptlet": {
|
||||
"name": "meta.tag.scriptlet.jsp",
|
||||
"contentName": "meta.embedded.block.java",
|
||||
"begin": "(<)(%[\\s!=])",
|
||||
"end": "(%)(>)",
|
||||
"beginCaptures": {
|
||||
"1": { "name": "punctuation.definition.tag.jsp" },
|
||||
"2": { "name": "entity.name.tag.jsp" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"1": { "name": "entity.name.tag.jsp" },
|
||||
"2": { "name": "punctuation.definition.tag.jsp" }
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\{(?=\\s*(%>|$))",
|
||||
"comment": "consume trailing curly brackets for fragmented scriptlets"
|
||||
},
|
||||
{ "include": "source.java" }
|
||||
]
|
||||
},
|
||||
"expression": {
|
||||
"name": "string.template.expression.jsp",
|
||||
"contentName": "meta.embedded.block.java",
|
||||
"begin": "[$#]\\{",
|
||||
"end": "\\}",
|
||||
"beginCaptures": {
|
||||
"0": { "name": "punctuation.definition.template-expression.begin.jsp" }
|
||||
},
|
||||
"endCaptures": {
|
||||
"0": { "name": "punctuation.definition.template-expression.end.jsp" }
|
||||
},
|
||||
"patterns": [
|
||||
{ "include": "#escape" },
|
||||
{ "include": "source.java" }
|
||||
]
|
||||
},
|
||||
"escape": {
|
||||
"match": "\\\\.",
|
||||
"name": "constant.character.escape.jsp"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"information_for_contributors": [
|
||||
"This file has been copied from https://github.com/eclipse/buildship/blob/6bb773e7692f913dec27105129ebe388de34e68b/org.eclipse.buildship.kotlindsl.provider/kotlin.tmLanguage.json"
|
||||
"This file has been copied from https://github.com/eclipse/buildship/blob/6bb773e7692f913dec27105129ebe388de34e68b/org.eclipse.buildship.kotlindsl.provider/kotlin.tmLanguage.json",
|
||||
"The original file was licensed under the Eclipse Public License, Version 1.0",
|
||||
"https://github.com/eclipse-buildship/buildship/blob/6bb773e7692f913dec27105129ebe388de34e68b/README.md"
|
||||
],
|
||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||
"name": "Kotlin",
|
||||
|
@ -698,4 +700,4 @@
|
|||
"name": "variable.language.this.kotlin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
"scopeName": "source.toml",
|
||||
"uuid": "8b4e5008-c50d-11ea-a91b-54ee75aeeb97",
|
||||
"information_for_contributors": [
|
||||
"Originally was maintained by aster (galaster@foxmail.com). This notice is only kept here for the record, please don't send e-mails about bugs and other issues."
|
||||
"Originally was maintained by aster (galaster@foxmail.com). This notice is only kept here for the record, please don't send e-mails about bugs and other issues.",
|
||||
"This file has been copied from https://github.com/kkiyama117/coc-toml/blob/main/toml.tmLanguage.json",
|
||||
"The original file was licensed under the MIT License",
|
||||
"https://github.com/kkiyama117/coc-toml/blob/main/LICENSE"
|
||||
],
|
||||
"patterns": [
|
||||
{
|
||||
|
|
|
@ -5,12 +5,16 @@
|
|||
<StreamGeometry x:Key="Icons.Binary">M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Blame">M128 256h192a64 64 0 110 128H128a64 64 0 110-128zm576 192h192a64 64 0 010 128h-192a64 64 0 010-128zm-576 192h192a64 64 0 010 128H128a64 64 0 010-128zm576 0h192a64 64 0 010 128h-192a64 64 0 010-128zm0-384h192a64 64 0 010 128h-192a64 64 0 010-128zM128 448h192a64 64 0 110 128H128a64 64 0 110-128zm384-320a64 64 0 0164 64v640a64 64 0 01-128 0V192a64 64 0 0164-64z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Bookmark">M832 64H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V96c0-18-14-32-32-32zM736 596 624 502 506 596V131h230v318z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Bottom">M509 546 780 275 871 366 509 728 147 366 238 275zM509 728h-362v128h724v-128z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Branch">M757 226a143 143 0 00-55 276 96 96 0 01-88 59h-191a187 187 0 00-96 27V312a143 143 0 10-96 0v399a143 143 0 10103 2 96 96 0 0188-59h191a191 191 0 00187-151 143 143 0 00-43-279zM280 130a48 48 0 110 96 48 48 0 010-96zm0 764a48 48 0 110-96 48 48 0 010 96zM757 417a48 48 0 110-96 48 48 0 010 96z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Branch.Add">M896 128h-64V64c0-35-29-64-64-64s-64 29-64 64v64h-64c-35 0-64 29-64 64s29 64 64 64h64v64c0 35 29 64 64 64s64-29 64-64V256h64c35 0 64-29 64-64s-29-64-64-64zm-204 307C673 481 628 512 576 512H448c-47 0-90 13-128 35V372C394 346 448 275 448 192c0-106-86-192-192-192S64 86 64 192c0 83 54 154 128 180v280c-74 26-128 97-128 180c0 106 86 192 192 192s192-86 192-192c0-67-34-125-84-159c22-20 52-33 84-33h128c122 0 223-85 249-199c-19 4-37 7-57 7c-26 0-51-5-76-13zM256 128c35 0 64 29 64 64s-29 64-64 64s-64-29-64-64s29-64 64-64zm0 768c-35 0-64-29-64-64s29-64 64-64s64 29 64 64s-29 64-64 64z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Check">M512 597m-1 0a1 1 0 103 0a1 1 0 10-3 0ZM810 393 732 315 448 600 293 444 214 522l156 156 78 78 362-362z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.CheckCircled">M512 32C246 32 32 250 32 512s218 480 480 480 480-218 480-480S774 32 512 32zm269 381L496 698c-26 26-61 26-83 0L243 528c-26-26-26-61 0-83s61-26 83 0l128 128 240-240c26-26 61-26 83 0 26 19 26 54 3 80z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Changes">M747 467c29 0 56 4 82 12v-363c0-47-38-84-84-84H125c-47 0-84 38-84 84v707c0 47 38 84 84 84h375a287 287 0 01-43-152c0-160 129-289 289-289zm-531-250h438c19 0 34 15 34 34s-15 34-34 34H216c-19 0-34-15-34-34s15-34 34-34zm0 179h263c19 0 34 15 34 34s-15 34-34 34H216c-19 0-34-15-34-34s15-34 34-34zm131 247h-131c-19 0-34-15-34-34s15-34 34-34h131c19 0 34 15 34 34s-15 34-34 34zM747 521c-130 0-236 106-236 236S617 992 747 992s236-106 236-236S877 521 747 521zm11 386v-65h-130c-12 0-22-10-22-22s10-22 22-22h260l-130 108zm108-192H606l130-108v65h130c12 0 22 10 22 22s-10 22-22 22z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.CherryPick">M529 511c115 0 212 79 239 185h224a62 62 0 017 123l-7 0-224 0a247 247 0 01-479 0H65a62 62 0 01-7-123l7-0h224a247 247 0 01239-185zm0 124a124 124 0 100 247 124 124 0 000-247zm0-618c32 0 58 24 61 55l0 7V206c89 11 165 45 225 103a74 74 0 0122 45l0 9v87a62 62 0 01-123 7l-0-7v-65l-6-4c-43-33-97-51-163-53l-17-0c-74 0-133 18-180 54l-6 4v65a62 62 0 01-55 61l-7 0a62 62 0 01-61-55l-0-7V362c0-20 8-39 23-53 60-58 135-92 224-103V79c0-34 28-62 62-62z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.CircleDown">M512 926c-229 0-414-186-414-414S283 98 512 98s414 186 414 414-186 414-414 414zm0-73c189 0 341-153 341-341S701 171 512 171 171 323 171 512s153 341 341 341zm-6-192L284 439l52-52 171 171 171-171L728 439l-222 222z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Clear">M512 57c251 0 455 204 455 455S763 967 512 967 57 763 57 512 261 57 512 57zm181 274c-11-11-29-11-40 0L512 472 371 331c-11-11-29-11-40 0-11 11-11 29 0 40L471 512 331 653c-11 11-11 29 0 40 11 11 29 11 40 0l141-141 141 141c11 11 29 11 40 0 11-11 11-29 0-40L552 512l141-141c11-11 11-29 0-40z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.ClearNotifications">M591 907A85 85 0 01427 875h114a299 299 0 0050 32zM725 405c130 0 235 105 235 235s-105 235-235 235-235-105-235-235 105-235 235-235zM512 64a43 43 0 0143 43v24c126 17 229 107 264 225A298 298 0 00725 341l-4 0A235 235 0 00512 213l-5 0c-125 4-224 104-228 229l-0 6v167a211 211 0 01-26 101l-4 7-14 23h211a298 298 0 0050 85l-276-0a77 77 0 01-66-39c-13-22-14-50-2-73l2-4 22-36c10-17 16-37 17-57l0-7v-167C193 287 313 153 469 131V107a43 43 0 0139-43zm345 505L654 771a149 149 0 00202-202zM725 491a149 149 0 00-131 220l202-202A149 149 0 00725 491z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Clean">M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Clone">M1280 704c0 141-115 256-256 256H288C129 960 0 831 0 672c0-126 80-232 192-272A327 327 0 01192 384c0-177 143-320 320-320 119 0 222 64 277 160C820 204 857 192 896 192c106 0 192 86 192 192 0 24-5 48-13 69C1192 477 1280 580 1280 704zm-493-128H656V352c0-18-14-32-32-32h-96c-18 0-32 14-32 32v224h-131c-29 0-43 34-23 55l211 211c12 12 33 12 45 0l211-211c20-20 6-55-23-55z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Code">M853 102H171C133 102 102 133 102 171v683C102 891 133 922 171 922h683C891 922 922 891 922 853V171C922 133 891 102 853 102zM390 600l-48 48L205 512l137-137 48 48L301 512l88 88zM465 819l-66-18L559 205l66 18L465 819zm218-171L634 600 723 512l-88-88 48-48L819 512 683 649z</StreamGeometry>
|
||||
|
@ -50,6 +54,7 @@
|
|||
<StreamGeometry x:Key="Icons.GitFlow.Feature">M939 94v710L512 998 85 805V94h-64A21 21 0 010 73v-0C0 61 10 51 21 51h981c12 0 21 10 21 21v0c0 12-10 21-21 21h-64zm-536 588L512 624l109 58c6 3 13 4 20 3a32 32 0 0026-37l-21-122 88-87c5-5 8-11 9-18a32 32 0 00-27-37l-122-18-54-111a32 32 0 00-57 0l-54 111-122 18c-7 1-13 4-18 9a33 33 0 001 46l88 87-21 122c-1 7-0 14 3 20a32 32 0 0043 14z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.GitFlow.Hotfix">M236 542a32 32 0 109 63l86-12a180 180 0 0022 78l-71 47a32 32 0 1035 53l75-50a176 176 0 00166 40L326 529zM512 16C238 16 16 238 16 512s222 496 496 496 496-222 496-496S786 16 512 16zm0 896c-221 0-400-179-400-400a398 398 0 0186-247l561 561A398 398 0 01512 912zm314-154L690 622a179 179 0 004-29l85 12a32 32 0 109-63l-94-13v-49l94-13a32 32 0 10-9-63l-87 12a180 180 0 00-20-62l71-47A32 32 0 10708 252l-75 50a181 181 0 00-252 10l-115-115A398 398 0 01512 112c221 0 400 179 400 400a398 398 0 01-86 247z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.GitFlow.Release">M884 159l-18-18a43 43 0 00-38-12l-235 43a166 166 0 00-101 60L400 349a128 128 0 00-148 47l-120 171a21 21 0 005 29l17 12a128 128 0 00178-32l27-38 124 124-38 27a128 128 0 00-32 178l12 17a21 21 0 0029 5l171-120a128 128 0 0047-148l117-92A166 166 0 00853 431l43-235a43 43 0 00-12-38zm-177 249a64 64 0 110-90 64 64 0 010 90zm-373 312a21 21 0 010 30l-139 139a21 21 0 01-30 0l-30-30a21 21 0 010-30l139-139a21 21 0 0130 0z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Github">M525 0C235 0 0 235 0 525c0 232 150 429 359 498 26 5 36-11 36-25 0-12-1-54-1-97-146 31-176-63-176-63-23-61-58-76-58-76-48-32 3-32 3-32 53 3 81 54 81 54 47 80 123 57 153 43 4-34 18-57 33-70-116-12-239-57-239-259 0-57 21-104 54-141-5-13-23-67 5-139 0 0 44-14 144 54 42-11 87-17 131-17s90 6 131 17C756 203 801 217 801 217c29 72 10 126 5 139 34 37 54 83 54 141 0 202-123 246-240 259 19 17 36 48 36 97 0 70-1 127-1 144 0 14 10 30 36 25 209-70 359-266 359-498C1050 235 814 0 525 0z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.GitIgnore">M590 74 859 342V876c0 38-31 68-68 68H233c-38 0-68-31-68-68V142c0-38 31-68 68-68h357zm-12 28H233a40 40 0 00-40 38L193 142v734a40 40 0 0038 40L233 916h558a40 40 0 0040-38L831 876V354L578 102zM855 371h-215c-46 0-83-36-84-82l0-2V74h28v213c0 30 24 54 54 55l2 0h215v28zM57 489m28 0 853 0q28 0 28 28l0 284q0 28-28 28l-853 0q-28 0-28-28l0-284q0-28 28-28ZM157 717c15 0 29-6 37-13v-51h-41v22h17v18c-2 2-6 3-10 3-21 0-30-13-30-34 0-21 12-34 28-34 9 0 15 4 20 9l14-17C184 610 172 603 156 603c-29 0-54 21-54 57 0 37 24 56 54 56zM245 711v-108h-34v108h34zm69 0v-86H341V603H262v22h28V711h24zM393 711v-108h-34v108h34zm66 6c15 0 29-6 37-13v-51h-41v22h17v18c-2 2-6 3-10 3-21 0-30-13-30-34 0-21 12-34 28-34 9 0 15 4 20 9l14-17C485 610 474 603 458 603c-29 0-54 21-54 57 0 37 24 56 54 56zm88-6v-36c0-13-2-28-3-40h1l10 24 25 52H603v-108h-23v36c0 13 2 28 3 40h-1l-10-24L548 603H523v108h23zM677 717c30 0 51-22 51-57 0-36-21-56-51-56-30 0-51 20-51 56 0 36 21 57 51 57zm3-23c-16 0-26-12-26-32 0-19 10-31 26-31 16 0 26 11 26 31S696 694 680 694zm93 17v-38h13l21 38H836l-25-43c12-5 19-15 19-31 0-26-20-34-44-34H745v108h27zm16-51H774v-34h15c16 0 25 4 25 16s-9 18-25 18zM922 711v-22h-43v-23h35v-22h-35V625h41V603H853v108h68z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Grid">M30 271l241 0 0-241-241 0 0 241zM392 271l241 0 0-241-241 0 0 241zM753 30l0 241 241 0 0-241-241 0zM30 632l241 0 0-241-241 0 0 241zM392 632l241 0 0-241-241 0 0 241zM753 632l241 0 0-241-241 0 0 241zM30 994l241 0 0-241-241 0 0 241zM392 994l241 0 0-241-241 0 0 241zM753 994l241 0 0-241-241 0 0 241z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Head">M0 512M1024 512M512 0M512 1024M955 323q0 23-16 39l-414 414-78 78q-16 16-39 16t-39-16l-78-78-207-207q-16-16-16-39t16-39l78-78q16-16 39-16t39 16l168 169 375-375q16-16 39-16t39 16l78 78q16 16 16 39z</StreamGeometry>
|
||||
|
@ -86,6 +91,7 @@
|
|||
<StreamGeometry x:Key="Icons.Plus">m186 532 287 0 0 287c0 11 9 20 20 20s20-9 20-20l0-287 287 0c11 0 20-9 20-20s-9-20-20-20l-287 0 0-287c0-11-9-20-20-20s-20 9-20 20l0 287-287 0c-11 0-20 9-20 20s9 20 20 20z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Pull">M432 0h160c27 0 48 21 48 48v336h175c36 0 53 43 28 68L539 757c-15 15-40 15-55 0L180 452c-25-25-7-68 28-68H384V48c0-27 21-48 48-48zm592 752v224c0 27-21 48-48 48H48c-27 0-48-21-48-48V752c0-27 21-48 48-48h293l98 98c40 40 105 40 145 0l98-98H976c27 0 48 21 48 48zm-248 176c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40zm128 0c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Push">M592 768h-160c-27 0-48-21-48-48V384h-175c-36 0-53-43-28-68L485 11c15-15 40-15 55 0l304 304c25 25 7 68-28 68H640v336c0 27-21 48-48 48zm432-16v224c0 27-21 48-48 48H48c-27 0-48-21-48-48V752c0-27 21-48 48-48h272v16c0 62 50 112 112 112h160c62 0 112-50 112-112v-16h272c27 0 48 21 48 48zm-248 176c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40zm128 0c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Quit">M563 555c0 28-23 51-51 51-28 0-51-23-51-51L461 113c0-28 23-51 51-51s51 23 51 51L563 555 563 555zM85 535c0-153 81-287 201-362 24-15 55-8 70 16C371 214 363 245 340 260 248 318 187 419 187 535c0 180 146 325 325 325 180-0 325-146 325-325 0-119-64-223-160-280-24-14-32-46-18-70 14-24 46-32 70-18 125 74 210 211 210 367 0 236-191 427-427 427C276 963 85 772 85 535</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Rebase">M277 85a149 149 0 00-43 292v230a32 32 0 0064 0V555h267A160 160 0 00725 395v-12a149 149 0 10-64-5v17a96 96 0 01-96 96H299V383A149 149 0 00277 85zM228 720a32 32 0 00-37-52 150 150 0 00-53 68 32 32 0 1060 23 85 85 0 0130-39zm136-52a32 32 0 00-37 52 86 86 0 0130 39 32 32 0 1060-23 149 149 0 00-53-68zM204 833a32 32 0 10-55 32 149 149 0 0063 58 32 32 0 0028-57 85 85 0 01-36-33zm202 32a32 32 0 00-55-32 85 85 0 01-36 33 32 32 0 0028 57 149 149 0 0063-58z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Reference">M467 556c0-0 0-1 0-1C467 555 467 556 467 556zM467 556c0-0 0-0 0-0C467 556 467 556 467 556zM467 556c-0 0-0 0-0 0C467 557 467 557 467 556zM468 549C468 532 468 541 468 549L468 549zM468 549c0 1-0 1-0 2C468 551 468 550 468 549zM468 552c-0 1-0 2-0 3C467 554 468 553 468 552zM736 549C736 532 736 541 736 549L736 549zM289 378l0 179 89 0c-1 80-89 89-89 89l45 45c0 0 129-15 134-134L467 378 289 378zM959 244l0 536c0 99-80 179-179 179L244 959c-99 0-179-80-179-179L65 244c0-99 80-179 179-179l536 0C879 65 959 145 959 244zM869 289c0-74-60-134-134-134L289 155c-74 0-134 60-134 134l0 447c0 74 60 134 134 134l447 0c74 0 134-60 134-134L869 289zM557 557l89 0c-1 80-89 89-89 89l45 45c0 0 129-15 134-134L735 378 557 378 557 557z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Relation">m224 154a166 166 0 00-166 166v192a166 166 0 00166 166h64v-76h-64a90 90 0 01-90-90v-192a90 90 0 0190-90h320a90 90 0 0190 90v192a90 90 0 01-90 90h-128v77h128a166 166 0 00166-167v-192a166 166 0 00-166-166h-320zm166 390a90 90 0 0190-90h128v-76h-128a166 166 0 00-166 166v192a166 166 0 00166 166h320a166 166 0 00166-166v-192a166 166 0 00-166-166h-64v77h64a90 90 0 0190 90v192a90 90 0 01-90 90h-320a90 90 0 01-90-90v-192z</StreamGeometry>
|
||||
|
@ -115,6 +121,7 @@
|
|||
<StreamGeometry x:Key="Icons.Tags">M996 452 572 28A96 96 0 00504 0H96C43 0 0 43 0 96v408a96 96 0 0028 68l424 424c37 37 98 37 136 0l408-408c37-37 37-98 0-136zM224 320c-53 0-96-43-96-96s43-96 96-96 96 43 96 96-43 96-96 96zm1028 268L844 996c-37 37-98 37-136 0l-1-1L1055 647c34-34 53-79 53-127s-19-93-53-127L663 0h97a96 96 0 0168 28l424 424c37 37 37 98 0 136z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Target">M765 118 629 239l-16 137-186 160 54 59 183-168 144 4 136-129 47-43-175-12L827 67zM489 404c-66 0-124 55-124 125s54 121 124 121c66 0 120-55 120-121H489l23-121c-8-4-16-4-23-4zM695 525c0 114-93 207-206 207s-206-94-206-207 93-207 206-207c16 0 27 0 43 4l43-207c-27-4-54-8-85-8-229 0-416 188-416 419s187 419 416 419c225 0 408-180 416-403v-12l-210-4z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Terminal">M144 112h736c18 0 32 14 32 32v736c0 18-14 32-32 32H144c-18 0-32-14-32-32V144c0-18 14-32 32-32zm112 211v72a9 9 0 003 7L386 509 259 615a9 9 0 00-3 7v72a9 9 0 0015 7L493 516a9 9 0 000-14l-222-186a9 9 0 00-15 7zM522 624a10 10 0 00-10 10v60a10 10 0 0010 10h237a10 10 0 0010-10v-60a10 10 0 00-10-10H522z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Top">M170 831 513 489 855 831 960 726 512 278 64 726 170 831zM512 278h448v-128h-896v128h448z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Track">M897 673v13c0 51-42 93-93 93h-10c-1 0-2 0-2 0H220c-23 0-42 19-42 42v13c0 23 19 42 42 42h552c14 0 26 12 26 26 0 14-12 26-26 26H220c-51 0-93-42-93-93v-13c0-51 42-93 93-93h20c1-0 2-0 2-0h562c23 0 42-19 42-42v-13c0-11-5-22-13-29-8-7-17-11-28-10H660c-14 0-26-12-26-26 0-14 12-26 26-26h144c24-1 47 7 65 24 18 17 29 42 29 67zM479 98c-112 0-203 91-203 203 0 44 14 85 38 118l132 208c15 24 50 24 66 0l133-209c23-33 37-73 37-117 0-112-91-203-203-203zm0 327c-68 0-122-55-122-122s55-122 122-122 122 55 122 122-55 122-122 122z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Tree">M912 800a48 48 0 1 1 0 96h-416a48 48 0 1 1 0-96h416z m-704-704A112 112 0 0 1 256 309.184V480h80a48 48 0 0 1 0 96H256v224h81.664a48 48 0 1 1 0 96H256a96 96 0 0 1-96-96V309.248A112 112 0 0 1 208 96z m704 384a48 48 0 1 1 0 96h-416a48 48 0 0 1 0-96h416z m0-320a48 48 0 1 1 0 96h-416a48 48 0 0 1 0-96h416z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.TriangleLeft">M30 0 30 30 0 15z</StreamGeometry>
|
||||
|
|
BIN
src/Resources/Images/ExternalToolIcons/plastic_merge.png
Normal file
BIN
src/Resources/Images/ExternalToolIcons/plastic_merge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
BIN
src/Resources/Images/ShellIcons/ghostty.png
Normal file
BIN
src/Resources/Images/ShellIcons/ghostty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue