diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 374bd841..0e462a55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,3 +99,41 @@ jobs: with: name: sourcegit.linux-x64 path: sourcegit.linux-x64.tar + build-linux-arm64: + name: Build Linux (arm64) + runs-on: ubuntu-20.04 + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Configure arm64 packages + run: | + sudo dpkg --add-architecture arm64 + echo 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main restricted + deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted + deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted' \ + | sudo tee /etc/apt/sources.list.d/arm64.list + sudo sed -i -e 's/^deb http/deb [arch=amd64] http/g' /etc/apt/sources.list + sudo sed -i -e 's/^deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list + - name: Install cross-compiling dependencies + run: | + sudo apt-get update + sudo apt-get install clang llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64 + - name: Build + run: dotnet build -c Release + - name: Publish + run: dotnet publish src/SourceGit.csproj -c Release -o publish -r linux-arm64 + - name: Rename Executable File + run: mv publish/SourceGit publish/sourcegit + - name: Packing Program + run: tar -cvf sourcegit.linux-arm64.tar -C publish/ . + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: sourcegit.linux-arm64 + path: sourcegit.linux-arm64.tar diff --git a/README.md b/README.md index 50f0be5d..0af00613 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Opensource Git GUI client. * GIT commands with GUI * Clone/Fetch/Pull/Push... * Merge/Rebase/Reset/Revert/Amend/Cherry-pick... + * Amend/Reword * Interactive rebase (Basic) * Branches * Remotes @@ -30,8 +31,9 @@ Opensource Git GUI client. * Revision Diffs * Branch Diff * Image Diff - Side-By-Side/Swipe/Blend -* GitFlow support -* Git LFS support +* GitFlow +* Git LFS +* Issue Link > **Linux** only tested on **Debian 12** on both **X11** & **Wayland**. diff --git a/VERSION b/VERSION index 4399d1b4..86a3e56a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.23 \ No newline at end of file +8.24 \ No newline at end of file diff --git a/build/resources/_common/applications/sourcegit.desktop b/build/resources/_common/applications/sourcegit.desktop index 5b5f5bba..ff7ef135 100644 --- a/build/resources/_common/applications/sourcegit.desktop +++ b/build/resources/_common/applications/sourcegit.desktop @@ -5,4 +5,5 @@ Exec=/opt/sourcegit/sourcegit Icon=/usr/share/icons/sourcegit.png Terminal=false Type=Application -Categories=Development \ No newline at end of file +Categories=Development +MimeType=inode/directory; diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 3cb3909c..a8f1e32e 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -208,8 +208,8 @@ namespace SourceGit { if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - if (desktop.MainWindow?.Clipboard is { } clipbord) - await clipbord.SetTextAsync(data); + if (desktop.MainWindow?.Clipboard is { } clipboard) + await clipboard.SetTextAsync(data); } } diff --git a/src/Models/AvatarManager.cs b/src/Models/AvatarManager.cs index ed39bcf7..e85de1fd 100644 --- a/src/Models/AvatarManager.cs +++ b/src/Models/AvatarManager.cs @@ -1,21 +1,26 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Avalonia.Media.Imaging; +using Avalonia.Platform; using Avalonia.Threading; namespace SourceGit.Models { public interface IAvatarHost { - void OnAvatarResourceChanged(string md5); + void OnAvatarResourceChanged(string email); } - public static class AvatarManager + public static partial class AvatarManager { public static string SelectedServer { @@ -29,33 +34,42 @@ namespace SourceGit.Models if (!Directory.Exists(_storePath)) Directory.CreateDirectory(_storePath); + var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/github.png", UriKind.RelativeOrAbsolute)); + _resources.Add("noreply@github.com", new Bitmap(icon)); + Task.Run(() => { while (true) { - var md5 = null as string; + var email = null as string; lock (_synclock) { foreach (var one in _requesting) { - md5 = one; + email = one; break; } } - if (md5 == null) + if (email == null) { Thread.Sleep(100); continue; } + var md5 = GetEmailHash(email); + var matchGithubUser = REG_GITHUB_USER_EMAIL().Match(email); + var url = matchGithubUser.Success ? + $"https://avatars.githubusercontent.com/{matchGithubUser.Groups[2].Value}" : + $"{SelectedServer}{md5}?d=404"; + var localFile = Path.Combine(_storePath, md5); var img = null as Bitmap; try { var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) }; - var task = client.GetAsync($"{SelectedServer}{md5}?d=404"); + var task = client.GetAsync(url); task.Wait(); var rsp = task.Result; @@ -82,13 +96,13 @@ namespace SourceGit.Models lock (_synclock) { - _requesting.Remove(md5); + _requesting.Remove(email); } Dispatcher.UIThread.InvokeAsync(() => { - _resources[md5] = img; - NotifyResourceChanged(md5); + _resources[email] = img; + NotifyResourceChanged(email); }); } }); @@ -104,25 +118,28 @@ namespace SourceGit.Models _avatars.Remove(host); } - public static Bitmap Request(string md5, bool forceRefetch = false) + public static Bitmap Request(string email, bool forceRefetch) { if (forceRefetch) { - if (_resources.ContainsKey(md5)) - _resources.Remove(md5); + if (email.Equals("noreply@github.com", StringComparison.Ordinal)) + return null; - var localFile = Path.Combine(_storePath, md5); + if (_resources.ContainsKey(email)) + _resources.Remove(email); + + var localFile = Path.Combine(_storePath, GetEmailHash(email)); if (File.Exists(localFile)) File.Delete(localFile); - NotifyResourceChanged(md5); + NotifyResourceChanged(email); } else { - if (_resources.TryGetValue(md5, out var value)) + if (_resources.TryGetValue(email, out var value)) return value; - var localFile = Path.Combine(_storePath, md5); + var localFile = Path.Combine(_storePath, GetEmailHash(email)); if (File.Exists(localFile)) { try @@ -130,7 +147,7 @@ namespace SourceGit.Models using (var stream = File.OpenRead(localFile)) { var img = Bitmap.DecodeToWidth(stream, 128); - _resources.Add(md5, img); + _resources.Add(email, img); return img; } } @@ -143,18 +160,28 @@ namespace SourceGit.Models lock (_synclock) { - if (!_requesting.Contains(md5)) - _requesting.Add(md5); + if (!_requesting.Contains(email)) + _requesting.Add(email); } return null; } - private static void NotifyResourceChanged(string md5) + private static string GetEmailHash(string email) + { + var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim(); + var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered)); + var builder = new StringBuilder(); + foreach (var c in hash) + builder.Append(c.ToString("x2")); + return builder.ToString(); + } + + private static void NotifyResourceChanged(string email) { foreach (var avatar in _avatars) { - avatar.OnAvatarResourceChanged(md5); + avatar.OnAvatarResourceChanged(email); } } @@ -163,5 +190,8 @@ namespace SourceGit.Models private static readonly List _avatars = new List(); private static readonly Dictionary _resources = new Dictionary(); private static readonly HashSet _requesting = new HashSet(); + + [GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")] + private static partial Regex REG_GITHUB_USER_EMAIL(); } } diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs index 9a27db0b..9855f9d3 100644 --- a/src/Models/ExternalMerger.cs +++ b/src/Models/ExternalMerger.cs @@ -20,7 +20,7 @@ namespace SourceGit.Models { get { - var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute)); + var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute)); return new Bitmap(icon); } } diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs index 401fb987..5ea8c744 100644 --- a/src/Models/ExternalTool.cs +++ b/src/Models/ExternalTool.cs @@ -25,7 +25,7 @@ namespace SourceGit.Models try { - var asset = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{icon}.png", + var asset = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/ExternalToolIcons/{icon}.png", UriKind.RelativeOrAbsolute)); IconImage = new Bitmap(asset); } diff --git a/src/Models/IssueTrackerRule.cs b/src/Models/IssueTrackerRule.cs new file mode 100644 index 00000000..a4a9a3d1 --- /dev/null +++ b/src/Models/IssueTrackerRule.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; + +using Avalonia.Collections; + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.Models +{ + public class IssueTrackerMatch + { + public int Start { get; set; } = 0; + public int Length { get; set; } = 0; + public string URL { get; set; } = ""; + + public bool Intersect(int start, int length) + { + if (start == Start) + return true; + + if (start < Start) + return start + length > Start; + + return start < Start + Length; + } + } + + public class IssueTrackerRule : ObservableObject + { + public string Name + { + get => _name; + set => SetProperty(ref _name, value); + } + + public string RegexString + { + get => _regexString; + set + { + if (SetProperty(ref _regexString, value)) + { + try + { + _regex = null; + _regex = new Regex(_regexString, RegexOptions.Multiline); + } + catch + { + // Ignore errors. + } + } + + OnPropertyChanged(nameof(IsRegexValid)); + } + } + + public bool IsRegexValid + { + get => _regex != null; + } + + public string URLTemplate + { + get => _urlTemplate; + set => SetProperty(ref _urlTemplate, value); + } + + public void Matches(List outs, string message) + { + if (_regex == null || string.IsNullOrEmpty(_urlTemplate)) + return; + + var matches = _regex.Matches(message); + for (var i = 0; i < matches.Count; i++) + { + var match = matches[i]; + if (!match.Success) + continue; + + var start = match.Index; + var len = match.Length; + var intersect = false; + foreach (var exist in outs) + { + if (exist.Intersect(start, len)) + { + intersect = true; + break; + } + } + + if (intersect) + continue; + + var range = new IssueTrackerMatch(); + range.Start = start; + range.Length = len; + range.URL = _urlTemplate; + for (var j = 1; j < match.Groups.Count; j++) + { + var group = match.Groups[j]; + if (group.Success) + range.URL = range.URL.Replace($"${j}", group.Value); + } + + outs.Add(range); + } + } + + private string _name; + private string _regexString; + private string _urlTemplate; + private Regex _regex = null; + } +} diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs index 514a0d59..bf15c4f4 100644 --- a/src/Models/RepositorySettings.cs +++ b/src/Models/RepositorySettings.cs @@ -76,6 +76,12 @@ namespace SourceGit.Models set; } = new AvaloniaList(); + public AvaloniaList IssueTrackerRules + { + get; + set; + } = new AvaloniaList(); + public void PushCommitMessage(string message) { var existIdx = CommitMessages.IndexOf(message); @@ -93,5 +99,50 @@ namespace SourceGit.Models CommitMessages.Insert(0, message); } + + public IssueTrackerRule AddNewIssueTracker() + { + var rule = new IssueTrackerRule() + { + Name = "New Issue Tracker", + RegexString = "#(\\d+)", + URLTemplate = "https://xxx/$1", + }; + + IssueTrackerRules.Add(rule); + return rule; + } + + public IssueTrackerRule AddGithubIssueTracker(string repoURL) + { + var rule = new IssueTrackerRule() + { + Name = "Github ISSUE", + RegexString = "#(\\d+)", + URLTemplate = string.IsNullOrEmpty(repoURL) ? "https://github.com/username/repository/issues/$1" : $"{repoURL}/issues/$1", + }; + + IssueTrackerRules.Add(rule); + return rule; + } + + public IssueTrackerRule AddJiraIssueTracker() + { + var rule = new IssueTrackerRule() + { + Name = "Jira Tracker", + RegexString = "PROJ-(\\d+)", + URLTemplate = "https://jira.yourcompany.com/browse/PROJ-$1", + }; + + IssueTrackerRules.Add(rule); + return rule; + } + + public void RemoveIssueTracker(IssueTrackerRule rule) + { + if (rule != null) + IssueTrackerRules.Remove(rule); + } } } diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index e2940f68..1657ead3 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -52,6 +52,7 @@ M512 0C229 0 0 229 0 512s229 512 512 512 512-229 512-512S795 0 512 0zM512 928c-230 0-416-186-416-416S282 96 512 96s416 186 416 416S742 928 512 928zM538 343c47 0 83-38 83-78 0-32-21-61-62-61-55 0-82 45-82 77C475 320 498 343 538 343zM533 729c-8 0-11-10-3-40l43-166c16-61 11-100-22-100-39 0-131 40-211 108l16 27c25-17 68-35 78-35 8 0 7 10 0 36l-38 158c-23 89 1 110 34 110 33 0 118-30 196-110l-19-25C575 717 543 729 533 729z M412 66C326 132 271 233 271 347c0 17 1 34 4 50-41-48-98-79-162-83a444 444 0 00-46 196c0 207 142 382 337 439h2c19 0 34 15 34 33 0 11-6 21-14 26l1 14C183 973 0 763 0 511 0 272 166 70 393 7A35 35 0 01414 0c19 0 34 15 34 33a33 33 0 01-36 33zm200 893c86-66 141-168 141-282 0-17-1-34-4-50 41 48 98 79 162 83a444 444 0 0046-196c0-207-142-382-337-439h-2a33 33 0 01-34-33c0-11 6-21 14-26L596 0C841 51 1024 261 1024 513c0 239-166 441-393 504A35 35 0 01610 1024a33 33 0 01-34-33 33 33 0 0136-33zM512 704a192 192 0 110-384 192 192 0 010 384z M512 64A447 447 0 0064 512c0 248 200 448 448 448s448-200 448-448S760 64 512 64zM218 295h31c54 0 105 19 145 55 13 12 13 31 3 43a35 35 0 01-22 10 36 36 0 01-21-7 155 155 0 00-103-39h-31a32 32 0 01-31-31c0-18 13-31 30-31zm31 433h-31a32 32 0 01-31-31c0-16 13-31 31-31h31A154 154 0 00403 512 217 217 0 01620 295h75l-93-67a33 33 0 01-7-43 33 33 0 0143-7l205 148-205 148a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67H620a154 154 0 00-154 154c0 122-97 220-217 220zm390 118a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67h-75c-52 0-103-19-143-54-12-12-13-31-1-43a30 30 0 0142-3 151 151 0 00102 39h75L602 599a33 33 0 01-7-43 33 33 0 0143-7l205 148-203 151z + M922 39H102A65 65 0 0039 106v609a65 65 0 0063 68h94v168a34 34 0 0019 31 30 30 0 0012 3 30 30 0 0022-10l182-192H922a65 65 0 0063-68V106A65 65 0 00922 39zM288 378h479a34 34 0 010 68H288a34 34 0 010-68zm0-135h479a34 34 0 010 68H288a34 34 0 010-68zm0 270h310a34 34 0 010 68H288a34 34 0 010-68z M640 96c-158 0-288 130-288 288 0 17 3 31 5 46L105 681 96 691V928h224v-96h96v-96h96v-95c38 18 82 31 128 31 158 0 288-130 288-288s-130-288-288-288zm0 64c123 0 224 101 224 224s-101 224-224 224a235 235 0 01-109-28l-8-4H448v96h-96v96H256v96H160v-146l253-254 12-11-3-17C419 417 416 400 416 384c0-123 101-224 224-224zm64 96a64 64 0 100 128 64 64 0 100-128z M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zM139 832V192c0-6 4-11 11-11h331v661H149c-6 0-11-4-11-11zm747 0c0 6-4 11-11 11H544v-661H875c6 0 11 4 11 11v640z M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zm-725 64h725c6 0 11 4 11 11v288h-747V192c0-6 4-11 11-11zm725 661H149c-6 0-11-4-11-11V544h747V832c0 6-4 11-11 11z @@ -67,6 +68,7 @@ M0 4 0 20 16 20 0 4M4 0 20 0 20 16 4 0z M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z M824 645V307c0-56-46-102-102-102h-102V102l-154 154 154 154V307h102v338c-46 20-82 67-82 123 0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123zm-51 195c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72zM384 256c0-72-61-133-133-133-72 0-133 61-133 133 0 56 36 102 82 123v266C154 666 118 712 118 768c0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123V379C348 358 384 312 384 256zM323 768c0 41-31 72-72 72-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72zM251 328c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72z + M299 811 299 725 384 725 384 811 299 811M469 811 469 725 555 725 555 811 469 811M640 811 640 725 725 725 725 811 640 811M299 640 299 555 384 555 384 640 299 640M469 640 469 555 555 555 555 640 469 640M640 640 640 555 725 555 725 640 640 640M299 469 299 384 384 384 384 469 299 469M469 469 469 384 555 384 555 469 469 469M640 469 640 384 725 384 725 469 640 469M299 299 299 213 384 213 384 299 299 299M469 299 469 213 555 213 555 299 469 299M640 299 640 213 725 213 725 299 640 299Z M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z M544 85c49 0 90 37 95 85h75a96 96 0 0196 89L811 267a32 32 0 01-28 32L779 299a32 32 0 01-32-28L747 267a32 32 0 00-28-32L715 235h-91a96 96 0 01-80 42H395c-33 0-62-17-80-42L224 235a32 32 0 00-32 28L192 267v576c0 16 12 30 28 32l4 0h128a32 32 0 0132 28l0 4a32 32 0 01-32 32h-128a96 96 0 01-96-89L128 843V267a96 96 0 0189-96L224 171h75a96 96 0 0195-85h150zm256 256a96 96 0 0196 89l0 7v405a96 96 0 01-89 96L800 939h-277a96 96 0 01-96-89L427 843v-405a96 96 0 0189-96L523 341h277zm-256-192H395a32 32 0 000 64h150a32 32 0 100-64z m186 532 287 0 0 287c0 11 9 20 20 20s20-9 20-20l0-287 287 0c11 0 20-9 20-20s-9-20-20-20l-287 0 0-287c0-11-9-20-20-20s-20 9-20 20l0 287-287 0c-11 0-20 9-20 20s9 20 20 20z diff --git a/src/Resources/ExternalToolIcons/JetBrains/CL.png b/src/Resources/Images/ExternalToolIcons/JetBrains/CL.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/CL.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/CL.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/DB.png b/src/Resources/Images/ExternalToolIcons/JetBrains/DB.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/DB.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/DB.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/DL.png b/src/Resources/Images/ExternalToolIcons/JetBrains/DL.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/DL.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/DL.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/DS.png b/src/Resources/Images/ExternalToolIcons/JetBrains/DS.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/DS.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/DS.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/GO.png b/src/Resources/Images/ExternalToolIcons/JetBrains/GO.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/GO.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/GO.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/JB.png b/src/Resources/Images/ExternalToolIcons/JetBrains/JB.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/JB.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/JB.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/PC.png b/src/Resources/Images/ExternalToolIcons/JetBrains/PC.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/PC.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/PC.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/PS.png b/src/Resources/Images/ExternalToolIcons/JetBrains/PS.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/PS.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/PS.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/PY.png b/src/Resources/Images/ExternalToolIcons/JetBrains/PY.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/PY.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/PY.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/QA.png b/src/Resources/Images/ExternalToolIcons/JetBrains/QA.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/QA.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/QA.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/QD.png b/src/Resources/Images/ExternalToolIcons/JetBrains/QD.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/QD.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/QD.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/RD.png b/src/Resources/Images/ExternalToolIcons/JetBrains/RD.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/RD.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/RD.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/RM.png b/src/Resources/Images/ExternalToolIcons/JetBrains/RM.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/RM.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/RM.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/RR.png b/src/Resources/Images/ExternalToolIcons/JetBrains/RR.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/RR.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/RR.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/WRS.png b/src/Resources/Images/ExternalToolIcons/JetBrains/WRS.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/WRS.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/WRS.png diff --git a/src/Resources/ExternalToolIcons/JetBrains/WS.png b/src/Resources/Images/ExternalToolIcons/JetBrains/WS.png similarity index 100% rename from src/Resources/ExternalToolIcons/JetBrains/WS.png rename to src/Resources/Images/ExternalToolIcons/JetBrains/WS.png diff --git a/src/Resources/ExternalToolIcons/beyond_compare.png b/src/Resources/Images/ExternalToolIcons/beyond_compare.png similarity index 100% rename from src/Resources/ExternalToolIcons/beyond_compare.png rename to src/Resources/Images/ExternalToolIcons/beyond_compare.png diff --git a/src/Resources/ExternalToolIcons/codium.png b/src/Resources/Images/ExternalToolIcons/codium.png similarity index 100% rename from src/Resources/ExternalToolIcons/codium.png rename to src/Resources/Images/ExternalToolIcons/codium.png diff --git a/src/Resources/ExternalToolIcons/fleet.png b/src/Resources/Images/ExternalToolIcons/fleet.png similarity index 100% rename from src/Resources/ExternalToolIcons/fleet.png rename to src/Resources/Images/ExternalToolIcons/fleet.png diff --git a/src/Resources/ExternalToolIcons/git.png b/src/Resources/Images/ExternalToolIcons/git.png similarity index 100% rename from src/Resources/ExternalToolIcons/git.png rename to src/Resources/Images/ExternalToolIcons/git.png diff --git a/src/Resources/ExternalToolIcons/kdiff3.png b/src/Resources/Images/ExternalToolIcons/kdiff3.png similarity index 100% rename from src/Resources/ExternalToolIcons/kdiff3.png rename to src/Resources/Images/ExternalToolIcons/kdiff3.png diff --git a/src/Resources/ExternalToolIcons/meld.png b/src/Resources/Images/ExternalToolIcons/meld.png similarity index 100% rename from src/Resources/ExternalToolIcons/meld.png rename to src/Resources/Images/ExternalToolIcons/meld.png diff --git a/src/Resources/ExternalToolIcons/p4merge.png b/src/Resources/Images/ExternalToolIcons/p4merge.png similarity index 100% rename from src/Resources/ExternalToolIcons/p4merge.png rename to src/Resources/Images/ExternalToolIcons/p4merge.png diff --git a/src/Resources/ExternalToolIcons/rider.png b/src/Resources/Images/ExternalToolIcons/rider.png similarity index 100% rename from src/Resources/ExternalToolIcons/rider.png rename to src/Resources/Images/ExternalToolIcons/rider.png diff --git a/src/Resources/ExternalToolIcons/sublime_text.png b/src/Resources/Images/ExternalToolIcons/sublime_text.png similarity index 100% rename from src/Resources/ExternalToolIcons/sublime_text.png rename to src/Resources/Images/ExternalToolIcons/sublime_text.png diff --git a/src/Resources/ExternalToolIcons/tortoise_merge.png b/src/Resources/Images/ExternalToolIcons/tortoise_merge.png similarity index 100% rename from src/Resources/ExternalToolIcons/tortoise_merge.png rename to src/Resources/Images/ExternalToolIcons/tortoise_merge.png diff --git a/src/Resources/ExternalToolIcons/vs.png b/src/Resources/Images/ExternalToolIcons/vs.png similarity index 100% rename from src/Resources/ExternalToolIcons/vs.png rename to src/Resources/Images/ExternalToolIcons/vs.png diff --git a/src/Resources/ExternalToolIcons/vscode.png b/src/Resources/Images/ExternalToolIcons/vscode.png similarity index 100% rename from src/Resources/ExternalToolIcons/vscode.png rename to src/Resources/Images/ExternalToolIcons/vscode.png diff --git a/src/Resources/ExternalToolIcons/vscode_insiders.png b/src/Resources/Images/ExternalToolIcons/vscode_insiders.png similarity index 100% rename from src/Resources/ExternalToolIcons/vscode_insiders.png rename to src/Resources/Images/ExternalToolIcons/vscode_insiders.png diff --git a/src/Resources/ExternalToolIcons/win_merge.png b/src/Resources/Images/ExternalToolIcons/win_merge.png similarity index 100% rename from src/Resources/ExternalToolIcons/win_merge.png rename to src/Resources/Images/ExternalToolIcons/win_merge.png diff --git a/src/Resources/ExternalToolIcons/xcode.png b/src/Resources/Images/ExternalToolIcons/xcode.png similarity index 100% rename from src/Resources/ExternalToolIcons/xcode.png rename to src/Resources/Images/ExternalToolIcons/xcode.png diff --git a/src/Resources/ShellIcons/cmd.png b/src/Resources/Images/ShellIcons/cmd.png similarity index 100% rename from src/Resources/ShellIcons/cmd.png rename to src/Resources/Images/ShellIcons/cmd.png diff --git a/src/Resources/ShellIcons/git-bash.png b/src/Resources/Images/ShellIcons/git-bash.png similarity index 100% rename from src/Resources/ShellIcons/git-bash.png rename to src/Resources/Images/ShellIcons/git-bash.png diff --git a/src/Resources/ShellIcons/pwsh.png b/src/Resources/Images/ShellIcons/pwsh.png similarity index 100% rename from src/Resources/ShellIcons/pwsh.png rename to src/Resources/Images/ShellIcons/pwsh.png diff --git a/src/Resources/ShellIcons/wt.png b/src/Resources/Images/ShellIcons/wt.png similarity index 100% rename from src/Resources/ShellIcons/wt.png rename to src/Resources/Images/ShellIcons/wt.png diff --git a/src/Resources/Images/github.png b/src/Resources/Images/github.png new file mode 100644 index 00000000..3a7abb16 Binary files /dev/null and b/src/Resources/Images/github.png differ diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index d95d4c74..2c1e570e 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -124,6 +124,15 @@ Repository Configure Email Address Email address + GIT + ISSUE TRACKER + Add Sample Github Rule + Add Sample Jira Rule + New Rule + Issue Regex Expression: + Rule Name: + Result URL: + Please use $1, $2 to access regex groups values. HTTP Proxy HTTP proxy used by this repository User Name diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 537c0d6c..c0ef6b6e 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -127,6 +127,15 @@ 仓库配置 电子邮箱 邮箱地址 + GIT配置 + ISSUE追踪 + 新增匹配Github Issue规则 + 新增匹配Jira规则 + 新增自定义规则 + 匹配ISSUE的正则表达式 : + 规则名 : + 为ISSUE生成的URL链接 : + 可在URL中使用$1,$2等变量填入正则表达式匹配的内容 HTTP代理 HTTP网络代理 用户名 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 25df2430..f991c912 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -127,6 +127,15 @@ 倉庫配置 電子郵箱 郵箱地址 + GIT配置 + ISSUE追蹤 + 新增匹配Github Issue規則 + 新增匹配Jira規則 + 新增自定義規則 + 匹配ISSUE的正則表達式 : + 規則名 : + 為ISSUE生成的URL連結 : + 可在URL中使用$1,$2等變數填入正則表示式匹配的內容 HTTP代理 HTTP網路代理 使用者名稱 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index 64a6c9b9..ffe69765 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -164,6 +164,7 @@ + @@ -276,6 +277,13 @@ + + @@ -45,7 +47,7 @@ - + MessageProperty = + AvaloniaProperty.Register(nameof(Message)); + + public string Message + { + get => GetValue(MessageProperty); + set => SetValue(MessageProperty, value); + } + + public static readonly StyledProperty> IssueTrackerRulesProperty = + AvaloniaProperty.Register>(nameof(IssueTrackerRules)); + + public AvaloniaList IssueTrackerRules + { + get => GetValue(IssueTrackerRulesProperty); + set => SetValue(IssueTrackerRulesProperty, value); + } + + protected override Type StyleKeyOverride => typeof(SelectableTextBlock); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == MessageProperty || change.Property == IssueTrackerRulesProperty) + { + Inlines.Clear(); + + var message = Message; + if (string.IsNullOrEmpty(message)) + return; + + var rules = IssueTrackerRules; + if (rules == null || rules.Count == 0) + { + Inlines.Add(new Run(message)); + return; + } + + var matches = new List(); + foreach (var rule in rules) + rule.Matches(matches, message); + + if (matches.Count == 0) + { + Inlines.Add(new Run(message)); + return; + } + + matches.Sort((l, r) => l.Start - r.Start); + + int pos = 0; + foreach (var match in matches) + { + if (match.Start > pos) + Inlines.Add(new Run(message.Substring(pos, match.Start - pos))); + + var link = new TextBlock(); + link.SetValue(TextProperty, message.Substring(match.Start, match.Length)); + link.SetValue(ToolTip.TipProperty, match.URL); + link.Classes.Add("issue_link"); + link.PointerPressed += OnLinkPointerPressed; + Inlines.Add(link); + + pos = match.Start + match.Length; + } + + if (pos < message.Length) + Inlines.Add(new Run(message.Substring(pos))); + } + } + + private void OnLinkPointerPressed(object sender, PointerPressedEventArgs e) + { + if (sender is TextBlock text) + { + var tooltip = text.GetValue(ToolTip.TipProperty) as string; + if (!string.IsNullOrEmpty(tooltip)) + Native.OS.OpenBrowser(tooltip); + + e.Handled = true; + } + } + } +} diff --git a/src/Views/FileHistories.axaml b/src/Views/FileHistories.axaml index 3b44e5ec..ace50cc6 100644 --- a/src/Views/FileHistories.axaml +++ b/src/Views/FileHistories.axaml @@ -26,9 +26,7 @@ Background="{DynamicResource Brush.TitleBar}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}" DoubleTapped="MaximizeOrRestoreWindow" - PointerPressed="BeginMoveWindow" - PointerMoved="MoveWindow" - PointerReleased="EndMoveWindow"/> + PointerPressed="BeginMoveWindow"/> diff --git a/src/Views/FileHistories.axaml.cs b/src/Views/FileHistories.axaml.cs index 0d006413..fc525f14 100644 --- a/src/Views/FileHistories.axaml.cs +++ b/src/Views/FileHistories.axaml.cs @@ -1,4 +1,3 @@ -using Avalonia; using Avalonia.Controls; using Avalonia.Input; @@ -13,8 +12,6 @@ namespace SourceGit.Views private void MaximizeOrRestoreWindow(object _, TappedEventArgs e) { - _pressedTitleBar = false; - if (WindowState == WindowState.Maximized) WindowState = WindowState.Normal; else @@ -25,36 +22,10 @@ namespace SourceGit.Views private void BeginMoveWindow(object _, PointerPressedEventArgs e) { - if (e.ClickCount != 2) - _pressedTitleBar = true; + if (e.ClickCount == 1) + BeginMoveDrag(e); + + e.Handled = true; } - - private void MoveWindow(object _, PointerEventArgs e) - { - if (!_pressedTitleBar || e.Source == null) - return; - - var visual = (Visual)e.Source; - if (visual == null) - return; - -#pragma warning disable CS0618 - BeginMoveDrag(new PointerPressedEventArgs( - e.Source, - e.Pointer, - visual, - e.GetPosition(visual), - e.Timestamp, - new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), - e.KeyModifiers)); -#pragma warning restore CS0618 - } - - private void EndMoveWindow(object _1, PointerReleasedEventArgs _2) - { - _pressedTitleBar = false; - } - - private bool _pressedTitleBar = false; } } diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index 63d4da95..b9ff71e6 100644 --- a/src/Views/Histories.axaml +++ b/src/Views/Histories.axaml @@ -27,10 +27,12 @@ ColumnHeaderHeight="24" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" + ClipboardCopyMode="None" LayoutUpdated="OnCommitDataGridLayoutUpdated" SelectionChanged="OnCommitDataGridSelectionChanged" ContextRequested="OnCommitDataGridContextRequested" - DoubleTapped="OnCommitDataGridDoubleTapped"> + DoubleTapped="OnCommitDataGridDoubleTapped" + KeyDown="OnCommitDataGridKeyDown"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 0faa943b..b309453d 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -16,11 +16,6 @@ namespace SourceGit.Views } private void CloseWindow(object _1, RoutedEventArgs _2) - { - Close(); - } - - private void SaveAndClose(object _1, RoutedEventArgs _2) { (DataContext as ViewModels.RepositoryConfigure)?.Save(); Close();