1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

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
This commit is contained in:
epriestley 2018-03-30 07:01:36 -07:00
parent 9fbf4ee58c
commit 66392e5b8b
3 changed files with 308 additions and 2 deletions

View file

@ -4020,6 +4020,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryManagementRefsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php',
'PhabricatorRepositoryManagementReparseWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php',
'PhabricatorRepositoryManagementThawWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php',
'PhabricatorRepositoryManagementUnpublishWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php',
'PhabricatorRepositoryManagementUpdateWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php',
'PhabricatorRepositoryManagementWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementWorkflow.php',
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php',
@ -8563,7 +8564,10 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
'PhabricatorMarkupInterface',
),
'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO',
'PhabricatorFeedStoryData' => array(
'PhabricatorFeedDAO',
'PhabricatorDestructibleInterface',
),
'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO',
'PhabricatorFeedStoryPublisher' => 'Phobject',
'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
@ -9814,6 +9818,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryManagementRefsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementReparseWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementThawWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementUnpublishWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementUpdateWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',

View file

@ -1,6 +1,8 @@
<?php
final class PhabricatorFeedStoryData extends PhabricatorFeedDAO {
final class PhabricatorFeedStoryData
extends PhabricatorFeedDAO
implements PhabricatorDestructibleInterface {
protected $phid;
@ -66,4 +68,30 @@ final class PhabricatorFeedStoryData extends PhabricatorFeedDAO {
return idx($this->storyData, $key, $default);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$conn = $this->establishConnection('w');
queryfx(
$conn,
'DELETE FROM %T WHERE chronologicalKey = %s',
id(new PhabricatorFeedStoryNotification())->getTableName(),
$this->getChronologicalKey());
queryfx(
$conn,
'DELETE FROM %T WHERE chronologicalKey = %s',
id(new PhabricatorFeedStoryReference())->getTableName(),
$this->getChronologicalKey());
$this->delete();
$this->saveTransaction();
}
}

View file

@ -0,0 +1,273 @@
<?php
final class PhabricatorRepositoryManagementUnpublishWorkflow
extends PhabricatorRepositoryManagementWorkflow {
protected function didConstruct() {
$this
->setName('unpublish')
->setExamples(
'**unpublish** [__options__] __repository__')
->setSynopsis(
pht(
'Unpublish all feed stories and notifications that a repository '.
'has generated. Keep expectations low; can not rewind time.'))
->setArguments(
array(
array(
'name' => 'force',
'help' => pht('Do not prompt for confirmation.'),
),
array(
'name' => 'dry-run',
'help' => pht('Do not perform any writes.'),
),
array(
'name' => 'repositories',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$is_force = $args->getArg('force');
$is_dry_run = $args->getArg('dry-run');
$repositories = $this->loadLocalRepositories($args, 'repositories');
if (count($repositories) !== 1) {
throw new PhutilArgumentUsageException(
pht('Specify exactly one repository to unpublish.'));
}
$repository = head($repositories);
if (!$is_force) {
echo tsprintf(
"%s\n",
pht(
'This script will unpublish all feed stories and notifications '.
'which a repository generated during import. This action can not '.
'be undone.'));
$prompt = pht(
'Permanently unpublish "%s"?',
$repository->getDisplayName());
if (!phutil_console_confirm($prompt)) {
throw new PhutilArgumentUsageException(
pht('User aborted workflow.'));
}
}
$commits = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
->execute();
echo pht("Will unpublish %s commits.\n", count($commits));
foreach ($commits as $commit) {
$this->unpublishCommit($commit, $is_dry_run);
}
return 0;
}
private function unpublishCommit(
PhabricatorRepositoryCommit $commit,
$is_dry_run) {
$viewer = $this->getViewer();
echo tsprintf(
"%s\n",
pht(
'Unpublishing commit "%s".',
$commit->getMonogram()));
$stories = id(new PhabricatorFeedQuery())
->setViewer($viewer)
->withFilterPHIDs(array($commit->getPHID()))
->execute();
if ($stories) {
echo tsprintf(
"%s\n",
pht(
'Found %s feed storie(s).',
count($stories)));
if (!$is_dry_run) {
$engine = new PhabricatorDestructionEngine();
foreach ($stories as $story) {
$story_data = $story->getStoryData();
$engine->destroyObject($story_data);
}
echo tsprintf(
"%s\n",
pht(
'Destroyed %s feed storie(s).',
count($stories)));
}
}
$edge_types = array(
PhabricatorObjectMentionsObjectEdgeType::EDGECONST => true,
DiffusionCommitHasTaskEdgeType::EDGECONST => true,
DiffusionCommitHasRevisionEdgeType::EDGECONST => true,
DiffusionCommitRevertsCommitEdgeType::EDGECONST => true,
);
$query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($commit->getPHID()))
->withEdgeTypes(array_keys($edge_types));
$edges = $query->execute();
foreach ($edges[$commit->getPHID()] as $type => $edge_list) {
foreach ($edge_list as $edge) {
$dst = $edge['dst'];
echo tsprintf(
"%s\n",
pht(
'Commit "%s" has edge of type "%s" to object "%s".',
$commit->getMonogram(),
$type,
$dst));
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($dst))
->executeOne();
if ($object) {
if ($object instanceof PhabricatorApplicationTransactionInterface) {
$this->unpublishEdgeTransaction(
$commit,
$type,
$object,
$is_dry_run);
}
}
}
}
}
private function unpublishEdgeTransaction(
$src,
$type,
PhabricatorApplicationTransactionInterface $dst,
$is_dry_run) {
$viewer = $this->getViewer();
$query = PhabricatorApplicationTransactionQuery::newQueryForObject($dst)
->setViewer($viewer)
->withObjectPHIDs(array($dst->getPHID()));
$xactions = id(clone $query)
->withTransactionTypes(
array(
PhabricatorTransactions::TYPE_EDGE,
))
->execute();
$type_obj = PhabricatorEdgeType::getByConstant($type);
$inverse_type = $type_obj->getInverseEdgeConstant();
$engine = new PhabricatorDestructionEngine();
foreach ($xactions as $xaction) {
$edge_type = $xaction->getMetadataValue('edge:type');
if ($edge_type != $inverse_type) {
// Some other type of edge was edited.
continue;
}
$record = PhabricatorEdgeChangeRecord::newFromTransaction($xaction);
$changed = $record->getChangedPHIDs();
if ($changed !== array($src->getPHID())) {
// Affected objects were not just the object we're unpublishing.
continue;
}
echo tsprintf(
"%s\n",
pht(
'Found edge transaction "%s" on object "%s" for type "%s".',
$xaction->getPHID(),
$dst->getPHID(),
$type));
if (!$is_dry_run) {
$engine->destroyObject($xaction);
echo tsprintf(
"%s\n",
pht(
'Destroyed transaction "%s" on object "%s".',
$xaction->getPHID(),
$dst->getPHID()));
}
}
if ($type === DiffusionCommitHasTaskEdgeType::EDGECONST) {
$xactions = id(clone $query)
->withTransactionTypes(
array(
ManiphestTaskStatusTransaction::TRANSACTIONTYPE,
))
->execute();
if ($xactions) {
foreach ($xactions as $xaction) {
$metadata = $xaction->getMetadata();
if (idx($metadata, 'commitPHID') === $src->getPHID()) {
echo tsprintf(
"%s\n",
pht(
'MANUAL Task "%s" was likely closed improperly by "%s".',
$dst->getMonogram(),
$src->getMonogram()));
}
}
}
}
if ($type === DiffusionCommitHasRevisionEdgeType::EDGECONST) {
$xactions = id(clone $query)
->withTransactionTypes(
array(
DifferentialRevisionCloseTransaction::TRANSACTIONTYPE,
))
->execute();
if ($xactions) {
foreach ($xactions as $xaction) {
$metadata = $xaction->getMetadata();
if (idx($metadata, 'isCommitClose')) {
if (idx($metadata, 'commitPHID') === $src->getPHID()) {
echo tsprintf(
"%s\n",
pht(
'MANUAL Revision "%s" was likely closed improperly by "%s".',
$dst->getMonogram(),
$src->getMonogram()));
}
}
}
}
}
if (!$is_dry_run) {
id(new PhabricatorEdgeEditor())
->removeEdge($src->getPHID(), $type, $dst->getPHID())
->save();
echo tsprintf(
"%s\n",
pht(
'Destroyed edge of type "%s" between "%s" and "%s".',
$type,
$src->getPHID(),
$dst->getPHID()));
}
}
}