def test_git_invocation(tmp_path):
    """ Tests for the git/git_output functions. """
    repo_path = tmp_path / 'repo'
    repo_path.mkdir()
    assert repo_path.is_dir()  # Ensure the dir is there for us to work with.
    repo_dir = str(repo_path)

    git('init', git_dir=repo_dir)

    (repo_path / 'initial').write_text(u'initial')
    git('add', 'initial', git_dir=repo_dir)

    # Check that we can report an error on failure.
    with pytest.raises(GitError) as err:
        git('add', 'foo', git_dir=repo_dir)
    assert err.value.stderr.startswith('fatal')
    assert repr(err.value).startswith('GitError')

    # Check that errors can be ignored.
    git('add', 'foo', git_dir=repo_dir, ignore_error=True)

    output = git_output('commit', '-m', 'initial', git_dir=repo_dir)
    assert len(output) > 0

    # Ensure that the output is stripped.
    output = git_output('rev-list', 'HEAD', git_dir=repo_dir)
    assert '\n' not in output
    output = git_output('rev-list', 'HEAD', git_dir=repo_dir, strip=False)
    assert '\n' in output

    # Ensure that commit exists works only for commit hashes.
    hash = output.strip()
    assert commit_exists(hash)
    assert not commit_exists('HEAD')
    assert not commit_exists(hash + 'abc')
    assert not commit_exists('000000')

    # Ensure that we can get the directory of the checkout even when the
    # working directory is a subdirectory.
    os.chdir(repo_dir)
    dir_a = get_current_checkout_directory()
    (repo_path / 'subdir').mkdir()
    cwd = os.getcwd()
    os.chdir(os.path.join(repo_dir, 'subdir'))
    dir_b = get_current_checkout_directory()
    os.chdir(cwd)
    assert dir_a == dir_b

    assert read_file_or_none('HEAD', 'initial') == 'initial'
    assert read_file_or_none(hash, 'initial') == 'initial'
    assert read_file_or_none('HEAD', 'path/does-not-exist') is None
    assert read_file_or_none('foo', 'initial') is None
def verify_branch_contents(branch_name: str, verbose):
    logging.basicConfig(level=logging.DEBUG if verbose else logging.WARNING,
                        format='%(levelname)s: %(message)s [%(filename)s:%(lineno)d at %(asctime)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)

    if branch_name.startswith('swift/'):
        verify_swift_branch(branch_name)
def pr(verbose):
    """ Tool for working with pull requests """
    logging.basicConfig(
        level=logging.DEBUG if verbose else logging.WARNING,
        format=
        '%(levelname)s: %(message)s [%(filename)s:%(lineno)d at %(asctime)s] ',
    )

    # Verify that we're in a git checkout.
    if not get_current_checkout_directory():
        fatal('not a git repository')

    # Load the config file.
    config = load_pr_config()
    if not config:
        fatal('missing `git apple-llvm pr` configuration file')

    global pr_tool, ci_test_type
    ci_test_type = config.test_type
    if pr_tool:
        return
    pr_tool = config.create_tool()
Exemplo n.º 4
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)