From 240c1fcc76939bf1d3b9f45d0414537c6330cb1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roch=C3=A9=20Compaan?= Date: Sun, 1 Mar 2026 18:54:59 +0200 Subject: [PATCH 1/3] fix(template): correct gh repo creation flags and provider handling --- template/tasks.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/template/tasks.py b/template/tasks.py index 9eee064..13c168d 100644 --- a/template/tasks.py +++ b/template/tasks.py @@ -101,7 +101,6 @@ def maybe_create_repo() -> None: repo_url = "{{ copier__repo_url }}".strip() if not repo_url: - print(WARNING + "Repo creation requested but no repo_url was provided." + TERMINATOR) return parsed = parse_repo_url(repo_url) @@ -119,10 +118,17 @@ def maybe_create_repo() -> None: print(WARNING + "gh CLI is not installed. Skipping repo creation." + TERMINATOR) return + gh_env = os.environ.copy() + repo_ref = repo_name + if host != "github.com": + gh_env["GH_HOST"] = host + repo_ref = f"{host}/{repo_name}" + repo_exists = subprocess.run( - ["gh", "repo", "view", repo_name, "--hostname", host], + ["gh", "repo", "view", repo_ref], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + env=gh_env, ) if repo_exists.returncode == 0: print(INFO + f"GitHub repository {repo_name} already exists." + TERMINATOR) @@ -134,9 +140,10 @@ def maybe_create_repo() -> None: + TERMINATOR ) create_repo = subprocess.run( - ["gh", "repo", "create", repo_name, f"--{REPO_VISIBILITY}", "--hostname", host], + ["gh", "repo", "create", repo_name, f"--{REPO_VISIBILITY}"], capture_output=True, text=True, + env=gh_env, ) if create_repo.returncode != 0: error = create_repo.stderr.strip() or create_repo.stdout.strip() or "unknown error" From 3abb77b4dc56612328c7fc3e79e7994d75245b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roch=C3=A9=20Compaan?= Date: Sun, 1 Mar 2026 19:10:17 +0200 Subject: [PATCH 2/3] feat(template): add provider/org/repo questions and derive remote URL --- README.md | 5 ++++- copier.yml | 38 ++++++++++++++++++++++++++------ template/tasks.py | 55 ++++++++++++++++------------------------------- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 968c995..a122af4 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,10 @@ GitHub App), optional gitleaks scanning, and generated `make`/`task`/`just` - `copier__github_semantic_release_auth`: `github_token` or `github_app` (GitHub + semantic-release only) - `copier__enable_secret_scanning`: include gitleaks CI - `copier__task_runner`: `make`, `task`, or `just` -- `copier__repo_url`: optional remote URL to configure as `origin` during generation +- `copier__configure_repo`: enable repository remote setup +- `copier__repo_provider`: `github` or `gitlab` +- `copier__repo_org`: organization/group name +- `copier__repo_name`: repository name - `copier__create_repo`: create provider repo automatically when missing (`gh` for GitHub, `glab` for GitLab) - `copier__repo_visibility`: `private` or `public` for automatic repo creation diff --git a/copier.yml b/copier.yml index 1b5ca27..5d0e17e 100644 --- a/copier.yml +++ b/copier.yml @@ -51,25 +51,51 @@ copier__email: default: "{{ copier__author_name.lower().replace(' ', '.') }}@example.com" help: "Author email." +copier__configure_repo: + type: bool + default: true + help: "Configure a git remote for the generated project." + +copier__repo_provider: + type: str + default: github + choices: + - github + - gitlab + help: "Repository provider." + when: "{{ copier__configure_repo }}" + +copier__repo_org: + type: str + default: "getscaf" + help: "Repository organization/group." + when: "{{ copier__configure_repo }}" + +copier__repo_name: + type: str + default: "{{ copier__project_dash }}" + help: "Repository name." + when: "{{ copier__configure_repo }}" + copier__repo_url: type: str - default: "" - help: "Optional git remote URL for the generated project (e.g. git@github.com:org/repo.git)." + default: "{{ ('git@github.com:' + copier__repo_org + '/' + copier__repo_name + '.git') if (copier__configure_repo and copier__repo_provider == 'github') else (('git@gitlab.com:' + copier__repo_org + '/' + copier__repo_name + '.git') if copier__configure_repo else '') }}" + when: false copier__create_repo: type: bool default: true - help: "Create the selected provider repository automatically if it does not exist (gh for GitHub, glab for GitLab)." - when: "{{ copier__repo_url | trim != '' }}" + help: "Create the repository automatically if it does not exist (gh for GitHub, glab for GitLab)." + when: "{{ copier__configure_repo }}" copier__repo_visibility: type: str - default: private + default: public choices: - private - public help: "Visibility to use when creating the repository." - when: "{{ copier__create_repo }}" + when: "{{ copier__configure_repo and copier__create_repo }}" copier__version: type: str diff --git a/template/tasks.py b/template/tasks.py index 13c168d..137926d 100644 --- a/template/tasks.py +++ b/template/tasks.py @@ -1,6 +1,5 @@ import os import pathlib -import re import shlex import shutil import subprocess @@ -9,6 +8,11 @@ SEMANTIC_RELEASE = {{ "True" if copier__enable_semantic_release else "False" }} SECRET_SCANNING = {{ "True" if copier__enable_secret_scanning else "False" }} TASK_RUNNER = "{{ copier__task_runner }}" +CONFIGURE_REPO = {{ "True" if copier__configure_repo else "False" }} +REPO_PROVIDER = "{{ copier__repo_provider }}" +REPO_ORG = "{{ copier__repo_org }}" +REPO_NAME = "{{ copier__repo_name }}" +REPO_URL = "{{ copier__repo_url }}" CREATE_REPO = {{ "True" if copier__create_repo else "False" }} REPO_VISIBILITY = "{{ copier__repo_visibility }}" @@ -53,7 +57,9 @@ def init_git_repo() -> None: def configure_git_remote() -> None: - repo_url = "{{ copier__repo_url }}" + if not CONFIGURE_REPO: + return + repo_url = REPO_URL.strip() if repo_url: print(INFO + f"repo_url: {repo_url}" + TERMINATOR) existing_origin = subprocess.run( @@ -80,52 +86,27 @@ def configure_git_remote() -> None: ) -def parse_repo_url(repo_url: str) -> tuple[str, str] | None: - patterns = [ - r"^git@(?P[^:]+):(?P[^ ]+?)(?:\.git)?$", - r"^https?://(?P[^/]+)/(?P[^ ]+?)(?:\.git)?/?$", - ] - for pattern in patterns: - match = re.match(pattern, repo_url) - if match: - host = match.group("host") - path = match.group("path").strip("/") - if path.count("/") >= 1: - return (host, path) - return None - - def maybe_create_repo() -> None: if not CREATE_REPO: return - repo_url = "{{ copier__repo_url }}".strip() - if not repo_url: + if not CONFIGURE_REPO: return - - parsed = parse_repo_url(repo_url) - if not parsed: - print( - WARNING - + f"Repo URL is not in a supported SSH/HTTPS format ({repo_url}). Skipping repo creation." - + TERMINATOR - ) + if not REPO_ORG.strip() or not REPO_NAME.strip(): + print(WARNING + "Repo org/name not set. Skipping repo creation." + TERMINATOR) return - host, repo_name = parsed + repo_name = f"{REPO_ORG.strip()}/{REPO_NAME.strip()}" - if CI_PROVIDER == "github": + if REPO_PROVIDER == "github": if not shutil.which("gh"): print(WARNING + "gh CLI is not installed. Skipping repo creation." + TERMINATOR) return gh_env = os.environ.copy() - repo_ref = repo_name - if host != "github.com": - gh_env["GH_HOST"] = host - repo_ref = f"{host}/{repo_name}" + gh_env["GH_HOST"] = "github.com" repo_exists = subprocess.run( - ["gh", "repo", "view", repo_ref], + ["gh", "repo", "view", repo_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=gh_env, @@ -156,13 +137,13 @@ def maybe_create_repo() -> None: print(SUCCESS + f"GitHub repository {repo_name} created." + TERMINATOR) return - if CI_PROVIDER == "gitlab": + if REPO_PROVIDER == "gitlab": if not shutil.which("glab"): print(WARNING + "glab CLI is not installed. Skipping repo creation." + TERMINATOR) return glab_env = os.environ.copy() - glab_env["GITLAB_HOST"] = host + glab_env["GITLAB_HOST"] = "gitlab.com" repo_exists = subprocess.run( ["glab", "repo", "view", repo_name], stdout=subprocess.DEVNULL, @@ -197,7 +178,7 @@ def maybe_create_repo() -> None: print( WARNING - + f"Repo creation not implemented for provider '{CI_PROVIDER}'. Skipping." + + f"Repo creation not implemented for provider '{REPO_PROVIDER}'. Skipping." + TERMINATOR ) From c8350a331781d68fbfad3d5b52bd8f44cd84e0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roch=C3=A9=20Compaan?= Date: Sun, 1 Mar 2026 19:15:57 +0200 Subject: [PATCH 3/3] feat(copier): infer ci provider from repo provider --- README.md | 2 +- copier.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a122af4..44c2101 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ GitHub App), optional gitleaks scanning, and generated `make`/`task`/`just` ## Copier Options -- `copier__ci_provider`: `github` or `gitlab` +- `copier__ci_provider`: `github` or `gitlab` (only asked when repo setup is disabled; otherwise inferred from `copier__repo_provider`) - `copier__enable_semantic_release`: include release automation - `copier__github_semantic_release_auth`: `github_token` or `github_app` (GitHub + semantic-release only) - `copier__enable_secret_scanning`: include gitleaks CI diff --git a/copier.yml b/copier.yml index 5d0e17e..a19a821 100644 --- a/copier.yml +++ b/copier.yml @@ -104,11 +104,12 @@ copier__version: copier__ci_provider: type: str - default: github + default: "{{ copier__repo_provider if copier__configure_repo else 'github' }}" help: "CI provider to scaffold." choices: - github - gitlab + when: "{{ not copier__configure_repo }}" copier__enable_semantic_release: type: bool