4 * Streaming interface on top of "hg log" that gives us performant access to
5 * the Mercurial commit graph with one nonblocking invocation of "hg". See
6 * @{class:PhabricatorRepositoryPullLocalDaemon}.
8 final class PhabricatorMercurialGraphStream
9 extends PhabricatorRepositoryGraphStream
{
14 private $parents = array();
15 private $dates = array();
16 private $local = array();
17 private $localParents = array();
19 public function __construct(PhabricatorRepository
$repository,
20 $start_commit = null) {
22 $this->repository
= $repository;
24 $command = 'log --template %s --rev %s';
25 $template = '{rev}\1{node}\1{date}\1{parents}\2';
26 if ($start_commit !== null) {
27 $revset = hgsprintf('reverse(ancestors(%s))', $start_commit);
29 $revset = 'reverse(all())';
32 $future = $repository->getLocalCommandFuture(
37 $this->iterator
= new LinesOfALargeExecFuture($future);
38 $this->iterator
->setDelimiter("\2");
39 $this->iterator
->rewind();
42 public function getParents($commit) {
43 if (!isset($this->parents
[$commit])) {
44 $this->parseUntil('node', $commit);
46 $local = $this->localParents
[$commit];
48 // The normal parsing pass gives us the local revision numbers of the
49 // parents, but since we've decided we care about this data, we need to
50 // convert them into full hashes. To do this, we parse to the deepest
51 // one and then just look them up.
55 $this->parseUntil('rev', min($local));
56 foreach ($local as $rev) {
57 $parents[] = $this->local
[$rev];
61 $this->parents
[$commit] = $parents;
63 // Throw away the local info for this commit, we no longer need it.
64 unset($this->localParents
[$commit]);
67 return $this->parents
[$commit];
70 public function getCommitDate($commit) {
71 if (!isset($this->dates
[$commit])) {
72 $this->parseUntil('node', $commit);
74 return $this->dates
[$commit];
78 * Parse until we have consumed some object. There are two types of parses:
79 * parse until we find a commit hash ($until_type = "node"), or parse until we
80 * find a local commit number ($until_type = "rev"). We use the former when
81 * looking up commits, and the latter when resolving parents.
83 private function parseUntil($until_type, $until_name) {
84 if ($this->isParsed($until_type, $until_name)) {
88 $hglog = $this->iterator
;
90 while ($hglog->valid()) {
91 $line = $hglog->current();
98 list($rev, $node, $date, $parents) = explode("\1", $line);
101 $date = (int)head(explode('.', $date));
103 $this->dates
[$node] = $date;
104 $this->local
[$rev] = $node;
105 $this->localParents
[$node] = $this->parseParents($parents, $rev);
107 if ($this->isParsed($until_type, $until_name)) {
114 "No such %s '%s' in repository!",
121 * Parse a {parents} template, returning the local commit numbers.
123 private function parseParents($parents, $target_rev) {
125 // The hg '{parents}' token is empty if there is one "natural" parent
126 // (predecessor local commit ID). Otherwise, it may have one or two
127 // parents. The string looks like this:
129 // 151:1f6c61a60586 154:1d5f799ebe1e
131 $parents = trim($parents);
132 if (strlen($parents)) {
135 $parents = explode(' ', $parents);
136 foreach ($parents as $key => $parent) {
137 $parent = (int)head(explode(':', $parent));
139 // Initial commits will sometimes have "-1" as a parent.
144 } else if ($target_rev) {
145 // We have empty parents. If there's a predecessor, that's the local
147 $local = array($target_rev - 1);
149 // Initial commits will sometimes have no parents.
158 * Returns true if the object specified by $type ('rev' or 'node') and
159 * $name (rev or node name) has been consumed from the hg process.
161 private function isParsed($type, $name) {
164 return isset($this->local
[$name]);
166 return isset($this->dates
[$name]);