diff --git a/scripts/repository/commit_hook.php b/scripts/repository/commit_hook.php index dabf9320a9..386075111d 100755 --- a/scripts/repository/commit_hook.php +++ b/scripts/repository/commit_hook.php @@ -36,9 +36,10 @@ if ($repository->isGit() || $repository->isHg()) { pht('usage: %s should be defined!', DiffusionCommitHookEngine::ENV_USER)); } - // TODO: If this is a Mercurial repository, the hook we're responding to - // is available in $argv[2]. It's unclear if we actually need this, or if - // we can block all actions we care about with just pretxnchangegroup. + if ($repository->isHg()) { + // We respond to several different hooks in Mercurial. + $engine->setMercurialHook($argv[2]); + } } else if ($repository->isSVN()) { // NOTE: In Subversion, the entire environment gets wiped so we can't read diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index 4f1b80534c..c75c2b1866 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -24,6 +24,7 @@ final class DiffusionCommitHookEngine extends Phobject { private $remoteAddress; private $remoteProtocol; private $transactionKey; + private $mercurialHook; /* -( Config )------------------------------------------------------------- */ @@ -97,6 +98,15 @@ final class DiffusionCommitHookEngine extends Phobject { return $this->viewer; } + public function setMercurialHook($mercurial_hook) { + $this->mercurialHook = $mercurial_hook; + return $this; + } + + public function getMercurialHook() { + return $this->mercurialHook; + } + /* -( Hook Execution )----------------------------------------------------- */ @@ -239,7 +249,10 @@ final class DiffusionCommitHookEngine extends Phobject { } else if (preg_match('(^refs/tags/)', $ref_raw)) { $ref_type = PhabricatorRepositoryPushLog::REFTYPE_TAG; } else { - $ref_type = PhabricatorRepositoryPushLog::REFTYPE_UNKNOWN; + throw new Exception( + pht( + "Unable to identify the reftype of '%s'. Rejecting push.", + $ref_raw)); } $ref_update = $this->newPushLog() @@ -413,6 +426,20 @@ final class DiffusionCommitHookEngine extends Phobject { private function findMercurialRefUpdates() { + $hook = $this->getMercurialHook(); + switch ($hook) { + case 'pretxnchangegroup': + return $this->findMercurialChangegroupRefUpdates(); + case 'prepushkey': + return $this->findMercurialPushKeyRefUpdates(); + case 'pretag': + return $this->findMercurialPreTagRefUpdates(); + default: + throw new Exception(pht('Unrecognized hook "%s"!', $hook)); + } + } + + private function findMercurialChangegroupRefUpdates() { $hg_node = getenv('HG_NODE'); if (!$hg_node) { throw new Exception(pht('Expected HG_NODE in environment!')); @@ -594,6 +621,87 @@ final class DiffusionCommitHookEngine extends Phobject { return $ref_updates; } + private function findMercurialPushKeyRefUpdates() { + $key_namespace = getenv('HG_NAMESPACE'); + + if ($key_namespace === 'phases') { + // Mercurial changes commit phases as part of normal push operations. We + // just ignore these, as they don't seem to represent anything + // interesting. + return array(); + } + + $key_name = getenv('HG_KEY'); + + $key_old = getenv('HG_OLD'); + if (!strlen($key_old)) { + $key_old = null; + } + + $key_new = getenv('HG_NEW'); + if (!strlen($key_new)) { + $key_new = null; + } + + if ($key_namespace !== 'bookmarks') { + throw new Exception( + pht( + "Unknown Mercurial key namespace '%s', with key '%s' (%s -> %s). ". + "Rejecting push.", + $key_namespace, + $key_name, + coalesce($key_old, pht('null')), + coalesce($key_new, pht('null')))); + } + + if ($key_old === $key_new) { + // We get a callback when the bookmark doesn't change. Just ignore this, + // as it's a no-op. + return array(); + } + + $ref_flags = 0; + $merge_base = null; + if ($key_old === null) { + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_ADD; + } else if ($key_new === null) { + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE; + } else { + list($merge_base_raw) = $this->getRepository()->execxLocalCommand( + 'log --template %s --rev %s', + '{node}', + hgsprintf('ancestor(%s, %s)', $key_old, $key_new)); + + if (strlen(trim($merge_base_raw))) { + $merge_base = trim($merge_base_raw); + } + + if ($merge_base && ($merge_base === $key_old)) { + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_APPEND; + } else { + $ref_flags |= PhabricatorRepositoryPushLog::CHANGEFLAG_REWRITE; + } + } + + $ref_update = $this->newPushLog() + ->setRefType(PhabricatorRepositoryPushLog::REFTYPE_BOOKMARK) + ->setRefName($key_name) + ->setRefOld(coalesce($key_old, self::EMPTY_HASH)) + ->setRefNew(coalesce($key_new, self::EMPTY_HASH)) + ->setChangeFlags($ref_flags); + + return array($ref_update); + } + + private function findMercurialPreTagRefUpdates() { + return array(); + } + + private function findMercurialContentUpdates(array $ref_updates) { + // TODO: Implement. + return array(); + } + private function parseMercurialCommits($raw) { $commits_lines = explode("\2", $raw); $commits_lines = array_filter($commits_lines); @@ -626,11 +734,6 @@ final class DiffusionCommitHookEngine extends Phobject { return $heads; } - private function findMercurialContentUpdates(array $ref_updates) { - // TODO: Implement. - return array(); - } - /* -( Subversion )--------------------------------------------------------- */ diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index d575fb2e0a..59be2016fd 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -397,11 +397,27 @@ final class PhabricatorRepositoryPullEngine $data = array(); $data[] = '[hooks]'; + + // This hook handles normal pushes. $data[] = csprintf( 'pretxnchangegroup.phabricator = %s %s %s', $bin, $repository->getCallsign(), 'pretxnchangegroup'); + + // This one handles creating bookmarks. + $data[] = csprintf( + 'prepushkey.phabricator = %s %s %s', + $bin, + $repository->getCallsign(), + 'prepushkey'); + + // This one handles creating tags. + $data[] = csprintf( + 'pretag.phabricator = %s %s %s', + $bin, + $repository->getCallsign(), + 'pretag'); $data[] = null; $data = implode("\n", $data); diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php index b0d885bd99..107aefb934 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php @@ -18,7 +18,6 @@ final class PhabricatorRepositoryPushLog const REFTYPE_BOOKMARK = 'bookmark'; const REFTYPE_SVN = 'svn'; const REFTYPE_COMMIT = 'commit'; - const REFTYPE_UNKNOWN = 'unknown'; const CHANGEFLAG_ADD = 1; const CHANGEFLAG_DELETE = 2;