88 "fmt"
99 "os"
1010 "slices"
11- "sort"
1211 "strings"
1312
1413 "github.com/containerd/platforms"
@@ -24,9 +23,10 @@ import (
2423)
2524
2625type treeOptions struct {
27- images []imagetypes.Summary
28- all bool
29- filters client.Filters
26+ images []imagetypes.Summary
27+ all bool
28+ filters client.Filters
29+ expanded bool
3030}
3131
3232type treeView struct {
@@ -36,7 +36,7 @@ type treeView struct {
3636 imageSpacing bool
3737}
3838
39- func runTree (ctx context.Context , dockerCLI command.Cli , opts treeOptions ) error {
39+ func runTree (ctx context.Context , dockerCLI command.Cli , opts treeOptions ) ( int , error ) {
4040 images := opts .images
4141
4242 view := treeView {
@@ -46,9 +46,9 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
4646
4747 for _ , img := range images {
4848 if ctx .Err () != nil {
49- return ctx .Err ()
49+ return 0 , ctx .Err ()
5050 }
51- details := imageDetails {
51+ topDetails := imageDetails {
5252 ID : img .ID ,
5353 DiskUsage : units .HumanSizeWithPrecision (float64 (img .Size ), 3 ),
5454 InUse : img .Containers > 0 ,
@@ -67,41 +67,58 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
6767 continue
6868 }
6969
70+ inUse := len (im .ImageData .Containers ) > 0
71+ if inUse {
72+ // Mark top-level parent image as used if any of its subimages are used.
73+ topDetails .InUse = true
74+ }
75+
76+ if ! opts .expanded {
77+ continue
78+ }
79+
7080 sub := subImage {
7181 Platform : platforms .Format (im .ImageData .Platform ),
7282 Available : im .Available ,
7383 Details : imageDetails {
7484 ID : im .ID ,
7585 DiskUsage : units .HumanSizeWithPrecision (float64 (im .Size .Total ), 3 ),
76- InUse : len ( im . ImageData . Containers ) > 0 ,
86+ InUse : inUse ,
7787 ContentSize : units .HumanSizeWithPrecision (float64 (im .Size .Content ), 3 ),
7888 },
7989 }
8090
81- if sub .Details .InUse {
82- // Mark top-level parent image as used if any of its subimages are used.
83- details .InUse = true
84- }
85-
8691 children = append (children , sub )
8792
8893 // Add extra spacing between images if there's at least one entry with children.
8994 view .imageSpacing = true
9095 }
9196
92- details .ContentSize = units .HumanSizeWithPrecision (float64 (totalContent ), 3 )
97+ topDetails .ContentSize = units .HumanSizeWithPrecision (float64 (totalContent ), 3 )
9398
9499 // Sort tags for this image
95100 sortedTags := make ([]string , len (img .RepoTags ))
96101 copy (sortedTags , img .RepoTags )
97102 slices .Sort (sortedTags )
98103
99- view .images = append (view .images , topImage {
100- Names : sortedTags ,
101- Details : details ,
102- Children : children ,
103- created : img .Created ,
104- })
104+ if opts .expanded {
105+ view .images = append (view .images , topImage {
106+ Names : sortedTags ,
107+ Details : topDetails ,
108+ Children : children ,
109+ created : img .Created ,
110+ })
111+ continue
112+ }
113+
114+ for _ , tag := range sortedTags {
115+ view .images = append (view .images , topImage {
116+ Names : []string {tag },
117+ Details : topDetails ,
118+ Children : children ,
119+ created : img .Created ,
120+ })
121+ }
105122 }
106123
107124 slices .SortFunc (view .images , func (a , b topImage ) int {
@@ -123,7 +140,8 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
123140 return strings .Compare (nameA , nameB )
124141 })
125142
126- return printImageTree (dockerCLI , view )
143+ printImageTree (dockerCLI , view )
144+ return len (view .images ), nil
127145}
128146
129147type imageDetails struct {
@@ -206,7 +224,7 @@ func getPossibleChips(view treeView) (chips []imageChip) {
206224 return possible
207225}
208226
209- func printImageTree (dockerCLI command.Cli , view treeView ) error {
227+ func printImageTree (dockerCLI command.Cli , view treeView ) {
210228 if streamRedirected (dockerCLI .Out ()) {
211229 _ , _ = fmt .Fprintln (dockerCLI .Err (), "WARNING: This output is designed for human readability. For machine-readable output, please use --format." )
212230 }
@@ -313,8 +331,6 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
313331 printChildren (out , columns , img , normalColor )
314332 _ , _ = fmt .Fprintln (out )
315333 }
316-
317- return nil
318334}
319335
320336// adjustColumns adjusts the width of the first column to maximize the space
@@ -356,7 +372,6 @@ func generateLegend(out tui.Output, width uint) string {
356372 legend += " |"
357373 }
358374 }
359- legend += " "
360375
361376 r := int (width ) - tui .Width (legend )
362377 if r < 0 {
@@ -406,15 +421,7 @@ func printNames(out tui.Output, headers []imgColumn, img topImage, color, untagg
406421 _ , _ = fmt .Fprint (out , headers [0 ].Print (untaggedColor , "<untagged>" ))
407422 }
408423
409- // TODO: Replace with namesLongestToShortest := slices.SortedFunc(slices.Values(img.Names))
410- // once we move to Go 1.23.
411- namesLongestToShortest := make ([]string , len (img .Names ))
412- copy (namesLongestToShortest , img .Names )
413- sort .Slice (namesLongestToShortest , func (i , j int ) bool {
414- return len (namesLongestToShortest [i ]) > len (namesLongestToShortest [j ])
415- })
416-
417- for nameIdx , name := range namesLongestToShortest {
424+ for nameIdx , name := range img .Names {
418425 // Don't limit first names to the column width because only the last
419426 // name will be printed alongside other columns.
420427 if nameIdx < len (img .Names )- 1 {
0 commit comments