From 6aed51e3cea6f7114b42bcbc4f66abb48e6ae490 Mon Sep 17 00:00:00 2001
From: Laurent Goubet
+ * The hook's standard output and error streams will be redirected to
+ *
+ *
+ * @param pool
+ * the pool to shutdown
+ * @return
+ * Typically used to empty processes' standard output and error, preventing
+ * them to choke.
+ *
+ * Note that a {@link StreamGobbler} will never close either of its
+ * streams.
+ *
+ * For example, if this is called with the two following paths :
+ *
+ * other
should be
+ * relativized.
+ * @param other
+ * The path that will be made relative to base
.
+ * @return A relative path that, when resolved against base
,
+ * will yield the original other
.
+ * @see FileUtils#relativize(String, String)
+ * @since 3.7
+ */
+ public String relativize(String base, String other) {
+ return FileUtils.relativize(base, other);
+ }
+
+ /**
+ * Checks whether the given hook is defined for the given repository, then
+ * runs it with the given arguments.
+ * System.out
and System.err
respectively. The
+ * hook will have no stdin.
+ * null
,
+ * but can be an empty array.
+ * @return The ProcessResult describing this hook's execution.
+ * @throws JGitInternalException
+ * if we fail to run the hook somehow. Causes may include an
+ * interrupted process or I/O errors.
+ * @since 3.7
+ */
+ public ProcessResult runIfPresent(Repository repository, final Hook hook,
+ String[] args) throws JGitInternalException {
+ return runIfPresent(repository, hook, args, System.out, System.err,
+ null);
+ }
+
+ /**
+ * Checks whether the given hook is defined for the given repository, then
+ * runs it with the given arguments.
+ *
+ * @param repository
+ * The repository for which a hook should be run.
+ * @param hook
+ * The hook to be executed.
+ * @param args
+ * Arguments to pass to this hook. Cannot be null
,
+ * but can be an empty array.
+ * @param outRedirect
+ * A print stream on which to redirect the hook's stdout. Can be
+ * null
, in which case the hook's standard output
+ * will be lost.
+ * @param errRedirect
+ * A print stream on which to redirect the hook's stderr. Can be
+ * null
, in which case the hook's standard error
+ * will be lost.
+ * @param stdinArgs
+ * A string to pass on to the standard input of the hook. May be
+ * null
.
+ * @return The ProcessResult describing this hook's execution.
+ * @throws JGitInternalException
+ * if we fail to run the hook somehow. Causes may include an
+ * interrupted process or I/O errors.
+ * @since 3.7
+ */
+ public ProcessResult runIfPresent(Repository repository, final Hook hook,
+ String[] args, PrintStream outRedirect, PrintStream errRedirect,
+ String stdinArgs) throws JGitInternalException {
+ return new ProcessResult(Status.NOT_SUPPORTED);
+ }
+
+ /**
+ * See
+ * {@link #runIfPresent(Repository, Hook, String[], PrintStream, PrintStream, String)}
+ * . Should only be called by FS supporting shell scripts execution.
+ *
+ * @param repository
+ * The repository for which a hook should be run.
+ * @param hook
+ * The hook to be executed.
+ * @param args
+ * Arguments to pass to this hook. Cannot be null
,
+ * but can be an empty array.
+ * @param outRedirect
+ * A print stream on which to redirect the hook's stdout. Can be
+ * null
, in which case the hook's standard output
+ * will be lost.
+ * @param errRedirect
+ * A print stream on which to redirect the hook's stderr. Can be
+ * null
, in which case the hook's standard error
+ * will be lost.
+ * @param stdinArgs
+ * A string to pass on to the standard input of the hook. May be
+ * null
.
+ * @return The ProcessResult describing this hook's execution.
+ * @throws JGitInternalException
+ * if we fail to run the hook somehow. Causes may include an
+ * interrupted process or I/O errors.
+ * @since 3.7
+ */
+ protected ProcessResult internalRunIfPresent(Repository repository,
+ final Hook hook, String[] args, PrintStream outRedirect,
+ PrintStream errRedirect, String stdinArgs)
+ throws JGitInternalException {
+ final File hookFile = findHook(repository, hook);
+ if (hookFile == null)
+ return new ProcessResult(Status.NOT_PRESENT);
+
+ final String hookPath = hookFile.getAbsolutePath();
+ final File runDirectory;
+ if (repository.isBare())
+ runDirectory = repository.getDirectory();
+ else
+ runDirectory = repository.getWorkTree();
+ final String cmd = relativize(runDirectory.getAbsolutePath(),
+ hookPath);
+ ProcessBuilder hookProcess = runInShell(cmd, args);
+ hookProcess.directory(runDirectory);
+ try {
+ return new ProcessResult(runProcess(hookProcess, outRedirect,
+ errRedirect, stdinArgs), Status.OK);
+ } catch (IOException e) {
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().exceptionCaughtDuringExecutionOfHook,
+ hook.getName()), e);
+ } catch (InterruptedException e) {
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().exceptionHookExecutionInterrupted,
+ hook.getName()), e);
+ }
+ }
+
+
+ /**
+ * Tries to find a hook matching the given one in the given repository.
+ *
+ * @param repository
+ * The repository within which to find a hook.
+ * @param hook
+ * The hook we're trying to find.
+ * @return The {@link File} containing this particular hook if it exists in
+ * the given repository, null
otherwise.
+ * @since 3.7
+ */
+ public File findHook(Repository repository, final Hook hook) {
+ final File hookFile = new File(new File(repository.getDirectory(),
+ Constants.HOOKS), hook.getName());
+ return hookFile.isFile() ? hookFile : null;
+ }
+
+ /**
+ * Runs the given process until termination, clearing its stdout and stderr
+ * streams on-the-fly.
+ *
+ * @param hookProcessBuilder
+ * The process builder configured for this hook.
+ * @param outRedirect
+ * A print stream on which to redirect the hook's stdout. Can be
+ * null
, in which case the hook's standard output
+ * will be lost.
+ * @param errRedirect
+ * A print stream on which to redirect the hook's stderr. Can be
+ * null
, in which case the hook's standard error
+ * will be lost.
+ * @param stdinArgs
+ * A string to pass on to the standard input of the hook. Can be
+ * null
.
+ * @return the exit value of this hook.
+ * @throws IOException
+ * if an I/O error occurs while executing this hook.
+ * @throws InterruptedException
+ * if the current thread is interrupted while waiting for the
+ * process to end.
+ * @since 3.7
+ */
+ protected int runProcess(ProcessBuilder hookProcessBuilder,
+ OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
+ throws IOException, InterruptedException {
+ final ExecutorService executor = Executors.newFixedThreadPool(2);
+ Process process = null;
+ // We'll record the first I/O exception that occurs, but keep on trying
+ // to dispose of our open streams and file handles
+ IOException ioException = null;
+ try {
+ process = hookProcessBuilder.start();
+ final Callabletrue
if the pool has been properly shutdown,
+ * false
otherwise.
+ */
+ private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
+ boolean hasShutdown = true;
+ pool.shutdown(); // Disable new tasks from being submitted
+ try {
+ // Wait a while for existing tasks to terminate
+ if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
+ pool.shutdownNow(); // Cancel currently executing tasks
+ // Wait a while for tasks to respond to being canceled
+ if (!pool.awaitTermination(5, TimeUnit.SECONDS))
+ hasShutdown = false;
+ }
+ } catch (InterruptedException ie) {
+ // (Re-)Cancel if current thread also interrupted
+ pool.shutdownNow();
+ // Preserve interrupt status
+ Thread.currentThread().interrupt();
+ hasShutdown = false;
+ }
+ return hasShutdown;
+ }
+
/**
* Initialize a ProcesssBuilder to run a command using the system shell.
*
@@ -802,4 +1097,50 @@ public abstract class FS {
public String normalize(String name) {
return name;
}
+
+ /**
+ * This runnable will consume an input stream's content into an output
+ * stream as soon as it gets available.
+ *
+ *
+ *
+ * This will return "..\\another_project\\pom.xml".
+ * base = "c:\\Users\\jdoe\\eclipse\\git\\project"
+ * other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"
+ *
+ * This method uses {@link File#separator} to split the paths into segments. + *
+ *
+ * Note that this will return the empty String if base
+ * and other
are equal.
+ *
other
should be
+ * relativized. This will be assumed to denote the path to a
+ * folder and not a file.
+ * @param other
+ * The path that will be made relative to base
.
+ * @return A relative path that, when resolved against base
,
+ * will yield the original other
.
+ * @since 3.7
+ */
+ public static String relativize(String base, String other) {
+ if (base.equals(other))
+ return ""; //$NON-NLS-1$
+
+ final boolean ignoreCase = !FS.DETECTED.isCaseSensitive();
+ final String[] baseSegments = base.split(Pattern.quote(File.separator));
+ final String[] otherSegments = other.split(Pattern
+ .quote(File.separator));
+
+ int commonPrefix = 0;
+ while (commonPrefix < baseSegments.length
+ && commonPrefix < otherSegments.length) {
+ if (ignoreCase
+ && baseSegments[commonPrefix]
+ .equalsIgnoreCase(otherSegments[commonPrefix]))
+ commonPrefix++;
+ else if (!ignoreCase
+ && baseSegments[commonPrefix]
+ .equals(otherSegments[commonPrefix]))
+ commonPrefix++;
+ else
+ break;
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ for (int i = commonPrefix; i < baseSegments.length; i++)
+ builder.append("..").append(File.separator); //$NON-NLS-1$
+ for (int i = commonPrefix; i < otherSegments.length; i++) {
+ builder.append(otherSegments[i]);
+ if (i < otherSegments.length - 1)
+ builder.append(File.separator);
+ }
+ return builder.toString();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java
new file mode 100644
index 000000000..c24c9a3d0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Hook.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2014 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.util;
+
+/**
+ * An enum describing the different hooks a user can implement to customize his
+ * repositories.
+ *
+ * @since 3.7
+ */
+public enum Hook {
+ /**
+ * Literal for the "pre-commit" git hook.
+ * + * This hook is invoked by git commit, and can be bypassed with the + * "no-verify" option. It takes no parameter, and is invoked before + * obtaining the proposed commit log message and making a commit. + *
+ *+ * A non-zero exit code from the called hook means that the commit should be + * aborted. + *
+ */ + PRE_COMMIT("pre-commit"), //$NON-NLS-1$ + + /** + * Literal for the "prepare-commit-msg" git hook. + *+ * This hook is invoked by git commit right after preparing the default + * message, and before any editing possibility is displayed to the user. + *
+ *+ * A non-zero exit code from the called hook means that the commit should be + * aborted. + *
+ */ + PREPARE_COMMIT_MSG("prepare-commit-msg"), //$NON-NLS-1$ + + /** + * Literal for the "commit-msg" git hook. + *+ * This hook is invoked by git commit, and can be bypassed with the + * "no-verify" option. Its single parameter is the path to the file + * containing the prepared commit message (typically + * "<gitdir>/COMMIT-EDITMSG"). + *
+ *+ * A non-zero exit code from the called hook means that the commit should be + * aborted. + *
+ */ + COMMIT_MSG("commit-msg"), //$NON-NLS-1$ + + /** + * Literal for the "post-commit" git hook. + *+ * This hook is invoked by git commit. It takes no parameter and is invoked + * after a commit has been made. + *
+ *+ * The exit code of this hook has no significance. + *
+ */ + POST_COMMIT("post-commit"), //$NON-NLS-1$ + + /** + * Literal for the "post-rewrite" git hook. + *
+ * This hook is invoked after commands that rewrite commits (currently, only
+ * "git rebase" and "git commit --amend"). It a single argument denoting the
+ * source of the call (one of rebase
or amend
). It
+ * then accepts a list of rewritten commits through stdin, in the form
+ * <old SHA-1> <new SHA-1>LF
.
+ *
+ * The exit code of this hook has no significance. + *
+ */ + POST_REWRITE("post-rewrite"), //$NON-NLS-1$ + + /** + * Literal for the "pre-rebase" git hook. + *+ *
+ * This hook is invoked right before the rebase operation runs. It accepts + * up to two parameters, the first being the upstream from which the branch + * to rebase has been forked. If the tip of the series of commits to rebase + * is HEAD, the other parameter is unset. Otherwise, that tip is passed as + * the second parameter of the script. + *+ * A non-zero exit code from the called hook means that the rebase should be + * aborted. + *
+ */ + PRE_REBASE("pre-rebase"); //$NON-NLS-1$ + + private final String name; + + private Hook(String name) { + this.name = name; + } + + /** + * @return The name of this hook. + */ + public String getName() { + return name; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java new file mode 100644 index 000000000..f56bb1577 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2014 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.util; + +/** + * Describes the result of running an external process. + * + * @since 3.7 + */ +public class ProcessResult { + /** + * Status of a process' execution. + */ + public static enum Status { + /** + * The script was found and launched properly. It may still have exited + * with a non-zero {@link #exitCode}. + */ + OK, + + /** The script was not found on disk and thus could not be launched. */ + NOT_PRESENT, + + /** + * The script was found but could not be launched since it was not + * supported by the current {@link FS}. + */ + NOT_SUPPORTED; + } + + /** The exit code of the process. */ + private final int exitCode; + + /** Status of the process' execution. */ + private final Status status; + + /** + * Instantiates a process result with the given status and an exit code of + *-1
.
+ *
+ * @param status
+ * Status describing the execution of the external process.
+ */
+ public ProcessResult(Status status) {
+ this(-1, status);
+ }
+
+ /**
+ * @param exitCode
+ * Exit code of the process.
+ * @param status
+ * Status describing the execution of the external process.
+ */
+ public ProcessResult(int exitCode, Status status) {
+ this.exitCode = exitCode;
+ this.status = status;
+ }
+
+ /**
+ * @return The exit code of the process.
+ */
+ public int getExitCode() {
+ return exitCode;
+ }
+
+ /**
+ * @return The status of the process' execution.
+ */
+ public Status getStatus() {
+ return status;
+ }
+}