Merge branch 'stable-5.6'

* 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>
This commit is contained in:
Matthias Sohn 2019-12-09 10:14:38 +01:00
commit 23c5ebff79
12 changed files with 326 additions and 19 deletions

View File

@ -106,6 +106,16 @@ public PrePushHook getPrePushHook(Repository repo,
return null; 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 * @param db
* the repository * the repository

View File

@ -107,6 +107,20 @@ public LfsPrePushHook(Repository repo, PrintStream outputStream) {
super(repo, outputStream); 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 @Override
public void setRefs(Collection<RemoteRefUpdate> toRefs) { public void setRefs(Collection<RemoteRefUpdate> toRefs) {
this.refs = toRefs; this.refs = toRefs;

View File

@ -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;version="[1.61.0,2.0.0)",
org.bouncycastle.openpgp.operator.jcajce;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.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.slf4j;version="[1.7.0,2.0.0)",
org.xml.sax, org.xml.sax,
org.xml.sax.helpers org.xml.sax.helpers

View File

@ -143,6 +143,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
private HashMap<String, PrintStream> hookOutRedirect = new HashMap<>(3); private HashMap<String, PrintStream> hookOutRedirect = new HashMap<>(3);
private HashMap<String, PrintStream> hookErrRedirect = new HashMap<>(3);
private Boolean allowEmpty; private Boolean allowEmpty;
private Boolean signCommit; private Boolean signCommit;
@ -188,7 +190,8 @@ public RevCommit call() throws GitAPIException, NoHeadException,
state.name())); state.name()));
if (!noVerify) { if (!noVerify) {
Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME)) Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME),
hookErrRedirect.get(PreCommitHook.NAME))
.call(); .call();
} }
@ -230,7 +233,8 @@ public RevCommit call() throws GitAPIException, NoHeadException,
if (!noVerify) { if (!noVerify) {
message = Hooks message = Hooks
.commitMsg(repo, .commitMsg(repo,
hookOutRedirect.get(CommitMsgHook.NAME)) hookOutRedirect.get(CommitMsgHook.NAME),
hookErrRedirect.get(CommitMsgHook.NAME))
.setCommitMessage(message).call(); .setCommitMessage(message).call();
} }
@ -311,7 +315,8 @@ public RevCommit call() throws GitAPIException, NoHeadException,
repo.writeRevertHead(null); repo.writeRevertHead(null);
} }
Hooks.postCommit(repo, Hooks.postCommit(repo,
hookOutRedirect.get(PostCommitHook.NAME)).call(); hookOutRedirect.get(PostCommitHook.NAME),
hookErrRedirect.get(PostCommitHook.NAME)).call();
return revCommit; return revCommit;
} }
@ -890,6 +895,23 @@ public CommitCommand setHookOutputStream(PrintStream hookStdOut) {
return this; 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 * 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 * (pre-commit, commit-msg, post-commit). If not set it defaults to
@ -915,6 +937,30 @@ public CommitCommand setHookOutputStream(String hookName,
return this; 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 * Sets the signing key
* <p> * <p>

View File

@ -66,20 +66,27 @@ public class AbortedByHookException extends GitAPIException {
*/ */
private final int returnCode; private final int returnCode;
/**
* The stderr output of the hook.
*/
private final String hookStdErr;
/** /**
* Constructor for AbortedByHookException * Constructor for AbortedByHookException
* *
* @param message * @param hookStdErr
* The error details. * The error details from the stderr output of the hook
* @param hookName * @param hookName
* The name of the hook that interrupted the command, must not be * The name of the hook that interrupted the command, must not be
* null. * null.
* @param returnCode * @param returnCode
* The return code of the hook process that has been run. * 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) { int returnCode) {
super(message); super(MessageFormat.format(JGitText.get().commandRejectedByHook,
hookName, hookStdErr));
this.hookStdErr = hookStdErr;
this.hookName = hookName; this.hookName = hookName;
this.returnCode = returnCode; this.returnCode = returnCode;
} }
@ -102,10 +109,13 @@ public int getReturnCode() {
return returnCode; return returnCode;
} }
/** {@inheritDoc} */ /**
@Override * Get the stderr output of the hook.
public String getMessage() { *
return MessageFormat.format(JGitText.get().commandRejectedByHook, * @return A string containing the complete stderr output of the hook.
hookName, super.getMessage()); * @since 5.6
*/
public String getHookStdErr() {
return hookStdErr;
} }
} }

View File

@ -72,6 +72,9 @@ public class CommitMsgHook extends GitHook<String> {
/** /**
* Constructor for CommitMsgHook * Constructor for CommitMsgHook
* <p>
* This constructor will use the default error stream.
* </p>
* *
* @param repo * @param repo
* The repository * The repository
@ -83,6 +86,24 @@ protected CommitMsgHook(Repository repo, PrintStream outputStream) {
super(repo, outputStream); 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} */ /** {@inheritDoc} */
@Override @Override
public String call() throws IOException, AbortedByHookException { public String call() throws IOException, AbortedByHookException {

View File

@ -50,6 +50,7 @@
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.bouncycastle.util.io.TeeOutputStream;
import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
@ -79,7 +80,15 @@ abstract class GitHook<T> implements Callable<T> {
protected final PrintStream outputStream; 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 * @param repo
* a {@link org.eclipse.jgit.lib.Repository} object. * 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}. * in which case the hook will use {@code System.out}.
*/ */
protected GitHook(Repository repo, PrintStream outputStream) { 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.repo = repo;
this.outputStream = outputStream; this.outputStream = outputStream;
this.errorStream = errorStream;
} }
/** /**
@ -147,6 +174,16 @@ protected PrintStream getOutputStream() {
return outputStream == null ? System.out : outputStream; 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. * Runs the hook, without performing any validity checks.
* *
@ -155,9 +192,11 @@ protected PrintStream getOutputStream() {
*/ */
protected void doRun() throws AbortedByHookException { protected void doRun() throws AbortedByHookException {
final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream(); final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream();
final TeeOutputStream stderrStream = new TeeOutputStream(errorByteArray,
getErrorStream());
PrintStream hookErrRedirect = null; PrintStream hookErrRedirect = null;
try { try {
hookErrRedirect = new PrintStream(errorByteArray, false, hookErrRedirect = new PrintStream(stderrStream, false,
UTF_8.name()); UTF_8.name());
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
// UTF-8 is guaranteed to be available // UTF-8 is guaranteed to be available

View File

@ -57,7 +57,8 @@
public class Hooks { 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 * @param repo
* a {@link org.eclipse.jgit.lib.Repository} object. * a {@link org.eclipse.jgit.lib.Repository} object.
@ -71,7 +72,25 @@ public static PreCommitHook preCommit(Repository repo,
} }
/** /**
* 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 * @param repo
* a {@link org.eclipse.jgit.lib.Repository} object. * a {@link org.eclipse.jgit.lib.Repository} object.
@ -86,7 +105,25 @@ public static PostCommitHook postCommit(Repository repo,
} }
/** /**
* 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 * @param repo
* a {@link org.eclipse.jgit.lib.Repository} object. * a {@link org.eclipse.jgit.lib.Repository} object.
@ -100,7 +137,25 @@ public static CommitMsgHook commitMsg(Repository repo,
} }
/** /**
* 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 * @param repo
* a {@link org.eclipse.jgit.lib.Repository} object. * a {@link org.eclipse.jgit.lib.Repository} object.
@ -127,4 +182,36 @@ public static PrePushHook prePush(Repository repo, PrintStream outputStream) {
} }
return new PrePushHook(repo, outputStream); 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);
}
} }

View File

@ -61,6 +61,9 @@ public class PostCommitHook extends GitHook<Void> {
/** /**
* Constructor for PostCommitHook * Constructor for PostCommitHook
* <p>
* This constructor will use the default error stream.
* </p>
* *
* @param repo * @param repo
* The repository * The repository
@ -72,6 +75,24 @@ protected PostCommitHook(Repository repo, PrintStream outputStream) {
super(repo, outputStream); 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} */ /** {@inheritDoc} */
@Override @Override
public Void call() throws IOException, AbortedByHookException { public Void call() throws IOException, AbortedByHookException {

View File

@ -61,6 +61,9 @@ public class PreCommitHook extends GitHook<Void> {
/** /**
* Constructor for PreCommitHook * Constructor for PreCommitHook
* <p>
* This constructor will use the default error stream.
* </p>
* *
* @param repo * @param repo
* The repository * The repository
@ -72,6 +75,24 @@ protected PreCommitHook(Repository repo, PrintStream outputStream) {
super(repo, outputStream); 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} */ /** {@inheritDoc} */
@Override @Override
public Void call() throws IOException, AbortedByHookException { public Void call() throws IOException, AbortedByHookException {

View File

@ -73,6 +73,9 @@ public class PrePushHook extends GitHook<String> {
/** /**
* Constructor for PrePushHook * Constructor for PrePushHook
* <p>
* This constructor will use the default error stream.
* </p>
* *
* @param repo * @param repo
* The repository * The repository
@ -84,6 +87,24 @@ protected PrePushHook(Repository repo, PrintStream outputStream) {
super(repo, outputStream); 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} */ /** {@inheritDoc} */
@Override @Override
protected String getStdinArgs() { protected String getStdinArgs() {

View File

@ -145,7 +145,7 @@ public ObjectLoader applySmudgeFilter(Repository db,
} }
/** /**
* Retrieve a pre-push hook to be applied. * Retrieve a pre-push hook to be applied using the default error stream.
* *
* @param repo * @param repo
* the {@link Repository} the hook is applied to. * the {@link Repository} the hook is applied to.
@ -158,6 +158,22 @@ public PrePushHook getPrePushHook(Repository repo,
return null; 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 * Retrieve an {@link LfsInstallCommand} which can be used to enable LFS
* support (if available) either per repository or for the user. * support (if available) either per repository or for the user.