diff options
author | Christian Halstrick <christian.halstrick@sap.com> | 2010-05-19 10:01:25 +0200 |
---|---|---|
committer | Christian Halstrick <christian.halstrick@sap.com> | 2010-05-21 01:49:46 +0200 |
commit | 6ca9843f3ebbea152969a8b795efce1d4ff15dbf (patch) | |
tree | 516f608960debe427e540912941b300ace2b033a /org.eclipse.jgit | |
parent | 3c667b328ae086dcbfe159a22b3c86779a4590e5 (diff) | |
download | jgit-6ca9843f3ebbea152969a8b795efce1d4ff15dbf.tar.gz jgit-6ca9843f3ebbea152969a8b795efce1d4ff15dbf.zip |
Added merge support to CommitCommand
The CommitCommand should take care to create a merge commit if the file
$GIT_DIR/MERGE_HEAD exists. It should then read the parents for the merge
commit out of this file. It should also take care that when commiting
a merge and no commit message was specified to read the message from
$GIT_DIR/MERGE_MSG.
Finally the CommitCommand should remove these files if the commit
succeeded.
Change-Id: I4e292115085099d5b86546d2021680cb1454266c
Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
Diffstat (limited to 'org.eclipse.jgit')
6 files changed, 181 insertions, 12 deletions
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index 4cdf914163..e9ed28a198 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -27,6 +27,7 @@ blobNotFound=Blob not found: {0} blobNotFoundForPath=Blob not found: {0} for path: {1} cannotBeCombined=Cannot be combined. cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RefFilter {1}. +cannotCommitOnARepoWithState=Cannot commit on a repo with state: {0} cannotCommitWriteTo=Cannot commit write to {0} cannotConnectPipes=cannot connect pipes cannotConvertScriptToText=Cannot convert script to text @@ -137,6 +138,7 @@ errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on th errorReadingInfoRefs=error reading info/refs exceptionCaughtDuringExecutionOfCommitCommand=Exception caught during execution of commit command 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 expectedACKNAKGot=Expected ACK/NAK, got: {0} expectedBooleanStringValue=Expected boolean string value diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index fbc17aad8e..a7c2e685c1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -87,6 +87,7 @@ public class JGitText extends TranslationBundle { /***/ public String blobNotFoundForPath; /***/ public String cannotBeCombined; /***/ public String cannotCombineTreeFilterWithRevFilter; + /***/ public String cannotCommitOnARepoWithState; /***/ public String cannotCommitWriteTo; /***/ public String cannotConnectPipes; /***/ public String cannotConvertScriptToText; @@ -197,6 +198,7 @@ public class JGitText extends TranslationBundle { /***/ public String errorReadingInfoRefs; /***/ public String exceptionCaughtDuringExecutionOfCommitCommand; /***/ public String exceptionOccuredDuringAddingOfOptionToALogCommand; + /***/ public String exceptionOccuredDuringReadingOfGIT_DIR; /***/ public String expectedACKNAKFoundEOF; /***/ public String expectedACKNAKGot; /***/ public String expectedBooleanStringValue; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 542c821451..eef952e7cd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -42,8 +42,11 @@ */ 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.dircache.DirCache; @@ -57,6 +60,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; @@ -77,6 +81,12 @@ public class CommitCommand extends GitCommand<RevCommit> { private String message; /** + * parents this commit should have. The current HEAD will be in this list + * and also all commits mentioned in .git/MERGE_HEAD + */ + private List<ObjectId> parents = new LinkedList<ObjectId>(); + + /** * @param repo */ protected CommitCommand(Repository repo) { @@ -96,6 +106,8 @@ public class CommitCommand extends GitCommand<RevCommit> { * when called without specifying a commit message * @throws UnmergedPathException * when the current index contained unmerged pathes (conflicts) + * @throws WrongRepositoryStateException + * when repository is not in the right state for committing * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling @@ -106,9 +118,14 @@ public class CommitCommand extends GitCommand<RevCommit> { */ public RevCommit call() throws NoHeadException, NoMessageException, UnmergedPathException, ConcurrentRefUpdateException, - JGitInternalException { + JGitInternalException, WrongRepositoryStateException { checkCallable(); - processOptions(); + + RepositoryState state = repo.getRepositoryState(); + if (!state.canCommit()) + throw new WrongRepositoryStateException(MessageFormat.format( + JGitText.get().cannotCommitOnARepoWithState, state.name())); + processOptions(state); try { Ref head = repo.getRef(Constants.HEAD); @@ -117,7 +134,9 @@ public class CommitCommand extends GitCommand<RevCommit> { JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); // determine the current HEAD and the commit it is referring to - ObjectId parentID = repo.resolve(Constants.HEAD + "^{commit}"); + ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); + if (headId != null) + parents.add(0, headId); // lock the index DirCache index = DirCache.lock(repo); @@ -134,8 +153,8 @@ public class CommitCommand extends GitCommand<RevCommit> { commit.setCommitter(committer); commit.setAuthor(author); commit.setMessage(message); - if (parentID != null) - commit.setParentIds(new ObjectId[] { parentID }); + + commit.setParentIds(parents.toArray(new ObjectId[]{})); commit.setTreeId(indexTreeId); ObjectId commitId = repoWriter.writeCommit(commit); @@ -145,12 +164,20 @@ public class CommitCommand extends GitCommand<RevCommit> { ru.setRefLogMessage("commit : " + revCommit.getShortMessage(), false); - ru.setExpectedOldObjectId(parentID); + ru.setExpectedOldObjectId(headId); Result rc = ru.update(); switch (rc) { case NEW: case FAST_FORWARD: setCallable(false); + if (state == RepositoryState.MERGING_RESOLVED) { + // Commit was successful. Now delete the files + // used for merge commits + new File(repo.getDirectory(), Constants.MERGE_HEAD) + .delete(); + new File(repo.getDirectory(), Constants.MERGE_MSG) + .delete(); + } return revCommit; case REJECTED: case LOCK_FAILURE: @@ -179,18 +206,41 @@ public class CommitCommand extends GitCommand<RevCommit> { * Sets default values for not explicitly specified options. Then validates * that all required data has been provided. * + * @param state + * the state of the repository we are working on + * * @throws NoMessageException * if the commit message has not been specified */ - private void processOptions() throws NoMessageException { - if (message == null) - // as long as we don't suppport -C option we have to have - // an explicit message - throw new NoMessageException(JGitText.get().commitMessageNotSpecified); + private void processOptions(RepositoryState state) throws NoMessageException { if (committer == null) committer = new PersonIdent(repo); if (author == null) author = committer; + + // when doing a merge commit parse MERGE_HEAD and MERGE_MSG files + if (state == RepositoryState.MERGING_RESOLVED) { + try { + parents = repo.readMergeHeads(); + } catch (IOException e) { + throw new JGitInternalException(MessageFormat.format( + JGitText.get().exceptionOccuredDuringReadingOfGIT_DIR, + Constants.MERGE_HEAD, e)); + } + if (message == null) { + try { + message = repo.readMergeCommitMsg(); + } catch (IOException e) { + throw new JGitInternalException(MessageFormat.format( + JGitText.get().exceptionOccuredDuringReadingOfGIT_DIR, + Constants.MERGE_MSG, e)); + } + } + } + if (message == null) + // as long as we don't suppport -C option we have to have + // an explicit message + throw new NoMessageException(JGitText.get().commitMessageNotSpecified); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/WrongRepositoryStateException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/WrongRepositoryStateException.java new file mode 100644 index 0000000000..833cf8d6ed --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/WrongRepositoryStateException.java @@ -0,0 +1,55 @@ +/* + * 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 the state of the repository doesn't allow the execution + * of a certain command. E.g. when a CommitCommand should be executed on a + * repository with unresolved conflicts this exception will be thrown. + */ +public class WrongRepositoryStateException extends GitAPIException { + private static final long serialVersionUID = 1L; + + WrongRepositoryStateException(String message, Throwable cause) { + super(message, cause); + } + + WrongRepositoryStateException(String message) { + super(message); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index 37836f3246..03ab629790 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -518,6 +518,12 @@ public final class Constants { CHARSET = Charset.forName(CHARACTER_ENCODING); } + /** name of the file containing the commit msg for a merge commit */ + public static final String MERGE_MSG = "MERGE_MSG"; + + /** name of the file containing the IDs of the parents of a merge commit */ + public static final String MERGE_HEAD = "MERGE_HEAD"; + private Constants() { // Hide the default constructor } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index e4d857bf8d..233cecf310 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -47,6 +47,7 @@ package org.eclipse.jgit.lib; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; @@ -61,12 +62,14 @@ import java.util.Set; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; -import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.SystemReader; /** @@ -1338,4 +1341,55 @@ public class Repository { return new ReflogReader(this, ref.getName()); return null; } + + /** + * Return the information stored in the file $GIT_DIR/MERGE_MSG. In this + * file operations triggering a merge will store a template for the commit + * message of the merge commit. + * + * @return a String containing the content of the MERGE_MSG file or + * {@code null} if this file doesn't exist + * @throws IOException + */ + public String readMergeCommitMsg() throws IOException { + File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG); + try { + return new String(IO.readFully(mergeMsgFile)); + } catch (FileNotFoundException e) { + // MERGE_MSG file has disappeared in the meantime + // ignore it + return null; + } + } + + /** + * Return the information stored in the file $GIT_DIR/MERGE_HEAD. In this + * file operations triggering a merge will store the IDs of all heads which + * should be merged together with HEAD. + * + * @return a list of {@link Commit}s which IDs are listed in the MERGE_HEAD + * file or {@code null} if this file doesn't exist. Also if the file + * exists but is empty {@code null} will be returned + * @throws IOException + */ + public List<ObjectId> readMergeHeads() throws IOException { + File mergeHeadFile = new File(gitDir, Constants.MERGE_HEAD); + byte[] raw; + try { + raw = IO.readFully(mergeHeadFile); + } catch (FileNotFoundException notFound) { + return new LinkedList<ObjectId>(); + } + + if (raw.length == 0) + throw new IOException("MERGE_HEAD file empty: " + mergeHeadFile); + + LinkedList<ObjectId> heads = new LinkedList<ObjectId>(); + for (int p = 0; p < raw.length;) { + heads.add(ObjectId.fromString(raw, p)); + p = RawParseUtils + .nextLF(raw, p + Constants.OBJECT_ID_STRING_LENGTH); + } + return heads; + } } |