Skip to content

Commit cd614c8

Browse files
committed
feature: support to detect conflict file state with binary file and hide MERGE button for non-text file conflict (#2566)
1 parent bca63c4 commit cd614c8

6 files changed

Lines changed: 119 additions & 78 deletions

File tree

‎src/Commands/IsConflictResolved.cs‎

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Threading.Tasks;
4+
5+
namespace SourceGit.Commands
6+
{
7+
public class QueryConflictFileState : Command
8+
{
9+
public QueryConflictFileState(string repo, Models.Change change)
10+
{
11+
var opt = new Models.DiffOption(change, true);
12+
13+
WorkingDirectory = repo;
14+
Context = repo;
15+
Args = $"diff --no-color --no-ext-diff --full-index --patch {opt}";
16+
}
17+
18+
public async Task<Models.ConflictFileState> GetResultAsync()
19+
{
20+
try
21+
{
22+
using var proc = new Process();
23+
proc.StartInfo = CreateGitStartInfo(true);
24+
proc.Start();
25+
26+
var isBinary = false;
27+
var tokenCount = 0;
28+
29+
while (await proc.StandardOutput.ReadLineAsync().ConfigureAwait(false) is { } line)
30+
{
31+
if (isBinary)
32+
continue;
33+
34+
if (line.StartsWith("Binary files ", StringComparison.Ordinal))
35+
isBinary = true;
36+
else if (line.StartsWith("++<<<<<<<", StringComparison.Ordinal) ||
37+
line.StartsWith("++=======", StringComparison.Ordinal) ||
38+
line.StartsWith("++>>>>>>>", StringComparison.Ordinal))
39+
tokenCount++;
40+
}
41+
42+
await proc.WaitForExitAsync().ConfigureAwait(false);
43+
44+
if (isBinary)
45+
return Models.ConflictFileState.UnmergedBinary;
46+
47+
return tokenCount == 0 ? Models.ConflictFileState.Resolved : Models.ConflictFileState.UnmergedText;
48+
}
49+
catch
50+
{
51+
return Models.ConflictFileState.Unknown;
52+
}
53+
}
54+
}
55+
}

‎src/Models/Conflict.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
namespace SourceGit.Models
44
{
5+
public enum ConflictFileState
6+
{
7+
Unknown,
8+
UnmergedText,
9+
UnmergedBinary,
10+
Resolved
11+
}
12+
513
public enum ConflictPanelType
614
{
715
Ours,

‎src/ViewModels/Conflict.cs‎

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System.IO;
22
using System.Threading.Tasks;
3+
using Avalonia.Threading;
4+
using CommunityToolkit.Mvvm.ComponentModel;
35

46
namespace SourceGit.ViewModels
57
{
6-
public class Conflict
8+
public class Conflict : ObservableObject
79
{
810
public string Marker
911
{
@@ -15,52 +17,57 @@ public string Description
1517
get => _change.ConflictDesc;
1618
}
1719

18-
public object Theirs
20+
public Models.ConflictFileState State
1921
{
20-
get;
21-
private set;
22+
get => _state;
23+
private set => SetProperty(ref _state, value);
2224
}
2325

24-
public object Mine
26+
public object Theirs
2527
{
26-
get;
27-
private set;
28+
get => _theirs;
29+
private set => SetProperty(ref _theirs, value);
2830
}
2931

30-
public bool IsResolved
31-
{
32-
get;
33-
private set;
34-
} = false;
35-
36-
public bool CanMerge
32+
public object Mine
3733
{
38-
get;
39-
private set;
40-
} = false;
34+
get => _mine;
35+
private set => SetProperty(ref _mine, value);
36+
}
4137

4238
public Conflict(Repository repo, WorkingCopy wc, Models.Change change)
4339
{
4440
_repo = repo;
4541
_wc = wc;
42+
_canMerge = (change.ConflictReason is Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified) && !Directory.Exists(Path.Combine(repo.FullPath, change.Path));
4643
_change = change;
4744

48-
CanMerge = _change.ConflictReason is Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified;
49-
if (CanMerge)
50-
CanMerge = !Directory.Exists(Path.Combine(repo.FullPath, change.Path)); // Cannot merge directories (submodules)
45+
Task.Run(async () =>
46+
{
47+
_head = new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResult();
5148

52-
if (CanMerge)
53-
IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).GetResult();
49+
var (mine, theirs) = wc.InProgressContext switch
50+
{
51+
CherryPickInProgress cherryPick => (_head, cherryPick.Head),
52+
RebaseInProgress rebase => (rebase.Onto, rebase.StoppedAt),
53+
RevertInProgress revert => (_head, revert.Head),
54+
MergeInProgress merge => (_head, merge.Source),
55+
_ => (_head, (object)"Stash or Patch"),
56+
};
5457

55-
_head = new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResult();
56-
(Mine, Theirs) = wc.InProgressContext switch
57-
{
58-
CherryPickInProgress cherryPick => (_head, cherryPick.Head),
59-
RebaseInProgress rebase => (rebase.Onto, rebase.StoppedAt),
60-
RevertInProgress revert => (_head, revert.Head),
61-
MergeInProgress merge => (_head, merge.Source),
62-
_ => (_head, (object)"Stash or Patch"),
63-
};
58+
var state = Models.ConflictFileState.Unknown;
59+
if (_canMerge)
60+
state = await new Commands.QueryConflictFileState(repo.FullPath, change)
61+
.GetResultAsync()
62+
.ConfigureAwait(false);
63+
64+
Dispatcher.UIThread.Post(() =>
65+
{
66+
State = state;
67+
Mine = mine;
68+
Theirs = theirs;
69+
});
70+
});
6471
}
6572

6673
public async Task UseTheirsAsync()
@@ -75,18 +82,22 @@ public async Task UseMineAsync()
7582

7683
public MergeConflictEditor CreateOpenMergeEditorRequest()
7784
{
78-
return CanMerge ? new MergeConflictEditor(_repo, _head, _change.Path) : null;
85+
return _canMerge ? new MergeConflictEditor(_repo, _head, _change.Path) : null;
7986
}
8087

8188
public async Task MergeExternalAsync()
8289
{
83-
if (CanMerge)
90+
if (_canMerge)
8491
await _wc.UseExternalMergeToolAsync(_change);
8592
}
8693

8794
private Repository _repo = null;
8895
private WorkingCopy _wc = null;
89-
private Models.Commit _head = null;
96+
private bool _canMerge = false;
9097
private Models.Change _change = null;
98+
private Models.Commit _head = null;
99+
private Models.ConflictFileState _state = Models.ConflictFileState.Unknown;
100+
private object _mine = null;
101+
private object _theirs = null;
91102
}
92103
}

‎src/ViewModels/WorkingCopy.cs‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -689,15 +689,16 @@ public async Task CommitAsync(bool autoStage, bool autoPush)
689689
{
690690
if (c.IsConflicted)
691691
{
692-
var isResolved = c.ConflictReason switch
692+
if (c.ConflictReason is Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified)
693+
{
694+
var state = await new Commands.QueryConflictFileState(_repo.FullPath, c).GetResultAsync();
695+
if (state != Models.ConflictFileState.Resolved)
696+
continue;
697+
}
698+
else
693699
{
694-
Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified =>
695-
await new Commands.IsConflictResolved(_repo.FullPath, c).GetResultAsync(),
696-
_ => false,
697-
};
698-
699-
if (!isResolved)
700700
continue;
701+
}
701702
}
702703

703704
outs.Add(c);

‎src/Views/Conflict.axaml‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
x:DataType="vm:Conflict">
1212
<Border Background="{DynamicResource Brush.Window}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
1313
<Grid VerticalAlignment="Center">
14-
<StackPanel Orientation="Vertical" IsVisible="{Binding !IsResolved}">
14+
<StackPanel Orientation="Vertical" IsVisible="{Binding State, Mode=OneWay, Converter={x:Static ObjectConverters.NotEqual}, ConverterParameter={x:Static m:ConflictFileState.Resolved}}">
1515
<Path Width="64" Height="64" Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
1616
<TextBlock Margin="0,16" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
1717

@@ -50,13 +50,6 @@
5050
</DataTemplate>
5151
</Border.DataTemplates>
5252

53-
<Border.IsVisible>
54-
<MultiBinding Converter="{x:Static BoolConverters.And}">
55-
<Binding Path="Theirs" Converter="{x:Static ObjectConverters.IsNotNull}"/>
56-
<Binding Path="Mine" Converter="{x:Static ObjectConverters.IsNotNull}"/>
57-
</MultiBinding>
58-
</Border.IsVisible>
59-
6053
<Grid Margin="8,0,0,0" RowDefinitions="32,32" ColumnDefinitions="Auto,*">
6154
<TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" Text="MINE"/>
6255
<ContentControl Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Content="{Binding Mine}"/>
@@ -81,7 +74,7 @@
8174
<SplitButton Height="28"
8275
Margin="8,0,0,0" Padding="8,0"
8376
Click="OnMerge"
84-
IsVisible="{Binding CanMerge, Mode=OneWay}">
77+
IsVisible="{Binding State, Mode=OneWay, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:ConflictFileState.UnmergedText}}">
8578
<SplitButton.Styles>
8679
<Style Selector="SplitButton">
8780
<Setter Property="MinHeight" Value="24"/>
@@ -157,7 +150,7 @@
157150
</StackPanel>
158151
</StackPanel>
159152

160-
<StackPanel Orientation="Vertical" IsVisible="{Binding IsResolved}">
153+
<StackPanel Orientation="Vertical" IsVisible="{Binding State, Mode=OneWay, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:ConflictFileState.Resolved}}">
161154
<Path Width="64" Height="64" Data="{StaticResource Icons.Check}" Fill="Green"/>
162155
<TextBlock Margin="0,16,0,8" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts.Resolved}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
163156
<TextBlock Text="{DynamicResource Text.WorkingCopy.CanStageTip}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>

0 commit comments

Comments
 (0)