From 1cca22f3fda77ae8bb52907e468c55f9577f7152 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Mar 2012 11:18:22 -0700 Subject: [PATCH] Provide a script to "undo" the negative effects of an accidental push in Differential Summary: If someone accidentally pushes a bunch of commits, revisions might get marked as "Committed" incorrectly. This will restore them to their previous state without too much fuss. Test Plan: Ran the script on some commits to undo them, it seemed to work correctly. Reviewers: davidreuss, btrahan Reviewed By: btrahan CC: aran, epriestley Differential Revision: https://secure.phabricator.com/D1877 --- scripts/repository/undo_commits.php | 194 ++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100755 scripts/repository/undo_commits.php diff --git a/scripts/repository/undo_commits.php b/scripts/repository/undo_commits.php new file mode 100755 index 0000000000..e3b4a0694e --- /dev/null +++ b/scripts/repository/undo_commits.php @@ -0,0 +1,194 @@ +#!/usr/bin/env php +setTagline('reopen reviews accidentally closed by a bad push'); +$args->setSynopsis(<<parseStandardArguments(); +$args->parse( + array( + array( + 'name' => 'repository', + 'param' => 'callsign', + 'help' => 'Callsign for the repository these commits appear in.', + ), + )); + +$callsign = $args->getArg('repository'); +if (!$callsign) { + $args->printHelpAndExit(); +} + +$repository = id(new PhabricatorRepository())->loadOneWhere( + 'callsign = %s', + $callsign); + +if (!$repository) { + throw new Exception("No repository with callsign '{$callsign}'!"); +} + +echo "Reading commit identifiers from stdin...\n"; + +$identifiers = @file_get_contents('php://stdin'); +$identifiers = trim($identifiers); +$identifiers = explode("\n", $identifiers); + +echo "Read ".count($identifiers)." commit identifiers.\n"; + +if (!$identifiers) { + throw new Exception("You must provide commmit identifiers on stdin!"); +} + +echo "Looking up commits...\n"; +$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( + 'repositoryID = %d AND commitIdentifier IN (%Ls)', + $repository->getID(), + $identifiers); + +echo "Found ".count($commits)." matching commits.\n"; + +if (!$commits) { + throw new Exception("None of the commits could be found!"); +} + +$commit_phids = mpull($commits, 'getPHID', 'getPHID'); + +echo "Looking up revisions marked 'committed' by these commits...\n"; +$revision_ids = queryfx_all( + id(new DifferentialRevision())->establishConnection('r'), + 'SELECT DISTINCT revisionID from %T WHERE commitPHID IN (%Ls)', + DifferentialRevision::TABLE_COMMIT, + $commit_phids); +$revision_ids = ipull($revision_ids, 'revisionID'); + +echo "Found ".count($revision_ids)." associated revisions.\n"; +if (!$revision_ids) { + echo "Done -- nothing to do.\n"; + return; +} + +$status_committed = ArcanistDifferentialRevisionStatus::COMMITTED; + +$revisions = array(); +$map = array(); + +if ($revision_ids) { + foreach ($revision_ids as $revision_id) { + echo "Assessing revision D{$revision_id}...\n"; + $revision = id(new DifferentialRevision())->load($revision_id); + + if ($revision->getStatus() != $status_committed) { + echo "Revision is not 'committed', skipping.\n"; + } + + $assoc_commits = queryfx_all( + $revision->establishConnection('r'), + 'SELECT commitPHID FROM %T WHERE revisionID = %d', + DifferentialRevision::TABLE_COMMIT, + $revision_id); + $assoc_commits = ipull($assoc_commits, 'commitPHID', 'commitPHID'); + + if (array_diff_key($assoc_commits, $commit_phids)) { + echo "Revision is associated with other commits, skipping.\n"; + } + + $comments = id(new DifferentialComment())->loadAllWhere( + 'revisionID = %d', + $revision_id); + + $new_status = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; + foreach ($comments as $comment) { + switch ($comment->getAction()) { + case DifferentialAction::ACTION_ACCEPT: + $new_status = ArcanistDifferentialRevisionStatus::ACCEPTED; + break; + case DifferentialAction::ACTION_REJECT: + case DifferentialAction::ACTION_RETHINK: + $new_status = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; + break; + case DifferentialAction::ACTION_ABANDON: + $new_status = ArcanistDifferentialRevisionStatus::ABANDONED; + break; + case DifferentialAction::ACTION_RECLAIM: + case DifferentialAction::ACTION_UPDATE: + $new_status = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; + break; + } + } + + $revisions[$revision_id] = $revision; + $map[$revision_id] = $new_status; + } +} + +if (!$revisions) { + echo "Done -- nothing to do.\n"; +} + +echo "Found ".count($revisions)." revisions to update:\n\n"; +foreach ($revisions as $id => $revision) { + + $old_status = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( + $revision->getStatus()); + $new_status = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus( + $map[$id]); + + echo " - D{$id}: ".$revision->getTitle()."\n"; + echo " Will update: {$old_status} -> {$new_status}\n\n"; +} + +$ok = phutil_console_confirm('Apply these changes?'); +if (!$ok) { + echo "Aborted.\n"; + exit(1); +} + +echo "Saving changes...\n"; +foreach ($revisions as $id => $revision) { + queryfx( + $revision->establishConnection('r'), + 'UPDATE %T SET status = %d WHERE id = %d', + $revision->getTableName(), + $map[$id], + $id); +} +echo "Done.\n"; + +