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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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 @@ + 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(''); + } + + 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 @@ +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 @@ - 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; } }