blob: fead751c92f3f93bb26f4962cd3ae04dc21eba95 [file] [log] [blame]
#!/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())