182c98161b9cd8e8f94e00d89b6fd56540473b3d4Torne (Richard Coles)#!/usr/bin/python
233e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)#
333e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# Copyright (C) 2012 The Android Open Source Project
433e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)#
533e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# Licensed under the Apache License, Version 2.0 (the "License");
633e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# you may not use this file except in compliance with the License.
733e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# You may obtain a copy of the License at
833e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)#
933e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)#      http://www.apache.org/licenses/LICENSE-2.0
1033e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)#
1133e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# Unless required by applicable law or agreed to in writing, software
1233e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# distributed under the License is distributed on an "AS IS" BASIS,
1333e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1433e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# See the License for the specific language governing permissions and
1533e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)# limitations under the License.
1633e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
1782c98161b9cd8e8f94e00d89b6fd56540473b3d4Torne (Richard Coles)"""Merge master-chromium to master within the Android tree."""
1833e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
195d65b76f6747f51bb623d00205aca85581ad4d91Torne (Richard Coles)import logging
2033e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)import optparse
2133e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)import os
2233e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)import re
2333e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)import shutil
2470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucciimport subprocess
2533e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)import sys
2633e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
2733e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)import merge_common
2833e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
2933e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
3033e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)AUTOGEN_MESSAGE = 'This commit was generated by merge_to_master.py.'
3170e496d940c22bf07cd2c5634e1819276ddac416Primiano TucciWEBVIEW_PROJECT = 'frameworks/webview'
3233e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
3333e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
3470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef _GetAbsPath(project):
3570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Returns the full path to a given project (either Chromium or Android)."""
3670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if project in merge_common.ALL_PROJECTS:
3770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    abs_path = os.path.join(merge_common.REPOSITORY_ROOT, project)
3870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  else:
3970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    abs_path = os.path.join(os.environ['ANDROID_BUILD_TOP'], project)
4070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if not os.path.exists(abs_path):
4170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    raise merge_common.MergeError('Cannot find path ' + abs_path)
4270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  return abs_path
4382c98161b9cd8e8f94e00d89b6fd56540473b3d4Torne (Richard Coles)
4470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
4570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef _CheckoutSingleProject(project, target_branch):
4670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Checks out the tip of the target_branch into a local branch (merge-to-XXX).
4782c98161b9cd8e8f94e00d89b6fd56540473b3d4Torne (Richard Coles)
4833e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  Args:
4970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    project: a Chromium project (., third_party/foo) or frameworks/webview.
5070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    target_branch: name of the target branch (in the goog remote).
5133e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  """
5270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  dest_dir = _GetAbsPath(project)
5370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  tracking_branch = 'goog/' + target_branch
5470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  logging.debug('Check out %-45s at %-16s', project, tracking_branch)
5570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_common.GetCommandStdout(['git', 'remote', 'update', 'goog'],
5670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                cwd=dest_dir)
5770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_common.GetCommandStdout(['git', 'checkout',
5870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                 '-b', 'merge-to-' + target_branch,
5970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                 '-t', tracking_branch], cwd=dest_dir)
6070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
6170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
6270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef _FetchSingleProject(project, remote, remote_ref):
6370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Fetches a remote ref for the given project and returns the fetched SHA.
6470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
6570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  Args:
6670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    project: a Chromium project (., third_party/foo) or frameworks/webview.
6770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    remote: Git remote name (goog for most projects, history for squashed ones).
6870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    remote_ref: the remote ref to fetch (e.g., refs/archive/chromium-XXX).
6970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
7070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  Returns:
7170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    The SHA1 of the FETCH_HEAD.
7270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """
7370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  dest_dir = _GetAbsPath(project)
7470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  logging.debug('Fetch     %-45s %s:%s', project, remote, remote_ref)
7570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_common.GetCommandStdout(['git', 'fetch', remote, remote_ref],
7670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                cwd=dest_dir)
7770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  return merge_common.GetCommandStdout(['git', 'rev-parse', 'FETCH_HEAD'],
7870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                       cwd=dest_dir).strip()
7970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
8070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
8170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef _MergeSingleProject(project, merge_sha, revision, target_branch, flatten):
8270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Merges a single project at a given SHA.
8370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
8470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  Args:
8570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    project: a Chromium project (., third_party/foo) or frameworks/webview.
8670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    merge_sha: the SHA to merge.
8770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    revision: Abbrev. commitish in the main Chromium repository.
8870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    target_branch: name of the target branch.
8970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    flatten: True: squash history while merging; False: perform a normal merge.
9070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """
9170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  dest_dir = _GetAbsPath(project)
9270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if flatten:
9333e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)    # Make the previous merges into grafts so we can do a correct merge.
9470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    old_sha = merge_common.GetCommandStdout(['git', 'rev-parse', 'HEAD'],
9570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                            cwd=dest_dir).strip()
9633e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)    merge_log = os.path.join(dest_dir, '.merged-revisions')
9733e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)    if os.path.exists(merge_log):
9833e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)      shutil.copyfile(merge_log,
9933e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)                      os.path.join(dest_dir, '.git', 'info', 'grafts'))
10070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
10170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  # Early out if there is nothing to merge.
10270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if not merge_common.GetCommandStdout(['git', 'rev-list', '-1',
10370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                        'HEAD..' + merge_sha], cwd=dest_dir):
10470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    logging.debug('No new commits to merge in project %s', project)
10570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    return
10670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
10770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  logging.debug('Merging project %s (flatten: %s)...', project, flatten)
10870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_cmd = ['git', 'merge', '--no-commit']
10970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_cmd += ['--squash'] if flatten else ['--no-ff']
11070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_cmd += [merge_sha]
11170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  # Merge conflicts cause 'git merge' to return 1, so ignore errors
11270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_common.GetCommandStdout(merge_cmd, cwd=dest_dir, ignore_errors=True)
11370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
11470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if flatten:
11570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    dirs_to_prune = merge_common.PRUNE_WHEN_FLATTENING.get(project, [])
11670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    if dirs_to_prune:
11770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      merge_common.GetCommandStdout(['git', 'rm', '--ignore-unmatch', '-rf'] +
11870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                    dirs_to_prune, cwd=dest_dir)
11970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
12070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if project in merge_common.ALL_PROJECTS:
12170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    commit_msg = 'Merge from Chromium at DEPS revision %s' % revision
12270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  else:
12370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    commit_msg = 'Merge master-chromium into %s at %s' % (target_branch,
12470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                                          revision)
12570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  commit_msg += '\n\n' + AUTOGEN_MESSAGE
12670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_common.CheckNoConflictsAndCommitMerge(commit_msg, cwd=dest_dir)
12770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
12870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if flatten:
12970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    # Generate the new grafts file and commit it on top of the merge.
13070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    new_sha = merge_common.GetCommandStdout(['git', 'rev-parse', 'HEAD'],
13170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                            cwd=dest_dir).strip()
13270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    with open(merge_log, 'a+') as f:
13370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      f.write('%s %s %s\n' % (new_sha, old_sha, merge_sha))
13470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    merge_common.GetCommandStdout(['git', 'add', '.merged-revisions'],
135e1a149c76208a84c7595203a465ae1218ded9d34Torne (Richard Coles)                                  cwd=dest_dir)
13670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    merge_common.GetCommandStdout(
13770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        ['git', 'commit', '-m',
13870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci         'Record Chromium merge at DEPS revision %s\n\n%s' %
13970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci         (revision, AUTOGEN_MESSAGE)], cwd=dest_dir)
14070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
14170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
14270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef _IsAncestor(ref1, ref2, cwd):
14370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Checks whether ref1 is a ancestor of ref2 in the given Git repo."""
14470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  cmd = ['git', 'merge-base', '--is-ancestor', ref1, ref2]
14570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  ret = subprocess.call(cmd, cwd=cwd)
14670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if ret == 0:
14770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    return True
14870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  elif ret == 1:
14970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    return False
15070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  else:
15170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    raise merge_common.CommandError(ret, ' '.join(cmd), cwd, 'N/A', 'N/A')
15270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
15370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
15470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef _MergeChromiumProjects(revision, target_branch, repo_shas=None,
15570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                           force=False):
15670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Merges the Chromium projects from master-chromium to target_branch.
15770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
15870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  The larger projects' histories are flattened in the process.
15970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  When repo_shas != None, it checks that the SHAs of the projects in the
16070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  archive match exactly the SHAs of the projects in repo.prop.
16170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
16270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  Args:
16370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    revision: Abbrev. commitish in the main Chromium repository.
16470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    target_branch: target branch name to merge and push to.
16570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    repo_shas: optional dict. of expected revisions (only for --repo-prop).
16670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    force: True: merge anyways using the SHAs from repo.prop; False: bail out if
16770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                 projects mismatch (archive vs repo.prop).
16870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """
16970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  # Sync and checkout ToT for all projects (creating the merge-to-XXX branch)
17070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  # and fetch the archive snapshot.
17170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  fetched_shas = {}
17270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  remote_ref = 'refs/archive/chromium-%s' % revision
17370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  for project in merge_common.PROJECTS_WITH_FLAT_HISTORY:
17470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    _CheckoutSingleProject(project, target_branch)
17570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    fetched_shas[project] = _FetchSingleProject(project, 'history', remote_ref)
17670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  for project in merge_common.PROJECTS_WITH_FULL_HISTORY:
17770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    _CheckoutSingleProject(project, target_branch)
17870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    fetched_shas[project] = _FetchSingleProject(project, 'goog', remote_ref)
17970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
18070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if repo_shas:
18170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    project_shas_mismatch = False
18270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    for project, merge_sha in fetched_shas.items():  # the dict can be modified.
18370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      expected_sha = repo_shas.get(project)
18470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      if expected_sha != merge_sha:
18570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        logging.warn('The SHA for project %s specified in the repo.prop (%s) '
18670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                     'and the one in the archive (%s) differ.',
18770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                     project, expected_sha, merge_sha)
18870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        dest_dir = _GetAbsPath(project)
18970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        if expected_sha is None:
19070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          reason = 'cannot find a SHA in the repo.pro for %s' % project
19170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        elif _IsAncestor(merge_sha, expected_sha, cwd=dest_dir):
19270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          reason = 'the SHA in repo.prop is ahead of the SHA in the archive. '
19370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          log_cmd = ['git', 'log', '--oneline', '--graph', '--max-count=10',
19470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                     '%s..%s' % (merge_sha, expected_sha)]
19570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          log_cmd_output = merge_common.GetCommandStdout(log_cmd, cwd=dest_dir)
19670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          reason += 'showing partial log (%s): \n %s' % (' '.join(log_cmd),
19770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                                         log_cmd_output)
19870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        elif _IsAncestor(expected_sha, merge_sha, cwd=dest_dir):
19970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          reason = 'The SHA is already merged in the archive'
20070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        else:
20170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          reason = 'The project history diverged. Consult your Git historian.'
20270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
20370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        project_shas_mismatch = True
20470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        if force:
20570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          logging.debug('Merging the SHA in repo.prop anyways (due to --force)')
20670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          fetched_shas[project] = expected_sha
20770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        else:
20870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          logging.debug('Reason: %s', reason)
20970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    if not force and project_shas_mismatch:
21070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      raise merge_common.MergeError(
21170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          'The revision of some projects in the archive is different from the '
21270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          'one provided in build.prop. See the log for more details. Re-run '
21370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          'with --force to continue.')
21470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
21570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  for project in merge_common.PROJECTS_WITH_FLAT_HISTORY:
21670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    _MergeSingleProject(project, fetched_shas[project], revision, target_branch,
21770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                        flatten=True)
21870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  for project in merge_common.PROJECTS_WITH_FULL_HISTORY:
21970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    _MergeSingleProject(project, fetched_shas[project], revision, target_branch,
22070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                        flatten=False)
22170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
22270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
22370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef _GetNearestUpstreamAbbrevSHA(reference='history/master-chromium'):
22470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Returns the abbrev. upstream SHA which closest to the given reference."""
22570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  logging.debug('Getting upstream SHA for %s...', reference)
22670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_common.GetCommandStdout(['git', 'remote', 'update', 'history'])
22770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  upstream_commit = merge_common.Abbrev(merge_common.GetCommandStdout([
22870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      'git', 'merge-base', 'history/upstream-master', reference]))
22970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
23070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  # Pedantic check: look for the existence of a merge commit which contains the
23170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  # |upstream_commit| in its message and is its children.
23270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_parents = merge_common.GetCommandStdout([
23370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      'git', 'rev-list', reference, '--grep', upstream_commit, '--merges',
23470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      '--parents', '-1'])
23570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if upstream_commit not in merge_parents:
23670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    raise merge_common.MergeError(
23770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        'Found upstream commit %s, but the merge child (%s) could not be found '
23870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        'or is not a parent of the upstream SHA')
23970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  logging.debug('Found nearest Chromium revision %s', upstream_commit)
24070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  return upstream_commit
24170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
24270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
24370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef _MergeWithRepoProp(repo_prop_file, target_branch, force):
24470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Performs a merge using a repo.prop file (from Android build waterfall).
24570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
24670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  This does NOT merge (unless forced with force=True) the pinned
24770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  revisions in repo.prop, as a repo.prop can snapshot an intermediate state
24870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  (between two automerger cycles). Instead, this looks up the archived snapshot
24970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  (generated by the chromium->master-chromium auto-merger) which is closest to
25070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  the given repo.prop (following the main Chromium project) and merges that one.
25170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  If the projects revisions don't match, it fails with detailed error messages.
25270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
25370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  Args:
25470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    repo_prop_file: Path to a downloaded repo.prop file.
25570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    target_branch: name of the target branch to merget to.
25670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    force: ignores the aforementioned check and merged anyways.
25770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """
25858324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)  chromium_sha = None
25958324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)  webview_sha = None
26070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  repo_shas = {}  # 'project/path' -> 'sha'
26158324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)  with open(repo_prop_file) as prop:
26258324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)    for line in prop:
26370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      repo, sha = line.split()
26470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      # Translate the Android repo paths into the relative project paths used in
26570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      # merge_common (e.g., platform/external/chromium_org/foo -> foo).
26670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      m = (
26770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          re.match(r'^platform/(frameworks/.+)$', repo) or
26870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci          re.match(r'^platform/external/chromium_org/?(.*?)(-history)?$', repo))
26970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      if m:
27070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        project = m.group(1) if m.group(1) else '.'  # '.' = Main project.
27170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci        repo_shas[project] = sha
27270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
27370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  chromium_sha = repo_shas.get('.')
27470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  webview_sha = repo_shas.get(WEBVIEW_PROJECT)
27558324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)  if not chromium_sha or not webview_sha:
27670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    raise merge_common.MergeError('SHAs for projects not found; '
27770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                  'invalid build.prop?')
27858324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)
27970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  # Check that the revisions in repo.prop and the on in the archive match.
28070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  archived_chromium_revision = _GetNearestUpstreamAbbrevSHA(chromium_sha)
28170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  logging.info('Merging Chromium at %s and WebView at %s',
28270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci               archived_chromium_revision, webview_sha)
28370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  _MergeChromiumProjects(archived_chromium_revision, target_branch, repo_shas,
28470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                         force)
28570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
28670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  _CheckoutSingleProject(WEBVIEW_PROJECT, target_branch)
28770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  _MergeSingleProject(WEBVIEW_PROJECT, webview_sha,
28870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                      archived_chromium_revision, target_branch, flatten=False)
28958324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)
29058324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)
29170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tuccidef Push(target_branch):
29270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """Push the finished snapshot to the Android repository.
29370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
29470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  Creates first a CL for frameworks/webview (if the merge-to-XXX branch exists)
29570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  then wait for user confirmation and pushes the Chromium merges. This is to
29670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  give an opportunity to get a +2 for  frameworks/webview and then push both
29770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  frameworks/webview and the Chromium projects atomically(ish).
29870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
29970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  Args:
30070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    target_branch: name of the target branch (in the goog remote).
30170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  """
30270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  merge_branch = 'merge-to-%s' % target_branch
30370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
30470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  # Create a Gerrit CL for the frameworks/webview project (if needed).
30570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  dest_dir = _GetAbsPath(WEBVIEW_PROJECT)
30670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  did_upload_webview_cl = False
30770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if merge_common.GetCommandStdout(['git', 'branch', '--list', merge_branch],
30870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                   cwd=dest_dir):
30970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    # Check that there was actually something to merge.
31070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    merge_range = 'goog/%s..%s' % (target_branch, merge_branch)
31170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    if merge_common.GetCommandStdout(['git', 'rev-list', '-1', merge_range],
31270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                     cwd=dest_dir):
31370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      logging.info('Uploading a merge CL for %s...', WEBVIEW_PROJECT)
31470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      refspec = '%s:refs/for/%s' % (merge_branch, target_branch)
31570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      upload = merge_common.GetCommandStdout(['git', 'push', 'goog', refspec],
31670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                                             cwd=dest_dir)
31770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      logging.info(upload)
31870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      did_upload_webview_cl = True
31970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
32070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  prompt_msg = 'About push the Chromium projects merge. '
32170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if not did_upload_webview_cl:
32270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    logging.info('No merge CL needed for %s.', WEBVIEW_PROJECT)
32370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  else:
32470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    prompt_msg += ('At this point you should have the CL +2-ed and merge it '
32570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                   'together with this push.')
32670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  prompt_msg += '\nPress "y" to continue: '
32770e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  if raw_input(prompt_msg) != 'y':
32870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    logging.warn('Push aborted by the user!')
32970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    return
33070e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci
33170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  logging.debug('Pushing Chromium projects to %s ...', target_branch)
33270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  refspec = '%s:%s' % (merge_branch, target_branch)
33309ead754abd2cfcffea54040b1c9ac98db8af834Torne (Richard Coles)  for path in merge_common.ALL_PROJECTS:
33409ead754abd2cfcffea54040b1c9ac98db8af834Torne (Richard Coles)    logging.debug('Pushing %s', path)
33570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    dest_dir = _GetAbsPath(path)
336e1a149c76208a84c7595203a465ae1218ded9d34Torne (Richard Coles)    # Delete the graft before pushing otherwise git will attempt to push all the
337e1a149c76208a84c7595203a465ae1218ded9d34Torne (Richard Coles)    # grafted-in objects to the server as well as the ones we want.
338e1a149c76208a84c7595203a465ae1218ded9d34Torne (Richard Coles)    graftfile = os.path.join(dest_dir, '.git', 'info', 'grafts')
339e1a149c76208a84c7595203a465ae1218ded9d34Torne (Richard Coles)    if os.path.exists(graftfile):
340e1a149c76208a84c7595203a465ae1218ded9d34Torne (Richard Coles)      os.remove(graftfile)
3416adc8a2e72d90b3a5c1cf7cf7ec7be2f6c974851Torne (Richard Coles)    merge_common.GetCommandStdout(['git', 'push', 'goog', refspec],
3426adc8a2e72d90b3a5c1cf7cf7ec7be2f6c974851Torne (Richard Coles)                                  cwd=dest_dir)
34309ead754abd2cfcffea54040b1c9ac98db8af834Torne (Richard Coles)
34409ead754abd2cfcffea54040b1c9ac98db8af834Torne (Richard Coles)
34533e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)def main():
34633e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  parser = optparse.OptionParser(usage='%prog [options]')
34733e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  parser.epilog = ('Takes the current master-chromium branch of the Chromium '
34833e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)                   'projects in Android and merges them into master to publish '
34933e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)                   'them.')
35033e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  parser.add_option(
35170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      '', '--revision',
352e1a149c76208a84c7595203a465ae1218ded9d34Torne (Richard Coles)      default=None,
35370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      help=('Merge to the specified archived master-chromium revision (abbrev. '
35470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci            'SHA or release version) rather than using HEAD. e.g., '
35570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci            '--revision=a1b2c3d4e5f6 or --revision=38.0.2125.24'))
356e1a149c76208a84c7595203a465ae1218ded9d34Torne (Richard Coles)  parser.add_option(
35758324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)      '', '--repo-prop',
35858324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)      default=None, metavar='FILE',
35958324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)      help=('Merge to the revisions specified in this repo.prop file.'))
36058324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)  parser.add_option(
36170e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      '', '--force',
36270e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      default=False, action='store_true',
36370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci      help=('Skip history checks and merged anyways (only for --repo-prop).'))
36470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  parser.add_option(
36514ac2c8d49fa1f5f12e6a80af93f1c50312d80a8Torne (Richard Coles)      '', '--push',
36682c98161b9cd8e8f94e00d89b6fd56540473b3d4Torne (Richard Coles)      default=False, action='store_true',
36714ac2c8d49fa1f5f12e6a80af93f1c50312d80a8Torne (Richard Coles)      help=('Push the result of a previous merge to the server.'))
3686adc8a2e72d90b3a5c1cf7cf7ec7be2f6c974851Torne (Richard Coles)  parser.add_option(
3696adc8a2e72d90b3a5c1cf7cf7ec7be2f6c974851Torne (Richard Coles)      '', '--target',
3706adc8a2e72d90b3a5c1cf7cf7ec7be2f6c974851Torne (Richard Coles)      default='master', metavar='BRANCH',
3716adc8a2e72d90b3a5c1cf7cf7ec7be2f6c974851Torne (Richard Coles)      help=('Target branch to push to. Defaults to master.'))
37233e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  (options, args) = parser.parse_args()
37333e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  if args:
37433e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)    parser.print_help()
37533e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)    return 1
37633e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
3775d65b76f6747f51bb623d00205aca85581ad4d91Torne (Richard Coles)  logging.basicConfig(format='%(message)s', level=logging.DEBUG,
3785d65b76f6747f51bb623d00205aca85581ad4d91Torne (Richard Coles)                      stream=sys.stdout)
3795d65b76f6747f51bb623d00205aca85581ad4d91Torne (Richard Coles)
38014ac2c8d49fa1f5f12e6a80af93f1c50312d80a8Torne (Richard Coles)  if options.push:
3816adc8a2e72d90b3a5c1cf7cf7ec7be2f6c974851Torne (Richard Coles)    Push(options.target)
38258324375a4ba319e7b114f3afece23a98de3f373Torne (Richard Coles)  elif options.repo_prop:
38370e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    _MergeWithRepoProp(os.path.expanduser(options.repo_prop),
38470e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci                       options.target, options.force)
38570e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci  elif options.revision:
38670e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    _MergeChromiumProjects(options.revision, options.target)
38714ac2c8d49fa1f5f12e6a80af93f1c50312d80a8Torne (Richard Coles)  else:
38870e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    first_upstream_sha = _GetNearestUpstreamAbbrevSHA()
38970e496d940c22bf07cd2c5634e1819276ddac416Primiano Tucci    _MergeChromiumProjects(first_upstream_sha, options.target)
39082c98161b9cd8e8f94e00d89b6fd56540473b3d4Torne (Richard Coles)
39133e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  return 0
39233e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)
39333e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)if __name__ == '__main__':
39433e5c69dc781743cc2560bef57763170b61bae3fTorne (Richard Coles)  sys.exit(main())
395