From 5f258d91c02f0876b6dd3fd798717d2fe7c2db18 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Tue, 1 Feb 2011 08:47:04 -0600 Subject: [PATCH] Add git-reset to the Git API Bug: 334764 Change-Id: Ice404629687d7f2a595d8d4eccf471b12f7e32ec Signed-off-by: Chris Aniszczyk --- .../eclipse/jgit/api/ResetCommandTest.java | 207 ++++++++++++++ .../org/eclipse/jgit/JGitText.properties | 1 + .../src/org/eclipse/jgit/JGitText.java | 1 + .../src/org/eclipse/jgit/api/Git.java | 13 + .../org/eclipse/jgit/api/ResetCommand.java | 258 ++++++++++++++++++ 5 files changed, 480 insertions(+) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java 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 new file mode 100644 index 000000000..b886432a3 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2011, Chris Aniszczyk + * 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.api; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; + +import org.eclipse.jgit.api.ResetCommand.ResetType; +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +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.errors.AmbiguousObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RepositoryTestCase; +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.Test; + +public class ResetCommandTest extends RepositoryTestCase { + + private Git git; + + private RevCommit initialCommit; + + private File indexFile; + + private File untrackedFile; + + public void setupRepository() throws IOException, NoFilepatternException, + NoHeadException, NoMessageException, ConcurrentRefUpdateException, + JGitInternalException, WrongRepositoryStateException { + + // create initial commit + git = new Git(db); + initialCommit = git.commit().setMessage("initial commit").call(); + + // create file + indexFile = new File(db.getWorkTree(), "a.txt"); + FileUtils.createNewFile(indexFile); + PrintWriter writer = new PrintWriter(indexFile); + writer.print("content"); + writer.flush(); + + // add file and commit it + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("adding a.txt").call(); + + // modify file and add to index + writer.print("new content"); + writer.close(); + git.add().addFilepattern("a.txt").call(); + + // create a file not added to the index + untrackedFile = new File(db.getWorkTree(), + "notAddedToIndex.txt"); + FileUtils.createNewFile(untrackedFile); + PrintWriter writer2 = new PrintWriter(untrackedFile); + writer2.print("content"); + writer2.close(); + } + + @Test + public void testHardReset() throws JGitInternalException, + AmbiguousObjectException, IOException, NoFilepatternException, + NoHeadException, NoMessageException, ConcurrentRefUpdateException, + WrongRepositoryStateException { + setupRepository(); + git.reset().setMode(ResetType.HARD).setRef(initialCommit.getName()) + .call(); + // check if HEAD points to initial commit now + ObjectId head = db.resolve(Constants.HEAD); + assertTrue(head.equals(initialCommit)); + // check if files were removed + assertFalse(indexFile.exists()); + assertTrue(untrackedFile.exists()); + // fileInIndex must no longer be in HEAD and in the index + String fileInIndexPath = indexFile.getAbsolutePath(); + assertFalse(inHead(fileInIndexPath)); + assertFalse(inIndex(indexFile.getName())); + } + + @Test + public void testSoftReset() throws JGitInternalException, + AmbiguousObjectException, IOException, NoFilepatternException, + NoHeadException, NoMessageException, ConcurrentRefUpdateException, + WrongRepositoryStateException { + setupRepository(); + git.reset().setMode(ResetType.SOFT).setRef(initialCommit.getName()) + .call(); + // check if HEAD points to initial commit now + ObjectId head = db.resolve(Constants.HEAD); + assertTrue(head.equals(initialCommit)); + // check if files still exist + assertTrue(untrackedFile.exists()); + assertTrue(indexFile.exists()); + // fileInIndex must no longer be in HEAD but has to be in the index + String fileInIndexPath = indexFile.getAbsolutePath(); + assertFalse(inHead(fileInIndexPath)); + assertTrue(inIndex(indexFile.getName())); + } + + @Test + public void testMixedReset() throws JGitInternalException, + AmbiguousObjectException, IOException, NoFilepatternException, + NoHeadException, NoMessageException, ConcurrentRefUpdateException, + WrongRepositoryStateException { + setupRepository(); + git.reset().setMode(ResetType.MIXED).setRef(initialCommit.getName()) + .call(); + // check if HEAD points to initial commit now + ObjectId head = db.resolve(Constants.HEAD); + assertTrue(head.equals(initialCommit)); + // check if files still exist + assertTrue(untrackedFile.exists()); + assertTrue(indexFile.exists()); + // fileInIndex must no longer be in HEAD and in the index + String fileInIndexPath = indexFile.getAbsolutePath(); + assertFalse(inHead(fileInIndexPath)); + assertFalse(inIndex(indexFile.getName())); + } + + /** + * Checks if a file with the given path exists in the HEAD tree + * + * @param path + * @return true if the file exists + * @throws IOException + */ + private boolean inHead(String path) throws IOException { + ObjectId headId = db.resolve(Constants.HEAD); + RevWalk rw = new RevWalk(db); + TreeWalk tw = null; + try { + tw = TreeWalk.forPath(db, path, rw.parseTree(headId)); + return tw != null; + } finally { + rw.release(); + rw.dispose(); + if (tw != null) + tw.release(); + } + } + + /** + * Checks if a file with the given path exists in the index + * + * @param path + * @return true if the file exists + * @throws IOException + */ + private boolean inIndex(String path) throws IOException { + DirCache dc = DirCache.read(db.getIndexFile(), db.getFS()); + return dc.getEntry(path) != null; + } + +} diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index e48638397..b2d9937da 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -175,6 +175,7 @@ exceptionCaughtDuringExecutionOfFetchCommand=Exception caught during execution o exceptionCaughtDuringExecutionOfMergeCommand=Exception caught during execution of merge command. {0} exceptionCaughtDuringExecutionOfPushCommand=Exception caught during execution of push command exceptionCaughtDuringExecutionOfPullCommand=Exception caught during execution of pull command +exceptionCaughtDuringExecutionOfResetCommand=Exception caught during execution of reset command. {0} exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0} exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 7276dd26f..7265db8bc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -235,6 +235,7 @@ public static JGitText get() { /***/ public String exceptionCaughtDuringExecutionOfMergeCommand; /***/ public String exceptionCaughtDuringExecutionOfPushCommand; /***/ public String exceptionCaughtDuringExecutionOfPullCommand; + /***/ public String exceptionCaughtDuringExecutionOfResetCommand; /***/ public String exceptionCaughtDuringExecutionOfRevertCommand; /***/ public String exceptionCaughtDuringExecutionOfRmCommand; /***/ public String exceptionCaughtDuringExecutionOfTagCommand; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 27045eb47..88643f170 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -321,6 +321,19 @@ public CheckoutCommand checkout() { return new CheckoutCommand(repo); } + /** + * Returns a command object to execute a {@code reset} command + * + * @see Git documentation about reset + * @return a {@link ResetCommand} used to collect all optional parameters + * and to finally execute the {@code reset} command + */ + public ResetCommand reset() { + return new ResetCommand(repo); + } + /** * @return the git repository this class is interacting with */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java new file mode 100644 index 000000000..d55767d3a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2011, Chris Aniszczyk + * 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.api; + +import java.io.IOException; +import java.text.MessageFormat; + +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.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * A class used to execute a {@code Reset} command. It has setters for all + * supported options and arguments of this command and a {@link #call()} method + * to finally execute the command. Each instance of this class should only be + * used for one invocation of the command (means: one call to {@link #call()}) + * + * @see Git documentation about Reset + */ +public class ResetCommand extends GitCommand { + + /** + * Kind of reset + */ + public enum ResetType { + /** + * Just change the ref, the index and workdir are not changed. + */ + SOFT, + + /** + * Change the ref and the index, the workdir is not changed. + */ + MIXED, + + /** + * Change the ref, the index and the workdir + */ + HARD, + + /** + * Resets the index and updates the files in the working tree that are + * different between respective commit and HEAD, but keeps those which + * are different between the index and working tree + */ + MERGE, // TODO not implemented yet + + /** + * Change the ref, the index and the workdir that are different between + * respective commit and HEAD + */ + KEEP // TODO not implemented yet + } + + private String ref; + + private ResetType mode; + + /** + * + * @param repo + */ + public ResetCommand(Repository repo) { + super(repo); + } + + /** + * Executes the {@code Reset} command. Each instance of this class should + * only be used for one invocation of the command. Don't call this method + * twice on an instance. + * + * @return the Ref after reset + */ + public Ref call() throws IOException { + checkCallable(); + + Ref r; + RevCommit commit; + + try { + boolean merging = false; + if (repo.getRepositoryState().equals(RepositoryState.MERGING) + || repo.getRepositoryState().equals( + RepositoryState.MERGING_RESOLVED)) + merging = true; + + // resolve the ref to a commit + final ObjectId commitId; + try { + commitId = repo.resolve(ref); + } catch (IOException e) { + throw new JGitInternalException( + MessageFormat.format(JGitText.get().cannotRead, ref), + e); + } + RevWalk rw = new RevWalk(repo); + try { + commit = rw.parseCommit(commitId); + } catch (IOException e) { + throw new JGitInternalException( + MessageFormat.format( + JGitText.get().cannotReadCommit, commitId.toString()), + e); + } finally { + rw.release(); + } + + // write the ref + final RefUpdate ru = repo.updateRef(Constants.HEAD); + ru.setNewObjectId(commitId); + + String refName = Repository.shortenRefName(ref); + String message = "reset --" //$NON-NLS-1$ + + mode.toString().toLowerCase() + " " + refName; //$NON-NLS-1$ + ru.setRefLogMessage(message, false); + if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().cannotLock, ru.getName())); + + switch (mode) { + case HARD: + checkoutIndex(commit); + break; + case MIXED: + resetIndex(commit); + break; + case SOFT: // do nothing, only the ref was changed + break; + case KEEP: // TODO + case MERGE: // TODO + throw new UnsupportedOperationException(); + + } + + if (mode != ResetType.SOFT && merging) + resetMerge(); + + setCallable(false); + r = ru.getRef(); + } catch (IOException e) { + throw new JGitInternalException( + JGitText.get().exceptionCaughtDuringExecutionOfResetCommand, + e); + } + + return r; + } + + /** + * @param ref + * the ref to reset to + * @return this instance + */ + public ResetCommand setRef(String ref) { + this.ref = ref; + return this; + } + + /** + * @param mode + * the mode of the reset command + * @return this instance + */ + public ResetCommand setMode(ResetType mode) { + this.mode = mode; + return this; + } + + private void resetIndex(RevCommit commit) throws IOException { + DirCache dc = null; + try { + dc = repo.lockDirCache(); + dc.clear(); + DirCacheBuilder dcb = dc.builder(); + dcb.addTree(new byte[0], 0, repo.newObjectReader(), + commit.getTree()); + dcb.commit(); + } catch (IOException e) { + throw e; + } finally { + if (dc != null) + dc.unlock(); + } + } + + private void checkoutIndex(RevCommit commit) throws IOException { + DirCache dc = null; + try { + dc = repo.lockDirCache(); + DirCacheCheckout checkout = new DirCacheCheckout(repo, dc, + commit.getTree()); + checkout.setFailOnConflict(false); + checkout.checkout(); + } catch (IOException e) { + throw e; + } finally { + if (dc != null) + dc.unlock(); + } + } + + private void resetMerge() throws IOException { + repo.writeMergeHeads(null); + repo.writeMergeCommitMsg(null); + } + +}