Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15294727
D7884.id17844.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
D7884.id17844.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D7884: Add 'hook.d/' directories to SVN and Git repositories for custom hooks
Attached
Detach File
Event Timeline
Log In to Comment