[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Resolves references (like short commit names, branch names, tag names, etc.) 5 * into canonical, stable commit identifiers. This query works for all 6 * repository types. 7 */ 8 final class DiffusionLowLevelResolveRefsQuery 9 extends DiffusionLowLevelQuery { 10 11 private $refs; 12 13 public function withRefs(array $refs) { 14 $this->refs = $refs; 15 return $this; 16 } 17 18 public function executeQuery() { 19 if (!$this->refs) { 20 return array(); 21 } 22 23 switch ($this->getRepository()->getVersionControlSystem()) { 24 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 25 $result = $this->resolveGitRefs(); 26 break; 27 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 28 $result = $this->resolveMercurialRefs(); 29 break; 30 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 31 $result = $this->resolveSubversionRefs(); 32 break; 33 default: 34 throw new Exception('Unsupported repository type!'); 35 } 36 37 return $result; 38 } 39 40 private function resolveGitRefs() { 41 $repository = $this->getRepository(); 42 43 $future = $repository->getLocalCommandFuture('cat-file --batch-check'); 44 $future->write(implode("\n", $this->refs)); 45 list($stdout) = $future->resolvex(); 46 47 $lines = explode("\n", rtrim($stdout, "\n")); 48 if (count($lines) !== count($this->refs)) { 49 throw new Exception('Unexpected line count from `git cat-file`!'); 50 } 51 52 $hits = array(); 53 $tags = array(); 54 55 $lines = array_combine($this->refs, $lines); 56 foreach ($lines as $ref => $line) { 57 $parts = explode(' ', $line); 58 if (count($parts) < 2) { 59 throw new Exception("Failed to parse `git cat-file` output: {$line}"); 60 } 61 list($identifier, $type) = $parts; 62 63 if ($type == 'missing') { 64 // This is either an ambiguous reference which resolves to several 65 // objects, or an invalid reference. For now, always treat it as 66 // invalid. It would be nice to resolve all possibilities for 67 // ambiguous references at some point, although the strategy for doing 68 // so isn't clear to me. 69 continue; 70 } 71 72 switch ($type) { 73 case 'commit': 74 break; 75 case 'tag': 76 $tags[] = $identifier; 77 break; 78 default: 79 throw new Exception( 80 "Unexpected object type from `git cat-file`: {$line}"); 81 } 82 83 $hits[] = array( 84 'ref' => $ref, 85 'type' => $type, 86 'identifier' => $identifier, 87 ); 88 } 89 90 $tag_map = array(); 91 if ($tags) { 92 // If some of the refs were tags, just load every tag in order to figure 93 // out which commits they map to. This might be somewhat inefficient in 94 // repositories with a huge number of tags. 95 $tag_refs = id(new DiffusionLowLevelGitRefQuery()) 96 ->setRepository($repository) 97 ->withIsTag(true) 98 ->executeQuery(); 99 foreach ($tag_refs as $tag_ref) { 100 $tag_map[$tag_ref->getShortName()] = $tag_ref->getCommitIdentifier(); 101 } 102 } 103 104 $results = array(); 105 foreach ($hits as $hit) { 106 $type = $hit['type']; 107 $ref = $hit['ref']; 108 109 $alternate = null; 110 if ($type == 'tag') { 111 $alternate = $identifier; 112 $identifier = idx($tag_map, $ref); 113 if (!$identifier) { 114 throw new Exception("Failed to look up tag '{$ref}'!"); 115 } 116 } 117 118 $result = array( 119 'type' => $type, 120 'identifier' => $identifier, 121 ); 122 123 if ($alternate !== null) { 124 $result['alternate'] = $alternate; 125 } 126 127 $results[$ref][] = $result; 128 } 129 130 return $results; 131 } 132 133 private function resolveMercurialRefs() { 134 $repository = $this->getRepository(); 135 136 $futures = array(); 137 foreach ($this->refs as $ref) { 138 $futures[$ref] = $repository->getLocalCommandFuture( 139 'log --template=%s --rev %s', 140 '{node}', 141 hgsprintf('%s', $ref)); 142 } 143 144 $results = array(); 145 foreach (Futures($futures) as $ref => $future) { 146 try { 147 list($stdout) = $future->resolvex(); 148 } catch (CommandException $ex) { 149 if (preg_match('/ambiguous identifier/', $ex->getStdErr())) { 150 // This indicates that the ref ambiguously matched several things. 151 // Eventually, it would be nice to return all of them, but it is 152 // unclear how to best do that. For now, treat it as a miss instead. 153 continue; 154 } 155 throw $ex; 156 } 157 158 // It doesn't look like we can figure out the type (commit/branch/rev) 159 // from this output very easily. For now, just call everything a commit. 160 $type = 'commit'; 161 162 $results[$ref][] = array( 163 'type' => $type, 164 'identifier' => trim($stdout), 165 ); 166 } 167 168 return $results; 169 } 170 171 private function resolveSubversionRefs() { 172 $repository = $this->getRepository(); 173 174 $max_commit = id(new PhabricatorRepositoryCommit()) 175 ->loadOneWhere( 176 'repositoryID = %d ORDER BY epoch DESC, id DESC LIMIT 1', 177 $repository->getID()); 178 if (!$max_commit) { 179 // This repository is empty or hasn't parsed yet, so none of the refs are 180 // going to resolve. 181 return array(); 182 } 183 184 $max_commit_id = (int)$max_commit->getCommitIdentifier(); 185 186 $results = array(); 187 foreach ($this->refs as $ref) { 188 if ($ref == 'HEAD') { 189 // Resolve "HEAD" to mean "the most recent commit". 190 $results[$ref][] = array( 191 'type' => 'commit', 192 'identifier' => $max_commit_id, 193 ); 194 continue; 195 } 196 197 if (!preg_match('/^\d+$/', $ref)) { 198 // This ref is non-numeric, so it doesn't resolve to anything. 199 continue; 200 } 201 202 // Resolve other commits if we can deduce their existence. 203 204 // TODO: When we import only part of a repository, we won't necessarily 205 // have all of the smaller commits. Should we fail to resolve them here 206 // for repositories with a subpath? It might let us simplify other things 207 // elsewhere. 208 if ((int)$ref <= $max_commit_id) { 209 $results[$ref][] = array( 210 'type' => 'commit', 211 'identifier' => (int)$ref, 212 ); 213 } 214 } 215 216 return $results; 217 } 218 219 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |