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.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;
@ -220,6 +222,75 @@ public void testRunHook() throws Exception {
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
public void testFailedPreCommitHookBlockCommit() throws Exception {
assumeSupportedPlatform();

View File

@ -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}

View File

@ -162,9 +162,14 @@ protected void doRun() throws AbortedByHookException {
} 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 @@ protected void doRun() throws AbortedByHookException {
* @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;
}
}

View File

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

View File

@ -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";

View File

@ -61,6 +61,7 @@
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.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 @@ protected ProcessResult internalRunHookIfPresent(Repository repository,
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 @@ protected ProcessResult internalRunHookIfPresent(Repository repository,
* @since 4.0
*/
public File findHook(Repository repository, String hookName) {
File gitDir = repository.getDirectory();
if (gitDir == null)
if (hookName == null) {
return null;
final File hookFile = new File(new File(gitDir,
Constants.HOOKS), hookName);
return hookFile.isFile() ? hookFile : null;
}
File hookDir = getHooksDirectory(repository);
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.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;
@ -309,20 +308,6 @@ public String normalize(String 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} */
@Override
public boolean supportsAtomicCreateNewFile() {

View File

@ -47,8 +47,6 @@
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 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 ProcessResult runHookIfPresent(Repository repository, String hookName,
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;
}
}