| Index: tools/revert.py
|
| ===================================================================
|
| --- tools/revert.py (revision 0)
|
| +++ tools/revert.py (revision 0)
|
| @@ -0,0 +1,193 @@
|
| +#!/usr/bin/python
|
| +
|
| +# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| +# for details. All rights reserved. Use of this source code is governed by a
|
| +# BSD-style license that can be found in the LICENSE file.
|
| +
|
| +import optparse
|
| +import os
|
| +import platform
|
| +import subprocess
|
| +import sys
|
| +
|
| +"""A script used to revert one or a sequence of consecutive CLs, for svn and
|
| +git-svn users.
|
| +"""
|
| +
|
| +def parse_args():
|
| + parser = optparse.OptionParser()
|
| + parser.add_option('--revisions', '-r', dest='rev_range', action='store',
|
| + default=None, help='The revision number(s) of the commits '
|
| + 'you wish to undo. An individual number, or a range (8-10, '
|
| + '8..10, or 8:10).')
|
| + args, _ = parser.parse_args()
|
| + revision_range = args.rev_range
|
| + if revision_range == None:
|
| + maybe_fail('You must specify at least one revision number to revert.')
|
| + if revision_range.find('-') > -1 or revision_range.find(':') > -1 or \
|
| + revision_range.find('..') > -1:
|
| + # We have a range of commits to revert.
|
| + split = revision_range.split('-')
|
| + if len(split) == 1:
|
| + split = revision_range.split(':')
|
| + if len(split) == 1:
|
| + split = revision_range.split('..')
|
| + start = int(split[0])
|
| + end = int(split[1])
|
| + if start > end:
|
| + temp = start
|
| + start = end
|
| + end = temp
|
| + if start != end:
|
| + maybe_fail('Warning: Are you sure you want to revert a range of '
|
| + 'revisions? If you just want to revert one CL, only specify '
|
| + 'one revision number.', user_input=True)
|
| + else:
|
| + start = end = int(revision_range)
|
| + return start, end
|
| +
|
| +def maybe_fail(msg, user_input=False):
|
| + """Determine if we have encountered a condition upon which our script cannot
|
| + continue, and abort if so.
|
| + Args:
|
| + - msg: The error or user prompt message to print.
|
| + - user_input: True if we require user confirmation to continue. We assume
|
| + that the user must enter y to proceed.
|
| + """
|
| + if user_input:
|
| + force = raw_input(msg + ' (y/N) ')
|
| + if force != 'y':
|
| + sys.exit(0)
|
| + else:
|
| + print msg
|
| + sys.exit(1)
|
| +
|
| +def has_new_code(is_git):
|
| + """Tests if there are any newer versions of files on the server.
|
| + Args:
|
| + - is_git: True if we are working in a git repository.
|
| + """
|
| + os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| + if not is_git:
|
| + results, _ = run_cmd(['svn', 'st'])
|
| + else:
|
| + results, _ = run_cmd(['git', 'status'])
|
| + for line in results.split('\n'):
|
| + if not is_git and (not line.strip().startswith('?') and line != ''):
|
| + return True
|
| + elif is_git and ('Changes to be committed' in line or
|
| + 'Changes not staged for commit:' in line):
|
| + return True
|
| + if is_git:
|
| + p = subprocess.Popen(['git', 'log', '-1'], stdout=subprocess.PIPE)
|
| + output, _ = p.communicate()
|
| + if find_git_info(output) == None:
|
| + return True
|
| + return False
|
| +
|
| +def run_cmd(cmd_list, suppress_output=False, std_in=''):
|
| + """Run the specified command and print out any output to stdout."""
|
| + print ' '.join(cmd_list)
|
| + p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
| + stdin=subprocess.PIPE,
|
| + shell=(platform.system()=='Windows'))
|
| + output, stderr = p.communicate(std_in)
|
| + if output and not suppress_output:
|
| + print output
|
| + if stderr and not suppress_output:
|
| + print stderr
|
| + return (output, stderr)
|
| +
|
| +def runs_git():
|
| + """Returns True if we're standing in an svn-git repository."""
|
| + p = subprocess.Popen(['svn', 'info'], stdout=subprocess.PIPE,
|
| + stderr=subprocess.PIPE)
|
| + output, err = p.communicate()
|
| + if err != None and 'is not a working copy' in err:
|
| + p = subprocess.Popen(['git', 'status'], stdout=subprocess.PIPE)
|
| + output, _ = p.communicate()
|
| + if 'fatal: Not a git repository' in output:
|
| + maybe_fail('Error: not running git or svn.')
|
| + else:
|
| + return True
|
| + return False
|
| +
|
| +def find_git_info(git_log, rev_num=None):
|
| + """Determine the latest svn revision number if rev_num = None, or find the
|
| + git commit_id that corresponds to a particular svn revision number.
|
| + """
|
| + for line in git_log.split('\n'):
|
| + tokens = line.split()
|
| + if len(tokens) == 2 and tokens[0] == 'commit':
|
| + current_commit_id = tokens[1]
|
| + elif len(tokens) > 0 and tokens[0] == 'git-svn-id:':
|
| + revision_number = int(tokens[1].split('@')[1])
|
| + if revision_number == rev_num:
|
| + return current_commit_id
|
| + if rev_num == None:
|
| + return revision_number
|
| +
|
| +def revert(start, end, is_git):
|
| + """Revert the sequence of CLs.
|
| + Args:
|
| + - start: The first CL to revert.
|
| + - end: The last CL to revert.
|
| + - is_git: True if we are in a git-svn checkout.
|
| + """
|
| + if not is_git:
|
| + _, err = run_cmd(['svn', 'merge', '-r', '%d:%d' % (end, start-1), '.'],
|
| + std_in='p')
|
| + if 'Conflict discovered' in err:
|
| + maybe_fail('Please fix the above conflicts before submitting. Then create'
|
| + ' a CL and submit your changes to complete the revert.')
|
| +
|
| + else:
|
| + # If we're running git, we have to use the log feature to find the commit
|
| + # id(s) that correspond to the particular revision number(s).
|
| + output, _ = run_cmd(['git', 'log', '-1'], suppress_output=True)
|
| + current_revision = find_git_info(output)
|
| + distance = (current_revision-start) + 1
|
| + output, _ = run_cmd(['git', 'log', '-%d' % distance], suppress_output=True)
|
| + reverts = [start]
|
| + commit_msg = '"Reverting %d"' % start
|
| + if end != start:
|
| + reverts = range(start, end + 1)
|
| + reverts.reverse()
|
| + commit_msg = '%s-%d"' % (commit_msg[:-1], end)
|
| + for revert in reverts:
|
| + git_commit_id = find_git_info(output, revert)
|
| + if git_commit_id == None:
|
| + maybe_fail('Error: Revision number not found. Is this earlier than your'
|
| + ' git checkout history?')
|
| + _, err = run_cmd(['git', 'revert', '-n', git_commit_id])
|
| + if 'error: could not revert' in err or 'unmerged' in err:
|
| + command_sequence = ''
|
| + for revert in reverts:
|
| + git_commit_id = find_git_info(output, revert)
|
| + command_sequence += 'git revert -n %s\n' % git_commit_id
|
| + maybe_fail('There are conflicts while reverting. Please resolve these '
|
| + 'after manually running:\n' + command_sequence + 'and then '
|
| + 'create a CL and submit to complete the revert.')
|
| + run_cmd(['git', 'commit', '-m', commit_msg])
|
| +
|
| +def main():
|
| + revisions = parse_args()
|
| + git_user = runs_git()
|
| + if has_new_code(git_user):
|
| + if git_user:
|
| + maybe_fail('Your tree has local modifications! Move to a clean tree and '
|
| + 'try running this script again.')
|
| + else:
|
| + maybe_fail('WARNING: This checkout has local modifications!! This could '
|
| + 'result in a CL that is not just a revert and/or you could lose your'
|
| + ' local changes! Are you **SURE** you want to continue? ',
|
| + user_input=True)
|
| + if git_user:
|
| + run_cmd(['git', 'cl', 'rebase'])
|
| + run_cmd(['gclient', 'sync'])
|
| + revert(revisions[0], revisions[1], git_user)
|
| + print ('Now, create a CL and submit! The buildbots and your teammates thank '
|
| + 'you!')
|
| +
|
| +if __name__ == '__main__':
|
| + main()
|
|
|