Page MenuHomePhabricator

D7884.id17844.diff
No OneTemporary

D7884.id17844.diff

Index: scripts/repository/commit_hook.php
===================================================================
--- scripts/repository/commit_hook.php
+++ scripts/repository/commit_hook.php
@@ -88,6 +88,7 @@
}
$engine->setStdin($stdin);
+$engine->setOriginalArgv(array_slice($argv, 2));
$remote_address = getenv(DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS);
if (strlen($remote_address)) {
Index: src/applications/diffusion/engine/DiffusionCommitHookEngine.php
===================================================================
--- src/applications/diffusion/engine/DiffusionCommitHookEngine.php
+++ src/applications/diffusion/engine/DiffusionCommitHookEngine.php
@@ -19,6 +19,7 @@
private $viewer;
private $repository;
private $stdin;
+ private $originalArgv;
private $subversionTransaction;
private $subversionRepository;
private $remoteAddress;
@@ -84,6 +85,15 @@
return $this->stdin;
}
+ public function setOriginalArgv(array $original_argv) {
+ $this->originalArgv = $original_argv;
+ return $this;
+ }
+
+ public function getOriginalArgv() {
+ return $this->originalArgv;
+ }
+
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
@@ -141,7 +151,8 @@
$this->applyHeraldContentRules($content_updates, $all_updates);
- // TODO: Fire external hooks.
+ // Run custom scripts in `hook.d/` directories.
+ $this->applyCustomHooks($all_updates);
// If we make it this far, we're accepting these changes. Mark all the
// logs as accepted.
@@ -551,6 +562,74 @@
return $content_updates;
}
+/* -( Custom )------------------------------------------------------------- */
+
+ private function applyCustomHooks(array $updates) {
+ $args = $this->getOriginalArgv();
+ $stdin = $this->getStdin();
+ $console = PhutilConsole::getConsole();
+
+ $env = array(
+ 'PHABRICATOR_REPOSITORY' => $this->getRepository()->getCallsign(),
+ self::ENV_USER => $this->getViewer()->getUsername(),
+ self::ENV_REMOTE_PROTOCOL => $this->getRemoteProtocol(),
+ self::ENV_REMOTE_ADDRESS => $this->getRemoteAddress(),
+ );
+
+ $directories = $this->getRepository()->getHookDirectories();
+ foreach ($directories as $directory) {
+ $hooks = $this->getExecutablesInDirectory($directory);
+ sort($hooks);
+ foreach ($hooks as $hook) {
+ // NOTE: We're explicitly running the hooks in sequential order to
+ // make this more predictable.
+ $future = id(new ExecFuture('%s %Ls', $hook, $args))
+ ->setEnv($env, $wipe_process_env = false)
+ ->write($stdin);
+
+ list($err, $stdout, $stderr) = $future->resolve();
+ if (!$err) {
+ // This hook ran OK, but echo its output in case there was something
+ // informative.
+ $console->writeOut("%s", $stdout);
+ $console->writeErr("%s", $stderr);
+ continue;
+ }
+
+ // Mark everything as rejected by this hook.
+ foreach ($updates as $update) {
+ $update->setRejectCode(
+ PhabricatorRepositoryPushLog::REJECT_EXTERNAL);
+ $update->setRejectDetails(basename($hook));
+ }
+
+ throw new DiffusionCommitHookRejectException(
+ pht(
+ "This push was rejected by custom hook script '%s':\n\n%s%s",
+ basename($hook),
+ $stdout,
+ $stderr));
+ }
+ }
+ }
+
+ private function getExecutablesInDirectory($directory) {
+ $executables = array();
+
+ if (!Filesystem::pathExists($directory)) {
+ return $executables;
+ }
+
+ foreach (Filesystem::listDirectory($directory) as $path) {
+ $full_path = $directory.DIRECTORY_SEPARATOR.$path;
+ if (is_executable($full_path)) {
+ $executables[] = $full_path;
+ }
+ }
+
+ return $executables;
+ }
+
/* -( Mercurial )---------------------------------------------------------- */
Index: src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
===================================================================
--- src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
+++ src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
@@ -103,6 +103,10 @@
} else if ($is_hg) {
$this->installMercurialHook();
}
+
+ foreach ($repository->getHookDirectories() as $directory) {
+ $this->installHookDirectory($directory);
+ }
}
} catch (Exception $ex) {
@@ -173,6 +177,17 @@
Filesystem::changePermissions($path, 0755);
}
+ private function installHookDirectory($path) {
+ $readme = pht(
+ "To add custom hook scripts to this repository, add them to this ".
+ "directory.\n\nPhabricator will run any executables in this directory ".
+ "after running its own checks, as though they were normal hook ".
+ "scripts.");
+
+ Filesystem::createDirectory($path, 0755);
+ Filesystem::writeFile($path.'/README', $readme);
+ }
+
/* -( Pulling Git Working Copies )----------------------------------------- */
@@ -311,15 +326,15 @@
*/
private function installGitHook() {
$repository = $this->getRepository();
- $path = $repository->getLocalPath();
+ $root = $repository->getLocalPath();
if ($repository->isWorkingCopyBare()) {
- $path .= '/hooks/pre-receive';
+ $path = '/hooks/pre-receive';
} else {
- $path .= '/.git/hooks/pre-receive';
+ $path = '/.git/hooks/pre-receive';
}
- $this->installHook($path);
+ $this->installHook($root.$path);
}
@@ -438,14 +453,17 @@
execx('svnadmin create -- %s', $path);
}
+
/**
* @task svn
*/
private function installSubversionHook() {
$repository = $this->getRepository();
- $path = $repository->getLocalPath().'/hooks/pre-commit';
+ $root = $repository->getLocalPath();
+
+ $path = '/hooks/pre-commit';
- $this->installHook($path);
+ $this->installHook($root.$path);
}
Index: src/applications/repository/storage/PhabricatorRepository.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepository.php
+++ src/applications/repository/storage/PhabricatorRepository.php
@@ -945,6 +945,35 @@
}
}
+ public function getHookDirectories() {
+ $directories = array();
+ if (!$this->isHosted()) {
+ return $directories;
+ }
+
+ $root = $this->getLocalPath();
+
+ switch ($this->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ if ($this->isWorkingCopyBare()) {
+ $directories[] = $root.'/hooks/pre-receive-phabricator.d/';
+ } else {
+ $directories[] = $root.'/.git/hooks/pre-receive-phabricator.d/';
+ }
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+ $directories[] = $root.'/hooks/pre-commit-phabricator.d/';
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
+ // NOTE: We don't support custom Mercurial hooks for now because they're
+ // messy and we can't easily just drop a `hooks.d/` directory next to
+ // the hooks.
+ break;
+ }
+
+ return $directories;
+ }
+
public function canDestroyWorkingCopy() {
if ($this->isHosted()) {
// Never destroy hosted working copies.

File Metadata

Mime Type
text/plain
Expires
Thu, Mar 6, 10:40 AM (2 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7239562
Default Alt Text
D7884.id17844.diff (7 KB)

Event Timeline