Skip to content

How to inspect a target before applying the template

tools/detect.py answers two questions from the filesystem alone, before copier writes anything:

  1. 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?
  2. 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 --skip for every collision produces exactly the same file set as --overwrite would, 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_protect lists 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, strictness and the rest are absent on purpose: notes/SPEC-adoption.md section 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 --defaults take 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.