From b88031f09d8e0317e65d73e31291de174a068556 Mon Sep 17 00:00:00 2001 From: warappa Date: Thu, 1 Aug 2024 17:49:01 +0200 Subject: [PATCH 01/58] Enhance German translation --- src/Resources/Locales/de_DE.axaml | 52 +++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index f8e3f2b3..7a1a57c2 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -4,7 +4,7 @@ Info Über SourceGit - • Erstellen mit + • Erstellt mit © 2024 sourcegit-scm • TextEditor von • Monospace Schriftarten von @@ -26,7 +26,7 @@ Alle Fehler Ähnlich wie 'Fehler', zeigt aber mehr an Patch Datei: - Wählen Sie anzuwendende .patch Datei + Wähle die anzuwendende .patch Datei Ignoriere Leerzeichenänderungen Keine Warnungen Schaltet die Warnung vor überschüssigen Leerzeichen aus @@ -38,7 +38,7 @@ Speichere Archiv in: Wähle Archivpfad aus Revision: - Archiv + Archiv erstellen SourceGit Askpass UNVERÄNDERTE DATEIEN KEINE UNVERÄNDERTEN DATEIEN GEFUNDEN @@ -63,7 +63,7 @@ Rebase ${0}$ auf ${1}$... Benenne ${0}$ um... Setze verfolgten Branch - Upstream Verbindung löschen + Upstream Verfolgung aufheben Branch Vergleich Bytes ABBRECHEN @@ -73,13 +73,13 @@ Zeige als Dateisystembaum Checkout Branch Checkout Commit - Warnung: Beim auschecken eines Commits wird dein HEAD detached sein + Warnung: Beim Auschecken eines Commits wird dein HEAD losgelöst (detached) sein! Commit: Branch: Lokale Änderungen: Verwerfen Nichts tun - Stash & wieder anwenden + Stashen & wieder anwenden Diesen Commit cherry-picken Commit: Alle Änderungen committen @@ -101,9 +101,9 @@ Mit Worktree vergleichen Info kopieren SHA kopieren - Interactives Rebase ${0}$ bis hier - Rebase ${0}$ bis hier - Reset ${0}$ bis hier + Interactives Rebase von ${0}$ auf diesen Commit + Rebase von ${0}$ auf diesen Commit + Reset ${0}$ auf diesen Commit Commit rückgängig machen Umformulieren Als Patch speichern... @@ -118,12 +118,12 @@ GEÄNDERT COMMITTER Zeigt nur die ersten 100 Änderungen. Alle Änderungen im ÄNDERUNGEN Tab. - NACHRICHT + COMMIT-NACHRICHT PARENTS REFS SHA - Commit Nachricht - Beschreibung + Commit-Nachricht + Details Repository konfigurieren Email Adresse Email Adresse @@ -132,16 +132,16 @@ Benutzername Benutzername für dieses Repository Kopieren - NACHRICHT KOPIEREN + COMMIT-NACHRICHT KOPIEREN Pfad kopieren Dateiename kopieren Branch erstellen... - Basiert auf: + Basierend auf: Erstellten Branch auschecken Lokale Änderungen: Verwerfen Nichts tun - Stash & wieder anwenden + Stashen & wieder anwenden Neuer Branch-Name: Branch-Namen eingeben. Lokalen Branch erstellen @@ -255,7 +255,7 @@ Versions-Tag Prefix: Git LFS Verfolgungsmuster hinzufügen... - Muster ist Dateiname + Muster ist ein Dateiname Eigenes Muster: Verfolgungsmuster zu Git LFS hinzufügen Fetch @@ -283,9 +283,9 @@ Wechsle zwischen horizontalem und vertikalem Layout Wechsle zwischen Kurven- und Konturgraphenmodus AUTOR - GRAPH & SUBJEKT + GRAPH & COMMIT-NACHRICHT SHA - COMMIT ZEIT + COMMIT ZEITPUNKT DURCHSUCHE SHA/SUBJEKT/AUTOR. DRÜCKE ZUM SUCHEN ENTER, ESC UM ABZUBRECHEN LÖSCHEN {0} COMMITS AUSGEWÄHLT @@ -473,8 +473,8 @@ AUFLÖSEN Commit suchen Suche über - Datei - Nachricht + Dateiname + Commit-Nachricht SHA Autor & Committer Duche Branches & Tags @@ -494,12 +494,12 @@ Verschiebe zu: Aktueller Branch: Zeige im Datei Explorer - Commit umkehren + Commit rückgängig machen Commit: - Commit Änderungen umkehren + Commit Änderungen rückgängig machen Commit Nachricht umformulieren Verwende 'Shift+Enter' um eine neue Zeile einzufügen. 'Enter' ist das Kürzl für den OK Button - Läuft. Bitte warten... + Bitte warten... SPEICHERN Speichern als... Patch wurde erfolgreich gespeichert! @@ -516,9 +516,9 @@ START Stash Inklusive nicht-verfolgter Dateien - Nachricht: + Name: Optional. Name dieses Stashes - Lokale Änderugen stashen + Lokale Änderungen stashen Anwenden Entfernen Anwenden und entfernen @@ -581,7 +581,7 @@ STRG + Enter KONFLIKTE ERKANNT DATEI KONFLIKTE GELÖST - LETZTE COMMIT NACHRICHTEN + LETZTE COMMIT-NACHRICHTEN NICHT-VERFOLGTE DATEIEN INKLUDIEREN NACHRICHTEN HISTORIE KEINE BISHERIGEN COMMIT NACHRICHTEN From f754b2c63a931f052d27a9cd4f447e2d5ee909e5 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 5 Aug 2024 17:34:49 +0800 Subject: [PATCH 02/58] feature: supports issue tracker in commit message (#315) --- src/App.JsonCodeGen.cs | 1 + src/Resources/Icons.axaml | 1 + src/Resources/Locales/en_US.axaml | 9 + src/Resources/Locales/zh_CN.axaml | 9 + src/Resources/Locales/zh_TW.axaml | 9 + src/Resources/Styles.axaml | 8 + src/ViewModels/CommitDetail.cs | 13 +- src/ViewModels/FileHistories.cs | 4 +- src/ViewModels/Histories.cs | 4 +- src/ViewModels/InteractiveRebase.cs | 2 +- src/ViewModels/IssueTrackerRule.cs | 164 ++++++++++++++++++ src/ViewModels/Repository.cs | 27 +++ src/ViewModels/RepositoryConfigure.cs | 49 +++++- src/ViewModels/WorkingCopy.cs | 2 +- src/Views/CommitBaseInfo.axaml | 7 +- src/Views/CommitBaseInfo.axaml.cs | 9 + src/Views/CommitDetail.axaml | 4 +- src/Views/CommitMessagePresenter.cs | 97 +++++++++++ src/Views/RepositoryConfigure.axaml | 224 +++++++++++++++++-------- src/Views/RepositoryConfigure.axaml.cs | 5 - 20 files changed, 563 insertions(+), 85 deletions(-) create mode 100644 src/ViewModels/IssueTrackerRule.cs create mode 100644 src/Views/CommitMessagePresenter.cs diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs index f6e3cd88..228928a7 100644 --- a/src/App.JsonCodeGen.cs +++ b/src/App.JsonCodeGen.cs @@ -64,5 +64,6 @@ namespace SourceGit [JsonSerializable(typeof(Models.Version))] [JsonSerializable(typeof(Models.RepositorySettings))] [JsonSerializable(typeof(ViewModels.Preference))] + [JsonSerializable(typeof(ViewModels.IssueTrackerRuleSetting))] internal partial class JsonCodeGen : JsonSerializerContext { } } diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index e2940f68..5e79e402 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 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 ad443a2c..c87b2c84 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -164,6 +164,7 @@ + @@ -276,6 +277,13 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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(); From dfd098e13106cefe468993dd1c751361e5790e2a Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 5 Aug 2024 18:18:57 +0800 Subject: [PATCH 03/58] refactor: merge sourcegit.issuetracker.setting to sourcegit.settings. --- src/App.JsonCodeGen.cs | 1 - .../IssueTrackerRule.cs | 54 ++----------------- src/Models/RepositorySettings.cs | 51 ++++++++++++++++++ src/ViewModels/CommitDetail.cs | 15 +++--- src/ViewModels/FileHistories.cs | 6 +-- src/ViewModels/Histories.cs | 4 +- src/ViewModels/InteractiveRebase.cs | 2 +- src/ViewModels/Repository.cs | 27 ---------- src/ViewModels/RepositoryConfigure.cs | 23 ++++---- src/ViewModels/WorkingCopy.cs | 2 +- src/Views/CommitBaseInfo.axaml | 2 +- src/Views/CommitBaseInfo.axaml.cs | 11 ++-- src/Views/CommitDetail.axaml | 2 +- src/Views/CommitMessagePresenter.cs | 17 +++--- src/Views/RepositoryConfigure.axaml | 5 +- 15 files changed, 100 insertions(+), 122 deletions(-) rename src/{ViewModels => Models}/IssueTrackerRule.cs (67%) diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs index 228928a7..f6e3cd88 100644 --- a/src/App.JsonCodeGen.cs +++ b/src/App.JsonCodeGen.cs @@ -64,6 +64,5 @@ namespace SourceGit [JsonSerializable(typeof(Models.Version))] [JsonSerializable(typeof(Models.RepositorySettings))] [JsonSerializable(typeof(ViewModels.Preference))] - [JsonSerializable(typeof(ViewModels.IssueTrackerRuleSetting))] internal partial class JsonCodeGen : JsonSerializerContext { } } diff --git a/src/ViewModels/IssueTrackerRule.cs b/src/Models/IssueTrackerRule.cs similarity index 67% rename from src/ViewModels/IssueTrackerRule.cs rename to src/Models/IssueTrackerRule.cs index 3056b984..a4a9a3d1 100644 --- a/src/ViewModels/IssueTrackerRule.cs +++ b/src/Models/IssueTrackerRule.cs @@ -5,7 +5,7 @@ using Avalonia.Collections; using CommunityToolkit.Mvvm.ComponentModel; -namespace SourceGit.ViewModels +namespace SourceGit.Models { public class IssueTrackerMatch { @@ -72,7 +72,7 @@ namespace SourceGit.ViewModels return; var matches = _regex.Matches(message); - for (int i = 0; i < matches.Count; i++) + for (var i = 0; i < matches.Count; i++) { var match = matches[i]; if (!match.Success) @@ -97,7 +97,7 @@ namespace SourceGit.ViewModels range.Start = start; range.Length = len; range.URL = _urlTemplate; - for (int j = 1; j < match.Groups.Count; j++) + for (var j = 1; j < match.Groups.Count; j++) { var group = match.Groups[j]; if (group.Success) @@ -113,52 +113,4 @@ namespace SourceGit.ViewModels private string _urlTemplate; private Regex _regex = null; } - - public class IssueTrackerRuleSetting - { - public AvaloniaList Rules - { - get; - set; - } = new AvaloniaList(); - - public IssueTrackerRule Add() - { - var rule = new IssueTrackerRule() - { - Name = "New Issue Tracker", - RegexString = "#(\\d+)", - URLTemplate = "https://xxx/$1", - }; - - Rules.Add(rule); - return rule; - } - - public IssueTrackerRule AddGithub(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", - }; - - Rules.Add(rule); - return rule; - } - - public IssueTrackerRule AddJira() - { - var rule = new IssueTrackerRule() - { - Name = "Jira Tracker", - RegexString = "PROJ-(\\d+)", - URLTemplate = "https://jira.yourcompany.com/browse/PROJ-$1", - }; - - Rules.Add(rule); - return rule; - } - } } 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/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 667ed657..f01e333f 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Media.Imaging; using Avalonia.Platform.Storage; @@ -88,15 +89,15 @@ namespace SourceGit.ViewModels set => SetProperty(ref _viewRevisionFileContent, value); } - public IssueTrackerRuleSetting IssueTrackerSetting + public AvaloniaList IssueTrackerRules { - get => _issueTrackerSetting; + get => _issueTrackerRules; } - public CommitDetail(string repo, IssueTrackerRuleSetting issueTrackerSetting) + public CommitDetail(string repo, AvaloniaList issueTrackerRules) { _repo = repo; - _issueTrackerSetting = issueTrackerSetting; + _issueTrackerRules = issueTrackerRules; } public void Cleanup() @@ -248,7 +249,7 @@ namespace SourceGit.ViewModels history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path, _issueTrackerSetting) }; + var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path, _issueTrackerRules) }; window.Show(); ev.Handled = true; }; @@ -311,7 +312,7 @@ namespace SourceGit.ViewModels history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path, _issueTrackerSetting) }; + var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path, _issueTrackerRules) }; window.Show(); ev.Handled = true; }; @@ -463,7 +464,7 @@ namespace SourceGit.ViewModels }; private string _repo; - private IssueTrackerRuleSetting _issueTrackerSetting = null; + private AvaloniaList _issueTrackerRules = null; private int _activePageIndex = 0; private Models.Commit _commit = null; private string _fullMessage = string.Empty; diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs index 82b0b11a..79696a7e 100644 --- a/src/ViewModels/FileHistories.cs +++ b/src/ViewModels/FileHistories.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; - +using Avalonia.Collections; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -54,11 +54,11 @@ namespace SourceGit.ViewModels set => SetProperty(ref _detailContext, value); } - public FileHistories(string repo, string file, IssueTrackerRuleSetting issueTrackerSetting) + public FileHistories(string repo, string file, AvaloniaList issueTrackerRules) { _repo = repo; _file = file; - _detailContext = new CommitDetail(repo, issueTrackerSetting); + _detailContext = new CommitDetail(repo, issueTrackerRules); Task.Run(() => { diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index da18f815..25500a4c 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -94,7 +94,7 @@ namespace SourceGit.ViewModels } else { - var commitDetail = new CommitDetail(_repo.FullPath, _repo.IssueTrackerSetting); + var commitDetail = new CommitDetail(_repo.FullPath, _repo.Settings.IssueTrackerRules); commitDetail.Commit = commit; DetailContext = commitDetail; } @@ -122,7 +122,7 @@ namespace SourceGit.ViewModels } else { - var commitDetail = new CommitDetail(_repo.FullPath, _repo.IssueTrackerSetting); + var commitDetail = new CommitDetail(_repo.FullPath, _repo.Settings.IssueTrackerRules); commitDetail.Commit = commit; DetailContext = commitDetail; } diff --git a/src/ViewModels/InteractiveRebase.cs b/src/ViewModels/InteractiveRebase.cs index aaeab204..32417e01 100644 --- a/src/ViewModels/InteractiveRebase.cs +++ b/src/ViewModels/InteractiveRebase.cs @@ -114,7 +114,7 @@ namespace SourceGit.ViewModels Current = current; On = on; IsLoading = true; - DetailContext = new CommitDetail(repoPath, repo.IssueTrackerSetting); + DetailContext = new CommitDetail(repoPath, repo.Settings.IssueTrackerRules); Task.Run(() => { diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index f37f59f0..3ee8edd8 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -44,11 +44,6 @@ namespace SourceGit.ViewModels get => _settings; } - public IssueTrackerRuleSetting IssueTrackerSetting - { - get => _issueTrackerSetting; - } - public int SelectedViewIndex { get => _selectedViewIndex; @@ -324,23 +319,6 @@ namespace SourceGit.ViewModels _settings = new Models.RepositorySettings(); } - var issueTrackerSettingsFile = Path.Combine(_gitDir, "sourcegit.issuetracker.settings"); - if (File.Exists(issueTrackerSettingsFile)) - { - try - { - _issueTrackerSetting = JsonSerializer.Deserialize(File.ReadAllText(issueTrackerSettingsFile), JsonCodeGen.Default.IssueTrackerRuleSetting); - } - catch - { - _issueTrackerSetting = new IssueTrackerRuleSetting(); - } - } - else - { - _issueTrackerSetting = new IssueTrackerRuleSetting(); - } - _watcher = new Models.Watcher(this); _histories = new Histories(this); _workingCopy = new WorkingCopy(this); @@ -361,10 +339,6 @@ namespace SourceGit.ViewModels File.WriteAllText(Path.Combine(_gitDir, "sourcegit.settings"), settingsSerialized); _settings = null; - var issueTrackerSerialized = JsonSerializer.Serialize(_issueTrackerSetting, JsonCodeGen.Default.IssueTrackerRuleSetting); - File.WriteAllText(Path.Combine(_gitDir, "sourcegit.issuetracker.settings"), issueTrackerSerialized); - _issueTrackerSetting = null; - _watcher.Dispose(); _histories.Cleanup(); _workingCopy.Cleanup(); @@ -1986,7 +1960,6 @@ namespace SourceGit.ViewModels private string _fullpath = string.Empty; private string _gitDir = string.Empty; private Models.RepositorySettings _settings = null; - private IssueTrackerRuleSetting _issueTrackerSetting = null; private Models.Watcher _watcher = null; private Histories _histories = null; diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs index 4ce73c61..f252a075 100644 --- a/src/ViewModels/RepositoryConfigure.cs +++ b/src/ViewModels/RepositoryConfigure.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Avalonia.Collections; using CommunityToolkit.Mvvm.ComponentModel; @@ -43,12 +42,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _httpProxy, value); } - public AvaloniaList IssueTrackerRules + public AvaloniaList IssueTrackerRules { - get => _repo.IssueTrackerSetting.Rules; + get => _repo.Settings.IssueTrackerRules; } - public IssueTrackerRule SelectedIssueTrackerRule + public Models.IssueTrackerRule SelectedIssueTrackerRule { get => _selectedIssueTrackerRule; set => SetProperty(ref _selectedIssueTrackerRule, value); @@ -86,29 +85,29 @@ namespace SourceGit.ViewModels { if (remote.TryGetVisitURL(out string url)) { - SelectedIssueTrackerRule = _repo.IssueTrackerSetting.AddGithub(url); + SelectedIssueTrackerRule = _repo.Settings.AddGithubIssueTracker(url); return; } } } - SelectedIssueTrackerRule = _repo.IssueTrackerSetting.AddGithub(null); + SelectedIssueTrackerRule = _repo.Settings.AddGithubIssueTracker(null); } public void AddSampleJiraIssueTracker() { - SelectedIssueTrackerRule = _repo.IssueTrackerSetting.AddJira(); + SelectedIssueTrackerRule = _repo.Settings.AddJiraIssueTracker(); } public void NewIssueTracker() { - SelectedIssueTrackerRule = _repo.IssueTrackerSetting.Add(); + SelectedIssueTrackerRule = _repo.Settings.AddNewIssueTracker(); } public void RemoveSelectedIssueTracker() { - if (_selectedIssueTrackerRule != null) - _repo.IssueTrackerSetting.Rules.Remove(_selectedIssueTrackerRule); + _repo.Settings.RemoveIssueTracker(_selectedIssueTrackerRule); + SelectedIssueTrackerRule = null; } public void Save() @@ -142,6 +141,6 @@ namespace SourceGit.ViewModels private readonly Repository _repo = null; private readonly Dictionary _cached = null; private string _httpProxy; - private IssueTrackerRule _selectedIssueTrackerRule = null; + private Models.IssueTrackerRule _selectedIssueTrackerRule = null; } } diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index f334c599..8f352fbe 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -541,7 +541,7 @@ namespace SourceGit.ViewModels history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path, _repo.IssueTrackerSetting) }; + var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path, _repo.Settings.IssueTrackerRules) }; window.Show(); e.Handled = true; }; diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index cc8becd7..d2c57810 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -96,7 +96,7 @@ Margin="12,5,8,0" Classes="primary" Message="{Binding #ThisControl.Message}" - IssueTrackerSetting="{Binding #ThisControl.IssueTrackerSetting}" + IssueTrackerRules="{Binding #ThisControl.IssueTrackerRules}" TextWrapping="Wrap"/> diff --git a/src/Views/CommitBaseInfo.axaml.cs b/src/Views/CommitBaseInfo.axaml.cs index eca16ada..86451dfe 100644 --- a/src/Views/CommitBaseInfo.axaml.cs +++ b/src/Views/CommitBaseInfo.axaml.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Input; @@ -24,13 +25,13 @@ namespace SourceGit.Views set => SetValue(MessageProperty, value); } - public static readonly StyledProperty IssueTrackerSettingProperty = - AvaloniaProperty.Register(nameof(IssueTrackerSetting)); + public static readonly StyledProperty> IssueTrackerRulesProperty = + AvaloniaProperty.Register>(nameof(IssueTrackerRules)); - public ViewModels.IssueTrackerRuleSetting IssueTrackerSetting + public AvaloniaList IssueTrackerRules { - get => GetValue(IssueTrackerSettingProperty); - set => SetValue(IssueTrackerSettingProperty, value); + get => GetValue(IssueTrackerRulesProperty); + set => SetValue(IssueTrackerRulesProperty, value); } public CommitBaseInfo() diff --git a/src/Views/CommitDetail.axaml b/src/Views/CommitDetail.axaml index 9b54f8ba..af733f49 100644 --- a/src/Views/CommitDetail.axaml +++ b/src/Views/CommitDetail.axaml @@ -21,7 +21,7 @@ + IssueTrackerRules="{Binding IssueTrackerRules}"/> diff --git a/src/Views/CommitMessagePresenter.cs b/src/Views/CommitMessagePresenter.cs index 593de4ce..116442d9 100644 --- a/src/Views/CommitMessagePresenter.cs +++ b/src/Views/CommitMessagePresenter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Avalonia; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Documents; using Avalonia.Input; @@ -19,13 +20,13 @@ namespace SourceGit.Views set => SetValue(MessageProperty, value); } - public static readonly StyledProperty IssueTrackerSettingProperty = - AvaloniaProperty.Register(nameof(IssueTrackerSetting)); + public static readonly StyledProperty> IssueTrackerRulesProperty = + AvaloniaProperty.Register>(nameof(IssueTrackerRules)); - public ViewModels.IssueTrackerRuleSetting IssueTrackerSetting + public AvaloniaList IssueTrackerRules { - get => GetValue(IssueTrackerSettingProperty); - set => SetValue(IssueTrackerSettingProperty, value); + get => GetValue(IssueTrackerRulesProperty); + set => SetValue(IssueTrackerRulesProperty, value); } protected override Type StyleKeyOverride => typeof(SelectableTextBlock); @@ -34,7 +35,7 @@ namespace SourceGit.Views { base.OnPropertyChanged(change); - if (change.Property == MessageProperty || change.Property == IssueTrackerSettingProperty) + if (change.Property == MessageProperty || change.Property == IssueTrackerRulesProperty) { Inlines.Clear(); @@ -42,14 +43,14 @@ namespace SourceGit.Views if (string.IsNullOrEmpty(message)) return; - var rules = IssueTrackerSetting?.Rules; + var rules = IssueTrackerRules; if (rules == null || rules.Count == 0) { Inlines.Add(new Run(message)); return; } - var matches = new List(); + var matches = new List(); foreach (var rule in rules) rule.Matches(matches, message); diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml index 42e60b7c..6ffe66d1 100644 --- a/src/Views/RepositoryConfigure.axaml +++ b/src/Views/RepositoryConfigure.axaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" @@ -141,7 +142,7 @@ - + @@ -187,7 +188,7 @@ - + From 91daa42c089bd1ac557a9bce1944ec3098b3dd22 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 5 Aug 2024 19:32:50 +0800 Subject: [PATCH 04/58] ux: margins between textbox and its label in issue tracker configure page --- src/Views/RepositoryConfigure.axaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml index 6ffe66d1..1af0cc7d 100644 --- a/src/Views/RepositoryConfigure.axaml +++ b/src/Views/RepositoryConfigure.axaml @@ -191,18 +191,18 @@ - + - + - - + + From 85d3f0993c4728bf59e3bc6f2e35645f9855ed2e Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 5 Aug 2024 20:12:41 +0800 Subject: [PATCH 05/58] refactor: load default github avatar --- src/Models/AvatarManager.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Models/AvatarManager.cs b/src/Models/AvatarManager.cs index a380c1bc..6a68ac51 100644 --- a/src/Models/AvatarManager.cs +++ b/src/Models/AvatarManager.cs @@ -34,6 +34,9 @@ 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) @@ -117,17 +120,6 @@ namespace SourceGit.Models public static Bitmap Request(string email, bool forceRefetch) { - if (email.Equals("noreply@github.com", StringComparison.Ordinal)) - { - if (_githubEmailAvatar == null) - { - var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/github.png", UriKind.RelativeOrAbsolute)); - _githubEmailAvatar = new Bitmap(icon); - } - - return _githubEmailAvatar; - } - if (forceRefetch) { if (_resources.ContainsKey(email)) @@ -198,6 +190,5 @@ namespace SourceGit.Models [GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")] private static partial Regex REG_GITHUB_USER_EMAIL(); - private static Bitmap _githubEmailAvatar = null; } } From b0594233914bf0ea4e70eb9fc593a9d39f15c235 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 5 Aug 2024 22:10:43 +0800 Subject: [PATCH 06/58] feature: enable using drag-drop to re-order commits (#319) --- src/Resources/Icons.axaml | 1 + src/Views/InteractiveRebase.axaml | 14 +++++++- src/Views/InteractiveRebase.axaml.cs | 49 ++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 5e79e402..1657ead3 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -68,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/Views/InteractiveRebase.axaml b/src/Views/InteractiveRebase.axaml index 553d99f1..c2bd7741 100644 --- a/src/Views/InteractiveRebase.axaml +++ b/src/Views/InteractiveRebase.axaml @@ -73,7 +73,6 @@ CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" - DragDrop.AllowDrop="True" IsReadOnly="True" HeadersVisibility="None" Focusable="False" @@ -82,6 +81,19 @@ VerticalScrollBarVisibility="Auto" KeyDown="OnDataGridKeyDown"> + + + + + + + + + + diff --git a/src/Views/InteractiveRebase.axaml.cs b/src/Views/InteractiveRebase.axaml.cs index 287a0be1..453f1a6c 100644 --- a/src/Views/InteractiveRebase.axaml.cs +++ b/src/Views/InteractiveRebase.axaml.cs @@ -21,6 +21,55 @@ namespace SourceGit.Views Close(); } + private void OnSetupRowHeaderDragDrop(object sender, RoutedEventArgs e) + { + if (sender is Border border) + { + DragDrop.SetAllowDrop(border, true); + border.AddHandler(DragDrop.DragOverEvent, OnRowHeaderDragOver); + } + } + + private void OnRowHeaderPointerPressed(object sender, PointerPressedEventArgs e) + { + if (sender is Border border && border.DataContext is ViewModels.InteractiveRebaseItem item) + { + var data = new DataObject(); + data.Set("InteractiveRebaseItem", item); + DragDrop.DoDragDrop(e, data, DragDropEffects.Move | DragDropEffects.Copy | DragDropEffects.Link); + } + } + + private void OnRowHeaderDragOver(object sender, DragEventArgs e) + { + if (DataContext is ViewModels.InteractiveRebase vm && + e.Data.Get("InteractiveRebaseItem") is ViewModels.InteractiveRebaseItem src && + sender is Border { DataContext: ViewModels.InteractiveRebaseItem dst } border && + src != dst) + { + e.DragEffects = DragDropEffects.Move | DragDropEffects.Copy | DragDropEffects.Link; + + var p = e.GetPosition(border); + if (p.Y > border.Bounds.Height * 0.33 && p.Y < border.Bounds.Height * 0.67) + { + var srcIdx = vm.Items.IndexOf(src); + var dstIdx = vm.Items.IndexOf(dst); + if (srcIdx < dstIdx) + { + for (var i = srcIdx; i < dstIdx; i++) + vm.MoveItemDown(src); + } + else + { + for (var i = srcIdx; i > dstIdx; i--) + vm.MoveItemUp(src); + } + } + + e.Handled = true; + } + } + private void OnMoveItemUp(object sender, RoutedEventArgs e) { if (sender is Control control && DataContext is ViewModels.InteractiveRebase vm) From 6d9613945e32d40e31eaede05ceb111f3686e409 Mon Sep 17 00:00:00 2001 From: warappa Date: Mon, 5 Aug 2024 23:10:42 +0200 Subject: [PATCH 07/58] Fixes and tweaks German translation --- src/Resources/Locales/de_DE.axaml | 76 +++++++++++++++---------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 7a1a57c2..f34c270d 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -19,14 +19,14 @@ Branch Name: Optional. Standard ist der Zielordnername. Branch verfolgen: - Remote Branch verfolgen + Remote-Branch verfolgen Patch Fehler Fehler werfen und anwenden des Patches verweigern Alle Fehler Ähnlich wie 'Fehler', zeigt aber mehr an - Patch Datei: - Wähle die anzuwendende .patch Datei + Patch-Datei: + Wähle die anzuwendende .patch-Datei Ignoriere Leerzeichenänderungen Keine Warnungen Schaltet die Warnung vor überschüssigen Leerzeichen aus @@ -124,7 +124,7 @@ SHA Commit-Nachricht Details - Repository konfigurieren + Repository Einstellungen Email Adresse Email Adresse HTTP Proxy @@ -211,7 +211,7 @@ Ohne Tags fetchen Alle toten Branches entfernen Remote: - Remote Änderungen fetchen + Remote-Änderungen fetchen Als unverändert annehmen Verwerfen... Verwerfe {0} Dateien... @@ -231,20 +231,20 @@ Datei Historie FILTER Git-Flow - Development Branch: + Development-Branch: Feature: - Feature Prefix: + Feature-Prefix: FLOW - Finish Feature FLOW - Finish Hotfix FLOW - Finish Release Ziel: Hotfix: - Hotfix Prefix: + Hotfix-Prefix: Git-Flow initialisieren Branch behalten - Production Branch: + Production-Branch: Release: - Release Prefix: + Release-Prefix: Feature starten... FLOW - Feature starten Hotfix starten... @@ -252,7 +252,7 @@ Name eingeben Release starten... FLOW - Release starten - Versions-Tag Prefix: + Versions-Tag-Prefix: Git LFS Verfolgungsmuster hinzufügen... Muster ist ein Dateiname @@ -291,19 +291,19 @@ {0} COMMITS AUSGEWÄHLT Tastaturkürzel Referenz GLOBAL - Aktuelles Popup abbrechen - Aktuelle Seite schließen - Zu vorheriger Seite gehen - Zu nächster Seite gehen - Neue Seite erstellen + Aktuelles Popup schließen + Aktuellen Tab schließen + Zum vorherigen Tab wechseln + Zum nächsten Tab wechseln + Neuen Tab erstellen Einstellungen öffnen REPOSITORY Gestagte Änderungen committen - Gestagte Änderungen Committen und pushen + Gestagte Änderungen committen und pushen Dashboard Modus (Standard) - Erzwinge Neuladen dieses Repositorys + Erzwinge Neuladen des Repositorys Ausgewählte Änderungen stagen/unstagen - Commit Suchmodus + Commit-Suchmodus Wechsle zu 'Änderungen' Wechsle zu 'Historie' Wechsle zu 'Stashes' @@ -361,25 +361,25 @@ Einstellungen OBERFLÄCHE Standardschriftart - Standard Schriftgröße + Standardschriftgröße Monospace Schriftart Design - Design Überbrückung + Design-Anpassungen ALLGEMEIN Avatar Server Beim Starten nach Updates suchen Sprache - Commits Historie + Commit-Historie Zuletzt geöffnete Tabs beim Starten wiederherstellen - Betreff Hilfslinienlänge + Hilfslinienlänge von Commit-Nachricht Fixe Tab-Breite in Titelleiste Sichtbare Vergleichskontextzeilen GIT Remotes automatisch fetchen - Auto-Fetch Interval + Auto-Fetch Intervall Minute(n) Aktiviere Auto-CRLF - Standard Klon-Ordner + Clone Standardordner Benutzer Email Globale Git Benutzer Email Installationspfad @@ -389,15 +389,15 @@ Git Version Git (>= 2.23.0) wird von dieser App benötigt GPG SIGNIERUNG - Commit GPG Signierung - Tag GPG Signierung + Commit-Signierung + Tag-Signierung GPG Format - Program Installspfad + GPG Installationspfad Gebe Installationspfad zu installiertem GPG Programm an Benutzer Signierungsschlüssel - Benutzer GPG Signierungsschlüssel + GPG Benutzer Signierungsschlüssel DIFF/MERGE TOOL - Installspfad + Installationspfad Gebe Installationspfad von Diff/Merge Tool an Tool Remote löschen @@ -405,9 +405,9 @@ Worktrees löschen Worktree Informationen in `$GIT_DIR/worktrees` löschen Pull - Branch: + Remote-Branch: Alle Branches fetchen - Ziel-Branch: + Lokaler Branch: Lokale Änderungen: Verwerfen Nichts tun @@ -420,9 +420,9 @@ Erzwinge Push Lokaler Branch: Remote: - Änderungen zum Remote pushen - Remote Branch: - Als verfogender Branch konfigurieren + Push + Remote-Branch: + Remote-Branch verfolgen Alle Tags pushen Tag zum Remote pushen Zu allen Remotes pushen @@ -458,9 +458,9 @@ Aufräumen (GC & Prune) Führt `git gc` auf diesem Repository aus. Alles löschen - Dieses Repository konfigureren + Repository Einstellungen WEITER - Repository im Datei-Browser öffnen + Öffne im Datei-Browser GEFILTERT: LOKALE BRANCHES Zum HEAD wechseln @@ -493,7 +493,7 @@ Rücksetzmodus: Verschiebe zu: Aktueller Branch: - Zeige im Datei Explorer + Zeige im Datei-Explorer Commit rückgängig machen Commit: Commit Änderungen rückgängig machen From 4ba7c879c5e892e903745cc1f07b328bf11cbcec Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 6 Aug 2024 10:04:08 +0800 Subject: [PATCH 08/58] feature: use `Ctrl+C` to copy selected commits in histories (#321) --- src/Views/Histories.axaml | 4 +++- src/Views/Histories.axaml.cs | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index 63d4da95..510e4e6e 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"> @@ -77,12 +82,15 @@ + + - + - + - + From 7ff92c4e1d1dda7514b154f50f0b9d6608165f05 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 7 Aug 2024 19:42:41 +0800 Subject: [PATCH 29/58] ux: stage/unstage/discard button aligment in TextDiffView --- src/Views/TextDiffView.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 0a97e051..fd82d852 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1085,7 +1085,7 @@ namespace SourceGit.Views return; } - var top = chunk.Y + 16; + var top = chunk.Y + (chunk.Height >= 36 ? 16 : 4); var right = (chunk.Combined || !chunk.IsOldSide) ? 16 : v.Bounds.Width * 0.5f + 16; v.Popup.Margin = new Thickness(0, top, right, 0); v.Popup.IsVisible = true; From dc32c3e95d866e55e65cdf77ee50bfd67d633b57 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 7 Aug 2024 19:52:08 +0800 Subject: [PATCH 30/58] project: upgrade AvaloniaUI to 11.1.2 --- src/SourceGit.csproj | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj index 5a73dea8..8958b927 100644 --- a/src/SourceGit.csproj +++ b/src/SourceGit.csproj @@ -37,12 +37,12 @@ - - - - + + + + - + @@ -53,7 +53,7 @@ - - + + From f8bc48c49c7e7abf3cec01b8d30fcf5619c01427 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 8 Aug 2024 09:33:46 +0800 Subject: [PATCH 31/58] enhance: exec git command directly instead of call methods from WorkingCopy (#330) --- src/Views/TextDiffView.axaml.cs | 102 ++++++++++++++------------------ 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index fd82d852..d5f6194a 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1147,27 +1147,22 @@ namespace SourceGit.Views if (!selection.HasChanges) return; + var repoView = this.FindAncestorOfType(); + if (repoView == null) + return; + + var repo = repoView.DataContext as ViewModels.Repository; + if (repo == null) + return; + + repo.SetWatcherEnabled(false); + if (!selection.HasLeftChanges) { - var workcopyView = this.FindAncestorOfType(); - if (workcopyView == null) - return; - - var workcopy = workcopyView.DataContext as ViewModels.WorkingCopy; - workcopy?.StageChanges(new List { change }); + new Commands.Add(repo.FullPath, [change]).Exec(); } else { - var repoView = this.FindAncestorOfType(); - if (repoView == null) - return; - - var repo = repoView.DataContext as ViewModels.Repository; - if (repo == null) - return; - - repo.SetWatcherEnabled(false); - var tmpFile = Path.GetTempFileName(); if (change.WorkTree == Models.ChangeState.Untracked) { @@ -1186,10 +1181,10 @@ namespace SourceGit.Views new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index").Exec(); File.Delete(tmpFile); - - repo.MarkWorkingCopyDirtyManually(); - repo.SetWatcherEnabled(true); } + + repo.MarkWorkingCopyDirtyManually(); + repo.SetWatcherEnabled(true); } private void OnUnstageChunk(object sender, RoutedEventArgs e) @@ -1210,27 +1205,25 @@ namespace SourceGit.Views if (!selection.HasChanges) return; + var repoView = this.FindAncestorOfType(); + if (repoView == null) + return; + + var repo = repoView.DataContext as ViewModels.Repository; + if (repo == null) + return; + + repo.SetWatcherEnabled(false); + if (!selection.HasLeftChanges) { - var workcopyView = this.FindAncestorOfType(); - if (workcopyView == null) - return; - - var workcopy = workcopyView.DataContext as ViewModels.WorkingCopy; - workcopy?.UnstageChanges(new List { change }); + if (change.DataForAmend != null) + new Commands.UnstageChangesForAmend(repo.FullPath, [change]).Exec(); + else + new Commands.Reset(repo.FullPath, [change]).Exec(); } else { - var repoView = this.FindAncestorOfType(); - if (repoView == null) - return; - - var repo = repoView.DataContext as ViewModels.Repository; - if (repo == null) - return; - - repo.SetWatcherEnabled(false); - var treeGuid = new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).Result(); var tmpFile = Path.GetTempFileName(); if (change.Index == Models.ChangeState.Added) @@ -1242,10 +1235,10 @@ namespace SourceGit.Views new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index --reverse").Exec(); File.Delete(tmpFile); - - repo.MarkWorkingCopyDirtyManually(); - repo.SetWatcherEnabled(true); } + + repo.MarkWorkingCopyDirtyManually(); + repo.SetWatcherEnabled(true); } private void OnDiscardChunk(object sender, RoutedEventArgs e) @@ -1266,27 +1259,22 @@ namespace SourceGit.Views if (!selection.HasChanges) return; + var repoView = this.FindAncestorOfType(); + if (repoView == null) + return; + + var repo = repoView.DataContext as ViewModels.Repository; + if (repo == null) + return; + + repo.SetWatcherEnabled(false); + if (!selection.HasLeftChanges) { - var workcopyView = this.FindAncestorOfType(); - if (workcopyView == null) - return; - - var workcopy = workcopyView.DataContext as ViewModels.WorkingCopy; - workcopy?.Discard(new List { change }); + Commands.Discard.Changes(repo.FullPath, [change]); } else { - var repoView = this.FindAncestorOfType(); - if (repoView == null) - return; - - var repo = repoView.DataContext as ViewModels.Repository; - if (repo == null) - return; - - repo.SetWatcherEnabled(false); - var tmpFile = Path.GetTempFileName(); if (change.Index == Models.ChangeState.Added) { @@ -1305,10 +1293,10 @@ namespace SourceGit.Views new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--reverse").Exec(); File.Delete(tmpFile); - - repo.MarkWorkingCopyDirtyManually(); - repo.SetWatcherEnabled(true); } + + repo.MarkWorkingCopyDirtyManually(); + repo.SetWatcherEnabled(true); } } } From 1ab0ea27df9f6b65c88187f7cd487c4969b05ba4 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 8 Aug 2024 09:38:11 +0800 Subject: [PATCH 32/58] enhance: remove fixed fontsize in launcher's tabbar (#332) --- src/Views/LauncherTabBar.axaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml index a219365d..7b9f8b61 100644 --- a/src/Views/LauncherTabBar.axaml +++ b/src/Views/LauncherTabBar.axaml @@ -64,7 +64,6 @@ IsHitTestVisible="False"/> Date: Thu, 8 Aug 2024 10:12:39 +0800 Subject: [PATCH 33/58] feature: enable syntax highlighting in revision files view (#333) --- src/Models/RevisionFile.cs | 1 + src/ViewModels/CommitDetail.cs | 2 +- src/Views/RevisionFiles.axaml.cs | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Models/RevisionFile.cs b/src/Models/RevisionFile.cs index e2f4abb0..59868fcc 100644 --- a/src/Models/RevisionFile.cs +++ b/src/Models/RevisionFile.cs @@ -14,6 +14,7 @@ namespace SourceGit.Models public class RevisionTextFile { + public string FileName { get; set; } public string Content { get; set; } } diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index f01e333f..ae0e2cb9 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -183,7 +183,7 @@ namespace SourceGit.ViewModels } else { - var txt = new Models.RevisionTextFile() { Content = content }; + var txt = new Models.RevisionTextFile() { FileName = file.Path, Content = content }; Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = txt); } }); diff --git a/src/Views/RevisionFiles.axaml.cs b/src/Views/RevisionFiles.axaml.cs index 227c11a1..3ef430ad 100644 --- a/src/Views/RevisionFiles.axaml.cs +++ b/src/Views/RevisionFiles.axaml.cs @@ -9,6 +9,7 @@ using Avalonia.Media; using AvaloniaEdit; using AvaloniaEdit.Document; using AvaloniaEdit.Editing; +using AvaloniaEdit.TextMate; namespace SourceGit.Views { @@ -35,6 +36,7 @@ namespace SourceGit.Views base.OnLoaded(e); TextArea.TextView.ContextRequested += OnTextViewContextRequested; + UpdateTextMate(); } protected override void OnUnloaded(RoutedEventArgs e) @@ -42,6 +44,13 @@ namespace SourceGit.Views base.OnUnloaded(e); TextArea.TextView.ContextRequested -= OnTextViewContextRequested; + + if (_textMate != null) + { + _textMate.Dispose(); + _textMate = null; + } + GC.Collect(); } @@ -50,9 +59,14 @@ namespace SourceGit.Views base.OnDataContextChanged(e); if (DataContext is Models.RevisionTextFile source) + { + UpdateTextMate(); Text = source.Content; + } else + { Text = string.Empty; + } } private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) @@ -85,6 +99,17 @@ namespace SourceGit.Views TextArea.TextView.OpenContextMenu(menu); e.Handled = true; } + + private void UpdateTextMate() + { + if (_textMate == null) + _textMate = Models.TextMateHelper.CreateForEditor(this); + + if (DataContext is Models.RevisionTextFile file) + Models.TextMateHelper.SetGrammarByFileName(_textMate, file.FileName); + } + + private TextMate.Installation _textMate = null; } public partial class RevisionFiles : UserControl From 4c471c6bb9d93677c781631416fa3645fbb5ae65 Mon Sep 17 00:00:00 2001 From: Gadfly Date: Thu, 8 Aug 2024 10:34:30 +0800 Subject: [PATCH 34/58] fix: do NOT change parent of RepositoryNode if it exists when open it from FolderPicker --- src/Views/WelcomeToolbar.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/WelcomeToolbar.axaml.cs b/src/Views/WelcomeToolbar.axaml.cs index e0a6acfb..2f8de643 100644 --- a/src/Views/WelcomeToolbar.axaml.cs +++ b/src/Views/WelcomeToolbar.axaml.cs @@ -55,7 +55,7 @@ namespace SourceGit.Views } var normalizedPath = root.Replace("\\", "/"); - var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, true); + var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, false); var launcher = this.FindAncestorOfType()?.DataContext as ViewModels.Launcher; launcher?.OpenRepositoryInTab(node, launcher.ActivePage); } From 6c08ee1b05345e7a5c8445297ddfcdac93f6b320 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 8 Aug 2024 14:04:48 +0800 Subject: [PATCH 35/58] ux: use smaller fontsize for launcher tabbar --- src/Views/LauncherTabBar.axaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml index 7b9f8b61..45cd414a 100644 --- a/src/Views/LauncherTabBar.axaml +++ b/src/Views/LauncherTabBar.axaml @@ -65,6 +65,7 @@ Date: Thu, 8 Aug 2024 15:10:08 +0800 Subject: [PATCH 36/58] code_style: remove IDE warnings --- src/Views/RepositoryConfigure.axaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml index 1af0cc7d..02404f37 100644 --- a/src/Views/RepositoryConfigure.axaml +++ b/src/Views/RepositoryConfigure.axaml @@ -163,18 +163,18 @@ - + - + - + @@ -189,7 +189,7 @@ - + From eb441852b0d950b2cb3417ca9a75286d6725b722 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 8 Aug 2024 15:18:35 +0800 Subject: [PATCH 37/58] enhance: allow edit commit message only with `Amend` (#336) --- src/ViewModels/WorkingCopy.cs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index a3685875..b4a08fdb 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -1238,25 +1238,33 @@ namespace SourceGit.ViewModels App.RaiseException(_repo.FullPath, "Repository has unfinished job! Please wait!"); return; } - - if (_count == 0) - { - App.RaiseException(_repo.FullPath, "No files added to commit!"); - return; - } - - if (!AutoStageBeforeCommit && _staged.Count == 0) - { - App.RaiseException(_repo.FullPath, "No files added to commit!"); - return; - } - + if (string.IsNullOrWhiteSpace(_commitMessage)) { App.RaiseException(_repo.FullPath, "Commit without message is NOT allowed!"); return; } + if (!_useAmend) + { + if (AutoStageBeforeCommit) + { + if (_count == 0) + { + App.RaiseException(_repo.FullPath, "No files added to commit!"); + return; + } + } + else + { + if (_staged.Count == 0) + { + App.RaiseException(_repo.FullPath, "No files added to commit!"); + return; + } + } + } + IsCommitting = true; _repo.Settings.PushCommitMessage(_commitMessage); _repo.SetWatcherEnabled(false); From 1fe2be11a7bc303b81383c8aa8bbbbaaa92f5045 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 8 Aug 2024 21:11:10 +0800 Subject: [PATCH 38/58] refactor: rewrite submodule to support `IsDirty` state (#339) --- src/Commands/QuerySubmodules.cs | 9 +++--- src/Commands/UpdateSubmoduleStatus.cs | 43 +++++++++++++++++++++++++++ src/Models/IssueTrackerRule.cs | 2 -- src/Models/Submodule.cs | 8 +++++ src/Models/Watcher.cs | 29 +++++++++++++++++- src/Resources/Icons.axaml | 1 + src/ViewModels/Repository.cs | 7 +++-- src/ViewModels/UpdateSubmodules.cs | 11 +++++-- src/Views/Repository.axaml | 11 +++++-- src/Views/Repository.axaml.cs | 8 ++--- 10 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 src/Commands/UpdateSubmoduleStatus.cs create mode 100644 src/Models/Submodule.cs diff --git a/src/Commands/QuerySubmodules.cs b/src/Commands/QuerySubmodules.cs index 622de2fc..24e040d5 100644 --- a/src/Commands/QuerySubmodules.cs +++ b/src/Commands/QuerySubmodules.cs @@ -17,9 +17,10 @@ namespace SourceGit.Commands Args = "submodule status"; } - public List Result() + public List Result() { Exec(); + new UpdateSubmoduleStatus(WorkingDirectory, _submodules).Result(); return _submodules; } @@ -28,17 +29,17 @@ namespace SourceGit.Commands var match = REG_FORMAT1().Match(line); if (match.Success) { - _submodules.Add(match.Groups[1].Value); + _submodules.Add(new Models.Submodule() { Path = match.Groups[1].Value }); return; } match = REG_FORMAT2().Match(line); if (match.Success) { - _submodules.Add(match.Groups[1].Value); + _submodules.Add(new Models.Submodule() { Path = match.Groups[1].Value }); } } - private readonly List _submodules = new List(); + private readonly List _submodules = new List(); } } diff --git a/src/Commands/UpdateSubmoduleStatus.cs b/src/Commands/UpdateSubmoduleStatus.cs new file mode 100644 index 00000000..38b3c33e --- /dev/null +++ b/src/Commands/UpdateSubmoduleStatus.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace SourceGit.Commands +{ + public partial class UpdateSubmoduleStatus : Command + { + [GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")] + private static partial Regex REG_FORMAT(); + + public UpdateSubmoduleStatus(string repo, List submodules) + { + var pathes = new StringBuilder(); + foreach (var submodule in submodules) + pathes.Append($"\"{submodule.Path}\" "); + + _submodules = submodules; + + WorkingDirectory = repo; + Context = repo; + Args = $"status -uno --porcelain -- {pathes}"; + } + + public void Result() + { + Exec(); + + foreach (var submodule in _submodules) + submodule.IsDirty = _changed.Contains(submodule.Path); + } + + protected override void OnReadline(string line) + { + var match = REG_FORMAT().Match(line); + if (match.Success) + _changed.Add(match.Groups[1].Value); + } + + private List _submodules = null; + private HashSet _changed = new HashSet(); + } +} diff --git a/src/Models/IssueTrackerRule.cs b/src/Models/IssueTrackerRule.cs index a4a9a3d1..127cfa98 100644 --- a/src/Models/IssueTrackerRule.cs +++ b/src/Models/IssueTrackerRule.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Text.RegularExpressions; -using Avalonia.Collections; - using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.Models diff --git a/src/Models/Submodule.cs b/src/Models/Submodule.cs new file mode 100644 index 00000000..a038afef --- /dev/null +++ b/src/Models/Submodule.cs @@ -0,0 +1,8 @@ +namespace SourceGit.Models +{ + public class Submodule + { + public string Path { get; set; } = ""; + public bool IsDirty { get; set; } = false; + } +} diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index f8aacecb..971c44f6 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -70,6 +71,16 @@ namespace SourceGit.Models } } + public void UpdateSubmodules(List submodules) + { + lock (_lockSubmodule) + { + _submodules.Clear(); + foreach (var submodule in submodules) + _submodules.Add(submodule.Path); + } + } + public void MarkBranchDirtyManually() { _updateBranch = DateTime.Now.ToFileTime() - 1; @@ -168,7 +179,7 @@ namespace SourceGit.Models return; var name = e.Name.Replace("\\", "/"); - if (name.StartsWith("modules", StringComparison.Ordinal)) + if (name.StartsWith("modules", StringComparison.Ordinal) && name.EndsWith("HEAD", StringComparison.Ordinal)) { _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); } @@ -201,6 +212,19 @@ namespace SourceGit.Models var name = e.Name.Replace("\\", "/"); if (name == ".git" || name.StartsWith(".git/", StringComparison.Ordinal)) return; + + lock (_submodules) + { + foreach (var submodule in _submodules) + { + if (name.StartsWith(submodule, StringComparison.Ordinal)) + { + _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); + return; + } + } + } + _updateWC = DateTime.Now.AddSeconds(1).ToFileTime(); } @@ -214,5 +238,8 @@ namespace SourceGit.Models private long _updateSubmodules = 0; private long _updateStashes = 0; private long _updateTags = 0; + + private object _lockSubmodule = new object(); + private List _submodules = new List(); } } diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 1657ead3..a159d764 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -68,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 + M896 64H128C96 64 64 96 64 128v768c0 32 32 64 64 64h768c32 0 64-32 64-64V128c0-32-32-64-64-64z m-64 736c0 16-17 32-32 32H224c-18 0-32-12-32-32V224c0-16 16-32 32-32h576c15 0 32 16 32 32v576zM512 384c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z 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 diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 3ee8edd8..17e395ca 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -136,7 +136,7 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _visibleTags, value); } - public List Submodules + public List Submodules { get => _submodules; private set => SetProperty(ref _submodules, value); @@ -778,6 +778,9 @@ namespace SourceGit.ViewModels public void RefreshSubmodules() { var submodules = new Commands.QuerySubmodules(_fullpath).Result(); + if (_watcher != null) + _watcher.UpdateSubmodules(submodules); + Dispatcher.UIThread.Invoke(() => Submodules = submodules); } @@ -1992,7 +1995,7 @@ namespace SourceGit.ViewModels private List _worktrees = new List(); private List _tags = new List(); private List _visibleTags = new List(); - private List _submodules = new List(); + private List _submodules = new List(); private bool _includeUntracked = true; private InProgressContext _inProgressContext = null; diff --git a/src/ViewModels/UpdateSubmodules.cs b/src/ViewModels/UpdateSubmodules.cs index 2e381900..d1c433e2 100644 --- a/src/ViewModels/UpdateSubmodules.cs +++ b/src/ViewModels/UpdateSubmodules.cs @@ -7,8 +7,9 @@ namespace SourceGit.ViewModels { public List Submodules { - get => _repo.Submodules; - } + get; + private set; + } = new List(); public string SelectedSubmodule { @@ -43,7 +44,11 @@ namespace SourceGit.ViewModels public UpdateSubmodules(Repository repo) { _repo = repo; - SelectedSubmodule = repo.Submodules.Count > 0 ? repo.Submodules[0] : string.Empty; + + foreach (var submodule in _repo.Submodules) + Submodules.Add(submodule.Path); + + SelectedSubmodule = Submodules.Count > 0 ? Submodules[0] : string.Empty; View = new Views.UpdateSubmodules() { DataContext = this }; } diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index b06b056b..be29a543 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -349,9 +349,14 @@ - - - + + + + diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index cfe9d7ef..0c9a106f 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -123,9 +123,9 @@ namespace SourceGit.Views private void OnSubmoduleContextRequested(object sender, ContextRequestedEventArgs e) { - if (sender is DataGrid { SelectedItem: string submodule } grid && DataContext is ViewModels.Repository repo) + if (sender is DataGrid { SelectedItem: Models.Submodule submodule } grid && DataContext is ViewModels.Repository repo) { - var menu = repo.CreateContextMenuForSubmodule(submodule); + var menu = repo.CreateContextMenuForSubmodule(submodule.Path); grid.OpenContextMenu(menu); } @@ -134,9 +134,9 @@ namespace SourceGit.Views private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e) { - if (sender is DataGrid { SelectedItem: string submodule } && DataContext is ViewModels.Repository repo) + if (sender is DataGrid { SelectedItem: Models.Submodule submodule } && DataContext is ViewModels.Repository repo) { - repo.OpenSubmodule(submodule); + repo.OpenSubmodule(submodule.Path); } e.Handled = true; From 39691113935e2f247b4f8cab49e54cde2db23cde Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 8 Aug 2024 21:21:30 +0800 Subject: [PATCH 39/58] ux: force datagrid row height to 24 --- src/Views/BranchTree.axaml | 1 + src/Views/Repository.axaml | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml index 468eeccd..366814fe 100644 --- a/src/Views/BranchTree.axaml +++ b/src/Views/BranchTree.axaml @@ -23,6 +23,7 @@ + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index 0c9a106f..8b3eaac4 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -2,7 +2,6 @@ using System; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; @@ -30,6 +29,9 @@ namespace SourceGit.Views private void OnSearchKeyDown(object _, KeyEventArgs e) { var repo = DataContext as ViewModels.Repository; + if (repo == null) + return; + if (e.Key == Key.Enter) { if (!string.IsNullOrWhiteSpace(repo.SearchCommitFilter)) @@ -79,46 +81,25 @@ namespace SourceGit.Views private void OnLocalBranchTreeSelectionChanged(object _1, RoutedEventArgs _2) { RemoteBranchTree.UnselectAll(); - TagsList.SelectedItem = null; + TagsList.UnselectAll(); } private void OnRemoteBranchTreeSelectionChanged(object _1, RoutedEventArgs _2) { LocalBranchTree.UnselectAll(); - TagsList.SelectedItem = null; + TagsList.UnselectAll(); } - private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs _) + private void OnTagsRowsChanged(object _, RoutedEventArgs e) { - if (sender is DataGrid { SelectedItem: Models.Tag tag }) - { - LocalBranchTree.UnselectAll(); - RemoteBranchTree.UnselectAll(); - - if (DataContext is ViewModels.Repository repo) - repo.NavigateToCommit(tag.SHA); - } - } - - private void OnTagContextRequested(object sender, ContextRequestedEventArgs e) - { - if (sender is DataGrid { SelectedItem: Models.Tag tag } grid && DataContext is ViewModels.Repository repo) - { - var menu = repo.CreateContextMenuForTag(tag); - grid.OpenContextMenu(menu); - } - + UpdateLeftSidebarLayout(); e.Handled = true; } - private void OnTagFilterIsCheckedChanged(object sender, RoutedEventArgs e) + private void OnTagsSelectionChanged(object _1, RoutedEventArgs _2) { - if (sender is ToggleButton { DataContext: Models.Tag tag } toggle && DataContext is ViewModels.Repository repo) - { - repo.UpdateFilter(tag.Name, toggle.IsChecked == true); - } - - e.Handled = true; + LocalBranchTree.UnselectAll(); + RemoteBranchTree.UnselectAll(); } private void OnSubmoduleContextRequested(object sender, ContextRequestedEventArgs e) @@ -188,7 +169,7 @@ namespace SourceGit.Views var localBranchRows = vm.IsLocalBranchGroupExpanded ? LocalBranchTree.Rows.Count : 0; var remoteBranchRows = vm.IsRemoteGroupExpanded ? RemoteBranchTree.Rows.Count : 0; var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0; - var desiredTag = vm.IsTagGroupExpanded ? TagsList.RowHeight * vm.VisibleTags.Count : 0; + var desiredTag = vm.IsTagGroupExpanded ? 24.0 * TagsList.Rows : 0; var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? SubmoduleList.RowHeight * vm.Submodules.Count : 0; var desiredWorktree = vm.IsWorktreeGroupExpanded ? WorktreeList.RowHeight * vm.Worktrees.Count : 0; var desiredOthers = desiredTag + desiredSubmodule + desiredWorktree; @@ -295,9 +276,12 @@ namespace SourceGit.Views } } - private void OnSearchSuggestionBoxKeyDown(object sender, KeyEventArgs e) + private void OnSearchSuggestionBoxKeyDown(object _, KeyEventArgs e) { var repo = DataContext as ViewModels.Repository; + if (repo == null) + return; + if (e.Key == Key.Escape) { repo.IsSearchCommitSuggestionOpen = false; @@ -317,6 +301,9 @@ namespace SourceGit.Views private void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e) { var repo = DataContext as ViewModels.Repository; + if (repo == null) + return; + var content = (sender as StackPanel)?.DataContext as string; if (!string.IsNullOrEmpty(content)) { diff --git a/src/Views/TagsView.axaml b/src/Views/TagsView.axaml new file mode 100644 index 00000000..bcbbe358 --- /dev/null +++ b/src/Views/TagsView.axaml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs new file mode 100644 index 00000000..23d31ab4 --- /dev/null +++ b/src/Views/TagsView.axaml.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.VisualTree; + +namespace SourceGit.Views +{ + public class TagTreeNodeToggleButton : ToggleButton + { + protected override Type StyleKeyOverride => typeof(ToggleButton); + + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && + DataContext is ViewModels.TagTreeNode { IsFolder: true } node) + { + var view = this.FindAncestorOfType(); + view?.ToggleNodeIsExpanded(node); + } + + e.Handled = true; + } + } + + public class TagTreeNodeIcon : UserControl + { + public static readonly StyledProperty NodeProperty = + AvaloniaProperty.Register(nameof(Node)); + + public ViewModels.TagTreeNode Node + { + get => GetValue(NodeProperty); + set => SetValue(NodeProperty, value); + } + + public static readonly StyledProperty IsExpandedProperty = + AvaloniaProperty.Register(nameof(IsExpanded)); + + public bool IsExpanded + { + get => GetValue(IsExpandedProperty); + set => SetValue(IsExpandedProperty, value); + } + + static TagTreeNodeIcon() + { + NodeProperty.Changed.AddClassHandler((icon, _) => icon.UpdateContent()); + IsExpandedProperty.Changed.AddClassHandler((icon, _) => icon.UpdateContent()); + } + + private void UpdateContent() + { + var node = Node; + if (node == null) + { + Content = null; + return; + } + + if (node.Tag != null) + CreateContent(new Thickness(0, 2, 0, 0), "Icons.Tag"); + else if (node.IsExpanded) + CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder.Open"); + else + CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder"); + } + + private void CreateContent(Thickness margin, string iconKey) + { + var geo = this.FindResource(iconKey) as StreamGeometry; + if (geo == null) + return; + + Content = new Avalonia.Controls.Shapes.Path() + { + Width = 12, + Height = 12, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = margin, + Data = geo, + }; + } + } + + public partial class TagsView : UserControl + { + public static readonly StyledProperty ShowTagsAsTreeProperty = + AvaloniaProperty.Register(nameof(ShowTagsAsTree)); + + public bool ShowTagsAsTree + { + get => GetValue(ShowTagsAsTreeProperty); + set => SetValue(ShowTagsAsTreeProperty, value); + } + + public static readonly StyledProperty> TagsProperty = + AvaloniaProperty.Register>(nameof(Tags)); + + public List Tags + { + get => GetValue(TagsProperty); + set => SetValue(TagsProperty, value); + } + + public static readonly RoutedEvent SelectionChangedEvent = + RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + public event EventHandler SelectionChanged + { + add { AddHandler(SelectionChangedEvent, value); } + remove { RemoveHandler(SelectionChangedEvent, value); } + } + + public static readonly RoutedEvent RowsChangedEvent = + RoutedEvent.Register(nameof(RowsChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + public event EventHandler RowsChanged + { + add { AddHandler(RowsChangedEvent, value); } + remove { RemoveHandler(RowsChangedEvent, value); } + } + + public int Rows + { + get; + private set; + } + + public TagsView() + { + InitializeComponent(); + } + + public void UnselectAll() + { + var list = this.FindDescendantOfType(); + if (list != null) + list.SelectedItem = null; + } + + public void ToggleNodeIsExpanded(ViewModels.TagTreeNode node) + { + if (Content is ViewModels.TagCollectionAsTree tree) + { + node.IsExpanded = !node.IsExpanded; + + var depth = node.Depth; + var idx = tree.Rows.IndexOf(node); + if (idx == -1) + return; + + if (node.IsExpanded) + { + var subrows = new List(); + MakeTreeRows(subrows, node.Children); + tree.Rows.InsertRange(idx + 1, subrows); + } + else + { + var removeCount = 0; + for (int i = idx + 1; i < tree.Rows.Count; i++) + { + var row = tree.Rows[i]; + if (row.Depth <= depth) + break; + + removeCount++; + } + tree.Rows.RemoveRange(idx + 1, removeCount); + } + + Rows = tree.Rows.Count; + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); + } + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ShowTagsAsTreeProperty || change.Property == TagsProperty) + { + UpdateDataSource(); + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); + } + else if (change.Property == IsVisibleProperty) + { + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); + } + } + + private void OnDoubleTappedNode(object sender, TappedEventArgs e) + { + if (sender is Grid { DataContext: ViewModels.TagTreeNode node }) + { + if (node.IsFolder) + ToggleNodeIsExpanded(node); + } + + e.Handled = true; + } + + private void OnRowContextRequested(object sender, ContextRequestedEventArgs e) + { + var control = sender as Control; + if (control == null) + return; + + Models.Tag selected; + if (control.DataContext is ViewModels.TagTreeNode node) + selected = node.Tag; + else if (control.DataContext is Models.Tag tag) + selected = tag; + else + selected = null; + + if (selected != null && DataContext is ViewModels.Repository repo) + { + var menu = repo.CreateContextMenuForTag(selected); + control.OpenContextMenu(menu); + } + + e.Handled = true; + } + + private void OnRowSelectionChanged(object sender, SelectionChangedEventArgs _) + { + var selected = (sender as ListBox)?.SelectedItem; + var selectedTag = null as Models.Tag; + if (selected is ViewModels.TagTreeNode node) + selectedTag = node.Tag; + else if (selected is Models.Tag tag) + selectedTag = tag; + + if (selectedTag != null && DataContext is ViewModels.Repository repo) + { + RaiseEvent(new RoutedEventArgs(SelectionChangedEvent)); + repo.NavigateToCommit(selectedTag.SHA); + } + } + + private void OnToggleFilter(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo) + { + var target = null as Models.Tag; + if (toggle.DataContext is ViewModels.TagTreeNode node) + target = node.Tag; + else if (toggle.DataContext is Models.Tag tag) + target = tag; + + if (target != null) + repo.UpdateFilter(target.Name, toggle.IsChecked == true); + } + + e.Handled = true; + } + + private void MakeTreeRows(List rows, List nodes) + { + foreach (var node in nodes) + { + rows.Add(node); + + if (!node.IsExpanded || !node.IsFolder) + continue; + + MakeTreeRows(rows, node.Children); + } + } + + private void UpdateDataSource() + { + var tags = Tags; + if (tags == null || tags.Count == 0) + { + Content = null; + return; + } + + if (ShowTagsAsTree) + { + var oldExpanded = new HashSet(); + if (Content is ViewModels.TagCollectionAsTree oldTree) + { + foreach (var row in oldTree.Rows) + { + if (row.IsFolder && row.IsExpanded) + oldExpanded.Add(row.FullPath); + } + } + + var tree = new ViewModels.TagCollectionAsTree(); + tree.Tree = ViewModels.TagTreeNode.Build(tags, oldExpanded); + + var rows = new List(); + MakeTreeRows(rows, tree.Tree); + tree.Rows.AddRange(rows); + + Content = tree; + Rows = rows.Count; + } + else + { + var list = new ViewModels.TagCollectionAsList(); + list.Tags.AddRange(tags); + + Content = list; + Rows = tags.Count; + } + + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); + } + } +} + From dabdd0b06ad494e93fe3f0fd9129cb70ab1584a9 Mon Sep 17 00:00:00 2001 From: leo Date: Sun, 11 Aug 2024 18:41:59 +0800 Subject: [PATCH 57/58] code_style: remove empty lines --- src/Views/Repository.axaml.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index 8b3eaac4..0e500b2c 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -47,7 +47,6 @@ namespace SourceGit.Views SearchSuggestionBox.SelectedIndex = 0; } - e.Handled = true; } else if (e.Key == Key.Escape) From 733045c8f04ddc024e7a9e8d7e4e70543551fb3f Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 12 Aug 2024 10:13:01 +0800 Subject: [PATCH 58/58] version: Release 8.25 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 86a3e56a..68697155 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.24 \ No newline at end of file +8.25 \ No newline at end of file