diff --git a/docs/upload.md b/docs/upload.md index 9400ab5..eab1d4b 100644 --- a/docs/upload.md +++ b/docs/upload.md @@ -10,6 +10,7 @@ revup upload - Modify or create code reviews. `[--status] [--no-update-pr-body] [--review-graph]` `[--trim-tags] [--create-local-branches] [--patchsets] [--auto-add-users=]` `[--force-reviewers] [--pr-body-source=] [--skip-empty-first-commit]` +`[--draft-on-create-only]` `[--labels=] []` # DESCRIPTION @@ -72,7 +73,8 @@ name in github exactly. If a label cannot be found a warning is printed. **Draft:** : Accepts "true" or "false" and marks or unmarks the PR as a draft. PRs are -not drafts if this tag is omitted. +not drafts if this tag is omitted. See --draft-on-create-only to only apply +this tag when the PR is created. **Uploader:** : Optionally specifies a custom uploader name that will be used instead of the @@ -236,5 +238,10 @@ If "a2r", add users from the Assignees tag as reviewers. If "both", do both of t using an empty commit purely for PR title and body text without that commit appearing in the merged history. +**--draft-on-create-only** +: Only use the "Draft:" tag to decide draft status when creating a PR, and never +change it afterwards. This allows marking a PR ready for review in github without +revup turning it back into a draft. + **--head** : The name or commit of the branch to be uploaded. If not specified, defaults to HEAD. diff --git a/revup/revup.py b/revup/revup.py index dec2504..24d5f7a 100755 --- a/revup/revup.py +++ b/revup/revup.py @@ -197,6 +197,7 @@ def build_parser() -> Tuple[RevupArgParser, List[RevupArgParser]]: default=PrBodySource.FIRST_COMMIT, ) upload_parser.add_argument("--skip-empty-first-commit", action="store_true") + upload_parser.add_argument("--draft-on-create-only", action="store_true") upload_parser.add_argument("--head", default="HEAD") restack_parser.add_argument("--topicless-last", "-t", action="store_true") diff --git a/revup/topic_stack.py b/revup/topic_stack.py index a61895d..989c695 100644 --- a/revup/topic_stack.py +++ b/revup/topic_stack.py @@ -1267,6 +1267,7 @@ def populate_update_info( update_pr_body_arg: bool, force_reviewers: bool = False, pr_body_source: PrBodySource = PrBodySource.FIRST_COMMIT, + draft_on_create_only: bool = False, ) -> None: """ Populate information necessary to do PR creation / update on the forge. @@ -1391,7 +1392,10 @@ def populate_update_info( review.pr_update.body = body if update_pr_body and review.pr_info.title != title: review.pr_update.title = title - if review.pr_info.is_draft != review.is_draft: + if draft_on_create_only: + # The forge's draft status wins, since the user may have changed it there + review.is_draft = review.pr_info.is_draft + elif review.pr_info.is_draft != review.is_draft: review.pr_update.is_draft = review.is_draft review.pr_update.label_ids = label_ids review.pr_update.reviewer_ids = reviewer_ids diff --git a/revup/upload.py b/revup/upload.py index d9d5889..ea9000d 100644 --- a/revup/upload.py +++ b/revup/upload.py @@ -90,7 +90,12 @@ async def run( return if not args.push_only: - topics.populate_update_info(args.update_pr_body, args.force_reviewers, args.pr_body_source) + topics.populate_update_info( + args.update_pr_body, + args.force_reviewers, + args.pr_body_source, + args.draft_on_create_only, + ) if not args.skip_confirm and topics.num_reviews_changed() > 0: topics.print(not args.verbose) if git_ctx.sh.wait_for_confirmation(): diff --git a/tests/test_upload.py b/tests/test_upload.py index 4ee8537..4068b80 100644 --- a/tests/test_upload.py +++ b/tests/test_upload.py @@ -1429,6 +1429,7 @@ def make_forge_upload_args(**kwargs): "verbose": False, "force_reviewers": False, "pr_body_source": PrBodySource.FIRST_COMMIT, + "draft_on_create_only": False, } defaults.update(kwargs) return argparse.Namespace(**defaults) @@ -1731,6 +1732,44 @@ async def test_draft_tag_creates_draft_pr(self): pr = forge.created_prs[0] assert pr.is_draft is True + @async_test + async def test_draft_tag_redrafts_undrafted_pr(self): + async with GitTestEnvironment() as env: + await setup_repo(env) + forge = FakeForge() + await env.commit("feat\n\nTopic: alpha\nDraft: true", {"a.txt": "a"}) + + await full_upload_pipeline(env, forge) + + # Simulate the user marking the pr ready for review in github + pr = list(forge.prs.values())[0] + pr.is_draft = False + + topics = await full_upload_pipeline(env, forge) + + assert topics.topics["alpha"].reviews["origin/main"].pr_update.is_draft is True + assert pr.is_draft is True + + @async_test + async def test_draft_on_create_only_keeps_pr_undrafted(self): + async with GitTestEnvironment() as env: + await setup_repo(env) + forge = FakeForge() + await env.commit("feat\n\nTopic: alpha\nDraft: true", {"a.txt": "a"}) + + await full_upload_pipeline(env, forge, draft_on_create_only=True) + assert forge.created_prs[0].is_draft is True + + pr = list(forge.prs.values())[0] + pr.is_draft = False + + topics = await full_upload_pipeline(env, forge, draft_on_create_only=True) + + review = topics.topics["alpha"].reviews["origin/main"] + assert review.pr_update.is_draft is None + assert review.is_draft is False + assert pr.is_draft is False + class TestForgeReviewGraph: @async_test