diff options
author | Thomas Wolf <thomas.wolf@paranor.ch> | 2019-09-20 16:41:39 +0200 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2019-10-21 18:05:31 -0400 |
commit | 385b503ae8635ddb4233c9ae1f2d472a51609162 (patch) | |
tree | b828b829579027704eb0600bb289e737a8ca125c | |
parent | b138f169458e254cd5d8cb768b447e24273741b5 (diff) | |
download | jgit-385b503ae8635ddb4233c9ae1f2d472a51609162.tar.gz jgit-385b503ae8635ddb4233c9ae1f2d472a51609162.zip |
Support for core.hooksPath
Support the core.hooksPath git config. This can be an absolute or
relative path of a directory where to find git hooks; a relative
path is resolved relative to the directory the hook will run in.
Bug: 500266
Change-Id: I671999a6386a837e897c31718583c91d8035f3ba
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
8 files changed, 167 insertions, 52 deletions
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 e5fcbf9d7c..70a2dbbfe8 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 @@ -58,6 +58,8 @@ import org.eclipse.jgit.hooks.PostCommitHook; import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Assume; import org.junit.Test; @@ -221,6 +223,75 @@ public class HookTest extends RepositoryTestCase { } @Test + public void testRunHookHooksPathRelative() throws Exception { + assumeSupportedPlatform(); + + writeHookFile(PreCommitHook.NAME, + "#!/bin/sh\necho \"Wrong hook $1 $2\"\nread INPUT\necho $INPUT\n" + + "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\""); + writeHookFile("../../" + PreCommitHook.NAME, + "#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n" + + "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\""); + StoredConfig cfg = db.getConfig(); + cfg.load(); + cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_HOOKS_PATH, "."); + cfg.save(); + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream()) { + ProcessResult res = FS.DETECTED.runHookIfPresent(db, + PreCommitHook.NAME, new String[] { "arg1", "arg2" }, + new PrintStream(out), new PrintStream(err), "stdin"); + + assertEquals("unexpected hook output", + "test arg1 arg2\nstdin\n" + + db.getDirectory().getAbsolutePath() + '\n' + + db.getWorkTree().getAbsolutePath() + '\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 testRunHookHooksPathAbsolute() throws Exception { + assumeSupportedPlatform(); + + writeHookFile(PreCommitHook.NAME, + "#!/bin/sh\necho \"Wrong hook $1 $2\"\nread INPUT\necho $INPUT\n" + + "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\""); + writeHookFile("../../" + PreCommitHook.NAME, + "#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n" + + "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\""); + StoredConfig cfg = db.getConfig(); + cfg.load(); + cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_HOOKS_PATH, + db.getWorkTree().getAbsolutePath()); + cfg.save(); + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream()) { + ProcessResult res = FS.DETECTED.runHookIfPresent(db, + PreCommitHook.NAME, new String[] { "arg1", "arg2" }, + new PrintStream(out), new PrintStream(err), "stdin"); + + assertEquals("unexpected hook output", + "test arg1 arg2\nstdin\n" + + db.getDirectory().getAbsolutePath() + '\n' + + db.getWorkTree().getAbsolutePath() + '\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 testFailedPreCommitHookBlockCommit() 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 280d95a9f5..4607f9ee45 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -348,6 +348,7 @@ invalidFilter=Invalid filter: {0} invalidGitdirRef = Invalid .git reference in file ''{0}'' invalidGitModules=Invalid .gitmodules file invalidGitType=invalid git type: {0} +invalidHooksPath=Invalid git config core.hooksPath = {0} invalidId=Invalid id: {0} invalidId0=Invalid id invalidIdLength=Invalid id length {0}; should be {1} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index ad43e2ca83..6bb5bfc4c4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -162,9 +162,14 @@ abstract class GitHook<T> implements Callable<T> { } catch (UnsupportedEncodingException e) { // UTF-8 is guaranteed to be available } - ProcessResult result = FS.DETECTED.runHookIfPresent(getRepository(), - getHookName(), getParameters(), getOutputStream(), - hookErrRedirect, getStdinArgs()); + Repository repository = getRepository(); + FS fs = repository.getFS(); + if (fs == null) { + fs = FS.DETECTED; + } + ProcessResult result = fs.runHookIfPresent(repository, getHookName(), + getParameters(), getOutputStream(), hookErrRedirect, + getStdinArgs()); if (result.isExecutedWithError()) { throw new AbortedByHookException( new String(errorByteArray.toByteArray(), UTF_8), @@ -180,7 +185,11 @@ abstract class GitHook<T> implements Callable<T> { * @since 4.11 */ public boolean isNativeHookPresent() { - return FS.DETECTED.findHook(getRepository(), getHookName()) != null; + FS fs = getRepository().getFS(); + if (fs == null) { + fs = FS.DETECTED; + } + return fs.findHook(getRepository(), getHookName()) != null; } } 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 1aae4f4be5..2357e8ee99 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -409,6 +409,7 @@ public class JGitText extends TranslationBundle { /***/ public String invalidGitdirRef; /***/ public String invalidGitModules; /***/ public String invalidGitType; + /***/ public String invalidHooksPath; /***/ public String invalidId; /***/ public String invalidId0; /***/ public String invalidIdLength; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 8f40db626a..f2f1d5a48f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -149,6 +149,12 @@ public final class ConfigConstants { */ public static final String CONFIG_KEY_GPGSIGN = "gpgSign"; + /** + * The "hooksPath" key. + * @since 5.6 + */ + public static final String CONFIG_KEY_HOOKS_PATH = "hooksPath"; + /** The "algorithm" key */ public static final String CONFIG_KEY_ALGORITHM = "algorithm"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index 068e6450fe..68e19ce722 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -61,6 +61,7 @@ import java.nio.charset.Charset; import java.nio.file.AccessDeniedException; import java.nio.file.FileStore; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; @@ -98,6 +99,7 @@ import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.FileSnapshot; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; @@ -1730,20 +1732,18 @@ public abstract class FS { final String hookName, String[] args, PrintStream outRedirect, PrintStream errRedirect, String stdinArgs) throws JGitInternalException { - final File hookFile = findHook(repository, hookName); - if (hookFile == null) + File hookFile = findHook(repository, hookName); + if (hookFile == null || hookName == 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); + File runDirectory = getRunDirectory(repository, hookName); + if (runDirectory == null) { + return new ProcessResult(Status.NOT_PRESENT); + } + String cmd = hookFile.getAbsolutePath(); ProcessBuilder hookProcess = runInShell(cmd, args); - hookProcess.directory(runDirectory); + hookProcess.directory(runDirectory.getAbsoluteFile()); Map<String, String> environment = hookProcess.environment(); environment.put(Constants.GIT_DIR_KEY, repository.getDirectory().getAbsolutePath()); @@ -1778,12 +1778,71 @@ public abstract class FS { * @since 4.0 */ public File findHook(Repository repository, String hookName) { - File gitDir = repository.getDirectory(); - if (gitDir == null) + if (hookName == null) { + return null; + } + File hookDir = getHooksDirectory(repository); + if (hookDir == null) { return null; - final File hookFile = new File(new File(gitDir, - Constants.HOOKS), hookName); - return hookFile.isFile() ? hookFile : null; + } + File hookFile = new File(hookDir, hookName); + if (hookFile.isAbsolute()) { + if (!hookFile.exists() || FS.DETECTED.supportsExecute() + && !FS.DETECTED.canExecute(hookFile)) { + return null; + } + } else { + try { + File runDirectory = getRunDirectory(repository, hookName); + if (runDirectory == null) { + return null; + } + Path hookPath = runDirectory.getAbsoluteFile().toPath() + .resolve(hookFile.toPath()); + FS fs = repository.getFS(); + if (fs == null) { + fs = FS.DETECTED; + } + if (!Files.exists(hookPath) || fs.supportsExecute() + && !fs.canExecute(hookPath.toFile())) { + return null; + } + hookFile = hookPath.toFile(); + } catch (InvalidPathException e) { + LOG.warn(MessageFormat.format(JGitText.get().invalidHooksPath, + hookFile)); + return null; + } + } + return hookFile; + } + + private File getRunDirectory(Repository repository, + @NonNull String hookName) { + if (repository.isBare()) { + return repository.getDirectory(); + } + switch (hookName) { + case "pre-receive": //$NON-NLS-1$ + case "update": //$NON-NLS-1$ + case "post-receive": //$NON-NLS-1$ + case "post-update": //$NON-NLS-1$ + case "push-to-checkout": //$NON-NLS-1$ + return repository.getDirectory(); + default: + return repository.getWorkTree(); + } + } + + private File getHooksDirectory(Repository repository) { + Config config = repository.getConfig(); + String hooksDir = config.getString(ConfigConstants.CONFIG_CORE_SECTION, + null, ConfigConstants.CONFIG_KEY_HOOKS_PATH); + if (hooksDir != null) { + return new File(hooksDir); + } + File dir = repository.getDirectory(); + return dir == null ? null : new File(dir, Constants.HOOKS); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java index 6a1eef2d66..7c170ac4eb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java @@ -74,7 +74,6 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.slf4j.Logger; @@ -311,20 +310,6 @@ public class FS_POSIX extends FS { /** {@inheritDoc} */ @Override - public File findHook(Repository repository, String hookName) { - final File gitdir = repository.getDirectory(); - if (gitdir == null) { - return null; - } - final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS) - .resolve(hookName); - if (Files.isExecutable(hookPath)) - return hookPath.toFile(); - return null; - } - - /** {@inheritDoc} */ - @Override public boolean supportsAtomicCreateNewFile() { if (supportsAtomicFileCreation == AtomicFileCreation.UNDEFINED) { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java index 9a163e8e38..4efa9888f7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java @@ -47,8 +47,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; import java.io.PrintStream; -import java.nio.file.Files; -import java.nio.file.Path; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; @@ -57,7 +55,6 @@ import java.util.List; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.CommandFailedException; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -175,18 +172,4 @@ public class FS_Win32_Cygwin extends FS_Win32 { return internalRunHookIfPresent(repository, hookName, args, outRedirect, errRedirect, stdinArgs); } - - /** {@inheritDoc} */ - @Override - public File findHook(Repository repository, String hookName) { - final File gitdir = repository.getDirectory(); - if (gitdir == null) { - return null; - } - final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS) - .resolve(hookName); - if (Files.isExecutable(hookPath)) - return hookPath.toFile(); - return null; - } } |