Skip to content

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.