Skip to content

Commit 1faa3b1

Browse files
committed
enhance: merge
- Testing for merge when not fast-forward - Move `Merge <SELECTED> into <CURRENT>` from submenu to main context menu Signed-off-by: leo <longshuang@msn.cn>
1 parent 2647b2d commit 1faa3b1

8 files changed

Lines changed: 206 additions & 57 deletions

File tree

src/Commands/MergeBase.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Threading.Tasks;
2+
3+
namespace SourceGit.Commands
4+
{
5+
public class MergeBase : Command
6+
{
7+
public MergeBase(string repo, string source, string dest)
8+
{
9+
WorkingDirectory = repo;
10+
Context = repo;
11+
RaiseError = false;
12+
Args = $"merge-base {source} {dest}";
13+
}
14+
15+
public async Task<string> GetResultAsync()
16+
{
17+
var rs = await ReadToEndAsync().ConfigureAwait(false);
18+
if (rs.IsSuccess)
19+
{
20+
var trimmed = rs.StdOut.Trim();
21+
if (trimmed.Length == 40 || trimmed.Length == 64)
22+
return trimmed;
23+
}
24+
25+
return string.Empty;
26+
}
27+
}
28+
}

src/Commands/MergeTree.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
namespace SourceGit.Commands
5+
{
6+
public class MergeTree : Command
7+
{
8+
public MergeTree(string repo, string baseTree, string source, string dest)
9+
{
10+
WorkingDirectory = repo;
11+
Context = repo;
12+
RaiseError = false;
13+
Args = $"merge-tree {baseTree} {source} {dest}";
14+
}
15+
16+
public async Task<bool> CheckAsync()
17+
{
18+
var rs = await ReadToEndAsync().ConfigureAwait(false);
19+
if (!rs.IsSuccess)
20+
return false;
21+
22+
var stdout = rs.StdOut;
23+
return !stdout.Contains("\n+>>>>>>>", StringComparison.Ordinal) &&
24+
!stdout.Contains("\n+<<<<<<<", StringComparison.Ordinal) &&
25+
!stdout.Contains("\n->>>>>>>", StringComparison.Ordinal) &&
26+
!stdout.Contains("\n-<<<<<<<", StringComparison.Ordinal);
27+
}
28+
}
29+
}

src/Resources/Locales/en_US.axaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@
165165
<x:String x:Key="Text.CommitCM.InteractiveRebase.Manually" xml:space="preserve">Interactively Rebase ${0}$ on ${1}$</x:String>
166166
<x:String x:Key="Text.CommitCM.InteractiveRebase.Reword" xml:space="preserve">Reword...</x:String>
167167
<x:String x:Key="Text.CommitCM.InteractiveRebase.Squash" xml:space="preserve">Squash into Parent...</x:String>
168-
<x:String x:Key="Text.CommitCM.Merge" xml:space="preserve">Merge to ${0}$</x:String>
169168
<x:String x:Key="Text.CommitCM.MergeMultiple" xml:space="preserve">Merge ...</x:String>
170169
<x:String x:Key="Text.CommitCM.PushRevision" xml:space="preserve">Push ${0}$ to ${1}$</x:String>
171170
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">Rebase ${0}$ on ${1}$</x:String>
@@ -571,6 +570,9 @@
571570
<x:String x:Key="Text.Merge.Into" xml:space="preserve">Into:</x:String>
572571
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">Merge Option:</x:String>
573572
<x:String x:Key="Text.Merge.Source" xml:space="preserve">Source:</x:String>
573+
<x:String x:Key="Text.Merge.Test" xml:space="preserve">Testing for merge...</x:String>
574+
<x:String x:Key="Text.Merge.Test.Conflicts" xml:space="preserve">Merge will cause conflicts</x:String>
575+
<x:String x:Key="Text.Merge.Test.OK" xml:space="preserve">Merge can be done without conflicts</x:String>
574576
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.MineFirst" xml:space="preserve">First Mine, then Theirs</x:String>
575577
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.TheirsFirst" xml:space="preserve">First Theirs, then Mine</x:String>
576578
<x:String x:Key="Text.MergeConflictEditor.UseBoth" xml:space="preserve">USE BOTH</x:String>
@@ -930,7 +932,6 @@
930932
<x:String x:Key="Text.TagCM.CustomAction" xml:space="preserve">Custom Action</x:String>
931933
<x:String x:Key="Text.TagCM.Delete" xml:space="preserve">Delete ${0}$...</x:String>
932934
<x:String x:Key="Text.TagCM.DeleteMultiple" xml:space="preserve">Delete selected {0} tags...</x:String>
933-
<x:String x:Key="Text.TagCM.Merge" xml:space="preserve">Merge ${0}$ into ${1}$...</x:String>
934935
<x:String x:Key="Text.TagCM.Push" xml:space="preserve">Push ${0}$...</x:String>
935936
<x:String x:Key="Text.UpdateSubmodules" xml:space="preserve">Update Submodules</x:String>
936937
<x:String x:Key="Text.UpdateSubmodules.All" xml:space="preserve">All submodules</x:String>

src/Resources/Locales/zh_CN.axaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,9 @@
575575
<x:String x:Key="Text.Merge.Into" xml:space="preserve">目标分支 :</x:String>
576576
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">合并方式 :</x:String>
577577
<x:String x:Key="Text.Merge.Source" xml:space="preserve">合并目标 :</x:String>
578+
<x:String x:Key="Text.Merge.Test" xml:space="preserve">检测合并冲突中...</x:String>
579+
<x:String x:Key="Text.Merge.Test.Conflicts" xml:space="preserve">合并操作将存在冲突文件</x:String>
580+
<x:String x:Key="Text.Merge.Test.OK" xml:space="preserve">合并操作不会产生冲突</x:String>
578581
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.MineFirst" xml:space="preserve">先应用 MINE 后 THEIRS</x:String>
579582
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.TheirsFirst" xml:space="preserve">先应用 THEIRS 后 MINE</x:String>
580583
<x:String x:Key="Text.MergeConflictEditor.UseBoth" xml:space="preserve">应用全部</x:String>

src/Resources/Locales/zh_TW.axaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,9 @@
575575
<x:String x:Key="Text.Merge.Into" xml:space="preserve">目標分支:</x:String>
576576
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">合併方式:</x:String>
577577
<x:String x:Key="Text.Merge.Source" xml:space="preserve">合併來源:</x:String>
578+
<x:String x:Key="Text.Merge.Test" xml:space="preserve">偵測合併衝突中...</x:String>
579+
<x:String x:Key="Text.Merge.Test.Conflicts" xml:space="preserve">合併操作將存在衝突檔案</x:String>
580+
<x:String x:Key="Text.Merge.Test.OK" xml:space="preserve">合併操作不會產生衝突</x:String>
578581
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.MineFirst" xml:space="preserve">先套用我方版本 (ours),再套用對方版本 (theirs)</x:String>
579582
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.TheirsFirst" xml:space="preserve">先套用對方版本 (theirs),再套用我方版本 (ours)</x:String>
580583
<x:String x:Key="Text.MergeConflictEditor.UseBoth" xml:space="preserve">套用兩者</x:String>

src/ViewModels/Merge.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
using System.IO;
22
using System.Threading.Tasks;
3+
using Avalonia.Threading;
34

45
namespace SourceGit.ViewModels
56
{
7+
public enum MergeTestingState
8+
{
9+
Disabled = 0,
10+
Testing = 1,
11+
WillCauseConflicts = 2,
12+
NoConflicts = 3,
13+
}
14+
615
public class Merge : Popup
716
{
817
public object Source
@@ -37,6 +46,12 @@ public bool Edit
3746
set;
3847
} = false;
3948

49+
public MergeTestingState TestingState
50+
{
51+
get => _testingState;
52+
private set => SetProperty(ref _testingState, value);
53+
}
54+
4055
public Merge(Repository repo, Models.Branch source, string into, bool forceFastForward)
4156
{
4257
_repo = repo;
@@ -45,6 +60,9 @@ public Merge(Repository repo, Models.Branch source, string into, bool forceFastF
4560
Source = source;
4661
Into = into;
4762
Mode = forceFastForward ? Models.MergeMode.FastForward : AutoSelectMergeMode();
63+
64+
if (!forceFastForward)
65+
Test();
4866
}
4967

5068
public Merge(Repository repo, Models.Commit source, string into)
@@ -55,6 +73,8 @@ public Merge(Repository repo, Models.Commit source, string into)
5573
Source = source;
5674
Into = into;
5775
Mode = AutoSelectMergeMode();
76+
77+
Test();
5878
}
5979

6080
public Merge(Repository repo, Models.Tag source, string into)
@@ -65,6 +85,8 @@ public Merge(Repository repo, Models.Tag source, string into)
6585
Source = source;
6686
Into = into;
6787
Mode = AutoSelectMergeMode();
88+
89+
Test();
6890
}
6991

7092
public override async Task<bool> Sure()
@@ -125,9 +147,35 @@ private Models.MergeMode AutoSelectMergeMode()
125147
return Models.MergeMode.Supported[preferredMergeModeIdx];
126148
}
127149

150+
private void Test()
151+
{
152+
TestingState = MergeTestingState.Testing;
153+
154+
Task.Run(async () =>
155+
{
156+
var mergeBase = await new Commands.MergeBase(_repo.FullPath, _sourceName, Into)
157+
.GetResultAsync()
158+
.ConfigureAwait(false);
159+
160+
if (!string.IsNullOrEmpty(mergeBase))
161+
{
162+
var ok = await new Commands.MergeTree(_repo.FullPath, mergeBase, _sourceName, Into)
163+
.CheckAsync()
164+
.ConfigureAwait(false);
165+
166+
Dispatcher.UIThread.Post(() => TestingState = ok ? MergeTestingState.NoConflicts : MergeTestingState.WillCauseConflicts);
167+
}
168+
else
169+
{
170+
Dispatcher.UIThread.Post(() => TestingState = MergeTestingState.Disabled);
171+
}
172+
});
173+
}
174+
128175
private readonly Repository _repo = null;
129176
private readonly string _sourceName;
130177
private Models.MergeMode _mode = Models.MergeMode.Default;
131178
private bool _canEditMessage = true;
179+
private MergeTestingState _testingState = MergeTestingState.Disabled;
132180
}
133181
}

src/Views/Histories.axaml.cs

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -849,15 +849,15 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo,
849849
FillCurrentBranchMenu(menu, repo, current);
850850
break;
851851
case Models.DecoratorType.LocalBranchHead:
852-
var lb = repo.Branches.Find(x => x.IsLocal && d.Name == x.Name);
852+
var lb = repo.Branches.Find(x => x.IsLocal && d.Name.Equals(x.Name, StringComparison.Ordinal));
853853
FillOtherLocalBranchMenu(menu, repo, lb, current, commit.IsMerged);
854854
break;
855855
case Models.DecoratorType.RemoteBranchHead:
856-
var rb = repo.Branches.Find(x => !x.IsLocal && d.Name == x.FriendlyName);
856+
var rb = repo.Branches.Find(x => !x.IsLocal && d.Name.Equals(x.FriendlyName, StringComparison.Ordinal));
857857
FillRemoteBranchMenu(menu, repo, rb, current, commit.IsMerged);
858858
break;
859859
case Models.DecoratorType.Tag:
860-
var t = repo.Tags.Find(x => x.Name == d.Name);
860+
var t = repo.Tags.Find(x => d.Name.Equals(x.Name, StringComparison.Ordinal));
861861
if (t != null)
862862
tags.Add(t);
863863
break;
@@ -903,7 +903,7 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo,
903903
if (!repo.IsBare)
904904
{
905905
var target = commit.GetFriendlyName();
906-
if (target.Length > 32)
906+
if (target.Length > 40)
907907
target = commit.SHA.Substring(0, 10);
908908

909909
if (!isHead)
@@ -933,20 +933,55 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo,
933933
};
934934
menu.Items.Add(rebase);
935935

936-
if (!commit.HasDecorators)
936+
var merge = new MenuItem();
937+
merge.Header = App.Text("BranchCM.Merge", target, current.Name);
938+
merge.Icon = this.CreateMenuIcon("Icons.Merge");
939+
merge.Click += (_, e) =>
937940
{
938-
var merge = new MenuItem();
939-
merge.Header = App.Text("CommitCM.Merge", current.Name);
940-
merge.Icon = this.CreateMenuIcon("Icons.Merge");
941-
merge.Click += (_, e) =>
941+
if (repo.CanCreatePopup())
942942
{
943-
if (repo.CanCreatePopup())
943+
var found = false;
944+
foreach (var d in commit.Decorators)
945+
{
946+
if (d.Type == Models.DecoratorType.LocalBranchHead)
947+
{
948+
var b = repo.Branches.Find(x => x.IsLocal && x.Name.Equals(d.Name, StringComparison.Ordinal));
949+
if (b != null)
950+
{
951+
found = true;
952+
repo.ShowPopup(new ViewModels.Merge(repo, b, current.Name, false));
953+
break;
954+
}
955+
}
956+
else if (d.Type == Models.DecoratorType.RemoteBranchHead)
957+
{
958+
var rb = repo.Branches.Find(x => !x.IsLocal && x.FriendlyName.Equals(d.Name, StringComparison.Ordinal));
959+
if (rb != null)
960+
{
961+
found = true;
962+
repo.ShowPopup(new ViewModels.Merge(repo, rb, current.Name, false));
963+
break;
964+
}
965+
}
966+
else if (d.Type == Models.DecoratorType.Tag)
967+
{
968+
var t = repo.Tags.Find(x => x.Name.Equals(d.Name, StringComparison.Ordinal));
969+
if (t != null)
970+
{
971+
found = true;
972+
repo.ShowPopup(new ViewModels.Merge(repo, t, current.Name));
973+
break;
974+
}
975+
}
976+
}
977+
978+
if (!found)
944979
repo.ShowPopup(new ViewModels.Merge(repo, commit, current.Name));
980+
}
945981

946-
e.Handled = true;
947-
};
948-
menu.Items.Add(merge);
949-
}
982+
e.Handled = true;
983+
};
984+
menu.Items.Add(merge);
950985

951986
var cherryPick = new MenuItem();
952987
cherryPick.Header = App.Text("CommitCM.CherryPick");
@@ -1405,18 +1440,6 @@ private void FillOtherLocalBranchMenu(ContextMenu menu, ViewModels.Repository re
14051440
e.Handled = true;
14061441
};
14071442
submenu.Items.Add(checkout);
1408-
1409-
var merge = new MenuItem();
1410-
merge.Header = App.Text("BranchCM.Merge", branch.Name, current.Name);
1411-
merge.Icon = this.CreateMenuIcon("Icons.Merge");
1412-
merge.IsEnabled = !merged;
1413-
merge.Click += (_, e) =>
1414-
{
1415-
if (repo.CanCreatePopup())
1416-
repo.ShowPopup(new ViewModels.Merge(repo, branch, current.Name, false));
1417-
e.Handled = true;
1418-
};
1419-
submenu.Items.Add(merge);
14201443
}
14211444

14221445
var rename = new MenuItem();
@@ -1513,19 +1536,6 @@ private void FillRemoteBranchMenu(ContextMenu menu, ViewModels.Repository repo,
15131536
};
15141537
submenu.Items.Add(checkout);
15151538

1516-
var merge = new MenuItem();
1517-
merge.Header = App.Text("BranchCM.Merge", name, current.Name);
1518-
merge.Icon = this.CreateMenuIcon("Icons.Merge");
1519-
merge.IsEnabled = !merged;
1520-
merge.Click += (_, e) =>
1521-
{
1522-
if (repo.CanCreatePopup())
1523-
repo.ShowPopup(new ViewModels.Merge(repo, branch, current.Name, false));
1524-
e.Handled = true;
1525-
};
1526-
1527-
submenu.Items.Add(merge);
1528-
15291539
var delete = new MenuItem();
15301540
delete.Header = App.Text("BranchCM.Delete", name);
15311541
delete.Icon = this.CreateMenuIcon("Icons.Clear");
@@ -1588,20 +1598,6 @@ private void FillTagMenu(ContextMenu menu, ViewModels.Repository repo, Models.Ta
15881598
};
15891599
submenu.Items.Add(push);
15901600

1591-
if (!repo.IsBare && !merged)
1592-
{
1593-
var merge = new MenuItem();
1594-
merge.Header = App.Text("TagCM.Merge", tag.Name, current.Name);
1595-
merge.Icon = this.CreateMenuIcon("Icons.Merge");
1596-
merge.Click += (_, e) =>
1597-
{
1598-
if (repo.CanCreatePopup())
1599-
repo.ShowPopup(new ViewModels.Merge(repo, tag, current.Name));
1600-
e.Handled = true;
1601-
};
1602-
submenu.Items.Add(merge);
1603-
}
1604-
16051601
var delete = new MenuItem();
16061602
delete.Header = App.Text("TagCM.Delete", tag.Name);
16071603
delete.Icon = this.CreateMenuIcon("Icons.Clear");

0 commit comments

Comments
 (0)