| #!/usr/bin/env python |
| # |
| # Copyright (C) 2017 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| import argparse |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| from utils import * |
| |
| PROJECT_PATH = ( |
| ('llvm', llvm_path()), |
| ('cfe', llvm_path('tools/clang')), |
| ('clang-tools-extra', llvm_path('tools/clang/tools/extra')), |
| ('compiler-rt', llvm_path('projects/compiler-rt')), |
| ('libcxx', llvm_path('projects/libcxx')), |
| ('libcxxabi', llvm_path('projects/libcxxabi')), |
| ('lld', llvm_path('tools/lld')), |
| ('openmp', llvm_path('projects/openmp')),) |
| |
| |
| def parse_args(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| 'revision', help='Revision number of llvm source.', type=int) |
| parser.add_argument( |
| '--create-new-branch', |
| action='store_true', |
| default=False, |
| help='Create new branch using `repo start` before ' |
| 'merging from upstream.') |
| parser.add_argument( |
| '--dry-run', |
| action='store_true', |
| default=False, |
| help='Dry run, does not actually commit changes to local workspace.') |
| return parser.parse_args() |
| |
| |
| def sync_upstream_branch(path): |
| subprocess.check_call(['repo', 'sync', '.'], cwd=path) |
| |
| |
| def merge_projects(revision, create_new_branch, dry_run): |
| project_sha_dict = {} |
| for (project, path) in PROJECT_PATH: |
| if not dry_run: |
| sync_upstream_branch(path) |
| sha = get_commit_hash(revision, path) |
| if sha is None: |
| return |
| project_sha_dict[project] = sha |
| print('Project %s git hash: %s' % (project, sha)) |
| |
| for (project, path) in PROJECT_PATH: |
| sha = project_sha_dict[project] |
| if create_new_branch: |
| branch_name = 'merge-upstream-r%s' % revision |
| check_call_d(['repo', 'start', branch_name, '.'], cwd=path, dry_run=dry_run) |
| |
| # Get the info since the last tag, the format is |
| # llvm-svn.[svn]-[number of changes since tag]-[sha of the current commit] |
| desc = subprocess.check_output( |
| ['git', 'describe', '--tags', '--long', '--match', 'llvm-svn.[0-9]*'], |
| cwd=path, universal_newlines=True).strip() |
| _, svnNum, numChanges, _ = desc.split('-') |
| |
| # Check changes since the previous merge point |
| reapplyList = [] |
| for i in range(int(numChanges) - 1, -1, -1): |
| changeLog = subprocess.check_output( |
| ['git', 'show', 'HEAD~' + str(i), '--quiet', '--format=%h%x1f%B%x1e'], |
| cwd=path, universal_newlines=True |
| ) |
| changeLog = changeLog.strip('\n\x1e') |
| patchSha, patchRev = parse_log(changeLog) |
| if patchRev is None or patchRev > revision: |
| reapplyList.append(patchSha) |
| |
| # Reset to previous branch point, if necessary |
| if int(numChanges) > 0: |
| check_output_d( |
| [ |
| 'git', 'revert', '--no-commit', '--no-merges', |
| 'llvm-' + svnNum + '...HEAD' |
| ], |
| cwd=path, |
| dry_run=dry_run |
| ) |
| check_output_d( |
| ['git', 'commit', '-m revert to previous base llvm-' + svnNum], |
| cwd=path, |
| dry_run=dry_run |
| ) |
| |
| # Merge upstream revision |
| check_call_d( |
| [ |
| 'git', 'merge', '--quiet', sha, '-m', |
| 'Merge %s for LLVM update to %d' % (sha, revision) |
| ], |
| cwd=path, |
| dry_run=dry_run |
| ) |
| |
| # Tag the merge point |
| check_call_d( |
| ['git', 'tag', '-f', 'llvm-svn.' + str(revision)], |
| cwd=path, |
| dry_run=dry_run |
| ) |
| |
| # Reapply |
| FNULL = open(os.devnull, 'w') |
| for sha in reapplyList: |
| subprocess.check_call( |
| ['git', '--no-pager', 'show', sha, '--quiet'], |
| cwd=path |
| ) |
| |
| # Check whether applying this change will cause conflict |
| ret_code = subprocess.call( |
| ['git', 'cherry-pick', '--no-commit', '--no-ff', sha], |
| cwd=path, |
| stdout=FNULL, |
| stderr=FNULL |
| ) |
| subprocess.check_call( |
| ['git', 'reset', '--hard'], |
| cwd=path, |
| stdout=FNULL, |
| stderr=FNULL |
| ) |
| |
| if ret_code != 0: |
| print 'Change cannot merge cleanly, please manual merge if needed' |
| print |
| keep_going = yes_or_no("Continue?", default=False) |
| if not keep_going: |
| sys.exit(1) |
| continue |
| |
| # Change can apply cleanly... |
| reapply = yes_or_no("Reapply change?", default=True) |
| if reapply: |
| check_call_d( |
| ['git', 'cherry-pick', sha], |
| cwd=path, |
| stdout=FNULL, |
| stderr=FNULL, |
| dry_run=dry_run |
| ) |
| # Now change the commit Change-Id. |
| check_call_d( |
| ['git', 'commit', '--amend'], |
| cwd=path, |
| dry_run=dry_run |
| ) |
| else: |
| print 'Skipping ' + sha |
| |
| print |
| |
| print |
| |
| |
| def get_commit_hash(revision, path): |
| # Get sha and commit message body for each log. |
| p = subprocess.Popen( |
| ['git', 'log', 'aosp/upstream-master', '--format=%h%x1f%B%x1e'], |
| stdout=subprocess.PIPE, |
| cwd=path) |
| (log, _) = p.communicate() |
| if p.returncode != 0: |
| print('git log for path: %s failed!' % path) |
| return |
| |
| # Log will be in reversed order. |
| log = log.strip('\n\x1e').split('\x1e') |
| |
| # Binary search log data. |
| low, high = 0, len(log) - 1 |
| while low < high: |
| pos = (low + high) // 2 |
| (sha, cur_revision) = parse_log(log[pos]) |
| if cur_revision == revision: |
| return sha |
| elif cur_revision < revision: |
| high = pos |
| else: |
| low = pos + 1 |
| (sha, _) = parse_log(log[high]) |
| return sha |
| |
| |
| def parse_log(raw_log): |
| log = raw_log.strip().split('\x1f') |
| # Extract revision number from log data. |
| revision_string = log[1].strip().split('\n')[-1] |
| revision = re.search(r'trunk@(\d+)', revision_string) |
| if revision is None: |
| return (log[0], None) |
| return (log[0], int(revision.group(1))) |
| |
| |
| def main(): |
| args = parse_args() |
| merge_projects(args.revision, args.create_new_branch, args.dry_run) |
| |
| |
| if __name__ == '__main__': |
| main() |