Lines Matching +full:pr +full:- +full:dependencies +full:- +full:check
3 # NB: the following functions are used in Meta-internal workflows
377 # This query needs read-org permission
442 RE_GHSTACK_HEAD_REF = re.compile(r"^(gh/[^/]+/[0-9]+/)head$")
446 r"https://github.com/(?P<owner>[^/]+)/(?P<repo>[^/]+)/pull/(?P<number>[0-9]+)",
450 RE_DIFF_REV = re.compile(r"^Differential Revision:.+?(D[0-9]+)", re.MULTILINE)
458 INTERNAL_CHANGES_CHECKRUN_NAME = "Meta Internal-Only Changes Check"
462 # This could be set to -1 to ignore all flaky and broken trunk failures. On the
467 def gh_get_pr_info(org: str, proj: str, pr_no: int) -> Any:
473 def gh_get_team_members(org: str, name: str) -> List[str]:
487 warn(f"Requested non-existing team {org}/{name}")
494 def get_check_run_name_prefix(workflow_run: Any) -> str:
501 def is_passing_status(status: Optional[str]) -> bool:
509 ) -> JobNameToStateDict:
513 # workflow -> job -> job info
519 def add_conclusions(edges: Any) -> None:
608 def parse_args() -> Any:
611 parser = ArgumentParser("Merge PR into default branch")
612 parser.add_argument("--dry-run", action="store_true")
613 parser.add_argument("--revert", action="store_true")
614 parser.add_argument("--force", action="store_true")
615 parser.add_argument("--ignore-current", action="store_true")
616 parser.add_argument("--check-mergeability", action="store_true")
617 parser.add_argument("--comment-id", type=int)
618 parser.add_argument("--reason", type=str)
623 def can_skip_internal_checks(pr: "GitHubPR", comment_id: Optional[int] = None) -> bool:
626 comment = pr.get_comment_by_id(comment_id)
629 return comment.author_login == "facebook-github-bot"
634 pr: "GitHubPR",
637 ) -> List[Tuple["GitHubPR", str]]:
644 f"Could not find PR-resolved string in {msg} of ghstacked PR {pr.pr_num}"
646 if pr.org != m.group("owner") or pr.project != m.group("repo"):
648 f"PR {m.group('number')} resolved to wrong owner/repo pair"
651 candidate = GitHubPR(pr.org, pr.project, pr_num) if pr_num != pr.pr_num else pr
659 repo: GitRepo, pr: "GitHubPR", open_only: bool = True
660 ) -> List[Tuple["GitHubPR", str]]:
662 …Get the PRs in the stack that are below this PR (inclusive). Throws error if any of the open PRs …
665 # For ghstack, cherry-pick commits based from origin
666 orig_ref = f"{repo.remote}/{pr.get_ghstack_orig_ref()}"
667 rev_list = repo.revlist(f"{pr.default_branch()}..{orig_ref}")
669 def skip_func(idx: int, candidate: "GitHubPR") -> bool:
673 … f"Skipping {idx+1} of {len(rev_list)} PR (#{candidate.pr_num}) as its already been merged"
677 assert pr.is_ghstack_pr()
678 entire_stack = _revlist_to_prs(repo, pr, reversed(rev_list), skip_func)
684 if base_ref == pr.default_branch():
690 f"PR {stacked_pr.pr_num} is out of sync with the corresponding revision {rev} on "
692 + "This usually happens because there is a non ghstack change in the PR. "
699 def __init__(self, org: str, project: str, pr_num: int) -> None:
714 def is_closed(self) -> bool:
717 def is_cross_repo(self) -> bool:
720 def base_ref(self) -> str:
723 def default_branch(self) -> str:
726 def head_ref(self) -> str:
729 def is_ghstack_pr(self) -> bool:
732 def get_ghstack_orig_ref(self) -> str:
736 def is_base_repo_private(self) -> bool:
739 def get_changed_files_count(self) -> int:
742 def last_commit(self) -> Any:
743 return self.info["commits"]["nodes"][-1]["commit"]
745 def get_merge_base(self) -> str:
750 # NB: We could use self.base_ref() here for regular PR, however, that doesn't
758 # points to the base ref associated with the PR or, in other words, the head of main
759 # when the PR is created or rebased. This is not necessarily the merge base commit,
761 # of the PR info
767 def get_changed_files(self) -> List[str]:
790 def get_submodules(self) -> List[str]:
797 def get_changed_submodules(self) -> List[str]:
801 def has_invalid_submodule_updates(self) -> bool:
802 """Submodule updates in PR are invalid if submodule keyword
813 def _get_reviews(self) -> List[Tuple[str, str]]:
838 def get_approved_by(self) -> List[str]:
841 def get_commit_count(self) -> int:
844 def get_pr_creator_login(self) -> str:
847 def _fetch_authors(self) -> List[Tuple[str, str]]:
852 def add_authors(info: Dict[str, Any]) -> None:
879 def get_committer_login(self, num: int = 0) -> str:
882 def get_committer_author(self, num: int = 0) -> str:
885 def get_labels(self) -> List[str]:
896 def get_checkrun_conclusions(self) -> JobNameToStateDict:
897 """Returns dict of checkrun -> [conclusion, url]"""
904 ) -> Any:
910 cs_cursor=edges[edge_idx - 1]["cursor"] if edge_idx > 0 else None,
914 -1
916 checkruns = last_commit["checkSuites"]["nodes"][-1]["checkRuns"]
919 def get_pr_next_checksuites(checksuites: Any) -> Any:
925 cursor=checksuites["edges"][-1]["cursor"],
928 last_commit = info["commits"]["nodes"][-1]["commit"]
930 raise RuntimeError("Last commit changed on PR")
955 def get_authors(self) -> Dict[str, str]:
962 def get_author(self) -> str:
967 # If PR creator is not among authors
973 def get_title(self) -> str:
976 def get_body(self) -> str:
979 def get_merge_commit(self) -> Optional[str]:
983 def get_pr_url(self) -> str:
987 def _comment_from_node(node: Any) -> GitHubComment:
999 def get_comments(self) -> List[GitHubComment]:
1021 def get_last_comment(self) -> GitHubComment:
1022 return self._comment_from_node(self.info["comments"]["nodes"][-1])
1024 def get_comment_by_id(self, database_id: int) -> GitHubComment:
1026 # Fastpath - try searching in partial prefetched comments
1036 …# The comment could have actually been a review left on the PR (the message written alongside the …
1038 # Check those review comments to see if one of those was the comment in question.
1047 def get_diff_revision(self) -> Optional[str]:
1051 def has_internal_changes(self) -> bool:
1060 def has_no_connected_diff(self) -> bool:
1073 ) -> List["GitHubPR"]:
1079 for pr, rev in ghstack_prs:
1080 if pr.is_closed():
1081 pr_dependencies.append(pr)
1084 commit_msg = pr.gen_commit_message(
1087 if pr.pr_num != self.pr_num and not skip_all_rule_checks:
1090 pr,
1097 pr_dependencies.append(pr)
1104 ) -> str:
1105 """Fetches title and body from PR description
1122 msg += f"ghstack dependencies: {', '.join([f'#{pr.pr_num}' for pr in ghstack_deps])}\n"
1124 # Mention PR co-authors, which should be at the end of the message
1131 msg += f"\nCo-authored-by: {author_name}"
1135 def add_numbered_label(self, label_base: str, dry_run: bool) -> None:
1153 ) -> None:
1174 for pr in additional_merged_prs:
1175 pr.add_numbered_label(MERGE_COMPLETE_LABEL, dry_run)
1204 print("Missing comment ID or PR number, couldn't upload to Rockset")
1213 ) -> List["GitHubPR"]:
1215 … :param skip_all_rule_checks: If true, skips all rule checks, useful for dry-running merge locally
1222 pr_branch_name = f"__pull-request-{self.pr_num}__init__"
1224 repo._run_git("merge", "--squash", pr_branch_name)
1225 repo._run_git("commit", f'--author="{self.get_author()}"', "-m", msg)
1237 def __init__(self, message: str, rule: Optional["MergeRule"] = None) -> None:
1260 org: str, project: str, labels: List[str], template: str = "bug-report.yml"
1261 ) -> str:
1272 ) -> List[MergeRule]:
1275 NB: this function is used in Meta-internal workflows, see the comment
1298 pr: GitHubPR,
1303 ) -> Tuple[
1310 Returns merge rule matching to this pr together with the list of associated pending
1313 NB: this function is used in Meta-internal workflows, see the comment at the top of
1316 changed_files = pr.get_changed_files()
1317 approved_by = set(pr.get_approved_by())
1320 org=pr.org,
1321 project=pr.project,
1324 … reject_reason = f"No rule found to match PR. Please [report]{issue_link} this issue to DevX team."
1326 rules = read_merge_rules(repo, pr.org, pr.project)
1331 checks = pr.get_checkrun_conclusions()
1333 pr.pr_num,
1334 pr.project,
1350 # Score 0 to 10K - how many files rule matched
1351 # Score 10K - matched all files, but no overlapping approvers
1352 # Score 20K - matched all files and approvers, but mandatory checks are pending
1353 # Score 30k - Matched all files and approvers, but mandatory checks failed
1365 num_matching_files = len(changed_files) - len(non_matching_files)
1371 … f"{num_matching_files} files matched, but there are still non-matching files:",
1377 # If rule needs approvers but PR has not been reviewed, skip it
1381 reject_reason = f"PR #{pr.pr_num} has not been reviewed yet"
1384 # Does the PR have the required approvals for this rule?
1393 # If rule requires approvers but they aren't the ones that reviewed PR
1403 … f"- {name} ({', '.join(approved_by[:5])}{', ...' if len(approved_by) > 5 else ''})"
1412 # Does the PR pass the checks required by this rule?
1419 or ("Facebook CLA Check" in x)
1439 hud_link = f"https://hud.pytorch.org/{pr.org}/{pr.project}/commit/{pr.last_commit()['oid']}"
1445 f"{len(failed_checks)} mandatory check(s) failed. The first few are:",
1457 … f"{len(pending_checks)} mandatory check(s) are pending/not yet run. The first few are:",
1465 if not skip_internal_checks and pr.has_internal_changes():
1467 …"This PR has internal changes and must be landed via Phabricator! Please try reimporting/rexportin…
1494 def checks_to_str(checks: List[Tuple[str, Optional[str]]]) -> str:
1500 ) -> List[str]:
1502 f"- [{c[0]}]({c[1]})" if c[1] is not None else f"- {c[0]}" for c in checks[:5]
1526 ) -> None:
1554 "_id": f"{project}-{pr_num}-{comment_id}-{os.environ.get('GITHUB_RUN_ID')}",
1564 def get_rockset_results(head_sha: str, merge_base: str) -> List[Dict[str, Any]]:
1594 def get_drci_classifications(pr_num: int, project: str = "pytorch") -> Any:
1614 REMOVE_JOB_NAME_SUFFIX_REGEX = re.compile(r", [0-9]+, [0-9]+, .+\)$")
1617 def remove_job_name_suffix(name: str, replacement: str = ")") -> str:
1622 check: JobCheckState,
1624 ) -> bool:
1625 if not check or not drci_classifications:
1628 name = check.name
1629 job_id = check.job_id
1639 check: JobCheckState,
1641 ) -> bool:
1642 if not check or not drci_classifications:
1645 name = check.name
1646 job_id = check.job_id
1661 check: JobCheckState,
1663 ) -> bool:
1664 if not check or not drci_classifications:
1667 name = check.name
1668 job_id = check.job_id
1681 ) -> bool:
1683 After https://github.com/pytorch/test-infra/pull/4579, invalid cancelled
1707 ) -> Dict[str, JobCheckState]:
1710 # to get the latest results as well as update Dr.CI PR comment
1713 def get_readable_drci_results(drci_classifications: Any) -> str:
1727 # SandCastle, we fallback to any results we can find on Dr.CI check run summary
1743 for name, check in checks.items():
1744 if check.status == "SUCCESS" or check.status == "NEUTRAL":
1747 if is_unstable(check, drci_classifications):
1749 check.name,
1750 check.url,
1751 check.status,
1753 check.job_id,
1754 check.title,
1755 check.summary,
1761 if is_broken_trunk(check, drci_classifications):
1763 check.name,
1764 check.url,
1765 check.status,
1767 check.job_id,
1768 check.title,
1769 check.summary,
1773 elif is_flaky(check, drci_classifications):
1775 check.name,
1776 check.url,
1777 check.status,
1779 check.job_id,
1780 check.title,
1781 check.summary,
1785 elif is_invalid_cancel(name, check.status, drci_classifications):
1790 check.name,
1791 check.url,
1792 check.status,
1794 check.job_id,
1795 check.title,
1796 check.summary,
1802 check.name,
1803 check.url,
1804 check.status,
1806 check.job_id,
1807 check.title,
1808 check.summary,
1816 ) -> List[JobCheckState]:
1817 return [check for check in checks.values() if status_filter(check.status)]
1820 def get_pr_commit_sha(repo: GitRepo, pr: GitHubPR) -> str:
1821 commit_sha = pr.get_merge_commit()
1824 commits = repo.commits_resolving_gh_pr(pr.pr_num)
1826 raise PostCommentError("Can't find any commits resolving PR")
1831 repo: GitRepo, pr: GitHubPR, *, comment_id: Optional[int] = None
1832 ) -> Tuple[str, str]:
1834 pr.get_last_comment()
1836 else pr.get_comment_by_id(comment_id)
1844 if pr.is_base_repo_private():
1854 pr, repo, skip_mandatory_checks=True, skip_internal_checks=True
1856 commit_sha = get_pr_commit_sha(repo, pr)
1861 repo: GitRepo, pr: GitHubPR, only_closed: bool = True
1862 ) -> List[Tuple[str, GitHubPR]]:
1864 Get the PRs in the stack that are above this PR (inclusive).
1867 assert pr.is_ghstack_pr()
1868 orig_ref = f"{repo.remote}/{pr.get_ghstack_orig_ref()}"
1869 rev_list = repo.revlist(f"{pr.default_branch()}..{orig_ref}")
1872 f"PR {pr.pr_num} does not have any revisions associated with it"
1874 skip_len = len(rev_list) - 1
1876 candidate = repo.revlist(f"{pr.default_branch()}..{branch}")
1880 # Validate that candidate always ends rev-list
1881 if rev_list[-len(candidate) :] != candidate:
1885 # Remove commits original PR depends on
1887 rev_list = rev_list[:-skip_len]
1889 for pr_, sha in _revlist_to_prs(repo, pr, rev_list):
1907 ) -> None:
1910 for commit_sha, pr in shas_and_prs:
1911 … revert_msg = f"\nReverted {pr.get_pr_url()} on behalf of {prefix_with_github_url(author_login)}"
1913 repo.checkout(pr.default_branch())
1922 for commit_sha, pr in shas_and_prs:
1924 f"@{pr.get_pr_creator_login()} your PR has been successfully reverted."
1927 pr.has_internal_changes()
1928 and not pr.has_no_connected_diff()
1931 revert_message += "\n:warning: This PR might contain internal changes"
1932 revert_message += "\ncc: @pytorch/pytorch-dev-infra"
1934 pr.org, pr.project, pr.pr_num, revert_message, dry_run=dry_run
1937 pr.add_numbered_label("reverted", dry_run)
1939 gh_post_commit_comment(pr.org, pr.project, commit_sha, revert_msg)
1940 gh_update_pr_state(pr.org, pr.project, pr.pr_num)
1945 pr: GitHubPR,
1950 ) -> None:
1952 author_login, commit_sha = validate_revert(repo, pr, comment_id=comment_id)
1954 gh_post_pr_comment(pr.org, pr.project, pr.pr_num, str(e), dry_run=dry_run)
1959 f" ([comment]({pr.get_comment_by_id(comment_id).url}))\n"
1963 shas_and_prs = [(commit_sha, pr)]
1964 if pr.is_ghstack_pr():
1966 shas_and_prs = get_ghstack_dependent_prs(repo, pr)
1980 skip_internal_checks=can_skip_internal_checks(pr, comment_id),
1984 def prefix_with_github_url(suffix_str: str) -> str:
1988 def check_for_sev(org: str, project: str, skip_mandatory_checks: bool) -> None:
2009 def has_label(labels: List[str], pattern: Pattern[str] = CIFLOW_LABEL) -> bool:
2017 ) -> Tuple[
2094 pr: GitHubPR,
2102 ) -> None:
2103 initial_commit_sha = pr.last_commit()["oid"]
2104 pr_link = f"https://github.com/{pr.org}/{pr.project}/pull/{pr.pr_num}"
2107 if MERGE_IN_PROGRESS_LABEL not in pr.get_labels():
2108 gh_add_labels(pr.org, pr.project, pr.pr_num, [MERGE_IN_PROGRESS_LABEL], dry_run)
2112 pr.get_labels(),
2113 pr.pr_num,
2114 pr.org,
2115 pr.project,
2120 # ignored and is toggled by the --ignore-current flag
2123 if pr.is_ghstack_pr():
2124 get_ghstack_prs(repo, pr) # raises error if out of sync
2126 check_for_sev(pr.org, pr.project, skip_mandatory_checks)
2130 pr.org,
2131 pr.project,
2132 pr.pr_num,
2136 return pr.merge_into(
2143 # Check for approvals
2144 find_matching_merge_rule(pr, repo, skip_mandatory_checks=True)
2146 if not has_required_labels(pr):
2150 checks = pr.get_checkrun_conclusions()
2159 pr.org,
2160 pr.project,
2161 pr.pr_num,
2173 check_for_sev(pr.org, pr.project, skip_mandatory_checks)
2175 elapsed_time = current_time - start_time
2177 …f"Attempting merge of https://github.com/{pr.org}/{pr.project}/pull/{pr.pr_num} ({elapsed_time / 6…
2179 pr = GitHubPR(pr.org, pr.project, pr.pr_num)
2180 if initial_commit_sha != pr.last_commit()["oid"]:
2190 pr, repo, ignore_current_checks=ignore_current_checks
2199 checks = pr.get_checkrun_conclusions()
2201 pr.pr_num,
2202 pr.project,
2220 … f"{len(startup_failures)} STARTUP failures reported, please check workflows syntax! "
2239 return pr.merge_into(
2249 …f"Merge of https://github.com/{pr.org}/{pr.project}/pull/{pr.pr_num} failed due to: {ex}. Retrying…
2255 gh_add_labels(pr.org, pr.project, pr.pr_num, ["land-failed"], dry_run)
2259 def main() -> None:
2263 pr = GitHubPR(org, project, args.pr_num)
2265 def handle_exception(e: Exception, title: str = "Merge failed") -> None:
2300 get_revert_message(org, project, pr.pr_num),
2305 pr,
2311 handle_exception(e, f"Reverting PR {args.pr_num} failed")
2314 if pr.is_closed():
2319 f"Can't merge closed PR #{args.pr_num}",
2324 if pr.is_cross_repo() and pr.is_ghstack_pr():
2329 "Cross-repo ghstack merges are not supported",
2333 if not pr.is_ghstack_pr() and pr.base_ref() != pr.default_branch():
2338 f"PR targets {pr.base_ref()} rather than {pr.default_branch()}, refusing merge request",
2344 if pr.is_ghstack_pr():
2345 get_ghstack_prs(repo, pr) # raises error if out of sync
2346 pr.merge_changes(
2353 if not args.force and pr.has_invalid_submodule_updates():
2355 f"This PR updates submodules {', '.join(pr.get_changed_submodules())}\n"
2357 …message += '\nIf those updates are intentional, please add "submodule" keyword to PR title/descrip…
2362 pr,
2381 author=pr.get_author(),
2388 last_commit_sha=pr.last_commit().get("oid", ""),
2389 merge_base_sha=pr.get_merge_base(),
2396 print("Missing comment ID or PR number, couldn't upload to Rockset")