repository = $repository; $future = $repository->getLocalCommandFuture( "log --template '{rev}\1{node}\1{date}\1{parents}\2'"); $this->iterator = new LinesOfALargeExecFuture($future); $this->iterator->setDelimiter("\2"); $this->iterator->rewind(); } public function getParents($commit) { if (!isset($this->parents[$commit])) { $this->parseUntil('node', $commit); $local = $this->localParents[$commit]; // The normal parsing pass gives us the local revision numbers of the // parents, but since we've decided we care about this data, we need to // convert them into full hashes. To do this, we parse to the deepest // one and then just look them up. $parents = array(); if ($local) { $this->parseUntil('rev', min($local)); foreach ($local as $rev) { $parents[] = $this->local[$rev]; } } $this->parents[$commit] = $parents; // Throw away the local info for this commit, we no longer need it. unset($this->localParents[$commit]); } return $this->parents[$commit]; } public function getCommitDate($commit) { if (!isset($this->dates[$commit])) { $this->parseUntil('node', $commit); } return $this->dates[$commit]; } /** * Parse until we have consumed some object. There are two types of parses: * parse until we find a commit hash ($until_type = "node"), or parse until we * find a local commit number ($until_type = "rev"). We use the former when * looking up commits, and the latter when resolving parents. */ private function parseUntil($until_type, $until_name) { if ($this->isParsed($until_type, $until_name)) { return; } $hglog = $this->iterator; while ($hglog->valid()) { $line = $hglog->current(); $hglog->next(); $line = trim($line); if (!strlen($line)) { break; } list($rev, $node, $date, $parents) = explode("\1", $line); $rev = (int)$rev; $date = (int)head(explode('.', $date)); $this->dates[$node] = $date; $this->local[$rev] = $node; $this->localParents[$node] = $this->parseParents($parents, $rev); if ($this->isParsed($until_type, $until_name)) { return; } } throw new Exception( "No such {$until_type} '{$until_name}' in repository!"); } /** * Parse a {parents} template, returning the local commit numbers. */ private function parseParents($parents, $target_rev) { // The hg '{parents}' token is empty if there is one "natural" parent // (predecessor local commit ID). Othwerwise, it may have one or two // parents. The string looks like this: // // 151:1f6c61a60586 154:1d5f799ebe1e $parents = trim($parents); if (strlen($parents)) { $local = array(); $parents = explode(' ', $parents); foreach ($parents as $key => $parent) { $parent = (int)head(explode(':', $parent)); if ($parent == -1) { // Initial commits will sometimes have "-1" as a parent. continue; } $local[] = $parent; } } else if ($target_rev) { // We have empty parents. If there's a predecessor, that's the local // parent number. $local = array($target_rev - 1); } else { // Initial commits will sometimes have no parents. $local = array(); } return $local; } /** * Returns true if the object specified by $type ('rev' or 'node') and * $name (rev or node name) has been consumed from the hg process. */ private function isParsed($type, $name) { switch ($type) { case 'rev': return isset($this->local[$name]); case 'node': return isset($this->dates[$name]); } } }