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
on:
push:
branches:
- develop
branches: [develop]
pull_request:
branches: [develop]
workflow_dispatch:
workflow_call:
jobs:
build-windows:
name: Build Windows x64
runs-on: windows-2019
build:
strategy:
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:
- 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 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
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Configure arm64 packages
if: ${{ matrix.runtime == 'linux-arm64' }}
run: |
sudo dpkg --add-architecture arm64
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 mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list
- name: Install cross-compiling dependencies
if: ${{ matrix.runtime == 'linux-arm64' }}
run: |
sudo apt-get update
sudo apt-get install clang llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
- name: Build
run: dotnet build -c Release
- name: Publish
run: dotnet publish src/SourceGit.csproj -c Release -o publish -r linux-arm64
- name: Rename Executable File
run: dotnet publish src/SourceGit.csproj -c Release -o publish -r ${{ matrix.runtime }}
- name: Rename executable file
if: ${{ startsWith(matrix.runtime, 'linux-') }}
run: mv publish/SourceGit publish/sourcegit
- name: Packing Program
run: tar -cvf sourcegit.linux-arm64.tar -C publish/ .
- name: Upload Artifact
- name: Tar artifact
if: ${{ startsWith(matrix.runtime, 'linux-') }}
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
with:
name: sourcegit.linux-arm64
path: sourcegit.linux-arm64.tar
name: sourcegit.${{ matrix.runtime }}
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
* Issue Link
> [!WARNING]
> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**.
## How to Use
@ -45,11 +46,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.
| OS | PATH |
|---------|-------------------------------------------------|
| Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` |
| Linux | `${HOME}/.config/SourceGit` |
| macOS | `${HOME}/Library/Application Support/SourceGit` |
| OS | PATH |
|---------|-----------------------------------------------------|
| Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` |
| Linux | `${HOME}/.config/SourceGit` or `${HOME}/.sourcegit` |
| macOS | `${HOME}/Library/Application Support/SourceGit` |
> [!TIP]
> You can open the app data dir from the main menu.
For **Windows** users:
@ -58,7 +62,8 @@ For **Windows** users:
```shell
winget install SourceGit
```
> `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.
> [!NOTE]
> `winget` will install this software as a commandline tool. You need run `SourceGit` from console or `Win+R` at the first time. Then you can add it to the taskbar.
* You can install the latest stable by `scoope` with follow commands:
```shell
scoop bucket add extras
@ -84,17 +89,27 @@ For **Linux** users:
This app supports open repository in external tools listed in the table below.
| Tool | Windows | macOS | Linux | Environment Variable |
|-------------------------------|---------|-------|-------|----------------------|
| Visual Studio Code | YES | YES | YES | VSCODE_PATH |
| Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS_PATH |
| VSCodium | YES | YES | YES | VSCODIUM_PATH |
| JetBrains Fleet | YES | YES | YES | FLEET_PATH |
| Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH |
| Tool | Windows | macOS | Linux | KEY IN `external_editors.json` |
|-------------------------------|---------|-------|-------|--------------------------------|
| Visual Studio Code | YES | YES | YES | VSCODE |
| Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS |
| VSCodium | YES | YES | YES | VSCODIUM |
| JetBrains Fleet | YES | YES | YES | FLEET |
| 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.
* Installing `JetBrains Toolbox` will help this app to find other JetBrains tools installed on your device.
* On macOS, you may need to use `launchctl setenv` to make sure the app can read these environment variables.
> [!NOTE]
> This app will try to find those tools based on some pre-defined or expected locations automatically. If you are using one portable version of these tools, it will not be detected by this app.
> To solve this problem you can add a file named `external_editors.json` in app data dir and provide the path directly. For example:
```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

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}"
EndProject
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
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "resources", "resources", "{FD384607-ED99-47B7-AF31-FB245841BC92}"
EndProject
@ -19,6 +14,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{67B6D05F-A000-40BA-ADB4-C9065F880D7B}"
ProjectSection(SolutionItems) = preProject
.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
EndProject
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
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "appimage", "appimage", "{5D125DD9-B48A-491F-B2FB-D7830D74C4DC}"
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.png = build\resources\appimage\sourcegit.png
EndProjectSection
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -114,6 +115,7 @@ Global
{9BA0B044-0CC9-46F8-B551-204F149BF45D} = {FD384607-ED99-47B7-AF31-FB245841BC92}
{7802CD7A-591B-4EDD-96F8-9BF3F61692E4} = {9BA0B044-0CC9-46F8-B551-204F149BF45D}
{5D125DD9-B48A-491F-B2FB-D7830D74C4DC} = {FD384607-ED99-47B7-AF31-FB245841BC92}
{C54D4001-9940-477C-A0B6-E795ED0A3209} = {773082AC-D9C8-4186-8521-4B6A7BEE6158}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
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"?>
<component type="desktop-application">
<id>com.sourcegit-scm.SourceGit</id>
<id>com.sourcegit_scm.SourceGit</id>
<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<name>SourceGit</name>
@ -8,8 +8,9 @@
<description>
<p>Open-source GUI client for git users</p>
</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>
<id>com.sourcegit-scm.SourceGit.desktop</id>
<id>com.sourcegit_scm.SourceGit.desktop</id>
</provides>
</component>
</component>

View file

@ -1,5 +1,5 @@
Package: sourcegit
Version: 8.18
Version: 8.23
Priority: optional
Depends: libx11-6, libice6, libsm6
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
%install
mkdir -p $RPM_BUILD_ROOT/opt/sourcegit
mkdir -p $RPM_BUILD_ROOT/usr/share/applications
mkdir -p $RPM_BUILD_ROOT/usr/share/icons
cp -r ../../_common/applications $RPM_BUILD_ROOT/usr/share/
cp -r ../../_common/icons $RPM_BUILD_ROOT/usr/share/
cp -f ../../../SourceGit/* $RPM_BUILD_ROOT/opt/sourcegit/
chmod 755 -R $RPM_BUILD_ROOT/opt/sourcegit
chmod 755 $RPM_BUILD_ROOT/usr/share/applications/sourcegit.desktop
mkdir -p %{buildroot}/opt/sourcegit
mkdir -p %{buildroot}/%{_bindir}
mkdir -p %{buildroot}/usr/share/applications
mkdir -p %{buildroot}/usr/share/icons
cp -f ../../../SourceGit/* %{buildroot}/opt/sourcegit/
ln -sf ../../opt/sourcegit/sourcegit %{buildroot}/%{_bindir}
cp -r ../../_common/applications %{buildroot}/%{_datadir}
cp -r ../../_common/icons %{buildroot}/%{_datadir}
chmod 755 -R %{buildroot}/opt/sourcegit
chmod 755 %{buildroot}/%{_datadir}/applications/sourcegit.desktop
%files
/opt/sourcegit
/usr/share
%post
ln -s /opt/sourcegit/sourcegit /usr/bin/sourcegit
%postun
rm -f /usr/bin/sourcegit
%dir /opt/sourcegit/
/opt/sourcegit/*
/usr/share/applications/sourcegit.desktop
/usr/share/icons/*
%{_bindir}/sourcegit
%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),
]
)]
[JsonSerializable(typeof(Models.ExternalToolPaths))]
[JsonSerializable(typeof(Models.InteractiveRebaseJobCollection))]
[JsonSerializable(typeof(Models.JetBrainsState))]
[JsonSerializable(typeof(Models.ThemeOverrides))]

View file

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

View file

@ -44,6 +44,8 @@ namespace SourceGit
[STAThread]
public static void Main(string[] args)
{
Native.OS.SetupDataDir();
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
{
LogException(e.ExceptionObject as Exception);
@ -107,6 +109,11 @@ namespace SourceGit
dialog.ShowDialog(toplevel);
});
public static readonly SimpleCommand OpenAppDataDirCommand = new SimpleCommand(() =>
{
Native.OS.OpenInFileManager(Native.OS.DataDir);
});
public static readonly SimpleCommand OpenAboutCommand = new SimpleCommand(() =>
{
var toplevel = GetTopLevel() as Window;
@ -521,6 +528,8 @@ namespace SourceGit
private void TryLaunchedAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
{
Native.OS.SetupEnternalTools();
Models.AvatarManager.Instance.Start();
Models.AutoFetchManager.Instance.Start();
string startupRepo = null;
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 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();
File.WriteAllText(file, message);
@ -13,8 +13,6 @@ namespace SourceGit.Commands
Context = repo;
TraitErrorAsOutput = true;
Args = $"commit --file=\"{file}\"";
if (autoStage)
Args += " --all";
if (amend)
Args += " --amend --no-edit";
if (allowEmpty)

View file

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace SourceGit.Commands
{
@ -26,7 +23,7 @@ namespace SourceGit.Commands
Args += remote;
AutoFetch.MarkFetched(repo);
Models.AutoFetchManager.Instance.MarkFetched(repo);
}
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;
}
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);
}
public static partial class AvatarManager
public partial class AvatarManager
{
public static string SelectedServer
public static AvatarManager Instance
{
get;
set;
} = "https://www.gravatar.com/avatar/";
get
{
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");
if (!Directory.Exists(_storePath))
@ -62,7 +78,7 @@ namespace SourceGit.Models
var matchGithubUser = REG_GITHUB_USER_EMAIL().Match(email);
var url = matchGithubUser.Success ?
$"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 img = null as Bitmap;
@ -105,20 +121,22 @@ namespace SourceGit.Models
NotifyResourceChanged(email);
});
}
// ReSharper disable once FunctionNeverReturns
});
}
public static void Subscribe(IAvatarHost host)
public void Subscribe(IAvatarHost host)
{
_avatars.Add(host);
}
public static void Unsubscribe(IAvatarHost host)
public void Unsubscribe(IAvatarHost host)
{
_avatars.Remove(host);
}
public static Bitmap Request(string email, bool forceRefetch)
public Bitmap Request(string email, bool forceRefetch)
{
if (forceRefetch)
{
@ -167,7 +185,7 @@ namespace SourceGit.Models
return null;
}
private static string GetEmailHash(string email)
private string GetEmailHash(string email)
{
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered));
@ -177,21 +195,12 @@ namespace SourceGit.Models
return builder.ToString();
}
private static void NotifyResourceChanged(string email)
private void NotifyResourceChanged(string email)
{
foreach (var avatar in _avatars)
{
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 int Start { get; set; }
public int Count { get; set; }
public TextInlineRange(int p, int n) { Start = p; Count = n; }
public int End { get; set; }
public TextInlineRange(int p, int n) { Start = p; End = p + n - 1; }
}
public class TextDiffLine

View file

@ -79,6 +79,12 @@ namespace SourceGit.Models
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 List<ExternalTool> Founded
@ -87,42 +93,60 @@ namespace SourceGit.Models
private set;
} = new List<ExternalTool>();
public void TryAdd(string name, string icon, string args, string env, Func<string> finder)
public ExternalToolsFinder()
{
var path = Environment.GetEnvironmentVariable(env);
if (string.IsNullOrEmpty(path) || !File.Exists(path))
var customPathsConfig = Path.Combine(Native.OS.DataDir, "external_editors.json");
try
{
path = finder();
if (string.IsNullOrEmpty(path) || !File.Exists(path))
return;
if (File.Exists(customPathsConfig))
_customPaths = JsonSerializer.Deserialize(File.ReadAllText(customPathsConfig), JsonCodeGen.Default.ExternalToolPaths);
}
catch
{
// Ignore
}
Founded.Add(new ExternalTool(name, icon, path, args));
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));
}
}
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)
{
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)
{
TryAdd("VSCodium", "codium", "\"{0}\"", "VSCODIUM_PATH", platformFinder);
TryAdd("VSCodium", "codium", "\"{0}\"", "VSCODIUM", 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)
{
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)
@ -146,5 +170,7 @@ namespace SourceGit.Models
}
}
}
private ExternalToolPaths _customPaths = null;
}
}

View file

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

View file

@ -58,10 +58,12 @@ namespace SourceGit.Models
if (URL.StartsWith("http", StringComparison.Ordinal))
{
if (URL.EndsWith(".git"))
url = URL.Substring(0, URL.Length - 4);
// Try to remove the user before host and `.git` extension.
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
url = URL;
url = $"{uri.Scheme}://{uri.Host}{uri.LocalPath}";
return true;
}

View file

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

View file

@ -1,24 +1,102 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Styling;
using AvaloniaEdit;
using AvaloniaEdit.TextMate;
using TextMateSharp.Grammars;
using TextMateSharp.Internal.Grammars.Reader;
using TextMateSharp.Internal.Types;
using TextMateSharp.Registry;
using TextMateSharp.Themes;
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 TextMate.Installation CreateForEditor(TextEditor editor)
{
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)
@ -26,26 +104,18 @@ namespace SourceGit.Models
if (installation == null)
return;
if (installation.RegistryOptions is RegistryOptions reg)
if (installation.RegistryOptions is RegistryOptionsWrapper reg)
{
if (Application.Current?.ActualThemeVariant == ThemeVariant.Dark)
installation.SetTheme(reg.LoadTheme(ThemeName.DarkPlus));
else
installation.SetTheme(reg.LoadTheme(ThemeName.LightPlus));
var isDark = Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
installation.SetTheme(reg.LoadTheme(isDark ? ThemeName.DarkPlus : ThemeName.LightPlus));
}
}
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);
if (ext == ".h")
ext = ".cpp";
else if (ext == ".resx" || ext == ".plist")
ext = ".xml";
installation.SetGrammar(reg.GetScopeByExtension(ext));
installation.SetGrammar(reg.GetScopeByFileName(filePath));
GC.Collect();
}
}

View file

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

View file

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

View file

@ -21,9 +21,9 @@ namespace SourceGit.Native
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 List<Models.ExternalTool> ExternalTools { get; set; } = new List<Models.ExternalTool>();
public static List<Models.ExternalTool> ExternalTools { get; set; } = [];
static OS()
{
@ -70,10 +70,19 @@ namespace SourceGit.Native
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))
Directory.CreateDirectory(DataDir);
_backend.SetupApp(builder);
}
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.Clean">M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z</StreamGeometry>
<StreamGeometry x:Key="Icons.Clone">M1280 704c0 141-115 256-256 256H288C129 960 0 831 0 672c0-126 80-232 192-272A327 327 0 01192 384c0-177 143-320 320-320 119 0 222 64 277 160C820 204 857 192 896 192c106 0 192 86 192 192 0 24-5 48-13 69C1192 477 1280 580 1280 704zm-493-128H656V352c0-18-14-32-32-32h-96c-18 0-32 14-32 32v224h-131c-29 0-43 34-23 55l211 211c12 12 33 12 45 0l211-211c20-20 6-55-23-55z</StreamGeometry>
<StreamGeometry x:Key="Icons.Code">M853 102H171C133 102 102 133 102 171v683C102 891 133 922 171 922h683C891 922 922 891 922 853V171C922 133 891 102 853 102zM390 600l-48 48L205 512l137-137 48 48L301 512l88 88zM465 819l-66-18L559 205l66 18L465 819zm218-171L634 600 723 512l-88-88 48-48L819 512 683 649z</StreamGeometry>
<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.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.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.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.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>
@ -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.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.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.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>

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.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.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.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.AddWorktree" xml:space="preserve">Worktree hinzufügen</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.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.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.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>
@ -41,12 +41,12 @@
<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.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.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.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.CompareWithHead" xml:space="preserve">Mit HEAD 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.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.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.DeleteBranch" xml:space="preserve">Branch löschen</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.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.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.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>
@ -240,7 +241,7 @@
<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.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.FeaturePrefix" xml:space="preserve">Feature-Prefix:</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.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.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.ReleasePrefix" xml:space="preserve">Release-Prefix:</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.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.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.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.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>
@ -314,9 +315,9 @@
<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.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.TextEditor" xml:space="preserve">TEXT EDITOR</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.GotoNextMatch" xml:space="preserve">Suche nächste Übereinstimmung</x:String>
<x:String x:Key="Text.Hotkeys.TextEditor.GotoPrevMatch" xml:space="preserve">Suche vorherige Übereinstimmung</x:String>
@ -334,8 +335,8 @@
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">Interaktiver Rebase</x:String>
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">Ziel Branch:</x:String>
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">Auf:</x:String>
<x:String x:Key="Text.InteractiveRebase.MoveUp" xml:space="preserve">Hoch schieben</x:String>
<x:String x:Key="Text.InteractiveRebase.MoveDown" xml:space="preserve">Runter schieben</x:String>
<x:String x:Key="Text.InteractiveRebase.MoveUp" xml:space="preserve">Hochschieben</x:String>
<x:String x:Key="Text.InteractiveRebase.MoveDown" xml:space="preserve">Runterschieben</x:String>
<x:String x:Key="Text.Launcher" xml:space="preserve">Source Git</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">FEHLER</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">INFO</x:String>
@ -347,14 +348,15 @@
<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.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.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.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.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.CloseRight" xml:space="preserve">Schließe Rechte 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">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.Welcome.Title" xml:space="preserve">Repositories</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.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.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.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.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.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.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.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.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.VisibleDiffContextLines" xml:space="preserve">Sichtbare Vergleichskontextzeilen</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.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.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.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.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.PruneRemote" xml:space="preserve">Remote löschen</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.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.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.By" xml:space="preserve">Suche über</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.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.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.Submodules" xml:space="preserve">SUBMODULE</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.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.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.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.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.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.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.Staged" xml:space="preserve">GESTAGED</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.Bytes" xml:space="preserve">Bytes</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.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>
@ -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.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.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.Placeholder" xml:space="preserve">Email address</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.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.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.DeleteBranch" xml:space="preserve">Delete 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.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.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.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.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.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.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>
@ -344,6 +350,7 @@
<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.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.OpenWith" xml:space="preserve">Open With...</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.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.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.Locale" xml:space="preserve">Language</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.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.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.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.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.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.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.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.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.Unstage" xml:space="preserve">UNSTAGE</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.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.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.Worktree" xml:space="preserve">WORKTREE</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.Bytes" xml:space="preserve">Bytes</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.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.Tree" xml:space="preserve">Mostrar como Árvore de Sistema de Arquivos</x:String>
<x:String x:Key="Text.Checkout" xml:space="preserve">Checar Branch</x:String>
<x:String x:Key="Text.Checkout.Commit" xml:space="preserve">Checar Commit</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">Checkout Branch</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.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.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.DoNothing" xml:space="preserve">Não Fazer 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.DoNothing" xml:space="preserve">Nada</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.Commit" xml:space="preserve">Commit:</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.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.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.Reset" xml:space="preserve">Resetar ${0}$ até Aqui</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.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.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.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.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>
@ -156,6 +171,7 @@
<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.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.DeleteBranch" xml:space="preserve">Excluir 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.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.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.OpenWith" xml:space="preserve">Abrir Com...</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.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.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.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.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.Locale" xml:space="preserve">Idioma</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.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.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.Submodules" xml:space="preserve">SUBMÓDULOS</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.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.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.AddSubFolder" xml:space="preserve">Criar Subgrupo</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.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.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.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.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.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.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.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.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.Unstage" xml:space="preserve">DESSTAGEAR</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.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.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.Worktree" xml:space="preserve">WORKTREE</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.Bytes" 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.Grid" 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.MessagePlaceholder" 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.Placeholder" xml:space="preserve">邮箱地址</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.Annotated" 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.DeleteBranch" 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.NotConfigured" xml:space="preserve">GIT尚未配置。请打开【偏好设置】配置GIT路径。</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.OpenWith" 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.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.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.Locale" 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.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.AutoStage" xml:space="preserve">自动暂存(--all)</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">提交前自动将修改过和删除的文件加入暂存区,但新增文件需要手动添加。</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage" 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.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.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.HasCommitHistories" 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.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.Unstage" 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.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.UseCommitTemplate" xml:space="preserve">模板:${0}$</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.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.Bytes" 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.Grid" 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.MessagePlaceholder" 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.Placeholder" xml:space="preserve">郵箱地址</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.Annotated" 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.DeleteBranch" 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.NotConfigured" xml:space="preserve">GIT尚未配置。請開啟【偏好設定】配置GIT路徑。</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.OpenWith" 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.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.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.Locale" 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.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.AutoStage" xml:space="preserve">自動暫存(--all)</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">提交前自動將修改過和刪除的檔案加入暫存區,但新增檔案需要手動添加。</x:String>
<x:String x:Key="Text.WorkingCopy.AutoStage" 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.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.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.HasCommitHistories" 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.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.Unstage" 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.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.UseCommitTemplate" xml:space="preserve">範本:${0}$</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.CopyPath" xml:space="preserve">拷贝工作樹路徑</x:String>

View file

@ -251,8 +251,12 @@
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<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}"/>
</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">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
@ -277,12 +281,9 @@
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
<Style Selector="TextBlock.issue_link">
<Style Selector="Run.issue_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 Selector="SelectableTextBlock">
@ -455,6 +456,21 @@
<Setter Property="Fill" Value="{DynamicResource Brush.FG2}"/>
</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">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border2}"/>
@ -753,148 +769,137 @@
</Style>
<Style Selector="MenuItem">
<Style.Resources>
<ControlTheme x:Key="{x:Type MenuItem}" TargetType="MenuItem">
<Setter Property="Height" Value="28"/>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}" />
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Border Name="PART_LayoutRoot"
Padding="0,0,4,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIcon" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGT" />
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemChevron" />
</Grid.ColumnDefinitions>
<Setter Property="Height" Value="28"/>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}" />
<Border Grid.Column="0" Width="28">
<ContentControl x:Name="PART_IconPresenter"
Content="{TemplateBinding Icon}"
IsVisible="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Border Name="PART_LayoutRoot"
Padding="0,0,4,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" SharedSizeGroup="MenuItemIcon" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGT" />
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemChevron" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="1"
Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
RecognizesAccessKey="False"/>
<TextBlock x:Name="PART_InputGestureText"
Grid.Column="2"
Classes="CaptionTextBlockStyle"
Margin="{DynamicResource MenuInputGestureTextMargin}"
Text="{TemplateBinding InputGesture, Converter={StaticResource KeyGestureConverter}}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForeground}"
FontSize="11"/>
<Path Name="PART_ChevronPath"
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"
Fill="{DynamicResource Brush.FG2}"
Margin="8,0,0,0"
VerticalAlignment="Center"
Grid.Column="3" />
</Grid>
<ContentControl Grid.Column="0"
x:Name="PART_IconPresenter"
Content="{TemplateBinding Icon}"
IsVisible="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<ContentPresenter Grid.Column="1"
x:Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
Foreground="{TemplateBinding Foreground}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
RecognizesAccessKey="False"/>
<TextBlock x:Name="PART_InputGestureText"
Grid.Column="2"
Classes="CaptionTextBlockStyle"
Margin="{DynamicResource MenuInputGestureTextMargin}"
Text="{TemplateBinding InputGesture, Converter={StaticResource KeyGestureConverter}}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForeground}"
FontSize="11"/>
<Path Name="PART_ChevronPath"
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"
Fill="{DynamicResource Brush.FG2}"
Margin="8,0,0,0"
VerticalAlignment="Center"
Grid.Column="3" />
</Grid>
</Border>
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
Placement="RightEdgeAlignedTop"
MaxHeight="400"
IsLightDismissEnabled="False"
HorizontalOffset="-4"
VerticalOffset="-4"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Grid>
<Border Background="{DynamicResource Brush.Popup}" Margin="4" CornerRadius="{DynamicResource OverlayCornerRadius}">
<Border.Effect>
<DropShadowEffect OffsetX="0" OffsetY="0" Color="Black" BlurRadius="4" Opacity=".6"/>
</Border.Effect>
</Border>
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
Placement="RightEdgeAlignedTop"
MaxHeight="400"
IsLightDismissEnabled="False"
HorizontalOffset="-4"
VerticalOffset="-4"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Grid>
<Border Background="{DynamicResource Brush.Popup}" Margin="4" CornerRadius="{DynamicResource OverlayCornerRadius}">
<Border.Effect>
<DropShadowEffect OffsetX="0" OffsetY="0" Color="Black" BlurRadius="4" Opacity=".6"/>
</Border.Effect>
</Border>
<Border BorderThickness="0"
Margin="8,4"
MaxWidth="{DynamicResource FlyoutThemeMaxWidth}"
MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}"
HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer Theme="{StaticResource FluentMenuScrollViewer}">
<ItemsPresenter Name="PART_ItemsPresenter"
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
Grid.IsSharedSizeScope="True">
<ItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsPresenter.ItemsPanel>
</ItemsPresenter>
</ScrollViewer>
</Border>
</Grid>
</Popup>
</Panel>
</ControlTemplate>
</Setter>
<Border BorderThickness="0"
Margin="8,4"
MaxWidth="{DynamicResource FlyoutThemeMaxWidth}"
MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}"
HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer Theme="{StaticResource FluentMenuScrollViewer}">
<ItemsPresenter Name="PART_ItemsPresenter"
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
Grid.IsSharedSizeScope="True">
<ItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsPresenter.ItemsPanel>
</ItemsPresenter>
</ScrollViewer>
</Border>
</Grid>
</Popup>
</Panel>
</ControlTemplate>
</Setter>
<Style Selector="^:icon /template/ ContentControl#PART_IconPresenter">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:icon /template/ ContentControl#PART_IconPresenter">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:selected">
<Style Selector="^ /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundPointerOver}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_HeaderPresenter">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPointerOver}" />
</Style>
<Style Selector="^ /template/ TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPointerOver}" />
</Style>
<Style Selector="^ /template/ Path#PART_ChevronPath">
<Setter Property="Fill" Value="{DynamicResource MenuFlyoutSubItemChevronPointerOver}" />
</Style>
</Style>
<Style Selector="^:selected">
<Style Selector="^ /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundPointerOver}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_HeaderPresenter">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForegroundPointerOver}" />
</Style>
<Style Selector="^ /template/ TextBlock#PART_InputGestureText">
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPointerOver}" />
</Style>
<Style Selector="^ /template/ Path#PART_ChevronPath">
<Setter Property="Fill" Value="{DynamicResource MenuFlyoutSubItemChevronPointerOver}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Setter Property="Opacity" Value="0.65"/>
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundDisabled}" />
</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 Selector="^:empty /template/ Path#PART_ChevronPath">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:empty /template/ Path#PART_ChevronPath">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:separator">
<Setter Property="Height" Value="5"/>
<Setter Property="Template">
<ControlTemplate>
<Separator Margin="28,0,4,0" Height=".5" VerticalAlignment="Center" Background="{DynamicResource Brush.Border2}"/>
</ControlTemplate>
</Setter>
</Style>
</ControlTheme>
</Style.Resources>
<Style Selector="^:separator">
<Setter Property="Height" Value="5"/>
<Setter Property="Template">
<ControlTemplate>
<Separator Margin="28,0,4,0" Height=".5" VerticalAlignment="Center" Background="{DynamicResource Brush.Border2}"/>
</ControlTemplate>
</Setter>
</Style>
</Style>
<Style Selector="ComboBox">
@ -1324,6 +1329,9 @@
<Style Selector="DataGrid /template/ Rectangle#PART_ColumnHeadersAndRowsSeparator">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="DataGridCell">
<Setter Property="MinHeight" Value="24"/>
</Style>
<Style Selector="DataGridCell:focus /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False"/>
</Style>

View file

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

View file

@ -36,11 +36,6 @@ namespace SourceGit.ViewModels
get => Backend is Models.Branch;
}
public string TrackStatus
{
get => Backend is Models.Branch { IsLocal: true } branch ? branch.TrackStatus.ToString() : string.Empty;
}
public FontWeight NameFontWeight
{
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)
DiffContext = null;
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);
}
public AvaloniaList<Models.CommitLink> WebLinks
{
get;
private set;
} = new AvaloniaList<Models.CommitLink>();
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;
_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()
@ -118,8 +138,7 @@ namespace SourceGit.ViewModels
public void NavigateTo(string commitSHA)
{
var repo = App.FindOpenedRepository(_repo);
repo?.NavigateToCommit(commitSHA);
_repo?.NavigateToCommit(commitSHA);
}
public void ClearSearchChangeFilter()
@ -129,7 +148,7 @@ namespace SourceGit.ViewModels
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)
@ -145,13 +164,13 @@ namespace SourceGit.ViewModels
case Models.ObjectType.Blob:
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)
{
var ext = Path.GetExtension(file.Path);
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;
Dispatcher.UIThread.Invoke(() =>
{
@ -160,7 +179,7 @@ namespace SourceGit.ViewModels
}
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(() =>
{
ViewRevisionFileContent = new Models.RevisionBinaryFile() { Size = size };
@ -170,7 +189,7 @@ namespace SourceGit.ViewModels
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 matchLFS = REG_LFS_FORMAT().Match(content);
if (matchLFS.Success)
@ -191,7 +210,7 @@ namespace SourceGit.ViewModels
case Models.ObjectType.Commit:
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();
if (commit != null)
{
@ -237,10 +256,49 @@ namespace SourceGit.ViewModels
var toolPath = Preference.Instance.ExternalMergeToolPath;
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;
};
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)
{
@ -249,7 +307,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
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();
ev.Handled = true;
};
@ -259,26 +317,13 @@ namespace SourceGit.ViewModels
blame.Icon = App.CreateMenuIcon("Icons.Blame");
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();
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(blame);
menu.Items.Add(explore);
menu.Items.Add(new MenuItem { Header = "-" });
}
@ -307,34 +352,25 @@ namespace SourceGit.ViewModels
public ContextMenu CreateRevisionFileContextMenu(Models.Object file)
{
var history = new MenuItem();
history.Header = App.Text("FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
var fullPath = Path.Combine(_repo.FullPath, file.Path);
var resetToThisRevision = new MenuItem();
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) };
window.Show();
new Commands.Checkout(_repo.FullPath).FileWithRevision(file.Path, $"{_commit.SHA}");
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();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.IsEnabled = File.Exists(fullPath);
explore.Click += (_, ev) =>
{
Native.OS.OpenInFileManager(full, file.Type == Models.ObjectType.Blob);
Native.OS.OpenInFileManager(fullPath, file.Type == Models.ObjectType.Blob);
ev.Handled = true;
};
@ -353,12 +389,33 @@ namespace SourceGit.ViewModels
if (selected.Count == 1)
{
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;
};
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();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
@ -378,10 +435,14 @@ namespace SourceGit.ViewModels
};
var menu = new ContextMenu();
menu.Items.Add(history);
menu.Items.Add(blame);
menu.Items.Add(resetToThisRevision);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(explore);
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(copyFileName);
return menu;
@ -406,9 +467,9 @@ namespace SourceGit.ViewModels
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 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 visible = changes;
if (!string.IsNullOrWhiteSpace(_searchChangeFilter))
@ -463,8 +524,7 @@ namespace SourceGit.ViewModels
".ico", ".bmp", ".jpg", ".png", ".jpeg"
};
private string _repo;
private AvaloniaList<Models.IssueTrackerRule> _issueTrackerRules = null;
private Repository _repo = null;
private int _activePageIndex = 0;
private Models.Commit _commit = null;
private string _fullMessage = string.Empty;

View file

@ -93,11 +93,8 @@ namespace SourceGit.ViewModels
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))
return new ValidationResult("Given SSH private key can NOT be found!");
}

View file

@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
@ -35,7 +33,7 @@ namespace SourceGit.ViewModels
}
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;
}
}
@ -54,15 +52,15 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _detailContext, value);
}
public FileHistories(string repo, string file, AvaloniaList<Models.IssueTrackerRule> issueTrackerRules)
public FileHistories(Repository repo, string file)
{
_repo = repo;
_file = file;
_detailContext = new CommitDetail(repo, issueTrackerRules);
_detailContext = new CommitDetail(repo);
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(() =>
{
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 bool _isLoading = true;
private List<Models.Commit> _commits = null;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -42,6 +42,17 @@ namespace SourceGit.ViewModels
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
{
get => _repo.Settings.IssueTrackerRules;
@ -77,6 +88,20 @@ namespace SourceGit.ViewModels
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()
{
foreach (var remote in _repo.Remotes)
@ -106,7 +131,8 @@ namespace SourceGit.ViewModels
public void RemoveSelectedIssueTracker()
{
_repo.Settings.RemoveIssueTracker(_selectedIssueTrackerRule);
if (_selectedIssueTrackerRule != null)
_repo.Settings.RemoveIssueTracker(_selectedIssueTrackerRule);
SelectedIssueTrackerRule = null;
}
@ -141,6 +167,7 @@ namespace SourceGit.ViewModels
private readonly Repository _repo = null;
private readonly Dictionary<string, string> _cached = null;
private string _httpProxy;
private Models.CommitTemplate _selectedCommitTemplate = null;
private Models.IssueTrackerRule _selectedIssueTrackerRule = null;
}
}

View file

@ -39,7 +39,7 @@ namespace SourceGit.ViewModels
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));
return succ;
});

View file

@ -43,7 +43,7 @@ namespace SourceGit.ViewModels
{
var succ = new Commands.Reset(_repo.FullPath, Parent.SHA, "--soft").Exec();
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));
return succ;
});

View file

@ -177,8 +177,6 @@ namespace SourceGit.ViewModels
}
}
public int Count => _count;
public object DetailContext
{
get => _detailContext;
@ -317,6 +315,17 @@ namespace SourceGit.ViewModels
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()
{
StageChanges(_selectedUnstaged);
@ -556,7 +565,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
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();
e.Handled = true;
};
@ -1118,34 +1127,62 @@ namespace SourceGit.ViewModels
public ContextMenu CreateContextMenuForCommitMessages()
{
var menu = new ContextMenu();
if (_repo.Settings.CommitMessages.Count == 0)
var templateCount = _repo.Settings.CommitTemplates.Count;
if (templateCount == 0)
{
var empty = new MenuItem();
empty.Header = App.Text("WorkingCopy.NoCommitHistories");
empty.IsEnabled = false;
menu.Items.Add(empty);
return menu;
menu.Items.Add(new MenuItem()
{
Header = App.Text("WorkingCopy.NoCommitTemplates"),
Icon = App.CreateMenuIcon("Icons.Code"),
IsEnabled = false
});
}
else
{
for (int i = 0; i < templateCount; i++)
{
var template = _repo.Settings.CommitTemplates[i];
var item = new MenuItem();
item.Header = new Views.NameHighlightedTextBlock("WorkingCopy.UseCommitTemplate", template.Name);
item.Icon = App.CreateMenuIcon("Icons.Code");
item.Click += (_, e) =>
{
CommitMessage = template.Content;
e.Handled = true;
};
menu.Items.Add(item);
}
}
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 historiesCount = _repo.Settings.CommitMessages.Count;
if (historiesCount == 0)
{
var dump = message;
var item = new MenuItem();
item.Header = dump;
item.Click += (_, e) =>
menu.Items.Add(new MenuItem()
{
CommitMessage = dump;
e.Handled = true;
};
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;
};
menu.Items.Add(item);
menu.Items.Add(item);
}
}
return menu;
@ -1245,9 +1282,10 @@ namespace SourceGit.ViewModels
return;
}
var autoStage = AutoStageBeforeCommit;
if (!_useAmend)
{
if (AutoStageBeforeCommit)
if (autoStage)
{
if (_count == 0)
{
@ -1269,26 +1307,28 @@ namespace SourceGit.ViewModels
_repo.Settings.PushCommitMessage(_commitMessage);
_repo.SetWatcherEnabled(false);
var autoStage = AutoStageBeforeCommit;
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(() =>
{
if (succ)
{
SelectedStaged = [];
CommitMessage = string.Empty;
UseAmend = false;
if (autoPush)
{
PopupHost.ShowAndStartPopup(new Push(_repo, null));
}
}
_repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true);
IsCommitting = false;
});
});

View file

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

View file

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

View file

@ -59,7 +59,7 @@
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
<TextBlock Text="{Binding Base.FriendlyName}" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/>
</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"/>
</Grid>
@ -83,7 +83,7 @@
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
<TextBlock Text="{Binding To.FriendlyName}" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/>
</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"/>
</Grid>

View file

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

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia;
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 static readonly StyledProperty<List<ViewModels.BranchTreeNode>> NodesProperty =

View file

@ -54,7 +54,17 @@
<Grid RowDefinitions="24,Auto,Auto,Auto" ColumnDefinitions="96,*">
<!-- 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 -->
<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}}"
Foreground="DarkOrange"
TextDecorations="Underline"
Cursor="Hand"
Margin="0,0,16,0"
PointerPressed="OnParentSHAPressed"/>
</DataTemplate>

View file

@ -2,20 +2,12 @@ using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace SourceGit.Views
{
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 =
AvaloniaProperty.Register<CommitBaseInfo, string>(nameof(Message), string.Empty);
@ -25,6 +17,15 @@ namespace SourceGit.Views
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 =
AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
@ -39,11 +40,43 @@ namespace SourceGit.Views
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)
{
if (sender is Control { DataContext: string sha } &&
DataContext is ViewModels.CommitDetail detail &&
CanNavigate)
if (DataContext is ViewModels.CommitDetail detail && sender is Control { DataContext: string sha })
{
detail.NavigateTo(sha);
}

View file

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

View file

@ -6,6 +6,7 @@ using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Input;
using Avalonia.Utilities;
namespace SourceGit.Views
{
@ -38,6 +39,8 @@ namespace SourceGit.Views
if (change.Property == MessageProperty || change.Property == IssueTrackerRulesProperty)
{
Inlines.Clear();
_matches = null;
ClearHoveredIssueLink();
var message = Message;
if (string.IsNullOrEmpty(message))
@ -61,6 +64,7 @@ namespace SourceGit.Views
}
matches.Sort((l, r) => l.Start - r.Start);
_matches = matches;
int pos = 0;
foreach (var match in matches)
@ -68,12 +72,9 @@ namespace SourceGit.Views
if (match.Start > pos)
Inlines.Add(new Run(message.Substring(pos, match.Start - pos)));
var link = new TextBlock();
link.SetValue(TextProperty, message.Substring(match.Start, match.Length));
link.SetValue(ToolTip.TipProperty, match.URL);
link.Classes.Add("issue_link");
link.PointerPressed += OnLinkPointerPressed;
Inlines.Add(link);
match.Link = new Run(message.Substring(match.Start, match.Length));
match.Link.Classes.Add("issue_link");
Inlines.Add(match.Link);
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)
{
var tooltip = text.GetValue(ToolTip.TipProperty) as string;
if (!string.IsNullOrEmpty(tooltip))
Native.OS.OpenBrowser(tooltip);
base.OnPointerMoved(e);
e.Handled = true;
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;
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 */;
}
InvalidateVisual();
return new Size(requiredWidth, 16);
}
InvalidateVisual();
return new Size(0, 0);
}

View file

@ -176,7 +176,7 @@
<ContentControl.DataTemplates>
<DataTemplate DataType="m:RevisionSubmodule">
<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>
</DataTemplate>
</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}}"/>
<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>
</StackPanel>
</ScrollViewer>

View file

@ -77,7 +77,7 @@
FontSize="10"
VerticalAlignment="Center"/>
<v:CommitSubjectPresenter Classes="primary"
<v:CommitSubjectPresenter Classes="primary"
Subject="{Binding Subject}"
IssueTrackerRules="{Binding $parent[v:Histories].IssueTrackerRules}"
Opacity="{Binding Opacity}"

View file

@ -11,6 +11,7 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace SourceGit.Views
@ -185,6 +186,8 @@ namespace SourceGit.Views
if (change.Property == SubjectProperty || change.Property == IssueTrackerRulesProperty)
{
Inlines.Clear();
_matches = null;
ClearHoveredIssueLink();
var subject = Subject;
if (string.IsNullOrEmpty(subject))
@ -208,6 +211,7 @@ namespace SourceGit.Views
}
matches.Sort((l, r) => l.Start - r.Start);
_matches = matches;
int pos = 0;
foreach (var match in matches)
@ -215,32 +219,82 @@ namespace SourceGit.Views
if (match.Start > pos)
Inlines.Add(new Run(subject.Substring(pos, match.Start - pos)));
var link = new TextBlock();
link.SetValue(TextProperty, subject.Substring(match.Start, match.Length));
link.SetValue(ToolTip.TipProperty, match.URL);
link.Classes.Add("issue_link");
link.PointerPressed += OnLinkPointerPressed;
Inlines.Add(link);
match.Link = new Run(subject.Substring(match.Start, match.Length));
match.Link.Classes.Add("issue_link");
Inlines.Add(match.Link);
pos = match.Start + match.Length;
}
if (pos < subject.Length)
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)
{
var tooltip = text.GetValue(ToolTip.TipProperty) as string;
if (!string.IsNullOrEmpty(tooltip))
Native.OS.OpenBrowser(tooltip);
base.OnPointerMoved(e);
e.Handled = true;
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;
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
@ -541,6 +595,15 @@ namespace SourceGit.Views
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 =
AvaloniaProperty.Register<Histories, long>(nameof(NavigationId));
@ -550,16 +613,6 @@ namespace SourceGit.Views
set => SetValue(NavigationIdProperty, value);
}
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get
{
if (DataContext is ViewModels.Histories histories)
return histories.IssueTrackerRules;
return null;
}
}
static Histories()
{
NavigationIdProperty.Changed.AddClassHandler<Histories>((h, _) =>

View file

@ -41,6 +41,11 @@
<Path Width="14" Height="14" Data="{StaticResource Icons.Settings}"/>
</MenuItem.Icon>
</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.Icon>
<Path Width="14" Height="14" Data="{StaticResource Icons.Hotkeys}"/>

View file

@ -20,6 +20,11 @@ namespace SourceGit.Views
InitializeComponent();
}
public bool HasKeyModifier(KeyModifiers modifier)
{
return _unhandledModifiers.HasFlag(modifier);
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
@ -147,6 +152,27 @@ namespace SourceGit.Views
}
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)
@ -178,5 +204,7 @@ namespace SourceGit.Views
e.Handled = true;
}
private KeyModifiers _unhandledModifiers = KeyModifiers.None;
}
}

View file

@ -57,7 +57,7 @@
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.General}"/>
</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"
Text="{DynamicResource Text.Preference.General.Locale}"
HorizontalAlignment="Right"
@ -71,25 +71,10 @@
SelectedItem="{Binding Locale, Mode=TwoWay, Converter={x:Static c:StringConverters.ToLocale}}"/>
<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}"
HorizontalAlignment="Right"
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"
Height="28"
Padding="4"
@ -98,11 +83,11 @@
CornerRadius="3"
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}"
HorizontalAlignment="Right"
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"
Height="28"
Padding="4"
@ -111,11 +96,11 @@
CornerRadius="3"
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}"
HorizontalAlignment="Right"
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"
Minimum="20000" Maximum="100000"
TickPlacement="BottomRight" TickFrequency="5000"
@ -130,16 +115,16 @@
Text="{Binding MaxHistoryCommits}"/>
</Grid>
<CheckBox Grid.Row="5" Grid.Column="1"
<CheckBox Grid.Row="4" Grid.Column="1"
Content="{DynamicResource Text.Preference.General.RestoreTabs}"
IsChecked="{Binding RestoreTabs, Mode=TwoWay}"/>
<CheckBox Grid.Row="6" Grid.Column="1"
<CheckBox Grid.Row="5" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preference.General.UseFixedTabWidth}"
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"
Content="{DynamicResource Text.Preference.General.Check4UpdatesOnStartup}"
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">
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Changes}"/>
<TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.WorkingCopy}"/>
<Border Grid.Column="2"
Margin="6,0"
Height="18"
CornerRadius="9"
VerticalAlignment="Center"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding WorkingCopyChangesCount, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
<TextBlock Classes="primary" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding WorkingCopyChangesCount}" Foreground="{DynamicResource Brush.BadgeFG}"/>
</Border>
<v:CounterPresenter Grid.Column="2"
Margin="6,0"
VerticalAlignment="Center"
Count="{Binding LocalChangesCount}"
FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
FontSize="10"
Foreground="{DynamicResource Brush.BadgeFG}"
Background="{DynamicResource Brush.Badge}"/>
</Grid>
</ListBoxItem>
@ -107,15 +106,14 @@
<Grid Classes="view_mode" ColumnDefinitions="32,*,Auto">
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Stashes}"/>
<TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.Stashes}"/>
<Border Grid.Column="2"
Margin="6,0"
Height="18"
CornerRadius="9"
VerticalAlignment="Center"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding StashesCount, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
<TextBlock Classes="primary" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding StashesCount}" Foreground="{DynamicResource Brush.BadgeFG}"/>
</Border>
<v:CounterPresenter Grid.Column="2"
Margin="6,0"
VerticalAlignment="Center"
Count="{Binding StashesCount}"
FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}"
FontSize="10"
Foreground="{DynamicResource Brush.BadgeFG}"
Background="{DynamicResource Brush.Badge}"/>
</Grid>
</ListBoxItem>
</ListBox>
@ -670,7 +668,8 @@
<ContentControl Grid.Row="2" Content="{Binding SelectedView}">
<ContentControl.DataTemplates>
<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}"/>
</DataTemplate>

View file

@ -1,12 +1,109 @@
using System;
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
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 Repository()
@ -164,7 +261,7 @@ namespace SourceGit.Views
if (!IsLoaded)
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 remoteBranchRows = vm.IsRemoteGroupExpanded ? RemoteBranchTree.Rows.Count : 0;
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;

View file

@ -112,6 +112,91 @@
</Grid>
</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.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.IssueTracker}"/>

View file

@ -31,19 +31,47 @@
</StackPanel>
<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}"/>
</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}"/>
</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}"/>
</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}"/>
</Button>

View file

@ -1,5 +1,7 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
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)
{
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}">
<TextBlock Text="HEAD" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/>
</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"/>
</Grid>

View file

@ -73,7 +73,7 @@
<DataTemplate DataType="m:RevisionSubmodule">
<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}"/>
<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>
</DataTemplate>
</ContentControl.DataTemplates>

View file

@ -41,8 +41,8 @@ namespace SourceGit.Views
Math.Abs(Height - old.Height) > 0.001 ||
StartIdx != old.StartIdx ||
EndIdx != old.EndIdx ||
Combined != Combined ||
IsOldSide != IsOldSide;
Combined != old.Combined ||
IsOldSide != old.IsOldSide;
}
}
@ -92,6 +92,9 @@ namespace SourceGit.Views
var typeface = view.CreateTypeface();
foreach (var line in view.VisualLines)
{
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber;
if (index > lines.Count)
break;
@ -160,7 +163,7 @@ namespace SourceGit.Views
var width = textView.Bounds.Width;
foreach (var line in textView.VisualLines)
{
if (line.FirstDocumentLine == null)
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber;
@ -172,8 +175,47 @@ namespace SourceGit.Views
if (bg == null)
continue;
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop) - textView.VerticalOffset;
drawingContext.DrawRectangle(bg, null, new Rect(0, y, width, line.Height));
var startY = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineTop) - textView.VerticalOffset;
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.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))
return;
var color = (Color)this.FindResource("SystemAccentColor");
var color = (Color)this.FindResource("SystemAccentColor")!;
var brush = new SolidColorBrush(color, 0.1);
var pen = new Pen(color.ToUInt32());
var rect = new Rect(0, chunk.Y, Bounds.Width, chunk.Height);
@ -409,6 +437,7 @@ namespace SourceGit.Views
base.OnLoaded(e);
TextArea.TextView.ContextRequested += OnTextViewContextRequested;
TextArea.TextView.PointerEntered += OnTextViewPointerEntered;
TextArea.TextView.PointerMoved += OnTextViewPointerMoved;
TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged;
@ -420,6 +449,7 @@ namespace SourceGit.Views
base.OnUnloaded(e);
TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
TextArea.TextView.PointerEntered -= OnTextViewPointerEntered;
TextArea.TextView.PointerMoved -= OnTextViewPointerMoved;
TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged;
@ -480,6 +510,12 @@ namespace SourceGit.Views
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)
{
if (EnableChunkSelection && sender is TextView view)
@ -675,12 +711,7 @@ namespace SourceGit.Views
var firstLineIdx = view.VisualLines[0].FirstDocumentLine.LineNumber - 1;
var lastLineIdx = view.VisualLines[^1].FirstDocumentLine.LineNumber - 1;
if (endIdx < firstLineIdx)
{
TrySetChunk(null);
return;
}
else if (startIdx > lastLineIdx)
if (endIdx < firstLineIdx || startIdx > lastLineIdx)
{
TrySetChunk(null);
return;
@ -711,6 +742,9 @@ namespace SourceGit.Views
var lineIdx = -1;
foreach (var line in view.VisualLines)
{
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber;
if (index > diff.Lines.Count)
break;
@ -853,12 +887,7 @@ namespace SourceGit.Views
var firstLineIdx = view.VisualLines[0].FirstDocumentLine.LineNumber - 1;
var lastLineIdx = view.VisualLines[^1].FirstDocumentLine.LineNumber - 1;
if (endIdx < firstLineIdx)
{
TrySetChunk(null);
return;
}
else if (startIdx > lastLineIdx)
if (endIdx < firstLineIdx || startIdx > lastLineIdx)
{
TrySetChunk(null);
return;
@ -895,6 +924,9 @@ namespace SourceGit.Views
var lineIdx = -1;
foreach (var line in view.VisualLines)
{
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber;
if (index > lines.Count)
break;
@ -1129,7 +1161,7 @@ namespace SourceGit.Views
SetCurrentValue(SelectedChunkProperty, null);
}
private void OnStageChunk(object sender, RoutedEventArgs e)
private void OnStageChunk(object _1, RoutedEventArgs _2)
{
var chunk = SelectedChunk;
if (chunk == null)
@ -1187,7 +1219,7 @@ namespace SourceGit.Views
repo.SetWatcherEnabled(true);
}
private void OnUnstageChunk(object sender, RoutedEventArgs e)
private void OnUnstageChunk(object _1, RoutedEventArgs _2)
{
var chunk = SelectedChunk;
if (chunk == null)
@ -1241,7 +1273,7 @@ namespace SourceGit.Views
repo.SetWatcherEnabled(true);
}
private void OnDiscardChunk(object sender, RoutedEventArgs e)
private void OnDiscardChunk(object _1, RoutedEventArgs _2)
{
var chunk = SelectedChunk;
if (chunk == null)

View file

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