Home | History | Annotate | Download | only in tools
      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 
     19 import logging
     20 import optparse
     21 import os
     22 import re
     23 import shutil
     24 import sys
     25 
     26 import merge_common
     27 
     28 
     29 AUTOGEN_MESSAGE = 'This commit was generated by merge_to_master.py.'
     30 
     31 
     32 def _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 
    112 def _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 
    121 def _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 
    164 def 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 
    181 def 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 
    224 if __name__ == '__main__':
    225   sys.exit(main())
    226