diff --git a/cmd/oras/internal/display/status/text.go b/cmd/oras/internal/display/status/text.go index 403155e9e..def6f9619 100644 --- a/cmd/oras/internal/display/status/text.go +++ b/cmd/oras/internal/display/status/text.go @@ -20,6 +20,7 @@ import ( "sync" "oras.land/oras/cmd/oras/internal/output" + "oras.land/oras/internal/descriptor" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" @@ -65,9 +66,13 @@ 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]) - if err := output.PrintSuccessorStatus(ctx, desc, fetcher, committed, ph.printer.StatusPrinter(PushPromptSkipped)); err != nil { + successors, err := descriptor.GetSuccessors(ctx, desc, fetcher, committed) + if err != nil { return err } + for _, successor := range successors { + _ = ph.printer.PrintStatus(successor, PushPromptSkipped) + } return ph.printer.PrintStatus(desc, PushPromptUploaded) } } diff --git a/cmd/oras/internal/display/status/tty.go b/cmd/oras/internal/display/status/tty.go index fa7e4345c..21c46f823 100644 --- a/cmd/oras/internal/display/status/tty.go +++ b/cmd/oras/internal/display/status/tty.go @@ -20,12 +20,12 @@ import ( "os" "sync" - "oras.land/oras/cmd/oras/internal/output" + "oras.land/oras/cmd/oras/internal/display/status/track" + "oras.land/oras/internal/descriptor" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" - "oras.land/oras/cmd/oras/internal/display/status/track" ) // TTYPushHandler handles TTY status output for push command. @@ -70,9 +70,17 @@ 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]) - return output.PrintSuccessorStatus(ctx, desc, fetcher, committed, func(d ocispec.Descriptor) error { - return ph.tracked.Prompt(d, PushPromptSkipped) - }) + successors, err := descriptor.GetSuccessors(ctx, desc, fetcher, committed) + if err != nil { + return err + } + for _, successor := range successors { + err = ph.tracked.Prompt(successor, PushPromptSkipped) + if err != nil { + return err + } + } + return nil } } diff --git a/cmd/oras/internal/output/print.go b/cmd/oras/internal/output/print.go index f257f3401..0613ca828 100644 --- a/cmd/oras/internal/output/print.go +++ b/cmd/oras/internal/output/print.go @@ -16,7 +16,6 @@ limitations under the License. package output import ( - "context" "fmt" "io" "sync" @@ -24,12 +23,8 @@ import ( "oras.land/oras/internal/descriptor" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "oras.land/oras-go/v2/content" ) -// PrintFunc is the function type returned by StatusPrinter. -type PrintFunc func(ocispec.Descriptor) error - // Printer prints for status handlers. type Printer struct { out io.Writer @@ -92,28 +87,3 @@ func (p *Printer) PrintStatus(desc ocispec.Descriptor, status string) error { } return p.Println(status, descriptor.ShortDigest(desc), name) } - -// StatusPrinter returns a tracking function for transfer status. -func (p *Printer) StatusPrinter(status string) PrintFunc { - return func(desc ocispec.Descriptor) error { - return p.PrintStatus(desc, status) - } -} - -// PrintSuccessorStatus prints transfer status of successors. -func PrintSuccessorStatus(ctx context.Context, desc ocispec.Descriptor, fetcher content.Fetcher, committed *sync.Map, print PrintFunc) error { - successors, err := content.Successors(ctx, fetcher, desc) - if err != nil { - return err - } - for _, s := range successors { - name := s.Annotations[ocispec.AnnotationTitle] - if v, ok := committed.Load(s.Digest.String()); ok && v != name { - // Reprint status for deduplicated content - if err := print(s); err != nil { - return err - } - } - } - return nil -} diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 3a9ab2ef4..c0149c0f2 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -38,6 +38,7 @@ import ( oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/cmd/oras/internal/output" + "oras.land/oras/internal/descriptor" "oras.land/oras/internal/docker" "oras.land/oras/internal/graph" "oras.land/oras/internal/listener" @@ -188,9 +189,13 @@ 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]) - if err := output.PrintSuccessorStatus(ctx, desc, dst, committed, printer.StatusPrinter(promptSkipped)); err != nil { + successors, err := descriptor.GetSuccessors(ctx, desc, dst, committed) + if err != nil { return err } + for _, successor := range successors { + _ = printer.PrintStatus(successor, promptSkipped) + } return printer.PrintStatus(desc, promptCopied) } extendedCopyOptions.OnMounted = func(ctx context.Context, desc ocispec.Descriptor) error { @@ -211,9 +216,17 @@ 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]) - return output.PrintSuccessorStatus(ctx, desc, tracked, committed, func(desc ocispec.Descriptor) error { - return tracked.Prompt(desc, promptSkipped) - }) + successors, err := descriptor.GetSuccessors(ctx, desc, tracked, committed) + if err != nil { + return err + } + for _, successor := range successors { + err = tracked.Prompt(successor, promptSkipped) + if err != nil { + return err + } + } + return nil } extendedCopyOptions.OnMounted = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) diff --git a/internal/descriptor/descriptor.go b/internal/descriptor/descriptor.go index 46ad93249..eec87f5d2 100644 --- a/internal/descriptor/descriptor.go +++ b/internal/descriptor/descriptor.go @@ -16,8 +16,11 @@ limitations under the License. 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" ) @@ -52,3 +55,18 @@ func GetTitleOrMediaType(desc ocispec.Descriptor) (name string, isTitle bool) { 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) (successors []ocispec.Descriptor, err error) { + allSuccessors, err := content.Successors(ctx, fetcher, desc) + if err != nil { + return nil, err + } + for _, s := range allSuccessors { + name := s.Annotations[ocispec.AnnotationTitle] + if v, ok := committed.Load(s.Digest.String()); ok && v != name { + successors = append(successors, s) + } + } + return successors, nil +}