aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java5
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java4
-rw-r--r--org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java100
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java61
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java19
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java263
-rw-r--r--org.eclipse.jgit/.settings/.api_filters20
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java86
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java25
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java52
23 files changed, 679 insertions, 52 deletions
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
index 1079d98439..136c64726f 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
@@ -229,6 +229,11 @@ public abstract class JGitTestUtil {
return read(file);
}
+ public static boolean check(final Repository db, final String name) {
+ File file = new File(db.getWorkTree(), name);
+ return file.exists();
+ }
+
public static void deleteTrashFile(final Repository db,
final String name) throws IOException {
File path = new File(db.getWorkTree(), name);
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
index c1e0a2dcf3..bc225cc017 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
@@ -119,6 +119,10 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase {
return JGitTestUtil.read(db, name);
}
+ protected boolean check(final String name) {
+ return JGitTestUtil.check(db, name);
+ }
+
protected void deleteTrashFile(final String name) throws IOException {
JGitTestUtil.deleteTrashFile(db, name);
}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java
index bcb3cb7296..90efae286b 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java
@@ -149,7 +149,7 @@ public class RepoTest extends CLIRepositoryTestCase {
!groupAUri.startsWith(prefix) ||
!groupBUri.startsWith(prefix)) {
start++;
- rootUri = defaultUri.substring(0, start);
+ rootUri = defaultUri.substring(0, start) + "manifest";
defaultUri = defaultUri.substring(start);
notDefaultUri = notDefaultUri.substring(start);
groupAUri = groupAUri.substring(start);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
index bb3e7a92e8..060168c673 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
@@ -99,6 +99,8 @@ public class RevertCommandTest extends RepositoryTestCase {
git.revert().include(fixingA).call();
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+
assertTrue(new File(db.getWorkTree(), "b").exists());
checkFile(new File(db.getWorkTree(), "a"),
"first line\nsec. line\nthird line\nfourth line\n");
@@ -124,6 +126,104 @@ public class RevertCommandTest extends RepositoryTestCase {
}
@Test
+ public void testRevertMultiple() throws IOException, JGitInternalException,
+ GitAPIException {
+ Git git = new Git(db);
+
+ writeTrashFile("a", "first\n");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("add first").call();
+
+ writeTrashFile("a", "first\nsecond\n");
+ git.add().addFilepattern("a").call();
+ RevCommit secondCommit = git.commit().setMessage("add second").call();
+
+ writeTrashFile("a", "first\nsecond\nthird\n");
+ git.add().addFilepattern("a").call();
+ RevCommit thirdCommit = git.commit().setMessage("add third").call();
+
+ git.revert().include(thirdCommit).include(secondCommit).call();
+
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+
+ checkFile(new File(db.getWorkTree(), "a"), "first\n");
+ Iterator<RevCommit> history = git.log().call().iterator();
+ RevCommit revertCommit = history.next();
+ String expectedMessage = "Revert \"add second\"\n\n"
+ + "This reverts commit "
+ + secondCommit.getId().getName() + ".\n";
+ assertEquals(expectedMessage, revertCommit.getFullMessage());
+ revertCommit = history.next();
+ expectedMessage = "Revert \"add third\"\n\n"
+ + "This reverts commit " + thirdCommit.getId().getName()
+ + ".\n";
+ assertEquals(expectedMessage, revertCommit.getFullMessage());
+ assertEquals("add third", history.next().getFullMessage());
+ assertEquals("add second", history.next().getFullMessage());
+ assertEquals("add first", history.next().getFullMessage());
+ assertFalse(history.hasNext());
+
+ ReflogReader reader = db.getReflogReader(Constants.HEAD);
+ assertTrue(reader.getLastEntry().getComment()
+ .startsWith("revert: Revert \""));
+ reader = db.getReflogReader(db.getBranch());
+ assertTrue(reader.getLastEntry().getComment()
+ .startsWith("revert: Revert \""));
+
+ }
+
+ @Test
+ public void testRevertMultipleWithFail() throws IOException,
+ JGitInternalException, GitAPIException {
+ Git git = new Git(db);
+
+ writeTrashFile("a", "first\n");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("add first").call();
+
+ writeTrashFile("a", "first\nsecond\n");
+ git.add().addFilepattern("a").call();
+ RevCommit secondCommit = git.commit().setMessage("add second").call();
+
+ writeTrashFile("a", "first\nsecond\nthird\n");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("add third").call();
+
+ writeTrashFile("a", "first\nsecond\nthird\nfourth\n");
+ git.add().addFilepattern("a").call();
+ RevCommit fourthCommit = git.commit().setMessage("add fourth").call();
+
+ git.revert().include(fourthCommit).include(secondCommit).call();
+
+ // not SAFE because it failed
+ assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
+
+ checkFile(new File(db.getWorkTree(), "a"), "first\n"
+ + "<<<<<<< master\n" + "second\n" + "third\n" + "=======\n"
+ + ">>>>>>> " + secondCommit.getId().abbreviate(7).name()
+ + " add second\n");
+ Iterator<RevCommit> history = git.log().call().iterator();
+ RevCommit revertCommit = history.next();
+ String expectedMessage = "Revert \"add fourth\"\n\n"
+ + "This reverts commit " + fourthCommit.getId().getName()
+ + ".\n";
+ assertEquals(expectedMessage, revertCommit.getFullMessage());
+ assertEquals("add fourth", history.next().getFullMessage());
+ assertEquals("add third", history.next().getFullMessage());
+ assertEquals("add second", history.next().getFullMessage());
+ assertEquals("add first", history.next().getFullMessage());
+ assertFalse(history.hasNext());
+
+ ReflogReader reader = db.getReflogReader(Constants.HEAD);
+ assertTrue(reader.getLastEntry().getComment()
+ .startsWith("revert: Revert \""));
+ reader = db.getReflogReader(db.getBranch());
+ assertTrue(reader.getLastEntry().getComment()
+ .startsWith("revert: Revert \""));
+
+ }
+
+ @Test
public void testRevertDirtyIndex() throws Exception {
Git git = new Git(db);
RevCommit sideCommit = prepareRevert(git);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
index 7d645cf935..3e5ef02738 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
@@ -512,6 +512,65 @@ public class RepoCommandTest extends RepositoryTestCase {
assertFalse("The foo submodule shouldn't exist", foo);
}
+ @Test
+ public void testRemoveOverlappingBare() throws Exception {
+ Repository remoteDb = createBareRepository();
+ Repository tempDb = createWorkRepository();
+ StringBuilder xmlContent = new StringBuilder();
+ xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+ .append("<manifest>")
+ .append("<remote name=\"remote1\" fetch=\".\" />")
+ .append("<default revision=\"master\" remote=\"remote1\" />")
+ .append("<project path=\"foo/bar\" name=\"")
+ .append(groupBUri)
+ .append("\" />")
+ .append("<project path=\"a\" name=\"")
+ .append(groupAUri)
+ .append("\" />")
+ .append("<project path=\"foo\" name=\"")
+ .append(defaultUri)
+ .append("\" />")
+ .append("</manifest>");
+ JGitTestUtil.writeTrashFile(
+ tempDb, "manifest.xml", xmlContent.toString());
+ RepoCommand command = new RepoCommand(remoteDb);
+ command
+ .setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+ .setURI(rootUri)
+ .call();
+ // Clone it
+ File directory = createTempDirectory("testRemoveOverlappingBare");
+ Repository localDb = Git
+ .cloneRepository()
+ .setDirectory(directory)
+ .setURI(remoteDb.getDirectory().toURI().toString())
+ .call()
+ .getRepository();
+ // The .gitmodules file should have 'submodule "foo"' and shouldn't have
+ // 'submodule "foo/bar"' lines.
+ File dotmodules = new File(localDb.getWorkTree(),
+ Constants.DOT_GIT_MODULES);
+ BufferedReader reader = new BufferedReader(new FileReader(dotmodules));
+ boolean foo = false;
+ boolean foobar = false;
+ boolean a = false;
+ while (true) {
+ String line = reader.readLine();
+ if (line == null)
+ break;
+ if (line.contains("submodule \"foo\""))
+ foo = true;
+ if (line.contains("submodule \"foo/bar\""))
+ foobar = true;
+ if (line.contains("submodule \"a\""))
+ a = true;
+ }
+ reader.close();
+ assertTrue("The foo submodule should exist", foo);
+ assertFalse("The foo/bar submodule shouldn't exist", foobar);
+ assertTrue("The a submodule should exist", a);
+ }
+
private void resolveRelativeUris() {
// Find the longest common prefix ends with "/" as rootUri.
defaultUri = defaultDb.getDirectory().toURI().toString();
@@ -526,7 +585,7 @@ public class RepoCommandTest extends RepositoryTestCase {
!groupAUri.startsWith(prefix) ||
!groupBUri.startsWith(prefix)) {
start++;
- rootUri = defaultUri.substring(0, start);
+ rootUri = defaultUri.substring(0, start) + "manifest";
defaultUri = defaultUri.substring(start);
notDefaultUri = notDefaultUri.substring(start);
groupAUri = groupAUri.substring(start);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
index afbad6ab2c..f7e6fa9b79 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
@@ -72,6 +72,8 @@ import org.eclipse.jgit.errors.CheckoutConflictException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
@@ -213,6 +215,23 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertIndex(mkmap("x", "x"));
}
+ /**
+ * Test first checkout in a repo
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testInitialCheckout() throws Exception {
+ Git git = new Git(db);
+
+ TestRepository<Repository> db_t = new TestRepository<Repository>(db);
+ BranchBuilder master = db_t.branch("master");
+ master.commit().add("f", "1").message("m0").create();
+ assertFalse(new File(db.getWorkTree(), "f").exists());
+ git.checkout().setName("master").call();
+ assertTrue(new File(db.getWorkTree(), "f").exists());
+ }
+
private DirCacheCheckout resetHard(RevCommit commit)
throws NoWorkTreeException,
CorruptObjectException, IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
index 8f43f7f868..37cc88be11 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
@@ -44,6 +44,7 @@ package org.eclipse.jgit.merge;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.File;
@@ -178,7 +179,7 @@ public class RecursiveMergerTest extends RepositoryTestCase {
/**
* Merging m2,s2 from the following topology. The same file is modified
* in both branches. The modifications should be mergeable. m2 and s2
- * contain branch specific conflict resolutions. Therefore m2 and don't contain the same content.
+ * contain branch specific conflict resolutions. Therefore m2 and s2 don't contain the same content.
*
* <pre>
* m0--m1--m2
@@ -260,6 +261,266 @@ public class RecursiveMergerTest extends RepositoryTestCase {
@Theory
/**
* Merging m2,s2 from the following topology. The same file is modified
+ * in both branches. The modifications should be mergeable but only if the automerge of m1 and s1
+ * is choosen as parent. Choosing m0 as parent would not be sufficient (in contrast to the merge in
+ * crissCrossMerge_mergeable). m2 and s2 contain branch specific conflict resolutions. Therefore m2
+ * and s2 don't contain the same content.
+ *
+ * <pre>
+ * m0--m1--m2
+ * \ \/
+ * \ /\
+ * s1--s2
+ * </pre>
+ */
+ public void crissCrossMerge_mergeable2(MergeStrategy strategy,
+ IndexState indexState, WorktreeState worktreeState)
+ throws Exception {
+ if (!validateStates(indexState, worktreeState))
+ return;
+
+ BranchBuilder master = db_t.branch("master");
+ RevCommit m0 = master.commit().add("f", "1\n2\n3\n")
+ .message("m0")
+ .create();
+ RevCommit m1 = master.commit().add("f", "1-master\n2\n3\n")
+ .message("m1").create();
+ db_t.getRevWalk().parseCommit(m1);
+
+ BranchBuilder side = db_t.branch("side");
+ RevCommit s1 = side.commit().parent(m0).add("f", "1\n2\n3-side\n")
+ .message("s1").create();
+ RevCommit s2 = side.commit().parent(m1)
+ .add("f", "1-master\n2\n3-side-r\n")
+ .message("s2(merge)")
+ .create();
+ RevCommit m2 = master.commit().parent(s1)
+ .add("f", "1-master-r\n2\n3-side\n")
+ .message("m2(merge)")
+ .create();
+
+ Git git = Git.wrap(db);
+ git.checkout().setName("master").call();
+ modifyWorktree(worktreeState, "f", "side");
+ modifyIndex(indexState, "f", "side");
+
+ ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+ worktreeState == WorktreeState.Bare);
+ if (worktreeState != WorktreeState.Bare)
+ merger.setWorkingTreeIterator(new FileTreeIterator(db));
+ try {
+ boolean expectSuccess = true;
+ if (!(indexState == IndexState.Bare
+ || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
+ // index is dirty
+ expectSuccess = false;
+ else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
+ || worktreeState == WorktreeState.SameAsOther)
+ expectSuccess = false;
+ assertEquals(Boolean.valueOf(expectSuccess),
+ Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+ assertEquals(MergeStrategy.RECURSIVE, strategy);
+ if (!expectSuccess)
+ // if the merge was not successful skip testing the state of
+ // index and workingtree
+ return;
+ assertEquals(
+ "1-master-r\n2\n3-side-r",
+ contentAsString(db, merger.getResultTreeId(), "f"));
+ if (indexState != IndexState.Bare)
+ assertEquals(
+ "[f, mode:100644, content:1-master-r\n2\n3-side-r\n]",
+ indexState(RepositoryTestCase.CONTENT));
+ if (worktreeState != WorktreeState.Bare
+ && worktreeState != WorktreeState.Missing)
+ assertEquals(
+ "1-master-r\n2\n3-side-r\n",
+ read("f"));
+ } catch (NoMergeBaseException e) {
+ assertEquals(MergeStrategy.RESOLVE, strategy);
+ assertEquals(e.getReason(),
+ MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+ }
+ }
+
+ @Theory
+ /**
+ * Merging m2,s2 from the following topology. m1 and s1 are not mergeable
+ * without conflicts. The same file is modified in both branches. The
+ * modifications should be mergeable but only if the merge result of
+ * merging m1 and s1 is choosen as parent (including the conflict markers).
+ *
+ * <pre>
+ * m0--m1--m2
+ * \ \/
+ * \ /\
+ * s1--s2
+ * </pre>
+ */
+ public void crissCrossMerge_ParentsNotMergeable(MergeStrategy strategy,
+ IndexState indexState, WorktreeState worktreeState)
+ throws Exception {
+ if (!validateStates(indexState, worktreeState))
+ return;
+
+ BranchBuilder master = db_t.branch("master");
+ RevCommit m0 = master.commit().add("f", "1\n2\n3\n").message("m0")
+ .create();
+ RevCommit m1 = master.commit().add("f", "1\nx(master)\n2\n3\n")
+ .message("m1").create();
+ db_t.getRevWalk().parseCommit(m1);
+
+ BranchBuilder side = db_t.branch("side");
+ RevCommit s1 = side.commit().parent(m0)
+ .add("f", "1\nx(side)\n2\n3\ny(side)\n")
+ .message("s1").create();
+ RevCommit s2 = side.commit().parent(m1)
+ .add("f", "1\nx(side)\n2\n3\ny(side-again)\n")
+ .message("s2(merge)")
+ .create();
+ RevCommit m2 = master.commit().parent(s1)
+ .add("f", "1\nx(side)\n2\n3\ny(side)\n").message("m2(merge)")
+ .create();
+
+ Git git = Git.wrap(db);
+ git.checkout().setName("master").call();
+ modifyWorktree(worktreeState, "f", "side");
+ modifyIndex(indexState, "f", "side");
+
+ ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+ worktreeState == WorktreeState.Bare);
+ if (worktreeState != WorktreeState.Bare)
+ merger.setWorkingTreeIterator(new FileTreeIterator(db));
+ try {
+ boolean expectSuccess = true;
+ if (!(indexState == IndexState.Bare
+ || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
+ // index is dirty
+ expectSuccess = false;
+ else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
+ || worktreeState == WorktreeState.SameAsOther)
+ expectSuccess = false;
+ assertEquals("Merge didn't return as expected: strategy:"
+ + strategy.getName() + ", indexState:" + indexState
+ + ", worktreeState:" + worktreeState + " . ",
+ Boolean.valueOf(expectSuccess),
+ Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+ assertEquals(MergeStrategy.RECURSIVE, strategy);
+ if (!expectSuccess)
+ // if the merge was not successful skip testing the state of
+ // index and workingtree
+ return;
+ assertEquals("1\nx(side)\n2\n3\ny(side-again)",
+ contentAsString(db, merger.getResultTreeId(), "f"));
+ if (indexState != IndexState.Bare)
+ assertEquals(
+ "[f, mode:100644, content:1\nx(side)\n2\n3\ny(side-again)\n]",
+ indexState(RepositoryTestCase.CONTENT));
+ if (worktreeState != WorktreeState.Bare
+ && worktreeState != WorktreeState.Missing)
+ assertEquals("1\nx(side)\n2\n3\ny(side-again)\n", read("f"));
+ } catch (NoMergeBaseException e) {
+ assertEquals(MergeStrategy.RESOLVE, strategy);
+ assertEquals(e.getReason(),
+ MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+ }
+ }
+
+ @Theory
+ /**
+ * Merging m2,s2 from the following topology. The same file is modified
+ * in both branches. The modifications should be mergeable but only if the automerge of m1 and s1
+ * is choosen as parent. On both branches delete and modify files untouched on the other branch.
+ * On both branches create new files. Make sure these files are correctly merged and
+ * exist in the workingtree.
+ *
+ * <pre>
+ * m0--m1--m2
+ * \ \/
+ * \ /\
+ * s1--s2
+ * </pre>
+ */
+ public void crissCrossMerge_checkOtherFiles(MergeStrategy strategy,
+ IndexState indexState, WorktreeState worktreeState)
+ throws Exception {
+ if (!validateStates(indexState, worktreeState))
+ return;
+
+ BranchBuilder master = db_t.branch("master");
+ RevCommit m0 = master.commit().add("f", "1\n2\n3\n").add("m.m", "0")
+ .add("m.d", "0").add("s.m", "0").add("s.d", "0").message("m0")
+ .create();
+ RevCommit m1 = master.commit().add("f", "1-master\n2\n3\n")
+ .add("m.c", "0").add("m.m", "1").rm("m.d").message("m1")
+ .create();
+ db_t.getRevWalk().parseCommit(m1);
+
+ BranchBuilder side = db_t.branch("side");
+ RevCommit s1 = side.commit().parent(m0).add("f", "1\n2\n3-side\n")
+ .add("s.c", "0").add("s.m", "1").rm("s.d").message("s1")
+ .create();
+ RevCommit s2 = side.commit().parent(m1)
+ .add("f", "1-master\n2\n3-side-r\n").add("m.m", "1")
+ .add("m.c", "0").rm("m.d").message("s2(merge)").create();
+ RevCommit m2 = master.commit().parent(s1)
+ .add("f", "1-master-r\n2\n3-side\n").add("s.m", "1")
+ .add("s.c", "0").rm("s.d").message("m2(merge)").create();
+
+ Git git = Git.wrap(db);
+ git.checkout().setName("master").call();
+ modifyWorktree(worktreeState, "f", "side");
+ modifyIndex(indexState, "f", "side");
+
+ ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+ worktreeState == WorktreeState.Bare);
+ if (worktreeState != WorktreeState.Bare)
+ merger.setWorkingTreeIterator(new FileTreeIterator(db));
+ try {
+ boolean expectSuccess = true;
+ if (!(indexState == IndexState.Bare
+ || indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
+ // index is dirty
+ expectSuccess = false;
+ else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
+ || worktreeState == WorktreeState.SameAsOther)
+ expectSuccess = false;
+ assertEquals(Boolean.valueOf(expectSuccess),
+ Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+ assertEquals(MergeStrategy.RECURSIVE, strategy);
+ if (!expectSuccess)
+ // if the merge was not successful skip testing the state of
+ // index and workingtree
+ return;
+ assertEquals(
+ "1-master-r\n2\n3-side-r",
+ contentAsString(db, merger.getResultTreeId(), "f"));
+ if (indexState != IndexState.Bare)
+ assertEquals(
+ "[f, mode:100644, content:1-master-r\n2\n3-side-r\n][m.c, mode:100644, content:0][m.m, mode:100644, content:1][s.c, mode:100644, content:0][s.m, mode:100644, content:1]",
+ indexState(RepositoryTestCase.CONTENT));
+ if (worktreeState != WorktreeState.Bare
+ && worktreeState != WorktreeState.Missing) {
+ assertEquals(
+ "1-master-r\n2\n3-side-r\n",
+ read("f"));
+ assertTrue(check("s.c"));
+ assertFalse(check("s.d"));
+ assertTrue(check("s.m"));
+ assertTrue(check("m.c"));
+ assertFalse(check("m.d"));
+ assertTrue(check("m.m"));
+ }
+ } catch (NoMergeBaseException e) {
+ assertEquals(MergeStrategy.RESOLVE, strategy);
+ assertEquals(e.getReason(),
+ MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+ }
+ }
+
+ @Theory
+ /**
+ * Merging m2,s2 from the following topology. The same file is modified
* in both branches. The modifications are not automatically
* mergeable. m2 and s2 contain branch specific conflict resolutions.
* Therefore m2 and s2 don't contain the same content.
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 0b8473e719..01c243417a 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -8,4 +8,24 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
+ <filter comment="Doesn't break consumers. Breaking providers is allowed also in minor versions." id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+ <message_argument value="mergeTreeWalk(TreeWalk)"/>
+ </message_arguments>
+ </filter>
+ <filter comment="Doesn't break consumers. Breaking providers is allowed also in minor versions." id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+ <message_argument value="mergeTrees(AbstractTreeIterator, RevTree, RevTree)"/>
+ </message_arguments>
+ </filter>
+ <filter comment="Doesn't break consumers. Breaking providers is allowed also in minor versions." id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+ <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator)"/>
+ </message_arguments>
+ </filter>
+ </resource>
</component>
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 36acfb6710..210316b6a1 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -48,6 +48,7 @@ Export-Package: org.eclipse.jgit.api;version="3.5.0";
org.eclipse.jgit.gitrepo;version="3.5.0";
uses:="org.eclipse.jgit.api,
org.eclipse.jgit.lib",
+ org.eclipse.jgit.gitrepo.internal;version="3.5.0";x-internal:=true,
org.eclipse.jgit.ignore;version="3.5.0",
org.eclipse.jgit.internal;version="3.5.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
org.eclipse.jgit.internal.storage.dfs;version="3.5.0";x-friends:="org.eclipse.jgit.test",
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index fe431319ff..750b65c651 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -311,6 +311,7 @@ mergeConflictOnNotes=Merge conflict on note {0}. base = {1}, ours = {2}, theirs
mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy
mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
+mergeRecursiveConflictsWhenMergingCommonAncestors=Multiple common ancestors were found and merging them resulted in a conflict: {0}, {1}
mergeRecursiveReturnedNoCommit=Merge returned no commit:\n Depth {0}\n Head one {1}\n Head two {2}
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
messageAndTaggerNotAllowedInUnannotatedTags = Unannotated tags cannot have a message or tagger
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
index 2def12f1a2..9dc33b5ad5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
@@ -483,10 +483,10 @@ public class MergeResult {
* for (String path : allConflicts.keySet()) {
* int[][] c = allConflicts.get(path);
* System.out.println("Conflicts in file " + path);
- * for (int i = 0; i < c.length; ++i) {
+ * for (int i = 0; i &lt; c.length; ++i) {
* System.out.println(" Conflict #" + i);
- * for (int j = 0; j &lt; (c[i].length) - 1; ++j) {
- * if (c[i][j] >= 0)
+ * for (int j = 0; j &lt; (c[i].length) - 1; ++j) {
+ * if (c[i][j] &gt;= 0)
* System.out.println(" Chunk for "
* + m.getMergedCommits()[j] + " starts on line #"
* + c[i][j]);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
index 52327d76c7..470d823aca 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
@@ -186,6 +186,7 @@ public class RevertCommand extends GitCommand<RevCommit> {
.setMessage(newMessage)
.setReflogComment("revert: " + shortMessage).call(); //$NON-NLS-1$
revertedRefs.add(src);
+ headCommit = newHead;
} else {
unmergedPaths = merger.getUnmergedPaths();
Map<String, MergeFailureReason> failingPaths = merger
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
index 57a2018abf..7eecd13513 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
@@ -51,16 +51,15 @@ package org.eclipse.jgit.diff;
* Regions should be specified using 0 based notation, so add 1 to the start and
* end marks for line numbers in a file.
* <p>
- * An edit where <code>beginA == endA && beginB &lt; endB</code> is an insert
- * edit, that is sequence B inserted the elements in region
- * <code>[beginB, endB)</code> at <code>beginA</code>.
+ * An edit where {@code beginA == endA && beginB < endB} is an insert edit, that
+ * is sequence B inserted the elements in region <code>[beginB, endB)</code> at
+ * <code>beginA</code>.
* <p>
- * An edit where <code>beginA &lt; endA && beginB == endB</code> is a delete
- * edit, that is sequence B has removed the elements between
- * <code>[beginA, endA)</code>.
+ * An edit where {@code beginA < endA && beginB == endB} is a delete edit, that
+ * is sequence B has removed the elements between <code>[beginA, endA)</code>.
* <p>
- * An edit where <code>beginA &lt; endA && beginB &lt; endB</code> is a replace
- * edit, that is sequence B has replaced the range of elements between
+ * An edit where {@code beginA < endA && beginB < endB} is a replace edit, that
+ * is sequence B has replaced the range of elements between
* <code>[beginA, endA)</code> with those found in <code>[beginB, endB)</code>.
*/
public class Edit {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index 80dda8eb83..4b0d58600f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -110,6 +110,8 @@ public class DirCacheCheckout {
private ArrayList<String> toBeDeleted = new ArrayList<String>();
+ private boolean emptyDirCache;
+
/**
* @return a list of updated paths and objectIds
*/
@@ -168,6 +170,7 @@ public class DirCacheCheckout {
this.headCommitTree = headCommitTree;
this.mergeCommitTree = mergeCommitTree;
this.workingTree = workingTree;
+ this.emptyDirCache = (dc == null) || (dc.getEntryCount() == 0);
}
/**
@@ -716,7 +719,8 @@ public class DirCacheCheckout {
* 0 nothing nothing nothing (does not happen)
* 1 nothing nothing exists use M
* 2 nothing exists nothing remove path from index
- * 3 nothing exists exists yes keep index
+ * 3 nothing exists exists yes keep index if not in initial checkout
+ * , otherwise use M
* nothing exists exists no fail
* </pre>
*/
@@ -743,9 +747,12 @@ public class DirCacheCheckout {
// in the index there is nothing (e.g. 'git rm ...' was
// called before). Ignore the cached deletion and use what we
// find in Merge. Potentially updates the file.
- if (equalIdAndMode(hId, hMode, mId, mMode))
- keep(dce);
- else
+ if (equalIdAndMode(hId, hMode, mId, mMode)) {
+ if (emptyDirCache)
+ update(name, mId, mMode);
+ else
+ keep(dce);
+ } else
conflict(name, dce, h, m);
}
} else {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java
index 699eca9683..6211b246f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java
@@ -57,4 +57,9 @@ final class CharacterHead extends AbstractHead {
return c == expectedCharacter;
}
+ @Override
+ public String toString() {
+ return String.valueOf(expectedCharacter);
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
index 92a4837b2e..f9c239431a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
@@ -307,7 +307,11 @@ public class FileNameMatcher {
return new WildCardHead(star);
}
- private void extendStringToMatchByOneCharacter(final char c) {
+ /**
+ * @param c new character to append
+ * @return true to continue, false if the matcher can stop appending
+ */
+ private boolean extendStringToMatchByOneCharacter(final char c) {
final List<Head> newHeads = listForLocalUseage;
newHeads.clear();
List<Head> lastAddedHeads = null;
@@ -320,12 +324,14 @@ public class FileNameMatcher {
// This is the case with the heads "a" and "*" of "a*b" which
// both can return the list ["*","b"]
if (headsToAdd != lastAddedHeads) {
- newHeads.addAll(headsToAdd);
+ if (!headsToAdd.isEmpty())
+ newHeads.addAll(headsToAdd);
lastAddedHeads = headsToAdd;
}
}
listForLocalUseage = heads;
heads = newHeads;
+ return !newHeads.isEmpty();
}
private static int indexOfUnescaped(final String searchString,
@@ -349,7 +355,8 @@ public class FileNameMatcher {
public void append(final String stringToMatch) {
for (int i = 0; i < stringToMatch.length(); i++) {
final char c = stringToMatch.charAt(i);
- extendStringToMatchByOneCharacter(c);
+ if (!extendStringToMatchByOneCharacter(c))
+ break;
}
}
@@ -378,6 +385,9 @@ public class FileNameMatcher {
* @return true, if the string currently being matched does match.
*/
public boolean isMatch() {
+ if (heads.isEmpty())
+ return false;
+
final ListIterator<Head> headIterator = heads
.listIterator(heads.size());
while (headIterator.hasPrevious()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java
index 6d527d2b2d..4a0a03df25 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java
@@ -56,4 +56,9 @@ final class RestrictedWildCardHead extends AbstractHead {
protected final boolean matches(final char c) {
return c != excludedCharacter;
}
+
+ @Override
+ public String toString() {
+ return isStar() ? "*" : "?"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index ee814300ae..12d4d311f3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -53,8 +53,10 @@ import java.nio.channels.FileChannel;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -104,6 +106,11 @@ import org.xml.sax.helpers.XMLReaderFactory;
* If called against a bare repository, it will replace all the existing content
* of the repository with the contents populated from the manifest.
*
+ * repo manifest allows projects overlapping, e.g. one project's path is
+ * &quot;foo&quot; and another project's path is &quot;foo/bar&quot;. This won't
+ * work in git submodule, so we'll skip all the sub projects
+ * (&quot;foo/bar&quot; in the example) while converting.
+ *
* @see <a href="https://code.google.com/p/git-repo/">git-repo project page</a>
* @since 3.4
*/
@@ -249,7 +256,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
}
}
- private static class Project {
+ private static class Project implements Comparable<Project> {
final String name;
final String path;
final String revision;
@@ -269,6 +276,35 @@ public class RepoCommand extends GitCommand<RevCommit> {
void addCopyFile(CopyFile copyfile) {
copyfiles.add(copyfile);
}
+
+ String getPathWithSlash() {
+ if (path.endsWith("/")) //$NON-NLS-1$
+ return path;
+ else
+ return path + "/"; //$NON-NLS-1$
+ }
+
+ boolean isAncestorOf(Project that) {
+ return that.getPathWithSlash().startsWith(this.getPathWithSlash());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Project) {
+ Project that = (Project) o;
+ return this.getPathWithSlash().equals(that.getPathWithSlash());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.getPathWithSlash().hashCode();
+ }
+
+ public int compareTo(Project that) {
+ return this.getPathWithSlash().compareTo(that.getPathWithSlash());
+ }
}
private static class XmlManifest extends DefaultHandler {
@@ -277,9 +313,9 @@ public class RepoCommand extends GitCommand<RevCommit> {
private final String filename;
private final String baseUrl;
private final Map<String, String> remotes;
- private final List<Project> projects;
private final Set<String> plusGroups;
private final Set<String> minusGroups;
+ private List<Project> projects;
private String defaultRemote;
private String defaultRevision;
private Project currentProject;
@@ -289,7 +325,13 @@ public class RepoCommand extends GitCommand<RevCommit> {
this.command = command;
this.inputStream = inputStream;
this.filename = filename;
- this.baseUrl = baseUrl;
+
+ // Strip trailing /s to match repo behavior.
+ int lastIndex = baseUrl.length() - 1;
+ while (lastIndex >= 0 && baseUrl.charAt(lastIndex) == '/')
+ lastIndex--;
+ this.baseUrl = baseUrl.substring(0, lastIndex + 1);
+
remotes = new HashMap<String, String>();
projects = new ArrayList<Project>();
plusGroups = new HashSet<String>();
@@ -383,14 +425,38 @@ public class RepoCommand extends GitCommand<RevCommit> {
} catch (URISyntaxException e) {
throw new SAXException(e);
}
+ removeNotInGroup();
+ removeOverlaps();
for (Project proj : projects) {
- if (inGroups(proj)) {
- command.addSubmodule(remoteUrl + proj.name,
- proj.path,
- proj.revision == null
- ? defaultRevision : proj.revision,
- proj.copyfiles);
- }
+ command.addSubmodule(remoteUrl + proj.name,
+ proj.path,
+ proj.revision == null
+ ? defaultRevision : proj.revision,
+ proj.copyfiles);
+ }
+ }
+
+ /** Remove projects that are not in our desired groups. */
+ void removeNotInGroup() {
+ Iterator<Project> iter = projects.iterator();
+ while (iter.hasNext())
+ if (!inGroups(iter.next()))
+ iter.remove();
+ }
+
+ /** Remove projects that sits in a subdirectory of any other project. */
+ void removeOverlaps() {
+ Collections.sort(projects);
+ Iterator<Project> iter = projects.iterator();
+ if (!iter.hasNext())
+ return;
+ Project last = iter.next();
+ while (iter.hasNext()) {
+ Project p = iter.next();
+ if (last.isAncestorOf(p))
+ iter.remove();
+ else
+ last = p;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
index 980f2094bd..42bbd9e9b8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
@@ -191,6 +191,9 @@ public class IgnoreRule {
final String[] segments = target.split("/"); //$NON-NLS-1$
for (int idx = 0; idx < segments.length; idx++) {
final String segmentName = segments[idx];
+ // String.split("/") creates empty segment for leading slash
+ if (segmentName.length() == 0)
+ continue;
if (segmentName.equals(pattern) &&
doesMatchDirectoryExpectations(isDirectory, idx, segments.length))
return true;
@@ -207,6 +210,9 @@ public class IgnoreRule {
if (nameOnly) {
for (int idx = 0; idx < segments.length; idx++) {
final String segmentName = segments[idx];
+ // String.split("/") creates empty segment for leading slash
+ if (segmentName.length() == 0)
+ continue;
//Iterate through each sub-directory
matcher.reset();
matcher.append(segmentName);
@@ -218,14 +224,18 @@ public class IgnoreRule {
//TODO: This is the slowest operation
//This matches e.g. "/src/ne?" to "/src/new/file.c"
matcher.reset();
+
for (int idx = 0; idx < segments.length; idx++) {
final String segmentName = segments[idx];
- if (segmentName.length() > 0) {
- matcher.append("/" + segmentName); //$NON-NLS-1$
- }
+ // String.split("/") creates empty segment for leading slash
+ if (segmentName.length() == 0)
+ continue;
- if (matcher.isMatch() &&
- doesMatchDirectoryExpectations(isDirectory, idx, segments.length))
+ matcher.append("/" + segmentName); //$NON-NLS-1$
+
+ if (matcher.isMatch()
+ && doesMatchDirectoryExpectations(isDirectory, idx,
+ segments.length))
return true;
}
}
@@ -255,4 +265,9 @@ public class IgnoreRule {
// We are checking the last part of the segment for which isDirectory has to be considered.
return !dirOnly || isDirectory;
}
+
+ @Override
+ public String toString() {
+ return pattern;
+ }
} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index f075db3e57..ed94514924 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -370,6 +370,7 @@ public class JGitText extends TranslationBundle {
/***/ public String mergeStrategyAlreadyExistsAsDefault;
/***/ public String mergeStrategyDoesNotSupportHeads;
/***/ public String mergeUsingStrategyResultedInDescription;
+ /***/ public String mergeRecursiveConflictsWhenMergingCommonAncestors;
/***/ public String mergeRecursiveReturnedNoCommit;
/***/ public String mergeRecursiveTooManyMergeBasesFor;
/***/ public String messageAndTaggerNotAllowedInUnannotatedTags;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
index 2dd4426e11..dc3c772efb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
@@ -161,4 +161,17 @@ public class MergeResult<S extends Sequence> implements Iterable<MergeChunk> {
public boolean containsConflicts() {
return containsConflicts;
}
+
+ /**
+ * Sets explicitly whether this merge should be seen as containing a
+ * conflict or not. Needed because during RecursiveMerger we want to do
+ * content-merges and take the resulting content (even with conflict
+ * markers!) as new conflict-free content
+ *
+ * @param containsConflicts
+ * @since 3.5
+ */
+ protected void setContainsConflicts(boolean containsConflicts) {
+ this.containsConflicts = containsConflicts;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
index 5802850a30..af6c1f9647 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -196,22 +196,25 @@ public class RecursiveMerger extends ResolveMerger {
if (mergeTrees(
openTree(getBaseCommit(currentBase, nextBase,
callDepth + 1).getTree()),
- currentBase.getTree(),
- nextBase.getTree()))
+ currentBase.getTree(), nextBase.getTree(), true))
currentBase = createCommitForTree(resultTree, parents);
else
throw new NoMergeBaseException(
NoMergeBaseException.MergeBaseFailureReason.CONFLICTS_DURING_MERGE_BASE_CALCULATION,
MessageFormat.format(
- JGitText.get().mergeRecursiveTooManyMergeBasesFor,
- Integer.valueOf(MAX_BASES), a.name(),
- b.name(),
- Integer.valueOf(baseCommits.size())));
+ JGitText.get().mergeRecursiveConflictsWhenMergingCommonAncestors,
+ currentBase.getName(), nextBase.getName()));
}
} finally {
inCore = oldIncore;
dircache = oldDircache;
workingTreeIterator = oldWTreeIt;
+ toBeCheckedOut.clear();
+ toBeDeleted.clear();
+ modifiedFiles.clear();
+ unmergedPaths.clear();
+ mergeResults.clear();
+ failingPaths.clear();
}
return currentBase;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index 28d42a6161..712bb0d35e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -297,7 +297,8 @@ public class ResolveMerger extends ThreeWayMerger {
dircache = getRepository().lockDirCache();
try {
- return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1]);
+ return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
+ false);
} finally {
if (implicitDirCache)
dircache.unlock();
@@ -443,6 +444,7 @@ public class ResolveMerger extends ThreeWayMerger {
* conflict is detected the content-merge algorithm will try to write a
* merged version into the working-tree. If the file is dirty we would
* override unsaved data.</li>
+ * </ul>
*
* @param base
* the common base for ours and theirs
@@ -457,6 +459,9 @@ public class ResolveMerger extends ThreeWayMerger {
* the index entry
* @param work
* the file in the working tree
+ * @param ignoreConflicts
+ * see
+ * {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
* @return <code>false</code> if the merge will fail because the index entry
* didn't match ours or the working-dir file was dirty and a
* conflict occurred
@@ -464,11 +469,12 @@ public class ResolveMerger extends ThreeWayMerger {
* @throws IncorrectObjectTypeException
* @throws CorruptObjectException
* @throws IOException
- * @since 3.4
+ * @since 3.5
*/
protected boolean processEntry(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
- DirCacheBuildIterator index, WorkingTreeIterator work)
+ DirCacheBuildIterator index, WorkingTreeIterator work,
+ boolean ignoreConflicts)
throws MissingObjectException, IncorrectObjectTypeException,
CorruptObjectException, IOException {
enterSubtree = true;
@@ -627,9 +633,11 @@ public class ResolveMerger extends ThreeWayMerger {
}
MergeResult<RawText> result = contentMerge(base, ours, theirs);
+ if (ignoreConflicts)
+ result.setContainsConflicts(false);
File of = writeMergedFile(result);
updateIndex(base, ours, theirs, result, of);
- if (result.containsConflicts())
+ if (result.containsConflicts() && !ignoreConflicts)
unmergedPaths.add(tw.getPathString());
modifiedFiles.add(tw.getPathString());
} else if (modeO != modeT) {
@@ -993,12 +1001,32 @@ public class ResolveMerger extends ThreeWayMerger {
* @param baseTree
* @param headTree
* @param mergeTree
+ * @param ignoreConflicts
+ * Controls what to do in case a content-merge is done and a
+ * conflict is detected. The default setting for this should be
+ * <code>false</code>. In this case the working tree file is
+ * filled with new content (containing conflict markers) and the
+ * index is filled with multiple stages containing BASE, OURS and
+ * THEIRS content. Having such non-0 stages is the sign to git
+ * tools that there are still conflicts for that path.
+ * <p>
+ * If <code>true</code> is specified the behavior is different.
+ * In case a conflict is detected the working tree file is again
+ * filled with new content (containing conflict markers). But
+ * also stage 0 of the index is filled with that content. No
+ * other stages are filled. Means: there is no conflict on that
+ * path but the new content (including conflict markers) is
+ * stored as successful merge result. This is needed in the
+ * context of {@link RecursiveMerger} where when determining
+ * merge bases we don't want to deal with content-merge
+ * conflicts.
* @return whether the trees merged cleanly
* @throws IOException
- * @since 3.0
+ * @since 3.5
*/
protected boolean mergeTrees(AbstractTreeIterator baseTree,
- RevTree headTree, RevTree mergeTree) throws IOException {
+ RevTree headTree, RevTree mergeTree, boolean ignoreConflicts)
+ throws IOException {
builder = dircache.builder();
DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder);
@@ -1011,7 +1039,7 @@ public class ResolveMerger extends ThreeWayMerger {
if (workingTreeIterator != null)
tw.addTree(workingTreeIterator);
- if (!mergeTreeWalk(tw)) {
+ if (!mergeTreeWalk(tw, ignoreConflicts)) {
return false;
}
@@ -1050,11 +1078,15 @@ public class ResolveMerger extends ThreeWayMerger {
*
* @param treeWalk
* The walk to iterate over.
+ * @param ignoreConflicts
+ * see
+ * {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
* @return Whether the trees merged cleanly.
* @throws IOException
- * @since 3.4
+ * @since 3.5
*/
- protected boolean mergeTreeWalk(TreeWalk treeWalk) throws IOException {
+ protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
+ throws IOException {
boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE;
while (treeWalk.next()) {
if (!processEntry(
@@ -1063,7 +1095,7 @@ public class ResolveMerger extends ThreeWayMerger {
treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class),
treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
- WorkingTreeIterator.class) : null)) {
+ WorkingTreeIterator.class) : null, ignoreConflicts)) {
cleanUp();
return false;
}