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>
This commit is contained in:
Thomas Wolf 2019-09-20 16:41:39 +02:00 committed by Matthias Sohn
parent b138f16945
commit 385b503ae8
8 changed files with 167 additions and 52 deletions

View File

@ -58,6 +58,8 @@
import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.hooks.PreCommitHook;
import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase; 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.eclipse.jgit.revwalk.RevCommit;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Test; import org.junit.Test;
@ -220,6 +222,75 @@ public void testRunHook() throws Exception {
res.getStatus()); res.getStatus());
} }
@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 @Test
public void testFailedPreCommitHookBlockCommit() throws Exception { public void testFailedPreCommitHookBlockCommit() throws Exception {
assumeSupportedPlatform(); assumeSupportedPlatform();

View File

@ -348,6 +348,7 @@ invalidFilter=Invalid filter: {0}
invalidGitdirRef = Invalid .git reference in file ''{0}'' invalidGitdirRef = Invalid .git reference in file ''{0}''
invalidGitModules=Invalid .gitmodules file invalidGitModules=Invalid .gitmodules file
invalidGitType=invalid git type: {0} invalidGitType=invalid git type: {0}
invalidHooksPath=Invalid git config core.hooksPath = {0}
invalidId=Invalid id: {0} invalidId=Invalid id: {0}
invalidId0=Invalid id invalidId0=Invalid id
invalidIdLength=Invalid id length {0}; should be {1} invalidIdLength=Invalid id length {0}; should be {1}

View File

@ -162,9 +162,14 @@ protected void doRun() throws AbortedByHookException {
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
// UTF-8 is guaranteed to be available // UTF-8 is guaranteed to be available
} }
ProcessResult result = FS.DETECTED.runHookIfPresent(getRepository(), Repository repository = getRepository();
getHookName(), getParameters(), getOutputStream(), FS fs = repository.getFS();
hookErrRedirect, getStdinArgs()); if (fs == null) {
fs = FS.DETECTED;
}
ProcessResult result = fs.runHookIfPresent(repository, getHookName(),
getParameters(), getOutputStream(), hookErrRedirect,
getStdinArgs());
if (result.isExecutedWithError()) { if (result.isExecutedWithError()) {
throw new AbortedByHookException( throw new AbortedByHookException(
new String(errorByteArray.toByteArray(), UTF_8), new String(errorByteArray.toByteArray(), UTF_8),
@ -180,7 +185,11 @@ protected void doRun() throws AbortedByHookException {
* @since 4.11 * @since 4.11
*/ */
public boolean isNativeHookPresent() { 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;
} }
} }

View File

@ -409,6 +409,7 @@ public static JGitText get() {
/***/ public String invalidGitdirRef; /***/ public String invalidGitdirRef;
/***/ public String invalidGitModules; /***/ public String invalidGitModules;
/***/ public String invalidGitType; /***/ public String invalidGitType;
/***/ public String invalidHooksPath;
/***/ public String invalidId; /***/ public String invalidId;
/***/ public String invalidId0; /***/ public String invalidId0;
/***/ public String invalidIdLength; /***/ public String invalidIdLength;

View File

@ -149,6 +149,12 @@ public final class ConfigConstants {
*/ */
public static final String CONFIG_KEY_GPGSIGN = "gpgSign"; 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 */ /** The "algorithm" key */
public static final String CONFIG_KEY_ALGORITHM = "algorithm"; public static final String CONFIG_KEY_ALGORITHM = "algorithm";

View File

@ -61,6 +61,7 @@
import java.nio.file.AccessDeniedException; import java.nio.file.AccessDeniedException;
import java.nio.file.FileStore; import java.nio.file.FileStore;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
@ -98,6 +99,7 @@
import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.FileSnapshot; 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.ConfigConstants;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
@ -1730,20 +1732,18 @@ protected ProcessResult internalRunHookIfPresent(Repository repository,
final String hookName, String[] args, PrintStream outRedirect, final String hookName, String[] args, PrintStream outRedirect,
PrintStream errRedirect, String stdinArgs) PrintStream errRedirect, String stdinArgs)
throws JGitInternalException { throws JGitInternalException {
final File hookFile = findHook(repository, hookName); File hookFile = findHook(repository, hookName);
if (hookFile == null) if (hookFile == null || hookName == null) {
return new ProcessResult(Status.NOT_PRESENT); return new ProcessResult(Status.NOT_PRESENT);
}
final String hookPath = hookFile.getAbsolutePath(); File runDirectory = getRunDirectory(repository, hookName);
final File runDirectory; if (runDirectory == null) {
if (repository.isBare()) return new ProcessResult(Status.NOT_PRESENT);
runDirectory = repository.getDirectory(); }
else String cmd = hookFile.getAbsolutePath();
runDirectory = repository.getWorkTree();
final String cmd = relativize(runDirectory.getAbsolutePath(),
hookPath);
ProcessBuilder hookProcess = runInShell(cmd, args); ProcessBuilder hookProcess = runInShell(cmd, args);
hookProcess.directory(runDirectory); hookProcess.directory(runDirectory.getAbsoluteFile());
Map<String, String> environment = hookProcess.environment(); Map<String, String> environment = hookProcess.environment();
environment.put(Constants.GIT_DIR_KEY, environment.put(Constants.GIT_DIR_KEY,
repository.getDirectory().getAbsolutePath()); repository.getDirectory().getAbsolutePath());
@ -1778,12 +1778,71 @@ protected ProcessResult internalRunHookIfPresent(Repository repository,
* @since 4.0 * @since 4.0
*/ */
public File findHook(Repository repository, String hookName) { public File findHook(Repository repository, String hookName) {
File gitDir = repository.getDirectory(); if (hookName == null) {
if (gitDir == null)
return null; return null;
final File hookFile = new File(new File(gitDir, }
Constants.HOOKS), hookName); File hookDir = getHooksDirectory(repository);
return hookFile.isFile() ? hookFile : null; if (hookDir == null) {
return 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);
} }
/** /**

View File

@ -74,7 +74,6 @@
import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.errors.CommandFailedException;
import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.StoredConfig;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -309,20 +308,6 @@ public String normalize(String name) {
return FileUtils.normalize(name); return FileUtils.normalize(name);
} }
/** {@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} */ /** {@inheritDoc} */
@Override @Override
public boolean supportsAtomicCreateNewFile() { public boolean supportsAtomicCreateNewFile() {

View File

@ -47,8 +47,6 @@
import java.io.File; import java.io.File;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.ArrayList; import java.util.ArrayList;
@ -57,7 +55,6 @@
import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.errors.CommandFailedException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -175,18 +172,4 @@ public ProcessResult runHookIfPresent(Repository repository, String hookName,
return internalRunHookIfPresent(repository, hookName, args, outRedirect, return internalRunHookIfPresent(repository, hookName, args, outRedirect,
errRedirect, stdinArgs); 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;
}
} }