1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-25 00:02:40 +01:00

Allow arc to identify repositories without "project_id"

Summary:
Ref T4343. Continues the process of reducing the prominence of Arcanist Projects. Primarily:

  - Query Phabricator to identify the working copy based on explicit configuration, or guess based on heuristics.
  - Enhance `arc which` to explain the process to the user.
  - The `project_id` key is no longer required in `.arcconfig`.

Minor/cleanup changes:

  - Rename `project_id` to `project.name` (consistency, clarity).
  - Rename `conduit_uri` to `phabricator.uri` (consistency, clairty).
  - These both need documentation updates.
  - Add `repository.callsign` to explicitly bind to a repository.
  - Updated `.arcconfig` for the new values.
  - Fix a unit test which broke a while ago when we fixed a rare definition of "unstaged".
  - Make `getRepositoryUUID()` generic so we can get rid of one `instanceof`.

Test Plan:
  - Ran `arc which`.
  - Ran `arc diff`.
  - This doesn't really change anything, so the only real risk is version compatibility breaks. This //does// introduce such a break, but the window is very narrow: if you upgrade `arc` after this commit, and try to diff against a Phabricator which was updated after yesterday (D8068) but before D8072 lands, the lookup will work so we'll add `repositoryPHID` to the `differential.creatediff` call, but it won't exist in Phabricator yet. This window is so narrow that I'm not going to try to fix it, as I'd guess there is a significant chance that no users will be affected. I don't see a clever way to fix it that doesn't involve a lot of work, either.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T4343

Differential Revision: https://secure.phabricator.com/D8073
This commit is contained in:
epriestley 2014-01-26 15:31:30 -08:00
parent e4b1e8e681
commit a7376624b4
13 changed files with 310 additions and 27 deletions

View file

@ -1,6 +1,5 @@
{ {
"project_id" : "arcanist", "phabricator.uri" : "https://secure.phabricator.com/",
"conduit_uri" : "https://secure.phabricator.com/",
"lint.engine" : "PhutilLintEngine", "lint.engine" : "PhutilLintEngine",
"unit.engine" : "PhutilUnitTestEngine", "unit.engine" : "PhutilUnitTestEngine",
"load" : [ "load" : [

View file

@ -204,8 +204,8 @@ try {
if ($force_conduit) { if ($force_conduit) {
$conduit_uri = $force_conduit; $conduit_uri = $force_conduit;
} else { } else {
$project_conduit_uri = $project_conduit_uri = $configuration_manager->getProjectConfig(
$configuration_manager->getProjectConfig('conduit_uri'); 'phabricator.uri');
if ($project_conduit_uri) { if ($project_conduit_uri) {
$conduit_uri = $project_conduit_uri; $conduit_uri = $project_conduit_uri;
} else { } else {

View file

@ -205,7 +205,7 @@ phutil_register_library_map(array(
'ArcanistBundleTestCase' => 'ArcanistTestCase', 'ArcanistBundleTestCase' => 'ArcanistTestCase',
'ArcanistCSSLintLinter' => 'ArcanistExternalLinter', 'ArcanistCSSLintLinter' => 'ArcanistExternalLinter',
'ArcanistCSSLintLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistCSSLintLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistCSharpLinter' => 'ArcanistFutureLinter', 'ArcanistCSharpLinter' => 'ArcanistLinter',
'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCapabilityNotSupportedException' => 'Exception', 'ArcanistCapabilityNotSupportedException' => 'Exception',
'ArcanistChooseInvalidRevisionException' => 'Exception', 'ArcanistChooseInvalidRevisionException' => 'Exception',

View file

@ -32,6 +32,32 @@ final class ArcanistSettings {
'unit test engines.', 'unit test engines.',
'example' => '["/var/arc/customlib/src"]', 'example' => '["/var/arc/customlib/src"]',
), ),
'repository.callsign' => array(
'type' => 'string',
'example' => '"X"',
'help' => pht(
'Associate the working copy with a specific Phabricator repository. '.
'Normally, arc can figure this association out on its own, but if '.
'your setup is unusual you can use this option to tell it what the '.
'desired value is.'),
),
'phabricator.uri' => array(
'type' => 'string',
'legacy' => 'conduit_uri',
'example' => '"https://phabricator.mycompany.com/"',
'help' => pht(
'Associates this working copy with a specific installation of '.
'Phabricator.'),
),
'project.name' => array(
'type' => 'string',
'legacy' => 'project_id',
'example' => '"arcanist"',
'help' => pht(
'Associates this working copy with a named Arcanist Project. '.
'This is primarily useful if you use SVN and have several different '.
'projects in the same repository.'),
),
'lint.engine' => array( 'lint.engine' => array(
'type' => 'string', 'type' => 'string',
'legacy' => 'lint_engine', 'legacy' => 'lint_engine',

View file

@ -369,6 +369,18 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
if (preg_match('/^\* ([^\(].*)$/m', $stdout, $matches)) { if (preg_match('/^\* ([^\(].*)$/m', $stdout, $matches)) {
return $matches[1]; return $matches[1];
} }
return null;
}
public function getRemoteURI() {
list($stdout) = $this->execxLocal('remote show -n origin');
$matches = null;
if (preg_match('/^\s*Fetch URL: (.*)$/m', $stdout, $matches)) {
return trim($matches[1]);
}
return null; return null;
} }

View file

@ -1038,4 +1038,16 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
return array(trim($name), trim($rev)); return array(trim($name), trim($rev));
} }
public function getRemoteURI() {
list($stdout) = $this->execxLocal('paths default');
$stdout = trim($stdout);
if (strlen($stdout)) {
return $stdout;
}
return null;
}
} }

View file

@ -330,6 +330,8 @@ abstract class ArcanistRepositoryAPI {
abstract public function loadWorkingCopyDifferentialRevisions( abstract public function loadWorkingCopyDifferentialRevisions(
ConduitClient $conduit, ConduitClient $conduit,
array $query); array $query);
abstract public function getRemoteURI();
public function getUnderlyingWorkingCopyRevision() { public function getUnderlyingWorkingCopyRevision() {
return $this->getWorkingCopyRevision(); return $this->getWorkingCopyRevision();
@ -643,4 +645,8 @@ abstract class ArcanistRepositoryAPI {
return $commit; return $commit;
} }
public function getRepositoryUUID() {
return null;
}
} }

View file

@ -243,6 +243,10 @@ final class ArcanistSubversionAPI extends ArcanistRepositoryAPI {
return 'svn'; return 'svn';
} }
public function getRemoteURI() {
return idx($this->getSVNInfo('/'), 'Repository Root');
}
public function buildInfoFuture($path) { public function buildInfoFuture($path) {
if ($path == '/') { if ($path == '/') {
// When the root of a working copy is referenced by a symlink and you // When the root of a working copy is referenced by a symlink and you
@ -587,7 +591,7 @@ EODIFF;
return null; return null;
} }
public function getRepositorySVNUUID() { public function getRepositoryUUID() {
$info = $this->getSVNInfo('/'); $info = $this->getSVNInfo('/');
return $info['Repository UUID']; return $info['Repository UUID'];
} }

View file

@ -87,6 +87,7 @@ final class ArcanistRepositoryAPIStateTestCase extends ArcanistTestCase {
'UNSTAGED' => $f_mod | $f_uns | $f_unc, 'UNSTAGED' => $f_mod | $f_uns | $f_unc,
'UNTRACKED' => $f_unt, 'UNTRACKED' => $f_unt,
); );
$this->assertEqual($expect_uncommitted, $api->getUncommittedStatus()); $this->assertEqual($expect_uncommitted, $api->getUncommittedStatus());
$expect_range = array( $expect_range = array(

View file

@ -31,7 +31,8 @@
* *
* @task conduit Conduit * @task conduit Conduit
* @task scratch Scratch Files * @task scratch Scratch Files
* @group workflow * @task phabrep Phabricator Repositories
*
* @stable * @stable
*/ */
abstract class ArcanistBaseWorkflow extends Phobject { abstract class ArcanistBaseWorkflow extends Phobject {
@ -64,6 +65,8 @@ abstract class ArcanistBaseWorkflow extends Phobject {
private $stashed; private $stashed;
private $projectInfo; private $projectInfo;
private $repositoryInfo;
private $repositoryReasons;
private $arcanistConfiguration; private $arcanistConfiguration;
private $parentWorkflow; private $parentWorkflow;
@ -1518,4 +1521,187 @@ abstract class ArcanistBaseWorkflow extends Phobject {
return $this->repositoryVersion; return $this->repositoryVersion;
} }
/* -( Phabricator Repositories )------------------------------------------- */
/**
* Get the PHID of the Phabricator repository this working copy corresponds
* to. Returns `null` no repository can be identified.
*
* @return phid|null Repository PHID, or null if no repository can be
* identified.
*
* @task phabrep
*/
protected function getRepositoryPHID() {
return idx($this->getRepositoryInformation(), 'phid');
}
/**
* Get the callsign of the Phabricator repository this working copy
* corresponds to. Returns `null` no repository can be identified.
*
* @return string|null Repository callsign, or null if no repository can be
* identified.
*
* @task phabrep
*/
protected function getRepositoryCallsign() {
return idx($this->getRepositoryInformation(), 'callsign');
}
/**
* Get human-readable reasoning explaining how `arc` evaluated which
* Phabricator repository corresponds to this working copy. Used by
* `arc which` to explain the process to users.
*
* @return list<string> Human-readable explanation of the repository
* association process.
*
* @task phabrep
*/
protected function getRepositoryReasons() {
$this->getRepositoryInformation();
return $this->repositoryReasons;
}
/**
* @task phabrep
*/
private function getRepositoryInformation() {
if ($this->repositoryInfo === null) {
list($info, $reasons) = $this->loadRepositoryInformation();
$this->repositoryInfo = $info;
$this->repositoryReasons = $reasons;
}
return $this->repositoryInfo;
}
/**
* @task phabrep
*/
private function loadRepositoryInformation() {
list($query, $reasons) = $this->getRepositoryQuery();
if (!$query) {
return array(null, $reasons);
}
try {
$results = $this->getConduit()->callMethodSynchronous(
'repository.query',
$query);
} catch (ConduitClientException $ex) {
if ($ex->getErrorCode() == 'ERR-CONDUIT-CALL') {
$reasons[] = pht(
'This version of Arcanist is more recent than the version of '.
'Phabricator you are connecting to: the Phabricator install is '.
'out of date and does not have support for identifying '.
'repositories by callsign or URI. Update Phabricator to enable '.
'these features.');
return array(null, $reasons);
}
throw $ex;
}
$result = null;
if (!$results) {
$reasons[] = pht(
'No repositories matched the query. Check that your configuration '.
'is correct, or use "repository.callsign" to select a repository '.
'explicitly.');
} else if (count($results) > 1) {
$reasons[] = pht(
'Multiple repostories (%s) matched the query. You can use the '.
'"repository.callsign" configuration to select the one you want.',
implode(', ', ipull($results, 'callsign')));
} else {
$result = head($results);
$reasons[] = pht('Found a unique matching repository.');
}
return array($result, $reasons);
}
/**
* @task phabrep
*/
private function getRepositoryQuery() {
$reasons = array();
$callsign = $this->getConfigFromAnySource('repository.callsign');
if ($callsign) {
$query = array(
'callsigns' => array($callsign),
);
$reasons[] = pht(
'Configuration value "repository.callsign" is set to "%s".',
$callsign);
return array($query, $reasons);
} else {
$reasons[] = pht(
'Configuration value "repository.callsign" is empty.');
}
$project_info = $this->getProjectInfo();
if ($this->getProjectInfo()) {
if (!empty($project_info['repository']['callsign'])) {
$callsign = $project_info['repository']['callsign'];
$query = array(
'callsigns' => array($callsign),
);
$reasons[] = pht(
'Configuration value "project.id" is set to "%s"; this project '.
'is associated with the "%s" repository.',
$this->getWorkingCopy()->getProjectID(),
$callsign);
return array($query, $reasons);
} else {
$reasons[] = pht(
'Configuration value "project.id" is set to "%s", but this '.
'project is not associated with a repository.');
}
} else {
$reasons[] = pht(
'Configuration value "project.id" is empty.');
}
$uuid = $this->getRepositoryAPI()->getRepositoryUUID();
if ($uuid !== null) {
$query = array(
'uuids' => array($uuid),
);
$reasons[] = pht(
'The UUID for this working copy is "%s".',
$uuid);
return array($query, $reasons);
} else {
$reasons[] = pht(
'This repository has no VCS UUID (this is normal for git/hg).');
}
$remote_uri = $this->getRepositoryAPI()->getRemoteURI();
if ($remote_uri !== null) {
$query = array(
'remoteURIs' => array($remote_uri),
);
$reasons[] = pht(
'The remote URI for this working copy is "%s".',
$remote_uri);
return array($query, $reasons);
} else {
$reasons[] = pht(
'Unable to determine the remote URI for this repository.');
}
return array(null, $reasons);
}
} }

View file

@ -508,9 +508,9 @@ EOTEXT
} }
$diff_spec = array( $diff_spec = array(
'changes' => mpull($changes, 'toDictionary'), 'changes' => mpull($changes, 'toDictionary'),
'lintStatus' => $this->getLintStatus($lint_result), 'lintStatus' => $this->getLintStatus($lint_result),
'unitStatus' => $this->getUnitStatus($unit_result), 'unitStatus' => $this->getUnitStatus($unit_result),
) + $this->buildDiffSpecification(); ) + $this->buildDiffSpecification();
$conduit = $this->getConduit(); $conduit = $this->getConduit();
@ -2243,6 +2243,7 @@ EOTEXT
$vcs = $repository_api->getSourceControlSystemName(); $vcs = $repository_api->getSourceControlSystemName();
$source_path = $repository_api->getPath(); $source_path = $repository_api->getPath();
$branch = $repository_api->getBranchName(); $branch = $repository_api->getBranchName();
$repo_uuid = $repository_api->getRepositoryUUID();
if ($repository_api instanceof ArcanistGitAPI) { if ($repository_api instanceof ArcanistGitAPI) {
$info = $this->getGitParentLogInfo(); $info = $this->getGitParentLogInfo();
@ -2258,8 +2259,6 @@ EOTEXT
if ($info['uuid']) { if ($info['uuid']) {
$repo_uuid = $info['uuid']; $repo_uuid = $info['uuid'];
} }
} else if ($repository_api instanceof ArcanistSubversionAPI) {
$repo_uuid = $repository_api->getRepositorySVNUUID();
} else if ($repository_api instanceof ArcanistMercurialAPI) { } else if ($repository_api instanceof ArcanistMercurialAPI) {
$bookmark = $repository_api->getActiveBookmark(); $bookmark = $repository_api->getActiveBookmark();
@ -2280,7 +2279,7 @@ EOTEXT
$project_id = $this->getWorkingCopy()->getProjectID(); $project_id = $this->getWorkingCopy()->getProjectID();
} }
return array( $data = array(
'sourceMachine' => php_uname('n'), 'sourceMachine' => php_uname('n'),
'sourcePath' => $source_path, 'sourcePath' => $source_path,
'branch' => $branch, 'branch' => $branch,
@ -2288,12 +2287,16 @@ EOTEXT
'sourceControlSystem' => $vcs, 'sourceControlSystem' => $vcs,
'sourceControlPath' => $base_path, 'sourceControlPath' => $base_path,
'sourceControlBaseRevision' => $base_revision, 'sourceControlBaseRevision' => $base_revision,
'parentRevisionID' => $parent,
'repositoryUUID' => $repo_uuid,
'creationMethod' => 'arc', 'creationMethod' => 'arc',
'arcanistProject' => $project_id, 'arcanistProject' => $project_id,
'authorPHID' => $this->getUserPHID(),
); );
$repository_phid = $this->getRepositoryPHID();
if ($repository_phid) {
$data['repositoryPHID'] = $repository_phid;
}
return $data;
} }

View file

@ -22,7 +22,8 @@ EOTEXT
public function getCommandHelp() { public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT return phutil_console_format(<<<EOTEXT
Supports: svn, git, hg Supports: svn, git, hg
Shows which commits 'arc diff' will select, and which revision is in Shows which repository the current working copy corresponds to,
which commits 'arc diff' will select, and which revision is in
the working copy (or which revisions, if more than one matches). the working copy (or which revisions, if more than one matches).
EOTEXT EOTEXT
); );
@ -69,6 +70,11 @@ EOTEXT
public function run() { public function run() {
$console = PhutilConsole::getConsole();
$this->printRepositorySection();
$console->writeOut("\n");
$repository_api = $this->getRepositoryAPI(); $repository_api = $this->getRepositoryAPI();
$arg_commit = $this->getArgument('commit'); $arg_commit = $this->getArgument('commit');
@ -184,4 +190,34 @@ EOTEXT
return 0; return 0;
} }
private function printRepositorySection() {
$console = PhutilConsole::getConsole();
$console->writeOut("**%s**\n", pht('REPOSITORY'));
$callsign = $this->getRepositoryCallsign();
$console->writeOut(
"%s\n\n",
pht(
'To identify the repository associated with this working copy, '.
'arc followed this process:'));
foreach ($this->getRepositoryReasons() as $reason) {
$reason = phutil_console_wrap($reason, 4);
$console->writeOut("%s\n\n", $reason);
}
if ($callsign) {
$console->writeOut(
"%s\n",
pht('This working copy is associated with the %s repository.',
phutil_console_format('**%s**', $callsign)));
} else {
$console->writeOut(
"%s\n",
pht('This working copy is not associated with any repository.'));
}
}
} }

View file

@ -187,6 +187,7 @@ final class ArcanistWorkingCopyIdentity {
private static function parseRawConfigFile($raw_config, $from_where) { private static function parseRawConfigFile($raw_config, $from_where) {
$proj = json_decode($raw_config, true); $proj = json_decode($raw_config, true);
if (!is_array($proj)) { if (!is_array($proj)) {
throw new Exception( throw new Exception(
"Unable to parse '.arcconfig' file '{$from_where}'. The file contents ". "Unable to parse '.arcconfig' file '{$from_where}'. The file contents ".
@ -194,16 +195,7 @@ final class ArcanistWorkingCopyIdentity {
"FILE CONTENTS\n". "FILE CONTENTS\n".
substr($raw_config, 0, 2048)); substr($raw_config, 0, 2048));
} }
$required_keys = array(
'project_id',
);
foreach ($required_keys as $key) {
if (!array_key_exists($key, $proj)) {
throw new Exception(
"Required key '{$key}' is missing from '.arcconfig' file ".
"'{$from_where}'.");
}
}
return $proj; return $proj;
} }
@ -213,6 +205,12 @@ final class ArcanistWorkingCopyIdentity {
} }
public function getProjectID() { public function getProjectID() {
$project_id = $this->getProjectConfig('project.name');
if ($project_id) {
return $project_id;
}
// This is an older name for the setting.
return $this->getProjectConfig('project_id'); return $this->getProjectConfig('project_id');
} }