Page MenuHomePhabricator

D16925.diff
No OneTemporary

D16925.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -42,7 +42,13 @@
'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.php',
'ArcanistBranchRef' => 'ref/ArcanistBranchRef.php',
'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php',
- 'ArcanistBrowseWorkflow' => 'workflow/ArcanistBrowseWorkflow.php',
+ 'ArcanistBrowseCommitURIHardpointLoader' => 'browse/loader/ArcanistBrowseCommitURIHardpointLoader.php',
+ 'ArcanistBrowseObjectNameURIHardpointLoader' => 'browse/loader/ArcanistBrowseObjectNameURIHardpointLoader.php',
+ 'ArcanistBrowsePathURIHardpointLoader' => 'browse/loader/ArcanistBrowsePathURIHardpointLoader.php',
+ 'ArcanistBrowseRef' => 'browse/ref/ArcanistBrowseRef.php',
+ 'ArcanistBrowseURIHardpointLoader' => 'browse/loader/ArcanistBrowseURIHardpointLoader.php',
+ 'ArcanistBrowseURIRef' => 'browse/ref/ArcanistBrowseURIRef.php',
+ 'ArcanistBrowseWorkflow' => 'browse/workflow/ArcanistBrowseWorkflow.php',
'ArcanistBundle' => 'parser/ArcanistBundle.php',
'ArcanistBundleTestCase' => 'parser/__tests__/ArcanistBundleTestCase.php',
'ArcanistCSSLintLinter' => 'lint/linter/ArcanistCSSLintLinter.php',
@@ -322,6 +328,7 @@
'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php',
'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php',
'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php',
+ 'ArcanistRepositoryRef' => 'ref/ArcanistRepositoryRef.php',
'ArcanistReusedAsIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php',
'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedAsIteratorXHPASTLinterRuleTestCase.php',
'ArcanistReusedIteratorReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedIteratorReferenceXHPASTLinterRule.php',
@@ -472,6 +479,12 @@
'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistBranchRef' => 'ArcanistRef',
'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow',
+ 'ArcanistBrowseCommitURIHardpointLoader' => 'ArcanistBrowseURIHardpointLoader',
+ 'ArcanistBrowseObjectNameURIHardpointLoader' => 'ArcanistBrowseURIHardpointLoader',
+ 'ArcanistBrowsePathURIHardpointLoader' => 'ArcanistBrowseURIHardpointLoader',
+ 'ArcanistBrowseRef' => 'ArcanistRef',
+ 'ArcanistBrowseURIHardpointLoader' => 'ArcanistHardpointLoader',
+ 'ArcanistBrowseURIRef' => 'ArcanistRef',
'ArcanistBrowseWorkflow' => 'ArcanistWorkflow',
'ArcanistBundle' => 'Phobject',
'ArcanistBundleTestCase' => 'PhutilTestCase',
@@ -752,6 +765,7 @@
'ArcanistRepositoryAPI' => 'Phobject',
'ArcanistRepositoryAPIMiscTestCase' => 'PhutilTestCase',
'ArcanistRepositoryAPIStateTestCase' => 'PhutilTestCase',
+ 'ArcanistRepositoryRef' => 'ArcanistRef',
'ArcanistReusedAsIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistReusedIteratorReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
diff --git a/src/browse/loader/ArcanistBrowseCommitURIHardpointLoader.php b/src/browse/loader/ArcanistBrowseCommitURIHardpointLoader.php
new file mode 100644
--- /dev/null
+++ b/src/browse/loader/ArcanistBrowseCommitURIHardpointLoader.php
@@ -0,0 +1,107 @@
+<?php
+
+final class ArcanistBrowseCommitURIHardpointLoader
+ extends ArcanistBrowseURIHardpointLoader {
+
+ const LOADERKEY = 'browse.uri.commit';
+ const BROWSETYPE = 'commit';
+
+ public function willLoadBrowseURIRefs(array $refs) {
+ $refs = $this->getRefsWithSupportedTypes($refs);
+
+ if (!$refs) {
+ return;
+ }
+
+ $query = $this->getQuery();
+
+ $working_ref = $query->getWorkingCopyRef();
+ if (!$working_ref) {
+ // If we aren't in a working copy, don't warn about this.
+ return;
+ }
+
+ $repository_ref = $this->getQuery()->getRepositoryRef();
+ if (!$repository_ref) {
+ echo pht(
+ 'NO REPOSITORY: Unable to determine which repository this working '.
+ 'copy belongs to, so arguments can not be resolved as commits. Use '.
+ '"%s" to understand how repositories are resolved.',
+ 'arc which');
+ echo "\n";
+ return;
+ }
+ }
+
+ public function loadHardpoints(array $refs, $hardpoint) {
+ $api = $this->getQuery()->getRepositoryAPI();
+ if (!$api) {
+ return array();
+ }
+
+ $repository_ref = $this->getQuery()->getRepositoryRef();
+ if (!$repository_ref) {
+ return array();
+ }
+
+ $repository_phid = $repository_ref->getPHID();
+
+ $refs = $this->getRefsWithSupportedTypes($refs);
+
+ $commit_map = array();
+ foreach ($refs as $key => $ref) {
+ $is_commit = $ref->hasType('commit');
+
+ $token = $ref->getToken();
+
+ if ($token === '.') {
+ // Git resolves "." like HEAD, but we want to treat it as "browse the
+ // current directory" instead in all cases.
+ continue;
+ }
+
+ if ($token === null) {
+ if ($is_commit) {
+ $token = $api->getHeadCommit();
+ } else {
+ continue;
+ }
+ }
+
+ try {
+ $commit = $api->getCanonicalRevisionName($token);
+ if ($commit) {
+ $commit_map[$commit][] = $key;
+ }
+ } catch (Exception $ex) {
+ // Ignore anything we can't resolve.
+ }
+ }
+
+ if (!$commit_map) {
+ return array();
+ }
+
+ $commit_info = $this->resolveCall(
+ 'diffusion.querycommits',
+ array(
+ 'repositoryPHID' => $repository_phid,
+ 'names' => array_keys($commit_map),
+ ));
+
+ $results = array();
+ foreach ($commit_info['identifierMap'] as $commit_key => $commit_phid) {
+ foreach ($commit_map[$commit_key] as $key) {
+ $commit_uri = $commit_info['data'][$commit_phid]['uri'];
+
+ $results[$key][] = id(new ArcanistBrowseURIRef())
+ ->setURI($commit_uri)
+ ->setType('commit');
+ }
+ }
+
+ return $results;
+ }
+
+
+}
diff --git a/src/browse/loader/ArcanistBrowseObjectNameURIHardpointLoader.php b/src/browse/loader/ArcanistBrowseObjectNameURIHardpointLoader.php
new file mode 100644
--- /dev/null
+++ b/src/browse/loader/ArcanistBrowseObjectNameURIHardpointLoader.php
@@ -0,0 +1,54 @@
+<?php
+
+final class ArcanistBrowseObjectNameURIHardpointLoader
+ extends ArcanistBrowseURIHardpointLoader {
+
+ const LOADERKEY = 'browse.uri.name';
+ const BROWSETYPE = 'object';
+
+ public function loadHardpoints(array $refs, $hardpoint) {
+ $refs = $this->getRefsWithSupportedTypes($refs);
+
+ $name_map = array();
+ foreach ($refs as $key => $ref) {
+ $token = $ref->getToken();
+ if (!strlen($token)) {
+ continue;
+ }
+
+ $name_map[$key] = $token;
+ }
+
+ if (!$name_map) {
+ return array();
+ }
+
+ $objects = $this->resolveCall(
+ 'phid.lookup',
+ array(
+ 'names' => $name_map,
+ ));
+
+ $result = array();
+
+ $reverse_map = array_flip($name_map);
+ foreach ($objects as $name => $object) {
+ $key = idx($reverse_map, $name);
+ if ($key === null) {
+ continue;
+ }
+
+ $uri = idx($object, 'uri');
+ if (!strlen($uri)) {
+ continue;
+ }
+
+ $result[$key][] = id(new ArcanistBrowseURIRef())
+ ->setURI($object['uri'])
+ ->setType('object');
+ }
+
+ return $result;
+ }
+
+}
diff --git a/src/browse/loader/ArcanistBrowsePathURIHardpointLoader.php b/src/browse/loader/ArcanistBrowsePathURIHardpointLoader.php
new file mode 100644
--- /dev/null
+++ b/src/browse/loader/ArcanistBrowsePathURIHardpointLoader.php
@@ -0,0 +1,132 @@
+<?php
+
+final class ArcanistBrowsePathURIHardpointLoader
+ extends ArcanistBrowseURIHardpointLoader {
+
+ const LOADERKEY = 'browse.uri.path';
+ const BROWSETYPE = 'path';
+
+ public function willLoadBrowseURIRefs(array $refs) {
+ $refs = $this->getRefsWithSupportedTypes($refs);
+ if (!$refs) {
+ return;
+ }
+
+ $query = $this->getQuery();
+
+ $working_ref = $query->getWorkingCopyRef();
+ if (!$working_ref) {
+ echo pht(
+ 'NO WORKING COPY: The current directory is not a repository '.
+ 'working copy, so arguments can not be resolved as paths. Run '.
+ 'this command inside a working copy to resolve paths.');
+ echo "\n";
+ return;
+ }
+
+ $repository_ref = $query->getRepositoryRef();
+ if (!$repository_ref) {
+ echo pht(
+ 'NO REPOSITORY: Unable to determine which repository this working '.
+ 'copy belongs to, so arguments can not be resolved as paths. Use '.
+ '"%s" to understand how repositories are resolved.',
+ 'arc which');
+ echo "\n";
+ return;
+ }
+ }
+
+ public function didFailToLoadBrowseURIRefs(array $refs) {
+ $refs = $this->getRefsWithSupportedTypes($refs);
+ if (!$refs) {
+ return;
+ }
+
+ $query = $this->getQuery();
+
+ $working_ref = $query->getWorkingCopyRef();
+ if (!$working_ref) {
+ return;
+ }
+
+ $repository_ref = $query->getRepositoryRef();
+ if (!$repository_ref) {
+ return;
+ }
+
+ echo pht(
+ 'Use "--types path" to force arguments to be interpreted as paths.');
+ echo "\n";
+ }
+
+
+ public function loadHardpoints(array $refs, $hardpoint) {
+ $query = $this->getQuery();
+
+ $working_ref = $query->getWorkingCopyRef();
+ if (!$working_ref) {
+ return array();
+ }
+
+ $repository_ref = $query->getRepositoryRef();
+ if (!$repository_ref) {
+ return array();
+ }
+
+ $refs = $this->getRefsWithSupportedTypes($refs);
+ $project_root = $working_ref->getRootDirectory();
+
+ $results = array();
+ foreach ($refs as $key => $ref) {
+ $is_path = $ref->hasType(self::BROWSETYPE);
+
+ $path = $ref->getToken();
+ if ($path === null) {
+ // If we're explicitly resolving no arguments as a path, treat it
+ // as the current working directory.
+ if ($is_path) {
+ $path = '.';
+ } else {
+ continue;
+ }
+ }
+
+ $lines = null;
+ $parts = explode(':', $path);
+ if (count($parts) > 1) {
+ $lines = array_pop($parts);
+ }
+ $path = implode(':', $parts);
+
+ $full_path = Filesystem::resolvePath($path);
+
+ if (!Filesystem::pathExists($full_path)) {
+ if (!$is_path) {
+ continue;
+ }
+ }
+
+ if ($full_path == $project_root) {
+ $path = '';
+ } else {
+ $path = Filesystem::readablePath($full_path, $project_root);
+ }
+
+ $params = array(
+ 'path' => $path,
+ 'lines' => $lines,
+ 'branch' => $ref->getBranch(),
+ );
+
+ $uri = $repository_ref->newBrowseURI($params);
+
+ $results[$key][] = id(new ArcanistBrowseURIRef())
+ ->setURI($uri)
+ ->setType(self::BROWSETYPE);
+ }
+
+ return $results;
+ }
+
+
+}
diff --git a/src/browse/loader/ArcanistBrowseURIHardpointLoader.php b/src/browse/loader/ArcanistBrowseURIHardpointLoader.php
new file mode 100644
--- /dev/null
+++ b/src/browse/loader/ArcanistBrowseURIHardpointLoader.php
@@ -0,0 +1,55 @@
+<?php
+
+abstract class ArcanistBrowseURIHardpointLoader
+ extends ArcanistHardpointLoader {
+
+ public function getSupportedBrowseType() {
+ return $this->getPhobjectClassConstant('BROWSETYPE', 32);
+ }
+
+ public function canLoadRepositoryAPI(ArcanistRepositoryAPI $api) {
+ return true;
+ }
+
+ public function canLoadRef(ArcanistRef $ref) {
+ return ($ref instanceof ArcanistBrowseRef);
+ }
+
+ public function canLoadHardpoint(ArcanistRef $ref, $hardpoint) {
+ return ($hardpoint == 'uris');
+ }
+
+ public function willLoadBrowseURIRefs(array $refs) {
+ return;
+ }
+
+ public function didFailToLoadBrowseURIRefs(array $refs) {
+ return;
+ }
+
+ public function getRefsWithSupportedTypes(array $refs) {
+ $type = $this->getSupportedBrowseType();
+
+ foreach ($refs as $key => $ref) {
+ if ($ref->isUntyped()) {
+ continue;
+ }
+
+ if ($ref->hasType($type)) {
+ continue;
+ }
+
+ unset($refs[$key]);
+ }
+
+ return $refs;
+ }
+
+ public static function getAllBrowseLoaders() {
+ return id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->setUniqueMethod('getLoaderKey')
+ ->execute();
+ }
+
+}
diff --git a/src/browse/ref/ArcanistBrowseRef.php b/src/browse/ref/ArcanistBrowseRef.php
new file mode 100644
--- /dev/null
+++ b/src/browse/ref/ArcanistBrowseRef.php
@@ -0,0 +1,64 @@
+<?php
+
+final class ArcanistBrowseRef
+ extends ArcanistRef {
+
+ private $token;
+ private $types;
+ private $branch;
+
+ public function getRefIdentifier() {
+ return pht('Browse Query "%s"', $this->getToken());
+ }
+
+ public function defineHardpoints() {
+ return array(
+ 'uris' => array(
+ 'type' => 'ArcanistBrowseURIRef',
+ 'vector' => true,
+ ),
+ );
+ }
+
+ public function setToken($token) {
+ $this->token = $token;
+ return $this;
+ }
+
+ public function getToken() {
+ return $this->token;
+ }
+
+ public function setTypes(array $types) {
+ $this->types = $types;
+ return $this;
+ }
+
+ public function getTypes() {
+ return $this->types;
+ }
+
+ public function hasType($type) {
+ $map = $this->getTypes();
+ $map = array_fuse($map);
+ return isset($map[$type]);
+ }
+
+ public function isUntyped() {
+ return !$this->types;
+ }
+
+ public function setBranch($branch) {
+ $this->branch = $branch;
+ return $this;
+ }
+
+ public function getBranch() {
+ return $this->branch;
+ }
+
+ public function getURIs() {
+ return $this->getHardpoint('uris');
+ }
+
+}
diff --git a/src/browse/ref/ArcanistBrowseURIRef.php b/src/browse/ref/ArcanistBrowseURIRef.php
new file mode 100644
--- /dev/null
+++ b/src/browse/ref/ArcanistBrowseURIRef.php
@@ -0,0 +1,35 @@
+<?php
+
+final class ArcanistBrowseURIRef
+ extends ArcanistRef {
+
+ private $uri;
+ private $type;
+
+ public function getRefIdentifier() {
+ return pht('Browse URI "%s"', $this->getURI());
+ }
+
+ public function defineHardpoints() {
+ return array();
+ }
+
+ public function setURI($uri) {
+ $this->uri = $uri;
+ return $this;
+ }
+
+ public function getURI() {
+ return $this->uri;
+ }
+
+ public function setType($type) {
+ $this->type = $type;
+ return $this;
+ }
+
+ public function getType() {
+ return $this->type;
+ }
+
+}
diff --git a/src/browse/workflow/ArcanistBrowseWorkflow.php b/src/browse/workflow/ArcanistBrowseWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/browse/workflow/ArcanistBrowseWorkflow.php
@@ -0,0 +1,224 @@
+<?php
+
+/**
+ * Browse files or objects in the Phabricator web interface.
+ */
+final class ArcanistBrowseWorkflow extends ArcanistWorkflow {
+
+ public function getWorkflowName() {
+ return 'browse';
+ }
+
+ public function getCommandSynopses() {
+ return phutil_console_format(<<<EOTEXT
+ **browse** [__options__] __path__ ...
+ **browse** [__options__] __object__ ...
+EOTEXT
+ );
+ }
+
+ public function getCommandHelp() {
+ return phutil_console_format(<<<EOTEXT
+ Supports: git, hg, svn
+ Open a file or object (like a task or revision) in your web browser.
+
+ $ arc browse README # Open a file in Diffusion.
+ $ arc browse T123 # View a task.
+ $ arc browse HEAD # View a symbolic commit.
+
+ Set the 'browser' value using 'arc set-config' to select a browser. If
+ no browser is set, the command will try to guess which browser to use.
+EOTEXT
+ );
+ }
+
+ public function getArguments() {
+ return array(
+ 'branch' => array(
+ 'param' => 'branch_name',
+ 'help' => pht(
+ 'Default branch name to view on server. Defaults to "%s".',
+ 'master'),
+ ),
+ 'types' => array(
+ 'param' => 'types',
+ 'aliases' => array('type'),
+ 'help' => pht(
+ 'Parse arguments with particular types.'),
+ ),
+ 'force' => array(
+ 'help' => pht(
+ '(DEPRECATED) Obsolete, use "--types path" instead.'),
+ ),
+ '*' => 'targets',
+ );
+ }
+
+ public function desiresWorkingCopy() {
+ return true;
+ }
+
+ public function desiresRepositoryAPI() {
+ return true;
+ }
+
+ public function run() {
+ $conduit = $this->getConduitEngine();
+
+ $console = PhutilConsole::getConsole();
+
+ $targets = $this->getArgument('targets');
+ if (!$targets) {
+ throw new ArcanistUsageException(
+ pht(
+ 'Specify one or more paths or objects to browse. Use the '.
+ 'command "%s" if you want to browse this directory.',
+ 'arc browse .'));
+ }
+ $targets = array_fuse($targets);
+
+ if (!$targets) {
+ $refs = array(
+ new ArcanistBrowseRef(),
+ );
+ } else {
+ $refs = array();
+ foreach ($targets as $target) {
+ $refs[] = id(new ArcanistBrowseRef())
+ ->setToken($target);
+ }
+ }
+
+ $is_force = $this->getArgument('force');
+ if ($is_force) {
+ // TODO: Remove this completely.
+ $this->writeWarn(
+ pht('DEPRECATED'),
+ pht(
+ 'Argument "--force" for "arc browse" is deprecated. Use '.
+ '"--type %s" instead.',
+ ArcanistBrowsePathURIHardpointLoader::BROWSETYPE));
+ }
+
+ $types = $this->getArgument('types');
+ if ($types !== null) {
+ $types = preg_split('/[\s,]+/', $types);
+ } else {
+ if ($is_force) {
+ $types = array(ArcanistBrowsePathURIHardpointLoader::BROWSETYPE);
+ } else {
+ $types = array();
+ }
+ }
+
+ foreach ($refs as $ref) {
+ $ref->setTypes($types);
+ }
+
+ $branch = $this->getArgument('branch');
+ if ($branch) {
+ foreach ($refs as $ref) {
+ $ref->setBranch($branch);
+ }
+ }
+
+ $loaders = ArcanistBrowseURIHardpointLoader::getAllBrowseLoaders();
+ foreach ($loaders as $key => $loader) {
+ $loaders[$key] = clone $loader;
+ }
+
+ $query = $this->newRefQuery($refs)
+ ->needHardpoints(
+ array(
+ 'uris',
+ ))
+ ->setLoaders($loaders);
+
+ foreach ($loaders as $loader) {
+ $loader->willLoadBrowseURIRefs($refs);
+ }
+
+ $query->execute();
+
+ $zero_hits = array();
+ $open_uris = array();
+ $many_hits = array();
+ foreach ($refs as $ref) {
+ $uris = $ref->getURIs();
+ if (!$uris) {
+ $zero_hits[] = $ref;
+ } else if (count($uris) == 1) {
+ $open_uris[] = $ref;
+ } else {
+ $many_hits[] = $ref;
+ }
+ }
+
+ if ($many_hits) {
+ foreach ($many_hits as $ref) {
+ $token = $ref->getToken();
+ if (strlen($token)) {
+ $message = pht('Argument "%s" is ambiguous.', $token);
+ } else {
+ $message = pht('Default behavior is ambiguous.');
+ }
+
+ $this->writeWarn(pht('AMBIGUOUS'), $message);
+ }
+
+ $table = id(new PhutilConsoleTable())
+ ->addColumn('argument', array('title' => pht('Argument')))
+ ->addColumn('type', array('title' => pht('Type')))
+ ->addColumn('uri', array('title' => pht('URI')));
+
+ foreach ($many_hits as $ref) {
+ $token_display = $ref->getToken();
+ if (!strlen($token)) {
+ $token_display = pht('<default>');
+ }
+
+ foreach ($ref->getURIs() as $uri) {
+ $row = array(
+ 'argument' => $token_display,
+ 'type' => $uri->getType(),
+ 'uri' => $uri->getURI(),
+ );
+
+ $table->addRow($row);
+ }
+ }
+
+ $table->draw();
+
+ $this->writeInfo(
+ pht('CHOOSE'),
+ pht('Use "--types" to select between alternatives.'));
+ }
+
+ // If anything failed to resolve, this is also an error.
+ if ($zero_hits) {
+ foreach ($zero_hits as $ref) {
+ echo tsprintf(
+ "%s\n",
+ pht(
+ 'Unable to resolve argument "%s".',
+ $ref->getToken()));
+ }
+
+ foreach ($loaders as $loader) {
+ $loader->didFailToLoadBrowseURIRefs($refs);
+ }
+ }
+
+ $uris = array();
+ foreach ($open_uris as $ref) {
+ $ref_uri = head($ref->getURIs());
+ $uris[] = $ref_uri->getURI();
+ }
+
+ $this->openURIsInBrowser($uris);
+
+ return 0;
+ }
+
+}
diff --git a/src/ref/ArcanistRefQuery.php b/src/ref/ArcanistRefQuery.php
--- a/src/ref/ArcanistRefQuery.php
+++ b/src/ref/ArcanistRefQuery.php
@@ -4,9 +4,12 @@
private $repositoryAPI;
private $conduitEngine;
+ private $repositoryRef;
+ private $workingCopyRef;
private $refs;
private $hardpoints;
+ private $loaders;
public function setRefs(array $refs) {
assert_instances_of($refs, 'ArcanistRef');
@@ -27,6 +30,14 @@
return $this->repositoryAPI;
}
+ public function setRepositoryRef(ArcanistRepositoryRef $repository_ref) {
+ $this->repositoryRef = $repository_ref;
+ return $this;
+ }
+ public function getRepositoryRef() {
+ return $this->repositoryRef;
+ }
+
public function setConduitEngine(ArcanistConduitEngine $conduit_engine) {
$this->conduitEngine = $conduit_engine;
return $this;
@@ -36,11 +47,31 @@
return $this->conduitEngine;
}
+ public function setWorkingCopyRef(ArcanistWorkingCopyStateRef $working_ref) {
+ $this->workingCopyRef = $working_ref;
+ return $this;
+ }
+
+ public function getWorkingCopyRef() {
+ return $this->workingCopyRef;
+ }
+
public function needHardpoints(array $hardpoints) {
$this->hardpoints = $hardpoints;
return $this;
}
+ public function setLoaders(array $loaders) {
+ assert_instances_of($loaders, 'ArcanistHardpointLoader');
+
+ foreach ($loaders as $key => $loader) {
+ $loader->setQuery($this);
+ }
+ $this->loaders = $loaders;
+
+ return $this;
+ }
+
public function execute() {
$refs = $this->getRefs();
@@ -52,13 +83,23 @@
throw new PhutilInvalidStateException('needHardpoints');
}
- $api = $this->getRepositoryAPI();
- $all_loaders = ArcanistHardpointLoader::getAllLoaders();
+ if ($this->loaders == null) {
+ $all_loaders = ArcanistHardpointLoader::getAllLoaders();
+ foreach ($all_loaders as $key => $loader) {
+ $all_loaders[$key] = clone $loader;
+ }
+ $this->setLoaders($all_loaders);
+ }
+
+ $all_loaders = $this->loaders;
+ $api = $this->getRepositoryAPI();
$loaders = array();
foreach ($all_loaders as $loader_key => $loader) {
- if (!$loader->canLoadRepositoryAPI($api)) {
- continue;
+ if ($api) {
+ if (!$loader->canLoadRepositoryAPI($api)) {
+ continue;
+ }
}
$loaders[$loader_key] = id(clone $loader)
diff --git a/src/ref/ArcanistRepositoryRef.php b/src/ref/ArcanistRepositoryRef.php
new file mode 100644
--- /dev/null
+++ b/src/ref/ArcanistRepositoryRef.php
@@ -0,0 +1,79 @@
+<?php
+
+final class ArcanistRepositoryRef
+ extends ArcanistRef {
+
+ private $phid;
+ private $browseURI;
+
+ public function getRefIdentifier() {
+ return pht('Remote Repository');
+ }
+
+ public function defineHardpoints() {
+ return array();
+ }
+
+ public function setPHID($phid) {
+ $this->phid = $phid;
+ return $this;
+ }
+
+ public function getPHID() {
+ return $this->phid;
+ }
+
+ public function setBrowseURI($browse_uri) {
+ $this->browseURI = $browse_uri;
+ return $this;
+ }
+
+ public function newBrowseURI(array $params) {
+ PhutilTypeSpec::checkMap(
+ $params,
+ array(
+ 'path' => 'optional string|null',
+ 'branch' => 'optional string|null',
+ 'lines' => 'optional string|null',
+ ));
+
+ foreach ($params as $key => $value) {
+ if (!strlen($value)) {
+ unset($params[$key]);
+ }
+ }
+
+ $defaults = array(
+ 'path' => '/',
+ 'branch' => $this->getDefaultBranch(),
+ 'lines' => null,
+ );
+
+ $params = $params + $defaults;
+
+ $uri_base = $this->browseURI;
+ $uri_base = rtrim($uri_base, '/');
+
+ $uri_branch = phutil_escape_uri_path_component($params['branch']);
+
+ $uri_path = ltrim($params['path'], '/');
+ $uri_path = phutil_escape_uri($uri_path);
+
+ $uri_lines = null;
+ if ($params['lines']) {
+ $uri_lines = '$'.phutil_escape_uri($params['lines']);
+ }
+
+ // TODO: This construction, which includes a branch, is probably wrong for
+ // Subversion.
+
+ return "{$uri_base}/browse/{$uri_branch}/{$uri_path}{$uri_lines}";
+ }
+
+ public function getDefaultBranch() {
+ // TODO: This should read from the remote, and is not correct for
+ // Mercurial anyway, as "default" would be a better default branch.
+ return 'master';
+ }
+
+}
diff --git a/src/ref/ArcanistWorkingCopyStateRef.php b/src/ref/ArcanistWorkingCopyStateRef.php
--- a/src/ref/ArcanistWorkingCopyStateRef.php
+++ b/src/ref/ArcanistWorkingCopyStateRef.php
@@ -3,6 +3,8 @@
final class ArcanistWorkingCopyStateRef
extends ArcanistRef {
+ private $rootDirectory;
+
public function getRefIdentifier() {
// TODO: This could check attached hardpoints and render something more
// insightful.
@@ -24,6 +26,15 @@
);
}
+ public function setRootDirectory($root_directory) {
+ $this->rootDirectory = $root_directory;
+ return $this;
+ }
+
+ public function getRootDirectory() {
+ return $this->rootDirectory;
+ }
+
public function attachBranchRef(ArcanistBranchRef $branch_ref) {
return $this->attachHardpoint('branchRef', $branch_ref);
}
diff --git a/src/workflow/ArcanistBrowseWorkflow.php b/src/workflow/ArcanistBrowseWorkflow.php
deleted file mode 100644
--- a/src/workflow/ArcanistBrowseWorkflow.php
+++ /dev/null
@@ -1,232 +0,0 @@
-<?php
-
-/**
- * Browse files or objects in the Phabricator web interface.
- */
-final class ArcanistBrowseWorkflow extends ArcanistWorkflow {
-
- public function getWorkflowName() {
- return 'browse';
- }
-
- public function getCommandSynopses() {
- return phutil_console_format(<<<EOTEXT
- **browse** [__options__] __path__ ...
- **browse** [__options__] __object__ ...
-EOTEXT
- );
- }
-
- public function getCommandHelp() {
- return phutil_console_format(<<<EOTEXT
- Supports: git, hg, svn
- Open a file or object (like a task or revision) in your web browser.
-
- $ arc browse README # Open a file in Diffusion.
- $ arc browse T123 # View a task.
- $ arc browse HEAD # View a symbolic commit.
-
- Set the 'browser' value using 'arc set-config' to select a browser. If
- no browser is set, the command will try to guess which browser to use.
-EOTEXT
- );
- }
-
- public function getArguments() {
- return array(
- 'branch' => array(
- 'param' => 'branch_name',
- 'help' => pht(
- 'Default branch name to view on server. Defaults to "%s".',
- 'master'),
- ),
- 'force' => array(
- 'help' => pht(
- 'Open arguments as paths, even if they do not exist in the '.
- 'working copy.'),
- ),
- '*' => 'paths',
- );
- }
-
- public function desiresWorkingCopy() {
- return true;
- }
-
- public function desiresRepositoryAPI() {
- return true;
- }
-
- public function run() {
- $conduit = $this->getConduitEngine();
-
- $console = PhutilConsole::getConsole();
-
- $is_force = $this->getArgument('force');
-
- $things = $this->getArgument('paths');
- if (!$things) {
- throw new ArcanistUsageException(
- pht(
- 'Specify one or more paths or objects to browse. Use the command '.
- '"%s" if you want to browse this directory.',
- 'arc browse .'));
- }
- $things = array_fuse($things);
-
- $method = 'phid.lookup';
- $params = array(
- 'names' => array_keys($things),
- );
-
- $objects = $conduit->newCall($method, $params)
- ->resolve();
-
- $uris = array();
- foreach ($objects as $name => $object) {
- $uris[] = $object['uri'];
-
- $console->writeOut(
- pht(
- 'Opening **%s** as an object.',
- $name)."\n");
-
- unset($things[$name]);
- }
-
- if ($this->hasRepositoryAPI()) {
- $repository_api = $this->getRepositoryAPI();
- $project_root = $this->getWorkingCopy()->getProjectRoot();
-
- // First, try to resolve arguments as symbolic commits.
-
- $commits = array();
- foreach ($things as $key => $thing) {
- if ($thing == '.') {
- // Git resolves '.' like HEAD, but it should be interpreted to mean
- // "the current directory". Just skip resolution and fall through.
- continue;
- }
-
- try {
- $commit = $repository_api->getCanonicalRevisionName($thing);
- if ($commit) {
- $commits[$commit] = $key;
- }
- } catch (Exception $ex) {
- // Ignore.
- }
- }
-
- if ($commits) {
- $method = 'diffusion.querycommits';
-
- $params = array(
- 'repositoryPHID' => $this->getRepositoryPHID(),
- 'names' => array_keys($commits),
- );
-
- $commit_info = $conduit->newCall($method, $params)
- ->resolve();
-
- foreach ($commit_info['identifierMap'] as $ckey => $cphid) {
- $thing = $commits[$ckey];
- unset($things[$thing]);
-
- $uris[] = $commit_info['data'][$cphid]['uri'];
-
- $console->writeOut(
- pht(
- 'Opening **%s** as a commit.',
- $thing)."\n");
- }
- }
-
- // If we fail, try to resolve them as paths.
-
- foreach ($things as $key => $path) {
- $lines = null;
- $parts = explode(':', $path);
- if (count($parts) > 1) {
- $lines = array_pop($parts);
- }
- $path = implode(':', $parts);
-
- $full_path = Filesystem::resolvePath($path);
-
- if (!$is_force && !Filesystem::pathExists($full_path)) {
- continue;
- }
-
- $console->writeOut(
- pht(
- 'Opening **%s** as a repository path.',
- $key)."\n");
-
- unset($things[$key]);
-
- if ($full_path == $project_root) {
- $path = '';
- } else {
- $path = Filesystem::readablePath($full_path, $project_root);
- }
-
- $base_uri = $this->getBaseURI();
- $uri = $base_uri.$path;
-
- if ($lines) {
- $uri = $uri.'$'.$lines;
- }
-
- $uris[] = $uri;
- }
- } else {
- if ($things) {
- $console->writeOut(
- "%s\n",
- pht(
- "The current working directory is not a repository working ".
- "copy, so remaining arguments can not be resolved as paths or ".
- "commits. To browse paths or symbolic commits in Diffusion, run ".
- "'%s' from inside a working copy.",
- 'arc browse'));
- }
- }
-
- foreach ($things as $thing) {
- $console->writeOut(
- "%s\n",
- pht(
- 'Unable to find an object named **%s**, no such commit exists in '.
- 'the remote, and no such path exists in the working copy. Use '.
- '__%s__ to treat this as a path anyway.',
- $thing,
- '--force'));
- }
-
- if ($uris) {
- $this->openURIsInBrowser($uris);
- }
-
- return 0;
- }
-
- private function getBaseURI() {
- $repo_uri = $this->getRepositoryURI();
- if ($repo_uri === null) {
- throw new ArcanistUsageException(
- pht(
- 'arc is unable to determine which repository in Diffusion '.
- 'this working copy belongs to. Use "%s" to understand how '.
- '%s looks for a repository.',
- 'arc which',
- 'arc'));
- }
-
- $branch = $this->getArgument('branch', 'master');
- $branch = phutil_escape_uri_path_component($branch);
-
- return $repo_uri.'browse/'.$branch.'/';
- }
-
-}
diff --git a/src/workflow/ArcanistWorkflow.php b/src/workflow/ArcanistWorkflow.php
--- a/src/workflow/ArcanistWorkflow.php
+++ b/src/workflow/ArcanistWorkflow.php
@@ -62,6 +62,7 @@
private $projectInfo;
private $repositoryInfo;
private $repositoryReasons;
+ private $repositoryRef;
private $arcanistConfiguration;
private $parentWorkflow;
@@ -583,6 +584,7 @@
$arc_config = $this->getArcanistConfiguration();
$workflow = $arc_config->buildWorkflow($command);
$workflow->setParentWorkflow($this);
+ $workflow->setConduitEngine($this->getConduitEngine());
$workflow->setCommand($command);
$workflow->setConfigurationManager($this->getConfigurationManager());
@@ -2074,15 +2076,57 @@
}
final protected function newWorkingCopyStateRef() {
- return new ArcanistWorkingCopyStateRef();
+ $ref = new ArcanistWorkingCopyStateRef();
+
+ $working_copy = $this->getWorkingCopy();
+ $ref->setRootDirectory($working_copy->getProjectRoot());
+
+ return $ref;
}
final protected function newRefQuery(array $refs) {
assert_instances_of($refs, 'ArcanistRef');
- return id(new ArcanistRefQuery())
- ->setRepositoryAPI($this->getRepositoryAPI())
+
+ $query = id(new ArcanistRefQuery())
->setConduitEngine($this->getConduitEngine())
->setRefs($refs);
+
+ if ($this->hasRepositoryAPI()) {
+ $query->setRepositoryAPI($this->getRepositoryAPI());
+ }
+
+ $repository_ref = $this->getRepositoryRef();
+ if ($repository_ref) {
+ $query->setRepositoryRef($repository_ref);
+ }
+
+ $working_copy = $this->getConfigurationManager()->getWorkingCopyIdentity();
+ if ($working_copy) {
+ $working_ref = $this->newWorkingCopyStateRef();
+ $query->setWorkingCopyRef($working_ref);
+ }
+
+ return $query;
+ }
+
+ final public function getRepositoryRef() {
+ if (!$this->getConfigurationManager()->getWorkingCopyIdentity()) {
+ return null;
+ }
+
+ if (!$this->repositoryAPI) {
+ return null;
+ }
+
+ if (!$this->repositoryRef) {
+ $ref = id(new ArcanistRepositoryRef())
+ ->setPHID($this->getRepositoryPHID())
+ ->setBrowseURI($this->getRepositoryURI());
+
+ $this->repositoryRef = $ref;
+ }
+
+ return $this->repositoryRef;
}
}

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 22, 5:56 AM (5 h, 58 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6773741
Default Alt Text
D16925.diff (34 KB)

Event Timeline