@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at recaptime-dev/main 187 lines 5.4 kB view raw
1<?php 2 3/** 4 * Resolves references into canonical, stable commit identifiers by examining 5 * database caches. 6 * 7 * This is a counterpart to @{class:DiffusionLowLevelResolveRefsQuery}. This 8 * query offers fast resolution, but can not resolve everything that the 9 * low-level query can. 10 * 11 * This class can resolve the most common refs (commits, branches, tags) and 12 * can do so cheaply (by examining the database, without needing to make calls 13 * to the VCS or the service host). 14 */ 15final class DiffusionCachedResolveRefsQuery 16 extends DiffusionLowLevelQuery { 17 18 private $refs; 19 private $types; 20 21 public function withRefs(array $refs) { 22 $this->refs = $refs; 23 return $this; 24 } 25 26 public function withTypes(array $types) { 27 $this->types = $types; 28 return $this; 29 } 30 31 protected function executeQuery() { 32 if (!$this->refs) { 33 return array(); 34 } 35 36 switch ($this->getRepository()->getVersionControlSystem()) { 37 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 38 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 39 $result = $this->resolveGitAndMercurialRefs(); 40 break; 41 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 42 $result = $this->resolveSubversionRefs(); 43 break; 44 default: 45 throw new Exception(pht('Unsupported repository type!')); 46 } 47 48 if ($this->types !== null) { 49 $result = $this->filterRefsByType($result, $this->types); 50 } 51 52 return $result; 53 } 54 55 /** 56 * Resolve refs in Git and Mercurial repositories. 57 * 58 * We can resolve commit hashes from the commits table, and branch and tag 59 * names from the refcursor table. 60 */ 61 private function resolveGitAndMercurialRefs() { 62 $repository = $this->getRepository(); 63 64 $conn_r = $repository->establishConnection('r'); 65 66 $results = array(); 67 68 $prefixes = array(); 69 foreach ($this->refs as $ref) { 70 // We require refs to look like hashes and be at least 4 characters 71 // long. This is similar to the behavior of git. 72 if (preg_match('/^[a-f0-9]{4,}$/', $ref)) { 73 $prefixes[] = qsprintf( 74 $conn_r, 75 '(commitIdentifier LIKE %>)', 76 $ref); 77 } 78 } 79 80 if ($prefixes) { 81 $commits = queryfx_all( 82 $conn_r, 83 'SELECT commitIdentifier FROM %T 84 WHERE repositoryID = %s AND %LO', 85 id(new PhabricatorRepositoryCommit())->getTableName(), 86 $repository->getID(), 87 $prefixes); 88 89 foreach ($commits as $commit) { 90 $hash = $commit['commitIdentifier']; 91 foreach ($this->refs as $ref) { 92 if (!strncmp($hash, $ref, strlen($ref))) { 93 $results[$ref][] = array( 94 'type' => 'commit', 95 'identifier' => $hash, 96 ); 97 } 98 } 99 } 100 } 101 102 $name_hashes = array(); 103 foreach ($this->refs as $ref) { 104 $name_hashes[PhabricatorHash::digestForIndex($ref)] = $ref; 105 } 106 107 $cursors = queryfx_all( 108 $conn_r, 109 'SELECT c.refNameHash, c.refType, p.commitIdentifier, p.isClosed 110 FROM %T c JOIN %T p ON p.cursorID = c.id 111 WHERE c.repositoryPHID = %s AND c.refNameHash IN (%Ls)', 112 id(new PhabricatorRepositoryRefCursor())->getTableName(), 113 id(new PhabricatorRepositoryRefPosition())->getTableName(), 114 $repository->getPHID(), 115 array_keys($name_hashes)); 116 117 foreach ($cursors as $cursor) { 118 if (isset($name_hashes[$cursor['refNameHash']])) { 119 $results[$name_hashes[$cursor['refNameHash']]][] = array( 120 'type' => $cursor['refType'], 121 'identifier' => $cursor['commitIdentifier'], 122 'closed' => (bool)$cursor['isClosed'], 123 ); 124 125 // TODO: In Git, we don't store (and thus don't return) the hash 126 // of the tag itself. It would be vaguely nice to do this. 127 } 128 } 129 130 return $results; 131 } 132 133 134 /** 135 * Resolve refs in Subversion repositories. 136 * 137 * We can resolve all numeric identifiers and the keyword `HEAD`. 138 */ 139 private function resolveSubversionRefs() { 140 $repository = $this->getRepository(); 141 142 $max_commit = id(new PhabricatorRepositoryCommit()) 143 ->loadOneWhere( 144 'repositoryID = %d ORDER BY epoch DESC, id DESC LIMIT 1', 145 $repository->getID()); 146 if (!$max_commit) { 147 // This repository is empty or hasn't parsed yet, so none of the refs are 148 // going to resolve. 149 return array(); 150 } 151 152 $max_commit_id = (int)$max_commit->getCommitIdentifier(); 153 154 $results = array(); 155 foreach ($this->refs as $ref) { 156 if ($ref == 'HEAD') { 157 // Resolve "HEAD" to mean "the most recent commit". 158 $results[$ref][] = array( 159 'type' => 'commit', 160 'identifier' => $max_commit_id, 161 ); 162 continue; 163 } 164 165 if (!preg_match('/^\d+$/', $ref)) { 166 // This ref is non-numeric, so it doesn't resolve to anything. 167 continue; 168 } 169 170 // Resolve other commits if we can deduce their existence. 171 172 // TODO: When we import only part of a repository, we won't necessarily 173 // have all of the smaller commits. Should we fail to resolve them here 174 // for repositories with a subpath? It might let us simplify other things 175 // elsewhere. 176 if ((int)$ref <= $max_commit_id) { 177 $results[$ref][] = array( 178 'type' => 'commit', 179 'identifier' => (int)$ref, 180 ); 181 } 182 } 183 184 return $results; 185 } 186 187}