From 37fe0988b26b7893888a073a33ccf0865723ca8b Mon Sep 17 00:00:00 2001 From: Bernard Leach Date: Thu, 19 May 2011 21:04:20 +1000 Subject: [PATCH] Add reset with paths support to ResetCommand Bug: 338701 Change-Id: Id7cbce47131b459e632ddc2c9a94628c7d0b75cd Signed-off-by: Chris Aniszczyk --- .../eclipse/jgit/api/ResetCommandTest.java | 83 +++++++++++++++++-- .../org/eclipse/jgit/api/ResetCommand.java | 79 +++++++++++++++++- 2 files changed, 154 insertions(+), 8 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java index 578b98170..f79592f22 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; @@ -58,6 +59,7 @@ import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -66,6 +68,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FileUtils; +import org.junit.Assert; import org.junit.Test; public class ResetCommandTest extends RepositoryTestCase { @@ -74,10 +77,14 @@ public class ResetCommandTest extends RepositoryTestCase { private RevCommit initialCommit; + private RevCommit secondCommit; + private File indexFile; private File untrackedFile; + private DirCacheEntry prestage; + public void setupRepository() throws IOException, NoFilepatternException, NoHeadException, NoMessageException, ConcurrentRefUpdateException, JGitInternalException, WrongRepositoryStateException { @@ -95,7 +102,10 @@ public class ResetCommandTest extends RepositoryTestCase { // add file and commit it git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("adding a.txt").call(); + secondCommit = git.commit().setMessage("adding a.txt").call(); + + prestage = DirCache.read(db.getIndexFile(), db.getFS()).getEntry( + indexFile.getName()); // modify file and add to index writer.print("new content"); @@ -178,6 +188,66 @@ public class ResetCommandTest extends RepositoryTestCase { assertReflog(prevHead, head); } + @Test + public void testPathsReset() throws Exception { + setupRepository(); + + DirCacheEntry preReset = DirCache.read(db.getIndexFile(), db.getFS()) + .getEntry(indexFile.getName()); + assertNotNull(preReset); + + git.add().addFilepattern(untrackedFile.getName()).call(); + + // 'a.txt' has already been modified in setupRepository + // 'notAddedToIndex.txt' has been added to repository + git.reset().addPath(indexFile.getName()) + .addPath(untrackedFile.getName()).call(); + + DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS()) + .getEntry(indexFile.getName()); + assertNotNull(postReset); + Assert.assertNotSame(preReset.getObjectId(), postReset.getObjectId()); + Assert.assertEquals(prestage.getObjectId(), postReset.getObjectId()); + + // check that HEAD hasn't moved + ObjectId head = db.resolve(Constants.HEAD); + assertTrue(head.equals(secondCommit)); + // check if files still exist + assertTrue(untrackedFile.exists()); + assertTrue(indexFile.exists()); + assertTrue(inHead(indexFile.getName())); + assertTrue(inIndex(indexFile.getName())); + assertFalse(inIndex(untrackedFile.getName())); + } + + @Test + public void testPathsResetWithRef() throws Exception { + setupRepository(); + + DirCacheEntry preReset = DirCache.read(db.getIndexFile(), db.getFS()) + .getEntry(indexFile.getName()); + assertNotNull(preReset); + + git.add().addFilepattern(untrackedFile.getName()).call(); + + // 'a.txt' has already been modified in setupRepository + // 'notAddedToIndex.txt' has been added to repository + // reset to the inital commit + git.reset().setRef(initialCommit.getName()) + .addPath(indexFile.getName()) + .addPath(untrackedFile.getName()).call(); + + // check that HEAD hasn't moved + ObjectId head = db.resolve(Constants.HEAD); + assertTrue(head.equals(secondCommit)); + // check if files still exist + assertTrue(untrackedFile.exists()); + assertTrue(indexFile.exists()); + assertTrue(inHead(indexFile.getName())); + assertFalse(inIndex(indexFile.getName())); + assertFalse(inIndex(untrackedFile.getName())); + } + private void assertReflog(ObjectId prevHead, ObjectId head) throws IOException { // Check the reflog for HEAD @@ -187,9 +257,8 @@ public class ResetCommandTest extends RepositoryTestCase { assertEquals(expectedHeadMessage, actualHeadMessage); assertEquals(head.getName(), db.getReflogReader(Constants.HEAD) .getLastEntry().getNewId().getName()); - assertEquals(prevHead.getName(), db - .getReflogReader(Constants.HEAD).getLastEntry().getOldId() - .getName()); + assertEquals(prevHead.getName(), db.getReflogReader(Constants.HEAD) + .getLastEntry().getOldId().getName()); // The reflog for master contains the same as the one for HEAD String actualMasterMessage = db.getReflogReader("refs/heads/master") @@ -198,9 +267,9 @@ public class ResetCommandTest extends RepositoryTestCase { assertEquals(expectedMasterMessage, actualMasterMessage); assertEquals(head.getName(), db.getReflogReader(Constants.HEAD) .getLastEntry().getNewId().getName()); - assertEquals(prevHead.getName(), - db.getReflogReader("refs/heads/master").getLastEntry() - .getOldId().getName()); + assertEquals(prevHead.getName(), db + .getReflogReader("refs/heads/master").getLastEntry().getOldId() + .getName()); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java index 1ddfd6d77..206d4062b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java @@ -44,12 +44,17 @@ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; +import java.util.Collection; +import java.util.LinkedList; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; @@ -58,6 +63,9 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * A class used to execute a {@code Reset} command. It has setters for all @@ -103,10 +111,12 @@ public class ResetCommand extends GitCommand { KEEP // TODO not implemented yet } - private String ref; + private String ref = Constants.HEAD; private ResetType mode; + private Collection filepaths = new LinkedList(); + /** * * @param repo @@ -157,6 +167,13 @@ public class ResetCommand extends GitCommand { rw.release(); } + if (!filepaths.isEmpty()) { + // reset [commit] -- paths + resetIndexForPaths(commit); + setCallable(false); + return repo.getRef(Constants.HEAD); + } + // write the ref final RefUpdate ru = repo.updateRef(Constants.HEAD); ru.setNewObjectId(commitId); @@ -217,10 +234,70 @@ public class ResetCommand extends GitCommand { * @return this instance */ public ResetCommand setMode(ResetType mode) { + if (!filepaths.isEmpty()) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().illegalCombinationOfArguments, + "[--mixed | --soft | --hard]", "...")); this.mode = mode; return this; } + /** + * @param file + * the file to add + * @return this instance + */ + public ResetCommand addPath(String file) { + if (mode != null) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().illegalCombinationOfArguments, "...", + "[--mixed | --soft | --hard]")); + filepaths.add(file); + return this; + } + + private void resetIndexForPaths(RevCommit commit) { + DirCache dc = null; + final DirCacheEditor edit; + try { + dc = repo.lockDirCache(); + edit = dc.editor(); + + final TreeWalk tw = new TreeWalk(repo); + tw.addTree(new DirCacheIterator(dc)); + tw.addTree(commit.getTree()); + tw.setFilter(PathFilterGroup.createFromStrings(filepaths)); + + while (tw.next()) { + final String path = tw.getPathString(); + // DirCacheIterator dci = tw.getTree(0, DirCacheIterator.class); + final CanonicalTreeParser tree = tw.getTree(1, + CanonicalTreeParser.class); + if (tree == null) + // file is not in the commit, remove from index + edit.add(new DirCacheEditor.DeletePath(path)); + else { + // revert index to commit + edit.add(new DirCacheEditor.PathEdit(path) { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(tree.getEntryFileMode()); + ent.setObjectId(tree.getEntryObjectId()); + ent.setLastModified(0); + } + }); + } + } + + edit.commit(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (dc != null) + dc.unlock(); + } + } + private void resetIndex(RevCommit commit) throws IOException { DirCache dc = null; try {