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 @@ -57,13 +57,22 @@ 'ArcanistBranchRefPro' => 'ref/ArcanistBranchRefPro.php', 'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php', 'ArcanistBrowseCommitHardpointLoader' => 'browse/loader/ArcanistBrowseCommitHardpointLoader.php', + 'ArcanistBrowseCommitHardpointQuery' => 'browse/query/ArcanistBrowseCommitHardpointQuery.php', 'ArcanistBrowseCommitURIHardpointLoader' => 'browse/loader/ArcanistBrowseCommitURIHardpointLoader.php', + 'ArcanistBrowseCommitURIHardpointQuery' => 'browse/query/ArcanistBrowseCommitURIHardpointQuery.php', 'ArcanistBrowseObjectNameURIHardpointLoader' => 'browse/loader/ArcanistBrowseObjectNameURIHardpointLoader.php', + 'ArcanistBrowseObjectNameURIHardpointQuery' => 'browse/query/ArcanistBrowseObjectNameURIHardpointQuery.php', 'ArcanistBrowsePathURIHardpointLoader' => 'browse/loader/ArcanistBrowsePathURIHardpointLoader.php', + 'ArcanistBrowsePathURIHardpointQuery' => 'browse/query/ArcanistBrowsePathURIHardpointQuery.php', 'ArcanistBrowseRef' => 'browse/ref/ArcanistBrowseRef.php', + 'ArcanistBrowseRefInspector' => 'inspector/ArcanistBrowseRefInspector.php', + 'ArcanistBrowseRefPro' => 'browse/ref/ArcanistBrowseRefPro.php', 'ArcanistBrowseRevisionURIHardpointLoader' => 'browse/loader/ArcanistBrowseRevisionURIHardpointLoader.php', + 'ArcanistBrowseRevisionURIHardpointQuery' => 'browse/query/ArcanistBrowseRevisionURIHardpointQuery.php', 'ArcanistBrowseURIHardpointLoader' => 'browse/loader/ArcanistBrowseURIHardpointLoader.php', + 'ArcanistBrowseURIHardpointQuery' => 'browse/query/ArcanistBrowseURIHardpointQuery.php', 'ArcanistBrowseURIRef' => 'browse/ref/ArcanistBrowseURIRef.php', + 'ArcanistBrowseURIRefPro' => 'browse/ref/ArcanistBrowseURIRefPro.php', 'ArcanistBrowseWorkflow' => 'browse/workflow/ArcanistBrowseWorkflow.php', 'ArcanistBuildPlanRef' => 'ref/ArcanistBuildPlanRef.php', 'ArcanistBuildRef' => 'ref/ArcanistBuildRef.php', @@ -1031,13 +1040,22 @@ 'ArcanistBranchRefPro' => 'ArcanistRefPro', 'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow', 'ArcanistBrowseCommitHardpointLoader' => 'ArcanistHardpointLoader', + 'ArcanistBrowseCommitHardpointQuery' => 'ArcanistWorkflowHardpointQuery', 'ArcanistBrowseCommitURIHardpointLoader' => 'ArcanistBrowseURIHardpointLoader', + 'ArcanistBrowseCommitURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery', 'ArcanistBrowseObjectNameURIHardpointLoader' => 'ArcanistBrowseURIHardpointLoader', + 'ArcanistBrowseObjectNameURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery', 'ArcanistBrowsePathURIHardpointLoader' => 'ArcanistBrowseURIHardpointLoader', + 'ArcanistBrowsePathURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery', 'ArcanistBrowseRef' => 'ArcanistRef', + 'ArcanistBrowseRefInspector' => 'ArcanistRefInspector', + 'ArcanistBrowseRefPro' => 'ArcanistRefPro', 'ArcanistBrowseRevisionURIHardpointLoader' => 'ArcanistBrowseURIHardpointLoader', + 'ArcanistBrowseRevisionURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery', 'ArcanistBrowseURIHardpointLoader' => 'ArcanistHardpointLoader', + 'ArcanistBrowseURIHardpointQuery' => 'ArcanistWorkflowHardpointQuery', 'ArcanistBrowseURIRef' => 'ArcanistRef', + 'ArcanistBrowseURIRefPro' => 'ArcanistRefPro', 'ArcanistBrowseWorkflow' => 'ArcanistArcWorkflow', 'ArcanistBuildPlanRef' => 'Phobject', 'ArcanistBuildRef' => 'Phobject', diff --git a/src/browse/query/ArcanistBrowseCommitHardpointQuery.php b/src/browse/query/ArcanistBrowseCommitHardpointQuery.php new file mode 100644 --- /dev/null +++ b/src/browse/query/ArcanistBrowseCommitHardpointQuery.php @@ -0,0 +1,65 @@ +getRepositoryAPI(); + + $commit_map = array(); + foreach ($refs as $key => $ref) { + $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; + } + + // Always resolve the empty token; top-level loaders filter out + // irrelevant tokens before this stage. + if ($token === null) { + $token = $api->getHeadCommit(); + } + + // TODO: We should pull a full commit ref out of the API as soon as it + // is able to provide them. In particular, we currently miss Git tree + // hashes which reduces the accuracy of lookups. + + try { + $commit = $api->getCanonicalRevisionName($token); + if ($commit) { + $commit_map[$commit][] = $key; + } + } catch (Exception $ex) { + // Ignore anything we can't resolve. + } + } + + if (!$commit_map) { + yield $this->yieldMap(array()); + } + + $results = array(); + foreach ($commit_map as $commit_identifier => $ref_keys) { + foreach ($ref_keys as $key) { + $commit_ref = id(new ArcanistCommitRefPro()) + ->setCommitHash($commit_identifier); + $results[$key][] = $commit_ref; + } + } + + yield $this->yieldMap($results); + } + +} diff --git a/src/browse/query/ArcanistBrowseCommitURIHardpointQuery.php b/src/browse/query/ArcanistBrowseCommitURIHardpointQuery.php new file mode 100644 --- /dev/null +++ b/src/browse/query/ArcanistBrowseCommitURIHardpointQuery.php @@ -0,0 +1,76 @@ +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 loadHardpoint(array $refs, $hardpoint) { + $refs = $this->getRefsWithSupportedTypes($refs); + if (!$refs) { + yield $this->yieldMap(array()); + } + + yield $this->yieldRequests( + $refs, + array( + ArcanistBrowseRefPro::HARDPOINT_COMMITREFS, + )); + + $commit_refs = array(); + foreach ($refs as $key => $ref) { + foreach ($ref->getCommitRefs() as $commit_ref) { + $commit_refs[] = $commit_ref; + } + } + + yield $this->yieldRequests( + $commit_refs, + array( + ArcanistCommitRefPro::HARDPOINT_UPSTREAM, + )); + + $results = array(); + foreach ($refs as $key => $ref) { + $commit_refs = $ref->getCommitRefs(); + foreach ($commit_refs as $commit_ref) { + $uri = $commit_ref->getURI(); + if ($uri !== null) { + $results[$key][] = $this->newBrowseURIRef() + ->setURI($uri); + } + } + } + + yield $this->yieldMap($results); + } + + +} diff --git a/src/browse/query/ArcanistBrowseObjectNameURIHardpointQuery.php b/src/browse/query/ArcanistBrowseObjectNameURIHardpointQuery.php new file mode 100644 --- /dev/null +++ b/src/browse/query/ArcanistBrowseObjectNameURIHardpointQuery.php @@ -0,0 +1,56 @@ +getRefsWithSupportedTypes($refs); + if (!$refs) { + yield $this->yieldMap(array()); + } + + $name_map = array(); + $token_set = array(); + foreach ($refs as $key => $ref) { + $token = $ref->getToken(); + if (!strlen($token)) { + continue; + } + + $name_map[$key] = $token; + $token_set[$token] = $token; + } + + if (!$token_set) { + yield $this->yieldMap(array()); + } + + $objects = (yield $this->yieldConduit( + 'phid.lookup', + array( + 'names' => $token_set, + ))); + + $result = array(); + foreach ($name_map as $ref_key => $token) { + $object = idx($objects, $token); + + if (!$object) { + continue; + } + + $uri = idx($object, 'uri'); + if (!strlen($uri)) { + continue; + } + + $result[$ref_key][] = $this->newBrowseURIRef() + ->setURI($object['uri']); + } + + yield $this->yieldMap($result); + } + +} diff --git a/src/browse/query/ArcanistBrowsePathURIHardpointQuery.php b/src/browse/query/ArcanistBrowsePathURIHardpointQuery.php new file mode 100644 --- /dev/null +++ b/src/browse/query/ArcanistBrowsePathURIHardpointQuery.php @@ -0,0 +1,128 @@ +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 loadHardpoint(array $refs, $hardpoint) { + $refs = $this->getRefsWithSupportedTypes($refs); + if (!$refs) { + yield $this->yieldMap(array()); + } + + $repository_ref = (yield $this->yieldRepositoryRef()); + if (!$repository_ref) { + yield $this->yieldMap(array()); + } + + $working_copy = $this->getWorkingCopy(); + $working_root = $working_copy->getPath(); + + $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 == $working_root) { + $path = ''; + } else { + $path = Filesystem::readablePath($full_path, $working_root); + } + + $params = array( + 'path' => $path, + 'lines' => $lines, + 'branch' => $ref->getBranch(), + ); + + $uri = $repository_ref->newBrowseURI($params); + + $results[$key][] = $this->newBrowseURIRef() + ->setURI($uri); + } + + yield $this->yieldMap($results); + } + + +} diff --git a/src/browse/query/ArcanistBrowseRevisionURIHardpointQuery.php b/src/browse/query/ArcanistBrowseRevisionURIHardpointQuery.php new file mode 100644 --- /dev/null +++ b/src/browse/query/ArcanistBrowseRevisionURIHardpointQuery.php @@ -0,0 +1,62 @@ +getRefsWithSupportedTypes($refs); + if (!$refs) { + yield $this->yieldMap(array()); + } + + yield $this->yieldRequests( + $refs, + array( + ArcanistBrowseRefPro::HARDPOINT_COMMITREFS, + )); + + $states = array(); + $map = array(); + foreach ($refs as $key => $ref) { + foreach ($ref->getCommitRefs() as $commit_ref) { + $hash = $commit_ref->getCommitHash(); + $states[$hash] = id(new ArcanistWorkingCopyStateRefPro()) + ->setCommitRef($commit_ref); + $map[$hash][] = $key; + } + } + + if (!$states) { + yield $this->yieldMap(array()); + } + + yield $this->yieldRequests( + $states, + array( + 'revisionRefs', + )); + + $results = array(); + foreach ($states as $hash => $state) { + foreach ($state->getRevisionRefs() as $revision) { + if ($revision->isClosed()) { + // Don't resolve closed revisions. + continue; + } + + $uri = $revision->getURI(); + + foreach ($map[$hash] as $key) { + $results[$key][] = $this->newBrowseURIRef() + ->setURI($uri); + } + } + } + + yield $this->yieldMap($results); + } + + +} diff --git a/src/browse/query/ArcanistBrowseURIHardpointQuery.php b/src/browse/query/ArcanistBrowseURIHardpointQuery.php new file mode 100644 --- /dev/null +++ b/src/browse/query/ArcanistBrowseURIHardpointQuery.php @@ -0,0 +1,49 @@ +getPhobjectClassConstant('BROWSETYPE', 32); + } + + public function getHardpoints() { + return array( + ArcanistBrowseRefPro::HARDPOINT_URIS, + ); + } + + protected function canLoadRef(ArcanistRefPro $ref) { + return ($ref instanceof ArcanistBrowseRefPro); + } + + 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 getAllBrowseQueries() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->execute(); + } + + final protected function newBrowseURIRef() { + return id(new ArcanistBrowseURIRefPro()) + ->setType($this->getSupportedBrowseType()); + } + +} diff --git a/src/browse/ref/ArcanistBrowseRefPro.php b/src/browse/ref/ArcanistBrowseRefPro.php new file mode 100644 --- /dev/null +++ b/src/browse/ref/ArcanistBrowseRefPro.php @@ -0,0 +1,69 @@ +getToken()); + } + + protected function newHardpoints() { + return array( + $this->newVectorHardpoint(self::HARDPOINT_COMMITREFS), + $this->newVectorHardpoint(self::HARDPOINT_URIS), + ); + } + + 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(self::HARDPOINT_URIS); + } + + public function getCommitRefs() { + return $this->getHardpoint(self::HARDPOINT_COMMITREFS); + } + +} diff --git a/src/browse/ref/ArcanistBrowseURIRefPro.php b/src/browse/ref/ArcanistBrowseURIRefPro.php new file mode 100644 --- /dev/null +++ b/src/browse/ref/ArcanistBrowseURIRefPro.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/hardpoint/ArcanistHardpointTask.php b/src/hardpoint/ArcanistHardpointTask.php --- a/src/hardpoint/ArcanistHardpointTask.php +++ b/src/hardpoint/ArcanistHardpointTask.php @@ -171,6 +171,19 @@ $result = $generator_result->getValue(); } else { $result = $generator->getReturn(); + + if ($result instanceof ArcanistHardpointTaskResult) { + throw new Exception( + pht( + 'Generator (for query "%s") returned an '. + '"ArcanistHardpointTaskResult" object, which is not a valid '. + 'thing to return from a generator.'. + "\n\n". + 'This almost always means the generator implementation has a '. + '"return $this->yield..." statement which should be '. + 'a "yield $this->yield..." instead.', + get_class($query))); + } } $this->attachResult($result); diff --git a/src/inspector/ArcanistBrowseRefInspector.php b/src/inspector/ArcanistBrowseRefInspector.php new file mode 100644 --- /dev/null +++ b/src/inspector/ArcanistBrowseRefInspector.php @@ -0,0 +1,22 @@ +setToken($argv[0]); + } + +} diff --git a/src/workflow/ArcanistInspectWorkflow.php b/src/workflow/ArcanistInspectWorkflow.php --- a/src/workflow/ArcanistInspectWorkflow.php +++ b/src/workflow/ArcanistInspectWorkflow.php @@ -21,7 +21,7 @@ public function getWorkflowArguments() { return array( - $this->newWorkflowArgument('all') + $this->newWorkflowArgument('explore') ->setHelp(pht('Load all object hardpoints.')), $this->newWorkflowArgument('objects') ->setWildcard(true), @@ -29,7 +29,7 @@ } public function runWorkflow() { - $is_all = $this->getArgument('all'); + $is_explore = $this->getArgument('explore'); $objects = $this->getArgument('objects'); $inspectors = ArcanistRefInspector::getAllInspectors(); @@ -88,7 +88,7 @@ $all_refs[] = $ref; } - if ($is_all) { + if ($is_explore) { foreach ($ref_lists as $ref_class => $refs) { $ref = head($refs);