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

Side by Side Diff: tools/roll_deps.py

Issue 123523003: DEPS roll script (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: changes Created 6 years, 11 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | 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/python2
2
3 # Copyright 2014 Google Inc.
4 #
5 # Use of this source code is governed by a BSD-style license that can be
6 # found in the LICENSE file.
7
8
9 """
10 Skia's Chromium DEPS roll script
11
12 This script:
13 - searches through the last N Skia git commits to find out the hash that is
14 associated with the SVN revision number.
15 - creates a new branch in the Chromium tree, modifies the DEPS file to
16 point at the given Skia commit, commits, uploads to Rietveld, and
17 deletes the local copy of the branch.
18 - creates a whitespace-only commit and uploads that to to Rietveld.
19 - returns the Chromium tree to its previous state.
20
21 Usage:
22 %prog -c CHROMIUM_PATH -r REVISION [OPTIONAL_OPTIONS]"""
23
24
25 import optparse
26 import os
27 import re
28 import subprocess
29 import shutil
30 import sys
31 import tempfile
32
33
34 def test_git(git):
35 """Test to see if the git executable can be run.
36
37 Args:
38 git: git executable.
39 Raises:
40 OSError on failure
41
42 """
43 with open(os.devnull, "w") as devnull:
44 subprocess.call([git, '--version'], stdout=devnull)
45
46
47 def strip_output(*args, **kwargs):
48 """Wrap subprocess.check_output and str.strip()
49
50 Pass the given arguments into subprocess.check_output() and return
51 the results, after stripping any excess whitespace.
52
53 Returns:
54 a string without leading or trailing whitespace.
borenet 2014/01/06 14:06:46 This may be obvious, but I might include "output o
hal.canary 2014/01/06 18:27:57 Done.
55 """
56 return str(subprocess.check_output(*args, **kwargs)).strip()
57
58
59 def find_hash_from_revision(revision, search_depth, git):
60 """Finds the hash associated with a revision.
61
62 Searches through the last search_depth commits to find out the hash
63 that is associated with the SVN revision number.
64
65 Args:
66 revision: (int) SVN revision number.
67 search_depth: (int) Number of revisions to limit the search to.
68 git: git executable.
69
70 Returns:
71 Hash as a string.
72
73 Raises an exception on failure
74 """
75 skia_url = 'https://skia.googlesource.com/skia.git'
borenet 2014/01/06 14:06:46 Would prefer that this be in a global variable.
hal.canary 2014/01/06 18:27:57 Done.
76 temp_dir = tempfile.mkdtemp(prefix='git_skia_tmp_')
77 devnull = open(os.devnull, "w")
78 revision_format = 'http://skia.googlecode.com/svn/trunk@%d'
borenet 2014/01/06 14:06:46 Ditto here.
hal.canary 2014/01/06 18:27:57 Done.
79 revision_regex = re.compile(revision_format % revision)
80 try:
81 subprocess.check_call(
82 [git, 'clone', '--depth=%d' % search_depth, '--single-branch',
83 skia_url, temp_dir], stdout=devnull, stderr=devnull)
borenet 2014/01/06 14:06:46 This isn't as expensive as it used to be, but mayb
hal.canary 2014/01/06 18:27:57 Done.
84 for i in xrange(search_depth):
85 commit = 'origin/master~%d' % i
86 output = subprocess.check_output(
87 [git, 'log', '-n', '1', '--format=format:%B', commit],
88 cwd=temp_dir, stderr=devnull)
89 if revision_regex.search(output):
90 return strip_output(
91 [git, 'log', '-n', '1', '--format=format:%H', commit],
92 cwd=temp_dir)
93 finally:
94 shutil.rmtree(temp_dir)
95 devnull.close()
96 raise Exception('Failed to find revision.')
97
98
99 def fetch_origin(git):
100 """Call git fetch
101
102 Updates origin/master (via git fetch). Leaves local tree alone.
103 Assumes current directory is a git repository.
104
105 Args:
106 git: git executable.
107 Returns:
108 the commit hash of origin/master
109 """
110 with open(os.devnull, "w") as devnull:
111 subprocess.check_call(
112 [git, 'fetch', 'origin'], stdout=devnull, stderr=devnull)
113 return strip_output([git, 'show-ref', 'origin/master', '--hash'])
114
115
116 class GitBranchCLUpload(object):
borenet 2014/01/06 14:06:46 This is a really elegant way of handling this. +1
hal.canary 2014/01/06 18:27:57 Thanks!
117 """
118 This class allows one to create a new branch in a repository based
119 off of origin/master, make changes to the tree inside the
120 with-block, upload that new branch to Rietveld, restore the original
121 tree state, and delete the local copy of the new branch.
122
123 See roll_deps() for an example of use.
124
125 Constructor Args:
126 message: the commit message.
127 file_list: list of files to pass to `git add`.
128 git: git executable.
129 set_brach_name: if not None, the name of the branch to use.
130 If None, then use a temporary branch that will be deleted.
131 """
132 # (Too few public methods) pylint: disable=I0011,R0903
133 def __init__(self, message, file_list, git, set_branch_name):
borenet 2014/01/06 14:06:46 I'm not a big fan of having to pass the file_list
134 self._message = message
135 self._file_list = file_list
136 self._git = git
137 self._issue = None
138 self._branch_name = set_branch_name
139 self._stash = None
140 self._original_branch = None
141
142 def __enter__(self):
143 diff = subprocess.check_output([self._git, 'diff', '--shortstat'])
144 self._stash = (0 != len(diff))
145 if self._stash:
146 subprocess.check_call([self._git, 'stash', 'save'])
147 try:
148 self._original_branch = strip_output(
149 [self._git, 'symbolic-ref', '--short', 'HEAD'])
150 except (subprocess.CalledProcessError,):
151 self._original_branch = strip_output(
152 [self._git, 'rev-parse', 'HEAD'])
153
154 if not self._branch_name:
155 self._branch_name = 'autogenerated_deps_roll_branch'
borenet 2014/01/06 14:06:46 Please put this in a default_branch_name variable
hal.canary 2014/01/06 18:27:57 Done.
156
157 try:
158 subprocess.check_call(
159 [self._git, 'checkout', '-b',
160 self._branch_name, 'origin/master'])
161 except (subprocess.CalledProcessError,):
162 # Branch already exists.
163 subprocess.check_call([self._git, 'checkout', 'master'])
164 subprocess.check_call(
165 [self._git, 'branch', '-D', self._branch_name])
166 subprocess.check_call(
167 [self._git, 'checkout', '-b',
168 self._branch_name, 'origin/master'])
169
170
171 def __exit__(self, etype, value, traceback):
172 for filename in self._file_list:
173 subprocess.check_call([self._git, 'add', filename])
174 subprocess.check_call([self._git, 'commit', '-m', self._message])
borenet 2014/01/06 14:06:46 self._message probably needs to be double quoted a
hal.canary 2014/01/06 18:27:57 Nope. execvp doesn't require that.
175
176 environ = os.environ.copy()
177 if sys.platform != 'win32':
178 environ['GIT_EDITOR'] = ':' # Bypass the editor
179 subprocess.check_call([self._git, 'cl', 'upload'], env=environ)
180
181 self._issue = strip_output([self._git, 'cl', 'issue'])
182
183 # deal with the aftermath of failed executions of this script.
184 if 'autogenerated_deps_roll_branch' == self._original_branch:
185 subprocess.check_call([self._git, 'checkout', 'master'])
186 else:
187 subprocess.check_call(
188 [self._git, 'checkout', self._original_branch])
189
190 if 'autogenerated_deps_roll_branch' == self._branch_name:
191 subprocess.check_call(
192 [self._git, 'branch', '-D', self._branch_name])
193 if self._stash:
194 subprocess.check_call([self._git, 'stash', 'pop'])
195
196 @property
197 def issue(self):
198 """
199 Returns:
200 a string describing the codereview issue, after __exit__
201 has been called.
borenet 2014/01/06 14:06:46 Should this raise an exception if called before __
hal.canary 2014/01/06 18:27:57 I thought about that (which was why I had it in a
202 """
203 return self._issue
204
205
206 def change_skia_deps(revision, hashval, depspath):
207 """Update the DEPS file.
208
209 Modify the skia_revision and skia_hash entries in the given DEPS file.
210
211 Args:
212 revision: (int) Skia SVN revision.
213 hashval: (string) Skia Git hash.
214 depspath: (string) path to DEPS file.
215 """
216 temp_file = tempfile.NamedTemporaryFile(delete=False,
217 prefix='skia_DEPS_ROLL_tmp_')
218 try:
219 deps_regex_rev = re.compile('"skia_revision": "[0-9]*",')
220 deps_regex_hash = re.compile('"skia_hash": "[0-9a-f]*",')
221
222 deps_regex_rev_repl = '"skia_revision": "%d",' % revision
223 deps_regex_hash_repl = '"skia_hash": "%s",' % hashval
224
225 with open(depspath, 'r') as input_stream:
226 for line in input_stream:
227 line = deps_regex_rev.sub(deps_regex_rev_repl, line)
228 line = deps_regex_hash.sub(deps_regex_hash_repl, line)
229 temp_file.write(line)
230 finally:
231 temp_file.close()
232 shutil.move(temp_file.name, depspath)
233
234
235 def roll_deps(revision, hashval, chromium_dir, save_branches, git):
236 """Upload changed DEPS and a whitespace change.
237
238 Given the correct hashval, create two Reitveld issues. Returns a
239 tuple containing textual description of the two issues.
240
241 Args:
242 revision: (int) Skia SVN revision.
243 hashval: (string) Skia Git hash.
244 chromium_dir: (string) path to a local chromium git repository.
245 save_branches: (boolean) iff false, delete temprary branches.
borenet 2014/01/06 14:06:46 "temporary"
hal.canary 2014/01/06 18:27:57 Done.
246 git: (string) git executable.
247 """
248 cwd = os.getcwd()
249 os.chdir(chromium_dir)
250 try:
251 master_hash = fetch_origin(git)
252
253 message = 'roll skia DEPS to %d' % revision
254 branch = message.replace(' ','_') if save_branches else None
255 codereview = GitBranchCLUpload(message, ['DEPS'], git, branch)
256 with codereview:
borenet 2014/01/06 14:06:46 Why not do this on one line: with GitBranchCLUploa
hal.canary 2014/01/06 18:27:57 I tried that; that syntax leaves codereview == No
257 change_skia_deps(revision, hashval, 'DEPS')
258 if save_branches:
259 deps_issue = '%s\n branch: %s' % (codereview.issue, branch)
260 else:
261 deps_issue = codereview.issue
262
263 message = 'whitespace change %s' % master_hash[:8] # Unique name
borenet 2014/01/06 14:06:46 Could this point to deps_issue as well? So that w
hal.canary 2014/01/06 18:27:57 That sounds like a pain.
264 branch = message.replace(' ','_') if save_branches else None
265 codereview = GitBranchCLUpload(message, ['DEPS'], git, branch)
266 with codereview:
267 with open('DEPS', 'a') as output_stream:
268 output_stream.write('\n')
269 if save_branches:
270 whitespace_issue = '%s\n branch: %s' % (
271 codereview.issue, branch)
272 else:
273 whitespace_issue = codereview.issue
274
275 return deps_issue, whitespace_issue
276 finally:
277 os.chdir(cwd)
278
279
280 def find_hash_and_roll_deps(revision, chromium_dir, search_depth,
281 save_branches, git):
282 """Call find_hash_from_revision() and roll_deps().
283
284 Args:
285 chromium_dir: (string) path to Chromium Git repository.
286 revision: (int) the Skia SVN revision number.
287 search_depth: (int) how far back to look for the revision.
288 git: (string) Git executable.
289 save_branches: (boolean) save the temporary branches.
290 """
291 hashval = find_hash_from_revision(revision, search_depth, git)
292 if not hashval:
293 raise Exception('failed to find revision')
294
295 print 'revision = @%d\nhash = %s\n' % (revision, hashval)
296
297 deps_issue, whitespace_issue = roll_deps(
298 revision, hashval, chromium_dir, save_branches, git)
299 print '\nDEPS roll:\n %s\n' % deps_issue
300 print 'Whitespace change:\n %s\n' % whitespace_issue
301
302
303 def main(args):
304 """
305 main function; see module-level docstring and option_parser help.
306 """
307 option_parser = optparse.OptionParser(usage=__doc__)
308 # Anyone using this script on a regular basis should set the
309 # CHROMIUM_REPO_PATH environment variable.
310 option_parser.add_option(
311 '-c', '--chromium_path', help='Path to Chromium Git repository.',
312 default=os.environ.get('CHROMIUM_REPO_PATH'))
313 option_parser.add_option(
314 '-r', '--revision', help='The Skia SVN revision number', type="int")
315 option_parser.add_option(
316 '', '--search_depth', help='How far back to look for the revision',
317 type="int", default=100)
318 option_parser.add_option(
319 '', '--git_path', help='Git executable', default='git')
320 option_parser.add_option(
321 '', '--save_branches', help='Save the temporary branches',
322 action="store_true", dest="save_branches", default=False)
323
324 options = option_parser.parse_args(args)[0]
325
326 if not options.revision and not options.chromium_path:
327 option_parser.error('Must specify revision and chromium_path.')
328 if not options.revision:
329 option_parser.error('Must specify revision.')
330 if not options.chromium_path:
331 option_parser.error('Must specify chromium_path.')
332 test_git(options.git_path)
333
334 find_hash_and_roll_deps(
335 options.revision, options.chromium_path, options.search_depth,
336 options.save_branches, options.git_path)
337
338
339 if __name__ == '__main__':
340 main(sys.argv[1:])
341
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698