get_merge_conflicts and resolve_conflict_hunk let an MCP caller resolve a leaveConflictsInTree merge hunk-by-hunk (ours/theirs/base/literal text) instead of grep-parsing diff3 markers itself. Reuses the existing TaskMergeService/ConflictMarkerParser machinery (adds MergeSegment.StartLine and a non-staging TaskMergeService.WriteConflictFileAsync) rather than a second parser. A partially resolved file is written without git add so continue_merge keeps refusing while any hunk still has markers.
138 lines
4.4 KiB
C#
138 lines
4.4 KiB
C#
using System.Linq;
|
|
using ClaudeDo.Data.Git;
|
|
using Xunit;
|
|
|
|
namespace ClaudeDo.Data.Tests;
|
|
|
|
public class ConflictMarkerParserTests
|
|
{
|
|
[Fact]
|
|
public void NoMarkers_YieldsSingleStableSegment_AndRoundTrips()
|
|
{
|
|
const string text = "just\nsome\nplain\nlines\n";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
var only = Assert.Single(segments);
|
|
Assert.False(only.IsConflict);
|
|
Assert.Equal(text, ConflictMarkerParser.Compose(segments, c => c.Ours));
|
|
Assert.False(ConflictMarkerParser.HasConflicts(text));
|
|
}
|
|
|
|
[Fact]
|
|
public void SimpleConflict_SplitsIntoStable_Conflict_Stable()
|
|
{
|
|
const string text =
|
|
"line1\n<<<<<<< HEAD\nours line\n=======\ntheirs line\n>>>>>>> branch\nline2\n";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
Assert.Equal(3, segments.Count);
|
|
Assert.Equal("line1\n", segments[0].Text);
|
|
Assert.True(segments[1].IsConflict);
|
|
Assert.Equal("ours line\n", segments[1].Ours);
|
|
Assert.Equal("theirs line\n", segments[1].Theirs);
|
|
Assert.Null(segments[1].Base);
|
|
Assert.Equal("line2\n", segments[2].Text);
|
|
Assert.True(ConflictMarkerParser.HasConflicts(text));
|
|
}
|
|
|
|
[Fact]
|
|
public void AcceptingOurs_Or_Theirs_ProducesTheResolvedFile()
|
|
{
|
|
const string text =
|
|
"line1\n<<<<<<< HEAD\nours line\n=======\ntheirs line\n>>>>>>> branch\nline2\n";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
Assert.Equal("line1\nours line\nline2\n",
|
|
ConflictMarkerParser.Compose(segments, c => c.Ours));
|
|
Assert.Equal("line1\ntheirs line\nline2\n",
|
|
ConflictMarkerParser.Compose(segments, c => c.Theirs));
|
|
// "Accept both" = ours followed by theirs.
|
|
Assert.Equal("line1\nours line\ntheirs line\nline2\n",
|
|
ConflictMarkerParser.Compose(segments, c => c.Ours + c.Theirs));
|
|
}
|
|
|
|
[Fact]
|
|
public void Diff3Style_CapturesTheMergeBase()
|
|
{
|
|
const string text =
|
|
"a\n<<<<<<< HEAD\nX\n||||||| base\nB\n=======\nY\n>>>>>>> branch\nz\n";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
Assert.Equal(3, segments.Count);
|
|
Assert.Equal("X\n", segments[1].Ours);
|
|
Assert.Equal("B\n", segments[1].Base);
|
|
Assert.Equal("Y\n", segments[1].Theirs);
|
|
}
|
|
|
|
[Fact]
|
|
public void Segments_ExposeStartLine()
|
|
{
|
|
const string text =
|
|
"line1\nline2\n<<<<<<< HEAD\nours\n=======\ntheirs\n>>>>>>> branch\nline3\n";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
Assert.Equal(3, segments.Count);
|
|
Assert.Equal(1, segments[0].StartLine); // "line1\nline2\n"
|
|
Assert.Equal(3, segments[1].StartLine); // the "<<<<<<<" line
|
|
Assert.Equal(8, segments[2].StartLine); // "line3\n"
|
|
}
|
|
|
|
[Fact]
|
|
public void MultipleConflicts_AreEachCaptured()
|
|
{
|
|
const string text =
|
|
"<<<<<<< HEAD\nA\n=======\nB\n>>>>>>> br\nmid\n<<<<<<< HEAD\nC\n=======\nD\n>>>>>>> br\n";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
Assert.Equal(3, segments.Count);
|
|
Assert.True(segments[0].IsConflict);
|
|
Assert.Equal("A\n", segments[0].Ours);
|
|
Assert.Equal("mid\n", segments[1].Text);
|
|
Assert.True(segments[2].IsConflict);
|
|
Assert.Equal("D\n", segments[2].Theirs);
|
|
}
|
|
|
|
[Fact]
|
|
public void CrlfLineEndings_ArePreserved()
|
|
{
|
|
const string text =
|
|
"a\r\n<<<<<<< HEAD\r\nX\r\n=======\r\nY\r\n>>>>>>> br\r\nb\r\n";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
Assert.Equal("a\r\nX\r\nb\r\n", ConflictMarkerParser.Compose(segments, c => c.Ours));
|
|
}
|
|
|
|
[Fact]
|
|
public void ConflictAtEndOfFile_WithoutTrailingNewline_IsParsed()
|
|
{
|
|
const string text = "a\n<<<<<<< HEAD\nX\n=======\nY\n>>>>>>> br";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
Assert.Equal(2, segments.Count);
|
|
Assert.Equal("a\n", segments[0].Text);
|
|
Assert.True(segments[1].IsConflict);
|
|
Assert.Equal("X\n", segments[1].Ours);
|
|
Assert.Equal("Y\n", segments[1].Theirs);
|
|
}
|
|
|
|
[Fact]
|
|
public void SevenEqualsInOrdinaryText_IsNotTreatedAsAConflict()
|
|
{
|
|
const string text = "title\n=======\nbody\n";
|
|
|
|
var segments = ConflictMarkerParser.Parse(text);
|
|
|
|
var only = Assert.Single(segments);
|
|
Assert.False(only.IsConflict);
|
|
Assert.Equal(text, ConflictMarkerParser.Compose(segments, c => c.Ours));
|
|
}
|
|
}
|