Skip to content

Commit 1cca22f

Browse files
author
epriestley
committed
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
1 parent 11cccb9 commit 1cca22f

File tree

1 file changed

+194
-0
lines changed

1 file changed

+194
-0
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
/*
5+
* Copyright 2012 Facebook, Inc.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
$root = dirname(dirname(dirname(__FILE__)));
21+
require_once $root.'/scripts/__init_script__.php';
22+
23+
$args = new PhutilArgumentParser($argv);
24+
$args->setTagline('reopen reviews accidentally closed by a bad push');
25+
$args->setSynopsis(<<<EOSYNOPSIS
26+
**undo_commits.php** --repository __callsign__ < __commits__
27+
28+
Reopen code reviews accidentally closed by a bad push. If someone
29+
pushes a bunch of commits to a tracked branch that they shouldn't
30+
have, you can pipe in all the commit hashes to this script to
31+
"undo" the damage in Differential after you revert the commits.
32+
33+
To use this script:
34+
35+
1. Identify the commits you want to undo the effects of.
36+
2. Put all their identifiers (commit hashes in git/hg, revision
37+
numbers in svn) into a file, one per line.
38+
3. Pipe that file into this script with relevant arguments.
39+
4. Revisions marked "committed" by those commits will be
40+
restored to their previous state.
41+
42+
EOSYNOPSIS
43+
);
44+
$args->parseStandardArguments();
45+
$args->parse(
46+
array(
47+
array(
48+
'name' => 'repository',
49+
'param' => 'callsign',
50+
'help' => 'Callsign for the repository these commits appear in.',
51+
),
52+
));
53+
54+
$callsign = $args->getArg('repository');
55+
if (!$callsign) {
56+
$args->printHelpAndExit();
57+
}
58+
59+
$repository = id(new PhabricatorRepository())->loadOneWhere(
60+
'callsign = %s',
61+
$callsign);
62+
63+
if (!$repository) {
64+
throw new Exception("No repository with callsign '{$callsign}'!");
65+
}
66+
67+
echo "Reading commit identifiers from stdin...\n";
68+
69+
$identifiers = @file_get_contents('php://stdin');
70+
$identifiers = trim($identifiers);
71+
$identifiers = explode("\n", $identifiers);
72+
73+
echo "Read ".count($identifiers)." commit identifiers.\n";
74+
75+
if (!$identifiers) {
76+
throw new Exception("You must provide commmit identifiers on stdin!");
77+
}
78+
79+
echo "Looking up commits...\n";
80+
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
81+
'repositoryID = %d AND commitIdentifier IN (%Ls)',
82+
$repository->getID(),
83+
$identifiers);
84+
85+
echo "Found ".count($commits)." matching commits.\n";
86+
87+
if (!$commits) {
88+
throw new Exception("None of the commits could be found!");
89+
}
90+
91+
$commit_phids = mpull($commits, 'getPHID', 'getPHID');
92+
93+
echo "Looking up revisions marked 'committed' by these commits...\n";
94+
$revision_ids = queryfx_all(
95+
id(new DifferentialRevision())->establishConnection('r'),
96+
'SELECT DISTINCT revisionID from %T WHERE commitPHID IN (%Ls)',
97+
DifferentialRevision::TABLE_COMMIT,
98+
$commit_phids);
99+
$revision_ids = ipull($revision_ids, 'revisionID');
100+
101+
echo "Found ".count($revision_ids)." associated revisions.\n";
102+
if (!$revision_ids) {
103+
echo "Done -- nothing to do.\n";
104+
return;
105+
}
106+
107+
$status_committed = ArcanistDifferentialRevisionStatus::COMMITTED;
108+
109+
$revisions = array();
110+
$map = array();
111+
112+
if ($revision_ids) {
113+
foreach ($revision_ids as $revision_id) {
114+
echo "Assessing revision D{$revision_id}...\n";
115+
$revision = id(new DifferentialRevision())->load($revision_id);
116+
117+
if ($revision->getStatus() != $status_committed) {
118+
echo "Revision is not 'committed', skipping.\n";
119+
}
120+
121+
$assoc_commits = queryfx_all(
122+
$revision->establishConnection('r'),
123+
'SELECT commitPHID FROM %T WHERE revisionID = %d',
124+
DifferentialRevision::TABLE_COMMIT,
125+
$revision_id);
126+
$assoc_commits = ipull($assoc_commits, 'commitPHID', 'commitPHID');
127+
128+
if (array_diff_key($assoc_commits, $commit_phids)) {
129+
echo "Revision is associated with other commits, skipping.\n";
130+
}
131+
132+
$comments = id(new DifferentialComment())->loadAllWhere(
133+
'revisionID = %d',
134+
$revision_id);
135+
136+
$new_status = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
137+
foreach ($comments as $comment) {
138+
switch ($comment->getAction()) {
139+
case DifferentialAction::ACTION_ACCEPT:
140+
$new_status = ArcanistDifferentialRevisionStatus::ACCEPTED;
141+
break;
142+
case DifferentialAction::ACTION_REJECT:
143+
case DifferentialAction::ACTION_RETHINK:
144+
$new_status = ArcanistDifferentialRevisionStatus::NEEDS_REVISION;
145+
break;
146+
case DifferentialAction::ACTION_ABANDON:
147+
$new_status = ArcanistDifferentialRevisionStatus::ABANDONED;
148+
break;
149+
case DifferentialAction::ACTION_RECLAIM:
150+
case DifferentialAction::ACTION_UPDATE:
151+
$new_status = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
152+
break;
153+
}
154+
}
155+
156+
$revisions[$revision_id] = $revision;
157+
$map[$revision_id] = $new_status;
158+
}
159+
}
160+
161+
if (!$revisions) {
162+
echo "Done -- nothing to do.\n";
163+
}
164+
165+
echo "Found ".count($revisions)." revisions to update:\n\n";
166+
foreach ($revisions as $id => $revision) {
167+
168+
$old_status = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus(
169+
$revision->getStatus());
170+
$new_status = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus(
171+
$map[$id]);
172+
173+
echo " - D{$id}: ".$revision->getTitle()."\n";
174+
echo " Will update: {$old_status} -> {$new_status}\n\n";
175+
}
176+
177+
$ok = phutil_console_confirm('Apply these changes?');
178+
if (!$ok) {
179+
echo "Aborted.\n";
180+
exit(1);
181+
}
182+
183+
echo "Saving changes...\n";
184+
foreach ($revisions as $id => $revision) {
185+
queryfx(
186+
$revision->establishConnection('r'),
187+
'UPDATE %T SET status = %d WHERE id = %d',
188+
$revision->getTableName(),
189+
$map[$id],
190+
$id);
191+
}
192+
echo "Done.\n";
193+
194+

0 commit comments

Comments
 (0)