Basic Mercurial support for Arcanist
Summary:
There's a lot of ground left to cover but this makes "arc diff" work (on one
trivial diff) in my sandbox, at least, and supports parsing of Mercurial native
diffs (which are unified + a custom header). Piles of missing features, still.
Some of this is blocked by me not understanding the mercurial model well yet.
This is also a really good opportunity for cleanup (especially, reducing the
level of "instanceof" in the diff workflow), I'll try to do a bunch of that in
followup diffs.
Test Plan: Ran "arc diff" in a mercurial repository, got a diff out of it.
Reviewed By: aran
Reviewers: Makinde, jungejason, tuomaspelkonen, aran, codeblock
CC: aran, epriestley, codeblock, fratrik
Differential Revision: 792
2011-08-09 09:00:29 -07:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright 2011 Facebook, Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Interfaces with the Mercurial working copies.
|
|
|
|
*
|
|
|
|
* @group workingcopy
|
|
|
|
*/
|
|
|
|
class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|
|
|
|
|
|
|
private $status;
|
|
|
|
private $base;
|
2011-08-09 18:54:10 -07:00
|
|
|
private $relativeCommit;
|
Basic Mercurial support for Arcanist
Summary:
There's a lot of ground left to cover but this makes "arc diff" work (on one
trivial diff) in my sandbox, at least, and supports parsing of Mercurial native
diffs (which are unified + a custom header). Piles of missing features, still.
Some of this is blocked by me not understanding the mercurial model well yet.
This is also a really good opportunity for cleanup (especially, reducing the
level of "instanceof" in the diff workflow), I'll try to do a bunch of that in
followup diffs.
Test Plan: Ran "arc diff" in a mercurial repository, got a diff out of it.
Reviewed By: aran
Reviewers: Makinde, jungejason, tuomaspelkonen, aran, codeblock
CC: aran, epriestley, codeblock, fratrik
Differential Revision: 792
2011-08-09 09:00:29 -07:00
|
|
|
|
|
|
|
public function getSourceControlSystemName() {
|
|
|
|
return 'hg';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getSourceControlBaseRevision() {
|
|
|
|
list($stdout) = execx(
|
|
|
|
'(cd %s && hg id -ir %s)',
|
|
|
|
$this->getPath(),
|
|
|
|
$this->getRelativeCommit());
|
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getSourceControlPath() {
|
|
|
|
return '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getBranchName() {
|
|
|
|
// TODO: I have nearly no idea how hg local branches work.
|
|
|
|
list($stdout) = execx(
|
|
|
|
'(cd %s && hg branch)',
|
|
|
|
$this->getPath());
|
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
2011-08-09 18:54:10 -07:00
|
|
|
public function setRelativeCommit($commit) {
|
|
|
|
list($err) = exec_manual(
|
|
|
|
'(cd %s && hg id -ir %s)',
|
|
|
|
$this->getPath(),
|
|
|
|
$commit);
|
|
|
|
|
|
|
|
if ($err) {
|
|
|
|
throw new ArcanistUsageException(
|
|
|
|
"Commit '{$commit}' is not a valid Mercurial commit identifier.");
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->relativeCommit = $commit;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
Basic Mercurial support for Arcanist
Summary:
There's a lot of ground left to cover but this makes "arc diff" work (on one
trivial diff) in my sandbox, at least, and supports parsing of Mercurial native
diffs (which are unified + a custom header). Piles of missing features, still.
Some of this is blocked by me not understanding the mercurial model well yet.
This is also a really good opportunity for cleanup (especially, reducing the
level of "instanceof" in the diff workflow), I'll try to do a bunch of that in
followup diffs.
Test Plan: Ran "arc diff" in a mercurial repository, got a diff out of it.
Reviewed By: aran
Reviewers: Makinde, jungejason, tuomaspelkonen, aran, codeblock
CC: aran, epriestley, codeblock, fratrik
Differential Revision: 792
2011-08-09 09:00:29 -07:00
|
|
|
public function getRelativeCommit() {
|
2011-08-09 18:54:10 -07:00
|
|
|
if (empty($this->relativeCommit)) {
|
|
|
|
list($stdout) = execx(
|
|
|
|
'(cd %s && hg outgoing --limit 1)',
|
|
|
|
$this->getPath());
|
|
|
|
$logs = $this->parseMercurialLog($stdout);
|
|
|
|
if (!count($logs)) {
|
|
|
|
throw new ArcanistUsageException("You have no outgoing changes!");
|
|
|
|
}
|
|
|
|
$oldest_log = head($logs);
|
|
|
|
|
|
|
|
$this->relativeCommit = $oldest_log['rev'].'~1';
|
|
|
|
}
|
|
|
|
return $this->relativeCommit;
|
Basic Mercurial support for Arcanist
Summary:
There's a lot of ground left to cover but this makes "arc diff" work (on one
trivial diff) in my sandbox, at least, and supports parsing of Mercurial native
diffs (which are unified + a custom header). Piles of missing features, still.
Some of this is blocked by me not understanding the mercurial model well yet.
This is also a really good opportunity for cleanup (especially, reducing the
level of "instanceof" in the diff workflow), I'll try to do a bunch of that in
followup diffs.
Test Plan: Ran "arc diff" in a mercurial repository, got a diff out of it.
Reviewed By: aran
Reviewers: Makinde, jungejason, tuomaspelkonen, aran, codeblock
CC: aran, epriestley, codeblock, fratrik
Differential Revision: 792
2011-08-09 09:00:29 -07:00
|
|
|
}
|
|
|
|
|
2011-08-23 18:48:55 -07:00
|
|
|
public function getLocalCommitInformation() {
|
|
|
|
list($info) = execx(
|
|
|
|
'(cd %s && hg log --rev %s..%s --)',
|
|
|
|
$this->getPath(),
|
|
|
|
$this->getRelativeCommit(),
|
|
|
|
'tip');
|
|
|
|
return $this->parseMercurialLog($info);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
Basic Mercurial support for Arcanist
Summary:
There's a lot of ground left to cover but this makes "arc diff" work (on one
trivial diff) in my sandbox, at least, and supports parsing of Mercurial native
diffs (which are unified + a custom header). Piles of missing features, still.
Some of this is blocked by me not understanding the mercurial model well yet.
This is also a really good opportunity for cleanup (especially, reducing the
level of "instanceof" in the diff workflow), I'll try to do a bunch of that in
followup diffs.
Test Plan: Ran "arc diff" in a mercurial repository, got a diff out of it.
Reviewed By: aran
Reviewers: Makinde, jungejason, tuomaspelkonen, aran, codeblock
CC: aran, epriestley, codeblock, fratrik
Differential Revision: 792
2011-08-09 09:00:29 -07:00
|
|
|
public function getBlame($path) {
|
|
|
|
list($stdout) = execx(
|
|
|
|
'(cd %s && hg blame -u -v -c --rev %s -- %s)',
|
|
|
|
$this->getPath(),
|
|
|
|
$this->getRelativeCommit(),
|
|
|
|
$path);
|
|
|
|
|
|
|
|
$blame = array();
|
|
|
|
foreach (explode("\n", trim($stdout)) as $line) {
|
|
|
|
if (!strlen($line)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$matches = null;
|
|
|
|
$ok = preg_match('^/\s*([^:]+?) [a-f0-9]{12}: (.*)$/', $line, $matches);
|
|
|
|
|
|
|
|
if (!$ok) {
|
|
|
|
throw new Exception("Unable to parse Mercurial blame line: {$line}");
|
|
|
|
}
|
|
|
|
|
|
|
|
$revision = $matches[2];
|
|
|
|
$author = trim($matches[1]);
|
|
|
|
$blame[] = array($author, $revision);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $blame;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getWorkingCopyStatus() {
|
|
|
|
|
2011-08-09 18:54:10 -07:00
|
|
|
// A reviewable revision spans multiple local commits in Mercurial, but
|
|
|
|
// there is no way to get file change status across multiple commits, so
|
|
|
|
// just take the entire diff and parse it to figure out what's changed.
|
|
|
|
|
|
|
|
$diff = $this->getFullMercurialDiff();
|
|
|
|
$parser = new ArcanistDiffParser();
|
|
|
|
$changes = $parser->parseDiff($diff);
|
|
|
|
|
|
|
|
$status_map = array();
|
|
|
|
|
|
|
|
foreach ($changes as $change) {
|
|
|
|
$flags = 0;
|
|
|
|
switch ($change->getType()) {
|
|
|
|
case ArcanistDiffChangeType::TYPE_ADD:
|
|
|
|
case ArcanistDiffChangeType::TYPE_MOVE_HERE:
|
|
|
|
case ArcanistDiffChangeType::TYPE_COPY_HERE:
|
|
|
|
$flags |= self::FLAG_ADDED;
|
|
|
|
break;
|
|
|
|
case ArcanistDiffChangeType::TYPE_CHANGE:
|
|
|
|
case ArcanistDiffChangeType::TYPE_COPY_AWAY: // Check for changes?
|
|
|
|
$flags |= self::FLAG_MODIFIED;
|
|
|
|
break;
|
|
|
|
case ArcanistDiffChangeType::TYPE_DELETE:
|
|
|
|
case ArcanistDiffChangeType::TYPE_MOVE_AWAY:
|
|
|
|
case ArcanistDiffChangeType::TYPE_MULTICOPY:
|
|
|
|
$flags |= self::FLAG_DELETED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
$status_map[$change->getCurrentPath()] = $flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($stdout) = execx(
|
|
|
|
'(cd %s && hg status)',
|
|
|
|
$this->getPath());
|
|
|
|
|
|
|
|
$working_status = $this->parseMercurialStatus($stdout);
|
|
|
|
foreach ($working_status as $path => $status) {
|
|
|
|
$status |= self::FLAG_UNCOMMITTED;
|
|
|
|
if (!empty($status_map[$path])) {
|
|
|
|
$status_map[$path] |= $status;
|
|
|
|
} else {
|
|
|
|
$status_map[$path] = $status;
|
|
|
|
}
|
|
|
|
}
|
Basic Mercurial support for Arcanist
Summary:
There's a lot of ground left to cover but this makes "arc diff" work (on one
trivial diff) in my sandbox, at least, and supports parsing of Mercurial native
diffs (which are unified + a custom header). Piles of missing features, still.
Some of this is blocked by me not understanding the mercurial model well yet.
This is also a really good opportunity for cleanup (especially, reducing the
level of "instanceof" in the diff workflow), I'll try to do a bunch of that in
followup diffs.
Test Plan: Ran "arc diff" in a mercurial repository, got a diff out of it.
Reviewed By: aran
Reviewers: Makinde, jungejason, tuomaspelkonen, aran, codeblock
CC: aran, epriestley, codeblock, fratrik
Differential Revision: 792
2011-08-09 09:00:29 -07:00
|
|
|
|
2011-08-09 18:54:10 -07:00
|
|
|
return $status_map;
|
Basic Mercurial support for Arcanist
Summary:
There's a lot of ground left to cover but this makes "arc diff" work (on one
trivial diff) in my sandbox, at least, and supports parsing of Mercurial native
diffs (which are unified + a custom header). Piles of missing features, still.
Some of this is blocked by me not understanding the mercurial model well yet.
This is also a really good opportunity for cleanup (especially, reducing the
level of "instanceof" in the diff workflow), I'll try to do a bunch of that in
followup diffs.
Test Plan: Ran "arc diff" in a mercurial repository, got a diff out of it.
Reviewed By: aran
Reviewers: Makinde, jungejason, tuomaspelkonen, aran, codeblock
CC: aran, epriestley, codeblock, fratrik
Differential Revision: 792
2011-08-09 09:00:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private function getDiffOptions() {
|
|
|
|
$options = array(
|
|
|
|
'-g',
|
|
|
|
'-U'.$this->getDiffLinesOfContext(),
|
|
|
|
);
|
|
|
|
return implode(' ', $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRawDiffText($path) {
|
|
|
|
$options = $this->getDiffOptions();
|
|
|
|
|
|
|
|
list($stdout) = execx(
|
|
|
|
'(cd %s && hg diff %C --rev %s --rev tip -- %s)',
|
|
|
|
$this->getPath(),
|
|
|
|
$options,
|
|
|
|
$this->getRelativeCommit(),
|
|
|
|
$path);
|
|
|
|
|
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFullMercurialDiff() {
|
|
|
|
$options = $this->getDiffOptions();
|
|
|
|
|
|
|
|
list($stdout) = execx(
|
|
|
|
'(cd %s && hg diff %C --rev %s --rev tip --)',
|
|
|
|
$this->getPath(),
|
|
|
|
$options,
|
|
|
|
$this->getRelativeCommit());
|
|
|
|
|
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getOriginalFileData($path) {
|
|
|
|
return $this->getFileDataAtRevision($path, $this->getRelativeCommit());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCurrentFileData($path) {
|
|
|
|
return $this->getFileDataAtRevision($path, 'tip');
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getFileDataAtRevision($path, $revision) {
|
|
|
|
list($stdout) = execx(
|
|
|
|
'(cd %s && hg cat --rev %s -- %s)',
|
|
|
|
$this->getPath(),
|
|
|
|
$path);
|
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
2011-08-09 18:54:10 -07:00
|
|
|
private function parseMercurialStatus($status) {
|
|
|
|
$result = array();
|
|
|
|
|
|
|
|
$status = trim($status);
|
|
|
|
if (!strlen($status)) {
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
$lines = explode("\n", $status);
|
|
|
|
foreach ($lines as $line) {
|
|
|
|
$flags = 0;
|
|
|
|
list($code, $path) = explode(' ', $line, 2);
|
|
|
|
switch ($code) {
|
|
|
|
case 'A':
|
|
|
|
$flags |= self::FLAG_ADDED;
|
|
|
|
break;
|
|
|
|
case 'R':
|
|
|
|
$flags |= self::FLAG_REMOVED;
|
|
|
|
break;
|
|
|
|
case 'M':
|
|
|
|
$flags |= self::FLAG_MODIFIED;
|
|
|
|
break;
|
|
|
|
case 'C':
|
|
|
|
// This is "clean" and included only for completeness, these files
|
|
|
|
// have not been changed.
|
|
|
|
break;
|
|
|
|
case '!':
|
|
|
|
$flags |= self::FLAG_MISSING;
|
|
|
|
break;
|
|
|
|
case '?':
|
|
|
|
$flags |= self::FLAG_UNTRACKED;
|
|
|
|
break;
|
|
|
|
case 'I':
|
|
|
|
// This is "ignored" and included only for completeness.
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Exception("Unknown Mercurial status '{$code}'.");
|
|
|
|
}
|
|
|
|
|
|
|
|
$result[$path] = $flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function parseMercurialLog($log) {
|
|
|
|
$result = array();
|
|
|
|
|
|
|
|
$chunks = explode("\n\n", trim($log));
|
|
|
|
foreach ($chunks as $chunk) {
|
|
|
|
$commit = array();
|
|
|
|
$lines = explode("\n", $chunk);
|
|
|
|
foreach ($lines as $line) {
|
|
|
|
if (preg_match('/^(comparing with|searching for changes)/', $line)) {
|
|
|
|
// These are sent to stdout when you run "hg outgoing" although the
|
|
|
|
// format is otherwise identical to "hg log".
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
list($name, $value) = explode(':', $line, 2);
|
|
|
|
$value = trim($value);
|
|
|
|
switch ($name) {
|
|
|
|
case 'user':
|
|
|
|
$commit['user'] = $value;
|
|
|
|
break;
|
|
|
|
case 'date':
|
|
|
|
$commit['date'] = strtotime($value);
|
|
|
|
break;
|
|
|
|
case 'summary':
|
|
|
|
$commit['summary'] = $value;
|
|
|
|
break;
|
|
|
|
case 'changeset':
|
|
|
|
list($local, $rev) = explode(':', $value, 2);
|
|
|
|
$commit['local'] = $local;
|
|
|
|
$commit['rev'] = $rev;
|
|
|
|
break;
|
2011-08-23 18:48:55 -07:00
|
|
|
case 'parent':
|
|
|
|
if (empty($commit['parents'])) {
|
|
|
|
$commit['parents'] = array();
|
|
|
|
}
|
|
|
|
list($local, $rev) = explode(':', $value, 2);
|
|
|
|
$commit['parents'][] = array(
|
|
|
|
'local' => $local,
|
|
|
|
'rev' => $rev,
|
|
|
|
);
|
|
|
|
break;
|
2011-08-09 18:54:10 -07:00
|
|
|
default:
|
|
|
|
throw new Exception("Unknown Mercurial log field '{$name}'!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$result[] = $commit;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
Basic Mercurial support for Arcanist
Summary:
There's a lot of ground left to cover but this makes "arc diff" work (on one
trivial diff) in my sandbox, at least, and supports parsing of Mercurial native
diffs (which are unified + a custom header). Piles of missing features, still.
Some of this is blocked by me not understanding the mercurial model well yet.
This is also a really good opportunity for cleanup (especially, reducing the
level of "instanceof" in the diff workflow), I'll try to do a bunch of that in
followup diffs.
Test Plan: Ran "arc diff" in a mercurial repository, got a diff out of it.
Reviewed By: aran
Reviewers: Makinde, jungejason, tuomaspelkonen, aran, codeblock
CC: aran, epriestley, codeblock, fratrik
Differential Revision: 792
2011-08-09 09:00:29 -07:00
|
|
|
}
|