@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 PhabricatorAuditManagementDeleteWorkflow
4 extends PhabricatorAuditManagementWorkflow {
5
6 protected function didConstruct() {
7 $this
8 ->setName('delete')
9 ->setExamples('**delete** [--dry-run] ...')
10 ->setSynopsis(pht('Delete audit requests matching parameters.'))
11 ->setArguments(
12 array(
13 array(
14 'name' => 'dry-run',
15 'help' => pht(
16 'Show what would be deleted, but do not actually delete '.
17 'anything.'),
18 ),
19 array(
20 'name' => 'users',
21 'param' => 'names',
22 'help' => pht('Select only audits by a given list of users.'),
23 ),
24 array(
25 'name' => 'repositories',
26 'param' => 'repos',
27 'help' => pht(
28 'Select only audits in a given list of repositories.'),
29 ),
30 array(
31 'name' => 'commits',
32 'param' => 'commits',
33 'help' => pht('Select only audits for the given commits.'),
34 ),
35 array(
36 'name' => 'min-commit-date',
37 'param' => 'date',
38 'help' => pht(
39 'Select only audits for commits on or after the given date.'),
40 ),
41 array(
42 'name' => 'max-commit-date',
43 'param' => 'date',
44 'help' => pht(
45 'Select only audits for commits on or before the given date.'),
46 ),
47 array(
48 'name' => 'status',
49 'param' => 'status',
50 'help' => pht(
51 'Select only audits in the given status. By default, '.
52 'only open audits are selected.'),
53 ),
54 array(
55 'name' => 'ids',
56 'param' => 'ids',
57 'help' => pht('Select only audits with the given IDs.'),
58 ),
59 ));
60 }
61
62 public function execute(PhutilArgumentParser $args) {
63 $viewer = $this->getViewer();
64 $users = $this->loadUsers($args->getArg('users'));
65 $repos = $this->loadRepos($args->getArg('repositories'));
66 $commits = $this->loadCommits($args->getArg('commits'));
67 $ids = $this->parseList($args->getArg('ids'));
68
69 $status = $args->getArg('status');
70
71 $min_date = $this->loadDate($args->getArg('min-commit-date'));
72 $max_date = $this->loadDate($args->getArg('max-commit-date'));
73 if ($min_date && $max_date && ($min_date > $max_date)) {
74 throw new PhutilArgumentUsageException(
75 pht('Specified maximum date must come after specified minimum date.'));
76 }
77
78 $is_dry_run = $args->getArg('dry-run');
79
80 $query = id(new DiffusionCommitQuery())
81 ->setViewer($this->getViewer())
82 ->needAuditRequests(true);
83
84 if ($status) {
85 $query->withStatuses(array($status));
86 }
87
88 $id_map = array();
89 if ($ids) {
90 $id_map = array_fuse($ids);
91 $query->withAuditIDs($ids);
92 }
93
94 if ($repos) {
95 $query->withRepositoryIDs(mpull($repos, 'getID'));
96
97 // See T13457. If we're iterating over commits in a single large
98 // repository, the lack of a "<repositoryID, [id]>" key can slow things
99 // down. Iterate in a specific order to use a key which is present
100 // on the table ("<repositoryID, epoch, [id]>").
101 $query->setOrderVector(array('-epoch', '-id'));
102 }
103
104 $auditor_map = array();
105 if ($users) {
106 $auditor_map = array_fuse(mpull($users, 'getPHID'));
107 $query->withAuditorPHIDs($auditor_map);
108 }
109
110 if ($commits) {
111 $query->withPHIDs(mpull($commits, 'getPHID'));
112 }
113
114 $commit_iterator = new PhabricatorQueryIterator($query);
115
116 // See T13457. We may be examining many commits; each commit is small so
117 // we can safely increase the page size to improve performance a bit.
118 $commit_iterator->setPageSize(1000);
119
120 $audits = array();
121 foreach ($commit_iterator as $commit) {
122 $commit_audits = $commit->getAudits();
123 foreach ($commit_audits as $key => $audit) {
124 if ($id_map && empty($id_map[$audit->getID()])) {
125 unset($commit_audits[$key]);
126 continue;
127 }
128
129 if ($auditor_map && empty($auditor_map[$audit->getAuditorPHID()])) {
130 unset($commit_audits[$key]);
131 continue;
132 }
133
134 if ($min_date && $commit->getEpoch() < $min_date) {
135 unset($commit_audits[$key]);
136 continue;
137 }
138
139 if ($max_date && $commit->getEpoch() > $max_date) {
140 unset($commit_audits[$key]);
141 continue;
142 }
143 }
144
145 if (!$commit_audits) {
146 continue;
147 }
148
149 $handles = id(new PhabricatorHandleQuery())
150 ->setViewer($viewer)
151 ->withPHIDs(mpull($commit_audits, 'getAuditorPHID'))
152 ->execute();
153
154 foreach ($commit_audits as $audit) {
155 $audit_id = $audit->getID();
156 $status = $audit->getAuditRequestStatusObject();
157
158 $description = sprintf(
159 '%10d %-16s %-16s %s: %s',
160 $audit_id,
161 $handles[$audit->getAuditorPHID()]->getName(),
162 $status->getStatusName(),
163 $commit->getRepository()->formatCommitName(
164 $commit->getCommitIdentifier()),
165 trim($commit->getSummary()));
166
167 $audits[] = array(
168 'auditID' => $audit_id,
169 'commitPHID' => $commit->getPHID(),
170 'description' => $description,
171 );
172 }
173 }
174
175 if (!$audits) {
176 echo tsprintf(
177 "%s\n",
178 pht('No audits match the query.'));
179 return 0;
180 }
181
182 foreach ($audits as $audit_spec) {
183 echo tsprintf(
184 "%s\n",
185 $audit_spec['description']);
186 }
187
188 if ($is_dry_run) {
189 echo tsprintf(
190 "%s\n",
191 pht('This is a dry run, so no changes will be made.'));
192 return 0;
193 }
194
195 $message = pht(
196 'Really delete these %s audit(s)? They will be permanently deleted '.
197 'and can not be recovered.',
198 phutil_count($audits));
199 if (!phutil_console_confirm($message)) {
200 echo tsprintf(
201 "%s\n",
202 pht('User aborted the workflow.'));
203 return 1;
204 }
205
206 $audits_by_commit = igroup($audits, 'commitPHID');
207 foreach ($audits_by_commit as $commit_phid => $audit_specs) {
208 $audit_ids = ipull($audit_specs, 'auditID');
209
210 $audits = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere(
211 'id IN (%Ld)',
212 $audit_ids);
213
214 foreach ($audits as $audit) {
215 $id = $audit->getID();
216
217 echo tsprintf(
218 "%s\n",
219 pht('Deleting audit %d...', $id));
220
221 $audit->delete();
222 }
223
224 $this->synchronizeCommitAuditState($commit_phid);
225 }
226
227 return 0;
228 }
229
230 private function loadUsers($users) {
231 $users = $this->parseList($users);
232 if (!$users) {
233 return null;
234 }
235
236 $objects = id(new PhabricatorPeopleQuery())
237 ->setViewer($this->getViewer())
238 ->withUsernames($users)
239 ->execute();
240 $objects = mpull($objects, null, 'getUsername');
241
242 foreach ($users as $name) {
243 if (empty($objects[$name])) {
244 throw new PhutilArgumentUsageException(
245 pht('No such user with username "%s"!', $name));
246 }
247 }
248
249 return $objects;
250 }
251
252 private function parseList($list) {
253 $list = preg_split('/\s*,\s*/', $list);
254
255 foreach ($list as $key => $item) {
256 $list[$key] = trim($item);
257 }
258
259 foreach ($list as $key => $item) {
260 if (!strlen($item)) {
261 unset($list[$key]);
262 }
263 }
264
265 return $list;
266 }
267
268 private function loadRepos($identifiers) {
269 $identifiers = $this->parseList($identifiers);
270 if (!$identifiers) {
271 return null;
272 }
273
274 $query = id(new PhabricatorRepositoryQuery())
275 ->setViewer($this->getViewer())
276 ->withIdentifiers($identifiers);
277
278 $repos = $query->execute();
279
280 $map = $query->getIdentifierMap();
281 foreach ($identifiers as $identifier) {
282 if (empty($map[$identifier])) {
283 throw new PhutilArgumentUsageException(
284 pht('No repository "%s" exists!', $identifier));
285 }
286 }
287
288 return $repos;
289 }
290
291 private function loadDate($date) {
292 if (!$date) {
293 return null;
294 }
295
296 $epoch = strtotime($date);
297 if (!$epoch || $epoch < 1) {
298 throw new PhutilArgumentUsageException(
299 pht(
300 'Unable to parse date "%s". Use a format like "%s".',
301 $date,
302 '2000-01-01'));
303 }
304
305 return $epoch;
306 }
307
308 private function loadCommits($commits) {
309 $names = $this->parseList($commits);
310 if (!$names) {
311 return null;
312 }
313
314 $query = id(new DiffusionCommitQuery())
315 ->setViewer($this->getViewer())
316 ->withIdentifiers($names);
317
318 $commits = $query->execute();
319
320 $map = $query->getIdentifierMap();
321 foreach ($names as $name) {
322 if (empty($map[$name])) {
323 throw new PhutilArgumentUsageException(
324 pht('No such commit "%s"!', $name));
325 }
326 }
327
328 return $commits;
329 }
330
331}