Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F1707818
undo_transactions.php
No One
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Authored By
epriestley
Jul 1 2016, 12:47 PM
2016-07-01 12:47:42 (UTC+0)
Size
10 KB
Referenced Files
None
Subscribers
None
undo_transactions.php
View Options
#!/usr/bin/env php
<?php
require_once
'scripts/__init_script__.php'
;
// Configure a list of problem repository IDs. "Fixes Txxx" will only be undone
// if they came from these repositories.
$repository_ids
=
array
(
1
,
2
,
3
);
// Configure the start and end times for the incident. Transactions which
// applied between these times will be undone, but older or newer transactions
// will not.
$epoch_start
=
PhabricatorTime
::
getNow
()
-
phutil_units
(
'1 day in seconds'
);
$epoch_end
=
PhabricatorTime
::
getNow
();
// Probably no need to change this; this is a heuristic for identifying that
// transactions happened in the same group by observing that they happend very
// close together (transaction groups aren't stored formally).
$a_few_seconds
=
15
;
$args
=
new
PhutilArgumentParser
(
$argv
);
$args
->
setTagline
(
pht
(
'undo transactions created by a repository import'
));
$args
->
setSynopsis
(
<<<EOHELP
**undo_transactions.php** T123
**undo_transactions.php** --all
Undo "Fixes" transactions from configured repositories.
By default, shows you what would be done. With --write, actually does it.
EOHELP
);
$args
->
parseStandardArguments
();
$args
->
parse
(
array
(
array
(
'name'
=>
'write'
,
'help'
=>
pht
(
'Actually repair damage instead of just showing it.'
),
),
array
(
'name'
=>
'all'
,
'help'
=>
pht
(
'Process ALL tasks.'
),
),
array
(
'name'
=>
'tasks'
,
'wildcard'
=>
true
,
),
));
$viewer
=
PhabricatorUser
::
getOmnipotentUser
();
$repositories
=
id
(
new
PhabricatorRepositoryQuery
())
->
setViewer
(
$viewer
)
->
withIDs
(
$repository_ids
)
->
execute
();
$repositories
=
mpull
(
$repositories
,
null
,
'getID'
);
foreach
(
$repository_ids
as
$repository_id
)
{
if
(
empty
(
$repositories
[
$repository_id
]))
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Configured repository ID "%s" does not correspond to a loadable '
.
'repository.'
,
$repository_id
));
}
}
$all
=
$args
->
getArg
(
'all'
);
$monograms
=
$args
->
getArg
(
'tasks'
);
if
(
$all
&&
$monograms
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Specify either "--all" or a list of tasks, but not both.'
));
}
else
if
(!
$all
&&
!
$monograms
)
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Specify "--all" or a list of tasks to act on.'
));
}
else
if
(
$all
)
{
$tasks
=
new
LiskMigrationIterator
(
new
ManiphestTask
());
}
else
{
$ids
=
array
();
foreach
(
$monograms
as
$monogram
)
{
if
(!
preg_match
(
'/^T
\d
+
\z
/'
,
$monogram
))
{
throw
new
PhutilArgumentUsageException
(
pht
(
'When providing a list of tasks, they should be in the form '
.
'"T123". Provided task "%s" is not.'
,
$monogram
));
}
$ids
[
$monogram
]
=
trim
(
$monogram
,
'T'
);
}
$tasks
=
id
(
new
ManiphestTaskQuery
())
->
setViewer
(
$viewer
)
->
withIDs
(
$ids
)
->
execute
();
$tasks
=
mpull
(
$tasks
,
null
,
'getID'
);
foreach
(
$ids
as
$monogram
=>
$id
)
{
if
(
empty
(
$tasks
[
$id
]))
{
throw
new
PhutilArgumentUsageException
(
pht
(
'Task "%s" is not a valid, loadable task.'
,
$monogram
));
}
}
}
$is_write
=
$args
->
getArg
(
'write'
);
echo
tsprintf
(
"**<bg:green> %s </bg>** %s
\n
"
,
pht
(
'TARGETS'
),
pht
(
'Transactions originating from these repositories will be undone: %s.'
,
implode
(
', '
,
mpull
(
$repositories
,
'getDisplayName'
))));
echo
tsprintf
(
"**<bg:green> %s </bg>** %s
\n
"
,
pht
(
'RANGE'
),
pht
(
'Transactions between %s and %s will be undone.'
,
phabricator_datetime
(
$epoch_start
,
$viewer
),
phabricator_datetime
(
$epoch_end
,
$viewer
)));
foreach
(
$tasks
as
$task
)
{
echo
tsprintf
(
"**<bg:blue> %s </bg>** %s
\n
"
,
pht
(
'TASK'
),
pht
(
'Examining task: %s %s.'
,
$task
->
getMonogram
(),
$task
->
getTitle
()));
$status
=
$task
->
getStatus
();
if
(
ManiphestTaskStatus
::
isOpenStatus
(
$status
))
{
echo
tsprintf
(
"**<bg:blue> %s </bg>** %s
\n
"
,
pht
(
'STATUS'
),
pht
(
'Task has an open status ("%s") and will not be mutated.'
,
$status
));
continue
;
}
$xactions
=
id
(
new
ManiphestTransactionQuery
())
->
setViewer
(
$viewer
)
->
withObjectPHIDs
(
array
(
$task
->
getPHID
()))
->
execute
();
$status_xaction
=
null
;
$owner_xactions
=
array
();
$edge_xactions
=
array
();
$already_fixed
=
null
;
foreach
(
$xactions
as
$xaction
)
{
$created
=
$xaction
->
getDateCreated
();
$type
=
$xaction
->
getTransactionType
();
// If this transaction happened after the incident, we aren't going to
// undo it.
if
(
$created
>
$epoch_end
)
{
// If it's a status transaction, the task status has been updated
// after the incident, so we don't want to undo that. Stop looking for
// stuff to fix.
if
(
$type
==
ManiphestTransaction
::
TYPE_STATUS
)
{
$already_fixed
=
$xaction
;
break
;
}
continue
;
}
// If this happened before the incident then we're done picking through
// the rubble.
if
(
$created
<
$epoch_start
)
{
break
;
}
switch
(
$type
)
{
case
ManiphestTransaction
::
TYPE_STATUS
:
if
(!
$status_xaction
)
{
if
(
$xaction
->
getMetadataValue
(
'commitPHID'
))
{
$status_xaction
=
$xaction
;
}
}
break
;
case
PhabricatorTransactions
::
TYPE_EDGE
:
$edge_type
=
$xaction
->
getMetadataValue
(
'edge:type'
);
if
(
$edge_type
==
ManiphestTaskHasCommitEdgeType
::
EDGECONST
)
{
if
(
$xaction
->
getMetadataValue
(
'commitPHID'
))
{
$edge_xactions
[]
=
$xaction
;
}
}
break
;
case
ManiphestTransaction
::
TYPE_OWNER
:
$owner_xactions
[]
=
$xaction
;
break
;
}
}
if
(
$already_fixed
)
{
echo
tsprintf
(
"**<bg:blue> %s </bg>** %s
\n
"
,
pht
(
'FIXED'
),
pht
(
'Task had its status changed after the incident, and will not be '
.
'mutated.'
));
continue
;
}
if
(!
$status_xaction
)
{
echo
tsprintf
(
"**<bg:blue> %s </bg>** %s
\n
"
,
pht
(
'UNAFFECTED'
),
pht
(
'Task was not closed during the incident, and will not be mutated.'
));
continue
;
}
// Find an edge transaction which added commits within a few seconds of the
// status transaction, if one exists.
$edge_xaction
=
null
;
$status_date
=
$status_xaction
->
getDateCreated
();
foreach
(
$edge_xactions
as
$xaction
)
{
$edge_date
=
$xaction
->
getDateCreated
();
$delta
=
abs
(
$edge_date
-
$status_date
);
if
(
$delta
<
$a_few_seconds
)
{
$edge_xaction
=
$xaction
;
break
;
}
}
if
(!
$edge_xaction
)
{
echo
tsprintf
(
"**<bg:blue> %s </bg>** %s
\n
"
,
pht
(
'NO COMMITS'
),
pht
(
'Task was closed during the incident, but no commits were attached '
.
'at similar times. This looks like an unrelated status change, so it '
.
'will not be mutated.'
));
continue
;
}
$old_commits
=
$edge_xaction
->
getOldValue
();
if
(!
is_array
(
$old_commits
))
{
$old_commits
=
array
();
}
$old_commits
=
array_keys
(
$old_commits
);
$new_commits
=
$edge_xaction
->
getNewValue
();
if
(!
is_array
(
$new_commits
))
{
$new_commits
=
array
();
}
$new_commits
=
array_keys
(
$new_commits
);
$add_commits
=
array_diff
(
$new_commits
,
$old_commits
);
$commits
=
array
();
if
(
$add_commits
)
{
$commits
=
id
(
new
DiffusionCommitQuery
())
->
setViewer
(
$viewer
)
->
withPHIDs
(
$add_commits
)
->
execute
();
}
if
(!
$commits
)
{
echo
tsprintf
(
"**<bg:blue> %s </bg>** %s
\n
"
,
pht
(
'NO ADDED COMMITS'
),
pht
(
'Task was closed during the incident and commits were changed, but '
.
'none were added. This looks like a coincidence, so the task will '
.
'not be mutated.'
));
continue
;
}
$in_repository
=
array
();
foreach
(
$commits
as
$commit
)
{
$repository_id
=
$commit
->
getRepository
()->
getID
();
if
(
isset
(
$repositories
[
$repository_id
]))
{
$in_repository
[]
=
$commit
;
}
}
if
(!
$in_repository
)
{
echo
tsprintf
(
"**<bg:blue> %s </bg>** %s
\n
"
,
pht
(
'NO REPOSITORY COMMITS'
),
pht
(
'Task was closed during the incident and commits were added, but '
.
'not from the specified repositories. This looks unrelated, so the '
.
'task will not be mutated.'
));
continue
;
}
$owner_xaction
=
null
;
foreach
(
$owner_xactions
as
$xaction
)
{
$owner_date
=
$xaction
->
getDateCreated
();
$delta
=
abs
(
$edge_date
-
$owner_date
);
if
(
$delta
<
$a_few_seconds
)
{
$owner_xaction
=
$xaction
;
break
;
}
}
// We're ready to undo damage: we have a closing transaction in the incident
// window that applied adjacent to commits from the repository being
// attached. We're going to undo the status change, remove the commits, and
// destroy the transaction.
echo
tsprintf
(
"**<bg:yellow> %s </bg>** %s
\n
"
,
pht
(
'UNDO STATUS'
),
pht
(
'Task was affected, status will be reverted from "%s" to "%s".'
,
$status_xaction
->
getNewValue
(),
$status_xaction
->
getOldValue
()));
if
(
$owner_xaction
)
{
echo
tsprintf
(
"**<bg:yellow> %s </bg>** %s
\n
"
,
pht
(
'UNDO OWNER'
),
pht
(
'Owner will be reverted from "%s" to "%s".'
,
nonempty
(
$owner_xaction
->
getNewValue
(),
pht
(
'None'
)),
nonempty
(
$owner_xaction
->
getOldValue
(),
pht
(
'None'
))));
}
echo
tsprintf
(
"**<bg:yellow> %s </bg>** %s
\n
"
,
pht
(
'UNDO COMMITS'
),
pht
(
'Commits will be unlinked: %s.'
,
implode
(
', '
,
mpull
(
$in_repository
,
'getDisplayName'
))));
$will_delete
=
array
(
$status_xaction
->
getID
(),
$edge_xaction
->
getID
(),
);
if
(
$owner_xaction
)
{
$will_delete
[]
=
$owner_xaction
->
getID
();
}
echo
tsprintf
(
"**<bg:yellow> %s </bg>** %s
\n
"
,
pht
(
'UNDO TRANSCATIONS'
),
pht
(
'Transactions will be deleted: %s.'
,
implode
(
', '
,
$will_delete
)));
if
(!
$is_write
)
{
echo
tsprintf
(
"**<bg:blue> %s </bg>** %s
\n
"
,
pht
(
'NO ACTION'
),
pht
(
'This command was not run with --write, so no actual action will '
.
'be taken.'
));
continue
;
}
// Revert the status.
$task
->
setStatus
(
$status_xaction
->
getOldValue
());
// Revert the owner.
if
(
$owner_xaction
)
{
$task
->
setOwnerPHID
(
$owner_xaction
->
getOldValue
());
}
$task
->
save
();
// Detach the commits.
$edge_editor
=
new
PhabricatorEdgeEditor
();
foreach
(
$in_repository
as
$commit
)
{
$edge_editor
->
removeEdge
(
$task
->
getPHID
(),
ManiphestTaskHasCommitEdgeType
::
EDGECONST
,
$commit
->
getPHID
());
}
$edge_editor
->
save
();
// Destroy the transactions.
$status_xaction
->
delete
();
$edge_xaction
->
delete
();
if
(
$owner_xaction
)
{
$owner_xaction
->
delete
();
}
echo
tsprintf
(
"**<bg:red> %s </bg>** %s
\n
"
,
pht
(
'UNDONE'
),
pht
(
'Mutated task to pre-incident state.'
));
}
File Metadata
Details
Attached
Mime Type
text/plain; charset=utf-8
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
193062
Default Alt Text
undo_transactions.php (10 KB)
Attached To
Mode
P1997 undo_transactions.php
Attached
Detach File
Event Timeline
Log In to Comment