Skip to content

index: checkout: keep cache objects protected after removing links - #679

Open
MrNK2107 wants to merge 1 commit into
treeverse:mainfrom
MrNK2107:fix/checkout-keep-cache-protected-11119
Open

MrNK2107 wants to merge 1 commit into
treeverse:mainfrom
MrNK2107:fix/checkout-keep-cache-protected-11119

Conversation

@MrNK2107

Copy link
Copy Markdown

Part of treeverse/dvc#11119.

On NTFS a hardlink shares its read-only attribute with the cache object. To delete a protected workspace file, dvc_objects.fs.utils.remove() clears that attribute first, so the cache object becomes writable too. In index-based checkout this happens on dvc checkout --relink and whenever a hardlinked file is replaced by another version. A later edit of the workspace file then rewrites the cache object, and the next dvc status deletes it as corrupted.

The old hashfile/checkout.py handled this in _relink() by calling cache.protect() after re-linking. Index-based checkout lost that step. This PR adds it back in apply(): before deleting workspace files, it collects the cache objects they point to (from the new index's cache storage), then protects them again once the files are gone. The links are then created from protected objects, so both the old and the new version stay read-only. It only runs when the workspace filesystem is local.

Not covered here: dvc unprotect and Stage.remove_outs reach the same remove() call from dvc's side (see the issue and the comment from @kpaxico). They can follow in separate PRs if this approach looks right.

Tests: two new tests in tests/index/test_checkout.py, one for relink and one for switching a hardlinked file to another version. On Windows both fail on main and pass with this change. On Linux and macOS they pass either way, because deleting a file there does not touch its mode.

On NTFS a hardlink shares its read-only attribute with the cache object.
To delete a protected workspace file, dvc_objects' remove() clears that
attribute first, which also leaves the cache object writable. This
happens on `dvc checkout --relink` and whenever a hardlinked file is
replaced by another version. A later edit of the workspace file then
silently corrupts the cache object, and `dvc status` deletes it.

The old hashfile checkout protected the object again in _relink().
Do the same in index-based checkout: after deleting workspace files,
protect the cache objects they pointed to.

Part of treeverse/dvc#11119.
@github-project-automation github-project-automation Bot moved this to Backlog in DVC Oct 28, 2026
@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.92857% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.26%. Comparing base (e38c8c2) to head (30ae2c9).
⚠️ Report is 72 commits behind head on main.

Files with missing lines Patch % Lines
src/dvc_data/index/checkout.py 57.14% 5 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #679      +/-   ##
==========================================
+ Coverage   72.42%   74.26%   +1.83%     
==========================================
  Files          67       65       -2     
  Lines        5132     5409     +277     
  Branches      605      629      +24     
==========================================
+ Hits         3717     4017     +300     
+ Misses       1227     1195      -32     
- Partials      188      197       +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
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.

1 participant