Support for the pre-commit hook
Introduce support for the pre-commit hook into JGit, along with the --no-verify commit command option to bypass it when rebasing / cherry-picking. Change-Id: If86df98577fa56c5c03d783579c895a38bee9d18 Signed-off-by: Laurent Goubet <laurent.goubet@obeo.fr> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
parent
d2e0bfa568
commit
494e893c54
|
@ -6,6 +6,7 @@ Bundle-Version: 3.7.0.qualifier
|
|||
Bundle-Vendor: %provider_name
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
||||
Import-Package: org.eclipse.jgit.api;version="[3.7.0,3.8.0)",
|
||||
org.eclipse.jgit.api.errors;version="[3.7.0,3.8.0)",
|
||||
org.eclipse.jgit.diff;version="[3.7.0,3.8.0)",
|
||||
org.eclipse.jgit.dircache;version="[3.7.0,3.8.0)",
|
||||
org.eclipse.jgit.internal.storage.file;version="3.7.0",
|
||||
|
|
|
@ -44,12 +44,15 @@
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.RejectCommitException;
|
||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||
import org.junit.Assume;
|
||||
|
@ -91,6 +94,33 @@ public void testRunHook() throws Exception {
|
|||
res.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreCommitHook() throws Exception {
|
||||
assumeSupportedPlatform();
|
||||
|
||||
Hook h = Hook.PRE_COMMIT;
|
||||
writeHookFile(h.getName(),
|
||||
"#!/bin/sh\necho \"test\"\n\necho 1>&2 \"stderr\"\nexit 1");
|
||||
Git git = Git.wrap(db);
|
||||
String path = "a.txt";
|
||||
writeTrashFile(path, "content");
|
||||
git.add().addFilepattern(path).call();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try {
|
||||
git.commit().setMessage("commit")
|
||||
.setHookOutputStream(new PrintStream(out)).call();
|
||||
fail("expected pre-commit hook to abort commit");
|
||||
} catch (RejectCommitException e) {
|
||||
assertEquals("unexpected error message from pre-commit hook",
|
||||
"Commit rejected by \"pre-commit\" hook.\nstderr\n",
|
||||
e.getMessage());
|
||||
assertEquals("unexpected output from pre-commit hook", "test\n",
|
||||
out.toString());
|
||||
} catch (Throwable e) {
|
||||
fail("unexpected exception thrown by pre-commit hook: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private File writeHookFile(final String name, final String data)
|
||||
throws IOException {
|
||||
File path = new File(db.getWorkTree() + "/.git/hooks/", name);
|
||||
|
|
|
@ -104,6 +104,7 @@ commitAlreadyExists=exists {0}
|
|||
commitMessageNotSpecified=commit message not specified
|
||||
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
|
||||
commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
|
||||
commitRejectedByHook=Commit rejected by "{0}" hook.\n{1}
|
||||
compressingObjects=Compressing objects
|
||||
connectionFailed=connection failed
|
||||
connectionTimeOut=Connection time out: {0}
|
||||
|
|
|
@ -169,7 +169,8 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
|
|||
.setMessage(srcCommit.getFullMessage())
|
||||
.setReflogComment(reflogPrefix + " " //$NON-NLS-1$
|
||||
+ srcCommit.getShortMessage())
|
||||
.setAuthor(srcCommit.getAuthorIdent()).call();
|
||||
.setAuthor(srcCommit.getAuthorIdent())
|
||||
.setNoVerify(true).call();
|
||||
cherryPickedRefs.add(src);
|
||||
} else {
|
||||
if (merger.failed())
|
||||
|
|
|
@ -42,8 +42,10 @@
|
|||
*/
|
||||
package org.eclipse.jgit.api;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -56,6 +58,7 @@
|
|||
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
||||
import org.eclipse.jgit.api.errors.NoHeadException;
|
||||
import org.eclipse.jgit.api.errors.NoMessageException;
|
||||
import org.eclipse.jgit.api.errors.RejectCommitException;
|
||||
import org.eclipse.jgit.api.errors.UnmergedPathsException;
|
||||
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
|
||||
import org.eclipse.jgit.dircache.DirCache;
|
||||
|
@ -84,6 +87,9 @@
|
|||
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import org.eclipse.jgit.util.ChangeIdUtil;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
import org.eclipse.jgit.util.Hook;
|
||||
import org.eclipse.jgit.util.ProcessResult;
|
||||
|
||||
/**
|
||||
* A class used to execute a {@code Commit} command. It has setters for all
|
||||
|
@ -119,11 +125,20 @@ public class CommitCommand extends GitCommand<RevCommit> {
|
|||
|
||||
private String reflogComment;
|
||||
|
||||
/**
|
||||
* Setting this option bypasses the {@link Hook#PRE_COMMIT pre-commit} and
|
||||
* {@link Hook#COMMIT_MSG commit-msg} hooks.
|
||||
*/
|
||||
private boolean noVerify;
|
||||
|
||||
private PrintStream hookOutRedirect;
|
||||
|
||||
/**
|
||||
* @param repo
|
||||
*/
|
||||
protected CommitCommand(Repository repo) {
|
||||
super(repo);
|
||||
hookOutRedirect = System.out;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,11 +159,14 @@ protected CommitCommand(Repository repo) {
|
|||
* else
|
||||
* @throws WrongRepositoryStateException
|
||||
* when repository is not in the right state for committing
|
||||
* @throws RejectCommitException
|
||||
* if there are either pre-commit or commit-msg hooks present in
|
||||
* the repository and at least one of them rejects the commit.
|
||||
*/
|
||||
public RevCommit call() throws GitAPIException, NoHeadException,
|
||||
NoMessageException, UnmergedPathsException,
|
||||
ConcurrentRefUpdateException,
|
||||
WrongRepositoryStateException {
|
||||
ConcurrentRefUpdateException, WrongRepositoryStateException,
|
||||
RejectCommitException {
|
||||
checkCallable();
|
||||
Collections.sort(only);
|
||||
|
||||
|
@ -160,6 +178,23 @@ public RevCommit call() throws GitAPIException, NoHeadException,
|
|||
throw new WrongRepositoryStateException(MessageFormat.format(
|
||||
JGitText.get().cannotCommitOnARepoWithState,
|
||||
state.name()));
|
||||
|
||||
if (!noVerify) {
|
||||
final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream();
|
||||
final PrintStream hookErrRedirect = new PrintStream(
|
||||
errorByteArray);
|
||||
ProcessResult preCommitHookResult = FS.DETECTED.runIfPresent(
|
||||
repo, Hook.PRE_COMMIT, new String[0], hookOutRedirect,
|
||||
hookErrRedirect, null);
|
||||
if (preCommitHookResult.getStatus() == ProcessResult.Status.OK
|
||||
&& preCommitHookResult.getExitCode() != 0) {
|
||||
String errorMessage = MessageFormat.format(
|
||||
JGitText.get().commitRejectedByHook, Hook.PRE_COMMIT.getName(),
|
||||
errorByteArray.toString());
|
||||
throw new RejectCommitException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
processOptions(state, rw);
|
||||
|
||||
if (all && !repo.isBare() && repo.getWorkTree() != null) {
|
||||
|
@ -733,4 +768,36 @@ public CommitCommand setReflogComment(String reflogComment) {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #noVerify} option on this commit command.
|
||||
* <p>
|
||||
* Both the {@link Hook#PRE_COMMIT pre-commit} and {@link Hook#COMMIT_MSG
|
||||
* commit-msg} hooks can block a commit by their return value; setting this
|
||||
* option to <code>true</code> will bypass these two hooks.
|
||||
* </p>
|
||||
*
|
||||
* @param noVerify
|
||||
* Whether this commit should be verified by the pre-commit and
|
||||
* commit-msg hooks.
|
||||
* @return {@code this}
|
||||
* @since 3.7
|
||||
*/
|
||||
public CommitCommand setNoVerify(boolean noVerify) {
|
||||
this.noVerify = noVerify;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the output stream for hook scripts executed by this command. If not
|
||||
* set it defaults to {@code System.out}.
|
||||
*
|
||||
* @param hookStdOut
|
||||
* the output stream for hook scripts executed by this command
|
||||
* @return {@code this}
|
||||
* @since 3.7
|
||||
*/
|
||||
public CommitCommand setHookOutputStream(PrintStream hookStdOut) {
|
||||
this.hookOutRedirect = hookStdOut;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -462,7 +462,7 @@ private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick)
|
|||
String newMessage = interactiveHandler
|
||||
.modifyCommitMessage(oldMessage);
|
||||
newHead = new Git(repo).commit().setMessage(newMessage)
|
||||
.setAmend(true).call();
|
||||
.setAmend(true).setNoVerify(true).call();
|
||||
return null;
|
||||
case EDIT:
|
||||
rebaseState.createFile(AMEND, commitToPick.name());
|
||||
|
@ -768,15 +768,14 @@ private RevCommit squashIntoPrevious(boolean sequenceContainsSquash,
|
|||
}
|
||||
retNewHead = new Git(repo).commit()
|
||||
.setMessage(stripCommentLines(commitMessage))
|
||||
.setAmend(true).call();
|
||||
.setAmend(true).setNoVerify(true).call();
|
||||
rebaseState.getFile(MESSAGE_SQUASH).delete();
|
||||
rebaseState.getFile(MESSAGE_FIXUP).delete();
|
||||
|
||||
} else {
|
||||
// Next step is either Squash or Fixup
|
||||
retNewHead = new Git(repo).commit()
|
||||
.setMessage(commitMessage).setAmend(true)
|
||||
.call();
|
||||
retNewHead = new Git(repo).commit().setMessage(commitMessage)
|
||||
.setAmend(true).setNoVerify(true).call();
|
||||
}
|
||||
return retNewHead;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Obeo.
|
||||
* 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.errors;
|
||||
|
||||
/**
|
||||
* Exception thrown when a commit is rejected by a hook (either
|
||||
* {@link org.eclipse.jgit.util.Hook#PRE_COMMIT pre-commit} or
|
||||
* {@link org.eclipse.jgit.util.Hook#COMMIT_MSG commit-msg}).
|
||||
*
|
||||
* @since 3.7
|
||||
*/
|
||||
public class RejectCommitException extends GitAPIException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* @param message
|
||||
*/
|
||||
public RejectCommitException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -163,6 +163,7 @@ public static JGitText get() {
|
|||
/***/ public String commitMessageNotSpecified;
|
||||
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
|
||||
/***/ public String commitAmendOnInitialNotPossible;
|
||||
/***/ public String commitRejectedByHook;
|
||||
/***/ public String compressingObjects;
|
||||
/***/ public String connectionFailed;
|
||||
/***/ public String connectionTimeOut;
|
||||
|
|
Loading…
Reference in New Issue