* stable-5.6: Add ability to redirect stderr from git hooks Add possibility to get pure stderr output from AbortedByHookException Change-Id: Ifc02675542dad6ced25fdd8b9fae80b5736db688 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v5.7.0.202001151323-m1
@@ -106,6 +106,16 @@ public class BuiltinLFS extends LfsFactory { | |||
return null; | |||
} | |||
@Override | |||
@Nullable | |||
public PrePushHook getPrePushHook(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
if (isEnabled(repo)) { | |||
return new LfsPrePushHook(repo, outputStream, errorStream); | |||
} | |||
return null; | |||
} | |||
/** | |||
* @param db | |||
* the repository |
@@ -107,6 +107,20 @@ public class LfsPrePushHook extends PrePushHook { | |||
super(repo, outputStream); | |||
} | |||
/** | |||
* @param repo | |||
* the repository | |||
* @param outputStream | |||
* not used by this implementation | |||
* @param errorStream | |||
* not used by this implementation | |||
* @since 5.6 | |||
*/ | |||
public LfsPrePushHook(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
super(repo, outputStream, errorStream); | |||
} | |||
@Override | |||
public void setRefs(Collection<RemoteRefUpdate> toRefs) { | |||
this.refs = toRefs; |
@@ -172,6 +172,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", | |||
org.bouncycastle.openpgp.operator;version="[1.61.0,2.0.0)", | |||
org.bouncycastle.openpgp.operator.jcajce;version="[1.61.0,2.0.0)", | |||
org.bouncycastle.util.encoders;version="[1.61.0,2.0.0)", | |||
org.bouncycastle.util.io;version="[1.61.0,2.0.0)", | |||
org.slf4j;version="[1.7.0,2.0.0)", | |||
org.xml.sax, | |||
org.xml.sax.helpers |
@@ -143,6 +143,8 @@ public class CommitCommand extends GitCommand<RevCommit> { | |||
private HashMap<String, PrintStream> hookOutRedirect = new HashMap<>(3); | |||
private HashMap<String, PrintStream> hookErrRedirect = new HashMap<>(3); | |||
private Boolean allowEmpty; | |||
private Boolean signCommit; | |||
@@ -188,7 +190,8 @@ public class CommitCommand extends GitCommand<RevCommit> { | |||
state.name())); | |||
if (!noVerify) { | |||
Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME)) | |||
Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME), | |||
hookErrRedirect.get(PreCommitHook.NAME)) | |||
.call(); | |||
} | |||
@@ -230,7 +233,8 @@ public class CommitCommand extends GitCommand<RevCommit> { | |||
if (!noVerify) { | |||
message = Hooks | |||
.commitMsg(repo, | |||
hookOutRedirect.get(CommitMsgHook.NAME)) | |||
hookOutRedirect.get(CommitMsgHook.NAME), | |||
hookErrRedirect.get(CommitMsgHook.NAME)) | |||
.setCommitMessage(message).call(); | |||
} | |||
@@ -311,7 +315,8 @@ public class CommitCommand extends GitCommand<RevCommit> { | |||
repo.writeRevertHead(null); | |||
} | |||
Hooks.postCommit(repo, | |||
hookOutRedirect.get(PostCommitHook.NAME)).call(); | |||
hookOutRedirect.get(PostCommitHook.NAME), | |||
hookErrRedirect.get(PostCommitHook.NAME)).call(); | |||
return revCommit; | |||
} | |||
@@ -890,6 +895,23 @@ public class CommitCommand extends GitCommand<RevCommit> { | |||
return this; | |||
} | |||
/** | |||
* Set the error stream for all hook scripts executed by this command | |||
* (pre-commit, commit-msg, post-commit). If not set it defaults to | |||
* {@code System.err}. | |||
* | |||
* @param hookStdErr | |||
* the error stream for hook scripts executed by this command | |||
* @return {@code this} | |||
* @since 5.6 | |||
*/ | |||
public CommitCommand setHookErrorStream(PrintStream hookStdErr) { | |||
setHookErrorStream(PreCommitHook.NAME, hookStdErr); | |||
setHookErrorStream(CommitMsgHook.NAME, hookStdErr); | |||
setHookErrorStream(PostCommitHook.NAME, hookStdErr); | |||
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 | |||
@@ -915,6 +937,30 @@ public class CommitCommand extends GitCommand<RevCommit> { | |||
return this; | |||
} | |||
/** | |||
* Set the error stream for a selected hook script executed by this command | |||
* (pre-commit, commit-msg, post-commit). If not set it defaults to | |||
* {@code System.err}. | |||
* | |||
* @param hookName | |||
* name of the hook to set the output stream for | |||
* @param hookStdErr | |||
* the output stream to use for the selected hook | |||
* @return {@code this} | |||
* @since 5.6 | |||
*/ | |||
public CommitCommand setHookErrorStream(String hookName, | |||
PrintStream hookStdErr) { | |||
if (!(PreCommitHook.NAME.equals(hookName) | |||
|| CommitMsgHook.NAME.equals(hookName) | |||
|| PostCommitHook.NAME.equals(hookName))) { | |||
throw new IllegalArgumentException(MessageFormat | |||
.format(JGitText.get().illegalHookName, hookName)); | |||
} | |||
hookErrRedirect.put(hookName, hookStdErr); | |||
return this; | |||
} | |||
/** | |||
* Sets the signing key | |||
* <p> |
@@ -66,20 +66,27 @@ public class AbortedByHookException extends GitAPIException { | |||
*/ | |||
private final int returnCode; | |||
/** | |||
* The stderr output of the hook. | |||
*/ | |||
private final String hookStdErr; | |||
/** | |||
* Constructor for AbortedByHookException | |||
* | |||
* @param message | |||
* The error details. | |||
* @param hookStdErr | |||
* The error details from the stderr output of the hook | |||
* @param hookName | |||
* The name of the hook that interrupted the command, must not be | |||
* null. | |||
* @param returnCode | |||
* The return code of the hook process that has been run. | |||
*/ | |||
public AbortedByHookException(String message, String hookName, | |||
public AbortedByHookException(String hookStdErr, String hookName, | |||
int returnCode) { | |||
super(message); | |||
super(MessageFormat.format(JGitText.get().commandRejectedByHook, | |||
hookName, hookStdErr)); | |||
this.hookStdErr = hookStdErr; | |||
this.hookName = hookName; | |||
this.returnCode = returnCode; | |||
} | |||
@@ -102,10 +109,13 @@ public class AbortedByHookException extends GitAPIException { | |||
return returnCode; | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public String getMessage() { | |||
return MessageFormat.format(JGitText.get().commandRejectedByHook, | |||
hookName, super.getMessage()); | |||
/** | |||
* Get the stderr output of the hook. | |||
* | |||
* @return A string containing the complete stderr output of the hook. | |||
* @since 5.6 | |||
*/ | |||
public String getHookStdErr() { | |||
return hookStdErr; | |||
} | |||
} |
@@ -72,6 +72,9 @@ public class CommitMsgHook extends GitHook<String> { | |||
/** | |||
* Constructor for CommitMsgHook | |||
* <p> | |||
* This constructor will use the default error stream. | |||
* </p> | |||
* | |||
* @param repo | |||
* The repository | |||
@@ -83,6 +86,24 @@ public class CommitMsgHook extends GitHook<String> { | |||
super(repo, outputStream); | |||
} | |||
/** | |||
* Constructor for CommitMsgHook | |||
* | |||
* @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}. | |||
* @param errorStream | |||
* The error stream the hook must use. {@code null} is allowed, | |||
* in which case the hook will use {@code System.err}. | |||
* @since 5.6 | |||
*/ | |||
protected CommitMsgHook(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
super(repo, outputStream, errorStream); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public String call() throws IOException, AbortedByHookException { |
@@ -50,6 +50,7 @@ import java.io.PrintStream; | |||
import java.io.UnsupportedEncodingException; | |||
import java.util.concurrent.Callable; | |||
import org.bouncycastle.util.io.TeeOutputStream; | |||
import org.eclipse.jgit.api.errors.AbortedByHookException; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.util.FS; | |||
@@ -79,7 +80,15 @@ abstract class GitHook<T> implements Callable<T> { | |||
protected final PrintStream outputStream; | |||
/** | |||
* Constructor for GitHook | |||
* The error stream to be used by the hook. | |||
*/ | |||
protected final PrintStream errorStream; | |||
/** | |||
* Constructor for GitHook. | |||
* <p> | |||
* This constructor will use stderr for the error stream. | |||
* </p> | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
@@ -88,8 +97,26 @@ abstract class GitHook<T> implements Callable<T> { | |||
* in which case the hook will use {@code System.out}. | |||
*/ | |||
protected GitHook(Repository repo, PrintStream outputStream) { | |||
this(repo, outputStream, null); | |||
} | |||
/** | |||
* Constructor for GitHook | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
* @param outputStream | |||
* The output stream the hook must use. {@code null} is allowed, | |||
* in which case the hook will use {@code System.out}. | |||
* @param errorStream | |||
* The error stream the hook must use. {@code null} is allowed, | |||
* in which case the hook will use {@code System.err}. | |||
*/ | |||
protected GitHook(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
this.repo = repo; | |||
this.outputStream = outputStream; | |||
this.errorStream = errorStream; | |||
} | |||
/** | |||
@@ -147,6 +174,16 @@ abstract class GitHook<T> implements Callable<T> { | |||
return outputStream == null ? System.out : outputStream; | |||
} | |||
/** | |||
* Get error stream | |||
* | |||
* @return The error stream the hook must use. Never {@code null}, | |||
* {@code System.err} is returned by default. | |||
*/ | |||
protected PrintStream getErrorStream() { | |||
return errorStream == null ? System.err : errorStream; | |||
} | |||
/** | |||
* Runs the hook, without performing any validity checks. | |||
* | |||
@@ -155,9 +192,11 @@ abstract class GitHook<T> implements Callable<T> { | |||
*/ | |||
protected void doRun() throws AbortedByHookException { | |||
final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream(); | |||
final TeeOutputStream stderrStream = new TeeOutputStream(errorByteArray, | |||
getErrorStream()); | |||
PrintStream hookErrRedirect = null; | |||
try { | |||
hookErrRedirect = new PrintStream(errorByteArray, false, | |||
hookErrRedirect = new PrintStream(stderrStream, false, | |||
UTF_8.name()); | |||
} catch (UnsupportedEncodingException e) { | |||
// UTF-8 is guaranteed to be available |
@@ -57,7 +57,8 @@ import org.eclipse.jgit.util.LfsFactory; | |||
public class Hooks { | |||
/** | |||
* Create pre-commit hook for the given repository | |||
* Create pre-commit hook for the given repository with the default error | |||
* stream | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
@@ -71,7 +72,25 @@ public class Hooks { | |||
} | |||
/** | |||
* Create post-commit hook for the given repository | |||
* Create pre-commit hook for the given repository | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
* @param outputStream | |||
* The output stream, or {@code null} to use {@code System.out} | |||
* @param errorStream | |||
* The error stream, or {@code null} to use {@code System.err} | |||
* @return The pre-commit hook for the given repository. | |||
* @since 5.6 | |||
*/ | |||
public static PreCommitHook preCommit(Repository repo, | |||
PrintStream outputStream, PrintStream errorStream) { | |||
return new PreCommitHook(repo, outputStream, errorStream); | |||
} | |||
/** | |||
* Create post-commit hook for the given repository with the default error | |||
* stream | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
@@ -86,7 +105,25 @@ public class Hooks { | |||
} | |||
/** | |||
* Create commit-msg hook for the given repository | |||
* Create post-commit hook for the given repository | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
* @param outputStream | |||
* The output stream, or {@code null} to use {@code System.out} | |||
* @param errorStream | |||
* The error stream, or {@code null} to use {@code System.err} | |||
* @return The pre-commit hook for the given repository. | |||
* @since 5.6 | |||
*/ | |||
public static PostCommitHook postCommit(Repository repo, | |||
PrintStream outputStream, PrintStream errorStream) { | |||
return new PostCommitHook(repo, outputStream, errorStream); | |||
} | |||
/** | |||
* Create commit-msg hook for the given repository with the default error | |||
* stream | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
@@ -100,7 +137,25 @@ public class Hooks { | |||
} | |||
/** | |||
* Create pre-push hook for the given repository | |||
* Create commit-msg hook for the given repository | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
* @param outputStream | |||
* The output stream, or {@code null} to use {@code System.out} | |||
* @param errorStream | |||
* The error stream, or {@code null} to use {@code System.err} | |||
* @return The pre-commit hook for the given repository. | |||
* @since 5.6 | |||
*/ | |||
public static CommitMsgHook commitMsg(Repository repo, | |||
PrintStream outputStream, PrintStream errorStream) { | |||
return new CommitMsgHook(repo, outputStream, errorStream); | |||
} | |||
/** | |||
* Create pre-push hook for the given repository with the default error | |||
* stream | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
@@ -127,4 +182,36 @@ public class Hooks { | |||
} | |||
return new PrePushHook(repo, outputStream); | |||
} | |||
/** | |||
* Create pre-push hook for the given repository | |||
* | |||
* @param repo | |||
* a {@link org.eclipse.jgit.lib.Repository} object. | |||
* @param outputStream | |||
* The output stream, or {@code null} to use {@code System.out} | |||
* @param errorStream | |||
* The error stream, or {@code null} to use {@code System.err} | |||
* @return The pre-push hook for the given repository. | |||
* @since 5.6 | |||
*/ | |||
public static PrePushHook prePush(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
if (LfsFactory.getInstance().isAvailable()) { | |||
PrePushHook hook = LfsFactory.getInstance().getPrePushHook(repo, | |||
outputStream, errorStream); | |||
if (hook != null) { | |||
if (hook.isNativeHookPresent()) { | |||
PrintStream ps = outputStream; | |||
if (ps == null) { | |||
ps = System.out; | |||
} | |||
ps.println(MessageFormat | |||
.format(JGitText.get().lfsHookConflict, repo)); | |||
} | |||
return hook; | |||
} | |||
} | |||
return new PrePushHook(repo, outputStream, errorStream); | |||
} | |||
} |
@@ -61,6 +61,9 @@ public class PostCommitHook extends GitHook<Void> { | |||
/** | |||
* Constructor for PostCommitHook | |||
* <p> | |||
* This constructor will use the default error stream. | |||
* </p> | |||
* | |||
* @param repo | |||
* The repository | |||
@@ -72,6 +75,24 @@ public class PostCommitHook extends GitHook<Void> { | |||
super(repo, outputStream); | |||
} | |||
/** | |||
* Constructor for PostCommitHook | |||
* | |||
* @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}. | |||
* @param errorStream | |||
* The error stream the hook must use. {@code null} is allowed, | |||
* in which case the hook will use {@code System.err}. | |||
* @since 5.6 | |||
*/ | |||
protected PostCommitHook(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
super(repo, outputStream, errorStream); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public Void call() throws IOException, AbortedByHookException { |
@@ -61,6 +61,9 @@ public class PreCommitHook extends GitHook<Void> { | |||
/** | |||
* Constructor for PreCommitHook | |||
* <p> | |||
* This constructor will use the default error stream. | |||
* </p> | |||
* | |||
* @param repo | |||
* The repository | |||
@@ -72,6 +75,24 @@ public class PreCommitHook extends GitHook<Void> { | |||
super(repo, outputStream); | |||
} | |||
/** | |||
* Constructor for PreCommitHook | |||
* | |||
* @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}. | |||
* @param errorStream | |||
* The error stream the hook must use. {@code null} is allowed, | |||
* in which case the hook will use {@code System.err}. | |||
* @since 5.6 | |||
*/ | |||
protected PreCommitHook(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
super(repo, outputStream, errorStream); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public Void call() throws IOException, AbortedByHookException { |
@@ -73,6 +73,9 @@ public class PrePushHook extends GitHook<String> { | |||
/** | |||
* Constructor for PrePushHook | |||
* <p> | |||
* This constructor will use the default error stream. | |||
* </p> | |||
* | |||
* @param repo | |||
* The repository | |||
@@ -84,6 +87,24 @@ public class PrePushHook extends GitHook<String> { | |||
super(repo, outputStream); | |||
} | |||
/** | |||
* Constructor for PrePushHook | |||
* | |||
* @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}. | |||
* @param errorStream | |||
* The error stream the hook must use. {@code null} is allowed, | |||
* in which case the hook will use {@code System.err}. | |||
* @since 5.6 | |||
*/ | |||
protected PrePushHook(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
super(repo, outputStream, errorStream); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
protected String getStdinArgs() { |
@@ -145,7 +145,7 @@ public class LfsFactory { | |||
} | |||
/** | |||
* Retrieve a pre-push hook to be applied. | |||
* Retrieve a pre-push hook to be applied using the default error stream. | |||
* | |||
* @param repo | |||
* the {@link Repository} the hook is applied to. | |||
@@ -158,6 +158,22 @@ public class LfsFactory { | |||
return null; | |||
} | |||
/** | |||
* Retrieve a pre-push hook to be applied. | |||
* | |||
* @param repo | |||
* the {@link Repository} the hook is applied to. | |||
* @param outputStream | |||
* @param errorStream | |||
* @return a {@link PrePushHook} implementation or <code>null</code> | |||
* @since 5.6 | |||
*/ | |||
@Nullable | |||
public PrePushHook getPrePushHook(Repository repo, PrintStream outputStream, | |||
PrintStream errorStream) { | |||
return getPrePushHook(repo, outputStream); | |||
} | |||
/** | |||
* Retrieve an {@link LfsInstallCommand} which can be used to enable LFS | |||
* support (if available) either per repository or for the user. |