Skip to content

Copier provenance#171

Merged
zendern merged 33 commits into
mainfrom
copier-markers
Jun 29, 2026
Merged

Copier provenance#171
zendern merged 33 commits into
mainfrom
copier-markers

Conversation

@zendern

@zendern zendern commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Link to Issue or Message thread

Downstream repo PR conversation

Why is this change necessary?

Knowing where files come from as far as copier templates go is a pain unless you are Eli

How does this change address the issue?

Adds header/footer to files that support it, creates a json file that contains which template owns which file and AI guidance to add a comment/summary about which files touched copier related files.

What side effects does this change have?

Tons of files touched

How is this change tested?

Unit tests and downstream

Summary by CodeRabbit

  • New Features
    • Template-instantiated files now receive language-appropriate “managed by template” provenance headers.
    • A provenance manifest (.copier-managed-files.json) is generated/updated to track which template source produced each managed file, including attribution across template chains.
  • Documentation
    • Added clearer guidance on how to review and summarize changes for copier-managed files, including rules for template-chain attribution.
  • Tests
    • Added unit tests covering header insertion/placement, filename matching (including Jinja patterns), manifest generation, idempotency, and template-chain attribution.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Copier provenance task that stamps managed files, records ownership chains in .copier-managed-files.json, wires the task into template instantiation, documents review guidance, and adds tests for template matching, header placement, manifest updates, and ancestor attribution.

Changes

Copier provenance tracking and file management

Layer / File(s) Summary
Documentation and reviewer guidance
AGENTS.md, .coderabbit.yaml
Documents .copier-managed-files.json format, template-chain ownership rules, and review guidance for manifest-listed files.
Copier task wiring and symlinks
copier.yaml, template/copier.yml.jinja-base, template/src/copier_tasks/copier_provenance.py, template/tests/unit/copier_tasks/test_copier_provenance.py
Adds Copier _tasks steps that invoke src/copier_tasks/copier_provenance.py during template instantiation and replaces template-local copies with symlinks to the shared implementation and tests.
Comment formats and header generation
src/copier_tasks/copier_provenance.py
Defines provenance comment types and placement rules, builds the warning header text, resolves template filenames to destination names, renders comment-specific header blocks, and strips existing managed headers.
Template path matching and marker application
src/copier_tasks/copier_provenance.py
Collects template base paths, matches destination files to those bases, attributes files to template sources or ancestor entries, handles shebang placement, and rewrites managed file contents.
Manifest I/O and CLI orchestration
src/copier_tasks/copier_provenance.py
Reads _src_path from .copier-answers.yml, loads and updates .copier-managed-files.json, builds ancestor attribution from parent manifests, and orchestrates the full CLI flow.
Unit tests for provenance behavior
tests/unit/copier_tasks/test_copier_provenance.py
Tests comment formats, Jinja template matching, file-type placement, shebang handling, existing comment preservation, manifest creation and chaining, and ancestor attribution across multi-template runs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • idonaldson
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly reflects the main change: adding copier provenance tracking.
Description check ✅ Passed The description covers the required sections and gives the motivation, approach, side effects, and testing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/copier_tasks/copier_provenance.py`:
- Around line 117-172: Change the function signatures of apply_file_markers and
update_manifest to enforce keyword-only arguments (insert a lone * in each
signature before parameters) and then update every call site (notably in main())
to pass arguments by keyword (e.g., src_template_directory=...,
dst_directory=... and dst_directory=..., template_src=..., managed_files=...,
parent_src=...) so the calls match the new signatures; also apply the same
keyword-only refactor to the other multi-argument function referenced around the
201-203 scope (add * and update its call sites to use keyword arguments).
- Line 203: The call to update_manifest uses a boolean-shortcut fallback
(args.template_src or str(args.src_template_dir)); replace this with an explicit
if/else assignment: compute a local variable (e.g., template_path) by checking
if args.template_src is truthy then set template_path = args.template_src else
set template_path = str(args.src_template_dir), and pass template_path into
update_manifest(result.managed_files, parent_src) call site so the branch is
explicit and clearer; update the invocation at the update_manifest(...) call to
use that variable.
- Around line 131-152: The loop in copier_provenance.py assumes all files are
UTF-8 text when calling file.read_text() to check for a shebang, causing
UnicodeDecodeError on binary templates; wrap the shebang-read in a try/except
that catches UnicodeDecodeError (and other decode problems) and, on failure,
skip the shebang check (i.e., do not flip comment_formatting to "bottom") and
continue processing normally so binary files are still handled by
_build_specific_header/_write_file_marker and appended to managed; update the
block around file.read_text(...).split("\n", 1)[0] (related symbols:
dst_directory glob loop, template_base_paths, custom_filename_handling,
custom_file_handling, default_comment_format, CommentFormat,
_build_specific_header, _write_file_marker, managed) to safely handle non-text
files without raising.

In `@tests/unit/copier_tasks/test_copier_provenance.py`:
- Around line 507-510: Add a positive presence/identity assertion before the
final absence check: after loading manifest and setting entry =
manifest["templates"][0], assert a concrete expected property of that entry (for
example that it has the expected "src" or "path" value or that entry["name"]
equals the known template name) to ensure the test is not vacuously passing,
then keep the existing assert "parent_src" not in entry; reference manifest,
entry and manifest["templates"] when adding the presence check.
- Around line 467-470: Replace the loop+all checks with a violations collection:
iterate over manifest["templates"] and for each entry check types for
entry["src"] and each item in entry["managed_files"], append descriptive
messages to a local violations list for any failures (e.g., "entry X: src not
str" or "entry X: managed_files item Y not str"), then assert that violations is
empty (or assert not violations) so failures report the collected violations
rather than a generic loop failure; reference the manifest variable and the
"templates" entries as the location to change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 03f1dd82-e983-42ca-8636-4fb19568939c

📥 Commits

Reviewing files that changed from the base of the PR and between 76a7882 and e9ad30d.

📒 Files selected for processing (8)
  • .coderabbit.yaml
  • AGENTS.md
  • copier.yaml
  • src/copier_tasks/copier_provenance.py
  • template/copier.yml.jinja-base
  • template/src/copier_tasks/copier_provenance.py
  • template/tests/unit/copier_tasks/test_copier_provenance.py
  • tests/unit/copier_tasks/test_copier_provenance.py

Comment thread src/copier_tasks/copier_provenance.py
Comment thread src/copier_tasks/copier_provenance.py Outdated
Comment thread src/copier_tasks/copier_provenance.py Outdated
Comment thread tests/unit/copier_tasks/test_copier_provenance.py
Comment thread tests/unit/copier_tasks/test_copier_provenance.py Outdated
zendern added 3 commits June 11, 2026 18:47
Add `*` to `apply_file_markers` and `update_manifest` signatures.
Update `main()` call sites to use keyword args and replace
`or` shortcut with ternary conditional.
Catch UnicodeDecodeError when checking for shebang line so binary
template files are tracked in the manifest without crashing.
@zendern zendern marked this pull request as ready for review June 11, 2026 19:10
Comment thread src/copier_tasks/copier_provenance.py
zendern added 4 commits June 11, 2026 19:43
Previously only the filename part was normalized; directory parts like
{% if has_backend %}backend{% endif %} were left as-is and never matched
resolved paths in the destination, so backend files silently skipped provenance headers.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/copier_tasks/copier_provenance.py`:
- Around line 279-297: main() currently builds ancestor_managed_by_src but does
not preserve ancestor parent_src values, then calls update_manifest with
parent_src=None for non-current sources which causes update_manifest (keyed by
"src") to overwrite and drop existing parent_src ancestry; modify main() to also
read and store ancestor parent_src (e.g., ancestor_parent_by_src) when parsing
the ancestor manifest and, when calling update_manifest for non-current
templates, pass the preserved parent_src value from ancestor_parent_by_src
instead of None so update_manifest does not erase existing ancestry metadata for
keys in ancestor_managed_by_src.
- Around line 115-121: The _strip_existing_header function is using overly broad
regexes for the "block", "jinja", and "markdown" branches that can remove any
comment block; change those patterns to match only the copier-generated marker
signature (e.g., include the exact marker text like "copier", "Generated by
Copier", or the project's specific marker token) so only the existing copier
header is stripped. Concretely, update the regex assigned in the branches for t
== "block", t == "jinja", and t == "markdown" (inside _strip_existing_header) to
include the copier marker phrase/unique token and keep any surrounding comment
delimiters, and add/adjust tests to ensure normal user comments/licenses are
preserved.
- Around line 157-167: The function _get_comment_format_for_file currently
returns early when default_format.location != "top" (or comment_type == "none"),
which skips binary detection and allows binary files to reach _write_file_marker
and fail; change the flow to always perform a binary/text check first (e.g.,
attempt to read the file as UTF-8 and catch UnicodeDecodeError) and return None
for binary files before returning any CommentFormat, and apply the same fix to
the similar logic found around the other block (the one referenced at lines
218-220) so both places avoid performing text I/O on binary files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 92e45c44-813e-455f-8230-2b80c1e3c339

📥 Commits

Reviewing files that changed from the base of the PR and between e9ad30d and 44df37c.

📒 Files selected for processing (2)
  • src/copier_tasks/copier_provenance.py
  • tests/unit/copier_tasks/test_copier_provenance.py

Comment thread src/copier_tasks/copier_provenance.py
Comment thread src/copier_tasks/copier_provenance.py
Comment thread src/copier_tasks/copier_provenance.py
@zendern zendern marked this pull request as draft June 16, 2026 11:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/copier_tasks/test_copier_provenance.py`:
- Around line 371-378: Add the `*` separator after `self` in the method
signatures of both `test_non_marker_leading_comment_is_not_stripped` (starting
at line 371) and the other test method referenced at lines 404-410 to enforce
keyword-only parameters for all arguments following `self`. This aligns with the
repository's coding guideline preference for keyword-only parameters in
multi-argument function signatures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 81173dff-fe3c-4c07-84a5-5a0159515fe0

📥 Commits

Reviewing files that changed from the base of the PR and between d6e3fde and 3b3c86f.

📒 Files selected for processing (2)
  • src/copier_tasks/copier_provenance.py
  • tests/unit/copier_tasks/test_copier_provenance.py

Comment thread tests/unit/copier_tasks/test_copier_provenance.py
@zendern zendern marked this pull request as ready for review June 18, 2026 19:43
@zendern zendern requested a review from ejfine June 18, 2026 19:50
Comment thread tests/unit/copier_tasks/test_copier_provenance.py Outdated
Comment thread tests/unit/copier_tasks/test_copier_provenance.py
Comment thread tests/unit/copier_tasks/test_copier_provenance.py Outdated
assert "README.md" in srcs["https://github.com/org/base-template"]["managed_files"]
assert "README.md" not in srcs.get("https://github.com/org/nuxt-template", {}).get("managed_files", [])

def test_full_chain_base_nuxt_final_attribution(self, tmp_path: Path) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: do we want "nuxt" to be in the test name? or is this supposed to be more "child template"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went further — removed the inline comment and also replaced all remaining nuxt/bag-driver references across the test file with generic parent/child/grandchild terminology. URL strings like https://github.com/org/nuxt-template became https://github.com/org/child-template, variable names like nuxt_repo became child_repo, etc.

d4b147f

Reply drafted by AI (Claude), reviewed and approved by the author before posting.

zendern added 3 commits June 28, 2026 09:57
Assert result.returncode == 0 in _run_script so call sites don't need
to repeat it. All test methods now call _run_script(...) directly without
a separate returncode check.

Claude-Session: https://claude.ai/code/session_01SygrfHbfqWGwF3urCTnomP
JSONC supports /* */ block comments, so treat .jsonc the same as
JS/TS files (block comment at top) rather than skipping the marker.

Claude-Session: https://claude.ai/code/session_01SygrfHbfqWGwF3urCTnomP
…nt/child/grandchild

Remove domain-specific names (nuxt, bag-driver) from test file. Use
parent/child/grandchild terminology for the 3-level chain tests to
keep the tests implementation-agnostic.

Claude-Session: https://claude.ai/code/session_01SygrfHbfqWGwF3urCTnomP

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
tests/unit/copier_tasks/test_copier_provenance.py (1)

219-225: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add * to the remaining multi-argument test signatures.

These three methods still take multiple post-self arguments positionally. The repo guideline prefers keyword-only parameters for this shape.

Suggested change
     def test_comment_format_by_file_type(
         self,
+        *,
         filename: str,
         expected_location: str,
         expected_comment: str,
         tmp_path: Path,
@@
     def test_comment_not_duplicated_when_already_present(
         self,
+        *,
         template_filename: str,
         expected_location: str,
         expected_comment: str,
         tmp_path: Path,
@@
     def test_shebang_forces_comment_to_bottom(
         self,
+        *,
         shebang_line: str,
         expected_location: str,
         tmp_path: Path,
     ) -> None:

As per coding guidelines, “Prefer keyword-only parameters unless there is a very clear single-argument function: use * in Python signatures`.”

#!/bin/bash
ast-grep run --lang python --pattern $'def $NAME(self, $A, $B, $$$REST) -> None:\n    $$$BODY' tests/unit/copier_tasks/test_copier_provenance.py

Also applies to: 254-260, 311-316

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/copier_tasks/test_copier_provenance.py` around lines 219 - 225,
The remaining test methods in test_copier_provenance still accept multiple
post-self parameters positionally, so update their signatures to make those
arguments keyword-only by adding a * after self. Apply this to
test_comment_format_by_file_type and the other matching test methods in the same
class, keeping the existing parameter names (filename, expected_location,
expected_comment, tmp_path, etc.) but forcing callers to pass them by keyword.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/unit/copier_tasks/test_copier_provenance.py`:
- Around line 219-225: The remaining test methods in test_copier_provenance
still accept multiple post-self parameters positionally, so update their
signatures to make those arguments keyword-only by adding a * after self. Apply
this to test_comment_format_by_file_type and the other matching test methods in
the same class, keeping the existing parameter names (filename,
expected_location, expected_comment, tmp_path, etc.) but forcing callers to pass
them by keyword.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4d2ac588-1b4c-4c37-b29f-d2459e540ef0

📥 Commits

Reviewing files that changed from the base of the PR and between 3b3c86f and d4b147f.

📒 Files selected for processing (2)
  • src/copier_tasks/copier_provenance.py
  • tests/unit/copier_tasks/test_copier_provenance.py

@zendern zendern merged commit 3be2550 into main Jun 29, 2026
7 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants