diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..56725e7b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,306 @@ +# 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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..bd1dfea9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +* 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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..12792cf6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,79 @@ +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/* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..50e02dc9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +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 }} diff --git a/.github/workflows/localization-check.yml b/.github/workflows/localization-check.yml new file mode 100644 index 00000000..8dcd61c8 --- /dev/null +++ b/.github/workflows/localization-check.yml @@ -0,0 +1,41 @@ +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 }} diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 00000000..2dfc97fd --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,111 @@ +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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..e61e608b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +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/* diff --git a/.gitignore b/.gitignore index 725330cd..e686a534 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,41 @@ -.idea -.vs -bin -obj \ No newline at end of file +.vs/ +.vscode/ +.idea/ + +*.sln.docstates +*.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 diff --git a/LICENSE b/LICENSE index 05cd0620..442ce085 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 leo +Copyright (c) 2025 sourcegit Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Preview_Dark.png b/Preview_Dark.png deleted file mode 100644 index 43ef67f9..00000000 Binary files a/Preview_Dark.png and /dev/null differ diff --git a/Preview_Light.png b/Preview_Light.png deleted file mode 100644 index 09792474..00000000 Binary files a/Preview_Light.png and /dev/null differ diff --git a/README.md b/README.md index c0fd512f..f9ba3072 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,207 @@ -# SourceGit +# SourceGit - Opensource Git GUI client. -开源的Git客户端,仅用于Windows 10。单文件,无需安装,< 500KB。 +[](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 -* DarkTheme +* 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**. -* LightTheme +## 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 -## Thanks +**To use this tool, you need to install Git(>=2.25.1) first.** -* [PUMA](https://gitee.com/whgfu) 配置默认User +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). diff --git a/SourceGit.sln b/SourceGit.sln index 50aba2bc..624322f8 100644 --- a/SourceGit.sln +++ b/SourceGit.sln @@ -1,9 +1,89 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34714.143 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGit", "SourceGit\SourceGit.csproj", "{0A04DD59-7A6C-410C-B427-7DC8183993BD}" +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 @@ -11,15 +91,32 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0A04DD59-7A6C-410C-B427-7DC8183993BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A04DD59-7A6C-410C-B427-7DC8183993BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A04DD59-7A6C-410C-B427-7DC8183993BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A04DD59-7A6C-410C-B427-7DC8183993BD}.Release|Any CPU.Build.0 = Release|Any CPU + {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 = {01F4EC04-5B3C-4D74-BB48-1C251B2D2853} + SolutionGuid = {7FF1B9C6-B5BF-4A50-949F-4B407A0E31C9} EndGlobalSection EndGlobal diff --git a/SourceGit/App.config b/SourceGit/App.config deleted file mode 100644 index 941cc663..00000000 --- a/SourceGit/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/SourceGit/App.ico b/SourceGit/App.ico deleted file mode 100644 index 9063ffee..00000000 Binary files a/SourceGit/App.ico and /dev/null differ diff --git a/SourceGit/App.manifest b/SourceGit/App.manifest deleted file mode 100644 index 5a6db8f3..00000000 --- a/SourceGit/App.manifest +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - PerMonitorV2 - true - - - diff --git a/SourceGit/App.xaml b/SourceGit/App.xaml deleted file mode 100644 index 408df2f0..00000000 --- a/SourceGit/App.xaml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - diff --git a/SourceGit/App.xaml.cs b/SourceGit/App.xaml.cs deleted file mode 100644 index fed0ffda..00000000 --- a/SourceGit/App.xaml.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Microsoft.Win32; -using System; -using System.IO; -using System.Windows; - -namespace SourceGit { - - /// - /// Application. - /// - public partial class App : Application { - - /// - /// Getter/Setter for Git preference. - /// - public static Git.Preference Preference { - get { return Git.Preference.Instance; } - set { Git.Preference.Instance = value; } - } - - /// - /// Check if GIT has been configured. - /// - public static bool IsGitConfigured { - get { - return !string.IsNullOrEmpty(Preference.GitExecutable) - && File.Exists(Preference.GitExecutable); - } - } - - /// - /// Error handler. - /// - public static Action OnError { - get; - set; - } - - /// - /// Raise error message. - /// - /// - public static void RaiseError(string message) { - OnError?.Invoke(message); - } - - /// - /// Get popup manager by repository - /// - /// - /// - public static UI.PopupManager GetPopupManager(Git.Repository repo) { - var main = Current.MainWindow as UI.Launcher; - if (main == null) return null; - if (repo == null) return (main.Tabs[0].Page as UI.Manager).popupManager; - - for (int i = 1; i < main.openedTabs.Items.Count; i++) { - var opened = main.openedTabs.Items[i] as UI.Launcher.Tab; - if (opened.Repo.Path == repo.Path) { - return (opened.Page as UI.Dashboard).popupManager; - } - } - - return null; - } - - /// - /// Startup event. - /// - /// - /// - private void OnAppStartup(object sender, StartupEventArgs e) { - // Use this app as a sequence editor? - var args = e.Args; - if (args.Length > 1) { - if (args[0] == "--interactive-rebase") { - if (args.Length < 3) { - Environment.Exit(1); - return; - } - - File.WriteAllText(args[2], File.ReadAllText(args[1])); - } - - Environment.Exit(0); - return; - } - - // Try auto configure git via registry. - if (!IsGitConfigured) { - var root = RegistryKey.OpenBaseKey( - RegistryHive.LocalMachine, - Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32); - - var git = root.OpenSubKey("SOFTWARE\\GitForWindows"); - if (git != null) { - Preference.GitExecutable = Path.Combine( - git.GetValue("InstallPath") as string, - "bin", - "git.exe"); - } - } - - // Apply themes - if (Preference.UIUseLightTheme) { - foreach (var rs in Current.Resources.MergedDictionaries) { - if (rs.Source != null && rs.Source.OriginalString.StartsWith("pack://application:,,,/Resources/Themes/")) { - rs.Source = new Uri("pack://application:,,,/Resources/Themes/Light.xaml", UriKind.Absolute); - break; - } - } - } - - // Show main window - Current.MainWindow = new UI.Launcher(); - Current.MainWindow.Show(); - } - - /// - /// Deactivated event. - /// - /// - /// - private void OnAppDeactivated(object sender, EventArgs e) { - Git.Preference.Save(); - GC.Collect(); - } - } -} diff --git a/SourceGit/Converters/BoolToCollapsed.cs b/SourceGit/Converters/BoolToCollapsed.cs deleted file mode 100644 index 47ce3c73..00000000 --- a/SourceGit/Converters/BoolToCollapsed.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace SourceGit.Converters { - - /// - /// Same as BoolToVisibilityConverter. - /// - public class BoolToCollapsed : IValueConverter { - - /// - /// Implement IValueConverter.Convert - /// - /// - /// - /// - /// - /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - return (bool)value ? Visibility.Visible : Visibility.Collapsed; - } - - /// - /// Implement IValueConverter.ConvertBack - /// - /// - /// - /// - /// - /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotImplementedException(); - } - } -} diff --git a/SourceGit/Converters/FileStatusToColor.cs b/SourceGit/Converters/FileStatusToColor.cs deleted file mode 100644 index aba855f9..00000000 --- a/SourceGit/Converters/FileStatusToColor.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; -using System.Windows.Media; - -namespace SourceGit.Converters { - - /// - /// Convert file status to brush - /// - public class FileStatusToColor : IValueConverter { - - /// - /// Is only test local changes. - /// - public bool OnlyWorkTree { get; set; } = false; - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - var change = value as Git.Change; - if (change == null) return Brushes.Transparent; - - var status = Git.Change.Status.None; - if (OnlyWorkTree) { - if (change.IsConflit) return Brushes.Yellow; - status = change.WorkTree; - } else { - status = change.Index; - } - - if (App.Preference.UIUseLightTheme) { - switch (status) { - case Git.Change.Status.Modified: return Brushes.Goldenrod; - case Git.Change.Status.Added: return Brushes.Green; - case Git.Change.Status.Deleted: return Brushes.Red; - case Git.Change.Status.Renamed: return Brushes.Magenta; - case Git.Change.Status.Copied: return Brushes.Goldenrod; - case Git.Change.Status.Unmerged: return Brushes.Goldenrod; - case Git.Change.Status.Untracked: return Brushes.Green; - default: return Brushes.Transparent; - } - } else { - switch (status) { - case Git.Change.Status.Modified: return Brushes.DarkGoldenrod; - case Git.Change.Status.Added: return Brushes.DarkGreen; - case Git.Change.Status.Deleted: return Brushes.DarkRed; - case Git.Change.Status.Renamed: return Brushes.DarkMagenta; - case Git.Change.Status.Copied: return Brushes.DarkGoldenrod; - case Git.Change.Status.Unmerged: return Brushes.DarkGoldenrod; - case Git.Change.Status.Untracked: return Brushes.DarkGreen; - default: return Brushes.Transparent; - } - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotImplementedException(); - } - } -} diff --git a/SourceGit/Converters/FileStatusToIcon.cs b/SourceGit/Converters/FileStatusToIcon.cs deleted file mode 100644 index 85447770..00000000 --- a/SourceGit/Converters/FileStatusToIcon.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace SourceGit.Converters { - - /// - /// Convert file status to icon. - /// - public class FileStatusToIcon : IValueConverter { - - /// - /// Is only test local changes. - /// - public bool OnlyWorkTree { get; set; } = false; - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - var change = value as Git.Change; - if (change == null) return ""; - - var status = Git.Change.Status.None; - if (OnlyWorkTree) { - if (change.IsConflit) return "X"; - status = change.WorkTree; - } else { - status = change.Index; - } - - switch (status) { - case Git.Change.Status.Modified: return "M"; - case Git.Change.Status.Added: return "A"; - case Git.Change.Status.Deleted: return "D"; - case Git.Change.Status.Renamed: return "R"; - case Git.Change.Status.Copied: return "C"; - case Git.Change.Status.Unmerged: return "U"; - case Git.Change.Status.Untracked: return "?"; - default: return "?"; - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotImplementedException(); - } - } -} diff --git a/SourceGit/Converters/IndentToMargin.cs b/SourceGit/Converters/IndentToMargin.cs deleted file mode 100644 index 5214396b..00000000 --- a/SourceGit/Converters/IndentToMargin.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace SourceGit.Converters { - - /// - /// Convert indent(horizontal offset) to Margin property - /// - public class IndentToMargin : IValueConverter { - - /// - /// Implement IValueConverter.Convert - /// - /// - /// - /// - /// - /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - return new Thickness((double)value, 0, 0, 0); - } - - /// - /// Implement IValueConverter.ConvertBack - /// - /// - /// - /// - /// - /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - return ((Thickness)value).Left; - } - } -} diff --git a/SourceGit/Converters/InverseBool.cs b/SourceGit/Converters/InverseBool.cs deleted file mode 100644 index 932ae4ef..00000000 --- a/SourceGit/Converters/InverseBool.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace SourceGit.Converters { - - /// - /// Inverse bool converter. - /// - public class InverseBool : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - return !((bool)value); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotImplementedException(); - } - } -} diff --git a/SourceGit/Converters/InverseBoolToCollapsed.cs b/SourceGit/Converters/InverseBoolToCollapsed.cs deleted file mode 100644 index 862bf913..00000000 --- a/SourceGit/Converters/InverseBoolToCollapsed.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace SourceGit.Converters { - - /// - /// Inverse BoolToCollapsed. - /// - public class InverseBoolToCollapsed : IValueConverter { - - /// - /// Implement IValueConverter.Convert - /// - /// - /// - /// - /// - /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - return (bool)value ? Visibility.Collapsed : Visibility.Visible; - } - - /// - /// Implement IValueConverter.ConvertBack - /// - /// - /// - /// - /// - /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotImplementedException(); - } - } -} diff --git a/SourceGit/Converters/PercentToDouble.cs b/SourceGit/Converters/PercentToDouble.cs deleted file mode 100644 index a76c9b92..00000000 --- a/SourceGit/Converters/PercentToDouble.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace SourceGit.Converters { - - /// - /// Convert percent to double. - /// - public class PercentToDouble : IValueConverter { - - /// - /// Percentage. - /// - public double Percent { get; set; } - - /// - /// Implement IValueConverter.Convert - /// - /// - /// - /// - /// - /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - return (double)value * Percent; - } - - /// - /// Implement IValueConverter.ConvertBack - /// - /// - /// - /// - /// - /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotImplementedException(); - } - } -} diff --git a/SourceGit/Converters/TreeViewItemDepthToMargin.cs b/SourceGit/Converters/TreeViewItemDepthToMargin.cs deleted file mode 100644 index 8c7eb856..00000000 --- a/SourceGit/Converters/TreeViewItemDepthToMargin.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Media; - -namespace SourceGit.Converters { - - /// - /// Convert depth of a TreeViewItem to Margin property. - /// - public class TreeViewItemDepthToMargin : IValueConverter { - - /// - /// Indent length - /// - public double Indent { get; set; } = 19; - - /// - /// Implement IValueConverter.Convert - /// - /// - /// - /// - /// - /// - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - TreeViewItem item = value as TreeViewItem; - if (item == null) return new Thickness(0); - - TreeViewItem iterator = GetParent(item); - int depth = 0; - while (iterator != null) { - depth++; - iterator = GetParent(iterator); - } - - return new Thickness(Indent * depth, 0, 0, 0); - } - - /// - /// Implement IValueConvert.ConvertBack - /// - /// - /// - /// - /// - /// - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotImplementedException(); - } - - /// - /// Get parent item. - /// - /// - /// - private TreeViewItem GetParent(TreeViewItem item) { - var parent = VisualTreeHelper.GetParent(item); - - while (parent != null && !(parent is TreeView) && !(parent is TreeViewItem)) { - parent = VisualTreeHelper.GetParent(parent); - } - - return parent as TreeViewItem; - } - } -} diff --git a/SourceGit/Git/Blame.cs b/SourceGit/Git/Blame.cs deleted file mode 100644 index bf597274..00000000 --- a/SourceGit/Git/Blame.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; - -namespace SourceGit.Git { - - /// - /// Blame - /// - public class Blame { - - /// - /// Block content. - /// - public class Block { - public string CommitSHA { get; set; } - public string Author { get; set; } - public string Time { get; set; } - public string Content { get; set; } - } - - /// - /// Blocks - /// - public List Blocks { get; set; } = new List(); - - /// - /// Is binary file? - /// - public bool IsBinary { get; set; } = false; - - /// - /// Line count. - /// - public int LineCount { get; set; } = 0; - } -} diff --git a/SourceGit/Git/Branch.cs b/SourceGit/Git/Branch.cs deleted file mode 100644 index 5410c9b6..00000000 --- a/SourceGit/Git/Branch.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace SourceGit.Git { - - /// - /// Git branch - /// - public class Branch { - private static readonly string PRETTY_FORMAT = @"$%(refname)$%(objectname)$%(HEAD)$%(upstream)$%(upstream:track)$%(contents:subject)"; - private static readonly Regex PARSE = new Regex(@"\$(.*)\$(.*)\$([\* ])\$(.*)\$(.*?)\$(.*)"); - private static readonly Regex AHEAD = new Regex(@"ahead (\d+)"); - private static readonly Regex BEHIND = new Regex(@"behind (\d+)"); - - /// - /// Branch type. - /// - public enum Type { - Normal, - Feature, - Release, - Hotfix, - } - - /// - /// Branch name - /// - public string Name { get; set; } = ""; - - /// - /// Full name. - /// - public string FullName { get; set; } = ""; - - /// - /// Head ref - /// - public string Head { get; set; } = ""; - - /// - /// Subject for head ref. - /// - public string HeadSubject { get; set; } = ""; - - /// - /// Is local branch - /// - public bool IsLocal { get; set; } = false; - - /// - /// Branch type. - /// - public Type Kind { get; set; } = Type.Normal; - - /// - /// Remote name. Only used for remote branch - /// - public string Remote { get; set; } = ""; - - /// - /// Upstream. Only used for local branches. - /// - public string Upstream { get; set; } - - /// - /// Track information for upstream. Only used for local branches. - /// - public string UpstreamTrack { get; set; } - - /// - /// Is current branch. Only used for local branches. - /// - public bool IsCurrent { get; set; } - - /// - /// Is this branch's HEAD same with upstream? - /// - public bool IsSameWithUpstream => string.IsNullOrEmpty(UpstreamTrack); - - /// - /// Enable filter in log histories. - /// - public bool IsFiltered { get; set; } - - /// - /// Load branches. - /// - /// - public static List Load(Repository repo) { - var localPrefix = "refs/heads/"; - var remotePrefix = "refs/remotes/"; - var branches = new List(); - var remoteBranches = new List(); - - repo.RunCommand("branch -l --all -v --format=\"" + PRETTY_FORMAT + "\"", line => { - var match = PARSE.Match(line); - if (!match.Success) return; - - var branch = new Branch(); - var refname = match.Groups[1].Value; - if (refname.EndsWith("/HEAD")) return; - - if (refname.StartsWith(localPrefix, StringComparison.Ordinal)) { - branch.Name = refname.Substring(localPrefix.Length); - branch.IsLocal = true; - } else if (refname.StartsWith(remotePrefix, StringComparison.Ordinal)) { - var name = refname.Substring(remotePrefix.Length); - branch.Remote = name.Substring(0, name.IndexOf('/')); - branch.Name = name; - branch.IsLocal = false; - remoteBranches.Add(refname); - } - - branch.FullName = refname; - branch.Head = match.Groups[2].Value; - branch.IsCurrent = match.Groups[3].Value == "*"; - branch.Upstream = match.Groups[4].Value; - branch.UpstreamTrack = ParseTrack(match.Groups[5].Value); - branch.HeadSubject = match.Groups[6].Value; - - branches.Add(branch); - }); - - // Fixed deleted remote branch - foreach (var b in branches) { - if (!string.IsNullOrEmpty(b.Upstream) && !remoteBranches.Contains(b.Upstream)) { - b.Upstream = null; - } - } - - return branches; - } - - /// - /// Create new branch. - /// - /// - /// - /// - public static void Create(Repository repo, string name, string startPoint) { - var errs = repo.RunCommand($"branch {name} {startPoint}", null); - if (errs != null) App.RaiseError(errs); - } - - /// - /// Rename branch - /// - /// - /// - public void Rename(Repository repo, string name) { - var errs = repo.RunCommand($"branch -M {Name} {name}", null); - if (errs != null) App.RaiseError(errs); - } - - /// - /// Delete branch. - /// - /// - public void Delete(Repository repo) { - string errs = null; - - if (!IsLocal) { - errs = repo.RunCommand($"-c credential.helper=manager push {Remote} --delete {Name.Substring(Name.IndexOf('/')+1)}", null); - } else { - errs = repo.RunCommand($"branch -D {Name}", null); - } - - if (errs != null) App.RaiseError(errs); - } - - private static string ParseTrack(string data) { - if (string.IsNullOrEmpty(data)) return ""; - - string track = ""; - - var ahead = AHEAD.Match(data); - if (ahead.Success) { - track += ahead.Groups[1].Value + "↑ "; - } - - var behind = BEHIND.Match(data); - if (behind.Success) { - track += behind.Groups[1].Value + "↓"; - } - - return track.Trim(); - } - } -} diff --git a/SourceGit/Git/Change.cs b/SourceGit/Git/Change.cs deleted file mode 100644 index c5f9ca4a..00000000 --- a/SourceGit/Git/Change.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System.Text.RegularExpressions; - -namespace SourceGit.Git { - - /// - /// Changed file status. - /// - public class Change { - private static readonly Regex FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$"); - - /// - /// Status Code - /// - public enum Status { - None, - Modified, - Added, - Deleted, - Renamed, - Copied, - Unmerged, - Untracked, - } - - /// - /// Index status - /// - public Status Index { get; set; } - - /// - /// Work tree status. - /// - public Status WorkTree { get; set; } - - /// - /// Current file path. - /// - public string Path { get; set; } - - /// - /// Original file path before this revision. - /// - public string OriginalPath { get; set; } - - /// - /// Staged(added) in index? - /// - public bool IsAddedToIndex { - get { - if (Index == Status.None || Index == Status.Untracked) return false; - return true; - } - } - - /// - /// Is conflict? - /// - public bool IsConflit { - get { - if (Index == Status.Unmerged || WorkTree == Status.Unmerged) return true; - if (Index == Status.Added && WorkTree == Status.Added) return true; - if (Index == Status.Deleted && WorkTree == Status.Deleted) return true; - return false; - } - } - - /// - /// Parse change for `--name-status` data. - /// - /// Raw data. - /// Read from commit? - /// Parsed change instance. - public static Change Parse(string data, bool fromCommit = false) { - var match = FORMAT.Match(data); - if (!match.Success) return null; - - var change = new Change() { Path = match.Groups[2].Value }; - var status = match.Groups[1].Value; - - if (fromCommit) { - switch (status[0]) { - case 'M': change.Set(Status.Modified); break; - case 'A': change.Set(Status.Added); break; - case 'D': change.Set(Status.Deleted); break; - case 'R': change.Set(Status.Renamed); break; - case 'C': change.Set(Status.Copied); break; - default: return null; - } - } else { - switch (status) { - case " M": change.Set(Status.None, Status.Modified); break; - case " A": change.Set(Status.None, Status.Added); break; - case " D": change.Set(Status.None, Status.Deleted); break; - case " R": change.Set(Status.None, Status.Renamed); break; - case " C": change.Set(Status.None, Status.Copied); break; - case "M": change.Set(Status.Modified, Status.None); break; - case "MM": change.Set(Status.Modified, Status.Modified); break; - case "MD": change.Set(Status.Modified, Status.Deleted); break; - case "A": change.Set(Status.Added, Status.None); break; - case "AM": change.Set(Status.Added, Status.Modified); break; - case "AD": change.Set(Status.Added, Status.Deleted); break; - case "D": change.Set(Status.Deleted, Status.None); break; - case "R": change.Set(Status.Renamed, Status.None); break; - case "RM": change.Set(Status.Renamed, Status.Modified); break; - case "RD": change.Set(Status.Renamed, Status.Deleted); break; - case "C": change.Set(Status.Copied, Status.None); break; - case "CM": change.Set(Status.Copied, Status.Modified); break; - case "CD": change.Set(Status.Copied, Status.Deleted); break; - case "DR": change.Set(Status.Deleted, Status.Renamed); break; - case "DC": change.Set(Status.Deleted, Status.Copied); break; - case "DD": change.Set(Status.Deleted, Status.Deleted); break; - case "AU": change.Set(Status.Added, Status.Unmerged); break; - case "UD": change.Set(Status.Unmerged, Status.Deleted); break; - case "UA": change.Set(Status.Unmerged, Status.Added); break; - case "DU": change.Set(Status.Deleted, Status.Unmerged); break; - case "AA": change.Set(Status.Added, Status.Added); break; - case "UU": change.Set(Status.Unmerged, Status.Unmerged); break; - case "??": change.Set(Status.Untracked, Status.Untracked); break; - default: return null; - } - } - - if (change.Path[0] == '"') change.Path = change.Path.Substring(1, change.Path.Length - 2); - if (!string.IsNullOrEmpty(change.OriginalPath) && change.OriginalPath[0] == '"') change.OriginalPath = change.OriginalPath.Substring(1, change.OriginalPath.Length - 2); - return change; - } - - private void Set(Status index, Status workTree = Status.None) { - Index = index; - WorkTree = workTree; - - if (index == Status.Renamed || workTree == Status.Renamed) { - var idx = Path.IndexOf('\t'); - if (idx >= 0) { - OriginalPath = Path.Substring(0, idx); - Path = Path.Substring(idx + 1); - } else { - idx = Path.IndexOf(" -> "); - if (idx > 0) { - OriginalPath = Path.Substring(0, idx); - Path = Path.Substring(idx + 4); - } - } - } - } - } -} diff --git a/SourceGit/Git/Commit.cs b/SourceGit/Git/Commit.cs deleted file mode 100644 index ef0480f7..00000000 --- a/SourceGit/Git/Commit.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text.RegularExpressions; - -namespace SourceGit.Git { - - /// - /// Git commit information. - /// - public class Commit { - private static readonly string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----"; - private static readonly string GPGSIG_END = " -----END PGP SIGNATURE-----"; - - /// - /// Object in commit. - /// - public class Object { - public enum Type { - Tag, - Blob, - Tree, - Commit, - } - - public string Path { get; set; } - public Type Kind { get; set; } - public string SHA { get; set; } - } - - /// - /// SHA - /// - public string SHA { get; set; } - - /// - /// Short SHA. - /// - public string ShortSHA => SHA.Substring(0, 8); - - /// - /// Parent commit SHAs. - /// - public List Parents { get; set; } = new List(); - - /// - /// Author - /// - public User Author { get; set; } = new User(); - - /// - /// Committer. - /// - public User Committer { get; set; } = new User(); - - /// - /// Subject - /// - public string Subject { get; set; } = ""; - - /// - /// Extra message. - /// - public string Message { get; set; } = ""; - - /// - /// HEAD commit? - /// - public bool IsHEAD { get; set; } = false; - - /// - /// Merged in current branch? - /// - public bool IsMerged { get; set; } = false; - - /// - /// X offset in graph - /// - public double GraphOffset { get; set; } = 0; - - /// - /// Has decorators. - /// - public bool HasDecorators => Decorators.Count > 0; - - /// - /// Decorators. - /// - public List Decorators { get; set; } = new List(); - - /// - /// Read commits. - /// - /// Repository - /// Limitations - /// Parsed commits. - public static List Load(Repository repo, string limit) { - List commits = new List(); - Commit current = null; - bool bSkippingGpgsig = false; - bool findHead = false; - - repo.RunCommand("log --date-order --decorate=full --pretty=raw " + limit, line => { - if (bSkippingGpgsig) { - if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal)) bSkippingGpgsig = false; - return; - } else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal)) { - bSkippingGpgsig = true; - return; - } - - if (line.StartsWith("commit ", StringComparison.Ordinal)) { - if (current != null) { - current.Message = current.Message.TrimEnd(); - commits.Add(current); - } - - current = new Commit(); - ParseSHA(current, line.Substring("commit ".Length)); - if (!findHead) findHead = current.IsHEAD; - 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)) { - current.Author.Parse(line); - } else if (line.StartsWith("committer ", StringComparison.Ordinal)) { - current.Committer.Parse(line); - } else if (string.IsNullOrEmpty(current.Subject)) { - current.Subject = line.Trim(); - } else { - current.Message += (line.Trim() + "\n"); - } - }); - - if (current != null) { - current.Message = current.Message.TrimEnd(); - commits.Add(current); - } - - if (!findHead && commits.Count > 0) { - var startInfo = new ProcessStartInfo(); - startInfo.FileName = Preference.Instance.GitExecutable; - startInfo.Arguments = $"merge-base --is-ancestor {commits[0].SHA} HEAD"; - startInfo.WorkingDirectory = repo.Path; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.RedirectStandardOutput = false; - startInfo.RedirectStandardError = false; - - var proc = new Process() { StartInfo = startInfo }; - proc.Start(); - proc.WaitForExit(); - - commits[0].IsMerged = proc.ExitCode == 0; - proc.Close(); - } - - return commits; - } - - /// - /// Get changed file list. - /// - /// - /// - public List GetChanges(Repository repo) { - var changes = new List(); - var regex = new Regex(@"^[MADRC]\d*\s*.*$"); - - var errs = repo.RunCommand($"show --name-status {SHA}", line => { - if (!regex.IsMatch(line)) return; - - var change = Change.Parse(line, true); - if (change != null) changes.Add(change); - }); - - if (errs != null) App.RaiseError(errs); - return changes; - } - - /// - /// Get revision files. - /// - /// - /// - public List GetFiles(Repository repo) { - var files = new List(); - var test = new Regex(@"^\d+\s+(\w+)\s+([0-9a-f]+)\s+(.*)$"); - - var errs = repo.RunCommand($"ls-tree -r {SHA}", line => { - var match = test.Match(line); - if (!match.Success) return; - - var obj = new Object(); - obj.Path = match.Groups[3].Value; - obj.Kind = Object.Type.Blob; - obj.SHA = match.Groups[2].Value; - - switch (match.Groups[1].Value) { - case "tag": obj.Kind = Object.Type.Tag; break; - case "blob": obj.Kind = Object.Type.Blob; break; - case "tree": obj.Kind = Object.Type.Tree; break; - case "commit": obj.Kind = Object.Type.Commit; break; - } - - files.Add(obj); - }); - - if (errs != null) App.RaiseError(errs); - return files; - } - - /// - /// Get file content. - /// - /// - /// - /// - public string GetTextFileContent(Repository repo, string file, out bool isBinary) { - var data = new List(); - var count = 0; - var binary = false; - - var errs = repo.RunCommand($"show {SHA}:\"{file}\"", line => { - if (binary) return; - - count++; - if (data.Count >= 1000) return; - - if (line.IndexOf('\0') >= 0) { - binary = true; - data.Clear(); - data.Add("BINARY FILE PREVIEW NOT SUPPORTED!"); - return; - } - - data.Add(line); - }); - - if (!binary && count > 1000) { - data.Add("..."); - data.Add($"Total {count} lines. Hide {count-1000} lines."); - } - - isBinary = binary; - - if (errs != null) App.RaiseError(errs); - return string.Join("\n", data); - } - - private static void ParseSHA(Commit commit, string data) { - var decoratorStart = data.IndexOf('('); - if (decoratorStart < 0) { - commit.SHA = data.Trim(); - return; - } - - commit.SHA = data.Substring(0, decoratorStart).Trim(); - - var subs = data.Substring(decoratorStart + 1).Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var sub in subs) { - var d = sub.Trim(); - if (d.StartsWith("tag: refs/tags/", StringComparison.Ordinal)) { - commit.Decorators.Add(new Decorator() { - Type = DecoratorType.Tag, - Name = d.Substring(15).Trim() - }); - } else if (d.EndsWith("/HEAD")) { - continue; - } else if (d.StartsWith("HEAD -> refs/heads/", StringComparison.Ordinal)) { - commit.IsHEAD = true; - commit.Decorators.Add(new Decorator() { - Type = DecoratorType.CurrentBranchHead, - Name = d.Substring(19).Trim() - }); - } else if (d.StartsWith("refs/heads/", StringComparison.Ordinal)) { - commit.Decorators.Add(new Decorator() { - Type = DecoratorType.LocalBranchHead, - Name = d.Substring(11).Trim() - }); - } else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal)) { - commit.Decorators.Add(new Decorator() { - Type = DecoratorType.RemoteBranchHead, - Name = d.Substring(13).Trim() - }); - } - } - } - } -} diff --git a/SourceGit/Git/Decorator.cs b/SourceGit/Git/Decorator.cs deleted file mode 100644 index d9131712..00000000 --- a/SourceGit/Git/Decorator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace SourceGit.Git { - - /// - /// Decorator type. - /// - public enum DecoratorType { - None, - CurrentBranchHead, - LocalBranchHead, - RemoteBranchHead, - Tag, - } - - /// - /// Commit decorator. - /// - public class Decorator { - public DecoratorType Type { get; set; } - public string Name { get; set; } - } -} diff --git a/SourceGit/Git/Diff.cs b/SourceGit/Git/Diff.cs deleted file mode 100644 index 7ce0190c..00000000 --- a/SourceGit/Git/Diff.cs +++ /dev/null @@ -1,263 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; - -namespace SourceGit.Git { - - /// - /// Diff helper. - /// - public class Diff { - private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@", RegexOptions.None); - - /// - /// Line mode. - /// - public enum LineMode { - Normal, - Indicator, - Empty, - Added, - Deleted, - } - - /// - /// Side - /// - public enum Side { - Left, - Right, - Both, - } - - /// - /// Binary change. - /// - public class BinaryChange { - public long Size = 0; - public long PreSize = 0; - } - - /// - /// Block - /// - public class Block { - public Side Side = Side.Both; - public LineMode Mode = LineMode.Normal; - public int LeftStart = 0; - public int RightStart = 0; - public int Count = 0; - public StringBuilder Builder = new StringBuilder(); - - public bool IsLeftDelete => Side == Side.Left && Mode == LineMode.Deleted; - public bool IsRightAdded => Side == Side.Right && Mode == LineMode.Added; - public bool IsBothSideNormal => Side == Side.Both && Mode == LineMode.Normal; - public bool CanShowNumber => Mode != LineMode.Indicator && Mode != LineMode.Empty; - - public void Append(string data) { - if (Count > 0) Builder.AppendLine(); - Builder.Append(data); - Count++; - } - } - - /// - /// Diff result. - /// - public class Result { - public bool IsValid = false; - public bool IsBinary = false; - public List Blocks = new List(); - public int LeftLineCount = 0; - public int RightLineCount = 0; - - public void SetBinary() { - IsValid = true; - IsBinary = true; - } - - public void Add(Block b) { - if (b.Count == 0) return; - - switch (b.Side) { - case Side.Left: - LeftLineCount += b.Count; - break; - case Side.Right: - RightLineCount += b.Count; - break; - default: - LeftLineCount += b.Count; - RightLineCount += b.Count; - break; - } - - Blocks.Add(b); - } - - public void Fit() { - if (LeftLineCount > RightLineCount) { - var b = new Block(); - b.Side = Side.Right; - b.Mode = LineMode.Empty; - - var delta = LeftLineCount - RightLineCount; - for (int i = 0; i < delta; i++) b.Append(""); - - Add(b); - } else if (LeftLineCount < RightLineCount) { - var b = new Block(); - b.Side = Side.Left; - b.Mode = LineMode.Empty; - - var delta = RightLineCount - LeftLineCount; - for (int i = 0; i < delta; i++) b.Append(""); - - Add(b); - } - } - } - - /// - /// Run diff process. - /// - /// - /// - /// - public static Result Run(Repository repo, string args) { - var rs = new Result(); - var current = new Block(); - var left = 0; - var right = 0; - - repo.RunCommand($"diff --ignore-cr-at-eol {args}", line => { - if (rs.IsBinary) return; - - if (!rs.IsValid) { - var match = REG_INDICATOR.Match(line); - if (!match.Success) { - if (line.StartsWith("Binary ")) rs.SetBinary(); - return; - } - - rs.IsValid = true; - left = int.Parse(match.Groups[1].Value); - right = int.Parse(match.Groups[2].Value); - current.Mode = LineMode.Indicator; - current.Append(line); - } else { - if (line[0] == '-') { - if (current.IsLeftDelete) { - current.Append(line.Substring(1)); - } else { - rs.Add(current); - - current = new Block(); - current.Side = Side.Left; - current.Mode = LineMode.Deleted; - current.LeftStart = left; - current.Append(line.Substring(1)); - } - - left++; - } else if (line[0] == '+') { - if (current.IsRightAdded) { - current.Append(line.Substring(1)); - } else { - rs.Add(current); - - current = new Block(); - current.Side = Side.Right; - current.Mode = LineMode.Added; - current.RightStart = right; - current.Append(line.Substring(1)); - } - - right++; - } else if (line[0] == '\\') { - var tmp = new Block(); - tmp.Side = current.Side; - tmp.Mode = LineMode.Indicator; - tmp.Append(line.Substring(1)); - - rs.Add(current); - rs.Add(tmp); - rs.Fit(); - - current = new Block(); - current.LeftStart = left; - current.RightStart = right; - } else { - var match = REG_INDICATOR.Match(line); - if (match.Success) { - rs.Add(current); - rs.Fit(); - - left = int.Parse(match.Groups[1].Value); - right = int.Parse(match.Groups[2].Value); - - current = new Block(); - current.Mode = LineMode.Indicator; - current.Append(line); - } else { - if (current.IsBothSideNormal) { - current.Append(line.Substring(1)); - } else { - rs.Add(current); - rs.Fit(); - - current = new Block(); - current.LeftStart = left; - current.RightStart = right; - current.Append(line.Substring(1)); - } - - left++; - right++; - } - } - } - }); - - rs.Add(current); - rs.Fit(); - - if (rs.IsBinary) rs.Blocks.Clear(); - return rs; - } - - /// - /// Get file size changes for binary file. - /// - /// - /// - /// - /// - /// - public static BinaryChange GetSizeChange(Repository repo, string[] revisions, string path, string orgPath = null) { - var change = new BinaryChange(); - - if (revisions.Length == 0) { // Compare working copy with HEAD - change.Size = new FileInfo(Path.Combine(repo.Path, path)).Length; - change.PreSize = repo.GetFileSize("HEAD", path); - } else if (revisions.Length == 1) { // Compare HEAD with given revision. - change.Size = repo.GetFileSize("HEAD", path); - if (!string.IsNullOrEmpty(orgPath)) { - change.PreSize = repo.GetFileSize(revisions[0], orgPath); - } else { - change.PreSize = repo.GetFileSize(revisions[0], path); - } - } else { - change.Size = repo.GetFileSize(revisions[1], path); - if (!string.IsNullOrEmpty(orgPath)) { - change.PreSize = repo.GetFileSize(revisions[0], orgPath); - } else { - change.PreSize = repo.GetFileSize(revisions[0], path); - } - } - - return change; - } - } -} diff --git a/SourceGit/Git/MergeTool.cs b/SourceGit/Git/MergeTool.cs deleted file mode 100644 index 8fda9492..00000000 --- a/SourceGit/Git/MergeTool.cs +++ /dev/null @@ -1,202 +0,0 @@ -using Microsoft.Win32; -using SourceGit.UI; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SourceGit.Git { - - /// - /// External merge tool - /// - public class MergeTool { - - /// - /// Display name - /// - public string Name { get; set; } - - /// - /// Executable file name. - /// - public string ExecutableName { get; set; } - - /// - /// Command line parameter. - /// - public string Parameter { get; set; } - - /// - /// Auto finder. - /// - public Func Finder { get; set; } - - /// - /// Is this merge tool configured. - /// - public bool IsConfigured => !string.IsNullOrEmpty(ExecutableName); - - /// - /// Supported merge tools. - /// - public static List Supported = new List() { - new MergeTool("--", "", "", FindInvalid), - new MergeTool("Araxis Merge", "Compare.exe", "/wait /merge /3 /a1 \"$BASE\" \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", FindAraxisMerge), - new MergeTool("Beyond Compare 4", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", FindBCompare), - new MergeTool("KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", FindKDiff3), - new MergeTool("P4Merge", "p4merge.exe", "\"$BASE\" \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", FindP4Merge), - new MergeTool("Tortoise Merge", "TortoiseMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", FindTortoiseMerge), - new MergeTool("Visual Studio 2017/2019", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" //m", FindVSMerge), - new MergeTool("Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", FindVSCode), - }; - - /// - /// Finder for invalid merge tool. - /// - /// - public static string FindInvalid() { - return "--"; - } - - /// - /// Find araxis merge tool install path. - /// - /// - public static string FindAraxisMerge() { - var path = @"C:\Program Files\Araxis\Araxis Merge\Compare.exe"; - if (File.Exists(path)) return path; - return ""; - } - - /// - /// Find kdiff3.exe by registry. - /// - /// - public static string FindKDiff3() { - var root = RegistryKey.OpenBaseKey( - RegistryHive.LocalMachine, - Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32); - - var kdiff = root.OpenSubKey(@"SOFTWARE\KDiff3\diff-ext"); - if (kdiff == null) return ""; - return kdiff.GetValue("diffcommand") as string; - } - - /// - /// Finder for p4merge - /// - /// - public static string FindP4Merge() { - var path = @"C:\Program Files\Perforce\p4merge.exe"; - if (File.Exists(path)) return path; - return ""; - } - - /// - /// Find BComp.exe by registry. - /// - /// - public static string FindBCompare() { - var root = RegistryKey.OpenBaseKey( - RegistryHive.LocalMachine, - Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32); - - var bc = root.OpenSubKey(@"SOFTWARE\Scooter Software\Beyond Compare"); - if (bc == null) return ""; - - var exec = bc.GetValue("ExePath") as string; - var dir = Path.GetDirectoryName(exec); - return $"{dir}\\BComp.exe"; - } - - /// - /// Find TortoiseMerge.exe by registry. - /// - /// - public static string FindTortoiseMerge() { - var root = RegistryKey.OpenBaseKey( - RegistryHive.LocalMachine, - Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32); - - var tortoiseSVN = root.OpenSubKey("SOFTWARE\\TortoiseSVN"); - if (tortoiseSVN == null) return ""; - return tortoiseSVN.GetValue("TMergePath") as string; - } - - /// - /// Find vsDiffMerge.exe. - /// - /// - public static string FindVSMerge() { - var dir = @"C:\Program Files (x86)\Microsoft Visual Studio"; - if (Directory.Exists($"{dir}\\2019")) { - dir += "\\2019"; - } else if (Directory.Exists($"{dir}\\2017")) { - dir += "\\2017"; - } else { - return ""; - } - - if (Directory.Exists($"{dir}\\Community")) { - dir += "\\Community"; - } else if (Directory.Exists($"{dir}\\Enterprise")) { - dir += "\\Enterprise"; - } else if (Directory.Exists($"{dir}\\Professional")) { - dir += "\\Professional"; - } else { - return ""; - } - - return $"{dir}\\Common7\\IDE\\CommonExtensions\\Microsoft\\TeamFoundation\\Team Explorer\\vsDiffMerge.exe"; - } - - /// - /// Find VSCode executable file path. - /// - /// - public static string FindVSCode() { - var root = RegistryKey.OpenBaseKey( - RegistryHive.LocalMachine, - Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32); - - var vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{C26E74D1-022E-4238-8B9D-1E7564A36CC9}_is1"); - if (vscode != null) { - return vscode.GetValue("DisplayIcon") as string; - } - - vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1287CAD5-7C8D-410D-88B9-0D1EE4A83FF2}_is1"); - if (vscode != null) { - return vscode.GetValue("DisplayIcon") as string; - } - - vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F8A2A208-72B3-4D61-95FC-8A65D340689B}_is1"); - if (vscode != null) { - return vscode.GetValue("DisplayIcon") as string; - } - - vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{EA457B21-F73E-494C-ACAB-524FDE069978}_is1"); - if (vscode != null) { - return vscode.GetValue("DisplayIcon") as string; - } - - return ""; - } - - /// - /// Constructor. - /// - /// - /// - /// - /// - public MergeTool(string name, string exe, string param, Func finder) { - Name = name; - ExecutableName = exe; - Parameter = param; - Finder = finder; - } - } -} diff --git a/SourceGit/Git/Preference.cs b/SourceGit/Git/Preference.cs deleted file mode 100644 index 092462fe..00000000 --- a/SourceGit/Git/Preference.cs +++ /dev/null @@ -1,300 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Xml.Serialization; - -namespace SourceGit.Git { - - /// - /// User's preference settings. Serialized to - /// - public class Preference { - - /// - /// Group(Virtual folder) for watched repositories. - /// - public class Group { - /// - /// Unique ID of this group. - /// - public string Id { get; set; } - /// - /// Display name. - /// - public string Name { get; set; } - /// - /// Parent ID. - /// - public string ParentId { get; set; } - /// - /// Cache UI IsExpended status. - /// - public bool IsExpended { get; set; } - } - - #region STATICS - /// - /// Storage path for Preference. - /// - private static readonly string SAVE_PATH = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "SourceGit", - "preference.xml"); - /// - /// Runtime singleton instance. - /// - private static Preference instance = null; - public static Preference Instance { - get { - if (instance == null) Load(); - return instance; - } - set { - instance = value; - } - } - #endregion - - #region SETTING_GIT - /// - /// Git executable file path. - /// - public string GitExecutable { get; set; } - /// - /// Default clone directory. - /// - public string GitDefaultCloneDir { get; set; } - #endregion - - #region SETTING_MERGE_TOOL - /// - /// Selected merge tool. - /// - public int MergeTool { get; set; } = 0; - /// - /// Executable file path for merge tool. - /// - public string MergeExecutable { get; set; } = "--"; - #endregion - - #region SETTING_UI - /// - /// Main window's width - /// - public double UIMainWindowWidth { get; set; } - /// - /// Main window's height - /// - public double UIMainWindowHeight { get; set; } - /// - /// Use light color theme. - /// - public bool UIUseLightTheme { get; set; } - /// - /// Show/Hide tags' list view. - /// - public bool UIShowTags { get; set; } = true; - /// - /// Use horizontal layout for histories. - /// - public bool UIUseHorizontalLayout { get; set; } - /// - /// Use list instead of tree in unstaged view - /// - public bool UIUseListInUnstaged { get; set; } - /// - /// Use list instead of tree in staged view. - /// - public bool UIUseListInStaged { get; set; } - /// - /// Use list instead of tree in change view. - /// - public bool UIUseListInChanges { get; set; } - #endregion - - #region SETTING_REPOS - /// - /// Groups for repositories. - /// - public List Groups { get; set; } = new List(); - /// - /// Watched repositories. - /// - public List Repositories { get; set; } = new List(); - #endregion - - #region METHODS_LOAD_SAVE - /// - /// Load preference from disk. - /// - /// Loaded preference instance. - public static void Load() { - if (!File.Exists(SAVE_PATH)) { - instance = new Preference(); - return; - } - - try { - var stream = new FileStream(SAVE_PATH, FileMode.Open); - var reader = new XmlSerializer(typeof(Preference)); - instance = (Preference)reader.Deserialize(stream); - stream.Close(); - } catch { - instance = new Preference(); - } - } - - /// - /// Save current preference into disk. - /// - public static void Save() { - if (instance == null) return; - - var dir = Path.GetDirectoryName(SAVE_PATH); - if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); - - var stream = new FileStream(SAVE_PATH, FileMode.Create); - var writer = new XmlSerializer(typeof(Preference)); - writer.Serialize(stream, instance); - stream.Flush(); - stream.Close(); - } - #endregion - - #region METHODS_ON_GROUP - /// - /// Add new group(virtual folder). - /// - /// Display name. - /// Parent group ID. - /// Added group instance. - public Group AddGroup(string name, string parentId) { - var group = new Group() { - Name = name, - Id = Guid.NewGuid().ToString(), - ParentId = parentId, - IsExpended = false, - }; - - Groups.Add(group); - Groups.Sort((l, r) => l.Name.CompareTo(r.Name)); - - return group; - } - - /// - /// Find group by ID. - /// - /// Unique ID - /// Founded group's instance. - public Group FindGroup(string id) { - foreach (var group in Groups) { - if (group.Id == id) return group; - } - return null; - } - - /// - /// Rename group. - /// - /// Unique ID - /// New name. - public void RenameGroup(string id, string newName) { - foreach (var group in Groups) { - if (group.Id == id) { - group.Name = newName; - break; - } - } - - Groups.Sort((l, r) => l.Name.CompareTo(r.Name)); - } - - /// - /// Remove a group. - /// - /// Unique ID - public void RemoveGroup(string id) { - int removedIdx = -1; - - for (int i = 0; i < Groups.Count; i++) { - if (Groups[i].Id == id) { - removedIdx = i; - break; - } - } - - if (removedIdx >= 0) Groups.RemoveAt(removedIdx); - } - #endregion - - #region METHODS_ON_REPOS - /// - /// Add repository. - /// - /// Local storage path. - /// Group's ID - /// Added repository instance. - public Repository AddRepository(string path, string groupId) { - var repo = FindRepository(path); - if (repo != null) return repo; - - var dir = new DirectoryInfo(path); - repo = new Repository() { - Path = dir.FullName, - Name = dir.Name, - GroupId = groupId, - LastOpenTime = 0, - }; - - Repositories.Add(repo); - Repositories.Sort((l, r) => l.Name.CompareTo(r.Name)); - return repo; - } - - /// - /// Find repository by path. - /// - /// Local storage path. - /// Founded repository instance. - public Repository FindRepository(string path) { - var dir = new DirectoryInfo(path); - foreach (var repo in Repositories) { - if (repo.Path == dir.FullName) return repo; - } - return null; - } - - /// - /// Change a repository's display name in RepositoryManager. - /// - /// Local storage path. - /// New name - public void RenameRepository(string path, string newName) { - var repo = FindRepository(path); - if (repo == null) return; - - repo.Name = newName; - Repositories.Sort((l, r) => l.Name.CompareTo(r.Name)); - } - - /// - /// Remove a repository in RepositoryManager. - /// - /// Local storage path. - public void RemoveRepository(string path) { - var dir = new DirectoryInfo(path); - var removedIdx = -1; - - for (int i = 0; i < Repositories.Count; i++) { - if (Repositories[i].Path == dir.FullName) { - removedIdx = i; - break; - } - } - - if (removedIdx >= 0) Repositories.RemoveAt(removedIdx); - } - #endregion - } -} diff --git a/SourceGit/Git/Remote.cs b/SourceGit/Git/Remote.cs deleted file mode 100644 index ed32e810..00000000 --- a/SourceGit/Git/Remote.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace SourceGit.Git { - - /// - /// Git remote - /// - public class Remote { - private static readonly Regex FORMAT = new Regex(@"^([\w\.\-]+)\s*(\S+).*$"); - - /// - /// Name of this remote - /// - public string Name { get; set; } - - /// - /// URL - /// - public string URL { get; set; } - - /// - /// Parsing remote - /// - /// Repository - /// - public static List Load(Repository repo) { - var remotes = new List(); - var added = new List(); - - repo.RunCommand("remote -v", data => { - var match = FORMAT.Match(data); - if (!match.Success) return; - - var remote = new Remote() { - Name = match.Groups[1].Value, - URL = match.Groups[2].Value, - }; - - if (added.Contains(remote.Name)) return; - - added.Add(remote.Name); - remotes.Add(remote); - }); - - return remotes; - } - - /// - /// Add new remote - /// - /// - /// - /// - public static void Add(Repository repo, string name, string url) { - var errs = repo.RunCommand($"remote add {name} {url}", null); - if (errs != null) { - App.RaiseError(errs); - } else { - repo.Fetch(new Remote() { Name = name }, true, null); - } - } - - /// - /// Delete remote. - /// - /// - /// - public static void Delete(Repository repo, string remote) { - var errs = repo.RunCommand($"remote remove {remote}", null); - if (errs != null) App.RaiseError(errs); - } - - /// - /// Edit remote. - /// - /// - /// - /// - public void Edit(Repository repo, string name, string url) { - string errs = null; - - if (name != Name) { - errs = repo.RunCommand($"remote rename {Name} {name}", null); - if (errs != null) { - App.RaiseError(errs); - return; - } - } - - if (url != URL) { - errs = repo.RunCommand($"remote set-url {name} {url}", null); - if (errs != null) App.RaiseError(errs); - } - } - } -} diff --git a/SourceGit/Git/Repository.cs b/SourceGit/Git/Repository.cs deleted file mode 100644 index 9efc74e3..00000000 --- a/SourceGit/Git/Repository.cs +++ /dev/null @@ -1,1163 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Windows.Threading; -using System.Xml.Serialization; - -namespace SourceGit.Git { - - /// - /// Git repository - /// - public class Repository { - - #region HOOKS - public static Action OnOpen = null; - [XmlIgnore] public Action OnNavigateCommit = null; - [XmlIgnore] public Action OnWorkingCopyChanged = null; - [XmlIgnore] public Action OnTagChanged = null; - [XmlIgnore] public Action OnStashChanged = null; - [XmlIgnore] public Action OnBranchChanged = null; - [XmlIgnore] public Action OnCommitsChanged = null; - [XmlIgnore] public Action OnSubmoduleChanged = null; - #endregion - - #region PROPERTIES_SAVED - /// - /// Storage path. - /// - public string Path { get; set; } - /// - /// Display name. - /// - public string Name { get; set; } - /// - /// Owner group. - /// - public string GroupId { get; set; } - /// - /// Last open time(File time format). - /// - public long LastOpenTime { get; set; } - /// - /// Filters for logs. - /// - public List LogFilters { get; set; } = new List(); - /// - /// Last 10 Commit message. - /// - public List CommitMsgRecords { get; set; } = new List(); - /// - /// Commit template. - /// - public string CommitTemplate { get; set; } - #endregion - - #region PROPERTIES_RUNTIME - [XmlIgnore] public Repository Parent = null; - [XmlIgnore] public string GitDir = null; - - private List cachedRemotes = new List(); - private List cachedBranches = new List(); - private List cachedTags = new List(); - private FileSystemWatcher gitDirWatcher = null; - private FileSystemWatcher workingCopyWatcher = null; - private DispatcherTimer timer = null; - private bool isWatcherDisabled = false; - private long nextUpdateTags = 0; - private long nextUpdateLocalChanges = 0; - private long nextUpdateStashes = 0; - private long nextUpdateTree = 0; - - private string featurePrefix = null; - private string releasePrefix = null; - private string hotfixPrefix = null; - #endregion - - #region METHOD_PROCESS - /// - /// Read git config - /// - /// - /// - public string GetConfig(string key) { - var startInfo = new ProcessStartInfo(); - startInfo.FileName = Preference.Instance.GitExecutable; - startInfo.Arguments = $"config {key}"; - startInfo.WorkingDirectory = Path; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.RedirectStandardOutput = true; - startInfo.StandardOutputEncoding = Encoding.UTF8; - - var proc = new Process() { StartInfo = startInfo }; - proc.Start(); - var output = proc.StandardOutput.ReadToEnd(); - proc.WaitForExit(); - proc.Close(); - - return output.Trim(); - } - - /// - /// Configure git. - /// - /// - /// - public void SetConfig(string key, string value) { - var startInfo = new ProcessStartInfo(); - startInfo.FileName = Preference.Instance.GitExecutable; - startInfo.Arguments = $"config {key} \"{value}\""; - startInfo.WorkingDirectory = Path; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - - var proc = new Process() { StartInfo = startInfo }; - proc.Start(); - proc.WaitForExit(); - proc.Close(); - } - - /// - /// Run git command without repository. - /// - /// Working directory. - /// Arguments for running git command. - /// Handler for output. - /// Handle error as output. - /// Errors if exists. - public static string RunCommand(string cwd, string args, Action outputHandler, bool includeError = false) { - var startInfo = new ProcessStartInfo(); - startInfo.FileName = Preference.Instance.GitExecutable; - startInfo.Arguments = "--no-pager -c core.quotepath=off " + args; - startInfo.WorkingDirectory = cwd; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - startInfo.StandardOutputEncoding = Encoding.UTF8; - startInfo.StandardErrorEncoding = Encoding.UTF8; - - var progressFilter = new Regex(@"\d+\%"); - var errs = new List(); - var proc = new Process() { StartInfo = startInfo }; - - proc.OutputDataReceived += (o, e) => { - if (e.Data == null) return; - outputHandler?.Invoke(e.Data); - }; - proc.ErrorDataReceived += (o, e) => { - if (e.Data == null) return; - if (includeError) outputHandler?.Invoke(e.Data); - if (string.IsNullOrEmpty(e.Data)) return; - if (progressFilter.IsMatch(e.Data)) return; - if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return; - errs.Add(e.Data); - }; - - proc.Start(); - proc.BeginOutputReadLine(); - proc.BeginErrorReadLine(); - proc.WaitForExit(); - - int exitCode = proc.ExitCode; - proc.Close(); - - if (exitCode != 0 && errs.Count > 0) { - return string.Join("\n", errs); - } else { - return null; - } - } - - /// - /// Create process for reading outputs/errors using git.exe - /// - /// Arguments for running git command. - /// Handler for output. - /// Handle error as output. - /// Errors if exists. - public string RunCommand(string args, Action outputHandler, bool includeError = false) { - return RunCommand(Path, args, outputHandler, includeError); - } - - /// - /// Create process and redirect output to file. - /// - /// Git command arguments. - /// File path to redirect output into. - public void RunAndRedirect(string args, string redirectTo) { - var startInfo = new ProcessStartInfo(); - startInfo.FileName = Preference.Instance.GitExecutable; - startInfo.Arguments = "--no-pager " + args; - startInfo.WorkingDirectory = Path; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - - var proc = new Process() { StartInfo = startInfo }; - proc.Start(); - - using (var writer = new FileStream(redirectTo, FileMode.OpenOrCreate)) { - proc.StandardOutput.BaseStream.CopyTo(writer); - } - - proc.WaitForExit(); - proc.Close(); - } - - /// - /// Assert command result and then update branches and commits. - /// - /// - public void AssertCommand(string err) { - if (!string.IsNullOrEmpty(err)) App.RaiseError(err); - - Branches(true); - OnBranchChanged?.Invoke(); - OnCommitsChanged?.Invoke(); - OnWorkingCopyChanged?.Invoke(); - OnTagChanged?.Invoke(); - - isWatcherDisabled = false; - } - #endregion - - #region METHOD_VALIDATIONS - /// - /// Is valid git directory. - /// - /// Local path. - /// - public static bool IsValid(string path) { - var startInfo = new ProcessStartInfo(); - startInfo.FileName = Preference.Instance.GitExecutable; - startInfo.Arguments = "rev-parse --git-dir"; - startInfo.WorkingDirectory = path; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - - try { - var proc = new Process() { StartInfo = startInfo }; - proc.Start(); - proc.WaitForExit(); - - var test = proc.ExitCode == 0; - proc.Close(); - return test; - } catch { - return false; - } - } - - /// - /// Is remote url valid. - /// - /// - /// - public static bool IsValidUrl(string url) { - return !string.IsNullOrEmpty(url) - && (url.StartsWith("http://", StringComparison.Ordinal) - || url.StartsWith("https://", StringComparison.Ordinal) - || url.StartsWith("git://", StringComparison.Ordinal) - || url.StartsWith("ssh://", StringComparison.Ordinal) - || url.StartsWith("file://", StringComparison.Ordinal)); - } - #endregion - - #region METHOD_OPEN_CLOSE - /// - /// Open repository. - /// - public void Open() { - LastOpenTime = DateTime.Now.ToFileTime(); - isWatcherDisabled = false; - - GitDir = ".git"; - RunCommand("rev-parse --git-dir", line => { - GitDir = line; - }); - if (!System.IO.Path.IsPathRooted(GitDir)) GitDir = System.IO.Path.Combine(Path, GitDir); - - var checkGitDir = new DirectoryInfo(GitDir); - if (!checkGitDir.Exists) { - App.RaiseError("GIT_DIR for this repository NOT FOUND!"); - return; - } else { - GitDir = checkGitDir.FullName; - } - - gitDirWatcher = new FileSystemWatcher(); - gitDirWatcher.Path = GitDir; - gitDirWatcher.Filter = "*"; - gitDirWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName; - gitDirWatcher.IncludeSubdirectories = true; - gitDirWatcher.Created += OnGitDirFSChanged; - gitDirWatcher.Renamed += OnGitDirFSChanged; - gitDirWatcher.Changed += OnGitDirFSChanged; - gitDirWatcher.Deleted += OnGitDirFSChanged; - gitDirWatcher.EnableRaisingEvents = true; - - workingCopyWatcher = new FileSystemWatcher(); - workingCopyWatcher.Path = Path; - workingCopyWatcher.Filter = "*"; - workingCopyWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName; - workingCopyWatcher.IncludeSubdirectories = true; - workingCopyWatcher.Created += OnWorkingCopyFSChanged; - workingCopyWatcher.Renamed += OnWorkingCopyFSChanged; - workingCopyWatcher.Changed += OnWorkingCopyFSChanged; - workingCopyWatcher.Deleted += OnWorkingCopyFSChanged; - workingCopyWatcher.EnableRaisingEvents = true; - - timer = new DispatcherTimer(); - timer.Tick += Tick; - timer.Interval = TimeSpan.FromSeconds(.1); - timer.Start(); - - featurePrefix = GetConfig("gitflow.prefix.feature"); - releasePrefix = GetConfig("gitflow.prefix.release"); - hotfixPrefix = GetConfig("gitflow.prefix.hotfix"); - - OnOpen?.Invoke(this); - } - - /// - /// Close repository. - /// - public void Close() { - OnBranchChanged = null; - OnCommitsChanged = null; - OnTagChanged = null; - OnStashChanged = null; - OnWorkingCopyChanged = null; - OnNavigateCommit = null; - OnSubmoduleChanged = null; - - cachedBranches.Clear(); - cachedRemotes.Clear(); - cachedTags.Clear(); - - gitDirWatcher.EnableRaisingEvents = false; - workingCopyWatcher.EnableRaisingEvents = false; - gitDirWatcher.Dispose(); - workingCopyWatcher.Dispose(); - timer.Stop(); - - gitDirWatcher = null; - workingCopyWatcher = null; - timer = null; - featurePrefix = null; - releasePrefix = null; - hotfixPrefix = null; - - GC.Collect(); - } - #endregion - - #region METHOD_WATCHER - public void SetWatcherEnabled(bool enabled) { - isWatcherDisabled = !enabled; - } - - private void Tick(object sender, EventArgs e) { - if (isWatcherDisabled) { - nextUpdateLocalChanges = 0; - nextUpdateStashes = 0; - nextUpdateTags = 0; - nextUpdateTree = 0; - return; - } - - var now = DateTime.Now.ToFileTime(); - if (nextUpdateLocalChanges > 0 && now >= nextUpdateLocalChanges) { - nextUpdateLocalChanges = 0; - OnWorkingCopyChanged?.Invoke(); - } - - if (nextUpdateTags > 0 && now >= nextUpdateTags) { - nextUpdateTags = 0; - OnTagChanged?.Invoke(); - } - - if (nextUpdateStashes > 0 && now >= nextUpdateStashes) { - nextUpdateStashes = 0; - OnStashChanged?.Invoke(); - } - - if (nextUpdateTree > 0 && now >= nextUpdateTree) { - nextUpdateTree = 0; - Branches(true); - OnBranchChanged?.Invoke(); - OnCommitsChanged?.Invoke(); - } - } - - private void OnGitDirFSChanged(object sender, FileSystemEventArgs e) { - if (string.IsNullOrEmpty(e.Name)) return; - if (e.Name.StartsWith("index")) return; - - if (e.Name.StartsWith("refs\\tags", StringComparison.Ordinal)) { - nextUpdateTags = DateTime.Now.AddSeconds(.5).ToFileTime(); - } else if (e.Name.StartsWith("refs\\stash", StringComparison.Ordinal)) { - nextUpdateStashes = DateTime.Now.AddSeconds(.5).ToFileTime(); - } else if (e.Name.EndsWith("_HEAD", StringComparison.Ordinal) || - e.Name.StartsWith("refs\\heads", StringComparison.Ordinal) || - e.Name.StartsWith("refs\\remotes", StringComparison.Ordinal)) { - nextUpdateTree = DateTime.Now.AddSeconds(.5).ToFileTime(); - } - } - - private void OnWorkingCopyFSChanged(object sender, FileSystemEventArgs e) { - if (string.IsNullOrEmpty(e.Name)) return; - if (e.Name == ".git" || e.Name.StartsWith(".git\\")) return; - - nextUpdateLocalChanges = DateTime.Now.AddSeconds(1.5).ToFileTime(); - } - #endregion - - #region METHOD_GITCOMMANDS - /// - /// Clone repository. - /// - /// Remote repository URL - /// Folder to clone into - /// Local name - /// - /// - public static Repository Clone(string url, string folder, string rName, string lName, Action onProgress) { - string RemoteName; - if (rName != null) { - RemoteName = $" --origin {rName}"; - } else { - RemoteName = null; - } - - var errs = RunCommand(folder, $"-c credential.helper=manager clone --progress --verbose {RemoteName} --recurse-submodules {url} {lName}", line => { - if (line != null) onProgress?.Invoke(line); - }, true); - - if (errs != null) { - App.RaiseError(errs); - return null; - } - - var path = new DirectoryInfo(folder + "/" + lName).FullName; - var repo = Preference.Instance.AddRepository(path, ""); - return repo; - } - - /// - /// Fetch remote changes - /// - /// - /// - /// - public void Fetch(Remote remote, bool prune, Action onProgress) { - isWatcherDisabled = true; - - var args = "-c credential.helper=manager fetch --progress --verbose "; - - if (prune) args += "--prune "; - - if (remote == null) { - args += "--all"; - } else { - args += remote.Name; - } - - var errs = RunCommand(args, line => { - if (line != null) onProgress?.Invoke(line); - }, true); - - OnSubmoduleChanged?.Invoke(); - - AssertCommand(errs); - } - - /// - /// Pull remote changes. - /// - /// remote - /// branch - /// Progress message handler. - /// Use rebase instead of merge. - /// Auto stash local changes. - /// Progress message handler. - public void Pull(string remote, string branch, Action onProgress, bool rebase = false, bool autostash = false) { - isWatcherDisabled = true; - - var args = "-c credential.helper=manager pull --verbose --progress "; - var needPopStash = false; - - if (rebase) args += "--rebase "; - if (autostash) { - if (rebase) { - args += "--autostash "; - } else { - var changes = LocalChanges(); - if (changes.Count > 0) { - var fatal = RunCommand("stash push -u -m \"PULL_AUTO_STASH\"", null); - if (fatal != null) { - App.RaiseError(fatal); - isWatcherDisabled = false; - return; - } - needPopStash = true; - } - } - } - - var errs = RunCommand(args + remote + " " + branch, line => { - if (line != null) onProgress?.Invoke(line); - }, true); - - OnSubmoduleChanged?.Invoke(); - - AssertCommand(errs); - - if (needPopStash) RunCommand("stash pop -q stash@{0}", null); - } - - /// - /// Push local branch to remote. - /// - /// Remote - /// Local branch name - /// Remote branch name - /// Progress message handler. - /// Push tags - /// Create track reference - /// Force push - public void Push(string remote, string localBranch, string remoteBranch, Action onProgress, bool withTags = false, bool track = false, bool force = false) { - isWatcherDisabled = true; - - var args = "-c credential.helper=manager push --progress --verbose "; - - if (withTags) args += "--tags "; - if (track) args += "-u "; - if (force) args += "--force-with-lease "; - - var errs = RunCommand(args + remote + " " + localBranch + ":" + remoteBranch, line => { - if (line != null) onProgress?.Invoke(line); - }, true); - - AssertCommand(errs); - } - - /// - /// Apply patch. - /// - /// - /// - /// - public void Apply(string patch, bool ignoreSpaceChanges, string whitespaceMode) { - isWatcherDisabled = true; - - var args = "apply "; - if (ignoreSpaceChanges) args += "--ignore-whitespace "; - else args += $"--whitespace={whitespaceMode} "; - - var errs = RunCommand($"{args} \"{patch}\"", null); - if (errs != null) { - App.RaiseError(errs); - } else { - OnWorkingCopyChanged?.Invoke(); - } - - isWatcherDisabled = false; - } - - /// - /// Revert given commit. - /// - /// - /// - public void Revert(string commit, bool autoCommit) { - isWatcherDisabled = true; - - var errs = RunCommand($"revert {commit} --no-edit" + (autoCommit ? "" : " --no-commit"), null); - AssertCommand(errs); - } - - /// - /// Checkout - /// - /// Options. - public void Checkout(string option) { - isWatcherDisabled = true; - - var errs = RunCommand($"checkout {option}", null); - AssertCommand(errs); - } - - /// - /// Merge given branch into current. - /// - /// - /// - public void Merge(string branch, string option) { - isWatcherDisabled = true; - - var errs = RunCommand($"merge {branch} {option}", null); - AssertCommand(errs); - } - - /// - /// Rebase current branch to revision - /// - /// - /// - public void Rebase(string revision, bool autoStash) { - isWatcherDisabled = true; - - var args = $"rebase "; - if (autoStash) args += "--autostash "; - args += revision; - - var errs = RunCommand(args, null); - AssertCommand(errs); - } - - /// - /// Reset. - /// - /// - /// - public void Reset(string revision, string mode = "") { - isWatcherDisabled = true; - - var errs = RunCommand($"reset {mode} {revision}", null); - AssertCommand(errs); - } - - /// - /// Cherry pick commit. - /// - /// - /// - public void CherryPick(string commit, bool noCommit) { - isWatcherDisabled = true; - - var args = "cherry-pick "; - args += noCommit ? "-n " : "--ff "; - args += commit; - - var errs = RunCommand(args, null); - AssertCommand(errs); - } - - /// - /// Stage(add) files to index. - /// - /// - public void Stage(params string[] files) { - isWatcherDisabled = true; - - var args = "add"; - if (files == null || files.Length == 0) { - args += " ."; - } else { - args += " --"; - foreach (var file in files) args += $" \"{file}\""; - } - - var errs = RunCommand(args, null); - if (errs != null) App.RaiseError(errs); - - OnWorkingCopyChanged?.Invoke(); - isWatcherDisabled = false; - } - - /// - /// Unstage files from index - /// - /// - public void Unstage(params string[] files) { - isWatcherDisabled = true; - - var args = "reset"; - if (files != null && files.Length > 0) { - args += " --"; - foreach (var file in files) args += $" \"{file}\""; - } - - var errs = RunCommand(args, null); - if (errs != null) App.RaiseError(errs); - - OnWorkingCopyChanged?.Invoke(); - isWatcherDisabled = false; - } - - /// - /// Discard changes. - /// - /// - public void Discard(List changes) { - isWatcherDisabled = true; - - if (changes == null || changes.Count == 0) { - var errs = RunCommand("reset --hard HEAD", null); - if (errs != null) { - App.RaiseError(errs); - isWatcherDisabled = false; - return; - } - - RunCommand("clean -qfd", null); - } else { - foreach (var change in changes) { - if (change.WorkTree == Change.Status.Untracked || change.WorkTree == Change.Status.Added) { - RunCommand($"clean -qfd -- \"{change.Path}\"", null); - } else { - RunCommand($"checkout -f -- \"{change.Path}\"", null); - } - } - } - - OnWorkingCopyChanged?.Invoke(); - isWatcherDisabled = false; - } - - /// - /// Commit - /// - /// - /// - public bool DoCommit(string message, bool amend) { - isWatcherDisabled = true; - - var file = System.IO.Path.GetTempFileName(); - File.WriteAllText(file, message); - - var args = $"commit --file=\"{file}\""; - if (amend) args += " --amend --no-edit"; - var errs = RunCommand(args, null); - AssertCommand(errs); - - var branch = CurrentBranch(); - OnNavigateCommit?.Invoke(branch.Head); - return string.IsNullOrEmpty(errs); - } - - /// - /// Get all remotes of this repository. - /// - /// Force reload - /// Remote collection - public List Remotes(bool bForceReload = false) { - if (cachedRemotes.Count == 0 || bForceReload) { - cachedRemotes = Remote.Load(this); - } - - return cachedRemotes; - } - - /// - /// Local changes in working copy. - /// - /// Changes. - public List LocalChanges() { - List changes = new List(); - RunCommand("status -uall --ignore-submodules=dirty --porcelain", line => { - if (!string.IsNullOrEmpty(line)) { - var change = Change.Parse(line); - if (change != null) changes.Add(change); - } - }); - return changes; - } - - /// - /// Get total commit count. - /// - /// Number of total commits. - public int TotalCommits() { - int count = 0; - RunCommand("rev-list --all --count", line => { - if (!string.IsNullOrEmpty(line)) count = int.Parse(line.Trim()); - }); - return count; - } - - /// - /// Load commits. - /// - /// Extra limit arguments for `git log` - /// Commit collection - public List Commits(string limit = null) { - return Commit.Load(this, (limit == null ? "" : limit)); ; - } - - /// - /// Load all branches. - /// - /// Force reload. - /// Branches collection. - public List Branches(bool bForceReload = false) { - if (cachedBranches.Count == 0 || bForceReload) { - cachedBranches = Branch.Load(this); - } - - if (IsGitFlowEnabled()) { - foreach (var b in cachedBranches) { - if (b.IsLocal) { - if (b.Name.StartsWith(featurePrefix)) { - b.Kind = Branch.Type.Feature; - } else if (b.Name.StartsWith(releasePrefix)) { - b.Kind = Branch.Type.Release; - } else if (b.Name.StartsWith(hotfixPrefix)) { - b.Kind = Branch.Type.Hotfix; - } - } - } - } - - return cachedBranches; - } - - /// - /// Get current branch - /// - /// - public Branch CurrentBranch() { - foreach (var b in cachedBranches) { - if (b.IsCurrent) return b; - } - - return null; - } - - /// - /// Load all tags. - /// - /// - /// - public List Tags(bool bForceReload = false) { - if (cachedTags.Count == 0 || bForceReload) { - cachedTags = Tag.Load(this); - } - - return cachedTags; - } - - /// - /// Get all stashes - /// - /// - public List Stashes() { - var reflog = new Regex(@"^Reflog: refs/(stash@\{\d+\}).*$"); - var stashes = new List(); - var current = null as Stash; - - var errs = RunCommand("stash list --pretty=raw", line => { - if (line.StartsWith("commit ")) { - if (current != null && !string.IsNullOrEmpty(current.Name)) stashes.Add(current); - current = new Stash() { SHA = line.Substring(7, 8) }; - return; - } - - if (current == null) return; - - if (line.StartsWith("Reflog: refs/stash@")) { - var match = reflog.Match(line); - if (match.Success) current.Name = match.Groups[1].Value; - } else if (line.StartsWith("Reflog message: ")) { - current.Message = line.Substring(16); - } else if (line.StartsWith("author ")) { - current.Author.Parse(line); - } - }); - - if (current != null) stashes.Add(current); - if (errs != null) App.RaiseError(errs); - return stashes; - } - - /// - /// Get all submodules - /// - /// - public List Submodules() { - var test = new Regex(@"^[\-\+ ][0-9a-f]+\s(.*)\(.*\)$"); - var modules = new List(); - - var errs = RunCommand("submodule status", line => { - var match = test.Match(line); - if (!match.Success) return; - - modules.Add(match.Groups[1].Value); - }); - - return modules; - } - - /// - /// Add submodule - /// - /// - /// - /// - /// - public void AddSubmodule(string url, string localPath, bool recursive, Action onProgress) { - isWatcherDisabled = true; - - var errs = RunCommand($"submodule add {url} {localPath}", onProgress, true); - if (errs == null) { - if (recursive) RunCommand($"submodule update --init --recursive -- {localPath}", onProgress, true); - OnWorkingCopyChanged?.Invoke(); - OnSubmoduleChanged?.Invoke(); - } else { - App.RaiseError(errs); - } - - isWatcherDisabled = false; - } - - /// - /// Update submodule. - /// - public void UpdateSubmodule() { - isWatcherDisabled = true; - - var errs = RunCommand("submodule update --rebase --remote", null); - if (errs != null) { - App.RaiseError(errs); - } else { - OnSubmoduleChanged?.Invoke(); - } - - isWatcherDisabled = false; - } - - /// - /// Blame file. - /// - /// - /// - /// - public Blame BlameFile(string file, string revision) { - var regex = new Regex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)"); - var blame = new Blame(); - var current = null as Blame.Block; - - var errs = RunCommand($"blame -t {revision} -- \"{file}\"", line => { - if (blame.IsBinary) return; - if (string.IsNullOrEmpty(line)) return; - - if (line.IndexOf('\0') >= 0) { - blame.IsBinary = true; - blame.Blocks.Clear(); - return; - } - - var match = regex.Match(line); - if (!match.Success) return; - - var commit = match.Groups[1].Value; - var data = match.Groups[4].Value; - if (current != null && current.CommitSHA == commit) { - current.Content = current.Content + "\n" + data; - } else { - var timestamp = int.Parse(match.Groups[3].Value); - var when = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); - - current = new Blame.Block() { - CommitSHA = commit, - Author = match.Groups[2].Value, - Time = when, - Content = data, - }; - - if (current.Author == null) current.Author = ""; - blame.Blocks.Add(current); - } - - blame.LineCount++; - }); - - if (errs != null) App.RaiseError(errs); - return blame; - } - - /// - /// Get file size. - /// - /// - /// - /// - public long GetFileSize(string sha, string path) { - long size = 0; - RunCommand($"cat-file -s {sha}:\"{path}\"", line => { - if (!long.TryParse(line, out size)) size = 0; - }); - return size; - } - #endregion - - #region METHOD_GITFLOW - /// - /// Check if git-flow feature enabled - /// - /// - public bool IsGitFlowEnabled() { - return !string.IsNullOrEmpty(featurePrefix) - && !string.IsNullOrEmpty(releasePrefix) - && !string.IsNullOrEmpty(hotfixPrefix); - } - - /// - /// Get git-flow branch prefix. - /// - /// - public string GetFeaturePrefix() { return featurePrefix; } - public string GetReleasePrefix() { return releasePrefix; } - public string GetHotfixPrefix() { return hotfixPrefix; } - - /// - /// Enable git-flow - /// - /// - /// - /// - /// - /// - /// - public void EnableGitFlow(string master, string develop, string feature, string release, string hotfix, string version = "") { - isWatcherDisabled = true; - - var branches = Branches(); - var masterBranch = branches.Find(b => b.Name == master); - var devBranch = branches.Find(b => b.Name == develop); - var refreshBranches = false; - - if (masterBranch == null) { - var errs = RunCommand($"branch --no-track {master}", null); - if (errs != null) { - App.RaiseError(errs); - isWatcherDisabled = false; - return; - } - - refreshBranches = true; - } - - if (devBranch == null) { - var errs = RunCommand($"branch --no-track {develop}", null); - if (errs != null) { - App.RaiseError(errs); - if (refreshBranches) { - Branches(true); - OnBranchChanged?.Invoke(); - OnCommitsChanged?.Invoke(); - OnWorkingCopyChanged?.Invoke(); - } - isWatcherDisabled = false; - return; - } - - refreshBranches = true; - } - - SetConfig("gitflow.branch.master", master); - SetConfig("gitflow.branch.develop", develop); - SetConfig("gitflow.prefix.feature", feature); - SetConfig("gitflow.prefix.bugfix", "bugfix"); - SetConfig("gitflow.prefix.release", release); - SetConfig("gitflow.prefix.hotfix", hotfix); - SetConfig("gitflow.prefix.support", "support"); - SetConfig("gitflow.prefix.versiontag", version); - - RunCommand("flow init -d", null); - - featurePrefix = GetConfig("gitflow.prefix.feature"); - releasePrefix = GetConfig("gitflow.prefix.release"); - hotfixPrefix = GetConfig("gitflow.prefix.hotfix"); - - if (!IsGitFlowEnabled()) App.RaiseError("Initialize Git-flow failed!"); - - if (refreshBranches) { - Branches(true); - OnBranchChanged?.Invoke(); - OnCommitsChanged?.Invoke(); - OnWorkingCopyChanged?.Invoke(); - } - - isWatcherDisabled = false; - } - - /// - /// Start git-flow branch - /// - /// - /// - public void StartGitFlowBranch(Branch.Type type, string name) { - isWatcherDisabled = true; - - string args; - switch (type) { - case Branch.Type.Feature: args = $"flow feature start {name}"; break; - case Branch.Type.Release: args = $"flow release start {name}"; break; - case Branch.Type.Hotfix: args = $"flow hotfix start {name}"; break; - default: - App.RaiseError("Bad git-flow branch type!"); - return; - } - - var errs = RunCommand(args, null); - AssertCommand(errs); - } - - /// - /// Finish git-flow branch - /// - /// - public void FinishGitFlowBranch(Branch branch) { - isWatcherDisabled = true; - - string args; - switch (branch.Kind) { - case Branch.Type.Feature: - args = $"flow feature finish {branch.Name.Substring(featurePrefix.Length)}"; - break; - case Branch.Type.Release: - var releaseName = branch.Name.Substring(releasePrefix.Length); - args = $"flow release finish {releaseName} -m \"Release done\""; - break; - case Branch.Type.Hotfix: - var hotfixName = branch.Name.Substring(hotfixPrefix.Length); - args = $"flow hotfix finish {hotfixName} -m \"Hotfix done\""; - break; - default: - App.RaiseError("Bad git-flow branch type!"); - return; - } - - var errs = RunCommand(args, null); - AssertCommand(errs); - OnTagChanged?.Invoke(); - } - #endregion - - #region METHOD_COMMITMSG - public void RecordCommitMessage(string message) { - if (string.IsNullOrEmpty(message)) return; - - int exists = CommitMsgRecords.Count; - if (exists > 0) { - var last = CommitMsgRecords[0]; - if (last == message) return; - } - - if (exists >= 10) { - CommitMsgRecords.RemoveRange(9, exists - 9); - } - - CommitMsgRecords.Insert(0, message); - } - #endregion - } -} diff --git a/SourceGit/Git/Stash.cs b/SourceGit/Git/Stash.cs deleted file mode 100644 index d83f85f0..00000000 --- a/SourceGit/Git/Stash.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SourceGit.Git { - - /// - /// Git stash - /// - public class Stash { - - /// - /// SHA for this stash - /// - public string SHA { get; set; } - - /// - /// Name - /// - public string Name { get; set; } - - /// - /// Author - /// - public User Author { get; set; } = new User(); - - /// - /// Message - /// - public string Message { get; set; } - - /// - /// Stash push. - /// - /// - /// - /// - /// - public static void Push(Repository repo, bool includeUntracked, string message, List files) { - string specialFiles = ""; - - if (files.Count > 0) { - specialFiles = " --"; - foreach (var f in files) specialFiles += $" \"{f}\""; - } - - string args = "stash push "; - if (includeUntracked) args += "-u "; - if (!string.IsNullOrEmpty(message)) args += $"-m \"{message}\" "; - - var errs = repo.RunCommand(args + specialFiles, null); - if (errs != null) App.RaiseError(errs); - } - - /// - /// Get changed file list in this stash. - /// - /// - /// - public List GetChanges(Repository repo) { - List changes = new List(); - - var errs = repo.RunCommand($"diff --name-status --pretty=format: {SHA}^ {SHA}", line => { - var change = Change.Parse(line); - if (change != null) changes.Add(change); - }); - - if (errs != null) App.RaiseError(errs); - return changes; - } - - /// - /// Apply stash. - /// - /// - public void Apply(Repository repo) { - var errs = repo.RunCommand($"stash apply -q {Name}", null); - if (errs != null) App.RaiseError(errs); - } - - /// - /// Pop stash - /// - /// - public void Pop(Repository repo) { - var errs = repo.RunCommand($"stash pop -q {Name}", null); - if (errs != null) App.RaiseError(errs); - } - - /// - /// Drop stash - /// - /// - public void Drop(Repository repo) { - var errs = repo.RunCommand($"stash drop -q {Name}", null); - if (errs != null) App.RaiseError(errs); - } - } -} diff --git a/SourceGit/Git/Tag.cs b/SourceGit/Git/Tag.cs deleted file mode 100644 index f329c4d4..00000000 --- a/SourceGit/Git/Tag.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; - -namespace SourceGit.Git { - - /// - /// Git tag. - /// - public class Tag { - private static readonly Regex FORMAT = new Regex(@"\$(.*)\$(.*)\$(.*)"); - - /// - /// SHA - /// - public string SHA { get; set; } - - /// - /// Display name. - /// - public string Name { get; set; } - - /// - /// Enable filter in log histories. - /// - public bool IsFiltered { get; set; } - - /// - /// Load all tags - /// - /// - /// - public static List Load(Repository repo) { - var args = "for-each-ref --sort=-creatordate --format=\"$%(refname:short)$%(objectname)$%(*objectname)\" refs/tags"; - var tags = new List(); - - repo.RunCommand(args, line => { - var match = FORMAT.Match(line); - if (!match.Success) return; - - var name = match.Groups[1].Value; - var commit = match.Groups[2].Value; - var dereference = match.Groups[3].Value; - - if (string.IsNullOrEmpty(dereference)) { - tags.Add(new Tag() { - Name = name, - SHA = commit, - }); - } else { - tags.Add(new Tag() { - Name = name, - SHA = dereference, - }); - } - }); - - return tags; - } - - /// - /// Add new tag. - /// - /// - /// - /// - /// - public static void Add(Repository repo, string name, string startPoint, string message) { - var args = $"tag -a {name} {startPoint} "; - - if (!string.IsNullOrEmpty(message)) { - string temp = Path.GetTempFileName(); - File.WriteAllText(temp, message); - args += $"-F \"{temp}\""; - } else { - args += $"-m {name}"; - } - - var errs = repo.RunCommand(args, null); - if (errs != null) App.RaiseError(errs); - else repo.OnCommitsChanged?.Invoke(); - } - - /// - /// Delete tag. - /// - /// - /// - /// - public static void Delete(Repository repo, string name, bool push) { - var errs = repo.RunCommand($"tag --delete {name}", null); - if (errs != null) { - App.RaiseError(errs); - return; - } - - if (push) { - var remotes = repo.Remotes(); - foreach (var r in remotes) { - repo.RunCommand($"-c credential.helper=manager push --delete {r.Name} refs/tags/{name}", null); - } - } - - repo.OnCommitsChanged?.Invoke(); - } - - /// - /// Push tag to remote. - /// - /// - /// - /// - public static void Push(Repository repo, string name, string remote) { - var errs = repo.RunCommand($"-c credential.helper=manager push {remote} refs/tags/{name}", null); - if (errs != null) App.RaiseError(errs); - } - } -} diff --git a/SourceGit/Git/User.cs b/SourceGit/Git/User.cs deleted file mode 100644 index 0e1b4120..00000000 --- a/SourceGit/Git/User.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Text.RegularExpressions; - -namespace SourceGit.Git { - - /// - /// Git user. - /// - public class User { - private static readonly Regex FORMAT = new Regex(@"\w+ (.*) <([\w\.\-_]+@[\w\.\-_]+)> (\d{10}) [\+\-]\d+"); - - /// - /// Name. - /// - public string Name { get; set; } = ""; - - /// - /// Email. - /// - public string Email { get; set; } = ""; - - /// - /// Operation time. - /// - public string Time { get; set; } = ""; - - /// - /// Parse user from raw string. - /// - /// Raw string - public void Parse(string data) { - var match = FORMAT.Match(data); - if (!match.Success) return; - - var time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(int.Parse(match.Groups[3].Value)); - - Name = match.Groups[1].Value; - Email = match.Groups[2].Value; - Time = time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); - } - } -} diff --git a/SourceGit/Helpers/CommitGraph.cs b/SourceGit/Helpers/CommitGraph.cs deleted file mode 100644 index e2294cab..00000000 --- a/SourceGit/Helpers/CommitGraph.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Media; - -namespace SourceGit.Helpers { - - /// - /// Tools to parse commit graph. - /// - public class CommitGraphMaker { - /// - /// Sizes - /// - public static readonly double UNIT_WIDTH = 12; - public static readonly double HALF_WIDTH = 6; - public static readonly double DOUBLE_WIDTH = 24; - public static readonly double UNIT_HEIGHT = 24; - public static readonly double HALF_HEIGHT = 12; - - /// - /// Colors - /// - public static Brush[] Colors = new Brush[] { - Brushes.Orange, - Brushes.ForestGreen, - Brushes.Gold, - Brushes.Magenta, - Brushes.Red, - Brushes.Gray, - Brushes.Turquoise, - Brushes.Olive, - }; - - /// - /// Helpers to draw lines. - /// - public class LineHelper { - private double lastX = 0; - private double lastY = 0; - - /// - /// Parent commit id. - /// - public string Next { get; set; } - - /// - /// Is merged into this tree. - /// - public bool IsMerged { get; set; } - - /// - /// Points in line - /// - public List Points { get; set; } - - /// - /// Brush to draw line - /// - public Brush Brush { get; set; } - - /// - /// Current horizontal offset. - /// - public double HorizontalOffset => lastX; - - /// - /// Constructor. - /// - /// Parent commit id - /// Is merged in tree - /// Color index - /// Start point - public LineHelper(string nextCommitId, bool isMerged, int colorIdx, Point startPoint) { - Next = nextCommitId; - IsMerged = isMerged; - Points = new List() { startPoint }; - Brush = Colors[colorIdx % Colors.Length]; - - lastX = startPoint.X; - lastY = startPoint.Y; - } - - /// - /// Line to. - /// - /// - /// - /// - public void AddPoint(double x, double y, bool isEnd = false) { - if (x > lastX) { - Points.Add(new Point(lastX, lastY)); - Points.Add(new Point(x, y - HALF_HEIGHT)); - } else if (x < lastX) { - Points.Add(new Point(lastX, lastY + HALF_HEIGHT)); - Points.Add(new Point(x, y)); - } - - lastX = x; - lastY = y; - - if (isEnd) { - var last = Points.Last(); - if (last.X != lastX || last.Y != lastY) Points.Add(new Point(lastX, lastY)); - } - } - } - - /// - /// Short link between two commits. - /// - public struct ShortLink { - public Point Start; - public Point Control; - public Point End; - public Brush Brush; - } - - /// - /// Dot - /// - public struct Dot { - public double X; - public double Y; - public Brush Color; - } - - /// - /// Independent lines in graph - /// - public List Lines { get; set; } = new List(); - - /// - /// Short links. - /// - public List Links { get; set; } = new List(); - - /// - /// All dots. - /// - public List Dots { get; set; } = new List(); - - /// - /// Highlight commit id. - /// - public string Highlight { get; set; } - - /// - /// Parse commits. - /// - /// - /// - public static CommitGraphMaker Parse(List commits) { - CommitGraphMaker maker = new CommitGraphMaker(); - - List unsolved = new List(); - List ended = new List(); - Dictionary currentMap = new Dictionary(); - double offsetY = -HALF_HEIGHT; - int colorIdx = 0; - - for (int i = 0; i < commits.Count; i++) { - Git.Commit commit = commits[i]; - LineHelper major = null; - bool isMerged = commit.IsHEAD || commit.IsMerged; - int oldCount = unsolved.Count; - - // 更新Y坐标 - offsetY += UNIT_HEIGHT; - - // 找到当前的分支的HEAD,用于默认选中 - if (maker.Highlight == null && commit.IsHEAD) { - maker.Highlight = commit.SHA; - } - - // 找到第一个依赖于本提交的树,将其他依赖于本提交的树标记为终止,并对已存在的线路调整(防止线重合) - double offsetX = -HALF_WIDTH; - foreach (var l in unsolved) { - if (l.Next == commit.SHA) { - if (major == null) { - offsetX += UNIT_WIDTH; - major = l; - - if (commit.Parents.Count > 0) { - major.Next = commit.Parents[0]; - if (!currentMap.ContainsKey(major.Next)) currentMap.Add(major.Next, major); - } else { - major.Next = "ENDED"; - ended.Add(l); - } - - major.AddPoint(offsetX, offsetY); - } else { - ended.Add(l); - } - - isMerged = isMerged || l.IsMerged; - } else { - if (!currentMap.ContainsKey(l.Next)) currentMap.Add(l.Next, l); - offsetX += UNIT_WIDTH; - l.AddPoint(offsetX, offsetY); - } - } - - // 处理本提交为非当前分支HEAD的情况(创建新依赖线路) - if (major == null && commit.Parents.Count > 0) { - offsetX += UNIT_WIDTH; - major = new LineHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY)); - unsolved.Add(major); - colorIdx++; - } - - // 确定本提交的点的位置 - Point position = new Point(offsetX, offsetY); - if (major != null) { - major.IsMerged = isMerged; - position.X = major.HorizontalOffset; - position.Y = offsetY; - maker.Dots.Add(new Dot() { X = position.X - 3, Y = position.Y - 3, Color = major.Brush }); - } else { - maker.Dots.Add(new Dot() { X = position.X - 3, Y = position.Y - 3, Color = Brushes.Orange }); - } - - // 处理本提交的其他依赖 - for (int j = 1; j < commit.Parents.Count; j++) { - var parent = commit.Parents[j]; - if (currentMap.ContainsKey(parent)) { - var l = currentMap[parent]; - var link = new ShortLink(); - - link.Start = position; - link.End = new Point(l.HorizontalOffset, offsetY + HALF_HEIGHT); - link.Control = new Point(link.End.X, link.Start.Y); - link.Brush = l.Brush; - maker.Links.Add(link); - } else { - offsetX += UNIT_WIDTH; - unsolved.Add(new LineHelper(commit.Parents[j], isMerged, colorIdx, position)); - colorIdx++; - } - } - - // 处理已终止的线 - foreach (var l in ended) { - l.AddPoint(position.X, position.Y, true); - maker.Lines.Add(l); - unsolved.Remove(l); - } - - // 加入本次提交 - commit.IsMerged = isMerged; - commit.GraphOffset = System.Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH); - - // 清理临时数据 - ended.Clear(); - currentMap.Clear(); - } - - // 处理尚未终结的线 - for (int i = 0; i < unsolved.Count; i++) { - var path = unsolved[i]; - path.AddPoint((i + 0.5) * UNIT_WIDTH, (commits.Count - 0.5) * UNIT_HEIGHT, true); - maker.Lines.Add(path); - } - unsolved.Clear(); - - // 处理默认选中异常 - if (maker.Highlight == null && commits.Count > 0) { - maker.Highlight = commits[0].SHA; - } - - return maker; - } - } -} diff --git a/SourceGit/Helpers/TextBoxHelper.cs b/SourceGit/Helpers/TextBoxHelper.cs deleted file mode 100644 index 9e28c20f..00000000 --- a/SourceGit/Helpers/TextBoxHelper.cs +++ /dev/null @@ -1,224 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; - -namespace SourceGit.Helpers { - - /// - /// Attached properties to TextBox. - /// - public static class TextBoxHelper { - - /// - /// Auto scroll on text changed or selection changed. - /// - public static readonly DependencyProperty AutoScrollProperty = DependencyProperty.RegisterAttached( - "AutoScroll", - typeof(bool), - typeof(TextBoxHelper), - new PropertyMetadata(false, OnAutoScrollChanged)); - - /// - /// Placeholder property - /// - public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached( - "Placeholder", - typeof(string), - typeof(TextBoxHelper), - new PropertyMetadata(string.Empty, OnPlaceholderChanged)); - - /// - /// Vertical alignment for placeholder. - /// - public static readonly DependencyProperty PlaceholderBaselineProperty = DependencyProperty.RegisterAttached( - "PlaceholderBaseline", - typeof(AlignmentY), - typeof(TextBoxHelper), - new PropertyMetadata(AlignmentY.Center)); - - /// - /// Property to store generated placeholder brush. - /// - public static readonly DependencyProperty PlaceholderBrushProperty = DependencyProperty.RegisterAttached( - "PlaceholderBrush", - typeof(Brush), - typeof(TextBoxHelper), - new PropertyMetadata(Brushes.Transparent)); - - /// - /// Setter for AutoScrollProperty - /// - /// - /// - public static void SetAutoScroll(UIElement element, bool enabled) { - element.SetValue(AutoScrollProperty, enabled); - } - - /// - /// Getter for AutoScrollProperty - /// - /// - /// - public static bool GetAutoScroll(UIElement element) { - return (bool)element.GetValue(AutoScrollProperty); - } - - /// - /// Triggered when AutoScroll property changed. - /// - /// - /// - public static void OnAutoScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - var textBox = d as TextBox; - if (textBox == null) return; - - textBox.SelectionChanged -= UpdateScrollOnSelectionChanged; - if ((bool)e.NewValue == true) { - textBox.SelectionChanged += UpdateScrollOnSelectionChanged; - } - } - - /// - /// Triggered when placeholder changed. - /// - /// - /// - private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - var textBox = d as TextBox; - if (textBox != null) textBox.Loaded += OnTextLoaded; - } - - /// - /// Setter for Placeholder property - /// - /// - /// - public static void SetPlaceholder(UIElement element, string value) { - element.SetValue(PlaceholderProperty, value); - } - - /// - /// Getter for Placeholder property - /// - /// - /// - public static string GetPlaceholder(UIElement element) { - return (string)element.GetValue(PlaceholderProperty); - } - - /// - /// Setter for PlaceholderBaseline property - /// - /// - /// - public static void SetPlaceholderBaseline(UIElement element, AlignmentY align) { - element.SetValue(PlaceholderBaselineProperty, align); - } - - /// - /// Setter for PlaceholderBaseline property. - /// - /// - /// - public static AlignmentY GetPlaceholderBaseline(UIElement element) { - return (AlignmentY)element.GetValue(PlaceholderBaselineProperty); - } - - /// - /// Setter for PlaceholderBrush property. - /// - /// - /// - public static void SetPlaceholderBrush(UIElement element, Brush value) { - element.SetValue(PlaceholderBrushProperty, value); - } - - /// - /// Getter for PlaceholderBrush property. - /// - /// - /// - public static Brush GetPlaceholderBrush(UIElement element) { - return (Brush)element.GetValue(PlaceholderBrushProperty); - } - - /// - /// Set placeholder as background when TextBox was loaded. - /// - /// - /// - private static void OnTextLoaded(object sender, RoutedEventArgs e) { - var textBox = sender as TextBox; - if (textBox == null) return; - - Label placeholder = new Label(); - placeholder.Content = textBox.GetValue(PlaceholderProperty); - - VisualBrush brush = new VisualBrush(); - brush.AlignmentX = AlignmentX.Left; - brush.AlignmentY = GetPlaceholderBaseline(textBox); - brush.TileMode = TileMode.None; - brush.Stretch = Stretch.None; - brush.Opacity = 0.3; - brush.Visual = placeholder; - - textBox.SetValue(PlaceholderBrushProperty, brush); - textBox.Background = brush; - textBox.TextChanged += UpdatePlaceholder; - UpdatePlaceholder(textBox, null); - } - - /// - /// Dynamically hide/show placeholder. - /// - /// - /// - private static void UpdatePlaceholder(object sender, RoutedEventArgs e) { - var textBox = sender as TextBox; - if (string.IsNullOrEmpty(textBox.Text)) { - textBox.Background = textBox.GetValue(PlaceholderBrushProperty) as Brush; - } else { - textBox.Background = Brushes.Transparent; - } - } - - /// - /// - /// - /// - /// - private static void UpdateScrollOnSelectionChanged(object sender, RoutedEventArgs e) { - var textBox = sender as TextBox; - if (textBox != null && textBox.IsFocused) { - if (Mouse.LeftButton == MouseButtonState.Pressed && textBox.SelectionLength > 0) { - var p = Mouse.GetPosition(textBox); - if (p.X <= 8) { - textBox.LineLeft(); - } else if (p.X >= textBox.ActualWidth - 8) { - textBox.LineRight(); - } - - if (p.Y <= 8) { - textBox.LineUp(); - } else if (p.Y >= textBox.ActualHeight - 8) { - textBox.LineDown(); - } - } else { - var rect = textBox.GetRectFromCharacterIndex(textBox.CaretIndex); - if (rect.Left <= 0) { - textBox.ScrollToHorizontalOffset(textBox.HorizontalOffset + rect.Left); - } else if (rect.Right >= textBox.ActualWidth) { - textBox.ScrollToHorizontalOffset(textBox.HorizontalOffset + rect.Right); - } - - if (rect.Top <= 0) { - textBox.ScrollToVerticalOffset(textBox.VerticalOffset + rect.Top); - } else if (rect.Bottom >= textBox.ActualHeight) { - textBox.ScrollToVerticalOffset(textBox.VerticalOffset + rect.Bottom); - } - } - } - } - } -} diff --git a/SourceGit/Helpers/TreeViewHelper.cs b/SourceGit/Helpers/TreeViewHelper.cs deleted file mode 100644 index d1b74bf2..00000000 --- a/SourceGit/Helpers/TreeViewHelper.cs +++ /dev/null @@ -1,329 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; - -namespace SourceGit.Helpers { - - /// - /// Helper class to enable multi-selection of TreeView - /// - public static class TreeViewHelper { - - /// - /// Definition of EnableMultiSelection property. - /// - public static readonly DependencyProperty EnableMultiSelectionProperty = - DependencyProperty.RegisterAttached( - "EnableMultiSelection", - typeof(bool), - typeof(TreeViewHelper), - new FrameworkPropertyMetadata(false, OnEnableMultiSelectionChanged)); - - /// - /// Getter of EnableMultiSelection - /// - /// - /// - public static bool GetEnableMultiSelection(DependencyObject obj) { - return (bool)obj.GetValue(EnableMultiSelectionProperty); - } - - /// - /// Setter of EnableMultiSelection - /// - /// - /// - public static void SetEnableMultiSelection(DependencyObject obj, bool value) { - obj.SetValue(EnableMultiSelectionProperty, value); - } - - /// - /// Definition of SelectedItems - /// - public static readonly DependencyProperty SelectedItemsProperty = - DependencyProperty.RegisterAttached( - "SelectedItems", - typeof(ObservableCollection), - typeof(TreeViewHelper), - new FrameworkPropertyMetadata(null)); - - /// - /// Getter of SelectedItems - /// - /// - /// - public static ObservableCollection GetSelectedItems(DependencyObject obj) { - return (ObservableCollection)obj.GetValue(SelectedItemsProperty); - } - - /// - /// Setter of SelectedItems - /// - /// - /// - public static void SetSelectedItems(DependencyObject obj, ObservableCollection value) { - obj.SetValue(SelectedItemsProperty, value); - } - - /// - /// Definition of IsChecked property. - /// - public static readonly DependencyProperty IsCheckedProperty = - DependencyProperty.RegisterAttached( - "IsChecked", - typeof(bool), - typeof(TreeViewHelper), - new FrameworkPropertyMetadata(false)); - - /// - /// Getter of IsChecked Property. - /// - /// - /// - public static bool GetIsChecked(DependencyObject obj) { - return (bool)obj.GetValue(IsCheckedProperty); - } - - /// - /// Setter of IsChecked property - /// - /// - /// - public static void SetIsChecked(DependencyObject obj, bool value) { - obj.SetValue(IsCheckedProperty, value); - } - - /// - /// Definition of MultiSelectionChangedEvent - /// - public static readonly RoutedEvent MultiSelectionChangedEvent = - EventManager.RegisterRoutedEvent("MultiSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TreeViewHelper)); - - /// - /// Add handler for MultiSelectionChanged event. - /// - /// - /// - public static void AddMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) { - var tree = d as TreeView; - if (tree != null) tree.AddHandler(MultiSelectionChangedEvent, handler); - } - - /// - /// Remove handler for MultiSelectionChanged event. - /// - /// - /// - public static void RemoveMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) { - var tree = d as TreeView; - if (tree != null) tree.RemoveHandler(MultiSelectionChangedEvent, handler); - } - - /// - /// Select all items in tree. - /// - /// - public static void SelectWholeTree(TreeView tree) { - var selected = GetSelectedItems(tree); - selected.Clear(); - SelectAll(selected, tree); - tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent)); - } - - /// - /// Selected one item by DataContext - /// - /// - /// - public static void SelectOneByContext(TreeView tree, object obj) { - var item = FindTreeViewItemByDataContext(tree, obj); - if (item != null) { - var selected = GetSelectedItems(tree); - selected.Add(item); - item.SetValue(IsCheckedProperty, true); - tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent)); - } - } - - /// - /// Unselect the whole tree. - /// - /// - public static void UnselectTree(TreeView tree) { - var selected = GetSelectedItems(tree); - if (selected.Count == 0) return; - - foreach (var old in selected) old.SetValue(IsCheckedProperty, false); - selected.Clear(); - tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent)); - } - - /// - /// Hooks when EnableMultiSelection changed. - /// - /// - /// - private static void OnEnableMultiSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - var tree = d as TreeView; - if (tree != null && (bool)e.NewValue) { - tree.SetValue(SelectedItemsProperty, new ObservableCollection()); - tree.PreviewMouseDown += OnTreeMouseDown; - } - } - - /// - /// Preview mouse button select. - /// - /// - /// - private static void OnTreeMouseDown(object sender, MouseButtonEventArgs e) { - var tree = sender as TreeView; - if (tree == null) return; - - var hit = VisualTreeHelper.HitTest(tree, e.GetPosition(tree)); - if (hit == null || hit.VisualHit is null) return; - - var item = FindTreeViewItem(hit.VisualHit as UIElement); - if (item == null) return; - - var selected = GetSelectedItems(tree); - if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) { - if (GetIsChecked(item)) { - selected.Remove(item); - item.SetValue(IsCheckedProperty, false); - } else { - selected.Add(item); - item.SetValue(IsCheckedProperty, true); - } - } else if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) && selected.Count > 0) { - var last = selected.Last(); - if (last == item) return; - - var lastPos = last.PointToScreen(new Point(0, 0)); - var curPos = item.PointToScreen(new Point(0, 0)); - if (lastPos.Y > curPos.Y) { - SelectRange(selected, tree, item, last); - } else { - SelectRange(selected, tree, last, item); - } - - selected.Add(item); - item.SetValue(IsCheckedProperty, true); - } else if (e.RightButton == MouseButtonState.Pressed) { - if (GetIsChecked(item)) return; - - foreach (var old in selected) old.SetValue(IsCheckedProperty, false); - selected.Clear(); - selected.Add(item); - item.SetValue(IsCheckedProperty, true); - } else { - foreach (var old in selected) old.SetValue(IsCheckedProperty, false); - selected.Clear(); - selected.Add(item); - item.SetValue(IsCheckedProperty, true); - } - - tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent)); - } - - /// - /// Find TreeViewItem by child element. - /// - /// - /// - /// - private static TreeViewItem FindTreeViewItem(DependencyObject child) { - if (child == null) return null; - if (child is TreeViewItem) return child as TreeViewItem; - if (child is TreeView) return null; - return FindTreeViewItem(VisualTreeHelper.GetParent(child)); - } - - /// - /// Find TreeViewItem by DataContext - /// - /// - /// - /// - private static TreeViewItem FindTreeViewItemByDataContext(ItemsControl control, object obj) { - if (control == null) return null; - if (control.DataContext == obj) return control as TreeViewItem; - - for (int i = 0; i < control.Items.Count; i++) { - var child = control.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl; - var found = FindTreeViewItemByDataContext(child, obj); - if (found != null) return found; - } - - return null; - } - - /// - /// Select all items. - /// - /// - /// - private static void SelectAll(ObservableCollection selected, ItemsControl control) { - for (int i = 0; i < control.Items.Count; i++) { - var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem; - if (child == null) continue; - - selected.Add(child); - child.SetValue(IsCheckedProperty, true); - SelectAll(selected, child); - } - } - - /// - /// Select range items between given. - /// - /// - /// - /// - /// - /// - private static int SelectRange(ObservableCollection selected, ItemsControl control, TreeViewItem from, TreeViewItem to, int matches = 0) { - for (int i = 0; i < control.Items.Count; i++) { - var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem; - if (child == null) continue; - - if (matches == 1) { - if (child == to) return 2; - selected.Add(child); - child.SetValue(IsCheckedProperty, true); - if (TryEndRangeSelection(selected, child, to)) return 2; - } else if (child == from) { - matches = 1; - if (TryEndRangeSelection(selected, child, to)) return 2; - } else { - matches = SelectRange(selected, child, from, to, matches); - if (matches == 2) return 2; - } - } - - return matches; - } - - private static bool TryEndRangeSelection(ObservableCollection selected, TreeViewItem control, TreeViewItem end) { - for (int i = 0; i < control.Items.Count; i++) { - var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem; - if (child == null) continue; - - if (child == end) { - return true; - } else { - selected.Add(child); - child.SetValue(IsCheckedProperty, true); - - var ended = TryEndRangeSelection(selected, child, end); - if (ended) return true; - } - } - - return false; - } - } -} \ No newline at end of file diff --git a/SourceGit/Helpers/Validations.cs b/SourceGit/Helpers/Validations.cs deleted file mode 100644 index a92425b7..00000000 --- a/SourceGit/Helpers/Validations.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Globalization; -using System.IO; -using System.Text.RegularExpressions; -using System.Windows.Controls; - -namespace SourceGit.Helpers { - - /// - /// Validate clone folder. - /// - public class CloneFolderRule : ValidationRule { - public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var badPath = "EXISTS and FULL ACCESS CONTROL needed"; - var path = value as string; - return Directory.Exists(path) ? ValidationResult.ValidResult : new ValidationResult(false, badPath); - } - } - - /// - /// Validate git remote URL - /// - public class RemoteUriRule : ValidationRule { - public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var badUrl = "Remote git URL not supported"; - return Git.Repository.IsValidUrl(value as string) ? ValidationResult.ValidResult : new ValidationResult(false, badUrl); - } - } - - /// - /// Validate tag name. - /// - public class RemoteNameRule : ValidationRule { - public Git.Repository Repo { get; set; } - - public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var regex = new Regex(@"^[\w\-\.]+$"); - var name = value as string; - var remotes = Repo.Remotes(); - - if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Remote name can NOT be null"); - if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for remote. Regex: ^[\\w\\-\\.]+$"); - - foreach (var t in remotes) { - if (t.Name == name) { - return new ValidationResult(false, $"Remote '{name}' already exists"); - } - } - - return ValidationResult.ValidResult; - } - } - - /// - /// Validate branch name. - /// - public class BranchNameRule : ValidationRule { - public Git.Repository Repo { get; set; } - public string Prefix { get; set; } = ""; - - public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var regex = new Regex(@"^[\w\-/\.]+$"); - var name = value as string; - var branches = Repo.Branches(); - - if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Branch name can NOT be null"); - if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for branch. Regex: ^[\\w\\-/\\.]+$"); - - name = Prefix + name; - - foreach (var b in branches) { - if (b.Name == name) { - return new ValidationResult(false, $"Branch '{name}' already exists"); - } - } - - return ValidationResult.ValidResult; - } - } - - /// - /// Validate tag name. - /// - public class TagNameRule : ValidationRule { - public Git.Repository Repo { get; set; } - - public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var regex = new Regex(@"^[\w\-\.]+$"); - var name = value as string; - var tags = Repo.Tags(); - - if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Tag name can NOT be null"); - if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for tag. Regex: ^[\\w\\-\\.]+$"); - - foreach (var t in tags) { - if (t.Name == name) { - return new ValidationResult(false, $"Tag '{name}' already exists"); - } - } - - return ValidationResult.ValidResult; - } - } - - /// - /// Required for commit subject. - /// - public class CommitSubjectRequiredRule : ValidationRule { - public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var subject = value as string; - return string.IsNullOrWhiteSpace(subject) ? new ValidationResult(false, "Commit subject can NOT be empty") : ValidationResult.ValidResult; - } - } - - /// - /// Required for patch file. - /// - public class PatchFileRequiredRule : ValidationRule { - public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var path = value as string; - var succ = !string.IsNullOrEmpty(path) && File.Exists(path); - return !succ ? new ValidationResult(false, "Invalid path for patch file") : ValidationResult.ValidResult; - } - } - - /// - /// Required for submodule path. - /// - public class SubmodulePathRequiredRule : ValidationRule { - public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var regex = new Regex(@"^[\w\-\._/]+$"); - var path = value as string; - var succ = !string.IsNullOrEmpty(path) && regex.IsMatch(path.Trim()); - return !succ ? new ValidationResult(false, "Invalid path for submodules") : ValidationResult.ValidResult; - } - } -} diff --git a/SourceGit/Properties/AssemblyInfo.cs b/SourceGit/Properties/AssemblyInfo.cs deleted file mode 100644 index c0ea722f..00000000 --- a/SourceGit/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Windows; - -[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] \ No newline at end of file diff --git a/SourceGit/Resources/Controls.xaml b/SourceGit/Resources/Controls.xaml deleted file mode 100644 index 03450990..00000000 --- a/SourceGit/Resources/Controls.xaml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Icons.xaml b/SourceGit/Resources/Icons.xaml deleted file mode 100644 index 66103f5a..00000000 --- a/SourceGit/Resources/Icons.xaml +++ /dev/null @@ -1,61 +0,0 @@ - - M1004.824 466.4L557.72 19.328c-25.728-25.76-67.488-25.76-93.28 0L360.568 123.2l78.176 78.176c12.544-5.984 26.56-9.376 41.376-9.376 53.024 0 96 42.976 96 96 0 14.816-3.36 28.864-9.376 41.376l127.968 127.968c12.544-5.984 26.56-9.376 41.376-9.376 53.024 0 96 42.976 96 96s-42.976 96-96 96-96-42.976-96-96c0-14.816 3.36-28.864 9.376-41.376L521.496 374.624a88.837 88.837 0 0 1-9.376 3.872v266.976c37.28 13.184 64 48.704 64 90.528 0 53.024-42.976 96-96 96s-96-42.976-96-96c0-41.792 26.72-77.344 64-90.528V378.496c-37.28-13.184-64-48.704-64-90.528 0-14.816 3.36-28.864 9.376-41.376l-78.176-78.176L19.416 464.288c-25.76 25.792-25.76 67.52 0 93.28l447.136 447.072c25.728 25.76 67.488 25.76 93.28 0l444.992-444.992c25.76-25.76 25.76-67.552 0-93.28z - M557.696 545.347L789.873 402.66c23.998-14.999 31.297-46.496 16.398-70.493-14.798-23.798-45.995-31.197-69.993-16.699L506.501 456.555 277.123 315.37c-24.098-14.798-55.595-7.3-70.493 16.799-14.799 24.097-7.3 55.594 16.798 70.493l231.778 142.586V819.12c0 28.297 22.897 51.195 51.195 51.195 28.297 0 51.195-22.898 51.195-51.195V545.347h0.1zM506.5 0l443.356 255.975v511.95L506.501 1023.9 63.144 767.925v-511.95L506.5 0z - - M753.613 996.727L269.38 511.505 754.602 27.272z - M270.387 27.273L754.62 512.495 269.398 996.728z - - F1M0,6L0,9 9,9 9,6 0,6z - F1M0,0L0,9 9,9 9,0 0,0 0,3 8,3 8,8 1,8 1,3z - F1M0,10L0,3 3,3 3,0 10,0 10,2 4,2 4,3 7,3 7,6 6,6 6,5 1,5 1,10z M1,10L7,10 7,7 10,7 10,2 9,2 9,6 6,6 6,9 1,9z - M810.666667 273.493333L750.506667 213.333333 512 451.84 273.493333 213.333333 213.333333 273.493333 451.84 512 213.333333 750.506667 273.493333 810.666667 512 572.16 750.506667 810.666667 810.666667 750.506667 572.16 512z - M512 597.33333332m-1.26648097 0a1.26648097 1.26648097 0 1 0 2.53296194 0 1.26648097 1.26648097 0 1 0-2.53296194 0ZM809.691429 392.777143L732.16 314.514286 447.634286 599.771429 292.571429 443.977143 214.308571 521.508571l155.794286 155.794286 77.531429 77.531429 362.057143-362.057143z - M511.680999 0C233.071131 0 6.524722 222.580887 0.12872 499.655715 6.013042 257.886821 189.834154 63.960025 415.740962 63.960025c229.61649 0 415.740162 200.450718 415.740162 447.720175 0 52.958901 42.981137 95.940037 95.940038 95.940037s95.940037-42.981137 95.940037-95.940037c0-282.57539-229.104809-511.6802-511.6802-511.6802z m0 1023.3604c278.609869 0 505.156277-222.580887 511.55228-499.655715-5.884322 241.768894-189.705434 435.69569-415.612242 435.69569-229.61649 0-415.740162-200.450718-415.740163-447.720175 0-52.958901-42.981137-95.940037-95.940037-95.940038s-95.940037 42.981137-95.940037 95.940038c0 282.57539 229.104809 511.6802 511.680199 511.6802z - M701.9062029 677.41589899L589.90712068 565.41681675a148.33953321 148.33953321 0 1 0-24.97646381 26.55648342L676.07895931 703.12160261z m-346.38891409-199.50786053a114.97681148 114.97681148 0 1 1 114.85527151 114.97681148A115.09835147 115.09835147 0 0 1 355.45651882 477.90803846z - M352 64h320L960 352v320L672 960h-320L64 672v-320L352 64z m161.28 362.688L344.128 256 259.584 341.312 428.736 512l-169.152 170.688L344.128 768 513.28 597.312 682.432 768l84.544-85.312L597.824 512l169.152-170.688L682.432 256 513.28 426.688z - - M51.2 204.8h102.4v102.4H51.2V204.8z m204.8 0h716.8v102.4H256V204.8zM51.2 460.8h102.4v102.4H51.2V460.8z m204.8 0h716.8v102.4H256V460.8z m-204.8 256h102.4v102.4H51.2v-102.4z m204.8 0h716.8v102.4H256v-102.4z - M912 737l0 150L362 887l0-100 0-50 0-150 0-150 0-150L112 287l0-150 450 0 0 150L412 287l0 150L912 437l0 150L412 587l0 150L912 737z - - M868 545.5L536.1 163c-12.7-14.7-35.5-14.7-48.3 0L156 545.5c-4.5 5.2-0.8 13.2 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z - M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861c12.7 14.7 35.5 14.7 48.3 0L868 478.5c4.5-5.2 0.8-13.2-6-13.2z - - M509.44 546.304l270.848-270.912 90.56 90.56-347.52 349.056-0.832-0.768-13.056 13.056-362.624-361.28 91.136-91.264z - M256 224l1e-8 115.2L512 544l255.99999999-204.8 1e-8-115.2-256 204.80000001L256 224zM512 684.8l-256-204.8L256 595.2 512 800 768 595.2l0-115.2L512 684.8z - M169.5 831l342.8-341.9L855.1 831l105.3-105.3-448.1-448.1L64.2 725.7 169.5 831z - M768 800V684.8L512 480 256 684.8V800l256-204.8L768 800zM512 339.2L768 544V428.8L512 224 256 428.8V544l256-204.8z - - M64.2 180.3h418.2v120.6H64.2zM64.2 461.7h358.5v120.6H64.2zM64.2 723.1h418.2v120.6H64.2zM601.9 180.3h358.5v120.6H601.9zM482.4 119.9h179.2v241.3H482.4zM303.2 401.4h179.2v241.3H303.2zM482.4 662.8h179.2v241.3H482.4zM540.3 461.7h420.1v120.6H540.3zM601.9 723.1h358.5v120.6H601.9z - M887 576.8v-129.4L796.6 418c-4.6-14-10.2-27.4-16.8-40.4l43.2-84.8-91.6-91.6-84.8 43.2c-13-6.6-26.6-12.2-40.4-16.8l-29.4-90.4h-129.4L418 227.6c-13.8 4.6-27.4 10.2-40.4 16.8l-84.8-43.2-91.6 91.6 43.2 84.8c-6.6 13-12.2 26.6-16.8 40.4l-90.4 29.4v129.4l90.4 29.4c4.6 13.8 10.2 27.4 16.8 40.4l-43.2 84.8 91.6 91.6 84.8-43.2c13 6.6 26.6 12.2 40.4 16.8l29.4 90.4h129.4l29.4-90.4c14-4.6 27.4-10.2 40.4-16.8l84.8 43.2 91.6-91.6-43.2-84.8c6.6-13 12.2-26.6 16.8-40.4l90.4-29.4zM512 662c-82.8 0-150-67.2-150-150s67.2-150 150-150 150 67.2 150 150-67.2 150-150 150z - M 38,19C 48.4934,19 57,27.5066 57,38C 57,48.4934 48.4934,57 38,57C 27.5066,57 19,48.4934 19,38C 19,27.5066 27.5066,19 38,19 Z M 33.25,33.25L 33.25,36.4167L 36.4166,36.4167L 36.4166,47.5L 33.25,47.5L 33.25,50.6667L 44.3333,50.6667L 44.3333,47.5L 41.1666,47.5L 41.1666,36.4167L 41.1666,33.25L 33.25,33.25 Z M 38.7917,25.3333C 37.48,25.3333 36.4167,26.3967 36.4167,27.7083C 36.4167,29.02 37.48,30.0833 38.7917,30.0833C 40.1033,30.0833 41.1667,29.02 41.1667,27.7083C 41.1667,26.3967 40.1033,25.3333 38.7917,25.3333 Z - M64 864h896V288h-396.224a64 64 0 0 1-57.242667-35.376L460.224 160H64v704z m-64 32V128a32 32 0 0 1 32-32h448a32 32 0 0 1 28.624 17.690667L563.776 224H992a32 32 0 0 1 32 32v640a32 32 0 0 1-32 32H32a32 32 0 0 1-32-32z - M448 64l128 128h448v768H0V64z - M832 960l192-512H192L0 960zM128 384L0 960V128h288l128 128h416v128z - M958.656 320H960v639.936A64 64 0 0 1 896.128 1024H191.936A63.872 63.872 0 0 1 128 959.936V64.064A64 64 0 0 1 191.936 0H640v320.96h319.616L958.656 320zM320 544c0 17.152 14.464 32 32.192 32h383.552A32.384 32.384 0 0 0 768 544c0-17.152-14.464-32-32.256-32H352.192A32.448 32.448 0 0 0 320 544z m0 128c0 17.152 14.464 32 32.192 32h383.552a32.384 32.384 0 0 0 32.256-32c0-17.152-14.464-32-32.256-32H352.192a32.448 32.448 0 0 0-32.192 32z m0 128c0 17.152 14.464 32 32.192 32h383.552a32.384 32.384 0 0 0 32.256-32c0-17.152-14.464-32-32.256-32H352.192a32.448 32.448 0 0 0-32.192 32z - M854.2 306.6L611.3 72.9c-6-5.7-13.9-8.9-22.2-8.9H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h277l219 210.6V824c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V329.6c0-8.7-3.5-17-9.8-23zM553.4 201.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v704c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32V397.3c0-8.5-3.4-16.6-9.4-22.6L553.4 201.4zM568 753c0 3.8-3.4 7-7.5 7h-225c-4.1 0-7.5-3.2-7.5-7v-42c0-3.8 3.4-7 7.5-7h225c4.1 0 7.5 3.2 7.5 7v42z m0-220c0 3.8-3.4 7-7.5 7H476v84.9c0 3.9-3.1 7.1-7 7.1h-42c-3.8 0-7-3.2-7-7.1V540h-84.5c-4.1 0-7.5-3.2-7.5-7v-42c0-3.9 3.4-7 7.5-7H420v-84.9c0-3.9 3.2-7.1 7-7.1h42c3.9 0 7 3.2 7 7.1V484h84.5c4.1 0 7.5 3.1 7.5 7v42z - M599.22969 424.769286 599.22969 657.383158 424.769286 831.844585 424.769286 424.769286 192.155415 192.155415 831.844585 192.155415Z - M71.111111 1024V0h661.333333L952.888889 219.420444V1024H71.111111z m808.305778-731.420444l-220.444445-219.448889H144.583111V950.897778h734.833778V292.579556zM438.528 512h-220.444444V219.420444h220.444444V512z m-73.500444-219.420444H291.555556v146.289777h73.472v-146.289777z m0 512h73.500444v73.130666h-220.444444v-73.130666H291.555556v-146.289778H218.083556V585.102222h146.944v219.448889z m293.944888-365.710223h73.472V512H512v-73.130667h73.472v-146.289777H512V219.420444h146.972444v219.448889z m73.472 438.840889H512V585.130667h220.444444v292.579555z m-73.472-219.420444h-73.500444v146.289778h73.500444v-146.289778z - - M1024 1024H0V0h1024v1024z m-64-64V320H320V256h640V64H64v896h192V64h64v896z - M81.92 81.92v860.16h860.16V81.92H81.92z m802.304 57.856V322.56H139.776V139.776h744.448z m-744.448 240.64H322.56v503.808H139.776V380.416z m240.128 503.808V380.416h504.32v503.808H379.904z - - M1024 896v128H0V704h128v192h768V704h128v192zM576 554.688L810.688 320 896 405.312l-384 384-384-384L213.312 320 448 554.688V0h128v554.688z - M432 0h160c26.6 0 48 21.4 48 48v336h175.4c35.6 0 53.4 43 28.2 68.2L539.4 756.6c-15 15-39.6 15-54.6 0L180.2 452.2c-25.2-25.2-7.4-68.2 28.2-68.2H384V48c0-26.6 21.4-48 48-48z m592 752v224c0 26.6-21.4 48-48 48H48c-26.6 0-48-21.4-48-48V752c0-26.6 21.4-48 48-48h293.4l98 98c40.2 40.2 105 40.2 145.2 0l98-98H976c26.6 0 48 21.4 48 48z m-248 176c0-22-18-40-40-40s-40 18-40 40 18 40 40 40 40-18 40-40z m128 0c0-22-18-40-40-40s-40 18-40 40 18 40 40 40 40-18 40-40z - M592 768h-160c-26.6 0-48-21.4-48-48V384h-175.4c-35.6 0-53.4-43-28.2-68.2L484.6 11.4c15-15 39.6-15 54.6 0l304.4 304.4c25.2 25.2 7.4 68.2-28.2 68.2H640v336c0 26.6-21.4 48-48 48z m432-16v224c0 26.6-21.4 48-48 48H48c-26.6 0-48-21.4-48-48V752c0-26.6 21.4-48 48-48h272v16c0 61.8 50.2 112 112 112h160c61.8 0 112-50.2 112-112v-16h272c26.6 0 48 21.4 48 48z m-248 176c0-22-18-40-40-40s-40 18-40 40 18 40 40 40 40-18 40-40z m128 0c0-22-18-40-40-40s-40 18-40 40 18 40 40 40 40-18 40-40z - M961.3 319.6L512 577.3 62.7 319.6 512 62l449.3 257.6zM512 628.4L185.4 441.6 62.7 512 512 769.6 961.3 512l-122.7-70.4L512 628.4zM512 820.8L185.4 634 62.7 704.3 512 962l449.3-257.7L838.6 634 512 820.8z - M295.328 472l143.184 276.032S671.184 186.992 1038.096 0c-8.944 133.568-44.8 249.328 17.904 391.792C894.912 427.408 563.792 828.112 456.4 1024 304.272 837.008 125.296 694.544 0 650.016z - M89.6 806.4h844.8V217.6H89.6v588.8zM0 128h1024v768H0V128z m242.816 577.536L192 654.72l154.304-154.368L192 346.048l50.816-50.816L448 500.352 242.816 705.536z m584.32 13.248H512V640h315.072v78.72z - M508.928 556.125091l92.904727 148.759273h124.462546l-79.639273-79.173819 49.245091-49.524363 164.584727 163.700363-164.631273 163.002182-49.152-49.617454 79.36-78.568728h-162.955636l-95.650909-153.227636 41.472-65.349818z m186.973091-394.705455l164.584727 163.700364-164.631273 163.002182-49.152-49.617455L726.109091 359.936H529.687273l-135.540364 223.976727H139.636364v-69.818182h215.133091l135.586909-223.976727h235.938909l-79.639273-79.173818 49.245091-49.524364z - - M795.968 471.04A291.584 291.584 0 0 0 512 256a293.376 293.376 0 0 0-283.968 215.04H0v144h228.032A292.864 292.864 0 0 0 512 832a291.136 291.136 0 0 0 283.968-216.96H1024V471.04h-228.032M512 688A145.856 145.856 0 0 1 366.016 544 144.576 144.576 0 0 1 512 400c80 0 145.984 63.104 145.984 144A145.856 145.856 0 0 1 512 688 - M0 586.459429l403.968 118.784 497.517714-409.892572-385.536 441.490286-1.609143 250.587428 154.916572-204.580571 278.601143 83.456L1170.285714 36.571429z - M24.356571 512A488.155429 488.155429 0 0 1 512 24.356571 488.155429 488.155429 0 0 1 999.643429 512 488.155429 488.155429 0 0 1 512 999.643429 488.155429 488.155429 0 0 1 24.356571 512z m446.976-325.046857v326.656L242.614857 619.227429l51.126857 110.665142 299.52-138.24V186.953143H471.332571z - M714.624 253.648h-404.8l-57.808 57.328h520.48z m-491.568 85.984v200.624h578.336V339.632z m404.8 143.296h-28.88v-28.64H425.472v28.64h-28.912v-57.312h231.328v57.312z m-404.8 295.12h578.336V559.36H223.056z m173.504-132.704h231.328v57.328h-28.912v-28.656H425.472v28.656h-28.912v-57.328z - M868.736 144.96a144.64 144.64 0 1 0-289.408 0c0 56.064 32.64 107.008 83.456 130.624-4.928 95.552-76.608 128-201.088 174.592-52.48 19.712-110.336 41.6-159.744 74.432V276.16A144.448 144.448 0 0 0 241.664 0.192a144.64 144.64 0 0 0-144.64 144.768c0 58.24 34.688 108.288 84.352 131.2v461.184a144.32 144.32 0 0 0-84.416 131.2 144.704 144.704 0 1 0 289.472 0 144.32 144.32 0 0 0-83.52-130.688c4.992-95.488 76.672-127.936 201.152-174.592 122.368-45.952 273.792-103.168 279.744-286.784a144.64 144.64 0 0 0 84.928-131.52zM241.664 61.44a83.456 83.456 0 1 1 0 166.912 83.456 83.456 0 0 1 0-166.912z m0 890.56a83.52 83.52 0 1 1 0-167.04 83.52 83.52 0 0 1 0 167.04zM724.032 228.416a83.52 83.52 0 1 1 0-167.04 83.52 83.52 0 0 1 0 167.04z - M896 128h-64V64c0-35.2-28.8-64-64-64s-64 28.8-64 64v64h-64c-35.2 0-64 28.8-64 64s28.8 64 64 64h64v64c0 35.2 28.8 64 64 64s64-28.8 64-64V256h64c35.2 0 64-28.8 64-64s-28.8-64-64-64z m-203.52 307.2C672.64 480.64 628.48 512 576 512H448c-46.72 0-90.24 12.8-128 35.2V372.48C394.24 345.6 448 275.2 448 192c0-106.24-85.76-192-192-192S64 85.76 64 192c0 83.2 53.76 153.6 128 180.48v279.68c-74.24 25.6-128 96.64-128 179.84 0 106.24 85.76 192 192 192s192-85.76 192-192c0-66.56-33.92-124.8-84.48-159.36 22.4-19.84 51.84-32.64 84.48-32.64h128c121.6 0 223.36-85.12 248.96-199.04-18.56 4.48-37.12 7.04-56.96 7.04-26.24 0-51.2-5.12-75.52-12.8zM256 128c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m0 768c-35.2 0-64-28.8-64-64s28.8-64 64-64 64 28.8 64 64-28.8 64-64 64z - M901.802667 479.232v-1.024c0-133.461333-111.616-241.664-249.514667-241.664-105.813333 0-195.925333 63.829333-232.448 153.941333-27.989333-20.138667-62.464-32.426667-100.010667-32.426666-75.776 0-139.605333 49.152-159.744 116.053333-51.882667 36.522667-86.016 96.938667-86.016 165.205333 0 111.616 90.453333 201.728 201.728 201.728h503.466667c111.616 0 201.728-90.453333 201.728-201.728 0-65.194667-31.061333-123.221333-79.189333-160.085333z - M363.789474 512h67.368421v107.789474h107.789473v67.368421h-107.789473v107.789473h-67.368421v-107.789473h-107.789474v-67.368421h107.789474v-107.789474z m297.539368-64A106.671158 106.671158 0 0 1 768 554.671158C768 613.578105 719.548632 660.210526 660.210526 660.210526h-107.789473v-53.894737h-107.789474v-107.789473h-94.31579v107.789473h-94.315789c4.311579-21.194105 22.231579-46.807579 43.560421-50.755368l-0.889263-11.560421a74.671158 74.671158 0 0 1 71.248842-74.590316 128.053895 128.053895 0 0 1 238.605474-7.437473 106.172632 106.172632 0 0 1 52.816842-13.972211z - M177.311335 156.116617c-22.478967 4.729721-32.774451 17.336854-36.251645 36.893258-10.080589 56.697303-33.399691 257.604032-13.234419 277.769304l445.342858 445.341834c23.177885 23.177885 60.757782 23.178909 83.935668 0l246.019183-246.019183c23.177885-23.177885 23.177885-60.757782 0-83.935668l-445.341834-445.341834C437.419398 120.463606 231.004211 144.82034 177.311335 156.116617zM331.22375 344.221786c-26.195615 26.195615-68.667939 26.195615-94.863555 0-26.195615-26.195615-26.195615-68.666916 0-94.863555s68.667939-26.195615 94.862531 0C357.418342 275.55487 357.419366 318.02617 331.22375 344.221786z - M682.666667 536.576h-143.701334v-142.336h-142.336V283.306667H238.933333a44.032 44.032 0 0 0-40.96 40.96v170.666666a55.978667 55.978667 0 0 0 14.336 34.133334l320.512 320.512a40.96 40.96 0 0 0 57.685334 0l173.738666-173.738667a40.96 40.96 0 0 0 0-57.685333z m-341.333334-108.544a40.96 40.96 0 1 1 0-57.685333 40.96 40.96 0 0 1 0 57.685333zM649.216 284.330667V141.994667h-68.608v142.336h-142.336v68.266666h142.336v142.336h68.608v-142.336h142.336v-68.266666h-142.336z - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/Border.xaml b/SourceGit/Resources/Styles/Border.xaml deleted file mode 100644 index a6f9bcd5..00000000 --- a/SourceGit/Resources/Styles/Border.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/Button.xaml b/SourceGit/Resources/Styles/Button.xaml deleted file mode 100644 index 49c6169b..00000000 --- a/SourceGit/Resources/Styles/Button.xaml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/CheckBox.xaml b/SourceGit/Resources/Styles/CheckBox.xaml deleted file mode 100644 index b271d993..00000000 --- a/SourceGit/Resources/Styles/CheckBox.xaml +++ /dev/null @@ -1,38 +0,0 @@ - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/ComboBox.xaml b/SourceGit/Resources/Styles/ComboBox.xaml deleted file mode 100644 index 5fd3e228..00000000 --- a/SourceGit/Resources/Styles/ComboBox.xaml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/ContextMenu.xaml b/SourceGit/Resources/Styles/ContextMenu.xaml deleted file mode 100644 index c82d6fee..00000000 --- a/SourceGit/Resources/Styles/ContextMenu.xaml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/DataGrid.xaml b/SourceGit/Resources/Styles/DataGrid.xaml deleted file mode 100644 index 88bc5450..00000000 --- a/SourceGit/Resources/Styles/DataGrid.xaml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/HyperLink.xaml b/SourceGit/Resources/Styles/HyperLink.xaml deleted file mode 100644 index db1e0330..00000000 --- a/SourceGit/Resources/Styles/HyperLink.xaml +++ /dev/null @@ -1,11 +0,0 @@ - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/Label.xaml b/SourceGit/Resources/Styles/Label.xaml deleted file mode 100644 index 7fecb8de..00000000 --- a/SourceGit/Resources/Styles/Label.xaml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/ListView.xaml b/SourceGit/Resources/Styles/ListView.xaml deleted file mode 100644 index 72ab8ba5..00000000 --- a/SourceGit/Resources/Styles/ListView.xaml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/Path.xaml b/SourceGit/Resources/Styles/Path.xaml deleted file mode 100644 index d3f643e6..00000000 --- a/SourceGit/Resources/Styles/Path.xaml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/RadioButton.xaml b/SourceGit/Resources/Styles/RadioButton.xaml deleted file mode 100644 index d7b65747..00000000 --- a/SourceGit/Resources/Styles/RadioButton.xaml +++ /dev/null @@ -1,52 +0,0 @@ - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/ScrollBar.xaml b/SourceGit/Resources/Styles/ScrollBar.xaml deleted file mode 100644 index 25c06ecf..00000000 --- a/SourceGit/Resources/Styles/ScrollBar.xaml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/ScrollViewer.xaml b/SourceGit/Resources/Styles/ScrollViewer.xaml deleted file mode 100644 index 971545e7..00000000 --- a/SourceGit/Resources/Styles/ScrollViewer.xaml +++ /dev/null @@ -1,54 +0,0 @@ - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/TabControl.xaml b/SourceGit/Resources/Styles/TabControl.xaml deleted file mode 100644 index 1a449039..00000000 --- a/SourceGit/Resources/Styles/TabControl.xaml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/TextBox.xaml b/SourceGit/Resources/Styles/TextBox.xaml deleted file mode 100644 index 48a96a72..00000000 --- a/SourceGit/Resources/Styles/TextBox.xaml +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/ToggleButton.xaml b/SourceGit/Resources/Styles/ToggleButton.xaml deleted file mode 100644 index b12157d6..00000000 --- a/SourceGit/Resources/Styles/ToggleButton.xaml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/Tooltip.xaml b/SourceGit/Resources/Styles/Tooltip.xaml deleted file mode 100644 index 0e7f9d3e..00000000 --- a/SourceGit/Resources/Styles/Tooltip.xaml +++ /dev/null @@ -1,21 +0,0 @@ - - - \ No newline at end of file diff --git a/SourceGit/Resources/Styles/TreeView.xaml b/SourceGit/Resources/Styles/TreeView.xaml deleted file mode 100644 index f8b9b802..00000000 --- a/SourceGit/Resources/Styles/TreeView.xaml +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Themes/Dark.xaml b/SourceGit/Resources/Themes/Dark.xaml deleted file mode 100644 index 2f8cac59..00000000 --- a/SourceGit/Resources/Themes/Dark.xaml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/Resources/Themes/Light.xaml b/SourceGit/Resources/Themes/Light.xaml deleted file mode 100644 index 2d936bb2..00000000 --- a/SourceGit/Resources/Themes/Light.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/SourceGit.csproj b/SourceGit/SourceGit.csproj deleted file mode 100644 index 896fbe8e..00000000 --- a/SourceGit/SourceGit.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - net46 - WinExe - true - true - App.ico - sourcegit - OpenSource GIT client for Windows - Copyright © sourcegit 2020. All rights reserved. - App.manifest - 1.5 - MIT - - - AnyCPU - true - - - - - \ No newline at end of file diff --git a/SourceGit/UI/About.xaml b/SourceGit/UI/About.xaml deleted file mode 100644 index 823d9b13..00000000 --- a/SourceGit/UI/About.xaml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/About.xaml.cs b/SourceGit/UI/About.xaml.cs deleted file mode 100644 index 4e3a0f90..00000000 --- a/SourceGit/UI/About.xaml.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using System.Windows; -using System.Windows.Navigation; - -namespace SourceGit.UI { - - /// - /// About dialog - /// - public partial class About : Window { - - /// - /// Current app version - /// - public string Version { - get { - return "VERSION : " + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion; - } - } - - /// - /// Constructor - /// - public About() { - InitializeComponent(); - } - - /// - /// Open source code link - /// - /// - /// - private void OpenSource(object sender, RequestNavigateEventArgs e) { - Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)); - e.Handled = true; - } - - /// - /// Close this dialog - /// - private void Quit(object sender, RoutedEventArgs e) { - Close(); - } - } -} diff --git a/SourceGit/UI/AddSubmodule.xaml b/SourceGit/UI/AddSubmodule.xaml deleted file mode 100644 index a6bd9a53..00000000 --- a/SourceGit/UI/AddSubmodule.xaml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/AddSubmodule.xaml.cs b/SourceGit/UI/AddSubmodule.xaml.cs deleted file mode 100644 index ebc46cec..00000000 --- a/SourceGit/UI/AddSubmodule.xaml.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Dialog to add new submodule. - /// - public partial class AddSubmodule : UserControl { - private Git.Repository repo = null; - - /// - /// Submodule's repository URL. - /// - public string RepoURL { get; set; } - - /// - /// Submodule's relative path. - /// - public string LocalPath { get; set; } - - /// - /// Constructor. - /// - /// - public AddSubmodule(Git.Repository opened) { - repo = opened; - InitializeComponent(); - } - - /// - /// Show this dialog. - /// - /// - public static void Show(Git.Repository repo) { - var popup = App.GetPopupManager(repo); - popup?.Show(new AddSubmodule(repo)); - } - - #region EVENTS - private void SelectFolder(object sender, RoutedEventArgs e) { - var dialog = new System.Windows.Forms.FolderBrowserDialog(); - dialog.Description = "Select Folder To Clone Repository"; - dialog.SelectedPath = repo.Path; - dialog.ShowNewFolderButton = true; - - if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - txtPath.Text = dialog.SelectedPath; - } - } - - private async void Sure(object sender, RoutedEventArgs e) { - txtRepoUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtRepoUrl)) return; - - txtPath.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtPath)) return; - - var recursive = chkRecursive.IsChecked == true; - var popup = App.GetPopupManager(repo); - - popup?.Lock(); - await Task.Run(() => repo.AddSubmodule(RepoURL, LocalPath, recursive, msg => { - popup?.UpdateStatus(msg); - })); - popup?.Close(true); - } - - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - #endregion - } -} diff --git a/SourceGit/UI/Apply.xaml b/SourceGit/UI/Apply.xaml deleted file mode 100644 index 5b15371a..00000000 --- a/SourceGit/UI/Apply.xaml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/UI/Apply.xaml.cs b/SourceGit/UI/Apply.xaml.cs deleted file mode 100644 index 7d881a98..00000000 --- a/SourceGit/UI/Apply.xaml.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Microsoft.Win32; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Apply patch dialog - /// - public partial class Apply : UserControl { - private Git.Repository repo = null; - - /// - /// Whitespace option. - /// - public class WhitespaceOption { - public string Name { get; set; } - public string Desc { get; set; } - public string Arg { get; set; } - - public WhitespaceOption(string n, string d, string a) { - Name = n; - Desc = d; - Arg = a; - } - } - - /// - /// Path of file to be patched. - /// - public string PatchFile { get; set; } - - /// - /// Constructor. - /// - public Apply(Git.Repository opened) { - repo = opened; - InitializeComponent(); - - combWhitespaceOptions.ItemsSource = new WhitespaceOption[] { - new WhitespaceOption("No Warn", "Turns off the trailing whitespace warning", "nowarn"), - new WhitespaceOption("Warn", "Outputs warnings for a few such errors, but applies", "warn"), - new WhitespaceOption("Error", "Raise errors and refuses to apply the patch", "error"), - new WhitespaceOption("Error All", "Similar to 'error', but shows more", "error-all"), - }; - combWhitespaceOptions.SelectedIndex = 0; - } - - /// - /// Show this dialog. - /// - /// - public static void Show(Git.Repository opened) { - var popup = App.GetPopupManager(opened); - popup?.Show(new Apply(opened)); - } - - /// - /// Open file browser dialog for select a file to patch. - /// - /// - /// - private void FindPatchFile(object sender, RoutedEventArgs e) { - var dialog = new OpenFileDialog(); - dialog.Filter = "Patch File|*.patch"; - dialog.Title = "Select Patch File"; - dialog.InitialDirectory = repo.Path; - dialog.CheckFileExists = true; - - if (dialog.ShowDialog() == true) { - PatchFile = dialog.FileName; - txtPatchFile.Text = dialog.FileName; - } - } - - /// - /// Start apply selected path. - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - txtPatchFile.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtPatchFile)) return; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - - var mode = combWhitespaceOptions.SelectedItem as WhitespaceOption; - var ignoreSpaceChanges = chkIgnoreWhitespace.IsChecked == true; - await Task.Run(() => repo.Apply(PatchFile, ignoreSpaceChanges, mode.Arg)); - - popup?.Close(true); - } - - /// - /// Cancel options. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/Blame.xaml b/SourceGit/UI/Blame.xaml deleted file mode 100644 index f61c6e9f..00000000 --- a/SourceGit/UI/Blame.xaml +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Blame.xaml.cs b/SourceGit/UI/Blame.xaml.cs deleted file mode 100644 index abf44055..00000000 --- a/SourceGit/UI/Blame.xaml.cs +++ /dev/null @@ -1,240 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Animation; - -namespace SourceGit.UI { - - /// - /// Viewer to show git-blame - /// - public partial class Blame : Window { - - /// - /// Background color for blocks. - /// - public static Brush[] BG = new Brush[] { - Brushes.Transparent, - new SolidColorBrush(Color.FromArgb(128, 0, 0, 0)) - }; - - /// - /// Constructor - /// - /// - /// - /// - public Blame(Git.Repository repo, string file, string revision) { - InitializeComponent(); - - double minWidth = content.ActualWidth; - - // Move to center. - var parent = App.Current.MainWindow; - Left = parent.Left + (parent.Width - Width) * 0.5; - Top = parent.Top + (parent.Height - Height) * 0.5; - - // Show loading. - DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1)); - anim.RepeatBehavior = RepeatBehavior.Forever; - loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim); - loading.Visibility = Visibility.Visible; - - // Layout content - blameFile.Content = $"{file}@{revision.Substring(0, 8)}"; - Task.Run(() => { - var blame = repo.BlameFile(file, revision); - - Dispatcher.Invoke(() => { - content.Document.Blocks.Clear(); - - if (blame.IsBinary) { - lineNumber.Text = "0"; - - Paragraph p = new Paragraph(new Run("BINARY FILE BLAME NOT SUPPORTED!!!")); - p.Margin = new Thickness(0); - p.Padding = new Thickness(0); - p.LineHeight = 1; - p.Background = Brushes.Transparent; - p.Foreground = FindResource("Brush.FG") as SolidColorBrush; - p.FontStyle = FontStyles.Normal; - - content.Document.Blocks.Add(p); - } else { - List numbers = new List(); - for (int i = 0; i < blame.LineCount; i++) numbers.Add(i.ToString()); - lineNumber.Text = string.Join("\n", numbers); - numbers.Clear(); - - for (int i = 0; i < blame.Blocks.Count; i++) { - var frag = blame.Blocks[i]; - var idx = i; - - Paragraph p = new Paragraph(new Run(frag.Content)); - p.DataContext = frag; - p.Margin = new Thickness(0); - p.Padding = new Thickness(0); - p.LineHeight = 1; - p.Background = BG[i % 2]; - p.Foreground = FindResource("Brush.FG") as SolidColorBrush; - p.FontStyle = FontStyles.Normal; - p.ContextMenuOpening += (sender, ev) => { - if (!content.Selection.IsEmpty) return; - - Hyperlink link = new Hyperlink(new Run(frag.CommitSHA)); - link.ToolTip = "CLICK TO GO"; - link.Click += (o, e) => { - repo.OnNavigateCommit?.Invoke(frag.CommitSHA); - e.Handled = true; - }; - - foreach (var block in content.Document.Blocks) { - var paragraph = block as Paragraph; - if ((paragraph.DataContext as Git.Blame.Block).CommitSHA == frag.CommitSHA) { - paragraph.Background = Brushes.Green; - } else { - paragraph.Background = BG[i % 2]; - } - } - - commitID.Content = link; - authorName.Content = frag.Author; - authorTime.Content = frag.Time; - popup.IsOpen = true; - ev.Handled = true; - }; - - var formatter = new FormattedText( - frag.Content, - CultureInfo.CurrentUICulture, - FlowDirection.LeftToRight, - new Typeface(content.FontFamily, p.FontStyle, p.FontWeight, p.FontStretch), - content.FontSize, - Brushes.Black, - new NumberSubstitution(), - TextFormattingMode.Ideal); - if (minWidth < formatter.Width) { - content.Document.PageWidth = formatter.Width + 16; - minWidth = formatter.Width; - } - - content.Document.Blocks.Add(p); - } - } - - // Hide loading. - loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null); - loading.Visibility = Visibility.Collapsed; - }); - }); - } - - /// - /// Click logo - /// - /// - /// - private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) { - var element = e.OriginalSource as FrameworkElement; - if (element == null) return; - - var pos = PointToScreen(new Point(0, 33)); - SystemCommands.ShowSystemMenu(this, pos); - } - - /// - /// Minimize - /// - private void Minimize(object sender, RoutedEventArgs e) { - SystemCommands.MinimizeWindow(this); - } - - /// - /// Maximize/Restore - /// - private void MaximizeOrRestore(object sender, RoutedEventArgs e) { - if (WindowState == WindowState.Normal) { - SystemCommands.MaximizeWindow(this); - } else { - SystemCommands.RestoreWindow(this); - } - } - - /// - /// Quit - /// - private void Quit(object sender, RoutedEventArgs e) { - Close(); - } - - /// - /// Sync scroll - /// - /// - /// - private void SyncScrollChanged(object sender, ScrollChangedEventArgs e) { - if (e.VerticalChange != 0) { - var margin = new Thickness(4, -e.VerticalOffset, 4, 0); - lineNumber.Margin = margin; - } - } - - /// - /// Mouse wheel - /// - /// - /// - private void MouseWheelOnContent(object sender, MouseWheelEventArgs e) { - if (e.Delta > 0) { - content.LineUp(); - } else { - content.LineDown(); - } - - e.Handled = true; - } - - /// - /// Content size changed. - /// - /// - /// - private void ContentSizeChanged(object sender, SizeChangedEventArgs e) { - if (content.Document.PageWidth < content.ActualWidth) { - content.Document.PageWidth = content.ActualWidth; - } - } - - /// - /// Auto scroll when selection changed. - /// - /// - /// - private void ContentSelectionChanged(object sender, RoutedEventArgs e) { - var doc = sender as RichTextBox; - if (doc == null || doc.IsFocused == false) return; - - if (Mouse.LeftButton == MouseButtonState.Pressed && !doc.Selection.IsEmpty) { - var p = Mouse.GetPosition(doc); - - if (p.X <= 8) { - doc.LineLeft(); - } else if (p.X >= doc.ActualWidth - 8) { - doc.LineRight(); - } - - if (p.Y <= 8) { - doc.LineUp(); - } else if (p.Y >= doc.ActualHeight - 8) { - doc.LineDown(); - } - } - } - } -} diff --git a/SourceGit/UI/CherryPick.xaml b/SourceGit/UI/CherryPick.xaml deleted file mode 100644 index c22fd9b7..00000000 --- a/SourceGit/UI/CherryPick.xaml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/UI/CherryPick.xaml.cs b/SourceGit/UI/CherryPick.xaml.cs deleted file mode 100644 index 9491bdc2..00000000 --- a/SourceGit/UI/CherryPick.xaml.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Cherry pick commit dialog. - /// - public partial class CherryPick : UserControl { - private Git.Repository repo = null; - private string commitSHA = null; - - /// - /// Constructor. - /// - /// - /// - public CherryPick(Git.Repository opened, Git.Commit commit) { - InitializeComponent(); - - repo = opened; - commitSHA = commit.SHA; - desc.Content = $"{commit.ShortSHA} {commit.Subject}"; - } - - /// - /// Display this dialog. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Commit commit) { - var popup = App.GetPopupManager(repo); - popup?.Show(new CherryPick(repo, commit)); - } - - /// - /// Start pick. - /// - /// - /// - private void Start(object sender, RoutedEventArgs e) { - repo.CherryPick(commitSHA, chkCommitChanges.IsChecked != true); - - var popup = App.GetPopupManager(repo); - popup?.Close(); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Close(); - } - } -} diff --git a/SourceGit/UI/Clone.xaml b/SourceGit/UI/Clone.xaml deleted file mode 100644 index 9ba6304f..00000000 --- a/SourceGit/UI/Clone.xaml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Clone.xaml.cs b/SourceGit/UI/Clone.xaml.cs deleted file mode 100644 index fad40a03..00000000 --- a/SourceGit/UI/Clone.xaml.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Clone dialog. - /// - public partial class Clone : UserControl { - - /// - /// Remote repository - /// - public string RemoteUri { get; set; } - - /// - /// Parent folder. - /// - public string ParentFolder { get; set; } - - /// - /// Local name. - /// - public string LocalName { get; set; } - - /// - /// Remote name. - /// - public string RemoteName { get; set; } - - /// - /// Constructor. - /// - public Clone() { - ParentFolder = App.Preference.GitDefaultCloneDir; - InitializeComponent(); - } - - /// - /// Show clone dialog. - /// - public static void Show() { - var popup = App.GetPopupManager(null); - popup?.Show(new Clone()); - } - - /// - /// Select parent folder. - /// - /// - /// - private void SelectParentFolder(object sender, RoutedEventArgs e) { - var dialog = new System.Windows.Forms.FolderBrowserDialog(); - dialog.Description = "Git Repository URL"; - dialog.RootFolder = Environment.SpecialFolder.MyComputer; - dialog.ShowNewFolderButton = true; - - if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - txtParentFolder.Text = dialog.SelectedPath; - } - } - - /// - /// Start clone - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - txtUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtUrl)) return; - - txtParentFolder.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtParentFolder)) return; - - string repoName; - if (string.IsNullOrWhiteSpace(LocalName)) { - var from = RemoteUri.LastIndexOfAny(new char[] { '\\', '/' }); - if (from <= 0) return; - - var name = RemoteUri.Substring(from + 1); - repoName = name.Replace(".git", ""); - } else { - repoName = LocalName; - } - - string rName; - if (string.IsNullOrWhiteSpace(RemoteName)){ - rName = null; - } else { - rName = RemoteName; - } - - var popup = App.GetPopupManager(null); - popup.Lock(); - - var repo = await Task.Run(() => { - return Git.Repository.Clone(RemoteUri, ParentFolder, rName, repoName, popup.UpdateStatus); - }); - - if (repo == null) { - popup.Unlock(); - } else { - popup.Close(true); - repo.Open(); - } - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(null).Close(); - } - } -} diff --git a/SourceGit/UI/CommitViewer.xaml b/SourceGit/UI/CommitViewer.xaml deleted file mode 100644 index 715c44fd..00000000 --- a/SourceGit/UI/CommitViewer.xaml +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/CommitViewer.xaml.cs b/SourceGit/UI/CommitViewer.xaml.cs deleted file mode 100644 index 7d0ecaf9..00000000 --- a/SourceGit/UI/CommitViewer.xaml.cs +++ /dev/null @@ -1,504 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Navigation; - -namespace SourceGit.UI { - - /// - /// Commit detail viewer - /// - public partial class CommitViewer : UserControl { - private Git.Repository repo = null; - private Git.Commit commit = null; - private List cachedChanges = new List(); - private List displayChanges = new List(); - private string changeFilter = null; - - /// - /// Node for file tree. - /// - public class Node { - public string FilePath { get; set; } = ""; - public string OriginalPath { get; set; } = ""; - public string Name { get; set; } = ""; - public bool IsFile { get; set; } = false; - public bool IsNodeExpanded { get; set; } = true; - public Git.Change Change { get; set; } = null; - public Git.Commit.Object CommitObject { get; set; } = null; - public List Children { get; set; } = new List(); - } - - /// - /// Constructor. - /// - public CommitViewer() { - InitializeComponent(); - } - - #region DATA - public void SetData(Git.Repository opened, Git.Commit selected) { - repo = opened; - commit = selected; - - SetBaseInfo(commit); - - Task.Run(() => { - cachedChanges.Clear(); - cachedChanges = commit.GetChanges(repo); - - Dispatcher.Invoke(() => { - changeList1.ItemsSource = null; - changeList1.ItemsSource = cachedChanges; - }); - - LayoutChanges(); - SetRevisionFiles(commit.GetFiles(repo)); - }); - } - - private void Cleanup(object sender, RoutedEventArgs e) { - fileTree.ItemsSource = null; - changeList1.ItemsSource = null; - changeList2.ItemsSource = null; - displayChanges.Clear(); - cachedChanges.Clear(); - diffViewer.Reset(); - } - #endregion - - #region BASE_INFO - private void SetBaseInfo(Git.Commit commit) { - var parentIds = new List(); - foreach (var p in commit.Parents) parentIds.Add(p.Substring(0, 8)); - - SHA.Text = commit.SHA; - refs.ItemsSource = commit.Decorators; - parents.ItemsSource = parentIds; - author.Text = $"{commit.Author.Name} <{commit.Author.Email}>"; - authorTime.Text = commit.Author.Time; - committer.Text = $"{commit.Committer.Name} <{commit.Committer.Email}>"; - committerTime.Text = commit.Committer.Time; - subject.Text = commit.Subject; - message.Text = commit.Message.Trim(); - - if (commit.Decorators.Count == 0) lblRefs.Visibility = Visibility.Collapsed; - else lblRefs.Visibility = Visibility.Visible; - - if (commit.Committer.Email == commit.Author.Email && commit.Committer.Time == commit.Author.Time) { - committerRow.Height = new GridLength(0); - } else { - committerRow.Height = GridLength.Auto; - } - } - - private void NavigateParent(object sender, RequestNavigateEventArgs e) { - repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString); - e.Handled = true; - } - - #endregion - - #region CHANGES - private void LayoutChanges() { - displayChanges.Clear(); - - if (string.IsNullOrEmpty(changeFilter)) { - displayChanges.AddRange(cachedChanges); - } else { - foreach (var c in cachedChanges) { - if (c.Path.ToUpper().Contains(changeFilter)) displayChanges.Add(c); - } - } - - List changeTreeSource = new List(); - Dictionary folders = new Dictionary(); - bool isDefaultExpanded = displayChanges.Count < 50; - - foreach (var c in displayChanges) { - var sepIdx = c.Path.IndexOf('/'); - if (sepIdx == -1) { - Node node = new Node(); - node.FilePath = c.Path; - node.IsFile = true; - node.Name = c.Path; - node.Change = c; - node.IsNodeExpanded = isDefaultExpanded; - if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath; - changeTreeSource.Add(node); - } else { - Node lastFolder = null; - var start = 0; - - while (sepIdx != -1) { - var folder = c.Path.Substring(0, sepIdx); - if (folders.ContainsKey(folder)) { - lastFolder = folders[folder]; - } else if (lastFolder == null) { - lastFolder = new Node(); - lastFolder.FilePath = folder; - lastFolder.Name = folder.Substring(start); - lastFolder.IsNodeExpanded = isDefaultExpanded; - changeTreeSource.Add(lastFolder); - folders.Add(folder, lastFolder); - } else { - var folderNode = new Node(); - folderNode.FilePath = folder; - folderNode.Name = folder.Substring(start); - folderNode.IsNodeExpanded = isDefaultExpanded; - folders.Add(folder, folderNode); - lastFolder.Children.Add(folderNode); - lastFolder = folderNode; - } - - start = sepIdx + 1; - sepIdx = c.Path.IndexOf('/', start); - } - - Node node = new Node(); - node.FilePath = c.Path; - node.Name = c.Path.Substring(start); - node.IsFile = true; - node.Change = c; - if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath; - lastFolder.Children.Add(node); - } - } - - folders.Clear(); - SortTreeNodes(changeTreeSource); - - Dispatcher.Invoke(() => { - changeList2.ItemsSource = null; - changeList2.ItemsSource = displayChanges; - changeTree.ItemsSource = changeTreeSource; - diffViewer.Reset(); - }); - } - - private void SearchChangeFileTextChanged(object sender, TextChangedEventArgs e) { - changeFilter = txtChangeFilter.Text.ToUpper(); - Task.Run(() => LayoutChanges()); - } - - private void ChangeTreeItemSelected(object sender, RoutedPropertyChangedEventArgs e) { - diffViewer.Reset(); - - var node = e.NewValue as Node; - if (node == null || !node.IsFile) return; - - var start = $"{commit.SHA}^"; - if (commit.Parents.Count == 0) { - start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; - } - - diffViewer.Diff(repo, new DiffViewer.Option() { - RevisionRange = new string[] { start, commit.SHA }, - Path = node.FilePath, - OrgPath = node.OriginalPath - }); - } - - private void ChangeListSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count != 1) return; - - var change = e.AddedItems[0] as Git.Change; - if (change == null) return; - - var start = $"{commit.SHA}^"; - if (commit.Parents.Count == 0) { - start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; - } - - diffViewer.Diff(repo, new DiffViewer.Option() { - RevisionRange = new string[] { start, commit.SHA }, - Path = change.Path, - OrgPath = change.OriginalPath - }); - } - - private void ChangeListContextMenuOpening(object sender, ContextMenuEventArgs e) { - var row = sender as DataGridRow; - if (row == null) return; - - var change = row.DataContext as Git.Change; - if (change == null) return; - - var path = change.Path; - var menu = new ContextMenu(); - if (change.Index != Git.Change.Status.Deleted) { - MenuItem history = new MenuItem(); - history.Header = "File History"; - history.Click += (o, ev) => { - var viewer = new FileHistories(repo, path); - viewer.Show(); - }; - menu.Items.Add(history); - - MenuItem blame = new MenuItem(); - blame.Header = "Blame"; - blame.Click += (obj, ev) => { - Blame viewer = new Blame(repo, path, commit.SHA); - viewer.Show(); - }; - menu.Items.Add(blame); - - MenuItem explore = new MenuItem(); - explore.Header = "Reveal in File Explorer"; - explore.Click += (o, ev) => { - var absPath = Path.GetFullPath(repo.Path + "\\" + path); - Process.Start("explorer", $"/select,{absPath}"); - e.Handled = true; - }; - menu.Items.Add(explore); - - MenuItem saveAs = new MenuItem(); - saveAs.Header = "Save As ..."; - saveAs.Click += (obj, ev) => { - var dialog = new System.Windows.Forms.FolderBrowserDialog(); - dialog.Description = change.Path; - dialog.ShowNewFolderButton = true; - - if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - var savePath = Path.Combine(dialog.SelectedPath, Path.GetFileName(path)); - repo.RunAndRedirect($"show {commit.SHA}:\"{path}\"", savePath); - } - }; - menu.Items.Add(saveAs); - } - - MenuItem copyPath = new MenuItem(); - copyPath.Header = "Copy Path"; - copyPath.Click += (obj, ev) => { - Clipboard.SetText(path); - }; - menu.Items.Add(copyPath); - menu.IsOpen = true; - e.Handled = true; - } - - private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e) - { - var row = sender as DataGridRow; - if (row == null) return; - - var change = row.DataContext as Git.Change; - if (change == null) return; - - var viewer = new FileHistories(repo, change.Path); - viewer.Show(); - } - #endregion - - #region FILES - private void SetRevisionFiles(List files) { - List fileTreeSource = new List(); - Dictionary folders = new Dictionary(); - - foreach (var obj in files) { - var sepIdx = obj.Path.IndexOf("/"); - if (sepIdx == -1) { - Node node = new Node(); - node.FilePath = obj.Path; - node.Name = obj.Path; - node.IsFile = true; - node.IsNodeExpanded = false; - node.CommitObject = obj; - fileTreeSource.Add(node); - } else { - Node lastFolder = null; - var start = 0; - - while (sepIdx != -1) { - var folder = obj.Path.Substring(0, sepIdx); - if (folders.ContainsKey(folder)) { - lastFolder = folders[folder]; - } else if (lastFolder == null) { - lastFolder = new Node(); - lastFolder.FilePath = folder; - lastFolder.Name = folder.Substring(start); - lastFolder.IsNodeExpanded = false; - fileTreeSource.Add(lastFolder); - folders.Add(folder, lastFolder); - } else { - var folderNode = new Node(); - folderNode.FilePath = folder; - folderNode.Name = folder.Substring(start); - folderNode.IsNodeExpanded = false; - folders.Add(folder, folderNode); - lastFolder.Children.Add(folderNode); - lastFolder = folderNode; - } - - start = sepIdx + 1; - sepIdx = obj.Path.IndexOf('/', start); - } - - Node node = new Node(); - node.FilePath = obj.Path; - node.Name = obj.Path.Substring(start); - node.IsFile = true; - node.IsNodeExpanded = false; - node.CommitObject = obj; - lastFolder.Children.Add(node); - } - } - - folders.Clear(); - SortTreeNodes(fileTreeSource); - - Dispatcher.Invoke(() => { - fileTree.ItemsSource = fileTreeSource; - filePreview.Text = ""; - }); - } - - private async void FileTreeItemSelected(object sender, RoutedPropertyChangedEventArgs e) { - filePreview.Text = ""; - maskPreviewNotSupported.Visibility = Visibility.Collapsed; - maskRevision.Visibility = Visibility.Collapsed; - - var node = e.NewValue as Node; - if (node == null || !node.IsFile || node.CommitObject == null) return; - - switch (node.CommitObject.Kind) { - case Git.Commit.Object.Type.Blob: - await Task.Run(() => { - var isBinary = false; - var data = commit.GetTextFileContent(repo, node.FilePath, out isBinary); - - if (isBinary) { - Dispatcher.Invoke(() => maskPreviewNotSupported.Visibility = Visibility.Visible); - } else { - Dispatcher.Invoke(() => filePreview.Text = data); - } - }); - break; - case Git.Commit.Object.Type.Tag: - maskRevision.Visibility = Visibility.Visible; - iconPreviewRevision.Data = FindResource("Icon.Tag") as Geometry; - txtPreviewRevision.Content = "TAG: " + node.CommitObject.SHA; - break; - case Git.Commit.Object.Type.Commit: - maskRevision.Visibility = Visibility.Visible; - iconPreviewRevision.Data = FindResource("Icon.Submodule") as Geometry; - txtPreviewRevision.Content = "SUBMODULE: " + node.CommitObject.SHA; - break; - default: - return; - } - } - #endregion - - #region TREE_COMMON - private void SortTreeNodes(List list) { - list.Sort((l, r) => { - if (l.IsFile) { - return r.IsFile ? l.Name.CompareTo(r.Name) : 1; - } else { - return r.IsFile ? -1 : l.Name.CompareTo(r.Name); - } - }); - - foreach (var sub in list) { - if (sub.Children.Count > 0) SortTreeNodes(sub.Children); - } - } - - private ScrollViewer GetScrollViewer(FrameworkElement owner) { - if (owner == null) return null; - if (owner is ScrollViewer) return owner as ScrollViewer; - - int n = VisualTreeHelper.GetChildrenCount(owner); - for (int i = 0; i < n; i++) { - var child = VisualTreeHelper.GetChild(owner, i) as FrameworkElement; - var deep = GetScrollViewer(child); - if (deep != null) return deep; - } - - return null; - } - - private void TreeMouseWheel(object sender, MouseWheelEventArgs e) { - var scroll = GetScrollViewer(sender as TreeView); - if (scroll == null) return; - - if (e.Delta > 0) { - scroll.LineUp(); - } else { - scroll.LineDown(); - } - - e.Handled = true; - } - - private void TreeContextMenuOpening(object sender, ContextMenuEventArgs e) { - var item = sender as TreeViewItem; - if (item == null) return; - - var node = item.DataContext as Node; - if (node == null || !node.IsFile) return; - - item.IsSelected = true; - - ContextMenu menu = new ContextMenu(); - if (node.Change == null || node.Change.Index != Git.Change.Status.Deleted) { - MenuItem history = new MenuItem(); - history.Header = "File History"; - history.Click += (o, ev) => { - var viewer = new FileHistories(repo, node.FilePath); - viewer.Show(); - }; - menu.Items.Add(history); - - MenuItem blame = new MenuItem(); - blame.Header = "Blame"; - blame.Click += (obj, ev) => { - Blame viewer = new Blame(repo, node.FilePath, commit.SHA); - viewer.Show(); - }; - menu.Items.Add(blame); - - MenuItem explore = new MenuItem(); - explore.Header = "Reveal in File Explorer"; - explore.Click += (o, ev) => { - var path = Path.GetFullPath(repo.Path + "\\" + node.FilePath); - Process.Start("explorer", $"/select,{path}"); - e.Handled = true; - }; - menu.Items.Add(explore); - - MenuItem saveAs = new MenuItem(); - saveAs.Header = "Save As ..."; - saveAs.IsEnabled = node.CommitObject == null || node.CommitObject.Kind == Git.Commit.Object.Type.Blob; - saveAs.Click += (obj, ev) => { - var dialog = new System.Windows.Forms.FolderBrowserDialog(); - dialog.Description = node.FilePath; - dialog.ShowNewFolderButton = true; - - if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - var path = Path.Combine(dialog.SelectedPath, node.Name); - repo.RunAndRedirect($"show {commit.SHA}:\"{node.FilePath}\"", path); - } - }; - menu.Items.Add(saveAs); - } - - MenuItem copyPath = new MenuItem(); - copyPath.Header = "Copy Path"; - copyPath.Click += (obj, ev) => { - Clipboard.SetText(node.FilePath); - }; - menu.Items.Add(copyPath); - menu.IsOpen = true; - e.Handled = true; - } - #endregion - - } -} diff --git a/SourceGit/UI/Configure.xaml b/SourceGit/UI/Configure.xaml deleted file mode 100644 index 8bd23ef3..00000000 --- a/SourceGit/UI/Configure.xaml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Configure.xaml.cs b/SourceGit/UI/Configure.xaml.cs deleted file mode 100644 index b16c3157..00000000 --- a/SourceGit/UI/Configure.xaml.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Repository configuration dialog - /// - public partial class Configure : UserControl { - private Git.Repository repo = null; - - /// - /// User name for this repository. - /// - public string UserName { get; set; } - - /// - /// User email for this repository. - /// - public string UserEmail { get; set; } - - /// - /// Commit template for this repository. - /// - public string CommitTemplate { get; set; } - - /// - /// Constructor. - /// - /// - public Configure(Git.Repository repo) { - this.repo = repo; - - UserName = repo.GetConfig("user.name"); - UserEmail = repo.GetConfig("user.email"); - CommitTemplate = repo.CommitTemplate; - - InitializeComponent(); - } - - /// - /// Show this dialog. - /// - /// - public static void Show(Git.Repository repo) { - var popup = App.GetPopupManager(repo); - popup?.Show(new Configure(repo)); - } - - #region EVENTS - private void Save(object sender, RoutedEventArgs e) { - var oldUser = repo.GetConfig("user.name"); - if (oldUser != UserName) repo.SetConfig("user.name", UserName); - - var oldEmail = repo.GetConfig("user.email"); - if (oldEmail != UserEmail) repo.SetConfig("user.email", UserEmail); - - if (CommitTemplate != repo.CommitTemplate) { - repo.CommitTemplate = CommitTemplate; - Git.Preference.Save(); - } - - Close(sender, e); - } - - private void Close(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - #endregion - } -} diff --git a/SourceGit/UI/CreateBranch.xaml b/SourceGit/UI/CreateBranch.xaml deleted file mode 100644 index 7f71f6ff..00000000 --- a/SourceGit/UI/CreateBranch.xaml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/CreateBranch.xaml.cs b/SourceGit/UI/CreateBranch.xaml.cs deleted file mode 100644 index 5efc18cf..00000000 --- a/SourceGit/UI/CreateBranch.xaml.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace SourceGit.UI { - - /// - /// Create branch dialog - /// - public partial class CreateBranch : UserControl { - private Git.Repository repo = null; - private string based = null; - - /// - /// New branch name. - /// - public string BranchName { - get; - set; - } - - /// - /// Auto Stash - /// - public bool AutoStash { get; set; } = false; - - /// - /// Constructor. - /// - /// Opened repository - public CreateBranch(Git.Repository opened) { - InitializeComponent(); - - repo = opened; - nameValidator.Repo = opened; - } - - /// - /// Create branch based on current head. - /// - /// - public static void Show(Git.Repository repo) { - var current = repo.CurrentBranch(); - if (current != null) Show(repo, current); - } - - /// - /// Create branch base on existed one. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Branch branch) { - var dialog = new CreateBranch(repo); - dialog.based = branch.Name; - dialog.basedOnType.Data = dialog.FindResource("Icon.Branch") as Geometry; - dialog.basedOnDesc.Content = branch.Name; - - if (!branch.IsLocal) dialog.txtName.Text = branch.Name.Substring(branch.Remote.Length + 1); - - var popup = App.GetPopupManager(repo); - popup?.Show(dialog); - } - - /// - /// Create branch based on tag. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Tag tag) { - var dialog = new CreateBranch(repo); - dialog.based = tag.Name; - dialog.basedOnType.Data = dialog.FindResource("Icon.Tag") as Geometry; - dialog.basedOnDesc.Content = tag.Name; - - var popup = App.GetPopupManager(repo); - popup?.Show(dialog); - } - - /// - /// Create branch based on commit. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Commit commit) { - var dialog = new CreateBranch(repo); - dialog.based = commit.SHA; - dialog.basedOnType.Data = dialog.FindResource("Icon.Commit") as Geometry; - dialog.basedOnDesc.Content = $"{commit.ShortSHA} {commit.Subject}"; - - var popup = App.GetPopupManager(repo); - popup?.Show(dialog); - } - - /// - /// Start create branch. - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtName)) return; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - - bool checkout = chkCheckout.IsChecked == true; - await Task.Run(() => { - if (checkout) { - bool stashed = false; - - if (repo.LocalChanges().Count > 0 && AutoStash) { - Git.Stash.Push(repo, true, "CREATE BRANCH AUTO STASH", new List()); - stashed = true; - } - - repo.Checkout($"-b {BranchName} {based}"); - - if (stashed) { - var stashes = repo.Stashes(); - if (stashes.Count > 0) stashes[0].Pop(repo); - } - } else { - Git.Branch.Create(repo, BranchName, based); - } - }); - - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/CreateTag.xaml b/SourceGit/UI/CreateTag.xaml deleted file mode 100644 index 3d701ec6..00000000 --- a/SourceGit/UI/CreateTag.xaml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/CreateTag.xaml.cs b/SourceGit/UI/CreateTag.xaml.cs deleted file mode 100644 index bf2ef238..00000000 --- a/SourceGit/UI/CreateTag.xaml.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace SourceGit.UI { - - /// - /// Create tag dialog - /// - public partial class CreateTag : UserControl { - private Git.Repository repo = null; - private string based = null; - - /// - /// Tag name - /// - public string TagName { get; set; } - - /// - /// Constructor. - /// - /// - public CreateTag(Git.Repository opened) { - InitializeComponent(); - - repo = opened; - nameValidator.Repo = opened; - } - - /// - /// Create tag using current branch. - /// - /// Opened repository. - public static void Show(Git.Repository repo) { - Show(repo, repo.Branches().First(b => b.IsCurrent)); - } - - /// - /// Create tag using branch - /// - /// - /// - public static void Show(Git.Repository repo, Git.Branch branch) { - if (branch == null) { - App.RaiseError("Empty repository!"); - return; - } - - var dialog = new CreateTag(repo); - dialog.based = branch.Head; - dialog.basedOnType.Data = dialog.FindResource("Icon.Branch") as Geometry; - dialog.basedOnDesc.Content = branch.Name; - - var popup = App.GetPopupManager(repo); - popup?.Show(dialog); - } - - /// - /// Create tag using commit. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Commit commit) { - var dialog = new CreateTag(repo); - dialog.based = commit.SHA; - dialog.basedOnType.Data = dialog.FindResource("Icon.Commit") as Geometry; - dialog.basedOnDesc.Content = $"{commit.ShortSHA} {commit.Subject}"; - - var popup = App.GetPopupManager(repo); - popup?.Show(dialog); - } - - /// - /// Start to create tag. - /// - /// - /// - private void Start(object sender, RoutedEventArgs e) { - tagName.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(tagName)) return; - - Git.Tag.Add(repo, TagName, based, tagMessage.Text); - - var popup = App.GetPopupManager(repo); - popup?.Close(); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/Dashboard.xaml b/SourceGit/UI/Dashboard.xaml deleted file mode 100644 index fb8446ee..00000000 --- a/SourceGit/UI/Dashboard.xaml +++ /dev/null @@ -1,526 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Dashboard.xaml.cs b/SourceGit/UI/Dashboard.xaml.cs deleted file mode 100644 index 8cc33b22..00000000 --- a/SourceGit/UI/Dashboard.xaml.cs +++ /dev/null @@ -1,1067 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Threading; - -namespace SourceGit.UI { - - /// - /// Branch node in tree. - /// - public class BranchNode { - public string Name { get; set; } - public Git.Branch Branch { get; set; } - public bool IsExpanded { get; set; } - public bool IsCurrent => Branch != null ? Branch.IsCurrent : false; - public bool IsFiltered => Branch != null ? Branch.IsFiltered : false; - public string Track => Branch != null ? Branch.UpstreamTrack : ""; - public Visibility FilterVisibility => Branch == null ? Visibility.Collapsed : Visibility.Visible; - public Visibility TrackVisibility => (Branch != null && !Branch.IsSameWithUpstream) ? Visibility.Visible : Visibility.Collapsed; - public List Children { get; set; } - } - - /// - /// Remote node in tree. - /// - public class RemoteNode { - public string Name { get; set; } - public bool IsExpanded { get; set; } - public List Children { get; set; } - } - - /// - /// Dashboard for opened repository. - /// - public partial class Dashboard : UserControl { - private Git.Repository repo = null; - private List cachedLocalBranches = new List(); - private List cachedRemotes = new List(); - private string abortCommand = null; - - /// - /// Constructor. - /// - /// Opened repository. - public Dashboard(Git.Repository opened) { - opened.OnWorkingCopyChanged = UpdateLocalChanges; - opened.OnTagChanged = UpdateTags; - opened.OnStashChanged = UpdateStashes; - opened.OnBranchChanged = () => UpdateBranches(false); - opened.OnCommitsChanged = UpdateHistories; - opened.OnSubmoduleChanged = UpdateSubmodules; - opened.OnNavigateCommit = commit => { - Dispatcher.Invoke(() => { - workspace.SelectedItem = historiesSwitch; - histories.Navigate(commit); - }); - }; - - InitializeComponent(); - - repo = opened; - histories.Repo = opened; - commits.Repo = opened; - - UpdateBranches(); - UpdateHistories(); - UpdateLocalChanges(); - UpdateStashes(); - UpdateTags(); - UpdateSubmodules(); - } - - #region DATA_UPDATE - private void UpdateHistories() { - Dispatcher.Invoke(() => { - histories.SetLoadingEnabled(true); - }); - - Task.Run(() => { - var args = "-8000 "; - if (repo.LogFilters.Count > 0) { - args = args + string.Join(" ", repo.LogFilters); - } else { - args = args + "--branches --remotes --tags"; - } - - var commits = repo.Commits(args); - histories.SetCommits(commits); - }); - } - - private void UpdateLocalChanges() { - Task.Run(() => { - var changes = repo.LocalChanges(); - var conflicts = commits.SetData(changes); - - Dispatcher.Invoke(() => { - localChangesBadge.Visibility = changes.Count == 0 ? Visibility.Collapsed : Visibility.Visible; - localChangesCount.Content = changes.Count; - btnContinue.Visibility = conflicts ? Visibility.Collapsed : Visibility.Visible; - DetectMergeState(); - }); - }); - } - - private void UpdateStashes() { - Task.Run(() => { - var data = repo.Stashes(); - Dispatcher.Invoke(() => { - stashBadge.Visibility = data.Count > 0 ? Visibility.Visible : Visibility.Collapsed; - stashCount.Content = data.Count; - stashes.SetData(repo, data); - }); - }); - } - - private void BackupBranchNodeExpandState(Dictionary states, List nodes, string prefix) { - foreach (var node in nodes) { - var path = prefix + "/" + node.Name; - states.Add(path, node.IsExpanded); - BackupBranchNodeExpandState(states, node.Children, path); - } - } - - private void MakeBranchNode(Git.Branch branch, List collection, Dictionary folders, Dictionary expandStates, string prefix) { - var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - if (!branch.IsLocal) { - if (subs.Length < 2) return; - subs = subs.Skip(1).ToArray(); - } - - branch.IsFiltered = repo.LogFilters.Contains(branch.FullName); - - if (subs.Length == 1) { - var node = new BranchNode() { - Name = subs[0], - Branch = branch, - Children = new List(), - }; - collection.Add(node); - } else { - BranchNode lastFolder = null; - string path = prefix; - for (int i = 0; i < subs.Length - 1; i++) { - path = path + "/" + subs[i]; - if (folders.ContainsKey(path)) { - lastFolder = folders[path]; - } else if (lastFolder == null) { - lastFolder = new BranchNode() { - Name = subs[i], - IsExpanded = expandStates.ContainsKey(path) ? expandStates[path] : false, - Children = new List(), - }; - collection.Add(lastFolder); - folders.Add(path, lastFolder); - } else { - var folder = new BranchNode() { - Name = subs[i], - IsExpanded = expandStates.ContainsKey(path) ? expandStates[path] : false, - Children = new List(), - }; - lastFolder.Children.Add(folder); - folders.Add(path, folder); - lastFolder = folder; - } - } - - BranchNode node = new BranchNode(); - node.Name = subs[subs.Length - 1]; - node.Branch = branch; - node.Children = new List(); - lastFolder.Children.Add(node); - } - } - - private void SortBranchNodes(List collection) { - collection.Sort((l, r) => { - if (l.Branch != null) { - return r.Branch != null ? l.Branch.Name.CompareTo(r.Branch.Name) : -1; - } else { - return r.Branch == null ? l.Name.CompareTo(r.Name) : 1; - } - }); - - foreach (var sub in collection) { - if (sub.Children.Count > 0) SortBranchNodes(sub.Children); - } - } - - private void UpdateBranches(bool force = true) { - bool IsDetached = false; - Git.Branch branch = null; - Task.Run(() => { - var branches = repo.Branches(force); - var remotes = repo.Remotes(true); - var localBranchNodes = new List(); - var remoteNodes = new List(); - var remoteMap = new Dictionary(); - var folders = new Dictionary(); - var states = new Dictionary(); - - BackupBranchNodeExpandState(states, cachedLocalBranches, "locals"); - foreach (var r in cachedRemotes) { - var prefix = $"remotes/{r.Name}"; - states.Add(prefix, r.IsExpanded); - BackupBranchNodeExpandState(states, r.Children, prefix); - } - - foreach (var b in branches) { - if (b.IsLocal) { - MakeBranchNode(b, localBranchNodes, folders, states, "locals"); - branch = b; - } else if (!string.IsNullOrEmpty(b.Remote)) { - RemoteNode remote = null; - - if (!remoteMap.ContainsKey(b.Remote)) { - var key = "remotes/" + b.Remote; - remote = new RemoteNode() { - Name = b.Remote, - IsExpanded = states.ContainsKey(key) ? states[key] : false, - Children = new List(), - }; - remoteNodes.Add(remote); - remoteMap.Add(b.Remote, remote); - } else { - remote = remoteMap[b.Remote]; - } - - MakeBranchNode(b, remote.Children, folders, states, "remotes"); - } else { - /// 对于 SUBMODULE HEAD 出于游离状态(detached on commit id) - /// 此时,分支既不是 本地分支,也不是远程分支 - IsDetached = b.IsCurrent; - } - } - - foreach (var r in remotes) { - if (!remoteMap.ContainsKey(r.Name)) { - var remote = new RemoteNode() { - Name = r.Name, - IsExpanded = false, - Children = new List(), - }; - remoteNodes.Add(remote); - } - } - - SortBranchNodes(localBranchNodes); - foreach (var r in remoteNodes) SortBranchNodes(r.Children); - - cachedLocalBranches = localBranchNodes; - cachedRemotes = remoteNodes; - - Dispatcher.Invoke(() => { - localBranchTree.ItemsSource = localBranchNodes; - remoteBranchTree.ItemsSource = remoteNodes; - }); - - if (IsDetached && branch != null) repo.Checkout(branch.Name); - }); - } - - private void UpdateTags() { - Task.Run(() => { - var tags = repo.Tags(true); - foreach (var t in tags) t.IsFiltered = repo.LogFilters.Contains(t.Name); - - Dispatcher.Invoke(() => { - tagCount.Content = $"TAGS ({tags.Count})"; - tagList.ItemsSource = tags; - }); - }); - } - - private void UpdateSubmodules() { - Task.Run(() => { - var submodules = repo.Submodules(); - Dispatcher.Invoke(() => { - submoduleCount.Content = $"SUBMODULES ({submodules.Count})"; - submoduleList.ItemsSource = submodules; - }); - }); - } - - private void Cleanup(object sender, RoutedEventArgs e) { - localBranchTree.ItemsSource = null; - remoteBranchTree.ItemsSource = null; - tagList.ItemsSource = null; - cachedLocalBranches.Clear(); - cachedRemotes.Clear(); - } - #endregion - - #region TOOLBAR - private void OpenFetch(object sender, RoutedEventArgs e) { - Fetch.Show(repo); - } - - private void OpenPull(object sender, RoutedEventArgs e) { - Pull.Show(repo); - } - - private void OpenPush(object sender, RoutedEventArgs e) { - Push.Show(repo); - } - - private void OpenStash(object sender, RoutedEventArgs e) { - Stash.Show(repo, new List()); - } - - private void OpenApply(object sender, RoutedEventArgs e) { - Apply.Show(repo); - } - - private void OpenSearch(object sender, RoutedEventArgs e) { - if (popupManager.IsLocked()) return; - - workspace.SelectedItem = historiesSwitch; - if (histories.searchBar.Margin.Top == 0) { - histories.HideSearchBar(); - } else { - histories.OpenSearchBar(); - } - } - - private void OpenConfigure(object sender, RoutedEventArgs e) { - Configure.Show(repo); - } - - private void OpenExplorer(object sender, RoutedEventArgs e) { - Process.Start(repo.Path); - } - - private void OpenTerminal(object sender, RoutedEventArgs e) { - var bash = Path.Combine(App.Preference.GitExecutable, "..", "bash.exe"); - if (!File.Exists(bash)) { - App.RaiseError("Can NOT locate bash.exe. Make sure bash.exe exists under the same folder with git.exe"); - return; - } - - var start = new ProcessStartInfo(); - start.WorkingDirectory = repo.Path; - start.FileName = bash; - Process.Start(start); - } - #endregion - - #region HOT_KEYS - public void OpenSearchBar(object sender, ExecutedRoutedEventArgs e) { - workspace.SelectedItem = historiesSwitch; - histories.OpenSearchBar(); - } - - public void HideSearchBar(object sender, ExecutedRoutedEventArgs e) { - if (histories.Visibility == Visibility.Visible) { - histories.HideSearchBar(); - } - } - #endregion - - #region MERGE_ABORTS - public void DetectMergeState() { - var cherryPickMerge = Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD"); - var rebaseMerge = Path.Combine(repo.GitDir, "REBASE_HEAD"); - var revertMerge = Path.Combine(repo.GitDir, "REVERT_HEAD"); - var otherMerge = Path.Combine(repo.GitDir, "MERGE_HEAD"); - - if (File.Exists(cherryPickMerge)) { - abortCommand = "cherry-pick"; - txtMergeProcessing.Content = "Cherry-Pick merge request detected! Press 'Abort' to restore original HEAD"; - } else if (File.Exists(rebaseMerge)) { - abortCommand = "rebase"; - txtMergeProcessing.Content = "Rebase merge request detected! Press 'Abort' to restore original HEAD"; - } else if (File.Exists(revertMerge)) { - abortCommand = "revert"; - txtMergeProcessing.Content = "Revert merge request detected! Press 'Abort' to restore original HEAD"; - } else if (File.Exists(otherMerge)) { - abortCommand = "merge"; - txtMergeProcessing.Content = "Merge request detected! Press 'Abort' to restore original HEAD"; - } else { - abortCommand = null; - } - - if (abortCommand != null) { - abortPanel.Visibility = Visibility.Visible; - if (commits.Visibility == Visibility.Visible) { - btnResolve.Visibility = Visibility.Collapsed; - } else { - btnResolve.Visibility = Visibility.Visible; - } - - commits.LoadMergeMessage(); - } else { - abortPanel.Visibility = Visibility.Collapsed; - } - } - - private void Resolve(object sender, RoutedEventArgs e) { - workspace.SelectedItem = workingCopySwitch; - } - - private async void Continue(object sender, RoutedEventArgs e) { - if (abortCommand == null) return; - - await Task.Run(() => { - repo.SetWatcherEnabled(false); - var errs = repo.RunCommand($"-c core.editor=true {abortCommand} --continue", null); - repo.AssertCommand(errs); - }); - - commits.ClearMessage(); - } - - private async void Abort(object sender, RoutedEventArgs e) { - if (abortCommand == null) return; - - await Task.Run(() => { - repo.SetWatcherEnabled(false); - var errs = repo.RunCommand($"{abortCommand} --abort", null); - repo.AssertCommand(errs); - }); - - commits.ClearMessage(); - } - #endregion - - #region WORKSPACE - private void SwitchWorkingCopy(object sender, RoutedEventArgs e) { - if (commits == null || histories == null || stashes == null) return; - - commits.Visibility = Visibility.Visible; - histories.Visibility = Visibility.Collapsed; - stashes.Visibility = Visibility.Collapsed; - - if (abortPanel.Visibility == Visibility.Visible) { - btnResolve.Visibility = Visibility.Collapsed; - } - } - - private void SwitchHistories(object sender, RoutedEventArgs e) { - if (commits == null || histories == null || stashes == null) return; - - commits.Visibility = Visibility.Collapsed; - histories.Visibility = Visibility.Visible; - stashes.Visibility = Visibility.Collapsed; - - if (abortPanel.Visibility == Visibility.Visible) { - btnResolve.Visibility = Visibility.Visible; - } - } - - private void SwitchStashes(object sender, RoutedEventArgs e) { - if (commits == null || histories == null || stashes == null) return; - - commits.Visibility = Visibility.Collapsed; - histories.Visibility = Visibility.Collapsed; - stashes.Visibility = Visibility.Visible; - - if (abortPanel.Visibility == Visibility.Visible) { - btnResolve.Visibility = Visibility.Visible; - } - } - #endregion - - #region LOCAL_BRANCHES - private void OpenNewBranch(object sender, RoutedEventArgs e) { - CreateBranch.Show(repo); - } - - private void OpenGitFlow(object sender, RoutedEventArgs ev) { - var button = sender as Button; - if (button.ContextMenu == null) { - button.ContextMenu = new ContextMenu(); - button.ContextMenu.PlacementTarget = button; - button.ContextMenu.Placement = PlacementMode.Bottom; - button.ContextMenu.StaysOpen = false; - button.ContextMenu.Focusable = true; - } else { - button.ContextMenu.Items.Clear(); - } - - if (repo.IsGitFlowEnabled()) { - var startFeature = new MenuItem(); - startFeature.Header = "Start Feature ..."; - startFeature.Click += (o, e) => { - GitFlowStartBranch.Show(repo, Git.Branch.Type.Feature); - e.Handled = true; - }; - - var startRelease = new MenuItem(); - startRelease.Header = "Start Release ..."; - startRelease.Click += (o, e) => { - GitFlowStartBranch.Show(repo, Git.Branch.Type.Release); - e.Handled = true; - }; - - var startHotfix = new MenuItem(); - startHotfix.Header = "Start Hotfix ..."; - startHotfix.Click += (o, e) => { - GitFlowStartBranch.Show(repo, Git.Branch.Type.Hotfix); - e.Handled = true; - }; - - button.ContextMenu.Items.Add(startFeature); - button.ContextMenu.Items.Add(startRelease); - button.ContextMenu.Items.Add(startHotfix); - } else { - var init = new MenuItem(); - init.Header = "Initialize Git-Flow"; - init.Click += (o, e) => { - GitFlowSetup.Show(repo); - e.Handled = true; - }; - button.ContextMenu.Items.Add(init); - } - - button.ContextMenu.IsOpen = true; - ev.Handled = true; - } - - private void LocalBranchSelected(object sender, RoutedPropertyChangedEventArgs e) { - var node = e.NewValue as BranchNode; - if (node == null || node.Branch == null) return; - repo.OnNavigateCommit?.Invoke(node.Branch.Head); - } - - private void LocalBranchMouseDoubleClick(object sender, MouseButtonEventArgs e) { - var node = (sender as TreeViewItem).DataContext as BranchNode; - if (node == null || node.Branch == null) return; - Task.Run(() => repo.Checkout(node.Branch.Name)); - } - - private void LocalBranchContextMenuOpening(object sender, ContextMenuEventArgs ev) { - var node = (sender as TreeViewItem).DataContext as BranchNode; - if (node == null || node.Branch == null) return; - - var menu = new ContextMenu(); - var branch = node.Branch; - - var push = new MenuItem(); - push.Header = $"Push '{branch.Name}'"; - push.Click += (o, e) => { - Push.Show(repo, branch); - e.Handled = true; - }; - - if (branch.IsCurrent) { - var discard = new MenuItem(); - discard.Header = "Discard all changes"; - discard.Click += (o, e) => { - Discard.Show(repo, null); - e.Handled = true; - }; - menu.Items.Add(discard); - menu.Items.Add(new Separator()); - - if (!string.IsNullOrEmpty(branch.Upstream)) { - var upstream = branch.Upstream.Substring(13); - var fastForward = new MenuItem(); - fastForward.Header = $"Fast-Forward to '{upstream}'"; - fastForward.Click += (o, e) => { - Merge.StartDirectly(repo, upstream, branch.Name); - e.Handled = true; - }; - - var pull = new MenuItem(); - pull.Header = $"Pull '{upstream}'"; - pull.Click += (o, e) => { - Pull.Show(repo); - e.Handled = true; - }; - - menu.Items.Add(fastForward); - menu.Items.Add(pull); - } - - menu.Items.Add(push); - } else { - var current = repo.CurrentBranch(); - - var checkout = new MenuItem(); - checkout.Header = $"Checkout {branch.Name}"; - checkout.Click += (o, e) => { - Task.Run(() => repo.Checkout(node.Branch.Name)); - e.Handled = true; - }; - menu.Items.Add(checkout); - menu.Items.Add(new Separator()); - menu.Items.Add(push); - - var merge = new MenuItem(); - merge.Header = $"Merge '{branch.Name}' into '{current.Name}'"; - merge.Click += (o, e) => { - Merge.Show(repo, branch.Name, current.Name); - e.Handled = true; - }; - menu.Items.Add(merge); - - var rebase = new MenuItem(); - rebase.Header = $"Rebase '{current.Name}' on '{branch.Name}'"; - rebase.Click += (o, e) => { - Rebase.Show(repo, branch); - e.Handled = true; - }; - menu.Items.Add(rebase); - } - - if (branch.Kind != Git.Branch.Type.Normal) { - menu.Items.Add(new Separator()); - - var icon = new System.Windows.Shapes.Path(); - icon.Style = FindResource("Style.Icon") as Style; - icon.Data = FindResource("Icon.Flow") as Geometry; - icon.Width = 10; - - var finish = new MenuItem(); - finish.Header = $"Git Flow - Finish '{branch.Name}'"; - finish.Icon = icon; - finish.Click += (o, e) => { - GitFlowFinishBranch.Show(repo, branch); - e.Handled = true; - }; - - menu.Items.Add(finish); - } - - var rename = new MenuItem(); - rename.Header = $"Rename '{branch.Name}'"; - rename.Click += (o, e) => { - RenameBranch.Show(repo, branch); - e.Handled = true; - }; - menu.Items.Add(new Separator()); - menu.Items.Add(rename); - - var delete = new MenuItem(); - delete.Header = $"Delete '{branch.Name}'"; - delete.IsEnabled = !branch.IsCurrent; - delete.Click += (o, e) => { - DeleteBranch.Show(repo, branch); - e.Handled = true; - }; - menu.Items.Add(delete); - menu.Items.Add(new Separator()); - - var createBranch = new MenuItem(); - createBranch.Header = "Create Branch"; - createBranch.Click += (o, e) => { - CreateBranch.Show(repo, branch); - e.Handled = true; - }; - menu.Items.Add(createBranch); - - var createTag = new MenuItem(); - createTag.Header = "Create Tag"; - createTag.Click += (o, e) => { - CreateTag.Show(repo, branch); - e.Handled = true; - }; - menu.Items.Add(createTag); - menu.Items.Add(new Separator()); - - var copy = new MenuItem(); - copy.Header = "Copy Branch Name"; - copy.Click += (o, e) => { - Clipboard.SetText(branch.Name); - e.Handled = true; - }; - menu.Items.Add(copy); - - menu.IsOpen = true; - ev.Handled = true; - } - #endregion - - #region REMOTE_BRANCHES - private void OpenRemote(object sender, RoutedEventArgs e) { - Remote.Show(repo); - } - - private void OpenRemoteContextMenu(RemoteNode node) { - var fetch = new MenuItem(); - fetch.Header = $"Fetch '{node.Name}'"; - fetch.Click += (o, e) => { - Fetch.Show(repo, node.Name); - e.Handled = true; - }; - - var edit = new MenuItem(); - edit.Header = $"Edit '{node.Name}'"; - edit.Click += (o, e) => { - var remotes = repo.Remotes(); - var found = remotes.Find(r => r.Name == node.Name); - if (found != null) Remote.Show(repo, found); - e.Handled = true; - }; - - var delete = new MenuItem(); - delete.Header = $"Delete '{node.Name}'"; - delete.Click += (o, e) => { - DeleteRemote.Show(repo, node.Name); - e.Handled = true; - }; - - var copy = new MenuItem(); - copy.Header = "Copy Remote URL"; - copy.Click += (o, e) => { - var remotes = repo.Remotes(); - var found = remotes.Find(r => r.Name == node.Name); - if (found != null) Clipboard.SetText(found.URL); - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(fetch); - menu.Items.Add(new Separator()); - menu.Items.Add(edit); - menu.Items.Add(delete); - menu.Items.Add(new Separator()); - menu.Items.Add(copy); - menu.IsOpen = true; - } - - private void OpenRemoteBranchContextMenu(BranchNode node) { - var branch = node.Branch; - var current = repo.CurrentBranch(); - if (current == null) return; - - var checkout = new MenuItem(); - checkout.Header = $"Checkout '{branch.Name}'"; - checkout.Click += (o, e) => { - var branches = repo.Branches(); - var tracked = null as Git.Branch; - var upstream = $"refs/remotes/{branch.Name}"; - - foreach (var b in branches) { - if (b.IsLocal && b.Upstream == upstream) { - tracked = b; - break; - } - } - - if (tracked == null) { - CreateBranch.Show(repo, branch); - } else if (!tracked.IsCurrent) { - Task.Run(() => repo.Checkout(tracked.Name)); - } - - e.Handled = true; - }; - - var pull = new MenuItem(); - pull.Header = $"Pull '{branch.Name}' into '{current.Name}'"; - pull.Click += (o, e) => { - Pull.Show(repo, branch.Name); - e.Handled = true; - }; - - var merge = new MenuItem(); - merge.Header = $"Merge '{branch.Name}' into '{current.Name}'"; - merge.Click += (o, e) => { - Merge.Show(repo, branch.Name, current.Name); - e.Handled = true; - }; - - var rebase = new MenuItem(); - rebase.Header = $"Rebase '{current.Name}' on '{branch.Name}'"; - rebase.Click += (o, e) => { - Rebase.Show(repo, branch); - e.Handled = true; - }; - - var delete = new MenuItem(); - delete.Header = $"Delete '{branch.Name}'"; - delete.Click += (o, e) => { - DeleteBranch.Show(repo, branch); - e.Handled = true; - }; - - var createBranch = new MenuItem(); - createBranch.Header = "Create New Branch"; - createBranch.Click += (o, e) => { - CreateBranch.Show(repo, branch); - e.Handled = true; - }; - - var createTag = new MenuItem(); - createTag.Header = "Create New Tag"; - createTag.Click += (o, e) => { - CreateTag.Show(repo, branch); - e.Handled = true; - }; - - var copy = new MenuItem(); - copy.Header = "Copy Branch Name"; - copy.Click += (o, e) => { - Clipboard.SetText(branch.Name); - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(checkout); - menu.Items.Add(new Separator()); - menu.Items.Add(pull); - menu.Items.Add(merge); - menu.Items.Add(rebase); - menu.Items.Add(new Separator()); - menu.Items.Add(delete); - menu.Items.Add(new Separator()); - menu.Items.Add(createBranch); - menu.Items.Add(createTag); - menu.Items.Add(new Separator()); - menu.Items.Add(copy); - menu.IsOpen = true; - } - - private void RemoteBranchSelected(object sender, RoutedPropertyChangedEventArgs e) { - var node = e.NewValue as BranchNode; - if (node == null || node.Branch == null) return; - repo.OnNavigateCommit?.Invoke(node.Branch.Head); - } - - private void RemoteContextMenuOpening(object sender, ContextMenuEventArgs ev) { - var remoteNode = (sender as TreeViewItem).DataContext as RemoteNode; - if (remoteNode != null) { - OpenRemoteContextMenu(remoteNode); - ev.Handled = true; - return; - } - - var branchNode = (sender as TreeViewItem).DataContext as BranchNode; - if (branchNode != null && branchNode.Branch != null) { - OpenRemoteBranchContextMenu(branchNode); - ev.Handled = true; - return; - } - } - #endregion - - #region TAGS - private void OpenNewTag(object sender, RoutedEventArgs e) { - CreateTag.Show(repo); - } - - private void TagLostFocus(object sender, RoutedEventArgs e) { - (sender as DataGrid).UnselectAll(); - } - - private void TagSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count == 1) { - var item = e.AddedItems[0] as Git.Tag; - repo.OnNavigateCommit?.Invoke(item.SHA); - } - } - - private void TagContextMenuOpening(object sender, ContextMenuEventArgs e) { - var tag = (sender as DataGrid).SelectedItem as Git.Tag; - if (tag == null) return; - - var createBranch = new MenuItem(); - createBranch.Header = "Create New Branch"; - createBranch.Click += (o, ev) => { - CreateBranch.Show(repo, tag); - ev.Handled = true; - }; - - var pushTag = new MenuItem(); - pushTag.Header = $"Push '{tag.Name}'"; - pushTag.Click += (o, ev) => { - PushTag.Show(repo, tag); - ev.Handled = true; - }; - - var deleteTag = new MenuItem(); - deleteTag.Header = $"Delete '{tag.Name}'"; - deleteTag.Click += (o, ev) => { - DeleteTag.Show(repo, tag); - ev.Handled = true; - }; - - var copy = new MenuItem(); - copy.Header = "Copy Name"; - copy.Click += (o, ev) => { - Clipboard.SetText(tag.Name); - ev.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(createBranch); - menu.Items.Add(new Separator()); - menu.Items.Add(pushTag); - menu.Items.Add(deleteTag); - menu.Items.Add(new Separator()); - menu.Items.Add(copy); - menu.IsOpen = true; - - e.Handled = true; - } - #endregion - - #region SUBMODULES - private void OpenAddSubmodule(object sender, RoutedEventArgs e) { - AddSubmodule.Show(repo); - } - - private void UpdateSubmodule(object sender, RoutedEventArgs e) { - Waiting.Show(repo, () => repo.UpdateSubmodule()); - } - - private void SubmoduleLostFocus(object sender, RoutedEventArgs e) { - (sender as DataGrid).UnselectAll(); - } - - private void SubmoduleContextMenuOpening(object sender, ContextMenuEventArgs e) { - var path = (sender as DataGrid).SelectedItem as string; - if (path == null) return; - - var open = new MenuItem(); - open.Header = "Open Submodule Repository"; - open.Click += (o, ev) => { - var sub = new Git.Repository(); - sub.Path = Path.Combine(repo.Path, path); - sub.Name = Path.GetFileName(path); - sub.Parent = repo; - sub.Open(); - - ev.Handled = true; - }; - - var copy = new MenuItem(); - copy.Header = "Copy Relative Path"; - copy.Click += (o, ev) => { - Clipboard.SetText(path); - ev.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(open); - menu.Items.Add(copy); - menu.IsOpen = true; - - e.Handled = true; - } - - private void SubmoduleMouseDoubleClick(object sender, MouseButtonEventArgs e) { - var path = (sender as DataGridRow).DataContext as string; - if (path == null) return; - - var sub = new Git.Repository(); - sub.Path = Path.Combine(repo.Path, path); - sub.Name = Path.GetFileName(path); - sub.Parent = repo; - sub.Open(); - } - #endregion - - #region TREES - private TreeViewItem FindTreeViewItem(ItemsControl item, BranchNode node) { - if (item == null) return null; - - var data = item.DataContext as BranchNode; - if (data == node) return item as TreeViewItem; - - for (int i = 0; i < item.Items.Count; i++) { - var childContainer = item.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl; - var child = FindTreeViewItem(childContainer, node); - if (child != null) return child; - } - - return null; - } - - private void TreeLostFocus(object sender, RoutedEventArgs e) { - var tree = sender as TreeView; - var remote = tree.SelectedItem as RemoteNode; - if (remote != null) { - var remoteItem = tree.ItemContainerGenerator.ContainerFromItem(remote) as TreeViewItem; - if (remoteItem != null) remoteItem.IsSelected = false; - return; - } - - var node = tree.SelectedItem as BranchNode; - if (node == null) return; - - var item = FindTreeViewItem(tree, node); - if (item != null) item.IsSelected = false; - } - - private ScrollViewer GetScrollViewer(FrameworkElement owner) { - if (owner == null) return null; - if (owner is ScrollViewer) return owner as ScrollViewer; - - int n = VisualTreeHelper.GetChildrenCount(owner); - for (int i = 0; i < n; i++) { - var child = VisualTreeHelper.GetChild(owner, i) as FrameworkElement; - var deep = GetScrollViewer(child); - if (deep != null) return deep; - } - - return null; - } - - private void TreeMouseWheel(object sender, MouseWheelEventArgs e) { - var scroll = GetScrollViewer(sender as TreeView); - if (scroll == null) return; - - if (e.Delta > 0) { - scroll.LineUp(); - } else { - scroll.LineDown(); - } - - e.Handled = true; - } - #endregion - - #region FILETER - private void FilterChanged(object sender, RoutedEventArgs e) { - var toggle = sender as ToggleButton; - if (toggle == null) return; - - if (toggle.DataContext is BranchNode) { - var branch = (toggle.DataContext as BranchNode).Branch; - if (branch == null) return; - - if (toggle.IsChecked == true) { - if (!repo.LogFilters.Contains(branch.FullName)) { - repo.LogFilters.Add(branch.FullName); - } - if (!string.IsNullOrEmpty(branch.Upstream) && !repo.LogFilters.Contains(branch.Upstream)) { - repo.LogFilters.Add(branch.Upstream); - UpdateBranches(false); - } - } else { - repo.LogFilters.Remove(branch.FullName); - if (!string.IsNullOrEmpty(branch.Upstream)) { - repo.LogFilters.Remove(branch.Upstream); - UpdateBranches(false); - } - } - } - - if (toggle.DataContext is Git.Tag) { - var tag = toggle.DataContext as Git.Tag; - - if (toggle.IsChecked == true) { - if (!repo.LogFilters.Contains(tag.Name)) { - repo.LogFilters.Add(tag.Name); - } - } else { - repo.LogFilters.Remove(tag.Name); - } - } - - UpdateHistories(); - } - #endregion - } -} diff --git a/SourceGit/UI/DeleteBranch.xaml b/SourceGit/UI/DeleteBranch.xaml deleted file mode 100644 index 79080a55..00000000 --- a/SourceGit/UI/DeleteBranch.xaml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/DeleteBranch.xaml.cs b/SourceGit/UI/DeleteBranch.xaml.cs deleted file mode 100644 index d5f56321..00000000 --- a/SourceGit/UI/DeleteBranch.xaml.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - /// - /// Confirm to delete branch - /// - public partial class DeleteBranch : UserControl { - private Git.Repository repo = null; - private Git.Branch branch = null; - - /// - /// Constructor. - /// - /// Opened repository. - /// Branch to be deleted. - public DeleteBranch(Git.Repository opened, Git.Branch target) { - InitializeComponent(); - repo = opened; - branch = target; - branchName.Content = target.Name; - } - - /// - /// Show this dialog. - /// - /// - /// - public static void Show(Git.Repository opened, Git.Branch branch) { - var popup = App.GetPopupManager(opened); - popup?.Show(new DeleteBranch(opened, branch)); - } - - /// - /// Delete - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => branch.Delete(repo)); - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/DeleteRemote.xaml b/SourceGit/UI/DeleteRemote.xaml deleted file mode 100644 index 69c06d5b..00000000 --- a/SourceGit/UI/DeleteRemote.xaml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/UI/DeleteRemote.xaml.cs b/SourceGit/UI/DeleteRemote.xaml.cs deleted file mode 100644 index 960c614c..00000000 --- a/SourceGit/UI/DeleteRemote.xaml.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Confirm to delete a remote - /// - public partial class DeleteRemote : UserControl { - private Git.Repository repo = null; - private string remote = null; - - /// - /// Constructor. - /// - /// Opened repository - /// Remote to be deleted - public DeleteRemote(Git.Repository opened, string target) { - InitializeComponent(); - repo = opened; - remote = target; - remoteName.Content = target; - } - - /// - /// Show this dialog - /// - /// - /// - public static void Show(Git.Repository opened, string remote) { - var popup = App.GetPopupManager(opened); - popup?.Show(new DeleteRemote(opened, remote)); - } - - /// - /// Delete - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => Git.Remote.Delete(repo, remote)); - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/DeleteTag.xaml b/SourceGit/UI/DeleteTag.xaml deleted file mode 100644 index e4b73198..00000000 --- a/SourceGit/UI/DeleteTag.xaml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/DeleteTag.xaml.cs b/SourceGit/UI/DeleteTag.xaml.cs deleted file mode 100644 index 320c6167..00000000 --- a/SourceGit/UI/DeleteTag.xaml.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Delete tag dialog. - /// - public partial class DeleteTag : UserControl { - private Git.Repository repo = null; - private Git.Tag tag = null; - - /// - /// Constructor - /// - /// Opened repo - /// Delete tag - public DeleteTag(Git.Repository repo, Git.Tag tag) { - this.repo = repo; - this.tag = tag; - - InitializeComponent(); - tagName.Content = tag.Name; - } - - /// - /// Display this dialog. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Tag tag) { - var popup = App.GetPopupManager(repo); - popup?.Show(new DeleteTag(repo, tag)); - } - - /// - /// Start request. - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Lock(); - - var push = chkWithRemote.IsChecked == true; - await Task.Run(() => Git.Tag.Delete(repo, tag.Name, push)); - - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/DiffViewer.xaml b/SourceGit/UI/DiffViewer.xaml deleted file mode 100644 index 90e02f11..00000000 --- a/SourceGit/UI/DiffViewer.xaml +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/DiffViewer.xaml.cs b/SourceGit/UI/DiffViewer.xaml.cs deleted file mode 100644 index 17a25462..00000000 --- a/SourceGit/UI/DiffViewer.xaml.cs +++ /dev/null @@ -1,405 +0,0 @@ -using System; -using System.Globalization; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; - -namespace SourceGit.UI { - - /// - /// Viewer for git diff - /// - public partial class DiffViewer : UserControl { - private double minWidth = 0; - - /// - /// Diff options. - /// - public class Option { - public string[] RevisionRange = new string[] { }; - public string Path = ""; - public string OrgPath = null; - public string ExtraArgs = ""; - } - - /// - /// Constructor - /// - public DiffViewer() { - InitializeComponent(); - Reset(); - } - - /// - /// Reset data. - /// - public void Reset() { - mask.Visibility = Visibility.Visible; - } - - /// - /// Diff with options. - /// - /// - /// - public void Diff(Git.Repository repo, Option opts) { - SetTitle(opts.Path, opts.OrgPath); - - loading.Visibility = Visibility.Visible; - mask.Visibility = Visibility.Collapsed; - textChange.Visibility = Visibility.Collapsed; - sizeChange.Visibility = Visibility.Collapsed; - noChange.Visibility = Visibility.Collapsed; - - Task.Run(() => { - var args = $"{opts.ExtraArgs} "; - if (opts.RevisionRange.Length > 0) args += $"{opts.RevisionRange[0]} "; - if (opts.RevisionRange.Length > 1) args += $"{opts.RevisionRange[1]} -- "; - if (!string.IsNullOrEmpty(opts.OrgPath)) args += $"\"{opts.OrgPath}\" "; - args += $"\"{opts.Path}\""; - - var rs = Git.Diff.Run(repo, args); - if (rs.IsBinary) { - SetSizeChangeData(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath)); - } else if (rs.Blocks.Count > 0) { - SetData(rs); - } else { - SetSame(); - } - }); - } - - #region LAYOUT - /// - /// Show diff title - /// - /// - /// - private void SetTitle(string file, string orgFile) { - fileName.Text = file; - if (!string.IsNullOrEmpty(orgFile) && orgFile != "/dev/null") { - orgFileNamePanel.Visibility = Visibility.Visible; - orgFileName.Text = orgFile; - } else { - orgFileNamePanel.Visibility = Visibility.Collapsed; - } - } - - /// - /// Show size changes. - /// - /// - private void SetSizeChangeData(Git.Diff.BinaryChange bc) { - Dispatcher.Invoke(() => { - loading.Visibility = Visibility.Collapsed; - sizeChange.Visibility = Visibility.Visible; - diffNavigation.Visibility = Visibility.Collapsed; - txtNewSize.Content = $"{bc.Size} Bytes"; - txtOldSize.Content = $"{bc.PreSize} Bytes"; - }); - } - - /// - /// Show no changes or only EOL changes. - /// - private void SetSame() { - Dispatcher.Invoke(() => { - loading.Visibility = Visibility.Collapsed; - noChange.Visibility = Visibility.Visible; - diffNavigation.Visibility = Visibility.Collapsed; - }); - } - - /// - /// Show diff content. - /// - /// - private void SetData(Git.Diff.Result rs) { - Dispatcher.Invoke(() => { - loading.Visibility = Visibility.Collapsed; - textChange.Visibility = Visibility.Visible; - diffNavigation.Visibility = Visibility.Visible; - - minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16; - - leftLineNumber.Text = ""; - rightLineNumber.Text = ""; - leftText.Document.Blocks.Clear(); - rightText.Document.Blocks.Clear(); - - foreach (var b in rs.Blocks) ShowBlock(b); - - leftText.Document.PageWidth = minWidth + 16; - rightText.Document.PageWidth = minWidth + 16; - leftText.ScrollToHome(); - }); - } - - /// - /// Make paragraph. - /// - /// - private void ShowBlock(Git.Diff.Block b) { - var content = b.Builder.ToString(); - - Paragraph p = new Paragraph(new Run(content)); - p.Margin = new Thickness(0); - p.Padding = new Thickness(); - p.LineHeight = 1; - p.Background = Brushes.Transparent; - p.Foreground = FindResource("Brush.FG") as SolidColorBrush; - p.FontStyle = FontStyles.Normal; - p.DataContext = b; - - switch (b.Mode) { - case Git.Diff.LineMode.Normal: - break; - case Git.Diff.LineMode.Indicator: - p.Foreground = Brushes.Gray; - p.FontStyle = FontStyles.Italic; - break; - case Git.Diff.LineMode.Empty: - p.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0)); - break; - case Git.Diff.LineMode.Added: - p.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)); - break; - case Git.Diff.LineMode.Deleted: - p.Background = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)); - break; - } - - var formatter = new FormattedText( - content, - CultureInfo.CurrentUICulture, - FlowDirection.LeftToRight, - new Typeface(leftText.FontFamily, p.FontStyle, p.FontWeight, p.FontStretch), - leftText.FontSize, - Brushes.Black, - new NumberSubstitution(), - TextFormattingMode.Ideal); - - if (minWidth < formatter.Width) minWidth = formatter.Width; - - switch (b.Side) { - case Git.Diff.Side.Left: - leftText.Document.Blocks.Add(p); - for (int i = 0; i < b.Count; i++) { - if (b.CanShowNumber) leftLineNumber.AppendText($"{i + b.LeftStart}\n"); - else leftLineNumber.AppendText("\n"); - } - break; - case Git.Diff.Side.Right: - rightText.Document.Blocks.Add(p); - for (int i = 0; i < b.Count; i++) { - if (b.CanShowNumber) rightLineNumber.AppendText($"{i + b.RightStart}\n"); - else rightLineNumber.AppendText("\n"); - } - break; - default: - leftText.Document.Blocks.Add(p); - - var cp = new Paragraph(new Run(content)); - cp.Margin = new Thickness(0); - cp.Padding = new Thickness(); - cp.LineHeight = 1; - cp.Background = p.Background; - cp.Foreground = p.Foreground; - cp.FontStyle = p.FontStyle; - cp.DataContext = b; - rightText.Document.Blocks.Add(cp); - - for (int i = 0; i < b.Count; i++) { - if (b.Mode != Git.Diff.LineMode.Indicator) { - leftLineNumber.AppendText($"{i + b.LeftStart}\n"); - rightLineNumber.AppendText($"{i + b.RightStart}\n"); - } else { - leftLineNumber.AppendText("\n"); - rightLineNumber.AppendText("\n"); - } - } - break; - } - } - #endregion - - #region EVENTS - /// - /// Sync scroll both sides. - /// - /// - /// - private void OnViewerScroll(object sender, ScrollChangedEventArgs e) { - if (e.VerticalChange != 0) { - if (leftText.VerticalOffset != e.VerticalOffset) { - leftText.ScrollToVerticalOffset(e.VerticalOffset); - } - - if (rightText.VerticalOffset != e.VerticalOffset) { - rightText.ScrollToVerticalOffset(e.VerticalOffset); - } - - leftLineNumber.Margin = new Thickness(0, -e.VerticalOffset, 0, 0); - rightLineNumber.Margin = new Thickness(0, -e.VerticalOffset, 0, 0); - } else { - if (leftText.HorizontalOffset != e.HorizontalOffset) { - leftText.ScrollToHorizontalOffset(e.HorizontalOffset); - } - - if (rightText.HorizontalOffset != e.HorizontalOffset) { - rightText.ScrollToHorizontalOffset(e.HorizontalOffset); - } - } - } - - /// - /// Scroll using mouse wheel. - /// - /// - /// - private void OnViewerMouseWheel(object sender, MouseWheelEventArgs e) { - var text = sender as RichTextBox; - if (text == null) return; - - if (e.Delta > 0) { - text.LineUp(); - } else { - text.LineDown(); - } - - e.Handled = true; - } - - /// - /// Fix document size for left side. - /// - /// - /// - private void LeftSizeChanged(object sender, SizeChangedEventArgs e) { - if (leftText.Document.PageWidth < leftText.ActualWidth) { - leftText.Document.PageWidth = leftText.ActualWidth; - } - } - - /// - /// Fix document size for right side. - /// - /// - /// - private void RightSizeChanged(object sender, SizeChangedEventArgs e) { - if (rightText.Document.PageWidth < rightText.ActualWidth) { - rightText.Document.PageWidth = rightText.ActualWidth; - } - } - - /// - /// Auto scroll when selection changed. - /// - /// - /// - private void OnViewerSelectionChanged(object sender, RoutedEventArgs e) { - var doc = sender as RichTextBox; - if (doc == null || doc.IsFocused == false) return; - - if (Mouse.LeftButton == MouseButtonState.Pressed && !doc.Selection.IsEmpty) { - var p = Mouse.GetPosition(doc); - - if (p.X <= 8) { - doc.LineLeft(); - } else if (p.X >= doc.ActualWidth - 8) { - doc.LineRight(); - } - - if (p.Y <= 8) { - doc.LineUp(); - } else if (p.Y >= doc.ActualHeight - 8) { - doc.LineDown(); - } - } - } - - /// - /// Go to next difference. - /// - /// - /// - private void Go2Next(object sender, RoutedEventArgs e) { - Paragraph next = null; - double minTop = 0; - - foreach (var p in leftText.Document.Blocks) { - var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward); - var block = p.DataContext as Git.Diff.Block; - if (rect.Top > 17 && block.IsLeftDelete) { - next = p as Paragraph; - minTop = rect.Top; - break; - } - } - - foreach (var p in rightText.Document.Blocks) { - var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward); - var block = p.DataContext as Git.Diff.Block; - if (rect.Top > 17 && block.IsRightAdded) { - if (next == null || minTop > rect.Top) { - next = p as Paragraph; - minTop = rect.Top; - } - - break; - } - } - - if (next != null) { - rightText.ScrollToVerticalOffset(rightText.VerticalOffset + minTop - 16); - } - } - - /// - /// Go to previous difference. - /// - /// - /// - private void Go2Prev(object sender, RoutedEventArgs e) { - Paragraph next = null; - double maxTop = 0; - - var p = leftText.Document.Blocks.LastBlock as Paragraph; - do { - var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward); - var block = p.DataContext as Git.Diff.Block; - if (rect.Top < 15 && block.IsLeftDelete) { - next = p; - maxTop = rect.Top; - break; - } - - p = p.PreviousBlock as Paragraph; - } while (p != null); - - p = rightText.Document.Blocks.LastBlock as Paragraph; - do { - var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward); - var block = p.DataContext as Git.Diff.Block; - if (rect.Top < 15 && block.IsRightAdded) { - if (next == null || maxTop < rect.Top) { - next = p; - maxTop = rect.Top; - } - - break; - } - - p = p.PreviousBlock as Paragraph; - } while (p != null); - - if (next != null) { - rightText.ScrollToVerticalOffset(rightText.VerticalOffset + maxTop - 16); - } - } - #endregion - } -} diff --git a/SourceGit/UI/Discard.xaml b/SourceGit/UI/Discard.xaml deleted file mode 100644 index b725e4c4..00000000 --- a/SourceGit/UI/Discard.xaml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Discard.xaml.cs b/SourceGit/UI/Discard.xaml.cs deleted file mode 100644 index 794835d8..00000000 --- a/SourceGit/UI/Discard.xaml.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace SourceGit.UI { - - /// - /// Confirm to discard changes dialog. - /// - public partial class Discard : UserControl { - private Git.Repository repo = null; - private List changes = null; - - /// - /// Constructor. - /// - /// - /// - public Discard(Git.Repository opened, List targets) { - repo = opened; - changes = targets; - - InitializeComponent(); - - if (changes == null || changes.Count == 0) { - txtPath.Content = "All local changes in working copy."; - icon.Data = FindResource("Icon.Folder") as Geometry; - } else if (changes.Count == 1) { - txtPath.Content = changes[0].Path; - } else { - txtPath.Content = $"Total {changes.Count} changes ..."; - } - } - - /// - /// Show this dialog - /// - /// - /// - public static void Show(Git.Repository opened, List targets) { - var popup = App.GetPopupManager(opened); - popup?.Show(new Discard(opened, targets)); - } - - private async void Sure(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => repo.Discard(changes)); - popup?.Close(true); - } - - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/Fetch.xaml b/SourceGit/UI/Fetch.xaml deleted file mode 100644 index 550ed554..00000000 --- a/SourceGit/UI/Fetch.xaml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Fetch.xaml.cs b/SourceGit/UI/Fetch.xaml.cs deleted file mode 100644 index 3efb3eb9..00000000 --- a/SourceGit/UI/Fetch.xaml.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Fetch dialog. - /// - public partial class Fetch : UserControl { - private Git.Repository repo = null; - - /// - /// Constructor. - /// - /// Opened repository - /// Prefer selected remote. - public Fetch(Git.Repository opened, string preferRemote) { - repo = opened; - InitializeComponent(); - - Task.Run(() => { - var remotes = repo.Remotes(); - Dispatcher.Invoke(() => { - combRemotes.ItemsSource = remotes; - if (preferRemote != null) { - combRemotes.SelectedIndex = remotes.FindIndex(r => r.Name == preferRemote); - chkFetchAll.IsChecked = false; - } else { - combRemotes.SelectedIndex = 0; - chkFetchAll.IsChecked = true; - } - }); - }); - } - - /// - /// Show fetch dialog. - /// - /// - /// - public static void Show(Git.Repository repo, string preferRemote = null) { - var popup = App.GetPopupManager(repo); - popup?.Show(new Fetch(repo, preferRemote)); - } - - /// - /// Start fetch - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - bool prune = chkPrune.IsChecked == true; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - - if (chkFetchAll.IsChecked == true) { - await Task.Run(() => repo.Fetch(null, prune, msg => popup?.UpdateStatus(msg))); - } else { - var remote = combRemotes.SelectedItem as Git.Remote; - await Task.Run(() => repo.Fetch(remote, prune, msg => popup?.UpdateStatus(msg))); - } - - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/FileHistories.xaml b/SourceGit/UI/FileHistories.xaml deleted file mode 100644 index ea3347b6..00000000 --- a/SourceGit/UI/FileHistories.xaml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/FileHistories.xaml.cs b/SourceGit/UI/FileHistories.xaml.cs deleted file mode 100644 index 5f72b4a2..00000000 --- a/SourceGit/UI/FileHistories.xaml.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Animation; -using System.Windows.Navigation; - -namespace SourceGit.UI { - - /// - /// File histories panel. - /// - public partial class FileHistories : Window { - private Git.Repository repo = null; - private string file = null; - - /// - /// Constructor. - /// - /// - /// - public FileHistories(Git.Repository repo, string file) { - this.repo = repo; - this.file = file; - - InitializeComponent(); - - // Move to center - var parent = App.Current.MainWindow; - Left = parent.Left + (parent.Width - Width) * 0.5; - Top = parent.Top + (parent.Height - Height) * 0.5; - - // Show loading - DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1)); - anim.RepeatBehavior = RepeatBehavior.Forever; - loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim); - loading.Visibility = Visibility.Visible; - - // Load commits - Task.Run(() => { - var commits = repo.Commits($"-n 10000 -- \"{file}\""); - Dispatcher.Invoke(() => { - commitList.ItemsSource = commits; - commitList.SelectedIndex = 0; - - loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null); - loading.Visibility = Visibility.Collapsed; - }); - }); - } - - /// - /// Logo click - /// - /// - /// - private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) { - var element = e.OriginalSource as FrameworkElement; - if (element == null) return; - - var pos = PointToScreen(new Point(0, 33)); - SystemCommands.ShowSystemMenu(this, pos); - } - - /// - /// Minimize - /// - private void Minimize(object sender, RoutedEventArgs e) { - SystemCommands.MinimizeWindow(this); - } - - /// - /// Maximize/Restore - /// - private void MaximizeOrRestore(object sender, RoutedEventArgs e) { - if (WindowState == WindowState.Normal) { - SystemCommands.MaximizeWindow(this); - } else { - SystemCommands.RestoreWindow(this); - } - } - - /// - /// Quit - /// - private void Quit(object sender, RoutedEventArgs e) { - Close(); - } - - /// - /// Commit selection change event. - /// - /// - /// - private void CommitSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count != 1) return; - - var commit = e.AddedItems[0] as Git.Commit; - var start = $"{commit.SHA}^"; - if (commit.Parents.Count == 0) start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; - - diff.Diff(repo, new DiffViewer.Option() { - RevisionRange = new string[] { start, commit.SHA }, - Path = file - }); - } - - /// - /// Navigate to given string - /// - /// - /// - private void NavigateToCommit(object sender, RequestNavigateEventArgs e) { - repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString); - e.Handled = true; - } - } -} diff --git a/SourceGit/UI/GitFlowFinishBranch.xaml b/SourceGit/UI/GitFlowFinishBranch.xaml deleted file mode 100644 index 07f7db6c..00000000 --- a/SourceGit/UI/GitFlowFinishBranch.xaml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/GitFlowFinishBranch.xaml.cs b/SourceGit/UI/GitFlowFinishBranch.xaml.cs deleted file mode 100644 index bcd48dad..00000000 --- a/SourceGit/UI/GitFlowFinishBranch.xaml.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Confirm finish git-flow branch dialog - /// - public partial class GitFlowFinishBranch : UserControl { - private Git.Repository repo = null; - private Git.Branch branch = null; - - /// - /// Constructor. - /// - /// - /// - public GitFlowFinishBranch(Git.Repository repo, Git.Branch branch) { - this.repo = repo; - this.branch = branch; - - InitializeComponent(); - - switch (branch.Kind) { - case Git.Branch.Type.Feature: - txtTitle.Content = "Git Flow - Finish Feature"; - txtBranchType.Content = "Feature :"; - break; - case Git.Branch.Type.Release: - txtTitle.Content = "Git Flow - Finish Release"; - txtBranchType.Content = "Release :"; - break; - case Git.Branch.Type.Hotfix: - txtTitle.Content = "Git Flow - Finish Hotfix"; - txtBranchType.Content = "Hotfix :"; - break; - default: - var popup = App.GetPopupManager(repo); - popup?.Close(); - return; - } - - txtBranchName.Content = branch.Name; - } - - /// - /// Show this dialog. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Branch branch) { - var popup = App.GetPopupManager(repo); - popup?.Show(new GitFlowFinishBranch(repo, branch)); - } - - /// - /// Do finish - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => repo.FinishGitFlowBranch(branch)); - popup?.Close(true); - } - - /// - /// Cancel finish - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/GitFlowSetup.xaml b/SourceGit/UI/GitFlowSetup.xaml deleted file mode 100644 index 616eab54..00000000 --- a/SourceGit/UI/GitFlowSetup.xaml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/GitFlowSetup.xaml.cs b/SourceGit/UI/GitFlowSetup.xaml.cs deleted file mode 100644 index 75ac07bb..00000000 --- a/SourceGit/UI/GitFlowSetup.xaml.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Dialog to initialize git flow. - /// - public partial class GitFlowSetup : UserControl { - private Git.Repository repo = null; - private Regex regex = new Regex(@"^[\w\-/\.]+$"); - - /// - /// Constructor. - /// - /// - public GitFlowSetup(Git.Repository opened) { - repo = opened; - InitializeComponent(); - } - - /// - /// Open this dialog. - /// - /// - public static void Show(Git.Repository repo) { - var popup = App.GetPopupManager(repo); - popup?.Show(new GitFlowSetup(repo)); - } - - /// - /// Start to initialize git-flow. - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Lock(); - - var master = txtMaster.Text; - var dev = txtDevelop.Text; - var feature = txtFeature.Text; - var release = txtRelease.Text; - var hotfix = txtHotfix.Text; - var version = txtVersion.Text; - - await Task.Run(() => repo.EnableGitFlow(master, dev, feature, release, hotfix, version)); - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - - /// - /// Validate input names. - /// - /// - /// - private void ValidateNames(object sender, TextChangedEventArgs e) { - if (!IsLoaded) return; - - var master = txtMaster.Text; - var dev = txtDevelop.Text; - var feature = txtFeature.Text; - var release = txtRelease.Text; - var hotfix = txtHotfix.Text; - - if (!ValidateBranch("Production", master)) return; - if (!ValidateBranch("Development", dev)) return; - - if (dev == master) { - txtValidation.Content = "Development branch is same with production!"; - btnSure.IsEnabled = false; - return; - } - - if (!ValidatePrefix("Feature", feature)) return; - if (!ValidatePrefix("Release", release)) return; - if (!ValidatePrefix("Hotfix", hotfix)) return; - - txtValidation.Content = ""; - btnSure.IsEnabled = true; - } - - private bool ValidateBranch(string type, string name) { - if (string.IsNullOrEmpty(name)) { - txtValidation.Content = $"{type} branch name can't be empty"; - btnSure.IsEnabled = false; - return false; - } - - if (!regex.IsMatch(name)) { - txtValidation.Content = $"{type} branch name contains invalid characters."; - btnSure.IsEnabled = false; - return false; - } - - return true; - } - - private bool ValidatePrefix(string type, string prefix) { - if (string.IsNullOrEmpty(prefix)) { - txtValidation.Content = $"{type} prefix is required!"; - btnSure.IsEnabled = false; - return false; - } - - if (!regex.IsMatch(prefix)) { - txtValidation.Content = $"{type} prefix contains invalid characters."; - btnSure.IsEnabled = false; - return false; - } - - return true; - } - } -} diff --git a/SourceGit/UI/GitFlowStartBranch.xaml b/SourceGit/UI/GitFlowStartBranch.xaml deleted file mode 100644 index 472b56d2..00000000 --- a/SourceGit/UI/GitFlowStartBranch.xaml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/GitFlowStartBranch.xaml.cs b/SourceGit/UI/GitFlowStartBranch.xaml.cs deleted file mode 100644 index 5a0cc3e2..00000000 --- a/SourceGit/UI/GitFlowStartBranch.xaml.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Start git-flow branch dialog. - /// - public partial class GitFlowStartBranch : UserControl { - private Git.Repository repo = null; - private Git.Branch.Type type = Git.Branch.Type.Feature; - - /// - /// Sub-name for this git-flow branch. - /// - public string SubName { - get; - set; - } - - /// - /// Constructor. - /// - /// - /// - public GitFlowStartBranch(Git.Repository repo, Git.Branch.Type type) { - this.repo = repo; - this.type = type; - - InitializeComponent(); - nameValidator.Repo = repo; - - switch (type) { - case Git.Branch.Type.Feature: - var featurePrefix = repo.GetFeaturePrefix(); - txtTitle.Content = "Git Flow - Start Feature"; - txtPrefix.Content = featurePrefix; - nameValidator.Prefix = featurePrefix; - break; - case Git.Branch.Type.Release: - var releasePrefix = repo.GetReleasePrefix(); - txtTitle.Content = "Git Flow - Start Release"; - txtPrefix.Content = releasePrefix; - nameValidator.Prefix = releasePrefix; - break; - case Git.Branch.Type.Hotfix: - var hotfixPrefix = repo.GetHotfixPrefix(); - txtTitle.Content = "Git Flow - Start Hotfix"; - txtPrefix.Content = hotfixPrefix; - nameValidator.Prefix = hotfixPrefix; - break; - default: - var popup = App.GetPopupManager(repo); - popup?.Close(); - return; - } - } - - /// - /// Display this dialog - /// - /// - /// - public static void Show(Git.Repository repo, Git.Branch.Type type) { - var popup = App.GetPopupManager(repo); - popup?.Show(new GitFlowStartBranch(repo, type)); - } - - /// - /// Start git-flow branch - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtName)) return; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => repo.StartGitFlowBranch(type, SubName)); - popup?.Close(true); - } - - /// - /// Cancel - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/Histories.xaml b/SourceGit/UI/Histories.xaml deleted file mode 100644 index 999df59f..00000000 --- a/SourceGit/UI/Histories.xaml +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Histories.xaml.cs b/SourceGit/UI/Histories.xaml.cs deleted file mode 100644 index b8936766..00000000 --- a/SourceGit/UI/Histories.xaml.cs +++ /dev/null @@ -1,649 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Animation; -using System.Windows.Shapes; - -namespace SourceGit.UI { - - /// - /// Commit histories viewer - /// - public partial class Histories : UserControl { - - /// - /// Current opened repository. - /// - public Git.Repository Repo { get; set; } - - /// - /// Cached commits. - /// - private List cachedCommits = new List(); - - /// - /// Is in search mode? - /// - private bool isSearchMode = false; - - /// - /// Regex to test search input. - /// - private Regex commitRegex = new Regex(@"^[0-9a-f]{6,40}$", RegexOptions.None); - - /// - /// Constructor - /// - public Histories() { - InitializeComponent(); - ChangeOrientation(null, null); - } - - /// - /// Navigate to given commit. - /// - /// - public void Navigate(string commit) { - if (string.IsNullOrEmpty(commit)) return; - - foreach (var item in commitList.ItemsSource) { - var c = item as Git.Commit; - if (c.SHA.StartsWith(commit)) { - commitList.SelectedItem = c; - commitList.ScrollIntoView(c); - return; - } - } - } - - /// - /// Loading tips. - /// - /// - public void SetLoadingEnabled(bool enabled) { - if (enabled) { - loading.Visibility = Visibility.Visible; - - DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1)); - anim.RepeatBehavior = RepeatBehavior.Forever; - loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim); - } else { - loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null); - loading.Visibility = Visibility.Collapsed; - } - } - - #region DATA - public void SetCommits(List commits) { - cachedCommits = commits; - if (isSearchMode) return; - - var maker = Helpers.CommitGraphMaker.Parse(commits); - - Dispatcher.Invoke(() => { - commitGraph.Children.Clear(); - isSearchMode = false; - txtSearch.Text = ""; - - // Draw all lines. - foreach (var path in maker.Lines) { - var size = path.Points.Count; - var geo = new StreamGeometry(); - var last = path.Points[0]; - - using (var ctx = geo.Open()) { - ctx.BeginFigure(last, false, false); - - for (int i = 1; i < size; i++) { - var cur = path.Points[i]; - - if (cur.X > last.X) { - ctx.QuadraticBezierTo(new Point(cur.X, last.Y), cur, true, false); - } else if (cur.X < last.X) { - if (i < size - 1) { - cur.Y += Helpers.CommitGraphMaker.HALF_HEIGHT; - - var midY = (last.Y + cur.Y) / 2; - var midX = (last.X + cur.X) / 2; - ctx.PolyQuadraticBezierTo(new Point[] { - new Point(last.X, midY), - new Point(midX, midY), - new Point(cur.X, midY), - cur - }, true, false); - } else { - ctx.QuadraticBezierTo(new Point(last.X, cur.Y), cur, true, false); - } - } else { - ctx.LineTo(cur, true, false); - } - - last = cur; - } - } - - geo.Freeze(); - - var p = new Path(); - p.Data = geo; - p.Stroke = path.Brush; - p.StrokeThickness = 2; - commitGraph.Children.Add(p); - } - maker.Lines.Clear(); - - // Draw short links - foreach (var link in maker.Links) { - var geo = new StreamGeometry(); - - using (var ctx = geo.Open()) { - ctx.BeginFigure(link.Start, false, false); - ctx.QuadraticBezierTo(link.Control, link.End, true, false); - } - - geo.Freeze(); - - var p = new Path(); - p.Data = geo; - p.Stroke = link.Brush; - p.StrokeThickness = 2; - commitGraph.Children.Add(p); - } - maker.Links.Clear(); - - // Draw points. - foreach (var dot in maker.Dots) { - var ellipse = new Ellipse(); - ellipse.Height = 6; - ellipse.Width = 6; - ellipse.Fill = dot.Color; - ellipse.SetValue(Canvas.LeftProperty, dot.X); - ellipse.SetValue(Canvas.TopProperty, dot.Y); - commitGraph.Children.Add(ellipse); - } - maker.Dots.Clear(); - - commitList.ItemsSource = new List(cachedCommits); - // Navigate(maker.Highlight); - SetLoadingEnabled(false); - }); - } - - public void SetSearchResult(List commits) { - isSearchMode = true; - - foreach (var c in commits) c.GraphOffset = 0; - - Dispatcher.Invoke(() => { - commitGraph.Children.Clear(); - commitList.ItemsSource = new List(commits); - SetLoadingEnabled(false); - }); - } - - private void Cleanup(object sender, RoutedEventArgs e) { - commitGraph.Children.Clear(); - commitList.ItemsSource = null; - cachedCommits.Clear(); - } - #endregion - - #region SEARCH_BAR - public void OpenSearchBar() { - if (searchBar.Margin.Top == 0) return; - - ThicknessAnimation anim = new ThicknessAnimation(); - anim.From = new Thickness(0, -32, 0, 0); - anim.To = new Thickness(0); - anim.Duration = TimeSpan.FromSeconds(.3); - searchBar.BeginAnimation(Grid.MarginProperty, anim); - - txtSearch.Focus(); - } - - public void HideSearchBar() { - if (searchBar.Margin.Top != 0) return; - - ClearSearch(null, null); - - ThicknessAnimation anim = new ThicknessAnimation(); - anim.From = new Thickness(0); - anim.To = new Thickness(0, -32, 0, 0); - anim.Duration = TimeSpan.FromSeconds(.3); - searchBar.BeginAnimation(Grid.MarginProperty, anim); - } - - private void ClearSearch(object sender, RoutedEventArgs e) { - txtSearch.Text = ""; - if (isSearchMode) { - isSearchMode = false; - SetLoadingEnabled(true); - Task.Run(() => SetCommits(cachedCommits)); - } - } - - private void PreviewSearchKeyDown(object sender, KeyEventArgs e) { - if (e.Key == Key.Enter) { - string search = txtSearch.Text; - if (string.IsNullOrEmpty(search)) { - ClearSearch(sender, e); - } else if (commitRegex.IsMatch(search)) { - SetLoadingEnabled(true); - Task.Run(() => { - var commits = Repo.Commits($"search -n 1"); - SetSearchResult(commits); - }); - } else { - SetLoadingEnabled(true); - - Task.Run(() => { - List found = new List(); - - foreach (var commit in cachedCommits) { - if (commit.Subject.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 || - (commit.Author != null && commit.Author.Name.Equals(search, StringComparison.OrdinalIgnoreCase)) || - (commit.Committer != null && commit.Committer.Name.Equals(search, StringComparison.OrdinalIgnoreCase)) || - commit.Message.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0) { - found.Add(commit); - } - } - - SetSearchResult(found); - }); - } - } - } - #endregion - - #region COMMIT_DATAGRID_AND_GRAPH - private void CommitListScrolled(object sender, ScrollChangedEventArgs e) { - commitGraph.Margin = new Thickness(0, -e.VerticalOffset * Helpers.CommitGraphMaker.UNIT_HEIGHT, 0, 0); - } - - private void CommitSelectChanged(object sender, SelectionChangedEventArgs e) { - mask4MultiSelection.Visibility = Visibility.Collapsed; - - var selected = commitList.SelectedItems; - if (selected.Count == 1) { - var commit = selected[0] as Git.Commit; - if (commit != null) commitViewer.SetData(Repo, commit); - } else if (selected.Count > 1) { - mask4MultiSelection.Visibility = Visibility.Visible; - txtTotalSelected.Content = $"SELECTED {selected.Count} COMMITS"; - } - } - - private MenuItem GetCurrentBranchContextMenu(Git.Branch branch) { - var icon = new Path(); - icon.Style = FindResource("Style.Icon") as Style; - icon.Data = FindResource("Icon.Branch") as Geometry; - icon.VerticalAlignment = VerticalAlignment.Bottom; - icon.Width = 10; - - var submenu = new MenuItem(); - submenu.Header = branch.Name; - submenu.Icon = icon; - - if (!string.IsNullOrEmpty(branch.Upstream)) { - var upstream = branch.Upstream.Substring(13); - var fastForward = new MenuItem(); - fastForward.Header = $"Fast-Forward to '{upstream}'"; - fastForward.Click += (o, e) => { - Merge.StartDirectly(Repo, upstream, branch.Name); - e.Handled = true; - }; - submenu.Items.Add(fastForward); - - var pull = new MenuItem(); - pull.Header = $"Pull '{upstream}' ..."; - pull.Click += (o, e) => { - Pull.Show(Repo); - e.Handled = true; - }; - submenu.Items.Add(pull); - } - - var push = new MenuItem(); - push.Header = $"Push '{branch.Name}' ..."; - push.Click += (o, e) => { - Push.Show(Repo, branch); - e.Handled = true; - }; - submenu.Items.Add(push); - submenu.Items.Add(new Separator()); - - if (branch.Kind != Git.Branch.Type.Normal) { - var flowIcon = new Path(); - flowIcon.Style = FindResource("Style.Icon") as Style; - flowIcon.Data = FindResource("Icon.Flow") as Geometry; - flowIcon.Width = 10; - - var finish = new MenuItem(); - finish.Header = $"Git Flow - Finish '{branch.Name}'"; - finish.Icon = flowIcon; - finish.Click += (o, e) => { - GitFlowFinishBranch.Show(Repo, branch); - e.Handled = true; - }; - - submenu.Items.Add(finish); - submenu.Items.Add(new Separator()); - } - - var rename = new MenuItem(); - rename.Header = "Rename ..."; - rename.Click += (o, e) => { - RenameBranch.Show(Repo, branch); - e.Handled = true; - }; - submenu.Items.Add(rename); - - return submenu; - } - - private MenuItem GetOtherBranchContextMenu(Git.Branch current, Git.Branch branch, bool merged) { - var icon = new Path(); - icon.Style = FindResource("Style.Icon") as Style; - icon.Data = FindResource("Icon.Branch") as Geometry; - icon.VerticalAlignment = VerticalAlignment.Bottom; - icon.Width = 10; - - var submenu = new MenuItem(); - submenu.Header = branch.Name; - submenu.Icon = icon; - - var checkout = new MenuItem(); - checkout.Header = $"Checkout '{branch.Name}'"; - checkout.Click += (o, e) => { - if (branch.IsLocal) { - Task.Run(() => Repo.Checkout(branch.Name)); - } else { - var upstream = $"refs/remotes/{branch.Name}"; - var tracked = Repo.Branches().Find(b => b.IsLocal && b.Upstream == upstream); - - if (tracked == null) { - CreateBranch.Show(Repo, branch); - } else if (!tracked.IsCurrent) { - Task.Run(() => Repo.Checkout(tracked.Name)); - } - } - - e.Handled = true; - }; - submenu.Items.Add(checkout); - - var merge = new MenuItem(); - merge.Header = $"Merge into '{current.Name}' ..."; - merge.IsEnabled = !merged; - merge.Click += (o, e) => { - Merge.Show(Repo, branch.Name, current.Name); - e.Handled = true; - }; - submenu.Items.Add(merge); - submenu.Items.Add(new Separator()); - - if (branch.Kind != Git.Branch.Type.Normal) { - var flowIcon = new Path(); - flowIcon.Style = FindResource("Style.Icon") as Style; - flowIcon.Data = FindResource("Icon.Flow") as Geometry; - flowIcon.Width = 10; - - var finish = new MenuItem(); - finish.Header = $"Git Flow - Finish '{branch.Name}'"; - finish.Icon = flowIcon; - finish.Click += (o, e) => { - GitFlowFinishBranch.Show(Repo, branch); - e.Handled = true; - }; - - submenu.Items.Add(finish); - submenu.Items.Add(new Separator()); - } - - var rename = new MenuItem(); - rename.Header = "Rename ..."; - rename.Visibility = branch.IsLocal ? Visibility.Visible : Visibility.Collapsed; - rename.Click += (o, e) => { - RenameBranch.Show(Repo, current); - e.Handled = true; - }; - submenu.Items.Add(rename); - - var delete = new MenuItem(); - delete.Header = "Delete ..."; - delete.Click += (o, e) => { - DeleteBranch.Show(Repo, branch); - }; - submenu.Items.Add(delete); - - return submenu; - } - - private MenuItem GetTagContextMenu(Git.Tag tag) { - var icon = new Path(); - icon.Style = FindResource("Style.Icon") as Style; - icon.Data = FindResource("Icon.Tag") as Geometry; - icon.Width = 10; - - var submenu = new MenuItem(); - submenu.Header = tag.Name; - submenu.Icon = icon; - submenu.MinWidth = 200; - - var push = new MenuItem(); - push.Header = "Push ..."; - push.Click += (o, e) => { - PushTag.Show(Repo, tag); - e.Handled = true; - }; - submenu.Items.Add(push); - - var delete = new MenuItem(); - delete.Header = "Delete ..."; - delete.Click += (o, e) => { - DeleteTag.Show(Repo, tag); - e.Handled = true; - }; - submenu.Items.Add(delete); - - return submenu; - } - - private void CommitContextMenuOpening(object sender, ContextMenuEventArgs ev) { - var row = sender as DataGridRow; - if (row == null) return; - - var commit = row.DataContext as Git.Commit; - if (commit == null) return; - commitList.SelectedItem = commit; - - var current = Repo.CurrentBranch(); - if (current == null) return; - - var menu = new ContextMenu(); - menu.MinWidth = 200; - - // Decorators. - { - var localBranchContextMenus = new List(); - var remoteBranchContextMenus = new List(); - var tagContextMenus = new List(); - - foreach (var d in commit.Decorators) { - if (d.Type == Git.DecoratorType.CurrentBranchHead) { - menu.Items.Add(GetCurrentBranchContextMenu(current)); - } else if (d.Type == Git.DecoratorType.LocalBranchHead) { - var branch = Repo.Branches().Find(b => b.Name == d.Name); - if (branch != null) { - localBranchContextMenus.Add(GetOtherBranchContextMenu(current, branch, commit.IsMerged)); - } - } else if (d.Type == Git.DecoratorType.RemoteBranchHead) { - var branch = Repo.Branches().Find(b => b.Name == d.Name); - if (branch != null) { - remoteBranchContextMenus.Add(GetOtherBranchContextMenu(current, branch, commit.IsMerged)); - } - } else if (d.Type == Git.DecoratorType.Tag) { - var tag = Repo.Tags().Find(t => t.Name == d.Name); - if (tag != null) tagContextMenus.Add(GetTagContextMenu(tag)); - } - } - - foreach (var m in localBranchContextMenus) menu.Items.Add(m); - foreach (var m in remoteBranchContextMenus) menu.Items.Add(m); - if (menu.Items.Count > 0) menu.Items.Add(new Separator()); - - if (tagContextMenus.Count > 0) { - foreach (var m in tagContextMenus) menu.Items.Add(m); - menu.Items.Add(new Separator()); - } - } - - // Reset - var reset = new MenuItem(); - reset.Header = $"Reset '{current.Name}' To Here"; - reset.Visibility = commit.IsHEAD ? Visibility.Collapsed : Visibility.Visible; - reset.Click += (o, e) => { - Reset.Show(Repo, commit); - e.Handled = true; - }; - menu.Items.Add(reset); - - // Rebase or interactive rebase - var rebase = new MenuItem(); - rebase.Header = commit.IsMerged ? $"Interactive Rebase '{current.Name}' From Here" : $"Rebase '{current.Name}' To Here"; - rebase.Visibility = commit.IsHEAD ? Visibility.Collapsed : Visibility.Visible; - rebase.Click += (o, e) => { - if (commit.IsMerged) { - if (Repo.LocalChanges().Count > 0) { - App.RaiseError("You have local changes!!!"); - e.Handled = true; - return; - } - - var dialog = new InteractiveRebase(Repo, commit); - dialog.Owner = App.Current.MainWindow; - dialog.ShowDialog(); - } else { - Rebase.Show(Repo, commit); - } - - e.Handled = true; - }; - menu.Items.Add(rebase); - - // Cherry-Pick - var cherryPick = new MenuItem(); - cherryPick.Header = "Cherry-Pick This Commit"; - cherryPick.Visibility = commit.IsMerged ? Visibility.Collapsed : Visibility.Visible; - cherryPick.Click += (o, e) => { - CherryPick.Show(Repo, commit); - e.Handled = true; - }; - menu.Items.Add(cherryPick); - - // Revert commit - var revert = new MenuItem(); - revert.Header = "Revert commit"; - revert.Visibility = !commit.IsMerged ? Visibility.Collapsed : Visibility.Visible; - revert.Click += (o, e) => { - Revert.Show(Repo, commit); - e.Handled = true; - }; - menu.Items.Add(revert); - menu.Items.Add(new Separator()); - - // Common - var createBranch = new MenuItem(); - createBranch.Header = "Create Branch"; - createBranch.Click += (o, e) => { - CreateBranch.Show(Repo, commit); - e.Handled = true; - }; - menu.Items.Add(createBranch); - var createTag = new MenuItem(); - createTag.Header = "Create Tag"; - createTag.Click += (o, e) => { - CreateTag.Show(Repo, commit); - e.Handled = true; - }; - menu.Items.Add(createTag); - menu.Items.Add(new Separator()); - - // Save as patch - var patch = new MenuItem(); - patch.Header = "Save As Patch"; - patch.Click += (o, e) => { - var dialog = new System.Windows.Forms.FolderBrowserDialog(); - dialog.ShowNewFolderButton = true; - - if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - Repo.RunCommand($"format-patch {commit.SHA} -1 -o \"{dialog.SelectedPath}\"", null); - } - }; - menu.Items.Add(patch); - menu.Items.Add(new Separator()); - - // Copy SHA - var copySHA = new MenuItem(); - copySHA.Header = "Copy Commit SHA"; - copySHA.Click += (o, e) => { - Clipboard.SetText(commit.SHA); - }; - menu.Items.Add(copySHA); - - // Copy info - var copyInfo = new MenuItem(); - copyInfo.Header = "Copy Commit Info"; - copyInfo.Click += (o, e) => { - Clipboard.SetText(string.Format( - "SHA: {0}\nTITLE: {1}\nAUTHOR: {2} <{3}>\nTIME: {4}", - commit.SHA, commit.Subject, commit.Committer.Name, commit.Committer.Email, commit.Committer.Time)); - }; - menu.Items.Add(copyInfo); - - menu.IsOpen = true; - ev.Handled = true; - } - #endregion - - #region LAYOUT - private void ChangeOrientation(object sender, RoutedEventArgs e) { - if (commitDetailPanel == null || splitter == null || commitListPanel == null) return; - - layout.RowDefinitions.Clear(); - layout.ColumnDefinitions.Clear(); - - if (App.Preference.UIUseHorizontalLayout) { - layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), MinWidth = 200 }); - layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(2) }); - layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), MinWidth = 200 }); - - Grid.SetRow(commitListPanel, 0); - Grid.SetRow(splitter, 0); - Grid.SetRow(commitDetailPanel, 0); - Grid.SetColumn(commitListPanel, 0); - Grid.SetColumn(splitter, 1); - Grid.SetColumn(commitDetailPanel, 2); - } else { - layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), MinHeight = 100 }); - layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(2) }); - layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), MinHeight = 100 }); - - Grid.SetRow(commitListPanel, 0); - Grid.SetRow(splitter, 1); - Grid.SetRow(commitDetailPanel, 2); - Grid.SetColumn(commitListPanel, 0); - Grid.SetColumn(splitter, 0); - Grid.SetColumn(commitDetailPanel, 0); - } - - layout.InvalidateVisual(); - } - #endregion - } -} diff --git a/SourceGit/UI/Init.xaml b/SourceGit/UI/Init.xaml deleted file mode 100644 index fd644564..00000000 --- a/SourceGit/UI/Init.xaml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Init.xaml.cs b/SourceGit/UI/Init.xaml.cs deleted file mode 100644 index d7bd449a..00000000 --- a/SourceGit/UI/Init.xaml.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// `git init` confirm panel. - /// - public partial class Init : UserControl { - private string workingDir = null; - - /// - /// Constructor. - /// - /// - public Init(string path) { - workingDir = path; - InitializeComponent(); - txtPath.Content = path; - } - - /// - /// Show this dialog. - /// - /// - public static void Show(string path) { - var popup = App.GetPopupManager(null); - popup.Show(new Init(path)); - } - - /// - /// Do `git init` - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(null); - popup.Lock(); - - await Task.Run(() => { - var errs = Git.Repository.RunCommand(workingDir, "init -q", null); - if (errs != null) { - App.RaiseError(errs); - } else { - App.Preference.AddRepository(workingDir, ""); - } - }); - - popup.Close(true); - - var repo = App.Preference.FindRepository(workingDir); - if (repo != null) repo.Open(); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(null).Close(); - } - } -} diff --git a/SourceGit/UI/InteractiveRebase.xaml b/SourceGit/UI/InteractiveRebase.xaml deleted file mode 100644 index 7b91d882..00000000 --- a/SourceGit/UI/InteractiveRebase.xaml +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/InteractiveRebase.xaml.cs b/SourceGit/UI/InteractiveRebase.xaml.cs deleted file mode 100644 index 60710604..00000000 --- a/SourceGit/UI/InteractiveRebase.xaml.cs +++ /dev/null @@ -1,303 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; - -namespace SourceGit.UI { - - /// - /// Rebase mode. - /// - public enum InteractiveRebaseMode { - Pick, - Reword, - Squash, - Fixup, - Drop, - } - - /// - /// Rebase mode information to display in UI. - /// - public class InteractiveRebaseModeInfo { - public InteractiveRebaseMode Mode { get; set; } - public string Title { get; set; } - public string Desc { get; set; } - public Brush Theme { get; set; } - - public InteractiveRebaseModeInfo(InteractiveRebaseMode mode, string title, string desc, Brush brush) { - Mode = mode; - Title = title; - Desc = desc; - Theme = brush; - } - - public static List Supported = new List() { - new InteractiveRebaseModeInfo(InteractiveRebaseMode.Pick, "Pick", "Use this commit", Brushes.Green), - new InteractiveRebaseModeInfo(InteractiveRebaseMode.Reword, "Reword", "Edit the commit message", Brushes.Yellow), - new InteractiveRebaseModeInfo(InteractiveRebaseMode.Squash, "Squash", "Meld into previous commit", App.Preference.UIUseLightTheme ? Brushes.Gray : Brushes.White), - new InteractiveRebaseModeInfo(InteractiveRebaseMode.Fixup, "Fixup", "Like 'Squash' but discard log message", App.Preference.UIUseLightTheme ? Brushes.Gray : Brushes.White), - new InteractiveRebaseModeInfo(InteractiveRebaseMode.Drop, "Drop", "Remove commit", Brushes.Red), - }; - } - - /// - /// Rebase item. - /// - public class InteractiveRebaseItem : INotifyPropertyChanged { - private InteractiveRebaseMode mode = InteractiveRebaseMode.Pick; - private bool isEditorOpened = false; - private string editSubject = null; - private string editMsg = null; - - public event PropertyChangedEventHandler PropertyChanged; - - public Git.Commit Commit { get; set; } - - public int Mode { - get { return (int)mode; } - set { - if (value != (int)mode) { - mode = (InteractiveRebaseMode)value; - PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Mode")); - } - } - } - - public bool IsEditorOpened { - get { return isEditorOpened; } - set { - if (value != isEditorOpened) { - isEditorOpened = value; - PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsEditorOpened")); - } - } - } - - public string Subject { - get { return Commit.Subject; } - set { - if (value != Commit.Subject) { - Commit.Subject = value; - PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Subject")); - } - } - } - - public string EditSubject { - get { return editSubject; } - set { - if (value != editMsg) { - editSubject = value; - PropertyChanged.Invoke(this, new PropertyChangedEventArgs("EditSubject")); - } - } - } - - public string EditMessage { - get { return editMsg; } - set { - if (value != editMsg) { - editMsg = value; - PropertyChanged.Invoke(this, new PropertyChangedEventArgs("EditMessage")); - } - } - } - } - - /// - /// Interactive rebase panel. - /// - public partial class InteractiveRebase : Window { - private Git.Repository repo = null; - private string from = null; - - /// - /// Edit commit list. - /// - public ObservableCollection Items { - get; - set; - } - - /// - /// Constructor. - /// - /// - /// - public InteractiveRebase(Git.Repository opened, Git.Commit start) { - repo = opened; - Items = new ObservableCollection(); - from = start.ShortSHA; - - InitializeComponent(); - - branch.Content = opened.CurrentBranch().Name; - on.Content = $"{start.ShortSHA} {start.Subject}"; - - Task.Run(() => { - var commits = repo.Commits($"{start.SHA}..HEAD"); - commits.Add(start); - - Dispatcher.Invoke(() => { - Items.Clear(); - foreach (var c in commits) Items.Add(new InteractiveRebaseItem() { Commit = c }); - }); - }); - } - - #region WINDOW_COMMANDS - private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) { - var element = e.OriginalSource as FrameworkElement; - if (element == null) return; - - var pos = PointToScreen(new Point(0, 33)); - SystemCommands.ShowSystemMenu(this, pos); - } - - private void Minimize(object sender, RoutedEventArgs e) { - SystemCommands.MinimizeWindow(this); - } - - private void MaximizeOrRestore(object sender, RoutedEventArgs e) { - if (WindowState == WindowState.Normal) { - SystemCommands.MaximizeWindow(this); - } else { - SystemCommands.RestoreWindow(this); - } - } - - private void Quit(object sender, RoutedEventArgs e) { - Close(); - } - #endregion - - private void CommitSelectionChanged(object sender, SelectionChangedEventArgs e) { - foreach (var obj in e.RemovedItems) { - var item = obj as InteractiveRebaseItem; - if (item != null) item.IsEditorOpened = false; - } - - if (e.AddedItems.Count == 1) { - var item = e.AddedItems[0] as InteractiveRebaseItem; - if (item != null) commitViewer.SetData(repo, item.Commit); - } - } - - private void PopupMessageEditor(object sender, MouseButtonEventArgs e) { - var item = (sender as Control).DataContext as InteractiveRebaseItem; - if (item == null) return; - - item.EditSubject = item.Commit.Subject; - item.EditMessage = item.Commit.Message; - item.IsEditorOpened = true; - } - - private void HideMessageEditor(object sender, RoutedEventArgs e) { - var item = (sender as Button).DataContext as InteractiveRebaseItem; - if (item == null) return; - item.IsEditorOpened = false; - } - - private void ApplyMessageEdit(object sender, RoutedEventArgs e) { - var item = (sender as Button).DataContext as InteractiveRebaseItem; - if (item == null) return; - - item.Subject = item.EditSubject; - item.Commit.Message = item.EditMessage; - item.Mode = (int)InteractiveRebaseMode.Reword; - item.IsEditorOpened = false; - } - - private void CommitMessageChanged(object sender, TextChangedEventArgs e) { - (sender as TextBox).ScrollToEnd(); - } - - private void MoveUp(object sender, RoutedEventArgs e) { - var item = (sender as Button).DataContext as InteractiveRebaseItem; - if (item == null) return; - - var idx = -1; - for (int i = 0; i < Items.Count; i++) { - if (Items[i].Commit.SHA == item.Commit.SHA) { - idx = i; - break; - } - } - - if (idx > 0) { - Items.RemoveAt(idx); - Items.Insert(idx - 1, item); - } - } - - private void MoveDown(object sender, RoutedEventArgs e) { - var item = (sender as Button).DataContext as InteractiveRebaseItem; - if (item == null) return; - - var idx = -1; - for (int i = 0; i < Items.Count; i++) { - if (Items[i].Commit.SHA == item.Commit.SHA) { - idx = i; - break; - } - } - - if (idx < Items.Count - 1) { - Items.RemoveAt(idx); - Items.Insert(idx + 1, item); - } - } - - private void Start(object sender, RoutedEventArgs e) { - var temp = Path.GetTempFileName(); - var stream = new FileStream(temp, FileMode.Create); - var writer = new StreamWriter(stream); - - for (int i = Items.Count - 1; i >= 0; i--) { - var item = Items[i]; - - switch ((InteractiveRebaseMode)item.Mode) { - case InteractiveRebaseMode.Pick: - writer.WriteLine($"p {item.Commit.ShortSHA} {item.Subject}"); - break; - case InteractiveRebaseMode.Reword: - writer.WriteLine($"r {item.Commit.ShortSHA} {item.Subject}"); - break; - case InteractiveRebaseMode.Squash: - writer.WriteLine($"s {item.Commit.ShortSHA} {item.Subject}"); - break; - case InteractiveRebaseMode.Fixup: - writer.WriteLine($"f {item.Commit.ShortSHA} {item.Subject}"); - break; - case InteractiveRebaseMode.Drop: - writer.WriteLine($"d {item.Commit.ShortSHA} {item.Subject}"); - break; - } - } - - writer.Flush(); - stream.Flush(); - writer.Close(); - stream.Close(); - - repo.SetWatcherEnabled(false); - var editor = Process.GetCurrentProcess().MainModule.FileName; - var errs = repo.RunCommand($"-c sequence.editor=\"\\\"{editor}\\\" --interactive-rebase \\\"{temp}\\\"\" rebase -i {from}^", null); - repo.AssertCommand(errs); - - Close(); - } - - private void Cancel(object sender, RoutedEventArgs e) { - Close(); - } - } -} diff --git a/SourceGit/UI/Launcher.xaml b/SourceGit/UI/Launcher.xaml deleted file mode 100644 index 549f4ec7..00000000 --- a/SourceGit/UI/Launcher.xaml +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Launcher.xaml.cs b/SourceGit/UI/Launcher.xaml.cs deleted file mode 100644 index 1d9146cb..00000000 --- a/SourceGit/UI/Launcher.xaml.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System.Collections.ObjectModel; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; - -namespace SourceGit.UI { - - /// - /// Main window for this app. - /// - public partial class Launcher : Window { - - /// - /// Tab data. - /// - public class Tab { - public string Title { get; set; } - public bool IsActive { get; set; } - public Git.Repository Repo { get; set; } - public object Page { get; set; } - } - - /// - /// Alert data. - /// - public class Alert { - public string Title { get; set; } - public string Message { get; set; } - } - - /// - /// Alerts. - /// - public ObservableCollection Alerts { get; set; } = new ObservableCollection(); - - /// - /// Opened tabs. - /// - public ObservableCollection Tabs { get; set; } = new ObservableCollection(); - - /// - /// Constructor - /// - public Launcher() { - App.OnError = msg => { - ShowAlert(new Alert() { Title = "ERROR", Message = msg }); - }; - - Git.Repository.OnOpen = repo => { - Dispatcher.Invoke(() => { - for (int i = 1; i < openedTabs.Items.Count; i++) { - var opened = openedTabs.Items[i] as Tab; - if (opened.Repo.Path == repo.Path) { - openedTabs.SelectedItem = opened; - return; - } - } - - var tab = new Tab() { - Title = repo.Parent == null ? repo.Name : $"{repo.Parent.Name} : {repo.Name}", - Repo = repo, - Page = new Dashboard(repo), - }; - - Tabs.Add(tab); - openedTabs.SelectedItem = tab; - }); - }; - - Tabs.Add(new Tab() { - Title = "SOURCE GIT", - Page = new Manager(), - }); - - InitializeComponent(); - openedTabs.SelectedItem = Tabs[0]; - } - - #region LAYOUT_CONTENT - /// - /// Close repository tab. - /// - /// - /// - private void CloseRepo(object sender, RoutedEventArgs e) { - var tab = (sender as Button).DataContext as Tab; - if (tab == null || tab.Repo == null) return; - - var popup = (tab.Page as Dashboard).popupManager; - if (popup.IsLocked()) popup.Close(true); - Tabs.Remove(tab); - - tab.Page = null; - tab.Repo.Close(); - } - - /// - /// Open preference dialog. - /// - /// - /// - private void ShowPreference(object sender, RoutedEventArgs e) { - var dialog = new Preference(); - dialog.Owner = this; - dialog.ShowDialog(); - } - - /// - /// Open about dialog. - /// - /// - /// - private void ShowAbout(object sender, RoutedEventArgs e) { - var about = new About(); - about.Owner = this; - about.ShowDialog(); - } - - /// - /// Show alert. - /// - /// - private void ShowAlert(Alert alert) { - Dispatcher.Invoke(() => Alerts.Add(alert)); - } - - /// - /// Remove an alert. - /// - /// - /// - private void RemoveAlert(object sender, RoutedEventArgs e) { - var alert = (sender as Button).DataContext as Alert; - Alerts.Remove(alert); - } - #endregion - - #region WINDOW_COMMANDS - /// - /// Minimize - /// - private void Minimize(object sender, RoutedEventArgs e) { - SystemCommands.MinimizeWindow(this); - } - - /// - /// Maximize/Restore - /// - private void MaximizeOrRestore(object sender, RoutedEventArgs e) { - if (WindowState == WindowState.Normal) { - SystemCommands.MaximizeWindow(this); - } else { - SystemCommands.RestoreWindow(this); - } - } - - /// - /// Quit - /// - private void Quit(object sender, RoutedEventArgs e) { - App.Current.Shutdown(); - } - #endregion - - #region DRAG_DROP - private void TabsMouseMove(object sender, MouseEventArgs e) { - var tab = e.Source as TabItem; - if (tab == null || (tab.DataContext as Tab).Repo == null) return; - - if (Mouse.LeftButton == MouseButtonState.Pressed) { - DragDrop.DoDragDrop(tab, tab, DragDropEffects.All); - e.Handled = true; - } - } - - private void TabsDrop(object sender, DragEventArgs e) { - var tabItemSrc = e.Data.GetData(typeof(TabItem)) as TabItem; - var tabItemDst = e.Source as TabItem; - if (tabItemSrc.Equals(tabItemDst)) return; - - var tabSrc = tabItemSrc.DataContext as Tab; - var tabDst = tabItemDst.DataContext as Tab; - if (tabDst.Repo == null) { - Tabs.Remove(tabSrc); - Tabs.Insert(1, tabSrc); - } else { - int dstIdx = Tabs.IndexOf(tabDst); - - Tabs.Remove(tabSrc); - Tabs.Insert(dstIdx, tabSrc); - } - } - #endregion - - #region TAB_SCROLL - private void OpenedTabsSizeChanged(object sender, SizeChangedEventArgs e) { - if (openedTabs.ActualWidth > openedTabsColumn.ActualWidth) { - openedTabsOpts.Visibility = Visibility.Visible; - } else { - openedTabsOpts.Visibility = Visibility.Collapsed; - } - } - - private void ScrollToLeft(object sender, RoutedEventArgs e) { - openedTabsScroller.LineLeft(); - } - - private void ScrollToRight(object sender, RoutedEventArgs e) { - openedTabsScroller.LineRight(); - } - #endregion - } -} diff --git a/SourceGit/UI/Manager.xaml b/SourceGit/UI/Manager.xaml deleted file mode 100644 index 520bd6bb..00000000 --- a/SourceGit/UI/Manager.xaml +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Manager.xaml.cs b/SourceGit/UI/Manager.xaml.cs deleted file mode 100644 index c8bcbf4b..00000000 --- a/SourceGit/UI/Manager.xaml.cs +++ /dev/null @@ -1,490 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; - -namespace SourceGit.UI { - - /// - /// Repository manager. - /// - public partial class Manager : UserControl { - private TreeViewItem selectedTreeViewItem = null; - - /// - /// Used to build tree - /// - public class Node { - public string Id { get; set; } - public string ParentId { get; set; } - public string Name { get; set; } - public bool IsRepo { get; set; } - public bool IsExpended { get; set; } - public bool IsEditing { get; set; } - public List Children { get; set; } = new List(); - } - - /// - /// Constructor. - /// - public Manager() { - InitializeComponent(); - UpdateRecentOpened(); - UpdateTree(); - } - - #region TOOLBAR - /// - /// Open or add local repository. - /// - /// - /// - private void OpenOrAddRepo(object sender, RoutedEventArgs e) { - var dialog = new System.Windows.Forms.FolderBrowserDialog(); - dialog.Description = "Open or init local repository"; - dialog.RootFolder = Environment.SpecialFolder.MyComputer; - dialog.ShowNewFolderButton = true; - - if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - CheckAndOpenRepo(dialog.SelectedPath); - } - } - - /// - /// Clone remote repository. - /// - /// - /// - private void CloneRepo(object sender, RoutedEventArgs e) { - if (MakeSureReady()) Clone.Show(); - } - #endregion - - #region EVENT_RECENT_LISTVIEW - private void RecentsGotFocus(object sender, RoutedEventArgs e) { - if (selectedTreeViewItem != null) selectedTreeViewItem.IsSelected = false; - selectedTreeViewItem = null; - e.Handled = true; - } - - private void RecentsSelectionChanged(object sender, SelectionChangedEventArgs e) { - var recent = recentOpened.SelectedItem as Git.Repository; - if (recent != null) ShowBrief(recent); - e.Handled = true; - } - - private void RecentsMouseDoubleClick(object sender, MouseButtonEventArgs e) { - var list = sender as ListView; - var recent = list.SelectedItem as Git.Repository; - - if (recent != null) { - CheckAndOpenRepo(recent.Path); - e.Handled = true; - } - } - #endregion - - #region EVENT_TREEVIEW - private void TreeGotFocus(object sender, RoutedEventArgs e) { - recentOpened.SelectedItems.Clear(); - e.Handled = true; - } - - private void TreeContextMenuOpening(object sender, ContextMenuEventArgs e) { - var addFolder = new MenuItem(); - addFolder.Header = "Add Folder"; - addFolder.Click += (o, ev) => { - var group = App.Preference.AddGroup("New Group", ""); - UpdateTree(group.Id); - ev.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(addFolder); - menu.IsOpen = true; - e.Handled = true; - } - - private void TreeMouseMove(object sender, MouseEventArgs e) { - if (e.LeftButton != MouseButtonState.Pressed) return; - - if (selectedTreeViewItem == null) return; - - var node = selectedTreeViewItem.DataContext as Node; - if (node == null || !node.IsRepo) return; - - DragDrop.DoDragDrop(repositories, selectedTreeViewItem, DragDropEffects.Move); - e.Handled = true; - } - - private void TreeDrop(object sender, DragEventArgs e) { - bool needRebuild = false; - - if (e.Data.GetDataPresent(DataFormats.FileDrop)) { - if (!MakeSureReady()) return; - - string[] paths = e.Data.GetData(DataFormats.FileDrop) as string[]; - string group = ""; - - var node = (sender as TreeViewItem)?.DataContext as Node; - if (node != null) group = node.IsRepo ? node.ParentId : node.Id; - - foreach (var path in paths) { - FileInfo info = new FileInfo(path); - if (info.Attributes == FileAttributes.Directory && Git.Repository.IsValid(path)) { - App.Preference.AddRepository(path, group); - needRebuild = true; - } - } - } else if (e.Data.GetDataPresent(typeof(TreeViewItem))) { - var item = e.Data.GetData(typeof(TreeViewItem)) as TreeViewItem; - var node = item.DataContext as Node; - if (node == null || !node.IsRepo) return; - - var group = ""; - var to = (sender as TreeViewItem)?.DataContext as Node; - if (to != null) group = to.IsRepo ? to.ParentId : to.Id; - App.Preference.FindRepository(node.Id).GroupId = group; - needRebuild = true; - } - - if (needRebuild) UpdateTree(); - e.Handled = true; - } - #endregion - - #region EVENT_TREEVIEWITEM - private void TreeNodeSelected(object sender, RoutedEventArgs e) { - selectedTreeViewItem = sender as TreeViewItem; - - var node = selectedTreeViewItem.DataContext as Node; - if (node.IsRepo) { - ShowBrief(App.Preference.FindRepository(node.Id)); - } else { - HideBrief(); - } - - e.Handled = true; - } - - private void TreeNodeDoubleClick(object sender, MouseButtonEventArgs e) { - var node = (sender as TreeViewItem).DataContext as Node; - if (node != null && node.IsRepo) { - CheckAndOpenRepo(node.Id); - e.Handled = true; - } - } - - private void TreeNodeDragOver(object sender, DragEventArgs e) { - var item = sender as TreeViewItem; - var node = item.DataContext as Node; - if (node != null && !node.IsRepo) item.IsExpanded = true; - e.Handled = true; - } - - private void TreeNodeDrop(object sender, DragEventArgs e) { - TreeDrop(sender, e); - } - - private void TreeNodeIsExpandedChanged(object sender, RoutedEventArgs e) { - var item = sender as TreeViewItem; - var node = item.DataContext as Node; - - if (node != null && !node.IsRepo) { - var group = App.Preference.FindGroup(node.Id); - group.IsExpended = item.IsExpanded; - e.Handled = true; - } - } - - private void TreeNodeKeyDown(object sender, KeyEventArgs e) { - if (e.Key != Key.Delete) return; - - var node = (sender as TreeViewItem).DataContext as Node; - if (node != null) DeleteNode(node); - e.Handled = true; - } - - private void TreeNodeRenameStart(object sender, RoutedEventArgs e) { - var text = sender as TextBox; - if (text.IsVisible) { - text.SelectAll(); - text.Focus(); - } - e.Handled = true; - } - - private void TreeNodeRenameKeyDown(object sender, KeyEventArgs e) { - if (e.Key == Key.Escape) { - UpdateTree(); - e.Handled = true; - } else if (e.Key == Key.Enter) { - TreeNodeRenameEnd(sender, e); - e.Handled = true; - } - } - - private void TreeNodeRenameEnd(object sender, RoutedEventArgs e) { - var text = sender as TextBox; - if (string.IsNullOrWhiteSpace(text.Text)) { - UpdateTree(); - e.Handled = false; - return; - } - - var node = text.DataContext as Node; - if (node != null) { - if (node.IsRepo) { - App.Preference.RenameRepository(node.Id, text.Text); - } else { - App.Preference.RenameGroup(node.Id, text.Text); - } - - UpdateRecentOpened(); - UpdateTree(); - e.Handled = true; - } - } - - private void TreeNodeContextMenuOpening(object sender, ContextMenuEventArgs e) { - var item = sender as TreeViewItem; - var node = item.DataContext as Node; - var menu = new ContextMenu(); - - if (node.IsRepo) { - var open = new MenuItem(); - open.Header = "Open"; - open.Click += (o, ev) => { - CheckAndOpenRepo(node.Id); - ev.Handled = true; - }; - - var explore = new MenuItem(); - explore.Header = "Open Container Folder"; - explore.Click += (o, ev) => { - Process.Start(node.Id); - ev.Handled = true; - }; - - menu.Items.Add(open); - menu.Items.Add(explore); - } else { - var addSubFolder = new MenuItem(); - addSubFolder.Header = "Add Sub-Folder"; - addSubFolder.Click += (o, ev) => { - var parent = App.Preference.FindGroup(node.Id); - if (parent != null) parent.IsExpended = true; - - var group = App.Preference.AddGroup("New Group", node.Id); - UpdateTree(group.Id); - ev.Handled = true; - }; - - menu.Items.Add(addSubFolder); - } - - var rename = new MenuItem(); - rename.Header = "Rename"; - rename.Click += (o, ev) => { - UpdateTree(node.Id); - ev.Handled = true; - }; - - var delete = new MenuItem(); - delete.Header = "Delete"; - delete.Click += (o, ev) => { - DeleteNode(node); - ev.Handled = true; - }; - - menu.Items.Add(rename); - menu.Items.Add(delete); - menu.IsOpen = true; - e.Handled = true; - } - #endregion - - #region EVENT_BRIEF - private void ShowBrief(Git.Repository repo) { - if (repo == null || !Git.Repository.IsValid(repo.Path)) { - if (Directory.Exists(repo.Path)) { - Init.Show(repo.Path); - } else { - App.RaiseError("Path is NOT valid git repository or has been removed."); - App.Preference.RemoveRepository(repo.Path); - UpdateRecentOpened(); - UpdateTree(); - } - - return; - } - - briefMask.Visibility = Visibility.Hidden; - - repoName.Content = repo.Name; - repoPath.Content = repo.Path; - - Task.Run(() => { - var changes = repo.LocalChanges(); - var count = changes.Count; - Dispatcher.Invoke(() => localChanges.Content = count); - }); - - Task.Run(() => { - var count = repo.TotalCommits(); - Dispatcher.Invoke(() => totalCommits.Content = count); - }); - - Task.Run(() => { - var commits = repo.Commits("-n 1"); - Dispatcher.Invoke(() => { - if (commits.Count > 0) { - var c = commits[0]; - lastCommitId.Content = c.ShortSHA; - lastCommit.Content = c.Subject; - } else { - lastCommitId.Content = "---"; - lastCommit.Content = ""; - } - }); - }); - - if (File.Exists(repo.Path + "/README.md")) { - readme.Text = File.ReadAllText(repo.Path + "/README.md"); - } else { - readme.Text = ""; - } - } - - private void HideBrief() { - briefMask.Visibility = Visibility.Visible; - } - - private void OpenRepo(object sender, RoutedEventArgs e) { - CheckAndOpenRepo(repoPath.Content as string); - } - #endregion - - #region PRIVATES - /// - /// Make sure git is configured. - /// - /// - private bool MakeSureReady() { - if (!App.IsGitConfigured) { - App.RaiseError("Git has NOT been configured.\nPlease to go [Preference] and configure it first."); - return false; - } - - return true; - } - - /// - /// Check and open repository - /// - /// - private void CheckAndOpenRepo(string path) { - if (!MakeSureReady()) return; - - if (!Git.Repository.IsValid(path)) { - if (Directory.Exists(path)) { - Init.Show(path); - return; - } - - App.RaiseError($"Path[{path}] not exists!"); - return; - } - - var repo = App.Preference.AddRepository(path, ""); - repo.Open(); - } - - /// - /// Update recent opened repositories. - /// - private void UpdateRecentOpened() { - var sorted = App.Preference.Repositories.OrderByDescending(a => a.LastOpenTime).ToList(); - var top5 = new List(); - - for (int i = 0; i < sorted.Count && i < 5; i++) { - if (sorted[i].LastOpenTime <= 0) break; - top5.Add(sorted[i]); - } - - recentOpened.ItemsSource = top5; - } - - /// - /// Update tree items. - /// - /// - private void UpdateTree(string editingNodeId = null) { - var groupNodes = new Dictionary(); - var nodes = new List(); - - foreach (var group in App.Preference.Groups) { - Node node = new Node() { - Id = group.Id, - ParentId = group.ParentId, - Name = group.Name, - IsRepo = false, - IsExpended = group.IsExpended, - IsEditing = group.Id == editingNodeId, - }; - - groupNodes.Add(node.Id, node); - } - - nodes.Clear(); - - foreach (var kv in groupNodes) { - if (groupNodes.ContainsKey(kv.Value.ParentId)) { - groupNodes[kv.Value.ParentId].Children.Add(kv.Value); - } else { - nodes.Add(kv.Value); - } - } - - foreach (var repo in App.Preference.Repositories) { - Node node = new Node() { - Id = repo.Path, - ParentId = repo.GroupId, - Name = repo.Name, - IsRepo = true, - IsExpended = false, - IsEditing = repo.Path == editingNodeId, - }; - - if (groupNodes.ContainsKey(repo.GroupId)) { - groupNodes[repo.GroupId].Children.Add(node); - } else { - nodes.Add(node); - } - } - - repositories.ItemsSource = nodes; - } - - /// - /// Delete tree node. - /// - /// - private void DeleteNode(Node node) { - if (node.IsRepo) { - App.Preference.RemoveRepository(node.Id); - UpdateRecentOpened(); - } else { - App.Preference.RemoveGroup(node.Id); - } - - UpdateTree(); - } - #endregion - } -} diff --git a/SourceGit/UI/Merge.xaml b/SourceGit/UI/Merge.xaml deleted file mode 100644 index 5caa8b09..00000000 --- a/SourceGit/UI/Merge.xaml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/UI/Merge.xaml.cs b/SourceGit/UI/Merge.xaml.cs deleted file mode 100644 index 0d9fbeef..00000000 --- a/SourceGit/UI/Merge.xaml.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Merge branch dialog. - /// - public partial class Merge : UserControl { - private Git.Repository repo = null; - - /// - /// Merge option. - /// - public class Option { - public string Name { get; set; } - public string Desc { get; set; } - public string Arg { get; set; } - - public Option(string n, string d, string a) { - Name = n; - Desc = d; - Arg = a; - } - } - - /// - /// Constructor. - /// - /// Opened repository - /// Source branch to merge data from. - /// Target branch to merge into - public Merge(Git.Repository opened, string source, string dest) { - InitializeComponent(); - - repo = opened; - sourceBranch.Content = source; - targetBranch.Content = dest; - combOptions.ItemsSource = new Option[] { - new Option("Default", "Fast-forward if possible", ""), - new Option("No Fast-forward", "Always create a merge commit", "--no-ff"), - new Option("Squash", "Use '--squash'", "--squash"), - new Option("Don't commit", "Merge without commit", "--no-commit"), - }; - combOptions.SelectedIndex = 0; - } - - /// - /// Display this dialog. - /// - /// - /// - /// - public static void Show(Git.Repository opened, string source, string dest) { - var popup = App.GetPopupManager(opened); - popup?.Show(new Merge(opened, source, dest)); - } - - /// - /// Start merge directly(Fast-forward). - /// - /// - /// - /// - public static void StartDirectly(Git.Repository opened, string source, string dest) { - var merge = new Merge(opened, source, dest); - var popup = App.GetPopupManager(opened); - popup?.Show(merge); - popup?.Lock(); - - Task.Run(() => { - opened.Merge(source, ""); - merge.Dispatcher.Invoke(() => { - popup?.Close(true); - }); - }); - } - - /// - /// Start merge - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Lock(); - - var branch = sourceBranch.Content as string; - var opt = combOptions.SelectedItem as Option; - await Task.Run(() => repo.Merge(branch, opt.Arg)); - - popup?.Close(true); - } - - /// - /// Cancel merge. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/PopupManager.xaml b/SourceGit/UI/PopupManager.xaml deleted file mode 100644 index 8ddb084c..00000000 --- a/SourceGit/UI/PopupManager.xaml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/PopupManager.xaml.cs b/SourceGit/UI/PopupManager.xaml.cs deleted file mode 100644 index 948bc8bf..00000000 --- a/SourceGit/UI/PopupManager.xaml.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Media.Animation; - -namespace SourceGit.UI { - - /// - /// Common popup manager. - /// - public partial class PopupManager : UserControl { - private bool locked = false; - - /// - /// Constructor. - /// - public PopupManager() { - InitializeComponent(); - } - - /// - /// Show content as popup. - /// - /// - public void Show(UIElement elem) { - if (locked) return; - - var gone = new Thickness(0, -(double)elem.GetValue(HeightProperty) - 16, 0, 0); - - ThicknessAnimation anim = new ThicknessAnimation(); - anim.Duration = TimeSpan.FromMilliseconds(150); - anim.From = gone; - anim.To = new Thickness(0); - - statusMsg.Content = ""; - popupContent.Child = elem; - popupContent.Margin = gone; - Visibility = Visibility.Visible; - popupContent.BeginAnimation(MarginProperty, anim); - } - - /// - /// Is current locked. - /// - /// - public bool IsLocked() { - return locked; - } - - /// - /// Lock - /// - public void Lock() { - locked = true; - status.Visibility = Visibility.Visible; - - DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1)); - anim.RepeatBehavior = RepeatBehavior.Forever; - statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim); - } - - /// - /// Unlock - /// - public void Unlock() { - locked = false; - statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null); - status.Visibility = Visibility.Collapsed; - } - - /// - /// Update status description - /// - /// - public void UpdateStatus(string desc) { - Dispatcher.Invoke(() => { - statusMsg.Content = desc; - }); - } - - /// - /// Close current popup. - /// - /// - public void Close(bool unlockFirst = false) { - if (popupContent.Child == null) return; - if (locked && !unlockFirst) return; - locked = false; - - ThicknessAnimation anim = new ThicknessAnimation(); - anim.Duration = TimeSpan.FromMilliseconds(150); - anim.From = new Thickness(0); - anim.To = new Thickness(0, -(double)popupContent.Child.GetValue(HeightProperty) - 16, 0, 0); - anim.Completed += (obj, ev) => { - Visibility = Visibility.Collapsed; - popupContent.Child = null; - }; - - popupContent.BeginAnimation(MarginProperty, anim); - statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null); - status.Visibility = Visibility.Collapsed; - } - - /// - /// Close by click blank area. - /// - /// - /// - private void Close(object sender, RoutedEventArgs e) { - Close(); - } - } -} diff --git a/SourceGit/UI/Preference.xaml b/SourceGit/UI/Preference.xaml deleted file mode 100644 index 52ab8e5e..00000000 --- a/SourceGit/UI/Preference.xaml +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/UI/Preference.xaml.cs b/SourceGit/UI/Preference.xaml.cs deleted file mode 100644 index 8fa950b6..00000000 --- a/SourceGit/UI/Preference.xaml.cs +++ /dev/null @@ -1,220 +0,0 @@ -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Preference window. - /// - public partial class Preference : Window { - - /// - /// Git global user name. - /// - public string GlobalUser { - get; - set; - } - - /// - /// Git global user email. - /// - public string GlobalUserEmail { - get; - set; - } - - /// - /// Git core.autocrlf setting. - /// - public string AutoCRLF { - get; - set; - } - - /// - /// Options for core.autocrlf - /// - public class AutoCRLFOption { - public string Value { get; set; } - public string Desc { get; set; } - - public AutoCRLFOption(string v, string d) { - Value = v; - Desc = d; - } - } - - /// - /// Constructor. - /// - public Preference() { - GlobalUser = GetConfig("user.name"); - GlobalUserEmail = GetConfig("user.email"); - AutoCRLF = GetConfig("core.autocrlf"); - if (string.IsNullOrEmpty(AutoCRLF)) AutoCRLF = "false"; - - InitializeComponent(); - - int mergeType = App.Preference.MergeTool; - var merger = Git.MergeTool.Supported[mergeType]; - txtMergePath.IsReadOnly = !merger.IsConfigured; - txtMergeParam.Text = merger.Parameter; - - var crlfOptions = new List() { - new AutoCRLFOption("true", "Commit as LF, checkout as CRLF"), - new AutoCRLFOption("input", "Only convert for commit"), - new AutoCRLFOption("false", "Do NOT convert"), - }; - cmbAutoCRLF.ItemsSource = crlfOptions; - cmbAutoCRLF.SelectedItem = crlfOptions.Find(o => o.Value == AutoCRLF); - } - - /// - /// Close this dialog - /// - private void Close(object sender, RoutedEventArgs e) { - var oldUser = GetConfig("user.name"); - if (oldUser != GlobalUser) SetConfig("user.name", GlobalUser); - - var oldEmail = GetConfig("user.email"); - if (oldEmail != GlobalUserEmail) SetConfig("user.email", GlobalUserEmail); - - var oldAutoCRLF = GetConfig("core.autocrlf"); - if (oldAutoCRLF != AutoCRLF) SetConfig("core.autocrlf", AutoCRLF); - - Close(); - } - - /// - /// Select git executable file path. - /// - /// - /// - private void SelectGitPath(object sender, RoutedEventArgs e) { - var dialog = new OpenFileDialog(); - dialog.Filter = "Git Executable|git.exe"; - dialog.FileName = "git.exe"; - dialog.Title = "Select Git Executable File"; - dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); - dialog.CheckFileExists = true; - - if (dialog.ShowDialog() == true) { - txtGitPath.Text = dialog.FileName; - App.Preference.GitExecutable = dialog.FileName; - } - } - - /// - /// Set default clone path. - /// - /// - /// - private void SelectDefaultClonePath(object sender, RoutedEventArgs e) { - var dialog = new System.Windows.Forms.FolderBrowserDialog(); - dialog.Description = "Select Folder To Clone Repository Into As Default"; - dialog.RootFolder = Environment.SpecialFolder.MyComputer; - dialog.ShowNewFolderButton = true; - - if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - txtGitCloneDir.Text = dialog.SelectedPath; - App.Preference.GitDefaultCloneDir = dialog.SelectedPath; - } - } - - /// - /// Choose external merge tool. - /// - /// - /// - private void ChangeMergeTool(object sender, SelectionChangedEventArgs e) { - if (IsLoaded) { - var t = Git.MergeTool.Supported[App.Preference.MergeTool]; - - App.Preference.MergeExecutable = t.Finder(); - - txtMergePath.Text = App.Preference.MergeExecutable; - txtMergeParam.Text = t.Parameter; - txtMergePath.IsReadOnly = !t.IsConfigured; - } - } - - /// - /// Set merge tool executable file path. - /// - /// - /// - private void SelectMergeToolPath(object sender, RoutedEventArgs e) { - int mergeType = App.Preference.MergeTool; - if (mergeType == 0) return; - - var merger = Git.MergeTool.Supported[mergeType]; - var dialog = new OpenFileDialog(); - dialog.Filter = $"{merger.Name} Executable|{merger.ExecutableName}"; - dialog.Title = $"Select {merger.Name} Install Path"; - dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); - dialog.CheckFileExists = true; - - if (dialog.ShowDialog() == true) { - txtMergePath.Text = dialog.FileName; - App.Preference.MergeExecutable = dialog.FileName; - } - } - - /// - /// Set core.autocrlf - /// - /// - /// - private void AutoCRLFSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count != 1) return; - - var mode = e.AddedItems[0] as AutoCRLFOption; - if (mode == null) return; - - AutoCRLF = mode.Value; - } - - #region CONFIG - private string GetConfig(string key) { - if (!App.IsGitConfigured) return ""; - - var startInfo = new ProcessStartInfo(); - startInfo.FileName = App.Preference.GitExecutable; - startInfo.Arguments = $"config --global {key}"; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.RedirectStandardOutput = true; - startInfo.StandardOutputEncoding = Encoding.UTF8; - - var proc = new Process() { StartInfo = startInfo }; - proc.Start(); - var output = proc.StandardOutput.ReadToEnd(); - proc.WaitForExit(); - proc.Close(); - - return output.Trim(); - } - - private void SetConfig(string key, string val) { - if (!App.IsGitConfigured) return; - - var startInfo = new ProcessStartInfo(); - startInfo.FileName = App.Preference.GitExecutable; - startInfo.Arguments = $"config --global {key} \"{val}\""; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - - var proc = new Process() { StartInfo = startInfo }; - proc.Start(); - proc.WaitForExit(); - proc.Close(); - } - #endregion - } -} diff --git a/SourceGit/UI/Pull.xaml b/SourceGit/UI/Pull.xaml deleted file mode 100644 index 57c0e072..00000000 --- a/SourceGit/UI/Pull.xaml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Pull.xaml.cs b/SourceGit/UI/Pull.xaml.cs deleted file mode 100644 index fd97ed9f..00000000 --- a/SourceGit/UI/Pull.xaml.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Git pull - /// - public partial class Pull : UserControl { - private Git.Repository repo = null; - private string preferRemote = null; - private string preferBranch = null; - - /// - /// Constructor - /// - /// Opened repository - /// Prefered remote branch. - public Pull(Git.Repository opened, string preferRemoteBranch) { - repo = opened; - InitializeComponent(); - SetContent(preferRemoteBranch); - } - - /// - /// Display git pull dialog. - /// - /// Opened repository - /// Prefered remote branch - public static void Show(Git.Repository opened, string preferRemoteBranch = null) { - var popup = App.GetPopupManager(opened); - popup?.Show(new Pull(opened, preferRemoteBranch)); - } - - /// - /// Set content. - /// - private void SetContent(string prefered) { - var branches = repo.Branches(); - var remotes = new List(); - var current = null as Git.Branch; - - foreach (var b in branches) { - if (b.IsLocal) { - if (b.IsCurrent) current = b; - } else { - if (!remotes.Contains(b.Remote)) remotes.Add(b.Remote); - } - } - - if (!string.IsNullOrEmpty(prefered)) { - preferRemote = prefered.Substring(0, prefered.IndexOf('/')); - preferBranch = prefered; - } else if (current != null && !string.IsNullOrEmpty(current.Upstream)) { - var upstream = current.Upstream.Substring("refs/remotes/".Length); - preferRemote = upstream.Substring(0, upstream.IndexOf('/')); - preferBranch = upstream; - } - - txtInto.Content = current.Name; - combRemotes.ItemsSource = remotes; - combRemotes.SelectedItem = preferRemote; - } - - /// - /// Start pull - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - var remote = combRemotes.SelectedItem as string; - var branch = combBranches.SelectedItem as string; - var rebase = chkRebase.IsChecked == true; - var autoStash = chkAutoStash.IsChecked == true; - - if (remote == null || branch == null) return; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => repo.Pull(remote, branch.Substring(branch.IndexOf('/')+1), msg => popup?.UpdateStatus(msg), rebase, autoStash)); - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - - /// - /// Remote selection changed event. - /// - /// - /// - private void RemotesSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count != 1) return; - - var remote = e.AddedItems[0] as string; - var allBranches = repo.Branches(); - var branches = new List(); - - foreach (var b in allBranches) { - if (!b.IsLocal && b.Remote == remote) { - branches.Add(b.Name); - } - } - - combBranches.ItemsSource = branches; - if (remote == preferRemote && preferBranch != null) { - combBranches.SelectedItem = preferBranch; - } else { - combBranches.SelectedIndex = 0; - } - } - } -} diff --git a/SourceGit/UI/Push.xaml b/SourceGit/UI/Push.xaml deleted file mode 100644 index b5ed0148..00000000 --- a/SourceGit/UI/Push.xaml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Push.xaml.cs b/SourceGit/UI/Push.xaml.cs deleted file mode 100644 index 3587e070..00000000 --- a/SourceGit/UI/Push.xaml.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Git push dialog - /// - public partial class Push : UserControl { - private Git.Repository repo = null; - - /// - /// Constructor. - /// - /// Opened repository. - /// Prefered push branch. - public Push(Git.Repository opened, Git.Branch prefer) { - repo = opened; - InitializeComponent(); - SetContent(prefer); - } - - /// - /// Show push dialog. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Branch prefer = null) { - var popup = App.GetPopupManager(repo); - popup?.Show(new Push(repo, prefer)); - } - - /// - /// Show push and start directly. - /// - /// - public static void StartDirectly(Git.Repository repo) { - var current = repo.CurrentBranch(); - if (current == null || string.IsNullOrEmpty(current.Upstream)) { - App.RaiseError("Current branch has no tracked upstream"); - return; - } - - var push = new Push(repo, current); - var popup = App.GetPopupManager(repo); - popup?.Show(push); - popup?.Lock(); - - var upstream = current.Upstream.Substring(13); - var remoteIdx = upstream.IndexOf('/'); - var remote = upstream.Substring(0, remoteIdx); - var remoteBranch = upstream.Substring(remoteIdx + 1); - - Task.Run(() => { - repo.Push(remote, current.Name, remoteBranch, msg => popup?.UpdateStatus(msg)); - push.Dispatcher.Invoke(() => { - popup?.Close(true); - }); - }); - } - - /// - /// Set content. - /// - private void SetContent(Git.Branch prefer) { - var allBranches = repo.Branches(); - var localBranches = new List(); - - foreach (var b in allBranches) { - if (b.IsLocal) { - localBranches.Add(b); - if (b.IsCurrent && prefer == null) prefer = b; - } - } - - combLocalBranches.ItemsSource = localBranches; - combLocalBranches.SelectedItem = prefer; - } - - /// - /// Start push. - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - var localBranch = combLocalBranches.SelectedItem as Git.Branch; - var remote = combRemotes.SelectedItem as string; - var remoteBranch = combRemoteBranches.SelectedItem as string; - var track = string.IsNullOrEmpty(localBranch.Upstream); - var tags = chkTags.IsChecked == true; - var force = chkForce.IsChecked == true; - - remoteBranch = remoteBranch.Substring($"{remote}/".Length); - if (remoteBranch.Contains(" (new)")) { - remoteBranch = remoteBranch.Substring(0, remoteBranch.Length - 6); - } - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => repo.Push(remote, localBranch.Name, remoteBranch, msg => popup?.UpdateStatus(msg), tags, track, force)); - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - - /// - /// Local branch selection changed. - /// - /// - /// - private void LocalBranchesSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count != 1) return; - - var current = e.AddedItems[0] as Git.Branch; - var allRemotes = repo.Remotes(); - var remoteNames = new List(); - foreach (var r in allRemotes) remoteNames.Add(r.Name); - combRemotes.ItemsSource = null; - combRemotes.ItemsSource = remoteNames; - - if (!string.IsNullOrEmpty(current.Upstream)) { - var upstream = current.Upstream.Substring("refs/remotes/".Length); - combRemotes.SelectedItem = upstream.Substring(0, upstream.IndexOf('/')); - } else { - combRemotes.SelectedIndex = 0; - } - } - - /// - /// Remote selection changed. - /// - /// - /// - private void RemotesSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count != 1) return; - - var remote = e.AddedItems[0] as string; - var allBranches = repo.Branches(); - var branches = new List(); - - combRemoteBranches.ItemsSource = null; - - foreach (var b in allBranches) { - if (!b.IsLocal && b.Remote == remote) { - branches.Add(b.Name); - } - } - - var current = combLocalBranches.SelectedItem as Git.Branch; - if (string.IsNullOrEmpty(current.Upstream)) { - var newBranch = $"{remote}/{current.Name} (new)"; - branches.Add(newBranch); - combRemoteBranches.ItemsSource = branches; - combRemoteBranches.SelectedItem = newBranch; - } else if (current.Upstream.StartsWith($"refs/remotes/{remote}", StringComparison.Ordinal)) { - combRemoteBranches.ItemsSource = branches; - combRemoteBranches.SelectedItem = current.Upstream.Substring("refs/remotes/".Length); - } else { - var match = $"{remote}/{current.Name}"; - foreach (var b in branches) { - if (b == match) { - combRemoteBranches.ItemsSource = branches; - combRemoteBranches.SelectedItem = b; - return; - } - } - - var newBranch = $"{remote}/{current.Name} (new)"; - branches.Add(newBranch); - combRemoteBranches.ItemsSource = branches; - combRemoteBranches.SelectedItem = newBranch; - } - } - } -} diff --git a/SourceGit/UI/PushTag.xaml b/SourceGit/UI/PushTag.xaml deleted file mode 100644 index a78183c6..00000000 --- a/SourceGit/UI/PushTag.xaml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SourceGit/UI/PushTag.xaml.cs b/SourceGit/UI/PushTag.xaml.cs deleted file mode 100644 index b2fc8fc0..00000000 --- a/SourceGit/UI/PushTag.xaml.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Push tag to remote dialog - /// - public partial class PushTag : UserControl { - private Git.Repository repo = null; - private Git.Tag tag = null; - - /// - /// Constructor - /// - /// Opened repo - /// Delete tag - public PushTag(Git.Repository repo, Git.Tag tag) { - this.repo = repo; - this.tag = tag; - - InitializeComponent(); - tagName.Content = tag.Name; - combRemotes.ItemsSource = repo.Remotes(); - combRemotes.SelectedIndex = 0; - } - - /// - /// Display this dialog. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Tag tag) { - var popup = App.GetPopupManager(repo); - popup?.Show(new PushTag(repo, tag)); - } - - /// - /// Start request. - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - var remote = combRemotes.SelectedItem as Git.Remote; - if (remote == null) return; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => Git.Tag.Push(repo, tag.Name, remote.Name)); - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Close(); - } - } -} diff --git a/SourceGit/UI/Rebase.xaml b/SourceGit/UI/Rebase.xaml deleted file mode 100644 index f46f1193..00000000 --- a/SourceGit/UI/Rebase.xaml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Rebase.xaml.cs b/SourceGit/UI/Rebase.xaml.cs deleted file mode 100644 index 1c583882..00000000 --- a/SourceGit/UI/Rebase.xaml.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace SourceGit.UI { - - /// - /// Rebase current branch on selected commit/branch - /// - public partial class Rebase : UserControl { - private Git.Repository repo = null; - private string based = null; - - /// - /// Constructor. - /// - /// Opened repository - public Rebase(Git.Repository opened) { - repo = opened; - InitializeComponent(); - } - - /// - /// Rebase current branch on selected branch - /// - /// - /// - public static void Show(Git.Repository opened, Git.Branch branch) { - if (branch == null) return; - - var current = opened.CurrentBranch(); - if (current == null) return; - - var dialog = new Rebase(opened); - dialog.based = branch.Head; - dialog.branch.Content = current.Name; - dialog.type.Data = dialog.FindResource("Icon.Branch") as Geometry; - dialog.desc.Content = branch.Name; - - var popup = App.GetPopupManager(opened); - popup?.Show(dialog); - } - - /// - /// Rebase current branch on selected commit. - /// - /// - /// - public static void Show(Git.Repository opened, Git.Commit commit) { - var current = opened.CurrentBranch(); - if (current == null) return; - - var dialog = new Rebase(opened); - dialog.based = commit.SHA; - dialog.branch.Content = current.Name; - dialog.type.Data = dialog.FindResource("Icon.Commit") as Geometry; - dialog.desc.Content = $"{commit.ShortSHA} {commit.Subject}"; - - var popup = App.GetPopupManager(opened); - popup?.Show(dialog); - } - - /// - /// Start rebase. - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Lock(); - - var autoStash = chkAutoStash.IsChecked == true; - await Task.Run(() => repo.Rebase(based, autoStash)); - - popup?.Close(true); - } - - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/Remote.xaml b/SourceGit/UI/Remote.xaml deleted file mode 100644 index c044c157..00000000 --- a/SourceGit/UI/Remote.xaml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Remote.xaml.cs b/SourceGit/UI/Remote.xaml.cs deleted file mode 100644 index 3c6ecee9..00000000 --- a/SourceGit/UI/Remote.xaml.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Create or edit remote dialog. - /// - public partial class Remote : UserControl { - private Git.Repository repo = null; - private Git.Remote remote = null; - - public string RemoteName { get; set; } - public string RemoteUri { get; set; } - - /// - /// Constructor. - /// - /// Opened repository - /// Editing remote - public Remote(Git.Repository opened, Git.Remote editing) { - repo = opened; - remote = editing; - - if (remote != null) { - RemoteName = remote.Name; - RemoteUri = remote.URL; - } - - InitializeComponent(); - nameValidator.Repo = repo; - - if (remote != null) { - title.Content = "Edit Remote"; - } else { - title.Content = "Add New Remote"; - } - } - - /// - /// Display this dialog. - /// - /// - /// - public static void Show(Git.Repository opened, Git.Remote editing = null) { - App.GetPopupManager(opened)?.Show(new Remote(opened, editing)); - } - - /// - /// Commit request. - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtName)) return; - - txtUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtUrl)) return; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - - await Task.Run(() => { - if (remote != null) { - remote.Edit(repo, RemoteName, RemoteUri); - } else { - Git.Remote.Add(repo, RemoteName, RemoteUri); - } - }); - - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/RenameBranch.xaml b/SourceGit/UI/RenameBranch.xaml deleted file mode 100644 index 21aaf9ed..00000000 --- a/SourceGit/UI/RenameBranch.xaml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/RenameBranch.xaml.cs b/SourceGit/UI/RenameBranch.xaml.cs deleted file mode 100644 index a89c30a7..00000000 --- a/SourceGit/UI/RenameBranch.xaml.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Rename branch dialog. - /// - public partial class RenameBranch : UserControl { - private Git.Repository repo = null; - private Git.Branch branch = null; - - /// - /// New branch name. - /// - public string NewName { get; set; } - - /// - /// Constructor. - /// - /// Opened repository. - /// Branch to rename. - public RenameBranch(Git.Repository opened, Git.Branch target) { - repo = opened; - branch = target; - NewName = target.Name; - - InitializeComponent(); - - nameValidator.Repo = opened; - txtOldName.Content = target.Name; - } - - /// - /// Show this dialog - /// - /// - /// - public static void Show(Git.Repository opened, Git.Branch branch) { - App.GetPopupManager(opened)?.Show(new RenameBranch(opened, branch)); - } - - /// - /// Rename - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - txtNewName.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtNewName)) return; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => branch.Rename(repo, NewName)); - popup?.Close(true); - } - - /// - /// Cancel merge. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/Reset.xaml b/SourceGit/UI/Reset.xaml deleted file mode 100644 index 6ae66237..00000000 --- a/SourceGit/UI/Reset.xaml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Reset.xaml.cs b/SourceGit/UI/Reset.xaml.cs deleted file mode 100644 index 60cebfd7..00000000 --- a/SourceGit/UI/Reset.xaml.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace SourceGit.UI { - - /// - /// Reset branch to revision dialog. - /// - public partial class Reset : UserControl { - private Git.Repository repo = null; - private string revision = null; - - /// - /// Reset mode. - /// - public class Mode { - public Brush Color { get; set; } - public string Name { get; set; } - public string Desc { get; set; } - public string Arg { get; set; } - public Mode(Brush b, string n, string d, string a) { - Color = b; - Name = n; - Desc = d; - Arg = a; - } - } - - /// - /// Constructor. - /// - /// - /// - /// - public Reset(Git.Repository opened, Git.Branch current, Git.Commit commit) { - InitializeComponent(); - - repo = opened; - revision = commit.SHA; - - branch.Content = current.Name; - desc.Content = $"{commit.ShortSHA} {commit.Subject}"; - combMode.ItemsSource = new Mode[] { - new Mode(Brushes.Green, "Soft", "Keep all changes. Stage differences", "--soft"), - new Mode(Brushes.Yellow, "Mixed", "Keep all changes. Unstage differences", "--mixed"), - new Mode(Brushes.Red, "Hard", "Discard all changes", "--hard"), - }; - combMode.SelectedIndex = 0; - } - - /// - /// Show dialog. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Commit commit) { - var current = repo.CurrentBranch(); - if (current == null) return; - - App.GetPopupManager(repo)?.Show(new Reset(repo, current, commit)); - } - - /// - /// Start reset. - /// - /// - /// - private async void Start(object sender, RoutedEventArgs e) { - var mode = combMode.SelectedItem as Mode; - if (mode == null) return; - - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => repo.Reset(revision, mode.Arg)); - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/Revert.xaml b/SourceGit/UI/Revert.xaml deleted file mode 100644 index 5eb0a24a..00000000 --- a/SourceGit/UI/Revert.xaml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Revert.xaml.cs b/SourceGit/UI/Revert.xaml.cs deleted file mode 100644 index 7bee143e..00000000 --- a/SourceGit/UI/Revert.xaml.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Confirm to revert selected commit. - /// - public partial class Revert : UserControl { - private Git.Repository repo = null; - private string sha = null; - - /// - /// Constructor. - /// - /// Opened repository - /// Commit to be reverted - public Revert(Git.Repository opened, Git.Commit commit) { - repo = opened; - sha = commit.SHA; - - InitializeComponent(); - txtDesc.Content = $"{commit.ShortSHA} {commit.Subject}"; - } - - /// - /// Open this dialog. - /// - /// - /// - public static void Show(Git.Repository repo, Git.Commit commit) { - var popup = App.GetPopupManager(repo); - popup?.Show(new Revert(repo, commit)); - } - - /// - /// Start revert. - /// - /// - /// - private async void Sure(object sender, RoutedEventArgs e) { - bool autoCommit = chkCommit.IsChecked == true; - var popup = App.GetPopupManager(repo); - popup?.Lock(); - await Task.Run(() => repo.Revert(sha, autoCommit)); - popup?.Close(true); - } - - /// - /// Cancel. - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - var popup = App.GetPopupManager(repo); - popup?.Close(); - } - } -} diff --git a/SourceGit/UI/Stash.xaml b/SourceGit/UI/Stash.xaml deleted file mode 100644 index ba1f26de..00000000 --- a/SourceGit/UI/Stash.xaml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Stash.xaml.cs b/SourceGit/UI/Stash.xaml.cs deleted file mode 100644 index 1d3ba0e9..00000000 --- a/SourceGit/UI/Stash.xaml.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Git save stash panel. - /// - public partial class Stash : UserControl { - private Git.Repository repo = null; - private List files = new List(); - - /// - /// Constructor. - /// - /// Opened repsitory - public Stash(Git.Repository repo, List files) { - this.repo = repo; - this.files = files; - InitializeComponent(); - chkIncludeUntracked.IsEnabled = files.Count == 0; - } - - /// - /// Open this dialog. - /// - /// Opened repository - /// Special files to stash - public static void Show(Git.Repository repo, List files) { - var popup = App.GetPopupManager(repo); - popup?.Show(new Stash(repo, files)); - } - - /// - /// Start saving stash. - /// - /// - /// - private void Start(object sender, RoutedEventArgs e) { - bool includeUntracked = chkIncludeUntracked.IsChecked == true; - string message = txtName.Text; - - Git.Stash.Push(repo, includeUntracked, message, files); - App.GetPopupManager(repo)?.Close(); - } - - /// - /// Cancel - /// - /// - /// - private void Cancel(object sender, RoutedEventArgs e) { - App.GetPopupManager(repo)?.Close(); - } - } -} diff --git a/SourceGit/UI/Stashes.xaml b/SourceGit/UI/Stashes.xaml deleted file mode 100644 index 832b0302..00000000 --- a/SourceGit/UI/Stashes.xaml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/Stashes.xaml.cs b/SourceGit/UI/Stashes.xaml.cs deleted file mode 100644 index 79cfa182..00000000 --- a/SourceGit/UI/Stashes.xaml.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.Generic; -using System.Windows; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// Stashes viewer. - /// - public partial class Stashes : UserControl { - private Git.Repository repo = null; - private string selectedStash = null; - - /// - /// File tree node. - /// - public class Node { - public string FilePath { get; set; } = ""; - public string OriginalPath { get; set; } = ""; - public string Name { get; set; } = ""; - public bool IsFile { get; set; } = false; - public bool IsNodeExpanded { get; set; } = true; - public Git.Change.Status Status { get; set; } = Git.Change.Status.None; - public List Children { get; set; } = new List(); - } - - /// - /// Constructor. - /// - public Stashes() { - InitializeComponent(); - } - - /// - /// Cleanup - /// - /// - /// - private void Cleanup(object sender, RoutedEventArgs e) { - stashList.ItemsSource = null; - changeList.ItemsSource = null; - diff.Reset(); - } - - /// - /// Set data. - /// - /// - /// - public void SetData(Git.Repository opened, List stashes) { - repo = opened; - selectedStash = null; - stashList.ItemsSource = stashes; - changeList.ItemsSource = null; - diff.Reset(); - } - - /// - /// Stash list selection changed event. - /// - /// - /// - private void StashSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count != 1) return; - - var stash = e.AddedItems[0] as Git.Stash; - if (stash == null) return; - - selectedStash = stash.SHA; - diff.Reset(); - changeList.ItemsSource = stash.GetChanges(repo); - } - - /// - /// File selection changed in TreeView. - /// - /// - /// - private void FileSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.AddedItems.Count != 1) return; - - var change = e.AddedItems[0] as Git.Change; - if (change == null) return; - - diff.Diff(repo, new DiffViewer.Option() { - RevisionRange = new string[] { $"{selectedStash}^", selectedStash }, - Path = change.Path, - OrgPath = change.OriginalPath - }); - } - - /// - /// Stash context menu. - /// - /// - /// - private void StashContextMenuOpening(object sender, ContextMenuEventArgs ev) { - var stash = (sender as ListViewItem).DataContext as Git.Stash; - if (stash == null) return; - - var apply = new MenuItem(); - apply.Header = "Apply"; - apply.Click += (o, e) => stash.Apply(repo); - - var pop = new MenuItem(); - pop.Header = "Pop"; - pop.Click += (o, e) => stash.Pop(repo); - - var delete = new MenuItem(); - delete.Header = "Drop"; - delete.Click += (o, e) => stash.Drop(repo); - - var menu = new ContextMenu(); - menu.Items.Add(apply); - menu.Items.Add(pop); - menu.Items.Add(delete); - menu.IsOpen = true; - ev.Handled = true; - } - } -} diff --git a/SourceGit/UI/Waiting.xaml b/SourceGit/UI/Waiting.xaml deleted file mode 100644 index bf98f70a..00000000 --- a/SourceGit/UI/Waiting.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - diff --git a/SourceGit/UI/Waiting.xaml.cs b/SourceGit/UI/Waiting.xaml.cs deleted file mode 100644 index 27e8836d..00000000 --- a/SourceGit/UI/Waiting.xaml.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Controls; - -namespace SourceGit.UI { - - /// - /// General waiting dialog. - /// - public partial class Waiting : UserControl { - - /// - /// Constructor. - /// - public Waiting() { - InitializeComponent(); - } - - /// - /// Show this dialog. - /// - /// - /// - public static void Show(Git.Repository repo, Action job) { - var dialog = new Waiting(); - var popup = App.GetPopupManager(repo); - - popup?.Show(dialog); - popup?.Lock(); - Task.Run(() => { - job.Invoke(); - dialog.Dispatcher.Invoke(() => { - popup?.Close(true); - }); - }); - } - } -} diff --git a/SourceGit/UI/WorkingCopy.xaml b/SourceGit/UI/WorkingCopy.xaml deleted file mode 100644 index 63fd3747..00000000 --- a/SourceGit/UI/WorkingCopy.xaml +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SourceGit/UI/WorkingCopy.xaml.cs b/SourceGit/UI/WorkingCopy.xaml.cs deleted file mode 100644 index 5e1e9fb4..00000000 --- a/SourceGit/UI/WorkingCopy.xaml.cs +++ /dev/null @@ -1,1061 +0,0 @@ -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Input; -using System.Windows.Media; - -namespace SourceGit.UI { - - /// - /// Working copy panel. - /// - public partial class WorkingCopy : UserControl { - - /// - /// Node for file tree. - /// - public class Node { - public string FilePath { get; set; } = ""; - public string Name { get; set; } = ""; - public bool IsFile { get; set; } = false; - public bool IsNodeExpanded { get; set; } = true; - public Git.Change Change { get; set; } = null; - public List Children { get; set; } = new List(); - } - - /// - /// Current opened repository. - /// - public Git.Repository Repo { get; set; } - - /// - /// Just for Validation. - /// - public string CommitMessage { get; set; } - - /// - /// Has conflict object? - /// - private bool hasConflict = false; - - /// - /// Constructor. - /// - public WorkingCopy() { - InitializeComponent(); - } - - /// - /// Set display data. - /// - /// - public bool SetData(List changes) { - List staged = new List(); - List unstaged = new List(); - hasConflict = false; - - foreach (var c in changes) { - hasConflict = hasConflict || c.IsConflit; - - if (c.Index != Git.Change.Status.None && c.Index != Git.Change.Status.Untracked) { - staged.Add(c); - } - - if (c.WorkTree != Git.Change.Status.None) { - unstaged.Add(c); - } - } - - Dispatcher.Invoke(() => mergePanel.Visibility = Visibility.Collapsed); - - SetData(unstaged, true); - SetData(staged, false); - - Dispatcher.Invoke(() => { - var current = Repo.CurrentBranch(); - if (current != null && !string.IsNullOrEmpty(current.Upstream) && chkAmend.IsChecked != true) { - btnCommitAndPush.Visibility = Visibility.Visible; - } else { - btnCommitAndPush.Visibility = Visibility.Collapsed; - } - - diffViewer.Reset(); - }); - - return hasConflict; - } - - /// - /// Try to load merge message. - /// - public void LoadMergeMessage() { - if (string.IsNullOrEmpty(txtCommitMsg.Text)) { - var mergeMsgFile = Path.Combine(Repo.GitDir, "MERGE_MSG"); - if (!File.Exists(mergeMsgFile)) return; - - var content = File.ReadAllText(mergeMsgFile); - txtCommitMsg.Text = content; - } - } - - /// - /// Clear message. - /// - public void ClearMessage() { - txtCommitMsg.Text = ""; - Validation.ClearInvalid(txtCommitMsg.GetBindingExpression(TextBox.TextProperty)); - } - - #region UNSTAGED - private void UnstagedTreeMultiSelectionChanged(object sender, RoutedEventArgs e) { - mergePanel.Visibility = Visibility.Collapsed; - diffViewer.Reset(); - - var selected = Helpers.TreeViewHelper.GetSelectedItems(unstagedTree); - if (selected.Count == 0) return; - - Helpers.TreeViewHelper.UnselectTree(stageTree); - stageList.SelectedItems.Clear(); - - if (selected.Count != 1) return; - - var node = selected[0].DataContext as Node; - if (!node.IsFile) return; - - if (node.Change.IsConflit) { - mergePanel.Visibility = Visibility.Visible; - return; - } - - DiffViewer.Option opt; - switch (node.Change.WorkTree) { - case Git.Change.Status.Added: - case Git.Change.Status.Untracked: - opt = new DiffViewer.Option() { ExtraArgs = "--no-index", Path = node.FilePath, OrgPath = "/dev/null" }; - break; - default: - opt = new DiffViewer.Option() { Path = node.FilePath, OrgPath = node.Change.OriginalPath }; - break; - } - - diffViewer.Diff(Repo, opt); - } - - private void UnstagedListSelectionChanged(object sender, SelectionChangedEventArgs e) { - var selected = unstagedList.SelectedItems; - if (selected.Count == 0) return; - - mergePanel.Visibility = Visibility.Collapsed; - diffViewer.Reset(); - Helpers.TreeViewHelper.UnselectTree(stageTree); - stageList.SelectedItems.Clear(); - - if (selected.Count != 1) return; - - var change = selected[0] as Git.Change; - if (change.IsConflit) { - mergePanel.Visibility = Visibility.Visible; - return; - } - - DiffViewer.Option opt; - switch (change.WorkTree) { - case Git.Change.Status.Added: - case Git.Change.Status.Untracked: - opt = new DiffViewer.Option() { ExtraArgs = "--no-index", Path = change.Path, OrgPath = "/dev/null" }; - break; - default: - opt = new DiffViewer.Option() { Path = change.Path, OrgPath = change.OriginalPath }; - break; - } - - diffViewer.Diff(Repo, opt); - } - - private void SaveAsPatchFromUnstagedChanges(string path, List changes) { - FileStream stream = new FileStream(path, FileMode.Create); - StreamWriter writer = new StreamWriter(stream); - - foreach (var change in changes) { - if (change.WorkTree == Git.Change.Status.Added || change.WorkTree == Git.Change.Status.Untracked) { - Repo.RunCommand($"diff --no-index --no-ext-diff --find-renames -- /dev/null \"{change.Path}\"", line => { - writer.WriteLine(line); - }); - } else { - var orgFile = string.IsNullOrEmpty(change.OriginalPath) ? "" : $"\"{change.OriginalPath}\""; - Repo.RunCommand($"diff --binary --no-ext-diff --find-renames --full-index -- {orgFile} \"{change.Path}\"", line => { - writer.WriteLine(line); - }); - } - } - - writer.Flush(); - stream.Flush(); - writer.Close(); - stream.Close(); - } - - private void GetChangesFromNode(Node node, List outs) { - if (node.Change != null) { - if (!outs.Contains(node.Change)) outs.Add(node.Change); - } else if (node.Children.Count > 0) { - foreach (var sub in node.Children) GetChangesFromNode(sub, outs); - } - } - - private void UnstagedTreeContextMenuOpening(object sender, ContextMenuEventArgs ev) { - var selected = Helpers.TreeViewHelper.GetSelectedItems(unstagedTree); - - if (selected.Count == 1) { - var item = sender as TreeViewItem; - var node = item.DataContext as Node; - if (node == null) return; - - var changes = new List(); - GetChangesFromNode(node, changes); - - var path = Path.GetFullPath(Repo.Path + "\\" + node.FilePath); - var explore = new MenuItem(); - explore.IsEnabled = File.Exists(path) || Directory.Exists(path); - explore.Header = "Reveal in File Explorer"; - explore.Click += (o, e) => { - if (node.IsFile) Process.Start("explorer", $"/select,{path}"); - else Process.Start(path); - e.Handled = true; - }; - - var stage = new MenuItem(); - stage.Header = "Stage"; - stage.Click += async (o, e) => { - await Task.Run(() => Repo.Stage(node.FilePath)); - e.Handled = true; - }; - - var discard = new MenuItem(); - discard.Header = "Discard changes ..."; - discard.Click += (o, e) => { - Discard.Show(Repo, changes); - e.Handled = true; - }; - - var stash = new MenuItem(); - stash.Header = $"Stash ..."; - stash.Click += (o, e) => { - List nodes = new List() { node.FilePath }; - Stash.Show(Repo, nodes); - e.Handled = true; - }; - - var patch = new MenuItem(); - patch.Header = $"Save as patch ..."; - patch.Click += (o, e) => { - var dialog = new SaveFileDialog(); - dialog.Filter = "Patch File|*.patch"; - dialog.Title = "Select file to store patch data."; - dialog.InitialDirectory = Repo.Path; - - if (dialog.ShowDialog() == true) { - SaveAsPatchFromUnstagedChanges(dialog.FileName, changes); - } - - e.Handled = true; - }; - - var copyPath = new MenuItem(); - copyPath.Header = "Copy full path"; - copyPath.Click += (o, e) => { - Clipboard.SetText(node.FilePath); - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(explore); - menu.Items.Add(new Separator()); - menu.Items.Add(stage); - menu.Items.Add(discard); - menu.Items.Add(stash); - menu.Items.Add(patch); - menu.Items.Add(new Separator()); - menu.Items.Add(copyPath); - menu.IsOpen = true; - } else if (selected.Count > 1) { - var changes = new List(); - var files = new List(); - - foreach (var item in selected) GetChangesFromNode(item.DataContext as Node, changes); - foreach (var c in changes) files.Add(c.Path); - - var stage = new MenuItem(); - stage.Header = $"Stage {changes.Count} files ..."; - stage.Click += async (o, e) => { - await Task.Run(() => Repo.Stage(files.ToArray())); - e.Handled = true; - }; - - var discard = new MenuItem(); - discard.Header = $"Discard {changes.Count} changes ..."; - discard.Click += (o, e) => { - Discard.Show(Repo, changes); - e.Handled = true; - }; - - var stash = new MenuItem(); - stash.Header = $"Stash {changes.Count} files ..."; - stash.Click += (o, e) => { - Stash.Show(Repo, files); - e.Handled = true; - }; - - var patch = new MenuItem(); - patch.Header = $"Save as patch ..."; - patch.Click += (o, e) => { - var dialog = new SaveFileDialog(); - dialog.Filter = "Patch File|*.patch"; - dialog.Title = "Select file to store patch data."; - dialog.InitialDirectory = Repo.Path; - - if (dialog.ShowDialog() == true) { - SaveAsPatchFromUnstagedChanges(dialog.FileName, changes); - } - - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(stage); - menu.Items.Add(discard); - menu.Items.Add(stash); - menu.Items.Add(patch); - menu.IsOpen = true; - } - - ev.Handled = true; - } - - private void UnstagedListContextMenuOpening(object sender, ContextMenuEventArgs ev) { - var row = sender as DataGridRow; - if (row == null) return; - - if (!row.IsSelected) { - unstagedList.SelectedItems.Clear(); - unstagedList.SelectedItems.Add(row.DataContext); - } - - var selected = unstagedList.SelectedItems; - var brush = new SolidColorBrush(Color.FromRgb(48, 48, 48)); - - if (selected.Count == 1) { - var change = selected[0] as Git.Change; - var path = Path.GetFullPath(Repo.Path + "\\" + change.Path); - var explore = new MenuItem(); - explore.IsEnabled = File.Exists(path) || Directory.Exists(path); - explore.Header = "Reveal in File Explorer"; - explore.Click += (o, e) => { - Process.Start("explorer", $"/select,{path}"); - e.Handled = true; - }; - - var stage = new MenuItem(); - stage.Header = "Stage"; - stage.Click += async (o, e) => { - await Task.Run(() => Repo.Stage(change.Path)); - e.Handled = true; - }; - - var discard = new MenuItem(); - discard.Header = "Discard changes ..."; - discard.Click += (o, e) => { - Discard.Show(Repo, new List() { change }); - e.Handled = true; - }; - - var stash = new MenuItem(); - stash.Header = $"Stash ..."; - stash.Click += (o, e) => { - List nodes = new List() { change.Path }; - Stash.Show(Repo, nodes); - e.Handled = true; - }; - - var patch = new MenuItem(); - patch.Header = $"Save as patch ..."; - patch.Click += (o, e) => { - var dialog = new SaveFileDialog(); - dialog.Filter = "Patch File|*.patch"; - dialog.Title = "Select file to store patch data."; - dialog.InitialDirectory = Repo.Path; - - if (dialog.ShowDialog() == true) { - SaveAsPatchFromUnstagedChanges(dialog.FileName, new List() { change }); - } - - e.Handled = true; - }; - - var copyPath = new MenuItem(); - copyPath.Header = "Copy file path"; - copyPath.Click += (o, e) => { - Clipboard.SetText(change.Path); - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(explore); - menu.Items.Add(new Separator()); - menu.Items.Add(stage); - menu.Items.Add(discard); - menu.Items.Add(stash); - menu.Items.Add(patch); - menu.Items.Add(new Separator()); - menu.Items.Add(copyPath); - menu.IsOpen = true; - } else if (selected.Count > 1) { - List files = new List(); - List changes = new List(); - foreach (var item in selected) { - files.Add((item as Git.Change).Path); - changes.Add(item as Git.Change); - } - - var stage = new MenuItem(); - stage.Header = $"Stage {changes.Count} files ..."; - stage.Click += async (o, e) => { - await Task.Run(() => Repo.Stage(files.ToArray())); - e.Handled = true; - }; - - var discard = new MenuItem(); - discard.Header = $"Discard {changes.Count} changes ..."; - discard.Click += (o, e) => { - Discard.Show(Repo, changes); - e.Handled = true; - }; - - var stash = new MenuItem(); - stash.Header = $"Stash {changes.Count} files ..."; - stash.Click += (o, e) => { - Stash.Show(Repo, files); - e.Handled = true; - }; - - var patch = new MenuItem(); - patch.Header = $"Save as patch ..."; - patch.Click += (o, e) => { - var dialog = new SaveFileDialog(); - dialog.Filter = "Patch File|*.patch"; - dialog.Title = "Select file to store patch data."; - dialog.InitialDirectory = Repo.Path; - - if (dialog.ShowDialog() == true) { - SaveAsPatchFromUnstagedChanges(dialog.FileName, changes); - } - - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(stage); - menu.Items.Add(discard); - menu.Items.Add(stash); - menu.Items.Add(patch); - menu.IsOpen = true; - } - - ev.Handled = true; - } - - private async void Stage(object sender, RoutedEventArgs e) { - var files = new List(); - - if (App.Preference.UIUseListInUnstaged) { - var selected = unstagedList.SelectedItems; - foreach (var one in selected) { - var node = one as Git.Change; - if (node != null) files.Add(node.Path); - } - } else { - var selected = Helpers.TreeViewHelper.GetSelectedItems(unstagedTree); - foreach (var one in selected) { - var node = one.DataContext as Node; - if (node != null) files.Add(node.FilePath); - } - } - - if (files.Count == 0) return; - await Task.Run(() => Repo.Stage(files.ToArray())); - } - - private async void StageAll(object sender, RoutedEventArgs e) { - await Task.Run(() => Repo.Stage()); - } - #endregion - - #region STAGED - private void StageTreeMultiSelectionChanged(object sender, RoutedEventArgs e) { - mergePanel.Visibility = Visibility.Collapsed; - diffViewer.Reset(); - - var selected = Helpers.TreeViewHelper.GetSelectedItems(stageTree); - if (selected.Count == 0) return; - - Helpers.TreeViewHelper.UnselectTree(unstagedTree); - unstagedList.SelectedItems.Clear(); - - if (selected.Count != 1) return; - - var node = selected[0].DataContext as Node; - if (!node.IsFile) return; - - mergePanel.Visibility = Visibility.Collapsed; - diffViewer.Diff(Repo, new DiffViewer.Option() { - ExtraArgs = "--cached", - Path = node.FilePath, - OrgPath = node.Change.OriginalPath - }); - e.Handled = true; - } - - private void StagedListSelectionChanged(object sender, SelectionChangedEventArgs e) { - var selected = stageList.SelectedItems; - if (selected.Count == 0) return; - - mergePanel.Visibility = Visibility.Collapsed; - diffViewer.Reset(); - Helpers.TreeViewHelper.UnselectTree(unstagedTree); - unstagedList.SelectedItems.Clear(); - - if (selected.Count != 1) return; - - var change = selected[0] as Git.Change; - mergePanel.Visibility = Visibility.Collapsed; - diffViewer.Diff(Repo, new DiffViewer.Option() { - ExtraArgs = "--cached", - Path = change.Path, - OrgPath = change.OriginalPath - }); - e.Handled = true; - } - - private void StageTreeContextMenuOpening(object sender, ContextMenuEventArgs ev) { - var selected = Helpers.TreeViewHelper.GetSelectedItems(stageTree); - var brush = new SolidColorBrush(Color.FromRgb(48, 48, 48)); - - if (selected.Count == 1) { - var item = sender as TreeViewItem; - if (item == null) return; - - var node = item.DataContext as Node; - if (node == null) return; - - var path = Path.GetFullPath(Repo.Path + "\\" + node.FilePath); - - var explore = new MenuItem(); - explore.IsEnabled = File.Exists(path) || Directory.Exists(path); - explore.Header = "Reveal in File Explorer"; - explore.Click += (o, e) => { - if (node.IsFile) Process.Start("explorer", $"/select,{path}"); - else Process.Start(path); - e.Handled = true; - }; - - var unstage = new MenuItem(); - unstage.Header = "Unstage"; - unstage.Click += async (o, e) => { - await Task.Run(() => Repo.Unstage(node.FilePath)); - e.Handled = true; - }; - - var copyPath = new MenuItem(); - copyPath.Header = "Copy full path"; - copyPath.Click += (o, e) => { - Clipboard.SetText(node.FilePath); - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(explore); - menu.Items.Add(new Separator()); - menu.Items.Add(unstage); - menu.Items.Add(new Separator()); - menu.Items.Add(copyPath); - menu.IsOpen = true; - } else if (selected.Count > 1) { - var changes = new List(); - var files = new List(); - foreach (var item in selected) GetChangesFromNode(item.DataContext as Node, changes); - foreach (var c in changes) files.Add(c.Path); - - var unstage = new MenuItem(); - unstage.Header = $"Unstage {changes.Count} files"; - unstage.Click += async (o, e) => { - await Task.Run(() => Repo.Unstage(files.ToArray())); - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(unstage); - menu.IsOpen = true; - } - - ev.Handled = true; - } - - private void StagedListContextMenuOpening(object sender, ContextMenuEventArgs ev) { - var row = sender as DataGridRow; - if (row == null) return; - - if (!row.IsSelected) { - stageList.SelectedItems.Clear(); - stageList.SelectedItems.Add(row.DataContext); - } - - var selected = stageList.SelectedItems; - var brush = new SolidColorBrush(Color.FromRgb(48, 48, 48)); - - if (selected.Count == 1) { - var change = selected[0] as Git.Change; - var path = Path.GetFullPath(Repo.Path + "\\" + change.Path); - - var explore = new MenuItem(); - explore.IsEnabled = File.Exists(path) || Directory.Exists(path); - explore.Header = "Reveal in File Explorer"; - explore.Click += (o, e) => { - Process.Start("explorer", $"/select,{path}"); - e.Handled = true; - }; - - var unstage = new MenuItem(); - unstage.Header = "Unstage"; - unstage.Click += async (o, e) => { - await Task.Run(() => Repo.Unstage(change.Path)); - e.Handled = true; - }; - - var copyPath = new MenuItem(); - copyPath.Header = "Copy full path"; - copyPath.Click += (o, e) => { - Clipboard.SetText(change.Path); - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(explore); - menu.Items.Add(new Separator()); - menu.Items.Add(unstage); - menu.Items.Add(new Separator()); - menu.Items.Add(copyPath); - menu.IsOpen = true; - } else if (selected.Count > 1) { - List files = new List(); - foreach (var one in selected) files.Add((one as Git.Change).Path); - - var unstage = new MenuItem(); - unstage.Header = $"Unstage {selected.Count} files"; - unstage.Click += async (o, e) => { - await Task.Run(() => Repo.Unstage(files.ToArray())); - e.Handled = true; - }; - - var menu = new ContextMenu(); - menu.Items.Add(unstage); - menu.IsOpen = true; - } - - ev.Handled = true; - } - - private async void Unstage(object sender, RoutedEventArgs e) { - var files = new List(); - - if (App.Preference.UIUseListInStaged) { - var selected = stageList.SelectedItems; - foreach (var one in selected) { - var node = one as Git.Change; - if (node != null) files.Add(node.Path); - } - } else { - var selected = Helpers.TreeViewHelper.GetSelectedItems(stageTree); - foreach (var one in selected) { - var node = one.DataContext as Node; - if (node != null) files.Add(node.FilePath); - } - } - - if (files.Count == 0) return; - await Task.Run(() => Repo.Unstage(files.ToArray())); - } - - private async void UnstageAll(object sender, RoutedEventArgs e) { - await Task.Run(() => Repo.Unstage()); - } - #endregion - - #region COMMIT_PANEL - private void CommitMsgGotFocus(object sender, RoutedEventArgs e) { - var textBox = sender as TextBox; - if (textBox == null) return; - - if (string.IsNullOrEmpty(textBox.Text) && !string.IsNullOrEmpty(Repo.CommitTemplate)) { - textBox.Text = Repo.CommitTemplate; - } - } - - private void CommitMsgPreviewMouseWheel(object sender, MouseWheelEventArgs e) { - var textBox = sender as TextBox; - if (textBox == null) return; - - if (e.Delta > 0) { - textBox.LineUp(); - } else { - textBox.LineDown(); - } - } - - private void OpenCommitMessageSelector(object sender, RoutedEventArgs e) { - var anchor = sender as Button; - - if (anchor.ContextMenu == null) { - anchor.ContextMenu = new ContextMenu(); - anchor.ContextMenu.PlacementTarget = anchor; - anchor.ContextMenu.Placement = PlacementMode.Top; - anchor.ContextMenu.VerticalOffset = -4; - anchor.ContextMenu.StaysOpen = false; - anchor.ContextMenu.Focusable = true; - anchor.ContextMenu.MaxWidth = 500; - } else { - anchor.ContextMenu.Items.Clear(); - } - - if (Repo.CommitMsgRecords.Count == 0) { - var tip = new MenuItem(); - tip.Header = "NO RECENT INPUT MESSAGES"; - tip.IsEnabled = false; - anchor.ContextMenu.Items.Add(tip); - } else { - var tip = new MenuItem(); - tip.Header = "RECENT INPUT MESSAGES"; - tip.IsEnabled = false; - anchor.ContextMenu.Items.Add(tip); - anchor.ContextMenu.Items.Add(new Separator()); - - foreach (var one in Repo.CommitMsgRecords) { - var dump = one; - - var item = new MenuItem(); - item.Header = dump; - item.Padding = new Thickness(0); - item.Click += (o, ev) => { - txtCommitMsg.Text = dump; - ev.Handled = true; - }; - - anchor.ContextMenu.Items.Add(item); - } - } - - anchor.ContextMenu.IsOpen = true; - e.Handled = true; - } - - private void StartAmend(object sender, RoutedEventArgs e) { - var commits = Repo.Commits("-n 1"); - if (commits.Count == 0) { - App.RaiseError("No commit to amend!"); - chkAmend.IsChecked = false; - return; - } - - txtCommitMsg.Text = commits[0].Subject; - btnCommitAndPush.Visibility = Visibility.Collapsed; - } - - private void EndAmend(object sender, RoutedEventArgs e) { - if (!IsLoaded) return; - - var current = Repo.CurrentBranch(); - if (current != null && !string.IsNullOrEmpty(current.Upstream)) { - btnCommitAndPush.Visibility = Visibility.Visible; - } else { - btnCommitAndPush.Visibility = Visibility.Collapsed; - } - } - - private async void Commit(object sender, RoutedEventArgs e) { - var amend = chkAmend.IsChecked == true; - - Repo.RecordCommitMessage(CommitMessage); - - if (hasConflict) { - App.RaiseError("You have unsolved conflicts in your working copy!"); - return; - } - - if (stageTree.Items.Count == 0) { - App.RaiseError("Nothing to commit!"); - return; - } - - txtCommitMsg.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtCommitMsg)) return; - - bool succ = await Task.Run(() => Repo.DoCommit(CommitMessage, amend)); - if (succ) ClearMessage(); - } - - private async void CommitAndPush(object sender, RoutedEventArgs e) { - var amend = chkAmend.IsChecked == true; - - Repo.RecordCommitMessage(CommitMessage); - - if (hasConflict) { - App.RaiseError("You have unsolved conflicts in your working copy!"); - return; - } - - if (stageTree.Items.Count == 0) { - App.RaiseError("Nothing to commit!"); - return; - } - - txtCommitMsg.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - if (Validation.GetHasError(txtCommitMsg)) return; - - bool succ = await Task.Run(() => Repo.DoCommit(CommitMessage, amend)); - if (!succ) return; - - ClearMessage(); - Push.StartDirectly(Repo); - } - #endregion - - #region MERGE - private async void OpenMergeTool(object sender, RoutedEventArgs e) { - var mergeExe = App.Preference.MergeExecutable; - var mergeParam = Git.MergeTool.Supported[App.Preference.MergeTool].Parameter; - - if (!File.Exists(mergeExe) || mergeParam.IndexOf("$MERGED") < 0) { - App.RaiseError("Invalid merge tool in preference setting!"); - return; - } - - string file = null; - if (App.Preference.UIUseListInUnstaged) { - var selected = unstagedList.SelectedItems; - if (selected.Count <= 0) return; - - var change = selected[0] as Git.Change; - if (change == null) return; - - file = change.Path; - } else { - var selected = Helpers.TreeViewHelper.GetSelectedItems(unstagedTree); - if (selected.Count <= 0) return; - - var node = selected[0].DataContext as Node; - if (node == null || !node.IsFile) return; - - file = node.FilePath; - } - - await Task.Run(() => { - Repo.RunCommand($"-c mergetool.sourcegit.cmd=\"\\\"{mergeExe}\\\" {mergeParam}\" -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit {file}", null); - }); - } - - private async void UseTheirs(object sender, RoutedEventArgs e) { - var files = new List(); - if (App.Preference.UIUseListInUnstaged) { - var selected = unstagedList.SelectedItems; - foreach (var one in selected) { - var node = one as Git.Change; - if (node != null) files.Add(node.Path); - } - } else { - var selected = Helpers.TreeViewHelper.GetSelectedItems(unstagedTree); - foreach (var one in selected) { - var node = one.DataContext as Node; - if (node != null) files.Add(node.FilePath); - } - } - - await Task.Run(() => { - Repo.SetWatcherEnabled(false); - var errs = Repo.RunCommand($"checkout --theirs -- {string.Join(" ", files)}", null); - if (errs != null) { - Repo.SetWatcherEnabled(true); - App.RaiseError("Use theirs failed: " + errs); - return; - } - - Repo.Stage(files.ToArray()); - }); - } - - private async void UseMine(object sender, RoutedEventArgs e) { - var files = new List(); - if (App.Preference.UIUseListInUnstaged) { - var selected = unstagedList.SelectedItems; - foreach (var one in selected) { - var node = one as Git.Change; - if (node != null) files.Add(node.Path); - } - } else { - var selected = Helpers.TreeViewHelper.GetSelectedItems(unstagedTree); - foreach (var one in selected) { - var node = one.DataContext as Node; - if (node != null) files.Add(node.FilePath); - } - } - - await Task.Run(() => { - Repo.SetWatcherEnabled(false); - var errs = Repo.RunCommand($"checkout --ours -- {string.Join(" ", files)}", null); - if (errs != null) { - Repo.SetWatcherEnabled(true); - App.RaiseError("Use mine failed: " + errs); - return; - } - - Repo.Stage(files.ToArray()); - }); - } - #endregion - - #region TREE_COMMON - private void SelectWholeTree(object sender, ExecutedRoutedEventArgs e) { - var tree = sender as TreeView; - if (tree == null) return; - - Helpers.TreeViewHelper.SelectWholeTree(tree); - } - - private void SetData(List changes, bool unstaged) { - List source = new List(); - Dictionary folders = new Dictionary(); - bool isExpendDefault = changes.Count <= 50; - - foreach (var c in changes) { - var subs = c.Path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); - if (subs.Length == 1) { - Node node = new Node(); - node.FilePath = c.Path; - node.IsFile = true; - node.Name = c.Path; - node.Change = c; - source.Add(node); - } else { - Node lastFolder = null; - var folder = ""; - for (int i = 0; i < subs.Length - 1; i++) { - folder += (subs[i] + "/"); - if (folders.ContainsKey(folder)) { - lastFolder = folders[folder]; - } else if (lastFolder == null) { - lastFolder = new Node(); - lastFolder.FilePath = folder; - lastFolder.Name = subs[i]; - lastFolder.IsNodeExpanded = isExpendDefault; - source.Add(lastFolder); - folders.Add(folder, lastFolder); - } else { - var folderNode = new Node(); - folderNode.FilePath = folder; - folderNode.Name = subs[i]; - folderNode.IsNodeExpanded = isExpendDefault; - folders.Add(folder, folderNode); - lastFolder.Children.Add(folderNode); - lastFolder = folderNode; - } - } - - Node node = new Node(); - node.FilePath = c.Path; - node.Name = subs[subs.Length - 1]; - node.IsFile = true; - node.Change = c; - lastFolder.Children.Add(node); - } - } - - folders.Clear(); - SortTreeNodes(source); - - Dispatcher.Invoke(() => { - if (unstaged) { - unstagedList.ItemsSource = changes; - unstagedTree.ItemsSource = source; - } else { - stageList.ItemsSource = changes; - stageTree.ItemsSource = source; - } - }); - } - - private Node FindNodeByPath(List nodes, string filePath) { - foreach (var node in nodes) { - if (node.FilePath == filePath) return node; - var found = FindNodeByPath(node.Children, filePath); - if (found != null) return found; - } - return null; - } - - private void SortTreeNodes(List list) { - list.Sort((l, r) => { - if (l.IsFile) { - return r.IsFile ? l.FilePath.CompareTo(r.FilePath) : 1; - } else { - return r.IsFile ? -1 : l.FilePath.CompareTo(r.FilePath); - } - }); - - foreach (var sub in list) { - if (sub.Children.Count > 0) SortTreeNodes(sub.Children); - } - } - - private ScrollViewer GetScrollViewer(FrameworkElement owner) { - if (owner == null) return null; - if (owner is ScrollViewer) return owner as ScrollViewer; - - int n = VisualTreeHelper.GetChildrenCount(owner); - for (int i = 0; i < n; i++) { - var child = VisualTreeHelper.GetChild(owner, i) as FrameworkElement; - var deep = GetScrollViewer(child); - if (deep != null) return deep; - } - - return null; - } - - private void TreeMouseWheel(object sender, MouseWheelEventArgs e) { - var scroll = GetScrollViewer(sender as TreeView); - if (scroll == null) return; - - if (e.Delta > 0) { - scroll.LineUp(); - } else { - scroll.LineDown(); - } - - e.Handled = true; - } - #endregion - - #region DATAGRID_COMMON - private void SelectWholeDataGrid(object sender, ExecutedRoutedEventArgs e) { - var grid = sender as DataGrid; - if (grid == null) return; - - var source = grid.ItemsSource; - foreach (var item in source) grid.SelectedItems.Add(item); - } - #endregion - } -} diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md new file mode 100644 index 00000000..2338263c --- /dev/null +++ b/THIRD-PARTY-LICENSES.md @@ -0,0 +1,86 @@ +# Third-Party Licenses + +This project incorporates components from the following third parties: + +## Packages + +### AvaloniaUI + +- **Source**: https://github.com/AvaloniaUI/Avalonia +- **Version**: 11.2.5 +- **License**: MIT License +- **License Link**: https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md + +### AvaloniaEdit + +- **Source**: https://github.com/AvaloniaUI/AvaloniaEdit +- **Version**: 11.2.0 +- **License**: MIT License +- **License Link**: https://github.com/AvaloniaUI/AvaloniaEdit/blob/master/LICENSE + +### LiveChartsCore.SkiaSharpView.Avalonia + +- **Source**: https://github.com/beto-rodriguez/LiveCharts2 +- **Version**: 2.0.0-rc5.4 +- **License**: MIT License +- **License Link**: https://github.com/beto-rodriguez/LiveCharts2/blob/master/LICENSE + +### TextMateSharp + +- **Source**: https://github.com/danipen/TextMateSharp +- **Version**: 1.0.66 +- **License**: MIT License +- **License Link**: https://github.com/danipen/TextMateSharp/blob/master/LICENSE.md + +### OpenAI .NET SDK + +- **Source**: https://github.com/openai/openai-dotnet +- **Version**: 2.2.0-beta2 +- **License**: MIT License +- **License Link**: https://github.com/openai/openai-dotnet/blob/main/LICENSE + +### Azure.AI.OpenAI + +- **Source**: https://github.com/Azure/azure-sdk-for-net +- **Version**: 2.2.0-beta2 +- **License**: MIT License +- **License Link**: https://github.com/Azure/azure-sdk-for-net/blob/main/LICENSE.txt + +## Fonts + +### JetBrainsMono + +- **Source**: https://github.com/JetBrains/JetBrainsMono +- **Commit**: v2.304 +- **License**: SIL Open Font License, Version 1.1 +- **License Link**: https://github.com/JetBrains/JetBrainsMono/blob/v2.304/OFL.txt + +## Grammar Files + +### haxe-TmLanguage + +- **Source**: https://github.com/vshaxe/haxe-TmLanguage +- **Commit**: ddad8b4c6d0781ac20be0481174ec1be772c5da5 +- **License**: MIT License +- **License Link**: https://github.com/vshaxe/haxe-TmLanguage/blob/ddad8b4c6d0781ac20be0481174ec1be772c5da5/LICENSE.md + +### coc-toml + +- **Source**: https://github.com/kkiyama117/coc-toml +- **Commit**: aac3e0c65955c03314b2733041b19f903b7cc447 +- **License**: MIT License +- **License Link**: https://github.com/kkiyama117/coc-toml/blob/aac3e0c65955c03314b2733041b19f903b7cc447/LICENSE + +### eclipse-buildship + +- **Source**: https://github.com/eclipse/buildship +- **Commit**: 6bb773e7692f913dec27105129ebe388de34e68b +- **License**: Eclipse Public License 1.0 +- **License Link**: https://github.com/eclipse-buildship/buildship/blob/6bb773e7692f913dec27105129ebe388de34e68b/README.md + +### vscode-jsp-lang + +- **Source**: https://github.com/samuel-weinhardt/vscode-jsp-lang +- **Commit**: 0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355 +- **License**: MIT License +- **License Link**: https://github.com/samuel-weinhardt/vscode-jsp-lang/blob/0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355/LICENSE diff --git a/TRANSLATION.md b/TRANSLATION.md new file mode 100644 index 00000000..051440f0 --- /dev/null +++ b/TRANSLATION.md @@ -0,0 +1,511 @@ +# Translation Status + +This document shows the translation status of each locale file in the repository. + +## Details + +###  + +###  + + +Missing keys in de_DE.axaml + +- 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 + + + +###  + +###  + + +Missing keys in fr_FR.axaml + +- 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 + + + +###  + + +Missing keys in it_IT.axaml + +- 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 + + + +###  + + +Missing keys in ja_JP.axaml + +- 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 + + + +###  + + +Missing keys in pt_BR.axaml + +- 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 + + + +###  + + +Missing keys in ru_RU.axaml + +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream + + + +###  + + +Missing keys in ta_IN.axaml + +- 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 + + + +###  + + +Missing keys in uk_UA.axaml + +- 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 + + + +###  + +###  \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..d3e094ba --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2025.22 \ No newline at end of file diff --git a/build/README.md b/build/README.md new file mode 100644 index 00000000..17305edf --- /dev/null +++ b/build/README.md @@ -0,0 +1,15 @@ +# 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. diff --git a/build/resources/_common/applications/sourcegit.desktop b/build/resources/_common/applications/sourcegit.desktop new file mode 100644 index 00000000..bcf9c813 --- /dev/null +++ b/build/resources/_common/applications/sourcegit.desktop @@ -0,0 +1,9 @@ +[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; diff --git a/build/resources/_common/icons/sourcegit.png b/build/resources/_common/icons/sourcegit.png new file mode 100644 index 00000000..8cdcd3a8 Binary files /dev/null and b/build/resources/_common/icons/sourcegit.png differ diff --git a/build/resources/app/App.icns b/build/resources/app/App.icns new file mode 100644 index 00000000..4dc51b20 Binary files /dev/null and b/build/resources/app/App.icns differ diff --git a/build/resources/app/App.plist b/build/resources/app/App.plist new file mode 100644 index 00000000..ba6f40a2 --- /dev/null +++ b/build/resources/app/App.plist @@ -0,0 +1,26 @@ + + + + + CFBundleIconFile + App.icns + CFBundleIdentifier + com.sourcegit-scm.sourcegit + CFBundleName + SourceGit + CFBundleVersion + SOURCE_GIT_VERSION.0 + LSMinimumSystemVersion + 11.0 + CFBundleExecutable + SourceGit + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + SOURCE_GIT_VERSION + NSHighResolutionCapable + + + diff --git a/build/resources/appimage/sourcegit.appdata.xml b/build/resources/appimage/sourcegit.appdata.xml new file mode 100644 index 00000000..012c82d3 --- /dev/null +++ b/build/resources/appimage/sourcegit.appdata.xml @@ -0,0 +1,16 @@ + + + com.sourcegit_scm.SourceGit + MIT + MIT + SourceGit + Open-source GUI client for git users + + Open-source GUI client for git users + + https://github.com/sourcegit-scm/sourcegit + com.sourcegit_scm.SourceGit.desktop + + com.sourcegit_scm.SourceGit.desktop + + diff --git a/build/resources/appimage/sourcegit.png b/build/resources/appimage/sourcegit.png new file mode 100644 index 00000000..8cdcd3a8 Binary files /dev/null and b/build/resources/appimage/sourcegit.png differ diff --git a/build/resources/deb/DEBIAN/control b/build/resources/deb/DEBIAN/control new file mode 100755 index 00000000..71786b43 --- /dev/null +++ b/build/resources/deb/DEBIAN/control @@ -0,0 +1,8 @@ +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 diff --git a/build/resources/deb/DEBIAN/preinst b/build/resources/deb/DEBIAN/preinst new file mode 100755 index 00000000..a93f8090 --- /dev/null +++ b/build/resources/deb/DEBIAN/preinst @@ -0,0 +1,32 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# for details, see http://www.debian.org/doc/debian-policy/ + +case "$1" in + install|upgrade) + # Check if SourceGit is running and stop it + if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then + echo "Stopping running SourceGit instance..." + pkill -f '/opt/sourcegit/sourcegit' || true + # Give the process a moment to terminate + sleep 1 + fi + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/build/resources/deb/DEBIAN/prerm b/build/resources/deb/DEBIAN/prerm new file mode 100755 index 00000000..c2c9e4f0 --- /dev/null +++ b/build/resources/deb/DEBIAN/prerm @@ -0,0 +1,35 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + remove|upgrade|deconfigure) + if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then + echo "Stopping running SourceGit instance..." + pkill -f '/opt/sourcegit/sourcegit' || true + # Give the process a moment to terminate + sleep 1 + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/build/resources/rpm/SPECS/build.spec b/build/resources/rpm/SPECS/build.spec new file mode 100644 index 00000000..2a684837 --- /dev/null +++ b/build/resources/rpm/SPECS/build.spec @@ -0,0 +1,38 @@ +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 diff --git a/build/scripts/localization-check.js b/build/scripts/localization-check.js new file mode 100644 index 00000000..8d636b5b --- /dev/null +++ b/build/scripts/localization-check.js @@ -0,0 +1,83 @@ +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(' 0) { + const progress = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100; + const badgeColor = progress >= 75 ? 'yellow' : 'red'; + + lines.push(`### }%25-${badgeColor})`); + lines.push(`\nMissing keys in ${file}\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n`) + } else { + lines.push(`### `); + } + } + + const content = lines.join('\n\n'); + console.log(content); + await fs.writeFile(outputFile, content, 'utf8'); +} + +calculateTranslationRate().catch(err => console.error(err)); diff --git a/build/scripts/package.linux.sh b/build/scripts/package.linux.sh new file mode 100755 index 00000000..1b4adbdc --- /dev/null +++ b/build/scripts/package.linux.sh @@ -0,0 +1,70 @@ +#!/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" ./ diff --git a/build/scripts/package.osx-app.sh b/build/scripts/package.osx-app.sh new file mode 100755 index 00000000..2d43e24a --- /dev/null +++ b/build/scripts/package.osx-app.sh @@ -0,0 +1,16 @@ +#!/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 diff --git a/build/scripts/package.windows.sh b/build/scripts/package.windows.sh new file mode 100755 index 00000000..c22a9d35 --- /dev/null +++ b/build/scripts/package.windows.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -e +set -o +set -u +set pipefail + +cd build + +rm -rf SourceGit/*.pdb + +if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then + powershell -Command "Compress-Archive -Path SourceGit -DestinationPath \"sourcegit_$VERSION.$RUNTIME.zip\" -Force" +else + zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit +fi diff --git a/global.json b/global.json new file mode 100644 index 00000000..a27a2b82 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "9.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/screenshots/theme_dark.png b/screenshots/theme_dark.png new file mode 100644 index 00000000..85e18481 Binary files /dev/null and b/screenshots/theme_dark.png differ diff --git a/screenshots/theme_light.png b/screenshots/theme_light.png new file mode 100644 index 00000000..2e8cf6fc Binary files /dev/null and b/screenshots/theme_light.png differ diff --git a/src/App.Commands.cs b/src/App.Commands.cs new file mode 100644 index 00000000..22e9fb51 --- /dev/null +++ b/src/App.Commands.cs @@ -0,0 +1,58 @@ +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 action) + { + _action = action; + } + + public bool CanExecute(object parameter) => _action != null; + public void Execute(object parameter) => _action?.Invoke(parameter); + + private Action _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); + }); + } +} diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs new file mode 100644 index 00000000..9cad0792 --- /dev/null +++ b/src/App.JsonCodeGen.cs @@ -0,0 +1,54 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Avalonia.Controls; +using Avalonia.Media; + +namespace SourceGit +{ + public class ColorConverter : JsonConverter + { + 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 + { + 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 { } +} diff --git a/src/App.axaml b/src/App.axaml new file mode 100644 index 00000000..186022d5 --- /dev/null +++ b/src/App.axaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.axaml.cs b/src/App.axaml.cs new file mode 100644 index 00000000..8e579373 --- /dev/null +++ b/src/App.axaml.cs @@ -0,0 +1,706 @@ +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; + +namespace SourceGit +{ + public partial class App : Application + { + #region App Entry Point + [STAThread] + public static void Main(string[] args) + { + Native.OS.SetupDataDir(); + + AppDomain.CurrentDomain.UnhandledException += (_, e) => + { + LogException(e.ExceptionObject as Exception); + }; + + 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); + } + } + + public static AppBuilder BuildAvaloniaApp() + { + var builder = AppBuilder.Configure(); + 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); + 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._launcher != null) + app._launcher.DispatchNotification(context, message, true); + } + + 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) + { + var app = Current as App; + if (app == null) + return; + + var targetLocale = app.Resources[localeKey] as ResourceDictionary; + if (targetLocale == null || targetLocale == app._activeLocale) + return; + + 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 SetFonts(string defaultFont, string monospaceFont, bool onlyUseMonospaceFontInEditor) + { + var app = Current as App; + if (app == null) + return; + + if (app._fontsOverrides != null) + { + app.Resources.MergedDictionaries.Remove(app._fontsOverrides); + app._fontsOverrides = null; + } + + defaultFont = app.FixFontFamilyName(defaultFont); + monospaceFont = app.FixFontFamilyName(monospaceFont); + + var resDic = new ResourceDictionary(); + if (!string.IsNullOrEmpty(defaultFont)) + resDic.Add("Fonts.Default", new FontFamily(defaultFont)); + + if (string.IsNullOrEmpty(monospaceFont)) + { + if (!string.IsNullOrEmpty(defaultFont)) + { + monospaceFont = $"fonts:SourceGit#JetBrains Mono,{defaultFont}"; + resDic.Add("Fonts.Monospace", new FontFamily(monospaceFont)); + } + } + else + { + if (!string.IsNullOrEmpty(defaultFont) && !monospaceFont.Contains(defaultFont, StringComparison.Ordinal)) + monospaceFont = $"{monospaceFont},{defaultFont}"; + + resDic.Add("Fonts.Monospace", new FontFamily(monospaceFont)); + } + + if (onlyUseMonospaceFontInEditor) + { + if (string.IsNullOrEmpty(defaultFont)) + resDic.Add("Fonts.Primary", new FontFamily("fonts:Inter#Inter")); + else + resDic.Add("Fonts.Primary", new FontFamily(defaultFont)); + } + else + { + if (!string.IsNullOrEmpty(monospaceFont)) + resDic.Add("Fonts.Primary", new FontFamily(monospaceFont)); + } + + if (resDic.Count > 0) + { + app.Resources.MergedDictionaries.Add(resDic); + app._fontsOverrides = resDic; + } + } + + public static async void CopyText(string data) + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (desktop.MainWindow?.Clipboard is { } clipboard) + await clipboard.SetTextAsync(data ?? ""); + } + } + + public static async Task 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; + + 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; + + 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); + } + } + #endregion + + #region Overrides + 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); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + BindingPlugins.DataValidators.RemoveAt(0); + + // Disable tooltip if window is not active. + ToolTip.ToolTipOpeningEvent.AddClassHandler((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(); + 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; + } + } + + File.WriteAllLines(file, lines); + + exitCode = 0; + return true; + } + + 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(); + + 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; + } +} diff --git a/src/App.ico b/src/App.ico new file mode 100644 index 00000000..fb537a6a Binary files /dev/null and b/src/App.ico differ diff --git a/src/App.manifest b/src/App.manifest new file mode 100644 index 00000000..11a2ff11 --- /dev/null +++ b/src/App.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/Commands/Add.cs b/src/Commands/Add.cs new file mode 100644 index 00000000..210eb4b2 --- /dev/null +++ b/src/Commands/Add.cs @@ -0,0 +1,26 @@ +namespace SourceGit.Commands +{ + public class Add : Command + { + public Add(string repo, bool includeUntracked) + { + WorkingDirectory = repo; + Context = repo; + Args = includeUntracked ? "add ." : "add -u ."; + } + + public Add(string repo, Models.Change change) + { + WorkingDirectory = repo; + Context = repo; + Args = $"add -- \"{change.Path}\""; + } + + public Add(string repo, string pathspecFromFile) + { + WorkingDirectory = repo; + Context = repo; + Args = $"add --pathspec-from-file=\"{pathspecFromFile}\""; + } + } +} diff --git a/src/Commands/Apply.cs b/src/Commands/Apply.cs new file mode 100644 index 00000000..d1c9ffbc --- /dev/null +++ b/src/Commands/Apply.cs @@ -0,0 +1,19 @@ +namespace SourceGit.Commands +{ + public class Apply : Command + { + public Apply(string repo, string file, bool ignoreWhitespace, string whitespaceMode, string extra) + { + WorkingDirectory = repo; + Context = repo; + Args = "apply "; + if (ignoreWhitespace) + Args += "--ignore-whitespace "; + else + Args += $"--whitespace={whitespaceMode} "; + if (!string.IsNullOrEmpty(extra)) + Args += $"{extra} "; + Args += $"\"{file}\""; + } + } +} diff --git a/src/Commands/Archive.cs b/src/Commands/Archive.cs new file mode 100644 index 00000000..5e0919f7 --- /dev/null +++ b/src/Commands/Archive.cs @@ -0,0 +1,12 @@ +namespace SourceGit.Commands +{ + public class Archive : Command + { + public Archive(string repo, string revision, string saveTo) + { + WorkingDirectory = repo; + Context = repo; + Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}"; + } + } +} diff --git a/src/Commands/AssumeUnchanged.cs b/src/Commands/AssumeUnchanged.cs new file mode 100644 index 00000000..28f78280 --- /dev/null +++ b/src/Commands/AssumeUnchanged.cs @@ -0,0 +1,14 @@ +namespace SourceGit.Commands +{ + public class AssumeUnchanged : Command + { + public AssumeUnchanged(string repo, string file, bool bAdd) + { + var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged"; + + WorkingDirectory = repo; + Context = repo; + Args = $"update-index {mode} -- \"{file}\""; + } + } +} diff --git a/src/Commands/Bisect.cs b/src/Commands/Bisect.cs new file mode 100644 index 00000000..a3bf1a97 --- /dev/null +++ b/src/Commands/Bisect.cs @@ -0,0 +1,13 @@ +namespace SourceGit.Commands +{ + public class Bisect : Command + { + public Bisect(string repo, string subcmd) + { + WorkingDirectory = repo; + Context = repo; + RaiseError = false; + Args = $"bisect {subcmd}"; + } + } +} diff --git a/src/Commands/Blame.cs b/src/Commands/Blame.cs new file mode 100644 index 00000000..1fc51fa4 --- /dev/null +++ b/src/Commands/Blame.cs @@ -0,0 +1,97 @@ +using System; +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(); + + public Blame(string repo, string file, string revision) + { + WorkingDirectory = repo; + Context = repo; + Args = $"blame -t {revision} -- \"{file}\""; + RaiseError = false; + + _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; + } + + if (_needUnifyCommitSHA) + { + foreach (var line in _result.LineInfos) + { + if (line.CommitSHA.Length > _minSHALen) + { + line.CommitSHA = line.CommitSHA.Substring(0, _minSHALen); + } + } + } + + _result.Content = _content.ToString(); + return _result; + } + + private void ParseLine(string line) + { + if (line.Contains('\0', StringComparison.Ordinal)) + { + _result.IsBinary = true; + _result.LineInfos.Clear(); + 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); + + var info = new Models.BlameLineInfo() + { + IsFirstInGroup = commit != _lastSHA, + CommitSHA = commit, + Author = author, + Time = when, + }; + + _result.LineInfos.Add(info); + _lastSHA = commit; + + 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 string _lastSHA = string.Empty; + private bool _needUnifyCommitSHA = false; + private int _minSHALen = 64; + } +} diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs new file mode 100644 index 00000000..0d1b1f8f --- /dev/null +++ b/src/Commands/Branch.cs @@ -0,0 +1,83 @@ +using System.Text; + +namespace SourceGit.Commands +{ + public static class Branch + { + public static string ShowCurrent(string repo) + { + 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; + return cmd.Exec(); + } + + public static bool Rename(string repo, string name, string to, Models.ICommandLog log) + { + 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) + { + var cmd = new Command(); + cmd.WorkingDirectory = repo; + cmd.Context = repo; + cmd.Log = log; + + if (string.IsNullOrEmpty(upstream)) + cmd.Args = $"branch {name} --unset-upstream"; + else + cmd.Args = $"branch {name} -u {upstream}"; + + return cmd.Exec(); + } + + public static bool DeleteLocal(string repo, string name, Models.ICommandLog log) + { + 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(); + } + } +} diff --git a/src/Commands/Checkout.cs b/src/Commands/Checkout.cs new file mode 100644 index 00000000..d2876740 --- /dev/null +++ b/src/Commands/Checkout.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Text; + +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(); + 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(); + return Exec(); + } + + public bool Commit(string commitId, bool force) + { + var option = force ? "--force" : string.Empty; + Args = $"checkout {option} --detach --progress {commitId}"; + return Exec(); + } + + public bool UseTheirs(List files) + { + var builder = new StringBuilder(); + builder.Append("checkout --theirs --"); + foreach (var f in files) + { + builder.Append(" \""); + builder.Append(f); + builder.Append("\""); + } + Args = builder.ToString(); + return Exec(); + } + + public bool UseMine(List 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(); + } + + public bool FileWithRevision(string file, string revision) + { + Args = $"checkout --no-overlay {revision} -- \"{file}\""; + return Exec(); + } + } +} diff --git a/src/Commands/CherryPick.cs b/src/Commands/CherryPick.cs new file mode 100644 index 00000000..0c82b9fd --- /dev/null +++ b/src/Commands/CherryPick.cs @@ -0,0 +1,20 @@ +namespace SourceGit.Commands +{ + public class CherryPick : Command + { + public CherryPick(string repo, string commits, bool noCommit, bool appendSourceToMessage, string extraParams) + { + WorkingDirectory = repo; + Context = repo; + + Args = "cherry-pick "; + if (noCommit) + Args += "-n "; + if (appendSourceToMessage) + Args += "-x "; + if (!string.IsNullOrEmpty(extraParams)) + Args += $"{extraParams} "; + Args += commits; + } + } +} diff --git a/src/Commands/Clean.cs b/src/Commands/Clean.cs new file mode 100644 index 00000000..6ed74999 --- /dev/null +++ b/src/Commands/Clean.cs @@ -0,0 +1,12 @@ +namespace SourceGit.Commands +{ + public class Clean : Command + { + public Clean(string repo) + { + WorkingDirectory = repo; + Context = repo; + Args = "clean -qfdx"; + } + } +} diff --git a/src/Commands/Clone.cs b/src/Commands/Clone.cs new file mode 100644 index 00000000..efec264b --- /dev/null +++ b/src/Commands/Clone.cs @@ -0,0 +1,21 @@ +namespace SourceGit.Commands +{ + public class Clone : Command + { + public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs) + { + Context = ctx; + WorkingDirectory = path; + SSHKey = sshKey; + Args = "clone --progress --verbose "; + + if (!string.IsNullOrEmpty(extraArgs)) + Args += $"{extraArgs} "; + + Args += $"{url} "; + + if (!string.IsNullOrEmpty(localName)) + Args += localName; + } + } +} diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs new file mode 100644 index 00000000..975922fc --- /dev/null +++ b/src/Commands/Command.cs @@ -0,0 +1,220 @@ +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(); + 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 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(); + } +} diff --git a/src/Commands/Commit.cs b/src/Commands/Commit.cs new file mode 100644 index 00000000..1585e7e3 --- /dev/null +++ b/src/Commands/Commit.cs @@ -0,0 +1,39 @@ +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); + + 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"; + } + + public bool Run() + { + var succ = Exec(); + + try + { + File.Delete(_tmpFile); + } + catch + { + // Ignore + } + + return succ; + } + + private readonly string _tmpFile; + } +} diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs new file mode 100644 index 00000000..c88e087a --- /dev/null +++ b/src/Commands/CompareRevisions.cs @@ -0,0 +1,88 @@ +using System; +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(); + + 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}"; + } + + public CompareRevisions(string repo, string start, string end, string path) + { + WorkingDirectory = repo; + Context = repo; + + var based = string.IsNullOrEmpty(start) ? "-R" : start; + Args = $"diff --name-status {based} {end} -- \"{path}\""; + } + + public List Result() + { + 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)); + 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; + } + + 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; + } + } + + private readonly List _changes = new List(); + } +} diff --git a/src/Commands/Config.cs b/src/Commands/Config.cs new file mode 100644 index 00000000..49e8fcb7 --- /dev/null +++ b/src/Commands/Config.cs @@ -0,0 +1,68 @@ +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; + } + + RaiseError = false; + } + + public Dictionary
Open-source GUI client for git users