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