def _GetAbsPath(project): """Returns the full path to a given project (either Chromium or Android).""" if project in merge_common.ALL_PROJECTS: abs_path = os.path.join(merge_common.REPOSITORY_ROOT, project) else: abs_path = os.path.join(os.environ['ANDROID_BUILD_TOP'], project) if not os.path.exists(abs_path): raise merge_common.MergeError('Cannot find path ' + abs_path) return abs_path
def Snapshot(svn_revision, root_sha1, release, target, unattended, buildspec_url): """Takes a snapshot of the Chromium tree and merges it into Android. Android makefiles and a top-level NOTICE file are generated and committed after the merge. Args: svn_revision: The SVN revision in the Chromium repository to merge from. root_sha1: The sha1 in the Chromium git mirror to merge from. release: The Chromium release version to merge from (e.g. "30.0.1599.20"). Only one of svn_revision, root_sha1 and release should be specified. target: The target branch to merge to. unattended: Run in unattended mode. buildspec_url: URL for buildspec repository, used when merging a release. Returns: True if new commits were merged; False if no new commits were present. """ if svn_revision: svn_revision, root_sha1 = _GetSVNRevisionAndSHA1( SRC_GIT_BRANCH, svn_revision) elif root_sha1: svn_revision = _GetSVNRevisionFromSha(root_sha1) if svn_revision and root_sha1: version = svn_revision if not merge_common.GetCommandStdout( ['git', 'rev-list', '-1', 'HEAD..' + root_sha1]): logging.info('No new commits to merge at %s (%s)', svn_revision, root_sha1) return False elif release: version = release root_sha1 = None else: raise merge_common.MergeError('No merge source specified') logging.info('Snapshotting Chromium at %s (%s)', version, root_sha1) # 1. Merge, accounting for excluded directories _MergeProjects(version, root_sha1, target, unattended, buildspec_url) # 2. Generate Android makefiles _GenerateMakefiles(version, unattended) # 3. Check for incompatible licenses _CheckLicenses() # 4. Generate Android NOTICE file _GenerateNoticeFile(version) # 5. Generate LASTCHANGE file _GenerateLastChange(version) return True
def _MergeWithRepoProp(repo_prop_file, target_branch, force): """Performs a merge using a repo.prop file (from Android build waterfall). This does NOT merge (unless forced with force=True) the pinned revisions in repo.prop, as a repo.prop can snapshot an intermediate state (between two automerger cycles). Instead, this looks up the archived snapshot (generated by the chromium->master-chromium auto-merger) which is closest to the given repo.prop (following the main Chromium project) and merges that one. If the projects revisions don't match, it fails with detailed error messages. Args: repo_prop_file: Path to a downloaded repo.prop file. target_branch: name of the target branch to merget to. force: ignores the aforementioned check and merged anyways. """ chromium_sha = None webview_sha = None repo_shas = {} # 'project/path' -> 'sha' with open(repo_prop_file) as prop: for line in prop: repo, sha = line.split() # Translate the Android repo paths into the relative project paths used in # merge_common (e.g., platform/external/chromium_org/foo -> foo). m = (re.match(r'^platform/(frameworks/.+)$', repo) or re.match( r'^platform/external/chromium_org/?(.*?)(-history)?$', repo)) if m: project = m.group(1) if m.group( 1) else '.' # '.' = Main project. repo_shas[project] = sha chromium_sha = repo_shas.get('.') webview_sha = repo_shas.get(WEBVIEW_PROJECT) if not chromium_sha or not webview_sha: raise merge_common.MergeError('SHAs for projects not found; ' 'invalid build.prop?') # Check that the revisions in repo.prop and the on in the archive match. archived_chromium_revision = _GetNearestUpstreamAbbrevSHA(chromium_sha) logging.info('Merging Chromium at %s and WebView at %s', archived_chromium_revision, webview_sha) _MergeChromiumProjects(archived_chromium_revision, target_branch, repo_shas, force) _CheckoutSingleProject(WEBVIEW_PROJECT, target_branch) _MergeSingleProject(WEBVIEW_PROJECT, webview_sha, archived_chromium_revision, target_branch, flatten=False)
def _GetNearestUpstreamAbbrevSHA(reference='history/master-chromium'): """Returns the abbrev. upstream SHA which closest to the given reference.""" logging.debug('Getting upstream SHA for %s...', reference) merge_common.GetCommandStdout(['git', 'remote', 'update', 'history']) upstream_commit = merge_common.Abbrev( merge_common.GetCommandStdout( ['git', 'merge-base', 'history/upstream-master', reference])) # Pedantic check: look for the existence of a merge commit which contains the # |upstream_commit| in its message and is its children. merge_parents = merge_common.GetCommandStdout([ 'git', 'rev-list', reference, '--grep', upstream_commit, '--merges', '--parents', '-1' ]) if upstream_commit not in merge_parents: raise merge_common.MergeError( 'Found upstream commit %s, but the merge child (%s) could not be found ' 'or is not a parent of the upstream SHA') logging.debug('Found nearest Chromium revision %s', upstream_commit) return upstream_commit
def _MergeChromiumProjects(revision, target_branch, repo_shas=None, force=False): """Merges the Chromium projects from master-chromium to target_branch. The larger projects' histories are flattened in the process. When repo_shas != None, it checks that the SHAs of the projects in the archive match exactly the SHAs of the projects in repo.prop. Args: revision: Abbrev. commitish in the main Chromium repository. target_branch: target branch name to merge and push to. repo_shas: optional dict. of expected revisions (only for --repo-prop). force: True: merge anyways using the SHAs from repo.prop; False: bail out if projects mismatch (archive vs repo.prop). """ # Sync and checkout ToT for all projects (creating the merge-to-XXX branch) # and fetch the archive snapshot. fetched_shas = {} remote_ref = 'refs/archive/chromium-%s' % revision for project in merge_common.PROJECTS_WITH_FLAT_HISTORY: _CheckoutSingleProject(project, target_branch) fetched_shas[project] = _FetchSingleProject(project, 'history', remote_ref) for project in merge_common.PROJECTS_WITH_FULL_HISTORY: _CheckoutSingleProject(project, target_branch) fetched_shas[project] = _FetchSingleProject(project, 'goog', remote_ref) if repo_shas: project_shas_mismatch = False for project, merge_sha in fetched_shas.items( ): # the dict can be modified. expected_sha = repo_shas.get(project) if expected_sha != merge_sha: logging.warn( 'The SHA for project %s specified in the repo.prop (%s) ' 'and the one in the archive (%s) differ.', project, expected_sha, merge_sha) dest_dir = _GetAbsPath(project) if expected_sha is None: reason = 'cannot find a SHA in the repo.pro for %s' % project elif _IsAncestor(merge_sha, expected_sha, cwd=dest_dir): reason = 'the SHA in repo.prop is ahead of the SHA in the archive. ' log_cmd = [ 'git', 'log', '--oneline', '--graph', '--max-count=10', '%s..%s' % (merge_sha, expected_sha) ] log_cmd_output = merge_common.GetCommandStdout( log_cmd, cwd=dest_dir) reason += 'showing partial log (%s): \n %s' % ( ' '.join(log_cmd), log_cmd_output) elif _IsAncestor(expected_sha, merge_sha, cwd=dest_dir): reason = 'The SHA is already merged in the archive' else: reason = 'The project history diverged. Consult your Git historian.' project_shas_mismatch = True if force: logging.debug( 'Merging the SHA in repo.prop anyways (due to --force)' ) fetched_shas[project] = expected_sha else: logging.debug('Reason: %s', reason) if not force and project_shas_mismatch: raise merge_common.MergeError( 'The revision of some projects in the archive is different from the ' 'one provided in build.prop. See the log for more details. Re-run ' 'with --force to continue.') for project in merge_common.PROJECTS_WITH_FLAT_HISTORY: _MergeSingleProject(project, fetched_shas[project], revision, target_branch, flatten=True) for project in merge_common.PROJECTS_WITH_FULL_HISTORY: _MergeSingleProject(project, fetched_shas[project], revision, target_branch, flatten=False)