How to inspect a target before applying the template¶
tools/detect.py answers two questions from the filesystem alone, before
copier writes anything:
- Which operation is correct for this directory — a fresh scaffold, an adoption into an existing project, or an update of a project this template already generated?
- What does the project already have, and which of the files the template would write would land on top of one of them?
task detect DIR=/path/to/existing-project
task detect CLI_ARGS="--json"
task detect DIR=/path/to/project CLI_ARGS="--answers answers.yml"
| Mode | Condition | What to run |
|---|---|---|
fresh |
Missing, or empty apart from .git |
copier copy (full scaffold) |
adopt |
Has files, no .copier-answers.yml |
copier copy with existing_project: true |
update |
.copier-answers.yml points back at this template |
copier update |
foreign |
.copier-answers.yml points at another template |
Nothing — another template owns it |
.copier-answers.yml identity is resolved through copier's own _src_path:
a URL, a path, or a clone whose origin is this repository. A clone of this
template under an unrelated directory name is therefore still recognised as
update, not mistaken for a foreign template.
The report¶
mode: adopt
facts
vcs .git (git@github.com:acme/legacy.git)
packaging pyproject.toml (name='legacy')
layout src/, src/legacy
task runner justfile
ci .github/workflows/
quality ruff, renovate.json, .gitleaks.toml
kept (your files; adopt mode does not write them)
.gitignore
CHANGELOG.md
LICENSE
README.md
docs/index.md
justfile
pyproject.toml
COLLISIONS (already exist and adopt mode WOULD write them)
.github/workflows/ci.yml
.gitleaks.toml
renovate.json
COLLISIONS is the part to act on. Adopt mode only protects the files
named by existing_project / adopt_protect; the infrastructure the template
always ships (the CI workflows, .gitleaks.toml, renovate.json,
cliff.toml, ...) is rendered regardless. Copier's own handling of those is
blunt (measured, copier 9.18.1, against a project holding its own
.github/workflows/ci.yml):
- no flag:
conflict→Interactive session required: Consider using --overwrite, exit 1, after writing the files it had already rendered; --overwrite: replaced by the template's version;--skip <path>: left alone, everything else still added — and a run with--skipfor every collision produces exactly the same file set as--overwritewould, with your files intact.
So the report ends with the command that uses --skip, one flag per
collision (the same list appears as skip in --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>
copier stops at the first conflict without --skip (and replaces it with --overwrite);
a --skip path that the template would not render for your answers is harmless.
A --skip path that the selected answers would never render is a no-op, so
an over-long list is safe; a missing one is the failure mode to avoid.
How the report is derived matters for trusting it: which files the template
can render, and whether each one is protected in adopt mode, is read out of
template/'s own file-name conditions (existing_project /
adopt_protect) at run time — not from a list kept in this tool. So the
report follows the template when those conditions change, including the
committed tree and a working tree mid-refactor.
Answers file¶
--answers FILE writes a copier --data-file for the detected mode:
# Generated by tools/detect.py from /path/to/project
# mode: adopt
existing_project: true
adopt_protect:
- readme
- license
- pyproject
- gitignore
- python_version
- scaffold
- docs
package_name: legacy
description: The legacy service
repo_name: legacy
distribution_name: legacy
git_platform: github.com
github_org: acme
author_name: Ada Lovelace
author_email: ada@example.com
Two deliberate limits:
adopt_protectlists only what the target actually has. Protecting a file the project does not have would stop the template from creating it.- Shape questions are not guessed.
project_type,include_*,docs_type,strictnessand the rest are absent on purpose:notes/SPEC-adoption.mdsection 12 keeps code-based questionnaire inference a non-goal, and a wrong guess would be silently baked into the generated project. Add them yourself, or let--defaultstake each question's own default.
--answers is refused (exit 2) for update and foreign: those need
copier update, not an answers file.
Exit codes¶
0 a report was produced. 2 the target cannot be inspected, or
--answers was requested for a mode where it does not apply.
--json prints the same report as JSON on stdout.
Related¶
- Run a Batch of Generation Requests — the batch runner, which is how a set of generation requests gets verified without eyeballing.
- Adopt the template into an existing repo — the user-facing adoption flow.