Skip to content

Commit e5a5fad

Browse files
authored
feature: built-in merge conflict solver (#2070)
* feat: first implementation of merge tool * feat: three way merge test * feat: moved to two way merge solver * refactor: moved files * feat: color themes in merge conflict panel * feat: updated logic * feat: better scroll * fix: localization * feat: code cleanup * feat: scroll sync * fix: arrows work * fix: scroll with wheel * fix: arrow and scrollbar handlers * feat: added popup buttons * feat: moving and clicable flying buttons * fix: moving buttons * feat: undo button * fix: separator line * fix: scroll sync works with keyboard and scrollbar * feat: accept both * refactor: removed "accept all" buttons * feat: better navigation * feat: added right click entry and shortcut * fix: scroll sync works with keyboard and scrollbar (again) * fix: removed unnecessary directive
1 parent bdff65a commit e5a5fad

File tree

12 files changed

+3365
-0
lines changed

12 files changed

+3365
-0
lines changed

src/Commands/DiffConflictStages.cs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Text;
5+
using System.Text.RegularExpressions;
6+
using System.Threading.Tasks;
7+
8+
namespace SourceGit.Commands
9+
{
10+
/// <summary>
11+
/// Computes a diff between the conflict stages :2: (ours) and :3: (theirs)
12+
/// </summary>
13+
public partial class DiffConflictStages : Command
14+
{
15+
[GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
16+
private static partial Regex REG_INDICATOR();
17+
18+
public DiffConflictStages(string repo, string file, int unified = 9999)
19+
{
20+
WorkingDirectory = repo;
21+
Context = repo;
22+
_result.TextDiff = new Models.TextDiff();
23+
24+
// Diff between stage :2: (ours) and :3: (theirs)
25+
var builder = new StringBuilder();
26+
builder.Append("diff --no-color --no-ext-diff --patch ");
27+
if (Models.DiffOption.IgnoreCRAtEOL)
28+
builder.Append("--ignore-cr-at-eol ");
29+
builder.Append("--unified=").Append(unified).Append(' ');
30+
builder.Append($":2:{file.Quoted()} :3:{file.Quoted()}");
31+
32+
Args = builder.ToString();
33+
}
34+
35+
public async Task<Models.DiffResult> ReadAsync()
36+
{
37+
try
38+
{
39+
using var proc = new Process();
40+
proc.StartInfo = CreateGitStartInfo(true);
41+
proc.Start();
42+
43+
var text = await proc.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
44+
45+
var start = 0;
46+
var end = text.IndexOf('\n', start);
47+
while (end > 0)
48+
{
49+
var line = text[start..end];
50+
ParseLine(line);
51+
52+
start = end + 1;
53+
end = text.IndexOf('\n', start);
54+
}
55+
56+
if (start < text.Length)
57+
ParseLine(text[start..]);
58+
59+
await proc.WaitForExitAsync().ConfigureAwait(false);
60+
}
61+
catch
62+
{
63+
// Ignore exceptions.
64+
}
65+
66+
if (_result.IsBinary || _result.TextDiff.Lines.Count == 0)
67+
{
68+
_result.TextDiff = null;
69+
}
70+
else
71+
{
72+
ProcessInlineHighlights();
73+
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
74+
}
75+
76+
return _result;
77+
}
78+
79+
private void ParseLine(string line)
80+
{
81+
if (_result.IsBinary)
82+
return;
83+
84+
if (_result.TextDiff.Lines.Count == 0)
85+
{
86+
if (line.StartsWith("Binary", StringComparison.Ordinal))
87+
{
88+
_result.IsBinary = true;
89+
return;
90+
}
91+
92+
var match = REG_INDICATOR().Match(line);
93+
if (!match.Success)
94+
return;
95+
96+
_oldLine = int.Parse(match.Groups[1].Value);
97+
_newLine = int.Parse(match.Groups[2].Value);
98+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
99+
_result.TextDiff.Lines.Add(_last);
100+
}
101+
else
102+
{
103+
if (line.Length == 0)
104+
{
105+
ProcessInlineHighlights();
106+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine);
107+
_result.TextDiff.Lines.Add(_last);
108+
_oldLine++;
109+
_newLine++;
110+
return;
111+
}
112+
113+
var ch = line[0];
114+
if (ch == '-')
115+
{
116+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0);
117+
_deleted.Add(_last);
118+
_oldLine++;
119+
}
120+
else if (ch == '+')
121+
{
122+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine);
123+
_added.Add(_last);
124+
_newLine++;
125+
}
126+
else if (ch != '\\')
127+
{
128+
ProcessInlineHighlights();
129+
var match = REG_INDICATOR().Match(line);
130+
if (match.Success)
131+
{
132+
_oldLine = int.Parse(match.Groups[1].Value);
133+
_newLine = int.Parse(match.Groups[2].Value);
134+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
135+
_result.TextDiff.Lines.Add(_last);
136+
}
137+
else
138+
{
139+
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine);
140+
_result.TextDiff.Lines.Add(_last);
141+
_oldLine++;
142+
_newLine++;
143+
}
144+
}
145+
else if (line.Equals("\\ No newline at end of file", StringComparison.Ordinal))
146+
{
147+
_last.NoNewLineEndOfFile = true;
148+
}
149+
}
150+
}
151+
152+
private void ProcessInlineHighlights()
153+
{
154+
if (_deleted.Count > 0)
155+
{
156+
if (_added.Count == _deleted.Count)
157+
{
158+
for (int i = _added.Count - 1; i >= 0; i--)
159+
{
160+
var left = _deleted[i];
161+
var right = _added[i];
162+
163+
if (left.Content.Length > 1024 || right.Content.Length > 1024)
164+
continue;
165+
166+
var chunks = Models.TextInlineChange.Compare(left.Content, right.Content);
167+
if (chunks.Count > 4)
168+
continue;
169+
170+
foreach (var chunk in chunks)
171+
{
172+
if (chunk.DeletedCount > 0)
173+
left.Highlights.Add(new Models.TextRange(chunk.DeletedStart, chunk.DeletedCount));
174+
175+
if (chunk.AddedCount > 0)
176+
right.Highlights.Add(new Models.TextRange(chunk.AddedStart, chunk.AddedCount));
177+
}
178+
}
179+
}
180+
181+
_result.TextDiff.Lines.AddRange(_deleted);
182+
_deleted.Clear();
183+
}
184+
185+
if (_added.Count > 0)
186+
{
187+
_result.TextDiff.Lines.AddRange(_added);
188+
_added.Clear();
189+
}
190+
}
191+
192+
private readonly Models.DiffResult _result = new Models.DiffResult();
193+
private readonly List<Models.TextDiffLine> _deleted = new List<Models.TextDiffLine>();
194+
private readonly List<Models.TextDiffLine> _added = new List<Models.TextDiffLine>();
195+
private Models.TextDiffLine _last = null;
196+
private int _oldLine = 0;
197+
private int _newLine = 0;
198+
}
199+
}

0 commit comments

Comments
 (0)