1#!/usr/bin/python 2# 3# Copyright (C) 2012 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Merge master-chromium to master within the Android tree.""" 18 19import logging 20import optparse 21import os 22import re 23import shutil 24import sys 25 26import merge_common 27 28 29AUTOGEN_MESSAGE = 'This commit was generated by merge_to_master.py.' 30 31 32def _MergeProjects(svn_revision, target): 33 """Merges the Chromium projects from master-chromium to target. 34 35 The larger projects' histories are flattened in the process. 36 37 Args: 38 svn_revision: The SVN revision for the main Chromium repository 39 """ 40 for path in merge_common.PROJECTS_WITH_FLAT_HISTORY: 41 dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path) 42 merge_common.GetCommandStdout(['git', 'remote', 'update', 43 'goog', 'history'], cwd=dest_dir) 44 merge_common.GetCommandStdout(['git', 'checkout', 45 '-b', 'merge-to-' + target, 46 '-t', 'goog/' + target], cwd=dest_dir) 47 merge_common.GetCommandStdout(['git', 'fetch', 'history', 48 'refs/archive/chromium-%s' % svn_revision], 49 cwd=dest_dir) 50 merge_sha1 = merge_common.GetCommandStdout(['git', 'rev-parse', 51 'FETCH_HEAD'], 52 cwd=dest_dir).strip() 53 old_sha1 = merge_common.GetCommandStdout(['git', 'rev-parse', 'HEAD'], 54 cwd=dest_dir).strip() 55 # Make the previous merges into grafts so we can do a correct merge. 56 merge_log = os.path.join(dest_dir, '.merged-revisions') 57 if os.path.exists(merge_log): 58 shutil.copyfile(merge_log, 59 os.path.join(dest_dir, '.git', 'info', 'grafts')) 60 if merge_common.GetCommandStdout(['git', 'rev-list', '-1', 61 'HEAD..' + merge_sha1], cwd=dest_dir): 62 logging.debug('Merging project %s ...', path) 63 # Merge conflicts cause 'git merge' to return 1, so ignore errors 64 merge_common.GetCommandStdout(['git', 'merge', '--no-commit', '--squash', 65 merge_sha1], 66 cwd=dest_dir, ignore_errors=True) 67 dirs_to_prune = merge_common.PRUNE_WHEN_FLATTENING.get(path, []) 68 if dirs_to_prune: 69 merge_common.GetCommandStdout(['git', 'rm', '--ignore-unmatch', '-rf'] + 70 dirs_to_prune, cwd=dest_dir) 71 merge_common.CheckNoConflictsAndCommitMerge( 72 'Merge from Chromium at DEPS revision %s\n\n%s' % 73 (svn_revision, AUTOGEN_MESSAGE), cwd=dest_dir) 74 new_sha1 = merge_common.GetCommandStdout(['git', 'rev-parse', 'HEAD'], 75 cwd=dest_dir).strip() 76 with open(merge_log, 'a+') as f: 77 f.write('%s %s %s\n' % (new_sha1, old_sha1, merge_sha1)) 78 merge_common.GetCommandStdout(['git', 'add', '.merged-revisions'], 79 cwd=dest_dir) 80 merge_common.GetCommandStdout( 81 ['git', 'commit', '-m', 82 'Record Chromium merge at DEPS revision %s\n\n%s' % 83 (svn_revision, AUTOGEN_MESSAGE)], cwd=dest_dir) 84 else: 85 logging.debug('No new commits to merge in project %s', path) 86 87 for path in merge_common.PROJECTS_WITH_FULL_HISTORY: 88 dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path) 89 merge_common.GetCommandStdout(['git', 'remote', 'update', 'goog'], 90 cwd=dest_dir) 91 merge_common.GetCommandStdout(['git', 'checkout', 92 '-b', 'merge-to-' + target, 93 '-t', 'goog/' + target], cwd=dest_dir) 94 merge_common.GetCommandStdout(['git', 'fetch', 'goog', 95 'refs/archive/chromium-%s' % svn_revision], 96 cwd=dest_dir) 97 if merge_common.GetCommandStdout(['git', 'rev-list', '-1', 98 'HEAD..FETCH_HEAD'], 99 cwd=dest_dir): 100 logging.debug('Merging project %s ...', path) 101 # Merge conflicts cause 'git merge' to return 1, so ignore errors 102 merge_common.GetCommandStdout(['git', 'merge', '--no-commit', '--no-ff', 103 'FETCH_HEAD'], 104 cwd=dest_dir, ignore_errors=True) 105 merge_common.CheckNoConflictsAndCommitMerge( 106 'Merge from Chromium at DEPS revision %s\n\n%s' % 107 (svn_revision, AUTOGEN_MESSAGE), cwd=dest_dir) 108 else: 109 logging.debug('No new commits to merge in project %s', path) 110 111 112def _GetSVNRevision(commitish='history/master-chromium'): 113 logging.debug('Getting SVN revision ...') 114 commit = merge_common.GetCommandStdout([ 115 'git', 'log', '-n1', '--grep=git-svn-id:', '--format=%H%n%b', commitish]) 116 svn_revision = re.search(r'^git-svn-id: .*@([0-9]+)', commit, 117 flags=re.MULTILINE).group(1) 118 return svn_revision 119 120 121def _MergeWithRepoProp(repo_prop_file, target): 122 chromium_sha = None 123 webview_sha = None 124 with open(repo_prop_file) as prop: 125 for line in prop: 126 project, sha = line.split() 127 if project == 'platform/external/chromium_org-history': 128 chromium_sha = sha 129 elif project == 'platform/frameworks/webview': 130 webview_sha = sha 131 if not chromium_sha or not webview_sha: 132 logging.error('SHA1s for projects not found; invalid build.prop?') 133 return 1 134 chromium_revision = _GetSVNRevision(chromium_sha) 135 logging.info('Merging Chromium at r%s and WebView at %s', chromium_revision, 136 webview_sha) 137 _MergeProjects(chromium_revision, target) 138 139 dest_dir = os.path.join(os.environ['ANDROID_BUILD_TOP'], 'frameworks/webview') 140 merge_common.GetCommandStdout(['git', 'remote', 'update', 'goog'], 141 cwd=dest_dir) 142 merge_common.GetCommandStdout(['git', 'checkout', 143 '-b', 'merge-to-' + target, 144 '-t', 'goog/' + target], cwd=dest_dir) 145 if merge_common.GetCommandStdout(['git', 'rev-list', '-1', 146 'HEAD..' + webview_sha], cwd=dest_dir): 147 logging.debug('Creating merge for framework...') 148 # Merge conflicts cause 'git merge' to return 1, so ignore errors 149 merge_common.GetCommandStdout(['git', 'merge', '--no-commit', '--no-ff', 150 webview_sha], cwd=dest_dir, 151 ignore_errors=True) 152 merge_common.CheckNoConflictsAndCommitMerge( 153 'Merge master-chromium into %s at r%s\n\n%s' % 154 (target, chromium_revision, AUTOGEN_MESSAGE), cwd=dest_dir) 155 upload = merge_common.GetCommandStdout(['git', 'push', 'goog', 156 'HEAD:refs/for/' + target], 157 cwd=dest_dir) 158 logging.info(upload) 159 else: 160 logging.debug('No new commits to merge in framework') 161 return 0 162 163 164def Push(target): 165 """Push the finished snapshot to the Android repository.""" 166 logging.debug('Pushing to server ...') 167 refspec = 'merge-to-%s:%s' % (target, target) 168 for path in merge_common.ALL_PROJECTS: 169 logging.debug('Pushing %s', path) 170 dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path) 171 # Delete the graft before pushing otherwise git will attempt to push all the 172 # grafted-in objects to the server as well as the ones we want. 173 graftfile = os.path.join(dest_dir, '.git', 'info', 'grafts') 174 if os.path.exists(graftfile): 175 os.remove(graftfile) 176 merge_common.GetCommandStdout(['git', 'push', 'goog', refspec], 177 cwd=dest_dir) 178 179 180 181def main(): 182 parser = optparse.OptionParser(usage='%prog [options]') 183 parser.epilog = ('Takes the current master-chromium branch of the Chromium ' 184 'projects in Android and merges them into master to publish ' 185 'them.') 186 parser.add_option( 187 '', '--svn_revision', '--release', 188 default=None, 189 help=('Merge to the specified archived master-chromium SVN revision,' 190 'rather than using HEAD.')) 191 parser.add_option( 192 '', '--repo-prop', 193 default=None, metavar='FILE', 194 help=('Merge to the revisions specified in this repo.prop file.')) 195 parser.add_option( 196 '', '--push', 197 default=False, action='store_true', 198 help=('Push the result of a previous merge to the server.')) 199 parser.add_option( 200 '', '--target', 201 default='master', metavar='BRANCH', 202 help=('Target branch to push to. Defaults to master.')) 203 (options, args) = parser.parse_args() 204 if args: 205 parser.print_help() 206 return 1 207 208 logging.basicConfig(format='%(message)s', level=logging.DEBUG, 209 stream=sys.stdout) 210 211 if options.push: 212 Push(options.target) 213 elif options.repo_prop: 214 return _MergeWithRepoProp(os.path.expanduser(options.repo_prop), 215 options.target) 216 elif options.svn_revision: 217 _MergeProjects(options.svn_revision, options.target) 218 else: 219 svn_revision = _GetSVNRevision() 220 _MergeProjects(svn_revision, options.target) 221 222 return 0 223 224if __name__ == '__main__': 225 sys.exit(main()) 226