Merges the current head with one other commit. In this first iteration the merge command supports only fast forward and already up-to-date. Change-Id: I0db480f061e01b343570cf7da02cac13a0cbdf8f Signed-off-by: Stefan Lay <stefan.lay@sap.com> Signed-off-by: Christian Halstrick <christian.halstrick@sap.com> Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>tags/v0.8.1
@@ -0,0 +1,183 @@ | |||
/* | |||
* Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com> | |||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@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.File; | |||
import java.io.IOException; | |||
import org.eclipse.jgit.errors.CheckoutConflictException; | |||
import org.eclipse.jgit.errors.CorruptObjectException; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.GitIndex; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.RefUpdate; | |||
import org.eclipse.jgit.lib.RepositoryTestCase; | |||
import org.eclipse.jgit.lib.WorkDirCheckout; | |||
import org.eclipse.jgit.lib.GitIndex.Entry; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
public class MergeCommandTest extends RepositoryTestCase { | |||
public void testMergeInItself() throws Exception { | |||
Git git = new Git(db); | |||
git.commit().setMessage("initial commit").call(); | |||
MergeResult result = git.merge().include(db.getRef(Constants.HEAD)).call(); | |||
assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus()); | |||
} | |||
public void testAlreadyUpToDate() throws Exception { | |||
Git git = new Git(db); | |||
RevCommit first = git.commit().setMessage("initial commit").call(); | |||
createBranch(first, "refs/heads/branch1"); | |||
RevCommit second = git.commit().setMessage("second commit").call(); | |||
MergeResult result = git.merge().include(db.getRef("refs/heads/branch1")).call(); | |||
assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus()); | |||
assertEquals(second, result.getNewHead()); | |||
} | |||
public void testFastForward() throws Exception { | |||
Git git = new Git(db); | |||
RevCommit first = git.commit().setMessage("initial commit").call(); | |||
createBranch(first, "refs/heads/branch1"); | |||
RevCommit second = git.commit().setMessage("second commit").call(); | |||
checkoutBranch("refs/heads/branch1"); | |||
MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call(); | |||
assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus()); | |||
assertEquals(second, result.getNewHead()); | |||
} | |||
public void testFastForwardWithFiles() throws Exception { | |||
Git git = new Git(db); | |||
addNewFileToIndex("file1"); | |||
RevCommit first = git.commit().setMessage("initial commit").call(); | |||
assertTrue(new File(db.getWorkDir(), "file1").exists()); | |||
createBranch(first, "refs/heads/branch1"); | |||
addNewFileToIndex("file2"); | |||
RevCommit second = git.commit().setMessage("second commit").call(); | |||
assertTrue(new File(db.getWorkDir(), "file2").exists()); | |||
checkoutBranch("refs/heads/branch1"); | |||
assertFalse(new File(db.getWorkDir(), "file2").exists()); | |||
MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call(); | |||
assertTrue(new File(db.getWorkDir(), "file1").exists()); | |||
assertTrue(new File(db.getWorkDir(), "file2").exists()); | |||
assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus()); | |||
assertEquals(second, result.getNewHead()); | |||
} | |||
public void testMultipleHeads() throws Exception { | |||
Git git = new Git(db); | |||
addNewFileToIndex("file1"); | |||
RevCommit first = git.commit().setMessage("initial commit").call(); | |||
createBranch(first, "refs/heads/branch1"); | |||
addNewFileToIndex("file2"); | |||
RevCommit second = git.commit().setMessage("second commit").call(); | |||
addNewFileToIndex("file3"); | |||
git.commit().setMessage("third commit").call(); | |||
checkoutBranch("refs/heads/branch1"); | |||
assertFalse(new File(db.getWorkDir(), "file2").exists()); | |||
assertFalse(new File(db.getWorkDir(), "file3").exists()); | |||
MergeCommand merge = git.merge(); | |||
merge.include(second.getId()); | |||
merge.include(db.getRef(Constants.MASTER)); | |||
try { | |||
merge.call(); | |||
fail("Expected exception not thrown when merging multiple heads"); | |||
} catch (InvalidMergeHeadsException e) { | |||
} | |||
} | |||
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 Exception { | |||
File workDir = db.getWorkDir(); | |||
if (workDir != null) { | |||
WorkDirCheckout workDirCheckout = new WorkDirCheckout(db, | |||
workDir, db.mapCommit(Constants.HEAD).getTree(), | |||
db.getIndex(), db.mapCommit(branchName).getTree()); | |||
workDirCheckout.setFailOnConflict(true); | |||
try { | |||
workDirCheckout.checkout(); | |||
} catch (CheckoutConflictException e) { | |||
throw new JGitInternalException( | |||
"Couldn't check out because of conflicts", e); | |||
} | |||
} | |||
// update the HEAD | |||
RefUpdate refUpdate = db.updateRef(Constants.HEAD); | |||
refUpdate.link(branchName); | |||
} | |||
private void addNewFileToIndex(String filename) throws IOException, | |||
CorruptObjectException { | |||
File writeTrashFile = writeTrashFile(filename, filename); | |||
GitIndex index = db.getIndex(); | |||
Entry entry = index.add(db.getWorkDir(), writeTrashFile); | |||
entry.update(writeTrashFile); | |||
index.write(); | |||
} | |||
} |
@@ -100,6 +100,7 @@ corruptObjectNoType=no type | |||
corruptObjectNotree=no tree | |||
corruptObjectPackfileChecksumIncorrect=Packfile checksum incorrect. | |||
corruptionDetectedReReadingAt=Corruption detected re-reading at {0} | |||
couldNotCheckOutBecauseOfConflicts=Could not check out because of conflicts | |||
couldNotDeleteLockFileShouldNotHappen=Could not delete lock file. Should not happen | |||
couldNotDeleteTemporaryIndexFileShouldNotHappen=Could not delete temporary index file. Should not happen | |||
couldNotLockHEAD=Could not lock HEAD | |||
@@ -137,6 +138,7 @@ errorListing=Error listing {0} | |||
errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on the remote end: {0} | |||
errorReadingInfoRefs=error reading info/refs | |||
exceptionCaughtDuringExecutionOfCommitCommand=Exception caught during execution of commit command | |||
exceptionCaughtDuringExecutionOfMergeCommand=Exception caught during execution of merge command. {0} | |||
exceptionOccuredDuringAddingOfOptionToALogCommand=Exception occured during adding of {0} as option to a Log command | |||
exceptionOccuredDuringReadingOfGIT_DIR=Exception occured during reading of $GIT_DIR/{0}. {1} | |||
expectedACKNAKFoundEOF=Expected ACK/NAK, found EOF | |||
@@ -215,6 +217,8 @@ lockOnNotClosed=Lock on {0} not closed. | |||
lockOnNotHeld=Lock on {0} not held. | |||
malformedpersonIdentString=Malformed PersonIdent string (no < was found): {0} | |||
mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy | |||
mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD | |||
mergeUsingStrategyResultedInDescription=Merge using strategy {0} resulted in: {1}. {2} | |||
missingAccesskey=Missing accesskey. | |||
missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch | |||
missingObject=Missing {0} {1} | |||
@@ -231,6 +235,7 @@ noApplyInDelete=No apply in delete | |||
noClosingBracket=No closing {0} found for {1} at index {2}. | |||
noHEADExistsAndNoExplicitStartingRevisionWasSpecified=No HEAD exists and no explicit starting revision was specified | |||
noHMACsupport=No {0} support: {1} | |||
noMergeHeadSpecified=No merge head specified | |||
noSuchRef=no such ref | |||
noXMLParserAvailable=No XML parser available. | |||
notABoolean=Not a boolean: {0} | |||
@@ -251,6 +256,7 @@ objectIsCorrupt=Object {0} is corrupt: {1} | |||
objectIsNotA=Object {0} is not a {1}. | |||
objectNotFoundIn=Object {0} not found in {1}. | |||
offsetWrittenDeltaBaseForObjectNotFoundInAPack=Offset-written delta base for object not found in a pack | |||
onlyAlreadyUpToDateAndFastForwardMergesAreAvailable=only already-up-to-date and fast forward merges are available | |||
onlyOneFetchSupported=Only one fetch supported | |||
onlyOneOperationCallPerConnectionIsSupported=Only one operation call per connection is supported. | |||
openFilesMustBeAtLeast1=Open files must be >= 1 |
@@ -160,6 +160,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String corruptObjectNotree; | |||
/***/ public String corruptObjectPackfileChecksumIncorrect; | |||
/***/ public String corruptionDetectedReReadingAt; | |||
/***/ public String couldNotCheckOutBecauseOfConflicts; | |||
/***/ public String couldNotDeleteLockFileShouldNotHappen; | |||
/***/ public String couldNotDeleteTemporaryIndexFileShouldNotHappen; | |||
/***/ public String couldNotLockHEAD; | |||
@@ -197,6 +198,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String errorOccurredDuringUnpackingOnTheRemoteEnd; | |||
/***/ public String errorReadingInfoRefs; | |||
/***/ public String exceptionCaughtDuringExecutionOfCommitCommand; | |||
/***/ public String exceptionCaughtDuringExecutionOfMergeCommand; | |||
/***/ public String exceptionOccuredDuringAddingOfOptionToALogCommand; | |||
/***/ public String exceptionOccuredDuringReadingOfGIT_DIR; | |||
/***/ public String expectedACKNAKFoundEOF; | |||
@@ -275,6 +277,8 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String lockOnNotHeld; | |||
/***/ public String malformedpersonIdentString; | |||
/***/ public String mergeStrategyAlreadyExistsAsDefault; | |||
/***/ public String mergeStrategyDoesNotSupportHeads; | |||
/***/ public String mergeUsingStrategyResultedInDescription; | |||
/***/ public String missingAccesskey; | |||
/***/ public String missingForwardImageInGITBinaryPatch; | |||
/***/ public String missingObject; | |||
@@ -291,6 +295,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String noClosingBracket; | |||
/***/ public String noHEADExistsAndNoExplicitStartingRevisionWasSpecified; | |||
/***/ public String noHMACsupport; | |||
/***/ public String noMergeHeadSpecified; | |||
/***/ public String noSuchRef; | |||
/***/ public String noXMLParserAvailable; | |||
/***/ public String notABoolean; | |||
@@ -311,6 +316,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String objectIsNotA; | |||
/***/ public String objectNotFoundIn; | |||
/***/ public String offsetWrittenDeltaBaseForObjectNotFoundInAPack; | |||
/***/ public String onlyAlreadyUpToDateAndFastForwardMergesAreAvailable; | |||
/***/ public String onlyOneFetchSupported; | |||
/***/ public String onlyOneOperationCallPerConnectionIsSupported; | |||
/***/ public String openFilesMustBeAtLeast1; |
@@ -0,0 +1,85 @@ | |||
/* | |||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@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.util.LinkedList; | |||
import java.util.List; | |||
/** | |||
* Exception thrown when a command can't succeed because of unresolved | |||
* conflicts. | |||
*/ | |||
public class CheckoutConflictException extends GitAPIException { | |||
private static final long serialVersionUID = 1L; | |||
private List<String> conflictingPaths; | |||
CheckoutConflictException(String message, Throwable cause) { | |||
super(message, cause); | |||
} | |||
CheckoutConflictException(String message, List<String> conflictingPaths, Throwable cause) { | |||
super(message, cause); | |||
this.conflictingPaths = conflictingPaths; | |||
} | |||
CheckoutConflictException(String message) { | |||
super(message); | |||
} | |||
CheckoutConflictException(String message, List<String> conflictingPaths) { | |||
super(message); | |||
this.conflictingPaths = conflictingPaths; | |||
} | |||
/** @return all the paths where unresolved conflicts have been detected */ | |||
public List<String> getConflictingPaths() { | |||
return conflictingPaths; | |||
} | |||
/** | |||
* Adds a new conflicting path | |||
* @param conflictingPath | |||
* @return {@code this} | |||
*/ | |||
CheckoutConflictException addConflictingPath(String conflictingPath) { | |||
if (conflictingPaths == null) | |||
conflictingPaths = new LinkedList<String>(); | |||
conflictingPaths.add(conflictingPath); | |||
return this; | |||
} | |||
} |
@@ -119,6 +119,19 @@ public class Git { | |||
return new LogCommand(repo); | |||
} | |||
/** | |||
* Returns a command class to execute a {@code Merge} command | |||
* | |||
* @see <a | |||
* href="http://www.kernel.org/pub/software/scm/git/docs/git-merge.html" | |||
* >Git documentation about Merge</a> | |||
* @return a {@link MergeCommand} used to collect all optional parameters | |||
* and to finally execute the {@code Merge} command | |||
*/ | |||
public MergeCommand merge() { | |||
return new MergeCommand(repo); | |||
} | |||
/** | |||
* @return the git repository this class is interacting with | |||
*/ |
@@ -0,0 +1,53 @@ | |||
/* | |||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@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; | |||
/** | |||
* Exception thrown when a merge command was called without specifying the | |||
* proper amount/type of merge heads. E.g. a non-octopus merge strategy was | |||
* confronted with more than one head to be merged into HEAD. Another | |||
* case would be if a merge was called without including any head. | |||
*/ | |||
public class InvalidMergeHeadsException extends GitAPIException { | |||
private static final long serialVersionUID = 1L; | |||
InvalidMergeHeadsException(String msg) { | |||
super(msg); | |||
} | |||
} |
@@ -0,0 +1,249 @@ | |||
/* | |||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> | |||
* Copyright (C) 2010, Stefan Lay <stefan.lay@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.File; | |||
import java.io.IOException; | |||
import java.text.MessageFormat; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import org.eclipse.jgit.JGitText; | |||
import org.eclipse.jgit.api.MergeResult.MergeStatus; | |||
import org.eclipse.jgit.lib.AnyObjectId; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.GitIndex; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.ObjectIdRef; | |||
import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.Ref.Storage; | |||
import org.eclipse.jgit.lib.RefUpdate; | |||
import org.eclipse.jgit.lib.RefUpdate.Result; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.WorkDirCheckout; | |||
import org.eclipse.jgit.merge.MergeStrategy; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.revwalk.RevWalk; | |||
/** | |||
* A class used to execute a {@code Merge} 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> | |||
* This is currently a very basic implementation which takes only one commits to | |||
* merge with as option. Furthermore it does supports only fast forward. | |||
* | |||
* @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-merge.html" | |||
* >Git documentation about Merge</a> | |||
*/ | |||
public class MergeCommand extends GitCommand<MergeResult> { | |||
private MergeStrategy mergeStrategy = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE; | |||
private List<Ref> commits = new LinkedList<Ref>(); | |||
/** | |||
* @param repo | |||
*/ | |||
protected MergeCommand(Repository repo) { | |||
super(repo); | |||
} | |||
/** | |||
* Executes the {@code Merge} command with all the options and parameters | |||
* collected by the setter methods (e.g. {@link #include(Ref)}) 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 the result of the merge | |||
*/ | |||
public MergeResult call() throws NoHeadException, | |||
ConcurrentRefUpdateException, CheckoutConflictException, | |||
InvalidMergeHeadsException { | |||
checkCallable(); | |||
if (commits.size() != 1) | |||
throw new InvalidMergeHeadsException( | |||
commits.isEmpty() ? JGitText.get().noMergeHeadSpecified | |||
: MessageFormat.format( | |||
JGitText.get().mergeStrategyDoesNotSupportHeads, | |||
mergeStrategy.getName(), commits.size())); | |||
try { | |||
Ref head = repo.getRef(Constants.HEAD); | |||
if (head == null) | |||
throw new NoHeadException(JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); | |||
StringBuilder refLogMessage = new StringBuilder("merge "); | |||
// Check for FAST_FORWARD, ALREADY_UP_TO_DATE | |||
RevWalk revWalk = new RevWalk(repo); | |||
RevCommit headCommit = revWalk.lookupCommit(head.getObjectId()); | |||
Ref ref = commits.get(0); | |||
refLogMessage.append(ref.getName()); | |||
// handle annotated tags | |||
ObjectId objectId = ref.getPeeledObjectId(); | |||
if (objectId == null) | |||
objectId = ref.getObjectId(); | |||
RevCommit srcCommit = revWalk.lookupCommit(objectId); | |||
if (revWalk.isMergedInto(srcCommit, headCommit)) { | |||
setCallable(false); | |||
return new MergeResult(headCommit, | |||
MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy); | |||
} else if (revWalk.isMergedInto(headCommit, srcCommit)) { | |||
// FAST_FORWARD detected: skip doing a real merge but only | |||
// update HEAD | |||
refLogMessage.append(": " + MergeStatus.FAST_FORWARD); | |||
checkoutNewHead(revWalk, headCommit, srcCommit); | |||
updateHead(refLogMessage, srcCommit, head.getObjectId()); | |||
setCallable(false); | |||
return new MergeResult(srcCommit, MergeStatus.FAST_FORWARD, | |||
mergeStrategy); | |||
} else { | |||
return new MergeResult( | |||
headCommit, | |||
MergeResult.MergeStatus.NOT_SUPPORTED, | |||
mergeStrategy, | |||
JGitText.get().onlyAlreadyUpToDateAndFastForwardMergesAreAvailable); | |||
} | |||
} catch (IOException e) { | |||
throw new JGitInternalException( | |||
MessageFormat.format( | |||
JGitText.get().exceptionCaughtDuringExecutionOfMergeCommand, | |||
e)); | |||
} | |||
} | |||
private void checkoutNewHead(RevWalk revWalk, RevCommit headCommit, | |||
RevCommit newHeadCommit) throws IOException, CheckoutConflictException { | |||
GitIndex index = repo.getIndex(); | |||
File workDir = repo.getWorkDir(); | |||
if (workDir != null) { | |||
WorkDirCheckout workDirCheckout = new WorkDirCheckout(repo, | |||
workDir, headCommit.asCommit(revWalk).getTree(), index, | |||
newHeadCommit.asCommit(revWalk).getTree()); | |||
workDirCheckout.setFailOnConflict(true); | |||
try { | |||
workDirCheckout.checkout(); | |||
} catch (org.eclipse.jgit.errors.CheckoutConflictException e) { | |||
throw new CheckoutConflictException( | |||
JGitText.get().couldNotCheckOutBecauseOfConflicts, | |||
workDirCheckout.getConflicts(), e); | |||
} | |||
index.write(); | |||
} | |||
} | |||
private void updateHead(StringBuilder refLogMessage, | |||
ObjectId newHeadId, ObjectId oldHeadID) throws IOException, | |||
ConcurrentRefUpdateException { | |||
RefUpdate refUpdate = repo.updateRef(Constants.HEAD); | |||
refUpdate.setNewObjectId(newHeadId); | |||
refUpdate.setRefLogMessage(refLogMessage.toString(), false); | |||
refUpdate.setExpectedOldObjectId(oldHeadID); | |||
Result rc = refUpdate.update(); | |||
switch (rc) { | |||
case NEW: | |||
case FAST_FORWARD: | |||
return; | |||
case REJECTED: | |||
case LOCK_FAILURE: | |||
throw new ConcurrentRefUpdateException( | |||
JGitText.get().couldNotLockHEAD, refUpdate.getRef(), rc); | |||
default: | |||
throw new JGitInternalException(MessageFormat.format( | |||
JGitText.get().updatingRefFailed, Constants.HEAD, | |||
newHeadId.toString(), rc)); | |||
} | |||
} | |||
/** | |||
* | |||
* @param mergeStrategy | |||
* the {@link MergeStrategy} to be used | |||
* @return {@code this} | |||
*/ | |||
public MergeCommand setStrategy(MergeStrategy mergeStrategy) { | |||
checkCallable(); | |||
this.mergeStrategy = mergeStrategy; | |||
return this; | |||
} | |||
/** | |||
* @param commit | |||
* a reference to a commit which is merged with the current | |||
* head | |||
* @return {@code this} | |||
*/ | |||
public MergeCommand include(Ref commit) { | |||
checkCallable(); | |||
commits.add(commit); | |||
return this; | |||
} | |||
/** | |||
* @param commit | |||
* the Id of a commit which is merged with the current head | |||
* @return {@code this} | |||
*/ | |||
public MergeCommand include(AnyObjectId commit) { | |||
return include(commit.getName(), commit); | |||
} | |||
/** | |||
* @param name a name given to the commit | |||
* @param commit | |||
* the Id of a commit which is merged with the current head | |||
* @return {@code this} | |||
*/ | |||
public MergeCommand include(String name, AnyObjectId commit) { | |||
return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name, | |||
commit.copy())); | |||
} | |||
} |
@@ -0,0 +1,150 @@ | |||
/* | |||
* Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com> | |||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@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.text.MessageFormat; | |||
import org.eclipse.jgit.JGitText; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.merge.MergeStrategy; | |||
/** | |||
* Encapsulates the result of a {@link MergeCommand}. | |||
*/ | |||
public class MergeResult { | |||
/** | |||
* The status the merge resulted in. | |||
*/ | |||
public enum MergeStatus { | |||
/** */ | |||
FAST_FORWARD { | |||
@Override | |||
public String toString() { | |||
return "Fast-forward"; | |||
} | |||
}, | |||
/** */ | |||
ALREADY_UP_TO_DATE { | |||
public String toString() { | |||
return "Already-up-to-date"; | |||
} | |||
}, | |||
/** */ | |||
FAILED { | |||
public String toString() { | |||
return "Failed"; | |||
} | |||
}, | |||
/** */ | |||
MERGED { | |||
public String toString() { | |||
return "Merged"; | |||
} | |||
}, | |||
/** */ | |||
NOT_SUPPORTED { | |||
public String toString() { | |||
return "Not-yet-supported"; | |||
} | |||
} | |||
} | |||
private ObjectId newHead; | |||
private MergeStatus mergeStatus; | |||
private String description; | |||
private MergeStrategy mergeStrategy; | |||
/** | |||
* @param newHead the object the head points at after the merge | |||
* @param mergeStatus the status the merge resulted in | |||
* @param mergeStrategy the used {@link MergeStrategy} | |||
*/ | |||
public MergeResult(ObjectId newHead, MergeStatus mergeStatus, | |||
MergeStrategy mergeStrategy) { | |||
this.newHead = newHead; | |||
this.mergeStatus = mergeStatus; | |||
this.mergeStrategy = mergeStrategy; | |||
} | |||
/** | |||
* @param newHead the object the head points at after the merge | |||
* @param mergeStatus the status the merge resulted in | |||
* @param mergeStrategy the used {@link MergeStrategy} | |||
* @param description a user friendly description of the merge result | |||
*/ | |||
public MergeResult(ObjectId newHead, MergeStatus mergeStatus, | |||
MergeStrategy mergeStrategy, String description) { | |||
this.newHead = newHead; | |||
this.mergeStatus = mergeStatus; | |||
this.mergeStrategy = mergeStrategy; | |||
this.description = description; | |||
} | |||
/** | |||
* @return the object the head points at after the merge | |||
*/ | |||
public ObjectId getNewHead() { | |||
return newHead; | |||
} | |||
/** | |||
* @return the status the merge resulted in | |||
*/ | |||
public MergeStatus getMergeStatus() { | |||
return mergeStatus; | |||
} | |||
@Override | |||
public String toString() { | |||
return MessageFormat.format( | |||
JGitText.get().mergeUsingStrategyResultedInDescription, | |||
mergeStrategy.getName(), mergeStatus, (description == null ? "" | |||
: ", " + description)); | |||
} | |||
} |