diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index c824d41bd0..60c412a93a 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -1,10 +1,11 @@ package images import ( + "cmp" "errors" "fmt" "os" - "sort" + "slices" "strings" "time" "unicode" @@ -249,7 +250,35 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { listFlag.readOnly = e.IsReadOnly() } - sort.Slice(imgs, sortFunc(listFlag.sort, imgs)) + slices.SortFunc(imgs, func(a, b imageReporter) int { + switch listFlag.sort { + case "id": + return cmp.Compare(a.ID(), b.ID()) + + case "repository": + r := cmp.Compare(a.Repository, b.Repository) + if r == 0 { + // if repository is the same imply tag sorting + return cmp.Compare(a.Tag, b.Tag) + } + return r + case "size": + return cmp.Compare(a.size(), b.size()) + case "tag": + return cmp.Compare(a.Tag, b.Tag) + case "created": + if a.created().After(b.created()) { + return -1 + } + if a.created().Equal(b.created()) { + return 0 + } + return 1 + default: + return 0 + } + }) + return imgs, err } @@ -284,32 +313,6 @@ func tokenRepoTag(ref string) (string, string, error) { return name, tag, nil } -func sortFunc(key string, data []imageReporter) func(i, j int) bool { - switch key { - case "id": - return func(i, j int) bool { - return data[i].ID() < data[j].ID() - } - case "repository": - return func(i, j int) bool { - return data[i].Repository < data[j].Repository - } - case "size": - return func(i, j int) bool { - return data[i].size() < data[j].size() - } - case "tag": - return func(i, j int) bool { - return data[i].Tag < data[j].Tag - } - default: - // case "created": - return func(i, j int) bool { - return data[i].created().After(data[j].created()) - } - } -} - func lsFormatFromFlags(flags listFlagType) string { row := []string{ "{{if .Repository}}{{.Repository}}{{else}}{{end}}", diff --git a/docs/source/markdown/podman-images.1.md.in b/docs/source/markdown/podman-images.1.md.in index 9be1766911..fda6af0479 100644 --- a/docs/source/markdown/podman-images.1.md.in +++ b/docs/source/markdown/podman-images.1.md.in @@ -119,6 +119,7 @@ Lists only the image IDs. #### **--sort**=*sort* Sort by *created*, *id*, *repository*, *size* or *tag* (default: **created**) +When sorting by *repository* it also sorts by the *tag* as second criteria to provide a stable output. ## EXAMPLE diff --git a/test/system/020-tag.bats b/test/system/020-tag.bats index 5fbbf6bdd9..226523aa31 100644 --- a/test/system/020-tag.bats +++ b/test/system/020-tag.bats @@ -27,6 +27,25 @@ function _tag_and_check() { # prepended and ":latest" is appended. _tag_and_check image localhost/image:latest + # The order is intentionally wrong here to check the sorting + # https://github.com/containers/podman/issues/23803 + local image1="registry.com/image:1" + run_podman tag $IMAGE $image1 + local image3="registry.com/image:3" + run_podman tag $IMAGE $image3 + local image2="registry.com/image:2" + run_podman tag $IMAGE $image2 + + local imageA="registry.com/aaa:a" + run_podman tag $IMAGE $imageA + + local nl=" +" + run_podman images --format '{{.Repository}}:{{.Tag}}' --sort repository + assert "$output" =~ "$imageA${nl}$image1${nl}$image2${nl}$image3" "images are sorted by repository and tag" + + run_podman untag $IMAGE $imageA $image1 $image2 $image3 + # Test error case. run_podman 125 untag $IMAGE registry.com/foo:bar is "$output" "Error: registry.com/foo:bar: tag not known"