summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Lay <stefan.lay@sap.com>2010-05-20 15:09:39 +0200
committerChris Aniszczyk <caniszczyk@gmail.com>2010-05-24 09:52:28 -0500
commit5b0e73b849d19c9f072c4c6738a5d5adae413112 (patch)
treece7fdcf571a78a173539cc06660b99ca4c16d0c8
parent6ca9843f3ebbea152969a8b795efce1d4ff15dbf (diff)
downloadjgit-5b0e73b849d19c9f072c4c6738a5d5adae413112.tar.gz
jgit-5b0e73b849d19c9f072c4c6738a5d5adae413112.zip
Add a merge command to the jgit API
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>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java183
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutConflictException.java85
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/InvalidMergeHeadsException.java53
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java249
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java150
8 files changed, 745 insertions, 0 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
new file mode 100644
index 0000000000..c965c67664
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -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();
+ }
+}
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
index e9ed28a198..b769671c76 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
@@ -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
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
index a7c2e685c1..49056c7b8b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
@@ -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;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutConflictException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutConflictException.java
new file mode 100644
index 0000000000..09dda6f61d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutConflictException.java
@@ -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;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
index 30dcbd75b2..28946e5fe7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
@@ -120,6 +120,19 @@ public class Git {
}
/**
+ * 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
*/
public Repository getRepository() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InvalidMergeHeadsException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InvalidMergeHeadsException.java
new file mode 100644
index 0000000000..7748853910
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InvalidMergeHeadsException.java
@@ -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);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
new file mode 100644
index 0000000000..00a0309152
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -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()));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
new file mode 100644
index 0000000000..a293ad0c95
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
@@ -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));
+ }
+
+}