diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4117364..bcb32580 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index e973c1ab..2dfc97fd 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -7,12 +7,12 @@ on: required: true type: string jobs: - windows-portable: - name: Package portable Windows app - runs-on: ubuntu-latest + windows: + name: Package Windows + 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,10 +22,11 @@ jobs: name: sourcegit.${{ matrix.runtime }} path: build/SourceGit - name: Package + shell: bash env: VERSION: ${{ inputs.version }} RUNTIME: ${{ matrix.runtime }} - run: ./build/scripts/package.windows-portable.sh + run: ./build/scripts/package.windows.sh - name: Upload package artifact uses: actions/upload-artifact@v4 with: @@ -36,7 +37,7 @@ jobs: with: name: sourcegit.${{ matrix.runtime }} osx-app: - name: Package OSX app + name: Package macOS runs-on: macos-latest strategy: matrix: @@ -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 diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml deleted file mode 100644 index 9e465fe7..00000000 --- a/.github/workflows/publish-packages.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Publish to Buildkite -on: - workflow_call: - secrets: - BUILDKITE_TOKEN: - required: true -jobs: - publish: - name: Publish to Buildkite - runs-on: ubuntu-latest - strategy: - matrix: - runtime: [linux-x64, linux-arm64] - steps: - - name: Download package artifacts - uses: actions/download-artifact@v4 - with: - name: package.${{ matrix.runtime }} - path: packages - - - name: Publish DEB package - env: - BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} - run: | - FILE=$(echo packages/*.deb) - curl -X POST https://api.buildkite.com/v2/packages/organizations/sourcegit/registries/sourcegit-deb/packages \ - -H "Authorization: Bearer $BUILDKITE_TOKEN" \ - -F "file=@$FILE" \ - --fail - - - name: Publish RPM package - env: - BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} - run: | - FILE=$(echo packages/*.rpm) - curl -X POST https://api.buildkite.com/v2/packages/organizations/sourcegit/registries/sourcegit-rpm/packages \ - -H "Authorization: Bearer $BUILDKITE_TOKEN" \ - -F "file=@$FILE" \ - --fail diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 223fe75f..e61e608b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,12 +24,6 @@ jobs: uses: ./.github/workflows/package.yml with: version: ${{ needs.version.outputs.version }} - publish-packages: - needs: [package, version] - name: Publish Packages - uses: ./.github/workflows/publish-packages.yml - secrets: - BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} release: needs: [package, version] name: Release @@ -44,7 +38,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ github.ref_name }} VERSION: ${{ needs.version.outputs.version }} - run: gh release create "$TAG" -t "Release $VERSION" --notes-from-tag + run: gh release create "$TAG" -t "$VERSION" --notes-from-tag - name: Download artifacts uses: actions/download-artifact@v4 with: diff --git a/LICENSE b/LICENSE index dceab2d8..442ce085 100644 --- a/LICENSE +++ b/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. \ No newline at end of file +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 6916edbc..d63886ed 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ * 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,7 +48,7 @@ ## Translation Status -[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-97.50%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.78%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-95.00%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-95.56%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-96.81%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-97.92%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) +You can find the current translation status in [TRANSLATION.md](TRANSLATION.md) ## How to Use @@ -59,12 +60,13 @@ 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` | > [!TIP] -> You can open the app data dir from the main menu. +> * You can open this data storage directory from the main menu `Open Data Storage Directory`. +> * You can create a `data` folder next to the `SourceGit` executable to force this app to store data (user settings, downloaded avatars and crash logs) into it (Portable-Mode). Only works on Windows. For **Windows** users: @@ -75,12 +77,12 @@ 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 ``` -* Portable versions can be found in [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) +* Pre-built binaries can be found in [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) For **macOS** users: @@ -98,49 +100,45 @@ For **macOS** users: For **Linux** users: -* For Debian/Ubuntu based distributions, you can add the `sourcegit` repository by following: - You may need to install curl and/or gpg first, if you're on a very minimal host: +* Thanks [@aikawayataro](https://github.com/aikawayataro) for providing `rpm` and `deb` repositories, hosted on [Codeberg](https://codeberg.org/yataro/-/packages). + + `deb` how to: ```shell - apt update && apt install curl gpg -y + 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, 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 ``` - Install the registry signing key: + + `rpm` how to: ```shell - curl -fsSL "https://packages.buildkite.com/sourcegit/sourcegit-deb/gpgkey" | gpg --dearmor -o /etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg + curl https://codeberg.org/api/packages/yataro/rpm.repo | sed -e 's/gpgcheck=1/gpgcheck=0/' > sourcegit.repo + + # Fedora 41 and newer + sudo dnf config-manager addrepo --from-repofile=./sourcegit.repo + # Fedora 40 and earlier + sudo dnf config-manager --add-repo ./sourcegit.repo + + sudo dnf install sourcegit ``` - Configure the source: - ```shell - echo -e "deb [signed-by=/etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg] https://packages.buildkite.com/sourcegit/sourcegit-deb/any/ any main\ndeb-src [signed-by=/etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg] https://packages.buildkite.com/sourcegit/sourcegit-deb/any/ any main" > /etc/apt/sources.list.d/buildkite-sourcegit-sourcegit-deb.list - ``` - Update your local repository and install the package: - ```shell - apt update && apt install sourcegit - ``` -* For RHEL/Fedora based distributions, you can add the `sourcegit` repository by following: - Configure the source: - ```shell - sudo sh -c 'echo -e "[sourcegit-rpm]\nname=sourcegit-rpm\nbaseurl=https://packages.buildkite.com/sourcegit/sourcegit-rpm/rpm_any/rpm_any/\$basearch\nenabled=1\nrepo_gpgcheck=1\ngpgcheck=0\ngpgkey=https://packages.buildkite.com/sourcegit/sourcegit-rpm/gpgkey\npriority=1"' > /etc/yum.repos.d/sourcegit-rpm.repo - ``` - Install the package with this command: - ```shell - sudo dnf install -y sourcegit - ``` -* `Appimage` files can be found on [AppimageHub](https://appimage.github.io/SourceGit/) -* `xdg-open` must be installed to support open native file manager. -* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your linux. + + If your distribution isn't using `dnf`, please refer to the documentation of your distribution on how to add an `rpm` repository. +* `AppImage` files can be found on [AppImage hub](https://appimage.github.io/SourceGit/), `xdg-open` (`xdg-utils`) must be installed to support open native file manager. +* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your Linux. * Maybe you need to set environment variable `AVALONIA_SCREEN_SCALE_FACTORS`. See https://github.com/AvaloniaUI/Avalonia/wiki/Configuring-X11-per-monitor-DPI. -* If you can NOT type accented characters, such as `ê`, `ó`, try to set the environment variable `AVALONIA_IM_MODULE` to `none`. +* If you can NOT type accented characters, such as `ê`, `ó`, try to set the environment variable `AVALONIA_IM_MODULE` to `none`. ## 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 @@ -159,7 +157,7 @@ This app supports open repository in external tools listed in the table below. > [!NOTE] > This app will try to find those tools based on some pre-defined or expected locations automatically. If you are using one portable version of these tools, it will not be detected by this app. -> To solve this problem you can add a file named `external_editors.json` in app data dir and provide the path directly. For example: +> To solve this problem you can add a file named `external_editors.json` in app data storage directory and provide the path directly. For example: ```json { "tools": { @@ -189,6 +187,19 @@ This app supports open repository in external tools listed in the table below. Everyone is welcome to submit a PR. Please make sure your PR is based on the latest `develop` branch and the target branch of PR is `develop`. +In short, here are the commands to get started once [.NET tools are installed](https://dotnet.microsoft.com/en-us/download): + +```sh +dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org +dotnet restore +dotnet build +dotnet run --project src/SourceGit.csproj +``` + Thanks to all the people who contribute. [![Contributors](https://contrib.rocks/image?repo=sourcegit-scm/sourcegit&columns=20)](https://github.com/sourcegit-scm/sourcegit/graphs/contributors) + +## Third-Party Components + +For detailed license information, see [THIRD-PARTY-LICENSES.md](THIRD-PARTY-LICENSES.md). diff --git a/SourceGit.sln b/SourceGit.sln index 3eeb8a54..624322f8 100644 --- a/SourceGit.sln +++ b/SourceGit.sln @@ -18,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\package.yml = .github\workflows\package.yml .github\workflows\release.yml = .github\workflows\release.yml .github\workflows\localization-check.yml = .github\workflows\localization-check.yml - .github\workflows\publish-packages.yml = .github\workflows\publish-packages.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}" @@ -61,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}" @@ -81,7 +82,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{C54D build\scripts\localization-check.js = build\scripts\localization-check.js build\scripts\package.linux.sh = build\scripts\package.linux.sh build\scripts\package.osx-app.sh = build\scripts\package.osx-app.sh - build\scripts\package.windows-portable.sh = build\scripts\package.windows-portable.sh + build\scripts\package.windows.sh = build\scripts\package.windows.sh EndProjectSection EndProject Global diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md new file mode 100644 index 00000000..2338263c --- /dev/null +++ b/THIRD-PARTY-LICENSES.md @@ -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 diff --git a/TRANSLATION.md b/TRANSLATION.md index 6e743f14..341fdce0 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -1,117 +1,91 @@ -### de_DE.axaml: 97.50% +# Translation Status +This document shows the translation status of each locale file in the repository. + +## Details + +### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) + +### ![de__DE](https://img.shields.io/badge/de__DE-98.65%25-yellow)
-Missing Keys +Missing keys in de_DE.axaml -- Text.BranchCM.MergeMultiBranches -- Text.CommitCM.Merge -- Text.CommitCM.MergeMultiple -- Text.CommitDetail.Files.Search -- Text.Diff.UseBlockNavigation -- 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.Repository.Skip -- Text.WorkingCopy.CommitToEdit +- Text.BranchUpstreamInvalid +- Text.Configure.CustomAction.WaitForExit +- Text.Configure.IssueTracker.AddSampleAzure +- Text.CopyFullPath +- Text.Diff.First +- Text.Diff.Last +- Text.Preferences.AI.Streaming +- Text.Preferences.Appearance.EditorTabWidth +- Text.Preferences.General.ShowTagsInGraph +- Text.StashCM.SaveAsPatch
-### es_ES.axaml: 97.78% - +### ![es__ES](https://img.shields.io/badge/es__ES-99.87%25-yellow)
-Missing Keys +Missing keys in es_ES.axaml -- Text.BranchCM.MergeMultiBranches -- Text.CommitCM.Merge -- Text.CommitCM.MergeMultiple -- Text.Diff.UseBlockNavigation -- 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.Repository.Skip +- Text.CopyFullPath
-### fr_FR.axaml: 95.00% - +### ![fr__FR](https://img.shields.io/badge/fr__FR-99.87%25-yellow)
-Missing Keys +Missing keys in fr_FR.axaml -- Text.BranchCM.MergeMultiBranches -- Text.CherryPick.AppendSourceToMessage -- Text.CherryPick.Mainline.Tips -- Text.CommitCM.CherryPickMultiple -- Text.CommitCM.Merge -- Text.CommitCM.MergeMultiple -- Text.CommitDetail.Files.Search -- 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.Appearance.FontSize -- Text.Preference.Appearance.FontSize.Default -- Text.Preference.Appearance.FontSize.Editor -- Text.Preference.General.ShowChildren -- Text.Repository.CustomActions -- Text.Repository.FilterCommits -- Text.Repository.FilterCommits.Default -- Text.Repository.FilterCommits.Exclude -- Text.Repository.FilterCommits.Include -- Text.Repository.HistoriesOrder -- Text.Repository.HistoriesOrder.ByDate -- Text.Repository.HistoriesOrder.Topo -- Text.Repository.Skip -- Text.ScanRepositories -- Text.SHALinkCM.NavigateTo -- Text.WorkingCopy.CommitToEdit +- Text.CopyFullPath
-### it_IT.axaml: 95.56% - +### ![it__IT](https://img.shields.io/badge/it__IT-99.73%25-yellow)
-Missing Keys +Missing keys in it_IT.axaml +- Text.CopyFullPath +- Text.Preferences.General.ShowTagsInGraph + +
+ +### ![pt__BR](https://img.shields.io/badge/pt__BR-90.98%25-yellow) + +
+Missing keys in pt_BR.axaml + +- Text.AIAssistant.Regen +- Text.AIAssistant.Use +- Text.ApplyStash +- Text.ApplyStash.DropAfterApply +- Text.ApplyStash.RestoreIndex +- Text.ApplyStash.Stash +- Text.BranchCM.CustomAction - Text.BranchCM.MergeMultiBranches +- Text.BranchUpstreamInvalid +- Text.Clone.RecurseSubmodules - Text.CommitCM.Merge - Text.CommitCM.MergeMultiple - Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children -- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest -- Text.Configure.OpenAI.Preferred -- Text.Configure.OpenAI.Preferred.Tip +- Text.Configure.CustomAction.Scope.Branch +- Text.Configure.CustomAction.WaitForExit +- Text.Configure.IssueTracker.AddSampleGiteeIssue +- Text.Configure.IssueTracker.AddSampleGiteePullRequest +- 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 +- Text.Hotkeys.Global.Clone - Text.InProgress.CherryPick.Head - Text.InProgress.Merge.Operating - Text.InProgress.Rebase.StoppedAt @@ -121,93 +95,40 @@ - Text.MergeMultiple.CommitChanges - Text.MergeMultiple.Strategy - Text.MergeMultiple.Targets -- Text.Preference.General.ShowChildren +- 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.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.Notifications.Clear +- Text.Repository.OnlyHighlightCurrentBranchInHistories - Text.Repository.Skip -- Text.SHALinkCM.CopySHA +- 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.NavigateTo +- Text.Stash.AutoRestore +- Text.Stash.AutoRestore.Tip +- Text.StashCM.SaveAsPatch - Text.WorkingCopy.CommitToEdit +- Text.WorkingCopy.SignOff
-### pt_BR.axaml: 96.81% +### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) +### ![zh__CN](https://img.shields.io/badge/zh__CN-%E2%88%9A-brightgreen) -
-Missing Keys - -- Text.BranchCM.MergeMultiBranches -- Text.CommitCM.Merge -- Text.CommitCM.MergeMultiple -- Text.CommitDetail.Files.Search -- Text.CommitDetail.Info.Children -- 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.ShowChildren -- Text.Repository.FilterCommits -- Text.Repository.Skip -- Text.SHALinkCM.NavigateTo -- Text.WorkingCopy.CommitToEdit - -
- -### ru_RU.axaml: 97.92% - - -
-Missing Keys - -- Text.BranchCM.MergeMultiBranches -- Text.CommitCM.Merge -- Text.CommitCM.MergeMultiple -- 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.Repository.Skip - -
- -### zh_CN.axaml: 100.00% - - -
-Missing Keys - - - -
- -### zh_TW.axaml: 100.00% - - -
-Missing Keys - - - -
+### ![zh__TW](https://img.shields.io/badge/zh__TW-%E2%88%9A-brightgreen) \ No newline at end of file diff --git a/VERSION b/VERSION index d72e21bd..75c26f38 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.42 \ No newline at end of file +2025.11 \ No newline at end of file diff --git a/build/resources/deb/DEBIAN/control b/build/resources/deb/DEBIAN/control index 7cfed330..71786b43 100755 --- a/build/resources/deb/DEBIAN/control +++ b/build/resources/deb/DEBIAN/control @@ -1,7 +1,8 @@ Package: sourcegit -Version: 8.23 +Version: 2025.10 Priority: optional -Depends: libx11-6, libice6, libsm6 +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 diff --git a/build/resources/deb/DEBIAN/preinst b/build/resources/deb/DEBIAN/preinst new file mode 100755 index 00000000..a93f8090 --- /dev/null +++ b/build/resources/deb/DEBIAN/preinst @@ -0,0 +1,32 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# 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 diff --git a/build/resources/deb/DEBIAN/prerm b/build/resources/deb/DEBIAN/prerm new file mode 100755 index 00000000..c2c9e4f0 --- /dev/null +++ b/build/resources/deb/DEBIAN/prerm @@ -0,0 +1,35 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# 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 diff --git a/build/resources/rpm/SPECS/build.spec b/build/resources/rpm/SPECS/build.spec index bc10ca48..2a684837 100644 --- a/build/resources/rpm/SPECS/build.spec +++ b/build/resources/rpm/SPECS/build.spec @@ -8,6 +8,7 @@ Source: https://github.com/sourcegit-scm/sourcegit/archive/refs/tags/v%_version. Requires: libX11.so.6()(%{__isa_bits}bit) Requires: libSM.so.6()(%{__isa_bits}bit) Requires: libicu +Requires: xdg-utils %define _build_id_links none diff --git a/build/scripts/localization-check.js b/build/scripts/localization-check.js index 45db82be..1e8f1f0d 100644 --- a/build/scripts/localization-check.js +++ b/build/scripts/localization-check.js @@ -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(`[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](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(`### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)`); 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(`
\nMissing Keys\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n
`); + 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(`[![${locale}](https://img.shields.io/badge/${locale}-${translationRate.toFixed(2)}%25-${badgeColor})](TRANSLATION.md)`); + lines.push(`### ![${locale}](https://img.shields.io/badge/${locale}-${progress.toFixed(2)}%25-${badgeColor})`); + lines.push(`
\nMissing keys in ${file}\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n
`) + } else { + lines.push(`### ![${locale}](https://img.shields.io/badge/${locale}-%E2%88%9A-brightgreen)`); + } } - 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)); diff --git a/build/scripts/package.linux.sh b/build/scripts/package.linux.sh index 5abb058b..1b4adbdc 100755 --- a/build/scripts/package.linux.sh +++ b/build/scripts/package.linux.sh @@ -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" ./ diff --git a/build/scripts/package.windows-portable.sh b/build/scripts/package.windows-portable.sh deleted file mode 100755 index 6bd3879b..00000000 --- a/build/scripts/package.windows-portable.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o -set -u -set pipefail - -cd build - -rm -rf SourceGit/*.pdb - -zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit diff --git a/build/scripts/package.windows.sh b/build/scripts/package.windows.sh new file mode 100755 index 00000000..c22a9d35 --- /dev/null +++ b/build/scripts/package.windows.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -e +set -o +set -u +set pipefail + +cd build + +rm -rf SourceGit/*.pdb + +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 diff --git a/src/App.Commands.cs b/src/App.Commands.cs index 8a485029..85a75829 100644 --- a/src/App.Commands.cs +++ b/src/App.Commands.cs @@ -25,12 +25,34 @@ namespace SourceGit private Action _action = null; } - public static readonly Command OpenPreferenceCommand = new Command(_ => OpenDialog(new Views.Preference())); + public static bool IsCheckForUpdateCommandVisible + { + get + { +#if DISABLE_UPDATE_DETECTION + return false; +#else + return true; +#endif + } + } + + 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())); - public static readonly Command CheckForUpdateCommand = new Command(_ => Check4Update(true)); + public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true)); public static readonly Command QuitCommand = new Command(_ => Quit(0)); - public static readonly Command CopyTextBlockCommand = new Command(p => CopyTextBlock(p as TextBlock)); + public static readonly Command CopyTextBlockCommand = new Command(p => + { + var textBlock = p as TextBlock; + if (textBlock == null) + return; + + if (textBlock.Inlines is { Count: > 0 } inlines) + CopyText(inlines.Text); + else if (!string.IsNullOrEmpty(textBlock.Text)) + CopyText(textBlock.Text); + }); } } diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs index 70567af5..9cad0792 100644 --- a/src/App.JsonCodeGen.cs +++ b/src/App.JsonCodeGen.cs @@ -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 { } } diff --git a/src/App.axaml b/src/App.axaml index 59418965..fdfa8e07 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -34,9 +34,9 @@ - + - + diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 0615724a..0448a247 100644 --- a/src/App.axaml.cs +++ b/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; @@ -22,6 +24,7 @@ namespace SourceGit { public partial class App : Application { + #region App Entry Point [STAThread] public static void Main(string[] args) { @@ -34,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,34 +77,33 @@ namespace SourceGit return builder; } - public override void Initialize() + private static void LogException(Exception ex) { - AvaloniaXamlLoader.Load(this); + if (ex == null) + return; - var pref = ViewModels.Preference.Instance; - pref.PropertyChanged += (_, _) => pref.Save(); + 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); - SetLocale(pref.Locale); - SetTheme(pref.Theme, pref.ThemeOverrides); - SetFonts(pref.DefaultFontFamily, pref.MonospaceFontFamily, pref.OnlyUseMonoFontInEditor); - } - - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - BindingPlugins.DataValidators.RemoveAt(0); - - if (TryLaunchedAsCoreEditor(desktop)) - return; - - if (TryLaunchedAsAskpass(desktop)) - return; - - TryLaunchedAsNormal(desktop); - } + 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 public static void OpenDialog(Window window) { if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) @@ -204,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)); @@ -304,21 +308,6 @@ namespace SourceGit return Current is App app ? app._launcher : null; } - public static ViewModels.Repository FindOpenedRepository(string repoPath) - { - if (Current is App app && app._launcher != null) - { - foreach (var page in app._launcher.Pages) - { - var id = page.Node.Id.Replace("\\", "/"); - if (id == repoPath && page.Data is ViewModels.Repository repo) - return repo; - } - } - - return null; - } - public static void Quit(int exitCode) { if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) @@ -331,94 +320,39 @@ namespace SourceGit Environment.Exit(exitCode); } } + #endregion - private static void CopyTextBlock(TextBlock textBlock) + #region Overrides + public override void Initialize() { - if (textBlock == null) - return; + AvaloniaXamlLoader.Load(this); - if (textBlock.Inlines is { Count: > 0 } inlines) - CopyText(inlines.Text); - else if (!string.IsNullOrEmpty(textBlock.Text)) - CopyText(textBlock.Text); + var pref = ViewModels.Preferences.Instance; + pref.PropertyChanged += (_, _) => pref.Save(); + + SetLocale(pref.Locale); + SetTheme(pref.Theme, pref.ThemeOverrides); + SetFonts(pref.DefaultFontFamily, pref.MonospaceFontFamily, pref.OnlyUseMonoFontInEditor); } - private static void LogException(Exception ex) + public override void OnFrameworkInitializationCompleted() { - 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) + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - ex = ex.InnerException; - builder.Append($"\n\nInnerException::: {ex.GetType().FullName}: {ex.Message}\n"); - builder.Append(ex.StackTrace); + BindingPlugins.DataValidators.RemoveAt(0); + + if (TryLaunchAsCoreEditor(desktop)) + return; + + if (TryLaunchAsAskpass(desktop)) + return; + + TryLaunchAsNormal(desktop); } - - 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 - private static void Check4Update(bool manually = false) - { - Task.Run(async () => - { - try - { - // Fetch lastest release information. - var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(5) }; - var data = await client.GetStringAsync("https://sourcegit-scm.github.io/data/version.json"); - - // Parse json into Models.Version. - var ver = JsonSerializer.Deserialize(data, JsonCodeGen.Default.Version); - if (ver == null) - return; - - // Check if already up-to-date. - if (!ver.IsNewVersion) - { - if (manually) - ShowSelfUpdateResult(new Models.AlreadyUpToDate()); - return; - } - - // Should not check ignored tag if this is called manually. - if (!manually) - { - var pref = ViewModels.Preference.Instance; - if (ver.TagName == pref.IgnoreUpdateTag) - return; - } - - ShowSelfUpdateResult(ver); - } - catch (Exception e) - { - if (manually) - ShowSelfUpdateResult(e); - } - }); - } - - private static void ShowSelfUpdateResult(object data) - { - Dispatcher.UIThread.Post(() => - { - OpenDialog(new Views.SelfUpdate() { DataContext = new ViewModels.SelfUpdate() { Data = data } }); - }); - } - - private static bool TryLaunchedAsRebaseTodoEditor(string[] args, out int exitCode) + private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode) { exitCode = -1; @@ -471,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; @@ -505,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)) @@ -513,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") @@ -529,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(); @@ -548,9 +488,96 @@ namespace SourceGit _launcher = new ViewModels.Launcher(startupRepo); desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; - var pref = ViewModels.Preference.Instance; +#if !DISABLE_UPDATE_DETECTION + var pref = ViewModels.Preferences.Instance; if (pref.ShouldCheck4UpdateOnStartup()) Check4Update(); +#endif + } + + private void Check4Update(bool manually = false) + { + Task.Run(async () => + { + try + { + // Fetch latest release information. + var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(5) }; + var data = await client.GetStringAsync("https://sourcegit-scm.github.io/data/version.json"); + + // Parse JSON into Models.Version. + var ver = JsonSerializer.Deserialize(data, JsonCodeGen.Default.Version); + if (ver == null) + return; + + // Check if already up-to-date. + if (!ver.IsNewVersion) + { + if (manually) + ShowSelfUpdateResult(new Models.AlreadyUpToDate()); + return; + } + + // Should not check ignored tag if this is called manually. + if (!manually) + { + var pref = ViewModels.Preferences.Instance; + if (ver.TagName == pref.IgnoreUpdateTag) + return; + } + + ShowSelfUpdateResult(ver); + } + catch (Exception e) + { + if (manually) + ShowSelfUpdateResult(new Models.SelfUpdateFailed(e)); + } + }); + } + + private void ShowSelfUpdateResult(object data) + { + Dispatcher.UIThread.Post(() => + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.SelfUpdate() { DataContext = new ViewModels.SelfUpdate() { Data = data } }; + dialog.ShowDialog(owner); + } + }); + } + + private string FixFontFamilyName(string input) + { + if (string.IsNullOrEmpty(input)) + return string.Empty; + + var parts = input.Split(','); + var trimmed = new List(); + + 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; diff --git a/src/Commands/Add.cs b/src/Commands/Add.cs index c3d1d3e6..b2aa803d 100644 --- a/src/Commands/Add.cs +++ b/src/Commands/Add.cs @@ -12,7 +12,7 @@ namespace SourceGit.Commands Args = includeUntracked ? "add ." : "add -u ."; } - public Add(string repo, List changes) + public Add(string repo, List changes) { WorkingDirectory = repo; Context = repo; @@ -22,10 +22,17 @@ namespace SourceGit.Commands foreach (var c in changes) { builder.Append(" \""); - builder.Append(c.Path); + builder.Append(c); builder.Append("\""); } Args = builder.ToString(); } + + public Add(string repo, string pathspecFromFile) + { + WorkingDirectory = repo; + Context = repo; + Args = $"add --pathspec-from-file=\"{pathspecFromFile}\""; + } } } diff --git a/src/Commands/Blame.cs b/src/Commands/Blame.cs index de221b07..291249be 100644 --- a/src/Commands/Blame.cs +++ b/src/Commands/Blame.cs @@ -65,7 +65,7 @@ namespace SourceGit.Commands var commit = match.Groups[1].Value; var author = match.Groups[2].Value; var timestamp = int.Parse(match.Groups[3].Value); - var when = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString("yyyy/MM/dd"); + var when = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString(_dateFormat); var info = new Models.BlameLineInfo() { @@ -87,6 +87,7 @@ namespace SourceGit.Commands private readonly Models.BlameData _result = new Models.BlameData(); private readonly StringBuilder _content = new StringBuilder(); + private readonly string _dateFormat = Models.DateTimeFormat.Actived.DateOnly; private string _lastSHA = string.Empty; private bool _needUnifyCommitSHA = false; private int _minSHALen = 64; diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs index 391aeeb2..2dc8a98d 100644 --- a/src/Commands/Branch.cs +++ b/src/Commands/Branch.cs @@ -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(); } } diff --git a/src/Commands/Clone.cs b/src/Commands/Clone.cs index 683b8846..f2faed14 100644 --- a/src/Commands/Clone.cs +++ b/src/Commands/Clone.cs @@ -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} "; diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 96a5b9c9..0fef1235 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -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(); 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,15 +104,23 @@ 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) { - var errMsg = string.Join("\n", errs); - if (!string.IsNullOrWhiteSpace(errMsg)) + var errMsg = string.Join("\n", errs).Trim(); + if (!string.IsNullOrEmpty(errMsg)) Dispatcher.UIThread.Post(() => App.RaiseException(Context, errMsg)); } @@ -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) diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs index c4674c8e..e311a88e 100644 --- a/src/Commands/CompareRevisions.cs +++ b/src/Commands/CompareRevisions.cs @@ -6,7 +6,7 @@ namespace SourceGit.Commands { public partial class CompareRevisions : Command { - [GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")] + [GeneratedRegex(@"^([MADRC])\s+(.+)$")] private static partial Regex REG_FORMAT(); public CompareRevisions(string repo, string start, string end) @@ -18,6 +18,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 Result() { Exec(); diff --git a/src/Commands/Config.cs b/src/Commands/Config.cs index 2fb83325..49e8fcb7 100644 --- a/src/Commands/Config.cs +++ b/src/Commands/Config.cs @@ -29,7 +29,7 @@ namespace SourceGit.Commands var rs = new Dictionary(); 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); diff --git a/src/Commands/CountLocalChangesWithoutUntracked.cs b/src/Commands/CountLocalChangesWithoutUntracked.cs index afb62840..0ba508f8 100644 --- a/src/Commands/CountLocalChangesWithoutUntracked.cs +++ b/src/Commands/CountLocalChangesWithoutUntracked.cs @@ -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; } diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index da971e58..8ae6350f 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -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; diff --git a/src/Commands/ExecuteCustomAction.cs b/src/Commands/ExecuteCustomAction.cs index 4981b2f9..000c8fd1 100644 --- a/src/Commands/ExecuteCustomAction.cs +++ b/src/Commands/ExecuteCustomAction.cs @@ -8,7 +8,26 @@ namespace SourceGit.Commands { public static class ExecuteCustomAction { - public static void Run(string repo, string file, string args, Action 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 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(); @@ -53,26 +64,21 @@ namespace SourceGit.Commands proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); proc.WaitForExit(); + + var exitCode = proc.ExitCode; + if (exitCode != 0) + { + 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)); } - var exitCode = proc.ExitCode; proc.Close(); - - if (exitCode != 0) - { - var errMsg = builder.ToString(); - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, errMsg); - }); - } } } } diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs index 1c3e78cb..06ae8cb6 100644 --- a/src/Commands/Fetch.cs +++ b/src/Commands/Fetch.cs @@ -4,7 +4,7 @@ namespace SourceGit.Commands { public class Fetch : Command { - public Fetch(string repo, string remote, bool noTags, bool prune, bool force, Action outputHandler) + public Fetch(string repo, string remote, bool noTags, bool force, Action outputHandler) { _outputHandler = outputHandler; WorkingDirectory = repo; @@ -21,9 +21,6 @@ namespace SourceGit.Commands if (force) Args += "--force "; - if (prune) - Args += "--prune "; - Args += remote; } diff --git a/src/Commands/GenerateCommitMessage.cs b/src/Commands/GenerateCommitMessage.cs index e4f25f38..df61fdd2 100644 --- a/src/Commands/GenerateCommitMessage.cs +++ b/src/Commands/GenerateCommitMessage.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Text; using System.Threading; +using Avalonia.Threading; + namespace SourceGit.Commands { /// @@ -20,82 +22,78 @@ namespace SourceGit.Commands } } - public GenerateCommitMessage(Models.OpenAIService service, string repo, List changes, CancellationToken cancelToken, Action onProgress) + public GenerateCommitMessage(Models.OpenAIService service, string repo, List changes, CancellationToken cancelToken, Action 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 _changes; private CancellationToken _cancelToken; - private Action _onProgress; + private Action _onResponse; } } diff --git a/src/Commands/IsBareRepository.cs b/src/Commands/IsBareRepository.cs new file mode 100644 index 00000000..f92d0888 --- /dev/null +++ b/src/Commands/IsBareRepository.cs @@ -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"; + } + } +} diff --git a/src/Commands/IsCommitSHA.cs b/src/Commands/IsCommitSHA.cs new file mode 100644 index 00000000..1b0c50e3 --- /dev/null +++ b/src/Commands/IsCommitSHA.cs @@ -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"); + } + } +} diff --git a/src/Commands/LFS.cs b/src/Commands/LFS.cs index c9ab7b41..f7e56486 100644 --- a/src/Commands/LFS.cs +++ b/src/Commands/LFS.cs @@ -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); diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs index 732530f5..35a6289a 100644 --- a/src/Commands/Pull.cs +++ b/src/Commands/Pull.cs @@ -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 outputHandler) + public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action 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}"; } diff --git a/src/Commands/Push.cs b/src/Commands/Push.cs index 69b859ab..dc81f606 100644 --- a/src/Commands/Push.cs +++ b/src/Commands/Push.cs @@ -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) diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs index 95f97214..8dbc4055 100644 --- a/src/Commands/QueryBranches.cs +++ b/src/Commands/QueryBranches.cs @@ -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(); 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(); diff --git a/src/Commands/QueryCommitChildren.cs b/src/Commands/QueryCommitChildren.cs index bef09abb..31fb34f8 100644 --- a/src/Commands/QueryCommitChildren.cs +++ b/src/Commands/QueryCommitChildren.cs @@ -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 Result() + public List Result() { Exec(); return _lines; diff --git a/src/Commands/QueryCommitFullMessage.cs b/src/Commands/QueryCommitFullMessage.cs index c8f1867d..36b6d1c7 100644 --- a/src/Commands/QueryCommitFullMessage.cs +++ b/src/Commands/QueryCommitFullMessage.cs @@ -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() diff --git a/src/Commands/QueryCommitSignInfo.cs b/src/Commands/QueryCommitSignInfo.cs index 5c81cf57..133949af 100644 --- a/src/Commands/QueryCommitSignInfo.cs +++ b/src/Commands/QueryCommitSignInfo.cs @@ -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] }; - } } } diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 80497a90..dd3c39b4 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -6,13 +6,11 @@ namespace SourceGit.Commands { public class QueryCommits : Command { - public QueryCommits(string repo, bool useTopoOrder, string limits, bool needFindHead = true) + public QueryCommits(string repo, string limits, bool needFindHead = true) { - var order = useTopoOrder ? "--topo-order" : "--date-order"; - WorkingDirectory = repo; Context = repo; - Args = $"log {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 {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; } @@ -20,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) { @@ -33,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); @@ -46,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; } @@ -122,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; diff --git a/src/Commands/QueryCommitsWithFullMessage.cs b/src/Commands/QueryCommitsForInteractiveRebase.cs similarity index 84% rename from src/Commands/QueryCommitsWithFullMessage.cs rename to src/Commands/QueryCommitsForInteractiveRebase.cs index c15cdbe1..615060a5 100644 --- a/src/Commands/QueryCommitsWithFullMessage.cs +++ b/src/Commands/QueryCommitsForInteractiveRebase.cs @@ -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 Result() + public List 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 _commits = new List(); - private Models.CommitWithMessage _current = null; + private List _commits = []; + private Models.InteractiveCommit _current = null; private string _boundary = ""; } } diff --git a/src/Commands/QueryGitCommonDir.cs b/src/Commands/QueryGitCommonDir.cs new file mode 100644 index 00000000..1076243e --- /dev/null +++ b/src/Commands/QueryGitCommonDir.cs @@ -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)); + } + } +} diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs index ea422215..9458e5f5 100644 --- a/src/Commands/QueryLocalChanges.cs +++ b/src/Commands/QueryLocalChanges.cs @@ -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 Result() diff --git a/src/Commands/QueryRefsContainsCommit.cs b/src/Commands/QueryRefsContainsCommit.cs index e3b73ccc..cabe1b50 100644 --- a/src/Commands/QueryRefsContainsCommit.cs +++ b/src/Commands/QueryRefsContainsCommit.cs @@ -20,9 +20,12 @@ 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)) + continue; + if (line.StartsWith("refs/heads/", StringComparison.Ordinal)) rs.Add(new() { Name = line.Substring("refs/heads/".Length), Type = Models.DecoratorType.LocalBranchHead }); else if (line.StartsWith("refs/remotes/", StringComparison.Ordinal)) diff --git a/src/Commands/QueryRevisionByRefName.cs b/src/Commands/QueryRevisionByRefName.cs new file mode 100644 index 00000000..7fb4ecfa --- /dev/null +++ b/src/Commands/QueryRevisionByRefName.cs @@ -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; + } + } +} diff --git a/src/Commands/QueryRevisionFileNames.cs b/src/Commands/QueryRevisionFileNames.cs index d2d69614..c6fd7373 100644 --- a/src/Commands/QueryRevisionFileNames.cs +++ b/src/Commands/QueryRevisionFileNames.cs @@ -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 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(); + foreach (var line in lines) + outs.Add(line); + return outs; } } } diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs index 1e1c9ea4..35289ec5 100644 --- a/src/Commands/QuerySingleCommit.cs +++ b/src/Commands/QuerySingleCommit.cs @@ -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() diff --git a/src/Commands/QueryStagedChangesWithAmend.cs b/src/Commands/QueryStagedChangesWithAmend.cs index f5c9659f..cfea5e35 100644 --- a/src/Commands/QueryStagedChangesWithAmend.cs +++ b/src/Commands/QueryStagedChangesWithAmend.cs @@ -24,7 +24,7 @@ namespace SourceGit.Commands if (rs.IsSuccess) { var changes = new List(); - 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); diff --git a/src/Commands/QueryStashChanges.cs b/src/Commands/QueryStashChanges.cs index 3b8d2db6..7fc27ea3 100644 --- a/src/Commands/QueryStashChanges.cs +++ b/src/Commands/QueryStashChanges.cs @@ -1,60 +1,68 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text.RegularExpressions; namespace SourceGit.Commands { + /// + /// Query stash changes. Requires git >= 2.32.0 + /// public partial class QueryStashChanges : Command { - [GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")] + [GeneratedRegex(@"^([MADRC])\s+(.+)$")] private static partial Regex REG_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 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(); + 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) + continue; - private readonly List _changes = new List(); + 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 'R': + change.Set(Models.ChangeState.Renamed); + 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; + } } } diff --git a/src/Commands/QueryStashes.cs b/src/Commands/QueryStashes.cs index 6d089f8e..dd5d10cc 100644 --- a/src/Commands/QueryStashes.cs +++ b/src/Commands/QueryStashes.cs @@ -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 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 _stashes = new List(); private Models.Stash _current = null; private int _nextLineIdx = 0; diff --git a/src/Commands/QuerySubmodules.cs b/src/Commands/QuerySubmodules.cs index 5fd6e3d5..6016b0be 100644 --- a/src/Commands/QuerySubmodules.cs +++ b/src/Commands/QuerySubmodules.cs @@ -24,11 +24,9 @@ namespace SourceGit.Commands { var submodules = new List(); 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(); - 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); diff --git a/src/Commands/QueryTags.cs b/src/Commands/QueryTags.cs index 3aa20dc2..73f63d8b 100644 --- a/src/Commands/QueryTags.cs +++ b/src/Commands/QueryTags.cs @@ -11,7 +11,7 @@ namespace SourceGit.Commands Context = repo; WorkingDirectory = repo; - Args = $"tag -l --sort=-creatordate --format=\"{_boundary}%(refname)%00%(objectname)%00%(*objectname)%00%(contents:subject)%0a%0a%(contents:body)\""; + Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\""; } public List Result() @@ -25,14 +25,15 @@ namespace SourceGit.Commands foreach (var record in records) { var subs = record.Split('\0', StringSplitOptions.None); - if (subs.Length != 4) + if (subs.Length != 5) continue; - var message = subs[3].Trim(); + var message = subs[4].Trim(); tags.Add(new Models.Tag() { Name = subs[0].Substring(10), SHA = string.IsNullOrEmpty(subs[2]) ? subs[1] : subs[2], + CreatorDate = ulong.Parse(subs[3]), Message = string.IsNullOrEmpty(message) ? null : message, }); } diff --git a/src/Commands/QueryTrackStatus.cs b/src/Commands/QueryTrackStatus.cs index 0bf9746f..e7e1f1c9 100644 --- a/src/Commands/QueryTrackStatus.cs +++ b/src/Commands/QueryTrackStatus.cs @@ -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] == '>') diff --git a/src/Commands/SaveChangesAsPatch.cs b/src/Commands/SaveChangesAsPatch.cs index 461bbfb5..b10037a1 100644 --- a/src/Commands/SaveChangesAsPatch.cs +++ b/src/Commands/SaveChangesAsPatch.cs @@ -37,6 +37,19 @@ namespace SourceGit.Commands return true; } + public static bool ProcessStashChanges(string repo, List 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(); diff --git a/src/Commands/Stash.cs b/src/Commands/Stash.cs index 77f1af53..7d1a269b 100644 --- a/src/Commands/Stash.cs +++ b/src/Commands/Stash.cs @@ -11,72 +11,84 @@ namespace SourceGit.Commands Context = repo; } - public bool Push(string message) - { - Args = $"stash push -m \"{message}\""; - return Exec(); - } - - public bool Push(List changes, string message, bool onlyStaged, bool keepIndex) + public bool Push(string message, bool includeUntracked = true, bool keepIndex = false) { var builder = new StringBuilder(); builder.Append("stash push "); - if (onlyStaged) - builder.Append("--staged "); + 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 changes, bool keepIndex) + { + var builder = new StringBuilder(); + builder.Append("stash push --include-untracked "); if (keepIndex) builder.Append("--keep-index "); builder.Append("-m \""); builder.Append(message); builder.Append("\" -- "); - if (onlyStaged) - { - foreach (var c in changes) - builder.Append($"\"{c.Path}\" "); - } - else - { - var needAdd = new List(); - foreach (var c in changes) - { - builder.Append($"\"{c.Path}\" "); - - if (c.WorkTree == Models.ChangeState.Added || c.WorkTree == Models.ChangeState.Untracked) - { - needAdd.Add(c); - if (needAdd.Count > 10) - { - new Add(WorkingDirectory, needAdd).Exec(); - needAdd.Clear(); - } - } - } - if (needAdd.Count > 0) - { - new Add(WorkingDirectory, needAdd).Exec(); - needAdd.Clear(); - } - } + foreach (var c in changes) + builder.Append($"\"{c.Path}\" "); Args = builder.ToString(); return Exec(); } - public bool Apply(string name) + public bool Push(string message, string pathspecFromFile, bool keepIndex) { - Args = $"stash apply -q {name}"; + var builder = new StringBuilder(); + builder.Append("stash push --include-untracked --pathspec-from-file=\""); + builder.Append(pathspecFromFile); + builder.Append("\" "); + if (keepIndex) + builder.Append("--keep-index "); + builder.Append("-m \""); + builder.Append(message); + builder.Append("\""); + + Args = builder.ToString(); + return Exec(); + } + + public bool PushOnlyStaged(string message, bool keepIndex) + { + var builder = new StringBuilder(); + builder.Append("stash push --staged "); + if (keepIndex) + builder.Append("--keep-index "); + builder.Append("-m \""); + builder.Append(message); + builder.Append("\""); + Args = builder.ToString(); + return Exec(); + } + + public bool Apply(string name, bool restoreIndex) + { + 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(); } diff --git a/src/Commands/Statistics.cs b/src/Commands/Statistics.cs index 511c43e8..1439cedd 100644 --- a/src/Commands/Statistics.cs +++ b/src/Commands/Statistics.cs @@ -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"; } public Models.Statistics Result() diff --git a/src/Commands/Tag.cs b/src/Commands/Tag.cs index fa11e366..23dbb11c 100644 --- a/src/Commands/Tag.cs +++ b/src/Commands/Tag.cs @@ -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; diff --git a/src/Commands/Version.cs b/src/Commands/Version.cs deleted file mode 100644 index ed7c6892..00000000 --- a/src/Commands/Version.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace SourceGit.Commands -{ - public class Version : Command - { - public Version() - { - Args = "--version"; - RaiseError = false; - } - - public string Query() - { - var rs = ReadToEnd(); - if (!rs.IsSuccess || string.IsNullOrWhiteSpace(rs.StdOut)) - return string.Empty; - return rs.StdOut.Trim().Substring("git version ".Length); - } - } -} diff --git a/src/Commands/Worktree.cs b/src/Commands/Worktree.cs index 7516b1e3..960d5501 100644 --- a/src/Commands/Worktree.cs +++ b/src/Commands/Worktree.cs @@ -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(); diff --git a/src/Converters/BoolConverters.cs b/src/Converters/BoolConverters.cs index 2d738700..3563fb37 100644 --- a/src/Converters/BoolConverters.cs +++ b/src/Converters/BoolConverters.cs @@ -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 ToPageTabWidth = new FuncValueConverter(x => x ? 200 : double.NaN); + + public static readonly FuncValueConverter IsBoldToFontWeight = + new FuncValueConverter(x => x ? FontWeight.Bold : FontWeight.Normal); } } diff --git a/src/Converters/IntConverters.cs b/src/Converters/IntConverters.cs index 17a88da2..f21c5d24 100644 --- a/src/Converters/IntConverters.cs +++ b/src/Converters/IntConverters.cs @@ -23,10 +23,10 @@ namespace SourceGit.Converters new FuncValueConverter(v => v != 1); public static readonly FuncValueConverter IsSubjectLengthBad = - new FuncValueConverter(v => v > ViewModels.Preference.Instance.SubjectGuideLength); + new FuncValueConverter(v => v > ViewModels.Preferences.Instance.SubjectGuideLength); public static readonly FuncValueConverter IsSubjectLengthGood = - new FuncValueConverter(v => v <= ViewModels.Preference.Instance.SubjectGuideLength); + new FuncValueConverter(v => v <= ViewModels.Preferences.Instance.SubjectGuideLength); public static readonly FuncValueConverter ToTreeMargin = new FuncValueConverter(v => new Thickness(v * 16, 0, 0, 0)); diff --git a/src/Converters/StringConverters.cs b/src/Converters/StringConverters.cs index 585e0f02..5e4608c5 100644 --- a/src/Converters/StringConverters.cs +++ b/src/Converters/StringConverters.cs @@ -1,13 +1,12 @@ using System; using System.Globalization; -using System.Text.RegularExpressions; using Avalonia.Data.Converters; using Avalonia.Styling; namespace SourceGit.Converters { - public static partial class StringConverters + public static class StringConverters { public class ToLocaleConverter : IValueConverter { @@ -68,22 +67,6 @@ namespace SourceGit.Converters public static readonly FuncValueConverter ToShortSHA = new FuncValueConverter(v => v == null ? string.Empty : (v.Length > 10 ? v.Substring(0, 10) : v)); - public static readonly FuncValueConverter UnderRecommendGitVersion = - new(v => - { - var match = REG_GIT_VERSION().Match(v ?? ""); - if (match.Success) - { - var major = int.Parse(match.Groups[1].Value); - var minor = int.Parse(match.Groups[2].Value); - var build = int.Parse(match.Groups[3].Value); - - return new Version(major, minor, build) < MINIMAL_GIT_VERSION; - } - - return true; - }); - public static readonly FuncValueConverter TrimRefsPrefix = new FuncValueConverter(v => { @@ -96,9 +79,7 @@ namespace SourceGit.Converters return v; }); - [GeneratedRegex(@"^[\s\w]*(\d+)\.(\d+)[\.\-](\d+).*$")] - private static partial Regex REG_GIT_VERSION(); - - private static readonly Version MINIMAL_GIT_VERSION = new Version(2, 23, 0); + public static readonly FuncValueConverter ContainsSpaces = + new FuncValueConverter(v => v != null && v.Contains(' ')); } } diff --git a/src/Models/ApplyWhiteSpaceMode.cs b/src/Models/ApplyWhiteSpaceMode.cs index 6fbce0b2..aad45f57 100644 --- a/src/Models/ApplyWhiteSpaceMode.cs +++ b/src/Models/ApplyWhiteSpaceMode.cs @@ -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; } } diff --git a/src/Models/AvatarManager.cs b/src/Models/AvatarManager.cs index 313553f9..a506d886 100644 --- a/src/Models/AvatarManager.cs +++ b/src/Models/AvatarManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -35,7 +35,7 @@ namespace SourceGit.Models private static AvatarManager _instance = null; - [GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")] + [GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@.+\.github\.com$")] private static partial Regex REG_GITHUB_USER_EMAIL(); private object _synclock = new object(); @@ -43,6 +43,7 @@ namespace SourceGit.Models private List _avatars = new List(); private Dictionary _resources = new Dictionary(); private HashSet _requesting = new HashSet(); + private HashSet _defaultAvatars = new HashSet(); public void Start() { @@ -50,8 +51,8 @@ namespace SourceGit.Models if (!Directory.Exists(_storePath)) Directory.CreateDirectory(_storePath); - var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/github.png", UriKind.RelativeOrAbsolute)); - _resources.Add("noreply@github.com", new Bitmap(icon)); + LoadDefaultAvatar("noreply@github.com", "github.png"); + LoadDefaultAvatar("unrealbot@epicgames.com", "unreal.png"); Task.Run(() => { @@ -140,7 +141,7 @@ namespace SourceGit.Models { if (forceRefetch) { - if (email.Equals("noreply@github.com", StringComparison.Ordinal)) + if (_defaultAvatars.Contains(email)) return null; if (_resources.ContainsKey(email)) @@ -185,11 +186,18 @@ namespace SourceGit.Models return null; } + private void LoadDefaultAvatar(string key, string img) + { + var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/{img}", UriKind.RelativeOrAbsolute)); + _resources.Add(key, new Bitmap(icon)); + _defaultAvatars.Add(key); + } + 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(); diff --git a/src/Models/Branch.cs b/src/Models/Branch.cs index 0ba320c1..2d0ae5b2 100644 --- a/src/Models/Branch.cs +++ b/src/Models/Branch.cs @@ -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}"; } diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index 534cf5bb..0bad8376 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -8,7 +8,9 @@ namespace SourceGit.Models { public enum CommitSearchMethod { - ByUser, + BySHA = 0, + ByAuthor, + ByCommitter, ByMessage, ByFile, } @@ -31,9 +33,10 @@ namespace SourceGit.Models public List Decorators { get; set; } = new List(); public bool HasDecorators => Decorators.Count > 0; - public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"); - public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"); - public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString("yyyy/MM/dd"); + 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; @@ -111,9 +114,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 Links { get; set; } = []; } } diff --git a/src/Models/CommitGraph.cs b/src/Models/CommitGraph.cs index 31f5a40e..77209751 100644 --- a/src/Models/CommitGraph.cs +++ b/src/Models/CommitGraph.cs @@ -25,10 +25,11 @@ namespace SourceGit.Models s_penCount = colors.Count; } - public class Path(int color) + public class Path(int color, bool isMerged) { public List Points { get; } = []; public int Color { get; } = color; + public bool IsMerged { get; } = isMerged; } public class Link @@ -37,6 +38,7 @@ namespace SourceGit.Models public Point Control; public Point End; public int Color; + public bool IsMerged; } public enum DotType @@ -51,6 +53,7 @@ namespace SourceGit.Models public DotType Type; public Point Center; public int Color; + public bool IsMerged; } public List Paths { get; } = []; @@ -68,7 +71,7 @@ namespace SourceGit.Models var unsolved = new List(); var ended = new List(); var offsetY = -halfHeight; - var colorIdx = 0; + var colorPicker = new ColorPicker(); foreach (var commit in commits) { @@ -108,7 +111,6 @@ namespace SourceGit.Models } isMerged = isMerged || l.IsMerged; - major.IsMerged = isMerged; } else { @@ -119,28 +121,35 @@ namespace SourceGit.Models // Remove ended curves from unsolved foreach (var l in ended) + { + colorPicker.Recycle(l.Path.Color); unsolved.Remove(l); + } ended.Clear(); - // Create new curve for branch head + // If no path found, create new curve for branch head + // Otherwise, create new curve for new merged commit if (major == null) { offsetX += unitWidth; if (commit.Parents.Count > 0) { - major = new PathHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY)); + major = new PathHelper(commit.Parents[0], isMerged, colorPicker.Next(), new Point(offsetX, offsetY)); unsolved.Add(major); temp.Paths.Add(major.Path); } - - colorIdx = (colorIdx + 1) % s_penCount; + } + else if (isMerged && !major.IsMerged && commit.Parents.Count > 0) + { + major.ReplaceMerged(); + temp.Paths.Add(major.Path); } // Calculate link position of this commit. var position = new Point(major?.LastX ?? offsetX, offsetY); var dotColor = major?.Path.Color ?? 0; - var anchor = new Dot() { Center = position, Color = dotColor }; + var anchor = new Dot() { Center = position, Color = dotColor, IsMerged = isMerged }; if (commit.IsCurrentHead) anchor.Type = DotType.Head; else if (commit.Parents.Count > 1) @@ -158,16 +167,20 @@ namespace SourceGit.Models var parent = unsolved.Find(x => x.Next.Equals(parentHash, StringComparison.Ordinal)); if (parent != null) { - // Try to change the merge state of linked graph - if (isMerged) - parent.IsMerged = true; + if (isMerged && !parent.IsMerged) + { + parent.Goto(parent.LastX, offsetY + halfHeight, halfHeight); + parent.ReplaceMerged(); + temp.Paths.Add(parent.Path); + } temp.Links.Add(new Link { Start = position, End = new Point(parent.LastX, offsetY + halfHeight), Control = new Point(parent.LastX, position.Y), - Color = parent.Path.Color + Color = parent.Path.Color, + IsMerged = isMerged, }); } else @@ -175,10 +188,9 @@ namespace SourceGit.Models offsetX += unitWidth; // Create new curve for parent commit that not includes before - var l = new PathHelper(parentHash, isMerged, colorIdx, position, new Point(offsetX, position.Y + halfHeight)); + var l = new PathHelper(parentHash, isMerged, colorPicker.Next(), position, new Point(offsetX, position.Y + halfHeight)); unsolved.Add(l); temp.Paths.Add(l.Path); - colorIdx = (colorIdx + 1) % s_penCount; } } } @@ -205,32 +217,53 @@ namespace SourceGit.Models return temp; } + private class ColorPicker + { + public int Next() + { + if (_colorsQueue.Count == 0) + { + for (var i = 0; i < s_penCount; i++) + _colorsQueue.Enqueue(i); + } + + return _colorsQueue.Dequeue(); + } + + public void Recycle(int idx) + { + if (!_colorsQueue.Contains(idx)) + _colorsQueue.Enqueue(idx); + } + + private Queue _colorsQueue = new Queue(); + } + private class PathHelper { - public Path Path { get; } + public Path Path { get; private set; } public string Next { get; set; } - public bool IsMerged { get; set; } public double LastX { get; private set; } + public bool IsMerged => Path.IsMerged; + public PathHelper(string next, bool isMerged, int color, Point start) { Next = next; - IsMerged = isMerged; LastX = start.X; _lastY = start.Y; - Path = new Path(color); + Path = new Path(color, isMerged); Path.Points.Add(start); } public PathHelper(string next, bool isMerged, int color, Point start, Point to) { Next = next; - IsMerged = isMerged; LastX = to.X; _lastY = to.Y; - Path = new Path(color); + Path = new Path(color, isMerged); Path.Points.Add(start); Path.Points.Add(to); } @@ -310,6 +343,19 @@ namespace SourceGit.Models _lastY = y; } + /// + /// End the current path and create a new from the end. + /// + public void ReplaceMerged() + { + var color = Path.Color; + Add(LastX, _lastY); + + Path = new Path(color, true); + Path.Points.Add(new Point(LastX, _lastY)); + _endY = 0; + } + private void Add(double x, double y) { if (_endY < y) @@ -327,7 +373,6 @@ namespace SourceGit.Models private static readonly List s_defaultPenColors = [ Colors.Orange, Colors.ForestGreen, - Colors.Gray, Colors.Turquoise, Colors.Olive, Colors.Magenta, diff --git a/src/Models/CustomAction.cs b/src/Models/CustomAction.cs index 8452a42d..a614961a 100644 --- a/src/Models/CustomAction.cs +++ b/src/Models/CustomAction.cs @@ -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; } } diff --git a/src/Models/DateTimeFormat.cs b/src/Models/DateTimeFormat.cs new file mode 100644 index 00000000..4e71a74f --- /dev/null +++ b/src/Models/DateTimeFormat.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace SourceGit.Models +{ + public class DateTimeFormat + { + public string DateOnly { get; set; } + public string DateTime { get; set; } + + public string Example + { + get => _example.ToString(DateTime); + } + + public DateTimeFormat(string dateOnly, string dateTime) + { + DateOnly = dateOnly; + DateTime = dateTime; + } + + public static int ActiveIndex + { + get; + set; + } = 0; + + public static DateTimeFormat Actived + { + get => Supported[ActiveIndex]; + } + + public static readonly List Supported = new List + { + 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); + } +} diff --git a/src/Models/DealWithLocalChanges.cs b/src/Models/DealWithLocalChanges.cs deleted file mode 100644 index f308a90c..00000000 --- a/src/Models/DealWithLocalChanges.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SourceGit.Models -{ - public enum DealWithLocalChanges - { - DoNothing, - StashAndReaply, - Discard, - } -} diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index e0ae82e0..88992e10 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -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}"; + } + } } } diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs index a09f808c..49d31df5 100644 --- a/src/Models/ExternalMerger.cs +++ b/src/Models/ExternalMerger.cs @@ -39,7 +39,7 @@ namespace SourceGit.Models new ExternalMerger(4, "tortoise_merge", "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""), new ExternalMerger(5, "kdiff3", "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(6, "beyond_compare", "Beyond Compare", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), - new ExternalMerger(7, "win_merge", "WinMerge", "WinMergeU.exe", "\"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""), + 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\""), }; diff --git a/src/Models/GitVersions.cs b/src/Models/GitVersions.cs new file mode 100644 index 00000000..92fd8c59 --- /dev/null +++ b/src/Models/GitVersions.cs @@ -0,0 +1,30 @@ +namespace SourceGit.Models +{ + public static class GitVersions + { + /// + /// The minimal version of Git that required by this app. + /// + public static readonly System.Version MINIMAL = new System.Version(2, 23, 0); + + /// + /// The minimal version of Git that supports the `add` command with the `--pathspec-from-file` option. + /// + public static readonly System.Version ADD_WITH_PATHSPECFILE = new System.Version(2, 25, 0); + + /// + /// The minimal version of Git that supports the `stash push` command with the `--pathspec-from-file` option. + /// + public static readonly System.Version STASH_PUSH_WITH_PATHSPECFILE = new System.Version(2, 26, 0); + + /// + /// The minimal version of Git that supports the `stash push` command with the `--staged` option. + /// + public static readonly System.Version STASH_PUSH_ONLY_STAGED = new System.Version(2, 35, 0); + + /// + /// The minimal version of Git that supports the `stash show` command with the `-u` option. + /// + public static readonly System.Version STASH_SHOW_WITH_UNTRACKED = new System.Version(2, 32, 0); + } +} diff --git a/src/Models/IRepository.cs b/src/Models/IRepository.cs index 12b1adba..0224d81f 100644 --- a/src/Models/IRepository.cs +++ b/src/Models/IRepository.cs @@ -2,9 +2,6 @@ { public interface IRepository { - string FullPath { get; set; } - string GitDir { get; set; } - void RefreshBranches(); void RefreshWorktrees(); void RefreshTags(); diff --git a/src/Models/InteractiveRebase.cs b/src/Models/InteractiveRebase.cs index 0980587a..691aadeb 100644 --- a/src/Models/InteractiveRebase.cs +++ b/src/Models/InteractiveRebase.cs @@ -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; diff --git a/src/Models/MergeMode.cs b/src/Models/MergeMode.cs index 15e3f7e9..5dc70030 100644 --- a/src/Models/MergeMode.cs +++ b/src/Models/MergeMode.cs @@ -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"), ]; diff --git a/src/Models/OpenAI.cs b/src/Models/OpenAI.cs index df67ff66..76daacd0 100644 --- a/src/Models/OpenAI.cs +++ b/src/Models/OpenAI.cs @@ -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 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 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 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)>.*?", RegexOptions.Singleline)] + private static partial Regex REG_COT(); + + private Action _onUpdate = null; + private StringBuilder _thinkTail = new StringBuilder(); + private HashSet _thinkTags = ["think", "thought", "thinking", "thought_chain"]; + private bool _hasTrimmedStart = false; } public class OpenAIService : ObservableObject @@ -87,7 +107,15 @@ namespace SourceGit.Models public string Server { get => _server; - set => SetProperty(ref _server, value); + set + { + // migrate old server value + if (!string.IsNullOrEmpty(value) && value.EndsWith("/chat/completions", StringComparison.Ordinal)) + { + value = value.Substring(0, value.Length - "/chat/completions".Length); + } + SetProperty(ref _server, value); + } } public string ApiKey @@ -102,6 +130,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 +181,54 @@ namespace SourceGit.Models """; } - public OpenAIChatResponse Chat(string prompt, string question, CancellationToken cancellation) + public void Chat(string prompt, string question, CancellationToken cancellation, Action 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(); + 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 +236,7 @@ namespace SourceGit.Models private string _server; private string _apiKey; private string _model; + private bool _streaming = true; private string _analyzeDiffPrompt; private string _generateSubjectPrompt; } diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs index 3c452460..ec9b8f20 100644 --- a/src/Models/Remote.cs +++ b/src/Models/Remote.cs @@ -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 = [ diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs index 5b3aa331..76a7f160 100644 --- a/src/Models/RepositorySettings.cs +++ b/src/Models/RepositorySettings.cs @@ -14,13 +14,43 @@ namespace SourceGit.Models set; } = string.Empty; - public DealWithLocalChanges DealWithLocalChangesOnCheckoutBranch + public bool EnableReflog { get; set; - } = DealWithLocalChanges.DoNothing; + } = false; - public bool EnablePruneOnFetch + public bool EnableFirstParentInHistories + { + get; + set; + } = false; + + public bool EnableTopoOrderInHistories + { + get; + set; + } = false; + + public bool OnlyHighlighCurrentBranchInHistories + { + get; + set; + } = false; + + public TagSortMode TagSortMode + { + get; + set; + } = TagSortMode.CreatorDate; + + public bool IncludeUntrackedInLocalChanges + { + get; + set; + } = true; + + public bool EnableForceOnFetch { get; set; @@ -32,12 +62,6 @@ namespace SourceGit.Models set; } = false; - public DealWithLocalChanges DealWithLocalChangesOnPull - { - get; - set; - } = DealWithLocalChanges.DoNothing; - public bool PreferRebaseInsteadOfMerge { get; @@ -68,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 { @@ -84,31 +114,31 @@ namespace SourceGit.Models { get; set; - } = new AvaloniaList(); + } = []; public AvaloniaList CommitTemplates { get; set; - } = new AvaloniaList(); + } = []; public AvaloniaList CommitMessages { get; set; - } = new AvaloniaList(); + } = []; public AvaloniaList IssueTrackerRules { get; set; - } = new AvaloniaList(); + } = []; public AvaloniaList CustomActions { get; set; - } = new AvaloniaList(); + } = []; public bool EnableAutoFetch { @@ -146,12 +176,54 @@ namespace SourceGit.Models set; } = false; + public bool AutoRestoreAfterStash + { + get; + set; + } = false; + public string PreferedOpenAIService { get; set; } = "---"; + public bool IsLocalBranchesExpandedInSideBar + { + get; + set; + } = true; + + public bool IsRemotesExpandedInSideBar + { + get; + set; + } = false; + + public bool IsTagsExpandedInSideBar + { + get; + set; + } = false; + + public bool IsSubmodulesExpandedInSideBar + { + get; + set; + } = false; + + public bool IsWorktreeExpandedInSideBar + { + get; + set; + } = false; + + public List ExpandedBranchNodesInSideBar + { + get; + set; + } = []; + public Dictionary CollectHistoriesFilters() { var map = new Dictionary(); @@ -378,65 +450,13 @@ namespace SourceGit.Models CommitMessages.Insert(0, message); } - public IssueTrackerRule AddNewIssueTracker() + public IssueTrackerRule AddIssueTracker(string name, string regex, string url) { var rule = new IssueTrackerRule() { - Name = "New Issue Tracker", - RegexString = "#(\\d+)", - URLTemplate = "https://xxx/$1", - }; - - IssueTrackerRules.Add(rule); - return rule; - } - - public IssueTrackerRule AddGithubIssueTracker(string repoURL) - { - var rule = new IssueTrackerRule() - { - Name = "Github ISSUE", - RegexString = "#(\\d+)", - URLTemplate = string.IsNullOrEmpty(repoURL) ? "https://github.com/username/repository/issues/$1" : $"{repoURL}/issues/$1", - }; - - IssueTrackerRules.Add(rule); - return rule; - } - - public IssueTrackerRule AddJiraIssueTracker() - { - var rule = new IssueTrackerRule() - { - Name = "Jira Tracker", - RegexString = "PROJ-(\\d+)", - URLTemplate = "https://jira.yourcompany.com/browse/PROJ-$1", - }; - - IssueTrackerRules.Add(rule); - return rule; - } - - public IssueTrackerRule AddGitLabIssueTracker(string repoURL) - { - var rule = new IssueTrackerRule() - { - Name = "GitLab ISSUE", - RegexString = "#(\\d+)", - URLTemplate = string.IsNullOrEmpty(repoURL) ? "https://gitlab.com/username/repository/-/issues/$1" : $"{repoURL}/-/issues/$1", - }; - - IssueTrackerRules.Add(rule); - return rule; - } - - public IssueTrackerRule AddGitLabMergeRequestTracker(string repoURL) - { - var rule = new IssueTrackerRule() - { - Name = "GitLab MR", - RegexString = "!(\\d+)", - URLTemplate = string.IsNullOrEmpty(repoURL) ? "https://gitlab.com/username/repository/-/merge_requests/$1" : $"{repoURL}/-/merge_requests/$1", + Name = name, + RegexString = regex, + URLTemplate = url, }; IssueTrackerRules.Add(rule); @@ -451,11 +471,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; } diff --git a/src/Models/RevisionFile.cs b/src/Models/RevisionFile.cs index f1f5265f..8cc1be2a 100644 --- a/src/Models/RevisionFile.cs +++ b/src/Models/RevisionFile.cs @@ -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; } } diff --git a/src/Models/Version.cs b/src/Models/SelfUpdate.cs similarity index 65% rename from src/Models/Version.cs rename to src/Models/SelfUpdate.cs index 35c21778..e02f80d8 100644 --- a/src/Models/Version.cs +++ b/src/Models/SelfUpdate.cs @@ -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; + } + } } diff --git a/src/Models/ShellOrTerminal.cs b/src/Models/ShellOrTerminal.cs index 8ab25788..3ada2cf9 100644 --- a/src/Models/ShellOrTerminal.cs +++ b/src/Models/ShellOrTerminal.cs @@ -41,6 +41,8 @@ namespace SourceGit.Models { new ShellOrTerminal("mac-terminal", "Terminal", ""), new ShellOrTerminal("iterm2", "iTerm", ""), + new ShellOrTerminal("warp", "Warp", ""), + new ShellOrTerminal("ghostty", "Ghostty", "") }; } else @@ -55,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", ""), }; } diff --git a/src/Models/Stash.cs b/src/Models/Stash.cs index 06da763a..257b6d33 100644 --- a/src/Models/Stash.cs +++ b/src/Models/Stash.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace SourceGit.Models { @@ -6,9 +7,10 @@ namespace SourceGit.Models { public string Name { get; set; } = ""; public string SHA { get; set; } = ""; + public List Parents { get; set; } = []; public ulong Time { get; set; } = 0; public string Message { get; set; } = ""; - public string TimeStr => DateTime.UnixEpoch.AddSeconds(Time).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"); + public string TimeStr => DateTime.UnixEpoch.AddSeconds(Time).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime); } } diff --git a/src/Models/Tag.cs b/src/Models/Tag.cs index 2e8f2c8e..51681d93 100644 --- a/src/Models/Tag.cs +++ b/src/Models/Tag.cs @@ -2,10 +2,18 @@ namespace SourceGit.Models { + public enum TagSortMode + { + CreatorDate = 0, + NameInAscending, + NameInDescending, + } + public class Tag : ObservableObject { public string Name { get; set; } = string.Empty; public string SHA { get; set; } = string.Empty; + public ulong CreatorDate { get; set; } = 0; public string Message { get; set; } = string.Empty; public FilterMode FilterMode diff --git a/src/Models/TemplateEngine.cs b/src/Models/TemplateEngine.cs index 6b5f525d..8472750c 100644 --- a/src/Models/TemplateEngine.cs +++ b/src/Models/TemplateEngine.cs @@ -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 diff --git a/src/Models/TextMateHelper.cs b/src/Models/TextMateHelper.cs index 0eb5e489..b7efae72 100644 --- a/src/Models/TextMateHelper.cs +++ b/src/Models/TextMateHelper.cs @@ -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 Extensions, string File) { public readonly string Scope = Scope; - public readonly string Extension = Extension; + public readonly List Extensions = Extensions; public readonly string File = File; } } diff --git a/src/Models/User.cs b/src/Models/User.cs index 850bcf2f..066ab747 100644 --- a/src/Models/User.cs +++ b/src/Models/User.cs @@ -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 _caches = new ConcurrentDictionary(); private readonly int _hash; } diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index 710b307d..3ccecad4 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -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 (_submodules) - { - if (_submodules.Count > 0) - _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); - } } else if (name.StartsWith("objects/", StringComparison.Ordinal) || name.Equals("index", StringComparison.Ordinal)) { @@ -195,7 +189,7 @@ namespace SourceGit.Models if (name == ".git" || name.StartsWith(".git/", StringComparison.Ordinal)) return; - lock (_submodules) + lock (_lockSubmodule) { foreach (var submodule in _submodules) { diff --git a/src/Models/Worktree.cs b/src/Models/Worktree.cs index f9ba14e4..bc40e320 100644 --- a/src/Models/Worktree.cs +++ b/src/Models/Worktree.cs @@ -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; } } diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index a24f1b65..bfb98500 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -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 { diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index 9660920d..d7ef4701 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -18,14 +18,33 @@ 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() { - return File.Exists("/usr/bin/git") ? "/usr/bin/git" : string.Empty; + var gitPathVariants = new List() { + "/usr/bin/git", "/usr/local/bin/git", "/opt/homebrew/bin/git", "/opt/homebrew/opt/git/bin/git" + }; + foreach (var path in gitPathVariants) + if (File.Exists(path)) + return path; + return string.Empty; } public string FindTerminal(Models.ShellOrTerminal shell) @@ -36,6 +55,10 @@ namespace SourceGit.Native return "Terminal"; case "iterm2": return "iTerm"; + case "warp": + return "Warp"; + case "ghostty": + return "Ghostty"; } return string.Empty; diff --git a/src/Native/OS.cs b/src/Native/OS.cs index b53f81d9..320b5208 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Text; +using System.Text.RegularExpressions; using Avalonia; namespace SourceGit.Native { - public static class OS + public static partial class OS { public interface IBackend { @@ -22,11 +25,48 @@ namespace SourceGit.Native void OpenWithDefaultEditor(string file); } - public static string DataDir { get; private set; } = string.Empty; - public static string GitExecutable { get; set; } = string.Empty; - public static string ShellOrTerminal { get; set; } = string.Empty; - public static List ExternalTools { get; set; } = []; - public static string CustomPathEnv { get; set; } = string.Empty; + public static string DataDir + { + get; + private set; + } = string.Empty; + + public static string GitExecutable + { + get => _gitExecutable; + set + { + if (_gitExecutable != value) + { + _gitExecutable = value; + UpdateGitVersion(); + } + } + } + + public static string GitVersionString + { + get; + private set; + } = string.Empty; + + public static Version GitVersion + { + get; + private set; + } = new Version(0, 0, 0); + + public static string ShellOrTerminal + { + get; + set; + } = string.Empty; + + public static List ExternalTools + { + get; + set; + } = []; static OS() { @@ -55,6 +95,17 @@ namespace SourceGit.Native public static void SetupDataDir() { + if (OperatingSystem.IsWindows()) + { + var execFile = Process.GetCurrentProcess().MainModule!.FileName; + var portableDir = Path.Combine(Path.GetDirectoryName(execFile), "data"); + if (Directory.Exists(portableDir)) + { + DataDir = portableDir; + return; + } + } + var osAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); if (string.IsNullOrEmpty(osAppDataDir)) DataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".sourcegit"); @@ -111,6 +162,68 @@ 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)) + { + GitVersionString = string.Empty; + GitVersion = new Version(0, 0, 0); + return; + } + + var start = new ProcessStartInfo(); + start.FileName = _gitExecutable; + start.Arguments = "--version"; + start.UseShellExecute = false; + start.CreateNoWindow = true; + start.RedirectStandardOutput = true; + start.RedirectStandardError = true; + start.StandardOutputEncoding = Encoding.UTF8; + start.StandardErrorEncoding = Encoding.UTF8; + + var proc = new Process() { StartInfo = start }; + try + { + proc.Start(); + + var rs = proc.StandardOutput.ReadToEnd(); + proc.WaitForExit(); + if (proc.ExitCode == 0 && !string.IsNullOrWhiteSpace(rs)) + { + GitVersionString = rs.Trim(); + + var match = REG_GIT_VERSION().Match(GitVersionString); + if (match.Success) + { + var major = int.Parse(match.Groups[1].Value); + var minor = int.Parse(match.Groups[2].Value); + var build = int.Parse(match.Groups[3].Value); + GitVersion = new Version(major, minor, build); + GitVersionString = GitVersionString.Substring(11).Trim(); + } + } + } + catch + { + // Ignore errors + } + + proc.Close(); + } + + [GeneratedRegex(@"^git version[\s\w]*(\d+)\.(\d+)[\.\-](\d+).*$")] + private static partial Regex REG_GIT_VERSION(); + private static IBackend _backend = null; + private static string _gitExecutable = string.Empty; } } diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index 48fbb287..eb354f10 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -8,6 +8,7 @@ using System.Text; using Avalonia; using Avalonia.Controls; +using Avalonia.Threading; namespace SourceGit.Native { @@ -26,9 +27,21 @@ namespace SourceGit.Native internal string szCSDVersion; } - [DllImport("ntdll")] + [StructLayout(LayoutKind.Sequential)] + internal struct MARGINS + { + public int cxLeftWidth; + public int cxRightWidth; + public int cyTopHeight; + public int cyBottomHeight; + } + + [DllImport("ntdll.dll")] private static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation); + [DllImport("dwmapi.dll")] + private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); + [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)] private static extern bool PathFindOnPath([In, Out] StringBuilder pszFile, [In] string[] ppszOtherDirs); @@ -140,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); } @@ -202,10 +215,17 @@ namespace SourceGit.Native private void FixWindowFrameOnWin10(Window w) { - if (w.WindowState == WindowState.Maximized || w.WindowState == WindowState.FullScreen) - w.SystemDecorations = SystemDecorations.Full; - else if (w.WindowState == WindowState.Normal) - w.SystemDecorations = SystemDecorations.BorderOnly; + // 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); + }, DispatcherPriority.Render); } #region EXTERNAL_EDITOR_FINDER diff --git a/src/Resources/Grammars/haxe.json b/src/Resources/Grammars/haxe.json index 12acc538..3f78154d 100644 --- a/src/Resources/Grammars/haxe.json +++ b/src/Resources/Grammars/haxe.json @@ -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" } } -} \ No newline at end of file +} diff --git a/src/Resources/Grammars/hxml.json b/src/Resources/Grammars/hxml.json index 829c403e..3be42577 100644 --- a/src/Resources/Grammars/hxml.json +++ b/src/Resources/Grammars/hxml.json @@ -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" -} \ No newline at end of file +} diff --git a/src/Resources/Grammars/jsp.json b/src/Resources/Grammars/jsp.json new file mode 100644 index 00000000..2fbfd97c --- /dev/null +++ b/src/Resources/Grammars/jsp.json @@ -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" + } + } +} diff --git a/src/Resources/Grammars/kotlin.json b/src/Resources/Grammars/kotlin.json index e8f844d0..2857f717 100644 --- a/src/Resources/Grammars/kotlin.json +++ b/src/Resources/Grammars/kotlin.json @@ -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" } } -} \ No newline at end of file +} diff --git a/src/Resources/Grammars/toml.json b/src/Resources/Grammars/toml.json index 86c2ef87..6be4678f 100644 --- a/src/Resources/Grammars/toml.json +++ b/src/Resources/Grammars/toml.json @@ -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": [ { diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 3eeb4b41..66589440 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -5,13 +5,16 @@ M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z 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 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 + M509 546 780 275 871 366 509 728 147 366 238 275zM509 728h-362v128h724v-128z 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 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 - M378 116l265 0 0 47-265 0 0-47ZM888 116 748 116l0 47 124 0c18 0 33 15 33 33l0 93L115 290l0-93c0-18 15-33 33-33l124 0 0-47L132 116c-35 0-64 29-64 64l0 714c0 35 29 64 64 64l757 0c35 0 64-29 64-64l-0-714C952 145 924 116 888 116zM905 337l0 540c0 18-15 33-33 33L148 910c-18 0-33-15-33-33L115 337 905 337zM301 65l47 0 0 170-47 0 0-170ZM673 65l47 0 0 170-47 0 0-170ZM358 548l0 231 53 0L411 459l-35 0-3 4c-18 26-41 49-70 68l-4 3 0 54 13-8C331 569 346 559 358 548zM618 727c-10 6-24 8-42 5-16-3-28-18-35-46l-2-9-48 13 2 8c6 30 18 52 36 65 17 13 36 20 55 21 3 0 7 0 10 0 15 0 28-2 40-7 14-6 27-13 37-23 10-10 18-22 23-37 5-14 8-28 8-42 1-14-1-27-4-39l-0-0c-3-12-8-24-15-36-7-13-19-23-35-30-15-7-31-11-47-11-11-0-23 1-36 5 4-15 8-32 11-52l114 0 0-49L536 464l-1 7c-25 116-32 145-33 150l-3 10 46 5 3-4c8-11 18-18 31-21 13-3 25-3 35-0 10 3 18 9 24 18 7 9 10 20 11 34 1 14-2 26-6 37C636 711 629 720 618 727z 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 + 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 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 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 + 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 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 + 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 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 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 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 @@ -51,6 +54,7 @@ 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 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 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 + 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 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 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 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 @@ -63,9 +67,9 @@ M412 66C326 132 271 233 271 347c0 17 1 34 4 50-41-48-98-79-162-83a444 444 0 00-46 196c0 207 142 382 337 439h2c19 0 34 15 34 33 0 11-6 21-14 26l1 14C183 973 0 763 0 511 0 272 166 70 393 7A35 35 0 01414 0c19 0 34 15 34 33a33 33 0 01-36 33zm200 893c86-66 141-168 141-282 0-17-1-34-4-50 41 48 98 79 162 83a444 444 0 0046-196c0-207-142-382-337-439h-2a33 33 0 01-34-33c0-11 6-21 14-26L596 0C841 51 1024 261 1024 513c0 239-166 441-393 504A35 35 0 01610 1024a33 33 0 01-34-33 33 33 0 0136-33zM512 704a192 192 0 110-384 192 192 0 010 384z M512 64A447 447 0 0064 512c0 248 200 448 448 448s448-200 448-448S760 64 512 64zM218 295h31c54 0 105 19 145 55 13 12 13 31 3 43a35 35 0 01-22 10 36 36 0 01-21-7 155 155 0 00-103-39h-31a32 32 0 01-31-31c0-18 13-31 30-31zm31 433h-31a32 32 0 01-31-31c0-16 13-31 31-31h31A154 154 0 00403 512 217 217 0 01620 295h75l-93-67a33 33 0 01-7-43 33 33 0 0143-7l205 148-205 148a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67H620a154 154 0 00-154 154c0 122-97 220-217 220zm390 118a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67h-75c-52 0-103-19-143-54-12-12-13-31-1-43a30 30 0 0142-3 151 151 0 00102 39h75L602 599a33 33 0 01-7-43 33 33 0 0143-7l205 148-203 151z M922 39H102A65 65 0 0039 106v609a65 65 0 0063 68h94v168a34 34 0 0019 31 30 30 0 0012 3 30 30 0 0022-10l182-192H922a65 65 0 0063-68V106A65 65 0 00922 39zM288 378h479a34 34 0 010 68H288a34 34 0 010-68zm0-135h479a34 34 0 010 68H288a34 34 0 010-68zm0 270h310a34 34 0 010 68H288a34 34 0 010-68z - M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zM139 832V192c0-6 4-11 11-11h331v661H149c-6 0-11-4-11-11zm747 0c0 6-4 11-11 11H544v-661H875c6 0 11 4 11 11v640z - M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zm-725 64h725c6 0 11 4 11 11v288h-747V192c0-6 4-11 11-11zm725 661H149c-6 0-11-4-11-11V544h747V832c0 6-4 11-11 11z + M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zM139 832V192c0-6 4-11 11-11h331v661H149c-6 0-11-4-11-11zm747 0c0 6-4 11-11 11H544v-661H875c6 0 11 4 11 11v640z M40 9 15 23 15 31 9 28 9 20 34 5 24 0 0 14 0 34 25 48 25 28 49 14zM26 29 26 48 49 34 49 15z + M892 251c-5-11-18-18-30-18H162c-12 0-23 7-30 18-5 11-5 26 2 35l179 265v320c0 56 44 102 99 102h200c55 0 99-46 99-102v-320l179-266c9-11 9-24 4-34zm-345 540c0 18-14 35-34 35-18 0-34-14-34-35v-157c0-18 14-34 34-34 18 0 34 14 34 34v157zM512 205c18 0 34-14 34-35V87c0-20-16-35-34-35s-34 14-34 35v84c1 20 16 34 34 34zM272 179c5 18 23 30 40 24 17-6 28-24 23-42l-25-51c-5-18-23-30-40-24s-28 24-23 42L272 179zM777 127c5-18-6-36-23-42-17-6-35 5-40 24l-25 51c-5 18 6 37 23 42 17 6 35-5 40-24l25-52z M416 192m32 0 448 0q32 0 32 32l0 0q0 32-32 32l-448 0q-32 0-32-32l0 0q0-32 32-32ZM416 448m32 0 448 0q32 0 32 32l0 0q0 32-32 32l-448 0q-32 0-32-32l0 0q0-32 32-32ZM416 704m32 0 448 0q32 0 32 32l0 0q0 32-32 32l-448 0q-32 0-32-32l0 0q0-32 32-32ZM96 320l128-192 128 192h-256zM96 640l128 192 128-192h-256zM190 320h64v320H190z M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l132 0 0-128 64 0 0 128 132 0 0 64-132 0 0 128-64 0 0-128-132 0Z M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l328 0 0 64-328 0Z @@ -77,6 +81,7 @@ M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z M824 645V307c0-56-46-102-102-102h-102V102l-154 154 154 154V307h102v338c-46 20-82 67-82 123 0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123zm-51 195c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72zM384 256c0-72-61-133-133-133-72 0-133 61-133 133 0 56 36 102 82 123v266C154 666 118 712 118 768c0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123V379C348 358 384 312 384 256zM323 768c0 41-31 72-72 72-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72zM251 328c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72z M896 64H128C96 64 64 96 64 128v768c0 32 32 64 64 64h768c32 0 64-32 64-64V128c0-32-32-64-64-64z m-64 736c0 16-17 32-32 32H224c-18 0-32-12-32-32V224c0-16 16-32 32-32h576c15 0 32 16 32 32v576zM512 384c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z + M0 512M1024 512M512 0M512 1024M813 448c-46 0-83 37-83 83 0 46 37 83 83 83 46 0 83-37 83-83 0-46-37-83-83-83zM211 448C165 448 128 485 128 531c0 46 37 83 83 83 46 0 83-37 83-83 0-46-37-83-83-83zM512 448c-46 0-83 37-83 83 0 46 37 83 83 83 46 0 83-37 83-83C595 485 558 448 512 448z M299 811 299 725 384 725 384 811 299 811M469 811 469 725 555 725 555 811 469 811M640 811 640 725 725 725 725 811 640 811M299 640 299 555 384 555 384 640 299 640M469 640 469 555 555 555 555 640 469 640M640 640 640 555 725 555 725 640 640 640M299 469 299 384 384 384 384 469 299 469M469 469 469 384 555 384 555 469 469 469M640 469 640 384 725 384 725 469 640 469M299 299 299 213 384 213 384 299 299 299M469 299 469 213 555 213 555 299 469 299M640 299 640 213 725 213 725 299 640 299Z M64 363l0 204 265 0L329 460c0-11 6-18 14-20C349 437 355 437 362 441c93 60 226 149 226 149 33 22 34 60 0 82 0 0-133 89-226 149-14 9-32-3-32-18l-1-110L64 693l0 117c0 41 34 75 75 75l746 0c41 0 75-34 75-74L960 364c0-0 0-1 0-1L64 363zM64 214l0 75 650 0-33-80c-16-38-62-69-103-69l-440 0C97 139 64 173 64 214z M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z @@ -86,8 +91,9 @@ 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 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 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 + 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 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 - M854 234a171 171 0 00-171 171v51c13-5 28-7 43-7h35a136 136 0 01136 136v93a198 198 0 01-198 198 101 101 0 01-101-101V405a256 256 0 01256-256h21a21 21 0 0121 21v43a21 21 0 01-21 21h-21zM213 456c13-5 28-7 43-7h35a136 136 0 01136 136v93a198 198 0 01-198 198 101 101 0 01-101-101V405a256 256 0 01256-256h21a21 21 0 0121 21v43a21 21 0 01-21 21h-21a171 171 0 00-171 171v51z + 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 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 M512 128M706 302a289 289 0 00-173 44 27 27 0 1029 46 234 234 0 01125-36c23 0 45 3 66 9 93 28 161 114 161 215C914 704 813 805 687 805H337C211 805 110 704 110 580c0-96 61-178 147-210C282 263 379 183 495 183a245 245 0 01210 119z M364 512h67v108h108v67h-108v108h-67v-108h-108v-67h108v-108zm298-64A107 107 0 01768 555C768 614 720 660 660 660h-108v-54h-108v-108h-94v108h-94c4-21 22-47 44-51l-1-12a75 75 0 0171-75a128 128 0 01239-7a106 106 0 0153-14z @@ -115,6 +121,7 @@ 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 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 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 + M170 831 513 489 855 831 960 726 512 278 64 726 170 831zM512 278h448v-128h-896v128h448z 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 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 M30 0 30 30 0 15z diff --git a/src/Resources/Images/ShellIcons/ghostty.png b/src/Resources/Images/ShellIcons/ghostty.png new file mode 100644 index 00000000..e394a517 Binary files /dev/null and b/src/Resources/Images/ShellIcons/ghostty.png differ diff --git a/src/Resources/Images/ShellIcons/ptyxis.png b/src/Resources/Images/ShellIcons/ptyxis.png new file mode 100644 index 00000000..9202f6e1 Binary files /dev/null and b/src/Resources/Images/ShellIcons/ptyxis.png differ diff --git a/src/Resources/Images/ShellIcons/warp.png b/src/Resources/Images/ShellIcons/warp.png new file mode 100644 index 00000000..7d604d8e Binary files /dev/null and b/src/Resources/Images/ShellIcons/warp.png differ diff --git a/src/Resources/Images/unreal.png b/src/Resources/Images/unreal.png new file mode 100644 index 00000000..4faae92b Binary files /dev/null and b/src/Resources/Images/unreal.png differ diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 2045a229..bbfe4545 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -4,12 +4,6 @@ Info Über SourceGit - • Erstellt mit - • Grafik gerendert durch - © 2024 sourcegit-scm - • Texteditor von - • Monospace-Schriftarten von - • Quelltext findest du auf Open Source & freier Git GUI Client Worktree hinzufügen Was auschecken: @@ -22,21 +16,19 @@ Branch verfolgen: Remote-Branch verfolgen OpenAI Assistent + Neu generieren Verwende OpenAI, um Commit-Nachrichten zu generieren + Als Commit-Nachricht verwenden Patch - Fehler - Fehler werfen und anwenden des Patches verweigern - Alle Fehler - Ähnlich wie 'Fehler', zeigt aber mehr an Patch-Datei: Wähle die anzuwendende .patch-Datei Ignoriere Leerzeichenänderungen - Keine Warnungen - Keine Warnung vor Leerzeichen am Zeilenende Patch anwenden - Warnen - Gibt eine Warnung für ein paar solcher Fehler aus, aber wendet es an Leerzeichen: + Stash anwenden + Nach dem Anwenden löschen + Änderungen des Index wiederherstellen + Stash: Archivieren... Speichere Archiv in: Wähle Archivpfad aus @@ -50,10 +42,10 @@ Blame BLAME WIRD BEI DIESER DATEI NICHT UNTERSTÜTZT!!! Auschecken von ${0}$... - Mit Branch vergleichen Mit HEAD vergleichen Mit Worktree vergleichen Branch-Namen kopieren + Benutzerdefinierte Aktion Lösche ${0}$... Lösche alle ausgewählten {0} Branches Alle Änderungen verwerfen @@ -61,13 +53,13 @@ Fetche ${0}$ in ${1}$ hinein... Git Flow - Abschließen ${0}$ Merge ${0}$ in ${1}$ hinein... + Merge ausgewählte {0} Branches in aktuellen hinein Pull ${0}$ Pull ${0}$ in ${1}$ hinein... Push ${0}$ Rebase ${0}$ auf ${1}$... Benenne ${0}$ um... - Setze verfolgten Branch - Upstream Verfolgung aufheben + Setze verfolgten Branch... Branch Vergleich Bytes ABBRECHEN @@ -85,7 +77,6 @@ Branch: Lokale Änderungen: Verwerfen - Nichts tun Stashen & wieder anwenden Cherry Pick Quelle an Commit-Nachricht anhängen @@ -101,6 +92,7 @@ Lokaler Name: Repository-Name. Optional. Übergeordneter Ordner: + Submodule initialisieren und aktualisieren Repository URL: SCHLIESSEN Editor @@ -113,6 +105,8 @@ SHA kopieren Benutzerdefinierte Aktion Interactives Rebase von ${0}$ auf diesen Commit + Merge in ${0}$ hinein + Merge ... Rebase von ${0}$ auf diesen Commit Reset ${0}$ auf diesen Commit Commit rückgängig machen @@ -124,6 +118,7 @@ Änderungen durchsuchen... DATEIEN LFS DATEI + Dateien durchsuchen... Submodule INFORMATION AUTOR @@ -150,6 +145,7 @@ Ausführbare Datei: Name: Geltungsbereich: + Branch Commit Repository Email Adresse @@ -158,9 +154,9 @@ Remotes automatisch fetchen Minute(n) Standard Remote - Aktivere --prune beim fetchen - Aktiviere --signoff für Commits TICKETSYSTEM + Beispiel für Gitee Issue Regel einfügen + Beispiel für Gitee Pull Request Regel einfügen Beispiel für Github-Regel hinzufügen Beispiel für Jira-Regel hinzufügen Beispiel für Gitlab Issue Regel einfügen @@ -190,16 +186,15 @@ Kopieren Kopiere gesamten Text Pfad kopieren - Dateinamen kopieren Branch erstellen... Basierend auf: Erstellten Branch auschecken Lokale Änderungen: Verwerfen - Nichts tun Stashen & wieder anwenden Neuer Branch-Name: Branch-Namen eingeben. + Leerzeichen werden durch Bindestriche ersetzt. Lokalen Branch erstellen Tag erstellen... Neuer Tag auf: @@ -223,8 +218,11 @@ Du versuchst mehrere Branches auf einmal zu löschen. Kontrolliere noch einmal vor dem Fortfahren! Remote löschen Remote: + Pfad: Ziel: Bestätige löschen von Gruppe + Alle Nachfolger werden aus der Liste entfernt. + Dadurch wird es nur aus der Liste entfernt, nicht von der Festplatte! Bestätige löschen von Repository Lösche Submodul Submodul Pfad: @@ -249,6 +247,7 @@ Seiten wechseln Syntax Hervorhebung Zeilenumbruch + Aktiviere Block-Navigation Öffne in Merge Tool Alle Zeilen anzeigen Weniger Zeilen anzeigen @@ -280,6 +279,7 @@ Verwerfe {0} Dateien... Verwerfe Änderungen in ausgewählten Zeilen Öffne externes Merge Tool + Löse mit ${0}$ Als Patch speichern... Stagen {0} Dateien stagen @@ -345,7 +345,6 @@ Verfolge alle '{0}' Dateien Verfolge alle *{0} Dateien Verlauf - Wechsle zwischen horizontalem und vertikalem Layout AUTOR AUTOR ZEITPUNKT GRAPH & COMMIT-NACHRICHT @@ -358,11 +357,12 @@ Tastaturkürzel Referenz GLOBAL Aktuelles Popup schließen + Klone neues Repository Aktuellen Tab schließen Zum vorherigen Tab wechseln Zum nächsten Tab wechseln Neuen Tab erstellen - Einstellungen öffnen + Einstellungen öffnen REPOSITORY Gestagte Änderungen committen Gestagte Änderungen committen und pushen @@ -390,9 +390,13 @@ Initialisiere Repository Pfad: Cherry-Pick wird durchgeführt. + Verarbeite commit Merge request wird durchgeführt. + Verarbeite Rebase wird durchgeführt. + Angehalten bei Revert wird durchgeführt. + Reverte commit Interaktiver Rebase Ziel Branch: Auf: @@ -403,6 +407,11 @@ Branch mergen Ziel-Branch: Merge Option: + Quelle: + Merge (mehrere) + Alle Änderungen committen + Strategie: + Ziele: Bewege Repository Knoten Wähle Vorgänger-Knoten für: Name: @@ -420,6 +429,7 @@ Einfügen Gerade eben Vor {0} Minuten + Vor 1 Stunde Vor {0} Stunden Gestern Vor {0} Tagen @@ -427,69 +437,71 @@ Vor {0} Monaten Leztes Jahr Vor {0} Jahren - Einstellungen - OPEN AI - Analysierung des Diff Befehl - API Schlüssel - Generiere Nachricht Befehl - Name - Server - Modell - DARSTELLUNG - Standardschriftart - Schriftgröße - Standard - Texteditor - Monospace-Schriftart - Verwende die Monospace-Schriftart nur im Texteditor - Design - Design-Anpassungen - Fixe Tab-Breite in Titelleiste - Verwende nativen Fensterrahmen - DIFF/MERGE TOOL - Installationspfad - Installationspfad zum Diff/Merge Tool - Tool - ALLGEMEIN - Beim Starten nach Updates suchen - Sprache - Commit-Historie - Zeige Autor Zeitpunkt anstatt Commit Zeitpunkt - Zeige Nachfolger in den Commit Details - Längenvorgabe für Commit-Nachrichten - GIT - Aktiviere Auto-CRLF - Clone Standardordner - Benutzer Email - Globale Git Benutzer Email - Installationspfad - Benutzername - Globaler Git Benutzername - Git Version - Diese App setzt Git (>= 2.23.0) voraus - GPG SIGNIERUNG - Commit-Signierung - Tag-Signierung - GPG Format - GPG Installationspfad - Installationspfad zum GPG Programm - Benutzer Signierungsschlüssel - GPG Benutzer Signierungsschlüssel - EINBINDUNGEN - SHELL/TERMINAL - Shell/Terminal - Pfad + Einstellungen + OPEN AI + Analysierung des Diff Befehl + API Schlüssel + Generiere Nachricht Befehl + Name + Server + Modell + DARSTELLUNG + Standardschriftart + Schriftgröße + Standard + Texteditor + Monospace-Schriftart + Verwende die Monospace-Schriftart nur im Texteditor + Design + Design-Anpassungen + Fixe Tab-Breite in Titelleiste + Verwende nativen Fensterrahmen + DIFF/MERGE TOOL + Installationspfad + Installationspfad zum Diff/Merge Tool + Tool + ALLGEMEIN + Beim Starten nach Updates suchen + Datumsformat + Sprache + Commit-Historie + Zeige Autor Zeitpunkt anstatt Commit Zeitpunkt + Zeige Nachfolger in den Commit Details + Längenvorgabe für Commit-Nachrichten + GIT + Aktiviere Auto-CRLF + Klon Standardordner + Benutzer Email + Globale Git Benutzer Email + Aktivere --prune beim fetchen + Installationspfad + Aktiviere HTTP SSL Verifizierung + Benutzername + Globaler Git Benutzername + Git Version + Diese App setzt Git (>= 2.23.0) voraus + GPG SIGNIERUNG + Commit-Signierung + Tag-Signierung + GPG Format + GPG Installationspfad + Installationspfad zum GPG Programm + Benutzer Signierungsschlüssel + GPG Benutzer Signierungsschlüssel + EINBINDUNGEN + SHELL/TERMINAL + Shell/Terminal + Pfad Remote löschen Ziel: Worktrees löschen - Worktree Informationen in `$GIT_DIR/worktrees` löschen + Worktree Informationen in `$GIT_COMMON_DIR/worktrees` löschen Pull Remote-Branch: Alle Branches fetchen Lokaler Branch: Lokale Änderungen: Verwerfen - Nichts tun Stashen & wieder anwenden Ohne Tags fetchen Remote: @@ -549,32 +561,44 @@ Aufheben Im Graph ausblenden Im Graph filtern - Sortierungsmodus wechseln - Commit Zeitpunkt (--date-order) - Topologie (--topo-order) + Aktiviere '--first-parent' Option + LAYOUT + Horizontal + Vertikal + COMMIT SORTIERUNG + Commit Zeitpunkt + Topologie LOKALE BRANCHES Zum HEAD wechseln - Aktiviere '--first-parent' Option Erstelle Branch + BENACHRICHTIGUNGEN LÖSCHEN + Nur aktuellen Branch im Graphen hervorheben Öffne in {0} Öffne in externen Tools Aktualisiern REMOTES REMOTE HINZUFÜGEN Commit suchen + Autor + Committer Dateiname Commit-Nachricht SHA - Autor & Committer Aktueller Branch Zeige Tags als Baum + ÜBERSPRINGEN Statistiken SUBMODULE SUBMODUL HINZUFÜGEN SUBMODUL AKTUALISIEREN TAGS NEUER TAG + Nach Erstellungsdatum + Nach Namen (Aufsteigend) + Nach Namen (Absteigend) + Sortiere Öffne im Terminal + Verwende relative Zeitangaben in Verlauf WORKTREES WORKTREE HINZUFÜGEN PRUNE @@ -602,6 +626,10 @@ Diese Version überspringen Software Update Es sind momentan kein Updates verfügbar. + Setze verfolgten Branch + Branch: + Upstream Verfolgung aufheben + Upstream: SHA kopieren Zum Commit wechseln Squash Commits @@ -610,6 +638,8 @@ Pfad zum privaten SSH Schlüssel START Stash + Automatisch wiederherstellen nach dem Stashen + Die Arbeitsdateien bleiben unverändert, aber ein Stash wird gespeichert. Inklusive nicht-verfolgter Dateien Behalte gestagte Dateien Name: @@ -619,7 +649,6 @@ Lokale Änderungen stashen Anwenden Entfernen - Anwenden und entfernen Stash entfernen Entfernen: Stashes @@ -666,7 +695,7 @@ Öffne alle Repositories Öffne Repository Öffne Terminal - Clone Standardordner erneut nach Repositories durchsuchen + Klon Standardordner erneut nach Repositories durchsuchen Suche Repositories... Sortieren Änderungen @@ -681,6 +710,7 @@ COMMIT & PUSH Template/Historie Klick-Ereignis auslösen + Commit (Bearbeitung) Alle Änderungen stagen und committen Leerer Commit erkannt! Fortfahren (--allow-empty)? KONFLIKTE ERKANNT @@ -688,6 +718,7 @@ NICHT-VERFOLGTE DATEIEN INKLUDIEREN KEINE BISHERIGEN COMMIT-NACHRICHTEN KEINE COMMIT TEMPLATES + SignOff GESTAGED UNSTAGEN ALLES UNSTAGEN diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 65b4dd96..5ff1e3a4 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -1,12 +1,6 @@ About About SourceGit - • Build with - • Chart is rendered by - © 2024 sourcegit-scm - • TextEditor from - • Monospace fonts come from - • Source code can be found at Opensource & Free Git GUI Client Add Worktree What to Checkout: @@ -19,21 +13,19 @@ Track Branch: Tracking remote branch AI Assistant + RE-GENERATE Use AI to generate commit message + APPLY AS COMMIT MESSAGE Patch - Error - Raise errors and refuses to apply the patch - Error All - Similar to 'error', but shows more Patch File: Select .patch file to apply Ignore whitespace changes - No Warn - Turns off the trailing whitespace warning Apply Patch - Warn - Outputs warnings for a few such errors, but applies Whitespace: + Apply Stash + Delete after applying + Reinstate the index's changes + Stash: Archive... Save Archive To: Select archive file path @@ -47,10 +39,10 @@ Blame BLAME ON THIS FILE IS NOT SUPPORTED!!! Checkout ${0}$... - Compare with Branch Compare with HEAD Compare with Worktree Copy Branch Name + Custom Action Delete ${0}$... Delete selected {0} branches Discard all changes @@ -64,9 +56,9 @@ Push ${0}$ Rebase ${0}$ on ${1}$... Rename ${0}$... - Set Tracking Branch - Unset Upstream + Set Tracking Branch... Branch Compare + Invalid upstream! Bytes CANCEL Reset to This Revision @@ -83,7 +75,6 @@ Branch: Local Changes: Discard - Do Nothing Stash & Reapply Cherry Pick Append source to commit message @@ -99,6 +90,7 @@ Local Name: Repository name. Optional. Parent Folder: + Initialize & update submodules Repository URL: CLOSE Editor @@ -147,25 +139,28 @@ Template Content: CUSTOM ACTION Arguments: - ${REPO} - Repository's path; ${SHA} - Selected commit's SHA + ${REPO} - Repository's path; ${BRANCH} - Selected branch; ${SHA} - Selected commit's SHA Executable File: Name: Scope: + Branch Commit Repository + Wait for action exit Email Address Email address GIT Fetch remotes automatically Minute(s) Default Remote - Enable --prune on fetch - Enable --signoff for commit ISSUE TRACKER + Add Sample Gitee Issue Rule + Add Sample Gitee Pull Request Rule Add Sample Github Rule - Add Sample Jira Rule Add Sample GitLab Issue Rule Add Sample GitLab Merge Request Rule + Add Sample Jira Rule + Add Sample Azure DevOps Rule New Rule Issue Regex Expression: Rule Name: @@ -191,16 +186,16 @@ Copy Copy All Text Copy Path - Copy File Name + Copy Full Path Create Branch... Based On: Check out the created branch Local Changes: Discard - Do Nothing Stash & Reapply New Branch Name: Enter branch name. + Spaces will be replaced with dashes. Create Local Branch Create Tag... New Tag At: @@ -224,8 +219,11 @@ You are trying to delete multiple branches at one time. Be sure to double-check before taking action! Delete Remote Remote: + Path: Target: + All children will be removed from list. Confirm Deleting Group + This will only remove it from list, not from disk! Confirm Deleting Repository Delete Submodule Submodule Path: @@ -237,7 +235,9 @@ OLD Copy File Mode Changed + First Difference Ignore Whitespace Change + Last Difference LFS OBJECT CHANGE Next Difference NO CHANGES OR ONLY EOL CHANGES @@ -261,7 +261,7 @@ All local changes in working copy. Changes: Include ignored files - Total {0} changes will be discard + {0} changes will be discarded You can't undo this action!!! Bookmark: New Name: @@ -273,7 +273,7 @@ Fast-Forward (without checkout) Fetch Fetch all remotes - Enable '--force' option + Force override local refs Fetch without tags Remote: Fetch Remote Changes @@ -348,15 +348,14 @@ Track files named '{0}' Track all *{0} files HISTORY - Switch Horizontal/Vertical Layout AUTHOR AUTHOR TIME GRAPH & SUBJECT SHA COMMIT TIME SELECTED {0} COMMITS - Holding 'Ctrl' or 'Shift' to select multiple commits. - Holding ⌘ or ⇧ to select multiple commits. + Hold 'Ctrl' or 'Shift' to select multiple commits. + Hold ⌘ or ⇧ to select multiple commits. TIPS: Keyboard Shortcuts Reference GLOBAL @@ -366,7 +365,7 @@ Go to previous page Go to next page Create new page - Open preference dialog + Open Preferences dialog REPOSITORY Commit staged changes Commit and push staged changes @@ -395,8 +394,8 @@ Path: Cherry-Pick in progress. Processing commit - Merge request in progress. - Operating + Merge in progress. + Merging Rebase in progress. Stopped at Revert in progress. @@ -419,8 +418,8 @@ Move Repository Node Select parent node for: Name: - Git has NOT been configured. Please to go [Preference] and configure it first. - Open App Data Dir + Git has NOT been configured. Please to go [Preferences] and configure it first. + Open Data Storage Directory Open with... Optional. Create New Page @@ -433,6 +432,7 @@ Paste Just now {0} minutes ago + 1 hour ago {0} hours ago Yesterday {0} days ago @@ -440,69 +440,74 @@ {0} months ago Last year {0} years ago - Preference - AI - Analyze Diff Prompt - API Key - Generate Subject Prompt - Model - Name - Server - APPEARANCE - Default Font - Font Size - Default - Editor - Monospace Font - Only use monospace font in text editor - Theme - Theme Overrides - Use fixed tab width in titlebar - Use native window frame - DIFF/MERGE TOOL - Install Path - Input path for diff/merge tool - Tool - GENERAL - Check for updates on startup - Language - History Commits - Show author time instead of commit time in graph - Show children in the commit details - Subject Guide Length - GIT - Enable Auto CRLF - Default Clone Dir - User Email - Global git user email - Install Path - User Name - Global git user name - Git version - Git (>= 2.23.0) is required by this app - GPG SIGNING - Commit GPG signing - Tag GPG signing - GPG Format - Program Install Path - Input path for installed gpg program - User Signing Key - User's gpg signing key - INTEGRATION - SHELL/TERMINAL - Shell/Terminal - Path + Preferences + AI + Analyze Diff Prompt + API Key + Generate Subject Prompt + Model + Name + Server + Enable Streaming + APPEARANCE + Default Font + Editor Tab Width + Font Size + Default + Editor + Monospace Font + Use monospace font only in text editor + Theme + Theme Overrides + Use fixed tab width in titlebar + Use native window frame + DIFF/MERGE TOOL + Install Path + Input path for diff/merge tool + Tool + GENERAL + Check for updates on startup + Date Format + Language + History Commits + Show author time instead of commit time in graph + Show children in the commit details + Show tags in commit graph + Subject Guide Length + GIT + Enable Auto CRLF + Default Clone Dir + User Email + Global git user email + Enable --prune on fetch + Install Path + Enable HTTP SSL Verify + User Name + Global git user name + Git version + Git (>= 2.23.0) is required by this app + GPG SIGNING + Commit GPG signing + Tag GPG signing + GPG Format + Program Install Path + Input path for installed gpg program + User Signing Key + User's gpg signing key + INTEGRATION + SHELL/TERMINAL + Shell/Terminal + Path Prune Remote Target: Prune Worktrees - Prune worktree information in `$GIT_DIR/worktrees` + Prune worktree information in `$GIT_COMMON_DIR/worktrees` Pull - Branch: + Remote Branch: Fetch all branches Into: Local Changes: Discard - Do Nothing Stash & Reapply Fetch without tags Remote: @@ -562,23 +567,29 @@ Unset Hide in commit graph Filter in commit graph - Switch Order Mode - Commit Date (--date-order) - Topologically (--topo-order) + Enable '--first-parent' Option + LAYOUT + Horizontal + Vertical + COMMITS ORDER + Commit Date + Topologically LOCAL BRANCHES Navigate to HEAD - Enable '--first-parent' Option Create Branch + CLEAR NOTIFICATIONS + Only highlight current branch in graph Open in {0} Open in External Tools Refresh REMOTES ADD REMOTE Search Commit + Author + Committer File Message SHA - Author & Committer Current Branch Show Tags as Tree SKIP @@ -588,7 +599,12 @@ UPDATE SUBMODULE TAGS NEW TAG + By Creator Date + By Name (Ascending) + By Name (Descending) + Sort Open in Terminal + Use relative time in histories WORKTREES ADD WORKTREE PRUNE @@ -616,6 +632,10 @@ Skip This Version Software Update There are currently no updates available. + Set Tracking Branch + Branch: + Unset upstream + Upstream: Copy SHA Go to Squash Commits @@ -624,6 +644,8 @@ Private SSH key store path START Stash + Auto-restore after stashing + Your working files remain unchanged, but a stash is saved. Include untracked files Keep staged files Message: @@ -633,7 +655,7 @@ Stash Local Changes Apply Drop - Pop + Save as Patch... Drop Stash Drop: STASHES @@ -703,6 +725,7 @@ INCLUDE UNTRACKED FILES NO RECENT INPUT MESSAGES NO COMMIT TEMPLATES + SignOff STAGED UNSTAGE UNSTAGE ALL diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index cf0f05b5..d8018097 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -4,12 +4,6 @@ Acerca de Acerca de SourceGit - • Construido con - • El gráfico es renderizado por - © 2024 sourcegit-scm - • Editor de texto de - • Las fuentes monoespaciadas provienen de - • El código fuente se puede encontrar en Cliente Git GUI de código abierto y gratuito Agregar Worktree Qué Checkout: @@ -22,21 +16,19 @@ Rama de Seguimiento: Seguimiento de rama remota Asistente OpenAI + RE-GENERAR Usar OpenAI para generar mensaje de commit - Aplicar Patch - Error - Genera errores y se niega a aplicar el patch - Error Todo - Similar a 'error', pero muestra más - Archivo Patch: + APLICAR CÓMO MENSAJE DE COMMIT + Aplicar Parche + Archivo del Parche: Seleccionar archivo .patch para aplicar Ignorar cambios de espacios en blanco - Sin Advertencia - Desactiva la advertencia de espacios en blanco al final - Aplicar Patch - Advertencia - Genera advertencias para algunos de estos errores, pero aplica + Aplicar Parche Espacios en Blanco: + Aplicar Stash + Borrar después de aplicar + Restaurar los cambios del índice + Stash: Archivar... Guardar Archivo en: Seleccionar ruta del archivo @@ -50,10 +42,10 @@ Blame ¡BLAME EN ESTE ARCHIVO NO SOPORTADO! Checkout ${0}$... - Comparar con Rama Comparar con HEAD Comparar con Worktree Copiar Nombre de Rama + Acción personalizada Eliminar ${0}$... Eliminar {0} ramas seleccionadas Descartar todos los cambios @@ -61,14 +53,15 @@ Fetch ${0}$ en ${1}$... Git Flow - Finalizar ${0}$ Merge ${0}$ en ${1}$... + Hacer merge de las ramas {0} seleccionadas hacia la rama actual Pull ${0}$ Pull ${0}$ en ${1}$... Push ${0}$ Rebase ${0}$ en ${1}$... Renombrar ${0}$... - Establecer Rama de Seguimiento - Desestablecer Upstream + Establecer Rama de Seguimiento... Comparar Ramas + ¡Upstream inválido! Bytes CANCELAR Resetear a Esta Revisión @@ -85,7 +78,6 @@ Rama: Cambios Locales: Descartar - No Hacer Nada Stash & Reaplicar Cherry Pick Añadir fuente al mensaje de commit @@ -101,6 +93,7 @@ Nombre Local: Nombre del repositorio. Opcional. Carpeta Padre: + Inicializar y actualizar submodulos URL del Repositorio: CERRAR Editor @@ -113,13 +106,13 @@ Copiar SHA Acción personalizada Rebase Interactivo ${0}$ hasta Aquí - Abrir en el Navegador - Copiar Enlace + Merge a ${0}$ + Merge ... Rebase ${0}$ hasta Aquí Reset ${0}$ hasta Aquí Revertir Commit Reescribir - Guardar como Patch... + Guardar como Parche... Squash en Parent Squash Commits Hijos hasta Aquí CAMBIOS @@ -153,19 +146,22 @@ Archivo Ejecutable: Nombre: Alcance: + Rama Commit Repositorio + Esperar la acción de salida Dirección de Email Dirección de email GIT Fetch remotos automáticamente Minuto(s) Remoto por Defecto - Habilitar --prune para fetch - Habilitar --signoff para commit SEGUIMIENTO DE INCIDENCIAS + Añadir Regla de Ejemplo para Incidencias de Gitee + Añadir Regla de Ejemplo para Pull Requests de Gitee Añadir Regla de Ejemplo para Github Añadir Regla de Ejemplo para Jira + Añadir Regla de Ejemplo para Azure DevOps Añadir Regla de Ejemplo para Incidencias de GitLab Añadir Regla de Ejemplo para Merge Requests de GitLab Nueva Regla @@ -193,16 +189,15 @@ Copiar Copiar Todo el Texto Copiar Ruta - Copiar Nombre del Archivo Crear Rama... Basado En: Checkout de la rama creada Cambios Locales: Descartar - No Hacer Nada Stash & Reaplicar Nombre de la Nueva Rama: Introduzca el nombre de la rama. + Los espacios serán reemplazados con guiones. Crear Rama Local Crear Etiqueta... Nueva Etiqueta En: @@ -226,8 +221,11 @@ Estás intentando eliminar múltiples ramas a la vez. ¡Asegúrate de revisar antes de tomar acción! Eliminar Remoto Remoto: + Ruta: Destino: + Todos los hijos serán removidos de la lista. Confirmar Eliminación de Grupo + ¡Esto solo lo removera de la lista, no del disco! Confirmar Eliminación de Repositorio Eliminar Submódulo Ruta del Submódulo: @@ -239,12 +237,14 @@ ANTIGUO Copiar Modo de Archivo Cambiado + Primera Diferencia Ignorar Cambio de Espacios en Blanco + Última Diferencia CAMBIO DE OBJETO LFS Siguiente Diferencia SIN CAMBIOS O SOLO CAMBIOS DE EOL Diferencia Anterior - Guardar como Patch + Guardar como Parche Mostrar símbolos ocultos Diferencia Lado a Lado SUBMÓDULO @@ -252,6 +252,7 @@ Intercambiar Resaltado de Sintaxis Ajuste de Línea + Habilitar navegación en bloque Abrir en Herramienta de Merge Mostrar Todas las Líneas Disminuir Número de Líneas Visibles @@ -283,7 +284,8 @@ Descartar {0} archivos... Descartar Cambios en Línea(s) Seleccionada(s) Abrir Herramienta de Merge Externa - Guardar Como Patch... + Resolver usando ${0}$ + Guardar como Parche... Stage Stage {0} archivos Stage Cambios en Línea(s) Seleccionada(s) @@ -348,7 +350,6 @@ Seguir archivos llamados '{0}' Seguir todos los archivos *{0} Historias - Cambiar a Disposición Horizontal/Vertical AUTOR HORA DEL AUTOR GRÁFICO & ASUNTO @@ -361,11 +362,12 @@ Referencia de Atajos de Teclado GLOBAL Cancelar popup actual + Clonar repositorio nuevo Cerrar página actual Ir a la página anterior Ir a la siguiente página Crear nueva página - Abrir diálogo de preferencias + Abrir diálogo de preferencias REPOSITORIO Commit cambios staged Commit y push cambios staged @@ -393,17 +395,28 @@ Inicializar Repositorio Ruta: Cherry-Pick en progreso. + Procesando commit Merge en progreso. + Haciendo merge Rebase en progreso. + Pausado en Revert en progreso. + Haciendo revert del commit Rebase Interactivo Rama Objetivo: En: + Abrir en el Navegador + Copiar Enlace ERROR AVISO Merge Rama En: Opción de Merge: + Rama Fuente: + Merge (Multiplo) + Commit todos los cambios + Estrategia: + Destino: Mover Nodo del Repositorio Seleccionar nodo padre para: Nombre: @@ -421,6 +434,7 @@ Pegar Justo ahora Hace {0} minutos + Hace 1 hora Hace {0} horas Ayer Hace {0} días @@ -428,70 +442,75 @@ Hace {0} meses Último año Hace {0} años - Preferencias - Opciones Avanzadas - OPEN AI - Analizar Diff Prompt - Clave API - Generar Subject Prompt - Modelo - Nombre - Servidor - APARIENCIA - Fuente por defecto - Tamaño de fuente - Por defecto - Editor - Fuente Monospace - Usar solo fuente monospace en el editor de texto - Tema - Sobreescritura de temas - Usar ancho de pestaña fijo en la barra de título - Usar marco de ventana nativo - HERRAMIENTA DIFF/MERGE - Ruta de instalación - Introducir ruta para la herramienta diff/merge - Herramienta - GENERAL - Buscar actualizaciones al iniciar - Idioma - Commits en el historial - Mostrar hora del autor en lugar de la hora del commit en el gráfico - Mostrar hijos en los detalles de commit - Longitud de la guía del asunto - GIT - Habilitar Auto CRLF - Directorio de clonado por defecto - Email de usuario - Email global del usuario git - Ruta de instalación - Nombre de usuario - Nombre global del usuario git - Versión de Git - Se requiere Git (>= 2.23.0) para esta aplicación - FIRMA GPG - Firma GPG en commit - Firma GPG en etiqueta - Formato GPG - Ruta de instalación del programa - Introducir ruta para el programa gpg instalado - Clave de firma del usuario - Clave de firma gpg del usuario - INTEGRACIÓN - SHELL/TERMINAL - Shell/Terminal - Ruta + Preferencias + Opciones Avanzadas + OPEN AI + Analizar Diff Prompt + Clave API + Generar Subject Prompt + Modelo + Nombre + Servidor + Activar Transmisión + APARIENCIA + Fuente por defecto + Ancho de la Pestaña del Editor + Tamaño de fuente + Por defecto + Editor + Fuente Monospace + Usar solo fuente monospace en el editor de texto + Tema + Sobreescritura de temas + Usar ancho de pestaña fijo en la barra de título + Usar marco de ventana nativo + HERRAMIENTA DIFF/MERGE + Ruta de instalación + Introducir ruta para la herramienta diff/merge + Herramienta + GENERAL + Buscar actualizaciones al iniciar + Formato de Fecha + Idioma + Commits en el historial + Mostrar hora del autor en lugar de la hora del commit en el gráfico + Mostrar hijos en los detalles de commit + Mostrar etiquetas en el gráfico de commit + Longitud de la guía del asunto + GIT + Habilitar Auto CRLF + Directorio de clonado por defecto + Email de usuario + Email global del usuario git + Habilitar --prune para fetch + Ruta de instalación + Habilitar verificación HTTP SSL + Nombre de usuario + Nombre global del usuario git + Versión de Git + Se requiere Git (>= 2.23.0) para esta aplicación + FIRMA GPG + Firma GPG en commit + Firma GPG en etiqueta + Formato GPG + Ruta de instalación del programa + Introducir ruta para el programa gpg instalado + Clave de firma del usuario + Clave de firma gpg del usuario + INTEGRACIÓN + SHELL/TERMINAL + Shell/Terminal + Ruta Podar Remoto Destino: Podar Worktrees - Podar información de worktree en `$GIT_DIR/worktrees` + Podar información de worktree en `$GIT_COMMON_DIR/worktrees` Pull - Rama: + Rama Remota: Fetch todas las ramas En: Cambios Locales: Descartar - No Hacer Nada Stash & Reaplicar Fetch sin etiquetas Remoto: @@ -551,32 +570,44 @@ Desestablecer Ocultar en el Gráfico de Commits Filtrar en el Gráfico de Commits - Cambiar Modo de Ordenación - Fecha de Commit (--date-order) - Topológicamente (--topo-order) + Habilitar Opción '--first-parent' + DISPOSICIÓN + Horizontal + Vertical + ORDEN DE COMMITS + Fecha de Commit + Topológicamente RAMAS LOCALES Navegar a HEAD - Habilitar Opción '--first-parent' Crear Rama + LIMPIAR NOTIFICACIONES + Resaltar solo la rama actual en el gráfico Abrir en {0} Abrir en Herramientas Externas Refrescar REMOTOS AÑADIR REMOTO Buscar Commit + Autor + Committer Archivo Mensaje SHA - Autor & Committer Rama Actual Mostrar Etiquetas como Árbol + OMITIR Estadísticas SUBMÓDULOS AÑADIR SUBMÓDULO ACTUALIZAR SUBMÓDULO ETIQUETAS NUEVA ETIQUETA + Por Fecha de Creación + Por Nombre (Ascendiente) + Por Nombre (Descendiente) + Ordenar Abrir en Terminal + Usar tiempo relativo en las historias WORKTREES AÑADIR WORKTREE PRUNE @@ -594,7 +625,7 @@ Ejecutando. Por favor espera... GUARDAR Guardar Como... - ¡El patch se ha guardado exitosamente! + ¡El parche se ha guardado exitosamente! Escanear Repositorios Directorio Raíz: Buscar Actualizaciones... @@ -604,6 +635,10 @@ Omitir Esta Versión Actualización de Software Actualmente no hay actualizaciones disponibles. + Establecer Rama de Seguimiento + Rama: + Desestablecer upstream + Upstream: Copiar SHA Ir a Squash Commits @@ -612,6 +647,8 @@ Ruta de almacenamiento de la clave privada SSH INICIAR Stash + Restaurar automáticamente después del stashing + Tus archivos de trabajo permanecen sin cambios, pero se guarda un stash. Incluir archivos no rastreados Mantener archivos staged Mensaje: @@ -621,7 +658,7 @@ Stash Cambios Locales Aplicar Eliminar - Pop + Guardar como Parche... Eliminar Stash Eliminar: Stashes @@ -677,7 +714,7 @@ Ignorar archivos *{0} en la misma carpeta Ignorar archivos en la misma carpeta Ignorar solo este archivo - Enmendar (Amend) + Enmendar Puedes stagear este archivo ahora. COMMIT COMMIT & PUSH @@ -691,6 +728,7 @@ INCLUIR ARCHIVOS NO RASTREADOS NO HAY MENSAJES DE ENTRADA RECIENTES NO HAY PLANTILLAS DE COMMIT + Firmar STAGED UNSTAGE UNSTAGE TODO diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 20237bb9..70d0af22 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -4,15 +4,9 @@ À propos À propos de SourceGit - • Compilé avec - • Le graphique est rendu par - © 2024 sourcegit-scm - • TextEditor de - • Les polices Monospace proviennent de - • Le code source est disponible sur Client Git Open Source et Gratuit Ajouter un Worktree - What to Checkout: + Que récupérer : Créer une nouvelle branche Branche existante Emplacement : @@ -24,18 +18,10 @@ Assistant IA Utiliser l'IA pour générer un message de commit Appliquer - Erreur - Soulever les erreurs et refuser d'appliquer le patch - Toutes les erreurs - Similaire à 'Erreur', mais plus détaillé Fichier de patch : Selectionner le fichier .patch à appliquer Ignorer les changements d'espaces blancs - Pas d'avertissement - Désactiver l'avertissement sur les espaces blancs terminaux Appliquer le patch - Avertissement - Affiche des avertissements pour ce type d'erreurs tout en appliquant le patch Espaces blancs : Archiver... Enregistrer l'archive sous : @@ -49,8 +35,7 @@ FICHIER BINAIRE NON SUPPORTÉ !!! Blâme LE BLÂME SUR CE FICHIER N'EST PAS SUPPORTÉ!!! - Checkout ${0}$... - Comparer avec la branche + Récupérer ${0}$... Comparer avec HEAD Comparer avec le worktree Copier le nom de la branche @@ -61,13 +46,13 @@ Fetch ${0}$ vers ${1}$... Git Flow - Terminer ${0}$ Fusionner ${0}$ dans ${1}$... + Fusionner les {0} branches sélectionnées dans celle en cours Tirer ${0}$ Tirer ${0}$ dans ${1}$... Pousser ${0}$ Rebaser ${0}$ sur ${1}$... Renommer ${0}$... - Définir la branche de suivi - Ne plus suivre la branche distante + Définir la branche de suivi... Comparer les branches Octets ANNULER @@ -78,19 +63,20 @@ Afficher comme liste de dossiers/fichiers Afficher comme liste de chemins Afficher comme arborescence - Checkout Branch - Checkout ce commit + Récupérer la branche + Récupérer ce commit Commit : - Avertissement: un checkout vers un commit aboutiera vers un HEAD détaché + Avertissement: une récupération vers un commit aboutiera vers un HEAD détaché Branche : Changements locaux : Annuler - Ne rien faire Mettre en stash et réappliquer Cherry-Pick de ce commit + Ajouter la source au message de commit Commit : Commit tous les changements Ligne principale : + Habituellement, on ne peut pas cherry-pick un commit car on ne sait pas quel côté devrait être considéré comme principal. Cette option permet de rejouer les changements relatifs au parent spécifié. Cherry Pick Supprimer les stashes Vous essayez de supprimer tous les stashes. Êtes-vous sûr de vouloir continuer ? @@ -103,14 +89,17 @@ URL du dépôt : FERMER Éditeur - Changer de commit + Récupérer ce commit Cherry-Pick ce commit + Cherry-Pick ... Comparer avec HEAD Comparer avec le worktree Copier les informations Copier le SHA Action personnalisée Rebase interactif de ${0}$ ici + Fusionner dans ${0}$ + Fusionner ... Rebaser ${0}$ ici Réinitialiser ${0}$ ici Annuler le commit @@ -122,6 +111,7 @@ Rechercher les changements... FICHIERS Fichier LFS + Rechercher des fichiers... Sous-module INFORMATIONS AUTEUR @@ -156,11 +146,12 @@ Fetch les dépôts distants automatiquement minute(s) Dépôt par défaut - Activer --prune pour fetch - Activer --signoff pour commit SUIVI DES PROBLÈMES + Ajouter une règle d'exemple Gitee + Ajouter une règle d'exemple pour Pull Request Gitee Ajouter une règle d'exemple Github Ajouter une règle d'exemple Jira + Ajouter une règle d'exemple Azure DevOps Ajouter une règle d'exemple pour Incidents GitLab Ajouter une règle d'exemple pour Merge Request GitLab Nouvelle règle @@ -187,14 +178,12 @@ Type de Changement : Copier Copier tout le texte - Copier le nom de fichier Copier le chemin Créer une branche... Basé sur : - Passer à la branche créée + Récupérer la branche créée Changements locaux : Rejeter - Ne rien faire Stash & Réappliquer Nom de la nouvelle branche : Entrez le nom de la branche. @@ -248,6 +237,7 @@ Coloration syntaxique Retour à la ligne Ouvrir dans l'outil de fusion + Activer la navigation par blocs Voir toutes les lignes Réduit le nombre de ligne visibles Augmente le nombre de ligne visibles @@ -266,9 +256,10 @@ Éditer le dépôt sélectionné Lancer action personnalisée Nom de l'action : - Fast-Forward (sans checkout) + Fast-Forward (sans récupération) Fetch Fetch toutes les branches distantes + Outrepasser les vérifications de refs Fetch sans les tags Remote : Récupérer les changements distants @@ -277,6 +268,7 @@ Rejeter {0} fichiers... Rejeter les changements dans les lignes sélectionnées Ouvrir l'outil de fusion externe + Résoudre en utilisant ${0}$ Enregistrer en tant que patch... Indexer Indexer {0} fichiers @@ -330,19 +322,18 @@ Verrous LFS Déverouiller Forcer le déverouillage - Prune + Elaguer Lancer `git lfs prune` pour supprimer les anciens fichier LFS du stockage local Pull Pull les objets LFS - Lancer `git lfs pull` pour télécharger tous les fichier Git LFS de la référence actuelle & checkout - Push - Push les objets LFS + Lancer `git lfs pull` pour télécharger tous les fichier Git LFS de la référence actuelle & récupérer + Pousser + Pousser les objets LFS Transférer les fichiers volumineux en file d'attente vers le point de terminaison Git LFS Dépôt : Suivre les fichiers appelés '{0}' Suivre tous les fichiers *{0} Historique - Basculer entre dispositions Horizontal/Vertical AUTEUR HEURE DE L'AUTEUR GRAPHE & SUJET @@ -355,11 +346,12 @@ Référence des raccourcis clavier GLOBAL Annuler le popup en cours + Cloner un nouveau dépôt Fermer la page en cours Aller à la page précédente Aller à la page suivante Créer une nouvelle page - Ouvrir le dialogue des préférences + Ouvrir le dialogue des préférences DÉPÔT Commit les changements de l'index Commit et pousser les changements de l'index @@ -381,15 +373,15 @@ Trouver la prochaine correspondance Trouver la correspondance précédente Ouvrir le panneau de recherche - Stage + Indexer Retirer de l'index Rejeter Initialiser le repository Chemin : Cherry-Pick en cours. - Merge request in progress. - Rebase in progress. - Revert in progress. + Merge request en cours. + Rebase en cours. + Annulation en cours. Rebase interactif Branche cible : Sur : @@ -412,11 +404,12 @@ Fermer l'onglet Fermer les autres onglets Fermer les onglets à droite - Copy Repository Path + Copier le chemin vers le dépôt Dépôts - Paste + Coller A l'instant il y a {0} minutes + il y a 1 heure il y a {0} heures Hier il y a {0} jours @@ -424,83 +417,83 @@ il y a {0} mois L'an dernier il y a {0} ans - Préférences - IA - Analyser Diff Prompt - Clé d'API - Générer le sujet de Prompt - Modèle - Nom - Serveur - APPARENCE - Police par défaut - Taille de police par défaut - Taille de police de l'éditeur - Police monospace - N'utiliser que des polices monospace pour l'éditeur de texte - Thème - Dérogations de thème - Utiliser des onglets de taille fixe dans la barre de titre - Utiliser un cadre de fenêtre natif - OUTIL DIFF/MERGE - Chemin d'installation - Saisir le chemin d'installation de l'outil diff/merge - Outil - GÉNÉRAL - Vérifier les mises à jour au démarrage - Language - Historique de commits - Afficher l'heure de l'auteur au lieu de l'heure de validation dans le graphique - Guide de longueur du sujet - GIT - Activer auto CRLF - Répertoire de clônage par défaut - E-mail utilsateur - E-mail utilsateur global - Chemin d'installation - Nom d'utilisateur - Nom d'utilisateur global - Version de Git - Cette application requière Git (>= 2.23.0) - SIGNATURE GPG - Signature GPG de commit - Signature GPG de tag - Format GPG - Chemin d'installation du programme - Saisir le chemin d'installation vers le programme GPG - Clé de signature de l'utilisateur - Clé de signature GPG de l'utilisateur - INTEGRATION - SHELL/TERMINAL - Shell/Terminal - Chemin + Préférences + IA + Analyser Diff Prompt + Clé d'API + Générer le sujet de Prompt + Modèle + Nom + Serveur + APPARENCE + Police par défaut + Taille de police par défaut + Taille de police de l'éditeur + Police monospace + N'utiliser que des polices monospace pour l'éditeur de texte + Thème + Dérogations de thème + Utiliser des onglets de taille fixe dans la barre de titre + Utiliser un cadre de fenêtre natif + OUTIL DIFF/MERGE + Chemin d'installation + Saisir le chemin d'installation de l'outil diff/merge + Outil + GÉNÉRAL + Vérifier les mises à jour au démarrage + Language + Historique de commits + Afficher l'heure de l'auteur au lieu de l'heure de validation dans le graphique + Guide de longueur du sujet + GIT + Activer auto CRLF + Répertoire de clônage par défaut + E-mail utilsateur + E-mail utilsateur global + Activer --prune pour fetch + Chemin d'installation + Nom d'utilisateur + Nom d'utilisateur global + Version de Git + Cette application requière Git (>= 2.23.0) + SIGNATURE GPG + Signature GPG de commit + Signature GPG de tag + Format GPG + Chemin d'installation du programme + Saisir le chemin d'installation vers le programme GPG + Clé de signature de l'utilisateur + Clé de signature GPG de l'utilisateur + INTEGRATION + SHELL/TERMINAL + Shell/Terminal + Chemin Élaguer une branche distant Cible : Élaguer les Worktrees - Élaguer les information de worktree dans `$GIT_DIR/worktrees` + Élaguer les information de worktree dans `$GIT_COMMON_DIR/worktrees` Pull - Branche : + Branche distante : Fetch toutes les branches Dans : Changements locaux : Rejeter - Ne rien faire Stash & Réappliquer Fetch sans les tags Dépôt distant : Pull (Fetch & Merge) Utiliser rebase au lieu de merge - Push + Pousser Assurez-vous que les submodules ont été poussés - Force push + Poussage forcé Branche locale : Dépôt distant : Pousser les changements vers le dépôt distant Branche distante : Définir comme branche de suivi - Push tous les tags - Push Tag To Remote - Push tous les dépôts distants + Pousser tous les tags + Pousser les tags vers le dépôt distant + Pousser tous les dépôts distants Dépôt distant : Tag : Quitter @@ -520,7 +513,7 @@ Editer... Fetch Ouvrir dans le navigateur - Prune + Elaguer Confirmer la suppression du Worktree Activer l'option `--force` Cible : @@ -530,7 +523,7 @@ Branche : ABORT Fetch automatique des changements depuis les dépôts... - Nettoyage(GC & Prune) + Nettoyage(GC & Elaguage) Lancer `git gc` pour ce repository. Tout effacer Configurer ce repository @@ -539,20 +532,22 @@ Activer l'option '--reflog' Ouvrir dans l'explorateur de fichiers Rechercher Branches/Tags/Submodules + Activer l'option '--first-parent' BRANCHES LOCALES Naviguer vers le HEAD - Activer l'option '--first-parent' Créer une branche + Mettre la branche courante en surbrillance dans le graph Ouvrir dans {0} Ouvrir dans un outil externe Rafraîchir DEPOTS DISTANTS AJOUTER DEPOT DISTANT Rechercher un commit + Auteur + Committer Fichier Message SHA - Auteur & Committer Branche actuelle Voir les Tags en tant qu'arbre Statistiques @@ -564,16 +559,16 @@ Ouvrir dans un terminal WORKTREES AJOUTER WORKTREE - PRUNE + ELAGUER URL du repository Git Reset branche actuelle à la révision Reset Mode: Déplacer vers : Branche actuelle : Ouvrir dans l'explorateur de fichier - Revert le Commit + Annuler le Commit Commit : - Commit les changements du revert + Commit les changements de l'annulation Reformuler le message de commit Utiliser 'Maj+Entrée' pour insérer une nouvelle ligne. 'Entrée' est la touche pour valider En exécution. Veuillez patienter... @@ -591,47 +586,46 @@ Copier le SHA Squash Commits Dans : - SSH Private Key: - Private SSH key store path + Clé privée SSH : + Chemin du magasin de clés privées SSH START Stash - Include untracked files - Garder les fichiers staged + Inclure les fichiers non-suivis + Garder les fichiers indexés Message : Optionnel. Nom de ce stash - Seulement les changements staged - Les modifications staged et unstaged des fichiers sélectionnés seront stockées!!! + Seulement les changements indexés + Les modifications indexées et non-indexées des fichiers sélectionnés seront stockées!!! Stash les changements locaux Appliquer Effacer - Extraire Effacer le Stash Effacer : Stashes CHANGEMENTS STASHES - Statistics + Statistiques COMMITS COMMITTER - MONTH - WEEK + MOIS + SEMAINE COMMITS: - AUTHORS: + AUTEURS : APERCU - SUBMODULES - Add Submodule - Copy Relative Path - Fetch nested submodules - Open Submodule Repository + SOUS-MODULES + Ajouter un sous-module + Copier le chemin relatif + Fetch les sous-modules imbriqués + Ouvrir le dépôt de sous-module Relative Path: Relative folder to store this module. - Delete Submodule + Supprimer le sous-module OK - Copy Tag Name + Copier le nom du Tag Copier le message du tag - Delete ${0}$... + Supprimer ${0}$... Fusionner ${0}$ dans ${1}$... - Push ${0}$... + Pousser ${0}$... URL : Actualiser les sous-modules Tous les sous-modules @@ -663,10 +657,10 @@ Amender Vous pouvez indexer ce fichier. COMMIT - COMMIT & PUSH + COMMIT & POUSSER Modèles/Historiques Trigger click event - Stage tous les changements et commit + Indexer tous les changements et commit Un commit vide a été détecté ! Voulez-vous continuer (--allow-empty) ? CONFLITS DÉTECTÉS LES CONFLITS DE FICHIER SONT RÉSOLUS @@ -689,4 +683,68 @@ Verrouiller Supprimer Déverrouiller + RE-GÉNÉRER + APPLIQUER COMME MESSAGE DE COMMIT + Appliquer le Stash + Supprimer après l'application + Rétablir les modifications de l'index + Stash: + Action personnalisée + Branche en amont invalide! + Initialiser et mettre à jour les sous-modules + Branche + Attendre la fin de l'action + Les espaces seront remplacés par des tirets. + Chemin: + Tous les enfants seront retirés de la liste. + Cela le supprimera uniquement de la liste, pas du disque ! + Première différence + Dernière différence + Traitement du commit + Fusionnement + Arrêté à + Annulation du commit + Source: + Fusionner (Plusieurs) + Commit tous les changement + Stratégie: + Cibles: + Activer le streaming + Largeur de tab dans l'éditeur + Taille de police + Défaut + Éditeur + Format de date + Afficher les enfants dans les détails du commit + Afficher les tags dans le graphique des commits + Activer la vérification HTTP SSL + Actions personnalisées + Visibilité dans le graphique + Réinitialiser + Cacher dans le graphique des commits + Filtrer dans le graphique des commits + DISPOSITION + Horizontal + Vertical + ORDRE DES COMMITS + Date du commit + Topologiquement + EFFACER LES NOTIFICATIONS + PASSER + Par date de créateur + Par nom (Croissant) + Par nom (Décroissant) + Trier + Utiliser le temps relatif dans les historiques + Analyser les repositories + Définir la branche suivie + Branche: + Retirer la branche amont + En amont: + Aller sur + Restauration automatique après le stashing + Vos fichiers de travail restent inchangés, mais une sauvegarde est enregistrée. + Sauvegarder en tant que patch... + Commit (Modifier) + SignOff diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index 18754c8e..85038d9e 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -4,15 +4,9 @@ Informazioni Informazioni su SourceGit - • Creato con - • Il grafico è reso da - © 2024 sourcegit-scm - • Editor di testo da - • I font monospaziati provengono da - • Il codice sorgente è disponibile su Client GUI Git open source e gratuito Aggiungi Worktree - Cosa fare il checkout: + Di cosa fare il checkout: Branch esistente Crea nuovo branch Posizione: @@ -22,21 +16,19 @@ Traccia Branch: Traccia branch remoto Assistente AI + RIGENERA Usa AI per generare il messaggio di commit + APPLICA COME MESSAGGIO DI COMMIT Applica - Errore - Genera errori e si rifiuta di applicare la patch - Tutti gli errori - Simile a 'errore', ma mostra di più File Patch: Seleziona file .patch da applicare Ignora modifiche agli spazi - Nessun avviso - Disattiva l'avviso sugli spazi finali Applica Patch - Avviso - Mostra avvisi per alcuni errori, ma applica comunque Spazi: + Applica lo stash + Rimuovi dopo aver applicato + Ripristina le modifiche all'indice + Stash: Archivia... Salva Archivio In: Seleziona il percorso del file archivio @@ -50,10 +42,10 @@ Attribuisci L'ATTRIBUZIONE SU QUESTO FILE NON È SUPPORTATA!!! Checkout ${0}$... - Confronta con Branch Confronta con HEAD Confronta con Worktree Copia Nome Branch + Azione personalizzata Elimina ${0}$... Elimina i {0} branch selezionati Scarta tutte le modifiche @@ -61,21 +53,22 @@ Recupera ${0}$ in ${1}$... Git Flow - Completa ${0}$ Unisci ${0}$ in ${1}$... - Recupera ${0}$ - Recupera ${0}$ in ${1}$... + Unisci i {0} branch selezionati in quello corrente + Scarica ${0}$ + Scarica ${0}$ in ${1}$... Invia ${0}$ Riallinea ${0}$ su ${1}$... Rinomina ${0}$... - Imposta Branch di Tracciamento - Rimuovi Tracciamento + Imposta Branch di Tracciamento... Confronto Branch + Upstream non valido Byte ANNULLA Ripristina Questa Revisione Ripristina la Revisione Padre Genera messaggio di commit CAMBIA MODALITÀ DI VISUALIZZAZIONE - Mostra come elenco di file e directory + Mostra come elenco di file e cartelle Mostra come elenco di percorsi Mostra come albero del filesystem Checkout Branch @@ -85,14 +78,13 @@ Branch: Modifiche Locali: Scarta - Non fare nulla - Stash e Ripristina + Stasha e Ripristina Cherry Pick Aggiungi sorgente al messaggio di commit Commit(s): Conferma tutte le modifiche Mainline: - Di solito non è possibile cherry-pick su una fusione perché non si sa quale lato della fusione deve essere considerato il mainline. Questa opzione consente di riprodurre la modifica relativa al genitore specificato. + Di solito non è possibile fare cherry-pick sdi una unione perché non si sa quale lato deve essere considerato il mainline. Questa opzione consente di riprodurre la modifica relativa al genitore specificato. Cancella Stash Stai per cancellare tutti gli stash. Sei sicuro di voler continuare? Clona Repository Remoto @@ -101,6 +93,7 @@ Nome Locale: Nome del repository. Facoltativo. Cartella Principale: + Inizializza e aggiorna i sottomoduli URL del Repository: CHIUDI Editor @@ -112,22 +105,26 @@ Copia Info Copia SHA Azione Personalizzata - Rebase Interattivo ${0}$ fino a Qui + Riallinea Interattivamente ${0}$ fino a Qui + Unisci a ${0}$ + Unisci ... Riallinea ${0}$ fino a Qui Ripristina ${0}$ fino a Qui Annulla Commit Modifica Salva come Patch... - Unisci al Genitore - Unisci Commit Figli fino a Qui + Compatta nel Genitore + Compatta Commit Figli fino a Qui MODIFICHE Cerca Modifiche... FILE File LFS + Cerca File... Sottomodulo INFORMAZIONI AUTORE MODIFICATO + FIGLI CHI HA COMMITTATO Controlla i riferimenti che contengono questo commit IL COMMIT È CONTENUTO DA @@ -149,29 +146,32 @@ File Eseguibile: Nome: Ambito: + Branch Commit Repository + Attendi la fine dell'azione Indirizzo Email Indirizzo email GIT Recupera automaticamente i remoti Minuto/i Remoto Predefinito - Abilita --prune durante il fetch - Abilita --signoff per i commit TRACCIAMENTO ISSUE - Aggiungi Regola Esempio per GitHub - Aggiungi Regola Esempio per Jira - Aggiungi Regola Esempio per Issue GitLab - Aggiungi Regola Esempio per Merge Request GitLab + Aggiungi una regola di esempio per un Issue Gitee + Aggiungi una regola di esempio per un Pull Request Gitee + Aggiungi una regola di esempio per GitHub + Aggiungi una regola di esempio per Jira + Aggiungi una regola di esempio per Azure DevOps + Aggiungi una regola di esempio per Issue GitLab + Aggiungi una regola di esempio per una Merge Request GitLab Nuova Regola Espressione Regex Issue: Nome Regola: URL Risultato: Utilizza $1, $2 per accedere ai valori dei gruppi regex. AI - Servizio Preferito: - Se il 'Servizio Preferito' è impostato, SourceGit utilizzerà solo quello per questo repository. In caso contrario, se sono disponibili più servizi, verrà mostrato un menu contestuale per sceglierne uno. + Servizio preferito: + Se il 'Servizio Preferito' é impostato, SourceGit utilizzerà solo quello per questo repository. Altrimenti, se ci sono più servizi disponibili, verrà mostrato un menu contestuale per sceglierne uno. Proxy HTTP Proxy HTTP usato da questo repository Nome Utente @@ -189,16 +189,15 @@ Copia Copia Tutto il Testo Copia Percorso - Copia Nome File Crea Branch... Basato Su: Checkout del Branch Creato Modifiche Locali: Scarta - Non Fare Nulla - Stash e Ripristina + Stasha e Ripristina Nome Nuovo Branch: Inserisci il nome del branch. + Gli spazi verranno rimpiazzati con dei trattini. Crea Branch Locale Crea Tag... Nuovo Tag Su: @@ -222,7 +221,10 @@ Stai per eliminare più branch contemporaneamente. Controlla attentamente prima di procedere! Elimina Remoto Remoto: + Percorso: Destinazione: + Tutti i figli verranno rimossi dalla lista. + Lo rimuoverà solamente dalla lista, non dal disco! Conferma Eliminazione Gruppo Conferma Eliminazione Repository Elimina Sottomodulo @@ -235,11 +237,14 @@ VECCHIO Copia Modalità File Modificata + Prima differenza Ignora Modifiche agli Spazi + Ultima differenza MODIFICA OGGETTO LFS Differenza Successiva NESSUNA MODIFICA O SOLO CAMBIAMENTI DI FINE LINEA Differenza Precedente + Abilita la navigazione a blocchi Salva come Patch Mostra Simboli Nascosti Diff Affiancato @@ -270,6 +275,7 @@ Avanzamento Veloce (senza verifica) Recupera Recupera da tutti i remoti + Forza la sovrascrittura dei riferimenti locali Recupera senza tag Remoto: Recupera Modifiche Remote @@ -278,15 +284,16 @@ Scarta {0} file... Scarta Modifiche nelle Righe Selezionate Apri Strumento di Merge Esterno + Risolvi Usando ${0}$ Salva come Patch... - Staging - Staging {0} file - Staging Modifiche nelle Righe Selezionate - Stash... - Stash {0} file... - Rimuovi dallo Staging - Rimuovi dallo Staging {0} file - Rimuovi dallo Staging Modifiche nelle Righe Selezionate + Stage + Stage di {0} file + Stage delle Modifiche nelle Righe Selezionate + Stasha... + Stasha {0} file... + Rimuovi da Stage + Rimuovi da Stage {0} file + Rimuovi le Righe Selezionate da Stage Usa Il Loro (checkout --theirs) Usa Il Mio (checkout --ours) Cronologia File @@ -299,22 +306,22 @@ Prefisso Feature: FLOW - Completa Feature FLOW - Completa Hotfix - FLOW - Completa Release + FLOW - Completa Rilascio Target: Hotfix: Prefisso Hotfix: Inizializza Git-Flow Mantieni branch Branch di Produzione: - Release: - Prefisso Release: + Rilascio: + Prefisso Rilascio: Inizia Feature... FLOW - Inizia Feature Inizia Hotfix... FLOW - Inizia Hotfix Inserisci nome - Inizia Release... - FLOW - Inizia Release + Inizia Rilascio... + FLOW - Inizia Rilascio Prefisso Tag Versione: Git LFS Aggiungi Modello di Tracciamento... @@ -325,29 +332,28 @@ Recupera Oggetti LFS Esegui `git lfs fetch` per scaricare gli oggetti Git LFS. Questo non aggiorna la copia di lavoro. Installa hook di Git LFS - Mostra Bloccaggi + Mostra Blocchi Nessun File Bloccato Blocca - Mostra solo i miei bloccaggi - Bloccaggi LFS + Mostra solo i miei blocchi + Blocchi LFS Sblocca Forza Sblocco Elimina Esegui `git lfs prune` per eliminare vecchi file LFS dallo storage locale - Pull - Pull Oggetti LFS + Scarica + Scarica Oggetti LFS Esegui `git lfs pull` per scaricare tutti i file LFS per il ref corrente e fare il checkout - Push - Push Oggetti LFS + Invia + Invia Oggetti LFS Invia grandi file in coda al punto finale di Git LFS Remoto: Traccia file con nome '{0}' Traccia tutti i file *{0} - Storico - Passa Layout Orizzontale/Verticale + STORICO AUTORE ORA AUTORE - GRAFICO & OGGETTO + GRAFICO E OGGETTO SHA ORA COMMIT {0} COMMIT SELEZIONATI @@ -362,19 +368,19 @@ Vai alla pagina precedente Vai alla pagina successiva Crea una nuova pagina - Apri la finestra di preferenze + Apri la finestra delle preferenze REPOSITORY - Conferma le modifiche in fase - Conferma e invia le modifiche in fase - Aggiungi tutte le modifiche e conferma + Committa le modifiche in tsage + Committa e invia le modifiche in stage + Fai lo stage di tutte le modifiche e committa Crea un nuovo branch dal commit selezionato Scarta le modifiche selezionate Recupera, avvia direttamente Modalità Dashboard (Predefinita) - Recupera e integra, avvia direttamente + Scarica, avvia direttamente Invia, avvia direttamente - Forza il ricaricamento di questo repository - Aggiungi/Rimuovi le modifiche selezionate + Forza l'aggiornamento di questo repository + Aggiungi/Rimuovi da stage le modifiche selezionate Modalità ricerca commit Passa a 'Modifiche' Passa a 'Storico' @@ -384,16 +390,20 @@ Trova il prossimo risultato Trova il risultato precedente Apri il pannello di ricerca - Aggiungi + Aggiungi in stage Rimuovi Scarta Inizializza Repository Percorso: Cherry-Pick in corso. - Richiesta di merge in corso. - Rebase in corso. - Revert in corso. - Rebase Interattivo + Elaborando il commit + Unione in corso. + Unendo + Riallineamento in corso. + Interrotto a + Ripristino in corso. + Ripristinando il commit + Riallinea Interattivamente Branch di destinazione: Su: Apri nel Browser @@ -402,11 +412,16 @@ AVVISO Unisci Branch In: - Opzione di Merge: + Opzione di Unione: + Sorgente: + Unione (multipla) + Commit di tutte le modifiche + Strategia: + Obiettivi: Sposta Nodo Repository Seleziona nodo padre per: Nome: - Git NON è configurato. Vai su [Preferenze] e configurarlo prima. + Git NON è configurato. Prima vai su [Preferenze] per configurarlo. Apri Cartella Dati App Apri con... Opzionale. @@ -420,6 +435,7 @@ Incolla Proprio ora {0} minuti fa + 1 ora fa {0} ore fa Ieri {0} giorni fa @@ -427,76 +443,81 @@ {0} mesi fa L'anno scorso {0} anni fa - Preferenze - AI - Analizza il Prompt Differenza - Chiave API - Genera Prompt Soggetto - Modello - Nome - Server - ASPETTO - Font Predefinito - Font Size - Dimensione Font Predefinita - Dimensione Font Editor - Font Monospaziato - Usa solo font monospaziato nell'editor - Tema - Sostituzioni Tema - Usa larghezza fissa per i tab nella barra del titolo - Usa cornice finestra nativa - STRUMENTO DI DIFFERENZA/UNIONE - Percorso Installazione - Inserisci il percorso per lo strumento di differenza/unione - Strumento - GENERALE - Controlla aggiornamenti all'avvio - Lingua - Numero massimo di commit nella cronologia - Mostra l'orario dell'autore anziché quello del commit nel grafico - Lunghezza Guida Soggetto - GIT - Abilita Auto CRLF - Cartella predefinita per cloni - Email Utente - Email globale utente Git - Percorso Installazione - Nome Utente - Nome globale utente Git - Versione di Git - Git (>= 2.23.0) è richiesto da questa applicazione - FIRMA GPG - Firma GPG per commit - Firma GPG per tag - Formato GPG - Percorso Programma Installato - Inserisci il percorso per il programma GPG installato - Chiave Firma Utente - Chiave GPG dell'utente per la firma - INTEGRAZIONE - SHELL/TERMINALE - Shell/Terminale - Percorso + Preferenze + AI + Analizza il Prompt Differenza + Chiave API + Genera Prompt Oggetto + Modello + Nome + Server + Abilita streaming + ASPETTO + Font Predefinito + Larghezza della Tab Editor + Dimensione Font + Dimensione Font Predefinita + Dimensione Font Editor + Font Monospaziato + Usa solo font monospaziato nell'editor + Tema + Sostituzioni Tema + Usa larghezza fissa per i tab nella barra del titolo + Usa cornice finestra nativa + STRUMENTO DI DIFFERENZA/UNIONE + Percorso Installazione + Inserisci il percorso per lo strumento di differenza/unione + Strumento + GENERALE + Controlla aggiornamenti all'avvio + Formato data + Lingua + Numero massimo di commit nella cronologia + Mostra nel grafico l'orario dell'autore anziché quello del commit + Mostra i figli nei dettagli del commit + Lunghezza Guida Oggetto + GIT + Abilita Auto CRLF + Cartella predefinita per cloni + Email Utente + Email utente Git globale + Abilita --prune durante il fetch + Percorso Installazione + Nome Utente + Nome utente Git globale + Versione di Git + Questa applicazione richiede Git (>= 2.23.0) + Abilita la verifica HTTP SSL + FIRMA GPG + Firma GPG per commit + Firma GPG per tag + Formato GPG + Percorso Programma Installato + Inserisci il percorso per il programma GPG installato + Chiave Firma Utente + Chiave GPG dell'utente per la firma + INTEGRAZIONE + SHELL/TERMINALE + Shell/Terminale + Percorso Potatura Remota Destinazione: Potatura Worktrees - Potatura delle informazioni di worktree in `$GIT_DIR/worktrees` - Pull - Branch: + Potatura delle informazioni di worktree in `$GIT_COMMON_DIR/worktrees` + Scarica + Branch Remoto: Recupera tutti i branch In: Modifiche Locali: Scarta - Non fare nulla - Accantona e Riapplica + Stasha e Riapplica Recupera senza tag Remoto: - Pull (Fetch & Merge) - Usa rebase anziché merge - Push - Assicurati che i submoduli siano stati spinti - Forza il push + Scarica (Recupera e Unisci) + Riallineare anziché unire + Invia + Assicurati che i sottomoduli siano stati inviati + Forza l'invio Branch Locale: Remoto: Invia modifiche al remoto @@ -508,10 +529,10 @@ Remoto: Tag: Esci - Rebase Branch Corrente - Accantona & Riapplica modifiche locali + Riallinea Branch Corrente + Stasha e Riapplica modifiche locali Su: - Rebase: + Riallinea: Aggiorna Aggiungi Remoto Modifica Remoto @@ -534,7 +555,7 @@ Branch: ANNULLA Recupero automatico delle modifiche dai remoti... - Pulizia (GC & Potatura) + Pulizia (GC e Potatura) Esegui il comando `git gc` per questo repository. Cancella tutto Configura questo repository @@ -542,32 +563,51 @@ Azioni Personalizzate Nessuna Azione Personalizzata Abilita opzione '--reflog' - Apri nel Browser File - Cerca Branch/Tag/Submodule + Apri nell'Esplora File + Cerca Branch/Tag/Sottomodulo FILTRATO DA: + Visibilità nel grafico + Non impostato + Nascondi nel grafico dei commit + Filtra nel grafico dei commit + Abilita opzione '--first-parent' + LAYOUT + Orizzontale + Verticale + Ordine dei commit + Per data del commit + Topologicamente BRANCH LOCALI Vai a HEAD - Abilita opzione '--first-parent' Crea Branch + CANCELLA LE NOTIFICHE + Evidenzia nel grafico solo il branch corrente Apri in {0} Apri in Strumenti Esterni Aggiorna REMOTI AGGIUNGI REMOTO Cerca Commit + Autore + Committente File Messaggio SHA - Autore & Committente Branch Corrente Mostra Tag come Albero + SALTA Statistiche - SUBMODULE - AGGIUNGI SUBMODULE - AGGIORNA SUBMODULE + SOTTOMODULI + AGGIUNGI SOTTOMODULI + AGGIORNA SOTTOMODULI TAG NUOVO TAG + Per data di creazione + Per nome (ascendente) + Per nome (discendente) + Ordina Apri nel Terminale + Usa tempo relativo nello storico WORKTREE AGGIUNGI WORKTREE POTATURA @@ -576,10 +616,10 @@ Modalità Reset: Sposta a: Branch Corrente: - Mostra nel File Explorer - Revert Commit + Mostra nell'Esplora File + Ripristina Commit Commit: - Commit delle modifiche di revert + Commit delle modifiche di ripristino Modifica Messaggio di Commit Usa 'Shift+Enter' per inserire una nuova riga. 'Enter' è il tasto rapido per il pulsante OK In esecuzione. Attendere... @@ -595,27 +635,35 @@ Salta questa versione Aggiornamento Software Non ci sono aggiornamenti disponibili. - Squash Commit + Imposta il Branch + Branch: + Rimuovi upstream + Upstream: + Copia SHA + Vai a + Compatta Commit In: Chiave Privata SSH: Percorso per la chiave SSH privata AVVIA - Accantona + Stasha + Auto-ripristino dopo lo stash + I tuoi file di lavoro rimangono inalterati, ma viene salvato uno stash. Includi file non tracciati - Mantieni file indicizzati + Mantieni file in stage Messaggio: - Opzionale. Nome di questo accantonamento - Solo modifiche indicizzate - Sia le modifiche indicizzate che quelle non indicizzate dei file selezionati saranno accantonate!!! - Accantona Modifiche Locali + Opzionale. Nome di questo stash + Solo modifiche in stage + Sia le modifiche in stage che quelle non in stage dei file selezionati saranno stashate!!! + Stasha Modifiche Locali Applica Elimina - Estrai - Elimina Accantonamento + Salva come Patch... + Elimina Stash Elimina: - Accantonamenti + STASH MODIFICHE - ACCANTONAMENTI + STASH Statistiche COMMIT COMMITTER @@ -624,14 +672,14 @@ COMMIT: AUTORI: PANORAMICA - SUBMODULE - Aggiungi Submodule + SOTTOMODULI + Aggiungi Sottomodulo Copia Percorso Relativo - Recupera submodule annidati - Apri Repository Submodule + Recupera sottomoduli annidati + Apri Repository del Sottomodulo Percorso Relativo: Cartella relativa per memorizzare questo modulo. - Elimina Submodule + Elimina Sottomodulo OK Copia Nome Tag Copia Messaggio Tag @@ -639,11 +687,11 @@ Unisci ${0}$ in ${1}$... Invia ${0}$... URL: - Aggiorna Submodule - Tutti i submodule + Aggiorna Sottomoduli + Tutti i sottomoduli Inizializza se necessario Ricorsivamente - Submodule: + Sottomodulo: Usa opzione --remote Avviso Pagina di Benvenuto @@ -651,7 +699,7 @@ Crea Sottogruppo Clona Repository Elimina - TRASCINA & RILASCIA CARTELLA SUPPORTATO. RAGGRUPPAMENTI PERSONALIZZATI SUPPORTATI. + TRASCINA E RILASCIA CARTELLA SUPPORTATO. RAGGRUPPAMENTI PERSONALIZZATI SUPPORTATI. Modifica Sposta in un Altro Gruppo Apri Tutti i Repository @@ -660,34 +708,36 @@ Riscansiona Repository nella Cartella Clone Predefinita Cerca Repository... Ordina - Modifiche + MODIFICHE LOCALI Git Ignore Ignora tutti i file *{0} Ignora i file *{0} nella stessa cartella Ignora i file nella stessa cartella Ignora solo questo file Modifica - Puoi indicizzare questo file ora. + Puoi aggiungere in stage questo file ora. COMMIT - COMMIT & PUSH + COMMIT E INVIA Template/Storico Attiva evento click - Indica tutte le modifiche e fai il commit - Commit vuoto rilevato! Vuoi continuare (--allow-empty)? + Commit (Modifica) + Stage di tutte le modifiche e fai il commit + Trovato un commit vuoto! Vuoi continuare (--allow-empty)? CONFLITTI RILEVATI CONFLITTI NEI FILE RISOLTI INCLUDI FILE NON TRACCIATI NESSUN MESSAGGIO RECENTE INSERITO NESSUN TEMPLATE DI COMMIT - INDICIZZATI - RIMUOVI DALL'INDICIZZAZIONE - RIMUOVI TUTTO DALL'INDICIZZAZIONE - NON INDICIZZATI - INDICIZZA - INDICIZZA TUTTO + IN STAGE + RIMUOVI DA STAGE + RIMUOVI TUTTO DA STAGE + NON IN STAGE + FAI LO STAGE + FAI LO STAGE DI TUTTO VISUALIZZA COME NON MODIFICATO Template: ${0}$ - Clicca con il tasto destro sul file(i) selezionato, quindi scegli come risolvere i conflitti. + Clicca con il tasto destro sul(i) file selezionato, quindi scegli come risolvere i conflitti. + SignOff WORKSPACE: Configura Workspaces... WORKTREE diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index d72dd370..4ee6cdbc 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -29,12 +29,6 @@ Sobre Sobre o SourceGit - • Construído com - • Gráfico desenhado por - © 2024 sourcegit-scm - • Editor de Texto de - • Fontes monoespaçadas de - • Código-fonte pode ser encontrado em Cliente Git GUI Livre e de Código Aberto Adicionar Worktree O que Checar: @@ -49,18 +43,10 @@ Assietente IA Utilizar IA para gerar mensagem de commit Patch - Erro - Erros levantados e se recusa a aplicar o patch - Erro Total - Semelhante a 'erro', mas mostra mais Arquivo de Patch: Selecione o arquivo .patch para aplicar Ignorar mudanças de espaço em branco - Sem Aviso - Desativa o aviso de espaço em branco no final Aplicar Patch - Aviso - Emite avisos para alguns erros, mas aplica Espaço em Branco: Arquivar... Salvar Arquivo Como: @@ -75,7 +61,6 @@ Blame BLAME NESTE ARQUIVO NÃO É SUPORTADO!!! Checkout ${0}$... - Comparar com Branch Comparar com HEAD Comparar com Worktree Copiar Nome do Branch @@ -91,8 +76,7 @@ Subir ${0}$ Rebase ${0}$ em ${1}$... Renomear ${0}$... - Definir Branch de Rastreamento - Remover Upstream + Definir Branch de Rastreamento... Comparação de Branches Bytes CANCELAR @@ -110,7 +94,6 @@ Branch: Alterações Locais: Descartar - Nada Stash & Reaplicar Cherry-Pick Adicionar origem à mensagem de commit @@ -182,11 +165,10 @@ Buscar remotos automaticamente Minuto(s) Remoto padrão - Habilita --prune ao buscar - Habilita --signoff para commits RASTREADOR DE PROBLEMAS Adicionar Regra de Exemplo do Github Adicionar Regra de Exemplo do Jira + Adicionar Regra de Exemplo do Azure DevOps Adicionar Regra de Exemplo do GitLab Adicionar regra de exemplo de Merge Request do GitLab Nova Regra @@ -214,13 +196,11 @@ Copiar Copiar todo o texto Copiar Caminho - Copiar Nome do Arquivo Criar Branch... Baseado Em: Checar o branch criado Alterações Locais: Descartar - Não Fazer Nada Guardar & Reaplicar Nome do Novo Branch: Insira o nome do branch. @@ -368,7 +348,6 @@ Rastrear arquivos nomeados '{0}' Rastrear todos os arquivos *{0} Históricos - Alternar Layout Horizontal/Vertical AUTOR DATA DO AUTOR GRÁFICO & ASSUNTO @@ -385,7 +364,7 @@ Ir para a página anterior Ir para a próxima página Criar nova página - Abrir diálogo de preferências + Abrir diálogo de preferências REPOSITÓRIO Commitar mudanças preparadas Commitar e enviar mudanças preparadas @@ -443,6 +422,7 @@ Colar Agora mesmo {0} minutos atrás + 1 hora atrás {0} horas atrás Ontem {0} dias atrás @@ -450,68 +430,68 @@ {0} meses atrás Ano passado {0} anos atrás - Preferências - INTELIGÊNCIA ARTIFICIAL - Prompt para Analisar Diff - Chave da API - Prompt para Gerar Título - Modelo - Nome - Servidor - APARÊNCIA - Fonte Padrão - Tamanho da Fonte - Padrão - Editor - Fonte Monoespaçada - Usar fonte monoespaçada apenas no editor de texto - Tema - Substituições de Tema - Usar largura fixa de aba na barra de título - Usar moldura de janela nativa - FERRAMENTA DE DIFF/MERGE - Caminho de Instalação - Insira o caminho para a ferramenta de diff/merge - Ferramenta - GERAL - Verificar atualizações na inicialização - Idioma - Commits do Histórico - Exibir data do autor em vez da data do commit no gráfico - Comprimento do Guia de Assunto - GIT - Habilitar Auto CRLF - Diretório de Clone Padrão - Email do Usuário - Email global do usuário git - Caminho de Instalação - Nome do Usuário - Nome global do usuário git - Versão do Git - Git (>= 2.23.0) é necessário para este aplicativo - ASSINATURA GPG - Assinatura GPG de commit - Assinatura GPG de tag - Formato GPG - Caminho de Instalação do Programa - Insira o caminho para o programa gpg instalado - Chave de Assinatura do Usuário - Chave de assinatura gpg do usuário - INTEGRAÇÃO - SHELL/TERMINAL - Shell/Terminal - Caminho + Preferências + INTELIGÊNCIA ARTIFICIAL + Prompt para Analisar Diff + Chave da API + Prompt para Gerar Título + Modelo + Nome + Servidor + APARÊNCIA + Fonte Padrão + Tamanho da Fonte + Padrão + Editor + Fonte Monoespaçada + Usar fonte monoespaçada apenas no editor de texto + Tema + Substituições de Tema + Usar largura fixa de aba na barra de título + Usar moldura de janela nativa + FERRAMENTA DE DIFF/MERGE + Caminho de Instalação + Insira o caminho para a ferramenta de diff/merge + Ferramenta + GERAL + Verificar atualizações na inicialização + Idioma + Commits do Histórico + Exibir data do autor em vez da data do commit no gráfico + Comprimento do Guia de Assunto + GIT + Habilitar Auto CRLF + Diretório de Clone Padrão + Email do Usuário + Email global do usuário git + Habilita --prune ao buscar + Caminho de Instalação + Nome do Usuário + Nome global do usuário git + Versão do Git + Git (>= 2.23.0) é necessário para este aplicativo + ASSINATURA GPG + Assinatura GPG de commit + Assinatura GPG de tag + Formato GPG + Caminho de Instalação do Programa + Insira o caminho para o programa gpg instalado + Chave de Assinatura do Usuário + Chave de assinatura gpg do usuário + INTEGRAÇÃO + SHELL/TERMINAL + Shell/Terminal + Caminho Prunar Remoto Alvo: Podar Worktrees - Podar informações de worktree em `$GIT_DIR/worktrees` + Podar informações de worktree em `$GIT_COMMON_DIR/worktrees` Puxar - Branch: + Branch Remoto: Buscar todos os branches Para: Alterações Locais: Descartar - Não Fazer Nada Guardar & Reaplicar Buscar sem tags Remoto: @@ -570,12 +550,11 @@ Desfazer Esconder no gráfico de commit Incluir no gráfico de commit - Alternar Modo de Ordenação - Data do Commit (--date-order) - Topologicamente (--topo-order) + Habilitar opção '--first-parent' + Data do Commit + Topologicamente BRANCHES LOCAIS Navegar para HEAD - Habilitar opção '--first-parent' Criar Branch Abrir em {0} Abrir em Ferramentas Externas @@ -583,10 +562,11 @@ REMOTOS ADICIONAR REMOTO Pesquisar Commit + Autor + Committer Arquivo Mensagem SHA - Autor & Committer Branch Atual Exibir Tags como Árvore Estatísticas @@ -639,7 +619,6 @@ Guardar Alterações Locais Aplicar Descartar - Pop Descartar Stash Descartar: Stashes diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 76142274..2ea274d1 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -4,166 +4,164 @@ О программе О SourceGit - • Сборка с - • Диаграмма отображается с помощью - © 2024 sourcegit-scm - • Текстовый редактор от - • Моноширинные шрифты взяты из - • Исходный код можно найти по адресу Бесплатный графический клиент Git с исходным кодом - Добавить рабочее дерево - Что проверить: - Существующую ветку - Создать новую ветку + Добавить рабочий каталог + Переключиться на: + ветку из списка + создать новую ветку Расположение: - Путь к этому рабочему дереву. Поддерживается относительный путь. + Путь к рабочему каталогу (поддерживается относительный путь) Имя ветки: - Необязательно. По умолчанию используется имя целевой папки. + Имя целевого каталога по умолчанию. (необязательно) Отслеживание ветки: Отслеживание внешней ветки - OpenAI Ассистент - Использовать OpenAI для создания сообщения о фиксации + Помощник OpenAI + ПЕРЕСОЗДАТЬ + Использовать OpenAI для создания сообщения о ревизии + ПРИМЕНИТЬ КАК СООБЩЕНИЕ РЕВИЗИИ Исправить - Ошибка - Выдает ошибки и отказывается применять исправление - Все ошибки - Аналогично «ошибке», но показывает больше - Файл исправлений: + Файл заплатки: Выберите файл .patch для применения Игнорировать изменения пробелов - Нет предупреждений - Отключает предупреждение о пробелах в конце - Применить исправление - Предупреждение - Выдает предупреждения о нескольких таких ошибках, но применяет + Применить заплатку Пробел: + Отложить + Удалить после применения + Восстановить изменения индекса + Отложенный: Архивировать... Сохранить архив в: Выберите путь к архивному файлу Ревизия: Архив Спросить разрешения SourceGit - ФАЙЛЫ СЧИТАЮТСЯ НЕИЗМЕНЕННЫМИ - НИ ОДИН ФАЙЛ НЕ СЧИТАЕТСЯ НЕИЗМЕНЕННЫМ + НЕОТСЛЕЖИВАЕМЫЕ ФАЙЛЫ + СПИСОК ПУСТ УДАЛИТЬ ДВОИЧНЫЙ ФАЙЛ НЕ ПОДДЕРЖИВАЕТСЯ!!! - Обвинение - ОБВИНЕНИЕ В ЭТОМ ФАЙЛЕ НЕ ПОДДЕРЖИВАЕТСЯ!!! + Расследование + РАССЛЕДОВАНИЕ В ЭТОМ ФАЙЛЕ НЕ ПОДДЕРЖИВАЕТСЯ!!! Проверить ${0}$... - Сравнить в веткой - Сравнить в заголовком - Сравнить в рабочим деревом + Сравнить с ГОЛОВОЙ (HEAD) + Сравнить с рабочим каталогом Копировать имя ветки + Изменить действие Удалить ${0}$... Удалить выбранные {0} ветки Отклонить все изменения. Перемотать вперёд к ${0}$ Извлечь ${0}$ в ${1}$... Поток Git - Завершение ${0}$ - Слить ${0}$ в ${1}$... + Влить ${0}$ в ${1}$... + Влить {0} выделенных веток в текущую Забрать ${0}$ Забрать ${0}$ в ${1}$... Выложить ${0}$ Переместить ${0}$ на ${1}$... Переименовать ${0}$... - Установить отслеживание ветки - Отключить основной поток - Сравнение ветвей + Отслеживать ветку... + Сравнение веток + Недопустимая основная ветка! Байты ОТМЕНА Сбросить эту ревизию Сбросить родительскую ревизию - Произвести сообщение о фиксации + Произвести сообщение о ревизии ИЗМЕНИТЬ РЕЖИМ ОТОБРАЖЕНИЯ Показывать в виде списка файлов и каталогов Показывать в виде списка путей Показывать в виде дерева файловой системы - Проверить ветку - Проверить фиксацию - Предупреждение: При выполнении проверки фиксации ваша голова будет отсоединена - Фиксация: + Переключить ветку + Переключение ревизии + Предупреждение: После переключения ревизии ваша Голова (HEAD) будет отсоединена + Ревизия: Ветка: Локальные изменения: Отклонить - Ничего не делать Отложить и примненить повторно Частичный выбор - Добавить источник для фиксации сообщения - Фиксация(и): - Фиксировать все изменения. + Добавить источник для ревизии сообщения + Ревизия(и): + Ревизия всех изменений. Основной: - Обычно вы не можете выделить слияние, потому что не знаете, какую сторону слияния следует считать основной. Эта опция позволяет отобразить изменение относительно указанного родительского элемента. + Обычно вы не можете выделить слияние, потому что не знаете, какую сторону слияния следует считать основной. Эта опция позволяет отобразить изменения относительно указанного родительского элемента. Очистить отложенные - Вы пытаетесь очистить все отложенные. Вы уверены, что будете продолжать? - Клонировать внешнее хранилище + Вы пытаетесь очистить все отложенные. Вы уверены, что хотите продолжить? + Клонировать внешний репозиторий Расширенные параметры: - Дополнительные аргументы для клонирования хранилища. Необязательно. + Дополнительные аргументы для клонирования репозитория. (необязательно). Локальное имя: - Имя хранилища. Необязательно. + Имя репозитория. (необязательно). Родительский каталог: - Адрес хранилища: + Создать и обновить подмодуль + Адрес репозитория: ЗАКРЫТЬ Редактор - Выбрать из списка эту фиксацию - Список выбора ... - Проверить фиксацию - Сравнить в заголовком - Сравнить с рабочим деревом + Применить эту ревизию (cherry-pick) + Применить несколько ревизий ... + Переключиться на эту ревизию + Сравнить c ГОЛОВОЙ (HEAD) + Сравнить с рабочим каталогом Копировать информацию Копировать SHA Пользовательское действие - Интерактивное перемещение ${0}$ сюда + Интерактивное перемещение (rebase -i) ${0}$ сюда + Влить в ${0}$ + Влить ... Переместить ${0}$ сюда Сбросить ${0}$ сюда - Вернуть фиксацию - Переформулировать - Сохранить как исправление... - Втиснуть в родительскую - Втиснуть дочерную фиксацию сюда + Отменить ревизию + Изменить комментарий + Сохранить как заплатки... + Объединить с предыдущей ревизией + Объединить все следующие ревизии с этим ИЗМЕНЕНИЯ Найти изменения.... ФАЙЛЫ - Файл ХБФ + Файл LFS Поиск файлов... Подмодуль ИНФОРМАЦИЯ АВТОР ИЗМЕНЁННЫЙ ДОЧЕРНИЙ - ИСПОЛНИТЕЛЬ - Проверить ссылки, содержащие эту фиксацию - ФИКСАЦИЯ СОДЕРЖИТСЯ В + РЕВИЗОР (ИСПОЛНИТЕЛЬ) + Найти все ветки с этой ревизией + ВЕТКИ С ЭТОЙ РЕВИЗИЕЙ Отображаются только первые 100 изменений. Смотрите все изменения на вкладке ИЗМЕНЕНИЯ. СООБЩЕНИЕ РОДИТЕЛИ ССЫЛКИ SHA Открыть в браузере - Введите тему фиксации + Введите тему ревизии Описание - Настройка хранилища - ШАБЛОН ФИКСАЦИИ - Имя шаблона: - Шаблон содержания: + Настройка репозитория + ШАБЛОН РЕВИЗИИ + Название: + Cодержание: ПОЛЬЗОВАТЕЛЬСКОЕ ДЕЙСТВИЕ Аргументы: - ${REPO} - Путь хранилища; ${SHA} - Выбранные фиксации SHA - Исполняемый фалй: + ${REPO} - Путь к репозиторию; ${SHA} - SHA ревизий + Исполняемый файл: Имя: Диапазон: - Фиксация - Хранилище + Ветка + Ревизия + Репозиторий + Ждать для выполения выхода Адрес электронной почты Адрес электронной почты GIT - Автоматическое извлечение внешних хранилищ + Автозагрузка изменений Минут(а/ы) - Разрешить --signoff для фиксации - Удалённое хранилище по-умолчанию - Разрешить --prune при извлечении + Внешний репозиторий по умолчанию ОТСЛЕЖИВАНИЕ ПРОБЛЕМ + Добавить пример правила для тем в Gitea + Добавить пример правила запроса скачивания из Gitea Добавить пример правила для Git Добавить пример правила Jira + Добавить пример правила Azure DevOps Добавить пример правила выдачи GitLab Добавить пример правила запроса на слияние в GitLab Новое правило @@ -172,80 +170,85 @@ Адрес результата: Пожалуйста, используйте $1, $2 для доступа к значениям групп регулярных выражений. ОТКРЫТЬ ИИ - Предпочитаемый сервис: - Если «Предпочитаемый сервис» установлен, SourceGit будет использовать только этот хранилище. В противном случае, если доступно более одной услуги, будет отображено контекстное меню для выбора одной из них. + Предпочтительный сервис: + Если «Предпочтительный сервис» установлен, SourceGit будет использовать только этот репозиторий. В противном случае, если доступно более одной услуги, будет отображено контекстное меню для выбора одной из них. HTTP-прокси - HTTP-прокси, используемый этим хранилищем + HTTP-прокси для репозитория Имя пользователя - Имя пользователя для этого хранилища + Имя пользователя репозитория Рабочие пространства Имя Цвет Восстанавливать вкладки при запуске - Общепринятый помощник по фиксации изменений + Общепринятый помощник по ревизии Кардинальные изменения: Закрытая тема: Детали изменений: Область: - Коротнкое описание: + Короткое описание: Тип изменения: Копировать Копировать весь текст Копировать путь - Копировать имя файла + Копировать полный путь Создать ветку... Основан на: Проверить созданную ветку Локальные изменения: Отклонить - Ничего не делать Отложить и применить повторно Имя новой ветки: - Ввести имя ветки. + Введите имя ветки. + Пробелы будут заменены на тире. Создать локальную ветку Создать метку... Новая метка у: - Подпись GPG - Сообщение с меткой: + GPG подпись + Сообщение с меткой: Необязательно. Имя метки: Рекомендуемый формат: v1.0.0-alpha - Выложить на все внешние хранилища после создания + Выложить на все внешние репозитории после создания Создать новую метку Вид: - Аннотированный - Лёгкий - Удерживайте Ctrl, чтобы начать сразу + С примечаниями + Простой + Удерживайте Ctrl, чтобы сразу начать Вырезать Удалить ветку Ветка: - Вы собираетесь удалить удаленную ветку!!! - Также удалите удаленную ветку ${0}$ + Вы собираетесь удалить внешнюю ветку!!! + Также удалите внешнюю ветку ${0}$ Удаление нескольких веток Вы пытаетесь удалить несколько веток одновременно. Обязательно перепроверьте, прежде чем предпринимать какие-либо действия! - Удалить внешнее хранилище - Внешнее хранилище: + Удалить внешний репозиторий + Внешний репозиторий: + Путь: Цель: + Все дочерние элементы будут удалены из списка. Подтвердите удаление группы - Подтвердите удаление хранилища + Будет удалён из списка. На диске останется. + Подтвердите удаление репозитория Удалить подмодуль Путь подмодуля: Удалить метку Метка: - Удалить из внешнего хранилища + Удалить из внешнего репозитория РАЗНИЦА БИНАРНИКОВ НОВЫЙ СТАРЫЙ Копировать Режим файла изменён + Первое различие Игнорировать изменение пробелов + Последнее различие Показывать скрытые символы - ИЗМЕНЕНИЕ ОБЪЕКТА ХБФ + ИЗМЕНЕНИЕ ОБЪЕКТА LFS Следующее различие НИКАКИХ ИЗМЕНЕНИЙ ИЛИ МЕНЯЕТСЯ ТОЛЬКО EOL Предыдущее различие - Сохранить как исправление - Различие бок о бок + Сохранить как заплатку + Различие рядом ПОДМОДУЛЬ НОВЫЙ Обмен @@ -253,37 +256,38 @@ Перенос слов в строке Разрешить навигацию по блокам Открыть в инструменте слияния - Показывать все линии - Уменьшить количество видимых линий - Увеличить количество видимых линий - ВЫБРАТЬ ФАЙЛ ДЛЯ ПРОСМОТРА ИЗМЕНЕНИЙ + Показывать все строки + Уменьшить количество видимых строк + Увеличить количество видимых строк + ВЫБЕРИТЕ ФАЙЛ ДЛЯ ПРОСМОТРА ИЗМЕНЕНИЙ Открыть в инструменте слияния Отклонить изменения Все локальные изменения в рабочей копии. Изменения: Включить игнорируемые файлы - Всего {0} изменений будут отменены + {0} изменений будут отменены Вы не можете отменить это действие!!! Закладка: Новое имя: Цель: Редактировать выбранную группу - Редактировать выбранное хранилище + Редактировать выбранный репозиторий Выполнить пользовательское действие Имя действия: Быстрая перемотка вперёд (без проверки) Извлечь - Извлечь все внешние хранилища - Разрешить опцию '--force' + Извлечь все внешние репозитории + Разрешить опцию (--force) Извлечь без меток - Внешнее хранилище: + Внешний репозиторий: Извлечь внешние изменения - Допустить без изменений + Не отслеживать Отклонить... Отклонить {0} файлов... - Отклонить изменения в выбранной(ых) строке(ах) + Отменить изменения в выбранной(ых) строке(ах) Открыть расширенный инструмент слияния - Сохранить как исправление... + Взять версию ${0}$ + Сохранить как файл заплатки... Подготовить Подготовленные {0} файлы Подготовленные изменения в выбранной(ых) строке(ах) @@ -307,7 +311,7 @@ Цель: Исправление: Префикс исправлений: - Инициализировать Git-поток + Создать Git-поток Держать ветку Производственная ветка: Выпуск: @@ -320,65 +324,65 @@ Запуск выпуска... ПОТОК - Запуск выпуска Префикс метки версии: - Git хранилища больших файлов + Git LFS (хранилище больших файлов) Добавить шаблон отслеживания... Шаблон — это имя файла Изменить шаблон: - Добавить шаблон отслеживания в ХБФ Git + Добавить шаблон отслеживания в LFS Git Извлечь - Извлечь объекты ХБФ - Запустить «git lfs fetch», чтобы загрузить объекты ХБФ Git. При этом рабочая копия не обновляется. - Установить перехват ХБФ Git + Извлечь объекты LFS + Запустить (git lfs fetch), чтобы загрузить объекты LFS Git. При этом рабочая копия не обновляется. + Установить перехват LFS Git Показывать блокировки Нет заблокированных файлов Блокировка Показывать только мои блокировки - Блокировки ХБФ + Блокировки LFS Разблокировать Принудительно разблокировать Обрезать - Запустить «git lfs prune», чтобы удалить старые файлы ХБФ из локального хранилища + Запустить (git lfs prune), чтобы удалить старые файлы LFS из локального хранилища Забрать - Забрать объекты ХБФ - Запустить «git lfs pull», чтобы загрузить все файлы ХБФ Git для текущей ссылки и проверить + Забрать объекты LFS + Запустить (git lfs pull), чтобы загрузить все файлы LFS Git для текущей ссылки и проверить Выложить - Выложить объекты ХБФ - Отправляйте большие файлы, помещенные в очередь, в конечную точку ХБФ Git + Выложить объекты LFS + Отправляйте большие файлы, помещенные в очередь, в конечную точку LFS Git Внешнее хранилище: Отслеживать файлы с именем «{0}» Отслеживать все *{0} файлов Истории - Переключение горизонтального/вертикального расположения АВТОР ВРЕМЯ АВТОРА ГРАФ И СУБЪЕКТ SHA - ВРЕМЯ ФИКСАЦИИ - ВЫБРАННЫЕ {0} ФИКСАЦИИ - Удерживайте Ctrl или Shift, чтобы выбрать несколько фиксаций. - Удерживайте ⌘ или ⇧, чтобы выбрать несколько фиксаций. + ВРЕМЯ РЕВИЗИИ + ВЫБРАННЫЕ {0} РЕВИЗИИ + Удерживайте Ctrl или Shift, чтобы выбрать несколько ревизий. + Удерживайте ⌘ или ⇧, чтобы выбрать несколько ревизий. ПОДСКАЗКИ: Ссылка на сочетания клавиш ОБЩЕЕ - Отменить текущее всплывающее окно - Закрыть текущее окно - Перейти на предыдущую страницу - Перейти на следующую страницу - Создать новую страницу - Открыть диалоговое окно настроек - ХРАНИЛИЩЕ + Закрыть окно + Клонировать репозиторий + Закрыть вкладку + Перейти на предыдущую вкладку + Перейти на следующую вкладку + Создать новую вкладку + Открыть диалоговое окно настроек + РЕПОЗИТОРИЙ Зафиксировать подготовленные изменения Зафиксировать и выложить подготовленные изменения Подготовить все изменения и зафиксировать Создать новую ветку на основе выбранной ветки Отклонить выбранные изменения Извлечение, запускается сразу - Режим доски (по-умолчанию) - Принудительно перезагрузить этот хранилище + Режим доски (по умолчанию) + Принудительно перезагрузить репозиторий Забрать, запускается сразу Выложить, запускается сразу Подготовленные/Неподготовленные выбранные изменения - Режим поиска фиксаций + Режим поиска ревизий Переключить на «Изменения» Переключить на «Истории» Переключить на «Отложенные» @@ -390,12 +394,16 @@ Подготовить Снять из подготовленных Отклонить - Инициализировать хранилище + Создать репозиторий Путь: - Выполняется частичный забор. - Выполняет запрос слияния. + Выполняется частичный перенос ревизий (cherry-pick). + Обрабтка ревизии. + Выполняется слияние. + Выполяется. Выполняется перенос. - Выполняется возврат. + Остановлен на + Выполняется отмена ревизии. + Выполняется отмена Интерактивное перемещение Целевая ветка: На: @@ -403,11 +411,16 @@ Копировать ссылку ОШИБКА УВЕДОМЛЕНИЕ - Слить ветку + Влить ветку В: Опции слияния: - Переместить узел хранилища - Выбрать родительский узел для: + Источник: + Влить несколько веток + Зафиксировать все изменения + Стратегия: + Цели: + Переместить репозиторий в другую группу + Выбрать группу для: Имя: Git НЕ был настроен. Пожалуйста, перейдите в [Настройки] и сначала настройте его. Открыть приложение каталогов данных @@ -418,98 +431,104 @@ Закрыть вкладку Закрыть другие вкладки Закрыть вкладки справа - Копировать путь хранилища - Хранилища + Копировать путь репозитория + Репозитории Вставить Сейчас {0} минут назад + 1 час назад {0} часов назад Вчера {0} дней назад Последний месяц {0} месяцев назад - В пролому году + В прошлом году {0} лет назад - Параметры - ОТКРЫТЬ ИИ - Ключ API - Запрос на анализ различий - Произвести запрос на тему - Модель - Имя: - Сервер - ВИД - Шрифт по-умолчанию - Размер шрифта - По-умолчанию - Редактор - Моноширный шрифт - В текстовом редакторе используется только моноширный шрифт - Тема - Переопределение темы - Использовать фиксированную ширину табуляции в строке заголовка. - Использовать системное окно - ИНСТРУМЕНТ РАЗЛИЧИЙ/СЛИЯНИЯ - Путь установки - Введите путь для инструмента различия/слияния - Инструмент - ГЛАВНЫЙ - Проверить обновления при старте - Язык - История фиксаций - Показывать время автора вместо времени фиксации на графике - Показать наследника в деталях комментария - Длина темы фиксации - GIT - Включить автозавершение CRLF - Каталог клонирования по-умолчанию - Электроная почта пользователя - Общая электроная почта пользователя git - Путь установки - Имя пользователя - общее имя пользователя git - Версия Git - Git (>= 2.23.0) требуется для этого приложения - ПОДПИСЬ GPG - Фиксация подписи GPG - Метка подписи GPG - Формат GPG - Путь установки программы - Введите путь для установленной программы GPG - Ключ подписи пользователя - Ключ подписи GPG пользователя - ВНЕДРЕНИЕ - ОБОЛОЧКА/ТЕРМИНАЛ - Оболочка/Терминал - Путь - Удалить внешнее хранилище + Параметры + ОТКРЫТЬ ИИ + Ключ API + Запрос на анализ различий + Произвести запрос на тему + Модель + Имя: + Сервер + Разрешить потоковую передачу + ВИД + Шрифт по умолчанию + Размер шрифта + По умолчанию + Редактировать ширину вкладки + Редактор + Моноширный шрифт + В текстовом редакторе используется только моноширный шрифт + Тема + Переопределение темы + Использовать фиксированную ширину табуляции в строке заголовка. + Использовать системное окно + ИНСТРУМЕНТ РАЗЛИЧИЙ/СЛИЯНИЯ + Путь установки + Введите путь для инструмента различия/слияния + Инструмент + ОСНОВНЫЕ + Проверить обновления при старте + Формат даты + Язык + Максимальная длина истории + Показывать время автора вместо времени ревизии на графике + Показать наследника в деталях комментария + Показывать метки на графике + Длина темы ревизии + GIT + Включить автозавершение CRLF + Каталог клонирования по умолчанию + Электроная почта пользователя + Общая электроная почта пользователя git + Разрешить (--prune) при скачивании + Путь установки + Разрешить верификацию HTTP SSL + Имя пользователя + Общее имя пользователя git + Версия Git + Для работы программы требуется версия Git (>= 2.23.0) + GPG ПОДПИСЬ + GPG подпись ревизии + GPG подпись метки + Формат GPG + Путь установки программы + Введите путь для установленной программы GPG + Ключ подписи пользователя + Ключ GPG подписи пользователя + ВНЕДРЕНИЕ + ОБОЛОЧКА/ТЕРМИНАЛ + Оболочка/Терминал + Путь + Удалить внешний репозиторий Цель: - Удалить рабочее дерево - Информация об обрезке рабочего дерева в «$GIT_DIR/worktrees» + Удалить рабочий каталог + Информация об обрезке рабочего каталога в «$GIT_COMMON_DIR/worktrees» Забрать - Ветка: + Ветка внешнего репозитория: Извлечь все ветки В: Локальные изменения: Отклонить - Ничего не делать Отложить и применить повторно Забрать без меток - Внешнее хранилище: + Внешний репозиторий: Забрать (Получить и слить) Использовать перемещение вместо слияния Выложить Убедитесь, что подмодули были вставлены Принудительно выложить Локальная ветка: - Внешнее хранилище: - Выложить изменения на внешнее хранилище - Ветка внешнего хранилища: - Установить в качестве ветки отслеживания + Внешний репозиторий: + Выложить изменения на внешний репозиторий + Ветка внешнего репозитория: + Отслеживать ветку Выложить все метки - Выложить метку на внешнее хранилище - Выложить на все внешние хранилища - Внешнее хранилище: + Выложить метку на внешний репозиторий + Выложить на все внешние репозитории + Внешний репозиторий: Метка: Выйти Перемещение текущей ветки @@ -517,31 +536,31 @@ На: Переместить: Обновить - Добавить внешнее хранилище - Редактировать внешнее хранилище + Добавить внешний репозиторий + Редактировать внешний репозиторий Имя: - Имя внешнего хранилища - Адрес хранилища: - Адрес внешнего хранилища git + Имя внешнего репозитория + Адрес: + Адрес внешнего репозитория git Копировать адрес Удалить... Редактировать... Извлечь Открыть в браузере Удалить - Подтвердить удаление рабочего дерева - Включить опцию --force + Подтвердить удаление рабочего каталога + Включить опцию (--force) Цель: Переименовать ветку Новое имя: Уникальное имя для данной ветки Ветка: Отказ - Автоматическое извлечение изменений с внешних хранилищ... + Автоматическое извлечение изменений с внешних репозиторий... Очистить (Сбор мусора и удаление) - Запустить команду «git gc» для данного хранилища. + Запустить команду (git gc) для данного репозитория. Очистить всё - Настройка этого хранилища + Настройка репозитория ПРОДОЛЖИТЬ Изменить действия Не изменять действия @@ -549,129 +568,147 @@ Открыть в файловом менеджере Поиск веток, меток и подмодулей Видимость на графике - Не установлен (По-умолчанию) - Скрыть в графе фиксации - Фильтр в графе фиксации + Не установлен (по умолчанию) + Скрыть в графе ревизии + Фильтр в графе ревизии ОТФИЛЬТРОВАНО: - Переключить режим запроса - Дата фиксации (--date-order) - Топологически (--topo-order) + Включить опцию (--first-parent) + РАСПОЛОЖЕНИЕ + Горизонтально + Вертикально + ЗАПРОС РЕВИЗИЙ + Дата ревизии + Топологически ЛОКАЛЬНЫЕ ВЕТКИ - Навигация по заголовку - Включить опцию --first-parent + Навигация по ГОЛОВЕ (HEAD) Создать ветку + ОЧИСТКА УВЕДОМЛЕНИЙ + Выделять только текущую ветку на графике Открыть в {0} Открыть в расширенном инструменте Обновить - ВНЕШНИЕ ХРАНИЛИЩА - ДОБАВИТЬ ВНЕШНЕЕ ХРАНИЛИЩЕ - Поиск фиксации + ВНЕШНИЕ РЕПОЗИТОРИИ + ДОБАВИТЬ ВНЕШНИЙ РЕПОЗИТОРИЙ + Поиск ревизии + Автор + Ревизор Файл Сообщение SHA - Автор и исполнитель Текущая ветка - Показывать метки как дерево - Статистики + Показывать метки как катлог + ПРОПУСТИТЬ + Статистикa ПОДМОДУЛИ ДОБАВИТЬ ПОДМОДУЛЬ ОБНОВИТЬ ПОДМОДУЛЬ МЕТКИ НОВАЯ МЕТКА + По дате создания + По имени (по возрастанию) + По имени (по убыванию) + Сортировать Открыть в терминале - РАБОЧИЕ ДЕРЕВЬЯ - ДОБАВИТЬ РАБОЧЕЕ ДЕРЕВО + Использовать относительное время в историях + РАБОЧИЕ КАТАЛОГИ + ДОБАВИТЬ РАБОЧИЙ КАТАЛОГ ОБРЕЗАТЬ - Адрес хранилища Git - Сбросить текущую втеку до версии + Адрес репозитория Git + Сбросить текущую ветку до версии Режим сброса: Переместить в: Текущая ветка: Открыть в файловом менеджере - Отменить фиксацию - Фиксация: - Отмена изменений фиксации - Переформулировать сообщение фиксации - Использовать «Shift+Enter» для ввода новой строки. «Enter» - это горячая клавиша кнопки OK + Отменить ревизию + Ревизия: + Отмена ревизии + Изменить комментарий ревизии + Используйте «Shift+Enter» для ввода новой строки. «Enter» - это горячая клавиша кнопки «OK» Запуск. Подождите пожалуйста... СОХРАНИТЬ Сохранить как... - Исправление успешно сохранено! - Сканирование хранилищ + Заплатка успешно сохранена! + Сканирование репозиторий Корневой каталог: Копировать SHA Перейти Проверка для обновления... - Доступна новая версия этого программного обеспечения: + Доступна новая версия программного обеспечения: Не удалось проверить наличие обновлений! Загрузка Пропустить эту версию Обновление ПО В настоящее время обновления недоступны. - Втиснуть фиксации + Отслеживать ветку + Ветка: + Снять основную ветку + Основная ветка: + Втиснуть ревизии В: - Частный ключ SSH: - Путь хранения частного ключа SSH + Приватный ключ SSH: + Путь хранения приватного ключа SSH Только подготовленные изменения - Подготовленные так и подготовленные изменения выбранных файлов будут сохранены!!! + Подготовленные так и неподготовленные изменения выбранных файлов будут сохранены!!! ЗАПУСК Отложить + Автоматически восстанавливать после откладывания + Ваши рабочие файлы остаются неизменными, но отложенные сохранятся. Включить неотслеживаемые файлы Хранить отложенные файлы Сообщение: - Необязательно. Имя этого тайника + Имя тайника (необязательно) Отложить локальные изменения Принять Отбросить - Применить - Отрбосить тайник + Сохранить как заплатку... + Отбросить тайник Отбросить: Отложенные ИЗМЕНЕНИЯ ОТЛОЖЕННЫЕ - Статистики - ФИКСАЦИИ - ИСПОЛНИТЕЛИ + Статистика + РЕВИЗИИ + РЕВИЗОРЫ (ИСПОЛНИТЕЛИ) МЕСЯЦ НЕДЕЛЯ - ФИКСАЦИИ: + РЕВИЗИИ: АВТОРЫ: ОБЗОР ПОДМОДУЛИ - Добавить подмодули + Добавить подмодули Копировать относительный путь Извлечение вложенных подмодулей - Открыть подмодуль хранилища - Относительный путь: - Относительный каталог для хранения подмодуля. + Открыть подмодуль репозитория + Каталог: + Относительный путь для хранения подмодуля. Удалить подмодуль ОК Копировать имя метки Копировать сообщение с метки Удалить ${0}$... - Слить ${0}$ в ${1}$... + Влить ${0}$ в ${1}$... Выложить ${0}$... Сетевой адрес: Обновление подмодулей Все подмодули - Инициализировать по необходимости + Создавать по необходимости Рекурсивно Подмодуль: - Использовать опцию --remote + Использовать опцию (--remote) Предупреждение Приветствие Создать группу Создать подгруппу - Клонировать хранилище + Клонировать репозиторий Удалить ПОДДЕРЖИВАЕТСЯ: ПЕРЕТАСКИВАНИЕ КАТАЛОГОВ, ПОЛЬЗОВАТЕЛЬСКАЯ ГРУППИРОВКА. Редактировать Перейти в другую группу - Открыть все хранилища - Открыть хранилище + Открыть все репозитории + Открыть репозиторий Открыть терминал - Повторное сканирование хранилищ в каталоге клонирования по-умолчанию - Поиск хранилищ... + Повторное сканирование репозиториев в каталоге клонирования по умолчанию + Поиск репозиториев... Сортировка Изменения Игнорировать Git @@ -680,31 +717,32 @@ Игнорировать файлы в том же каталоге Игнорировать только эти файлы Изменить - Теперь вы можете подготовитть этот файл. + Теперь вы можете подготовить этот файл. ЗАФИКСИРОВАТЬ ЗАФИКСИРОВАТЬ и ОТПРАВИТЬ Шаблон/Истории Запустить событие щелчка Зафиксировать (Редактировать) Подготовить все изменения и зафиксировать - Обнаружена пустая фиксация! Вы хотите продолжить (--allow-empty)? + Обнаружена пустая ревизия! Вы хотите продолжить (--allow-empty)? ОБНАРУЖЕНЫ КОНФЛИКТЫ КОНФЛИКТЫ ФАЙЛОВ РАЗРЕШЕНЫ ВКЛЮЧИТЬ НЕОТСЛЕЖИВАЕМЫЕ ФАЙЛЫ НЕТ ПОСЛЕДНИХ ВХОДНЫХ СООБЩЕНИЙ - НЕТ ШАБЛОНОВ ФИКСАЦИИ + НЕТ ШАБЛОНОВ РЕВИЗИИ + Завершение работы ПОДГОТОВЛЕННЫЕ СНЯТЬ ПОДГОТОВЛЕННЫЙ СНЯТЬ ВСЕ ПОДГОТОВЛЕННЫЕ НЕПОДГОТОВЛЕННЫЕ ПОДГОТОВИТЬ ВСЕ ПОДГОТОВИТЬ - ВИД ПРЕДПОЛАГАЕТСЯ НЕИЗМЕННЫМ + ОТКРЫТЬ СПИСОК НЕОТСЛЕЖИВАЕМЫХ ФАЙЛОВ Шаблон: ${0}$ - Щёлкните правой кнопкой мыши выбранный файл(ы) и сделайте свой выбор для разрешения конфликтов. + Щёлкните правой кнопкой мыши выбранный файл(ы) и разрешите конфликты. РАБОЧЕЕ ПРОСТРАНСТВО: Настройка рабочего пространства... - РАБОЧЕЕ ДЕРЕВО + РАБОЧИЙ КАТАЛОГ Копировать путь Заблокировать Удалить diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index f711cdee..8d2b4f1e 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -4,12 +4,6 @@ 关于软件 关于本软件 - • 项目依赖于 - • 图表绘制组件来自 - © 2024 sourcegit-scm - • 文本编辑器使用 - • 等宽字体来自于 - • 项目源代码地址 开源免费的Git客户端 新增工作树 检出分支方式 : @@ -22,21 +16,19 @@ 跟踪分支 设置上游跟踪分支 AI助手 + 重新生成 使用AI助手生成提交信息 + 应用本次生成 应用补丁(apply) - 错误 - 输出错误,并终止应用补丁 - 更多错误 - 与【错误】级别相似,但输出内容更多 补丁文件 : 选择补丁文件 忽略空白符号 - 忽略 - 关闭所有警告 应用补丁 - 警告 - 应用补丁,输出关于空白符的警告 空白符号处理 : + 应用贮藏 + 在成功应用后丢弃该贮藏 + 恢复索引中已暂存的变化 + 已选贮藏 : 存档(archive) ... 存档文件路径: 选择存档文件的存放路径 @@ -50,10 +42,10 @@ 逐行追溯(blame) 选中文件不支持该操作!!! 检出(checkout) ${0}$... - 与其他分支对比 与当前HEAD比较 与本地工作树比较 复制分支名 + 自定义操作 删除 ${0}$... 删除选中的 {0} 个分支 放弃所有更改 @@ -67,9 +59,9 @@ 推送(push)${0}$ 变基(rebase) ${0}$ 分支至 ${1}$... 重命名 ${0}$... - 切换上游分支 - 取消追踪 + 切换上游分支 ... 分支比较 + 跟踪的上游分支不存在或已删除! 字节 取 消 重置文件到该版本 @@ -86,7 +78,6 @@ 目标分支 : 未提交更改 : 丢弃更改 - 不做处理 贮藏并自动恢复 挑选提交 提交信息中追加来源信息 @@ -102,6 +93,7 @@ 本地仓库名 : 本地仓库目录的名字,选填。 父级目录 : + 初始化并更新子模块 远程仓库 : 关闭 提交信息编辑器 @@ -150,25 +142,28 @@ 模板内容 : 自定义操作 命令行参数 : - 请使用${REPO}代替仓库路径,${SHA}代替提交哈希 + 请使用${REPO}代替仓库路径,${BRANCH}代替选中的分支,${SHA}代替提交哈希 可执行文件路径 : 名称 : 作用目标 : + 选中的分支 选中的提交 仓库 + 等待操作执行完成 电子邮箱 邮箱地址 GIT配置 启用定时自动拉取远程更新 分钟 默认远程 - 提交信息追加署名 (--signoff) - 拉取更新时启用修剪(--prune) ISSUE追踪 + 新增匹配Azure DevOps规则 + 新增匹配Gitee议题规则 + 新增匹配Gitee合并请求规则 新增匹配Github Issue规则 - 新增匹配Jira规则 新增匹配GitLab议题规则 新增匹配GitLab合并请求规则 + 新增匹配Jira规则 新增自定义规则 匹配ISSUE的正则表达式 : 规则名 : @@ -194,16 +189,16 @@ 复制 复制全部文本 复制路径 - 复制文件名 + 复制完整路径 新建分支 ... 新分支基于 : 完成后切换到新分支 未提交更改 : 丢弃更改 - 不做处理 贮藏并自动恢复 新分支名 : 填写分支名称。 + 空格将被替换为'-'符号 创建本地分支 新建标签 ... 标签位于 : @@ -227,8 +222,11 @@ 您正在尝试一次性删除多个分支,请务必仔细检查后再执行操作! 删除远程确认 远程名 : + 路径 : 目标 : + 所有子节点将被同时从列表中移除。 删除分组确认 + 仅从列表中移除,不会删除硬盘中的文件! 删除仓库确认 删除子模块确认 子模块路径 : @@ -240,7 +238,9 @@ 原始大小 复制 文件权限已变化 + 首个差异 忽略空白符号变化 + 最后一个差异 LFS对象变更 下一个差异 没有变更或仅有换行符差异 @@ -276,7 +276,7 @@ 快进(fast-forward,无需checkout) 拉取(fetch) 拉取所有的远程仓库 - 启用 --force 选项 + 强制覆盖本地REFs 不拉取远程标签 远程仓库 : 拉取远程仓库内容 @@ -351,7 +351,6 @@ 跟踪名为'{0}'的文件 跟踪所有 *{0} 文件 历史记录 - 切换横向/纵向显示 作者 修改时间 路线图与主题 @@ -369,7 +368,7 @@ 切换到上一个页面 切换到下一个页面 新建页面 - 打开偏好设置面板 + 打开偏好设置面板 仓库页面快捷键 提交暂存区更改 提交暂存区更改并推送 @@ -436,6 +435,7 @@ 粘贴 刚刚 {0}分钟前 + 1小时前 {0}小时前 昨天 {0}天前 @@ -443,70 +443,75 @@ {0}个月前 一年前 {0}年前 - 偏好设置 - AI - Analyze Diff Prompt - API密钥 - Generate Subject Prompt - 模型 - 配置名称 - 服务地址 - 外观配置 - 缺省字体 - 字体大小 - 默认 - 代码编辑器 - 代码字体大小 - 等宽字体 - 仅在文本编辑器中使用等宽字体 - 主题 - 主题自定义 - 主标签使用固定宽度 - 使用系统默认窗体样式 - 对比/合并工具 - 安装路径 - 填写工具可执行文件所在位置 - 工具 - 通用配置 - 启动时检测软件更新 - 显示语言 - 最大历史提交数 - 在提交路线图中显示修改时间而非提交时间 - 在提交详情页中显示子提交列表 - SUBJECT字数检测 - GIT配置 - 自动换行转换 - 默认克隆路径 - 邮箱 - 默认GIT用户邮箱 - 安装路径 - 用户名 - 默认GIT用户名 - Git 版本 - 本软件要求GIT最低版本为2.23.0 - GPG签名 - 启用提交签名 - 启用标签签名 - 签名格式 - 签名程序位置 - 签名程序所在路径 - 用户签名KEY - 输入签名提交所使用的KEY - 第三方工具集成 - 终端/SHELL - 终端/SHELL - 安装路径 + 偏好设置 + AI + Analyze Diff Prompt + API密钥 + Generate Subject Prompt + 模型 + 配置名称 + 服务地址 + 启用流式输出 + 外观配置 + 缺省字体 + 编辑器制表符宽度 + 字体大小 + 默认 + 代码编辑器 + 代码字体大小 + 等宽字体 + 仅在文本编辑器中使用等宽字体 + 主题 + 主题自定义 + 主标签使用固定宽度 + 使用系统默认窗体样式 + 对比/合并工具 + 安装路径 + 填写工具可执行文件所在位置 + 工具 + 通用配置 + 启动时检测软件更新 + 日期时间格式 + 显示语言 + 最大历史提交数 + 在提交路线图中显示修改时间而非提交时间 + 在提交详情页中显示子提交列表 + 在提交路线图中显示标签 + SUBJECT字数检测 + GIT配置 + 自动换行转换 + 默认克隆路径 + 邮箱 + 默认GIT用户邮箱 + 拉取更新时启用修剪(--prune) + 安装路径 + 启用HTTP SSL验证 + 用户名 + 默认GIT用户名 + Git 版本 + 本软件要求GIT最低版本为2.23.0 + GPG签名 + 启用提交签名 + 启用标签签名 + 签名格式 + 签名程序位置 + 签名程序所在路径 + 用户签名KEY + 输入签名提交所使用的KEY + 第三方工具集成 + 终端/SHELL + 终端/SHELL + 安装路径 清理远程已删除分支 目标 : 清理工作树 - 清理在`$GIT_DIR/worktrees`中的无效工作树信息 + 清理在`$GIT_COMMON_DIR/worktrees`中的无效工作树信息 拉回(pull) 拉取分支 : 拉取远程中的所有分支变更 本地分支 : 未提交更改 : 丢弃更改 - 不做处理 贮藏并自动恢复 不拉取远程标签 远程 : @@ -566,23 +571,29 @@ 不指定 在提交列表中隐藏 使用其对提交列表过滤 - 切换排序模式 - 按提交时间 (--date-order) - 按拓扑排序 (--topo-order) + 启用 --first-parent 过滤选项 + 布局方式 + 水平排布 + 竖直排布 + 提交列表排序规则 + 按提交时间 + 按拓扑排序 本地分支 定位HEAD - 启用 --first-parent 过滤选项 新建分支 + 清空通知列表 + 提交路线图中仅高亮显示当前分支 在 {0} 中打开 使用外部工具打开 重新加载 远程列表 添加远程 查找提交 + 作者 + 提交者 文件 提交信息 提交指纹 - 作者及提交者 仅在当前分支中查找 以树型结构展示 跳过此提交 @@ -592,7 +603,12 @@ 更新子模块 标签列表 新建标签 + 按创建时间 + 按名称(升序) + 按名称(降序) + 排序 在终端中打开 + 在提交列表中使用相对时间 工作树列表 新增工作树 清理 @@ -620,6 +636,10 @@ 忽略此版本 软件更新 当前已是最新版本。 + 切换上游分支 + 本地分支 : + 取消追踪 + 上游分支 : 复制提交指纹 跳转到提交 压缩为单个提交 @@ -628,6 +648,8 @@ SSH密钥文件 开 始 贮藏(stash) + 贮藏后自动恢复工作区 + 工作区文件保持未修改状态,但贮藏内容已保存。 包含未跟踪的文件 保留暂存区文件 信息 : @@ -637,7 +659,7 @@ 贮藏本地变更 应用(apply) 删除(drop) - 应用并删除(pop) + 另存为补丁... 丢弃贮藏确认 丢弃贮藏 : 贮藏列表 @@ -693,7 +715,7 @@ 忽略同目录下所有 *{0} 文件 忽略同目录下所有文件 忽略本文件 - 修补(--amend) + 修补 现在您已可将其加入暂存区中 提交 提交并推送 @@ -707,6 +729,7 @@ 显示未跟踪文件 没有提交信息记录 没有可应用的提交信息模板 + 署名 已暂存 从暂存区移除选中 从暂存区移除所有 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 90444967..8e823f68 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -4,12 +4,6 @@ 關於 關於 SourceGit - • 專案依賴於 - • 圖表繪製元件來自 - © 2024 sourcegit-scm - • 文字編輯器使用 - • 等寬字型來自於 - • 專案原始碼網址 開源免費的 Git 客戶端 新增工作區 簽出分支方式: @@ -22,21 +16,19 @@ 追蹤分支 設定遠端追蹤分支 AI 助理 + 重新產生 使用 AI 產生提交訊息 + 套用為提交訊息 套用修補檔 (apply patch) - 錯誤 - 輸出錯誤,並中止套用修補檔 - 更多錯誤 - 與 [錯誤] 級別相似,但輸出更多內容 修補檔: 選擇修補檔 忽略空白符號 - 忽略 - 關閉所有警告 套用修補檔 - 警告 - 套用修補檔,輸出關於空白字元的警告 空白字元處理: + 套用擱置變更 + 套用擱置變更後刪除 + 還原索引中已暫存的變更 (--index) + 已選擇擱置變更: 封存 (archive)... 封存檔案路徑: 選擇封存檔案的儲存路徑 @@ -50,10 +42,10 @@ 逐行溯源 (blame) 所選擇的檔案不支援該操作! 簽出 (checkout) ${0}$... - 與其他分支比較 與目前 HEAD 比較 與本機工作區比較 複製分支名稱 + 自訂動作 刪除 ${0}$... 刪除所選的 {0} 個分支 捨棄所有變更 @@ -67,9 +59,9 @@ 推送 (push) ${0}$ 重定基底 (rebase) ${0}$ 分支至 ${1}$... 重新命名 ${0}$... - 切換上游分支 - 取消設定上游分支 + 切換上游分支... 分支比較 + 追蹤上游分支不存在或已刪除! 位元組 取 消 重設檔案為此版本 @@ -86,7 +78,6 @@ 目標分支: 未提交變更: 捨棄變更 - 不做處理 擱置變更並自動復原 揀選提交 提交資訊中追加來源資訊 @@ -102,6 +93,7 @@ 本機存放庫名稱: 本機存放庫目錄的名稱,選填。 父級目錄: + 初始化並更新子模組 遠端存放庫: 關閉 提交訊息編輯器 @@ -150,25 +142,28 @@ 範本內容: 自訂動作 指令參數: - 使用 ${REPO} 表示存放庫路徑、${SHA} 表示所選的提交編號 + 使用 ${REPO} 表示存放庫路徑、${BRANCH} 表示所選的分支、${SHA} 表示所選的提交編號 可執行檔案路徑: 名稱: 執行範圍: + 選取的分支 選取的提交 存放庫 + 等待自訂動作執行結束 電子郵件 電子郵件地址 Git 設定 啟用定時自動提取 (fetch) 遠端更新 分鐘 預設遠端存放庫 - 提交訊息追加署名 (--signoff) - 拉取變更時進行清理 (--prune) Issue 追蹤 + 新增符合 Azure DevOps 規則 + 新增符合 Gitee 議題規則 + 新增符合 Gitee 合併請求規則 新增符合 GitHub Issue 規則 - 新增符合 Jira 規則 新增符合 GitLab 議題規則 新增符合 GitLab 合併請求規則 + 新增符合 Jira 規則 新增自訂規則 符合 Issue 的正規表達式: 規則名稱: @@ -194,16 +189,16 @@ 複製 複製全部內容 複製路徑 - 複製檔案名稱 + 複製完整路徑 新增分支... 新分支基於: 完成後切換到新分支 未提交變更: 捨棄變更 - 不做處理 擱置變更並自動復原 新分支名稱: 輸入分支名稱。 + 空格將以英文破折號取代 建立本機分支 新增標籤... 標籤位於: @@ -227,8 +222,11 @@ 您正在嘗試一次性刪除多個分支,請務必仔細檢查後再刪除! 刪除遠端確認 遠端名稱: + 路徑: 目標: + 所有子節點都會從清單中移除。 刪除群組確認 + 只會從清單中移除,而不會刪除磁碟中的檔案! 刪除存放庫確認 刪除子模組確認 子模組路徑: @@ -240,7 +238,9 @@ 原始大小 複製 檔案權限已變更 + 第一個差異 忽略空白符號變化 + 最後一個差異 LFS 物件變更 下一個差異 沒有變更或僅有換行字元差異 @@ -253,7 +253,7 @@ 交換比對雙方 語法上色 自動換行 - 啟用基於變更區塊的導航 + 區塊切換上/下一個差異 使用外部合併工具檢視 顯示檔案的全部內容 減少可見的行數 @@ -276,7 +276,7 @@ 快進 (fast-forward,無需 checkout) 提取 (fetch) 提取所有的遠端存放庫 - 啟用 [--force] 選項 + 強制覆寫本機 REFs 不提取遠端標籤 遠端存放庫: 提取遠端存放庫內容 @@ -351,7 +351,6 @@ 追蹤名稱為「{0}」的檔案 追蹤所有 *{0} 檔案 歷史記錄 - 切換橫向/縱向顯示 作者 修改時間 路線圖與訊息標題 @@ -369,7 +368,7 @@ 切換到上一個頁面 切換到下一個頁面 新增頁面 - 開啟偏好設定面板 + 開啟偏好設定面板 存放庫頁面快速鍵 提交暫存區變更 提交暫存區變更並推送 @@ -401,7 +400,7 @@ 合併操作進行中。 正在處理 重定基底 (rebase) 操作進行中。 - 当前停止于 + 目前停止於 復原提交操作進行中。 正在復原提交 互動式重定基底 @@ -414,8 +413,8 @@ 合併分支 目標分支: 合併方式: - 合併目標: - 合併(多目標) + 合併來源: + 合併 (多個來源) 提交變更 合併策略: 目標列表: @@ -436,6 +435,7 @@ 貼上 剛剛 {0} 分鐘前 + 1 小時前 {0} 小時前 昨天 {0} 天前 @@ -443,69 +443,74 @@ {0} 個月前 一年前 {0} 年前 - 偏好設定 - AI - 伺服器 - API 金鑰 - 模型 - 名稱 - 分析變更差異提示詞 - 產生提交訊息提示詞 - 外觀設定 - 預設字型 - 字型大小 - 預設 - 程式碼 - 等寬字型 - 僅在文字編輯器中使用等寬字型 - 佈景主題 - 自訂主題 - 使用固定寬度的分頁標籤 - 使用系統原生預設視窗樣式 - 對比/合併工具 - 安裝路徑 - 填寫可執行檔案所在路徑 - 工具 - 一般設定 - 啟動時檢查軟體更新 - 顯示語言 - 最大歷史提交數 - 在提交路線圖中顯示修改時間而非提交時間 - 在提交詳細資訊中顯示後續提交 - 提交標題字數偵測 - Git 設定 - 自動換行轉換 - 預設複製 (clone) 路徑 - 電子郵件 - 預設 Git 使用者電子郵件 - 安裝路徑 - 使用者名稱 - 預設 Git 使用者名稱 - Git 版本 - 本軟體要求 Git 最低版本為 2.23.0 - GPG 簽章 - 啟用提交簽章 - 啟用標籤簽章 - GPG 簽章格式 - 可執行檔案路徑 - 填寫 gpg.exe 所在路徑 - 使用者簽章金鑰 - 填寫簽章提交所使用的金鑰 - 第三方工具整合 - 終端機/Shell - 終端機/Shell - 安裝路徑 + 偏好設定 + AI + 分析變更差異提示詞 + API 金鑰 + 產生提交訊息提示詞 + 模型 + 名稱 + 伺服器 + 啟用串流輸出 + 外觀設定 + 預設字型 + 編輯器制表符寬度 + 字型大小 + 預設 + 程式碼 + 等寬字型 + 僅在文字編輯器中使用等寬字型 + 佈景主題 + 自訂主題 + 使用固定寬度的分頁標籤 + 使用系統原生預設視窗樣式 + 對比/合併工具 + 安裝路徑 + 填寫可執行檔案所在路徑 + 工具 + 一般設定 + 啟動時檢查軟體更新 + 日期時間格式 + 顯示語言 + 最大歷史提交數 + 在提交路線圖中顯示修改時間而非提交時間 + 在提交詳細資訊中顯示後續提交 + 在路線圖中顯示標籤 + 提交標題字數偵測 + Git 設定 + 自動換行轉換 + 預設複製 (clone) 路徑 + 電子郵件 + 預設 Git 使用者電子郵件 + 拉取變更時進行清理 (--prune) + 安裝路徑 + 啟用 HTTP SSL 驗證 + 使用者名稱 + 預設 Git 使用者名稱 + Git 版本 + 本軟體要求 Git 最低版本為 2.23.0 + GPG 簽章 + 啟用提交簽章 + 啟用標籤簽章 + GPG 簽章格式 + 可執行檔案路徑 + 填寫 gpg.exe 所在路徑 + 使用者簽章金鑰 + 填寫簽章提交所使用的金鑰 + 第三方工具整合 + 終端機/Shell + 終端機/Shell + 安裝路徑 清理遠端已刪除分支 目標: 清理工作區 - 清理在 `$GIT_DIR/worktrees` 中的無效工作區資訊 + 清理在 `$GIT_COMMON_DIR/worktrees` 中的無效工作區資訊 拉取 (pull) 拉取分支: 拉取遠端中的所有分支 本機分支: 未提交變更: 捨棄變更 - 不做處理 擱置變更並自動復原 不拉取遠端標籤 遠端: @@ -565,23 +570,29 @@ 取消指定 在提交列表中隱藏 以其篩選提交列表 - 切換排序方式 - 依提交時間排序 (--date-order) - 依拓撲排序 (--topo-order) + 啟用 [--first-parent] 選項 + 版面配置 + 橫向顯示 + 縱向顯示 + 提交顯示順序 + 依時間排序 + 依拓撲排序 本機分支 回到 HEAD - 啟用 [--first-parent] 選項 新增分支 + 清除所有通知 + 在提交路線圖中僅對目前分支上色 在 {0} 中開啟 使用外部工具開啟 重新載入 遠端列表 新增遠端 搜尋提交 + 作者 + 提交者 檔案 提交訊息 提交編號 - 作者及提交者 僅搜尋目前分支 以樹型結構展示 跳過此提交 @@ -591,7 +602,12 @@ 更新子模組 標籤列表 新增標籤 + 依建立時間 + 依名稱升序 + 依名稱降序 + 排序 在終端機中開啟 + 在提交列表中使用相對時間 工作區列表 新增工作區 清理 @@ -619,6 +635,10 @@ 忽略此版本 軟體更新 目前已是最新版本。 + 切換上游分支 + 本機分支: + 取消設定上游分支 + 上游分支: 複製提交編號 前往此提交 壓縮為單個提交 @@ -627,6 +647,8 @@ SSH 金鑰檔案 開 始 擱置變更 (stash) + 擱置變更後自動復原工作區 + 工作區檔案保持未修改,但擱置內容已儲存。 包含未追蹤的檔案 保留已暫存的變更 擱置變更訊息: @@ -636,7 +658,7 @@ 擱置本機變更 套用 (apply) 刪除 (drop) - 套用並刪除 (pop) + 另存為修補檔 (patch)... 捨棄擱置變更確認 捨棄擱置變更: 擱置變更 @@ -692,13 +714,13 @@ 忽略同路徑下所有 *{0} 檔案 忽略同路徑下所有檔案 忽略本檔案 - 修補 (--amend) + 修補 現在您已可將其加入暫存區中 提 交 提交並推送 歷史輸入/範本 觸發點擊事件 - 提交(修改現有提交) + 提交 (修改原始提交) 自動暫存全部變更並提交 未包含任何檔案變更! 您是否仍要提交 (--allow-empty)? 檢測到衝突 @@ -706,6 +728,7 @@ 顯示未追蹤檔案 沒有提交訊息記錄 沒有可套用的提交訊息範本 + 署名 已暫存 取消暫存選取的檔案 取消暫存所有檔案 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index 2cc09e39..38321356 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -2,8 +2,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="using:SourceGit" xmlns:vm="using:SourceGit.ViewModels" + xmlns:v="using:SourceGit.Views" xmlns:c="using:SourceGit.Converters" xmlns:ae="using:AvaloniaEdit" + xmlns:aee="using:AvaloniaEdit.Editing" xmlns:aes="using:AvaloniaEdit.Search"> @@ -158,7 +160,7 @@ + + - - - - - - - - - - + - + + DoubleTapped="OnRowDoubleTapped" + ToolTip.Tip="{Binding FullPath}"> - + - + SetValue(ChangesProperty, value); } + public static readonly StyledProperty AutoSelectFirstChangeProperty = + AvaloniaProperty.Register(nameof(AutoSelectFirstChange), false); + + public bool AutoSelectFirstChange + { + get => GetValue(AutoSelectFirstChangeProperty); + set => SetValue(AutoSelectFirstChangeProperty, value); + } + public static readonly StyledProperty> SelectedChangesProperty = AvaloniaProperty.Register>(nameof(SelectedChanges)); @@ -205,9 +214,9 @@ namespace SourceGit.Views base.OnPropertyChanged(change); if (change.Property == ViewModeProperty) - UpdateDataSource(false); - else if (change.Property == ChangesProperty) UpdateDataSource(true); + else if (change.Property == ChangesProperty) + UpdateDataSource(false); else if (change.Property == SelectedChangesProperty) UpdateSelection(); } @@ -292,9 +301,9 @@ namespace SourceGit.Views } } - private void UpdateDataSource(bool disableEvents) + private void UpdateDataSource(bool onlyViewModeChange) { - _disableSelectionChangingEvent = disableEvents; + _disableSelectionChangingEvent = !onlyViewModeChange; var changes = Changes; if (changes == null || changes.Count == 0) @@ -324,7 +333,19 @@ namespace SourceGit.Views MakeTreeRows(rows, tree.Tree); tree.Rows.AddRange(rows); - if (selected.Count > 0) + if (!onlyViewModeChange && AutoSelectFirstChange) + { + foreach (var row in tree.Rows) + { + if (row.Change != null) + { + tree.SelectedRows.Add(row); + SetCurrentValue(SelectedChangesProperty, [row.Change]); + break; + } + } + } + else if (selected.Count > 0) { var sets = new HashSet(); foreach (var c in selected) @@ -346,16 +367,34 @@ namespace SourceGit.Views { var grid = new ViewModels.ChangeCollectionAsGrid(); grid.Changes.AddRange(changes); - if (selected.Count > 0) + + if (!onlyViewModeChange && AutoSelectFirstChange) + { + grid.SelectedChanges.Add(changes[0]); + SetCurrentValue(SelectedChangesProperty, [changes[0]]); + } + else if (selected.Count > 0) + { grid.SelectedChanges.AddRange(selected); + } + Content = grid; } else { var list = new ViewModels.ChangeCollectionAsList(); list.Changes.AddRange(changes); - if (selected.Count > 0) + + if (!onlyViewModeChange && AutoSelectFirstChange) + { + list.SelectedChanges.Add(changes[0]); + SetCurrentValue(SelectedChangesProperty, [changes[0]]); + } + else if (selected.Count > 0) + { list.SelectedChanges.AddRange(selected); + } + Content = list; } diff --git a/src/Views/ChangeViewModeSwitcher.axaml b/src/Views/ChangeViewModeSwitcher.axaml index 4ded60c7..911fb41d 100644 --- a/src/Views/ChangeViewModeSwitcher.axaml +++ b/src/Views/ChangeViewModeSwitcher.axaml @@ -6,38 +6,38 @@ xmlns:v="using:SourceGit.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.ChangeViewModeSwitcher" - x:DataType="v:ChangeViewModeSwitcher"> + x:Name="ThisControl"> diff --git a/src/Views/ChangeViewModeSwitcher.axaml.cs b/src/Views/ChangeViewModeSwitcher.axaml.cs index 0cb2c4a9..ed306619 100644 --- a/src/Views/ChangeViewModeSwitcher.axaml.cs +++ b/src/Views/ChangeViewModeSwitcher.axaml.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Interactivity; namespace SourceGit.Views { @@ -16,13 +17,25 @@ namespace SourceGit.Views public ChangeViewModeSwitcher() { - DataContext = this; InitializeComponent(); } - public void SwitchMode(object param) + private void SwitchToList(object sender, RoutedEventArgs e) { - ViewMode = (Models.ChangeViewMode)param; + ViewMode = Models.ChangeViewMode.List; + e.Handled = true; + } + + private void SwitchToGrid(object sender, RoutedEventArgs e) + { + ViewMode = Models.ChangeViewMode.Grid; + e.Handled = true; + } + + private void SwitchToTree(object sender, RoutedEventArgs e) + { + ViewMode = Models.ChangeViewMode.Tree; + e.Handled = true; } } } diff --git a/src/Views/Checkout.axaml b/src/Views/Checkout.axaml index eb1c9de0..3cdfd94e 100644 --- a/src/Views/Checkout.axaml +++ b/src/Views/Checkout.axaml @@ -18,7 +18,7 @@ - + - - + - - + Content="{DynamicResource Text.CreateBranch.LocalChanges.StashAndReply}" + IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/> + diff --git a/src/Views/Checkout.axaml.cs b/src/Views/Checkout.axaml.cs index da6e6b31..f8398a1d 100644 --- a/src/Views/Checkout.axaml.cs +++ b/src/Views/Checkout.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Interactivity; namespace SourceGit.Views { @@ -9,51 +8,5 @@ namespace SourceGit.Views { InitializeComponent(); } - - protected override void OnLoaded(RoutedEventArgs e) - { - base.OnLoaded(e); - - var vm = DataContext as ViewModels.Checkout; - if (vm == null) - return; - - switch (vm.PreAction) - { - case Models.DealWithLocalChanges.DoNothing: - RadioDoNothing.IsChecked = true; - break; - case Models.DealWithLocalChanges.StashAndReaply: - RadioStashAndReply.IsChecked = true; - break; - default: - RadioDiscard.IsChecked = true; - break; - } - } - - private void OnLocalChangeActionIsCheckedChanged(object sender, RoutedEventArgs e) - { - var vm = DataContext as ViewModels.Checkout; - if (vm == null) - return; - - if (RadioDoNothing.IsChecked == true) - { - if (vm.PreAction != Models.DealWithLocalChanges.DoNothing) - vm.PreAction = Models.DealWithLocalChanges.DoNothing; - return; - } - - if (RadioStashAndReply.IsChecked == true) - { - if (vm.PreAction != Models.DealWithLocalChanges.StashAndReaply) - vm.PreAction = Models.DealWithLocalChanges.StashAndReaply; - return; - } - - if (vm.PreAction != Models.DealWithLocalChanges.Discard) - vm.PreAction = Models.DealWithLocalChanges.Discard; - } } } diff --git a/src/Views/CheckoutCommit.axaml b/src/Views/CheckoutCommit.axaml index 3ee3943f..9b418823 100644 --- a/src/Views/CheckoutCommit.axaml +++ b/src/Views/CheckoutCommit.axaml @@ -30,16 +30,16 @@ + Margin="0,0,8,0" + IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/> + GroupName="LocalChanges"/> - - + - - + diff --git a/src/Views/ChromelessWindow.cs b/src/Views/ChromelessWindow.cs index 107a7ba3..647c657e 100644 --- a/src/Views/ChromelessWindow.cs +++ b/src/Views/ChromelessWindow.cs @@ -11,7 +11,7 @@ namespace SourceGit.Views { public bool UseSystemWindowFrame { - get => OperatingSystem.IsLinux() && ViewModels.Preference.Instance.UseSystemWindowFrame; + get => OperatingSystem.IsLinux() && ViewModels.Preferences.Instance.UseSystemWindowFrame; } protected override Type StyleKeyOverride => typeof(Window); diff --git a/src/Views/Clone.axaml b/src/Views/Clone.axaml index 25c46a00..8c7c9faf 100644 --- a/src/Views/Clone.axaml +++ b/src/Views/Clone.axaml @@ -10,15 +10,15 @@ - - + - - - - + + diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 7b498b10..f83b43fc 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -19,14 +19,14 @@ - - - - - + + + + + @@ -35,14 +35,14 @@ - - - - - + + + + + @@ -55,9 +55,9 @@ - + - + - + - + @@ -60,44 +73,57 @@ ToolTip.Tip="{DynamicResource Text.Diff.Next}"> - + + + - - - - + @@ -106,7 +132,7 @@ @@ -114,28 +140,29 @@ - + - + @@ -143,10 +170,10 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - + + - + @@ -246,7 +258,7 @@ HorizontalAlignment="Center" Fill="{DynamicResource Brush.FG2}" IsVisible="{Binding SelectedItem, Converter={x:Static ObjectConverters.IsNull}}"/> - + @@ -260,12 +272,12 @@ - @@ -104,7 +104,7 @@ - + diff --git a/src/Views/LFSTrackCustomPattern.axaml b/src/Views/LFSTrackCustomPattern.axaml index 36eaef65..f60304d5 100644 --- a/src/Views/LFSTrackCustomPattern.axaml +++ b/src/Views/LFSTrackCustomPattern.axaml @@ -11,7 +11,7 @@ - + + WindowStartupLocation="CenterScreen"> @@ -35,7 +35,7 @@ - + - + - + diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index a3a3af8b..9ccec78c 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -34,14 +34,14 @@ namespace SourceGit.Views get { if (OperatingSystem.IsLinux()) - return !ViewModels.Preference.Instance.UseSystemWindowFrame; + return !ViewModels.Preferences.Instance.UseSystemWindowFrame; return OperatingSystem.IsWindows(); } } public Launcher() { - var layout = ViewModels.Preference.Instance.Layout; + var layout = ViewModels.Preferences.Instance.Layout; if (layout.LauncherWindowState != WindowState.Maximized) { Width = layout.LauncherWidth; @@ -81,7 +81,7 @@ namespace SourceGit.Views { base.OnOpened(e); - var state = ViewModels.Preference.Instance.Layout.LauncherWindowState; + var state = ViewModels.Preferences.Instance.Layout.LauncherWindowState; if (state == WindowState.Maximized || state == WindowState.FullScreen) WindowState = WindowState.Maximized; } @@ -99,7 +99,7 @@ namespace SourceGit.Views if (OperatingSystem.IsMacOS()) HasLeftCaptionButton = state != WindowState.FullScreen; - ViewModels.Preference.Instance.Layout.LauncherWindowState = state; + ViewModels.Preferences.Instance.Layout.LauncherWindowState = state; } } @@ -112,14 +112,32 @@ namespace SourceGit.Views // We should clear all unhandled key modifiers. _unhandledModifiers = KeyModifiers.None; - // Ctrl+Shift+P opens preference dialog (macOS use hotkeys in system menu bar) - if (!OperatingSystem.IsMacOS() && e.KeyModifiers == (KeyModifiers.Control | KeyModifiers.Shift) && e.Key == Key.P) + // Check for AltGr (which is detected as Ctrl+Alt) + bool isAltGr = e.KeyModifiers.HasFlag(KeyModifiers.Control) && + e.KeyModifiers.HasFlag(KeyModifiers.Alt); + + // Skip hotkey processing if AltGr is pressed + if (isAltGr) { - App.OpenDialog(new Preference()); + base.OnKeyDown(e); + return; + } + + // Ctrl+Shift+P opens preference dialog (macOS use hotkeys in system menu bar) + if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: (KeyModifiers.Control | KeyModifiers.Shift), Key: Key.P }) + { + App.OpenDialog(new Preferences()); e.Handled = true; return; } + // Ctrl+Q quits the application (macOS use hotkeys in system menu bar) + if (!OperatingSystem.IsMacOS() && e.KeyModifiers == KeyModifiers.Control && e.Key == Key.Q) + { + App.Quit(0); + return; + } + if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control)) { if (e.Key == Key.W) @@ -141,7 +159,7 @@ namespace SourceGit.Views if (vm.ActivePage.Data is not ViewModels.Welcome) vm.AddNewTab(); - ViewModels.Welcome.Instance.Clone(); + ViewModels.Welcome.Instance.Clone(); e.Handled = true; return; } @@ -236,13 +254,13 @@ namespace SourceGit.Views { _unhandledModifiers = e.KeyModifiers; - if (!_unhandledModifiers.HasFlag(KeyModifiers.Alt) && (e.Key == Key.LeftAlt || e.Key == Key.RightAlt)) + if (!_unhandledModifiers.HasFlag(KeyModifiers.Alt) && e.Key is Key.LeftAlt or Key.RightAlt) _unhandledModifiers |= KeyModifiers.Alt; - if (!_unhandledModifiers.HasFlag(KeyModifiers.Control) && (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)) + if (!_unhandledModifiers.HasFlag(KeyModifiers.Control) && e.Key is Key.LeftCtrl or Key.RightCtrl) _unhandledModifiers |= KeyModifiers.Control; - if (!_unhandledModifiers.HasFlag(KeyModifiers.Shift) && (e.Key == Key.LeftShift || e.Key == Key.RightShift)) + if (!_unhandledModifiers.HasFlag(KeyModifiers.Shift) && e.Key is Key.LeftShift or Key.RightShift) _unhandledModifiers |= KeyModifiers.Shift; } } @@ -255,8 +273,10 @@ namespace SourceGit.Views protected override void OnClosing(WindowClosingEventArgs e) { - (DataContext as ViewModels.Launcher)?.Quit(Width, Height); base.OnClosing(e); + + if (!Design.IsDesignMode && DataContext is ViewModels.Launcher launcher) + launcher.Quit(Width, Height); } private void OnOpenWorkspaceMenu(object sender, RoutedEventArgs e) diff --git a/src/Views/LauncherPage.axaml b/src/Views/LauncherPage.axaml index d09c2c0d..7ce450de 100644 --- a/src/Views/LauncherPage.axaml +++ b/src/Views/LauncherPage.axaml @@ -17,14 +17,14 @@ - + - + @@ -32,7 +32,7 @@ - + @@ -53,7 +53,7 @@ PointerPressed="OnMaskClicked" IsVisible="{Binding Popup, Converter={x:Static ObjectConverters.IsNotNull}}"/> - + + + + - + @@ -122,14 +138,12 @@ + - - - - - + + + diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml index 40a2efe6..73b48f58 100644 --- a/src/Views/LauncherTabBar.axaml +++ b/src/Views/LauncherTabBar.axaml @@ -4,11 +4,17 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:SourceGit.ViewModels" xmlns:c="using:SourceGit.Converters" + xmlns:v="using:SourceGit.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.LauncherTabBar" - x:DataType="vm:Launcher"> + x:DataType="vm:Launcher" + x:Name="ThisControl"> - + @@ -48,8 +54,8 @@ - - + + + Command="{Binding AddNewTab}" + IsVisible="{Binding !#ThisControl.IsScrollerVisible}"> @@ -112,8 +119,30 @@ - - - + + + + + + + + + diff --git a/src/Views/LauncherTabBar.axaml.cs b/src/Views/LauncherTabBar.axaml.cs index f8c9107c..12bca91f 100644 --- a/src/Views/LauncherTabBar.axaml.cs +++ b/src/Views/LauncherTabBar.axaml.cs @@ -10,6 +10,15 @@ namespace SourceGit.Views { public partial class LauncherTabBar : UserControl { + public static readonly StyledProperty IsScrollerVisibleProperty = + AvaloniaProperty.Register(nameof(IsScrollerVisible)); + + public bool IsScrollerVisible + { + get => GetValue(IsScrollerVisibleProperty); + set => SetValue(IsScrollerVisibleProperty, value); + } + public LauncherTabBar() { InitializeComponent(); @@ -43,6 +52,9 @@ namespace SourceGit.Views if (containerEndX < startX || containerEndX > endX) continue; + if (IsScrollerVisible && i == count - 1) + break; + var separatorX = containerEndX - startX + LauncherTabsScroller.Bounds.X; context.DrawLine(separatorPen, new Point(separatorX, separatorY), new Point(separatorX, separatorY + 20)); } @@ -88,7 +100,7 @@ namespace SourceGit.Views x = drawRightX - 6; } - if (drawRightX < LauncherTabsScroller.Bounds.Right) + if (drawRightX <= LauncherTabsScroller.Bounds.Right) { ctx.LineTo(new Point(x, y)); x = drawRightX; @@ -140,19 +152,7 @@ namespace SourceGit.Views private void OnTabsLayoutUpdated(object _1, EventArgs _2) { - if (LauncherTabsScroller.Extent.Width > LauncherTabsScroller.Viewport.Width) - { - LeftScrollIndicator.IsVisible = true; - LeftScrollIndicator.IsEnabled = LauncherTabsScroller.Offset.X > 0; - RightScrollIndicator.IsVisible = true; - RightScrollIndicator.IsEnabled = LauncherTabsScroller.Offset.X < LauncherTabsScroller.Extent.Width - LauncherTabsScroller.Viewport.Width; - } - else - { - LeftScrollIndicator.IsVisible = false; - RightScrollIndicator.IsVisible = false; - } - + SetCurrentValue(IsScrollerVisibleProperty, LauncherTabsScroller.Extent.Width > LauncherTabsScroller.Viewport.Width); InvalidateVisual(); } @@ -248,6 +248,15 @@ namespace SourceGit.Views e.Handled = true; } + private void OnGotoSelectedPage(object sender, LauncherTabSelectedEventArgs e) + { + if (DataContext is ViewModels.Launcher vm) + vm.ActivePage = e.Page; + + PageSelector.Flyout?.Hide(); + e.Handled = true; + } + private bool _pressedTab = false; private Point _pressedTabPosition = new Point(); private bool _startDragTab = false; diff --git a/src/Views/LauncherTabsSelector.axaml b/src/Views/LauncherTabsSelector.axaml new file mode 100644 index 00000000..109a2ce7 --- /dev/null +++ b/src/Views/LauncherTabsSelector.axaml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/LauncherTabsSelector.axaml.cs b/src/Views/LauncherTabsSelector.axaml.cs new file mode 100644 index 00000000..61d7a966 --- /dev/null +++ b/src/Views/LauncherTabsSelector.axaml.cs @@ -0,0 +1,120 @@ +using System; + +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Interactivity; + +namespace SourceGit.Views +{ + public class LauncherTabSelectedEventArgs : RoutedEventArgs + { + public ViewModels.LauncherPage Page { get; } + + public LauncherTabSelectedEventArgs(ViewModels.LauncherPage page) + { + RoutedEvent = LauncherTabsSelector.PageSelectedEvent; + Page = page; + } + } + + public partial class LauncherTabsSelector : UserControl + { + public static readonly StyledProperty> PagesProperty = + AvaloniaProperty.Register>(nameof(Pages)); + + public AvaloniaList Pages + { + get => GetValue(PagesProperty); + set => SetValue(PagesProperty, value); + } + + public static readonly StyledProperty SearchFilterProperty = + AvaloniaProperty.Register(nameof(SearchFilter)); + + public string SearchFilter + { + get => GetValue(SearchFilterProperty); + set => SetValue(SearchFilterProperty, value); + } + + public static readonly RoutedEvent PageSelectedEvent = + RoutedEvent.Register(nameof(PageSelected), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + public event EventHandler PageSelected + { + add { AddHandler(PageSelectedEvent, value); } + remove { RemoveHandler(PageSelectedEvent, value); } + } + + public AvaloniaList VisiblePages + { + get; + private set; + } + + public LauncherTabsSelector() + { + VisiblePages = new AvaloniaList(); + InitializeComponent(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == PagesProperty || change.Property == SearchFilterProperty) + UpdateVisiblePages(); + } + + private void OnClearSearchFilter(object sender, RoutedEventArgs e) + { + SearchFilter = string.Empty; + } + + private void OnPageSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is ListBox { SelectedItem: ViewModels.LauncherPage page }) + { + _isProcessingSelection = true; + RaiseEvent(new LauncherTabSelectedEventArgs(page)); + _isProcessingSelection = false; + } + + e.Handled = true; + } + + private void UpdateVisiblePages() + { + if (_isProcessingSelection) + return; + + VisiblePages.Clear(); + + if (Pages == null) + return; + + var filter = SearchFilter?.Trim() ?? ""; + if (string.IsNullOrEmpty(filter)) + { + foreach (var p in Pages) + VisiblePages.Add(p); + + return; + } + + foreach (var page in Pages) + { + if (!page.Node.IsRepository) + continue; + + if (page.Node.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) || + page.Node.Id.Contains(filter, StringComparison.OrdinalIgnoreCase)) + VisiblePages.Add(page); + } + } + + private bool _isProcessingSelection = false; + } +} + diff --git a/src/Views/MenuItemExtension.cs b/src/Views/MenuItemExtension.cs new file mode 100644 index 00000000..1c23b2ea --- /dev/null +++ b/src/Views/MenuItemExtension.cs @@ -0,0 +1,12 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data; + +namespace SourceGit.Views +{ + public class MenuItemExtension : AvaloniaObject + { + public static readonly AttachedProperty CommandProperty = + AvaloniaProperty.RegisterAttached("Command", string.Empty, false, BindingMode.OneWay); + } +} diff --git a/src/Views/Merge.axaml b/src/Views/Merge.axaml index 805ac6d0..33d07f02 100644 --- a/src/Views/Merge.axaml +++ b/src/Views/Merge.axaml @@ -60,15 +60,32 @@ Height="28" Padding="8,0" VerticalAlignment="Center" HorizontalAlignment="Stretch" ItemsSource="{Binding Source={x:Static m:MergeMode.Supported}}" - SelectedItem="{Binding SelectedMode, Mode=TwoWay}"> + SelectedItem="{Binding Mode, Mode=TwoWay}" + Grid.IsSharedSizeScope="True"> - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/MergeMultiple.axaml b/src/Views/MergeMultiple.axaml index bb543e16..332d9fef 100644 --- a/src/Views/MergeMultiple.axaml +++ b/src/Views/MergeMultiple.axaml @@ -12,7 +12,7 @@ - + - + @@ -84,7 +84,7 @@ - + diff --git a/src/Views/PopupRunningStatus.axaml b/src/Views/PopupRunningStatus.axaml index 0522e46b..a8467415 100644 --- a/src/Views/PopupRunningStatus.axaml +++ b/src/Views/PopupRunningStatus.axaml @@ -9,19 +9,19 @@ x:Name="ThisControl"> - + - + - + @@ -29,7 +30,7 @@ IsVisible="{OnPlatform True, macOS=False}"/> @@ -43,11 +44,11 @@ - + - + - + + + + + + + + + + + + @@ -73,11 +94,11 @@ - - - - + - - + Content="{DynamicResource Text.Preferences.General.ShowAuthorTime}" + IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowAuthorTimeInGraph, Mode=TwoWay}"/> + Content="{DynamicResource Text.Preferences.General.ShowTagsInGraph}" + IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowTagsInGraph, Mode=TwoWay}"/> + + + + - + - + + Minimum="10" Maximum="18" Increment="0.5" + Height="28" + Padding="4" + BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" + CornerRadius="3" + Value="{Binding DefaultFontSize, Mode=TwoWay}"> - + @@ -189,17 +216,30 @@ Value="{Binding EditorFontSize, Mode=TwoWay}"> - + - + + + + + @@ -210,19 +250,19 @@ - - + Content="{DynamicResource Text.Preferences.Appearance.OnlyUseMonoFontInEditor}" + IsChecked="{Binding OnlyUseMonoFontInEditor, Mode=TwoWay}"/> + + @@ -230,18 +270,19 @@ - + - + + Text="{Binding GitInstallPath, Mode=TwoWay}" + TextChanged="OnGitInstallPathChanged"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -554,19 +719,19 @@ - + - + - + - + - - + + - + + + diff --git a/src/Views/Preference.axaml.cs b/src/Views/Preferences.axaml.cs similarity index 68% rename from src/Views/Preference.axaml.cs rename to src/Views/Preferences.axaml.cs index e54d4c25..73b2e995 100644 --- a/src/Views/Preference.axaml.cs +++ b/src/Views/Preferences.axaml.cs @@ -8,7 +8,7 @@ using Avalonia.Platform.Storage; namespace SourceGit.Views { - public partial class Preference : ChromelessWindow + public partial class Preferences : ChromelessWindow { public string DefaultUser { @@ -28,8 +28,14 @@ namespace SourceGit.Views set; } = null; + public bool EnablePruneOnFetch + { + get; + set; + } + public static readonly StyledProperty GitVersionProperty = - AvaloniaProperty.Register(nameof(GitVersion)); + AvaloniaProperty.Register(nameof(GitVersion)); public string GitVersion { @@ -37,6 +43,15 @@ namespace SourceGit.Views set => SetValue(GitVersionProperty, value); } + public static readonly StyledProperty ShowGitVersionWarningProperty = + AvaloniaProperty.Register(nameof(ShowGitVersionWarning)); + + public bool ShowGitVersionWarning + { + get => GetValue(ShowGitVersionWarningProperty); + set => SetValue(ShowGitVersionWarningProperty, value); + } + public bool EnableGPGCommitSigning { get; @@ -50,7 +65,7 @@ namespace SourceGit.Views } public static readonly StyledProperty GPGFormatProperty = - AvaloniaProperty.Register(nameof(GPGFormat), Models.GPGFormat.Supported[0]); + AvaloniaProperty.Register(nameof(GPGFormat), Models.GPGFormat.Supported[0]); public Models.GPGFormat GPGFormat { @@ -59,7 +74,7 @@ namespace SourceGit.Views } public static readonly StyledProperty GPGExecutableFileProperty = - AvaloniaProperty.Register(nameof(GPGExecutableFile)); + AvaloniaProperty.Register(nameof(GPGExecutableFile)); public string GPGExecutableFile { @@ -73,8 +88,14 @@ namespace SourceGit.Views set; } + public bool EnableHTTPSSLVerify + { + get; + set; + } = false; + public static readonly StyledProperty SelectedOpenAIServiceProperty = - AvaloniaProperty.Register(nameof(SelectedOpenAIService)); + AvaloniaProperty.Register(nameof(SelectedOpenAIService)); public Models.OpenAIService SelectedOpenAIService { @@ -82,12 +103,20 @@ namespace SourceGit.Views set => SetValue(SelectedOpenAIServiceProperty, value); } - public Preference() + public static readonly StyledProperty SelectedCustomActionProperty = + AvaloniaProperty.Register(nameof(SelectedCustomAction)); + + public Models.CustomAction SelectedCustomAction { - var pref = ViewModels.Preference.Instance; + get => GetValue(SelectedCustomActionProperty); + set => SetValue(SelectedCustomActionProperty, value); + } + + public Preferences() + { + var pref = ViewModels.Preferences.Instance; DataContext = pref; - var ver = string.Empty; if (pref.IsGitConfigured()) { var config = new Commands.Config(null).ListAll(); @@ -100,6 +129,8 @@ namespace SourceGit.Views GPGUserKey = signingKey; if (config.TryGetValue("core.autocrlf", out var crlf)) CRLFMode = Models.CRLFMode.Supported.Find(x => x.Value == crlf); + if (config.TryGetValue("fetch.prune", out var pruneOnFetch)) + EnablePruneOnFetch = (pruneOnFetch == "true"); if (config.TryGetValue("commit.gpgsign", out var gpgCommitSign)) EnableGPGCommitSigning = (gpgCommitSign == "true"); if (config.TryGetValue("tag.gpgsign", out var gpgTagSign)) @@ -112,11 +143,14 @@ namespace SourceGit.Views else if (config.TryGetValue($"gpg.{GPGFormat.Value}.program", out var gpgProgram)) GPGExecutableFile = gpgProgram; - ver = new Commands.Version().Query(); + if (config.TryGetValue("http.sslverify", out var sslVerify)) + EnableHTTPSSLVerify = sslVerify == "true"; + else + EnableHTTPSSLVerify = true; } + UpdateGitVersion(); InitializeComponent(); - GitVersion = ver; } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) @@ -135,13 +169,20 @@ namespace SourceGit.Views protected override void OnClosing(WindowClosingEventArgs e) { + base.OnClosing(e); + + if (Design.IsDesignMode) + return; + var config = new Commands.Config(null).ListAll(); SetIfChanged(config, "user.name", DefaultUser, ""); SetIfChanged(config, "user.email", DefaultEmail, ""); SetIfChanged(config, "user.signingkey", GPGUserKey, ""); SetIfChanged(config, "core.autocrlf", CRLFMode != null ? CRLFMode.Value : null, null); + SetIfChanged(config, "fetch.prune", EnablePruneOnFetch ? "true" : "false", "false"); SetIfChanged(config, "commit.gpgsign", EnableGPGCommitSigning ? "true" : "false", "false"); SetIfChanged(config, "tag.gpgsign", EnableGPGTagSigning ? "true" : "false", "false"); + SetIfChanged(config, "http.sslverify", EnableHTTPSSLVerify ? "" : "false", ""); SetIfChanged(config, "gpg.format", GPGFormat.Value, "openpgp"); if (!GPGFormat.Value.Equals("ssh", StringComparison.Ordinal)) @@ -162,7 +203,7 @@ namespace SourceGit.Views new Commands.Config(null).Set($"gpg.{GPGFormat.Value}.program", GPGExecutableFile); } - base.OnClosing(e); + ViewModels.Preferences.Instance.Save(); } private async void SelectThemeOverrideFile(object _, RoutedEventArgs e) @@ -176,7 +217,7 @@ namespace SourceGit.Views var selected = await StorageProvider.OpenFilePickerAsync(options); if (selected.Count == 1) { - ViewModels.Preference.Instance.ThemeOverrides = selected[0].Path.LocalPath; + ViewModels.Preferences.Instance.ThemeOverrides = selected[0].Path.LocalPath; } e.Handled = true; @@ -194,8 +235,8 @@ namespace SourceGit.Views var selected = await StorageProvider.OpenFilePickerAsync(options); if (selected.Count == 1) { - ViewModels.Preference.Instance.GitInstallPath = selected[0].Path.LocalPath; - GitVersion = new Commands.Version().Query(); + ViewModels.Preferences.Instance.GitInstallPath = selected[0].Path.LocalPath; + UpdateGitVersion(); } e.Handled = true; @@ -209,7 +250,7 @@ namespace SourceGit.Views var selected = await StorageProvider.OpenFolderPickerAsync(options); if (selected.Count == 1) { - ViewModels.Preference.Instance.GitDefaultCloneDir = selected[0].Path.LocalPath; + ViewModels.Preferences.Instance.GitDefaultCloneDir = selected[0].Path.LocalPath; } } catch (Exception ex) @@ -245,7 +286,7 @@ namespace SourceGit.Views private async void SelectShellOrTerminal(object _, RoutedEventArgs e) { - var type = ViewModels.Preference.Instance.ShellOrTerminal; + var type = ViewModels.Preferences.Instance.ShellOrTerminal; if (type == -1) return; @@ -259,7 +300,7 @@ namespace SourceGit.Views var selected = await StorageProvider.OpenFilePickerAsync(options); if (selected.Count == 1) { - ViewModels.Preference.Instance.ShellOrTerminalPath = selected[0].Path.LocalPath; + ViewModels.Preferences.Instance.ShellOrTerminalPath = selected[0].Path.LocalPath; } e.Handled = true; @@ -267,10 +308,10 @@ namespace SourceGit.Views private async void SelectExternalMergeTool(object _, RoutedEventArgs e) { - var type = ViewModels.Preference.Instance.ExternalMergeToolType; + var type = ViewModels.Preferences.Instance.ExternalMergeToolType; if (type < 0 || type >= Models.ExternalMerger.Supported.Count) { - ViewModels.Preference.Instance.ExternalMergeToolType = 0; + ViewModels.Preferences.Instance.ExternalMergeToolType = 0; e.Handled = true; return; } @@ -285,7 +326,7 @@ namespace SourceGit.Views var selected = await StorageProvider.OpenFilePickerAsync(options); if (selected.Count == 1) { - ViewModels.Preference.Instance.ExternalMergeToolPath = selected[0].Path.LocalPath; + ViewModels.Preferences.Instance.ExternalMergeToolPath = selected[0].Path.LocalPath; } e.Handled = true; @@ -307,7 +348,7 @@ namespace SourceGit.Views { if (sender is CheckBox box) { - ViewModels.Preference.Instance.UseSystemWindowFrame = box.IsChecked == true; + ViewModels.Preferences.Instance.UseSystemWindowFrame = box.IsChecked == true; var dialog = new ConfirmRestart(); App.OpenDialog(dialog); @@ -316,10 +357,15 @@ namespace SourceGit.Views e.Handled = true; } + private void OnGitInstallPathChanged(object sender, TextChangedEventArgs e) + { + UpdateGitVersion(); + } + private void OnAddOpenAIService(object sender, RoutedEventArgs e) { var service = new Models.OpenAIService() { Name = "Unnamed Service" }; - ViewModels.Preference.Instance.OpenAIServices.Add(service); + ViewModels.Preferences.Instance.OpenAIServices.Add(service); SelectedOpenAIService = service; e.Handled = true; @@ -330,9 +376,49 @@ namespace SourceGit.Views if (SelectedOpenAIService == null) return; - ViewModels.Preference.Instance.OpenAIServices.Remove(SelectedOpenAIService); + ViewModels.Preferences.Instance.OpenAIServices.Remove(SelectedOpenAIService); SelectedOpenAIService = null; e.Handled = true; } + + private void OnAddCustomAction(object sender, RoutedEventArgs e) + { + var action = new Models.CustomAction() { Name = "Unnamed Action (Global)" }; + ViewModels.Preferences.Instance.CustomActions.Add(action); + SelectedCustomAction = action; + + e.Handled = true; + } + + private async void SelectExecutableForCustomAction(object sender, RoutedEventArgs e) + { + var options = new FilePickerOpenOptions() + { + FileTypeFilter = [new FilePickerFileType("Executable file(script)") { Patterns = ["*.*"] }], + AllowMultiple = false, + }; + + var selected = await StorageProvider.OpenFilePickerAsync(options); + if (selected.Count == 1 && sender is Button { DataContext: Models.CustomAction action }) + action.Executable = selected[0].Path.LocalPath; + + e.Handled = true; + } + + private void OnRemoveSelectedCustomAction(object sender, RoutedEventArgs e) + { + if (SelectedCustomAction == null) + return; + + ViewModels.Preferences.Instance.CustomActions.Remove(SelectedCustomAction); + SelectedCustomAction = null; + e.Handled = true; + } + + private void UpdateGitVersion() + { + GitVersion = Native.OS.GitVersionString; + ShowGitVersionWarning = !string.IsNullOrEmpty(GitVersion) && Native.OS.GitVersion < Models.GitVersions.MINIMAL; + } } } diff --git a/src/Views/Pull.axaml b/src/Views/Pull.axaml index f6aa245f..67121826 100644 --- a/src/Views/Pull.axaml +++ b/src/Views/Pull.axaml @@ -22,7 +22,7 @@ - + - + + + + + + + - - - + Content="{DynamicResource Text.Pull.LocalChanges.StashAndReply}" + IsChecked="{Binding !DiscardLocalChanges, Mode=TwoWay}"/> + - + + IsChecked="{Binding NoTags, Mode=TwoWay}" + ToolTip.Tip="--no-tags"/> + IsChecked="{Binding UseRebase, Mode=TwoWay}" + ToolTip.Tip="--rebase"/> diff --git a/src/Views/Pull.axaml.cs b/src/Views/Pull.axaml.cs index 3003f02c..c6b4923e 100644 --- a/src/Views/Pull.axaml.cs +++ b/src/Views/Pull.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Interactivity; namespace SourceGit.Views { @@ -9,51 +8,5 @@ namespace SourceGit.Views { InitializeComponent(); } - - protected override void OnLoaded(RoutedEventArgs e) - { - base.OnLoaded(e); - - var vm = DataContext as ViewModels.Pull; - if (vm == null) - return; - - switch (vm.PreAction) - { - case Models.DealWithLocalChanges.DoNothing: - RadioDoNothing.IsChecked = true; - break; - case Models.DealWithLocalChanges.StashAndReaply: - RadioStashAndReply.IsChecked = true; - break; - default: - RadioDiscard.IsChecked = true; - break; - } - } - - private void OnLocalChangeActionIsCheckedChanged(object sender, RoutedEventArgs e) - { - var vm = DataContext as ViewModels.Pull; - if (vm == null) - return; - - if (RadioDoNothing.IsChecked == true) - { - if (vm.PreAction != Models.DealWithLocalChanges.DoNothing) - vm.PreAction = Models.DealWithLocalChanges.DoNothing; - return; - } - - if (RadioStashAndReply.IsChecked == true) - { - if (vm.PreAction != Models.DealWithLocalChanges.StashAndReaply) - vm.PreAction = Models.DealWithLocalChanges.StashAndReaply; - return; - } - - if (vm.PreAction != Models.DealWithLocalChanges.Discard) - vm.PreAction = Models.DealWithLocalChanges.Discard; - } } } diff --git a/src/Views/Push.axaml b/src/Views/Push.axaml index 87921c88..743606ee 100644 --- a/src/Views/Push.axaml +++ b/src/Views/Push.axaml @@ -22,6 +22,7 @@ VerticalAlignment="Center" HorizontalAlignment="Stretch" ItemsSource="{Binding LocalBranches}" SelectedItem="{Binding SelectedLocalBranch, Mode=TwoWay}" + IsTextSearchEnabled="True" IsEnabled="{Binding !HasSpecifiedLocalBranch}"> @@ -31,6 +32,12 @@ + + + + + + @@ -68,8 +76,8 @@ @@ -77,27 +85,37 @@ + + + + + + + IsVisible="{Binding IsSetTrackOptionVisible}" + ToolTip.Tip="-u"/> + IsVisible="{Binding IsCheckSubmodulesVisible}" + ToolTip.Tip="--recurse-submodules=check"/> + IsChecked="{Binding PushAllTags, Mode=TwoWay}" + ToolTip.Tip="--tags"/> + IsChecked="{Binding ForcePush, Mode=TwoWay}" + ToolTip.Tip="--force-with-lease"/> diff --git a/src/Views/RemoveWorktree.axaml b/src/Views/RemoveWorktree.axaml index 6d7ea914..736e6e40 100644 --- a/src/Views/RemoveWorktree.axaml +++ b/src/Views/RemoveWorktree.axaml @@ -18,8 +18,8 @@ - - + + diff --git a/src/Views/RenameBranch.axaml b/src/Views/RenameBranch.axaml index 59a849fe..efbbf323 100644 --- a/src/Views/RenameBranch.axaml +++ b/src/Views/RenameBranch.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.RenameBranch" x:DataType="vm:RenameBranch"> @@ -11,7 +12,7 @@ - + + + + + + diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 6d034130..b16447fa 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -11,7 +11,7 @@ x:DataType="vm:Repository"> - + @@ -40,7 +40,7 @@ - + @@ -501,18 +530,22 @@ - - - - - - - - - - - - + + + + + + + + + + @@ -529,7 +562,7 @@ - + - + @@ -578,11 +611,18 @@ - - + + - - + @@ -675,8 +719,9 @@ diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index 376c81c3..d848c000 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -134,7 +134,7 @@ namespace SourceGit.Views } else if (e.Key == Key.Down) { - if (repo.IsSearchCommitSuggestionOpen) + if (repo.MatchedFilesForSearching is { Count: > 0 }) { SearchSuggestionBox.Focus(NavigationMethod.Tab); SearchSuggestionBox.SelectedIndex = 0; @@ -144,12 +144,7 @@ namespace SourceGit.Views } else if (e.Key == Key.Escape) { - if (repo.IsSearchCommitSuggestionOpen) - { - repo.SearchCommitFilterSuggestion.Clear(); - repo.IsSearchCommitSuggestionOpen = false; - } - + repo.ClearMatchedFilesForSearching(); e.Handled = true; } } @@ -248,6 +243,9 @@ namespace SourceGit.Views return; var leftHeight = LeftSidebarGroups.Bounds.Height - 28.0 * 5 - 4; + if (leftHeight <= 0) + return; + var localBranchRows = vm.IsLocalBranchGroupExpanded ? LocalBranchTree.Rows.Count : 0; var remoteBranchRows = vm.IsRemoteGroupExpanded ? RemoteBranchTree.Rows.Count : 0; var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0; @@ -307,7 +305,7 @@ namespace SourceGit.Views WorktreeList.Height = height; } - if (desiredBranches > leftHeight) + if (leftHeight > 0 && desiredBranches > leftHeight) { var local = localBranchRows * 24.0; var remote = remoteBranchRows * 24.0; @@ -366,9 +364,7 @@ namespace SourceGit.Views if (e.Key == Key.Escape) { - repo.IsSearchCommitSuggestionOpen = false; - repo.SearchCommitFilterSuggestion.Clear(); - + repo.ClearMatchedFilesForSearching(); e.Handled = true; } else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content) @@ -396,34 +392,23 @@ namespace SourceGit.Views e.Handled = true; } - private void OnSwitchHistoriesOrderClicked(object sender, RoutedEventArgs e) + private void OnOpenAdvancedHistoriesOption(object sender, RoutedEventArgs e) { if (sender is Button button && DataContext is ViewModels.Repository repo) { - var checkIcon = App.CreateMenuIcon("Icons.Check"); + var menu = repo.CreateContextMenuForHistoriesPage(); + menu?.Open(button); + } - var dateOrder = new MenuItem(); - dateOrder.Header = App.Text("Repository.HistoriesOrder.ByDate"); - dateOrder.Icon = repo.EnableTopoOrderInHistories ? null : checkIcon; - dateOrder.Click += (_, ev) => - { - repo.EnableTopoOrderInHistories = false; - ev.Handled = true; - }; + e.Handled = true; + } - var topoOrder = new MenuItem(); - topoOrder.Header = App.Text("Repository.HistoriesOrder.Topo"); - topoOrder.Icon = repo.EnableTopoOrderInHistories ? checkIcon : null; - topoOrder.Click += (_, ev) => - { - repo.EnableTopoOrderInHistories = true; - ev.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(dateOrder); - menu.Items.Add(topoOrder); - menu.Open(button); + private void OnOpenSortTagMenu(object sender, RoutedEventArgs e) + { + if (sender is Button button && DataContext is ViewModels.Repository repo) + { + var menu = repo.CreateContextMenuForTagSortMode(); + menu?.Open(button); } e.Handled = true; @@ -436,5 +421,13 @@ namespace SourceGit.Views e.Handled = true; } + + private void OnRemoveSelectedHistoriesFilter(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.Repository repo && sender is Button { DataContext: Models.Filter filter }) + repo.RemoveHistoriesFilter(filter); + + e.Handled = true; + } } } diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml index 424a2346..e41375b9 100644 --- a/src/Views/RepositoryConfigure.axaml +++ b/src/Views/RepositoryConfigure.axaml @@ -5,7 +5,6 @@ xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" - xmlns:ac="using:Avalonia.Controls.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.RepositoryConfigure" x:DataType="vm:RepositoryConfigure" @@ -45,7 +44,7 @@ - + - + + Text="{DynamicResource Text.Preferences.GPG.UserKey}"/> - - - - + @@ -155,7 +150,7 @@ - + @@ -249,9 +244,9 @@ BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}" Background="{DynamicResource Brush.Contents}"> - @@ -261,7 +256,7 @@ - + @@ -288,8 +283,11 @@ + + + @@ -318,7 +316,7 @@ - + @@ -347,7 +345,7 @@ - + @@ -418,20 +416,12 @@ - - - - - - - - - + + + + + + @@ -445,13 +435,15 @@ + + - + diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 3faba5ee..455731aa 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -13,8 +13,10 @@ namespace SourceGit.Views protected override void OnClosing(WindowClosingEventArgs e) { - (DataContext as ViewModels.RepositoryConfigure)?.Save(); base.OnClosing(e); + + if (!Design.IsDesignMode && DataContext is ViewModels.RepositoryConfigure configure) + configure.Save(); } private async void SelectExecutableForCustomAction(object sender, RoutedEventArgs e) diff --git a/src/Views/RepositoryToolbar.axaml b/src/Views/RepositoryToolbar.axaml index 2562775a..9f7f1801 100644 --- a/src/Views/RepositoryToolbar.axaml +++ b/src/Views/RepositoryToolbar.axaml @@ -8,128 +8,128 @@ x:Class="SourceGit.Views.RepositoryToolbar" x:DataType="vm:Repository"> - - + + - - - + - + - - + - - + + - + + + + - + + + - + + + - + + + - + - - + - - + - - - - - - - - + - - - - - - - - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index aa78c4d3..66b49fc2 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -59,6 +59,12 @@ namespace SourceGit.Views var launcher = this.FindAncestorOfType(); if (launcher is not null && DataContext is ViewModels.Repository repo) { + if (repo.IsBare) + { + App.RaiseException(repo.FullPath, "Can't run `git pull` in bare repository!"); + return; + } + var startDirectly = launcher.HasKeyModifier(KeyModifiers.Control); launcher.ClearKeyModifier(); repo.Pull(startDirectly); diff --git a/src/Views/RevisionCompare.axaml b/src/Views/RevisionCompare.axaml index 912fde39..f0498fbf 100644 --- a/src/Views/RevisionCompare.axaml +++ b/src/Views/RevisionCompare.axaml @@ -15,7 +15,7 @@ - + @@ -32,7 +32,7 @@ - + @@ -91,14 +91,15 @@ + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}"/> - diff --git a/src/Views/RevisionFileContentViewer.axaml b/src/Views/RevisionFileContentViewer.axaml index 5c2501e9..86393316 100644 --- a/src/Views/RevisionFileContentViewer.axaml +++ b/src/Views/RevisionFileContentViewer.axaml @@ -21,7 +21,8 @@ @@ -34,12 +35,12 @@ - + - + @@ -63,7 +64,7 @@ - + diff --git a/src/Views/RevisionFileContentViewer.axaml.cs b/src/Views/RevisionFileContentViewer.axaml.cs index bca6a082..16f4fc83 100644 --- a/src/Views/RevisionFileContentViewer.axaml.cs +++ b/src/Views/RevisionFileContentViewer.axaml.cs @@ -1,7 +1,139 @@ +using System; + +using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Media; + +using AvaloniaEdit; +using AvaloniaEdit.Document; +using AvaloniaEdit.Editing; +using AvaloniaEdit.TextMate; namespace SourceGit.Views { + public class RevisionTextFileView : TextEditor + { + public static readonly StyledProperty TabWidthProperty = + AvaloniaProperty.Register(nameof(TabWidth), 4); + + public int TabWidth + { + get => GetValue(TabWidthProperty); + set => SetValue(TabWidthProperty, value); + } + + protected override Type StyleKeyOverride => typeof(TextEditor); + + public RevisionTextFileView() : base(new TextArea(), new TextDocument()) + { + IsReadOnly = true; + ShowLineNumbers = true; + WordWrap = false; + + Options.IndentationSize = TabWidth; + Options.EnableHyperlinks = false; + Options.EnableEmailHyperlinks = false; + + HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; + VerticalScrollBarVisibility = ScrollBarVisibility.Auto; + + TextArea.LeftMargins[0].Margin = new Thickness(8, 0); + TextArea.TextView.Margin = new Thickness(4, 0); + } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + + TextArea.TextView.ContextRequested += OnTextViewContextRequested; + UpdateTextMate(); + } + + protected override void OnUnloaded(RoutedEventArgs e) + { + base.OnUnloaded(e); + + TextArea.TextView.ContextRequested -= OnTextViewContextRequested; + + if (_textMate != null) + { + _textMate.Dispose(); + _textMate = null; + } + + GC.Collect(); + } + + protected override void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + + if (DataContext is Models.RevisionTextFile source) + { + UpdateTextMate(); + Text = source.Content; + } + else + { + Text = string.Empty; + } + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == TabWidthProperty) + { + Options.IndentationSize = TabWidth; + } + } + + private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) + { + var selected = SelectedText; + if (string.IsNullOrEmpty(selected)) + return; + + var copy = new MenuItem() { Header = App.Text("Copy") }; + copy.Click += (_, ev) => + { + App.CopyText(selected); + ev.Handled = true; + }; + + if (this.FindResource("Icons.Copy") is Geometry geo) + { + copy.Icon = new Avalonia.Controls.Shapes.Path() + { + Width = 10, + Height = 10, + Stretch = Stretch.Uniform, + Data = geo, + }; + } + + var menu = new ContextMenu(); + menu.Items.Add(copy); + menu.Open(TextArea.TextView); + + e.Handled = true; + } + + private void UpdateTextMate() + { + if (_textMate == null) + _textMate = Models.TextMateHelper.CreateForEditor(this); + + if (DataContext is Models.RevisionTextFile file) + Models.TextMateHelper.SetGrammarByFileName(_textMate, file.FileName); + } + + private TextMate.Installation _textMate = null; + } + public partial class RevisionFileContentViewer : UserControl { public RevisionFileContentViewer() diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml index fdb15807..6575dc66 100644 --- a/src/Views/RevisionFiles.axaml +++ b/src/Views/RevisionFiles.axaml @@ -10,7 +10,7 @@ x:DataType="vm:CommitDetail"> - + @@ -43,8 +43,14 @@ + HorizontalOffset="-8" VerticalAlignment="-8"> + + + + + + + typeof(TextEditor); - - public RevisionTextFileView() : base(new TextArea(), new TextDocument()) - { - IsReadOnly = true; - ShowLineNumbers = true; - WordWrap = false; - HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; - VerticalScrollBarVisibility = ScrollBarVisibility.Auto; - - TextArea.LeftMargins[0].Margin = new Thickness(8, 0); - TextArea.TextView.Margin = new Thickness(4, 0); - TextArea.TextView.Options.EnableHyperlinks = false; - TextArea.TextView.Options.EnableEmailHyperlinks = false; - } - - protected override void OnLoaded(RoutedEventArgs e) - { - base.OnLoaded(e); - - TextArea.TextView.ContextRequested += OnTextViewContextRequested; - UpdateTextMate(); - } - - protected override void OnUnloaded(RoutedEventArgs e) - { - base.OnUnloaded(e); - - TextArea.TextView.ContextRequested -= OnTextViewContextRequested; - - if (_textMate != null) - { - _textMate.Dispose(); - _textMate = null; - } - - GC.Collect(); - } - - protected override void OnDataContextChanged(EventArgs e) - { - base.OnDataContextChanged(e); - - if (DataContext is Models.RevisionTextFile source) - { - UpdateTextMate(); - Text = source.Content; - } - else - { - Text = string.Empty; - } - } - - private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) - { - var selected = SelectedText; - if (string.IsNullOrEmpty(selected)) - return; - - var copy = new MenuItem() { Header = App.Text("Copy") }; - copy.Click += (_, ev) => - { - App.CopyText(selected); - ev.Handled = true; - }; - - if (this.FindResource("Icons.Copy") is Geometry geo) - { - copy.Icon = new Avalonia.Controls.Shapes.Path() - { - Width = 10, - Height = 10, - Stretch = Stretch.Uniform, - Data = geo, - }; - } - - var menu = new ContextMenu(); - menu.Items.Add(copy); - menu.Open(TextArea.TextView); - - e.Handled = true; - } - - private void UpdateTextMate() - { - if (_textMate == null) - _textMate = Models.TextMateHelper.CreateForEditor(this); - - if (DataContext is Models.RevisionTextFile file) - Models.TextMateHelper.SetGrammarByFileName(_textMate, file.FileName); - } - - private TextMate.Installation _textMate = null; - } - public partial class RevisionFiles : UserControl { public RevisionFiles() @@ -133,7 +23,7 @@ namespace SourceGit.Views } else if (e.Key == Key.Down || e.Key == Key.Up) { - if (vm.IsRevisionFileSearchSuggestionOpen) + if (vm.RevisionFileSearchSuggestion.Count > 0) { SearchSuggestionBox.Focus(NavigationMethod.Tab); SearchSuggestionBox.SelectedIndex = 0; @@ -143,12 +33,7 @@ namespace SourceGit.Views } else if (e.Key == Key.Escape) { - if (vm.IsRevisionFileSearchSuggestionOpen) - { - vm.RevisionFileSearchSuggestion.Clear(); - vm.IsRevisionFileSearchSuggestionOpen = false; - } - + vm.CancelRevisionFileSuggestions(); e.Handled = true; } } @@ -167,7 +52,7 @@ namespace SourceGit.Views if (e.Key == Key.Escape) { - vm.RevisionFileSearchSuggestion.Clear(); + vm.CancelRevisionFileSuggestions(); e.Handled = true; } else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content) diff --git a/src/Views/Reword.axaml b/src/Views/Reword.axaml index f5de4ee7..3ea1ad98 100644 --- a/src/Views/Reword.axaml +++ b/src/Views/Reword.axaml @@ -17,9 +17,9 @@ - + - + - + + + - + - + - - + + + - - - - - - - + + + + - - - - - + + + + - - - - - - - - - + + - - - - - - + + + + + - + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + - - - - - + + + + + @@ -159,7 +158,7 @@ Classes="italic" Margin="0,0,8,0" HorizontalAlignment="Center" VerticalAlignment="Center" - FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}" + FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}" Text="{DynamicResource Text.Welcome.DragDropTip}" Foreground="{DynamicResource Brush.FG2}"/> diff --git a/src/Views/Welcome.axaml.cs b/src/Views/Welcome.axaml.cs index a292a6ef..3ab11782 100644 --- a/src/Views/Welcome.axaml.cs +++ b/src/Views/Welcome.axaml.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using Avalonia; using Avalonia.Controls; @@ -197,7 +196,7 @@ namespace SourceGit.Views { foreach (var item in items) { - OpenOrInitRepository(item.Path.LocalPath); + ViewModels.Welcome.Instance.OpenOrInitRepository(item.Path.LocalPath, null, true); break; } } @@ -261,7 +260,7 @@ namespace SourceGit.Views { foreach (var item in items) { - OpenOrInitRepository(item.Path.LocalPath, to); + ViewModels.Welcome.Instance.OpenOrInitRepository(item.Path.LocalPath, to, true); break; } } @@ -290,31 +289,6 @@ namespace SourceGit.Views } } - private void OpenOrInitRepository(string path, ViewModels.RepositoryNode parent = null) - { - if (!Directory.Exists(path)) - { - if (File.Exists(path)) - path = Path.GetDirectoryName(path); - else - return; - } - - var test = new Commands.QueryRepositoryRootPath(path).ReadToEnd(); - if (!test.IsSuccess || string.IsNullOrEmpty(test.StdOut)) - { - ViewModels.Welcome.Instance.InitRepository(path, parent, test.StdErr); - return; - } - - var normalizedPath = test.StdOut.Trim().Replace("\\", "/"); - var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, true); - ViewModels.Welcome.Instance.Refresh(); - - var launcher = this.FindAncestorOfType()?.DataContext as ViewModels.Launcher; - launcher?.OpenRepositoryInTab(node, launcher.ActivePage); - } - private bool _pressedTreeNode = false; private Point _pressedTreeNodePosition = new Point(); private bool _startDragTreeNode = false; diff --git a/src/Views/WelcomeToolbar.axaml b/src/Views/WelcomeToolbar.axaml index 59802241..5245a130 100644 --- a/src/Views/WelcomeToolbar.axaml +++ b/src/Views/WelcomeToolbar.axaml @@ -18,14 +18,14 @@ + - + diff --git a/src/Views/WelcomeToolbar.axaml.cs b/src/Views/WelcomeToolbar.axaml.cs index fc36e050..9e170c1a 100644 --- a/src/Views/WelcomeToolbar.axaml.cs +++ b/src/Views/WelcomeToolbar.axaml.cs @@ -4,7 +4,6 @@ using System.IO; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Platform.Storage; -using Avalonia.VisualTree; namespace SourceGit.Views { @@ -17,7 +16,8 @@ namespace SourceGit.Views private async void OpenLocalRepository(object _1, RoutedEventArgs e) { - if (!ViewModels.PopupHost.CanCreatePopup()) + var activePage = App.GetLauncer().ActivePage; + if (activePage == null || !activePage.CanCreatePopup()) return; var topLevel = TopLevel.GetTopLevel(this); @@ -25,9 +25,9 @@ namespace SourceGit.Views return; var options = new FolderPickerOpenOptions() { AllowMultiple = false }; - if (Directory.Exists(ViewModels.Preference.Instance.GitDefaultCloneDir)) + if (Directory.Exists(ViewModels.Preferences.Instance.GitDefaultCloneDir)) { - var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(ViewModels.Preference.Instance.GitDefaultCloneDir); + var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(ViewModels.Preferences.Instance.GitDefaultCloneDir); options.SuggestedStartLocation = folder; } @@ -35,7 +35,7 @@ namespace SourceGit.Views { var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options); if (selected.Count == 1) - OpenOrInitRepository(selected[0].Path.LocalPath); + ViewModels.Welcome.Instance.OpenOrInitRepository(selected[0].Path.LocalPath, null, false); } catch (Exception exception) { @@ -44,31 +44,6 @@ namespace SourceGit.Views e.Handled = true; } - - private void OpenOrInitRepository(string path, ViewModels.RepositoryNode parent = null) - { - if (!Directory.Exists(path)) - { - if (File.Exists(path)) - path = Path.GetDirectoryName(path); - else - return; - } - - var test = new Commands.QueryRepositoryRootPath(path).ReadToEnd(); - if (!test.IsSuccess || string.IsNullOrEmpty(test.StdOut)) - { - ViewModels.Welcome.Instance.InitRepository(path, parent, test.StdErr); - return; - } - - var normalizedPath = test.StdOut.Trim().Replace("\\", "/"); - var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, false); - ViewModels.Welcome.Instance.Refresh(); - - var launcher = this.FindAncestorOfType()?.DataContext as ViewModels.Launcher; - launcher?.OpenRepositoryInTab(node, launcher.ActivePage); - } } } diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 342ea4e0..4d79135d 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -11,7 +11,7 @@ x:DataType="vm:WorkingCopy"> - + @@ -23,9 +23,9 @@ - + - + @@ -71,25 +71,57 @@ + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeViewMode, Mode=TwoWay}"/> + + + + + + + + + + + + + - - + - + @@ -120,7 +152,7 @@ + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeViewMode, Mode=TwoWay}"/> @@ -130,7 +162,7 @@ Focusable="True" SelectionMode="Multiple" Background="{DynamicResource Brush.Contents}" - ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode}" + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeViewMode}" Changes="{Binding Staged}" SelectedChanges="{Binding SelectedStaged, Mode=TwoWay}" ContextRequested="OnStagedContextRequested" @@ -154,7 +186,7 @@ - + @@ -171,75 +203,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index df45a7f1..4ae4d779 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -17,7 +17,7 @@ namespace SourceGit.Views if (grid == null) return; - var layout = ViewModels.Preference.Instance.Layout; + var layout = ViewModels.Preferences.Instance.Layout; var width = grid.Bounds.Width; var maxLeft = width - 304;