Skip to content

Commit d4ceb3f

Browse files
jason-krausemsohn
authored andcommitted
Support diff3 conflict style in merges
Add conflict style setters to commands that can produce merge conflicts and default to merge.conflictStyle from the Git config. Propagate the setting into the merger and select the appropriate formatter in ResolveMerger. Activate existing diff3 support and add tests for the new behavior. Bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=442284 Change-Id: Ic201492b2f086712d61fa61dc2c53fcbe5de46b3
1 parent 490ed19 commit d4ceb3f

15 files changed

Lines changed: 451 additions & 13 deletions

org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import static org.eclipse.jgit.api.CherryPickCommitMessageProvider.ORIGINAL;
1313
import static org.eclipse.jgit.api.CherryPickCommitMessageProvider.ORIGINAL_WITH_REFERENCE;
14+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONFLICTSTYLE;
15+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
1416
import static org.junit.Assert.assertEquals;
1517
import static org.junit.Assert.assertFalse;
1618
import static org.junit.Assert.assertNotNull;
@@ -22,6 +24,7 @@
2224
import java.util.Iterator;
2325

2426
import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus;
27+
import org.eclipse.jgit.api.MergeCommand.ConflictStyle;
2528
import org.eclipse.jgit.api.ResetCommand.ResetType;
2629
import org.eclipse.jgit.api.errors.GitAPIException;
2730
import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -395,6 +398,23 @@ public void testCherryPickConflictMarkers() throws Exception {
395398
}
396399
}
397400

401+
@Test
402+
public void testCherryPickConflictDiff3Markers() throws Exception {
403+
try (Git git = new Git(db)) {
404+
RevCommit sideCommit = prepareCherryPick(git);
405+
406+
db.getConfig().setEnum(CONFIG_MERGE_SECTION, null,
407+
CONFIG_KEY_CONFLICTSTYLE, ConflictStyle.DIFF3);
408+
409+
CherryPickResult result = git.cherryPick()
410+
.include(sideCommit.getId()).call();
411+
assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
412+
413+
String expected = "<<<<<<< master\na(master)\n||||||| BASE\na\n=======\na(side)\n>>>>>>> 527460a side\n";
414+
checkFile(new File(db.getWorkTree(), "a"), expected);
415+
}
416+
}
417+
398418
@Test
399419
public void testCherryPickConflictFiresModifiedEvent() throws Exception {
400420
ListenerHandle listener = null;

org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
*/
1111
package org.eclipse.jgit.api;
1212

13+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONFLICTSTYLE;
14+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
1315
import static org.eclipse.jgit.lib.Constants.MASTER;
1416
import static org.eclipse.jgit.lib.Constants.R_HEADS;
1517
import static org.junit.Assert.assertEquals;
@@ -27,6 +29,7 @@
2729
import java.util.Iterator;
2830
import java.util.regex.Pattern;
2931

32+
import org.eclipse.jgit.api.MergeCommand.ConflictStyle;
3033
import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
3134
import org.eclipse.jgit.api.MergeResult.MergeStatus;
3235
import org.eclipse.jgit.api.ResetCommand.ResetType;
@@ -2220,4 +2223,37 @@ private void checkMergeFailedResult(final MergeResult result,
22202223
assertEquals(null, result.getConflicts());
22212224
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
22222225
}
2226+
2227+
@Test
2228+
public void testDiff3ConflictStyle() throws Exception {
2229+
try (Git git = new Git(db)) {
2230+
writeTrashFile("a", "1\na\n3\n");
2231+
git.add().addFilepattern("a").call();
2232+
RevCommit initialCommit = git.commit().setMessage("initial").call();
2233+
2234+
createBranch(initialCommit, "refs/heads/side");
2235+
checkoutBranch("refs/heads/side");
2236+
2237+
writeTrashFile("a", "1\na(side)\n3\n");
2238+
git.add().addFilepattern("a").call();
2239+
RevCommit secondCommit = git.commit().setMessage("side").call();
2240+
2241+
checkoutBranch("refs/heads/master");
2242+
2243+
writeTrashFile("a", "1\na(main)\n3\n");
2244+
git.add().addFilepattern("a").call();
2245+
git.commit().setMessage("main").call();
2246+
2247+
db.getConfig().setEnum(CONFIG_MERGE_SECTION, null,
2248+
CONFIG_KEY_CONFLICTSTYLE, ConflictStyle.DIFF3);
2249+
2250+
MergeResult result = git.merge().include(secondCommit.getId())
2251+
.setStrategy(MergeStrategy.RESOLVE).call();
2252+
assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
2253+
2254+
assertEquals(
2255+
"1\n<<<<<<< HEAD\na(main)\n||||||| BASE\na\n=======\na(side)\n>>>>>>> d97aebf6e0bbdb3f21f8a22ec8cbf1ac24d986d8\n3\n",
2256+
read(new File(db.getWorkTree(), "a")));
2257+
}
2258+
}
22232259
}

org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
package org.eclipse.jgit.api;
1111

1212
import static java.nio.charset.StandardCharsets.UTF_8;
13+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONFLICTSTYLE;
14+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
1315
import static org.junit.Assert.assertEquals;
1416
import static org.junit.Assert.assertFalse;
1517
import static org.junit.Assert.assertNotNull;
@@ -25,6 +27,7 @@
2527
import java.util.concurrent.Callable;
2628

2729
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
30+
import org.eclipse.jgit.api.MergeCommand.ConflictStyle;
2831
import org.eclipse.jgit.api.MergeResult.MergeStatus;
2932
import org.eclipse.jgit.api.errors.NoHeadException;
3033
import org.eclipse.jgit.junit.JGitTestUtil;
@@ -162,6 +165,40 @@ public void testPullConflict() throws Exception {
162165
assertEquals(StageState.BOTH_MODIFIED, conflicting.get("SomeFile.txt"));
163166
}
164167

168+
@Test
169+
public void testPullConflictDiff3() throws Exception {
170+
PullResult res = target.pull().call();
171+
// nothing to update since we don't have different data yet
172+
assertTrue(res.getFetchResult().getTrackingRefUpdates().isEmpty());
173+
assertTrue(res.getMergeResult().getMergeStatus()
174+
.equals(MergeStatus.ALREADY_UP_TO_DATE));
175+
176+
assertFileContentsEqual(targetFile, "Hello world");
177+
178+
// change the source file
179+
writeToFile(sourceFile, "Source change");
180+
source.add().addFilepattern("SomeFile.txt").call();
181+
source.commit().setMessage("Source change in remote").call();
182+
183+
// change the target file
184+
writeToFile(targetFile, "Target change");
185+
target.add().addFilepattern("SomeFile.txt").call();
186+
target.commit().setMessage("Target change in local").call();
187+
188+
target.getRepository().getConfig().setEnum(CONFIG_MERGE_SECTION, null,
189+
CONFIG_KEY_CONFLICTSTYLE, ConflictStyle.DIFF3);
190+
191+
res = target.pull().call();
192+
193+
String sourceChangeString = "Source change\n>>>>>>> branch 'master' of "
194+
+ target.getRepository().getConfig().getString("remote",
195+
"origin", "url");
196+
197+
assertFileContentsEqual(targetFile, "<<<<<<< HEAD\n" + "Target change\n"
198+
+ "||||||| BASE\n" + "Hello world\n" + "=======\n"
199+
+ sourceChangeString + "\n");
200+
}
201+
165202
@Test
166203
public void testPullConflictTheirs() throws Exception {
167204
PullResult res = target.pull().call();

org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
package org.eclipse.jgit.api;
1111

1212
import static java.nio.charset.StandardCharsets.UTF_8;
13+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONFLICTSTYLE;
14+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
1315
import static org.hamcrest.CoreMatchers.equalTo;
1416
import static org.hamcrest.CoreMatchers.not;
1517
import static org.hamcrest.MatcherAssert.assertThat;
@@ -30,6 +32,7 @@
3032
import java.util.Iterator;
3133
import java.util.List;
3234

35+
import org.eclipse.jgit.api.MergeCommand.ConflictStyle;
3336
import org.eclipse.jgit.api.MergeResult.MergeStatus;
3437
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler;
3538
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler2;
@@ -370,6 +373,43 @@ public void testRebaseNoMergeBaseConflict() throws Exception {
370373
+ ">>>>>>> " + first.abbreviate(7).name() + " Add file\n");
371374
}
372375

376+
/**
377+
* Create a commit A and an unrelated commit B creating the same file with
378+
* different content. Then rebase A onto B. The rebase should stop with a
379+
* conflict and the conflict style should be DIFF3.
380+
*
381+
* @throws Exception on errors
382+
*/
383+
@Test
384+
public void testRebaseNoMergeBaseConflictDiff3() throws Exception {
385+
writeTrashFile(FILE1, FILE1);
386+
git.add().addFilepattern(FILE1).call();
387+
RevCommit first = git.commit().setMessage("Add file").call();
388+
File file1 = new File(db.getWorkTree(), FILE1);
389+
assertTrue(file1.exists());
390+
// Create an independent branch
391+
git.checkout().setOrphan(true).setName("orphan").call();
392+
git.rm().addFilepattern(FILE1).call();
393+
assertFalse(file1.exists());
394+
writeTrashFile(FILE1, "something else");
395+
git.add().addFilepattern(FILE1).call();
396+
git.commit().setMessage("Orphan").call();
397+
checkoutBranch("refs/heads/master");
398+
assertEquals(first.getId(), db.resolve("HEAD"));
399+
400+
db.getConfig().setEnum(CONFIG_MERGE_SECTION, null,
401+
CONFIG_KEY_CONFLICTSTYLE, ConflictStyle.DIFF3);
402+
403+
RebaseResult res = git.rebase().setUpstream("refs/heads/orphan").call();
404+
assertEquals(Status.STOPPED, res.getStatus());
405+
assertEquals(first, res.getCurrentCommit());
406+
checkFile(file1,
407+
"<<<<<<< Upstream, based on orphan\n" + "something else\n"
408+
+ "||||||| BASE\n" + "=======\n" + "file1\n"
409+
+ ">>>>>>> "
410+
+ first.abbreviate(7).name() + " Add file\n");
411+
}
412+
373413
/**
374414
* Create the following commits and then attempt to rebase topic onto
375415
* master. This will serialize the branches.

org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*/
1010
package org.eclipse.jgit.api;
1111

12+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONFLICTSTYLE;
13+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
1214
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
1315
import static org.junit.Assert.assertEquals;
1416
import static org.junit.Assert.assertFalse;
@@ -20,6 +22,7 @@
2022
import java.io.IOException;
2123
import java.util.Iterator;
2224

25+
import org.eclipse.jgit.api.MergeCommand.ConflictStyle;
2326
import org.eclipse.jgit.api.MergeResult.MergeStatus;
2427
import org.eclipse.jgit.api.ResetCommand.ResetType;
2528
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -376,6 +379,25 @@ public void testRevertConflictMarkers() throws Exception {
376379
}
377380
}
378381

382+
@Test
383+
public void testRevertConflictMarkersDiff3() throws Exception {
384+
try (Git git = new Git(db)) {
385+
RevCommit sideCommit = prepareRevert(git);
386+
387+
db.getConfig().setEnum(CONFIG_MERGE_SECTION, null,
388+
CONFIG_KEY_CONFLICTSTYLE, ConflictStyle.DIFF3);
389+
390+
RevertCommand revert = git.revert();
391+
RevCommit newHead = revert.include(sideCommit.getId()).call();
392+
assertNull(newHead);
393+
MergeResult result = revert.getFailingResult();
394+
assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
395+
396+
String expected = "<<<<<<< master\na(latest)\n||||||| BASE\na(previous)\n=======\na\n>>>>>>> ca96c31 second master\n";
397+
checkFile(new File(db.getWorkTree(), "a"), expected);
398+
}
399+
}
400+
379401
@Test
380402
public void testRevertOurCommitName() throws Exception {
381403
try (Git git = new Git(db)) {

org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@
99
*/
1010
package org.eclipse.jgit.api;
1111

12+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONFLICTSTYLE;
13+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
1214
import static org.junit.Assert.assertEquals;
1315
import static org.junit.Assert.assertFalse;
1416
import static org.junit.Assert.assertNotNull;
17+
import static org.junit.Assert.assertThrows;
1518
import static org.junit.Assert.assertTrue;
1619
import static org.junit.Assert.fail;
1720

1821
import java.io.File;
1922
import java.text.MessageFormat;
2023

24+
import org.eclipse.jgit.api.MergeCommand.ConflictStyle;
2125
import org.eclipse.jgit.api.errors.InvalidRefNameException;
2226
import org.eclipse.jgit.api.errors.JGitInternalException;
2327
import org.eclipse.jgit.api.errors.NoHeadException;
@@ -428,6 +432,43 @@ public void stashedContentMerge() throws Exception {
428432
read(PATH));
429433
}
430434

435+
@Test
436+
public void stashedContentMergeDiff3() throws Exception {
437+
writeTrashFile(PATH, "content\nmore content\n");
438+
git.add().addFilepattern(PATH).call();
439+
git.commit().setMessage("more content").call();
440+
441+
writeTrashFile(PATH, "content\nhead change\nmore content\n");
442+
git.add().addFilepattern(PATH).call();
443+
git.commit().setMessage("even content").call();
444+
445+
writeTrashFile(PATH, "content\nstashed change\nmore content\n");
446+
447+
db.getConfig().setEnum(CONFIG_MERGE_SECTION, null,
448+
CONFIG_KEY_CONFLICTSTYLE, ConflictStyle.DIFF3);
449+
450+
RevCommit stashed = git.stashCreate().call();
451+
assertNotNull(stashed);
452+
assertEquals("content\nhead change\nmore content\n",
453+
read(committedFile));
454+
assertTrue(git.status().call().isClean());
455+
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
456+
457+
writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
458+
git.add().addFilepattern(PATH).call();
459+
git.commit().setMessage("committed change").call();
460+
recorder.assertNoEvent();
461+
462+
assertThrows(StashApplyFailureException.class,
463+
() -> git.stashApply().call());
464+
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
465+
Status status = new StatusCommand(db).call();
466+
assertEquals(1, status.getConflicting().size());
467+
assertEquals(
468+
"content\n<<<<<<< HEAD\n||||||| stashed HEAD\nhead change\n=======\nstashed change\n>>>>>>> stash\nmore content\ncommitted change\n",
469+
read(PATH));
470+
}
471+
431472
@Test
432473
public void stashedContentMergeXtheirs() throws Exception {
433474
writeTrashFile(PATH, "content\nmore content\n");

org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import static java.nio.charset.StandardCharsets.UTF_8;
1313
import static java.time.Instant.EPOCH;
14+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONFLICTSTYLE;
15+
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_MERGE_SECTION;
1416
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
1517
import static org.junit.Assert.assertEquals;
1618
import static org.junit.Assert.assertFalse;
@@ -30,6 +32,7 @@
3032
import java.util.Set;
3133

3234
import org.eclipse.jgit.api.Git;
35+
import org.eclipse.jgit.api.MergeCommand.ConflictStyle;
3336
import org.eclipse.jgit.api.MergeResult;
3437
import org.eclipse.jgit.api.MergeResult.MergeStatus;
3538
import org.eclipse.jgit.api.RebaseResult;
@@ -963,6 +966,39 @@ public void checkContentMergeConflict(MergeStrategy strategy)
963966
assertEquals(expected, read("file"));
964967
}
965968

969+
@Theory
970+
public void checkContentMergeConflictDiff3(MergeStrategy strategy)
971+
throws Exception {
972+
Git git = Git.wrap(db);
973+
974+
writeTrashFile("file", "1\n2\n3");
975+
git.add().addFilepattern("file").call();
976+
RevCommit first = git.commit().setMessage("added file").call();
977+
978+
writeTrashFile("file", "1master\n2\n3");
979+
git.commit().setAll(true).setMessage("modified file on master").call();
980+
981+
git.checkout().setCreateBranch(true).setStartPoint(first)
982+
.setName("side").call();
983+
writeTrashFile("file", "1side\n2\n3");
984+
RevCommit sideCommit = git.commit().setAll(true)
985+
.setMessage("modified file on side").call();
986+
987+
git.checkout().setName("master").call();
988+
989+
db.getConfig().setEnum(CONFIG_MERGE_SECTION, null,
990+
CONFIG_KEY_CONFLICTSTYLE, ConflictStyle.DIFF3);
991+
992+
MergeResult result = git.merge().setStrategy(strategy)
993+
.include(sideCommit).call();
994+
assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
995+
String expected = "<<<<<<< HEAD\n" + "1master\n" + "||||||| BASE\n"
996+
+ "1\n" + "=======\n" + "1side\n" + ">>>>>>> "
997+
+ sideCommit.name() + "\n" + "2\n"
998+
+ "3";
999+
assertEquals(expected, read("file"));
1000+
}
1001+
9661002
@Theory
9671003
public void checkContentMergeConflict_noTree(MergeStrategy strategy)
9681004
throws Exception {

0 commit comments

Comments
 (0)