diff --git a/src/ref/ArcanistRepositoryRef.php b/src/ref/ArcanistRepositoryRef.php --- a/src/ref/ArcanistRepositoryRef.php +++ b/src/ref/ArcanistRepositoryRef.php @@ -108,4 +108,39 @@ return $branch; } + public function isPermanentRef(ArcanistMarkerRef $ref) { + $rules = idxv( + $this->parameters, + array('fields', 'refRules', 'permanentRefRules')); + + if ($rules === null) { + return false; + } + + // If the rules exist but there are no specified rules, treat every ref + // as permanent. + if (!$rules) { + return true; + } + + // TODO: It would be nice to unify evaluation of permanent ref rules + // across Arcanist and Phabricator. + + $ref_name = $ref->getName(); + foreach ($rules as $rule) { + $matches = null; + if (preg_match('(^regexp\\((.*)\\)\z)', $rule, $matches)) { + if (preg_match($matches[1], $ref_name)) { + return true; + } + } else { + if ($rule === $ref_name) { + return true; + } + } + } + + return false; + } + } diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php --- a/src/repository/api/ArcanistGitAPI.php +++ b/src/repository/api/ArcanistGitAPI.php @@ -1773,4 +1773,47 @@ $uri); } + protected function newPublishedCommitHashes() { + $remotes = $this->newRemoteRefQuery() + ->execute(); + if (!$remotes) { + return array(); + } + + $markers = $this->newMarkerRefQuery() + ->withIsRemoteCache(true) + ->execute(); + + if (!$markers) { + return array(); + } + + $runtime = $this->getRuntime(); + $workflow = $runtime->getCurrentWorkflow(); + + $workflow->loadHardpoints( + $remotes, + ArcanistRemoteRef::HARDPOINT_REPOSITORYREFS); + + $remotes = mpull($remotes, null, 'getRemoteName'); + + $hashes = array(); + + foreach ($markers as $marker) { + $remote_name = $marker->getRemoteName(); + $remote = idx($remotes, $remote_name); + if (!$remote) { + continue; + } + + if (!$remote->isPermanentRef($marker)) { + continue; + } + + $hashes[] = $marker->getCommitHash(); + } + + return $hashes; + } + } diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php --- a/src/repository/api/ArcanistRepositoryAPI.php +++ b/src/repository/api/ArcanistRepositoryAPI.php @@ -807,4 +807,12 @@ return $uri; } + final public function getPublishedCommitHashes() { + return $this->newPublishedCommitHashes(); + } + + protected function newPublishedCommitHashes() { + return array(); + } + } diff --git a/src/repository/marker/ArcanistGitRepositoryMarkerQuery.php b/src/repository/marker/ArcanistGitRepositoryMarkerQuery.php --- a/src/repository/marker/ArcanistGitRepositoryMarkerQuery.php +++ b/src/repository/marker/ArcanistGitRepositoryMarkerQuery.php @@ -23,11 +23,11 @@ $branch_prefix = 'refs/heads/'; $branch_length = strlen($branch_prefix); - // NOTE: Since we only return branches today, we restrict this operation - // to branches. + $remote_prefix = 'refs/remotes/'; + $remote_length = strlen($remote_prefix); list($stdout) = $api->newFuture( - 'for-each-ref --format %s -- refs/heads/', + 'for-each-ref --format %s -- refs/', implode('%01', $field_list))->resolve(); $markers = array(); @@ -53,9 +53,20 @@ list($ref, $hash, $epoch, $tree, $dst_hash, $summary, $text) = $fields; + $remote_name = null; + if (!strncmp($ref, $branch_prefix, $branch_length)) { $type = ArcanistMarkerRef::TYPE_BRANCH; $name = substr($ref, $branch_length); + } else if (!strncmp($ref, $remote_prefix, $remote_length)) { + // This isn't entirely correct: the ref may be a tag, etc. + $type = ArcanistMarkerRef::TYPE_BRANCH; + + $label = substr($ref, $remote_length); + $parts = explode('/', $label, 2); + + $remote_name = $parts[0]; + $name = $parts[1]; } else { // For now, discard other refs. continue; @@ -70,6 +81,10 @@ ->setSummary($summary) ->setMessage($text); + if ($remote_name !== null) { + $marker->setRemoteName($remote_name); + } + if (strlen($dst_hash)) { $commit_hash = $dst_hash; } else { diff --git a/src/repository/marker/ArcanistMarkerRef.php b/src/repository/marker/ArcanistMarkerRef.php --- a/src/repository/marker/ArcanistMarkerRef.php +++ b/src/repository/marker/ArcanistMarkerRef.php @@ -20,6 +20,7 @@ private $summary; private $message; private $isActive = false; + private $remoteName; public function getRefDisplayName() { switch ($this->getMarkerType()) { @@ -130,6 +131,15 @@ return $this->isActive; } + public function setRemoteName($remote_name) { + $this->remoteName = $remote_name; + return $this; + } + + public function getRemoteName() { + return $this->remoteName; + } + public function isBookmark() { return ($this->getMarkerType() === self::TYPE_BOOKMARK); } diff --git a/src/repository/marker/ArcanistRepositoryMarkerQuery.php b/src/repository/marker/ArcanistRepositoryMarkerQuery.php --- a/src/repository/marker/ArcanistRepositoryMarkerQuery.php +++ b/src/repository/marker/ArcanistRepositoryMarkerQuery.php @@ -9,6 +9,7 @@ private $commitHashes; private $ancestorCommitHashes; private $remotes; + private $isRemoteCache = false; final public function withMarkerTypes(array $types) { $this->markerTypes = array_fuse($types); @@ -26,6 +27,11 @@ return $this; } + final public function withIsRemoteCache($is_cache) { + $this->isRemoteCache = $is_cache; + return $this; + } + final public function withIsActive($active) { $this->isActive = $active; return $this; @@ -88,6 +94,16 @@ } } + if ($this->isRemoteCache !== null) { + $want_cache = $this->isRemoteCache; + foreach ($markers as $key => $marker) { + $is_cache = ($marker->getRemoteName() !== null); + if ($is_cache !== $want_cache) { + unset($markers[$key]); + } + } + } + return $this->sortMarkers($markers); } diff --git a/src/repository/remote/ArcanistRemoteRef.php b/src/repository/remote/ArcanistRemoteRef.php --- a/src/repository/remote/ArcanistRemoteRef.php +++ b/src/repository/remote/ArcanistRemoteRef.php @@ -89,4 +89,13 @@ return null; } + public function isPermanentRef(ArcanistMarkerRef $ref) { + $repository_ref = $this->getPushRepositoryRef(); + if (!$repository_ref) { + return false; + } + + return $repository_ref->isPermanentRef($ref); + } + } diff --git a/src/workflow/ArcanistLookWorkflow.php b/src/workflow/ArcanistLookWorkflow.php --- a/src/workflow/ArcanistLookWorkflow.php +++ b/src/workflow/ArcanistLookWorkflow.php @@ -41,6 +41,10 @@ return $this->lookRemotes(); } + if ($argv === array('published')) { + return $this->lookPublished(); + } + echo tsprintf( "%s\n", pht( @@ -195,6 +199,48 @@ echo tsprintf('%s', $view); } + + echo tsprintf("\n"); + echo tsprintf( + pht( + "Across the grove, a stream flows north toward ". + "**published** commits.\n")); + } + + private function lookPublished() { + echo tsprintf( + "%W\n\n", + pht( + 'You walk along the narrow bank of the stream as it winds lazily '. + 'downhill and turns east, gradually widening into a river.')); + + $api = $this->getRepositoryAPI(); + + $published = $api->getPublishedCommitHashes(); + + if ($published) { + echo tsprintf( + "%W\n\n", + pht( + 'Floating on the water, you see published commits:')); + + foreach ($published as $hash) { + echo tsprintf( + "%s\n", + $hash); + } + + echo tsprintf( + "\n%W\n", + pht( + 'They river bubbles peacefully.')); + } else { + echo tsprintf( + "%W\n", + pht( + 'The river bubbles quietly, but you do not see any published '. + 'commits anywhere.')); + } } }