Skip to content

Copier deletes previously copied files after adding them to _exclude #2135

Open
@philippkarg

Description

@philippkarg

Describe the problem

Hello!
I love copier and I'm incrementally implementing a template for Python projects.
Previously, I added src/* and tests/* to _skip_if_exsists, to no re-copy those files when updating.
However, when a file was deleted in project, it was re-copied (as is intended with _skip_if_exists).
So after having a look at the docs again, I found that I could exclude files only for updating, which I tried.

I don't know if I'm doing something wrong, but when I try to update my project with the new template version, the template files in the excluded directories are removed.

To be more specific, in my template my template has the following structure:

|
| - src
    | - {{ project_slug }}
        | - {{ project_slug }}.py.jinja
        | - __main__.py.jinja
        | - __init__.py.jinja
| - tests
    | - test_main.py.jinja

In my project, where I have initialized these files using copier, removed the {{ project_slug }}.py file & then added code to the others, __init__.py, __main__.py & test_main.py are deleted after updating.

Template

Configuration before

_exclude:
  - "base-template"
  - "copier.yaml"
  - ".git"
  - ".gitmodules"

_skip_if_exists:
  - "src/*"
  - "tests/*"

_jinja_extensions:
  - jinja2_time.TimeExtension
  - cookiecutter.extensions.SlugifyExtension

_tasks:
  - command: [uv, sync, --all-extras]
    when: "{{ _copier_operation == 'copy' }}"

_migrations:
  - command: [uv, lock]
    when: "{{ _stage == 'after' }}"

project_name:
  type: str
  help: Select your package name
  placeholder: example-project

project_slug:
  type: str
  when: false
  default: "{{ project_name | slugify(separator='_') }}"

# etc

Configuration After

_exclude:
  - "base-template"
  - "copier.yaml"
  - ".git"
  - ".gitmodules"
  - "{% if _copier_operation == 'update' %}src/{{ project_slug }}/__init__.py{% endif %}"
  - "{% if _copier_operation == 'update' %}src/{{ project_slug }}/__main__.py{% endif %}"
  - "{% if _copier_operation == 'update' %}src/{{ project_slug }}/{{ project_slug }}.py{% endif %}"
  - "{% if _copier_operation == 'update' %}tests/test_main.py{% endif %}"

_jinja_extensions:
  - jinja2_time.TimeExtension
  - cookiecutter.extensions.SlugifyExtension

_tasks:
  - command: [uv, sync, --all-extras]
    when: "{{ _copier_operation == 'copy' }}"

_migrations:
  - command: [uv, lock]
    when: "{{ _stage == 'after' }}"

project_name:
  type: str
  help: Select your package name
  placeholder: example-project

project_slug:
  type: str
  when: false
  default: "{{ project_name | slugify(separator='_') }}"

# etc

To Reproduce

  • Setup the initial project with the _skip_if_exists config
  • Change some of the files, remove `{{ project_slug }}.py (optional)
  • Change the configuration to _exclude the files
  • Update the project

Logs

-

Expected behavior

Files are not overwritten, but no removed either

Screenshots/screencasts/logs

No response

Operating system

Windows

Operating system distribution and version

WSL Ubuntu 24.04

Copier version

9.7.1

Python version

CPython 3.12

Installation method

uvx+pypi

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugtriageTrying to make sure if this is valid or not

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions