def merge_into(self, repo: GitRepo, dry_run: bool = False) -> None: # Raises exception if matching rule is not found find_matching_merge_rule(self, repo) if repo.current_branch() != self.default_branch(): repo.checkout(self.default_branch()) if not self.is_ghstack_pr(): msg = self.get_title() + "\n\n" + self.get_body() msg += f"\nPull Request resolved: {self.get_pr_url()}\n" pr_branch_name = f"__pull-request-{self.pr_num}__init__" repo.fetch(f"pull/{self.pr_num}/head", pr_branch_name) repo._run_git("merge", "--squash", pr_branch_name) repo._run_git("commit", f"--author=\"{self.get_author()}\"", "-m", msg) else: self.merge_ghstack_into(repo) repo.push(self.default_branch(), dry_run)
def try_revert(repo: GitRepo, pr: GitHubPR, dry_run: bool = False) -> None: def post_comment(msg: str) -> None: gh_post_comment(pr.org, pr.project, pr.pr_num, msg, dry_run=dry_run) if not pr.is_closed(): return post_comment(f"Can't revert open PR #{pr.pr_num}") if not RE_REVERT_CMD.match(pr.get_comment_body()): raise RuntimeError( f"Comment {pr.get_comment_body()} does not seem to be a valid revert command" ) if pr.get_comment_editor_login() is not None: return post_comment("Don't want to revert based on edited command") author_association = pr.get_comment_author_association() author_login = pr.get_comment_author_login() # For some reason, one can not be a member of private repo, only CONTRIBUTOR expected_association = "CONTRIBUTOR" if pr.is_base_repo_private( ) else "MEMBER" if author_association != expected_association and author_association != "OWNER": return post_comment( f"Will not revert as @{author_login} is not a {expected_association}, but {author_association}" ) # Raises exception if matching rule is not found find_matching_merge_rule(pr, repo) commit_sha = pr.get_merge_commit() if commit_sha is None: commits = repo.commits_resolving_gh_pr(pr.pr_num) if len(commits) == 0: raise RuntimeError("Can't find any commits resolving PR") commit_sha = commits[0] msg = repo.commit_message(commit_sha) rc = RE_DIFF_REV.search(msg) if rc is not None: raise RuntimeError( f"Can't revert PR that was landed via phabricator as {rc.group(1)}" ) repo.checkout(pr.default_branch()) repo.revert(commit_sha) msg = repo.commit_message("HEAD") msg = re.sub(RE_PULL_REQUEST_RESOLVED, "", msg) msg += f"\nReverted {pr.get_pr_url()} on behalf of @{author_login}\n" repo.amend_commit_message(msg) repo.push(pr.default_branch(), dry_run) if not dry_run: gh_add_labels(pr.org, pr.project, pr.pr_num, ["reverted"])
def merge_changes(self, repo: GitRepo, force: bool = False, comment_id: Optional[int] = None, branch: Optional[str] = None) -> None: branch_to_merge_into = self.default_branch( ) if branch is None else branch if repo.current_branch() != branch_to_merge_into: repo.checkout(branch_to_merge_into) if not self.is_ghstack_pr(): msg = self.gen_commit_message() pr_branch_name = f"__pull-request-{self.pr_num}__init__" repo.fetch(f"pull/{self.pr_num}/head", pr_branch_name) repo._run_git("merge", "--squash", pr_branch_name) repo._run_git("commit", f"--author=\"{self.get_author()}\"", "-m", msg) else: self.merge_ghstack_into(repo, force, comment_id=comment_id)
def try_revert(repo: GitRepo, pr: GitHubPR, *, dry_run: bool = False, comment_id: Optional[int] = None, reason: Optional[str] = None) -> None: def post_comment(msg: str) -> None: gh_post_comment(pr.org, pr.project, pr.pr_num, msg, dry_run=dry_run) if not pr.is_closed(): return post_comment(f"Can't revert open PR #{pr.pr_num}") comment = pr.get_last_comment() if comment_id is None else pr.get_comment_by_id(comment_id) if not RE_REVERT_CMD.match(comment.body_text) and not RE_REVERT_CMD_CLI.match(comment.body_text): raise RuntimeError(f"Comment {comment.body_text} does not seem to be a valid revert command") if comment.editor_login is not None: return post_comment("Don't want to revert based on edited command") author_association = comment.author_association author_login = comment.author_login # For some reason, one can not be a member of private repo, only CONTRIBUTOR expected_association = "CONTRIBUTOR" if pr.is_base_repo_private() else "MEMBER" if author_association != expected_association and author_association != "OWNER": return post_comment(f"Will not revert as @{author_login} is not a {expected_association}, but {author_association}") skip_internal_checks = can_skip_internal_checks(pr, comment_id) # Raises exception if matching rule is not found, but ignores all status checks find_matching_merge_rule(pr, repo, force=True, skip_internal_checks=skip_internal_checks) commit_sha = pr.get_merge_commit() if commit_sha is None: commits = repo.commits_resolving_gh_pr(pr.pr_num) if len(commits) == 0: raise RuntimeError("Can't find any commits resolving PR") commit_sha = commits[0] msg = repo.commit_message(commit_sha) rc = RE_DIFF_REV.search(msg) if rc is not None and not can_skip_internal_checks: raise RuntimeError(f"Can't revert PR that was landed via phabricator as {rc.group(1)}") repo.checkout(pr.default_branch()) repo.revert(commit_sha) msg = repo.commit_message("HEAD") msg = re.sub(RE_PULL_REQUEST_RESOLVED, "", msg) msg += f"\nReverted {pr.get_pr_url()} on behalf of {prefix_with_github_url(author_login)}" msg += f" due to {reason}\n" if reason is not None else "\n" repo.amend_commit_message(msg) repo.push(pr.default_branch(), dry_run) if not dry_run: gh_add_labels(pr.org, pr.project, pr.pr_num, ["reverted"])
def merge_into(self, repo: GitRepo, *, force: bool = False, dry_run: bool = False, comment_id: Optional[int] = None) -> None: # Raises exception if matching rule is not found find_matching_merge_rule(self, repo, force=force, skip_internal_checks=can_skip_internal_checks(self, comment_id)) if repo.current_branch() != self.default_branch(): repo.checkout(self.default_branch()) if not self.is_ghstack_pr(): # Adding the url here makes it clickable within the Github UI approved_by_urls = ', '.join(prefix_with_github_url(login) for login in self.get_approved_by()) msg = self.get_title() + f" (#{self.pr_num})\n\n" + self.get_body() msg += f"\nPull Request resolved: {self.get_pr_url()}\n" msg += f"Approved by: {approved_by_urls}\n" pr_branch_name = f"__pull-request-{self.pr_num}__init__" repo.fetch(f"pull/{self.pr_num}/head", pr_branch_name) repo._run_git("merge", "--squash", pr_branch_name) repo._run_git("commit", f"--author=\"{self.get_author()}\"", "-m", msg) else: self.merge_ghstack_into(repo, force, comment_id=comment_id) repo.push(self.default_branch(), dry_run) if not dry_run: gh_add_labels(self.org, self.project, self.pr_num, ["merged"])
def merge_into(self, repo: GitRepo, *, force: bool = False, dry_run: bool = False) -> None: # Raises exception if matching rule is not found find_matching_merge_rule(self, repo, force=force) if self.has_internal_changes(): raise RuntimeError("This PR must be landed via phabricator") if repo.current_branch() != self.default_branch(): repo.checkout(self.default_branch()) if not self.is_ghstack_pr(): # Adding the url here makes it clickable within the Github UI approved_by_urls = ', '.join(prefix_with_github_url(login) for login in self.get_approved_by()) msg = self.get_title() + "\n\n" + self.get_body() msg += f"\nPull Request resolved: {self.get_pr_url()}\n" msg += f"Approved by: {approved_by_urls}\n" pr_branch_name = f"__pull-request-{self.pr_num}__init__" repo.fetch(f"pull/{self.pr_num}/head", pr_branch_name) repo._run_git("merge", "--squash", pr_branch_name) repo._run_git("commit", f"--author=\"{self.get_author()}\"", "-m", msg) else: self.merge_ghstack_into(repo, force) repo.push(self.default_branch(), dry_run)