Example #1
0
def merge_directive_changes(
    local_branch: Branch,
    main_branch: Branch,
    hoster: Hoster,
    name: str,
    message: str,
    include_patch: bool = False,
    include_bundle: bool = False,
    overwrite_existing: bool = False,
) -> MergeDirective:
    from breezy import osutils
    import time

    remote_branch, public_branch_url = hoster.publish_derived(
        local_branch, main_branch, name=name, overwrite=overwrite_existing)
    public_branch = open_branch(public_branch_url)
    directive = MergeDirective2.from_objects(
        local_branch.repository,
        local_branch.last_revision(),
        time.time(),
        osutils.local_time_offset(),
        main_branch,
        public_branch=public_branch,
        include_patch=include_patch,
        include_bundle=include_bundle,
        message=message,
        base_revision_id=main_branch.last_revision(),
    )
    return directive
Example #2
0
def check_proposal_diff(
    other_branch: Branch, main_branch: Branch, stop_revision: Optional[bytes] = None
) -> None:
    if stop_revision is None:
        stop_revision = other_branch.last_revision()
    main_revid = main_branch.last_revision()
    other_branch.repository.fetch(main_branch.repository, main_revid)
    with other_branch.lock_read():
        main_tree = other_branch.repository.revision_tree(main_revid)
        revision_graph = other_branch.repository.get_graph()
        tree_branch = MemoryBranch(other_branch.repository, (None, main_revid), None)
        merger = _mod_merge.Merger(
            tree_branch, this_tree=main_tree, revision_graph=revision_graph
        )
        merger.set_other_revision(stop_revision, other_branch)
        try:
            merger.find_base()
        except errors.UnrelatedBranches:
            merger.set_base_revision(_mod_revision.NULL_REVISION, other_branch)
        merger.merge_type = _mod_merge.Merge3Merger  # type: ignore
        tree_merger = merger.make_merger()
        with tree_merger.make_preview_transform() as tt:
            changes = tt.iter_changes()
            if not any(changes):
                raise EmptyMergeProposal(other_branch, main_branch)
Example #3
0
def merge_conflicts(
    main_branch: Branch, other_branch: Branch, other_revision: Optional[bytes] = None
) -> bool:
    """Check whether two branches are conflicted when merged.

    Args:
      main_branch: Main branch to merge into
      other_branch: Branch to merge (and use for scratch access, needs write
                    access)
      other_revision: Other revision to check
    Returns:
      boolean indicating whether the merge would result in conflicts
    """
    if other_revision is None:
        other_revision = other_branch.last_revision()
    if other_branch.repository.get_graph().is_ancestor(
        main_branch.last_revision(), other_revision
    ):
        return False

    other_branch.repository.fetch(
        main_branch.repository, revision_id=main_branch.last_revision()
    )

    # Reset custom merge hooks, since they could make it harder to detect
    # conflicted merges that would appear on the hosting site.
    old_file_content_mergers = _mod_merge.Merger.hooks["merge_file_content"]
    _mod_merge.Merger.hooks["merge_file_content"] = []

    other_tree = other_branch.repository.revision_tree(other_revision)
    try:
        try:
            merger = _mod_merge.Merger.from_revision_ids(
                other_tree,
                other_branch=other_branch,
                other=main_branch.last_revision(),
                tree_branch=other_branch,
            )
        except errors.UnrelatedBranches:
            # Unrelated branches don't technically *have* to lead to
            # conflicts, but there's not a lot to be salvaged here, either.
            return True
        merger.merge_type = _mod_merge.Merge3Merger
        tree_merger = merger.make_merger()
        with tree_merger.make_preview_transform():
            return bool(tree_merger.cooked_conflicts)
    finally:
        _mod_merge.Merger.hooks["merge_file_content"] = old_file_content_mergers
Example #4
0
def _changelog_stats(branch: Branch, history: int, debian_path: str):
    mixed = 0
    changelog_only = 0
    other_only = 0
    dch_references = 0
    with branch.lock_read():
        graph = branch.repository.get_graph()
        revids, truncated = _greedy_revisions(graph, branch.last_revision(), history)
        revs = []
        for revid, rev in branch.repository.iter_revisions(revids):
            if rev is None:
                # Ghost
                continue
            if "Git-Dch: " in rev.message:
                dch_references += 1
            revs.append(rev)
        get_deltas = branch.repository.get_revision_deltas
        for delta in get_deltas(revs):
            filenames = set(
                [a.path[1] for a in delta.added]
                + [r.path[0] for r in delta.removed]
                + [r.path[0] for r in delta.renamed]
                + [r.path[1] for r in delta.renamed]
                + [m.path[0] for m in delta.modified]
            )
            if not set(
                [
                    f
                    for f in filenames
                    if f.startswith(debian_path + '/')
                ]
            ):
                continue
            if osutils.pathjoin(debian_path, "changelog") in filenames:
                if len(filenames) > 1:
                    mixed += 1
                else:
                    changelog_only += 1
            else:
                other_only += 1
    return (changelog_only, other_only, mixed, dch_references)
Example #5
0
def publish_changes(
    local_branch: Branch,
    main_branch: Branch,
    resume_branch: Optional[Branch],
    mode: str,
    name: str,
    get_proposal_description: Callable[[str, Optional[MergeProposal]], str],
    get_proposal_commit_message: Callable[
        [Optional[MergeProposal]], Optional[str]
    ] = None,
    dry_run: bool = False,
    hoster: Optional[Hoster] = None,
    allow_create_proposal: bool = True,
    labels: Optional[List[str]] = None,
    overwrite_existing: Optional[bool] = True,
    existing_proposal: Optional[MergeProposal] = None,
    reviewers: Optional[List[str]] = None,
    tags: Optional[Union[List[str], Dict[str, bytes]]] = None,
    derived_owner: Optional[str] = None,
    allow_collaboration: bool = False,
    stop_revision: Optional[bytes] = None,
) -> PublishResult:
    """Publish a set of changes.

    Args:
      ws: Workspace to push from
      mode: Mode to use ('push', 'push-derived', 'propose')
      name: Branch name to push
      get_proposal_description: Function to retrieve proposal description
      get_proposal_commit_message: Function to retrieve proposal commit message
      dry_run: Whether to dry run
      hoster: Hoster, if known
      allow_create_proposal: Whether to allow creating proposals
      labels: Labels to set for any merge proposals
      overwrite_existing: Whether to overwrite existing (but unrelated) branch
      existing_proposal: Existing proposal to update
      reviewers: List of reviewers for merge proposal
      tags: Tags to push (None for default behaviour)
      derived_owner: Name of any derived branch
      allow_collaboration: Whether to allow target branch owners to modify
        source branch.
    """
    if mode not in SUPPORTED_MODES:
        raise ValueError("invalid mode %r" % mode)

    if stop_revision is None:
        stop_revision = local_branch.last_revision()

    if stop_revision == main_branch.last_revision():
        if existing_proposal is not None:
            logging.info("closing existing merge proposal - no new revisions")
            existing_proposal.close()
        return PublishResult(mode)

    if resume_branch and resume_branch.last_revision() == stop_revision:
        # No new revisions added on this iteration, but changes since main
        # branch. We may not have gotten round to updating/creating the
        # merge proposal last time.
        logging.info("No changes added; making sure merge proposal is up to date.")

    if hoster is None:
        hoster = get_hoster(main_branch)

    if mode == MODE_PUSH_DERIVED:
        (remote_branch, public_url) = push_derived_changes(
            local_branch,
            main_branch,
            hoster=hoster,
            name=name,
            overwrite_existing=overwrite_existing,
            tags=tags,
            owner=derived_owner,
            stop_revision=stop_revision,
        )
        return PublishResult(mode)

    if mode in (MODE_PUSH, MODE_ATTEMPT_PUSH):
        try:
            # breezy would do this check too, but we want to be *really* sure.
            with local_branch.lock_read():
                graph = local_branch.repository.get_graph()
                if not graph.is_ancestor(main_branch.last_revision(), stop_revision):
                    raise errors.DivergedBranches(main_branch, local_branch)
            push_changes(
                local_branch,
                main_branch,
                hoster=hoster,
                dry_run=dry_run,
                tags=tags,
                stop_revision=stop_revision,
            )
        except errors.PermissionDenied:
            if mode == MODE_ATTEMPT_PUSH:
                logging.info("push access denied, falling back to propose")
                mode = MODE_PROPOSE
            else:
                logging.info("permission denied during push")
                raise
        else:
            return PublishResult(mode=mode)

    assert mode == "propose"
    if not resume_branch and not allow_create_proposal:
        raise InsufficientChangesForNewProposal()

    mp_description = get_proposal_description(
        getattr(hoster, "merge_proposal_description_format", "plain"),
        existing_proposal if resume_branch else None,
    )
    if get_proposal_commit_message is not None:
        commit_message = get_proposal_commit_message(
            existing_proposal if resume_branch else None
        )
    (proposal, is_new) = propose_changes(
        local_branch,
        main_branch,
        hoster=hoster,
        name=name,
        mp_description=mp_description,
        resume_branch=resume_branch,
        resume_proposal=existing_proposal,
        overwrite_existing=overwrite_existing,
        labels=labels,
        dry_run=dry_run,
        commit_message=commit_message,
        reviewers=reviewers,
        tags=tags,
        owner=derived_owner,
        allow_collaboration=allow_collaboration,
        stop_revision=stop_revision,
    )
    return PublishResult(mode, proposal, is_new)