Skip to content

Commit c00728a

Browse files
committed
feat: accept both
1 parent 6fbff56 commit c00728a

File tree

5 files changed

+254
-10
lines changed

5 files changed

+254
-10
lines changed

src/Models/MergeConflict.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public enum ConflictResolution
88
UseOurs,
99
UseTheirs,
1010
UseBoth,
11+
UseBothMineFirst,
12+
UseBothTheirsFirst,
1113
Manual,
1214
}
1315

src/Resources/Locales/en_US.axaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,11 @@
878878
<x:String x:Key="Text.MergeConflictEditor.AcceptMine.Tip" xml:space="preserve">Resolve all conflicts using Mine version</x:String>
879879
<x:String x:Key="Text.MergeConflictEditor.AcceptTheirs" xml:space="preserve">ACCEPT ALL THEIRS</x:String>
880880
<x:String x:Key="Text.MergeConflictEditor.AcceptTheirs.Tip" xml:space="preserve">Resolve all conflicts using Theirs version</x:String>
881+
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth" xml:space="preserve">ACCEPT ALL BOTH</x:String>
882+
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.Tip" xml:space="preserve">Resolve all conflicts using both versions</x:String>
883+
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.MineFirst" xml:space="preserve">First Mine, then Theirs</x:String>
884+
<x:String x:Key="Text.MergeConflictEditor.AcceptBoth.TheirsFirst" xml:space="preserve">First Theirs, then Mine</x:String>
885+
<x:String x:Key="Text.MergeConflictEditor.UseBoth" xml:space="preserve">USE BOTH</x:String>
881886
<x:String x:Key="Text.MergeConflictEditor.AllResolved" xml:space="preserve">All conflicts resolved</x:String>
882887
<x:String x:Key="Text.MergeConflictEditor.ConflictsRemaining" xml:space="preserve">{0} conflict(s) remaining</x:String>
883888
<x:String x:Key="Text.MergeConflictEditor.Mine" xml:space="preserve">MINE</x:String>

src/ViewModels/MergeConflictEditor.cs

Lines changed: 139 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -462,18 +462,49 @@ private void BuildAlignedResultPanel()
462462
if (currentRegion.ResolvedContent != null)
463463
{
464464
// Resolved - show resolved content with color based on resolution type
465-
var lineType = currentRegion.ResolutionType switch
465+
if (currentRegion.ResolutionType == Models.ConflictResolution.UseBothMineFirst)
466466
{
467-
Models.ConflictResolution.UseOurs => Models.TextDiffLineType.Deleted, // Mine color
468-
Models.ConflictResolution.UseTheirs => Models.TextDiffLineType.Added, // Theirs color
469-
_ => Models.TextDiffLineType.Normal
470-
};
471-
472-
foreach (var line in currentRegion.ResolvedContent)
467+
// First portion is Mine (Deleted color), second is Theirs (Added color)
468+
int mineCount = currentRegion.OursContent.Count;
469+
for (int i = 0; i < currentRegion.ResolvedContent.Count; i++)
470+
{
471+
var lineType = i < mineCount
472+
? Models.TextDiffLineType.Deleted
473+
: Models.TextDiffLineType.Added;
474+
resultLines.Add(new Models.TextDiffLine(
475+
lineType, currentRegion.ResolvedContent[i], resultLineNumber, resultLineNumber));
476+
resultLineNumber++;
477+
}
478+
}
479+
else if (currentRegion.ResolutionType == Models.ConflictResolution.UseBothTheirsFirst)
473480
{
474-
resultLines.Add(new Models.TextDiffLine(
475-
lineType, line, resultLineNumber, resultLineNumber));
476-
resultLineNumber++;
481+
// First portion is Theirs (Added color), second is Mine (Deleted color)
482+
int theirsCount = currentRegion.TheirsContent.Count;
483+
for (int i = 0; i < currentRegion.ResolvedContent.Count; i++)
484+
{
485+
var lineType = i < theirsCount
486+
? Models.TextDiffLineType.Added
487+
: Models.TextDiffLineType.Deleted;
488+
resultLines.Add(new Models.TextDiffLine(
489+
lineType, currentRegion.ResolvedContent[i], resultLineNumber, resultLineNumber));
490+
resultLineNumber++;
491+
}
492+
}
493+
else
494+
{
495+
var lineType = currentRegion.ResolutionType switch
496+
{
497+
Models.ConflictResolution.UseOurs => Models.TextDiffLineType.Deleted, // Mine color
498+
Models.ConflictResolution.UseTheirs => Models.TextDiffLineType.Added, // Theirs color
499+
_ => Models.TextDiffLineType.Normal
500+
};
501+
502+
foreach (var line in currentRegion.ResolvedContent)
503+
{
504+
resultLines.Add(new Models.TextDiffLine(
505+
lineType, line, resultLineNumber, resultLineNumber));
506+
resultLineNumber++;
507+
}
477508
}
478509
// Pad with empty lines to match Mine/Theirs panel height
479510
int padding = regionLines - currentRegion.ResolvedContent.Count;
@@ -663,6 +694,104 @@ public void AcceptTheirsAtIndex(int conflictIndex)
663694
IsModified = true;
664695
}
665696

697+
public void AcceptBothMineFirst()
698+
{
699+
if (_conflictRegions.Count == 0)
700+
return;
701+
702+
bool anyResolved = false;
703+
foreach (var region in _conflictRegions)
704+
{
705+
if (!region.IsResolved)
706+
{
707+
var combined = new List<string>(region.OursContent);
708+
combined.AddRange(region.TheirsContent);
709+
region.ResolvedContent = combined;
710+
region.IsResolved = true;
711+
region.ResolutionType = Models.ConflictResolution.UseBothMineFirst;
712+
anyResolved = true;
713+
}
714+
}
715+
716+
if (anyResolved)
717+
{
718+
RebuildResultContent();
719+
BuildAlignedResultPanel();
720+
UpdateConflictInfo();
721+
IsModified = true;
722+
}
723+
}
724+
725+
public void AcceptBothTheirsFirst()
726+
{
727+
if (_conflictRegions.Count == 0)
728+
return;
729+
730+
bool anyResolved = false;
731+
foreach (var region in _conflictRegions)
732+
{
733+
if (!region.IsResolved)
734+
{
735+
var combined = new List<string>(region.TheirsContent);
736+
combined.AddRange(region.OursContent);
737+
region.ResolvedContent = combined;
738+
region.IsResolved = true;
739+
region.ResolutionType = Models.ConflictResolution.UseBothTheirsFirst;
740+
anyResolved = true;
741+
}
742+
}
743+
744+
if (anyResolved)
745+
{
746+
RebuildResultContent();
747+
BuildAlignedResultPanel();
748+
UpdateConflictInfo();
749+
IsModified = true;
750+
}
751+
}
752+
753+
public void AcceptBothMineFirstAtIndex(int conflictIndex)
754+
{
755+
if (conflictIndex < 0 || conflictIndex >= _conflictRegions.Count)
756+
return;
757+
758+
var region = _conflictRegions[conflictIndex];
759+
if (region.IsResolved)
760+
return;
761+
762+
var combined = new List<string>(region.OursContent);
763+
combined.AddRange(region.TheirsContent);
764+
region.ResolvedContent = combined;
765+
region.IsResolved = true;
766+
region.ResolutionType = Models.ConflictResolution.UseBothMineFirst;
767+
768+
RebuildResultContent();
769+
BuildAlignedResultPanel();
770+
UpdateConflictInfo();
771+
IsModified = true;
772+
}
773+
774+
public void AcceptBothTheirsFirstAtIndex(int conflictIndex)
775+
{
776+
if (conflictIndex < 0 || conflictIndex >= _conflictRegions.Count)
777+
return;
778+
779+
var region = _conflictRegions[conflictIndex];
780+
if (region.IsResolved)
781+
return;
782+
783+
var combined = new List<string>(region.TheirsContent);
784+
combined.AddRange(region.OursContent);
785+
region.ResolvedContent = combined;
786+
region.IsResolved = true;
787+
region.ResolutionType = Models.ConflictResolution.UseBothTheirsFirst;
788+
789+
RebuildResultContent();
790+
BuildAlignedResultPanel();
791+
UpdateConflictInfo();
792+
IsModified = true;
793+
}
794+
666795
public void UndoResolutionAtIndex(int conflictIndex)
667796
{
668797
if (conflictIndex < 0 || conflictIndex >= _conflictRegions.Count)

src/Views/MergeConflictEditor.axaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,34 @@
7373
Click="OnAcceptTheirs"
7474
ToolTip.Tip="{DynamicResource Text.MergeConflictEditor.AcceptTheirs.Tip}"
7575
IsEnabled="{Binding HasUnresolvedConflicts}"/>
76+
<Button Classes="flat"
77+
ToolTip.Tip="{DynamicResource Text.MergeConflictEditor.AcceptBoth.Tip}"
78+
IsEnabled="{Binding HasUnresolvedConflicts}">
79+
<StackPanel Orientation="Horizontal" Spacing="4">
80+
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth}"/>
81+
<Path Width="10" Height="10" Data="{StaticResource Icons.Down}" VerticalAlignment="Center"/>
82+
</StackPanel>
83+
<Button.Flyout>
84+
<MenuFlyout Placement="BottomEdgeAlignedLeft">
85+
<MenuItem Click="OnAcceptBothMineFirst">
86+
<MenuItem.Icon>
87+
<Path Width="12" Height="12" Data="{StaticResource Icons.February}" Fill="{DynamicResource Brush.Diff.MineHeader}"/>
88+
</MenuItem.Icon>
89+
<MenuItem.Header>
90+
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth.MineFirst}"/>
91+
</MenuItem.Header>
92+
</MenuItem>
93+
<MenuItem Click="OnAcceptBothTheirsFirst">
94+
<MenuItem.Icon>
95+
<Path Width="12" Height="12" Data="{StaticResource Icons.February}" Fill="{DynamicResource Brush.Diff.TheirsHeader}"/>
96+
</MenuItem.Icon>
97+
<MenuItem.Header>
98+
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth.TheirsFirst}"/>
99+
</MenuItem.Header>
100+
</MenuItem>
101+
</MenuFlyout>
102+
</Button.Flyout>
103+
</Button>
76104

77105
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" Margin="4,4"/>
78106

@@ -221,6 +249,32 @@
221249
<Button Classes="flat primary"
222250
Content="{DynamicResource Text.MergeConflictEditor.UseTheirs}"
223251
Click="OnUseTheirsFromHover"/>
252+
<Button Classes="flat primary">
253+
<StackPanel Orientation="Horizontal" Spacing="4">
254+
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.UseBoth}"/>
255+
<Path Width="8" Height="8" Data="{StaticResource Icons.Down}" VerticalAlignment="Center"/>
256+
</StackPanel>
257+
<Button.Flyout>
258+
<MenuFlyout Placement="BottomEdgeAlignedLeft">
259+
<MenuItem Click="OnUseBothMineFirstFromHover">
260+
<MenuItem.Icon>
261+
<Path Width="12" Height="12" Data="{StaticResource Icons.February}" Fill="{DynamicResource Brush.Diff.MineHeader}"/>
262+
</MenuItem.Icon>
263+
<MenuItem.Header>
264+
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth.MineFirst}"/>
265+
</MenuItem.Header>
266+
</MenuItem>
267+
<MenuItem Click="OnUseBothTheirsFirstFromHover">
268+
<MenuItem.Icon>
269+
<Path Width="12" Height="12" Data="{StaticResource Icons.February}" Fill="{DynamicResource Brush.Diff.TheirsHeader}"/>
270+
</MenuItem.Icon>
271+
<MenuItem.Header>
272+
<TextBlock Text="{DynamicResource Text.MergeConflictEditor.AcceptBoth.TheirsFirst}"/>
273+
</MenuItem.Header>
274+
</MenuItem>
275+
</MenuFlyout>
276+
</Button.Flyout>
277+
</Button>
224278
</StackPanel>
225279
</Border>
226280
<Border x:Name="ResultUndoPopup"

src/Views/MergeConflictEditor.axaml.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,60 @@ private void OnAcceptTheirs(object sender, RoutedEventArgs e)
12181218
e.Handled = true;
12191219
}
12201220

1221+
private void OnAcceptBothMineFirst(object sender, RoutedEventArgs e)
1222+
{
1223+
if (DataContext is ViewModels.MergeConflictEditor vm)
1224+
{
1225+
var savedOffset = SaveScrollOffset();
1226+
vm.AcceptBothMineFirst();
1227+
UpdateCurrentConflictHighlight();
1228+
UpdateResolvedRanges();
1229+
RestoreScrollOffset(savedOffset);
1230+
}
1231+
e.Handled = true;
1232+
}
1233+
1234+
private void OnAcceptBothTheirsFirst(object sender, RoutedEventArgs e)
1235+
{
1236+
if (DataContext is ViewModels.MergeConflictEditor vm)
1237+
{
1238+
var savedOffset = SaveScrollOffset();
1239+
vm.AcceptBothTheirsFirst();
1240+
UpdateCurrentConflictHighlight();
1241+
UpdateResolvedRanges();
1242+
RestoreScrollOffset(savedOffset);
1243+
}
1244+
e.Handled = true;
1245+
}
1246+
1247+
private void OnUseBothMineFirstFromHover(object sender, RoutedEventArgs e)
1248+
{
1249+
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
1250+
{
1251+
var savedOffset = SaveScrollOffset();
1252+
vm.AcceptBothMineFirstAtIndex(chunk.ConflictIndex);
1253+
UpdateCurrentConflictHighlight();
1254+
UpdateResolvedRanges();
1255+
RestoreScrollOffset(savedOffset);
1256+
vm.SelectedChunk = null;
1257+
}
1258+
e.Handled = true;
1259+
}
1260+
1261+
private void OnUseBothTheirsFirstFromHover(object sender, RoutedEventArgs e)
1262+
{
1263+
if (DataContext is ViewModels.MergeConflictEditor vm && vm.SelectedChunk is { } chunk)
1264+
{
1265+
var savedOffset = SaveScrollOffset();
1266+
vm.AcceptBothTheirsFirstAtIndex(chunk.ConflictIndex);
1267+
UpdateCurrentConflictHighlight();
1268+
UpdateResolvedRanges();
1269+
RestoreScrollOffset(savedOffset);
1270+
vm.SelectedChunk = null;
1271+
}
1272+
e.Handled = true;
1273+
}
1274+
12211275
private Vector SaveScrollOffset()
12221276
{
12231277
if (DataContext is ViewModels.MergeConflictEditor vm)

0 commit comments

Comments
 (0)