#!/usr/bin/python
#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Merge master-chromium to master within the Android tree."""
import logging
import optparse
import os
import re
import shutil
import sys
import merge_common
AUTOGEN_MESSAGE = 'This commit was generated by merge_to_master.py.'
def _MergeProjects(svn_revision, target):
"""Merges the Chromium projects from master-chromium to target.
The larger projects' histories are flattened in the process.
Args:
svn_revision: The SVN revision for the main Chromium repository
"""
for path in merge_common.PROJECTS_WITH_FLAT_HISTORY:
dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path)
merge_common.GetCommandStdout(['git', 'remote', 'update',
'goog', 'history'], cwd=dest_dir)
merge_common.GetCommandStdout(['git', 'checkout',
'-b', 'merge-to-' + target,
'-t', 'goog/' + target], cwd=dest_dir)
merge_common.GetCommandStdout(['git', 'fetch', 'history',
'refs/archive/chromium-%s' % svn_revision],
cwd=dest_dir)
merge_sha1 = merge_common.GetCommandStdout(['git', 'rev-parse',
'FETCH_HEAD'],
cwd=dest_dir).strip()
old_sha1 = merge_common.GetCommandStdout(['git', 'rev-parse', 'HEAD'],
cwd=dest_dir).strip()
# Make the previous merges into grafts so we can do a correct merge.
merge_log = os.path.join(dest_dir, '.merged-revisions')
if os.path.exists(merge_log):
shutil.copyfile(merge_log,
os.path.join(dest_dir, '.git', 'info', 'grafts'))
if merge_common.GetCommandStdout(['git', 'rev-list', '-1',
'HEAD..' + merge_sha1], cwd=dest_dir):
logging.debug('Merging project %s ...', path)
# Merge conflicts cause 'git merge' to return 1, so ignore errors
merge_common.GetCommandStdout(['git', 'merge', '--no-commit', '--squash',
merge_sha1],
cwd=dest_dir, ignore_errors=True)
dirs_to_prune = merge_common.PRUNE_WHEN_FLATTENING.get(path, [])
if dirs_to_prune:
merge_common.GetCommandStdout(['git', 'rm', '--ignore-unmatch', '-rf'] +
dirs_to_prune, cwd=dest_dir)
merge_common.CheckNoConflictsAndCommitMerge(
'Merge from Chromium at DEPS revision %s\n\n%s' %
(svn_revision, AUTOGEN_MESSAGE), cwd=dest_dir)
new_sha1 = merge_common.GetCommandStdout(['git', 'rev-parse', 'HEAD'],
cwd=dest_dir).strip()
with open(merge_log, 'a+') as f:
f.write('%s %s %s\n' % (new_sha1, old_sha1, merge_sha1))
merge_common.GetCommandStdout(['git', 'add', '.merged-revisions'],
cwd=dest_dir)
merge_common.GetCommandStdout(
['git', 'commit', '-m',
'Record Chromium merge at DEPS revision %s\n\n%s' %
(svn_revision, AUTOGEN_MESSAGE)], cwd=dest_dir)
else:
logging.debug('No new commits to merge in project %s', path)
for path in merge_common.PROJECTS_WITH_FULL_HISTORY:
dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path)
merge_common.GetCommandStdout(['git', 'remote', 'update', 'goog'],
cwd=dest_dir)
merge_common.GetCommandStdout(['git', 'checkout',
'-b', 'merge-to-' + target,
'-t', 'goog/' + target], cwd=dest_dir)
merge_common.GetCommandStdout(['git', 'fetch', 'goog',
'refs/archive/chromium-%s' % svn_revision],
cwd=dest_dir)
if merge_common.GetCommandStdout(['git', 'rev-list', '-1',
'HEAD..FETCH_HEAD'],
cwd=dest_dir):
logging.debug('Merging project %s ...', path)
# Merge conflicts cause 'git merge' to return 1, so ignore errors
merge_common.GetCommandStdout(['git', 'merge', '--no-commit', '--no-ff',
'FETCH_HEAD'],
cwd=dest_dir, ignore_errors=True)
merge_common.CheckNoConflictsAndCommitMerge(
'Merge from Chromium at DEPS revision %s\n\n%s' %
(svn_revision, AUTOGEN_MESSAGE), cwd=dest_dir)
else:
logging.debug('No new commits to merge in project %s', path)
def _GetSVNRevision(commitish='history/master-chromium'):
logging.debug('Getting SVN revision ...')
commit = merge_common.GetCommandStdout([
'git', 'log', '-n1', '--grep=git-svn-id:', '--format=%H%n%b', commitish])
svn_revision = re.search(r'^git-svn-id: .*@([0-9]+)', commit,
flags=re.MULTILINE).group(1)
return svn_revision
def _MergeWithRepoProp(repo_prop_file, target):
chromium_sha = None
webview_sha = None
with open(repo_prop_file) as prop:
for line in prop:
project, sha = line.split()
if project == 'platform/external/chromium_org-history':
chromium_sha = sha
elif project == 'platform/frameworks/webview':
webview_sha = sha
if not chromium_sha or not webview_sha:
logging.error('SHA1s for projects not found; invalid build.prop?')
return 1
chromium_revision = _GetSVNRevision(chromium_sha)
logging.info('Merging Chromium at r%s and WebView at %s', chromium_revision,
webview_sha)
_MergeProjects(chromium_revision, target)
dest_dir = os.path.join(os.environ['ANDROID_BUILD_TOP'], 'frameworks/webview')
merge_common.GetCommandStdout(['git', 'remote', 'update', 'goog'],
cwd=dest_dir)
merge_common.GetCommandStdout(['git', 'checkout',
'-b', 'merge-to-' + target,
'-t', 'goog/' + target], cwd=dest_dir)
if merge_common.GetCommandStdout(['git', 'rev-list', '-1',
'HEAD..' + webview_sha], cwd=dest_dir):
logging.debug('Creating merge for framework...')
# Merge conflicts cause 'git merge' to return 1, so ignore errors
merge_common.GetCommandStdout(['git', 'merge', '--no-commit', '--no-ff',
webview_sha], cwd=dest_dir,
ignore_errors=True)
merge_common.CheckNoConflictsAndCommitMerge(
'Merge master-chromium into %s at r%s\n\n%s' %
(target, chromium_revision, AUTOGEN_MESSAGE), cwd=dest_dir)
upload = merge_common.GetCommandStdout(['git', 'push', 'goog',
'HEAD:refs/for/' + target],
cwd=dest_dir)
logging.info(upload)
else:
logging.debug('No new commits to merge in framework')
return 0
def Push(target):
"""Push the finished snapshot to the Android repository."""
logging.debug('Pushing to server ...')
refspec = 'merge-to-%s:%s' % (target, target)
for path in merge_common.ALL_PROJECTS:
logging.debug('Pushing %s', path)
dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path)
# Delete the graft before pushing otherwise git will attempt to push all the
# grafted-in objects to the server as well as the ones we want.
graftfile = os.path.join(dest_dir, '.git', 'info', 'grafts')
if os.path.exists(graftfile):
os.remove(graftfile)
merge_common.GetCommandStdout(['git', 'push', 'goog', refspec],
cwd=dest_dir)
def main():
parser = optparse.OptionParser(usage='%prog [options]')
parser.epilog = ('Takes the current master-chromium branch of the Chromium '
'projects in Android and merges them into master to publish '
'them.')
parser.add_option(
'', '--svn_revision', '--release',
default=None,
help=('Merge to the specified archived master-chromium SVN revision,'
'rather than using HEAD.'))
parser.add_option(
'', '--repo-prop',
default=None, metavar='FILE',
help=('Merge to the revisions specified in this repo.prop file.'))
parser.add_option(
'', '--push',
default=False, action='store_true',
help=('Push the result of a previous merge to the server.'))
parser.add_option(
'', '--target',
default='master', metavar='BRANCH',
help=('Target branch to push to. Defaults to master.'))
(options, args) = parser.parse_args()
if args:
parser.print_help()
return 1
logging.basicConfig(format='%(message)s', level=logging.DEBUG,
stream=sys.stdout)
if options.push:
Push(options.target)
elif options.repo_prop:
return _MergeWithRepoProp(os.path.expanduser(options.repo_prop),
options.target)
elif options.svn_revision:
_MergeProjects(options.svn_revision, options.target)
else:
svn_revision = _GetSVNRevision()
_MergeProjects(svn_revision, options.target)
return 0
if __name__ == '__main__':
sys.exit(main())