1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +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:
epriestley 2013-12-02 15:45:36 -08:00
parent 95c2b50974
commit 618b5cbbc4
9 changed files with 189 additions and 7 deletions

1
bin/commit-hook Symbolic link
View file

@ -0,0 +1 @@
../scripts/repository/commit_hook.php

View 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);

View file

@ -474,6 +474,7 @@ phutil_register_library_map(array(
'DiffusionCommitChangeTableView' => 'applications/diffusion/view/DiffusionCommitChangeTableView.php', 'DiffusionCommitChangeTableView' => 'applications/diffusion/view/DiffusionCommitChangeTableView.php',
'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php', 'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php',
'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php', 'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php',
'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php',
'DiffusionCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionCommitParentsQuery.php', 'DiffusionCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionCommitParentsQuery.php',
'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php', 'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php',
'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php', 'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
@ -2797,6 +2798,7 @@ phutil_register_library_map(array(
'DiffusionCommitChangeTableView' => 'DiffusionView', 'DiffusionCommitChangeTableView' => 'DiffusionView',
'DiffusionCommitController' => 'DiffusionController', 'DiffusionCommitController' => 'DiffusionController',
'DiffusionCommitEditController' => 'DiffusionController', 'DiffusionCommitEditController' => 'DiffusionController',
'DiffusionCommitHookEngine' => 'Phobject',
'DiffusionCommitParentsQuery' => 'DiffusionQuery', 'DiffusionCommitParentsQuery' => 'DiffusionQuery',
'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DiffusionCommitTagsController' => 'DiffusionController', 'DiffusionCommitTagsController' => 'DiffusionController',

View file

@ -318,6 +318,7 @@ final class DiffusionServeController extends DiffusionController {
'PATH_INFO' => $request_path, 'PATH_INFO' => $request_path,
'REMOTE_USER' => $viewer->getUsername(), 'REMOTE_USER' => $viewer->getUsername(),
'PHABRICATOR_USER' => $viewer->getUsername(),
// TODO: Set these correctly. // TODO: Set these correctly.
// GIT_COMMITTER_NAME // GIT_COMMITTER_NAME

View file

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

View file

@ -25,7 +25,8 @@ final class DiffusionSSHGitReceivePackWorkflow
$command = csprintf('git-receive-pack %s', $repository->getLocalPath()); $command = csprintf('git-receive-pack %s', $repository->getLocalPath());
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
$future = new ExecFuture('%C', $command); $future = id(new ExecFuture('%C', $command))
->setEnv($this->getEnvironment());
$err = $this->newPassthruCommand() $err = $this->newPassthruCommand()
->setIOChannel($this->getIOChannel()) ->setIOChannel($this->getIOChannel())

View file

@ -22,7 +22,8 @@ final class DiffusionSSHGitUploadPackWorkflow
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath()); $command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
$future = new ExecFuture('%C', $command); $future = id(new ExecFuture('%C', $command))
->setEnv($this->getEnvironment());
$err = $this->newPassthruCommand() $err = $this->newPassthruCommand()
->setIOChannel($this->getIOChannel()) ->setIOChannel($this->getIOChannel())

View file

@ -17,6 +17,12 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
return $this->args; return $this->args;
} }
public function getEnvironment() {
return array(
'PHABRICATOR_USER' => $this->getUser()->getUsername(),
);
}
abstract protected function executeRepositoryOperations(); abstract protected function executeRepositoryOperations();
protected function writeError($message) { protected function writeError($message) {

View file

@ -83,11 +83,15 @@ final class PhabricatorRepositoryPullEngine
} }
} else { } else {
if ($repository->isHosted()) { if ($repository->isHosted()) {
$this->logPull( if ($is_git) {
pht( $this->installGitHook();
"Repository '%s' is hosted, so Phabricator does not pull ". } else {
"updates for it.", $this->logPull(
$callsign)); pht(
"Repository '%s' is hosted, so Phabricator does not pull ".
"updates for it.",
$callsign));
}
} else { } else {
$this->logPull( $this->logPull(
pht( 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 )----------------------------------------- */ /* -( 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 )----------------------------------- */ /* -( Pulling Mercurial Working Copies )----------------------------------- */
@ -357,4 +394,5 @@ final class PhabricatorRepositoryPullEngine
execx('svnadmin create -- %s', $path); execx('svnadmin create -- %s', $path);
} }
} }