This is a first iteration to implement Rebase. At the moment, this does not implement --continue and --skip, so if the first conflict is found, the only option is to --abort the command. Bug: 328217 Change-Id: I24d60c0214e71e5572955f8261e10a42e9e95298 Signed-off-by: Mathias Kinzler <mathias.kinzler@sap.com> Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>tags/v0.10.1
/* | |||||
* Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.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.BufferedReader; | |||||
import java.io.File; | |||||
import java.io.FileInputStream; | |||||
import java.io.IOException; | |||||
import java.io.InputStreamReader; | |||||
import org.eclipse.jgit.api.RebaseCommand.Operation; | |||||
import org.eclipse.jgit.api.RebaseResult.Status; | |||||
import org.eclipse.jgit.dircache.DirCacheCheckout; | |||||
import org.eclipse.jgit.lib.Constants; | |||||
import org.eclipse.jgit.lib.ObjectId; | |||||
import org.eclipse.jgit.lib.RefUpdate; | |||||
import org.eclipse.jgit.lib.RepositoryState; | |||||
import org.eclipse.jgit.lib.RepositoryTestCase; | |||||
import org.eclipse.jgit.revwalk.RevCommit; | |||||
import org.eclipse.jgit.revwalk.RevWalk; | |||||
public class RebaseCommandTest extends RepositoryTestCase { | |||||
private void createBranch(ObjectId objectId, String branchName) | |||||
throws IOException { | |||||
RefUpdate updateRef = db.updateRef(branchName); | |||||
updateRef.setNewObjectId(objectId); | |||||
updateRef.update(); | |||||
} | |||||
private void checkoutBranch(String branchName) | |||||
throws IllegalStateException, IOException { | |||||
RevWalk walk = new RevWalk(db); | |||||
RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD)); | |||||
RevCommit branch = walk.parseCommit(db.resolve(branchName)); | |||||
DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree().getId(), | |||||
db.lockDirCache(), branch.getTree().getId()); | |||||
dco.setFailOnConflict(true); | |||||
dco.checkout(); | |||||
walk.release(); | |||||
// update the HEAD | |||||
RefUpdate refUpdate = db.updateRef(Constants.HEAD); | |||||
refUpdate.link(branchName); | |||||
} | |||||
public void testFastForwardWithNewFile() throws Exception { | |||||
Git git = new Git(db); | |||||
// create file1 on master | |||||
writeTrashFile("file1", "file1"); | |||||
git.add().addFilepattern("file1").call(); | |||||
RevCommit first = git.commit().setMessage("Add file1").call(); | |||||
assertTrue(new File(db.getWorkTree(), "file1").exists()); | |||||
// create a topic branch | |||||
createBranch(first, "refs/heads/topic"); | |||||
// create file2 on master | |||||
writeTrashFile("file2", "file2"); | |||||
git.add().addFilepattern("file2").call(); | |||||
git.commit().setMessage("Add file2").call(); | |||||
assertTrue(new File(db.getWorkTree(), "file2").exists()); | |||||
checkoutBranch("refs/heads/topic"); | |||||
assertFalse(new File(db.getWorkTree(), "file2").exists()); | |||||
RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); | |||||
assertEquals(Status.UP_TO_DATE, res.getStatus()); | |||||
} | |||||
public void testConflictFreeWithSingleFile() throws Exception { | |||||
Git git = new Git(db); | |||||
// create file1 on master | |||||
File theFile = writeTrashFile("file1", "1\n2\n3\n"); | |||||
git.add().addFilepattern("file1").call(); | |||||
RevCommit second = git.commit().setMessage("Add file1").call(); | |||||
assertTrue(new File(db.getWorkTree(), "file1").exists()); | |||||
// change first line in master and commit | |||||
writeTrashFile("file1", "1master\n2\n3\n"); | |||||
checkFile(theFile, "1master\n2\n3\n"); | |||||
git.add().addFilepattern("file1").call(); | |||||
RevCommit lastMasterChange = git.commit().setMessage( | |||||
"change file1 in master").call(); | |||||
// create a topic branch based on second commit | |||||
createBranch(second, "refs/heads/topic"); | |||||
checkoutBranch("refs/heads/topic"); | |||||
// we have the old content again | |||||
checkFile(theFile, "1\n2\n3\n"); | |||||
assertTrue(new File(db.getWorkTree(), "file1").exists()); | |||||
// change third line in topic branch | |||||
writeTrashFile("file1", "1\n2\n3\ntopic\n"); | |||||
git.add().addFilepattern("file1").call(); | |||||
git.commit().setMessage("change file1 in topic").call(); | |||||
RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); | |||||
assertEquals(Status.OK, res.getStatus()); | |||||
checkFile(theFile, "1master\n2\n3\ntopic\n"); | |||||
// our old branch should be checked out again | |||||
assertEquals("refs/heads/topic", db.getFullBranch()); | |||||
assertEquals(lastMasterChange, new RevWalk(db).parseCommit( | |||||
db.resolve(Constants.HEAD)).getParent(0)); | |||||
} | |||||
public void testFilesAddedFromTwoBranches() throws Exception { | |||||
Git git = new Git(db); | |||||
// create file1 on master | |||||
writeTrashFile("file1", "file1"); | |||||
git.add().addFilepattern("file1").call(); | |||||
RevCommit masterCommit = git.commit().setMessage("Add file1 to master") | |||||
.call(); | |||||
// create a branch named file2 and add file2 | |||||
createBranch(masterCommit, "refs/heads/file2"); | |||||
checkoutBranch("refs/heads/file2"); | |||||
writeTrashFile("file2", "file2"); | |||||
git.add().addFilepattern("file2").call(); | |||||
RevCommit addFile2 = git.commit().setMessage( | |||||
"Add file2 to branch file2").call(); | |||||
// create a branch named file3 and add file3 | |||||
createBranch(masterCommit, "refs/heads/file3"); | |||||
checkoutBranch("refs/heads/file3"); | |||||
writeTrashFile("file3", "file3"); | |||||
git.add().addFilepattern("file3").call(); | |||||
git.commit().setMessage("Add file3 to branch file3").call(); | |||||
assertTrue(new File(db.getWorkTree(), "file1").exists()); | |||||
assertFalse(new File(db.getWorkTree(), "file2").exists()); | |||||
assertTrue(new File(db.getWorkTree(), "file3").exists()); | |||||
RebaseResult res = git.rebase().setUpstream("refs/heads/file2").call(); | |||||
assertEquals(Status.OK, res.getStatus()); | |||||
assertTrue(new File(db.getWorkTree(), "file1").exists()); | |||||
assertTrue(new File(db.getWorkTree(), "file2").exists()); | |||||
assertTrue(new File(db.getWorkTree(), "file3").exists()); | |||||
// our old branch should be checked out again | |||||
assertEquals("refs/heads/file3", db.getFullBranch()); | |||||
assertEquals(addFile2, new RevWalk(db).parseCommit( | |||||
db.resolve(Constants.HEAD)).getParent(0)); | |||||
checkoutBranch("refs/heads/file2"); | |||||
assertTrue(new File(db.getWorkTree(), "file1").exists()); | |||||
assertTrue(new File(db.getWorkTree(), "file2").exists()); | |||||
assertFalse(new File(db.getWorkTree(), "file3").exists()); | |||||
} | |||||
public void testAbortOnConflict() throws Exception { | |||||
Git git = new Git(db); | |||||
// create file1 on master | |||||
File theFile = writeTrashFile("file1", "1\n2\n3\n"); | |||||
git.add().addFilepattern("file1").call(); | |||||
RevCommit second = git.commit().setMessage("Add file1").call(); | |||||
assertTrue(new File(db.getWorkTree(), "file1").exists()); | |||||
// change first line in master and commit | |||||
writeTrashFile("file1", "1master\n2\n3\n"); | |||||
checkFile(theFile, "1master\n2\n3\n"); | |||||
git.add().addFilepattern("file1").call(); | |||||
git.commit().setMessage("change file1 in master").call(); | |||||
// create a topic branch based on second commit | |||||
createBranch(second, "refs/heads/topic"); | |||||
checkoutBranch("refs/heads/topic"); | |||||
// we have the old content again | |||||
checkFile(theFile, "1\n2\n3\n"); | |||||
assertTrue(new File(db.getWorkTree(), "file1").exists()); | |||||
// add a line (non-conflicting) | |||||
writeTrashFile("file1", "1\n2\n3\n4\n"); | |||||
git.add().addFilepattern("file1").call(); | |||||
git.commit().setMessage("add a line to file1 in topic").call(); | |||||
// change first line (conflicting) | |||||
writeTrashFile("file1", "1topic\n2\n3\n4\n"); | |||||
git.add().addFilepattern("file1").call(); | |||||
git.commit().setMessage("change file1 in topic").call(); | |||||
// change second line (not conflicting) | |||||
writeTrashFile("file1", "1topic\n2topic\n3\n4\n"); | |||||
git.add().addFilepattern("file1").call(); | |||||
RevCommit lastTopicCommit = git.commit().setMessage( | |||||
"change file1 in topic again").call(); | |||||
RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); | |||||
assertEquals(Status.STOPPED, res.getStatus()); | |||||
checkFile(theFile, | |||||
"<<<<<<< OURS\n1master\n=======\n1topic\n>>>>>>> THEIRS\n2\n3\n4\n"); | |||||
assertEquals(RepositoryState.REBASING_MERGE, db.getRepositoryState()); | |||||
// the first one should be included, so we should have left two picks in | |||||
// the file | |||||
assertEquals(countPicks(), 2); | |||||
// abort should reset to topic branch | |||||
res = git.rebase().setOperation(Operation.ABORT).call(); | |||||
assertEquals(res.getStatus(), Status.ABORTED); | |||||
assertEquals("refs/heads/topic", db.getFullBranch()); | |||||
checkFile(theFile, "1topic\n2topic\n3\n4\n"); | |||||
RevWalk rw = new RevWalk(db); | |||||
assertEquals(lastTopicCommit, rw | |||||
.parseCommit(db.resolve(Constants.HEAD))); | |||||
} | |||||
private int countPicks() throws IOException { | |||||
int count = 0; | |||||
File todoFile = new File(db.getDirectory(), | |||||
"rebase-merge/git-rebase-todo"); | |||||
BufferedReader br = new BufferedReader(new InputStreamReader( | |||||
new FileInputStream(todoFile), "UTF-8")); | |||||
try { | |||||
String line = br.readLine(); | |||||
while (line != null) { | |||||
if (line.startsWith("pick ")) | |||||
count++; | |||||
line = br.readLine(); | |||||
} | |||||
return count; | |||||
} finally { | |||||
br.close(); | |||||
} | |||||
} | |||||
} |
URLNotFound={0} not found | URLNotFound={0} not found | ||||
aNewObjectIdIsRequired=A NewObjectId is required. | aNewObjectIdIsRequired=A NewObjectId is required. | ||||
abbreviationLengthMustBeNonNegative=Abbreviation length must not be negative. | abbreviationLengthMustBeNonNegative=Abbreviation length must not be negative. | ||||
abortingRebase=Aborting rebase: resetting to {0} | |||||
advertisementCameBefore=advertisement of {0}^{} came before {1} | advertisementCameBefore=advertisement of {0}^{} came before {1} | ||||
advertisementOfCameBefore=advertisement of {0}^{} came before {1} | advertisementOfCameBefore=advertisement of {0}^{} came before {1} | ||||
amazonS3ActionFailed={0} of '{1}' failed: {2} {3} | amazonS3ActionFailed={0} of '{1}' failed: {2} {3} | ||||
ambiguousObjectAbbreviation=Object abbreviation {0} is ambiguous | ambiguousObjectAbbreviation=Object abbreviation {0} is ambiguous | ||||
anExceptionOccurredWhileTryingToAddTheIdOfHEAD=An exception occurred while trying to add the Id of HEAD | anExceptionOccurredWhileTryingToAddTheIdOfHEAD=An exception occurred while trying to add the Id of HEAD | ||||
anSSHSessionHasBeenAlreadyCreated=An SSH session has been already created | anSSHSessionHasBeenAlreadyCreated=An SSH session has been already created | ||||
applyingCommit=Applying {0} | |||||
atLeastOnePathIsRequired=At least one path is required. | atLeastOnePathIsRequired=At least one path is required. | ||||
atLeastOnePatternIsRequired=At least one pattern is required. | atLeastOnePatternIsRequired=At least one pattern is required. | ||||
atLeastTwoFiltersNeeded=At least two filters needed. | atLeastTwoFiltersNeeded=At least two filters needed. | ||||
missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch | missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch | ||||
missingObject=Missing {0} {1} | missingObject=Missing {0} {1} | ||||
missingPrerequisiteCommits=missing prerequisite commits: | missingPrerequisiteCommits=missing prerequisite commits: | ||||
missingRequiredParameter=Parameter "{0}" is missing | |||||
missingSecretkey=Missing secretkey. | missingSecretkey=Missing secretkey. | ||||
mixedStagesNotAllowed=Mixed stages not allowed | mixedStagesNotAllowed=Mixed stages not allowed | ||||
multipleMergeBasesFor=Multiple merge bases for:\n {0}\n {1} found:\n {2}\n {3} | multipleMergeBasesFor=Multiple merge bases for:\n {0}\n {1} found:\n {2}\n {3} | ||||
objectIsCorrupt=Object {0} is corrupt: {1} | objectIsCorrupt=Object {0} is corrupt: {1} | ||||
objectIsNotA=Object {0} is not a {1}. | objectIsNotA=Object {0} is not a {1}. | ||||
objectNotFoundIn=Object {0} not found in {1}. | objectNotFoundIn=Object {0} not found in {1}. | ||||
obtainingCommitsForCherryPick=Obtaining commits that need to be cherry-picked | |||||
offsetWrittenDeltaBaseForObjectNotFoundInAPack=Offset-written delta base for object not found in a pack | offsetWrittenDeltaBaseForObjectNotFoundInAPack=Offset-written delta base for object not found in a pack | ||||
onlyAlreadyUpToDateAndFastForwardMergesAreAvailable=only already-up-to-date and fast forward merges are available | onlyAlreadyUpToDateAndFastForwardMergesAreAvailable=only already-up-to-date and fast forward merges are available | ||||
onlyOneFetchSupported=Only one fetch supported | onlyOneFetchSupported=Only one fetch supported | ||||
repositoryState_rebaseWithMerge=Rebase w/merge | repositoryState_rebaseWithMerge=Rebase w/merge | ||||
requiredHashFunctionNotAvailable=Required hash function {0} not available. | requiredHashFunctionNotAvailable=Required hash function {0} not available. | ||||
resolvingDeltas=Resolving deltas | resolvingDeltas=Resolving deltas | ||||
resettingHead=Resetting head to {0} | |||||
resultLengthIncorrect=result length incorrect | resultLengthIncorrect=result length incorrect | ||||
rewinding=Rewinding to commit {0} | |||||
searchForReuse=Finding sources | searchForReuse=Finding sources | ||||
sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm. | sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm. | ||||
serviceNotPermitted={0} not permitted | serviceNotPermitted={0} not permitted | ||||
writingNotSupported=Writing {0} not supported. | writingNotSupported=Writing {0} not supported. | ||||
writingObjects=Writing objects | writingObjects=Writing objects | ||||
wrongDecompressedLength=wrong decompressed length | wrongDecompressedLength=wrong decompressed length | ||||
wrongRepositoryState=Wrong Repository State: {0} |
/***/ public String URLNotFound; | /***/ public String URLNotFound; | ||||
/***/ public String aNewObjectIdIsRequired; | /***/ public String aNewObjectIdIsRequired; | ||||
/***/ public String abbreviationLengthMustBeNonNegative; | /***/ public String abbreviationLengthMustBeNonNegative; | ||||
/***/ public String abortingRebase; | |||||
/***/ public String advertisementCameBefore; | /***/ public String advertisementCameBefore; | ||||
/***/ public String advertisementOfCameBefore; | /***/ public String advertisementOfCameBefore; | ||||
/***/ public String amazonS3ActionFailed; | /***/ public String amazonS3ActionFailed; | ||||
/***/ public String amazonS3ActionFailedGivingUp; | /***/ public String amazonS3ActionFailedGivingUp; | ||||
/***/ public String ambiguousObjectAbbreviation; | /***/ public String ambiguousObjectAbbreviation; | ||||
/***/ public String anExceptionOccurredWhileTryingToAddTheIdOfHEAD; | /***/ public String anExceptionOccurredWhileTryingToAddTheIdOfHEAD; | ||||
/***/ public String applyingCommit; | |||||
/***/ public String anSSHSessionHasBeenAlreadyCreated; | /***/ public String anSSHSessionHasBeenAlreadyCreated; | ||||
/***/ public String atLeastOnePathIsRequired; | /***/ public String atLeastOnePathIsRequired; | ||||
/***/ public String atLeastOnePatternIsRequired; | /***/ public String atLeastOnePatternIsRequired; | ||||
/***/ public String missingForwardImageInGITBinaryPatch; | /***/ public String missingForwardImageInGITBinaryPatch; | ||||
/***/ public String missingObject; | /***/ public String missingObject; | ||||
/***/ public String missingPrerequisiteCommits; | /***/ public String missingPrerequisiteCommits; | ||||
/***/ public String missingRequiredParameter; | |||||
/***/ public String missingSecretkey; | /***/ public String missingSecretkey; | ||||
/***/ public String mixedStagesNotAllowed; | /***/ public String mixedStagesNotAllowed; | ||||
/***/ public String multipleMergeBasesFor; | /***/ public String multipleMergeBasesFor; | ||||
/***/ public String objectIsCorrupt; | /***/ public String objectIsCorrupt; | ||||
/***/ public String objectIsNotA; | /***/ public String objectIsNotA; | ||||
/***/ public String objectNotFoundIn; | /***/ public String objectNotFoundIn; | ||||
/***/ public String obtainingCommitsForCherryPick; | |||||
/***/ public String offsetWrittenDeltaBaseForObjectNotFoundInAPack; | /***/ public String offsetWrittenDeltaBaseForObjectNotFoundInAPack; | ||||
/***/ public String onlyAlreadyUpToDateAndFastForwardMergesAreAvailable; | /***/ public String onlyAlreadyUpToDateAndFastForwardMergesAreAvailable; | ||||
/***/ public String onlyOneFetchSupported; | /***/ public String onlyOneFetchSupported; | ||||
/***/ public String repositoryState_rebaseOrApplyMailbox; | /***/ public String repositoryState_rebaseOrApplyMailbox; | ||||
/***/ public String repositoryState_rebaseWithMerge; | /***/ public String repositoryState_rebaseWithMerge; | ||||
/***/ public String requiredHashFunctionNotAvailable; | /***/ public String requiredHashFunctionNotAvailable; | ||||
/***/ public String resettingHead; | |||||
/***/ public String resolvingDeltas; | /***/ public String resolvingDeltas; | ||||
/***/ public String resultLengthIncorrect; | /***/ public String resultLengthIncorrect; | ||||
/***/ public String rewinding; | |||||
/***/ public String searchForReuse; | /***/ public String searchForReuse; | ||||
/***/ public String sequenceTooLargeForDiffAlgorithm; | /***/ public String sequenceTooLargeForDiffAlgorithm; | ||||
/***/ public String serviceNotPermitted; | /***/ public String serviceNotPermitted; | ||||
/***/ public String writingNotSupported; | /***/ public String writingNotSupported; | ||||
/***/ public String writingObjects; | /***/ public String writingObjects; | ||||
/***/ public String wrongDecompressedLength; | /***/ public String wrongDecompressedLength; | ||||
/***/ public String wrongRepositoryState; | |||||
} | } |
return new CherryPickCommand(repo); | return new CherryPickCommand(repo); | ||||
} | } | ||||
/** | |||||
* Returns a command object to execute a {@code Rebase} command | |||||
* | |||||
* @see <a | |||||
* href="http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html" | |||||
* >Git documentation about rebase</a> | |||||
* @return a {@link RebaseCommand} used to collect all optional parameters | |||||
* and to finally execute the {@code rebase} command | |||||
*/ | |||||
public RebaseCommand rebase() { | |||||
return new RebaseCommand(repo); | |||||
} | |||||
/** | /** | ||||
* @return the git repository this class is interacting with | * @return the git repository this class is interacting with | ||||
*/ | */ |
/* | |||||
* Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.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.BufferedReader; | |||||
import java.io.BufferedWriter; | |||||
import java.io.File; | |||||
import java.io.FileInputStream; | |||||
import java.io.FileOutputStream; | |||||
import java.io.IOException; | |||||
import java.io.InputStreamReader; | |||||
import java.io.OutputStreamWriter; | |||||
import java.text.MessageFormat; | |||||
import java.util.ArrayList; | |||||
import java.util.Collection; | |||||
import java.util.Collections; | |||||
import java.util.List; | |||||
import org.eclipse.jgit.JGitText; | |||||
import org.eclipse.jgit.api.RebaseResult.Status; | |||||
import org.eclipse.jgit.api.errors.GitAPIException; | |||||
import org.eclipse.jgit.api.errors.JGitInternalException; | |||||
import org.eclipse.jgit.api.errors.NoHeadException; | |||||
import org.eclipse.jgit.api.errors.RefNotFoundException; | |||||
import org.eclipse.jgit.api.errors.WrongRepositoryStateException; | |||||
import org.eclipse.jgit.dircache.DirCacheCheckout; | |||||
import org.eclipse.jgit.lib.AbbreviatedObjectId; | |||||
import org.eclipse.jgit.lib.Constants; | |||||
import org.eclipse.jgit.lib.NullProgressMonitor; | |||||
import org.eclipse.jgit.lib.ObjectId; | |||||
import org.eclipse.jgit.lib.ObjectReader; | |||||
import org.eclipse.jgit.lib.ProgressMonitor; | |||||
import org.eclipse.jgit.lib.Ref; | |||||
import org.eclipse.jgit.lib.RefUpdate; | |||||
import org.eclipse.jgit.lib.Repository; | |||||
import org.eclipse.jgit.lib.RefUpdate.Result; | |||||
import org.eclipse.jgit.revwalk.RevCommit; | |||||
import org.eclipse.jgit.revwalk.RevWalk; | |||||
import org.eclipse.jgit.util.IO; | |||||
import org.eclipse.jgit.util.RawParseUtils; | |||||
/** | |||||
* A class used to execute a {@code Rebase} 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()}) | |||||
* <p> | |||||
* | |||||
* @see <a | |||||
* href="http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html" | |||||
* >Git documentation about Rebase</a> | |||||
*/ | |||||
public class RebaseCommand extends GitCommand<RebaseResult> { | |||||
/** | |||||
* The available operations | |||||
*/ | |||||
public enum Operation { | |||||
/** | |||||
* Initiates rebase | |||||
*/ | |||||
BEGIN, | |||||
/** | |||||
* Continues after a conflict resolution | |||||
*/ | |||||
CONTINUE, | |||||
/** | |||||
* Skips the "current" commit | |||||
*/ | |||||
SKIP, | |||||
/** | |||||
* Aborts and resets the current rebase | |||||
*/ | |||||
ABORT; | |||||
} | |||||
private Operation operation = Operation.BEGIN; | |||||
private RevCommit upstreamCommit; | |||||
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; | |||||
private final RevWalk walk; | |||||
private final File rebaseDir; | |||||
/** | |||||
* @param repo | |||||
*/ | |||||
protected RebaseCommand(Repository repo) { | |||||
super(repo); | |||||
walk = new RevWalk(repo); | |||||
rebaseDir = new File(repo.getDirectory(), "rebase-merge"); | |||||
} | |||||
/** | |||||
* Executes the {@code Rebase} command with all the options and parameters | |||||
* collected by the setter methods of this class. 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 an object describing the result of this command | |||||
*/ | |||||
public RebaseResult call() throws NoHeadException, RefNotFoundException, | |||||
JGitInternalException, GitAPIException { | |||||
checkCallable(); | |||||
checkParameters(); | |||||
try { | |||||
switch (operation) { | |||||
case ABORT: | |||||
try { | |||||
return abort(); | |||||
} catch (IOException ioe) { | |||||
throw new JGitInternalException(ioe.getMessage(), ioe); | |||||
} | |||||
case SKIP: | |||||
// fall through | |||||
case CONTINUE: | |||||
String upstreamCommitName = readFile(rebaseDir, "onto"); | |||||
this.upstreamCommit = walk.parseCommit(repo | |||||
.resolve(upstreamCommitName)); | |||||
break; | |||||
case BEGIN: | |||||
RebaseResult res = initFilesAndRewind(); | |||||
if (res != null) | |||||
return res; | |||||
} | |||||
if (monitor.isCancelled()) | |||||
return abort(); | |||||
if (this.operation == Operation.CONTINUE) | |||||
throw new UnsupportedOperationException( | |||||
"--continue Not yet implemented"); | |||||
if (this.operation == Operation.SKIP) | |||||
throw new UnsupportedOperationException( | |||||
"--skip Not yet implemented"); | |||||
RevCommit newHead = null; | |||||
List<Step> steps = loadSteps(); | |||||
ObjectReader or = repo.newObjectReader(); | |||||
int stepsToPop = 0; | |||||
for (Step step : steps) { | |||||
if (step.action != Action.PICK) | |||||
continue; | |||||
Collection<ObjectId> ids = or.resolve(step.commit); | |||||
if (ids.size() != 1) | |||||
throw new JGitInternalException( | |||||
"Could not resolve uniquely the abbreviated object ID"); | |||||
RevCommit commitToPick = walk | |||||
.parseCommit(ids.iterator().next()); | |||||
if (monitor.isCancelled()) | |||||
return new RebaseResult(commitToPick); | |||||
monitor.beginTask(MessageFormat.format( | |||||
JGitText.get().applyingCommit, commitToPick | |||||
.getShortMessage()), ProgressMonitor.UNKNOWN); | |||||
// TODO if the first parent of commitToPick is the current HEAD, | |||||
// we should fast-forward instead of cherry-pick to avoid | |||||
// unnecessary object rewriting | |||||
newHead = new Git(repo).cherryPick().include(commitToPick) | |||||
.call(); | |||||
monitor.endTask(); | |||||
if (newHead == null) { | |||||
popSteps(stepsToPop); | |||||
return new RebaseResult(commitToPick); | |||||
} | |||||
stepsToPop++; | |||||
} | |||||
if (newHead != null) { | |||||
// point the previous head (if any) to the new commit | |||||
String headName = readFile(rebaseDir, "head-name"); | |||||
if (headName.startsWith(Constants.R_REFS)) { | |||||
RefUpdate rup = repo.updateRef(headName); | |||||
rup.setNewObjectId(newHead); | |||||
rup.forceUpdate(); | |||||
rup = repo.updateRef(Constants.HEAD); | |||||
rup.link(headName); | |||||
} | |||||
deleteRecursive(rebaseDir); | |||||
return new RebaseResult(Status.OK); | |||||
} | |||||
return new RebaseResult(Status.UP_TO_DATE); | |||||
} catch (IOException ioe) { | |||||
throw new JGitInternalException(ioe.getMessage(), ioe); | |||||
} | |||||
} | |||||
/** | |||||
* Removes the number of lines given in the parameter from the | |||||
* <code>git-rebase-todo</code> file but preserves comments and other lines | |||||
* that can not be parsed as steps | |||||
* | |||||
* @param numSteps | |||||
* @throws IOException | |||||
*/ | |||||
private void popSteps(int numSteps) throws IOException { | |||||
if (numSteps == 0) | |||||
return; | |||||
List<String> lines = new ArrayList<String>(); | |||||
File file = new File(rebaseDir, "git-rebase-todo"); | |||||
BufferedReader br = new BufferedReader(new InputStreamReader( | |||||
new FileInputStream(file), "UTF-8")); | |||||
int popped = 0; | |||||
try { | |||||
// check if the line starts with a action tag (pick, skip...) | |||||
while (popped < numSteps) { | |||||
String popCandidate = br.readLine(); | |||||
if (popCandidate == null) | |||||
break; | |||||
int spaceIndex = popCandidate.indexOf(' '); | |||||
boolean pop = false; | |||||
if (spaceIndex >= 0) { | |||||
String actionToken = popCandidate.substring(0, spaceIndex); | |||||
pop = Action.parse(actionToken) != null; | |||||
} | |||||
if (pop) | |||||
popped++; | |||||
else | |||||
lines.add(popCandidate); | |||||
} | |||||
String readLine = br.readLine(); | |||||
while (readLine != null) { | |||||
lines.add(readLine); | |||||
readLine = br.readLine(); | |||||
} | |||||
} finally { | |||||
br.close(); | |||||
} | |||||
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( | |||||
new FileOutputStream(file), "UTF-8")); | |||||
try { | |||||
for (String writeLine : lines) { | |||||
bw.write(writeLine); | |||||
bw.newLine(); | |||||
} | |||||
} finally { | |||||
bw.close(); | |||||
} | |||||
} | |||||
private RebaseResult initFilesAndRewind() throws RefNotFoundException, | |||||
IOException, NoHeadException, JGitInternalException { | |||||
// we need to store everything into files so that we can implement | |||||
// --skip, --continue, and --abort | |||||
// first of all, we determine the commits to be applied | |||||
List<RevCommit> cherryPickList = new ArrayList<RevCommit>(); | |||||
Ref head = repo.getRef(Constants.HEAD); | |||||
if (head == null || head.getObjectId() == null) | |||||
throw new RefNotFoundException(MessageFormat.format( | |||||
JGitText.get().refNotResolved, Constants.HEAD)); | |||||
String headName; | |||||
if (head.isSymbolic()) | |||||
headName = head.getTarget().getName(); | |||||
else | |||||
headName = "detached HEAD"; | |||||
ObjectId headId = head.getObjectId(); | |||||
if (headId == null) | |||||
throw new RefNotFoundException(MessageFormat.format( | |||||
JGitText.get().refNotResolved, Constants.HEAD)); | |||||
RevCommit headCommit = walk.lookupCommit(headId); | |||||
monitor.beginTask(JGitText.get().obtainingCommitsForCherryPick, | |||||
ProgressMonitor.UNKNOWN); | |||||
LogCommand cmd = new Git(repo).log().addRange(upstreamCommit, | |||||
headCommit); | |||||
Iterable<RevCommit> commitsToUse = cmd.call(); | |||||
for (RevCommit commit : commitsToUse) { | |||||
cherryPickList.add(commit); | |||||
} | |||||
// nothing to do: return with UP_TO_DATE_RESULT | |||||
if (cherryPickList.isEmpty()) | |||||
return RebaseResult.UP_TO_DATE_RESULT; | |||||
Collections.reverse(cherryPickList); | |||||
// create the folder for the meta information | |||||
rebaseDir.mkdir(); | |||||
createFile(repo.getDirectory(), "ORIG_HEAD", headId.name()); | |||||
createFile(rebaseDir, "head", headId.name()); | |||||
createFile(rebaseDir, "head-name", headName); | |||||
createFile(rebaseDir, "onto", upstreamCommit.name()); | |||||
BufferedWriter fw = new BufferedWriter(new OutputStreamWriter( | |||||
new FileOutputStream(new File(rebaseDir, "git-rebase-todo")), | |||||
"UTF-8")); | |||||
fw.write("# Created by EGit: rebasing " + upstreamCommit.name() | |||||
+ " onto " + headId.name()); | |||||
fw.newLine(); | |||||
try { | |||||
StringBuilder sb = new StringBuilder(); | |||||
ObjectReader reader = walk.getObjectReader(); | |||||
for (RevCommit commit : cherryPickList) { | |||||
sb.setLength(0); | |||||
sb.append(Action.PICK.toToken()); | |||||
sb.append(" "); | |||||
sb.append(reader.abbreviate(commit).name()); | |||||
sb.append(" "); | |||||
sb.append(commit.getShortMessage()); | |||||
fw.write(sb.toString()); | |||||
fw.newLine(); | |||||
} | |||||
} finally { | |||||
fw.close(); | |||||
} | |||||
monitor.endTask(); | |||||
// we rewind to the upstream commit | |||||
monitor.beginTask(MessageFormat.format(JGitText.get().rewinding, | |||||
upstreamCommit.getShortMessage()), ProgressMonitor.UNKNOWN); | |||||
checkoutCommit(upstreamCommit); | |||||
monitor.endTask(); | |||||
return null; | |||||
} | |||||
private void checkParameters() throws WrongRepositoryStateException { | |||||
if (this.operation != Operation.BEGIN) { | |||||
// these operations are only possible while in a rebasing state | |||||
switch (repo.getRepositoryState()) { | |||||
case REBASING: | |||||
// fall through | |||||
case REBASING_INTERACTIVE: | |||||
// fall through | |||||
case REBASING_MERGE: | |||||
// fall through | |||||
case REBASING_REBASING: | |||||
break; | |||||
default: | |||||
throw new WrongRepositoryStateException(MessageFormat.format( | |||||
JGitText.get().wrongRepositoryState, repo | |||||
.getRepositoryState().name())); | |||||
} | |||||
} else | |||||
switch (repo.getRepositoryState()) { | |||||
case SAFE: | |||||
if (this.upstreamCommit == null) | |||||
throw new JGitInternalException(MessageFormat | |||||
.format(JGitText.get().missingRequiredParameter, | |||||
"upstream")); | |||||
return; | |||||
default: | |||||
throw new WrongRepositoryStateException(MessageFormat.format( | |||||
JGitText.get().wrongRepositoryState, repo | |||||
.getRepositoryState().name())); | |||||
} | |||||
} | |||||
private void createFile(File parentDir, String name, String content) | |||||
throws IOException { | |||||
File file = new File(parentDir, name); | |||||
FileOutputStream fos = new FileOutputStream(file); | |||||
try { | |||||
fos.write(content.getBytes("UTF-8")); | |||||
} finally { | |||||
fos.close(); | |||||
} | |||||
} | |||||
private RebaseResult abort() throws IOException { | |||||
try { | |||||
String commitId = readFile(repo.getDirectory(), "ORIG_HEAD"); | |||||
monitor.beginTask(MessageFormat.format( | |||||
JGitText.get().abortingRebase, commitId), | |||||
ProgressMonitor.UNKNOWN); | |||||
RevCommit commit = walk.parseCommit(repo.resolve(commitId)); | |||||
// no head in order to reset --hard | |||||
DirCacheCheckout dco = new DirCacheCheckout(repo, repo | |||||
.lockDirCache(), commit.getTree()); | |||||
dco.setFailOnConflict(false); | |||||
dco.checkout(); | |||||
walk.release(); | |||||
} finally { | |||||
monitor.endTask(); | |||||
} | |||||
try { | |||||
String headName = readFile(rebaseDir, "head-name"); | |||||
if (headName.startsWith(Constants.R_REFS)) { | |||||
monitor.beginTask(MessageFormat.format( | |||||
JGitText.get().resettingHead, headName), | |||||
ProgressMonitor.UNKNOWN); | |||||
// update the HEAD | |||||
RefUpdate refUpdate = repo.updateRef(Constants.HEAD, false); | |||||
Result res = refUpdate.link(headName); | |||||
switch (res) { | |||||
case FAST_FORWARD: | |||||
case FORCED: | |||||
case NO_CHANGE: | |||||
break; | |||||
default: | |||||
throw new IOException("Could not abort rebase"); | |||||
} | |||||
} | |||||
// cleanup the files | |||||
deleteRecursive(rebaseDir); | |||||
return new RebaseResult(Status.ABORTED); | |||||
} finally { | |||||
monitor.endTask(); | |||||
} | |||||
} | |||||
private void deleteRecursive(File fileOrFolder) throws IOException { | |||||
File[] children = fileOrFolder.listFiles(); | |||||
if (children != null) { | |||||
for (File child : children) | |||||
deleteRecursive(child); | |||||
} | |||||
if (!fileOrFolder.delete()) | |||||
throw new IOException("Could not delete " + fileOrFolder.getPath()); | |||||
} | |||||
private String readFile(File directory, String fileName) throws IOException { | |||||
return RawParseUtils | |||||
.decode(IO.readFully(new File(directory, fileName))); | |||||
} | |||||
private void checkoutCommit(RevCommit commit) throws IOException { | |||||
try { | |||||
RevCommit head = walk.parseCommit(repo.resolve(Constants.HEAD)); | |||||
DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(), | |||||
repo.lockDirCache(), commit.getTree()); | |||||
dco.setFailOnConflict(true); | |||||
dco.checkout(); | |||||
// update the HEAD | |||||
RefUpdate refUpdate = repo.updateRef(Constants.HEAD, true); | |||||
refUpdate.setExpectedOldObjectId(head); | |||||
refUpdate.setNewObjectId(commit); | |||||
Result res = refUpdate.forceUpdate(); | |||||
switch (res) { | |||||
case FAST_FORWARD: | |||||
case NO_CHANGE: | |||||
case FORCED: | |||||
break; | |||||
default: | |||||
throw new IOException("Could not rewind to upstream commit"); | |||||
} | |||||
} finally { | |||||
walk.release(); | |||||
monitor.endTask(); | |||||
} | |||||
} | |||||
private List<Step> loadSteps() throws IOException { | |||||
byte[] buf = IO.readFully(new File(rebaseDir, "git-rebase-todo")); | |||||
int ptr = 0; | |||||
int tokenBegin = 0; | |||||
ArrayList<Step> r = new ArrayList<Step>(); | |||||
while (ptr < buf.length) { | |||||
tokenBegin = ptr; | |||||
ptr = RawParseUtils.nextLF(buf, ptr); | |||||
int nextSpace = 0; | |||||
int tokenCount = 0; | |||||
Step current = null; | |||||
while (tokenCount < 3 && nextSpace < ptr) { | |||||
switch (tokenCount) { | |||||
case 0: | |||||
nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); | |||||
String actionToken = new String(buf, tokenBegin, nextSpace | |||||
- tokenBegin - 1); | |||||
tokenBegin = nextSpace; | |||||
Action action = Action.parse(actionToken); | |||||
if (action != null) | |||||
current = new Step(Action.parse(actionToken)); | |||||
break; | |||||
case 1: | |||||
if (current == null) | |||||
break; | |||||
nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); | |||||
String commitToken = new String(buf, tokenBegin, nextSpace | |||||
- tokenBegin - 1); | |||||
tokenBegin = nextSpace; | |||||
current.commit = AbbreviatedObjectId | |||||
.fromString(commitToken); | |||||
break; | |||||
case 2: | |||||
if (current == null) | |||||
break; | |||||
nextSpace = ptr; | |||||
int length = ptr - tokenBegin; | |||||
current.shortMessage = new byte[length]; | |||||
System.arraycopy(buf, tokenBegin, current.shortMessage, 0, | |||||
length); | |||||
r.add(current); | |||||
break; | |||||
} | |||||
tokenCount++; | |||||
} | |||||
} | |||||
return r; | |||||
} | |||||
/** | |||||
* @param upstream | |||||
* the upstream commit | |||||
* @return {@code this} | |||||
*/ | |||||
public RebaseCommand setUpstream(RevCommit upstream) { | |||||
this.upstreamCommit = upstream; | |||||
return this; | |||||
} | |||||
/** | |||||
* @param upstream | |||||
* the upstream branch | |||||
* @return {@code this} | |||||
* @throws RefNotFoundException | |||||
*/ | |||||
public RebaseCommand setUpstream(String upstream) | |||||
throws RefNotFoundException { | |||||
try { | |||||
ObjectId upstreamId = repo.resolve(upstream); | |||||
if (upstreamId == null) | |||||
throw new RefNotFoundException(MessageFormat.format(JGitText | |||||
.get().refNotResolved, upstream)); | |||||
upstreamCommit = walk.parseCommit(repo.resolve(upstream)); | |||||
return this; | |||||
} catch (IOException ioe) { | |||||
throw new JGitInternalException(ioe.getMessage(), ioe); | |||||
} | |||||
} | |||||
/** | |||||
* @param operation | |||||
* the operation to perform | |||||
* @return {@code this} | |||||
*/ | |||||
public RebaseCommand setOperation(Operation operation) { | |||||
this.operation = operation; | |||||
return this; | |||||
} | |||||
/** | |||||
* @param monitor | |||||
* a progress monitor | |||||
* @return this instance | |||||
*/ | |||||
public RebaseCommand setProgressMonitor(ProgressMonitor monitor) { | |||||
this.monitor = monitor; | |||||
return this; | |||||
} | |||||
static enum Action { | |||||
PICK("pick"); // later add SQUASH, EDIT, etc. | |||||
private final String token; | |||||
private Action(String token) { | |||||
this.token = token; | |||||
} | |||||
public String toToken() { | |||||
return this.token; | |||||
} | |||||
static Action parse(String token) { | |||||
if (token.equals("pick") || token.equals("p")) | |||||
return PICK; | |||||
return null; | |||||
} | |||||
} | |||||
static class Step { | |||||
Action action; | |||||
AbbreviatedObjectId commit; | |||||
byte[] shortMessage; | |||||
Step(Action action) { | |||||
this.action = action; | |||||
} | |||||
} | |||||
} |
/* | |||||
* Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.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 org.eclipse.jgit.revwalk.RevCommit; | |||||
/** | |||||
* The result of a {@link RebaseCommand} execution | |||||
*/ | |||||
public class RebaseResult { | |||||
/** | |||||
* The overall status | |||||
*/ | |||||
public enum Status { | |||||
/** | |||||
* Rebase was successful, HEAD points to the new commit | |||||
*/ | |||||
OK, | |||||
/** | |||||
* Aborted; the original HEAD was restored | |||||
*/ | |||||
ABORTED, | |||||
/** | |||||
* Stopped due to a conflict; must either abort or resolve or skip | |||||
*/ | |||||
STOPPED, | |||||
/** | |||||
* Already up-to-date | |||||
*/ | |||||
UP_TO_DATE; | |||||
} | |||||
static final RebaseResult UP_TO_DATE_RESULT = new RebaseResult( | |||||
Status.UP_TO_DATE); | |||||
private final Status mySatus; | |||||
private final RevCommit currentCommit; | |||||
RebaseResult(Status status) { | |||||
this.mySatus = status; | |||||
currentCommit = null; | |||||
} | |||||
RebaseResult(RevCommit commit) { | |||||
this.mySatus = Status.STOPPED; | |||||
currentCommit = commit; | |||||
} | |||||
/** | |||||
* @return the overall status | |||||
*/ | |||||
public Status getStatus() { | |||||
return mySatus; | |||||
} | |||||
/** | |||||
* @return the current commit if status is {@link Status#STOPPED}, otherwise | |||||
* <code>null</code> | |||||
*/ | |||||
public RevCommit getCurrentCommit() { | |||||
return currentCommit; | |||||
} | |||||
} |