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

Side by Side Diff: revert.py

Issue 115514: Clean up revert.py and fixes an issue when only one file is reverted in a subdirectory. (Closed)
Patch Set: Created 11 years, 7 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 | « no previous file | tests/revert_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 # 5 #
6 # Tool to quickly revert a change. 6 # Tool to quickly revert a change.
7 7
8 import exceptions 8 import exceptions
9 import optparse 9 import optparse
10 import os 10 import os
11 import sys 11 import sys
12 import xml 12 import xml
13 13
14 import gcl 14 import gcl
15 import gclient 15 import gclient
16 16
17 class ModifiedFile(exceptions.Exception): 17 class ModifiedFile(exceptions.Exception):
18 pass 18 pass
19 class NoModifiedFile(exceptions.Exception): 19 class NoModifiedFile(exceptions.Exception):
20 pass 20 pass
21 class NoBlameList(exceptions.Exception): 21 class NoBlameList(exceptions.Exception):
22 pass 22 pass
23 class OutsideOfCheckout(exceptions.Exception): 23 class OutsideOfCheckout(exceptions.Exception):
24 pass 24 pass
25 25
26 26
27 def getTexts(nodelist):
28 """Return a list of texts in the children of a list of DOM nodes."""
29 rc = []
30 for node in nodelist:
31 if node.nodeType == node.TEXT_NODE:
32 rc.append(node.data)
33 else:
34 rc.extend(getTexts(node.childNodes))
35 return rc
36
37
38 def RunShellXML(command, print_output=False, keys=None):
39 output = gcl.RunShell(command, print_output)
40 try:
41 dom = xml.dom.minidom.parseString(output)
42 if not keys:
43 return dom
44 result = {}
45 for key in keys:
46 result[key] = getTexts(dom.getElementsByTagName(key))
47 except xml.parsers.expat.ExpatError:
48 print "Failed to parse output:\n%s" % output
49 raise
50 return result
51
52
53 def UniqueFast(list): 27 def UniqueFast(list):
54 list = [item for item in set(list)] 28 list = [item for item in set(list)]
55 list.sort() 29 list.sort()
56 return list 30 return list
57 31
58 32
59 def GetRepoBase(): 33 def GetRepoBase():
60 """Returns the repository base of the root local checkout.""" 34 """Returns the repository base of the root local checkout."""
61 xml_data = RunShellXML(['svn', 'info', '.', '--xml'], keys=['root', 'url']) 35 info = gclient.CaptureSVNInfo('.')
62 root = xml_data['root'][0] 36 root = info['Repository Root']
63 url = xml_data['url'][0] 37 url = info['URL']
64 if not root or not url: 38 if not root or not url:
65 raise exceptions.Exception("I'm confused by your checkout") 39 raise exceptions.Exception("I'm confused by your checkout")
66 if not url.startswith(root): 40 if not url.startswith(root):
67 raise exceptions.Exception("I'm confused by your checkout", url, root) 41 raise exceptions.Exception("I'm confused by your checkout", url, root)
68 return url[len(root):] + '/' 42 return url[len(root):] + '/'
69 43
70 44
45 def CaptureSVNLog(args):
46 command = ['log', '--xml']
47 if args:
48 command += args
49 output = gclient.CaptureSVN(command)
50 dom = gclient.ParseXML(output)
51 entries = []
52 if dom:
53 # /log/logentry/
54 # @revision
55 # author|date
56 # paths/
57 # path (@kind&@action)
58 for node in dom.getElementsByTagName('logentry'):
59 paths = []
60 for path in node.getElementsByTagName('path'):
61 item = {
62 'kind': path.getAttribute('kind'),
63 'action': path.getAttribute('action'),
64 'path': path.firstChild.nodeValue,
65 }
66 paths.append(item)
67 entry = {
68 'revision': int(node.getAttribute('revision')),
69 'author': gclient.GetNamedNodeText(node, 'author'),
70 'date': gclient.GetNamedNodeText(node, 'date'),
71 'paths': paths,
72 }
73 entries.append(entry)
74 return entries
75
76
71 def Revert(revisions, force=False, commit=True, send_email=True, message=None, 77 def Revert(revisions, force=False, commit=True, send_email=True, message=None,
72 reviewers=None): 78 reviewers=None):
73 """Reverts many revisions in one change list. 79 """Reverts many revisions in one change list.
74 80
75 If force is True, it will override local modifications. 81 If force is True, it will override local modifications.
76 If commit is True, a commit is done after the revert. 82 If commit is True, a commit is done after the revert.
77 If send_mail is True, a review email is sent. 83 If send_mail is True, a review email is sent.
78 If message is True, it is used as the change description. 84 If message is True, it is used as the change description.
79 reviewers overrides the blames email addresses for review email.""" 85 reviewers overrides the blames email addresses for review email."""
80 86
81 # Use the oldest revision as the primary revision. 87 # Use the oldest revision as the primary revision.
82 changename = "revert%d" % revisions[len(revisions)-1] 88 changename = "revert%d" % revisions[len(revisions)-1]
83 if not force and os.path.exists(gcl.GetChangelistInfoFile(changename)): 89 if not force and os.path.exists(gcl.GetChangelistInfoFile(changename)):
84 print "Error, change %s already exist." % changename 90 print "Error, change %s already exist." % changename
85 return 1 91 return 1
86 92
87 # Move to the repository root and make the revision numbers sorted in 93 # Move to the repository root and make the revision numbers sorted in
88 # decreasing order. 94 # decreasing order.
89 os.chdir(gcl.GetRepositoryRoot()) 95 os.chdir(gcl.GetRepositoryRoot())
90 revisions.sort(reverse=True) 96 revisions.sort(reverse=True)
91 revisions_string = ",".join([str(rev) for rev in revisions]) 97 revisions_string = ",".join([str(rev) for rev in revisions])
92 revisions_string_rev = ",".join([str(-rev) for rev in revisions]) 98 revisions_string_rev = ",".join([str(-rev) for rev in revisions])
93 99
94 repo_base = GetRepoBase() 100 # Get all the modified files by the revision. We'll use this list to optimize
101 # the svn merge.
102 logs = []
103 for revision in revisions:
104 logs.extend(CaptureSVNLog(["-r", str(revision), "-v"]))
105
95 files = [] 106 files = []
96 blames = [] 107 blames = []
97 # Get all the modified files by the revision. We'll use this list to optimize 108 repo_base = GetRepoBase()
98 # the svn merge. 109 for log in logs:
99 for revision in revisions: 110 for file in log['paths']:
100 log = RunShellXML(["svn", "log", "-r", str(revision), "-v", "--xml"], 111 file_name = file['path']
101 keys=['path', 'author'])
102 for file in log['path']:
103 # Remove the /trunk/src/ part. The + 1 is for the last slash. 112 # Remove the /trunk/src/ part. The + 1 is for the last slash.
104 if not file.startswith(repo_base): 113 if not file_name.startswith(repo_base):
105 raise OutsideOfCheckout(file) 114 raise OutsideOfCheckout(file_name)
106 files.append(file[len(repo_base):]) 115 files.append(file_name[len(repo_base):])
107 blames.extend(log['author']) 116 blames.append(log['author'])
108 117
109 # On Windows, we need to fix the slashes once they got the url part removed. 118 # On Windows, we need to fix the slashes once they got the url part removed.
110 if sys.platform == 'win32': 119 if sys.platform == 'win32':
111 # On Windows, gcl expect the correct slashes. 120 # On Windows, gcl expect the correct slashes.
112 files = [file.replace('/', os.sep) for file in files] 121 files = [file.replace('/', os.sep) for file in files]
113 122
114 # Keep unique. 123 # Keep unique.
115 files = UniqueFast(files) 124 files = UniqueFast(files)
116 blames = UniqueFast(blames) 125 blames = UniqueFast(blames)
117 if not reviewers: 126 if not reviewers:
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 180
172 print "Reverting %s in %s/" % (revisions_string, root) 181 print "Reverting %s in %s/" % (revisions_string, root)
173 if need_to_update: 182 if need_to_update:
174 # Make sure '.' revision is high enough otherwise merge will be 183 # Make sure '.' revision is high enough otherwise merge will be
175 # unhappy. 184 # unhappy.
176 retcode = gcl.RunShellWithReturnCode(['svn', 'up', '.', '-N'])[1] 185 retcode = gcl.RunShellWithReturnCode(['svn', 'up', '.', '-N'])[1]
177 if retcode: 186 if retcode:
178 print 'svn up . -N failed in %s/.' % root 187 print 'svn up . -N failed in %s/.' % root
179 return retcode 188 return retcode
180 189
181 # TODO(maruel): BUG WITH ONLY ONE FILE.
182 command = ["svn", "merge", "-c", revisions_string_rev] 190 command = ["svn", "merge", "-c", revisions_string_rev]
183 command.extend(file_list) 191 command.extend(file_list)
184 (output, retcode) = gcl.RunShellWithReturnCode(command, print_output=True) 192 (output, retcode) = gcl.RunShellWithReturnCode(command, print_output=True)
185 if retcode: 193 if retcode:
186 print "'%s' failed:" % command 194 print "'%s' failed:" % command
187 return retcode 195 return retcode
188 196
189 # Grab the status 197 # Grab the status
190 lines = output.split('\n') 198 lines = output.split('\n')
191 for line in lines: 199 for line in lines:
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
276 print "".join(e.args) 284 print "".join(e.args)
277 print "You can use the --force flag to revert the files." 285 print "You can use the --force flag to revert the files."
278 except OutsideOfCheckout, e: 286 except OutsideOfCheckout, e:
279 print "Your repository doesn't contain ", str(e) 287 print "Your repository doesn't contain ", str(e)
280 288
281 return retcode 289 return retcode
282 290
283 291
284 if __name__ == "__main__": 292 if __name__ == "__main__":
285 sys.exit(Main(sys.argv)) 293 sys.exit(Main(sys.argv))
OLDNEW
« no previous file with comments | « no previous file | tests/revert_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698