diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a38c174..223fe75f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,6 +25,7 @@ jobs: with: version: ${{ needs.version.outputs.version }} publish-packages: + needs: [package, version] name: Publish Packages uses: ./.github/workflows/publish-packages.yml secrets: diff --git a/README.md b/README.md index 06842e37..6916edbc 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ ## Translation Status -[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.58%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.86%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.03%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.45%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-98.87%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) +[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-97.50%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.78%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-95.00%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-95.56%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-96.81%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-97.92%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) ## How to Use @@ -98,6 +98,33 @@ For **macOS** users: For **Linux** users: +* For Debian/Ubuntu based distributions, you can add the `sourcegit` repository by following: + You may need to install curl and/or gpg first, if you're on a very minimal host: + ```shell + apt update && apt install curl gpg -y + ``` + Install the registry signing key: + ```shell + curl -fsSL "https://packages.buildkite.com/sourcegit/sourcegit-deb/gpgkey" | gpg --dearmor -o /etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg + ``` + Configure the source: + ```shell + echo -e "deb [signed-by=/etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg] https://packages.buildkite.com/sourcegit/sourcegit-deb/any/ any main\ndeb-src [signed-by=/etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg] https://packages.buildkite.com/sourcegit/sourcegit-deb/any/ any main" > /etc/apt/sources.list.d/buildkite-sourcegit-sourcegit-deb.list + ``` + Update your local repository and install the package: + ```shell + apt update && apt install sourcegit + ``` +* For RHEL/Fedora based distributions, you can add the `sourcegit` repository by following: + Configure the source: + ```shell + sudo sh -c 'echo -e "[sourcegit-rpm]\nname=sourcegit-rpm\nbaseurl=https://packages.buildkite.com/sourcegit/sourcegit-rpm/rpm_any/rpm_any/\$basearch\nenabled=1\nrepo_gpgcheck=1\ngpgcheck=0\ngpgkey=https://packages.buildkite.com/sourcegit/sourcegit-rpm/gpgkey\npriority=1"' > /etc/yum.repos.d/sourcegit-rpm.repo + ``` + Install the package with this command: + ```shell + sudo dnf install -y sourcegit + ``` +* `Appimage` files can be found on [AppimageHub](https://appimage.github.io/SourceGit/) * `xdg-open` must be installed to support open native file manager. * Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your linux. * Maybe you need to set environment variable `AVALONIA_SCREEN_SCALE_FACTORS`. See https://github.com/AvaloniaUI/Avalonia/wiki/Configuring-X11-per-monitor-DPI. diff --git a/TRANSLATION.md b/TRANSLATION.md index 761edbf6..6e743f14 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -1,37 +1,81 @@ -### de_DE.axaml: 99.58% +### de_DE.axaml: 97.50%
Missing Keys +- Text.BranchCM.MergeMultiBranches +- Text.CommitCM.Merge +- Text.CommitCM.MergeMultiple - Text.CommitDetail.Files.Search - Text.Diff.UseBlockNavigation +- Text.FileCM.ResolveUsing +- Text.Hotkeys.Global.Clone +- Text.InProgress.CherryPick.Head +- Text.InProgress.Merge.Operating +- Text.InProgress.Rebase.StoppedAt +- Text.InProgress.Revert.Head +- Text.Merge.Source +- Text.MergeMultiple +- Text.MergeMultiple.CommitChanges +- Text.MergeMultiple.Strategy +- Text.MergeMultiple.Targets +- Text.Repository.Skip - Text.WorkingCopy.CommitToEdit
-### es_ES.axaml: 99.86% +### es_ES.axaml: 97.78%
Missing Keys +- Text.BranchCM.MergeMultiBranches +- Text.CommitCM.Merge +- Text.CommitCM.MergeMultiple - Text.Diff.UseBlockNavigation +- Text.FileCM.ResolveUsing +- Text.Hotkeys.Global.Clone +- Text.InProgress.CherryPick.Head +- Text.InProgress.Merge.Operating +- Text.InProgress.Rebase.StoppedAt +- Text.InProgress.Revert.Head +- Text.Merge.Source +- Text.MergeMultiple +- Text.MergeMultiple.CommitChanges +- Text.MergeMultiple.Strategy +- Text.MergeMultiple.Targets +- Text.Repository.Skip
-### fr_FR.axaml: 97.03% +### fr_FR.axaml: 95.00%
Missing Keys +- Text.BranchCM.MergeMultiBranches - Text.CherryPick.AppendSourceToMessage - Text.CherryPick.Mainline.Tips - Text.CommitCM.CherryPickMultiple +- Text.CommitCM.Merge +- Text.CommitCM.MergeMultiple - Text.CommitDetail.Files.Search - Text.Diff.UseBlockNavigation - Text.Fetch.Force +- Text.FileCM.ResolveUsing +- Text.Hotkeys.Global.Clone +- Text.InProgress.CherryPick.Head +- Text.InProgress.Merge.Operating +- Text.InProgress.Rebase.StoppedAt +- Text.InProgress.Revert.Head +- Text.Merge.Source +- Text.MergeMultiple +- Text.MergeMultiple.CommitChanges +- Text.MergeMultiple.Strategy +- Text.MergeMultiple.Targets - Text.Preference.Appearance.FontSize - Text.Preference.Appearance.FontSize.Default - Text.Preference.Appearance.FontSize.Editor @@ -44,18 +88,22 @@ - Text.Repository.HistoriesOrder - Text.Repository.HistoriesOrder.ByDate - Text.Repository.HistoriesOrder.Topo +- Text.Repository.Skip - Text.ScanRepositories - Text.SHALinkCM.NavigateTo - Text.WorkingCopy.CommitToEdit
-### it_IT.axaml: 97.45% +### it_IT.axaml: 95.56%
Missing Keys +- Text.BranchCM.MergeMultiBranches +- Text.CommitCM.Merge +- Text.CommitCM.MergeMultiple - Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children - Text.Configure.IssueTracker.AddSampleGitLabMergeRequest @@ -63,6 +111,16 @@ - Text.Configure.OpenAI.Preferred.Tip - Text.Diff.UseBlockNavigation - Text.Fetch.Force +- Text.FileCM.ResolveUsing +- Text.InProgress.CherryPick.Head +- Text.InProgress.Merge.Operating +- Text.InProgress.Rebase.StoppedAt +- Text.InProgress.Revert.Head +- Text.Merge.Source +- Text.MergeMultiple +- Text.MergeMultiple.CommitChanges +- Text.MergeMultiple.Strategy +- Text.MergeMultiple.Targets - Text.Preference.General.ShowChildren - Text.Repository.FilterCommits - Text.Repository.FilterCommits.Default @@ -71,36 +129,66 @@ - Text.Repository.HistoriesOrder - Text.Repository.HistoriesOrder.ByDate - Text.Repository.HistoriesOrder.Topo +- Text.Repository.Skip - Text.SHALinkCM.CopySHA - Text.SHALinkCM.NavigateTo - Text.WorkingCopy.CommitToEdit
-### pt_BR.axaml: 98.87% +### pt_BR.axaml: 96.81%
Missing Keys +- Text.BranchCM.MergeMultiBranches +- Text.CommitCM.Merge +- Text.CommitCM.MergeMultiple - Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children - Text.Diff.UseBlockNavigation - Text.Fetch.Force +- Text.FileCM.ResolveUsing +- Text.Hotkeys.Global.Clone +- Text.InProgress.CherryPick.Head +- Text.InProgress.Merge.Operating +- Text.InProgress.Rebase.StoppedAt +- Text.InProgress.Revert.Head +- Text.Merge.Source +- Text.MergeMultiple +- Text.MergeMultiple.CommitChanges +- Text.MergeMultiple.Strategy +- Text.MergeMultiple.Targets - Text.Preference.General.ShowChildren - Text.Repository.FilterCommits +- Text.Repository.Skip - Text.SHALinkCM.NavigateTo - Text.WorkingCopy.CommitToEdit
-### ru_RU.axaml: 100.00% +### ru_RU.axaml: 97.92%
Missing Keys - +- Text.BranchCM.MergeMultiBranches +- Text.CommitCM.Merge +- Text.CommitCM.MergeMultiple +- Text.FileCM.ResolveUsing +- Text.Hotkeys.Global.Clone +- Text.InProgress.CherryPick.Head +- Text.InProgress.Merge.Operating +- Text.InProgress.Rebase.StoppedAt +- Text.InProgress.Revert.Head +- Text.Merge.Source +- Text.MergeMultiple +- Text.MergeMultiple.CommitChanges +- Text.MergeMultiple.Strategy +- Text.MergeMultiple.Targets +- Text.Repository.Skip
diff --git a/VERSION b/VERSION index d72e21bd..50a1eb5b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.42 \ No newline at end of file +8.43 \ No newline at end of file diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs index 4ec8da82..391aeeb2 100644 --- a/src/Commands/Branch.cs +++ b/src/Commands/Branch.cs @@ -2,6 +2,15 @@ { public static class Branch { + public static string ShowCurrent(string repo) + { + var cmd = new Command(); + cmd.WorkingDirectory = repo; + cmd.Context = repo; + cmd.Args = $"branch --show-current"; + return cmd.ReadToEnd().StdOut.Trim(); + } + public static bool Create(string repo, string name, string basedOn) { var cmd = new Command(); diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 8d304410..96a5b9c9 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -74,7 +74,11 @@ namespace SourceGit.Commands } if (string.IsNullOrEmpty(e.Data)) + { + errs.Add(string.Empty); return; + } + if (TraitErrorAsOutput) OnReadline(e.Data); @@ -89,6 +93,7 @@ namespace SourceGit.Commands return; if (REG_PROGRESS().IsMatch(e.Data)) return; + errs.Add(e.Data); }; @@ -99,12 +104,8 @@ namespace SourceGit.Commands catch (Exception e) { if (RaiseError) - { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(Context, e.Message); - }); - } + Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message)); + return false; } @@ -115,15 +116,15 @@ namespace SourceGit.Commands int exitCode = proc.ExitCode; proc.Close(); - if (!isCancelled && exitCode != 0 && errs.Count > 0) + if (!isCancelled && exitCode != 0) { if (RaiseError) { - Dispatcher.UIThread.Invoke(() => - { - App.RaiseException(Context, string.Join("\n", errs)); - }); + var errMsg = string.Join("\n", errs); + if (!string.IsNullOrWhiteSpace(errMsg)) + Dispatcher.UIThread.Post(() => App.RaiseException(Context, errMsg)); } + return false; } diff --git a/src/Commands/Merge.cs b/src/Commands/Merge.cs index cf2e285f..bd1f3653 100644 --- a/src/Commands/Merge.cs +++ b/src/Commands/Merge.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Text; namespace SourceGit.Commands { @@ -13,6 +15,29 @@ namespace SourceGit.Commands Args = $"merge --progress {source} {mode}"; } + public Merge(string repo, List targets, bool autoCommit, string strategy, Action outputHandler) + { + _outputHandler = outputHandler; + WorkingDirectory = repo; + Context = repo; + TraitErrorAsOutput = true; + + var builder = new StringBuilder(); + builder.Append("merge --progress "); + if (!string.IsNullOrEmpty(strategy)) + builder.Append($"--strategy={strategy} "); + if (!autoCommit) + builder.Append("--no-commit "); + + foreach (var t in targets) + { + builder.Append(t); + builder.Append(' '); + } + + Args = builder.ToString(); + } + protected override void OnReadline(string line) { _outputHandler?.Invoke(line); diff --git a/src/Commands/QueryCommitsWithFullMessage.cs b/src/Commands/QueryCommitsWithFullMessage.cs index 116cb3cd..c15cdbe1 100644 --- a/src/Commands/QueryCommitsWithFullMessage.cs +++ b/src/Commands/QueryCommitsWithFullMessage.cs @@ -52,16 +52,28 @@ namespace SourceGit.Commands _current.Commit.CommitterTime = ulong.Parse(line); break; default: - if (line.Equals(_boundary, StringComparison.Ordinal)) - nextPartIdx = -1; + var boundary = rs.StdOut.IndexOf(_boundary, end + 1); + if (boundary > end) + { + _current.Message = rs.StdOut.Substring(start, boundary - start - 1); + end = boundary + _boundary.Length; + } else - _current.Message += line; + { + _current.Message = rs.StdOut.Substring(start); + end = rs.StdOut.Length - 2; + } + + nextPartIdx = -1; break; } nextPartIdx++; start = end + 1; + if (start >= rs.StdOut.Length - 1) + break; + end = rs.StdOut.IndexOf('\n', start); } diff --git a/src/Models/MergeStrategy.cs b/src/Models/MergeStrategy.cs new file mode 100644 index 00000000..ab1d446b --- /dev/null +++ b/src/Models/MergeStrategy.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace SourceGit.Models +{ + public class MergeStrategy + { + public string Name { get; internal set; } + public string Desc { get; internal set; } + public string Arg { get; internal set; } + + public static List ForMultiple { get; private set; } = [ + new MergeStrategy("Default", "Let Git automatically select a strategy", string.Empty), + new MergeStrategy("Octopus", "Attempt merging multiple heads", "octopus"), + new MergeStrategy("Ours", "Record the merge without modifying the tree", "ours"), + ]; + + public MergeStrategy(string n, string d, string a) + { + Name = n; + Desc = d; + Arg = a; + } + } +} diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs index 2b88c3be..3c452460 100644 --- a/src/Models/Remote.cs +++ b/src/Models/Remote.cs @@ -6,7 +6,7 @@ namespace SourceGit.Models { public partial class Remote { - [GeneratedRegex(@"^https?://([-a-zA-Z0-9:%._\+~#=]+@)?[-a-zA-Z0-9:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}(:[0-9]{1,5})?\b(/[-a-zA-Z0-9()@:%_\+.~#?&=]*)*(\.git)?$")] + [GeneratedRegex(@"^https?://([-a-zA-Z0-9:%._\+~#=]+@)?[-a-zA-Z0-9:%._\+~#=]{1,256}(\.[a-zA-Z0-9()]{1,6})?(:[0-9]{1,5})?\b(/[-a-zA-Z0-9()@:%_\+.~#?&=]+)+(\.git)?$")] private static partial Regex REG_HTTPS(); [GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:[\w\-/~%]+/[\w\-\.%]+(\.git)?$")] private static partial Regex REG_SSH1(); diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 8fa23fb7..3eeb4b41 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -15,9 +15,9 @@ M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z M1280 704c0 141-115 256-256 256H288C129 960 0 831 0 672c0-126 80-232 192-272A327 327 0 01192 384c0-177 143-320 320-320 119 0 222 64 277 160C820 204 857 192 896 192c106 0 192 86 192 192 0 24-5 48-13 69C1192 477 1280 580 1280 704zm-493-128H656V352c0-18-14-32-32-32h-96c-18 0-32 14-32 32v224h-131c-29 0-43 34-23 55l211 211c12 12 33 12 45 0l211-211c20-20 6-55-23-55z 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 - M320 171A21 21 0 00299 192v213c0 49-32 85-61 107 30 22 61 58 61 107V832A21 21 0 00320 853h85v85H320A107 107 0 01213 832V619c0-11-8-26-32-42a157 157 0 00-33-17c-11-4-18-5-20-5v-85c2 0 9-1 20-5a157 157 0 0033-17c24-16 32-32 32-42V192A107 107 0 01320 85h85v85H320zm384 0h-85V85H704A107 107 0 01811 192v213c0 11 8 26 32 42 11 7 22 13 33 17 11 4 18 5 20 5v85c-2 0-9 1-20 5a157 157 0 00-33 17c-24 16-32 31-32 42V832A107 107 0 01704 939h-85v-85H704A21 21 0 00725 832V619c0-49 32-85 61-107-30-22-61-58-61-107V192A21 21 0 00704 171z + M684 736 340 736l0-53 344 1-0 53zM552 565l-213-2 0-53 212 2-0 53zM684 392 340 392l0-53 344 1-0 53zM301 825c-45 0-78-9-100-27-22-18-33-43-33-75v-116c0-22-4-37-12-45-7-9-20-13-40-13v-61c19 0 32-4 40-12 8-9 12-24 12-46v-116c0-32 11-57 33-75 22-18 56-27 100-27h24v61h-24a35 35 0 00-27 12 41 41 0 00-11 29v116c0 35-10 60-31 75a66 66 0 01-31 14c11 2 22 6 31 14 20 17 31 42 31 75v116c0 12 4 22 11 29 7 8 16 12 27 12h24v61h-24zM701 764h24c10 0 19-4 27-12a41 41 0 0011-29v-116c0-33 10-58 31-75 9-7 19-12 31-14a66 66 0 01-31-14c-20-15-31-40-31-75v-116a41 41 0 00-11-29 35 35 0 00-27-12h-24v-61h24c45 0 78 9 100 27 22 18 33 43 33 75v116c0 22 4 37 11 46 8 8 21 12 40 12v61c-19 0-33 4-40 13-7 8-11 23-11 45v116c0 32-11 57-33 75-22 18-55 27-100 27h-24v-61z 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 + M0 512M1024 512M512 0M512 1024M796 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 diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index bb31fa8f..2045a229 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -389,10 +389,10 @@ Verwerfen Initialisiere Repository Pfad: - Cherry-Pick wird durchgeführt. Drücke 'Abbrechen' um den originalen HEAD wiederherzustellen. - Merge request wird durchgeführt. Drücke 'Abbrechen' um den originalen HEAD wiederherzustellen. - Rebase wird durchgeführt. Drücke 'Abbrechen' um den originalen HEAD wiederherzustellen. - Revert wird durchgeführt. Drücke 'Abbrechen' um den originalen HEAD wiederherzustellen. + Cherry-Pick wird durchgeführt. + Merge request wird durchgeführt. + Rebase wird durchgeführt. + Revert wird durchgeführt. Interaktiver Rebase Ziel Branch: Auf: @@ -403,7 +403,6 @@ Branch mergen Ziel-Branch: Merge Option: - Quell-Branch: Bewege Repository Knoten Wähle Vorgänger-Knoten für: Name: @@ -562,7 +561,6 @@ Aktualisiern REMOTES REMOTE HINZUFÜGEN - KONFLIKTE BEHEBEN Commit suchen Dateiname Commit-Nachricht diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 89309bef..65b4dd96 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -58,6 +58,7 @@ Fetch ${0}$ into ${1}$... Git Flow - Finish ${0}$ Merge ${0}$ into ${1}$... + Merge selected {0} branches into current Pull ${0}$ Pull ${0}$ into ${1}$... Push ${0}$ @@ -101,7 +102,7 @@ Repository URL: CLOSE Editor - Cherry-Pick This Commit + Cherry-Pick Commit Cherry-Pick ... Checkout Commit Compare with HEAD @@ -109,14 +110,16 @@ Copy Info Copy SHA Custom Action - Interactive Rebase ${0}$ to Here - Rebase ${0}$ to Here + Interactively Rebase ${0}$ on Here + Merge to ${0}$ + Merge ... + Rebase ${0}$ on Here Reset ${0}$ to Here Revert Commit Reword Save as Patch... Squash into Parent - Squash Child Commits to Here + Squash Children into Here CHANGES Search Changes... FILES @@ -279,6 +282,7 @@ Discard {0} files... Discard Changes in Selected Line(s) Open External Merge Tool + Resolve Using ${0}$ Save as Patch... Stage Stage {0} files @@ -357,6 +361,7 @@ Keyboard Shortcuts Reference GLOBAL Cancel current popup + Clone new repository Close current page Go to previous page Go to next page @@ -388,10 +393,14 @@ Discard Initialize Repository Path: - Cherry-Pick in progress. Press 'Abort' to restore original HEAD. - Merge request in progress. Press 'Abort' to restore original HEAD. - Rebase in progress. Press 'Abort' to restore original HEAD. - Revert in progress. Press 'Abort' to restore original HEAD. + Cherry-Pick in progress. + Processing commit + Merge request in progress. + Operating + Rebase in progress. + Stopped at + Revert in progress. + Reverting commit Interactive Rebase Target Branch: On: @@ -402,7 +411,11 @@ Merge Branch Into: Merge Option: - Source Branch: + Source: + Merge (Multiple) + Commit all changes + Strategy: + Targets: Move Repository Node Select parent node for: Name: @@ -561,7 +574,6 @@ Refresh REMOTES ADD REMOTE - RESOLVE Search Commit File Message @@ -569,6 +581,7 @@ Author & Committer Current Branch Show Tags as Tree + SKIP Statistics SUBMODULES ADD SUBMODULE diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 7c7f8d7c..cf0f05b5 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -392,10 +392,10 @@ Descartar Inicializar Repositorio Ruta: - Cherry-Pick en progreso. Presiona 'Abort' para restaurar el HEAD original. - Merge en progreso. Presiona 'Abort' para restaurar el HEAD original. - Rebase en progreso. Presiona 'Abort' para restaurar el HEAD original. - Revert en progreso. Presiona 'Abort' para restaurar el HEAD original. + Cherry-Pick en progreso. + Merge en progreso. + Rebase en progreso. + Revert en progreso. Rebase Interactivo Rama Objetivo: En: @@ -404,7 +404,6 @@ Merge Rama En: Opción de Merge: - Rama Fuente: Mover Nodo del Repositorio Seleccionar nodo padre para: Nombre: @@ -564,7 +563,6 @@ Refrescar REMOTOS AÑADIR REMOTO - RESOLVER Buscar Commit Archivo Mensaje diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index a9a18be3..20237bb9 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -386,10 +386,10 @@ Rejeter Initialiser le repository Chemin : - Cherry-Pick en cours. Appuyer sur 'Abort' pour restaurer le HEAD d'origine. - Merge request in progress. Appuyer sur 'Abort' pour restaurer le HEAD d'origine. - Rebase in progress. Appuyer sur 'Abort' pour restaurer le HEAD d'origine. - Revert in progress. Appuyer sur 'Abort' pour restaurer le HEAD d'origine. + Cherry-Pick en cours. + Merge request in progress. + Rebase in progress. + Revert in progress. Rebase interactif Branche cible : Sur : @@ -400,7 +400,6 @@ Merger la branche Dans : Option de merge: - Branche source : Déplacer le noeud du repository Sélectionnier le noeud parent pour : Nom : @@ -549,7 +548,6 @@ Rafraîchir DEPOTS DISTANTS AJOUTER DEPOT DISTANT - RESOUDRE Rechercher un commit Fichier Message diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index 2e3acf3e..18754c8e 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -357,6 +357,7 @@ Riferimento Scorciatoie da Tastiera GLOBALE Annulla il popup corrente + Clona una nuova repository Chiudi la pagina corrente Vai alla pagina precedente Vai alla pagina successiva @@ -388,10 +389,10 @@ Scarta Inizializza Repository Percorso: - Cherry-Pick in corso. Premi 'Annulla' per ripristinare l'HEAD originale. - Richiesta di merge in corso. Premi 'Annulla' per ripristinare l'HEAD originale. - Rebase in corso. Premi 'Annulla' per ripristinare l'HEAD originale. - Revert in corso. Premi 'Annulla' per ripristinare l'HEAD originale. + Cherry-Pick in corso. + Richiesta di merge in corso. + Rebase in corso. + Revert in corso. Rebase Interattivo Branch di destinazione: Su: @@ -402,7 +403,6 @@ Unisci Branch In: Opzione di Merge: - Branch Sorgente: Sposta Nodo Repository Seleziona nodo padre per: Nome: @@ -554,7 +554,6 @@ Aggiorna REMOTI AGGIUNGI REMOTO - RISOLVI Cerca Commit File Messaggio diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 4d011b32..d72dd370 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -412,10 +412,10 @@ Descartar Inicializar Repositório Caminho: - Cherry-Pick em andamento. Pressione 'Abort' para restaurar o HEAD original. - Merge em andamento. Pressione 'Abort' para restaurar o HEAD original. - Rebase em andamento. Pressione 'Abort' para restaurar o HEAD original. - Revert em andamento. Pressione 'Abort' para restaurar o HEAD original. + Cherry-Pick em andamento. + Merge em andamento. + Rebase em andamento. + Revert em andamento. Rebase Interativo Ramo Alvo: Em: @@ -426,7 +426,6 @@ Mesclar Ramo Para: Opção de Mesclagem: - Ramo de Origem: Mover nó do repositório Selecionar nó pai para: Nome: @@ -583,7 +582,6 @@ Atualizar REMOTOS ADICIONAR REMOTO - RESOLVER Pesquisar Commit Arquivo Mensagem diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 5b2cf129..76142274 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -392,10 +392,10 @@ Отклонить Инициализировать хранилище Путь: - Выполняется частичный забор. Нажмите «Отказ» для восстановления заголовка. - Выполняет запрос слияния. Нажмите «Отказ» для восстановления заголовка. - Выполняется перенос. Нажмите «Отказ» для восстановления заголовка. - Выполняется возврат. Нажмите «Отказ» для восстановления заголовка. + Выполняется частичный забор. + Выполняет запрос слияния. + Выполняется перенос. + Выполняется возврат. Интерактивное перемещение Целевая ветка: На: @@ -406,7 +406,6 @@ Слить ветку В: Опции слияния: - Исходная ветка: Переместить узел хранилища Выбрать родительский узел для: Имя: @@ -566,7 +565,6 @@ Обновить ВНЕШНИЕ ХРАНИЛИЩА ДОБАВИТЬ ВНЕШНЕЕ ХРАНИЛИЩЕ - РАЗРЕШИТЬ Поиск фиксации Файл Сообщение diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index d6bbad3f..f711cdee 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -61,6 +61,7 @@ 拉取(fetch) ${0}$ 至 ${1}$... GIT工作流 - 完成 ${0}$ 合并 ${0}$ 到 ${1}$... + 合并 {0} 个分支到当前分支 拉回(pull) ${0}$ 拉回(pull) ${0}$ 内容至 ${1}$... 推送(push)${0}$ @@ -113,6 +114,8 @@ 复制提交指纹 自定义操作 交互式变基(rebase -i) ${0}$ 到此处 + 合并(merge)此提交至 ${0}$ + 合并(merge)... 变基(rebase) ${0}$ 到此处 重置(reset) ${0}$ 到此处 回滚此提交 @@ -282,6 +285,7 @@ 放弃 {0} 个文件的更改... 放弃选中的更改 使用外部合并工具打开 + 应用 ${0}$ 另存为补丁... 暂存(add) 暂存(add){0} 个文件 @@ -360,6 +364,7 @@ 快捷键参考 全局快捷键 取消弹出面板 + 克隆远程仓库 关闭当前页面 切换到上一个页面 切换到下一个页面 @@ -391,10 +396,14 @@ 丢弃 初始化新仓库 路径 : - 挑选(Cherry-Pick)操作进行中。点击【终止】回滚到操作前的状态。 - 合并操作进行中。点击【终止】回滚到操作前的状态。 - 变基(Rebase)操作进行中。点击【终止】回滚到操作前的状态。 - 回滚提交操作进行中。点击【终止】回滚到操作前的状态。 + 挑选(Cherry-Pick)操作进行中。 + 正在处理提交 + 合并操作进行中。 + 正在处理 + 变基(Rebase)操作进行中。 + 当前停止于 + 回滚提交操作进行中。 + 正在回滚提交 交互式变基 目标分支 : 起始提交 : @@ -405,7 +414,11 @@ 合并分支 目标分支 : 合并方式 : - 合并分支 : + 合并目标 : + 合并(多目标) + 提交变化 + 合并策略 : + 目标列表 : 调整仓库分组 请选择目标分组: 名称 : @@ -565,7 +578,6 @@ 重新加载 远程列表 添加远程 - 解决冲突 查找提交 文件 提交信息 @@ -573,6 +585,7 @@ 作者及提交者 仅在当前分支中查找 以树型结构展示 + 跳过此提交 提交统计 子模块列表 添加子模块 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 0034c3f4..90444967 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -61,6 +61,7 @@ 提取 (fetch) ${0}$ 到 ${1}$... Git 工作流 - 完成 ${0}$ 合併 ${0}$ 到 ${1}$... + 合併 {0} 個分支到目前分支 拉取 (pull) ${0}$ 拉取 (pull) ${0}$ 內容至 ${1}$... 推送 (push) ${0}$ @@ -113,6 +114,8 @@ 複製提交編號 自訂動作 互動式重定基底 (rebase -i) ${0}$ 到此處 + 合併 (merge) 此提交到 ${0}$ + 合併 (merge)... 重定基底 (rebase) ${0}$ 到此處 重設 (reset) ${0}$ 到此處 復原此提交 @@ -282,6 +285,7 @@ 捨棄已選的 {0} 個檔案變更... 捨棄選取的變更 使用外部合併工具開啟 + 使用 ${0}$ 另存為修補檔 (patch)... 暫存 (add) 暫存 (add) 已選的 {0} 個檔案 @@ -360,6 +364,7 @@ 快速鍵參考 全域快速鍵 取消彈出面板 + 複製 (clone) 遠端存放庫 關閉目前頁面 切換到上一個頁面 切換到下一個頁面 @@ -391,10 +396,14 @@ 捨棄 初始化存放庫 路徑: - 揀選 (cherry-pick) 操作進行中。點選 [中止] 復原到操作前的狀態。 - 合併操作進行中。點選 [中止] 復原到操作前的狀態。 - 重定基底 (rebase) 操作進行中。點選 [中止] 復原到操作前的狀態。 - 復原提交操作進行中。點選 [中止] 復原到操作前的狀態。 + 揀選 (cherry-pick) 操作進行中。 + 正在處理提交 + 合併操作進行中。 + 正在處理 + 重定基底 (rebase) 操作進行中。 + 当前停止于 + 復原提交操作進行中。 + 正在復原提交 互動式重定基底 目標分支: 起始提交: @@ -405,7 +414,11 @@ 合併分支 目標分支: 合併方式: - 合併分支: + 合併目標: + 合併(多目標) + 提交變更 + 合併策略: + 目標列表: 調整存放庫分組 請選擇目標分組: 名稱: @@ -564,7 +577,6 @@ 重新載入 遠端列表 新增遠端 - 解決衝突 搜尋提交 檔案 提交訊息 @@ -572,6 +584,7 @@ 作者及提交者 僅搜尋目前分支 以樹型結構展示 + 跳過此提交 提交統計 子模組列表 新增子模組 diff --git a/src/ViewModels/CherryPick.cs b/src/ViewModels/CherryPick.cs index 19dac059..c3ee07cc 100644 --- a/src/ViewModels/CherryPick.cs +++ b/src/ViewModels/CherryPick.cs @@ -72,10 +72,9 @@ namespace SourceGit.ViewModels return Task.Run(() => { - var succ = false; if (IsMergeCommit) { - succ = new Commands.CherryPick( + new Commands.CherryPick( _repo.FullPath, Targets[0].SHA, !AutoCommit, @@ -84,7 +83,7 @@ namespace SourceGit.ViewModels } else { - succ = new Commands.CherryPick( + new Commands.CherryPick( _repo.FullPath, string.Join(' ', Targets.ConvertAll(c => c.SHA)), !AutoCommit, @@ -93,7 +92,7 @@ namespace SourceGit.ViewModels } CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; + return true; }); } diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs new file mode 100644 index 00000000..41a0a137 --- /dev/null +++ b/src/ViewModels/Conflict.cs @@ -0,0 +1,78 @@ +namespace SourceGit.ViewModels +{ + public class Conflict + { + public object Theirs + { + get; + private set; + } + + public object Mine + { + get; + private set; + } + + public bool IsResolved + { + get; + private set; + } + + public Conflict(Repository repo, WorkingCopy wc, Models.Change change) + { + _wc = wc; + _change = change; + + IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).ReadToEnd().IsSuccess; + + var context = wc.InProgressContext; + if (context is CherryPickInProgress cherryPick) + { + Theirs = cherryPick.Head; + Mine = repo.CurrentBranch; + } + else if (context is RebaseInProgress rebase) + { + Theirs = repo.Branches.Find(x => x.IsLocal && x.Name == rebase.HeadName) ?? + new Models.Branch() + { + IsLocal = true, + Name = rebase.HeadName, + FullName = $"refs/heads/{rebase.HeadName}" + }; + + Mine = rebase.Onto; + } + else if (context is RevertInProgress revert) + { + Theirs = revert.Head; + Mine = repo.CurrentBranch; + } + else if (context is MergeInProgress merge) + { + Theirs = merge.Source; + Mine = repo.CurrentBranch; + } + } + + public void UseTheirs() + { + _wc.UseTheirs([_change]); + } + + public void UseMine() + { + _wc.UseMine([_change]); + } + + public void OpenExternalMergeTool() + { + _wc.UseExternalMergeTool(_change); + } + + private WorkingCopy _wc = null; + private Models.Change _change = null; + } +} diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 18ca2e51..59f420eb 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -228,22 +228,28 @@ namespace SourceGit.ViewModels { var selected = new List(); var canCherryPick = true; + var canMerge = true; + foreach (var item in list.SelectedItems) { if (item is Models.Commit c) { selected.Add(c); - if (c.IsMerged || c.Parents.Count > 1) + if (c.IsMerged) + { + canMerge = false; canCherryPick = false; + } + else if (c.Parents.Count > 1) + { + canCherryPick = false; + } } } // Sort selected commits in order. - selected.Sort((l, r) => - { - return _commits.IndexOf(r) - _commits.IndexOf(l); - }); + selected.Sort((l, r) => _commits.IndexOf(r) - _commits.IndexOf(l)); var multipleMenu = new ContextMenu(); @@ -259,9 +265,25 @@ namespace SourceGit.ViewModels e.Handled = true; }; multipleMenu.Items.Add(cherryPickMultiple); - multipleMenu.Items.Add(new MenuItem() { Header = "-" }); } + if (canMerge) + { + var mergeMultiple = new MenuItem(); + mergeMultiple.Header = App.Text("CommitCM.MergeMultiple"); + mergeMultiple.Icon = App.CreateMenuIcon("Icons.Merge"); + mergeMultiple.Click += (_, e) => + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowPopup(new MergeMultiple(_repo, selected)); + e.Handled = true; + }; + multipleMenu.Items.Add(mergeMultiple); + } + + if (canCherryPick || canMerge) + multipleMenu.Items.Add(new MenuItem() { Header = "-" }); + var saveToPatchMultiple = new MenuItem(); saveToPatchMultiple.Icon = App.CreateMenuIcon("Icons.Diff"); saveToPatchMultiple.Header = App.Text("CommitCM.SaveAsPatch"); @@ -385,24 +407,26 @@ namespace SourceGit.ViewModels }; menu.Items.Add(reset); - var squash = new MenuItem(); - squash.Header = App.Text("CommitCM.SquashCommitsSinceThis"); - squash.Icon = App.CreateMenuIcon("Icons.SquashIntoParent"); - squash.IsVisible = commit.IsMerged; - squash.Click += (_, e) => + if (commit.IsMerged) { - if (_repo.LocalChangesCount > 0) + var squash = new MenuItem(); + squash.Header = App.Text("CommitCM.SquashCommitsSinceThis"); + squash.Icon = App.CreateMenuIcon("Icons.SquashIntoParent"); + squash.Click += (_, e) => { - App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first."); - return; - } + if (_repo.LocalChangesCount > 0) + { + App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first."); + return; + } - if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new Squash(_repo, commit, commit.SHA)); + if (PopupHost.CanCreatePopup()) + PopupHost.ShowPopup(new Squash(_repo, commit, commit.SHA)); - e.Handled = true; - }; - menu.Items.Add(squash); + e.Handled = true; + }; + menu.Items.Add(squash); + } } else { @@ -460,6 +484,21 @@ namespace SourceGit.ViewModels }; menu.Items.Add(rebase); + if (!commit.HasDecorators) + { + var merge = new MenuItem(); + merge.Header = new Views.NameHighlightedTextBlock("CommitCM.Merge", current.Name); + merge.Icon = App.CreateMenuIcon("Icons.Merge"); + merge.Click += (_, e) => + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowPopup(new Merge(_repo, commit, current.Name)); + + e.Handled = true; + }; + menu.Items.Add(merge); + } + var cherryPick = new MenuItem(); cherryPick.Header = App.Text("CommitCM.CherryPick"); cherryPick.Icon = App.CreateMenuIcon("Icons.CherryPick"); @@ -504,27 +543,6 @@ namespace SourceGit.ViewModels e.Handled = true; }; menu.Items.Add(revert); - - var interactiveRebase = new MenuItem(); - interactiveRebase.Header = new Views.NameHighlightedTextBlock("CommitCM.InteractiveRebase", current.Name); - interactiveRebase.Icon = App.CreateMenuIcon("Icons.InteractiveRebase"); - interactiveRebase.IsVisible = current.Head != commit.SHA; - interactiveRebase.Click += (_, e) => - { - if (_repo.LocalChangesCount > 0) - { - App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first."); - return; - } - - App.OpenDialog(new Views.InteractiveRebase() - { - DataContext = new InteractiveRebase(_repo, current, commit) - }); - - e.Handled = true; - }; - menu.Items.Add(interactiveRebase); } if (current.Head != commit.SHA) @@ -543,6 +561,30 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); + if (commit.IsMerged && current.Head != commit.SHA) + { + var interactiveRebase = new MenuItem(); + interactiveRebase.Header = new Views.NameHighlightedTextBlock("CommitCM.InteractiveRebase", current.Name); + interactiveRebase.Icon = App.CreateMenuIcon("Icons.InteractiveRebase"); + interactiveRebase.Click += (_, e) => + { + if (_repo.LocalChangesCount > 0) + { + App.RaiseException(_repo.FullPath, "You have local changes. Please run stash or discard first."); + return; + } + + App.OpenDialog(new Views.InteractiveRebase() + { + DataContext = new InteractiveRebase(_repo, current, commit) + }); + + e.Handled = true; + }; + menu.Items.Add(interactiveRebase); + menu.Items.Add(new MenuItem() { Header = "-" }); + } + if (current.Head != commit.SHA) { var compareWithHead = new MenuItem(); @@ -825,8 +867,13 @@ namespace SourceGit.ViewModels fastForward.IsEnabled = current.TrackStatus.Ahead.Count == 0; fastForward.Click += (_, e) => { + var b = _repo.Branches.Find(x => x.FriendlyName == upstream); + if (b == null) + return; + if (PopupHost.CanCreatePopup()) - PopupHost.ShowAndStartPopup(new Merge(_repo, upstream, current.Name)); + PopupHost.ShowAndStartPopup(new Merge(_repo, b, current.Name)); + e.Handled = true; }; submenu.Items.Add(fastForward); @@ -921,7 +968,7 @@ namespace SourceGit.ViewModels merge.Click += (_, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new Merge(_repo, branch.Name, current.Name)); + PopupHost.ShowPopup(new Merge(_repo, branch, current.Name)); e.Handled = true; }; submenu.Items.Add(merge); @@ -1005,7 +1052,7 @@ namespace SourceGit.ViewModels merge.Click += (_, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new Merge(_repo, name, current.Name)); + PopupHost.ShowPopup(new Merge(_repo, branch, current.Name)); e.Handled = true; }; @@ -1064,7 +1111,7 @@ namespace SourceGit.ViewModels merge.Click += (_, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new Merge(_repo, tag.Name, current.Name)); + PopupHost.ShowPopup(new Merge(_repo, tag, current.Name)); e.Handled = true; }; submenu.Items.Add(merge); diff --git a/src/ViewModels/InProgressContexts.cs b/src/ViewModels/InProgressContexts.cs index f7b85032..78f845f3 100644 --- a/src/ViewModels/InProgressContexts.cs +++ b/src/ViewModels/InProgressContexts.cs @@ -4,31 +4,29 @@ namespace SourceGit.ViewModels { public abstract class InProgressContext { - public string Repository - { - get; - set; - } - - public string Cmd - { - get; - set; - } - public InProgressContext(string repo, string cmd) { - Repository = repo; - Cmd = cmd; + _repo = repo; + _cmd = cmd; } public bool Abort() { return new Commands.Command() { - WorkingDirectory = Repository, - Context = Repository, - Args = $"{Cmd} --abort", + WorkingDirectory = _repo, + Context = _repo, + Args = $"{_cmd} --abort", + }.Exec(); + } + + public virtual bool Skip() + { + return new Commands.Command() + { + WorkingDirectory = _repo, + Context = _repo, + Args = $"{_cmd} --skip", }.Exec(); } @@ -36,32 +34,96 @@ namespace SourceGit.ViewModels { return new Commands.Command() { - WorkingDirectory = Repository, - Context = Repository, + WorkingDirectory = _repo, + Context = _repo, Editor = Commands.Command.EditorType.None, - Args = $"{Cmd} --continue", + Args = $"{_cmd} --continue", }.Exec(); } + + protected string GetFriendlyNameOfCommit(Models.Commit commit) + { + var branchDecorator = commit.Decorators.Find(x => x.Type == Models.DecoratorType.LocalBranchHead || x.Type == Models.DecoratorType.RemoteBranchHead); + if (branchDecorator != null) + return branchDecorator.Name; + + var tagDecorator = commit.Decorators.Find(x => x.Type == Models.DecoratorType.Tag); + if (tagDecorator != null) + return tagDecorator.Name; + + return commit.SHA.Substring(0, 10); + } + + protected string _repo = string.Empty; + protected string _cmd = string.Empty; } public class CherryPickInProgress : InProgressContext { - public CherryPickInProgress(string repo) : base(repo, "cherry-pick") { } + public Models.Commit Head + { + get; + private set; + } + + public string HeadName + { + get => GetFriendlyNameOfCommit(Head); + } + + public CherryPickInProgress(Repository repo) : base(repo.FullPath, "cherry-pick") + { + var headSHA = File.ReadAllText(Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD")).Trim(); + Head = new Commands.QuerySingleCommit(repo.FullPath, headSHA).Result() ?? new Models.Commit() { SHA = headSHA }; + } } public class RebaseInProgress : InProgressContext { + public string HeadName + { + get; + private set; + } + + public string BaseName + { + get => GetFriendlyNameOfCommit(Onto); + } + + public Models.Commit StoppedAt + { + get; + private set; + } + + public Models.Commit Onto + { + get; + private set; + } + public RebaseInProgress(Repository repo) : base(repo.FullPath, "rebase") { _gitDir = repo.GitDir; + + var stoppedSHA = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "stopped-sha")).Trim(); + StoppedAt = new Commands.QuerySingleCommit(repo.FullPath, stoppedSHA).Result() ?? new Models.Commit() { SHA = stoppedSHA }; + + var ontoSHA = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "onto")).Trim(); + Onto = new Commands.QuerySingleCommit(repo.FullPath, ontoSHA).Result() ?? new Models.Commit() { SHA = ontoSHA }; + + HeadName = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "head-name")).Trim(); + if (HeadName.StartsWith("refs/heads/")) + HeadName = HeadName.Substring(11); } public override bool Continue() { var succ = new Commands.Command() { - WorkingDirectory = Repository, - Context = Repository, + WorkingDirectory = _repo, + Context = _repo, Editor = Commands.Command.EditorType.RebaseEditor, Args = $"rebase --continue", }.Exec(); @@ -90,11 +152,49 @@ namespace SourceGit.ViewModels public class RevertInProgress : InProgressContext { - public RevertInProgress(string repo) : base(repo, "revert") { } + public Models.Commit Head + { + get; + private set; + } + + public RevertInProgress(Repository repo) : base(repo.FullPath, "revert") + { + var headSHA = File.ReadAllText(Path.Combine(repo.GitDir, "REVERT_HEAD")).Trim(); + Head = new Commands.QuerySingleCommit(repo.FullPath, headSHA).Result() ?? new Models.Commit() { SHA = headSHA }; + } } public class MergeInProgress : InProgressContext { - public MergeInProgress(string repo) : base(repo, "merge") { } + public string Current + { + get; + private set; + } + + public Models.Commit Source + { + get; + private set; + } + + public string SourceName + { + get => GetFriendlyNameOfCommit(Source); + } + + public MergeInProgress(Repository repo) : base(repo.FullPath, "merge") + { + Current = Commands.Branch.ShowCurrent(repo.FullPath); + + var sourceSHA = File.ReadAllText(Path.Combine(repo.GitDir, "MERGE_HEAD")).Trim(); + Source = new Commands.QuerySingleCommit(repo.FullPath, sourceSHA).Result() ?? new Models.Commit() { SHA = sourceSHA }; + } + + public override bool Skip() + { + return true; + } } } diff --git a/src/ViewModels/Merge.cs b/src/ViewModels/Merge.cs index b7630101..d07ee9b7 100644 --- a/src/ViewModels/Merge.cs +++ b/src/ViewModels/Merge.cs @@ -5,7 +5,7 @@ namespace SourceGit.ViewModels { public class Merge : Popup { - public string Source + public object Source { get; } @@ -21,9 +21,33 @@ namespace SourceGit.ViewModels set; } - public Merge(Repository repo, string source, string into) + public Merge(Repository repo, Models.Branch source, string into) { _repo = repo; + _sourceName = source.FriendlyName; + + Source = source; + Into = into; + SelectedMode = AutoSelectMergeMode(); + View = new Views.Merge() { DataContext = this }; + } + + public Merge(Repository repo, Models.Commit source, string into) + { + _repo = repo; + _sourceName = source.SHA; + + Source = source; + Into = into; + SelectedMode = AutoSelectMergeMode(); + View = new Views.Merge() { DataContext = this }; + } + + public Merge(Repository repo, Models.Tag source, string into) + { + _repo = repo; + _sourceName = source.Name; + Source = source; Into = into; SelectedMode = AutoSelectMergeMode(); @@ -33,11 +57,11 @@ namespace SourceGit.ViewModels public override Task Sure() { _repo.SetWatcherEnabled(false); - ProgressDescription = $"Merging '{Source}' into '{Into}' ..."; + ProgressDescription = $"Merging '{_sourceName}' into '{Into}' ..."; return Task.Run(() => { - var succ = new Commands.Merge(_repo.FullPath, Source, SelectedMode.Arg, SetProgressDescription).Exec(); + var succ = new Commands.Merge(_repo.FullPath, _sourceName, SelectedMode.Arg, SetProgressDescription).Exec(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); @@ -59,5 +83,6 @@ namespace SourceGit.ViewModels } private readonly Repository _repo = null; + private readonly string _sourceName = string.Empty; } } diff --git a/src/ViewModels/MergeMultiple.cs b/src/ViewModels/MergeMultiple.cs new file mode 100644 index 00000000..dd984a15 --- /dev/null +++ b/src/ViewModels/MergeMultiple.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class MergeMultiple : Popup + { + public List Targets + { + get; + private set; + } = []; + + public bool AutoCommit + { + get; + set; + } + + public Models.MergeStrategy Strategy + { + get; + set; + } + + public MergeMultiple(Repository repo, List commits) + { + _repo = repo; + Targets.AddRange(commits); + AutoCommit = true; + Strategy = Models.MergeStrategy.ForMultiple[0]; + View = new Views.MergeMultiple() { DataContext = this }; + } + + public MergeMultiple(Repository repo, List branches) + { + _repo = repo; + Targets.AddRange(branches); + AutoCommit = true; + Strategy = Models.MergeStrategy.ForMultiple[0]; + View = new Views.MergeMultiple() { DataContext = this }; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = "Merge head(s) ..."; + + return Task.Run(() => + { + var succ = new Commands.Merge( + _repo.FullPath, + ConvertTargetToMergeSources(), + AutoCommit, + Strategy.Arg, + SetProgressDescription).Exec(); + + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return succ; + }); + } + + private List ConvertTargetToMergeSources() + { + var ret = new List(); + foreach (var t in Targets) + { + if (t is Models.Branch branch) + { + ret.Add(branch.FriendlyName); + } + else if (t is Models.Commit commit) + { + var d = commit.Decorators.Find(x => + { + return x.Type == Models.DecoratorType.LocalBranchHead || + x.Type == Models.DecoratorType.RemoteBranchHead || + x.Type == Models.DecoratorType.Tag; + }); + + if (d != null) + ret.Add(d.Name); + else + ret.Add(commit.SHA); + } + } + + return ret; + } + + private readonly Repository _repo = null; + } +} diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index a02df359..04f0fa91 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using Avalonia.Collections; @@ -65,8 +66,8 @@ namespace SourceGit.ViewModels get => _defaultFontFamily; set { - var trimmed = value.Trim(); - if (SetProperty(ref _defaultFontFamily, trimmed) && !_isLoading) + var name = FixFontFamilyName(value); + if (SetProperty(ref _defaultFontFamily, name) && !_isLoading) App.SetFonts(_defaultFontFamily, _monospaceFontFamily, _onlyUseMonoFontInEditor); } } @@ -76,8 +77,8 @@ namespace SourceGit.ViewModels get => _monospaceFontFamily; set { - var trimmed = value.Trim(); - if (SetProperty(ref _monospaceFontFamily, trimmed) && !_isLoading) + var name = FixFontFamilyName(value); + if (SetProperty(ref _monospaceFontFamily, name) && !_isLoading) App.SetFonts(_defaultFontFamily, _monospaceFontFamily, _onlyUseMonoFontInEditor); } } @@ -588,6 +589,35 @@ namespace SourceGit.ViewModels return changed; } + private string FixFontFamilyName(string name) + { + var trimmed = name.Trim(); + if (string.IsNullOrEmpty(trimmed)) + return string.Empty; + + var builder = new StringBuilder(); + var lastIsSpace = false; + for (int i = 0; i < trimmed.Length; i++) + { + var c = trimmed[i]; + if (char.IsWhiteSpace(c)) + { + if (lastIsSpace) + continue; + + lastIsSpace = true; + } + else + { + lastIsSpace = false; + } + + builder.Append(c); + } + + return builder.ToString(); + } + private static Preference _instance = null; private static bool _isLoading = false; diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 38855fef..d8d6806c 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -764,10 +764,9 @@ namespace SourceGit.ViewModels _workingCopy?.StashAll(autoStart); } - public void GotoResolve() + public void SkipMerge() { - if (_workingCopy != null) - SelectedViewIndex = 1; + _workingCopy?.SkipMerge(); } public void AbortMerge() @@ -950,6 +949,12 @@ namespace SourceGit.ViewModels PopupHost.ShowPopup(new DeleteMultipleBranches(this, branches, isLocal)); } + public void MergeMultipleBranches(List branches) + { + if (PopupHost.CanCreatePopup()) + PopupHost.ShowPopup(new MergeMultiple(this, branches)); + } + public void CreateNewTag() { if (_currentBranch == null) @@ -1305,8 +1310,13 @@ namespace SourceGit.ViewModels fastForward.IsEnabled = branch.TrackStatus.Ahead.Count == 0; fastForward.Click += (_, e) => { + var b = _branches.Find(x => x.FriendlyName == upstream); + if (b == null) + return; + if (PopupHost.CanCreatePopup()) - PopupHost.ShowAndStartPopup(new Merge(this, upstream, branch.Name)); + PopupHost.ShowAndStartPopup(new Merge(this, b, branch.Name)); + e.Handled = true; }; @@ -1385,7 +1395,7 @@ namespace SourceGit.ViewModels merge.Click += (_, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new Merge(this, branch.Name, _currentBranch.Name)); + PopupHost.ShowPopup(new Merge(this, branch, _currentBranch.Name)); e.Handled = true; }; @@ -1681,7 +1691,7 @@ namespace SourceGit.ViewModels merge.Click += (_, e) => { if (PopupHost.CanCreatePopup()) - PopupHost.ShowPopup(new Merge(this, name, _currentBranch.Name)); + PopupHost.ShowPopup(new Merge(this, branch, _currentBranch.Name)); e.Handled = true; }; diff --git a/src/ViewModels/Revert.cs b/src/ViewModels/Revert.cs index bbe1d9e9..f0eefab1 100644 --- a/src/ViewModels/Revert.cs +++ b/src/ViewModels/Revert.cs @@ -31,9 +31,9 @@ namespace SourceGit.ViewModels return Task.Run(() => { - var succ = new Commands.Revert(_repo.FullPath, Target.SHA, AutoCommit).Exec(); + new Commands.Revert(_repo.FullPath, Target.SHA, AutoCommit).Exec(); CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; + return true; }); } diff --git a/src/ViewModels/Welcome.cs b/src/ViewModels/Welcome.cs index 04ffef87..3645744f 100644 --- a/src/ViewModels/Welcome.cs +++ b/src/ViewModels/Welcome.cs @@ -119,9 +119,9 @@ namespace SourceGit.ViewModels { var defaultCloneDir = Preference.Instance.GitDefaultCloneDir; if (string.IsNullOrEmpty(defaultCloneDir)) - App.RaiseException(PopupHost.Active.GetId(), "The default clone dir haven't been configured!"); + App.RaiseException(PopupHost.Active.GetId(), "The default clone dir hasn't been configured!"); else if (!Directory.Exists(defaultCloneDir)) - App.RaiseException(PopupHost.Active.GetId(), $"The default clone dir '{defaultCloneDir}' is not exists!"); + App.RaiseException(PopupHost.Active.GetId(), $"The default clone dir '{defaultCloneDir}' does not exist!"); else if (PopupHost.CanCreatePopup()) PopupHost.ShowAndStartPopup(new ScanRepositories(defaultCloneDir)); } diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 4a6e5a01..59de9245 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -11,26 +11,6 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public class ConflictContext : ObservableObject - { - public bool IsResolved - { - get => _isResolved; - set => SetProperty(ref _isResolved, value); - } - - public ConflictContext(string repo, Models.Change change) - { - Task.Run(() => - { - var result = new Commands.IsConflictResolved(repo, change).ReadToEnd().IsSuccess; - Dispatcher.UIThread.Post(() => IsResolved = result); - }); - } - - private bool _isResolved = false; - } - public class WorkingCopy : ObservableObject { public bool IncludeUntracked @@ -49,11 +29,7 @@ namespace SourceGit.ViewModels public bool CanCommitWithPush { get => _canCommitWithPush; - set - { - if (SetProperty(ref _canCommitWithPush, value)) - OnPropertyChanged(nameof(IsCommitWithPushVisible)); - } + set => SetProperty(ref _canCommitWithPush, value); } public bool HasUnsolvedConflicts @@ -109,16 +85,10 @@ namespace SourceGit.ViewModels Staged = GetStagedChanges(); SelectedStaged = []; - OnPropertyChanged(nameof(IsCommitWithPushVisible)); } } } - public bool IsCommitWithPushVisible - { - get => !UseAmend && CanCommitWithPush; - } - public List Unstaged { get => _unstaged; @@ -237,13 +207,13 @@ namespace SourceGit.ViewModels var inProgress = null as InProgressContext; if (File.Exists(Path.Combine(_repo.GitDir, "CHERRY_PICK_HEAD"))) - inProgress = new CherryPickInProgress(_repo.FullPath); + inProgress = new CherryPickInProgress(_repo); else if (File.Exists(Path.Combine(_repo.GitDir, "REBASE_HEAD")) && Directory.Exists(Path.Combine(_repo.GitDir, "rebase-merge"))) inProgress = new RebaseInProgress(_repo); else if (File.Exists(Path.Combine(_repo.GitDir, "REVERT_HEAD"))) - inProgress = new RevertInProgress(_repo.FullPath); + inProgress = new RevertInProgress(_repo); else if (File.Exists(Path.Combine(_repo.GitDir, "MERGE_HEAD"))) - inProgress = new MergeInProgress(_repo.FullPath); + inProgress = new MergeInProgress(_repo); HasUnsolvedConflicts = _cached.Find(x => x.IsConflit) != null; InProgressContext = inProgress; @@ -310,13 +280,13 @@ namespace SourceGit.ViewModels var inProgress = null as InProgressContext; if (File.Exists(Path.Combine(_repo.GitDir, "CHERRY_PICK_HEAD"))) - inProgress = new CherryPickInProgress(_repo.FullPath); + inProgress = new CherryPickInProgress(_repo); else if (File.Exists(Path.Combine(_repo.GitDir, "REBASE_HEAD")) && Directory.Exists(Path.Combine(_repo.GitDir, "rebase-merge"))) inProgress = new RebaseInProgress(_repo); else if (File.Exists(Path.Combine(_repo.GitDir, "REVERT_HEAD"))) - inProgress = new RevertInProgress(_repo.FullPath); + inProgress = new RevertInProgress(_repo); else if (File.Exists(Path.Combine(_repo.GitDir, "MERGE_HEAD"))) - inProgress = new MergeInProgress(_repo.FullPath); + inProgress = new MergeInProgress(_repo); InProgressContext = inProgress; @@ -440,6 +410,54 @@ namespace SourceGit.ViewModels } } + public async void UseTheirs(List changes) + { + var files = new List(); + foreach (var change in changes) + { + if (change.IsConflit) + files.Add(change.Path); + } + + _repo.SetWatcherEnabled(false); + var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseTheirs(files)); + if (succ) + { + await Task.Run(() => new Commands.Add(_repo.FullPath, changes).Exec()); + } + _repo.MarkWorkingCopyDirtyManually(); + _repo.SetWatcherEnabled(true); + } + + public async void UseMine(List changes) + { + var files = new List(); + foreach (var change in changes) + { + if (change.IsConflit) + files.Add(change.Path); + } + + _repo.SetWatcherEnabled(false); + var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseMine(files)); + if (succ) + { + await Task.Run(() => new Commands.Add(_repo.FullPath, changes).Exec()); + } + _repo.MarkWorkingCopyDirtyManually(); + _repo.SetWatcherEnabled(true); + } + + public async void UseExternalMergeTool(Models.Change change) + { + var toolType = Preference.Instance.ExternalMergeToolType; + var toolPath = Preference.Instance.ExternalMergeToolPath; + + _repo.SetWatcherEnabled(false); + await Task.Run(() => Commands.MergeTool.OpenForMerge(_repo.FullPath, toolType, toolPath, change.Path)); + _repo.SetWatcherEnabled(true); + } + public void ContinueMerge() { if (_inProgressContext != null) @@ -463,6 +481,29 @@ namespace SourceGit.ViewModels } } + public void SkipMerge() + { + if (_inProgressContext != null) + { + _repo.SetWatcherEnabled(false); + Task.Run(() => + { + var succ = _inProgressContext.Skip(); + Dispatcher.UIThread.Invoke(() => + { + if (succ) + CommitMessage = string.Empty; + + _repo.SetWatcherEnabled(true); + }); + }); + } + else + { + _repo.MarkWorkingCopyDirtyManually(); + } + } + public void AbortMerge() { if (_inProgressContext != null) @@ -569,8 +610,30 @@ namespace SourceGit.ViewModels e.Handled = true; }; + if (_inProgressContext is CherryPickInProgress cherryPick) + { + useTheirs.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", cherryPick.HeadName); + useMine.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + } + else if(_inProgressContext is RebaseInProgress rebase) + { + useTheirs.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", rebase.HeadName); + useMine.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", rebase.BaseName); + } + else if (_inProgressContext is RevertInProgress revert) + { + useTheirs.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", revert.Head.SHA.Substring(0, 10) + " (revert)"); + useMine.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + } + else if (_inProgressContext is MergeInProgress merge) + { + useTheirs.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", merge.SourceName); + useMine.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + } + menu.Items.Add(useTheirs); menu.Items.Add(useMine); + menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(openMerger); menu.Items.Add(new MenuItem() { Header = "-" }); } @@ -892,6 +955,27 @@ namespace SourceGit.ViewModels e.Handled = true; }; + if (_inProgressContext is CherryPickInProgress cherryPick) + { + useTheirs.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", cherryPick.HeadName); + useMine.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + } + else if (_inProgressContext is RebaseInProgress rebase) + { + useTheirs.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", rebase.HeadName); + useMine.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", rebase.BaseName); + } + else if (_inProgressContext is RevertInProgress revert) + { + useTheirs.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", revert.Head.SHA.Substring(0,10) + " (revert)"); + useMine.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + } + else if (_inProgressContext is MergeInProgress merge) + { + useTheirs.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", merge.SourceName); + useMine.Header = new Views.NameHighlightedTextBlock("FileCM.ResolveUsing", _repo.CurrentBranch.Name); + } + menu.Items.Add(useTheirs); menu.Items.Add(useMine); return menu; @@ -1395,59 +1479,11 @@ namespace SourceGit.ViewModels if (change == null) DetailContext = null; else if (change.IsConflit && isUnstaged) - DetailContext = new ConflictContext(_repo.FullPath, change); + DetailContext = new Conflict(_repo, this, change); else DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged), _detailContext as DiffContext); } - private async void UseTheirs(List changes) - { - var files = new List(); - foreach (var change in changes) - { - if (change.IsConflit) - files.Add(change.Path); - } - - _repo.SetWatcherEnabled(false); - var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseTheirs(files)); - if (succ) - { - await Task.Run(() => new Commands.Add(_repo.FullPath, changes).Exec()); - } - _repo.MarkWorkingCopyDirtyManually(); - _repo.SetWatcherEnabled(true); - } - - private async void UseMine(List changes) - { - var files = new List(); - foreach (var change in changes) - { - if (change.IsConflit) - files.Add(change.Path); - } - - _repo.SetWatcherEnabled(false); - var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseMine(files)); - if (succ) - { - await Task.Run(() => new Commands.Add(_repo.FullPath, changes).Exec()); - } - _repo.MarkWorkingCopyDirtyManually(); - _repo.SetWatcherEnabled(true); - } - - private async void UseExternalMergeTool(Models.Change change) - { - var toolType = Preference.Instance.ExternalMergeToolType; - var toolPath = Preference.Instance.ExternalMergeToolPath; - - _repo.SetWatcherEnabled(false); - await Task.Run(() => Commands.MergeTool.OpenForMerge(_repo.FullPath, toolType, toolPath, change.Path)); - _repo.SetWatcherEnabled(true); - } - private void DoCommit(bool autoStage, bool autoPush, bool allowEmpty) { if (!PopupHost.CanCreatePopup()) diff --git a/src/Views/Archive.axaml b/src/Views/Archive.axaml index 8652dd96..e9d0ad02 100644 --- a/src/Views/Archive.axaml +++ b/src/Views/Archive.axaml @@ -29,7 +29,7 @@ - + diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index 92c2b043..d9588a0f 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -405,6 +405,17 @@ namespace SourceGit.Views ev.Handled = true; }; menu.Items.Add(deleteMulti); + + var mergeMulti = new MenuItem(); + mergeMulti.Header = App.Text("BranchCM.MergeMultiBranches", branches.Count); + mergeMulti.Icon = App.CreateMenuIcon("Icons.Merge"); + mergeMulti.Click += (_, ev) => + { + repo.MergeMultipleBranches(branches); + ev.Handled = true; + }; + menu.Items.Add(mergeMulti); + menu?.Open(this); } } diff --git a/src/Views/CheckoutCommit.axaml b/src/Views/CheckoutCommit.axaml index 37021565..3ee3943f 100644 --- a/src/Views/CheckoutCommit.axaml +++ b/src/Views/CheckoutCommit.axaml @@ -18,7 +18,7 @@ Margin="0,0,8,0" Text="{DynamicResource Text.Checkout.Commit.Target}" /> - + diff --git a/src/Views/CherryPick.axaml b/src/Views/CherryPick.axaml index 1ba62ff7..779a0414 100644 --- a/src/Views/CherryPick.axaml +++ b/src/Views/CherryPick.axaml @@ -44,7 +44,7 @@ - + diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 2ab64525..7b498b10 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -112,7 +112,8 @@ Cursor="Hand" Margin="0,0,16,0" PointerEntered="OnSHAPointerEntered" - PointerPressed="OnSHAPressed"> + PointerPressed="OnSHAPressed" + ToolTip.ShowDelay="0"> @@ -149,7 +150,8 @@ Cursor="Hand" Margin="0,0,16,0" PointerEntered="OnSHAPointerEntered" - PointerPressed="OnSHAPressed"> + PointerPressed="OnSHAPressed" + ToolTip.ShowDelay="0"> diff --git a/src/Views/CommitBaseInfo.axaml.cs b/src/Views/CommitBaseInfo.axaml.cs index 57592647..0b4c1f2d 100644 --- a/src/Views/CommitBaseInfo.axaml.cs +++ b/src/Views/CommitBaseInfo.axaml.cs @@ -130,19 +130,15 @@ namespace SourceGit.Views { var tooltip = ToolTip.GetTip(ctl); if (tooltip is Models.Commit commit && commit.SHA == sha) - { - ToolTip.SetIsOpen(ctl, true); - } - else - { - var c = await Task.Run(() => detail.GetParent(sha)); - if (c != null && ctl.IsVisible && ctl.DataContext is string newSHA && newSHA == sha) - { - ToolTip.SetTip(ctl, c); + return; - if (ctl.IsPointerOver) - ToolTip.SetIsOpen(ctl, true); - } + var c = await Task.Run(() => detail.GetParent(sha)); + if (c != null && ctl.IsVisible && ctl.DataContext is string newSHA && newSHA == sha) + { + ToolTip.SetTip(ctl, c); + + if (ctl.IsPointerOver) + ToolTip.SetIsOpen(ctl, true); } } diff --git a/src/Views/CommitMessageTextBox.axaml.cs b/src/Views/CommitMessageTextBox.axaml.cs index 91f8c47e..0811d97d 100644 --- a/src/Views/CommitMessageTextBox.axaml.cs +++ b/src/Views/CommitMessageTextBox.axaml.cs @@ -95,7 +95,7 @@ namespace SourceGit.Views if (change.Property == TextProperty && _changingWay == TextChangeWay.None) { _changingWay = TextChangeWay.FromSource; - var normalized = Text.ReplaceLineEndings("\n").Trim(); + var normalized = Text.ReplaceLineEndings("\n"); var subjectEnd = normalized.IndexOf("\n\n", StringComparison.Ordinal); if (subjectEnd == -1) { diff --git a/src/Views/CreateBranch.axaml b/src/Views/CreateBranch.axaml index caea0be4..43f4e50c 100644 --- a/src/Views/CreateBranch.axaml +++ b/src/Views/CreateBranch.axaml @@ -37,7 +37,7 @@ - + diff --git a/src/Views/CreateTag.axaml b/src/Views/CreateTag.axaml index 7e9a839a..20b6798a 100644 --- a/src/Views/CreateTag.axaml +++ b/src/Views/CreateTag.axaml @@ -29,7 +29,7 @@ - + diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml index 4fe77066..19f9dc78 100644 --- a/src/Views/Hotkeys.axaml +++ b/src/Views/Hotkeys.axaml @@ -45,7 +45,7 @@ FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}" Margin="0,0,0,8"/> - + @@ -61,8 +61,11 @@ - - + + + + + - + diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 4137579d..a3a3af8b 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -136,6 +136,16 @@ namespace SourceGit.Views return; } + if (e.Key == Key.N) + { + if (vm.ActivePage.Data is not ViewModels.Welcome) + vm.AddNewTab(); + + ViewModels.Welcome.Instance.Clone(); + e.Handled = true; + return; + } + if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Right) || (!OperatingSystem.IsMacOS() && !e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.Tab)) { diff --git a/src/Views/LauncherPage.axaml b/src/Views/LauncherPage.axaml index be2b339f..d09c2c0d 100644 --- a/src/Views/LauncherPage.axaml +++ b/src/Views/LauncherPage.axaml @@ -77,7 +77,7 @@ HorizontalAlignment="Right" IsVisible="{Binding InProgress, Converter={x:Static BoolConverters.Not}}"> + - - - + + + + + diff --git a/src/Views/LauncherPage.axaml.cs b/src/Views/LauncherPage.axaml.cs index d37d22bd..3fa8269e 100644 --- a/src/Views/LauncherPage.axaml.cs +++ b/src/Views/LauncherPage.axaml.cs @@ -32,6 +32,14 @@ namespace SourceGit.Views OnPopupCancel(sender, e); } + private void OnCopyNotification(object sender, RoutedEventArgs e) + { + if (sender is Button { DataContext: Models.Notification notice }) + App.CopyText(notice.Message); + + e.Handled = true; + } + private void OnDismissNotification(object sender, RoutedEventArgs e) { if (sender is Button { DataContext: Models.Notification notice } && diff --git a/src/Views/Merge.axaml b/src/Views/Merge.axaml index b63bfc04..805ac6d0 100644 --- a/src/Views/Merge.axaml +++ b/src/Views/Merge.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" + xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.Merge" x:DataType="vm:Merge"> @@ -11,15 +12,36 @@ - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/MergeMultiple.axaml.cs b/src/Views/MergeMultiple.axaml.cs new file mode 100644 index 00000000..c0997067 --- /dev/null +++ b/src/Views/MergeMultiple.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class MergeMultiple : UserControl + { + public MergeMultiple() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/Rebase.axaml b/src/Views/Rebase.axaml index 166e9e32..91c1fab2 100644 --- a/src/Views/Rebase.axaml +++ b/src/Views/Rebase.axaml @@ -37,7 +37,7 @@ - + diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index a0e83046..6d034130 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -546,45 +546,76 @@ BorderBrush="{DynamicResource Brush.Border0}"/> - - + + + + + + + + + + + + + + - + + + + + + + + -