#!/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())