Bug: 334764 Change-Id: Ice404629687d7f2a595d8d4eccf471b12f7e32ec Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>tags/v0.12.1
/* | |||||
* 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; | |||||
} | |||||
} |
exceptionCaughtDuringExecutionOfMergeCommand=Exception caught during execution of merge command. {0} | exceptionCaughtDuringExecutionOfMergeCommand=Exception caught during execution of merge command. {0} | ||||
exceptionCaughtDuringExecutionOfPushCommand=Exception caught during execution of push command | exceptionCaughtDuringExecutionOfPushCommand=Exception caught during execution of push command | ||||
exceptionCaughtDuringExecutionOfPullCommand=Exception caught during execution of pull 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} | exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0} | ||||
exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command | exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command | ||||
exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command | exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command |
/***/ public String exceptionCaughtDuringExecutionOfMergeCommand; | /***/ public String exceptionCaughtDuringExecutionOfMergeCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfPushCommand; | /***/ public String exceptionCaughtDuringExecutionOfPushCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfPullCommand; | /***/ public String exceptionCaughtDuringExecutionOfPullCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfResetCommand; | |||||
/***/ public String exceptionCaughtDuringExecutionOfRevertCommand; | /***/ public String exceptionCaughtDuringExecutionOfRevertCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfRmCommand; | /***/ public String exceptionCaughtDuringExecutionOfRmCommand; | ||||
/***/ public String exceptionCaughtDuringExecutionOfTagCommand; | /***/ public String exceptionCaughtDuringExecutionOfTagCommand; |
return new CheckoutCommand(repo); | 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 | * @return the git repository this class is interacting with | ||||
*/ | */ |
/* | |||||
* 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); | |||||
} | |||||
} |