def _GetSVNRevisionAndSHA1(git_branch, svn_revision): logging.debug('Getting SVN revision and SHA1 ...') if svn_revision == 'HEAD': # Just use the latest commit. commit = merge_common.GetCommandStdout([ 'git', 'log', '-n1', '--grep=git-svn-id:', '--format=%H%n%b', git_branch ]) sha1 = commit.split()[0] svn_revision = _ParseSvnRevisionFromGitCommitMessage(commit) return (svn_revision, sha1) if svn_revision is None: # Fetch LKGR from upstream. svn_revision = GetLKGR() output = merge_common.GetCommandStdout([ 'git', 'log', '--grep=git-svn-id: .*@%s' % svn_revision, '--format=%H', git_branch ]) if not output: raise merge_common.TemporaryMergeError( 'Revision %s not found in git repo.' % svn_revision) # The log grep will sometimes match reverts/reapplies of commits. We take the # oldest (last) match because the first time it appears in history is # overwhelmingly likely to be the correct commit. sha1 = output.split()[-1] return (svn_revision, sha1)
def _GenerateLastChange(version): """Write a build/util/LASTCHANGE file containing the current revision. The revision number is compiled into the binary at build time from this file. Args: version: The version to mention in generated commit messages. """ logging.debug('Updating LASTCHANGE ...') svn_revision, sha1 = _GetSVNRevisionAndSHA1('HEAD', 'HEAD') with open( os.path.join(merge_common.REPOSITORY_ROOT, 'build/util/LASTCHANGE'), 'w') as f: f.write('LASTCHANGE=%s\n' % svn_revision) merge_common.GetCommandStdout( ['git', 'add', '-f', 'build/util/LASTCHANGE']) logging.debug('Updating LASTCHANGE.blink ...') with open( os.path.join(merge_common.REPOSITORY_ROOT, 'build/util/LASTCHANGE.blink'), 'w') as f: f.write('LASTCHANGE=%s\n' % _GetBlinkRevision()) merge_common.GetCommandStdout( ['git', 'add', '-f', 'build/util/LASTCHANGE.blink']) if _ModifiedFilesInIndex(): merge_common.GetCommandStdout([ 'git', 'commit', '-m', 'Update LASTCHANGE file after merge of Chromium at %s\n\n%s' % (version, AUTOGEN_MESSAGE) ])
def Push(target_branch): """Push the finished snapshot to the Android repository. Creates first a CL for frameworks/webview (if the merge-to-XXX branch exists) then wait for user confirmation and pushes the Chromium merges. This is to give an opportunity to get a +2 for frameworks/webview and then push both frameworks/webview and the Chromium projects atomically(ish). Args: target_branch: name of the target branch (in the goog remote). """ merge_branch = 'merge-to-%s' % target_branch # Create a Gerrit CL for the frameworks/webview project (if needed). dest_dir = _GetAbsPath(WEBVIEW_PROJECT) did_upload_webview_cl = False if merge_common.GetCommandStdout(['git', 'branch', '--list', merge_branch], cwd=dest_dir): # Check that there was actually something to merge. merge_range = 'goog/%s..%s' % (target_branch, merge_branch) if merge_common.GetCommandStdout( ['git', 'rev-list', '-1', merge_range], cwd=dest_dir): logging.info('Uploading a merge CL for %s...', WEBVIEW_PROJECT) refspec = '%s:refs/for/%s' % (merge_branch, target_branch) upload = merge_common.GetCommandStdout( ['git', 'push', 'goog', refspec], cwd=dest_dir) logging.info(upload) did_upload_webview_cl = True prompt_msg = 'About push the Chromium projects merge. ' if not did_upload_webview_cl: logging.info('No merge CL needed for %s.', WEBVIEW_PROJECT) else: prompt_msg += ( 'At this point you should have the CL +2-ed and merge it ' 'together with this push.') prompt_msg += '\nPress "y" to continue: ' if raw_input(prompt_msg) != 'y': logging.warn('Push aborted by the user!') return logging.debug('Pushing Chromium projects to %s ...', target_branch) refspec = '%s:%s' % (merge_branch, target_branch) for path in merge_common.ALL_PROJECTS: logging.debug('Pushing %s', path) dest_dir = _GetAbsPath(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 _ReadGitFile(sha1, path, git_url=None, git_branch=None): """Reads a file from a (possibly remote) git project at a specific revision. Args: sha1: The SHA1 at which to read. path: The relative path of the file to read. git_url: The URL of the git server, if reading a remote project. git_branch: The branch to fetch, if reading a remote project. Returns: The contents of the specified file. """ if git_url: merge_common.GetCommandStdout( ['git', 'fetch', '-f', git_url, git_branch]) return merge_common.GetCommandStdout( ['git', 'show', '%s:%s' % (sha1, path)])
def _CheckoutSingleProject(project, target_branch): """Checks out the tip of the target_branch into a local branch (merge-to-XXX). Args: project: a Chromium project (., third_party/foo) or frameworks/webview. target_branch: name of the target branch (in the goog remote). """ dest_dir = _GetAbsPath(project) tracking_branch = 'goog/' + target_branch logging.debug('Check out %-45s at %-16s', project, tracking_branch) merge_common.GetCommandStdout(['git', 'remote', 'update', 'goog'], cwd=dest_dir) merge_common.GetCommandStdout([ 'git', 'checkout', '-b', 'merge-to-' + target_branch, '-t', tracking_branch ], cwd=dest_dir)
def _FetchSingleProject(project, remote, remote_ref): """Fetches a remote ref for the given project and returns the fetched SHA. Args: project: a Chromium project (., third_party/foo) or frameworks/webview. remote: Git remote name (goog for most projects, history for squashed ones). remote_ref: the remote ref to fetch (e.g., refs/archive/chromium-XXX). Returns: The SHA1 of the FETCH_HEAD. """ dest_dir = _GetAbsPath(project) logging.debug('Fetch %-45s %s:%s', project, remote, remote_ref) merge_common.GetCommandStdout(['git', 'fetch', remote, remote_ref], cwd=dest_dir) return merge_common.GetCommandStdout(['git', 'rev-parse', 'FETCH_HEAD'], cwd=dest_dir).strip()
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 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): 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 _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 _GenerateNoticeFile(version): """Generates and commits a NOTICE file containing code licenses. This covers all third-party code (from Android's perspective) that lives in the Chromium tree. Args: version: The version to mention in generated commit messages. """ logging.debug('Regenerating NOTICE file ...') contents = webview_licenses.GenerateNoticeFile() with open(os.path.join(merge_common.REPOSITORY_ROOT, 'NOTICE'), 'w') as f: f.write(contents) merge_common.GetCommandStdout(['git', 'add', 'NOTICE']) # Only try to commit the NOTICE update if the file has actually changed. if _ModifiedFilesInIndex(): merge_common.GetCommandStdout([ 'git', 'commit', '-m', 'Update NOTICE file after merge of Chromium at %s\n\n%s' % (version, AUTOGEN_MESSAGE) ])
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 Push(version, target): """Push the finished snapshot to the Android repository.""" src = 'merge-from-chromium-%s' % version # Use forced pushes ('+' prefix) for the temporary and archive branches in # case they already got updated by a previous (possibly failed?) merge, but # do not force push to the real master-chromium branch as this could erase # downstream changes. refspecs = [ '%s:%s' % (src, target), '+%s:refs/archive/chromium-%s' % (src, version) ] if target == 'master-chromium': refspecs.insert(0, '+%s:master-chromium-merge' % src) for refspec in refspecs: logging.debug('Pushing to server (%s) ...' % refspec) for path in merge_common.ALL_PROJECTS: if path in merge_common.PROJECTS_WITH_FLAT_HISTORY: remote = 'history' else: remote = 'goog' logging.debug('Pushing %s', path) dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path) merge_common.GetCommandStdout(['git', 'push', remote, refspec], cwd=dest_dir)
def _ModifiedFilesInIndex(cwd=merge_common.REPOSITORY_ROOT): """Returns true if git's index contains any changes.""" status = merge_common.GetCommandStdout(['git', 'status', '--porcelain'], cwd=cwd) return re.search(r'^[MADRC]', status, flags=re.MULTILINE) is not None
def _MergeSingleProject(project, merge_sha, revision, target_branch, flatten): """Merges a single project at a given SHA. Args: project: a Chromium project (., third_party/foo) or frameworks/webview. merge_sha: the SHA to merge. revision: Abbrev. commitish in the main Chromium repository. target_branch: name of the target branch. flatten: True: squash history while merging; False: perform a normal merge. """ dest_dir = _GetAbsPath(project) if flatten: # Make the previous merges into grafts so we can do a correct merge. old_sha = merge_common.GetCommandStdout(['git', 'rev-parse', 'HEAD'], cwd=dest_dir).strip() 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')) # Early out if there is nothing to merge. if not merge_common.GetCommandStdout( ['git', 'rev-list', '-1', 'HEAD..' + merge_sha], cwd=dest_dir): logging.debug('No new commits to merge in project %s', project) return logging.debug('Merging project %s (flatten: %s)...', project, flatten) merge_cmd = ['git', 'merge', '--no-commit'] merge_cmd += ['--squash'] if flatten else ['--no-ff'] merge_cmd += [merge_sha] # Merge conflicts cause 'git merge' to return 1, so ignore errors merge_common.GetCommandStdout(merge_cmd, cwd=dest_dir, ignore_errors=True) if flatten: dirs_to_prune = merge_common.PRUNE_WHEN_FLATTENING.get(project, []) if dirs_to_prune: merge_common.GetCommandStdout( ['git', 'rm', '--ignore-unmatch', '-rf'] + dirs_to_prune, cwd=dest_dir) if project in merge_common.ALL_PROJECTS: commit_msg = 'Merge from Chromium at DEPS revision %s' % revision else: commit_msg = 'Merge master-chromium into %s at %s' % (target_branch, revision) commit_msg += '\n\n' + AUTOGEN_MESSAGE merge_common.CheckNoConflictsAndCommitMerge(commit_msg, cwd=dest_dir) if flatten: # Generate the new grafts file and commit it on top of the merge. new_sha = 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_sha, old_sha, merge_sha)) 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' % (revision, AUTOGEN_MESSAGE) ], cwd=dest_dir)
def _GetSVNRevisionFromSha(sha1): commit = merge_common.GetCommandStdout( ['git', 'show', '--format=%H%n%b', sha1]) return _ParseSvnRevisionFromGitCommitMessage(commit)
def _GetBlinkRevision(): commit = merge_common.GetCommandStdout( ['git', 'log', '-n1', '--grep=git-svn-id:', '--format=%H%n%b'], cwd=os.path.join(merge_common.REPOSITORY_ROOT, 'third_party', 'WebKit')) return _ParseSvnRevisionFromGitCommitMessage(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)
def _GenerateMakefiles(version, unattended): """Run gyp to generate the Android build system makefiles. Args: version: The version to mention in generated commit messages. unattended: Run in unattended mode. """ logging.debug('Generating makefiles ...') # TODO(torne): come up with a way to deal with hooks from DEPS properly # TODO(torne): The .tmp files are generated by # third_party/WebKit/Source/WebCore/WebCore.gyp/WebCore.gyp into the source # tree. We should avoid this, or at least use a more specific name to avoid # accidentally removing or adding other files. for path in merge_common.ALL_PROJECTS: dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path) merge_common.GetCommandStdout([ 'git', 'rm', '--ignore-unmatch', 'GypAndroid.*.mk', '*.target.*.mk', '*.host.*.mk', '*.tmp' ], cwd=dest_dir) try: merge_common.GetCommandStdout( ['android_webview/tools/gyp_webview', 'all']) except merge_common.MergeError as e: if not unattended: raise else: for path in merge_common.ALL_PROJECTS: merge_common.GetCommandStdout(['git', 'reset', '--hard'], cwd=os.path.join( merge_common.REPOSITORY_ROOT, path)) raise merge_common.TemporaryMergeError( 'Makefile generation failed: ' + str(e)) for path in merge_common.ALL_PROJECTS: dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path) # git add doesn't have an --ignore-unmatch so we have to do this instead: merge_common.GetCommandStdout(['git', 'add', '-f', 'GypAndroid.*.mk'], ignore_errors=True, cwd=dest_dir) merge_common.GetCommandStdout(['git', 'add', '-f', '*.target.*.mk'], ignore_errors=True, cwd=dest_dir) merge_common.GetCommandStdout(['git', 'add', '-f', '*.host.*.mk'], ignore_errors=True, cwd=dest_dir) merge_common.GetCommandStdout(['git', 'add', '-f', '*.tmp'], ignore_errors=True, cwd=dest_dir) # Only try to commit the makefiles if something has actually changed. if _ModifiedFilesInIndex(dest_dir): merge_common.GetCommandStdout([ 'git', 'commit', '-m', 'Update makefiles after merge of Chromium at %s\n\n%s' % (version, AUTOGEN_MESSAGE) ], cwd=dest_dir)
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 _MergeProjects(version, root_sha1, target, unattended, buildspec_url): """Merges each required Chromium project into the Android repository. .DEPS.git is consulted to determine which revision each project must be merged at. Only a whitelist of required projects are merged. Args: version: The version to mention in generated commit messages. root_sha1: The git hash to merge in the root repository. target: The target branch to merge to. unattended: Run in unattended mode. buildspec_url: URL for buildspec repository, when merging a branch. Raises: TemporaryMergeError: If incompatibly licensed code is left after pruning. """ # The logic for this step lives here, in the Android tree, as it makes no # sense for a Chromium tree to know about this merge. if unattended: branch_create_flag = '-B' else: branch_create_flag = '-b' branch_name = 'merge-from-chromium-%s' % version logging.debug('Parsing DEPS ...') if root_sha1: deps_content = _ReadGitFile(root_sha1, '.DEPS.git') else: deps_content = _ReadGitFile('FETCH_HEAD', 'releases/' + version + '/.DEPS.git', buildspec_url, 'master') deps_vars = _ParseDEPS(deps_content) merge_info = _GetProjectMergeInfo(merge_common.THIRD_PARTY_PROJECTS, deps_vars) for path in merge_info: # webkit needs special handling as we have a local mirror local_mirrored = path == 'third_party/WebKit' url = merge_info[path]['url'] sha1 = merge_info[path]['sha1'] dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path) if local_mirrored: remote = 'history' else: remote = 'goog' merge_common.GetCommandStdout([ 'git', 'checkout', branch_create_flag, branch_name, '-t', remote + '/' + target ], cwd=dest_dir) if not local_mirrored or not root_sha1: logging.debug('Fetching project %s at %s ...', path, sha1) fetch_args = ['git', 'fetch', url, sha1] merge_common.GetCommandStdout(fetch_args, cwd=dest_dir) if merge_common.GetCommandStdout( ['git', 'rev-list', '-1', 'HEAD..' + sha1], cwd=dest_dir): logging.debug('Merging project %s at %s ...', path, sha1) # Merge conflicts make git merge return 1, so ignore errors merge_common.GetCommandStdout( ['git', 'merge', '--no-commit', sha1], cwd=dest_dir, ignore_errors=True) merge_common.CheckNoConflictsAndCommitMerge( 'Merge %s from %s at %s\n\n%s' % (path, url, sha1, AUTOGEN_MESSAGE), cwd=dest_dir, unattended=unattended) else: logging.debug('No new commits to merge in project %s', path) # Handle root repository separately. merge_common.GetCommandStdout([ 'git', 'checkout', branch_create_flag, branch_name, '-t', 'history/' + target ]) if not root_sha1: merge_info = _GetProjectMergeInfo([''], deps_vars) url = merge_info['']['url'] root_sha1 = merge_info['']['sha1'] merge_common.GetCommandStdout(['git', 'fetch', url, root_sha1]) logging.debug('Merging Chromium at %s ...', root_sha1) # Merge conflicts make git merge return 1, so ignore errors merge_common.GetCommandStdout(['git', 'merge', '--no-commit', root_sha1], ignore_errors=True) merge_common.CheckNoConflictsAndCommitMerge( 'Merge Chromium at %s (%s)\n\n%s' % (version, root_sha1, AUTOGEN_MESSAGE), unattended=unattended) logging.debug('Getting directories to exclude ...') # We import this now that we have merged the latest version. # It imports to a global in order that it can be used to generate NOTICE # later. We also disable writing bytecode to keep the source tree clean. sys.path.append( os.path.join(merge_common.REPOSITORY_ROOT, 'android_webview', 'tools')) sys.dont_write_bytecode = True global webview_licenses import webview_licenses import known_issues for path, exclude_list in known_issues.KNOWN_INCOMPATIBLE.iteritems(): logging.debug(' %s', '\n '.join(os.path.join(path, x) for x in exclude_list)) dest_dir = os.path.join(merge_common.REPOSITORY_ROOT, path) merge_common.GetCommandStdout( ['git', 'rm', '-rf', '--ignore-unmatch'] + exclude_list, cwd=dest_dir) if _ModifiedFilesInIndex(dest_dir): merge_common.GetCommandStdout( ['git', 'commit', '-m', 'Exclude unwanted directories'], cwd=dest_dir)