#!/usr/bin/python # Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Read .DEPS.git and use the information to update git submodules""" import optparse import os import re import subprocess import sys from deps_utils import GetDepsContent SHA1_RE = re.compile('[0-9a-fA-F]{40}') def SanitizeDeps(submods): """ Look for conflicts (primarily nested submodules) in submodule data. In the case of a conflict, the higher-level (shallower) submodule takes precedence. Modifies the submods argument in-place. """ for submod_name in submods.keys(): parts = submod_name.split('/')[:-1] while parts: may_conflict = '/'.join(parts) if may_conflict in submods: msg = ('Warning: dropping submodule "%s", because ' 'it is nested in submodule "%s".' % (submod_name, may_conflict)) print >> sys.stderr, msg submods.pop(submod_name) break parts.pop() return submods def CollateDeps(deps_content): """ Take the output of deps_utils.GetDepsContent and return a hash of: { submod_name : [ [ submod_os, ... ], submod_url, submod_sha1 ], ... } """ fixdep = lambda x: x[4:] if x.startswith('src/') else x spliturl = lambda x: list(x.partition('@')[0::2]) if x else [None, None] submods = {} # Non-OS-specific DEPS always override OS-specific deps. This is an interim # hack until there is a better way to handle OS-specific DEPS. for (deps_os, val) in deps_content[1].iteritems(): for (dep, url) in val.iteritems(): submod_data = submods.setdefault(fixdep(dep), [[]] + spliturl(url)) submod_data[0].append(deps_os) for (dep, url) in deps_content[0].iteritems(): submods[fixdep(dep)] = [['all']] + spliturl(url) return submods def WriteGitmodules(submods, gitless=False, rewrite_rules=None): """ Take the output of CollateDeps, use it to write a .gitmodules file and return a map of submodule name -> sha1 to be added to the git index. """ adds = {} if not rewrite_rules: rewrite_rules = [] def _rewrite(url): if not url: return url for rule in rewrite_rules: if url.startswith(rule[0]): return rule[1] + url[len(rule[0]):] return url fh = open('.gitmodules', 'w') for submod in sorted(submods.keys()): [submod_os, submod_url, submod_sha1] = submods[submod] submod_url = _rewrite(submod_url) print >> fh, '[submodule "%s"]' % submod print >> fh, '\tpath = %s' % submod print >> fh, '\turl = %s' % (submod_url if submod_url else '') print >> fh, '\tos = %s' % ','.join(submod_os) if submod_sha1 and not SHA1_RE.match(submod_sha1): raise RuntimeError('sha1 hash "%s" for submodule "%s" is malformed' % (submod_sha1, submod)) if gitless or not submod_url: continue if not submod_sha1: # We don't know what sha1 to register, so we have to infer it from the # submodule's origin/master. if not os.path.exists(os.path.join(submod, '.git')): # Not cloned yet subprocess.check_call(['git', 'clone', '-n', submod_url, submod]) else: # Already cloned; let's fetch subprocess.check_call(['git', 'fetch', 'origin'], cwd=submod) sub = subprocess.Popen(['git', 'rev-list', 'origin/HEAD^!'], cwd=submod, stdout=subprocess.PIPE) submod_sha1 = sub.communicate()[0].rstrip() adds[submod] = submod_sha1 fh.close() if not gitless: subprocess.check_call(['git', 'add', '.gitmodules']) return adds def RemoveObsoleteSubmodules(): """ Delete from the git repository any submodules which aren't in .gitmodules. """ lsfiles_proc = subprocess.Popen(['git', 'ls-files', '-s'], stdout=subprocess.PIPE) grep_proc = subprocess.Popen(['grep', '^160000'], stdin = lsfiles_proc.stdout, stdout=subprocess.PIPE) (grep_out, _) = grep_proc.communicate() or ('', '') lsfiles_proc.communicate() with open(os.devnull, 'w') as nullpipe: for line in grep_out.splitlines(): [_, _, _, path] = line.split() cmd = ['git', 'config', '-f', '.gitmodules', '--get-regexp', 'submodule\..*\.path', '^%s$' % path] try: subprocess.check_call(cmd, stdout=nullpipe) except subprocess.CalledProcessError: subprocess.check_call(['git', 'update-index', '--force-remove', path]) def main(): parser = optparse.OptionParser() parser.add_option('--gitless', action='store_true', help='Skip all actions that assume a git working copy ' '(to support presubmit checks)') parser.add_option('--rewrite-url', action='append', metavar='OLD_URL=NEW_URL', default=[], help='Translate urls according to this rule') options, args = parser.parse_args() if args: deps_file = args[0] else: deps_file = '.DEPS.git' rewrite_rules = [] for rule in options.rewrite_url: (old_url, new_url) = rule.split('=', 1) if not old_url or not new_url: print 'Bad url rewrite rule: "%s"' % rule parser.print_help() return 1 rewrite_rules.append((old_url, new_url)) # 9/18/2012 -- HACK to fix try bots without restarting hack_deps_file = os.path.join('src', '.DEPS.git') if not os.path.exists(deps_file) and os.path.exists(hack_deps_file): deps_file = hack_deps_file adds = WriteGitmodules(SanitizeDeps(CollateDeps(GetDepsContent(deps_file))), rewrite_rules=rewrite_rules, gitless=options.gitless) if not options.gitless: RemoveObsoleteSubmodules() for submod_path, submod_sha1 in adds.iteritems(): subprocess.check_call(['git', 'update-index', '--add', '--cacheinfo', '160000', submod_sha1, submod_path]) return 0 if __name__ == '__main__': sys.exit(main())