def git_apple_llvm_push()

in git_apple_llvm/git_tools/push.py [0:0]


def git_apple_llvm_push(refspec, dry_run, verbose, merge_strategy, push_limit):
    """ Push changes back to the split Git repositories. """
    logging.basicConfig(level=logging.DEBUG if verbose else logging.WARNING,
                        format='%(levelname)s: %(message)s')

    # Verify that we're in a git checkout.
    git_path = get_current_checkout_directory()
    if git_path is None:
        fatal('not a git repository')
    os.chdir(git_path)

    # Figure out the set of remote branches we care about.
    remote = 'origin'
    remote_monorepo_branches = [x.strip() for x in git_output(
        'branch', '-r', '-l').splitlines()]
    remote_monorepo_branches = list(
        filter(lambda x: isKnownTrackingBranch(remote, x), remote_monorepo_branches))
    log.info('Branches we care about %s', remote_monorepo_branches)

    refs = refspec.split(':')
    if len(refs) < 2:
        fatal(f'Git refspec "{refspec}" is invalid')
    source_ref = refs[0]
    dest_ref = refs[1]
    remote_dest_ref = f'{remote}/{dest_ref}'
    # Verify that the source ref is valid and get its commit hash.
    source_commit_hash = git_output('rev-parse', source_ref, ignore_error=True)
    if source_commit_hash is None:
        fatal(f'source Git refspec "{source_ref}" is invalid')
    # Ensure that the source ref is associated with a ref that can be fetched.
    git('branch', '-f', MONOREPO_SRC_REF_NAME, source_commit_hash)

    # Verify that the destination ref is valid and load its push config.
    dest_commit_hash = git_output(
        'rev-parse', remote_dest_ref, ignore_error=True)
    if dest_commit_hash is None:
        fatal(f'destination Git refspec "{dest_ref}" is invalid')
    push_config = load_push_config(source_commit_hash, dest_ref)
    if push_config is None:
        fatal(f'destination Git refspec "{dest_ref}" cannot be pushed to.')

    # The rev-list command is used to compute the graph we would like to
    # commit.
    rev_list = git_output('rev-list', '--boundary', source_commit_hash,
                          '--not', *remote_monorepo_branches,
                          ignore_error=True)
    if rev_list is None:
        fatal('unable to determine the commit graph to push')
    commit_graph = compute_commit_graph(rev_list)
    if commit_graph is None:
        print('No commits to commit: everything up-to-date.')
        return
    # Prohibit pushing more than 50 commits by default in a bid to avoid
    # inadvertent mistakes.
    if push_limit != 0 and len(commit_graph.commits) >= push_limit:
        fatal(
            f'pushing {len(commit_graph.commits)} commits, are you really sure?'
            f'\nPass --push-limit={len(commit_graph.commits)+1} if yes.')

    click.echo(click.style(
        f'Preparing to push to {len(commit_graph.commits)} commits:', bold=True))
    git('log', '--format=%h %s', '--graph', commit_graph.source_commit_hash,
        '--not', *commit_graph.roots)

    message_bodies = git_output('log', '--format=%b', commit_graph.source_commit_hash,
                                '--not', *commit_graph.roots)
    if 'apple-llvm-split-commit:' in message_bodies:
        fatal('one or more commits is already present in the split repo')

    # Prepare the split remotes.
    split_repos_of_interest = commit_graph.compute_changed_split_repos()

    click.echo(
        f'Split repos that should be updated: {", ".join(map(split_dir_to_str, split_repos_of_interest))}\n')

    split_remotes = {}
    for split_dir in split_repos_of_interest:
        if not push_config.can_push_to_split_dir(split_dir):
            fatal(
                f'push configuration "{push_config.name}" prohibits pushing to "{split_dir}"')
        remote = SplitRemote(split_dir, push_config.repo_mapping[split_dir],
                             push_config.get_split_repo_branch(split_dir,
                                                               dest_ref))
        click.echo(click.style(
            f'Fetching "{remote.destination_branch}" for {split_dir_to_str(split_dir)}...', bold=True))
        try:
            remote.update_remote()
        except GitError:
            fatal(
                f'failed to fetch from the remote for {split_dir_to_str(split_dir)}.')
        click.echo(
            'Fetching monorepo commits from monorepo to the split clone (takes time on first push)...\n')
        remote.update_mono_remote()
        split_remotes[split_dir] = remote

    # Regraft the commit history.
    for split_dir in split_repos_of_interest:
        click.echo(click.style(
            f'Regrafting the commits from monorepo to {split_dir_to_str(split_dir)}...', bold=True))
        split_remotes[split_dir].begin()
        split_remotes[split_dir].regrafted_graph = regraft_commit_graph_onto_split_repo(commit_graph,
                                                                                        split_dir)

    # Merge/rebase the commit history.
    for split_dir in split_repos_of_interest:
        click.echo(click.style(
            f'\nRebasing/merging the {split_dir_to_str(split_dir)} commits...', bold=True))
        remote = split_remotes[split_dir]
        remote.begin()
        try:
            remote.commit_hash = merge_commit_graph_with_top_of_branch(remote.regrafted_graph,
                                                                       split_dir,
                                                                       'origin/' + remote.destination_branch,
                                                                       merge_strategy)
        except ImpossibleMergeError as err:
            fatal(f'unable to {err.operation} commits in {split_dir_to_str(split_dir)}.'
                  f'Please rebase your monorepo commits first.')

    # Once everything is ready, push!
    for split_dir in split_repos_of_interest:
        split_remotes[split_dir].push(dry_run)