diff --git a/src/stack_pr/cli.py b/src/stack_pr/cli.py index b5f4023..9ec2e1f 100755 --- a/src/stack_pr/cli.py +++ b/src/stack_pr/cli.py @@ -294,7 +294,7 @@ class StackEntry: _pr: str | None = None _base: str | None = None _head: str | None = None - need_update: bool = False + is_tmp_draft: bool = False @property def pr(self) -> str: @@ -783,7 +783,7 @@ def toc_entry(se: StackEntry) -> str: return f"Stacked PRs:\n{''.join(entries)}\n" -def get_current_pr_body(e: StackEntry) -> str: +def get_pr_body(e: StackEntry) -> str: out = get_command_output( ["gh", "pr", "view", e.pr, "--json", "body"], ) @@ -815,7 +815,7 @@ def add_cross_links(st: list[StackEntry], *, keep_body: bool, verbose: bool) -> if keep_body: # Keep current body of the PR after the cross links component - current_pr_body = get_current_pr_body(e) + current_pr_body = get_pr_body(e) body_content = current_pr_body.split(CROSS_LINKS_DELIMETER, 1)[-1].lstrip() pr_body = [*header, body_content] @@ -831,6 +831,13 @@ def add_cross_links(st: list[StackEntry], *, keep_body: bool, verbose: bool) -> raise RuntimeError +def is_draft_pr(e: StackEntry) -> bool: + out = get_command_output( + ["gh", "pr", "view", e.pr, "--json", "isDraft"], + ) + return json.loads(out)["isDraft"] + + # Temporarily set base branches of existing PRs to the bottom of the stack. # This needs to be done to avoid PRs getting closed when commits are # rearranged. @@ -852,14 +859,21 @@ def add_cross_links(st: list[StackEntry], *, keep_body: bool, verbose: bool) -> # stack/2. If we push stack/1, then PR #2 gets automatically closed, since its # head branch will contain all the commits from its base branch. # -# To avoid this, we temporarily set all base branches to point to 'main' - once -# all the branches are pushed we can set the actual base branches. +# To avoid this, we temporarily set all base branches to point to 'main'. To ensure +# we don't accidentally notify reviewers in this transient state (where the PRs are +# pointing to 'main'), we mark the PRs as draft - once all the branches are pushed +# we can set the actual base branches and undraft the PRs. def reset_remote_base_branches( st: list[StackEntry], target: str, *, verbose: bool ) -> None: log(h("Resetting remote base branches"), level=2) for e in filter(lambda e: e.has_pr(), st): + # We need to check if the PR is already draft, otherwise we would + # unintentionally undo the draft status later. + if not is_draft_pr(e): + run_shell_command(["gh", "pr", "ready", e.pr, "--undo"], quiet=not verbose) + e.is_tmp_draft = True run_shell_command(["gh", "pr", "edit", e.pr, "-B", target], quiet=not verbose) @@ -1117,6 +1131,12 @@ def command_submit( log(h("Adding cross-links to PRs"), level=1) add_cross_links(st, keep_body=keep_body, verbose=args.verbose) + # Undraft the PRs if they were marked as temporary drafts. + for e in st: + if e.is_tmp_draft: + run_shell_command(["gh", "pr", "ready", e.pr], quiet=not args.verbose) + e.is_tmp_draft = False + if need_to_rebase_current: log(h(f"Rebasing the original branch '{current_branch}'"), level=2) run_shell_command(