Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(500)

Side by Side Diff: revert.py

Issue 553145: Delete the revert.py tool. (Closed)
Patch Set: Created 10 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « revert.bat ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5 #
6 # Tool to quickly revert a change.
7
8 import exceptions
9 import optparse
10 import os
11 import sys
12 import xml
13
14 import gcl
15 import gclient
16 import gclient_scm
17 import gclient_utils
18
19 class ModifiedFile(exceptions.Exception):
20 pass
21 class NoModifiedFile(exceptions.Exception):
22 pass
23 class NoBlameList(exceptions.Exception):
24 pass
25 class OutsideOfCheckout(exceptions.Exception):
26 pass
27
28
29 def UniqueFast(list):
30 list = [item for item in set(list)]
31 list.sort()
32 return list
33
34
35 def GetRepoBase():
36 """Returns the repository base of the root local checkout."""
37 info = gclient_scm.CaptureSVNInfo('.')
38 root = info['Repository Root']
39 url = info['URL']
40 if not root or not url:
41 raise exceptions.Exception("I'm confused by your checkout")
42 if not url.startswith(root):
43 raise exceptions.Exception("I'm confused by your checkout", url, root)
44 return url[len(root):] + '/'
45
46
47 def CaptureSVNLog(args):
48 command = ['log', '--xml']
49 if args:
50 command += args
51 output = gclient_scm.CaptureSVN(command)
52 dom = gclient_utils.ParseXML(output)
53 entries = []
54 if dom:
55 # /log/logentry/
56 # @revision
57 # author|date
58 # paths/
59 # path (@kind&@action)
60 for node in dom.getElementsByTagName('logentry'):
61 paths = []
62 for path in node.getElementsByTagName('path'):
63 item = {
64 'kind': path.getAttribute('kind'),
65 'action': path.getAttribute('action'),
66 'path': path.firstChild.nodeValue,
67 }
68 paths.append(item)
69 entry = {
70 'revision': int(node.getAttribute('revision')),
71 'author': gclient_utils.GetNamedNodeText(node, 'author'),
72 'date': gclient_utils.GetNamedNodeText(node, 'date'),
73 'paths': paths,
74 }
75 entries.append(entry)
76 return entries
77
78
79 def Revert(revisions, force=False, commit=True, send_email=True, message=None,
80 reviewers=None):
81 """Reverts many revisions in one change list.
82
83 If force is True, it will override local modifications.
84 If commit is True, a commit is done after the revert.
85 If send_mail is True, a review email is sent.
86 If message is True, it is used as the change description.
87 reviewers overrides the blames email addresses for review email."""
88
89 # Use the oldest revision as the primary revision.
90 changename = "revert%d" % revisions[len(revisions)-1]
91 if not force and os.path.exists(gcl.GetChangelistInfoFile(changename)):
92 print "Error, change %s already exist." % changename
93 return 1
94
95 # Move to the repository root and make the revision numbers sorted in
96 # decreasing order.
97 local_root = gcl.GetRepositoryRoot()
98 os.chdir(local_root)
99 revisions.sort(reverse=True)
100 revisions_string = ",".join([str(rev) for rev in revisions])
101 revisions_string_rev = ",".join([str(-rev) for rev in revisions])
102
103 # Get all the modified files by the revision. We'll use this list to optimize
104 # the svn merge.
105 logs = []
106 for revision in revisions:
107 logs.extend(CaptureSVNLog(["-r", str(revision), "-v"]))
108
109 files = []
110 blames = []
111 repo_base = GetRepoBase()
112 for log in logs:
113 for file in log['paths']:
114 file_name = file['path']
115 # Remove the /trunk/src/ part. The + 1 is for the last slash.
116 if not file_name.startswith(repo_base):
117 raise OutsideOfCheckout(file_name)
118 files.append(file_name[len(repo_base):])
119 blames.append(log['author'])
120
121 # On Windows, we need to fix the slashes once they got the url part removed.
122 if sys.platform == 'win32':
123 # On Windows, gcl expect the correct slashes.
124 files = [file.replace('/', os.sep) for file in files]
125
126 # Keep unique.
127 files = UniqueFast(files)
128 blames = UniqueFast(blames)
129 if not reviewers:
130 reviewers = blames
131 else:
132 reviewers = UniqueFast(reviewers)
133
134 # Make sure there's something to revert.
135 if not files:
136 raise NoModifiedFile
137 if not reviewers:
138 raise NoBlameList
139
140 if blames:
141 print "Blaming %s\n" % ",".join(blames)
142 if reviewers != blames:
143 print "Emailing %s\n" % ",".join(reviewers)
144 print "These files were modified in %s:" % revisions_string
145 print "\n".join(files)
146 print ""
147
148 # Make sure these files are unmodified with svn status.
149 status = gclient_scm.scm.SVN.CaptureStatus(files)
150 if status:
151 if force:
152 # TODO(maruel): Use the tool to correctly revert '?' files.
153 gcl.RunShell(["svn", "revert"] + files)
154 else:
155 raise ModifiedFile(status)
156 # svn up on each of these files
157 gcl.RunShell(["svn", "up"] + files)
158
159 files_status = {}
160 # Extract the first level subpaths. Subversion seems to degrade
161 # exponentially w.r.t. repository size during merges. Working at the root
162 # directory is too rough for svn due to the repository size.
163 roots = UniqueFast([file.split(os.sep)[0] for file in files])
164 for root in roots:
165 # Is it a subdirectory or a files?
166 is_root_subdir = os.path.isdir(root)
167 need_to_update = False
168 if is_root_subdir:
169 os.chdir(root)
170 file_list = []
171 # List the file directly since it is faster when there is only one file.
172 for file in files:
173 if file.startswith(root):
174 file_list.append(file[len(root)+1:])
175 if len(file_list) > 1:
176 # Listing multiple files is not supported by svn merge.
177 file_list = ['.']
178 need_to_update = True
179 else:
180 # Oops, root was in fact a file in the root directory.
181 file_list = [root]
182 root = "."
183
184 print "Reverting %s in %s/" % (revisions_string, root)
185 if need_to_update:
186 # Make sure '.' revision is high enough otherwise merge will be
187 # unhappy.
188 retcode = gcl.RunShellWithReturnCode(['svn', 'up', '.', '-N'])[1]
189 if retcode:
190 print 'svn up . -N failed in %s/.' % root
191 return retcode
192
193 command = ["svn", "merge", "-c", revisions_string_rev]
194 command.extend(file_list)
195 (output, retcode) = gcl.RunShellWithReturnCode(command, print_output=True)
196 if retcode:
197 print "'%s' failed:" % command
198 return retcode
199
200 # Grab the status
201 lines = output.split('\n')
202 for line in lines:
203 if line.startswith('---'):
204 continue
205 if line.startswith('Skipped'):
206 print ""
207 raise ModifiedFile(line[9:-1])
208 # Update the status.
209 status = line[:5] + ' '
210 file = line[5:]
211 if is_root_subdir:
212 files_status[root + os.sep + file] = status
213 else:
214 files_status[file] = status
215
216 if is_root_subdir:
217 os.chdir('..')
218
219 # Transform files_status from a dictionary to a list of tuple.
220 files_status = [(files_status[file], file) for file in files]
221
222 description = "Reverting %s." % revisions_string
223 if message:
224 description += "\n\n"
225 description += message
226 # Don't use gcl.Change() since it prompts the user for infos.
227 change_info = gcl.ChangeInfo(changename, 0, 0, description, files_status,
228 local_root)
229 change_info.Save()
230
231 upload_args = ['--no_presubmit', '-r', ",".join(reviewers)]
232 if send_email:
233 upload_args.append('--send_mail')
234 if commit:
235 upload_args.append('--no_try')
236 gcl.UploadCL(change_info, upload_args)
237
238 retcode = 0
239 if commit:
240 gcl.Commit(change_info, ['--no_presubmit', '--force'])
241 # TODO(maruel): gclient sync (to leave the local checkout in an usable
242 # state)
243 retcode = gclient.Main(["gclient.py", "sync"])
244 return retcode
245
246
247 def Main(argv):
248 usage = (
249 """%prog [options] [revision numbers to revert]
250 Revert a set of revisions, send the review to Rietveld, sends a review email
251 and optionally commit the revert.""")
252
253 parser = optparse.OptionParser(usage=usage)
254 parser.add_option("-c", "--commit", default=False, action="store_true",
255 help="Commits right away.")
256 parser.add_option("-f", "--force", default=False, action="store_true",
257 help="Forces the local modification even if a file is "
258 "already modified locally.")
259 parser.add_option("-n", "--no_email", default=False, action="store_true",
260 help="Inhibits from sending a review email.")
261 parser.add_option("-m", "--message", default=None,
262 help="Additional change description message.")
263 parser.add_option("-r", "--reviewers", action="append",
264 help="Reviewers to send the email to. By default, the list "
265 "of commiters is used.")
266 if len(argv) < 2:
267 parser.print_help()
268 return 1;
269
270 options, args = parser.parse_args(argv)
271 revisions = []
272 try:
273 for item in args[1:]:
274 revisions.append(int(item))
275 except ValueError:
276 parser.error("You need to pass revision numbers.")
277 if not revisions:
278 parser.error("You need to pass revision numbers.")
279 retcode = 1
280 try:
281 if not os.path.exists(gcl.GetInfoDir()):
282 os.mkdir(gcl.GetInfoDir())
283 retcode = Revert(revisions, options.force, options.commit,
284 not options.no_email, options.message, options.reviewers)
285 except NoBlameList:
286 print "Error: no one to blame."
287 except NoModifiedFile:
288 print "Error: no files to revert."
289 except ModifiedFile, e:
290 print "You need to revert these files since they were already modified:"
291 print "".join(e.args)
292 print "You can use the --force flag to revert the files."
293 except OutsideOfCheckout, e:
294 print "Your repository doesn't contain ", str(e)
295
296 return retcode
297
298
299 if __name__ == "__main__":
300 sys.exit(Main(sys.argv))
OLDNEW
« no previous file with comments | « revert.bat ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698