diff --git a/.gitattributes b/.gitattributes
index 69139978..7410eb08 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,78 +1,12 @@
-# Auto detect text files and perform LF normalization
-# https://www.davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
* text=auto
-
-#
-# The above will handle all files NOT found below
-#
-
-# Documents
-*.bibtex text diff=bibtex
-*.doc diff=astextplain
-*.DOC diff=astextplain
-*.docx diff=astextplain
-*.DOCX diff=astextplain
-*.dot diff=astextplain
-*.DOT diff=astextplain
-*.pdf diff=astextplain
-*.PDF diff=astextplain
-*.rtf diff=astextplain
-*.RTF diff=astextplain
*.md text
-*.tex text diff=tex
-*.adoc text
-*.textile text
-*.mustache text
-*.csv text
-*.tab text
-*.tsv text
-*.txt text
-*.sql text
-
-# Graphics
*.png binary
-*.jpg binary
-*.jpeg binary
-*.gif binary
-*.tif binary
-*.tiff binary
*.ico binary
-# SVG treated as an asset (binary) by default.
-*.svg text
-# If you want to treat it as binary,
-# use the following line instead.
-# *.svg binary
-*.eps binary
-
-# Scripts
-*.bash text eol=lf
-*.fish text eol=lf
*.sh text eol=lf
-# These are explicitly windows files and should use crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
-
-# Serialisation
*.json text
-*.toml text
-*.xml text
-*.yaml text
-*.yml text
-
-# Archives
-*.7z binary
-*.gz binary
-*.tar binary
-*.tgz binary
-*.zip binary
-
-# Text files where line endings should be preserved
-*.patch -text
-
-#
-# Exclude files from exporting
-#
.gitattributes export-ignore
.gitignore export-ignore
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0b5677c5..95bde92d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,425 +1,13 @@
-# User-specific files
-*.rsuser
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Mono auto generated files
-mono_crash.*
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-[Ww][Ii][Nn]32/
-[Aa][Rr][Mm]/
-[Aa][Rr][Mm]64/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
-[Ll]ogs/
-
-# Visual Studio 2015/2017 cache/options directory
.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
+.vscode/
+.idea/
-# Visual Studio 2017 auto generated files
-Generated\ Files/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUnit
-*.VisualState.xml
-TestResult.xml
-nunit-*.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
-
-# .NET Core
-project.lock.json
-project.fragment.lock.json
-artifacts/
-
-# ASP.NET Scaffolding
-ScaffoldingReadMe.txt
-
-# StyleCop
-StyleCopReport.xml
-
-# Files built by Visual Studio
-*_i.c
-*_p.c
-*_h.h
-*.ilk
-*.meta
-*.obj
-*.iobj
-*.pch
-*.pdb
-*.ipdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*_wpftmp.csproj
-*.log
-*.tlog
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# Visual Studio Trace Files
-*.e2e
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Coverlet is a free, cross platform Code Coverage Tool
-coverage*.json
-coverage*.xml
-coverage*.info
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# NuGet Symbol Packages
-*.snupkg
-# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-*.appx
-*.appxbundle
-*.appxupload
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!?*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
-
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-ServiceFabricBackup/
-*.rptproj.bak
-
-# SQL Server files
-*.mdf
-*.ldf
-*.ndf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-*.rptproj.rsuser
-*- [Bb]ackup.rdl
-*- [Bb]ackup ([0-9]).rdl
-*- [Bb]ackup ([0-9][0-9]).rdl
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio 6 auto-generated project file (contains which files were open etc.)
-*.vbp
-
-# Visual Studio 6 workspace and project file (working project files containing files to include in project)
-*.dsw
-*.dsp
-
-# Visual Studio 6 technical files
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# CodeRush personal settings
-.cr/personal
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Nuke Build - Uncomment if you are using it
-.nuke/temp
-
-# Tabs Studio
-*.tss
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-# NVidia Nsight GPU debugger configuration file
-*.nvuser
-
-# MFractors (Xamarin productivity tool) working folder
-.mfractor/
-
-# Local History for Visual Studio
-.localhistory/
-
-# Visual Studio History (VSHistory) files
-.vshistory/
-
-# BeatPulse healthcheck temp database
-healthchecksdb
-
-# Backup folder for Package Reference Convert tool in Visual Studio 2017
-MigrationBackup/
-
-# Ionide (cross platform F# VS Code tools) working folder
-.ionide/
-
-# Fody - auto-generated XML schema
-FodyWeavers.xsd
-
-# VS Code files for those working on multiple tools
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
+*.sln.docstates
+*.user
+*.suo
*.code-workspace
-# Local History for Visual Studio Code
-.history/
-
-# Windows Installer files from build outputs
-*.cab
-*.msi
-*.msix
-*.msm
-*.msp
-
-# JetBrains Rider
-*.sln.iml
-
-### Linux ###
-
-# temporary files which can be created if a process still has a handle open of a deleted file
-.fuse_hidden*
-
-# KDE directory preferences
-.directory
-
-# Linux trash folder which might appear on any partition or disk
-.Trash-*
-
-# .nfs files are created when an open file is removed but is still being accessed
-.nfs*
-
-### macOS ###
-# General
.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
@@ -428,175 +16,14 @@ Icon
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-### macOS Patch ###
-# iCloud generated files
-*.icloud
-
-### Rider ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-.idea/
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# SonarLint plugin
-.idea/sonarlint/
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-### VisualStudioCode ###
-!.vscode/*.code-snippets
-
-# Local History for Visual Studio Code
-
-# Built Visual Studio Code Extensions
-*.vsix
-
-### VisualStudioCode Patch ###
-# Ignore all local history of files
-.history
-.ionide
-
-### Windows ###
-# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
-# Dump file
-*.stackdump
+bin/
+obj/
-# Folder config file
-[Dd]esktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-
-# Windows shortcuts
-*.lnk
-
-### Specifics ###
-
-# Specials
-*.zip
-archives
-package-lock.json
-*.private.env.json
-**/**/[Dd]ata/*.json
-**/**/[Dd]ata/*.csv
-
-# SpecFlow
-*.feature.cs
-
-# Azurite
-*azurite*.json
-
-# Build Folders
-[Pp]ublish
-[Oo]utput
-[Ss]cripts
-[Tt]ests/[Rr]esults
-
-# LibraryManager
-**/lib
-
-# BuildBundlerMinifier
-*.min.*
-*.map
-
-# Sass Output
-**/css
-
-# SQLite files
-*.db
-*.sqlite3
-*.sqlite
-*.db-journal
-*.sqlite3-journal
-*.sqlite-journal
-*.db-shm
-*.db-wal
-
-# SourceGit output files
build/resources/
build/SourceGit/
build/SourceGit.app/
@@ -605,3 +32,4 @@ build/*.tar.gz
build/*.deb
build/*.rpm
build/*.AppImage
+SourceGit.app/
\ No newline at end of file
diff --git a/VERSION b/VERSION
index a05d8762..eed4bd77 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.31
\ No newline at end of file
+8.32
\ No newline at end of file
diff --git a/build/README.md b/build/README.md
new file mode 100644
index 00000000..a75f4d73
--- /dev/null
+++ b/build/README.md
@@ -0,0 +1,15 @@
+# build
+
+> [!WARNING]
+> The files under the `build` folder is used for `Github Action` only, **NOT** for end users.
+
+## How to build this project manually
+
+1. Make sure [.NET SDK 8](https://dotnet.microsoft.com/en-us/download) is installed on your machine.
+2. Clone this project
+3. Run the follow command under the project root dir
+```sh
+dotnet publish -c Release -r $RUNTIME_IDENTIFIER -o $DESTINATION_FOLDER src/SourceGit.csproj
+```
+> [!NOTE]
+> Please replace the `$RUNTIME_IDENTIFIER` with one of `win-x64`,`win-arm64`,`linux-x64`,`linux-arm64`,`osx-x64`,`osx-arm64`, and replece the `$DESTINATION_FOLDER` with the real path that will store the output executable files.
diff --git a/build/resources/app/App.plist b/build/resources/app/App.plist
index a9852019..ba6f40a2 100644
--- a/build/resources/app/App.plist
+++ b/build/resources/app/App.plist
@@ -12,11 +12,6 @@
SOURCE_GIT_VERSION.0
LSMinimumSystemVersion
11.0
- LSEnvironment
-
- PATH
- /opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
-
CFBundleExecutable
SourceGit
CFBundleInfoDictionaryVersion
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index 090f060e..682ec5fc 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -516,27 +516,24 @@ namespace SourceGit
private bool TryLaunchedAsAskpass(IClassicDesktopStyleApplicationLifetime desktop)
{
+ var launchAsAskpass = Environment.GetEnvironmentVariable("SOURCEGIT_LAUNCH_AS_ASKPASS");
+ if (launchAsAskpass is not "TRUE")
+ return false;
+
var args = desktop.Args;
- if (args == null || args.Length != 1)
- return false;
+ if (args?.Length > 0)
+ {
+ desktop.MainWindow = new Views.Askpass(args[0]);
+ return true;
+ }
- var param = args[0];
- if (Directory.Exists(param))
- return false;
-
- if (!param.StartsWith("enter passphrase", StringComparison.OrdinalIgnoreCase) &&
- !param.Contains(" password", StringComparison.OrdinalIgnoreCase))
- return false;
-
- desktop.MainWindow = new Views.Askpass(param);
- return true;
+ return false;
}
private void TryLaunchedAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
{
Native.OS.SetupEnternalTools();
Models.AvatarManager.Instance.Start();
- Models.AutoFetchManager.Instance.Start();
string startupRepo = null;
if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0]))
diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs
index c30ba59b..55fc6d17 100644
--- a/src/Commands/Command.cs
+++ b/src/Commands/Command.cs
@@ -40,59 +40,7 @@ namespace SourceGit.Commands
public bool Exec()
{
- var start = new ProcessStartInfo();
- start.FileName = Native.OS.GitExecutable;
- start.Arguments = "--no-pager -c core.quotepath=off ";
- start.UseShellExecute = false;
- start.CreateNoWindow = true;
- start.RedirectStandardOutput = true;
- start.RedirectStandardError = true;
- start.StandardOutputEncoding = Encoding.UTF8;
- start.StandardErrorEncoding = Encoding.UTF8;
-
- // Force using this app as SSH askpass program
- var selfExecFile = Process.GetCurrentProcess().MainModule!.FileName;
- if (!OperatingSystem.IsLinux())
- start.Environment.Add("DISPLAY", "required");
- start.Environment.Add("SSH_ASKPASS", selfExecFile); // Can not use parameter here, because it invoked by SSH with `exec`
- start.Environment.Add("SSH_ASKPASS_REQUIRE", "prefer");
-
- // If an SSH private key was provided, sets the environment.
- if (!string.IsNullOrEmpty(SSHKey))
- {
- start.Environment.Add("GIT_SSH_COMMAND", $"ssh -o StrictHostKeyChecking=accept-new -i '{SSHKey}'");
- }
- else
- {
- start.Environment.Add("GIT_SSH_COMMAND", $"ssh -o StrictHostKeyChecking=accept-new");
- start.Arguments += "-c credential.helper=manager ";
- }
-
- // Force using en_US.UTF-8 locale to avoid GCM crash
- if (OperatingSystem.IsLinux())
- start.Environment.Add("LANG", "en_US.UTF-8");
-
- // Force using this app as git editor.
- switch (Editor)
- {
- case EditorType.CoreEditor:
- start.Arguments += $"-c core.editor=\"\\\"{selfExecFile}\\\" --core-editor\" ";
- break;
- case EditorType.RebaseEditor:
- start.Arguments += $"-c core.editor=\"\\\"{selfExecFile}\\\" --rebase-message-editor\" -c sequence.editor=\"\\\"{selfExecFile}\\\" --rebase-todo-editor\" -c rebase.abbreviateCommands=true ";
- break;
- default:
- start.Arguments += "-c core.editor=true ";
- break;
- }
-
- // Append command args
- start.Arguments += Args;
-
- // Working directory
- if (!string.IsNullOrEmpty(WorkingDirectory))
- start.WorkingDirectory = WorkingDirectory;
-
+ var start = CreateGitStartInfo();
var errs = new List();
var proc = new Process() { StartInfo = start };
var isCancelled = false;
@@ -178,28 +126,15 @@ namespace SourceGit.Commands
}
return false;
}
- else
- {
- return true;
- }
+
+ return true;
}
public ReadToEndResult ReadToEnd()
{
- var start = new ProcessStartInfo();
- start.FileName = Native.OS.GitExecutable;
- start.Arguments = "--no-pager -c core.quotepath=off " + Args;
- start.UseShellExecute = false;
- start.CreateNoWindow = true;
- start.RedirectStandardOutput = true;
- start.RedirectStandardError = true;
- start.StandardOutputEncoding = Encoding.UTF8;
- start.StandardErrorEncoding = Encoding.UTF8;
-
- if (!string.IsNullOrEmpty(WorkingDirectory))
- start.WorkingDirectory = WorkingDirectory;
-
+ var start = CreateGitStartInfo();
var proc = new Process() { StartInfo = start };
+
try
{
proc.Start();
@@ -227,7 +162,68 @@ namespace SourceGit.Commands
return rs;
}
- protected virtual void OnReadline(string line) { }
+ protected virtual void OnReadline(string line)
+ {
+ // Implemented by derived class
+ }
+
+ private ProcessStartInfo CreateGitStartInfo()
+ {
+ var start = new ProcessStartInfo();
+ start.FileName = Native.OS.GitExecutable;
+ start.Arguments = "--no-pager -c core.quotepath=off -c credential.helper=manager ";
+ start.UseShellExecute = false;
+ start.CreateNoWindow = true;
+ start.RedirectStandardOutput = true;
+ start.RedirectStandardError = true;
+ start.StandardOutputEncoding = Encoding.UTF8;
+ start.StandardErrorEncoding = Encoding.UTF8;
+
+ // Force using this app as SSH askpass program
+ var selfExecFile = Process.GetCurrentProcess().MainModule!.FileName;
+ if (!OperatingSystem.IsLinux())
+ start.Environment.Add("DISPLAY", "required");
+ start.Environment.Add("SSH_ASKPASS", selfExecFile); // Can not use parameter here, because it invoked by SSH with `exec`
+ start.Environment.Add("SSH_ASKPASS_REQUIRE", "prefer");
+ start.Environment.Add("SOURCEGIT_LAUNCH_AS_ASKPASS", "TRUE");
+
+ // If an SSH private key was provided, sets the environment.
+ if (!string.IsNullOrEmpty(SSHKey))
+ start.Environment.Add("GIT_SSH_COMMAND", $"ssh -o StrictHostKeyChecking=accept-new -i '{SSHKey}'");
+ else
+ start.Environment.Add("GIT_SSH_COMMAND", $"ssh -o StrictHostKeyChecking=accept-new");
+
+ // Force using en_US.UTF-8 locale to avoid GCM crash
+ if (OperatingSystem.IsLinux())
+ start.Environment.Add("LANG", "en_US.UTF-8");
+
+ // Fix sometimes `LSEnvironment` not working on macOS
+ if (OperatingSystem.IsMacOS())
+ start.Environment.Add("PATH", "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin");
+
+ // Force using this app as git editor.
+ switch (Editor)
+ {
+ case EditorType.CoreEditor:
+ start.Arguments += $"-c core.editor=\"\\\"{selfExecFile}\\\" --core-editor\" ";
+ break;
+ case EditorType.RebaseEditor:
+ start.Arguments += $"-c core.editor=\"\\\"{selfExecFile}\\\" --rebase-message-editor\" -c sequence.editor=\"\\\"{selfExecFile}\\\" --rebase-todo-editor\" -c rebase.abbreviateCommands=true ";
+ break;
+ default:
+ start.Arguments += "-c core.editor=true ";
+ break;
+ }
+
+ // Append command args
+ start.Arguments += Args;
+
+ // Working directory
+ if (!string.IsNullOrEmpty(WorkingDirectory))
+ start.WorkingDirectory = WorkingDirectory;
+
+ return start;
+ }
[GeneratedRegex(@"\d+%")]
private static partial Regex REG_PROGRESS();
diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs
index 07622821..94b7fde9 100644
--- a/src/Commands/Fetch.cs
+++ b/src/Commands/Fetch.cs
@@ -22,8 +22,6 @@ namespace SourceGit.Commands
Args += "--force ";
Args += remote;
-
- Models.AutoFetchManager.Instance.MarkFetched(repo);
}
public Fetch(string repo, string remote, string localBranch, string remoteBranch, Action outputHandler)
diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs
index 5f9d31a8..ee82ce88 100644
--- a/src/Commands/QueryBranches.cs
+++ b/src/Commands/QueryBranches.cs
@@ -19,29 +19,35 @@ namespace SourceGit.Commands
public List Result()
{
- Exec();
+ var branches = new List();
+ var rs = ReadToEnd();
+ if (!rs.IsSuccess)
+ return branches;
- foreach (var b in _needQueryTrackStatus)
- b.TrackStatus = new QueryTrackStatus(WorkingDirectory, b.Name, b.Upstream).Result();
+ var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
+ foreach (var line in lines)
+ {
+ var b = ParseLine(line);
+ if (b != null)
+ branches.Add(b);
+ }
- return _branches;
+ return branches;
}
- protected override void OnReadline(string line)
+ private Models.Branch ParseLine(string line)
{
var parts = line.Split('$');
if (parts.Length != 5)
- return;
+ return null;
var branch = new Models.Branch();
var refName = parts[0];
if (refName.EndsWith("/HEAD", StringComparison.Ordinal))
- return;
+ return null;
- if (refName.StartsWith(PREFIX_DETACHED_AT, StringComparison.Ordinal) || refName.StartsWith(PREFIX_DETACHED_FROM, StringComparison.Ordinal))
- {
- branch.IsDetachedHead = true;
- }
+ branch.IsDetachedHead = refName.StartsWith(PREFIX_DETACHED_AT, StringComparison.Ordinal) ||
+ refName.StartsWith(PREFIX_DETACHED_FROM, StringComparison.Ordinal);
if (refName.StartsWith(PREFIX_LOCAL, StringComparison.Ordinal))
{
@@ -53,7 +59,7 @@ namespace SourceGit.Commands
var name = refName.Substring(PREFIX_REMOTE.Length);
var shortNameIdx = name.IndexOf('/', StringComparison.Ordinal);
if (shortNameIdx < 0)
- return;
+ return null;
branch.Remote = name.Substring(0, shortNameIdx);
branch.Name = name.Substring(branch.Remote.Length + 1);
@@ -71,14 +77,11 @@ namespace SourceGit.Commands
branch.Upstream = parts[3];
if (branch.IsLocal && !string.IsNullOrEmpty(parts[4]) && !parts[4].Equals("=", StringComparison.Ordinal))
- _needQueryTrackStatus.Add(branch);
+ branch.TrackStatus = new QueryTrackStatus(WorkingDirectory, branch.Name, branch.Upstream).Result();
else
branch.TrackStatus = new Models.BranchTrackStatus();
- _branches.Add(branch);
+ return branch;
}
-
- private readonly List _branches = new List();
- private List _needQueryTrackStatus = new List();
}
}
diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs
index 8a48eff3..76894412 100644
--- a/src/Commands/QueryCommits.cs
+++ b/src/Commands/QueryCommits.cs
@@ -14,21 +14,23 @@ namespace SourceGit.Commands
_findFirstMerged = needFindHead;
}
- public QueryCommits(string repo, string filter, Models.CommitSearchMethod method)
+ public QueryCommits(string repo, string filter, Models.CommitSearchMethod method, bool onlyCurrentBranch)
{
- string search;
+ string search = onlyCurrentBranch ? string.Empty : "--branches --remotes ";
if (method == Models.CommitSearchMethod.ByUser)
{
- search = $"-i --author=\"{filter}\" --committer=\"{filter}\"";
+ search += $"-i --author=\"{filter}\" --committer=\"{filter}\"";
}
else if (method == Models.CommitSearchMethod.ByFile)
{
- search = $"-- \"{filter}\"";
+ search += $"-- \"{filter}\"";
}
else
{
var argsBuilder = new StringBuilder();
+ argsBuilder.Append(search);
+
var words = filter.Split(new[] { ' ', '\t', '\r' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var word in words)
{
@@ -36,12 +38,13 @@ namespace SourceGit.Commands
argsBuilder.Append($"--grep=\"{escaped}\" ");
}
argsBuilder.Append("--all-match -i");
+
search = argsBuilder.ToString();
}
WorkingDirectory = repo;
Context = repo;
- Args = $"log -1000 --date-order --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s --branches --remotes " + search;
+ Args = $"log -1000 --date-order --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s " + search;
_findFirstMerged = false;
}
diff --git a/src/Commands/QueryTags.cs b/src/Commands/QueryTags.cs
index 2abbe277..7da324de 100644
--- a/src/Commands/QueryTags.cs
+++ b/src/Commands/QueryTags.cs
@@ -14,31 +14,43 @@ namespace SourceGit.Commands
public List Result()
{
- Exec();
- return _loaded;
+ var tags = new List();
+ var rs = ReadToEnd();
+ if (!rs.IsSuccess)
+ return tags;
+
+ var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
+ foreach (var line in lines)
+ {
+ var tag = ParseLine(line);
+ if (tag != null)
+ tags.Add(tag);
+ }
+
+ return tags;
}
- protected override void OnReadline(string line)
+ private Models.Tag ParseLine(string line)
{
var subs = line.Split('$', StringSplitOptions.RemoveEmptyEntries);
if (subs.Length == 2)
{
- _loaded.Add(new Models.Tag()
+ return new Models.Tag()
{
Name = subs[0].Substring(10),
SHA = subs[1],
- });
+ };
}
else if (subs.Length == 3)
{
- _loaded.Add(new Models.Tag()
+ return new Models.Tag()
{
Name = subs[0].Substring(10),
SHA = subs[2],
- });
+ };
}
- }
- private readonly List _loaded = new List();
+ return null;
+ }
}
}
diff --git a/src/Commands/Statistics.cs b/src/Commands/Statistics.cs
index 6b2296ca..adfcc574 100644
--- a/src/Commands/Statistics.cs
+++ b/src/Commands/Statistics.cs
@@ -6,21 +6,35 @@ namespace SourceGit.Commands
{
public Statistics(string repo)
{
- _statistics = new Models.Statistics();
-
WorkingDirectory = repo;
Context = repo;
- Args = $"log --date-order --branches --remotes --since=\"{_statistics.Since()}\" --pretty=format:\"%ct$%an\"";
+ Args = $"log --date-order --branches --remotes -40000 --pretty=format:\"%ct$%aN\"";
}
public Models.Statistics Result()
{
- Exec();
- _statistics.Complete();
- return _statistics;
+ var statistics = new Models.Statistics();
+ var rs = ReadToEnd();
+ if (!rs.IsSuccess)
+ return statistics;
+
+ var start = 0;
+ var end = rs.StdOut.IndexOf('\n', start);
+ while (end > 0)
+ {
+ ParseLine(statistics, rs.StdOut.Substring(start, end - start));
+ start = end + 1;
+ end = rs.StdOut.IndexOf('\n', start);
+ }
+
+ if (start < rs.StdOut.Length)
+ ParseLine(statistics, rs.StdOut.Substring(start));
+
+ statistics.Complete();
+ return statistics;
}
- protected override void OnReadline(string line)
+ private void ParseLine(Models.Statistics statistics, string line)
{
var dateEndIdx = line.IndexOf('$', StringComparison.Ordinal);
if (dateEndIdx == -1)
@@ -28,9 +42,7 @@ namespace SourceGit.Commands
var dateStr = line.Substring(0, dateEndIdx);
if (double.TryParse(dateStr, out var date))
- _statistics.AddCommit(line.Substring(dateEndIdx + 1), date);
+ statistics.AddCommit(line.Substring(dateEndIdx + 1), date);
}
-
- private readonly Models.Statistics _statistics = null;
}
}
diff --git a/src/Converters/ListConverters.cs b/src/Converters/ListConverters.cs
index fef0f584..81cac8b7 100644
--- a/src/Converters/ListConverters.cs
+++ b/src/Converters/ListConverters.cs
@@ -10,6 +10,9 @@ namespace SourceGit.Converters
public static readonly FuncValueConverter ToCount =
new FuncValueConverter(v => v == null ? " (0)" : $" ({v.Count})");
+ public static readonly FuncValueConverter IsNullOrEmpty =
+ new FuncValueConverter(v => v == null || v.Count == 0);
+
public static readonly FuncValueConverter IsNotNullOrEmpty =
new FuncValueConverter(v => v != null && v.Count > 0);
diff --git a/src/Converters/PathConverters.cs b/src/Converters/PathConverters.cs
index 95da6c79..dd7cfa49 100644
--- a/src/Converters/PathConverters.cs
+++ b/src/Converters/PathConverters.cs
@@ -18,7 +18,7 @@ namespace SourceGit.Converters
{
if (OperatingSystem.IsWindows())
return v;
-
+
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var prefixLen = home.EndsWith('/') ? home.Length - 1 : home.Length;
if (v.StartsWith(home, StringComparison.Ordinal))
diff --git a/src/Models/AutoFetchManager.cs b/src/Models/AutoFetchManager.cs
deleted file mode 100644
index 313f5f64..00000000
--- a/src/Models/AutoFetchManager.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace SourceGit.Models
-{
- public class AutoFetchManager
- {
- public static AutoFetchManager Instance
- {
- get
- {
- if (_instance == null)
- _instance = new AutoFetchManager();
-
- return _instance;
- }
- }
-
- public class Job
- {
- public string IndexLockFile = string.Empty;
- public Commands.Fetch Cmd = null;
- public DateTime NextRunTimepoint = DateTime.MinValue;
- }
-
- public bool IsEnabled
- {
- get;
- set;
- } = false;
-
- public int Interval
- {
- get => _interval;
- set
- {
- _interval = Math.Max(1, value);
-
- lock (_lock)
- {
- foreach (var job in _jobs)
- job.Value.NextRunTimepoint = DateTime.Now.AddMinutes(_interval * 1.0);
- }
- }
- }
-
- private static AutoFetchManager _instance = null;
- private Dictionary _jobs = new Dictionary();
- private object _lock = new object();
- private int _interval = 10;
-
- public void Start()
- {
- Task.Run(() =>
- {
- while (true)
- {
- if (!IsEnabled)
- {
- Thread.Sleep(10000);
- continue;
- }
-
- var now = DateTime.Now;
- var uptodate = new List();
- lock (_lock)
- {
- foreach (var job in _jobs)
- {
- if (job.Value.NextRunTimepoint.Subtract(now).TotalSeconds <= 0)
- uptodate.Add(job.Value);
- }
- }
-
- foreach (var job in uptodate)
- {
- if (!File.Exists(job.IndexLockFile))
- {
- job.Cmd.Exec();
- job.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
- }
- }
-
- Thread.Sleep(2000);
- }
-
- // ReSharper disable once FunctionNeverReturns
- });
- }
-
- public void AddRepository(string repo, string gitDir)
- {
- var job = new Job
- {
- IndexLockFile = Path.Combine(gitDir, "index.lock"),
- Cmd = new Commands.Fetch(repo, "--all", true, false, null) { RaiseError = false },
- NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
- };
-
- lock (_lock)
- {
- _jobs[repo] = job;
- }
- }
-
- public void RemoveRepository(string repo)
- {
- lock (_lock)
- {
- _jobs.Remove(repo);
- }
- }
-
- public void MarkFetched(string repo)
- {
- lock (_lock)
- {
- if (_jobs.TryGetValue(repo, out var value))
- value.NextRunTimepoint = DateTime.Now.AddMinutes(Interval * 1.0);
- }
- }
- }
-}
diff --git a/src/Models/CommitGraph.cs b/src/Models/CommitGraph.cs
index fde8011c..3872fcc6 100644
--- a/src/Models/CommitGraph.cs
+++ b/src/Models/CommitGraph.cs
@@ -8,85 +8,27 @@ namespace SourceGit.Models
{
public class CommitGraph
{
- public class Path
+ public static List Pens { get; } = [];
+
+ public static void SetDefaultPens(double thickness = 2)
{
- public List Points = new List();
- public int Color = 0;
+ SetPens(s_defaultPenColors, thickness);
}
- public class PathHelper
+ public static void SetPens(List colors, double thickness)
{
- public string Next;
- public bool IsMerged;
- public double LastX;
- public double LastY;
- public double EndY;
- public Path Path;
+ Pens.Clear();
- public PathHelper(string next, bool isMerged, int color, Point start)
- {
- Next = next;
- IsMerged = isMerged;
- LastX = start.X;
- LastY = start.Y;
- EndY = LastY;
+ foreach (var c in colors)
+ Pens.Add(new Pen(c.ToUInt32(), thickness));
- Path = new Path();
- Path.Color = color;
- Path.Points.Add(start);
- }
+ s_penCount = colors.Count;
+ }
- public PathHelper(string next, bool isMerged, int color, Point start, Point to)
- {
- Next = next;
- IsMerged = isMerged;
- LastX = to.X;
- LastY = to.Y;
- EndY = LastY;
-
- Path = new Path();
- Path.Color = color;
- Path.Points.Add(start);
- Path.Points.Add(to);
- }
-
- public void Add(double x, double y, double halfHeight, bool isEnd = false)
- {
- if (x > LastX)
- {
- Add(new Point(LastX, LastY));
- Add(new Point(x, y - halfHeight));
- if (isEnd)
- Add(new Point(x, y));
- }
- else if (x < LastX)
- {
- var testY = LastY + halfHeight;
- if (y > testY)
- Add(new Point(LastX, testY));
-
- if (!isEnd)
- y += halfHeight;
-
- Add(new Point(x, y));
- }
- else if (isEnd)
- {
- Add(new Point(x, y));
- }
-
- LastX = x;
- LastY = y;
- }
-
- private void Add(Point p)
- {
- if (EndY < p.Y)
- {
- Path.Points.Add(p);
- EndY = p.Y;
- }
- }
+ public class Path(int color)
+ {
+ public List Points { get; } = [];
+ public int Color { get; } = color;
}
public class Link
@@ -111,82 +53,57 @@ namespace SourceGit.Models
public int Color;
}
- public List Paths { get; set; } = new List();
- public List Links { get; set; } = new List();
- public List Dots { get; set; } = new List();
-
- public static List Pens
- {
- get;
- private set;
- } = new List();
-
- public static void SetDefaultPens(double thickness = 2)
- {
- SetPens(_defaultPenColors, thickness);
- }
-
- public static void SetPens(List colors, double thickness)
- {
- Pens.Clear();
-
- foreach (var c in colors)
- Pens.Add(new Pen(c.ToUInt32(), thickness));
-
- _penCount = colors.Count;
- }
+ public List Paths { get; } = [];
+ public List Links { get; } = [];
+ public List Dots { get; } = [];
public static CommitGraph Parse(List commits, bool firstParentOnlyEnabled)
{
- double UNIT_WIDTH = 12;
- double HALF_WIDTH = 6;
- double UNIT_HEIGHT = 28;
- double HALF_HEIGHT = 14;
- double H_MARGIN = 2;
+ const double unitWidth = 12;
+ const double halfWidth = 6;
+ const double unitHeight = 28;
+ const double halfHeight = 14;
var temp = new CommitGraph();
var unsolved = new List();
- var mapUnsolved = new Dictionary();
var ended = new List();
- var offsetY = -HALF_HEIGHT;
+ var offsetY = -halfHeight;
var colorIdx = 0;
foreach (var commit in commits)
{
var major = null as PathHelper;
var isMerged = commit.IsMerged;
- var oldCount = unsolved.Count;
// Update current y offset
- offsetY += UNIT_HEIGHT;
+ offsetY += unitHeight;
// Find first curves that links to this commit and marks others that links to this commit ended.
- double offsetX = H_MARGIN - HALF_WIDTH;
+ var offsetX = 4 - halfWidth;
+ var maxOffsetOld = unsolved.Count > 0 ? unsolved[^1].LastX : offsetX + unitWidth;
foreach (var l in unsolved)
{
- if (l.Next == commit.SHA)
+ if (l.Next.Equals(commit.SHA, StringComparison.Ordinal))
{
if (major == null)
{
- offsetX += UNIT_WIDTH;
+ offsetX += unitWidth;
major = l;
if (commit.Parents.Count > 0)
{
major.Next = commit.Parents[0];
- if (!mapUnsolved.ContainsKey(major.Next))
- mapUnsolved.Add(major.Next, major);
+ major.Goto(offsetX, offsetY, halfHeight);
}
else
{
- major.Next = "ENDED";
+ major.End(offsetX, offsetY, halfHeight);
ended.Add(l);
}
-
- major.Add(offsetX, offsetY, HALF_HEIGHT);
}
else
{
+ l.End(major.LastX, offsetY, halfHeight);
ended.Add(l);
}
@@ -195,17 +112,20 @@ namespace SourceGit.Models
}
else
{
- if (!mapUnsolved.ContainsKey(l.Next))
- mapUnsolved.Add(l.Next, l);
- offsetX += UNIT_WIDTH;
- l.Add(offsetX, offsetY, HALF_HEIGHT);
+ offsetX += unitWidth;
+ l.Pass(offsetX, offsetY, halfHeight);
}
}
+ // Remove ended curves from unsolved
+ foreach (var l in ended)
+ unsolved.Remove(l);
+ ended.Clear();
+
// Create new curve for branch head
if (major == null)
{
- offsetX += UNIT_WIDTH;
+ offsetX += unitWidth;
if (commit.Parents.Count > 0)
{
@@ -214,18 +134,13 @@ namespace SourceGit.Models
temp.Paths.Add(major.Path);
}
- colorIdx = (colorIdx + 1) % _penCount;
+ colorIdx = (colorIdx + 1) % s_penCount;
}
// Calculate link position of this commit.
- Point position = new Point(offsetX, offsetY);
- int dotColor = 0;
- if (major != null)
- {
- position = new Point(major.LastX, offsetY);
- dotColor = major.Path.Color;
- }
- Dot anchor = new Dot() { Center = position, Color = dotColor };
+ var position = new Point(major?.LastX ?? offsetX, offsetY);
+ var dotColor = major?.Path.Color ?? 0;
+ var anchor = new Dot() { Center = position, Color = dotColor };
if (commit.IsCurrentHead)
anchor.Type = DotType.Head;
else if (commit.Parents.Count > 1)
@@ -239,69 +154,176 @@ namespace SourceGit.Models
{
for (int j = 1; j < commit.Parents.Count; j++)
{
- var parent = commit.Parents[j];
- if (mapUnsolved.TryGetValue(parent, out var value))
+ var parentHash = commit.Parents[j];
+ var parent = unsolved.Find(x => x.Next.Equals(parentHash, StringComparison.Ordinal));
+ if (parent != null)
{
// Try to change the merge state of linked graph
- var l = value;
if (isMerged)
- l.IsMerged = true;
+ parent.IsMerged = true;
- var link = new Link();
- link.Start = position;
- link.End = new Point(l.LastX, offsetY + HALF_HEIGHT);
- link.Control = new Point(link.End.X, link.Start.Y);
- link.Color = l.Path.Color;
-
- temp.Links.Add(link);
+ temp.Links.Add(new Link
+ {
+ Start = position,
+ End = new Point(parent.LastX, offsetY + halfHeight),
+ Control = new Point(parent.LastX, position.Y),
+ Color = parent.Path.Color
+ });
}
else
{
- offsetX += UNIT_WIDTH;
+ offsetX += unitWidth;
// Create new curve for parent commit that not includes before
- var l = new PathHelper(parent, isMerged, colorIdx, position, new Point(offsetX, position.Y + HALF_HEIGHT));
+ var l = new PathHelper(parentHash, isMerged, colorIdx, position, new Point(offsetX, position.Y + halfHeight));
unsolved.Add(l);
temp.Paths.Add(l.Path);
- colorIdx = (colorIdx + 1) % _penCount;
+ colorIdx = (colorIdx + 1) % s_penCount;
}
}
}
- // Remove ended curves from unsolved
- foreach (var l in ended)
- {
- l.Add(position.X, position.Y, HALF_HEIGHT, true);
- unsolved.Remove(l);
- }
-
// Margins & merge state (used by Views.Histories).
commit.IsMerged = isMerged;
- commit.Margin = new Thickness(Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH + H_MARGIN) + H_MARGIN, 0, 0, 0);
-
- // Clean up
- ended.Clear();
- mapUnsolved.Clear();
+ commit.Margin = new Thickness(Math.Max(offsetX, maxOffsetOld) + halfWidth + 2, 0, 0, 0);
}
// Deal with curves haven't ended yet.
- for (int i = 0; i < unsolved.Count; i++)
+ for (var i = 0; i < unsolved.Count; i++)
{
var path = unsolved[i];
- var endY = (commits.Count - 0.5) * UNIT_HEIGHT;
+ var endY = (commits.Count - 0.5) * unitHeight;
if (path.Path.Points.Count == 1 && Math.Abs(path.Path.Points[0].Y - endY) < 0.0001)
continue;
- path.Add((i + 0.5) * UNIT_WIDTH + H_MARGIN, endY + HALF_HEIGHT, HALF_HEIGHT, true);
+ path.End((i + 0.5) * unitWidth + 4, endY + halfHeight, halfHeight);
}
unsolved.Clear();
return temp;
}
- private static int _penCount = 0;
- private static readonly List _defaultPenColors = [
+ private class PathHelper
+ {
+ public Path Path { get; }
+ public string Next { get; set; }
+ public bool IsMerged { get; set; }
+ public double LastX { get; private set; }
+
+ public PathHelper(string next, bool isMerged, int color, Point start)
+ {
+ Next = next;
+ IsMerged = isMerged;
+ LastX = start.X;
+ _lastY = start.Y;
+
+ Path = new Path(color);
+ Path.Points.Add(start);
+ }
+
+ public PathHelper(string next, bool isMerged, int color, Point start, Point to)
+ {
+ Next = next;
+ IsMerged = isMerged;
+ LastX = to.X;
+ _lastY = to.Y;
+
+ Path = new Path(color);
+ Path.Points.Add(start);
+ Path.Points.Add(to);
+ }
+
+ ///
+ /// A path that just passed this row.
+ ///
+ ///
+ ///
+ ///
+ public void Pass(double x, double y, double halfHeight)
+ {
+ if (x > LastX)
+ {
+ Add(LastX, _lastY);
+ Add(x, y - halfHeight);
+ }
+ else if (x < LastX)
+ {
+ Add(LastX, y - halfHeight);
+ y += halfHeight;
+ Add(x, y);
+ }
+
+ LastX = x;
+ _lastY = y;
+ }
+
+ ///
+ /// A path that has commit in this row but not ended
+ ///
+ ///
+ ///
+ ///
+ public void Goto(double x, double y, double halfHeight)
+ {
+ if (x > LastX)
+ {
+ Add(LastX, _lastY);
+ Add(x, y - halfHeight);
+ }
+ else if (x < LastX)
+ {
+ var minY = y - halfHeight;
+ if (minY > _lastY)
+ minY -= halfHeight;
+
+ Add(LastX, minY);
+ Add(x, y);
+ }
+
+ LastX = x;
+ _lastY = y;
+ }
+
+ ///
+ /// A path that has commit in this row and end.
+ ///
+ ///
+ ///
+ ///
+ public void End(double x, double y, double halfHeight)
+ {
+ if (x > LastX)
+ {
+ Add(LastX, _lastY);
+ Add(x, y - halfHeight);
+ }
+ else if (x < LastX)
+ {
+ Add(LastX, y - halfHeight);
+ }
+
+ Add(x, y);
+
+ LastX = x;
+ _lastY = y;
+ }
+
+ private void Add(double x, double y)
+ {
+ if (_endY < y)
+ {
+ Path.Points.Add(new Point(x, y));
+ _endY = y;
+ }
+ }
+
+ private double _lastY = 0;
+ private double _endY = 0;
+ }
+
+ private static int s_penCount = 0;
+ private static readonly List s_defaultPenColors = [
Colors.Orange,
Colors.ForestGreen,
Colors.Gold,
diff --git a/src/Models/IRepository.cs b/src/Models/IRepository.cs
new file mode 100644
index 00000000..12b1adba
--- /dev/null
+++ b/src/Models/IRepository.cs
@@ -0,0 +1,16 @@
+namespace SourceGit.Models
+{
+ public interface IRepository
+ {
+ string FullPath { get; set; }
+ string GitDir { get; set; }
+
+ void RefreshBranches();
+ void RefreshWorktrees();
+ void RefreshTags();
+ void RefreshCommits();
+ void RefreshSubmodules();
+ void RefreshWorkingCopyChanges();
+ void RefreshStashes();
+ }
+}
diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs
index 06e60a87..244fc673 100644
--- a/src/Models/RepositorySettings.cs
+++ b/src/Models/RepositorySettings.cs
@@ -94,6 +94,18 @@ namespace SourceGit.Models
set;
} = new AvaloniaList();
+ public bool EnableAutoFetch
+ {
+ get;
+ set;
+ } = false;
+
+ public int AutoFetchInterval
+ {
+ get;
+ set;
+ } = 10;
+
public void PushCommitMessage(string message)
{
var existIdx = CommitMessages.IndexOf(message);
diff --git a/src/Models/ResetMode.cs b/src/Models/ResetMode.cs
index f2bfb5ef..735533c2 100644
--- a/src/Models/ResetMode.cs
+++ b/src/Models/ResetMode.cs
@@ -8,6 +8,8 @@ namespace SourceGit.Models
[
new ResetMode("Soft", "Keep all changes. Stage differences", "--soft", Brushes.Green),
new ResetMode("Mixed", "Keep all changes. Unstage differences", "--mixed", Brushes.Orange),
+ new ResetMode("Merge", "Reset while keeping unmerged changes", "--merge", Brushes.Purple),
+ new ResetMode("Keep", "Reset while keeping local modifications", "--keep", Brushes.Purple),
new ResetMode("Hard", "Discard all changes", "--hard", Brushes.Red),
];
diff --git a/src/Models/Statistics.cs b/src/Models/Statistics.cs
index 21f1e65d..b669eb55 100644
--- a/src/Models/Statistics.cs
+++ b/src/Models/Statistics.cs
@@ -1,122 +1,172 @@
using System;
using System.Collections.Generic;
+using LiveChartsCore;
+using LiveChartsCore.Defaults;
+using LiveChartsCore.SkiaSharpView;
+using LiveChartsCore.SkiaSharpView.Painting;
+
+using SkiaSharp;
+
namespace SourceGit.Models
{
- public class StatisticsSample(string name)
+ public enum StaticsticsMode
+ {
+ All,
+ ThisMonth,
+ ThisWeek,
+ }
+
+ public class StaticsticsAuthor(string name, int count)
{
public string Name { get; set; } = name;
- public int Count { get; set; } = 0;
+ public int Count { get; set; } = count;
+ }
+
+ public class StaticsticsSample(DateTime time, int count)
+ {
+ public DateTime Time { get; set; } = time;
+ public int Count { get; set; } = count;
}
public class StatisticsReport
{
+ public static readonly string[] WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
+
public int Total { get; set; } = 0;
- public List Samples { get; set; } = new List();
- public List ByAuthor { get; set; } = new List();
+ public List Authors { get; set; } = new List();
+ public List Series { get; set; } = new List();
+ public List XAxes { get; set; } = new List();
+ public List YAxes { get; set; } = new List();
- public void AddCommit(int index, string author)
+ public StatisticsReport(StaticsticsMode mode, DateTime start)
{
- Total++;
- Samples[index].Count++;
+ _mode = mode;
- if (_mapUsers.TryGetValue(author, out var value))
+ YAxes = [new Axis()
{
- value.Count++;
+ TextSize = 10,
+ MinLimit = 0,
+ SeparatorsPaint = new SolidColorPaint(new SKColor(0x40808080)) { StrokeThickness = 1 }
+ }];
+
+ if (mode == StaticsticsMode.ThisWeek)
+ {
+ for (int i = 0; i < 7; i++)
+ _mapSamples.Add(start.AddDays(i), 0);
+
+ XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(1), v => WEEKDAYS[(int)v.DayOfWeek]) { TextSize = 10 });
+ }
+ else if (mode == StaticsticsMode.ThisMonth)
+ {
+ var now = DateTime.Now;
+ var maxDays = DateTime.DaysInMonth(now.Year, now.Month);
+ for (int i = 0; i < maxDays; i++)
+ _mapSamples.Add(start.AddDays(i), 0);
+
+ XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(1), v => $"{v:MM/dd}") { TextSize = 10 });
}
else
{
- var sample = new StatisticsSample(author);
- sample.Count++;
-
- _mapUsers.Add(author, sample);
- ByAuthor.Add(sample);
+ XAxes.Add(new DateTimeAxis(TimeSpan.FromDays(30), v => $"{v:yyyy/MM}") { TextSize = 10 });
}
}
+ public void AddCommit(DateTime time, string author)
+ {
+ Total++;
+
+ var normalized = DateTime.MinValue;
+ if (_mode == StaticsticsMode.ThisWeek || _mode == StaticsticsMode.ThisMonth)
+ normalized = time.Date;
+ else
+ normalized = new DateTime(time.Year, time.Month, 1).ToLocalTime();
+
+ if (_mapSamples.TryGetValue(normalized, out var vs))
+ _mapSamples[normalized] = vs + 1;
+ else
+ _mapSamples.Add(normalized, 1);
+
+ if (_mapUsers.TryGetValue(author, out var vu))
+ _mapUsers[author] = vu + 1;
+ else
+ _mapUsers.Add(author, 1);
+ }
+
public void Complete()
{
- ByAuthor.Sort((l, r) => r.Count - l.Count);
+ var samples = new List();
+ foreach (var kv in _mapSamples)
+ samples.Add(new DateTimePoint(kv.Key, kv.Value));
+
+ Series.Add(
+ new ColumnSeries()
+ {
+ Values = samples,
+ Stroke = null,
+ Fill = null,
+ Padding = 1,
+ }
+ );
+
+ foreach (var kv in _mapUsers)
+ Authors.Add(new StaticsticsAuthor(kv.Key, kv.Value));
+
+ Authors.Sort((l, r) => r.Count - l.Count);
+
_mapUsers.Clear();
+ _mapSamples.Clear();
}
- private readonly Dictionary _mapUsers = new Dictionary();
+ public void ChangeColor(uint color)
+ {
+ if (Series is [ColumnSeries series])
+ series.Fill = new SolidColorPaint(new SKColor(color));
+ }
+
+ private StaticsticsMode _mode = StaticsticsMode.All;
+ private Dictionary _mapUsers = new Dictionary();
+ private Dictionary _mapSamples = new Dictionary();
}
public class Statistics
{
- public StatisticsReport Year { get; set; } = new StatisticsReport();
- public StatisticsReport Month { get; set; } = new StatisticsReport();
- public StatisticsReport Week { get; set; } = new StatisticsReport();
+ public StatisticsReport All { get; set; }
+ public StatisticsReport Month { get; set; }
+ public StatisticsReport Week { get; set; }
public Statistics()
{
- _today = DateTime.Today;
- _thisWeekStart = _today.AddSeconds(-(int)_today.DayOfWeek * 3600 * 24 - _today.Hour * 3600 - _today.Minute * 60 - _today.Second);
- _thisWeekEnd = _thisWeekStart.AddDays(7);
+ _today = DateTime.Now.ToLocalTime().Date;
+ _thisWeekStart = _today.AddSeconds(-(int)_today.DayOfWeek * 3600 * 24);
+ _thisMonthStart = _today.AddDays(1 - _today.Day);
- for (int i = 0; i < 12; i++)
- Year.Samples.Add(new StatisticsSample(""));
-
- var monthDays = DateTime.DaysInMonth(_today.Year, _today.Month);
- for (int i = 0; i < monthDays; i++)
- Month.Samples.Add(new StatisticsSample($"{i + 1}"));
-
- string[] weekDayNames = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
- for (int i = 0; i < weekDayNames.Length; i++)
- Week.Samples.Add(new StatisticsSample(weekDayNames[i]));
- }
-
- public string Since()
- {
- return _today.AddMonths(-11).ToString("yyyy-MM-01 00:00:00");
+ All = new StatisticsReport(StaticsticsMode.All, DateTime.MinValue);
+ Month = new StatisticsReport(StaticsticsMode.ThisMonth, _thisMonthStart);
+ Week = new StatisticsReport(StaticsticsMode.ThisWeek, _thisWeekStart);
}
public void AddCommit(string author, double timestamp)
{
var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime();
- if (time.CompareTo(_thisWeekStart) >= 0 && time.CompareTo(_thisWeekEnd) < 0)
- Week.AddCommit((int)time.DayOfWeek, author);
+ if (time >= _thisWeekStart)
+ Week.AddCommit(time, author);
- if (time.Month == _today.Month)
- Month.AddCommit(time.Day - 1, author);
+ if (time >= _thisMonthStart)
+ Month.AddCommit(time, author);
- Year.AddCommit(time.Month - 1, author);
+ All.AddCommit(time, author);
}
public void Complete()
{
- Year.Complete();
+ All.Complete();
Month.Complete();
Week.Complete();
-
- // Year is start from 11 months ago from now.
- var thisYear = _today.Year;
- var start = _today.AddMonths(-11);
- if (start.Month == 1)
- {
- for (int i = 0; i < 12; i++)
- Year.Samples[i].Name = $"{thisYear}/{i + 1:00}";
- }
- else
- {
- var lastYearIdx = start.Month - 1;
- var lastYearMonths = Year.Samples.GetRange(lastYearIdx, 12 - lastYearIdx);
- for (int i = 0; i < lastYearMonths.Count; i++)
- lastYearMonths[i].Name = $"{thisYear - 1}/{lastYearIdx + i + 1:00}";
-
- var thisYearMonths = Year.Samples.GetRange(0, lastYearIdx);
- for (int i = 0; i < thisYearMonths.Count; i++)
- thisYearMonths[i].Name = $"{thisYear}/{i + 1:00}";
-
- Year.Samples.Clear();
- Year.Samples.AddRange(lastYearMonths);
- Year.Samples.AddRange(thisYearMonths);
- }
}
private readonly DateTime _today;
+ private readonly DateTime _thisMonthStart;
private readonly DateTime _thisWeekStart;
- private readonly DateTime _thisWeekEnd;
}
}
diff --git a/src/Models/TextMateHelper.cs b/src/Models/TextMateHelper.cs
index f8e5acf2..2e38e4dd 100644
--- a/src/Models/TextMateHelper.cs
+++ b/src/Models/TextMateHelper.cs
@@ -38,7 +38,7 @@ namespace SourceGit.Models
extension = ".sh";
else if (extension == ".kt" || extension == ".kts")
extension = ".kotlin";
-
+
foreach (var grammar in s_extraGrammars)
{
if (grammar.Extension.Equals(extension, StringComparison.OrdinalIgnoreCase))
@@ -87,7 +87,7 @@ namespace SourceGit.Models
public ICollection GetInjections(string scopeName) => _backend.GetInjections(scopeName);
public IRawGrammar GetGrammar(string scopeName) => GrammarUtility.GetGrammar(scopeName, _backend);
public string GetScope(string filename) => GrammarUtility.GetScope(filename, _backend);
-
+
private readonly RegistryOptions _backend = new(defaultTheme);
}
@@ -95,8 +95,8 @@ namespace SourceGit.Models
{
public static TextMate.Installation CreateForEditor(TextEditor editor)
{
- return editor.InstallTextMate(Application.Current?.ActualThemeVariant == ThemeVariant.Dark ?
- new RegistryOptionsWrapper(ThemeName.DarkPlus) :
+ return editor.InstallTextMate(Application.Current?.ActualThemeVariant == ThemeVariant.Dark ?
+ new RegistryOptionsWrapper(ThemeName.DarkPlus) :
new RegistryOptionsWrapper(ThemeName.LightPlus));
}
diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs
index 6cd77a15..6665250c 100644
--- a/src/Models/Watcher.cs
+++ b/src/Models/Watcher.cs
@@ -6,20 +6,6 @@ using System.Threading.Tasks;
namespace SourceGit.Models
{
- public interface IRepository
- {
- string FullPath { get; set; }
- string GitDir { get; set; }
-
- void RefreshBranches();
- void RefreshWorktrees();
- void RefreshTags();
- void RefreshCommits();
- void RefreshSubmodules();
- void RefreshWorkingCopyChanges();
- void RefreshStashes();
- }
-
public class Watcher : IDisposable
{
public Watcher(IRepository repo)
diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs
index b02112cc..6ca0bbb0 100644
--- a/src/Native/Windows.cs
+++ b/src/Native/Windows.cs
@@ -201,9 +201,9 @@ namespace SourceGit.Native
private void FixWindowFrameOnWin10(Window w)
{
- if (w.WindowState != WindowState.Normal)
+ if (w.WindowState == WindowState.Maximized || w.WindowState == WindowState.FullScreen)
w.SystemDecorations = SystemDecorations.Full;
- else
+ else if (w.WindowState == WindowState.Normal)
w.SystemDecorations = SystemDecorations.BorderOnly;
}
diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml
index ef3e13aa..8650e068 100644
--- a/src/Resources/Icons.axaml
+++ b/src/Resources/Icons.axaml
@@ -14,6 +14,7 @@
M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z
M1280 704c0 141-115 256-256 256H288C129 960 0 831 0 672c0-126 80-232 192-272A327 327 0 01192 384c0-177 143-320 320-320 119 0 222 64 277 160C820 204 857 192 896 192c106 0 192 86 192 192 0 24-5 48-13 69C1192 477 1280 580 1280 704zm-493-128H656V352c0-18-14-32-32-32h-96c-18 0-32 14-32 32v224h-131c-29 0-43 34-23 55l211 211c12 12 33 12 45 0l211-211c20-20 6-55-23-55z
M853 102H171C133 102 102 133 102 171v683C102 891 133 922 171 922h683C891 922 922 891 922 853V171C922 133 891 102 853 102zM390 600l-48 48L205 512l137-137 48 48L301 512l88 88zM465 819l-66-18L559 205l66 18L465 819zm218-171L634 600 723 512l-88-88 48-48L819 512 683 649z
+ M128 854h768v86H128zM390 797c13 13 29 19 48 19s35-6 45-19l291-288c26-22 26-64 0-90L435 83l-61 61L426 192l-272 269c-22 22-22 64 0 90l237 246zm93-544 211 211-32 32H240l243-243zM707 694c0 48 38 86 86 86 48 0 86-38 86-86 0-22-10-45-26-61L794 576l-61 61c-13 13-26 35-26 58z
M796 471A292 292 0 00512 256a293 293 0 00-284 215H0v144h228A293 293 0 00512 832a291 291 0 00284-217H1024V471h-228M512 688A146 146 0 01366 544A145 145 0 01512 400c80 0 146 63 146 144A146 146 0 01512 688
M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z
M608 0q48 0 88 23t63 63 23 87v70h55q35 0 67 14t57 38 38 57 14 67V831q0 34-14 66t-38 57-57 38-67 13H426q-34 0-66-13t-57-38-38-57-14-66v-70h-56q-34 0-66-14t-57-38-38-57-13-67V174q0-47 23-87T109 23 196 0h412m175 244H426q-46 0-86 22T278 328t-26 85v348H608q47 0 86-22t63-62 25-85l1-348m-269 318q18 0 31 13t13 31-13 31-31 13-31-13-13-31 13-31 31-13m0-212q13 0 22 9t11 22v125q0 14-9 23t-22 10-23-7-11-22l-1-126q0-13 10-23t23-10z
diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml
index 8ec97547..eac15bca 100644
--- a/src/Resources/Locales/de_DE.axaml
+++ b/src/Resources/Locales/de_DE.axaml
@@ -99,6 +99,7 @@
SCHLIESSEN
Editor
Diesen Commit cherry-picken
+ Mehrere cherry-picken
Commit auschecken
Mit HEAD vergleichen
Mit Worktree vergleichen
@@ -128,6 +129,7 @@
VORGÄNGER
REFS
SHA
+ Im Browser öffnen
Commit-Nachricht
Details
Repository Einstellungen
@@ -137,6 +139,8 @@
Email Adresse
Email Adresse
GIT
+ Remotes automatisch fetchen
+ Minute(n)
TICKETSYSTEM
Beispiel für Github-Regel hinzufügen
Beispiel für Jira-Regel hinzufügen
@@ -290,6 +294,7 @@
Sperren anzeigen
Keine gesperrten Dateien
Sperre
+ Zeige nur meine Sperren
LFS Sperren
Entsperren
Erzwinge entsperren
@@ -410,9 +415,6 @@
Commit-Historie
Längenvorgabe für Commit-Nachrichten
GIT
- Remotes automatisch fetchen
- Auto-Fetch Intervall
- Minute(n)
Aktiviere Auto-CRLF
Clone Standardordner
Benutzer Email
@@ -430,6 +432,10 @@
Installationspfad zum GPG Programm
Benutzer Signierungsschlüssel
GPG Benutzer Signierungsschlüssel
+ EINBINDUNGEN
+ SHELL/TERMINAL
+ Shell/Terminal
+ Pfad
Remote löschen
Ziel:
Worktrees löschen
@@ -486,6 +492,7 @@
Eindeutiger Name für diesen Branch
Branch:
ABBRECHEN
+ Änderungen automatisch von Remote fetchen...
Aufräumen (GC & Prune)
Führt `git gc` auf diesem Repository aus.
Alles löschen
@@ -505,7 +512,6 @@
REMOTE HINZUFÜGEN
KONFLIKTE BEHEBEN
Commit suchen
- Suche über
Dateiname
Commit-Nachricht
SHA
@@ -546,6 +552,7 @@
Software Update
Es sind momentan kein Updates verfügbar.
Squash Commits
+ In:
SSH privater Schlüssel:
Pfad zum privaten SSH Schlüssel
START
@@ -567,9 +574,9 @@
COMMITTER
MONAT
WOCHE
- JAHR
COMMITS:
AUTOREN:
+ ÜBERSICHT
SUBMODULE
Submodul hinzufügen
Relativen Pfad kopieren
@@ -616,7 +623,6 @@
COMMIT
COMMIT & PUSH
Template/Historie
- STRG + Enter
KONFLIKTE ERKANNT
DATEI KONFLIKTE GELÖST
NICHT-VERFOLGTE DATEIEN INKLUDIEREN
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index e9717845..ca320f4d 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -136,6 +136,8 @@
Email Address
Email address
GIT
+ Fetch remotes automatically
+ Minute(s)
ISSUE TRACKER
Add Sample Github Rule
Add Sample Jira Rule
@@ -289,6 +291,7 @@
Show Locks
No Locked Files
Lock
+ Show only my locks
LFS Locks
Unlock
Force Unlock
@@ -324,6 +327,7 @@
REPOSITORY
Commit staged changes
Commit and push staged changes
+ Stage all changes and commit
Discard selected changes
Dashboard mode (Default)
Force to reload this repository
@@ -409,9 +413,6 @@
History Commits
Subject Guide Length
GIT
- Fetch remotes automatically
- Auto Fetch Interval
- Minute(s)
Enable Auto CRLF
Default Clone Dir
User Email
@@ -489,6 +490,7 @@
Unique name for this branch
Branch:
ABORT
+ Auto fetching changes from remotes...
Cleanup(GC & Prune)
Run `git gc` command for this repository.
Clear all
@@ -508,11 +510,11 @@
ADD REMOTE
RESOLVE
Search Commit
- Search By
File
Message
SHA
Author & Committer
+ Current Branch
Show Tags as Tree
Statistics
SUBMODULES
@@ -571,9 +573,9 @@
COMMITTER
MONTH
WEEK
- YEAR
COMMITS:
AUTHORS:
+ OVERVIEW
SUBMODULES
Add Submodule
Copy Relative Path
@@ -620,7 +622,8 @@
COMMIT
COMMIT & PUSH
Template/Histories
- CTRL + Enter
+ Trigger click event
+ Stage all changes and commit
CONFLICTS DETECTED
FILE CONFLICTS ARE RESOLVED
INCLUDE UNTRACKED FILES
diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml
index 40df6d99..c90a19cb 100644
--- a/src/Resources/Locales/fr_FR.axaml
+++ b/src/Resources/Locales/fr_FR.axaml
@@ -2,178 +2,181 @@
- A propos
- A propos de SourceGit
+
+ À propos
• Compilé avec
© 2024 sourcegit-scm
• TextEditor de
-
- • La police Monospace vient de
+ • Les polices Monospace proviennent de
+ À propos de SourceGit
• Le code source est disponible sur
- Client Git gratuit et open source
+ Client Git Open Source et Gratuit
Ajouter un Worktree
- What to Checkout:
- Branche existante
- Créer une nouvelle branche
- Location:
+ Emplacement :
Chemin vers ce worktree. Relatif supporté.
Nom de branche:
- Optionnel. Par défaut le nom du dossier de destination.
- Track Branch:
- Tracking remote branch
- Patch
+ Optionnel. Nom du dossier de destination par défaut.
+ Suivre la branche :
+ Suivi de la branche distante
+ What to Checkout:
+ Créer une nouvelle branche
+ Branche existante
+ Appliquer
Erreur
Soulever les erreurs et refuser d'appliquer le patch
- Toutes erreurs
- Similaire à 'error', mais plus détaillé
+ Toutes les erreurs
+ Similaire à 'Erreur', mais plus détaillé
Fichier de patch :
Selectionner le fichier .patch à appliquer
- Ignorer les espaces blancs
+ Ignorer les changements d'espaces blancs
Pas d'avertissement
- Désactive l'avertissement des espaces blancs terminaux
+ Désactiver l'avertissement sur les espaces blancs terminaux
Appliquer le patch
- Warn
- Affiche les avertissements Outputs warnings for a few such errors, mais applique
- Espace blanc:
- Archive...
- Sauvegarder l'archive vers :
- Selectionner le chemin de l'archive
- Révision:
- Archive
+ Avertissement
+ Affiche des avertissements pour ce type d'erreurs tout en appliquant le patch
+ Espaces blancs :
+ Archiver...
+ Enregistrer l'archive sous :
+ Sélectionnez le chemin du fichier d'archive
+ Révision :
+ Archiver
SourceGit Askpass
FICHIERS PRÉSUMÉS INCHANGÉS
PAS DE FICHIERS PRÉSUMÉS INCHANGÉS
- REMOVE
+ SUPPRIMER
FICHIER BINAIRE NON SUPPORTÉ !!!
- Blame
- BLAME SUR CE FICHIER NON SUPPORTÉ !!!
+ Blâme
+ LE BLÂME SUR CE FICHIER N'EST PAS SUPPORTÉ!!!
Checkout ${0}$...
Comparer avec la branche
Comparer avec HEAD
- Comparer avec le Worktree
+ Comparer avec le worktree
Copier le nom de la branche
Supprimer ${0}$...
Supprimer {0} branches sélectionnées
Rejeter tous les changements
- Fast-Forward to ${0}$
- Git Flow - Finir ${0}$
- Merger ${0}$ dans ${1}$...
- Pull ${0}$
- Pull ${0}$ dans ${1}$...
- Push ${0}$
- Rebase ${0}$ sur ${1}$...
+ Fast-Forward vers ${0}$
+ Git Flow - Terminer ${0}$
+ Fusionner ${0}$ dans ${1}$...
+ Tirer ${0}$
+ Tirer ${0}$ dans ${1}$...
+ Pousser ${0}$
+ Rebaser ${0}$ sur ${1}$...
Renommer ${0}$...
Définir la branche de suivi
- Unset Upstream
+ Ne plus suivre la branche distante
Comparer les branches
Octets
ANNULER
- Reset vers cette révision
- Reset vers la révision parente
+ Réinitialiser à la révision parente
+ Réinitialiser à cette révision
CHANGER LE MODE D'AFFICHAGE
Afficher comme liste de dossiers/fichiers
Afficher comme liste de chemins
Afficher comme arborescence
Checkout Branch
- Checkout Commit
- Avertissement: un checkout vers un commit aboutiera vers un HEAD détaché
+ Checkout ce commit
Commit :
- Branche :
- Changement locaux:
- Rejeter
+ Avertissement: un checkout vers un commit aboutiera vers un HEAD détaché
+ Changements locaux :
+ Annuler
Ne rien faire
- Stash et Réappliquer
- Cherry-Pick
- Commit(s) :
+ Mettre en stash et réappliquer
+ Branche :
+ Cherry-Pick de ce commit
+ Commit :
Commit tous les changements
- Vider les Stashes
- Voulez-vous vider tous les stashes. Êtes-vous sûr de vouloir continuer ?
- Cloner le dépôt distant
+ Cherry Pick
+ Supprimer les stashes
+ Vous essayez de supprimer tous les stashes. Êtes-vous sûr de vouloir continuer ?
Paramètres supplémentaires :
Arguments additionnels au clônage. Optionnel.
Nom local :
Nom de dépôt. Optionnel.
Dossier parent :
URL du dépôt :
+ Cloner le dépôt distant
FERMER
Éditeur
+ Changer de commit
Cherry-Pick ce commit
- Checkout ce commit
Comparer avec HEAD
- Comparer avec le Worktree
- Copier les infos
+ Comparer avec le worktree
+ Copier les informations
Copier le SHA
Rebase interactif de ${0}$ ici
- Rebase ${0}$ ici
- Reset ${0}$ ici
+ Rebaser ${0}$ ici
+ Réinitialiser ${0}$ ici
Annuler le commit
Reformuler
- Sauver en tant que patch...
+ Enregistrer en tant que patch...
Squash dans le parent
CHANGEMENTS
- Chercher des changements...
+ Rechercher les changements...
FICHIERS
- Fichiers LFS
+ Fichier LFS
Sous-module
- INFORMATION
+ INFORMATIONS
AUTEUR
CHANGÉ
COMMITTER
+ Vérifier les références contenant ce commit
+ LE COMMIT EST CONTENU PAR
Afficher seulement les 100 premiers changements. Voir tous les changements dans l'onglet CHANGEMENTS.
MESSAGE
PARENTS
REFS
SHA
- Enter le sujet du commit
Description
- Repository Configurer
+ Entrez le message du commit
+ Configurer le dépôt
MODÈLE DE COMMIT
- Nom de modèle:
Contenu de modèle:
- Addresse e-mail
- Addresse e-mail
+ Nom de modèle:
+ Adresse e-mail
+ Adresse e-mail
GIT
- ISSUE TRACKER
- Add Sample Github Rule
- Add Sample Jira Rule
+ Fetch les dépôts distants automatiquement
+ minute(s)
+ SUIVI DES PROBLÈMES
+ Ajouter une règle d'exemple Github
+ Ajouter une règle d'exemple Jira
Nouvelle règle
Issue Regex Expression:
Nom de règle :
+ Veuillez utiliser $1, $2 pour accéder aux valeurs des groupes regex.
URL résultant:
-
- Please use $1, $2 to access regex groups values.
- HTTP Proxy
- HTTP proxy used by this repository
+ Proxy HTTP
+ Proxy HTTP utilisé par ce dépôt
Nom d'utilisateur
Nom d'utilisateur pour ce dépôt
Copier
+ Copier tout le texte
+ Copier le nom de fichier
COPIER LE MESSAGE
Copier le chemin
- Copier le nom de fichier
Créer une branche...
Basé sur :
-
- Check out the created branch
+ Passer à la branche créée
Changements locaux :
Rejeter
Ne rien faire
Stash & Réappliquer
- Nouveau nom de branche:
- Enter le nom de branche.
+ Nom de la nouvelle branche :
+ Entrez le nom de la branche.
Créer une branche locale
Créer un tag...
Nouveau tag à :
Signature GPG
- Message de tag :
+ Message du tag :
Optionnel.
- Nom de tag :
- Format Recommendé : v1.0.0-alpha
- Push vers tous les arpès création
+ Nom du tag :
+ Format recommandé : v1.0.0-alpha
+ Pousser sur tous les dépôts distants après création
Créer un nouveau tag
- Kind:
+ Type :
annoté
-
- lightweight
+ léger
Maintenir Ctrl pour commencer directement
Couper
Supprimer la branche
@@ -187,39 +190,39 @@
Cible :
Confirmer la suppression du groupe
Confirmer la suppression du dépôt
- Supprimer le Sous-module
+ Supprimer le sous-module
Chemin du sous-module :
Supprimer le tag
- Tag:
- Delete from remote repositories
+ Tag :
+ Supprimer des dépôts distants
DIFF BINAIRE
NOUVEAU
ANCIEN
Copier
Mode de fichier changé
- LFS OBJECT CHANGE
+ CHANGEMENT D'OBJET LFS
Différence suivante
PAS DE CHANGEMENT OU SEULEMENT EN FIN DE LIGNE
Différence précédente
+ Afficher les caractères invisibles
Diff côte-à-côte
SOUS-MODULE
NOUVEAU
+ Permuter
Coloration syntaxique
Retour à la ligne
- Ouvrir dans l'outil de merge
+ Ouvrir dans l'outil de fusion
Réduit le nombre de ligne visibles
Augmente le nombre de ligne visibles
- SÉLECTIONNER UN FICHIER POUR VOIR LES CHANGEMENTS
- Afficher les caractères invisibles
- Permuter
- Ouvrir dans l'outil de merge
+ SÉLECTIONNEZ UN FICHIER POUR VOIR LES CHANGEMENTS
+ Ouvrir dans l'outil de fusion
Rejeter les changements
Tous les changements dans la copie de travail.
Changements :
{0} changements seront rejetés
Vous ne pouvez pas annuler cette action !!!
- Bookmark:
- Nouveau nom:
+ Signet :
+ Nouveau nom :
Cible :
Éditer le groupe sélectionné
Éditer le dépôt sélectionné
@@ -227,28 +230,28 @@
Fetch
Fetch toutes les branches distantes
Fetch sans les tags
- Purger les branches distantes mortes
+ Élaguer les branches mortes distantes
Remote :
- Fetch des changements distants
+ Récupérer les changements distants
Présumer inchangé
Rejeter...
Rejeter {0} fichiers...
Rejeter les changements dans les lignes sélectionnées
- Ouvrir l'outil de merge externe
- Savegarder le patch sous...
- Stage
-
- Stage {0} files
- Stage Changes in Selected Line(s)
+ Ouvrir l'outil de fusion externe
+ Enregistrer en tant que patch...
+ Indexer
+ Indexer {0} fichiers
+ Indexer les changements dans les lignes sélectionnées
Stash...
Stash {0} fichiers...
- Unstage
- Unstage {0} files
- Unstage Changes in Selected Line(s)
- Use Theirs (checkout --theirs)
- Use Mine (checkout --ours)
- File History
- FILTER
+ Désindexer
+ Désindexer {0} fichiers
+ Désindexer les changements dans les lignes sélectionnées
+ Utiliser les miennes (checkout --ours)
+ Utiliser les leurs (checkout --theirs)
+ Historique du fichier
+ CONTENU
+ FILTRER
Git-Flow
Development Branch:
Feature:
@@ -397,9 +400,6 @@
Historique de commits
Guide de longueur du sujet
GIT
- Fetch les dépôts distants automatiquement
- Intervalle de fetch auto
- minute(s)
Activer auto CRLF
Répertoire de clônage par défaut
E-mail utilsateur
@@ -492,7 +492,6 @@
ADD REMOTE
RESOLVE
Search Commit
- Search By
File
Message
SHA
@@ -552,7 +551,6 @@
COMMITTER
MONTH
WEEK
- YEAR
COMMITS:
AUTHORS:
SUBMODULES
@@ -599,7 +597,6 @@
COMMIT
COMMIT & PUSH
Modèles/Historiques
- CTRL + Entrée
CONFLITS DÉTECTÉS
LES CONFLITS DE FICHIER SONT RÉSOLUS
INCLURE LES FICHIERS NON-SUIVIS
diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml
index 96cdda3e..90341b44 100644
--- a/src/Resources/Locales/pt_BR.axaml
+++ b/src/Resources/Locales/pt_BR.axaml
@@ -132,6 +132,8 @@
Endereço de Email
Endereço de email
GIT
+ Buscar remotos automaticamente
+ Minuto(s)
RASTREADOR DE PROBLEMAS
Adicionar Regra de Exemplo do Github
Adicionar Regra de Exemplo do Jira
@@ -391,9 +393,6 @@
Commits do Histórico
Comprimento do Guia de Assunto
GIT
- Buscar remotos automaticamente
- Intervalo de Busca Automática
- Minuto(s)
Habilitar Auto CRLF
Diretório Padrão de Clone
E-mail do Usuário
@@ -485,7 +484,6 @@
ADICIONAR REMOTO
RESOLVER
Pesquisar Commit
- Pesquisar Por
Arquivo
Mensagem
SHA
@@ -545,7 +543,6 @@
COMMITTER
MÊS
SEMANA
- ANO
COMMITS:
AUTORES:
SUBMÓDULOS
@@ -592,7 +589,6 @@
COMMIT
COMMIT & PUSH
Template/Histories
- CTRL + Enter
CONFLITOS DETECTADOS
CONFLITOS DE ARQUIVOS RESOLVIDOS
INCLUIR ARQUIVOS NÃO RASTREADOS
diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml
index 56b01b0b..edfc8b08 100644
--- a/src/Resources/Locales/ru_RU.axaml
+++ b/src/Resources/Locales/ru_RU.axaml
@@ -1,4 +1,7 @@
+
+
+
О программе
О SourceGit
• Сборка с
@@ -118,7 +121,7 @@
ИНФОРМАЦИЯ
АВТОР
ИЗМЕНЁННЫЙ
- ФИКСАТОР
+ ИСПОЛНИТЕЛЬ
Проверить ссылки, содержащие эту фиксацию
ФИКСАЦИЯ СОДЕРЖИТСЯ В
Отображаются только первые 100 изменений. Смотрите все изменения на вкладке ИЗМЕНЕНИЯ.
@@ -136,6 +139,8 @@
Адрес электронной почты
Адрес электронной почты
GIT
+ Автоматическое извлечение внешних хранилищ
+ Минут(а/ы)
ОТСЛЕЖИВАНИЕ ПРОБЛЕМ
Добавить пример правила для Git
Добавить пример правила Jira
@@ -176,8 +181,8 @@
Выложить на все внешние хранилища после создания
Создать новый тег
Добрый:
- аннотированный
- лёгкий
+ Аннотированный
+ Лёгкий
Удерживайте Ctrl, чтобы начать непосредственно
Вырезать
Удалить ветку
@@ -289,10 +294,11 @@
Показать блокировки
Нет заблокированных файлов
Блокировка
+ Показывать только мои блокировки
Блокировки ХБФ
- Разблокировка
- Принудительная разблокировка
- Обрезка
+ Разблокировать
+ Принудительно разблокировать
+ Обрезать
Запустите `git lfs prune", чтобы удалить старые файлы ХБФ из локального хранилища
Забрать
Забрать объекты ХБФ
@@ -324,6 +330,7 @@
ХРАНИЛИЩЕ
Фиксация подготовленных изменений
Фиксировать и выложить подготовленные изменения
+ Подготовить все изменения и зафиксировать
Отклонить выбранные изменения
Режим доски (по-умолчанию)
Принудительно перезагрузить этот репозиторий
@@ -342,10 +349,10 @@
Отклонить
Инициализировать хранилище
Путь:
- Выполняется частичный забор. Нажмите 'Отбой' для восстановления заголовка.
- Выполняет запрос слияния. Нажмите 'Отбой' для восстановления заголовка.
- Выполняется перенос. Нажмите 'Отбой' для восстановления заголовка.
- Выполняется возврат. Нажмите 'Отбой' для восстановления заголовка.
+ Выполняется частичный забор. Нажмите 'Отказ' для восстановления заголовка.
+ Выполняет запрос слияния. Нажмите 'Отказ' для восстановления заголовка.
+ Выполняется перенос. Нажмите 'Отказ' для восстановления заголовка.
+ Выполняется возврат. Нажмите 'Отказ' для восстановления заголовка.
Интерактивное перемещение
целевая ветка:
На:
@@ -409,9 +416,6 @@
История фиксаций
Длина темы фиксации
GIT
- Автоматическое извлечение внешних хранилищ
- Интервал автоматического извлечения
- Минут(а/ы)
Включить автозавершение CRLF
Каталог клонирования по-умолчанию
Электроная почта пользователя
@@ -488,14 +492,15 @@
Новое имя:
Уникальное имя для данной ветки
Ветка:
- ОТБОЙ
+ Отказ
+ Автоматическое извлечение изменений с внешних хранилищ...
Очистка (Сбор мусора и удаление)
Запустить команду `git gc` для данного хранилища.
Очистить всё
Настройка этого хранилища
ПРОДОЛЖИТЬ
Открыть в файловом менеджере
- поиск веток, тегов и подмодулей
+ Поиск веток, тегов и подмодулей
ОТФИЛЬТРОВАНО ОТ:
ЛОКАЛЬНЫЕ ВЕТКИ
Навигация по заголовку
@@ -508,11 +513,11 @@
ДОБАВИТЬ ВНЕШНЕЕ ХРАНИЛИЩЕ
РАЗРЕШИТЬ
Поиск фиксации
- Поиск по
Файл
Сообщение
SHA
- Автор и фиксатор
+ Автор и исполнитель
+ Текущая ветка
Показать теги как дерево
Статистики
ПОДМОДУЛИ
@@ -523,7 +528,7 @@
Открыть в терминале
РАБОЧИЕ ДЕРЕВЬЯ
ДОБАВИТЬ РАБОЧЕЕ ДЕРЕВО
- ОБРЕЗКА
+ ОБРЕЗАТЬ
Адрес хранилища Git
Сбросить текущую втеку до версии
Режим сброса:
@@ -568,12 +573,12 @@
ОТЛОЖЕННЫЕ
Статистики
ФИКСАЦИИ
- ФИКСАТОРЫ
+ ИСПОЛНИТЕЛИ
МЕСЯЦ
НЕДЕЛЯ
- ГОД
ФИКСАЦИИ:
АВТОРЫ:
+ ОБЗОР
ПОДМОДУЛИ
Добавить подмодули
Копировать относительный путь
@@ -581,7 +586,7 @@
Открыть подмодуль хранилища
Относительный путь:
Относительный каталог для хранения подмодуля.
- удалить подмодуль
+ Удалить подмодуль
ОК
Копировать имя тега
Удалить ${0}$...
@@ -617,10 +622,11 @@
Изменить
Автоподготовка
Теперь вы можете подготовитть этот файл.
- ФИКСАЦИЯ
- ФИКСАЦИЯ и ОТПРАВКА
+ ЗАФИКСИРОВАТЬ
+ ЗАФИКСИРОВАТЬ и ОТПРАВИТЬ
Шаблон/Истории
- CTRL + Enter
+ Запуск события щелчка
+ Подготовить все изменения и зафиксировать
ОБНАРУЖЕНЫ КОНФЛИКТЫ
КОНФЛИКТЫ ФАЙЛОВ РАЗРЕШЕНЫ
ВКЛЮЧИТЬ НЕОТСЛЕЖИВАЕМЫЕ ФАЙЛЫ
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index 6d3474b6..5b753a08 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -139,6 +139,8 @@
电子邮箱
邮箱地址
GIT配置
+ 启用定时自动拉取远程更新
+ 分钟
ISSUE追踪
新增匹配Github Issue规则
新增匹配Jira规则
@@ -292,6 +294,7 @@
显示LFS对象锁
没有锁定的LFS文件
锁定
+ 仅显示被我锁定的文件
LFS对象锁状态
解锁
强制解锁
@@ -327,6 +330,7 @@
仓库页面快捷键
提交暂存区更改
提交暂存区更改并推送
+ 自动暂存全部变更并提交
丢弃选中的更改
切换左边栏为分支/标签等显示模式(默认)
重新加载仓库状态
@@ -408,9 +412,6 @@
最大历史提交数
SUBJECT字数检测
GIT配置
- 启用定时自动拉取远程更新
- 自动拉取间隔
- 分钟
自动换行转换
默认克隆路径
邮箱
@@ -487,6 +488,7 @@
新的分支名不能与现有分支名相同
分支 :
终止合并
+ 自动拉取远端变更中...
清理本仓库(GC)
本操作将执行`git gc`命令。
清空过滤规则
@@ -506,11 +508,11 @@
添加远程
解决冲突
查找提交
- 搜索途径
文件
提交信息
提交指纹
作者及提交者
+ 仅在当前分支中查找
以树型结构展示
提交统计
子模块列表
@@ -569,9 +571,9 @@
提交者
本月
本周
- 最近一年
提交次数:
贡献者人数:
+ 总览
子模块
添加子模块
复制路径
@@ -618,7 +620,8 @@
提交
提交并推送
历史输入/模板
- CTRL + Enter
+ 触发点击事件
+ 自动暂存所有变更并提交
检测到冲突
文件冲突已解决
显示未跟踪文件
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index 7d31e33e..733b3f39 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -139,6 +139,8 @@
電子郵件
電子郵件地址
Git 設定
+ 啟用定時自動提取 (fetch) 遠端更新
+ 分鐘
Issue 追蹤
新增符合 GitHub Issue 規則
新增符合 Jira 規則
@@ -292,6 +294,7 @@
顯示 LFS 物件鎖
沒有鎖定的 LFS 物件
鎖定
+ 僅顯示被我鎖定的檔案
LFS 物件鎖
解鎖
強制解鎖
@@ -327,6 +330,7 @@
存放庫頁面快速鍵
提交暫存區變更
提交暫存區變更並推送
+ 自動暫存全部變更並提交
捨棄選取的變更
切換左邊欄為分支/標籤等顯示模式 (預設)
強制重新載入存放庫
@@ -412,9 +416,6 @@
最大歷史提交數
提交標題字數偵測
Git 設定
- 啟用定時自動提取 (fetch) 遠端更新
- 自動提取間隔
- 分鐘
自動換行轉換
預設複製 (clone) 路徑
電子郵件
@@ -492,6 +493,7 @@
新的分支名稱不能與現有分支名稱相同
分支:
中止
+ 自動提取遠端變更中...
清理本存放庫 (GC)
本操作將執行 `git gc` 命令。
清空篩選規則
@@ -511,11 +513,11 @@
新增遠端
解決衝突
搜尋提交
- 搜尋方式
檔案
提交訊息
提交編號
作者及提交者
+ 僅搜尋當前分支
以樹型結構展示
提交統計
子模組列表
@@ -574,9 +576,9 @@
提交者
本月
本週
- 最近一年
提交次數:
貢獻者人數:
+ 總覽
子模組
新增子模組
複製路徑
@@ -623,7 +625,8 @@
提 交
提交並推送
歷史輸入/範本
- CTRL + Enter
+ 觸發點擊事件
+ 自動暫存全部變更並提交
檢測到衝突
檔案衝突已解決
顯示未追蹤檔案
diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml
index 6de1a479..f0743baf 100644
--- a/src/Resources/Styles.axaml
+++ b/src/Resources/Styles.axaml
@@ -192,6 +192,7 @@
+
@@ -962,12 +963,13 @@
-
+
+
diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj
index 53bc0176..036dcb57 100644
--- a/src/SourceGit.csproj
+++ b/src/SourceGit.csproj
@@ -45,7 +45,8 @@
-
+
+
diff --git a/src/ViewModels/AddRemote.cs b/src/ViewModels/AddRemote.cs
index 104a4ed6..f2e71f4c 100644
--- a/src/ViewModels/AddRemote.cs
+++ b/src/ViewModels/AddRemote.cs
@@ -104,6 +104,7 @@ namespace SourceGit.ViewModels
}
CallUIThread(() =>
{
+ _repo.MarkFetched();
_repo.MarkBranchesDirtyManually();
_repo.SetWatcherEnabled(true);
});
diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs
index e3e65da0..cf04784e 100644
--- a/src/ViewModels/BranchTreeNode.cs
+++ b/src/ViewModels/BranchTreeNode.cs
@@ -15,10 +15,15 @@ namespace SourceGit.ViewModels
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 Children { get; private set; } = new List();
+ public bool IsFiltered
+ {
+ get => _isFiltered;
+ set => SetProperty(ref _isFiltered, value);
+ }
+
public bool IsExpanded
{
get => _isExpanded;
@@ -46,6 +51,7 @@ namespace SourceGit.ViewModels
get => Backend is Models.Branch b ? b.FriendlyName : null;
}
+ private bool _isFiltered = false;
private bool _isExpanded = false;
private CornerRadius _cornerRadius = new CornerRadius(4);
diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs
index 68311b15..5661e2ed 100644
--- a/src/ViewModels/Checkout.cs
+++ b/src/ViewModels/Checkout.cs
@@ -62,7 +62,16 @@ namespace SourceGit.ViewModels
rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}");
}
- CallUIThread(() => _repo.SetWatcherEnabled(true));
+ CallUIThread(() =>
+ {
+ var b = _repo.Branches.Find(x => x.IsLocal && x.Name == Branch);
+ if (b != null)
+ _repo.AutoAddBranchFilterPostCheckout(b);
+
+ _repo.MarkBranchesDirtyManually();
+ _repo.SetWatcherEnabled(true);
+ });
+
return rs;
});
}
diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs
index c89baacf..3692be10 100644
--- a/src/ViewModels/CommitDetail.cs
+++ b/src/ViewModels/CommitDetail.cs
@@ -321,6 +321,9 @@ namespace SourceGit.ViewModels
menu.Items.Add(resetToFirstParent);
menu.Items.Add(new MenuItem { Header = "-" });
+ if (File.Exists(Path.Combine(fullPath)))
+ TryToAddContextMenuItemsForGitLFS(menu, change.Path);
+
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
@@ -346,6 +349,7 @@ namespace SourceGit.ViewModels
public ContextMenu CreateRevisionFileContextMenu(Models.Object file)
{
+ var menu = new ContextMenu();
var fullPath = Path.Combine(_repo.FullPath, file.Path);
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
@@ -385,6 +389,10 @@ namespace SourceGit.ViewModels
ev.Handled = true;
};
+ menu.Items.Add(explore);
+ menu.Items.Add(saveAs);
+ menu.Items.Add(new MenuItem() { Header = "-" });
+
var history = new MenuItem();
history.Header = App.Text("FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
@@ -406,6 +414,10 @@ namespace SourceGit.ViewModels
ev.Handled = true;
};
+ menu.Items.Add(history);
+ menu.Items.Add(blame);
+ menu.Items.Add(new MenuItem() { Header = "-" });
+
var resetToThisRevision = new MenuItem();
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
@@ -428,6 +440,13 @@ namespace SourceGit.ViewModels
ev.Handled = true;
};
+ menu.Items.Add(resetToThisRevision);
+ menu.Items.Add(resetToFirstParent);
+ menu.Items.Add(new MenuItem() { Header = "-" });
+
+ if (File.Exists(Path.Combine(fullPath)))
+ TryToAddContextMenuItemsForGitLFS(menu, file.Path);
+
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
@@ -446,16 +465,6 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
- var menu = new ContextMenu();
- menu.Items.Add(explore);
- menu.Items.Add(saveAs);
- menu.Items.Add(new MenuItem() { Header = "-" });
- menu.Items.Add(history);
- menu.Items.Add(blame);
- menu.Items.Add(new MenuItem() { Header = "-" });
- menu.Items.Add(resetToThisRevision);
- menu.Items.Add(resetToFirstParent);
- menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFileName);
return menu;
@@ -532,6 +541,90 @@ namespace SourceGit.ViewModels
}
}
+ private void TryToAddContextMenuItemsForGitLFS(ContextMenu menu, string path)
+ {
+ var lfsEnabled = new Commands.LFS(_repo.FullPath).IsEnabled();
+ if (!lfsEnabled)
+ return;
+
+ var lfs = new MenuItem();
+ lfs.Header = App.Text("GitLFS");
+ lfs.Icon = App.CreateMenuIcon("Icons.LFS");
+
+ var lfsLock = new MenuItem();
+ lfsLock.Header = App.Text("GitLFS.Locks.Lock");
+ lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
+ lfsLock.IsEnabled = _repo.Remotes.Count > 0;
+ if (_repo.Remotes.Count == 1)
+ {
+ lfsLock.Click += async (_, e) =>
+ {
+ var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(_repo.Remotes[0].Name, path));
+ if (succ)
+ App.SendNotification(_repo.FullPath, $"Lock file \"{path}\" successfully!");
+
+ e.Handled = true;
+ };
+ }
+ else
+ {
+ foreach (var remote in _repo.Remotes)
+ {
+ var remoteName = remote.Name;
+ var lockRemote = new MenuItem();
+ lockRemote.Header = remoteName;
+ lockRemote.Click += async (_, e) =>
+ {
+ var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(remoteName, path));
+ if (succ)
+ App.SendNotification(_repo.FullPath, $"Lock file \"{path}\" successfully!");
+
+ e.Handled = true;
+ };
+ lfsLock.Items.Add(lockRemote);
+ }
+ }
+ lfs.Items.Add(lfsLock);
+
+ var lfsUnlock = new MenuItem();
+ lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
+ lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
+ lfsUnlock.IsEnabled = _repo.Remotes.Count > 0;
+ if (_repo.Remotes.Count == 1)
+ {
+ lfsUnlock.Click += async (_, e) =>
+ {
+ var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(_repo.Remotes[0].Name, path, false));
+ if (succ)
+ App.SendNotification(_repo.FullPath, $"Unlock file \"{path}\" successfully!");
+
+ e.Handled = true;
+ };
+ }
+ else
+ {
+ foreach (var remote in _repo.Remotes)
+ {
+ var remoteName = remote.Name;
+ var unlockRemote = new MenuItem();
+ unlockRemote.Header = remoteName;
+ unlockRemote.Click += async (_, e) =>
+ {
+ var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(remoteName, path, false));
+ if (succ)
+ App.SendNotification(_repo.FullPath, $"Unlock file \"{path}\" successfully!");
+
+ e.Handled = true;
+ };
+ lfsUnlock.Items.Add(unlockRemote);
+ }
+ }
+ lfs.Items.Add(lfsUnlock);
+
+ menu.Items.Add(lfs);
+ menu.Items.Add(new MenuItem() { Header = "-" });
+ }
+
[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();
diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs
index e6d2edc1..2c925c03 100644
--- a/src/ViewModels/CreateBranch.cs
+++ b/src/ViewModels/CreateBranch.cs
@@ -126,6 +126,15 @@ namespace SourceGit.ViewModels
CallUIThread(() =>
{
+ if (CheckoutAfterCreated)
+ {
+ _repo.AutoAddBranchFilterPostCheckout(new Models.Branch()
+ {
+ FullName = $"refs/heads/{_name}",
+ Upstream = BasedOn is Models.Branch { IsLocal: false } remoteBranch ? remoteBranch.FullName : string.Empty,
+ });
+ }
+
_repo.MarkBranchesDirtyManually();
_repo.SetWatcherEnabled(true);
});
diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs
index fa1986b3..e00669bf 100644
--- a/src/ViewModels/Fetch.cs
+++ b/src/ViewModels/Fetch.cs
@@ -62,7 +62,12 @@ namespace SourceGit.ViewModels
new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, Prune, NoTags, SetProgressDescription).Exec();
}
- CallUIThread(() => _repo.SetWatcherEnabled(true));
+ CallUIThread(() =>
+ {
+ _repo.MarkFetched();
+ _repo.SetWatcherEnabled(true);
+ });
+
return true;
});
}
diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs
index 9563896b..035cadfd 100644
--- a/src/ViewModels/FileHistories.cs
+++ b/src/ViewModels/FileHistories.cs
@@ -41,12 +41,12 @@ namespace SourceGit.ViewModels
}
}
- public int ViewMode
+ public bool IsViewContent
{
- get => _viewMode;
+ get => _isViewContent;
set
{
- if (SetProperty(ref _viewMode, value))
+ if (SetProperty(ref _isViewContent, value))
RefreshViewContent();
}
}
@@ -93,10 +93,10 @@ namespace SourceGit.ViewModels
return;
}
- if (_viewMode == 0)
- SetViewContentAsDiff();
- else
+ if (_isViewContent)
SetViewContentAsRevisionFile();
+ else
+ SetViewContentAsDiff();
}
private void SetViewContentAsRevisionFile()
@@ -197,7 +197,7 @@ namespace SourceGit.ViewModels
private bool _isLoading = true;
private List _commits = null;
private Models.Commit _selectedCommit = null;
- private int _viewMode = 0;
+ private bool _isViewContent = false;
private object _viewContent = null;
}
}
diff --git a/src/ViewModels/LFSLocks.cs b/src/ViewModels/LFSLocks.cs
index 02b3e9a6..a09e7d1e 100644
--- a/src/ViewModels/LFSLocks.cs
+++ b/src/ViewModels/LFSLocks.cs
@@ -1,6 +1,6 @@
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading.Tasks;
-using Avalonia.Collections;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -9,40 +9,49 @@ namespace SourceGit.ViewModels
{
public class LFSLocks : ObservableObject
{
+ public bool HasValidUserName
+ {
+ get;
+ private set;
+ } = false;
+
public bool IsLoading
{
get => _isLoading;
private set => SetProperty(ref _isLoading, value);
}
- public bool IsEmpty
+ public bool ShowOnlyMyLocks
{
- get => _isEmpty;
- private set => SetProperty(ref _isEmpty, value);
+ get => _showOnlyMyLocks;
+ set
+ {
+ if (SetProperty(ref _showOnlyMyLocks, value))
+ UpdateVisibleLocks();
+ }
}
- public AvaloniaList Locks
+ public List VisibleLocks
{
- get;
- private set;
+ get => _visibleLocks;
+ private set => SetProperty(ref _visibleLocks, value);
}
public LFSLocks(string repo, string remote)
{
_repo = repo;
_remote = remote;
- Locks = new AvaloniaList();
+ _userName = new Commands.Config(repo).Get("user.name");
+
+ HasValidUserName = !string.IsNullOrEmpty(_userName);
Task.Run(() =>
{
- var collect = new Commands.LFS(_repo).Locks(_remote);
+ _cachedLocks = new Commands.LFS(_repo).Locks(_remote);
Dispatcher.UIThread.Invoke(() =>
{
- if (collect.Count > 0)
- Locks.AddRange(collect);
-
+ UpdateVisibleLocks();
IsLoading = false;
- IsEmpty = collect.Count == 0;
});
});
}
@@ -59,17 +68,41 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Invoke(() =>
{
if (succ)
- Locks.Remove(lfsLock);
+ {
+ _cachedLocks.Remove(lfsLock);
+ UpdateVisibleLocks();
+ }
IsLoading = false;
- IsEmpty = Locks.Count == 0;
});
});
}
+ private void UpdateVisibleLocks()
+ {
+ if (!_showOnlyMyLocks)
+ {
+ VisibleLocks = _cachedLocks;
+ }
+ else
+ {
+ var visible = new List();
+ foreach (var lfsLock in _cachedLocks)
+ {
+ if (lfsLock.User == _userName)
+ visible.Add(lfsLock);
+ }
+
+ VisibleLocks = visible;
+ }
+ }
+
private string _repo;
private string _remote;
private bool _isLoading = true;
- private bool _isEmpty = false;
+ private List _cachedLocks = [];
+ private List _visibleLocks = [];
+ private bool _showOnlyMyLocks = false;
+ private string _userName;
}
}
diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs
index 15c22c16..5abf50e8 100644
--- a/src/ViewModels/Launcher.cs
+++ b/src/ViewModels/Launcher.cs
@@ -32,8 +32,8 @@ namespace SourceGit.ViewModels
{
PopupHost.Active = value;
- if (!_ignoreIndexChange && value is { Data: Repository })
- ActiveWorkspace.ActiveIdx = Pages.IndexOf(value);
+ if (!_ignoreIndexChange && value is { Data: Repository repo })
+ ActiveWorkspace.ActiveIdx = ActiveWorkspace.Repositories.IndexOf(repo.FullPath);
}
}
}
@@ -131,10 +131,22 @@ namespace SourceGit.ViewModels
public void MoveTab(LauncherPage from, LauncherPage to)
{
+ _ignoreIndexChange = true;
+
var fromIdx = Pages.IndexOf(from);
var toIdx = Pages.IndexOf(to);
Pages.Move(fromIdx, toIdx);
ActivePage = from;
+
+ ActiveWorkspace.Repositories.Clear();
+ foreach (var p in Pages)
+ {
+ if (p.Data is Repository r)
+ ActiveWorkspace.Repositories.Add(r.FullPath);
+ }
+ ActiveWorkspace.ActiveIdx = ActiveWorkspace.Repositories.IndexOf(from.Node.Id);
+
+ _ignoreIndexChange = false;
}
public void GotoNextTab()
@@ -164,8 +176,9 @@ namespace SourceGit.ViewModels
var last = Pages[0];
if (last.Data is Repository repo)
{
- ActiveWorkspace.Repositories.Remove(repo.FullPath);
- Models.AutoFetchManager.Instance.RemoveRepository(repo.FullPath);
+ ActiveWorkspace.Repositories.Clear();
+ ActiveWorkspace.ActiveIdx = 0;
+
repo.Close();
Welcome.Instance.ClearSearchFilter();
@@ -180,6 +193,7 @@ namespace SourceGit.ViewModels
App.Quit(0);
}
+ _ignoreIndexChange = false;
return;
}
@@ -213,6 +227,8 @@ namespace SourceGit.ViewModels
if (Pages.Count == 1)
return;
+ _ignoreIndexChange = true;
+
var id = ActivePage.Node.Id;
foreach (var one in Pages)
{
@@ -221,12 +237,17 @@ namespace SourceGit.ViewModels
}
Pages = new AvaloniaList { ActivePage };
+ ActiveWorkspace.ActiveIdx = 0;
OnPropertyChanged(nameof(Pages));
+
+ _ignoreIndexChange = false;
GC.Collect();
}
public void CloseRightTabs()
{
+ _ignoreIndexChange = true;
+
var endIdx = Pages.IndexOf(ActivePage);
for (var i = Pages.Count - 1; i > endIdx; i--)
{
@@ -234,6 +255,7 @@ namespace SourceGit.ViewModels
Pages.Remove(Pages[i]);
}
+ _ignoreIndexChange = false;
GC.Collect();
}
@@ -270,8 +292,6 @@ namespace SourceGit.ViewModels
};
repo.Open();
- ActiveWorkspace.AddRepository(repo.FullPath);
- Models.AutoFetchManager.Instance.AddRepository(repo.FullPath, repo.GitDir);
if (page == null)
{
@@ -294,6 +314,16 @@ namespace SourceGit.ViewModels
}
ActivePage = page;
+
+ ActiveWorkspace.Repositories.Clear();
+ foreach (var p in Pages)
+ {
+ if (p.Data is Repository r)
+ ActiveWorkspace.Repositories.Add(r.FullPath);
+ }
+
+ if (!_ignoreIndexChange)
+ ActiveWorkspace.ActiveIdx = ActiveWorkspace.Repositories.IndexOf(node.Id);
}
public void DispatchNotification(string pageId, string message, bool isError)
@@ -490,7 +520,6 @@ namespace SourceGit.ViewModels
if (removeFromWorkspace)
ActiveWorkspace.Repositories.Remove(repo.FullPath);
- Models.AutoFetchManager.Instance.RemoveRepository(repo.FullPath);
repo.Close();
}
diff --git a/src/ViewModels/LauncherPage.cs b/src/ViewModels/LauncherPage.cs
index 71d4a634..92114f5e 100644
--- a/src/ViewModels/LauncherPage.cs
+++ b/src/ViewModels/LauncherPage.cs
@@ -44,6 +44,14 @@ namespace SourceGit.ViewModels
return _node.Id;
}
+ public override bool IsInProgress()
+ {
+ if (_data is Repository { IsAutoFetching: true })
+ return true;
+
+ return base.IsInProgress();
+ }
+
public void CopyPath()
{
if (_node.IsRepository)
diff --git a/src/ViewModels/PopupHost.cs b/src/ViewModels/PopupHost.cs
index 1a4c8bb2..01d1158c 100644
--- a/src/ViewModels/PopupHost.cs
+++ b/src/ViewModels/PopupHost.cs
@@ -18,7 +18,7 @@ namespace SourceGit.ViewModels
public static bool CanCreatePopup()
{
- return Active != null && (Active._popup == null || !Active._popup.InProgress);
+ return Active?.IsInProgress() != true;
}
public static void ShowPopup(Popup popup)
@@ -40,6 +40,11 @@ namespace SourceGit.ViewModels
return string.Empty;
}
+ public virtual bool IsInProgress()
+ {
+ return _popup is { InProgress: true };
+ }
+
public async void ProcessPopup()
{
if (_popup != null)
diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs
index ef63d61e..68966f5d 100644
--- a/src/ViewModels/Preference.cs
+++ b/src/ViewModels/Preference.cs
@@ -216,35 +216,6 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _gitDefaultCloneDir, value);
}
- public bool GitAutoFetch
- {
- get => Models.AutoFetchManager.Instance.IsEnabled;
- set
- {
- if (Models.AutoFetchManager.Instance.IsEnabled != value)
- {
- Models.AutoFetchManager.Instance.IsEnabled = value;
- OnPropertyChanged();
- }
- }
- }
-
- public int? GitAutoFetchInterval
- {
- get => Models.AutoFetchManager.Instance.Interval;
- set
- {
- if (value is null || value < 1)
- return;
-
- if (Models.AutoFetchManager.Instance.Interval != value)
- {
- Models.AutoFetchManager.Instance.Interval = (int)value;
- OnPropertyChanged();
- }
- }
- }
-
public int ShellOrTerminal
{
get => _shellOrTerminal;
@@ -337,6 +308,12 @@ namespace SourceGit.ViewModels
}
}
+ public uint StatisticsSampleColor
+ {
+ get => _statisticsSampleColor;
+ set => SetProperty(ref _statisticsSampleColor, value);
+ }
+
public List RepositoryNodes
{
get;
@@ -621,5 +598,7 @@ namespace SourceGit.ViewModels
private int _shellOrTerminal = -1;
private int _externalMergeToolType = 0;
private string _externalMergeToolPath = string.Empty;
+
+ private uint _statisticsSampleColor = 0xFF00FF00;
}
}
diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs
index 75c0f47a..49c3cb19 100644
--- a/src/ViewModels/Pull.cs
+++ b/src/ViewModels/Pull.cs
@@ -140,6 +140,8 @@ namespace SourceGit.ViewModels
if (!rs)
return false;
+ _repo.MarkFetched();
+
// Use merge/rebase instead of pull as fetch is done manually.
if (UseRebase)
{
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index 42d26f94..0667c546 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
+using System.Threading;
using System.Threading.Tasks;
using Avalonia.Collections;
@@ -209,6 +210,12 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _isSearchLoadingVisible, value);
}
+ public bool OnlySearchCommitsInCurrentBranch
+ {
+ get => _onlySearchCommitsInCurrentBranch;
+ set => SetProperty(ref _onlySearchCommitsInCurrentBranch, value);
+ }
+
public int SearchCommitFilterType
{
get => _searchCommitFilterType;
@@ -323,6 +330,12 @@ namespace SourceGit.ViewModels
}
}
+ public bool IsAutoFetching
+ {
+ get;
+ private set;
+ }
+
public void Open()
{
var settingsFile = Path.Combine(_gitDir, "sourcegit.settings");
@@ -359,6 +372,7 @@ namespace SourceGit.ViewModels
_inProgressContext = null;
_hasUnsolvedConflicts = false;
+ _autoFetchTimer = new Timer(AutoFetchImpl, null, 5000, 5000);
RefreshAll();
}
@@ -377,6 +391,9 @@ namespace SourceGit.ViewModels
}
_settings = null;
+ _autoFetchTimer.Dispose();
+ _autoFetchTimer = null;
+
_watcher?.Dispose();
_histories.Cleanup();
_workingCopy.Cleanup();
@@ -578,13 +595,13 @@ namespace SourceGit.ViewModels
visible.Add(commit);
break;
case 1:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByUser).Result();
+ visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByUser, _onlySearchCommitsInCurrentBranch).Result();
break;
case 2:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByMessage).Result();
+ visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByMessage, _onlySearchCommitsInCurrentBranch).Result();
break;
case 3:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByFile).Result();
+ visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByFile, _onlySearchCommitsInCurrentBranch).Result();
break;
}
@@ -628,6 +645,11 @@ namespace SourceGit.ViewModels
_watcher.MarkWorkingCopyDirtyManually();
}
+ public void MarkFetched()
+ {
+ _lastFetchTime = DateTime.Now;
+ }
+
public void NavigateToCommit(string sha)
{
if (_histories != null)
@@ -643,20 +665,49 @@ namespace SourceGit.ViewModels
NavigateToCommit(_currentBranch.Head);
}
- public void UpdateFilter(string filter, bool toggle)
+ public void AutoAddBranchFilterPostCheckout(Models.Branch local)
+ {
+ if (_settings.Filters.Count == 0 || _settings.Filters.Contains(local.FullName))
+ return;
+
+ var hasLeft = false;
+ foreach (var b in _branches)
+ {
+ if (!b.FullName.Equals(local.FullName, StringComparison.Ordinal) &&
+ !b.FullName.Equals(local.Upstream, StringComparison.Ordinal) &&
+ !_settings.Filters.Contains(b.FullName))
+ {
+ hasLeft = true;
+ break;
+ }
+ }
+
+ if (!hasLeft)
+ _settings.Filters.Clear();
+ else if (string.IsNullOrEmpty(local.Upstream) || _settings.Filters.Contains(local.Upstream))
+ _settings.Filters.Add(local.FullName);
+ else
+ _settings.Filters.AddRange([local.FullName, local.Upstream]);
+ }
+
+ public void UpdateFilters(List filters, bool toggle)
{
var changed = false;
if (toggle)
{
- if (!_settings.Filters.Contains(filter))
+ foreach (var filter in filters)
{
- _settings.Filters.Add(filter);
- changed = true;
+ if (!_settings.Filters.Contains(filter))
+ {
+ _settings.Filters.Add(filter);
+ changed = true;
+ }
}
}
else
{
- changed = _settings.Filters.Remove(filter);
+ foreach (var filter in filters)
+ changed |= _settings.Filters.Remove(filter);
}
if (changed)
@@ -837,32 +888,14 @@ namespace SourceGit.ViewModels
var hasUnsolvedConflict = _workingCopy.SetData(changes);
var inProgress = null as InProgressContext;
- var rebaseMergeFolder = Path.Combine(_gitDir, "rebase-merge");
- var rebaseApplyFolder = Path.Combine(_gitDir, "rebase-apply");
if (File.Exists(Path.Combine(_gitDir, "CHERRY_PICK_HEAD")))
- {
inProgress = new CherryPickInProgress(_fullpath);
- }
- else if (File.Exists(Path.Combine(_gitDir, "REBASE_HEAD")) && Directory.Exists(rebaseMergeFolder))
- {
+ else if (File.Exists(Path.Combine(_gitDir, "REBASE_HEAD")) && Directory.Exists(Path.Combine(_gitDir, "rebase-merge")))
inProgress = new RebaseInProgress(this);
- }
else if (File.Exists(Path.Combine(_gitDir, "REVERT_HEAD")))
- {
inProgress = new RevertInProgress(_fullpath);
- }
else if (File.Exists(Path.Combine(_gitDir, "MERGE_HEAD")))
- {
inProgress = new MergeInProgress(_fullpath);
- }
- else
- {
- if (Directory.Exists(rebaseMergeFolder))
- Directory.Delete(rebaseMergeFolder, true);
-
- if (Directory.Exists(rebaseApplyFolder))
- Directory.Delete(rebaseApplyFolder, true);
- }
Dispatcher.UIThread.Invoke(() =>
{
@@ -2005,6 +2038,28 @@ namespace SourceGit.ViewModels
}
}
+ private void AutoFetchImpl(object sender)
+ {
+ if (!_settings.EnableAutoFetch || IsAutoFetching)
+ return;
+
+ var lockFile = Path.Combine(_gitDir, "index.lock");
+ if (File.Exists(lockFile))
+ return;
+
+ var now = DateTime.Now;
+ var desire = _lastFetchTime.AddMinutes(_settings.AutoFetchInterval);
+ if (desire > now)
+ return;
+
+ IsAutoFetching = true;
+ Dispatcher.UIThread.Invoke(() => OnPropertyChanged(nameof(IsAutoFetching)));
+ new Commands.Fetch(_fullpath, "--all", true, false, null) { RaiseError = false }.Exec();
+ _lastFetchTime = DateTime.Now;
+ IsAutoFetching = false;
+ Dispatcher.UIThread.Invoke(() => OnPropertyChanged(nameof(IsAutoFetching)));
+ }
+
private string _fullpath = string.Empty;
private string _gitDir = string.Empty;
private Models.RepositorySettings _settings = null;
@@ -2023,6 +2078,7 @@ namespace SourceGit.ViewModels
private bool _isSearchLoadingVisible = false;
private bool _isSearchCommitSuggestionOpen = false;
private int _searchCommitFilterType = 2;
+ private bool _onlySearchCommitsInCurrentBranch = false;
private bool _enableFirstParentInHistories = false;
private string _searchCommitFilter = string.Empty;
private List _searchedCommits = new List();
@@ -2050,5 +2106,8 @@ namespace SourceGit.ViewModels
private InProgressContext _inProgressContext = null;
private bool _hasUnsolvedConflicts = false;
private Models.Commit _searchResultSelectedCommit = null;
+
+ private Timer _autoFetchTimer = null;
+ private DateTime _lastFetchTime = DateTime.MinValue;
}
}
diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs
index 8ddf84a5..fe5b1a2f 100644
--- a/src/ViewModels/RepositoryConfigure.cs
+++ b/src/ViewModels/RepositoryConfigure.cs
@@ -42,6 +42,26 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _httpProxy, value);
}
+ public bool EnableAutoFetch
+ {
+ get => _repo.Settings.EnableAutoFetch;
+ set => _repo.Settings.EnableAutoFetch = value;
+ }
+
+ public int? AutoFetchInterval
+ {
+ get => _repo.Settings.AutoFetchInterval;
+ set
+ {
+ if (value is null || value < 1)
+ return;
+
+ var interval = (int)value;
+ if (_repo.Settings.AutoFetchInterval != interval)
+ _repo.Settings.AutoFetchInterval = interval;
+ }
+ }
+
public AvaloniaList CommitTemplates
{
get => _repo.Settings.CommitTemplates;
diff --git a/src/ViewModels/Statistics.cs b/src/ViewModels/Statistics.cs
index 587b1b71..7852a367 100644
--- a/src/ViewModels/Statistics.cs
+++ b/src/ViewModels/Statistics.cs
@@ -1,5 +1,8 @@
using System.Threading.Tasks;
+
+using Avalonia.Media;
using Avalonia.Threading;
+
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
@@ -28,6 +31,25 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _selectedReport, value);
}
+ public uint SampleColor
+ {
+ get => Preference.Instance.StatisticsSampleColor;
+ set
+ {
+ if (value != Preference.Instance.StatisticsSampleColor)
+ {
+ Preference.Instance.StatisticsSampleColor = value;
+ OnPropertyChanged(nameof(SampleBrush));
+ _selectedReport?.ChangeColor(value);
+ }
+ }
+ }
+
+ public IBrush SampleBrush
+ {
+ get => new SolidColorBrush(SampleColor);
+ }
+
public Statistics(string repo)
{
Task.Run(() =>
@@ -47,18 +69,15 @@ namespace SourceGit.ViewModels
if (_data == null)
return;
- switch (_selectedIndex)
+ var report = _selectedIndex switch
{
- case 0:
- SelectedReport = _data.Year;
- break;
- case 1:
- SelectedReport = _data.Month;
- break;
- default:
- SelectedReport = _data.Week;
- break;
- }
+ 0 => _data.All,
+ 1 => _data.Month,
+ _ => _data.Week,
+ };
+
+ report.ChangeColor(SampleColor);
+ SelectedReport = report;
}
private bool _isLoading = true;
diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs
index 2dad3fc2..74ff3e0e 100644
--- a/src/ViewModels/WorkingCopy.cs
+++ b/src/ViewModels/WorkingCopy.cs
@@ -240,11 +240,6 @@ namespace SourceGit.ViewModels
_cached = changes;
_count = _cached.Count;
- var unstaged = new List();
- var staged = new List();
- var selectedUnstaged = new List();
- var selectedStaged = new List();
-
var lastSelectedUnstaged = new HashSet();
var lastSelectedStaged = new HashSet();
if (_selectedUnstaged != null && _selectedUnstaged.Count > 0)
@@ -258,6 +253,8 @@ namespace SourceGit.ViewModels
lastSelectedStaged.Add(c.Path);
}
+ var unstaged = new List();
+ var selectedUnstaged = new List();
var hasConflict = false;
foreach (var c in changes)
{
@@ -271,7 +268,8 @@ namespace SourceGit.ViewModels
}
}
- staged = GetStagedChanges();
+ var staged = GetStagedChanges();
+ var selectedStaged = new List();
foreach (var c in staged)
{
if (lastSelectedStaged.Contains(c.Path))
@@ -418,12 +416,17 @@ namespace SourceGit.ViewModels
public void Commit()
{
- DoCommit(false);
+ DoCommit(AutoStageBeforeCommit, false);
+ }
+
+ public void CommitWithAutoStage()
+ {
+ DoCommit(true, false);
}
public void CommitWithPush()
{
- DoCommit(true);
+ DoCommit(AutoStageBeforeCommit, true);
}
public ContextMenu CreateContextMenuForUnstagedChanges()
@@ -1276,7 +1279,7 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(true);
}
- private void DoCommit(bool autoPush)
+ private void DoCommit(bool autoStage, bool autoPush)
{
if (!PopupHost.CanCreatePopup())
{
@@ -1290,7 +1293,6 @@ namespace SourceGit.ViewModels
return;
}
- var autoStage = AutoStageBeforeCommit;
if (!_useAmend)
{
if (autoStage)
diff --git a/src/ViewModels/Workspace.cs b/src/ViewModels/Workspace.cs
index 949cca82..d527ff48 100644
--- a/src/ViewModels/Workspace.cs
+++ b/src/ViewModels/Workspace.cs
@@ -51,12 +51,6 @@ namespace SourceGit.ViewModels
get => new SolidColorBrush(_color);
}
- public void AddRepository(string repo)
- {
- if (!Repositories.Contains(repo))
- Repositories.Add(repo);
- }
-
private string _name = string.Empty;
private uint _color = 4278221015;
private bool _isActive = false;
diff --git a/src/Views/Blame.axaml.cs b/src/Views/Blame.axaml.cs
index 68e14de5..f96870e0 100644
--- a/src/Views/Blame.axaml.cs
+++ b/src/Views/Blame.axaml.cs
@@ -33,7 +33,7 @@ namespace SourceGit.Views
return;
var view = TextView;
- if (view != null && view.VisualLinesValid)
+ if (view is { VisualLinesValid: true })
{
var typeface = view.CreateTypeface();
var underlinePen = new Pen(Brushes.DarkOrange);
@@ -142,12 +142,12 @@ namespace SourceGit.Views
return new Size(maxWidth, 0);
}
- protected override void OnPointerPressed(PointerPressedEventArgs e)
+ protected override void OnPointerMoved(PointerEventArgs e)
{
- base.OnPointerPressed(e);
+ base.OnPointerMoved(e);
var view = TextView;
- if (!e.Handled && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && view != null && view.VisualLinesValid)
+ if (!e.Handled && view is { VisualLinesValid: true })
{
var pos = e.GetPosition(this);
var typeface = view.CreateTypeface();
@@ -158,7 +158,48 @@ namespace SourceGit.Views
continue;
var lineNumber = line.FirstDocumentLine.LineNumber;
- if (lineNumber >= _editor.BlameData.LineInfos.Count)
+ if (lineNumber > _editor.BlameData.LineInfos.Count)
+ break;
+
+ var info = _editor.BlameData.LineInfos[lineNumber - 1];
+ var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop) - view.VerticalOffset;
+ var shaLink = new FormattedText(
+ info.CommitSHA,
+ CultureInfo.CurrentCulture,
+ FlowDirection.LeftToRight,
+ typeface,
+ _editor.FontSize,
+ Brushes.DarkOrange);
+
+ var rect = new Rect(0, y, shaLink.Width, shaLink.Height);
+ if (rect.Contains(pos))
+ {
+ Cursor = Cursor.Parse("Hand");
+ return;
+ }
+ }
+ }
+
+ Cursor = Cursor.Default;
+ }
+
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
+ {
+ base.OnPointerPressed(e);
+
+ var view = TextView;
+ if (!e.Handled && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && view is { VisualLinesValid: true })
+ {
+ var pos = e.GetPosition(this);
+ var typeface = view.CreateTypeface();
+
+ foreach (var line in view.VisualLines)
+ {
+ if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
+ continue;
+
+ var lineNumber = line.FirstDocumentLine.LineNumber;
+ if (lineNumber > _editor.BlameData.LineInfos.Count)
break;
var info = _editor.BlameData.LineInfos[lineNumber - 1];
@@ -262,7 +303,7 @@ namespace SourceGit.Views
continue;
var lineNumber = line.FirstDocumentLine.LineNumber;
- if (lineNumber >= BlameData.LineInfos.Count)
+ if (lineNumber > BlameData.LineInfos.Count)
break;
var info = BlameData.LineInfos[lineNumber - 1];
@@ -321,7 +362,7 @@ namespace SourceGit.Views
return;
var caret = TextArea.Caret;
- if (caret == null || caret.Line >= BlameData.LineInfos.Count)
+ if (caret == null || caret.Line > BlameData.LineInfos.Count)
return;
_highlight = BlameData.LineInfos[caret.Line - 1].CommitSHA;
diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml
index 9525631c..6df2dbb8 100644
--- a/src/Views/BranchTree.axaml
+++ b/src/Views/BranchTree.axaml
@@ -54,10 +54,10 @@
+ Text="{Binding Name}"
+ Classes="primary"
+ FontWeight="{Binding NameFontWeight}"
+ TextTrimming="CharacterEllipsis"/>
diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs
index bba91274..49d15ef0 100644
--- a/src/Views/BranchTree.axaml.cs
+++ b/src/Views/BranchTree.axaml.cs
@@ -428,12 +428,23 @@ namespace SourceGit.Views
}
}
- private void OnToggleFilter(object sender, RoutedEventArgs e)
+ private void OnToggleFilterClicked(object sender, RoutedEventArgs e)
{
- if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo)
+ if (DataContext is ViewModels.Repository repo &&
+ sender is ToggleButton toggle &&
+ toggle.DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch } node)
{
- if (toggle.DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
- repo.UpdateFilter(branch.FullName, toggle.IsChecked == true);
+ bool filtered = toggle.IsChecked == true;
+ List filters = [branch.FullName];
+ if (branch.IsLocal && !string.IsNullOrEmpty(branch.Upstream))
+ {
+ filters.Add(branch.Upstream);
+
+ node.IsFiltered = filtered;
+ UpdateUpstreamFilterState(repo.RemoteBranchTrees, branch.Upstream, filtered);
+ }
+
+ repo.UpdateFilters(filters, filtered);
}
e.Handled = true;
@@ -466,6 +477,23 @@ namespace SourceGit.Views
CollectBranchesInNode(outs, sub);
}
+ private bool UpdateUpstreamFilterState(List collection, string upstream, bool isFiltered)
+ {
+ foreach (var node in collection)
+ {
+ if (node.Backend is Models.Branch b && b.FullName == upstream)
+ {
+ node.IsFiltered = isFiltered;
+ return true;
+ }
+
+ if (node.Backend is Models.Remote r && upstream.StartsWith($"refs/remotes/{r.Name}/", StringComparison.Ordinal))
+ return UpdateUpstreamFilterState(node.Children, upstream, isFiltered);
+ }
+
+ return false;
+ }
+
private bool _disableSelectionChangingEvent = false;
}
}
diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs
index 39d46d77..a47988f0 100644
--- a/src/Views/ChangeCollectionView.axaml.cs
+++ b/src/Views/ChangeCollectionView.axaml.cs
@@ -41,7 +41,7 @@ namespace SourceGit.Views
e.Handled = true;
}
}
-
+
if (!e.Handled && e.Key != Key.Space)
base.OnKeyDown(e);
}
@@ -166,10 +166,10 @@ namespace SourceGit.Views
{
if (lastUnselected == -1)
continue;
-
+
break;
}
-
+
lastUnselected = i;
}
}
@@ -186,10 +186,10 @@ namespace SourceGit.Views
{
if (lastUnselected == -1)
continue;
-
+
break;
}
-
+
lastUnselected = i;
}
@@ -244,7 +244,7 @@ namespace SourceGit.Views
_disableSelectionChangingEvent = true;
var selected = new List();
- if (sender is ListBox { SelectedItems: {} selectedItems })
+ if (sender is ListBox { SelectedItems: { } selectedItems })
{
foreach (var item in selectedItems)
{
diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml
index bc36b023..41e0e29d 100644
--- a/src/Views/CommitChanges.axaml
+++ b/src/Views/CommitChanges.axaml
@@ -9,9 +9,9 @@
x:DataType="vm:CommitDetail">
-
+
-
+
diff --git a/src/Views/ContextMenuExtension.cs b/src/Views/ContextMenuExtension.cs
index 6e6dbf20..2abcf2b9 100644
--- a/src/Views/ContextMenuExtension.cs
+++ b/src/Views/ContextMenuExtension.cs
@@ -1,5 +1,4 @@
using System.ComponentModel;
-
using Avalonia.Controls;
namespace SourceGit.Views
@@ -15,7 +14,7 @@ namespace SourceGit.Views
menu.Closing += OnContextMenuClosing; // Clear context menu because it is dynamic.
control.ContextMenu = menu;
- control.ContextMenu.Open();
+ control.ContextMenu?.Open();
}
private static void OnContextMenuClosing(object sender, CancelEventArgs e)
diff --git a/src/Views/FileHistories.axaml b/src/Views/FileHistories.axaml
index a5e229dc..bc048706 100644
--- a/src/Views/FileHistories.axaml
+++ b/src/Views/FileHistories.axaml
@@ -87,6 +87,7 @@
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml
index cd7f9c4a..5d98238d 100644
--- a/src/Views/Hotkeys.axaml
+++ b/src/Views/Hotkeys.axaml
@@ -78,7 +78,7 @@
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}"
Margin="0,8"/>
-
+
@@ -102,12 +102,15 @@
-
+
-
+
-
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
+
+
+
+
+
+
+
+
diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml
index 3d1b1bfd..285ea72c 100644
--- a/src/Views/Launcher.axaml
+++ b/src/Views/Launcher.axaml
@@ -70,7 +70,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml
index 346639e2..8d03c0bb 100644
--- a/src/Views/RepositoryConfigure.axaml
+++ b/src/Views/RepositoryConfigure.axaml
@@ -51,7 +51,7 @@
-
+
+
+
+
+
+
+
+
+
@@ -186,7 +206,11 @@
-
+
+
diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs
index 09e2159e..a6e8df41 100644
--- a/src/Views/RevisionFileTreeView.axaml.cs
+++ b/src/Views/RevisionFileTreeView.axaml.cs
@@ -117,7 +117,7 @@ namespace SourceGit.Views
e.Handled = true;
}
}
-
+
if (!e.Handled)
base.OnKeyDown(e);
}
diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml
index 0165ccab..d0b20963 100644
--- a/src/Views/RevisionFiles.axaml
+++ b/src/Views/RevisionFiles.axaml
@@ -9,9 +9,9 @@
x:DataType="vm:CommitDetail">
-
+
-
+
diff --git a/src/Views/Statistics.axaml b/src/Views/Statistics.axaml
index 4efb1e9d..6e2a00dd 100644
--- a/src/Views/Statistics.axaml
+++ b/src/Views/Statistics.axaml
@@ -5,12 +5,13 @@
xmlns:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
+ xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.Statistics"
x:DataType="vm:Statistics"
x:Name="ThisControl"
Title="{DynamicResource Text.Statistics}"
- Width="860" Height="500"
+ Width="800" Height="500"
WindowStartupLocation="CenterOwner"
CanResize="False">
@@ -62,6 +63,7 @@