Revision control
++ #############################################################################
++ ##
++ ## Copyright (C) 2021 The Qt Company Ltd.
++ ## Contact: https://www.qt.io/licensing/
++ ##
++
++ import os
++ import json
++ import subprocess
++
++ import gitfunctions as gf
++ import config as c
++
++
++ ### Helpers
++
++ def run_list(cmd: [], **kwargs):
++ print(' '.join(cmd))
++ #cmd = cmd.replace('"', '').split(' ')
++ return subprocess.run(cmd, **kwargs)
++
++
++ # Basic run wrapper for commands. Will not work for command elements with spaces
++ def run(cmd: str, **kwargs):
++ return run_list(cmd.split(' '), **kwargs)
++
++
++ # Get the path to the hooks of the specified repo
++ def get_hooks_dir(repo: str = '.') -> str:
++ cwd = os.getcwd()
++ os.chdir(repo)
++
++ result = run('git rev-parse --git-path hooks', capture_output=True)
++ output = result.stdout.decode('utf-8').strip()
++
++ os.chdir(cwd)
++
++ return output
++
++
++ # Get the absolute path of the api review script
++ def get_api_review_script() -> str:
++ relative_path = os.path.join(
++ os.path.dirname(os.path.dirname(__file__)),
++ 'api-review-gen')
++
++ return os.path.abspath(relative_path)
++
++
++ # Write module data to json file
++ def export_modules(modules: {}, filename: str):
++ with open(filename, 'w') as outfile:
++ outfile.write(json.dumps(modules, indent=4))
++
++
++ def branch_exists(branch: str, repo: str = '.') -> bool:
++ cwd = os.getcwd()
++
++ os.chdir(repo)
++
++ result = run('git branch --list', capture_output=True).stdout.decode('utf-8')
++
++ os.chdir(cwd)
++
++ return branch in result
++
++
++ # diffs the given change and returns if its empty or not
++ def is_nonempty_change(module: str, previous_version: str) -> bool:
++ cwd = os.getcwd()
++
++ module_path = os.path.join(c.qt5_repo, module)
++ os.chdir(module_path)
++
++ change = f'api-review-{previous_version}-{c.next_version}'
++
++ cmd = f'git diff {previous_version} {change}'
++ result = run(cmd, capture_output=True).stdout.decode('utf-8').strip()
++
++ os.chdir(cwd)
++
++ return result != ''
++
++
++ # maps the module to which version API review should be based on
++ def map_modules() -> {}:
++ cwd = os.getcwd()
++
++ os.chdir(c.qt5_repo)
++
++ module_version_mapping = {}
++
++ next_version_modules = list(filter(
++ lambda module: module not in c.exclude_modules,
++ gf.get_active_submodules()))
++
++ for module in next_version_modules:
++ module_version_mapping[module] = 'unknown'
++
++ previous_versions_modules = {}
++
++ for version in c.previous_versions:
++ previous_versions_modules[version] = gf.get_active_submodules('.', version)
++
++ # try to map all modules in next version
++ for module in next_version_modules:
++ for previous_version, previous_version_modules in previous_versions_modules.items():
++ if module in previous_version_modules:
++ module_version_mapping[module] = previous_version
++ break
++
++ if module_version_mapping[module] == 'unknown':
++ print('No previous version found for ' + module)
++
++ os.chdir(cwd)
++
++ return module_version_mapping
++
++ ### Main steps
++
++
++ def update_repo():
++ print('### Updating repo...')
++ cwd = os.getcwd()
++
++ if os.path.exists(c.qt5_repo):
++ os.chdir(c.qt5_repo)
++ else:
++ os.system(f'git clone {c.qt5_url} {c.qt5_repo}')
++ os.chdir(c.qt5_repo)
++ print(os.getcwd())
++ hook_cmd = c.commit_hook_cmd.split(' ')
++ hook_cmd.append(get_hooks_dir())
++ run_list(hook_cmd)
++ run_list(['git', 'config', 'user.email', c.git_email])
++
++ run(f'git checkout {c.next_version}')
++
++ run('git pull --ff-only')
++
++ os.chdir(cwd)
++
++
++ # Updates specified submodule, and fetch given previous version
++ def update_module(module: str, previous_version: str):
++ print(f'### Updating module {module}...')
++ cwd = os.getcwd()
++
++ gitfile = os.path.join(c.qt5_repo, module, '.git')
++
++ if not os.path.exists(gitfile):
++ os.chdir(c.qt5_repo)
++
++ run(f'git submodule update --init --recursive {module}')
++
++ os.chdir(module)
++
++ hooks_dir = get_hooks_dir()
++
++ hook_cmd = c.commit_hook_cmd.split(' ')
++ hook_cmd.append(hooks_dir)
++
++ run_list(hook_cmd)
++ run_list(['git', 'config', 'user.email', c.git_email])
++ run(f'git fetch origin {previous_version}:{previous_version}')
++ else:
++ os.chdir(os.path.join(c.qt5_repo, module))
++
++ run(f'git checkout {c.next_version}')
++ run('git pull --ff-only')
++
++ os.chdir(cwd)
++
++
++ # Generate and upload the Gerrit change for specified module and version
++ def generate_change(module: str, previous_version: str):
++ print(f'### Generating change for {module}...')
++
++ cwd = os.getcwd()
++
++ api_review_script = get_api_review_script()
++ module_path = os.path.join(c.qt5_repo, module)
++ os.chdir(module_path)
++
++ branch = f'api-review-{previous_version}-{c.next_version}'
++
++ cmd_base = api_review_script
++ cmd_options = f'-t {c.task_id}'
++
++ if branch_exists(branch):
++ cmd_options += ' --amend'
++
++ cmd_full = f'{cmd_base} {cmd_options} {previous_version} {c.next_version}'
++
++ run(cmd_full)
++
++ os.chdir(cwd)
++
++
++ def review_change(module: str, previous_version: str):
++ print(f'### Reviewing change for {module}...')
++
++ cwd = os.getcwd()
++
++ module_path = os.path.join(c.qt5_repo, module)
++
++ os.chdir(module_path)
++
++ input(f'Reviewing BORING changes for {module}. Press any key to continue...')
++ run('git diff -b -D')
++
++ input(f'Reviewing INTERESTING changes for {module}. Press any key to continue...')
++ run(f'git log -p {previous_version}..')
++
++ os.chdir(cwd)
++
++
++ def ask_to_upload(module: str) -> bool:
++ while True:
++ r = input(f'Upload change for {module}? [y/n] > ')
++
++ if r in 'yn':
++ return r == 'y'
++
++ print('Invalid response. Expected y or n.')
++
++
++ def upload_change(module: str, previous_version: str):
++ print(f'### Uploading change for {module}...')
++
++ cwd = os.getcwd()
++ module_path = os.path.join(c.qt5_repo, module)
++
++ os.chdir(module_path)
++
++ current_branch = gf.get_branch()
++ branch = f'api-review-{previous_version}-{c.next_version}'
++
++ run(f'git checkout {branch}')
++ run(f'git push origin HEAD:refs/for/{c.next_version}%topic=api-change-review-{c.next_version}')
++ run(f'git checkout {current_branch}')
++
++ os.chdir(cwd)
++
++
++ def reset_module(module: str):
++ print(f'### Resetting module {module}...')
++
++ cwd = os.getcwd()
++
++ module_path = os.path.join(c.qt5_repo, module)
++
++ os.chdir(module_path)
++
++ run('git reset --hard HEAD')
++ run(f'git checkout {c.next_version}')
++
++ os.chdir(cwd)
++
++
++ def main():
++ update_repo()
++ module_version_mapping = map_modules()
++
++ for module, previous_version in module_version_mapping.items():
++ print(f'### Processing {module}')
++ input('Press any key to continue...')
++ update_module(module, previous_version)
++ generate_change(module, previous_version)
++ if is_nonempty_change(module, previous_version):
++ review_change(module, previous_version)
++ if ask_to_upload(module):
++ upload_change(module, previous_version)
++ else:
++ print(f'Change for {module} in uninteresting. Skipping.')
++
++ reset_module(module)
++
++
++ if __name__ == '__main__':
++ main()