diff --git a/bin/commit-hook b/bin/commit-hook new file mode 120000 index 0000000000..9eda12cfb7 --- /dev/null +++ b/bin/commit-hook @@ -0,0 +1 @@ +../scripts/repository/commit_hook.php \ No newline at end of file diff --git a/scripts/repository/commit_hook.php b/scripts/repository/commit_hook.php new file mode 100755 index 0000000000..fbd93b1797 --- /dev/null +++ b/scripts/repository/commit_hook.php @@ -0,0 +1,56 @@ +#!/usr/bin/env php +setViewer(PhabricatorUser::getOmnipotentUser()) + ->withUsernames(array($username)) + ->executeOne(); +if (!$user) { + throw new Exception(pht('No such user "%s"!', $username)); +} + +if ($argc < 2) { + throw new Exception(pht('usage: commit-hook ')); +} + +$repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($user) + ->withCallsigns(array($argv[1])) + ->requireCapabilities( + array( + // This capability check is redundant, but can't hurt. + PhabricatorPolicyCapability::CAN_VIEW, + DiffusionCapabilityPush::CAPABILITY, + )) + ->executeOne(); + +if (!$repository) { + throw new Exception(pht('No such repository "%s"!', $callsign)); +} + +if (!$repository->isHosted()) { + // This should be redundant too, but double check just in case. + throw new Exception(pht('Repository "%s" is not hosted!', $callsign)); +} + +$stdin = @file_get_contents('php://stdin'); +if ($stdin === false) { + throw new Exception(pht('Failed to read stdin!')); +} + +$engine = id(new DiffusionCommitHookEngine()) + ->setViewer($user) + ->setRepository($repository) + ->setStdin($stdin); + +$err = $engine->execute(); + +exit($err); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5dddf8cc9a..88ab3d46de 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -474,6 +474,7 @@ phutil_register_library_map(array( 'DiffusionCommitChangeTableView' => 'applications/diffusion/view/DiffusionCommitChangeTableView.php', 'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php', 'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php', + 'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php', 'DiffusionCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionCommitParentsQuery.php', 'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php', 'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php', @@ -2797,6 +2798,7 @@ phutil_register_library_map(array( 'DiffusionCommitChangeTableView' => 'DiffusionView', 'DiffusionCommitController' => 'DiffusionController', 'DiffusionCommitEditController' => 'DiffusionController', + 'DiffusionCommitHookEngine' => 'Phobject', 'DiffusionCommitParentsQuery' => 'DiffusionQuery', 'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DiffusionCommitTagsController' => 'DiffusionController', diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 7bb19e47ce..de7c2e1eb2 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -318,6 +318,7 @@ final class DiffusionServeController extends DiffusionController { 'PATH_INFO' => $request_path, 'REMOTE_USER' => $viewer->getUsername(), + 'PHABRICATOR_USER' => $viewer->getUsername(), // TODO: Set these correctly. // GIT_COMMITTER_NAME diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php new file mode 100644 index 0000000000..ca24d0271b --- /dev/null +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -0,0 +1,76 @@ +stdin = $stdin; + return $this; + } + + public function getStdin() { + return $this->stdin; + } + + public function setRepository(PhabricatorRepository $repository) { + $this->repository = $repository; + return $this; + } + + public function getRepository() { + return $this->repository; + } + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function execute() { + $type = $this->getRepository()->getVersionControlSystem(); + switch ($type) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $err = $this->executeGitHook(); + break; + default: + throw new Exception(pht('Unsupported repository type "%s"!', $type)); + } + + return $err; + } + + private function executeGitHook() { + $updates = $this->parseGitUpdates($this->getStdin()); + + // TODO: Do useful things. + + return 0; + } + + private function parseGitUpdates($stdin) { + $updates = array(); + + $lines = phutil_split_lines($stdin, $retain_endings = false); + foreach ($lines as $line) { + $parts = explode(' ', $line, 3); + if (count($parts) != 3) { + throw new Exception(pht('Expected "old new ref", got "%s".', $line)); + } + $updates[] = array( + 'old' => $parts[0], + 'new' => $parts[1], + 'ref' => $parts[2], + ); + } + + return $updates; + } + +} diff --git a/src/applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php index 4c379c4638..d2c59dee79 100644 --- a/src/applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php @@ -25,7 +25,8 @@ final class DiffusionSSHGitReceivePackWorkflow $command = csprintf('git-receive-pack %s', $repository->getLocalPath()); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); - $future = new ExecFuture('%C', $command); + $future = id(new ExecFuture('%C', $command)) + ->setEnv($this->getEnvironment()); $err = $this->newPassthruCommand() ->setIOChannel($this->getIOChannel()) diff --git a/src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php index 0ccfb95508..242c9d0c7f 100644 --- a/src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php @@ -22,7 +22,8 @@ final class DiffusionSSHGitUploadPackWorkflow $command = csprintf('git-upload-pack -- %s', $repository->getLocalPath()); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); - $future = new ExecFuture('%C', $command); + $future = id(new ExecFuture('%C', $command)) + ->setEnv($this->getEnvironment()); $err = $this->newPassthruCommand() ->setIOChannel($this->getIOChannel()) diff --git a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php index 954e01fd1a..be1a1d55fd 100644 --- a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php @@ -17,6 +17,12 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow { return $this->args; } + public function getEnvironment() { + return array( + 'PHABRICATOR_USER' => $this->getUser()->getUsername(), + ); + } + abstract protected function executeRepositoryOperations(); protected function writeError($message) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index a7ef8b7cd1..1ab6818a46 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -83,11 +83,15 @@ final class PhabricatorRepositoryPullEngine } } else { if ($repository->isHosted()) { - $this->logPull( - pht( - "Repository '%s' is hosted, so Phabricator does not pull ". - "updates for it.", - $callsign)); + if ($is_git) { + $this->installGitHook(); + } else { + $this->logPull( + pht( + "Repository '%s' is hosted, so Phabricator does not pull ". + "updates for it.", + $callsign)); + } } else { $this->logPull( pht( @@ -146,6 +150,22 @@ final class PhabricatorRepositoryPullEngine )); } + private function installHook($path) { + $this->log('%s', pht('Installing commit hook to "%s"...', $path)); + + $repository = $this->getRepository(); + $callsign = $repository->getCallsign(); + + $root = dirname(phutil_get_library_root('phabricator')); + $bin = $root.'/bin/commit-hook'; + $cmd = csprintf('exec -- %s %s', $bin, $callsign); + + $hook = "#!/bin/sh\n{$cmd}\n"; + + Filesystem::writeFile($path, $hook); + Filesystem::changePermissions($path, 0755); + } + /* -( Pulling Git Working Copies )----------------------------------------- */ @@ -279,6 +299,23 @@ final class PhabricatorRepositoryPullEngine } + /** + * @task git + */ + private function installGitHook() { + $repository = $this->getRepository(); + $path = $repository->getLocalPath(); + + if ($repository->isWorkingCopyBare()) { + $path .= 'hooks/pre-receive'; + } else { + $path .= '.git/hooks/pre-receive'; + } + + $this->installHook($path); + } + + /* -( Pulling Mercurial Working Copies )----------------------------------- */ @@ -357,4 +394,5 @@ final class PhabricatorRepositoryPullEngine execx('svnadmin create -- %s', $path); } + }