OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 | |
3 # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
4 # for details. All rights reserved. Use of this source code is governed by a | |
5 # BSD-style license that can be found in the LICENSE file. | |
6 | |
7 import optparse | |
8 import os | |
9 import platform | |
10 import subprocess | |
11 import sys | |
12 | |
13 """A script used to revert one or a sequence of consecutive CLs, for svn and | |
14 git-svn users. | |
15 """ | |
16 | |
17 def parse_args(): | |
18 parser = optparse.OptionParser() | |
19 parser.add_option('--revisions', '-r', dest='rev_range', action='store', | |
20 default=None, help='The revision number(s) of the commits ' | |
21 'you wish to undo. An individual number, or a range (8-10, ' | |
22 '8..10, or 8:10).') | |
23 args, _ = parser.parse_args() | |
24 revision_range = args.rev_range | |
25 if revision_range == None: | |
26 maybe_fail('You must specify at least one revision number to revert.') | |
27 if revision_range.find('-') > -1 or revision_range.find(':') > -1 or \ | |
28 revision_range.find('..') > -1: | |
29 # We have a range of commits to revert. | |
30 split = revision_range.split('-') | |
31 if len(split) == 1: | |
32 split = revision_range.split(':') | |
33 if len(split) == 1: | |
34 split = revision_range.split('..') | |
35 start = int(split[0]) | |
36 end = int(split[1]) | |
37 if start > end: | |
38 temp = start | |
39 start = end | |
40 end = temp | |
41 if start != end: | |
42 maybe_fail('Warning: are you sure you want to revert a range of ' | |
43 'revisions? If you just want to revert one CL, only specify ' | |
44 'one revision number.', user_input=True) | |
45 else: | |
46 start = end = int(revision_range) | |
47 return start, end | |
48 | |
49 def maybe_fail(msg, user_input=False): | |
50 """Determine if we have encountered a condition upon which our script cannot | |
51 continue, and abort if so. | |
52 Args: | |
53 - msg: The error or user prompt message to print. | |
54 - user_input: True if we require user confirmation to continue. We assume | |
55 that the user must enter y to proceed. | |
56 """ | |
57 if user_input: | |
58 force = raw_input(msg + ' (y/N) ') | |
59 if force != 'y': | |
60 sys.exit(0) | |
61 else: | |
62 print msg | |
63 sys.exit(1) | |
64 | |
65 def has_new_code(is_git): | |
66 """Tests if there are any newer versions of files on the server. | |
67 Args: | |
68 - is_git: True if we are working in a git repository. | |
69 """ | |
70 os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
71 if not is_git: | |
72 results = run_cmd(['svn', 'st']) | |
73 else: | |
74 results = run_cmd(['git', 'status']) | |
75 for line in results.split('\n'): | |
76 if not is_git and (not line.strip().startswith('?') and line != ''): | |
77 return True | |
78 elif is_git and ('Changes to be committed' in line or | |
79 'Changes not staged for commit:' in line): | |
80 return True | |
81 if is_git: | |
82 p = subprocess.Popen(['git', 'log', '-1'], stdout=subprocess.PIPE) | |
83 output, _ = p.communicate() | |
84 if find_git_info(output) == None: | |
85 return True | |
86 return False | |
87 | |
88 def run_cmd(cmd_list): | |
89 """Run the specified command and print out any output to stdout.""" | |
90 print ' '.join(cmd_list) | |
91 out = subprocess.PIPE | |
92 p = subprocess.Popen(cmd_list, stdout=out, stderr=subprocess.PIPE, | |
93 stdin=subprocess.PIPE, | |
94 shell=(platform.system()=='Windows')) | |
95 output, stderr = p.communicate() | |
96 if output: | |
97 print output | |
98 if stderr: | |
99 print stderr | |
100 return output | |
101 | |
102 def runs_git(): | |
103 """Returns True if we're standing in an svn-git repository.""" | |
104 p = subprocess.Popen(['svn', 'info'], stdout=subprocess.PIPE, | |
105 stderr=subprocess.PIPE) | |
106 output, err = p.communicate() | |
107 if err != None and 'is not a working copy' in err: | |
108 p = subprocess.Popen(['git', 'status'], stdout=subprocess.PIPE) | |
109 output, _ = p.communicate() | |
110 if 'fatal: Not a git repository' in output: | |
111 maybe_fail('Error: not running git or svn.') | |
112 else: | |
113 return True | |
114 return False | |
115 | |
116 def find_git_info(git_log, rev_num=None): | |
117 """Determine the latest svn revision number if rev_num = None, or find the | |
118 git commit_id that corresponds to a particular svn revision number. | |
119 """ | |
120 for line in git_log.split('\n'): | |
121 tokens = line.split() | |
122 if len(tokens) == 2 and tokens[0] == 'commit': | |
123 current_commit_id = tokens[1] | |
124 elif len(tokens) > 0 and tokens[0] == 'git-svn-id:': | |
125 revision_number = int(tokens[1].split('@')[1]) | |
126 if revision_number == rev_num: | |
127 return current_commit_id | |
128 if rev_num == None: | |
129 return revision_number | |
130 | |
131 def revert(start, end, is_git): | |
132 """Revert the sequence of CLs. | |
133 Args: | |
134 - start: The first CL to revert. | |
135 - end: The last CL to revert. | |
136 - is_git: True if we are in a git-svn checkout. | |
137 """ | |
138 if not is_git: | |
139 run_cmd(['svn', 'merge', '-r', '%d:%d' % (end, start-1), '.']) | |
140 else: | |
141 # If we're running git, we have to use the log feature to find the commit | |
142 # id(s) that correspond to the particular revision number(s). | |
143 output = run_cmd(['git', 'log', '-1']) | |
144 current_revision = find_git_info(output) | |
145 distance = (current_revision-start) + 1 | |
146 reverts = [start] | |
147 commit_msg = '"Reverting %d"' % start | |
148 if end != start: | |
149 reverts = range(start, end + 1) | |
150 reverts.reverse() | |
151 commit_msg = '%s-%d"' % (commit_msg[:-1], end) | |
152 for revert in reverts: | |
153 git_commit_id = find_git_info(output, revert) | |
154 run_cmd(['git', 'revert', '-n', git_commit_id]) | |
155 run_cmd(['git', 'commit', '-m', commit_msg]) | |
156 | |
157 def main(): | |
158 revisions = parse_args() | |
159 git_user = runs_git() | |
160 if has_new_code(git_user): | |
161 if git_user: | |
162 maybe_fail('Your tree has local modifications! Move to a clean tree and ' | |
163 'try running this script again.') | |
164 else: | |
165 maybe_fail('WARNING: This checkout has local modifications!! This could ' | |
166 'result in a CL that is not just a revert and/or you could lose your' | |
167 ' local changes! Are you **SURE** you want to continue? ', | |
168 user_input=True) | |
169 if git_user: | |
170 run_cmd(['git', 'cl', 'rebase']) | |
171 run_cmd(['gclient', 'sync']) | |
172 revert(revisions[0], revisions[1], git_user) | |
173 print ('Reverting success! The buildbots and your teammates thank you! Now ' | |
Siggi Cherem (dart-lang)
2012/05/08 17:48:23
:) nice message.
One suggestion, maybe reword the
| |
174 'create a CL, and submit!') | |
175 | |
176 if __name__ == '__main__': | |
177 main() | |
OLD | NEW |