Adopt the template into an existing repo¶
You can adopt this template into an existing repo by running copier copy in much the same way as in a new project.
Note
The fork's own release tags start at 6.0.0, the fork detach; the
inherited upstream tags are gone. A URL-based copier copy with no
--vcs-ref therefore expands this fork's newest release, which is what
the commands below rely on. Pass --vcs-ref=6.0.0 (or any other release
tag) only to pin an exact release.
This will:
- Ask some questions about the existing project
- Expand the template with the answers given, protecting your own files
- Add the infrastructure you do not have yet (CI, hygiene, quality tooling)
- Record the answers in the project so they can be used in later updates
Note
Adoption protects your files, not the template's canonical ones. Read the next two sections before running anything: the conflict row in the table is the difference between "added" and "replaced".
What copier does with the files you already have¶
This is the part that decides whether an adoption is safe, and it is worth knowing exactly (all measured against copier 9.18.1):
| Your file | What happens |
|---|---|
Named by existing_project / adopt_protect (README, LICENSE, pyproject.toml, .gitignore, .python-version, source scaffolding, docs/ if you protect it) |
Not rendered at all — yours is untouched |
CHANGELOG.md |
Never overwritten (_skip_if_exists), whatever the mode |
Task-runner files in adopt mode (justfile, Taskfile.yml, Makefile, tasks.py, duties.py) |
Not rendered at all |
Anything else that already exists (.github/workflows/ci.yml, .gitleaks.toml, renovate.json, cliff.toml, ...) |
Conflict: copier stops with Interactive session required: Consider using --overwrite and exits 1 — after writing every file it had already rendered |
The same, with --overwrite |
Replaced by the template's version |
The same, with --skip <path> |
Left alone, and everything else is still added |
The last row is the one to use. --skip needs no --overwrite, and a run
with --skip for every collision produces exactly the same file set as
--overwrite would — with your files intact. Adding --overwrite on top
only widens the blast radius to collisions nobody listed.
The short version¶
If you would rather not assemble the flags yourself, one command does the plan, the apply and the rollback:
uv run --locked python tools/adopt.py /path/to/existing-project --dry-run
uv run --locked python tools/adopt.py /path/to/existing-project
It skips the collisions for you, picks the revision (the latest tag only when that tag carries this questionnaire), shows what it would add to the files you already have and asks before writing, and undoes everything if any existing file changed anyway — see Adopt Into an Existing Project. The rest of this page explains what those commands are doing and why.
Before you start: inspect the target¶
Run the detection tool first. It reports which operation fits (fresh copy / adoption / update of a project this template already generated), what your project already has, and the collisions above — as a ready-to-run command:
uv run --locked python tools/detect.py /path/to/existing-project
mode: adopt
kept (your files; adopt mode does not write them)
.gitignore CHANGELOG.md LICENSE README.md pyproject.toml
COLLISIONS (exist and adopt mode would replace them -- keep them with --skip)
.github/workflows/ci.yml
renovate.json
next (keep your files, add the rest):
python tools/detect.py <path> --answers answers.yml
copier copy --trust --defaults --vcs-ref=<ref> --data-file answers.yml \
--skip .github/workflows/ci.yml --skip renovate.json <TEMPLATE_URL> <path>
Copy those two lines. --answers writes a --data-file holding
existing_project: true, the protection list for the files you actually
have, and the Project Details that have exactly one answer on disk — it does
not guess the questionnaire's shape questions
(details). Pass one --skip per collision: a path the
template would not render for your answers is harmless, so an over-long
--skip list is safe.
Then review the result the way you would any merge:
git diff # delete the files you do not want, keep yours
git status # untracked additions: CI, hygiene, AGENTS.md, ...
git add -A && git commit -m "chore: adopt python-copier-template"
Later updates: copier update --trust and review the diff (see
Update Template).
If you have a skeleton-based project¶
If you have a python3-pip-skeleton based project then it is best to adopt the first release of this template (6.0.0, the tag the fork detach creates), then copier update to get to the latest. This is because copier update will try and merge file changes across renames done between releases, while copier copy cannot. This looks like:
uvx copier copy https://github.com/kasi-x/python-copier-template.git --trust --vcs-ref=6.0.0 /path/to/existing-project
git diff
# Examine the changes, put back anything you want to keep
git commit -m "Adopt python-copier-template 6.0.0"
uvx copier update /path/to/existing-project --trust
git diff
# Examine the changes, resolve any merge conflicts
git commit -m "Update to python-copier-template x.x.x"
Without the detection tool¶
The recipe detect prints, by hand: answer existing_project=true so your
own files are protected, and --skip every file you want to keep that the
template also ships (check the template's tree, or run detect — it lists
them):
uvx copier copy --trust --data existing_project=true \
--skip .github/workflows/ci.yml --skip renovate.json \
https://github.com/kasi-x/python-copier-template.git /path/to/existing-project
git diff
# Examine the changes, put back anything you want to keep
git commit -m "Adopt python-copier-template x.x.x"
Without those --skip flags copier stops at the first one of those files
(Interactive session required), so a half-written destination is the
failure mode to expect — not a silent replacement.
Keep your own files (infra-only adoption)¶
To add only the infrastructure (CI workflows, hygiene, quality tooling
recipes) and keep your existing README.md, LICENSE, pyproject.toml
and .gitignore, protect them with --skip:
uvx copier copy --trust \
--skip README.md --skip LICENSE --skip pyproject.toml --skip .gitignore \
https://github.com/kasi-x/python-copier-template.git /path/to/existing-project
git diff
git commit -m "chore: adopt python-copier-template (infra only)"
Your own files are left untouched; everything you do not have yet (CI
workflows, .gitleaks.toml, AGENTS.md, ...) is added. The task entries
from justfile / Taskfile.yml are yours to copy into your own setup.
The first-class alternative is the existing_project answer
(--data existing_project=true), which protects those files by default
instead of listing each --skip, and merges what the template generated into
the files you kept — dependencies and [tool.*] lint settings in
pyproject.toml, missing .gitignore patterns, missing Makefile/justfile
recipes, and a copier-ci.yml beside your own workflow. The
tools/adopt.py driver above wraps exactly that, plus the rollback; see
Adopt Into an Existing Project for what is merged and
what is deliberately left to you.
Note
Adopting adds files; it does not remove or merge your files. (The one
exception is pyproject.toml, where the template's dependencies are
added — never a requirement of yours.) Files that the template does not
ship — an old workflow with no counterpart, a differently named CI job —
stay exactly as they were, so delete or merge them yourself after
reviewing git status.
Note
Updating a project that was generated from an older template version:
if your recorded template version still declared Jinja extensions
(versions before the extensions removal), run your first
copier update with
uvx --with copier-template-extensions copier update --trust ...
once — later updates need nothing extra.
Getting started with your new structure¶
You can now read Setup Repository, Developer Installation, and then follow some of the other How-to Guides.