diff --git a/cmd/oras/internal/display/status/text.go b/cmd/oras/internal/display/status/text.go index 341d7c8a0..d0c9a8eec 100644 --- a/cmd/oras/internal/display/status/text.go +++ b/cmd/oras/internal/display/status/text.go @@ -66,7 +66,7 @@ func (ph *TextPushHandler) UpdateCopyOptions(opts *oras.CopyGraphOptions, fetche } opts.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - err, successors := descriptor.GetSuccessors(ctx, desc, fetcher, committed) + successors, err := descriptor.GetSuccessors(ctx, desc, fetcher, DeduplicatedFilter(committed)) if err != nil { return err } diff --git a/cmd/oras/internal/display/status/tty.go b/cmd/oras/internal/display/status/tty.go index 4cbc6cbf0..7aa803ec3 100644 --- a/cmd/oras/internal/display/status/tty.go +++ b/cmd/oras/internal/display/status/tty.go @@ -70,7 +70,7 @@ func (ph *TTYPushHandler) UpdateCopyOptions(opts *oras.CopyGraphOptions, fetcher } opts.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - err, successors := descriptor.GetSuccessors(ctx, desc, fetcher, committed) + successors, err := descriptor.GetSuccessors(ctx, desc, fetcher, DeduplicatedFilter(committed)) if err != nil { return err } diff --git a/cmd/oras/internal/display/status/utils.go b/cmd/oras/internal/display/status/utils.go index 40e857c42..6b5838ce6 100644 --- a/cmd/oras/internal/display/status/utils.go +++ b/cmd/oras/internal/display/status/utils.go @@ -15,6 +15,12 @@ limitations under the License. package status +import ( + "sync" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + // Prompts for pull events. const ( PullPromptDownloading = "Downloading" @@ -32,3 +38,14 @@ const ( PushPromptSkipped = "Skipped " PushPromptExists = "Exists " ) + +// DeduplicatedFilter filters out deduplicated descriptors. +func DeduplicatedFilter(committed *sync.Map) func(desc ocispec.Descriptor) bool { + return func(desc ocispec.Descriptor) bool { + + name := desc.Annotations[ocispec.AnnotationTitle] + v, ok := committed.Load(desc.Digest.String()) + // committed but not printed == deduplicated + return ok && v != name + } +} diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 88771c921..7d5e84eb1 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -34,6 +34,7 @@ import ( "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/command" "oras.land/oras/cmd/oras/internal/display" + "oras.land/oras/cmd/oras/internal/display/status" "oras.land/oras/cmd/oras/internal/display/status/track" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" @@ -189,7 +190,7 @@ func doCopy(ctx context.Context, printer *output.Printer, src oras.ReadOnlyGraph } extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - err, successors := descriptor.GetSuccessors(ctx, desc, dst, committed) + successors, err := descriptor.GetSuccessors(ctx, desc, dst, status.DeduplicatedFilter(committed)) if err != nil { return err } @@ -216,13 +217,12 @@ func doCopy(ctx context.Context, printer *output.Printer, src oras.ReadOnlyGraph } extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - err, successors := descriptor.GetSuccessors(ctx, desc, tracked, committed) + successors, err := descriptor.GetSuccessors(ctx, desc, tracked, status.DeduplicatedFilter(committed)) if err != nil { return err } for _, successor := range successors { - err = tracked.Prompt(successor, promptSkipped) - if err != nil { + if err = tracked.Prompt(successor, promptSkipped); err != nil { return err } } diff --git a/internal/descriptor/descriptor.go b/internal/descriptor/descriptor.go index e449392b6..62dbb18ea 100644 --- a/internal/descriptor/descriptor.go +++ b/internal/descriptor/descriptor.go @@ -17,10 +17,10 @@ package descriptor import ( "context" + "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2/content" - "sync" "oras.land/oras/internal/docker" ) @@ -56,17 +56,18 @@ func GenerateContentKey(desc ocispec.Descriptor) string { return desc.Digest.String() + desc.Annotations[ocispec.AnnotationTitle] } -// GetSuccessors prints transfer status of successors. -func GetSuccessors(ctx context.Context, desc ocispec.Descriptor, fetcher content.Fetcher, committed *sync.Map) (err error, successors []ocispec.Descriptor) { +// GetSuccessors fetches successors and returns filterred ones. +func GetSuccessors(ctx context.Context, desc ocispec.Descriptor, fetcher content.Fetcher, filter func(ocispec.Descriptor) bool) ([]ocispec.Descriptor, error) { allSuccessors, err := content.Successors(ctx, fetcher, desc) if err != nil { - return err, nil + return nil, err } + + var successors []ocispec.Descriptor for _, s := range allSuccessors { - name := s.Annotations[ocispec.AnnotationTitle] - if v, ok := committed.Load(s.Digest.String()); ok && v != name { + if filter(s) { successors = append(successors, s) } } - return nil, successors + return successors, nil }