* 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
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 |
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; |
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 |
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; | ||||
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(); | ||||
} | } | ||||
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(); | ||||
} | } | ||||
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; | ||||
} | } | ||||
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 | ||||
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> |
*/ | */ | ||||
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 | |||||
* The error details. | |||||
* @param hookStdErr | |||||
* 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; | ||||
} | } | ||||
return returnCode; | 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; | |||||
} | } | ||||
} | } |
/** | /** | ||||
* Constructor for CommitMsgHook | * Constructor for CommitMsgHook | ||||
* <p> | |||||
* This constructor will use the default error stream. | |||||
* </p> | |||||
* | * | ||||
* @param repo | * @param repo | ||||
* The repository | * The repository | ||||
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 { |
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; | ||||
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. | ||||
* 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; | |||||
} | } | ||||
/** | /** | ||||
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. | ||||
* | * | ||||
*/ | */ | ||||
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 |
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. | ||||
} | } | ||||
/** | /** | ||||
* 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. | ||||
} | } | ||||
/** | /** | ||||
* 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. | ||||
} | } | ||||
/** | /** | ||||
* 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. | ||||
} | } | ||||
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); | |||||
} | |||||
} | } |
/** | /** | ||||
* Constructor for PostCommitHook | * Constructor for PostCommitHook | ||||
* <p> | |||||
* This constructor will use the default error stream. | |||||
* </p> | |||||
* | * | ||||
* @param repo | * @param repo | ||||
* The repository | * The repository | ||||
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 { |
/** | /** | ||||
* Constructor for PreCommitHook | * Constructor for PreCommitHook | ||||
* <p> | |||||
* This constructor will use the default error stream. | |||||
* </p> | |||||
* | * | ||||
* @param repo | * @param repo | ||||
* The repository | * The repository | ||||
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 { |
/** | /** | ||||
* Constructor for PrePushHook | * Constructor for PrePushHook | ||||
* <p> | |||||
* This constructor will use the default error stream. | |||||
* </p> | |||||
* | * | ||||
* @param repo | * @param repo | ||||
* The repository | * The repository | ||||
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() { |
} | } | ||||
/** | /** | ||||
* 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. | ||||
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. |