blob: a73f1d347f8d12a78c5b631d1301355fdd7e23e3 [file] [log] [blame]
[email protected]725f1c32011-04-01 20:24:541#!/usr/bin/env python
[email protected]66bc1a52012-05-23 19:34:042# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]817ba612009-09-23 21:00:533# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
[email protected]5953f3f2013-03-18 19:35:106import datetime
[email protected]a4899042009-10-16 19:42:417import optparse
8import os
9import re
[email protected]d90ad9b2009-08-06 00:20:1710import sys
[email protected]08145472012-11-30 23:09:1211import urlparse
[email protected]d90ad9b2009-08-06 00:20:1712
[email protected]ada4c652009-12-03 15:32:0113
[email protected]3a292682010-08-23 18:54:5514import gclient_utils
[email protected]98385dd2011-04-05 14:48:3215import subprocess2
[email protected]3a292682010-08-23 18:54:5516
[email protected]a4899042009-10-16 19:42:4117USAGE = """
18WARNING: Please use this tool in an empty directory
19(or at least one that you don't mind clobbering.)
20
21REQUIRES: SVN 1.5+
[email protected]98385dd2011-04-05 14:48:3222NOTE: NO NEED TO CHECKOUT ANYTHING IN ADVANCE OF USING THIS TOOL.
[email protected]a4899042009-10-16 19:42:4123Valid parameters:
24
25[Merge from trunk to branch]
[email protected]9ce98222009-10-19 20:24:1726--merge <revision> --branch <branch_num>
27Example: %(app)s --merge 12345 --branch 187
[email protected]a4899042009-10-16 19:42:4128
[email protected]0b9e6932010-02-22 16:49:0629[Merge from trunk to local copy]
30--merge <revision> --local
31Example: %(app)s --merge 12345 --local
32
[email protected]2d6af5f2010-01-22 18:18:5133[Merge from branch to branch]
[email protected]6e29d572010-06-04 17:32:2034--merge <revision> --sbranch <branch_num> --branch <branch_num>
[email protected]2d6af5f2010-01-22 18:18:5135Example: %(app)s --merge 12345 --sbranch 248 --branch 249
36
[email protected]a4899042009-10-16 19:42:4137[Revert from trunk]
[email protected]9ce98222009-10-19 20:24:1738--revert <revision>
39Example: %(app)s --revert 12345
[email protected]a4899042009-10-16 19:42:4140
41[Revert from branch]
[email protected]9ce98222009-10-19 20:24:1742--revert <revision> --branch <branch_num>
43Example: %(app)s --revert 12345 --branch 187
[email protected]a4899042009-10-16 19:42:4144"""
45
[email protected]b97010c2009-10-15 23:50:5646export_map_ = None
47files_info_ = None
48delete_map_ = None
[email protected]35299542010-01-27 01:22:1649file_pattern_ = r"[ ]+([MADUC])[ ]+/((?:trunk|branches/.*?)/src(.*)/(.*))"
[email protected]90a685e2010-10-14 02:09:2550depot_tools_dir_ = os.path.dirname(os.path.abspath(__file__))
[email protected]817ba612009-09-23 21:00:5351
[email protected]d90ad9b2009-08-06 00:20:1752
[email protected]363b4d42009-11-03 21:13:3653def runGcl(subcommand):
[email protected]90a685e2010-10-14 02:09:2554 gcl_path = os.path.join(depot_tools_dir_, "gcl")
[email protected]363b4d42009-11-03 21:13:3655 if not os.path.exists(gcl_path):
56 print "WARNING: gcl not found beside drover.py. Using system gcl instead..."
57 gcl_path = 'gcl'
58
59 command = "%s %s" % (gcl_path, subcommand)
60 return os.system(command)
61
[email protected]d90ad9b2009-08-06 00:20:1762def gclUpload(revision, author):
[email protected]363b4d42009-11-03 21:13:3663 command = ("upload " + str(revision) +
[email protected]8010f072010-07-20 21:08:5564 " --send_mail --no_presubmit --reviewers=" + author)
[email protected]363b4d42009-11-03 21:13:3665 return runGcl(command)
[email protected]817ba612009-09-23 21:00:5366
[email protected]b97010c2009-10-15 23:50:5667def getSVNInfo(url, revision):
[email protected]a4899042009-10-16 19:42:4168 info = {}
[email protected]5953f3f2013-03-18 19:35:1069 svn_info = subprocess2.capture(
70 ['svn', 'info', '--non-interactive', '%s@%s' % (url, revision)],
71 stderr=subprocess2.VOID).splitlines()
72 for line in svn_info:
73 match = re.search(r"(.*?):(.*)", line)
74 if match:
75 info[match.group(1).strip()] = match.group(2).strip()
[email protected]a4899042009-10-16 19:42:4176 return info
[email protected]d90ad9b2009-08-06 00:20:1777
[email protected]0b9e6932010-02-22 16:49:0678def isSVNDirty():
[email protected]98385dd2011-04-05 14:48:3279 svn_status = subprocess2.check_output(['svn', 'status']).splitlines()
[email protected]0b9e6932010-02-22 16:49:0680 for line in svn_status:
81 match = re.search(r"^[^X?]", line)
82 if match:
83 return True
84
85 return False
86
[email protected]d90ad9b2009-08-06 00:20:1787def getAuthor(url, revision):
[email protected]b97010c2009-10-15 23:50:5688 info = getSVNInfo(url, revision)
[email protected]b97010c2009-10-15 23:50:5689 if (info.has_key("Last Changed Author")):
90 return info["Last Changed Author"]
[email protected]d90ad9b2009-08-06 00:20:1791 return None
92
[email protected]b97010c2009-10-15 23:50:5693def isSVNFile(url, revision):
94 info = getSVNInfo(url, revision)
[email protected]b97010c2009-10-15 23:50:5695 if (info.has_key("Node Kind")):
[email protected]a4899042009-10-16 19:42:4196 if (info["Node Kind"] == "file"):
97 return True
98 return False
[email protected]d90ad9b2009-08-06 00:20:1799
[email protected]b97010c2009-10-15 23:50:56100def isSVNDirectory(url, revision):
101 info = getSVNInfo(url, revision)
[email protected]b97010c2009-10-15 23:50:56102 if (info.has_key("Node Kind")):
[email protected]a4899042009-10-16 19:42:41103 if (info["Node Kind"] == "directory"):
104 return True
105 return False
106
[email protected]0b9e6932010-02-22 16:49:06107def inCheckoutRoot(path):
108 info = getSVNInfo(path, "HEAD")
109 if (not info.has_key("Repository Root")):
110 return False
[email protected]6e29d572010-06-04 17:32:20111 repo_root = info["Repository Root"]
[email protected]0b9e6932010-02-22 16:49:06112 info = getSVNInfo(os.path.dirname(os.path.abspath(path)), "HEAD")
113 if (info.get("Repository Root", None) != repo_root):
114 return True
115 return False
116
[email protected]d90ad9b2009-08-06 00:20:17117def getRevisionLog(url, revision):
[email protected]a4899042009-10-16 19:42:41118 """Takes an svn url and gets the associated revision."""
[email protected]98385dd2011-04-05 14:48:32119 svn_log = subprocess2.check_output(
[email protected]403a0f92011-04-29 18:50:32120 ['svn', 'log', url, '-r', str(revision)],
121 universal_newlines=True).splitlines(True)
122 # Don't include the header lines and the trailing "---..." line.
123 return ''.join(svn_log[3:-1])
[email protected]d90ad9b2009-08-06 00:20:17124
[email protected]004d2ef2009-12-17 23:49:04125def getSVNVersionInfo():
126 """Extract version information from SVN"""
[email protected]98385dd2011-04-05 14:48:32127 svn_info = subprocess2.check_output(['svn', '--version']).splitlines()
[email protected]004d2ef2009-12-17 23:49:04128 info = {}
129 for line in svn_info:
[email protected]450e50d2011-02-18 16:45:29130 match = re.search(r"svn, version ((\d+)\.(\d+)\.(\d+))", line)
[email protected]004d2ef2009-12-17 23:49:04131 if match:
[email protected]6e29d572010-06-04 17:32:20132 info['version'] = match.group(1)
133 info['major'] = int(match.group(2))
134 info['minor'] = int(match.group(3))
135 info['patch'] = int(match.group(4))
[email protected]6e29d572010-06-04 17:32:20136 return info
[email protected]004d2ef2009-12-17 23:49:04137
138 return None
139
140def isMinimumSVNVersion(major, minor, patch=0):
141 """Test for minimum SVN version"""
142 return _isMinimumSVNVersion(getSVNVersionInfo(), major, minor, patch)
143
144def _isMinimumSVNVersion(version, major, minor, patch=0):
145 """Test for minimum SVN version, internal method"""
146 if not version:
147 return False
148
149 if (version['major'] > major):
150 return True
151 elif (version['major'] < major):
152 return False
153
154 if (version['minor'] > minor):
155 return True
156 elif (version['minor'] < minor):
157 return False
158
159 if (version['patch'] >= patch):
160 return True
161 else:
162 return False
163
[email protected]0689a622013-01-29 23:13:29164def checkoutRevision(url, revision, branch_url, revert=False, pop=True):
[email protected]a4899042009-10-16 19:42:41165 files_info = getFileInfo(url, revision)
[email protected]b97010c2009-10-15 23:50:56166 paths = getBestMergePaths2(files_info, revision)
167 export_map = getBestExportPathsMap2(files_info, revision)
[email protected]a4899042009-10-16 19:42:41168
[email protected]b97010c2009-10-15 23:50:56169 command = 'svn checkout -N ' + branch_url
170 print command
171 os.system(command)
[email protected]817ba612009-09-23 21:00:53172
[email protected]7aae8fb2010-09-08 17:46:40173 match = re.search(r"^[a-z]+://.*/(.*)", branch_url)
[email protected]a4899042009-10-16 19:42:41174
[email protected]b97010c2009-10-15 23:50:56175 if match:
176 os.chdir(match.group(1))
[email protected]d90ad9b2009-08-06 00:20:17177
[email protected]a4899042009-10-16 19:42:41178 # This line is extremely important due to the way svn behaves in the
179 # set-depths action. If parents aren't handled before children, the child
180 # directories get clobbered and the merge step fails.
[email protected]d90ad9b2009-08-06 00:20:17181 paths.sort()
[email protected]817ba612009-09-23 21:00:53182
[email protected]6e29d572010-06-04 17:32:20183 # Checkout the directories that already exist
[email protected]b97010c2009-10-15 23:50:56184 for path in paths:
[email protected]6e29d572010-06-04 17:32:20185 if (export_map.has_key(path) and not revert):
186 print "Exclude new directory " + path
187 continue
188 subpaths = path.split('/')
[email protected]0689a622013-01-29 23:13:29189 #In the normal case, where no url override is specified and it's just
190 # chromium source, it's necessary to remove the 'trunk' from the filepath,
191 # since in the checkout we include 'trunk' or 'branch/\d+'.
192 #
193 # However, when a url is specified we want to preserve that because it's
194 # a part of the filepath and necessary for path operations on svn (because
195 # frankly, we are checking out the correct top level, and not hacking it).
196 if pop:
197 subpaths.pop(0)
[email protected]6e29d572010-06-04 17:32:20198 base = ''
199 for subpath in subpaths:
200 base += '/' + subpath
201 # This logic ensures that you don't empty out any directories
202 if not os.path.exists("." + base):
203 command = ('svn update --depth empty ' + "." + base)
204 print command
205 os.system(command)
[email protected]a4899042009-10-16 19:42:41206
[email protected]b97010c2009-10-15 23:50:56207 if (revert):
208 files = getAllFilesInRevision(files_info)
209 else:
210 files = getExistingFilesInRevision(files_info)
[email protected]6e29d572010-06-04 17:32:20211
212 for f in files:
213 # Prevent the tool from clobbering the src directory
214 if (f == ""):
[email protected]d90ad9b2009-08-06 00:20:17215 continue
[email protected]6e29d572010-06-04 17:32:20216 command = ('svn up ".' + f + '"')
[email protected]d90ad9b2009-08-06 00:20:17217 print command
218 os.system(command)
[email protected]a4899042009-10-16 19:42:41219
[email protected]b97010c2009-10-15 23:50:56220def mergeRevision(url, revision):
[email protected]d90ad9b2009-08-06 00:20:17221 paths = getBestMergePaths(url, revision)
[email protected]b97010c2009-10-15 23:50:56222 export_map = getBestExportPathsMap(url, revision)
[email protected]a4899042009-10-16 19:42:41223
[email protected]d90ad9b2009-08-06 00:20:17224 for path in paths:
[email protected]b97010c2009-10-15 23:50:56225 if export_map.has_key(path):
226 continue
[email protected]d90ad9b2009-08-06 00:20:17227 command = ('svn merge -N -r ' + str(revision-1) + ":" + str(revision) + " ")
[email protected]94ed5022009-10-21 20:28:53228 command += " --ignore-ancestry "
[email protected]f5a11632009-12-18 00:07:37229 command += " -x --ignore-eol-style "
[email protected]94ed5022009-10-21 20:28:53230 command += url + path + "@" + str(revision) + " ." + path
[email protected]a4899042009-10-16 19:42:41231
[email protected]d90ad9b2009-08-06 00:20:17232 print command
233 os.system(command)
234
[email protected]b97010c2009-10-15 23:50:56235def exportRevision(url, revision):
236 paths = getBestExportPathsMap(url, revision).keys()
[email protected]b97010c2009-10-15 23:50:56237 paths.sort()
[email protected]a4899042009-10-16 19:42:41238
[email protected]b97010c2009-10-15 23:50:56239 for path in paths:
[email protected]a4899042009-10-16 19:42:41240 command = ('svn export -N ' + url + path + "@" + str(revision) + " ." +
241 path)
[email protected]b97010c2009-10-15 23:50:56242 print command
243 os.system(command)
[email protected]a4899042009-10-16 19:42:41244
245 command = 'svn add .' + path
[email protected]6e29d572010-06-04 17:32:20246 print command
[email protected]b97010c2009-10-15 23:50:56247 os.system(command)
248
249def deleteRevision(url, revision):
250 paths = getBestDeletePathsMap(url, revision).keys()
251 paths.sort()
252 paths.reverse()
[email protected]a4899042009-10-16 19:42:41253
[email protected]b97010c2009-10-15 23:50:56254 for path in paths:
[email protected]a4899042009-10-16 19:42:41255 command = "svn delete ." + path
[email protected]b97010c2009-10-15 23:50:56256 print command
257 os.system(command)
258
[email protected]b97010c2009-10-15 23:50:56259def revertExportRevision(url, revision):
260 paths = getBestExportPathsMap(url, revision).keys()
261 paths.sort()
262 paths.reverse()
[email protected]a4899042009-10-16 19:42:41263
[email protected]b97010c2009-10-15 23:50:56264 for path in paths:
[email protected]a4899042009-10-16 19:42:41265 command = "svn delete ." + path
[email protected]b97010c2009-10-15 23:50:56266 print command
267 os.system(command)
[email protected]817ba612009-09-23 21:00:53268
[email protected]d90ad9b2009-08-06 00:20:17269def revertRevision(url, revision):
[email protected]d2046a82013-08-22 20:43:37270 command = ('svn merge --ignore-ancestry -c -%d %s .' % (revision, url))
271 print command
272 os.system(command)
[email protected]a4899042009-10-16 19:42:41273
[email protected]b97010c2009-10-15 23:50:56274def getFileInfo(url, revision):
[email protected]3a292682010-08-23 18:54:55275 global files_info_
[email protected]a4899042009-10-16 19:42:41276
[email protected]b97010c2009-10-15 23:50:56277 if (files_info_ != None):
278 return files_info_
[email protected]d90ad9b2009-08-06 00:20:17279
[email protected]98385dd2011-04-05 14:48:32280 svn_log = subprocess2.check_output(
281 ['svn', 'log', url, '-r', str(revision), '-v']).splitlines()
[email protected]a4899042009-10-16 19:42:41282
283 info = []
[email protected]b97010c2009-10-15 23:50:56284 for line in svn_log:
[email protected]a4899042009-10-16 19:42:41285 # A workaround to dump the (from .*) stuff, regex not so friendly in the 2nd
286 # pass...
[email protected]b97010c2009-10-15 23:50:56287 match = re.search(r"(.*) \(from.*\)", line)
288 if match:
289 line = match.group(1)
290 match = re.search(file_pattern_, line)
291 if match:
[email protected]a4899042009-10-16 19:42:41292 info.append([match.group(1).strip(), match.group(2).strip(),
293 match.group(3).strip(),match.group(4).strip()])
[email protected]b97010c2009-10-15 23:50:56294
[email protected]a4899042009-10-16 19:42:41295 files_info_ = info
[email protected]9ce98222009-10-19 20:24:17296 return info
[email protected]817ba612009-09-23 21:00:53297
[email protected]d90ad9b2009-08-06 00:20:17298def getBestMergePaths(url, revision):
[email protected]a4899042009-10-16 19:42:41299 """Takes an svn url and gets the associated revision."""
[email protected]b97010c2009-10-15 23:50:56300 return getBestMergePaths2(getFileInfo(url, revision), revision)
301
302def getBestMergePaths2(files_info, revision):
[email protected]a4899042009-10-16 19:42:41303 """Takes an svn url and gets the associated revision."""
[email protected]6e29d572010-06-04 17:32:20304 return list(set([f[2] for f in files_info]))
[email protected]d90ad9b2009-08-06 00:20:17305
[email protected]b97010c2009-10-15 23:50:56306def getBestExportPathsMap(url, revision):
307 return getBestExportPathsMap2(getFileInfo(url, revision), revision)
[email protected]a4899042009-10-16 19:42:41308
[email protected]b97010c2009-10-15 23:50:56309def getBestExportPathsMap2(files_info, revision):
[email protected]a4899042009-10-16 19:42:41310 """Takes an svn url and gets the associated revision."""
[email protected]b97010c2009-10-15 23:50:56311 global export_map_
[email protected]a4899042009-10-16 19:42:41312
[email protected]b97010c2009-10-15 23:50:56313 if export_map_:
314 return export_map_
[email protected]817ba612009-09-23 21:00:53315
[email protected]6e29d572010-06-04 17:32:20316 result = {}
[email protected]b97010c2009-10-15 23:50:56317 for file_info in files_info:
318 if (file_info[0] == "A"):
[email protected]b2ab0692009-11-03 23:35:27319 if(isSVNDirectory("svn://svn.chromium.org/chrome/" + file_info[1],
320 revision)):
[email protected]6e29d572010-06-04 17:32:20321 result[file_info[2] + "/" + file_info[3]] = ""
[email protected]d90ad9b2009-08-06 00:20:17322
[email protected]6e29d572010-06-04 17:32:20323 export_map_ = result
324 return result
[email protected]d90ad9b2009-08-06 00:20:17325
[email protected]b97010c2009-10-15 23:50:56326def getBestDeletePathsMap(url, revision):
[email protected]a4899042009-10-16 19:42:41327 return getBestDeletePathsMap2(getFileInfo(url, revision), revision)
[email protected]817ba612009-09-23 21:00:53328
[email protected]b97010c2009-10-15 23:50:56329def getBestDeletePathsMap2(files_info, revision):
[email protected]a4899042009-10-16 19:42:41330 """Takes an svn url and gets the associated revision."""
[email protected]b97010c2009-10-15 23:50:56331 global delete_map_
[email protected]a4899042009-10-16 19:42:41332
[email protected]b97010c2009-10-15 23:50:56333 if delete_map_:
334 return delete_map_
[email protected]d90ad9b2009-08-06 00:20:17335
[email protected]6e29d572010-06-04 17:32:20336 result = {}
[email protected]b97010c2009-10-15 23:50:56337 for file_info in files_info:
338 if (file_info[0] == "D"):
[email protected]b2ab0692009-11-03 23:35:27339 if(isSVNDirectory("svn://svn.chromium.org/chrome/" + file_info[1],
340 revision)):
[email protected]6e29d572010-06-04 17:32:20341 result[file_info[2] + "/" + file_info[3]] = ""
[email protected]d90ad9b2009-08-06 00:20:17342
[email protected]6e29d572010-06-04 17:32:20343 delete_map_ = result
344 return result
345
[email protected]a4899042009-10-16 19:42:41346
[email protected]b97010c2009-10-15 23:50:56347def getExistingFilesInRevision(files_info):
[email protected]a4899042009-10-16 19:42:41348 """Checks for existing files in the revision.
[email protected]6e29d572010-06-04 17:32:20349
[email protected]a4899042009-10-16 19:42:41350 Anything that's A will require special treatment (either a merge or an
351 export + add)
[email protected]b97010c2009-10-15 23:50:56352 """
[email protected]6e29d572010-06-04 17:32:20353 return ['%s/%s' % (f[2], f[3]) for f in files_info if f[0] != 'A']
[email protected]817ba612009-09-23 21:00:53354
[email protected]817ba612009-09-23 21:00:53355
[email protected]b97010c2009-10-15 23:50:56356def getAllFilesInRevision(files_info):
[email protected]a4899042009-10-16 19:42:41357 """Checks for existing files in the revision.
358
359 Anything that's A will require special treatment (either a merge or an
360 export + add)
[email protected]b97010c2009-10-15 23:50:56361 """
[email protected]6e29d572010-06-04 17:32:20362 return ['%s/%s' % (f[2], f[3]) for f in files_info]
[email protected]817ba612009-09-23 21:00:53363
[email protected]b63a5c52011-11-11 22:26:22364
[email protected]08145472012-11-30 23:09:12365def getSVNAuthInfo(folder=None):
366 """Fetches SVN authorization information in the subversion auth folder and
367 returns it as a dictionary of dictionaries."""
368 if not folder:
369 if sys.platform == 'win32':
370 folder = '%%APPDATA%\\Subversion\\auth'
371 else:
372 folder = '~/.subversion/auth'
373 folder = os.path.expandvars(os.path.expanduser(folder))
374 svn_simple_folder = os.path.join(folder, 'svn.simple')
375 results = {}
376 try:
377 for auth_file in os.listdir(svn_simple_folder):
378 # Read the SVN auth file, convert it into a dictionary, and store it.
[email protected]dc58a972016-02-11 08:23:47379 results[auth_file] = dict(re.findall(r'K [0-9]+\n(.*)\nV [0-9]+\n(.*)\n',
[email protected]08145472012-11-30 23:09:12380 open(os.path.join(svn_simple_folder, auth_file)).read()))
381 except Exception as _:
382 pass
383 return results
384
385
386def getCurrentSVNUsers(url):
387 """Tries to fetch the current SVN in the current checkout by scanning the
388 SVN authorization folder for a match with the current SVN URL."""
389 netloc = urlparse.urlparse(url)[1]
390 auth_infos = getSVNAuthInfo()
391 results = []
392 for _, auth_info in auth_infos.iteritems():
[email protected]dc58a972016-02-11 08:23:47393 if ('svn:realmstring' in auth_info
[email protected]08145472012-11-30 23:09:12394 and netloc in auth_info['svn:realmstring']):
395 username = auth_info['username']
396 results.append(username)
397 if 'google.com' in username:
398 results.append(username.replace('google.com', 'chromium.org'))
399 return results
400
401
[email protected]d90ad9b2009-08-06 00:20:17402def prompt(question):
[email protected]6e29d572010-06-04 17:32:20403 while True:
[email protected]c4e7bb32009-11-20 23:58:27404 print question + " [y|n]:",
[email protected]a4899042009-10-16 19:42:41405 answer = sys.stdin.readline()
406 if answer.lower().startswith('n'):
[email protected]d90ad9b2009-08-06 00:20:17407 return False
[email protected]a4899042009-10-16 19:42:41408 elif answer.lower().startswith('y'):
[email protected]d90ad9b2009-08-06 00:20:17409 return True
[email protected]6e29d572010-06-04 17:32:20410
[email protected]817ba612009-09-23 21:00:53411
[email protected]b97010c2009-10-15 23:50:56412def text_prompt(question, default):
413 print question + " [" + default + "]:"
[email protected]a4899042009-10-16 19:42:41414 answer = sys.stdin.readline()
415 if answer.strip() == "":
[email protected]b97010c2009-10-15 23:50:56416 return default
[email protected]a4899042009-10-16 19:42:41417 return answer
418
[email protected]6e29d572010-06-04 17:32:20419
420def drover(options, args):
[email protected]5ebf1632009-10-23 22:33:47421 revision = options.revert or options.merge
422
423 # Initialize some variables used below. They can be overwritten by
424 # the drover.properties file.
[email protected]b2ab0692009-11-03 23:35:27425 BASE_URL = "svn://svn.chromium.org/chrome"
[email protected]87e634f2013-04-04 22:12:17426 REVERT_ALT_URLS = ['svn://svn.chromium.org/blink',
427 'svn://svn.chromium.org/chrome-internal',
[email protected]5953f3f2013-03-18 19:35:10428 'svn://svn.chromium.org/native_client']
[email protected]d90ad9b2009-08-06 00:20:17429 TRUNK_URL = BASE_URL + "/trunk/src"
[email protected]b97010c2009-10-15 23:50:56430 BRANCH_URL = BASE_URL + "/branches/$branch/src"
[email protected]b97010c2009-10-15 23:50:56431 SKIP_CHECK_WORKING = True
432 PROMPT_FOR_AUTHOR = False
[email protected]0ee2ef22013-06-06 18:52:03433 NO_ALT_URLS = options.no_alt_urls
[email protected]a4899042009-10-16 19:42:41434
[email protected]5ebf1632009-10-23 22:33:47435 DEFAULT_WORKING = "drover_" + str(revision)
436 if options.branch:
437 DEFAULT_WORKING += ("_" + options.branch)
438
[email protected]6e29d572010-06-04 17:32:20439 if not isMinimumSVNVersion(1, 5):
[email protected]004d2ef2009-12-17 23:49:04440 print "You need to use at least SVN version 1.5.x"
[email protected]6e29d572010-06-04 17:32:20441 return 1
[email protected]004d2ef2009-12-17 23:49:04442
[email protected]a4899042009-10-16 19:42:41443 # Override the default properties if there is a drover.properties file.
[email protected]b97010c2009-10-15 23:50:56444 global file_pattern_
445 if os.path.exists("drover.properties"):
[email protected]c100b0f2013-04-05 22:43:04446 print 'Using options from %s' % os.path.join(
447 os.getcwd(), 'drover.properties')
[email protected]8ea22292010-09-13 18:56:27448 FILE_PATTERN = file_pattern_
[email protected]1309f812010-09-14 18:59:28449 f = open("drover.properties")
450 exec(f)
451 f.close()
[email protected]7d1ce062010-09-11 01:25:46452 if FILE_PATTERN:
453 file_pattern_ = FILE_PATTERN
[email protected]5ff58672013-06-07 00:06:22454 NO_ALT_URLS = True
[email protected]d90ad9b2009-08-06 00:20:17455
[email protected]9ce98222009-10-19 20:24:17456 if options.revert and options.branch:
[email protected]5953f3f2013-03-18 19:35:10457 print 'Note: --branch is usually not needed for reverts.'
[email protected]2d6af5f2010-01-22 18:18:51458 url = BRANCH_URL.replace("$branch", options.branch)
459 elif options.merge and options.sbranch:
460 url = BRANCH_URL.replace("$branch", options.sbranch)
[email protected]5953f3f2013-03-18 19:35:10461 elif options.revert:
462 url = options.url or BASE_URL
[email protected]17f70a52013-02-11 16:07:18463 file_pattern_ = r"[ ]+([MADUC])[ ]+((/.*)/(.*))"
[email protected]d90ad9b2009-08-06 00:20:17464 else:
465 url = TRUNK_URL
[email protected]a4899042009-10-16 19:42:41466
[email protected]864fbdd2009-10-23 20:11:40467 working = options.workdir or DEFAULT_WORKING
[email protected]a4899042009-10-16 19:42:41468
[email protected]0b9e6932010-02-22 16:49:06469 if options.local:
470 working = os.getcwd()
471 if not inCheckoutRoot(working):
472 print "'%s' appears not to be the root of a working copy" % working
[email protected]6e29d572010-06-04 17:32:20473 return 1
[email protected]ebd0b542010-07-30 22:26:21474 if (isSVNDirty() and not
475 prompt("Working copy contains uncommitted files. Continue?")):
[email protected]6e29d572010-06-04 17:32:20476 return 1
[email protected]0b9e6932010-02-22 16:49:06477
[email protected]0ee2ef22013-06-06 18:52:03478 if options.revert and not NO_ALT_URLS and not options.url:
[email protected]5953f3f2013-03-18 19:35:10479 for cur_url in [url] + REVERT_ALT_URLS:
480 try:
481 commit_date_str = getSVNInfo(
482 cur_url, options.revert).get('Last Changed Date', 'x').split()[0]
483 commit_date = datetime.datetime.strptime(commit_date_str, '%Y-%m-%d')
[email protected]c100b0f2013-04-05 22:43:04484 if (datetime.datetime.now() - commit_date).days < 180:
[email protected]5953f3f2013-03-18 19:35:10485 if cur_url != url:
486 print 'Guessing svn repo: %s.' % cur_url,
487 print 'Use --no-alt-urls to disable heuristic.'
488 url = cur_url
489 break
490 except ValueError:
491 pass
[email protected]d90ad9b2009-08-06 00:20:17492 command = 'svn log ' + url + " -r "+str(revision) + " -v"
493 os.system(command)
[email protected]a4899042009-10-16 19:42:41494
[email protected]9ce98222009-10-19 20:24:17495 if not (options.revertbot or prompt("Is this the correct revision?")):
[email protected]6e29d572010-06-04 17:32:20496 return 0
[email protected]a4899042009-10-16 19:42:41497
[email protected]0b9e6932010-02-22 16:49:06498 if (os.path.exists(working)) and not options.local:
[email protected]9ce98222009-10-19 20:24:17499 if not (options.revertbot or SKIP_CHECK_WORKING or
500 prompt("Working directory: '%s' already exists, clobber?" % working)):
[email protected]6e29d572010-06-04 17:32:20501 return 0
[email protected]98385dd2011-04-05 14:48:32502 gclient_utils.rmtree(working)
[email protected]a4899042009-10-16 19:42:41503
[email protected]0b9e6932010-02-22 16:49:06504 if not options.local:
505 os.makedirs(working)
506 os.chdir(working)
[email protected]a4899042009-10-16 19:42:41507
[email protected]9ce98222009-10-19 20:24:17508 if options.merge:
509 action = "Merge"
[email protected]0b9e6932010-02-22 16:49:06510 if not options.local:
511 branch_url = BRANCH_URL.replace("$branch", options.branch)
512 # Checkout everything but stuff that got added into a new dir
513 checkoutRevision(url, revision, branch_url)
[email protected]9ce98222009-10-19 20:24:17514 # Merge everything that changed
515 mergeRevision(url, revision)
516 # "Export" files that were added from the source and add them to branch
517 exportRevision(url, revision)
518 # Delete directories that were deleted (file deletes are handled in the
519 # merge).
520 deleteRevision(url, revision)
521 elif options.revert:
522 action = "Revert"
[email protected]0689a622013-01-29 23:13:29523 pop_em = not options.url
524 checkoutRevision(url, revision, url, True, pop_em)
[email protected]9ce98222009-10-19 20:24:17525 revertRevision(url, revision)
526 revertExportRevision(url, revision)
[email protected]a4899042009-10-16 19:42:41527
528 # Check the base url so we actually find the author who made the change
[email protected]638044d2010-01-25 18:52:41529 if options.auditor:
530 author = options.auditor
531 else:
532 author = getAuthor(url, revision)
533 if not author:
534 author = getAuthor(TRUNK_URL, revision)
[email protected]a4899042009-10-16 19:42:41535
[email protected]08145472012-11-30 23:09:12536 # Check that the author of the CL is different than the user making
537 # the revert. If they're the same, then we'll want to prompt the user
538 # for a different reviewer to TBR.
539 current_users = getCurrentSVNUsers(BASE_URL)
540 is_self_revert = options.revert and author in current_users
541
[email protected]d90ad9b2009-08-06 00:20:17542 filename = str(revision)+".txt"
543 out = open(filename,"w")
[email protected]da4bfad2013-03-11 18:48:53544 drover_title = '%s %s' % (action, revision)
545 revision_log = getRevisionLog(url, revision).splitlines()
546 if revision_log:
547 commit_title = revision_log[0]
548 # Limit title to 68 chars so git log --oneline is <80 chars.
549 max_commit_title = 68 - (len(drover_title) + 3)
550 if len(commit_title) > max_commit_title:
551 commit_title = commit_title[:max_commit_title-3] + '...'
552 drover_title += ' "%s"' % commit_title
553 out.write(drover_title + '\n\n')
554 for line in revision_log:
[email protected]08145472012-11-30 23:09:12555 out.write('> %s\n' % line)
[email protected]da4bfad2013-03-11 18:48:53556 if author:
[email protected]0c6b52b2010-06-02 21:56:22557 out.write("\nTBR=" + author)
[email protected]d90ad9b2009-08-06 00:20:17558 out.close()
[email protected]a4899042009-10-16 19:42:41559
[email protected]363b4d42009-11-03 21:13:36560 change_cmd = 'change ' + str(revision) + " " + filename
[email protected]9ce98222009-10-19 20:24:17561 if options.revertbot:
[email protected]0e0436a2011-10-25 13:32:41562 if sys.platform == 'win32':
563 os.environ['SVN_EDITOR'] = 'cmd.exe /c exit'
564 else:
565 os.environ['SVN_EDITOR'] = 'true'
[email protected]363b4d42009-11-03 21:13:36566 runGcl(change_cmd)
[email protected]d90ad9b2009-08-06 00:20:17567 os.unlink(filename)
[email protected]0b9e6932010-02-22 16:49:06568
569 if options.local:
[email protected]6e29d572010-06-04 17:32:20570 return 0
[email protected]0b9e6932010-02-22 16:49:06571
[email protected]d90ad9b2009-08-06 00:20:17572 print author
573 print revision
[email protected]a4899042009-10-16 19:42:41574 print ("gcl upload " + str(revision) +
[email protected]8010f072010-07-20 21:08:55575 " --send_mail --no_presubmit --reviewers=" + author)
[email protected]d90ad9b2009-08-06 00:20:17576
[email protected]9ce98222009-10-19 20:24:17577 if options.revertbot or prompt("Would you like to upload?"):
[email protected]08145472012-11-30 23:09:12578 if PROMPT_FOR_AUTHOR or is_self_revert:
[email protected]9ce98222009-10-19 20:24:17579 author = text_prompt("Enter new author or press enter to accept default",
580 author)
581 if options.revertbot and options.revertbot_reviewers:
582 author += ","
583 author += options.revertbot_reviewers
[email protected]d90ad9b2009-08-06 00:20:17584 gclUpload(revision, author)
585 else:
586 print "Deleting the changelist."
[email protected]9ce98222009-10-19 20:24:17587 print "gcl delete " + str(revision)
[email protected]363b4d42009-11-03 21:13:36588 runGcl("delete " + str(revision))
[email protected]6e29d572010-06-04 17:32:20589 return 0
[email protected]d90ad9b2009-08-06 00:20:17590
[email protected]9ce98222009-10-19 20:24:17591 # We commit if the reverbot is set to commit automatically, or if this is
592 # not the revertbot and the user agrees.
593 if options.revertbot_commit or (not options.revertbot and
594 prompt("Would you like to commit?")):
595 print "gcl commit " + str(revision) + " --no_presubmit --force"
[email protected]6e29d572010-06-04 17:32:20596 return runGcl("commit " + str(revision) + " --no_presubmit --force")
[email protected]d90ad9b2009-08-06 00:20:17597 else:
[email protected]6e29d572010-06-04 17:32:20598 return 0
[email protected]a4899042009-10-16 19:42:41599
[email protected]6e29d572010-06-04 17:32:20600
601def main():
[email protected]9ce98222009-10-19 20:24:17602 option_parser = optparse.OptionParser(usage=USAGE % {"app": sys.argv[0]})
603 option_parser.add_option('-m', '--merge', type="int",
604 help='Revision to merge from trunk to branch')
605 option_parser.add_option('-b', '--branch',
606 help='Branch to revert or merge from')
[email protected]0b9e6932010-02-22 16:49:06607 option_parser.add_option('-l', '--local', action='store_true',
608 help='Local working copy to merge to')
[email protected]2d6af5f2010-01-22 18:18:51609 option_parser.add_option('-s', '--sbranch',
610 help='Source branch for merge')
[email protected]9ce98222009-10-19 20:24:17611 option_parser.add_option('-r', '--revert', type="int",
612 help='Revision to revert')
[email protected]864fbdd2009-10-23 20:11:40613 option_parser.add_option('-w', '--workdir',
614 help='subdir to use for the revert')
[email protected]0689a622013-01-29 23:13:29615 option_parser.add_option('-u', '--url',
616 help='svn url to use for the revert')
[email protected]638044d2010-01-25 18:52:41617 option_parser.add_option('-a', '--auditor',
[email protected]6e29d572010-06-04 17:32:20618 help='overrides the author for reviewer')
[email protected]5953f3f2013-03-18 19:35:10619 option_parser.add_option('--revertbot', action='store_true',
[email protected]9ce98222009-10-19 20:24:17620 default=False)
[email protected]5953f3f2013-03-18 19:35:10621 option_parser.add_option('--no-alt-urls', action='store_true',
622 help='Disable heuristics used to determine svn url')
623 option_parser.add_option('--revertbot-commit', action='store_true',
[email protected]9ce98222009-10-19 20:24:17624 default=False)
[email protected]5953f3f2013-03-18 19:35:10625 option_parser.add_option('--revertbot-reviewers')
[email protected]9ce98222009-10-19 20:24:17626 options, args = option_parser.parse_args()
627
628 if not options.merge and not options.revert:
629 option_parser.error("You need at least --merge or --revert")
[email protected]6e29d572010-06-04 17:32:20630 return 1
[email protected]9ce98222009-10-19 20:24:17631
[email protected]49fb93c2013-10-07 21:36:19632 if options.merge and not (options.branch or options.local):
633 option_parser.error("--merge requires --branch or --local")
[email protected]6e29d572010-06-04 17:32:20634 return 1
[email protected]0b9e6932010-02-22 16:49:06635
[email protected]49fb93c2013-10-07 21:36:19636 if options.local and (options.revert or options.branch):
637 option_parser.error("--local cannot be used with --revert or --branch")
[email protected]6e29d572010-06-04 17:32:20638 return 1
[email protected]9ce98222009-10-19 20:24:17639
[email protected]6e29d572010-06-04 17:32:20640 return drover(options, args)
641
642
643if __name__ == "__main__":
[email protected]013731e2015-02-26 18:28:43644 try:
645 sys.exit(main())
646 except KeyboardInterrupt:
647 sys.stderr.write('interrupted\n')
648 sys.exit(1)