mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-20 05:42:40 +01:00
Do the heavy lifting for git commit hooks
Summary: Ref T4189. This doesn't add any rules yet, but does all the heavy lifting to figure out what's changed and put it in a consuamble (if somewhat ad-hoc) datastructure, which lists all the ref and tag modifications and all the new commits in a consistent way. From here, it should be fairly straightforward to add top-level rules (e.g., ff pushes only). Test Plan: Output is huge, see comments. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T4189 Differential Revision: https://secure.phabricator.com/D7687
This commit is contained in:
parent
9942c2a39e
commit
632e1ceda6
1 changed files with 115 additions and 21 deletions
|
@ -61,14 +61,128 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
return $err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @task git
|
||||
*/
|
||||
private function executeGitHook() {
|
||||
$updates = $this->parseGitUpdates($this->getStdin());
|
||||
|
||||
// TODO: Do useful things.
|
||||
// TODO: Do cheap checks: non-ff commits, mutating refs without access,
|
||||
// creating or deleting things you can't touch. We can do all non-content
|
||||
// checks here.
|
||||
|
||||
$updates = $this->findGitNewCommits($updates);
|
||||
|
||||
// TODO: Now, do content checks.
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task git
|
||||
*/
|
||||
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));
|
||||
}
|
||||
$update = array(
|
||||
'old' => $parts[0],
|
||||
'old.short' => substr($parts[0], 0, 8),
|
||||
'new' => $parts[1],
|
||||
'new.short' => substr($parts[1], 0, 8),
|
||||
'ref' => $parts[2],
|
||||
);
|
||||
|
||||
if (preg_match('(^refs/heads/)', $update['ref'])) {
|
||||
$update['type'] = 'branch';
|
||||
} else if (preg_match('(^refs/tags/)', $update['ref'])) {
|
||||
$update['type'] = 'tag';
|
||||
} else {
|
||||
$update['type'] = 'unknown';
|
||||
}
|
||||
|
||||
$updates[] = $update;
|
||||
}
|
||||
|
||||
$updates = $this->findGitMergeBases($updates);
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task git
|
||||
*/
|
||||
private function findGitMergeBases(array $updates) {
|
||||
$empty = str_repeat('0', 40);
|
||||
|
||||
$futures = array();
|
||||
foreach ($updates as $key => $update) {
|
||||
// Updates are in the form:
|
||||
//
|
||||
// <old hash> <new hash> <ref>
|
||||
//
|
||||
// If the old hash is "00000...", the ref is being created (either a new
|
||||
// branch, or a new tag). If the new hash is "00000...", the ref is being
|
||||
// deleted. If both are nonempty, the ref is being updated. For updates,
|
||||
// we'll figure out the `merge-base` of the old and new objects here. This
|
||||
// lets us reject non-FF changes cheaply; later, we'll figure out exactly
|
||||
// which commits are new.
|
||||
|
||||
if ($update['old'] == $empty) {
|
||||
$updates[$key]['operation'] = 'create';
|
||||
} else if ($update['new'] == $empty) {
|
||||
$updates[$key]['operation'] = 'delete';
|
||||
} else {
|
||||
$updates[$key]['operation'] = 'update';
|
||||
$futures[$key] = $this->getRepository()->getLocalCommandFuture(
|
||||
'merge-base %s %s',
|
||||
$update['old'],
|
||||
$update['new']);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Futures($futures)->limit(8) as $key => $future) {
|
||||
list($stdout) = $future->resolvex();
|
||||
$updates[$key]['merge-base'] = rtrim($stdout, "\n");
|
||||
}
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
private function findGitNewCommits(array $updates) {
|
||||
$futures = array();
|
||||
foreach ($updates as $key => $update) {
|
||||
if ($update['type'] == 'delete') {
|
||||
// Deleting a branch or tag can never create any new commits.
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: This piece of magic finds all new commits, by walking backward
|
||||
// from the new value to the value of *any* existing ref in the
|
||||
// repository. Particularly, this will cover the cases of a new branch, a
|
||||
// completely moved tag, etc.
|
||||
$futures[$key] = $this->getRepository()->getLocalCommandFuture(
|
||||
'log --format=%s %s --not --all',
|
||||
'%H',
|
||||
$update['new']);
|
||||
}
|
||||
|
||||
foreach (Futures($futures)->limit(8) as $key => $future) {
|
||||
list($stdout) = $future->resolvex();
|
||||
$commits = phutil_split_lines($stdout, $retain_newlines = false);
|
||||
$updates[$key]['commits'] = $commits;
|
||||
}
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
private function executeSubversionHook() {
|
||||
|
||||
// TODO: Do useful things here, too.
|
||||
|
@ -82,24 +196,4 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue