Add git-reset to the Git API
Bug: 334764 Change-Id: Ice404629687d7f2a595d8d4eccf471b12f7e32ec Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
This commit is contained in:
parent
c13bf05754
commit
5f258d91c0
|
@ -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 static JGitText get() {
|
|||
/***/ 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 CheckoutCommand checkout() {
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue