1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-02-03 10:28:22 +01:00
phorge-arcanist/src/repository/api/ArcanistMercurialAPI.php

737 lines
21 KiB
PHP
Raw Normal View History

<?php
/*
* Copyright 2012 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
*/
final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
private $status;
private $base;
private $relativeCommit;
2012-07-01 11:06:05 -07:00
private $branch;
private $workingCopyRevision;
private $localCommitInfo;
private $includeDirectoryStateInDiffs;
protected function buildLocalFuture(array $argv) {
// Mercurial has a "defaults" feature which basically breaks automation by
// allowing the user to add random flags to any command. This feature is
// "deprecated" and "a bad idea" that you should "forget ... existed"
// according to project lead Matt Mackall:
//
// http://markmail.org/message/hl3d6eprubmkkqh5
//
// There is an HGPLAIN environmental variable which enables "plain mode"
// and hopefully disables this stuff.
if (phutil_is_windows()) {
$argv[0] = 'set HGPLAIN=1 & hg '.$argv[0];
} else {
$argv[0] = 'HGPLAIN=1 hg '.$argv[0];
}
$future = newv('ExecFuture', $argv);
$future->setCWD($this->getPath());
return $future;
}
public function getSourceControlSystemName() {
return 'hg';
}
public function getMetadataPath() {
return $this->getPath('.hg');
}
public function getSourceControlBaseRevision() {
return $this->getCanonicalRevisionName($this->getRelativeCommit());
}
public function getCanonicalRevisionName($string) {
list($stdout) = $this->execxLocal(
'log -l 1 --template %s -r %s --',
'{node}',
$string);
return $stdout;
}
public function getSourceControlPath() {
return '/';
}
public function getBranchName() {
2012-07-01 11:06:05 -07:00
if (!$this->branch) {
list($stdout) = $this->execxLocal('branch');
$this->branch = trim($stdout);
}
return $this->branch;
}
public function setRelativeCommit($commit) {
try {
$commit = $this->getCanonicalRevisionName($commit);
} catch (Exception $ex) {
throw new ArcanistUsageException(
"Commit '{$commit}' is not a valid Mercurial commit identifier.");
}
$this->relativeCommit = $commit;
2012-07-01 11:06:05 -07:00
$this->status = null;
$this->localCommitInfo = null;
return $this;
}
public function getRelativeCommit() {
if (empty($this->relativeCommit)) {
[Arcanist] mercurial base commit DSL support Summary: This adds Mercurial support to the base commit DSL that's equivalent to git's, though not identical. Specifically, Mercurial has arc:outgoing instead of arc:upstream, and hg:gca(commit) instead of git:merge-base(commit), though they have similar behaviors. Test Plan: [13:37:13 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ hg log -G -r master:: @ changeset: 11:6970e9263ab8 | bookmark: something | tag: tip | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:03:46 2012 -0700 | summary: thing | o changeset: 10:433a93023f03 | parent: 8:f47ccfe34267 | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:03:34 2012 -0700 | summary: some | | o changeset: 9:c8379ef32b0d |/ bookmark: other | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:01:04 2012 -0700 | summary: other | o changeset: 8:f47ccfe34267 | bookmark: master | user: David Schleimer <dschleimer@fb.com> | date: Mon Jun 04 14:30:47 2012 -0700 | summary: typo-fix | [13:38:00 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ ~/devtools/arcanist/bin/arc which --show-base --base hg:.^ .^ [13:38:11 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ ~/devtools/arcanist/bin/arc which --show-base --base 'hg:gca(other)' f47ccfe34267592dd2e336174a3a311b8783c024 [13:38:21 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ hg id -r f47ccfe34267592dd2e336174a3a311b8783c024 f47ccfe34267 master [13:38:31 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20537 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:empty' null [13:38:44 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20538 $ hg id -r null 000000000000 [13:38:48 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20538 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:outgoing' f47ccfe34267592dd2e336174a3a311b8783c024 Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1233 Differential Revision: https://secure.phabricator.com/D2822
2012-06-21 18:12:09 -07:00
if ($this->getBaseCommitArgumentRules() ||
$this->getWorkingCopyIdentity()->getConfigFromAnySource('base')) {
[Arcanist] mercurial base commit DSL support Summary: This adds Mercurial support to the base commit DSL that's equivalent to git's, though not identical. Specifically, Mercurial has arc:outgoing instead of arc:upstream, and hg:gca(commit) instead of git:merge-base(commit), though they have similar behaviors. Test Plan: [13:37:13 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ hg log -G -r master:: @ changeset: 11:6970e9263ab8 | bookmark: something | tag: tip | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:03:46 2012 -0700 | summary: thing | o changeset: 10:433a93023f03 | parent: 8:f47ccfe34267 | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:03:34 2012 -0700 | summary: some | | o changeset: 9:c8379ef32b0d |/ bookmark: other | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:01:04 2012 -0700 | summary: other | o changeset: 8:f47ccfe34267 | bookmark: master | user: David Schleimer <dschleimer@fb.com> | date: Mon Jun 04 14:30:47 2012 -0700 | summary: typo-fix | [13:38:00 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ ~/devtools/arcanist/bin/arc which --show-base --base hg:.^ .^ [13:38:11 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ ~/devtools/arcanist/bin/arc which --show-base --base 'hg:gca(other)' f47ccfe34267592dd2e336174a3a311b8783c024 [13:38:21 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ hg id -r f47ccfe34267592dd2e336174a3a311b8783c024 f47ccfe34267 master [13:38:31 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20537 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:empty' null [13:38:44 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20538 $ hg id -r null 000000000000 [13:38:48 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20538 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:outgoing' f47ccfe34267592dd2e336174a3a311b8783c024 Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1233 Differential Revision: https://secure.phabricator.com/D2822
2012-06-21 18:12:09 -07:00
$base = $this->resolveBaseCommit();
if (!$base) {
throw new ArcanistUsageException(
"None of the rules in your 'base' configuration matched a valid ".
"commit. Adjust rules or specify which commit you want to use ".
"explicitly.");
}
$this->relativeCommit = $base;
return $this->relativeCommit;
}
list($err, $stdout) = $this->execManualLocal(
2012-07-01 11:06:05 -07:00
'outgoing --branch %s --style default',
$this->getBranchName());
if (!$err) {
$logs = ArcanistMercurialParser::parseMercurialLog($stdout);
} else {
// Mercurial (in some versions?) raises an error when there's nothing
// outgoing.
$logs = array();
}
if (!$logs) {
$this->setBaseCommitExplanation(
"you have no outgoing commits, so arc assumes you intend to submit ".
"uncommitted changes in the working copy.");
// In Mercurial, we support operations against uncommitted changes.
$this->setRelativeCommit($this->getWorkingCopyRevision());
return $this->relativeCommit;
}
$outgoing_revs = ipull($logs, 'rev');
// This is essentially an implementation of a theoretical `hg merge-base`
// command.
$against = $this->getWorkingCopyRevision();
while (true) {
// NOTE: The "^" and "~" syntaxes were only added in hg 1.9, which is
// new as of July 2011, so do this in a compatible way. Also, "hg log"
// and "hg outgoing" don't necessarily show parents (even if given an
// explicit template consisting of just the parents token) so we need
// to separately execute "hg parents".
list($stdout) = $this->execxLocal(
'parents --style default --rev %s',
$against);
$parents_logs = ArcanistMercurialParser::parseMercurialLog($stdout);
list($p1, $p2) = array_merge($parents_logs, array(null, null));
if ($p1 && !in_array($p1['rev'], $outgoing_revs)) {
$against = $p1['rev'];
break;
} else if ($p2 && !in_array($p2['rev'], $outgoing_revs)) {
$against = $p2['rev'];
break;
} else if ($p1) {
$against = $p1['rev'];
} else {
// This is the case where you have a new repository and the entire
// thing is outgoing; Mercurial literally accepts "--rev null" as
// meaning "diff against the empty state".
$against = 'null';
break;
}
}
if ($against == 'null') {
$this->setBaseCommitExplanation(
"this is a new repository (all changes are outgoing).");
} else {
$this->setBaseCommitExplanation(
"it is the first commit reachable from the working copy state ".
"which is not outgoing.");
}
$this->setRelativeCommit($against);
}
return $this->relativeCommit;
}
public function getLocalCommitInformation() {
if ($this->localCommitInfo === null) {
list($info) = $this->execxLocal(
2012-07-01 11:06:05 -07:00
"log --template '%C' --rev %s --branch %s --",
"{node}\1{rev}\1{author}\1{date|rfc822date}\1".
"{branch}\1{tag}\1{parents}\1{desc}\2",
2012-07-01 11:06:05 -07:00
'(ancestors(.) - ancestors('.$this->getRelativeCommit().'))',
$this->getBranchName());
$logs = array_filter(explode("\2", $info));
$last_node = null;
$futures = array();
$commits = array();
foreach ($logs as $log) {
list($node, $rev, $author, $date, $branch, $tag, $parents, $desc) =
explode("\1", $log);
// NOTE: If a commit has only one parent, {parents} returns empty.
// If it has two parents, {parents} returns revs and short hashes, not
// full hashes. Try to avoid making calls to "hg parents" because it's
// relatively expensive.
$commit_parents = null;
if (!$parents) {
if ($last_node) {
$commit_parents = array($last_node);
}
}
if (!$commit_parents) {
// We didn't get a cheap hit on previous commit, so do the full-cost
// "hg parents" call. We can run these in parallel, at least.
$futures[$node] = $this->execFutureLocal(
"parents --template='{node}\\n' --rev %s",
$node);
}
$commits[$node] = array(
'author' => $author,
'time' => strtotime($date),
'branch' => $branch,
'tag' => $tag,
'commit' => $node,
'rev' => $node, // TODO: Remove eventually.
'local' => $rev,
'parents' => $commit_parents,
'summary' => head(explode("\n", $desc)),
'message' => $desc,
);
$last_node = $node;
}
foreach (Futures($futures)->limit(4) as $node => $future) {
list($parents) = $future->resolvex();
$parents = array_filter(explode("\n", $parents));
$commits[$node]['parents'] = $parents;
}
// Put commits in newest-first order, to be consistent with Git and the
// expected order of "hg log" and "git log" under normal circumstances.
// The order of ancestors() is oldest-first.
$commits = array_reverse($commits);
$this->localCommitInfo = $commits;
}
return $this->localCommitInfo;
}
public function getBlame($path) {
list($stdout) = $this->execxLocal(
'annotate -u -v -c --rev %s -- %s',
$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() {
if (!isset($this->status)) {
// 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();
if (!$diff) {
$this->status = array();
return $this->status;
}
$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) = $this->execxLocal('status');
$working_status = ArcanistMercurialParser::parseMercurialStatus($stdout);
foreach ($working_status as $path => $status) {
if ($status & ArcanistRepositoryAPI::FLAG_UNTRACKED) {
// If the file is untracked, don't mark it uncommitted.
continue;
}
$status |= self::FLAG_UNCOMMITTED;
if (!empty($status_map[$path])) {
$status_map[$path] |= $status;
} else {
$status_map[$path] = $status;
}
}
$this->status = $status_map;
}
return $this->status;
}
private function getDiffOptions() {
$options = array(
'--git',
'-U'.$this->getDiffLinesOfContext(),
);
return implode(' ', $options);
}
public function getRawDiffText($path) {
$options = $this->getDiffOptions();
2012-07-01 11:06:05 -07:00
// NOTE: In Mercurial, "--rev x" means "diff between x and the working
// copy state", while "--rev x..." means "diff between x and the working
// copy commit" (i.e., from 'x' to '.'). The latter excludes any dirty
// changes in the working copy.
$range = $this->getRelativeCommit();
if (!$this->includeDirectoryStateInDiffs) {
$range .= '...';
}
list($stdout) = $this->execxLocal(
2012-07-01 11:06:05 -07:00
'diff %C --rev %s -- %s',
$options,
2012-07-01 11:06:05 -07:00
$range,
$path);
return $stdout;
}
public function getFullMercurialDiff() {
2012-07-01 11:06:05 -07:00
return $this->getRawDiffText('');
}
public function getOriginalFileData($path) {
return $this->getFileDataAtRevision($path, $this->getRelativeCommit());
}
public function getCurrentFileData($path) {
return $this->getFileDataAtRevision(
$path,
$this->getWorkingCopyRevision());
}
private function getFileDataAtRevision($path, $revision) {
list($err, $stdout) = $this->execManualLocal(
'cat --rev %s -- %s',
$revision,
$path);
if ($err) {
// Assume this is "no file at revision", i.e. a deleted or added file.
return null;
} else {
return $stdout;
}
}
public function getWorkingCopyRevision() {
2012-07-01 11:06:05 -07:00
return '.';
}
public function isHistoryDefaultImmutable() {
return true;
}
public function supportsAmend() {
list ($err, $stdout) = $this->execManualLocal('help commit');
if ($err) {
return false;
} else {
return (strstr($stdout, "amend") !== false);
}
}
public function supportsRelativeLocalCommits() {
return true;
}
public function hasLocalCommit($commit) {
try {
$this->getCanonicalRevisionName($commit);
return true;
} catch (Exception $ex) {
return false;
}
}
2012-07-01 11:06:05 -07:00
public function getCommitMessage($commit) {
list($message) = $this->execxLocal(
'log --template={desc} --rev %s',
$commit);
return $message;
}
public function parseRelativeLocalCommit(array $argv) {
if (count($argv) == 0) {
return;
}
if (count($argv) != 1) {
throw new ArcanistUsageException("Specify only one commit.");
}
$this->setBaseCommitExplanation("you explicitly specified it.");
// This does the "hg id" call we need to normalize/validate the revision
// identifier.
$this->setRelativeCommit(reset($argv));
}
public function getAllLocalChanges() {
$diff = $this->getFullMercurialDiff();
if (!strlen(trim($diff))) {
return array();
}
$parser = new ArcanistDiffParser();
return $parser->parseDiff($diff);
}
public function supportsLocalBranchMerge() {
return true;
}
public function performLocalBranchMerge($branch, $message) {
if ($branch) {
$err = phutil_passthru(
'(cd %s && HGPLAIN=1 hg merge --rev %s && hg commit -m %s)',
$this->getPath(),
$branch,
$message);
} else {
$err = phutil_passthru(
'(cd %s && HGPLAIN=1 hg merge && hg commit -m %s)',
$this->getPath(),
$message);
}
if ($err) {
throw new ArcanistUsageException("Merge failed!");
}
}
public function getFinalizedRevisionMessage() {
return "You may now push this commit upstream, as appropriate (e.g. with ".
"'hg push' or by printing and faxing it).";
}
public function getCommitMessageLog() {
list($stdout) = $this->execxLocal(
2012-07-01 11:06:05 -07:00
"log --template '{node}\\2{desc}\\1' --rev %s --branch %s --",
'ancestors(.) - ancestors('.$this->getRelativeCommit().')',
$this->getBranchName());
$map = array();
2012-07-01 11:06:05 -07:00
$logs = explode("\1", trim($stdout));
foreach (array_filter($logs) as $log) {
2012-07-01 11:06:05 -07:00
list($node, $desc) = explode("\2", $log);
$map[$node] = $desc;
}
return array_reverse($map);
}
public function loadWorkingCopyDifferentialRevisions(
ConduitClient $conduit,
array $query) {
$messages = $this->getCommitMessageLog();
$parser = new ArcanistDiffParser();
// First, try to find revisions by explicit revision IDs in commit messages.
$reason_map = array();
$revision_ids = array();
foreach ($messages as $node_id => $message) {
$object = ArcanistDifferentialCommitMessage::newFromRawCorpus($message);
if ($object->getRevisionID()) {
$revision_ids[] = $object->getRevisionID();
$reason_map[$object->getRevisionID()] = $node_id;
}
}
if ($revision_ids) {
$results = $conduit->callMethodSynchronous(
'differential.query',
$query + array(
'ids' => $revision_ids,
));
foreach ($results as $key => $result) {
$hash = substr($reason_map[$result['id']], 0, 16);
$results[$key]['why'] =
"Commit message for '{$hash}' has explicit 'Differential Revision'.";
}
return $results;
}
// Try to find revisions by hash.
$hashes = array();
foreach ($this->getLocalCommitInformation() as $commit) {
$hashes[] = array('hgcm', $commit['commit']);
}
$results = $conduit->callMethodSynchronous(
'differential.query',
$query + array(
'commitHashes' => $hashes,
));
foreach ($results as $key => $hash) {
$results[$key]['why'] =
"A mercurial commit hash in the commit range is already attached ".
"to the Differential revision.";
}
return $results;
}
public function updateWorkingCopy() {
$this->execxLocal('up');
}
public function amendCommit($message) {
$tmp_file = new TempFile();
Filesystem::writeFile($tmp_file, $message);
$this->execxLocal(
'commit --amend -l %s',
$tmp_file);
}
public function setIncludeDirectoryStateInDiffs($include) {
$this->includeDirectoryStateInDiffs = $include;
return $this;
}
public function getCommitSummary($commit) {
if ($commit == 'null') {
return '(The Empty Void)';
}
list($summary) = $this->execxLocal(
'log --template {desc} --limit 1 --rev %s',
$commit);
$summary = head(explode("\n", $summary));
return trim($summary);
}
[Arcanist] mercurial base commit DSL support Summary: This adds Mercurial support to the base commit DSL that's equivalent to git's, though not identical. Specifically, Mercurial has arc:outgoing instead of arc:upstream, and hg:gca(commit) instead of git:merge-base(commit), though they have similar behaviors. Test Plan: [13:37:13 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ hg log -G -r master:: @ changeset: 11:6970e9263ab8 | bookmark: something | tag: tip | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:03:46 2012 -0700 | summary: thing | o changeset: 10:433a93023f03 | parent: 8:f47ccfe34267 | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:03:34 2012 -0700 | summary: some | | o changeset: 9:c8379ef32b0d |/ bookmark: other | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:01:04 2012 -0700 | summary: other | o changeset: 8:f47ccfe34267 | bookmark: master | user: David Schleimer <dschleimer@fb.com> | date: Mon Jun 04 14:30:47 2012 -0700 | summary: typo-fix | [13:38:00 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ ~/devtools/arcanist/bin/arc which --show-base --base hg:.^ .^ [13:38:11 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ ~/devtools/arcanist/bin/arc which --show-base --base 'hg:gca(other)' f47ccfe34267592dd2e336174a3a311b8783c024 [13:38:21 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ hg id -r f47ccfe34267592dd2e336174a3a311b8783c024 f47ccfe34267 master [13:38:31 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20537 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:empty' null [13:38:44 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20538 $ hg id -r null 000000000000 [13:38:48 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20538 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:outgoing' f47ccfe34267592dd2e336174a3a311b8783c024 Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1233 Differential Revision: https://secure.phabricator.com/D2822
2012-06-21 18:12:09 -07:00
public function resolveBaseCommitRule($rule, $source) {
list($type, $name) = explode(':', $rule, 2);
switch ($type) {
case 'hg':
$matches = null;
if (preg_match('/^gca\((.+)\)$/', $name, $matches)) {
list($err, $merge_base) = $this->execManualLocal(
'log --template={node} --rev %s',
sprintf('ancestor(., %s)', $matches[1]));
if (!$err) {
$this->setBaseCommitExplanation(
"it is the greatest common ancestor of '{$matches[1]}' and ., as".
"specified by '{$rule}' in your {$source} 'base' ".
"configuration.");
return trim($merge_base);
}
} else {
list($err) = $this->execManualLocal(
'id -r %s',
$name);
if (!$err) {
$this->setBaseCommitExplanation(
"it is specified by '{$rule}' in your {$source} 'base' ".
"configuration.");
return $name;
}
}
break;
case 'arc':
switch ($name) {
case 'empty':
$this->setBaseCommitExplanation(
"you specified '{$rule}' in your {$source} 'base' ".
"configuration.");
return 'null';
case 'outgoing':
list($err, $outgoing_base) = $this->execManualLocal(
'log --template={node} --rev %s',
'limit(reverse(ancestors(.) - outgoing()), 1)'
);
if (!$err) {
$this->setBaseCommitExplanation(
"it is the first ancestor of the working copy that is not ".
"outgoing, and it matched the rule {$rule} in your {$source} ".
"'base' configuration.");
return trim($outgoing_base);
}
2012-07-01 11:06:05 -07:00
case 'amended':
$text = $this->getCommitMessage('.');
$message = ArcanistDifferentialCommitMessage::newFromRawCorpus(
$text);
if ($message->getRevisionID()) {
$this->setBaseCommitExplanation(
"'.' has been amended with 'Differential Revision:', ".
"as specified by '{$rule}' in your {$source} 'base' ".
"configuration.");
// NOTE: This should be safe because Mercurial doesn't support
// amend until 2.2.
return '.^';
}
break;
[Arcanist] add an arc:bookmark base for Mercurial Summary: This adds a new base option that Does the Right Thing (or as close to it as I can get) for someone using Mercurial bookmarks as lightweight branches, and where each branch should be one differential revision. Specifically, it walks backwards through the ancestors of the working copy until it finds a revision that is either not outgoing (ie already in the remote) or that is bookmarked. This means that bookmarks effectively act as delimiters, and point at the last revision that will be included in an arc diff. Test Plan: [14:40:54 Tue Jun 26 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 1226 $ hg log -r '(first(outgoing())^)::' --template='node: {node}\nbookmark: {bookmarks}\n\n' -G @ node: c8379ef32b0d0e6cf94fe636751ea4fe1353e157 | bookmark: | | o node: 14f03139049cbda339190b814e52f4ec8b05c431 | | bookmark: more | | | o node: 6970e9263ab8c6da428420606d1f15c9980da183 | | bookmark: something | | | o node: 433a93023f03d5f3eddaa243fa973d32a1566aee |/ bookmark: | o node: f47ccfe34267592dd2e336174a3a311b8783c024 | bookmark: | [14:41:00 Tue Jun 26 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 1226 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:bookmark' f47ccfe34267592dd2e336174a3a311b8783c024 [14:41:05 Tue Jun 26 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 1226 $ hg up 14f03139049cbda339190b814e52f4ec8b05c431 3 files updated, 0 files merged, 1 files removed, 0 files unresolved [14:41:10 Tue Jun 26 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 1226 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:bookmark' 6970e9263ab8c6da428420606d1f15c9980da183 [14:41:14 Tue Jun 26 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 1226 $ hg up 6970e9263ab8c6da428420606d1f15c9980da183 0 files updated, 0 files merged, 1 files removed, 0 files unresolved [14:41:44 Tue Jun 26 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 1227 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:bookmark' f47ccfe34267592dd2e336174a3a311b8783c024 Reviewers: epriestley, bos Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1331 Differential Revision: https://secure.phabricator.com/D2863
2012-06-26 15:04:35 -07:00
case 'bookmark':
$revset =
'limit('.
' sort('.
' (ancestors(.) and bookmark() - .) or'.
' (ancestors(.) - outgoing()), '.
' -rev),'.
'1)';
list($err, $bookmark_base) = $this->execManualLocal(
'log --template={node} --rev %s',
$revset);
if (!$err) {
$this->setBaseCommitExplanation(
"it is the first ancestor of . that either has a bookmark, or ".
"is already in the remote and it matched the rule {$rule} in ".
"your {$source} 'base' configuration");
return trim($bookmark_base);
}
[Arcanist] mercurial base commit DSL support Summary: This adds Mercurial support to the base commit DSL that's equivalent to git's, though not identical. Specifically, Mercurial has arc:outgoing instead of arc:upstream, and hg:gca(commit) instead of git:merge-base(commit), though they have similar behaviors. Test Plan: [13:37:13 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ hg log -G -r master:: @ changeset: 11:6970e9263ab8 | bookmark: something | tag: tip | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:03:46 2012 -0700 | summary: thing | o changeset: 10:433a93023f03 | parent: 8:f47ccfe34267 | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:03:34 2012 -0700 | summary: some | | o changeset: 9:c8379ef32b0d |/ bookmark: other | user: David Schleimer <dschleimer@fb.com> | date: Thu Jun 21 13:01:04 2012 -0700 | summary: other | o changeset: 8:f47ccfe34267 | bookmark: master | user: David Schleimer <dschleimer@fb.com> | date: Mon Jun 04 14:30:47 2012 -0700 | summary: typo-fix | [13:38:00 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ ~/devtools/arcanist/bin/arc which --show-base --base hg:.^ .^ [13:38:11 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ ~/devtools/arcanist/bin/arc which --show-base --base 'hg:gca(other)' f47ccfe34267592dd2e336174a3a311b8783c024 [13:38:21 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20536 $ hg id -r f47ccfe34267592dd2e336174a3a311b8783c024 f47ccfe34267 master [13:38:31 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20537 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:empty' null [13:38:44 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20538 $ hg id -r null 000000000000 [13:38:48 Thu Jun 21 2012] dschleimer@dev4022.snc6 ~/hg-dummy hg-dummy 20538 $ ~/devtools/arcanist/bin/arc which --show-base --base 'arc:outgoing' f47ccfe34267592dd2e336174a3a311b8783c024 Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1233 Differential Revision: https://secure.phabricator.com/D2822
2012-06-21 18:12:09 -07:00
}
break;
default:
return null;
}
return null;
}
[Arcanist] collect svn and bookmark info for Mercurial Summary: This increases the amount of information arc diff collects in Mercurial repositories. IN particular, it collects the active bookmark, if there is one, and svn information in hgsubversion repositories. The Phabricator half of this is https://secure.phabricator.com/D2897 Test Plan: [14:06:59 Sat Jun 30 2012] dschleimer@dev4022.snc6 ~/www-hg www-hg 2941 $ ~/devtools/arcanist/bin/arc diff --only --conduit-uri http://phabricator.dschleimer.dev4022.facebook.com HipHop Notice: Undefined index: 1 in /data/users/dschleimer/devtools/arcanist/src/repository/api/ArcanistMercurialAPI.php on line 708 Created a new Differential diff: Diff URI: http://phabricator.dschleimer.dev4022.facebook.com/differential/diff/126/ Included changes: A foo HipHop Notice: Undefined index: 1 in /data/users/dschleimer/www-hg/lib/arcanist/arcanist/FacebookArcanistConfiguration.php on line 81 mcproxy on this server is out of date version: , expected version: please restart (http://fburl.com/1787362) [14:07:46 Sat Jun 30 2012] dschleimer@dev4022.snc6 ~/www-hg www-hg 2941 $ echo '{"diff_id": 126}' | ~/devtools/arcanist/bin/arc call-conduit differential.getdiff --conduit-uri http://phabricator.dschleimer.dev4022.facebook.com | json_pretty | egrep -i 'bookmark|sourcecontrol' "bookmark": "bar", "sourceControlBaseRevision": "svn+ssh://tubbs/svnroot/tfb/trunk/www@583442", "sourceControlPath": "svn+ssh://tubbs/svnroot/tfb/trunk/www", "sourceControlSystem": "hg", Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T1331 Differential Revision: https://secure.phabricator.com/D2896
2012-06-30 16:00:48 -07:00
public function getSubversionInfo() {
$info = array();
$base_path = null;
$revision = null;
list($err, $raw_info) = $this->execManualLocal('svn info');
if (!$err) {
foreach (explode("\n", trim($raw_info)) as $line) {
list($key, $value) = explode(': ', $line, 2);
switch ($key) {
case 'URL':
$info['base_path'] = $value;
$base_path = $value;
break;
case 'Repository UUID':
$info['uuid'] = $value;
break;
case 'Revision':
$revision = $value;
break;
default:
break;
}
}
if ($base_path && $revision) {
$info['base_revision'] = $base_path.'@'.$revision;
}
}
return $info;
}
public function getActiveBookmark() {
list($raw_output) = $this->execxLocal('bookmarks');
$raw_output = trim($raw_output);
if ($raw_output !== 'no bookmarks set') {
foreach (explode("\n", $raw_output) as $line) {
$line = trim($line);
if ('*' === $line[0]) {
return idx(explode(' ', $line, 3), 1);
}
}
}
return null;
}
}