From 9a0b10bd9c9646b46980e5c72d884b4d38df790b Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 17 Jun 2024 18:25:57 +0800 Subject: [PATCH 01/68] enhance: Git LFS support --- src/Commands/GitIgnore.cs | 4 +- src/Commands/LFS.cs | 79 +++++++++++++++++-- src/Models/LFSLock.cs | 9 +++ src/Resources/Icons.axaml | 6 +- src/Resources/Locales/en_US.axaml | 22 +++++- src/Resources/Locales/zh_CN.axaml | 22 +++++- src/Resources/Locales/zh_TW.axaml | 22 +++++- src/ViewModels/Cleanup.cs | 8 -- src/ViewModels/LFSFetch.cs | 27 +++++++ src/ViewModels/LFSLocks.cs | 73 +++++++++++++++++ src/ViewModels/LFSPrune.cs | 28 +++++++ src/ViewModels/LFSPull.cs | 27 +++++++ src/ViewModels/Repository.cs | 91 ++++++++++++++++++++- src/ViewModels/WorkingCopy.cs | 127 +++++++++++++++++++++++++++--- src/Views/LFSFetch.axaml | 18 +++++ src/Views/LFSFetch.axaml.cs | 12 +++ src/Views/LFSLocks.axaml | 126 +++++++++++++++++++++++++++++ src/Views/LFSLocks.axaml.cs | 40 ++++++++++ src/Views/LFSPrune.axaml | 16 ++++ src/Views/LFSPrune.axaml.cs | 12 +++ src/Views/LFSPull.axaml | 18 +++++ src/Views/LFSPull.axaml.cs | 12 +++ src/Views/Repository.axaml | 4 + src/Views/Repository.axaml.cs | 11 +++ 24 files changed, 780 insertions(+), 34 deletions(-) create mode 100644 src/Models/LFSLock.cs create mode 100644 src/ViewModels/LFSFetch.cs create mode 100644 src/ViewModels/LFSLocks.cs create mode 100644 src/ViewModels/LFSPrune.cs create mode 100644 src/ViewModels/LFSPull.cs create mode 100644 src/Views/LFSFetch.axaml create mode 100644 src/Views/LFSFetch.axaml.cs create mode 100644 src/Views/LFSLocks.axaml create mode 100644 src/Views/LFSLocks.axaml.cs create mode 100644 src/Views/LFSPrune.axaml create mode 100644 src/Views/LFSPrune.axaml.cs create mode 100644 src/Views/LFSPull.axaml create mode 100644 src/Views/LFSPull.axaml.cs diff --git a/src/Commands/GitIgnore.cs b/src/Commands/GitIgnore.cs index 44bb268b..e666eba6 100644 --- a/src/Commands/GitIgnore.cs +++ b/src/Commands/GitIgnore.cs @@ -8,9 +8,9 @@ namespace SourceGit.Commands { var file = Path.Combine(repo, ".gitignore"); if (!File.Exists(file)) - File.WriteAllLines(file, [ pattern ]); + File.WriteAllLines(file, [pattern]); else - File.AppendAllLines(file, [ pattern ]); + File.AppendAllLines(file, [pattern]); } } } diff --git a/src/Commands/LFS.cs b/src/Commands/LFS.cs index 3b8a1cc2..a48fae55 100644 --- a/src/Commands/LFS.cs +++ b/src/Commands/LFS.cs @@ -1,17 +1,22 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; namespace SourceGit.Commands { - public class LFS + public partial class LFS { - class PruneCmd : Command + [GeneratedRegex(@"^(.+)\s+(\w+)\s+\w+:(\d+)$")] + private static partial Regex REG_LOCK(); + + class SubCmd : Command { - public PruneCmd(string repo, Action onProgress) + public SubCmd(string repo, string args, Action onProgress) { WorkingDirectory = repo; Context = repo; - Args = "lfs prune"; + Args = args; TraitErrorAsOutput = true; _outputHandler = onProgress; } @@ -39,9 +44,73 @@ namespace SourceGit.Commands return content.Contains("git lfs pre-push"); } + public bool Install() + { + return new SubCmd(_repo, $"lfs install", null).Exec(); + } + + public bool Track(string pattern, bool isFilenameMode = false) + { + var opt = isFilenameMode ? "--filename" : ""; + return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", null).Exec(); + } + + public void Fetch(Action outputHandler) + { + new SubCmd(_repo, $"lfs fetch", outputHandler).Exec(); + } + + public void Pull(Action outputHandler) + { + new SubCmd(_repo, $"lfs pull", outputHandler).Exec(); + } + public void Prune(Action outputHandler) { - new PruneCmd(_repo, outputHandler).Exec(); + new SubCmd(_repo, "lfs prune", outputHandler).Exec(); + } + + public List Locks() + { + var locks = new List(); + var cmd = new SubCmd(_repo, "lfs locks", null); + var rs = cmd.ReadToEnd(); + if (rs.IsSuccess) + { + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + var match = REG_LOCK().Match(line); + if (match.Success) + { + locks.Add(new Models.LFSLock() + { + File = match.Groups[1].Value, + User = match.Groups[2].Value, + ID = long.Parse(match.Groups[3].Value), + }); + } + } + } + + return locks; + } + + public bool Lock(string file) + { + return new SubCmd(_repo, $"lfs lock \"{file}\"", null).Exec(); + } + + public bool Unlock(string file, bool force) + { + var opt = force ? "-f" : ""; + return new SubCmd(_repo, $"lfs unlock {opt} \"{file}\"", null).Exec(); + } + + public bool Unlock(long id, bool force) + { + var opt = force ? "-f" : ""; + return new SubCmd(_repo, $"lfs unlock {opt} --id={id}", null).Exec(); } private readonly string _repo; diff --git a/src/Models/LFSLock.cs b/src/Models/LFSLock.cs new file mode 100644 index 00000000..0a328cfb --- /dev/null +++ b/src/Models/LFSLock.cs @@ -0,0 +1,9 @@ +namespace SourceGit.Models +{ + public class LFSLock + { + public string File { get; set; } = string.Empty; + public string User { get; set; } = string.Empty; + public long ID { get; set; } = 0; + } +} diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 32124080..d8b0e7f5 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -61,7 +61,6 @@ M683 537h-144v-142h-142V283H239a44 44 0 00-41 41v171a56 56 0 0014 34l321 321a41 41 0 0058 0l174-174a41 41 0 000-58zm-341-109a41 41 0 110-58a41 41 0 010 58zM649 284V142h-69v142h-142v68h142v142h69v-142h142v-68h-142z M557.7 545.3 789.9 402.7c24-15 31.3-46.5 16.4-70.5c-14.8-23.8-46-31.2-70-16.7L506.5 456.6 277.1 315.4c-24.1-14.8-55.6-7.3-70.5 16.8c-14.8 24.1-7.3 55.6 16.8 70.5l231.8 142.6V819.1c0 28.3 22.9 51.2 51.2 51.2c28.3 0 51.2-22.9 51.2-51.2V545.3h.1zM506.5 0l443.4 256v511.9L506.5 1023.9 63.1 767.9v-511.9L506.5 0z M770 320a41 41 0 00-56-14l-252 153L207 306a41 41 0 10-43 70l255 153 2 296a41 41 0 0082 0l-2-295 255-155a41 41 0 0014-56zM481 935a42 42 0 01-42 0L105 741a42 42 0 01-21-36v-386a42 42 0 0121-36L439 89a42 42 0 0142 0l335 193a42 42 0 0121 36v87h84v-87a126 126 0 00-63-109L523 17a126 126 0 00-126 0L63 210a126 126 0 00-63 109v386a126 126 0 0063 109l335 193a126 126 0 00126 0l94-54-42-72zM1029 700h-126v-125a42 42 0 00-84 0v126h-126a42 42 0 000 84h126v126a42 42 0 1084 0v-126h126a42 42 0 000-84z - M170 470l0 84 86 0 0-84-86 0zM86 598l0-172 852 0 0 172-852 0zM256 298l0-84-86 0 0 84 86 0zM86 170l852 0 0 172-852 0 0-172zM170 726l0 84 86 0 0-84-86 0zM86 854l0-172 852 0 0 172-852 0z M812 864h-29V654c0-21-11-40-28-52l-133-88 134-89c18-12 28-31 28-52V164h28c18 0 32-14 32-32s-14-32-32-32H212c-18 0-32 14-32 32s14 32 32 32h30v210c0 21 11 40 28 52l133 88-134 89c-18 12-28 31-28 52V864H212c-18 0-32 14-32 32s14 32 32 32h600c18 0 32-14 32-32s-14-32-32-32zM441 566c18-12 28-31 28-52s-11-40-28-52L306 373V164h414v209l-136 90c-18 12-28 31-28 52 0 21 11 40 28 52l135 89V695c-9-7-20-13-32-19-30-15-93-41-176-41-63 0-125 14-175 38-12 6-22 12-31 18v-36l136-90z M512 0C233 0 7 223 0 500C6 258 190 64 416 64c230 0 416 200 416 448c0 53 43 96 96 96s96-43 96-96c0-283-229-512-512-512zm0 1023c279 0 505-223 512-500c-6 242-190 436-416 436c-230 0-416-200-416-448c0-53-43-96-96-96s-96 43-96 96c0 283 229 512 512 512z M888.8 0H135.2c-32.3 0-58.9 26.1-58.9 58.9v906.2c0 32.3 26.1 58.9 58.9 58.9h753.2c32.3 0 58.9-26.1 58.9-58.9v-906.2c.5-32.8-26.1-58.9-58.4-58.9zm-164.9 176.6c30.7 0 55.8 25.1 55.8 55.8s-25.1 55.8-55.8 55.8s-55.8-25.1-55.8-55.8s24.6-55.8 55.8-55.8zm-212 0c30.7 0 55.8 25.1 55.8 55.8S542.7 288.3 512 288.3s-55.8-25.1-55.8-55.8S481.3 176.6 512 176.6zm-212 0c30.7 0 55.8 25.1 55.8 55.8s-25.1 55.8-55.8 55.8s-55.8-25.1-55.8-55.8s25.1-55.8 55.8-55.8zm208.9 606.2H285.2c-24.6 0-44-20-44-44c0-24.6 20-44 44-44h223.7c24.6 0 44 20 44 44c0 24.1-19.5 44-44 44zm229.9-212H285.2c-24.6 0-44-20-44-44c0-24.6 20-44 44-44h453.1c24.6 0 44 20 44 44c.5 24.1-19.5 44-43.5 44z @@ -90,7 +89,7 @@ M765 118 629 239l-16 137-186 160 54 59 183-168 144 4 136-129 47-43-175-12L827 67zM489 404c-66 0-124 55-124 125s54 121 124 121c66 0 120-55 120-121H489l23-121c-8-4-16-4-23-4zM695 525c0 114-93 207-206 207s-206-94-206-207 93-207 206-207c16 0 27 0 43 4l43-207c-27-4-54-8-85-8-229 0-416 188-416 419s187 419 416 419c225 0 408-180 416-403v-12l-210-4z M973 358a51 51 0 0151 51v563a51 51 0 01-51 51H51a51 51 0 01-51-51V410a51 51 0 0151-51h256a51 51 0 110 102H102v461h819V461h-205a51 51 0 110-102h256zM51 102a51 51 0 110-102h256c141 0 256 115 256 256v388l66-66a51 51 0 1172 72l-154 154a51 51 0 01-72 0l-154-154a51 51 0 1172-72L461 644V256c0-85-69-154-154-154H51z M976 0h-928A48 48 0 000 48v652a48 48 0 0048 48h416V928H200a48 48 0 000 96h624a48 48 0 000-96H560v-180h416a48 48 0 0048-48V48A48 48 0 00976 0zM928 652H96V96h832v556z - 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 + 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 M939 94v710L512 998 85 805V94h-64A21 21 0 010 73v-0C0 61 10 51 21 51h981c12 0 21 10 21 21v0c0 12-10 21-21 21h-64zm-536 588L512 624l109 58c6 3 13 4 20 3a32 32 0 0026-37l-21-122 88-87c5-5 8-11 9-18a32 32 0 00-27-37l-122-18-54-111a32 32 0 00-57 0l-54 111-122 18c-7 1-13 4-18 9a33 33 0 001 46l88 87-21 122c-1 7-0 14 3 20a32 32 0 0043 14z M236 542a32 32 0 109 63l86-12a180 180 0 0022 78l-71 47a32 32 0 1035 53l75-50a176 176 0 00166 40L326 529zM512 16C238 16 16 238 16 512s222 496 496 496 496-222 496-496S786 16 512 16zm0 896c-221 0-400-179-400-400a398 398 0 0186-247l561 561A398 398 0 01512 912zm314-154L690 622a179 179 0 004-29l85 12a32 32 0 109-63l-94-13v-49l94-13a32 32 0 10-9-63l-87 12a180 180 0 00-20-62l71-47A32 32 0 10708 252l-75 50a181 181 0 00-252 10l-115-115A398 398 0 01512 112c221 0 400 179 400 400a398 398 0 01-86 247z M884 159l-18-18a43 43 0 00-38-12l-235 43a166 166 0 00-101 60L400 349a128 128 0 00-148 47l-120 171a21 21 0 005 29l17 12a128 128 0 00178-32l27-38 124 124-38 27a128 128 0 00-32 178l12 17a21 21 0 0029 5l171-120a128 128 0 0047-148l117-92A166 166 0 00853 431l43-235a43 43 0 00-12-38zm-177 249a64 64 0 110-90 64 64 0 010 90zm-373 312a21 21 0 010 30l-139 139a21 21 0 01-30 0l-30-30a21 21 0 010-30l139-139a21 21 0 0130 0z @@ -100,4 +99,7 @@ M248 221a77 77 0 00-30-21c-18-7-40-10-68-5a224 224 0 00-45 13c-5 2-10 5-15 8l-3 2v68l11-9c10-8 21-14 34-19 13-5 26-7 39-7 12 0 21 3 28 10 6 6 9 16 9 29l-62 9c-14 2-26 6-36 11a80 80 0 00-25 20c-7 8-12 17-15 27-6 21-6 44 1 65a70 70 0 0041 43c10 4 21 6 34 6a80 80 0 0063-28v22h64V298c0-16-2-31-6-44a91 91 0 00-18-33zm-41 121v15c0 8-1 15-4 22a48 48 0 01-24 29 44 44 0 01-33 2 29 29 0 01-10-6 25 25 0 01-6-9 30 30 0 01-2-12c0-5 1-9 2-14a21 21 0 015-9 28 28 0 0110-7 83 83 0 0120-5l42-6zm323-68a144 144 0 00-16-42 87 87 0 00-28-29 75 75 0 00-41-11 73 73 0 00-44 14c-6 5-12 11-17 17V64H326v398h59v-18c8 10 18 17 30 21 6 2 13 3 21 3 16 0 31-4 43-11 12-7 23-18 31-31a147 147 0 0019-46 248 248 0 006-57c0-17-2-33-5-49zm-55 49c0 15-1 28-4 39-2 11-6 20-10 27a41 41 0 01-15 15 37 37 0 01-36 1 44 44 0 01-13-12 59 59 0 01-9-18A76 76 0 01384 352v-33c0-10 1-20 4-29 2-8 6-15 10-22a43 43 0 0115-13 37 37 0 0119-5 35 35 0 0132 18c4 6 7 14 9 23 2 9 3 20 3 31zM154 634a58 58 0 0120-15c14-6 35-7 49-1 7 3 13 6 20 12l21 17V572l-6-4a124 124 0 00-58-14c-20 0-38 4-54 11-16 7-30 17-41 30-12 13-20 29-26 46-6 17-9 36-9 57 0 18 3 36 8 52 6 16 14 30 24 42 10 12 23 21 38 28 15 7 32 10 50 10 15 0 28-2 39-5 11-3 21-8 30-14l5-4v-57l-13 6a26 26 0 01-5 2c-3 1-6 2-8 3-2 1-15 6-15 6-4 2-9 3-14 4a63 63 0 01-38-4 53 53 0 01-20-14 70 70 0 01-13-24 111 111 0 01-5-34c0-13 2-26 5-36 3-10 8-19 14-26zM896 384h-256V320h288c21 1 32 12 32 32v384c0 18-12 32-32 32H504l132 133-45 45-185-185c-16-21-16-25 0-45l185-185L637 576l-128 128H896V384z M128 183C128 154 154 128 183 128h521c30 0 55 26 55 55v38c0 17-17 34-34 34s-34-17-34-34v-26H196v495h26c17 0 34 17 34 34s-17 34-34 34h-38c-30 0-55-26-55-55V183zM380 896h-34c-26 0-47-21-47-47v-90h68V828h64V896H380c4 0 0 0 0 0zM759 828V896h90c26 0 47-21 47-47v-90h-68V828h-68zM828 435H896V346c0-26-21-47-47-47h-90v68H828v68zM435 299v68H367V439H299V346C299 320 320 299 346 299h90zM367 649H299v-107h68v107zM546 367V299h107v68h-107zM828 546H896v107h-68v-107zM649 828V896h-107v-68h107zM730 508v188c0 17-17 34-34 34h-188c-17 0-34-17-34-34s17-34 34-34h102l-124-124c-13-13-13-34 0-47 13-13 34-13 47 0l124 124V512c0-17 17-34 34-34 21-4 38 9 38 30z M590 74 859 342V876c0 38-31 68-68 68H233c-38 0-68-31-68-68V142c0-38 31-68 68-68h357zm-12 28H233a40 40 0 00-40 38L193 142v734a40 40 0 0038 40L233 916h558a40 40 0 0040-38L831 876V354L578 102zM855 371h-215c-46 0-83-36-84-82l0-2V74h28v213c0 30 24 54 54 55l2 0h215v28zM57 489m28 0 853 0q28 0 28 28l0 284q0 28-28 28l-853 0q-28 0-28-28l0-284q0-28 28-28ZM157 717c15 0 29-6 37-13v-51h-41v22h17v18c-2 2-6 3-10 3-21 0-30-13-30-34 0-21 12-34 28-34 9 0 15 4 20 9l14-17C184 610 172 603 156 603c-29 0-54 21-54 57 0 37 24 56 54 56zM245 711v-108h-34v108h34zm69 0v-86H341V603H262v22h28V711h24zM393 711v-108h-34v108h34zm66 6c15 0 29-6 37-13v-51h-41v22h17v18c-2 2-6 3-10 3-21 0-30-13-30-34 0-21 12-34 28-34 9 0 15 4 20 9l14-17C485 610 474 603 458 603c-29 0-54 21-54 57 0 37 24 56 54 56zm88-6v-36c0-13-2-28-3-40h1l10 24 25 52H603v-108h-23v36c0 13 2 28 3 40h-1l-10-24L548 603H523v108h23zM677 717c30 0 51-22 51-57 0-36-21-56-51-56-30 0-51 20-51 56 0 36 21 57 51 57zm3-23c-16 0-26-12-26-32 0-19 10-31 26-31 16 0 26 11 26 31S696 694 680 694zm93 17v-38h13l21 38H836l-25-43c12-5 19-15 19-31 0-26-20-34-44-34H745v108h27zm16-51H774v-34h15c16 0 25 4 25 16s-9 18-25 18zM922 711v-22h-43v-23h35v-22h-35V625h41V603H853v108h68z + M40 9 15 23 15 31 9 28 9 20 34 5 24 0 0 14 0 34 25 48 25 28 49 14zM26 29 26 48 49 34 49 15z + M832 464h-68V240a128 128 0 00-128-128h-248a128 128 0 00-128 128v224H192c-18 0-32 14-32 32v384c0 18 14 32 32 32h640c18 0 32-14 32-32v-384c0-18-14-32-32-32zm-292 237v53a8 8 0 01-8 8h-40a8 8 0 01-8-8v-53a48 48 0 1156 0zm152-237H332V240a56 56 0 0156-56h248a56 56 0 0156 56v224z + M832 464H332V240c0-31 25-56 56-56h248c31 0 56 25 56 56v68c0 4 4 8 8 8h56c4 0 8-4 8-8v-68c0-71-57-128-128-128H388c-71 0-128 57-128 128v224h-68c-18 0-32 14-32 32v384c0 18 14 32 32 32h640c18 0 32-14 32-32V496c0-18-14-32-32-32zM540 701v53c0 4-4 8-8 8h-40c-4 0-8-4-8-8v-53c-12-9-20-23-20-39 0-27 22-48 48-48s48 22 48 48c0 16-8 30-20 39z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index a1b03bf0..91d3ed83 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -228,6 +228,24 @@ Start Release ... FLOW - Start Release Version Tag Prefix : + Git LFS + Fetch ... + Fetch LFS Objects + Run `git lfs fetch` to download Git LFS objects. This does not update the working copy. + Install Git LFS hooks + Show Locks + No Locked Files + Lock + LFS Locks + Unlock + Force Unlock + Prune + Run `git lfs prune` to delete old LFS files from local storage + Pull ... + Pull LFS Objects + Run `git lfs pull` to download all Git LFS files for current ref & checkout + Track files named '{0}' + Track all *{0} files Histories Switch Horizontal/Vertical Layout Switch Curve/Polyline Graph Mode @@ -373,7 +391,7 @@ Branch : ABORT Cleanup(GC & Prune) - Run `gc` command and do `lfs prune` if LFS is installed. + Run `git gc` command for this repository. Configure this repository CONTINUE Open In File Browser @@ -477,7 +495,7 @@ Search Repositories ... Sort Changes - Add To .gitignore ... + Git Ignore Ignore all *{0} files Ignore *{0} files in the same folder Ignore files in the same folder diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 1de41e04..5ecf6a63 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -231,6 +231,24 @@ 开始版本分支... 开始版本分支 版本标签前缀 : + Git LFS + 拉取LFS对象 (fetch) ... + 拉取LFS对象 + 执行`git lfs prune`命令,下载远程LFS对象,但不会更新工作副本。 + 启用Git LFS支持 + 显示LFS对象锁 + 没有锁定的LFS文件 + 锁定 + LFS对象锁状态 + 解锁 + 强制解锁 + 精简本地LFS对象存储 + 运行`git lfs prune`命令,从本地存储中精简当前版本不需要的LFS对象 + 拉回LFS对象 (pull) ... + 拉回LFS对象 + 运行`git lfs pull`命令,下载远程LFS对象并更新工作副本。 + 跟踪名为'{0}'的文件 + 跟踪所有 *{0} 文件 历史记录 切换横向/纵向显示 切换曲线/折线显示 @@ -376,7 +394,7 @@ 分支 : 终止合并 清理本仓库(GC) - 本操作将执行`gc`,对于启用LFS的仓库也会执行`lfs prune`。 + 本操作将执行`git gc`命令。 配置本仓库 下一步 在文件浏览器中打开 @@ -480,7 +498,7 @@ 快速查找仓库... 排序 本地更改 - 添加至 .gitignore 忽略列表 ... + 添加至 .gitignore 忽略列表 忽略所有 *{0} 文件 忽略同目录下所有 *{0} 文件 忽略同目录下所有文件 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 79c4be87..9c97df8b 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -231,6 +231,24 @@ 開始版本分支... 開始版本分支 版本標籤字首 : + Git LFS + 拉取LFS物件 (fetch) ... + 拉取LFS物件 + 執行`git lfs fetch`命令,下載遠端LFS物件,但不會更新工作副本。 + 啟用Git LFS支援 + 顯示LFS物件鎖 + 沒有鎖定的LFS物件 + 鎖定 + LFS物件鎖 + 解鎖 + 強制解鎖 + 精簡本地LFS物件存儲 + 執行`git lfs prune`命令,從本地存儲中精簡當前版本不需要的LFS物件 + 拉回LFS物件 (pull) ... + 拉回LFS物件 + 執行`git lfs pull`命令,下載遠端LFS物件并更新工作副本。 + 跟蹤名為'{0}'的檔案 + 跟蹤所有 *{0} 檔案 歷史記錄 切換橫向/縱向顯示 切換曲線/折線顯示 @@ -376,7 +394,7 @@ 分支 : 終止合併 清理本倉庫(GC) - 本操作將執行`gc`,對於啟用LFS的倉庫也會執行`lfs prune`。 + 本操作將執行`git gc`命令。 配置本倉庫 下一步 在檔案瀏覽器中開啟 @@ -480,7 +498,7 @@ 快速查詢倉庫... 排序 本地更改 - 添加至 .gitignore 忽略清單 ... + 添加至 .gitignore 忽略清單 忽略所有 *{0} 檔案 忽略同路徑下所有 *{0} 檔案 忽略同路徑下所有檔案 diff --git a/src/ViewModels/Cleanup.cs b/src/ViewModels/Cleanup.cs index 75cc5943..7efcf73e 100644 --- a/src/ViewModels/Cleanup.cs +++ b/src/ViewModels/Cleanup.cs @@ -18,14 +18,6 @@ namespace SourceGit.ViewModels return Task.Run(() => { new Commands.GC(_repo.FullPath, SetProgressDescription).Exec(); - - var lfs = new Commands.LFS(_repo.FullPath); - if (lfs.IsEnabled()) - { - SetProgressDescription("Run LFS prune ..."); - lfs.Prune(SetProgressDescription); - } - CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/LFSFetch.cs b/src/ViewModels/LFSFetch.cs new file mode 100644 index 00000000..2591c7d9 --- /dev/null +++ b/src/ViewModels/LFSFetch.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class LFSFetch : Popup + { + public LFSFetch(Repository repo) + { + _repo = repo; + View = new Views.LFSFetch() { DataContext = this }; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = $"Fetching LFS objects from remote ..."; + return Task.Run(() => + { + new Commands.LFS(_repo.FullPath).Fetch(SetProgressDescription); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return true; + }); + } + + private readonly Repository _repo = null; + } +} diff --git a/src/ViewModels/LFSLocks.cs b/src/ViewModels/LFSLocks.cs new file mode 100644 index 00000000..92a01d10 --- /dev/null +++ b/src/ViewModels/LFSLocks.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; + +using Avalonia.Collections; +using Avalonia.Threading; + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class LFSLocks : ObservableObject + { + public bool IsLoading + { + get => _isLoading; + private set => SetProperty(ref _isLoading, value); + } + + public bool IsEmpty + { + get => _isEmpty; + private set => SetProperty(ref _isEmpty, value); + } + + public AvaloniaList Locks + { + get; + private set; + } + + public LFSLocks(string repo) + { + _repo = repo; + Locks = new AvaloniaList(); + + Task.Run(() => + { + var collect = new Commands.LFS(_repo).Locks(); + Dispatcher.UIThread.Invoke(() => + { + if (collect.Count > 0) + Locks.AddRange(collect); + + IsLoading = false; + IsEmpty = collect.Count == 0; + }); + }); + } + + public void Unlock(Models.LFSLock lfsLock, bool force) + { + if (_isLoading) + return; + + IsLoading = true; + Task.Run(() => + { + var succ = new Commands.LFS(_repo).Unlock(lfsLock.ID, force); + Dispatcher.UIThread.Invoke(() => + { + if (succ) + Locks.Remove(lfsLock); + + IsLoading = false; + IsEmpty = Locks.Count == 0; + }); + }); + } + + private string _repo = string.Empty; + private bool _isLoading = true; + private bool _isEmpty = false; + } +} diff --git a/src/ViewModels/LFSPrune.cs b/src/ViewModels/LFSPrune.cs new file mode 100644 index 00000000..3475cb81 --- /dev/null +++ b/src/ViewModels/LFSPrune.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class LFSPrune : Popup + { + public LFSPrune(Repository repo) + { + _repo = repo; + View = new Views.LFSPrune() { DataContext = this }; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = "LFS prune ..."; + + return Task.Run(() => + { + new Commands.LFS(_repo.FullPath).Prune(SetProgressDescription); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return true; + }); + } + + private readonly Repository _repo = null; + } +} diff --git a/src/ViewModels/LFSPull.cs b/src/ViewModels/LFSPull.cs new file mode 100644 index 00000000..f1f55448 --- /dev/null +++ b/src/ViewModels/LFSPull.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class LFSPull : Popup + { + public LFSPull(Repository repo) + { + _repo = repo; + View = new Views.LFSPull() { DataContext = this }; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = $"Pull LFS objects from remote ..."; + return Task.Run(() => + { + new Commands.LFS(_repo.FullPath).Pull(SetProgressDescription); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return true; + }); + } + + private readonly Repository _repo = null; + } +} diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 4eb6c5fc..e2d13d9a 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -814,7 +814,7 @@ namespace SourceGit.ViewModels { var init = new MenuItem(); init.Header = App.Text("GitFlow.Init"); - init.Icon = App.CreateMenuIcon("Icons.GitFlow.Init"); + init.Icon = App.CreateMenuIcon("Icons.Init"); init.Click += (o, e) => { if (PopupHost.CanCreatePopup()) @@ -826,6 +826,95 @@ namespace SourceGit.ViewModels return menu; } + public ContextMenu CreateContextMenuForGitLFS() + { + var menu = new ContextMenu(); + menu.Placement = PlacementMode.BottomEdgeAlignedLeft; + + var lfs = new Commands.LFS(_fullpath); + if (lfs.IsEnabled()) + { + var fetch = new MenuItem(); + fetch.Header = App.Text("GitLFS.Fetch"); + fetch.Icon = App.CreateMenuIcon("Icons.Fetch"); + fetch.IsEnabled = Remotes.Count > 0; + fetch.Click += (o, e) => + { + if (PopupHost.CanCreatePopup()) + { + if (Remotes.Count == 1) + PopupHost.ShowAndStartPopup(new LFSFetch(this)); + else + PopupHost.ShowPopup(new LFSFetch(this)); + } + + e.Handled = true; + }; + menu.Items.Add(fetch); + + var pull = new MenuItem(); + pull.Header = App.Text("GitLFS.Pull"); + pull.Icon = App.CreateMenuIcon("Icons.Pull"); + pull.IsEnabled = Remotes.Count > 0; + pull.Click += (o, e) => + { + if (PopupHost.CanCreatePopup()) + { + if (Remotes.Count == 1) + PopupHost.ShowAndStartPopup(new LFSPull(this)); + else + PopupHost.ShowPopup(new LFSPull(this)); + } + + e.Handled = true; + }; + menu.Items.Add(pull); + + var prune = new MenuItem(); + prune.Header = App.Text("GitLFS.Prune"); + prune.Icon = App.CreateMenuIcon("Icons.Clean"); + prune.Click += (o, e) => + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowAndStartPopup(new LFSPrune(this)); + + e.Handled = true; + }; + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(prune); + + var locks = new MenuItem(); + locks.Header = App.Text("GitLFS.Locks"); + locks.Icon = App.CreateMenuIcon("Icons.Lock"); + locks.Click += (o, e) => + { + var dialog = new Views.LFSLocks() { DataContext = new LFSLocks(_fullpath) }; + dialog.Show(App.GetTopLevel() as Window); + + e.Handled = true; + }; + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(locks); + } + else + { + var install = new MenuItem(); + install.Header = App.Text("GitLFS.Install"); + install.Icon = App.CreateMenuIcon("Icons.Init"); + install.Click += (o, e) => + { + var succ = new Commands.LFS(_fullpath).Install(); + if (succ) + App.SendNotification(_fullpath, $"LFS enabled successfully!"); + + e.Handled = true; + }; + menu.Items.Add(install); + } + + return menu; + } + public ContextMenu CreateContextMenuForLocalBranch(Models.Branch branch) { var menu = new ContextMenu(); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 464465d7..0f5c11e5 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -527,7 +527,7 @@ namespace SourceGit.ViewModels e.Handled = true; }; - + var assumeUnchanged = new MenuItem(); assumeUnchanged.Header = App.Text("FileCM.AssumeUnchanged"); assumeUnchanged.Icon = App.CreateMenuIcon("Icons.File.Ignore"); @@ -556,7 +556,9 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(history); menu.Items.Add(new MenuItem() { Header = "-" }); - + + var extension = Path.GetExtension(change.Path); + var hasExtra = false; if (change.WorkTree == Models.ChangeState.Untracked) { var isRooted = change.Path.IndexOf('/', StringComparison.Ordinal) <= 0; @@ -583,8 +585,7 @@ namespace SourceGit.ViewModels }; addToIgnore.Items.Add(byParentFolder); - var extension = Path.GetExtension(change.Path); - if (!string.IsNullOrEmpty(extension)) + if (!string.IsNullOrEmpty(extension)) { var byExtension = new MenuItem(); byExtension.Header = App.Text("WorkingCopy.AddToGitIgnore.Extension", extension); @@ -594,7 +595,7 @@ namespace SourceGit.ViewModels e.Handled = true; }; addToIgnore.Items.Add(byExtension); - + var byExtensionInSameFolder = new MenuItem(); byExtensionInSameFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.ExtensionInSameFolder", extension); byExtensionInSameFolder.IsVisible = !isRooted; @@ -607,8 +608,77 @@ namespace SourceGit.ViewModels } menu.Items.Add(addToIgnore); - menu.Items.Add(new MenuItem() { Header = "-" }); + hasExtra = true; } + + var lfsEnabled = new Commands.LFS(_repo.FullPath).IsEnabled(); + if (lfsEnabled) + { + var lfs = new MenuItem(); + lfs.Header = App.Text("GitLFS"); + lfs.Icon = App.CreateMenuIcon("Icons.LFS"); + + var filename = Path.GetFileName(change.Path); + var lfsTrackThisFile = new MenuItem(); + lfsTrackThisFile.Header = App.Text("GitLFS.Track", filename); + lfsTrackThisFile.Click += async (_, e) => + { + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track(filename, true)); + if (succ) + App.SendNotification(_repo.FullPath, $"Tracking file named {filename} successfully!"); + + e.Handled = true; + }; + lfs.Items.Add(lfsTrackThisFile); + + if (!string.IsNullOrEmpty(extension)) + { + var lfsTrackByExtension = new MenuItem(); + lfsTrackByExtension.Header = App.Text("GitLFS.TrackByExtension", extension); + lfsTrackByExtension.Click += async (_, e) => + { + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track("*" + extension, false)); + if (succ) + App.SendNotification(_repo.FullPath, $"Tracking all *{extension} files successfully!"); + + e.Handled = true; + }; + lfs.Items.Add(lfsTrackByExtension); + } + + var lfsLock = new MenuItem(); + lfsLock.Header = App.Text("GitLFS.Locks.Lock"); + lfsLock.Icon = App.CreateMenuIcon("Icons.Lock"); + lfsLock.Click += async (_, e) => + { + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(change.Path)); + if (succ) + App.SendNotification(_repo.FullPath, $"Lock file \"{change.Path}\" successfully!"); + + e.Handled = true; + }; + lfs.Items.Add(new MenuItem() { Header = "-" }); + lfs.Items.Add(lfsLock); + + var lfsUnlock = new MenuItem(); + lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock"); + lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock"); + lfsUnlock.Click += async (_, e) => + { + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(change.Path, false)); + if (succ) + App.SendNotification(_repo.FullPath, $"Unlock file \"{change.Path}\" successfully!"); + + e.Handled = true; + }; + lfs.Items.Add(lfsUnlock); + + menu.Items.Add(lfs); + hasExtra = true; + } + + if (hasExtra) + menu.Items.Add(new MenuItem() { Header = "-" }); } var copy = new MenuItem(); @@ -702,9 +772,8 @@ namespace SourceGit.ViewModels stash.Click += (_, e) => { if (PopupHost.CanCreatePopup()) - { PopupHost.ShowPopup(new StashChanges(_repo, _selectedUnstaged, false)); - } + e.Handled = true; }; @@ -797,9 +866,8 @@ namespace SourceGit.ViewModels stash.Click += (_, e) => { if (PopupHost.CanCreatePopup()) - { PopupHost.ShowPopup(new StashChanges(_repo, _selectedStaged, false)); - } + e.Handled = true; }; @@ -854,6 +922,45 @@ namespace SourceGit.ViewModels menu.Items.Add(stash); menu.Items.Add(patch); menu.Items.Add(new MenuItem() { Header = "-" }); + + var lfsEnabled = new Commands.LFS(_repo.FullPath).IsEnabled(); + if (lfsEnabled) + { + var lfs = new MenuItem(); + lfs.Header = App.Text("GitLFS"); + lfs.Icon = App.CreateMenuIcon("Icons.LFS"); + + var lfsLock = new MenuItem(); + lfsLock.Header = App.Text("GitLFS.Locks.Lock"); + lfsLock.Icon = App.CreateMenuIcon("Icons.Lock"); + lfsLock.Click += async (_, e) => + { + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(change.Path)); + if (succ) + App.SendNotification(_repo.FullPath, $"Lock file \"{change.Path}\" successfully!"); + + e.Handled = true; + }; + lfs.Items.Add(new MenuItem() { Header = "-" }); + lfs.Items.Add(lfsLock); + + var lfsUnlock = new MenuItem(); + lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock"); + lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock"); + lfsUnlock.Click += async (_, e) => + { + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(change.Path, false)); + if (succ) + App.SendNotification(_repo.FullPath, $"Unlock file \"{change.Path}\" successfully!"); + + e.Handled = true; + }; + lfs.Items.Add(lfsUnlock); + + menu.Items.Add(lfs); + menu.Items.Add(new MenuItem() { Header = "-" }); + } + menu.Items.Add(copyPath); menu.Items.Add(copyFileName); } diff --git a/src/Views/LFSFetch.axaml b/src/Views/LFSFetch.axaml new file mode 100644 index 00000000..4fd8e5cb --- /dev/null +++ b/src/Views/LFSFetch.axaml @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/Views/LFSFetch.axaml.cs b/src/Views/LFSFetch.axaml.cs new file mode 100644 index 00000000..2de6cc8d --- /dev/null +++ b/src/Views/LFSFetch.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class LFSFetch : UserControl + { + public LFSFetch() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/LFSLocks.axaml b/src/Views/LFSLocks.axaml new file mode 100644 index 00000000..b2e3fe84 --- /dev/null +++ b/src/Views/LFSLocks.axaml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/LFSLocks.axaml.cs b/src/Views/LFSLocks.axaml.cs new file mode 100644 index 00000000..a46674af --- /dev/null +++ b/src/Views/LFSLocks.axaml.cs @@ -0,0 +1,40 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; + +namespace SourceGit.Views +{ + public partial class LFSLocks : ChromelessWindow + { + public LFSLocks() + { + InitializeComponent(); + } + + private void BeginMoveWindow(object sender, PointerPressedEventArgs e) + { + BeginMoveDrag(e); + } + + private void CloseWindow(object sender, RoutedEventArgs e) + { + Close(); + } + + private void OnUnlockButtonClicked(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.LFSLocks vm && sender is Button button) + vm.Unlock(button.DataContext as Models.LFSLock, false); + + e.Handled = true; + } + + private void OnForceUnlockButtonClicked(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.LFSLocks vm && sender is Button button) + vm.Unlock(button.DataContext as Models.LFSLock, true); + + e.Handled = true; + } + } +} diff --git a/src/Views/LFSPrune.axaml b/src/Views/LFSPrune.axaml new file mode 100644 index 00000000..a8ada710 --- /dev/null +++ b/src/Views/LFSPrune.axaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/src/Views/LFSPrune.axaml.cs b/src/Views/LFSPrune.axaml.cs new file mode 100644 index 00000000..dbb4a376 --- /dev/null +++ b/src/Views/LFSPrune.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class LFSPrune : UserControl + { + public LFSPrune() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/LFSPull.axaml b/src/Views/LFSPull.axaml new file mode 100644 index 00000000..f472f567 --- /dev/null +++ b/src/Views/LFSPull.axaml @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/Views/LFSPull.axaml.cs b/src/Views/LFSPull.axaml.cs new file mode 100644 index 00000000..db71afe6 --- /dev/null +++ b/src/Views/LFSPull.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class LFSPull : UserControl + { + public LFSPull() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 1dc364aa..cf7ae7fd 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -72,6 +72,10 @@ + + diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index bc155592..4a6d2977 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -37,6 +37,17 @@ namespace SourceGit.Views e.Handled = true; } + private void OpenGitLFSMenu(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.Repository repo) + { + var menu = repo.CreateContextMenuForGitLFS(); + (sender as Control)?.OpenContextMenu(menu); + } + + e.Handled = true; + } + private async void OpenStatistics(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.Repository repo) From bac82ad6cf304bab07fb028948943bbe7c62097c Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 17 Jun 2024 18:32:42 +0800 Subject: [PATCH 02/68] readme: add description for GIT LFS support --- README.md | 1 + src/ViewModels/WorkingCopy.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d923107..b325e6fc 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Opensource Git GUI client. * Branch Diff * Image Diff * GitFlow support +* Git LFS support > **Linux** only tested on **Ubuntu 22.04** on **X11**. diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 0f5c11e5..a0b406cb 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -676,7 +676,7 @@ namespace SourceGit.ViewModels menu.Items.Add(lfs); hasExtra = true; } - + if (hasExtra) menu.Items.Add(new MenuItem() { Header = "-" }); } From 3afb134037246e12706cb2e1267128df2a22248d Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 17 Jun 2024 19:44:54 +0800 Subject: [PATCH 03/68] cleanup: remove duplicated --- src/Resources/Icons.axaml | 4 +--- src/ViewModels/Repository.cs | 2 +- src/Views/Launcher.axaml | 2 +- src/Views/Preference.axaml | 2 +- src/Views/Repository.axaml | 2 +- src/Views/StashesPage.axaml | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index d8b0e7f5..f96d4eb7 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -12,12 +12,10 @@ M576 832C576 867 547 896 512 896 477 896 448 867 448 832 448 797 477 768 512 768 547 768 576 797 576 832ZM512 256C477 256 448 285 448 320L448 640C448 675 477 704 512 704 547 704 576 675 576 640L576 320C576 285 547 256 512 256ZM1024 896C1024 967 967 1024 896 1024L128 1024C57 1024 0 967 0 896 0 875 5 855 14 837L14 837 398 69 398 69C420 28 462 0 512 0 562 0 604 28 626 69L1008 835C1018 853 1024 874 1024 896ZM960 896C960 885 957 875 952 865L952 864 951 863 569 98C557 77 536 64 512 64 488 64 466 77 455 99L452 105 92 825 93 825 71 867C66 876 64 886 64 896 64 931 93 960 128 960L896 960C931 960 960 931 960 896Z M608 0q48 0 88 23t63 63 23 87v70h55q35 0 67 14t57 38 38 57 14 67V831q0 34-14 66t-38 57-57 38-67 13H426q-34 0-66-13t-57-38-38-57-14-66v-70h-56q-34 0-66-14t-57-38-38-57-13-67V174q0-47 23-87T109 23 196 0h412m175 244H426q-46 0-86 22T278 328t-26 85v348H608q47 0 86-22t63-62 25-85l1-348m-269 318q18 0 31 13t13 31-13 31-31 13-31-13-13-31 13-31 31-13m0-212q13 0 22 9t11 22v125q0 14-9 23t-22 10-23-7-11-22l-1-126q0-13 10-23t23-10z 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 - M716.3 383.1c0 38.4-6.5 76-19.4 111.8l-10.7 29.7 229.6 229.5c44.5 44.6 44.5 117.1 0 161.6a113.6 113.6 0 01-80.8 33.5a113.6 113.6 0 01-80.8-33.5L529 694l-32 13a331.6 331.6 0 01-111.9 19.4A333.5 333.5 0 0150 383.1c0-39 6.8-77.2 20-113.6L285 482l194-195-214-210A331 331 0 01383.1 50A333.5 333.5 0 01716.3 383.1zM231.6 31.6l-22.9 9.9a22.2 22.2 0 00-5.9 4.2a19.5 19.5 0 000 27.5l215 215.2L288.4 417.8 77.8 207.1a26 26 0 00-17.2-7.1a22.8 22.8 0 00-21.5 15a400.5 400.5 0 00-7.5 16.6A381.6 381.6 0 000 384c0 211.7 172.2 384 384 384c44.3 0 87.6-7.5 129-22.3L743.1 975.8A163.5 163.5 0 00859.5 1024c43.9 0 85.3-17.1 116.4-48.2a164.8 164.8 0 000-233L745.5 513C760.5 471.5 768 428 768 384C768 172 596 0 384 0c-53 0-104 10.5-152.5 31.5z - M928 500a21 21 0 00-19-20L858 472a11 11 0 01-9-9c-1-6-2-13-3-19a11 11 0 015-12l46-25a21 21 0 0010-26l-8-22a21 21 0 00-24-13l-51 10a11 11 0 01-12-6c-3-6-6-11-10-17a11 11 0 011-13l34-39a21 21 0 001-28l-15-18a20 20 0 00-27-4l-45 27a11 11 0 01-13-1c-5-4-10-9-15-12a11 11 0 01-3-12l19-49a21 21 0 00-9-26l-20-12a21 21 0 00-27 6L650 193a9 9 0 01-11 3c-1-1-12-5-20-7a11 11 0 01-7-10l1-52a21 21 0 00-17-22l-23-4a21 21 0 00-24 14L532 164a11 11 0 01-11 7h-20a11 11 0 01-11-7l-17-49a21 21 0 00-24-15l-23 4a21 21 0 00-17 22l1 52a11 11 0 01-8 11c-5 2-15 6-19 7c-4 1-8 0-12-4l-33-40A21 21 0 00313 146l-20 12A21 21 0 00285 184l19 49a11 11 0 01-3 12c-5 4-10 8-15 12a11 11 0 01-13 1L228 231a21 21 0 00-27 4L186 253a21 21 0 001 28L221 320a11 11 0 011 13c-3 5-7 11-10 17a11 11 0 01-12 6l-51-10a21 21 0 00-24 13l-8 22a21 21 0 0010 26l46 25a11 11 0 015 12l0 3c-1 6-2 11-3 16a11 11 0 01-9 9l-51 8A21 21 0 0096 500v23A21 21 0 00114 544l51 8a11 11 0 019 9c1 6 2 13 3 19a11 11 0 01-5 12l-46 25a21 21 0 00-10 26l8 22a21 21 0 0024 13l51-10a11 11 0 0112 6c3 6 6 11 10 17a11 11 0 01-1 13l-34 39a21 21 0 00-1 28l15 18a20 20 0 0027 4l45-27a11 11 0 0113 1c5 4 10 9 15 12a11 11 0 013 12l-19 49a21 21 0 009 26l20 12a21 21 0 0027-6L374 832c3-3 7-5 10-4c7 3 12 5 20 7a11 11 0 018 10l-1 52a21 21 0 0017 22l23 4a21 21 0 0024-14l17-50a11 11 0 0111-7h20a11 11 0 0111 7l17 49a21 21 0 0020 15a19 19 0 004 0l23-4a21 21 0 0017-22l-1-52a11 11 0 018-10c8-3 13-5 18-7l1 0c6-2 9 0 11 3l34 41A21 21 0 00710 878l20-12a21 21 0 009-26l-18-49a11 11 0 013-12c5-4 10-8 15-12a11 11 0 0113-1l45 27a21 21 0 0027-4l15-18a21 21 0 00-1-28l-34-39a11 11 0 01-1-13c3-5 7-11 10-17a11 11 0 0112-6l51 10a21 21 0 0024-13l8-22a21 21 0 00-10-26l-46-25a11 11 0 01-5-12l0-3c1-6 2-11 3-16a11 11 0 019-9l51-8a21 21 0 0018-21v-23zm-565 188a32 32 0 01-51 5a270 270 0 011-363a32 32 0 0151 6l91 161a32 32 0 010 31zM512 782a270 270 0 01-57-6a32 32 0 01-20-47l92-160a32 32 0 0127-16h184a32 32 0 0130 41c-35 109-137 188-257 188zm15-328L436 294a32 32 0 0121-47a268 268 0 0155-6c120 0 222 79 257 188a32 32 0 01-30 41h-184a32 32 0 01-28-16z + M928 500a21 21 0 00-19-20L858 472a11 11 0 01-9-9c-1-6-2-13-3-19a11 11 0 015-12l46-25a21 21 0 0010-26l-8-22a21 21 0 00-24-13l-51 10a11 11 0 01-12-6c-3-6-6-11-10-17a11 11 0 011-13l34-39a21 21 0 001-28l-15-18a20 20 0 00-27-4l-45 27a11 11 0 01-13-1c-5-4-10-9-15-12a11 11 0 01-3-12l19-49a21 21 0 00-9-26l-20-12a21 21 0 00-27 6L650 193a9 9 0 01-11 3c-1-1-12-5-20-7a11 11 0 01-7-10l1-52a21 21 0 00-17-22l-23-4a21 21 0 00-24 14L532 164a11 11 0 01-11 7h-20a11 11 0 01-11-7l-17-49a21 21 0 00-24-15l-23 4a21 21 0 00-17 22l1 52a11 11 0 01-8 11c-5 2-15 6-19 7c-4 1-8 0-12-4l-33-40A21 21 0 00313 146l-20 12A21 21 0 00285 184l19 49a11 11 0 01-3 12c-5 4-10 8-15 12a11 11 0 01-13 1L228 231a21 21 0 00-27 4L186 253a21 21 0 001 28L221 320a11 11 0 011 13c-3 5-7 11-10 17a11 11 0 01-12 6l-51-10a21 21 0 00-24 13l-8 22a21 21 0 0010 26l46 25a11 11 0 015 12l0 3c-1 6-2 11-3 16a11 11 0 01-9 9l-51 8A21 21 0 0096 500v23A21 21 0 00114 544l51 8a11 11 0 019 9c1 6 2 13 3 19a11 11 0 01-5 12l-46 25a21 21 0 00-10 26l8 22a21 21 0 0024 13l51-10a11 11 0 0112 6c3 6 6 11 10 17a11 11 0 01-1 13l-34 39a21 21 0 00-1 28l15 18a20 20 0 0027 4l45-27a11 11 0 0113 1c5 4 10 9 15 12a11 11 0 013 12l-19 49a21 21 0 009 26l20 12a21 21 0 0027-6L374 832c3-3 7-5 10-4c7 3 12 5 20 7a11 11 0 018 10l-1 52a21 21 0 0017 22l23 4a21 21 0 0024-14l17-50a11 11 0 0111-7h20a11 11 0 0111 7l17 49a21 21 0 0020 15a19 19 0 004 0l23-4a21 21 0 0017-22l-1-52a11 11 0 018-10c8-3 13-5 18-7l1 0c6-2 9 0 11 3l34 41A21 21 0 00710 878l20-12a21 21 0 009-26l-18-49a11 11 0 013-12c5-4 10-8 15-12a11 11 0 0113-1l45 27a21 21 0 0027-4l15-18a21 21 0 00-1-28l-34-39a11 11 0 01-1-13c3-5 7-11 10-17a11 11 0 0112-6l51 10a21 21 0 0024-13l8-22a21 21 0 00-10-26l-46-25a11 11 0 01-5-12l0-3c1-6 2-11 3-16a11 11 0 019-9l51-8a21 21 0 0018-21v-23zm-565 188a32 32 0 01-51 5a270 270 0 011-363a32 32 0 0151 6l91 161a32 32 0 010 31zM512 782a270 270 0 01-57-6a32 32 0 01-20-47l92-160a32 32 0 0127-16h184a32 32 0 0130 41c-35 109-137 188-257 188zm15-328L436 294a32 32 0 0121-47a268 268 0 0155-6c120 0 222 79 257 188a32 32 0 01-30 41h-184a32 32 0 01-28-16z 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 M702 677 590 565a148 148 0 10-25 27L676 703zm-346-200a115 115 0 11115 115A115 115 0 01355 478z M512 57c251 0 455 204 455 455S763 967 512 967 57 763 57 512 261 57 512 57zm181 274c-11-11-29-11-40 0L512 472 371 331c-11-11-29-11-40 0-11 11-11 29 0 40L471 512 331 653c-11 11-11 29 0 40 11 11 29 11 40 0l141-141 141 141c11 11 29 11 40 0 11-11 11-29 0-40L552 512l141-141c11-11 11-29 0-40z - M899 870l-53-306H864c14 0 26-12 26-26V346c0-14-12-26-26-26H618V138c0-14-12-26-26-26H432c-14 0-26 12-26 26v182H160c-14 0-26 12-26 26v192c0 14 12 26 26 26h18l-53 306c0 2 0 3 0 4c0 14 12 26 26 26h723c2 0 3 0 4 0c14-2 24-16 21-30zM204 390h272V182h72v208h272v104H204V390zm468 440V674c0-4-4-8-8-8h-48c-4 0-8 4-8 8v156H416V674c0-4-4-8-8-8h-48c-4 0-8 4-8 8v156H203l45-260H776l45 260H672z M960 146v91C960 318 759 384 512 384S64 318 64 238V146C64 66 265 0 512 0s448 66 448 146zM960 352v206C960 638 759 704 512 704S64 638 64 558V352c96 66 272 97 448 97S864 418 960 352zm0 320v206C960 958 759 1024 512 1024S64 958 64 878V672c96 66 272 97 448 97S864 738 960 672z M800 928l-512 0 0-704 224 0 0 292 113-86 111 86 0-292 128 0 0 640c0 35-29 64-64 64zM625 388l-81 64 0-260 160 0 0 260-79-64zM192 160l0 32c0 18 14 32 32 32l32 0 0 704-32 0c-35 0-64-29-64-64l0-704c0-35 29-64 64-64l576 0c24 0 44 13 55 32l-631 0c-18 0-32 14-32 32z M64 864h896V288h-396a64 64 0 01-57-35L460 160H64v704zm-64 32V128a32 32 0 0132-32h448a32 32 0 0129 18L564 224H992a32 32 0 0132 32v640a32 32 0 01-32 32H32a32 32 0 01-32-32z diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index e2d13d9a..50155133 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1226,7 +1226,7 @@ namespace SourceGit.ViewModels var prune = new MenuItem(); prune.Header = App.Text("RemoteCM.Prune"); - prune.Icon = App.CreateMenuIcon("Icons.Clear2"); + prune.Icon = App.CreateMenuIcon("Icons.Clean"); prune.Click += (o, e) => { if (PopupHost.CanCreatePopup()) diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml index 255d57bd..84ecdd3a 100644 --- a/src/Views/Launcher.axaml +++ b/src/Views/Launcher.axaml @@ -50,7 +50,7 @@ - + diff --git a/src/Views/Preference.axaml b/src/Views/Preference.axaml index 037b6353..d502e818 100644 --- a/src/Views/Preference.axaml +++ b/src/Views/Preference.axaml @@ -27,7 +27,7 @@ diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index cf7ae7fd..3c0b28d0 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -98,7 +98,7 @@ diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index 3a98c852..89b4fd13 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -30,7 +30,7 @@ Padding="0" Command="{Binding Clear}" IsEnabled="{Binding Stashes.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"> - + From ad2fc68c6b648821a52f31d4810c4a43dc416c7a Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 17 Jun 2024 20:31:54 +0800 Subject: [PATCH 04/68] feature: supports visit remote url in browser --- src/Models/Remote.cs | 30 +++++++++++++++++++++++++++++- src/Resources/Locales/en_US.axaml | 6 ++++-- src/Resources/Locales/zh_CN.axaml | 6 ++++-- src/Resources/Locales/zh_TW.axaml | 6 ++++-- src/ViewModels/Repository.cs | 15 +++++++++++++++ src/Views/PruneRemote.axaml | 4 ++-- 6 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs index 75ca961f..950dde62 100644 --- a/src/Models/Remote.cs +++ b/src/Models/Remote.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System; +using System.Text.RegularExpressions; namespace SourceGit.Models { @@ -11,6 +12,9 @@ namespace SourceGit.Models [GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/]+/[\w\-\.]+(\.git)?$")] private static partial Regex REG_SSH2(); + [GeneratedRegex(@"^git@([\w\.\-]+):([\w\-/]+/[\w\-\.]+)\.git$")] + private static partial Regex REG_TO_VISIT_URL_CAPTURE(); + private static readonly Regex[] URL_FORMATS = [ REG_HTTPS(), REG_SSH1(), @@ -43,5 +47,29 @@ namespace SourceGit.Models } return false; } + + public bool TryGetVisitURL(out string url) + { + url = null; + + if (URL.StartsWith("http", StringComparison.Ordinal)) + { + if (URL.EndsWith(".git")) + url = URL.Substring(0, URL.Length - 4); + else + url = URL; + + return true; + } + + var match = REG_TO_VISIT_URL_CAPTURE().Match(URL); + if (match.Success) + { + url = $"https://{match.Groups[1].Value}/{match.Groups[2].Value}"; + return true; + } + + return false; + } } } diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 91d3ed83..ae541e00 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -345,6 +345,8 @@ Install Path Input path for merge tool Merger + Prune Remote + Target : Pull Branch : Into : @@ -383,8 +385,8 @@ Delete ... Edit ... Fetch ... - Prune - Target : + Open In Browser + Prune ... Rename Branch New Name : Unique name for this branch diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 5ecf6a63..fc773174 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -348,6 +348,8 @@ 安装路径 填写工具可执行文件所在位置 工具 + 清理远程已删除分支 + 目标 : 拉回(pull) 拉取分支 : 本地分支 : @@ -386,8 +388,8 @@ 删除 ... 编辑 ... 拉取(fetch)更新 ... - 清理远程已删除分支 - 目标 : + 在浏览器中打开 + 清理远程已删除分支 ... 分支重命名 新的名称 : 新的分支名不能与现有分支名相同 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 9c97df8b..ef50a35e 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -348,6 +348,8 @@ 安裝路徑 填寫工具可執行檔案所在位置 工具 + 清理遠端已刪除分支 + 目標 : 拉回(pull) 拉取分支 : 本地分支 : @@ -386,8 +388,8 @@ 刪除 ... 編輯 ... 拉取(fetch)更新 ... - 清理遠端已刪除分支 - 目標 : + 在瀏覽器中訪問網址 + 清理遠端已刪除分支 ... 分支重新命名 新的名稱 : 新的分支名不能與現有分支名相同 diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 50155133..3f035150 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1214,6 +1214,21 @@ namespace SourceGit.ViewModels { var menu = new ContextMenu(); + if (remote.TryGetVisitURL(out string visitURL)) + { + var visit = new MenuItem(); + visit.Header = App.Text("RemoteCM.OpenInBrowser"); + visit.Icon = App.CreateMenuIcon("Icons.OpenWith"); + visit.Click += (o, e) => + { + Native.OS.OpenBrowser(visitURL); + e.Handled = true; + }; + + menu.Items.Add(visit); + menu.Items.Add(new MenuItem() { Header = "-" }); + } + var fetch = new MenuItem(); fetch.Header = App.Text("RemoteCM.Fetch"); fetch.Icon = App.CreateMenuIcon("Icons.Fetch"); diff --git a/src/Views/PruneRemote.axaml b/src/Views/PruneRemote.axaml index 2b3dbcf0..72b09d26 100644 --- a/src/Views/PruneRemote.axaml +++ b/src/Views/PruneRemote.axaml @@ -9,9 +9,9 @@ + Text="{DynamicResource Text.PruneRemote}"/> - + From 47d690f0d7c99b74c06593b3898c54a9318cf932 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 17 Jun 2024 21:09:22 +0800 Subject: [PATCH 05/68] ux: new style for Statistics window --- src/Views/Statistics.axaml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Views/Statistics.axaml b/src/Views/Statistics.axaml index efeed607..8f3bd7e8 100644 --- a/src/Views/Statistics.axaml +++ b/src/Views/Statistics.axaml @@ -77,7 +77,7 @@ @@ -127,10 +127,30 @@ BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Background="{DynamicResource Brush.Contents}" + HorizontalGridLinesBrush="{DynamicResource Brush.Border1}" + VerticalGridLinesBrush="{DynamicResource Brush.Border1}" IsReadOnly="True" RowHeight="26" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> + + + + + + + From 37066e940ad729a898bd1389aeb88df68eba2ae8 Mon Sep 17 00:00:00 2001 From: Ere Maijala Date: Mon, 17 Jun 2024 21:46:41 +0300 Subject: [PATCH 06/68] Tweak English translations. - Menu entries that require additional input end with ellipsis. - No blank space before ellipsis or colon. - A few minor tweaks e.g. to unify the style of adjacent choices. --- src/Resources/Locales/en_US.axaml | 213 +++++++++++++++--------------- 1 file changed, 107 insertions(+), 106 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index ae541e00..c67d59f9 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -12,7 +12,7 @@ Raise errors and refuses to apply the patch Error All Similar to 'error', but shows more - Patch File : + Patch File: Select .patch file to apply Ignore whitespace changes No Warn @@ -20,11 +20,11 @@ Apply Patch Warn Outputs warnings for a few such errors, but applies - Whitespace : - Archive ... - Save Archive To : + Whitespace: + Archive... + Save Archive To: Select archive file path - Revision : + Revision: Archive FILES ASSUME UNCHANGED NO FILES ASSUMED AS UNCHANGED @@ -32,23 +32,23 @@ BINARY FILE NOT SUPPORTED!!! Blame BLAME ON THIS FILE IS NOT SUPPORTED!!! - Checkout${0}$ + Checkout${0}... Compare with Branch Compare with HEAD Compare with Worktree Copy Branch Name - Delete${0}$ + Delete${0}... Delete selected {0} branches Discard all changes Fast-Forward to${0}$ Git Flow - Finish${0}$ - Merge${0}$into${1}$ + Merge${0}$into${1}... Pull${0}$ - Pull${0}$into${1}$ + Pull${0}$into${1}... Push${0}$ - Rebase${0}$on${1}$ - Rename${0}$ - Tracking ... + Rebase${0}$on${1}... + Rename${0}... + Set Tracking Branch Unset Upstream Branch Compare Bytes @@ -60,25 +60,25 @@ Checkout Branch Checkout Commit Warning: By doing a commit checkout, your Head will be detached - Commit : - Branch : - Local Changes : + Commit: + Branch: + Local Changes: Discard Do Nothing Stash & Reapply Cherry-Pick This Commit - Commit : + Commit: Commit all changes Cherry Pick Clear Stashes You are trying to clear all stashes. Are you sure to continue? Clone Remote Repository - Extra Parameters : + Extra Parameters: Additional arguments to clone repository. Optional. - Local Name : + Local Name: Repository name. Optional. - Parent Folder : - Repository URL : + Parent Folder: + Repository URL: CLOSE Cherry-Pick This Commit Checkout Commit @@ -89,10 +89,10 @@ Reset${0}$to Here Revert Commit Reword - Save as Patch ... + Save as Patch... Squash Into Parent CHANGES - Search Changes ... + Search Changes... FILES LFS File Submodule @@ -114,43 +114,43 @@ Copy Copy Path Copy File Name - Create Branch - Based On : - Check out after created - Local Changes : + Create Branch... + Based On: + Check out the created branch + Local Changes: Discard Do Nothing Stash & Reapply - New Branch Name : + New Branch Name: Enter branch name. Create Local Branch - Create Tag - New Tag At : + Create Tag... + New Tag At: GPG signing - Tag Message : + Tag Message: Optional. - Tag Name : - Recommended format :v1.0.0-alpha + Tag Name: + Recommended format: v1.0.0-alpha Push to all remotes after created - Kind : + Kind: annotated lightweight Cut Delete Branch - Branch : + Branch: You are about to delete a remote branch!!! Also delete remote branch${0}$ Delete Multiple Branches You are trying to delete multiple branches at one time. Be sure to double-check before taking action! Delete Remote - Remote : - Target : + Remote: + Target: Confirm Deleting Group Confirm Deleting Repository Delete Submodule - Submodule Path : + Submodule Path: Delete Tag - Tag : + Tag: Delete from remote repositories BINARY DIFF NEW @@ -173,19 +173,19 @@ Open In Merge Tool Discard Changes All local changes in working copy. - Changes : + Changes: Total {0} changes will be discard You can't undo this action!!! - Bookmark : - New Name : - Target : + Bookmark: + New Name: + Target: Edit Selected Group Edit Selected Repository Fast-Forward (without checkout) Fetch Fetch all remotes Prune remote dead branches - Remote : + Remote: Fetch Remote Changes Assume unchanged Discard... @@ -193,8 +193,8 @@ Discard Changes in Selected Line(s) Open External Merge Tool Save As Patch... - Stage... - Stage {0} files... + Stage + Stage {0} files Stage Changes in Selected Line(s) Stash... Stash {0} files... @@ -206,28 +206,28 @@ File History FILTER Git-Flow - Development Branch : - Feature : - Feature Prefix : + Development Branch: + Feature: + Feature Prefix: FLOW - Finish Feature FLOW - Finish Hotfix FLOW - Finish Release - Target : - Hotfix : - Hotfix Prefix : + Target: + Hotfix: + Hotfix Prefix: Initialize Git-Flow Keep branch - Production Branch : - Release : - Release Prefix : - Start Feature ... + Production Branch: + Release: + Release Prefix: + Start Feature... FLOW - Start Feature - Start Hotfix ... + Start Hotfix... FLOW - Start Hotfix Enter name - Start Release ... + Start Release... FLOW - Start Release - Version Tag Prefix : + Version Tag Prefix: Git LFS Fetch ... Fetch LFS Objects @@ -275,7 +275,7 @@ Find previous match Open search panel Initialize Repository - Path : + Path: Invalid repository detected. Run `git init` under this path? Cherry-Pick in progress. Press 'Abort' to restore original HEAD. Merge request in progress. Press 'Abort' to restore original HEAD. @@ -286,14 +286,14 @@ NOTICE Open Main Menu Merge Branch - Into : - Merge Option : - Source Branch : - Name : + Into: + Merge Option: + Source Branch: + Name: Git has NOT been configured. Please to go [Preference] and configure it first. NOTICE SELECT FOLDER - Open With ... + Open With... Optional. Create New Page Bookmark @@ -346,51 +346,52 @@ Input path for merge tool Merger Prune Remote - Target : + Target: Pull - Branch : - Into : - Local Changes : + Branch: + Into: + Local Changes: Discard Do Nothing Stash & Reapply - Remote : + Remote: Pull (Fetch & Merge) Use rebase instead of merge Push Force push - Local Branch : - Remote : + Local Branch: + Remote: Push Changes To Remote - Remote Branch : - Tracking remote branch + Remote Branch: + Set as tracking branch Push all tags Push Tag To Remote Push to all remotes - Remote : - Tag : + Remote: + Tag: Quit Rebase Current Branch Stash & reapply local changes - On : - Rebase : + On: + Rebase: Refresh Add Remote Edit Remote - Name : + Name: Remote name - Repository URL : + Repository URL: Remote git repository URL Copy URL - Delete ... - Edit ... - Fetch ... + Delete... + Edit... + Fetch Open In Browser - Prune ... + Prune + Target: Rename Branch - New Name : + New Name: Unique name for this branch - Branch : + Branch: ABORT Cleanup(GC & Prune) Run `git gc` command for this repository. @@ -400,7 +401,7 @@ Filter Branches LOCAL BRANCHES Navigate To HEAD - Create Branch + Create Branch... Open In {0} Open In External Tools Refresh @@ -421,21 +422,21 @@ WORKSPACE Git Repository URL Reset Current Branch To Revision - Reset Mode : - Move To : - Current Branch : + Reset Mode: + Move To: + Current Branch: Reveal in File Explorer Revert Commit - Commit : + Commit: Commit revert changes Reword Commit Message - Message : - On : - Running. Please wait ... + Message: + On: + Running. Please wait... SAVE - Save As ... + Save As... Patch has been saved successfully! - Check for Updates ... + Check for Updates... New version of this software is available: Check for updates failed! Download @@ -443,22 +444,22 @@ Software Update There are currently no updates available. Squash HEAD Into Parent - HEAD : - Reword : - To : - SSH Private Key : + HEAD: + Reword: + To: + SSH Private Key: Private SSH key store path START Stash Include untracked files - Message : + Message: Optional. Name of this stash Stash Local Changes Apply Drop Pop Drop Stash - Drop : + Drop: Stashes CHANGES STASHES @@ -475,14 +476,14 @@ Copy Relative Path Fetch nested submodules Open Submodule Repository - Relative Path : + Relative Path: Relative folder to store this module. Delete Submodule OK Copy Tag Name - Delete${0}$ - Push${0}$ - URL : + Delete${0}... + Push${0}... + URL: Update Submodules Run `submodule update` command for this repository. Warning @@ -494,7 +495,7 @@ Edit Open Repository Open Terminal - Search Repositories ... + Search Repositories... Sort Changes Git Ignore From f79dc1f91f6ee2171ecc5eab9160fb33cd582075 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 10:13:39 +0800 Subject: [PATCH 07/68] code_review: PR #182 * avoid highlight trailing ellipsis * use WidthIncludingTrailingWhitespace instead of Width to keep original white space in translation * add same modification on zh_CN and zh_TW --- src/Resources/Locales/en_US.axaml | 32 +++++++++++----------- src/Resources/Locales/zh_CN.axaml | 38 +++++++++++++-------------- src/Resources/Locales/zh_TW.axaml | 38 +++++++++++++-------------- src/Views/NameHighlightedTextBlock.cs | 7 ++--- 4 files changed, 56 insertions(+), 59 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index c67d59f9..5d110718 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -32,22 +32,22 @@ BINARY FILE NOT SUPPORTED!!! Blame BLAME ON THIS FILE IS NOT SUPPORTED!!! - Checkout${0}... + Checkout ${0}$... Compare with Branch Compare with HEAD Compare with Worktree Copy Branch Name - Delete${0}... + Delete ${0}$... Delete selected {0} branches Discard all changes - Fast-Forward to${0}$ - Git Flow - Finish${0}$ - Merge${0}$into${1}... - Pull${0}$ - Pull${0}$into${1}... - Push${0}$ - Rebase${0}$on${1}... - Rename${0}... + Fast-Forward to ${0}$ + Git Flow - Finish ${0}$ + Merge ${0}$ into ${1}$... + Pull ${0}$ + Pull ${0}$ into ${1}$... + Push ${0}$ + Rebase ${0}$ on ${1}$... + Rename ${0}$... Set Tracking Branch Unset Upstream Branch Compare @@ -85,8 +85,8 @@ Compare with HEAD Compare with Worktree Copy SHA - Rebase${0}$to Here - Reset${0}$to Here + Rebase ${0}$ to Here + Reset ${0}$ to Here Revert Commit Reword Save as Patch... @@ -139,7 +139,7 @@ Delete Branch Branch: You are about to delete a remote branch!!! - Also delete remote branch${0}$ + Also delete remote branch ${0}$ Delete Multiple Branches You are trying to delete multiple branches at one time. Be sure to double-check before taking action! Delete Remote @@ -401,7 +401,7 @@ Filter Branches LOCAL BRANCHES Navigate To HEAD - Create Branch... + Create Branch Open In {0} Open In External Tools Refresh @@ -481,8 +481,8 @@ Delete Submodule OK Copy Tag Name - Delete${0}... - Push${0}... + Delete ${0}$... + Push ${0}$... URL: Update Submodules Run `submodule update` command for this repository. diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index fc773174..9d5e7613 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -35,23 +35,23 @@ 二进制文件不支持该操作!!! 逐行追溯(blame) 选中文件不支持该操作!!! - 检出(checkout)${0}$ + 检出(checkout) ${0}$... 与其他分支对比 与当前HEAD比较 与本地工作树比较 复制分支名 - 删除${0}$ + 删除 ${0}$... 删除选中的 {0} 个分支 放弃所有更改 - 快进(fast-forward)到${0}$ - GIT工作流 - 完成${0}$ - 合并${0}$到${1}$ - 拉回(pull)${0}$ - 拉回(pull)${0}$内容至${1}$ + 快进(fast-forward)到 ${0}$ + GIT工作流 - 完成 ${0}$ + 合并 ${0}$ 到 ${1}$... + 拉回(pull) ${0}$ + 拉回(pull) ${0}$ 内容至 ${1}$... 推送(push)${0}$ - 变基(rebase)${0}$分支至${1}$ - 重命名${0}$ - 切换上游分支... + 变基(rebase) ${0}$ 分支至 ${1}$... + 重命名 ${0}$... + 切换上游分支 取消追踪 分支比较 字节 @@ -88,8 +88,8 @@ 与当前HEAD比较 与本地工作树比较 复制提交指纹 - 变基(rebase)${0}$到此处 - 重置(reset)${0}$到此处 + 变基(rebase) ${0}$ 到此处 + 重置(reset) ${0}$ 到此处 回滚此提交 编辑提交信息 另存为补丁 ... @@ -142,7 +142,7 @@ 删除分支确认 分支名 : 您正在删除远程上的分支,请务必小心!!! - 同时删除远程分支${0}$ + 同时删除远程分支 ${0}$ 删除多个分支 您正在尝试一次性删除多个分支,请务必仔细检查后再执行操作! 删除远程确认 @@ -196,8 +196,8 @@ 放弃选中的更改 使用外部合并工具打开 另存为补丁... - 暂存(add)... - 暂存(add){0} 个文件... + 暂存(add) + 暂存(add){0} 个文件 暂存选中的更改 贮藏(stash)... 贮藏(stash)选中的 {0} 个文件... @@ -387,9 +387,9 @@ 复制远程地址 删除 ... 编辑 ... - 拉取(fetch)更新 ... + 拉取(fetch)更新 在浏览器中打开 - 清理远程已删除分支 ... + 清理远程已删除分支 分支重命名 新的名称 : 新的分支名不能与现有分支名相同 @@ -483,8 +483,8 @@ 删除子模块 确 定 复制标签名 - 删除${0}$ - 推送${0}$ + 删除 ${0}$... + 推送 ${0}$... 仓库地址 : 更新子模块 为此仓库执行`submodule update`命令,更新所有的子模块。 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index ef50a35e..0611cbf9 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -35,23 +35,23 @@ 二進位制檔案不支援該操作!!! 逐行追溯(blame) 選中檔案不支援該操作!!! - 檢出(checkout)${0}$ + 檢出(checkout) ${0}$... 與其他分支比較 與當前HEAD比較 與本地工作樹比較 複製分支名 - 刪除${0}$ + 刪除 ${0}$... 刪除選中的 {0} 個分支 放棄所有更改 - 快進(fast-forward)到${0}$ - GIT工作流 - 完成${0}$ - 合併${0}$到${1}$ - 拉回(pull)${0}$ - 拉回(pull)${0}$內容至${1}$ + 快進(fast-forward)到 ${0}$ + GIT工作流 - 完成 ${0}$ + 合併 ${0}$ 到 ${1}$... + 拉回(pull) ${0}$ + 拉回(pull) ${0}$ 內容至 ${1}$... 推送(push)${0}$ - 變基(rebase)${0}$分支至${1}$ - 重新命名${0}$ - 切換上游分支... + 變基(rebase) ${0}$ 分支至 ${1}$... + 重新命名 ${0}$... + 切換上游分支 取消追蹤 分支比較 位元組 @@ -88,8 +88,8 @@ 與當前HEAD比較 與本地工作樹比較 複製提交指紋 - 變基(rebase)${0}$到此處 - 重置(reset)${0}$到此處 + 變基(rebase) ${0}$ 到此處 + 重置(reset) ${0}$ 到此處 回滾此提交 編輯提交資訊 另存為補丁 ... @@ -142,7 +142,7 @@ 刪除分支確認 分支名 : 您正在刪除遠端上的分支,請務必小心!!! - 同時刪除遠端分支${0}$ + 同時刪除遠端分支 ${0}$ 刪除多個分支 您正在嘗試一次性刪除多個分支,請務必仔細檢查後再執行操作! 刪除遠端確認 @@ -196,8 +196,8 @@ 放棄選中的更改 使用外部合併工具開啟 另存為補丁... - 暫存(add)... - 暫存(add){0} 個檔案... + 暫存(add) + 暫存(add){0} 個檔案 暫存選中的更改 儲藏(stash)... 儲藏(stash)選中的 {0} 個檔案... @@ -387,9 +387,9 @@ 複製遠端地址 刪除 ... 編輯 ... - 拉取(fetch)更新 ... + 拉取(fetch)更新 在瀏覽器中訪問網址 - 清理遠端已刪除分支 ... + 清理遠端已刪除分支 分支重新命名 新的名稱 : 新的分支名不能與現有分支名相同 @@ -483,8 +483,8 @@ 刪除子模組 確 定 複製標籤名 - 刪除${0}$ - 推送${0}$ + 刪除 ${0}$... + 推送 ${0}$... 倉庫地址 : 更新子模組 本操作將執行 `submodule update` 。 diff --git a/src/Views/NameHighlightedTextBlock.cs b/src/Views/NameHighlightedTextBlock.cs index 3be81186..c0583223 100644 --- a/src/Views/NameHighlightedTextBlock.cs +++ b/src/Views/NameHighlightedTextBlock.cs @@ -81,7 +81,6 @@ namespace SourceGit.Views return; var normalTypeface = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Normal, FontStretch.Normal); - //var highlightTypeface = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Bold, FontStretch.Normal); var underlinePen = new Pen(Foreground, 1); var offsetX = 0.0; @@ -99,7 +98,6 @@ namespace SourceGit.Views part, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, - //isName ? highlightTypeface : normalTypeface, normalTypeface, FontSize, Foreground); @@ -107,15 +105,14 @@ namespace SourceGit.Views if (isName) { var lineY = formatted.Baseline + 2; - offsetX += 4; context.DrawText(formatted, new Point(offsetX, 0)); context.DrawLine(underlinePen, new Point(offsetX, lineY), new Point(offsetX + formatted.Width, lineY)); - offsetX += formatted.Width + 4; + offsetX += formatted.WidthIncludingTrailingWhitespace; } else { context.DrawText(formatted, new Point(offsetX, 0)); - offsetX += formatted.Width; + offsetX += formatted.WidthIncludingTrailingWhitespace; } isName = !isName; From 2f6519fa4d6119970d7c38ade7de0756c589ff8b Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 10:19:55 +0800 Subject: [PATCH 08/68] enhance: avoid crash when missing parameters to format string --- src/App.axaml.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 69917120..f1676fa3 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -191,6 +191,10 @@ namespace SourceGit var fmt = Current.FindResource($"Text.{key}") as string; if (string.IsNullOrWhiteSpace(fmt)) return $"Text.{key}"; + + if (args == null || args.Length == 0) + return fmt; + return string.Format(fmt, args); } From 4217d62f474bf10b54877055005bd8cbd301eba8 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 10:29:27 +0800 Subject: [PATCH 09/68] localization: remove ellipsis for git-lfs options --- src/Resources/Locales/en_US.axaml | 4 ++-- src/Resources/Locales/zh_CN.axaml | 4 ++-- src/Resources/Locales/zh_TW.axaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 5d110718..26e3b849 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -229,7 +229,7 @@ FLOW - Start Release Version Tag Prefix: Git LFS - Fetch ... + Fetch Fetch LFS Objects Run `git lfs fetch` to download Git LFS objects. This does not update the working copy. Install Git LFS hooks @@ -241,7 +241,7 @@ Force Unlock Prune Run `git lfs prune` to delete old LFS files from local storage - Pull ... + Pull Pull LFS Objects Run `git lfs pull` to download all Git LFS files for current ref & checkout Track files named '{0}' diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 9d5e7613..4b8932ee 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -232,7 +232,7 @@ 开始版本分支 版本标签前缀 : Git LFS - 拉取LFS对象 (fetch) ... + 拉取LFS对象 (fetch) 拉取LFS对象 执行`git lfs prune`命令,下载远程LFS对象,但不会更新工作副本。 启用Git LFS支持 @@ -244,7 +244,7 @@ 强制解锁 精简本地LFS对象存储 运行`git lfs prune`命令,从本地存储中精简当前版本不需要的LFS对象 - 拉回LFS对象 (pull) ... + 拉回LFS对象 (pull) 拉回LFS对象 运行`git lfs pull`命令,下载远程LFS对象并更新工作副本。 跟踪名为'{0}'的文件 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 0611cbf9..1c4ec518 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -232,7 +232,7 @@ 開始版本分支 版本標籤字首 : Git LFS - 拉取LFS物件 (fetch) ... + 拉取LFS物件 (fetch) 拉取LFS物件 執行`git lfs fetch`命令,下載遠端LFS物件,但不會更新工作副本。 啟用Git LFS支援 @@ -244,7 +244,7 @@ 強制解鎖 精簡本地LFS物件存儲 執行`git lfs prune`命令,從本地存儲中精簡當前版本不需要的LFS物件 - 拉回LFS物件 (pull) ... + 拉回LFS物件 (pull) 拉回LFS物件 執行`git lfs pull`命令,下載遠端LFS物件并更新工作副本。 跟蹤名為'{0}'的檔案 From 06245320a9c7062f65572be6cfe3c1e143ebc77f Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 11:07:48 +0800 Subject: [PATCH 10/68] feature: add a context menu item to open all repositories in a group (#179) --- src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/ViewModels/Welcome.cs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 26e3b849..28afdf9c 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -493,6 +493,7 @@ Delete DRAG & DROP FOLDER SUPPORTED. CUSTOM GROUPING SUPPORTED. Edit + Open All Repositories Open Repository Open Terminal Search Repositories... diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 4b8932ee..b1658c34 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -495,6 +495,7 @@ 删除 支持拖放目录添加。支持自定义分组。 编辑 + 打开所有包含仓库 打开本地仓库 打开终端 快速查找仓库... diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 1c4ec518..7a8bd837 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -495,6 +495,7 @@ 刪除 支援拖放目錄新增。支援自定義分組。 編輯 + 打開所有包含倉庫 開啟本地倉庫 開啟終端 快速查詢倉庫... diff --git a/src/ViewModels/Welcome.cs b/src/ViewModels/Welcome.cs index d077bfb7..415c56ee 100644 --- a/src/ViewModels/Welcome.cs +++ b/src/ViewModels/Welcome.cs @@ -92,6 +92,26 @@ namespace SourceGit.ViewModels var menu = new ContextMenu(); var hasRepo = Preference.FindRepository(node.Id) != null; + if (!node.IsRepository && node.SubNodes.Count > 0) + { + var openAll = new MenuItem(); + openAll.Header = App.Text("Welcome.OpenAllInNode"); + openAll.Icon = App.CreateMenuIcon("Icons.Folder.Open"); + openAll.Click += (_, e) => + { + if (App.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var launcher = desktop.MainWindow.DataContext as Launcher; + OpenAllInNode(launcher, node); + } + + e.Handled = true; + }; + + menu.Items.Add(openAll); + menu.Items.Add(new MenuItem() { Header = "-" }); + } + var edit = new MenuItem(); edit.Header = App.Text("Welcome.Edit"); edit.Icon = App.CreateMenuIcon("Icons.Edit"); @@ -201,6 +221,17 @@ namespace SourceGit.ViewModels } } + private void OpenAllInNode(Launcher launcher, RepositoryNode node) + { + foreach (var subNode in node.SubNodes) + { + if (subNode.IsRepository) + launcher.OpenRepositoryInTab(subNode, null); + else if (subNode.SubNodes.Count > 0) + OpenAllInNode(launcher, subNode); + } + } + private static Welcome _instance = new Welcome(); private string _searchFilter = string.Empty; } From c56d0cf85ead68d68331e6d63bb7c1246346a8f4 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 12:10:38 +0800 Subject: [PATCH 11/68] refactor: external diff merge tool - supports to use difftool/mergetool settings from git config directly (#181) --- src/Commands/MergeTool.cs | 90 ++++++++++-------- src/Models/ExternalMerger.cs | 8 +- .../ExternalToolIcons/custom_diff.png | Bin 1943 -> 0 bytes src/Resources/ExternalToolIcons/git.png | Bin 0 -> 1223 bytes src/Resources/Locales/en_US.axaml | 10 +- src/Resources/Locales/zh_CN.axaml | 10 +- src/Resources/Locales/zh_TW.axaml | 10 +- src/ViewModels/BranchCompare.cs | 14 +-- src/ViewModels/CommitDetail.cs | 14 +-- src/ViewModels/DiffContext.cs | 15 +-- src/ViewModels/Preference.cs | 14 --- src/ViewModels/RevisionCompare.cs | 14 +-- src/ViewModels/WorkingCopy.cs | 15 +-- src/Views/Preference.axaml | 37 ++----- src/Views/Preference.axaml.cs | 1 + 15 files changed, 89 insertions(+), 163 deletions(-) delete mode 100644 src/Resources/ExternalToolIcons/custom_diff.png create mode 100644 src/Resources/ExternalToolIcons/git.png diff --git a/src/Commands/MergeTool.cs b/src/Commands/MergeTool.cs index 75b88bc9..d3478d59 100644 --- a/src/Commands/MergeTool.cs +++ b/src/Commands/MergeTool.cs @@ -6,57 +6,63 @@ namespace SourceGit.Commands { public static class MergeTool { - public static bool OpenForMerge(string repo, string tool, string mergeCmd, string file) + public static bool OpenForMerge(string repo, int toolType, string toolPath, string file) { - if (string.IsNullOrWhiteSpace(tool) || string.IsNullOrWhiteSpace(mergeCmd)) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, "Invalid external merge tool settings!"); - }); - return false; - } - - if (!File.Exists(tool)) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, $"Can NOT found external merge tool in '{tool}'!"); - }); - return false; - } - var cmd = new Command(); cmd.WorkingDirectory = repo; - cmd.RaiseError = false; - cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{tool}\\\" {mergeCmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit \"{file}\""; + cmd.Context = repo; + cmd.RaiseError = true; + + if (toolType == 0) + { + cmd.Args = $"mergetool \"{file}\""; + return cmd.Exec(); + } + + if (!File.Exists(toolPath)) + { + Dispatcher.UIThread.Post(() => App.RaiseException(repo, $"Can NOT found external merge tool in '{toolPath}'!")); + return false; + } + + var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType); + if (supported == null) + { + Dispatcher.UIThread.Post(() => App.RaiseException(repo, "Invalid merge tool in preference setting!")); + return false; + } + + cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit \"{file}\""; return cmd.Exec(); } - public static bool OpenForDiff(string repo, string tool, string diffCmd, Models.DiffOption option) + public static bool OpenForDiff(string repo, int toolType, string toolPath, Models.DiffOption option) { - if (string.IsNullOrWhiteSpace(tool) || string.IsNullOrWhiteSpace(diffCmd)) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, "Invalid external merge tool settings!"); - }); - return false; - } - - if (!File.Exists(tool)) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(repo, $"Can NOT found external merge tool in '{tool}'!"); - }); - return false; - } - var cmd = new Command(); cmd.WorkingDirectory = repo; - cmd.RaiseError = false; - cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{tool}\\\" {diffCmd}\" difftool --tool=sourcegit --no-prompt {option}"; + cmd.Context = repo; + cmd.RaiseError = true; + + if (toolType == 0) + { + cmd.Args = $"difftool -g --no-prompt {option}"; + return cmd.Exec(); + } + + if (!File.Exists(toolPath)) + { + Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT found external diff tool in '{toolPath}'!")); + return false; + } + + var supported = Models.ExternalMerger.Supported.Find(x => x.Type == toolType); + if (supported == null) + { + Dispatcher.UIThread.Post(() => App.RaiseException(repo, "Invalid merge tool in preference setting!")); + return false; + } + + cmd.Args = $"-c difftool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.DiffCmd}\" difftool --tool=sourcegit --no-prompt {option}"; return cmd.Exec(); } } diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs index d61ce055..3130408e 100644 --- a/src/Models/ExternalMerger.cs +++ b/src/Models/ExternalMerger.cs @@ -32,7 +32,7 @@ namespace SourceGit.Models if (OperatingSystem.IsWindows()) { Supported = new List() { - new ExternalMerger(0, "custom_diff", "Custom", "", "", ""), + new ExternalMerger(0, "git", "Use Git Settings", "", "", ""), new ExternalMerger(1, "vscode", "Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(2, "vscode_insiders", "Visual Studio Code - Insiders", "Code - Insiders.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(3, "vs", "Visual Studio", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" /m", "\"$LOCAL\" \"$REMOTE\""), @@ -46,7 +46,7 @@ namespace SourceGit.Models else if (OperatingSystem.IsMacOS()) { Supported = new List() { - new ExternalMerger(0, "custom_diff", "Custom", "", "", ""), + new ExternalMerger(0, "git", "Use Git Settings", "", "", ""), new ExternalMerger(1, "xcode", "FileMerge", "/usr/bin/opendiff", "\"$BASE\" \"$LOCAL\" \"$REMOTE\" -ancestor \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(2, "vscode", "Visual Studio Code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(3, "vscode_insiders", "Visual Studio Code - Insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), @@ -58,7 +58,7 @@ namespace SourceGit.Models else if (OperatingSystem.IsLinux()) { Supported = new List() { - new ExternalMerger(0, "custom_diff", "Custom", "", "", ""), + new ExternalMerger(0, "git", "Use Git Settings", "", "", ""), new ExternalMerger(1, "vscode", "Visual Studio Code", "/usr/share/code/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(2, "vscode_insiders", "Visual Studio Code - Insiders", "/usr/share/code-insiders/code-insiders", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(3, "kdiff3", "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), @@ -70,7 +70,7 @@ namespace SourceGit.Models else { Supported = new List() { - new ExternalMerger(0, "custom_diff", "Custom", "", "", ""), + new ExternalMerger(0, "git", "Use Git Settings", "", "", ""), }; } } diff --git a/src/Resources/ExternalToolIcons/custom_diff.png b/src/Resources/ExternalToolIcons/custom_diff.png deleted file mode 100644 index 8d046a2f12ebaabe7e7cac974d4acccc109c7699..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1943 zcmV;I2Wa?-P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf6951U69E94oEQKA2P{cMK~#8N?OSbZ z6h#<*XLqj$#+Fh+`AA8HLM3OUVrmRuy(20}0-=G33c(;?kXV6W#2+ZBf7Bm<3L&UJ z3I;JqwSo$$fr4OyF&KUbk@68ftdO3z2bS9F-Ol*V&R(xyZv9R9~3_Erj&>zL8l3d4ZHUG^?zm@chHf2f^V*q_wGq4aDIfeNmhsaWa}PILauya^XX5 zguOc@qy;Ej#;j{~W-At&2N=7A2Jlp^A>j8z@Z=^fERLiHRi4sn>r{kS=o$hZ=HyW2 zDJ=kwF)O@;MV9Am6KyY`$}@Wv(3wF|J>=PiiWeB;VK@6MRC#8t0x|_vp7H{qripvD z7noj6{h|4^ta1S@t*m6da(_KRm8bk9h^q4N3egrQ!A-r!pcc(_q1c-~RC!9@)2A*F z(XOe8Bp{YV!@>I1E2fD4 zPM&U%7U0R}MB?vvio`$fp#l7$EI`F9(SI5WfWO{=9zIO^Ck6OruetyRI?3@fM1Lnw zH%JTc)UzV-{?FtRyMYUUW7Tqk)IMiX z0b2O}V|%GpA+jJEoFfJ-nlN4Tck*rjW6_ zz|xsQ)sMdxrNx~f_Et7Rt3smxl9|a#uwNX4n+}Mfv9Up|%D55IjieKF@^nM5D_~+9 z#y{$jfb*ef0zLI{i^<&{sAQ}HUY;S8-Mr76$z}O8Tnp&76=ul_NEfO+v*!dcbQ8nF zoE#aefYsB4vg^MS%34ma8A|Z#DLKLSY$v#qSAabS_|XWxZCAibMJGtm2^Ms7f=-@p zkQQLg6C&}uKZwK$Cx{ItCs=Je!Pig#Fi`-4Rv4GF2>~`KIzc3LixYJ6bc6H|a86j| zZD5w)gufqZgRn~2-2oC0MIGhZQ=e2(-VQAjqO2=RPZMh@NLRp{Q-!i`9TLi>>jc+^ z@HS{a4~YAd6_BnIbn^5JH6g}$*v&o*Rh~H~Xm4Hd&J>~Q>pyx^TJKM%{*D_^V*0?0 z0?y(`#4Mh!6IA7ywF=-TuW3+km{6M!$*00SIX$~t zKp(K;n7D%JLX~G$=K$-kI7$WYj*L~nmdAv$%a40g0h=q(2=Vs=&=-W+FZk6P0X}({mg2A1V$zn*JXo~!xOl3YE>wAD zZ4NkJKrZUIisL8$$8i#|cc%@nd3Is?<^KyXMo`Og zLdZ_W*xAroZH#>=C8Py7QcB;zFV|qS;}6}$twNzCfhXMP_d!)#42J0T%hI?W2x>YM zp(E_|{|g`)el7(Vc^V~)CtV=htOG8P7U1XzHc&IzTKqT(I~7QGbbt{!4}Mr^Mqwy6 zEdg*}5QoA5yoB4at@OS@0w@5#AQ3#HZ=!LsA#{m!h$E??bRGCggl%JQngA!m^Q62lwk+vET>w+9|$RURu da!3V${{TH&7KWrINNfNA002ovPDHLkV1n#1f*}9^ diff --git a/src/Resources/ExternalToolIcons/git.png b/src/Resources/ExternalToolIcons/git.png new file mode 100644 index 0000000000000000000000000000000000000000..cbb908b9ce125742e37a372c59469ca97e44b2bf GIT binary patch literal 1223 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPVEzz*rLC6XFWw{)eE@(Ekn&|Ba3R zYis}aRC#7>{K8G|m4o(cYxNH{x*sjIKRFn`wl;igr}N&*@PnP+Cu_a8*81VB}+`Cz3Fl=^4`RABJVM(@3~9?-N8)<8kM zk3eH>^noJpZS+3?#ewRDF*N={A7$jr{kD=00muBmTnYwwycamtLDv**m4zhKegrOTGDTEAiA zrmcJT9Xx#G*s0TJ&Yiz-@zT|6x9>c7^z8ZTH}5}u`uyeVw;w-${r>a!UwP!4+dx+^ zCV9KNRQq4r`iFsmsoK-UF(l&f-D~OPmkmVN5=7Zg<=m*=dSR24TSTfTlkdXT0JB_8 z?KMVD97`7N-7jZZmJ_*eQ>kk0Bf0sL`K!Ll$j5pbCR>$$`FNcF>f1A+m7yP2+rBzA zyKO<(sXSZvMGq_mh37E`ep+*MO1kzkTfKQ(8T$VpJ@Tnut4jIc`<1EYuTT88e)mas z_wS<%CEnSmCvBN8+j;MA#H7Fw1%FHA!W+luGjC2yI@Im5pR3%*<)nAV z>gO+#PBBY#y7N!GF8DDa-Db}#>%b#65&A6mF8L|GI@mB_#pH9Q?+zx+iMYJ*jtCcj zmeaC5i}Y97MA+r1XxTO#o6tDp;m4Y#yVErmne#?|y>&iEntRjoz>hXZxg9TvecmIt zLi<^j;0pCB(+g}5iwu^V#O<=UI_YTfu6YdKT=tutUGT_Op>fd-bH#4eV}1;RB1iw7 z4=TuHpRL;b_Fu#v74_9?T-oBer1Q4VS+iM9vPP*P`sow(Z+Bktem`+i(#(f#dW&|A zkDpxP8!^$A)f2bp8D16MH#0Bz;JJVI<}avG%aXe+JXv9j+wStm&z7%PuI#0d|LiPx zxZK~sikLM4j%(z^TAU*{vb*YsUtyRWH*W_2skpLqEAu}tAKR@2zdO7C*=Ft`BCatn z>dl+is%-lLY?fL`s{2i{nOb4Mx8|?!Cy_^PiXYmZJ$%HLSTpa%!bf_ETO7X{e5|su z@c1t9@%gkD-9M%_ws*YmUTB{BMDfve<2{D03t64{qmP&Ujb-k!?Ufea8~?^-)^z=# zy*3lyrFN^uP0M(;(lyv$byoF`jK_(K=1v#OwT;~JebORsf04TtF+BTcy{|eG%4?tf fud2{T`xpOhwo;C$SBf3LG|AxU>gTe~DWM4fyFxPD literal 0 HcmV?d00001 diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 28afdf9c..25b50e63 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -339,12 +339,10 @@ Input path for installed gpg program User Signing Key User's gpg signing key - EXTERNAL MERGE TOOL - Diff Command - Merge Command - Install Path - Input path for merge tool - Merger + DIFF/MERGE TOOL + Install Path + Input path for diff/merge tool + Tool Prune Remote Target: Pull diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index b1658c34..c2c69099 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -342,12 +342,10 @@ 签名程序所在路径 用户签名KEY 输入签名提交所使用的KEY - 外部合并工具 - 对比模式启动参数 - 合并模式启动参数 - 安装路径 - 填写工具可执行文件所在位置 - 工具 + 对比/合并工具 + 安装路径 + 填写工具可执行文件所在位置 + 工具 清理远程已删除分支 目标 : 拉回(pull) diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 7a8bd837..8cd3c88b 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -342,12 +342,10 @@ gpg.exe所在路徑 使用者簽名KEY 輸入簽名提交所使用的KEY - 外部合併工具 - 對比模式啟動引數 - 合併模式啟動引數 - 安裝路徑 - 填寫工具可執行檔案所在位置 - 工具 + 對比/合併工具 + 安裝路徑 + 填寫工具可執行檔案所在位置 + 工具 清理遠端已刪除分支 目標 : 拉回(pull) diff --git a/src/ViewModels/BranchCompare.cs b/src/ViewModels/BranchCompare.cs index 6b19d249..be133bd2 100644 --- a/src/ViewModels/BranchCompare.cs +++ b/src/ViewModels/BranchCompare.cs @@ -133,19 +133,11 @@ namespace SourceGit.ViewModels diffWithMerger.Icon = App.CreateMenuIcon("Icons.Diff"); diffWithMerger.Click += (_, ev) => { + var toolType = Preference.Instance.ExternalMergeToolType; + var toolPath = Preference.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption(Base.Head, To.Head, change); - var type = Preference.Instance.ExternalMergeToolType; - var exec = Preference.Instance.ExternalMergeToolPath; - var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type); - if (tool == null || !File.Exists(exec)) - { - App.RaiseException(_repo, "Invalid merge tool in preference setting!"); - return; - } - - var args = tool.Type != 0 ? tool.DiffCmd : Preference.Instance.ExternalMergeToolDiffCmd; - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, exec, args, opt)); + Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, toolType, toolPath, opt)); ev.Handled = true; }; menu.Items.Add(diffWithMerger); diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 430f4b7d..4ab521b8 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -218,19 +218,11 @@ namespace SourceGit.ViewModels diffWithMerger.Icon = App.CreateMenuIcon("Icons.Diff"); diffWithMerger.Click += (_, ev) => { + var toolType = Preference.Instance.ExternalMergeToolType; + var toolPath = Preference.Instance.ExternalMergeToolPath; var opt = new Models.DiffOption(_commit, change); - var type = Preference.Instance.ExternalMergeToolType; - var exec = Preference.Instance.ExternalMergeToolPath; - var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type); - if (tool == null || !File.Exists(exec)) - { - App.RaiseException(_repo, "Invalid merge tool in preference setting!"); - return; - } - - var args = tool.Type != 0 ? tool.DiffCmd : Preference.Instance.ExternalMergeToolDiffCmd; - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, exec, args, opt)); + Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, toolType, toolPath, opt)); ev.Handled = true; }; menu.Items.Add(diffWithMerger); diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 53f6839c..8a240752 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -98,18 +98,9 @@ namespace SourceGit.ViewModels public void OpenExternalMergeTool() { - var type = Preference.Instance.ExternalMergeToolType; - var exec = Preference.Instance.ExternalMergeToolPath; - - var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type); - if (tool == null || !File.Exists(exec)) - { - App.RaiseException(_repo, "Invalid merge tool in preference setting!"); - return; - } - - var args = tool.Type != 0 ? tool.DiffCmd : Preference.Instance.ExternalMergeToolDiffCmd; - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, exec, args, _option)); + var toolType = Preference.Instance.ExternalMergeToolType; + var toolPath = Preference.Instance.ExternalMergeToolPath; + Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, toolType, toolPath, _option)); } private void LoadDiffContent() diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index debe672a..b72fb8c4 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -290,18 +290,6 @@ namespace SourceGit.ViewModels set => SetProperty(ref _externalMergeToolPath, value); } - public string ExternalMergeToolCmd - { - get => _externalMergeToolCmd; - set => SetProperty(ref _externalMergeToolCmd, value); - } - - public string ExternalMergeToolDiffCmd - { - get => _externalMergeToolDiffCmd; - set => SetProperty(ref _externalMergeToolDiffCmd, value); - } - public List Repositories { get; @@ -556,8 +544,6 @@ namespace SourceGit.ViewModels private int _externalMergeToolType = 0; private string _externalMergeToolPath = string.Empty; - private string _externalMergeToolCmd = string.Empty; - private string _externalMergeToolDiffCmd = string.Empty; private AvaloniaList _repositoryNodes = new AvaloniaList(); } diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index 99fae39f..80a06757 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -142,18 +142,10 @@ namespace SourceGit.ViewModels diffWithMerger.Click += (_, ev) => { var opt = new Models.DiffOption(StartPoint.SHA, _endPoint, change); - var type = Preference.Instance.ExternalMergeToolType; - var exec = Preference.Instance.ExternalMergeToolPath; + var toolType = Preference.Instance.ExternalMergeToolType; + var toolPath = Preference.Instance.ExternalMergeToolPath; - var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type); - if (tool == null || !File.Exists(exec)) - { - App.RaiseException(_repo, "Invalid merge tool in preference setting!"); - return; - } - - var args = tool.Type != 0 ? tool.DiffCmd : Preference.Instance.ExternalMergeToolDiffCmd; - Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, exec, args, opt)); + Task.Run(() => Commands.MergeTool.OpenForDiff(_repo, toolType, toolPath, opt)); ev.Handled = true; }; menu.Items.Add(diffWithMerger); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index a0b406cb..434923f2 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -1120,20 +1120,11 @@ namespace SourceGit.ViewModels private async void UseExternalMergeTool(Models.Change change) { - var type = Preference.Instance.ExternalMergeToolType; - var exec = Preference.Instance.ExternalMergeToolPath; - - var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type); - if (tool == null) - { - App.RaiseException(_repo.FullPath, "Invalid merge tool in preference setting!"); - return; - } - - var args = tool.Type != 0 ? tool.Cmd : Preference.Instance.ExternalMergeToolCmd; + var toolType = Preference.Instance.ExternalMergeToolType; + var toolPath = Preference.Instance.ExternalMergeToolPath; _repo.SetWatcherEnabled(false); - await Task.Run(() => Commands.MergeTool.OpenForMerge(_repo.FullPath, exec, args, change.Path)); + await Task.Run(() => Commands.MergeTool.OpenForMerge(_repo.FullPath, toolType, toolPath, change.Path)); _repo.SetWatcherEnabled(true); } diff --git a/src/Views/Preference.axaml b/src/Views/Preference.axaml index d502e818..b84354aa 100644 --- a/src/Views/Preference.axaml +++ b/src/Views/Preference.axaml @@ -449,12 +449,12 @@ - + - + + Margin="0,0,16,0" + IsVisible="{Binding ExternalMergeToolType, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/> + Text="{Binding ExternalMergeToolPath, Mode=TwoWay}" + Watermark="{DynamicResource Text.Preference.DiffMerge.Path.Placeholder}" + IsVisible="{Binding ExternalMergeToolType, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"> - - - - - - diff --git a/src/Views/Preference.axaml.cs b/src/Views/Preference.axaml.cs index cccbd522..2cb9dca4 100644 --- a/src/Views/Preference.axaml.cs +++ b/src/Views/Preference.axaml.cs @@ -287,6 +287,7 @@ namespace SourceGit.Views { ViewModels.Preference.Instance.ExternalMergeToolType = 0; type = 0; + return; } var tool = Models.ExternalMerger.Supported[type]; From a3c6431efa9e9856a0633148d4d41b9501bbb8aa Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 14:14:13 +0800 Subject: [PATCH 12/68] feature: supports adding custom LFS track pattern --- src/Resources/Locales/en_US.axaml | 4 +++ src/Resources/Locales/zh_CN.axaml | 4 +++ src/Resources/Locales/zh_TW.axaml | 4 +++ src/ViewModels/LFSTrackCustomPattern.cs | 43 ++++++++++++++++++++++++ src/ViewModels/Repository.cs | 13 +++++++ src/Views/LFSTrackCustomPattern.axaml | 33 ++++++++++++++++++ src/Views/LFSTrackCustomPattern.axaml.cs | 12 +++++++ 7 files changed, 113 insertions(+) create mode 100644 src/ViewModels/LFSTrackCustomPattern.cs create mode 100644 src/Views/LFSTrackCustomPattern.axaml create mode 100644 src/Views/LFSTrackCustomPattern.axaml.cs diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 25b50e63..48280f8d 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -229,6 +229,10 @@ FLOW - Start Release Version Tag Prefix: Git LFS + Add Track Pattern... + Pattern is file name + Custom Pattern: + Add Track Pattern to Git LFS Fetch Fetch LFS Objects Run `git lfs fetch` to download Git LFS objects. This does not update the working copy. diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index c2c69099..70955de8 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -232,6 +232,10 @@ 开始版本分支 版本标签前缀 : Git LFS + 添加追踪文件规则... + 匹配完整文件名 + 规则 : + 添加LFS追踪文件规则 拉取LFS对象 (fetch) 拉取LFS对象 执行`git lfs prune`命令,下载远程LFS对象,但不会更新工作副本。 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 8cd3c88b..f6f31fa7 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -232,6 +232,10 @@ 開始版本分支 版本標籤字首 : Git LFS + 添加追蹤檔案規則... + 匹配完整檔案名 + 規則 : + 添加LFS追蹤檔案規則 拉取LFS物件 (fetch) 拉取LFS物件 執行`git lfs fetch`命令,下載遠端LFS物件,但不會更新工作副本。 diff --git a/src/ViewModels/LFSTrackCustomPattern.cs b/src/ViewModels/LFSTrackCustomPattern.cs new file mode 100644 index 00000000..777e2d22 --- /dev/null +++ b/src/ViewModels/LFSTrackCustomPattern.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class LFSTrackCustomPattern : Popup + { + [Required(ErrorMessage = "LFS track pattern is required!!!")] + public string Pattern + { + get => _pattern; + set => SetProperty(ref _pattern, value, true); + } + + public bool IsFilename + { + get; + set; + } = false; + + public LFSTrackCustomPattern(Repository repo) + { + _repo = repo; + View = new Views.LFSTrackCustomPattern() { DataContext = this }; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = "Adding custom LFS tracking pattern ..."; + + return Task.Run(() => + { + var succ = new Commands.LFS(_repo.FullPath).Track(_pattern, IsFilename); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return succ; + }); + } + + private readonly Repository _repo = null; + private string _pattern = string.Empty; + } +} diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 3f035150..5dd56772 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -834,6 +834,19 @@ namespace SourceGit.ViewModels var lfs = new Commands.LFS(_fullpath); if (lfs.IsEnabled()) { + var addPattern = new MenuItem(); + addPattern.Header = App.Text("GitLFS.AddTrackPattern"); + addPattern.Icon = App.CreateMenuIcon("Icons.File.Add"); + addPattern.Click += (o, e) => + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowPopup(new LFSTrackCustomPattern(this)); + + e.Handled = true; + }; + menu.Items.Add(addPattern); + menu.Items.Add(new MenuItem() { Header = "-" }); + var fetch = new MenuItem(); fetch.Header = App.Text("GitLFS.Fetch"); fetch.Icon = App.CreateMenuIcon("Icons.Fetch"); diff --git a/src/Views/LFSTrackCustomPattern.axaml b/src/Views/LFSTrackCustomPattern.axaml new file mode 100644 index 00000000..6333dcd2 --- /dev/null +++ b/src/Views/LFSTrackCustomPattern.axaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/src/Views/LFSTrackCustomPattern.axaml.cs b/src/Views/LFSTrackCustomPattern.axaml.cs new file mode 100644 index 00000000..2e66f55a --- /dev/null +++ b/src/Views/LFSTrackCustomPattern.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class LFSTrackCustomPattern : UserControl + { + public LFSTrackCustomPattern() + { + InitializeComponent(); + } + } +} From 6dface0b62a4ee2a42525ca7fb94407558bd808b Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 14:18:29 +0800 Subject: [PATCH 13/68] ux: allow resize window size of LFSLocks --- src/Views/LFSLocks.axaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Views/LFSLocks.axaml b/src/Views/LFSLocks.axaml index b2e3fe84..54a4fa0a 100644 --- a/src/Views/LFSLocks.axaml +++ b/src/Views/LFSLocks.axaml @@ -11,7 +11,6 @@ Icon="/App.ico" Title="{DynamicResource Text.GitLFS.Locks.Title}" Width="600" Height="400" - CanResize="False" WindowStartupLocation="CenterOwner"> @@ -82,7 +81,7 @@ - + From 08567a7420b3171dff73fac4d26de9f9889cf992 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 14:27:59 +0800 Subject: [PATCH 14/68] ux: only show `Set as tracking branch` option if selected remote branch is not upstream of selected local branch --- src/ViewModels/Push.cs | 33 +++++++++++++-------------------- src/Views/Push.axaml | 6 ++++-- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs index 0ca4f9de..863ef873 100644 --- a/src/ViewModels/Push.cs +++ b/src/ViewModels/Push.cs @@ -19,25 +19,7 @@ namespace SourceGit.ViewModels set { if (SetProperty(ref _selectedLocalBranch, value)) - { - // If selected local branch has upstream branch. Try to find it's remote. - if (!string.IsNullOrEmpty(value.Upstream)) - { - var branch = _repo.Branches.Find(x => x.FullName == value.Upstream); - if (branch != null) - { - var remote = _repo.Remotes.Find(x => x.Name == branch.Remote); - if (remote != null && remote != _selectedRemote) - { - SelectedRemote = remote; - return; - } - } - } - - // Re-generate remote branches and auto-select remote branches. AutoSelectBranchByRemote(); - } } } @@ -73,7 +55,11 @@ namespace SourceGit.ViewModels public Models.Branch SelectedRemoteBranch { get => _selectedRemoteBranch; - set => SetProperty(ref _selectedRemoteBranch, value); + set + { + if (SetProperty(ref _selectedRemoteBranch, value)) + IsSetTrackOptionVisible = value != null && _selectedLocalBranch.Upstream != value.FullName; + } } public bool PushAllTags @@ -82,6 +68,12 @@ namespace SourceGit.ViewModels set; } + public bool IsSetTrackOptionVisible + { + get => _isSetTrackOptionVisible; + private set => SetProperty(ref _isSetTrackOptionVisible, value); + } + public bool Tracking { get; @@ -160,7 +152,7 @@ namespace SourceGit.ViewModels remoteBranchName, PushAllTags, ForcePush, - Tracking, + _isSetTrackOptionVisible && Tracking, SetProgressDescription).Exec(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; @@ -218,5 +210,6 @@ namespace SourceGit.ViewModels private Models.Remote _selectedRemote = null; private List _remoteBranches = new List(); private Models.Branch _selectedRemoteBranch = null; + private bool _isSetTrackOptionVisible = false; } } diff --git a/src/Views/Push.axaml b/src/Views/Push.axaml index 00cdafbf..043910c2 100644 --- a/src/Views/Push.axaml +++ b/src/Views/Push.axaml @@ -13,7 +13,7 @@ Classes="bold" Text="{DynamicResource Text.Push.Title}"/> - + + IsChecked="{Binding Tracking, Mode=TwoWay}" + IsVisible="{Binding IsSetTrackOptionVisible}"/> Date: Tue, 18 Jun 2024 14:47:55 +0800 Subject: [PATCH 15/68] localiztion: popup panel title should not end with ellipsis --- src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 5 +++-- src/Resources/Locales/zh_TW.axaml | 5 +++-- src/Views/CreateBranch.axaml | 2 +- src/Views/CreateTag.axaml | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 48280f8d..e42321d7 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -132,6 +132,7 @@ Tag Name: Recommended format: v1.0.0-alpha Push to all remotes after created + Create New Tag Kind: annotated lightweight diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 70955de8..677ad43d 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -117,7 +117,7 @@ 复制 复制路径 复制文件名 - 新建分支 + 新建分支 ... 新分支基于 : 完成后切换到新分支 未提交更改 : @@ -127,7 +127,7 @@ 新分支名 : 填写分支名称。 创建本地分支 - 新建标签 + 新建标签 ... 标签位于 : 使用GPG签名 标签描述 : @@ -135,6 +135,7 @@ 标签名 : 推荐格式 :v1.0.0-alpha 推送到所有远程仓库 + 新建标签 类型 : 附注标签 轻量标签 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index f6f31fa7..115edc40 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -117,7 +117,7 @@ 複製 複製路徑 複製檔名 - 新建分支 + 新建分支 ... 新分支基於 : 完成後切換到新分支 未提交更改 : @@ -127,7 +127,7 @@ 新分支名 : 填寫分支名稱。 建立本地分支 - 新建標籤 + 新建標籤 ... 標籤位於 : 使用GPG簽名 標籤描述 : @@ -135,6 +135,7 @@ 標籤名 : 推薦格式 :v1.0.0-alpha 推送到所有遠端倉庫 + 新建標籤 型別 : 附註標籤 輕量標籤 diff --git a/src/Views/CreateBranch.axaml b/src/Views/CreateBranch.axaml index dd204b18..36745c99 100644 --- a/src/Views/CreateBranch.axaml +++ b/src/Views/CreateBranch.axaml @@ -13,7 +13,7 @@ + Text="{DynamicResource Text.CreateBranch.Title}"/> + Text="{DynamicResource Text.CreateTag.Title}"/> Date: Tue, 18 Jun 2024 14:55:22 +0800 Subject: [PATCH 16/68] ux: new icon for track option --- src/Resources/Icons.axaml | 1 + src/ViewModels/Repository.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index f96d4eb7..a0d5feb6 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -100,4 +100,5 @@ M40 9 15 23 15 31 9 28 9 20 34 5 24 0 0 14 0 34 25 48 25 28 49 14zM26 29 26 48 49 34 49 15z M832 464h-68V240a128 128 0 00-128-128h-248a128 128 0 00-128 128v224H192c-18 0-32 14-32 32v384c0 18 14 32 32 32h640c18 0 32-14 32-32v-384c0-18-14-32-32-32zm-292 237v53a8 8 0 01-8 8h-40a8 8 0 01-8-8v-53a48 48 0 1156 0zm152-237H332V240a56 56 0 0156-56h248a56 56 0 0156 56v224z M832 464H332V240c0-31 25-56 56-56h248c31 0 56 25 56 56v68c0 4 4 8 8 8h56c4 0 8-4 8-8v-68c0-71-57-128-128-128H388c-71 0-128 57-128 128v224h-68c-18 0-32 14-32 32v384c0 18 14 32 32 32h640c18 0 32-14 32-32V496c0-18-14-32-32-32zM540 701v53c0 4-4 8-8 8h-40c-4 0-8-4-8-8v-53c-12-9-20-23-20-39 0-27 22-48 48-48s48 22 48 48c0 16-8 30-20 39z + M897 673v13c0 51-42 93-93 93h-10c-1 0-2 0-2 0H220c-23 0-42 19-42 42v13c0 23 19 42 42 42h552c14 0 26 12 26 26 0 14-12 26-26 26H220c-51 0-93-42-93-93v-13c0-51 42-93 93-93h20c1-0 2-0 2-0h562c23 0 42-19 42-42v-13c0-11-5-22-13-29-8-7-17-11-28-10H660c-14 0-26-12-26-26 0-14 12-26 26-26h144c24-1 47 7 65 24 18 17 29 42 29 67zM479 98c-112 0-203 91-203 203 0 44 14 85 38 118l132 208c15 24 50 24 66 0l133-209c23-33 37-73 37-117 0-112-91-203-203-203zm0 327c-68 0-122-55-122-122s55-122 122-122 122 55 122 122-55 122-122 122z diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 5dd56772..d2b8d462 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1160,7 +1160,7 @@ namespace SourceGit.ViewModels { var tracking = new MenuItem(); tracking.Header = App.Text("BranchCM.Tracking"); - tracking.Icon = App.CreateMenuIcon("Icons.Branch"); + tracking.Icon = App.CreateMenuIcon("Icons.Track"); foreach (var b in remoteBranches) { From 99afc74871d135e9a1c7be1887f26f944ffe1c02 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 18:27:57 +0800 Subject: [PATCH 17/68] enhance: shows current histories filters and add a button to clear all histories filters (#184) --- src/Converters/StringConverters.cs | 10 ++++++++ src/Resources/Locales/en_US.axaml | 2 ++ src/Resources/Locales/zh_CN.axaml | 2 ++ src/Resources/Locales/zh_TW.axaml | 2 ++ src/ViewModels/Repository.cs | 22 ++++++++++++++++++ src/Views/Repository.axaml | 37 ++++++++++++++++++++++++++++-- 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/Converters/StringConverters.cs b/src/Converters/StringConverters.cs index ac1785c0..42eaa164 100644 --- a/src/Converters/StringConverters.cs +++ b/src/Converters/StringConverters.cs @@ -81,6 +81,16 @@ namespace SourceGit.Converters return true; }); + public static readonly FuncValueConverter TrimRefsPrefix = + new FuncValueConverter(v => + { + if (v.StartsWith("refs/heads/", StringComparison.Ordinal)) + return v.Substring(11); + if (v.StartsWith("refs/remotes/", StringComparison.Ordinal)) + return v.Substring(13); + return v; + }); + [GeneratedRegex(@"^[\s\w]*(\d+)\.(\d+)[\.\-](\d+).*$")] private static partial Regex REG_GIT_VERSION(); diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index e42321d7..a7ca7671 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -398,10 +398,12 @@ ABORT Cleanup(GC & Prune) Run `git gc` command for this repository. + Clear all Configure this repository CONTINUE Open In File Browser Filter Branches + FILTERED BY: LOCAL BRANCHES Navigate To HEAD Create Branch diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 677ad43d..e7f88389 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -400,10 +400,12 @@ 终止合并 清理本仓库(GC) 本操作将执行`git gc`命令。 + 清空过滤规则 配置本仓库 下一步 在文件浏览器中打开 过滤显示分支 + 过滤规则 : 本地分支 定位HEAD 新建分支 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 115edc40..d8e5e336 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -400,10 +400,12 @@ 終止合併 清理本倉庫(GC) 本操作將執行`git gc`命令。 + 清空過濾規則 配置本倉庫 下一步 在檔案瀏覽器中開啟 過濾顯示分支 + 過濾規則 : 本地分支 定位HEAD 新建分支 diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index d2b8d462..6dc6fb9e 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -404,6 +404,18 @@ namespace SourceGit.ViewModels PopupHost.ShowPopup(new RepositoryConfigure(this)); } + public void ClearHistoriesFilter() + { + Filters.Clear(); + + Task.Run(() => + { + RefreshBranches(); + RefreshTags(); + RefreshCommits(); + }); + } + public void ClearSearchCommitFilter() { SearchCommitFilter = string.Empty; @@ -602,9 +614,19 @@ namespace SourceGit.ViewModels validFilters.Add(filter); } } + if (validFilters.Count > 0) { limits += string.Join(" ", validFilters); + + if (Filters.Count != validFilters.Count) + { + Dispatcher.UIThread.Post(() => + { + Filters.Clear(); + Filters.AddRange(validFilters); + }); + } } else { diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 3c0b28d0..0e48186a 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -614,7 +614,7 @@ BorderBrush="{DynamicResource Brush.Border0}"/> - + @@ -646,7 +646,40 @@ + + + + From 41fbbdf6434d08c2075fa7f10e50db0781571a1c Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 18 Jun 2024 20:14:36 +0800 Subject: [PATCH 18/68] ux: placeholder for inputs --- src/Views/CreateBranch.axaml | 1 + src/Views/CreateTag.axaml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Views/CreateBranch.axaml b/src/Views/CreateBranch.axaml index 36745c99..007ae537 100644 --- a/src/Views/CreateBranch.axaml +++ b/src/Views/CreateBranch.axaml @@ -54,6 +54,7 @@ VerticalAlignment="Center" CornerRadius="2" Text="{Binding Name, Mode=TwoWay}" + Watermark="{DynamicResource Text.CreateBranch.Name.Placeholder}" v:AutoFocusBehaviour.IsEnabled="True"/> Date: Tue, 18 Jun 2024 21:12:23 +0800 Subject: [PATCH 19/68] readme: update development environment for Linux --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b325e6fc..4e7f87b3 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Opensource Git GUI client. * GitFlow support * Git LFS support -> **Linux** only tested on **Ubuntu 22.04** on **X11**. +> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**. ## How to Use From 86226d5484e0bed59ff2c5300a5db99e51719a1d Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 19 Jun 2024 10:21:36 +0800 Subject: [PATCH 20/68] fix: tag.gpgsign setting not updated --- src/Commands/Config.cs | 5 ++- src/ViewModels/RepositoryConfigure.cs | 4 +-- src/Views/Preference.axaml.cs | 51 ++++++++++++--------------- 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/Commands/Config.cs b/src/Commands/Config.cs index 62340aa3..e1016f1e 100644 --- a/src/Commands/Config.cs +++ b/src/Commands/Config.cs @@ -14,7 +14,10 @@ namespace SourceGit.Commands public Dictionary ListAll() { - Args = "config -l"; + if (string.IsNullOrEmpty(WorkingDirectory)) + Args = "config --global -l"; + else + Args = "config -l"; var output = ReadToEnd(); var rs = new Dictionary(); diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs index 1409dfa2..6472e733 100644 --- a/src/ViewModels/RepositoryConfigure.cs +++ b/src/ViewModels/RepositoryConfigure.cs @@ -52,7 +52,7 @@ namespace SourceGit.ViewModels UserEmail = email; if (_cached.TryGetValue("commit.gpgsign", out var gpgCommitSign)) GPGCommitSigningEnabled = gpgCommitSign == "true"; - if (_cached.TryGetValue("tag.gpgSign", out var gpgTagSign)) + if (_cached.TryGetValue("tag.gpgsign", out var gpgTagSign)) GPGTagSigningEnabled = gpgTagSign == "true"; if (_cached.TryGetValue("user.signingkey", out var signingKey)) GPGUserSigningKey = signingKey; @@ -67,7 +67,7 @@ namespace SourceGit.ViewModels SetIfChanged("user.name", UserName); SetIfChanged("user.email", UserEmail); SetIfChanged("commit.gpgsign", GPGCommitSigningEnabled ? "true" : "false"); - SetIfChanged("tag.gpgSign", GPGTagSigningEnabled ? "true" : "false"); + SetIfChanged("tag.gpgsign", GPGTagSigningEnabled ? "true" : "false"); SetIfChanged("user.signingkey", GPGUserSigningKey); SetIfChanged("http.proxy", HttpProxy); return null; diff --git a/src/Views/Preference.axaml.cs b/src/Views/Preference.axaml.cs index 2cb9dca4..5669c9ea 100644 --- a/src/Views/Preference.axaml.cs +++ b/src/Views/Preference.axaml.cs @@ -157,7 +157,7 @@ namespace SourceGit.Views CRLFMode = Models.CRLFMode.Supported.Find(x => x.Value == crlf); if (config.TryGetValue("commit.gpgsign", out var gpgCommitSign)) EnableGPGCommitSigning = (gpgCommitSign == "true"); - if (config.TryGetValue("tag.gpgSign", out var gpgTagSign)) + if (config.TryGetValue("tag.gpgsign", out var gpgTagSign)) EnableGPGTagSigning = (gpgTagSign == "true"); if (config.TryGetValue("gpg.format", out var gpgFormat)) GPGFormat = Models.GPGFormat.Supported.Find(x => x.Value == gpgFormat) ?? Models.GPGFormat.Supported[0]; @@ -181,34 +181,15 @@ namespace SourceGit.Views private void CloseWindow(object sender, RoutedEventArgs e) { - var cmd = new Commands.Config(null); - - var config = cmd.ListAll(); - var oldUser = config.TryGetValue("user.name", out var user) ? user : string.Empty; - var oldEmail = config.TryGetValue("user.email", out var email) ? email : string.Empty; - var oldGPGSignKey = config.TryGetValue("user.signingkey", out var signingKey) ? signingKey : string.Empty; - var oldCRLF = config.TryGetValue("core.autocrlf", out var crlf) ? crlf : string.Empty; - var oldGPGFormat = config.TryGetValue("gpg.format", out var gpgFormat) ? gpgFormat : "opengpg"; - var oldGPGCommitSignEnable = config.TryGetValue("commit.gpgsign", out var gpgCommitSign) ? gpgCommitSign : "false"; - var oldGPGTagSignEnable = config.TryGetValue("tag.gpgSign", out var gpgTagSign) ? gpgTagSign : "false"; - var oldGPGExec = config.TryGetValue("gpg.program", out var program) ? program : string.Empty; - - if (DefaultUser != oldUser) - cmd.Set("user.name", DefaultUser); - if (DefaultEmail != oldEmail) - cmd.Set("user.email", DefaultEmail); - if (GPGUserKey != oldGPGSignKey) - cmd.Set("user.signingkey", GPGUserKey); - if (CRLFMode != null && CRLFMode.Value != oldCRLF) - cmd.Set("core.autocrlf", CRLFMode.Value); - if (EnableGPGCommitSigning != (oldGPGCommitSignEnable == "true")) - cmd.Set("commit.gpgsign", EnableGPGCommitSigning ? "true" : "false"); - if (EnableGPGTagSigning != (oldGPGTagSignEnable == "true")) - cmd.Set("tag.gpgSign", EnableGPGTagSigning ? "true" : "false"); - if (GPGFormat.Value != oldGPGFormat) - cmd.Set("gpg.format", GPGFormat.Value); - if (GPGExecutableFile != oldGPGExec) - cmd.Set($"gpg.{GPGFormat.Value}.program", GPGExecutableFile); + var config = new Commands.Config(null).ListAll(); + SetIfChanged(config, "user.name", DefaultUser); + SetIfChanged(config, "user.email", DefaultEmail); + SetIfChanged(config, "user.signingkey", GPGUserKey); + SetIfChanged(config, "core.autocrlf", CRLFMode.Value); + SetIfChanged(config, "commit.gpgsign", EnableGPGCommitSigning ? "true" : "false"); + SetIfChanged(config, "tag.gpgsign", EnableGPGTagSigning ? "true" : "false"); + SetIfChanged(config, "gpg.format", GPGFormat.Value); + SetIfChanged(config, $"gpg.{GPGFormat.Value}.program", GPGFormat.Value != "ssh" ? GPGExecutableFile : null); Close(); } @@ -303,5 +284,17 @@ namespace SourceGit.Views ViewModels.Preference.Instance.ExternalMergeToolPath = selected[0].Path.LocalPath; } } + + private void SetIfChanged(Dictionary cached, string key, string value) + { + bool changed = false; + if (cached.TryGetValue(key, out var old)) + changed = old != value; + else if (!string.IsNullOrEmpty(value)) + changed = true; + + if (changed) + new Commands.Config(null).Set(key, value); + } } } From 4ce3d73d61b2993470c5fc04f2241f39f082185a Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 19 Jun 2024 10:38:30 +0800 Subject: [PATCH 21/68] fix: remote url not supports charator `~` (#186) --- src/Models/Remote.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs index 950dde62..29c10d51 100644 --- a/src/Models/Remote.cs +++ b/src/Models/Remote.cs @@ -5,14 +5,14 @@ namespace SourceGit.Models { public partial class Remote { - [GeneratedRegex(@"^http[s]?://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/]+/[\w\-\.]+(\.git)?$")] + [GeneratedRegex(@"^http[s]?://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/~]+/[\w\-\.]+(\.git)?$")] private static partial Regex REG_HTTPS(); - [GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:[\w\-/]+/[\w\-\.]+(\.git)?$")] + [GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:[\w\-/~]+/[\w\-\.]+(\.git)?$")] private static partial Regex REG_SSH1(); - [GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/]+/[\w\-\.]+(\.git)?$")] + [GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/~]+/[\w\-\.]+(\.git)?$")] private static partial Regex REG_SSH2(); - [GeneratedRegex(@"^git@([\w\.\-]+):([\w\-/]+/[\w\-\.]+)\.git$")] + [GeneratedRegex(@"^git@([\w\.\-]+):([\w\-/~]+/[\w\-\.]+)\.git$")] private static partial Regex REG_TO_VISIT_URL_CAPTURE(); private static readonly Regex[] URL_FORMATS = [ From e17e4b9a5349a313c42a66511e23ddb929bd66a3 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 19 Jun 2024 12:06:34 +0800 Subject: [PATCH 22/68] enhance: only show tracking options when file is not tracked by GIT LFS --- src/ViewModels/WorkingCopy.cs | 49 +++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 434923f2..b0f91dea 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -618,33 +618,39 @@ namespace SourceGit.ViewModels lfs.Header = App.Text("GitLFS"); lfs.Icon = App.CreateMenuIcon("Icons.LFS"); - var filename = Path.GetFileName(change.Path); - var lfsTrackThisFile = new MenuItem(); - lfsTrackThisFile.Header = App.Text("GitLFS.Track", filename); - lfsTrackThisFile.Click += async (_, e) => + var isLFSFiltered = new Commands.IsLFSFiltered(_repo.FullPath, change.Path).Result(); + if (!isLFSFiltered) { - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track(filename, true)); - if (succ) - App.SendNotification(_repo.FullPath, $"Tracking file named {filename} successfully!"); - - e.Handled = true; - }; - lfs.Items.Add(lfsTrackThisFile); - - if (!string.IsNullOrEmpty(extension)) - { - var lfsTrackByExtension = new MenuItem(); - lfsTrackByExtension.Header = App.Text("GitLFS.TrackByExtension", extension); - lfsTrackByExtension.Click += async (_, e) => + var filename = Path.GetFileName(change.Path); + var lfsTrackThisFile = new MenuItem(); + lfsTrackThisFile.Header = App.Text("GitLFS.Track", filename); + lfsTrackThisFile.Click += async (_, e) => { - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track("*" + extension, false)); + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track(filename, true)); if (succ) - App.SendNotification(_repo.FullPath, $"Tracking all *{extension} files successfully!"); + App.SendNotification(_repo.FullPath, $"Tracking file named {filename} successfully!"); e.Handled = true; }; - lfs.Items.Add(lfsTrackByExtension); - } + lfs.Items.Add(lfsTrackThisFile); + + if (!string.IsNullOrEmpty(extension)) + { + var lfsTrackByExtension = new MenuItem(); + lfsTrackByExtension.Header = App.Text("GitLFS.TrackByExtension", extension); + lfsTrackByExtension.Click += async (_, e) => + { + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track("*" + extension, false)); + if (succ) + App.SendNotification(_repo.FullPath, $"Tracking all *{extension} files successfully!"); + + e.Handled = true; + }; + lfs.Items.Add(lfsTrackByExtension); + } + + lfs.Items.Add(new MenuItem() { Header = "-" }); + } var lfsLock = new MenuItem(); lfsLock.Header = App.Text("GitLFS.Locks.Lock"); @@ -657,7 +663,6 @@ namespace SourceGit.ViewModels e.Handled = true; }; - lfs.Items.Add(new MenuItem() { Header = "-" }); lfs.Items.Add(lfsLock); var lfsUnlock = new MenuItem(); From a168b05ac610c34fb1629edb8b654652512192a0 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 19 Jun 2024 14:24:49 +0800 Subject: [PATCH 23/68] ux: re-design submodule preview in revision files --- src/Models/DiffResult.cs | 10 ++-------- src/Models/RevisionFile.cs | 3 ++- src/ViewModels/CommitDetail.cs | 29 ++++++++++++++++++++++++++++- src/ViewModels/DiffContext.cs | 6 +++--- src/Views/DiffView.axaml | 2 +- src/Views/RevisionFiles.axaml | 5 ++--- 6 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index a0a02c6f..a625fc7b 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -587,16 +587,10 @@ namespace SourceGit.Models public string New { get; set; } = string.Empty; } - public class SubmoduleRevision - { - public Commit Commit { get; set; } = null; - public string FullMessage { get; set; } = string.Empty; - } - public class SubmoduleDiff { - public SubmoduleRevision Old { get; set; } = null; - public SubmoduleRevision New { get; set; } = null; + public RevisionSubmodule Old { get; set; } = null; + public RevisionSubmodule New { get; set; } = null; } public class DiffResult diff --git a/src/Models/RevisionFile.cs b/src/Models/RevisionFile.cs index b918e2e7..59868fcc 100644 --- a/src/Models/RevisionFile.cs +++ b/src/Models/RevisionFile.cs @@ -25,6 +25,7 @@ namespace SourceGit.Models public class RevisionSubmodule { - public string SHA { get; set; } + public Commit Commit { get; set; } = null; + public string FullMessage { get; set; } = string.Empty; } } diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 4ab521b8..04fe8129 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -201,7 +201,34 @@ namespace SourceGit.ViewModels }); break; case Models.ObjectType.Commit: - ViewRevisionFileContent = new Models.RevisionSubmodule() { SHA = file.SHA }; + Task.Run(() => + { + var submoduleRoot = Path.Combine(_repo, file.Path); + var commit = new Commands.QuerySingleCommit(submoduleRoot, file.SHA).Result(); + if (commit != null) + { + var body = new Commands.QueryCommitFullMessage(submoduleRoot, file.SHA).Result(); + Dispatcher.UIThread.Invoke(() => + { + ViewRevisionFileContent = new Models.RevisionSubmodule() + { + Commit = commit, + FullMessage = body, + }; + }); + } + else + { + Dispatcher.UIThread.Invoke(() => + { + ViewRevisionFileContent = new Models.RevisionSubmodule() + { + Commit = new Models.Commit() { SHA = file.SHA }, + FullMessage = string.Empty, + }; + }); + } + }); break; default: ViewRevisionFileContent = null; diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 8a240752..fc57788f 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -210,16 +210,16 @@ namespace SourceGit.ViewModels return size > 0 ? (new Bitmap(stream), size) : (null, size); } - private Models.SubmoduleRevision QuerySubmoduleRevision(string repo, string sha) + private Models.RevisionSubmodule QuerySubmoduleRevision(string repo, string sha) { var commit = new Commands.QuerySingleCommit(repo, sha).Result(); if (commit != null) { var body = new Commands.QueryCommitFullMessage(repo, sha).Result(); - return new Models.SubmoduleRevision() { Commit = commit, FullMessage = body }; + return new Models.RevisionSubmodule() { Commit = commit, FullMessage = body }; } - return new Models.SubmoduleRevision() + return new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = sha }, FullMessage = string.Empty, diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 027a2bb3..273bf6ce 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -151,7 +151,7 @@ - + diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml index a3fb85e7..da48651b 100644 --- a/src/Views/RevisionFiles.axaml +++ b/src/Views/RevisionFiles.axaml @@ -78,10 +78,9 @@ - + - - + From a717e48a29188096f3cd28fb53814e8967d44c3a Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 19 Jun 2024 15:29:40 +0800 Subject: [PATCH 24/68] enhance: change the default action to deal with local changes on pull/checkout/create new branch to `Do Nothing` (#185) --- src/Models/DealWithLocalChanges.cs | 2 +- src/ViewModels/Checkout.cs | 2 +- src/ViewModels/CreateBranch.cs | 2 +- src/ViewModels/Pull.cs | 2 +- src/Views/Checkout.axaml | 10 +++++----- src/Views/CreateBranch.axaml | 10 +++++----- src/Views/Pull.axaml | 8 ++++---- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Models/DealWithLocalChanges.cs b/src/Models/DealWithLocalChanges.cs index 82609642..f308a90c 100644 --- a/src/Models/DealWithLocalChanges.cs +++ b/src/Models/DealWithLocalChanges.cs @@ -2,8 +2,8 @@ { public enum DealWithLocalChanges { + DoNothing, StashAndReaply, Discard, - DoNothing, } } diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs index ade6d2f8..7f7d1c00 100644 --- a/src/ViewModels/Checkout.cs +++ b/src/ViewModels/Checkout.cs @@ -78,6 +78,6 @@ namespace SourceGit.ViewModels } private readonly Repository _repo = null; - private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.StashAndReaply; + private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.DoNothing; } } diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs index aa3dc165..e80dec15 100644 --- a/src/ViewModels/CreateBranch.cs +++ b/src/ViewModels/CreateBranch.cs @@ -138,6 +138,6 @@ namespace SourceGit.ViewModels private readonly Repository _repo = null; private string _name = null; private readonly string _baseOnRevision = null; - private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.StashAndReaply; + private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.DoNothing; } } diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs index b4f06a9d..44287138 100644 --- a/src/ViewModels/Pull.cs +++ b/src/ViewModels/Pull.cs @@ -162,6 +162,6 @@ namespace SourceGit.ViewModels private Models.Remote _selectedRemote = null; private List _remoteBranches = null; private Models.Branch _selectedBranch = null; - private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.StashAndReaply; + private Models.DealWithLocalChanges _preAction = Models.DealWithLocalChanges.DoNothing; } } diff --git a/src/Views/Checkout.axaml b/src/Views/Checkout.axaml index 501b3de0..02ed64b7 100644 --- a/src/Views/Checkout.axaml +++ b/src/Views/Checkout.axaml @@ -31,18 +31,18 @@ - + + - diff --git a/src/Views/CreateBranch.axaml b/src/Views/CreateBranch.axaml index 007ae537..a6565807 100644 --- a/src/Views/CreateBranch.axaml +++ b/src/Views/CreateBranch.axaml @@ -65,18 +65,18 @@ - + + - + - Date: Wed, 19 Jun 2024 15:36:49 +0800 Subject: [PATCH 25/68] enhance: remember the last selection of `Use rebase instead of merge` on pull for each repository (#185) --- src/ViewModels/Pull.cs | 6 +++--- src/ViewModels/Repository.cs | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs index 44287138..657f52a6 100644 --- a/src/ViewModels/Pull.cs +++ b/src/ViewModels/Pull.cs @@ -55,9 +55,9 @@ namespace SourceGit.ViewModels public bool UseRebase { - get; - set; - } = true; + get => _repo.PreferRebaseInsteadOfMerge; + set => _repo.PreferRebaseInsteadOfMerge = value; + } public Pull(Repository repo, Models.Branch specifiedRemoteBranch) { diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 6dc6fb9e..ee6cd223 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -39,6 +39,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _gitDir, value); } + public bool PreferRebaseInsteadOfMerge + { + get; + set; + } = true; + public AvaloniaList Filters { get; From 3dc32c87a8296feb209428336a351d9adc9956b7 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 19 Jun 2024 16:14:41 +0800 Subject: [PATCH 26/68] ux: change the primary button theme --- README.md | 2 -- src/Resources/Styles.axaml | 11 ++++++----- src/Resources/Themes.axaml | 6 ------ 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4e7f87b3..d84335ed 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,6 @@ This app supports open repository in external tools listed in the table below. | Color.Border2 | Border color used in visual lines, like seperators, Rectange, etc. | | Color.FlatButton.Background | Flat button background color, like `Cancel`, `Commit & Push` button | | Color.FlatButton.BackgroundHovered | Flat button background color when hovered, like `Cancel` button | -| Color.FlatButton.PrimaryBackground | Primary flat button background color, like `Ok`, `Commit` button | -| Color.FlatButton.PrimaryBackgroundHovered | Primary flat button background color when hovered, like `Ok`, `Commit` button | | Color.FG1 | Primary foreground color for all text elements | | Color.FG2 | Secondary foreground color for all text elements | | Color.Diff.EmptyBG | Background color used in empty lines in diff viewer | diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index fdf3ec3e..ca15f55f 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -405,7 +405,8 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0,0,0,4 + 0 + 0 + 8 + 16 + 16 + + + + + + + + + + + + diff --git a/src/Views/ImageDiffView.axaml.cs b/src/Views/ImageDiffView.axaml.cs new file mode 100644 index 00000000..ddb6968c --- /dev/null +++ b/src/Views/ImageDiffView.axaml.cs @@ -0,0 +1,294 @@ +using System; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Styling; + +namespace SourceGit.Views +{ + public class ImageContainer : ContentControl + { + protected override Type StyleKeyOverride => typeof(ContentControl); + + public override void Render(DrawingContext context) + { + if (_bgBrush == null) + { + var maskBrush = new SolidColorBrush(ActualThemeVariant == ThemeVariant.Dark ? 0xFF404040 : 0xFFBBBBBB); + var bg = new DrawingGroup() + { + Children = + { + new GeometryDrawing() { Brush = maskBrush, Geometry = new RectangleGeometry(new Rect(0, 0, 12, 12)) }, + new GeometryDrawing() { Brush = maskBrush, Geometry = new RectangleGeometry(new Rect(12, 12, 12, 12)) }, + } + }; + + _bgBrush = new DrawingBrush(bg) + { + AlignmentX = AlignmentX.Left, + AlignmentY = AlignmentY.Top, + DestinationRect = new RelativeRect(new Size(24, 24), RelativeUnit.Absolute), + Stretch = Stretch.None, + TileMode = TileMode.Tile, + }; + } + + context.FillRectangle(_bgBrush, new Rect(Bounds.Size)); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property.Name == "ActualThemeVariant") + { + _bgBrush = null; + InvalidateVisual(); + } + } + + private DrawingBrush _bgBrush = null; + } + + public class ImagesSwipeControl : Control + { + public static readonly StyledProperty AlphaProperty = + AvaloniaProperty.Register(nameof(Alpha), 0.5); + + public double Alpha + { + get => GetValue(AlphaProperty); + set => SetValue(AlphaProperty, value); + } + + public static readonly StyledProperty OldImageProperty = + AvaloniaProperty.Register(nameof(OldImage), null); + + public Bitmap OldImage + { + get => GetValue(OldImageProperty); + set => SetValue(OldImageProperty, value); + } + + public static readonly StyledProperty NewImageProperty = + AvaloniaProperty.Register(nameof(NewImage), null); + + public Bitmap NewImage + { + get => GetValue(NewImageProperty); + set => SetValue(NewImageProperty, value); + } + + static ImagesSwipeControl() + { + AffectsMeasure(OldImageProperty, NewImageProperty); + AffectsRender(AlphaProperty); + } + + public override void Render(DrawingContext context) + { + var alpha = Alpha; + var w = Bounds.Width; + var h = Bounds.Height; + var x = w * alpha; + var left = OldImage; + if (left != null && alpha > 0) + { + var src = new Rect(0, 0, left.Size.Width * alpha, left.Size.Height); + var dst = new Rect(0, 0, x, h); + context.DrawImage(left, src, dst); + } + + var right = NewImage; + if (right != null && alpha < 1) + { + var src = new Rect(right.Size.Width * alpha, 0, right.Size.Width * (1 - alpha), right.Size.Height); + var dst = new Rect(x, 0, w - x, h); + context.DrawImage(right, src, dst); + } + + context.DrawLine(new Pen(Brushes.DarkGreen, 2), new Point(x, 0), new Point(x, Bounds.Height)); + } + + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + base.OnPointerPressed(e); + + var p = e.GetPosition(this); + var hitbox = new Rect(Math.Max(Bounds.Width * Alpha - 2, 0), 0, 4, Bounds.Height); + var pointer = e.GetCurrentPoint(this); + if (pointer.Properties.IsLeftButtonPressed && hitbox.Contains(p)) + { + _pressedOnSlider = true; + Cursor = new Cursor(StandardCursorType.SizeWestEast); + e.Pointer.Capture(this); + e.Handled = true; + } + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + _pressedOnSlider = false; + } + + protected override void OnPointerMoved(PointerEventArgs e) + { + var w = Bounds.Width; + var p = e.GetPosition(this); + + if (_pressedOnSlider) + { + SetCurrentValue(AlphaProperty, Math.Clamp(p.X, 0, w) / w); + } + else + { + var hitbox = new Rect(Math.Max(w * Alpha - 2, 0), 0, 4, Bounds.Height); + if (hitbox.Contains(p)) + { + if (!_lastInSlider) + { + _lastInSlider = true; + Cursor = new Cursor(StandardCursorType.SizeWestEast); + } + } + else + { + if (_lastInSlider) + { + _lastInSlider = false; + Cursor = null; + } + } + } + } + + protected override Size MeasureOverride(Size availableSize) + { + var left = OldImage; + var right = NewImage; + + if (left == null) + return right == null ? availableSize : GetDesiredSize(right.Size, availableSize); + + if (right == null) + return GetDesiredSize(left.Size, availableSize); + + var ls = GetDesiredSize(left.Size, availableSize); + var rs = GetDesiredSize(right.Size, availableSize); + return ls.Width > rs.Width ? ls : rs; + } + + private Size GetDesiredSize(Size img, Size available) + { + var w = available.Width; + var h = available.Height; + + var sw = available.Width / img.Width; + var sh = available.Height / img.Height; + var scale = Math.Min(sw, sh); + + return new Size(scale * img.Width, scale * img.Height); + } + + private bool _pressedOnSlider = false; + private bool _lastInSlider = false; + } + + public class ImageBlendControl : Control + { + public static readonly StyledProperty AlphaProperty = + AvaloniaProperty.Register(nameof(Alpha), 1.0); + + public double Alpha + { + get => GetValue(AlphaProperty); + set => SetValue(AlphaProperty, value); + } + + public static readonly StyledProperty OldImageProperty = + AvaloniaProperty.Register(nameof(OldImage), null); + + public Bitmap OldImage + { + get => GetValue(OldImageProperty); + set => SetValue(OldImageProperty, value); + } + + public static readonly StyledProperty NewImageProperty = + AvaloniaProperty.Register(nameof(NewImage), null); + + public Bitmap NewImage + { + get => GetValue(NewImageProperty); + set => SetValue(NewImageProperty, value); + } + + static ImageBlendControl() + { + AffectsMeasure(OldImageProperty, NewImageProperty); + AffectsRender(AlphaProperty); + } + + public override void Render(DrawingContext context) + { + var rect = new Rect(0, 0, Bounds.Width, Bounds.Height); + var alpha = Alpha; + var left = OldImage; + if (left != null && alpha < 1) + { + var state = context.PushOpacity(1- alpha); + context.DrawImage(left, rect); + state.Dispose(); + } + + var right = NewImage; + if (right != null && alpha > 0) + { + var state = context.PushOpacity(alpha); + context.DrawImage(right, rect); + state.Dispose(); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + var left = OldImage; + var right = NewImage; + + if (left == null) + return right == null ? availableSize : GetDesiredSize(right.Size, availableSize); + + if (right == null) + return GetDesiredSize(left.Size, availableSize); + + var ls = GetDesiredSize(left.Size, availableSize); + var rs = GetDesiredSize(right.Size, availableSize); + return ls.Width > rs.Width ? ls : rs; + } + + private Size GetDesiredSize(Size img, Size available) + { + var w = available.Width; + var h = available.Height; + + var sw = available.Width / img.Width; + var sh = available.Height / img.Height; + var scale = Math.Min(sw, sh); + + return new Size(scale * img.Width, scale * img.Height); + } + } + + public partial class ImageDiffView : UserControl + { + public ImageDiffView() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/PopupRunningStatus.axaml b/src/Views/PopupRunningStatus.axaml index 5cc9a4bd..c1451a1e 100644 --- a/src/Views/PopupRunningStatus.axaml +++ b/src/Views/PopupRunningStatus.axaml @@ -18,7 +18,7 @@ diff --git a/src/Views/Welcome.axaml b/src/Views/Welcome.axaml index ceccc0ce..fb74205f 100644 --- a/src/Views/Welcome.axaml +++ b/src/Views/Welcome.axaml @@ -117,7 +117,7 @@ Classes="italic" Margin="0,0,8,0" HorizontalAlignment="Center" VerticalAlignment="Center" - FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Decrease}}" + FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}" Text="{DynamicResource Text.Welcome.DragDropTip}" Foreground="{DynamicResource Brush.FG2}"/> From 16b6e9065625e4049f220cb25c41cf07eaadef2e Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 21 Jun 2024 18:23:32 +0800 Subject: [PATCH 48/68] fix: configuration of `gpg.ssh.program` was cleared on Preference windows closing. --- src/Views/Preference.axaml.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Views/Preference.axaml.cs b/src/Views/Preference.axaml.cs index bb91bc5b..16d7b3cd 100644 --- a/src/Views/Preference.axaml.cs +++ b/src/Views/Preference.axaml.cs @@ -189,7 +189,9 @@ namespace SourceGit.Views SetIfChanged(config, "commit.gpgsign", EnableGPGCommitSigning ? "true" : "false"); SetIfChanged(config, "tag.gpgsign", EnableGPGTagSigning ? "true" : "false"); SetIfChanged(config, "gpg.format", GPGFormat.Value); - SetIfChanged(config, $"gpg.{GPGFormat.Value}.program", GPGFormat.Value != "ssh" ? GPGExecutableFile : null); + + if (!GPGFormat.Value.Equals("ssh", StringComparison.Ordinal)) + SetIfChanged(config, $"gpg.{GPGFormat.Value}.program", GPGExecutableFile); Close(); } From 1f0c4be6258aee0c11a9ae045c1e9362919ed3d5 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 21 Jun 2024 18:38:32 +0800 Subject: [PATCH 49/68] enhance: update GPG program path after type changed --- src/Views/Preference.axaml.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Views/Preference.axaml.cs b/src/Views/Preference.axaml.cs index 16d7b3cd..c70dad85 100644 --- a/src/Views/Preference.axaml.cs +++ b/src/Views/Preference.axaml.cs @@ -174,6 +174,20 @@ namespace SourceGit.Views GitVersion = ver; } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == GPGFormatProperty) + { + var config = new Commands.Config(null).ListAll(); + if (GPGFormat.Value == "opengpg" && config.TryGetValue("gpg.program", out var opengpg)) + GPGExecutableFile = opengpg; + else if (config.TryGetValue($"gpg.{GPGFormat.Value}.program", out var gpgProgram)) + GPGExecutableFile = gpgProgram; + } + } + private void BeginMoveWindow(object sender, PointerPressedEventArgs e) { BeginMoveDrag(e); From e1cdbae0ab392aecdd83b4ba703b635bd93a1994 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 21 Jun 2024 18:49:17 +0800 Subject: [PATCH 50/68] fix: wrong percentage for both old and new --- src/Views/ImageDiffView.axaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Views/ImageDiffView.axaml b/src/Views/ImageDiffView.axaml index 9987a523..f62dcb1f 100644 --- a/src/Views/ImageDiffView.axaml +++ b/src/Views/ImageDiffView.axaml @@ -132,7 +132,7 @@ @@ -159,7 +159,7 @@ From 2a85f26754e69693445b7d51d1b143f7c20faa89 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 21 Jun 2024 23:02:28 +0800 Subject: [PATCH 51/68] fix: `openpgp` typo --- src/Views/Preference.axaml.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Views/Preference.axaml.cs b/src/Views/Preference.axaml.cs index c70dad85..0bc477f3 100644 --- a/src/Views/Preference.axaml.cs +++ b/src/Views/Preference.axaml.cs @@ -162,8 +162,8 @@ namespace SourceGit.Views if (config.TryGetValue("gpg.format", out var gpgFormat)) GPGFormat = Models.GPGFormat.Supported.Find(x => x.Value == gpgFormat) ?? Models.GPGFormat.Supported[0]; - if (GPGFormat.Value == "opengpg" && config.TryGetValue("gpg.program", out var opengpg)) - GPGExecutableFile = opengpg; + if (GPGFormat.Value == "openpgp" && config.TryGetValue("gpg.program", out var openpgp)) + GPGExecutableFile = openpgp; else if (config.TryGetValue($"gpg.{GPGFormat.Value}.program", out var gpgProgram)) GPGExecutableFile = gpgProgram; @@ -181,8 +181,8 @@ namespace SourceGit.Views if (change.Property == GPGFormatProperty) { var config = new Commands.Config(null).ListAll(); - if (GPGFormat.Value == "opengpg" && config.TryGetValue("gpg.program", out var opengpg)) - GPGExecutableFile = opengpg; + if (GPGFormat.Value == "openpgp" && config.TryGetValue("gpg.program", out var openpgp)) + GPGExecutableFile = openpgp; else if (config.TryGetValue($"gpg.{GPGFormat.Value}.program", out var gpgProgram)) GPGExecutableFile = gpgProgram; } From 73e450c6846e517f08b782bafdbfdec219f51133 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 21 Jun 2024 23:07:11 +0800 Subject: [PATCH 52/68] enhance: new image blend algorithm --- src/Views/ImageDiffView.axaml.cs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Views/ImageDiffView.axaml.cs b/src/Views/ImageDiffView.axaml.cs index ddb6968c..786c588c 100644 --- a/src/Views/ImageDiffView.axaml.cs +++ b/src/Views/ImageDiffView.axaml.cs @@ -239,19 +239,27 @@ namespace SourceGit.Views var rect = new Rect(0, 0, Bounds.Width, Bounds.Height); var alpha = Alpha; var left = OldImage; - if (left != null && alpha < 1) - { - var state = context.PushOpacity(1- alpha); - context.DrawImage(left, rect); - state.Dispose(); - } - var right = NewImage; - if (right != null && alpha > 0) + + var drawLeft = left != null && alpha < 1.0; + var drawRight = right != null && alpha > 0; + if (drawLeft) { - var state = context.PushOpacity(alpha); - context.DrawImage(right, rect); - state.Dispose(); + using (context.PushRenderOptions(new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source })) + using (context.PushOpacity(1 - alpha)) + context.DrawImage(left, rect); + + if (drawRight) + { + using (context.PushRenderOptions(new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Plus })) + using (context.PushOpacity(alpha)) + context.DrawImage(right, rect); + } + } + else if (drawRight) + { + using (context.PushOpacity(alpha)) + context.DrawImage(right, rect); } } From d23740d9e8fc7324cd7203f770539cdcbe1a24a0 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 21 Jun 2024 23:18:46 +0800 Subject: [PATCH 53/68] ux: tab item font size for image diff view --- src/Views/ImageDiffView.axaml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Views/ImageDiffView.axaml b/src/Views/ImageDiffView.axaml index f62dcb1f..1f46b27e 100644 --- a/src/Views/ImageDiffView.axaml +++ b/src/Views/ImageDiffView.axaml @@ -16,7 +16,11 @@ - + + + + + @@ -59,8 +63,12 @@ - - + + + + + + @@ -94,7 +102,11 @@ - + + + + + From 7a5279f7170eebf0ed25b6e6454de209d0ed09ae Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 21 Jun 2024 23:52:40 +0800 Subject: [PATCH 54/68] ux: hide if image not exists --- src/Views/ImageDiffView.axaml | 72 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/Views/ImageDiffView.axaml b/src/Views/ImageDiffView.axaml index 1f46b27e..06cbf27d 100644 --- a/src/Views/ImageDiffView.axaml +++ b/src/Views/ImageDiffView.axaml @@ -20,48 +20,52 @@ - - - - - - - - - - + + + + + + - - - - - - + + + + + + + + + + + + - + + + + + + + - - - - + + + + - - - - - - - - - - - + + + + + + + - - + + From 209f51da9e15c07cce8cf3995009c257f9047e8a Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 22 Jun 2024 11:25:19 +0800 Subject: [PATCH 55/68] enhance: new image alpha blend implementation --- src/Views/ImageDiffView.axaml | 33 ++++++++-------- src/Views/ImageDiffView.axaml.cs | 65 +++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/Views/ImageDiffView.axaml b/src/Views/ImageDiffView.axaml index 06cbf27d..a7132f3e 100644 --- a/src/Views/ImageDiffView.axaml +++ b/src/Views/ImageDiffView.axaml @@ -21,8 +21,8 @@ - - + + @@ -36,15 +36,16 @@ - + + - + - + @@ -58,9 +59,10 @@ - + + - + @@ -95,11 +97,9 @@ - - - + @@ -133,12 +133,9 @@ - - - + diff --git a/src/Views/ImageDiffView.axaml.cs b/src/Views/ImageDiffView.axaml.cs index 786c588c..73c87f04 100644 --- a/src/Views/ImageDiffView.axaml.cs +++ b/src/Views/ImageDiffView.axaml.cs @@ -9,10 +9,8 @@ using Avalonia.Styling; namespace SourceGit.Views { - public class ImageContainer : ContentControl + public class ImageContainer : Control { - protected override Type StyleKeyOverride => typeof(ContentControl); - public override void Render(DrawingContext context) { if (_bgBrush == null) @@ -54,7 +52,7 @@ namespace SourceGit.Views private DrawingBrush _bgBrush = null; } - public class ImagesSwipeControl : Control + public class ImagesSwipeControl : ImageContainer { public static readonly StyledProperty AlphaProperty = AvaloniaProperty.Register(nameof(Alpha), 0.5); @@ -91,6 +89,8 @@ namespace SourceGit.Views public override void Render(DrawingContext context) { + base.Render(context); + var alpha = Alpha; var w = Bounds.Width; var h = Bounds.Height; @@ -199,7 +199,7 @@ namespace SourceGit.Views private bool _lastInSlider = false; } - public class ImageBlendControl : Control + public class ImageBlendControl : ImageContainer { public static readonly StyledProperty AlphaProperty = AvaloniaProperty.Register(nameof(Alpha), 1.0); @@ -236,30 +236,50 @@ namespace SourceGit.Views public override void Render(DrawingContext context) { + base.Render(context); + var rect = new Rect(0, 0, Bounds.Width, Bounds.Height); var alpha = Alpha; var left = OldImage; var right = NewImage; - var drawLeft = left != null && alpha < 1.0; var drawRight = right != null && alpha > 0; - if (drawLeft) - { - using (context.PushRenderOptions(new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source })) - using (context.PushOpacity(1 - alpha)) - context.DrawImage(left, rect); + var psize = left == null ? right.PixelSize : left.PixelSize; + var dpi = left == null ? right.Dpi : left.Dpi; - if (drawRight) - { - using (context.PushRenderOptions(new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Plus })) - using (context.PushOpacity(alpha)) - context.DrawImage(right, rect); - } - } - else if (drawRight) + using (var rt = new RenderTargetBitmap(psize, dpi)) { - using (context.PushOpacity(alpha)) - context.DrawImage(right, rect); + var rtRect = new Rect(rt.Size); + using (var dc = rt.CreateDrawingContext()) + { + if (drawLeft) + { + if (drawRight) + { + using (dc.PushRenderOptions(RO_SRC)) + using (dc.PushOpacity(1 - alpha)) + dc.DrawImage(left, rtRect); + + using (dc.PushRenderOptions(RO_DST)) + using (dc.PushOpacity(alpha)) + dc.DrawImage(right, rtRect); + } + else + { + using (dc.PushRenderOptions(RO_SRC)) + using (dc.PushOpacity(1 - alpha)) + dc.DrawImage(left, rtRect); + } + } + else if (drawRight) + { + using (dc.PushRenderOptions(RO_SRC)) + using (dc.PushOpacity(alpha)) + dc.DrawImage(right, rtRect); + } + } + + context.DrawImage(rt, rtRect, rect); } } @@ -290,6 +310,9 @@ namespace SourceGit.Views return new Size(scale * img.Width, scale * img.Height); } + + private static readonly RenderOptions RO_SRC = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; + private static readonly RenderOptions RO_DST = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Plus, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; } public partial class ImageDiffView : UserControl From 24590a0e43e5eeb0f5a633c184d31cd671e443ad Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 22 Jun 2024 11:51:58 +0800 Subject: [PATCH 56/68] readme: add image diff view mode tips --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24efee43..b8523562 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Opensource Git GUI client. * Blame * Revision Diffs * Branch Diff - * Image Diff + * Image Diff - Side-By-Side/Swipe/Blend * GitFlow support * Git LFS support From dde5d4a2a85320ed6cc274f8b47704e4de5b35e0 Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 22 Jun 2024 12:07:28 +0800 Subject: [PATCH 57/68] ux: margins for FileHistories content --- src/Views/FileHistories.axaml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Views/FileHistories.axaml b/src/Views/FileHistories.axaml index bcbc02b0..c97582bc 100644 --- a/src/Views/FileHistories.axaml +++ b/src/Views/FileHistories.axaml @@ -56,7 +56,10 @@ + BorderBrush="{DynamicResource Brush.Border0}"/> - - + + @@ -117,7 +120,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/> - + Date: Sat, 22 Jun 2024 14:00:49 +0800 Subject: [PATCH 58/68] enhance: do NOT use render target when there's only added or delete image. --- src/Views/ImageDiffView.axaml.cs | 47 ++++++++++++++------------------ 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Views/ImageDiffView.axaml.cs b/src/Views/ImageDiffView.axaml.cs index 73c87f04..bdeda323 100644 --- a/src/Views/ImageDiffView.axaml.cs +++ b/src/Views/ImageDiffView.axaml.cs @@ -244,42 +244,35 @@ namespace SourceGit.Views var right = NewImage; var drawLeft = left != null && alpha < 1.0; var drawRight = right != null && alpha > 0; - var psize = left == null ? right.PixelSize : left.PixelSize; - var dpi = left == null ? right.Dpi : left.Dpi; - using (var rt = new RenderTargetBitmap(psize, dpi)) + if (drawLeft && drawRight) { - var rtRect = new Rect(rt.Size); - using (var dc = rt.CreateDrawingContext()) + using (var rt = new RenderTargetBitmap(right.PixelSize, right.Dpi)) { - if (drawLeft) - { - if (drawRight) - { - using (dc.PushRenderOptions(RO_SRC)) - using (dc.PushOpacity(1 - alpha)) - dc.DrawImage(left, rtRect); - - using (dc.PushRenderOptions(RO_DST)) - using (dc.PushOpacity(alpha)) - dc.DrawImage(right, rtRect); - } - else - { - using (dc.PushRenderOptions(RO_SRC)) - using (dc.PushOpacity(1 - alpha)) - dc.DrawImage(left, rtRect); - } - } - else if (drawRight) + var rtRect = new Rect(rt.Size); + using (var dc = rt.CreateDrawingContext()) { using (dc.PushRenderOptions(RO_SRC)) + using (dc.PushOpacity(1 - alpha)) + dc.DrawImage(left, rtRect); + + using (dc.PushRenderOptions(RO_DST)) using (dc.PushOpacity(alpha)) dc.DrawImage(right, rtRect); } - } - context.DrawImage(rt, rtRect, rect); + context.DrawImage(rt, rtRect, rect); + } + } + else if (drawLeft) + { + using (context.PushOpacity(1 - alpha)) + context.DrawImage(left, rect); + } + else if (drawRight) + { + using (context.PushOpacity(alpha)) + context.DrawImage(right, rect); } } From 45b93a117e0add3131d5aadd986d434b06275156 Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 22 Jun 2024 18:38:18 +0800 Subject: [PATCH 59/68] ux: foreground for primary button --- src/Resources/Styles.axaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index c044d10c..988175b9 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -455,6 +455,9 @@ +