Browse Source
Bug: 334764 Change-Id: Ice404629687d7f2a595d8d4eccf471b12f7e32ec Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>stable-0.12
Chris Aniszczyk
14 years ago
5 changed files with 480 additions and 0 deletions
@ -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; |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue