mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
Merge branch 'release/v8.20'
This commit is contained in:
commit
7d3f228e0c
85 changed files with 2523 additions and 1233 deletions
51
README.md
51
README.md
|
@ -52,7 +52,9 @@ This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationD
|
|||
For **Windows** users:
|
||||
|
||||
* **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead.
|
||||
* `sourcegit_x.y.win-x64.zip` may be reported as virus by Windows Defender. I don't know why. I have manually tested the zip to be uploaded using Windows Defender before uploading and no virus was found. If you have installed .NET 8 SDK locally, I suggest you to compile it yourself. And if you have any idea about how to fix this, please open an issue.
|
||||
* You can install the latest stable by `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.
|
||||
* Portable versions can be found in [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest)
|
||||
|
||||
For **macOS** users:
|
||||
|
||||
|
@ -80,9 +82,9 @@ This app supports open repository in external tools listed in the table below.
|
|||
| JetBrains Fleet | YES | YES | YES | FLEET_PATH |
|
||||
| Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH |
|
||||
|
||||
> * You can set the given environment variable for special tool if it can NOT be found by this app automatically.
|
||||
> * Installing `JetBrains Toolbox` will help this app to find other JetBrains tools installed on your device.
|
||||
> * On macOS, you may need to use `launchctl setenv` to make sure the app can read these environment variables.
|
||||
* You can set the given environment variable for special tool if it can NOT be found by this app automatically.
|
||||
* Installing `JetBrains Toolbox` will help this app to find other JetBrains tools installed on your device.
|
||||
* On macOS, you may need to use `launchctl setenv` to make sure the app can read these environment variables.
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
@ -94,46 +96,9 @@ This app supports open repository in external tools listed in the table below.
|
|||
|
||||
![Theme Light](./screenshots/theme_light.png)
|
||||
|
||||
## How to Customize Theme
|
||||
* Custom Themes
|
||||
|
||||
1. Create a new json file, and provide your favorite colors with follow keys:
|
||||
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| Color.Window | Window background color |
|
||||
| Color.WindowBorder | Window border color. Only used on Linux. |
|
||||
| Color.TitleBar | Title bar background color |
|
||||
| Color.ToolBar | Tool bar background color |
|
||||
| Color.Popup | Popup panel background color |
|
||||
| Color.Contents | Background color used in inputs, data grids, file content viewer, change lists, text diff viewer, etc. |
|
||||
| Color.Badge | Badge background color |
|
||||
| Color.BadgeFG | Badge foreground color |
|
||||
| Color.Conflict | Conflict panel background color |
|
||||
| Color.ConflictForeground | Conflict panel foreground color |
|
||||
| Color.Border0 | Border color used in some controls, like Window, Tab, Toolbar, etc. |
|
||||
| Color.Border1 | Border color used in inputs, like TextBox, ComboBox, etc. |
|
||||
| Color.Border2 | Border color used in visual lines, like seperators, Rectange, etc. |
|
||||
| Color.FlatButton.Background | Flat button background color, like `Cancel`, `Commit & Push` button |
|
||||
| Color.FlatButton.BackgroundHovered | Flat button background color when hovered, like `Cancel` button |
|
||||
| Color.FG1 | Primary foreground color for all text elements |
|
||||
| Color.FG2 | Secondary foreground color for all text elements |
|
||||
| Color.Diff.EmptyBG | Background color used in empty lines in diff viewer |
|
||||
| Color.Diff.AddedBG | Background color used in added lines in diff viewer |
|
||||
| Color.Diff.DeletedBG | Background color used in deleted lines in diff viewer |
|
||||
| Color.Diff.AddedHighlight | Background color used for changed words in added lines in diff viewer |
|
||||
| Color.Diff.DeletedHighlight | Background color used for changed words in deleted lines in diff viewer |
|
||||
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"Color.Window": "#FFFF6059"
|
||||
}
|
||||
```
|
||||
|
||||
2. Open `Preference` -> `Appearance`, choose the json file you just created in `Custom Color Schema`.
|
||||
|
||||
> **NOTE**: The `Custom Color Schema` will override the colors with same keys in current active theme.
|
||||
You can find custom themes from [sourcegit-theme](https://github.com/sourcegit-scm/sourcegit-theme.git)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
8.19
|
||||
8.20
|
|
@ -3,17 +3,16 @@
|
|||
version=`cat ../VERSION`
|
||||
|
||||
# Cleanup
|
||||
rm -rf SourceGit *.tar.gz resources/deb/opt *.deb *.rpm
|
||||
rm -rf SourceGit *.tar.gz resources/deb/opt *.deb *.rpm *.AppImage
|
||||
|
||||
# Compile
|
||||
dotnet publish ../src/SourceGit.csproj -c Release -r linux-x64 -o SourceGit -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained
|
||||
mv SourceGit/SourceGit SourceGit/sourcegit
|
||||
cp resources/app/App.icns SourceGit/sourcegit.icns
|
||||
rm -f SourceGit/*.dbg
|
||||
# Generic AppImage
|
||||
cd resources/appimage
|
||||
./publish-appimage -y -o sourcegit-${version}.linux.x86_64.AppImage
|
||||
|
||||
# General Linux archive
|
||||
tar -zcvf sourcegit_${version}.linux-x64.tar.gz SourceGit
|
||||
rm -f SourceGit/sourcegit.icns
|
||||
# Move to build dir
|
||||
mv AppImages/sourcegit-${version}.linux.x86_64.AppImage ../../
|
||||
mv AppImages/AppDir/usr/bin ../../SourceGit
|
||||
cd ../../
|
||||
|
||||
# Debain/Ubuntu package
|
||||
mkdir -p resources/deb/opt/sourcegit/
|
||||
|
|
708
build/resources/appimage/publish-appimage
Executable file
708
build/resources/appimage/publish-appimage
Executable file
|
@ -0,0 +1,708 @@
|
|||
#!/bin/bash
|
||||
################################################################################
|
||||
# PROJECT : Publish-AppImage for .NET
|
||||
# WEBPAGE : https://github.com/kuiperzone/Publish-AppImage
|
||||
# COPYRIGHT : Andy Thomas 2021-2023
|
||||
# LICENSE : MIT
|
||||
################################################################################
|
||||
|
||||
###############################
|
||||
# CONSTANTS
|
||||
###############################
|
||||
|
||||
declare -r _SCRIPT_VERSION="1.3.1"
|
||||
declare -r _SCRIPT_TITLE="Publish-AppImage for .NET"
|
||||
declare -r _SCRIPT_IMPL_MIN=1
|
||||
declare -r _SCRIPT_IMPL_MAX=1
|
||||
declare -r _SCRIPT_COPYRIGHT="Copyright 2023 Andy Thomas"
|
||||
declare -r _SCRIPT_WEBSITE="https://github.com/kuiperzone/Publish-AppImage"
|
||||
|
||||
declare -r _SCRIPT_NAME="publish-appimage"
|
||||
declare -r _DEFAULT_CONF="${_SCRIPT_NAME}.conf"
|
||||
|
||||
declare -r _APPIMAGE_KIND="appimage"
|
||||
declare -r _ZIP_KIND="zip"
|
||||
declare -r _DOTNET_NONE="null"
|
||||
|
||||
|
||||
###############################
|
||||
# FUNCTIONS
|
||||
###############################
|
||||
|
||||
function assert_result
|
||||
{
|
||||
local _ret=$?
|
||||
|
||||
if [ ${_ret} -ne 0 ]; then
|
||||
echo
|
||||
exit ${_ret}
|
||||
fi
|
||||
}
|
||||
|
||||
function exec_or_die
|
||||
{
|
||||
echo "${1}"
|
||||
eval "${1}"
|
||||
assert_result
|
||||
}
|
||||
|
||||
function ensure_directory
|
||||
{
|
||||
local _path="${1}"
|
||||
|
||||
if [ ! -d "${_path}" ]; then
|
||||
mkdir -p "${_path}"
|
||||
assert_result
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_path
|
||||
{
|
||||
local _path="${1}"
|
||||
|
||||
if [ -d "${_path}" ]; then
|
||||
rm -rf "${_path}"
|
||||
assert_result
|
||||
elif [ -f "${_path}" ]; then
|
||||
rm -f "${_path}"
|
||||
assert_result
|
||||
fi
|
||||
}
|
||||
|
||||
function assert_mandatory
|
||||
{
|
||||
local _name="${1}"
|
||||
local _value="${2}"
|
||||
|
||||
if [ "${_value}" == "" ]; then
|
||||
echo "${_name} undefined in: ${_conf_arg_value}"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function assert_opt_file
|
||||
{
|
||||
local _name="${1}"
|
||||
local _value="${2}"
|
||||
|
||||
if [ "${_value}" != "" ] && [ ! -f "${_value}" ]; then
|
||||
echo "File not found: ${_value}"
|
||||
|
||||
if [ "${_name}" != "" ]; then
|
||||
echo "See ${_name} in: ${_conf_arg_value}"
|
||||
fi
|
||||
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
###############################
|
||||
# HANDLE ARGUMENTS
|
||||
###############################
|
||||
|
||||
# Specify conf file
|
||||
declare -r _CONF_ARG="f"
|
||||
declare -r _CONF_ARG_NAME="conf"
|
||||
_conf_arg_value="${_DEFAULT_CONF}"
|
||||
_arg_syntax=":${_CONF_ARG}:"
|
||||
|
||||
# Runtime ID
|
||||
declare -r _RID_ARG="r"
|
||||
declare -r _RID_ARG_NAME="runtime"
|
||||
_rid_arg_value="linux-x64"
|
||||
_arg_syntax="${_arg_syntax}${_RID_ARG}:"
|
||||
|
||||
# Package kind
|
||||
declare -r _KIND_ARG="k"
|
||||
declare -r _KIND_ARG_NAME="kind"
|
||||
declare -l _kind_arg_value="${_APPIMAGE_KIND}"
|
||||
_arg_syntax="${_arg_syntax}${_KIND_ARG}:"
|
||||
|
||||
# Run app
|
||||
declare -r _RUNAPP_ARG="u"
|
||||
declare -r _RUNAPP_ARG_NAME="run"
|
||||
_runapp_arg_value=false
|
||||
_arg_syntax="${_arg_syntax}${_RUNAPP_ARG}"
|
||||
|
||||
# Verbose
|
||||
declare -r _VERBOSE_ARG="b"
|
||||
declare -r _VERBOSE_ARG_NAME="verbose"
|
||||
_verbose_arg_value=false
|
||||
_arg_syntax="${_arg_syntax}${_VERBOSE_ARG}"
|
||||
|
||||
# Skip yes (no prompt)
|
||||
declare -r _SKIPYES_ARG="y"
|
||||
declare -r _SKIPYES_ARG_NAME="skip-yes"
|
||||
_skipyes_arg_value=false
|
||||
_arg_syntax="${_arg_syntax}${_SKIPYES_ARG}"
|
||||
|
||||
# Output name
|
||||
declare -r _OUTPUT_ARG="o"
|
||||
declare -r _OUTPUT_ARG_NAME="output"
|
||||
_output_arg_value=""
|
||||
_arg_syntax="${_arg_syntax}${_OUTPUT_ARG}:"
|
||||
|
||||
# Show version
|
||||
declare -r _VERSION_ARG="v"
|
||||
declare -r _VERSION_ARG_NAME="version"
|
||||
_version_arg_value=false
|
||||
_arg_syntax="${_arg_syntax}${_VERSION_ARG}"
|
||||
|
||||
# Show help
|
||||
declare -r _HELP_ARG="h"
|
||||
declare -r _HELP_ARG_NAME="help"
|
||||
_help_arg_value=false
|
||||
_arg_syntax="${_arg_syntax}${_HELP_ARG}"
|
||||
|
||||
_exit_help=0
|
||||
|
||||
# Transform long options to short ones
|
||||
for arg in "${@}"; do
|
||||
shift
|
||||
case "${arg}" in
|
||||
("--${_CONF_ARG_NAME}") set -- "$@" "-${_CONF_ARG}" ;;
|
||||
("--${_RID_ARG_NAME}") set -- "$@" "-${_RID_ARG}" ;;
|
||||
("--${_KIND_ARG_NAME}") set -- "$@" "-${_KIND_ARG}" ;;
|
||||
("--${_RUNAPP_NAME}") set -- "$@" "-${_RUNAPP_ARG}" ;;
|
||||
("--${_VERBOSE_ARG_NAME}") set -- "$@" "-${_VERBOSE_ARG}" ;;
|
||||
("--${_SKIPYES_ARG_NAME}") set -- "$@" "-${_SKIPYES_ARG}" ;;
|
||||
("--${_OUTPUT_ARG_NAME}") set -- "$@" "-${_OUTPUT_ARG}" ;;
|
||||
("--${_VERSION_ARG_NAME}") set -- "$@" "-${_VERSION_ARG}" ;;
|
||||
("--${_HELP_ARG_NAME}") set -- "$@" "-${_HELP_ARG}" ;;
|
||||
("--"*)
|
||||
echo "Illegal argument: ${arg}"
|
||||
echo
|
||||
|
||||
_exit_help=1
|
||||
break
|
||||
;;
|
||||
(*) set -- "$@" "${arg}" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ${_exit_help} == 0 ]; then
|
||||
# Read arguments
|
||||
while getopts ${_arg_syntax} arg; do
|
||||
case "${arg}" in
|
||||
(${_CONF_ARG}) _conf_arg_value="${OPTARG}" ;;
|
||||
(${_RID_ARG}) _rid_arg_value="${OPTARG}" ;;
|
||||
(${_KIND_ARG}) _kind_arg_value="${OPTARG}" ;;
|
||||
(${_RUNAPP_ARG}) _runapp_arg_value=true ;;
|
||||
(${_VERBOSE_ARG}) _verbose_arg_value=true ;;
|
||||
(${_SKIPYES_ARG}) _skipyes_arg_value=true ;;
|
||||
(${_OUTPUT_ARG}) _output_arg_value="${OPTARG}" ;;
|
||||
(${_VERSION_ARG}) _version_arg_value=true ;;
|
||||
(${_HELP_ARG}) _help_arg_value=true ;;
|
||||
(*)
|
||||
echo "Illegal argument"
|
||||
echo
|
||||
|
||||
_exit_help=1
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# Handle and help and version
|
||||
if [ ${_help_arg_value} == true ] || [ $_exit_help != 0 ]; then
|
||||
|
||||
_indent=" "
|
||||
echo "Usage:"
|
||||
echo "${_indent}${_SCRIPT_NAME} [-flags] [-option-n value-n]"
|
||||
echo
|
||||
|
||||
echo "Help Options:"
|
||||
echo "${_indent}-${_HELP_ARG}, --${_HELP_ARG_NAME}"
|
||||
echo "${_indent}Show help information flag."
|
||||
echo
|
||||
echo "${_indent}-${_VERSION_ARG}, --${_VERSION_ARG_NAME}"
|
||||
echo "${_indent}Show version and about information flag."
|
||||
echo
|
||||
|
||||
echo "Build Options:"
|
||||
echo "${_indent}-${_CONF_ARG}, --${_CONF_ARG_NAME} value"
|
||||
echo "${_indent}Specifies the conf file. Defaults to ${_SCRIPT_NAME}.conf."
|
||||
echo
|
||||
echo "${_indent}-${_RID_ARG}, --${_RID_ARG_NAME} value"
|
||||
echo "${_indent}Dotnet publish runtime identifier. Valid examples include:"
|
||||
echo "${_indent}linux-x64 and linux-arm64. Default is linux-x64 if unspecified."
|
||||
echo "${_indent}See also: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog"
|
||||
echo
|
||||
echo "${_indent}-${_KIND_ARG}, --${_KIND_ARG_NAME} value"
|
||||
echo "${_indent}Package output kind. Value must be one of: ${_APPIMAGE_KIND} or ${_ZIP_KIND}."
|
||||
echo "${_indent}Default is ${_APPIMAGE_KIND} if unspecified."
|
||||
echo
|
||||
echo "${_indent}-${_VERBOSE_ARG}, --${_VERBOSE_ARG_NAME}"
|
||||
echo "${_indent}Verbose review info output flag."
|
||||
echo
|
||||
echo "${_indent}-${_RUNAPP_ARG}, --${_RUNAPP_ARG_NAME}"
|
||||
echo "${_indent}Run the application after successful build flag."
|
||||
echo
|
||||
echo "${_indent}-${_SKIPYES_ARG}, --${_SKIPYES_ARG_NAME}"
|
||||
echo "${_indent}Skip confirmation prompt flag (assumes yes)."
|
||||
echo
|
||||
echo "${_indent}-${_OUTPUT_ARG}, --${_OUTPUT_ARG_NAME}"
|
||||
echo "${_indent}Explicit final output filename (excluding directory part)."
|
||||
echo
|
||||
|
||||
echo "Example:"
|
||||
echo "${_indent}${_SCRIPT_NAME} -${_RID_ARG} linux-arm64"
|
||||
echo
|
||||
|
||||
exit $_exit_help
|
||||
fi
|
||||
|
||||
if [ ${_version_arg_value} == true ]; then
|
||||
echo
|
||||
echo "${_SCRIPT_TITLE}, ${_SCRIPT_VERSION}"
|
||||
echo "${_SCRIPT_COPYRIGHT}"
|
||||
echo "${_SCRIPT_WEBSITE}"
|
||||
echo
|
||||
echo "MIT License"
|
||||
echo
|
||||
echo "Permission is hereby granted, free of charge, to any person obtaining a copy"
|
||||
echo "of this software and associated documentation files (the "Software"), to deal"
|
||||
echo "in the Software without restriction, including without limitation the rights"
|
||||
echo "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell"
|
||||
echo "copies of the Software, and to permit persons to whom the Software is"
|
||||
echo "furnished to do so, subject to the following conditions:"
|
||||
echo
|
||||
echo "The above copyright notice and this permission notice shall be included in all"
|
||||
echo "copies or substantial portions of the Software."
|
||||
echo
|
||||
echo "THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR"
|
||||
echo "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,"
|
||||
echo "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE"
|
||||
echo "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER"
|
||||
echo "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,"
|
||||
echo "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE"
|
||||
echo "SOFTWARE."
|
||||
echo
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
###############################
|
||||
# SOURCE & WORKING
|
||||
###############################
|
||||
|
||||
# Export these now as may be
|
||||
# useful in an advanced config file
|
||||
export DOTNET_RID="${_rid_arg_value}"
|
||||
export PKG_KIND="${_kind_arg_value}"
|
||||
export ISO_DATE=`date +"%Y-%m-%d"`
|
||||
|
||||
if [ ! -f "${_conf_arg_value}" ]; then
|
||||
echo "Configuration file not found: ${_conf_arg_value}"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Export contents to any post publish command
|
||||
set -a
|
||||
|
||||
# Source local to PWD
|
||||
source "${_conf_arg_value}"
|
||||
set +a
|
||||
|
||||
# For AppImage tool and backward compatibility
|
||||
export VERSION="${APP_VERSION}"
|
||||
|
||||
|
||||
# Then change PWD to conf file
|
||||
cd "$(dirname "${_conf_arg_value}")"
|
||||
|
||||
|
||||
###############################
|
||||
# SANITY
|
||||
###############################
|
||||
|
||||
if (( ${CONF_IMPL_VERSION} < ${_SCRIPT_IMPL_MIN} )) || (( ${CONF_IMPL_VERSION} > ${_SCRIPT_IMPL_MAX} )); then
|
||||
echo "Configuration format version ${_SCRIPT_IMPL_VERSION} not compatible"
|
||||
echo "Older conf file but newer ${_SCRIPT_NAME} implementation?"
|
||||
echo "Update from: ${_SCRIPT_WEBSITE}"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
assert_mandatory "APP_MAIN" "${APP_MAIN}"
|
||||
assert_mandatory "APP_ID" "${APP_ID}"
|
||||
assert_mandatory "APP_ICON_SRC" "${APP_ICON_SRC}"
|
||||
assert_mandatory "DE_NAME" "${DE_NAME}"
|
||||
assert_mandatory "DE_CATEGORIES" "${DE_CATEGORIES}"
|
||||
assert_mandatory "PKG_OUTPUT_DIR" "${PKG_OUTPUT_DIR}"
|
||||
|
||||
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
|
||||
assert_mandatory "APPIMAGETOOL_COMMAND" "${APPIMAGETOOL_COMMAND}"
|
||||
fi
|
||||
|
||||
assert_opt_file "APP_ICON_SRC" "${APP_ICON_SRC}"
|
||||
assert_opt_file "APP_XML_SRC" "${APP_XML_SRC}"
|
||||
|
||||
if [ "${DE_TERMINAL_FLAG}" != "true" ] && [ "${DE_TERMINAL_FLAG}" != "false" ]; then
|
||||
echo "DE_TERMINAL_FLAG invalid value: ${DE_TERMINAL_FLAG}"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${DOTNET_PROJECT_PATH}" == "${_DOTNET_NONE}" ] && [ "${POST_PUBLISH}" == "" ]; then
|
||||
echo "No publish or build operation defined (nothing will be built)"
|
||||
echo "See DOTNET_PROJECT_PATH and POST_PUBLISH in: ${_conf_arg_value}"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${DOTNET_PROJECT_PATH}" != "" ] && [ "${DOTNET_PROJECT_PATH}" != "${_DOTNET_NONE}" ] &&
|
||||
[ ! -f "${DOTNET_PROJECT_PATH}" ] && [ ! -d "${DOTNET_PROJECT_PATH}" ]; then
|
||||
echo "DOTNET_PROJECT_PATH path not found: ${DOTNET_PROJECT_PATH}"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${_kind_arg_value}" != "${_APPIMAGE_KIND}" ] && [ "${_kind_arg_value}" != "${_ZIP_KIND}" ]; then
|
||||
echo "Invalid argument value: ${_kind_arg_value}"
|
||||
echo "Use one of: ${_APPIMAGE_KIND} or ${_ZIP_KIND}"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Detect if publish for windows
|
||||
_exec_ext=""
|
||||
declare -l _tw="${_rid_arg_value}"
|
||||
|
||||
if [[ "${_tw}" == "win"* ]]; then
|
||||
|
||||
# May use this in future
|
||||
_exec_ext=".exe"
|
||||
|
||||
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
|
||||
echo "Invalid AppImage payload"
|
||||
echo "Looks like a windows binary to be packaged as AppImage."
|
||||
echo "Use --${_KIND_ARG_NAME} ${_ZIP_KIND} instead."
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
###############################
|
||||
# VARIABLES
|
||||
###############################
|
||||
|
||||
# Abbreviate RID where it maps well to arch
|
||||
if [ "${_rid_arg_value}" == "linux-x64" ]; then
|
||||
_file_out_arch="-x86_64"
|
||||
elif [ "${_rid_arg_value}" == "linux-arm64" ]; then
|
||||
_file_out_arch="-aarch64"
|
||||
else
|
||||
# Otherwise use RID itself
|
||||
_file_out_arch="-${_rid_arg_value}"
|
||||
fi
|
||||
|
||||
# APPDIR LOCATIONS
|
||||
export APPDIR_ROOT="${PKG_OUTPUT_DIR}/AppDir"
|
||||
|
||||
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
|
||||
# AppImage
|
||||
export APPDIR_USR="${APPDIR_ROOT}/usr"
|
||||
export APPDIR_BIN="${APPDIR_ROOT}/usr/bin"
|
||||
export APPDIR_SHARE="${APPDIR_ROOT}/usr/share"
|
||||
|
||||
_local_run="usr/bin/${APP_MAIN}${_exec_ext}"
|
||||
else
|
||||
# Simple zip
|
||||
export APPDIR_USR=""
|
||||
export APPDIR_BIN="${APPDIR_ROOT}"
|
||||
export APPDIR_SHARE="${APPDIR_ROOT}"
|
||||
|
||||
_local_run="${APP_MAIN}${_exec_ext}"
|
||||
fi
|
||||
|
||||
export APPRUN_TARGET="${APPDIR_BIN}/${APP_MAIN}${_exec_ext}"
|
||||
|
||||
|
||||
# DOTNET PUBLISH
|
||||
if [ "${DOTNET_PROJECT_PATH}" != "${_DOTNET_NONE}" ]; then
|
||||
|
||||
_publish_cmd="dotnet publish"
|
||||
|
||||
if [ "${DOTNET_PROJECT_PATH}" != "" ] && [ "${DOTNET_PROJECT_PATH}" != "." ]; then
|
||||
_publish_cmd="${_publish_cmd} \"${DOTNET_PROJECT_PATH}\""
|
||||
fi
|
||||
|
||||
_publish_cmd="${_publish_cmd} -r ${_rid_arg_value}"
|
||||
|
||||
if [ "${APP_VERSION}" != "" ]; then
|
||||
_publish_cmd="${_publish_cmd} -p:Version=${APP_VERSION}"
|
||||
fi
|
||||
|
||||
if [ "${DOTNET_PUBLISH_ARGS}" != "" ]; then
|
||||
_publish_cmd="${_publish_cmd} ${DOTNET_PUBLISH_ARGS}"
|
||||
fi
|
||||
|
||||
_publish_cmd="${_publish_cmd} -o \"${APPDIR_BIN}\""
|
||||
|
||||
fi
|
||||
|
||||
|
||||
# PACKAGE OUTPUT
|
||||
if [ $PKG_VERSION_FLAG == true ] && [ "${APP_VERSION}" != "" ]; then
|
||||
_version_out="-${APP_VERSION}"
|
||||
fi
|
||||
|
||||
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
|
||||
|
||||
# AppImageTool
|
||||
if [ "${_output_arg_value}" != "" ]; then
|
||||
_package_out="${PKG_OUTPUT_DIR}/${_output_arg_value}"
|
||||
else
|
||||
_package_out="${PKG_OUTPUT_DIR}/${APP_MAIN}${_version_out}${_file_out_arch}${PKG_APPIMAGE_SUFFIX}"
|
||||
fi
|
||||
|
||||
_package_cmd="${APPIMAGETOOL_COMMAND}"
|
||||
|
||||
if [ "${PKG_APPIMAGE_ARGS}" != "" ]; then
|
||||
_package_cmd="${_package_cmd} ${PKG_APPIMAGE_ARGS}"
|
||||
fi
|
||||
|
||||
_package_cmd="${_package_cmd} \"${APPDIR_ROOT}\" \"${_package_out}\""
|
||||
|
||||
if [ ${_runapp_arg_value} == true ]; then
|
||||
_packrun_cmd="${_package_out}"
|
||||
fi
|
||||
|
||||
else
|
||||
|
||||
# Simple zip
|
||||
if [ "${_output_arg_value}" != "" ]; then
|
||||
_package_out="${PKG_OUTPUT_DIR}/${_output_arg_value}"
|
||||
else
|
||||
_package_out="${PKG_OUTPUT_DIR}/${APP_MAIN}${_version_out}${_file_out_arch}.zip"
|
||||
fi
|
||||
|
||||
_package_cmd="(cd \"${APPDIR_ROOT}\" && zip -r \"${PWD}/${_package_out}\" ./)"
|
||||
|
||||
if [ ${_runapp_arg_value} == true ]; then
|
||||
_packrun_cmd="${APPRUN_TARGET}"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
|
||||
###############################
|
||||
# DESKTOP ENTRY & APPDATA
|
||||
###############################
|
||||
|
||||
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
|
||||
|
||||
_desktop="[Desktop Entry]\n"
|
||||
_desktop="${_desktop}Type=Application\n"
|
||||
_desktop="${_desktop}Name=${DE_NAME}\n"
|
||||
_desktop="${_desktop}Exec=AppRun\n"
|
||||
_desktop="${_desktop}Terminal=${DE_TERMINAL_FLAG}\n"
|
||||
_desktop="${_desktop}Categories=${DE_CATEGORIES}\n"
|
||||
|
||||
# Follow app-id
|
||||
_desktop="${_desktop}Icon=${APP_ID}\n"
|
||||
|
||||
if [ "${DE_COMMENT}" != "" ]; then
|
||||
_desktop="${_desktop}Comment=${DE_COMMENT}\n"
|
||||
fi
|
||||
|
||||
if [ "${DE_KEYWORDS}" != "" ]; then
|
||||
_desktop="${_desktop}Keywords=${DE_KEYWORDS}\n"
|
||||
fi
|
||||
|
||||
_desktop="${_desktop}${DE_EXTEND}\n"
|
||||
fi
|
||||
|
||||
|
||||
# Load appdata.xml
|
||||
if [ "${APP_XML_SRC}" != "" ]; then
|
||||
|
||||
if command -v envsubst &> /dev/null; then
|
||||
_appxml=$(envsubst <"${APP_XML_SRC}")
|
||||
else
|
||||
_appxml=$(<"${APP_XML_SRC}")
|
||||
echo "WARNING: Variable substitution not available for: ${APP_XML_SRC}"
|
||||
echo
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
|
||||
###############################
|
||||
# DISPLAY & CONFIRM
|
||||
###############################
|
||||
|
||||
echo "${_SCRIPT_TITLE}, ${_SCRIPT_VERSION}"
|
||||
echo "${_SCRIPT_COPYRIGHT}"
|
||||
echo
|
||||
|
||||
echo "APP_MAIN: ${APP_MAIN}"
|
||||
echo "APP_ID: ${APP_ID}"
|
||||
echo "APP_VERSION: ${APP_VERSION}"
|
||||
echo "OUTPUT: ${_package_out}"
|
||||
echo
|
||||
|
||||
if [ "${_desktop}" != "" ]; then
|
||||
echo -e "${_desktop}"
|
||||
fi
|
||||
|
||||
if [ ${_verbose_arg_value} == true ] && [ "${_appxml}" != "" ]; then
|
||||
echo -e "${_appxml}\n"
|
||||
fi
|
||||
|
||||
echo "Build Commands:"
|
||||
|
||||
if [ "${_publish_cmd}" != "" ]; then
|
||||
echo
|
||||
echo "${_publish_cmd}"
|
||||
fi
|
||||
|
||||
if [ "${POST_PUBLISH}" != "" ]; then
|
||||
echo
|
||||
echo "${POST_PUBLISH}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "${_package_cmd}"
|
||||
echo
|
||||
|
||||
# Prompt
|
||||
if [ $_skipyes_arg_value == false ]; then
|
||||
|
||||
echo
|
||||
read -p "Build now [N/y]? " prompt
|
||||
|
||||
if [ "${prompt}" != "y" ] && [ "${prompt}" != "Y" ]; then
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Continue
|
||||
echo
|
||||
fi
|
||||
|
||||
|
||||
###############################
|
||||
# PUBLISH & BUILD
|
||||
###############################
|
||||
|
||||
# Clean and ensure directoy exists
|
||||
ensure_directory "${PKG_OUTPUT_DIR}"
|
||||
remove_path "${APPDIR_ROOT}"
|
||||
remove_path "${_package_out}"
|
||||
|
||||
# Create AppDir structure
|
||||
ensure_directory "${APPDIR_BIN}"
|
||||
|
||||
if [ "${_kind_arg_value}" != "${_ZIP_KIND}" ]; then
|
||||
# We also create usr/share/icons, as some packages require this.
|
||||
# See: https://github.com/kuiperzone/Publish-AppImage/issues/7
|
||||
ensure_directory "${APPDIR_SHARE}/icons"
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# Publish dotnet
|
||||
if [ "${_publish_cmd}" != "" ]; then
|
||||
exec_or_die "${_publish_cmd}"
|
||||
echo
|
||||
fi
|
||||
|
||||
# Post-publish
|
||||
if [ "${POST_PUBLISH}" != "" ]; then
|
||||
|
||||
exec_or_die "${POST_PUBLISH}"
|
||||
echo
|
||||
|
||||
fi
|
||||
|
||||
# Application file must exist!
|
||||
if [ ! -f "${APPRUN_TARGET}" ]; then
|
||||
echo "Expected application file not found: ${APPRUN_TARGET}"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${_kind_arg_value}" == "${_APPIMAGE_KIND}" ]; then
|
||||
|
||||
echo
|
||||
|
||||
# Create desktop
|
||||
if [ "${_desktop}" != "" ]; then
|
||||
_file="${APPDIR_ROOT}/${APP_ID}.desktop"
|
||||
echo "Creating: ${_file}"
|
||||
echo -e "${_desktop}" > "${_file}"
|
||||
assert_result
|
||||
fi
|
||||
|
||||
if [ "${_appxml}" != "" ]; then
|
||||
_dir="${APPDIR_SHARE}/metainfo"
|
||||
_file="${_dir}/${APP_ID}.appdata.xml"
|
||||
echo "Creating: ${_file}"
|
||||
ensure_directory "${_dir}"
|
||||
echo -e "${_appxml}" > "${_file}"
|
||||
assert_result
|
||||
|
||||
if [ "${_desktop}" != "" ]; then
|
||||
# Copy of desktop under "applications"
|
||||
# Needed for launchable in appinfo.xml (if used)
|
||||
# See https://github.com/AppImage/AppImageKit/issues/603
|
||||
_dir="${APPDIR_SHARE}/applications"
|
||||
_file="${_dir}/${APP_ID}.desktop"
|
||||
echo "Creating: ${_file}"
|
||||
ensure_directory "${_dir}"
|
||||
echo -e "${_desktop}" > "${_file}"
|
||||
assert_result
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy icon
|
||||
if [ "${APP_ICON_SRC}" != "" ]; then
|
||||
|
||||
_icon_ext="${APP_ICON_SRC##*.}"
|
||||
|
||||
if [ "${_icon_ext}" != "" ]; then
|
||||
_icon_ext=".${_icon_ext}"
|
||||
fi
|
||||
|
||||
_temp="${APPDIR_ROOT}/${APP_ID}${_icon_ext}"
|
||||
echo "Creating: ${_temp}"
|
||||
|
||||
cp "${APP_ICON_SRC}" "${_temp}"
|
||||
assert_result
|
||||
fi
|
||||
|
||||
# AppRun
|
||||
_temp="${APPDIR_ROOT}/AppRun"
|
||||
|
||||
if [ ! -f "${_temp}" ]; then
|
||||
|
||||
echo "Creating: ${_temp}"
|
||||
ln -s "${_local_run}" "${_temp}"
|
||||
assert_result
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build package
|
||||
echo
|
||||
exec_or_die "${_package_cmd}"
|
||||
echo
|
||||
|
||||
echo "OUTPUT OK: ${_package_out}"
|
||||
echo
|
||||
|
||||
if [ "${_packrun_cmd}" != "" ]; then
|
||||
echo "RUNNING ..."
|
||||
exec_or_die "${_packrun_cmd}"
|
||||
echo
|
||||
fi
|
||||
|
||||
exit 0
|
140
build/resources/appimage/publish-appimage.conf
Normal file
140
build/resources/appimage/publish-appimage.conf
Normal file
|
@ -0,0 +1,140 @@
|
|||
################################################################################
|
||||
# BASH FORMAT CONFIG: Publish-AppImage for .NET
|
||||
# WEBPAGE : https://kuiper.zone/publish-appimage-dotnet/
|
||||
################################################################################
|
||||
|
||||
|
||||
########################################
|
||||
# Application
|
||||
########################################
|
||||
|
||||
# Mandatory application (file) name. This must be the base name of the main
|
||||
# runnable file to be created by the publish/build process. It should NOT
|
||||
# include any directory part or extension, i.e. do not append ".exe" or ".dll"
|
||||
# for dotnet. Example: "MyApp"
|
||||
APP_MAIN="sourcegit"
|
||||
|
||||
# Mandatory application ID in reverse DNS form, i.e. "tld.my-domain.MyApp".
|
||||
# Exclude any ".desktop" post-fix. Note that reverse DNS form is necessary
|
||||
# for compatibility with Freedesktop.org metadata.
|
||||
APP_ID="com.sourcegit-scm.SourceGit"
|
||||
|
||||
# Mandatory icon source file relative to this file (appimagetool seems to
|
||||
# require this). Use .svg or .png only. PNG should be one of standard sizes,
|
||||
# i.e, 128x128 or 256x256 pixels. Example: "Assets/app.svg"
|
||||
APP_ICON_SRC="sourcegit.png"
|
||||
|
||||
# Optional Freedesktop.org metadata source file relative to this file. It is not essential
|
||||
# (leave empty) but will be used by appimagetool for repository information if provided.
|
||||
# See for information: https://docs.appimage.org/packaging-guide/optional/appstream.html
|
||||
# NB. The file may embed bash variables defined in this file and those listed below
|
||||
# (these will be substituted during the build). Examples include: "<id>${APP_ID}</id>"
|
||||
# and "<release version="${APP_VERSION}" date="${ISO_DATE}">".
|
||||
# $ISO_DATE : date of build, i.e. "2021-10-29",
|
||||
# $APP_VERSION : application version (if provided),
|
||||
# Example: "Assets/appdata.xml".
|
||||
APP_XML_SRC="sourcegit.appdata.xml"
|
||||
|
||||
|
||||
########################################
|
||||
# Desktop Entry
|
||||
########################################
|
||||
|
||||
# Mandatory friendly name of the application.
|
||||
DE_NAME="SourceGit"
|
||||
|
||||
# Mandatory category(ies), separated with semicolon, in which the entry should be
|
||||
# shown. See https://specifications.freedesktop.org/menu-spec/latest/apa.html
|
||||
# Examples: "Development", "Graphics", "Network", "Utility" etc.
|
||||
DE_CATEGORIES="Utility"
|
||||
|
||||
# Optional short comment text (single line).
|
||||
# Example: "Perform calculations"
|
||||
DE_COMMENT="Open-source GUI client for git users"
|
||||
|
||||
# Optional keywords, separated with semicolon. Values are not meant for
|
||||
# display and should not be redundant with the value of DE_NAME.
|
||||
DE_KEYWORDS=""
|
||||
|
||||
# Flag indicating whether the program runs in a terminal window. Use true or false only.
|
||||
DE_TERMINAL_FLAG=false
|
||||
|
||||
# Optional name-value text to be appended to the Desktop Entry file, thus providing
|
||||
# additional metadata. Name-values should not be redundant with values above and
|
||||
# are to be terminated with new line ("\n").
|
||||
# Example: "Comment[fr]=Effectue des calculs compliqués\nMimeType=image/x-foo"
|
||||
DE_EXTEND=""
|
||||
|
||||
|
||||
########################################
|
||||
# Dotnet Publish
|
||||
########################################
|
||||
|
||||
# Optional path relative to this file in which to find the dotnet project (.csproj)
|
||||
# or solution (.sln) file, or the directory containing it. If empty (default), a single
|
||||
# project or solution file is expected under the same directory as this file.
|
||||
# IMPORTANT. If set to "null", dotnet publish is disabled (it is NOT called). Instead,
|
||||
# only POST_PUBLISH is called. Example: "Source/MyProject"
|
||||
DOTNET_PROJECT_PATH="../../../src/SourceGit.csproj"
|
||||
|
||||
# Optional arguments suppled to "dotnet publish". Do NOT include "-r" (runtime) or version here as they will
|
||||
# be added (see also $APP_VERSION). Typically you want as a minimum: "-c Release --self-contained true".
|
||||
# Additional useful arguments include:
|
||||
# "-p:DebugType=None -p:DebugSymbols=false -p:PublishSingleFile=true -p:PublishTrimmed=true -p:TrimMode=link"
|
||||
# Refer: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish
|
||||
DOTNET_PUBLISH_ARGS="-c Release -p:DebugType=None -p:DebugSymbols=false -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained"
|
||||
|
||||
|
||||
########################################
|
||||
# POST-PUBLISH
|
||||
########################################
|
||||
|
||||
# Optional post-publish or standalone build command. The value could, for example, copy
|
||||
# additional files into the "bin" directory. The working directory will be the location
|
||||
# of this file. The value is mandatory if DOTNET_PROJECT_PATH equals "null". In
|
||||
# addition to variables in this file, the following variables are exported prior:
|
||||
# $ISO_DATE : date of build, i.e. "2021-10-29",
|
||||
# $APP_VERSION : application version (if provided),
|
||||
# $DOTNET_RID : dotnet runtime identifier string provided at command line (i.e. "linux-x64),
|
||||
# $PKG_KIND : package kind (i.e. "appimage", "zip") provided at command line.
|
||||
# $APPDIR_ROOT : AppImage build directory root (i.e. "AppImages/AppDir").
|
||||
# $APPDIR_USR : AppImage user directory under root (i.e. "AppImages/AppDir/usr").
|
||||
# $APPDIR_BIN : AppImage bin directory under root (i.e. "AppImages/AppDir/usr/bin").
|
||||
# $APPRUN_TARGET : The expected target executable file (i.e. "AppImages/AppDir/usr/bin/app-name").
|
||||
# Example: "Assets/post-publish.sh"
|
||||
POST_PUBLISH="mv AppImages/AppDir/usr/bin/SourceGit AppImages/AppDir/usr/bin/sourcegit; rm -f AppImages/AppDir/usr/bin/*.dbg"
|
||||
|
||||
|
||||
########################################
|
||||
# Package Output
|
||||
########################################
|
||||
|
||||
# Additional arguments for use with appimagetool. See appimagetool --help.
|
||||
# Default is empty. Example: "--sign"
|
||||
PKG_APPIMAGE_ARGS="--runtime-file=runtime-x86_64"
|
||||
|
||||
# Mandatory output directory relative to this file. It will be created if it does not
|
||||
# exist. It will contain the final package file and temporary AppDir. Default: "AppImages".
|
||||
PKG_OUTPUT_DIR="AppImages"
|
||||
|
||||
# Boolean which sets whether to include the application version in the filename of the final
|
||||
# output package (i.e. "HelloWorld-1.2.3-x86_64.AppImage"). It is ignored if $APP_VERSION is
|
||||
# empty or the "output" command arg is specified. Default and recommended: false.
|
||||
PKG_VERSION_FLAG=false
|
||||
|
||||
# Optional AppImage output filename extension. It is ignored if generating a zip file, or if
|
||||
# the "output" command arg is specified. Default and recommended: ".AppImage".
|
||||
PKG_APPIMAGE_SUFFIX=".AppImage"
|
||||
|
||||
|
||||
########################################
|
||||
# Advanced Other
|
||||
########################################
|
||||
|
||||
# The appimagetool command. Default is "appimagetool" which is expected to be found
|
||||
# in the $PATH. If the tool is not in path or has different name, a full path can be given,
|
||||
# example: "/home/user/Apps/appimagetool-x86_64.AppImage"
|
||||
APPIMAGETOOL_COMMAND="appimagetool"
|
||||
|
||||
# Internal use only. Used for compatibility between conf and script. Do not modify.
|
||||
CONF_IMPL_VERSION=1
|
BIN
build/resources/appimage/runtime-x86_64
Executable file
BIN
build/resources/appimage/runtime-x86_64
Executable file
Binary file not shown.
15
build/resources/appimage/sourcegit.appdata.xml
Normal file
15
build/resources/appimage/sourcegit.appdata.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.sourcegit-scm.SourceGit</id>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<name>SourceGit</name>
|
||||
<summary>Open-source GUI client for git users</summary>
|
||||
<description>
|
||||
<p>Open-source GUI client for git users</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">com.sourcegit-scm.SourceGit.desktop</launchable>
|
||||
<provides>
|
||||
<id>com.sourcegit-scm.SourceGit.desktop</id>
|
||||
</provides>
|
||||
</component>
|
BIN
build/resources/appimage/sourcegit.png
Normal file
BIN
build/resources/appimage/sourcegit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
|
@ -4,10 +4,11 @@ using System.Text.Json.Serialization;
|
|||
namespace SourceGit
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)]
|
||||
[JsonSerializable(typeof(Models.Version))]
|
||||
[JsonSerializable(typeof(Models.JetBrainsState))]
|
||||
[JsonSerializable(typeof(List<Models.InteractiveRebaseJob>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||
[JsonSerializable(typeof(Models.JetBrainsState))]
|
||||
[JsonSerializable(typeof(Models.Version))]
|
||||
[JsonSerializable(typeof(Models.CustomColorSchema))]
|
||||
[JsonSerializable(typeof(ViewModels.Preference))]
|
||||
[JsonSerializable(typeof(ViewModels.RepositorySettings))]
|
||||
internal partial class JsonCodeGen : JsonSerializerContext { }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
|
@ -159,15 +160,33 @@ namespace SourceGit
|
|||
app._colorOverrides = null;
|
||||
}
|
||||
|
||||
Models.CommitGraph.SetDefaultPens();
|
||||
|
||||
if (!string.IsNullOrEmpty(colorsFile) && File.Exists(colorsFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
var resDic = new ResourceDictionary();
|
||||
|
||||
var schema = JsonSerializer.Deserialize(File.ReadAllText(colorsFile), JsonCodeGen.Default.DictionaryStringString);
|
||||
foreach (var kv in schema)
|
||||
resDic[kv.Key] = Color.Parse(kv.Value);
|
||||
var schema = JsonSerializer.Deserialize(File.ReadAllText(colorsFile), JsonCodeGen.Default.CustomColorSchema);
|
||||
foreach (var kv in schema.Basic)
|
||||
{
|
||||
if (kv.Key.Equals("SystemAccentColor", StringComparison.Ordinal))
|
||||
resDic["SystemAccentColor"] = Color.Parse(kv.Value);
|
||||
else
|
||||
resDic[$"Color.{kv.Key}"] = Color.Parse(kv.Value);
|
||||
}
|
||||
|
||||
|
||||
if (schema.Graph.Count > 0)
|
||||
{
|
||||
var penColors = new List<Color>();
|
||||
|
||||
foreach (var c in schema.Graph)
|
||||
penColors.Add(Color.Parse(c));
|
||||
|
||||
Models.CommitGraph.SetPenColors(penColors);
|
||||
}
|
||||
|
||||
app.Resources.MergedDictionaries.Add(resDic);
|
||||
app._colorOverrides = resDic;
|
||||
|
@ -187,6 +206,18 @@ namespace SourceGit
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task<string> GetClipboardTextAsync()
|
||||
{
|
||||
if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (desktop.MainWindow.Clipboard is { } clipboard)
|
||||
{
|
||||
return await clipboard.GetTextAsync();
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public static string Text(string key, params object[] args)
|
||||
{
|
||||
var fmt = Current.FindResource($"Text.{key}") as string;
|
||||
|
@ -259,6 +290,21 @@ namespace SourceGit
|
|||
});
|
||||
}
|
||||
|
||||
public static ViewModels.Repository FindOpenedRepository(string repoPath)
|
||||
{
|
||||
if (Current is App app && app._launcher != null)
|
||||
{
|
||||
foreach (var page in app._launcher.Pages)
|
||||
{
|
||||
var id = page.Node.Id.Replace("\\", "/");
|
||||
if (id == repoPath && page.Data is ViewModels.Repository repo)
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Quit()
|
||||
{
|
||||
if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
|
@ -288,9 +334,10 @@ namespace SourceGit
|
|||
_launcher = new ViewModels.Launcher();
|
||||
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
||||
|
||||
if (ViewModels.Preference.Instance.ShouldCheck4UpdateOnStartup)
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
if (pref.ShouldCheck4UpdateOnStartup)
|
||||
{
|
||||
ViewModels.Preference.Save();
|
||||
pref.Save();
|
||||
Check4Update();
|
||||
}
|
||||
}
|
||||
|
@ -317,18 +364,6 @@ namespace SourceGit
|
|||
});
|
||||
}
|
||||
|
||||
public static async Task<string> GetClipboardTextAsync()
|
||||
{
|
||||
if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (desktop.MainWindow.Clipboard is { } clipboard)
|
||||
{
|
||||
return await clipboard.GetTextAsync();
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
private ViewModels.Launcher _launcher = null;
|
||||
private ResourceDictionary _activeLocale = null;
|
||||
private ResourceDictionary _colorOverrides = null;
|
||||
|
|
|
@ -9,7 +9,6 @@ namespace SourceGit.Commands
|
|||
|
||||
[GeneratedRegex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
private static readonly DateTime UTC_START = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime();
|
||||
|
||||
public Blame(string repo, string file, string revision)
|
||||
{
|
||||
|
@ -67,7 +66,7 @@ namespace SourceGit.Commands
|
|||
var commit = match.Groups[1].Value;
|
||||
var author = match.Groups[2].Value;
|
||||
var timestamp = int.Parse(match.Groups[3].Value);
|
||||
var when = UTC_START.AddSeconds(timestamp).ToString("yyyy/MM/dd");
|
||||
var when = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString("yyyy/MM/dd");
|
||||
|
||||
var info = new Models.BlameLineInfo()
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class Commit : Command
|
||||
{
|
||||
public Commit(string repo, string message, bool amend, bool allowEmpty = false)
|
||||
public Commit(string repo, string message, bool autoStage, bool amend, bool allowEmpty = false)
|
||||
{
|
||||
var file = Path.GetTempFileName();
|
||||
File.WriteAllText(file, message);
|
||||
|
@ -12,6 +12,8 @@ namespace SourceGit.Commands
|
|||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"commit --file=\"{file}\"";
|
||||
if (autoStage)
|
||||
Args += " --all";
|
||||
if (amend)
|
||||
Args += " --amend --no-edit";
|
||||
if (allowEmpty)
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class Fetch : Command
|
||||
{
|
||||
public Fetch(string repo, string remote, bool prune, Action<string> outputHandler)
|
||||
public Fetch(string repo, string remote, bool prune, bool noTags, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
|
@ -24,9 +24,15 @@ namespace SourceGit.Commands
|
|||
Args = "-c credential.helper=manager ";
|
||||
}
|
||||
|
||||
Args += "fetch --force --progress --verbose ";
|
||||
Args += "fetch --progress --verbose ";
|
||||
if (prune)
|
||||
Args += "--prune ";
|
||||
|
||||
if (noTags)
|
||||
Args += "--no-tags ";
|
||||
else
|
||||
Args += "--force ";
|
||||
|
||||
Args += remote;
|
||||
|
||||
AutoFetch.MarkFetched(repo);
|
||||
|
@ -132,7 +138,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var job = new Job
|
||||
{
|
||||
Cmd = new Fetch(repo, "--all", true, null) { RaiseError = false },
|
||||
Cmd = new Fetch(repo, "--all", true, false, null) { RaiseError = false },
|
||||
NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
|
||||
};
|
||||
|
||||
|
|
|
@ -6,7 +6,15 @@
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"check-attr -a -z \"{path}\"";
|
||||
Args = $"check-attr -z filter \"{path}\"";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public IsLFSFiltered(string repo, string sha, string path)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"check-attr --source {sha} -z filter \"{path}\"";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class Pull : Command
|
||||
{
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, Action<string> outputHandler)
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
|
@ -24,6 +24,9 @@ namespace SourceGit.Commands
|
|||
Args += "pull --verbose --progress --tags ";
|
||||
if (useRebase)
|
||||
Args += "--rebase ";
|
||||
if (noTags)
|
||||
Args += "--no-tags ";
|
||||
|
||||
Args += $"{remote} {branch}";
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class QueryFileSize : Command
|
||||
{
|
||||
|
||||
[GeneratedRegex(@"^\d+\s+\w+\s+[0-9a-f]+\s+(\d+)\s+.*$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
|
||||
|
@ -25,9 +24,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var match = REG_FORMAT().Match(rs.StdOut);
|
||||
if (match.Success)
|
||||
{
|
||||
return long.Parse(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public static void Run(string repo, string revision, string file, string saveTo)
|
||||
{
|
||||
var isLFSFiltered = new IsLFSFiltered(repo, file).Result();
|
||||
var isLFSFiltered = new IsLFSFiltered(repo, revision, file).Result();
|
||||
if (isLFSFiltered)
|
||||
{
|
||||
var tmpFile = saveTo + ".tmp";
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
public static class BranchConverters
|
||||
{
|
||||
public static readonly FuncValueConverter<Models.Branch, string> ToName =
|
||||
new FuncValueConverter<Models.Branch, string>(v => v.IsLocal ? v.Name : $"{v.Remote}/{v.Name}");
|
||||
}
|
||||
}
|
|
@ -11,8 +11,8 @@ namespace SourceGit.Converters
|
|||
new FuncValueConverter<Models.DecoratorType, IBrush>(v =>
|
||||
{
|
||||
if (v == Models.DecoratorType.Tag)
|
||||
return Models.DecoratorResources.Backgrounds[0];
|
||||
return Models.DecoratorResources.Backgrounds[1];
|
||||
return Application.Current.FindResource("Brush.DecoratorTag") as IBrush;
|
||||
return Application.Current.FindResource("Brush.DecoratorBranch") as IBrush;
|
||||
});
|
||||
|
||||
public static readonly FuncValueConverter<Models.DecoratorType, StreamGeometry> ToIcon =
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Avalonia.Data.Converters;
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
|
@ -24,5 +25,8 @@ namespace SourceGit.Converters
|
|||
|
||||
public static readonly FuncValueConverter<int, bool> IsSubjectLengthGood =
|
||||
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preference.Instance.SubjectGuideLength);
|
||||
|
||||
public static readonly FuncValueConverter<int, Thickness> ToTreeMargin =
|
||||
new FuncValueConverter<int, Thickness>(v => new Thickness(v * 16, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
|
@ -11,5 +12,11 @@ namespace SourceGit.Converters
|
|||
|
||||
public static readonly FuncValueConverter<IList, bool> IsNotNullOrEmpty =
|
||||
new FuncValueConverter<IList, bool>(v => v != null && v.Count > 0);
|
||||
|
||||
public static readonly FuncValueConverter<List<Models.Change>, List<Models.Change>> Top100Changes =
|
||||
new FuncValueConverter<List<Models.Change>, List<Models.Change>>(v => (v == null || v.Count < 100) ? v : v.GetRange(0, 100));
|
||||
|
||||
public static readonly FuncValueConverter<IList, bool> IsOnlyTop100Shows =
|
||||
new FuncValueConverter<IList, bool>(v => v != null && v.Count > 100);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,5 +11,7 @@
|
|||
public string UpstreamTrackStatus { get; set; }
|
||||
public string Remote { get; set; }
|
||||
public bool IsHead { get; set; }
|
||||
|
||||
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,14 +19,13 @@ namespace SourceGit.Models
|
|||
public bool IsMerged { get; set; } = false;
|
||||
public Thickness Margin { get; set; } = new Thickness(0);
|
||||
|
||||
public string AuthorTimeStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd HH:mm:ss");
|
||||
public string CommitterTimeStr => _utcStart.AddSeconds(CommitterTime).ToString("yyyy/MM/dd HH:mm:ss");
|
||||
public string AuthorTimeShortStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd");
|
||||
public string CommitterTimeShortStr => _utcStart.AddSeconds(CommitterTime).ToString("yyyy/MM/dd");
|
||||
|
||||
public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
|
||||
public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
|
||||
public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString("yyyy/MM/dd");
|
||||
public string CommitterTimeShortStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToString("yyyy/MM/dd");
|
||||
|
||||
public bool IsCommitterVisible => Author != Committer || AuthorTime != CommitterTime;
|
||||
public bool IsCurrentHead => Decorators.Find(x => x.Type is DecoratorType.CurrentBranchHead or DecoratorType.CurrentCommitHead) != null;
|
||||
|
||||
private static readonly DateTime _utcStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,17 +8,6 @@ namespace SourceGit.Models
|
|||
{
|
||||
public class CommitGraph
|
||||
{
|
||||
public static readonly Pen[] Pens = [
|
||||
new Pen(Brushes.Orange, 2),
|
||||
new Pen(Brushes.ForestGreen, 2),
|
||||
new Pen(Brushes.Gold, 2),
|
||||
new Pen(Brushes.Magenta, 2),
|
||||
new Pen(Brushes.Red, 2),
|
||||
new Pen(Brushes.Gray, 2),
|
||||
new Pen(Brushes.Turquoise, 2),
|
||||
new Pen(Brushes.Olive, 2),
|
||||
];
|
||||
|
||||
public class Path
|
||||
{
|
||||
public List<Point> Points = new List<Point>();
|
||||
|
@ -113,7 +102,28 @@ namespace SourceGit.Models
|
|||
public List<Link> Links { get; set; } = new List<Link>();
|
||||
public List<Dot> Dots { get; set; } = new List<Dot>();
|
||||
|
||||
public static CommitGraph Parse(List<Commit> commits, int colorCount)
|
||||
public static List<Pen> Pens
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<Pen>();
|
||||
|
||||
public static void SetDefaultPens()
|
||||
{
|
||||
SetPenColors(_defaultPenColors);
|
||||
}
|
||||
|
||||
public static void SetPenColors(List<Color> colors)
|
||||
{
|
||||
Pens.Clear();
|
||||
|
||||
foreach (var c in colors)
|
||||
Pens.Add(new Pen(c.ToUInt32(), 2));
|
||||
|
||||
_penCount = colors.Count;
|
||||
}
|
||||
|
||||
public static CommitGraph Parse(List<Commit> commits)
|
||||
{
|
||||
double UNIT_WIDTH = 12;
|
||||
double HALF_WIDTH = 6;
|
||||
|
@ -184,7 +194,7 @@ namespace SourceGit.Models
|
|||
major = new PathHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY));
|
||||
unsolved.Add(major);
|
||||
temp.Paths.Add(major.Path);
|
||||
colorIdx = (colorIdx + 1) % colorCount;
|
||||
colorIdx = (colorIdx + 1) % _penCount;
|
||||
}
|
||||
|
||||
// Calculate link position of this commit.
|
||||
|
@ -223,7 +233,7 @@ namespace SourceGit.Models
|
|||
var l = new PathHelper(commit.Parents[j], isMerged, colorIdx, position, new Point(offsetX, position.Y + HALF_HEIGHT));
|
||||
unsolved.Add(l);
|
||||
temp.Paths.Add(l.Path);
|
||||
colorIdx = (colorIdx + 1) % colorCount;
|
||||
colorIdx = (colorIdx + 1) % _penCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,5 +267,19 @@ namespace SourceGit.Models
|
|||
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static int _penCount = 0;
|
||||
private static readonly List<Color> _defaultPenColors = [
|
||||
Colors.Orange,
|
||||
Colors.ForestGreen,
|
||||
Colors.Gold,
|
||||
Colors.Magenta,
|
||||
Colors.Red,
|
||||
Colors.Gray,
|
||||
Colors.Turquoise,
|
||||
Colors.Olive,
|
||||
Colors.Khaki,
|
||||
Colors.Lime,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
10
src/Models/CustomColorSchema.cs
Normal file
10
src/Models/CustomColorSchema.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class CustomColorSchema
|
||||
{
|
||||
public Dictionary<string, string> Basic { get; set; } = new Dictionary<string, string>();
|
||||
public List<string> Graph { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia.Media;
|
||||
|
||||
namespace SourceGit.Models
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum DecoratorType
|
||||
{
|
||||
|
@ -17,12 +15,4 @@ namespace SourceGit.Models
|
|||
public DecoratorType Type { get; set; } = DecoratorType.None;
|
||||
public string Name { get; set; } = "";
|
||||
}
|
||||
|
||||
public static class DecoratorResources
|
||||
{
|
||||
public static readonly IBrush[] Backgrounds = [
|
||||
new SolidColorBrush(0xFF02C302),
|
||||
new SolidColorBrush(0xFFFFB835),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,11 @@ namespace SourceGit.Models
|
|||
{
|
||||
public class Stash
|
||||
{
|
||||
private static readonly DateTime UTC_START = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime();
|
||||
|
||||
public string Name { get; set; } = "";
|
||||
public string SHA { get; set; } = "";
|
||||
public ulong Time { get; set; } = 0;
|
||||
public string Message { get; set; } = "";
|
||||
|
||||
public string TimeStr => UTC_START.AddSeconds(Time).ToString("yyyy/MM/dd HH:mm:ss");
|
||||
public string TimeStr => DateTime.UnixEpoch.AddSeconds(Time).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,6 @@ namespace SourceGit.Models
|
|||
|
||||
public Statistics()
|
||||
{
|
||||
_utcStart = DateTime.UnixEpoch;
|
||||
_today = DateTime.Today;
|
||||
_thisWeekStart = _today.AddSeconds(-(int)_today.DayOfWeek * 3600 * 24 - _today.Hour * 3600 - _today.Minute * 60 - _today.Second);
|
||||
_thisWeekEnd = _thisWeekStart.AddDays(7);
|
||||
|
@ -115,7 +114,7 @@ namespace SourceGit.Models
|
|||
|
||||
public void AddCommit(string committer, double timestamp)
|
||||
{
|
||||
var time = _utcStart.AddSeconds(timestamp).ToLocalTime();
|
||||
var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime();
|
||||
if (time.CompareTo(_thisWeekStart) >= 0 && time.CompareTo(_thisWeekEnd) < 0)
|
||||
{
|
||||
Week.AddCommit((int)time.DayOfWeek, committer);
|
||||
|
@ -136,7 +135,6 @@ namespace SourceGit.Models
|
|||
Week.Complete();
|
||||
}
|
||||
|
||||
private readonly DateTime _utcStart;
|
||||
private readonly DateTime _today;
|
||||
private readonly DateTime _thisWeekStart;
|
||||
private readonly DateTime _thisWeekEnd;
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">AUTHOR</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">CHANGED</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">COMMITTER</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.GotoChangesPage" xml:space="preserve">Shows only the first 100 changes. See all changes on the CHANGES tab.</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Message" xml:space="preserve">MESSAGE</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Parents" xml:space="preserve">PARENTS</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Refs" xml:space="preserve">REFS</x:String>
|
||||
|
@ -200,6 +201,7 @@
|
|||
<x:String x:Key="Text.FastForwardWithoutCheck" xml:space="preserve">Fast-Forward (without checkout)</x:String>
|
||||
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String>
|
||||
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Fetch all remotes</x:String>
|
||||
<x:String x:Key="Text.Fetch.NoTags" xml:space="preserve">Fetch without tags</x:String>
|
||||
<x:String x:Key="Text.Fetch.Prune" xml:space="preserve">Prune remote dead branches</x:String>
|
||||
<x:String x:Key="Text.Fetch.Remote" xml:space="preserve">Remote:</x:String>
|
||||
<x:String x:Key="Text.Fetch.Title" xml:space="preserve">Fetch Remote Changes</x:String>
|
||||
|
@ -386,6 +388,7 @@
|
|||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Discard</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">Do Nothing</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Stash & Reapply</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Fetch without tags</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Remote:</x:String>
|
||||
<x:String x:Key="Text.Pull.Title" xml:space="preserve">Pull (Fetch & Merge)</x:String>
|
||||
<x:String x:Key="Text.Pull.UseRebase" xml:space="preserve">Use rebase instead of merge</x:String>
|
||||
|
@ -540,6 +543,8 @@
|
|||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">Ignore files in the same folder</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignore this file only</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">Auto-Stage</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">You can stage this file now.</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT & PUSH</x:String>
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">修改者</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">变更列表</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">提交者</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.GotoChangesPage" xml:space="preserve">仅显示前100项变更。请前往【变更对比】页面查看全部。</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Message" xml:space="preserve">提交信息</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Parents" xml:space="preserve">父提交</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Refs" xml:space="preserve">相关引用</x:String>
|
||||
|
@ -203,6 +204,7 @@
|
|||
<x:String x:Key="Text.FastForwardWithoutCheck" xml:space="preserve">快进(fast-forward,无需checkout)</x:String>
|
||||
<x:String x:Key="Text.Fetch" xml:space="preserve">拉取(fetch)</x:String>
|
||||
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">拉取所有的远程仓库</x:String>
|
||||
<x:String x:Key="Text.Fetch.NoTags" xml:space="preserve">不拉取远程标签</x:String>
|
||||
<x:String x:Key="Text.Fetch.Prune" xml:space="preserve">自动清理远程已删除分支</x:String>
|
||||
<x:String x:Key="Text.Fetch.Remote" xml:space="preserve">远程仓库 :</x:String>
|
||||
<x:String x:Key="Text.Fetch.Title" xml:space="preserve">拉取远程仓库内容</x:String>
|
||||
|
@ -389,6 +391,7 @@
|
|||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">丢弃更改</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">不做处理</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">贮藏并自动恢复</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">不拉取远程标签</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">远程 :</x:String>
|
||||
<x:String x:Key="Text.Pull.Title" xml:space="preserve">拉回(拉取并合并)</x:String>
|
||||
<x:String x:Key="Text.Pull.UseRebase" xml:space="preserve">使用变基方式合并分支</x:String>
|
||||
|
@ -542,6 +545,8 @@
|
|||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同目录下所有文件</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本文件</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修补(--amend)</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">自动暂存(--all)</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">提交前自动将修改过和删除的文件加入暂存区,但新增文件需要手动添加。</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">现在您已可将其加入暂存区中</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交并推送</x:String>
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">修改者</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">變更列表</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">提交者</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.GotoChangesPage" xml:space="preserve">僅顯示前100項變更。 請前往『變更對比』頁面查看全部。</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Message" xml:space="preserve">提交資訊</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Parents" xml:space="preserve">父提交</x:String>
|
||||
<x:String x:Key="Text.CommitDetail.Info.Refs" xml:space="preserve">相關引用</x:String>
|
||||
|
@ -203,6 +204,7 @@
|
|||
<x:String x:Key="Text.FastForwardWithoutCheck" xml:space="preserve">快進(fast-forward,無需checkout)</x:String>
|
||||
<x:String x:Key="Text.Fetch" xml:space="preserve">拉取(fetch)</x:String>
|
||||
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">拉取所有的遠端倉庫</x:String>
|
||||
<x:String x:Key="Text.Fetch.NoTags" xml:space="preserve">不拉取遠端標籤</x:String>
|
||||
<x:String x:Key="Text.Fetch.Prune" xml:space="preserve">自動清理遠端已刪除分支</x:String>
|
||||
<x:String x:Key="Text.Fetch.Remote" xml:space="preserve">遠端倉庫 :</x:String>
|
||||
<x:String x:Key="Text.Fetch.Title" xml:space="preserve">拉取遠端倉庫內容</x:String>
|
||||
|
@ -389,6 +391,7 @@
|
|||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">丟棄更改</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">不做處理</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">儲藏並自動恢復</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">不拉取遠端標籤</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">遠端 :</x:String>
|
||||
<x:String x:Key="Text.Pull.Title" xml:space="preserve">拉回(拉取併合並)</x:String>
|
||||
<x:String x:Key="Text.Pull.UseRebase" xml:space="preserve">使用變基方式合併分支</x:String>
|
||||
|
@ -542,6 +545,8 @@
|
|||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.InSameFolder" xml:space="preserve">忽略同路徑下所有檔案</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">忽略本檔案</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修補(--amend)</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AutoStage" xml:space="preserve">自動暫存(--all)</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.AutoStage.Tip" xml:space="preserve">提交前自動將修改過和刪除的檔案加入暫存區,但新增檔案需要手動添加。</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">現在您已可將其加入暫存區中</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交併推送</x:String>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<Style.Resources>
|
||||
<SolidColorBrush x:Key="SystemControlErrorTextForegroundBrush" Color="Red"/>
|
||||
<SolidColorBrush x:Key="SystemErrorTextColor" Color="Red"/>
|
||||
<Color x:Key="SystemErrorTextColor">Red</Color>
|
||||
</Style.Resources>
|
||||
</Style>
|
||||
|
||||
|
@ -160,6 +160,11 @@
|
|||
<Setter Property="FontSize" Value="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="ToolTip">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Popup}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="FlyoutPresenter">
|
||||
<Setter Property="MaxWidth" Value="1024"/>
|
||||
<Setter Property="MaxHeight" Value="768"/>
|
||||
|
@ -767,6 +772,7 @@
|
|||
<Popup Name="PART_Popup"
|
||||
WindowManagerAddShadowHint="False"
|
||||
Placement="RightEdgeAlignedTop"
|
||||
MaxHeight="400"
|
||||
IsLightDismissEnabled="False"
|
||||
HorizontalOffset="-4"
|
||||
VerticalOffset="-4"
|
||||
|
@ -788,7 +794,13 @@
|
|||
<ItemsPresenter Name="PART_ItemsPresenter"
|
||||
ItemsPanel="{TemplateBinding ItemsPanel}"
|
||||
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
|
||||
Grid.IsSharedSizeScope="True" />
|
||||
Grid.IsSharedSizeScope="True">
|
||||
<ItemsPresenter.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsPresenter.ItemsPanel>
|
||||
</ItemsPresenter>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
@ -1211,7 +1223,8 @@
|
|||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
|
||||
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}"
|
||||
IsHitTestVisible="False" />
|
||||
</Panel>
|
||||
<ContentPresenter Name="PART_HeaderPresenter"
|
||||
Grid.Column="1"
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
<Color x:Key="Color.Contents">#FFFAFAFA</Color>
|
||||
<Color x:Key="Color.Badge">#FFB0CEE8</Color>
|
||||
<Color x:Key="Color.BadgeFG">#FF1F1F1F</Color>
|
||||
<Color x:Key="Color.Decorator">#FF6F6F6F</Color>
|
||||
<Color x:Key="Color.DecoratorIconBG">#FF6F6F6F</Color>
|
||||
<Color x:Key="Color.DecoratorIcon">#FFF8F8F8</Color>
|
||||
<Color x:Key="Color.DecoratorBranch">#FFFFB835</Color>
|
||||
<Color x:Key="Color.DecoratorTag">#FF02C302</Color>
|
||||
<Color x:Key="Color.DecoratorFG">Black</Color>
|
||||
<Color x:Key="Color.Conflict">#FF836C2E</Color>
|
||||
<Color x:Key="Color.ConflictForeground">#FFFFFFFF</Color>
|
||||
<Color x:Key="Color.Border0">#FFCFCFCF</Color>
|
||||
|
@ -43,8 +46,11 @@
|
|||
<Color x:Key="Color.Contents">#FF1B1B1B</Color>
|
||||
<Color x:Key="Color.Badge">#FF8F8F8F</Color>
|
||||
<Color x:Key="Color.BadgeFG">#FFDDDDDD</Color>
|
||||
<Color x:Key="Color.Decorator">#FF505050</Color>
|
||||
<Color x:Key="Color.DecoratorIconBG">#FF505050</Color>
|
||||
<Color x:Key="Color.DecoratorIcon">#FFF8F8F8</Color>
|
||||
<Color x:Key="Color.DecoratorBranch">#FFFFB835</Color>
|
||||
<Color x:Key="Color.DecoratorTag">#FF02C302</Color>
|
||||
<Color x:Key="Color.DecoratorFG">Black</Color>
|
||||
<Color x:Key="Color.Conflict">#FFFAFAD2</Color>
|
||||
<Color x:Key="Color.ConflictForeground">#FF252525</Color>
|
||||
<Color x:Key="Color.Border0">#FF181818</Color>
|
||||
|
@ -73,8 +79,11 @@
|
|||
<SolidColorBrush x:Key="Brush.Contents" Color="{DynamicResource Color.Contents}"/>
|
||||
<SolidColorBrush x:Key="Brush.Badge" Color="{DynamicResource Color.Badge}"/>
|
||||
<SolidColorBrush x:Key="Brush.BadgeFG" Color="{DynamicResource Color.BadgeFG}"/>
|
||||
<SolidColorBrush x:Key="Brush.Decorator" Color="{DynamicResource Color.Decorator}"/>
|
||||
<SolidColorBrush x:Key="Brush.DecoratorIconBG" Color="{DynamicResource Color.DecoratorIconBG}"/>
|
||||
<SolidColorBrush x:Key="Brush.DecoratorIcon" Color="{DynamicResource Color.DecoratorIcon}"/>
|
||||
<SolidColorBrush x:Key="Brush.DecoratorBranch" Color="{DynamicResource Color.DecoratorBranch}"/>
|
||||
<SolidColorBrush x:Key="Brush.DecoratorTag" Color="{DynamicResource Color.DecoratorTag}"/>
|
||||
<SolidColorBrush x:Key="Brush.DecoratorFG" Color="{DynamicResource Color.DecoratorFG}"/>
|
||||
<SolidColorBrush x:Key="Brush.Conflict" Color="{DynamicResource Color.Conflict}"/>
|
||||
<SolidColorBrush x:Key="Brush.ConflictForeground" Color="{DynamicResource Color.ConflictForeground}"/>
|
||||
<SolidColorBrush x:Key="Brush.Border0" Color="{DynamicResource Color.Border0}"/>
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
SetProgressDescription("Fetching from added remote ...");
|
||||
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null);
|
||||
new Commands.Fetch(_repo.FullPath, _name, true, SetProgressDescription).Exec();
|
||||
new Commands.Fetch(_repo.FullPath, _name, true, false, SetProgressDescription).Exec();
|
||||
}
|
||||
CallUIThread(() =>
|
||||
{
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace SourceGit.ViewModels
|
|||
if (branch.IsLocal)
|
||||
LocalBranches.Add(branch.Name);
|
||||
else
|
||||
RemoteBranches.Add($"{branch.Remote}/{branch.Name}");
|
||||
RemoteBranches.Add(branch.FriendlyName);
|
||||
}
|
||||
|
||||
if (RemoteBranches.Count > 0)
|
||||
|
|
|
@ -49,9 +49,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void NavigateToCommit(string commitSHA)
|
||||
{
|
||||
var repo = Preference.FindRepository(_repo);
|
||||
if (repo != null)
|
||||
repo.NavigateToCommit(commitSHA);
|
||||
var repo = App.FindOpenedRepository(_repo);
|
||||
repo?.NavigateToCommit(commitSHA);
|
||||
}
|
||||
|
||||
private readonly string _repo = string.Empty;
|
||||
|
|
|
@ -110,9 +110,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void NavigateTo(string commitSHA)
|
||||
{
|
||||
var repo = Preference.FindRepository(_repo);
|
||||
if (repo != null)
|
||||
repo.NavigateToCommit(commitSHA);
|
||||
var repo = App.FindOpenedRepository(_repo);
|
||||
repo?.NavigateToCommit(commitSHA);
|
||||
}
|
||||
|
||||
public void ClearSearchFilter()
|
||||
|
|
|
@ -1,72 +1,28 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Media;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public enum BranchTreeNodeType
|
||||
{
|
||||
DetachedHead,
|
||||
Remote,
|
||||
Folder,
|
||||
Branch,
|
||||
}
|
||||
|
||||
public class BranchTreeNode : ObservableObject
|
||||
{
|
||||
public const double DEFAULT_CORNER = 4.0;
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
public object Backend { get; private set; } = null;
|
||||
public int Depth { get; set; } = 0;
|
||||
public bool IsFiltered { get; set; } = false;
|
||||
public bool IsSelected { get; set; } = false;
|
||||
public List<BranchTreeNode> Children { get; private set; } = new List<BranchTreeNode>();
|
||||
|
||||
public string Name { get; set; }
|
||||
public BranchTreeNodeType Type { get; set; }
|
||||
public object Backend { get; set; }
|
||||
public bool IsExpanded { get; set; }
|
||||
public bool IsFiltered { get; set; }
|
||||
public List<BranchTreeNode> Children { get; set; } = new List<BranchTreeNode>();
|
||||
|
||||
public bool IsUpstreamTrackStatusVisible
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => IsBranch && !string.IsNullOrEmpty((Backend as Models.Branch).UpstreamTrackStatus);
|
||||
}
|
||||
|
||||
public string UpstreamTrackStatus
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Branch ? (Backend as Models.Branch).UpstreamTrackStatus : "";
|
||||
}
|
||||
|
||||
public bool IsRemote
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Remote;
|
||||
}
|
||||
|
||||
public bool IsFolder
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Folder;
|
||||
}
|
||||
|
||||
public bool IsBranch
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Branch;
|
||||
}
|
||||
|
||||
public bool IsDetachedHead
|
||||
{
|
||||
get => Type == BranchTreeNodeType.DetachedHead;
|
||||
}
|
||||
|
||||
public bool IsCurrent
|
||||
{
|
||||
get => IsBranch && (Backend as Models.Branch).IsCurrent;
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public CornerRadius CornerRadius
|
||||
|
@ -75,30 +31,33 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _cornerRadius, value);
|
||||
}
|
||||
|
||||
public void UpdateCornerRadius(ref BranchTreeNode prev)
|
||||
public bool IsBranch
|
||||
{
|
||||
if (_isSelected && prev != null && prev.IsSelected)
|
||||
{
|
||||
var prevTop = prev.CornerRadius.TopLeft;
|
||||
prev.CornerRadius = new CornerRadius(prevTop, 0);
|
||||
CornerRadius = new CornerRadius(0, DEFAULT_CORNER);
|
||||
}
|
||||
else if (CornerRadius.TopLeft != DEFAULT_CORNER ||
|
||||
CornerRadius.BottomLeft != DEFAULT_CORNER)
|
||||
{
|
||||
CornerRadius = new CornerRadius(DEFAULT_CORNER);
|
||||
}
|
||||
|
||||
prev = this;
|
||||
|
||||
if (!IsBranch && IsExpanded)
|
||||
{
|
||||
foreach (var child in Children)
|
||||
child.UpdateCornerRadius(ref prev);
|
||||
}
|
||||
get => Backend is Models.Branch;
|
||||
}
|
||||
private bool _isSelected = false;
|
||||
private CornerRadius _cornerRadius = new CornerRadius(DEFAULT_CORNER);
|
||||
|
||||
public bool IsUpstreamTrackStatusVisible
|
||||
{
|
||||
get => Backend is Models.Branch { IsLocal: true } branch && !string.IsNullOrEmpty(branch.UpstreamTrackStatus);
|
||||
}
|
||||
|
||||
public string UpstreamTrackStatus
|
||||
{
|
||||
get => Backend is Models.Branch branch ? branch.UpstreamTrackStatus : "";
|
||||
}
|
||||
|
||||
public FontWeight NameFontWeight
|
||||
{
|
||||
get => Backend is Models.Branch { IsCurrent: true } ? FontWeight.Bold : FontWeight.Regular;
|
||||
}
|
||||
|
||||
public string Tooltip
|
||||
{
|
||||
get => Backend is Models.Branch b ? b.FriendlyName : null;
|
||||
}
|
||||
|
||||
private bool _isExpanded = false;
|
||||
private CornerRadius _cornerRadius = new CornerRadius(4);
|
||||
|
||||
public class Builder
|
||||
{
|
||||
|
@ -115,7 +74,6 @@ namespace SourceGit.ViewModels
|
|||
var node = new BranchTreeNode()
|
||||
{
|
||||
Name = remote.Name,
|
||||
Type = BranchTreeNodeType.Remote,
|
||||
Backend = remote,
|
||||
IsExpanded = bForceExpanded || _expanded.Contains(path),
|
||||
};
|
||||
|
@ -158,9 +116,13 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (node.Backend is Models.Branch)
|
||||
continue;
|
||||
|
||||
var path = prefix + "/" + node.Name;
|
||||
if (node.Type != BranchTreeNodeType.Branch && node.IsExpanded)
|
||||
if (node.IsExpanded)
|
||||
_expanded.Add(path);
|
||||
|
||||
CollectExpandedNodes(node.Children, path);
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +135,6 @@ namespace SourceGit.ViewModels
|
|||
roots.Add(new BranchTreeNode()
|
||||
{
|
||||
Name = branch.Name,
|
||||
Type = BranchTreeNodeType.Branch,
|
||||
Backend = branch,
|
||||
IsExpanded = false,
|
||||
IsFiltered = isFiltered,
|
||||
|
@ -197,7 +158,6 @@ namespace SourceGit.ViewModels
|
|||
lastFolder = new BranchTreeNode()
|
||||
{
|
||||
Name = name,
|
||||
Type = BranchTreeNodeType.Folder,
|
||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
||||
};
|
||||
roots.Add(lastFolder);
|
||||
|
@ -208,7 +168,6 @@ namespace SourceGit.ViewModels
|
|||
var cur = new BranchTreeNode()
|
||||
{
|
||||
Name = name,
|
||||
Type = BranchTreeNodeType.Folder,
|
||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
||||
};
|
||||
lastFolder.Children.Add(cur);
|
||||
|
@ -220,10 +179,9 @@ namespace SourceGit.ViewModels
|
|||
sepIdx = branch.Name.IndexOf('/', start);
|
||||
}
|
||||
|
||||
lastFolder.Children.Add(new BranchTreeNode()
|
||||
lastFolder?.Children.Add(new BranchTreeNode()
|
||||
{
|
||||
Name = Path.GetFileName(branch.Name),
|
||||
Type = branch.IsHead ? BranchTreeNodeType.DetachedHead : BranchTreeNodeType.Branch,
|
||||
Backend = branch,
|
||||
IsExpanded = false,
|
||||
IsFiltered = isFiltered,
|
||||
|
@ -234,16 +192,13 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
nodes.Sort((l, r) =>
|
||||
{
|
||||
if (l.Type == BranchTreeNodeType.DetachedHead)
|
||||
{
|
||||
if (l.Backend is Models.Branch { IsHead: true })
|
||||
return -1;
|
||||
}
|
||||
if (l.Type == r.Type)
|
||||
{
|
||||
return l.Name.CompareTo(r.Name);
|
||||
}
|
||||
|
||||
return (int)l.Type - (int)r.Type;
|
||||
if (l.Backend is Models.Branch)
|
||||
return r.Backend is Models.Branch ? string.Compare(l.Name, r.Name, StringComparison.Ordinal) : 1;
|
||||
|
||||
return r.Backend is Models.Branch ? -1 : string.Compare(l.Name, r.Name, StringComparison.Ordinal);
|
||||
});
|
||||
|
||||
foreach (var node in nodes)
|
||||
|
|
|
@ -12,8 +12,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public Models.DealWithLocalChanges PreAction
|
||||
{
|
||||
get => _preAction;
|
||||
set => SetProperty(ref _preAction, value);
|
||||
get => _repo.Settings.DealWithLocalChangesOnCheckoutBranch;
|
||||
set => _repo.Settings.DealWithLocalChangesOnCheckoutBranch = value;
|
||||
}
|
||||
|
||||
public Checkout(Repository repo, string branch)
|
||||
|
@ -34,7 +34,7 @@ namespace SourceGit.ViewModels
|
|||
var needPopStash = false;
|
||||
if (hasLocalChanges)
|
||||
{
|
||||
if (_preAction == Models.DealWithLocalChanges.StashAndReaply)
|
||||
if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
|
||||
{
|
||||
SetProgressDescription("Adding untracked changes ...");
|
||||
var succ = new Commands.Add(_repo.FullPath).Exec();
|
||||
|
@ -52,7 +52,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
needPopStash = true;
|
||||
}
|
||||
else if (_preAction == Models.DealWithLocalChanges.Discard)
|
||||
else if (PreAction == Models.DealWithLocalChanges.Discard)
|
||||
{
|
||||
SetProgressDescription("Discard local changes ...");
|
||||
Commands.Discard.All(_repo.FullPath);
|
||||
|
@ -78,6 +78,5 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
|
||||
private readonly Repository _repo = null;
|
||||
private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.DoNothing;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,8 +128,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
CallUIThread(() =>
|
||||
{
|
||||
var repo = Preference.AddRepository(path, Path.Combine(path, ".git"));
|
||||
var node = Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null, true);
|
||||
var normalizedPath = path.Replace("\\", "/");
|
||||
var node = Preference.FindOrAddNodeByRepositoryPath(normalizedPath, null, true);
|
||||
_launcher.OpenRepositoryInTab(node, _page);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Controls;
|
||||
|
@ -12,7 +13,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class CommitDetail : ObservableObject
|
||||
public partial class CommitDetail : ObservableObject
|
||||
{
|
||||
public DiffContext DiffContext
|
||||
{
|
||||
|
@ -110,9 +111,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void NavigateTo(string commitSHA)
|
||||
{
|
||||
var repo = Preference.FindRepository(_repo);
|
||||
if (repo != null)
|
||||
repo.NavigateToCommit(commitSHA);
|
||||
var repo = App.FindOpenedRepository(_repo);
|
||||
repo?.NavigateToCommit(commitSHA);
|
||||
}
|
||||
|
||||
public void ClearSearchChangeFilter()
|
||||
|
@ -165,39 +165,26 @@ namespace SourceGit.ViewModels
|
|||
|
||||
var contentStream = Commands.QueryFileContent.Run(_repo, _commit.SHA, file.Path);
|
||||
var content = new StreamReader(contentStream).ReadToEnd();
|
||||
if (content.StartsWith("version https://git-lfs.github.com/spec/", StringComparison.Ordinal))
|
||||
var matchLFS = REG_LFS_FORMAT().Match(content);
|
||||
if (matchLFS.Success)
|
||||
{
|
||||
var obj = new Models.RevisionLFSObject() { Object = new Models.LFSObject() };
|
||||
var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lines.Length == 3)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("oid sha256:", StringComparison.Ordinal))
|
||||
{
|
||||
obj.Object.Oid = line.Substring(11);
|
||||
}
|
||||
else if (line.StartsWith("size ", StringComparison.Ordinal))
|
||||
{
|
||||
obj.Object.Size = long.Parse(line.Substring(5));
|
||||
}
|
||||
}
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
ViewRevisionFileContent = obj;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
obj.Object.Oid = matchLFS.Groups[1].Value;
|
||||
obj.Object.Size = long.Parse(matchLFS.Groups[2].Value);
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewRevisionFileContent = new Models.RevisionTextFile()
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
FileName = file.Path,
|
||||
Content = content
|
||||
};
|
||||
});
|
||||
ViewRevisionFileContent = new Models.RevisionTextFile()
|
||||
{
|
||||
FileName = file.Path,
|
||||
Content = content
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
case Models.ObjectType.Commit:
|
||||
|
@ -332,6 +319,7 @@ namespace SourceGit.ViewModels
|
|||
var blame = new MenuItem();
|
||||
blame.Header = App.Text("Blame");
|
||||
blame.Icon = App.CreateMenuIcon("Icons.Blame");
|
||||
blame.IsEnabled = file.Type == Models.ObjectType.Blob;
|
||||
blame.Click += (o, ev) =>
|
||||
{
|
||||
var window = new Views.Blame() { DataContext = new Blame(_repo, file.Path, _commit.SHA) };
|
||||
|
@ -465,6 +453,9 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")]
|
||||
private static partial Regex REG_LFS_FORMAT();
|
||||
|
||||
private static readonly HashSet<string> IMG_EXTS = new HashSet<string>()
|
||||
{
|
||||
".ico", ".bmp", ".jpg", ".png", ".jpeg"
|
||||
|
|
|
@ -22,15 +22,15 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public Models.DealWithLocalChanges PreAction
|
||||
{
|
||||
get => _preAction;
|
||||
set => SetProperty(ref _preAction, value);
|
||||
get => _repo.Settings.DealWithLocalChangesOnCreateBranch;
|
||||
set => _repo.Settings.DealWithLocalChangesOnCreateBranch = value;
|
||||
}
|
||||
|
||||
public bool CheckoutAfterCreated
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
get => _repo.Settings.CheckoutBranchOnCreateBranch;
|
||||
set => _repo.Settings.CheckoutBranchOnCreateBranch = value;
|
||||
}
|
||||
|
||||
public CreateBranch(Repository repo, Models.Branch branch)
|
||||
{
|
||||
|
@ -72,8 +72,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
foreach (var b in creator._repo.Branches)
|
||||
{
|
||||
var test = b.IsLocal ? b.Name : $"{b.Remote}/{b.Name}";
|
||||
if (test == name)
|
||||
if (b.FriendlyName == name)
|
||||
return new ValidationResult("A branch with same name already exists!");
|
||||
}
|
||||
|
||||
|
@ -90,7 +89,7 @@ namespace SourceGit.ViewModels
|
|||
bool needPopStash = false;
|
||||
if (_repo.WorkingCopyChangesCount > 0)
|
||||
{
|
||||
if (_preAction == Models.DealWithLocalChanges.StashAndReaply)
|
||||
if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
|
||||
{
|
||||
SetProgressDescription("Adding untracked changes...");
|
||||
var succ = new Commands.Add(_repo.FullPath).Exec();
|
||||
|
@ -108,7 +107,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
needPopStash = true;
|
||||
}
|
||||
else if (_preAction == Models.DealWithLocalChanges.Discard)
|
||||
else if (PreAction == Models.DealWithLocalChanges.Discard)
|
||||
{
|
||||
SetProgressDescription("Discard local changes...");
|
||||
Commands.Discard.All(_repo.FullPath);
|
||||
|
@ -138,6 +137,5 @@ namespace SourceGit.ViewModels
|
|||
private readonly Repository _repo = null;
|
||||
private string _name = null;
|
||||
private readonly string _baseOnRevision = null;
|
||||
private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.DoNothing;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,9 @@ namespace SourceGit.ViewModels
|
|||
|
||||
if (branch.IsLocal && !string.IsNullOrEmpty(branch.Upstream))
|
||||
{
|
||||
var upstream = branch.Upstream.Substring(13);
|
||||
TrackingRemoteBranch = repo.Branches.Find(x => !x.IsLocal && $"{x.Remote}/{x.Name}" == upstream);
|
||||
DeleteTrackingRemoteTip = new Views.NameHighlightedTextBlock("DeleteBranch.WithTrackingRemote", upstream);
|
||||
TrackingRemoteBranch = repo.Branches.Find(x => x.FullName == branch.Upstream);
|
||||
if (TrackingRemoteBranch != null)
|
||||
DeleteTrackingRemoteTip = new Views.NameHighlightedTextBlock("DeleteBranch.WithTrackingRemote", TrackingRemoteBranch.FriendlyName);
|
||||
}
|
||||
|
||||
View = new Views.DeleteBranch() { DataContext = this };
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
foreach (var target in Targets)
|
||||
{
|
||||
SetProgressDescription($"Deleting remote branch : {target.Remote}/{target.Name}");
|
||||
SetProgressDescription($"Deleting remote branch : {target.FriendlyName}");
|
||||
Commands.Branch.DeleteRemote(_repo.FullPath, target.Remote, target.Name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,27 +111,34 @@ namespace SourceGit.ViewModels
|
|||
|
||||
if (latest.TextDiff != null)
|
||||
{
|
||||
var repo = Preference.FindRepository(_repo);
|
||||
if (repo != null && repo.Submodules.Contains(_option.Path))
|
||||
var count = latest.TextDiff.Lines.Count;
|
||||
var isSubmodule = false;
|
||||
if (count <= 3)
|
||||
{
|
||||
var submoduleDiff = new Models.SubmoduleDiff();
|
||||
var submoduleRoot = $"{_repo}/{_option.Path}".Replace("\\", "/");
|
||||
foreach (var line in latest.TextDiff.Lines)
|
||||
isSubmodule = true;
|
||||
for (int i = 1; i < count; i++)
|
||||
{
|
||||
var line = latest.TextDiff.Lines[i];
|
||||
if (!line.Content.StartsWith("Subproject commit ", StringComparison.Ordinal))
|
||||
{
|
||||
isSubmodule = false;
|
||||
break;
|
||||
}
|
||||
|
||||
var sha = line.Content.Substring(18);
|
||||
if (line.Type == Models.TextDiffLineType.Added)
|
||||
{
|
||||
var sha = line.Content.Substring("Subproject commit ".Length);
|
||||
submoduleDiff.New = QuerySubmoduleRevision(submoduleRoot, sha);
|
||||
}
|
||||
else if (line.Type == Models.TextDiffLineType.Deleted)
|
||||
{
|
||||
var sha = line.Content.Substring("Subproject commit ".Length);
|
||||
submoduleDiff.Old = QuerySubmoduleRevision(submoduleRoot, sha);
|
||||
}
|
||||
}
|
||||
rs = submoduleDiff;
|
||||
|
||||
if (isSubmodule)
|
||||
rs = submoduleDiff;
|
||||
}
|
||||
else
|
||||
|
||||
if (!isSubmodule)
|
||||
{
|
||||
latest.TextDiff.File = _option.Path;
|
||||
rs = latest.TextDiff;
|
||||
|
|
|
@ -26,6 +26,12 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
|
||||
public bool NoTags
|
||||
{
|
||||
get => _repo.Settings.FetchWithoutTags;
|
||||
set => _repo.Settings.FetchWithoutTags = value;
|
||||
}
|
||||
|
||||
public Fetch(Repository repo, Models.Remote preferedRemote = null)
|
||||
|
@ -33,13 +39,13 @@ namespace SourceGit.ViewModels
|
|||
_repo = repo;
|
||||
_fetchAllRemotes = preferedRemote == null;
|
||||
SelectedRemote = preferedRemote != null ? preferedRemote : _repo.Remotes[0];
|
||||
Prune = true;
|
||||
View = new Views.Fetch() { DataContext = this };
|
||||
}
|
||||
|
||||
public override Task<bool> Sure()
|
||||
{
|
||||
_repo.SetWatcherEnabled(false);
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
if (FetchAllRemotes)
|
||||
|
@ -47,13 +53,13 @@ namespace SourceGit.ViewModels
|
|||
foreach (var remote in _repo.Remotes)
|
||||
{
|
||||
SetProgressDescription($"Fetching remote: {remote.Name}");
|
||||
new Commands.Fetch(_repo.FullPath, remote.Name, Prune, SetProgressDescription).Exec();
|
||||
new Commands.Fetch(_repo.FullPath, remote.Name, Prune, NoTags, SetProgressDescription).Exec();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetProgressDescription($"Fetching remote: {SelectedRemote.Name}");
|
||||
new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, Prune, SetProgressDescription).Exec();
|
||||
new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, Prune, NoTags, SetProgressDescription).Exec();
|
||||
}
|
||||
|
||||
CallUIThread(() => _repo.SetWatcherEnabled(true));
|
||||
|
|
|
@ -39,8 +39,7 @@ namespace SourceGit.ViewModels
|
|||
var check = $"{starter._prefix}{name}";
|
||||
foreach (var b in starter._repo.Branches)
|
||||
{
|
||||
var test = b.IsLocal ? b.Name : $"{b.Remote}/{b.Name}";
|
||||
if (test == check)
|
||||
if (b.FriendlyName == check)
|
||||
return new ValidationResult("A branch with same name already exists!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
else if (d.Type == Models.DecoratorType.RemoteBranchHead)
|
||||
{
|
||||
var b = _repo.Branches.Find(x => !x.IsLocal && d.Name == $"{x.Remote}/{x.Name}");
|
||||
var b = _repo.Branches.Find(x => !x.IsLocal && d.Name == x.FriendlyName);
|
||||
FillRemoteBranchMenu(menu, b, current, commit.IsMerged);
|
||||
}
|
||||
else if (d.Type == Models.DecoratorType.Tag)
|
||||
|
@ -583,7 +583,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
private void FillRemoteBranchMenu(ContextMenu menu, Models.Branch branch, Models.Branch current, bool merged)
|
||||
{
|
||||
var name = $"{branch.Remote}/{branch.Name}";
|
||||
var name = branch.FriendlyName;
|
||||
|
||||
var submenu = new MenuItem();
|
||||
submenu.Icon = App.CreateMenuIcon("Icons.Branch");
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
|
@ -29,11 +28,10 @@ namespace SourceGit.ViewModels
|
|||
if (!succ)
|
||||
return false;
|
||||
|
||||
var gitDir = Path.GetFullPath(Path.Combine(_targetPath, ".git"));
|
||||
CallUIThread(() =>
|
||||
{
|
||||
var repo = Preference.AddRepository(_targetPath, gitDir);
|
||||
Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, _parentNode, true);
|
||||
var normalizedPath = _targetPath.Replace("\\", "/");
|
||||
Preference.FindOrAddNodeByRepositoryPath(normalizedPath, _parentNode, true);
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
|
@ -48,9 +48,8 @@ namespace SourceGit.ViewModels
|
|||
return;
|
||||
}
|
||||
|
||||
var gitDir = new Commands.QueryGitDir(root).Result();
|
||||
var repo = Preference.AddRepository(root, gitDir);
|
||||
var node = Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null, false);
|
||||
var normalized = root.Replace("\\", "/");
|
||||
var node = Preference.FindOrAddNodeByRepositoryPath(normalized, null, false);
|
||||
OpenRepositoryInTab(node, null);
|
||||
}
|
||||
else if (Preference.Instance.RestoreTabs)
|
||||
|
@ -59,7 +58,15 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
var node = Preference.FindNode(id);
|
||||
if (node == null)
|
||||
continue;
|
||||
{
|
||||
node = new RepositoryNode()
|
||||
{
|
||||
Id = id,
|
||||
Name = Path.GetFileName(id),
|
||||
Bookmark = 0,
|
||||
IsRepository = true,
|
||||
};
|
||||
}
|
||||
|
||||
OpenRepositoryInTab(node, null);
|
||||
}
|
||||
|
@ -72,19 +79,26 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void Quit()
|
||||
{
|
||||
Preference.Instance.OpenedTabs.Clear();
|
||||
var pref = Preference.Instance;
|
||||
pref.OpenedTabs.Clear();
|
||||
|
||||
if (Preference.Instance.RestoreTabs)
|
||||
if (pref.RestoreTabs)
|
||||
{
|
||||
foreach (var page in Pages)
|
||||
{
|
||||
if (page.Node.IsRepository)
|
||||
Preference.Instance.OpenedTabs.Add(page.Node.Id);
|
||||
pref.OpenedTabs.Add(page.Node.Id);
|
||||
}
|
||||
}
|
||||
|
||||
Preference.Instance.LastActiveTabIdx = Pages.IndexOf(ActivePage);
|
||||
Preference.Save();
|
||||
pref.LastActiveTabIdx = Pages.IndexOf(ActivePage);
|
||||
pref.Save();
|
||||
|
||||
foreach (var page in Pages)
|
||||
{
|
||||
if (page.Data is Repository repo)
|
||||
repo.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNewTab()
|
||||
|
@ -211,14 +225,27 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
var repo = Preference.FindRepository(node.Id);
|
||||
if (repo == null || !Path.Exists(repo.FullPath))
|
||||
if (!Path.Exists(node.Id))
|
||||
{
|
||||
var ctx = page == null ? ActivePage.Node.Id : page.Node.Id;
|
||||
App.RaiseException(ctx, "Repository does NOT exists any more. Please remove it.");
|
||||
return;
|
||||
}
|
||||
|
||||
var gitDir = new Commands.QueryGitDir(node.Id).Result();
|
||||
if (string.IsNullOrEmpty(gitDir))
|
||||
{
|
||||
var ctx = page == null ? ActivePage.Node.Id : page.Node.Id;
|
||||
App.RaiseException(ctx, "Given path is not a valid git repository!");
|
||||
return;
|
||||
}
|
||||
|
||||
var repo = new Repository()
|
||||
{
|
||||
FullPath = node.Id,
|
||||
GitDir = gitDir,
|
||||
};
|
||||
|
||||
repo.Open();
|
||||
Commands.AutoFetch.AddRepository(repo.FullPath);
|
||||
|
||||
|
|
|
@ -37,23 +37,14 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
// It will cause some issue on Linux. See https://github.com/sourcegit-scm/sourcegit/issues/99
|
||||
// _instance.Repositories.RemoveAll(x => !Directory.Exists(x.FullPath));
|
||||
|
||||
if (_instance.DefaultFont == null)
|
||||
{
|
||||
_instance.DefaultFont = FontManager.Current.DefaultFontFamily;
|
||||
}
|
||||
|
||||
if (_instance.MonospaceFont == null)
|
||||
{
|
||||
_instance.MonospaceFont = new FontFamily("fonts:SourceGit#JetBrains Mono");
|
||||
}
|
||||
|
||||
if (!_instance.IsGitConfigured)
|
||||
{
|
||||
_instance.GitInstallPath = Native.OS.FindGitExecutable();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
|
@ -274,9 +265,8 @@ namespace SourceGit.ViewModels
|
|||
set
|
||||
{
|
||||
if (value is null or < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Commands.AutoFetch.Interval != value)
|
||||
{
|
||||
Commands.AutoFetch.Interval = (int)value;
|
||||
|
@ -308,12 +298,6 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _externalMergeToolPath, value);
|
||||
}
|
||||
|
||||
public List<Repository> Repositories
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new List<Repository>();
|
||||
|
||||
public AvaloniaList<RepositoryNode> RepositoryNodes
|
||||
{
|
||||
get => _repositoryNodes;
|
||||
|
@ -366,20 +350,14 @@ namespace SourceGit.ViewModels
|
|||
list.Sort((l, r) =>
|
||||
{
|
||||
if (l.IsRepository != r.IsRepository)
|
||||
{
|
||||
return l.IsRepository ? 1 : -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return l.Name.CompareTo(r.Name);
|
||||
}
|
||||
});
|
||||
|
||||
collection.Clear();
|
||||
foreach (var one in list)
|
||||
{
|
||||
collection.Add(one);
|
||||
}
|
||||
}
|
||||
|
||||
public static RepositoryNode FindNode(string id)
|
||||
|
@ -451,39 +429,9 @@ namespace SourceGit.ViewModels
|
|||
container.Add(one);
|
||||
}
|
||||
|
||||
public static Repository FindRepository(string path)
|
||||
public void Save()
|
||||
{
|
||||
foreach (var repo in _instance.Repositories)
|
||||
{
|
||||
if (repo.FullPath == path)
|
||||
return repo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Repository AddRepository(string rootDir, string gitDir)
|
||||
{
|
||||
var normalized = rootDir.Replace('\\', '/');
|
||||
var repo = FindRepository(normalized);
|
||||
if (repo != null)
|
||||
{
|
||||
repo.GitDir = gitDir;
|
||||
return repo;
|
||||
}
|
||||
|
||||
repo = new Repository()
|
||||
{
|
||||
FullPath = normalized,
|
||||
GitDir = gitDir
|
||||
};
|
||||
|
||||
_instance.Repositories.Add(repo);
|
||||
return repo;
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
var data = JsonSerializer.Serialize(_instance, JsonCodeGen.Default.Preference);
|
||||
var data = JsonSerializer.Serialize(this, JsonCodeGen.Default.Preference);
|
||||
File.WriteAllText(_savePath, data);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,14 +49,20 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public Models.DealWithLocalChanges PreAction
|
||||
{
|
||||
get => _preAction;
|
||||
set => SetProperty(ref _preAction, value);
|
||||
get => _repo.Settings.DealWithLocalChangesOnPull;
|
||||
set => _repo.Settings.DealWithLocalChangesOnPull = value;
|
||||
}
|
||||
|
||||
public bool UseRebase
|
||||
{
|
||||
get => _repo.PreferRebaseInsteadOfMerge;
|
||||
set => _repo.PreferRebaseInsteadOfMerge = value;
|
||||
get => _repo.Settings.PreferRebaseInsteadOfMerge;
|
||||
set => _repo.Settings.PreferRebaseInsteadOfMerge = value;
|
||||
}
|
||||
|
||||
public bool NoTags
|
||||
{
|
||||
get => _repo.Settings.FetchWithoutTagsOnPull;
|
||||
set => _repo.Settings.FetchWithoutTagsOnPull = value;
|
||||
}
|
||||
|
||||
public Pull(Repository repo, Models.Branch specifiedRemoteBranch)
|
||||
|
@ -114,12 +120,13 @@ namespace SourceGit.ViewModels
|
|||
public override Task<bool> Sure()
|
||||
{
|
||||
_repo.SetWatcherEnabled(false);
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var needPopStash = false;
|
||||
if (_repo.WorkingCopyChangesCount > 0)
|
||||
{
|
||||
if (_preAction == Models.DealWithLocalChanges.StashAndReaply)
|
||||
if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
|
||||
{
|
||||
SetProgressDescription("Adding untracked changes...");
|
||||
var succ = new Commands.Add(_repo.FullPath).Exec();
|
||||
|
@ -137,7 +144,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
needPopStash = true;
|
||||
}
|
||||
else if (_preAction == Models.DealWithLocalChanges.Discard)
|
||||
else if (PreAction == Models.DealWithLocalChanges.Discard)
|
||||
{
|
||||
SetProgressDescription("Discard local changes ...");
|
||||
Commands.Discard.All(_repo.FullPath);
|
||||
|
@ -145,7 +152,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
|
||||
SetProgressDescription($"Pull {_selectedRemote.Name}/{_selectedBranch.Name}...");
|
||||
var rs = new Commands.Pull(_repo.FullPath, _selectedRemote.Name, _selectedBranch.Name, UseRebase, SetProgressDescription).Exec();
|
||||
var rs = new Commands.Pull(_repo.FullPath, _selectedRemote.Name, _selectedBranch.Name, UseRebase, NoTags, SetProgressDescription).Exec();
|
||||
if (rs && needPopStash)
|
||||
{
|
||||
SetProgressDescription("Re-apply local changes...");
|
||||
|
@ -162,6 +169,5 @@ namespace SourceGit.ViewModels
|
|||
private Models.Remote _selectedRemote = null;
|
||||
private List<Models.Branch> _remoteBranches = null;
|
||||
private Models.Branch _selectedBranch = null;
|
||||
private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.DoNothing;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,8 +64,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public bool PushAllTags
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get => _repo.Settings.PushAllTags;
|
||||
set => _repo.Settings.PushAllTags = value;
|
||||
}
|
||||
|
||||
public bool IsSetTrackOptionVisible
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Collections;
|
||||
|
@ -14,6 +14,93 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class RepositorySettings
|
||||
{
|
||||
public Models.DealWithLocalChanges DealWithLocalChangesOnCheckoutBranch
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = Models.DealWithLocalChanges.DoNothing;
|
||||
|
||||
public bool FetchWithoutTags
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public Models.DealWithLocalChanges DealWithLocalChangesOnPull
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = Models.DealWithLocalChanges.DoNothing;
|
||||
|
||||
public bool PreferRebaseInsteadOfMerge
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
|
||||
public bool FetchWithoutTagsOnPull
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public bool PushAllTags
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public Models.DealWithLocalChanges DealWithLocalChangesOnCreateBranch
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = Models.DealWithLocalChanges.DoNothing;
|
||||
|
||||
public bool CheckoutBranchOnCreateBranch
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
|
||||
public bool AutoStageBeforeCommit
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public AvaloniaList<string> Filters
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new AvaloniaList<string>();
|
||||
|
||||
public AvaloniaList<string> CommitMessages
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new AvaloniaList<string>();
|
||||
|
||||
public void PushCommitMessage(string message)
|
||||
{
|
||||
var existIdx = CommitMessages.IndexOf(message);
|
||||
if (existIdx == 0)
|
||||
return;
|
||||
|
||||
if (existIdx > 0)
|
||||
{
|
||||
CommitMessages.Move(existIdx, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CommitMessages.Count > 9)
|
||||
CommitMessages.RemoveRange(9, CommitMessages.Count - 9);
|
||||
|
||||
CommitMessages.Insert(0, message);
|
||||
}
|
||||
}
|
||||
|
||||
public class Repository : ObservableObject, Models.IRepository
|
||||
{
|
||||
public string FullPath
|
||||
|
@ -39,25 +126,12 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _gitDir, value);
|
||||
}
|
||||
|
||||
public bool PreferRebaseInsteadOfMerge
|
||||
public RepositorySettings Settings
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
get => _settings;
|
||||
private set => SetProperty(ref _settings, value);
|
||||
}
|
||||
|
||||
public AvaloniaList<string> Filters
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new AvaloniaList<string>();
|
||||
|
||||
public AvaloniaList<string> CommitMessages
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new AvaloniaList<string>();
|
||||
|
||||
[JsonIgnore]
|
||||
public int SelectedViewIndex
|
||||
{
|
||||
get => _selectedViewIndex;
|
||||
|
@ -81,14 +155,12 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public object SelectedView
|
||||
{
|
||||
get => _selectedView;
|
||||
set => SetProperty(ref _selectedView, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string SearchBranchFilter
|
||||
{
|
||||
get => _searchBranchFilter;
|
||||
|
@ -104,75 +176,64 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.Remote> Remotes
|
||||
{
|
||||
get => _remotes;
|
||||
private set => SetProperty(ref _remotes, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.Branch> Branches
|
||||
{
|
||||
get => _branches;
|
||||
private set => SetProperty(ref _branches, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<BranchTreeNode> LocalBranchTrees
|
||||
{
|
||||
get => _localBranchTrees;
|
||||
private set => SetProperty(ref _localBranchTrees, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<BranchTreeNode> RemoteBranchTrees
|
||||
{
|
||||
get => _remoteBranchTrees;
|
||||
private set => SetProperty(ref _remoteBranchTrees, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.Worktree> Worktrees
|
||||
{
|
||||
get => _worktrees;
|
||||
private set => SetProperty(ref _worktrees, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.Tag> Tags
|
||||
{
|
||||
get => _tags;
|
||||
private set => SetProperty(ref _tags, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.Tag> VisibleTags
|
||||
{
|
||||
get => _visibleTags;
|
||||
private set => SetProperty(ref _visibleTags, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string> Submodules
|
||||
{
|
||||
get => _submodules;
|
||||
private set => SetProperty(ref _submodules, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public int WorkingCopyChangesCount
|
||||
{
|
||||
get => _workingCopy == null ? 0 : _workingCopy.Count;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public int StashesCount
|
||||
{
|
||||
get => _stashesPage == null ? 0 : _stashesPage.Stashes.Count;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IncludeUntracked
|
||||
{
|
||||
get => _includeUntracked;
|
||||
|
@ -183,7 +244,6 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsSearching
|
||||
{
|
||||
get => _isSearching;
|
||||
|
@ -199,63 +259,66 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public int SearchCommitFilterType
|
||||
{
|
||||
get => _searchCommitFilterType;
|
||||
set => SetProperty(ref _searchCommitFilterType, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string SearchCommitFilter
|
||||
{
|
||||
get => _searchCommitFilter;
|
||||
set => SetProperty(ref _searchCommitFilter, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.Commit> SearchedCommits
|
||||
{
|
||||
get => _searchedCommits;
|
||||
set => SetProperty(ref _searchedCommits, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsLocalBranchGroupExpanded
|
||||
{
|
||||
get => _isLocalBranchGroupExpanded;
|
||||
set => SetProperty(ref _isLocalBranchGroupExpanded, value);
|
||||
}
|
||||
|
||||
public bool IsRemoteGroupExpanded
|
||||
{
|
||||
get => _isRemoteGroupExpanded;
|
||||
set => SetProperty(ref _isRemoteGroupExpanded, value);
|
||||
}
|
||||
|
||||
public bool IsTagGroupExpanded
|
||||
{
|
||||
get => _isTagGroupExpanded;
|
||||
set => SetProperty(ref _isTagGroupExpanded, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsSubmoduleGroupExpanded
|
||||
{
|
||||
get => _isSubmoduleGroupExpanded;
|
||||
set => SetProperty(ref _isSubmoduleGroupExpanded, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsWorktreeGroupExpanded
|
||||
{
|
||||
get => _isWorktreeGroupExpanded;
|
||||
set => SetProperty(ref _isWorktreeGroupExpanded, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public InProgressContext InProgressContext
|
||||
{
|
||||
get => _inProgressContext;
|
||||
private set => SetProperty(ref _inProgressContext, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasUnsolvedConflicts
|
||||
{
|
||||
get => _hasUnsolvedConflicts;
|
||||
private set => SetProperty(ref _hasUnsolvedConflicts, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public Models.Commit SearchResultSelectedCommit
|
||||
{
|
||||
get => _searchResultSelectedCommit;
|
||||
|
@ -264,6 +327,23 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void Open()
|
||||
{
|
||||
var settingsFile = Path.Combine(_gitDir, "sourcegit.settings");
|
||||
if (File.Exists(settingsFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
_settings = JsonSerializer.Deserialize(File.ReadAllText(settingsFile), JsonCodeGen.Default.RepositorySettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_settings = new RepositorySettings();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings = new RepositorySettings();
|
||||
}
|
||||
|
||||
_watcher = new Models.Watcher(this);
|
||||
_histories = new Histories(this);
|
||||
_workingCopy = new WorkingCopy(this);
|
||||
|
@ -280,6 +360,10 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
SelectedView = 0.0; // Do NOT modify. Used to remove exists widgets for GC.Collect
|
||||
|
||||
var settingsSerialized = JsonSerializer.Serialize(_settings, JsonCodeGen.Default.RepositorySettings);
|
||||
File.WriteAllText(Path.Combine(_gitDir, "sourcegit.settings"), settingsSerialized);
|
||||
_settings = null;
|
||||
|
||||
_watcher.Dispose();
|
||||
_histories.Cleanup();
|
||||
_workingCopy.Cleanup();
|
||||
|
@ -289,14 +373,7 @@ namespace SourceGit.ViewModels
|
|||
_histories = null;
|
||||
_workingCopy = null;
|
||||
_stashesPage = null;
|
||||
_isSearching = false;
|
||||
_searchCommitFilter = string.Empty;
|
||||
|
||||
_isTagGroupExpanded = false;
|
||||
_isSubmoduleGroupExpanded = false;
|
||||
|
||||
_inProgressContext = null;
|
||||
_hasUnsolvedConflicts = false;
|
||||
|
||||
_remotes.Clear();
|
||||
_branches.Clear();
|
||||
|
@ -436,7 +513,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void ClearHistoriesFilter()
|
||||
{
|
||||
Filters.Clear();
|
||||
_settings.Filters.Clear();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
|
@ -525,15 +602,15 @@ namespace SourceGit.ViewModels
|
|||
var changed = false;
|
||||
if (toggle)
|
||||
{
|
||||
if (!Filters.Contains(filter))
|
||||
if (!_settings.Filters.Contains(filter))
|
||||
{
|
||||
Filters.Add(filter);
|
||||
_settings.Filters.Add(filter);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
changed = Filters.Remove(filter);
|
||||
changed = _settings.Filters.Remove(filter);
|
||||
}
|
||||
|
||||
if (changed)
|
||||
|
@ -637,7 +714,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
var tags = new Commands.QueryTags(FullPath).Result();
|
||||
foreach (var tag in tags)
|
||||
tag.IsFiltered = Filters.Contains(tag.Name);
|
||||
tag.IsFiltered = _settings.Filters.Contains(tag.Name);
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
|
@ -652,7 +729,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
var limits = $"-{Preference.Instance.MaxHistoryCommits} ";
|
||||
var validFilters = new List<string>();
|
||||
foreach (var filter in Filters)
|
||||
foreach (var filter in _settings.Filters)
|
||||
{
|
||||
if (filter.StartsWith("refs/", StringComparison.Ordinal))
|
||||
{
|
||||
|
@ -670,12 +747,12 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
limits += string.Join(" ", validFilters);
|
||||
|
||||
if (Filters.Count != validFilters.Count)
|
||||
if (_settings.Filters.Count != validFilters.Count)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
Filters.Clear();
|
||||
Filters.AddRange(validFilters);
|
||||
_settings.Filters.Clear();
|
||||
_settings.Filters.AddRange(validFilters);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -685,7 +762,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
|
||||
var commits = new Commands.QueryCommits(FullPath, limits).Result();
|
||||
var graph = Models.CommitGraph.Parse(commits, 8);
|
||||
var graph = Models.CommitGraph.Parse(commits);
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
|
@ -851,22 +928,23 @@ namespace SourceGit.ViewModels
|
|||
public void OpenSubmodule(string submodule)
|
||||
{
|
||||
var root = Path.GetFullPath(Path.Combine(_fullpath, submodule));
|
||||
var gitDir = new Commands.QueryGitDir(root).Result();
|
||||
var repo = Preference.AddRepository(root, gitDir);
|
||||
var normalizedPath = root.Replace("\\", "/");
|
||||
|
||||
var node = new RepositoryNode()
|
||||
var node = Preference.FindNode(normalizedPath);
|
||||
if (node == null)
|
||||
{
|
||||
Id = repo.FullPath,
|
||||
Name = Path.GetFileName(repo.FullPath),
|
||||
Bookmark = 0,
|
||||
IsRepository = true,
|
||||
};
|
||||
node = new RepositoryNode()
|
||||
{
|
||||
Id = normalizedPath,
|
||||
Name = Path.GetFileName(normalizedPath),
|
||||
Bookmark = 0,
|
||||
IsRepository = true,
|
||||
};
|
||||
}
|
||||
|
||||
var launcher = App.GetTopLevel().DataContext as Launcher;
|
||||
if (launcher != null)
|
||||
{
|
||||
launcher.OpenRepositoryInTab(node, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddWorktree()
|
||||
|
@ -883,16 +961,17 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void OpenWorktree(Models.Worktree worktree)
|
||||
{
|
||||
var gitDir = new Commands.QueryGitDir(worktree.FullPath).Result();
|
||||
var repo = Preference.AddRepository(worktree.FullPath, gitDir);
|
||||
|
||||
var node = new RepositoryNode()
|
||||
var node = Preference.FindNode(worktree.FullPath);
|
||||
if (node == null)
|
||||
{
|
||||
Id = repo.FullPath,
|
||||
Name = Path.GetFileName(repo.FullPath),
|
||||
Bookmark = 0,
|
||||
IsRepository = true,
|
||||
};
|
||||
node = new RepositoryNode()
|
||||
{
|
||||
Id = worktree.FullPath,
|
||||
Name = Path.GetFileName(worktree.FullPath),
|
||||
Bookmark = 0,
|
||||
IsRepository = true,
|
||||
};
|
||||
}
|
||||
|
||||
var launcher = App.GetTopLevel().DataContext as Launcher;
|
||||
if (launcher != null)
|
||||
|
@ -1183,7 +1262,7 @@ namespace SourceGit.ViewModels
|
|||
if (upstream != null)
|
||||
{
|
||||
var fastForward = new MenuItem();
|
||||
fastForward.Header = new Views.NameHighlightedTextBlock("BranchCM.FastForward", $"{upstream.Remote}/{upstream.Name}");
|
||||
fastForward.Header = new Views.NameHighlightedTextBlock("BranchCM.FastForward", upstream.FriendlyName);
|
||||
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
|
||||
fastForward.IsEnabled = !string.IsNullOrEmpty(branch.UpstreamTrackStatus) && branch.UpstreamTrackStatus.IndexOf('↑') < 0;
|
||||
fastForward.Click += (o, e) =>
|
||||
|
@ -1472,9 +1551,10 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
var menu = new ContextMenu();
|
||||
var current = Branches.Find(x => x.IsCurrent);
|
||||
var name = branch.FriendlyName;
|
||||
|
||||
var checkout = new MenuItem();
|
||||
checkout.Header = new Views.NameHighlightedTextBlock("BranchCM.Checkout", $"{branch.Remote}/{branch.Name}");
|
||||
checkout.Header = new Views.NameHighlightedTextBlock("BranchCM.Checkout", name);
|
||||
checkout.Icon = App.CreateMenuIcon("Icons.Check");
|
||||
checkout.Click += (o, e) =>
|
||||
{
|
||||
|
@ -1487,7 +1567,7 @@ namespace SourceGit.ViewModels
|
|||
if (current != null)
|
||||
{
|
||||
var pull = new MenuItem();
|
||||
pull.Header = new Views.NameHighlightedTextBlock("BranchCM.PullInto", $"{branch.Remote}/{branch.Name}", current.Name);
|
||||
pull.Header = new Views.NameHighlightedTextBlock("BranchCM.PullInto", name, current.Name);
|
||||
pull.Icon = App.CreateMenuIcon("Icons.Pull");
|
||||
pull.Click += (o, e) =>
|
||||
{
|
||||
|
@ -1497,17 +1577,17 @@ namespace SourceGit.ViewModels
|
|||
};
|
||||
|
||||
var merge = new MenuItem();
|
||||
merge.Header = new Views.NameHighlightedTextBlock("BranchCM.Merge", $"{branch.Remote}/{branch.Name}", current.Name);
|
||||
merge.Header = new Views.NameHighlightedTextBlock("BranchCM.Merge", name, current.Name);
|
||||
merge.Icon = App.CreateMenuIcon("Icons.Merge");
|
||||
merge.Click += (o, e) =>
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
PopupHost.ShowPopup(new Merge(this, $"{branch.Remote}/{branch.Name}", current.Name));
|
||||
PopupHost.ShowPopup(new Merge(this, name, current.Name));
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var rebase = new MenuItem();
|
||||
rebase.Header = new Views.NameHighlightedTextBlock("BranchCM.Rebase", current.Name, $"{branch.Remote}/{branch.Name}");
|
||||
rebase.Header = new Views.NameHighlightedTextBlock("BranchCM.Rebase", current.Name, name);
|
||||
rebase.Icon = App.CreateMenuIcon("Icons.Rebase");
|
||||
rebase.Click += (o, e) =>
|
||||
{
|
||||
|
@ -1554,7 +1634,7 @@ namespace SourceGit.ViewModels
|
|||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
var delete = new MenuItem();
|
||||
delete.Header = new Views.NameHighlightedTextBlock("BranchCM.Delete", $"{branch.Remote}/{branch.Name}");
|
||||
delete.Header = new Views.NameHighlightedTextBlock("BranchCM.Delete", name);
|
||||
delete.Icon = App.CreateMenuIcon("Icons.Clear");
|
||||
delete.Click += (o, e) =>
|
||||
{
|
||||
|
@ -1598,7 +1678,7 @@ namespace SourceGit.ViewModels
|
|||
copy.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copy.Click += (o, e) =>
|
||||
{
|
||||
App.CopyText(branch.Remote + "/" + branch.Name);
|
||||
App.CopyText(name);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -1791,7 +1871,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
var dup = b;
|
||||
var target = new MenuItem();
|
||||
target.Header = b.IsLocal ? b.Name : $"{b.Remote}/{b.Name}";
|
||||
target.Header = b.FriendlyName;
|
||||
target.Icon = App.CreateMenuIcon(b.IsCurrent ? "Icons.Check" : "Icons.Branch");
|
||||
target.Click += (_, e) =>
|
||||
{
|
||||
|
@ -1814,7 +1894,7 @@ namespace SourceGit.ViewModels
|
|||
private BranchTreeNode.Builder BuildBranchTree(List<Models.Branch> branches, List<Models.Remote> remotes)
|
||||
{
|
||||
var builder = new BranchTreeNode.Builder();
|
||||
builder.SetFilters(Filters);
|
||||
builder.SetFilters(_settings.Filters);
|
||||
|
||||
if (string.IsNullOrEmpty(_searchBranchFilter))
|
||||
{
|
||||
|
@ -1831,7 +1911,7 @@ namespace SourceGit.ViewModels
|
|||
visibles.Add(b);
|
||||
}
|
||||
|
||||
builder.Run(visibles, remotes, visibles.Count <= 20);
|
||||
builder.Run(visibles, remotes, true);
|
||||
}
|
||||
|
||||
return builder;
|
||||
|
@ -1858,6 +1938,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
private string _fullpath = string.Empty;
|
||||
private string _gitDir = string.Empty;
|
||||
private RepositorySettings _settings = null;
|
||||
|
||||
private Models.Watcher _watcher = null;
|
||||
private Histories _histories = null;
|
||||
|
@ -1871,6 +1952,8 @@ namespace SourceGit.ViewModels
|
|||
private string _searchCommitFilter = string.Empty;
|
||||
private List<Models.Commit> _searchedCommits = new List<Models.Commit>();
|
||||
|
||||
private bool _isLocalBranchGroupExpanded = true;
|
||||
private bool _isRemoteGroupExpanded = false;
|
||||
private bool _isTagGroupExpanded = false;
|
||||
private bool _isSubmoduleGroupExpanded = false;
|
||||
private bool _isWorktreeGroupExpanded = false;
|
||||
|
|
|
@ -118,9 +118,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void NavigateTo(string commitSHA)
|
||||
{
|
||||
var repo = Preference.FindRepository(_repo);
|
||||
if (repo != null)
|
||||
repo.NavigateToCommit(commitSHA);
|
||||
var repo = App.FindOpenedRepository(_repo);
|
||||
repo?.NavigateToCommit(commitSHA);
|
||||
}
|
||||
|
||||
public void ClearSearchFilter()
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var succ = new Commands.Commit(_repo.FullPath, _message, true, true).Exec();
|
||||
var succ = new Commands.Commit(_repo.FullPath, _message, false, true, true).Exec();
|
||||
CallUIThread(() => _repo.SetWatcherEnabled(true));
|
||||
return succ;
|
||||
});
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
var succ = new Commands.Reset(_repo.FullPath, Parent.SHA, "--soft").Exec();
|
||||
if (succ)
|
||||
succ = new Commands.Commit(_repo.FullPath, _message, true).Exec();
|
||||
succ = new Commands.Commit(_repo.FullPath, _message, false, true).Exec();
|
||||
CallUIThread(() => _repo.SetWatcherEnabled(true));
|
||||
return succ;
|
||||
});
|
||||
|
|
|
@ -90,7 +90,6 @@ namespace SourceGit.ViewModels
|
|||
public ContextMenu CreateContextMenu(RepositoryNode node)
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
var hasRepo = Preference.FindRepository(node.Id) != null;
|
||||
|
||||
if (!node.IsRepository && node.SubNodes.Count > 0)
|
||||
{
|
||||
|
@ -115,7 +114,6 @@ namespace SourceGit.ViewModels
|
|||
var edit = new MenuItem();
|
||||
edit.Header = App.Text("Welcome.Edit");
|
||||
edit.Icon = App.CreateMenuIcon("Icons.Edit");
|
||||
edit.IsEnabled = !node.IsRepository || hasRepo;
|
||||
edit.Click += (_, e) =>
|
||||
{
|
||||
node.Edit();
|
||||
|
@ -128,7 +126,6 @@ namespace SourceGit.ViewModels
|
|||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("Repository.Explore");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Folder.Open");
|
||||
explore.IsEnabled = hasRepo;
|
||||
explore.Click += (_, e) =>
|
||||
{
|
||||
node.OpenInFileManager();
|
||||
|
@ -139,7 +136,6 @@ namespace SourceGit.ViewModels
|
|||
var terminal = new MenuItem();
|
||||
terminal.Header = App.Text("Repository.Terminal");
|
||||
terminal.Icon = App.CreateMenuIcon("Icons.Terminal");
|
||||
terminal.IsEnabled = hasRepo;
|
||||
terminal.Click += (_, e) =>
|
||||
{
|
||||
node.OpenTerminal();
|
||||
|
|
|
@ -77,6 +77,12 @@ namespace SourceGit.ViewModels
|
|||
private set => SetProperty(ref _isCommitting, value);
|
||||
}
|
||||
|
||||
public bool AutoStageBeforeCommit
|
||||
{
|
||||
get => _repo.Settings.AutoStageBeforeCommit;
|
||||
set => _repo.Settings.AutoStageBeforeCommit = value;
|
||||
}
|
||||
|
||||
public bool UseAmend
|
||||
{
|
||||
get => _useAmend;
|
||||
|
@ -1113,7 +1119,7 @@ namespace SourceGit.ViewModels
|
|||
public ContextMenu CreateContextMenuForCommitMessages()
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
if (_repo.CommitMessages.Count == 0)
|
||||
if (_repo.Settings.CommitMessages.Count == 0)
|
||||
{
|
||||
var empty = new MenuItem();
|
||||
empty.Header = App.Text("WorkingCopy.NoCommitHistories");
|
||||
|
@ -1128,7 +1134,7 @@ namespace SourceGit.ViewModels
|
|||
menu.Items.Add(tip);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
foreach (var message in _repo.CommitMessages)
|
||||
foreach (var message in _repo.Settings.CommitMessages)
|
||||
{
|
||||
var dump = message;
|
||||
|
||||
|
@ -1216,7 +1222,13 @@ namespace SourceGit.ViewModels
|
|||
return;
|
||||
}
|
||||
|
||||
if (_staged.Count == 0)
|
||||
if (_count == 0)
|
||||
{
|
||||
App.RaiseException(_repo.FullPath, "No files added to commit!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AutoStageBeforeCommit && _staged.Count == 0)
|
||||
{
|
||||
App.RaiseException(_repo.FullPath, "No files added to commit!");
|
||||
return;
|
||||
|
@ -1228,15 +1240,16 @@ namespace SourceGit.ViewModels
|
|||
return;
|
||||
}
|
||||
|
||||
PushCommitMessage();
|
||||
_repo.Settings.PushCommitMessage(_commitMessage);
|
||||
|
||||
SetDetail(null);
|
||||
IsCommitting = true;
|
||||
_repo.SetWatcherEnabled(false);
|
||||
|
||||
var autoStage = AutoStageBeforeCommit;
|
||||
Task.Run(() =>
|
||||
{
|
||||
var succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend).Exec();
|
||||
var succ = new Commands.Commit(_repo.FullPath, _commitMessage, autoStage, _useAmend).Exec();
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (succ)
|
||||
|
@ -1257,27 +1270,6 @@ namespace SourceGit.ViewModels
|
|||
});
|
||||
}
|
||||
|
||||
private void PushCommitMessage()
|
||||
{
|
||||
var existIdx = _repo.CommitMessages.IndexOf(CommitMessage);
|
||||
if (existIdx == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (existIdx > 0)
|
||||
{
|
||||
_repo.CommitMessages.Move(existIdx, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_repo.CommitMessages.Count > 9)
|
||||
{
|
||||
_repo.CommitMessages.RemoveRange(9, _repo.CommitMessages.Count - 9);
|
||||
}
|
||||
|
||||
_repo.CommitMessages.Insert(0, CommitMessage);
|
||||
}
|
||||
|
||||
private Repository _repo = null;
|
||||
private bool _isLoadingData = false;
|
||||
private bool _isStaging = false;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<DataTemplate DataType="m:Branch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding FriendlyName}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
|
|
|
@ -378,11 +378,9 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnCommitSHAPointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Blame blame)
|
||||
{
|
||||
var txt = sender as TextBlock;
|
||||
if (sender is TextBlock txt && DataContext is ViewModels.Blame blame)
|
||||
blame.NavigateToCommit(txt.Text);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
User="{Binding BaseHead.Author}"/>
|
||||
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding BaseHead.Author.Name}" Margin="8,0,0,0"/>
|
||||
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
|
||||
<TextBlock Text="{Binding Base, Converter={x:Static c:BranchConverters.ToName}}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
|
||||
<TextBlock Text="{Binding Base.FriendlyName}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="3" Classes="monospace" Text="{Binding BaseHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA"/>
|
||||
<TextBlock Grid.Column="4" Classes="monospace" Text="{Binding BaseHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||
|
@ -82,7 +82,7 @@
|
|||
User="{Binding ToHead.Author}"/>
|
||||
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding ToHead.Author.Name}" Margin="8,0,0,0"/>
|
||||
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
|
||||
<TextBlock Text="{Binding To, Converter={x:Static c:BranchConverters.ToName}}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
|
||||
<TextBlock Text="{Binding To.FriendlyName}" Classes="monospace" Margin="4,0" Foreground="#FFDDDDDD"/>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="3" Classes="monospace" Text="{Binding ToHead.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" PointerPressed="OnPressedSHA"/>
|
||||
<TextBlock Grid.Column="4" Classes="monospace" Text="{Binding ToHead.CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||
|
|
108
src/Views/BranchTree.axaml
Normal file
108
src/Views/BranchTree.axaml
Normal file
|
@ -0,0 +1,108 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.BranchTree"
|
||||
x:Name="ThisControl">
|
||||
<DataGrid x:Name="BranchesPresenter"
|
||||
ItemsSource="{Binding #ThisControl.Rows}"
|
||||
Background="Transparent"
|
||||
RowHeight="24"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HeadersVisibility="None"
|
||||
SelectionChanged="OnNodesSelectionChanged"
|
||||
ContextRequested="OnTreeContextRequested">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow" x:DataType="vm:BranchTreeNode">
|
||||
<Setter Property="CornerRadius" Value="{Binding CornerRadius}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value=".5"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".65"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".8"/>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="vm:BranchTreeNode">
|
||||
<Grid Height="24"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
ColumnDefinitions="16,20,*,Auto,Auto"
|
||||
Background="Transparent"
|
||||
DoubleTapped="OnDoubleTappedBranchNode"
|
||||
ToolTip.Tip="{Binding Tooltip}">
|
||||
|
||||
<!-- Tree Expander -->
|
||||
<ToggleButton Grid.Column="0"
|
||||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding IsExpanded}"
|
||||
IsHitTestVisible="False"
|
||||
IsVisible="{Binding !IsBranch}"/>
|
||||
|
||||
<!-- Icon -->
|
||||
<v:BranchTreeNodeIcon Grid.Column="1"
|
||||
Node="{Binding}"
|
||||
IsExpanded="{Binding IsExpanded}"/>
|
||||
|
||||
<!-- Name -->
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding Name}"
|
||||
Classes="monospace"
|
||||
FontWeight="{Binding NameFontWeight}"/>
|
||||
|
||||
<!-- Tracking status -->
|
||||
<Border Grid.Column="3"
|
||||
Margin="8,0"
|
||||
Height="18"
|
||||
CornerRadius="9"
|
||||
VerticalAlignment="Center"
|
||||
Background="{DynamicResource Brush.Badge}"
|
||||
IsVisible="{Binding IsUpstreamTrackStatusVisible}">
|
||||
<TextBlock Classes="monospace" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding UpstreamTrackStatus}" Foreground="{DynamicResource Brush.BadgeFG}"/>
|
||||
</Border>
|
||||
|
||||
<!-- Filter Toggle Button -->
|
||||
<ToggleButton Grid.Column="4"
|
||||
Classes="filter"
|
||||
Margin="0,0,8,0"
|
||||
Background="Transparent"
|
||||
IsCheckedChanged="OnToggleFilter"
|
||||
IsVisible="{Binding IsBranch}"
|
||||
IsChecked="{Binding IsFiltered}"
|
||||
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</UserControl>
|
||||
|
347
src/Views/BranchTree.axaml.cs
Normal file
347
src/Views/BranchTree.axaml.cs
Normal file
|
@ -0,0 +1,347 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public class BranchTreeNodeIcon : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<ViewModels.BranchTreeNode> NodeProperty =
|
||||
AvaloniaProperty.Register<BranchTreeNodeIcon, ViewModels.BranchTreeNode>(nameof(Node));
|
||||
|
||||
public ViewModels.BranchTreeNode Node
|
||||
{
|
||||
get => GetValue(NodeProperty);
|
||||
set => SetValue(NodeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsExpandedProperty =
|
||||
AvaloniaProperty.Register<BranchTreeNodeIcon, bool>(nameof(IsExpanded));
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => GetValue(IsExpandedProperty);
|
||||
set => SetValue(IsExpandedProperty, value);
|
||||
}
|
||||
|
||||
static BranchTreeNodeIcon()
|
||||
{
|
||||
NodeProperty.Changed.AddClassHandler<BranchTreeNodeIcon>((icon, _) => icon.UpdateContent());
|
||||
IsExpandedProperty.Changed.AddClassHandler<BranchTreeNodeIcon>((icon, _) => icon.UpdateContent());
|
||||
}
|
||||
|
||||
private void UpdateContent()
|
||||
{
|
||||
var node = Node;
|
||||
if (node == null)
|
||||
{
|
||||
Content = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.Backend is Models.Remote)
|
||||
{
|
||||
CreateContent(12, new Thickness(0,2,0,0), "Icons.Remote");
|
||||
}
|
||||
else if (node.Backend is Models.Branch branch)
|
||||
{
|
||||
if (branch.IsCurrent)
|
||||
CreateContent(12, new Thickness(0,2,0,0), "Icons.Check");
|
||||
else
|
||||
CreateContent(12, new Thickness(2,0,0,0), "Icons.Branch");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (node.IsExpanded)
|
||||
CreateContent(10, new Thickness(0,2,0,0), "Icons.Folder.Open");
|
||||
else
|
||||
CreateContent(10, new Thickness(0,2,0,0), "Icons.Folder.Fill");
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateContent(double size, Thickness margin, string iconKey)
|
||||
{
|
||||
var geo = this.FindResource(iconKey) as StreamGeometry;
|
||||
if (geo == null)
|
||||
return;
|
||||
|
||||
Content = new Path()
|
||||
{
|
||||
Width = size,
|
||||
Height = size,
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = margin,
|
||||
Data = geo,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public partial class BranchTree : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<List<ViewModels.BranchTreeNode>> NodesProperty =
|
||||
AvaloniaProperty.Register<BranchTree, List<ViewModels.BranchTreeNode>>(nameof(Nodes));
|
||||
|
||||
public List<ViewModels.BranchTreeNode> Nodes
|
||||
{
|
||||
get => GetValue(NodesProperty);
|
||||
set => SetValue(NodesProperty, value);
|
||||
}
|
||||
|
||||
public AvaloniaList<ViewModels.BranchTreeNode> Rows
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new AvaloniaList<ViewModels.BranchTreeNode>();
|
||||
|
||||
public static readonly RoutedEvent<RoutedEventArgs> SelectionChangedEvent =
|
||||
RoutedEvent.Register<BranchTree, RoutedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<RoutedEventArgs> SelectionChanged
|
||||
{
|
||||
add { AddHandler(SelectionChangedEvent, value); }
|
||||
remove { RemoveHandler(SelectionChangedEvent, value); }
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent<RoutedEventArgs> RowsChangedEvent =
|
||||
RoutedEvent.Register<BranchTree, RoutedEventArgs>(nameof(RowsChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<RoutedEventArgs> RowsChanged
|
||||
{
|
||||
add { AddHandler(RowsChangedEvent, value); }
|
||||
remove { RemoveHandler(RowsChangedEvent, value); }
|
||||
}
|
||||
|
||||
public BranchTree()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void UnselectAll()
|
||||
{
|
||||
BranchesPresenter.SelectedItem = null;
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged(SizeChangedEventArgs e)
|
||||
{
|
||||
base.OnSizeChanged(e);
|
||||
|
||||
if (Bounds.Height >= 23.0)
|
||||
BranchesPresenter.Height = Bounds.Height;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == NodesProperty)
|
||||
{
|
||||
Rows.Clear();
|
||||
|
||||
if (Nodes is { Count: > 0 })
|
||||
{
|
||||
var rows = new List<ViewModels.BranchTreeNode>();
|
||||
MakeRows(rows, Nodes, 0);
|
||||
Rows.AddRange(rows);
|
||||
}
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
else if (change.Property == IsVisibleProperty)
|
||||
{
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodesSelectionChanged(object _, SelectionChangedEventArgs e)
|
||||
{
|
||||
var repo = DataContext as ViewModels.Repository;
|
||||
if (repo?.Settings == null)
|
||||
return;
|
||||
|
||||
foreach (var item in e.AddedItems)
|
||||
{
|
||||
if (item is ViewModels.BranchTreeNode node)
|
||||
node.IsSelected = true;
|
||||
}
|
||||
|
||||
foreach (var item in e.RemovedItems)
|
||||
{
|
||||
if (item is ViewModels.BranchTreeNode node)
|
||||
node.IsSelected = false;
|
||||
}
|
||||
|
||||
var selected = BranchesPresenter.SelectedItems;
|
||||
if (selected == null || selected.Count == 0)
|
||||
return;
|
||||
|
||||
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
|
||||
repo.NavigateToCommit(branch.Head);
|
||||
|
||||
var prev = null as ViewModels.BranchTreeNode;
|
||||
foreach (var row in Rows)
|
||||
{
|
||||
if (row.IsSelected)
|
||||
{
|
||||
if (prev is { IsSelected: true })
|
||||
{
|
||||
var prevTop = prev.CornerRadius.TopLeft;
|
||||
prev.CornerRadius = new CornerRadius(prevTop, 0);
|
||||
row.CornerRadius = new CornerRadius(0, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
row.CornerRadius = new CornerRadius(4);
|
||||
}
|
||||
}
|
||||
|
||||
prev = row;
|
||||
}
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||
}
|
||||
|
||||
private void OnTreeContextRequested(object _1, ContextRequestedEventArgs _2)
|
||||
{
|
||||
var repo = DataContext as ViewModels.Repository;
|
||||
if (repo?.Settings == null)
|
||||
return;
|
||||
|
||||
var selected = BranchesPresenter.SelectedItems;
|
||||
if (selected == null || selected.Count == 0)
|
||||
return;
|
||||
|
||||
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Remote remote })
|
||||
{
|
||||
var menu = repo.CreateContextMenuForRemote(remote);
|
||||
this.OpenContextMenu(menu);
|
||||
return;
|
||||
}
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
foreach (var item in selected)
|
||||
{
|
||||
if (item is ViewModels.BranchTreeNode node)
|
||||
CollectBranchesInNode(branches, node);
|
||||
}
|
||||
|
||||
if (branches.Count == 1)
|
||||
{
|
||||
var branch = branches[0];
|
||||
var menu = branch.IsLocal ?
|
||||
repo.CreateContextMenuForLocalBranch(branch) :
|
||||
repo.CreateContextMenuForRemoteBranch(branch);
|
||||
this.OpenContextMenu(menu);
|
||||
}
|
||||
else if (branches.Find(x => x.IsCurrent) == null)
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
var deleteMulti = new MenuItem();
|
||||
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
|
||||
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
|
||||
deleteMulti.Click += (_, ev) =>
|
||||
{
|
||||
repo.DeleteMultipleBranches(branches, branches[0].IsLocal);
|
||||
ev.Handled = true;
|
||||
};
|
||||
menu.Items.Add(deleteMulti);
|
||||
this.OpenContextMenu(menu);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDoubleTappedBranchNode(object sender, TappedEventArgs _)
|
||||
{
|
||||
if (sender is Grid { DataContext: ViewModels.BranchTreeNode node })
|
||||
{
|
||||
if (node.Backend is Models.Branch branch)
|
||||
{
|
||||
if (branch.IsCurrent)
|
||||
return;
|
||||
|
||||
if (DataContext is ViewModels.Repository { Settings: not null } repo)
|
||||
repo.CheckoutBranch(branch);
|
||||
}
|
||||
else
|
||||
{
|
||||
node.IsExpanded = !node.IsExpanded;
|
||||
|
||||
var rows = Rows;
|
||||
var depth = node.Depth;
|
||||
var idx = rows.IndexOf(node);
|
||||
if (idx == -1)
|
||||
return;
|
||||
|
||||
if (node.IsExpanded)
|
||||
{
|
||||
var subtree = new List<ViewModels.BranchTreeNode>();
|
||||
MakeRows(subtree, node.Children, depth + 1);
|
||||
rows.InsertRange(idx + 1, subtree);
|
||||
}
|
||||
else
|
||||
{
|
||||
var removeCount = 0;
|
||||
for (int i = idx + 1; i < rows.Count; i++)
|
||||
{
|
||||
var row = rows[i];
|
||||
if (row.Depth <= depth)
|
||||
break;
|
||||
|
||||
removeCount++;
|
||||
}
|
||||
rows.RemoveRange(idx + 1, removeCount);
|
||||
}
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnToggleFilter(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
if (toggle.DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
|
||||
repo.UpdateFilter(branch.FullName, toggle.IsChecked == true);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void MakeRows(List<ViewModels.BranchTreeNode> rows, List<ViewModels.BranchTreeNode> nodes, int depth)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.Depth = depth;
|
||||
rows.Add(node);
|
||||
|
||||
if (!node.IsExpanded || node.Backend is Models.Branch)
|
||||
continue;
|
||||
|
||||
MakeRows(rows, node.Children, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectBranchesInNode(List<Models.Branch> outs, ViewModels.BranchTreeNode node)
|
||||
{
|
||||
if (node.Backend is Models.Branch branch && !outs.Contains(branch))
|
||||
{
|
||||
outs.Add(branch);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sub in node.Children)
|
||||
CollectBranchesInNode(outs, sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -90,11 +90,11 @@
|
|||
<DataTemplate DataType="{x:Type m:Decorator}">
|
||||
<Border Height="16" Margin="0,0,6,0" CornerRadius="2" ClipToBounds="True">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border Background="{DynamicResource Brush.Decorator}" Width="16">
|
||||
<Border Background="{DynamicResource Brush.DecoratorIconBG}" Width="16">
|
||||
<Path Width="8" Height="8" Data="{Binding Type, Converter={x:Static c:DecoratorTypeConverters.ToIcon}}" Fill="{DynamicResource Brush.DecoratorIcon}"/>
|
||||
</Border>
|
||||
<Border Background="{Binding Type, Converter={x:Static c:DecoratorTypeConverters.ToBackground}}">
|
||||
<TextBlock Classes="monospace" Text="{Binding Name}" FontSize="10" Margin="4,0" Foreground="Black"/>
|
||||
<TextBlock Classes="monospace" Text="{Binding Name}" FontSize="10" Margin="4,0" Foreground="{DynamicResource Brush.DecoratorFG}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
@ -104,9 +104,7 @@
|
|||
|
||||
<!-- Messages -->
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Message}" VerticalAlignment="Top" Margin="0,4,0,0" />
|
||||
<ScrollViewer Grid.Row="3" Grid.Column="1" Margin="12,5,8,0" MaxHeight="64" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<SelectableTextBlock Text="{Binding #ThisControl.Message}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" TextWrapping="Wrap"/>
|
||||
</ScrollViewer>
|
||||
<SelectableTextBlock Grid.Row="3" Grid.Column="1" Margin="12,5,8,0" Text="{Binding #ThisControl.Message}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
|
|
@ -17,58 +17,55 @@
|
|||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.CommitDetail.Info}"/>
|
||||
</TabItem.Header>
|
||||
|
||||
<Grid RowDefinitions="Auto,1,*">
|
||||
<!-- Base Information -->
|
||||
<v:CommitBaseInfo Grid.Row="0" Content="{Binding Commit}" Message="{Binding FullMessage}"/>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<!-- Base Information -->
|
||||
<v:CommitBaseInfo Content="{Binding Commit}" Message="{Binding FullMessage}"/>
|
||||
|
||||
<!-- Line -->
|
||||
<Rectangle Grid.Row="1" Height=".65" Margin="8" Fill="{DynamicResource Brush.Border2}" VerticalAlignment="Center"/>
|
||||
<!-- Line -->
|
||||
<Rectangle Height=".65" Margin="8" Fill="{DynamicResource Brush.Border2}"/>
|
||||
|
||||
<!-- Change List -->
|
||||
<DataGrid Grid.Row="2"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Changes}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
Margin="64,0,8,16"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnChangeListContextRequested"
|
||||
DoubleTapped="OnChangeListDoubleTapped">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||
<Setter Property="ClipToBounds" Value="True"/>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
<!-- Change List -->
|
||||
<ListBox Background="Transparent"
|
||||
Margin="64,0,8,4"
|
||||
SelectionMode="Single"
|
||||
ItemsSource="{Binding Changes, Converter={x:Static c:ListConverters.Top100Changes}}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="MinHeight" Value="26"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="36" Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<v:ChangeStatusIcon Width="14" Height="14" HorizontalAlignment="Left" Margin="16,0,0,0" IsWorkingCopyChange="False" Change="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="PATH">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Classes="monospace" Text="{Binding Path}" Margin="8,0,0,0"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Change">
|
||||
<Grid Background="Transparent" Height="26" ColumnDefinitions="36,*" ContextRequested="OnChangeContextRequested" DoubleTapped="OnChangeDoubleTapped">
|
||||
<v:ChangeStatusIcon Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="16,0,0,0"
|
||||
IsWorkingCopyChange="False"
|
||||
Change="{Binding}"/>
|
||||
<TextBlock Grid.Column="1" Classes="monospace" Text="{Binding Path}" Margin="8,0" TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<!-- Only Top100 Tip -->
|
||||
<TextBlock Margin="108,0,0,16"
|
||||
Text="{DynamicResource Text.CommitDetail.Info.GotoChangesPage}"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
IsVisible="{Binding Changes, Converter={x:Static c:ListConverters.IsOnlyTop100Shows}}"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<TabItem>
|
||||
|
|
|
@ -10,37 +10,23 @@ namespace SourceGit.Views
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnChangeListDoubleTapped(object sender, TappedEventArgs e)
|
||||
private void OnChangeDoubleTapped(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.CommitDetail detail)
|
||||
if (DataContext is ViewModels.CommitDetail detail && sender is Grid grid && grid.DataContext is Models.Change change)
|
||||
{
|
||||
var datagrid = sender as DataGrid;
|
||||
if (datagrid.SelectedItem == null)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
detail.ActivePageIndex = 1;
|
||||
detail.SelectedChanges = new() { datagrid.SelectedItem as Models.Change };
|
||||
detail.SelectedChanges = new() { change };
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnChangeListContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.CommitDetail detail)
|
||||
if (DataContext is ViewModels.CommitDetail detail && sender is Grid grid && grid.DataContext is Models.Change change)
|
||||
{
|
||||
var datagrid = sender as DataGrid;
|
||||
if (datagrid.SelectedItem == null)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var menu = detail.CreateChangeContextMenu(datagrid.SelectedItem as Models.Change);
|
||||
datagrid.OpenContextMenu(menu);
|
||||
var menu = detail.CreateChangeContextMenu(change);
|
||||
grid.OpenContextMenu(menu);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
|
|
|
@ -20,6 +20,11 @@ namespace SourceGit.Views
|
|||
|
||||
protected override Type StyleKeyOverride => typeof(TextBox);
|
||||
|
||||
public void Paste(string text)
|
||||
{
|
||||
OnTextInput(new TextInputEventArgs() { Text = text });
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
var dump = new KeyEventArgs()
|
||||
|
@ -112,7 +117,7 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
private void OnSubjectTextBoxPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
private async void OnSubjectTextBoxPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter || (e.Key == Key.Right && SubjectEditor.CaretIndex == Subject.Length))
|
||||
{
|
||||
|
@ -120,6 +125,36 @@ namespace SourceGit.Views
|
|||
DescriptionEditor.CaretIndex = 0;
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.V && ((OperatingSystem.IsMacOS() && e.KeyModifiers == KeyModifiers.Meta) || (!OperatingSystem.IsMacOS() && e.KeyModifiers == KeyModifiers.Control)))
|
||||
{
|
||||
var text = await App.GetClipboardTextAsync();
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
text = text.Trim();
|
||||
|
||||
if (SubjectEditor.CaretIndex == Subject.Length)
|
||||
{
|
||||
var idx = text.IndexOf('\n');
|
||||
if (idx == -1)
|
||||
{
|
||||
SubjectEditor.Paste(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubjectEditor.Paste(text.Substring(0, idx));
|
||||
DescriptionEditor.Focus();
|
||||
DescriptionEditor.CaretIndex = 0;
|
||||
DescriptionEditor.Paste(text.Substring(idx + 1) + "\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SubjectEditor.Paste(text.ReplaceLineEndings(" "));
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDescriptionTextBoxPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<DataTemplate DataType="m:Branch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding FriendlyName}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<DataTemplate DataType="m:Branch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding FriendlyName}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
|
|
|
@ -2,10 +2,7 @@
|
|||
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"
|
||||
xmlns:m="using:SourceGit.Models"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.DeleteBranch"
|
||||
x:DataType="vm:DeleteBranch">
|
||||
|
@ -18,7 +15,7 @@
|
|||
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Text="{DynamicResource Text.DeleteBranch.Branch}"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Margin="8,0" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Text="{Binding Target, Converter={x:Static c:BranchConverters.ToName}}"/>
|
||||
<TextBlock Text="{Binding Target.FriendlyName}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Row="1" Grid.Column="1" Height="32" IsVisible="{Binding !Target.IsLocal}">
|
||||
|
|
|
@ -64,8 +64,7 @@
|
|||
<DataGridTemplateColumn Width="*" Header="NAME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static c:BranchConverters.ToName}}" ClipToBounds="True"
|
||||
Classes="monospace" />
|
||||
<TextBlock Text="{Binding FriendlyName}" ClipToBounds="True" Classes="monospace" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
|
|
@ -66,8 +66,14 @@
|
|||
Background="Transparent"
|
||||
Padding="9,6"
|
||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap, Mode=TwoWay}"
|
||||
IsVisible="{Binding IsTextDiff}"
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.ToggleWordWrap}">
|
||||
<ToggleButton.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsTextDiff"/>
|
||||
<Binding Source="{x:Static vm:Preference.Instance}" Path="UseSideBySideDiff" Mode="OneWay" Converter="{x:Static BoolConverters.Not}"/>
|
||||
</MultiBinding>
|
||||
</ToggleButton.IsVisible>
|
||||
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.WordWrap}" Margin="0,2,0,0"/>
|
||||
</ToggleButton>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<TextBlock Text="{Binding Local.Name}" Margin="8,0,0,0"/>
|
||||
<TextBlock Text="→" Margin="8,0"/>
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Text="{Binding To, Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
<TextBlock Text="{Binding To.FriendlyName}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Fetch.Title}"/>
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32" ColumnDefinitions="120,*">
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,32" ColumnDefinitions="120,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
|
@ -40,6 +40,10 @@
|
|||
<CheckBox Grid.Row="2" Grid.Column="1"
|
||||
Content="{DynamicResource Text.Fetch.Prune}"
|
||||
IsChecked="{Binding Prune, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="3" Grid.Column="1"
|
||||
Content="{DynamicResource Text.Fetch.NoTags}"
|
||||
IsChecked="{Binding NoTags, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
<DataTemplate DataType="{x:Type m:Decorator}">
|
||||
<Border Height="16" Margin="0,0,4,0" CornerRadius="2" ClipToBounds="True">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border Background="{DynamicResource Brush.Decorator}" Width="16">
|
||||
<Border Background="{DynamicResource Brush.DecoratorIconBG}" Width="16">
|
||||
<Path Width="8" Height="8"
|
||||
Stretch="Uniform"
|
||||
Data="{Binding Type, Converter={x:Static c:DecoratorTypeConverters.ToIcon}}"
|
||||
|
@ -58,7 +58,7 @@
|
|||
Text="{Binding Name}"
|
||||
FontSize="10"
|
||||
Margin="4,0"
|
||||
Foreground="Black"
|
||||
Foreground="{DynamicResource Brush.DecoratorFG}"
|
||||
FontWeight="{Binding Type, Converter={x:Static c:DecoratorTypeConverters.ToFontWeight}}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*" Margin="8">
|
||||
<TextBlock Grid.Column="0" Text="{DynamicResource Text.InteractiveRebase.Target}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Path Grid.Column="1" Width="14" Height="14" Margin="8,0,0,0" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Current, Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Current.FriendlyName}" Margin="8,0,0,0"/>
|
||||
|
||||
<TextBlock Grid.Column="3" Margin="48,0,0,0" Text="{DynamicResource Text.InteractiveRebase.On}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Path Grid.Column="4" Width="14" Height="14" Margin="8,8,0,0" Data="{StaticResource Icons.Commit}"/>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<TextBlock FontSize="18"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.Pull.Title}"/>
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,32,32" ColumnDefinitions="140,*">
|
||||
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,32,32,32" ColumnDefinitions="140,*">
|
||||
<TextBlock Grid.Column="0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
|
@ -48,7 +48,7 @@
|
|||
<DataTemplate x:DataType="{x:Type m:Branch}">
|
||||
<StackPanel Orientation="Horizontal" Height="20" VerticalAlignment="Center">
|
||||
<Path Margin="0,0,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Text="{Binding Converter={x:Static c:BranchConverters.ToName}}"/>
|
||||
<TextBlock Text="{Binding FriendlyName}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
|
@ -88,6 +88,10 @@
|
|||
<CheckBox Grid.Row="4" Grid.Column="1"
|
||||
Content="{DynamicResource Text.Pull.UseRebase}"
|
||||
IsChecked="{Binding UseRebase, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="5" Grid.Column="1"
|
||||
Content="{DynamicResource Text.Pull.NoTags}"
|
||||
IsChecked="{Binding NoTags, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<DataTemplate DataType="m:Branch">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding FriendlyName}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
</Grid>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<Grid Grid.Row="1" Margin="0,0,0,8" RowDefinitions="Auto,Auto,28,Auto,28,*,28,Auto,28,Auto,28,Auto" IsVisible="{Binding !IsSearching}">
|
||||
<Grid Grid.Row="1" Margin="0,0,0,8" RowDefinitions="Auto,Auto,*" IsVisible="{Binding !IsSearching}">
|
||||
<!-- Page Switcher for Right Panel -->
|
||||
<Border Grid.Row="0" Margin="8,0,4,0" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}" CornerRadius="6">
|
||||
<Border CornerRadius="6" ClipToBounds="True">
|
||||
|
@ -231,396 +231,303 @@
|
|||
</TextBox.InnerRightContent>
|
||||
</TextBox>
|
||||
|
||||
<!-- Local Branches -->
|
||||
<TextBlock Grid.Row="2" Classes="group_header_label" Text="{DynamicResource Text.Repository.LocalBranches}"/>
|
||||
<TreeView Grid.Row="3"
|
||||
x:Name="localBranchTree"
|
||||
MaxHeight="400"
|
||||
Margin="8,0,4,0"
|
||||
SelectionMode="Multiple"
|
||||
ItemsSource="{Binding LocalBranchTrees}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnLocalBranchContextMenuRequested"
|
||||
SelectionChanged="OnLocalBranchTreeSelectionChanged">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
|
||||
<Setter Property="CornerRadius" Value="{Binding CornerRadius}"/>
|
||||
</Style>
|
||||
<Grid Grid.Row="2" x:Name="leftSidebarGroups" Margin="0,4,0,0" RowDefinitions="28,Auto,28,Auto,28,Auto,28,Auto,28,Auto">
|
||||
<!-- Local Branches -->
|
||||
<ToggleButton Grid.Row="0" Classes="group_expander" IsChecked="{Binding IsLocalBranchGroupExpanded, Mode=TwoWay}">
|
||||
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.LocalBranches}"/>
|
||||
</ToggleButton>
|
||||
<v:BranchTree Grid.Row="1"
|
||||
x:Name="localBranchTree"
|
||||
Height="0"
|
||||
Margin="8,0,4,0"
|
||||
Nodes="{Binding LocalBranchTrees}"
|
||||
IsVisible="{Binding IsLocalBranchGroupExpanded}"
|
||||
SelectionChanged="OnLocalBranchTreeSelectionChanged"
|
||||
RowsChanged="OnBranchTreeRowsChanged"/>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel TreeViewItem /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value=".65"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".65"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".8"/>
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
|
||||
<Grid Height="24" ColumnDefinitions="20,*,Auto,Auto" Background="Transparent" DoubleTapped="OnDoubleTappedBranchNode">
|
||||
<Path Grid.Column="0" Classes="folder_icon" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,1,0,0" IsVisible="{Binding IsFolder}"/>
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Check}" IsVisible="{Binding IsCurrent}" VerticalAlignment="Center"/>
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="2,0,0,0" Data="{StaticResource Icons.Branch}" VerticalAlignment="Center">
|
||||
<Path.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="!IsFolder"/>
|
||||
<Binding Path="!IsCurrent"/>
|
||||
</MultiBinding>
|
||||
</Path.IsVisible>
|
||||
</Path>
|
||||
<!-- Remotes -->
|
||||
<ToggleButton Grid.Row="2" Classes="group_expander" IsChecked="{Binding IsRemoteGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Remotes}"/>
|
||||
<Button Grid.Column="1" Classes="icon_button" Width="14" Margin="8,0" Command="{Binding AddRemote}" ToolTip.Tip="{DynamicResource Text.Repository.Remotes.Add}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Remote.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<v:BranchTree Grid.Row="3"
|
||||
x:Name="remoteBranchTree"
|
||||
Height="0"
|
||||
Margin="8,0,4,0"
|
||||
Nodes="{Binding RemoteBranchTrees}"
|
||||
IsVisible="{Binding IsRemoteGroupExpanded}"
|
||||
SelectionChanged="OnRemoteBranchTreeSelectionChanged"
|
||||
RowsChanged="OnBranchTreeRowsChanged"/>
|
||||
|
||||
<TextBlock Grid.Column="1" Text="{Binding Name}" Classes="monospace" FontWeight="{Binding IsCurrent, Converter={x:Static c:BoolConverters.BoldIfTrue}}"/>
|
||||
<!-- Tags -->
|
||||
<ToggleButton Grid.Row="4" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Tags}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Button Grid.Column="2" Classes="icon_button" Width="14" Margin="8,0" Command="{Binding CreateNewTag}" ToolTip.Tip="{DynamicResource Text.Repository.Tags.Add}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Tag.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<DataGrid Grid.Row="5"
|
||||
x:Name="tagsList"
|
||||
Height="0"
|
||||
Margin="8,0,4,0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding VisibleTags}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="24"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
IsVisible="{Binding IsTagGroupExpanded, Mode=OneWay}"
|
||||
SelectionChanged="OnTagDataGridSelectionChanged"
|
||||
ContextRequested="OnTagContextRequested"
|
||||
PropertyChanged="OnLeftSidebarDataGridPropertyChanged">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
|
||||
<Border Grid.Column="2" Margin="8,0" Height="18" CornerRadius="9" VerticalAlignment="Center" Background="{DynamicResource Brush.Badge}" IsVisible="{Binding IsUpstreamTrackStatusVisible}">
|
||||
<TextBlock Classes="monospace" FontSize="10" HorizontalAlignment="Center" Margin="9,0" Text="{Binding UpstreamTrackStatus}" Foreground="{DynamicResource Brush.BadgeFG}"/>
|
||||
</Border>
|
||||
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
|
||||
<ToggleButton Grid.Column="3"
|
||||
Classes="filter"
|
||||
Margin="0,0,8,0"
|
||||
Background="Transparent"
|
||||
IsVisible="{Binding IsBranch}"
|
||||
Checked="OnToggleFilter"
|
||||
Unchecked="OnToggleFilter"
|
||||
IsChecked="{Binding IsFiltered}"/>
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
<Style Selector="Grid.repository_leftpanel DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value=".5"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".65"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".8"/>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<!-- Remotes -->
|
||||
<Grid Grid.Row="4" ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="group_header_label" Text="{DynamicResource Text.Repository.Remotes}"/>
|
||||
<Button Grid.Column="1" Classes="icon_button" Width="14" Margin="8,0" Command="{Binding AddRemote}" ToolTip.Tip="{DynamicResource Text.Repository.Remotes.Add}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Remote.Add}"/>
|
||||
</Button>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type m:Tag}">
|
||||
<Path Width="10" Height="10" Margin="8,0" Data="{StaticResource Icons.Tag}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="NAME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type m:Tag}">
|
||||
<TextBlock Text="{Binding Name}" Classes="monospace" TextTrimming="CharacterEllipsis" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="FILTER">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type m:Tag}">
|
||||
<ToggleButton Classes="filter"
|
||||
Margin="0,0,8,0"
|
||||
Background="Transparent"
|
||||
Checked="OnToggleTagFilter"
|
||||
Unchecked="OnToggleTagFilter"
|
||||
IsChecked="{Binding IsFiltered}"
|
||||
ToolTip.Tip="{DynamicResource Text.Filter}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Submodules -->
|
||||
<ToggleButton Grid.Row="6" Classes="group_expander" IsChecked="{Binding IsSubmoduleGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Submodules}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Submodules, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="8,0"
|
||||
Command="{Binding UpdateSubmodules}"
|
||||
IsVisible="{Binding Submodules, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.Submodules.Update}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Loading}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="3"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="0,0,8,0"
|
||||
Command="{Binding AddSubmodule}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.Submodules.Add}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Submodule.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<DataGrid Grid.Row="7"
|
||||
x:Name="submoduleList"
|
||||
Height="0"
|
||||
Margin="8,0,4,0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Submodules}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnSubmoduleContextRequested"
|
||||
DoubleTapped="OnDoubleTappedSubmodule"
|
||||
PropertyChanged="OnLeftSidebarDataGridPropertyChanged"
|
||||
IsVisible="{Binding IsSubmoduleGroupExpanded, Mode=OneWay}">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Path Width="10" Height="10" Margin="8,0" Data="{StaticResource Icons.Submodule}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="NAME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border Padding="0,0,4,0">
|
||||
<TextBlock Text="{Binding}" ClipToBounds="True" Classes="monospace" TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Worktrees -->
|
||||
<ToggleButton Grid.Row="8" Classes="group_expander" IsChecked="{Binding IsWorktreeGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Worktrees}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Worktrees, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="8,0"
|
||||
Command="{Binding PruneWorktrees}"
|
||||
IsVisible="{Binding Worktrees, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.Worktrees.Prune}">
|
||||
<Path x:Name="icon" Width="12" Height="12" Data="{StaticResource Icons.Loading}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="3"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="0,0,8,0"
|
||||
Command="{Binding AddWorktree}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.Worktrees.Add}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Worktree.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<DataGrid Grid.Row="9"
|
||||
x:Name="worktreeList"
|
||||
Height="0"
|
||||
Margin="8,0,4,0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Worktrees}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnWorktreeContextRequested"
|
||||
DoubleTapped="OnDoubleTappedWorktree"
|
||||
PropertyChanged="OnLeftSidebarDataGridPropertyChanged"
|
||||
IsVisible="{Binding IsWorktreeGroupExpanded, Mode=OneWay}">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Path Width="10" Height="10" Margin="8,0,0,0" Data="{StaticResource Icons.Worktree}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="FullPath">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Classes="monospace" Margin="8,0,0,0" TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{Binding FullPath}"/>
|
||||
<Run Text="{Binding Name}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="FullPath">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Path Width="10" Height="10" Margin="4,0,8,0" Data="{StaticResource Icons.Lock}" Fill="{DynamicResource Brush.FG2}" IsVisible="{Binding IsLocked}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
<TreeView Grid.Row="5"
|
||||
x:Name="remoteBranchTree"
|
||||
Margin="8,0,4,0"
|
||||
SelectionMode="Multiple"
|
||||
ItemsSource="{Binding RemoteBranchTrees}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnRemoteBranchContextMenuRequested"
|
||||
SelectionChanged="OnRemoteBranchTreeSelectionChanged">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
|
||||
<Setter Property="CornerRadius" Value="{Binding CornerRadius}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel TreeViewItem /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value=".65"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot Border#PART_Background">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".65"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within TreeViewItem:selected /template/ Border#PART_LayoutRoot:pointerover Border#PART_Background">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".8"/>
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
|
||||
<Grid Height="24" ColumnDefinitions="20,*,Auto" Background="Transparent" DoubleTapped="OnDoubleTappedBranchNode">
|
||||
<Path Grid.Column="0" Classes="folder_icon" Width="10" Height="10" HorizontalAlignment="Left" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" VerticalAlignment="Center"/>
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Remote}" IsVisible="{Binding IsRemote}" VerticalAlignment="Center"/>
|
||||
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="2,0,0,0" Data="{StaticResource Icons.Branch}" IsVisible="{Binding IsBranch}" VerticalAlignment="Center"/>
|
||||
|
||||
<TextBlock Grid.Column="1" Text="{Binding Name}" Classes="monospace"/>
|
||||
|
||||
<ToggleButton Grid.Column="2"
|
||||
Classes="filter"
|
||||
Margin="0,0,8,0"
|
||||
Background="Transparent"
|
||||
Checked="OnToggleFilter"
|
||||
Unchecked="OnToggleFilter"
|
||||
IsVisible="{Binding IsBranch}"
|
||||
IsChecked="{Binding IsFiltered}"/>
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<!-- Tags -->
|
||||
<ToggleButton Grid.Row="6" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Tags}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Button Grid.Column="2" Classes="icon_button" Width="14" Margin="8,0" Command="{Binding CreateNewTag}" ToolTip.Tip="{DynamicResource Text.Repository.Tags.Add}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Tag.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<DataGrid Grid.Row="7"
|
||||
x:Name="tagsList"
|
||||
Margin="8,0,4,0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding VisibleTags}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="24"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
IsVisible="{Binding IsTagGroupExpanded, Mode=OneWay}"
|
||||
SelectionChanged="OnTagDataGridSelectionChanged"
|
||||
ContextRequested="OnTagContextRequested"
|
||||
PropertyChanged="OnTagPropertyChanged">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.repository_leftpanel DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value=".5"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".65"/>
|
||||
</Style>
|
||||
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
<Setter Property="Opacity" Value=".8"/>
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type m:Tag}">
|
||||
<Path Width="10" Height="10" Margin="8,0" Data="{StaticResource Icons.Tag}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="NAME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type m:Tag}">
|
||||
<TextBlock Text="{Binding Name}" Classes="monospace" TextTrimming="CharacterEllipsis" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="FILTER">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type m:Tag}">
|
||||
<ToggleButton Classes="filter"
|
||||
Margin="0,0,8,0"
|
||||
Background="Transparent"
|
||||
Checked="OnToggleFilter"
|
||||
Unchecked="OnToggleFilter"
|
||||
IsChecked="{Binding IsFiltered}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Submodules -->
|
||||
<ToggleButton Grid.Row="8" Classes="group_expander" IsChecked="{Binding IsSubmoduleGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Submodules}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Submodules, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="8,0"
|
||||
Command="{Binding UpdateSubmodules}"
|
||||
IsVisible="{Binding Submodules, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.Submodules.Update}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Loading}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="3"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="0,0,8,0"
|
||||
Command="{Binding AddSubmodule}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.Submodules.Add}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Submodule.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<DataGrid Grid.Row="9"
|
||||
MaxHeight="200"
|
||||
Margin="8,0,4,0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Submodules}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnSubmoduleContextRequested"
|
||||
DoubleTapped="OnDoubleTappedSubmodule"
|
||||
IsVisible="{Binding IsSubmoduleGroupExpanded, Mode=OneWay}">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Path Width="10" Height="10" Margin="8,0" Data="{StaticResource Icons.Submodule}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="NAME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border Padding="0,0,4,0">
|
||||
<TextBlock Text="{Binding}" ClipToBounds="True" Classes="monospace" TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Worktrees -->
|
||||
<ToggleButton Grid.Row="10" Classes="group_expander" IsChecked="{Binding IsWorktreeGroupExpanded, Mode=TwoWay}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Worktrees}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Worktrees, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Button Grid.Column="2"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="8,0"
|
||||
Command="{Binding PruneWorktrees}"
|
||||
IsVisible="{Binding Worktrees, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.Worktrees.Prune}">
|
||||
<Path x:Name="icon" Width="12" Height="12" Data="{StaticResource Icons.Loading}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="3"
|
||||
Classes="icon_button"
|
||||
Width="14"
|
||||
Margin="0,0,8,0"
|
||||
Command="{Binding AddWorktree}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.Worktrees.Add}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Worktree.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<DataGrid Grid.Row="11"
|
||||
MaxHeight="200"
|
||||
Margin="8,0,4,0"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Worktrees}"
|
||||
SelectionMode="Single"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="26"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ContextRequested="OnWorktreeContextRequested"
|
||||
DoubleTapped="OnDoubleTappedWorktree"
|
||||
IsVisible="{Binding IsWorktreeGroupExpanded, Mode=OneWay}">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow /template/ Border#RowBorder">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="ICON">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Path Width="10" Height="10" Margin="8,0,0,0" Data="{StaticResource Icons.Worktree}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="FullPath">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Classes="monospace" Margin="8,0,0,0" TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{Binding FullPath}"/>
|
||||
<Run Text="{Binding Name}" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="FullPath">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Path Width="10" Height="10" Margin="4,0,8,0" Data="{StaticResource Icons.Lock}" Fill="{DynamicResource Brush.FG2}" IsVisible="{Binding IsLocked}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
|
||||
<!-- Commit Search Panel -->
|
||||
|
@ -794,14 +701,14 @@
|
|||
<Border.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="SelectedViewIndex" Converter="{x:Static c:IntConverters.IsZero}"/>
|
||||
<Binding Path="Filters.Count" Converter="{x:Static c:IntConverters.IsGreaterThanZero}"/>
|
||||
<Binding Path="Settings.Filters.Count" Converter="{x:Static c:IntConverters.IsGreaterThanZero}"/>
|
||||
</MultiBinding>
|
||||
</Border.IsVisible>
|
||||
|
||||
<Grid Height="28" ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Margin="8,0,0,0" Classes="info_label" Text="{DynamicResource Text.Repository.FilterCommitPrefix}"/>
|
||||
|
||||
<ItemsControl Grid.Column="1" Margin="8,0,0,0" ItemsSource="{Binding Filters}">
|
||||
<ItemsControl Grid.Column="1" Margin="8,0,0,0" ItemsSource="{Binding Settings.Filters}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
|
@ -17,6 +15,12 @@ namespace SourceGit.Views
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
UpdateLeftSidebarLayout();
|
||||
}
|
||||
|
||||
private void OpenWithExternalTools(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button && DataContext is ViewModels.Repository repo)
|
||||
|
@ -49,24 +53,23 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private async void OpenStatistics(object sender, RoutedEventArgs e)
|
||||
private async void OpenStatistics(object _, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Repository repo)
|
||||
if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner)
|
||||
{
|
||||
var dialog = new Statistics() { DataContext = new ViewModels.Statistics(repo.FullPath) };
|
||||
await dialog.ShowDialog(TopLevel.GetTopLevel(this) as Window);
|
||||
await dialog.ShowDialog(owner);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
var grid = sender as Grid;
|
||||
if (e.Property == IsVisibleProperty && grid.IsVisible)
|
||||
if (e.Property == IsVisibleProperty && sender is Grid { IsVisible: true})
|
||||
txtSearchCommitsBox.Focus();
|
||||
}
|
||||
|
||||
private void OnSearchKeyDown(object sender, KeyEventArgs e)
|
||||
private void OnSearchKeyDown(object _, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
|
@ -79,190 +82,39 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnSearchResultDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
|
||||
if (sender is DataGrid { SelectedItem: Models.Commit commit } && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
if (DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var commit = datagrid.SelectedItem as Models.Commit;
|
||||
repo.NavigateToCommit(commit.SHA);
|
||||
}
|
||||
repo.NavigateToCommit(commit.SHA);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnLocalBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
private void OnBranchTreeRowsChanged(object _, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
remoteBranchTree.UnselectAll();
|
||||
tagsList.SelectedItem = null;
|
||||
|
||||
ViewModels.BranchTreeNode prev = null;
|
||||
foreach (var node in repo.LocalBranchTrees)
|
||||
node.UpdateCornerRadius(ref prev);
|
||||
|
||||
if (tree.SelectedItems.Count == 1)
|
||||
{
|
||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
||||
if (node.IsBranch)
|
||||
repo.NavigateToCommit((node.Backend as Models.Branch).Head);
|
||||
}
|
||||
}
|
||||
UpdateLeftSidebarLayout();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRemoteBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
localBranchTree.UnselectAll();
|
||||
tagsList.SelectedItem = null;
|
||||
|
||||
ViewModels.BranchTreeNode prev = null;
|
||||
foreach (var node in repo.RemoteBranchTrees)
|
||||
node.UpdateCornerRadius(ref prev);
|
||||
|
||||
if (tree.SelectedItems.Count == 1)
|
||||
{
|
||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
||||
if (node.IsBranch)
|
||||
repo.NavigateToCommit((node.Backend as Models.Branch).Head);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLocalBranchContextMenuRequested(object sender, ContextRequestedEventArgs e)
|
||||
private void OnLocalBranchTreeSelectionChanged(object _1, RoutedEventArgs _2)
|
||||
{
|
||||
remoteBranchTree.UnselectAll();
|
||||
tagsList.SelectedItem = null;
|
||||
|
||||
var repo = DataContext as ViewModels.Repository;
|
||||
var tree = sender as TreeView;
|
||||
if (tree.SelectedItems.Count == 0)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
foreach (var item in tree.SelectedItems)
|
||||
CollectBranchesFromNode(branches, item as ViewModels.BranchTreeNode);
|
||||
|
||||
if (branches.Count == 1)
|
||||
{
|
||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
||||
if (item != null)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForLocalBranch(branches[0]);
|
||||
item.OpenContextMenu(menu);
|
||||
}
|
||||
}
|
||||
else if (branches.Count > 1 && branches.Find(x => x.IsCurrent) == null)
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
var deleteMulti = new MenuItem();
|
||||
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
|
||||
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
|
||||
deleteMulti.Click += (_, ev) =>
|
||||
{
|
||||
repo.DeleteMultipleBranches(branches, true);
|
||||
ev.Handled = true;
|
||||
};
|
||||
menu.Items.Add(deleteMulti);
|
||||
tree.OpenContextMenu(menu);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRemoteBranchContextMenuRequested(object sender, ContextRequestedEventArgs e)
|
||||
private void OnRemoteBranchTreeSelectionChanged(object _1, RoutedEventArgs _2)
|
||||
{
|
||||
localBranchTree.UnselectAll();
|
||||
tagsList.SelectedItem = null;
|
||||
|
||||
var repo = DataContext as ViewModels.Repository;
|
||||
var tree = sender as TreeView;
|
||||
if (tree.SelectedItems.Count == 0)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (tree.SelectedItems.Count == 1)
|
||||
{
|
||||
var node = tree.SelectedItem as ViewModels.BranchTreeNode;
|
||||
if (node != null && node.IsRemote)
|
||||
{
|
||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
||||
if (item != null && item.DataContext == node)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForRemote(node.Backend as Models.Remote);
|
||||
item.OpenContextMenu(menu);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
foreach (var item in tree.SelectedItems)
|
||||
CollectBranchesFromNode(branches, item as ViewModels.BranchTreeNode);
|
||||
|
||||
if (branches.Count == 1)
|
||||
{
|
||||
var item = (e.Source as Control)?.FindAncestorOfType<TreeViewItem>(true);
|
||||
if (item != null)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForRemoteBranch(branches[0]);
|
||||
item.OpenContextMenu(menu);
|
||||
}
|
||||
}
|
||||
else if (branches.Count > 1)
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
var deleteMulti = new MenuItem();
|
||||
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
|
||||
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
|
||||
deleteMulti.Click += (_, ev) =>
|
||||
{
|
||||
repo.DeleteMultipleBranches(branches, false);
|
||||
ev.Handled = true;
|
||||
};
|
||||
menu.Items.Add(deleteMulti);
|
||||
tree.OpenContextMenu(menu);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDoubleTappedBranchNode(object sender, TappedEventArgs e)
|
||||
private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs _)
|
||||
{
|
||||
if (!ViewModels.PopupHost.CanCreatePopup())
|
||||
return;
|
||||
|
||||
if (sender is Grid grid && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var node = grid.DataContext as ViewModels.BranchTreeNode;
|
||||
if (node != null && node.IsBranch)
|
||||
{
|
||||
var branch = node.Backend as Models.Branch;
|
||||
if (branch.IsCurrent)
|
||||
return;
|
||||
|
||||
repo.CheckoutBranch(branch);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null)
|
||||
if (sender is DataGrid { SelectedItem: Models.Tag tag })
|
||||
{
|
||||
localBranchTree.UnselectAll();
|
||||
remoteBranchTree.UnselectAll();
|
||||
|
||||
var tag = datagrid.SelectedItem as Models.Tag;
|
||||
if (DataContext is ViewModels.Repository repo)
|
||||
repo.NavigateToCommit(tag.SHA);
|
||||
}
|
||||
|
@ -280,37 +132,11 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnTagPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
||||
private void OnToggleTagFilter(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.Property == DataGrid.ItemsSourceProperty && DataContext is ViewModels.Repository vm)
|
||||
if (sender is ToggleButton { DataContext: Models.Tag tag } toggle && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
if (vm.VisibleTags == null)
|
||||
return;
|
||||
|
||||
var desiredHeight = tagsList.RowHeight * vm.VisibleTags.Count;
|
||||
tagsList.Height = Math.Min(200, desiredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnToggleFilter(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleButton toggle)
|
||||
{
|
||||
var filter = string.Empty;
|
||||
if (toggle.DataContext is ViewModels.BranchTreeNode node)
|
||||
{
|
||||
if (node.IsBranch)
|
||||
filter = (node.Backend as Models.Branch).FullName;
|
||||
}
|
||||
else if (toggle.DataContext is Models.Tag tag)
|
||||
{
|
||||
filter = tag.Name;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(filter) && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
repo.UpdateFilter(filter, toggle.IsChecked == true);
|
||||
}
|
||||
repo.UpdateFilter(tag.Name, toggle.IsChecked == true);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
|
@ -330,10 +156,10 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
||||
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var submodule = datagrid.SelectedItem as string;
|
||||
(DataContext as ViewModels.Repository).OpenSubmodule(submodule);
|
||||
var submodule = grid.SelectedItem as string;
|
||||
repo.OpenSubmodule(submodule);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
|
@ -341,11 +167,11 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
||||
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var worktree = datagrid.SelectedItem as Models.Worktree;
|
||||
var worktree = grid.SelectedItem as Models.Worktree;
|
||||
var menu = repo.CreateContextMenuForWorktree(worktree);
|
||||
datagrid.OpenContextMenu(menu);
|
||||
grid.OpenContextMenu(menu);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
|
@ -353,31 +179,143 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnDoubleTappedWorktree(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo)
|
||||
if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var worktree = datagrid.SelectedItem as Models.Worktree;
|
||||
(DataContext as ViewModels.Repository).OpenWorktree(worktree);
|
||||
var worktree = grid.SelectedItem as Models.Worktree;
|
||||
repo.OpenWorktree(worktree);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void CollectBranchesFromNode(List<Models.Branch> outs, ViewModels.BranchTreeNode node)
|
||||
private void OnLeftSidebarDataGridPropertyChanged(object _, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (node == null || node.IsRemote)
|
||||
if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty)
|
||||
{
|
||||
UpdateLeftSidebarLayout();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLeftSidebarLayout()
|
||||
{
|
||||
var vm = DataContext as ViewModels.Repository;
|
||||
if (vm == null || vm.Settings == null)
|
||||
return;
|
||||
|
||||
if (node.IsFolder)
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
var leftHeight = leftSidebarGroups.Bounds.Height - 28.0 * 5;
|
||||
var localBranchRows = vm.IsLocalBranchGroupExpanded ? localBranchTree.Rows.Count : 0;
|
||||
var remoteBranchRows = vm.IsRemoteGroupExpanded ? remoteBranchTree.Rows.Count : 0;
|
||||
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;
|
||||
var desiredTag = vm.IsTagGroupExpanded ? tagsList.RowHeight * vm.VisibleTags.Count : 0;
|
||||
var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? submoduleList.RowHeight * vm.Submodules.Count : 0;
|
||||
var desiredWorktree = vm.IsWorktreeGroupExpanded ? worktreeList.RowHeight * vm.Worktrees.Count : 0;
|
||||
var desiredOthers = desiredTag + desiredSubmodule + desiredWorktree;
|
||||
var hasOverflow = (desiredBranches + desiredOthers > leftHeight);
|
||||
|
||||
if (vm.IsTagGroupExpanded)
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
CollectBranchesFromNode(outs, child);
|
||||
var height = desiredTag;
|
||||
if (hasOverflow)
|
||||
{
|
||||
var test = leftHeight - desiredBranches - desiredSubmodule - desiredWorktree;
|
||||
if (test < 0)
|
||||
height = Math.Min(200, height);
|
||||
else
|
||||
height = Math.Max(200, test);
|
||||
}
|
||||
|
||||
leftHeight -= height;
|
||||
tagsList.Height = height;
|
||||
hasOverflow = (desiredBranches + desiredSubmodule + desiredWorktree) > leftHeight;
|
||||
}
|
||||
|
||||
if (vm.IsSubmoduleGroupExpanded)
|
||||
{
|
||||
var height = desiredSubmodule;
|
||||
if (hasOverflow)
|
||||
{
|
||||
var test = leftHeight - desiredBranches - desiredWorktree;
|
||||
if (test < 0)
|
||||
height = Math.Min(200, height);
|
||||
else
|
||||
height = Math.Max(200, test);
|
||||
}
|
||||
|
||||
leftHeight -= height;
|
||||
submoduleList.Height = height;
|
||||
hasOverflow = (desiredBranches + desiredWorktree) > leftHeight;
|
||||
}
|
||||
|
||||
if (vm.IsWorktreeGroupExpanded)
|
||||
{
|
||||
var height = desiredWorktree;
|
||||
if (hasOverflow)
|
||||
{
|
||||
var test = leftHeight - desiredBranches;
|
||||
if (test < 0)
|
||||
height = Math.Min(200, height);
|
||||
else
|
||||
height = Math.Max(200, test);
|
||||
}
|
||||
|
||||
leftHeight -= height;
|
||||
worktreeList.Height = height;
|
||||
}
|
||||
|
||||
if (desiredBranches > leftHeight)
|
||||
{
|
||||
var local = localBranchRows * 24.0;
|
||||
var remote = remoteBranchRows * 24.0;
|
||||
var half = leftHeight / 2;
|
||||
if (vm.IsLocalBranchGroupExpanded)
|
||||
{
|
||||
if (vm.IsRemoteGroupExpanded)
|
||||
{
|
||||
if (local < half)
|
||||
{
|
||||
localBranchTree.Height = local;
|
||||
remoteBranchTree.Height = leftHeight - local;
|
||||
}
|
||||
else if (remote < half)
|
||||
{
|
||||
remoteBranchTree.Height = remote;
|
||||
localBranchTree.Height = leftHeight - remote;
|
||||
}
|
||||
else
|
||||
{
|
||||
localBranchTree.Height = half;
|
||||
remoteBranchTree.Height = half;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
localBranchTree.Height = leftHeight;
|
||||
}
|
||||
}
|
||||
else if (vm.IsRemoteGroupExpanded)
|
||||
{
|
||||
remoteBranchTree.Height = leftHeight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var b = node.Backend as Models.Branch;
|
||||
if (b != null && !outs.Contains(b))
|
||||
outs.Add(b);
|
||||
if (vm.IsLocalBranchGroupExpanded)
|
||||
{
|
||||
var height = localBranchRows * 24;
|
||||
localBranchTree.Height = height;
|
||||
}
|
||||
|
||||
if (vm.IsRemoteGroupExpanded)
|
||||
{
|
||||
var height = remoteBranchRows * 24;
|
||||
remoteBranchTree.Height = height;
|
||||
}
|
||||
}
|
||||
|
||||
leftSidebarGroups.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ using Avalonia.Controls.Primitives;
|
|||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Styling;
|
||||
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Document;
|
||||
|
|
|
@ -429,7 +429,18 @@ namespace SourceGit.Views
|
|||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var line in textDiff.Lines)
|
||||
builder.AppendLine(line.Content);
|
||||
{
|
||||
if (line.Content.Length > 10000)
|
||||
{
|
||||
builder.Append(line.Content.Substring(0, 1000));
|
||||
builder.Append($"...({line.Content.Length - 1000} character trimmed)");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine(line.Content);
|
||||
}
|
||||
}
|
||||
|
||||
Text = builder.ToString();
|
||||
}
|
||||
|
@ -718,7 +729,18 @@ namespace SourceGit.Views
|
|||
var builder = new StringBuilder();
|
||||
var lines = IsOld ? diff.Old : diff.New;
|
||||
foreach (var line in lines)
|
||||
builder.AppendLine(line.Content);
|
||||
{
|
||||
if (line.Content.Length > 10000)
|
||||
{
|
||||
builder.Append(line.Content.Substring(0, 1000));
|
||||
builder.Append($"...({line.Content.Length - 1000} characters trimmed)");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine(line.Content);
|
||||
}
|
||||
}
|
||||
|
||||
Text = builder.ToString();
|
||||
}
|
||||
|
|
|
@ -253,11 +253,10 @@ namespace SourceGit.Views
|
|||
return;
|
||||
}
|
||||
|
||||
var gitDir = new Commands.QueryGitDir(root).Result();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
var repo = ViewModels.Preference.AddRepository(root, gitDir);
|
||||
var node = ViewModels.Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, parent, true);
|
||||
var normalizedPath = root.Replace("\\", "/");
|
||||
var node = ViewModels.Preference.FindOrAddNodeByRepositoryPath(normalizedPath, parent, true);
|
||||
launcher.OpenRepositoryInTab(node, page);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -174,7 +174,7 @@
|
|||
<v:CommitMessageTextBox Grid.Row="2" Text="{Binding CommitMessage, Mode=TwoWay}"/>
|
||||
|
||||
<!-- Commit Options -->
|
||||
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
|
||||
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto">
|
||||
<Button Grid.Column="0"
|
||||
Classes="icon_button"
|
||||
Width="14" Height="14"
|
||||
|
@ -184,15 +184,23 @@
|
|||
</Button>
|
||||
|
||||
<CheckBox Grid.Column="1"
|
||||
Height="24"
|
||||
Margin="12,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
IsChecked="{Binding AutoStageBeforeCommit, Mode=TwoWay}"
|
||||
Content="{DynamicResource Text.WorkingCopy.AutoStage}"
|
||||
ToolTip.Tip="{DynamicResource Text.WorkingCopy.AutoStage.Tip}"/>
|
||||
|
||||
<CheckBox Grid.Column="2"
|
||||
Height="24"
|
||||
Margin="12,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
IsChecked="{Binding UseAmend, Mode=TwoWay}"
|
||||
Content="{DynamicResource Text.WorkingCopy.Amend}"/>
|
||||
|
||||
<v:LoadingIcon Grid.Column="3" Width="18" Height="18" IsVisible="{Binding IsCommitting}"/>
|
||||
<v:LoadingIcon Grid.Column="4" Width="18" Height="18" IsVisible="{Binding IsCommitting}"/>
|
||||
|
||||
<Button Grid.Column="4"
|
||||
<Button Grid.Column="5"
|
||||
Classes="flat primary"
|
||||
Content="{DynamicResource Text.WorkingCopy.Commit}"
|
||||
Height="28"
|
||||
|
@ -202,7 +210,7 @@
|
|||
HotKey="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"
|
||||
ToolTip.Tip="{OnPlatform Ctrl+Enter, macOS=⌘+Enter}"/>
|
||||
|
||||
<Button Grid.Column="5"
|
||||
<Button Grid.Column="6"
|
||||
Classes="flat"
|
||||
Content="{DynamicResource Text.WorkingCopy.CommitAndPush}"
|
||||
Height="28"
|
||||
|
|
Loading…
Reference in a new issue