mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-23 11:25:00 +00:00
Compare commits
No commits in common. "master" and "v1.5" have entirely different histories.
763 changed files with 18508 additions and 73369 deletions
306
.editorconfig
306
.editorconfig
|
@ -1,306 +0,0 @@
|
||||||
# editorconfig.org
|
|
||||||
|
|
||||||
# top-most EditorConfig file
|
|
||||||
root = true
|
|
||||||
|
|
||||||
# Default settings:
|
|
||||||
# A newline ending every file
|
|
||||||
# Use 4 spaces as indentation
|
|
||||||
[*]
|
|
||||||
insert_final_newline = true
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
|
||||||
tab_width = 4
|
|
||||||
end_of_line = crlf
|
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
|
||||||
dotnet_style_null_propagation = true:suggestion
|
|
||||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
|
||||||
dotnet_style_prefer_auto_properties = true:silent
|
|
||||||
dotnet_style_object_initializer = true:suggestion
|
|
||||||
dotnet_style_prefer_collection_expression = true:suggestion
|
|
||||||
dotnet_style_collection_initializer = true:suggestion
|
|
||||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
|
||||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
|
||||||
dotnet_style_explicit_tuple_names = true:suggestion
|
|
||||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
|
||||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
|
||||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
|
||||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
|
||||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
|
||||||
dotnet_style_namespace_match_folder = true:suggestion
|
|
||||||
dotnet_style_readonly_field = true:suggestion
|
|
||||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
|
||||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
|
||||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
|
||||||
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
|
|
||||||
|
|
||||||
# C# files
|
|
||||||
[*.cs]
|
|
||||||
# New line preferences
|
|
||||||
csharp_new_line_before_open_brace = all
|
|
||||||
csharp_new_line_before_else = true
|
|
||||||
csharp_new_line_before_catch = true
|
|
||||||
csharp_new_line_before_finally = true
|
|
||||||
csharp_new_line_before_members_in_object_initializers = true
|
|
||||||
csharp_new_line_before_members_in_anonymous_types = true
|
|
||||||
csharp_new_line_between_query_expression_clauses = true
|
|
||||||
# trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
# Indentation preferences
|
|
||||||
csharp_indent_block_contents = true
|
|
||||||
csharp_indent_braces = false
|
|
||||||
csharp_indent_case_contents = true
|
|
||||||
csharp_indent_switch_labels = true
|
|
||||||
csharp_indent_labels = one_less_than_current
|
|
||||||
|
|
||||||
# avoid this. unless absolutely necessary
|
|
||||||
dotnet_style_qualification_for_field = false:suggestion
|
|
||||||
dotnet_style_qualification_for_property = false:suggestion
|
|
||||||
dotnet_style_qualification_for_method = false:suggestion
|
|
||||||
dotnet_style_qualification_for_event = false:suggestion
|
|
||||||
|
|
||||||
# prefer var
|
|
||||||
csharp_style_var_for_built_in_types = true
|
|
||||||
csharp_style_var_when_type_is_apparent = true
|
|
||||||
csharp_style_var_elsewhere = true:suggestion
|
|
||||||
|
|
||||||
# use language keywords instead of BCL types
|
|
||||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
|
||||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
|
||||||
|
|
||||||
# name all constant fields using PascalCase
|
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
|
||||||
|
|
||||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
|
||||||
|
|
||||||
# private static fields should have s_ prefix
|
|
||||||
dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
|
|
||||||
dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
|
|
||||||
dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
|
||||||
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
|
|
||||||
|
|
||||||
dotnet_naming_style.private_static_prefix_style.required_prefix = s_
|
|
||||||
dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
|
|
||||||
|
|
||||||
# internal and private fields should be _camelCase
|
|
||||||
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
|
|
||||||
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
|
|
||||||
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
|
|
||||||
|
|
||||||
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
|
|
||||||
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
|
|
||||||
|
|
||||||
# use accessibility modifiers
|
|
||||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
|
||||||
|
|
||||||
# Code style defaults
|
|
||||||
dotnet_sort_system_directives_first = true
|
|
||||||
csharp_preserve_single_line_blocks = true
|
|
||||||
csharp_preserve_single_line_statements = false
|
|
||||||
|
|
||||||
# Expression-level preferences
|
|
||||||
dotnet_style_object_initializer = true:suggestion
|
|
||||||
dotnet_style_collection_initializer = true:suggestion
|
|
||||||
dotnet_style_explicit_tuple_names = true:suggestion
|
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
|
||||||
dotnet_style_null_propagation = true:suggestion
|
|
||||||
|
|
||||||
# Expression-bodied members
|
|
||||||
csharp_style_expression_bodied_methods = false:none
|
|
||||||
csharp_style_expression_bodied_constructors = false:none
|
|
||||||
csharp_style_expression_bodied_operators = false:none
|
|
||||||
csharp_style_expression_bodied_properties = true:none
|
|
||||||
csharp_style_expression_bodied_indexers = true:none
|
|
||||||
csharp_style_expression_bodied_accessors = true:none
|
|
||||||
|
|
||||||
# Pattern matching
|
|
||||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
|
||||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
|
||||||
csharp_style_inlined_variable_declaration = true:suggestion
|
|
||||||
|
|
||||||
# Null checking preferences
|
|
||||||
csharp_style_throw_expression = true:suggestion
|
|
||||||
csharp_style_conditional_delegate_call = true:suggestion
|
|
||||||
|
|
||||||
# Space preferences
|
|
||||||
csharp_space_after_cast = false
|
|
||||||
csharp_space_after_colon_in_inheritance_clause = true
|
|
||||||
csharp_space_after_comma = true
|
|
||||||
csharp_space_after_dot = false
|
|
||||||
csharp_space_after_keywords_in_control_flow_statements = true
|
|
||||||
csharp_space_after_semicolon_in_for_statement = true
|
|
||||||
csharp_space_around_binary_operators = before_and_after
|
|
||||||
csharp_space_around_declaration_statements = false
|
|
||||||
csharp_space_before_colon_in_inheritance_clause = true
|
|
||||||
csharp_space_before_comma = false
|
|
||||||
csharp_space_before_dot = false
|
|
||||||
csharp_space_before_open_square_brackets = false
|
|
||||||
csharp_space_before_semicolon_in_for_statement = false
|
|
||||||
csharp_space_between_empty_square_brackets = false
|
|
||||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
|
||||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
|
||||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
|
||||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
|
||||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
|
||||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
|
||||||
csharp_space_between_parentheses = false
|
|
||||||
csharp_space_between_square_brackets = false
|
|
||||||
space_within_single_line_array_initializer_braces = true
|
|
||||||
|
|
||||||
#Net Analyzer
|
|
||||||
dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
|
|
||||||
|
|
||||||
# CS0649: Field 'field' is never assigned to, and will always have its default value 'value'
|
|
||||||
dotnet_diagnostic.CS0649.severity = error
|
|
||||||
|
|
||||||
# CS1591: Missing XML comment for publicly visible type or member
|
|
||||||
dotnet_diagnostic.CS1591.severity = suggestion
|
|
||||||
|
|
||||||
# CS0162: Remove unreachable code
|
|
||||||
dotnet_diagnostic.CS0162.severity = error
|
|
||||||
# CA1018: Mark attributes with AttributeUsageAttribute
|
|
||||||
dotnet_diagnostic.CA1018.severity = error
|
|
||||||
# CA1304: Specify CultureInfo
|
|
||||||
dotnet_diagnostic.CA1304.severity = warning
|
|
||||||
# CA1802: Use literals where appropriate
|
|
||||||
dotnet_diagnostic.CA1802.severity = warning
|
|
||||||
# CA1813: Avoid unsealed attributes
|
|
||||||
dotnet_diagnostic.CA1813.severity = error
|
|
||||||
# CA1815: Override equals and operator equals on value types
|
|
||||||
dotnet_diagnostic.CA1815.severity = warning
|
|
||||||
# CA1820: Test for empty strings using string length
|
|
||||||
dotnet_diagnostic.CA1820.severity = warning
|
|
||||||
# CA1821: Remove empty finalizers
|
|
||||||
dotnet_diagnostic.CA1821.severity = warning
|
|
||||||
# CA1822: Mark members as static
|
|
||||||
dotnet_diagnostic.CA1822.severity = suggestion
|
|
||||||
# CA1823: Avoid unused private fields
|
|
||||||
dotnet_diagnostic.CA1823.severity = warning
|
|
||||||
dotnet_code_quality.CA1822.api_surface = private, internal
|
|
||||||
# CA1825: Avoid zero-length array allocations
|
|
||||||
dotnet_diagnostic.CA1825.severity = warning
|
|
||||||
# CA1826: Use property instead of Linq Enumerable method
|
|
||||||
dotnet_diagnostic.CA1826.severity = suggestion
|
|
||||||
# CA1827: Do not use Count/LongCount when Any can be used
|
|
||||||
dotnet_diagnostic.CA1827.severity = warning
|
|
||||||
# CA1828: Do not use CountAsync/LongCountAsync when AnyAsync can be used
|
|
||||||
dotnet_diagnostic.CA1828.severity = warning
|
|
||||||
# CA1829: Use Length/Count property instead of Enumerable.Count method
|
|
||||||
dotnet_diagnostic.CA1829.severity = warning
|
|
||||||
#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
|
|
||||||
dotnet_diagnostic.CA1847.severity = warning
|
|
||||||
#CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method
|
|
||||||
dotnet_diagnostic.CA1854.severity = warning
|
|
||||||
#CA2211:Non-constant fields should not be visible
|
|
||||||
dotnet_diagnostic.CA2211.severity = error
|
|
||||||
|
|
||||||
# IDE0005: remove used namespace using
|
|
||||||
dotnet_diagnostic.IDE0005.severity = error
|
|
||||||
|
|
||||||
# Wrapping preferences
|
|
||||||
csharp_wrap_before_ternary_opsigns = false
|
|
||||||
|
|
||||||
# Avalonia DevAnalyzer preferences
|
|
||||||
dotnet_diagnostic.AVADEV2001.severity = error
|
|
||||||
|
|
||||||
# Avalonia PublicAnalyzer preferences
|
|
||||||
dotnet_diagnostic.AVP1000.severity = error
|
|
||||||
dotnet_diagnostic.AVP1001.severity = error
|
|
||||||
dotnet_diagnostic.AVP1002.severity = error
|
|
||||||
dotnet_diagnostic.AVP1010.severity = error
|
|
||||||
dotnet_diagnostic.AVP1011.severity = error
|
|
||||||
dotnet_diagnostic.AVP1012.severity = warning
|
|
||||||
dotnet_diagnostic.AVP1013.severity = error
|
|
||||||
dotnet_diagnostic.AVP1020.severity = error
|
|
||||||
dotnet_diagnostic.AVP1021.severity = error
|
|
||||||
dotnet_diagnostic.AVP1022.severity = error
|
|
||||||
dotnet_diagnostic.AVP1030.severity = error
|
|
||||||
dotnet_diagnostic.AVP1031.severity = error
|
|
||||||
dotnet_diagnostic.AVP1032.severity = error
|
|
||||||
dotnet_diagnostic.AVP1040.severity = error
|
|
||||||
dotnet_diagnostic.AVA2001.severity = error
|
|
||||||
csharp_using_directive_placement = outside_namespace:silent
|
|
||||||
csharp_prefer_simple_using_statement = true:suggestion
|
|
||||||
csharp_prefer_braces = true:silent
|
|
||||||
csharp_style_namespace_declarations = block_scoped:silent
|
|
||||||
csharp_style_prefer_method_group_conversion = true:silent
|
|
||||||
csharp_style_prefer_top_level_statements = true:silent
|
|
||||||
csharp_style_prefer_primary_constructors = true:suggestion
|
|
||||||
csharp_style_expression_bodied_lambdas = true:silent
|
|
||||||
csharp_style_expression_bodied_local_functions = false:silent
|
|
||||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
|
||||||
csharp_prefer_simple_default_expression = true:suggestion
|
|
||||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
|
||||||
csharp_style_prefer_index_operator = true:suggestion
|
|
||||||
csharp_style_prefer_range_operator = true:suggestion
|
|
||||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
|
||||||
csharp_style_prefer_tuple_swap = true:suggestion
|
|
||||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
|
||||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
|
||||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
|
||||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
|
||||||
csharp_style_prefer_readonly_struct = true:suggestion
|
|
||||||
csharp_prefer_static_local_function = true:suggestion
|
|
||||||
csharp_style_prefer_readonly_struct_member = true:suggestion
|
|
||||||
|
|
||||||
# Xaml files
|
|
||||||
[*.{xaml,axaml}]
|
|
||||||
indent_size = 2
|
|
||||||
# DuplicateSetterError
|
|
||||||
avalonia_xaml_diagnostic.AVLN2203.severity = error
|
|
||||||
# StyleInMergedDictionaries
|
|
||||||
avalonia_xaml_diagnostic.AVLN2204.severity = error
|
|
||||||
# RequiredTemplatePartMissing
|
|
||||||
avalonia_xaml_diagnostic.AVLN2205.severity = error
|
|
||||||
# OptionalTemplatePartMissing
|
|
||||||
avalonia_xaml_diagnostic.AVLN2206.severity = info
|
|
||||||
# TemplatePartWrongType
|
|
||||||
avalonia_xaml_diagnostic.AVLN2207.severity = error
|
|
||||||
# Obsolete
|
|
||||||
avalonia_xaml_diagnostic.AVLN5001.severity = error
|
|
||||||
|
|
||||||
# Xml project files
|
|
||||||
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# Xml build files
|
|
||||||
[*.builds]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# Xml files
|
|
||||||
[*.{xml,stylecop,resx,ruleset}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# Xml config files
|
|
||||||
[*.{props,targets,config,nuspec}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
[*.json]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# Shell scripts
|
|
||||||
[*.sh]
|
|
||||||
end_of_line = lf
|
|
||||||
[*.{cmd,bat}]
|
|
||||||
end_of_line = crlf
|
|
||||||
|
|
||||||
# Package manifests
|
|
||||||
[{*.spec,control}]
|
|
||||||
end_of_line = lf
|
|
||||||
|
|
||||||
# YAML files
|
|
||||||
[*.{yml,yaml}]
|
|
||||||
indent_size = 2
|
|
||||||
end_of_line = lf
|
|
14
.gitattributes
vendored
14
.gitattributes
vendored
|
@ -1,14 +0,0 @@
|
||||||
* text=auto
|
|
||||||
*.md text
|
|
||||||
*.png binary
|
|
||||||
*.ico binary
|
|
||||||
*.sh text eol=lf
|
|
||||||
*.spec text eol=lf
|
|
||||||
control text eol=lf
|
|
||||||
*.bat text eol=crlf
|
|
||||||
*.cmd text eol=crlf
|
|
||||||
*.ps1 text eol=crlf
|
|
||||||
*.json text
|
|
||||||
|
|
||||||
.gitattributes export-ignore
|
|
||||||
.gitignore export-ignore
|
|
79
.github/workflows/build.yml
vendored
79
.github/workflows/build.yml
vendored
|
@ -1,79 +0,0 @@
|
||||||
name: Build
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- name : Windows x64
|
|
||||||
os: windows-2019
|
|
||||||
runtime: win-x64
|
|
||||||
- name : Windows ARM64
|
|
||||||
os: windows-2019
|
|
||||||
runtime: win-arm64
|
|
||||||
- name : macOS (Intel)
|
|
||||||
os: macos-13
|
|
||||||
runtime: osx-x64
|
|
||||||
- name : macOS (Apple Silicon)
|
|
||||||
os: macos-latest
|
|
||||||
runtime: osx-arm64
|
|
||||||
- name : Linux
|
|
||||||
os: ubuntu-latest
|
|
||||||
runtime: linux-x64
|
|
||||||
container: ubuntu:20.04
|
|
||||||
- name : Linux (arm64)
|
|
||||||
os: ubuntu-latest
|
|
||||||
runtime: linux-arm64
|
|
||||||
container: ubuntu:20.04
|
|
||||||
name: Build ${{ matrix.name }}
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
container: ${{ matrix.container || '' }}
|
|
||||||
steps:
|
|
||||||
- name: Install common CLI tools
|
|
||||||
if: ${{ startsWith(matrix.runtime, 'linux-') }}
|
|
||||||
run: |
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y sudo
|
|
||||||
sudo apt-get install -y curl wget git unzip zip libicu66 tzdata clang
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Setup .NET
|
|
||||||
uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
dotnet-version: 9.0.x
|
|
||||||
- name: Configure arm64 packages
|
|
||||||
if: ${{ matrix.runtime == 'linux-arm64' }}
|
|
||||||
run: |
|
|
||||||
sudo dpkg --add-architecture arm64
|
|
||||||
echo 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main restricted
|
|
||||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted
|
|
||||||
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted' \
|
|
||||||
| sudo tee /etc/apt/sources.list.d/arm64.list
|
|
||||||
sudo sed -i -e 's/^deb http/deb [arch=amd64] http/g' /etc/apt/sources.list
|
|
||||||
sudo sed -i -e 's/^deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list
|
|
||||||
- name: Install cross-compiling dependencies
|
|
||||||
if: ${{ matrix.runtime == 'linux-arm64' }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y llvm gcc-aarch64-linux-gnu
|
|
||||||
- name: Build
|
|
||||||
run: dotnet build -c Release
|
|
||||||
- name: Publish
|
|
||||||
run: dotnet publish src/SourceGit.csproj -c Release -o publish -r ${{ matrix.runtime }}
|
|
||||||
- name: Rename executable file
|
|
||||||
if: ${{ startsWith(matrix.runtime, 'linux-') }}
|
|
||||||
run: mv publish/SourceGit publish/sourcegit
|
|
||||||
- name: Tar artifact
|
|
||||||
if: ${{ startsWith(matrix.runtime, 'linux-') || startsWith(matrix.runtime, 'osx-') }}
|
|
||||||
run: |
|
|
||||||
tar -cvf "sourcegit.${{ matrix.runtime }}.tar" -C publish .
|
|
||||||
rm -r publish/*
|
|
||||||
mv "sourcegit.${{ matrix.runtime }}.tar" publish
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: sourcegit.${{ matrix.runtime }}
|
|
||||||
path: publish/*
|
|
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
|
@ -1,29 +0,0 @@
|
||||||
name: Continuous Integration
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [develop]
|
|
||||||
pull_request:
|
|
||||||
branches: [develop]
|
|
||||||
workflow_dispatch:
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
uses: ./.github/workflows/build.yml
|
|
||||||
version:
|
|
||||||
name: Prepare version string
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.version.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Output version string
|
|
||||||
id: version
|
|
||||||
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
|
|
||||||
package:
|
|
||||||
needs: [build, version]
|
|
||||||
name: Package
|
|
||||||
uses: ./.github/workflows/package.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.version }}
|
|
41
.github/workflows/localization-check.yml
vendored
41
.github/workflows/localization-check.yml
vendored
|
@ -1,41 +0,0 @@
|
||||||
name: Localization Check
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ develop ]
|
|
||||||
paths:
|
|
||||||
- 'src/Resources/Locales/**'
|
|
||||||
workflow_dispatch:
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
localization-check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20.x'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm install fs-extra@11.2.0 path@0.12.7 xml2js@0.6.2
|
|
||||||
|
|
||||||
- name: Run localization check
|
|
||||||
run: node build/scripts/localization-check.js
|
|
||||||
|
|
||||||
- name: Commit changes
|
|
||||||
run: |
|
|
||||||
git config --global user.name 'github-actions[bot]'
|
|
||||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
|
||||||
git add TRANSLATION.md src/Resources/Locales/*.axaml
|
|
||||||
git commit -m 'doc: Update translation status and sort locale files'
|
|
||||||
git push
|
|
||||||
else
|
|
||||||
echo "No changes to commit"
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
111
.github/workflows/package.yml
vendored
111
.github/workflows/package.yml
vendored
|
@ -1,111 +0,0 @@
|
||||||
name: Package
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: SourceGit package version
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
jobs:
|
|
||||||
windows:
|
|
||||||
name: Package Windows
|
|
||||||
runs-on: windows-2019
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
runtime: [ win-x64, win-arm64 ]
|
|
||||||
steps:
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Download build
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: sourcegit.${{ matrix.runtime }}
|
|
||||||
path: build/SourceGit
|
|
||||||
- name: Package
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
VERSION: ${{ inputs.version }}
|
|
||||||
RUNTIME: ${{ matrix.runtime }}
|
|
||||||
run: ./build/scripts/package.windows.sh
|
|
||||||
- name: Upload package artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: package.${{ matrix.runtime }}
|
|
||||||
path: build/sourcegit_*.zip
|
|
||||||
- name: Delete temp artifacts
|
|
||||||
uses: geekyeggo/delete-artifact@v5
|
|
||||||
with:
|
|
||||||
name: sourcegit.${{ matrix.runtime }}
|
|
||||||
osx-app:
|
|
||||||
name: Package macOS
|
|
||||||
runs-on: macos-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
runtime: [osx-x64, osx-arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Download build
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: sourcegit.${{ matrix.runtime }}
|
|
||||||
path: build
|
|
||||||
- name: Package
|
|
||||||
env:
|
|
||||||
VERSION: ${{ inputs.version }}
|
|
||||||
RUNTIME: ${{ matrix.runtime }}
|
|
||||||
run: |
|
|
||||||
mkdir build/SourceGit
|
|
||||||
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit
|
|
||||||
./build/scripts/package.osx-app.sh
|
|
||||||
- name: Upload package artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: package.${{ matrix.runtime }}
|
|
||||||
path: build/sourcegit_*.zip
|
|
||||||
- name: Delete temp artifacts
|
|
||||||
uses: geekyeggo/delete-artifact@v5
|
|
||||||
with:
|
|
||||||
name: sourcegit.${{ matrix.runtime }}
|
|
||||||
linux:
|
|
||||||
name: Package Linux
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: ubuntu:20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
runtime: [linux-x64, linux-arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Download package dependencies
|
|
||||||
run: |
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y curl wget git dpkg-dev fakeroot tzdata zip unzip desktop-file-utils rpm libfuse2 file build-essential binutils
|
|
||||||
- name: Download build
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: sourcegit.${{ matrix.runtime }}
|
|
||||||
path: build
|
|
||||||
- name: Package
|
|
||||||
env:
|
|
||||||
VERSION: ${{ inputs.version }}
|
|
||||||
RUNTIME: ${{ matrix.runtime }}
|
|
||||||
APPIMAGE_EXTRACT_AND_RUN: 1
|
|
||||||
run: |
|
|
||||||
mkdir build/SourceGit
|
|
||||||
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit
|
|
||||||
./build/scripts/package.linux.sh
|
|
||||||
- name: Upload package artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: package.${{ matrix.runtime }}
|
|
||||||
path: |
|
|
||||||
build/sourcegit-*.AppImage
|
|
||||||
build/sourcegit_*.deb
|
|
||||||
build/sourcegit-*.rpm
|
|
||||||
- name: Delete temp artifacts
|
|
||||||
uses: geekyeggo/delete-artifact@v5
|
|
||||||
with:
|
|
||||||
name: sourcegit.${{ matrix.runtime }}
|
|
52
.github/workflows/release.yml
vendored
52
.github/workflows/release.yml
vendored
|
@ -1,52 +0,0 @@
|
||||||
name: Release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
uses: ./.github/workflows/build.yml
|
|
||||||
version:
|
|
||||||
name: Prepare version string
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.version.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Output version string
|
|
||||||
id: version
|
|
||||||
env:
|
|
||||||
TAG: ${{ github.ref_name }}
|
|
||||||
run: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
|
|
||||||
package:
|
|
||||||
needs: [build, version]
|
|
||||||
name: Package
|
|
||||||
uses: ./.github/workflows/package.yml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.version.outputs.version }}
|
|
||||||
release:
|
|
||||||
needs: [package, version]
|
|
||||||
name: Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Create release
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
TAG: ${{ github.ref_name }}
|
|
||||||
VERSION: ${{ needs.version.outputs.version }}
|
|
||||||
run: gh release create "$TAG" -t "$VERSION" --notes-from-tag
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
pattern: package.*
|
|
||||||
path: packages
|
|
||||||
merge-multiple: true
|
|
||||||
- name: Upload assets
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
TAG: ${{ github.ref_name }}
|
|
||||||
run: gh release upload "$TAG" packages/*
|
|
45
.gitignore
vendored
45
.gitignore
vendored
|
@ -1,41 +1,4 @@
|
||||||
.vs/
|
.idea
|
||||||
.vscode/
|
.vs
|
||||||
.idea/
|
bin
|
||||||
|
obj
|
||||||
*.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
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2025 sourcegit
|
Copyright (c) 2018 leo
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
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
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|
BIN
Preview_Dark.png
Normal file
BIN
Preview_Dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 115 KiB |
BIN
Preview_Light.png
Normal file
BIN
Preview_Light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
207
README.md
207
README.md
|
@ -1,207 +1,18 @@
|
||||||
# SourceGit - Opensource Git GUI client.
|
# SourceGit
|
||||||
|
|
||||||
[](https://github.com/sourcegit-scm/sourcegit/stargazers)
|
开源的Git客户端,仅用于Windows 10。单文件,无需安装,< 500KB。
|
||||||
[](https://github.com/sourcegit-scm/sourcegit/forks)
|
|
||||||
[](LICENSE)
|
|
||||||
[](https://github.com/sourcegit-scm/sourcegit/releases/latest)
|
|
||||||
[](https://github.com/sourcegit-scm/sourcegit/releases)
|
|
||||||
|
|
||||||
## Highlights
|
## 预览
|
||||||
|
|
||||||
* Supports Windows/macOS/Linux
|
* DarkTheme
|
||||||
* Opensource/Free
|
|
||||||
* Fast
|
|
||||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/Українська/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
|
||||||
* Built-in light/dark themes
|
|
||||||
* Customize theme
|
|
||||||
* Visual commit graph
|
|
||||||
* Supports SSH access with each remote
|
|
||||||
* GIT commands with GUI
|
|
||||||
* Clone/Fetch/Pull/Push...
|
|
||||||
* Merge/Rebase/Reset/Revert/Cherry-pick...
|
|
||||||
* Amend/Reword/Squash
|
|
||||||
* Interactive rebase
|
|
||||||
* Branches
|
|
||||||
* Remotes
|
|
||||||
* Tags
|
|
||||||
* Stashes
|
|
||||||
* Submodules
|
|
||||||
* Worktrees
|
|
||||||
* Archive
|
|
||||||
* Diff
|
|
||||||
* Save as patch/apply
|
|
||||||
* File histories
|
|
||||||
* Blame
|
|
||||||
* Revision Diffs
|
|
||||||
* Branch Diff
|
|
||||||
* Image Diff - Side-By-Side/Swipe/Blend
|
|
||||||
* Git command logs
|
|
||||||
* Search commits
|
|
||||||
* GitFlow
|
|
||||||
* Git LFS
|
|
||||||
* Bisect
|
|
||||||
* Issue Link
|
|
||||||
* Workspace
|
|
||||||
* Custom Action
|
|
||||||
* Using AI to generate commit message (C# port of [anjerodev/commitollama](https://github.com/anjerodev/commitollama))
|
|
||||||
|
|
||||||
> [!WARNING]
|

|
||||||
> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**.
|
|
||||||
|
|
||||||
## Translation Status
|
* LightTheme
|
||||||
|
|
||||||
You can find the current translation status in [TRANSLATION.md](https://github.com/sourcegit-scm/sourcegit/blob/develop/TRANSLATION.md)
|

|
||||||
|
|
||||||
## How to Use
|
|
||||||
|
|
||||||
**To use this tool, you need to install Git(>=2.25.1) first.**
|
## Thanks
|
||||||
|
|
||||||
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.
|
* [PUMA](https://gitee.com/whgfu) 配置默认User
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
113
SourceGit.sln
113
SourceGit.sln
|
@ -1,89 +1,9 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 17.9.34714.143
|
VisualStudioVersion = 16.0.30011.22
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGit", "src\SourceGit.csproj", "{2091C34D-4A17-4375-BEF3-4D60BE8113E4}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGit", "SourceGit\SourceGit.csproj", "{0A04DD59-7A6C-410C-B427-7DC8183993BD}"
|
||||||
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -91,32 +11,15 @@ Global
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{0A04DD59-7A6C-410C-B427-7DC8183993BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{0A04DD59-7A6C-410C-B427-7DC8183993BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{0A04DD59-7A6C-410C-B427-7DC8183993BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{2091C34D-4A17-4375-BEF3-4D60BE8113E4}.Release|Any CPU.Build.0 = Release|Any CPU
|
{0A04DD59-7A6C-410C-B427-7DC8183993BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
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
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {7FF1B9C6-B5BF-4A50-949F-4B407A0E31C9}
|
SolutionGuid = {01F4EC04-5B3C-4D74-BB48-1C251B2D2853}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
6
SourceGit/App.config
Normal file
6
SourceGit/App.config
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false" />
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
BIN
SourceGit/App.ico
Normal file
BIN
SourceGit/App.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
14
SourceGit/App.manifest
Normal file
14
SourceGit/App.manifest
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
15
SourceGit/App.xaml
Normal file
15
SourceGit/App.xaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<Application x:Class="SourceGit.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Startup="OnAppStartup"
|
||||||
|
Deactivated="OnAppDeactivated">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Icons.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Controls.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Themes/Dark.xaml"/>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
129
SourceGit/App.xaml.cs
Normal file
129
SourceGit/App.xaml.cs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace SourceGit {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application.
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Getter/Setter for Git preference.
|
||||||
|
/// </summary>
|
||||||
|
public static Git.Preference Preference {
|
||||||
|
get { return Git.Preference.Instance; }
|
||||||
|
set { Git.Preference.Instance = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if GIT has been configured.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsGitConfigured {
|
||||||
|
get {
|
||||||
|
return !string.IsNullOrEmpty(Preference.GitExecutable)
|
||||||
|
&& File.Exists(Preference.GitExecutable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error handler.
|
||||||
|
/// </summary>
|
||||||
|
public static Action<string> OnError {
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raise error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
public static void RaiseError(string message) {
|
||||||
|
OnError?.Invoke(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get popup manager by repository
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Startup event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deactivated event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void OnAppDeactivated(object sender, EventArgs e) {
|
||||||
|
Git.Preference.Save();
|
||||||
|
GC.Collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
SourceGit/Converters/BoolToCollapsed.cs
Normal file
37
SourceGit/Converters/BoolToCollapsed.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace SourceGit.Converters {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Same as BoolToVisibilityConverter.
|
||||||
|
/// </summary>
|
||||||
|
public class BoolToCollapsed : IValueConverter {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.Convert
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.ConvertBack
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
SourceGit/Converters/FileStatusToColor.cs
Normal file
59
SourceGit/Converters/FileStatusToColor.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace SourceGit.Converters {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert file status to brush
|
||||||
|
/// </summary>
|
||||||
|
public class FileStatusToColor : IValueConverter {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is only test local changes.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
SourceGit/Converters/FileStatusToIcon.cs
Normal file
45
SourceGit/Converters/FileStatusToIcon.cs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace SourceGit.Converters {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert file status to icon.
|
||||||
|
/// </summary>
|
||||||
|
public class FileStatusToIcon : IValueConverter {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is only test local changes.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
SourceGit/Converters/IndentToMargin.cs
Normal file
37
SourceGit/Converters/IndentToMargin.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace SourceGit.Converters {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert indent(horizontal offset) to Margin property
|
||||||
|
/// </summary>
|
||||||
|
public class IndentToMargin : IValueConverter {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.Convert
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
return new Thickness((double)value, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.ConvertBack
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
return ((Thickness)value).Left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
SourceGit/Converters/InverseBool.cs
Normal file
19
SourceGit/Converters/InverseBool.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace SourceGit.Converters {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inverse bool converter.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
SourceGit/Converters/InverseBoolToCollapsed.cs
Normal file
37
SourceGit/Converters/InverseBoolToCollapsed.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace SourceGit.Converters {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inverse BoolToCollapsed.
|
||||||
|
/// </summary>
|
||||||
|
public class InverseBoolToCollapsed : IValueConverter {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.Convert
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
return (bool)value ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.ConvertBack
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
SourceGit/Converters/PercentToDouble.cs
Normal file
41
SourceGit/Converters/PercentToDouble.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace SourceGit.Converters {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert percent to double.
|
||||||
|
/// </summary>
|
||||||
|
public class PercentToDouble : IValueConverter {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Percentage.
|
||||||
|
/// </summary>
|
||||||
|
public double Percent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.Convert
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
return (double)value * Percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.ConvertBack
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
SourceGit/Converters/TreeViewItemDepthToMargin.cs
Normal file
69
SourceGit/Converters/TreeViewItemDepthToMargin.cs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace SourceGit.Converters {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert depth of a TreeViewItem to Margin property.
|
||||||
|
/// </summary>
|
||||||
|
public class TreeViewItemDepthToMargin : IValueConverter {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indent length
|
||||||
|
/// </summary>
|
||||||
|
public double Indent { get; set; } = 19;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConverter.Convert
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement IValueConvert.ConvertBack
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get parent item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
SourceGit/Git/Blame.cs
Normal file
35
SourceGit/Git/Blame.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blame
|
||||||
|
/// </summary>
|
||||||
|
public class Blame {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Block content.
|
||||||
|
/// </summary>
|
||||||
|
public class Block {
|
||||||
|
public string CommitSHA { get; set; }
|
||||||
|
public string Author { get; set; }
|
||||||
|
public string Time { get; set; }
|
||||||
|
public string Content { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blocks
|
||||||
|
/// </summary>
|
||||||
|
public List<Block> Blocks { get; set; } = new List<Block>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is binary file?
|
||||||
|
/// </summary>
|
||||||
|
public bool IsBinary { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Line count.
|
||||||
|
/// </summary>
|
||||||
|
public int LineCount { get; set; } = 0;
|
||||||
|
}
|
||||||
|
}
|
190
SourceGit/Git/Branch.cs
Normal file
190
SourceGit/Git/Branch.cs
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Git branch
|
||||||
|
/// </summary>
|
||||||
|
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+)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Branch type.
|
||||||
|
/// </summary>
|
||||||
|
public enum Type {
|
||||||
|
Normal,
|
||||||
|
Feature,
|
||||||
|
Release,
|
||||||
|
Hotfix,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Branch name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full name.
|
||||||
|
/// </summary>
|
||||||
|
public string FullName { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Head ref
|
||||||
|
/// </summary>
|
||||||
|
public string Head { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subject for head ref.
|
||||||
|
/// </summary>
|
||||||
|
public string HeadSubject { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is local branch
|
||||||
|
/// </summary>
|
||||||
|
public bool IsLocal { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Branch type.
|
||||||
|
/// </summary>
|
||||||
|
public Type Kind { get; set; } = Type.Normal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remote name. Only used for remote branch
|
||||||
|
/// </summary>
|
||||||
|
public string Remote { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Upstream. Only used for local branches.
|
||||||
|
/// </summary>
|
||||||
|
public string Upstream { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Track information for upstream. Only used for local branches.
|
||||||
|
/// </summary>
|
||||||
|
public string UpstreamTrack { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is current branch. Only used for local branches.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCurrent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this branch's HEAD same with upstream?
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSameWithUpstream => string.IsNullOrEmpty(UpstreamTrack);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable filter in log histories.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFiltered { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load branches.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
public static List<Branch> Load(Repository repo) {
|
||||||
|
var localPrefix = "refs/heads/";
|
||||||
|
var remotePrefix = "refs/remotes/";
|
||||||
|
var branches = new List<Branch>();
|
||||||
|
var remoteBranches = new List<string>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create new branch.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="startPoint"></param>
|
||||||
|
public static void Create(Repository repo, string name, string startPoint) {
|
||||||
|
var errs = repo.RunCommand($"branch {name} {startPoint}", null);
|
||||||
|
if (errs != null) App.RaiseError(errs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rename branch
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
public void Rename(Repository repo, string name) {
|
||||||
|
var errs = repo.RunCommand($"branch -M {Name} {name}", null);
|
||||||
|
if (errs != null) App.RaiseError(errs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete branch.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
147
SourceGit/Git/Change.cs
Normal file
147
SourceGit/Git/Change.cs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changed file status.
|
||||||
|
/// </summary>
|
||||||
|
public class Change {
|
||||||
|
private static readonly Regex FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status Code
|
||||||
|
/// </summary>
|
||||||
|
public enum Status {
|
||||||
|
None,
|
||||||
|
Modified,
|
||||||
|
Added,
|
||||||
|
Deleted,
|
||||||
|
Renamed,
|
||||||
|
Copied,
|
||||||
|
Unmerged,
|
||||||
|
Untracked,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index status
|
||||||
|
/// </summary>
|
||||||
|
public Status Index { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Work tree status.
|
||||||
|
/// </summary>
|
||||||
|
public Status WorkTree { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current file path.
|
||||||
|
/// </summary>
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Original file path before this revision.
|
||||||
|
/// </summary>
|
||||||
|
public string OriginalPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Staged(added) in index?
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAddedToIndex {
|
||||||
|
get {
|
||||||
|
if (Index == Status.None || Index == Status.Untracked) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is conflict?
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse change for `--name-status` data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Raw data.</param>
|
||||||
|
/// <param name="fromCommit">Read from commit?</param>
|
||||||
|
/// <returns>Parsed change instance.</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
296
SourceGit/Git/Commit.cs
Normal file
296
SourceGit/Git/Commit.cs
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Git commit information.
|
||||||
|
/// </summary>
|
||||||
|
public class Commit {
|
||||||
|
private static readonly string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
|
||||||
|
private static readonly string GPGSIG_END = " -----END PGP SIGNATURE-----";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Object in commit.
|
||||||
|
/// </summary>
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SHA
|
||||||
|
/// </summary>
|
||||||
|
public string SHA { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short SHA.
|
||||||
|
/// </summary>
|
||||||
|
public string ShortSHA => SHA.Substring(0, 8);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent commit SHAs.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Parents { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author
|
||||||
|
/// </summary>
|
||||||
|
public User Author { get; set; } = new User();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Committer.
|
||||||
|
/// </summary>
|
||||||
|
public User Committer { get; set; } = new User();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subject
|
||||||
|
/// </summary>
|
||||||
|
public string Subject { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extra message.
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HEAD commit?
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHEAD { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merged in current branch?
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMerged { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// X offset in graph
|
||||||
|
/// </summary>
|
||||||
|
public double GraphOffset { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Has decorators.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasDecorators => Decorators.Count > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decorators.
|
||||||
|
/// </summary>
|
||||||
|
public List<Decorator> Decorators { get; set; } = new List<Decorator>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read commits.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo">Repository</param>
|
||||||
|
/// <param name="limit">Limitations</param>
|
||||||
|
/// <returns>Parsed commits.</returns>
|
||||||
|
public static List<Commit> Load(Repository repo, string limit) {
|
||||||
|
List<Commit> commits = new List<Commit>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get changed file list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<Change> GetChanges(Repository repo) {
|
||||||
|
var changes = new List<Change>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get revision files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<Object> GetFiles(Repository repo) {
|
||||||
|
var files = new List<Object>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get file content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetTextFileContent(Repository repo, string file, out bool isBinary) {
|
||||||
|
var data = new List<string>();
|
||||||
|
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()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
SourceGit/Git/Decorator.cs
Normal file
21
SourceGit/Git/Decorator.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decorator type.
|
||||||
|
/// </summary>
|
||||||
|
public enum DecoratorType {
|
||||||
|
None,
|
||||||
|
CurrentBranchHead,
|
||||||
|
LocalBranchHead,
|
||||||
|
RemoteBranchHead,
|
||||||
|
Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Commit decorator.
|
||||||
|
/// </summary>
|
||||||
|
public class Decorator {
|
||||||
|
public DecoratorType Type { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
263
SourceGit/Git/Diff.cs
Normal file
263
SourceGit/Git/Diff.cs
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Diff helper.
|
||||||
|
/// </summary>
|
||||||
|
public class Diff {
|
||||||
|
private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@", RegexOptions.None);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Line mode.
|
||||||
|
/// </summary>
|
||||||
|
public enum LineMode {
|
||||||
|
Normal,
|
||||||
|
Indicator,
|
||||||
|
Empty,
|
||||||
|
Added,
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Side
|
||||||
|
/// </summary>
|
||||||
|
public enum Side {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Both,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binary change.
|
||||||
|
/// </summary>
|
||||||
|
public class BinaryChange {
|
||||||
|
public long Size = 0;
|
||||||
|
public long PreSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Block
|
||||||
|
/// </summary>
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Diff result.
|
||||||
|
/// </summary>
|
||||||
|
public class Result {
|
||||||
|
public bool IsValid = false;
|
||||||
|
public bool IsBinary = false;
|
||||||
|
public List<Block> Blocks = new List<Block>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run diff process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get file size changes for binary file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="revisions"></param>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <param name="orgPath"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
202
SourceGit/Git/MergeTool.cs
Normal file
202
SourceGit/Git/MergeTool.cs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// External merge tool
|
||||||
|
/// </summary>
|
||||||
|
public class MergeTool {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executable file name.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutableName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Command line parameter.
|
||||||
|
/// </summary>
|
||||||
|
public string Parameter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto finder.
|
||||||
|
/// </summary>
|
||||||
|
public Func<string> Finder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this merge tool configured.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConfigured => !string.IsNullOrEmpty(ExecutableName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Supported merge tools.
|
||||||
|
/// </summary>
|
||||||
|
public static List<MergeTool> Supported = new List<MergeTool>() {
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finder for invalid merge tool.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string FindInvalid() {
|
||||||
|
return "--";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find araxis merge tool install path.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string FindAraxisMerge() {
|
||||||
|
var path = @"C:\Program Files\Araxis\Araxis Merge\Compare.exe";
|
||||||
|
if (File.Exists(path)) return path;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find kdiff3.exe by registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finder for p4merge
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string FindP4Merge() {
|
||||||
|
var path = @"C:\Program Files\Perforce\p4merge.exe";
|
||||||
|
if (File.Exists(path)) return path;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find BComp.exe by registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find TortoiseMerge.exe by registry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find vsDiffMerge.exe.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find VSCode executable file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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 "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="exe"></param>
|
||||||
|
/// <param name="param"></param>
|
||||||
|
/// <param name="finder"></param>
|
||||||
|
public MergeTool(string name, string exe, string param, Func<string> finder) {
|
||||||
|
Name = name;
|
||||||
|
ExecutableName = exe;
|
||||||
|
Parameter = param;
|
||||||
|
Finder = finder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
300
SourceGit/Git/Preference.cs
Normal file
300
SourceGit/Git/Preference.cs
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User's preference settings. Serialized to
|
||||||
|
/// </summary>
|
||||||
|
public class Preference {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Group(Virtual folder) for watched repositories.
|
||||||
|
/// </summary>
|
||||||
|
public class Group {
|
||||||
|
/// <summary>
|
||||||
|
/// Unique ID of this group.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Display name.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Parent ID.
|
||||||
|
/// </summary>
|
||||||
|
public string ParentId { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Cache UI IsExpended status.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsExpended { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region STATICS
|
||||||
|
/// <summary>
|
||||||
|
/// Storage path for Preference.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly string SAVE_PATH = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"SourceGit",
|
||||||
|
"preference.xml");
|
||||||
|
/// <summary>
|
||||||
|
/// Runtime singleton instance.
|
||||||
|
/// </summary>
|
||||||
|
private static Preference instance = null;
|
||||||
|
public static Preference Instance {
|
||||||
|
get {
|
||||||
|
if (instance == null) Load();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
instance = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SETTING_GIT
|
||||||
|
/// <summary>
|
||||||
|
/// Git executable file path.
|
||||||
|
/// </summary>
|
||||||
|
public string GitExecutable { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Default clone directory.
|
||||||
|
/// </summary>
|
||||||
|
public string GitDefaultCloneDir { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SETTING_MERGE_TOOL
|
||||||
|
/// <summary>
|
||||||
|
/// Selected merge tool.
|
||||||
|
/// </summary>
|
||||||
|
public int MergeTool { get; set; } = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// Executable file path for merge tool.
|
||||||
|
/// </summary>
|
||||||
|
public string MergeExecutable { get; set; } = "--";
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SETTING_UI
|
||||||
|
/// <summary>
|
||||||
|
/// Main window's width
|
||||||
|
/// </summary>
|
||||||
|
public double UIMainWindowWidth { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Main window's height
|
||||||
|
/// </summary>
|
||||||
|
public double UIMainWindowHeight { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Use light color theme.
|
||||||
|
/// </summary>
|
||||||
|
public bool UIUseLightTheme { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Show/Hide tags' list view.
|
||||||
|
/// </summary>
|
||||||
|
public bool UIShowTags { get; set; } = true;
|
||||||
|
/// <summary>
|
||||||
|
/// Use horizontal layout for histories.
|
||||||
|
/// </summary>
|
||||||
|
public bool UIUseHorizontalLayout { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Use list instead of tree in unstaged view
|
||||||
|
/// </summary>
|
||||||
|
public bool UIUseListInUnstaged { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Use list instead of tree in staged view.
|
||||||
|
/// </summary>
|
||||||
|
public bool UIUseListInStaged { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Use list instead of tree in change view.
|
||||||
|
/// </summary>
|
||||||
|
public bool UIUseListInChanges { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SETTING_REPOS
|
||||||
|
/// <summary>
|
||||||
|
/// Groups for repositories.
|
||||||
|
/// </summary>
|
||||||
|
public List<Group> Groups { get; set; } = new List<Group>();
|
||||||
|
/// <summary>
|
||||||
|
/// Watched repositories.
|
||||||
|
/// </summary>
|
||||||
|
public List<Repository> Repositories { get; set; } = new List<Git.Repository>();
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region METHODS_LOAD_SAVE
|
||||||
|
/// <summary>
|
||||||
|
/// Load preference from disk.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Loaded preference instance.</returns>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save current preference into disk.
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Add new group(virtual folder).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Display name.</param>
|
||||||
|
/// <param name="parentId">Parent group ID.</param>
|
||||||
|
/// <returns>Added group instance.</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find group by ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Unique ID</param>
|
||||||
|
/// <returns>Founded group's instance.</returns>
|
||||||
|
public Group FindGroup(string id) {
|
||||||
|
foreach (var group in Groups) {
|
||||||
|
if (group.Id == id) return group;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rename group.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Unique ID</param>
|
||||||
|
/// <param name="newName">New name.</param>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a group.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Unique ID</param>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Add repository.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Local storage path.</param>
|
||||||
|
/// <param name="groupId">Group's ID</param>
|
||||||
|
/// <returns>Added repository instance.</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find repository by path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Local storage path.</param>
|
||||||
|
/// <returns>Founded repository instance.</returns>
|
||||||
|
public Repository FindRepository(string path) {
|
||||||
|
var dir = new DirectoryInfo(path);
|
||||||
|
foreach (var repo in Repositories) {
|
||||||
|
if (repo.Path == dir.FullName) return repo;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change a repository's display name in RepositoryManager.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Local storage path.</param>
|
||||||
|
/// <param name="newName">New name</param>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a repository in RepositoryManager.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Local storage path.</param>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
97
SourceGit/Git/Remote.cs
Normal file
97
SourceGit/Git/Remote.cs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Git remote
|
||||||
|
/// </summary>
|
||||||
|
public class Remote {
|
||||||
|
private static readonly Regex FORMAT = new Regex(@"^([\w\.\-]+)\s*(\S+).*$");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of this remote
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL
|
||||||
|
/// </summary>
|
||||||
|
public string URL { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parsing remote
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo">Repository</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static List<Remote> Load(Repository repo) {
|
||||||
|
var remotes = new List<Remote>();
|
||||||
|
var added = new List<string>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add new remote
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="url"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete remote.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="remote"></param>
|
||||||
|
public static void Delete(Repository repo, string remote) {
|
||||||
|
var errs = repo.RunCommand($"remote remove {remote}", null);
|
||||||
|
if (errs != null) App.RaiseError(errs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Edit remote.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="url"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1163
SourceGit/Git/Repository.cs
Normal file
1163
SourceGit/Git/Repository.cs
Normal file
File diff suppressed because it is too large
Load diff
101
SourceGit/Git/Stash.cs
Normal file
101
SourceGit/Git/Stash.cs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Git stash
|
||||||
|
/// </summary>
|
||||||
|
public class Stash {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SHA for this stash
|
||||||
|
/// </summary>
|
||||||
|
public string SHA { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author
|
||||||
|
/// </summary>
|
||||||
|
public User Author { get; set; } = new User();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stash push.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="includeUntracked"></param>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <param name="files"></param>
|
||||||
|
public static void Push(Repository repo, bool includeUntracked, string message, List<string> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get changed file list in this stash.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<Change> GetChanges(Repository repo) {
|
||||||
|
List<Change> changes = new List<Change>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply stash.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
public void Apply(Repository repo) {
|
||||||
|
var errs = repo.RunCommand($"stash apply -q {Name}", null);
|
||||||
|
if (errs != null) App.RaiseError(errs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pop stash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
public void Pop(Repository repo) {
|
||||||
|
var errs = repo.RunCommand($"stash pop -q {Name}", null);
|
||||||
|
if (errs != null) App.RaiseError(errs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drop stash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
public void Drop(Repository repo) {
|
||||||
|
var errs = repo.RunCommand($"stash drop -q {Name}", null);
|
||||||
|
if (errs != null) App.RaiseError(errs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
SourceGit/Git/Tag.cs
Normal file
118
SourceGit/Git/Tag.cs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Git tag.
|
||||||
|
/// </summary>
|
||||||
|
public class Tag {
|
||||||
|
private static readonly Regex FORMAT = new Regex(@"\$(.*)\$(.*)\$(.*)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SHA
|
||||||
|
/// </summary>
|
||||||
|
public string SHA { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display name.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable filter in log histories.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFiltered { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load all tags
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static List<Tag> Load(Repository repo) {
|
||||||
|
var args = "for-each-ref --sort=-creatordate --format=\"$%(refname:short)$%(objectname)$%(*objectname)\" refs/tags";
|
||||||
|
var tags = new List<Tag>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add new tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="startPoint"></param>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="push"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Push tag to remote.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="remote"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
SourceGit/Git/User.cs
Normal file
42
SourceGit/Git/User.cs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Git user.
|
||||||
|
/// </summary>
|
||||||
|
public class User {
|
||||||
|
private static readonly Regex FORMAT = new Regex(@"\w+ (.*) <([\w\.\-_]+@[\w\.\-_]+)> (\d{10}) [\+\-]\d+");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Email.
|
||||||
|
/// </summary>
|
||||||
|
public string Email { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Operation time.
|
||||||
|
/// </summary>
|
||||||
|
public string Time { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse user from raw string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Raw string</param>
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
275
SourceGit/Helpers/CommitGraph.cs
Normal file
275
SourceGit/Helpers/CommitGraph.cs
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace SourceGit.Helpers {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tools to parse commit graph.
|
||||||
|
/// </summary>
|
||||||
|
public class CommitGraphMaker {
|
||||||
|
/// <summary>
|
||||||
|
/// Sizes
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Colors
|
||||||
|
/// </summary>
|
||||||
|
public static Brush[] Colors = new Brush[] {
|
||||||
|
Brushes.Orange,
|
||||||
|
Brushes.ForestGreen,
|
||||||
|
Brushes.Gold,
|
||||||
|
Brushes.Magenta,
|
||||||
|
Brushes.Red,
|
||||||
|
Brushes.Gray,
|
||||||
|
Brushes.Turquoise,
|
||||||
|
Brushes.Olive,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helpers to draw lines.
|
||||||
|
/// </summary>
|
||||||
|
public class LineHelper {
|
||||||
|
private double lastX = 0;
|
||||||
|
private double lastY = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent commit id.
|
||||||
|
/// </summary>
|
||||||
|
public string Next { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is merged into this tree.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMerged { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Points in line
|
||||||
|
/// </summary>
|
||||||
|
public List<Point> Points { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Brush to draw line
|
||||||
|
/// </summary>
|
||||||
|
public Brush Brush { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current horizontal offset.
|
||||||
|
/// </summary>
|
||||||
|
public double HorizontalOffset => lastX;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nextCommitId">Parent commit id</param>
|
||||||
|
/// <param name="isMerged">Is merged in tree</param>
|
||||||
|
/// <param name="colorIdx">Color index</param>
|
||||||
|
/// <param name="startPoint">Start point</param>
|
||||||
|
public LineHelper(string nextCommitId, bool isMerged, int colorIdx, Point startPoint) {
|
||||||
|
Next = nextCommitId;
|
||||||
|
IsMerged = isMerged;
|
||||||
|
Points = new List<Point>() { startPoint };
|
||||||
|
Brush = Colors[colorIdx % Colors.Length];
|
||||||
|
|
||||||
|
lastX = startPoint.X;
|
||||||
|
lastY = startPoint.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Line to.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x"></param>
|
||||||
|
/// <param name="y"></param>
|
||||||
|
/// <param name="isEnd"></param>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short link between two commits.
|
||||||
|
/// </summary>
|
||||||
|
public struct ShortLink {
|
||||||
|
public Point Start;
|
||||||
|
public Point Control;
|
||||||
|
public Point End;
|
||||||
|
public Brush Brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dot
|
||||||
|
/// </summary>
|
||||||
|
public struct Dot {
|
||||||
|
public double X;
|
||||||
|
public double Y;
|
||||||
|
public Brush Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Independent lines in graph
|
||||||
|
/// </summary>
|
||||||
|
public List<LineHelper> Lines { get; set; } = new List<LineHelper>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short links.
|
||||||
|
/// </summary>
|
||||||
|
public List<ShortLink> Links { get; set; } = new List<ShortLink>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All dots.
|
||||||
|
/// </summary>
|
||||||
|
public List<Dot> Dots { get; set; } = new List<Dot>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Highlight commit id.
|
||||||
|
/// </summary>
|
||||||
|
public string Highlight { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse commits.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commits"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static CommitGraphMaker Parse(List<Git.Commit> commits) {
|
||||||
|
CommitGraphMaker maker = new CommitGraphMaker();
|
||||||
|
|
||||||
|
List<LineHelper> unsolved = new List<LineHelper>();
|
||||||
|
List<LineHelper> ended = new List<LineHelper>();
|
||||||
|
Dictionary<string, LineHelper> currentMap = new Dictionary<string, LineHelper>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
224
SourceGit/Helpers/TextBoxHelper.cs
Normal file
224
SourceGit/Helpers/TextBoxHelper.cs
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace SourceGit.Helpers {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attached properties to TextBox.
|
||||||
|
/// </summary>
|
||||||
|
public static class TextBoxHelper {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto scroll on text changed or selection changed.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty AutoScrollProperty = DependencyProperty.RegisterAttached(
|
||||||
|
"AutoScroll",
|
||||||
|
typeof(bool),
|
||||||
|
typeof(TextBoxHelper),
|
||||||
|
new PropertyMetadata(false, OnAutoScrollChanged));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Placeholder property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached(
|
||||||
|
"Placeholder",
|
||||||
|
typeof(string),
|
||||||
|
typeof(TextBoxHelper),
|
||||||
|
new PropertyMetadata(string.Empty, OnPlaceholderChanged));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Vertical alignment for placeholder.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty PlaceholderBaselineProperty = DependencyProperty.RegisterAttached(
|
||||||
|
"PlaceholderBaseline",
|
||||||
|
typeof(AlignmentY),
|
||||||
|
typeof(TextBoxHelper),
|
||||||
|
new PropertyMetadata(AlignmentY.Center));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Property to store generated placeholder brush.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty PlaceholderBrushProperty = DependencyProperty.RegisterAttached(
|
||||||
|
"PlaceholderBrush",
|
||||||
|
typeof(Brush),
|
||||||
|
typeof(TextBoxHelper),
|
||||||
|
new PropertyMetadata(Brushes.Transparent));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setter for AutoScrollProperty
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <param name="enabled"></param>
|
||||||
|
public static void SetAutoScroll(UIElement element, bool enabled) {
|
||||||
|
element.SetValue(AutoScrollProperty, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Getter for AutoScrollProperty
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool GetAutoScroll(UIElement element) {
|
||||||
|
return (bool)element.GetValue(AutoScrollProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when AutoScroll property changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when placeholder changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||||
|
var textBox = d as TextBox;
|
||||||
|
if (textBox != null) textBox.Loaded += OnTextLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setter for Placeholder property
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public static void SetPlaceholder(UIElement element, string value) {
|
||||||
|
element.SetValue(PlaceholderProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Getter for Placeholder property
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetPlaceholder(UIElement element) {
|
||||||
|
return (string)element.GetValue(PlaceholderProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setter for PlaceholderBaseline property
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <param name="align"></param>
|
||||||
|
public static void SetPlaceholderBaseline(UIElement element, AlignmentY align) {
|
||||||
|
element.SetValue(PlaceholderBaselineProperty, align);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setter for PlaceholderBaseline property.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static AlignmentY GetPlaceholderBaseline(UIElement element) {
|
||||||
|
return (AlignmentY)element.GetValue(PlaceholderBaselineProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setter for PlaceholderBrush property.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public static void SetPlaceholderBrush(UIElement element, Brush value) {
|
||||||
|
element.SetValue(PlaceholderBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Getter for PlaceholderBrush property.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Brush GetPlaceholderBrush(UIElement element) {
|
||||||
|
return (Brush)element.GetValue(PlaceholderBrushProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set placeholder as background when TextBox was loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dynamically hide/show placeholder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
329
SourceGit/Helpers/TreeViewHelper.cs
Normal file
329
SourceGit/Helpers/TreeViewHelper.cs
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to enable multi-selection of TreeView
|
||||||
|
/// </summary>
|
||||||
|
public static class TreeViewHelper {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Definition of EnableMultiSelection property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty EnableMultiSelectionProperty =
|
||||||
|
DependencyProperty.RegisterAttached(
|
||||||
|
"EnableMultiSelection",
|
||||||
|
typeof(bool),
|
||||||
|
typeof(TreeViewHelper),
|
||||||
|
new FrameworkPropertyMetadata(false, OnEnableMultiSelectionChanged));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Getter of EnableMultiSelection
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool GetEnableMultiSelection(DependencyObject obj) {
|
||||||
|
return (bool)obj.GetValue(EnableMultiSelectionProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setter of EnableMultiSelection
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public static void SetEnableMultiSelection(DependencyObject obj, bool value) {
|
||||||
|
obj.SetValue(EnableMultiSelectionProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Definition of SelectedItems
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty SelectedItemsProperty =
|
||||||
|
DependencyProperty.RegisterAttached(
|
||||||
|
"SelectedItems",
|
||||||
|
typeof(ObservableCollection<TreeViewItem>),
|
||||||
|
typeof(TreeViewHelper),
|
||||||
|
new FrameworkPropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Getter of SelectedItems
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ObservableCollection<TreeViewItem> GetSelectedItems(DependencyObject obj) {
|
||||||
|
return (ObservableCollection<TreeViewItem>)obj.GetValue(SelectedItemsProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setter of SelectedItems
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public static void SetSelectedItems(DependencyObject obj, ObservableCollection<TreeViewItem> value) {
|
||||||
|
obj.SetValue(SelectedItemsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Definition of IsChecked property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty IsCheckedProperty =
|
||||||
|
DependencyProperty.RegisterAttached(
|
||||||
|
"IsChecked",
|
||||||
|
typeof(bool),
|
||||||
|
typeof(TreeViewHelper),
|
||||||
|
new FrameworkPropertyMetadata(false));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Getter of IsChecked Property.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool GetIsChecked(DependencyObject obj) {
|
||||||
|
return (bool)obj.GetValue(IsCheckedProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setter of IsChecked property
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public static void SetIsChecked(DependencyObject obj, bool value) {
|
||||||
|
obj.SetValue(IsCheckedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Definition of MultiSelectionChangedEvent
|
||||||
|
/// </summary>
|
||||||
|
public static readonly RoutedEvent MultiSelectionChangedEvent =
|
||||||
|
EventManager.RegisterRoutedEvent("MultiSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TreeViewHelper));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add handler for MultiSelectionChanged event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
/// <param name="handler"></param>
|
||||||
|
public static void AddMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) {
|
||||||
|
var tree = d as TreeView;
|
||||||
|
if (tree != null) tree.AddHandler(MultiSelectionChangedEvent, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove handler for MultiSelectionChanged event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
/// <param name="handler"></param>
|
||||||
|
public static void RemoveMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) {
|
||||||
|
var tree = d as TreeView;
|
||||||
|
if (tree != null) tree.RemoveHandler(MultiSelectionChangedEvent, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select all items in tree.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tree"></param>
|
||||||
|
public static void SelectWholeTree(TreeView tree) {
|
||||||
|
var selected = GetSelectedItems(tree);
|
||||||
|
selected.Clear();
|
||||||
|
SelectAll(selected, tree);
|
||||||
|
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selected one item by DataContext
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tree"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unselect the whole tree.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tree"></param>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hooks when EnableMultiSelection changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private static void OnEnableMultiSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||||
|
var tree = d as TreeView;
|
||||||
|
if (tree != null && (bool)e.NewValue) {
|
||||||
|
tree.SetValue(SelectedItemsProperty, new ObservableCollection<TreeViewItem>());
|
||||||
|
tree.PreviewMouseDown += OnTreeMouseDown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Preview mouse button select.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find TreeViewItem by child element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="child"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find TreeViewItem by DataContext
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="control"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select all items.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="selected"></param>
|
||||||
|
/// <param name="control"></param>
|
||||||
|
private static void SelectAll(ObservableCollection<TreeViewItem> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select range items between given.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="selected"></param>
|
||||||
|
/// <param name="control"></param>
|
||||||
|
/// <param name="from"></param>
|
||||||
|
/// <param name="to"></param>
|
||||||
|
/// <param name="started"></param>
|
||||||
|
private static int SelectRange(ObservableCollection<TreeViewItem> 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<TreeViewItem> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
SourceGit/Helpers/Validations.cs
Normal file
136
SourceGit/Helpers/Validations.cs
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.Helpers {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate clone folder.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate git remote URL
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate tag name.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate branch name.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate tag name.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required for commit subject.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required for patch file.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required for submodule path.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
SourceGit/Properties/AssemblyInfo.cs
Normal file
3
SourceGit/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
|
22
SourceGit/Resources/Controls.xaml
Normal file
22
SourceGit/Resources/Controls.xaml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Border.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Button.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/CheckBox.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ComboBox.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ContextMenu.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/DataGrid.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/HyperLink.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Label.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ListView.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Path.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/RadioButton.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ScrollBar.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ScrollViewer.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/TabControl.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/TextBox.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ToggleButton.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Tooltip.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/TreeView.xaml"/>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
61
SourceGit/Resources/Icons.xaml
Normal file
61
SourceGit/Resources/Icons.xaml
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Geometry x:Key="Icon.Git">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Submodule">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</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.ScrollLeft">M753.613 996.727L269.38 511.505 754.602 27.272z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.ScrollRight">M270.387 27.273L754.62 512.495 269.398 996.728z</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.Minimize">F1M0,6L0,9 9,9 9,6 0,6z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Maximize">F1M0,0L0,9 9,9 9,0 0,0 0,3 8,3 8,8 1,8 1,3z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Restore">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Close">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Check">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Loading">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Search">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Conflict">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</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.List">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Tree">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</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.MoveUp">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.MoveDown">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</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.StageSelected">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.StageAll">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.UnstageSelected">M169.5 831l342.8-341.9L855.1 831l105.3-105.3-448.1-448.1L64.2 725.7 169.5 831z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.UnstageAll">M768 800V684.8L512 480 256 684.8V800l256-204.8L768 800zM512 339.2L768 544V428.8L512 224 256 428.8V544l256-204.8z</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.Preference">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Setting">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Info">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Folder">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Folder.Fill">M448 64l128 128h448v768H0V64z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Folder.Open">M832 960l192-512H192L0 960zM128 384L0 960V128h288l128 128h416v128z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.File">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Diff">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Filter">M599.22969 424.769286 599.22969 657.383158 424.769286 831.844585 424.769286 424.769286 192.155415 192.155415 831.844585 192.155415Z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Binary">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</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.Vertical">M1024 1024H0V0h1024v1024z m-64-64V320H320V256h640V64H64v896h192V64h64v896z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Horizontal">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</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.Fetch">M1024 896v128H0V704h128v192h768V704h128v192zM576 554.688L810.688 320 896 405.312l-384 384-384-384L213.312 320 448 554.688V0h128v554.688z</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Pull">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Push">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.SaveStash">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Apply">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Terminal">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Flow">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</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Icon.Commit">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.WorkingCopy">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Histories">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Stashes">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Branch">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Branch.Add">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Remote">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Remote.Add">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Tag">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</Geometry>
|
||||||
|
<Geometry x:Key="Icon.Tag.Add">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</Geometry>
|
||||||
|
</ResourceDictionary>
|
12
SourceGit/Resources/Styles/Border.xaml
Normal file
12
SourceGit/Resources/Styles/Border.xaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style x:Key="Style.Border.Badge" TargetType="{x:Type Border}">
|
||||||
|
<Setter Property="CornerRadius" Value="9"/>
|
||||||
|
<Setter Property="Margin" Value="4,0"/>
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
|
<Setter Property="Height" Value="18"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Brush.Badge}"/>
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
55
SourceGit/Resources/Styles/Button.xaml
Normal file
55
SourceGit/Resources/Styles/Button.xaml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<!-- 无边框按钮(也是默认样式) -->
|
||||||
|
<Style x:Key="Style.Button" TargetType="{x:Type Button}">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="Opacity" Value="0.9"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Button}">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Opacity" Value="1"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- 修改默认样式 -->
|
||||||
|
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button}"/>
|
||||||
|
|
||||||
|
<!-- 无边框但显示Hover -->
|
||||||
|
<Style x:Key="Style.Button.HighlightHover" BasedOn="{StaticResource Style.Button}" TargetType="{x:Type Button}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#40000000"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- 有边框 -->
|
||||||
|
<Style x:Key="Style.Button.Bordered" BasedOn="{StaticResource Style.Button}" TargetType="{x:Type Button}">
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border1}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="8,0"/>
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="Style.Button.AccentBordered" BasedOn="{StaticResource Style.Button}" TargetType="{x:Type Button}">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="8,0"/>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
38
SourceGit/Resources/Styles/CheckBox.xaml
Normal file
38
SourceGit/Resources/Styles/CheckBox.xaml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type CheckBox}">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type CheckBox}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border x:Name="Border" Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" BorderBrush="{DynamicResource Brush.Border1}" BorderThickness="1" Background="Transparent">
|
||||||
|
<Path x:Name="Checked" Height="12" Width="12" Style="{DynamicResource Style.Icon}" Data="{DynamicResource Icon.Check}" Fill="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" Margin="8,0,0,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsChecked" Value="False">
|
||||||
|
<Setter TargetName="Checked" Property="Visibility" Value="Hidden"/>
|
||||||
|
</Trigger>
|
||||||
|
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
83
SourceGit/Resources/Styles/ComboBox.xaml
Normal file
83
SourceGit/Resources/Styles/ComboBox.xaml
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<ControlTemplate x:Key="Template.ComboBox.ToggleButton" TargetType="{x:Type ToggleButton}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="20" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border x:Name="Border" Grid.ColumnSpan="2" CornerRadius="0" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{DynamicResource Brush.Border1}" Background="Transparent"/>
|
||||||
|
<Border Grid.Column="0" CornerRadius="0" Margin="1" Background="Transparent"/>
|
||||||
|
<Path x:Name="Arrow" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 4 4 L 8 0 Z" Fill="{DynamicResource Brush.Border1}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="Template.ComboBox.TextBox" TargetType="{x:Type TextBox}">
|
||||||
|
<Border x:Name="PART_ContentHost" Focusable="False" Background="{TemplateBinding Background}" />
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type ComboBox}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="true" />
|
||||||
|
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
|
||||||
|
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
|
||||||
|
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="MinWidth" Value="120" />
|
||||||
|
<Setter Property="MinHeight" Value="20" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ComboBox}">
|
||||||
|
<Grid>
|
||||||
|
<ToggleButton x:Name="ToggleButton" BorderThickness="{TemplateBinding BorderThickness}" Template="{StaticResource Template.ComboBox.ToggleButton}" Grid.Column="2" Focusable="false" ClickMode="Press" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||||
|
<ContentPresenter x:Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding SelectionBoxItem}" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left" TextElement.Foreground="{DynamicResource Brush.FG}"/>
|
||||||
|
<TextBox x:Name="PART_EditableTextBox" Style="{x:Null}" Template="{StaticResource Template.ComboBox.TextBox}" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="3,3,23,3" Focusable="True" Background="Transparent" Visibility="Hidden" IsReadOnly="{TemplateBinding IsReadOnly}" />
|
||||||
|
<Popup x:Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Slide">
|
||||||
|
<Grid x:Name="DropDown" SnapsToDevicePixels="True" MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}">
|
||||||
|
<Border x:Name="DropDownBorder" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Background="{DynamicResource Brush.BG2}"/>
|
||||||
|
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
|
||||||
|
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" TextElement.Foreground="{DynamicResource Brush.FG}"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Popup>
|
||||||
|
</Grid>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsGrouping" Value="true">
|
||||||
|
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger SourceName="Popup" Property="AllowsTransparency" Value="true">
|
||||||
|
<Setter TargetName="DropDownBorder" Property="CornerRadius" Value="0" />
|
||||||
|
<Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type ComboBoxItem}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="true" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
|
||||||
|
<Border x:Name="Border" Padding="2" SnapsToDevicePixels="true" Background="Transparent">
|
||||||
|
<ContentPresenter/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
91
SourceGit/Resources/Styles/ContextMenu.xaml
Normal file
91
SourceGit/Resources/Styles/ContextMenu.xaml
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type MenuItem}">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="MinHeight" Value="24"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type ContextMenu}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||||
|
<Setter Property="Grid.IsSharedSizeScope" Value="False" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ContextMenu}">
|
||||||
|
<Border Background="{DynamicResource Brush.BG1}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}">
|
||||||
|
<StackPanel IsItemsHost="True" Margin="1" KeyboardNavigation.DirectionalNavigation="Cycle"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="{x:Static MenuItem.SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}">
|
||||||
|
<Border Name="Border" Background="Transparent">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="32"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<ContentPresenter Name="Icon" Grid.Column="0" HorizontalAlignment="Center" ContentSource="Icon"/>
|
||||||
|
<ContentPresenter Name="HeadHost" Grid.Column="1" ContentSource="Header" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Opacity" Value=".5"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="{x:Static MenuItem.SubmenuHeaderTemplateKey}" TargetType="{x:Type MenuItem}">
|
||||||
|
<Border Name="Border" Background="Transparent">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="32"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<ContentPresenter Name="Icon" Grid.Column="0" Margin="6,0" VerticalAlignment="Center" ContentSource="Icon"/>
|
||||||
|
<ContentPresenter Name="HeadHost" Grid.Column="1" ContentSource="Header" VerticalAlignment="Center"/>
|
||||||
|
<Path Grid.Column="2" Width="8" Height="8" Style="{DynamicResource Style.Icon}" Data="M 0 0 L 0 7 L 4 3.5 Z"/>
|
||||||
|
<Popup Name="Popup" Placement="Right" HorizontalOffset="-2" IsOpen="{TemplateBinding IsSubmenuOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Fade">
|
||||||
|
<Border Name="SubmenuBorder" SnapsToDevicePixels="True" Background="{DynamicResource Brush.BG1}" BorderBrush="{DynamicResource Brush.Border1}" BorderThickness="1">
|
||||||
|
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
|
||||||
|
</Border>
|
||||||
|
</Popup>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsHighlighted" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Opacity" Value=".5"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<Style x:Key="{x:Static MenuItem.SeparatorStyleKey}" TargetType="{x:Type Separator}">
|
||||||
|
<Setter Property="Margin" Value="30,2,0,2"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Separator}">
|
||||||
|
<Rectangle Height=".8" VerticalAlignment="Center" SnapsToDevicePixels="True" Fill="{DynamicResource Brush.Border1}"/>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
44
SourceGit/Resources/Styles/DataGrid.xaml
Normal file
44
SourceGit/Resources/Styles/DataGrid.xaml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<Style x:Key="Style.DataGridCell" TargetType="{x:Type DataGridCell}">
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.DataGridRow" TargetType="{x:Type DataGridRow}">
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type DataGrid}">
|
||||||
|
<Style.Resources>
|
||||||
|
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="{x:Static SystemColors.HighlightColor}"/>
|
||||||
|
</Style.Resources>
|
||||||
|
|
||||||
|
<Setter Property="IsReadOnly" Value="True"/>
|
||||||
|
<Setter Property="AutoGenerateColumns" Value="False"/>
|
||||||
|
<Setter Property="CanUserAddRows" Value="False"/>
|
||||||
|
<Setter Property="CanUserDeleteRows" Value="False"/>
|
||||||
|
<Setter Property="CanUserResizeRows" Value="False"/>
|
||||||
|
<Setter Property="CanUserReorderColumns" Value="False"/>
|
||||||
|
<Setter Property="CanUserResizeColumns" Value="False" />
|
||||||
|
<Setter Property="CanUserSortColumns" Value="False"/>
|
||||||
|
<Setter Property="AllowDrop" Value="False"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="TextElement.FontFamily" Value="Consolas"/>
|
||||||
|
<Setter Property="EnableColumnVirtualization" Value="True"/>
|
||||||
|
<Setter Property="EnableRowVirtualization" Value="True"/>
|
||||||
|
<Setter Property="RowBackground" Value="Transparent"/>
|
||||||
|
<Setter Property="HeadersVisibility" Value="None"/>
|
||||||
|
<Setter Property="GridLinesVisibility" Value="None"/>
|
||||||
|
<Setter Property="CellStyle" Value="{StaticResource Style.DataGridCell}"/>
|
||||||
|
<Setter Property="RowStyle" Value="{StaticResource Style.DataGridRow}"/>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
11
SourceGit/Resources/Styles/HyperLink.xaml
Normal file
11
SourceGit/Resources/Styles/HyperLink.xaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style TargetType="{x:Type Hyperlink}">
|
||||||
|
<Setter Property="TextDecorations" Value="{x:Null}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
15
SourceGit/Resources/Styles/Label.xaml
Normal file
15
SourceGit/Resources/Styles/Label.xaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style x:Key="Style.Label" TargetType="{x:Type Label}">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource Style.Label}"/>
|
||||||
|
|
||||||
|
<Style x:Key="Style.Label.GroupHeader" BasedOn="{StaticResource Style.Label}" TargetType="{x:Type Label}">
|
||||||
|
<Setter Property="FontWeight" Value="DemiBold"/>
|
||||||
|
<Setter Property="Opacity" Value=".5"/>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
66
SourceGit/Resources/Styles/ListView.xaml
Normal file
66
SourceGit/Resources/Styles/ListView.xaml
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<Style x:Key="Style.ListViewItem.Borderless" TargetType="{x:Type ListViewItem}">
|
||||||
|
<Setter Property="Padding" Value="2"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="true"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ListViewItem}">
|
||||||
|
<Border x:Name="Border" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true" Background="Transparent">
|
||||||
|
<ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
<MultiTrigger>
|
||||||
|
<MultiTrigger.Conditions>
|
||||||
|
<Condition Property="IsMouseOver" Value="True"/>
|
||||||
|
<Condition Property="IsSelected" Value="False"/>
|
||||||
|
</MultiTrigger.Conditions>
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||||
|
</MultiTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.ListView.Borderless" TargetType="{x:Type ListView}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||||
|
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
|
||||||
|
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
|
||||||
|
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="ItemContainerStyle" Value="{StaticResource Style.ListViewItem.Borderless}"/>
|
||||||
|
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="ItemsPanel">
|
||||||
|
<Setter.Value>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ListView}">
|
||||||
|
<Border Name="Border" BorderThickness="0" Background="{TemplateBinding Background}">
|
||||||
|
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
|
||||||
|
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
|
||||||
|
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}">
|
||||||
|
<ItemsPresenter/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsGrouping" Value="true">
|
||||||
|
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
21
SourceGit/Resources/Styles/Path.xaml
Normal file
21
SourceGit/Resources/Styles/Path.xaml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style x:Key="Style.Icon" TargetType="{x:Type Path}">
|
||||||
|
<Setter Property="Width" Value="16"/>
|
||||||
|
<Setter Property="Height" Value="16"/>
|
||||||
|
<Setter Property="Stretch" Value="Uniform"/>
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="RenderOptions.BitmapScalingMode" Value="HighQuality"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.WindowControlIcon" TargetType="{x:Type Path}">
|
||||||
|
<Setter Property="Stretch" Value="None"/>
|
||||||
|
<Setter Property="Fill" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="RenderOptions.BitmapScalingMode" Value="HighQuality"/>
|
||||||
|
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
52
SourceGit/Resources/Styles/RadioButton.xaml
Normal file
52
SourceGit/Resources/Styles/RadioButton.xaml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style TargetType="{x:Type RadioButton}">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type RadioButton}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Column="0">
|
||||||
|
<Path
|
||||||
|
x:Name="Border"
|
||||||
|
Width="14" Height="14"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Fill="Transparent"
|
||||||
|
Stroke="{DynamicResource Brush.Border1}" StrokeThickness="1"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Data="M 0,0 A 180,180 180 1 1 1,1 Z"/>
|
||||||
|
<Path
|
||||||
|
x:Name="Dot"
|
||||||
|
Width="10" Height="10"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Fill="{DynamicResource Brush.Accent1}"
|
||||||
|
Stroke="Transparent" StrokeThickness="1"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Data="M 0,0 A 180,180 180 1 1 1,1 Z"
|
||||||
|
Visibility="Collapsed"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Column="1" Margin="4,0">
|
||||||
|
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center" RecognizesAccessKey="True"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="Dot" Property="Visibility" Value="Visible"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Stroke" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
98
SourceGit/Resources/Styles/ScrollBar.xaml
Normal file
98
SourceGit/Resources/Styles/ScrollBar.xaml
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<Style x:Key="Style.ScrollBar.RepeatPage" TargetType="{x:Type RepeatButton}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type RepeatButton}">
|
||||||
|
<Border x:Name="area" Background="Transparent" />
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="area" Property="Background" Value="{DynamicResource Brush.FG}" />
|
||||||
|
<Setter TargetName="area" Property="Opacity" Value=".08"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.ScrollBar.Thumb" TargetType="{x:Type Thumb}">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Brush.Border1}" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Focusable" Value="false" />
|
||||||
|
<Setter Property="IsTabStop" Value="false" />
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||||
|
<Border x:Name="Border" Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
Opacity=".6"/>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Opacity" Value="1" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="Template.ScrollBar.Horizontal" TargetType="{x:Type ScrollBar}">
|
||||||
|
<Grid>
|
||||||
|
<Track Name="PART_Track" Grid.Column="1">
|
||||||
|
<Track.DecreaseRepeatButton>
|
||||||
|
<RepeatButton Command="ScrollBar.PageLeftCommand" Style="{StaticResource Style.ScrollBar.RepeatPage}" />
|
||||||
|
</Track.DecreaseRepeatButton>
|
||||||
|
<Track.Thumb>
|
||||||
|
<Thumb Style="{StaticResource Style.ScrollBar.Thumb}" />
|
||||||
|
</Track.Thumb>
|
||||||
|
<Track.IncreaseRepeatButton>
|
||||||
|
<RepeatButton Command="ScrollBar.PageRightCommand" Style="{StaticResource Style.ScrollBar.RepeatPage}" />
|
||||||
|
</Track.IncreaseRepeatButton>
|
||||||
|
</Track>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="Template.ScrollBar.Vertical" TargetType="{x:Type ScrollBar}">
|
||||||
|
<Grid>
|
||||||
|
<Track Name="PART_Track"
|
||||||
|
Grid.Row="1"
|
||||||
|
IsDirectionReversed="true">
|
||||||
|
<Track.DecreaseRepeatButton>
|
||||||
|
<RepeatButton Command="ScrollBar.PageUpCommand" Style="{StaticResource Style.ScrollBar.RepeatPage}" />
|
||||||
|
</Track.DecreaseRepeatButton>
|
||||||
|
<Track.Thumb>
|
||||||
|
<Thumb Style="{StaticResource Style.ScrollBar.Thumb}"/>
|
||||||
|
</Track.Thumb>
|
||||||
|
<Track.IncreaseRepeatButton>
|
||||||
|
<RepeatButton Command="ScrollBar.PageDownCommand" Style="{StaticResource Style.ScrollBar.RepeatPage}" />
|
||||||
|
</Track.IncreaseRepeatButton>
|
||||||
|
</Track>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type ScrollBar}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="Orientation" Value="Vertical">
|
||||||
|
<Setter Property="Height" Value="Auto" />
|
||||||
|
<Setter Property="Template" Value="{StaticResource Template.ScrollBar.Vertical}" />
|
||||||
|
<Setter Property="Width" Value="8" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="Orientation" Value="Horizontal">
|
||||||
|
<Setter Property="Height" Value="8" />
|
||||||
|
<Setter Property="Template" Value="{StaticResource Template.ScrollBar.Horizontal}" />
|
||||||
|
<Setter Property="Width" Value="Auto" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
54
SourceGit/Resources/Styles/ScrollViewer.xaml
Normal file
54
SourceGit/Resources/Styles/ScrollViewer.xaml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style TargetType="{x:Type ScrollViewer}">
|
||||||
|
<Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
|
||||||
|
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border1}"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ScrollViewer}">
|
||||||
|
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
|
||||||
|
<Grid Background="{TemplateBinding Background}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<ScrollContentPresenter
|
||||||
|
Cursor="{TemplateBinding Cursor}"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
ContentTemplate="{TemplateBinding ContentTemplate}"/>
|
||||||
|
|
||||||
|
<ScrollBar x:Name="PART_VerticalScrollBar"
|
||||||
|
IsTabStop="False"
|
||||||
|
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
|
||||||
|
Grid.Column="1" Grid.Row="0" Orientation="Vertical"
|
||||||
|
ViewportSize="{TemplateBinding ViewportHeight}"
|
||||||
|
Maximum="{TemplateBinding ScrollableHeight}"
|
||||||
|
Minimum="0"
|
||||||
|
Value="{TemplateBinding VerticalOffset}"
|
||||||
|
Margin="0,-1,-1,-1"/>
|
||||||
|
|
||||||
|
<ScrollBar x:Name="PART_HorizontalScrollBar"
|
||||||
|
IsTabStop="False"
|
||||||
|
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
|
||||||
|
Grid.Column="0" Grid.Row="1" Orientation="Horizontal"
|
||||||
|
ViewportSize="{TemplateBinding ViewportWidth}"
|
||||||
|
Maximum="{TemplateBinding ScrollableWidth}"
|
||||||
|
Minimum="0"
|
||||||
|
Value="{TemplateBinding HorizontalOffset}"
|
||||||
|
Margin="-1,0,-1,-1"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
62
SourceGit/Resources/Styles/TabControl.xaml
Normal file
62
SourceGit/Resources/Styles/TabControl.xaml
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style TargetType="{x:Type TabControl}">
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type TabControl}">
|
||||||
|
<Grid KeyboardNavigation.TabNavigation="Local">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TabPanel x:Name="HeaderPanel" Grid.Row="0" IsItemsHost="True" KeyboardNavigation.TabIndex="1" Background="Transparent" />
|
||||||
|
<Border
|
||||||
|
x:Name="Border"
|
||||||
|
Grid.Row="1"
|
||||||
|
Background="Transparent"
|
||||||
|
KeyboardNavigation.TabNavigation="Local"
|
||||||
|
KeyboardNavigation.DirectionalNavigation="Contained"
|
||||||
|
KeyboardNavigation.TabIndex="2">
|
||||||
|
<ContentPresenter x:Name="PART_SelectedContentHost" Margin="4" ContentSource="SelectedContent" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type TabItem}">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||||
|
<Border x:Name="Border" Margin="0" BorderThickness="0,0,0,1.1" BorderBrush="Transparent" Opacity=".7">
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="ContentSite"
|
||||||
|
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
|
TextElement.Foreground="{DynamicResource Brush.FG}"
|
||||||
|
TextElement.FontWeight="Bold"
|
||||||
|
ContentSource="Header"
|
||||||
|
Margin="8,6"
|
||||||
|
RecognizesAccessKey="True" />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
<Setter TargetName="Border" Property="Opacity" Value="1"/>
|
||||||
|
<Setter TargetName="ContentSite" Property="TextElement.Foreground" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
<MultiTrigger>
|
||||||
|
<MultiTrigger.Conditions>
|
||||||
|
<Condition Property="IsSelected" Value="False"/>
|
||||||
|
<Condition Property="IsMouseOver" Value="True"/>
|
||||||
|
</MultiTrigger.Conditions>
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent2}"/>
|
||||||
|
</MultiTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
126
SourceGit/Resources/Styles/TextBox.xaml
Normal file
126
SourceGit/Resources/Styles/TextBox.xaml
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers">
|
||||||
|
|
||||||
|
<!-- 错误Tooltip -->
|
||||||
|
<ControlTemplate x:Key="Template.Validation.Tooltip" TargetType="{x:Type ToolTip}">
|
||||||
|
<Border x:Name="Root" Margin="5,0,0,0" Opacity="0" Padding="0,0,20,20" RenderTransformOrigin="0,0">
|
||||||
|
<Border.RenderTransform>
|
||||||
|
<TranslateTransform x:Name="xform" X="-25" />
|
||||||
|
</Border.RenderTransform>
|
||||||
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
<VisualStateGroup x:Name="OpenStates">
|
||||||
|
<VisualStateGroup.Transitions>
|
||||||
|
<VisualTransition GeneratedDuration="0" />
|
||||||
|
<VisualTransition GeneratedDuration="0:0:0.2" To="Open">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform">
|
||||||
|
<DoubleAnimation.EasingFunction>
|
||||||
|
<BackEase Amplitude=".3" EasingMode="EaseOut" />
|
||||||
|
</DoubleAnimation.EasingFunction>
|
||||||
|
</DoubleAnimation>
|
||||||
|
<DoubleAnimation Duration="0:0:0.2" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" />
|
||||||
|
</Storyboard>
|
||||||
|
</VisualTransition>
|
||||||
|
</VisualStateGroup.Transitions>
|
||||||
|
<VisualState x:Name="Closed">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" />
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="Open">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform" />
|
||||||
|
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" />
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateManager.VisualStateGroups>
|
||||||
|
<FrameworkElement.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="11" ShadowDepth="6" Opacity="0.4" />
|
||||||
|
</FrameworkElement.Effect>
|
||||||
|
<Border Background="#FFDC000C" BorderThickness="1" BorderBrush="#FFBC000C">
|
||||||
|
<TextBlock Foreground="White" MaxWidth="250" Margin="8,4,8,4" TextWrapping="Wrap" Text="{Binding [0].ErrorContent}" UseLayoutRounding="false" />
|
||||||
|
</Border>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<!-- 验证错误模板 -->
|
||||||
|
<ControlTemplate x:Key="Template.Validation.Error">
|
||||||
|
<AdornedElementPlaceholder x:Name="Target">
|
||||||
|
<Border BorderBrush="#FFDB000C" BorderThickness="1" x:Name="root">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<ToolTip x:Name="validationTooltip"
|
||||||
|
Placement="Right"
|
||||||
|
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
|
Template="{StaticResource Template.Validation.Tooltip}"
|
||||||
|
Style="{x:Null}"/>
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
<Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Width="12" Margin="1,-4,-4,0" VerticalAlignment="Top">
|
||||||
|
<Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</AdornedElementPlaceholder>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding ElementName=Target, Path=AdornedElement.IsKeyboardFocusWithin, Mode=OneWay}" Value="True" />
|
||||||
|
<Condition Binding="{Binding ElementName=Target, Path=AdornedElement.(Validation.HasError), Mode=OneWay}" Value="True" />
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="validationTooltip" Property="IsOpen" Value="True"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<!-- 修改默认 -->
|
||||||
|
<Style TargetType="{x:Type TextBox}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="CaretBrush" Value="{DynamicResource Brush.FG}"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border1}"/>
|
||||||
|
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource Template.Validation.Error}"/>
|
||||||
|
<Setter Property="helpers:TextBoxHelper.AutoScroll" Value="True"/>
|
||||||
|
<Setter Property="ContextMenu">
|
||||||
|
<Setter.Value>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Command="ApplicationCommands.Copy" />
|
||||||
|
<MenuItem Command="ApplicationCommands.Cut" />
|
||||||
|
<MenuItem Command="ApplicationCommands.Paste" />
|
||||||
|
</ContextMenu>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type TextBox}">
|
||||||
|
<Border x:Name="Border"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}">
|
||||||
|
<ScrollViewer x:Name="PART_ContentHost"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
IsTabStop="False"
|
||||||
|
CanContentScroll="False"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="true">
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="AcceptsReturn" Value="True">
|
||||||
|
<Setter TargetName="PART_ContentHost" Property="VerticalAlignment" Value="Top"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
111
SourceGit/Resources/Styles/ToggleButton.xaml
Normal file
111
SourceGit/Resources/Styles/ToggleButton.xaml
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style x:Key="Style.ToggleButton.Expender" TargetType="{x:Type ToggleButton}">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||||
|
<Grid Background="Transparent">
|
||||||
|
<ContentPresenter/>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.ToggleButton.Filter" TargetType="{x:Type ToggleButton}">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||||
|
<Grid>
|
||||||
|
<Path
|
||||||
|
x:Name="Icon"
|
||||||
|
Height="12"
|
||||||
|
Style="{DynamicResource Style.Icon}"
|
||||||
|
Fill="Transparent"
|
||||||
|
Stroke="{DynamicResource Brush.FG2}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Data="{DynamicResource Icon.Filter}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="Icon" Property="Fill" Value="{DynamicResource Brush.FG2}"/>
|
||||||
|
</Trigger>
|
||||||
|
<MultiTrigger>
|
||||||
|
<MultiTrigger.Conditions>
|
||||||
|
<Condition Property="IsChecked" Value="False"/>
|
||||||
|
<Condition Property="IsMouseOver" Value="True"/>
|
||||||
|
</MultiTrigger.Conditions>
|
||||||
|
<Setter TargetName="Icon" Property="Fill" Value="{DynamicResource Brush.FG2}"/>
|
||||||
|
</MultiTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.ToggleButton.Orientation" TargetType="{x:Type ToggleButton}">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||||
|
<Grid Background="Transparent">
|
||||||
|
<Path
|
||||||
|
x:Name="Icon"
|
||||||
|
Width="18"
|
||||||
|
Height="18"
|
||||||
|
Style="{DynamicResource Style.Icon}"
|
||||||
|
Fill="{DynamicResource Brush.Border1}"
|
||||||
|
Data="{DynamicResource Icon.Horizontal}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="Icon" Property="Data" Value="{DynamicResource Icon.Vertical}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Icon" Property="Fill" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.ToggleButton.ListOrTree" TargetType="{x:Type ToggleButton}">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||||
|
<Grid Background="Transparent">
|
||||||
|
<Path
|
||||||
|
x:Name="Icon"
|
||||||
|
Height="12"
|
||||||
|
Width="12"
|
||||||
|
Style="{DynamicResource Style.Icon}"
|
||||||
|
Fill="Transparent"
|
||||||
|
Stroke="{DynamicResource Brush.FG}"
|
||||||
|
StrokeThickness=".4"
|
||||||
|
Data="{DynamicResource Icon.Tree}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="Icon" Property="Data" Value="{DynamicResource Icon.List}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
21
SourceGit/Resources/Styles/Tooltip.xaml
Normal file
21
SourceGit/Resources/Styles/Tooltip.xaml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style TargetType="{x:Type ToolTip}">
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
<Setter Property="HasDropShadow" Value="False"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ToolTip}">
|
||||||
|
<Border
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource Brush.Border1}"
|
||||||
|
Background="{DynamicResource Brush.BG1}"
|
||||||
|
Width="Auto"
|
||||||
|
Height="Auto">
|
||||||
|
<ContentPresenter Margin="6,4" TextElement.Foreground="{DynamicResource Brush.FG}" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
201
SourceGit/Resources/Styles/TreeView.xaml
Normal file
201
SourceGit/Resources/Styles/TreeView.xaml
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers">
|
||||||
|
|
||||||
|
<converters:TreeViewItemDepthToMargin x:Key="Converter.TreeViewItemIndent" Indent="19"/>
|
||||||
|
|
||||||
|
<Style x:Key="Style.TreeView.ToggleButton" TargetType="{x:Type ToggleButton}">
|
||||||
|
<Setter Property="Focusable" Value="False"/>
|
||||||
|
<Setter Property="Width" Value="16" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ToggleButton">
|
||||||
|
<Grid Width="16" Height="16" Margin="1" Background="Transparent">
|
||||||
|
<Path x:Name="ExpandPath" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="1,1,1,1" Fill="{DynamicResource Brush.FG}" Data="M 4 0 L 8 4 L 4 8 Z"/>
|
||||||
|
</Grid>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter Property="Data" TargetName="ExpandPath" Value="M 0 4 L 8 4 L 4 8 Z"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.TreeView.ItemContainerStyle" TargetType="{x:Type TreeViewItem}">
|
||||||
|
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, Mode=OneWay, FallbackValue=Stretch, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, Mode=OneWay, FallbackValue=Center, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type TreeViewItem}">
|
||||||
|
<StackPanel>
|
||||||
|
<Border
|
||||||
|
x:Name="BG"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
|
||||||
|
<Grid
|
||||||
|
Margin="{Binding Converter={StaticResource Converter.TreeViewItemIndent}, RelativeSource={x:Static RelativeSource.TemplatedParent}}"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Transparent">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ToggleButton
|
||||||
|
Grid.Column="0"
|
||||||
|
x:Name="Expander"
|
||||||
|
Style="{StaticResource Style.TreeView.ToggleButton}"
|
||||||
|
IsChecked="{Binding Path=IsExpanded, RelativeSource={x:Static RelativeSource.TemplatedParent}, Mode=TwoWay}"
|
||||||
|
ClickMode="Press"/>
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="PART_Header"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
ContentSource="Header"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<ItemsPresenter x:Name="ItemsHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsExpanded" Value="False">
|
||||||
|
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="HasItems" Value="False">
|
||||||
|
<Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
<MultiTrigger>
|
||||||
|
<MultiTrigger.Conditions>
|
||||||
|
<Condition SourceName="BG" Property="IsMouseOver" Value="True"/>
|
||||||
|
<Condition Property="IsSelected" Value="False"/>
|
||||||
|
</MultiTrigger.Conditions>
|
||||||
|
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||||
|
</MultiTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.TreeView.MultiSelectionItemContainerStyle" TargetType="{x:Type TreeViewItem}">
|
||||||
|
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, Mode=OneWay, FallbackValue=Stretch, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, Mode=OneWay, FallbackValue=Center, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type TreeViewItem}">
|
||||||
|
<StackPanel>
|
||||||
|
<Border
|
||||||
|
x:Name="BG"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
|
||||||
|
<Grid
|
||||||
|
Margin="{Binding Converter={StaticResource Converter.TreeViewItemIndent}, RelativeSource={x:Static RelativeSource.TemplatedParent}}"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Transparent">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ToggleButton
|
||||||
|
Grid.Column="0"
|
||||||
|
x:Name="Expander"
|
||||||
|
Style="{StaticResource Style.TreeView.ToggleButton}"
|
||||||
|
IsChecked="{Binding Path=IsExpanded, RelativeSource={x:Static RelativeSource.TemplatedParent}, Mode=TwoWay}"
|
||||||
|
ClickMode="Press"/>
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="PART_Header"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
ContentSource="Header"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<ItemsPresenter x:Name="ItemsHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsExpanded" Value="False">
|
||||||
|
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="HasItems" Value="False">
|
||||||
|
<Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="helpers:TreeViewHelper.IsChecked" Value="True">
|
||||||
|
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||||
|
</Trigger>
|
||||||
|
<MultiTrigger>
|
||||||
|
<MultiTrigger.Conditions>
|
||||||
|
<Condition SourceName="BG" Property="IsMouseOver" Value="True"/>
|
||||||
|
<Condition Property="helpers:TreeViewHelper.IsChecked" Value="False"/>
|
||||||
|
</MultiTrigger.Conditions>
|
||||||
|
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||||
|
</MultiTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type TreeView}">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||||
|
<Setter Property="ItemContainerStyle" Value="{StaticResource Style.TreeView.ItemContainerStyle}"/>
|
||||||
|
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True" />
|
||||||
|
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Standard" />
|
||||||
|
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="ItemsPanel">
|
||||||
|
<Setter.Value>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel IsItemsHost="True"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type TreeView}">
|
||||||
|
<Border Name="Border"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderThickness="0"
|
||||||
|
CornerRadius="0"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
|
||||||
|
<ScrollViewer Padding="{TemplateBinding Padding}"
|
||||||
|
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
|
||||||
|
Focusable="False"
|
||||||
|
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
|
||||||
|
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
|
||||||
|
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Opacity" Value=".5"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
17
SourceGit/Resources/Themes/Dark.xaml
Normal file
17
SourceGit/Resources/Themes/Dark.xaml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<SolidColorBrush x:Key="Brush.BG1" Color="#FF252525"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG2" Color="#FF1B1B1B"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG3" Color="#FF202020"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG4" Color="#FF303030"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG5" Color="#FF505050"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG6" Color="#FF404040"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Border1" Color="#FF7C7C7C"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Border2" Color="#FF404040"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.FG" Color="#FFF1F1F1"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.FG2" Color="#40F1F1F1"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Badge" Color="#FF8F8F8F"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Accent1" Color="#FF007ACC"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Accent2" Color="#4C007ACC"/>
|
||||||
|
</ResourceDictionary>
|
16
SourceGit/Resources/Themes/Light.xaml
Normal file
16
SourceGit/Resources/Themes/Light.xaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<SolidColorBrush x:Key="Brush.BG1" Color="#FFEEEEF2"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG2" Color="White"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG3" Color="WhiteSmoke"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG4" Color="#FFE6E7E8"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG5" Color="#FFBDBDBD"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.BG6" Color="#FFCFCFCF"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Border1" Color="#FF898989"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Border2" Color="#FFCFCFCF"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.FG" Color="#FF1F1F1F"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.FG2" Color="DarkGray"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Badge" Color="#FF8F8F8F"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Accent1" Color="#FF4295FF"/>
|
||||||
|
<SolidColorBrush x:Key="Brush.Accent2" Color="#4C007ACC"/>
|
||||||
|
</ResourceDictionary>
|
22
SourceGit/SourceGit.csproj
Normal file
22
SourceGit/SourceGit.csproj
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net46</TargetFramework>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<ApplicationIcon>App.ico</ApplicationIcon>
|
||||||
|
<Company>sourcegit</Company>
|
||||||
|
<Description>OpenSource GIT client for Windows</Description>
|
||||||
|
<Copyright>Copyright © sourcegit 2020. All rights reserved.</Copyright>
|
||||||
|
<ApplicationManifest>App.manifest</ApplicationManifest>
|
||||||
|
<Version>1.5</Version>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<Prefer32Bit>true</Prefer32Bit>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="App.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
82
SourceGit/UI/About.xaml
Normal file
82
SourceGit/UI/About.xaml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<Window x:Class="SourceGit.UI.About"
|
||||||
|
x:Name="me"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Height="280" Width="400"
|
||||||
|
Title="About"
|
||||||
|
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
|
||||||
|
|
||||||
|
<!-- Enable WindowChrome Feature -->
|
||||||
|
<WindowChrome.WindowChrome>
|
||||||
|
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
|
||||||
|
</WindowChrome.WindowChrome>
|
||||||
|
|
||||||
|
<!-- Window Layout -->
|
||||||
|
<Border Background="{StaticResource Brush.BG1}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Titlebar -->
|
||||||
|
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- LOGO -->
|
||||||
|
<Path Width="20" Height="20" Margin="6,-1,2,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Info}"/>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<Label Grid.Column="1" Content="ABOUT" FontWeight="Light"/>
|
||||||
|
|
||||||
|
<!-- Close Button -->
|
||||||
|
<Button Click="Quit" Width="32" Grid.Column="3" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="Red"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
|
||||||
|
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="90"/>
|
||||||
|
<RowDefinition Height="40"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,6,0,0">
|
||||||
|
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}" Fill="#FFF05133"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Label Grid.Row="1" Content="SourceGit - OPEN SOURCE GIT CLIENT" HorizontalContentAlignment="Center" VerticalContentAlignment="Bottom" FontSize="18" FontWeight="Bold"/>
|
||||||
|
<Label Grid.Row="2" Content="{Binding ElementName=me, Path=Version}" HorizontalContentAlignment="Center" FontSize="11"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="3" HorizontalContentAlignment="Center" FontSize="11">
|
||||||
|
<Hyperlink RequestNavigate="OpenSource" NavigateUri="https://gitee.com/sourcegit/SourceGit.git">
|
||||||
|
<Run Text="https://gitee.com/sourcegit/SourceGit.git"/>
|
||||||
|
</Hyperlink>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Label Grid.Row="4" Content="Copyright © sourcegit 2020. All rights reserved." HorizontalContentAlignment="Center" FontSize="11"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
46
SourceGit/UI/About.xaml.cs
Normal file
46
SourceGit/UI/About.xaml.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// About dialog
|
||||||
|
/// </summary>
|
||||||
|
public partial class About : Window {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current app version
|
||||||
|
/// </summary>
|
||||||
|
public string Version {
|
||||||
|
get {
|
||||||
|
return "VERSION : " + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public About() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Open source code link
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void OpenSource(object sender, RequestNavigateEventArgs e) {
|
||||||
|
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close this dialog
|
||||||
|
/// </summary>
|
||||||
|
private void Quit(object sender, RoutedEventArgs e) {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
72
SourceGit/UI/AddSubmodule.xaml
Normal file
72
SourceGit/UI/AddSubmodule.xaml
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.AddSubmodule"
|
||||||
|
x:Name="me"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="192" d:DesignWidth="500" Height="192" Width="500">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Add Submodule"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="URL :"/>
|
||||||
|
<TextBox x:Name="txtRepoUrl" Grid.Row="2" Grid.Column="1"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Git Repository URL">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding Path="RepoURL" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<helpers:RemoteUriRule/>
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Parent Folder :"/>
|
||||||
|
<TextBox Grid.Row="3" Grid.Column="1"
|
||||||
|
x:Name="txtPath"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Relative foler to store this module. Optional.">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding Path="LocalPath" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<helpers:SubmodulePathRequiredRule/>
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="4" Grid.Column="1"
|
||||||
|
x:Name="chkRecursive"
|
||||||
|
IsChecked="True"
|
||||||
|
Content="Fetch nested submodules"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="6" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
75
SourceGit/UI/AddSubmodule.xaml.cs
Normal file
75
SourceGit/UI/AddSubmodule.xaml.cs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dialog to add new submodule.
|
||||||
|
/// </summary>
|
||||||
|
public partial class AddSubmodule : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Submodule's repository URL.
|
||||||
|
/// </summary>
|
||||||
|
public string RepoURL { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Submodule's relative path.
|
||||||
|
/// </summary>
|
||||||
|
public string LocalPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened"></param>
|
||||||
|
public AddSubmodule(Git.Repository opened) {
|
||||||
|
repo = opened;
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show this dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
88
SourceGit/UI/Apply.xaml
Normal file
88
SourceGit/UI/Apply.xaml
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.Apply"
|
||||||
|
x:Name="me"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||||
|
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Height="192" Width="500">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.Resources>
|
||||||
|
<converters:InverseBool x:Key="InverseBool"/>
|
||||||
|
</Grid.Resources>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Apply Patch"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Patch File :"/>
|
||||||
|
<Grid Grid.Row="2" Grid.Column="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="28"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBox
|
||||||
|
Grid.Column="0"
|
||||||
|
x:Name="txtPatchFile"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Select .patch file to apply">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding Path="PatchFile" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<helpers:PatchFileRequiredRule/>
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Width="24" Height="24" Click="FindPatchFile" Padding="0" BorderThickness="1" Style="{StaticResource Style.Button.Bordered}">
|
||||||
|
<Path Width="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Whitespace :"/>
|
||||||
|
<ComboBox x:Name="combWhitespaceOptions" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" IsEnabled="{Binding ElementName=chkIgnoreWhitespace, Path=IsChecked, Converter={StaticResource InverseBool}}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Height="20">
|
||||||
|
<Label Content="{Binding Name}" Padding="4,0"/>
|
||||||
|
<Label Content="{Binding Desc}" Foreground="{StaticResource Brush.FG2}" FontSize="11" Padding="4,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="4" Grid.Column="1"
|
||||||
|
x:Name="chkIgnoreWhitespace"
|
||||||
|
IsChecked="True"
|
||||||
|
Content="Ignore whitespace changes"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="6" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
105
SourceGit/UI/Apply.xaml.cs
Normal file
105
SourceGit/UI/Apply.xaml.cs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply patch dialog
|
||||||
|
/// </summary>
|
||||||
|
public partial class Apply : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whitespace option.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path of file to be patched.
|
||||||
|
/// </summary>
|
||||||
|
public string PatchFile { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show this dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened"></param>
|
||||||
|
public static void Show(Git.Repository opened) {
|
||||||
|
var popup = App.GetPopupManager(opened);
|
||||||
|
popup?.Show(new Apply(opened));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Open file browser dialog for select a file to patch.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start apply selected path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Cancel(object sender, RoutedEventArgs e) {
|
||||||
|
App.GetPopupManager(repo)?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
208
SourceGit/UI/Blame.xaml
Normal file
208
SourceGit/UI/Blame.xaml
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
<Window x:Class="SourceGit.UI.Blame"
|
||||||
|
x:Name="me"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Blame"
|
||||||
|
Height="600" Width="800">
|
||||||
|
|
||||||
|
<!-- Enable WindowChrome -->
|
||||||
|
<WindowChrome.WindowChrome>
|
||||||
|
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
|
||||||
|
</WindowChrome.WindowChrome>
|
||||||
|
|
||||||
|
<!-- Window Content -->
|
||||||
|
<Border Background="{StaticResource Brush.BG1}">
|
||||||
|
<!-- Fix Maximize BUG -->
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="{x:Type Border}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
|
||||||
|
<Setter Property="Margin" Value="6"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
|
||||||
|
<Setter Property="Margin" Value="0"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Title bar -->
|
||||||
|
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Logo & TITLE -->
|
||||||
|
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||||
|
<Path
|
||||||
|
Width="20" Height="20" Margin="6,-1,2,0"
|
||||||
|
Style="{StaticResource Style.Icon}"
|
||||||
|
Data="{StaticResource Icon.Git}"
|
||||||
|
Fill="#FFF05133"
|
||||||
|
WindowChrome.IsHitTestVisibleInChrome="True"
|
||||||
|
MouseLeftButtonDown="LogoMouseButtonDown"/>
|
||||||
|
<Label Content="SOURCE GIT - BLAME" FontWeight="Light"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Options -->
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||||
|
<Button Click="Minimize" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
|
||||||
|
<Path Style="{StaticResource Style.WindowControlIcon}" Data="{StaticResource Icon.Minimize}"/>
|
||||||
|
</Button>
|
||||||
|
<Button Click="MaximizeOrRestore" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
|
||||||
|
<Path>
|
||||||
|
<Path.Style>
|
||||||
|
<Style TargetType="{x:Type Path}" BasedOn="{StaticResource Style.WindowControlIcon}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
|
||||||
|
<Setter Property="Data" Value="{StaticResource Icon.Restore}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
|
||||||
|
<Setter Property="Data" Value="{StaticResource Icon.Maximize}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
</Path>
|
||||||
|
</Button>
|
||||||
|
<Button Click="Quit" Width="32">
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="Red"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
|
||||||
|
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Blame file -->
|
||||||
|
<Border Grid.Row="1" Padding="2,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Column="0" x:Name="blameFile" HorizontalAlignment="Left" FontSize="11" Foreground="{StaticResource Brush.FG2}" FontFamily="Consolas"/>
|
||||||
|
<Label Grid.Column="1" HorizontalAlignment="Right" Foreground="{StaticResource Brush.FG2}" FontSize="11" Content="Use right mouse button to view commit information."/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<Border Grid.Row="2" BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}" ClipToBounds="True">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="1"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBox
|
||||||
|
x:Name="lineNumber"
|
||||||
|
Grid.Column="0"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
AcceptsTab="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="Transparent"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Margin="4,0,4,0"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalContentAlignment="Right"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
FontFamily="Consolas"/>
|
||||||
|
|
||||||
|
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
||||||
|
|
||||||
|
<RichTextBox
|
||||||
|
x:Name="content"
|
||||||
|
Grid.Column="2"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
AcceptsTab="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="Transparent"
|
||||||
|
Foreground="{StaticResource Brush.FG}"
|
||||||
|
Height="Auto"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
RenderOptions.ClearTypeHint="Enabled"
|
||||||
|
ScrollViewer.ScrollChanged="SyncScrollChanged"
|
||||||
|
PreviewMouseWheel="MouseWheelOnContent"
|
||||||
|
SizeChanged="ContentSizeChanged"
|
||||||
|
SelectionChanged="ContentSelectionChanged"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
FontFamily="Consolas">
|
||||||
|
<RichTextBox.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Command="ApplicationCommands.Copy"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</RichTextBox.ContextMenu>
|
||||||
|
<FlowDocument PageWidth="0"/>
|
||||||
|
</RichTextBox>
|
||||||
|
|
||||||
|
<!-- Loading tip -->
|
||||||
|
<Path x:Name="loading" Grid.ColumnSpan="5" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||||
|
<Path.RenderTransform>
|
||||||
|
<RotateTransform Angle="0"/>
|
||||||
|
</Path.RenderTransform>
|
||||||
|
|
||||||
|
<Path.Style>
|
||||||
|
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
|
||||||
|
<Setter Property="Width" Value="48"/>
|
||||||
|
<Setter Property="Height" Value="48"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
</Path>
|
||||||
|
|
||||||
|
<!-- Popup to show commit info -->
|
||||||
|
<Popup x:Name="popup" Grid.ColumnSpan="5" Placement="MousePoint" IsOpen="False" StaysOpen="False" Focusable="True">
|
||||||
|
<Border BorderBrush="{StaticResource Brush.Accent1}" BorderThickness="1" Background="{StaticResource Brush.BG1}">
|
||||||
|
<Grid Margin="4">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.Column="0" Content="COMMIT SHA" Foreground="{StaticResource Brush.FG2}"/>
|
||||||
|
<Label Grid.Row="0" Grid.Column="1" x:Name="commitID"/>
|
||||||
|
<Label Grid.Row="1" Grid.Column="0" Content="AUTHOR" Foreground="{StaticResource Brush.FG2}"/>
|
||||||
|
<Label Grid.Row="1" Grid.Column="1" x:Name="authorName"/>
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" Content="MODIFY TIME" Foreground="{StaticResource Brush.FG2}"/>
|
||||||
|
<Label Grid.Row="2" Grid.Column="1" x:Name="authorTime"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Popup>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
240
SourceGit/UI/Blame.xaml.cs
Normal file
240
SourceGit/UI/Blame.xaml.cs
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Viewer to show git-blame
|
||||||
|
/// </summary>
|
||||||
|
public partial class Blame : Window {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Background color for blocks.
|
||||||
|
/// </summary>
|
||||||
|
public static Brush[] BG = new Brush[] {
|
||||||
|
Brushes.Transparent,
|
||||||
|
new SolidColorBrush(Color.FromArgb(128, 0, 0, 0))
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <param name="revision"></param>
|
||||||
|
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<string> numbers = new List<string>();
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Click logo
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimize
|
||||||
|
/// </summary>
|
||||||
|
private void Minimize(object sender, RoutedEventArgs e) {
|
||||||
|
SystemCommands.MinimizeWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximize/Restore
|
||||||
|
/// </summary>
|
||||||
|
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
|
||||||
|
if (WindowState == WindowState.Normal) {
|
||||||
|
SystemCommands.MaximizeWindow(this);
|
||||||
|
} else {
|
||||||
|
SystemCommands.RestoreWindow(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quit
|
||||||
|
/// </summary>
|
||||||
|
private void Quit(object sender, RoutedEventArgs e) {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sync scroll
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void SyncScrollChanged(object sender, ScrollChangedEventArgs e) {
|
||||||
|
if (e.VerticalChange != 0) {
|
||||||
|
var margin = new Thickness(4, -e.VerticalOffset, 4, 0);
|
||||||
|
lineNumber.Margin = margin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mouse wheel
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void MouseWheelOnContent(object sender, MouseWheelEventArgs e) {
|
||||||
|
if (e.Delta > 0) {
|
||||||
|
content.LineUp();
|
||||||
|
} else {
|
||||||
|
content.LineDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Content size changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void ContentSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||||
|
if (content.Document.PageWidth < content.ActualWidth) {
|
||||||
|
content.Document.PageWidth = content.ActualWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto scroll when selection changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
SourceGit/UI/CherryPick.xaml
Normal file
45
SourceGit/UI/CherryPick.xaml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.CherryPick"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Cherry Pick"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Commit :"/>
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}" Margin="4,0"/>
|
||||||
|
<Label x:Name="desc"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="chkCommitChanges" IsChecked="True" Content="Commit the changes"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="5" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
58
SourceGit/UI/CherryPick.xaml.cs
Normal file
58
SourceGit/UI/CherryPick.xaml.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cherry pick commit dialog.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CherryPick : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private string commitSHA = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened"></param>
|
||||||
|
/// <param name="commit"></param>
|
||||||
|
public CherryPick(Git.Repository opened, Git.Commit commit) {
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
repo = opened;
|
||||||
|
commitSHA = commit.SHA;
|
||||||
|
desc.Content = $"{commit.ShortSHA} {commit.Subject}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display this dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="commit"></param>
|
||||||
|
public static void Show(Git.Repository repo, Git.Commit commit) {
|
||||||
|
var popup = App.GetPopupManager(repo);
|
||||||
|
popup?.Show(new CherryPick(repo, commit));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start pick.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Start(object sender, RoutedEventArgs e) {
|
||||||
|
repo.CherryPick(commitSHA, chkCommitChanges.IsChecked != true);
|
||||||
|
|
||||||
|
var popup = App.GetPopupManager(repo);
|
||||||
|
popup?.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Cancel(object sender, RoutedEventArgs e) {
|
||||||
|
var popup = App.GetPopupManager(repo);
|
||||||
|
popup?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
SourceGit/UI/Clone.xaml
Normal file
95
SourceGit/UI/Clone.xaml
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.Clone"
|
||||||
|
x:Name="me"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Width="500" Height="224">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Clone Remote Repository"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Repository URL :"/>
|
||||||
|
<TextBox x:Name="txtUrl" Grid.Row="2" Grid.Column="1"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Git Repository URL">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding Path="RemoteUri" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<helpers:RemoteUriRule/>
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Parent Folder :"/>
|
||||||
|
<Grid Grid.Row="3" Grid.Column="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="28"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBox Grid.Column="0"
|
||||||
|
x:Name="txtParentFolder"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Folder to contain this repository">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding Path="ParentFolder" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<helpers:CloneFolderRule/>
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
<Button Grid.Column="1" Width="24" Height="24" Padding="0" BorderThickness="1" Click="SelectParentFolder" Style="{StaticResource Style.Button.Bordered}">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" Content="Local Name :"/>
|
||||||
|
<TextBox Grid.Row="4" Grid.Column="1"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Repository name. Optional."
|
||||||
|
Text="{Binding LocalName, ElementName=me, Mode=TwoWay}">
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<Label Grid.Row="5" Grid.Column="0" HorizontalAlignment="Right" Content="Remote Name :"/>
|
||||||
|
<TextBox Grid.Row="5" Grid.Column="1"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Remote name. Optional."
|
||||||
|
Text="{Binding RemoteName, ElementName=me, Mode=TwoWay}">
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<Grid Grid.Row="7" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
|
|
119
SourceGit/UI/Clone.xaml.cs
Normal file
119
SourceGit/UI/Clone.xaml.cs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clone dialog.
|
||||||
|
/// </summary>
|
||||||
|
public partial class Clone : UserControl {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remote repository
|
||||||
|
/// </summary>
|
||||||
|
public string RemoteUri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent folder.
|
||||||
|
/// </summary>
|
||||||
|
public string ParentFolder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Local name.
|
||||||
|
/// </summary>
|
||||||
|
public string LocalName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remote name.
|
||||||
|
/// </summary>
|
||||||
|
public string RemoteName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
public Clone() {
|
||||||
|
ParentFolder = App.Preference.GitDefaultCloneDir;
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show clone dialog.
|
||||||
|
/// </summary>
|
||||||
|
public static void Show() {
|
||||||
|
var popup = App.GetPopupManager(null);
|
||||||
|
popup?.Show(new Clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select parent folder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start clone
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Cancel(object sender, RoutedEventArgs e) {
|
||||||
|
App.GetPopupManager(null).Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
420
SourceGit/UI/CommitViewer.xaml
Normal file
420
SourceGit/UI/CommitViewer.xaml
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.CommitViewer"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:source="clr-namespace:SourceGit"
|
||||||
|
xmlns:local="clr-namespace:SourceGit.UI"
|
||||||
|
xmlns:git="clr-namespace:SourceGit.Git"
|
||||||
|
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
Unloaded="Cleanup">
|
||||||
|
<TabControl>
|
||||||
|
<TabItem Header="INFORMATION">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition x:Name="committerRow" Height="Auto"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="96"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="96"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- SHA -->
|
||||||
|
<Label Grid.Row="0" Grid.Column="0" Content="SHA" HorizontalAlignment="Right" Opacity=".6"/>
|
||||||
|
<TextBox Grid.Row="0" Grid.Column="1"
|
||||||
|
x:Name="SHA"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="11,0,0,0"/>
|
||||||
|
|
||||||
|
<!-- Refs -->
|
||||||
|
<Label x:Name="lblRefs" Grid.Row="0" Grid.Column="2" Content="REFS" HorizontalAlignment="Right" Opacity=".6"/>
|
||||||
|
<ItemsControl Grid.Row="0" Grid.Column="3" x:Name="refs" Margin="8,0,0,0">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border x:Name="BG" Height="16" Margin="2">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="18"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border Grid.Column="0" Background="{StaticResource Brush.BG5}">
|
||||||
|
<Path x:Name="Icon" Width="8" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Label x:Name="Name" Grid.Column="1" Content="{Binding Name}" FontSize="11" Padding="4,0" Foreground="Black"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<DataTemplate.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.Tag}">
|
||||||
|
<Setter TargetName="BG" Property="Background" Value="#FF02C302"/>
|
||||||
|
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Tag}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.LocalBranchHead}">
|
||||||
|
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||||
|
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Branch}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.RemoteBranchHead}">
|
||||||
|
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||||
|
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Remote}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.CurrentBranchHead}">
|
||||||
|
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||||
|
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Check}"/>
|
||||||
|
<Setter TargetName="Icon" Property="Fill" Value="Orange"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</DataTemplate.Triggers>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<!-- PARENTS -->
|
||||||
|
<Label Grid.Row="1" Grid.Column="0" Content="PARENTS" HorizontalAlignment="Right" Opacity=".6"/>
|
||||||
|
<ItemsControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" x:Name="parents" Margin="8,0,0,0">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Label Margin="0,0,8,0">
|
||||||
|
<Hyperlink
|
||||||
|
RequestNavigate="NavigateParent"
|
||||||
|
NavigateUri="{Binding .}"
|
||||||
|
ToolTip="NAVIGATE TO COMMIT">
|
||||||
|
<Run Text="{Binding .}"/>
|
||||||
|
</Hyperlink>
|
||||||
|
</Label>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<!-- AUTHOR -->
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" Content="AUTHOR" HorizontalAlignment="Right" Opacity=".6"/>
|
||||||
|
<TextBox Grid.Row="2" Grid.Column="1" x:Name="author"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Background="Transparent"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="11,0,0,0"/>
|
||||||
|
|
||||||
|
<!-- AUTHOR TIME -->
|
||||||
|
<Label Grid.Row="2" Grid.Column="2" Content="AUTHOR TIME" HorizontalAlignment="Right" Opacity=".6"/>
|
||||||
|
<TextBox Grid.Row="2" Grid.Column="3" Grid.ColumnSpan="3" x:Name="authorTime"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Background="Transparent"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="8,0,0,0"/>
|
||||||
|
|
||||||
|
<!-- COMMITTER -->
|
||||||
|
<Label Grid.Row="3" Grid.Column="0" Content="COMMITTER" HorizontalAlignment="Right" Opacity=".6"/>
|
||||||
|
<TextBox Grid.Row="3" Grid.Column="1" x:Name="committer"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Background="Transparent"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="11,0,0,0"/>
|
||||||
|
|
||||||
|
<!-- COMMIT TIME -->
|
||||||
|
<Label Grid.Row="3" Grid.Column="2" Content="COMMIT TIME" HorizontalAlignment="Right" Opacity=".6"/>
|
||||||
|
<TextBox Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="3" x:Name="committerTime"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Background="Transparent"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="8,0,0,0"/>
|
||||||
|
|
||||||
|
<Rectangle Grid.Row="4" Grid.ColumnSpan="4" Height="1" Margin="8,0" Fill="{StaticResource Brush.Border2}"/>
|
||||||
|
|
||||||
|
<!-- SUBJECT -->
|
||||||
|
<Label Grid.Row="5" Grid.Column="0" Content="SUBJECT" HorizontalAlignment="Right" Opacity=".6"/>
|
||||||
|
<TextBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="3"
|
||||||
|
x:Name="subject"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="8,0,16,0"/>
|
||||||
|
|
||||||
|
<!-- MESSAGE -->
|
||||||
|
<Label Grid.Row="6" Grid.Column="0" Content="DESCRIPTION" HorizontalAlignment="Right" VerticalAlignment="Top" Opacity=".6"/>
|
||||||
|
<TextBox Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="3"
|
||||||
|
x:Name="message"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="11"
|
||||||
|
Margin="11,8,0,0"/>
|
||||||
|
|
||||||
|
<Rectangle Grid.Row="7" Grid.ColumnSpan="4" Height="1" Margin="8,0" Fill="{StaticResource Brush.Border2}"/>
|
||||||
|
|
||||||
|
<!-- CHANGELIST -->
|
||||||
|
<Label Grid.Row="8" Grid.Column="0" Content="CHANGED" HorizontalAlignment="Right" VerticalAlignment="Top" Opacity=".6"/>
|
||||||
|
<DataGrid
|
||||||
|
Grid.Row="8"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
x:Name="changeList1"
|
||||||
|
RowHeight="20"
|
||||||
|
Margin="11,2,0,2">
|
||||||
|
<DataGrid.Resources>
|
||||||
|
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
|
||||||
|
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
|
||||||
|
|
||||||
|
<Style x:Key="Style.DataGridText.VerticalCenter" TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Resources>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Width="22">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border Width="14" Height="14" x:Name="status" Background="{Binding ., Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="2,0,4,0">
|
||||||
|
<TextBlock Text="{Binding ., Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="8"/>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Width="*" Binding="{Binding Path}" Foreground="{StaticResource Brush.FG}" FontFamily="Consolas" ElementStyle="{StaticResource Style.DataGridText.VerticalCenter}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
|
||||||
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||||
|
<EventSetter Event="ContextMenuOpening" Handler="ChangeListContextMenuOpening"/>
|
||||||
|
<EventSetter Event="MouseDoubleClick" Handler="ChangeListMouseDoubleClick"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- CHANGES -->
|
||||||
|
<TabItem Header="CHANGES">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="200" MinWidth="200"/>
|
||||||
|
<ColumnDefinition Width="1"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Column="0" Margin="2,0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.Resources>
|
||||||
|
<converters:BoolToCollapsed x:Key="BoolToCollapsed"/>
|
||||||
|
<converters:InverseBoolToCollapsed x:Key="InverseBoolToCollapsed"/>
|
||||||
|
</Grid.Resources>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Margin="0,0,0,4">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="24"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="24"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border Grid.Column="0" Grid.ColumnSpan="2" BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}" Background="{StaticResource Brush.BG3}"/>
|
||||||
|
<Path Grid.Column="0" Width="14" Height="14" Fill="{StaticResource Brush.FG2}" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Search}"/>
|
||||||
|
<TextBox Grid.Column="1" x:Name="txtChangeFilter" BorderThickness="0" helpers:TextBoxHelper.Placeholder="Search File ..." TextChanged="SearchChangeFileTextChanged"/>
|
||||||
|
<ToggleButton
|
||||||
|
Grid.Column="2"
|
||||||
|
x:Name="toggleSwitchMode"
|
||||||
|
Margin="4,0,0,0"
|
||||||
|
ToolTip="SWITCH TO LIST/TREE VIEW"
|
||||||
|
Style="{StaticResource Style.ToggleButton.ListOrTree}"
|
||||||
|
IsChecked="{Binding Source={x:Static source:App.Preference}, Path=UIUseListInChanges, Mode=TwoWay}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TreeView
|
||||||
|
Grid.Row="1"
|
||||||
|
x:Name="changeTree"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
Visibility="{Binding ElementName=toggleSwitchMode, Path=IsChecked, Converter={StaticResource InverseBoolToCollapsed}}"
|
||||||
|
Background="{StaticResource Brush.BG2}"
|
||||||
|
SelectedItemChanged="ChangeTreeItemSelected"
|
||||||
|
PreviewMouseWheel="TreeMouseWheel">
|
||||||
|
<TreeView.Resources>
|
||||||
|
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
|
||||||
|
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
|
||||||
|
</TreeView.Resources>
|
||||||
|
<TreeView.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||||
|
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
|
||||||
|
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
|
||||||
|
</Style>
|
||||||
|
</TreeView.ItemContainerStyle>
|
||||||
|
<TreeView.ItemTemplate>
|
||||||
|
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||||
|
<StackPanel Orientation="Horizontal" Height="24">
|
||||||
|
<Border x:Name="status" Width="14" Height="14" Visibility="Collapsed" Background="{Binding Change, Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="0,0,4,0">
|
||||||
|
<TextBlock Text="{Binding Change, Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="10" RenderOptions.BitmapScalingMode="HighQuality"/>
|
||||||
|
</Border>
|
||||||
|
<Path x:Name="icon" Width="14" Style="{StaticResource Style.Icon}" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
|
||||||
|
<TextBlock Text="{Binding Name}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" Margin="4,0,0,0" FontSize="11"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<HierarchicalDataTemplate.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsFile}" Value="True">
|
||||||
|
<Setter TargetName="status" Property="Visibility" Value="Visible"/>
|
||||||
|
<Setter TargetName="icon" Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding IsFile}" Value="False"/>
|
||||||
|
<Condition Binding="{Binding IsNodeExpanded}" Value="True"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</HierarchicalDataTemplate.Triggers>
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
</TreeView.ItemTemplate>
|
||||||
|
</TreeView>
|
||||||
|
|
||||||
|
<DataGrid
|
||||||
|
Grid.Row="1"
|
||||||
|
x:Name="changeList2"
|
||||||
|
Visibility="{Binding ElementName=toggleSwitchMode, Path=IsChecked, Converter={StaticResource BoolToCollapsed}}"
|
||||||
|
RowHeight="24"
|
||||||
|
SelectionChanged="ChangeListSelectionChanged"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionUnit="FullRow"
|
||||||
|
Background="{StaticResource Brush.BG2}">
|
||||||
|
<DataGrid.Resources>
|
||||||
|
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
|
||||||
|
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
|
||||||
|
|
||||||
|
<Style x:Key="Style.DataGridText.VerticalCenter" TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Resources>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Width="22">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border Width="14" Height="14" x:Name="status" Background="{Binding ., Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="2,0,4,0">
|
||||||
|
<TextBlock Text="{Binding ., Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="8"/>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Width="*" Binding="{Binding Path}" Foreground="{StaticResource Brush.FG}" FontFamily="Consolas" ElementStyle="{StaticResource Style.DataGridText.VerticalCenter}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
|
||||||
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||||
|
<EventSetter Event="ContextMenuOpening" Handler="ChangeListContextMenuOpening"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
|
||||||
|
|
||||||
|
<local:DiffViewer Grid.Column="2" x:Name="diffViewer" Background="{StaticResource Brush.BG3}"/>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- FILE TREE -->
|
||||||
|
<TabItem Header="FILES">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="400"/>
|
||||||
|
<ColumnDefinition Width="1"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border Grid.Column="0" Margin="2" Background="{StaticResource Brush.BG2}">
|
||||||
|
<TreeView x:Name="fileTree" SelectedItemChanged="FileTreeItemSelected" FontFamily="Consolas" PreviewMouseWheel="TreeMouseWheel">
|
||||||
|
<TreeView.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||||
|
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
|
||||||
|
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
|
||||||
|
</Style>
|
||||||
|
</TreeView.ItemContainerStyle>
|
||||||
|
<TreeView.ItemTemplate>
|
||||||
|
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||||
|
<StackPanel Orientation="Horizontal" Height="24">
|
||||||
|
<Path x:Name="icon" Width="14" Style="{StaticResource Style.Icon}" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
|
||||||
|
<TextBlock Text="{Binding Name}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" Margin="6,0,0,0" FontSize="11"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<HierarchicalDataTemplate.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsFile}" Value="True">
|
||||||
|
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.File}"/>
|
||||||
|
<Setter TargetName="icon" Property="Fill" Value="{StaticResource Brush.FG}"/>
|
||||||
|
<Setter TargetName="icon" Property="Opacity" Value=".75"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding IsFile}" Value="False"/>
|
||||||
|
<Condition Binding="{Binding IsNodeExpanded}" Value="True"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</HierarchicalDataTemplate.Triggers>
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
</TreeView.ItemTemplate>
|
||||||
|
</TreeView>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
|
||||||
|
|
||||||
|
<Border Grid.Column="2" BorderThickness="1" Margin="2,0" BorderBrush="{StaticResource Brush.Border2}">
|
||||||
|
<Grid>
|
||||||
|
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||||
|
<TextBlock
|
||||||
|
FontSize="10pt"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
Padding="8"
|
||||||
|
Opacity="0.8"
|
||||||
|
Background="{StaticResource Brush.BG2}"
|
||||||
|
Foreground="{StaticResource Brush.FG}"
|
||||||
|
x:Name="filePreview"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<StackPanel x:Name="maskRevision" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed">
|
||||||
|
<Path x:Name="iconPreviewRevision" Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}" Fill="{StaticResource Brush.FG2}"/>
|
||||||
|
<Label x:Name="txtPreviewRevision" Margin="0,16,0,0" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel x:Name="maskPreviewNotSupported" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed">
|
||||||
|
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Info}" Fill="{StaticResource Brush.FG2}"/>
|
||||||
|
<Label Margin="0,16,0,0" Content="BINARY FILE DETECTED" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
</UserControl>
|
504
SourceGit/UI/CommitViewer.xaml.cs
Normal file
504
SourceGit/UI/CommitViewer.xaml.cs
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Commit detail viewer
|
||||||
|
/// </summary>
|
||||||
|
public partial class CommitViewer : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private Git.Commit commit = null;
|
||||||
|
private List<Git.Change> cachedChanges = new List<Git.Change>();
|
||||||
|
private List<Git.Change> displayChanges = new List<Git.Change>();
|
||||||
|
private string changeFilter = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Node for file tree.
|
||||||
|
/// </summary>
|
||||||
|
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<Node> Children { get; set; } = new List<Node>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
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<string>();
|
||||||
|
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<Node> changeTreeSource = new List<Node>();
|
||||||
|
Dictionary<string, Node> folders = new Dictionary<string, Node>();
|
||||||
|
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<object> 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<Git.Commit.Object> files) {
|
||||||
|
List<Node> fileTreeSource = new List<Node>();
|
||||||
|
Dictionary<string, Node> folders = new Dictionary<string, Node>();
|
||||||
|
|
||||||
|
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<object> 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<Node> 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
71
SourceGit/UI/Configure.xaml
Normal file
71
SourceGit/UI/Configure.xaml
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.Configure"
|
||||||
|
x:Name="me"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="500" Width="500" Height="314">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="36"/>
|
||||||
|
<RowDefinition Height="8"/>
|
||||||
|
<RowDefinition Height="28"/>
|
||||||
|
<RowDefinition Height="28"/>
|
||||||
|
<RowDefinition Height="18"/>
|
||||||
|
<RowDefinition Height="36"/>
|
||||||
|
<RowDefinition Height="8"/>
|
||||||
|
<RowDefinition Height="102"/>
|
||||||
|
<RowDefinition Height="18"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="120"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 仓库帐号 -->
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" Content="CREDENTIAL" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" Content="User : " HorizontalAlignment="Right"/>
|
||||||
|
<TextBox
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
Height="24"
|
||||||
|
Text="{Binding ElementName=me, Path=UserName, Mode=TwoWay}"
|
||||||
|
helpers:TextBoxHelper.Placeholder="User name for this repository"/>
|
||||||
|
<Label Grid.Row="3" Grid.Column="0" Content="Email : " HorizontalAlignment="Right"/>
|
||||||
|
<TextBox
|
||||||
|
Grid.Row="3"
|
||||||
|
Grid.Column="1"
|
||||||
|
Height="24"
|
||||||
|
Text="{Binding ElementName=me, Path=UserEmail, Mode=TwoWay}"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Email address"/>
|
||||||
|
|
||||||
|
<!-- 提交模板 -->
|
||||||
|
<Label Grid.Row="5" Grid.ColumnSpan="2" Content="COMMIT TEMPLATE" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
|
||||||
|
<Label Grid.Row="7" Grid.Column="0" Content="Template : " HorizontalAlignment="Right" VerticalAlignment="Top"/>
|
||||||
|
<TextBox
|
||||||
|
Grid.Row="7"
|
||||||
|
Grid.Column="1"
|
||||||
|
Height="100"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
AcceptsTab="True"
|
||||||
|
Padding="2"
|
||||||
|
Text="{Binding ElementName=me, Path=CommitTemplate, Mode=TwoWay}"/>
|
||||||
|
|
||||||
|
<!-- 操作 -->
|
||||||
|
<Grid Grid.Row="9" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Save" Content="SAVE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Close" Content="CLOSE" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
71
SourceGit/UI/Configure.xaml.cs
Normal file
71
SourceGit/UI/Configure.xaml.cs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Repository configuration dialog
|
||||||
|
/// </summary>
|
||||||
|
public partial class Configure : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User name for this repository.
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User email for this repository.
|
||||||
|
/// </summary>
|
||||||
|
public string UserEmail { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Commit template for this repository.
|
||||||
|
/// </summary>
|
||||||
|
public string CommitTemplate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
public Configure(Git.Repository repo) {
|
||||||
|
this.repo = repo;
|
||||||
|
|
||||||
|
UserName = repo.GetConfig("user.name");
|
||||||
|
UserEmail = repo.GetConfig("user.email");
|
||||||
|
CommitTemplate = repo.CommitTemplate;
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show this dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
80
SourceGit/UI/CreateBranch.xaml
Normal file
80
SourceGit/UI/CreateBranch.xaml
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.CreateBranch"
|
||||||
|
x:Name="me"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||||
|
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="192" d:DesignWidth="500" Height="224" Width="500">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:InverseBool x:Key="InverseBool"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Create Local Branch"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Based On :"/>
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Path x:Name="basedOnType" Width="12" Style="{StaticResource Style.Icon}"/>
|
||||||
|
<Label x:Name="basedOnDesc" VerticalAlignment="Center" Content="master"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="New Branch Name :"/>
|
||||||
|
<TextBox Grid.Row="3" Grid.Column="1"
|
||||||
|
x:Name="txtName"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Enter branch name.">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding ElementName="me" Path="BranchName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<helpers:BranchNameRule x:Name="nameValidator"/>
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" Content="Local Changes :"/>
|
||||||
|
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<RadioButton Content="Stash & Reapply" GroupName="LocalChanges" IsChecked="{Binding AutoStash, ElementName=me}"/>
|
||||||
|
<RadioButton Content="Discard" Margin="8,0,0,0" GroupName="LocalChanges" IsChecked="{Binding AutoStash, ElementName=me, Mode=OneWay, Converter={StaticResource InverseBool}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="5" Grid.Column="1"
|
||||||
|
x:Name="chkCheckout"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsChecked="True"
|
||||||
|
Content="Check out after created"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="7" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
|
|
141
SourceGit/UI/CreateBranch.xaml.cs
Normal file
141
SourceGit/UI/CreateBranch.xaml.cs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create branch dialog
|
||||||
|
/// </summary>
|
||||||
|
public partial class CreateBranch : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private string based = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// New branch name.
|
||||||
|
/// </summary>
|
||||||
|
public string BranchName {
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto Stash
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoStash { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened">Opened repository</param>
|
||||||
|
public CreateBranch(Git.Repository opened) {
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
repo = opened;
|
||||||
|
nameValidator.Repo = opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create branch based on current head.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
public static void Show(Git.Repository repo) {
|
||||||
|
var current = repo.CurrentBranch();
|
||||||
|
if (current != null) Show(repo, current);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create branch base on existed one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="branch"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create branch based on tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="tag"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create branch based on commit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="commit"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start create branch.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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<string>());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Cancel(object sender, RoutedEventArgs e) {
|
||||||
|
App.GetPopupManager(repo)?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
SourceGit/UI/CreateTag.xaml
Normal file
70
SourceGit/UI/CreateTag.xaml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.CreateTag"
|
||||||
|
x:Name="me"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="224" d:DesignWidth="500" Width="500" Height="224">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="64"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Create Tag"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="New Tag At :"/>
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Path Width="12" x:Name="basedOnType" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}"/>
|
||||||
|
<Label x:Name="basedOnDesc" VerticalAlignment="Center" Content="xxx"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Tag Name :"/>
|
||||||
|
<TextBox Grid.Row="3" Grid.Column="1"
|
||||||
|
x:Name="tagName"
|
||||||
|
Height="24"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Recommanded format :v1.0.0-alpha">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding ElementName="me" Path="TagName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<helpers:TagNameRule x:Name="nameValidator"/>
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
|
||||||
|
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,4" Content="Tag Message :"/>
|
||||||
|
<TextBox Grid.Row="4" Grid.Column="1"
|
||||||
|
x:Name="tagMessage"
|
||||||
|
Height="56"
|
||||||
|
Padding="2"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
helpers:TextBoxHelper.Placeholder="Optional"
|
||||||
|
helpers:TextBoxHelper.PlaceholderBaseline="Top"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="6" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
|
|
98
SourceGit/UI/CreateTag.xaml.cs
Normal file
98
SourceGit/UI/CreateTag.xaml.cs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create tag dialog
|
||||||
|
/// </summary>
|
||||||
|
public partial class CreateTag : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private string based = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tag name
|
||||||
|
/// </summary>
|
||||||
|
public string TagName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
public CreateTag(Git.Repository opened) {
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
repo = opened;
|
||||||
|
nameValidator.Repo = opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create tag using current branch.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo">Opened repository.</param>
|
||||||
|
public static void Show(Git.Repository repo) {
|
||||||
|
Show(repo, repo.Branches().First(b => b.IsCurrent));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create tag using branch
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="branch"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create tag using commit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="commit"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start to create tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Cancel(object sender, RoutedEventArgs e) {
|
||||||
|
App.GetPopupManager(repo)?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
526
SourceGit/UI/Dashboard.xaml
Normal file
526
SourceGit/UI/Dashboard.xaml
Normal file
|
@ -0,0 +1,526 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.Dashboard"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:source="clr-namespace:SourceGit"
|
||||||
|
xmlns:local="clr-namespace:SourceGit.UI"
|
||||||
|
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
Unloaded="Cleanup">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<RoutedUICommand x:Key="OpenSearchBarCommand" Text="OpenSearchBar"/>
|
||||||
|
<RoutedUICommand x:Key="HideSearchBarCommand" Text="HideSearchBar"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<UserControl.InputBindings>
|
||||||
|
<KeyBinding Key="F" Modifiers="Ctrl" Command="{StaticResource OpenSearchBarCommand}"/>
|
||||||
|
<KeyBinding Key="ESC" Command="{StaticResource HideSearchBarCommand}"/>
|
||||||
|
</UserControl.InputBindings>
|
||||||
|
|
||||||
|
<UserControl.CommandBindings>
|
||||||
|
<CommandBinding Command="{StaticResource OpenSearchBarCommand}" Executed="OpenSearchBar"/>
|
||||||
|
<CommandBinding Command="{StaticResource HideSearchBarCommand}" Executed="HideSearchBar"/>
|
||||||
|
</UserControl.CommandBindings>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- TitleBar -->
|
||||||
|
<Grid Grid.Row="0" Panel.ZIndex="9999">
|
||||||
|
<Border Background="{StaticResource Brush.BG1}">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect ShadowDepth="2" Direction="270" Opacity=".5" Color="Black"/>
|
||||||
|
</Border.Effect>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Browser -->
|
||||||
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="6,0">
|
||||||
|
<Button Click="OpenExplorer" Margin="4,0,0,0" ToolTip="Open In File Browser">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder.Open}"/>
|
||||||
|
<Label Content="Explore"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Click="OpenConfigure" Margin="4,0,0,0" ToolTip="Configure This Repository">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Setting}"/>
|
||||||
|
<Label Content="Configure"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Common Git Options -->
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="6,0">
|
||||||
|
<Button Click="OpenFetch" Margin="4,0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Fetch}"/>
|
||||||
|
<Label Content="Fetch"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Click="OpenPull" Margin="4,0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Pull}"/>
|
||||||
|
<Label Content="Pull"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Click="OpenPush" Margin="4,0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Push}"/>
|
||||||
|
<Label Content="Push"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Click="OpenStash" Margin="4,0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.SaveStash}"/>
|
||||||
|
<Label Content="Stash"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Click="OpenApply">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Apply}"/>
|
||||||
|
<Label Content="Apply"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- External Options -->
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button Click="OpenSearch" Margin="4,0" ToolTip="Search Commit">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Search}"/>
|
||||||
|
<Label Content="Search"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Click="OpenTerminal" Margin="4,0" ToolTip="Open Git Bash">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Terminal}"/>
|
||||||
|
<Label Content="Terminal"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Main body -->
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="300"/>
|
||||||
|
<ColumnDefinition Width="1"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Left panel -->
|
||||||
|
<Grid Grid.Column="0" x:Name="main" Background="{StaticResource Brush.BG4}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="1"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="24"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.Resources>
|
||||||
|
<converters:BoolToCollapsed x:Key="Bool2Collapsed"/>
|
||||||
|
</Grid.Resources>
|
||||||
|
|
||||||
|
<!-- WORKSPACE -->
|
||||||
|
<Label Grid.Row="0" Margin="4,0,0,0" Content="WORKSPACE" Style="{StaticResource Style.Label.GroupHeader}" />
|
||||||
|
<ListView
|
||||||
|
Grid.Row="1"
|
||||||
|
x:Name="workspace"
|
||||||
|
Background="{StaticResource Brush.BG3}"
|
||||||
|
Style="{StaticResource Style.ListView.Borderless}"
|
||||||
|
SelectionMode="Single">
|
||||||
|
<ListViewItem x:Name="historiesSwitch" Selected="SwitchHistories" IsSelected="True">
|
||||||
|
<StackPanel Margin="16,0,0,0" Orientation="Horizontal">
|
||||||
|
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Histories}"/>
|
||||||
|
<Label Margin="4,0,0,0" Content="Histories"/>
|
||||||
|
</StackPanel>
|
||||||
|
</ListViewItem>
|
||||||
|
|
||||||
|
<ListViewItem x:Name="workingCopySwitch" Selected="SwitchWorkingCopy">
|
||||||
|
<Grid Margin="16,0,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Path Grid.Column="0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.WorkingCopy}"/>
|
||||||
|
<Label Grid.Column="1" Margin="4,0,0,0" Content="Commit"/>
|
||||||
|
<Border Grid.Column="2" x:Name="localChangesBadge" Style="{StaticResource Style.Border.Badge}">
|
||||||
|
<Label x:Name="localChangesCount" Margin="4,-2,4,-2" Content="999" FontSize="10"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ListViewItem>
|
||||||
|
|
||||||
|
<ListViewItem x:Name="stashesSwitch" Selected="SwitchStashes">
|
||||||
|
<Grid Margin="16,0,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Path Grid.Column="0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Stashes}"/>
|
||||||
|
<Label Grid.Column="1" Margin="4,0,0,0" Content="Stashes"/>
|
||||||
|
<Border Grid.Column="2" x:Name="stashBadge" Style="{StaticResource Style.Border.Badge}">
|
||||||
|
<Label x:Name="stashCount" Margin="4,-2,4,-2" Content="999" FontSize="10"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ListViewItem>
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
<!-- LOCAL BRANCHES -->
|
||||||
|
<Grid Grid.Row="2" Margin="4,0,2,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Column="0" Content="LOCAL BRANCHES" Style="{StaticResource Style.Label.GroupHeader}"/>
|
||||||
|
<Button Grid.Column="1" Click="OpenGitFlow" Background="Transparent" ToolTip="GIT FLOW">
|
||||||
|
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Flow}"/>
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="3" Click="OpenNewBranch" Background="Transparent" ToolTip="NEW BRANCH">
|
||||||
|
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Branch.Add}"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<TreeView
|
||||||
|
Grid.Row="3"
|
||||||
|
x:Name="localBranchTree"
|
||||||
|
Background="{StaticResource Brush.BG3}"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
LostFocus="TreeLostFocus"
|
||||||
|
SelectedItemChanged="LocalBranchSelected"
|
||||||
|
PreviewMouseWheel="TreeMouseWheel">
|
||||||
|
<TreeView.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||||
|
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||||
|
<EventSetter Event="ContextMenuOpening" Handler="LocalBranchContextMenuOpening"/>
|
||||||
|
<EventSetter Event="MouseDoubleClick" Handler="LocalBranchMouseDoubleClick"/>
|
||||||
|
</Style>
|
||||||
|
</TreeView.ItemContainerStyle>
|
||||||
|
<TreeView.ItemTemplate>
|
||||||
|
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||||
|
<Grid Height="24">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
|
||||||
|
<Label Grid.Column="1" x:Name="name" Content="{Binding Name}" Padding="4,0,0,0"/>
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||||
|
<Border Style="{StaticResource Style.Border.Badge}" Visibility="{Binding TrackVisibility}">
|
||||||
|
<Label Margin="4,-2,4,-2" Content="{Binding Branch.UpstreamTrack}" FontSize="10"/>
|
||||||
|
</Border>
|
||||||
|
<ToggleButton
|
||||||
|
Visibility="{Binding FilterVisibility}"
|
||||||
|
IsChecked="{Binding IsFiltered, Mode=OneWay}"
|
||||||
|
Checked="FilterChanged"
|
||||||
|
Unchecked="FilterChanged"
|
||||||
|
Style="{StaticResource Style.ToggleButton.Filter}"
|
||||||
|
ToolTip="FILTER"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<HierarchicalDataTemplate.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
|
||||||
|
<Setter TargetName="name" Property="FontWeight" Value="ExtraBold"/>
|
||||||
|
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Check}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
|
||||||
|
<Condition Binding="{Binding IsExpanded}" Value="False"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
|
||||||
|
<Condition Binding="{Binding IsExpanded}" Value="True"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</HierarchicalDataTemplate.Triggers>
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
</TreeView.ItemTemplate>
|
||||||
|
</TreeView>
|
||||||
|
|
||||||
|
<!-- REMOTES -->
|
||||||
|
<Grid Grid.Row="4" Margin="4,0,2,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Column="0" Content="REMOTES" Style="{StaticResource Style.Label.GroupHeader}"/>
|
||||||
|
<Button Grid.Column="1" Click="OpenRemote" ToolTip="ADD REMOTE">
|
||||||
|
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Remote.Add}"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<TreeView
|
||||||
|
Grid.Row="5"
|
||||||
|
x:Name="remoteBranchTree"
|
||||||
|
Background="{StaticResource Brush.BG3}"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
|
SelectedItemChanged="RemoteBranchSelected"
|
||||||
|
LostFocus="TreeLostFocus"
|
||||||
|
PreviewMouseWheel="TreeMouseWheel">
|
||||||
|
<TreeView.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||||
|
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||||
|
<EventSetter Event="ContextMenuOpening" Handler="RemoteContextMenuOpening"/>
|
||||||
|
</Style>
|
||||||
|
</TreeView.ItemContainerStyle>
|
||||||
|
|
||||||
|
<TreeView.Resources>
|
||||||
|
<HierarchicalDataTemplate DataType="{x:Type local:RemoteNode}" ItemsSource="{Binding Children}">
|
||||||
|
<Grid Height="24">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
|
||||||
|
<TextBlock Grid.Column="1" x:Name="name" Text="{Binding Name}" Padding="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}" ClipToBounds="True"/>
|
||||||
|
</Grid>
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
|
||||||
|
<HierarchicalDataTemplate DataType="{x:Type local:BranchNode}" ItemsSource="{Binding Children}">
|
||||||
|
<Grid Height="24">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
|
||||||
|
<TextBlock Grid.Column="1" x:Name="name" Text="{Binding Name}" Padding="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}" ClipToBounds="True"/>
|
||||||
|
<ToggleButton
|
||||||
|
Grid.Column="2"
|
||||||
|
Visibility="{Binding FilterVisibility}"
|
||||||
|
IsChecked="{Binding IsFiltered, Mode=OneWay}"
|
||||||
|
Checked="FilterChanged"
|
||||||
|
Unchecked="FilterChanged"
|
||||||
|
Style="{StaticResource Style.ToggleButton.Filter}"
|
||||||
|
ToolTip="FILTER"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<HierarchicalDataTemplate.Triggers>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
|
||||||
|
<Condition Binding="{Binding IsExpanded}" Value="False"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
|
||||||
|
<Condition Binding="{Binding IsExpanded}" Value="True"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</HierarchicalDataTemplate.Triggers>
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
</TreeView.Resources>
|
||||||
|
</TreeView>
|
||||||
|
|
||||||
|
<!-- TAGS -->
|
||||||
|
<ToggleButton
|
||||||
|
x:Name="tagListToggle"
|
||||||
|
Grid.Row="6"
|
||||||
|
Style="{StaticResource Style.ToggleButton.Expender}"
|
||||||
|
IsChecked="{Binding Source={x:Static source:App.Preference}, Path=UIShowTags, Mode=TwoWay}">
|
||||||
|
<Grid Margin="4,0,2,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Column="0" x:Name="tagCount" Content="TAGS" Style="{StaticResource Style.Label.GroupHeader}"/>
|
||||||
|
<Button Grid.Column="1" Click="OpenNewTag" ToolTip="NEW TAG">
|
||||||
|
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag.Add}"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</ToggleButton>
|
||||||
|
<Rectangle Grid.Row="7" Height="1" Fill="{StaticResource Brush.BG3}"/>
|
||||||
|
<DataGrid
|
||||||
|
Grid.Row="8"
|
||||||
|
x:Name="tagList"
|
||||||
|
Visibility="{Binding ElementName=tagListToggle, Path=IsChecked, Converter={StaticResource Bool2Collapsed}}"
|
||||||
|
Background="{StaticResource Brush.BG3}"
|
||||||
|
RowHeight="24"
|
||||||
|
Height="200"
|
||||||
|
LostFocus="TagLostFocus"
|
||||||
|
SelectionChanged="TagSelectionChanged"
|
||||||
|
ContextMenuOpening="TagContextMenuOpening"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionUnit="FullRow">
|
||||||
|
<DataGrid.Resources>
|
||||||
|
<Style x:Key="Style.DataGridText.TagName" TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Resources>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Width="26">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
|
<DataGridTextColumn Width="*" IsReadOnly="True" Binding="{Binding Name}" ElementStyle="{StaticResource Style.DataGridText.TagName}"/>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn Width="16">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ToggleButton
|
||||||
|
Grid.Column="2"
|
||||||
|
IsChecked="{Binding IsFiltered, Mode=TwoWay}"
|
||||||
|
Checked="FilterChanged"
|
||||||
|
Unchecked="FilterChanged"
|
||||||
|
Style="{StaticResource Style.ToggleButton.Filter}"
|
||||||
|
ToolTip="FILTER"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
|
<!-- SUBMODULES -->
|
||||||
|
<ToggleButton
|
||||||
|
x:Name="submoduleListToggle"
|
||||||
|
Grid.Row="9"
|
||||||
|
Style="{StaticResource Style.ToggleButton.Expender}"
|
||||||
|
IsChecked="False">
|
||||||
|
<Grid Margin="4,0,2,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="16"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Column="0" x:Name="submoduleCount" Content="SUBMODULES" Style="{StaticResource Style.Label.GroupHeader}"/>
|
||||||
|
<Button Grid.Column="1" Click="OpenAddSubmodule" ToolTip="ADD SUBMODULE">
|
||||||
|
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}"/>
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="3" Click="UpdateSubmodule" ToolTip="UPDATE SUBMODULE">
|
||||||
|
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</ToggleButton>
|
||||||
|
<DataGrid
|
||||||
|
Grid.Row="11"
|
||||||
|
x:Name="submoduleList"
|
||||||
|
Visibility="{Binding ElementName=submoduleListToggle, Path=IsChecked, Converter={StaticResource Bool2Collapsed}}"
|
||||||
|
Background="{StaticResource Brush.BG3}"
|
||||||
|
RowHeight="24"
|
||||||
|
Height="100"
|
||||||
|
LostFocus="SubmoduleLostFocus"
|
||||||
|
ContextMenuOpening="SubmoduleContextMenuOpening"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionUnit="FullRow">
|
||||||
|
<DataGrid.Resources>
|
||||||
|
<Style x:Key="Style.DataGridText.SubmodulePath" TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Resources>
|
||||||
|
|
||||||
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||||
|
<EventSetter Event="MouseDoubleClick" Handler="SubmoduleMouseDoubleClick"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn Width="26">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
|
<DataGridTextColumn Width="*" IsReadOnly="True" Binding="{Binding}" ElementStyle="{StaticResource Style.DataGridText.SubmodulePath}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Splitter -->
|
||||||
|
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.BG3}"/>
|
||||||
|
|
||||||
|
<!-- Right -->
|
||||||
|
<Grid Grid.Column="2">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Abort panel -->
|
||||||
|
<Grid x:Name="abortPanel" Grid.Row="0" Background="LightGoldenrodYellow" Visibility="Collapsed">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Column="0" x:Name="txtMergeProcessing" FontWeight="DemiBold" Foreground="{StaticResource Brush.BG4}"/>
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Button x:Name="btnResolve" Click="Resolve" Content="RESOLVE" Margin="4">
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.Bordered}">
|
||||||
|
<Setter Property="Background" Value="{StaticResource Brush.BG1}"/>
|
||||||
|
<Setter Property="Margin" Value="2"/>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="btnContinue" Click="Continue" Content="CONTINUE" Style="{StaticResource Style.Button.AccentBordered}" Margin="4"/>
|
||||||
|
<Button Grid.Column="3" Click="Abort" Content="ABORT" Style="{StaticResource Style.Button.Bordered}" Foreground="{StaticResource Brush.BG1}" Margin="4"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Others -->
|
||||||
|
<local:Histories Grid.Row="1" x:Name="histories" Visibility="Visible"/>
|
||||||
|
<local:WorkingCopy Grid.Row="1" x:Name="commits" Visibility="Collapsed"/>
|
||||||
|
<local:Stashes Grid.Row="1" x:Name="stashes" Visibility="Collapsed"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Popups -->
|
||||||
|
<local:PopupManager x:Name="popupManager" Grid.Row="1"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
1067
SourceGit/UI/Dashboard.xaml.cs
Normal file
1067
SourceGit/UI/Dashboard.xaml.cs
Normal file
File diff suppressed because it is too large
Load diff
43
SourceGit/UI/DeleteBranch.xaml
Normal file
43
SourceGit/UI/DeleteBranch.xaml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.DeleteBranch"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:SourceGit.UI"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Branch"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Branch :"/>
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}" Margin="4,0"/>
|
||||||
|
<Label x:Name="branchName"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid Grid.Row="4" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
56
SourceGit/UI/DeleteBranch.xaml.cs
Normal file
56
SourceGit/UI/DeleteBranch.xaml.cs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
/// <summary>
|
||||||
|
/// Confirm to delete branch
|
||||||
|
/// </summary>
|
||||||
|
public partial class DeleteBranch : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private Git.Branch branch = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened">Opened repository.</param>
|
||||||
|
/// <param name="target">Branch to be deleted.</param>
|
||||||
|
public DeleteBranch(Git.Repository opened, Git.Branch target) {
|
||||||
|
InitializeComponent();
|
||||||
|
repo = opened;
|
||||||
|
branch = target;
|
||||||
|
branchName.Content = target.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show this dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened"></param>
|
||||||
|
/// <param name="branch"></param>
|
||||||
|
public static void Show(Git.Repository opened, Git.Branch branch) {
|
||||||
|
var popup = App.GetPopupManager(opened);
|
||||||
|
popup?.Show(new DeleteBranch(opened, branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private async void Sure(object sender, RoutedEventArgs e) {
|
||||||
|
var popup = App.GetPopupManager(repo);
|
||||||
|
popup?.Lock();
|
||||||
|
await Task.Run(() => branch.Delete(repo));
|
||||||
|
popup?.Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Cancel(object sender, RoutedEventArgs e) {
|
||||||
|
App.GetPopupManager(repo)?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
SourceGit/UI/DeleteRemote.xaml
Normal file
42
SourceGit/UI/DeleteRemote.xaml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.DeleteRemote"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Remote"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Remote :"/>
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}" Margin="4,0"/>
|
||||||
|
<Label x:Name="remoteName"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid Grid.Row="4" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
57
SourceGit/UI/DeleteRemote.xaml.cs
Normal file
57
SourceGit/UI/DeleteRemote.xaml.cs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Confirm to delete a remote
|
||||||
|
/// </summary>
|
||||||
|
public partial class DeleteRemote : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private string remote = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened">Opened repository</param>
|
||||||
|
/// <param name="target">Remote to be deleted</param>
|
||||||
|
public DeleteRemote(Git.Repository opened, string target) {
|
||||||
|
InitializeComponent();
|
||||||
|
repo = opened;
|
||||||
|
remote = target;
|
||||||
|
remoteName.Content = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show this dialog
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened"></param>
|
||||||
|
/// <param name="remote"></param>
|
||||||
|
public static void Show(Git.Repository opened, string remote) {
|
||||||
|
var popup = App.GetPopupManager(opened);
|
||||||
|
popup?.Show(new DeleteRemote(opened, remote));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Cancel(object sender, RoutedEventArgs e) {
|
||||||
|
App.GetPopupManager(repo)?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
SourceGit/UI/DeleteTag.xaml
Normal file
45
SourceGit/UI/DeleteTag.xaml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.DeleteTag"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Tag"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Tag :"/>
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}" Margin="4,0"/>
|
||||||
|
<Label x:Name="tagName"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="chkWithRemote" Content="Delete from remote repositories"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="5" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
61
SourceGit/UI/DeleteTag.xaml.cs
Normal file
61
SourceGit/UI/DeleteTag.xaml.cs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete tag dialog.
|
||||||
|
/// </summary>
|
||||||
|
public partial class DeleteTag : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private Git.Tag tag = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo">Opened repo</param>
|
||||||
|
/// <param name="tag">Delete tag</param>
|
||||||
|
public DeleteTag(Git.Repository repo, Git.Tag tag) {
|
||||||
|
this.repo = repo;
|
||||||
|
this.tag = tag;
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
tagName.Content = tag.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display this dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="tag"></param>
|
||||||
|
public static void Show(Git.Repository repo, Git.Tag tag) {
|
||||||
|
var popup = App.GetPopupManager(repo);
|
||||||
|
popup?.Show(new DeleteTag(repo, tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Cancel(object sender, RoutedEventArgs e) {
|
||||||
|
App.GetPopupManager(repo)?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
203
SourceGit/UI/DiffViewer.xaml
Normal file
203
SourceGit/UI/DiffViewer.xaml
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.DiffViewer"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
FontFamily="Consolas">
|
||||||
|
<Border BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="26"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="0,0,0,1">
|
||||||
|
<Grid Margin="8,4">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="0" x:Name="orgFileNamePanel" Orientation="Horizontal">
|
||||||
|
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}"/>
|
||||||
|
<TextBlock x:Name="orgFileName" Margin="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
|
||||||
|
<TextBlock Margin="8,0" VerticalAlignment="Center" Text="→" Foreground="{StaticResource Brush.FG}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}"/>
|
||||||
|
<TextBlock x:Name="fileName" Margin="4,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
|
||||||
|
<Path x:Name="loading" Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="2" x:Name="diffNavigation" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button Width="26" Click="Go2Next" ToolTip="Next Difference" Background="Transparent">
|
||||||
|
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveDown}"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Click="Go2Prev" ToolTip="Previous Difference" Background="Transparent">
|
||||||
|
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveUp}"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Grid x:Name="textChange" Grid.Row="1" ClipToBounds="True">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" MinWidth="100"/>
|
||||||
|
<ColumnDefinition Width="2"/>
|
||||||
|
<ColumnDefinition Width="*" MinWidth="100"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Column="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="1"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBox
|
||||||
|
x:Name="leftLineNumber"
|
||||||
|
Grid.Column="0"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
AcceptsTab="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="Transparent"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Padding="2,0"
|
||||||
|
Margin="0"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalContentAlignment="Right"
|
||||||
|
VerticalAlignment="Stretch"/>
|
||||||
|
|
||||||
|
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
||||||
|
|
||||||
|
<RichTextBox
|
||||||
|
x:Name="leftText"
|
||||||
|
Grid.Column="2"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
AcceptsTab="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="Transparent"
|
||||||
|
Foreground="{StaticResource Brush.FG}"
|
||||||
|
Height="Auto"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
RenderOptions.ClearTypeHint="Enabled"
|
||||||
|
ScrollViewer.ScrollChanged="OnViewerScroll"
|
||||||
|
PreviewMouseWheel="OnViewerMouseWheel"
|
||||||
|
SizeChanged="LeftSizeChanged"
|
||||||
|
SelectionChanged="OnViewerSelectionChanged"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<RichTextBox.Document>
|
||||||
|
<FlowDocument PageWidth="0"/>
|
||||||
|
</RichTextBox.Document>
|
||||||
|
<RichTextBox.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Command="ApplicationCommands.Copy"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</RichTextBox.ContextMenu>
|
||||||
|
</RichTextBox>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.Border2}"/>
|
||||||
|
|
||||||
|
<Grid Grid.Column="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="1"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBox
|
||||||
|
x:Name="rightLineNumber"
|
||||||
|
Grid.Column="0"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
AcceptsTab="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="Transparent"
|
||||||
|
Padding="2,0"
|
||||||
|
Margin="0"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalContentAlignment="Right"
|
||||||
|
VerticalAlignment="Stretch"/>
|
||||||
|
|
||||||
|
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
||||||
|
|
||||||
|
<RichTextBox
|
||||||
|
x:Name="rightText"
|
||||||
|
Grid.Column="2"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
AcceptsTab="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="Transparent"
|
||||||
|
Foreground="{StaticResource Brush.FG}"
|
||||||
|
Height="Auto"
|
||||||
|
FontSize="13"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
RenderOptions.ClearTypeHint="Enabled"
|
||||||
|
ScrollViewer.ScrollChanged="OnViewerScroll"
|
||||||
|
PreviewMouseWheel="OnViewerMouseWheel"
|
||||||
|
SizeChanged="RightSizeChanged"
|
||||||
|
SelectionChanged="OnViewerSelectionChanged"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<RichTextBox.Document>
|
||||||
|
<FlowDocument PageWidth="0"/>
|
||||||
|
</RichTextBox.Document>
|
||||||
|
<RichTextBox.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Command="ApplicationCommands.Copy"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</RichTextBox.ContextMenu>
|
||||||
|
</RichTextBox>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Border x:Name="sizeChange" Grid.Row="1" ClipToBounds="True" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
|
||||||
|
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||||
|
<Label Content="BINARY DIFF" Margin="0,0,0,32" FontSize="18" FontWeight="UltraBold" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||||
|
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Binary}" Fill="{StaticResource Brush.FG2}"/>
|
||||||
|
<Grid Margin="0,16,0,0" HorizontalAlignment="Center" TextElement.FontSize="18" TextElement.FontWeight="UltraBold">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="64"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.Column="0" Content="OLD :" Foreground="{StaticResource Brush.FG2}"/>
|
||||||
|
<Label Grid.Row="0" Grid.Column="1" x:Name="txtOldSize" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Right"/>
|
||||||
|
<Label Grid.Row="1" Grid.Column="0" Content="NEW :" Foreground="{StaticResource Brush.FG2}"/>
|
||||||
|
<Label Grid.Row="1" Grid.Column="1" x:Name="txtNewSize" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Right"/>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="noChange" Grid.Row="1" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
|
||||||
|
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Opacity=".2">
|
||||||
|
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Check}"/>
|
||||||
|
<Label Margin="0,8,0,0" Content="NO CHANGES OR ONLY EOL CHANGES" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="mask" Grid.RowSpan="2" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
|
||||||
|
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Opacity=".2">
|
||||||
|
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Diff}"/>
|
||||||
|
<Label Margin="0,8,0,0" Content="SELECT FILE TO VIEW CHANGES" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
405
SourceGit/UI/DiffViewer.xaml.cs
Normal file
405
SourceGit/UI/DiffViewer.xaml.cs
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Viewer for git diff
|
||||||
|
/// </summary>
|
||||||
|
public partial class DiffViewer : UserControl {
|
||||||
|
private double minWidth = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Diff options.
|
||||||
|
/// </summary>
|
||||||
|
public class Option {
|
||||||
|
public string[] RevisionRange = new string[] { };
|
||||||
|
public string Path = "";
|
||||||
|
public string OrgPath = null;
|
||||||
|
public string ExtraArgs = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public DiffViewer() {
|
||||||
|
InitializeComponent();
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset data.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset() {
|
||||||
|
mask.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Diff with options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repo"></param>
|
||||||
|
/// <param name="opts"></param>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Show diff title
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <param name="orgFile"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show size changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bc"></param>
|
||||||
|
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";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show no changes or only EOL changes.
|
||||||
|
/// </summary>
|
||||||
|
private void SetSame() {
|
||||||
|
Dispatcher.Invoke(() => {
|
||||||
|
loading.Visibility = Visibility.Collapsed;
|
||||||
|
noChange.Visibility = Visibility.Visible;
|
||||||
|
diffNavigation.Visibility = Visibility.Collapsed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show diff content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rs"></param>
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make paragraph.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="b"></param>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Sync scroll both sides.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scroll using mouse wheel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fix document size for left side.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void LeftSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||||
|
if (leftText.Document.PageWidth < leftText.ActualWidth) {
|
||||||
|
leftText.Document.PageWidth = leftText.ActualWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fix document size for right side.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void RightSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||||
|
if (rightText.Document.PageWidth < rightText.ActualWidth) {
|
||||||
|
rightText.Document.PageWidth = rightText.ActualWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto scroll when selection changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Go to next difference.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Go to previous difference.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
47
SourceGit/UI/Discard.xaml
Normal file
47
SourceGit/UI/Discard.xaml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.Discard"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:SourceGit.UI"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Discard Changes"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Changes :"/>
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<Path x:Name="icon" Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}" Fill="{StaticResource Brush.FG2}" Margin="4,0"/>
|
||||||
|
<Label x:Name="txtPath"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Label Grid.Row="3" Grid.Column="1" Content="You can't undo this action!!!" Foreground="{StaticResource Brush.FG2}"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="5" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
|
|
58
SourceGit/UI/Discard.xaml.cs
Normal file
58
SourceGit/UI/Discard.xaml.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Confirm to discard changes dialog.
|
||||||
|
/// </summary>
|
||||||
|
public partial class Discard : UserControl {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private List<Git.Change> changes = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened"></param>
|
||||||
|
/// <param name="targets"></param>
|
||||||
|
public Discard(Git.Repository opened, List<Git.Change> 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 ...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show this dialog
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opened"></param>
|
||||||
|
/// <param name="targets"></param>
|
||||||
|
public static void Show(Git.Repository opened, List<Git.Change> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
SourceGit/UI/Fetch.xaml
Normal file
66
SourceGit/UI/Fetch.xaml
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<UserControl x:Class="SourceGit.UI.Fetch"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:git="clr-namespace:SourceGit.Git"
|
||||||
|
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="192" d:DesignWidth="500" Height="192" Width="500">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
<RowDefinition Height="16"/>
|
||||||
|
<RowDefinition Height="32"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.Resources>
|
||||||
|
<converters:InverseBool x:Key="InverseBool"/>
|
||||||
|
</Grid.Resources>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Fetch Remote Changes"/>
|
||||||
|
|
||||||
|
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Remote :"/>
|
||||||
|
<ComboBox x:Name="combRemotes" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" IsEnabled="{Binding ElementName=chkFetchAll, Path=IsChecked, Converter={StaticResource InverseBool}}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type git:Remote}">
|
||||||
|
<StackPanel Orientation="Horizontal" Height="20">
|
||||||
|
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
|
||||||
|
<Label Content="{Binding Name}" Padding="8,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="3" Grid.Column="1"
|
||||||
|
x:Name="chkFetchAll"
|
||||||
|
IsChecked="True"
|
||||||
|
Content="Fetch all remotes"/>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="4" Grid.Column="1"
|
||||||
|
x:Name="chkPrune"
|
||||||
|
IsChecked="True"
|
||||||
|
Content="Prune remote dead branches"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="6" Grid.ColumnSpan="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
<ColumnDefinition Width="8"/>
|
||||||
|
<ColumnDefinition Width="80"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||||
|
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue