@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

Provide a more flexible script for administrative management of audits

Summary: Fixes T3679. This comes up every so often and the old script is extremely broad (nuke everything in a repository). Provide a more surgical tool.

Test Plan: Ran a bunch of variations of the script and they all seemed to work OK.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran, staticshock

Maniphest Tasks: T3679

Differential Revision: https://secure.phabricator.com/D6678

+342 -81
+1
bin/audit
··· 1 + ../scripts/setup/manage_audit.php
+2 -80
scripts/repository/audit.php
··· 1 1 #!/usr/bin/env php 2 2 <?php 3 3 4 - $root = dirname(dirname(dirname(__FILE__))); 5 - require_once $root.'/scripts/__init_script__.php'; 6 - 7 - $args = new PhutilArgumentParser($argv); 8 - $args->setTagline('manage open Audit requests'); 9 - $args->setSynopsis(<<<EOSYNOPSIS 10 - **audit.php** __repository_callsign__ 11 - Close all open audit requests in a repository. This is intended to 12 - reset the state of an imported repository which triggered a bunch of 13 - spurious audit requests during import. 14 - 15 - EOSYNOPSIS 16 - ); 17 - $args->parseStandardArguments(); 18 - $args->parse( 19 - array( 20 - array( 21 - 'name' => 'more', 22 - 'wildcard' => true, 23 - ), 24 - )); 25 - 26 - $more = $args->getArg('more'); 27 - if (count($more) !== 1) { 28 - $args->printHelpAndExit(); 29 - } 30 - $callsign = reset($more); 31 - 32 - $repository = id(new PhabricatorRepository())->loadOneWhere( 33 - 'callsign = %s', 34 - $callsign); 35 - if (!$repository) { 36 - throw new Exception("No repository exists with callsign '{$callsign}'!"); 37 - } 38 - 39 - $ok = phutil_console_confirm( 40 - 'This will reset all open audit requests ("Audit Required" or "Concern '. 41 - 'Raised") for commits in this repository to "Audit Not Required". This '. 42 - 'operation destroys information and can not be undone! Are you sure '. 43 - 'you want to proceed?'); 44 - if (!$ok) { 45 - echo "OK, aborting.\n"; 46 - die(1); 47 - } 48 - 49 - echo "Loading commits...\n"; 50 - $all_commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( 51 - 'repositoryID = %d', 52 - $repository->getID()); 53 - 54 - echo "Clearing audit requests...\n"; 55 - 56 - foreach ($all_commits as $commit) { 57 - $query = id(new PhabricatorAuditQuery()) 58 - ->withStatus(PhabricatorAuditQuery::STATUS_OPEN) 59 - ->withCommitPHIDs(array($commit->getPHID())); 60 - $requests = $query->execute(); 61 - 62 - echo "Clearing ".$commit->getPHID()."... "; 63 - 64 - if (!$requests) { 65 - echo "nothing to do.\n"; 66 - continue; 67 - } else { 68 - echo count($requests)." requests to clear"; 69 - } 70 - 71 - foreach ($requests as $request) { 72 - $request->setAuditStatus( 73 - PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED); 74 - $request->save(); 75 - echo "."; 76 - } 77 - 78 - $commit->setAuditStatus(PhabricatorAuditCommitStatusConstants::NONE); 79 - $commit->save(); 80 - echo "\n"; 81 - } 82 - 83 - echo "Done.\n"; 4 + echo "This script has been replaced with `bin/audit`.\n"; 5 + exit(1);
+22
scripts/setup/manage_audit.php
··· 1 + #!/usr/bin/env php 2 + <?php 3 + 4 + $root = dirname(dirname(dirname(__FILE__))); 5 + require_once $root.'/scripts/__init_script__.php'; 6 + 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setTagline('manage audits'); 9 + $args->setSynopsis(<<<EOSYNOPSIS 10 + **audit** __command__ [__options__] 11 + Manage Phabricator audits. 12 + 13 + EOSYNOPSIS 14 + ); 15 + $args->parseStandardArguments(); 16 + 17 + $workflows = array( 18 + new PhabricatorAuditManagementDeleteWorkflow(), 19 + new PhutilHelpArgumentWorkflow(), 20 + ); 21 + 22 + $args->parseWorkflows($workflows);
+4
src/__phutil_library_map__.php
··· 862 862 'PhabricatorAuditListController' => 'applications/audit/controller/PhabricatorAuditListController.php', 863 863 'PhabricatorAuditListView' => 'applications/audit/view/PhabricatorAuditListView.php', 864 864 'PhabricatorAuditMailReceiver' => 'applications/audit/mail/PhabricatorAuditMailReceiver.php', 865 + 'PhabricatorAuditManagementDeleteWorkflow' => 'applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php', 866 + 'PhabricatorAuditManagementWorkflow' => 'applications/audit/management/PhabricatorAuditManagementWorkflow.php', 865 867 'PhabricatorAuditPreviewController' => 'applications/audit/controller/PhabricatorAuditPreviewController.php', 866 868 'PhabricatorAuditQuery' => 'applications/audit/query/PhabricatorAuditQuery.php', 867 869 'PhabricatorAuditReplyHandler' => 'applications/audit/mail/PhabricatorAuditReplyHandler.php', ··· 2884 2886 'PhabricatorAuditListController' => 'PhabricatorAuditController', 2885 2887 'PhabricatorAuditListView' => 'AphrontView', 2886 2888 'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver', 2889 + 'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow', 2890 + 'PhabricatorAuditManagementWorkflow' => 'PhutilArgumentWorkflow', 2887 2891 'PhabricatorAuditPreviewController' => 'PhabricatorAuditController', 2888 2892 'PhabricatorAuditReplyHandler' => 'PhabricatorMailReplyHandler', 2889 2893 'PhabricatorAuthAccountView' => 'AphrontView',
+282
src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuditManagementDeleteWorkflow 4 + extends PhabricatorAuditManagementWorkflow { 5 + 6 + public function didConstruct() { 7 + $this 8 + ->setName('delete') 9 + ->setExamples('**delete** [--dry-run] ...') 10 + ->setSynopsis('Delete audit requests matching parameters.') 11 + ->setArguments( 12 + array( 13 + array( 14 + 'name' => 'dry-run', 15 + 'help' => 'Show what would be deleted, but do not actually delete '. 16 + 'anything.', 17 + ), 18 + array( 19 + 'name' => 'users', 20 + 'param' => 'names', 21 + 'help' => 'Select only audits by a given list of users.', 22 + ), 23 + array( 24 + 'name' => 'repositories', 25 + 'param' => 'repos', 26 + 'help' => 'Select only audits in a given list of repositories.', 27 + ), 28 + array( 29 + 'name' => 'commits', 30 + 'param' => 'commits', 31 + 'help' => 'Select only audits for the given commits.', 32 + ), 33 + array( 34 + 'name' => 'min-commit-date', 35 + 'param' => 'date', 36 + 'help' => 'Select only audits for commits on or after the given '. 37 + 'date.', 38 + ), 39 + array( 40 + 'name' => 'max-commit-date', 41 + 'param' => 'date', 42 + 'help' => 'Select only audits for commits on or before the given '. 43 + 'date.', 44 + ), 45 + array( 46 + 'name' => 'status', 47 + 'param' => 'status', 48 + 'help' => 'Select only audits in the given status. By default, '. 49 + 'only open audits are selected.', 50 + ), 51 + array( 52 + 'name' => 'ids', 53 + 'param' => 'ids', 54 + 'help' => 'Select only audits with the given IDs.', 55 + ), 56 + )); 57 + } 58 + 59 + public function execute(PhutilArgumentParser $args) { 60 + $viewer = $this->getViewer(); 61 + $users = $this->loadUsers($args->getArg('users')); 62 + $repos = $this->loadRepos($args->getArg('repositories')); 63 + $commits = $this->loadCommits($args->getArg('commits')); 64 + $ids = $this->parseList($args->getArg('ids')); 65 + 66 + $status = $args->getArg('status'); 67 + if (!$status) { 68 + $status = PhabricatorAuditQuery::STATUS_OPEN; 69 + } 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 + "Specified max date must come after specified min date."); 76 + } 77 + 78 + $is_dry_run = $args->getArg('dry-run'); 79 + 80 + $query = id(new PhabricatorAuditQuery()) 81 + ->needCommits(true); 82 + 83 + if ($status) { 84 + $query->withStatus($status); 85 + } 86 + 87 + if ($ids) { 88 + $query->withIDs($ids); 89 + } 90 + 91 + if ($repos) { 92 + $query->withRepositoryPHIDs(mpull($repos, 'getPHID')); 93 + } 94 + 95 + if ($users) { 96 + $query->withAuditorPHIDs(mpull($users, 'getPHID')); 97 + } 98 + 99 + if ($commits) { 100 + $query->withCommitPHIDs(mpull($commits, 'getPHID')); 101 + } 102 + 103 + $audits = $query->execute(); 104 + $commits = $query->getCommits(); 105 + 106 + // TODO: AuditQuery is currently not policy-aware and uses an old query 107 + // to load commits. Load them in the modern way to get repositories. Remove 108 + // this after modernizing PhabricatorAuditQuery. 109 + $commits = id(new DiffusionCommitQuery()) 110 + ->setViewer($viewer) 111 + ->withPHIDs(mpull($commits, 'getPHID')) 112 + ->execute(); 113 + $commits = mpull($commits, null, 'getPHID'); 114 + 115 + foreach ($audits as $key => $audit) { 116 + $commit = idx($commits, $audit->getCommitPHID()); 117 + if (!$commit) { 118 + unset($audits[$key]); 119 + continue; 120 + } 121 + 122 + if ($min_date && $commit->getEpoch() < $min_date) { 123 + unset($audits[$key]); 124 + continue; 125 + } 126 + 127 + if ($max_date && $commit->getEpoch() > $max_date) { 128 + unset($audits[$key]); 129 + continue; 130 + } 131 + } 132 + 133 + $console = PhutilConsole::getConsole(); 134 + 135 + if (!$audits) { 136 + $console->writeErr("%s\n", pht("No audits match the query.")); 137 + return 0; 138 + } 139 + 140 + $handles = id(new PhabricatorHandleQuery()) 141 + ->setViewer($this->getViewer()) 142 + ->withPHIDs(mpull($audits, 'getAuditorPHID')) 143 + ->execute(); 144 + 145 + 146 + foreach ($audits as $audit) { 147 + $commit = idx($commits, $audit->getCommitPHID()); 148 + 149 + $console->writeOut( 150 + "%s\n", 151 + sprintf( 152 + "%10d %-16s %-16s %s: %s", 153 + $audit->getID(), 154 + $handles[$audit->getAuditorPHID()]->getName(), 155 + PhabricatorAuditStatusConstants::getStatusName( 156 + $audit->getAuditStatus()), 157 + $commit->getRepository()->formatCommitName( 158 + $commit->getCommitIdentifier()), 159 + trim($commit->getSummary()))); 160 + } 161 + 162 + if (!$is_dry_run) { 163 + $message = pht( 164 + 'Really delete these %d audit(s)? They will be permanently deleted '. 165 + 'and can not be recovered.', 166 + count($audits)); 167 + if ($console->confirm($message)) { 168 + foreach ($audits as $audit) { 169 + $id = $audit->getID(); 170 + $console->writeOut("%s\n", pht("Deleting audit %d...", $id)); 171 + $audit->delete(); 172 + } 173 + } 174 + } 175 + 176 + return 0; 177 + } 178 + 179 + private function getViewer() { 180 + return PhabricatorUser::getOmnipotentUser(); 181 + } 182 + 183 + private function loadUsers($users) { 184 + $users = $this->parseList($users); 185 + if (!$users) { 186 + return null; 187 + } 188 + 189 + $objects = id(new PhabricatorPeopleQuery()) 190 + ->setViewer($this->getViewer()) 191 + ->withUsernames($users) 192 + ->execute(); 193 + $objects = mpull($objects, null, 'getUsername'); 194 + 195 + foreach ($users as $name) { 196 + if (empty($objects[$name])) { 197 + throw new PhutilArgumentUsageException( 198 + pht('No such user with username "%s"!', $name)); 199 + } 200 + } 201 + 202 + return $objects; 203 + } 204 + 205 + private function parseList($list) { 206 + $list = preg_split('/\s*,\s*/', $list); 207 + 208 + foreach ($list as $key => $item) { 209 + $list[$key] = trim($item); 210 + } 211 + 212 + foreach ($list as $key => $item) { 213 + if (!strlen($item)) { 214 + unset($list[$key]); 215 + } 216 + } 217 + 218 + return $list; 219 + } 220 + 221 + private function loadRepos($callsigns) { 222 + $callsigns = $this->parseList($callsigns); 223 + if (!$callsigns) { 224 + return null; 225 + } 226 + 227 + $repos = id(new PhabricatorRepositoryQuery()) 228 + ->setViewer($this->getViewer()) 229 + ->withCallsigns($callsigns) 230 + ->execute(); 231 + $repos = mpull($repos, null, 'getCallsign'); 232 + 233 + foreach ($callsigns as $sign) { 234 + if (empty($repos[$sign])) { 235 + throw new PhutilArgumentUsageException( 236 + pht('No such repository with callsign "%s"!', $sign)); 237 + } 238 + } 239 + 240 + return $repos; 241 + } 242 + 243 + private function loadDate($date) { 244 + if (!$date) { 245 + return null; 246 + } 247 + 248 + $epoch = strtotime($date); 249 + if (!$epoch || $epoch < 1) { 250 + throw new PhutilArgumentUsageException( 251 + pht( 252 + 'Unable to parse date "%s". Use a format like "2000-01-01".', 253 + $date)); 254 + } 255 + 256 + return $epoch; 257 + } 258 + 259 + private function loadCommits($commits) { 260 + $names = $this->parseList($commits); 261 + if (!$names) { 262 + return null; 263 + } 264 + 265 + $query = id(new DiffusionCommitQuery()) 266 + ->setViewer($this->getViewer()) 267 + ->withIdentifiers($names); 268 + 269 + $commits = $query->execute(); 270 + 271 + $map = $query->getIdentifierMap(); 272 + foreach ($names as $name) { 273 + if (empty($map[$name])) { 274 + throw new PhutilArgumentUsageException( 275 + pht('No such commit "%s"!', $name)); 276 + } 277 + } 278 + 279 + return $commits; 280 + } 281 + 282 + }
+10
src/applications/audit/management/PhabricatorAuditManagementWorkflow.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorAuditManagementWorkflow 4 + extends PhutilArgumentWorkflow { 5 + 6 + public function isExecutable() { 7 + return true; 8 + } 9 + 10 + }
+21 -1
src/applications/audit/query/PhabricatorAuditQuery.php
··· 2 2 3 3 final class PhabricatorAuditQuery { 4 4 5 + private $ids; 5 6 private $offset; 6 7 private $limit; 7 8 ··· 21 22 const STATUS_CONCERN = 'status-concern'; 22 23 23 24 private $commits; 25 + 26 + public function withIDs(array $ids) { 27 + $this->ids = $ids; 28 + return $this; 29 + } 24 30 25 31 public function withCommitPHIDs(array $commit_phids) { 26 32 $this->commitPHIDs = $commit_phids; ··· 156 162 private function buildWhereClause($conn_r) { 157 163 $where = array(); 158 164 165 + if ($this->ids) { 166 + $where[] = qsprintf( 167 + $conn_r, 168 + 'req.id IN (%Ld)', 169 + $this->ids); 170 + } 171 + 159 172 if ($this->commitPHIDs) { 160 173 $where[] = qsprintf( 161 174 $conn_r, ··· 212 225 case self::STATUS_ANY: 213 226 break; 214 227 default: 215 - throw new Exception("Unknown status '{$status}'!"); 228 + $valid = array( 229 + self::STATUS_ANY, 230 + self::STATUS_OPEN, 231 + self::STATUS_CONCERN, 232 + ); 233 + throw new Exception( 234 + "Unknown audit status '{$status}'! Valid statuses are: ". 235 + implode(', ', $valid)); 216 236 } 217 237 218 238 if ($where) {