From cdaded26b0711bfd99cc2990db25b4562540c3b5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 23 Sep 2012 01:57:20 +0200 Subject: [PATCH] CheckoutCommand: Support checking out ours and theirs The checkoutPaths body is split into two implementations, depending on whether we are checking out the index or a branch. This improves readability, as in the index case we now also need to have access to DirCacheIterator. Bug: 390147 Change-Id: I99fd599b25b2ace9bdd84535a56565286a3cb7f1 Signed-off-by: Chris Aniszczyk --- .../jgit/api/PathCheckoutCommandTest.java | 48 ++++- .../org/eclipse/jgit/api/CheckoutCommand.java | 176 +++++++++++++----- 2 files changed, 179 insertions(+), 45 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java index d37f57293..43b632d2c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java @@ -47,6 +47,7 @@ import static org.junit.Assert.assertEquals; import java.io.File; import java.io.IOException; +import org.eclipse.jgit.api.CheckoutCommand.Stage; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; @@ -246,10 +247,41 @@ public class PathCheckoutCommandTest extends RepositoryTestCase { assertEquals("a", read(test2)); } + @Test(expected = JGitInternalException.class) public void testCheckoutOfConflictingFileShouldThrow() throws Exception { - // Setup + setupConflictingState(); + + git.checkout().addPath(FILE1).call(); + } + + @Test + public void testCheckoutOurs() throws Exception { + setupConflictingState(); + + git.checkout().setStage(Stage.OURS).addPath(FILE1).call(); + + assertEquals("3", read(FILE1)); + assertStageOneToThree(FILE1); + } + + @Test + public void testCheckoutTheirs() throws Exception { + setupConflictingState(); + + git.checkout().setStage(Stage.THEIRS).addPath(FILE1).call(); + + assertEquals("Conflicting", read(FILE1)); + assertStageOneToThree(FILE1); + } + + @Test(expected = IllegalStateException.class) + public void testStageNotPossibleWithBranch() throws Exception { + git.checkout().setStage(Stage.OURS).setStartPoint("master").call(); + } + + private void setupConflictingState() throws Exception { git.checkout().setCreateBranch(true).setName("conflict") .setStartPoint(initialCommit).call(); writeTrashFile(FILE1, "Conflicting"); @@ -260,8 +292,18 @@ public class PathCheckoutCommandTest extends RepositoryTestCase { git.merge().include(conflict).call(); assertEquals(RepositoryState.MERGING, db.getRepositoryState()); + assertStageOneToThree(FILE1); + } - // Now check out the conflicting path - git.checkout().addPath(FILE1).call(); + private void assertStageOneToThree(String name) throws Exception { + DirCache cache = DirCache.read(db.getIndexFile(), db.getFS()); + int i = cache.findEntry(name); + DirCacheEntry stage1 = cache.getEntry(i); + DirCacheEntry stage2 = cache.getEntry(i + 1); + DirCacheEntry stage3 = cache.getEntry(i + 2); + + assertEquals(DirCacheEntry.STAGE_1, stage1.getStage()); + assertEquals(DirCacheEntry.STAGE_2, stage2.getStage()); + assertEquals(DirCacheEntry.STAGE_3, stage3.getStage()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index 9d3bf561b..ef227dc85 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -125,6 +125,33 @@ import org.eclipse.jgit.util.FileUtils; * >Git documentation about Checkout */ public class CheckoutCommand extends GitCommand { + + /** + * Stage to check out, see {@link CheckoutCommand#setStage(Stage)}. + */ + public static enum Stage { + /** + * Base stage (#1) + */ + BASE(DirCacheEntry.STAGE_1), + + /** + * Ours stage (#2) + */ + OURS(DirCacheEntry.STAGE_2), + + /** + * Theirs stage (#3) + */ + THEIRS(DirCacheEntry.STAGE_3); + + private final int number; + + private Stage(int number) { + this.number = number; + } + } + private String name; private boolean force = false; @@ -137,6 +164,8 @@ public class CheckoutCommand extends GitCommand { private RevCommit startCommit; + private Stage checkoutStage = null; + private CheckoutResult status; private List paths; @@ -327,52 +356,19 @@ public class CheckoutCommand extends GitCommand { RevWalk revWalk = new RevWalk(repo); DirCache dc = repo.lockDirCache(); try { - DirCacheEditor editor = dc.editor(); - TreeWalk startWalk = new TreeWalk(revWalk.getObjectReader()); - startWalk.setRecursive(true); + TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader()); + treeWalk.setRecursive(true); if (!checkoutAllPaths) - startWalk.setFilter(PathFilterGroup.createFromStrings(paths)); - final boolean checkoutIndex = startCommit == null - && startPoint == null; - if (!checkoutIndex) - startWalk.addTree(revWalk.parseCommit(getStartPoint()) - .getTree()); - else - startWalk.addTree(new DirCacheIterator(dc)); - - final File workTree = repo.getWorkTree(); - final ObjectReader r = repo.getObjectDatabase().newReader(); + treeWalk.setFilter(PathFilterGroup.createFromStrings(paths)); try { - while (startWalk.next()) { - final ObjectId blobId = startWalk.getObjectId(0); - final FileMode mode = startWalk.getFileMode(0); - editor.add(new PathEdit(startWalk.getPathString()) { - public void apply(DirCacheEntry ent) { - if (checkoutIndex - && ent.getStage() > DirCacheEntry.STAGE_0) { - UnmergedPathException e = new UnmergedPathException(ent); - throw new JGitInternalException(e.getMessage(), e); - } - ent.setObjectId(blobId); - ent.setFileMode(mode); - File file = new File(workTree, ent.getPathString()); - File parentDir = file.getParentFile(); - try { - FileUtils.mkdirs(parentDir, true); - DirCacheCheckout.checkoutEntry(repo, file, ent, r); - } catch (IOException e) { - throw new JGitInternalException( - MessageFormat.format( - JGitText.get().checkoutConflictWithFile, - ent.getPathString()), e); - } - } - }); + if (isCheckoutIndex()) + checkoutPathsFromIndex(treeWalk, dc); + else { + RevCommit commit = revWalk.parseCommit(getStartPoint()); + checkoutPathsFromCommit(treeWalk, dc, commit); } - editor.commit(); } finally { - startWalk.release(); - r.release(); + treeWalk.release(); } } finally { dc.unlock(); @@ -381,6 +377,75 @@ public class CheckoutCommand extends GitCommand { return this; } + private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc) + throws IOException { + DirCacheIterator dci = new DirCacheIterator(dc); + treeWalk.addTree(dci); + + final ObjectReader r = treeWalk.getObjectReader(); + DirCacheEditor editor = dc.editor(); + while (treeWalk.next()) { + DirCacheEntry entry = dci.getDirCacheEntry(); + // Only add one edit per path + if (entry != null && entry.getStage() > DirCacheEntry.STAGE_1) + continue; + editor.add(new PathEdit(treeWalk.getPathString()) { + public void apply(DirCacheEntry ent) { + int stage = ent.getStage(); + if (stage > DirCacheEntry.STAGE_0) { + if (checkoutStage != null) { + if (stage == checkoutStage.number) + checkoutPath(ent, r); + } else { + UnmergedPathException e = new UnmergedPathException( + ent); + throw new JGitInternalException(e.getMessage(), e); + } + } else { + checkoutPath(ent, r); + } + } + }); + } + editor.commit(); + } + + private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc, + RevCommit commit) throws IOException { + treeWalk.addTree(commit.getTree()); + final ObjectReader r = treeWalk.getObjectReader(); + DirCacheEditor editor = dc.editor(); + while (treeWalk.next()) { + final ObjectId blobId = treeWalk.getObjectId(0); + final FileMode mode = treeWalk.getFileMode(0); + editor.add(new PathEdit(treeWalk.getPathString()) { + public void apply(DirCacheEntry ent) { + ent.setObjectId(blobId); + ent.setFileMode(mode); + checkoutPath(ent, r); + } + }); + } + editor.commit(); + } + + private void checkoutPath(DirCacheEntry entry, ObjectReader reader) { + File file = new File(repo.getWorkTree(), entry.getPathString()); + File parentDir = file.getParentFile(); + try { + FileUtils.mkdirs(parentDir, true); + DirCacheCheckout.checkoutEntry(repo, file, entry, reader); + } catch (IOException e) { + throw new JGitInternalException(MessageFormat.format( + JGitText.get().checkoutConflictWithFile, + entry.getPathString()), e); + } + } + + private boolean isCheckoutIndex() { + return startCommit == null && startPoint == null; + } + private ObjectId getStartPoint() throws AmbiguousObjectException, RefNotFoundException, IOException { if (startCommit != null) @@ -483,6 +548,7 @@ public class CheckoutCommand extends GitCommand { checkCallable(); this.startPoint = startPoint; this.startCommit = null; + checkOptions(); return this; } @@ -503,6 +569,7 @@ public class CheckoutCommand extends GitCommand { checkCallable(); this.startCommit = startCommit; this.startPoint = null; + checkOptions(); return this; } @@ -522,6 +589,24 @@ public class CheckoutCommand extends GitCommand { return this; } + /** + * When checking out the index, check out the specified stage (ours or + * theirs) for unmerged paths. + *

+ * This can not be used when checking out a branch, only when checking out + * the index. + * + * @param stage + * the stage to check out + * @return this + */ + public CheckoutCommand setStage(Stage stage) { + checkCallable(); + this.checkoutStage = stage; + checkOptions(); + return this; + } + /** * @return the result, never null */ @@ -530,4 +615,11 @@ public class CheckoutCommand extends GitCommand { return CheckoutResult.NOT_TRIED_RESULT; return status; } + + private void checkOptions() { + if (checkoutStage != null && !isCheckoutIndex()) + throw new IllegalStateException( + "Checking out ours/theirs is only possible when checking out index, " + + "not when switching branches."); + } }