Bug: 334764 Change-Id: Ice404629687d7f2a595d8d4eccf471b12f7e32ec Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>tags/v0.12.1
@@ -0,0 +1,207 @@ | |||
/* | |||
* Copyright (C) 2011, Chris Aniszczyk <caniszczyk@gmail.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.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; | |||
} | |||
} |
@@ -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 |
@@ -235,6 +235,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String exceptionCaughtDuringExecutionOfMergeCommand; | |||
/***/ public String exceptionCaughtDuringExecutionOfPushCommand; | |||
/***/ public String exceptionCaughtDuringExecutionOfPullCommand; | |||
/***/ public String exceptionCaughtDuringExecutionOfResetCommand; | |||
/***/ public String exceptionCaughtDuringExecutionOfRevertCommand; | |||
/***/ public String exceptionCaughtDuringExecutionOfRmCommand; | |||
/***/ public String exceptionCaughtDuringExecutionOfTagCommand; |
@@ -321,6 +321,19 @@ public class Git { | |||
return new CheckoutCommand(repo); | |||
} | |||
/** | |||
* Returns a command object to execute a {@code reset} command | |||
* | |||
* @see <a | |||
* href="http://www.kernel.org/pub/software/scm/git/docs/git-reset.html" | |||
* >Git documentation about reset</a> | |||
* @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 | |||
*/ |
@@ -0,0 +1,258 @@ | |||
/* | |||
* Copyright (C) 2011, Chris Aniszczyk <caniszczyk@gmail.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.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 <a href="http://www.kernel.org/pub/software/scm/git/docs/git-reset.html" | |||
* >Git documentation about Reset</a> | |||
*/ | |||
public class ResetCommand extends GitCommand<Ref> { | |||
/** | |||
* 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); | |||
} | |||
} |