import java.io.File;
import java.io.IOException;
+import java.util.Iterator;
-import org.eclipse.jgit.errors.CheckoutConflictException;
-import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.api.MergeResult.MergeStatus;
+import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.GitIndex;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.RepositoryTestCase;
-import org.eclipse.jgit.lib.WorkDirCheckout;
-import org.eclipse.jgit.lib.GitIndex.Entry;
+import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
public class MergeCommandTest extends RepositoryTestCase {
-
public void testMergeInItself() throws Exception {
Git git = new Git(db);
git.commit().setMessage("initial commit").call();
public void testFastForwardWithFiles() throws Exception {
Git git = new Git(db);
- addNewFileToIndex("file1");
+ writeTrashFile("file1", "file1");
+ git.add().addFilepattern("file1").call();
RevCommit first = git.commit().setMessage("initial commit").call();
assertTrue(new File(db.getWorkTree(), "file1").exists());
createBranch(first, "refs/heads/branch1");
- addNewFileToIndex("file2");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
RevCommit second = git.commit().setMessage("second commit").call();
assertTrue(new File(db.getWorkTree(), "file2").exists());
public void testMultipleHeads() throws Exception {
Git git = new Git(db);
- addNewFileToIndex("file1");
+ writeTrashFile("file1", "file1");
+ git.add().addFilepattern("file1").call();
RevCommit first = git.commit().setMessage("initial commit").call();
createBranch(first, "refs/heads/branch1");
- addNewFileToIndex("file2");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
RevCommit second = git.commit().setMessage("second commit").call();
- addNewFileToIndex("file3");
+ writeTrashFile("file3", "file3");
+ git.add().addFilepattern("file3").call();
git.commit().setMessage("third commit").call();
checkoutBranch("refs/heads/branch1");
merge.call();
fail("Expected exception not thrown when merging multiple heads");
} catch (InvalidMergeHeadsException e) {
+ // expected this exception
}
}
+
+ public void testContentMerge() throws Exception {
+ Git git = new Git(db);
+
+ writeTrashFile("a", "1\na\n3\n");
+ writeTrashFile("b", "1\nb\n3\n");
+ writeTrashFile("c/c/c", "1\nc\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b")
+ .addFilepattern("c/c/c").call();
+ RevCommit initialCommit = git.commit().setMessage("initial").call();
+
+ createBranch(initialCommit, "refs/heads/side");
+ checkoutBranch("refs/heads/side");
+
+ writeTrashFile("a", "1\na(side)\n3\n");
+ writeTrashFile("b", "1\nb(side)\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b").call();
+ RevCommit secondCommit = git.commit().setMessage("side").call();
+
+ assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+ checkoutBranch("refs/heads/master");
+ assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+
+ writeTrashFile("a", "1\na(main)\n3\n");
+ writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+ git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+ git.commit().setMessage("main").call();
+
+ MergeResult result = git.merge().include(secondCommit.getId())
+ .setStrategy(MergeStrategy.RESOLVE).call();
+ assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+
+ assertEquals(
+ "1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n",
+ read(new File(db.getWorkTree(), "a")));
+ assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+ assertEquals("1\nc(main)\n3\n",
+ read(new File(db.getWorkTree(), "c/c/c")));
+
+ assertEquals(1, result.getConflicts().size());
+ assertEquals(3, result.getConflicts().get("a")[0].length);
+
+ assertEquals(RepositoryState.MERGING, db.getRepositoryState());
+ }
+
+ public void testSuccessfulContentMerge() throws Exception {
+ Git git = new Git(db);
+
+ writeTrashFile("a", "1\na\n3\n");
+ writeTrashFile("b", "1\nb\n3\n");
+ writeTrashFile("c/c/c", "1\nc\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b")
+ .addFilepattern("c/c/c").call();
+ RevCommit initialCommit = git.commit().setMessage("initial").call();
+
+ createBranch(initialCommit, "refs/heads/side");
+ checkoutBranch("refs/heads/side");
+
+ writeTrashFile("a", "1(side)\na\n3\n");
+ writeTrashFile("b", "1\nb(side)\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b").call();
+ RevCommit secondCommit = git.commit().setMessage("side").call();
+
+ assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+ checkoutBranch("refs/heads/master");
+ assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+
+ writeTrashFile("a", "1\na\n3(main)\n");
+ writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+ git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+ RevCommit thirdCommit = git.commit().setMessage("main").call();
+
+ MergeResult result = git.merge().include(secondCommit.getId())
+ .setStrategy(MergeStrategy.RESOLVE).call();
+ assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+
+ assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
+ "a")));
+ assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+ assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(),
+ "c/c/c")));
+
+ assertEquals(null, result.getConflicts());
+
+ assertTrue(2 == result.getMergedCommits().length);
+ assertEquals(thirdCommit, result.getMergedCommits()[0]);
+ assertEquals(secondCommit, result.getMergedCommits()[1]);
+
+ Iterator<RevCommit> it = git.log().call().iterator();
+ RevCommit newHead = it.next();
+ assertEquals(newHead, result.getNewHead());
+ assertEquals(2, newHead.getParentCount());
+ assertEquals(thirdCommit, newHead.getParent(0));
+ assertEquals(secondCommit, newHead.getParent(1));
+ assertEquals(
+ "merging 3fa334456d236a92db020289fe0bf481d91777b4 into HEAD",
+ newHead.getFullMessage());
+ // @TODO fix me
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+ // test index state
+ }
+
+ public void testSuccessfulContentMergeAndDirtyworkingTree()
+ throws Exception {
+ Git git = new Git(db);
+
+ writeTrashFile("a", "1\na\n3\n");
+ writeTrashFile("b", "1\nb\n3\n");
+ writeTrashFile("d", "1\nd\n3\n");
+ writeTrashFile("c/c/c", "1\nc\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b")
+ .addFilepattern("c/c/c").addFilepattern("d").call();
+ RevCommit initialCommit = git.commit().setMessage("initial").call();
+
+ createBranch(initialCommit, "refs/heads/side");
+ checkoutBranch("refs/heads/side");
+
+ writeTrashFile("a", "1(side)\na\n3\n");
+ writeTrashFile("b", "1\nb(side)\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b").call();
+ RevCommit secondCommit = git.commit().setMessage("side").call();
+
+ assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+ checkoutBranch("refs/heads/master");
+ assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+
+ writeTrashFile("a", "1\na\n3(main)\n");
+ writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+ git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+ RevCommit thirdCommit = git.commit().setMessage("main").call();
+
+ writeTrashFile("d", "--- dirty ---");
+ MergeResult result = git.merge().include(secondCommit.getId())
+ .setStrategy(MergeStrategy.RESOLVE).call();
+ assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+
+ assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
+ "a")));
+ assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+ assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(),
+ "c/c/c")));
+
+ assertEquals(null, result.getConflicts());
+
+ assertTrue(2 == result.getMergedCommits().length);
+ assertEquals(thirdCommit, result.getMergedCommits()[0]);
+ assertEquals(secondCommit, result.getMergedCommits()[1]);
+
+ Iterator<RevCommit> it = git.log().call().iterator();
+ RevCommit newHead = it.next();
+ assertEquals(newHead, result.getNewHead());
+ assertEquals(2, newHead.getParentCount());
+ assertEquals(thirdCommit, newHead.getParent(0));
+ assertEquals(secondCommit, newHead.getParent(1));
+ assertEquals(
+ "merging 064d54d98a4cdb0fed1802a21c656bfda67fe879 into HEAD",
+ newHead.getFullMessage());
+
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+ }
+
+ public void testMergeFailingWithDirtyWorkingTree() throws Exception {
+ Git git = new Git(db);
+
+ writeTrashFile("a", "1\na\n3\n");
+ writeTrashFile("b", "1\nb\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b").call();
+ RevCommit initialCommit = git.commit().setMessage("initial").call();
+
+ createBranch(initialCommit, "refs/heads/side");
+ checkoutBranch("refs/heads/side");
+
+ writeTrashFile("a", "1(side)\na\n3\n");
+ writeTrashFile("b", "1\nb(side)\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b").call();
+ RevCommit secondCommit = git.commit().setMessage("side").call();
+
+ assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+ checkoutBranch("refs/heads/master");
+ assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+
+ writeTrashFile("a", "1\na\n3(main)\n");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("main").call();
+
+ writeTrashFile("a", "--- dirty ---");
+ MergeResult result = git.merge().include(secondCommit.getId())
+ .setStrategy(MergeStrategy.RESOLVE).call();
+
+ assertEquals(MergeStatus.FAILED, result.getMergeStatus());
+
+ assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "a")));
+ assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+
+ assertEquals(null, result.getConflicts());
+
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+ }
+
+ public void testMergeConflictFileFolder() throws Exception {
+ Git git = new Git(db);
+
+ writeTrashFile("a", "1\na\n3\n");
+ writeTrashFile("b", "1\nb\n3\n");
+ git.add().addFilepattern("a").addFilepattern("b").call();
+ RevCommit initialCommit = git.commit().setMessage("initial").call();
+
+ createBranch(initialCommit, "refs/heads/side");
+ checkoutBranch("refs/heads/side");
+
+ writeTrashFile("c/c/c", "1\nc(side)\n3\n");
+ writeTrashFile("d", "1\nd(side)\n3\n");
+ git.add().addFilepattern("c/c/c").addFilepattern("d").call();
+ RevCommit secondCommit = git.commit().setMessage("side").call();
+
+ checkoutBranch("refs/heads/master");
+
+ writeTrashFile("c", "1\nc(main)\n3\n");
+ writeTrashFile("d/d/d", "1\nd(main)\n3\n");
+ git.add().addFilepattern("c").addFilepattern("d/d/d").call();
+ git.commit().setMessage("main").call();
+
+ MergeResult result = git.merge().include(secondCommit.getId())
+ .setStrategy(MergeStrategy.RESOLVE).call();
+
+ assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+
+ assertEquals("1\na\n3\n", read(new File(db.getWorkTree(), "a")));
+ assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+ assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), "c")));
+ assertEquals("1\nd(main)\n3\n", read(new File(db.getWorkTree(), "d/d/d")));
+
+ assertEquals(null, result.getConflicts());
+
+ assertEquals(RepositoryState.MERGING, db.getRepositoryState());
+ }
+
private void createBranch(ObjectId objectId, String branchName) throws IOException {
RefUpdate updateRef = db.updateRef(branchName);
updateRef.setNewObjectId(objectId);
updateRef.update();
}
- private void checkoutBranch(String branchName) throws Exception {
- File workDir = db.getWorkTree();
- if (workDir != null) {
- WorkDirCheckout workDirCheckout = new WorkDirCheckout(db,
- workDir, db.mapTree(Constants.HEAD),
- db.getIndex(), db.mapTree(branchName));
- workDirCheckout.setFailOnConflict(true);
- try {
- workDirCheckout.checkout();
- } catch (CheckoutConflictException e) {
- throw new JGitInternalException(
- "Couldn't check out because of conflicts", e);
- }
- }
-
+ private void checkoutBranch(String branchName) throws IllegalStateException, IOException {
+ RevWalk walk = new RevWalk(db);
+ RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD));
+ RevCommit branch = walk.parseCommit(db.resolve(branchName));
+ DirCacheCheckout dco = new DirCacheCheckout(db,
+ head.getTree().getId(), db.lockDirCache(),
+ branch.getTree().getId());
+ dco.setFailOnConflict(true);
+ dco.checkout();
+ walk.release();
// update the HEAD
RefUpdate refUpdate = db.updateRef(Constants.HEAD);
refUpdate.link(branchName);
}
-
- private void addNewFileToIndex(String filename) throws IOException,
- CorruptObjectException {
- File writeTrashFile = writeTrashFile(filename, filename);
-
- GitIndex index = db.getIndex();
- Entry entry = index.add(db.getWorkTree(), writeTrashFile);
- entry.update(writeTrashFile);
- index.write();
- }
}
*/
package org.eclipse.jgit.api;
-import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.api.MergeResult.MergeStatus;
+import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.GitIndex;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.WorkDirCheckout;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.ResolveMerger;
+import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
+import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
/**
* A class used to execute a {@code Merge} command. It has setters for all
*/
public MergeResult call() throws NoHeadException,
ConcurrentRefUpdateException, CheckoutConflictException,
- InvalidMergeHeadsException {
+ InvalidMergeHeadsException, WrongRepositoryStateException, NoMessageException {
checkCallable();
if (commits.size() != 1)
commits.isEmpty() ? JGitText.get().noMergeHeadSpecified
: MessageFormat.format(
JGitText.get().mergeStrategyDoesNotSupportHeads,
- mergeStrategy.getName(), commits.size()));
+ mergeStrategy.getName(),
+ Integer.valueOf(commits.size())));
+ RevWalk revWalk = null;
try {
Ref head = repo.getRef(Constants.HEAD);
if (head == null)
StringBuilder refLogMessage = new StringBuilder("merge ");
// Check for FAST_FORWARD, ALREADY_UP_TO_DATE
- RevWalk revWalk = new RevWalk(repo);
- try {
- RevCommit headCommit = revWalk.lookupCommit(head.getObjectId());
+ revWalk = new RevWalk(repo);
+ RevCommit headCommit = revWalk.lookupCommit(head.getObjectId());
- Ref ref = commits.get(0);
+ // we know for know there is only one commit
+ Ref ref = commits.get(0);
- refLogMessage.append(ref.getName());
+ refLogMessage.append(ref.getName());
- // handle annotated tags
- ObjectId objectId = ref.getPeeledObjectId();
- if (objectId == null)
- objectId = ref.getObjectId();
+ // handle annotated tags
+ ObjectId objectId = ref.getPeeledObjectId();
+ if (objectId == null)
+ objectId = ref.getObjectId();
- RevCommit srcCommit = revWalk.lookupCommit(objectId);
- if (revWalk.isMergedInto(srcCommit, headCommit)) {
- setCallable(false);
- return new MergeResult(headCommit, srcCommit,
- new ObjectId[] { srcCommit, headCommit },
- MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy);
- } else if (revWalk.isMergedInto(headCommit, srcCommit)) {
- // FAST_FORWARD detected: skip doing a real merge but only
- // update HEAD
- refLogMessage.append(": " + MergeStatus.FAST_FORWARD);
- checkoutNewHead(revWalk, headCommit, srcCommit);
- updateHead(refLogMessage, srcCommit, head.getObjectId());
- setCallable(false);
- return new MergeResult(srcCommit, headCommit,
- new ObjectId[] { srcCommit, headCommit },
- MergeStatus.FAST_FORWARD, mergeStrategy);
+ RevCommit srcCommit = revWalk.lookupCommit(objectId);
+ if (revWalk.isMergedInto(srcCommit, headCommit)) {
+ setCallable(false);
+ return new MergeResult(headCommit, srcCommit, new ObjectId[] {
+ headCommit, srcCommit },
+ MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy, null, null);
+ } else if (revWalk.isMergedInto(headCommit, srcCommit)) {
+ // FAST_FORWARD detected: skip doing a real merge but only
+ // update HEAD
+ refLogMessage.append(": " + MergeStatus.FAST_FORWARD);
+ DirCacheCheckout dco = new DirCacheCheckout(repo,
+ headCommit.getTree(), repo.lockDirCache(),
+ srcCommit.getTree());
+ dco.setFailOnConflict(true);
+ dco.checkout();
+
+ updateHead(refLogMessage, srcCommit, head.getObjectId());
+ setCallable(false);
+ return new MergeResult(srcCommit, srcCommit, new ObjectId[] {
+ headCommit, srcCommit }, MergeStatus.FAST_FORWARD,
+ mergeStrategy, null, null);
+ } else {
+ repo.writeMergeCommitMsg("merging " + ref.getName() + " into "
+ + head.getName());
+ repo.writeMergeHeads(Arrays.asList(ref.getObjectId()));
+ ThreeWayMerger merger = (ThreeWayMerger) mergeStrategy
+ .newMerger(repo);
+ boolean noProblems;
+ Map<String, org.eclipse.jgit.merge.MergeResult> lowLevelResults = null;
+ Map<String, MergeFailureReason> failingPaths = null;
+ if (merger instanceof ResolveMerger) {
+ ResolveMerger resolveMerger = (ResolveMerger) merger;
+ resolveMerger.setCommitNames(new String[] {
+ "BASE", "HEAD", ref.getName() });
+ resolveMerger.setWorkingTreeIt(new FileTreeIterator(repo));
+ noProblems = merger.merge(headCommit, srcCommit);
+ lowLevelResults = resolveMerger
+ .getMergeResults();
+ failingPaths = resolveMerger.getFailingPathes();
+ } else
+ noProblems = merger.merge(headCommit, srcCommit);
+
+ if (noProblems) {
+ DirCacheCheckout dco = new DirCacheCheckout(repo,
+ headCommit.getTree(), repo.lockDirCache(),
+ merger.getResultTreeId());
+ dco.setFailOnConflict(true);
+ dco.checkout();
+ RevCommit newHead = new Git(getRepository()).commit().call();
+ return new MergeResult(newHead.getId(),
+ null, new ObjectId[] {
+ headCommit.getId(), srcCommit.getId() },
+ MergeStatus.MERGED, mergeStrategy, null, null);
} else {
- return new MergeResult(
- headCommit,
- null,
- new ObjectId[] { srcCommit, headCommit },
- MergeResult.MergeStatus.NOT_SUPPORTED,
- mergeStrategy,
- JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable);
+ if (failingPaths != null) {
+ repo.writeMergeCommitMsg(null);
+ repo.writeMergeHeads(null);
+ return new MergeResult(null,
+ merger.getBaseCommit(0, 1),
+ new ObjectId[] {
+ headCommit.getId(), srcCommit.getId() },
+ MergeStatus.FAILED, mergeStrategy,
+ lowLevelResults, null);
+ } else
+ return new MergeResult(null,
+ merger.getBaseCommit(0, 1),
+ new ObjectId[] { headCommit.getId(),
+ srcCommit.getId() },
+ MergeStatus.CONFLICTING, mergeStrategy,
+ lowLevelResults, null);
}
- } finally {
- revWalk.release();
}
} catch (IOException e) {
throw new JGitInternalException(
MessageFormat.format(
JGitText.get().exceptionCaughtDuringExecutionOfMergeCommand,
e), e);
- }
- }
-
- private void checkoutNewHead(RevWalk revWalk, RevCommit headCommit,
- RevCommit newHeadCommit) throws IOException,
- CheckoutConflictException {
- GitIndex index = repo.getIndex();
-
- File workDir = repo.getWorkTree();
- if (workDir != null) {
- WorkDirCheckout workDirCheckout = new WorkDirCheckout(repo,
- workDir, repo.mapTree(headCommit.getTree()), index,
- repo.mapTree(newHeadCommit.getTree()));
- workDirCheckout.setFailOnConflict(true);
- try {
- workDirCheckout.checkout();
- } catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
- throw new CheckoutConflictException(
- JGitText.get().couldNotCheckOutBecauseOfConflicts,
- workDirCheckout.getConflicts(), e);
- }
- index.write();
+ } finally {
+ if (revWalk != null)
+ revWalk.release();
}
}
package org.eclipse.jgit.api;
import java.text.MessageFormat;
+import java.util.HashMap;
import java.util.Map;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.merge.MergeChunk;
+import org.eclipse.jgit.merge.MergeChunk.ConflictState;
import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.ResolveMerger;
/**
* Encapsulates the result of a {@link MergeCommand}.
* the status the merge resulted in
* @param mergeStrategy
* the used {@link MergeStrategy}
+ * @param lowLevelResults
+ * merge results as returned by {@link ResolveMerger#getMergeResults()}
*/
public MergeResult(ObjectId newHead, ObjectId base,
ObjectId[] mergedCommits, MergeStatus mergeStatus,
+ Map<String, org.eclipse.jgit.merge.MergeResult> lowLevelResults,
MergeStrategy mergeStrategy) {
- this(newHead, base, mergedCommits, mergeStatus, mergeStrategy, null);
+ this(newHead, base, mergedCommits, mergeStatus, mergeStrategy, lowLevelResults, null);
}
/**
* the status the merge resulted in
* @param mergeStrategy
* the used {@link MergeStrategy}
+ * @param lowLevelResults
+ * merge results as returned by {@link ResolveMerger#getMergeResults()}
* @param description
* a user friendly description of the merge result
*/
public MergeResult(ObjectId newHead, ObjectId base,
ObjectId[] mergedCommits, MergeStatus mergeStatus,
- MergeStrategy mergeStrategy, String description) {
+ MergeStrategy mergeStrategy,
+ Map<String, org.eclipse.jgit.merge.MergeResult> lowLevelResults,
+ String description) {
this.newHead = newHead;
this.mergedCommits = mergedCommits;
this.base = base;
this.mergeStatus = mergeStatus;
this.mergeStrategy = mergeStrategy;
this.description = description;
+ if (lowLevelResults != null)
+ for (String path : lowLevelResults.keySet())
+ addConflict(path, lowLevelResults.get(path));
}
/**
this.conflicts = conflicts;
}
+ /**
+ * @param path
+ * @param conflictingRanges
+ * the conflicts to set
+ */
+ public void addConflict(String path, int[][] conflictingRanges) {
+ if (conflicts == null)
+ conflicts = new HashMap<String, int[][]>();
+ conflicts.put(path, conflictingRanges);
+ }
+
+ /**
+ * @param path
+ * @param lowLevelResult
+ */
+ public void addConflict(String path, org.eclipse.jgit.merge.MergeResult lowLevelResult) {
+ if (conflicts == null)
+ conflicts = new HashMap<String, int[][]>();
+ int nrOfConflicts = 0;
+ // just counting
+ for (MergeChunk mergeChunk : lowLevelResult) {
+ if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
+ nrOfConflicts++;
+ }
+ }
+ int currentConflict = -1;
+ int[][] ret=new int[nrOfConflicts][mergedCommits.length+1];
+ for (MergeChunk mergeChunk : lowLevelResult) {
+ // to store the end of this chunk (end of the last conflicting range)
+ int endOfChunk = 0;
+ if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) {
+ if (currentConflict > -1) {
+ // there was a previous conflicting range for which the end
+ // is not set yet - set it!
+ ret[currentConflict][mergedCommits.length] = endOfChunk;
+ }
+ currentConflict++;
+ endOfChunk = mergeChunk.getEnd();
+ ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
+ }
+ if (mergeChunk.getConflictState().equals(ConflictState.NEXT_CONFLICTING_RANGE)) {
+ if (mergeChunk.getEnd() > endOfChunk)
+ endOfChunk = mergeChunk.getEnd();
+ ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin();
+ }
+ }
+ conflicts.put(path, ret);
+ }
+
/**
* Returns information about the conflicts which occurred during a
* {@link MergeCommand}. The returned value maps the path of a conflicting
public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc,
ObjectId mergeCommitTree) throws IOException {
this(repo, headCommitTree, dc, mergeCommitTree, new FileTreeIterator(
- repo.getWorkTree(), repo.getFS(), WorkingTreeOptions.createDefaultInstance()));
+ repo.getWorkTree(), repo.getFS(),
+ WorkingTreeOptions.createDefaultInstance()));
}
/**
file.getParentFile().mkdirs();
file.createNewFile();
DirCacheEntry entry = dc.getEntry(path);
- checkoutEntry(file, entry, config_filemode());
+ checkoutEntry(repo, file, entry, config_filemode());
}
NameConflictTreeWalk tw = new NameConflictTreeWalk(repo);
tw.reset();
tw.addTree(new DirCacheIterator(dc));
- tw.addTree(new FileTreeIterator(repo.getWorkTree(), repo.getFS(), WorkingTreeOptions.createDefaultInstance()));
+ tw.addTree(new FileTreeIterator(repo.getWorkTree(), repo.getFS(),
+ WorkingTreeOptions.createDefaultInstance()));
tw.setRecursive(true);
tw.setFilter(PathFilter.create(path));
DirCacheIterator dcIt;
* TODO: this method works directly on File IO, we may need another
* abstraction (like WorkingTreeIterator). This way we could tell e.g.
* Eclipse that Files in the workspace got changed
- *
+ * @param repo
* @param f
* the file to be modified. The parent directory for this file
* has to exist already
* whether the mode bits should be handled at all.
* @throws IOException
*/
- public void checkoutEntry(File f, DirCacheEntry entry,
+ public static void checkoutEntry(final Repository repo, File f, DirCacheEntry entry,
boolean config_filemode) throws IOException {
ObjectLoader ol = repo.open(entry.getObjectId());
if (ol == null)
/** Simple strategy to merge paths, without simultaneous edits. */
public static final ThreeWayMergeStrategy SIMPLE_TWO_WAY_IN_CORE = new StrategySimpleTwoWayInCore();
+ /** Simple strategy to merge paths. It tries to merge also contents. Multiple merge bases are not supported */
+ public static final ThreeWayMergeStrategy RESOLVE = new StrategyResolve();
+
private static final HashMap<String, MergeStrategy> STRATEGIES = new HashMap<String, MergeStrategy>();
static {
register(OURS);
register(THEIRS);
register(SIMPLE_TWO_WAY_IN_CORE);
+ register(RESOLVE);
}
/**
--- /dev/null
+/*
+ * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>,
+ * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.merge;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.JGitText;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuildIterator;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.IndexWriteException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+
+/**
+ * A three-way merger performing a content-merge if necessary
+ */
+public class ResolveMerger extends ThreeWayMerger {
+ /**
+ * If the merge fails abnormally (means: not because of unresolved
+ * conflicts) this enum is used to explain why it failed
+ */
+ public enum MergeFailureReason {
+ /** the merge failed because of a dirty index */
+ DIRTY_INDEX,
+ /** the merge failed because of a dirty workingtree */
+ DIRTY_WORKTREE
+ }
+
+ private NameConflictTreeWalk tw;
+
+ private String commitNames[];
+
+ private static final int T_BASE = 0;
+
+ private static final int T_OURS = 1;
+
+ private static final int T_THEIRS = 2;
+
+ private static final int T_INDEX = 3;
+
+ private static final int T_FILE = 4;
+
+ private DirCacheBuilder builder;
+
+ private ObjectId resultTree;
+
+ private List<String> unmergedPathes = new ArrayList<String>();
+
+ private List<String> modifiedFiles = new LinkedList<String>();
+
+ private Map<String, DirCacheEntry> toBeCheckedOut = new HashMap<String, DirCacheEntry>();
+
+ private Map<String, MergeResult> mergeResults = new HashMap<String, MergeResult>();
+
+ private Map<String, MergeFailureReason> failingPathes = new HashMap<String, MergeFailureReason>();
+
+ private ObjectInserter oi;
+
+ private boolean enterSubtree;
+
+ private DirCache dircache;
+
+ private WorkingTreeIterator workingTreeIt;
+
+
+ /**
+ * @param local
+ */
+ protected ResolveMerger(Repository local) {
+ super(local);
+ commitNames = new String[] { "BASE", "OURS", "THEIRS" };
+ oi = getObjectInserter();
+ }
+
+ @Override
+ protected boolean mergeImpl() throws IOException {
+ boolean implicitDirCache = false;
+
+ if (dircache == null) {
+ dircache = getRepository().lockDirCache();
+ implicitDirCache = true;
+ }
+
+ try {
+ builder = dircache.builder();
+ DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder);
+
+ tw = new NameConflictTreeWalk(db);
+ tw.reset();
+ tw.addTree(mergeBase());
+ tw.addTree(sourceTrees[0]);
+ tw.addTree(sourceTrees[1]);
+ tw.addTree(buildIt);
+ if (workingTreeIt != null)
+ tw.addTree(workingTreeIt);
+
+ while (tw.next()) {
+ if (!processEntry(
+ tw.getTree(T_BASE, CanonicalTreeParser.class),
+ tw.getTree(T_OURS, CanonicalTreeParser.class),
+ tw.getTree(T_THEIRS, CanonicalTreeParser.class),
+ tw.getTree(T_INDEX, DirCacheBuildIterator.class),
+ (workingTreeIt == null) ? null : tw.getTree(T_FILE, WorkingTreeIterator.class))) {
+ cleanUp();
+ return false;
+ }
+ if (tw.isSubtree() && enterSubtree)
+ tw.enterSubtree();
+ }
+
+ // All content-merges are successfully done. If we can now write the
+ // new
+ // index we are on quite safe ground. Even if the checkout of files
+ // coming from "theirs" fails the user can work around such failures
+ // by
+ // checking out the index again.
+ if (!builder.commit()) {
+ cleanUp();
+ throw new IndexWriteException();
+ }
+ builder = null;
+
+ // No problem found. The only thing left to be done is to checkout
+ // all files from "theirs" which have been selected to go into the
+ // new index.
+ checkout();
+ if (getUnmergedPathes().isEmpty()) {
+ resultTree = dircache.writeTree(oi);
+ return true;
+ } else {
+ resultTree = null;
+ return false;
+ }
+ } finally {
+ if (implicitDirCache)
+ dircache.unlock();
+ }
+ }
+
+ private void checkout() throws NoWorkTreeException, IOException {
+ for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut.entrySet()) {
+ File f = new File(db.getWorkTree(), entry.getKey());
+ createDir(f.getParentFile());
+ DirCacheCheckout.checkoutEntry(db,
+ f,
+ entry.getValue(), true);
+ modifiedFiles.add(entry.getKey());
+ }
+ }
+
+ private void createDir(File f) throws IOException {
+ if (!f.isDirectory() && !f.mkdirs()) {
+ File p = f;
+ while (p != null && !p.exists())
+ p = p.getParentFile();
+ if (p == null || p.isDirectory())
+ throw new IOException(JGitText.get().cannotCreateDirectory);
+ p.delete();
+ if (!f.mkdirs())
+ throw new IOException(JGitText.get().cannotCreateDirectory);
+ }
+ }
+
+ /**
+ * Reverts the worktree after an unsuccessful merge. We know that for all
+ * modified files the old content was in the old index and the index
+ * contained only stage 0
+ *
+ * @throws IOException
+ * @throws CorruptObjectException
+ * @throws NoWorkTreeException
+ */
+ private void cleanUp() throws NoWorkTreeException, CorruptObjectException, IOException {
+ DirCache dc = db.readDirCache();
+ ObjectReader or = db.getObjectDatabase().newReader();
+ Iterator<String> mpathsIt=modifiedFiles.iterator();
+ while(mpathsIt.hasNext()) {
+ String mpath=mpathsIt.next();
+ DirCacheEntry entry = dc.getEntry(mpath);
+ FileOutputStream fos = new FileOutputStream(new File(db.getWorkTree(), mpath));
+ try {
+ or.open(entry.getObjectId()).copyTo(fos);
+ } finally {
+ fos.close();
+ }
+ mpathsIt.remove();
+ }
+ }
+
+ /**
+ * adds a new path with the specified stage to the index builder
+ *
+ * @param path
+ * @param p
+ * @param stage
+ * @return the entry which was added to the index
+ */
+ private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage) {
+ if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
+ DirCacheEntry e = new DirCacheEntry(path, stage);
+ e.setFileMode(p.getEntryFileMode());
+ e.setObjectId(p.getEntryObjectId());
+ builder.add(e);
+ return e;
+ }
+ return null;
+ }
+
+ /**
+ * Processes one path and tries to merge. This method will do all do all
+ * trivial (not content) merges and will also detect if a merge will fail.
+ * The merge will fail when one of the following is true
+ * <ul>
+ * <li>the index entry does not match the entry in ours. When merging one
+ * branch into the current HEAD, ours will point to HEAD and theirs will
+ * point to the other branch. It is assumed that the index matches the HEAD
+ * because it will only not match HEAD if it was populated before the merge
+ * operation. But the merge commit should not accidentally contain
+ * modifications done before the merge. Check the <a href=
+ * "http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html#_3_way_merge"
+ * >git read-tree</a> documentation for further explanations.</li>
+ * <li>A conflict was detected and the working-tree file is dirty. When a
+ * 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>
+ *
+ * @param base
+ * the common base for ours and theirs
+ * @param ours
+ * the ours side of the merge. When merging a branch into the
+ * HEAD ours will point to HEAD
+ * @param theirs
+ * the theirs side of the merge. When merging a branch into the
+ * current HEAD theirs will point to the branch which is merged
+ * into HEAD.
+ * @param index
+ * the index entry
+ * @param work
+ * the file in the working tree
+ * @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 occured
+ * @throws MissingObjectException
+ * @throws IncorrectObjectTypeException
+ * @throws CorruptObjectException
+ * @throws IOException
+ */
+ private boolean processEntry(CanonicalTreeParser base,
+ CanonicalTreeParser ours, CanonicalTreeParser theirs,
+ DirCacheBuildIterator index, WorkingTreeIterator work)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ CorruptObjectException, IOException {
+ enterSubtree = true;
+ final int modeO = tw.getRawMode(T_OURS);
+ final int modeI = tw.getRawMode(T_INDEX);
+
+ // Each index entry has to match ours, means: it has to be clean
+ if (nonTree(modeI)
+ && !(tw.idEqual(T_INDEX, T_OURS) && modeO == modeI)) {
+ failingPathes.put(tw.getPathString(), MergeFailureReason.DIRTY_INDEX);
+ return false;
+ }
+
+ final int modeT = tw.getRawMode(T_THEIRS);
+ if (nonTree(modeO) && modeO == modeT && tw.idEqual(T_OURS, T_THEIRS)) {
+ // ours and theirs are equal: it doesn'nt matter
+ // which one we choose. OURS is choosen here.
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0);
+ // no checkout needed!
+ return true;
+ }
+
+ final int modeB = tw.getRawMode(T_BASE);
+ if (nonTree(modeO) && modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
+ // THEIRS was not changed compared to base. All changes must be in
+ // OURS. Choose OURS.
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0);
+ return true;
+ }
+
+ if (nonTree(modeT) && modeB == modeO && tw.idEqual(T_BASE, T_OURS)) {
+ // OURS was not changed compared to base. All changes must be in
+ // THEIRS. Choose THEIRS.
+ DirCacheEntry e=add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_0);
+ if (e!=null)
+ toBeCheckedOut.put(tw.getPathString(), e);
+ return true;
+ }
+
+ if (tw.isSubtree()) {
+ // file/folder conflicts: here I want to detect only file/folder
+ // conflict between ours and theirs. file/folder conflicts between
+ // base/index/workingTree and something else are not relevant or
+ // detected later
+ if (nonTree(modeO) && !nonTree(modeT)) {
+ if (nonTree(modeB))
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
+ unmergedPathes.add(tw.getPathString());
+ enterSubtree = false;
+ return true;
+ }
+ if (nonTree(modeT) && !nonTree(modeO)) {
+ if (nonTree(modeB))
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
+ add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3);
+ unmergedPathes.add(tw.getPathString());
+ enterSubtree = false;
+ return true;
+ }
+
+ // ours and theirs are both folders or both files (and treewalk
+ // tells us we are in a subtree because of index or working-dir).
+ // If they are both folders no content-merge is required - we can
+ // return here.
+ if (!nonTree(modeO))
+ return true;
+
+ // ours and theirs are both files, just fall out of the if block
+ // and do the content merge
+ }
+
+ if (nonTree(modeO) && nonTree(modeT)) {
+ // We are going to update the worktree. Make sure the worktree is
+ // not modified
+ if (work != null
+ && (!nonTree(work.getEntryRawMode()) || work.isModified(
+ index.getDirCacheEntry(), true, true, db.getFS()))) {
+ failingPathes.put(tw.getPathString(),
+ MergeFailureReason.DIRTY_WORKTREE);
+ return false;
+ }
+
+ if (!contentMerge(base, ours, theirs)) {
+ unmergedPathes.add(tw.getPathString());
+ }
+ modifiedFiles.add(tw.getPathString());
+ }
+ return true;
+ }
+
+ private boolean contentMerge(CanonicalTreeParser base,
+ CanonicalTreeParser ours, CanonicalTreeParser theirs)
+ throws FileNotFoundException, IllegalStateException, IOException {
+ MergeFormatter fmt = new MergeFormatter();
+
+ // do the merge
+ MergeResult result = MergeAlgorithm.merge(
+ getRawText(base.getEntryObjectId(), db),
+ getRawText(ours.getEntryObjectId(), db),
+ getRawText(theirs.getEntryObjectId(), db));
+
+ File workTree = db.getWorkTree();
+ if (workTree == null)
+ // TODO: This should be handled by WorkingTreeIterators which
+ // support write operations
+ throw new UnsupportedOperationException();
+
+ File of = new File(workTree, tw.getPathString());
+ FileOutputStream fos = new FileOutputStream(of);
+ try {
+ fmt.formatMerge(fos, result, Arrays.asList(commitNames),
+ Constants.CHARACTER_ENCODING);
+ } finally {
+ fos.close();
+ }
+ if (result.containsConflicts()) {
+ // a conflict occured, the file will contain conflict markers
+ // the index will be populated with the three stages and only the
+ // workdir contains the halfways merged content
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2);
+ add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3);
+ mergeResults.put(tw.getPathString(), result);
+ return false;
+ } else {
+ // no conflict occured, the file will contain fully merged content.
+ // the index will be populated with the new merged version
+ DirCacheEntry dce = new DirCacheEntry(tw.getPathString());
+ dce.setFileMode(tw.getFileMode(0));
+ dce.setLastModified(of.lastModified());
+ dce.setLength((int) of.length());
+ InputStream is = new FileInputStream(of);
+ try {
+ dce.setObjectId(oi.insert(Constants.OBJ_BLOB, of.length(),
+ is));
+ } finally {
+ is.close();
+ }
+ builder.add(dce);
+ return true;
+ }
+ }
+
+ private static RawText getRawText(ObjectId id, Repository db)
+ throws IOException {
+ if (id.equals(ObjectId.zeroId()))
+ return new RawText(new byte[] {});
+ return new RawText(db.open(id, Constants.OBJ_BLOB).getCachedBytes());
+ }
+
+ private static boolean nonTree(final int mode) {
+ return mode != 0 && !FileMode.TREE.equals(mode);
+ }
+
+ @Override
+ public ObjectId getResultTreeId() {
+ return (resultTree == null) ? null : resultTree.toObjectId();
+ }
+
+ /**
+ * @param commitNames
+ * the names of the commits as they would appear in conflict
+ * markers
+ */
+ public void setCommitNames(String[] commitNames) {
+ this.commitNames = commitNames;
+ }
+
+ /**
+ * @return the names of the commits as they would appear in conflict
+ * markers.
+ */
+ public String[] getCommitNames() {
+ return commitNames;
+ }
+
+ /**
+ * @return the paths with conflicts. This is a subset of the files listed
+ * by {@link #getModifiedFiles()}
+ */
+ public List<String> getUnmergedPathes() {
+ return unmergedPathes;
+ }
+
+ /**
+ * @return the paths of files which have been modified by this merge. A
+ * file will be modified if a content-merge works on this path or if
+ * the merge algorithm decides to take the theirs-version. This is a
+ * superset of the files listed by {@link #getUnmergedPathes()}.
+ */
+ public List<String> getModifiedFiles() {
+ return modifiedFiles;
+ }
+
+ /**
+ * @return a map which maps the paths of files which have to be checked out
+ * because the merge created new fully-merged content for this file
+ * into the index. This means: the merge wrote a new stage 0 entry
+ * for this path.
+ */
+ public Map<String, DirCacheEntry> getToBeCheckedOut() {
+ return toBeCheckedOut;
+ }
+
+ /**
+ * @return the mergeResults
+ */
+ public Map<String, MergeResult> getMergeResults() {
+ return mergeResults;
+ }
+
+ /**
+ * @return lists paths causing this merge to fail abnormally (not because of
+ * a conflict). <code>null</code> is returned if this merge didn't
+ * fail abnormally.
+ */
+ public Map<String, MergeFailureReason> getFailingPathes() {
+ return (failingPathes.size() == 0) ? null : failingPathes;
+ }
+
+ /**
+ * Sets the DirCache which shall be used by this merger. If the DirCache is
+ * not set explicitly this merger will implicitly get and lock a default
+ * DirCache. If the DirCache is explicitly set the caller is responsible to
+ * lock it in advance. Finally the merger will call
+ * {@link DirCache#commit()} which requires that the DirCache is locked. If
+ * the {@link #mergeImpl()} returns without throwing an exception the lock
+ * will be released. In case of exceptions the caller is responsible to
+ * release the lock.
+ *
+ * @param dc
+ * the DirCache to set
+ */
+ public void setDirCache(DirCache dc) {
+ this.dircache = dc;
+ }
+
+ /**
+ * Sets the WorkingTreeIterator to be used by this merger. If no
+ * WorkingTreeIterator is set this merger will ignore the working tree and
+ * fail if a content merge is necessary.
+ * <p>
+ * TODO: enhance WorkingTreeIterator to support write operations. Then this
+ * merger will be able to merge with a different working tree abstraction.
+ *
+ * @param workingTreeIt
+ * the workingTreeIt to set
+ */
+ public void setWorkingTreeIt(WorkingTreeIterator workingTreeIt) {
+ this.workingTreeIt = workingTreeIt;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>,
+ * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.merge;
+
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * A three-way merge strategy performing a content-merge if necessary
+ */
+public class StrategyResolve extends ThreeWayMergeStrategy {
+ @Override
+ public ThreeWayMerger newMerger(Repository db) {
+ return new ResolveMerger(db);
+ }
+
+ @Override
+ public String getName() {
+ return "resolve";
+ }
+}
\ No newline at end of file