Skip to content

Commit 618b5cb

Browse files
author
epriestley
committedDec 2, 2013
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
1 parent 95c2b50 commit 618b5cb

9 files changed

+189
-7
lines changed
 

‎bin/commit-hook

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../scripts/repository/commit_hook.php

‎scripts/repository/commit_hook.php

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
$root = dirname(dirname(dirname(__FILE__)));
5+
require_once $root.'/scripts/__init_script__.php';
6+
7+
$username = getenv('PHABRICATOR_USER');
8+
if (!$username) {
9+
throw new Exception(pht('usage: define PHABRICATOR_USER in environment'));
10+
}
11+
12+
$user = id(new PhabricatorPeopleQuery())
13+
->setViewer(PhabricatorUser::getOmnipotentUser())
14+
->withUsernames(array($username))
15+
->executeOne();
16+
if (!$user) {
17+
throw new Exception(pht('No such user "%s"!', $username));
18+
}
19+
20+
if ($argc < 2) {
21+
throw new Exception(pht('usage: commit-hook <callsign>'));
22+
}
23+
24+
$repository = id(new PhabricatorRepositoryQuery())
25+
->setViewer($user)
26+
->withCallsigns(array($argv[1]))
27+
->requireCapabilities(
28+
array(
29+
// This capability check is redundant, but can't hurt.
30+
PhabricatorPolicyCapability::CAN_VIEW,
31+
DiffusionCapabilityPush::CAPABILITY,
32+
))
33+
->executeOne();
34+
35+
if (!$repository) {
36+
throw new Exception(pht('No such repository "%s"!', $callsign));
37+
}
38+
39+
if (!$repository->isHosted()) {
40+
// This should be redundant too, but double check just in case.
41+
throw new Exception(pht('Repository "%s" is not hosted!', $callsign));
42+
}
43+
44+
$stdin = @file_get_contents('php://stdin');
45+
if ($stdin === false) {
46+
throw new Exception(pht('Failed to read stdin!'));
47+
}
48+
49+
$engine = id(new DiffusionCommitHookEngine())
50+
->setViewer($user)
51+
->setRepository($repository)
52+
->setStdin($stdin);
53+
54+
$err = $engine->execute();
55+
56+
exit($err);

‎src/__phutil_library_map__.php

+2
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@
474474
'DiffusionCommitChangeTableView' => 'applications/diffusion/view/DiffusionCommitChangeTableView.php',
475475
'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php',
476476
'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php',
477+
'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php',
477478
'DiffusionCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionCommitParentsQuery.php',
478479
'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php',
479480
'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
@@ -2797,6 +2798,7 @@
27972798
'DiffusionCommitChangeTableView' => 'DiffusionView',
27982799
'DiffusionCommitController' => 'DiffusionController',
27992800
'DiffusionCommitEditController' => 'DiffusionController',
2801+
'DiffusionCommitHookEngine' => 'Phobject',
28002802
'DiffusionCommitParentsQuery' => 'DiffusionQuery',
28012803
'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
28022804
'DiffusionCommitTagsController' => 'DiffusionController',

‎src/applications/diffusion/controller/DiffusionServeController.php

+1
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ private function serveGitRequest(
318318
'PATH_INFO' => $request_path,
319319

320320
'REMOTE_USER' => $viewer->getUsername(),
321+
'PHABRICATOR_USER' => $viewer->getUsername(),
321322

322323
// TODO: Set these correctly.
323324
// GIT_COMMITTER_NAME
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
final class DiffusionCommitHookEngine extends Phobject {
4+
5+
private $viewer;
6+
private $repository;
7+
private $stdin;
8+
9+
public function setStdin($stdin) {
10+
$this->stdin = $stdin;
11+
return $this;
12+
}
13+
14+
public function getStdin() {
15+
return $this->stdin;
16+
}
17+
18+
public function setRepository(PhabricatorRepository $repository) {
19+
$this->repository = $repository;
20+
return $this;
21+
}
22+
23+
public function getRepository() {
24+
return $this->repository;
25+
}
26+
27+
public function setViewer(PhabricatorUser $viewer) {
28+
$this->viewer = $viewer;
29+
return $this;
30+
}
31+
32+
public function getViewer() {
33+
return $this->viewer;
34+
}
35+
36+
public function execute() {
37+
$type = $this->getRepository()->getVersionControlSystem();
38+
switch ($type) {
39+
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
40+
$err = $this->executeGitHook();
41+
break;
42+
default:
43+
throw new Exception(pht('Unsupported repository type "%s"!', $type));
44+
}
45+
46+
return $err;
47+
}
48+
49+
private function executeGitHook() {
50+
$updates = $this->parseGitUpdates($this->getStdin());
51+
52+
// TODO: Do useful things.
53+
54+
return 0;
55+
}
56+
57+
private function parseGitUpdates($stdin) {
58+
$updates = array();
59+
60+
$lines = phutil_split_lines($stdin, $retain_endings = false);
61+
foreach ($lines as $line) {
62+
$parts = explode(' ', $line, 3);
63+
if (count($parts) != 3) {
64+
throw new Exception(pht('Expected "old new ref", got "%s".', $line));
65+
}
66+
$updates[] = array(
67+
'old' => $parts[0],
68+
'new' => $parts[1],
69+
'ref' => $parts[2],
70+
);
71+
}
72+
73+
return $updates;
74+
}
75+
76+
}

‎src/applications/diffusion/ssh/DiffusionSSHGitReceivePackWorkflow.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ protected function executeRepositoryOperations() {
2525
$command = csprintf('git-receive-pack %s', $repository->getLocalPath());
2626
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
2727

28-
$future = new ExecFuture('%C', $command);
28+
$future = id(new ExecFuture('%C', $command))
29+
->setEnv($this->getEnvironment());
2930

3031
$err = $this->newPassthruCommand()
3132
->setIOChannel($this->getIOChannel())

‎src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ protected function executeRepositoryOperations() {
2222
$command = csprintf('git-upload-pack -- %s', $repository->getLocalPath());
2323
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
2424

25-
$future = new ExecFuture('%C', $command);
25+
$future = id(new ExecFuture('%C', $command))
26+
->setEnv($this->getEnvironment());
2627

2728
$err = $this->newPassthruCommand()
2829
->setIOChannel($this->getIOChannel())

‎src/applications/diffusion/ssh/DiffusionSSHWorkflow.php

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ public function getArgs() {
1717
return $this->args;
1818
}
1919

20+
public function getEnvironment() {
21+
return array(
22+
'PHABRICATOR_USER' => $this->getUser()->getUsername(),
23+
);
24+
}
25+
2026
abstract protected function executeRepositoryOperations();
2127

2228
protected function writeError($message) {

‎src/applications/repository/engine/PhabricatorRepositoryPullEngine.php

+43-5
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,15 @@ public function pullRepository() {
8383
}
8484
} else {
8585
if ($repository->isHosted()) {
86-
$this->logPull(
87-
pht(
88-
"Repository '%s' is hosted, so Phabricator does not pull ".
89-
"updates for it.",
90-
$callsign));
86+
if ($is_git) {
87+
$this->installGitHook();
88+
} else {
89+
$this->logPull(
90+
pht(
91+
"Repository '%s' is hosted, so Phabricator does not pull ".
92+
"updates for it.",
93+
$callsign));
94+
}
9195
} else {
9296
$this->logPull(
9397
pht(
@@ -146,6 +150,22 @@ private function updateRepositoryInitStatus($code, $message = null) {
146150
));
147151
}
148152

153+
private function installHook($path) {
154+
$this->log('%s', pht('Installing commit hook to "%s"...', $path));
155+
156+
$repository = $this->getRepository();
157+
$callsign = $repository->getCallsign();
158+
159+
$root = dirname(phutil_get_library_root('phabricator'));
160+
$bin = $root.'/bin/commit-hook';
161+
$cmd = csprintf('exec -- %s %s', $bin, $callsign);
162+
163+
$hook = "#!/bin/sh\n{$cmd}\n";
164+
165+
Filesystem::writeFile($path, $hook);
166+
Filesystem::changePermissions($path, 0755);
167+
}
168+
149169

150170
/* -( Pulling Git Working Copies )----------------------------------------- */
151171

@@ -279,6 +299,23 @@ private function executeGitUpdate() {
279299
}
280300

281301

302+
/**
303+
* @task git
304+
*/
305+
private function installGitHook() {
306+
$repository = $this->getRepository();
307+
$path = $repository->getLocalPath();
308+
309+
if ($repository->isWorkingCopyBare()) {
310+
$path .= 'hooks/pre-receive';
311+
} else {
312+
$path .= '.git/hooks/pre-receive';
313+
}
314+
315+
$this->installHook($path);
316+
}
317+
318+
282319
/* -( Pulling Mercurial Working Copies )----------------------------------- */
283320

284321

@@ -357,4 +394,5 @@ private function executeSubversionCreate() {
357394
execx('svnadmin create -- %s', $path);
358395
}
359396

397+
360398
}

0 commit comments

Comments
 (0)
Failed to load comments.