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 -### ![es__ES](https://img.shields.io/badge/es__ES-98.69%25-yellow) - -
-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 - -
+### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen) ### ![fr__FR](https://img.shields.io/badge/fr__FR-98.04%25-yellow) @@ -71,7 +53,7 @@ This document shows the translation status of each locale file in the repository -### ![id__ID](https://img.shields.io/badge/id__ID-95.87%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-95.86%25-yellow)
Missing keys in id_ID.axaml @@ -117,7 +99,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-93.25%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-93.24%25-yellow)
Missing keys in it_IT.axaml @@ -187,7 +169,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-74.86%25-red) +### ![ja__JP](https://img.shields.io/badge/ja__JP-75.03%25-yellow)
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
-### ![ko__KR](https://img.shields.io/badge/ko__KR-96.19%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-96.18%25-yellow)
Missing keys in ko_KR.axaml @@ -469,7 +449,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-68.66%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-68.81%25-red)
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
-### ![ru__RU](https://img.shields.io/badge/ru__RU-99.89%25-yellow) - -
-Missing keys in ru_RU.axaml - -- Text.Preferences.DiffMerge.DiffArgs - -
+### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) -### ![ta__IN](https://img.shields.io/badge/ta__IN-74.97%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-75.14%25-yellow)
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
-### ![uk__UA](https://img.shields.io/badge/uk__UA-76.06%25-yellow) +### ![uk__UA](https://img.shields.io/badge/uk__UA-76.23%25-yellow)
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 @@ - + + + + + + + + + - +