|
#!/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() |