Merge branch 'release/v8.26'

This commit is contained in:
leo 2024-08-19 10:26:57 +08:00
commit f3d56ffb20
No known key found for this signature in database
85 changed files with 2280 additions and 1793 deletions

View file

@ -1,117 +1,45 @@
name: Continuous Integration name: Continuous Integration
on: on:
push: push:
branches: branches: [develop]
- develop
pull_request: pull_request:
branches: [develop] branches: [develop]
workflow_dispatch: workflow_dispatch:
workflow_call:
jobs: jobs:
build-windows: build:
name: Build Windows x64 strategy:
runs-on: windows-2019 matrix:
include:
- name : Windows x64
os: windows-2019
runtime: win-x64
- name : Windows ARM64
os: windows-2019
runtime: win-arm64
- name : macOS (Intel)
os: macos-13
runtime: osx-x64
- name : macOS (Apple Silicon)
os: macos-latest
runtime: osx-arm64
- name : Linux
os: ubuntu-20.04
runtime: linux-x64
- name : Linux (arm64)
os: ubuntu-20.04
runtime: linux-arm64
name: Build ${{ matrix.name }}
runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Build
run: dotnet build -c Release
- name: Publish
run: dotnet publish src/SourceGit.csproj -c Release -o publish -r win-x64
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: sourcegit.win-x64
path: publish
build-macos-intel:
name: Build macOS (Intel)
runs-on: macos-13
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Build
run: dotnet build -c Release
- name: Publish
run: dotnet publish src/SourceGit.csproj -c Release -o publish -r osx-x64
- name: Packing Program
run: tar -cvf sourcegit.osx-x64.tar -C publish/ .
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: sourcegit.osx-x64
path: sourcegit.osx-x64.tar
build-macos-arm64:
name: Build macOS (Apple Silicon)
runs-on: macos-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Build
run: dotnet build -c Release
- name: Publish
run: dotnet publish src/SourceGit.csproj -c Release -o publish -r osx-arm64
- name: Packing Program
run: tar -cvf sourcegit.osx-arm64.tar -C publish/ .
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: sourcegit.osx-arm64
path: sourcegit.osx-arm64.tar
build-linux:
name: Build Linux
runs-on: ubuntu-20.04
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Build
run: dotnet build -c Release
- name: Publish
run: dotnet publish src/SourceGit.csproj -c Release -o publish -r linux-x64
- name: Rename Executable File
run: mv publish/SourceGit publish/sourcegit
- name: Packing Program
run: tar -cvf sourcegit.linux-x64.tar -C publish/ .
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: sourcegit.linux-x64
path: sourcegit.linux-x64.tar
build-linux-arm64:
name: Build Linux (arm64)
runs-on: ubuntu-20.04
steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v4 uses: actions/setup-dotnet@v4
with: with:
dotnet-version: 8.0.x dotnet-version: 8.0.x
- name: Configure arm64 packages - name: Configure arm64 packages
if: ${{ matrix.runtime == 'linux-arm64' }}
run: | run: |
sudo dpkg --add-architecture arm64 sudo dpkg --add-architecture arm64
echo 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main restricted echo 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main restricted
@ -121,19 +49,25 @@ jobs:
sudo sed -i -e 's/^deb http/deb [arch=amd64] http/g' /etc/apt/sources.list sudo sed -i -e 's/^deb http/deb [arch=amd64] http/g' /etc/apt/sources.list
sudo sed -i -e 's/^deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list sudo sed -i -e 's/^deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list
- name: Install cross-compiling dependencies - name: Install cross-compiling dependencies
if: ${{ matrix.runtime == 'linux-arm64' }}
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install clang llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64 sudo apt-get install clang llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
- name: Build - name: Build
run: dotnet build -c Release run: dotnet build -c Release
- name: Publish - name: Publish
run: dotnet publish src/SourceGit.csproj -c Release -o publish -r linux-arm64 run: dotnet publish src/SourceGit.csproj -c Release -o publish -r ${{ matrix.runtime }}
- name: Rename Executable File - name: Rename executable file
if: ${{ startsWith(matrix.runtime, 'linux-') }}
run: mv publish/SourceGit publish/sourcegit run: mv publish/SourceGit publish/sourcegit
- name: Packing Program - name: Tar artifact
run: tar -cvf sourcegit.linux-arm64.tar -C publish/ . if: ${{ startsWith(matrix.runtime, 'linux-') }}
- name: Upload Artifact run: |
tar -cvf "sourcegit.${{ matrix.runtime }}.tar" -C publish .
rm -r publish/*
mv "sourcegit.${{ matrix.runtime }}.tar" publish
- name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: sourcegit.linux-arm64 name: sourcegit.${{ matrix.runtime }}
path: sourcegit.linux-arm64.tar path: publish

98
.github/workflows/package.yml vendored Normal file
View file

@ -0,0 +1,98 @@
name: Package
on:
workflow_call:
inputs:
version:
description: Source Git package version
required: true
type: string
jobs:
build:
name: Build
uses: ./.github/workflows/ci.yml
windows-portable:
name: Package portable Windows app
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
runtime: [win-x64, win-arm64]
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Download build
uses: actions/download-artifact@v4
with:
name: sourcegit.${{ matrix.runtime }}
path: build/SourceGit
- name: Package
env:
VERSION: ${{ inputs.version }}
RUNTIME: ${{ matrix.runtime }}
run: ./build/scripts/package.windows-portable.sh
- name: Upload package artifact
uses: actions/upload-artifact@v4
with:
name: package.${{ matrix.runtime }}
path: build/sourcegit_*.zip
osx-app:
name: Package OSX app
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
runtime: [osx-x64, osx-arm64]
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Download build
uses: actions/download-artifact@v4
with:
name: sourcegit.${{ matrix.runtime }}
path: build/SourceGit
- name: Package
env:
VERSION: ${{ inputs.version }}
RUNTIME: ${{ matrix.runtime }}
run: ./build/scripts/package.osx-app.sh
- name: Upload package artifact
uses: actions/upload-artifact@v4
with:
name: package.${{ matrix.runtime }}
path: build/sourcegit_*.zip
linux:
name: Package Linux
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
runtime: [linux-x64, linux-arm64]
steps:
- name: Checkout sources
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
- name: Download build
uses: actions/download-artifact@v4
with:
name: sourcegit.${{ matrix.runtime }}
path: build
- name: Package
env:
VERSION: ${{ inputs.version }}
RUNTIME: ${{ matrix.runtime }}
run: |
mkdir build/SourceGit
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit
./build/scripts/package.linux.sh
- name: Upload package artifacts
uses: actions/upload-artifact@v4
with:
name: package.${{ matrix.runtime }}
path: |
build/sourcegit-*.AppImage
build/sourcegit_*.deb
build/sourcegit-*.rpm

49
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,49 @@
name: Release
on:
push:
tags:
- v*
jobs:
version:
name: Prepare version string
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Output version string
id: version
env:
TAG: ${{ github.ref_name }}
run: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
package:
needs: version
name: Package
uses: ./.github/workflows/package.yml
with:
version: ${{ needs.version.outputs.version }}
release:
needs: [version, package]
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: gh release create "$TAG" -t "Release ${TAG#v}" --notes-from-tag
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: package.*
path: packages
merge-multiple: true
- name: Upload assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
VERSION: ${{ needs.version.outputs.version }}
run: gh release upload "$TAG" packages/*

View file

@ -35,6 +35,7 @@ Opensource Git GUI client.
* Git LFS * Git LFS
* Issue Link * Issue Link
> [!WARNING]
> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**. > **Linux** only tested on **Debian 12** on both **X11** & **Wayland**.
## How to Use ## How to Use
@ -46,11 +47,14 @@ You can download the latest stable from [Releases](https://github.com/sourcegit-
This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationData}/SourceGit"`, which is platform-dependent, to store user settings, downloaded avatars and crash logs. This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationData}/SourceGit"`, which is platform-dependent, to store user settings, downloaded avatars and crash logs.
| OS | PATH | | OS | PATH |
|---------|-------------------------------------------------| |---------|-----------------------------------------------------|
| Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` | | Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` |
| Linux | `${HOME}/.config/SourceGit` | | Linux | `${HOME}/.config/SourceGit` or `${HOME}/.sourcegit` |
| macOS | `${HOME}/Library/Application Support/SourceGit` | | macOS | `${HOME}/Library/Application Support/SourceGit` |
> [!TIP]
> You can open the app data dir from the main menu.
For **Windows** users: For **Windows** users:
* **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead. * **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead.
@ -58,6 +62,7 @@ For **Windows** users:
```shell ```shell
winget install SourceGit winget install SourceGit
``` ```
> [!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. > `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 `scoope` with follow commands:
```shell ```shell
@ -84,17 +89,27 @@ For **Linux** users:
This app supports open repository in external tools listed in the table below. This app supports open repository in external tools listed in the table below.
| Tool | Windows | macOS | Linux | Environment Variable | | Tool | Windows | macOS | Linux | KEY IN `external_editors.json` |
|-------------------------------|---------|-------|-------|----------------------| |-------------------------------|---------|-------|-------|--------------------------------|
| Visual Studio Code | YES | YES | YES | VSCODE_PATH | | Visual Studio Code | YES | YES | YES | VSCODE |
| Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS_PATH | | Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS |
| VSCodium | YES | YES | YES | VSCODIUM_PATH | | VSCodium | YES | YES | YES | VSCODIUM |
| JetBrains Fleet | YES | YES | YES | FLEET_PATH | | JetBrains Fleet | YES | YES | YES | FLEET |
| Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH | | Sublime Text | YES | YES | YES | SUBLIME_TEXT |
* You can set the given environment variable for special tool if it can NOT be found by this app automatically. > [!NOTE]
* Installing `JetBrains Toolbox` will help this app to find other JetBrains tools installed on your device. > 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.
* On macOS, you may need to use `launchctl setenv` to make sure the app can read these environment variables. > To solve this problem you can add a file named `external_editors.json` in app data dir and provide the path directly. For example:
```json
{
"tools": {
"VSCODE": "D:\\VSCode\\Code.exe"
}
}
```
> [!NOTE]
> This app also supports a lot of `JetBrains` IDEs, installing `JetBrains Toolbox` will help this app to find them.
## Screenshots ## Screenshots

View file

@ -6,11 +6,6 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGit", "src\SourceGit.csproj", "{2091C34D-4A17-4375-BEF3-4D60BE8113E4}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGit", "src\SourceGit.csproj", "{2091C34D-4A17-4375-BEF3-4D60BE8113E4}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{773082AC-D9C8-4186-8521-4B6A7BEE6158}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{773082AC-D9C8-4186-8521-4B6A7BEE6158}"
ProjectSection(SolutionItems) = preProject
build\build.linux.sh = build\build.linux.sh
build\build.osx.command = build\build.osx.command
build\build.windows.ps1 = build\build.windows.ps1
EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "resources", "resources", "{FD384607-ED99-47B7-AF31-FB245841BC92}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "resources", "resources", "{FD384607-ED99-47B7-AF31-FB245841BC92}"
EndProject EndProject
@ -19,6 +14,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{67B6D05F-A000-40BA-ADB4-C9065F880D7B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{67B6D05F-A000-40BA-ADB4-C9065F880D7B}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.github\workflows\ci.yml = .github\workflows\ci.yml .github\workflows\ci.yml = .github\workflows\ci.yml
.github\workflows\package.yml = .github\workflows\package.yml
.github\workflows\release.yml = .github\workflows\release.yml
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}"
@ -77,13 +74,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SPECS", "SPECS", "{7802CD7A
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "appimage", "appimage", "{5D125DD9-B48A-491F-B2FB-D7830D74C4DC}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "appimage", "appimage", "{5D125DD9-B48A-491F-B2FB-D7830D74C4DC}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
build\resources\appimage\publish-appimage = build\resources\appimage\publish-appimage
build\resources\appimage\publish-appimage.conf = build\resources\appimage\publish-appimage.conf
build\resources\appimage\runtime-x86_64 = build\resources\appimage\runtime-x86_64
build\resources\appimage\sourcegit.appdata.xml = build\resources\appimage\sourcegit.appdata.xml build\resources\appimage\sourcegit.appdata.xml = build\resources\appimage\sourcegit.appdata.xml
build\resources\appimage\sourcegit.png = build\resources\appimage\sourcegit.png build\resources\appimage\sourcegit.png = build\resources\appimage\sourcegit.png
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{C54D4001-9940-477C-A0B6-E795ED0A3209}"
ProjectSection(SolutionItems) = preProject
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
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -114,6 +115,7 @@ Global
{9BA0B044-0CC9-46F8-B551-204F149BF45D} = {FD384607-ED99-47B7-AF31-FB245841BC92} {9BA0B044-0CC9-46F8-B551-204F149BF45D} = {FD384607-ED99-47B7-AF31-FB245841BC92}
{7802CD7A-591B-4EDD-96F8-9BF3F61692E4} = {9BA0B044-0CC9-46F8-B551-204F149BF45D} {7802CD7A-591B-4EDD-96F8-9BF3F61692E4} = {9BA0B044-0CC9-46F8-B551-204F149BF45D}
{5D125DD9-B48A-491F-B2FB-D7830D74C4DC} = {FD384607-ED99-47B7-AF31-FB245841BC92} {5D125DD9-B48A-491F-B2FB-D7830D74C4DC} = {FD384607-ED99-47B7-AF31-FB245841BC92}
{C54D4001-9940-477C-A0B6-E795ED0A3209} = {773082AC-D9C8-4186-8521-4B6A7BEE6158}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7FF1B9C6-B5BF-4A50-949F-4B407A0E31C9} SolutionGuid = {7FF1B9C6-B5BF-4A50-949F-4B407A0E31C9}

View file

@ -1 +1 @@
8.25 8.26

View file

@ -1,32 +0,0 @@
#!/bin/sh
version=`cat ../VERSION`
# Cleanup
rm -rf SourceGit *.tar.gz resources/deb/opt *.deb *.rpm *.AppImage
# Generic AppImage
cd resources/appimage
./publish-appimage -y -o sourcegit-${version}.linux.x86_64.AppImage
# Move to build dir
mv AppImages/sourcegit-${version}.linux.x86_64.AppImage ../../
mv AppImages/AppDir/usr/bin ../../SourceGit
cd ../../
# Debain/Ubuntu package
mkdir -p resources/deb/opt/sourcegit/
mkdir -p resources/deb/usr/share/applications
mkdir -p resources/deb/usr/share/icons
cp -f SourceGit/* resources/deb/opt/sourcegit/
cp -r resources/_common/applications resources/deb/usr/share/
cp -r resources/_common/icons resources/deb/usr/share/
chmod +x -R resources/deb/opt/sourcegit
sed -i "2s/.*/Version: ${version}/g" resources/deb/DEBIAN/control
dpkg-deb --build resources/deb ./sourcegit_${version}-1_amd64.deb
# Redhat/CentOS/Fedora package
rpmbuild -bb --target=x86_64 resources/rpm/SPECS/build.spec --define "_topdir `pwd`/resources/rpm" --define "_version ${version}"
mv resources/rpm/RPMS/x86_64/sourcegit-${version}-1.x86_64.rpm .
rm -rf SourceGit

View file

@ -1,21 +0,0 @@
#!/bin/sh
version=`cat ../VERSION`
rm -rf SourceGit.app *.zip
mkdir -p SourceGit.app/Contents/Resources
cp resources/app/App.icns SourceGit.app/Contents/Resources/App.icns
sed "s/SOURCE_GIT_VERSION/${version}/g" resources/app/App.plist > SourceGit.app/Contents/Info.plist
mkdir -p SourceGit.app/Contents/MacOS
dotnet publish ../src/SourceGit.csproj -c Release -r osx-arm64 -o SourceGit.app/Contents/MacOS
zip sourcegit_${version}.osx-arm64.zip -r SourceGit.app -x "*/*\.dsym/*"
rm -rf SourceGit.app/Contents/MacOS
mkdir -p SourceGit.app/Contents/MacOS
dotnet publish ../src/SourceGit.csproj -c Release -r osx-x64 -o SourceGit.app/Contents/MacOS
zip sourcegit_${version}.osx-x64.zip -r SourceGit.app -x "*/*\.dsym/*"
rm -rf SourceGit.app

View file

@ -1,23 +0,0 @@
$version = Get-Content ..\VERSION
if (Test-Path SourceGit) {
Remove-Item SourceGit -Recurse -Force
}
Remove-Item *.zip -Force
dotnet publish ..\src\SourceGit.csproj -c Release -r win-arm64 -o SourceGit
Remove-Item SourceGit\*.pdb -Force
Compress-Archive -Path SourceGit -DestinationPath "sourcegit_$version.win-arm64.zip"
if (Test-Path SourceGit) {
Remove-Item SourceGit -Recurse -Force
}
dotnet publish ..\src\SourceGit.csproj -c Release -r win-x64 -o SourceGit
Remove-Item SourceGit\*.pdb -Force
Compress-Archive -Path SourceGit -DestinationPath "sourcegit_$version.win-x64.zip"

View file

@ -1,708 +0,0 @@
#!/bin/bash
################################################################################
# PROJECT : Publish-AppImage for .NET
# WEBPAGE : https://github.com/kuiperzone/Publish-AppImage
# COPYRIGHT : Andy Thomas 2021-2023
# LICENSE : MIT
################################################################################
###############################
# CONSTANTS
###############################
declare -r _SCRIPT_VERSION="1.3.1"
declare -r _SCRIPT_TITLE="Publish-AppImage for .NET"
declare -r _SCRIPT_IMPL_MIN=1
declare -r _SCRIPT_IMPL_MAX=1
declare -r _SCRIPT_COPYRIGHT="Copyright 2023 Andy Thomas"
declare -r _SCRIPT_WEBSITE="https://github.com/kuiperzone/Publish-AppImage"
declare -r _SCRIPT_NAME="publish-appimage"
declare -r _DEFAULT_CONF="${_SCRIPT_NAME}.conf"
declare -r _APPIMAGE_KIND="appimage"
declare -r _ZIP_KIND="zip"
declare -r _DOTNET_NONE="null"
###############################
# FUNCTIONS
###############################
function assert_result
{
local _ret=$?
if [ ${_ret} -ne 0 ]; then
echo
exit ${_ret}
fi
}
function exec_or_die
{
echo "${1}"
eval "${1}"
assert_result
}
function ensure_directory
{
local _path="${1}"
if [ ! -d "${_path}" ]; then
mkdir -p "${_path}"
assert_result
fi
}
function remove_path
{
local _path="${1}"
if [ -d "${_path}" ]; then
rm -rf "${_path}"
assert_result
elif [ -f "${_path}" ]; then
rm -f "${_path}"
assert_result
fi
}
function assert_mandatory
{
local _name="${1}"
local _value="${2}"
if [ "${_value}" == "" ]; then
echo "${_name} undefined in: ${_conf_arg_value}"
echo
exit 1
fi
}
function assert_opt_file
{
local _name="${1}"
local _value="${2}"
if [ "${_value}" != "" ] && [ ! -f "${_value}" ]; then
echo "File not found: ${_value}"
if [ "${_name}" != "" ]; then
echo "See ${_name} in: ${_conf_arg_value}"
fi
echo
exit 1
fi
}
###############################
# HANDLE ARGUMENTS
###############################
# Specify conf file
declare -r _CONF_ARG="f"
declare -r _CONF_ARG_NAME="conf"
_conf_arg_value="${_DEFAULT_CONF}"
_arg_syntax=":${_CONF_ARG}:"
# Runtime ID
declare -r _RID_ARG="r"
declare -r _RID_ARG_NAME="runtime"
_rid_arg_value="linux-x64"
_arg_syntax="${_arg_syntax}${_RID_ARG}:"
# Package kind
declare -r _KIND_ARG="k"
declare -r _KIND_ARG_NAME="kind"
declare -l _kind_arg_value="${_APPIMAGE_KIND}"
_arg_syntax="${_arg_syntax}${_KIND_ARG}:"
# Run app
declare -r _RUNAPP_ARG="u"
declare -r _RUNAPP_ARG_NAME="run"
_runapp_arg_value=false
_arg_syntax="${_arg_syntax}${_RUNAPP_ARG}"
# Verbose
declare -r _VERBOSE_ARG="b"
declare -r _VERBOSE_ARG_NAME="verbose"
_verbose_arg_value=false
_arg_syntax="${_arg_syntax}${_VERBOSE_ARG}"
# Skip yes (no prompt)
declare -r _SKIPYES_ARG="y"
declare -r _SKIPYES_ARG_NAME="skip-yes"
_skipyes_arg_value=false
_arg_syntax="${_arg_syntax}${_SKIPYES_ARG}"
# Output name
declare -r _OUTPUT_ARG="o"
declare -r _OUTPUT_ARG_NAME="output"
_output_arg_value=""
_arg_syntax="${_arg_syntax}${_OUTPUT_ARG}:"
# Show version
declare -r _VERSION_ARG="v"
declare -r _VERSION_ARG_NAME="version"
_version_arg_value=false
_arg_syntax="${_arg_syntax}${_VERSION_ARG}"
# Show help
declare -r _HELP_ARG="h"
declare -r _HELP_ARG_NAME="help"
_help_arg_value=false
_arg_syntax="${_arg_syntax}${_HELP_ARG}"
_exit_help=0
# Transform long options to short ones
for arg in "${@}"; do
shift
case "${arg}" in
("--${_CONF_ARG_NAME}") set -- "$@" "-${_CONF_ARG}" ;;
("--${_RID_ARG_NAME}") set -- "$@" "-${_RID_ARG}" ;;
("--${_KIND_ARG_NAME}") set -- "$@" "-${_KIND_ARG}" ;;
("--${_RUNAPP_NAME}") set -- "$@" "-${_RUNAPP_ARG}" ;;
("--${_VERBOSE_ARG_NAME}") set -- "$@" "-${_VERBOSE_ARG}" ;;
("--${_SKIPYES_ARG_NAME}") set -- "$@" "-${_SKIPYES_ARG}" ;;
("--${_OUTPUT_ARG_NAME}") set -- "$@" "-${_OUTPUT_ARG}" ;;
("--${_VERSION_ARG_NAME}") set -- "$@" "-${_VERSION_ARG}" ;;
("--${_HELP_ARG_NAME}") set -- "$@" "-${_HELP_ARG}" ;;
("--"*)
echo "Illegal argument: ${arg}"
echo
_exit_help=1
break
;;
(*) set -- "$@" "${arg}" ;;
esac
done
if [ ${_exit_help} == 0 ]; then
# Read arguments
while getopts ${_arg_syntax} arg; do
case "${arg}" in
(${_CONF_ARG}) _conf_arg_value="${OPTARG}" ;;
(${_RID_ARG}) _rid_arg_value="${OPTARG}" ;;
(${_KIND_ARG}) _kind_arg_value="${OPTARG}" ;;
(${_RUNAPP_ARG}) _runapp_arg_value=true ;;
(${_VERBOSE_ARG}) _verbose_arg_value=true ;;
(${_SKIPYES_ARG}) _skipyes_arg_value=true ;;
(${_OUTPUT_ARG}) _output_arg_value="${OPTARG}" ;;
(${_VERSION_ARG}) _version_arg_value=true ;;
(${_HELP_ARG}) _help_arg_value=true ;;
(*)
echo "Illegal argument"
echo
_exit_help=1
break
;;
esac
done
fi
# Handle and help and version
if [ ${_help_arg_value} == true ] || [ $_exit_help != 0 ]; then
_indent=" "
echo "Usage:"
echo "${_indent}${_SCRIPT_NAME} [-flags] [-option-n value-n]"
echo
echo "Help Options:"
echo "${_indent}-${_HELP_ARG}, --${_HELP_ARG_NAME}"
echo "${_indent}Show help information flag."
echo
echo "${_indent}-${_VERSION_ARG}, --${_VERSION_ARG_NAME}"
echo "${_indent}Show version and about information flag."
echo
echo "Build Options:"
echo "${_indent}-${_CONF_ARG}, --${_CONF_ARG_NAME} value"
echo "${_indent}Specifies the conf file. Defaults to ${_SCRIPT_NAME}.conf."
echo
echo "${_indent}-${_RID_ARG}, --${_RID_ARG_NAME} value"
echo "${_indent}Dotnet publish runtime identifier. Valid examples include:"
echo "${_indent}linux-x64 and linux-arm64. Default is linux-x64 if unspecified."
echo "${_indent}See also: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog"
echo
echo "${_indent}-${_KIND_ARG}, --${_KIND_ARG_NAME} value"
echo "${_indent}Package output kind. Value must be one of: ${_APPIMAGE_KIND} or ${_ZIP_KIND}."
echo "${_indent}Default is ${_APPIMAGE_KIND} if unspecified."
echo
echo "${_indent}-${_VERBOSE_ARG}, --${_VERBOSE_ARG_NAME}"
echo "${_indent}Verbose review info output flag."
echo
echo "${_indent}-${_RUNAPP_ARG}, --${_RUNAPP_ARG_NAME}"
echo "${_indent}Run the application after successful build flag."
echo
echo "${_indent}-${_SKIPYES_ARG}, --${_SKIPYES_ARG_NAME}"
echo "${_indent}Skip confirmation prompt flag (assumes yes)."
echo
echo "${_indent}-${_OUTPUT_ARG}, --${_OUTPUT_ARG_NAME}"
echo "${_indent}Explicit final output filename (excluding directory part)."
echo
echo "Example:"
echo "${_indent}${_SCRIPT_NAME} -${_RID_ARG} linux-arm64"
echo
exit $_exit_help
fi
if [ ${_version_arg_value} == true ]; then
echo
echo "${_SCRIPT_TITLE}, ${_SCRIPT_VERSION}"
echo "${_SCRIPT_COPYRIGHT}"
echo "${_SCRIPT_WEBSITE}"
echo
echo "MIT License"
echo
echo "Permission is hereby granted, free of charge, to any person obtaining a copy"
echo "of this software and associated documentation files (the "Software"), to deal"
echo "in the Software without restriction, including without limitation the rights"
echo "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell"
echo "copies of the Software, and to permit persons to whom the Software is"
echo "furnished to do so, subject to the following conditions:"
echo
echo "The above copyright notice and this permission notice shall be included in all"
echo "copies or substantial portions of the Software."
echo
echo "THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR"
echo "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,"
echo "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE"
echo "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER"
echo "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,"
echo "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE"
echo "SOFTWARE."
echo
exit 0
fi
###############################
# SOURCE & WORKING
###############################
# Export these now as may be
# useful in an advanced config file
export DOTNET_RID="${_rid_arg_value}"
export PKG_KIND="${_kind_arg_value}"
export ISO_DATE=`date +"%Y-%m-%d"`
if [ ! -f "${_conf_arg_value}" ]; then
echo "Configuration file not found: ${_conf_arg_value}"
echo
exit 1
fi
# Export contents to any post publish command
set -a
# Source local to PWD
source "${_conf_arg_value}"
set +a
# For AppImage tool and backward compatibility
export VERSION="${APP_VERSION}"
# Then change PWD to conf file
cd "$(dirname "${_conf_arg_value}")"
###############################
# SANITY
###############################
if (( ${CONF_IMPL_VERSION} < ${_SCRIPT_IMPL_MIN} )) || (( ${CONF_IMPL_VERSION} > ${_SCRIPT_IMPL_MAX} )); then
echo "Configuration format version ${_SCRIPT_IMPL_VERSION} not compatible"
echo "Older conf file but newer ${_SCRIPT_NAME} implementation?"
echo "Update from: ${_SCRIPT_WEBSITE}"
echo
exit 1
fi
assert_mandatory "APP_MAIN" "${APP_MAIN}"
assert_mandatory "APP_ID" "${APP_ID}"
assert_mandatory "APP_ICON_SRC" "${APP_ICON_SRC}"
assert_mandatory "DE_NAME" "${DE_NAME}"
assert_mandatory "DE_CATEGORIES" "${DE_CATEGORIES}"
assert_mandatory "PKG_OUTPUT_DIR" "${PKG_OUTPUT_DIR}"
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
assert_mandatory "APPIMAGETOOL_COMMAND" "${APPIMAGETOOL_COMMAND}"
fi
assert_opt_file "APP_ICON_SRC" "${APP_ICON_SRC}"
assert_opt_file "APP_XML_SRC" "${APP_XML_SRC}"
if [ "${DE_TERMINAL_FLAG}" != "true" ] && [ "${DE_TERMINAL_FLAG}" != "false" ]; then
echo "DE_TERMINAL_FLAG invalid value: ${DE_TERMINAL_FLAG}"
echo
exit 1
fi
if [ "${DOTNET_PROJECT_PATH}" == "${_DOTNET_NONE}" ] && [ "${POST_PUBLISH}" == "" ]; then
echo "No publish or build operation defined (nothing will be built)"
echo "See DOTNET_PROJECT_PATH and POST_PUBLISH in: ${_conf_arg_value}"
echo
exit 1
fi
if [ "${DOTNET_PROJECT_PATH}" != "" ] && [ "${DOTNET_PROJECT_PATH}" != "${_DOTNET_NONE}" ] &&
[ ! -f "${DOTNET_PROJECT_PATH}" ] && [ ! -d "${DOTNET_PROJECT_PATH}" ]; then
echo "DOTNET_PROJECT_PATH path not found: ${DOTNET_PROJECT_PATH}"
echo
exit 1
fi
if [ "${_kind_arg_value}" != "${_APPIMAGE_KIND}" ] && [ "${_kind_arg_value}" != "${_ZIP_KIND}" ]; then
echo "Invalid argument value: ${_kind_arg_value}"
echo "Use one of: ${_APPIMAGE_KIND} or ${_ZIP_KIND}"
echo
exit 1
fi
# Detect if publish for windows
_exec_ext=""
declare -l _tw="${_rid_arg_value}"
if [[ "${_tw}" == "win"* ]]; then
# May use this in future
_exec_ext=".exe"
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
echo "Invalid AppImage payload"
echo "Looks like a windows binary to be packaged as AppImage."
echo "Use --${_KIND_ARG_NAME} ${_ZIP_KIND} instead."
echo
exit 1
fi
fi
###############################
# VARIABLES
###############################
# Abbreviate RID where it maps well to arch
if [ "${_rid_arg_value}" == "linux-x64" ]; then
_file_out_arch="-x86_64"
elif [ "${_rid_arg_value}" == "linux-arm64" ]; then
_file_out_arch="-aarch64"
else
# Otherwise use RID itself
_file_out_arch="-${_rid_arg_value}"
fi
# APPDIR LOCATIONS
export APPDIR_ROOT="${PKG_OUTPUT_DIR}/AppDir"
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
# AppImage
export APPDIR_USR="${APPDIR_ROOT}/usr"
export APPDIR_BIN="${APPDIR_ROOT}/usr/bin"
export APPDIR_SHARE="${APPDIR_ROOT}/usr/share"
_local_run="usr/bin/${APP_MAIN}${_exec_ext}"
else
# Simple zip
export APPDIR_USR=""
export APPDIR_BIN="${APPDIR_ROOT}"
export APPDIR_SHARE="${APPDIR_ROOT}"
_local_run="${APP_MAIN}${_exec_ext}"
fi
export APPRUN_TARGET="${APPDIR_BIN}/${APP_MAIN}${_exec_ext}"
# DOTNET PUBLISH
if [ "${DOTNET_PROJECT_PATH}" != "${_DOTNET_NONE}" ]; then
_publish_cmd="dotnet publish"
if [ "${DOTNET_PROJECT_PATH}" != "" ] && [ "${DOTNET_PROJECT_PATH}" != "." ]; then
_publish_cmd="${_publish_cmd} \"${DOTNET_PROJECT_PATH}\""
fi
_publish_cmd="${_publish_cmd} -r ${_rid_arg_value}"
if [ "${APP_VERSION}" != "" ]; then
_publish_cmd="${_publish_cmd} -p:Version=${APP_VERSION}"
fi
if [ "${DOTNET_PUBLISH_ARGS}" != "" ]; then
_publish_cmd="${_publish_cmd} ${DOTNET_PUBLISH_ARGS}"
fi
_publish_cmd="${_publish_cmd} -o \"${APPDIR_BIN}\""
fi
# PACKAGE OUTPUT
if [ $PKG_VERSION_FLAG == true ] && [ "${APP_VERSION}" != "" ]; then
_version_out="-${APP_VERSION}"
fi
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
# AppImageTool
if [ "${_output_arg_value}" != "" ]; then
_package_out="${PKG_OUTPUT_DIR}/${_output_arg_value}"
else
_package_out="${PKG_OUTPUT_DIR}/${APP_MAIN}${_version_out}${_file_out_arch}${PKG_APPIMAGE_SUFFIX}"
fi
_package_cmd="${APPIMAGETOOL_COMMAND}"
if [ "${PKG_APPIMAGE_ARGS}" != "" ]; then
_package_cmd="${_package_cmd} ${PKG_APPIMAGE_ARGS}"
fi
_package_cmd="${_package_cmd} \"${APPDIR_ROOT}\" \"${_package_out}\""
if [ ${_runapp_arg_value} == true ]; then
_packrun_cmd="${_package_out}"
fi
else
# Simple zip
if [ "${_output_arg_value}" != "" ]; then
_package_out="${PKG_OUTPUT_DIR}/${_output_arg_value}"
else
_package_out="${PKG_OUTPUT_DIR}/${APP_MAIN}${_version_out}${_file_out_arch}.zip"
fi
_package_cmd="(cd \"${APPDIR_ROOT}\" && zip -r \"${PWD}/${_package_out}\" ./)"
if [ ${_runapp_arg_value} == true ]; then
_packrun_cmd="${APPRUN_TARGET}"
fi
fi
###############################
# DESKTOP ENTRY & APPDATA
###############################
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
_desktop="[Desktop Entry]\n"
_desktop="${_desktop}Type=Application\n"
_desktop="${_desktop}Name=${DE_NAME}\n"
_desktop="${_desktop}Exec=AppRun\n"
_desktop="${_desktop}Terminal=${DE_TERMINAL_FLAG}\n"
_desktop="${_desktop}Categories=${DE_CATEGORIES}\n"
# Follow app-id
_desktop="${_desktop}Icon=${APP_ID}\n"
if [ "${DE_COMMENT}" != "" ]; then
_desktop="${_desktop}Comment=${DE_COMMENT}\n"
fi
if [ "${DE_KEYWORDS}" != "" ]; then
_desktop="${_desktop}Keywords=${DE_KEYWORDS}\n"
fi
_desktop="${_desktop}${DE_EXTEND}\n"
fi
# Load appdata.xml
if [ "${APP_XML_SRC}" != "" ]; then
if command -v envsubst &> /dev/null; then
_appxml=$(envsubst <"${APP_XML_SRC}")
else
_appxml=$(<"${APP_XML_SRC}")
echo "WARNING: Variable substitution not available for: ${APP_XML_SRC}"
echo
fi
fi
###############################
# DISPLAY & CONFIRM
###############################
echo "${_SCRIPT_TITLE}, ${_SCRIPT_VERSION}"
echo "${_SCRIPT_COPYRIGHT}"
echo
echo "APP_MAIN: ${APP_MAIN}"
echo "APP_ID: ${APP_ID}"
echo "APP_VERSION: ${APP_VERSION}"
echo "OUTPUT: ${_package_out}"
echo
if [ "${_desktop}" != "" ]; then
echo -e "${_desktop}"
fi
if [ ${_verbose_arg_value} == true ] && [ "${_appxml}" != "" ]; then
echo -e "${_appxml}\n"
fi
echo "Build Commands:"
if [ "${_publish_cmd}" != "" ]; then
echo
echo "${_publish_cmd}"
fi
if [ "${POST_PUBLISH}" != "" ]; then
echo
echo "${POST_PUBLISH}"
fi
echo
echo "${_package_cmd}"
echo
# Prompt
if [ $_skipyes_arg_value == false ]; then
echo
read -p "Build now [N/y]? " prompt
if [ "${prompt}" != "y" ] && [ "${prompt}" != "Y" ]; then
echo
exit 1
fi
# Continue
echo
fi
###############################
# PUBLISH & BUILD
###############################
# Clean and ensure directoy exists
ensure_directory "${PKG_OUTPUT_DIR}"
remove_path "${APPDIR_ROOT}"
remove_path "${_package_out}"
# Create AppDir structure
ensure_directory "${APPDIR_BIN}"
if [ "${_kind_arg_value}" != "${_ZIP_KIND}" ]; then
# We also create usr/share/icons, as some packages require this.
# See: https://github.com/kuiperzone/Publish-AppImage/issues/7
ensure_directory "${APPDIR_SHARE}/icons"
fi
echo
# Publish dotnet
if [ "${_publish_cmd}" != "" ]; then
exec_or_die "${_publish_cmd}"
echo
fi
# Post-publish
if [ "${POST_PUBLISH}" != "" ]; then
exec_or_die "${POST_PUBLISH}"
echo
fi
# Application file must exist!
if [ ! -f "${APPRUN_TARGET}" ]; then
echo "Expected application file not found: ${APPRUN_TARGET}"
echo
exit 1
fi
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
echo
# Create desktop
if [ "${_desktop}" != "" ]; then
_file="${APPDIR_ROOT}/${APP_ID}.desktop"
echo "Creating: ${_file}"
echo -e "${_desktop}" > "${_file}"
assert_result
fi
if [ "${_appxml}" != "" ]; then
_dir="${APPDIR_SHARE}/metainfo"
_file="${_dir}/${APP_ID}.appdata.xml"
echo "Creating: ${_file}"
ensure_directory "${_dir}"
echo -e "${_appxml}" > "${_file}"
assert_result
if [ "${_desktop}" != "" ]; then
# Copy of desktop under "applications"
# Needed for launchable in appinfo.xml (if used)
# See https://github.com/AppImage/AppImageKit/issues/603
_dir="${APPDIR_SHARE}/applications"
_file="${_dir}/${APP_ID}.desktop"
echo "Creating: ${_file}"
ensure_directory "${_dir}"
echo -e "${_desktop}" > "${_file}"
assert_result
fi
fi
# Copy icon
if [ "${APP_ICON_SRC}" != "" ]; then
_icon_ext="${APP_ICON_SRC##*.}"
if [ "${_icon_ext}" != "" ]; then
_icon_ext=".${_icon_ext}"
fi
_temp="${APPDIR_ROOT}/${APP_ID}${_icon_ext}"
echo "Creating: ${_temp}"
cp "${APP_ICON_SRC}" "${_temp}"
assert_result
fi
# AppRun
_temp="${APPDIR_ROOT}/AppRun"
if [ ! -f "${_temp}" ]; then
echo "Creating: ${_temp}"
ln -s "${_local_run}" "${_temp}"
assert_result
fi
fi
# Build package
echo
exec_or_die "${_package_cmd}"
echo
echo "OUTPUT OK: ${_package_out}"
echo
if [ "${_packrun_cmd}" != "" ]; then
echo "RUNNING ..."
exec_or_die "${_packrun_cmd}"
echo
fi
exit 0

View file

@ -1,140 +0,0 @@
################################################################################
# BASH FORMAT CONFIG: Publish-AppImage for .NET
# WEBPAGE : https://kuiper.zone/publish-appimage-dotnet/
################################################################################
########################################
# Application
########################################
# Mandatory application (file) name. This must be the base name of the main
# runnable file to be created by the publish/build process. It should NOT
# include any directory part or extension, i.e. do not append ".exe" or ".dll"
# for dotnet. Example: "MyApp"
APP_MAIN="sourcegit"
# Mandatory application ID in reverse DNS form, i.e. "tld.my-domain.MyApp".
# Exclude any ".desktop" post-fix. Note that reverse DNS form is necessary
# for compatibility with Freedesktop.org metadata.
APP_ID="com.sourcegit-scm.SourceGit"
# Mandatory icon source file relative to this file (appimagetool seems to
# require this). Use .svg or .png only. PNG should be one of standard sizes,
# i.e, 128x128 or 256x256 pixels. Example: "Assets/app.svg"
APP_ICON_SRC="sourcegit.png"
# Optional Freedesktop.org metadata source file relative to this file. It is not essential
# (leave empty) but will be used by appimagetool for repository information if provided.
# See for information: https://docs.appimage.org/packaging-guide/optional/appstream.html
# NB. The file may embed bash variables defined in this file and those listed below
# (these will be substituted during the build). Examples include: "<id>${APP_ID}</id>"
# and "<release version="${APP_VERSION}" date="${ISO_DATE}">".
# $ISO_DATE : date of build, i.e. "2021-10-29",
# $APP_VERSION : application version (if provided),
# Example: "Assets/appdata.xml".
APP_XML_SRC="sourcegit.appdata.xml"
########################################
# Desktop Entry
########################################
# Mandatory friendly name of the application.
DE_NAME="SourceGit"
# Mandatory category(ies), separated with semicolon, in which the entry should be
# shown. See https://specifications.freedesktop.org/menu-spec/latest/apa.html
# Examples: "Development", "Graphics", "Network", "Utility" etc.
DE_CATEGORIES="Utility"
# Optional short comment text (single line).
# Example: "Perform calculations"
DE_COMMENT="Open-source GUI client for git users"
# Optional keywords, separated with semicolon. Values are not meant for
# display and should not be redundant with the value of DE_NAME.
DE_KEYWORDS=""
# Flag indicating whether the program runs in a terminal window. Use true or false only.
DE_TERMINAL_FLAG=false
# Optional name-value text to be appended to the Desktop Entry file, thus providing
# additional metadata. Name-values should not be redundant with values above and
# are to be terminated with new line ("\n").
# Example: "Comment[fr]=Effectue des calculs compliqués\nMimeType=image/x-foo"
DE_EXTEND=""
########################################
# Dotnet Publish
########################################
# Optional path relative to this file in which to find the dotnet project (.csproj)
# or solution (.sln) file, or the directory containing it. If empty (default), a single
# project or solution file is expected under the same directory as this file.
# IMPORTANT. If set to "null", dotnet publish is disabled (it is NOT called). Instead,
# only POST_PUBLISH is called. Example: "Source/MyProject"
DOTNET_PROJECT_PATH="../../../src/SourceGit.csproj"
# Optional arguments suppled to "dotnet publish". Do NOT include "-r" (runtime) or version here as they will
# be added (see also $APP_VERSION). Typically you want as a minimum: "-c Release --self-contained true".
# Additional useful arguments include:
# "-p:DebugType=None -p:DebugSymbols=false -p:PublishSingleFile=true -p:PublishTrimmed=true -p:TrimMode=link"
# Refer: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish
DOTNET_PUBLISH_ARGS="-c Release -p:DebugType=None -p:DebugSymbols=false"
########################################
# POST-PUBLISH
########################################
# Optional post-publish or standalone build command. The value could, for example, copy
# additional files into the "bin" directory. The working directory will be the location
# of this file. The value is mandatory if DOTNET_PROJECT_PATH equals "null". In
# addition to variables in this file, the following variables are exported prior:
# $ISO_DATE : date of build, i.e. "2021-10-29",
# $APP_VERSION : application version (if provided),
# $DOTNET_RID : dotnet runtime identifier string provided at command line (i.e. "linux-x64),
# $PKG_KIND : package kind (i.e. "appimage", "zip") provided at command line.
# $APPDIR_ROOT : AppImage build directory root (i.e. "AppImages/AppDir").
# $APPDIR_USR : AppImage user directory under root (i.e. "AppImages/AppDir/usr").
# $APPDIR_BIN : AppImage bin directory under root (i.e. "AppImages/AppDir/usr/bin").
# $APPRUN_TARGET : The expected target executable file (i.e. "AppImages/AppDir/usr/bin/app-name").
# Example: "Assets/post-publish.sh"
POST_PUBLISH="mv AppImages/AppDir/usr/bin/SourceGit AppImages/AppDir/usr/bin/sourcegit; rm -f AppImages/AppDir/usr/bin/*.dbg"
########################################
# Package Output
########################################
# Additional arguments for use with appimagetool. See appimagetool --help.
# Default is empty. Example: "--sign"
PKG_APPIMAGE_ARGS="--runtime-file=runtime-x86_64"
# Mandatory output directory relative to this file. It will be created if it does not
# exist. It will contain the final package file and temporary AppDir. Default: "AppImages".
PKG_OUTPUT_DIR="AppImages"
# Boolean which sets whether to include the application version in the filename of the final
# output package (i.e. "HelloWorld-1.2.3-x86_64.AppImage"). It is ignored if $APP_VERSION is
# empty or the "output" command arg is specified. Default and recommended: false.
PKG_VERSION_FLAG=false
# Optional AppImage output filename extension. It is ignored if generating a zip file, or if
# the "output" command arg is specified. Default and recommended: ".AppImage".
PKG_APPIMAGE_SUFFIX=".AppImage"
########################################
# Advanced Other
########################################
# The appimagetool command. Default is "appimagetool" which is expected to be found
# in the $PATH. If the tool is not in path or has different name, a full path can be given,
# example: "/home/user/Apps/appimagetool-x86_64.AppImage"
APPIMAGETOOL_COMMAND="appimagetool"
# Internal use only. Used for compatibility between conf and script. Do not modify.
CONF_IMPL_VERSION=1

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application"> <component type="desktop-application">
<id>com.sourcegit-scm.SourceGit</id> <id>com.sourcegit_scm.SourceGit</id>
<metadata_license>MIT</metadata_license> <metadata_license>MIT</metadata_license>
<project_license>MIT</project_license> <project_license>MIT</project_license>
<name>SourceGit</name> <name>SourceGit</name>
@ -8,8 +8,9 @@
<description> <description>
<p>Open-source GUI client for git users</p> <p>Open-source GUI client for git users</p>
</description> </description>
<launchable type="desktop-id">com.sourcegit-scm.SourceGit.desktop</launchable> <url type="homepage">https://github.com/sourcegit-scm/sourcegit</url>
<launchable type="desktop-id">com.sourcegit_scm.SourceGit.desktop</launchable>
<provides> <provides>
<id>com.sourcegit-scm.SourceGit.desktop</id> <id>com.sourcegit_scm.SourceGit.desktop</id>
</provides> </provides>
</component> </component>

View file

@ -1,5 +1,5 @@
Package: sourcegit Package: sourcegit
Version: 8.18 Version: 8.23
Priority: optional Priority: optional
Depends: libx11-6, libice6, libsm6 Depends: libx11-6, libice6, libsm6
Architecture: amd64 Architecture: amd64

View file

@ -1,5 +0,0 @@
#!bin/sh
echo 'Create link on /usr/bin'
ln -s /opt/sourcegit/sourcegit /usr/bin/sourcegit
exit 0

View file

@ -1,4 +0,0 @@
#!bin/sh
rm -f /usr/bin/sourcegit
exit 0

View file

@ -14,24 +14,23 @@ Requires: libSM.so.6
Open-source & Free Git Gui Client Open-source & Free Git Gui Client
%install %install
mkdir -p $RPM_BUILD_ROOT/opt/sourcegit mkdir -p %{buildroot}/opt/sourcegit
mkdir -p $RPM_BUILD_ROOT/usr/share/applications mkdir -p %{buildroot}/%{_bindir}
mkdir -p $RPM_BUILD_ROOT/usr/share/icons mkdir -p %{buildroot}/usr/share/applications
cp -r ../../_common/applications $RPM_BUILD_ROOT/usr/share/ mkdir -p %{buildroot}/usr/share/icons
cp -r ../../_common/icons $RPM_BUILD_ROOT/usr/share/ cp -f ../../../SourceGit/* %{buildroot}/opt/sourcegit/
cp -f ../../../SourceGit/* $RPM_BUILD_ROOT/opt/sourcegit/ ln -sf ../../opt/sourcegit/sourcegit %{buildroot}/%{_bindir}
chmod 755 -R $RPM_BUILD_ROOT/opt/sourcegit cp -r ../../_common/applications %{buildroot}/%{_datadir}
chmod 755 $RPM_BUILD_ROOT/usr/share/applications/sourcegit.desktop cp -r ../../_common/icons %{buildroot}/%{_datadir}
chmod 755 -R %{buildroot}/opt/sourcegit
chmod 755 %{buildroot}/%{_datadir}/applications/sourcegit.desktop
%files %files
/opt/sourcegit %dir /opt/sourcegit/
/usr/share /opt/sourcegit/*
/usr/share/applications/sourcegit.desktop
%post /usr/share/icons/*
ln -s /opt/sourcegit/sourcegit /usr/bin/sourcegit %{_bindir}/sourcegit
%postun
rm -f /usr/bin/sourcegit
%changelog %changelog
# skip # skip

70
build/scripts/package.linux.sh Executable file
View file

@ -0,0 +1,70 @@
#!/bin/bash
set -e
if [ -z "$VERSION" ]; then
echo "Provide the version as environment variable VERSION"
exit 1
fi
if [ -z "$RUNTIME" ]; then
echo "Provide the runtime as environment variable RUNTIME"
exit 1
fi
arch=
appimage_arch=
target=
case "$RUNTIME" in
linux-x64)
arch=amd64
appimage_arch=x86_64
target=x86_64;;
linux-arm64)
arch=arm64
appimage_arch=arm_aarch64
target=aarch64;;
*)
echo "Unknown runtime $RUNTIME"
exit 1;;
esac
APPIMAGETOOL_URL=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
cd build
if [ ! -f "appimagetool" ]; then
curl -o appimagetool -L "$APPIMAGETOOL_URL"
chmod +x appimagetool
fi
rm -f SourceGit/*.dbg
mkdir -p SourceGit.AppDir/opt
mkdir -p SourceGit.AppDir/usr/share/metainfo
mkdir -p SourceGit.AppDir/usr/share/applications
cp -r SourceGit SourceGit.AppDir/opt/sourcegit
desktop-file-install resources/_common/applications/sourcegit.desktop --dir SourceGit.AppDir/usr/share/applications \
--set-icon com.sourcegit_scm.SourceGit --set-key=Exec --set-value=AppRun
mv SourceGit.AppDir/usr/share/applications/{sourcegit,com.sourcegit_scm.SourceGit}.desktop
cp resources/appimage/sourcegit.png SourceGit.AppDir/com.sourcegit_scm.SourceGit.png
ln -rsf SourceGit.AppDir/opt/sourcegit/sourcegit SourceGit.AppDir/AppRun
ln -rsf SourceGit.AppDir/usr/share/applications/com.sourcegit_scm.SourceGit.desktop SourceGit.AppDir
cp resources/appimage/sourcegit.appdata.xml SourceGit.AppDir/usr/share/metainfo/com.sourcegit_scm.SourceGit.appdata.xml
ARCH="$appimage_arch" ./appimagetool -v SourceGit.AppDir "sourcegit-$VERSION.linux.$arch.AppImage"
mkdir -p resources/deb/opt/sourcegit/
mkdir -p resources/deb/usr/bin
mkdir -p resources/deb/usr/share/applications
mkdir -p resources/deb/usr/share/icons
cp -f SourceGit/* resources/deb/opt/sourcegit
ln -sf ../../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"
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" ./

View file

@ -0,0 +1,22 @@
#!/bin/bash
set -e
if [ -z "$VERSION" ]; then
echo "Provide the version as environment variable VERSION"
exit 1
fi
if [ -z "$RUNTIME" ]; then
echo "Provide the runtime as environment variable RUNTIME"
exit 1
fi
cd build
mkdir -p SourceGit.app/Contents/Resources
mv SourceGit SourceGit.app/Contents/MacOS
cp resources/app/App.icns SourceGit.app/Contents/Resources/App.icns
sed "s/SOURCE_GIT_VERSION/$VERSION/g" resources/app/App.plist > SourceGit.app/Contents/Info.plist
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit.app -x "*/*\.dsym/*"

View file

@ -0,0 +1,19 @@
#!/bin/bash
set -e
if [ -z "$VERSION" ]; then
echo "Provide the version as environment variable VERSION"
exit 1
fi
if [ -z "$RUNTIME" ]; then
echo "Provide the runtime as environment variable RUNTIME"
exit 1
fi
cd build
rm -rf SourceGit/*.pdb
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit

View file

@ -58,6 +58,7 @@ namespace SourceGit
typeof(GridLengthConverter), typeof(GridLengthConverter),
] ]
)] )]
[JsonSerializable(typeof(Models.ExternalToolPaths))]
[JsonSerializable(typeof(Models.InteractiveRebaseJobCollection))] [JsonSerializable(typeof(Models.InteractiveRebaseJobCollection))]
[JsonSerializable(typeof(Models.JetBrainsState))] [JsonSerializable(typeof(Models.JetBrainsState))]
[JsonSerializable(typeof(Models.ThemeOverrides))] [JsonSerializable(typeof(Models.ThemeOverrides))]

View file

@ -33,6 +33,7 @@
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}"/> <NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}"/>
<NativeMenuItemSeparator/> <NativeMenuItemSeparator/>
<NativeMenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" Gesture="⌘+,"/> <NativeMenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" Gesture="⌘+,"/>
<NativeMenuItem Header="{DynamicResource Text.OpenAppDataDir}" Command="{x:Static s:App.OpenAppDataDirCommand}"/>
<NativeMenuItemSeparator/> <NativeMenuItemSeparator/>
<NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}" Gesture="⌘+Q"/> <NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}" Gesture="⌘+Q"/>
</NativeMenu> </NativeMenu>

View file

@ -44,6 +44,8 @@ namespace SourceGit
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
{ {
Native.OS.SetupDataDir();
AppDomain.CurrentDomain.UnhandledException += (_, e) => AppDomain.CurrentDomain.UnhandledException += (_, e) =>
{ {
LogException(e.ExceptionObject as Exception); LogException(e.ExceptionObject as Exception);
@ -107,6 +109,11 @@ namespace SourceGit
dialog.ShowDialog(toplevel); dialog.ShowDialog(toplevel);
}); });
public static readonly SimpleCommand OpenAppDataDirCommand = new SimpleCommand(() =>
{
Native.OS.OpenInFileManager(Native.OS.DataDir);
});
public static readonly SimpleCommand OpenAboutCommand = new SimpleCommand(() => public static readonly SimpleCommand OpenAboutCommand = new SimpleCommand(() =>
{ {
var toplevel = GetTopLevel() as Window; var toplevel = GetTopLevel() as Window;
@ -521,6 +528,8 @@ namespace SourceGit
private void TryLaunchedAsNormal(IClassicDesktopStyleApplicationLifetime desktop) private void TryLaunchedAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
{ {
Native.OS.SetupEnternalTools(); Native.OS.SetupEnternalTools();
Models.AvatarManager.Instance.Start();
Models.AutoFetchManager.Instance.Start();
string startupRepo = null; string startupRepo = null;
if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0])) if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0]))

View file

@ -4,7 +4,7 @@ namespace SourceGit.Commands
{ {
public class Commit : Command public class Commit : Command
{ {
public Commit(string repo, string message, bool autoStage, bool amend, bool allowEmpty = false) public Commit(string repo, string message, bool amend, bool allowEmpty = false)
{ {
var file = Path.GetTempFileName(); var file = Path.GetTempFileName();
File.WriteAllText(file, message); File.WriteAllText(file, message);
@ -13,8 +13,6 @@ namespace SourceGit.Commands
Context = repo; Context = repo;
TraitErrorAsOutput = true; TraitErrorAsOutput = true;
Args = $"commit --file=\"{file}\""; Args = $"commit --file=\"{file}\"";
if (autoStage)
Args += " --all";
if (amend) if (amend)
Args += " --amend --no-edit"; Args += " --amend --no-edit";
if (allowEmpty) if (allowEmpty)

View file

@ -1,7 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace SourceGit.Commands namespace SourceGit.Commands
{ {
@ -26,7 +23,7 @@ namespace SourceGit.Commands
Args += remote; Args += remote;
AutoFetch.MarkFetched(repo); Models.AutoFetchManager.Instance.MarkFetched(repo);
} }
public Fetch(string repo, string remote, string localBranch, string remoteBranch, Action<string> outputHandler) public Fetch(string repo, string remote, string localBranch, string remoteBranch, Action<string> outputHandler)
@ -46,110 +43,4 @@ namespace SourceGit.Commands
private readonly Action<string> _outputHandler; private readonly Action<string> _outputHandler;
} }
public class AutoFetch
{
public static bool IsEnabled
{
get;
set;
} = false;
public static int Interval
{
get => _interval;
set
{
if (value < 1)
return;
_interval = value;
lock (_lock)
{
foreach (var job in _jobs)
{
job.Value.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(_interval));
}
}
}
}
class Job
{
public Fetch Cmd = null;
public DateTime NextRunTimepoint = DateTime.MinValue;
}
static AutoFetch()
{
Task.Run(() =>
{
while (true)
{
if (!IsEnabled)
{
Thread.Sleep(10000);
continue;
}
var now = DateTime.Now;
var uptodate = new List<Job>();
lock (_lock)
{
foreach (var job in _jobs)
{
if (job.Value.NextRunTimepoint.Subtract(now).TotalSeconds <= 0)
{
uptodate.Add(job.Value);
}
}
}
foreach (var job in uptodate)
{
job.Cmd.Exec();
job.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
Thread.Sleep(2000);
}
});
}
public static void AddRepository(string repo)
{
var job = new Job
{
Cmd = new Fetch(repo, "--all", true, false, null) { RaiseError = false },
NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
};
lock (_lock)
{
_jobs[repo] = job;
}
}
public static void RemoveRepository(string repo)
{
lock (_lock)
{
_jobs.Remove(repo);
}
}
public static void MarkFetched(string repo)
{
lock (_lock)
{
if (_jobs.TryGetValue(repo, out var value))
{
value.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
}
}
private static readonly Dictionary<string, Job> _jobs = new Dictionary<string, Job>();
private static readonly object _lock = new object();
private static int _interval = 10;
}
} }

View file

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace SourceGit.Models
{
public class AutoFetchManager
{
public static AutoFetchManager Instance
{
get
{
if (_instance == null)
_instance = new AutoFetchManager();
return _instance;
}
}
public class Job
{
public Commands.Fetch Cmd = null;
public DateTime NextRunTimepoint = DateTime.MinValue;
}
public bool IsEnabled
{
get;
set;
} = false;
public int Interval
{
get => _interval;
set
{
_interval = Math.Max(1, value);
lock (_lock)
{
foreach (var job in _jobs)
job.Value.NextRunTimepoint = DateTime.Now.AddMinutes(_interval * 1.0);
}
}
}
private static AutoFetchManager _instance = null;
private Dictionary<string, Job> _jobs = new Dictionary<string, Job>();
private object _lock = new object();
private int _interval = 10;
public void Start()
{
Task.Run(() =>
{
while (true)
{
if (!IsEnabled)
{
Thread.Sleep(10000);
continue;
}
var now = DateTime.Now;
var uptodate = new List<Job>();
lock (_lock)
{
foreach (var job in _jobs)
{
if (job.Value.NextRunTimepoint.Subtract(now).TotalSeconds <= 0)
uptodate.Add(job.Value);
}
}
foreach (var job in uptodate)
{
job.Cmd.Exec();
job.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
Thread.Sleep(2000);
}
// ReSharper disable once FunctionNeverReturns
});
}
public void AddRepository(string repo)
{
var job = new Job
{
Cmd = new Commands.Fetch(repo, "--all", true, false, null) { RaiseError = false },
NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
};
lock (_lock)
{
_jobs[repo] = job;
}
}
public void RemoveRepository(string repo)
{
lock (_lock)
{
_jobs.Remove(repo);
}
}
public void MarkFetched(string repo)
{
lock (_lock)
{
if (_jobs.TryGetValue(repo, out var value))
value.NextRunTimepoint = DateTime.Now.AddMinutes(Interval * 1.0);
}
}
}
}

View file

@ -20,15 +20,31 @@ namespace SourceGit.Models
void OnAvatarResourceChanged(string email); void OnAvatarResourceChanged(string email);
} }
public static partial class AvatarManager public partial class AvatarManager
{ {
public static string SelectedServer public static AvatarManager Instance
{ {
get; get
set; {
} = "https://www.gravatar.com/avatar/"; if (_instance == null)
_instance = new AvatarManager();
static AvatarManager() return _instance;
}
}
private static AvatarManager _instance = null;
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")]
private static partial Regex REG_GITHUB_USER_EMAIL();
private object _synclock = new object();
private string _storePath;
private List<IAvatarHost> _avatars = new List<IAvatarHost>();
private Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
private HashSet<string> _requesting = new HashSet<string>();
public void Start()
{ {
_storePath = Path.Combine(Native.OS.DataDir, "avatars"); _storePath = Path.Combine(Native.OS.DataDir, "avatars");
if (!Directory.Exists(_storePath)) if (!Directory.Exists(_storePath))
@ -62,7 +78,7 @@ namespace SourceGit.Models
var matchGithubUser = REG_GITHUB_USER_EMAIL().Match(email); var matchGithubUser = REG_GITHUB_USER_EMAIL().Match(email);
var url = matchGithubUser.Success ? var url = matchGithubUser.Success ?
$"https://avatars.githubusercontent.com/{matchGithubUser.Groups[2].Value}" : $"https://avatars.githubusercontent.com/{matchGithubUser.Groups[2].Value}" :
$"{SelectedServer}{md5}?d=404"; $"https://www.gravatar.com/avatar/{md5}?d=404";
var localFile = Path.Combine(_storePath, md5); var localFile = Path.Combine(_storePath, md5);
var img = null as Bitmap; var img = null as Bitmap;
@ -105,20 +121,22 @@ namespace SourceGit.Models
NotifyResourceChanged(email); NotifyResourceChanged(email);
}); });
} }
// ReSharper disable once FunctionNeverReturns
}); });
} }
public static void Subscribe(IAvatarHost host) public void Subscribe(IAvatarHost host)
{ {
_avatars.Add(host); _avatars.Add(host);
} }
public static void Unsubscribe(IAvatarHost host) public void Unsubscribe(IAvatarHost host)
{ {
_avatars.Remove(host); _avatars.Remove(host);
} }
public static Bitmap Request(string email, bool forceRefetch) public Bitmap Request(string email, bool forceRefetch)
{ {
if (forceRefetch) if (forceRefetch)
{ {
@ -167,7 +185,7 @@ namespace SourceGit.Models
return null; return null;
} }
private static string GetEmailHash(string email) private string GetEmailHash(string email)
{ {
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim(); var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered)); var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered));
@ -177,21 +195,12 @@ namespace SourceGit.Models
return builder.ToString(); return builder.ToString();
} }
private static void NotifyResourceChanged(string email) private void NotifyResourceChanged(string email)
{ {
foreach (var avatar in _avatars) foreach (var avatar in _avatars)
{ {
avatar.OnAvatarResourceChanged(email); avatar.OnAvatarResourceChanged(email);
} }
} }
private static readonly object _synclock = new object();
private static readonly string _storePath;
private static readonly List<IAvatarHost> _avatars = new List<IAvatarHost>();
private static readonly Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
private static readonly HashSet<string> _requesting = new HashSet<string>();
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")]
private static partial Regex REG_GITHUB_USER_EMAIL();
} }
} }

8
src/Models/CommitLink.cs Normal file
View file

@ -0,0 +1,8 @@
namespace SourceGit.Models
{
public class CommitLink
{
public string Name { get; set; } = null;
public string URLPrefix { get; set; } = null;
}
}

View file

@ -0,0 +1,22 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.Models
{
public class CommitTemplate : ObservableObject
{
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public string Content
{
get => _content;
set => SetProperty(ref _content, value);
}
private string _name = string.Empty;
private string _content = string.Empty;
}
}

View file

@ -19,8 +19,8 @@ namespace SourceGit.Models
public class TextInlineRange public class TextInlineRange
{ {
public int Start { get; set; } public int Start { get; set; }
public int Count { get; set; } public int End { get; set; }
public TextInlineRange(int p, int n) { Start = p; Count = n; } public TextInlineRange(int p, int n) { Start = p; End = p + n - 1; }
} }
public class TextDiffLine public class TextDiffLine

View file

@ -79,6 +79,12 @@ namespace SourceGit.Models
public string LaunchCommand { get; set; } public string LaunchCommand { get; set; }
} }
public class ExternalToolPaths
{
[JsonPropertyName("tools")]
public Dictionary<string, string> Tools { get; set; } = new Dictionary<string, string>();
}
public class ExternalToolsFinder public class ExternalToolsFinder
{ {
public List<ExternalTool> Founded public List<ExternalTool> Founded
@ -87,42 +93,60 @@ namespace SourceGit.Models
private set; private set;
} = new List<ExternalTool>(); } = new List<ExternalTool>();
public void TryAdd(string name, string icon, string args, string env, Func<string> finder) public ExternalToolsFinder()
{ {
var path = Environment.GetEnvironmentVariable(env); var customPathsConfig = Path.Combine(Native.OS.DataDir, "external_editors.json");
if (string.IsNullOrEmpty(path) || !File.Exists(path)) try
{ {
path = finder(); if (File.Exists(customPathsConfig))
if (string.IsNullOrEmpty(path) || !File.Exists(path)) _customPaths = JsonSerializer.Deserialize(File.ReadAllText(customPathsConfig), JsonCodeGen.Default.ExternalToolPaths);
return; }
catch
{
// Ignore
} }
if (_customPaths == null)
_customPaths = new ExternalToolPaths();
}
public void TryAdd(string name, string icon, string args, string key, Func<string> finder)
{
if (_customPaths.Tools.TryGetValue(key, out var customPath) && File.Exists(customPath))
{
Founded.Add(new ExternalTool(name, icon, customPath, args));
}
else
{
var path = finder();
if (!string.IsNullOrEmpty(path) && File.Exists(path))
Founded.Add(new ExternalTool(name, icon, path, args)); Founded.Add(new ExternalTool(name, icon, path, args));
} }
}
public void VSCode(Func<string> platformFinder) public void VSCode(Func<string> platformFinder)
{ {
TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE_PATH", platformFinder); TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE", platformFinder);
} }
public void VSCodeInsiders(Func<string> platformFinder) public void VSCodeInsiders(Func<string> platformFinder)
{ {
TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS_PATH", platformFinder); TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS", platformFinder);
} }
public void VSCodium(Func<string> platformFinder) public void VSCodium(Func<string> platformFinder)
{ {
TryAdd("VSCodium", "codium", "\"{0}\"", "VSCODIUM_PATH", platformFinder); TryAdd("VSCodium", "codium", "\"{0}\"", "VSCODIUM", platformFinder);
} }
public void Fleet(Func<string> platformFinder) public void Fleet(Func<string> platformFinder)
{ {
TryAdd("Fleet", "fleet", "\"{0}\"", "FLEET_PATH", platformFinder); TryAdd("Fleet", "fleet", "\"{0}\"", "FLEET", platformFinder);
} }
public void SublimeText(Func<string> platformFinder) public void SublimeText(Func<string> platformFinder)
{ {
TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT_PATH", platformFinder); TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT", platformFinder);
} }
public void FindJetBrainsFromToolbox(Func<string> platformFinder) public void FindJetBrainsFromToolbox(Func<string> platformFinder)
@ -146,5 +170,7 @@ namespace SourceGit.Models
} }
} }
} }
private ExternalToolPaths _customPaths = null;
} }
} }

View file

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Avalonia.Controls.Documents;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.Models namespace SourceGit.Models
@ -10,6 +10,7 @@ namespace SourceGit.Models
public int Start { get; set; } = 0; public int Start { get; set; } = 0;
public int Length { get; set; } = 0; public int Length { get; set; } = 0;
public string URL { get; set; } = ""; public string URL { get; set; } = "";
public Run Link { get; set; } = null;
public bool Intersect(int start, int length) public bool Intersect(int start, int length)
{ {

View file

@ -58,10 +58,12 @@ namespace SourceGit.Models
if (URL.StartsWith("http", StringComparison.Ordinal)) if (URL.StartsWith("http", StringComparison.Ordinal))
{ {
if (URL.EndsWith(".git")) // Try to remove the user before host and `.git` extension.
url = URL.Substring(0, URL.Length - 4); var uri = new Uri(URL.EndsWith(".git", StringComparison.Ordinal) ? URL.Substring(0, URL.Length - 4) : URL);
if (uri.Port != 80 && uri.Port != 443)
url = $"{uri.Scheme}://{uri.Host}:{uri.Port}{uri.LocalPath}";
else else
url = URL; url = $"{uri.Scheme}://{uri.Host}{uri.LocalPath}";
return true; return true;
} }

View file

@ -70,6 +70,12 @@ namespace SourceGit.Models
set; set;
} = new AvaloniaList<string>(); } = new AvaloniaList<string>();
public AvaloniaList<CommitTemplate> CommitTemplates
{
get;
set;
} = new AvaloniaList<CommitTemplate>();
public AvaloniaList<string> CommitMessages public AvaloniaList<string> CommitMessages
{ {
get; get;

View file

@ -1,24 +1,102 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using Avalonia; using Avalonia;
using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using AvaloniaEdit; using AvaloniaEdit;
using AvaloniaEdit.TextMate; using AvaloniaEdit.TextMate;
using TextMateSharp.Grammars; using TextMateSharp.Grammars;
using TextMateSharp.Internal.Grammars.Reader;
using TextMateSharp.Internal.Types;
using TextMateSharp.Registry;
using TextMateSharp.Themes;
namespace SourceGit.Models namespace SourceGit.Models
{ {
public class RegistryOptionsWrapper : IRegistryOptions
{
public RegistryOptionsWrapper(ThemeName defaultTheme)
{
_backend = new RegistryOptions(defaultTheme);
_extraGrammars = new List<IRawGrammar>();
string[] extraGrammarFiles = ["toml.json"];
foreach (var file in extraGrammarFiles)
{
var asset = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Grammars/{file}",
UriKind.RelativeOrAbsolute));
try
{
var grammar = GrammarReader.ReadGrammarSync(new StreamReader(asset));
_extraGrammars.Add(grammar);
}
catch
{
// ignore
}
}
}
public IRawTheme GetTheme(string scopeName)
{
return _backend.GetTheme(scopeName);
}
public IRawGrammar GetGrammar(string scopeName)
{
var grammar = _extraGrammars.Find(x => x.GetScopeName().Equals(scopeName, StringComparison.Ordinal));
return grammar ?? _backend.GetGrammar(scopeName);
}
public ICollection<string> GetInjections(string scopeName)
{
return _backend.GetInjections(scopeName);
}
public IRawTheme GetDefaultTheme()
{
return _backend.GetDefaultTheme();
}
public IRawTheme LoadTheme(ThemeName name)
{
return _backend.LoadTheme(name);
}
public string GetScopeByFileName(string filename)
{
var extension = Path.GetExtension(filename);
var grammar = _extraGrammars.Find(x => x.GetScopeName().EndsWith(extension, StringComparison.OrdinalIgnoreCase));
if (grammar != null)
return grammar.GetScopeName();
if (extension == ".h")
extension = ".cpp";
else if (extension == ".resx" || extension == ".plist" || extension == ".manifest")
extension = ".xml";
else if (extension == ".command")
extension = ".sh";
return _backend.GetScopeByExtension(extension);
}
private readonly RegistryOptions _backend;
private readonly List<IRawGrammar> _extraGrammars;
}
public static class TextMateHelper public static class TextMateHelper
{ {
public static TextMate.Installation CreateForEditor(TextEditor editor) public static TextMate.Installation CreateForEditor(TextEditor editor)
{ {
if (Application.Current?.ActualThemeVariant == ThemeVariant.Dark) if (Application.Current?.ActualThemeVariant == ThemeVariant.Dark)
return editor.InstallTextMate(new RegistryOptions(ThemeName.DarkPlus)); return editor.InstallTextMate(new RegistryOptionsWrapper(ThemeName.DarkPlus));
return editor.InstallTextMate(new RegistryOptions(ThemeName.LightPlus)); return editor.InstallTextMate(new RegistryOptionsWrapper(ThemeName.LightPlus));
} }
public static void SetThemeByApp(TextMate.Installation installation) public static void SetThemeByApp(TextMate.Installation installation)
@ -26,26 +104,18 @@ namespace SourceGit.Models
if (installation == null) if (installation == null)
return; return;
if (installation.RegistryOptions is RegistryOptions reg) if (installation.RegistryOptions is RegistryOptionsWrapper reg)
{ {
if (Application.Current?.ActualThemeVariant == ThemeVariant.Dark) var isDark = Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
installation.SetTheme(reg.LoadTheme(ThemeName.DarkPlus)); installation.SetTheme(reg.LoadTheme(isDark ? ThemeName.DarkPlus : ThemeName.LightPlus));
else
installation.SetTheme(reg.LoadTheme(ThemeName.LightPlus));
} }
} }
public static void SetGrammarByFileName(TextMate.Installation installation, string filePath) public static void SetGrammarByFileName(TextMate.Installation installation, string filePath)
{ {
if (installation is { RegistryOptions: RegistryOptions reg }) if (installation is { RegistryOptions: RegistryOptionsWrapper reg })
{ {
var ext = Path.GetExtension(filePath); installation.SetGrammar(reg.GetScopeByFileName(filePath));
if (ext == ".h")
ext = ".cpp";
else if (ext == ".resx" || ext == ".plist")
ext = ".xml";
installation.SetGrammar(reg.GetScopeByExtension(ext));
GC.Collect(); GC.Collect();
} }
} }

View file

@ -5,7 +5,6 @@ using System.IO;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using Avalonia; using Avalonia;
using Avalonia.Dialogs;
using Avalonia.Media; using Avalonia.Media;
namespace SourceGit.Native namespace SourceGit.Native
@ -47,9 +46,6 @@ namespace SourceGit.Native
{ {
EnableIme = true, EnableIme = true,
}); });
// Free-desktop file picker has an extra black background panel.
builder.UseManagedSystemDialogs();
} }
public string FindGitExecutable() public string FindGitExecutable()

View file

@ -53,7 +53,7 @@ namespace SourceGit.Native
{ {
if (Directory.Exists(path)) if (Directory.Exists(path))
{ {
Process.Start("open", path); Process.Start("open", $"\"{path}\"");
} }
else if (File.Exists(path)) else if (File.Exists(path))
{ {

View file

@ -21,9 +21,9 @@ namespace SourceGit.Native
void OpenWithDefaultEditor(string file); void OpenWithDefaultEditor(string file);
} }
public static readonly string DataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SourceGit"); public static string DataDir { get; private set; } = string.Empty;
public static string GitExecutable { get; set; } = string.Empty; public static string GitExecutable { get; set; } = string.Empty;
public static List<Models.ExternalTool> ExternalTools { get; set; } = new List<Models.ExternalTool>(); public static List<Models.ExternalTool> ExternalTools { get; set; } = [];
static OS() static OS()
{ {
@ -70,10 +70,19 @@ namespace SourceGit.Native
public static void SetupApp(AppBuilder builder) public static void SetupApp(AppBuilder builder)
{ {
_backend.SetupApp(builder);
}
public static void SetupDataDir()
{
var osAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (string.IsNullOrEmpty(osAppDataDir))
DataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".sourcegit");
else
DataDir = Path.Combine(osAppDataDir, "SourceGit");
if (!Directory.Exists(DataDir)) if (!Directory.Exists(DataDir))
Directory.CreateDirectory(DataDir); Directory.CreateDirectory(DataDir);
_backend.SetupApp(builder);
} }
public static void SetupEnternalTools() public static void SetupEnternalTools()

View file

@ -0,0 +1,343 @@
{
"version": "1.0.0",
"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."
],
"patterns": [
{
"include": "#commentDirective"
},
{
"include": "#comment"
},
{
"include": "#table"
},
{
"include": "#entryBegin"
},
{
"include": "#value"
}
],
"repository": {
"comment": {
"captures": {
"1": {
"name": "comment.line.number-sign.toml"
},
"2": {
"name": "punctuation.definition.comment.toml"
}
},
"comment": "Comments",
"match": "\\s*((#).*)$"
},
"commentDirective": {
"captures": {
"1": {
"name": "meta.preprocessor.toml"
},
"2": {
"name": "punctuation.definition.meta.preprocessor.toml"
}
},
"comment": "Comments",
"match": "\\s*((#):.*)$"
},
"table": {
"patterns": [
{
"name": "meta.table.toml",
"match": "^\\s*(\\[)\\s*((?:(?:(?:[A-Za-z0-9_+-]+)|(?:\"[^\"]+\")|(?:'[^']+'))\\s*\\.?\\s*)+)\\s*(\\])",
"captures": {
"1": {
"name": "punctuation.definition.table.toml"
},
"2": {
"patterns": [
{
"match": "(?:[A-Za-z0-9_+-]+)|(?:\"[^\"]+\")|(?:'[^']+')",
"name": "support.type.property-name.table.toml"
},
{
"match": "\\.",
"name": "punctuation.separator.dot.toml"
}
]
},
"3": {
"name": "punctuation.definition.table.toml"
}
}
},
{
"name": "meta.array.table.toml",
"match": "^\\s*(\\[\\[)\\s*((?:(?:(?:[A-Za-z0-9_+-]+)|(?:\"[^\"]+\")|(?:'[^']+'))\\s*\\.?\\s*)+)\\s*(\\]\\])",
"captures": {
"1": {
"name": "punctuation.definition.array.table.toml"
},
"2": {
"patterns": [
{
"match": "(?:[A-Za-z0-9_+-]+)|(?:\"[^\"]+\")|(?:'[^']+')",
"name": "support.type.property-name.array.toml"
},
{
"match": "\\.",
"name": "punctuation.separator.dot.toml"
}
]
},
"3": {
"name": "punctuation.definition.array.table.toml"
}
}
},
{
"begin": "(\\{)",
"end": "(\\})",
"name": "meta.table.inline.toml",
"beginCaptures": {
"1": {
"name": "punctuation.definition.table.inline.toml"
}
},
"endCaptures": {
"1": {
"name": "punctuation.definition.table.inline.toml"
}
},
"patterns": [
{
"include": "#comment"
},
{
"match": ",",
"name": "punctuation.separator.table.inline.toml"
},
{
"include": "#entryBegin"
},
{
"include": "#value"
}
]
}
]
},
"entryBegin": {
"name": "meta.entry.toml",
"match": "\\s*((?:(?:(?:[A-Za-z0-9_+-]+)|(?:\"[^\"]+\")|(?:'[^']+'))\\s*\\.?\\s*)+)\\s*(=)",
"captures": {
"1": {
"patterns": [
{
"match": "(?:[A-Za-z0-9_+-]+)|(?:\"[^\"]+\")|(?:'[^']+')",
"name": "support.type.property-name.toml"
},
{
"match": "\\.",
"name": "punctuation.separator.dot.toml"
}
]
},
"2": {
"name": "punctuation.eq.toml"
}
}
},
"value": {
"patterns": [
{
"name": "string.quoted.triple.basic.block.toml",
"begin": "\"\"\"",
"end": "\"\"\"",
"patterns": [
{
"match": "\\\\([btnfr\"\\\\\\n/ ]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})",
"name": "constant.character.escape.toml"
},
{
"match": "\\\\[^btnfr/\"\\\\\\n]",
"name": "invalid.illegal.escape.toml"
}
]
},
{
"name": "string.quoted.single.basic.line.toml",
"begin": "\"",
"end": "\"",
"patterns": [
{
"match": "\\\\([btnfr\"\\\\\\n/ ]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})",
"name": "constant.character.escape.toml"
},
{
"match": "\\\\[^btnfr/\"\\\\\\n]",
"name": "invalid.illegal.escape.toml"
}
]
},
{
"name": "string.quoted.triple.literal.block.toml",
"begin": "'''",
"end": "'''"
},
{
"name": "string.quoted.single.literal.line.toml",
"begin": "'",
"end": "'"
},
{
"captures": {
"1": {
"name": "constant.other.time.datetime.offset.toml"
}
},
"match": "(?<!\\w)(\\d{4}\\-\\d{2}\\-\\d{2}[T| ]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[\\+\\-]\\d{2}:\\d{2}))(?!\\w)"
},
{
"captures": {
"1": {
"name": "constant.other.time.datetime.local.toml"
}
},
"match": "(\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)"
},
{
"name": "constant.other.time.date.toml",
"match": "\\d{4}\\-\\d{2}\\-\\d{2}"
},
{
"name": "constant.other.time.time.toml",
"match": "\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?"
},
{
"match": "(?<!\\w)(true|false)(?!\\w)",
"captures": {
"1": {
"name": "constant.language.boolean.toml"
}
}
},
{
"match": "(?<!\\w)([\\+\\-]?(0|([1-9](([0-9]|_[0-9])+)?))(?:(?:\\.([0-9]+))?[eE][\\+\\-]?[1-9]_?[0-9]*|(?:\\.[0-9_]*)))(?!\\w)",
"captures": {
"1": {
"name": "constant.numeric.float.toml"
}
}
},
{
"match": "(?<!\\w)((?:[\\+\\-]?(0|([1-9](([0-9]|_[0-9])+)?))))(?!\\w)",
"captures": {
"1": {
"name": "constant.numeric.integer.toml"
}
}
},
{
"match": "(?<!\\w)([\\+\\-]?inf)(?!\\w)",
"captures": {
"1": {
"name": "constant.numeric.inf.toml"
}
}
},
{
"match": "(?<!\\w)([\\+\\-]?nan)(?!\\w)",
"captures": {
"1": {
"name": "constant.numeric.nan.toml"
}
}
},
{
"match": "(?<!\\w)((?:0x(([0-9a-fA-F](([0-9a-fA-F]|_[0-9a-fA-F])+)?))))(?!\\w)",
"captures": {
"1": {
"name": "constant.numeric.hex.toml"
}
}
},
{
"match": "(?<!\\w)(0o[0-7](_?[0-7])*)(?!\\w)",
"captures": {
"1": {
"name": "constant.numeric.oct.toml"
}
}
},
{
"match": "(?<!\\w)(0b[01](_?[01])*)(?!\\w)",
"captures": {
"1": {
"name": "constant.numeric.bin.toml"
}
}
},
{
"name": "meta.array.toml",
"begin": "(?<!\\w)(\\[)\\s*",
"end": "\\s*(\\])(?!\\w)",
"beginCaptures": {
"1": {
"name": "punctuation.definition.array.toml"
}
},
"endCaptures": {
"1": {
"name": "punctuation.definition.array.toml"
}
},
"patterns": [
{
"match": ",",
"name": "punctuation.separator.array.toml"
},
{
"include": "#comment"
},
{
"include": "#value"
}
]
},
{
"begin": "(\\{)",
"end": "(\\})",
"name": "meta.table.inline.toml",
"beginCaptures": {
"1": {
"name": "punctuation.definition.table.inline.toml"
}
},
"endCaptures": {
"1": {
"name": "punctuation.definition.table.inline.toml"
}
},
"patterns": [
{
"include": "#comment"
},
{
"match": ",",
"name": "punctuation.separator.table.inline.toml"
},
{
"include": "#entryBegin"
},
{
"include": "#value"
}
]
}
]
}
}
}

View file

@ -12,6 +12,7 @@
<StreamGeometry x:Key="Icons.Clear">M512 57c251 0 455 204 455 455S763 967 512 967 57 763 57 512 261 57 512 57zm181 274c-11-11-29-11-40 0L512 472 371 331c-11-11-29-11-40 0-11 11-11 29 0 40L471 512 331 653c-11 11-11 29 0 40 11 11 29 11 40 0l141-141 141 141c11 11 29 11 40 0 11-11 11-29 0-40L552 512l141-141c11-11 11-29 0-40z</StreamGeometry> <StreamGeometry x:Key="Icons.Clear">M512 57c251 0 455 204 455 455S763 967 512 967 57 763 57 512 261 57 512 57zm181 274c-11-11-29-11-40 0L512 472 371 331c-11-11-29-11-40 0-11 11-11 29 0 40L471 512 331 653c-11 11-11 29 0 40 11 11 29 11 40 0l141-141 141 141c11 11 29 11 40 0 11-11 11-29 0-40L552 512l141-141c11-11 11-29 0-40z</StreamGeometry>
<StreamGeometry x:Key="Icons.Clean">M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z</StreamGeometry> <StreamGeometry x:Key="Icons.Clean">M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z</StreamGeometry>
<StreamGeometry x:Key="Icons.Clone">M1280 704c0 141-115 256-256 256H288C129 960 0 831 0 672c0-126 80-232 192-272A327 327 0 01192 384c0-177 143-320 320-320 119 0 222 64 277 160C820 204 857 192 896 192c106 0 192 86 192 192 0 24-5 48-13 69C1192 477 1280 580 1280 704zm-493-128H656V352c0-18-14-32-32-32h-96c-18 0-32 14-32 32v224h-131c-29 0-43 34-23 55l211 211c12 12 33 12 45 0l211-211c20-20 6-55-23-55z</StreamGeometry> <StreamGeometry x:Key="Icons.Clone">M1280 704c0 141-115 256-256 256H288C129 960 0 831 0 672c0-126 80-232 192-272A327 327 0 01192 384c0-177 143-320 320-320 119 0 222 64 277 160C820 204 857 192 896 192c106 0 192 86 192 192 0 24-5 48-13 69C1192 477 1280 580 1280 704zm-493-128H656V352c0-18-14-32-32-32h-96c-18 0-32 14-32 32v224h-131c-29 0-43 34-23 55l211 211c12 12 33 12 45 0l211-211c20-20 6-55-23-55z</StreamGeometry>
<StreamGeometry x:Key="Icons.Code">M853 102H171C133 102 102 133 102 171v683C102 891 133 922 171 922h683C891 922 922 891 922 853V171C922 133 891 102 853 102zM390 600l-48 48L205 512l137-137 48 48L301 512l88 88zM465 819l-66-18L559 205l66 18L465 819zm218-171L634 600 723 512l-88-88 48-48L819 512 683 649z</StreamGeometry>
<StreamGeometry x:Key="Icons.Commit">M796 471A292 292 0 00512 256a293 293 0 00-284 215H0v144h228A293 293 0 00512 832a291 291 0 00284-217H1024V471h-228M512 688A146 146 0 01366 544A145 145 0 01512 400c80 0 146 63 146 144A146 146 0 01512 688</StreamGeometry> <StreamGeometry x:Key="Icons.Commit">M796 471A292 292 0 00512 256a293 293 0 00-284 215H0v144h228A293 293 0 00512 832a291 291 0 00284-217H1024V471h-228M512 688A146 146 0 01366 544A145 145 0 01512 400c80 0 146 63 146 144A146 146 0 01512 688</StreamGeometry>
<StreamGeometry x:Key="Icons.Compare">M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z</StreamGeometry> <StreamGeometry x:Key="Icons.Compare">M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z</StreamGeometry>
<StreamGeometry x:Key="Icons.Conflict">M608 0q48 0 88 23t63 63 23 87v70h55q35 0 67 14t57 38 38 57 14 67V831q0 34-14 66t-38 57-57 38-67 13H426q-34 0-66-13t-57-38-38-57-14-66v-70h-56q-34 0-66-14t-57-38-38-57-13-67V174q0-47 23-87T109 23 196 0h412m175 244H426q-46 0-86 22T278 328t-26 85v348H608q47 0 86-22t63-62 25-85l1-348m-269 318q18 0 31 13t13 31-13 31-31 13-31-13-13-31 13-31 31-13m0-212q13 0 22 9t11 22v125q0 14-9 23t-22 10-23-7-11-22l-1-126q0-13 10-23t23-10z</StreamGeometry> <StreamGeometry x:Key="Icons.Conflict">M608 0q48 0 88 23t63 63 23 87v70h55q35 0 67 14t57 38 38 57 14 67V831q0 34-14 66t-38 57-57 38-67 13H426q-34 0-66-13t-57-38-38-57-14-66v-70h-56q-34 0-66-14t-57-38-38-57-13-67V174q0-47 23-87T109 23 196 0h412m175 244H426q-46 0-86 22T278 328t-26 85v348H608q47 0 86-22t63-62 25-85l1-348m-269 318q18 0 31 13t13 31-13 31-31 13-31-13-13-31 13-31 31-13m0-212q13 0 22 9t11 22v125q0 14-9 23t-22 10-23-7-11-22l-1-126q0-13 10-23t23-10z</StreamGeometry>
@ -33,6 +34,7 @@
<StreamGeometry x:Key="Icons.Fetch">M1024 896v128H0V704h128v192h768V704h128v192zM576 555 811 320 896 405l-384 384-384-384L213 320 448 555V0h128v555z</StreamGeometry> <StreamGeometry x:Key="Icons.Fetch">M1024 896v128H0V704h128v192h768V704h128v192zM576 555 811 320 896 405l-384 384-384-384L213 320 448 555V0h128v555z</StreamGeometry>
<StreamGeometry x:Key="Icons.File">M959 320H960v640A64 64 0 01896 1024H192A64 64 0 01128 960V64A64 64 0 01192 0H640v321h320L959 320zM320 544c0 17 14 32 32 32h384A32 32 0 00768 544c0-17-14-32-32-32H352A32 32 0 00320 544zm0 128c0 17 14 32 32 32h384a32 32 0 0032-32c0-17-14-32-32-32H352a32 32 0 00-32 32zm0 128c0 17 14 32 32 32h384a32 32 0 0032-32c0-17-14-32-32-32H352a32 32 0 00-32 32z</StreamGeometry> <StreamGeometry x:Key="Icons.File">M959 320H960v640A64 64 0 01896 1024H192A64 64 0 01128 960V64A64 64 0 01192 0H640v321h320L959 320zM320 544c0 17 14 32 32 32h384A32 32 0 00768 544c0-17-14-32-32-32H352A32 32 0 00320 544zm0 128c0 17 14 32 32 32h384a32 32 0 0032-32c0-17-14-32-32-32H352a32 32 0 00-32 32zm0 128c0 17 14 32 32 32h384a32 32 0 0032-32c0-17-14-32-32-32H352a32 32 0 00-32 32z</StreamGeometry>
<StreamGeometry x:Key="Icons.File.Add">M683 85l213 213v598a42 42 0 01-42 42H170A43 43 0 01128 896V128C128 104 147 85 170 85H683zm-213 384H341v85h128v128h85v-128h128v-85h-128V341h-85v128z</StreamGeometry> <StreamGeometry x:Key="Icons.File.Add">M683 85l213 213v598a42 42 0 01-42 42H170A43 43 0 01128 896V128C128 104 147 85 170 85H683zm-213 384H341v85h128v128h85v-128h128v-85h-128V341h-85v128z</StreamGeometry>
<StreamGeometry x:Key="Icons.File.Checkout">M949 727l-217-231a33 33 0 00-48 0 33 33 0 000 48l157 172H389a35 35 0 00-35 35c0 19 16 34 35 34h452l-160 179a34 34 0 005 54c14 10 33 7 45-5l219-237a33 33 0 000-49zM719 196h131c-24-91-95-160-185-185v131c0 27 25 54 54 54zM129 846l1-747s-7-37 36-33h359v52s-7 76 32 133a191 191 0 00146 84h91v126h66v-191H719a126 126 0 01-127-127V0H155c-51 0-91 40-91 91v767c0 51 40 91 91 91h193v-66H155c0-0-26 4-26-36z</StreamGeometry>
<StreamGeometry x:Key="Icons.File.Ignore">M416 832H128V128h384v192C512 355 541 384 576 384L768 384v32c0 19 13 32 32 32S832 435 832 416v-64c0-6 0-19-6-25l-256-256c-6-6-19-6-25-6H128A64 64 0 0064 128v704C64 867 93 896 129 896h288c19 0 32-13 32-32S435 832 416 832zM576 172 722 320H576V172zM736 512C614 512 512 614 512 736S614 960 736 960s224-102 224-224S858 512 736 512zM576 736C576 646 646 576 736 576c32 0 58 6 83 26l-218 218c-19-26-26-51-26-83zm160 160c-32 0-64-13-96-32l224-224c19 26 32 58 32 96 0 90-70 160-160 160z</StreamGeometry> <StreamGeometry x:Key="Icons.File.Ignore">M416 832H128V128h384v192C512 355 541 384 576 384L768 384v32c0 19 13 32 32 32S832 435 832 416v-64c0-6 0-19-6-25l-256-256c-6-6-19-6-25-6H128A64 64 0 0064 128v704C64 867 93 896 129 896h288c19 0 32-13 32-32S435 832 416 832zM576 172 722 320H576V172zM736 512C614 512 512 614 512 736S614 960 736 960s224-102 224-224S858 512 736 512zM576 736C576 646 646 576 736 576c32 0 58 6 83 26l-218 218c-19-26-26-51-26-83zm160 160c-32 0-64-13-96-32l224-224c19 26 32 58 32 96 0 90-70 160-160 160z</StreamGeometry>
<StreamGeometry x:Key="Icons.File.Remove">M896 320c0-19-6-32-19-45l-192-192c-13-13-26-19-45-19H192c-38 0-64 26-64 64v768c0 38 26 64 64 64h640c38 0 64-26 64-64V320zm-256 384H384c-19 0-32-13-32-32s13-32 32-32h256c19 0 32 13 32 32s-13 32-32 32zm166-384H640V128l192 192h-26z</StreamGeometry> <StreamGeometry x:Key="Icons.File.Remove">M896 320c0-19-6-32-19-45l-192-192c-13-13-26-19-45-19H192c-38 0-64 26-64 64v768c0 38 26 64 64 64h640c38 0 64-26 64-64V320zm-256 384H384c-19 0-32-13-32-32s13-32 32-32h256c19 0 32 13 32 32s-13 32-32 32zm166-384H640V128l192 192h-26z</StreamGeometry>
<StreamGeometry x:Key="Icons.Filter">M599 425 599 657 425 832 425 425 192 192 832 192Z</StreamGeometry> <StreamGeometry x:Key="Icons.Filter">M599 425 599 657 425 832 425 425 192 192 832 192Z</StreamGeometry>
@ -59,6 +61,7 @@
<StreamGeometry x:Key="Icons.LFS">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</StreamGeometry> <StreamGeometry x:Key="Icons.LFS">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Lines.Incr">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</StreamGeometry> <StreamGeometry x:Key="Icons.Lines.Incr">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Lines.Decr">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</StreamGeometry> <StreamGeometry x:Key="Icons.Lines.Decr">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Link">M 968 418 l -95 94 c -59 59 -146 71 -218 37 L 874 331 a 64 64 0 0 0 0 -90 L 783 150 a 64 64 0 0 0 -90 0 L 475 368 c -34 -71 -22 -159 37 -218 l 94 -94 c 75 -75 196 -75 271 0 l 90 90 c 75 75 75 196 0 271 z M 332 693 a 64 64 0 0 1 0 -90 l 271 -271 c 25 -25 65 -25 90 0 s 25 65 0 90 L 422 693 a 64 64 0 0 1 -90 0 z M 151 783 l 90 90 a 64 64 0 0 0 90 0 l 218 -218 c 34 71 22 159 -37 218 l -86 94 a 192 192 0 0 1 -271 0 l -98 -98 a 192 192 0 0 1 0 -271 l 94 -86 c 59 -59 146 -71 218 -37 L 151 693 a 64 64 0 0 0 0 90 z</StreamGeometry>
<StreamGeometry x:Key="Icons.List">M0 33h1024v160H0zM0 432h1024v160H0zM0 831h1024v160H0z</StreamGeometry> <StreamGeometry x:Key="Icons.List">M0 33h1024v160H0zM0 432h1024v160H0zM0 831h1024v160H0z</StreamGeometry>
<StreamGeometry x:Key="Icons.Loading">M512 0C233 0 7 223 0 500C6 258 190 64 416 64c230 0 416 200 416 448c0 53 43 96 96 96s96-43 96-96c0-283-229-512-512-512zm0 1023c279 0 505-223 512-500c-6 242-190 436-416 436c-230 0-416-200-416-448c0-53-43-96-96-96s-96 43-96 96c0 283 229 512 512 512z</StreamGeometry> <StreamGeometry x:Key="Icons.Loading">M512 0C233 0 7 223 0 500C6 258 190 64 416 64c230 0 416 200 416 448c0 53 43 96 96 96s96-43 96-96c0-283-229-512-512-512zm0 1023c279 0 505-223 512-500c-6 242-190 436-416 436c-230 0-416-200-416-448c0-53-43-96-96-96s-96 43-96 96c0 283 229 512 512 512z</StreamGeometry>
<StreamGeometry x:Key="Icons.Local">M976 0h-928A48 48 0 000 48v652a48 48 0 0048 48h416V928H200a48 48 0 000 96h624a48 48 0 000-96H560v-180h416a48 48 0 0048-48V48A48 48 0 00976 0zM928 652H96V96h832v556z</StreamGeometry> <StreamGeometry x:Key="Icons.Local">M976 0h-928A48 48 0 000 48v652a48 48 0 0048 48h416V928H200a48 48 0 000 96h624a48 48 0 000-96H560v-180h416a48 48 0 0048-48V48A48 48 0 00976 0zM928 652H96V96h832v556z</StreamGeometry>

View file

@ -6,9 +6,9 @@
<x:String x:Key="Text.About.Menu" xml:space="preserve">Über SourceGit</x:String> <x:String x:Key="Text.About.Menu" xml:space="preserve">Über SourceGit</x:String>
<x:String x:Key="Text.About.BuildWith" xml:space="preserve">• Erstellt mit </x:String> <x:String x:Key="Text.About.BuildWith" xml:space="preserve">• Erstellt mit </x:String>
<x:String x:Key="Text.About.Copyright" xml:space="preserve">© 2024 sourcegit-scm</x:String> <x:String x:Key="Text.About.Copyright" xml:space="preserve">© 2024 sourcegit-scm</x:String>
<x:String x:Key="Text.About.Editor" xml:space="preserve">• Text Editor von </x:String> <x:String x:Key="Text.About.Editor" xml:space="preserve">• Texteditor von </x:String>
<x:String x:Key="Text.About.Fonts" xml:space="preserve">• Monospace-Schriftarten von </x:String> <x:String x:Key="Text.About.Fonts" xml:space="preserve">• Monospace-Schriftarten von </x:String>
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• Quelltext findest du unter </x:String> <x:String x:Key="Text.About.SourceCode" xml:space="preserve">• Quelltext findest du auf </x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">Open Source &amp; freier Git GUI Client</x:String> <x:String x:Key="Text.About.SubTitle" xml:space="preserve">Open Source &amp; freier Git GUI Client</x:String>
<x:String x:Key="Text.AddWorktree" xml:space="preserve">Worktree hinzufügen</x:String> <x:String x:Key="Text.AddWorktree" xml:space="preserve">Worktree hinzufügen</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">Was auschecken:</x:String> <x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">Was auschecken:</x:String>
@ -29,7 +29,7 @@
<x:String x:Key="Text.Apply.File.Placeholder" xml:space="preserve">Wähle die anzuwendende .patch-Datei</x:String> <x:String x:Key="Text.Apply.File.Placeholder" xml:space="preserve">Wähle die anzuwendende .patch-Datei</x:String>
<x:String x:Key="Text.Apply.IgnoreWS" xml:space="preserve">Ignoriere Leerzeichenänderungen</x:String> <x:String x:Key="Text.Apply.IgnoreWS" xml:space="preserve">Ignoriere Leerzeichenänderungen</x:String>
<x:String x:Key="Text.Apply.NoWarn" xml:space="preserve">Keine Warnungen</x:String> <x:String x:Key="Text.Apply.NoWarn" xml:space="preserve">Keine Warnungen</x:String>
<x:String x:Key="Text.Apply.NoWarn.Desc" xml:space="preserve">Schaltet die Warnung vor nachgestellte Leerzeichen aus</x:String> <x:String x:Key="Text.Apply.NoWarn.Desc" xml:space="preserve">Keine Warnung vor Leerzeichen am Zeilenende</x:String>
<x:String x:Key="Text.Apply.Title" xml:space="preserve">Patch anwenden</x:String> <x:String x:Key="Text.Apply.Title" xml:space="preserve">Patch anwenden</x:String>
<x:String x:Key="Text.Apply.Warn" xml:space="preserve">Warnen</x:String> <x:String x:Key="Text.Apply.Warn" xml:space="preserve">Warnen</x:String>
<x:String x:Key="Text.Apply.Warn.Desc" xml:space="preserve">Gibt eine Warnung für ein paar solcher Fehler aus, aber wendet es an</x:String> <x:String x:Key="Text.Apply.Warn.Desc" xml:space="preserve">Gibt eine Warnung für ein paar solcher Fehler aus, aber wendet es an</x:String>
@ -41,12 +41,12 @@
<x:String x:Key="Text.Archive.Title" xml:space="preserve">Archiv erstellen</x:String> <x:String x:Key="Text.Archive.Title" xml:space="preserve">Archiv erstellen</x:String>
<x:String x:Key="Text.Askpass" xml:space="preserve">SourceGit Askpass</x:String> <x:String x:Key="Text.Askpass" xml:space="preserve">SourceGit Askpass</x:String>
<x:String x:Key="Text.AssumeUnchanged" xml:space="preserve">ALS UNVERÄNDERT ANGENOMMENE DATEIEN</x:String> <x:String x:Key="Text.AssumeUnchanged" xml:space="preserve">ALS UNVERÄNDERT ANGENOMMENE DATEIEN</x:String>
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">KEINE UNVERÄNDERT ANGENOMMENEN DATEIEN GEFUNDEN</x:String> <x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">KEINE ALS UNVERÄNDERT ANGENOMMENEN DATEIEN</x:String>
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">ENTFERNEN</x:String> <x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">ENTFERNEN</x:String>
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">BINÄRE DATEI NICHT UNTERSTÜTZT!!!</x:String> <x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">BINÄRE DATEI NICHT UNTERSTÜTZT!!!</x:String>
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String> <x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">BLAME WIRD BEI DIESER DATEI NICHT UNTERSTÜTZT!!!</x:String> <x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">BLAME WIRD BEI DIESER DATEI NICHT UNTERSTÜTZT!!!</x:String>
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">${0}$ auschecken...</x:String> <x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Auscheken von ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">Mit Branch vergleichen</x:String> <x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">Mit Branch vergleichen</x:String>
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Mit HEAD vergleichen</x:String> <x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Mit HEAD vergleichen</x:String>
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">Mit Worktree vergleichen</x:String> <x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">Mit Worktree vergleichen</x:String>
@ -166,6 +166,7 @@
<x:String x:Key="Text.CreateTag.Type" xml:space="preserve">Art:</x:String> <x:String x:Key="Text.CreateTag.Type" xml:space="preserve">Art:</x:String>
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">Mit Anmerkung</x:String> <x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">Mit Anmerkung</x:String>
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">Ohne Anmerkung</x:String> <x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">Ohne Anmerkung</x:String>
<x:String x:Key="Text.CtrlClickTip" xml:space="preserve">Halte Strg gedrückt, um direkt auszuführen</x:String>
<x:String x:Key="Text.Cut" xml:space="preserve">Ausschneiden</x:String> <x:String x:Key="Text.Cut" xml:space="preserve">Ausschneiden</x:String>
<x:String x:Key="Text.DeleteBranch" xml:space="preserve">Branch löschen</x:String> <x:String x:Key="Text.DeleteBranch" xml:space="preserve">Branch löschen</x:String>
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch:</x:String> <x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch:</x:String>
@ -218,7 +219,7 @@
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String> <x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Alle Remotes fetchen</x:String> <x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Alle Remotes fetchen</x:String>
<x:String x:Key="Text.Fetch.NoTags" xml:space="preserve">Ohne Tags fetchen</x:String> <x:String x:Key="Text.Fetch.NoTags" xml:space="preserve">Ohne Tags fetchen</x:String>
<x:String x:Key="Text.Fetch.Prune" xml:space="preserve">Alle toten remote Branches entfernen</x:String> <x:String x:Key="Text.Fetch.Prune" xml:space="preserve">Alle verwaisten Branches entfernen</x:String>
<x:String x:Key="Text.Fetch.Remote" xml:space="preserve">Remote:</x:String> <x:String x:Key="Text.Fetch.Remote" xml:space="preserve">Remote:</x:String>
<x:String x:Key="Text.Fetch.Title" xml:space="preserve">Remote-Änderungen fetchen</x:String> <x:String x:Key="Text.Fetch.Title" xml:space="preserve">Remote-Änderungen fetchen</x:String>
<x:String x:Key="Text.FileCM.AssumeUnchanged" xml:space="preserve">Als unverändert annehmen</x:String> <x:String x:Key="Text.FileCM.AssumeUnchanged" xml:space="preserve">Als unverändert annehmen</x:String>
@ -240,7 +241,7 @@
<x:String x:Key="Text.FileHistory" xml:space="preserve">Datei Historie</x:String> <x:String x:Key="Text.FileHistory" xml:space="preserve">Datei Historie</x:String>
<x:String x:Key="Text.Filter" xml:space="preserve">FILTER</x:String> <x:String x:Key="Text.Filter" xml:space="preserve">FILTER</x:String>
<x:String x:Key="Text.GitFlow" xml:space="preserve">Git-Flow</x:String> <x:String x:Key="Text.GitFlow" xml:space="preserve">Git-Flow</x:String>
<x:String x:Key="Text.GitFlow.DevelopBranch" xml:space="preserve">Entwicklungs-Branch:</x:String> <x:String x:Key="Text.GitFlow.DevelopBranch" xml:space="preserve">Development-Branch:</x:String>
<x:String x:Key="Text.GitFlow.Feature" xml:space="preserve">Feature:</x:String> <x:String x:Key="Text.GitFlow.Feature" xml:space="preserve">Feature:</x:String>
<x:String x:Key="Text.GitFlow.FeaturePrefix" xml:space="preserve">Feature-Prefix:</x:String> <x:String x:Key="Text.GitFlow.FeaturePrefix" xml:space="preserve">Feature-Prefix:</x:String>
<x:String x:Key="Text.GitFlow.FinishFeature" xml:space="preserve">FLOW - Finish Feature</x:String> <x:String x:Key="Text.GitFlow.FinishFeature" xml:space="preserve">FLOW - Finish Feature</x:String>
@ -251,7 +252,7 @@
<x:String x:Key="Text.GitFlow.HotfixPrefix" xml:space="preserve">Hotfix-Prefix:</x:String> <x:String x:Key="Text.GitFlow.HotfixPrefix" xml:space="preserve">Hotfix-Prefix:</x:String>
<x:String x:Key="Text.GitFlow.Init" xml:space="preserve">Git-Flow initialisieren</x:String> <x:String x:Key="Text.GitFlow.Init" xml:space="preserve">Git-Flow initialisieren</x:String>
<x:String x:Key="Text.GitFlow.KeepBranchAfterFinish" xml:space="preserve">Branch behalten</x:String> <x:String x:Key="Text.GitFlow.KeepBranchAfterFinish" xml:space="preserve">Branch behalten</x:String>
<x:String x:Key="Text.GitFlow.ProductionBranch" xml:space="preserve">Produktions-Branch:</x:String> <x:String x:Key="Text.GitFlow.ProductionBranch" xml:space="preserve">Production-Branch:</x:String>
<x:String x:Key="Text.GitFlow.Release" xml:space="preserve">Release:</x:String> <x:String x:Key="Text.GitFlow.Release" xml:space="preserve">Release:</x:String>
<x:String x:Key="Text.GitFlow.ReleasePrefix" xml:space="preserve">Release-Prefix:</x:String> <x:String x:Key="Text.GitFlow.ReleasePrefix" xml:space="preserve">Release-Prefix:</x:String>
<x:String x:Key="Text.GitFlow.StartFeature" xml:space="preserve">Feature starten...</x:String> <x:String x:Key="Text.GitFlow.StartFeature" xml:space="preserve">Feature starten...</x:String>
@ -286,9 +287,9 @@
<x:String x:Key="Text.GitLFS.Push.Title" xml:space="preserve">LFS Objekte pushen</x:String> <x:String x:Key="Text.GitLFS.Push.Title" xml:space="preserve">LFS Objekte pushen</x:String>
<x:String x:Key="Text.GitLFS.Push.Tips" xml:space="preserve">Pushe große Dateien in der Warteschlange zum Git LFS Endpunkt</x:String> <x:String x:Key="Text.GitLFS.Push.Tips" xml:space="preserve">Pushe große Dateien in der Warteschlange zum Git LFS Endpunkt</x:String>
<x:String x:Key="Text.GitLFS.Remote" xml:space="preserve">Remote:</x:String> <x:String x:Key="Text.GitLFS.Remote" xml:space="preserve">Remote:</x:String>
<x:String x:Key="Text.GitLFS.Track" xml:space="preserve">Verfolge '{0}' benannte Dateien</x:String> <x:String x:Key="Text.GitLFS.Track" xml:space="preserve">Verfolge alle '{0}' Dateien</x:String>
<x:String x:Key="Text.GitLFS.TrackByExtension" xml:space="preserve">Verfolge alle *{0} Dateien</x:String> <x:String x:Key="Text.GitLFS.TrackByExtension" xml:space="preserve">Verfolge alle *{0} Dateien</x:String>
<x:String x:Key="Text.Histories" xml:space="preserve">Historien</x:String> <x:String x:Key="Text.Histories" xml:space="preserve">Verlauf</x:String>
<x:String x:Key="Text.Histories.DisplayMode" xml:space="preserve">Wechsle zwischen horizontalem und vertikalem Layout</x:String> <x:String x:Key="Text.Histories.DisplayMode" xml:space="preserve">Wechsle zwischen horizontalem und vertikalem Layout</x:String>
<x:String x:Key="Text.Histories.GraphMode" xml:space="preserve">Wechsle zwischen Kurven- und Konturgraphenmodus</x:String> <x:String x:Key="Text.Histories.GraphMode" xml:space="preserve">Wechsle zwischen Kurven- und Konturgraphenmodus</x:String>
<x:String x:Key="Text.Histories.Header.Author" xml:space="preserve">AUTOR</x:String> <x:String x:Key="Text.Histories.Header.Author" xml:space="preserve">AUTOR</x:String>
@ -314,7 +315,7 @@
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">Ausgewählte Änderungen stagen/unstagen</x:String> <x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">Ausgewählte Änderungen stagen/unstagen</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">Commit-Suchmodus</x:String> <x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">Commit-Suchmodus</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewChanges" xml:space="preserve">Wechsle zu 'Änderungen'</x:String> <x:String x:Key="Text.Hotkeys.Repo.ViewChanges" xml:space="preserve">Wechsle zu 'Änderungen'</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewHistories" xml:space="preserve">Wechsle zu 'Historien'</x:String> <x:String x:Key="Text.Hotkeys.Repo.ViewHistories" xml:space="preserve">Wechsle zu 'Verlauf'</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewStashes" xml:space="preserve">Wechsle zu 'Stashes'</x:String> <x:String x:Key="Text.Hotkeys.Repo.ViewStashes" xml:space="preserve">Wechsle zu 'Stashes'</x:String>
<x:String x:Key="Text.Hotkeys.TextEditor" xml:space="preserve">TEXTEDITOR</x:String> <x:String x:Key="Text.Hotkeys.TextEditor" xml:space="preserve">TEXTEDITOR</x:String>
<x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">Suchpanel schließen</x:String> <x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">Suchpanel schließen</x:String>
@ -347,14 +348,15 @@
<x:String x:Key="Text.Name" xml:space="preserve">Name:</x:String> <x:String x:Key="Text.Name" xml:space="preserve">Name:</x:String>
<x:String x:Key="Text.NotConfigured" xml:space="preserve">Git wurde NICHT konfiguriert. Gehe bitte zuerst in die [Einstellungen] und konfiguriere Git.</x:String> <x:String x:Key="Text.NotConfigured" xml:space="preserve">Git wurde NICHT konfiguriert. Gehe bitte zuerst in die [Einstellungen] und konfiguriere Git.</x:String>
<x:String x:Key="Text.Notice" xml:space="preserve">BENACHRICHTIGUNG</x:String> <x:String x:Key="Text.Notice" xml:space="preserve">BENACHRICHTIGUNG</x:String>
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">App-Daten Ordner öffnen</x:String>
<x:String x:Key="Text.OpenFolder" xml:space="preserve">ORDNER AUSWÄHLEN</x:String> <x:String x:Key="Text.OpenFolder" xml:space="preserve">ORDNER AUSWÄHLEN</x:String>
<x:String x:Key="Text.OpenWith" xml:space="preserve">Öffne mit...</x:String> <x:String x:Key="Text.OpenWith" xml:space="preserve">Öffne mit...</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Optional.</x:String> <x:String x:Key="Text.Optional" xml:space="preserve">Optional.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Neue Seite erstellen</x:String> <x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Neue Seite erstellen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Lesezeichen</x:String> <x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Lesezeichen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Tab schließen</x:String> <x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Tab schließen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Schließe andere Tabs</x:String> <x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Andere Tabs schließen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Schließe Rechte Tabs</x:String> <x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Rechte Tabs schließen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Kopiere Repository-Pfad</x:String> <x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Kopiere Repository-Pfad</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repositories</x:String> <x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repositories</x:String>
<x:String x:Key="Text.Paste" xml:space="preserve">Einfügen</x:String> <x:String x:Key="Text.Paste" xml:space="preserve">Einfügen</x:String>
@ -368,20 +370,19 @@
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">Leztes Jahr</x:String> <x:String x:Key="Text.Period.LastYear" xml:space="preserve">Leztes Jahr</x:String>
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">Vor {0} Jahren</x:String> <x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">Vor {0} Jahren</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">Einstellungen</x:String> <x:String x:Key="Text.Preference" xml:space="preserve">Einstellungen</x:String>
<x:String x:Key="Text.Preference.Appearance" xml:space="preserve">ERSCHEINUNGSBILD</x:String> <x:String x:Key="Text.Preference.Appearance" xml:space="preserve">DARSTELLUNG</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFont" xml:space="preserve">Standardschriftart</x:String> <x:String x:Key="Text.Preference.Appearance.DefaultFont" xml:space="preserve">Standardschriftart</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Standardschriftgröße</x:String> <x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Standardschriftgröße</x:String>
<x:String x:Key="Text.Preference.Appearance.MonospaceFont" xml:space="preserve">Monospace-Schriftart</x:String> <x:String x:Key="Text.Preference.Appearance.MonospaceFont" xml:space="preserve">Monospace-Schriftart</x:String>
<x:String x:Key="Text.Preference.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">Verwende nur die Monospace-Schriftart im Text Editor</x:String> <x:String x:Key="Text.Preference.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">Verwende nur die Monospace-Schriftart im Texteditor</x:String>
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Design</x:String> <x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Design</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Design-Anpassungen</x:String> <x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Design-Anpassungen</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">ALLGEMEIN</x:String> <x:String x:Key="Text.Preference.General" xml:space="preserve">ALLGEMEIN</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">Avatar Server</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Beim Starten nach Updates suchen</x:String> <x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Beim Starten nach Updates suchen</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Sprache</x:String> <x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Sprache</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">Commit-Historie</x:String> <x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">Commit-Historie</x:String>
<x:String x:Key="Text.Preference.General.RestoreTabs" xml:space="preserve">Zuletzt geöffnete Tabs beim Starten wiederherstellen</x:String> <x:String x:Key="Text.Preference.General.RestoreTabs" xml:space="preserve">Zuletzt geöffnete Tabs beim Starten wiederherstellen</x:String>
<x:String x:Key="Text.Preference.General.SubjectGuideLength" xml:space="preserve">Commit-Nachricht Hinweislänge</x:String> <x:String x:Key="Text.Preference.General.SubjectGuideLength" xml:space="preserve">Längenvorgabe für Commit-Nachrichten</x:String>
<x:String x:Key="Text.Preference.General.UseFixedTabWidth" xml:space="preserve">Fixe Tab-Breite in Titelleiste</x:String> <x:String x:Key="Text.Preference.General.UseFixedTabWidth" xml:space="preserve">Fixe Tab-Breite in Titelleiste</x:String>
<x:String x:Key="Text.Preference.General.VisibleDiffContextLines" xml:space="preserve">Sichtbare Vergleichskontextzeilen</x:String> <x:String x:Key="Text.Preference.General.VisibleDiffContextLines" xml:space="preserve">Sichtbare Vergleichskontextzeilen</x:String>
<x:String x:Key="Text.Preference.Git" xml:space="preserve">GIT</x:String> <x:String x:Key="Text.Preference.Git" xml:space="preserve">GIT</x:String>
@ -403,12 +404,12 @@
<x:String x:Key="Text.Preference.GPG.TagEnabled" xml:space="preserve">Tag-Signierung</x:String> <x:String x:Key="Text.Preference.GPG.TagEnabled" xml:space="preserve">Tag-Signierung</x:String>
<x:String x:Key="Text.Preference.GPG.Format" xml:space="preserve">GPG Format</x:String> <x:String x:Key="Text.Preference.GPG.Format" xml:space="preserve">GPG Format</x:String>
<x:String x:Key="Text.Preference.GPG.Path" xml:space="preserve">GPG Installationspfad</x:String> <x:String x:Key="Text.Preference.GPG.Path" xml:space="preserve">GPG Installationspfad</x:String>
<x:String x:Key="Text.Preference.GPG.Path.Placeholder" xml:space="preserve">Gebe Installationspfad zu installiertem GPG Programm an</x:String> <x:String x:Key="Text.Preference.GPG.Path.Placeholder" xml:space="preserve">Installationspfad zum GPG Programm</x:String>
<x:String x:Key="Text.Preference.GPG.UserKey" xml:space="preserve">Benutzer Signierungsschlüssel</x:String> <x:String x:Key="Text.Preference.GPG.UserKey" xml:space="preserve">Benutzer Signierungsschlüssel</x:String>
<x:String x:Key="Text.Preference.GPG.UserKey.Placeholder" xml:space="preserve">GPG Benutzer Signierungsschlüssel</x:String> <x:String x:Key="Text.Preference.GPG.UserKey.Placeholder" xml:space="preserve">GPG Benutzer Signierungsschlüssel</x:String>
<x:String x:Key="Text.Preference.DiffMerge" xml:space="preserve">DIFF/MERGE TOOL</x:String> <x:String x:Key="Text.Preference.DiffMerge" xml:space="preserve">DIFF/MERGE TOOL</x:String>
<x:String x:Key="Text.Preference.DiffMerge.Path" xml:space="preserve">Installationspfad</x:String> <x:String x:Key="Text.Preference.DiffMerge.Path" xml:space="preserve">Installationspfad</x:String>
<x:String x:Key="Text.Preference.DiffMerge.Path.Placeholder" xml:space="preserve">Gebe Installationspfad zum Diff/Merge Tool an</x:String> <x:String x:Key="Text.Preference.DiffMerge.Path.Placeholder" xml:space="preserve">Installationspfad zum Diff/Merge Tool</x:String>
<x:String x:Key="Text.Preference.DiffMerge.Type" xml:space="preserve">Tool</x:String> <x:String x:Key="Text.Preference.DiffMerge.Type" xml:space="preserve">Tool</x:String>
<x:String x:Key="Text.PruneRemote" xml:space="preserve">Remote löschen</x:String> <x:String x:Key="Text.PruneRemote" xml:space="preserve">Remote löschen</x:String>
<x:String x:Key="Text.PruneRemote.Target" xml:space="preserve">Ziel:</x:String> <x:String x:Key="Text.PruneRemote.Target" xml:space="preserve">Ziel:</x:String>
@ -480,7 +481,7 @@
<x:String x:Key="Text.Repository.Refresh" xml:space="preserve">Aktualisiern</x:String> <x:String x:Key="Text.Repository.Refresh" xml:space="preserve">Aktualisiern</x:String>
<x:String x:Key="Text.Repository.Remotes" xml:space="preserve">REMOTES</x:String> <x:String x:Key="Text.Repository.Remotes" xml:space="preserve">REMOTES</x:String>
<x:String x:Key="Text.Repository.Remotes.Add" xml:space="preserve">REMOTE HINZUFÜGEN</x:String> <x:String x:Key="Text.Repository.Remotes.Add" xml:space="preserve">REMOTE HINZUFÜGEN</x:String>
<x:String x:Key="Text.Repository.Resolve" xml:space="preserve">LÖSEN</x:String> <x:String x:Key="Text.Repository.Resolve" xml:space="preserve">KONFLIKTE BEHEBEN</x:String>
<x:String x:Key="Text.Repository.Search" xml:space="preserve">Commit suchen</x:String> <x:String x:Key="Text.Repository.Search" xml:space="preserve">Commit suchen</x:String>
<x:String x:Key="Text.Repository.Search.By" xml:space="preserve">Suche über</x:String> <x:String x:Key="Text.Repository.Search.By" xml:space="preserve">Suche über</x:String>
<x:String x:Key="Text.Repository.Search.ByFile" xml:space="preserve">Dateiname</x:String> <x:String x:Key="Text.Repository.Search.ByFile" xml:space="preserve">Dateiname</x:String>
@ -488,6 +489,7 @@
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String> <x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
<x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">Autor &amp; Committer</x:String> <x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">Autor &amp; Committer</x:String>
<x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">Suche Branches &amp; Tags</x:String> <x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">Suche Branches &amp; Tags</x:String>
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Zeige Tags als Baum</x:String>
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Statistiken</x:String> <x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Statistiken</x:String>
<x:String x:Key="Text.Repository.Submodules" xml:space="preserve">SUBMODULE</x:String> <x:String x:Key="Text.Repository.Submodules" xml:space="preserve">SUBMODULE</x:String>
<x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">SUBMODUL HINZUFÜGEN</x:String> <x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">SUBMODUL HINZUFÜGEN</x:String>
@ -585,16 +587,13 @@
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignoriere nur diese Datei</x:String> <x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignoriere nur diese Datei</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String> <x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">Auto-Stage</x:String> <x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">Auto-Stage</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">Weise den Befehl an automatisch Dateien zu stagen die verändert und modifiziert wurden, aber für Git unbekannte Dateien sind davon unberührt.</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">Du kannst diese Datei jetzt stagen.</x:String> <x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">Du kannst diese Datei jetzt stagen.</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String> <x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String> <x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">STRG + Enter</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">STRG + Enter</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">KONFLIKTE ERKANNT</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">KONFLIKTE ERKANNT</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">DATEI KONFLIKTE GELÖST</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">DATEI KONFLIKTE GELÖST</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">LETZTE COMMIT-NACHRICHTEN</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">NICHT-VERFOLGTE DATEIEN INKLUDIEREN</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">NICHT-VERFOLGTE DATEIEN INKLUDIEREN</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">NACHRICHTEN HISTORIE</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">KEINE BISHERIGEN COMMIT-NACHRICHTEN</x:String> <x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">KEINE BISHERIGEN COMMIT-NACHRICHTEN</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">GESTAGED</x:String> <x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">GESTAGED</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">UNSTAGEN</x:String> <x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">UNSTAGEN</x:String>

View file

@ -64,6 +64,8 @@
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Branch Compare</x:String> <x:String x:Key="Text.BranchCompare" xml:space="preserve">Branch Compare</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">Bytes</x:String> <x:String x:Key="Text.Bytes" xml:space="preserve">Bytes</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">CANCEL</x:String> <x:String x:Key="Text.Cancel" xml:space="preserve">CANCEL</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">Reset to This Revision</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">Reset to Parent Revision</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">CHANGE DISPLAY MODE</x:String> <x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">CHANGE DISPLAY MODE</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Show as File and Dir List</x:String> <x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Show as File and Dir List</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Show as Path List</x:String> <x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Show as Path List</x:String>
@ -122,6 +124,9 @@
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Enter commit subject</x:String> <x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Enter commit subject</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Description</x:String> <x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Description</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">Repository Configure</x:String> <x:String x:Key="Text.Configure" xml:space="preserve">Repository Configure</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate" xml:space="preserve">COMMIT TEMPLATE</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate.Name" xml:space="preserve">Template Name:</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate.Content" xml:space="preserve">Template Content:</x:String>
<x:String x:Key="Text.Configure.Email" xml:space="preserve">Email Address</x:String> <x:String x:Key="Text.Configure.Email" xml:space="preserve">Email Address</x:String>
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">Email address</x:String> <x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">Email address</x:String>
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT</x:String> <x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT</x:String>
@ -163,6 +168,7 @@
<x:String x:Key="Text.CreateTag.Type" xml:space="preserve">Kind:</x:String> <x:String x:Key="Text.CreateTag.Type" xml:space="preserve">Kind:</x:String>
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">annotated</x:String> <x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">annotated</x:String>
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">lightweight</x:String> <x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">lightweight</x:String>
<x:String x:Key="Text.CtrlClickTip" xml:space="preserve">Hold Ctrl to start directly</x:String>
<x:String x:Key="Text.Cut" xml:space="preserve">Cut</x:String> <x:String x:Key="Text.Cut" xml:space="preserve">Cut</x:String>
<x:String x:Key="Text.DeleteBranch" xml:space="preserve">Delete Branch</x:String> <x:String x:Key="Text.DeleteBranch" xml:space="preserve">Delete Branch</x:String>
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch:</x:String> <x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch:</x:String>
@ -194,13 +200,13 @@
<x:String x:Key="Text.Diff.Submodule.New" xml:space="preserve">NEW</x:String> <x:String x:Key="Text.Diff.Submodule.New" xml:space="preserve">NEW</x:String>
<x:String x:Key="Text.Diff.SyntaxHighlight" xml:space="preserve">Syntax Highlighting</x:String> <x:String x:Key="Text.Diff.SyntaxHighlight" xml:space="preserve">Syntax Highlighting</x:String>
<x:String x:Key="Text.Diff.ToggleWordWrap" xml:space="preserve">Line Word Wrap</x:String> <x:String x:Key="Text.Diff.ToggleWordWrap" xml:space="preserve">Line Word Wrap</x:String>
<x:String x:Key="Text.Diff.UseMerger" xml:space="preserve">Open In Merge Tool</x:String> <x:String x:Key="Text.Diff.UseMerger" xml:space="preserve">Open in Merge Tool</x:String>
<x:String x:Key="Text.Diff.VisualLines.Decr" xml:space="preserve">Decrease Number of Visible Lines</x:String> <x:String x:Key="Text.Diff.VisualLines.Decr" xml:space="preserve">Decrease Number of Visible Lines</x:String>
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">Increase Number of Visible Lines</x:String> <x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">Increase Number of Visible Lines</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">SELECT FILE TO VIEW CHANGES</x:String> <x:String x:Key="Text.Diff.Welcome" xml:space="preserve">SELECT FILE TO VIEW CHANGES</x:String>
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">Show hidden symbols</x:String> <x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">Show hidden symbols</x:String>
<x:String x:Key="Text.Diff.SwapCommits" xml:space="preserve">Swap</x:String> <x:String x:Key="Text.Diff.SwapCommits" xml:space="preserve">Swap</x:String>
<x:String x:Key="Text.DiffWithMerger" xml:space="preserve">Open In Merge Tool</x:String> <x:String x:Key="Text.DiffWithMerger" xml:space="preserve">Open in Merge Tool</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">Discard Changes</x:String> <x:String x:Key="Text.Discard" xml:space="preserve">Discard Changes</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">All local changes in working copy.</x:String> <x:String x:Key="Text.Discard.All" xml:space="preserve">All local changes in working copy.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Changes:</x:String> <x:String x:Key="Text.Discard.Changes" xml:space="preserve">Changes:</x:String>
@ -344,6 +350,7 @@
<x:String x:Key="Text.Name" xml:space="preserve">Name:</x:String> <x:String x:Key="Text.Name" xml:space="preserve">Name:</x:String>
<x:String x:Key="Text.NotConfigured" xml:space="preserve">Git has NOT been configured. Please to go [Preference] and configure it first.</x:String> <x:String x:Key="Text.NotConfigured" xml:space="preserve">Git has NOT been configured. Please to go [Preference] and configure it first.</x:String>
<x:String x:Key="Text.Notice" xml:space="preserve">NOTICE</x:String> <x:String x:Key="Text.Notice" xml:space="preserve">NOTICE</x:String>
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">Open App Data Dir</x:String>
<x:String x:Key="Text.OpenFolder" xml:space="preserve">SELECT FOLDER</x:String> <x:String x:Key="Text.OpenFolder" xml:space="preserve">SELECT FOLDER</x:String>
<x:String x:Key="Text.OpenWith" xml:space="preserve">Open With...</x:String> <x:String x:Key="Text.OpenWith" xml:space="preserve">Open With...</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Optional.</x:String> <x:String x:Key="Text.Optional" xml:space="preserve">Optional.</x:String>
@ -373,7 +380,6 @@
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Theme</x:String> <x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Theme</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Theme Overrides</x:String> <x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Theme Overrides</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">GENERAL</x:String> <x:String x:Key="Text.Preference.General" xml:space="preserve">GENERAL</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">Avatar Server</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Check for updates on startup</x:String> <x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Check for updates on startup</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Language</x:String> <x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Language</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">History Commits</x:String> <x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">History Commits</x:String>
@ -583,17 +589,16 @@
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignore this file only</x:String> <x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignore this file only</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String> <x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">Auto-Stage</x:String> <x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">Auto-Stage</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">You can stage this file now.</x:String> <x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">You can stage this file now.</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String> <x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String> <x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String>
<x:String x:Key="Text.WorkingCopy.CommitMessageHelper" xml:space="preserve">Template/Histories</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLICTS DETECTED</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLICTS DETECTED</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">FILE CONFLICTS ARE RESOLVED</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">FILE CONFLICTS ARE RESOLVED</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">RECENT INPUT MESSAGES</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUDE UNTRACKED FILES</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUDE UNTRACKED FILES</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">MESSAGE HISTORIES</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NO RECENT INPUT MESSAGES</x:String> <x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NO RECENT INPUT MESSAGES</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">NO COMMIT TEMPLATES</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">STAGED</x:String> <x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">STAGED</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">UNSTAGE</x:String> <x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">UNSTAGE</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">UNSTAGE ALL</x:String> <x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">UNSTAGE ALL</x:String>
@ -601,6 +606,7 @@
<x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">STAGE</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">STAGE</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">STAGE ALL</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">STAGE ALL</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">VIEW ASSUME UNCHANGED</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">VIEW ASSUME UNCHANGED</x:String>
<x:String x:Key="Text.WorkingCopy.UseCommitTemplate" xml:space="preserve">Template: ${0}$</x:String>
<x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">Right-click the selected file(s), and make your choice to resolve conflicts.</x:String> <x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">Right-click the selected file(s), and make your choice to resolve conflicts.</x:String>
<x:String x:Key="Text.Worktree" xml:space="preserve">WORKTREE</x:String> <x:String x:Key="Text.Worktree" xml:space="preserve">WORKTREE</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Copy Path</x:String> <x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Copy Path</x:String>

View file

@ -67,19 +67,21 @@
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Comparar Branch</x:String> <x:String x:Key="Text.BranchCompare" xml:space="preserve">Comparar Branch</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">Bytes</x:String> <x:String x:Key="Text.Bytes" xml:space="preserve">Bytes</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">CANCELAR</x:String> <x:String x:Key="Text.Cancel" xml:space="preserve">CANCELAR</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">Resetar para Esta Revisão</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">Resetar to Revisão Pai</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">ALTERAR MODO DE EXIBIÇÃO</x:String> <x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">ALTERAR MODO DE EXIBIÇÃO</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Mostrar como Lista de Arquivos e Diretórios</x:String> <x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Mostrar como Grade</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Mostrar como Lista de Caminhos</x:String> <x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Mostrar como Lista de Caminhos</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Mostrar como Árvore de Sistema de Arquivos</x:String> <x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Mostrar como Árvore de Arquivos do Sistema</x:String>
<x:String x:Key="Text.Checkout" xml:space="preserve">Checar Branch</x:String> <x:String x:Key="Text.Checkout" xml:space="preserve">Checkout Branch</x:String>
<x:String x:Key="Text.Checkout.Commit" xml:space="preserve">Checar Commit</x:String> <x:String x:Key="Text.Checkout.Commit" xml:space="preserve">Checkout Commit</x:String>
<x:String x:Key="Text.Checkout.Commit.Warning" xml:space="preserve">Aviso: Ao fazer o checkout de um commit, seu Head ficará desanexado</x:String> <x:String x:Key="Text.Checkout.Commit.Warning" xml:space="preserve">Aviso: Ao fazer o checkout de um commit, seu Head ficará desanexado</x:String>
<x:String x:Key="Text.Checkout.Commit.Target" xml:space="preserve">Commit:</x:String> <x:String x:Key="Text.Checkout.Commit.Target" xml:space="preserve">Commit:</x:String>
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branch:</x:String> <x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branch:</x:String>
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Alterações Locais:</x:String> <x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Alterações Locais:</x:String>
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Descartar</x:String> <x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Descartar</x:String>
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Não Fazer Nada</x:String> <x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Nada</x:String>
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Guardar &amp; Reaplicar</x:String> <x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stash &amp; Reaplicar</x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry-Pick Este Commit</x:String> <x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry-Pick Este Commit</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">Commit:</x:String> <x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">Commit:</x:String>
<x:String x:Key="Text.CherryPick.CommitChanges" xml:space="preserve">Commitar todas as alterações</x:String> <x:String x:Key="Text.CherryPick.CommitChanges" xml:space="preserve">Commitar todas as alterações</x:String>
@ -100,7 +102,8 @@
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">Comparar com HEAD</x:String> <x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">Comparar com HEAD</x:String>
<x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">Comparar com Worktree</x:String> <x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">Comparar com Worktree</x:String>
<x:String x:Key="Text.CommitCM.CopyInfo" xml:space="preserve">Copiar Informações</x:String> <x:String x:Key="Text.CommitCM.CopyInfo" xml:space="preserve">Copiar Informações</x:String>
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">Copiar SHA</x:String><x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Rebase Interativo ${0}$ até Aqui</x:String> <x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">Copiar SHA</x:String>
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Rebase Interativo ${0}$ até Aqui</x:String>
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">Rebase ${0}$ até Aqui</x:String> <x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">Rebase ${0}$ até Aqui</x:String>
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Resetar ${0}$ até Aqui</x:String> <x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Resetar ${0}$ até Aqui</x:String>
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">Reverter Commit</x:String> <x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">Reverter Commit</x:String>
@ -124,8 +127,20 @@
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Insira o assunto do commit</x:String> <x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Insira o assunto do commit</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Descrição</x:String> <x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Descrição</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">Configurar Repositório</x:String> <x:String x:Key="Text.Configure" xml:space="preserve">Configurar Repositório</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate" xml:space="preserve">TEMPLATE DE COMMIT</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate.Name" xml:space="preserve">Nome do Template:</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate.Content" xml:space="preserve">Conteúdo do Template:</x:String>
<x:String x:Key="Text.Configure.Email" xml:space="preserve">Endereço de Email</x:String> <x:String x:Key="Text.Configure.Email" xml:space="preserve">Endereço de Email</x:String>
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">Endereço de email</x:String> <x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">Endereço de email</x:String>
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT</x:String>
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">RASTREADOR DE PROBLEMAS</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGithub" xml:space="preserve">Adicionar Regra de Exemplo do Github</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleJira" xml:space="preserve">Adicionar Regra de Exemplo do Jira</x:String>
<x:String x:Key="Text.Configure.IssueTracker.NewRule" xml:space="preserve">Nova Regra</x:String>
<x:String x:Key="Text.Configure.IssueTracker.Regex" xml:space="preserve">Expressão Regex de Issue:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">Nome da Regra:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">URL de Resultado:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">Por favor, use $1, $2 para acessar os valores de grupos do regex.</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">Proxy HTTP</x:String> <x:String x:Key="Text.Configure.Proxy" xml:space="preserve">Proxy HTTP</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">Proxy HTTP usado por este repositório</x:String> <x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">Proxy HTTP usado por este repositório</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">Nome de Usuário</x:String> <x:String x:Key="Text.Configure.User" xml:space="preserve">Nome de Usuário</x:String>
@ -156,6 +171,7 @@
<x:String x:Key="Text.CreateTag.Type" xml:space="preserve">Tipo:</x:String> <x:String x:Key="Text.CreateTag.Type" xml:space="preserve">Tipo:</x:String>
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">anotada</x:String> <x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">anotada</x:String>
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">leve</x:String> <x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">leve</x:String>
<x:String x:Key="Text.CtrlClickTip" xml:space="preserve">Pressione Ctrl para iniciar diretamente</x:String>
<x:String x:Key="Text.Cut" xml:space="preserve">Recortar</x:String> <x:String x:Key="Text.Cut" xml:space="preserve">Recortar</x:String>
<x:String x:Key="Text.DeleteBranch" xml:space="preserve">Excluir Branch</x:String> <x:String x:Key="Text.DeleteBranch" xml:space="preserve">Excluir Branch</x:String>
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch:</x:String> <x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">Branch:</x:String>
@ -337,6 +353,7 @@
<x:String x:Key="Text.Name" xml:space="preserve">Nome:</x:String> <x:String x:Key="Text.Name" xml:space="preserve">Nome:</x:String>
<x:String x:Key="Text.NotConfigured" xml:space="preserve">O Git NÃO foi configurado. Por favor, vá para [Preferências] e configure primeiro.</x:String> <x:String x:Key="Text.NotConfigured" xml:space="preserve">O Git NÃO foi configurado. Por favor, vá para [Preferências] e configure primeiro.</x:String>
<x:String x:Key="Text.Notice" xml:space="preserve">AVISO</x:String> <x:String x:Key="Text.Notice" xml:space="preserve">AVISO</x:String>
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">Abrir Pasta de Dados do Aplicativo</x:String>
<x:String x:Key="Text.OpenFolder" xml:space="preserve">SELECIONAR PASTA</x:String> <x:String x:Key="Text.OpenFolder" xml:space="preserve">SELECIONAR PASTA</x:String>
<x:String x:Key="Text.OpenWith" xml:space="preserve">Abrir Com...</x:String> <x:String x:Key="Text.OpenWith" xml:space="preserve">Abrir Com...</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Opcional.</x:String> <x:String x:Key="Text.Optional" xml:space="preserve">Opcional.</x:String>
@ -362,10 +379,10 @@
<x:String x:Key="Text.Preference.Appearance.DefaultFont" xml:space="preserve">Fonte Padrão</x:String> <x:String x:Key="Text.Preference.Appearance.DefaultFont" xml:space="preserve">Fonte Padrão</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Tamanho da Fonte Padrão</x:String> <x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Tamanho da Fonte Padrão</x:String>
<x:String x:Key="Text.Preference.Appearance.MonospaceFont" xml:space="preserve">Fonte Monoespaçada</x:String> <x:String x:Key="Text.Preference.Appearance.MonospaceFont" xml:space="preserve">Fonte Monoespaçada</x:String>
<x:String x:Key="Text.Preference.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">Usar apenas fonte monoespaçada no editor de texto</x:String>
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Tema</x:String> <x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Tema</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Sobrescrever Tema</x:String> <x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Sobrescrever Tema</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">GERAL</x:String> <x:String x:Key="Text.Preference.General" xml:space="preserve">GERAL</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">Servidor de Avatar</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Verificar atualizações na inicialização</x:String> <x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Verificar atualizações na inicialização</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Idioma</x:String> <x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Idioma</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">Commits do Histórico</x:String> <x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">Commits do Histórico</x:String>
@ -477,6 +494,7 @@
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String> <x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
<x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">Autor &amp; Committer</x:String> <x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">Autor &amp; Committer</x:String>
<x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">Pesquisar Branches &amp; Tags</x:String> <x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">Pesquisar Branches &amp; Tags</x:String>
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Mostrar Tags como Árvore</x:String>
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Estatísticas</x:String> <x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Estatísticas</x:String>
<x:String x:Key="Text.Repository.Submodules" xml:space="preserve">SUBMÓDULOS</x:String> <x:String x:Key="Text.Repository.Submodules" xml:space="preserve">SUBMÓDULOS</x:String>
<x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">ADICIONAR SUBMÓDULO</x:String> <x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">ADICIONAR SUBMÓDULO</x:String>
@ -554,6 +572,7 @@
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Submódulo:</x:String> <x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Submódulo:</x:String>
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Usar opção --remote</x:String> <x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Usar opção --remote</x:String>
<x:String x:Key="Text.Warn" xml:space="preserve">Aviso</x:String> <x:String x:Key="Text.Warn" xml:space="preserve">Aviso</x:String>
<x:String x:Key="Text.Welcome" xml:space="preserve">Página de Boas-vindas</x:String>
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Criar Grupo Raíz</x:String> <x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Criar Grupo Raíz</x:String>
<x:String x:Key="Text.Welcome.AddSubFolder" xml:space="preserve">Criar Subgrupo</x:String> <x:String x:Key="Text.Welcome.AddSubFolder" xml:space="preserve">Criar Subgrupo</x:String>
<x:String x:Key="Text.Welcome.Clone" xml:space="preserve">Clonar Repositório</x:String> <x:String x:Key="Text.Welcome.Clone" xml:space="preserve">Clonar Repositório</x:String>
@ -573,17 +592,16 @@
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignorar apenas este arquivo</x:String> <x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignorar apenas este arquivo</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Corrigir</x:String> <x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Corrigir</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">Auto-Stage</x:String> <x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">Auto-Stage</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">Informe ao comando para automaticamente stagear arquivos que foram modificados e excluídos, mas novos arquivos que você não informou ao Git não serão afetados.</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">Você pode stagear este arquivo agora.</x:String> <x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">Você pode stagear este arquivo agora.</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String> <x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String> <x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String>
<x:String x:Key="Text.WorkingCopy.CommitMessageHelper" xml:space="preserve">Template/Histories</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLITOS DETECTADOS</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLITOS DETECTADOS</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">CONFLITOS DE ARQUIVOS RESOLVIDOS</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">CONFLITOS DE ARQUIVOS RESOLVIDOS</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">MENSAGENS RECENTES DE ENTRADA</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUIR ARQUIVOS NÃO RASTREADOS</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUIR ARQUIVOS NÃO RASTREADOS</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">HISTÓRICO DE MENSAGENS</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NENHUMA MENSAGEM DE ENTRADA RECENTE</x:String> <x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NENHUMA MENSAGEM DE ENTRADA RECENTE</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">NENHUM TEMPLATE DE COMMIT</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">STAGED</x:String> <x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">STAGED</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">DESSTAGEAR</x:String> <x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">DESSTAGEAR</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">DESSTAGEAR TODOS</x:String> <x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">DESSTAGEAR TODOS</x:String>
@ -591,6 +609,7 @@
<x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">STAGEAR</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">STAGEAR</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">STAGEAR TODOS</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">STAGEAR TODOS</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">VER SUPOR NÃO ALTERADO</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">VER SUPOR NÃO ALTERADO</x:String>
<x:String x:Key="Text.WorkingCopy.UseCommitTemplate" xml:space="preserve">Template: ${0}$</x:String>
<x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">Clique com o botão direito nos arquivos selecionados e escolha como resolver conflitos.</x:String> <x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">Clique com o botão direito nos arquivos selecionados e escolha como resolver conflitos.</x:String>
<x:String x:Key="Text.Worktree" xml:space="preserve">WORKTREE</x:String> <x:String x:Key="Text.Worktree" xml:space="preserve">WORKTREE</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Copiar Caminho</x:String> <x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Copiar Caminho</x:String>

View file

@ -67,6 +67,8 @@
<x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比较</x:String> <x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比较</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">字节</x:String> <x:String x:Key="Text.Bytes" xml:space="preserve">字节</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String> <x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">重置文件到该版本</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">重置文件到上一版本</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切换变更显示模式</x:String> <x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切换变更显示模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">文件名+路径列表模式</x:String> <x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">文件名+路径列表模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路径列表模式</x:String> <x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路径列表模式</x:String>
@ -125,6 +127,9 @@
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">填写提交信息主题</x:String> <x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">填写提交信息主题</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">详细描述</x:String> <x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">详细描述</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">仓库配置</x:String> <x:String x:Key="Text.Configure" xml:space="preserve">仓库配置</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate" xml:space="preserve">提交信息模板</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate.Name" xml:space="preserve">模板名 </x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate.Content" xml:space="preserve">模板内容 </x:String>
<x:String x:Key="Text.Configure.Email" xml:space="preserve">电子邮箱</x:String> <x:String x:Key="Text.Configure.Email" xml:space="preserve">电子邮箱</x:String>
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">邮箱地址</x:String> <x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">邮箱地址</x:String>
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT配置</x:String> <x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT配置</x:String>
@ -166,6 +171,7 @@
<x:String x:Key="Text.CreateTag.Type" xml:space="preserve">类型 </x:String> <x:String x:Key="Text.CreateTag.Type" xml:space="preserve">类型 </x:String>
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">附注标签</x:String> <x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">附注标签</x:String>
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">轻量标签</x:String> <x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">轻量标签</x:String>
<x:String x:Key="Text.CtrlClickTip" xml:space="preserve">按住Ctrl键点击将以默认参数运行</x:String>
<x:String x:Key="Text.Cut" xml:space="preserve">剪切</x:String> <x:String x:Key="Text.Cut" xml:space="preserve">剪切</x:String>
<x:String x:Key="Text.DeleteBranch" xml:space="preserve">删除分支确认</x:String> <x:String x:Key="Text.DeleteBranch" xml:space="preserve">删除分支确认</x:String>
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">分支名 </x:String> <x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">分支名 </x:String>
@ -347,6 +353,7 @@
<x:String x:Key="Text.Name" xml:space="preserve">名称 </x:String> <x:String x:Key="Text.Name" xml:space="preserve">名称 </x:String>
<x:String x:Key="Text.NotConfigured" xml:space="preserve">GIT尚未配置。请打开【偏好设置】配置GIT路径。</x:String> <x:String x:Key="Text.NotConfigured" xml:space="preserve">GIT尚未配置。请打开【偏好设置】配置GIT路径。</x:String>
<x:String x:Key="Text.Notice" xml:space="preserve">系统提示</x:String> <x:String x:Key="Text.Notice" xml:space="preserve">系统提示</x:String>
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">浏览应用数据目录</x:String>
<x:String x:Key="Text.OpenFolder" xml:space="preserve">选择文件夹</x:String> <x:String x:Key="Text.OpenFolder" xml:space="preserve">选择文件夹</x:String>
<x:String x:Key="Text.OpenWith" xml:space="preserve">打开文件...</x:String> <x:String x:Key="Text.OpenWith" xml:space="preserve">打开文件...</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">选填。</x:String> <x:String x:Key="Text.Optional" xml:space="preserve">选填。</x:String>
@ -376,7 +383,6 @@
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">主题</x:String> <x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">主题</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">主题自定义</x:String> <x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">主题自定义</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">通用配置</x:String> <x:String x:Key="Text.Preference.General" xml:space="preserve">通用配置</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">头像服务</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">启动时检测软件更新</x:String> <x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">启动时检测软件更新</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">显示语言</x:String> <x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">显示语言</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">最大历史提交数</x:String> <x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">最大历史提交数</x:String>
@ -584,18 +590,17 @@
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同目录下所有文件</x:String> <x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同目录下所有文件</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本文件</x:String> <x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本文件</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修补(--amend)</x:String> <x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修补(--amend)</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">自动暂存(--all)</x:String> <x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">自动暂存</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">提交前自动将修改过和删除的文件加入暂存区,但新增文件需要手动添加。</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">现在您已可将其加入暂存区中</x:String> <x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">现在您已可将其加入暂存区中</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String> <x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交并推送</x:String> <x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交并推送</x:String>
<x:String x:Key="Text.WorkingCopy.CommitMessageHelper" xml:space="preserve">历史输入/模板</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">检测到冲突</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">检测到冲突</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">文件冲突已解决</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">文件冲突已解决</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">最近输入的提交信息</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">显示未跟踪文件</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">显示未跟踪文件</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">历史提交信息</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">没有提交信息记录</x:String> <x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">没有提交信息记录</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">没有可应用的提交信息模板</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">已暂存</x:String> <x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">已暂存</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">从暂存区移除选中</x:String> <x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">从暂存区移除选中</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">从暂存区移除所有</x:String> <x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">从暂存区移除所有</x:String>
@ -603,6 +608,7 @@
<x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">暂存选中</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">暂存选中</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">暂存所有</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">暂存所有</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">查看忽略变更文件</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">查看忽略变更文件</x:String>
<x:String x:Key="Text.WorkingCopy.UseCommitTemplate" xml:space="preserve">模板:${0}$</x:String>
<x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">请选中冲突文件,打开右键菜单,选择合适的解决方式</x:String> <x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">请选中冲突文件,打开右键菜单,选择合适的解决方式</x:String>
<x:String x:Key="Text.Worktree" xml:space="preserve">本地工作树</x:String> <x:String x:Key="Text.Worktree" xml:space="preserve">本地工作树</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">复制工作树路径</x:String> <x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">复制工作树路径</x:String>

View file

@ -67,6 +67,8 @@
<x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比較</x:String> <x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比較</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">位元組</x:String> <x:String x:Key="Text.Bytes" xml:space="preserve">位元組</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String> <x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">重置檔案到該版本</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">重置檔案到上一版本</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切換變更顯示模式</x:String> <x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切換變更顯示模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">檔名+路徑列表模式</x:String> <x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">檔名+路徑列表模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路徑列表模式</x:String> <x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路徑列表模式</x:String>
@ -125,6 +127,9 @@
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">填寫提交信息主題</x:String> <x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">填寫提交信息主題</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">詳細描述</x:String> <x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">詳細描述</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">倉庫配置</x:String> <x:String x:Key="Text.Configure" xml:space="preserve">倉庫配置</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate" xml:space="preserve">提交資訊範本</x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate.Name" xml:space="preserve">範本名稱 </x:String>
<x:String x:Key="Text.Configure.CommitMessageTemplate.Content" xml:space="preserve">範本內容 </x:String>
<x:String x:Key="Text.Configure.Email" xml:space="preserve">電子郵箱</x:String> <x:String x:Key="Text.Configure.Email" xml:space="preserve">電子郵箱</x:String>
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">郵箱地址</x:String> <x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">郵箱地址</x:String>
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT配置</x:String> <x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT配置</x:String>
@ -166,6 +171,7 @@
<x:String x:Key="Text.CreateTag.Type" xml:space="preserve">型別 </x:String> <x:String x:Key="Text.CreateTag.Type" xml:space="preserve">型別 </x:String>
<x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">附註標籤</x:String> <x:String x:Key="Text.CreateTag.Type.Annotated" xml:space="preserve">附註標籤</x:String>
<x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">輕量標籤</x:String> <x:String x:Key="Text.CreateTag.Type.Lightweight" xml:space="preserve">輕量標籤</x:String>
<x:String x:Key="Text.CtrlClickTip" xml:space="preserve">按住Ctrl鍵點擊將以預設參數運行</x:String>
<x:String x:Key="Text.Cut" xml:space="preserve">剪下</x:String> <x:String x:Key="Text.Cut" xml:space="preserve">剪下</x:String>
<x:String x:Key="Text.DeleteBranch" xml:space="preserve">刪除分支確認</x:String> <x:String x:Key="Text.DeleteBranch" xml:space="preserve">刪除分支確認</x:String>
<x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">分支名 </x:String> <x:String x:Key="Text.DeleteBranch.Branch" xml:space="preserve">分支名 </x:String>
@ -347,6 +353,7 @@
<x:String x:Key="Text.Name" xml:space="preserve">名稱 </x:String> <x:String x:Key="Text.Name" xml:space="preserve">名稱 </x:String>
<x:String x:Key="Text.NotConfigured" xml:space="preserve">GIT尚未配置。請開啟【偏好設定】配置GIT路徑。</x:String> <x:String x:Key="Text.NotConfigured" xml:space="preserve">GIT尚未配置。請開啟【偏好設定】配置GIT路徑。</x:String>
<x:String x:Key="Text.Notice" xml:space="preserve">系統提示</x:String> <x:String x:Key="Text.Notice" xml:space="preserve">系統提示</x:String>
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">瀏覽程式資料目錄</x:String>
<x:String x:Key="Text.OpenFolder" xml:space="preserve">選擇資料夾</x:String> <x:String x:Key="Text.OpenFolder" xml:space="preserve">選擇資料夾</x:String>
<x:String x:Key="Text.OpenWith" xml:space="preserve">開啟檔案...</x:String> <x:String x:Key="Text.OpenWith" xml:space="preserve">開啟檔案...</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">選填。</x:String> <x:String x:Key="Text.Optional" xml:space="preserve">選填。</x:String>
@ -376,7 +383,6 @@
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">主題</x:String> <x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">主題</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">主題自訂</x:String> <x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">主題自訂</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">通用配置</x:String> <x:String x:Key="Text.Preference.General" xml:space="preserve">通用配置</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">頭像服務</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">啟動時檢測軟體更新</x:String> <x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">啟動時檢測軟體更新</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">顯示語言</x:String> <x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">顯示語言</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">最大歷史提交數</x:String> <x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">最大歷史提交數</x:String>
@ -584,18 +590,17 @@
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同路徑下所有檔案</x:String> <x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同路徑下所有檔案</x:String>
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本檔案</x:String> <x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本檔案</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修補(--amend)</x:String> <x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修補(--amend)</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">自動暫存(--all)</x:String> <x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">自動暫存</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">提交前自動將修改過和刪除的檔案加入暫存區,但新增檔案需要手動添加。</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">現在您已可將其加入暫存區中</x:String> <x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">現在您已可將其加入暫存區中</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String> <x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交併推送</x:String> <x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交併推送</x:String>
<x:String x:Key="Text.WorkingCopy.CommitMessageHelper" xml:space="preserve">歷史輸入/範本</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">檢測到衝突</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">檢測到衝突</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">檔案衝突已解決</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">檔案衝突已解決</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">最近輸入的提交資訊</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">顯示未跟蹤檔案</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">顯示未跟蹤檔案</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">歷史提交資訊</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">沒有提交資訊記錄</x:String> <x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">沒有提交資訊記錄</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">沒有可應用的提交資訊範本</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">已暫存</x:String> <x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">已暫存</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">從暫存區移除選中</x:String> <x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">從暫存區移除選中</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">從暫存區移除所有</x:String> <x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">從暫存區移除所有</x:String>
@ -603,6 +608,7 @@
<x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">暫存選中</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">暫存選中</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">暫存所有</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">暫存所有</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">檢視忽略變更檔案</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">檢視忽略變更檔案</x:String>
<x:String x:Key="Text.WorkingCopy.UseCommitTemplate" xml:space="preserve">範本:${0}$</x:String>
<x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">請選中衝突檔案,開啟右鍵選單,選擇合適的解決方式</x:String> <x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">請選中衝突檔案,開啟右鍵選單,選擇合適的解決方式</x:String>
<x:String x:Key="Text.Worktree" xml:space="preserve">本地工作樹</x:String> <x:String x:Key="Text.Worktree" xml:space="preserve">本地工作樹</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">拷贝工作樹路徑</x:String> <x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">拷贝工作樹路徑</x:String>

View file

@ -251,8 +251,12 @@
<Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}"/> <Setter Property="Foreground" Value="{DynamicResource Brush.FG1}"/>
<Setter Property="FontFamily" Value="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFont}"/>
<Setter Property="FontSize" Value="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize}"/> <Setter Property="FontSize" Value="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize}"/>
</Style> </Style>
<Style Selector="TextBlock.small">
<Setter Property="FontSize" Value="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"/>
</Style>
<Style Selector="TextBlock.bold"> <Style Selector="TextBlock.bold">
<Setter Property="FontWeight" Value="Bold"/> <Setter Property="FontWeight" Value="Bold"/>
</Style> </Style>
@ -277,12 +281,9 @@
<Setter Property="FontWeight" Value="Bold"/> <Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalAlignment" Value="Right"/> <Setter Property="HorizontalAlignment" Value="Right"/>
</Style> </Style>
<Style Selector="TextBlock.issue_link">
<Style Selector="Run.issue_link">
<Setter Property="Foreground" Value="{DynamicResource Brush.Link}"/> <Setter Property="Foreground" Value="{DynamicResource Brush.Link}"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
<Style Selector="TextBlock.issue_link:pointerover">
<Setter Property="TextDecorations" Value="Underline"/>
</Style> </Style>
<Style Selector="SelectableTextBlock"> <Style Selector="SelectableTextBlock">
@ -455,6 +456,21 @@
<Setter Property="Fill" Value="{DynamicResource Brush.FG2}"/> <Setter Property="Fill" Value="{DynamicResource Brush.FG2}"/>
</Style> </Style>
<Style Selector="Button.no_border">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style Selector="Button.no_border /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="Button.no_border:pointerover Path">
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}"/>
</Style>
<Style Selector="Button.no_border:pressed">
<Setter Property="RenderTransform" Value="scale(1.0)"/>
</Style>
<Style Selector="Button.flat"> <Style Selector="Button.flat">
<Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border2}"/> <Setter Property="BorderBrush" Value="{DynamicResource Brush.Border2}"/>
@ -753,11 +769,10 @@
</Style> </Style>
<Style Selector="MenuItem"> <Style Selector="MenuItem">
<Style.Resources>
<ControlTheme x:Key="{x:Type MenuItem}" TargetType="MenuItem">
<Setter Property="Height" Value="28"/> <Setter Property="Height" Value="28"/>
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}" /> <Setter Property="Foreground" Value="{DynamicResource Brush.FG1}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Panel> <Panel>
@ -769,27 +784,28 @@
CornerRadius="3"> CornerRadius="3">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIcon" /> <ColumnDefinition Width="28" SharedSizeGroup="MenuItemIcon" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGT" /> <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGT" />
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemChevron" /> <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemChevron" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Border Grid.Column="0" Width="28"> <ContentControl Grid.Column="0"
<ContentControl x:Name="PART_IconPresenter" x:Name="PART_IconPresenter"
Content="{TemplateBinding Icon}" Content="{TemplateBinding Icon}"
IsVisible="False" IsVisible="False"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</Border>
<ContentPresenter Grid.Column="1" <ContentPresenter Grid.Column="1"
Name="PART_HeaderPresenter" x:Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}" Content="{TemplateBinding Header}"
Foreground="{TemplateBinding Foreground}"
ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplate="{TemplateBinding HeaderTemplate}"
VerticalAlignment="Center" VerticalAlignment="Center"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
RecognizesAccessKey="False"/> RecognizesAccessKey="False"/>
<TextBlock x:Name="PART_InputGestureText" <TextBlock x:Name="PART_InputGestureText"
Grid.Column="2" Grid.Column="2"
Classes="CaptionTextBlockStyle" Classes="CaptionTextBlockStyle"
@ -799,6 +815,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForeground}" Foreground="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForeground}"
FontSize="11"/> FontSize="11"/>
<Path Name="PART_ChevronPath" <Path Name="PART_ChevronPath"
Width="6" Width="6"
Data="M573 512 215 881c-20 20-20 51 0 61l61 61c20 20 51 20 61 0l461-461c10-10 10-20 10-31s0-20-10-31L338 20C317 0 287 0 276 20L215 82c-20 20-20 51 0 61L573 512z" Data="M573 512 215 881c-20 20-20 51 0 61l61 61c20 20 51 20 61 0l461-461c10-10 10-20 10-31s0-20-10-31L338 20C317 0 287 0 276 20L215 82c-20 20-20 51 0 61L573 512z"
@ -867,18 +884,8 @@
</Style> </Style>
<Style Selector="^:disabled"> <Style Selector="^:disabled">
<Style Selector="^ /template/ Border#PART_LayoutRoot"> <Setter Property="Opacity" Value="0.65"/>
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundDisabled}" /> <Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_HeaderPresenter">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForegroundDisabled}" />
</Style>
<Style Selector="^ /template/ TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundDisabled}" />
</Style>
<Style Selector="^ /template/ Path#PART_ChevronPath">
<Setter Property="Fill" Value="{DynamicResource MenuFlyoutSubItemChevronDisabled}" />
</Style>
</Style> </Style>
<Style Selector="^:empty /template/ Path#PART_ChevronPath"> <Style Selector="^:empty /template/ Path#PART_ChevronPath">
@ -893,8 +900,6 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
</ControlTheme>
</Style.Resources>
</Style> </Style>
<Style Selector="ComboBox"> <Style Selector="ComboBox">
@ -1324,6 +1329,9 @@
<Style Selector="DataGrid /template/ Rectangle#PART_ColumnHeadersAndRowsSeparator"> <Style Selector="DataGrid /template/ Rectangle#PART_ColumnHeadersAndRowsSeparator">
<Setter Property="IsVisible" Value="False"/> <Setter Property="IsVisible" Value="False"/>
</Style> </Style>
<Style Selector="DataGridCell">
<Setter Property="MinHeight" Value="24"/>
</Style>
<Style Selector="DataGridCell:focus /template/ Grid#FocusVisual"> <Style Selector="DataGridCell:focus /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False"/> <Setter Property="IsVisible" Value="False"/>
</Style> </Style>

View file

@ -24,12 +24,13 @@
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode> <TrimMode>link</TrimMode>
<OptimizationPreference>Size</OptimizationPreference> <OptimizationPreference>Speed</OptimizationPreference>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="App.ico" /> <AvaloniaResource Include="App.ico" />
<AvaloniaResource Include="Resources/Fonts/*" /> <AvaloniaResource Include="Resources/Fonts/*" />
<AvaloniaResource Include="Resources/Grammars/*" />
<AvaloniaResource Include="Resources/Images/*" /> <AvaloniaResource Include="Resources/Images/*" />
<AvaloniaResource Include="Resources/Images/ExternalToolIcons/*" /> <AvaloniaResource Include="Resources/Images/ExternalToolIcons/*" />
<AvaloniaResource Include="Resources/Images/ExternalToolIcons/JetBrains/*" /> <AvaloniaResource Include="Resources/Images/ExternalToolIcons/JetBrains/*" />
@ -37,15 +38,16 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.13" /> <PackageReference Include="Avalonia" Version="11.1.3" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.13" /> <PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.13" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.13" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.3" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.13" Condition="'$(Configuration)' == 'Debug'" /> <PackageReference Include="Avalonia.Diagnostics" Version="11.1.3" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.1.0" /> <PackageReference Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" /> <PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.60" /> <PackageReference Include="TextMateSharp" Version="1.0.62" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.62" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -36,11 +36,6 @@ namespace SourceGit.ViewModels
get => Backend is Models.Branch; get => Backend is Models.Branch;
} }
public string TrackStatus
{
get => Backend is Models.Branch { IsLocal: true } branch ? branch.TrackStatus.ToString() : string.Empty;
}
public FontWeight NameFontWeight public FontWeight NameFontWeight
{ {
get => Backend is Models.Branch { IsCurrent: true } ? FontWeight.Bold : FontWeight.Regular; get => Backend is Models.Branch { IsCurrent: true } ? FontWeight.Bold : FontWeight.Regular;

View file

@ -66,7 +66,7 @@ namespace SourceGit.ViewModels
if (value == null || value.Count != 1) if (value == null || value.Count != 1)
DiffContext = null; DiffContext = null;
else else
DiffContext = new DiffContext(_repo, new Models.DiffOption(_commit, value[0]), _diffContext); DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(_commit, value[0]), _diffContext);
} }
} }
} }
@ -89,15 +89,35 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _viewRevisionFileContent, value); set => SetProperty(ref _viewRevisionFileContent, value);
} }
public AvaloniaList<Models.CommitLink> WebLinks
{
get;
private set;
} = new AvaloniaList<Models.CommitLink>();
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{ {
get => _issueTrackerRules; get => _repo.Settings?.IssueTrackerRules;
} }
public CommitDetail(string repo, AvaloniaList<Models.IssueTrackerRule> issueTrackerRules) public CommitDetail(Repository repo)
{ {
_repo = repo; _repo = repo;
_issueTrackerRules = issueTrackerRules;
foreach (var remote in repo.Remotes)
{
if (remote.TryGetVisitURL(out var url))
{
if (url.StartsWith("https://github.com/", StringComparison.Ordinal))
WebLinks.Add(new Models.CommitLink() { Name = "Github", URLPrefix = $"{url}/commit/" });
else if (url.StartsWith("https://gitlab.com/", StringComparison.Ordinal))
WebLinks.Add(new Models.CommitLink() { Name = "GitLab", URLPrefix = $"{url}/-/commit/" });
else if (url.StartsWith("https://gitee.com/", StringComparison.Ordinal))
WebLinks.Add(new Models.CommitLink() { Name = "Gitee", URLPrefix = $"{url}/commit/" });
else if (url.StartsWith("https://bitbucket.org/", StringComparison.Ordinal))
WebLinks.Add(new Models.CommitLink() { Name = "Bitbucket", URLPrefix = $"{url}/commits/" });
}
}
} }
public void Cleanup() public void Cleanup()
@ -118,8 +138,7 @@ namespace SourceGit.ViewModels
public void NavigateTo(string commitSHA) public void NavigateTo(string commitSHA)
{ {
var repo = App.FindOpenedRepository(_repo); _repo?.NavigateToCommit(commitSHA);
repo?.NavigateToCommit(commitSHA);
} }
public void ClearSearchChangeFilter() public void ClearSearchChangeFilter()
@ -129,7 +148,7 @@ namespace SourceGit.ViewModels
public List<Models.Object> GetRevisionFilesUnderFolder(string parentFolder) public List<Models.Object> GetRevisionFilesUnderFolder(string parentFolder)
{ {
return new Commands.QueryRevisionObjects(_repo, _commit.SHA, parentFolder).Result(); return new Commands.QueryRevisionObjects(_repo.FullPath, _commit.SHA, parentFolder).Result();
} }
public void ViewRevisionFile(Models.Object file) public void ViewRevisionFile(Models.Object file)
@ -145,13 +164,13 @@ namespace SourceGit.ViewModels
case Models.ObjectType.Blob: case Models.ObjectType.Blob:
Task.Run(() => Task.Run(() =>
{ {
var isBinary = new Commands.IsBinary(_repo, _commit.SHA, file.Path).Result(); var isBinary = new Commands.IsBinary(_repo.FullPath, _commit.SHA, file.Path).Result();
if (isBinary) if (isBinary)
{ {
var ext = Path.GetExtension(file.Path); var ext = Path.GetExtension(file.Path);
if (IMG_EXTS.Contains(ext)) if (IMG_EXTS.Contains(ext))
{ {
var stream = Commands.QueryFileContent.Run(_repo, _commit.SHA, file.Path); var stream = Commands.QueryFileContent.Run(_repo.FullPath, _commit.SHA, file.Path);
var bitmap = stream.Length > 0 ? new Bitmap(stream) : null; var bitmap = stream.Length > 0 ? new Bitmap(stream) : null;
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() =>
{ {
@ -160,7 +179,7 @@ namespace SourceGit.ViewModels
} }
else else
{ {
var size = new Commands.QueryFileSize(_repo, file.Path, _commit.SHA).Result(); var size = new Commands.QueryFileSize(_repo.FullPath, file.Path, _commit.SHA).Result();
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() =>
{ {
ViewRevisionFileContent = new Models.RevisionBinaryFile() { Size = size }; ViewRevisionFileContent = new Models.RevisionBinaryFile() { Size = size };
@ -170,7 +189,7 @@ namespace SourceGit.ViewModels
return; return;
} }
var contentStream = Commands.QueryFileContent.Run(_repo, _commit.SHA, file.Path); var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _commit.SHA, file.Path);
var content = new StreamReader(contentStream).ReadToEnd(); var content = new StreamReader(contentStream).ReadToEnd();
var matchLFS = REG_LFS_FORMAT().Match(content); var matchLFS = REG_LFS_FORMAT().Match(content);
if (matchLFS.Success) if (matchLFS.Success)
@ -191,7 +210,7 @@ namespace SourceGit.ViewModels
case Models.ObjectType.Commit: case Models.ObjectType.Commit:
Task.Run(() => Task.Run(() =>
{ {
var submoduleRoot = Path.Combine(_repo, file.Path); var submoduleRoot = Path.Combine(_repo.FullPath, file.Path);
var commit = new Commands.QuerySingleCommit(submoduleRoot, file.SHA).Result(); var commit = new Commands.QuerySingleCommit(submoduleRoot, file.SHA).Result();
if (commit != null) if (commit != null)
{ {
@ -237,10 +256,49 @@ namespace SourceGit.ViewModels
var toolPath = Preference.Instance.ExternalMergeToolPath; var toolPath = Preference.Instance.ExternalMergeToolPath;
var opt = new Models.DiffOption(_commit, change); var opt = new Models.DiffOption(_commit, change);
Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, toolType, toolPath, opt)); Task.Run(() => Commands.MergeTool.OpenForDiff(_repo.FullPath, toolType, toolPath, opt));
ev.Handled = true; ev.Handled = true;
}; };
menu.Items.Add(diffWithMerger); menu.Items.Add(diffWithMerger);
menu.Items.Add(new MenuItem { Header = "-" });
var fullPath = Path.Combine(_repo.FullPath, change.Path);
if (File.Exists(fullPath))
{
var resetToThisRevision = new MenuItem();
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.Click += (_, ev) =>
{
new Commands.Checkout(_repo.FullPath).FileWithRevision(change.Path, $"{_commit.SHA}");
ev.Handled = true;
};
var resetToFirstParent = new MenuItem();
resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToFirstParent.IsEnabled = _commit.Parents.Count > 0 && change.Index != Models.ChangeState.Added && change.Index != Models.ChangeState.Renamed;
resetToFirstParent.Click += (_, ev) =>
{
new Commands.Checkout(_repo.FullPath).FileWithRevision(change.Path, $"{_commit.SHA}~1");
ev.Handled = true;
};
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.Click += (_, ev) =>
{
Native.OS.OpenInFileManager(fullPath, true);
ev.Handled = true;
};
menu.Items.Add(resetToThisRevision);
menu.Items.Add(resetToFirstParent);
menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(explore);
menu.Items.Add(new MenuItem { Header = "-" });
}
if (change.Index != Models.ChangeState.Deleted) if (change.Index != Models.ChangeState.Deleted)
{ {
@ -249,7 +307,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) => history.Click += (_, ev) =>
{ {
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path, _issueTrackerRules) }; var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) };
window.Show(); window.Show();
ev.Handled = true; ev.Handled = true;
}; };
@ -259,26 +317,13 @@ namespace SourceGit.ViewModels
blame.Icon = App.CreateMenuIcon("Icons.Blame"); blame.Icon = App.CreateMenuIcon("Icons.Blame");
blame.Click += (_, ev) => blame.Click += (_, ev) =>
{ {
var window = new Views.Blame() { DataContext = new Blame(_repo, change.Path, _commit.SHA) }; var window = new Views.Blame() { DataContext = new Blame(_repo.FullPath, change.Path, _commit.SHA) };
window.Show(); window.Show();
ev.Handled = true; ev.Handled = true;
}; };
var full = Path.GetFullPath(Path.Combine(_repo, change.Path));
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.IsEnabled = File.Exists(full);
explore.Click += (_, ev) =>
{
Native.OS.OpenInFileManager(full, true);
ev.Handled = true;
};
menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(history); menu.Items.Add(history);
menu.Items.Add(blame); menu.Items.Add(blame);
menu.Items.Add(explore);
menu.Items.Add(new MenuItem { Header = "-" }); menu.Items.Add(new MenuItem { Header = "-" });
} }
@ -307,34 +352,25 @@ namespace SourceGit.ViewModels
public ContextMenu CreateRevisionFileContextMenu(Models.Object file) public ContextMenu CreateRevisionFileContextMenu(Models.Object file)
{ {
var history = new MenuItem(); var fullPath = Path.Combine(_repo.FullPath, file.Path);
history.Header = App.Text("FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories"); var resetToThisRevision = new MenuItem();
history.Click += (_, ev) => resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.IsEnabled = File.Exists(fullPath);
resetToThisRevision.Click += (_, ev) =>
{ {
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path, _issueTrackerRules) }; new Commands.Checkout(_repo.FullPath).FileWithRevision(file.Path, $"{_commit.SHA}");
window.Show();
ev.Handled = true; ev.Handled = true;
}; };
var blame = new MenuItem();
blame.Header = App.Text("Blame");
blame.Icon = App.CreateMenuIcon("Icons.Blame");
blame.IsEnabled = file.Type == Models.ObjectType.Blob;
blame.Click += (_, ev) =>
{
var window = new Views.Blame() { DataContext = new Blame(_repo, file.Path, _commit.SHA) };
window.Show();
ev.Handled = true;
};
var full = Path.GetFullPath(Path.Combine(_repo, file.Path));
var explore = new MenuItem(); var explore = new MenuItem();
explore.Header = App.Text("RevealFile"); explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore"); explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.IsEnabled = File.Exists(fullPath);
explore.Click += (_, ev) => explore.Click += (_, ev) =>
{ {
Native.OS.OpenInFileManager(full, file.Type == Models.ObjectType.Blob); Native.OS.OpenInFileManager(fullPath, file.Type == Models.ObjectType.Blob);
ev.Handled = true; ev.Handled = true;
}; };
@ -353,12 +389,33 @@ namespace SourceGit.ViewModels
if (selected.Count == 1) if (selected.Count == 1)
{ {
var saveTo = Path.Combine(selected[0].Path.LocalPath, Path.GetFileName(file.Path)); var saveTo = Path.Combine(selected[0].Path.LocalPath, Path.GetFileName(file.Path));
Commands.SaveRevisionFile.Run(_repo, _commit.SHA, file.Path, saveTo); Commands.SaveRevisionFile.Run(_repo.FullPath, _commit.SHA, file.Path, saveTo);
} }
ev.Handled = true; ev.Handled = true;
}; };
var history = new MenuItem();
history.Header = App.Text("FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path) };
window.Show();
ev.Handled = true;
};
var blame = new MenuItem();
blame.Header = App.Text("Blame");
blame.Icon = App.CreateMenuIcon("Icons.Blame");
blame.IsEnabled = file.Type == Models.ObjectType.Blob;
blame.Click += (_, ev) =>
{
var window = new Views.Blame() { DataContext = new Blame(_repo.FullPath, file.Path, _commit.SHA) };
window.Show();
ev.Handled = true;
};
var copyPath = new MenuItem(); var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath"); copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
@ -378,10 +435,14 @@ namespace SourceGit.ViewModels
}; };
var menu = new ContextMenu(); var menu = new ContextMenu();
menu.Items.Add(history); menu.Items.Add(resetToThisRevision);
menu.Items.Add(blame); menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(explore); menu.Items.Add(explore);
menu.Items.Add(saveAs); menu.Items.Add(saveAs);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(blame);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copyPath); menu.Items.Add(copyPath);
menu.Items.Add(copyFileName); menu.Items.Add(copyFileName);
return menu; return menu;
@ -406,9 +467,9 @@ namespace SourceGit.ViewModels
Task.Run(() => Task.Run(() =>
{ {
var fullMessage = new Commands.QueryCommitFullMessage(_repo, _commit.SHA).Result(); var fullMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA).Result();
var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0]; var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0];
var cmdChanges = new Commands.CompareRevisions(_repo, parent, _commit.SHA) { Cancel = _cancelToken }; var cmdChanges = new Commands.CompareRevisions(_repo.FullPath, parent, _commit.SHA) { Cancel = _cancelToken };
var changes = cmdChanges.Result(); var changes = cmdChanges.Result();
var visible = changes; var visible = changes;
if (!string.IsNullOrWhiteSpace(_searchChangeFilter)) if (!string.IsNullOrWhiteSpace(_searchChangeFilter))
@ -463,8 +524,7 @@ namespace SourceGit.ViewModels
".ico", ".bmp", ".jpg", ".png", ".jpeg" ".ico", ".bmp", ".jpg", ".png", ".jpeg"
}; };
private string _repo; private Repository _repo = null;
private AvaloniaList<Models.IssueTrackerRule> _issueTrackerRules = null;
private int _activePageIndex = 0; private int _activePageIndex = 0;
private Models.Commit _commit = null; private Models.Commit _commit = null;
private string _fullMessage = string.Empty; private string _fullMessage = string.Empty;

View file

@ -93,11 +93,8 @@ namespace SourceGit.ViewModels
public static ValidationResult ValidateSSHKey(string sshkey, ValidationContext ctx) public static ValidationResult ValidateSSHKey(string sshkey, ValidationContext ctx)
{ {
if (ctx.ObjectInstance is EditRemote edit && edit.UseSSH) if (ctx.ObjectInstance is EditRemote { _useSSH: true } && !string.IsNullOrEmpty(sshkey))
{ {
if (string.IsNullOrEmpty(sshkey))
return new ValidationResult("SSH private key is required");
if (!File.Exists(sshkey)) if (!File.Exists(sshkey))
return new ValidationResult("Given SSH private key can NOT be found!"); return new ValidationResult("Given SSH private key can NOT be found!");
} }

View file

@ -1,8 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels namespace SourceGit.ViewModels
@ -35,7 +33,7 @@ namespace SourceGit.ViewModels
} }
else else
{ {
DiffContext = new DiffContext(_repo, new Models.DiffOption(value, _file), _diffContext); DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(value, _file), _diffContext);
DetailContext.Commit = value; DetailContext.Commit = value;
} }
} }
@ -54,15 +52,15 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _detailContext, value); set => SetProperty(ref _detailContext, value);
} }
public FileHistories(string repo, string file, AvaloniaList<Models.IssueTrackerRule> issueTrackerRules) public FileHistories(Repository repo, string file)
{ {
_repo = repo; _repo = repo;
_file = file; _file = file;
_detailContext = new CommitDetail(repo, issueTrackerRules); _detailContext = new CommitDetail(repo);
Task.Run(() => Task.Run(() =>
{ {
var commits = new Commands.QueryCommits(_repo, $"-n 10000 -- \"{file}\"", false).Result(); var commits = new Commands.QueryCommits(_repo.FullPath, $"-n 10000 -- \"{file}\"", false).Result();
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() =>
{ {
IsLoading = false; IsLoading = false;
@ -73,7 +71,7 @@ namespace SourceGit.ViewModels
}); });
} }
private readonly string _repo = null; private readonly Repository _repo = null;
private readonly string _file = null; private readonly string _file = null;
private bool _isLoading = true; private bool _isLoading = true;
private List<Models.Commit> _commits = null; private List<Models.Commit> _commits = null;

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -11,6 +10,11 @@ namespace SourceGit.ViewModels
{ {
public class Histories : ObservableObject public class Histories : ObservableObject
{ {
public Repository Repo
{
get => _repo;
}
public bool IsLoading public bool IsLoading
{ {
get => _isLoading; get => _isLoading;
@ -55,11 +59,6 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _detailContext, value); set => SetProperty(ref _detailContext, value);
} }
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => _repo.Settings.IssueTrackerRules;
}
public Histories(Repository repo) public Histories(Repository repo)
{ {
_repo = repo; _repo = repo;
@ -99,7 +98,7 @@ namespace SourceGit.ViewModels
} }
else else
{ {
var commitDetail = new CommitDetail(_repo.FullPath, _repo.Settings.IssueTrackerRules); var commitDetail = new CommitDetail(_repo);
commitDetail.Commit = commit; commitDetail.Commit = commit;
DetailContext = commitDetail; DetailContext = commitDetail;
} }
@ -127,7 +126,7 @@ namespace SourceGit.ViewModels
} }
else else
{ {
var commitDetail = new CommitDetail(_repo.FullPath, _repo.Settings.IssueTrackerRules); var commitDetail = new CommitDetail(_repo);
commitDetail.Commit = commit; commitDetail.Commit = commit;
DetailContext = commitDetail; DetailContext = commitDetail;
} }
@ -249,7 +248,7 @@ namespace SourceGit.ViewModels
reword.Icon = App.CreateMenuIcon("Icons.Edit"); reword.Icon = App.CreateMenuIcon("Icons.Edit");
reword.Click += (_, e) => reword.Click += (_, e) =>
{ {
if (_repo.WorkingCopyChangesCount > 0) if (_repo.LocalChangesCount > 0)
{ {
App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first."); App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first.");
return; return;
@ -267,7 +266,7 @@ namespace SourceGit.ViewModels
squash.IsEnabled = commit.Parents.Count == 1; squash.IsEnabled = commit.Parents.Count == 1;
squash.Click += (_, e) => squash.Click += (_, e) =>
{ {
if (_repo.WorkingCopyChangesCount > 0) if (_repo.LocalChangesCount > 0)
{ {
App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first."); App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first.");
return; return;
@ -328,7 +327,7 @@ namespace SourceGit.ViewModels
interactiveRebase.IsVisible = current.Head != commit.SHA; interactiveRebase.IsVisible = current.Head != commit.SHA;
interactiveRebase.Click += (_, e) => interactiveRebase.Click += (_, e) =>
{ {
if (_repo.WorkingCopyChangesCount > 0) if (_repo.LocalChangesCount > 0)
{ {
App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first."); App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first.");
return; return;
@ -385,7 +384,7 @@ namespace SourceGit.ViewModels
}; };
menu.Items.Add(compareWithHead); menu.Items.Add(compareWithHead);
if (_repo.WorkingCopyChangesCount > 0) if (_repo.LocalChangesCount > 0)
{ {
var compareWithWorktree = new MenuItem(); var compareWithWorktree = new MenuItem();
compareWithWorktree.Header = App.Text("CommitCM.CompareWithWorktree"); compareWithWorktree.Header = App.Text("CommitCM.CompareWithWorktree");

View file

@ -114,7 +114,7 @@ namespace SourceGit.ViewModels
Current = current; Current = current;
On = on; On = on;
IsLoading = true; IsLoading = true;
DetailContext = new CommitDetail(repoPath, repo.Settings.IssueTrackerRules); DetailContext = new CommitDetail(repo);
Task.Run(() => Task.Run(() =>
{ {

View file

@ -141,7 +141,7 @@ namespace SourceGit.ViewModels
var last = Pages[0]; var last = Pages[0];
if (last.Data is Repository repo) if (last.Data is Repository repo)
{ {
Commands.AutoFetch.RemoveRepository(repo.FullPath); Models.AutoFetchManager.Instance.RemoveRepository(repo.FullPath);
repo.Close(); repo.Close();
last.Node = new RepositoryNode() { Id = Guid.NewGuid().ToString() }; last.Node = new RepositoryNode() { Id = Guid.NewGuid().ToString() };
@ -245,7 +245,7 @@ namespace SourceGit.ViewModels
}; };
repo.Open(); repo.Open();
Commands.AutoFetch.AddRepository(repo.FullPath); Models.AutoFetchManager.Instance.AddRepository(repo.FullPath);
if (page == null) if (page == null)
{ {
@ -371,7 +371,7 @@ namespace SourceGit.ViewModels
{ {
if (page.Data is Repository repo) if (page.Data is Repository repo)
{ {
Commands.AutoFetch.RemoveRepository(repo.FullPath); Models.AutoFetchManager.Instance.RemoveRepository(repo.FullPath);
repo.Close(); repo.Close();
} }

View file

@ -128,19 +128,6 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _layout, value); set => SetProperty(ref _layout, value);
} }
public string AvatarServer
{
get => Models.AvatarManager.SelectedServer;
set
{
if (Models.AvatarManager.SelectedServer != value)
{
Models.AvatarManager.SelectedServer = value;
OnPropertyChanged();
}
}
}
public int MaxHistoryCommits public int MaxHistoryCommits
{ {
get => _maxHistoryCommits; get => _maxHistoryCommits;
@ -262,11 +249,9 @@ namespace SourceGit.ViewModels
set set
{ {
if (Native.OS.SetShell(value)) if (Native.OS.SetShell(value))
{
OnPropertyChanged(); OnPropertyChanged();
} }
} }
}
public string GitDefaultCloneDir public string GitDefaultCloneDir
{ {
@ -276,12 +261,12 @@ namespace SourceGit.ViewModels
public bool GitAutoFetch public bool GitAutoFetch
{ {
get => Commands.AutoFetch.IsEnabled; get => Models.AutoFetchManager.Instance.IsEnabled;
set set
{ {
if (Commands.AutoFetch.IsEnabled != value) if (Models.AutoFetchManager.Instance.IsEnabled != value)
{ {
Commands.AutoFetch.IsEnabled = value; Models.AutoFetchManager.Instance.IsEnabled = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@ -289,15 +274,15 @@ namespace SourceGit.ViewModels
public int? GitAutoFetchInterval public int? GitAutoFetchInterval
{ {
get => Commands.AutoFetch.Interval; get => Models.AutoFetchManager.Instance.Interval;
set set
{ {
if (value is null or < 1) if (value is null || value < 1)
return; return;
if (Commands.AutoFetch.Interval != value) if (Models.AutoFetchManager.Instance.Interval != value)
{ {
Commands.AutoFetch.Interval = (int)value; Models.AutoFetchManager.Instance.Interval = (int)value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@ -336,7 +321,7 @@ namespace SourceGit.ViewModels
{ {
get; get;
set; set;
} = new List<string>(); } = [];
public int LastActiveTabIdx public int LastActiveTabIdx
{ {

View file

@ -142,14 +142,16 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _submodules, value); private set => SetProperty(ref _submodules, value);
} }
public int WorkingCopyChangesCount public int LocalChangesCount
{ {
get => _workingCopy == null ? 0 : _workingCopy.Count; get => _localChangesCount;
private set => SetProperty(ref _localChangesCount, value);
} }
public int StashesCount public int StashesCount
{ {
get => _stashesPage == null ? 0 : _stashesPage.Stashes.Count; get => _stashesCount;
private set => SetProperty(ref _stashesCount, value);
} }
public bool IncludeUntracked public bool IncludeUntracked
@ -350,6 +352,9 @@ namespace SourceGit.ViewModels
_stashesPage = null; _stashesPage = null;
_inProgressContext = null; _inProgressContext = null;
_localChangesCount = 0;
_stashesCount = 0;
_remotes.Clear(); _remotes.Clear();
_branches.Clear(); _branches.Clear();
_localBranchTrees.Clear(); _localBranchTrees.Clear();
@ -420,7 +425,7 @@ namespace SourceGit.ViewModels
return menu; return menu;
} }
public void Fetch() public void Fetch(bool autoStart)
{ {
if (!PopupHost.CanCreatePopup()) if (!PopupHost.CanCreatePopup())
return; return;
@ -431,10 +436,13 @@ namespace SourceGit.ViewModels
return; return;
} }
if (autoStart)
PopupHost.ShowAndStartPopup(new Fetch(this));
else
PopupHost.ShowPopup(new Fetch(this)); PopupHost.ShowPopup(new Fetch(this));
} }
public void Pull() public void Pull(bool autoStart)
{ {
if (!PopupHost.CanCreatePopup()) if (!PopupHost.CanCreatePopup())
return; return;
@ -445,10 +453,13 @@ namespace SourceGit.ViewModels
return; return;
} }
if (autoStart)
PopupHost.ShowAndStartPopup(new Pull(this, null));
else
PopupHost.ShowPopup(new Pull(this, null)); PopupHost.ShowPopup(new Pull(this, null));
} }
public void Push() public void Push(bool autoStart)
{ {
if (!PopupHost.CanCreatePopup()) if (!PopupHost.CanCreatePopup())
return; return;
@ -465,6 +476,9 @@ namespace SourceGit.ViewModels
return; return;
} }
if (autoStart)
PopupHost.ShowAndStartPopup(new Push(this, null));
else
PopupHost.ShowPopup(new Push(this, null)); PopupHost.ShowPopup(new Push(this, null));
} }
@ -607,15 +621,9 @@ namespace SourceGit.ViewModels
Task.Run(RefreshCommits); Task.Run(RefreshCommits);
} }
public void StashAll() public void StashAll(bool autoStart)
{ {
if (PopupHost.CanCreatePopup()) _workingCopy?.StashAll(autoStart);
{
var changes = new List<Models.Change>();
changes.AddRange(_workingCopy.Unstaged);
changes.AddRange(_workingCopy.Staged);
PopupHost.ShowPopup(new StashChanges(this, changes, true));
}
} }
public void GotoResolve() public void GotoResolve()
@ -812,7 +820,7 @@ namespace SourceGit.ViewModels
{ {
InProgressContext = inProgress; InProgressContext = inProgress;
HasUnsolvedConflicts = hasUnsolvedConflict; HasUnsolvedConflicts = hasUnsolvedConflict;
OnPropertyChanged(nameof(WorkingCopyChangesCount)); LocalChangesCount = changes.Count;
}); });
} }
@ -823,7 +831,8 @@ namespace SourceGit.ViewModels
{ {
if (_stashesPage != null) if (_stashesPage != null)
_stashesPage.Stashes = stashes; _stashesPage.Stashes = stashes;
OnPropertyChanged(nameof(StashesCount));
StashesCount = stashes.Count;
}); });
} }
@ -856,7 +865,7 @@ namespace SourceGit.ViewModels
if (branch.IsLocal) if (branch.IsLocal)
{ {
if (WorkingCopyChangesCount > 0) if (_localChangesCount > 0)
PopupHost.ShowPopup(new Checkout(this, branch.Name)); PopupHost.ShowPopup(new Checkout(this, branch.Name));
else else
PopupHost.ShowAndStartPopup(new Checkout(this, branch.Name)); PopupHost.ShowAndStartPopup(new Checkout(this, branch.Name));
@ -1193,7 +1202,7 @@ namespace SourceGit.ViewModels
var discard = new MenuItem(); var discard = new MenuItem();
discard.Header = App.Text("BranchCM.DiscardAll"); discard.Header = App.Text("BranchCM.DiscardAll");
discard.Icon = App.CreateMenuIcon("Icons.Undo"); discard.Icon = App.CreateMenuIcon("Icons.Undo");
discard.IsEnabled = _workingCopy.Count > 0; discard.IsEnabled = _localChangesCount > 0;
discard.Click += (_, e) => discard.Click += (_, e) =>
{ {
if (PopupHost.CanCreatePopup()) if (PopupHost.CanCreatePopup())
@ -1297,7 +1306,7 @@ namespace SourceGit.ViewModels
menu.Items.Add(merge); menu.Items.Add(merge);
menu.Items.Add(rebase); menu.Items.Add(rebase);
if (WorkingCopyChangesCount > 0) if (_localChangesCount > 0)
{ {
var compareWithWorktree = new MenuItem(); var compareWithWorktree = new MenuItem();
compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree"); compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree");
@ -1320,7 +1329,7 @@ namespace SourceGit.ViewModels
var compareWithBranch = CreateMenuItemToCompareBranches(branch); var compareWithBranch = CreateMenuItemToCompareBranches(branch);
if (compareWithBranch != null) if (compareWithBranch != null)
{ {
if (WorkingCopyChangesCount == 0) if (_localChangesCount == 0)
menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(compareWithBranch); menu.Items.Add(compareWithBranch);
@ -1597,7 +1606,7 @@ namespace SourceGit.ViewModels
} }
var hasCompare = false; var hasCompare = false;
if (WorkingCopyChangesCount > 0) if (_localChangesCount > 0)
{ {
var compareWithWorktree = new MenuItem(); var compareWithWorktree = new MenuItem();
compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree"); compareWithWorktree.Header = App.Text("BranchCM.CompareWithWorktree");
@ -1959,6 +1968,9 @@ namespace SourceGit.ViewModels
private int _selectedViewIndex = 0; private int _selectedViewIndex = 0;
private object _selectedView = null; private object _selectedView = null;
private int _localChangesCount = 0;
private int _stashesCount = 0;
private bool _isSearching = false; private bool _isSearching = false;
private bool _isSearchLoadingVisible = false; private bool _isSearchLoadingVisible = false;
private bool _isSearchCommitSuggestionOpen = false; private bool _isSearchCommitSuggestionOpen = false;

View file

@ -42,6 +42,17 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _httpProxy, value); set => SetProperty(ref _httpProxy, value);
} }
public AvaloniaList<Models.CommitTemplate> CommitTemplates
{
get => _repo.Settings.CommitTemplates;
}
public Models.CommitTemplate SelectedCommitTemplate
{
get => _selectedCommitTemplate;
set => SetProperty(ref _selectedCommitTemplate, value);
}
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{ {
get => _repo.Settings.IssueTrackerRules; get => _repo.Settings.IssueTrackerRules;
@ -77,6 +88,20 @@ namespace SourceGit.ViewModels
HttpProxy = string.Empty; HttpProxy = string.Empty;
} }
public void AddCommitTemplate()
{
var template = new Models.CommitTemplate() { Name = "New Template" };
_repo.Settings.CommitTemplates.Add(template);
SelectedCommitTemplate = template;
}
public void RemoveSelectedCommitTemplate()
{
if (_selectedCommitTemplate != null)
_repo.Settings.CommitTemplates.Remove(_selectedCommitTemplate);
SelectedCommitTemplate = null;
}
public void AddSampleGithubIssueTracker() public void AddSampleGithubIssueTracker()
{ {
foreach (var remote in _repo.Remotes) foreach (var remote in _repo.Remotes)
@ -106,6 +131,7 @@ namespace SourceGit.ViewModels
public void RemoveSelectedIssueTracker() public void RemoveSelectedIssueTracker()
{ {
if (_selectedIssueTrackerRule != null)
_repo.Settings.RemoveIssueTracker(_selectedIssueTrackerRule); _repo.Settings.RemoveIssueTracker(_selectedIssueTrackerRule);
SelectedIssueTrackerRule = null; SelectedIssueTrackerRule = null;
} }
@ -141,6 +167,7 @@ namespace SourceGit.ViewModels
private readonly Repository _repo = null; private readonly Repository _repo = null;
private readonly Dictionary<string, string> _cached = null; private readonly Dictionary<string, string> _cached = null;
private string _httpProxy; private string _httpProxy;
private Models.CommitTemplate _selectedCommitTemplate = null;
private Models.IssueTrackerRule _selectedIssueTrackerRule = null; private Models.IssueTrackerRule _selectedIssueTrackerRule = null;
} }
} }

View file

@ -39,7 +39,7 @@ namespace SourceGit.ViewModels
return Task.Run(() => return Task.Run(() =>
{ {
var succ = new Commands.Commit(_repo.FullPath, _message, false, true, true).Exec(); var succ = new Commands.Commit(_repo.FullPath, _message, true, true).Exec();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -43,7 +43,7 @@ namespace SourceGit.ViewModels
{ {
var succ = new Commands.Reset(_repo.FullPath, Parent.SHA, "--soft").Exec(); var succ = new Commands.Reset(_repo.FullPath, Parent.SHA, "--soft").Exec();
if (succ) if (succ)
succ = new Commands.Commit(_repo.FullPath, _message, false, true).Exec(); succ = new Commands.Commit(_repo.FullPath, _message, true).Exec();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -177,8 +177,6 @@ namespace SourceGit.ViewModels
} }
} }
public int Count => _count;
public object DetailContext public object DetailContext
{ {
get => _detailContext; get => _detailContext;
@ -317,6 +315,17 @@ namespace SourceGit.ViewModels
dialog.ShowDialog(toplevel); dialog.ShowDialog(toplevel);
} }
public void StashAll(bool autoStart)
{
if (!PopupHost.CanCreatePopup())
return;
if (autoStart)
PopupHost.ShowAndStartPopup(new StashChanges(_repo, _cached, true));
else
PopupHost.ShowPopup(new StashChanges(_repo, _cached, true));
}
public void StageSelected() public void StageSelected()
{ {
StageChanges(_selectedUnstaged); StageChanges(_selectedUnstaged);
@ -556,7 +565,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) => history.Click += (_, e) =>
{ {
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path, _repo.Settings.IssueTrackerRules) }; var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) };
window.Show(); window.Show();
e.Handled = true; e.Handled = true;
}; };
@ -1118,35 +1127,63 @@ namespace SourceGit.ViewModels
public ContextMenu CreateContextMenuForCommitMessages() public ContextMenu CreateContextMenuForCommitMessages()
{ {
var menu = new ContextMenu(); var menu = new ContextMenu();
if (_repo.Settings.CommitMessages.Count == 0)
var templateCount = _repo.Settings.CommitTemplates.Count;
if (templateCount == 0)
{ {
var empty = new MenuItem(); menu.Items.Add(new MenuItem()
empty.Header = App.Text("WorkingCopy.NoCommitHistories"); {
empty.IsEnabled = false; Header = App.Text("WorkingCopy.NoCommitTemplates"),
menu.Items.Add(empty); Icon = App.CreateMenuIcon("Icons.Code"),
return menu; IsEnabled = false
});
} }
else
var tip = new MenuItem();
tip.Header = App.Text("WorkingCopy.HasCommitHistories");
tip.IsEnabled = false;
menu.Items.Add(tip);
menu.Items.Add(new MenuItem() { Header = "-" });
foreach (var message in _repo.Settings.CommitMessages)
{ {
var dump = message; for (int i = 0; i < templateCount; i++)
{
var template = _repo.Settings.CommitTemplates[i];
var item = new MenuItem(); var item = new MenuItem();
item.Header = dump; item.Header = new Views.NameHighlightedTextBlock("WorkingCopy.UseCommitTemplate", template.Name);
item.Icon = App.CreateMenuIcon("Icons.Code");
item.Click += (_, e) => item.Click += (_, e) =>
{ {
CommitMessage = dump; CommitMessage = template.Content;
e.Handled = true;
};
menu.Items.Add(item);
}
}
menu.Items.Add(new MenuItem() { Header = "-" });
var historiesCount = _repo.Settings.CommitMessages.Count;
if (historiesCount == 0)
{
menu.Items.Add(new MenuItem()
{
Header = App.Text("WorkingCopy.NoCommitHistories"),
Icon = App.CreateMenuIcon("Icons.Histories"),
IsEnabled = false
});
}
else
{
for (int i = 0; i < historiesCount; i++)
{
var message = _repo.Settings.CommitMessages[i];
var item = new MenuItem();
item.Header = message;
item.Icon = App.CreateMenuIcon("Icons.Histories");
item.Click += (_, e) =>
{
CommitMessage = message;
e.Handled = true; e.Handled = true;
}; };
menu.Items.Add(item); menu.Items.Add(item);
} }
}
return menu; return menu;
} }
@ -1245,9 +1282,10 @@ namespace SourceGit.ViewModels
return; return;
} }
var autoStage = AutoStageBeforeCommit;
if (!_useAmend) if (!_useAmend)
{ {
if (AutoStageBeforeCommit) if (autoStage)
{ {
if (_count == 0) if (_count == 0)
{ {
@ -1269,26 +1307,28 @@ namespace SourceGit.ViewModels
_repo.Settings.PushCommitMessage(_commitMessage); _repo.Settings.PushCommitMessage(_commitMessage);
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
var autoStage = AutoStageBeforeCommit;
Task.Run(() => Task.Run(() =>
{ {
var succ = new Commands.Commit(_repo.FullPath, _commitMessage, autoStage, _useAmend).Exec(); var succ = true;
if (autoStage && _unstaged.Count > 0)
succ = new Commands.Add(_repo.FullPath).Exec();
if (succ)
succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend).Exec();
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
if (succ) if (succ)
{ {
SelectedStaged = [];
CommitMessage = string.Empty; CommitMessage = string.Empty;
UseAmend = false; UseAmend = false;
if (autoPush) if (autoPush)
{
PopupHost.ShowAndStartPopup(new Push(_repo, null)); PopupHost.ShowAndStartPopup(new Push(_repo, null));
} }
}
_repo.MarkWorkingCopyDirtyManually(); _repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true); _repo.SetWatcherEnabled(true);
IsCommitting = false; IsCommitting = false;
}); });
}); });

View file

@ -39,7 +39,7 @@ namespace SourceGit.Views
refetch.Click += (_, _) => refetch.Click += (_, _) =>
{ {
if (User != null) if (User != null)
Models.AvatarManager.Request(User.Email, true); Models.AvatarManager.Instance.Request(User.Email, true);
}; };
ContextMenu = new ContextMenu(); ContextMenu = new ContextMenu();
@ -54,7 +54,7 @@ namespace SourceGit.Views
return; return;
var corner = (float)Math.Max(2, Bounds.Width / 16); var corner = (float)Math.Max(2, Bounds.Width / 16);
var img = Models.AvatarManager.Request(User.Email, false); var img = Models.AvatarManager.Instance.Request(User.Email, false);
if (img != null) if (img != null)
{ {
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height); var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
@ -72,21 +72,19 @@ namespace SourceGit.Views
public void OnAvatarResourceChanged(string email) public void OnAvatarResourceChanged(string email)
{ {
if (User.Email.Equals(email, StringComparison.Ordinal)) if (User.Email.Equals(email, StringComparison.Ordinal))
{
InvalidateVisual(); InvalidateVisual();
} }
}
protected override void OnLoaded(RoutedEventArgs e) protected override void OnLoaded(RoutedEventArgs e)
{ {
base.OnLoaded(e); base.OnLoaded(e);
Models.AvatarManager.Subscribe(this); Models.AvatarManager.Instance.Subscribe(this);
} }
protected override void OnUnloaded(RoutedEventArgs e) protected override void OnUnloaded(RoutedEventArgs e)
{ {
base.OnUnloaded(e); base.OnUnloaded(e);
Models.AvatarManager.Unsubscribe(this); Models.AvatarManager.Instance.Unsubscribe(this);
} }
private static void OnUserPropertyChanged(Avatar avatar, AvaloniaPropertyChangedEventArgs e) private static void OnUserPropertyChanged(Avatar avatar, AvaloniaPropertyChangedEventArgs e)

View file

@ -40,6 +40,9 @@ namespace SourceGit.Views
foreach (var line in view.VisualLines) foreach (var line in view.VisualLines)
{ {
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var lineNumber = line.FirstDocumentLine.LineNumber; var lineNumber = line.FirstDocumentLine.LineNumber;
if (lineNumber > _editor.BlameData.LineInfos.Count) if (lineNumber > _editor.BlameData.LineInfos.Count)
break; break;
@ -151,6 +154,9 @@ namespace SourceGit.Views
foreach (var line in view.VisualLines) foreach (var line in view.VisualLines)
{ {
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var lineNumber = line.FirstDocumentLine.LineNumber; var lineNumber = line.FirstDocumentLine.LineNumber;
if (lineNumber >= _editor.BlameData.LineInfos.Count) if (lineNumber >= _editor.BlameData.LineInfos.Count)
break; break;

View file

@ -59,7 +59,7 @@
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4"> <Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
<TextBlock Text="{Binding Base.FriendlyName}" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/> <TextBlock Text="{Binding Base.FriendlyName}" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/>
</Border> </Border>
<TextBlock Grid.Column="3" Classes="primary" Text="{Binding BaseHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA"/> <TextBlock Grid.Column="3" Classes="primary" Text="{Binding BaseHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" Cursor="Hand" PointerPressed="OnPressedSHA"/>
<TextBlock Grid.Column="4" Classes="primary" Text="{Binding BaseHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/> <TextBlock Grid.Column="4" Classes="primary" Text="{Binding BaseHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
</Grid> </Grid>
@ -83,7 +83,7 @@
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4"> <Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
<TextBlock Text="{Binding To.FriendlyName}" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/> <TextBlock Text="{Binding To.FriendlyName}" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/>
</Border> </Border>
<TextBlock Grid.Column="3" Classes="primary" Text="{Binding ToHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA"/> <TextBlock Grid.Column="3" Classes="primary" Text="{Binding ToHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" Cursor="Hand" PointerPressed="OnPressedSHA"/>
<TextBlock Grid.Column="4" Classes="primary" Text="{Binding ToHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/> <TextBlock Grid.Column="4" Classes="primary" Text="{Binding ToHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
</Grid> </Grid>

View file

@ -83,20 +83,13 @@
FontWeight="{Binding NameFontWeight}"/> FontWeight="{Binding NameFontWeight}"/>
<!-- Tracking status --> <!-- Tracking status -->
<Border Grid.Column="2" <v:BranchTreeNodeTrackStatusPresenter Grid.Column="2"
Margin="8,0" Margin="8,0"
Height="18"
CornerRadius="9"
VerticalAlignment="Center" VerticalAlignment="Center"
Background="{DynamicResource Brush.Badge}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
IsVisible="{Binding TrackStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Classes="primary"
FontSize="10" FontSize="10"
HorizontalAlignment="Center" Foreground="{DynamicResource Brush.BadgeFG}"
Margin="9,0" Background="{DynamicResource Brush.Badge}"/>
Text="{Binding TrackStatus}"
Foreground="{DynamicResource Brush.BadgeFG}"/>
</Border>
<!-- Filter Toggle Button --> <!-- Filter Toggle Button -->
<ToggleButton Grid.Column="3" <ToggleButton Grid.Column="3"

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using Avalonia; using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
@ -104,6 +105,99 @@ namespace SourceGit.Views
} }
} }
public class BranchTreeNodeTrackStatusPresenter : Control
{
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<BranchTreeNodeTrackStatusPresenter>();
public FontFamily FontFamily
{
get => GetValue(FontFamilyProperty);
set => SetValue(FontFamilyProperty, value);
}
public static readonly StyledProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<BranchTreeNodeTrackStatusPresenter>();
public double FontSize
{
get => GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
public static readonly StyledProperty<IBrush> ForegroundProperty =
AvaloniaProperty.Register<BranchTreeNodeTrackStatusPresenter, IBrush>(nameof(Foreground), Brushes.White);
public IBrush Foreground
{
get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value);
}
public static readonly StyledProperty<IBrush> BackgroundProperty =
AvaloniaProperty.Register<BranchTreeNodeTrackStatusPresenter, IBrush>(nameof(Background), Brushes.White);
public IBrush Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
static BranchTreeNodeTrackStatusPresenter()
{
AffectsMeasure<BranchTreeNodeTrackStatusPresenter>(
FontSizeProperty,
FontFamilyProperty,
ForegroundProperty);
AffectsRender<BranchTreeNodeTrackStatusPresenter>(
ForegroundProperty,
BackgroundProperty);
}
public override void Render(DrawingContext context)
{
base.Render(context);
if (_label != null)
{
context.DrawRectangle(Background, null, new RoundedRect(new Rect(0, 0, _label.Width + 18, 18), new CornerRadius(9)));
context.DrawText(_label, new Point(9, 9 - _label.Height * 0.5));
}
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
InvalidateMeasure();
InvalidateVisual();
}
protected override Size MeasureOverride(Size availableSize)
{
_label = null;
if (DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
{
var status = branch.TrackStatus.ToString();
if (!string.IsNullOrEmpty(status))
{
_label = new FormattedText(
status,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily),
FontSize,
Foreground);
}
}
return _label != null ? new Size(_label.Width + 18, 18) : new Size(0, 0);
}
private FormattedText _label = null;
}
public partial class BranchTree : UserControl public partial class BranchTree : UserControl
{ {
public static readonly StyledProperty<List<ViewModels.BranchTreeNode>> NodesProperty = public static readonly StyledProperty<List<ViewModels.BranchTreeNode>> NodesProperty =

View file

@ -54,7 +54,17 @@
<Grid RowDefinitions="24,Auto,Auto,Auto" ColumnDefinitions="96,*"> <Grid RowDefinitions="24,Auto,Auto,Auto" ColumnDefinitions="96,*">
<!-- SHA --> <!-- SHA -->
<TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.SHA}" /> <TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.SHA}" />
<SelectableTextBlock Grid.Row="0" Grid.Column="1" Classes="primary" Text="{Binding SHA}" Margin="12,0,0,0" VerticalAlignment="Center"/> <StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
<SelectableTextBlock Classes="primary"
Text="{Binding SHA}"
Margin="12,0,0,0"
VerticalAlignment="Center"/>
<Button Classes="icon_button" Cursor="Hand" Click="OnOpenWebLink" IsVisible="{Binding #ThisControl.WebLinks, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
<Path Width="12" Height="12" Data="{StaticResource Icons.Link}" Fill="{DynamicResource Brush.Link}"/>
</Button>
</StackPanel>
<!-- PARENTS --> <!-- PARENTS -->
<TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/> <TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/>
@ -71,6 +81,7 @@
Text="{Binding Converter={x:Static c:StringConverters.ToShortSHA}}" Text="{Binding Converter={x:Static c:StringConverters.ToShortSHA}}"
Foreground="DarkOrange" Foreground="DarkOrange"
TextDecorations="Underline" TextDecorations="Underline"
Cursor="Hand"
Margin="0,0,16,0" Margin="0,0,16,0"
PointerPressed="OnParentSHAPressed"/> PointerPressed="OnParentSHAPressed"/>
</DataTemplate> </DataTemplate>

View file

@ -2,20 +2,12 @@ using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
namespace SourceGit.Views namespace SourceGit.Views
{ {
public partial class CommitBaseInfo : UserControl public partial class CommitBaseInfo : UserControl
{ {
public static readonly StyledProperty<bool> CanNavigateProperty =
AvaloniaProperty.Register<CommitBaseInfo, bool>(nameof(CanNavigate), true);
public bool CanNavigate
{
get => GetValue(CanNavigateProperty);
set => SetValue(CanNavigateProperty, value);
}
public static readonly StyledProperty<string> MessageProperty = public static readonly StyledProperty<string> MessageProperty =
AvaloniaProperty.Register<CommitBaseInfo, string>(nameof(Message), string.Empty); AvaloniaProperty.Register<CommitBaseInfo, string>(nameof(Message), string.Empty);
@ -25,6 +17,15 @@ namespace SourceGit.Views
set => SetValue(MessageProperty, value); set => SetValue(MessageProperty, value);
} }
public static readonly StyledProperty<AvaloniaList<Models.CommitLink>> WebLinksProperty =
AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<Models.CommitLink>>(nameof(WebLinks));
public AvaloniaList<Models.CommitLink> WebLinks
{
get => GetValue(WebLinksProperty);
set => SetValue(WebLinksProperty, value);
}
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty = public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules)); AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
@ -39,11 +40,43 @@ namespace SourceGit.Views
InitializeComponent(); InitializeComponent();
} }
private void OnOpenWebLink(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.CommitDetail detail)
{
var links = WebLinks;
if (links.Count > 1)
{
var menu = new ContextMenu();
foreach (var link in links)
{
var url = $"{link.URLPrefix}{detail.Commit.SHA}";
var item = new MenuItem() { Header = link.Name };
item.Click += (_, ev) =>
{
Native.OS.OpenBrowser(url);
ev.Handled = true;
};
menu.Items.Add(item);
}
(sender as Control)?.OpenContextMenu(menu);
}
else if (links.Count == 1)
{
var url = $"{links[0].URLPrefix}{detail.Commit.SHA}";
Native.OS.OpenBrowser(url);
}
}
e.Handled = true;
}
private void OnParentSHAPressed(object sender, PointerPressedEventArgs e) private void OnParentSHAPressed(object sender, PointerPressedEventArgs e)
{ {
if (sender is Control { DataContext: string sha } && if (DataContext is ViewModels.CommitDetail detail && sender is Control { DataContext: string sha })
DataContext is ViewModels.CommitDetail detail &&
CanNavigate)
{ {
detail.NavigateTo(sha); detail.NavigateTo(sha);
} }

View file

@ -21,6 +21,7 @@
<!-- Base Information --> <!-- Base Information -->
<v:CommitBaseInfo Content="{Binding Commit}" <v:CommitBaseInfo Content="{Binding Commit}"
Message="{Binding FullMessage}" Message="{Binding FullMessage}"
WebLinks="{Binding WebLinks}"
IssueTrackerRules="{Binding IssueTrackerRules}"/> IssueTrackerRules="{Binding IssueTrackerRules}"/>
<!-- Line --> <!-- Line -->

View file

@ -6,6 +6,7 @@ using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Documents; using Avalonia.Controls.Documents;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Utilities;
namespace SourceGit.Views namespace SourceGit.Views
{ {
@ -38,6 +39,8 @@ namespace SourceGit.Views
if (change.Property == MessageProperty || change.Property == IssueTrackerRulesProperty) if (change.Property == MessageProperty || change.Property == IssueTrackerRulesProperty)
{ {
Inlines.Clear(); Inlines.Clear();
_matches = null;
ClearHoveredIssueLink();
var message = Message; var message = Message;
if (string.IsNullOrEmpty(message)) if (string.IsNullOrEmpty(message))
@ -61,6 +64,7 @@ namespace SourceGit.Views
} }
matches.Sort((l, r) => l.Start - r.Start); matches.Sort((l, r) => l.Start - r.Start);
_matches = matches;
int pos = 0; int pos = 0;
foreach (var match in matches) foreach (var match in matches)
@ -68,12 +72,9 @@ namespace SourceGit.Views
if (match.Start > pos) if (match.Start > pos)
Inlines.Add(new Run(message.Substring(pos, match.Start - pos))); Inlines.Add(new Run(message.Substring(pos, match.Start - pos)));
var link = new TextBlock(); match.Link = new Run(message.Substring(match.Start, match.Length));
link.SetValue(TextProperty, message.Substring(match.Start, match.Length)); match.Link.Classes.Add("issue_link");
link.SetValue(ToolTip.TipProperty, match.URL); Inlines.Add(match.Link);
link.Classes.Add("issue_link");
link.PointerPressed += OnLinkPointerPressed;
Inlines.Add(link);
pos = match.Start + match.Length; pos = match.Start + match.Length;
} }
@ -83,16 +84,71 @@ namespace SourceGit.Views
} }
} }
private void OnLinkPointerPressed(object sender, PointerPressedEventArgs e) protected override void OnPointerMoved(PointerEventArgs e)
{ {
if (sender is TextBlock text) base.OnPointerMoved(e);
{
var tooltip = text.GetValue(ToolTip.TipProperty) as string;
if (!string.IsNullOrEmpty(tooltip))
Native.OS.OpenBrowser(tooltip);
if (e.Pointer.Captured == null && _matches != null)
{
var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Height, 0)));
var pos = TextLayout.HitTestPoint(point).TextPosition;
foreach (var match in _matches)
{
if (!match.Intersect(pos, 1))
continue;
if (match == _lastHover)
return;
_lastHover = match;
//_lastHover.Link.Classes.Add("issue_link_hovered");
SetCurrentValue(CursorProperty, Cursor.Parse("Hand"));
ToolTip.SetTip(this, match.URL);
ToolTip.SetIsOpen(this, true);
return;
}
ClearHoveredIssueLink();
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (_lastHover != null)
{
e.Pointer.Capture(null);
Native.OS.OpenBrowser(_lastHover.URL);
e.Handled = true; e.Handled = true;
return;
}
base.OnPointerPressed(e);
}
protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
ClearHoveredIssueLink();
}
private void ClearHoveredIssueLink()
{
if (_lastHover != null)
{
ToolTip.SetTip(this, null);
SetCurrentValue(CursorProperty, Cursor.Parse("IBeam"));
//_lastHover.Link.Classes.Remove("issue_link_hovered");
_lastHover = null;
} }
} }
private List<Models.IssueTrackerMatch> _matches = null;
private Models.IssueTrackerMatch _lastHover = null;
} }
} }

View file

@ -191,9 +191,11 @@ namespace SourceGit.Views
requiredWidth += label.Width + 16 /* icon */ + 8 /* label margin */ + 4 /* item right margin */; requiredWidth += label.Width + 16 /* icon */ + 8 /* label margin */ + 4 /* item right margin */;
} }
InvalidateVisual();
return new Size(requiredWidth, 16); return new Size(requiredWidth, 16);
} }
InvalidateVisual();
return new Size(0, 0); return new Size(0, 0);
} }

View file

@ -176,7 +176,7 @@
<ContentControl.DataTemplates> <ContentControl.DataTemplates>
<DataTemplate DataType="m:RevisionSubmodule"> <DataTemplate DataType="m:RevisionSubmodule">
<Border Margin="0,0,0,8" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Background="{DynamicResource Brush.Window}"> <Border Margin="0,0,0,8" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Background="{DynamicResource Brush.Window}">
<v:CommitBaseInfo MaxHeight="256" Margin="0,0,0,4" CanNavigate="False" Content="{Binding Commit}" Message="{Binding FullMessage}"/> <v:CommitBaseInfo MaxHeight="256" Margin="0,0,0,4" Content="{Binding Commit}" Message="{Binding FullMessage}"/>
</Border> </Border>
</DataTemplate> </DataTemplate>
</ContentControl.DataTemplates> </ContentControl.DataTemplates>
@ -190,7 +190,7 @@
<Path Width="16" Height="16" Data="{StaticResource Icons.DoubleDown}" HorizontalAlignment="Center" IsVisible="{Binding Old, Converter={x:Static ObjectConverters.IsNotNull}}"/> <Path Width="16" Height="16" Data="{StaticResource Icons.DoubleDown}" HorizontalAlignment="Center" IsVisible="{Binding Old, Converter={x:Static ObjectConverters.IsNotNull}}"/>
<Border Margin="0,8,0,0" BorderThickness="1" BorderBrush="Green" Background="{DynamicResource Brush.Window}"> <Border Margin="0,8,0,0" BorderThickness="1" BorderBrush="Green" Background="{DynamicResource Brush.Window}">
<v:CommitBaseInfo MaxHeight="256" Margin="0,0,0,4" CanNavigate="False" Content="{Binding New.Commit}" Message="{Binding New.FullMessage}"/> <v:CommitBaseInfo MaxHeight="256" Margin="0,0,0,4" Content="{Binding New.Commit}" Message="{Binding New.FullMessage}"/>
</Border> </Border>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View file

@ -11,6 +11,7 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace SourceGit.Views namespace SourceGit.Views
@ -185,6 +186,8 @@ namespace SourceGit.Views
if (change.Property == SubjectProperty || change.Property == IssueTrackerRulesProperty) if (change.Property == SubjectProperty || change.Property == IssueTrackerRulesProperty)
{ {
Inlines.Clear(); Inlines.Clear();
_matches = null;
ClearHoveredIssueLink();
var subject = Subject; var subject = Subject;
if (string.IsNullOrEmpty(subject)) if (string.IsNullOrEmpty(subject))
@ -208,6 +211,7 @@ namespace SourceGit.Views
} }
matches.Sort((l, r) => l.Start - r.Start); matches.Sort((l, r) => l.Start - r.Start);
_matches = matches;
int pos = 0; int pos = 0;
foreach (var match in matches) foreach (var match in matches)
@ -215,32 +219,82 @@ namespace SourceGit.Views
if (match.Start > pos) if (match.Start > pos)
Inlines.Add(new Run(subject.Substring(pos, match.Start - pos))); Inlines.Add(new Run(subject.Substring(pos, match.Start - pos)));
var link = new TextBlock(); match.Link = new Run(subject.Substring(match.Start, match.Length));
link.SetValue(TextProperty, subject.Substring(match.Start, match.Length)); match.Link.Classes.Add("issue_link");
link.SetValue(ToolTip.TipProperty, match.URL); Inlines.Add(match.Link);
link.Classes.Add("issue_link");
link.PointerPressed += OnLinkPointerPressed;
Inlines.Add(link);
pos = match.Start + match.Length; pos = match.Start + match.Length;
} }
if (pos < subject.Length) if (pos < subject.Length)
Inlines.Add(new Run(subject.Substring(pos))); Inlines.Add(new Run(subject.Substring(pos)));
InvalidateTextLayout();
} }
} }
private void OnLinkPointerPressed(object sender, PointerPressedEventArgs e) protected override void OnPointerMoved(PointerEventArgs e)
{ {
if (sender is TextBlock text) base.OnPointerMoved(e);
{
var tooltip = text.GetValue(ToolTip.TipProperty) as string;
if (!string.IsNullOrEmpty(tooltip))
Native.OS.OpenBrowser(tooltip);
if (_matches != null)
{
var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Height, 0)));
var textPosition = TextLayout.HitTestPoint(point).TextPosition;
foreach (var match in _matches)
{
if (!match.Intersect(textPosition, 1))
continue;
if (match == _lastHover)
return;
_lastHover = match;
//_lastHover.Link.Classes.Add("issue_link_hovered");
SetCurrentValue(CursorProperty, Cursor.Parse("Hand"));
ToolTip.SetTip(this, match.URL);
ToolTip.SetIsOpen(this, true);
e.Handled = true; e.Handled = true;
return;
}
ClearHoveredIssueLink();
} }
} }
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
if (_lastHover != null)
Native.OS.OpenBrowser(_lastHover.URL);
}
protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
ClearHoveredIssueLink();
}
private void ClearHoveredIssueLink()
{
if (_lastHover != null)
{
ToolTip.SetTip(this, null);
SetCurrentValue(CursorProperty, Cursor.Parse("Arrow"));
//_lastHover.Link.Classes.Remove("issue_link_hovered");
_lastHover = null;
}
}
private List<Models.IssueTrackerMatch> _matches = null;
private Models.IssueTrackerMatch _lastHover = null;
} }
public class CommitTimeTextBlock : TextBlock public class CommitTimeTextBlock : TextBlock
@ -541,6 +595,15 @@ namespace SourceGit.Views
set => SetValue(CurrentBranchProperty, value); set => SetValue(CurrentBranchProperty, value);
} }
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
AvaloniaProperty.Register<Histories, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => GetValue(IssueTrackerRulesProperty);
set => SetValue(IssueTrackerRulesProperty, value);
}
public static readonly StyledProperty<long> NavigationIdProperty = public static readonly StyledProperty<long> NavigationIdProperty =
AvaloniaProperty.Register<Histories, long>(nameof(NavigationId)); AvaloniaProperty.Register<Histories, long>(nameof(NavigationId));
@ -550,16 +613,6 @@ namespace SourceGit.Views
set => SetValue(NavigationIdProperty, value); set => SetValue(NavigationIdProperty, value);
} }
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get
{
if (DataContext is ViewModels.Histories histories)
return histories.IssueTrackerRules;
return null;
}
}
static Histories() static Histories()
{ {
NavigationIdProperty.Changed.AddClassHandler<Histories>((h, _) => NavigationIdProperty.Changed.AddClassHandler<Histories>((h, _) =>

View file

@ -41,6 +41,11 @@
<Path Width="14" Height="14" Data="{StaticResource Icons.Settings}"/> <Path Width="14" Height="14" Data="{StaticResource Icons.Settings}"/>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="{DynamicResource Text.OpenAppDataDir}" Command="{x:Static s:App.OpenAppDataDirCommand}">
<MenuItem.Icon>
<Path Width="14" Height="14" Data="{StaticResource Icons.Explore}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}"> <MenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}">
<MenuItem.Icon> <MenuItem.Icon>
<Path Width="14" Height="14" Data="{StaticResource Icons.Hotkeys}"/> <Path Width="14" Height="14" Data="{StaticResource Icons.Hotkeys}"/>

View file

@ -20,6 +20,11 @@ namespace SourceGit.Views
InitializeComponent(); InitializeComponent();
} }
public bool HasKeyModifier(KeyModifiers modifier)
{
return _unhandledModifiers.HasFlag(modifier);
}
protected override void OnOpened(EventArgs e) protected override void OnOpened(EventArgs e)
{ {
base.OnOpened(e); base.OnOpened(e);
@ -147,6 +152,27 @@ namespace SourceGit.Views
} }
base.OnKeyDown(e); base.OnKeyDown(e);
// Record unhandled key modifers.
if (!e.Handled)
{
_unhandledModifiers = e.KeyModifiers;
if (!_unhandledModifiers.HasFlag(KeyModifiers.Alt) && (e.Key == Key.LeftAlt || e.Key == Key.RightAlt))
_unhandledModifiers |= KeyModifiers.Alt;
if (!_unhandledModifiers.HasFlag(KeyModifiers.Control) && (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl))
_unhandledModifiers |= KeyModifiers.Control;
if (!_unhandledModifiers.HasFlag(KeyModifiers.Shift) && (e.Key == Key.LeftShift || e.Key == Key.RightShift))
_unhandledModifiers |= KeyModifiers.Shift;
}
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
_unhandledModifiers = KeyModifiers.None;
} }
protected override void OnClosing(WindowClosingEventArgs e) protected override void OnClosing(WindowClosingEventArgs e)
@ -178,5 +204,7 @@ namespace SourceGit.Views
e.Handled = true; e.Handled = true;
} }
private KeyModifiers _unhandledModifiers = KeyModifiers.None;
} }
} }

View file

@ -57,7 +57,7 @@
<TabItem.Header> <TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.General}"/> <TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.General}"/>
</TabItem.Header> </TabItem.Header>
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32" ColumnDefinitions="Auto,*"> <Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0" <TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.Locale}" Text="{DynamicResource Text.Preference.General.Locale}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
@ -71,25 +71,10 @@
SelectedItem="{Binding Locale, Mode=TwoWay, Converter={x:Static c:StringConverters.ToLocale}}"/> SelectedItem="{Binding Locale, Mode=TwoWay, Converter={x:Static c:StringConverters.ToLocale}}"/>
<TextBlock Grid.Row="1" Grid.Column="0" <TextBlock Grid.Row="1" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.AvatarServer}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<ComboBox Grid.Row="1" Grid.Column="1"
MinHeight="28"
Padding="8,0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding AvatarServer, Mode=TwoWay}">
<ComboBox.Items>
<sys:String>https://www.gravatar.com/avatar/</sys:String>
<sys:String>https://cravatar.cn/avatar/</sys:String>
</ComboBox.Items>
</ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.VisibleDiffContextLines}" Text="{DynamicResource Text.Preference.General.VisibleDiffContextLines}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<NumericUpDown Grid.Row="2" Grid.Column="1" <NumericUpDown Grid.Row="1" Grid.Column="1"
Minimum="4" Maximum="10000" Increment="1" Minimum="4" Maximum="10000" Increment="1"
Height="28" Height="28"
Padding="4" Padding="4"
@ -98,11 +83,11 @@
CornerRadius="3" CornerRadius="3"
Value="{Binding DiffViewVisualLineNumbers, Mode=TwoWay}"/> Value="{Binding DiffViewVisualLineNumbers, Mode=TwoWay}"/>
<TextBlock Grid.Row="3" Grid.Column="0" <TextBlock Grid.Row="2" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.SubjectGuideLength}" Text="{DynamicResource Text.Preference.General.SubjectGuideLength}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<NumericUpDown Grid.Row="3" Grid.Column="1" <NumericUpDown Grid.Row="2" Grid.Column="1"
Minimum="50" Maximum="1000" Increment="1" Minimum="50" Maximum="1000" Increment="1"
Height="28" Height="28"
Padding="4" Padding="4"
@ -111,11 +96,11 @@
CornerRadius="3" CornerRadius="3"
Value="{Binding SubjectGuideLength, Mode=TwoWay}"/> Value="{Binding SubjectGuideLength, Mode=TwoWay}"/>
<TextBlock Grid.Row="4" Grid.Column="0" <TextBlock Grid.Row="3" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.MaxHistoryCommits}" Text="{DynamicResource Text.Preference.General.MaxHistoryCommits}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<Grid Grid.Row="4" Grid.Column="1" ColumnDefinitions="*,64"> <Grid Grid.Row="3" Grid.Column="1" ColumnDefinitions="*,64">
<Slider Grid.Column="0" <Slider Grid.Column="0"
Minimum="20000" Maximum="100000" Minimum="20000" Maximum="100000"
TickPlacement="BottomRight" TickFrequency="5000" TickPlacement="BottomRight" TickFrequency="5000"
@ -130,16 +115,16 @@
Text="{Binding MaxHistoryCommits}"/> Text="{Binding MaxHistoryCommits}"/>
</Grid> </Grid>
<CheckBox Grid.Row="5" Grid.Column="1" <CheckBox Grid.Row="4" Grid.Column="1"
Content="{DynamicResource Text.Preference.General.RestoreTabs}" Content="{DynamicResource Text.Preference.General.RestoreTabs}"
IsChecked="{Binding RestoreTabs, Mode=TwoWay}"/> IsChecked="{Binding RestoreTabs, Mode=TwoWay}"/>
<CheckBox Grid.Row="6" Grid.Column="1" <CheckBox Grid.Row="5" Grid.Column="1"
Height="32" Height="32"
Content="{DynamicResource Text.Preference.General.UseFixedTabWidth}" Content="{DynamicResource Text.Preference.General.UseFixedTabWidth}"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFixedTabWidth, Mode=TwoWay}"/> IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFixedTabWidth, Mode=TwoWay}"/>
<CheckBox Grid.Row="7" Grid.Column="1" <CheckBox Grid.Row="6" Grid.Column="1"
Height="32" Height="32"
Content="{DynamicResource Text.Preference.General.Check4UpdatesOnStartup}" Content="{DynamicResource Text.Preference.General.Check4UpdatesOnStartup}"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=Check4UpdatesOnStartup, Mode=TwoWay}"/> IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=Check4UpdatesOnStartup, Mode=TwoWay}"/>

View file

@ -91,15 +91,14 @@
<Grid Classes="view_mode" ColumnDefinitions="32,*,Auto"> <Grid Classes="view_mode" ColumnDefinitions="32,*,Auto">
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Changes}"/> <Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Changes}"/>
<TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.WorkingCopy}"/> <TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.WorkingCopy}"/>
<Border Grid.Column="2" <v:CounterPresenter Grid.Column="2"
Margin="6,0" Margin="6,0"
Height="18"
CornerRadius="9"
VerticalAlignment="Center" VerticalAlignment="Center"
Background="{DynamicResource Brush.Badge}" Count="{Binding LocalChangesCount}"
IsVisible="{Binding WorkingCopyChangesCount, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"> FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
<TextBlock Classes="primary" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding WorkingCopyChangesCount}" Foreground="{DynamicResource Brush.BadgeFG}"/> FontSize="10"
</Border> Foreground="{DynamicResource Brush.BadgeFG}"
Background="{DynamicResource Brush.Badge}"/>
</Grid> </Grid>
</ListBoxItem> </ListBoxItem>
@ -107,15 +106,14 @@
<Grid Classes="view_mode" ColumnDefinitions="32,*,Auto"> <Grid Classes="view_mode" ColumnDefinitions="32,*,Auto">
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Stashes}"/> <Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Stashes}"/>
<TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.Stashes}"/> <TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.Stashes}"/>
<Border Grid.Column="2" <v:CounterPresenter Grid.Column="2"
Margin="6,0" Margin="6,0"
Height="18"
CornerRadius="9"
VerticalAlignment="Center" VerticalAlignment="Center"
Background="{DynamicResource Brush.Badge}" Count="{Binding StashesCount}"
IsVisible="{Binding StashesCount, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"> FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
<TextBlock Classes="primary" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding StashesCount}" Foreground="{DynamicResource Brush.BadgeFG}"/> FontSize="10"
</Border> Foreground="{DynamicResource Brush.BadgeFG}"
Background="{DynamicResource Brush.Badge}"/>
</Grid> </Grid>
</ListBoxItem> </ListBoxItem>
</ListBox> </ListBox>
@ -670,7 +668,8 @@
<ContentControl Grid.Row="2" Content="{Binding SelectedView}"> <ContentControl Grid.Row="2" Content="{Binding SelectedView}">
<ContentControl.DataTemplates> <ContentControl.DataTemplates>
<DataTemplate DataType="vm:Histories"> <DataTemplate DataType="vm:Histories">
<v:Histories CurrentBranch="{Binding $parent[v:Repository].((vm:Repository)DataContext).CurrentBranch}" <v:Histories CurrentBranch="{Binding Repo.CurrentBranch}"
IssueTrackerRules="{Binding Repo.Settings.IssueTrackerRules}"
NavigationId="{Binding NavigationId}"/> NavigationId="{Binding NavigationId}"/>
</DataTemplate> </DataTemplate>

View file

@ -1,12 +1,109 @@
using System; using System;
using System.Globalization;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media;
namespace SourceGit.Views namespace SourceGit.Views
{ {
public class CounterPresenter : Control
{
public static readonly StyledProperty<int> CountProperty =
AvaloniaProperty.Register<CounterPresenter, int>(nameof(Count), 0);
public int Count
{
get => GetValue(CountProperty);
set => SetValue(CountProperty, value);
}
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<CounterPresenter>();
public FontFamily FontFamily
{
get => GetValue(FontFamilyProperty);
set => SetValue(FontFamilyProperty, value);
}
public static readonly StyledProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<CounterPresenter>();
public double FontSize
{
get => GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
public static readonly StyledProperty<IBrush> ForegroundProperty =
AvaloniaProperty.Register<CounterPresenter, IBrush>(nameof(Foreground), Brushes.White);
public IBrush Foreground
{
get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value);
}
public static readonly StyledProperty<IBrush> BackgroundProperty =
AvaloniaProperty.Register<CounterPresenter, IBrush>(nameof(Background), Brushes.White);
public IBrush Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
static CounterPresenter()
{
AffectsMeasure<CounterPresenter>(
FontSizeProperty,
FontFamilyProperty,
ForegroundProperty,
CountProperty);
AffectsRender<CounterPresenter>(
ForegroundProperty,
BackgroundProperty,
CountProperty);
}
public override void Render(DrawingContext context)
{
base.Render(context);
if (_label != null)
{
context.DrawRectangle(Background, null, new RoundedRect(new Rect(0, 0, _label.Width + 18, 18), new CornerRadius(9)));
context.DrawText(_label, new Point(9, 9 - _label.Height * 0.5));
}
}
protected override Size MeasureOverride(Size availableSize)
{
if (Count > 0)
{
_label = new FormattedText(
Count.ToString(),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily),
FontSize,
Foreground);
}
else
{
_label = null;
}
return _label != null ? new Size(_label.Width + 18, 18) : new Size(0, 0);
}
private FormattedText _label = null;
}
public partial class Repository : UserControl public partial class Repository : UserControl
{ {
public Repository() public Repository()
@ -164,7 +261,7 @@ namespace SourceGit.Views
if (!IsLoaded) if (!IsLoaded)
return; return;
var leftHeight = LeftSidebarGroups.Bounds.Height - 28.0 * 5; var leftHeight = LeftSidebarGroups.Bounds.Height - 28.0 * 5 - 4;
var localBranchRows = vm.IsLocalBranchGroupExpanded ? LocalBranchTree.Rows.Count : 0; var localBranchRows = vm.IsLocalBranchGroupExpanded ? LocalBranchTree.Rows.Count : 0;
var remoteBranchRows = vm.IsRemoteGroupExpanded ? RemoteBranchTree.Rows.Count : 0; var remoteBranchRows = vm.IsRemoteGroupExpanded ? RemoteBranchTree.Rows.Count : 0;
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0; var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;

View file

@ -112,6 +112,91 @@
</Grid> </Grid>
</TabItem> </TabItem>
<TabItem>
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.CommitMessageTemplate}"/>
</TabItem.Header>
<Grid ColumnDefinitions="200,*" Height="250" Margin="0,8,0,16">
<Border Grid.Column="0"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
Background="{DynamicResource Brush.Contents}">
<Grid RowDefinitions="*,1,Auto">
<ListBox Grid.Row="0"
Background="Transparent"
ItemsSource="{Binding CommitTemplates}"
SelectedItem="{Binding SelectedCommitTemplate, Mode=TwoWay}"
SelectionMode="Single">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Padding" Value="4,2"/>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="m:CommitTemplate">
<Grid ColumnDefinitions="Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Data="{StaticResource Icons.Code}"/>
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="8,0" TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Rectangle Grid.Row="1" Height="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="{DynamicResource Brush.ToolBar}">
<Button Classes="icon_button" Command="{Binding AddCommitTemplate}">
<Path Width="14" Height="14" Data="{StaticResource Icons.Plus}"/>
</Button>
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
<Button Classes="icon_button" Command="{Binding RemoveSelectedCommitTemplate}">
<Path Width="14" Height="14" Data="{StaticResource Icons.Window.Minimize}"/>
</Button>
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
</StackPanel>
</Grid>
</Border>
<ContentControl Grid.Column="1" Margin="16,0,0,0">
<ContentControl.Content>
<Binding Path="SelectedCommitTemplate">
<Binding.TargetNullValue>
<Path Width="64" Height="64"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Empty}"/>
</Binding.TargetNullValue>
</Binding>
</ContentControl.Content>
<ContentControl.DataTemplates>
<DataTemplate DataType="m:CommitTemplate">
<StackPanel Orientation="Vertical">
<TextBlock Text="{DynamicResource Text.Configure.CommitMessageTemplate.Name}"/>
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Name, Mode=TwoWay}"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Configure.CommitMessageTemplate.Content}"/>
<v:CommitMessageTextBox Height="150" Text="{Binding Content, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
</TabItem>
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.IssueTracker}"/> <TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.IssueTracker}"/>

View file

@ -31,19 +31,47 @@
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal"> <StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Classes="icon_button" Width="32" Command="{Binding Fetch}" ToolTip.Tip="{DynamicResource Text.Fetch}"> <Button Classes="icon_button" Width="32" Click="Fetch">
<ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock Text="{DynamicResource Text.Fetch}"/>
<TextBlock Classes="small italic" Margin="0,4,0,0" Text="{DynamicResource Text.CtrlClickTip}" Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
</ToolTip.Tip>
<Path Width="14" Height="14" Data="{StaticResource Icons.Fetch}"/> <Path Width="14" Height="14" Data="{StaticResource Icons.Fetch}"/>
</Button> </Button>
<Button Classes="icon_button" Width="32" Margin="16,0,0,0" Command="{Binding Pull}" ToolTip.Tip="{DynamicResource Text.Pull}"> <Button Classes="icon_button" Width="32" Margin="16,0,0,0" Click="Pull">
<ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock Text="{DynamicResource Text.Pull}"/>
<TextBlock Classes="small italic" Margin="0,4,0,0" Text="{DynamicResource Text.CtrlClickTip}" Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
</ToolTip.Tip>
<Path Width="14" Height="14" Data="{StaticResource Icons.Pull}"/> <Path Width="14" Height="14" Data="{StaticResource Icons.Pull}"/>
</Button> </Button>
<Button Classes="icon_button" Width="32" Margin="16,0,0,0" Command="{Binding Push}" ToolTip.Tip="{DynamicResource Text.Push}"> <Button Classes="icon_button" Width="32" Margin="16,0,0,0" Click="Push">
<ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock Text="{DynamicResource Text.Push}"/>
<TextBlock Classes="small italic" Margin="0,4,0,0" Text="{DynamicResource Text.CtrlClickTip}" Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
</ToolTip.Tip>
<Path Width="14" Height="14" Data="{StaticResource Icons.Push}"/> <Path Width="14" Height="14" Data="{StaticResource Icons.Push}"/>
</Button> </Button>
<Button Classes="icon_button" Width="32" Margin="16,0,0,0" Command="{Binding StashAll}" ToolTip.Tip="{DynamicResource Text.Stash}"> <Button Classes="icon_button" Width="32" Margin="16,0,0,0" Click="StashAll">
<ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock Text="{DynamicResource Text.Stash}"/>
<TextBlock Classes="small italic" Margin="0,4,0,0" Text="{DynamicResource Text.CtrlClickTip}" Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
</ToolTip.Tip>
<Path Width="14" Height="14" Data="{StaticResource Icons.Stashes.Add}"/> <Path Width="14" Height="14" Data="{StaticResource Icons.Stashes.Add}"/>
</Button> </Button>

View file

@ -1,5 +1,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace SourceGit.Views namespace SourceGit.Views
{ {
@ -40,6 +42,34 @@ namespace SourceGit.Views
} }
} }
private void Fetch(object _, RoutedEventArgs e)
{
var launcher = this.FindAncestorOfType<Launcher>();
(DataContext as ViewModels.Repository)?.Fetch(launcher?.HasKeyModifier(KeyModifiers.Control) ?? false);
e.Handled = true;
}
private void Pull(object _, RoutedEventArgs e)
{
var launcher = this.FindAncestorOfType<Launcher>();
(DataContext as ViewModels.Repository)?.Pull(launcher?.HasKeyModifier(KeyModifiers.Control) ?? false);
e.Handled = true;
}
private void Push(object _, RoutedEventArgs e)
{
var launcher = this.FindAncestorOfType<Launcher>();
(DataContext as ViewModels.Repository)?.Push(launcher?.HasKeyModifier(KeyModifiers.Control) ?? false);
e.Handled = true;
}
private void StashAll(object _, RoutedEventArgs e)
{
var launcher = this.FindAncestorOfType<Launcher>();
(DataContext as ViewModels.Repository)?.StashAll(launcher?.HasKeyModifier(KeyModifiers.Control) ?? false);
e.Handled = true;
}
private void OpenGitFlowMenu(object sender, RoutedEventArgs e) private void OpenGitFlowMenu(object sender, RoutedEventArgs e)
{ {
if (DataContext is ViewModels.Repository repo) if (DataContext is ViewModels.Repository repo)

View file

@ -24,7 +24,7 @@
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4" IsVisible="{Binding IsCurrentHead}"> <Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4" IsVisible="{Binding IsCurrentHead}">
<TextBlock Text="HEAD" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/> <TextBlock Text="HEAD" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/>
</Border> </Border>
<TextBlock Grid.Column="3" Classes="primary" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA" /> <TextBlock Grid.Column="3" Classes="primary" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" Cursor="Hand" PointerPressed="OnPressedSHA" />
<TextBlock Grid.Column="4" Classes="primary" Text="{Binding CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/> <TextBlock Grid.Column="4" Classes="primary" Text="{Binding CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
</Grid> </Grid>

View file

@ -73,7 +73,7 @@
<DataTemplate DataType="m:RevisionSubmodule"> <DataTemplate DataType="m:RevisionSubmodule">
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Margin="8,8,8,0"> <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Margin="8,8,8,0">
<TextBlock Text="{DynamicResource Text.CommitDetail.Files.Submodule}" FontSize="18" FontWeight="Bold" HorizontalAlignment="Center" Foreground="{DynamicResource Brush.FG2}"/> <TextBlock Text="{DynamicResource Text.CommitDetail.Files.Submodule}" FontSize="18" FontWeight="Bold" HorizontalAlignment="Center" Foreground="{DynamicResource Brush.FG2}"/>
<v:CommitBaseInfo Margin="0,16,0,0" CanNavigate="False" Content="{Binding Commit}" Message="{Binding FullMessage}"/> <v:CommitBaseInfo Margin="0,16,0,0" Content="{Binding Commit}" Message="{Binding FullMessage}"/>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</ContentControl.DataTemplates> </ContentControl.DataTemplates>

View file

@ -41,8 +41,8 @@ namespace SourceGit.Views
Math.Abs(Height - old.Height) > 0.001 || Math.Abs(Height - old.Height) > 0.001 ||
StartIdx != old.StartIdx || StartIdx != old.StartIdx ||
EndIdx != old.EndIdx || EndIdx != old.EndIdx ||
Combined != Combined || Combined != old.Combined ||
IsOldSide != IsOldSide; IsOldSide != old.IsOldSide;
} }
} }
@ -92,6 +92,9 @@ namespace SourceGit.Views
var typeface = view.CreateTypeface(); var typeface = view.CreateTypeface();
foreach (var line in view.VisualLines) foreach (var line in view.VisualLines)
{ {
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber; var index = line.FirstDocumentLine.LineNumber;
if (index > lines.Count) if (index > lines.Count)
break; break;
@ -160,7 +163,7 @@ namespace SourceGit.Views
var width = textView.Bounds.Width; var width = textView.Bounds.Width;
foreach (var line in textView.VisualLines) foreach (var line in textView.VisualLines)
{ {
if (line.FirstDocumentLine == null) if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue; continue;
var index = line.FirstDocumentLine.LineNumber; var index = line.FirstDocumentLine.LineNumber;
@ -172,8 +175,47 @@ namespace SourceGit.Views
if (bg == null) if (bg == null)
continue; continue;
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop) - textView.VerticalOffset; var startY = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineTop) - textView.VerticalOffset;
drawingContext.DrawRectangle(bg, null, new Rect(0, y, width, line.Height)); var endY = line.GetTextLineVisualYPosition(line.TextLines[^1], VisualYPosition.LineBottom) - textView.VerticalOffset;
drawingContext.DrawRectangle(bg, null, new Rect(0, startY, width, endY - startY));
if (info.Highlights.Count > 0)
{
var highlightBG = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush;
var processingIdxStart = 0;
var processingIdxEnd = 0;
var nextHightlight = 0;
foreach (var tl in line.TextLines)
{
processingIdxEnd += tl.Length;
var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset;
var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y;
while (nextHightlight < info.Highlights.Count)
{
var highlight = info.Highlights[nextHightlight];
if (highlight.Start >= processingIdxEnd)
break;
var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start);
var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1);
var x = line.GetTextLineVisualXPosition(tl, start) - textView.HorizontalOffset;
var w = line.GetTextLineVisualXPosition(tl, end) - textView.HorizontalOffset - x;
var rect = new Rect(x, y, w, h);
drawingContext.DrawRectangle(highlightBG, null, rect);
if (highlight.End >= processingIdxEnd)
break;
nextHightlight++;
}
processingIdxStart = processingIdxEnd;
}
}
} }
} }
@ -217,20 +259,6 @@ namespace SourceGit.Views
v.TextRunProperties.SetForegroundBrush(_presenter.IndicatorForeground); v.TextRunProperties.SetForegroundBrush(_presenter.IndicatorForeground);
v.TextRunProperties.SetTypeface(new Typeface(_presenter.FontFamily, FontStyle.Italic)); v.TextRunProperties.SetTypeface(new Typeface(_presenter.FontFamily, FontStyle.Italic));
}); });
return;
}
if (info.Highlights.Count > 0)
{
var bg = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush;
foreach (var highlight in info.Highlights)
{
ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v =>
{
v.TextRunProperties.SetBackgroundBrush(bg);
});
}
} }
} }
@ -394,7 +422,7 @@ namespace SourceGit.Views
if (chunk == null || (!chunk.Combined && chunk.IsOldSide != IsOld)) if (chunk == null || (!chunk.Combined && chunk.IsOldSide != IsOld))
return; return;
var color = (Color)this.FindResource("SystemAccentColor"); var color = (Color)this.FindResource("SystemAccentColor")!;
var brush = new SolidColorBrush(color, 0.1); var brush = new SolidColorBrush(color, 0.1);
var pen = new Pen(color.ToUInt32()); var pen = new Pen(color.ToUInt32());
var rect = new Rect(0, chunk.Y, Bounds.Width, chunk.Height); var rect = new Rect(0, chunk.Y, Bounds.Width, chunk.Height);
@ -409,6 +437,7 @@ namespace SourceGit.Views
base.OnLoaded(e); base.OnLoaded(e);
TextArea.TextView.ContextRequested += OnTextViewContextRequested; TextArea.TextView.ContextRequested += OnTextViewContextRequested;
TextArea.TextView.PointerEntered += OnTextViewPointerEntered;
TextArea.TextView.PointerMoved += OnTextViewPointerMoved; TextArea.TextView.PointerMoved += OnTextViewPointerMoved;
TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged; TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged;
@ -420,6 +449,7 @@ namespace SourceGit.Views
base.OnUnloaded(e); base.OnUnloaded(e);
TextArea.TextView.ContextRequested -= OnTextViewContextRequested; TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
TextArea.TextView.PointerEntered -= OnTextViewPointerEntered;
TextArea.TextView.PointerMoved -= OnTextViewPointerMoved; TextArea.TextView.PointerMoved -= OnTextViewPointerMoved;
TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged; TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged;
@ -480,6 +510,12 @@ namespace SourceGit.Views
e.Handled = true; e.Handled = true;
} }
private void OnTextViewPointerEntered(object sender, PointerEventArgs e)
{
if (EnableChunkSelection && sender is TextView view)
UpdateSelectedChunk(e.GetPosition(view).Y + view.VerticalOffset);
}
private void OnTextViewPointerMoved(object sender, PointerEventArgs e) private void OnTextViewPointerMoved(object sender, PointerEventArgs e)
{ {
if (EnableChunkSelection && sender is TextView view) if (EnableChunkSelection && sender is TextView view)
@ -675,12 +711,7 @@ namespace SourceGit.Views
var firstLineIdx = view.VisualLines[0].FirstDocumentLine.LineNumber - 1; var firstLineIdx = view.VisualLines[0].FirstDocumentLine.LineNumber - 1;
var lastLineIdx = view.VisualLines[^1].FirstDocumentLine.LineNumber - 1; var lastLineIdx = view.VisualLines[^1].FirstDocumentLine.LineNumber - 1;
if (endIdx < firstLineIdx) if (endIdx < firstLineIdx || startIdx > lastLineIdx)
{
TrySetChunk(null);
return;
}
else if (startIdx > lastLineIdx)
{ {
TrySetChunk(null); TrySetChunk(null);
return; return;
@ -711,6 +742,9 @@ namespace SourceGit.Views
var lineIdx = -1; var lineIdx = -1;
foreach (var line in view.VisualLines) foreach (var line in view.VisualLines)
{ {
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber; var index = line.FirstDocumentLine.LineNumber;
if (index > diff.Lines.Count) if (index > diff.Lines.Count)
break; break;
@ -853,12 +887,7 @@ namespace SourceGit.Views
var firstLineIdx = view.VisualLines[0].FirstDocumentLine.LineNumber - 1; var firstLineIdx = view.VisualLines[0].FirstDocumentLine.LineNumber - 1;
var lastLineIdx = view.VisualLines[^1].FirstDocumentLine.LineNumber - 1; var lastLineIdx = view.VisualLines[^1].FirstDocumentLine.LineNumber - 1;
if (endIdx < firstLineIdx) if (endIdx < firstLineIdx || startIdx > lastLineIdx)
{
TrySetChunk(null);
return;
}
else if (startIdx > lastLineIdx)
{ {
TrySetChunk(null); TrySetChunk(null);
return; return;
@ -895,6 +924,9 @@ namespace SourceGit.Views
var lineIdx = -1; var lineIdx = -1;
foreach (var line in view.VisualLines) foreach (var line in view.VisualLines)
{ {
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber; var index = line.FirstDocumentLine.LineNumber;
if (index > lines.Count) if (index > lines.Count)
break; break;
@ -1129,7 +1161,7 @@ namespace SourceGit.Views
SetCurrentValue(SelectedChunkProperty, null); SetCurrentValue(SelectedChunkProperty, null);
} }
private void OnStageChunk(object sender, RoutedEventArgs e) private void OnStageChunk(object _1, RoutedEventArgs _2)
{ {
var chunk = SelectedChunk; var chunk = SelectedChunk;
if (chunk == null) if (chunk == null)
@ -1187,7 +1219,7 @@ namespace SourceGit.Views
repo.SetWatcherEnabled(true); repo.SetWatcherEnabled(true);
} }
private void OnUnstageChunk(object sender, RoutedEventArgs e) private void OnUnstageChunk(object _1, RoutedEventArgs _2)
{ {
var chunk = SelectedChunk; var chunk = SelectedChunk;
if (chunk == null) if (chunk == null)
@ -1241,7 +1273,7 @@ namespace SourceGit.Views
repo.SetWatcherEnabled(true); repo.SetWatcherEnabled(true);
} }
private void OnDiscardChunk(object sender, RoutedEventArgs e) private void OnDiscardChunk(object _1, RoutedEventArgs _2)
{ {
var chunk = SelectedChunk; var chunk = SelectedChunk;
if (chunk == null) if (chunk == null)

View file

@ -175,11 +175,13 @@
<!-- Commit Options --> <!-- Commit Options -->
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto"> <Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto">
<Button Grid.Column="0" <Button Grid.Column="0"
Classes="icon_button" Classes="no_border"
Width="14" Height="14" Margin="4,0,0,0" Padding="0"
Click="OnOpenCommitMessagePicker" Click="OnOpenCommitMessagePicker">
ToolTip.Tip="{DynamicResource Text.WorkingCopy.MessageHistories}"> <Grid ColumnDefinitions="Auto,*">
<Path Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.List}"/> <Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Menu}"/>
<TextBlock Grid.Column="1" Margin="8,0,0,0" Text="{DynamicResource Text.WorkingCopy.CommitMessageHelper}"/>
</Grid>
</Button> </Button>
<CheckBox Grid.Column="1" <CheckBox Grid.Column="1"
@ -187,8 +189,7 @@
Margin="12,0,0,0" Margin="12,0,0,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
IsChecked="{Binding AutoStageBeforeCommit, Mode=TwoWay}" IsChecked="{Binding AutoStageBeforeCommit, Mode=TwoWay}"
Content="{DynamicResource Text.WorkingCopy.AutoStage}" Content="{DynamicResource Text.WorkingCopy.AutoStage}"/>
ToolTip.Tip="{DynamicResource Text.WorkingCopy.AutoStage.Tip}"/>
<CheckBox Grid.Column="2" <CheckBox Grid.Column="2"
Height="24" Height="24"