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

Add a rough "bin/repository unpublish" workflow to attempt to cleanup improperly published repositories

Summary:
Ref T13114. See PHI514. This makes some attempt to undo the damage caused by incorrectly publishing a repository.

Don't run this.

Test Plan: Yikes.

Maniphest Tasks: T13114

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

+308 -2
+6 -1
src/__phutil_library_map__.php
··· 4020 4020 'PhabricatorRepositoryManagementRefsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php', 4021 4021 'PhabricatorRepositoryManagementReparseWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php', 4022 4022 'PhabricatorRepositoryManagementThawWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php', 4023 + 'PhabricatorRepositoryManagementUnpublishWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php', 4023 4024 'PhabricatorRepositoryManagementUpdateWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php', 4024 4025 'PhabricatorRepositoryManagementWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementWorkflow.php', 4025 4026 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php', ··· 8563 8564 'PhabricatorPolicyInterface', 8564 8565 'PhabricatorMarkupInterface', 8565 8566 ), 8566 - 'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO', 8567 + 'PhabricatorFeedStoryData' => array( 8568 + 'PhabricatorFeedDAO', 8569 + 'PhabricatorDestructibleInterface', 8570 + ), 8567 8571 'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO', 8568 8572 'PhabricatorFeedStoryPublisher' => 'Phobject', 8569 8573 'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO', ··· 9814 9818 'PhabricatorRepositoryManagementRefsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 9815 9819 'PhabricatorRepositoryManagementReparseWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 9816 9820 'PhabricatorRepositoryManagementThawWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 9821 + 'PhabricatorRepositoryManagementUnpublishWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 9817 9822 'PhabricatorRepositoryManagementUpdateWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 9818 9823 'PhabricatorRepositoryManagementWorkflow' => 'PhabricatorManagementWorkflow', 9819 9824 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
+29 -1
src/applications/feed/storage/PhabricatorFeedStoryData.php
··· 1 1 <?php 2 2 3 - final class PhabricatorFeedStoryData extends PhabricatorFeedDAO { 3 + final class PhabricatorFeedStoryData 4 + extends PhabricatorFeedDAO 5 + implements PhabricatorDestructibleInterface { 4 6 5 7 protected $phid; 6 8 ··· 64 66 65 67 public function getValue($key, $default = null) { 66 68 return idx($this->storyData, $key, $default); 69 + } 70 + 71 + 72 + /* -( PhabricatorDestructibleInterface )----------------------------------- */ 73 + 74 + 75 + public function destroyObjectPermanently( 76 + PhabricatorDestructionEngine $engine) { 77 + 78 + $this->openTransaction(); 79 + $conn = $this->establishConnection('w'); 80 + 81 + queryfx( 82 + $conn, 83 + 'DELETE FROM %T WHERE chronologicalKey = %s', 84 + id(new PhabricatorFeedStoryNotification())->getTableName(), 85 + $this->getChronologicalKey()); 86 + 87 + queryfx( 88 + $conn, 89 + 'DELETE FROM %T WHERE chronologicalKey = %s', 90 + id(new PhabricatorFeedStoryReference())->getTableName(), 91 + $this->getChronologicalKey()); 92 + 93 + $this->delete(); 94 + $this->saveTransaction(); 67 95 } 68 96 69 97 }
+273
src/applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php
··· 1 + <?php 2 + 3 + final class PhabricatorRepositoryManagementUnpublishWorkflow 4 + extends PhabricatorRepositoryManagementWorkflow { 5 + 6 + protected function didConstruct() { 7 + $this 8 + ->setName('unpublish') 9 + ->setExamples( 10 + '**unpublish** [__options__] __repository__') 11 + ->setSynopsis( 12 + pht( 13 + 'Unpublish all feed stories and notifications that a repository '. 14 + 'has generated. Keep expectations low; can not rewind time.')) 15 + ->setArguments( 16 + array( 17 + array( 18 + 'name' => 'force', 19 + 'help' => pht('Do not prompt for confirmation.'), 20 + ), 21 + array( 22 + 'name' => 'dry-run', 23 + 'help' => pht('Do not perform any writes.'), 24 + ), 25 + array( 26 + 'name' => 'repositories', 27 + 'wildcard' => true, 28 + ), 29 + )); 30 + } 31 + 32 + public function execute(PhutilArgumentParser $args) { 33 + $viewer = $this->getViewer(); 34 + $is_force = $args->getArg('force'); 35 + $is_dry_run = $args->getArg('dry-run'); 36 + 37 + $repositories = $this->loadLocalRepositories($args, 'repositories'); 38 + if (count($repositories) !== 1) { 39 + throw new PhutilArgumentUsageException( 40 + pht('Specify exactly one repository to unpublish.')); 41 + } 42 + $repository = head($repositories); 43 + 44 + if (!$is_force) { 45 + echo tsprintf( 46 + "%s\n", 47 + pht( 48 + 'This script will unpublish all feed stories and notifications '. 49 + 'which a repository generated during import. This action can not '. 50 + 'be undone.')); 51 + 52 + $prompt = pht( 53 + 'Permanently unpublish "%s"?', 54 + $repository->getDisplayName()); 55 + if (!phutil_console_confirm($prompt)) { 56 + throw new PhutilArgumentUsageException( 57 + pht('User aborted workflow.')); 58 + } 59 + } 60 + 61 + $commits = id(new DiffusionCommitQuery()) 62 + ->setViewer($viewer) 63 + ->withRepositoryPHIDs(array($repository->getPHID())) 64 + ->execute(); 65 + 66 + echo pht("Will unpublish %s commits.\n", count($commits)); 67 + 68 + foreach ($commits as $commit) { 69 + $this->unpublishCommit($commit, $is_dry_run); 70 + } 71 + 72 + return 0; 73 + } 74 + 75 + private function unpublishCommit( 76 + PhabricatorRepositoryCommit $commit, 77 + $is_dry_run) { 78 + $viewer = $this->getViewer(); 79 + 80 + echo tsprintf( 81 + "%s\n", 82 + pht( 83 + 'Unpublishing commit "%s".', 84 + $commit->getMonogram())); 85 + 86 + $stories = id(new PhabricatorFeedQuery()) 87 + ->setViewer($viewer) 88 + ->withFilterPHIDs(array($commit->getPHID())) 89 + ->execute(); 90 + 91 + if ($stories) { 92 + echo tsprintf( 93 + "%s\n", 94 + pht( 95 + 'Found %s feed storie(s).', 96 + count($stories))); 97 + 98 + if (!$is_dry_run) { 99 + $engine = new PhabricatorDestructionEngine(); 100 + foreach ($stories as $story) { 101 + $story_data = $story->getStoryData(); 102 + $engine->destroyObject($story_data); 103 + } 104 + 105 + echo tsprintf( 106 + "%s\n", 107 + pht( 108 + 'Destroyed %s feed storie(s).', 109 + count($stories))); 110 + } 111 + } 112 + 113 + $edge_types = array( 114 + PhabricatorObjectMentionsObjectEdgeType::EDGECONST => true, 115 + DiffusionCommitHasTaskEdgeType::EDGECONST => true, 116 + DiffusionCommitHasRevisionEdgeType::EDGECONST => true, 117 + DiffusionCommitRevertsCommitEdgeType::EDGECONST => true, 118 + ); 119 + 120 + $query = id(new PhabricatorEdgeQuery()) 121 + ->withSourcePHIDs(array($commit->getPHID())) 122 + ->withEdgeTypes(array_keys($edge_types)); 123 + $edges = $query->execute(); 124 + 125 + foreach ($edges[$commit->getPHID()] as $type => $edge_list) { 126 + foreach ($edge_list as $edge) { 127 + $dst = $edge['dst']; 128 + 129 + echo tsprintf( 130 + "%s\n", 131 + pht( 132 + 'Commit "%s" has edge of type "%s" to object "%s".', 133 + $commit->getMonogram(), 134 + $type, 135 + $dst)); 136 + 137 + $object = id(new PhabricatorObjectQuery()) 138 + ->setViewer($viewer) 139 + ->withPHIDs(array($dst)) 140 + ->executeOne(); 141 + if ($object) { 142 + if ($object instanceof PhabricatorApplicationTransactionInterface) { 143 + $this->unpublishEdgeTransaction( 144 + $commit, 145 + $type, 146 + $object, 147 + $is_dry_run); 148 + } 149 + } 150 + } 151 + } 152 + } 153 + 154 + private function unpublishEdgeTransaction( 155 + $src, 156 + $type, 157 + PhabricatorApplicationTransactionInterface $dst, 158 + $is_dry_run) { 159 + $viewer = $this->getViewer(); 160 + 161 + $query = PhabricatorApplicationTransactionQuery::newQueryForObject($dst) 162 + ->setViewer($viewer) 163 + ->withObjectPHIDs(array($dst->getPHID())); 164 + 165 + $xactions = id(clone $query) 166 + ->withTransactionTypes( 167 + array( 168 + PhabricatorTransactions::TYPE_EDGE, 169 + )) 170 + ->execute(); 171 + 172 + $type_obj = PhabricatorEdgeType::getByConstant($type); 173 + $inverse_type = $type_obj->getInverseEdgeConstant(); 174 + 175 + $engine = new PhabricatorDestructionEngine(); 176 + foreach ($xactions as $xaction) { 177 + $edge_type = $xaction->getMetadataValue('edge:type'); 178 + if ($edge_type != $inverse_type) { 179 + // Some other type of edge was edited. 180 + continue; 181 + } 182 + 183 + $record = PhabricatorEdgeChangeRecord::newFromTransaction($xaction); 184 + $changed = $record->getChangedPHIDs(); 185 + if ($changed !== array($src->getPHID())) { 186 + // Affected objects were not just the object we're unpublishing. 187 + continue; 188 + } 189 + 190 + echo tsprintf( 191 + "%s\n", 192 + pht( 193 + 'Found edge transaction "%s" on object "%s" for type "%s".', 194 + $xaction->getPHID(), 195 + $dst->getPHID(), 196 + $type)); 197 + 198 + if (!$is_dry_run) { 199 + $engine->destroyObject($xaction); 200 + 201 + echo tsprintf( 202 + "%s\n", 203 + pht( 204 + 'Destroyed transaction "%s" on object "%s".', 205 + $xaction->getPHID(), 206 + $dst->getPHID())); 207 + } 208 + } 209 + 210 + if ($type === DiffusionCommitHasTaskEdgeType::EDGECONST) { 211 + $xactions = id(clone $query) 212 + ->withTransactionTypes( 213 + array( 214 + ManiphestTaskStatusTransaction::TRANSACTIONTYPE, 215 + )) 216 + ->execute(); 217 + 218 + if ($xactions) { 219 + foreach ($xactions as $xaction) { 220 + $metadata = $xaction->getMetadata(); 221 + if (idx($metadata, 'commitPHID') === $src->getPHID()) { 222 + echo tsprintf( 223 + "%s\n", 224 + pht( 225 + 'MANUAL Task "%s" was likely closed improperly by "%s".', 226 + $dst->getMonogram(), 227 + $src->getMonogram())); 228 + } 229 + } 230 + } 231 + } 232 + 233 + if ($type === DiffusionCommitHasRevisionEdgeType::EDGECONST) { 234 + $xactions = id(clone $query) 235 + ->withTransactionTypes( 236 + array( 237 + DifferentialRevisionCloseTransaction::TRANSACTIONTYPE, 238 + )) 239 + ->execute(); 240 + 241 + if ($xactions) { 242 + foreach ($xactions as $xaction) { 243 + $metadata = $xaction->getMetadata(); 244 + if (idx($metadata, 'isCommitClose')) { 245 + if (idx($metadata, 'commitPHID') === $src->getPHID()) { 246 + echo tsprintf( 247 + "%s\n", 248 + pht( 249 + 'MANUAL Revision "%s" was likely closed improperly by "%s".', 250 + $dst->getMonogram(), 251 + $src->getMonogram())); 252 + } 253 + } 254 + } 255 + } 256 + } 257 + 258 + if (!$is_dry_run) { 259 + id(new PhabricatorEdgeEditor()) 260 + ->removeEdge($src->getPHID(), $type, $dst->getPHID()) 261 + ->save(); 262 + echo tsprintf( 263 + "%s\n", 264 + pht( 265 + 'Destroyed edge of type "%s" between "%s" and "%s".', 266 + $type, 267 + $src->getPHID(), 268 + $dst->getPHID())); 269 + } 270 + } 271 + 272 + 273 + }