Skip to content

Commit 001285e

Browse files
authored
Create template sync PRs from versioned branch (#401)
* Adapt TemplateUpdatePR class to distinguish between template and pr branches * Create versioned branch before making PR * Fix PR base branch name * Disable dry-run
1 parent e6e1e37 commit 001285e

2 files changed

Lines changed: 49 additions & 29 deletions

File tree

.github/workflows/cruft-prs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
with:
2323
cache-dependency-glob: scripts/pyproject.toml
2424
- name: Update template repo registry
25-
run: uvx --from ./scripts send-cruft-prs ${{ env.RELEASE }} --all_repos --log-dir log --dry-run
25+
run: uvx --from ./scripts send-cruft-prs ${{ env.RELEASE }} --all_repos --log-dir log
2626
env:
2727
RELEASE: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.release }}
2828
GITHUB_TOKEN: ${{ secrets.BOT_GH_TOKEN }}

scripts/src/scverse_template_scripts/cruft_prs.py

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,20 @@ def title(self) -> str:
121121
return f"{self.title_prefix}{self.release.tag_name}"
122122

123123
@property
124-
def branch(self) -> str:
124+
def template_branch(self) -> str:
125+
"""Branch name in the forked repo that tracks template updates (stay the same across versions)"""
125126
# as of v0.5.0 (new template sync), the branch name does not contain the release-tag anymore
126127
return f"{self.branch_prefix}{self.repo_id}"
127128

129+
@property
130+
def pr_branch(self) -> str:
131+
"""Name of the branch that is used to create the pull-request. A new branch is created for each version."""
132+
return f"{self.template_branch}-{self.release.tag_name}"
133+
128134
@property
129135
def namespaced_head(self) -> str:
130-
return f"{self.con.login}:{self.branch}"
136+
"""Branch used to crate the pull request, including repo namespace"""
137+
return f"{self.con.login}:{self.pr_branch}"
131138

132139
@property
133140
def body(self) -> str:
@@ -141,9 +148,9 @@ def matches_prefix(self, pr: PullRequest) -> bool:
141148
# Don’t compare title prefix, people might rename PRs
142149
return pr.head.ref.startswith(self.branch_prefix) and pr.user.id == self.con.user.id
143150

144-
def matches_exact_branch_name(self, pr: PullRequest) -> bool:
151+
def matches_current_version(self, pr: PullRequest) -> bool:
145152
"""Check if `pr` is a template update PR for the current version"""
146-
return pr.head.ref == self.branch and pr.user.id == self.con.user.id
153+
return pr.head.ref == self.pr_branch and pr.user.id == self.con.user.id
147154

148155

149156
class RepoInfo(TypedDict):
@@ -382,6 +389,7 @@ def template_update( # noqa: PLR0913, (= too many function arguments)
382389
forked_repo: GHRepo,
383390
original_repo: GHRepo,
384391
template_branch_name: str,
392+
versioned_branch_name: str,
385393
tag_name: str,
386394
cruft_log_file: Path,
387395
dry_run: bool,
@@ -400,6 +408,8 @@ def template_update( # noqa: PLR0913, (= too many function arguments)
400408
4) Use `cruft create` to instantiate the template into a separate directory
401409
5) sync the changes from the separate directory into the `template-branch`
402410
6) commit
411+
7) check out commit into a version-specific branch used for making the pull request. See #396 for why this is
412+
necessary.
403413
404414
--> From this commit, we can make a pull-request to the original repo including the latest template-changes.
405415
@@ -411,6 +421,8 @@ def template_update( # noqa: PLR0913, (= too many function arguments)
411421
The repo forked in scverse-bot namespace
412422
template_branch_name
413423
branch name to use for the template in the forked repo
424+
versioned_branch_name
425+
version-specific branch name (will be created off the template branch)
414426
original_repo
415427
The original (upstream) repo
416428
tag_name
@@ -446,17 +458,19 @@ def template_update( # noqa: PLR0913, (= too many function arguments)
446458
tmp_config = json.load(f)
447459
exclude_files = tmp_config["context"]["cookiecutter"].get("_exclude_on_template_update", [])
448460

449-
if _commit_update(
450-
clone,
451-
exclude_files=exclude_files,
452-
commit_msg=f"Automated template update to {tag_name}",
453-
commit_author=f"{con.sig.name} <{con.sig.email}>",
454-
):
455-
if not dry_run:
456-
clone.git.push("origin", template_branch_name)
457-
return True
458-
else:
459-
return False
461+
if (
462+
updated := _commit_update(
463+
clone,
464+
exclude_files=exclude_files,
465+
commit_msg=f"Automated template update to {tag_name}",
466+
commit_author=f"{con.sig.name} <{con.sig.email}>",
467+
)
468+
) and not dry_run:
469+
clone.git.switch(versioned_branch_name, template_branch_name, C=True)
470+
clone.git.push("origin", template_branch_name)
471+
clone.git.push("origin", versioned_branch_name)
472+
473+
return updated
460474

461475

462476
def make_pr(con: GitHubConnection, release: GHRelease, repo_url: str, *, log_dir: Path, dry_run: bool = False) -> None:
@@ -490,27 +504,33 @@ def make_pr(con: GitHubConnection, release: GHRelease, repo_url: str, *, log_dir
490504
con,
491505
forked_repo=forked_repo,
492506
original_repo=original_repo,
493-
template_branch_name=pr.branch,
507+
template_branch_name=pr.template_branch,
508+
versioned_branch_name=pr.pr_branch,
494509
tag_name=release.tag_name,
495-
cruft_log_file=log_dir / f"{pr.branch}.log",
510+
cruft_log_file=log_dir / f"{pr.template_branch}.log",
496511
dry_run=dry_run,
497512
)
498513
if dry_run:
499514
log.info("Skipping PR because in dry-run mode")
500515
return
501516
if updated:
502-
if old_pr := next((p for p in original_repo.get_pulls("open") if pr.matches_exact_branch_name(p)), None):
517+
if old_pr := next((p for p in original_repo.get_pulls("open") if pr.matches_current_version(p)), None):
503518
log.info(f"PR already exists: #{old_pr.number} with branch name `{old_pr.head.ref}`. Skipping PR creation.")
504-
else:
505-
log.info(f"Creating PR of {pr.namespaced_head} against {original_repo.default_branch}")
506-
new_pr = original_repo.create_pull(
507-
title=pr.title,
508-
body=pr.body,
509-
base=original_repo.default_branch,
510-
head=pr.namespaced_head,
511-
maintainer_can_modify=True,
512-
)
513-
log.info(f"Created PR #{new_pr.number} with branch name `{new_pr.head.ref}`.")
519+
return
520+
521+
if old_pr := next((p for p in original_repo.get_pulls("open") if pr.matches_prefix(p)), None):
522+
log.info(f"Closing old PR #{old_pr.number} with branch name `{old_pr.head.ref}`.")
523+
old_pr.edit(state="closed")
524+
525+
log.info(f"Creating PR of {pr.namespaced_head} against {original_repo.default_branch}")
526+
new_pr = original_repo.create_pull(
527+
title=pr.title,
528+
body=pr.body,
529+
base=original_repo.default_branch,
530+
head=pr.namespaced_head,
531+
maintainer_can_modify=True,
532+
)
533+
log.info(f"Created PR #{new_pr.number} with branch name `{new_pr.head.ref}`.")
514534

515535

516536
cli = App()

0 commit comments

Comments
 (0)