diff --git a/.github/workflows/homebrew-notify.yml b/.github/workflows/homebrew-notify.yml
new file mode 100644
index 000000000..d0538b37a
--- /dev/null
+++ b/.github/workflows/homebrew-notify.yml
@@ -0,0 +1,22 @@
+name: Notify Homebrew Tap
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ notify-homebrew:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify Homebrew tap
+ env:
+ TAG: ${{ github.event.release.tag_name }}
+ HOMEBREW_TAP_REPO_TOKEN: ${{ secrets.HOMEBREW_TAP_REPO_TOKEN }}
+ run: |
+ echo "📢 Notifying Homebrew tap of new release $TAG..."
+ curl -X POST \
+ -H "Authorization: token $HOMEBREW_TAP_REPO_TOKEN" \
+ -H "Accept: application/vnd.github.v3+json" \
+ https://api.github.com/repos/ybeapps/homebrew-sourcegit/dispatches \
+ -d "{\"event_type\":\"new-sourcegit-release\",\"client_payload\":{\"version\":\"$TAG\"}}"
+ echo "✅ Homebrew tap notified successfully"
diff --git a/README.md b/README.md
index eb370c3c6..c4ac40081 100644
--- a/README.md
+++ b/README.md
@@ -104,15 +104,17 @@ For **Windows** users:
For **macOS** users:
-* Thanks [@ybeapps](https://github.com/ybeapps) for making `SourceGit` available on `Homebrew`. You can simply install it with following command:
+* Thanks [@ybeapps](https://github.com/ybeapps) for making `SourceGit` available on `Homebrew`:
```shell
- brew tap ybeapps/homebrew-sourcegit
- brew install --cask --no-quarantine sourcegit
+ brew install --cask sourcegit
```
* If you want to install `SourceGit.app` from GitHub Release manually, you need run following command to make sure it works:
```shell
sudo xattr -cr /Applications/SourceGit.app
```
+> [!NOTE]
+> macOS packages in the `Release` page of this project are all unsigned. If you are worried about potential security issues with the above command, you can download the signed package from the [distribution repository](https://github.com/ybeapps/homebrew-sourcegit/releases) provided by [@ybeapps](https://github.com/ybeapps) (there is no need to execute the above command while installing `SourceGit`).
+
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your mac.
* You can run `echo $PATH > ~/Library/Application\ Support/SourceGit/PATH` to generate a custom PATH env file to introduce `PATH` env to SourceGit.
diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md
index 49de958a6..5c696b1ba 100644
--- a/THIRD-PARTY-LICENSES.md
+++ b/THIRD-PARTY-LICENSES.md
@@ -35,14 +35,14 @@ The project uses the following third-party libraries or assets
### OpenAI .NET SDK
- **Source**: https://github.com/openai/openai-dotnet
-- **Version**: 2.5.0
+- **Version**: 2.8.0
- **License**: MIT License
- **License Link**: https://github.com/openai/openai-dotnet/blob/main/LICENSE
### Azure.AI.OpenAI
- **Source**: https://github.com/Azure/azure-sdk-for-net
-- **Version**: 2.5.0-beta.1
+- **Version**: 2.8.0-beta.1
- **License**: MIT License
- **License Link**: https://github.com/Azure/azure-sdk-for-net/blob/main/LICENSE.txt
@@ -56,7 +56,7 @@ The project uses the following third-party libraries or assets
### Pfim
- **Source**: https://github.com/nickbabcock/Pfim
-- **Version**: 0.11.3
+- **Version**: 0.11.4
- **License**: MIT License
- **License Link**: https://github.com/nickbabcock/Pfim/blob/master/LICENSE.txt
diff --git a/TRANSLATION.md b/TRANSLATION.md
index e917c8659..4014aadf9 100644
--- a/TRANSLATION.md
+++ b/TRANSLATION.md
@@ -25,25 +25,7 @@ This document shows the translation status of each locale file in the repository
-### 
-
-
-Missing keys in es_ES.axaml
-
-- Text.Hotkeys.Global.ShowWorkspaceDropdownMenu
-- Text.PageTabBar.Tab.MoveToWorkspace
-- Text.PageTabBar.Tab.Refresh
-- Text.Preferences.DiffMerge.DiffArgs
-- Text.Preferences.DiffMerge.DiffArgs.Tip
-- Text.Preferences.DiffMerge.MergeArgs
-- Text.Preferences.DiffMerge.MergeArgs.Tip
-- Text.Preferences.Shell.Args
-- Text.Preferences.Shell.Args.Tip
-- Text.SquashOrFixup.Squash
-- Text.SquashOrFixup.Fixup
-- Text.SquashOrFixup.Into
-
-
+### 
### 
@@ -71,7 +53,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in id_ID.axaml
@@ -117,7 +99,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in it_IT.axaml
@@ -187,7 +169,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in ja_JP.axaml
@@ -226,7 +208,6 @@ This document shows the translation status of each locale file in the repository
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
-- Text.Checkout.RecurseSubmodules
- Text.Checkout.WarnLostCommits
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
@@ -345,7 +326,6 @@ This document shows the translation status of each locale file in the repository
- Text.Preferences.Git.UseLibsecret
- Text.Preferences.Shell.Args
- Text.Preferences.Shell.Args.Tip
-- Text.Pull.RecurseSubmodules
- Text.Push.New
- Text.Push.Revision
- Text.Push.Revision.Title
@@ -426,7 +406,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in ko_KR.axaml
@@ -469,7 +449,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in pt_BR.axaml
@@ -515,7 +495,6 @@ This document shows the translation status of each locale file in the repository
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
-- Text.Checkout.RecurseSubmodules
- Text.Checkout.WarnLostCommits
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
@@ -667,7 +646,6 @@ This document shows the translation status of each locale file in the repository
- Text.Preferences.Git.UseLibsecret
- Text.Preferences.Shell.Args
- Text.Preferences.Shell.Args.Tip
-- Text.Pull.RecurseSubmodules
- Text.Push.New
- Text.Push.Revision
- Text.Push.Revision.Title
@@ -765,16 +743,9 @@ This document shows the translation status of each locale file in the repository
-### 
-
-
-Missing keys in ru_RU.axaml
-
-- Text.Preferences.DiffMerge.DiffArgs
-
-
+### 
-### 
+### 
Missing keys in ta_IN.axaml
@@ -813,7 +784,6 @@ This document shows the translation status of each locale file in the repository
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
-- Text.Checkout.RecurseSubmodules
- Text.Checkout.WarnLostCommits
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
@@ -932,7 +902,6 @@ This document shows the translation status of each locale file in the repository
- Text.Preferences.Git.UseLibsecret
- Text.Preferences.Shell.Args
- Text.Preferences.Shell.Args.Tip
-- Text.Pull.RecurseSubmodules
- Text.Push.New
- Text.Push.Revision
- Text.Push.Revision.Title
@@ -1012,7 +981,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in uk_UA.axaml
@@ -1050,7 +1019,6 @@ This document shows the translation status of each locale file in the repository
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
-- Text.Checkout.RecurseSubmodules
- Text.Checkout.WarnLostCommits
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
@@ -1165,7 +1133,6 @@ This document shows the translation status of each locale file in the repository
- Text.Preferences.Git.UseLibsecret
- Text.Preferences.Shell.Args
- Text.Preferences.Shell.Args.Tip
-- Text.Pull.RecurseSubmodules
- Text.Push.New
- Text.Push.Revision
- Text.Push.Revision.Title
diff --git a/VERSION b/VERSION
index 1eb5f2619..dff1fb957 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2025.40
\ No newline at end of file
+2025.41
\ No newline at end of file
diff --git a/src/Commands/Add.cs b/src/Commands/Add.cs
index 9bd576359..916063d82 100644
--- a/src/Commands/Add.cs
+++ b/src/Commands/Add.cs
@@ -2,25 +2,11 @@
{
public class Add : Command
{
- public Add(string repo, bool includeUntracked)
- {
- WorkingDirectory = repo;
- Context = repo;
- Args = includeUntracked ? "add ." : "add -u .";
- }
-
- public Add(string repo, Models.Change change)
- {
- WorkingDirectory = repo;
- Context = repo;
- Args = $"add -- {change.Path.Quoted()}";
- }
-
public Add(string repo, string pathspecFromFile)
{
WorkingDirectory = repo;
Context = repo;
- Args = $"add --pathspec-from-file={pathspecFromFile.Quoted()}";
+ Args = $"add --force --verbose --pathspec-from-file={pathspecFromFile.Quoted()}";
}
}
}
diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs
index 520f19703..e6e64f054 100644
--- a/src/Commands/Command.cs
+++ b/src/Commands/Command.cs
@@ -181,7 +181,7 @@ protected ProcessStartInfo CreateGitStartInfo(bool redirect)
// If an SSH private key was provided, sets the environment.
if (!start.Environment.ContainsKey("GIT_SSH_COMMAND") && !string.IsNullOrEmpty(SSHKey))
- start.Environment.Add("GIT_SSH_COMMAND", $"ssh -i '{SSHKey}'");
+ start.Environment.Add("GIT_SSH_COMMAND", $"ssh -i '{SSHKey}' -F '/dev/null'");
// Force using en_US.UTF-8 locale
if (OperatingSystem.IsLinux())
diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs
index 3fdc62259..680aff63d 100644
--- a/src/Commands/Diff.cs
+++ b/src/Commands/Diff.cs
@@ -46,9 +46,22 @@ public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespa
proc.StartInfo = CreateGitStartInfo(true);
proc.Start();
- while (await proc.StandardOutput.ReadLineAsync().ConfigureAwait(false) is { } line)
+ var text = await proc.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
+
+ var start = 0;
+ var end = text.IndexOf('\n', start);
+ while (end > 0)
+ {
+ var line = text[start..end];
ParseLine(line);
+ start = end + 1;
+ end = text.IndexOf('\n', start);
+ }
+
+ if (start < text.Length)
+ ParseLine(text[start..]);
+
await proc.WaitForExitAsync().ConfigureAwait(false);
}
catch
diff --git a/src/Commands/Discard.cs b/src/Commands/Discard.cs
index cbf38bd49..aad589112 100644
--- a/src/Commands/Discard.cs
+++ b/src/Commands/Discard.cs
@@ -86,7 +86,7 @@ public static async Task ChangesAsync(string repo, List changes,
{
var pathSpecFile = Path.GetTempFileName();
await File.WriteAllLinesAsync(pathSpecFile, restores).ConfigureAwait(false);
- await new Restore(repo, pathSpecFile, false).Use(log).ExecAsync().ConfigureAwait(false);
+ await new Restore(repo, pathSpecFile).Use(log).ExecAsync().ConfigureAwait(false);
File.Delete(pathSpecFile);
}
}
diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs
index 9a599f821..914361257 100644
--- a/src/Commands/Fetch.cs
+++ b/src/Commands/Fetch.cs
@@ -22,6 +22,17 @@ public Fetch(string repo, string remote, bool noTags, bool force)
Args = builder.ToString();
}
+ public Fetch(string repo, string remote)
+ {
+ _remote = remote;
+
+ WorkingDirectory = repo;
+ Context = repo;
+ RaiseError = false;
+
+ Args = $"fetch --progress --verbose {remote}";
+ }
+
public Fetch(string repo, Models.Branch local, Models.Branch remote)
{
_remote = remote.Remote;
diff --git a/src/Commands/QueryRemotes.cs b/src/Commands/QueryRemotes.cs
index bd42aabf1..48d7040a0 100644
--- a/src/Commands/QueryRemotes.cs
+++ b/src/Commands/QueryRemotes.cs
@@ -40,6 +40,16 @@ public QueryRemotes(string repo)
if (outs.Find(x => x.Name == remote.Name) != null)
continue;
+ if (remote.URL.StartsWith("git@", StringComparison.Ordinal))
+ {
+ var hostEnd = remote.URL.IndexOf(':', 4);
+ if (hostEnd > 4)
+ {
+ var host = remote.URL.Substring(4, hostEnd - 4);
+ Models.HTTPSValidator.Add(host);
+ }
+ }
+
outs.Add(remote);
}
diff --git a/src/Commands/QueryUpdatableSubmodules.cs b/src/Commands/QueryUpdatableSubmodules.cs
index 05fcc0538..55f429905 100644
--- a/src/Commands/QueryUpdatableSubmodules.cs
+++ b/src/Commands/QueryUpdatableSubmodules.cs
@@ -7,14 +7,16 @@ namespace SourceGit.Commands
{
public partial class QueryUpdatableSubmodules : Command
{
- [GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
+ [GeneratedRegex(@"^([\-\+])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
private static partial Regex REG_FORMAT_STATUS();
- public QueryUpdatableSubmodules(string repo)
+ public QueryUpdatableSubmodules(string repo, bool includeUninited)
{
WorkingDirectory = repo;
Context = repo;
Args = "submodule status";
+
+ _includeUninited = includeUninited;
}
public async Task> GetResultAsync()
@@ -30,12 +32,16 @@ public async Task> GetResultAsync()
{
var stat = match.Groups[1].Value;
var path = match.Groups[3].Value;
- if (!stat.StartsWith(' '))
- submodules.Add(path);
+ if (!_includeUninited && stat.StartsWith('-'))
+ continue;
+
+ submodules.Add(path);
}
}
return submodules;
}
+
+ private bool _includeUninited = false;
}
}
diff --git a/src/Commands/Reset.cs b/src/Commands/Reset.cs
index 6a54533b1..cfcd337af 100644
--- a/src/Commands/Reset.cs
+++ b/src/Commands/Reset.cs
@@ -8,5 +8,12 @@ public Reset(string repo, string revision, string mode)
Context = repo;
Args = $"reset {mode} {revision}";
}
+
+ public Reset(string repo, string pathspec)
+ {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"reset HEAD --pathspec-from-file={pathspec.Quoted()}";
+ }
}
}
diff --git a/src/Commands/Restore.cs b/src/Commands/Restore.cs
index e2f9aa09a..bf3bd0a55 100644
--- a/src/Commands/Restore.cs
+++ b/src/Commands/Restore.cs
@@ -1,45 +1,12 @@
-using System.Text;
-
-namespace SourceGit.Commands
+namespace SourceGit.Commands
{
public class Restore : Command
{
- ///
- /// Only used for single staged change.
- ///
- ///
- ///
- public Restore(string repo, Models.Change stagedChange)
+ public Restore(string repo, string pathspecFile)
{
WorkingDirectory = repo;
Context = repo;
-
- var builder = new StringBuilder();
- builder.Append("restore --staged -- ").Append(stagedChange.Path.Quoted());
-
- if (stagedChange.Index == Models.ChangeState.Renamed)
- builder.Append(' ').Append(stagedChange.OriginalPath.Quoted());
-
- Args = builder.ToString();
- }
-
- ///
- /// Restore changes given in a path-spec file.
- ///
- ///
- ///
- ///
- public Restore(string repo, string pathspecFile, bool isStaged)
- {
- WorkingDirectory = repo;
- Context = repo;
-
- var builder = new StringBuilder();
- builder.Append("restore ");
- builder.Append(isStaged ? "--staged " : "--worktree --recurse-submodules ");
- builder.Append("--pathspec-from-file=").Append(pathspecFile.Quoted());
-
- Args = builder.ToString();
+ Args = $"restore --progress --worktree --recurse-submodules --pathspec-from-file={pathspecFile.Quoted()}";
}
}
}
diff --git a/src/Commands/UnstageChangesForAmend.cs b/src/Commands/UpdateIndexInfo.cs
similarity index 88%
rename from src/Commands/UnstageChangesForAmend.cs
rename to src/Commands/UpdateIndexInfo.cs
index 9170306a0..3633a2823 100644
--- a/src/Commands/UnstageChangesForAmend.cs
+++ b/src/Commands/UpdateIndexInfo.cs
@@ -6,9 +6,9 @@
namespace SourceGit.Commands
{
- public class UnstageChangesForAmend
+ public class UpdateIndexInfo
{
- public UnstageChangesForAmend(string repo, List changes)
+ public UpdateIndexInfo(string repo, List changes)
{
_repo = repo;
@@ -18,7 +18,7 @@ public UnstageChangesForAmend(string repo, List changes)
{
_patchBuilder.Append("0 0000000000000000000000000000000000000000\t");
_patchBuilder.Append(c.Path);
- _patchBuilder.Append("\0100644 ");
+ _patchBuilder.Append("\n100644 ");
_patchBuilder.Append(c.DataForAmend.ObjectHash);
_patchBuilder.Append("\t");
_patchBuilder.Append(c.OriginalPath);
@@ -60,6 +60,8 @@ public async Task ExecAsync()
starter.RedirectStandardInput = true;
starter.RedirectStandardOutput = false;
starter.RedirectStandardError = true;
+ starter.StandardInputEncoding = new UTF8Encoding(false);
+ starter.StandardErrorEncoding = Encoding.UTF8;
try
{
@@ -78,7 +80,7 @@ public async Task ExecAsync()
}
catch (Exception e)
{
- App.RaiseException(_repo, "Failed to unstage changes: " + e.Message);
+ App.RaiseException(_repo, "Failed to update index: " + e.Message);
return false;
}
}
diff --git a/src/Models/CommitLink.cs b/src/Models/CommitLink.cs
index e7c3f697a..fa78f206c 100644
--- a/src/Models/CommitLink.cs
+++ b/src/Models/CommitLink.cs
@@ -40,6 +40,8 @@ public static List Get(List remotes)
outs.Add(new($"Gitea ({route})", $"{link}/commit/"));
else if (host.Equals("git.sr.ht", StringComparison.Ordinal))
outs.Add(new($"sourcehut ({route})", $"{link}/commit/"));
+ else if (host.Equals("gitcode.com", StringComparison.Ordinal))
+ outs.Add(new($"GitCode ({route})", $"{link}/commit/"));
}
}
diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs
index ce90a7d44..655a1d58a 100644
--- a/src/Models/ExternalMerger.cs
+++ b/src/Models/ExternalMerger.cs
@@ -50,7 +50,7 @@ static ExternalMerger()
{
Supported = new List() {
new ExternalMerger("git", "Use Git Settings", "", "", ""),
- new ExternalMerger("xcode", "FileMerge", "/usr/bin/opendiff", "\"$BASE\" \"$LOCAL\" \"$REMOTE\" -ancestor \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
+ new ExternalMerger("xcode", "FileMerge", "/usr/bin/opendiff", "\"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger("vscode", "Visual Studio Code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger("vscode_insiders", "Visual Studio Code - Insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger("kdiff3", "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs
index bd04f2f90..0f272ab35 100644
--- a/src/Models/ExternalTool.cs
+++ b/src/Models/ExternalTool.cs
@@ -180,17 +180,24 @@ public void FindJetBrainsFromToolbox(Func platformFinder)
var state = Path.Combine(platformFinder(), "state.json");
if (File.Exists(state))
{
- using var stream = File.OpenRead(state);
- var stateData = JsonSerializer.Deserialize(stream, JsonCodeGen.Default.JetBrainsState);
- foreach (var tool in stateData.Tools)
+ try
{
- if (exclude.Contains(tool.ToolId.ToLowerInvariant()))
- continue;
-
- Tools.Add(new ExternalTool(
- $"{tool.DisplayName} {tool.DisplayVersion}",
- supportedIcons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : "JetBrains/JB",
- Path.Combine(tool.InstallLocation, tool.LaunchCommand)));
+ using var stream = File.OpenRead(state);
+ var stateData = JsonSerializer.Deserialize(stream, JsonCodeGen.Default.JetBrainsState);
+ foreach (var tool in stateData.Tools)
+ {
+ if (exclude.Contains(tool.ToolId.ToLowerInvariant()))
+ continue;
+
+ Tools.Add(new ExternalTool(
+ $"{tool.DisplayName} {tool.DisplayVersion}",
+ supportedIcons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : "JetBrains/JB",
+ Path.Combine(tool.InstallLocation, tool.LaunchCommand)));
+ }
+ }
+ catch
+ {
+ // Ignore exceptions.
}
}
}
diff --git a/src/Models/HTTPSValidator.cs b/src/Models/HTTPSValidator.cs
new file mode 100644
index 000000000..014207c21
--- /dev/null
+++ b/src/Models/HTTPSValidator.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SourceGit.Models
+{
+ public static class HTTPSValidator
+ {
+ public static void Add(string host)
+ {
+ lock (_syncLock)
+ {
+ // Already checked
+ if (_hosts.ContainsKey(host))
+ return;
+
+ // Temporarily mark as supported to avoid duplicate checks
+ _hosts.Add(host, true);
+
+ // Well-known hosts always support HTTPS
+ if (host.Contains("github.com", StringComparison.Ordinal) ||
+ host.Contains("gitlab", StringComparison.Ordinal) ||
+ host.Contains("azure.com", StringComparison.Ordinal) ||
+ host.Equals("gitee.com", StringComparison.Ordinal) ||
+ host.Equals("bitbucket.org", StringComparison.Ordinal) ||
+ host.Equals("gitea.org", StringComparison.Ordinal) ||
+ host.Equals("gitcode.com", StringComparison.Ordinal))
+ return;
+ }
+
+ Task.Run(() =>
+ {
+ var supported = false;
+
+ try
+ {
+ using (var client = new TcpClient())
+ {
+ client.ConnectAsync(host, 443).Wait(3000);
+ if (!client.Connected)
+ {
+ client.ConnectAsync(host, 80).Wait(3000);
+ supported = !client.Connected; // If the network is not available, assume HTTPS is supported
+ }
+ else
+ {
+ using (var ssl = new SslStream(client.GetStream(), false, (s, cert, chain, errs) => true))
+ {
+ ssl.AuthenticateAsClient(host);
+ supported = ssl.IsAuthenticated; // Hand-shake succeeded
+ }
+ }
+ }
+ }
+ catch
+ {
+ // Ignore exceptions
+ }
+
+ lock (_syncLock)
+ {
+ _hosts[host] = supported;
+ }
+ });
+ }
+
+ public static bool IsSupported(string host)
+ {
+ lock (_syncLock)
+ {
+ if (_hosts.TryGetValue(host, out var supported))
+ return supported;
+
+ return false;
+ }
+ }
+
+ private static Lock _syncLock = new();
+ private static Dictionary _hosts = new();
+ }
+}
diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs
index 3550a4e56..d1fa2cb4d 100644
--- a/src/Models/Remote.cs
+++ b/src/Models/Remote.cs
@@ -78,7 +78,10 @@ public bool TryGetVisitURL(out string url)
var match = REG_TO_VISIT_URL_CAPTURE().Match(URL);
if (match.Success)
{
- url = $"https://{match.Groups[1].Value}/{match.Groups[2].Value}";
+ var host = match.Groups[1].Value;
+ var supportHTTPS = HTTPSValidator.IsSupported(host);
+ var scheme = supportHTTPS ? "https" : "http";
+ url = $"{scheme}://{host}/{match.Groups[2].Value}";
return true;
}
diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs
index b0d52d50a..ddee7fe03 100644
--- a/src/Models/RepositorySettings.cs
+++ b/src/Models/RepositorySettings.cs
@@ -107,12 +107,6 @@ public bool CheckoutBranchOnCreateBranch
set;
} = true;
- public bool UpdateSubmodulesOnCheckoutBranch
- {
- get;
- set;
- } = true;
-
public AvaloniaList CommitTemplates
{
get;
diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs
index 843744724..cdeddd65f 100644
--- a/src/Models/Watcher.cs
+++ b/src/Models/Watcher.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
+using System.Threading.Tasks;
namespace SourceGit.Models
{
@@ -27,6 +28,7 @@ public Watcher(IRepository repo, string fullpath, string gitDir)
{
_repo = repo;
_root = new DirectoryInfo(fullpath).FullName;
+ _watchers = new List();
var testGitDir = new DirectoryInfo(Path.Combine(fullpath, ".git")).FullName;
var desiredDir = new DirectoryInfo(gitDir).FullName;
@@ -41,7 +43,7 @@ public Watcher(IRepository repo, string fullpath, string gitDir)
combined.Renamed += OnRepositoryChanged;
combined.Changed += OnRepositoryChanged;
combined.Deleted += OnRepositoryChanged;
- combined.EnableRaisingEvents = true;
+ combined.EnableRaisingEvents = false;
_watchers.Add(combined);
}
@@ -56,7 +58,7 @@ public Watcher(IRepository repo, string fullpath, string gitDir)
wc.Renamed += OnWorkingCopyChanged;
wc.Changed += OnWorkingCopyChanged;
wc.Deleted += OnWorkingCopyChanged;
- wc.EnableRaisingEvents = true;
+ wc.EnableRaisingEvents = false;
var git = new FileSystemWatcher();
git.Path = gitDir;
@@ -67,13 +69,27 @@ public Watcher(IRepository repo, string fullpath, string gitDir)
git.Renamed += OnGitDirChanged;
git.Changed += OnGitDirChanged;
git.Deleted += OnGitDirChanged;
- git.EnableRaisingEvents = true;
+ git.EnableRaisingEvents = false;
_watchers.Add(wc);
_watchers.Add(git);
}
_timer = new Timer(Tick, null, 100, 100);
+
+ // Starts filesystem watchers in another thread to avoid UI blocking
+ Task.Run(() =>
+ {
+ try
+ {
+ foreach (var watcher in _watchers)
+ watcher.EnableRaisingEvents = true;
+ }
+ catch
+ {
+ // Ignore exceptions. This may occur while `Dispose` is called.
+ }
+ });
}
public IDisposable Lock()
@@ -102,6 +118,11 @@ public void MarkStashUpdated()
Interlocked.Exchange(ref _updateStashes, 0);
}
+ public void MarkSubmodulesUpdated()
+ {
+ Interlocked.Exchange(ref _updateSubmodules, 0);
+ }
+
public void Dispose()
{
foreach (var watcher in _watchers)
@@ -312,26 +333,25 @@ private void HandleWorkingCopyFileChanged(string name, string fullpath)
private bool IsInSubmodule(string folder)
{
+ if (string.IsNullOrEmpty(folder) || folder.Equals(_root, StringComparison.Ordinal))
+ return false;
+
if (File.Exists($"{folder}/.git"))
return true;
- var parent = Path.GetDirectoryName(folder);
- if (parent == null || parent.Equals(_root, StringComparison.Ordinal))
- return false;
-
- return IsInSubmodule(parent);
+ return IsInSubmodule(Path.GetDirectoryName(folder));
}
- private readonly IRepository _repo = null;
- private readonly string _root = null;
- private List _watchers = [];
- private Timer _timer = null;
-
- private long _lockCount = 0;
- private long _updateWC = 0;
- private long _updateBranch = 0;
- private long _updateSubmodules = 0;
- private long _updateStashes = 0;
- private long _updateTags = 0;
+ private readonly IRepository _repo;
+ private readonly string _root;
+ private List _watchers;
+ private Timer _timer;
+
+ private long _lockCount;
+ private long _updateWC;
+ private long _updateBranch;
+ private long _updateSubmodules;
+ private long _updateStashes;
+ private long _updateTags;
}
}
diff --git a/src/Native/OS.cs b/src/Native/OS.cs
index 4da62b5ff..1fd4b34d9 100644
--- a/src/Native/OS.cs
+++ b/src/Native/OS.cs
@@ -198,7 +198,6 @@ public static Models.DiffMergeTool GetDiffMergeTool(bool onlyDiff)
if (ExternalMergerType != 0 && (string.IsNullOrEmpty(ExternalMergerExecFile) || !File.Exists(ExternalMergerExecFile)))
return null;
- var tool = Models.ExternalMerger.Supported[ExternalMergerType];
return new Models.DiffMergeTool(ExternalMergerExecFile, onlyDiff ? ExternalDiffArgs : ExternalMergeArgs);
}
diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml
index 530dcd505..7d2e27c08 100644
--- a/src/Resources/Locales/de_DE.axaml
+++ b/src/Resources/Locales/de_DE.axaml
@@ -109,7 +109,6 @@
Lokale Änderungen:
Verwerfen
Stashen & wieder anwenden
- Alle Submodule updaten
Branch:
Dein aktueller HEAD enthält Commit(s) ohne Verbindung zu einem Branch/Tag. Möchtest du trotzdem fortfahren?
Auschecken & Fast-Forward
@@ -645,7 +644,6 @@
Lokale Änderungen:
Verwerfen
Stashen & wieder anwenden
- Alle Submodule aktualisieren
Remote:
Pull (Fetch & Merge)
Rebase anstatt Merge verwenden
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index daa23e13d..d60e868e3 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -105,7 +105,6 @@
Local Changes:
Discard
Stash & Reapply
- Update all submodules
Branch:
Your current HEAD contains commit(s) not connected to any branches/tags! Do you want to continue?
Checkout & Fast-Forward
@@ -648,7 +647,6 @@
Local Changes:
Discard
Stash & Reapply
- Update all submodules
Remote:
Pull (Fetch & Merge)
Use rebase instead of merge
diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml
index acccc2f67..0aa2d2483 100644
--- a/src/Resources/Locales/es_ES.axaml
+++ b/src/Resources/Locales/es_ES.axaml
@@ -109,7 +109,6 @@
Cambios Locales:
Descartar
Stash & Reaplicar
- Actualizar todos los submódulos
Rama:
¡Tu HEAD actual contiene commit(s) que no están conectados a ningunas ramas/etiquetas! ¿Quieres continuar?
Checkout & Fast-Forward
@@ -481,6 +480,7 @@
Ir a la página anterior
Crear nueva página
Abrir diálogo de preferencias
+ Mostrar menú desplegable del espacio de trabajo
Cambiar página activa
REPOSITORIO
Commit cambios staged
@@ -556,6 +556,8 @@
Cerrar Otras Pestañas
Cerrar Pestañas a la Derecha
Copiar Ruta del Repositorio
+ Mover al Espacio de trabajo
+ Actualizar
Repositorios
Pegar
Hace {0} días
@@ -591,6 +593,10 @@
Usar ancho de pestaña fijo en la barra de título
Usar marco de ventana nativo
HERRAMIENTA DIFF/MERGE
+ Argumentos para Diff
+ Variables disponibles: $LOCAL, $REMOTE
+ Argumentos para Merge
+ Variables disponibles: $BASE, $LOCAL, $REMOTE, $MERGED
Ruta de instalación
Introducir ruta para la herramienta diff/merge
Herramienta
@@ -631,6 +637,8 @@
Clave de firma gpg del usuario
INTEGRACIÓN
SHELL/TERMINAL
+ Argumentos
+ Por favor utiliza '.' para indicar el directorio de trabajo
Ruta
Shell/Terminal
Podar Remoto
@@ -643,7 +651,6 @@
Cambios Locales:
Descartar
Stash & Reaplicar
- Actualizar todos los submódulos
Remoto:
Pull (Fetch & Merge)
Usar rebase en lugar de merge
@@ -795,6 +802,9 @@
Upstream:
Copiar SHA
Ir a
+ Squash HEAD en el Padre
+ Fixup HEAD en el Padre
+ En:
Clave Privada SSH:
Ruta de almacenamiento de la clave privada SSH
INICIAR
diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml
index 2178b207e..5f0c6f109 100644
--- a/src/Resources/Locales/fr_FR.axaml
+++ b/src/Resources/Locales/fr_FR.axaml
@@ -108,7 +108,6 @@
Changements locaux :
Annuler
Mettre en stash et réappliquer
- Mettre à jour tous les sous-modules
Branche :
Votre HEAD actuel contient un ou plusieurs commits non connectés à une branche/tag ! Voulez-vous continuer ?
Récupérer & Fast-Forward
@@ -637,7 +636,6 @@
Changements locaux :
Rejeter
Stash & Réappliquer
- Mettre à jour tous les sous-modules
Dépôt distant :
Pull (Fetch & Merge)
Utiliser rebase au lieu de merge
diff --git a/src/Resources/Locales/id_ID.axaml b/src/Resources/Locales/id_ID.axaml
index bc6e8e95b..77eb5cc57 100644
--- a/src/Resources/Locales/id_ID.axaml
+++ b/src/Resources/Locales/id_ID.axaml
@@ -104,7 +104,6 @@
Perubahan Lokal:
Buang
Stash & Terapkan Ulang
- Perbarui semua submodule
Branch:
HEAD saat ini mengandung commit yang tidak terhubung ke branch/tag manapun! Lanjutkan?
Checkout & Fast-Forward
@@ -612,7 +611,6 @@
Perubahan Lokal:
Buang
Stash & Terapkan Ulang
- Perbarui semua submodule
Remote:
Pull (Fetch & Merge)
Gunakan rebase alih-alih merge
diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml
index df5e6e912..c56954628 100644
--- a/src/Resources/Locales/it_IT.axaml
+++ b/src/Resources/Locales/it_IT.axaml
@@ -98,7 +98,6 @@
Modifiche Locali:
Scarta
Stasha e Ripristina
- Aggiorna tutti i sottomoduli
Branch:
Il tuo HEAD attuale contiene commit non connessi ad alcun branch/tag! Sicuro di voler continuare?
Checkout & Avanzamento Veloce
@@ -593,7 +592,6 @@
Modifiche Locali:
Scarta
Stasha e Riapplica
- Aggiorna tutti i sottomoduli
Remoto:
Scarica (Recupera e Unisci)
Riallineare anziché unire
diff --git a/src/Resources/Locales/ko_KR.axaml b/src/Resources/Locales/ko_KR.axaml
index 67d3d5ca1..5cc2a06a1 100644
--- a/src/Resources/Locales/ko_KR.axaml
+++ b/src/Resources/Locales/ko_KR.axaml
@@ -101,7 +101,6 @@
로컬 변경 사항:
폐기
스태시 & 재적용
- 모든 서브모듈 업데이트
브랜치:
현재 HEAD에 브랜치/태그에 연결되지 않은 커밋이 있습니다! 계속하시겠습니까?
체크아웃 & Fast-Forward
@@ -613,7 +612,6 @@
로컬 변경 사항:
폐기
스태시 & 재적용
- 모든 서브모듈 업데이트
원격:
Pull (Fetch & 병합)
병합 대신 리베이스 사용
diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml
index dafade49b..ea658ef9e 100644
--- a/src/Resources/Locales/ru_RU.axaml
+++ b/src/Resources/Locales/ru_RU.axaml
@@ -109,7 +109,6 @@
Локальные изменения:
Отклонить
Отложить и применить повторно
- Обновить все подкаталоги
Ветка:
Ваша текущая ГОЛОВА содержит ревизию(и), не связанные ни с к какими ветками или метками! Вы хотите продолжить?
Переключиться и перемотать
@@ -594,6 +593,7 @@
Использовать фиксированную ширину табуляции в строке заголовка.
Использовать системное окно
ИНСТРУМЕНТ СРАВНЕНИЙ/СЛИЯНИЯ
+ Аргументы сравнения
Доступны переменные: $LOCAL, $REMOTE
Слить аргументы
Доступны переменные: $BASE, $LOCAL, $REMOTE, $MERGED
@@ -651,7 +651,6 @@
Локальные изменения:
Отклонить
Отложить и применить повторно
- Обновить все подмодули
Внешний репозиторий:
Загрузить (Получить и слить)
Использовать перемещение вместо слияния
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index b9a5fb63f..bab627b7f 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -109,7 +109,6 @@
未提交更改 :
丢弃更改
贮藏并自动恢复
- 同时更新所有子模块
目标分支 :
您当前游离的HEAD包含未被任何分支及标签引用的提交!是否继续?
检出分支并快进
@@ -652,7 +651,6 @@
未提交更改 :
丢弃更改
贮藏并自动恢复
- 同时更新所有子模块
远程 :
拉回(拉取并合并)
使用变基方式合并分支
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index 9178f6fef..a2a4443c8 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -109,7 +109,6 @@
未提交變更:
捨棄變更
擱置變更並自動復原
- 同時更新所有子模組
目標分支:
您目前的分離的 HEAD 包含與任何分支/標籤無關的提交! 您要繼續嗎?
簽出分支並快轉
@@ -652,7 +651,6 @@
未提交變更:
捨棄變更
擱置變更並自動復原
- 同時更新所有子模組
遠端:
拉取 (提取並合併)
使用重定基底 (rebase) 合併分支
diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml
index 651bc9505..f259fd32d 100644
--- a/src/Resources/Styles.axaml
+++ b/src/Resources/Styles.axaml
@@ -13,16 +13,6 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -169,7 +188,7 @@
Opacity="{Binding IsMerged, Converter={x:Static c:BoolConverters.IsMergedToOpacity}}"
IsHitTestVisible="False"/>
-
+
0)
@@ -77,7 +78,7 @@ public override void Render(DrawingContext context)
var container = LauncherTabsList.ContainerFromIndex(0);
if (container != null)
{
- var x = container.Bounds.Left - startX + LauncherTabsScroller.Bounds.X;
+ var x = container.Bounds.Left - startX + LauncherTabsScroller.Bounds.X - 0.5;
context.DrawLine(separatorPen, new Point(x, separatorY), new Point(x, separatorY + 18));
}
}
@@ -98,7 +99,7 @@ public override void Render(DrawingContext context)
if (IsScrollerVisible && i == count - 1)
break;
- var separatorX = containerEndX - startX + LauncherTabsScroller.Bounds.X;
+ var separatorX = containerEndX - startX + LauncherTabsScroller.Bounds.X - 0.5;
context.DrawLine(separatorPen, new Point(separatorX, separatorY), new Point(separatorX, separatorY + 18));
}
@@ -113,54 +114,37 @@ public override void Render(DrawingContext context)
var geo = new StreamGeometry();
const double angle = Math.PI / 2;
- var y = height + 0.5;
+ var bottom = height + 0.5;
+ var cornerSize = new Size(5, 5);
+
using (var ctx = geo.Open())
{
- double x;
-
var drawLeftX = activeStartX - startX + LauncherTabsScroller.Bounds.X;
- var drawRightX = activeEndX - startX + LauncherTabsScroller.Bounds.X;
if (drawLeftX < LauncherTabsScroller.Bounds.X)
{
- x = LauncherTabsScroller.Bounds.X;
- ctx.BeginFigure(new Point(x, y), true);
- y = 1;
- ctx.LineTo(new Point(x, y));
+ ctx.BeginFigure(new Point(LauncherTabsScroller.Bounds.X - 0.5, bottom), true);
+ ctx.LineTo(new Point(LauncherTabsScroller.Bounds.X - 0.5, 0.5));
}
else
{
- x = drawLeftX - 5;
- ctx.BeginFigure(new Point(x, y), true);
- x = drawLeftX;
- y -= 5;
- ctx.ArcTo(new Point(x, y), new Size(5, 5), angle, false, SweepDirection.CounterClockwise);
- y = 6;
- ctx.LineTo(new Point(x, y));
- x += 6;
- y = 1;
- ctx.ArcTo(new Point(x, y), new Size(6, 6), angle, false, SweepDirection.Clockwise);
+ ctx.BeginFigure(new Point(drawLeftX - 5.5, bottom), true);
+ ctx.ArcTo(new Point(drawLeftX - 0.5, bottom - 5), cornerSize, angle, false, SweepDirection.CounterClockwise);
+ ctx.LineTo(new Point(drawLeftX - 0.5, 5.5));
+ ctx.ArcTo(new Point(drawLeftX + 4.5, 0.5), cornerSize, angle, false, SweepDirection.Clockwise);
}
- x = drawRightX - 6;
-
+ var drawRightX = activeEndX - startX + LauncherTabsScroller.Bounds.X;
if (drawRightX <= LauncherTabsScroller.Bounds.Right)
{
- ctx.LineTo(new Point(x, y));
- x = drawRightX;
- y = 6;
- ctx.ArcTo(new Point(x, y), new Size(6, 6), angle, false, SweepDirection.Clockwise);
- y = height + 0.5 - 5;
- ctx.LineTo(new Point(x, y));
- x += 5;
- y = height + 0.5;
- ctx.ArcTo(new Point(x, y), new Size(5, 5), angle, false, SweepDirection.CounterClockwise);
+ ctx.LineTo(new Point(drawRightX - 5.5, 0.5));
+ ctx.ArcTo(new Point(drawRightX - 0.5, 5.5), cornerSize, angle, false, SweepDirection.Clockwise);
+ ctx.LineTo(new Point(drawRightX - 0.5, bottom - 5));
+ ctx.ArcTo(new Point(drawRightX + 4.5, bottom), cornerSize, angle, false, SweepDirection.CounterClockwise);
}
else
{
- x = LauncherTabsScroller.Bounds.Right;
- ctx.LineTo(new Point(x, y));
- y = height + 0.5;
- ctx.LineTo(new Point(x, y));
+ ctx.LineTo(new Point(LauncherTabsScroller.Bounds.Right - 0.5, 0.5));
+ ctx.LineTo(new Point(LauncherTabsScroller.Bounds.Right - 0.5, bottom));
}
}
@@ -169,6 +153,14 @@ public override void Render(DrawingContext context)
context.DrawGeometry(fill, stroke, geo);
}
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property.Name == nameof(ActualThemeVariant))
+ InvalidateVisual();
+ }
+
private void ScrollTabs(object _, PointerWheelEventArgs e)
{
if (!e.KeyModifiers.HasFlag(KeyModifiers.Shift))
@@ -346,10 +338,12 @@ private void OnTabContextRequested(object sender, ContextRequestedEventArgs e)
{
var dupWs = ws;
var isCurrent = dupWs == vm.ActiveWorkspace;
+ var icon = App.CreateMenuIcon(isCurrent ? "Icons.Check" : "Icons.Workspace");
+ icon.Fill = dupWs.Brush;
var target = new MenuItem();
target.Header = ws.Name;
- target.Icon = App.CreateMenuIcon(isCurrent ? "Icons.Check" : "Icons.Workspace");
+ target.Icon = icon;
target.Click += (_, ev) =>
{
if (!isCurrent)
diff --git a/src/Views/Merge.axaml b/src/Views/Merge.axaml
index dffcc6fd9..5ceb93ff7 100644
--- a/src/Views/Merge.axaml
+++ b/src/Views/Merge.axaml
@@ -19,7 +19,7 @@
Text="{DynamicResource Text.Merge}"/>
-
+
diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml
index 37d83012f..0d5c3f8d3 100644
--- a/src/Views/Preferences.axaml
+++ b/src/Views/Preferences.axaml
@@ -758,12 +758,13 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Views/Pull.axaml b/src/Views/Pull.axaml
index e882132e9..b09f280f4 100644
--- a/src/Views/Pull.axaml
+++ b/src/Views/Pull.axaml
@@ -25,7 +25,6 @@
-
-
-
diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml
index ef472c321..df979c54e 100644
--- a/src/Views/RepositoryConfigure.axaml
+++ b/src/Views/RepositoryConfigure.axaml
@@ -514,12 +514,13 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Views/RevisionCompare.axaml b/src/Views/RevisionCompare.axaml
index 87424f0ab..77a215df1 100644
--- a/src/Views/RevisionCompare.axaml
+++ b/src/Views/RevisionCompare.axaml
@@ -65,7 +65,7 @@
-
+
+
+
+
+
+
+
+
+
-
+