blob: cb73be41d4bb86f523e2f6ec4e2d70cff5f6d2e5 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This script is used to roll cobalt's submodules to match those used in Fuchsia.
With a clean checkout, updating the submodules should be as simple as:
./cobaltb.py update_submodules --make_commit
This will update all of the submodules to match those used in Fuchsia, and then
if there are any changes, will add them all to a commit that you can then upload
to Gerrit.
If you omit the '--make_commit' flag, then the commit will not be generated, but
a suggested commit message will be printed out at the end of the command.
"""
import tempfile
import xml.etree.ElementTree as ElementTree
import subprocess
import os
import re
THIS_DIR = os.path.abspath(os.path.dirname(__file__))
ROOT_DIR = os.path.abspath(os.path.join(THIS_DIR, '..'))
def short_hash(long_hash):
"""Returns a shortened, still unique commit hash."""
return subprocess.check_output(['git', 'rev-parse', '--short',
long_hash]).decode('utf-8').strip()
def hash_distance(start, end):
"""Returns the number of commits between the two provided hashes."""
return len(
subprocess.check_output(['git', 'rev-list',
'%s..%s' % (start, end)
]).decode('utf-8').strip().split('\n'))
def update_from_project(project, update_report, commit_message):
"""Attempts to update the submodule for the supplied project.
The project is of the following format:
<project name="third_party/abseil-cpp"
path="third_party/abseil-cpp"
revision="17b90e77683fd71c72478c2af966fe0a4c1c07be"
remote="https://fuchsia.googlesource.com/third_party/abseil-cpp"/>
"""
full_path = os.path.join(ROOT_DIR, project.attrib['path'])
if os.path.isdir(full_path):
savedDir = os.getcwd()
try:
os.chdir(full_path)
root_dir = subprocess.check_output(
['git', 'rev-parse', '--show-toplevel']).decode('utf-8').strip()
if root_dir == ROOT_DIR:
# If the root_dir at this path is equal to the global ROOT_DIR, that means this path exists
# but is not actually a submodule. (for example //third_party/go)
return
current_hash = subprocess.check_output(['git', 'rev-parse',
'HEAD']).decode('utf-8').strip()
if current_hash != project.attrib['revision']:
# The current HEAD does not match the revision specified in the manifest
print('Updating %s to %s' %
(project.attrib['path'], project.attrib['revision']))
subprocess.check_call(['git', 'fetch', 'origin'])
subprocess.check_call(['git', 'checkout', project.attrib['revision']])
# Generate a human readable submodule update message
num_commits = hash_distance(current_hash, project.attrib['revision'])
commit_count = ''
if num_commits == 1:
commit_count = ' (1 commit)'
raw_msg = subprocess.check_output([
'git', 'log', '--format=%B', '-n', '1', project.attrib['revision']
]).decode('utf-8').strip()
# Replace commit labels (like: Change-Id: ... with Original-Change-Id: ...)
filtered_msg = re.sub(
r'^([a-zA-Z-_]*: .*$)', r'Original-\1', raw_msg, flags=re.MULTILINE)
commit_message.append('[roll] Roll %s' % filtered_msg)
elif num_commits > 1:
commit_count = ' (%d commits)' % num_commits
update_report.append(
'%s %s..%s%s' %
(project.attrib['name'], short_hash(current_hash),
short_hash(project.attrib['revision']), commit_count))
finally:
os.chdir(savedDir)
def update_from_manifest(manifest_path, commit_message):
"""Iterates through each project in a manifest calling update_from_project for each one.
The top level XML format is:
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<projects>
<project ... />
<project ... />
</projects>
</manifest>
"""
print('Parsing %s' % manifest_path)
flower_tree = ElementTree.parse(manifest_path)
root = flower_tree.getroot()
update_report = []
print('Checking through projects...')
for project in root.findall('./projects/project'):
update_from_project(project, update_report, commit_message)
return update_report
def update_submodules(integration_repo, manifest_files, make_commit=False):
"""Checks out an integration repository and calls update_from_manifest on each manifest_file.
If make_commit is true, the resulting submodule change will be added to a
commit.
"""
with tempfile.TemporaryDirectory(
prefix='fuchsia-integration-') as integration_dir:
print('Checking out integration repo...')
subprocess.check_call(['git', 'clone', integration_repo, integration_dir])
print()
update_report = []
commit_message = []
for manifest_file in manifest_files:
update_report.extend(
update_from_manifest(
os.path.join(integration_dir, manifest_file), commit_message))
commit_msg = ''
if len(update_report) > 1:
commit_msg = """[roll] Roll %d submodules
%s""" % (len(update_report), '\n'.join(sorted(update_report)))
elif len(update_report) == 1:
if len(commit_message) == 1:
commit_msg = commit_message[0]
else:
commit_msg = "[roll] Roll %s" % update_report[0]
if commit_msg != '':
if make_commit:
subprocess.check_call(['git', 'commit', '-am', commit_msg])
else:
print()
print()
print(commit_msg)
print()
print()
if __name__ == '__main__':
update_submodules('http://fuchsia.googlesource.com/integration',
['third_party/flower', 'third_party/boringssl/boringssl'])