Beispiel #1
0
def merge_on_green(pr_num: int,
                   repo: GitRepo,
                   dry_run: bool = False,
                   timeout_minutes: int = 400) -> None:
    repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
    org, project = repo.gh_owner_and_name()
    start_time = time.time()
    last_exception = ''
    elapsed_time = 0.0
    while elapsed_time < timeout_minutes * 60:
        current_time = time.time()
        elapsed_time = current_time - start_time

        print(
            f"Attempting merge of https://github.com/{org}/{project}/pull/{pr_num} ({elapsed_time / 60} minutes elapsed)"
        )
        pr = GitHubPR(org, project, pr_num)
        try:
            return pr.merge_into(repo, dry_run=dry_run)
        except MandatoryChecksMissingError as ex:
            last_exception = str(ex)
            print(
                f"Merge of https://github.com/{org}/{project}/pull/{pr_num} failed due to: {ex}. Retrying in 60 seconds."
            )
            time.sleep(60)
    # Finally report timeout back
    msg = f"Merged timed out after {timeout_minutes} minutes. Please contact the pytorch_dev_infra team."
    msg += f"The last exception was: {last_exception}"
    if not dry_run:
        gh_add_labels(org, project, pr_num, ["land-failed"])
    raise RuntimeError(msg)
Beispiel #2
0
def main() -> None:
    args = parse_args()
    repo = GitRepo(get_git_repo_dir(), get_git_remote_name(), debug=True)
    org, project = repo.gh_owner_and_name()

    pr = GitHubPR(org, project, args.pr_num)

    if pr.is_closed():
        gh_post_comment(org,
                        project,
                        args.pr_num,
                        f"PR #{args.pr_num} is closed, won't rebase",
                        dry_run=args.dry_run)
        return

    try:
        if pr.is_ghstack_pr():
            rebase_ghstack_onto(pr,
                                repo,
                                dry_run=args.dry_run,
                                stable=args.stable)
            return
        rebase_onto(pr, repo, dry_run=args.dry_run, stable=args.stable)
    except Exception as e:
        msg = f"Rebase failed due to {e}"
        run_url = os.getenv("GH_RUN_URL")
        if run_url is not None:
            msg += f"\nRaised by {run_url}"
        gh_post_comment(org, project, args.pr_num, msg, dry_run=args.dry_run)
Beispiel #3
0
def main() -> None:
    args = parse_args()
    repo = GitRepo(get_git_repo_dir(), get_git_remote_name(), debug=True)
    org, project = repo.gh_owner_and_name()

    pr = GitHubPR(org, project, args.pr_num)
    onto_branch = args.branch if args.branch else pr.default_branch()

    msg = "@pytorchbot successfully started a rebase job."
    msg += f" Check the current status [here]({os.getenv('GH_RUN_URL')})"
    gh_post_comment(org, project, args.pr_num, msg, dry_run=args.dry_run)

    if pr.is_closed():
        gh_post_comment(org,
                        project,
                        args.pr_num,
                        f"PR #{args.pr_num} is closed, won't rebase",
                        dry_run=args.dry_run)
        return

    try:
        if pr.is_ghstack_pr():
            rebase_ghstack_onto(pr, repo, onto_branch, dry_run=args.dry_run)
            return
        rebase_onto(pr, repo, onto_branch, dry_run=args.dry_run)
    except Exception as e:
        msg = f"Rebase failed due to {e}"
        run_url = os.getenv("GH_RUN_URL")
        if run_url is not None:
            msg += f"\nRaised by {run_url}"
        gh_post_comment(org, project, args.pr_num, msg, dry_run=args.dry_run)
Beispiel #4
0
def main() -> None:
    args = parse_args()
    repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
    org, project = repo.gh_owner_and_name()

    pr = GitHubPR(org, project, args.pr_num)
    if args.revert:
        try:
            try_revert(repo, pr, dry_run=args.dry_run, comment_id=args.comment_id)
        except Exception as e:
            msg = f"Reverting PR {args.pr_num} failed due to {e}"
            run_url = os.getenv("GH_RUN_URL")
            if run_url is not None:
                msg += f"\nRaised by {run_url}"
            gh_post_comment(org, project, args.pr_num, msg, dry_run=args.dry_run)
        return

    if pr.is_closed():
        gh_post_comment(org, project, args.pr_num, f"Can't merge closed PR #{args.pr_num}", dry_run=args.dry_run)
        return

    if pr.is_cross_repo() and pr.is_ghstack_pr():
        gh_post_comment(org, project, args.pr_num, "Cross-repo ghstack merges are not supported", dry_run=args.dry_run)
        return

    try:
        pr.merge_into(repo, dry_run=args.dry_run, force=args.force)
    except Exception as e:
        msg = f"Merge failed due to {e}"
        run_url = os.getenv("GH_RUN_URL")
        if run_url is not None:
            msg += f"\nRaised by {run_url}"
        gh_post_comment(org, project, args.pr_num, msg, dry_run=args.dry_run)
        import traceback
        traceback.print_exc()
Beispiel #5
0
def main() -> None:
    args = parse_args()
    repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
    org, project = repo.gh_owner_and_name()
    pr = GitHubPR(org, project, args.pr_num)

    def handle_exception(e: Exception, msg: str = "Merge failed") -> None:
        msg += f" due to {e}"
        run_url = os.getenv("GH_RUN_URL")
        if run_url is not None:
            msg += f"\nRaised by {run_url}"
        gh_post_pr_comment(org,
                           project,
                           args.pr_num,
                           msg,
                           dry_run=args.dry_run)
        import traceback
        traceback.print_exc()

    msg = f"@pytorchbot successfully started a {'revert' if args.revert else 'merge'} job."
    msg += f" Check the current status [here]({os.getenv('GH_RUN_URL')})"
    gh_post_pr_comment(org, project, args.pr_num, msg, dry_run=args.dry_run)

    if args.revert:
        try:
            try_revert(repo,
                       pr,
                       dry_run=args.dry_run,
                       comment_id=args.comment_id,
                       reason=args.reason)
        except Exception as e:
            handle_exception(e, f"Reverting PR {args.pr_num} failed")
        return

    if pr.is_closed():
        gh_post_pr_comment(org,
                           project,
                           args.pr_num,
                           f"Can't merge closed PR #{args.pr_num}",
                           dry_run=args.dry_run)
        return

    if pr.is_cross_repo() and pr.is_ghstack_pr():
        gh_post_pr_comment(org,
                           project,
                           args.pr_num,
                           "Cross-repo ghstack merges are not supported",
                           dry_run=args.dry_run)
        return

    try:
        merge(args.pr_num,
              repo,
              dry_run=args.dry_run,
              force=args.force,
              comment_id=args.comment_id,
              on_green=args.on_green,
              mandatory_only=args.on_mandatory)
    except Exception as e:
        handle_exception(e)
Beispiel #6
0
def merge(pr_num: int, repo: GitRepo,
          dry_run: bool = False,
          force: bool = False,
          comment_id: Optional[int] = None,
          mandatory_only: bool = False,
          on_green: bool = False,
          land_checks: bool = False,
          timeout_minutes: int = 400,
          stale_pr_days: int = 3) -> None:
    repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
    org, project = repo.gh_owner_and_name()
    pr = GitHubPR(org, project, pr_num)
    initial_commit_sha = pr.last_commit()['oid']
    check_for_sev(org, project, force)
    if force or can_skip_internal_checks(pr, comment_id):
        # do not wait for any pending signals if PR is closed as part of co-development process
        return pr.merge_into(repo, dry_run=dry_run, force=force, comment_id=comment_id)
    if (datetime.utcnow() - pr.last_pushed_at()).days > stale_pr_days:
        raise RuntimeError("This PR is too stale; the last push date was more than 3 days ago. Please rebase and try again.")

    if land_checks:
        commit = pr.create_land_time_check_branch(repo, 'viable/strict', force=force, comment_id=comment_id)

    start_time = time.time()
    last_exception = ''
    elapsed_time = 0.0
    while elapsed_time < timeout_minutes * 60:
        check_for_sev(org, project, force)
        current_time = time.time()
        elapsed_time = current_time - start_time
        print(f"Attempting merge of https://github.com/{org}/{project}/pull/{pr_num} ({elapsed_time / 60} minutes elapsed)")
        pr = GitHubPR(org, project, pr_num)
        if initial_commit_sha != pr.last_commit()['oid']:
            raise RuntimeError("New commits were pushed while merging. Please rerun the merge command.")
        try:
            find_matching_merge_rule(pr, repo)
            pending = pr_get_pending_checks(pr)
            failing = pr_get_failed_checks(pr)

            if (not mandatory_only and on_green) and len(failing) > 0:
                raise RuntimeError(f"{len(failing)} additional jobs have failed, first few of them are: " +
                                   ' ,'.join(f"[{x[0]}]({x[1]})" for x in failing[:5]))
            if (not mandatory_only and on_green) and len(pending) > 0:
                raise MandatoryChecksMissingError(f"Still waiting for {len(pending)} additional jobs to finish, " +
                                                  f"first few of them are: {' ,'.join(x[0] for x in pending[:5])}")
            if land_checks:
                validate_land_time_checks(repo, commit)

            return pr.merge_into(repo, dry_run=dry_run, force=force, comment_id=comment_id)
        except MandatoryChecksMissingError as ex:
            last_exception = str(ex)
            print(f"Merge of https://github.com/{org}/{project}/pull/{pr_num} failed due to: {ex}. Retrying in 5 min")
            time.sleep(5 * 60)
    # Finally report timeout back
    msg = f"Merged timed out after {timeout_minutes} minutes. Please contact the pytorch_dev_infra team."
    msg += f"The last exception was: {last_exception}"
    if not dry_run:
        gh_add_labels(org, project, pr_num, ["land-failed"])
    raise RuntimeError(msg)
Beispiel #7
0
 def test_pending_status_check(self, mocked_gql: Any, mocked_read_merge_rules: Any) -> None:
     """ Tests that PR with nonexistent/pending status checks fails with the right reason.
     """
     pr = GitHubPR("pytorch", "pytorch", 76118)
     repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
     self.assertRaisesRegex(MandatoryChecksMissingError,
                            ".*are pending/not yet run.*",
                            lambda: find_matching_merge_rule(pr, repo))
Beispiel #8
0
def main() -> None:
    args = parse_args()
    repo = GitRepo(get_git_repo_dir(), get_git_remote_name(), debug=True)
    org, project = repo.gh_owner_and_name()
    run_url = os.environ.get("GH_RUN_URL")

    job_link = f"[job]({run_url})" if run_url is not None else "job"
    msg = (
        f"The {args.action} {job_link} was canceled. If you believe this is a mistake,"
        + f"then you can re trigger it through [pytorch-bot]({BOT_COMMANDS_WIKI})."
    )

    gh_post_pr_comment(org, project, args.pr_num, msg)
    print(org, project, args.pr_num, msg)
Beispiel #9
0
 def test_rebase(self, mocked_post_comment: Any, mocked_run_git: Any,
                 mocked_gql: Any) -> None:
     "Tests rebase successfully"
     pr = GitHubPR("pytorch", "pytorch", 31093)
     repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
     rebase_onto(pr, repo)
     calls = [
         mock.call('fetch', 'origin', 'pull/31093/head:pull/31093/head'),
         mock.call('rebase', 'master', 'pull/31093/head'),
         mock.call('push', '-f', 'https://github.com/mingxiaoh/pytorch.git',
                   'pull/31093/head:master')
     ]
     mocked_run_git.assert_has_calls(calls)
     self.assertTrue("Successfully rebased `master` onto `master`" in
                     mocked_post_comment.call_args[0][3])
Beispiel #10
0
 def test_no_need_to_rebase(self, mocked_post_comment: Any,
                            mocked_run_git: Any, mocked_gql: Any) -> None:
     "Tests branch already up to date"
     pr = GitHubPR("pytorch", "pytorch", 31093)
     repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
     rebase_onto(pr, repo)
     calls = [
         mock.call('fetch', 'origin', 'pull/31093/head:pull/31093/head'),
         mock.call('rebase', 'master', 'pull/31093/head'),
         mock.call('push', '-f', 'https://github.com/mingxiaoh/pytorch.git',
                   'pull/31093/head:master')
     ]
     mocked_run_git.assert_has_calls(calls)
     self.assertTrue(
         "Tried to rebase and push PR #31093, but it was already up to date"
         in mocked_post_comment.call_args[0][3])
Beispiel #11
0
def main() -> None:
    import sys
    args = parse_args()
    repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
    org, project = repo.gh_owner_and_name()

    pr = GitHubPR(org, project, args.pr_num)
    if pr.is_closed():
        print(gh_post_comment(org, project, args.pr_num, f"Can't merge closed PR #{args.pr_num}", dry_run=args.dry_run))
        sys.exit(-1)

    if pr.is_cross_repo():
        print(gh_post_comment(org, project, args.pr_num, "Cross-repo merges are not supported at the moment", dry_run=args.dry_run))
        sys.exit(-1)

    try:
        pr.merge_into(repo, dry_run=args.dry_run)
    except Exception as e:
        gh_post_comment(org, project, args.pr_num, f"Merge failed due to {e}", dry_run=args.dry_run)
Beispiel #12
0
def main() -> None:
    args = parse_args()
    repo = GitRepo(get_git_repo_dir(), debug=args.debug)
    repo.cherry_pick_commits(args.sync_branch, args.default_branch)
    repo.push(args.default_branch, args.dry_run)
Beispiel #13
0
 def test_lint_fails(self, mocked_gql: Any) -> None:
     "Tests that PR fails mandatory lint check"
     pr = GitHubPR("pytorch", "pytorch", 74649)
     repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
     self.assertRaises(RuntimeError,
                       lambda: find_matching_merge_rule(pr, repo))
Beispiel #14
0
 def test_match_rules(self, mocked_gql: Any) -> None:
     "Tests that PR passes merge rules"
     pr = GitHubPR("pytorch", "pytorch", 77700)
     repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
     self.assertTrue(find_matching_merge_rule(pr, repo) is not None)
Beispiel #15
0
 def __init__(self) -> None:
     super().__init__(get_git_repo_dir(), get_git_remote_name())
Beispiel #16
0
 def test_revert_rules(self, mock_gql: Any) -> None:
     """ Tests that reverts from collaborators are allowed """
     pr = GitHubPR("pytorch", "pytorch", 79694)
     repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
     self.assertIsNotNone(validate_revert(repo, pr, comment_id=1189459845))
Beispiel #17
0
 def test_merge_rules_valid(self) -> None:
     "Test that merge_rules.json can be parsed"
     repo = GitRepo(get_git_repo_dir(), get_git_repo_dir())
     self.assertGreater(len(read_merge_rules(repo, "pytorch", "pytorch")), 1)