mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-16 16:05:00 +00:00
Compare commits
No commits in common. "master" and "v8.0" have entirely different histories.
641 changed files with 13650 additions and 64833 deletions
306
.editorconfig
306
.editorconfig
|
@ -1,306 +0,0 @@
|
|||
# editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Default settings:
|
||||
# A newline ending every file
|
||||
# Use 4 spaces as indentation
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_prefer_collection_expression = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_namespace_match_folder = true:suggestion
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
# New line preferences
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
# trim_trailing_whitespace = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
|
||||
# avoid this. unless absolutely necessary
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
|
||||
# prefer var
|
||||
csharp_style_var_for_built_in_types = true
|
||||
csharp_style_var_when_type_is_apparent = true
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
# use language keywords instead of BCL types
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
# name all constant fields using PascalCase
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
# private static fields should have s_ prefix
|
||||
dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
|
||||
dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
|
||||
dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style
|
||||
|
||||
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
||||
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
|
||||
|
||||
dotnet_naming_style.private_static_prefix_style.required_prefix = s_
|
||||
dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
|
||||
|
||||
# internal and private fields should be _camelCase
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
|
||||
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
|
||||
|
||||
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
|
||||
|
||||
# use accessibility modifiers
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
||||
# Code style defaults
|
||||
dotnet_sort_system_directives_first = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = false
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_methods = false:none
|
||||
csharp_style_expression_bodied_constructors = false:none
|
||||
csharp_style_expression_bodied_operators = false:none
|
||||
csharp_style_expression_bodied_properties = true:none
|
||||
csharp_style_expression_bodied_indexers = true:none
|
||||
csharp_style_expression_bodied_accessors = true:none
|
||||
|
||||
# Pattern matching
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
|
||||
# Null checking preferences
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
space_within_single_line_array_initializer_braces = true
|
||||
|
||||
#Net Analyzer
|
||||
dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
|
||||
|
||||
# CS0649: Field 'field' is never assigned to, and will always have its default value 'value'
|
||||
dotnet_diagnostic.CS0649.severity = error
|
||||
|
||||
# CS1591: Missing XML comment for publicly visible type or member
|
||||
dotnet_diagnostic.CS1591.severity = suggestion
|
||||
|
||||
# CS0162: Remove unreachable code
|
||||
dotnet_diagnostic.CS0162.severity = error
|
||||
# CA1018: Mark attributes with AttributeUsageAttribute
|
||||
dotnet_diagnostic.CA1018.severity = error
|
||||
# CA1304: Specify CultureInfo
|
||||
dotnet_diagnostic.CA1304.severity = warning
|
||||
# CA1802: Use literals where appropriate
|
||||
dotnet_diagnostic.CA1802.severity = warning
|
||||
# CA1813: Avoid unsealed attributes
|
||||
dotnet_diagnostic.CA1813.severity = error
|
||||
# CA1815: Override equals and operator equals on value types
|
||||
dotnet_diagnostic.CA1815.severity = warning
|
||||
# CA1820: Test for empty strings using string length
|
||||
dotnet_diagnostic.CA1820.severity = warning
|
||||
# CA1821: Remove empty finalizers
|
||||
dotnet_diagnostic.CA1821.severity = warning
|
||||
# CA1822: Mark members as static
|
||||
dotnet_diagnostic.CA1822.severity = suggestion
|
||||
# CA1823: Avoid unused private fields
|
||||
dotnet_diagnostic.CA1823.severity = warning
|
||||
dotnet_code_quality.CA1822.api_surface = private, internal
|
||||
# CA1825: Avoid zero-length array allocations
|
||||
dotnet_diagnostic.CA1825.severity = warning
|
||||
# CA1826: Use property instead of Linq Enumerable method
|
||||
dotnet_diagnostic.CA1826.severity = suggestion
|
||||
# CA1827: Do not use Count/LongCount when Any can be used
|
||||
dotnet_diagnostic.CA1827.severity = warning
|
||||
# CA1828: Do not use CountAsync/LongCountAsync when AnyAsync can be used
|
||||
dotnet_diagnostic.CA1828.severity = warning
|
||||
# CA1829: Use Length/Count property instead of Enumerable.Count method
|
||||
dotnet_diagnostic.CA1829.severity = warning
|
||||
#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
|
||||
dotnet_diagnostic.CA1847.severity = warning
|
||||
#CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method
|
||||
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
|
||||
|
||||
# Avalonia DevAnalyzer preferences
|
||||
dotnet_diagnostic.AVADEV2001.severity = error
|
||||
|
||||
# Avalonia PublicAnalyzer preferences
|
||||
dotnet_diagnostic.AVP1000.severity = error
|
||||
dotnet_diagnostic.AVP1001.severity = error
|
||||
dotnet_diagnostic.AVP1002.severity = error
|
||||
dotnet_diagnostic.AVP1010.severity = error
|
||||
dotnet_diagnostic.AVP1011.severity = error
|
||||
dotnet_diagnostic.AVP1012.severity = warning
|
||||
dotnet_diagnostic.AVP1013.severity = error
|
||||
dotnet_diagnostic.AVP1020.severity = error
|
||||
dotnet_diagnostic.AVP1021.severity = error
|
||||
dotnet_diagnostic.AVP1022.severity = error
|
||||
dotnet_diagnostic.AVP1030.severity = error
|
||||
dotnet_diagnostic.AVP1031.severity = error
|
||||
dotnet_diagnostic.AVP1032.severity = error
|
||||
dotnet_diagnostic.AVP1040.severity = error
|
||||
dotnet_diagnostic.AVA2001.severity = error
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
csharp_style_prefer_readonly_struct = true:suggestion
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
csharp_style_prefer_readonly_struct_member = true:suggestion
|
||||
|
||||
# Xaml files
|
||||
[*.{xaml,axaml}]
|
||||
indent_size = 2
|
||||
# DuplicateSetterError
|
||||
avalonia_xaml_diagnostic.AVLN2203.severity = error
|
||||
# StyleInMergedDictionaries
|
||||
avalonia_xaml_diagnostic.AVLN2204.severity = error
|
||||
# RequiredTemplatePartMissing
|
||||
avalonia_xaml_diagnostic.AVLN2205.severity = error
|
||||
# OptionalTemplatePartMissing
|
||||
avalonia_xaml_diagnostic.AVLN2206.severity = info
|
||||
# TemplatePartWrongType
|
||||
avalonia_xaml_diagnostic.AVLN2207.severity = error
|
||||
# Obsolete
|
||||
avalonia_xaml_diagnostic.AVLN5001.severity = error
|
||||
|
||||
# Xml project files
|
||||
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
|
||||
indent_size = 2
|
||||
|
||||
# Xml build files
|
||||
[*.builds]
|
||||
indent_size = 2
|
||||
|
||||
# Xml files
|
||||
[*.{xml,stylecop,resx,ruleset}]
|
||||
indent_size = 2
|
||||
|
||||
# Xml config files
|
||||
[*.{props,targets,config,nuspec}]
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
# Shell scripts
|
||||
[*.sh]
|
||||
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
|
14
.gitattributes
vendored
14
.gitattributes
vendored
|
@ -1,14 +0,0 @@
|
|||
* text=auto
|
||||
*.md text
|
||||
*.png binary
|
||||
*.ico binary
|
||||
*.sh text eol=lf
|
||||
*.spec text eol=lf
|
||||
control text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
*.json text
|
||||
|
||||
.gitattributes 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/*
|
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
|
@ -1,29 +0,0 @@
|
|||
name: Continuous Integration
|
||||
on:
|
||||
push:
|
||||
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 }}
|
||||
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 }}
|
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/*
|
45
.gitignore
vendored
45
.gitignore
vendored
|
@ -1,41 +1,6 @@
|
|||
.vs/
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
*.sln.docstates
|
||||
.idea
|
||||
.vs
|
||||
.vscode
|
||||
bin
|
||||
obj
|
||||
*.user
|
||||
*.suo
|
||||
*.code-workspace
|
||||
|
||||
.DS_Store
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
bin/
|
||||
obj/
|
||||
# ignore ci node files
|
||||
node_modules/
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
build/resources/
|
||||
build/SourceGit/
|
||||
build/SourceGit.app/
|
||||
build/*.zip
|
||||
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.
|
256
README.md
256
README.md
|
@ -1,207 +1,49 @@
|
|||
# SourceGit - Opensource Git GUI client.
|
||||
|
||||
[](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)
|
||||
|
||||
## Highlights
|
||||
|
||||
* Supports Windows/macOS/Linux
|
||||
* Opensource/Free
|
||||
* Fast
|
||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/Українська/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
||||
* Built-in light/dark themes
|
||||
* Customize theme
|
||||
* Visual commit graph
|
||||
* Supports SSH access with each remote
|
||||
* GIT commands with GUI
|
||||
* Clone/Fetch/Pull/Push...
|
||||
* Merge/Rebase/Reset/Revert/Cherry-pick...
|
||||
* Amend/Reword/Squash
|
||||
* Interactive rebase
|
||||
* Branches
|
||||
* Remotes
|
||||
* Tags
|
||||
* Stashes
|
||||
* Submodules
|
||||
* Worktrees
|
||||
* Archive
|
||||
* Diff
|
||||
* Save as patch/apply
|
||||
* File histories
|
||||
* Blame
|
||||
* 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))
|
||||
|
||||
> [!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.**
|
||||
|
||||
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.
|
||||
|
||||
For **Windows** users:
|
||||
|
||||
* **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead.
|
||||
* You can install the latest stable from `winget` with follow commands:
|
||||
```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:
|
||||
```shell
|
||||
scoop bucket add extras
|
||||
scoop install sourcegit
|
||||
```
|
||||
* Pre-built binaries 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
|
||||
```
|
||||
* 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.
|
||||
|
||||
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.
|
||||
* 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 |
|
||||
|
||||
> [!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.
|
||||
|
||||
## Screenshots
|
||||
|
||||
* Dark Theme
|
||||
|
||||

|
||||
|
||||
* Light Theme
|
||||
|
||||

|
||||
|
||||
* Custom
|
||||
|
||||
You can find custom themes from [sourcegit-theme](https://github.com/sourcegit-scm/sourcegit-theme.git). And welcome to share your own themes.
|
||||
|
||||
## 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).
|
||||
# SourceGit
|
||||
|
||||
Opensouce Git GUI client.
|
||||
|
||||
> To use this tool, you need to install Git first.
|
||||
|
||||
## High-lights
|
||||
|
||||
* Supports Windows/macOS
|
||||
* Opensource/Free
|
||||
* Fast
|
||||
* English/简体中文
|
||||
* Build-in light/dark themes
|
||||
* Visual commit graph
|
||||
* Supports SSH access with each remote
|
||||
* GIT commands with GUI
|
||||
* Clone/Fetch/Pull/Push...
|
||||
* Branches
|
||||
* Remotes
|
||||
* Tags
|
||||
* Stashes
|
||||
* Submodules
|
||||
* Archive
|
||||
* Patch/apply
|
||||
* File histories
|
||||
* Blame
|
||||
* Revision Diffs
|
||||
|
||||
## Download
|
||||
|
||||
Pre-build Binaries:[Releases](https://github.com/sourcegit-scm/sourcegit/releases)
|
||||
|
||||
## Screen Shots
|
||||
|
||||
* Drak Theme
|
||||
|
||||

|
||||
|
||||
* Light Theme
|
||||
|
||||

|
||||
|
||||
## Thanks
|
||||
|
||||
* [XiaoLinger](https://gitee.com/LingerNN) Hotkey: `CTRL + Enter` to commit
|
||||
* [carterl](https://gitee.com/carterl) Supports Windows Terminal; Rewrite way to find git executable
|
||||
* [PUMA](https://gitee.com/whgfu) Configure for default user
|
||||
* [Rwing](https://gitee.com/rwing) GitFlow: add an option to keep branch after finish
|
||||
* [XiaoLinger](https://gitee.com/LingerNN) Fix localizations in popup panel
|
||||
|
|
122
SourceGit.sln
122
SourceGit.sln
|
@ -1,122 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34714.143
|
||||
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}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "resources", "resources", "{FD384607-ED99-47B7-AF31-FB245841BC92}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{F45A9D95-AF25-42D8-BBAC-8259C9EEE820}"
|
||||
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}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "files", "files", "{3AB707DB-A02C-4AFC-BF12-D7DF2B333BAC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitattributes = .gitattributes
|
||||
.gitignore = .gitignore
|
||||
global.json = global.json
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
VERSION = VERSION
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "app", "app", "{ABC98884-F023-4EF4-A9C9-5DE9452BE955}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\resources\app\App.icns = build\resources\app\App.icns
|
||||
build\resources\app\App.plist = build\resources\app\App.plist
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_common", "_common", "{04FD74B1-FBDB-496E-A48F-3D59D71FF952}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "usr", "usr", "{76639799-54BC-45E8-BD90-F45F63ACD11D}"
|
||||
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}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\resources\_common\usr\share\applications\sourcegit.desktop = build\resources\_common\usr\share\applications\sourcegit.desktop
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "icons", "icons", "{7166EC6C-17F5-4B5E-B38E-1E53C81EACF6}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\resources\_common\usr\share\icons\sourcegit.png = build\resources\_common\usr\share\icons\sourcegit.png
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deb", "deb", "{9C2F0CDA-B56E-44A5-94B6-F3EA7AC20CDC}"
|
||||
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}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SPECS", "SPECS", "{7802CD7A-591B-4EDD-96F8-9BF3F61692E4}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\resources\rpm\SPECS\build.spec = build\resources\rpm\SPECS\build.spec
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "appimage", "appimage", "{5D125DD9-B48A-491F-B2FB-D7830D74C4DC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
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
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4} = {49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}
|
||||
{FD384607-ED99-47B7-AF31-FB245841BC92} = {773082AC-D9C8-4186-8521-4B6A7BEE6158}
|
||||
{67B6D05F-A000-40BA-ADB4-C9065F880D7B} = {F45A9D95-AF25-42D8-BBAC-8259C9EEE820}
|
||||
{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}
|
||||
{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}
|
||||
{9C2F0CDA-B56E-44A5-94B6-F3EA7AC20CDC} = {FD384607-ED99-47B7-AF31-FB245841BC92}
|
||||
{F101849D-BDB7-40D4-A516-751150C3CCFC} = {9C2F0CDA-B56E-44A5-94B6-F3EA7AC20CDC}
|
||||
{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}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -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>
|
||||
|
||||
### 
|
||||
|
||||
### 
|
1
VERSION
1
VERSION
|
@ -1 +0,0 @@
|
|||
2025.22
|
|
@ -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.
|
|
@ -1,9 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Name=SourceGit
|
||||
Comment=Open-source & Free Git GUI Client
|
||||
Exec=/opt/sourcegit/sourcegit
|
||||
Icon=/usr/share/icons/sourcegit.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Development
|
||||
MimeType=inode/directory;
|
Binary file not shown.
Before Width: | Height: | Size: 37 KiB |
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>App.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.sourcegit-scm.sourcegit</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>SourceGit</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>SOURCE_GIT_VERSION.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SourceGit</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>SOURCE_GIT_VERSION</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.sourcegit_scm.SourceGit</id>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<name>SourceGit</name>
|
||||
<summary>Open-source GUI client for git users</summary>
|
||||
<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>
|
||||
<provides>
|
||||
<id>com.sourcegit_scm.SourceGit.desktop</id>
|
||||
</provides>
|
||||
</component>
|
Binary file not shown.
Before Width: | Height: | Size: 37 KiB |
|
@ -1,8 +0,0 @@
|
|||
Package: sourcegit
|
||||
Version: 2025.10
|
||||
Priority: optional
|
||||
Depends: libx11-6, libice6, libsm6, libicu | libicu76 | libicu74 | libicu72 | libicu71 | libicu70 | libicu69 | libicu68 | libicu67 | libicu66 | libicu65 | libicu63 | libicu60 | libicu57 | libicu55 | libicu52, xdg-utils
|
||||
Architecture: amd64
|
||||
Installed-Size: 60440
|
||||
Maintainer: longshuang@msn.cn
|
||||
Description: Open-source & Free Git GUI Client
|
|
@ -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
|
|
@ -1,38 +0,0 @@
|
|||
Name: sourcegit
|
||||
Version: %_version
|
||||
Release: 1
|
||||
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
|
||||
|
||||
%define _build_id_links none
|
||||
|
||||
%description
|
||||
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
|
||||
|
||||
%files
|
||||
%dir /opt/sourcegit/
|
||||
/opt/sourcegit/*
|
||||
/usr/share/applications/sourcegit.desktop
|
||||
/usr/share/icons/*
|
||||
%{_bindir}/sourcegit
|
||||
|
||||
%changelog
|
||||
# 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,7 +0,0 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "9.0.0",
|
||||
"rollForward": "latestMajor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 804 KiB After Width: | Height: | Size: 340 KiB |
Binary file not shown.
Before Width: | Height: | Size: 712 KiB After Width: | Height: | Size: 343 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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace SourceGit
|
||||
{
|
||||
public class ColorConverter : JsonConverter<Color>
|
||||
{
|
||||
public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return Color.Parse(reader.GetString());
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public class GridLengthConverter : JsonConverter<GridLength>
|
||||
{
|
||||
public override GridLength Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var size = reader.GetDouble();
|
||||
return new GridLength(size, GridUnitType.Pixel);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, GridLength value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteNumberValue(value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(
|
||||
WriteIndented = true,
|
||||
IgnoreReadOnlyFields = true,
|
||||
IgnoreReadOnlyProperties = true,
|
||||
Converters = [
|
||||
typeof(ColorConverter),
|
||||
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))]
|
||||
internal partial class JsonCodeGen : JsonSerializerContext { }
|
||||
}
|
|
@ -1,47 +1,25 @@
|
|||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="using:SourceGit"
|
||||
x:Class="SourceGit.App"
|
||||
Name="SourceGit"
|
||||
RequestedThemeVariant="Dark">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceInclude Source="/Resources/Fonts.axaml"/>
|
||||
<ResourceInclude Source="/Resources/Icons.axaml"/>
|
||||
<ResourceInclude Source="/Resources/Themes.axaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<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>
|
||||
|
||||
<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}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" Gesture="⌘+,"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.OpenAppDataDir}" Command="{x:Static s:App.OpenAppDataDirCommand}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}" Gesture="⌘+Q"/>
|
||||
</NativeMenu>
|
||||
</NativeMenu.Menu>
|
||||
</Application>
|
||||
</Application>
|
743
src/App.axaml.cs
743
src/App.axaml.cs
|
@ -1,706 +1,159 @@
|
|||
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 Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
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;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit {
|
||||
public partial class App : Application {
|
||||
|
||||
namespace SourceGit
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
#region App Entry Point
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Native.OS.SetupDataDir();
|
||||
public static void Main(string[] args) {
|
||||
try {
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
} catch (Exception ex) {
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("Crash: ");
|
||||
builder.Append(ex.Message);
|
||||
builder.Append("\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);
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
|
||||
{
|
||||
LogException(e.ExceptionObject as Exception);
|
||||
};
|
||||
|
||||
TaskScheduler.UnobservedTaskException += (_, e) =>
|
||||
{
|
||||
e.SetObserved();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (TryLaunchAsRebaseTodoEditor(args, out int exitTodo))
|
||||
Environment.Exit(exitTodo);
|
||||
else if (TryLaunchAsRebaseMessageEditor(args, out int exitMessage))
|
||||
Environment.Exit(exitMessage);
|
||||
else
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogException(ex);
|
||||
}
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
var file = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"SourceGit",
|
||||
$"crash_{time}.log");
|
||||
File.WriteAllText(file, builder.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
public static AppBuilder BuildAvaloniaApp() {
|
||||
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(
|
||||
new Uri("fonts:SourceGit", UriKind.Absolute),
|
||||
new Uri("avares://SourceGit/Resources/Fonts", UriKind.Absolute));
|
||||
manager.AddFontCollection(monospace);
|
||||
});
|
||||
|
||||
Native.OS.SetupApp(builder);
|
||||
if (OperatingSystem.IsWindows()) {
|
||||
builder.With(new FontManagerOptions() {
|
||||
FontFallbacks = [
|
||||
new FontFallback { FontFamily = new FontFamily("Microsoft YaHei UI") }
|
||||
]
|
||||
});
|
||||
} else if (OperatingSystem.IsMacOS()) {
|
||||
builder.With(new FontManagerOptions() {
|
||||
FontFallbacks = [
|
||||
new FontFallback { FontFamily = new FontFamily("PingFang SC") }
|
||||
]
|
||||
});
|
||||
builder.With(new MacOSPlatformOptions() {
|
||||
DisableDefaultApplicationMenuItems = true,
|
||||
DisableNativeMenus = true,
|
||||
});
|
||||
}
|
||||
|
||||
builder.LogToTrace();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static void LogException(Exception ex)
|
||||
{
|
||||
if (ex == null)
|
||||
return;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n");
|
||||
builder.Append("----------------------------\n");
|
||||
builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
|
||||
builder.Append($"OS: {Environment.OSVersion}\n");
|
||||
builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
|
||||
builder.Append($"Source: {ex.Source}\n");
|
||||
builder.Append($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}\n");
|
||||
builder.Append($"User: {Environment.UserName}\n");
|
||||
builder.Append($"App Start Time: {Process.GetCurrentProcess().StartTime}\n");
|
||||
builder.Append($"Exception Time: {DateTime.Now}\n");
|
||||
builder.Append($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB\n");
|
||||
builder.Append($"---------------------------\n\n");
|
||||
builder.Append(ex);
|
||||
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
|
||||
File.WriteAllText(file, builder.ToString());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Utility Functions
|
||||
public static void ShowWindow(object data, bool showAsDialog)
|
||||
{
|
||||
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))
|
||||
return;
|
||||
|
||||
var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views.");
|
||||
var viewType = Type.GetType(viewTypeName);
|
||||
if (viewType == null || !viewType.IsSubclassOf(typeof(Views.ChromelessWindow)))
|
||||
return;
|
||||
|
||||
window = Activator.CreateInstance(viewType) as Views.ChromelessWindow;
|
||||
if (window != null)
|
||||
{
|
||||
window.DataContext = data;
|
||||
impl(window, showAsDialog);
|
||||
public static void RaiseException(string context, string message) {
|
||||
if (Current is App app && app._notificationReceiver != null) {
|
||||
var ctx = context.Replace('\\', '/');
|
||||
var notice = new Models.Notification() { IsError = true, Message = message };
|
||||
app._notificationReceiver.OnReceiveNotification(ctx, notice);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RaiseException(string context, string message)
|
||||
{
|
||||
if (Current is App app && app._launcher != null)
|
||||
app._launcher.DispatchNotification(context, message, true);
|
||||
public static void SendNotification(string context, string message) {
|
||||
if (Current is App app && app._notificationReceiver != null) {
|
||||
var ctx = context.Replace('\\', '/');
|
||||
var notice = new Models.Notification() { IsError = false, Message = message };
|
||||
app._notificationReceiver.OnReceiveNotification(ctx, notice);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SendNotification(string context, string message)
|
||||
{
|
||||
if (Current is App app && app._launcher != null)
|
||||
app._launcher.DispatchNotification(context, message, false);
|
||||
}
|
||||
|
||||
public static void SetLocale(string localeKey)
|
||||
{
|
||||
public static void SetLocale(string localeKey) {
|
||||
var app = Current as App;
|
||||
if (app == null)
|
||||
return;
|
||||
|
||||
var targetLocale = app.Resources[localeKey] as ResourceDictionary;
|
||||
if (targetLocale == null || targetLocale == app._activeLocale)
|
||||
if (targetLocale == null || targetLocale == app._activeLocale) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (app._activeLocale != null)
|
||||
if (app._activeLocale != null) {
|
||||
app.Resources.MergedDictionaries.Remove(app._activeLocale);
|
||||
}
|
||||
|
||||
app.Resources.MergedDictionaries.Add(targetLocale);
|
||||
app._activeLocale = targetLocale;
|
||||
}
|
||||
|
||||
public static void SetTheme(string theme, string themeOverridesFile)
|
||||
{
|
||||
var app = Current as App;
|
||||
if (app == null)
|
||||
return;
|
||||
|
||||
if (theme.Equals("Light", StringComparison.OrdinalIgnoreCase))
|
||||
app.RequestedThemeVariant = ThemeVariant.Light;
|
||||
else if (theme.Equals("Dark", StringComparison.OrdinalIgnoreCase))
|
||||
app.RequestedThemeVariant = ThemeVariant.Dark;
|
||||
else
|
||||
app.RequestedThemeVariant = ThemeVariant.Default;
|
||||
|
||||
if (app._themeOverrides != null)
|
||||
{
|
||||
app.Resources.MergedDictionaries.Remove(app._themeOverrides);
|
||||
app._themeOverrides = null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(themeOverridesFile) && File.Exists(themeOverridesFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
var resDic = new ResourceDictionary();
|
||||
var overrides = JsonSerializer.Deserialize(File.ReadAllText(themeOverridesFile), JsonCodeGen.Default.ThemeOverrides);
|
||||
foreach (var kv in overrides.BasicColors)
|
||||
{
|
||||
if (kv.Key.Equals("SystemAccentColor", StringComparison.Ordinal))
|
||||
resDic["SystemAccentColor"] = kv.Value;
|
||||
else
|
||||
resDic[$"Color.{kv.Key}"] = kv.Value;
|
||||
}
|
||||
|
||||
if (overrides.GraphColors.Count > 0)
|
||||
Models.CommitGraph.SetPens(overrides.GraphColors, overrides.GraphPenThickness);
|
||||
else
|
||||
Models.CommitGraph.SetDefaultPens(overrides.GraphPenThickness);
|
||||
|
||||
Models.Commit.OpacityForNotMerged = overrides.OpacityForNotMergedCommits;
|
||||
|
||||
app.Resources.MergedDictionaries.Add(resDic);
|
||||
app._themeOverrides = resDic;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Models.CommitGraph.SetDefaultPens();
|
||||
public static void SetTheme(string theme) {
|
||||
if (theme.Equals("Light", StringComparison.OrdinalIgnoreCase)) {
|
||||
App.Current.RequestedThemeVariant = ThemeVariant.Light;
|
||||
} else if (theme.Equals("Dark", StringComparison.OrdinalIgnoreCase)) {
|
||||
App.Current.RequestedThemeVariant = ThemeVariant.Dark;
|
||||
} else {
|
||||
App.Current.RequestedThemeVariant = ThemeVariant.Default;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
public static async void CopyText(string data) {
|
||||
if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||
if (desktop.MainWindow.Clipboard is { } clipbord) {
|
||||
await clipbord.SetTextAsync(data);
|
||||
}
|
||||
}
|
||||
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 ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> GetClipboardTextAsync()
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (desktop.MainWindow?.Clipboard is { } clipboard)
|
||||
{
|
||||
return await clipboard.GetTextAsync();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string Text(string key, params object[] args)
|
||||
{
|
||||
var fmt = Current?.FindResource($"Text.{key}") as string;
|
||||
if (string.IsNullOrWhiteSpace(fmt))
|
||||
return $"Text.{key}";
|
||||
|
||||
if (args == null || args.Length == 0)
|
||||
return fmt;
|
||||
|
||||
public static string Text(string key, params object[] args) {
|
||||
var fmt = Current.FindResource($"Text.{key}") as string;
|
||||
if (string.IsNullOrWhiteSpace(fmt)) return $"Text.{key}";
|
||||
return string.Format(fmt, args);
|
||||
}
|
||||
|
||||
public static Avalonia.Controls.Shapes.Path CreateMenuIcon(string key)
|
||||
{
|
||||
var icon = new Avalonia.Controls.Shapes.Path();
|
||||
icon.Width = 12;
|
||||
icon.Height = 12;
|
||||
icon.Stretch = Stretch.Uniform;
|
||||
|
||||
if (Current?.FindResource(key) is StreamGeometry geo)
|
||||
icon.Data = geo;
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
public static IStorageProvider GetStorageProvider()
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
return desktop.MainWindow?.StorageProvider;
|
||||
|
||||
public static TopLevel GetTopLevel() {
|
||||
if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||
return desktop.MainWindow;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ViewModels.Launcher GetLauncher()
|
||||
{
|
||||
return Current is App app ? app._launcher : null;
|
||||
}
|
||||
|
||||
public static void Quit(int exitCode)
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow?.Close();
|
||||
desktop.Shutdown(exitCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Environment.Exit(exitCode);
|
||||
public static void Quit() {
|
||||
if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||
desktop.MainWindow.Close();
|
||||
desktop.Shutdown();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
public override void Initialize()
|
||||
{
|
||||
public override void Initialize() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
pref.PropertyChanged += (_, _) => pref.Save();
|
||||
|
||||
SetLocale(pref.Locale);
|
||||
SetTheme(pref.Theme, pref.ThemeOverrides);
|
||||
SetFonts(pref.DefaultFontFamily, pref.MonospaceFontFamily, pref.OnlyUseMonoFontInEditor);
|
||||
SetLocale(ViewModels.Preference.Instance.Locale);
|
||||
SetTheme(ViewModels.Preference.Instance.Theme);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
public override void OnFrameworkInitializationCompleted() {
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||
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))
|
||||
return;
|
||||
|
||||
if (TryLaunchAsAskpass(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
|
||||
{
|
||||
exitCode = -1;
|
||||
|
||||
if (args.Length <= 1 || !args[0].Equals("--rebase-todo-editor", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
var file = args[1];
|
||||
var filename = Path.GetFileName(file);
|
||||
if (!filename.Equals("git-rebase-todo", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
var dirInfo = new DirectoryInfo(Path.GetDirectoryName(file)!);
|
||||
if (!dirInfo.Exists || !dirInfo.Name.Equals("rebase-merge", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
var jobsFile = Path.Combine(dirInfo.Parent!.FullName, "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(jobsFile))
|
||||
return true;
|
||||
|
||||
var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection);
|
||||
var lines = new List<string>();
|
||||
foreach (var job in collection.Jobs)
|
||||
{
|
||||
switch (job.Action)
|
||||
{
|
||||
case Models.InteractiveRebaseAction.Pick:
|
||||
lines.Add($"p {job.SHA}");
|
||||
break;
|
||||
case Models.InteractiveRebaseAction.Edit:
|
||||
lines.Add($"e {job.SHA}");
|
||||
break;
|
||||
case Models.InteractiveRebaseAction.Reword:
|
||||
lines.Add($"r {job.SHA}");
|
||||
break;
|
||||
case Models.InteractiveRebaseAction.Squash:
|
||||
lines.Add($"s {job.SHA}");
|
||||
break;
|
||||
case Models.InteractiveRebaseAction.Fixup:
|
||||
lines.Add($"f {job.SHA}");
|
||||
break;
|
||||
default:
|
||||
lines.Add($"d {job.SHA}");
|
||||
break;
|
||||
}
|
||||
var launcher = new Views.Launcher();
|
||||
_notificationReceiver = launcher;
|
||||
desktop.MainWindow = launcher;
|
||||
}
|
||||
|
||||
File.WriteAllLines(file, lines);
|
||||
|
||||
exitCode = 0;
|
||||
return true;
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private static bool TryLaunchAsRebaseMessageEditor(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))
|
||||
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))
|
||||
return true;
|
||||
|
||||
var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length == 0)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var args = desktop.Args;
|
||||
if (args == null || args.Length <= 1 || !args[0].Equals("--core-editor", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
var file = args[1];
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
desktop.Shutdown(-1);
|
||||
return true;
|
||||
}
|
||||
|
||||
var editor = new Views.StandaloneCommitMessageEditor();
|
||||
editor.SetFile(file);
|
||||
desktop.MainWindow = editor;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchAsAskpass(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var launchAsAskpass = Environment.GetEnvironmentVariable("SOURCEGIT_LAUNCH_AS_ASKPASS");
|
||||
if (launchAsAskpass is not "TRUE")
|
||||
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;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
Native.OS.SetupExternalTools();
|
||||
Models.AvatarManager.Instance.Start();
|
||||
|
||||
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
|
||||
if (pref.ShouldCheck4UpdateOnStartup())
|
||||
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;
|
||||
private Models.INotificationReceiver _notificationReceiver = 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"/>
|
||||
|
||||
|
|
26
src/App.plist
Normal file
26
src/App.plist
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>App.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.sourcegit-scm.sourcegit</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>SourceGit</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>8.0.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.12</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SourceGit</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>8.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
23
src/BuildMacOS.command
Normal file
23
src/BuildMacOS.command
Normal file
|
@ -0,0 +1,23 @@
|
|||
#!/bin/sh
|
||||
|
||||
dotnet publish -c Release -r osx-x64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained
|
||||
dotnet publish -c Release -r osx-arm64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained
|
||||
|
||||
rm -rf macOS
|
||||
mkdir -p macOS
|
||||
mkdir -p macOS/x64/SourceGit.app/Contents/MacOS
|
||||
mkdir -p macOS/arm64/SourceGit.app/Contents/MacOS
|
||||
mkdir -p macOS/x64/SourceGit.app/Contents/Resources
|
||||
mkdir -p macOS/arm64/SourceGit.app/Contents/Resources
|
||||
|
||||
cp App.plist macOS/x64/SourceGit.app/Contents/Info.plist
|
||||
cp App.plist macOS/arm64/SourceGit.app/Contents/Info.plist
|
||||
|
||||
cp App.icns macOS/x64/SourceGit.app/Contents/Resources/App.icns
|
||||
cp App.icns macOS/arm64/SourceGit.app/Contents/Resources/App.icns
|
||||
|
||||
cp -r bin/Release/net8.0/osx-x64/publish/* macOS/x64/SourceGit.app/Contents/MacOS/
|
||||
cp -r bin/Release/net8.0/osx-arm64/publish/* macOS/arm64/SourceGit.app/Contents/MacOS/
|
||||
|
||||
rm -rf macOS/x64/SourceGit.app/Contents/MacOS/SourceGit.dsym
|
||||
rm -rf macOS/arm64/SourceGit.app/Contents/MacOS/SourceGit.dsym
|
1
src/BuildWindows.bat
Normal file
1
src/BuildWindows.bat
Normal file
|
@ -0,0 +1 @@
|
|||
dotnet publish -c Release -r win-x64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained
|
|
@ -1,26 +1,24 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Add : Command
|
||||
{
|
||||
public Add(string repo, bool includeUntracked)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = includeUntracked ? "add ." : "add -u .";
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
public Add(string repo, Models.Change change)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class Add : Command {
|
||||
public Add(string repo, List<Models.Change> changes = null) {
|
||||
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,18 +1,11 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Apply : Command
|
||||
{
|
||||
public Apply(string repo, string file, bool ignoreWhitespace, string whitespaceMode, string extra)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class Apply : Command {
|
||||
public Apply(string repo, string file, bool ignoreWhitespace, string whitespaceMode) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "apply ";
|
||||
if (ignoreWhitespace)
|
||||
Args += "--ignore-whitespace ";
|
||||
else
|
||||
Args += $"--whitespace={whitespaceMode} ";
|
||||
if (!string.IsNullOrEmpty(extra))
|
||||
Args += $"{extra} ";
|
||||
if (ignoreWhitespace) Args += "--ignore-whitespace ";
|
||||
else Args += $"--whitespace={whitespaceMode} ";
|
||||
Args += $"\"{file}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Archive : Command
|
||||
{
|
||||
public Archive(string repo, string revision, string saveTo)
|
||||
{
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class Archive : Command {
|
||||
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 Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,60 @@
|
|||
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 class AssumeUnchanged {
|
||||
class ViewCommand : Command {
|
||||
private static readonly Regex REG = new Regex(@"^(\w)\s+(.+)$");
|
||||
|
||||
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.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
if (match.Groups[1].Value == "h") {
|
||||
_outs.Add(match.Groups[2].Value);
|
||||
}
|
||||
}
|
||||
|
||||
private 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 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}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class Blame : Command
|
||||
{
|
||||
[GeneratedRegex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
namespace SourceGit.Commands {
|
||||
public class Blame : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)");
|
||||
private static readonly DateTime UTC_START = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime();
|
||||
|
||||
public Blame(string repo, string file, string revision)
|
||||
{
|
||||
public Blame(string repo, string file, string revision) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"blame -t {revision} -- \"{file}\"";
|
||||
|
@ -19,27 +17,15 @@ namespace SourceGit.Commands
|
|||
_result.File = file;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ParseLine(line);
|
||||
|
||||
if (_result.IsBinary)
|
||||
break;
|
||||
public Models.BlameData Result() {
|
||||
var succ = Exec();
|
||||
if (!succ) {
|
||||
return new Models.BlameData();
|
||||
}
|
||||
|
||||
if (_needUnifyCommitSHA)
|
||||
{
|
||||
foreach (var line in _result.LineInfos)
|
||||
{
|
||||
if (line.CommitSHA.Length > _minSHALen)
|
||||
{
|
||||
if (_needUnifyCommitSHA) {
|
||||
foreach (var line in _result.LineInfos) {
|
||||
if (line.CommitSHA.Length > _minSHALen) {
|
||||
line.CommitSHA = line.CommitSHA.Substring(0, _minSHALen);
|
||||
}
|
||||
}
|
||||
|
@ -49,47 +35,54 @@ namespace SourceGit.Commands
|
|||
return _result;
|
||||
}
|
||||
|
||||
private void ParseLine(string line)
|
||||
{
|
||||
if (line.Contains('\0', StringComparison.Ordinal))
|
||||
{
|
||||
protected override void OnReadline(string line) {
|
||||
if (_result.IsBinary) return;
|
||||
if (string.IsNullOrEmpty(line)) return;
|
||||
|
||||
if (line.IndexOf('\0') >= 0) {
|
||||
_result.IsBinary = true;
|
||||
_result.LineInfos.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
_content.AppendLine(match.Groups[4].Value);
|
||||
|
||||
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);
|
||||
if (commit == _lastSHA) {
|
||||
var info = new Models.BlameLineInfo() {
|
||||
CommitSHA = commit,
|
||||
Author = string.Empty,
|
||||
Time = string.Empty,
|
||||
};
|
||||
|
||||
var info = new Models.BlameLineInfo()
|
||||
{
|
||||
IsFirstInGroup = commit != _lastSHA,
|
||||
CommitSHA = commit,
|
||||
Author = author,
|
||||
Time = when,
|
||||
};
|
||||
_result.LineInfos.Add(info);
|
||||
} else {
|
||||
var author = match.Groups[2].Value;
|
||||
var timestamp = int.Parse(match.Groups[3].Value);
|
||||
var when = UTC_START.AddSeconds(timestamp).ToString("yyyy/MM/dd");
|
||||
|
||||
_result.LineInfos.Add(info);
|
||||
_lastSHA = commit;
|
||||
var blameLine = new Models.BlameLineInfo() {
|
||||
IsFirstInGroup = true,
|
||||
CommitSHA = commit,
|
||||
Author = author,
|
||||
Time = when,
|
||||
};
|
||||
|
||||
if (line[0] == '^')
|
||||
{
|
||||
_lastSHA = commit;
|
||||
_result.LineInfos.Add(blameLine);
|
||||
}
|
||||
|
||||
if (line[0] == '^') {
|
||||
_needUnifyCommitSHA = true;
|
||||
_minSHALen = Math.Min(_minSHALen, commit.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Models.BlameData _result = new Models.BlameData();
|
||||
private readonly StringBuilder _content = new StringBuilder();
|
||||
private readonly string _dateFormat = Models.DateTimeFormat.Active.DateOnly;
|
||||
private Models.BlameData _result = new Models.BlameData();
|
||||
private StringBuilder _content = new StringBuilder();
|
||||
private string _lastSHA = string.Empty;
|
||||
private bool _needUnifyCommitSHA = false;
|
||||
private int _minSHALen = 64;
|
||||
|
|
|
@ -1,82 +1,38 @@
|
|||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Branch
|
||||
{
|
||||
public static string ShowCurrent(string repo)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public static class Branch {
|
||||
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))
|
||||
if (string.IsNullOrEmpty(upstream)) {
|
||||
cmd.Args = $"branch {name} --unset-upstream";
|
||||
else
|
||||
} else {
|
||||
cmd.Args = $"branch {name} -u {upstream}";
|
||||
|
||||
}
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool DeleteLocal(string repo, string name, Models.ICommandLog log)
|
||||
{
|
||||
public static bool Delete(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)
|
||||
{
|
||||
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;
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +1,47 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Checkout : Command
|
||||
{
|
||||
public Checkout(string repo)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class Checkout : Command {
|
||||
public Checkout(string repo) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
}
|
||||
|
||||
public bool Branch(string branch, bool force)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --progress ");
|
||||
if (force)
|
||||
builder.Append("--force ");
|
||||
builder.Append(branch);
|
||||
|
||||
Args = builder.ToString();
|
||||
public bool Branch(string branch, Action<string> onProgress) {
|
||||
Args = $"checkout --progress {branch}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Branch(string branch, string basedOn, bool force, bool allowOverwrite)
|
||||
{
|
||||
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();
|
||||
public bool Branch(string branch, string basedOn, Action<string> onProgress) {
|
||||
Args = $"checkout --progress -b {branch} {basedOn}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Commit(string commitId, bool force)
|
||||
{
|
||||
var option = force ? "--force" : string.Empty;
|
||||
Args = $"checkout {option} --detach --progress {commitId}";
|
||||
public bool File(string file, bool useTheirs) {
|
||||
if (useTheirs) {
|
||||
Args = $"checkout --theirs -- \"{file}\"";
|
||||
} else {
|
||||
Args = $"checkout --ours -- \"{file}\"";
|
||||
}
|
||||
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool UseTheirs(List<string> files)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --theirs --");
|
||||
foreach (var f in files)
|
||||
{
|
||||
public bool FileWithRevision(string file, string revision) {
|
||||
Args = $"checkout {revision} -- \"{file}\"";
|
||||
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("\"");
|
||||
|
@ -59,24 +50,10 @@ namespace SourceGit.Commands
|
|||
return Exec();
|
||||
}
|
||||
|
||||
public bool UseMine(List<string> files)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --ours --");
|
||||
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);
|
||||
}
|
||||
|
||||
public bool FileWithRevision(string file, string revision)
|
||||
{
|
||||
Args = $"checkout --no-overlay {revision} -- \"{file}\"";
|
||||
return Exec();
|
||||
}
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class CherryPick : Command
|
||||
{
|
||||
public CherryPick(string repo, string commits, bool noCommit, bool appendSourceToMessage, string extraParams)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class CherryPick : Command {
|
||||
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,12 +1,26 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Clean : Command
|
||||
{
|
||||
public Clean(string repo)
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class Clean : Command {
|
||||
public Clean(string repo) {
|
||||
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,21 +1,31 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Clone : Command
|
||||
{
|
||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs)
|
||||
{
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class Clone : Command {
|
||||
private Action<string> _notifyProgress;
|
||||
|
||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs, Action<string> ouputHandler) {
|
||||
Context = ctx;
|
||||
WorkingDirectory = path;
|
||||
SSHKey = sshKey;
|
||||
Args = "clone --progress --verbose ";
|
||||
TraitErrorAsOutput = true;
|
||||
|
||||
if (!string.IsNullOrEmpty(extraArgs))
|
||||
Args += $"{extraArgs} ";
|
||||
if (string.IsNullOrEmpty(sshKey)) {
|
||||
Args = "-c credential.helper=manager ";
|
||||
} else {
|
||||
Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
|
||||
}
|
||||
|
||||
Args += "clone --progress --verbose --recurse-submodules ";
|
||||
|
||||
if (!string.IsNullOrEmpty(extraArgs)) Args += $"{extraArgs} ";
|
||||
Args += $"{url} ";
|
||||
if (!string.IsNullOrEmpty(localName)) Args += localName;
|
||||
|
||||
if (!string.IsNullOrEmpty(localName))
|
||||
Args += localName;
|
||||
_notifyProgress = ouputHandler;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line) {
|
||||
_notifyProgress?.Invoke(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,220 +1,147 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class Command
|
||||
{
|
||||
public class ReadToEndResult
|
||||
{
|
||||
public bool IsSuccess { get; set; } = false;
|
||||
public string StdOut { get; set; } = "";
|
||||
public string StdErr { get; set; } = "";
|
||||
}
|
||||
|
||||
public enum EditorType
|
||||
{
|
||||
None,
|
||||
CoreEditor,
|
||||
RebaseEditor,
|
||||
}
|
||||
|
||||
public string Context { get; set; } = string.Empty;
|
||||
public CancellationToken CancellationToken { get; set; } = CancellationToken.None;
|
||||
public string WorkingDirectory { get; set; } = null;
|
||||
public EditorType Editor { get; set; } = EditorType.CoreEditor; // Only used in Exec() mode
|
||||
public string SSHKey { get; set; } = string.Empty;
|
||||
public string Args { get; set; } = string.Empty;
|
||||
public bool RaiseError { get; set; } = true;
|
||||
public Models.ICommandLog Log { get; set; } = null;
|
||||
|
||||
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.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
// Force using this app as SSH askpass program
|
||||
var selfExecFile = Process.GetCurrentProcess().MainModule!.FileName;
|
||||
if (!OperatingSystem.IsLinux())
|
||||
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}'");
|
||||
|
||||
// Force using en_US.UTF-8 locale
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
start.Environment.Add("LANG", "C");
|
||||
start.Environment.Add("LC_ALL", "C");
|
||||
}
|
||||
|
||||
// Force using this app as git editor.
|
||||
switch (Editor)
|
||||
{
|
||||
case EditorType.CoreEditor:
|
||||
start.Arguments += $"-c core.editor=\"\\\"{selfExecFile}\\\" --core-editor\" ";
|
||||
break;
|
||||
case EditorType.RebaseEditor:
|
||||
start.Arguments += $"-c core.editor=\"\\\"{selfExecFile}\\\" --rebase-message-editor\" -c sequence.editor=\"\\\"{selfExecFile}\\\" --rebase-todo-editor\" -c rebase.abbreviateCommands=true ";
|
||||
break;
|
||||
default:
|
||||
start.Arguments += "-c core.editor=true ";
|
||||
break;
|
||||
}
|
||||
|
||||
// Append command args
|
||||
start.Arguments += Args;
|
||||
|
||||
// Working directory
|
||||
if (!string.IsNullOrEmpty(WorkingDirectory))
|
||||
start.WorkingDirectory = WorkingDirectory;
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
private void HandleOutput(string line, List<string> errs)
|
||||
{
|
||||
line ??= string.Empty;
|
||||
Log?.AppendLine(line);
|
||||
|
||||
// Lines to hide in error message.
|
||||
if (line.Length > 0)
|
||||
{
|
||||
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))
|
||||
return;
|
||||
|
||||
if (REG_PROGRESS().IsMatch(line))
|
||||
return;
|
||||
}
|
||||
|
||||
errs.Add(line);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\d+%")]
|
||||
private static partial Regex REG_PROGRESS();
|
||||
}
|
||||
}
|
||||
using Avalonia.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class Command {
|
||||
public class CancelToken {
|
||||
public bool Requested { get; set; } = false;
|
||||
}
|
||||
|
||||
public class ReadToEndResult {
|
||||
public bool IsSuccess { get; set; }
|
||||
public string StdOut { get; set; }
|
||||
public string StdErr { get; set; }
|
||||
}
|
||||
|
||||
public string Context { get; set; } = string.Empty;
|
||||
public CancelToken Cancel { get; set; } = null;
|
||||
public string WorkingDirectory { get; set; } = null;
|
||||
public string Args { get; set; } = string.Empty;
|
||||
public bool RaiseError { get; set; } = true;
|
||||
public bool TraitErrorAsOutput { get; set; } = false;
|
||||
|
||||
public bool Exec() {
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Native.OS.GitExecutableFile;
|
||||
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 errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var isCancelled = false;
|
||||
|
||||
proc.OutputDataReceived += (_, e) => {
|
||||
if (Cancel != null && Cancel.Requested) {
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited) proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Data != null) OnReadline(e.Data);
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (_, e) => {
|
||||
if (Cancel != null && Cancel.Requested) {
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited) proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.Data)) 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 (_progressRegex.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;
|
||||
}
|
||||
|
||||
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.GitExecutableFile;
|
||||
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 (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;
|
||||
}
|
||||
|
||||
protected virtual void OnReadline(string line) { }
|
||||
|
||||
private static readonly Regex _progressRegex = new Regex(@"\d+%");
|
||||
}
|
||||
}
|
|
@ -1,39 +1,16 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Commit : Command
|
||||
{
|
||||
public Commit(string repo, string message, bool signOff, bool amend, bool resetAuthor)
|
||||
{
|
||||
_tmpFile = Path.GetTempFileName();
|
||||
File.WriteAllText(_tmpFile, message);
|
||||
namespace SourceGit.Commands {
|
||||
public class Commit : Command {
|
||||
public Commit(string repo, string message, bool amend, bool allowEmpty = false) {
|
||||
var file = Path.GetTempFileName();
|
||||
File.WriteAllText(file, message);
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"commit --allow-empty --file=\"{_tmpFile}\"";
|
||||
if (signOff)
|
||||
Args += " --signoff";
|
||||
if (amend)
|
||||
Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit";
|
||||
Args = $"commit --file=\"{file}\"";
|
||||
if (amend) 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,88 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class CompareRevisions : Command
|
||||
{
|
||||
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
|
||||
private static partial Regex REG_RENAME_FORMAT();
|
||||
namespace SourceGit.Commands {
|
||||
public class CompareRevisions : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
|
||||
public CompareRevisions(string repo, string start, string end)
|
||||
{
|
||||
public CompareRevisions(string repo, string start, string end) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var based = string.IsNullOrEmpty(start) ? "-R" : start;
|
||||
Args = $"diff --name-status {based} {end}";
|
||||
Args = $"diff --name-status {start} {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));
|
||||
public List<Models.Change> Result() {
|
||||
Exec();
|
||||
_changes.Sort((l, r) => l.Path.CompareTo(r.Path));
|
||||
return _changes;
|
||||
}
|
||||
|
||||
private void ParseLine(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;
|
||||
}
|
||||
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 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
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>();
|
||||
private List<Models.Change> _changes = new List<Models.Change>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Config : Command
|
||||
{
|
||||
public Config(string repository)
|
||||
{
|
||||
if (string.IsNullOrEmpty(repository))
|
||||
{
|
||||
WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
WorkingDirectory = repository;
|
||||
Context = repository;
|
||||
_isLocal = true;
|
||||
}
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class Config : Command {
|
||||
public Config(string repository) {
|
||||
WorkingDirectory = repository;
|
||||
Context = repository;
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> ListAll()
|
||||
{
|
||||
public Dictionary<string, string> ListAll() {
|
||||
Args = "config -l";
|
||||
|
||||
var output = ReadToEnd();
|
||||
var rs = new Dictionary<string, string>();
|
||||
if (output.IsSuccess)
|
||||
{
|
||||
var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var idx = line.IndexOf('=', StringComparison.Ordinal);
|
||||
if (idx != -1)
|
||||
{
|
||||
if (output.IsSuccess) {
|
||||
var lines = output.StdOut.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines) {
|
||||
var idx = line.IndexOf('=');
|
||||
if (idx != -1) {
|
||||
var key = line.Substring(0, idx).Trim();
|
||||
var val = line.Substring(idx + 1).Trim();
|
||||
rs[key] = val;
|
||||
var val = line.Substring(idx+1).Trim();
|
||||
if (rs.ContainsKey(key)) {
|
||||
rs[key] = val;
|
||||
} else {
|
||||
rs.Add(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,24 +33,27 @@ namespace SourceGit.Commands
|
|||
return rs;
|
||||
}
|
||||
|
||||
public string Get(string key)
|
||||
{
|
||||
public string Get(string key) {
|
||||
Args = $"config {key}";
|
||||
return ReadToEnd().StdOut.Trim();
|
||||
}
|
||||
|
||||
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}";
|
||||
else
|
||||
Args = $"config {scope} {key} \"{value}\"";
|
||||
public bool Set(string key, string value, bool allowEmpty = false) {
|
||||
if (!allowEmpty && string.IsNullOrWhiteSpace(value)) {
|
||||
if (string.IsNullOrEmpty(WorkingDirectory)) {
|
||||
Args = $"config --global --unset {key}";
|
||||
} else {
|
||||
Args = $"config --unset {key}";
|
||||
}
|
||||
} else {
|
||||
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,259 +1,127 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class Diff : Command
|
||||
{
|
||||
[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)
|
||||
{
|
||||
_result.TextDiff = new Models.TextDiff()
|
||||
{
|
||||
Repo = repo,
|
||||
Option = opt,
|
||||
};
|
||||
namespace SourceGit.Commands {
|
||||
public class Diff : Command {
|
||||
private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@");
|
||||
private static readonly string PREFIX_LFS = " version https://git-lfs.github.com/spec/";
|
||||
|
||||
public Diff(string repo, Models.DiffOption opt) {
|
||||
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=4 {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);
|
||||
public Models.DiffResult Result() {
|
||||
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
|
||||
{
|
||||
} 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)
|
||||
{
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
protected override void OnReadline(string line) {
|
||||
if (_result.IsBinary) return;
|
||||
|
||||
if (line.StartsWith("old mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.OldMode = line.Substring(9);
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("new mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.NewMode = line.Substring(9);
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("deleted file mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.OldMode = line.Substring(18);
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("new file mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.NewMode = line.Substring(14);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_result.IsLFS)
|
||||
{
|
||||
if (_result.IsLFS) {
|
||||
var ch = line[0];
|
||||
if (ch == '-')
|
||||
{
|
||||
if (line.StartsWith("-oid sha256:", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.Old.Oid = line.Substring(12);
|
||||
if (ch == '-') {
|
||||
line = line.Substring(1);
|
||||
if (line.StartsWith("oid sha256:")) {
|
||||
_result.LFSDiff.Old.Oid = line.Substring(11);
|
||||
} else if (line.StartsWith("size ")) {
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.Substring(5));
|
||||
}
|
||||
else if (line.StartsWith("-size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
|
||||
} else if (ch == '+') {
|
||||
line = line.Substring(1);
|
||||
if (line.StartsWith("oid sha256:")) {
|
||||
_result.LFSDiff.New.Oid = line.Substring(11);
|
||||
} else if (line.StartsWith("size ")) {
|
||||
_result.LFSDiff.New.Size = long.Parse(line.Substring(5));
|
||||
}
|
||||
}
|
||||
else if (ch == '+')
|
||||
{
|
||||
if (line.StartsWith("+oid sha256:", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Oid = line.Substring(12);
|
||||
}
|
||||
else if (line.StartsWith("+size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = long.Parse(line.AsSpan(6));
|
||||
}
|
||||
}
|
||||
else if (line.StartsWith(" size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
|
||||
} else if (line.StartsWith(" size ")) {
|
||||
_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))
|
||||
{
|
||||
_result.IsBinary = true;
|
||||
if (_result.TextDiff.Lines.Count == 0) {
|
||||
var match = REG_INDICATOR.Match(line);
|
||||
if (!match.Success) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (line.Length == 0)
|
||||
{
|
||||
_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, "", ""));
|
||||
} 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;
|
||||
}
|
||||
|
||||
var ch = line[0];
|
||||
if (ch == '-')
|
||||
{
|
||||
if (_oldLine == 1 && _newLine == 0 && line.StartsWith(PREFIX_LFS_DEL, StringComparison.Ordinal))
|
||||
{
|
||||
_result.IsLFS = true;
|
||||
_result.LFSDiff = new Models.LFSDiff();
|
||||
return;
|
||||
}
|
||||
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0);
|
||||
_deleted.Add(_last);
|
||||
if (ch == '-') {
|
||||
_deleted.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), $"{_oldLine}", ""));
|
||||
_oldLine++;
|
||||
}
|
||||
else if (ch == '+')
|
||||
{
|
||||
if (_oldLine == 0 && _newLine == 1 && line.StartsWith(PREFIX_LFS_NEW, StringComparison.Ordinal))
|
||||
{
|
||||
_result.IsLFS = true;
|
||||
_result.LFSDiff = new Models.LFSDiff();
|
||||
return;
|
||||
}
|
||||
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine);
|
||||
_added.Add(_last);
|
||||
} else if (ch == '+') {
|
||||
_added.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), "", $"{_newLine}"));
|
||||
_newLine++;
|
||||
}
|
||||
else if (ch != '\\')
|
||||
{
|
||||
} else if (ch != '\\') {
|
||||
ProcessInlineHighlights();
|
||||
var match = REG_INDICATOR().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var match = REG_INDICATOR.Match(line);
|
||||
if (match.Success) {
|
||||
_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);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_oldLine == 1 && _newLine == 1 && line.StartsWith(PREFIX_LFS_MODIFY, StringComparison.Ordinal))
|
||||
{
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, "", ""));
|
||||
} else {
|
||||
if (line.StartsWith(PREFIX_LFS)) {
|
||||
_result.IsLFS = true;
|
||||
_result.LFSDiff = new Models.LFSDiff();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessInlineHighlights()
|
||||
{
|
||||
if (_deleted.Count > 0)
|
||||
{
|
||||
if (_added.Count == _deleted.Count)
|
||||
{
|
||||
for (int i = _added.Count - 1; i >= 0; i--)
|
||||
{
|
||||
private void ProcessInlineHighlights() {
|
||||
if (_deleted.Count > 0) {
|
||||
if (_added.Count == _deleted.Count) {
|
||||
for (int i = _added.Count - 1; i >= 0; i--) {
|
||||
var left = _deleted[i];
|
||||
var right = _added[i];
|
||||
|
||||
if (left.Content.Length > 1024 || right.Content.Length > 1024)
|
||||
continue;
|
||||
if (left.Content.Length > 1024 || right.Content.Length > 1024) continue;
|
||||
|
||||
var chunks = Models.TextInlineChange.Compare(left.Content, right.Content);
|
||||
if (chunks.Count > 4)
|
||||
continue;
|
||||
if (chunks.Count > 4) continue;
|
||||
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
if (chunk.DeletedCount > 0)
|
||||
{
|
||||
foreach (var chunk in chunks) {
|
||||
if (chunk.DeletedCount > 0) {
|
||||
left.Highlights.Add(new Models.TextInlineRange(chunk.DeletedStart, chunk.DeletedCount));
|
||||
}
|
||||
|
||||
if (chunk.AddedCount > 0)
|
||||
{
|
||||
if (chunk.AddedCount > 0) {
|
||||
right.Highlights.Add(new Models.TextInlineRange(chunk.AddedStart, chunk.AddedCount));
|
||||
}
|
||||
}
|
||||
|
@ -264,17 +132,15 @@ namespace SourceGit.Commands
|
|||
_deleted.Clear();
|
||||
}
|
||||
|
||||
if (_added.Count > 0)
|
||||
{
|
||||
if (_added.Count > 0) {
|
||||
_result.TextDiff.Lines.AddRange(_added);
|
||||
_added.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
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 Models.DiffResult _result = new Models.DiffResult() { TextDiff = new Models.TextDiff() };
|
||||
private List<Models.TextDiffLine> _deleted = new List<Models.TextDiffLine>();
|
||||
private List<Models.TextDiffLine> _added = new List<Models.TextDiffLine>();
|
||||
private int _oldLine = 0;
|
||||
private int _newLine = 0;
|
||||
}
|
||||
|
|
|
@ -1,95 +1,33 @@
|
|||
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)
|
||||
{
|
||||
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();
|
||||
namespace SourceGit.Commands {
|
||||
public static class Discard {
|
||||
public static void All(string repo) {
|
||||
new Reset(repo, "HEAD", "--hard").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)
|
||||
{
|
||||
var restores = new List<string>();
|
||||
public static void Changes(string repo, List<Models.Change> changes) {
|
||||
var needClean = new List<string>();
|
||||
var needCheckout = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
foreach (var c in changes) {
|
||||
if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added) {
|
||||
needClean.Add(c.Path);
|
||||
} else {
|
||||
needCheckout.Add(c.Path);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
|
||||
});
|
||||
|
||||
for (int i = 0; i < needClean.Count; i += 10) {
|
||||
var count = Math.Min(10, needClean.Count - i);
|
||||
new Clean(repo, needClean.GetRange(i, count)).Exec();
|
||||
}
|
||||
|
||||
if (restores.Count > 0)
|
||||
{
|
||||
var pathSpecFile = Path.GetTempFileName();
|
||||
File.WriteAllLines(pathSpecFile, restores);
|
||||
new Restore(repo, pathSpecFile, false) { Log = log }.Exec();
|
||||
File.Delete(pathSpecFile);
|
||||
for (int i = 0; i < needCheckout.Count; i += 10) {
|
||||
var count = Math.Min(10, needCheckout.Count - i);
|
||||
new Checkout(repo).Files(needCheckout.GetRange(i, count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,123 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Fetch : Command
|
||||
{
|
||||
public Fetch(string repo, string remote, bool noTags, bool force)
|
||||
{
|
||||
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 prune, Action<string> outputHandler) {
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "fetch --progress --verbose ";
|
||||
TraitErrorAsOutput = true;
|
||||
|
||||
if (noTags)
|
||||
Args += "--no-tags ";
|
||||
else
|
||||
Args += "--tags ";
|
||||
|
||||
if (force)
|
||||
Args += "--force ";
|
||||
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
if (!string.IsNullOrEmpty(sshKey)) {
|
||||
Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
|
||||
} else {
|
||||
Args = "-c credential.helper=manager ";
|
||||
}
|
||||
|
||||
Args += "fetch --progress --verbose ";
|
||||
if (prune) Args += "--prune ";
|
||||
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;
|
||||
|
||||
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
if (!string.IsNullOrEmpty(sshKey)) {
|
||||
Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
|
||||
} else {
|
||||
Args = "-c credential.helper=manager ";
|
||||
}
|
||||
|
||||
Args += $"fetch --progress --verbose {remote} {remoteBranch}:{localBranch}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line) {
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
|
||||
public class AutoFetch {
|
||||
public static bool IsEnabled {
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
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.AddSeconds(_fetchInterval);
|
||||
}
|
||||
|
||||
Thread.Sleep(2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddRepository(string repo) {
|
||||
var job = new Job {
|
||||
Cmd = new Fetch(repo, "--all", true, null) { RaiseError = false },
|
||||
NextRunTimepoint = DateTime.Now.AddSeconds(_fetchInterval),
|
||||
};
|
||||
|
||||
lock (_lock) {
|
||||
if (_jobs.ContainsKey(repo)) {
|
||||
_jobs[repo] = job;
|
||||
} else {
|
||||
_jobs.Add(repo, job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveRepository(string repo) {
|
||||
lock (_lock) {
|
||||
_jobs.Remove(repo);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MarkFetched(string repo) {
|
||||
lock (_lock) {
|
||||
if (_jobs.ContainsKey(repo)) {
|
||||
_jobs[repo].NextRunTimepoint = DateTime.Now.AddSeconds(_fetchInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, Job> _jobs = new Dictionary<string, Job>();
|
||||
private static object _lock = new object();
|
||||
private static double _fetchInterval = 10 * 60;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class FormatPatch : Command
|
||||
{
|
||||
public FormatPatch(string repo, string commit, string saveTo)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class FormatPatch : Command {
|
||||
public FormatPatch(string repo, string commit, string saveTo) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Editor = EditorType.None;
|
||||
Args = $"format-patch {commit} -1 --output=\"{saveTo}\"";
|
||||
Args = $"format-patch {commit} -1 -o \"{saveTo}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class GC : Command
|
||||
{
|
||||
public GC(string repo)
|
||||
{
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class GC : Command {
|
||||
public GC(string repo, Action<string> outputHandler) {
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "gc --prune=now";
|
||||
TraitErrorAsOutput = true;
|
||||
Args = "gc";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line) {
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private 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,92 +1,72 @@
|
|||
using System.Text;
|
||||
using Avalonia.Threading;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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)
|
||||
{
|
||||
var config = new Config(repo);
|
||||
config.Set("gitflow.branch.master", master);
|
||||
config.Set("gitflow.branch.develop", develop);
|
||||
config.Set("gitflow.prefix.feature", feature);
|
||||
config.Set("gitflow.prefix.bugfix", "bugfix/");
|
||||
config.Set("gitflow.prefix.release", release);
|
||||
config.Set("gitflow.prefix.hotfix", hotfix);
|
||||
config.Set("gitflow.prefix.support", "support/");
|
||||
config.Set("gitflow.prefix.versiontag", version, true);
|
||||
|
||||
var init = new Command();
|
||||
init.WorkingDirectory = repo;
|
||||
init.Context = repo;
|
||||
init.Args = "flow init -d";
|
||||
init.Log = log;
|
||||
return init.Exec();
|
||||
namespace SourceGit.Commands {
|
||||
public class GitFlow : Command {
|
||||
public GitFlow(string repo) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
}
|
||||
|
||||
public static bool Start(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log)
|
||||
{
|
||||
var start = new Command();
|
||||
start.WorkingDirectory = repo;
|
||||
start.Context = repo;
|
||||
public bool Init(List<Models.Branch> branches, string master, string develop, string feature, string release, string hotfix, string version) {
|
||||
var current = branches.Find(x => x.IsCurrent);
|
||||
|
||||
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;
|
||||
}
|
||||
var masterBranch = branches.Find(x => x.Name == master);
|
||||
if (masterBranch == null && current != null) Branch.Create(WorkingDirectory, master, current.Head);
|
||||
|
||||
start.Log = log;
|
||||
return start.Exec();
|
||||
var devBranch = branches.Find(x => x.Name == develop);
|
||||
if (devBranch == null && current != null) Branch.Create(WorkingDirectory, develop, current.Head);
|
||||
|
||||
var cmd = new Config(WorkingDirectory);
|
||||
cmd.Set("gitflow.branch.master", master);
|
||||
cmd.Set("gitflow.branch.develop", develop);
|
||||
cmd.Set("gitflow.prefix.feature", feature);
|
||||
cmd.Set("gitflow.prefix.bugfix", "bugfix/");
|
||||
cmd.Set("gitflow.prefix.release", release);
|
||||
cmd.Set("gitflow.prefix.hotfix", hotfix);
|
||||
cmd.Set("gitflow.prefix.support", "support/");
|
||||
cmd.Set("gitflow.prefix.versiontag", version, true);
|
||||
|
||||
Args = "flow init -d";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public static bool Finish(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("flow ");
|
||||
|
||||
switch (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;
|
||||
public bool Start(Models.GitFlowBranchType type, string name) {
|
||||
switch (type) {
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
Args = $"flow feature start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
Args = $"flow release start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
Args = $"flow hotfix start {name}";
|
||||
break;
|
||||
default:
|
||||
App.RaiseException(Context, "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);
|
||||
return Exec();
|
||||
}
|
||||
|
||||
var finish = new Command();
|
||||
finish.WorkingDirectory = repo;
|
||||
finish.Context = repo;
|
||||
finish.Args = builder.ToString();
|
||||
finish.Log = log;
|
||||
return finish.Exec();
|
||||
public bool Finish(Models.GitFlowBranchType type, string name, bool keepBranch) {
|
||||
var option = keepBranch ? "-k" : string.Empty;
|
||||
switch (type) {
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
Args = $"flow feature finish {option} {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
Args = $"flow release finish {option} {name} -m \"RELEASE_DONE\"";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
Args = $"flow hotfix finish {option} {name} -m \"HOTFIX_DONE\"";
|
||||
break;
|
||||
default:
|
||||
App.RaiseException(Context, "Bad branch type!!!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return Exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class GitIgnore
|
||||
{
|
||||
public static void Add(string repo, string pattern)
|
||||
{
|
||||
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,9 +1,6 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Init : Command
|
||||
{
|
||||
public Init(string ctx, string dir)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class Init : Command {
|
||||
public Init(string ctx, string dir) {
|
||||
Context = ctx;
|
||||
WorkingDirectory = dir;
|
||||
Args = "init -q";
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +1,18 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class IsBinary : Command
|
||||
{
|
||||
[GeneratedRegex(@"^\-\s+\-\s+.*$")]
|
||||
private static partial Regex REG_TEST();
|
||||
namespace SourceGit.Commands {
|
||||
public class IsBinary : Command {
|
||||
private static readonly Regex REG_TEST = new Regex(@"^\-\s+\-\s+.*$");
|
||||
|
||||
public IsBinary(string repo, string commit, string path)
|
||||
{
|
||||
public IsBinary(string repo, string commit, string path) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff {Models.Commit.EmptyTreeSHA1} {commit} --numstat -- \"{path}\"";
|
||||
Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\"";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
return REG_TEST().IsMatch(ReadToEnd().StdOut);
|
||||
public bool Result() {
|
||||
return REG_TEST.IsMatch(ReadToEnd().StdOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class IsConflictResolved : Command
|
||||
{
|
||||
public IsConflictResolved(string repo, Models.Change change)
|
||||
{
|
||||
var opt = new Models.DiffOption(change, true);
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff -a --ignore-cr-at-eol --check {opt}";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
return ReadToEnd().IsSuccess;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +1,13 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class IsLFSFiltered : Command
|
||||
{
|
||||
public IsLFSFiltered(string repo, string path)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class IsLFSFiltered : Command {
|
||||
public IsLFSFiltered(string repo, string path) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"check-attr -z filter \"{path}\"";
|
||||
Args = $"check-attr -a -z \"{path}\"";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public IsLFSFiltered(string repo, string sha, string path)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"check-attr --source {sha} -z filter \"{path}\"";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
public bool Result() {
|
||||
var rs = ReadToEnd();
|
||||
return rs.IsSuccess && rs.StdOut.Contains("filter\0lfs");
|
||||
}
|
||||
|
|
|
@ -1,115 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class LFS
|
||||
{
|
||||
[GeneratedRegex(@"^(.+)\s+([\w.]+)\s+\w+:(\d+)$")]
|
||||
private static partial Regex REG_LOCK();
|
||||
|
||||
private class SubCmd : Command
|
||||
{
|
||||
public SubCmd(string repo, string args, Models.ICommandLog log)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class LFS {
|
||||
class PruneCmd : Command {
|
||||
public PruneCmd(string repo, Action<string> onProgress) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = args;
|
||||
Log = log;
|
||||
Args = "lfs prune";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line) {
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
|
||||
public LFS(string repo)
|
||||
{
|
||||
public LFS(string repo) {
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public bool IsEnabled()
|
||||
{
|
||||
public bool IsEnabled() {
|
||||
var path = Path.Combine(_repo, ".git", "hooks", "pre-push");
|
||||
if (!File.Exists(path))
|
||||
return false;
|
||||
if (!File.Exists(path)) return false;
|
||||
|
||||
var content = File.ReadAllText(path);
|
||||
return content.Contains("git lfs pre-push");
|
||||
}
|
||||
|
||||
public bool Install(Models.ICommandLog log)
|
||||
{
|
||||
return new SubCmd(_repo, "lfs install --local", log).Exec();
|
||||
public void Prune(Action<string> outputHandler) {
|
||||
new PruneCmd(_repo, outputHandler).Exec();
|
||||
}
|
||||
|
||||
public bool Track(string pattern, bool isFilenameMode, Models.ICommandLog log)
|
||||
{
|
||||
var opt = isFilenameMode ? "--filename" : "";
|
||||
return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", log).Exec();
|
||||
}
|
||||
|
||||
public void Fetch(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs fetch {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Pull(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs pull {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Push(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs push {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Prune(Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, "lfs prune", log).Exec();
|
||||
}
|
||||
|
||||
public List<Models.LFSLock> Locks(string remote)
|
||||
{
|
||||
var locks = new List<Models.LFSLock>();
|
||||
var cmd = new SubCmd(_repo, $"lfs locks --remote={remote}", null);
|
||||
var rs = cmd.ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_LOCK().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
locks.Add(new Models.LFSLock()
|
||||
{
|
||||
File = match.Groups[1].Value,
|
||||
User = match.Groups[2].Value,
|
||||
ID = long.Parse(match.Groups[3].Value),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return locks;
|
||||
}
|
||||
|
||||
public bool Lock(string remote, string file, Models.ICommandLog log)
|
||||
{
|
||||
return new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", log).Exec();
|
||||
}
|
||||
|
||||
public bool Unlock(string remote, string file, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var opt = force ? "-f" : "";
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", log).Exec();
|
||||
}
|
||||
|
||||
public bool Unlock(string remote, long id, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var opt = force ? "-f" : "";
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", log).Exec();
|
||||
}
|
||||
|
||||
private readonly string _repo;
|
||||
private string _repo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Merge : Command
|
||||
{
|
||||
public Merge(string repo, string source, string mode)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class Merge : Command {
|
||||
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)
|
||||
{
|
||||
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();
|
||||
protected override void OnReadline(string line) {
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,40 @@
|
|||
using System.IO;
|
||||
|
||||
using Avalonia.Threading;
|
||||
namespace SourceGit.Commands {
|
||||
public static class MergeTool {
|
||||
public static bool OpenForMerge(string repo, string tool, string mergeCmd, string file) {
|
||||
if (string.IsNullOrWhiteSpace(tool) || string.IsNullOrWhiteSpace(mergeCmd)) {
|
||||
App.RaiseException(repo, "Invalid external merge tool settings!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!File.Exists(tool)) {
|
||||
App.RaiseException(repo, $"Can NOT found external merge tool in '{tool}'!");
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class MergeTool
|
||||
{
|
||||
public static bool OpenForMerge(string repo, int toolType, string toolPath, string file)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
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}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
if (!File.Exists(toolPath))
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(repo, $"Can NOT find external merge tool in '{toolPath}'!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType);
|
||||
if (supported == null)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(repo, "Invalid merge tool in preference setting!"));
|
||||
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.RaiseError = false;
|
||||
cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{tool}\\\" {mergeCmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit \"{file}\"";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool OpenForDiff(string repo, int toolType, string toolPath, Models.DiffOption option)
|
||||
{
|
||||
public static bool OpenForDiff(string repo, string tool, string diffCmd, Models.DiffOption option) {
|
||||
if (string.IsNullOrWhiteSpace(tool) || string.IsNullOrWhiteSpace(diffCmd)) {
|
||||
App.RaiseException(repo, "Invalid external merge tool settings!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!File.Exists(tool)) {
|
||||
App.RaiseException(repo, $"Can NOT found external merge tool in '{tool}'!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.RaiseError = true;
|
||||
|
||||
if (toolType == 0)
|
||||
{
|
||||
cmd.Args = $"difftool -g --no-prompt {option}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
if (!File.Exists(toolPath))
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT find external diff tool in '{toolPath}'!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType);
|
||||
if (supported == null)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(repo, "Invalid merge tool in preference setting!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.DiffCmd}\" difftool --tool=sourcegit --no-prompt {option}";
|
||||
cmd.RaiseError = false;
|
||||
cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{tool}\\\" {diffCmd}\" difftool --tool=sourcegit --no-prompt {option}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Pull : Command
|
||||
{
|
||||
public Pull(string repo, string remote, string branch, bool useRebase)
|
||||
{
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class Pull : Command {
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, Action<string> outputHandler) {
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "pull --verbose --progress ";
|
||||
TraitErrorAsOutput = true;
|
||||
|
||||
if (useRebase)
|
||||
Args += "--rebase=true ";
|
||||
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
if (!string.IsNullOrEmpty(sshKey)) {
|
||||
Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
|
||||
} else {
|
||||
Args = "-c credential.helper=manager ";
|
||||
}
|
||||
|
||||
Args += "pull --verbose --progress --tags ";
|
||||
if (useRebase) Args += "--rebase ";
|
||||
Args += $"{remote} {branch}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line) {
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,70 @@
|
|||
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)
|
||||
{
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class Push : Command {
|
||||
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool force, bool track, Action<string> onProgress) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "push --progress --verbose ";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
|
||||
if (withTags)
|
||||
Args += "--tags ";
|
||||
if (checkSubmodules)
|
||||
Args += "--recurse-submodules=check ";
|
||||
if (track)
|
||||
Args += "-u ";
|
||||
if (force)
|
||||
Args += "--force-with-lease ";
|
||||
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
if (!string.IsNullOrEmpty(sshKey)) {
|
||||
Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
|
||||
} else {
|
||||
Args = "-c credential.helper=manager ";
|
||||
}
|
||||
|
||||
Args += "push --progress --verbose ";
|
||||
|
||||
if (withTags) Args += "--tags ";
|
||||
if (track) Args += "-u ";
|
||||
if (force) Args += "--force-with-lease ";
|
||||
|
||||
Args += $"{remote} {local}:{remoteBranch}";
|
||||
}
|
||||
|
||||
public Push(string repo, string remote, string refname, bool isDelete)
|
||||
{
|
||||
/// <summary>
|
||||
/// Only used to delete a remote branch!!!!!!
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="remote"></param>
|
||||
/// <param name="branch"></param>
|
||||
public Push(string repo, string remote, string branch) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "push ";
|
||||
TraitErrorAsOutput = true;
|
||||
|
||||
if (isDelete)
|
||||
Args += "--delete ";
|
||||
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
if (!string.IsNullOrEmpty(sshKey)) {
|
||||
Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
|
||||
} else {
|
||||
Args = "-c credential.helper=manager ";
|
||||
}
|
||||
|
||||
Args += $"{remote} {refname}";
|
||||
Args += $"push {remote} --delete {branch}";
|
||||
}
|
||||
|
||||
public Push(string repo, string remote, string tag, bool isDelete) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var sshKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
if (!string.IsNullOrEmpty(sshKey)) {
|
||||
Args = $"-c core.sshCommand=\"ssh -i '{sshKey}'\" ";
|
||||
} else {
|
||||
Args = "-c credential.helper=manager ";
|
||||
}
|
||||
|
||||
Args += "push ";
|
||||
if (isDelete) Args += "--delete ";
|
||||
Args += $"{remote} refs/tags/{tag}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line) {
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryBranches : Command
|
||||
{
|
||||
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";
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryBranches : Command {
|
||||
private static readonly string PREFIX_LOCAL = "refs/heads/";
|
||||
private static readonly string PREFIX_REMOTE = "refs/remotes/";
|
||||
private static readonly Regex REG_AHEAD = new Regex(@"ahead (\d+)");
|
||||
private static readonly Regex REG_BEHIND = new Regex(@"behind (\d+)");
|
||||
|
||||
public QueryBranches(string repo)
|
||||
{
|
||||
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:track)\"";
|
||||
}
|
||||
|
||||
public List<Models.Branch> Result(out int localBranchesCount)
|
||||
{
|
||||
localBranchesCount = 0;
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return branches;
|
||||
|
||||
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;
|
||||
public List<Models.Branch> Result() {
|
||||
Exec();
|
||||
return _branches;
|
||||
}
|
||||
|
||||
private Models.Branch ParseLine(string line)
|
||||
{
|
||||
var parts = line.Split('\0');
|
||||
if (parts.Length != 6)
|
||||
return null;
|
||||
protected override void OnReadline(string line) {
|
||||
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;
|
||||
if (refName.EndsWith("/HEAD")) return;
|
||||
|
||||
branch.IsDetachedHead = refName.StartsWith(PREFIX_DETACHED_AT, StringComparison.Ordinal) ||
|
||||
refName.StartsWith(PREFIX_DETACHED_FROM, StringComparison.Ordinal);
|
||||
|
||||
if (refName.StartsWith(PREFIX_LOCAL, StringComparison.Ordinal))
|
||||
{
|
||||
if (refName.StartsWith(PREFIX_LOCAL, StringComparison.Ordinal)) {
|
||||
branch.Name = refName.Substring(PREFIX_LOCAL.Length);
|
||||
branch.IsLocal = true;
|
||||
}
|
||||
else if (refName.StartsWith(PREFIX_REMOTE, StringComparison.Ordinal))
|
||||
{
|
||||
} else if (refName.StartsWith(PREFIX_REMOTE, StringComparison.Ordinal)) {
|
||||
var name = refName.Substring(PREFIX_REMOTE.Length);
|
||||
var shortNameIdx = name.IndexOf('/', StringComparison.Ordinal);
|
||||
if (shortNameIdx < 0)
|
||||
return null;
|
||||
var shortNameIdx = name.IndexOf('/');
|
||||
if (shortNameIdx < 0) return;
|
||||
|
||||
branch.Remote = name.Substring(0, shortNameIdx);
|
||||
branch.Name = name.Substring(branch.Remote.Length + 1);
|
||||
branch.IsLocal = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
branch.Name = refName;
|
||||
branch.IsLocal = true;
|
||||
}
|
||||
|
||||
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];
|
||||
branch.UpstreamTrackStatus = ParseTrackStatus(parts[4]);
|
||||
|
||||
if (!branch.IsLocal ||
|
||||
string.IsNullOrEmpty(branch.Upstream) ||
|
||||
string.IsNullOrEmpty(parts[5]) ||
|
||||
parts[5].Equals("=", StringComparison.Ordinal))
|
||||
branch.TrackStatus = new Models.BranchTrackStatus();
|
||||
|
||||
return branch;
|
||||
_branches.Add(branch);
|
||||
}
|
||||
|
||||
private string ParseTrackStatus(string data) {
|
||||
if (string.IsNullOrEmpty(data)) return string.Empty;
|
||||
|
||||
string track = string.Empty;
|
||||
|
||||
var ahead = REG_AHEAD.Match(data);
|
||||
if (ahead.Success) {
|
||||
track += ahead.Groups[1].Value + "↑ ";
|
||||
}
|
||||
|
||||
var behind = REG_BEHIND.Match(data);
|
||||
if (behind.Success) {
|
||||
track += behind.Groups[1].Value + "↓";
|
||||
}
|
||||
|
||||
return track.Trim();
|
||||
}
|
||||
|
||||
private List<Models.Branch> _branches = new List<Models.Branch>();
|
||||
}
|
||||
}
|
||||
|
|
38
src/Commands/QueryCommitChanges.cs
Normal file
38
src/Commands/QueryCommitChanges.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryCommitChanges : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
|
||||
public QueryCommitChanges(string repo, string commitSHA) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --name-status {commitSHA}";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result() {
|
||||
Exec();
|
||||
_changes.Sort((l, r) => l.Path.CompareTo(r.Path));
|
||||
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 List<Models.Change> _changes = new List<Models.Change>();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCommitFullMessage : Command
|
||||
{
|
||||
public QueryCommitFullMessage(string repo, string sha)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --no-show-signature --format=%B -s {sha}";
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
return rs.StdOut.TrimEnd();
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,154 +1,152 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCommits : Command
|
||||
{
|
||||
public QueryCommits(string repo, string limits, bool needFindHead = true)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryCommits : Command {
|
||||
private static readonly string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
|
||||
private static readonly string GPGSIG_END = " -----END PGP SIGNATURE-----";
|
||||
|
||||
private List<Models.Commit> commits = new List<Models.Commit>();
|
||||
private Models.Commit current = null;
|
||||
private bool isSkipingGpgsig = false;
|
||||
private bool isHeadFounded = false;
|
||||
private bool findFirstMerged = true;
|
||||
|
||||
public QueryCommits(string repo, string limits, bool needFindHead = true) {
|
||||
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}";
|
||||
_findFirstMerged = needFindHead;
|
||||
Args = "log --date-order --decorate=full --pretty=raw " + limits;
|
||||
findFirstMerged = needFindHead;
|
||||
}
|
||||
|
||||
public QueryCommits(string repo, string filter, Models.CommitSearchMethod method, bool onlyCurrentBranch)
|
||||
{
|
||||
string search = onlyCurrentBranch ? string.Empty : "--branches --remotes ";
|
||||
public List<Models.Commit> Result() {
|
||||
Exec();
|
||||
|
||||
if (method == Models.CommitSearchMethod.ByAuthor)
|
||||
{
|
||||
search += $"-i --author=\"{filter}\"";
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByCommitter)
|
||||
{
|
||||
search += $"-i --committer=\"{filter}\"";
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByMessage)
|
||||
{
|
||||
var argsBuilder = new StringBuilder();
|
||||
argsBuilder.Append(search);
|
||||
|
||||
var words = filter.Split([' ', '\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}\"";
|
||||
if (current != null) {
|
||||
current.Message = current.Message.Trim();
|
||||
commits.Add(current);
|
||||
}
|
||||
|
||||
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}";
|
||||
_findFirstMerged = false;
|
||||
}
|
||||
|
||||
public List<Models.Commit> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return _commits;
|
||||
|
||||
var nextPartIdx = 0;
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\n', start);
|
||||
while (end > 0)
|
||||
{
|
||||
var line = rs.StdOut.Substring(start, end - start);
|
||||
switch (nextPartIdx)
|
||||
{
|
||||
case 0:
|
||||
_current = new Models.Commit() { SHA = line };
|
||||
_commits.Add(_current);
|
||||
break;
|
||||
case 1:
|
||||
ParseParent(line);
|
||||
break;
|
||||
case 2:
|
||||
_current.ParseDecorators(line);
|
||||
if (_current.IsMerged && !_isHeadFounded)
|
||||
_isHeadFounded = true;
|
||||
break;
|
||||
case 3:
|
||||
_current.Author = Models.User.FindOrAdd(line);
|
||||
break;
|
||||
case 4:
|
||||
_current.AuthorTime = ulong.Parse(line);
|
||||
break;
|
||||
case 5:
|
||||
_current.Committer = Models.User.FindOrAdd(line);
|
||||
break;
|
||||
case 6:
|
||||
_current.CommitterTime = ulong.Parse(line);
|
||||
break;
|
||||
case 7:
|
||||
_current.Subject = line;
|
||||
nextPartIdx = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
nextPartIdx++;
|
||||
|
||||
start = end + 1;
|
||||
end = rs.StdOut.IndexOf('\n', start);
|
||||
}
|
||||
|
||||
if (start < rs.StdOut.Length)
|
||||
_current.Subject = rs.StdOut.Substring(start);
|
||||
|
||||
if (_findFirstMerged && !_isHeadFounded && _commits.Count > 0)
|
||||
if (findFirstMerged && !isHeadFounded && commits.Count > 0) {
|
||||
MarkFirstMerged();
|
||||
}
|
||||
|
||||
return _commits;
|
||||
return commits;
|
||||
}
|
||||
|
||||
private void ParseParent(string data)
|
||||
{
|
||||
if (data.Length < 8)
|
||||
protected override void OnReadline(string line) {
|
||||
if (isSkipingGpgsig) {
|
||||
if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal)) isSkipingGpgsig = false;
|
||||
return;
|
||||
} else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal)) {
|
||||
isSkipingGpgsig = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
if (line.StartsWith("commit ", StringComparison.Ordinal)) {
|
||||
if (current != null) {
|
||||
current.Message = current.Message.Trim();
|
||||
commits.Add(current);
|
||||
}
|
||||
|
||||
current = new Models.Commit();
|
||||
line = line.Substring(7);
|
||||
|
||||
var decoratorStart = line.IndexOf('(');
|
||||
if (decoratorStart < 0) {
|
||||
current.SHA = line.Trim();
|
||||
} else {
|
||||
current.SHA = line.Substring(0, decoratorStart).Trim();
|
||||
current.IsMerged = ParseDecorators(current.Decorators, line.Substring(decoratorStart + 1));
|
||||
if (!isHeadFounded) isHeadFounded = current.IsMerged;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (current == null) return;
|
||||
|
||||
if (line.StartsWith("tree ", StringComparison.Ordinal)) {
|
||||
return;
|
||||
} else if (line.StartsWith("parent ", StringComparison.Ordinal)) {
|
||||
current.Parents.Add(line.Substring("parent ".Length));
|
||||
} else if (line.StartsWith("author ", StringComparison.Ordinal)) {
|
||||
Models.User user = Models.User.Invalid;
|
||||
ulong time = 0;
|
||||
Models.Commit.ParseUserAndTime(line.Substring(7), ref user, ref time);
|
||||
current.Author = user;
|
||||
current.AuthorTime = time;
|
||||
} else if (line.StartsWith("committer ", StringComparison.Ordinal)) {
|
||||
Models.User user = Models.User.Invalid;
|
||||
ulong time = 0;
|
||||
Models.Commit.ParseUserAndTime(line.Substring(10), ref user, ref time);
|
||||
current.Committer = user;
|
||||
current.CommitterTime = time;
|
||||
} else if (string.IsNullOrEmpty(current.Subject)) {
|
||||
current.Subject = line.Trim();
|
||||
} else {
|
||||
current.Message += (line.Trim() + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
private void MarkFirstMerged()
|
||||
{
|
||||
Args = $"log --since=\"{_commits[^1].CommitterTimeStr}\" --format=\"%H\"";
|
||||
private bool ParseDecorators(List<Models.Decorator> decorators, string data) {
|
||||
bool isHeadOfCurrent = false;
|
||||
|
||||
var subs = data.Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var sub in subs) {
|
||||
var d = sub.Trim();
|
||||
if (d.StartsWith("tag: refs/tags/", StringComparison.Ordinal)) {
|
||||
decorators.Add(new Models.Decorator() {
|
||||
Type = Models.DecoratorType.Tag,
|
||||
Name = d.Substring(15).Trim(),
|
||||
});
|
||||
} else if (d.EndsWith("/HEAD", StringComparison.Ordinal)) {
|
||||
continue;
|
||||
} else if (d.StartsWith("HEAD -> refs/heads/", StringComparison.Ordinal)) {
|
||||
isHeadOfCurrent = true;
|
||||
decorators.Add(new Models.Decorator() {
|
||||
Type = Models.DecoratorType.CurrentBranchHead,
|
||||
Name = d.Substring(19).Trim(),
|
||||
});
|
||||
} else if (d.StartsWith("refs/heads/", StringComparison.Ordinal)) {
|
||||
decorators.Add(new Models.Decorator() {
|
||||
Type = Models.DecoratorType.LocalBranchHead,
|
||||
Name = d.Substring(11).Trim(),
|
||||
});
|
||||
} else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal)) {
|
||||
decorators.Add(new Models.Decorator() {
|
||||
Type = Models.DecoratorType.RemoteBranchHead,
|
||||
Name = d.Substring(13).Trim(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
decorators.Sort((l, r) => {
|
||||
if (l.Type != r.Type) {
|
||||
return (int)l.Type - (int)r.Type;
|
||||
} else {
|
||||
return l.Name.CompareTo(r.Name);
|
||||
}
|
||||
});
|
||||
|
||||
return isHeadOfCurrent;
|
||||
}
|
||||
|
||||
private void MarkFirstMerged() {
|
||||
Args = $"log --since=\"{commits[commits.Count - 1].CommitterTimeStr}\" --format=\"%H\"";
|
||||
|
||||
var rs = ReadToEnd();
|
||||
var shas = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (shas.Length == 0)
|
||||
return;
|
||||
var shas = rs.StdOut.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (shas.Length == 0) return;
|
||||
|
||||
var set = new HashSet<string>();
|
||||
foreach (var sha in shas)
|
||||
set.Add(sha);
|
||||
foreach (var sha in shas) set.Add(sha);
|
||||
|
||||
foreach (var c in _commits)
|
||||
{
|
||||
if (set.Contains(c.SHA))
|
||||
{
|
||||
foreach (var c in commits) {
|
||||
if (set.Contains(c.SHA)) {
|
||||
c.IsMerged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Models.Commit> _commits = new List<Models.Commit>();
|
||||
private Models.Commit _current = null;
|
||||
private bool _findFirstMerged = false;
|
||||
private bool _isHeadFounded = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCommitsForInteractiveRebase : Command
|
||||
{
|
||||
public QueryCommitsForInteractiveRebase(string repo, string on)
|
||||
{
|
||||
_boundary = $"----- BOUNDARY OF COMMIT {Guid.NewGuid()} -----";
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log --date-order --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD";
|
||||
}
|
||||
|
||||
public List<Models.InteractiveCommit> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return _commits;
|
||||
|
||||
var nextPartIdx = 0;
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\n', start);
|
||||
while (end > 0)
|
||||
{
|
||||
var line = rs.StdOut.Substring(start, end - start);
|
||||
switch (nextPartIdx)
|
||||
{
|
||||
case 0:
|
||||
_current = new Models.InteractiveCommit();
|
||||
_current.Commit.SHA = line;
|
||||
_commits.Add(_current);
|
||||
break;
|
||||
case 1:
|
||||
ParseParent(line);
|
||||
break;
|
||||
case 2:
|
||||
_current.Commit.ParseDecorators(line);
|
||||
break;
|
||||
case 3:
|
||||
_current.Commit.Author = Models.User.FindOrAdd(line);
|
||||
break;
|
||||
case 4:
|
||||
_current.Commit.AuthorTime = ulong.Parse(line);
|
||||
break;
|
||||
case 5:
|
||||
_current.Commit.Committer = Models.User.FindOrAdd(line);
|
||||
break;
|
||||
case 6:
|
||||
_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;
|
||||
}
|
||||
else
|
||||
{
|
||||
_current.Message = rs.StdOut.Substring(start);
|
||||
end = rs.StdOut.Length - 2;
|
||||
}
|
||||
|
||||
nextPartIdx = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
nextPartIdx++;
|
||||
|
||||
start = end + 1;
|
||||
if (start >= rs.StdOut.Length - 1)
|
||||
break;
|
||||
|
||||
end = rs.StdOut.IndexOf('\n', start);
|
||||
}
|
||||
|
||||
return _commits;
|
||||
}
|
||||
|
||||
private void ParseParent(string data)
|
||||
{
|
||||
if (data.Length < 8)
|
||||
return;
|
||||
|
||||
_current.Commit.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
|
||||
private List<Models.InteractiveCommit> _commits = [];
|
||||
private Models.InteractiveCommit _current = null;
|
||||
private readonly string _boundary;
|
||||
}
|
||||
}
|
|
@ -1,73 +1,23 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class QueryFileContent
|
||||
{
|
||||
public static Stream Run(string repo, string revision, string file)
|
||||
{
|
||||
var starter = new ProcessStartInfo();
|
||||
starter.WorkingDirectory = repo;
|
||||
starter.FileName = Native.OS.GitExecutable;
|
||||
starter.Arguments = $"show {revision}:\"{file}\"";
|
||||
starter.UseShellExecute = false;
|
||||
starter.CreateNoWindow = true;
|
||||
starter.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
starter.RedirectStandardOutput = true;
|
||||
|
||||
var stream = new MemoryStream();
|
||||
try
|
||||
{
|
||||
var proc = new Process() { StartInfo = starter };
|
||||
proc.Start();
|
||||
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;
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryFileContent : Command {
|
||||
public QueryFileContent(string repo, string revision, string file) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show {revision}:\"{file}\"";
|
||||
}
|
||||
|
||||
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;
|
||||
public string Result() {
|
||||
Exec();
|
||||
return _builder.ToString();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line) {
|
||||
_builder.Append(line);
|
||||
_builder.Append('\n');
|
||||
}
|
||||
|
||||
private StringBuilder _builder = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryFileSize : Command
|
||||
{
|
||||
[GeneratedRegex(@"^\d+\s+\w+\s+[0-9a-f]+\s+(\d+)\s+.*$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryFileSize : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^\d+\s+\w+\s+[0-9a-f]+\s+(\d+)\s+.*$");
|
||||
|
||||
public QueryFileSize(string repo, string file, string revision)
|
||||
{
|
||||
public QueryFileSize(string repo, string file, string revision) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"ls-tree {revision} -l -- \"{file}\"";
|
||||
Args = $"ls-tree {revision} -l -- {file}";
|
||||
}
|
||||
|
||||
public long Result()
|
||||
{
|
||||
public long Result() {
|
||||
if (_result != 0) return _result;
|
||||
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var match = REG_FORMAT().Match(rs.StdOut);
|
||||
if (match.Success)
|
||||
if (rs.IsSuccess) {
|
||||
var match = REG_FORMAT.Match(rs.StdOut);
|
||||
if (match.Success) {
|
||||
return long.Parse(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +1,19 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryGitDir : Command
|
||||
{
|
||||
public QueryGitDir(string workDir)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryGitDir : Command {
|
||||
public QueryGitDir(string workDir) {
|
||||
WorkingDirectory = workDir;
|
||||
Args = "rev-parse --git-dir";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
public string Result() {
|
||||
var rs = ReadToEnd().StdOut;
|
||||
if (string.IsNullOrEmpty(rs))
|
||||
return null;
|
||||
if (string.IsNullOrEmpty(rs)) return null;
|
||||
|
||||
rs = rs.Trim();
|
||||
if (Path.IsPathRooted(rs))
|
||||
return rs;
|
||||
if (Path.IsPathRooted(rs)) return rs;
|
||||
return Path.GetFullPath(Path.Combine(WorkingDirectory, rs));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,164 +2,65 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia.Threading;
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryLocalChanges : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
private static readonly string[] UNTRACKED = [ "no", "all" ];
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryLocalChanges : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
private static readonly string[] UNTRACKED = ["no", "all"];
|
||||
|
||||
public QueryLocalChanges(string repo, bool includeUntracked = true)
|
||||
{
|
||||
public QueryLocalChanges(string repo, bool includeUntracked = true) {
|
||||
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;
|
||||
public List<Models.Change> Result() {
|
||||
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 " 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, Models.ChangeState.None); break;
|
||||
case "MM": change.Set(Models.ChangeState.Modified, Models.ChangeState.Modified); break;
|
||||
case "MD": change.Set(Models.ChangeState.Modified, Models.ChangeState.Deleted); break;
|
||||
case "A": change.Set(Models.ChangeState.Added, Models.ChangeState.None); break;
|
||||
case "AM": change.Set(Models.ChangeState.Added, Models.ChangeState.Modified); break;
|
||||
case "AD": change.Set(Models.ChangeState.Added, Models.ChangeState.Deleted); break;
|
||||
case "D": change.Set(Models.ChangeState.Deleted, Models.ChangeState.None); break;
|
||||
case "R": change.Set(Models.ChangeState.Renamed, Models.ChangeState.None); break;
|
||||
case "RM": change.Set(Models.ChangeState.Renamed, Models.ChangeState.Modified); break;
|
||||
case "RD": change.Set(Models.ChangeState.Renamed, Models.ChangeState.Deleted); break;
|
||||
case "C": change.Set(Models.ChangeState.Copied, Models.ChangeState.None); break;
|
||||
case "CM": change.Set(Models.ChangeState.Copied, Models.ChangeState.Modified); 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 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,48 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryRemotes : Command
|
||||
{
|
||||
[GeneratedRegex(@"^([\w\.\-]+)\s*(\S+).*$")]
|
||||
private static partial Regex REG_REMOTE();
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryRemotes : Command {
|
||||
private static readonly Regex REG_REMOTE = new Regex(@"^([\w\.\-]+)\s*(\S+).*$");
|
||||
|
||||
public QueryRemotes(string repo)
|
||||
{
|
||||
public QueryRemotes(string repo) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "remote -v";
|
||||
}
|
||||
|
||||
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;
|
||||
public List<Models.Remote> Result() {
|
||||
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 List<Models.Remote> _loaded = new List<Models.Remote>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryRepositoryRootPath : Command
|
||||
{
|
||||
public QueryRepositoryRootPath(string path)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryRepositoryRootPath : Command {
|
||||
public QueryRepositoryRootPath(string path) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +1,39 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryRevisionObjects : Command
|
||||
{
|
||||
[GeneratedRegex(@"^\d+\s+(\w+)\s+([0-9a-f]+)\s+(.*)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryRevisionObjects : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^\d+\s+(\w+)\s+([0-9a-f]+)\s+(.*)$");
|
||||
private List<Models.Object> objects = new List<Models.Object>();
|
||||
|
||||
public QueryRevisionObjects(string repo, string sha, string parentFolder)
|
||||
{
|
||||
public QueryRevisionObjects(string repo, string sha) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"ls-tree -z {sha}";
|
||||
|
||||
if (!string.IsNullOrEmpty(parentFolder))
|
||||
Args += $" -- \"{parentFolder}\"";
|
||||
Args = $"ls-tree -r {sha}";
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
return _objects;
|
||||
public List<Models.Object> Result() {
|
||||
Exec();
|
||||
return objects;
|
||||
}
|
||||
|
||||
private void Parse(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
protected override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var obj = new Models.Object();
|
||||
obj.SHA = match.Groups[2].Value;
|
||||
obj.Type = Models.ObjectType.Blob;
|
||||
obj.Path = match.Groups[3].Value;
|
||||
|
||||
switch (match.Groups[1].Value)
|
||||
{
|
||||
case "blob":
|
||||
obj.Type = Models.ObjectType.Blob;
|
||||
break;
|
||||
case "tree":
|
||||
obj.Type = Models.ObjectType.Tree;
|
||||
break;
|
||||
case "tag":
|
||||
obj.Type = Models.ObjectType.Tag;
|
||||
break;
|
||||
case "commit":
|
||||
obj.Type = Models.ObjectType.Commit;
|
||||
break;
|
||||
switch (match.Groups[1].Value) {
|
||||
case "blob": obj.Type = Models.ObjectType.Blob; break;
|
||||
case "tree": obj.Type = Models.ObjectType.Tree; break;
|
||||
case "tag": obj.Type = Models.ObjectType.Tag; break;
|
||||
case "commit": obj.Type = Models.ObjectType.Commit; break;
|
||||
}
|
||||
|
||||
_objects.Add(obj);
|
||||
objects.Add(obj);
|
||||
}
|
||||
|
||||
private List<Models.Object> _objects = new List<Models.Object>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QuerySingleCommit : Command
|
||||
{
|
||||
public QuerySingleCommit(string repo, string sha)
|
||||
{
|
||||
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}";
|
||||
}
|
||||
|
||||
public Models.Commit Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut))
|
||||
{
|
||||
var commit = new Models.Commit();
|
||||
var lines = rs.StdOut.Split('\n');
|
||||
if (lines.Length < 8)
|
||||
return null;
|
||||
|
||||
commit.SHA = lines[0];
|
||||
if (!string.IsNullOrEmpty(lines[1]))
|
||||
commit.Parents.AddRange(lines[1].Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||
if (!string.IsNullOrEmpty(lines[2]))
|
||||
commit.ParseDecorators(lines[2]);
|
||||
commit.Author = Models.User.FindOrAdd(lines[3]);
|
||||
commit.AuthorTime = ulong.Parse(lines[4]);
|
||||
commit.Committer = Models.User.FindOrAdd(lines[5]);
|
||||
commit.CommitterTime = ulong.Parse(lines[6]);
|
||||
commit.Subject = lines[7];
|
||||
|
||||
return commit;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
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(.*)$")]
|
||||
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)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff-index --cached -M {parent}";
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var match = REG_FORMAT2().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var change = new Models.Change()
|
||||
{
|
||||
Path = match.Groups[3].Value,
|
||||
DataForAmend = new Models.ChangeDataForAmend()
|
||||
{
|
||||
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;
|
||||
}
|
||||
changes.Add(change);
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private readonly string _parent;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryStagedFileBlobGuid : Command
|
||||
{
|
||||
[GeneratedRegex(@"^\d+\s+([0-9a-f]+)\s+.*$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
|
||||
public QueryStagedFileBlobGuid(string repo, string file)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"ls-files -s -- \"{file}\"";
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
var match = REG_FORMAT().Match(rs.StdOut.Trim());
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
37
src/Commands/QueryStashChanges.cs
Normal file
37
src/Commands/QueryStashChanges.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryStashChanges : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
|
||||
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 List<Models.Change> _changes = new List<Models.Change>();
|
||||
}
|
||||
}
|
|
@ -1,73 +1,47 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryStashes : Command
|
||||
{
|
||||
public QueryStashes(string repo)
|
||||
{
|
||||
namespace SourceGit.Commands {
|
||||
public class QueryStashes : Command {
|
||||
private static readonly Regex REG_STASH = new Regex(@"^Reflog: refs/(stash@\{\d+\}).*$");
|
||||
|
||||
public QueryStashes(string repo) {
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "stash list --format=%H%n%P%n%ct%n%gd%n%s";
|
||||
Args = "stash list --pretty=raw";
|
||||
}
|
||||
|
||||
public List<Models.Stash> Result()
|
||||
{
|
||||
var outs = new List<Models.Stash>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return outs;
|
||||
public List<Models.Stash> Result() {
|
||||
Exec();
|
||||
if (_current != null) _stashes.Add(_current);
|
||||
return _stashes;
|
||||
}
|
||||
|
||||
var nextPartIdx = 0;
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\n', start);
|
||||
while (end > 0)
|
||||
{
|
||||
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);
|
||||
protected override void OnReadline(string line) {
|
||||
if (line.StartsWith("commit ", StringComparison.Ordinal)) {
|
||||
if (_current != null && !string.IsNullOrEmpty(_current.Name)) _stashes.Add(_current);
|
||||
_current = new Models.Stash() { SHA = line.Substring(7, 8) };
|
||||
return;
|
||||
}
|
||||
|
||||
if (start < rs.StdOut.Length)
|
||||
_current.Message = rs.StdOut.Substring(start);
|
||||
if (_current == null) return;
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
private void ParseParent(string data)
|
||||
{
|
||||
if (data.Length < 8)
|
||||
return;
|
||||
|
||||
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
if (line.StartsWith("Reflog: refs/stash@", StringComparison.Ordinal)) {
|
||||
var match = REG_STASH.Match(line);
|
||||
if (match.Success) _current.Name = match.Groups[1].Value;
|
||||
} else if (line.StartsWith("Reflog message: ", StringComparison.Ordinal)) {
|
||||
_current.Message = line.Substring(16);
|
||||
} else if (line.StartsWith("author ", StringComparison.Ordinal)) {
|
||||
Models.User user = Models.User.Invalid;
|
||||
ulong time = 0;
|
||||
Models.Commit.ParseUserAndTime(line.Substring(7), ref user, ref time);
|
||||
_current.Author = user;
|
||||
_current.Time = time;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Models.Stash> _stashes = new List<Models.Stash>();
|
||||
private Models.Stash _current = null;
|
||||
}
|
||||
}
|
||||
|
|
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