Add jj support to new_proposal.py (#6700)

Also scrutinizing how it runs from another directory, because that's
what I did to test these changes. Switching to the repo root is to make
it easier to just look for ".jj".

Assisted-by: Google Antigravity with Gemini 3 Flash
This commit is contained in:
Jon Ross-Perkins
2026-02-06 19:53:35 +00:00
committed by GitHub
parent c7a7688d21
commit 70614da67e
2 changed files with 77 additions and 50 deletions
+73 -46
View File
@@ -15,6 +15,7 @@ import shlex
import shutil
import subprocess
import sys
from pathlib import Path
from typing import List, Optional
_PROMPT = """This will:
@@ -63,9 +64,10 @@ def _parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
"'origin'.",
)
parser.add_argument(
"--proposals-dir",
metavar="PROPOSALS_DIR",
help="The proposals directory, mainly for testing cross-repository. "
"--repo_root",
metavar="REPO_ROOT",
default=Path(__file__).parents[2],
help="The repository root, mainly for testing cross-repository. "
"Automatically found by default.",
)
parser.add_argument(
@@ -109,16 +111,6 @@ def _fill_template(template_path: str, title: str, pr_num: int) -> str:
return content
def _get_proposals_dir(parsed_args: argparse.Namespace) -> str:
"""Returns the path to the proposals directory."""
if parsed_args.proposals_dir:
assert isinstance(parsed_args.proposals_dir, str)
return parsed_args.proposals_dir
return os.path.realpath(
os.path.join(os.path.dirname(__file__), "../../proposals")
)
def _run(
argv: List[str], check: bool = True, get_stdout: bool = False
) -> Optional[str]:
@@ -156,22 +148,28 @@ def _run_pr_create(argv: List[str]) -> int:
def main() -> None:
parsed_args = _parse_args()
# Switch to repo root early for consistent execution.
os.chdir(parsed_args.repo_root)
title = parsed_args.title
branch = _calculate_branch(parsed_args)
# Verify tools are available.
gh_bin = _find_tool("gh")
git_bin = _find_tool("git")
if Path(".jj").is_dir():
jj_bin = _find_tool("jj")
git_bin = None
else:
git_bin = _find_tool("git")
jj_bin = None
precommit_bin = _find_tool("pre-commit")
# Ensure a good working directory.
proposals_dir = _get_proposals_dir(parsed_args)
os.chdir(proposals_dir)
# Verify there are no uncommitted changes.
p = subprocess.run([git_bin, "diff-index", "--quiet", "HEAD", "--"])
if p.returncode != 0:
exit("ERROR: There are uncommitted changes in your git repo.")
# Verify there are no uncommitted changes (jj has no equivalent).
if git_bin:
p = subprocess.run([git_bin, "diff-index", "--quiet", "HEAD", "--"])
if p.returncode != 0:
exit("ERROR: There are uncommitted changes in your git repo.")
# Prompt before proceeding.
response = "?"
@@ -181,25 +179,51 @@ def main() -> None:
exit("ERROR: Cancelled")
# Create a proposal branch.
_run(
[git_bin, "switch", "--create", branch, parsed_args.branch_start_point]
)
_run([git_bin, "push", "-u", parsed_args.remote, branch])
if git_bin:
_run(
[
git_bin,
"switch",
"--create",
branch,
parsed_args.branch_start_point,
]
)
_run([git_bin, "push", "-u", parsed_args.remote, branch])
else:
assert jj_bin # For mypy.
_run([jj_bin, "new", parsed_args.branch_start_point])
_run([jj_bin, "bookmark", "create", branch])
# Copy template.md to a temp file.
template_path = os.path.join(proposals_dir, "scripts/template.md")
temp_path = os.path.join(proposals_dir, "new-proposal.tmp.md")
template_path = "proposals/scripts/template.md"
temp_path = "proposals/new-proposal.tmp.md"
shutil.copyfile(template_path, temp_path)
_run([git_bin, "add", temp_path])
_run([git_bin, "commit", "-m", "Creating new proposal: %s" % title])
initial_desc = "Creating new proposal: %s" % title
if git_bin:
_run([git_bin, "add", temp_path])
_run([git_bin, "commit", "-m", initial_desc])
else:
assert jj_bin # For mypy.
_run([jj_bin, "describe", "-m", initial_desc])
# Create a PR with WIP+proposal labels.
_run([git_bin, "push"])
if git_bin:
_run([git_bin, "push"])
user = _run([git_bin, "config", "get", "user.name"], get_stdout=True)
else:
assert jj_bin # For mypy.
_run([jj_bin, "git", "push"])
user = _run([jj_bin, "config", "get", "user.name"], get_stdout=True)
assert user # For mypy.
user = user.strip()
pr_num = _run_pr_create(
[
gh_bin,
"pr",
"create",
"--head",
"%s:%s" % (user, branch),
"--draft",
"--label",
"proposal",
@@ -216,25 +240,28 @@ def main() -> None:
# Remove the temp file, create p####.md, and fill in PR information.
os.remove(temp_path)
final_path = os.path.join(proposals_dir, "p%04d.md" % pr_num)
final_path = "proposals/p%04d.md" % pr_num
content = _fill_template(template_path, title, pr_num)
with open(final_path, "w") as final_file:
final_file.write(content)
_run([git_bin, "add", temp_path, final_path])
_run([precommit_bin, "run"], check=False) # Needs a ToC update.
_run([git_bin, "add", final_path, os.path.join(proposals_dir, "README.md")])
_run(
[
git_bin,
"commit",
"--amend",
"-m",
"Filling out template with PR %d" % pr_num,
]
)
# Push the PR update.
_run([git_bin, "push", "--force-with-lease"])
# Run pre-commit for a ToC update, then push the PR update.
final_desc = "Filling out template with PR %d" % pr_num
if git_bin:
_run([git_bin, "add", temp_path, final_path])
_run([precommit_bin, "run"], check=False)
_run([git_bin, "commit", "--amend", "-m", final_desc])
_run([git_bin, "push", "--force-with-lease"])
else:
assert jj_bin # For mypy.
_run(
[precommit_bin, "run", "--files", "$(jj diff --name-only)"],
check=False,
)
_run([jj_bin, "describe", "-m", final_desc])
_run([jj_bin, "git", "push"])
print(
"\nCreated PR %d for %s. Make changes to:\n %s"
+4 -4
View File
@@ -35,12 +35,12 @@ class TestNewProposal(unittest.TestCase):
self.assertEqual(new_proposal._calculate_branch(parsed_args), "wiz")
def test_fill_template(self):
# Switch directories for the test.
parsed_args = new_proposal._parse_args(["foo"])
os.chdir(parsed_args.repo_root)
content = new_proposal._fill_template(
os.path.join(
new_proposal._get_proposals_dir(parsed_args),
"scripts/template.md",
),
"proposals/scripts/template.md",
"TITLE",
123,
)