diff --git a/README.md b/README.md index cb803676..c2dee8c5 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,6 @@ For **macOS** users: * Make sure your mac trusts all software from anywhere. For more information, search `spctl --master-disable`. * Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your mac. * You may need to run `sudo xattr -cr /Applications/SourceGit.app` to make sure the software works. -* You may need to start this app from commandline by using `open -a SourceGit` to introduce the `PATH` environment variable from your shell. For **Linux** users: diff --git a/VERSION b/VERSION index b1766f34..66182729 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.34 \ No newline at end of file +8.35 \ No newline at end of file diff --git a/screenshots/theme_dark.png b/screenshots/theme_dark.png index 5954be00..85e18481 100644 Binary files a/screenshots/theme_dark.png and b/screenshots/theme_dark.png differ diff --git a/screenshots/theme_light.png b/screenshots/theme_light.png index cc68ccb3..2e8cf6fc 100644 Binary files a/screenshots/theme_light.png and b/screenshots/theme_light.png differ diff --git a/src/Commands/Add.cs b/src/Commands/Add.cs index 2251c892..7134ec4a 100644 --- a/src/Commands/Add.cs +++ b/src/Commands/Add.cs @@ -5,27 +5,27 @@ namespace SourceGit.Commands { public class Add : Command { - public Add(string repo, List changes = null) + public Add(string repo, bool includeUntracked) + { + WorkingDirectory = repo; + Context = repo; + Args = includeUntracked ? "add ." : "add -u ."; + } + + public Add(string repo, List changes) { WorkingDirectory = repo; Context = repo; - if (changes == null || changes.Count == 0) + var builder = new StringBuilder(); + builder.Append("add --"); + foreach (var c in changes) { - Args = "add ."; - } - else - { - var builder = new StringBuilder(); - builder.Append("add --"); - foreach (var c in changes) - { - builder.Append(" \""); - builder.Append(c.Path); - builder.Append("\""); - } - Args = builder.ToString(); + builder.Append(" \""); + builder.Append(c.Path); + builder.Append("\""); } + Args = builder.ToString(); } } } diff --git a/src/Commands/CherryPick.cs b/src/Commands/CherryPick.cs index 95b56655..0c82b9fd 100644 --- a/src/Commands/CherryPick.cs +++ b/src/Commands/CherryPick.cs @@ -2,12 +2,19 @@ { public class CherryPick : Command { - public CherryPick(string repo, string commits, bool noCommit) + public CherryPick(string repo, string commits, bool noCommit, bool appendSourceToMessage, string extraParams) { - var mode = noCommit ? "-n" : "--ff"; WorkingDirectory = repo; Context = repo; - Args = $"cherry-pick {mode} {commits}"; + + Args = "cherry-pick "; + if (noCommit) + Args += "-n "; + if (appendSourceToMessage) + Args += "-x "; + if (!string.IsNullOrEmpty(extraParams)) + Args += $"{extraParams} "; + Args += commits; } } } diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 7435dbfa..d774fa09 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -195,15 +195,6 @@ namespace SourceGit.Commands if (OperatingSystem.IsLinux()) start.Environment.Add("LANG", "en_US.UTF-8"); - // Fix sometimes `LSEnvironment` not working on macOS - if (OperatingSystem.IsMacOS()) - { - if (start.Environment.TryGetValue("PATH", out var path)) - start.Environment.Add("PATH", $"/opt/homebrew/bin:/opt/homebrew/sbin:{path}"); - else - start.Environment.Add("PATH", "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"); - } - // Force using this app as git editor. switch (Editor) { diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index e3185781..b9b6e064 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -24,9 +24,9 @@ namespace SourceGit.Commands Context = repo; if (ignoreWhitespace) - Args = $"diff --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}"; + Args = $"diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}"; else - Args = $"diff --ignore-cr-at-eol --unified={unified} {opt}"; + Args = $"diff --patch --ignore-cr-at-eol --unified={unified} {opt}"; } public Models.DiffResult Result() diff --git a/src/Commands/GC.cs b/src/Commands/GC.cs index 61735f53..393b915e 100644 --- a/src/Commands/GC.cs +++ b/src/Commands/GC.cs @@ -10,7 +10,7 @@ namespace SourceGit.Commands WorkingDirectory = repo; Context = repo; TraitErrorAsOutput = true; - Args = "gc --prune"; + Args = "gc --prune=now"; } protected override void OnReadline(string line) diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs index ee82ce88..95f97214 100644 --- a/src/Commands/QueryBranches.cs +++ b/src/Commands/QueryBranches.cs @@ -14,7 +14,7 @@ namespace SourceGit.Commands { WorkingDirectory = repo; Context = repo; - Args = "branch -l --all -v --format=\"%(refname)$%(objectname)$%(HEAD)$%(upstream)$%(upstream:trackshort)\""; + Args = "branch -l --all -v --format=\"%(refname)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\""; } public List Result() @@ -37,7 +37,7 @@ namespace SourceGit.Commands private Models.Branch ParseLine(string line) { - var parts = line.Split('$'); + var parts = line.Split('\0'); if (parts.Length != 5) return null; diff --git a/src/Commands/QueryTags.cs b/src/Commands/QueryTags.cs index 7da324de..3aa20dc2 100644 --- a/src/Commands/QueryTags.cs +++ b/src/Commands/QueryTags.cs @@ -7,9 +7,11 @@ namespace SourceGit.Commands { public QueryTags(string repo) { + _boundary = $"----- BOUNDARY OF TAGS {Guid.NewGuid()} -----"; + Context = repo; WorkingDirectory = repo; - Args = "tag -l --sort=-creatordate --format=\"$%(refname)$%(objectname)$%(*objectname)\""; + Args = $"tag -l --sort=-creatordate --format=\"{_boundary}%(refname)%00%(objectname)%00%(*objectname)%00%(contents:subject)%0a%0a%(contents:body)\""; } public List Result() @@ -19,38 +21,25 @@ namespace SourceGit.Commands if (!rs.IsSuccess) return tags; - var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries); - foreach (var line in lines) + var records = rs.StdOut.Split(_boundary, StringSplitOptions.RemoveEmptyEntries); + foreach (var record in records) { - var tag = ParseLine(line); - if (tag != null) - tags.Add(tag); + var subs = record.Split('\0', StringSplitOptions.None); + if (subs.Length != 4) + continue; + + var message = subs[3].Trim(); + tags.Add(new Models.Tag() + { + Name = subs[0].Substring(10), + SHA = string.IsNullOrEmpty(subs[2]) ? subs[1] : subs[2], + Message = string.IsNullOrEmpty(message) ? null : message, + }); } return tags; } - private Models.Tag ParseLine(string line) - { - var subs = line.Split('$', StringSplitOptions.RemoveEmptyEntries); - if (subs.Length == 2) - { - return new Models.Tag() - { - Name = subs[0].Substring(10), - SHA = subs[1], - }; - } - else if (subs.Length == 3) - { - return new Models.Tag() - { - Name = subs[0].Substring(10), - SHA = subs[2], - }; - } - - return null; - } + private string _boundary = string.Empty; } } diff --git a/src/Commands/Stash.cs b/src/Commands/Stash.cs index 1eb7d03d..fbdb6bd4 100644 --- a/src/Commands/Stash.cs +++ b/src/Commands/Stash.cs @@ -25,7 +25,7 @@ namespace SourceGit.Commands { foreach (var c in changes) pathsBuilder.Append($"\"{c.Path}\" "); - + var paths = pathsBuilder.ToString(); Args = $"stash push --staged -m \"{message}\" -- {paths}"; } @@ -51,11 +51,11 @@ namespace SourceGit.Commands new Add(WorkingDirectory, needAdd).Exec(); needAdd.Clear(); } - + var paths = pathsBuilder.ToString(); Args = $"stash push -m \"{message}\" -- {paths}"; } - + return Exec(); } diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index 9549e4a4..534cf5bb 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -31,18 +31,19 @@ namespace SourceGit.Models public List Decorators { get; set; } = new List(); public bool HasDecorators => Decorators.Count > 0; - public bool IsMerged { get; set; } = false; - public Thickness Margin { get; set; } = new Thickness(0); - public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"); public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"); public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString("yyyy/MM/dd"); + public bool IsMerged { get; set; } = false; public bool IsCommitterVisible => !Author.Equals(Committer) || AuthorTime != CommitterTime; public bool IsCurrentHead => Decorators.Find(x => x.Type is DecoratorType.CurrentBranchHead or DecoratorType.CurrentCommitHead) != null; + public int Color { get; set; } = 0; public double Opacity => IsMerged ? 1 : OpacityForNotMerged; public FontWeight FontWeight => IsCurrentHead ? FontWeight.Bold : FontWeight.Regular; + public Thickness Margin { get; set; } = new Thickness(0); + public IBrush Brush => CommitGraph.Pens[Color].Brush; public void ParseDecorators(string data) { diff --git a/src/Models/CommitGraph.cs b/src/Models/CommitGraph.cs index 3872fcc6..31f5a40e 100644 --- a/src/Models/CommitGraph.cs +++ b/src/Models/CommitGraph.cs @@ -186,6 +186,7 @@ namespace SourceGit.Models // Margins & merge state (used by Views.Histories). commit.IsMerged = isMerged; commit.Margin = new Thickness(Math.Max(offsetX, maxOffsetOld) + halfWidth + 2, 0, 0, 0); + commit.Color = dotColor; } // Deal with curves haven't ended yet. @@ -326,14 +327,15 @@ namespace SourceGit.Models private static readonly List s_defaultPenColors = [ Colors.Orange, Colors.ForestGreen, - Colors.Gold, - Colors.Magenta, - Colors.Red, Colors.Gray, Colors.Turquoise, Colors.Olive, + Colors.Magenta, + Colors.Red, Colors.Khaki, Colors.Lime, + Colors.RoyalBlue, + Colors.Teal, ]; } } diff --git a/src/Models/CommitTemplate.cs b/src/Models/CommitTemplate.cs index 57a88508..135d8ac9 100644 --- a/src/Models/CommitTemplate.cs +++ b/src/Models/CommitTemplate.cs @@ -55,7 +55,7 @@ namespace SourceGit.Models { var count = Math.Min(int.Parse(countStr.Substring(1)), changes.Count); for (int j = 0; j < count; j++) - paths.Add(changes[i].Path); + paths.Add(changes[j].Path); if (count < changes.Count) more = $" and {changes.Count - count} other files"; diff --git a/src/Models/ConventionalCommitType.cs b/src/Models/ConventionalCommitType.cs new file mode 100644 index 00000000..4fb61d87 --- /dev/null +++ b/src/Models/ConventionalCommitType.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace SourceGit.Models +{ + public class ConventionalCommitType + { + public string Type { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + + public static readonly List Supported = new List() + { + new ConventionalCommitType("feat", "Adding a new feature"), + new ConventionalCommitType("fix", "Fixing a bug"), + new ConventionalCommitType("docs", "Updating documentation"), + new ConventionalCommitType("style", "Elements or code styles without changing the code logic"), + new ConventionalCommitType("test", "Adding or updating tests"), + new ConventionalCommitType("chore", "Making changes to the build process or auxiliary tools and libraries"), + new ConventionalCommitType("revert", "Undoing a previous commit"), + new ConventionalCommitType("refactor", "Restructuring code without changing its external behavior") + }; + + public ConventionalCommitType(string type, string description) + { + Type = type; + Description = description; + } + } +} diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs index 2aa69cb5..dcf30ddc 100644 --- a/src/Models/Remote.cs +++ b/src/Models/Remote.cs @@ -5,9 +5,9 @@ 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)?$")] private static partial Regex REG_SSH2(); diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs index 4fb96efa..f2fd1bc6 100644 --- a/src/Models/RepositorySettings.cs +++ b/src/Models/RepositorySettings.cs @@ -9,7 +9,7 @@ namespace SourceGit.Models get; set; } = string.Empty; - + public DealWithLocalChanges DealWithLocalChangesOnCheckoutBranch { get; diff --git a/src/Models/Tag.cs b/src/Models/Tag.cs index c1a34bfa..2ec9e093 100644 --- a/src/Models/Tag.cs +++ b/src/Models/Tag.cs @@ -4,6 +4,7 @@ { public string Name { get; set; } public string SHA { get; set; } + public string Message { get; set; } public bool IsFiltered { get; set; } } } diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index 6e2293a7..316e509c 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.Versioning; +using System.Text; using Avalonia; @@ -17,6 +18,31 @@ namespace SourceGit.Native { DisableDefaultApplicationMenuItems = true, }); + + { + var startInfo = new ProcessStartInfo(); + startInfo.FileName = "zsh"; + startInfo.Arguments = "--login -c \"echo $PATH\""; + startInfo.UseShellExecute = false; + startInfo.CreateNoWindow = true; + startInfo.RedirectStandardOutput = true; + startInfo.StandardOutputEncoding = Encoding.UTF8; + + try + { + var proc = new Process() { StartInfo = startInfo }; + proc.Start(); + var pathData = proc.StandardOutput.ReadToEnd(); + proc.WaitForExit(); + if (proc.ExitCode == 0) + Environment.SetEnvironmentVariable("PATH", pathData); + proc.Close(); + } + catch + { + // Ignore error. + } + } } public string FindGitExecutable() diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 737b7fef..1a84597e 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -16,6 +16,7 @@ M853 102H171C133 102 102 133 102 171v683C102 891 133 922 171 922h683C891 922 922 891 922 853V171C922 133 891 102 853 102zM390 600l-48 48L205 512l137-137 48 48L301 512l88 88zM465 819l-66-18L559 205l66 18L465 819zm218-171L634 600 723 512l-88-88 48-48L819 512 683 649z M128 854h768v86H128zM390 797c13 13 29 19 48 19s35-6 45-19l291-288c26-22 26-64 0-90L435 83l-61 61L426 192l-272 269c-22 22-22 64 0 90l237 246zm93-544 211 211-32 32H240l243-243zM707 694c0 48 38 86 86 86 48 0 86-38 86-86 0-22-10-45-26-61L794 576l-61 61c-13 13-26 35-26 58z M796 471A292 292 0 00512 256a293 293 0 00-284 215H0v144h228A293 293 0 00512 832a291 291 0 00284-217H1024V471h-228M512 688A146 146 0 01366 544A145 145 0 01512 400c80 0 146 63 146 144A146 146 0 01512 688 + M796 561a5 5 0 014 7l-39 90a5 5 0 004 7h100a5 5 0 014 8l-178 247a5 5 0 01-9-4l32-148a5 5 0 00-5-6h-89a5 5 0 01-4-7l86-191a5 5 0 014-3h88zM731 122a73 73 0 0173 73v318a54 54 0 00-8-1H731V195H244v634h408l-16 73H244a73 73 0 01-73-73V195a73 73 0 0173-73h488zm-219 366v73h-195v-73h195zm146-146v73H317v-73h341z M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z 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 M896 811l-128 0c-23 0-43-19-43-43 0-23 19-43 43-43l107 0c13 0 21-9 21-21L896 107c0-13-9-21-21-21L448 85c-13 0-21 9-21 21l0 21c0 23-19 43-43 43-23 0-43-19-43-43L341 85c0-47 38-85 85-85l469 0c47 0 85 38 85 85l0 640C981 772 943 811 896 811zM683 299l0 640c0 47-38 85-85 85L128 1024c-47 0-85-38-85-85L43 299c0-47 38-85 85-85l469 0C644 213 683 252 683 299zM576 299 149 299c-13 0-21 9-21 21l0 597c0 13 9 21 21 21l427 0c13 0 21-9 21-21L597 320C597 307 589 299 576 299z @@ -50,6 +51,7 @@ 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 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 M30 271l241 0 0-241-241 0 0 241zM392 271l241 0 0-241-241 0 0 241zM753 30l0 241 241 0 0-241-241 0zM30 632l241 0 0-241-241 0 0 241zM392 632l241 0 0-241-241 0 0 241zM753 632l241 0 0-241-241 0 0 241zM30 994l241 0 0-241-241 0 0 241zM392 994l241 0 0-241-241 0 0 241zM753 994l241 0 0-241-241 0 0 241z + M0 512M1024 512M512 0M512 1024M955 323q0 23-16 39l-414 414-78 78q-16 16-39 16t-39-16l-78-78-207-207q-16-16-16-39t16-39l78-78q16-16 39-16t39 16l168 169 375-375q16-16 39-16t39 16l78 78q16 16 16 39z M416 64H768v64h-64v704h64v64H448v-64h64V512H416a224 224 0 1 1 0-448zM576 832h64V128H576v704zM416 128H512v320H416a160 160 0 0 1 0-320z M24 512A488 488 0 01512 24A488 488 0 011000 512A488 488 0 01512 1000A488 488 0 0124 512zm447-325v327L243 619l51 111 300-138V187H471z M832 64h128v278l-128-146V64zm64 448L512 73 128 512H0L448 0h128l448 512h-128zm0 83V1024H640V704c0-35-29-64-64-64h-128a64 64 0 00-64 64v320H128V595l384-424 384 424z @@ -84,8 +86,9 @@ M432 0h160c27 0 48 21 48 48v336h175c36 0 53 43 28 68L539 757c-15 15-40 15-55 0L180 452c-25-25-7-68 28-68H384V48c0-27 21-48 48-48zm592 752v224c0 27-21 48-48 48H48c-27 0-48-21-48-48V752c0-27 21-48 48-48h293l98 98c40 40 105 40 145 0l98-98H976c27 0 48 21 48 48zm-248 176c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40zm128 0c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40z M592 768h-160c-27 0-48-21-48-48V384h-175c-36 0-53-43-28-68L485 11c15-15 40-15 55 0l304 304c25 25 7 68-28 68H640v336c0 27-21 48-48 48zm432-16v224c0 27-21 48-48 48H48c-27 0-48-21-48-48V752c0-27 21-48 48-48h272v16c0 62 50 112 112 112h160c62 0 112-50 112-112v-16h272c27 0 48 21 48 48zm-248 176c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40zm128 0c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40z M277 85a149 149 0 00-43 292v230a32 32 0 0064 0V555h267A160 160 0 00725 395v-12a149 149 0 10-64-5v17a96 96 0 01-96 96H299V383A149 149 0 00277 85zM228 720a32 32 0 00-37-52 150 150 0 00-53 68 32 32 0 1060 23 85 85 0 0130-39zm136-52a32 32 0 00-37 52 86 86 0 0130 39 32 32 0 1060-23 149 149 0 00-53-68zM204 833a32 32 0 10-55 32 149 149 0 0063 58 32 32 0 0028-57 85 85 0 01-36-33zm202 32a32 32 0 00-55-32 85 85 0 01-36 33 32 32 0 0028 57 149 149 0 0063-58z + M854 234a171 171 0 00-171 171v51c13-5 28-7 43-7h35a136 136 0 01136 136v93a198 198 0 01-198 198 101 101 0 01-101-101V405a256 256 0 01256-256h21a21 21 0 0121 21v43a21 21 0 01-21 21h-21zM213 456c13-5 28-7 43-7h35a136 136 0 01136 136v93a198 198 0 01-198 198 101 101 0 01-101-101V405a256 256 0 01256-256h21a21 21 0 0121 21v43a21 21 0 01-21 21h-21a171 171 0 00-171 171v51z m224 154a166 166 0 00-166 166v192a166 166 0 00166 166h64v-76h-64a90 90 0 01-90-90v-192a90 90 0 0190-90h320a90 90 0 0190 90v192a90 90 0 01-90 90h-128v77h128a166 166 0 00166-167v-192a166 166 0 00-166-166h-320zm166 390a90 90 0 0190-90h128v-76h-128a166 166 0 00-166 166v192a166 166 0 00166 166h320a166 166 0 00166-166v-192a166 166 0 00-166-166h-64v77h64a90 90 0 0190 90v192a90 90 0 01-90 90h-320a90 90 0 01-90-90v-192z - M706 302a289 289 0 00-173 44 27 27 0 1029 46 234 234 0 01125-36c23 0 45 3 66 9 93 28 161 114 161 215C914 704 813 805 687 805H337C211 805 110 704 110 580c0-96 61-178 147-210C282 263 379 183 495 183a245 245 0 01210 119z + M512 128M706 302a289 289 0 00-173 44 27 27 0 1029 46 234 234 0 01125-36c23 0 45 3 66 9 93 28 161 114 161 215C914 704 813 805 687 805H337C211 805 110 704 110 580c0-96 61-178 147-210C282 263 379 183 495 183a245 245 0 01210 119z M364 512h67v108h108v67h-108v108h-67v-108h-108v-67h108v-108zm298-64A107 107 0 01768 555C768 614 720 660 660 660h-108v-54h-108v-108h-94v108h-94c4-21 22-47 44-51l-1-12a75 75 0 0171-75a128 128 0 01239-7a106 106 0 0153-14z M115 386l19 33c17 29 44 50 76 60l116 33c34 10 58 41 58 77v80c0 22 12 42 32 52s32 30 32 52v78c0 31 30 54 60 45 32-9 57-35 65-68l6-22c8-34 30-63 61-80l16-9c30-17 48-49 48-83v-17c0-25-10-50-28-68l-8-8c-18-18-42-28-68-28H514c-22 0-44-6-64-17l-69-39c-9-5-15-13-18-22-6-19 2-40 20-49l12-6c13-7 29-8 43-3l46 15c16 5 34-1 44-15 9-14 8-33-2-46l-27-33c-20-24-20-59 1-83l31-37c18-21 20-50 7-73l-5-8c-7-0-14-1-21-1-186 0-343 122-396 290zM928 512c0-74-19-143-53-203L824 330c-31 13-48 48-37 80l34 101c7 21 24 37 45 42l58 15c2-18 4-36 4-55zM0 512a512 512 0 111024 0 512 512 0 11-1024 0z M1024 64v704h-128v128h-128v128h-768v-704h128v-128h128v-128zM64 960h640v-576h-640zM320 128v64h576v512h64v-576zM192 256v64h576v512h64v-576zM432 688L576 832H480L384 736 288 832H192l144-144L192 544h96L384 640l96-96H576z diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 84e93505..7cc9fd63 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -5,6 +5,7 @@ Info Über SourceGit • Erstellt mit + • Grafik gerendert durch © 2024 sourcegit-scm • Texteditor von • Monospace-Schriftarten von @@ -85,8 +86,11 @@ Nichts tun Stashen & wieder anwenden Cherry Pick + Quelle an Commit-Nachricht anhängen Commit(s): Alle Änderungen committen + Hautplinie: + Normalerweise ist es nicht möglich einen Merge zu cherry-picken, da unklar ist welche Seite des Merges die Hauptlinie ist. Diese Option ermöglicht es die Änderungen relativ zum ausgewählten Vorgänger zu wiederholen. Stashes löschen Du versuchst alle Stashes zu löschen. Möchtest du wirklich fortfahren? Remote Repository klonen @@ -314,13 +318,11 @@ Verfolge alle *{0} Dateien Verlauf Wechsle zwischen horizontalem und vertikalem Layout - Wechsle zwischen Kurven- und Konturgraphenmodus AUTOR + AUTOR ZEITPUNKT GRAPH & COMMIT-NACHRICHT SHA COMMIT ZEITPUNKT - DURCHSUCHE SHA/SUBJEKT/AUTOR. DRÜCKE ZUM SUCHEN ENTER, ESC UM ABZUBRECHEN - LÖSCHEN {0} COMMITS AUSGEWÄHLT Tastaturkürzel Referenz GLOBAL @@ -359,10 +361,8 @@ Interaktiver Rebase Ziel Branch: Auf: - Source Git FEHLER INFO - Hauptmenü öffnen Branch mergen Ziel-Branch: Merge Option: @@ -371,9 +371,7 @@ Wähle Vorgänger-Knoten für: Name: Git wurde NICHT konfiguriert. Gehe bitte zuerst in die [Einstellungen] und konfiguriere Git. - BENACHRICHTIGUNG App-Daten Ordner öffnen - ORDNER AUSWÄHLEN Öffne mit... Optional. Neue Seite erstellen @@ -415,6 +413,7 @@ Beim Starten nach Updates suchen Sprache Commit-Historie + Zeige Autor Zeitpunkt anstatt Commit Zeitpunkt Längenvorgabe für Commit-Nachrichten GIT Aktiviere Auto-CRLF @@ -563,6 +562,8 @@ Inklusive nicht-verfolgter Dateien Name: Optional. Name dieses Stashes + Nur gestagte Änderungen + Gestagte und unstagte Änderungen der ausgewähleten Datei(en) werden gestasht!!! Lokale Änderungen stashen Anwenden Entfernen diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 22a1cde2..bfb0ace4 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -83,8 +83,11 @@ Do Nothing Stash & Reapply Cherry Pick + Append source to commit message Commit(s): Commit all changes + Mainline: + Usually you cannot cherry-pick a merge because you do not know which side of the merge should be considered the mainline. This option allows cherry-pick to replay the change relative to the specified parent. Clear Stashes You are trying to clear all stashes. Are you sure to continue? Clone Remote Repository @@ -155,6 +158,13 @@ Workspaces Color Restore tabs on startup + Conventional Commit Helper + Breaking Change: + Closed Issue: + Detail Changes: + Scope: + Short Description: + Type of Change: Copy Copy All Text COPY MESSAGE @@ -312,15 +322,15 @@ Track all *{0} files Histories Switch Horizontal/Vertical Layout - Switch Curve/Polyline Graph Mode AUTHOR AUTHOR TIME GRAPH & SUBJECT SHA COMMIT TIME - SEARCH SHA/SUBJECT/AUTHOR. PRESS ENTER TO SEARCH, ESC TO QUIT - CLEAR SELECTED {0} COMMITS + Holding 'Ctrl' or 'Shift' to select multiple commits. + Holding ⌘ or ⇧ to select multiple commits. + TIPS: Keyboard Shortcuts Reference GLOBAL Cancel current popup @@ -358,10 +368,8 @@ Interactive Rebase Target Branch: On: - Source Git ERROR NOTICE - Open Main Menu Merge Branch Into: Merge Option: @@ -370,9 +378,7 @@ Select parent node for: Name: Git has NOT been configured. Please to go [Preference] and configure it first. - NOTICE Open App Data Dir - SELECT FOLDER Open With... Optional. Create New Page @@ -500,6 +506,7 @@ Clear all Configure this repository CONTINUE + Enable '--reflog' Option Open In File Browser Search Branches/Tags/Submodules FILTERED BY: @@ -592,6 +599,7 @@ Delete Submodule OK Copy Tag Name + Copy Tag Message Delete ${0}$... Push ${0}$... URL: diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 82c7db7e..8e56248d 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -302,13 +302,10 @@ Track all *{0} files Historique Basculer entre dispositions Horizontal/Vertical - Basculer en un graph courbe ou polyligne AUTEUR GRAPHE & SUJET SHA HEURE DE COMMIT - CHERCHER UN SHA/SUJET/AUTEUR. ENTRÉE POUR CHERCHER, ESC POUR QUITTER - EFFACER {0} COMMITS SÉLECTIONNÉS Référence des raccourcis clavier GLOBAL @@ -345,19 +342,15 @@ Interactive Rebase Target Branch: On: - Source Git ERROR NOTICE - Open Main Menu Merge Branch Into: Merge Option: Source Branch: Nom : Git n'a PAS été configuré. Veuillez d'abord le faire dans le menu Préférence. - NOTICE Ouvrir le dossier AppData - SELECT FOLDER Open With... Optional. Créer un nouvel onglet diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 191d19a1..8a970730 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -297,13 +297,10 @@ Rastrear todos os arquivos *{0} Históricos Alternar Layout Horizontal/Vertical - Alternar Modo de Gráfico Curvo/Polilinha AUTOR GRÁFICO & ASSUNTO SHA HORA DO COMMIT - PROCURAR SHA/ASSUNTO/AUTOR. PRESSIONE ENTER PARA PROCURAR, ESC PARA SAIR - LIMPAR SELECIONADO {0} COMMITS Referência de Atalhos de Teclado GLOBAL @@ -340,19 +337,15 @@ Rebase Interativo Ramo Alvo: Em: - Source Git ERRO AVISO - Abrir Menu Principal Mesclar Ramo Para: Opção de Mesclagem: Ramo de Origem: Nome: O Git NÃO foi configurado. Por favor, vá para [Preferências] e configure primeiro. - AVISO Abrir Pasta de Dados do Aplicativo - SELECIONAR PASTA Abrir Com... Opcional. Criar Nova Página diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 273b0415..23ec125f 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -86,8 +86,11 @@ Ничего не делать Отложить и примненить повторно Частичный выбор + Добавить источник для фиксации сообщения Фиксация(и): Фиксировать все изменения. + Основной: + Обычно вы не можете выделить слияние, потому что не знаете, какую сторону слияния следует считать основной. Эта опция позволяет отобразить изменение относительно указанного родительского элемента. Очистить отложенные Вы пытаетесь очистить все отложенные. Вы уверены, что будете продолжать? Клонировать внешнее хранилище @@ -158,6 +161,14 @@ Рабочие пространства Имя Цвет + Восстанавливать вкладки при запуске + Общепринятый помощник по фиксации изменений + Кардинальные изменения: + Закрытая тема: + Детали изменений: + Область: + Коротнкое описание: + Тип изменения: Копировать Копировать весь текст КОПИРОВАТЬ СООБЩЕНИЕ @@ -182,7 +193,7 @@ Рекомендуемый формат: v1.0.0-alpha Выложить на все внешние хранилища после создания Создать новую метку - Добрый: + Вид: Аннотированный Лёгкий Удерживайте Ctrl, чтобы начать непосредственно @@ -293,7 +304,7 @@ Добавить шаблон отслеживания в ХБФ Git Извлечь Извлечь объекты ХБФ - Запустите `git lfs fetch", чтобы загрузить объекты ХБФ Git. При этом рабочая копия не обновляется. + Запустить `git lfs fetch", чтобы загрузить объекты ХБФ Git. При этом рабочая копия не обновляется. Установить перехват ХБФ Git Показать блокировки Нет заблокированных файлов @@ -315,15 +326,15 @@ Отслеживать все *{0} файлов Истории Переключение горизонтального/вертикального расположения - Переключение режима построения кривой/полилинии АВТОР ВРЕМЯ АВТОРА ГРАФ И СУБЪЕКТ SHA ВРЕМЯ ФИКСАЦИИ - ПОИСК SHA/СУБЪЕКТ/АВТОР. НАЖМИТЕ ВВОД ДЛЯ ПОИСКА, ESC ДЛЯ ВЫХОДА - ОЧИСТИТЬ ВЫБРАННЫЕ {0} ФИКСАЦИИ + Удерживайте 'Ctrl' или 'Shift', чтобы выбрать несколько фиксаций. + Удерживайте ⌘ или ⇧, чтобы выбрать несколько фиксаций. + ПОДСКАЗКИ: Ссылка на сочетания клавиш ОБЩЕЕ Отменить текущее всплывающее окно @@ -361,10 +372,8 @@ Интерактивное перемещение Целевая ветка: На: - Source Git ОШИБКА УВЕДОМЛЕНИЕ - Открыть главное меню Слить ветку В: Опции слияния: @@ -373,9 +382,7 @@ Выбрать родительский узел для: Имя: Git НЕ был настроен. Пожалуйста, перейдите в [Настройки] и сначала настройте его. - УВЕДОМЛЕНИЕ Открыть приложение каталогов данных - ВЫБОР КАТАЛОГА Окрыть с... Необязательно. Создать новую страницу @@ -595,6 +602,7 @@ Удалить подмодуль ОК Копировать имя метки + Копировать сообщение с метки Удалить ${0}$... Выложить ${0}$... Сетевой адрес: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index b95a85e8..919e4093 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -86,8 +86,11 @@ 不做处理 贮藏并自动恢复 挑选提交 + 提交信息中追加来源信息 提交列表 : 提交变化 + 对比的父提交 : + 通常你不能对一个合并进行挑选,因为你不知道合并的哪一边应该被视为主线。这个选项指定了作为主线的父提交,允许挑选相对于该提交的修改。 丢弃贮藏确认 您正在丢弃所有的贮藏,一经操作,无法回退,是否继续? 克隆远程仓库 @@ -158,6 +161,13 @@ 工作区 颜色 启动时恢复打开的仓库 + 规范化提交信息生成 + 破坏性更新: + 关闭的ISSUE: + 详细说明: + 模块: + 简述: + 类型: 复制 复制全部文本 复制内容 @@ -315,15 +325,15 @@ 跟踪所有 *{0} 文件 历史记录 切换横向/纵向显示 - 切换曲线/折线显示 作者 修改时间 路线图与主题 提交指纹 提交时间 - 查询提交指纹、信息、作者。回车键开始,ESC键取消 - 清空 已选中 {0} 项提交 + 可以按住 Ctrl 或 Shift 键选择多个提交 + 可以按住 ⌘ 或 ⇧ 键选择多个提交 + 小提示: 快捷键参考 全局快捷键 取消弹出面板 @@ -361,10 +371,8 @@ 交互式变基 目标分支 : 起始提交 : - Source Git 出错了 系统提示 - 主菜单 合并分支 目标分支 : 合并方式 : @@ -373,9 +381,7 @@ 请选择目标分组: 名称 : GIT尚未配置。请打开【偏好设置】配置GIT路径。 - 系统提示 浏览应用数据目录 - 选择文件夹 打开文件... 选填。 新建空白页 @@ -498,6 +504,7 @@ 清空过滤规则 配置本仓库 下一步 + 启用 --reflog 选项 在文件浏览器中打开 快速查找分支/标签/子模块 过滤规则 : @@ -590,6 +597,7 @@ 删除子模块 确 定 复制标签名 + 复制标签信息 删除 ${0}$... 推送 ${0}$... 仓库地址 : diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 2e0fcd3f..bccf4aab 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -86,8 +86,11 @@ 不做處理 擱置變更並自動復原 揀選提交 + 提交資訊中追加來源資訊 提交列表: 提交變更 + 對比的父提交: + 通常您不能對一個合併進行揀選 (cherry-pick),因為您不知道合併的哪一邊應該被視為主線。這個選項指定了作為主線的父提交,允許揀選相對於該提交的修改。 捨棄擱置變更確認 您正在捨棄所有的擱置變更,一經操作便無法復原,是否繼續? 複製 (clone) 遠端存放庫 @@ -158,6 +161,13 @@ 工作區 顏色 啟動時還原上次開啟的存放庫 + 產生約定式提交訊息 + 破壞性變更: + 關閉的 Issue: + 詳細資訊: + 模組: + 簡述: + 類型: 複製 複製全部內容 複製內容 @@ -315,15 +325,15 @@ 追蹤所有 *{0} 檔案 歷史記錄 切換橫向/縱向顯示 - 切換曲線/折線顯示 作者 修改時間 路線圖與訊息標題 提交編號 提交時間 - 搜尋提交編號、訊息、作者。按下 Enter 鍵以搜尋,ESC 鍵取消 - 清空 已選取 {0} 項提交 + 可以按住 Ctrl 或 Shift 鍵選擇多個提交 + 可以按住 ⌘ 或 ⇧ 鍵選擇多個提交 + 小提示: 快速鍵參考 全域快速鍵 取消彈出面板 @@ -361,10 +371,8 @@ 互動式重定基底 目標分支: 起始提交: - Source Git 發生錯誤 系統提示 - 主選單 合併分支 目標分支: 合併方式: @@ -373,9 +381,7 @@ 請選擇目標分組: 名稱: 尚未設定 Git。請開啟 [偏好設定] 以設定 Git 路徑。 - 系統提示 瀏覽程式資料目錄 - 選擇資料夾 開啟檔案... 選填。 新增分頁 @@ -503,6 +509,7 @@ 清空篩選規則 設定本存放庫 下一步 + 啟用 [--reflog] 選項 在檔案瀏覽器中開啟 快速搜尋分支/標籤/子模組 篩選規則: @@ -521,7 +528,7 @@ 提交訊息 提交編號 作者及提交者 - 僅搜尋當前分支 + 僅搜尋目前分支 以樹型結構展示 提交統計 子模組列表 @@ -567,7 +574,7 @@ 擱置變更訊息: 選填,用於命名此擱置變更 僅擱置已暫存的變更 - 選中檔案的所有變更均會被擱置! + 已選取的檔案中的變更均會被擱置! 擱置本機變更 套用 (apply) 刪除 (drop) @@ -595,6 +602,7 @@ 刪除子模組 確 定 複製標籤名稱 + 複製標籤訊息 刪除 ${0}$... 推送 ${0}$... 存放庫網址: diff --git a/src/Resources/Themes.axaml b/src/Resources/Themes.axaml index 5f8dca57..aa0cbbb8 100644 --- a/src/Resources/Themes.axaml +++ b/src/Resources/Themes.axaml @@ -10,12 +10,7 @@ #FFFAFAFA #FFB0CEE8 #FF1F1F1F - #A0A0A0 - White - #008585 - #0C0E21 - #79855f - White + DarkGreen #FF836C2E #FFFFFFFF #FFCFCFCF @@ -42,12 +37,7 @@ #FF1C1C1C #FF8F8F8F #FFDDDDDD - #FF505050 - #FFF8F8F8 - #FFFFB835 - #f4f1de #84c88a - Black #FFFAFAD2 #FF252525 #FF181818 @@ -74,12 +64,7 @@ - - - - - diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj index 036dcb57..12e08130 100644 --- a/src/SourceGit.csproj +++ b/src/SourceGit.csproj @@ -38,14 +38,14 @@ - - - - - + + + + + - + diff --git a/src/ViewModels/CherryPick.cs b/src/ViewModels/CherryPick.cs index 9f8fe882..dde43662 100644 --- a/src/ViewModels/CherryPick.cs +++ b/src/ViewModels/CherryPick.cs @@ -12,6 +12,30 @@ namespace SourceGit.ViewModels private set; } + public bool IsMergeCommit + { + get; + private set; + } + + public List ParentsForMergeCommit + { + get; + private set; + } + + public int MainlineForMergeCommit + { + get; + set; + } + + public bool AppendSourceToMessage + { + get; + set; + } + public bool AutoCommit { get; @@ -22,6 +46,22 @@ namespace SourceGit.ViewModels { _repo = repo; Targets = targets; + IsMergeCommit = false; + ParentsForMergeCommit = []; + MainlineForMergeCommit = 0; + AppendSourceToMessage = true; + AutoCommit = true; + View = new Views.CherryPick() { DataContext = this }; + } + + public CherryPick(Repository repo, Models.Commit merge, List parents) + { + _repo = repo; + Targets = [merge]; + IsMergeCommit = true; + ParentsForMergeCommit = parents; + MainlineForMergeCommit = 0; + AppendSourceToMessage = true; AutoCommit = true; View = new Views.CherryPick() { DataContext = this }; } @@ -33,12 +73,30 @@ namespace SourceGit.ViewModels return Task.Run(() => { - // Get commit SHAs reverted - var builder = new StringBuilder(); - for (int i = Targets.Count - 1; i >= 0; i--) - builder.Append($"{Targets[i].SHA} "); + var succ = false; + if (IsMergeCommit) + { + succ = new Commands.CherryPick( + _repo.FullPath, + Targets[0].SHA, + !AutoCommit, + AppendSourceToMessage, + $"-m {MainlineForMergeCommit + 1}").Exec(); + } + else + { + var builder = new StringBuilder(); + for (int i = Targets.Count - 1; i >= 0; i--) + builder.Append($"{Targets[i].SHA} "); + + succ = new Commands.CherryPick( + _repo.FullPath, + builder.ToString(), + !AutoCommit, + AppendSourceToMessage, + string.Empty).Exec(); + } - var succ = new Commands.CherryPick(_repo.FullPath, builder.ToString(), !AutoCommit).Exec(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/ConventionalCommitMessageBuilder.cs b/src/ViewModels/ConventionalCommitMessageBuilder.cs new file mode 100644 index 00000000..ab48bc30 --- /dev/null +++ b/src/ViewModels/ConventionalCommitMessageBuilder.cs @@ -0,0 +1,112 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class ConventionalCommitMessageBuilder : ObservableValidator + { + [Required(ErrorMessage = "Type of changes can not be null")] + public Models.ConventionalCommitType Type + { + get => _type; + set => SetProperty(ref _type, value, true); + } + + public string Scope + { + get => _scope; + set => SetProperty(ref _scope, value); + } + + [Required(ErrorMessage = "Short description can not be empty")] + public string Description + { + get => _description; + set => SetProperty(ref _description, value, true); + } + + public string Detail + { + get => _detail; + set => SetProperty(ref _detail, value); + } + + public string BreakingChanges + { + get => _breakingChanges; + set => SetProperty(ref _breakingChanges, value); + } + + public string ClosedIssue + { + get => _closedIssue; + set => SetProperty(ref _closedIssue, value); + } + + public ConventionalCommitMessageBuilder(WorkingCopy wc) + { + _wc = wc; + } + + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode")] + public bool Apply() + { + if (HasErrors) + return false; + + ValidateAllProperties(); + if (HasErrors) + return false; + + var builder = new StringBuilder(); + builder.Append(_type.Type); + + if (!string.IsNullOrEmpty(_scope)) + { + builder.Append("("); + builder.Append(_scope); + builder.Append("): "); + } + else + { + builder.Append(": "); + } + + builder.Append(_description); + builder.Append("\n\n"); + + if (!string.IsNullOrEmpty(_detail)) + { + builder.Append(_detail); + builder.Append("\n\n"); + } + + if (!string.IsNullOrEmpty(_breakingChanges)) + { + builder.Append("BREAKING CHANGE: "); + builder.Append(_breakingChanges); + builder.Append("\n\n"); + } + + if (!string.IsNullOrEmpty(_closedIssue)) + { + builder.Append("Closed "); + builder.Append(_closedIssue); + } + + _wc.CommitMessage = builder.ToString(); + return true; + } + + private WorkingCopy _wc = null; + private Models.ConventionalCommitType _type = Models.ConventionalCommitType.Supported[0]; + private string _scope = string.Empty; + private string _description = string.Empty; + private string _detail = string.Empty; + private string _breakingChanges = string.Empty; + private string _closedIssue = string.Empty; + } +} diff --git a/src/ViewModels/Discard.cs b/src/ViewModels/Discard.cs index e6653d02..e5b413db 100644 --- a/src/ViewModels/Discard.cs +++ b/src/ViewModels/Discard.cs @@ -29,7 +29,7 @@ namespace SourceGit.ViewModels set; } = 0; } - + public class Discard : Popup { public object Mode diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 10f4b60a..179f6d97 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -156,7 +156,7 @@ namespace SourceGit.ViewModels if (_repo.SearchResultSelectedCommit == null || _repo.SearchResultSelectedCommit.SHA != commit.SHA) { - _repo.SearchResultSelectedCommit = _repo.SearchedCommits.Find(x => x.SHA == commit.SHA); + _repo.SearchResultSelectedCommit = _repo.SearchedCommits.Find(x => x.SHA == commit.SHA); } AutoSelectedCommit = commit; @@ -424,7 +424,28 @@ namespace SourceGit.ViewModels cherryPick.Click += (_, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new CherryPick(_repo, [commit])); + { + if (commit.Parents.Count <= 1) + { + PopupHost.ShowPopup(new CherryPick(_repo, [commit])); + } + else + { + var parents = new List(); + foreach (var sha in commit.Parents) + { + var parent = _commits.Find(x => x.SHA == sha); + if (parent == null) + parent = new Commands.QuerySingleCommit(_repo.FullPath, sha).Result(); + + if (parent != null) + parents.Add(parent); + } + + PopupHost.ShowPopup(new CherryPick(_repo, commit, parents)); + } + } + e.Handled = true; }; menu.Items.Add(cherryPick); diff --git a/src/ViewModels/Merge.cs b/src/ViewModels/Merge.cs index fb1dccab..b7630101 100644 --- a/src/ViewModels/Merge.cs +++ b/src/ViewModels/Merge.cs @@ -54,7 +54,7 @@ namespace SourceGit.ViewModels return Models.MergeMode.Supported[2]; if (config.Equals("--no-commit", StringComparison.Ordinal) || config.Equals("--no-ff --no-commit", StringComparison.Ordinal)) return Models.MergeMode.Supported[3]; - + return Models.MergeMode.Supported[0]; } diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index 7a635498..5cb0ee4b 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -453,6 +453,13 @@ namespace SourceGit.ViewModels Save(); } + public void AutoRemoveInvalidNode() + { + var changed = RemoveInvalidRepositoriesRecursive(RepositoryNodes); + if (changed) + Save(); + } + public void Save() { if (_isLoading) @@ -567,6 +574,27 @@ namespace SourceGit.ViewModels return false; } + private bool RemoveInvalidRepositoriesRecursive(List collection) + { + bool changed = false; + + for (int i = collection.Count - 1; i >= 0; i--) + { + var node = collection[i]; + if (node.IsInvalid) + { + collection.RemoveAt(i); + changed = true; + } + else if (!node.IsRepository) + { + changed |= RemoveInvalidRepositoriesRecursive(node.SubNodes); + } + } + + return changed; + } + private static Preference _instance = null; private static bool _isLoading = false; diff --git a/src/ViewModels/RenameBranch.cs b/src/ViewModels/RenameBranch.cs index 9b387f9d..04a9b4c4 100644 --- a/src/ViewModels/RenameBranch.cs +++ b/src/ViewModels/RenameBranch.cs @@ -62,7 +62,7 @@ namespace SourceGit.ViewModels _repo.Settings.Filters.Remove(oldName); _repo.Settings.Filters.Add($"refs/heads/{_name}"); } - + _repo.MarkBranchesDirtyManually(); _repo.SetWatcherEnabled(true); }); diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 1a9b9955..dcc40154 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -74,6 +74,16 @@ namespace SourceGit.ViewModels set => SetProperty(ref _selectedView, value); } + public bool EnableReflog + { + get => _enableReflog; + set + { + if (SetProperty(ref _enableReflog, value)) + Task.Run(RefreshCommits); + } + } + public bool EnableFirstParentInHistories { get => _enableFirstParentInHistories; @@ -827,6 +837,8 @@ namespace SourceGit.ViewModels Dispatcher.UIThread.Invoke(() => _histories.IsLoading = true); var limits = $"-{Preference.Instance.MaxHistoryCommits} "; + if (_enableReflog) + limits += "--reflog "; if (_enableFirstParentInHistories) limits += "--first-parent "; @@ -862,7 +874,7 @@ namespace SourceGit.ViewModels { if (_settings.Filters.Count != 0) Dispatcher.UIThread.Invoke(() => _settings.Filters.Clear()); - + limits += "--exclude=refs/stash --all"; } @@ -1829,6 +1841,16 @@ namespace SourceGit.ViewModels ev.Handled = true; }; + var copyMessage = new MenuItem(); + copyMessage.Header = App.Text("TagCM.CopyMessage"); + copyMessage.Icon = App.CreateMenuIcon("Icons.Copy"); + copyMessage.IsEnabled = !string.IsNullOrEmpty(tag.Message); + copyMessage.Click += (_, ev) => + { + App.CopyText(tag.Message); + ev.Handled = true; + }; + var menu = new ContextMenu(); menu.Items.Add(createBranch); menu.Items.Add(new MenuItem() { Header = "-" }); @@ -1838,6 +1860,7 @@ namespace SourceGit.ViewModels menu.Items.Add(archive); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(copy); + menu.Items.Add(copyMessage); return menu; } @@ -2045,7 +2068,31 @@ namespace SourceGit.ViewModels Task.Run(() => { var files = new Commands.QueryCurrentRevisionFiles(_fullpath).Result(); - Dispatcher.UIThread.Invoke(() => _revisionFiles.AddRange(files)); + Dispatcher.UIThread.Invoke(() => + { + if (_searchCommitFilterType != 3) + return; + + _revisionFiles.AddRange(files); + + if (!string.IsNullOrEmpty(_searchCommitFilter) && _searchCommitFilter.Length > 2 && _revisionFiles.Count > 0) + { + var suggestion = new List(); + foreach (var file in _revisionFiles) + { + if (file.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) && file.Length != _searchCommitFilter.Length) + { + suggestion.Add(file); + if (suggestion.Count > 100) + break; + } + } + + SearchCommitFilterSuggestion.Clear(); + SearchCommitFilterSuggestion.AddRange(suggestion); + IsSearchCommitSuggestionOpen = SearchCommitFilterSuggestion.Count > 0; + } + }); }); } } @@ -2091,6 +2138,7 @@ namespace SourceGit.ViewModels private bool _isSearchCommitSuggestionOpen = false; private int _searchCommitFilterType = 2; private bool _onlySearchCommitsInCurrentBranch = false; + private bool _enableReflog = false; private bool _enableFirstParentInHistories = false; private string _searchCommitFilter = string.Empty; private List _searchedCommits = new List(); diff --git a/src/ViewModels/RepositoryNode.cs b/src/ViewModels/RepositoryNode.cs index 3cc98c16..7e121b36 100644 --- a/src/ViewModels/RepositoryNode.cs +++ b/src/ViewModels/RepositoryNode.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Text.Json.Serialization; using CommunityToolkit.Mvvm.ComponentModel; @@ -48,6 +49,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _isVisible, value); } + [JsonIgnore] + public bool IsInvalid + { + get => _isRepository && !Directory.Exists(_id); + } + [JsonIgnore] public int Depth { diff --git a/src/ViewModels/ScanRepositories.cs b/src/ViewModels/ScanRepositories.cs index 6130801d..115edf2d 100644 --- a/src/ViewModels/ScanRepositories.cs +++ b/src/ViewModels/ScanRepositories.cs @@ -56,12 +56,9 @@ namespace SourceGit.ViewModels var group = FindOrCreateGroupRecursive(Preference.Instance.RepositoryNodes, relative); Preference.Instance.FindOrAddNodeByRepositoryPath(f, group, false); } - else - { - // Should not happen. - } } + Preference.Instance.AutoRemoveInvalidNode(); Welcome.Instance.Refresh(); }); diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index e07ac432..1e35d647 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Avalonia.Controls; @@ -150,6 +151,74 @@ namespace SourceGit.ViewModels return menu; } + public ContextMenu MakeContextMenuForChange(Models.Change change) + { + if (change == null) + return null; + + var diffWithMerger = new MenuItem(); + diffWithMerger.Header = App.Text("DiffWithMerger"); + diffWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); + diffWithMerger.Click += (_, ev) => + { + var toolType = Preference.Instance.ExternalMergeToolType; + var toolPath = Preference.Instance.ExternalMergeToolPath; + var opt = new Models.DiffOption($"{_selectedStash.SHA}^", _selectedStash.SHA, change); + + Task.Run(() => Commands.MergeTool.OpenForDiff(_repo.FullPath, toolType, toolPath, opt)); + ev.Handled = true; + }; + + var fullPath = Path.Combine(_repo.FullPath, change.Path); + var explore = new MenuItem(); + explore.Header = App.Text("RevealFile"); + explore.Icon = App.CreateMenuIcon("Icons.Explore"); + explore.IsEnabled = File.Exists(fullPath); + explore.Click += (_, ev) => + { + Native.OS.OpenInFileManager(fullPath, true); + ev.Handled = true; + }; + + var resetToThisRevision = new MenuItem(); + resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision"); + resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout"); + resetToThisRevision.Click += (_, ev) => + { + new Commands.Checkout(_repo.FullPath).FileWithRevision(change.Path, $"{_selectedStash.SHA}"); + ev.Handled = true; + }; + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = App.CreateMenuIcon("Icons.Copy"); + copyPath.Click += (_, ev) => + { + App.CopyText(change.Path); + ev.Handled = true; + }; + + var copyFileName = new MenuItem(); + copyFileName.Header = App.Text("CopyFileName"); + copyFileName.Icon = App.CreateMenuIcon("Icons.Copy"); + copyFileName.Click += (_, e) => + { + App.CopyText(Path.GetFileName(change.Path)); + e.Handled = true; + }; + + var menu = new ContextMenu(); + menu.Items.Add(diffWithMerger); + menu.Items.Add(explore); + menu.Items.Add(new MenuItem { Header = "-" }); + menu.Items.Add(resetToThisRevision); + menu.Items.Add(new MenuItem { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFileName); + + return menu; + } + public void Clear() { if (PopupHost.CanCreatePopup()) diff --git a/src/ViewModels/TagCollection.cs b/src/ViewModels/TagCollection.cs index adc73b26..ecf13fb4 100644 --- a/src/ViewModels/TagCollection.cs +++ b/src/ViewModels/TagCollection.cs @@ -12,6 +12,11 @@ namespace SourceGit.ViewModels public Models.Tag Tag { get; private set; } = null; public List Children { get; private set; } = []; + public object ToolTip + { + get => Tag?.Message; + } + public bool IsFolder { get => Tag == null; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index e4acf8ca..70b4f11f 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -345,7 +345,7 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); if (changes.Count == _unstaged.Count) { - await Task.Run(() => new Commands.Add(_repo.FullPath).Exec()); + await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec()); } else { @@ -1202,7 +1202,7 @@ namespace SourceGit.ViewModels { if (_useAmend) return new Commands.QueryStagedChangesWithAmend(_repo.FullPath).Result(); - + var rs = new List(); foreach (var c in _cached) { @@ -1316,7 +1316,7 @@ namespace SourceGit.ViewModels { var succ = true; if (autoStage && _unstaged.Count > 0) - succ = new Commands.Add(_repo.FullPath).Exec(); + succ = new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec(); if (succ) succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend).Exec(); diff --git a/src/Views/Avatar.cs b/src/Views/Avatar.cs index fbff50a7..71ed204d 100644 --- a/src/Views/Avatar.cs +++ b/src/Views/Avatar.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using Avalonia; @@ -92,8 +93,8 @@ namespace SourceGit.Views if (avatar.User == null) return; - var placeholder = string.IsNullOrWhiteSpace(avatar.User.Name) ? "?" : avatar.User.Name.Substring(0, 1); - var chars = placeholder.ToCharArray(); + var fallback = GetFallbackString(avatar.User.Name); + var chars = fallback.ToCharArray(); var sum = 0; foreach (var c in chars) sum += Math.Abs(c); @@ -105,11 +106,9 @@ namespace SourceGit.Views EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), }; - var fontFamily = avatar.FindResource("Fonts.Monospace") as FontFamily; - var typeface = new Typeface(fontFamily); - + var typeface = new Typeface("fonts:SourceGit#JetBrains Mono"); avatar._fallbackLabel = new FormattedText( - placeholder, + fallback, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, @@ -119,6 +118,23 @@ namespace SourceGit.Views avatar.InvalidateVisual(); } + private static string GetFallbackString(string name) + { + if (string.IsNullOrWhiteSpace(name)) + return "?"; + + var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries); + var chars = new List(); + foreach (var part in parts) + chars.Add(part[0]); + + if (chars.Count >= 2) + return string.Format("{0}{1}", chars[0], chars[^1]); + if (chars.Count == 1) + return string.Format("{0}", chars[0]); + return name.Substring(0, 1); + } + private FormattedText _fallbackLabel = null; private LinearGradientBrush _fallbackBrush = null; } diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml index 6df2dbb8..70c9d8fd 100644 --- a/src/Views/BranchTree.axaml +++ b/src/Views/BranchTree.axaml @@ -33,49 +33,49 @@ ColumnDefinitions="16,*" ToolTip.Tip="{Binding Tooltip}"> - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index 49d15ef0..081160d0 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -52,7 +52,7 @@ namespace SourceGit.Views if (node.Backend is Models.Remote) { - CreateContent(new Thickness(0, 2, 0, 0), "Icons.Remote"); + CreateContent(new Thickness(0, 0, 0, 0), "Icons.Remote"); } else if (node.Backend is Models.Branch branch) { diff --git a/src/Views/ChangeStatusIcon.cs b/src/Views/ChangeStatusIcon.cs index d11a504a..5d34be09 100644 --- a/src/Views/ChangeStatusIcon.cs +++ b/src/Views/ChangeStatusIcon.cs @@ -62,7 +62,7 @@ namespace SourceGit.Views ]; private static readonly string[] INDICATOR = ["?", "±", "T", "+", "−", "➜", "❏", "U", "★"]; - private static readonly string[] TIPS = ["Unknown", "Modified", "Type Changed", "Added", "Deleted", "Renamed", "Copied", "Unmerged", "Untracked" ]; + private static readonly string[] TIPS = ["Unknown", "Modified", "Type Changed", "Added", "Deleted", "Renamed", "Copied", "Unmerged", "Untracked"]; public static readonly StyledProperty IsUnstagedChangeProperty = AvaloniaProperty.Register(nameof(IsUnstagedChange)); @@ -142,7 +142,7 @@ namespace SourceGit.Views ToolTip.SetTip(this, c.IsConflit ? "Conflict" : TIPS[(int)c.WorkTree]); else ToolTip.SetTip(this, TIPS[(int)c.Index]); - + InvalidateVisual(); } } diff --git a/src/Views/CherryPick.axaml b/src/Views/CherryPick.axaml index ba672b13..1ba62ff7 100644 --- a/src/Views/CherryPick.axaml +++ b/src/Views/CherryPick.axaml @@ -12,17 +12,11 @@ - - - - - - + - - + - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 2ded8ac7..623332c4 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -98,16 +98,12 @@ - + UseGraphColor="False"/> diff --git a/src/Views/CommitRefsPresenter.cs b/src/Views/CommitRefsPresenter.cs index ecb2c982..fc3233a5 100644 --- a/src/Views/CommitRefsPresenter.cs +++ b/src/Views/CommitRefsPresenter.cs @@ -14,16 +14,9 @@ namespace SourceGit.Views { public Geometry Icon { get; set; } = null; public FormattedText Label { get; set; } = null; - public IBrush LabelBG { get; set; } = null; - } - - public static readonly StyledProperty> RefsProperty = - AvaloniaProperty.Register>(nameof(Refs)); - - public List Refs - { - get => GetValue(RefsProperty); - set => SetValue(RefsProperty, value); + public IBrush Brush { get; set; } = null; + public bool IsHead { get; set; } = false; + public double Width { get; set; } = 0.0; } public static readonly StyledProperty FontFamilyProperty = @@ -44,58 +37,40 @@ namespace SourceGit.Views set => SetValue(FontSizeProperty, value); } - public static readonly StyledProperty IconBackgroundProperty = - AvaloniaProperty.Register(nameof(IconBackground), Brushes.White); + public static readonly StyledProperty BackgroundProperty = + AvaloniaProperty.Register(nameof(Background), null); - public IBrush IconBackground + public IBrush Background { - get => GetValue(IconBackgroundProperty); - set => SetValue(IconBackgroundProperty, value); + get => GetValue(BackgroundProperty); + set => SetValue(BackgroundProperty, value); } - public static readonly StyledProperty IconForegroundProperty = - AvaloniaProperty.Register(nameof(IconForeground), Brushes.White); + public static readonly StyledProperty ForegroundProperty = + AvaloniaProperty.Register(nameof(Foreground), Brushes.White); - public IBrush IconForeground + public IBrush Foreground { - get => GetValue(IconForegroundProperty); - set => SetValue(IconForegroundProperty, value); + get => GetValue(ForegroundProperty); + set => SetValue(ForegroundProperty, value); } - public static readonly StyledProperty LabelForegroundProperty = - AvaloniaProperty.Register(nameof(LabelForeground), Brushes.White); + public static readonly StyledProperty UseGraphColorProperty = + AvaloniaProperty.Register(nameof(UseGraphColor), false); - public IBrush LabelForeground + public bool UseGraphColor { - get => GetValue(LabelForegroundProperty); - set => SetValue(LabelForegroundProperty, value); + get => GetValue(UseGraphColorProperty); + set => SetValue(UseGraphColorProperty, value); } - public static readonly StyledProperty BranchNameBackgroundProperty = - AvaloniaProperty.Register(nameof(BranchNameBackground), Brushes.White); + public static readonly StyledProperty TagBackgroundProperty = + AvaloniaProperty.Register(nameof(TagBackground), Brushes.White); - public IBrush BranchNameBackground + public IBrush TagBackground { - get => GetValue(BranchNameBackgroundProperty); - set => SetValue(BranchNameBackgroundProperty, value); - } - - public static readonly StyledProperty HeadBranchNameBackgroundProperty = - AvaloniaProperty.Register(nameof(HeadBranchNameBackground), Brushes.White); - - public IBrush HeadBranchNameBackground - { - get => GetValue(HeadBranchNameBackgroundProperty); - set => SetValue(HeadBranchNameBackgroundProperty, value); - } - - public static readonly StyledProperty TagNameBackgroundProperty = - AvaloniaProperty.Register(nameof(TagNameBackground), Brushes.White); - - public IBrush TagNameBackground - { - get => GetValue(TagNameBackgroundProperty); - set => SetValue(TagNameBackgroundProperty, value); + get => GetValue(TagBackgroundProperty); + set => SetValue(TagBackgroundProperty, value); } static CommitRefsPresenter() @@ -103,14 +78,11 @@ namespace SourceGit.Views AffectsMeasure( FontFamilyProperty, FontSizeProperty, - LabelForegroundProperty, - RefsProperty); + ForegroundProperty, + TagBackgroundProperty); AffectsRender( - IconBackgroundProperty, - IconForegroundProperty, - BranchNameBackgroundProperty, - TagNameBackgroundProperty); + BackgroundProperty); } public override void Render(DrawingContext context) @@ -118,39 +90,72 @@ namespace SourceGit.Views if (_items.Count == 0) return; - var iconFG = IconForeground; - var iconBG = IconBackground; - var x = 0.0; - + var useGraphColor = UseGraphColor; + var fg = Foreground; + var bg = Background; + var x = 1.0; foreach (var item in _items) { var iconRect = new RoundedRect(new Rect(x, 0, 16, 16), new CornerRadius(2, 0, 0, 2)); - var labelRect = new RoundedRect(new Rect(x + 16, 0, item.Label.Width + 8, 16), new CornerRadius(0, 2, 2, 0)); + var entireRect = new RoundedRect(new Rect(x, 0, item.Width, 16), new CornerRadius(2)); - context.DrawRectangle(iconBG, null, iconRect); - context.DrawRectangle(item.LabelBG, null, labelRect); - context.DrawText(item.Label, new Point(x + 20, 8.0 - item.Label.Height * 0.5)); + if (item.IsHead) + { + if (useGraphColor) + { + if (bg != null) + context.DrawRectangle(bg, null, entireRect); - using (context.PushTransform(Matrix.CreateTranslation(x + 4, 4))) - context.DrawGeometry(iconFG, null, item.Icon); + using (context.PushOpacity(.6)) + context.DrawRectangle(item.Brush, null, entireRect); + } - x += item.Label.Width + 16 + 8 + 4; + context.DrawText(item.Label, new Point(x + 16, 8.0 - item.Label.Height * 0.5)); + } + else + { + if (bg != null) + context.DrawRectangle(bg, null, entireRect); + + var labelRect = new RoundedRect(new Rect(x + 16, 0, item.Label.Width + 8, 16), new CornerRadius(0, 2, 2, 0)); + using (context.PushOpacity(.2)) + context.DrawRectangle(item.Brush, null, labelRect); + + context.DrawLine(new Pen(item.Brush), new Point(x + 16, 0), new Point(x + 16, 16)); + context.DrawText(item.Label, new Point(x + 20, 8.0 - item.Label.Height * 0.5)); + } + + context.DrawRectangle(null, new Pen(item.Brush), entireRect); + + using (context.PushTransform(Matrix.CreateTranslation(x + 3, 3))) + context.DrawGeometry(fg, null, item.Icon); + + x += item.Width + 4; } } + protected override void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + InvalidateMeasure(); + } + protected override Size MeasureOverride(Size availableSize) { _items.Clear(); - var refs = Refs; + var commit = DataContext as Models.Commit; + if (commit == null) + return new Size(0, 0); + + var refs = commit.Decorators; if (refs != null && refs.Count > 0) { var typeface = new Typeface(FontFamily); var typefaceBold = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Bold); - var labelFG = LabelForeground; - var branchBG = BranchNameBackground; - var headBG = HeadBranchNameBackground; - var tagBG = TagNameBackground; + var fg = Foreground; + var normalBG = UseGraphColor ? commit.Brush : Brushes.Gray; + var tagBG = TagBackground; var labelSize = FontSize; var requiredWidth = 0.0; @@ -164,28 +169,31 @@ namespace SourceGit.Views CultureInfo.CurrentCulture, FlowDirection.LeftToRight, isHead ? typefaceBold : typeface, - labelSize, - labelFG); + isHead ? labelSize + 1 : labelSize, + fg); + + var item = new RenderItem() + { + Label = label, + Brush = normalBG, + IsHead = isHead + }; - var item = new RenderItem() { Label = label }; StreamGeometry geo; switch (decorator.Type) { case Models.DecoratorType.CurrentBranchHead: case Models.DecoratorType.CurrentCommitHead: - item.LabelBG = headBG; - geo = this.FindResource("Icons.Check") as StreamGeometry; + geo = this.FindResource("Icons.Head") as StreamGeometry; break; case Models.DecoratorType.RemoteBranchHead: - item.LabelBG = branchBG; geo = this.FindResource("Icons.Remote") as StreamGeometry; break; case Models.DecoratorType.Tag: - item.LabelBG = tagBG; + item.Brush = tagBG; geo = this.FindResource("Icons.Tag") as StreamGeometry; break; default: - item.LabelBG = branchBG; geo = this.FindResource("Icons.Branch") as StreamGeometry; break; } @@ -193,7 +201,7 @@ namespace SourceGit.Views var drawGeo = geo!.Clone(); var iconBounds = drawGeo.Bounds; var translation = Matrix.CreateTranslation(-(Vector)iconBounds.Position); - var scale = Math.Min(8.0 / iconBounds.Width, 8.0 / iconBounds.Height); + var scale = Math.Min(10.0 / iconBounds.Width, 10.0 / iconBounds.Height); var transform = translation * Matrix.CreateScale(scale, scale); if (drawGeo.Transform == null || drawGeo.Transform.Value == Matrix.Identity) drawGeo.Transform = new MatrixTransform(transform); @@ -201,12 +209,14 @@ namespace SourceGit.Views drawGeo.Transform = new MatrixTransform(drawGeo.Transform.Value * transform); item.Icon = drawGeo; + item.Width = 16 + (isHead ? 0 : 4) + label.Width + 4; _items.Add(item); - requiredWidth += label.Width + 16 /* icon */ + 8 /* label margin */ + 4 /* item right margin */; + + requiredWidth += item.Width + 4; } InvalidateVisual(); - return new Size(requiredWidth, 16); + return new Size(requiredWidth + 2, 16); } InvalidateVisual(); diff --git a/src/Views/ConventionalCommitMessageBuilder.axaml b/src/Views/ConventionalCommitMessageBuilder.axaml new file mode 100644 index 00000000..19174a6d --- /dev/null +++ b/src/Views/ConventionalCommitMessageBuilder.axaml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index 5bdb75f2..b3e52771 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -136,7 +136,7 @@ - + diff --git a/src/Views/StashesPage.axaml.cs b/src/Views/StashesPage.axaml.cs index 13b74ee2..c499f76a 100644 --- a/src/Views/StashesPage.axaml.cs +++ b/src/Views/StashesPage.axaml.cs @@ -18,5 +18,15 @@ namespace SourceGit.Views } e.Handled = true; } + + private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) + { + if (DataContext is ViewModels.StashesPage vm && sender is Grid grid) + { + var menu = vm.MakeContextMenuForChange(grid.DataContext as Models.Change); + grid.OpenContextMenu(menu); + } + e.Handled = true; + } } } diff --git a/src/Views/TagsView.axaml b/src/Views/TagsView.axaml index c0837dac..a30f63de 100644 --- a/src/Views/TagsView.axaml +++ b/src/Views/TagsView.axaml @@ -26,7 +26,8 @@ Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}" Background="Transparent" ContextRequested="OnRowContextRequested" - DoubleTapped="OnDoubleTappedNode"> + DoubleTapped="OnDoubleTappedNode" + ToolTip.Tip="{Binding ToolTip}"> - + - + + + + + - + - + + + + - - + -