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

Unified 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, 12 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/roll_deps.py
diff --git a/tools/roll_deps.py b/tools/roll_deps.py
new file mode 100755
index 0000000000000000000000000000000000000000..36cb73c7ded182b6848508a675e3b82e68c0c5b4
--- /dev/null
+++ b/tools/roll_deps.py
@@ -0,0 +1,341 @@
+#!/usr/bin/python2
+
+# Copyright 2014 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+"""
+Skia's Chromium DEPS roll script
+
+This script:
+- searches through the last N Skia git commits to find out the hash that is
+ associated with the SVN revision number.
+- creates a new branch in the Chromium tree, modifies the DEPS file to
+ point at the given Skia commit, commits, uploads to Rietveld, and
+ deletes the local copy of the branch.
+- creates a whitespace-only commit and uploads that to to Rietveld.
+- returns the Chromium tree to its previous state.
+
+Usage:
+ %prog -c CHROMIUM_PATH -r REVISION [OPTIONAL_OPTIONS]"""
+
+
+import optparse
+import os
+import re
+import subprocess
+import shutil
+import sys
+import tempfile
+
+
+def test_git(git):
+ """Test to see if the git executable can be run.
+
+ Args:
+ git: git executable.
+ Raises:
+ OSError on failure
+
+ """
+ with open(os.devnull, "w") as devnull:
+ subprocess.call([git, '--version'], stdout=devnull)
+
+
+def strip_output(*args, **kwargs):
+ """Wrap subprocess.check_output and str.strip()
+
+ Pass the given arguments into subprocess.check_output() and return
+ the results, after stripping any excess whitespace.
+
+ Returns:
+ 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.
+ """
+ return str(subprocess.check_output(*args, **kwargs)).strip()
+
+
+def find_hash_from_revision(revision, search_depth, git):
+ """Finds the hash associated with a revision.
+
+ Searches through the last search_depth commits to find out the hash
+ that is associated with the SVN revision number.
+
+ Args:
+ revision: (int) SVN revision number.
+ search_depth: (int) Number of revisions to limit the search to.
+ git: git executable.
+
+ Returns:
+ Hash as a string.
+
+ Raises an exception on failure
+ """
+ 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.
+ temp_dir = tempfile.mkdtemp(prefix='git_skia_tmp_')
+ devnull = open(os.devnull, "w")
+ 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.
+ revision_regex = re.compile(revision_format % revision)
+ try:
+ subprocess.check_call(
+ [git, 'clone', '--depth=%d' % search_depth, '--single-branch',
+ 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.
+ for i in xrange(search_depth):
+ commit = 'origin/master~%d' % i
+ output = subprocess.check_output(
+ [git, 'log', '-n', '1', '--format=format:%B', commit],
+ cwd=temp_dir, stderr=devnull)
+ if revision_regex.search(output):
+ return strip_output(
+ [git, 'log', '-n', '1', '--format=format:%H', commit],
+ cwd=temp_dir)
+ finally:
+ shutil.rmtree(temp_dir)
+ devnull.close()
+ raise Exception('Failed to find revision.')
+
+
+def fetch_origin(git):
+ """Call git fetch
+
+ Updates origin/master (via git fetch). Leaves local tree alone.
+ Assumes current directory is a git repository.
+
+ Args:
+ git: git executable.
+ Returns:
+ the commit hash of origin/master
+ """
+ with open(os.devnull, "w") as devnull:
+ subprocess.check_call(
+ [git, 'fetch', 'origin'], stdout=devnull, stderr=devnull)
+ return strip_output([git, 'show-ref', 'origin/master', '--hash'])
+
+
+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!
+ """
+ This class allows one to create a new branch in a repository based
+ off of origin/master, make changes to the tree inside the
+ with-block, upload that new branch to Rietveld, restore the original
+ tree state, and delete the local copy of the new branch.
+
+ See roll_deps() for an example of use.
+
+ Constructor Args:
+ message: the commit message.
+ file_list: list of files to pass to `git add`.
+ git: git executable.
+ set_brach_name: if not None, the name of the branch to use.
+ If None, then use a temporary branch that will be deleted.
+ """
+ # (Too few public methods) pylint: disable=I0011,R0903
+ 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
+ self._message = message
+ self._file_list = file_list
+ self._git = git
+ self._issue = None
+ self._branch_name = set_branch_name
+ self._stash = None
+ self._original_branch = None
+
+ def __enter__(self):
+ diff = subprocess.check_output([self._git, 'diff', '--shortstat'])
+ self._stash = (0 != len(diff))
+ if self._stash:
+ subprocess.check_call([self._git, 'stash', 'save'])
+ try:
+ self._original_branch = strip_output(
+ [self._git, 'symbolic-ref', '--short', 'HEAD'])
+ except (subprocess.CalledProcessError,):
+ self._original_branch = strip_output(
+ [self._git, 'rev-parse', 'HEAD'])
+
+ if not self._branch_name:
+ 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.
+
+ try:
+ subprocess.check_call(
+ [self._git, 'checkout', '-b',
+ self._branch_name, 'origin/master'])
+ except (subprocess.CalledProcessError,):
+ # Branch already exists.
+ subprocess.check_call([self._git, 'checkout', 'master'])
+ subprocess.check_call(
+ [self._git, 'branch', '-D', self._branch_name])
+ subprocess.check_call(
+ [self._git, 'checkout', '-b',
+ self._branch_name, 'origin/master'])
+
+
+ def __exit__(self, etype, value, traceback):
+ for filename in self._file_list:
+ subprocess.check_call([self._git, 'add', filename])
+ 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.
+
+ environ = os.environ.copy()
+ if sys.platform != 'win32':
+ environ['GIT_EDITOR'] = ':' # Bypass the editor
+ subprocess.check_call([self._git, 'cl', 'upload'], env=environ)
+
+ self._issue = strip_output([self._git, 'cl', 'issue'])
+
+ # deal with the aftermath of failed executions of this script.
+ if 'autogenerated_deps_roll_branch' == self._original_branch:
+ subprocess.check_call([self._git, 'checkout', 'master'])
+ else:
+ subprocess.check_call(
+ [self._git, 'checkout', self._original_branch])
+
+ if 'autogenerated_deps_roll_branch' == self._branch_name:
+ subprocess.check_call(
+ [self._git, 'branch', '-D', self._branch_name])
+ if self._stash:
+ subprocess.check_call([self._git, 'stash', 'pop'])
+
+ @property
+ def issue(self):
+ """
+ Returns:
+ a string describing the codereview issue, after __exit__
+ 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
+ """
+ return self._issue
+
+
+def change_skia_deps(revision, hashval, depspath):
+ """Update the DEPS file.
+
+ Modify the skia_revision and skia_hash entries in the given DEPS file.
+
+ Args:
+ revision: (int) Skia SVN revision.
+ hashval: (string) Skia Git hash.
+ depspath: (string) path to DEPS file.
+ """
+ temp_file = tempfile.NamedTemporaryFile(delete=False,
+ prefix='skia_DEPS_ROLL_tmp_')
+ try:
+ deps_regex_rev = re.compile('"skia_revision": "[0-9]*",')
+ deps_regex_hash = re.compile('"skia_hash": "[0-9a-f]*",')
+
+ deps_regex_rev_repl = '"skia_revision": "%d",' % revision
+ deps_regex_hash_repl = '"skia_hash": "%s",' % hashval
+
+ with open(depspath, 'r') as input_stream:
+ for line in input_stream:
+ line = deps_regex_rev.sub(deps_regex_rev_repl, line)
+ line = deps_regex_hash.sub(deps_regex_hash_repl, line)
+ temp_file.write(line)
+ finally:
+ temp_file.close()
+ shutil.move(temp_file.name, depspath)
+
+
+def roll_deps(revision, hashval, chromium_dir, save_branches, git):
+ """Upload changed DEPS and a whitespace change.
+
+ Given the correct hashval, create two Reitveld issues. Returns a
+ tuple containing textual description of the two issues.
+
+ Args:
+ revision: (int) Skia SVN revision.
+ hashval: (string) Skia Git hash.
+ chromium_dir: (string) path to a local chromium git repository.
+ 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.
+ git: (string) git executable.
+ """
+ cwd = os.getcwd()
+ os.chdir(chromium_dir)
+ try:
+ master_hash = fetch_origin(git)
+
+ message = 'roll skia DEPS to %d' % revision
+ branch = message.replace(' ','_') if save_branches else None
+ codereview = GitBranchCLUpload(message, ['DEPS'], git, branch)
+ 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
+ change_skia_deps(revision, hashval, 'DEPS')
+ if save_branches:
+ deps_issue = '%s\n branch: %s' % (codereview.issue, branch)
+ else:
+ deps_issue = codereview.issue
+
+ 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.
+ branch = message.replace(' ','_') if save_branches else None
+ codereview = GitBranchCLUpload(message, ['DEPS'], git, branch)
+ with codereview:
+ with open('DEPS', 'a') as output_stream:
+ output_stream.write('\n')
+ if save_branches:
+ whitespace_issue = '%s\n branch: %s' % (
+ codereview.issue, branch)
+ else:
+ whitespace_issue = codereview.issue
+
+ return deps_issue, whitespace_issue
+ finally:
+ os.chdir(cwd)
+
+
+def find_hash_and_roll_deps(revision, chromium_dir, search_depth,
+ save_branches, git):
+ """Call find_hash_from_revision() and roll_deps().
+
+ Args:
+ chromium_dir: (string) path to Chromium Git repository.
+ revision: (int) the Skia SVN revision number.
+ search_depth: (int) how far back to look for the revision.
+ git: (string) Git executable.
+ save_branches: (boolean) save the temporary branches.
+ """
+ hashval = find_hash_from_revision(revision, search_depth, git)
+ if not hashval:
+ raise Exception('failed to find revision')
+
+ print 'revision = @%d\nhash = %s\n' % (revision, hashval)
+
+ deps_issue, whitespace_issue = roll_deps(
+ revision, hashval, chromium_dir, save_branches, git)
+ print '\nDEPS roll:\n %s\n' % deps_issue
+ print 'Whitespace change:\n %s\n' % whitespace_issue
+
+
+def main(args):
+ """
+ main function; see module-level docstring and option_parser help.
+ """
+ option_parser = optparse.OptionParser(usage=__doc__)
+ # Anyone using this script on a regular basis should set the
+ # CHROMIUM_REPO_PATH environment variable.
+ option_parser.add_option(
+ '-c', '--chromium_path', help='Path to Chromium Git repository.',
+ default=os.environ.get('CHROMIUM_REPO_PATH'))
+ option_parser.add_option(
+ '-r', '--revision', help='The Skia SVN revision number', type="int")
+ option_parser.add_option(
+ '', '--search_depth', help='How far back to look for the revision',
+ type="int", default=100)
+ option_parser.add_option(
+ '', '--git_path', help='Git executable', default='git')
+ option_parser.add_option(
+ '', '--save_branches', help='Save the temporary branches',
+ action="store_true", dest="save_branches", default=False)
+
+ options = option_parser.parse_args(args)[0]
+
+ if not options.revision and not options.chromium_path:
+ option_parser.error('Must specify revision and chromium_path.')
+ if not options.revision:
+ option_parser.error('Must specify revision.')
+ if not options.chromium_path:
+ option_parser.error('Must specify chromium_path.')
+ test_git(options.git_path)
+
+ find_hash_and_roll_deps(
+ options.revision, options.chromium_path, options.search_depth,
+ options.save_branches, options.git_path)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
+
« 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