Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / diffusion / conduit / DiffusionHistoryQueryConduitAPIMethod.php
blobc6ae6dbd2facbbac05c0b08408e327b20f6d2f08
1 <?php
3 final class DiffusionHistoryQueryConduitAPIMethod
4 extends DiffusionQueryConduitAPIMethod {
6 private $parents = array();
8 public function getAPIMethodName() {
9 return 'diffusion.historyquery';
12 public function getMethodDescription() {
13 return pht(
14 'Returns history information for a repository at a specific '.
15 'commit and path.');
18 protected function defineReturnType() {
19 return 'array';
22 protected function defineCustomParamTypes() {
23 return array(
24 'commit' => 'required string',
25 'against' => 'optional string',
26 'path' => 'required string',
27 'offset' => 'required int',
28 'limit' => 'required int',
29 'needDirectChanges' => 'optional bool',
30 'needChildChanges' => 'optional bool',
34 protected function getResult(ConduitAPIRequest $request) {
35 $path_changes = parent::getResult($request);
37 return array(
38 'pathChanges' => mpull($path_changes, 'toDictionary'),
39 'parents' => $this->parents,
43 protected function getGitResult(ConduitAPIRequest $request) {
44 $drequest = $this->getDiffusionRequest();
45 $repository = $drequest->getRepository();
46 $commit_hash = $request->getValue('commit');
47 $against_hash = $request->getValue('against');
49 $path = $request->getValue('path');
50 if (!strlen($path)) {
51 $path = null;
54 $offset = $request->getValue('offset');
55 $limit = $request->getValue('limit');
57 if (strlen($against_hash)) {
58 $commit_range = "${against_hash}..${commit_hash}";
59 } else {
60 $commit_range = $commit_hash;
63 $argv = array();
65 $argv[] = '--skip';
66 $argv[] = $offset;
68 $argv[] = '--max-count';
69 $argv[] = $limit;
71 $argv[] = '--format=%H:%P';
73 $argv[] = gitsprintf('%s', $commit_range);
75 $argv[] = '--';
77 if ($path !== null) {
78 $argv[] = $path;
81 list($stdout) = $repository->execxLocalCommand(
82 'log %Ls',
83 $argv);
85 $lines = explode("\n", trim($stdout));
86 $lines = array_filter($lines);
88 $hash_list = array();
89 $parent_map = array();
90 foreach ($lines as $line) {
91 list($hash, $parents) = explode(':', $line);
92 $hash_list[] = $hash;
93 $parent_map[$hash] = preg_split('/\s+/', $parents);
96 $this->parents = $parent_map;
98 if (!$hash_list) {
99 return array();
102 return DiffusionQuery::loadHistoryForCommitIdentifiers(
103 $hash_list,
104 $drequest);
107 protected function getMercurialResult(ConduitAPIRequest $request) {
108 $drequest = $this->getDiffusionRequest();
109 $repository = $drequest->getRepository();
110 $commit_hash = $request->getValue('commit');
111 $path = $request->getValue('path');
112 $offset = $request->getValue('offset');
113 $limit = $request->getValue('limit');
115 $path = DiffusionPathIDQuery::normalizePath($path);
116 $path = ltrim($path, '/');
118 // NOTE: Older versions of Mercurial give different results for these
119 // commands (see T1268):
121 // $ hg log -- ''
122 // $ hg log
124 // All versions of Mercurial give different results for these commands
125 // (merge commits are excluded with the "." version):
127 // $ hg log -- .
128 // $ hg log
130 // If we don't have a path component in the query, omit it from the command
131 // entirely to avoid these inconsistencies.
133 // NOTE: When viewing the history of a file, we don't use "-b", because
134 // Mercurial stops history at the branchpoint but we're interested in all
135 // ancestors. When viewing history of a branch, we do use "-b", and thus
136 // stop history (this is more consistent with the Mercurial worldview of
137 // branches).
139 $path_args = array();
140 if (strlen($path)) {
141 $path_args[] = $path;
142 $revset_arg = hgsprintf(
143 'reverse(ancestors(%s))',
144 $commit_hash);
145 } else {
146 $revset_arg = hgsprintf(
147 'reverse(ancestors(%s)) and branch(%s)',
148 $commit_hash,
149 $drequest->getBranch());
152 $hg_analyzer = PhutilBinaryAnalyzer::getForBinary('hg');
153 if ($hg_analyzer->isMercurialTemplatePnodeAvailable()) {
154 $hg_log_template = '{node} {p1.node} {p2.node}\\n';
155 } else {
156 $hg_log_template = '{node} {p1node} {p2node}\\n';
159 list($stdout) = $repository->execxLocalCommand(
160 'log --template %s --limit %d --rev %s -- %Ls',
161 $hg_log_template,
162 ($offset + $limit), // No '--skip' in Mercurial.
163 $revset_arg,
164 $path_args);
166 $lines = explode("\n", trim($stdout));
167 $lines = array_slice($lines, $offset);
169 $hash_list = array();
170 $parent_map = array();
172 $last = null;
173 foreach (array_reverse($lines) as $line) {
174 $parts = explode(' ', trim($line));
175 $hash = $parts[0];
176 $parents = array_slice($parts, 1, 2);
177 foreach ($parents as $parent) {
178 if (!preg_match('/^0+\z/', $parent)) {
179 $parent_map[$hash][] = $parent;
182 // This may happen for the zeroth commit in repository, both hashes
183 // are "000000000...".
184 if (empty($parent_map[$hash])) {
185 $parent_map[$hash] = array('...');
188 // The rendering code expects the first commit to be "mainline", like
189 // Git. Flip the order so it does the right thing.
190 $parent_map[$hash] = array_reverse($parent_map[$hash]);
192 $hash_list[] = $hash;
193 $last = $hash;
196 $hash_list = array_reverse($hash_list);
197 $this->parents = array_reverse($parent_map, true);
199 return DiffusionQuery::loadHistoryForCommitIdentifiers(
200 $hash_list,
201 $drequest);
204 protected function getSVNResult(ConduitAPIRequest $request) {
205 $drequest = $this->getDiffusionRequest();
206 $repository = $drequest->getRepository();
207 $commit = $request->getValue('commit');
208 $path = $request->getValue('path');
209 $offset = $request->getValue('offset');
210 $limit = $request->getValue('limit');
211 $need_direct_changes = $request->getValue('needDirectChanges');
212 $need_child_changes = $request->getValue('needChildChanges');
214 $conn_r = $repository->establishConnection('r');
216 $paths = queryfx_all(
217 $conn_r,
218 'SELECT id, path FROM %T WHERE pathHash IN (%Ls)',
219 PhabricatorRepository::TABLE_PATH,
220 array(md5('/'.trim($path, '/'))));
221 $paths = ipull($paths, 'id', 'path');
222 $path_id = idx($paths, '/'.trim($path, '/'));
224 if (!$path_id) {
225 return array();
228 $filter_query = qsprintf($conn_r, '');
229 if ($need_direct_changes) {
230 if ($need_child_changes) {
231 $filter_query = qsprintf(
232 $conn_r,
233 'AND (isDirect = 1 OR changeType = %s)',
234 DifferentialChangeType::TYPE_CHILD);
235 } else {
236 $filter_query = qsprintf(
237 $conn_r,
238 'AND (isDirect = 1)');
242 $history_data = queryfx_all(
243 $conn_r,
244 'SELECT * FROM %T WHERE repositoryID = %d AND pathID = %d
245 AND commitSequence <= %d
247 ORDER BY commitSequence DESC
248 LIMIT %d, %d',
249 PhabricatorRepository::TABLE_PATHCHANGE,
250 $repository->getID(),
251 $path_id,
252 $commit ? $commit : 0x7FFFFFFF,
253 $filter_query,
254 $offset,
255 $limit);
257 $commits = array();
258 $commit_data = array();
260 $commit_ids = ipull($history_data, 'commitID');
261 if ($commit_ids) {
262 $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
263 'id IN (%Ld)',
264 $commit_ids);
265 if ($commits) {
266 $commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
267 'commitID in (%Ld)',
268 $commit_ids);
269 $commit_data = mpull($commit_data, null, 'getCommitID');
273 $history = array();
274 foreach ($history_data as $row) {
275 $item = new DiffusionPathChange();
277 $commit = idx($commits, $row['commitID']);
278 if ($commit) {
279 $item->setCommit($commit);
280 $item->setCommitIdentifier($commit->getCommitIdentifier());
281 $data = idx($commit_data, $commit->getID());
282 if ($data) {
283 $item->setCommitData($data);
287 $item->setChangeType($row['changeType']);
288 $item->setFileType($row['fileType']);
290 $history[] = $item;
293 return $history;