diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java index e07076e32..a680ef9f9 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java @@ -54,6 +54,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.hooks.CommitMsgHook; +import org.eclipse.jgit.hooks.PostCommitHook; import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; @@ -75,6 +76,18 @@ public void testFindHook() throws Exception { FS.DETECTED.findHook(db, PreCommitHook.NAME)); } + @Test + public void testFindPostCommitHook() throws Exception { + assumeSupportedPlatform(); + + assertNull("no hook should be installed", + FS.DETECTED.findHook(db, PostCommitHook.NAME)); + File hookFile = writeHookFile(PostCommitHook.NAME, + "#!/bin/bash\necho \"test $1 $2\""); + assertEquals("expected to find post-commit hook", hookFile, + FS.DETECTED.findHook(db, PostCommitHook.NAME)); + } + @Test public void testFailedCommitMsgHookBlocksCommit() throws Exception { assumeSupportedPlatform(); @@ -132,6 +145,55 @@ public void testCommitMsgHookCanModifyCommitMessage() throws Exception { assertEquals("new message\n", revCommit.getFullMessage()); } + @Test + public void testPostCommitRunHook() throws Exception { + assumeSupportedPlatform(); + + writeHookFile(PostCommitHook.NAME, + "#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\necho 1>&2 \"stderr\""); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + ProcessResult res = FS.DETECTED.runHookIfPresent(db, + PostCommitHook.NAME, + new String[] { + "arg1", "arg2" }, + new PrintStream(out), new PrintStream(err), "stdin"); + + assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n", + out.toString("UTF-8")); + assertEquals("unexpected output on stderr stream", "stderr\n", + err.toString("UTF-8")); + assertEquals("unexpected exit code", 0, res.getExitCode()); + assertEquals("unexpected process status", ProcessResult.Status.OK, + res.getStatus()); + } + + @Test + public void testAllCommitHooks() throws Exception { + assumeSupportedPlatform(); + + writeHookFile(PreCommitHook.NAME, + "#!/bin/sh\necho \"test pre-commit\"\n\necho 1>&2 \"stderr pre-commit\"\nexit 0"); + writeHookFile(CommitMsgHook.NAME, + "#!/bin/sh\necho \"test commit-msg $1\"\n\necho 1>&2 \"stderr commit-msg\"\nexit 0"); + writeHookFile(PostCommitHook.NAME, + "#!/bin/sh\necho \"test post-commit\"\necho 1>&2 \"stderr post-commit\"\nexit 0"); + 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(); + } catch (AbortedByHookException e) { + fail("unexpected hook failure"); + } + assertEquals("unexpected hook output", + "test pre-commit\ntest commit-msg .git/COMMIT_EDITMSG\ntest post-commit\n", + out.toString("UTF-8")); + } + @Test public void testRunHook() throws Exception { assumeSupportedPlatform(); diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index c1954ff07..327ca0a10 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -304,6 +304,7 @@ hunkDisconnectedFromFile=Hunk disconnected from file hunkHeaderDoesNotMatchBodyLineCountOf=Hunk header {0} does not match body line count of {1} illegalArgumentNotA=Not {0} illegalCombinationOfArguments=The combination of arguments {0} and {1} is not allowed +illegalHookName=Illegal hook name {0} illegalPackingPhase=Illegal packing phase {0} illegalStateExists=exists {0} improperlyPaddedBase64Input=Improperly padded Base64 input. 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 561319c44..e1793f31a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -48,6 +48,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -67,7 +68,10 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.UnmergedPathException; +import org.eclipse.jgit.hooks.CommitMsgHook; import org.eclipse.jgit.hooks.Hooks; +import org.eclipse.jgit.hooks.PostCommitHook; +import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; @@ -131,7 +135,7 @@ public class CommitCommand extends GitCommand { */ private boolean noVerify; - private PrintStream hookOutRedirect; + private HashMap hookOutRedirect = new HashMap<>(3); private Boolean allowEmpty; @@ -179,7 +183,8 @@ public RevCommit call() throws GitAPIException, NoHeadException, state.name())); if (!noVerify) { - Hooks.preCommit(repo, hookOutRedirect).call(); + Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME)) + .call(); } processOptions(state, rw); @@ -218,7 +223,9 @@ public RevCommit call() throws GitAPIException, NoHeadException, } if (!noVerify) { - message = Hooks.commitMsg(repo, hookOutRedirect) + message = Hooks + .commitMsg(repo, + hookOutRedirect.get(CommitMsgHook.NAME)) .setCommitMessage(message).call(); } @@ -292,6 +299,9 @@ public RevCommit call() throws GitAPIException, NoHeadException, repo.writeMergeCommitMsg(null); repo.writeRevertHead(null); } + Hooks.postCommit(repo, + hookOutRedirect.get(PostCommitHook.NAME)).call(); + return revCommit; } case REJECTED: @@ -822,8 +832,9 @@ public CommitCommand setNoVerify(boolean noVerify) { } /** - * Set the output stream for hook scripts executed by this command. If not - * set it defaults to {@code System.out}. + * Set the output stream for all hook scripts executed by this command + * (pre-commit, commit-msg, post-commit). If not set it defaults to + * {@code System.out}. * * @param hookStdOut * the output stream for hook scripts executed by this command @@ -831,7 +842,34 @@ public CommitCommand setNoVerify(boolean noVerify) { * @since 3.7 */ public CommitCommand setHookOutputStream(PrintStream hookStdOut) { - this.hookOutRedirect = hookStdOut; + setHookOutputStream(PreCommitHook.NAME, hookStdOut); + setHookOutputStream(CommitMsgHook.NAME, hookStdOut); + setHookOutputStream(PostCommitHook.NAME, hookStdOut); + return this; + } + + /** + * Set the output stream for a selected hook script executed by this command + * (pre-commit, commit-msg, post-commit). If not set it defaults to + * {@code System.out}. + * + * @param hookName + * name of the hook to set the output stream for + * @param hookStdOut + * the output stream to use for the selected hook + * @return {@code this} + * @since 4.5 + */ + public CommitCommand setHookOutputStream(String hookName, + PrintStream hookStdOut) { + if (!(PreCommitHook.NAME.equals(hookName) + || CommitMsgHook.NAME.equals(hookName) + || PostCommitHook.NAME.equals(hookName))) { + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().illegalHookName, + hookName)); + } + hookOutRedirect.put(hookName, hookStdOut); return this; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java index 6f7a21a73..46e884057 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java @@ -64,6 +64,18 @@ public static PreCommitHook preCommit(Repository repo, return new PreCommitHook(repo, outputStream); } + /** + * @param repo + * @param outputStream + * The output stream, or {@code null} to use {@code System.out} + * @return The post-commit hook for the given repository. + * @since 4.5 + */ + public static PostCommitHook postCommit(Repository repo, + PrintStream outputStream) { + return new PostCommitHook(repo, outputStream); + } + /** * @param repo * @param outputStream diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java new file mode 100644 index 000000000..70679e009 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java @@ -0,0 +1,84 @@ +/* + * 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.hooks; + +import java.io.IOException; +import java.io.PrintStream; + +import org.eclipse.jgit.api.errors.AbortedByHookException; +import org.eclipse.jgit.lib.Repository; + +/** + * The post-commit hook implementation. This hook is run after the + * commit was successfully executed. + * + * @since 4.5 + */ +public class PostCommitHook extends GitHook { + + /** The post-commit hook name. */ + public static final String NAME = "post-commit"; //$NON-NLS-1$ + + /** + * @param repo + * The repository + * @param outputStream + * The output stream the hook must use. {@code null} is allowed, + * in which case the hook will use {@code System.out}. + */ + protected PostCommitHook(Repository repo, PrintStream outputStream) { + super(repo, outputStream); + } + + @Override + public Void call() throws IOException, AbortedByHookException { + doRun(); + return null; + } + + @Override + public String getHookName() { + return NAME; + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 3a636a153..758f71d27 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -363,6 +363,7 @@ public static JGitText get() { /***/ public String hunkHeaderDoesNotMatchBodyLineCountOf; /***/ public String illegalArgumentNotA; /***/ public String illegalCombinationOfArguments; + /***/ public String illegalHookName; /***/ public String illegalPackingPhase; /***/ public String illegalStateExists; /***/ public String improperlyPaddedBase64Input;