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-Vendor: %provider_name
|
||||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
||||||
Import-Package: org.eclipse.jgit.api;version="[3.7.0,3.8.0)",
|
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.diff;version="[3.7.0,3.8.0)",
|
||||||
org.eclipse.jgit.dircache;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",
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
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.JGitTestUtil;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
|
@ -91,6 +94,33 @@ public void testRunHook() throws Exception {
|
||||||
res.getStatus());
|
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)
|
private File writeHookFile(final String name, final String data)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
File path = new File(db.getWorkTree() + "/.git/hooks/", name);
|
File path = new File(db.getWorkTree() + "/.git/hooks/", name);
|
||||||
|
|
|
@ -104,6 +104,7 @@ commitAlreadyExists=exists {0}
|
||||||
commitMessageNotSpecified=commit message not specified
|
commitMessageNotSpecified=commit message not specified
|
||||||
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
|
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
|
||||||
commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
|
commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
|
||||||
|
commitRejectedByHook=Commit rejected by "{0}" hook.\n{1}
|
||||||
compressingObjects=Compressing objects
|
compressingObjects=Compressing objects
|
||||||
connectionFailed=connection failed
|
connectionFailed=connection failed
|
||||||
connectionTimeOut=Connection time out: {0}
|
connectionTimeOut=Connection time out: {0}
|
||||||
|
|
|
@ -169,7 +169,8 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
|
||||||
.setMessage(srcCommit.getFullMessage())
|
.setMessage(srcCommit.getFullMessage())
|
||||||
.setReflogComment(reflogPrefix + " " //$NON-NLS-1$
|
.setReflogComment(reflogPrefix + " " //$NON-NLS-1$
|
||||||
+ srcCommit.getShortMessage())
|
+ srcCommit.getShortMessage())
|
||||||
.setAuthor(srcCommit.getAuthorIdent()).call();
|
.setAuthor(srcCommit.getAuthorIdent())
|
||||||
|
.setNoVerify(true).call();
|
||||||
cherryPickedRefs.add(src);
|
cherryPickedRefs.add(src);
|
||||||
} else {
|
} else {
|
||||||
if (merger.failed())
|
if (merger.failed())
|
||||||
|
|
|
@ -42,8 +42,10 @@
|
||||||
*/
|
*/
|
||||||
package org.eclipse.jgit.api;
|
package org.eclipse.jgit.api;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -56,6 +58,7 @@
|
||||||
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
||||||
import org.eclipse.jgit.api.errors.NoHeadException;
|
import org.eclipse.jgit.api.errors.NoHeadException;
|
||||||
import org.eclipse.jgit.api.errors.NoMessageException;
|
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.UnmergedPathsException;
|
||||||
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
|
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
|
@ -84,6 +87,9 @@
|
||||||
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import org.eclipse.jgit.util.ChangeIdUtil;
|
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
|
* 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;
|
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
|
* @param repo
|
||||||
*/
|
*/
|
||||||
protected CommitCommand(Repository repo) {
|
protected CommitCommand(Repository repo) {
|
||||||
super(repo);
|
super(repo);
|
||||||
|
hookOutRedirect = System.out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,11 +159,14 @@ protected CommitCommand(Repository repo) {
|
||||||
* else
|
* else
|
||||||
* @throws WrongRepositoryStateException
|
* @throws WrongRepositoryStateException
|
||||||
* when repository is not in the right state for committing
|
* 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,
|
public RevCommit call() throws GitAPIException, NoHeadException,
|
||||||
NoMessageException, UnmergedPathsException,
|
NoMessageException, UnmergedPathsException,
|
||||||
ConcurrentRefUpdateException,
|
ConcurrentRefUpdateException, WrongRepositoryStateException,
|
||||||
WrongRepositoryStateException {
|
RejectCommitException {
|
||||||
checkCallable();
|
checkCallable();
|
||||||
Collections.sort(only);
|
Collections.sort(only);
|
||||||
|
|
||||||
|
@ -160,6 +178,23 @@ public RevCommit call() throws GitAPIException, NoHeadException,
|
||||||
throw new WrongRepositoryStateException(MessageFormat.format(
|
throw new WrongRepositoryStateException(MessageFormat.format(
|
||||||
JGitText.get().cannotCommitOnARepoWithState,
|
JGitText.get().cannotCommitOnARepoWithState,
|
||||||
state.name()));
|
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);
|
processOptions(state, rw);
|
||||||
|
|
||||||
if (all && !repo.isBare() && repo.getWorkTree() != null) {
|
if (all && !repo.isBare() && repo.getWorkTree() != null) {
|
||||||
|
@ -733,4 +768,36 @@ public CommitCommand setReflogComment(String reflogComment) {
|
||||||
return this;
|
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
|
String newMessage = interactiveHandler
|
||||||
.modifyCommitMessage(oldMessage);
|
.modifyCommitMessage(oldMessage);
|
||||||
newHead = new Git(repo).commit().setMessage(newMessage)
|
newHead = new Git(repo).commit().setMessage(newMessage)
|
||||||
.setAmend(true).call();
|
.setAmend(true).setNoVerify(true).call();
|
||||||
return null;
|
return null;
|
||||||
case EDIT:
|
case EDIT:
|
||||||
rebaseState.createFile(AMEND, commitToPick.name());
|
rebaseState.createFile(AMEND, commitToPick.name());
|
||||||
|
@ -768,15 +768,14 @@ private RevCommit squashIntoPrevious(boolean sequenceContainsSquash,
|
||||||
}
|
}
|
||||||
retNewHead = new Git(repo).commit()
|
retNewHead = new Git(repo).commit()
|
||||||
.setMessage(stripCommentLines(commitMessage))
|
.setMessage(stripCommentLines(commitMessage))
|
||||||
.setAmend(true).call();
|
.setAmend(true).setNoVerify(true).call();
|
||||||
rebaseState.getFile(MESSAGE_SQUASH).delete();
|
rebaseState.getFile(MESSAGE_SQUASH).delete();
|
||||||
rebaseState.getFile(MESSAGE_FIXUP).delete();
|
rebaseState.getFile(MESSAGE_FIXUP).delete();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Next step is either Squash or Fixup
|
// Next step is either Squash or Fixup
|
||||||
retNewHead = new Git(repo).commit()
|
retNewHead = new Git(repo).commit().setMessage(commitMessage)
|
||||||
.setMessage(commitMessage).setAmend(true)
|
.setAmend(true).setNoVerify(true).call();
|
||||||
.call();
|
|
||||||
}
|
}
|
||||||
return retNewHead;
|
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 commitMessageNotSpecified;
|
||||||
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
|
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
|
||||||
/***/ public String commitAmendOnInitialNotPossible;
|
/***/ public String commitAmendOnInitialNotPossible;
|
||||||
|
/***/ public String commitRejectedByHook;
|
||||||
/***/ public String compressingObjects;
|
/***/ public String compressingObjects;
|
||||||
/***/ public String connectionFailed;
|
/***/ public String connectionFailed;
|
||||||
/***/ public String connectionTimeOut;
|
/***/ public String connectionTimeOut;
|
||||||
|
|
Loading…
Reference in New Issue