Example #1
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_comment(org, project, args.pr_num, msg, dry_run=args.dry_run)
        import traceback
        traceback.print_exc()

    if args.revert:
        try:
            try_revert(repo,
                       pr,
                       dry_run=args.dry_run,
                       comment_id=args.comment_id)
        except Exception as e:
            handle_exception(e, f"Reverting PR {args.pr_num} failed")
        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

    if args.on_green:
        try:
            merge_on_green(args.pr_num, repo, dry_run=args.dry_run)
        except Exception as e:
            handle_exception(e)
    else:
        try:
            pr.merge_into(repo, dry_run=args.dry_run, force=args.force)
        except Exception as e:
            handle_exception(e)
Example #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()
    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)
Example #3
0
def fetch_check_run_conclusions(repo: GitRepo,
                                commit: str) -> Dict[str, Tuple[str, str]]:
    [owner, name] = repo.gh_owner_and_name()
    checks = fetch_json_dict(
        f'https://api.github.com/repos/{owner}/{name}/commits/{commit}/check-runs'
    )
    check_run_conclusions = {}
    if len(checks['check_runs']) == 0:
        raise MandatoryChecksMissingError(
            "Refusing to merge as land check(s) are not yet run")
    for check_run in checks['check_runs']:
        check_run_conclusions[check_run['name']] = (check_run['conclusion'],
                                                    check_run['html_url'])
    return check_run_conclusions
Example #4
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,
          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']
    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.")

    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)
        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])}")
            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)
Example #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)
    if args.revert:
        try:
            try_revert(repo, pr, dry_run=args.dry_run)
        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)
    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)
Example #6
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,
              land_checks=args.land_checks)
    except Exception as e:
        handle_exception(e)
Example #7
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)
Example #8
0
def merge_on_green(pr_num: int, repo: GitRepo, dry_run: bool = False) -> 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 < 400 * 60:
        current_time = time.time()
        elapsed_time = current_time - start_time

        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"Merged failed due to: {ex}. Retrying in 60 seconds.")
            time.sleep(60)
    # Finally report timeout back
    msg = "Merged timed out after 6 hours. 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)
Example #9
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

    if pr.is_ghstack_pr():
        gh_post_comment(org, project, args.pr_num,
                        f"PR #{args.pr_num} is a ghstack, which is currently not supported", dry_run=args.dry_run)
        return

    try:
        rebase_onto(pr, repo, 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)
Example #10
0
def rebase_ghstack_onto(pr: GitHubPR,
                        repo: GitRepo,
                        onto_branch: str,
                        dry_run: bool = False) -> None:
    if subprocess.run([sys.executable, "-m", "ghstack", "--help"],
                      capture_output=True).returncode != 0:
        subprocess.run([sys.executable, "-m", "pip", "install", "ghstack"])
    orig_ref = f"{re.sub(r'/head$', '/orig', pr.head_ref())}"
    onto_branch = f"refs/remotes/origin/{onto_branch}"

    repo.fetch(orig_ref, orig_ref)
    repo._run_git("rebase", onto_branch, orig_ref)

    # steal the identity of the committer of the commit on the orig branch
    email = repo._run_git("log", orig_ref, "--pretty=format:%ae", "-1")
    name = repo._run_git("log", orig_ref, "--pretty=format:%an", "-1")
    repo._run_git("config", "--global", "user.name", name)
    repo._run_git("config", "--global", "user.email", email)

    os.environ["OAUTH_TOKEN"] = os.environ["GITHUB_TOKEN"]
    with open('.ghstackrc', 'w+') as f:
        f.write('[ghstack]\n' + "github_url=github.com\n" +
                "github_username=pytorchmergebot\n" + "remote_name=origin")

    if dry_run:
        print("Don't know how to dry-run ghstack")
    else:
        ghstack_result = subprocess.run(["ghstack"], capture_output=True)
        push_result = ghstack_result.stdout.decode("utf-8")
        print(push_result)
        if ghstack_result.returncode != 0:
            raise Exception(f"\n```{push_result}```")
        # The contents of a successful push result should look like:
        # Summary of changes (ghstack 0.6.0)

        #  - Updated https://github.com/clee2000/random-testing/pull/2
        #  - Updated https://github.com/clee2000/random-testing/pull/1

        # Facebook employees can import your changes by running
        # (on a Facebook machine):

        #     ghimport -s https://github.com/clee2000/random-testing/pull/2

        # If you want to work on this diff stack on another machine:

        #     ghstack checkout https://github.com/clee2000/random-testing/pull/2
        org, project = repo.gh_owner_and_name()
        for line in push_result.splitlines():
            if "Updated" in line:
                pr_num = int(line.split("/")[-1])
                if pr_num != pr.pr_num:
                    gh_post_comment(
                        pr.org,
                        pr.project,
                        pr_num,
                        f"Rebased `{orig_ref}` onto `{onto_branch}` because #{pr.pr_num} was rebased, "
                        "please pull locally before adding more changes (for example, via `ghstack "
                        +
                        f"checkout https://github.com/{org}/{project}/pull/{pr_num}`)",
                        dry_run=dry_run)
                else:
                    gh_post_comment(
                        pr.org,
                        pr.project,
                        pr_num,
                        f"Successfully rebased `{orig_ref}` onto `{onto_branch}`, please pull locally "
                        +
                        "before adding more changes (for example, via `ghstack "
                        +
                        f"checkout https://github.com/{org}/{project}/pull/{pr.pr_num}`)",
                        dry_run=dry_run)

        if f"Skipped https://github.com/{org}/{project}/pull/{pr.pr_num}" in push_result:
            gh_post_comment(
                pr.org,
                pr.project,
                pr.pr_num,
                f"Tried to rebase and push PR #{pr.pr_num}, but it was already up to date",
                dry_run=dry_run)
Example #11
0
def rebase_ghstack_onto(pr: GitHubPR,
                        repo: GitRepo,
                        dry_run: bool = False,
                        stable: bool = False) -> None:
    if subprocess.run([sys.executable, "-m", "ghstack", "--help"],
                      capture_output=True).returncode != 0:
        subprocess.run([sys.executable, "-m", "pip", "install", "ghstack"])
    orig_ref = f"{re.sub(r'/head$', '/orig', pr.head_ref())}"
    onto_branch = "refs/remotes/origin/viable/strict" if stable else pr.default_branch(
    )

    repo.fetch(orig_ref, orig_ref)
    repo._run_git("rebase", onto_branch, orig_ref)
    if dry_run:
        print("Don't know how to dry-run ghstack")
    else:
        ghstack_result = subprocess.run(["ghstack"], capture_output=True)
        push_result = ghstack_result.stdout.decode("utf-8")
        print(push_result)
        if ghstack_result.returncode != 0:
            raise Exception(f"\n```{push_result}```")
        # The contents of a successful push result should look like:
        # Summary of changes (ghstack 0.6.0)

        #  - Updated https://github.com/clee2000/random-testing/pull/2
        #  - Updated https://github.com/clee2000/random-testing/pull/1

        # Facebook employees can import your changes by running
        # (on a Facebook machine):

        #     ghimport -s https://github.com/clee2000/random-testing/pull/2

        # If you want to work on this diff stack on another machine:

        #     ghstack checkout https://github.com/clee2000/random-testing/pull/2
        org, project = repo.gh_owner_and_name()
        for line in push_result.splitlines():
            if "Updated" in line:
                pr_num = int(line.split("/")[-1])
                if pr_num != pr.pr_num:
                    gh_post_comment(
                        pr.org,
                        pr.project,
                        pr_num,
                        f"Rebased `{orig_ref}` onto `{onto_branch}` because #{pr.pr_num} was rebased, "
                        "please pull locally before adding more changes (for example, via `git checkout "
                        f"{orig_ref} && git pull --rebase`)",
                        dry_run=dry_run)
                else:
                    gh_post_comment(
                        pr.org,
                        pr.project,
                        pr_num,
                        f"Successfully rebased `{orig_ref}` onto `{onto_branch}`, please pull locally "
                        +
                        f"before adding more changes (for example, via `git checkout {orig_ref} && "
                        + "git pull --rebase`)",
                        dry_run=dry_run)

        if f"Skipped https://github.com/{org}/{project}/pull/{pr.pr_num}" in push_result:
            gh_post_comment(
                pr.org,
                pr.project,
                pr.pr_num,
                f"Tried to rebase and push PR #{pr.pr_num}, but it was already up to date",
                dry_run=dry_run)