mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-19 09:24:59 +00:00
Compare commits
No commits in common. "master" and "v8.24" have entirely different histories.
521 changed files with 14876 additions and 44465 deletions
|
@ -100,7 +100,7 @@ dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
|
|||
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
|
||||
|
||||
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
|
||||
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
|
||||
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
|
||||
|
||||
# use accessibility modifiers
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
@ -206,9 +206,6 @@ dotnet_diagnostic.CA1854.severity = warning
|
|||
#CA2211:Non-constant fields should not be visible
|
||||
dotnet_diagnostic.CA2211.severity = error
|
||||
|
||||
# IDE0005: remove used namespace using
|
||||
dotnet_diagnostic.IDE0005.severity = error
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_wrap_before_ternary_opsigns = false
|
||||
|
||||
|
@ -295,12 +292,3 @@ indent_size = 2
|
|||
end_of_line = lf
|
||||
[*.{cmd,bat}]
|
||||
end_of_line = crlf
|
||||
|
||||
# Package manifests
|
||||
[{*.spec,control}]
|
||||
end_of_line = lf
|
||||
|
||||
# YAML files
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
|
|
70
.gitattributes
vendored
70
.gitattributes
vendored
|
@ -1,14 +1,78 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
# https://www.davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
|
||||
* text=auto
|
||||
|
||||
#
|
||||
# The above will handle all files NOT found below
|
||||
#
|
||||
|
||||
# Documents
|
||||
*.bibtex text diff=bibtex
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.md text
|
||||
*.tex text diff=tex
|
||||
*.adoc text
|
||||
*.textile text
|
||||
*.mustache text
|
||||
*.csv text
|
||||
*.tab text
|
||||
*.tsv text
|
||||
*.txt text
|
||||
*.sql text
|
||||
|
||||
# Graphics
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.ico binary
|
||||
# SVG treated as an asset (binary) by default.
|
||||
*.svg text
|
||||
# If you want to treat it as binary,
|
||||
# use the following line instead.
|
||||
# *.svg binary
|
||||
*.eps binary
|
||||
|
||||
# Scripts
|
||||
*.bash text eol=lf
|
||||
*.fish text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.spec text eol=lf
|
||||
control text eol=lf
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
|
||||
# Serialisation
|
||||
*.json text
|
||||
*.toml text
|
||||
*.xml text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
|
||||
# Archives
|
||||
*.7z binary
|
||||
*.gz binary
|
||||
*.tar binary
|
||||
*.tgz binary
|
||||
*.zip binary
|
||||
|
||||
# Text files where line endings should be preserved
|
||||
*.patch -text
|
||||
|
||||
#
|
||||
# Exclude files from exporting
|
||||
#
|
||||
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.gitignore export-ignore
|
79
.github/workflows/build.yml
vendored
79
.github/workflows/build.yml
vendored
|
@ -1,79 +0,0 @@
|
|||
name: Build
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
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-latest
|
||||
runtime: linux-x64
|
||||
container: ubuntu:20.04
|
||||
- name : Linux (arm64)
|
||||
os: ubuntu-latest
|
||||
runtime: linux-arm64
|
||||
container: ubuntu:20.04
|
||||
name: Build ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container || '' }}
|
||||
steps:
|
||||
- name: Install common CLI tools
|
||||
if: ${{ startsWith(matrix.runtime, 'linux-') }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
||||
apt-get update
|
||||
apt-get install -y sudo
|
||||
sudo apt-get install -y curl wget git unzip zip libicu66 tzdata clang
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.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
|
||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted
|
||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted' \
|
||||
| sudo tee /etc/apt/sources.list.d/arm64.list
|
||||
sudo sed -i -e 's/^deb http/deb [arch=amd64] http/g' /etc/apt/sources.list
|
||||
sudo sed -i -e 's/^deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list
|
||||
- name: Install cross-compiling dependencies
|
||||
if: ${{ matrix.runtime == 'linux-arm64' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y llvm gcc-aarch64-linux-gnu
|
||||
- name: Build
|
||||
run: dotnet build -c Release
|
||||
- name: Publish
|
||||
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: Tar artifact
|
||||
if: ${{ startsWith(matrix.runtime, 'linux-') || startsWith(matrix.runtime, 'osx-') }}
|
||||
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.${{ matrix.runtime }}
|
||||
path: publish/*
|
148
.github/workflows/ci.yml
vendored
148
.github/workflows/ci.yml
vendored
|
@ -1,29 +1,139 @@
|
|||
name: Continuous Integration
|
||||
on:
|
||||
push:
|
||||
branches: [develop]
|
||||
branches:
|
||||
- develop
|
||||
pull_request:
|
||||
branches: [develop]
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/build.yml
|
||||
version:
|
||||
name: Prepare version string
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
build-windows:
|
||||
name: Build Windows x64
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Output version string
|
||||
id: version
|
||||
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
|
||||
package:
|
||||
needs: [build, version]
|
||||
name: Package
|
||||
uses: ./.github/workflows/package.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.version }}
|
||||
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
|
||||
run: |
|
||||
sudo dpkg --add-architecture arm64
|
||||
echo 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main restricted
|
||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted
|
||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted' \
|
||||
| sudo tee /etc/apt/sources.list.d/arm64.list
|
||||
sudo sed -i -e 's/^deb http/deb [arch=amd64] http/g' /etc/apt/sources.list
|
||||
sudo sed -i -e 's/^deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list
|
||||
- name: Install cross-compiling dependencies
|
||||
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: mv publish/SourceGit publish/sourcegit
|
||||
- name: Packing Program
|
||||
run: tar -cvf sourcegit.linux-arm64.tar -C publish/ .
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sourcegit.linux-arm64
|
||||
path: sourcegit.linux-arm64.tar
|
||||
|
|
41
.github/workflows/localization-check.yml
vendored
41
.github/workflows/localization-check.yml
vendored
|
@ -1,41 +0,0 @@
|
|||
name: Localization Check
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
paths:
|
||||
- 'src/Resources/Locales/**'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
localization-check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install fs-extra@11.2.0 path@0.12.7 xml2js@0.6.2
|
||||
|
||||
- name: Run localization check
|
||||
run: node build/scripts/localization-check.js
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git add TRANSLATION.md src/Resources/Locales/*.axaml
|
||||
git commit -m 'doc: Update translation status and sort locale files'
|
||||
git push
|
||||
else
|
||||
echo "No changes to commit"
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
111
.github/workflows/package.yml
vendored
111
.github/workflows/package.yml
vendored
|
@ -1,111 +0,0 @@
|
|||
name: Package
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: SourceGit package version
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
windows:
|
||||
name: Package Windows
|
||||
runs-on: windows-2019
|
||||
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
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
RUNTIME: ${{ matrix.runtime }}
|
||||
run: ./build/scripts/package.windows.sh
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: package.${{ matrix.runtime }}
|
||||
path: build/sourcegit_*.zip
|
||||
- name: Delete temp artifacts
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: sourcegit.${{ matrix.runtime }}
|
||||
osx-app:
|
||||
name: Package macOS
|
||||
runs-on: macos-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
|
||||
- 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.osx-app.sh
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: package.${{ matrix.runtime }}
|
||||
path: build/sourcegit_*.zip
|
||||
- name: Delete temp artifacts
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: sourcegit.${{ matrix.runtime }}
|
||||
linux:
|
||||
name: Package Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:20.04
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: [linux-x64, linux-arm64]
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Download package dependencies
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
||||
apt-get update
|
||||
apt-get install -y curl wget git dpkg-dev fakeroot tzdata zip unzip desktop-file-utils rpm libfuse2 file build-essential binutils
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: sourcegit.${{ matrix.runtime }}
|
||||
path: build
|
||||
- name: Package
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
RUNTIME: ${{ matrix.runtime }}
|
||||
APPIMAGE_EXTRACT_AND_RUN: 1
|
||||
run: |
|
||||
mkdir build/SourceGit
|
||||
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit
|
||||
./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
|
||||
- name: Delete temp artifacts
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: sourcegit.${{ matrix.runtime }}
|
52
.github/workflows/release.yml
vendored
52
.github/workflows/release.yml
vendored
|
@ -1,52 +0,0 @@
|
|||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/build.yml
|
||||
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: [build, version]
|
||||
name: Package
|
||||
uses: ./.github/workflows/package.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.version }}
|
||||
release:
|
||||
needs: [package, version]
|
||||
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 }}
|
||||
VERSION: ${{ needs.version.outputs.version }}
|
||||
run: gh release create "$TAG" -t "$VERSION" --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 }}
|
||||
run: gh release upload "$TAG" packages/*
|
596
.gitignore
vendored
596
.gitignore
vendored
|
@ -1,13 +1,425 @@
|
|||
.vs/
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
*.sln.docstates
|
||||
*.user
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Nuke Build - Uncomment if you are using it
|
||||
.nuke/temp
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
### Linux ###
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
|
@ -16,18 +428,175 @@
|
|||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### Rider ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### VisualStudioCode ###
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
bin/
|
||||
obj/
|
||||
# ignore ci node files
|
||||
node_modules/
|
||||
package.json
|
||||
package-lock.json
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
### Specifics ###
|
||||
|
||||
# Specials
|
||||
*.zip
|
||||
archives
|
||||
package-lock.json
|
||||
*.private.env.json
|
||||
**/**/[Dd]ata/*.json
|
||||
**/**/[Dd]ata/*.csv
|
||||
|
||||
# SpecFlow
|
||||
*.feature.cs
|
||||
|
||||
# Azurite
|
||||
*azurite*.json
|
||||
|
||||
# Build Folders
|
||||
[Pp]ublish
|
||||
[Oo]utput
|
||||
[Ss]cripts
|
||||
[Tt]ests/[Rr]esults
|
||||
|
||||
# LibraryManager
|
||||
**/lib
|
||||
|
||||
# BuildBundlerMinifier
|
||||
*.min.*
|
||||
*.map
|
||||
|
||||
# Sass Output
|
||||
**/css
|
||||
|
||||
# SQLite files
|
||||
*.db
|
||||
*.sqlite3
|
||||
*.sqlite
|
||||
*.db-journal
|
||||
*.sqlite3-journal
|
||||
*.sqlite-journal
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# SourceGit output files
|
||||
build/resources/
|
||||
build/SourceGit/
|
||||
build/SourceGit.app/
|
||||
|
@ -36,6 +605,3 @@ build/*.tar.gz
|
|||
build/*.deb
|
||||
build/*.rpm
|
||||
build/*.AppImage
|
||||
SourceGit.app/
|
||||
build.command
|
||||
src/Properties/launchSettings.json
|
||||
|
|
4
LICENSE
4
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 sourcegit
|
||||
Copyright (c) 2024 sourcegit
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
160
README.md
160
README.md
|
@ -1,26 +1,21 @@
|
|||
# SourceGit - Opensource Git GUI client.
|
||||
# SourceGit
|
||||
|
||||
[](https://github.com/sourcegit-scm/sourcegit/stargazers)
|
||||
[](https://github.com/sourcegit-scm/sourcegit/forks)
|
||||
[](LICENSE)
|
||||
[](https://github.com/sourcegit-scm/sourcegit/releases/latest)
|
||||
[](https://github.com/sourcegit-scm/sourcegit/releases)
|
||||
Opensource Git GUI client.
|
||||
|
||||
## Highlights
|
||||
|
||||
* Supports Windows/macOS/Linux
|
||||
* Opensource/Free
|
||||
* Fast
|
||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/Українська/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
||||
* English/German/Português/简体中文/繁體中文
|
||||
* Built-in light/dark themes
|
||||
* Customize theme
|
||||
* Visual commit graph
|
||||
* Supports SSH access with each remote
|
||||
* GIT commands with GUI
|
||||
* Clone/Fetch/Pull/Push...
|
||||
* Merge/Rebase/Reset/Revert/Cherry-pick...
|
||||
* Amend/Reword/Squash
|
||||
* Interactive rebase
|
||||
* Merge/Rebase/Reset/Revert/Amend/Cherry-pick...
|
||||
* Interactive rebase (Basic)
|
||||
* Branches
|
||||
* Remotes
|
||||
* Tags
|
||||
|
@ -35,40 +30,24 @@
|
|||
* Revision Diffs
|
||||
* Branch Diff
|
||||
* Image Diff - Side-By-Side/Swipe/Blend
|
||||
* Git command logs
|
||||
* Search commits
|
||||
* GitFlow
|
||||
* Git LFS
|
||||
* Bisect
|
||||
* Issue Link
|
||||
* Workspace
|
||||
* Custom Action
|
||||
* Using AI to generate commit message (C# port of [anjerodev/commitollama](https://github.com/anjerodev/commitollama))
|
||||
* GitFlow support
|
||||
* Git LFS support
|
||||
|
||||
> [!WARNING]
|
||||
> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**.
|
||||
|
||||
## Translation Status
|
||||
|
||||
You can find the current translation status in [TRANSLATION.md](https://github.com/sourcegit-scm/sourcegit/blob/develop/TRANSLATION.md)
|
||||
|
||||
## How to Use
|
||||
|
||||
**To use this tool, you need to install Git(>=2.25.1) first.**
|
||||
**To use this tool, you need to install Git(>=2.23.0) first.**
|
||||
|
||||
You can download the latest stable from [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) or download workflow artifacts from [GitHub Actions](https://github.com/sourcegit-scm/sourcegit/actions) to try this app based on latest commits.
|
||||
You can download the latest stable from [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) or download workflow artifacts from [Github Actions](https://github.com/sourcegit-scm/sourcegit/actions) to try this app based on latest commits.
|
||||
|
||||
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 | `%APPDATA%\SourceGit` |
|
||||
| Linux | `${HOME}/.config/SourceGit` or `${HOME}/.sourcegit` |
|
||||
| macOS | `${HOME}/Library/Application Support/SourceGit` |
|
||||
|
||||
> [!TIP]
|
||||
> * You can open this data storage directory from the main menu `Open Data Storage Directory`.
|
||||
> * You can create a `data` folder next to the `SourceGit` executable to force this app to store data (user settings, downloaded avatars and crash logs) into it (Portable-Mode). Only works on Windows.
|
||||
| OS | PATH |
|
||||
|---------|-------------------------------------------------|
|
||||
| Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` |
|
||||
| Linux | `${HOME}/.config/SourceGit` |
|
||||
| macOS | `${HOME}/Library/Application Support/SourceGit` |
|
||||
|
||||
For **Windows** users:
|
||||
|
||||
|
@ -77,99 +56,43 @@ For **Windows** users:
|
|||
```shell
|
||||
winget install SourceGit
|
||||
```
|
||||
> [!NOTE]
|
||||
> `winget` will install this software as a commandline tool. You need run `SourceGit` from console or `Win+R` at the first time. Then you can add it to the taskbar.
|
||||
* You can install the latest stable by `scoop` with follow commands:
|
||||
> `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
|
||||
scoop install sourcegit
|
||||
```
|
||||
* Pre-built binaries can be found in [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest)
|
||||
* Portable versions can be found in [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest)
|
||||
|
||||
For **macOS** users:
|
||||
|
||||
* Thanks [@ybeapps](https://github.com/ybeapps) for making `SourceGit` available on `Homebrew`. You can simply install it with following command:
|
||||
```shell
|
||||
brew tap ybeapps/homebrew-sourcegit
|
||||
brew install --cask --no-quarantine sourcegit
|
||||
```
|
||||
* If you want to install `SourceGit.app` from GitHub Release manually, you need run following command to make sure it works:
|
||||
```shell
|
||||
sudo xattr -cr /Applications/SourceGit.app
|
||||
```
|
||||
* Download `sourcegit_x.y.osx-x64.zip` or `sourcegit_x.y.osx-arm64.zip` from Releases. `x64` for Intel and `arm64` for Apple Silicon.
|
||||
* Move `SourceGit.app` to `Applications` folder.
|
||||
* Make sure your mac trusts all software from anywhere. For more information, search `spctl --master-disable`.
|
||||
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your mac.
|
||||
* You can run `echo $PATH > ~/Library/Application\ Support/SourceGit/PATH` to generate a custom PATH env file to introduce `PATH` env to SourceGit.
|
||||
* You may need to run `sudo xattr -cr /Applications/SourceGit.app` to make sure the software works.
|
||||
|
||||
For **Linux** users:
|
||||
|
||||
* Thanks [@aikawayataro](https://github.com/aikawayataro) for providing `rpm` and `deb` repositories, hosted on [Codeberg](https://codeberg.org/yataro/-/packages).
|
||||
|
||||
`deb` how to:
|
||||
```shell
|
||||
curl https://codeberg.org/api/packages/yataro/debian/repository.key | sudo tee /etc/apt/keyrings/sourcegit.asc
|
||||
echo "deb [signed-by=/etc/apt/keyrings/sourcegit.asc, arch=amd64,arm64] https://codeberg.org/api/packages/yataro/debian generic main" | sudo tee /etc/apt/sources.list.d/sourcegit.list
|
||||
sudo apt update
|
||||
sudo apt install sourcegit
|
||||
```
|
||||
|
||||
`rpm` how to:
|
||||
```shell
|
||||
curl https://codeberg.org/api/packages/yataro/rpm.repo | sed -e 's/gpgcheck=1/gpgcheck=0/' > sourcegit.repo
|
||||
|
||||
# Fedora 41 and newer
|
||||
sudo dnf config-manager addrepo --from-repofile=./sourcegit.repo
|
||||
# Fedora 40 and earlier
|
||||
sudo dnf config-manager --add-repo ./sourcegit.repo
|
||||
|
||||
sudo dnf install sourcegit
|
||||
```
|
||||
|
||||
If your distribution isn't using `dnf`, please refer to the documentation of your distribution on how to add an `rpm` repository.
|
||||
* `AppImage` files can be found on [AppImage hub](https://appimage.github.io/SourceGit/), `xdg-open` (`xdg-utils`) must be installed to support open native file manager.
|
||||
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your Linux.
|
||||
* `xdg-open` must be installed to support open native file manager.
|
||||
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your linux.
|
||||
* Maybe you need to set environment variable `AVALONIA_SCREEN_SCALE_FACTORS`. See https://github.com/AvaloniaUI/Avalonia/wiki/Configuring-X11-per-monitor-DPI.
|
||||
* If you can NOT type accented characters, such as `ê`, `ó`, try to set the environment variable `AVALONIA_IM_MODULE` to `none`.
|
||||
|
||||
## OpenAI
|
||||
|
||||
This software supports using OpenAI or other AI service that has an OpenAI compatible HTTP API to generate commit message. You need configurate the service in `Preference` window.
|
||||
|
||||
For `OpenAI`:
|
||||
|
||||
* `Server` must be `https://api.openai.com/v1`
|
||||
|
||||
For other AI service:
|
||||
|
||||
* The `Server` should fill in a URL equivalent to OpenAI's `https://api.openai.com/v1`. For example, when using `Ollama`, it should be `http://localhost:11434/v1` instead of `http://localhost:11434/api/generate`
|
||||
* The `API Key` is optional that depends on the service
|
||||
|
||||
## External Tools
|
||||
|
||||
This app supports open repository in external tools listed in the table below.
|
||||
|
||||
| Tool | Windows | macOS | Linux |
|
||||
|-------------------------------|---------|-------|-------|
|
||||
| Visual Studio Code | YES | YES | YES |
|
||||
| Visual Studio Code - Insiders | YES | YES | YES |
|
||||
| VSCodium | YES | YES | YES |
|
||||
| Fleet | YES | YES | YES |
|
||||
| Sublime Text | YES | YES | YES |
|
||||
| Zed | NO | YES | YES |
|
||||
| Visual Studio | YES | NO | NO |
|
||||
| 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 |
|
||||
|
||||
> [!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 storage directory and provide the path directly. For example:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"Visual Studio Code": "D:\\VSCode\\Code.exe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> This app also supports a lot of `JetBrains` IDEs, installing `JetBrains Toolbox` will help this app to find them.
|
||||
* 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.
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
@ -183,25 +106,12 @@ This app supports open repository in external tools listed in the table below.
|
|||
|
||||
* Custom
|
||||
|
||||
You can find custom themes from [sourcegit-theme](https://github.com/sourcegit-scm/sourcegit-theme.git). And welcome to share your own themes.
|
||||
You can find custom themes from [sourcegit-theme](https://github.com/sourcegit-scm/sourcegit-theme.git)
|
||||
|
||||
## Contributing
|
||||
|
||||
Everyone is welcome to submit a PR. Please make sure your PR is based on the latest `develop` branch and the target branch of PR is `develop`.
|
||||
|
||||
In short, here are the commands to get started once [.NET tools are installed](https://dotnet.microsoft.com/en-us/download):
|
||||
|
||||
```sh
|
||||
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
|
||||
dotnet restore
|
||||
dotnet build
|
||||
dotnet run --project src/SourceGit.csproj
|
||||
```
|
||||
|
||||
Thanks to all the people who contribute.
|
||||
|
||||
[](https://github.com/sourcegit-scm/sourcegit/graphs/contributors)
|
||||
|
||||
## Third-Party Components
|
||||
|
||||
For detailed license information, see [THIRD-PARTY-LICENSES.md](THIRD-PARTY-LICENSES.md).
|
||||
[](https://github.com/sourcegit-scm/sourcegit/graphs/contributors)
|
||||
|
|
|
@ -6,6 +6,11 @@ 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
|
||||
|
@ -13,11 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{F45A
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{67B6D05F-A000-40BA-ADB4-C9065F880D7B}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\build.yml = .github\workflows\build.yml
|
||||
.github\workflows\ci.yml = .github\workflows\ci.yml
|
||||
.github\workflows\package.yml = .github\workflows\package.yml
|
||||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
.github\workflows\localization-check.yml = .github\workflows\localization-check.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}"
|
||||
|
@ -43,6 +44,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_common", "_common", "{04FD
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "usr", "usr", "{76639799-54BC-45E8-BD90-F45F63ACD11D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bin", "bin", "{2E27E952-846B-4D75-A426-D22151277864}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\resources\_common\usr\bin\sourcegit = build\resources\_common\usr\bin\sourcegit
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "share", "share", "{A3ABAA7C-EE14-4448-B466-6E69C1347E7D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "applications", "applications", "{2AF28D3B-14A8-46A8-B828-157FAAB1B06F}"
|
||||
|
@ -60,8 +66,6 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DEBIAN", "DEBIAN", "{F101849D-BDB7-40D4-A516-751150C3CCFC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\resources\deb\DEBIAN\control = build\resources\deb\DEBIAN\control
|
||||
build\resources\deb\DEBIAN\preinst = build\resources\deb\DEBIAN\preinst
|
||||
build\resources\deb\DEBIAN\prerm = build\resources\deb\DEBIAN\prerm
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rpm", "rpm", "{9BA0B044-0CC9-46F8-B551-204F149BF45D}"
|
||||
|
@ -73,18 +77,13 @@ 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\localization-check.js = build\scripts\localization-check.js
|
||||
build\scripts\package.linux.sh = build\scripts\package.linux.sh
|
||||
build\scripts\package.osx-app.sh = build\scripts\package.osx-app.sh
|
||||
build\scripts\package.windows.sh = build\scripts\package.windows.sh
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -106,6 +105,7 @@ Global
|
|||
{ABC98884-F023-4EF4-A9C9-5DE9452BE955} = {FD384607-ED99-47B7-AF31-FB245841BC92}
|
||||
{04FD74B1-FBDB-496E-A48F-3D59D71FF952} = {FD384607-ED99-47B7-AF31-FB245841BC92}
|
||||
{76639799-54BC-45E8-BD90-F45F63ACD11D} = {04FD74B1-FBDB-496E-A48F-3D59D71FF952}
|
||||
{2E27E952-846B-4D75-A426-D22151277864} = {76639799-54BC-45E8-BD90-F45F63ACD11D}
|
||||
{A3ABAA7C-EE14-4448-B466-6E69C1347E7D} = {76639799-54BC-45E8-BD90-F45F63ACD11D}
|
||||
{2AF28D3B-14A8-46A8-B828-157FAAB1B06F} = {A3ABAA7C-EE14-4448-B466-6E69C1347E7D}
|
||||
{7166EC6C-17F5-4B5E-B38E-1E53C81EACF6} = {A3ABAA7C-EE14-4448-B466-6E69C1347E7D}
|
||||
|
@ -114,7 +114,6 @@ 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}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
# Third-Party Licenses
|
||||
|
||||
This project incorporates components from the following third parties:
|
||||
|
||||
## Packages
|
||||
|
||||
### AvaloniaUI
|
||||
|
||||
- **Source**: https://github.com/AvaloniaUI/Avalonia
|
||||
- **Version**: 11.2.5
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md
|
||||
|
||||
### AvaloniaEdit
|
||||
|
||||
- **Source**: https://github.com/AvaloniaUI/AvaloniaEdit
|
||||
- **Version**: 11.2.0
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/AvaloniaUI/AvaloniaEdit/blob/master/LICENSE
|
||||
|
||||
### LiveChartsCore.SkiaSharpView.Avalonia
|
||||
|
||||
- **Source**: https://github.com/beto-rodriguez/LiveCharts2
|
||||
- **Version**: 2.0.0-rc5.4
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/beto-rodriguez/LiveCharts2/blob/master/LICENSE
|
||||
|
||||
### TextMateSharp
|
||||
|
||||
- **Source**: https://github.com/danipen/TextMateSharp
|
||||
- **Version**: 1.0.66
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/danipen/TextMateSharp/blob/master/LICENSE.md
|
||||
|
||||
### OpenAI .NET SDK
|
||||
|
||||
- **Source**: https://github.com/openai/openai-dotnet
|
||||
- **Version**: 2.2.0-beta2
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/openai/openai-dotnet/blob/main/LICENSE
|
||||
|
||||
### Azure.AI.OpenAI
|
||||
|
||||
- **Source**: https://github.com/Azure/azure-sdk-for-net
|
||||
- **Version**: 2.2.0-beta2
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/Azure/azure-sdk-for-net/blob/main/LICENSE.txt
|
||||
|
||||
## Fonts
|
||||
|
||||
### JetBrainsMono
|
||||
|
||||
- **Source**: https://github.com/JetBrains/JetBrainsMono
|
||||
- **Commit**: v2.304
|
||||
- **License**: SIL Open Font License, Version 1.1
|
||||
- **License Link**: https://github.com/JetBrains/JetBrainsMono/blob/v2.304/OFL.txt
|
||||
|
||||
## Grammar Files
|
||||
|
||||
### haxe-TmLanguage
|
||||
|
||||
- **Source**: https://github.com/vshaxe/haxe-TmLanguage
|
||||
- **Commit**: ddad8b4c6d0781ac20be0481174ec1be772c5da5
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/vshaxe/haxe-TmLanguage/blob/ddad8b4c6d0781ac20be0481174ec1be772c5da5/LICENSE.md
|
||||
|
||||
### coc-toml
|
||||
|
||||
- **Source**: https://github.com/kkiyama117/coc-toml
|
||||
- **Commit**: aac3e0c65955c03314b2733041b19f903b7cc447
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/kkiyama117/coc-toml/blob/aac3e0c65955c03314b2733041b19f903b7cc447/LICENSE
|
||||
|
||||
### eclipse-buildship
|
||||
|
||||
- **Source**: https://github.com/eclipse/buildship
|
||||
- **Commit**: 6bb773e7692f913dec27105129ebe388de34e68b
|
||||
- **License**: Eclipse Public License 1.0
|
||||
- **License Link**: https://github.com/eclipse-buildship/buildship/blob/6bb773e7692f913dec27105129ebe388de34e68b/README.md
|
||||
|
||||
### vscode-jsp-lang
|
||||
|
||||
- **Source**: https://github.com/samuel-weinhardt/vscode-jsp-lang
|
||||
- **Commit**: 0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/samuel-weinhardt/vscode-jsp-lang/blob/0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355/LICENSE
|
511
TRANSLATION.md
511
TRANSLATION.md
|
@ -1,511 +0,0 @@
|
|||
# Translation Status
|
||||
|
||||
This document shows the translation status of each locale file in the repository.
|
||||
|
||||
## Details
|
||||
|
||||
### 
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in de_DE.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in fr_FR.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in it_IT.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ja_JP.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CompareWithCurrent
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in pt_BR.axaml</summary>
|
||||
|
||||
- Text.AIAssistant.Regen
|
||||
- Text.AIAssistant.Use
|
||||
- Text.ApplyStash
|
||||
- Text.ApplyStash.DropAfterApply
|
||||
- Text.ApplyStash.RestoreIndex
|
||||
- Text.ApplyStash.Stash
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CustomAction
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.BranchUpstreamInvalid
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.Clone.RecurseSubmodules
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.CommitDetail.Info.Children
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.CustomAction.Scope.Branch
|
||||
- Text.Configure.CustomAction.WaitForExit
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.Configure.IssueTracker.AddSampleGiteeIssue
|
||||
- Text.Configure.IssueTracker.AddSampleGiteePullRequest
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.CreateBranch.Name.WarnSpace
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.DeleteRepositoryNode.Path
|
||||
- Text.DeleteRepositoryNode.TipForGroup
|
||||
- Text.DeleteRepositoryNode.TipForRepository
|
||||
- Text.Diff.First
|
||||
- Text.Diff.Last
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.Fetch.Force
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Preferences.AI.Streaming
|
||||
- Text.Preferences.Appearance.EditorTabWidth
|
||||
- Text.Preferences.General.DateFormat
|
||||
- Text.Preferences.General.ShowChildren
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Preferences.Git.SSLVerify
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.HistoriesLayout
|
||||
- Text.Repository.HistoriesLayout.Horizontal
|
||||
- Text.Repository.HistoriesLayout.Vertical
|
||||
- Text.Repository.HistoriesOrder
|
||||
- Text.Repository.Notifications.Clear
|
||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.Skip
|
||||
- Text.Repository.Tags.OrderByCreatorDate
|
||||
- Text.Repository.Tags.OrderByName
|
||||
- Text.Repository.Tags.Sort
|
||||
- Text.Repository.UseRelativeTimeInHistories
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.SetUpstream
|
||||
- Text.SetUpstream.Local
|
||||
- Text.SetUpstream.Unset
|
||||
- Text.SetUpstream.Upstream
|
||||
- Text.SHALinkCM.NavigateTo
|
||||
- Text.Stash.AutoRestore
|
||||
- Text.Stash.AutoRestore.Tip
|
||||
- Text.StashCM.SaveAsPatch
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
- Text.WorkingCopy.SignOff
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ru_RU.axaml</summary>
|
||||
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ta_IN.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CompareWithCurrent
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.UpdateSubmodules.Target
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in uk_UA.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.ConfigureWorkspace.Name
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
### 
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
2025.22
|
||||
8.24
|
|
@ -1,15 +0,0 @@
|
|||
# build
|
||||
|
||||
> [!WARNING]
|
||||
> The files under the `build` folder is used for `Github Action` only, **NOT** for end users.
|
||||
|
||||
## How to build this project manually
|
||||
|
||||
1. Make sure [.NET SDK 9](https://dotnet.microsoft.com/en-us/download) is installed on your machine.
|
||||
2. Clone this project
|
||||
3. Run the follow command under the project root dir
|
||||
```sh
|
||||
dotnet publish -c Release -r $RUNTIME_IDENTIFIER -o $DESTINATION_FOLDER src/SourceGit.csproj
|
||||
```
|
||||
> [!NOTE]
|
||||
> Please replace the `$RUNTIME_IDENTIFIER` with one of `win-x64`,`win-arm64`,`linux-x64`,`linux-arm64`,`osx-x64`,`osx-arm64`, and replace the `$DESTINATION_FOLDER` with the real path that will store the output executable files.
|
32
build/build.linux.sh
Executable file
32
build/build.linux.sh
Executable file
|
@ -0,0 +1,32 @@
|
|||
#!/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
|
21
build/build.osx.command
Executable file
21
build/build.osx.command
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/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
|
23
build/build.windows.ps1
Normal file
23
build/build.windows.ps1
Normal file
|
@ -0,0 +1,23 @@
|
|||
$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"
|
|
@ -1,5 +1,5 @@
|
|||
[Desktop Entry]
|
||||
Name=SourceGit
|
||||
Name=Source Git
|
||||
Comment=Open-source & Free Git GUI Client
|
||||
Exec=/opt/sourcegit/sourcegit
|
||||
Icon=/usr/share/icons/sourcegit.png
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
<string>SOURCE_GIT_VERSION.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
</dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SourceGit</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
|
|
708
build/resources/appimage/publish-appimage
Executable file
708
build/resources/appimage/publish-appimage
Executable file
|
@ -0,0 +1,708 @@
|
|||
#!/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
|
140
build/resources/appimage/publish-appimage.conf
Normal file
140
build/resources/appimage/publish-appimage.conf
Normal file
|
@ -0,0 +1,140 @@
|
|||
################################################################################
|
||||
# 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
|
BIN
build/resources/appimage/runtime-x86_64
Executable file
BIN
build/resources/appimage/runtime-x86_64
Executable file
Binary file not shown.
|
@ -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,9 +8,8 @@
|
|||
<description>
|
||||
<p>Open-source GUI client for git users</p>
|
||||
</description>
|
||||
<url type="homepage">https://github.com/sourcegit-scm/sourcegit</url>
|
||||
<launchable type="desktop-id">com.sourcegit_scm.SourceGit.desktop</launchable>
|
||||
<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>
|
|
@ -1,8 +1,7 @@
|
|||
Package: sourcegit
|
||||
Version: 2025.10
|
||||
Version: 8.18
|
||||
Priority: optional
|
||||
Depends: libx11-6, libice6, libsm6, libicu | libicu76 | libicu74 | libicu72 | libicu71 | libicu70 | libicu69 | libicu68 | libicu67 | libicu66 | libicu65 | libicu63 | libicu60 | libicu57 | libicu55 | libicu52, xdg-utils
|
||||
Depends: libx11-6, libice6, libsm6
|
||||
Architecture: amd64
|
||||
Installed-Size: 60440
|
||||
Maintainer: longshuang@msn.cn
|
||||
Description: Open-source & Free Git GUI Client
|
||||
|
|
5
build/resources/deb/DEBIAN/postinst
Executable file
5
build/resources/deb/DEBIAN/postinst
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!bin/sh
|
||||
|
||||
echo 'Create link on /usr/bin'
|
||||
ln -s /opt/sourcegit/sourcegit /usr/bin/sourcegit
|
||||
exit 0
|
4
build/resources/deb/DEBIAN/postrm
Executable file
4
build/resources/deb/DEBIAN/postrm
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!bin/sh
|
||||
|
||||
rm -f /usr/bin/sourcegit
|
||||
exit 0
|
|
@ -1,32 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <new-preinst> `install'
|
||||
# * <new-preinst> `install' <old-version>
|
||||
# * <new-preinst> `upgrade' <old-version>
|
||||
# * <old-preinst> `abort-upgrade' <new-version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/
|
||||
|
||||
case "$1" in
|
||||
install|upgrade)
|
||||
# Check if SourceGit is running and stop it
|
||||
if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then
|
||||
echo "Stopping running SourceGit instance..."
|
||||
pkill -f '/opt/sourcegit/sourcegit' || true
|
||||
# Give the process a moment to terminate
|
||||
sleep 1
|
||||
fi
|
||||
;;
|
||||
|
||||
abort-upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "preinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
|
@ -1,35 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <prerm> `remove'
|
||||
# * <old-prerm> `upgrade' <new-version>
|
||||
# * <new-prerm> `failed-upgrade' <old-version>
|
||||
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
|
||||
# * <deconfigured's-prerm> `deconfigure' `in-favour'
|
||||
# <package-being-installed> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
case "$1" in
|
||||
remove|upgrade|deconfigure)
|
||||
if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then
|
||||
echo "Stopping running SourceGit instance..."
|
||||
pkill -f '/opt/sourcegit/sourcegit' || true
|
||||
# Give the process a moment to terminate
|
||||
sleep 1
|
||||
fi
|
||||
;;
|
||||
|
||||
failed-upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "prerm called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
|
@ -5,10 +5,8 @@ Summary: Open-source & Free Git Gui Client
|
|||
License: MIT
|
||||
URL: https://sourcegit-scm.github.io/
|
||||
Source: https://github.com/sourcegit-scm/sourcegit/archive/refs/tags/v%_version.tar.gz
|
||||
Requires: libX11.so.6()(%{__isa_bits}bit)
|
||||
Requires: libSM.so.6()(%{__isa_bits}bit)
|
||||
Requires: libicu
|
||||
Requires: xdg-utils
|
||||
Requires: libX11.so.6
|
||||
Requires: libSM.so.6
|
||||
|
||||
%define _build_id_links none
|
||||
|
||||
|
@ -16,23 +14,24 @@ Requires: xdg-utils
|
|||
Open-source & Free Git Gui Client
|
||||
|
||||
%install
|
||||
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 -rsf %{buildroot}/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
|
||||
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
|
||||
|
||||
%files
|
||||
%dir /opt/sourcegit/
|
||||
/opt/sourcegit/*
|
||||
/usr/share/applications/sourcegit.desktop
|
||||
/usr/share/icons/*
|
||||
%{_bindir}/sourcegit
|
||||
/opt/sourcegit
|
||||
/usr/share
|
||||
|
||||
%post
|
||||
ln -s /opt/sourcegit/sourcegit /usr/bin/sourcegit
|
||||
|
||||
%postun
|
||||
rm -f /usr/bin/sourcegit
|
||||
|
||||
%changelog
|
||||
# skip
|
||||
# skip
|
|
@ -1,83 +0,0 @@
|
|||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const xml2js = require('xml2js');
|
||||
|
||||
const repoRoot = path.join(__dirname, '../../');
|
||||
const localesDir = path.join(repoRoot, 'src/Resources/Locales');
|
||||
const enUSFile = path.join(localesDir, 'en_US.axaml');
|
||||
const outputFile = path.join(repoRoot, 'TRANSLATION.md');
|
||||
|
||||
const parser = new xml2js.Parser();
|
||||
|
||||
async function parseXml(filePath) {
|
||||
const data = await fs.readFile(filePath);
|
||||
return parser.parseStringPromise(data);
|
||||
}
|
||||
|
||||
async function filterAndSortTranslations(localeData, enUSKeys, enUSData) {
|
||||
const strings = localeData.ResourceDictionary['x:String'];
|
||||
// Remove keys that don't exist in English file
|
||||
const filtered = strings.filter(item => enUSKeys.has(item.$['x:Key']));
|
||||
|
||||
// Sort based on the key order in English file
|
||||
const enUSKeysArray = enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']);
|
||||
filtered.sort((a, b) => {
|
||||
const aIndex = enUSKeysArray.indexOf(a.$['x:Key']);
|
||||
const bIndex = enUSKeysArray.indexOf(b.$['x:Key']);
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
async function calculateTranslationRate() {
|
||||
const enUSData = await parseXml(enUSFile);
|
||||
const enUSKeys = new Set(enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||
const files = (await fs.readdir(localesDir)).filter(file => file !== 'en_US.axaml' && file.endsWith('.axaml'));
|
||||
|
||||
const lines = [];
|
||||
|
||||
lines.push('# Translation Status');
|
||||
lines.push('This document shows the translation status of each locale file in the repository.');
|
||||
lines.push(`## Details`);
|
||||
lines.push(`### `);
|
||||
|
||||
for (const file of files) {
|
||||
const locale = file.replace('.axaml', '').replace('_', '__');
|
||||
const filePath = path.join(localesDir, file);
|
||||
const localeData = await parseXml(filePath);
|
||||
const localeKeys = new Set(localeData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||
const missingKeys = [...enUSKeys].filter(key => !localeKeys.has(key));
|
||||
|
||||
// Sort and clean up extra translations
|
||||
const sortedAndCleaned = await filterAndSortTranslations(localeData, enUSKeys, enUSData);
|
||||
localeData.ResourceDictionary['x:String'] = sortedAndCleaned;
|
||||
|
||||
// Save the updated file
|
||||
const builder = new xml2js.Builder({
|
||||
headless: true,
|
||||
renderOpts: { pretty: true, indent: ' ' }
|
||||
});
|
||||
let xmlStr = builder.buildObject(localeData);
|
||||
|
||||
// Add an empty line before the first x:String
|
||||
xmlStr = xmlStr.replace(' <x:String', '\n <x:String');
|
||||
await fs.writeFile(filePath, xmlStr + '\n', 'utf8');
|
||||
|
||||
if (missingKeys.length > 0) {
|
||||
const progress = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
|
||||
const badgeColor = progress >= 75 ? 'yellow' : 'red';
|
||||
|
||||
lines.push(`### }%25-${badgeColor})`);
|
||||
lines.push(`<details>\n<summary>Missing keys in ${file}</summary>\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n</details>`)
|
||||
} else {
|
||||
lines.push(`### `);
|
||||
}
|
||||
}
|
||||
|
||||
const content = lines.join('\n\n');
|
||||
console.log(content);
|
||||
await fs.writeFile(outputFile, content, 'utf8');
|
||||
}
|
||||
|
||||
calculateTranslationRate().catch(err => console.error(err));
|
|
@ -1,70 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -o
|
||||
set -u
|
||||
set pipefail
|
||||
|
||||
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 -rsf resources/deb/opt/sourcegit/sourcegit resources/deb/usr/bin
|
||||
cp -r resources/_common/applications resources/deb/usr/share
|
||||
cp -r resources/_common/icons resources/deb/usr/share
|
||||
# Calculate installed size in KB
|
||||
installed_size=$(du -sk resources/deb | cut -f1)
|
||||
# Update the control file
|
||||
sed -i -e "s/^Version:.*/Version: $VERSION/" \
|
||||
-e "s/^Architecture:.*/Architecture: $arch/" \
|
||||
-e "s/^Installed-Size:.*/Installed-Size: $installed_size/" \
|
||||
resources/deb/DEBIAN/control
|
||||
# Build deb package with gzip compression
|
||||
dpkg-deb -Zgzip --root-owner-group --build resources/deb "sourcegit_$VERSION-1_$arch.deb"
|
||||
|
||||
rpmbuild -bb --target="$target" resources/rpm/SPECS/build.spec --define "_topdir $(pwd)/resources/rpm" --define "_version $VERSION"
|
||||
mv "resources/rpm/RPMS/$target/sourcegit-$VERSION-1.$target.rpm" ./
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -o
|
||||
set -u
|
||||
set pipefail
|
||||
|
||||
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
|
||||
rm -rf SourceGit.app/Contents/MacOS/SourceGit.dsym
|
||||
|
||||
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit.app
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -o
|
||||
set -u
|
||||
set pipefail
|
||||
|
||||
cd build
|
||||
|
||||
rm -rf SourceGit/*.pdb
|
||||
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
|
||||
powershell -Command "Compress-Archive -Path SourceGit -DestinationPath \"sourcegit_$VERSION.$RUNTIME.zip\" -Force"
|
||||
else
|
||||
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
|
||||
fi
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "9.0.0",
|
||||
"version": "8.0.0",
|
||||
"rollForward": "latestMajor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 804 KiB After Width: | Height: | Size: 329 KiB |
Binary file not shown.
Before Width: | Height: | Size: 712 KiB After Width: | Height: | Size: 334 KiB |
|
@ -1,58 +0,0 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit
|
||||
{
|
||||
public partial class App
|
||||
{
|
||||
public class Command : ICommand
|
||||
{
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public Command(Action<object> action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public bool CanExecute(object parameter) => _action != null;
|
||||
public void Execute(object parameter) => _action?.Invoke(parameter);
|
||||
|
||||
private Action<object> _action = null;
|
||||
}
|
||||
|
||||
public static bool IsCheckForUpdateCommandVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
#if DISABLE_UPDATE_DETECTION
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly Command OpenPreferencesCommand = new Command(_ => ShowWindow(new Views.Preferences(), false));
|
||||
public static readonly Command OpenHotkeysCommand = new Command(_ => ShowWindow(new Views.Hotkeys(), false));
|
||||
public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir));
|
||||
public static readonly Command OpenAboutCommand = new Command(_ => ShowWindow(new Views.About(), false));
|
||||
public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true));
|
||||
public static readonly Command QuitCommand = new Command(_ => Quit(0));
|
||||
public static readonly Command CopyTextBlockCommand = new Command(p =>
|
||||
{
|
||||
var textBlock = p as TextBlock;
|
||||
if (textBlock == null)
|
||||
return;
|
||||
|
||||
if (textBlock.Inlines is { Count: > 0 } inlines)
|
||||
CopyText(inlines.Text);
|
||||
else if (!string.IsNullOrEmpty(textBlock.Text))
|
||||
CopyText(textBlock.Text);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -20,6 +20,20 @@ namespace SourceGit
|
|||
}
|
||||
}
|
||||
|
||||
public class FontFamilyConverter : JsonConverter<FontFamily>
|
||||
{
|
||||
public override FontFamily Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var name = reader.GetString();
|
||||
return new FontFamily(name);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, FontFamily value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public class GridLengthConverter : JsonConverter<GridLength>
|
||||
{
|
||||
public override GridLength Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
|
@ -40,15 +54,15 @@ namespace SourceGit
|
|||
IgnoreReadOnlyProperties = true,
|
||||
Converters = [
|
||||
typeof(ColorConverter),
|
||||
typeof(FontFamilyConverter),
|
||||
typeof(GridLengthConverter),
|
||||
]
|
||||
)]
|
||||
[JsonSerializable(typeof(Models.ExternalToolPaths))]
|
||||
[JsonSerializable(typeof(Models.InteractiveRebaseJobCollection))]
|
||||
[JsonSerializable(typeof(Models.JetBrainsState))]
|
||||
[JsonSerializable(typeof(Models.ThemeOverrides))]
|
||||
[JsonSerializable(typeof(Models.Version))]
|
||||
[JsonSerializable(typeof(Models.RepositorySettings))]
|
||||
[JsonSerializable(typeof(ViewModels.Preferences))]
|
||||
[JsonSerializable(typeof(ViewModels.Preference))]
|
||||
internal partial class JsonCodeGen : JsonSerializerContext { }
|
||||
}
|
||||
|
|
|
@ -13,21 +13,15 @@
|
|||
|
||||
<ResourceInclude x:Key="de_DE" Source="/Resources/Locales/de_DE.axaml"/>
|
||||
<ResourceInclude x:Key="en_US" Source="/Resources/Locales/en_US.axaml"/>
|
||||
<ResourceInclude x:Key="fr_FR" Source="/Resources/Locales/fr_FR.axaml"/>
|
||||
<ResourceInclude x:Key="it_IT" Source="/Resources/Locales/it_IT.axaml"/>
|
||||
<ResourceInclude x:Key="pt_BR" Source="/Resources/Locales/pt_BR.axaml"/>
|
||||
<ResourceInclude x:Key="uk_UA" Source="/Resources/Locales/uk_UA.axaml"/>
|
||||
<ResourceInclude x:Key="ru_RU" Source="/Resources/Locales/ru_RU.axaml"/>
|
||||
<ResourceInclude x:Key="zh_CN" Source="/Resources/Locales/zh_CN.axaml"/>
|
||||
<ResourceInclude x:Key="zh_TW" Source="/Resources/Locales/zh_TW.axaml"/>
|
||||
<ResourceInclude x:Key="es_ES" Source="/Resources/Locales/es_ES.axaml"/>
|
||||
<ResourceInclude x:Key="ja_JP" Source="/Resources/Locales/ja_JP.axaml"/>
|
||||
<ResourceInclude x:Key="ta_IN" Source="/Resources/Locales/ta_IN.axaml"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||
<StyleInclude Source="/Resources/Styles.axaml"/>
|
||||
</Application.Styles>
|
||||
|
@ -35,11 +29,10 @@
|
|||
<NativeMenu.Menu>
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Header="{DynamicResource Text.About.Menu}" Command="{x:Static s:App.OpenAboutCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}" Gesture="F1"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}" IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" Gesture="⌘+,"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.OpenAppDataDir}" Command="{x:Static s:App.OpenAppDataDirCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" Gesture="⌘+,"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}" Gesture="⌘+Q"/>
|
||||
</NativeMenu>
|
||||
|
|
542
src/App.axaml.cs
542
src/App.axaml.cs
|
@ -1,14 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
@ -17,20 +15,35 @@ using Avalonia.Data.Core.Plugins;
|
|||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Fonts;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit
|
||||
{
|
||||
public class SimpleCommand : ICommand
|
||||
{
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public SimpleCommand(Action action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public bool CanExecute(object parameter) => _action != null;
|
||||
public void Execute(object parameter) => _action?.Invoke();
|
||||
|
||||
private Action _action = null;
|
||||
}
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
#region App Entry Point
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Native.OS.SetupDataDir();
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
|
||||
{
|
||||
LogException(e.ExceptionObject as Exception);
|
||||
|
@ -38,14 +51,15 @@ namespace SourceGit
|
|||
|
||||
TaskScheduler.UnobservedTaskException += (_, e) =>
|
||||
{
|
||||
LogException(e.Exception);
|
||||
e.SetObserved();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (TryLaunchAsRebaseTodoEditor(args, out int exitTodo))
|
||||
if (TryLaunchedAsRebaseTodoEditor(args, out int exitTodo))
|
||||
Environment.Exit(exitTodo);
|
||||
else if (TryLaunchAsRebaseMessageEditor(args, out int exitMessage))
|
||||
else if (TryLaunchedAsRebaseMessageEditor(args, out int exitMessage))
|
||||
Environment.Exit(exitMessage);
|
||||
else
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
@ -61,11 +75,6 @@ namespace SourceGit
|
|||
var builder = AppBuilder.Configure<App>();
|
||||
builder.UsePlatformDetect();
|
||||
builder.LogToTrace();
|
||||
builder.WithInterFont();
|
||||
builder.With(new FontManagerOptions()
|
||||
{
|
||||
DefaultFamilyName = "fonts:Inter#Inter"
|
||||
});
|
||||
builder.ConfigureFonts(manager =>
|
||||
{
|
||||
var monospace = new EmbeddedFontCollection(
|
||||
|
@ -78,72 +87,42 @@ namespace SourceGit
|
|||
return builder;
|
||||
}
|
||||
|
||||
public static void LogException(Exception ex)
|
||||
public static readonly SimpleCommand OpenPreferenceCommand = new SimpleCommand(() =>
|
||||
{
|
||||
if (ex == null)
|
||||
var toplevel = GetTopLevel() as Window;
|
||||
if (toplevel == null)
|
||||
return;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n");
|
||||
builder.Append("----------------------------\n");
|
||||
builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
|
||||
builder.Append($"OS: {Environment.OSVersion}\n");
|
||||
builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
|
||||
builder.Append($"Source: {ex.Source}\n");
|
||||
builder.Append($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}\n");
|
||||
builder.Append($"User: {Environment.UserName}\n");
|
||||
builder.Append($"App Start Time: {Process.GetCurrentProcess().StartTime}\n");
|
||||
builder.Append($"Exception Time: {DateTime.Now}\n");
|
||||
builder.Append($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB\n");
|
||||
builder.Append($"---------------------------\n\n");
|
||||
builder.Append(ex);
|
||||
var dialog = new Views.Preference();
|
||||
dialog.ShowDialog(toplevel);
|
||||
});
|
||||
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
|
||||
File.WriteAllText(file, builder.ToString());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Utility Functions
|
||||
public static void ShowWindow(object data, bool showAsDialog)
|
||||
public static readonly SimpleCommand OpenHotkeysCommand = new SimpleCommand(() =>
|
||||
{
|
||||
var impl = (Views.ChromelessWindow target, bool isDialog) =>
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
{
|
||||
if (isDialog)
|
||||
target.ShowDialog(owner);
|
||||
else
|
||||
target.Show(owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Show();
|
||||
}
|
||||
};
|
||||
|
||||
if (data is Views.ChromelessWindow window)
|
||||
{
|
||||
impl(window, showAsDialog);
|
||||
return;
|
||||
}
|
||||
|
||||
var dataTypeName = data.GetType().FullName;
|
||||
if (string.IsNullOrEmpty(dataTypeName) || !dataTypeName.Contains(".ViewModels.", StringComparison.Ordinal))
|
||||
var toplevel = GetTopLevel() as Window;
|
||||
if (toplevel == null)
|
||||
return;
|
||||
|
||||
var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views.");
|
||||
var viewType = Type.GetType(viewTypeName);
|
||||
if (viewType == null || !viewType.IsSubclassOf(typeof(Views.ChromelessWindow)))
|
||||
var dialog = new Views.Hotkeys();
|
||||
dialog.ShowDialog(toplevel);
|
||||
});
|
||||
|
||||
public static readonly SimpleCommand OpenAboutCommand = new SimpleCommand(() =>
|
||||
{
|
||||
var toplevel = GetTopLevel() as Window;
|
||||
if (toplevel == null)
|
||||
return;
|
||||
|
||||
window = Activator.CreateInstance(viewType) as Views.ChromelessWindow;
|
||||
if (window != null)
|
||||
{
|
||||
window.DataContext = data;
|
||||
impl(window, showAsDialog);
|
||||
}
|
||||
}
|
||||
var dialog = new Views.About();
|
||||
dialog.ShowDialog(toplevel);
|
||||
});
|
||||
|
||||
public static readonly SimpleCommand CheckForUpdateCommand = new SimpleCommand(() =>
|
||||
{
|
||||
Check4Update(true);
|
||||
});
|
||||
|
||||
public static readonly SimpleCommand QuitCommand = new SimpleCommand(() => Quit(0));
|
||||
|
||||
public static void RaiseException(string context, string message)
|
||||
{
|
||||
|
@ -160,10 +139,7 @@ namespace SourceGit
|
|||
public static void SetLocale(string localeKey)
|
||||
{
|
||||
var app = Current as App;
|
||||
if (app == null)
|
||||
return;
|
||||
|
||||
var targetLocale = app.Resources[localeKey] as ResourceDictionary;
|
||||
var targetLocale = app?.Resources[localeKey] as ResourceDictionary;
|
||||
if (targetLocale == null || targetLocale == app._activeLocale)
|
||||
return;
|
||||
|
||||
|
@ -228,67 +204,12 @@ namespace SourceGit
|
|||
}
|
||||
}
|
||||
|
||||
public static void SetFonts(string defaultFont, string monospaceFont, bool onlyUseMonospaceFontInEditor)
|
||||
{
|
||||
var app = Current as App;
|
||||
if (app == null)
|
||||
return;
|
||||
|
||||
if (app._fontsOverrides != null)
|
||||
{
|
||||
app.Resources.MergedDictionaries.Remove(app._fontsOverrides);
|
||||
app._fontsOverrides = null;
|
||||
}
|
||||
|
||||
defaultFont = app.FixFontFamilyName(defaultFont);
|
||||
monospaceFont = app.FixFontFamilyName(monospaceFont);
|
||||
|
||||
var resDic = new ResourceDictionary();
|
||||
if (!string.IsNullOrEmpty(defaultFont))
|
||||
resDic.Add("Fonts.Default", new FontFamily(defaultFont));
|
||||
|
||||
if (string.IsNullOrEmpty(monospaceFont))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(defaultFont))
|
||||
{
|
||||
monospaceFont = $"fonts:SourceGit#JetBrains Mono,{defaultFont}";
|
||||
resDic.Add("Fonts.Monospace", new FontFamily(monospaceFont));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(defaultFont) && !monospaceFont.Contains(defaultFont, StringComparison.Ordinal))
|
||||
monospaceFont = $"{monospaceFont},{defaultFont}";
|
||||
|
||||
resDic.Add("Fonts.Monospace", new FontFamily(monospaceFont));
|
||||
}
|
||||
|
||||
if (onlyUseMonospaceFontInEditor)
|
||||
{
|
||||
if (string.IsNullOrEmpty(defaultFont))
|
||||
resDic.Add("Fonts.Primary", new FontFamily("fonts:Inter#Inter"));
|
||||
else
|
||||
resDic.Add("Fonts.Primary", new FontFamily(defaultFont));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(monospaceFont))
|
||||
resDic.Add("Fonts.Primary", new FontFamily(monospaceFont));
|
||||
}
|
||||
|
||||
if (resDic.Count > 0)
|
||||
{
|
||||
app.Resources.MergedDictionaries.Add(resDic);
|
||||
app._fontsOverrides = resDic;
|
||||
}
|
||||
}
|
||||
|
||||
public static async void CopyText(string data)
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (desktop.MainWindow?.Clipboard is { } clipboard)
|
||||
await clipboard.SetTextAsync(data ?? "");
|
||||
await clipboard.SetTextAsync(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,7 +222,7 @@ namespace SourceGit
|
|||
return await clipboard.GetTextAsync();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
public static string Text(string key, params object[] args)
|
||||
|
@ -323,25 +244,84 @@ namespace SourceGit
|
|||
icon.Height = 12;
|
||||
icon.Stretch = Stretch.Uniform;
|
||||
|
||||
if (Current?.FindResource(key) is StreamGeometry geo)
|
||||
var geo = Current?.FindResource(key) as StreamGeometry;
|
||||
if (geo != null)
|
||||
icon.Data = geo;
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
public static IStorageProvider GetStorageProvider()
|
||||
public static TopLevel GetTopLevel()
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
return desktop.MainWindow?.StorageProvider;
|
||||
{
|
||||
return desktop.MainWindow;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ViewModels.Launcher GetLauncher()
|
||||
public static void Check4Update(bool manually = false)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Fetch lastest release information.
|
||||
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(5) };
|
||||
var data = await client.GetStringAsync("https://sourcegit-scm.github.io/data/version.json");
|
||||
|
||||
// Parse json into Models.Version.
|
||||
var ver = JsonSerializer.Deserialize(data, JsonCodeGen.Default.Version);
|
||||
if (ver == null)
|
||||
return;
|
||||
|
||||
// Check if already up-to-date.
|
||||
if (!ver.IsNewVersion)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(new Models.AlreadyUpToDate());
|
||||
return;
|
||||
}
|
||||
|
||||
// Should not check ignored tag if this is called manually.
|
||||
if (!manually)
|
||||
{
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
if (ver.TagName == pref.IgnoreUpdateTag)
|
||||
return;
|
||||
}
|
||||
|
||||
ShowSelfUpdateResult(ver);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static ViewModels.Launcher GetLauncer()
|
||||
{
|
||||
return Current is App app ? app._launcher : null;
|
||||
}
|
||||
|
||||
public static ViewModels.Repository FindOpenedRepository(string repoPath)
|
||||
{
|
||||
if (Current is App app && app._launcher != null)
|
||||
{
|
||||
foreach (var page in app._launcher.Pages)
|
||||
{
|
||||
var id = page.Node.Id.Replace("\\", "/");
|
||||
if (id == repoPath && page.Data is ViewModels.Repository repo)
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Quit(int exitCode)
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
|
@ -354,19 +334,15 @@ namespace SourceGit
|
|||
Environment.Exit(exitCode);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
pref.PropertyChanged += (_, _) => pref.Save();
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
|
||||
SetLocale(pref.Locale);
|
||||
SetTheme(pref.Theme, pref.ThemeOverrides);
|
||||
SetFonts(pref.DefaultFontFamily, pref.MonospaceFontFamily, pref.OnlyUseMonoFontInEditor);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
|
@ -375,47 +351,59 @@ namespace SourceGit
|
|||
{
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
// Disable tooltip if window is not active.
|
||||
ToolTip.ToolTipOpeningEvent.AddClassHandler<Control>((c, e) =>
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(c);
|
||||
if (topLevel is not Window { IsActive: true })
|
||||
e.Cancel = true;
|
||||
});
|
||||
|
||||
if (TryLaunchAsCoreEditor(desktop))
|
||||
if (TryLaunchedAsCoreEditor(desktop))
|
||||
return;
|
||||
|
||||
if (TryLaunchAsAskpass(desktop))
|
||||
if (TryLaunchedAsAskpass(desktop))
|
||||
return;
|
||||
|
||||
_ipcChannel = new Models.IpcChannel();
|
||||
if (!_ipcChannel.IsFirstInstance)
|
||||
{
|
||||
var arg = desktop.Args is { Length: > 0 } ? desktop.Args[0].Trim() : string.Empty;
|
||||
if (!string.IsNullOrEmpty(arg))
|
||||
{
|
||||
if (arg.StartsWith('"') && arg.EndsWith('"'))
|
||||
arg = arg.Substring(1, arg.Length - 2).Trim();
|
||||
|
||||
if (arg.Length > 0 && !Path.IsPathFullyQualified(arg))
|
||||
arg = Path.GetFullPath(arg);
|
||||
}
|
||||
|
||||
_ipcChannel.SendToFirstInstance(arg);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ipcChannel.MessageReceived += TryOpenRepository;
|
||||
desktop.Exit += (_, _) => _ipcChannel.Dispose();
|
||||
TryLaunchAsNormal(desktop);
|
||||
}
|
||||
TryLaunchedAsNormal(desktop);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
|
||||
private static void LogException(Exception ex)
|
||||
{
|
||||
if (ex == null)
|
||||
return;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n");
|
||||
builder.Append("----------------------------\n");
|
||||
builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
|
||||
builder.Append($"OS: {Environment.OSVersion.ToString()}\n");
|
||||
builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
|
||||
builder.Append($"Source: {ex.Source}\n");
|
||||
builder.Append($"---------------------------\n\n");
|
||||
builder.Append(ex.StackTrace);
|
||||
while (ex.InnerException != null)
|
||||
{
|
||||
ex = ex.InnerException;
|
||||
builder.Append($"\n\nInnerException::: {ex.GetType().FullName}: {ex.Message}\n");
|
||||
builder.Append(ex.StackTrace);
|
||||
}
|
||||
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
|
||||
File.WriteAllText(file, builder.ToString());
|
||||
}
|
||||
|
||||
private static void ShowSelfUpdateResult(object data)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: not null } desktop)
|
||||
{
|
||||
var dialog = new Views.SelfUpdate()
|
||||
{
|
||||
DataContext = new ViewModels.SelfUpdate() { Data = data }
|
||||
};
|
||||
|
||||
dialog.Show(desktop.MainWindow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static bool TryLaunchedAsRebaseTodoEditor(string[] args, out int exitCode)
|
||||
{
|
||||
exitCode = -1;
|
||||
|
||||
|
@ -468,57 +456,39 @@ namespace SourceGit
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool TryLaunchAsRebaseMessageEditor(string[] args, out int exitCode)
|
||||
private static bool TryLaunchedAsRebaseMessageEditor(string[] args, out int exitCode)
|
||||
{
|
||||
exitCode = -1;
|
||||
|
||||
if (args.Length <= 1 || !args[0].Equals("--rebase-message-editor", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
exitCode = 0;
|
||||
|
||||
var file = args[1];
|
||||
var filename = Path.GetFileName(file);
|
||||
if (!filename.Equals("COMMIT_EDITMSG", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
var gitDir = Path.GetDirectoryName(file)!;
|
||||
var origHeadFile = Path.Combine(gitDir, "rebase-merge", "orig-head");
|
||||
var ontoFile = Path.Combine(gitDir, "rebase-merge", "onto");
|
||||
var doneFile = Path.Combine(gitDir, "rebase-merge", "done");
|
||||
var jobsFile = Path.Combine(gitDir, "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(ontoFile) || !File.Exists(origHeadFile) || !File.Exists(doneFile) || !File.Exists(jobsFile))
|
||||
var jobsFile = Path.Combine(Path.GetDirectoryName(file)!, "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(jobsFile))
|
||||
return true;
|
||||
|
||||
var origHead = File.ReadAllText(origHeadFile).Trim();
|
||||
var onto = File.ReadAllText(ontoFile).Trim();
|
||||
var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection);
|
||||
if (!collection.Onto.Equals(onto) || !collection.OrigHead.Equals(origHead))
|
||||
var doneFile = Path.Combine(Path.GetDirectoryName(file)!, "rebase-merge", "done");
|
||||
if (!File.Exists(doneFile))
|
||||
return true;
|
||||
|
||||
var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length == 0)
|
||||
var done = File.ReadAllText(doneFile).Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length > collection.Jobs.Count)
|
||||
return true;
|
||||
|
||||
var current = done[^1].Trim();
|
||||
var match = REG_REBASE_TODO().Match(current);
|
||||
if (!match.Success)
|
||||
return true;
|
||||
|
||||
var sha = match.Groups[1].Value;
|
||||
foreach (var job in collection.Jobs)
|
||||
{
|
||||
if (job.SHA.StartsWith(sha))
|
||||
{
|
||||
File.WriteAllText(file, job.Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var job = collection.Jobs[done.Length - 1];
|
||||
File.WriteAllText(file, job.Message);
|
||||
|
||||
exitCode = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private bool TryLaunchedAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var args = desktop.Args;
|
||||
if (args == null || args.Length <= 1 || !args[0].Equals("--core-editor", StringComparison.Ordinal))
|
||||
|
@ -526,181 +496,49 @@ namespace SourceGit
|
|||
|
||||
var file = args[1];
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
desktop.Shutdown(-1);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
desktop.MainWindow = new Views.StandaloneCommitMessageEditor(file);
|
||||
|
||||
var editor = new Views.StandaloneCommitMessageEditor();
|
||||
editor.SetFile(file);
|
||||
desktop.MainWindow = editor;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchAsAskpass(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private bool TryLaunchedAsAskpass(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var launchAsAskpass = Environment.GetEnvironmentVariable("SOURCEGIT_LAUNCH_AS_ASKPASS");
|
||||
if (launchAsAskpass is not "TRUE")
|
||||
var args = desktop.Args;
|
||||
if (args == null || args.Length != 1)
|
||||
return false;
|
||||
|
||||
var args = desktop.Args;
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
var askpass = new Views.Askpass();
|
||||
askpass.TxtDescription.Text = args[0];
|
||||
desktop.MainWindow = askpass;
|
||||
return true;
|
||||
}
|
||||
var param = args[0];
|
||||
if (!param.StartsWith("enter passphrase", StringComparison.OrdinalIgnoreCase) &&
|
||||
!param.Contains(" password", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
return false;
|
||||
desktop.MainWindow = new Views.Askpass(param);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private void TryLaunchedAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
Native.OS.SetupExternalTools();
|
||||
Models.AvatarManager.Instance.Start();
|
||||
Native.OS.SetupEnternalTools();
|
||||
|
||||
string startupRepo = null;
|
||||
if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0]))
|
||||
startupRepo = desktop.Args[0];
|
||||
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
pref.SetCanModify();
|
||||
|
||||
_launcher = new ViewModels.Launcher(startupRepo);
|
||||
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
||||
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
||||
|
||||
#if !DISABLE_UPDATE_DETECTION
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
if (pref.ShouldCheck4UpdateOnStartup())
|
||||
{
|
||||
pref.Save();
|
||||
Check4Update();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void TryOpenRepository(string repo)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(repo) && Directory.Exists(repo))
|
||||
{
|
||||
var test = new Commands.QueryRepositoryRootPath(repo).ReadToEnd();
|
||||
if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut))
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
var node = ViewModels.Preferences.Instance.FindOrAddNodeByRepositoryPath(test.StdOut.Trim(), null, false);
|
||||
ViewModels.Welcome.Instance.Refresh();
|
||||
_launcher?.OpenRepositoryInTab(node, null);
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: Views.Launcher wnd })
|
||||
wnd.BringToTop();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: Views.Launcher launcher })
|
||||
launcher.BringToTop();
|
||||
});
|
||||
}
|
||||
|
||||
private void Check4Update(bool manually = false)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Fetch latest release information.
|
||||
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(5) };
|
||||
var data = await client.GetStringAsync("https://sourcegit-scm.github.io/data/version.json");
|
||||
|
||||
// Parse JSON into Models.Version.
|
||||
var ver = JsonSerializer.Deserialize(data, JsonCodeGen.Default.Version);
|
||||
if (ver == null)
|
||||
return;
|
||||
|
||||
// Check if already up-to-date.
|
||||
if (!ver.IsNewVersion)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(new Models.AlreadyUpToDate());
|
||||
return;
|
||||
}
|
||||
|
||||
// Should not check ignored tag if this is called manually.
|
||||
if (!manually)
|
||||
{
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
if (ver.TagName == pref.IgnoreUpdateTag)
|
||||
return;
|
||||
}
|
||||
|
||||
ShowSelfUpdateResult(ver);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(new Models.SelfUpdateFailed(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowSelfUpdateResult(object data)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ShowWindow(new ViewModels.SelfUpdate() { Data = data }, true);
|
||||
});
|
||||
}
|
||||
|
||||
private string FixFontFamilyName(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return string.Empty;
|
||||
|
||||
var parts = input.Split(',');
|
||||
var trimmed = new List<string>();
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var t = part.Trim();
|
||||
if (string.IsNullOrEmpty(t))
|
||||
continue;
|
||||
|
||||
// Collapse multiple spaces into single space
|
||||
var prevChar = '\0';
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var c in t)
|
||||
{
|
||||
if (c == ' ' && prevChar == ' ')
|
||||
continue;
|
||||
sb.Append(c);
|
||||
prevChar = c;
|
||||
}
|
||||
|
||||
var name = sb.ToString();
|
||||
if (name.Contains('#', StringComparison.Ordinal))
|
||||
{
|
||||
if (!name.Equals("fonts:Inter#Inter", StringComparison.Ordinal) &&
|
||||
!name.Equals("fonts:SourceGit#JetBrains Mono", StringComparison.Ordinal))
|
||||
continue;
|
||||
}
|
||||
|
||||
trimmed.Add(name);
|
||||
}
|
||||
|
||||
return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^[a-z]+\s+([a-fA-F0-9]{4,40})(\s+.*)?$")]
|
||||
private static partial Regex REG_REBASE_TODO();
|
||||
|
||||
private Models.IpcChannel _ipcChannel = null;
|
||||
private ViewModels.Launcher _launcher = null;
|
||||
private ResourceDictionary _activeLocale = null;
|
||||
private ResourceDictionary _themeOverrides = null;
|
||||
private ResourceDictionary _fontsOverrides = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
Don't remove it as it might cause problems with window transparency and embeded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="SourceGit.Desktop"/>
|
||||
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
namespace SourceGit.Commands
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Add : Command
|
||||
{
|
||||
public Add(string repo, bool includeUntracked)
|
||||
public Add(string repo, List<Models.Change> changes = null)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = includeUntracked ? "add ." : "add -u .";
|
||||
}
|
||||
|
||||
public Add(string repo, Models.Change change)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"add -- \"{change.Path}\"";
|
||||
}
|
||||
|
||||
public Add(string repo, string pathspecFromFile)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"add --pathspec-from-file=\"{pathspecFromFile}\"";
|
||||
if (changes == null || changes.Count == 0)
|
||||
{
|
||||
Args = "add .";
|
||||
}
|
||||
else
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("add --");
|
||||
foreach (var c in changes)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(c.Path);
|
||||
builder.Append("\"");
|
||||
}
|
||||
Args = builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
namespace SourceGit.Commands
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Archive : Command
|
||||
{
|
||||
public Archive(string repo, string revision, string saveTo)
|
||||
public Archive(string repo, string revision, string saveTo, Action<string> outputHandler)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = outputHandler;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,75 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class AssumeUnchanged : Command
|
||||
{
|
||||
public AssumeUnchanged(string repo, string file, bool bAdd)
|
||||
{
|
||||
var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged";
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"update-index {mode} -- \"{file}\"";
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class AssumeUnchanged
|
||||
{
|
||||
[GeneratedRegex(@"^(\w)\s+(.+)$")]
|
||||
private static partial Regex REG_PARSE();
|
||||
|
||||
class ViewCommand : Command
|
||||
{
|
||||
public ViewCommand(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = "ls-files -v";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
Exec();
|
||||
return _outs;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_PARSE().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
if (match.Groups[1].Value == "h")
|
||||
{
|
||||
_outs.Add(match.Groups[2].Value);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<string> _outs = new List<string>();
|
||||
}
|
||||
|
||||
class ModCommand : Command
|
||||
{
|
||||
public ModCommand(string repo, string file, bool bAdd)
|
||||
{
|
||||
var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged";
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"update-index {mode} -- \"{file}\"";
|
||||
}
|
||||
}
|
||||
|
||||
public AssumeUnchanged(string repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public List<string> View()
|
||||
{
|
||||
return new ViewCommand(_repo).Result();
|
||||
}
|
||||
|
||||
public void Add(string file)
|
||||
{
|
||||
new ModCommand(_repo, file, true).Exec();
|
||||
}
|
||||
|
||||
public void Remove(string file)
|
||||
{
|
||||
new ModCommand(_repo, file, false).Exec();
|
||||
}
|
||||
|
||||
private readonly string _repo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Bisect : Command
|
||||
{
|
||||
public Bisect(string repo, string subcmd)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
RaiseError = false;
|
||||
Args = $"bisect {subcmd}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,17 +21,10 @@ namespace SourceGit.Commands
|
|||
|
||||
public Models.BlameData Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return _result;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
var succ = Exec();
|
||||
if (!succ)
|
||||
{
|
||||
ParseLine(line);
|
||||
|
||||
if (_result.IsBinary)
|
||||
break;
|
||||
return new Models.BlameData();
|
||||
}
|
||||
|
||||
if (_needUnifyCommitSHA)
|
||||
|
@ -49,9 +42,14 @@ namespace SourceGit.Commands
|
|||
return _result;
|
||||
}
|
||||
|
||||
private void ParseLine(string line)
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
if (line.Contains('\0', StringComparison.Ordinal))
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
if (string.IsNullOrEmpty(line))
|
||||
return;
|
||||
|
||||
if (line.IndexOf('\0', StringComparison.Ordinal) >= 0)
|
||||
{
|
||||
_result.IsBinary = true;
|
||||
_result.LineInfos.Clear();
|
||||
|
@ -67,7 +65,7 @@ namespace SourceGit.Commands
|
|||
var commit = match.Groups[1].Value;
|
||||
var author = match.Groups[2].Value;
|
||||
var timestamp = int.Parse(match.Groups[3].Value);
|
||||
var when = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString(_dateFormat);
|
||||
var when = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString("yyyy/MM/dd");
|
||||
|
||||
var info = new Models.BlameLineInfo()
|
||||
{
|
||||
|
@ -89,7 +87,6 @@ namespace SourceGit.Commands
|
|||
|
||||
private readonly Models.BlameData _result = new Models.BlameData();
|
||||
private readonly StringBuilder _content = new StringBuilder();
|
||||
private readonly string _dateFormat = Models.DateTimeFormat.Active.DateOnly;
|
||||
private string _lastSHA = string.Empty;
|
||||
private bool _needUnifyCommitSHA = false;
|
||||
private int _minSHALen = 64;
|
||||
|
|
|
@ -1,52 +1,30 @@
|
|||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Branch
|
||||
{
|
||||
public static string ShowCurrent(string repo)
|
||||
public static bool Create(string repo, string name, string basedOn)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch --show-current";
|
||||
return cmd.ReadToEnd().StdOut.Trim();
|
||||
}
|
||||
|
||||
public static bool Create(string repo, string name, string basedOn, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("branch ");
|
||||
if (force)
|
||||
builder.Append("-f ");
|
||||
builder.Append(name);
|
||||
builder.Append(" ");
|
||||
builder.Append(basedOn);
|
||||
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = builder.ToString();
|
||||
cmd.Log = log;
|
||||
cmd.Args = $"branch {name} {basedOn}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool Rename(string repo, string name, string to, Models.ICommandLog log)
|
||||
public static bool Rename(string repo, string name, string to)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch -M {name} {to}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool SetUpstream(string repo, string name, string upstream, Models.ICommandLog log)
|
||||
public static bool SetUpstream(string repo, string name, string upstream)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Log = log;
|
||||
|
||||
if (string.IsNullOrEmpty(upstream))
|
||||
cmd.Args = $"branch {name} --unset-upstream";
|
||||
|
@ -56,27 +34,22 @@ namespace SourceGit.Commands
|
|||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool DeleteLocal(string repo, string name, Models.ICommandLog log)
|
||||
public static bool DeleteLocal(string repo, string name)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch -D {name}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool DeleteRemote(string repo, string remote, string name, Models.ICommandLog log)
|
||||
public static bool DeleteRemote(string repo, string remote, string name)
|
||||
{
|
||||
bool exists = new Remote(repo).HasBranch(remote, name);
|
||||
if (exists)
|
||||
return new Push(repo, remote, $"refs/heads/{name}", true) { Log = log }.Exec();
|
||||
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch -D -r {remote}/{name}";
|
||||
cmd.Log = log;
|
||||
cmd.SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
cmd.Args = $"push {remote} --delete {name}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
|
@ -11,43 +12,25 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
}
|
||||
|
||||
public bool Branch(string branch, bool force)
|
||||
public bool Branch(string branch, Action<string> onProgress)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --progress ");
|
||||
if (force)
|
||||
builder.Append("--force ");
|
||||
builder.Append(branch);
|
||||
|
||||
Args = builder.ToString();
|
||||
Args = $"checkout --progress {branch}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Branch(string branch, string basedOn, bool force, bool allowOverwrite)
|
||||
public bool Branch(string branch, string basedOn, Action<string> onProgress)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --progress ");
|
||||
if (force)
|
||||
builder.Append("--force ");
|
||||
builder.Append(allowOverwrite ? "-B " : "-b ");
|
||||
builder.Append(branch);
|
||||
builder.Append(" ");
|
||||
builder.Append(basedOn);
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Commit(string commitId, bool force)
|
||||
{
|
||||
var option = force ? "--force" : string.Empty;
|
||||
Args = $"checkout {option} --detach --progress {commitId}";
|
||||
Args = $"checkout --progress -b {branch} {basedOn}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool UseTheirs(List<string> files)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("checkout --theirs --");
|
||||
foreach (var f in files)
|
||||
{
|
||||
|
@ -61,7 +44,7 @@ namespace SourceGit.Commands
|
|||
|
||||
public bool UseMine(List<string> files)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("checkout --ours --");
|
||||
foreach (var f in files)
|
||||
{
|
||||
|
@ -75,8 +58,37 @@ namespace SourceGit.Commands
|
|||
|
||||
public bool FileWithRevision(string file, string revision)
|
||||
{
|
||||
Args = $"checkout --no-overlay {revision} -- \"{file}\"";
|
||||
Args = $"checkout {revision} -- \"{file}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Commit(string commitId, Action<string> onProgress)
|
||||
{
|
||||
Args = $"checkout --detach --progress {commitId}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Files(List<string> files)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("checkout -f -q --");
|
||||
foreach (var f in files)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(f);
|
||||
builder.Append("\"");
|
||||
}
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,12 @@
|
|||
{
|
||||
public class CherryPick : Command
|
||||
{
|
||||
public CherryPick(string repo, string commits, bool noCommit, bool appendSourceToMessage, string extraParams)
|
||||
public CherryPick(string repo, string commit, bool noCommit)
|
||||
{
|
||||
var mode = noCommit ? "-n" : "--ff";
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
Args = "cherry-pick ";
|
||||
if (noCommit)
|
||||
Args += "-n ";
|
||||
if (appendSourceToMessage)
|
||||
Args += "-x ";
|
||||
if (!string.IsNullOrEmpty(extraParams))
|
||||
Args += $"{extraParams} ";
|
||||
Args += commits;
|
||||
Args = $"cherry-pick {mode} {commit}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
namespace SourceGit.Commands
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Clean : Command
|
||||
{
|
||||
|
@ -6,7 +9,23 @@
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "clean -qfdx";
|
||||
Args = "clean -qfd";
|
||||
}
|
||||
|
||||
public Clean(string repo, List<string> files)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("clean -qfd --");
|
||||
foreach (var f in files)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(f);
|
||||
builder.Append("\"");
|
||||
}
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
namespace SourceGit.Commands
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Clone : Command
|
||||
{
|
||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs)
|
||||
private readonly Action<string> _notifyProgress;
|
||||
|
||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs, Action<string> ouputHandler)
|
||||
{
|
||||
Context = ctx;
|
||||
WorkingDirectory = path;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = sshKey;
|
||||
Args = "clone --progress --verbose ";
|
||||
Args = "clone --progress --verbose --recurse-submodules ";
|
||||
|
||||
if (!string.IsNullOrEmpty(extraArgs))
|
||||
Args += $"{extraArgs} ";
|
||||
|
@ -16,6 +21,13 @@
|
|||
|
||||
if (!string.IsNullOrEmpty(localName))
|
||||
Args += localName;
|
||||
|
||||
_notifyProgress = ouputHandler;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_notifyProgress?.Invoke(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
|
@ -11,11 +10,15 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class Command
|
||||
{
|
||||
public class CancelToken
|
||||
{
|
||||
public bool Requested { get; set; } = false;
|
||||
}
|
||||
|
||||
public class ReadToEndResult
|
||||
{
|
||||
public bool IsSuccess { get; set; } = false;
|
||||
public string StdOut { get; set; } = "";
|
||||
public string StdErr { get; set; } = "";
|
||||
public bool IsSuccess { get; set; }
|
||||
public string StdOut { get; set; }
|
||||
}
|
||||
|
||||
public enum EditorType
|
||||
|
@ -26,122 +29,19 @@ namespace SourceGit.Commands
|
|||
}
|
||||
|
||||
public string Context { get; set; } = string.Empty;
|
||||
public CancellationToken CancellationToken { get; set; } = CancellationToken.None;
|
||||
public CancelToken Cancel { get; set; } = null;
|
||||
public string WorkingDirectory { get; set; } = null;
|
||||
public EditorType Editor { get; set; } = EditorType.CoreEditor; // Only used in Exec() mode
|
||||
public string SSHKey { get; set; } = string.Empty;
|
||||
public string Args { get; set; } = string.Empty;
|
||||
public bool RaiseError { get; set; } = true;
|
||||
public Models.ICommandLog Log { get; set; } = null;
|
||||
public bool TraitErrorAsOutput { get; set; } = false;
|
||||
|
||||
public bool Exec()
|
||||
{
|
||||
Log?.AppendLine($"$ git {Args}\n");
|
||||
|
||||
var start = CreateGitStartInfo();
|
||||
var errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
|
||||
proc.OutputDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||
proc.ErrorDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||
|
||||
var dummy = null as Process;
|
||||
var dummyProcLock = new object();
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
|
||||
// It not safe, please only use `CancellationToken` in readonly commands.
|
||||
if (CancellationToken.CanBeCanceled)
|
||||
{
|
||||
dummy = proc;
|
||||
CancellationToken.Register(() =>
|
||||
{
|
||||
lock (dummyProcLock)
|
||||
{
|
||||
if (dummy is { HasExited: false })
|
||||
dummy.Kill();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (RaiseError)
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message));
|
||||
|
||||
Log?.AppendLine(string.Empty);
|
||||
return false;
|
||||
}
|
||||
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
if (dummy != null)
|
||||
{
|
||||
lock (dummyProcLock)
|
||||
{
|
||||
dummy = null;
|
||||
}
|
||||
}
|
||||
|
||||
int exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
Log?.AppendLine(string.Empty);
|
||||
|
||||
if (!CancellationToken.IsCancellationRequested && exitCode != 0)
|
||||
{
|
||||
if (RaiseError)
|
||||
{
|
||||
var errMsg = string.Join("\n", errs).Trim();
|
||||
if (!string.IsNullOrEmpty(errMsg))
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, errMsg));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ReadToEndResult ReadToEnd()
|
||||
{
|
||||
var start = CreateGitStartInfo();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ReadToEndResult()
|
||||
{
|
||||
IsSuccess = false,
|
||||
StdOut = string.Empty,
|
||||
StdErr = e.Message,
|
||||
};
|
||||
}
|
||||
|
||||
var rs = new ReadToEndResult()
|
||||
{
|
||||
StdOut = proc.StandardOutput.ReadToEnd(),
|
||||
StdErr = proc.StandardError.ReadToEnd(),
|
||||
};
|
||||
|
||||
proc.WaitForExit();
|
||||
rs.IsSuccess = proc.ExitCode == 0;
|
||||
proc.Close();
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
private ProcessStartInfo CreateGitStartInfo()
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Native.OS.GitExecutable;
|
||||
start.Arguments = "--no-pager -c core.quotepath=off -c credential.helper=manager ";
|
||||
start.Arguments = "--no-pager -c core.quotepath=off ";
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
|
@ -155,18 +55,16 @@ namespace SourceGit.Commands
|
|||
start.Environment.Add("DISPLAY", "required");
|
||||
start.Environment.Add("SSH_ASKPASS", selfExecFile); // Can not use parameter here, because it invoked by SSH with `exec`
|
||||
start.Environment.Add("SSH_ASKPASS_REQUIRE", "prefer");
|
||||
start.Environment.Add("SOURCEGIT_LAUNCH_AS_ASKPASS", "TRUE");
|
||||
|
||||
// If an SSH private key was provided, sets the environment.
|
||||
if (!start.Environment.ContainsKey("GIT_SSH_COMMAND") && !string.IsNullOrEmpty(SSHKey))
|
||||
start.Environment.Add("GIT_SSH_COMMAND", $"ssh -i '{SSHKey}'");
|
||||
if (!string.IsNullOrEmpty(SSHKey))
|
||||
start.Environment.Add("GIT_SSH_COMMAND", $"ssh -o StrictHostKeyChecking=accept-new -i '{SSHKey}'");
|
||||
else
|
||||
start.Arguments += "-c credential.helper=manager ";
|
||||
|
||||
// Force using en_US.UTF-8 locale
|
||||
// Force using en_US.UTF-8 locale to avoid GCM crash
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
start.Environment.Add("LANG", "C");
|
||||
start.Environment.Add("LC_ALL", "C");
|
||||
}
|
||||
start.Environment.Add("LANG", "en_US.UTF-8");
|
||||
|
||||
// Force using this app as git editor.
|
||||
switch (Editor)
|
||||
|
@ -189,31 +87,140 @@ namespace SourceGit.Commands
|
|||
if (!string.IsNullOrEmpty(WorkingDirectory))
|
||||
start.WorkingDirectory = WorkingDirectory;
|
||||
|
||||
return start;
|
||||
}
|
||||
var errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var isCancelled = false;
|
||||
|
||||
private void HandleOutput(string line, List<string> errs)
|
||||
{
|
||||
line ??= string.Empty;
|
||||
Log?.AppendLine(line);
|
||||
|
||||
// Lines to hide in error message.
|
||||
if (line.Length > 0)
|
||||
proc.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (line.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal) ||
|
||||
line.StartsWith("remote: Counting objects:", StringComparison.Ordinal) ||
|
||||
line.StartsWith("remote: Compressing objects:", StringComparison.Ordinal) ||
|
||||
line.StartsWith("Filtering content:", StringComparison.Ordinal) ||
|
||||
line.StartsWith("hint:", StringComparison.Ordinal))
|
||||
if (Cancel != null && Cancel.Requested)
|
||||
{
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited)
|
||||
proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (REG_PROGRESS().IsMatch(line))
|
||||
if (e.Data != null)
|
||||
OnReadline(e.Data);
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (Cancel != null && Cancel.Requested)
|
||||
{
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited)
|
||||
proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.Data))
|
||||
return;
|
||||
if (TraitErrorAsOutput)
|
||||
OnReadline(e.Data);
|
||||
|
||||
// Ignore progress messages
|
||||
if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("remote: Compressing objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("Filtering content:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (REG_PROGRESS().IsMatch(e.Data))
|
||||
return;
|
||||
errs.Add(e.Data);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (RaiseError)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(Context, e.Message);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
errs.Add(line);
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
int exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
|
||||
if (!isCancelled && exitCode != 0 && errs.Count > 0)
|
||||
{
|
||||
if (RaiseError)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(Context, string.Join("\n", errs));
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public ReadToEndResult ReadToEnd()
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Native.OS.GitExecutable;
|
||||
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
if (!string.IsNullOrEmpty(WorkingDirectory))
|
||||
start.WorkingDirectory = WorkingDirectory;
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ReadToEndResult()
|
||||
{
|
||||
IsSuccess = false,
|
||||
StdOut = string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
var rs = new ReadToEndResult()
|
||||
{
|
||||
StdOut = proc.StandardOutput.ReadToEnd(),
|
||||
};
|
||||
|
||||
proc.WaitForExit();
|
||||
rs.IsSuccess = proc.ExitCode == 0;
|
||||
proc.Close();
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
protected virtual void OnReadline(string line) { }
|
||||
|
||||
[GeneratedRegex(@"\d+%")]
|
||||
private static partial Regex REG_PROGRESS();
|
||||
}
|
||||
|
|
|
@ -4,36 +4,21 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class Commit : Command
|
||||
{
|
||||
public Commit(string repo, string message, bool signOff, bool amend, bool resetAuthor)
|
||||
public Commit(string repo, string message, bool autoStage, bool amend, bool allowEmpty = false)
|
||||
{
|
||||
_tmpFile = Path.GetTempFileName();
|
||||
File.WriteAllText(_tmpFile, message);
|
||||
var file = Path.GetTempFileName();
|
||||
File.WriteAllText(file, message);
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"commit --allow-empty --file=\"{_tmpFile}\"";
|
||||
if (signOff)
|
||||
Args += " --signoff";
|
||||
TraitErrorAsOutput = true;
|
||||
Args = $"commit --file=\"{file}\"";
|
||||
if (autoStage)
|
||||
Args += " --all";
|
||||
if (amend)
|
||||
Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit";
|
||||
Args += " --amend --no-edit";
|
||||
if (allowEmpty)
|
||||
Args += " --allow-empty";
|
||||
}
|
||||
|
||||
public bool Run()
|
||||
{
|
||||
var succ = Exec();
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(_tmpFile);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return succ;
|
||||
}
|
||||
|
||||
private readonly string _tmpFile;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,8 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class CompareRevisions : Command
|
||||
{
|
||||
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
|
||||
[GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
|
||||
private static partial Regex REG_RENAME_FORMAT();
|
||||
|
||||
public CompareRevisions(string repo, string start, string end)
|
||||
{
|
||||
|
@ -20,44 +18,18 @@ namespace SourceGit.Commands
|
|||
Args = $"diff --name-status {based} {end}";
|
||||
}
|
||||
|
||||
public CompareRevisions(string repo, string start, string end, string path)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var based = string.IsNullOrEmpty(start) ? "-R" : start;
|
||||
Args = $"diff --name-status {based} {end} -- \"{path}\"";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return _changes;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
ParseLine(line);
|
||||
|
||||
_changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path));
|
||||
Exec();
|
||||
_changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
|
||||
return _changes;
|
||||
}
|
||||
|
||||
private void ParseLine(string line)
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
match = REG_RENAME_FORMAT().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var renamed = new Models.Change() { Path = match.Groups[1].Value };
|
||||
renamed.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(renamed);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
@ -76,6 +48,10 @@ namespace SourceGit.Commands
|
|||
change.Set(Models.ChangeState.Deleted);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'R':
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
_changes.Add(change);
|
||||
|
|
|
@ -7,29 +7,23 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public Config(string repository)
|
||||
{
|
||||
if (string.IsNullOrEmpty(repository))
|
||||
{
|
||||
WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
WorkingDirectory = repository;
|
||||
Context = repository;
|
||||
_isLocal = true;
|
||||
}
|
||||
|
||||
WorkingDirectory = repository;
|
||||
Context = repository;
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> ListAll()
|
||||
{
|
||||
Args = "config -l";
|
||||
if (string.IsNullOrEmpty(WorkingDirectory))
|
||||
Args = "config --global -l";
|
||||
else
|
||||
Args = "config -l";
|
||||
|
||||
var output = ReadToEnd();
|
||||
var rs = new Dictionary<string, string>();
|
||||
if (output.IsSuccess)
|
||||
{
|
||||
var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = output.StdOut.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var idx = line.IndexOf('=', StringComparison.Ordinal);
|
||||
|
@ -53,16 +47,22 @@ namespace SourceGit.Commands
|
|||
|
||||
public bool Set(string key, string value, bool allowEmpty = false)
|
||||
{
|
||||
var scope = _isLocal ? "--local" : "--global";
|
||||
|
||||
if (!allowEmpty && string.IsNullOrWhiteSpace(value))
|
||||
Args = $"config {scope} --unset {key}";
|
||||
{
|
||||
if (string.IsNullOrEmpty(WorkingDirectory))
|
||||
Args = $"config --global --unset {key}";
|
||||
else
|
||||
Args = $"config --unset {key}";
|
||||
}
|
||||
else
|
||||
Args = $"config {scope} {key} \"{value}\"";
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(WorkingDirectory))
|
||||
Args = $"config --global {key} \"{value}\"";
|
||||
else
|
||||
Args = $"config {key} \"{value}\"";
|
||||
}
|
||||
|
||||
return Exec();
|
||||
}
|
||||
|
||||
private bool _isLocal = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class CountLocalChangesWithoutUntracked : Command
|
||||
{
|
||||
public CountLocalChangesWithoutUntracked(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "--no-optional-locks status -uno --ignore-submodules=all --porcelain";
|
||||
}
|
||||
|
||||
public int Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
return lines.Length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -8,15 +8,11 @@ namespace SourceGit.Commands
|
|||
{
|
||||
[GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
|
||||
private static partial Regex REG_INDICATOR();
|
||||
|
||||
[GeneratedRegex(@"^index\s([0-9a-f]{6,40})\.\.([0-9a-f]{6,40})(\s[1-9]{6})?")]
|
||||
private static partial Regex REG_HASH_CHANGE();
|
||||
|
||||
private const string PREFIX_LFS_NEW = "+version https://git-lfs.github.com/spec/";
|
||||
private const string PREFIX_LFS_DEL = "-version https://git-lfs.github.com/spec/";
|
||||
private const string PREFIX_LFS_MODIFY = " version https://git-lfs.github.com/spec/";
|
||||
|
||||
public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespace)
|
||||
public Diff(string repo, Models.DiffOption opt, int unified)
|
||||
{
|
||||
_result.TextDiff = new Models.TextDiff()
|
||||
{
|
||||
|
@ -26,50 +22,32 @@ namespace SourceGit.Commands
|
|||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
if (ignoreWhitespace)
|
||||
Args = $"diff --no-ext-diff --patch --ignore-all-space --unified={unified} {opt}";
|
||||
else if (Models.DiffOption.IgnoreCRAtEOL)
|
||||
Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
|
||||
else
|
||||
Args = $"diff --no-ext-diff --patch --unified={unified} {opt}";
|
||||
Args = $"diff --ignore-cr-at-eol --unified={unified} {opt}";
|
||||
}
|
||||
|
||||
public Models.DiffResult Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\n', start);
|
||||
while (end > 0)
|
||||
{
|
||||
var line = rs.StdOut.Substring(start, end - start);
|
||||
ParseLine(line);
|
||||
Exec();
|
||||
|
||||
start = end + 1;
|
||||
end = rs.StdOut.IndexOf('\n', start);
|
||||
}
|
||||
|
||||
if (start < rs.StdOut.Length)
|
||||
ParseLine(rs.StdOut.Substring(start));
|
||||
|
||||
if (_result.IsBinary || _result.IsLFS || _result.TextDiff.Lines.Count == 0)
|
||||
if (_result.IsBinary || _result.IsLFS)
|
||||
{
|
||||
_result.TextDiff = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessInlineHighlights();
|
||||
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
||||
|
||||
if (_result.TextDiff.Lines.Count == 0)
|
||||
_result.TextDiff = null;
|
||||
else
|
||||
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
||||
}
|
||||
|
||||
return _result;
|
||||
}
|
||||
|
||||
private void ParseLine(string line)
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
|
||||
if (line.StartsWith("old mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.OldMode = line.Substring(9);
|
||||
|
@ -82,17 +60,8 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("deleted file mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.OldMode = line.Substring(18);
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("new file mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.NewMode = line.Substring(14);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_result.IsLFS)
|
||||
{
|
||||
|
@ -105,7 +74,7 @@ namespace SourceGit.Commands
|
|||
}
|
||||
else if (line.StartsWith("-size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
|
||||
}
|
||||
}
|
||||
else if (ch == '+')
|
||||
|
@ -116,52 +85,36 @@ namespace SourceGit.Commands
|
|||
}
|
||||
else if (line.StartsWith("+size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = long.Parse(line.AsSpan(6));
|
||||
_result.LFSDiff.New.Size = long.Parse(line.Substring(6));
|
||||
}
|
||||
}
|
||||
else if (line.StartsWith(" size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_result.TextDiff.Lines.Count == 0)
|
||||
{
|
||||
if (line.StartsWith("Binary", StringComparison.Ordinal))
|
||||
var match = REG_INDICATOR().Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
_result.IsBinary = true;
|
||||
if (line.StartsWith("Binary", StringComparison.Ordinal))
|
||||
_result.IsBinary = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_result.OldHash))
|
||||
{
|
||||
var match = REG_HASH_CHANGE().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
_result.OldHash = match.Groups[1].Value;
|
||||
_result.NewHash = match.Groups[2].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var match = REG_INDICATOR().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
_oldLine = int.Parse(match.Groups[1].Value);
|
||||
_newLine = int.Parse(match.Groups[2].Value);
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
}
|
||||
_oldLine = int.Parse(match.Groups[1].Value);
|
||||
_newLine = int.Parse(match.Groups[2].Value);
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (line.Length == 0)
|
||||
{
|
||||
ProcessInlineHighlights();
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine));
|
||||
_oldLine++;
|
||||
_newLine++;
|
||||
return;
|
||||
|
@ -177,8 +130,7 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0);
|
||||
_deleted.Add(_last);
|
||||
_deleted.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0));
|
||||
_oldLine++;
|
||||
}
|
||||
else if (ch == '+')
|
||||
|
@ -190,8 +142,7 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine);
|
||||
_added.Add(_last);
|
||||
_added.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine));
|
||||
_newLine++;
|
||||
}
|
||||
else if (ch != '\\')
|
||||
|
@ -202,8 +153,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
_oldLine = int.Parse(match.Groups[1].Value);
|
||||
_newLine = int.Parse(match.Groups[2].Value);
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -214,16 +164,11 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine));
|
||||
_oldLine++;
|
||||
_newLine++;
|
||||
}
|
||||
}
|
||||
else if (line.Equals("\\ No newline at end of file", StringComparison.Ordinal))
|
||||
{
|
||||
_last.NoNewLineEndOfFile = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,7 +219,6 @@ namespace SourceGit.Commands
|
|||
private readonly Models.DiffResult _result = new Models.DiffResult();
|
||||
private readonly List<Models.TextDiffLine> _deleted = new List<Models.TextDiffLine>();
|
||||
private readonly List<Models.TextDiffLine> _added = new List<Models.TextDiffLine>();
|
||||
private Models.TextDiffLine _last = null;
|
||||
private int _oldLine = 0;
|
||||
private int _newLine = 0;
|
||||
}
|
||||
|
|
|
@ -1,95 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Discard
|
||||
{
|
||||
/// <summary>
|
||||
/// Discard all local changes (unstaged & staged)
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="includeIgnored"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void All(string repo, bool includeIgnored, Models.ICommandLog log)
|
||||
public static void All(string repo)
|
||||
{
|
||||
var changes = new QueryLocalChanges(repo).Result();
|
||||
try
|
||||
{
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.WorkTree == Models.ChangeState.Untracked ||
|
||||
c.WorkTree == Models.ChangeState.Added ||
|
||||
c.Index == Models.ChangeState.Added ||
|
||||
c.Index == Models.ChangeState.Renamed)
|
||||
{
|
||||
var fullPath = Path.Combine(repo, c.Path);
|
||||
if (Directory.Exists(fullPath))
|
||||
Directory.Delete(fullPath, true);
|
||||
else
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
new Reset(repo, "HEAD", "--hard") { Log = log }.Exec();
|
||||
|
||||
if (includeIgnored)
|
||||
new Clean(repo) { Log = log }.Exec();
|
||||
new Restore(repo).Exec();
|
||||
new Clean(repo).Exec();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discard selected changes (only unstaged).
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="changes"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void Changes(string repo, List<Models.Change> changes, Models.ICommandLog log)
|
||||
public static void Changes(string repo, List<Models.Change> changes)
|
||||
{
|
||||
var restores = new List<string>();
|
||||
var needClean = new List<string>();
|
||||
var needCheckout = new List<string>();
|
||||
|
||||
try
|
||||
foreach (var c in changes)
|
||||
{
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
|
||||
{
|
||||
var fullPath = Path.Combine(repo, c.Path);
|
||||
if (Directory.Exists(fullPath))
|
||||
Directory.Delete(fullPath, true);
|
||||
else
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
restores.Add(c.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
|
||||
});
|
||||
if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
|
||||
needClean.Add(c.Path);
|
||||
else
|
||||
needCheckout.Add(c.Path);
|
||||
}
|
||||
|
||||
if (restores.Count > 0)
|
||||
for (int i = 0; i < needClean.Count; i += 10)
|
||||
{
|
||||
var pathSpecFile = Path.GetTempFileName();
|
||||
File.WriteAllLines(pathSpecFile, restores);
|
||||
new Restore(repo, pathSpecFile, false) { Log = log }.Exec();
|
||||
File.Delete(pathSpecFile);
|
||||
var count = Math.Min(10, needClean.Count - i);
|
||||
new Clean(repo, needClean.GetRange(i, count)).Exec();
|
||||
}
|
||||
|
||||
for (int i = 0; i < needCheckout.Count; i += 10)
|
||||
{
|
||||
var count = Math.Min(10, needCheckout.Count - i);
|
||||
new Restore(repo, needCheckout.GetRange(i, count), "--worktree --recurse-submodules").Exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class ExecuteCustomAction
|
||||
{
|
||||
public static void Run(string repo, string file, string args)
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = file;
|
||||
start.Arguments = args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.WorkingDirectory = repo;
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(start);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunAndWait(string repo, string file, string args, Models.ICommandLog log)
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = file;
|
||||
start.Arguments = args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
start.WorkingDirectory = repo;
|
||||
|
||||
log?.AppendLine($"$ {file} {args}\n");
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var builder = new StringBuilder();
|
||||
|
||||
proc.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
log?.AppendLine(e.Data);
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
log?.AppendLine(e.Data);
|
||||
builder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
var exitCode = proc.ExitCode;
|
||||
if (exitCode != 0)
|
||||
{
|
||||
var errMsg = builder.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(errMsg))
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, errMsg));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, e.Message));
|
||||
}
|
||||
|
||||
proc.Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +1,155 @@
|
|||
namespace SourceGit.Commands
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Fetch : Command
|
||||
{
|
||||
public Fetch(string repo, string remote, bool noTags, bool force)
|
||||
public Fetch(string repo, string remote, bool prune, bool noTags, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "fetch --progress --verbose ";
|
||||
|
||||
if (prune)
|
||||
Args += "--prune ";
|
||||
|
||||
if (noTags)
|
||||
Args += "--no-tags ";
|
||||
else
|
||||
Args += "--tags ";
|
||||
|
||||
if (force)
|
||||
Args += "--force ";
|
||||
|
||||
Args += remote;
|
||||
|
||||
AutoFetch.MarkFetched(repo);
|
||||
}
|
||||
|
||||
public Fetch(string repo, Models.Branch local, Models.Branch remote)
|
||||
public Fetch(string repo, string remote, string localBranch, string remoteBranch, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey");
|
||||
Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}";
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = $"fetch --progress --verbose {remote} {remoteBranch}:{localBranch}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Editor = EditorType.None;
|
||||
Args = $"format-patch {commit} -1 --output=\"{saveTo}\"";
|
||||
Args = $"format-patch {commit} -1 -o \"{saveTo}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
namespace SourceGit.Commands
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class GC : Command
|
||||
{
|
||||
public GC(string repo)
|
||||
public GC(string repo, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "gc --prune=now";
|
||||
TraitErrorAsOutput = true;
|
||||
Args = "gc --prune";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A C# version of https://github.com/anjerodev/commitollama
|
||||
/// </summary>
|
||||
public class GenerateCommitMessage
|
||||
{
|
||||
public class GetDiffContent : Command
|
||||
{
|
||||
public GetDiffContent(string repo, Models.DiffOption opt)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff --diff-algorithm=minimal {opt}";
|
||||
}
|
||||
}
|
||||
|
||||
public GenerateCommitMessage(Models.OpenAIService service, string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> onResponse)
|
||||
{
|
||||
_service = service;
|
||||
_repo = repo;
|
||||
_changes = changes;
|
||||
_cancelToken = cancelToken;
|
||||
_onResponse = onResponse;
|
||||
}
|
||||
|
||||
public void Exec()
|
||||
{
|
||||
try
|
||||
{
|
||||
_onResponse?.Invoke("Waiting for pre-file analyzing to completed...\n\n");
|
||||
|
||||
var responseBuilder = new StringBuilder();
|
||||
var summaryBuilder = new StringBuilder();
|
||||
foreach (var change in _changes)
|
||||
{
|
||||
if (_cancelToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
responseBuilder.Append("- ");
|
||||
summaryBuilder.Append("- ");
|
||||
|
||||
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
_service.Chat(
|
||||
_service.AnalyzeDiffPrompt,
|
||||
$"Here is the `git diff` output: {rs.StdOut}",
|
||||
_cancelToken,
|
||||
update =>
|
||||
{
|
||||
responseBuilder.Append(update);
|
||||
summaryBuilder.Append(update);
|
||||
|
||||
_onResponse?.Invoke($"Waiting for pre-file analyzing to completed...\n\n{responseBuilder}");
|
||||
});
|
||||
}
|
||||
|
||||
responseBuilder.Append("\n");
|
||||
summaryBuilder.Append("(file: ");
|
||||
summaryBuilder.Append(change.Path);
|
||||
summaryBuilder.Append(")\n");
|
||||
}
|
||||
|
||||
if (_cancelToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
var responseBody = responseBuilder.ToString();
|
||||
var subjectBuilder = new StringBuilder();
|
||||
_service.Chat(
|
||||
_service.GenerateSubjectPrompt,
|
||||
$"Here are the summaries changes:\n{summaryBuilder}",
|
||||
_cancelToken,
|
||||
update =>
|
||||
{
|
||||
subjectBuilder.Append(update);
|
||||
_onResponse?.Invoke($"{subjectBuilder}\n\n{responseBody}");
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(_repo, $"Failed to generate commit message: {e}"));
|
||||
}
|
||||
}
|
||||
|
||||
private Models.OpenAIService _service;
|
||||
private string _repo;
|
||||
private List<Models.Change> _changes;
|
||||
private CancellationToken _cancelToken;
|
||||
private Action<string> _onResponse;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,52 @@
|
|||
using System.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class GitFlow
|
||||
{
|
||||
public static bool Init(string repo, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log)
|
||||
public class BranchDetectResult
|
||||
{
|
||||
public bool IsGitFlowBranch { get; set; } = false;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Prefix { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(string repo, List<Models.Branch> branches)
|
||||
{
|
||||
var localBrancheNames = new HashSet<string>();
|
||||
foreach (var branch in branches)
|
||||
{
|
||||
if (branch.IsLocal)
|
||||
localBrancheNames.Add(branch.Name);
|
||||
}
|
||||
|
||||
var config = new Config(repo).ListAll();
|
||||
if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master))
|
||||
return false;
|
||||
|
||||
if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop))
|
||||
return false;
|
||||
|
||||
return config.ContainsKey("gitflow.prefix.feature") &&
|
||||
config.ContainsKey("gitflow.prefix.release") &&
|
||||
config.ContainsKey("gitflow.prefix.hotfix");
|
||||
}
|
||||
|
||||
public static bool Init(string repo, List<Models.Branch> branches, string master, string develop, string feature, string release, string hotfix, string version)
|
||||
{
|
||||
var current = branches.Find(x => x.IsCurrent);
|
||||
|
||||
var masterBranch = branches.Find(x => x.Name == master);
|
||||
if (masterBranch == null && current != null)
|
||||
Branch.Create(repo, master, current.Head);
|
||||
|
||||
var devBranch = branches.Find(x => x.Name == develop);
|
||||
if (devBranch == null && current != null)
|
||||
Branch.Create(repo, develop, current.Head);
|
||||
|
||||
var config = new Config(repo);
|
||||
config.Set("gitflow.branch.master", master);
|
||||
config.Set("gitflow.branch.develop", develop);
|
||||
|
@ -21,72 +61,104 @@ namespace SourceGit.Commands
|
|||
init.WorkingDirectory = repo;
|
||||
init.Context = repo;
|
||||
init.Args = "flow init -d";
|
||||
init.Log = log;
|
||||
return init.Exec();
|
||||
}
|
||||
|
||||
public static bool Start(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log)
|
||||
public static string GetPrefix(string repo, string type)
|
||||
{
|
||||
return new Config(repo).Get($"gitflow.prefix.{type}");
|
||||
}
|
||||
|
||||
public static BranchDetectResult DetectType(string repo, List<Models.Branch> branches, string branch)
|
||||
{
|
||||
var rs = new BranchDetectResult();
|
||||
var localBrancheNames = new HashSet<string>();
|
||||
foreach (var b in branches)
|
||||
{
|
||||
if (b.IsLocal)
|
||||
localBrancheNames.Add(b.Name);
|
||||
}
|
||||
|
||||
var config = new Config(repo).ListAll();
|
||||
if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master))
|
||||
return rs;
|
||||
|
||||
if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop))
|
||||
return rs;
|
||||
|
||||
if (!config.TryGetValue("gitflow.prefix.feature", out var feature) ||
|
||||
!config.TryGetValue("gitflow.prefix.release", out var release) ||
|
||||
!config.TryGetValue("gitflow.prefix.hotfix", out var hotfix))
|
||||
return rs;
|
||||
|
||||
if (branch.StartsWith(feature, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "feature";
|
||||
rs.Prefix = feature;
|
||||
}
|
||||
else if (branch.StartsWith(release, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "release";
|
||||
rs.Prefix = release;
|
||||
}
|
||||
else if (branch.StartsWith(hotfix, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "hotfix";
|
||||
rs.Prefix = hotfix;
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
public static bool Start(string repo, string type, string name)
|
||||
{
|
||||
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
App.RaiseException(repo, "Bad branch type!!!");
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var start = new Command();
|
||||
start.WorkingDirectory = repo;
|
||||
start.Context = repo;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
start.Args = $"flow feature start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
start.Args = $"flow release start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
start.Args = $"flow hotfix start {name}";
|
||||
break;
|
||||
default:
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
start.Log = log;
|
||||
start.Args = $"flow {type} start {name}";
|
||||
return start.Exec();
|
||||
}
|
||||
|
||||
public static bool Finish(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log)
|
||||
public static bool Finish(string repo, string type, string name, bool keepBranch)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("flow ");
|
||||
|
||||
switch (type)
|
||||
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
|
||||
{
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
builder.Append("feature");
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
builder.Append("release");
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
builder.Append("hotfix");
|
||||
break;
|
||||
default:
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!"));
|
||||
return false;
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
App.RaiseException(repo, "Bad branch type!!!");
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
builder.Append(" finish ");
|
||||
if (squash)
|
||||
builder.Append("--squash ");
|
||||
if (push)
|
||||
builder.Append("--push ");
|
||||
if (keepBranch)
|
||||
builder.Append("-k ");
|
||||
builder.Append(name);
|
||||
|
||||
var option = keepBranch ? "-k" : string.Empty;
|
||||
var finish = new Command();
|
||||
finish.WorkingDirectory = repo;
|
||||
finish.Context = repo;
|
||||
finish.Args = builder.ToString();
|
||||
finish.Log = log;
|
||||
finish.Args = $"flow {type} finish {option} {name}";
|
||||
return finish.Exec();
|
||||
}
|
||||
|
||||
private static readonly List<string> SUPPORTED_BRANCH_TYPES = new List<string>()
|
||||
{
|
||||
"feature",
|
||||
"release",
|
||||
"bugfix",
|
||||
"hotfix",
|
||||
"support",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var file = Path.Combine(repo, ".gitignore");
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
File.WriteAllLines(file, [pattern]);
|
||||
return;
|
||||
}
|
||||
|
||||
var org = File.ReadAllText(file);
|
||||
if (!org.EndsWith('\n'))
|
||||
File.AppendAllLines(file, ["", pattern]);
|
||||
else
|
||||
File.AppendAllLines(file, [pattern]);
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class IsBareRepository : Command
|
||||
{
|
||||
public IsBareRepository(string path)
|
||||
{
|
||||
WorkingDirectory = path;
|
||||
Args = "rev-parse --is-bare-repository";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(WorkingDirectory, "refs")) ||
|
||||
!Directory.Exists(Path.Combine(WorkingDirectory, "objects")) ||
|
||||
!File.Exists(Path.Combine(WorkingDirectory, "HEAD")))
|
||||
return false;
|
||||
|
||||
var rs = ReadToEnd();
|
||||
return rs.IsSuccess && rs.StdOut.Trim() == "true";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff {Models.Commit.EmptyTreeSHA1} {commit} --numstat -- \"{path}\"";
|
||||
Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\"";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class IsCommitSHA : Command
|
||||
{
|
||||
public IsCommitSHA(string repo, string hash)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = $"cat-file -t {hash}";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
return rs.IsSuccess && rs.StdOut.Trim().Equals("commit");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,10 +10,5 @@
|
|||
Context = repo;
|
||||
Args = $"diff -a --ignore-cr-at-eol --check {opt}";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
return ReadToEnd().IsSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,26 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class LFS
|
||||
{
|
||||
[GeneratedRegex(@"^(.+)\s+([\w.]+)\s+\w+:(\d+)$")]
|
||||
[GeneratedRegex(@"^(.+)\s+(\w+)\s+\w+:(\d+)$")]
|
||||
private static partial Regex REG_LOCK();
|
||||
|
||||
private class SubCmd : Command
|
||||
class SubCmd : Command
|
||||
{
|
||||
public SubCmd(string repo, string args, Models.ICommandLog log)
|
||||
public SubCmd(string repo, string args, Action<string> onProgress)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = args;
|
||||
Log = log;
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
|
||||
public LFS(string repo)
|
||||
|
@ -36,35 +44,35 @@ namespace SourceGit.Commands
|
|||
return content.Contains("git lfs pre-push");
|
||||
}
|
||||
|
||||
public bool Install(Models.ICommandLog log)
|
||||
public bool Install()
|
||||
{
|
||||
return new SubCmd(_repo, "lfs install --local", log).Exec();
|
||||
return new SubCmd(_repo, "lfs install --local", null).Exec();
|
||||
}
|
||||
|
||||
public bool Track(string pattern, bool isFilenameMode, Models.ICommandLog log)
|
||||
public bool Track(string pattern, bool isFilenameMode = false)
|
||||
{
|
||||
var opt = isFilenameMode ? "--filename" : "";
|
||||
return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", log).Exec();
|
||||
return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", null).Exec();
|
||||
}
|
||||
|
||||
public void Fetch(string remote, Models.ICommandLog log)
|
||||
public void Fetch(string remote, Action<string> outputHandler)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs fetch {remote}", log).Exec();
|
||||
new SubCmd(_repo, $"lfs fetch {remote}", outputHandler).Exec();
|
||||
}
|
||||
|
||||
public void Pull(string remote, Models.ICommandLog log)
|
||||
public void Pull(string remote, Action<string> outputHandler)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs pull {remote}", log).Exec();
|
||||
new SubCmd(_repo, $"lfs pull {remote}", outputHandler).Exec();
|
||||
}
|
||||
|
||||
public void Push(string remote, Models.ICommandLog log)
|
||||
public void Push(string remote, Action<string> outputHandler)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs push {remote}", log).Exec();
|
||||
new SubCmd(_repo, $"lfs push {remote}", outputHandler).Exec();
|
||||
}
|
||||
|
||||
public void Prune(Models.ICommandLog log)
|
||||
public void Prune(Action<string> outputHandler)
|
||||
{
|
||||
new SubCmd(_repo, "lfs prune", log).Exec();
|
||||
new SubCmd(_repo, "lfs prune", outputHandler).Exec();
|
||||
}
|
||||
|
||||
public List<Models.LFSLock> Locks(string remote)
|
||||
|
@ -74,7 +82,7 @@ namespace SourceGit.Commands
|
|||
var rs = cmd.ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_LOCK().Match(line);
|
||||
|
@ -93,21 +101,21 @@ namespace SourceGit.Commands
|
|||
return locks;
|
||||
}
|
||||
|
||||
public bool Lock(string remote, string file, Models.ICommandLog log)
|
||||
public bool Lock(string remote, string file)
|
||||
{
|
||||
return new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", log).Exec();
|
||||
return new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", null).Exec();
|
||||
}
|
||||
|
||||
public bool Unlock(string remote, string file, bool force, Models.ICommandLog log)
|
||||
public bool Unlock(string remote, string file, bool force)
|
||||
{
|
||||
var opt = force ? "-f" : "";
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", log).Exec();
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", null).Exec();
|
||||
}
|
||||
|
||||
public bool Unlock(string remote, long id, bool force, Models.ICommandLog log)
|
||||
public bool Unlock(string remote, long id, bool force)
|
||||
{
|
||||
var opt = force ? "-f" : "";
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", log).Exec();
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", null).Exec();
|
||||
}
|
||||
|
||||
private readonly string _repo;
|
||||
|
|
|
@ -1,36 +1,23 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Merge : Command
|
||||
{
|
||||
public Merge(string repo, string source, string mode)
|
||||
public Merge(string repo, string source, string mode, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
Args = $"merge --progress {source} {mode}";
|
||||
}
|
||||
|
||||
public Merge(string repo, List<string> targets, bool autoCommit, string strategy)
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("merge --progress ");
|
||||
if (!string.IsNullOrEmpty(strategy))
|
||||
builder.Append($"--strategy={strategy} ");
|
||||
if (!autoCommit)
|
||||
builder.Append("--no-commit ");
|
||||
|
||||
foreach (var t in targets)
|
||||
{
|
||||
builder.Append(t);
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
Args = builder.ToString();
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,18 +13,15 @@ namespace SourceGit.Commands
|
|||
cmd.Context = repo;
|
||||
cmd.RaiseError = true;
|
||||
|
||||
// NOTE: If no <file> names are specified, 'git mergetool' will run the merge tool program on every file with merge conflicts.
|
||||
var fileArg = string.IsNullOrEmpty(file) ? "" : $"\"{file}\"";
|
||||
|
||||
if (toolType == 0)
|
||||
{
|
||||
cmd.Args = $"mergetool {fileArg}";
|
||||
cmd.Args = $"mergetool \"{file}\"";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
if (!File.Exists(toolPath))
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(repo, $"Can NOT find external merge tool in '{toolPath}'!"));
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(repo, $"Can NOT found external merge tool in '{toolPath}'!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -35,7 +32,7 @@ namespace SourceGit.Commands
|
|||
return false;
|
||||
}
|
||||
|
||||
cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit {fileArg}";
|
||||
cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit \"{file}\"";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
|
@ -54,7 +51,7 @@ namespace SourceGit.Commands
|
|||
|
||||
if (!File.Exists(toolPath))
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT find external diff tool in '{toolPath}'!"));
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT found external diff tool in '{toolPath}'!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,31 @@
|
|||
namespace SourceGit.Commands
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Pull : Command
|
||||
{
|
||||
public Pull(string repo, string remote, string branch, bool useRebase)
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "pull --verbose --progress ";
|
||||
Args = "pull --verbose --progress --tags ";
|
||||
|
||||
if (useRebase)
|
||||
Args += "--rebase=true ";
|
||||
Args += "--rebase ";
|
||||
if (noTags)
|
||||
Args += "--no-tags ";
|
||||
|
||||
Args += $"{remote} {branch}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
namespace SourceGit.Commands
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Push : Command
|
||||
{
|
||||
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force)
|
||||
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool force, bool track, Action<string> onProgress)
|
||||
{
|
||||
_outputHandler = onProgress;
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "push --progress --verbose ";
|
||||
|
||||
if (withTags)
|
||||
Args += "--tags ";
|
||||
if (checkSubmodules)
|
||||
Args += "--recurse-submodules=check ";
|
||||
if (track)
|
||||
Args += "-u ";
|
||||
if (force)
|
||||
|
@ -21,7 +24,7 @@
|
|||
Args += $"{remote} {local}:{remoteBranch}";
|
||||
}
|
||||
|
||||
public Push(string repo, string remote, string refname, bool isDelete)
|
||||
public Push(string repo, string remote, string tag, bool isDelete)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
@ -31,7 +34,14 @@
|
|||
if (isDelete)
|
||||
Args += "--delete ";
|
||||
|
||||
Args += $"{remote} {refname}";
|
||||
Args += $"{remote} refs/tags/{tag}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryAssumeUnchangedFiles : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\w)\s+(.+)$")]
|
||||
private static partial Regex REG_PARSE();
|
||||
|
||||
public QueryAssumeUnchangedFiles(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = "ls-files -v";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
var outs = new List<string>();
|
||||
var rs = ReadToEnd();
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_PARSE().Match(line);
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
if (match.Groups[1].Value == "h")
|
||||
outs.Add(match.Groups[2].Value);
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,77 +7,40 @@ namespace SourceGit.Commands
|
|||
{
|
||||
private const string PREFIX_LOCAL = "refs/heads/";
|
||||
private const string PREFIX_REMOTE = "refs/remotes/";
|
||||
private const string PREFIX_DETACHED_AT = "(HEAD detached at";
|
||||
private const string PREFIX_DETACHED_FROM = "(HEAD detached from";
|
||||
private const string PREFIX_DETACHED = "(HEAD detached at";
|
||||
|
||||
public QueryBranches(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
|
||||
Args = "branch -l --all -v --format=\"%(refname)$%(objectname)$%(HEAD)$%(upstream)$%(upstream:trackshort)\"";
|
||||
}
|
||||
|
||||
public List<Models.Branch> Result(out int localBranchesCount)
|
||||
public List<Models.Branch> Result()
|
||||
{
|
||||
localBranchesCount = 0;
|
||||
Exec();
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return branches;
|
||||
foreach (var b in _needQueryTrackStatus)
|
||||
b.TrackStatus = new QueryTrackStatus(WorkingDirectory, b.Name, b.Upstream).Result();
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var remoteHeads = new Dictionary<string, string>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var b = ParseLine(line);
|
||||
if (b != null)
|
||||
{
|
||||
branches.Add(b);
|
||||
if (!b.IsLocal)
|
||||
remoteHeads.Add(b.FullName, b.Head);
|
||||
else
|
||||
localBranchesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var b in branches)
|
||||
{
|
||||
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
|
||||
{
|
||||
if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead))
|
||||
{
|
||||
b.IsUpstreamGone = false;
|
||||
|
||||
if (b.TrackStatus == null)
|
||||
b.TrackStatus = new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).Result();
|
||||
}
|
||||
else
|
||||
{
|
||||
b.IsUpstreamGone = true;
|
||||
|
||||
if (b.TrackStatus == null)
|
||||
b.TrackStatus = new Models.BranchTrackStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return branches;
|
||||
return _branches;
|
||||
}
|
||||
|
||||
private Models.Branch ParseLine(string line)
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var parts = line.Split('\0');
|
||||
if (parts.Length != 6)
|
||||
return null;
|
||||
var parts = line.Split('$');
|
||||
if (parts.Length != 5)
|
||||
return;
|
||||
|
||||
var branch = new Models.Branch();
|
||||
var refName = parts[0];
|
||||
if (refName.EndsWith("/HEAD", StringComparison.Ordinal))
|
||||
return null;
|
||||
return;
|
||||
|
||||
branch.IsDetachedHead = refName.StartsWith(PREFIX_DETACHED_AT, StringComparison.Ordinal) ||
|
||||
refName.StartsWith(PREFIX_DETACHED_FROM, StringComparison.Ordinal);
|
||||
if (refName.StartsWith(PREFIX_DETACHED, StringComparison.Ordinal))
|
||||
{
|
||||
branch.IsHead = true;
|
||||
}
|
||||
|
||||
if (refName.StartsWith(PREFIX_LOCAL, StringComparison.Ordinal))
|
||||
{
|
||||
|
@ -89,7 +52,7 @@ namespace SourceGit.Commands
|
|||
var name = refName.Substring(PREFIX_REMOTE.Length);
|
||||
var shortNameIdx = name.IndexOf('/', StringComparison.Ordinal);
|
||||
if (shortNameIdx < 0)
|
||||
return null;
|
||||
return;
|
||||
|
||||
branch.Remote = name.Substring(0, shortNameIdx);
|
||||
branch.Name = name.Substring(branch.Remote.Length + 1);
|
||||
|
@ -102,19 +65,19 @@ namespace SourceGit.Commands
|
|||
}
|
||||
|
||||
branch.FullName = refName;
|
||||
branch.CommitterDate = ulong.Parse(parts[1]);
|
||||
branch.Head = parts[2];
|
||||
branch.IsCurrent = parts[3] == "*";
|
||||
branch.Upstream = parts[4];
|
||||
branch.IsUpstreamGone = false;
|
||||
branch.Head = parts[1];
|
||||
branch.IsCurrent = parts[2] == "*";
|
||||
branch.Upstream = parts[3];
|
||||
|
||||
if (!branch.IsLocal ||
|
||||
string.IsNullOrEmpty(branch.Upstream) ||
|
||||
string.IsNullOrEmpty(parts[5]) ||
|
||||
parts[5].Equals("=", StringComparison.Ordinal))
|
||||
if (branch.IsLocal && !string.IsNullOrEmpty(parts[4]) && !parts[4].Equals("=", StringComparison.Ordinal))
|
||||
_needQueryTrackStatus.Add(branch);
|
||||
else
|
||||
branch.TrackStatus = new Models.BranchTrackStatus();
|
||||
|
||||
return branch;
|
||||
_branches.Add(branch);
|
||||
}
|
||||
|
||||
private readonly List<Models.Branch> _branches = new List<Models.Branch>();
|
||||
private List<Models.Branch> _needQueryTrackStatus = new List<Models.Branch>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCommitChildren : Command
|
||||
{
|
||||
public QueryCommitChildren(string repo, string commit, int max)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
_commit = commit;
|
||||
Args = $"rev-list -{max} --parents --branches --remotes --ancestry-path ^{commit}";
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
var outs = new List<string>();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Contains(_commit))
|
||||
outs.Add(line.Substring(0, 40));
|
||||
}
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
private string _commit;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --no-show-signature --format=%B -s {sha}";
|
||||
Args = $"show --no-show-signature --pretty=format:%B -s {sha}";
|
||||
}
|
||||
|
||||
public string Result()
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCommitSignInfo : Command
|
||||
{
|
||||
public QueryCommitSignInfo(string repo, string sha, bool useFakeSignersFile)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
const string baseArgs = "show --no-show-signature --format=%G?%n%GS%n%GK -s";
|
||||
const string fakeSignersFileArg = "-c gpg.ssh.allowedSignersFile=/dev/null";
|
||||
Args = $"{(useFakeSignersFile ? fakeSignersFileArg : string.Empty)} {baseArgs} {sha}";
|
||||
}
|
||||
|
||||
public Models.CommitSignInfo Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return null;
|
||||
|
||||
var raw = rs.StdOut.Trim().ReplaceLineEndings("\n");
|
||||
if (raw.Length <= 1)
|
||||
return null;
|
||||
|
||||
var lines = raw.Split('\n');
|
||||
return new Models.CommitSignInfo()
|
||||
{
|
||||
VerifyResult = lines[0][0],
|
||||
Signer = lines[1],
|
||||
Key = lines[2]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,49 +10,33 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {limits}";
|
||||
Args = "log --date-order --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s " + limits;
|
||||
_findFirstMerged = needFindHead;
|
||||
}
|
||||
|
||||
public QueryCommits(string repo, string filter, Models.CommitSearchMethod method, bool onlyCurrentBranch)
|
||||
public QueryCommits(string repo, int maxCount, string messageFilter, bool isFile)
|
||||
{
|
||||
string search = onlyCurrentBranch ? string.Empty : "--branches --remotes ";
|
||||
|
||||
if (method == Models.CommitSearchMethod.ByAuthor)
|
||||
string search;
|
||||
if (isFile)
|
||||
{
|
||||
search += $"-i --author=\"{filter}\"";
|
||||
search = $"-- \"{messageFilter}\"";
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByCommitter)
|
||||
{
|
||||
search += $"-i --committer=\"{filter}\"";
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByMessage)
|
||||
else
|
||||
{
|
||||
var argsBuilder = new StringBuilder();
|
||||
argsBuilder.Append(search);
|
||||
|
||||
var words = filter.Split([' ', '\t', '\r'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var words = messageFilter.Split(new[] { ' ', '\t', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var word in words)
|
||||
{
|
||||
var escaped = word.Trim().Replace("\"", "\\\"", StringComparison.Ordinal);
|
||||
argsBuilder.Append($"--grep=\"{escaped}\" ");
|
||||
}
|
||||
argsBuilder.Append("--all-match -i");
|
||||
|
||||
search = argsBuilder.ToString();
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByFile)
|
||||
{
|
||||
search += $"-- \"{filter}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
search = $"-G\"{filter}\"";
|
||||
}
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log -1000 --date-order --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {search}";
|
||||
Args = $"log -{maxCount} --date-order --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s --branches --remotes " + search;
|
||||
_findFirstMerged = false;
|
||||
}
|
||||
|
||||
|
@ -120,7 +104,15 @@ namespace SourceGit.Commands
|
|||
if (data.Length < 8)
|
||||
return;
|
||||
|
||||
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
var idx = data.IndexOf(' ', StringComparison.Ordinal);
|
||||
if (idx == -1)
|
||||
{
|
||||
_current.Parents.Add(data);
|
||||
return;
|
||||
}
|
||||
|
||||
_current.Parents.Add(data.Substring(0, idx));
|
||||
_current.Parents.Add(data.Substring(idx + 1));
|
||||
}
|
||||
|
||||
private void MarkFirstMerged()
|
||||
|
@ -128,7 +120,7 @@ namespace SourceGit.Commands
|
|||
Args = $"log --since=\"{_commits[^1].CommitterTimeStr}\" --format=\"%H\"";
|
||||
|
||||
var rs = ReadToEnd();
|
||||
var shas = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var shas = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (shas.Length == 0)
|
||||
return;
|
||||
|
||||
|
|
|
@ -3,18 +3,18 @@ using System.Collections.Generic;
|
|||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCommitsForInteractiveRebase : Command
|
||||
public class QueryCommitsWithFullMessage : Command
|
||||
{
|
||||
public QueryCommitsForInteractiveRebase(string repo, string on)
|
||||
public QueryCommitsWithFullMessage(string repo, string args)
|
||||
{
|
||||
_boundary = $"----- BOUNDARY OF COMMIT {Guid.NewGuid()} -----";
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log --date-order --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD";
|
||||
Args = $"log --date-order --no-show-signature --decorate=full --pretty=format:\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {args}";
|
||||
}
|
||||
|
||||
public List<Models.InteractiveCommit> Result()
|
||||
public List<Models.CommitWithMessage> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
|
@ -29,7 +29,7 @@ namespace SourceGit.Commands
|
|||
switch (nextPartIdx)
|
||||
{
|
||||
case 0:
|
||||
_current = new Models.InteractiveCommit();
|
||||
_current = new Models.CommitWithMessage();
|
||||
_current.Commit.SHA = line;
|
||||
_commits.Add(_current);
|
||||
break;
|
||||
|
@ -52,28 +52,16 @@ namespace SourceGit.Commands
|
|||
_current.Commit.CommitterTime = ulong.Parse(line);
|
||||
break;
|
||||
default:
|
||||
var boundary = rs.StdOut.IndexOf(_boundary, end + 1, StringComparison.Ordinal);
|
||||
if (boundary > end)
|
||||
{
|
||||
_current.Message = rs.StdOut.Substring(start, boundary - start - 1);
|
||||
end = boundary + _boundary.Length;
|
||||
}
|
||||
if (line.Equals(_boundary, StringComparison.Ordinal))
|
||||
nextPartIdx = -1;
|
||||
else
|
||||
{
|
||||
_current.Message = rs.StdOut.Substring(start);
|
||||
end = rs.StdOut.Length - 2;
|
||||
}
|
||||
|
||||
nextPartIdx = -1;
|
||||
_current.Message += line;
|
||||
break;
|
||||
}
|
||||
|
||||
nextPartIdx++;
|
||||
|
||||
start = end + 1;
|
||||
if (start >= rs.StdOut.Length - 1)
|
||||
break;
|
||||
|
||||
end = rs.StdOut.IndexOf('\n', start);
|
||||
}
|
||||
|
||||
|
@ -85,11 +73,19 @@ namespace SourceGit.Commands
|
|||
if (data.Length < 8)
|
||||
return;
|
||||
|
||||
_current.Commit.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
var idx = data.IndexOf(' ', StringComparison.Ordinal);
|
||||
if (idx == -1)
|
||||
{
|
||||
_current.Commit.Parents.Add(data);
|
||||
return;
|
||||
}
|
||||
|
||||
_current.Commit.Parents.Add(data.Substring(0, idx));
|
||||
_current.Commit.Parents.Add(data.Substring(idx + 1));
|
||||
}
|
||||
|
||||
private List<Models.InteractiveCommit> _commits = [];
|
||||
private Models.InteractiveCommit _current = null;
|
||||
private readonly string _boundary;
|
||||
private List<Models.CommitWithMessage> _commits = new List<Models.CommitWithMessage>();
|
||||
private Models.CommitWithMessage _current = null;
|
||||
private string _boundary = "";
|
||||
}
|
||||
}
|
21
src/Commands/QueryCurrentRevisionFiles.cs
Normal file
21
src/Commands/QueryCurrentRevisionFiles.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCurrentRevisionFiles : Command
|
||||
{
|
||||
public QueryCurrentRevisionFiles(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "ls-tree -r --name-only HEAD";
|
||||
}
|
||||
|
||||
public string[] Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
return rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,39 +35,5 @@ namespace SourceGit.Commands
|
|||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static Stream FromLFS(string repo, string oid, long size)
|
||||
{
|
||||
var starter = new ProcessStartInfo();
|
||||
starter.WorkingDirectory = repo;
|
||||
starter.FileName = Native.OS.GitExecutable;
|
||||
starter.Arguments = $"lfs smudge";
|
||||
starter.UseShellExecute = false;
|
||||
starter.CreateNoWindow = true;
|
||||
starter.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
starter.RedirectStandardInput = true;
|
||||
starter.RedirectStandardOutput = true;
|
||||
|
||||
var stream = new MemoryStream();
|
||||
try
|
||||
{
|
||||
var proc = new Process() { StartInfo = starter };
|
||||
proc.Start();
|
||||
proc.StandardInput.WriteLine("version https://git-lfs.github.com/spec/v1");
|
||||
proc.StandardInput.WriteLine($"oid sha256:{oid}");
|
||||
proc.StandardInput.WriteLine($"size {size}");
|
||||
proc.StandardOutput.BaseStream.CopyTo(stream);
|
||||
proc.WaitForExit();
|
||||
proc.Close();
|
||||
|
||||
stream.Position = 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to query file content: {e}");
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,14 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"ls-tree {revision} -l -- \"{file}\"";
|
||||
Args = $"ls-tree {revision} -l -- {file}";
|
||||
}
|
||||
|
||||
public long Result()
|
||||
{
|
||||
if (_result != 0)
|
||||
return _result;
|
||||
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
|
@ -26,5 +29,7 @@ namespace SourceGit.Commands
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private readonly long _result = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryGitCommonDir : Command
|
||||
{
|
||||
public QueryGitCommonDir(string workDir)
|
||||
{
|
||||
WorkingDirectory = workDir;
|
||||
Args = "rev-parse --git-common-dir";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd().StdOut;
|
||||
if (string.IsNullOrEmpty(rs))
|
||||
return null;
|
||||
|
||||
rs = rs.Trim();
|
||||
if (Path.IsPathRooted(rs))
|
||||
return rs;
|
||||
return Path.GetFullPath(Path.Combine(WorkingDirectory, rs));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryLocalChanges : Command
|
||||
|
@ -16,150 +14,146 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"--no-optional-locks status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
|
||||
Args = $"status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
var outs = new List<Models.Change>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, rs.StdErr));
|
||||
return outs;
|
||||
}
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case " M":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Modified);
|
||||
break;
|
||||
case " T":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case " A":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Added);
|
||||
break;
|
||||
case " D":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case " R":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Renamed);
|
||||
break;
|
||||
case " C":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Copied);
|
||||
break;
|
||||
case "M":
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
break;
|
||||
case "MM":
|
||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "MT":
|
||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "MD":
|
||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "T":
|
||||
change.Set(Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "TM":
|
||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "TT":
|
||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "TD":
|
||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "A":
|
||||
change.Set(Models.ChangeState.Added);
|
||||
break;
|
||||
case "AM":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "AT":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "AD":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "D":
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "R":
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
break;
|
||||
case "RM":
|
||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "RT":
|
||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "RD":
|
||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "C":
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
break;
|
||||
case "CM":
|
||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "CT":
|
||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "CD":
|
||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "DD":
|
||||
change.ConflictReason = Models.ConflictReason.BothDeleted;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "AU":
|
||||
change.ConflictReason = Models.ConflictReason.AddedByUs;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UD":
|
||||
change.ConflictReason = Models.ConflictReason.DeletedByThem;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UA":
|
||||
change.ConflictReason = Models.ConflictReason.AddedByThem;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "DU":
|
||||
change.ConflictReason = Models.ConflictReason.DeletedByUs;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "AA":
|
||||
change.ConflictReason = Models.ConflictReason.BothAdded;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UU":
|
||||
change.ConflictReason = Models.ConflictReason.BothModified;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "??":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Untracked);
|
||||
break;
|
||||
}
|
||||
|
||||
if (change.Index != Models.ChangeState.None || change.WorkTree != Models.ChangeState.None)
|
||||
outs.Add(change);
|
||||
}
|
||||
|
||||
return outs;
|
||||
Exec();
|
||||
return _changes;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
if (line.EndsWith("/", StringComparison.Ordinal))
|
||||
return; // Ignore changes with git-worktree
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case " M":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Modified);
|
||||
break;
|
||||
case " T":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case " A":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Added);
|
||||
break;
|
||||
case " D":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case " R":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Renamed);
|
||||
break;
|
||||
case " C":
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Copied);
|
||||
break;
|
||||
case "M":
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
break;
|
||||
case "MM":
|
||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "MT":
|
||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "MD":
|
||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "T":
|
||||
change.Set(Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "TM":
|
||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "TT":
|
||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "TD":
|
||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "A":
|
||||
change.Set(Models.ChangeState.Added);
|
||||
break;
|
||||
case "AM":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "AT":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "AD":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "D":
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "R":
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
break;
|
||||
case "RM":
|
||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "RT":
|
||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "RD":
|
||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "C":
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
break;
|
||||
case "CM":
|
||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.Modified);
|
||||
break;
|
||||
case "CT":
|
||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "CD":
|
||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "DR":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Renamed);
|
||||
break;
|
||||
case "DC":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Copied);
|
||||
break;
|
||||
case "DD":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "AU":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Unmerged);
|
||||
break;
|
||||
case "UD":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "UA":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Added);
|
||||
break;
|
||||
case "DU":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Unmerged);
|
||||
break;
|
||||
case "AA":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Added);
|
||||
break;
|
||||
case "UU":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Unmerged);
|
||||
break;
|
||||
case "??":
|
||||
change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
_changes.Add(change);
|
||||
}
|
||||
|
||||
private readonly List<Models.Change> _changes = new List<Models.Change>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryRefsContainsCommit : Command
|
||||
{
|
||||
public QueryRefsContainsCommit(string repo, string commit)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
RaiseError = false;
|
||||
Args = $"for-each-ref --format=\"%(refname)\" --contains {commit}";
|
||||
}
|
||||
|
||||
public List<Models.Decorator> Result()
|
||||
{
|
||||
var rs = new List<Models.Decorator>();
|
||||
|
||||
var output = ReadToEnd();
|
||||
if (!output.IsSuccess)
|
||||
return rs;
|
||||
|
||||
var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.EndsWith("/HEAD", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
if (line.StartsWith("refs/heads/", StringComparison.Ordinal))
|
||||
rs.Add(new() { Name = line.Substring("refs/heads/".Length), Type = Models.DecoratorType.LocalBranchHead });
|
||||
else if (line.StartsWith("refs/remotes/", StringComparison.Ordinal))
|
||||
rs.Add(new() { Name = line.Substring("refs/remotes/".Length), Type = Models.DecoratorType.RemoteBranchHead });
|
||||
else if (line.StartsWith("refs/tags/", StringComparison.Ordinal))
|
||||
rs.Add(new() { Name = line.Substring("refs/tags/".Length), Type = Models.DecoratorType.Tag });
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
|
@ -18,31 +17,27 @@ namespace SourceGit.Commands
|
|||
|
||||
public List<Models.Remote> Result()
|
||||
{
|
||||
var outs = new List<Models.Remote>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return outs;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_REMOTE().Match(line);
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
var remote = new Models.Remote()
|
||||
{
|
||||
Name = match.Groups[1].Value,
|
||||
URL = match.Groups[2].Value,
|
||||
};
|
||||
|
||||
if (outs.Find(x => x.Name == remote.Name) != null)
|
||||
continue;
|
||||
|
||||
outs.Add(remote);
|
||||
}
|
||||
|
||||
return outs;
|
||||
Exec();
|
||||
return _loaded;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_REMOTE().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
var remote = new Models.Remote()
|
||||
{
|
||||
Name = match.Groups[1].Value,
|
||||
URL = match.Groups[2].Value,
|
||||
};
|
||||
|
||||
if (_loaded.Find(x => x.Name == remote.Name) != null)
|
||||
return;
|
||||
_loaded.Add(remote);
|
||||
}
|
||||
|
||||
private readonly List<Models.Remote> _loaded = new List<Models.Remote>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,15 @@
|
|||
{
|
||||
WorkingDirectory = path;
|
||||
Args = "rev-parse --show-toplevel";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd().StdOut;
|
||||
if (string.IsNullOrEmpty(rs))
|
||||
return null;
|
||||
return rs.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryRevisionByRefName : Command
|
||||
{
|
||||
public QueryRevisionByRefName(string repo, string refname)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"rev-parse {refname}";
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut))
|
||||
return rs.StdOut.Trim();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryRevisionFileNames : Command
|
||||
{
|
||||
public QueryRevisionFileNames(string repo, string revision)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"ls-tree -r -z --name-only {revision}";
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
var lines = rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var outs = new List<string>();
|
||||
foreach (var line in lines)
|
||||
outs.Add(line);
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"ls-tree -z {sha}";
|
||||
Args = $"ls-tree {sha}";
|
||||
|
||||
if (!string.IsNullOrEmpty(parentFolder))
|
||||
Args += $" -- \"{parentFolder}\"";
|
||||
|
@ -20,27 +20,11 @@ namespace SourceGit.Commands
|
|||
|
||||
public List<Models.Object> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\0', start);
|
||||
while (end > 0)
|
||||
{
|
||||
var line = rs.StdOut.Substring(start, end - start);
|
||||
Parse(line);
|
||||
start = end + 1;
|
||||
end = rs.StdOut.IndexOf('\0', start);
|
||||
}
|
||||
|
||||
if (start < rs.StdOut.Length)
|
||||
Parse(rs.StdOut.Substring(start));
|
||||
}
|
||||
|
||||
Exec();
|
||||
return _objects;
|
||||
}
|
||||
|
||||
private void Parse(string line)
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -8,7 +9,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s -s {sha}";
|
||||
Args = $"show --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s -s {sha}";
|
||||
}
|
||||
|
||||
public Models.Commit Result()
|
||||
|
|
|
@ -6,87 +6,87 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class QueryStagedChangesWithAmend : Command
|
||||
{
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMT])\d{0,6}\t(.*)$")]
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMTUX])\d{0,6}\t(.*)$")]
|
||||
private static partial Regex REG_FORMAT1();
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} R\d{0,6}\t(.*\t.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
|
||||
public QueryStagedChangesWithAmend(string repo, string parent)
|
||||
public QueryStagedChangesWithAmend(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff-index --cached -M {parent}";
|
||||
_parent = parent;
|
||||
Args = "diff-index --cached -M HEAD^";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
var changes = new List<Models.Change>();
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var match = REG_FORMAT2().Match(line);
|
||||
if (match.Success)
|
||||
var changes = new List<Models.Change>();
|
||||
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var change = new Models.Change()
|
||||
var match = REG_FORMAT2().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
Path = match.Groups[3].Value,
|
||||
DataForAmend = new Models.ChangeDataForAmend()
|
||||
var change = new Models.Change()
|
||||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
ParentSHA = _parent,
|
||||
},
|
||||
};
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
changes.Add(change);
|
||||
continue;
|
||||
}
|
||||
|
||||
match = REG_FORMAT1().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var change = new Models.Change()
|
||||
{
|
||||
Path = match.Groups[4].Value,
|
||||
DataForAmend = new Models.ChangeDataForAmend()
|
||||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
ParentSHA = _parent,
|
||||
},
|
||||
};
|
||||
|
||||
var type = match.Groups[3].Value;
|
||||
switch (type)
|
||||
{
|
||||
case "A":
|
||||
change.Set(Models.ChangeState.Added);
|
||||
break;
|
||||
case "C":
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
break;
|
||||
case "D":
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "M":
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
break;
|
||||
case "T":
|
||||
change.Set(Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
Path = match.Groups[3].Value,
|
||||
DataForAmend = new Models.ChangeDataForAmend()
|
||||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
},
|
||||
};
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
changes.Add(change);
|
||||
continue;
|
||||
}
|
||||
|
||||
match = REG_FORMAT1().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var change = new Models.Change()
|
||||
{
|
||||
Path = match.Groups[4].Value,
|
||||
DataForAmend = new Models.ChangeDataForAmend()
|
||||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
},
|
||||
};
|
||||
|
||||
var type = match.Groups[3].Value;
|
||||
switch (type)
|
||||
{
|
||||
case "A":
|
||||
change.Set(Models.ChangeState.Added);
|
||||
break;
|
||||
case "C":
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
break;
|
||||
case "D":
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "M":
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
break;
|
||||
case "T":
|
||||
change.Set(Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "U":
|
||||
change.Set(Models.ChangeState.Unmerged);
|
||||
break;
|
||||
}
|
||||
changes.Add(change);
|
||||
}
|
||||
changes.Add(change);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
return changes;
|
||||
return [];
|
||||
}
|
||||
|
||||
private readonly string _parent;
|
||||
}
|
||||
}
|
||||
|
|
60
src/Commands/QueryStashChanges.cs
Normal file
60
src/Commands/QueryStashChanges.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryStashChanges : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
|
||||
public QueryStashChanges(string repo, string sha)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff --name-status --pretty=format: {sha}^ {sha}";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
Exec();
|
||||
return _changes;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status[0])
|
||||
{
|
||||
case 'M':
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'A':
|
||||
change.Set(Models.ChangeState.Added);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'D':
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'R':
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Models.Change> _changes = new List<Models.Change>();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -9,65 +8,41 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "stash list --format=%H%n%P%n%ct%n%gd%n%s";
|
||||
Args = "stash list --pretty=format:%H%n%ct%n%gd%n%s";
|
||||
}
|
||||
|
||||
public List<Models.Stash> Result()
|
||||
{
|
||||
var outs = new List<Models.Stash>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return outs;
|
||||
Exec();
|
||||
return _stashes;
|
||||
}
|
||||
|
||||
var nextPartIdx = 0;
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\n', start);
|
||||
while (end > 0)
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
switch (_nextLineIdx)
|
||||
{
|
||||
var line = rs.StdOut.Substring(start, end - start);
|
||||
|
||||
switch (nextPartIdx)
|
||||
{
|
||||
case 0:
|
||||
_current = new Models.Stash() { SHA = line };
|
||||
outs.Add(_current);
|
||||
break;
|
||||
case 1:
|
||||
ParseParent(line);
|
||||
break;
|
||||
case 2:
|
||||
_current.Time = ulong.Parse(line);
|
||||
break;
|
||||
case 3:
|
||||
_current.Name = line;
|
||||
break;
|
||||
case 4:
|
||||
_current.Message = line;
|
||||
break;
|
||||
}
|
||||
|
||||
nextPartIdx++;
|
||||
if (nextPartIdx > 4)
|
||||
nextPartIdx = 0;
|
||||
|
||||
start = end + 1;
|
||||
end = rs.StdOut.IndexOf('\n', start);
|
||||
case 0:
|
||||
_current = new Models.Stash() { SHA = line };
|
||||
_stashes.Add(_current);
|
||||
break;
|
||||
case 1:
|
||||
_current.Time = ulong.Parse(line);
|
||||
break;
|
||||
case 2:
|
||||
_current.Name = line;
|
||||
break;
|
||||
case 3:
|
||||
_current.Message = line;
|
||||
break;
|
||||
}
|
||||
|
||||
if (start < rs.StdOut.Length)
|
||||
_current.Message = rs.StdOut.Substring(start);
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
private void ParseParent(string data)
|
||||
{
|
||||
if (data.Length < 8)
|
||||
return;
|
||||
|
||||
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
_nextLineIdx++;
|
||||
if (_nextLineIdx > 3)
|
||||
_nextLineIdx = 0;
|
||||
}
|
||||
|
||||
private readonly List<Models.Stash> _stashes = new List<Models.Stash>();
|
||||
private Models.Stash _current = null;
|
||||
private int _nextLineIdx = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QuerySubmodules : Command
|
||||
{
|
||||
[GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
|
||||
private static partial Regex REG_FORMAT_STATUS();
|
||||
[GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT_DIRTY();
|
||||
[GeneratedRegex(@"^submodule\.(\S*)\.(\w+)=(.*)$")]
|
||||
private static partial Regex REG_FORMAT_MODULE_INFO();
|
||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$")]
|
||||
private static partial Regex REG_FORMAT1();
|
||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
|
||||
public QuerySubmodules(string repo)
|
||||
{
|
||||
|
@ -21,122 +17,28 @@ namespace SourceGit.Commands
|
|||
Args = "submodule status";
|
||||
}
|
||||
|
||||
public List<Models.Submodule> Result()
|
||||
public List<string> Result()
|
||||
{
|
||||
var submodules = new List<Models.Submodule>();
|
||||
var rs = ReadToEnd();
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var map = new Dictionary<string, Models.Submodule>();
|
||||
var needCheckLocalChanges = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_STATUS().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var stat = match.Groups[1].Value;
|
||||
var sha = match.Groups[2].Value;
|
||||
var path = match.Groups[3].Value;
|
||||
|
||||
var module = new Models.Submodule() { Path = path, SHA = sha };
|
||||
switch (stat[0])
|
||||
{
|
||||
case '-':
|
||||
module.Status = Models.SubmoduleStatus.NotInited;
|
||||
break;
|
||||
case '+':
|
||||
module.Status = Models.SubmoduleStatus.RevisionChanged;
|
||||
break;
|
||||
case 'U':
|
||||
module.Status = Models.SubmoduleStatus.Unmerged;
|
||||
break;
|
||||
default:
|
||||
module.Status = Models.SubmoduleStatus.Normal;
|
||||
needCheckLocalChanges = true;
|
||||
break;
|
||||
}
|
||||
|
||||
map.Add(path, module);
|
||||
submodules.Add(module);
|
||||
}
|
||||
}
|
||||
|
||||
if (submodules.Count > 0)
|
||||
{
|
||||
Args = "config --file .gitmodules --list";
|
||||
rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var modules = new Dictionary<string, ModuleInfo>();
|
||||
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_MODULE_INFO().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var name = match.Groups[1].Value;
|
||||
var key = match.Groups[2].Value;
|
||||
var val = match.Groups[3].Value;
|
||||
|
||||
if (!modules.TryGetValue(name, out var m))
|
||||
{
|
||||
m = new ModuleInfo();
|
||||
modules.Add(name, m);
|
||||
}
|
||||
|
||||
if (key.Equals("path", StringComparison.Ordinal))
|
||||
m.Path = val;
|
||||
else if (key.Equals("url", StringComparison.Ordinal))
|
||||
m.URL = val;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in modules)
|
||||
{
|
||||
if (map.TryGetValue(kv.Value.Path, out var m))
|
||||
m.URL = kv.Value.URL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needCheckLocalChanges)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var kv in map)
|
||||
{
|
||||
if (kv.Value.Status == Models.SubmoduleStatus.Normal)
|
||||
{
|
||||
builder.Append('"');
|
||||
builder.Append(kv.Key);
|
||||
builder.Append("\" ");
|
||||
}
|
||||
}
|
||||
|
||||
Args = $"--no-optional-locks status --porcelain -- {builder}";
|
||||
rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_DIRTY().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
if (map.TryGetValue(path, out var m))
|
||||
m.Status = Models.SubmoduleStatus.Modified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return submodules;
|
||||
Exec();
|
||||
return _submodules;
|
||||
}
|
||||
|
||||
private class ModuleInfo
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string URL { get; set; } = string.Empty;
|
||||
var match = REG_FORMAT1().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
_submodules.Add(match.Groups[1].Value);
|
||||
return;
|
||||
}
|
||||
|
||||
match = REG_FORMAT2().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
_submodules.Add(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<string> _submodules = new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,45 +7,38 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public QueryTags(string repo)
|
||||
{
|
||||
_boundary = $"----- BOUNDARY OF TAGS {Guid.NewGuid()} -----";
|
||||
|
||||
Context = repo;
|
||||
WorkingDirectory = repo;
|
||||
Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objecttype)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\"";
|
||||
Args = "tag -l --sort=-creatordate --format=\"$%(refname)$%(objectname)$%(*objectname)\"";
|
||||
}
|
||||
|
||||
public List<Models.Tag> Result()
|
||||
{
|
||||
var tags = new List<Models.Tag>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return tags;
|
||||
|
||||
var records = rs.StdOut.Split(_boundary, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var record in records)
|
||||
{
|
||||
var subs = record.Split('\0', StringSplitOptions.None);
|
||||
if (subs.Length != 6)
|
||||
continue;
|
||||
|
||||
var name = subs[0].Substring(10);
|
||||
var message = subs[5].Trim();
|
||||
if (!string.IsNullOrEmpty(message) && message.Equals(name, StringComparison.Ordinal))
|
||||
message = null;
|
||||
|
||||
tags.Add(new Models.Tag()
|
||||
{
|
||||
Name = name,
|
||||
IsAnnotated = subs[1].Equals("tag", StringComparison.Ordinal),
|
||||
SHA = string.IsNullOrEmpty(subs[3]) ? subs[2] : subs[3],
|
||||
CreatorDate = ulong.Parse(subs[4]),
|
||||
Message = message,
|
||||
});
|
||||
}
|
||||
|
||||
return tags;
|
||||
Exec();
|
||||
return _loaded;
|
||||
}
|
||||
|
||||
private string _boundary = string.Empty;
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var subs = line.Split('$', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (subs.Length == 2)
|
||||
{
|
||||
_loaded.Add(new Models.Tag()
|
||||
{
|
||||
Name = subs[0].Substring(10),
|
||||
SHA = subs[1],
|
||||
});
|
||||
}
|
||||
else if (subs.Length == 3)
|
||||
{
|
||||
_loaded.Add(new Models.Tag()
|
||||
{
|
||||
Name = subs[0].Substring(10),
|
||||
SHA = subs[2],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Models.Tag> _loaded = new List<Models.Tag>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace SourceGit.Commands
|
|||
if (!rs.IsSuccess)
|
||||
return status;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line[0] == '>')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue