mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-21 22:32:41 +01:00
Install pre-commit hooks in Git repositories
Summary: Ref T4189. T4189 describes most of the intent here: - When updating hosted repositories, sync a pre-commit hook into them instead of doing a `git fetch`. - The hook calls into Phabricator. The acting Phabricator user is sent via PHABRICATOR_USER in the environment. The active repository is sent via CLI. - The hook doesn't do anything useful yet; it just veifies basic parameters, does a little parsing, and exits 0 to allow the commit. Test Plan: - Performed Git pushes and pulls over SSH and HTTP. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T4189 Differential Revision: https://secure.phabricator.com/D7682
This commit is contained in:
parent
95c2b50974
commit
618b5cbbc4
9 changed files with 189 additions and 7 deletions
1
bin/commit-hook
Symbolic link
1
bin/commit-hook
Symbolic link
|
@ -0,0 +1 @@
|
|||
../scripts/repository/commit_hook.php
|
56
scripts/repository/commit_hook.php
Executable file
56
scripts/repository/commit_hook.php
Executable file
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
$root = dirname(dirname(dirname(__FILE__)));
|
||||
require_once $root.'/scripts/__init_script__.php';
|
||||
|
||||
$username = getenv('PHABRICATOR_USER');
|
||||
if (!$username) {
|
||||
throw new Exception(pht('usage: define PHABRICATOR_USER in environment'));
|
||||
}
|
||||
|
||||
$user = id(new PhabricatorPeopleQuery())
|
||||
->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 <callsign>'));
|
||||
}
|
||||
|
||||
$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);
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionCommitHookEngine extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
private $repository;
|
||||
private $stdin;
|
||||
|
||||
public function setStdin($stdin) {
|
||||
$this->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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue