Feat add recursive directory support to push#1951
Feat add recursive directory support to push#1951TerryHowe wants to merge 3 commits intooras-project:mainfrom
Conversation
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (70.50%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #1951 +/- ##
==========================================
- Coverage 87.22% 86.09% -1.13%
==========================================
Files 143 147 +4
Lines 5540 5939 +399
==========================================
+ Hits 4832 5113 +281
- Misses 421 514 +93
- Partials 287 312 +25 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
c470325 to
ed59da2
Compare
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
ed59da2 to
1ade71a
Compare
Add comprehensive unit tests for the recursive push feature to improve test coverage of cmd/oras/root/push.go. Signed-off-by: Terry Howe <terrylhowe@gmail.com>
|
Thanks for implementing this! I'm having success with Generate some file data: mkdir -p sample/dirs-only/{assets,config}
mkdir -p sample/files-only
mkdir -p sample/mixed/{src,tests}
fortune > sample/dirs-only/assets/logo.png
fortune > sample/dirs-only/config/settings.json
fortune > sample/files-only/config.txt
fortune > sample/files-only/data.json
fortune > sample/files-only/readme.md
fortune > sample/mixed/package.json
fortune > sample/mixed/README.md
fortune > sample/mixed/src/main.js
fortune > sample/mixed/tests/test.jsThat should create the following: $ tree sample
sample
├── dirs-only
│ ├── assets
│ │ └── logo.png
│ └── config
│ └── settings.json
├── files-only
│ ├── config.txt
│ ├── data.json
│ └── readme.md
└── mixed
├── package.json
├── README.md
├── src
│ └── main.js
└── tests
└── test.jsThis is successful: $ oras push --recursive localhost:5000/oras/sample:latest ./sampleOn a different directory, this fails: $ oras pull localhost:5000/oras/sample:latest
✓ Pulled files-only/config.txt 61/61 B 100.00% 83µs
└─ sha256:989523c961939b4ab17482eea045896c679dd5695498df91fcb1941c607371df
✓ Pulled mixed/src/main.js 164/164 B 100.00% 93µs
└─ sha256:2f37125c73db3133437db82348cace40c4532a4e24f880237d12fa98d5d2968d
✓ Pulled files-only/data.json 25/25 B 100.00% 195µs
└─ sha256:6e95b87f32d8b6ba1f82e1dd8e25731f986f4ab58b92d05d3e6c9466dbd223b7
✓ Pulled mixed/README.md 228/228 B 100.00% 87µs
└─ sha256:4025c62d58896ee69189054e7527a9521cde1d94f184719a6fd44a77380c73d7
✓ Pulled mixed/package.json 140/140 B 100.00% 10µs
└─ sha256:0fbac35a1bb9a5fe62859a5c4d673b0e9eb7e7a70f3da5aef7a09a5cd38b281a
✓ Pulled mixed/tests/test.js 152/152 B 100.00% 54µs
└─ sha256:6abc94092687a502683788c37db77f6a6471c268c3edef0917c5de64e58a77ba
✓ Pulled files-only/readme.md 596/596 B 100.00% 5µs
└─ sha256:b693e58040e276bc3f4bc3bb1307bacfa1db22c702c42e11a183f92311ca0b98
✓ Pulled dirs-only/assets/logo.png 54/54 B 100.00% 45µs
└─ sha256:c4563c17afb4e4229a57ac01defb3c742c48844347d2eaa2fd9fc188c9b16269
✓ Pulled src 571/571 B 100.00% 122µs
└─ sha256:9832ec6366b5e58602c09745eb3bb5d5b95ab512d3d3bbe19f8a28cfe992b05f
✓ mixed 790/790 B 100.00% 0s
└─ sha256:74e1558a5e2700f61983035b8bfa733185a588c92da1c997ef69deddb113b4bc
✓ files-only 1022/1022 B 100.00% 0s
└─ sha256:2ff8374f79f0e130eeb815c94628e3d3ff09ffa92d3026c66a941bff1c354164
Error: failed to create file /tmp/scratch/crazy-noether/mixed: open /tmp/scratch/crazy-noether/mixed: is a directory
I think the problem is related to the |
| } | ||
|
|
||
| // Create a combined source for copying | ||
| combinedSource := contentutil.MultiReadOnlyTarget(memoryStore, store) |
There was a problem hiding this comment.
Could just use the previously created union?
|
|
||
| // MakeFileAnnotations creates annotations for a file descriptor. | ||
| // The title should be the filename for display purposes. | ||
| func MakeFileAnnotations(path, title string) map[string]string { |
| } | ||
|
|
||
| // ChunkFiles splits a list of nodes into chunks of at most maxSize. | ||
| func ChunkFiles(nodes []*Node, maxSize int) [][]*Node { |
| // IncludeEmpty includes empty directories in the tree. | ||
| IncludeEmpty bool | ||
| // ExcludePatterns are glob patterns for files/directories to exclude. | ||
| ExcludePatterns []string |
There was a problem hiding this comment.
This makes sense, but it seems unreachable from the CLI. Intentional?
|
|
||
| info, err := getFileInfo(entry, childAbsPath, opts.FollowSymlinks) | ||
| if err != nil { | ||
| // Skip files we can't stat (e.g., broken symlinks) |
There was a problem hiding this comment.
Should this return an error instead? If I'm trying to upload a broken symlink, I'd rather know about it.
|
This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 30 days. |
…e push When buildNode stored child Image Index descriptors in the parent index's manifests list, it called maps.Copy to attach directory annotations — but createIndex had already set ocispec.AnnotationTitle=<dirname> on the returned descriptor, so maps.Copy preserved it. oras pull treats any descriptor with AnnotationTitle as a named file and calls metadataHandler.OnFilePulled, which tries to open/create the path. When a directory with that name already exists on disk this fails with: "open <path>/<dirname>: is a directory" Fix: - createIndex no longer adds AnnotationTitle to its annotations or the returned descriptor - buildNode assigns (replaces) directory annotations instead of merging, ensuring no stale AnnotationTitle can survive from the inner call Adds TestBuildFromNode_noAnnotationTitleOnIndexDescriptors as a regression test that reproduces the exact failure mode reported in PR oras-project#1951 review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds three e2e specs under "pushing recursively with --recursive flag": 1. Push a nested directory tree and pull it back, verifying every file's content is preserved — this is the primary regression test for the "open <path>: is a directory" bug from PR oras-project#1951 review. 2. Error: --recursive with a file argument (not a directory). 3. Error: --recursive combined with --config. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e push When buildNode stored child Image Index descriptors in the parent index's manifests list, it called maps.Copy to attach directory annotations — but createIndex had already set ocispec.AnnotationTitle=<dirname> on the returned descriptor, so maps.Copy preserved it. oras pull treats any descriptor with AnnotationTitle as a named file and calls metadataHandler.OnFilePulled, which tries to open/create the path. When a directory with that name already exists on disk this fails with: "open <path>/<dirname>: is a directory" Fix: - createIndex no longer adds AnnotationTitle to its annotations or the returned descriptor - buildNode assigns (replaces) directory annotations instead of merging, ensuring no stale AnnotationTitle can survive from the inner call Adds TestBuildFromNode_noAnnotationTitleOnIndexDescriptors as a regression test that reproduces the exact failure mode reported in PR oras-project#1951 review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Robert Ream <154010+robertream@users.noreply.github.com>
Adds three e2e specs under "pushing recursively with --recursive flag": 1. Push a nested directory tree and pull it back, verifying every file's content is preserved — this is the primary regression test for the "open <path>: is a directory" bug from PR oras-project#1951 review. 2. Error: --recursive with a file argument (not a directory). 3. Error: --recursive combined with --config. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Robert Ream <154010+robertream@users.noreply.github.com>
…e push When buildNode stored child Image Index descriptors in the parent index's manifests list, it called maps.Copy to attach directory annotations — but createIndex had already set ocispec.AnnotationTitle=<dirname> on the returned descriptor, so maps.Copy preserved it. oras pull treats any descriptor with AnnotationTitle as a named file and calls metadataHandler.OnFilePulled, which tries to open/create the path. When a directory with that name already exists on disk this fails with: "open <path>/<dirname>: is a directory" Fix: - createIndex no longer adds AnnotationTitle to its annotations or the returned descriptor - buildNode assigns (replaces) directory annotations instead of merging, ensuring no stale AnnotationTitle can survive from the inner call Adds TestBuildFromNode_noAnnotationTitleOnIndexDescriptors as a regression test that reproduces the exact failure mode reported in PR oras-project#1951 review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Robert Ream <154010+robertream@users.noreply.github.com>
Adds three e2e specs under "pushing recursively with --recursive flag": 1. Push a nested directory tree and pull it back, verifying every file's content is preserved — this is the primary regression test for the "open <path>: is a directory" bug from PR oras-project#1951 review. 2. Error: --recursive with a file argument (not a directory). 3. Error: --recursive combined with --config. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Robert Ream <154010+robertream@users.noreply.github.com>
What this PR does / why we need it:
Recursive directory copy for
oras pushFixes: #1871