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
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)
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
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)
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)