@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
1<?php
2
3final class PhabricatorRepositoryManagementRebuildIdentitiesWorkflow
4 extends PhabricatorRepositoryManagementWorkflow {
5
6 private $identityCache = array();
7 private $phidCache = array();
8 private $dryRun;
9
10 protected function didConstruct() {
11 $this
12 ->setName('rebuild-identities')
13 ->setExamples(
14 '**rebuild-identities** [__options__] __repository__')
15 ->setSynopsis(pht('Rebuild repository identities from commits.'))
16 ->setArguments(
17 array(
18 array(
19 'name' => 'all-repositories',
20 'help' => pht('Rebuild identities across all repositories.'),
21 ),
22 array(
23 'name' => 'all-identities',
24 'help' => pht('Rebuild all currently-known identities.'),
25 ),
26 array(
27 'name' => 'repository',
28 'param' => 'repository',
29 'repeat' => true,
30 'help' => pht('Rebuild identities in a repository.'),
31 ),
32 array(
33 'name' => 'commit',
34 'param' => 'commit',
35 'repeat' => true,
36 'help' => pht('Rebuild identities for a commit.'),
37 ),
38 array(
39 'name' => 'user',
40 'param' => 'user',
41 'repeat' => true,
42 'help' => pht('Rebuild identities for a user.'),
43 ),
44 array(
45 'name' => 'email',
46 'param' => 'email',
47 'repeat' => true,
48 'help' => pht('Rebuild identities for an email address.'),
49 ),
50 array(
51 'name' => 'raw',
52 'param' => 'raw',
53 'repeat' => true,
54 'help' => pht('Rebuild identities for a raw commit string.'),
55 ),
56 array(
57 'name' => 'dry-run',
58 'help' => pht('Show changes, but do not make any changes.'),
59 ),
60 ));
61 }
62
63 public function execute(PhutilArgumentParser $args) {
64 $viewer = $this->getViewer();
65
66 $rebuilt_anything = false;
67
68 $all_repositories = $args->getArg('all-repositories');
69 $repositories = $args->getArg('repository');
70
71 if ($all_repositories && $repositories) {
72 throw new PhutilArgumentUsageException(
73 pht(
74 'Flags "--all-repositories" and "--repository" are not '.
75 'compatible.'));
76 }
77
78
79 $all_identities = $args->getArg('all-identities');
80 $raw = $args->getArg('raw');
81
82 if ($all_identities && $raw) {
83 throw new PhutilArgumentUsageException(
84 pht(
85 'Flags "--all-identities" and "--raw" are not '.
86 'compatible.'));
87 }
88
89 $dry_run = $args->getArg('dry-run');
90 $this->dryRun = $dry_run;
91
92 if ($this->dryRun) {
93 $this->logWarn(
94 pht('DRY RUN'),
95 pht('This is a dry run, so no changes will be written.'));
96 }
97
98 if ($all_repositories || $repositories) {
99 $rebuilt_anything = true;
100
101 if ($repositories) {
102 $repository_list = $this->loadRepositories($args, 'repository');
103 } else {
104 $repository_query = id(new PhabricatorRepositoryQuery())
105 ->setViewer($viewer);
106 $repository_list = new PhabricatorQueryIterator($repository_query);
107 }
108
109 foreach ($repository_list as $repository) {
110 $commit_query = id(new DiffusionCommitQuery())
111 ->setViewer($viewer)
112 ->needCommitData(true)
113 ->withRepositoryIDs(array($repository->getID()));
114
115 // See T13457. Adjust ordering to hit keys better and tweak page size
116 // to improve performance slightly, since these records are small.
117 $commit_query->setOrderVector(array('-epoch', '-id'));
118
119 $commit_iterator = id(new PhabricatorQueryIterator($commit_query))
120 ->setPageSize(1000);
121
122 $this->rebuildCommits($commit_iterator);
123 }
124 }
125
126 $commits = $args->getArg('commit');
127 if ($commits) {
128 $rebuilt_anything = true;
129 $commit_list = $this->loadCommits($args, 'commit');
130
131 // Reload commits to get commit data.
132 $commit_list = id(new DiffusionCommitQuery())
133 ->setViewer($viewer)
134 ->needCommitData(true)
135 ->withIDs(mpull($commit_list, 'getID'))
136 ->execute();
137
138 $this->rebuildCommits($commit_list);
139 }
140
141 $users = $args->getArg('user');
142 if ($users) {
143 $rebuilt_anything = true;
144
145 $user_list = $this->loadUsersFromArguments($users);
146 $this->rebuildUsers($user_list);
147 }
148
149 $emails = $args->getArg('email');
150 if ($emails) {
151 $rebuilt_anything = true;
152 $this->rebuildEmails($emails);
153 }
154
155 if ($all_identities || $raw) {
156 $rebuilt_anything = true;
157
158 if ($raw) {
159 $identities = id(new PhabricatorRepositoryIdentityQuery())
160 ->setViewer($viewer)
161 ->withIdentityNames($raw)
162 ->execute();
163
164 $identities = mpull($identities, null, 'getIdentityNameRaw');
165 foreach ($raw as $raw_identity) {
166 if (!isset($identities[$raw_identity])) {
167 throw new PhutilArgumentUsageException(
168 pht(
169 'No identity "%s" exists. When selecting identities with '.
170 '"--raw", the entire identity must match exactly.',
171 $raw_identity));
172 }
173 }
174
175 $identity_list = $identities;
176 } else {
177 $identity_query = id(new PhabricatorRepositoryIdentityQuery())
178 ->setViewer($viewer);
179
180 $identity_list = new PhabricatorQueryIterator($identity_query);
181
182 $this->logInfo(
183 pht('REBUILD'),
184 pht('Rebuilding all existing identities.'));
185 }
186
187 $this->rebuildIdentities($identity_list);
188 }
189
190 if (!$rebuilt_anything) {
191 throw new PhutilArgumentUsageException(
192 pht(
193 'Nothing specified to rebuild. Use flags to choose which '.
194 'identities to rebuild, or "--help" for help.'));
195 }
196
197 return 0;
198 }
199
200 private function rebuildCommits($commits) {
201 foreach ($commits as $commit) {
202 $needs_update = false;
203
204 $data = $commit->getCommitData();
205 $author = $data->getAuthorString();
206
207 $author_identity = $this->getIdentityForCommit(
208 $commit,
209 $author);
210
211 $author_phid = $commit->getAuthorIdentityPHID();
212 $identity_phid = $author_identity->getPHID();
213
214 $aidentity_phid = $identity_phid;
215 if ($author_phid !== $identity_phid) {
216 $commit->setAuthorIdentityPHID($identity_phid);
217 $data->setCommitDetail('authorIdentityPHID', $identity_phid);
218 $needs_update = true;
219 }
220
221 $committer_name = $data->getCommitterString();
222 $committer_phid = $commit->getCommitterIdentityPHID();
223 if (strlen($committer_name)) {
224 $committer_identity = $this->getIdentityForCommit(
225 $commit,
226 $committer_name);
227 $identity_phid = $committer_identity->getPHID();
228 } else {
229 $identity_phid = null;
230 }
231
232 if ($committer_phid !== $identity_phid) {
233 $commit->setCommitterIdentityPHID($identity_phid);
234 $data->setCommitDetail('committerIdentityPHID', $identity_phid);
235 $needs_update = true;
236 }
237
238 if ($needs_update) {
239 $commit->save();
240 $data->save();
241
242 $this->logInfo(
243 pht('COMMIT'),
244 pht(
245 'Rebuilt identities for "%s".',
246 $commit->getDisplayName()));
247 } else {
248 $this->logInfo(
249 pht('SKIP'),
250 pht(
251 'No changes for commit "%s".',
252 $commit->getDisplayName()));
253 }
254 }
255 }
256
257 private function getIdentityForCommit(
258 PhabricatorRepositoryCommit $commit,
259 $raw_identity) {
260
261 if (!isset($this->identityCache[$raw_identity])) {
262 $identity = $this->newIdentityEngine()
263 ->setSourcePHID($commit->getPHID())
264 ->newResolvedIdentity($raw_identity);
265
266 $this->identityCache[$raw_identity] = $identity;
267 }
268
269 return $this->identityCache[$raw_identity];
270 }
271
272
273 private function rebuildUsers($users) {
274 $viewer = $this->getViewer();
275
276 foreach ($users as $user) {
277 $this->logInfo(
278 pht('USER'),
279 pht(
280 'Rebuilding identities for user "%s".',
281 $user->getMonogram()));
282
283 $emails = id(new PhabricatorUserEmail())->loadAllWhere(
284 'userPHID = %s AND isVerified = 1',
285 $user->getPHID());
286 if ($emails) {
287 $this->rebuildEmails(mpull($emails, 'getAddress'));
288 }
289
290 $identities = id(new PhabricatorRepositoryIdentityQuery())
291 ->setViewer($viewer)
292 ->withRelatedPHIDs(array($user->getPHID()))
293 ->execute();
294
295 if (!$identities) {
296 $this->logWarn(
297 pht('NO IDENTITIES'),
298 pht('Found no identities directly related to user.'));
299 continue;
300 }
301
302 $this->rebuildIdentities($identities);
303 }
304 }
305
306 private function rebuildEmails($emails) {
307 $viewer = $this->getViewer();
308
309 foreach ($emails as $email) {
310 $this->logInfo(
311 pht('EMAIL'),
312 pht('Rebuilding identities for email address "%s".', $email));
313
314 $identities = id(new PhabricatorRepositoryIdentityQuery())
315 ->setViewer($viewer)
316 ->withEmailAddresses(array($email))
317 ->execute();
318
319 if (!$identities) {
320 $this->logWarn(
321 pht('NO IDENTITIES'),
322 pht('Found no identities for email address "%s".', $email));
323 continue;
324 }
325
326 $this->rebuildIdentities($identities);
327 }
328 }
329
330 private function rebuildIdentities($identities) {
331 $dry_run = $this->dryRun;
332
333 foreach ($identities as $identity) {
334 $raw_identity = $identity->getIdentityName();
335
336 if (isset($this->identityCache[$raw_identity])) {
337 $this->logInfo(
338 pht('SKIP'),
339 pht(
340 'Identity "%s" has already been rebuilt.',
341 $raw_identity));
342 continue;
343 }
344
345 $this->logInfo(
346 pht('IDENTITY'),
347 pht(
348 'Rebuilding identity "%s".',
349 $raw_identity));
350
351 $old_auto = $identity->getAutomaticGuessedUserPHID();
352 $old_assign = $identity->getManuallySetUserPHID();
353
354 $identity = $this->newIdentityEngine()
355 ->newUpdatedIdentity($identity);
356
357 $this->identityCache[$raw_identity] = $identity;
358
359 $new_auto = $identity->getAutomaticGuessedUserPHID();
360 $new_assign = $identity->getManuallySetUserPHID();
361
362 $same_auto = ($old_auto === $new_auto);
363 $same_assign = ($old_assign === $new_assign);
364
365 if ($same_auto && $same_assign) {
366 $this->logInfo(
367 pht('UNCHANGED'),
368 pht('No changes to identity.'));
369 } else {
370 if (!$same_auto) {
371 if ($dry_run) {
372 $this->logWarn(
373 pht('DETECTED PHID'),
374 pht(
375 '(Dry Run) Would update detected user from "%s" to "%s".',
376 $this->renderPHID($old_auto),
377 $this->renderPHID($new_auto)));
378 } else {
379 $this->logWarn(
380 pht('DETECTED PHID'),
381 pht(
382 'Detected user updated from "%s" to "%s".',
383 $this->renderPHID($old_auto),
384 $this->renderPHID($new_auto)));
385 }
386 }
387 if (!$same_assign) {
388 if ($dry_run) {
389 $this->logWarn(
390 pht('ASSIGNED PHID'),
391 pht(
392 '(Dry Run) Would update assigned user from "%s" to "%s".',
393 $this->renderPHID($old_assign),
394 $this->renderPHID($new_assign)));
395 } else {
396 $this->logWarn(
397 pht('ASSIGNED PHID'),
398 pht(
399 'Assigned user updated from "%s" to "%s".',
400 $this->renderPHID($old_assign),
401 $this->renderPHID($new_assign)));
402 }
403 }
404 }
405 }
406 }
407
408 private function renderPHID($phid) {
409 if ($phid == null) {
410 return pht('NULL');
411 }
412
413 if (!isset($this->phidCache[$phid])) {
414 $viewer = $this->getViewer();
415 $handles = $viewer->loadHandles(array($phid));
416 $this->phidCache[$phid] = pht(
417 '%s <%s>',
418 $handles[$phid]->getFullName(),
419 $phid);
420 }
421
422 return $this->phidCache[$phid];
423 }
424
425 private function newIdentityEngine() {
426 $viewer = $this->getViewer();
427
428 return id(new DiffusionRepositoryIdentityEngine())
429 ->setViewer($viewer)
430 ->setDryRun($this->dryRun);
431 }
432
433}