예제 #1
0
def get_zippered_state(config: AMTargetBranchConfig, remote: str):
    log.info(
        f'Computing status for [{config.upstream} -> {config.target} <- {config.secondary_upstream}]'
    )
    merges: Optional[List[List[str]]] = []
    merges = compute_zippered_merges(remote=remote,
                                     target=config.target,
                                     left_upstream=config.upstream,
                                     right_upstream=config.secondary_upstream,
                                     common_ancestor=config.common_ancestor,
                                     stop_on_first=True)
    if merges:
        return (EdgeStates.working, EdgeStates.working)

    left_commits: Optional[List[str]] = compute_unmerged_commits(
        remote=remote,
        target_branch=config.target,
        upstream_branch=config.upstream)
    right_commits: Optional[List[str]] = compute_unmerged_commits(
        remote=remote,
        target_branch=config.target,
        upstream_branch=config.secondary_upstream)

    left_state = EdgeStates.waiting if left_commits else EdgeStates.clear
    right_state = EdgeStates.waiting if right_commits else EdgeStates.clear
    return (left_state, right_state)
예제 #2
0
def compute_zippered_edges(config: AMTargetBranchConfig, remote: str,
                           merges: Optional[List[List[str]]]):
    log.info(
        f'Computing edges for [{config.upstream} -> {config.target} <- {config.secondary_upstream}]'
    )

    left_edge: Edge = Edge(config.upstream, config.target)
    right_edge: Edge = Edge(config.secondary_upstream, config.target)
    right_edge.set_constraint(False)
    if merges:
        left_edge.set_state(EdgeStates.working)
        right_edge.set_state(EdgeStates.working)
        return (left_edge, right_edge)

    left_commits: Optional[List[str]] = compute_unmerged_commits(
        remote=remote,
        target_branch=config.target,
        upstream_branch=config.upstream)
    left_edge.set_state(
        EdgeStates.waiting if left_commits else EdgeStates.clear)
    right_commits: Optional[List[str]] = compute_unmerged_commits(
        remote=remote,
        target_branch=config.target,
        upstream_branch=config.secondary_upstream)
    right_edge.set_state(
        EdgeStates.waiting if right_commits else EdgeStates.clear)
    return (left_edge, right_edge)
예제 #3
0
def print_zippered_edge_status(config: AMTargetBranchConfig,
                               remote: str,
                               graph=None):
    click.echo(
        click.style(
            f'[{config.upstream} -> {config.target} <- {config.secondary_upstream}]',
            bold=True))
    print(f'- This is a zippered merge branch!')

    merges: Optional[List[List[str]]] = []
    merges = compute_zippered_merges(remote=remote,
                                     target=config.target,
                                     left_upstream=config.upstream,
                                     right_upstream=config.secondary_upstream,
                                     common_ancestor=config.common_ancestor,
                                     stop_on_first=True)

    if graph:
        # FIXME: Don't duplicate compute_unmerged_commits in compute_zippered_edges.
        left_edge, right_edge = compute_zippered_edges(config, remote, merges)
        left_edge.materialize(graph)
        right_edge.materialize(graph)

    if not merges:
        left_commits: Optional[List[str]] = compute_unmerged_commits(
            remote=remote,
            target_branch=config.target,
            upstream_branch=config.upstream)
        right_commits: Optional[List[str]] = compute_unmerged_commits(
            remote=remote,
            target_branch=config.target,
            upstream_branch=config.secondary_upstream)
        if not left_commits and not right_commits:
            print(
                f'- There are no unmerged commits. The {config.target} branch is up to date.'
            )
            return

        def lenOrNone(x):
            return len(x) if x else 0

        print(
            f'- There are {lenOrNone(left_commits)} unmerged commits from {config.upstream}.'
        )
        print(
            f'- There are {lenOrNone(right_commits)} unmerged commits from {config.secondary_upstream}.'
        )
        print(f'- The automerger is waiting for unmerged commits in')
        print(
            f'  {config.upstream} and {config.secondary_upstream} to agree on a merge base'
        )
        print(
            f'  from the {config.common_ancestor} branch before performing a zippered merge.'
        )
        return
    print(
        f'- The automerger will perform at least one zippered merge on the next run.'
    )
예제 #4
0
def compute_zippered_merges(
        remote: str,
        target: str,
        left_upstream: str,
        right_upstream: str,
        common_ancestor: str,
        max_commits: int = 140,
        stop_on_first: bool = False) -> Optional[List[List[str]]]:
    """
        Return the list of lists that contain the parents of the merge commit that should
        be constructed to perform merges into the zippered branch, or None if the zippered
        branch is up-to-date.
    """
    def emptyIfNoneOrReversed(x: Optional[List[str]]) -> List[str]:
        return [] if not x else list(reversed(x))

    # Compute the unmerged commit lists.
    left_commits: List[str] = emptyIfNoneOrReversed(
        compute_unmerged_commits(remote=remote,
                                 target_branch=target,
                                 upstream_branch=left_upstream))
    right_commits: List[str] = emptyIfNoneOrReversed(
        compute_unmerged_commits(remote=remote,
                                 target_branch=target,
                                 upstream_branch=right_upstream))
    if len(left_commits) == 0 and len(right_commits) == 0:
        # The branches are up-to-date.
        return None
    # Restrict the size of the merge window, as computing a lot of merge bases
    # is expensive.
    if len(left_commits) > max_commits:
        left_commits = left_commits[:max_commits]
    if len(right_commits) > max_commits:
        right_commits = right_commits[:max_commits]

    def computeMergeBase(commit: str) -> str:
        return git_output('merge-base', commit, f'{remote}/{common_ancestor}')

    def computeInitialMergeBase(branch: str, commits: List[str]) -> str:
        # FIXME: This might be wrong if someone did a reverse merge into the upstream branch,
        # and the first parent of the first commit doesn't reach the common ancestor.
        return computeMergeBase(f'{remote}/{branch}' if len(commits) ==
                                0 else f'{commits[0]}^')

    log.debug(
        f'setting up left branch iterator for zippered merge to {target}')
    left = BranchIterator(left_commits, computeMergeBase,
                          computeInitialMergeBase(left_upstream, left_commits))
    log.debug(
        f'setting up right branch iterator for zippered merge to {target}')
    right = BranchIterator(
        right_commits, computeMergeBase,
        computeInitialMergeBase(right_upstream, right_commits))
    merges: List[List[str]] = compute_zippered_merge_commits(
        left, right, stop_on_first)
    log.debug(f'zippered merge algorithm produced {len(merges)} merges')
    return merges
예제 #5
0
def print_zippered_edge_status(config: AMTargetBranchConfig,
                               remote: str,
                               graph=None):
    click.echo(
        click.style(
            f'[{config.upstream} -> {config.target} <- {config.secondary_upstream}]',
            bold=True))
    print(f'- This is a zippered merge branch!')

    merges: Optional[List[List[str]]] = []
    merges = compute_zippered_merges(remote=remote,
                                     target=config.target,
                                     left_upstream=config.upstream,
                                     right_upstream=config.secondary_upstream,
                                     common_ancestor=config.common_ancestor,
                                     stop_on_first=True)

    if graph:
        # FIXME: Don't duplicate compute_unmerged_commits in compute_zippered_edges.
        left_edge, right_edge = compute_zippered_edges(config, remote, merges)
        left_edge.materialize(graph)
        right_edge.materialize(graph)

    left_commits: Optional[List[str]] = compute_unmerged_commits(
        remote=remote,
        target_branch=config.target,
        upstream_branch=config.upstream)
    right_commits: Optional[List[str]] = compute_unmerged_commits(
        remote=remote,
        target_branch=config.target,
        upstream_branch=config.secondary_upstream)
    if not left_commits and not right_commits:
        print(f'- 0 unmerged commits. {config.target} is up to date.')
        return

    def printUnmergedCommits(commits: Optional[List[str]], branch: str):
        num = len(commits) if commits else 0
        print(f'- {num} unmerged commits from {branch}.')

    printUnmergedCommits(commits=left_commits, branch=config.upstream)
    printUnmergedCommits(commits=right_commits,
                         branch=config.secondary_upstream)
    if merges:
        print(f'- The automerger has found a common merge-base.')
        return
    print(f'- The automerger is waiting for unmerged commits to share')
    print(f'  a merge-base from {config.common_ancestor}')
    print(f'  before merging (i.e., one of the upstreams is behind).')
예제 #6
0
def print_edge_status(upstream_branch: str,
                      target_branch: str,
                      inflight_merges: Dict[str, List[str]],
                      list_commits: bool = False,
                      remote: str = 'origin',
                      graph=None,
                      query_ci_status: bool = False):
    commits_inflight: Set[str] = set(inflight_merges[target_branch] if
                                     target_branch in inflight_merges else [])

    click.echo(
        click.style(f'[{upstream_branch} -> {target_branch}]', bold=True))
    commits: Optional[List[str]] = compute_unmerged_commits(
        remote=remote,
        target_branch=target_branch,
        upstream_branch=upstream_branch,
        format='%H %cd')
    if graph:
        edge = compute_edge(upstream_branch, target_branch, commits_inflight,
                            commits, remote, query_ci_status)
        edge.materialize(graph)

    if not commits:
        print(
            f'- There are no unmerged commits. The {target_branch} branch is up to date.'
        )
        return

    inflight_count = compute_inflight_commit_count(commits, commits_inflight)
    str2 = f'{inflight_count} commits are currently being merged/build/tested.'
    print(f'- There are {len(commits)} unmerged commits. {str2}')
    print('- Unmerged commits:')

    def print_commit_status(commit: str,
                            check_for_merge_conflict: bool = False):
        hash = commit.split(' ')[0]
        status: str = ''
        if query_ci_status:
            ci_state: Optional[str] = get_ci_status(hash, target_branch)
            if ci_state:
                status = ci_state
        if not status:
            if hash in commits_inflight:
                status = 'Auto merge in progress'
            if check_for_merge_conflict and has_merge_conflict(
                    hash, target_branch, remote):
                status = 'Conflict'
        print(f'  * {commit}: {status}')

    print_commit_status(commits[0], True)
    if list_commits:
        for commit in commits[1:]:
            print_commit_status(commit)
        return
    # Show an abbreviated list of commits.
    if len(commits) > 2:
        print(f'    ... {len(commits) - 2} commits in-between ...')
    if len(commits) > 1:
        print_commit_status(commits[-1])
예제 #7
0
def get_state(upstream_branch: str,
              target_branch: str,
              inflight_merges: Dict[str, List[str]],
              remote: str = 'origin',
              query_ci_status: bool = False):
    log.info(f'Computing status for [{upstream_branch} -> {target_branch}]')
    commits: Optional[List[str]] = compute_unmerged_commits(
        remote=remote,
        target_branch=target_branch,
        upstream_branch=upstream_branch,
        format='%H')
    if not commits:
        return EdgeStates.clear

    commits_inflight: Set[str] = set(inflight_merges[target_branch] if
                                     target_branch in inflight_merges else [])

    def get_commit_state(commit: str, check_for_merge_conflict: bool):
        if query_ci_status:
            ci_state: Optional[str] = get_ci_status(commit, target_branch)
            if ci_state:
                return EdgeStates.get_state(ci_state)
        if check_for_merge_conflict and has_merge_conflict(
                commit, target_branch, remote):
            return EdgeStates.blocked
        if commit in commits_inflight:
            return EdgeStates.working
        return EdgeStates.clear

    # Determine the status of this edge.
    # The edge is blocked if there is a least one blocked commit. If there are
    # no blocked commits, the edge is working if there's at least one working
    # commit. Otherwise the edge is clear.
    working: bool = False
    check_for_merge_conflict: bool = True
    for commit in commits:
        commit_state = get_commit_state(commit, check_for_merge_conflict)
        if commit_state is EdgeStates.blocked:
            return EdgeStates.blocked
        if commit_state is EdgeStates.working:
            working = True
        # Only check for a merge conflict on the first commit.
        check_for_merge_conflict = False
    if working:
        return EdgeStates.working
    return EdgeStates.clear