1#!/usr/bin/env python 2# Copyright (c) 2011 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"""Update third_party/WebKit using git. 7 8Under the assumption third_party/WebKit is a clone of git.webkit.org, 9we can use git commands to make it match the version requested by DEPS. 10 11See http://code.google.com/p/chromium/wiki/UsingWebKitGit for details on 12how to use this. 13""" 14 15import logging 16import optparse 17import os 18import re 19import subprocess 20import sys 21import urllib 22 23 24def RunGit(command): 25 """Run a git subcommand, returning its output.""" 26 # On Windows, use shell=True to get PATH interpretation. 27 command = ['git'] + command 28 logging.info(' '.join(command)) 29 shell = (os.name == 'nt') 30 proc = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE) 31 out = proc.communicate()[0].strip() 32 logging.info('Returned "%s"' % out) 33 return out 34 35 36def GetOverrideShortBranchName(): 37 """Returns the user-configured override branch name, if any.""" 38 override_config_name = 'chromium.sync-branch' 39 return RunGit(['config', '--get', override_config_name]) 40 41 42def GetGClientBranchName(): 43 """Returns the name of the magic branch that lets us know that DEPS is 44 managing the update cycle.""" 45 # Is there an override branch specified? 46 override_branch_name = GetOverrideShortBranchName() 47 if not override_branch_name: 48 return 'refs/heads/gclient' # No override, so return the default branch. 49 50 # Verify that the branch from config exists. 51 ref_branch = 'refs/heads/' + override_branch_name 52 current_head = RunGit(['show-ref', '--hash', ref_branch]) 53 if current_head: 54 return ref_branch 55 56 # Inform the user about the problem and how to fix it. 57 print ("The specified override branch ('%s') doesn't appear to exist." % 58 override_branch_name) 59 print "Please fix your git config value '%s'." % overide_config_name 60 sys.exit(1) 61 62 63def GetWebKitRev(): 64 """Extract the 'webkit_revision' variable out of DEPS.""" 65 locals = {'Var': lambda _: locals["vars"][_], 66 'From': lambda *args: None} 67 execfile('DEPS', {}, locals) 68 return locals['vars']['webkit_revision'] 69 70 71def GetWebKitRevFromTarball(version): 72 """Extract the 'webkit_revision' variable out of tarball DEPS.""" 73 deps_url = "http://src.chromium.org/svn/releases/" + version + "/DEPS" 74 f = urllib.urlopen(deps_url) 75 s = f.read() 76 m = re.search('(?<=/Source@)\w+', s) 77 return m.group(0) 78 79 80def FindSVNRev(branch_name, target_rev): 81 """Map an SVN revision to a git hash. 82 Like 'git svn find-rev' but without the git-svn bits.""" 83 84 # We iterate through the commit log looking for "git-svn-id" lines, 85 # which contain the SVN revision of that commit. We can stop once 86 # we've found our target (or hit a revision number lower than what 87 # we're looking for, indicating not found). 88 89 target_rev = int(target_rev) 90 91 # regexp matching the "commit" line from the log. 92 commit_re = re.compile(r'^commit ([a-f\d]{40})$') 93 # regexp matching the git-svn line from the log. 94 git_svn_re = re.compile(r'^\s+git-svn-id: [^@]+@(\d+) ') 95 if not branch_name: 96 branch_name = 'origin/master' 97 cmd = ['git', 'log', '--no-color', '--first-parent', '--pretty=medium', 98 branch_name] 99 logging.info(' '.join(cmd)) 100 log = subprocess.Popen(cmd, shell=(os.name == 'nt'), stdout=subprocess.PIPE) 101 # Track whether we saw a revision *later* than the one we're seeking. 102 saw_later = False 103 for line in log.stdout: 104 match = commit_re.match(line) 105 if match: 106 commit = match.group(1) 107 continue 108 match = git_svn_re.match(line) 109 if match: 110 rev = int(match.group(1)) 111 if rev <= target_rev: 112 log.stdout.close() # Break pipe. 113 if rev < target_rev: 114 if not saw_later: 115 return None # Can't be sure whether this rev is ok. 116 print ("WARNING: r%d not found, so using next nearest earlier r%d" % 117 (target_rev, rev)) 118 return commit 119 else: 120 saw_later = True 121 122 print "Error: reached end of log without finding commit info." 123 print "Something has likely gone horribly wrong." 124 return None 125 126 127def GetRemote(): 128 branch = GetOverrideShortBranchName() 129 if not branch: 130 branch = 'gclient' 131 132 remote = RunGit(['config', '--get', 'branch.' + branch + '.remote']) 133 if remote: 134 return remote 135 return 'origin' 136 137 138def UpdateGClientBranch(branch_name, webkit_rev, magic_gclient_branch): 139 """Update the magic gclient branch to point at |webkit_rev|. 140 141 Returns: true if the branch didn't need changes.""" 142 target = FindSVNRev(branch_name, webkit_rev) 143 if not target: 144 print "r%s not available; fetching." % webkit_rev 145 subprocess.check_call(['git', 'fetch', GetRemote()], 146 shell=(os.name == 'nt')) 147 target = FindSVNRev(branch_name, webkit_rev) 148 if not target: 149 print "ERROR: Couldn't map r%s to a git revision." % webkit_rev 150 sys.exit(1) 151 152 current = RunGit(['show-ref', '--hash', magic_gclient_branch]) 153 if current == target: 154 return False # No change necessary. 155 156 subprocess.check_call(['git', 'update-ref', '-m', 'gclient sync', 157 magic_gclient_branch, target], 158 shell=(os.name == 'nt')) 159 return True 160 161 162def UpdateCurrentCheckoutIfAppropriate(magic_gclient_branch): 163 """Reset the current gclient branch if that's what we have checked out.""" 164 branch = RunGit(['symbolic-ref', '-q', 'HEAD']) 165 if branch != magic_gclient_branch: 166 print "We have now updated the 'gclient' branch, but third_party/WebKit" 167 print "has some other branch ('%s') checked out." % branch 168 print "Run 'git checkout gclient' under third_party/WebKit if you want" 169 print "to switch it to the version requested by DEPS." 170 return 1 171 172 if subprocess.call(['git', 'diff-index', '--exit-code', '--shortstat', 173 'HEAD'], shell=(os.name == 'nt')): 174 print "Resetting tree state to new revision." 175 subprocess.check_call(['git', 'reset', '--hard'], shell=(os.name == 'nt')) 176 177 178def main(): 179 parser = optparse.OptionParser() 180 parser.add_option('-v', '--verbose', action='store_true') 181 parser.add_option('-r', '--revision', help="switch to desired revision") 182 parser.add_option('-t', '--tarball', help="switch to desired tarball release") 183 parser.add_option('-b', '--branch', help="branch name that gclient generate") 184 options, args = parser.parse_args() 185 if options.verbose: 186 logging.basicConfig(level=logging.INFO) 187 if not os.path.exists('third_party/WebKit/.git'): 188 if os.path.exists('third_party/WebKit'): 189 print "ERROR: third_party/WebKit appears to not be under git control." 190 else: 191 print "ERROR: third_party/WebKit could not be found." 192 print "Did you run this script from the right directory?" 193 194 print "See http://code.google.com/p/chromium/wiki/UsingWebKitGit for" 195 print "setup instructions." 196 return 1 197 198 if options.revision: 199 webkit_rev = options.revision 200 if options.tarball: 201 print "WARNING: --revision is given, so ignore --tarball" 202 else: 203 if options.tarball: 204 webkit_rev = GetWebKitRevFromTarball(options.tarball) 205 else: 206 webkit_rev = GetWebKitRev() 207 208 print 'Desired revision: r%s.' % webkit_rev 209 os.chdir('third_party/WebKit') 210 magic_gclient_branch = GetGClientBranchName() 211 changed = UpdateGClientBranch(options.branch, webkit_rev, 212 magic_gclient_branch) 213 if changed: 214 return UpdateCurrentCheckoutIfAppropriate(magic_gclient_branch) 215 else: 216 print "Already on correct revision." 217 return 0 218 219 220if __name__ == '__main__': 221 sys.exit(main()) 222