Skip to content

Feature: copier adopt — first-class workflow for adopting a template in an existing project #2486

@ichoosetoaccept

Description

@ichoosetoaccept

Actual Situation

When adopting a copier template in an existing project that wasn't originally created with that template, users are forced into a binary choice with no good middle ground:

copier copy --overwrite destroys project-specific content:

  • pyproject.toml dependencies get wiped
    • src/pkg/__init__.py exports get replaced
    • Custom CLI code, test fixtures, etc. are overwritten
      copier copy --skip / _skip_if_exists preserves project files but skips all template improvements:
  • Project doesn't get updated ruff rules, poe tasks, coverage config
    • Project doesn't get updated pre-commit hooks
    • No way to "merge" template config sections with existing config
      copier update is designed for projects already using the template. It uses 3-way merge, but requires a valid _commit pointing to a template version the project was created from. For adoption, there's no "old version" to diff against.

The current documented workaround (from #955 / PR #981) is to copier copy --overwrite and then use git difftool to manually cherry-pick changes back. This is tedious, error-prone, and doesn't scale when adopting a template across many existing repositories.

Root cause

Copier's merge logic only works in update mode with a known baseline. For first-time adoption, there's no baseline to diff against, so it's binary: overwrite or skip. This gap means there is no practical path from "existing project" to "template-managed project" without significant manual effort.

Related issues

Desired Situation

A first-class copier adopt command (or equivalent workflow) that lets an existing project adopt a template with intelligent handling of conflicts:

  1. Add new files from the template that don't exist in the project (docs, CI configs, etc.) — no conflict
  2. Merge overlapping config files (pyproject.toml tool sections, .pre-commit-config.yaml, .gitignore, etc.) — using conflict markers or a 3-way merge where the "base" is an empty file or a synthetic baseline
  3. Preserve project-specific content (dependencies, custom code, package exports) — never silently overwrite
  4. Establish a baseline for future copier update — after adoption, the project should be in the same state as if it had been created from the template, enabling the normal update workflow going forward

The result: a single command that takes a project from "not template-managed" to "template-managed and updatable" with explicit, reviewable conflict resolution rather than silent data loss.

Proposed solution

A new copier adopt subcommand that works roughly like this:

copier adopt https://github.com/org/template.git ./my-existing-project

MVP behavior:

  1. Render the template into a temporary directory (answering questions as normal)
  2. For each rendered file:
    • If the file doesn't exist in the project → copy it in
    • If the file exists and is identical → skip (no-op)
    • If the file exists and differs → write the file with git-style conflict markers (treating the existing file as "ours" and the template output as "theirs"), or use a 3-way merge with an empty base
  3. Write .copier-answers.yml with the current template version and answers
  4. Commit (or leave uncommitted for user review)

After this, the project has a valid .copier-answers.yml and _commit, so future copier update calls work normally with proper 3-way merge.

Key design consideration: The synthetic baseline. Since there's no "old template version" for a project that was never created from the template, the adopt command could use an empty tree or the template's initial version as the merge base. This is similar to how git merge --allow-unrelated-histories handles merging two trees with no common ancestor.

Alternative (lighter-weight): Rather than a new subcommand, a --adopt flag on copier copy that triggers the conflict-marker behavior instead of the overwrite-or-skip binary. This would also write valid .copier-answers.yml for future updates.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions