repo-scaffold gh-init¶
Bootstrap a GitHub repository for a project that you've already generated with repo-scaffold create. The command:
- Creates the repository on GitHub (or finds it, with
--allow-existing). - Sets the Actions secrets and variables that the generated workflows expect.
- Initializes git locally (if needed), commits, and pushes the initial revision.
- Creates the
gh-pagesbranch and sets it as the GitHub Pages source (unless--no-pages).
Pages is configured for you, so there's no manual Settings → Pages step left — push your first version tag and the docs-deploy workflow publishes to gh-pages.
Prerequisites¶
- A GitHub personal access token in the
GITHUB_TOKENenvironment variable. - Public repo:
public_reposcope is enough. - Private repo:
reposcope. - Generate one at https://github.com/settings/tokens.
gitonPATH(only needed when pushing).
gh-init does not read gh auth token. Set GITHUB_TOKEN explicitly so behavior is the same in CI and locally.
Usage¶
# Generate a project, then push it to your account.
repo-scaffold create python --no-input -o ./projects
export GITHUB_TOKEN=ghp_...
repo-scaffold gh-init ./projects/my-python-project
The command will print a summary, prompt you to confirm, then create the repo and push.
Common flags¶
| Flag | Behavior |
|---|---|
--owner <user-or-org> |
Push to an organization instead of your own account. |
--name <repo> |
Override the repository name (default: pyproject.toml project.name). |
--description <text> |
Override the repository description. |
--private / --public |
Repository visibility (default: public). |
--default-branch <name> |
Override the default branch (default: current local git branch or master). |
--allow-existing |
Don't fail if the repo already exists; refresh secrets/variables. |
--no-push |
Skip git init and push — only configure GitHub. Pages setup is skipped too. |
--no-pages |
Push, but don't create gh-pages or configure GitHub Pages. |
--protect-branch |
Protect the default branch after pushing (require PR review; admins can still push). |
--force-push |
git push --force the initial commit (if the remote already has commits). |
--no-input |
Don't prompt; missing optional secrets are skipped. |
Examples¶
Create a private org repo non-interactively:
GITHUB_TOKEN=... \
PYPI_SERVER_USERNAME=... \
PYPI_SERVER_PASSWORD=... \
repo-scaffold gh-init ./projects/my-private-tool \
--owner my-org --private --no-input
Re-run after rotating a secret (the repo already exists, you just want to refresh values):
Where the secret values come from¶
gh-init looks for each value in this order. The first non-empty source wins; an empty result is dropped, never written as an empty secret.
- The current process environment (
GITHUB_TOKEN,PYPI_TOKEN, etc.). - A
.envfile in the project directory (simpleKEY=VALUElines, comments with#). - Interactive prompts (skipped under
--no-input). - Defaults from the project's
pyproject.tomlfor the repo name and description.
The default keys configured in the generated workflows are:
| Kind | Name | Used by |
|---|---|---|
| Secret | PERSONAL_ACCESS_TOKEN |
version-bump (push tags), package-release (GitHub Release), container-release (GHCR) |
| Secret | PYPI_TOKEN |
package-release → public PyPI |
| Secret | PYPI_SERVER_USERNAME |
package-release → private PyPI server |
| Secret | PYPI_SERVER_PASSWORD |
package-release → private PyPI server |
| Variable | PUBLISH_TO_PUBLIC_PYPI |
gates the public PyPI publish job ('true' to enable) |
Anything you skip can be added later under Settings → Secrets and variables → Actions.
What the command actually does¶
- Reads
GITHUB_TOKEN, callsGET /userto verify the token early. - Calls
POST /user/repos(orPOST /orgs/{owner}/repos) withauto_init=false. If the repo exists and--allow-existingis set, it falls back toGET /repos/{owner}/{name}. - For each non-empty secret, calls the Actions secrets API (PUT, so existing secrets are overwritten).
- For each non-empty variable, calls the Actions variables API. If the variable already exists, the call falls back to an
edit. - Unless
--no-pushis set: runsgit initif needed, stages every tracked and untracked file, creates achore: initial commit from repo-scaffold [skip ci]commit (only if HEAD doesn't yet exist), renames the current branch, setsorigin, and pushes. GitHub Actions treats[skip ci]in the commit message as a built-in signal to skip workflows for this bootstrap push;gh-initonly adds it to the generated initial commit, not to later user commits. - Unless
--no-pages(or--no-push) is set: createsrefs/heads/gh-pagesfrom the pushed default branch's head commit (skipped if it already exists), then calls the Pages API (POST .../pages, falling back toPUTif a site already exists) to set the source togh-pages// (root). PyGithub 2.x has no Pages helper, so this goes through the raw REST requester. A failure here is reported but does not abort the bootstrap. - If
--protect-branchis set (and a push happened): enables branch protection on the default branch (PUT .../branches/{branch}/protectionviaBranch.edit_protection). A failure is reported but does not abort the bootstrap.
Protecting the default branch (--protect-branch)¶
Opt in with --protect-branch to protect the default branch right after the initial push. The applied rules are deliberately minimal and compatible with the generated version-bump workflow:
| Rule | Value |
|---|---|
| Require a pull request before merging | yes (1 approving review) |
Include administrators (enforce_admins) |
no |
| Allow force pushes | no |
| Allow deletions | no |
enforce_admins is left off on purpose: the version-bump workflow pushes chore(version): commits and tags directly to the default branch using PERSONAL_ACCESS_TOKEN. As long as that token belongs to a repo admin, those automated pushes keep working while human contributors still go through pull requests.
Caveats:
- Requires admin on the repository (classic PAT
reposcope on your own repo; fine-grained token needsAdministration: write). - Private repos need a paid plan. Branch protection on private repositories requires GitHub Pro/Team/Enterprise; on a free private repo the API returns 403 and
gh-initreports it without failing the run. - Needs
--no-pushto be absent — the branch must exist on the remote first.
Enabling Pages manually (--no-pages)¶
By default gh-init creates the gh-pages branch and points Pages at it, so the site goes live after the first docs-deploy run with no extra clicks. The docs workflow uses mkdocs gh-deploy --force to overwrite gh-pages with the built HTML on each version tag.
If you ran with --no-pages (or --no-push), enable it yourself after the first docs-deploy run (triggered by your first version tag, e.g. 0.1.0):
- Open the new repo on GitHub.
- Go to Settings → Pages.
- Under Build and deployment, set Source: Deploy from a branch.
- Pick the
gh-pagesbranch and/ (root)as the path. - Save. The site appears at
https://<owner>.github.io/<repo>within a minute.