From 16a771587f6b3904c1e80838d32a3d75f6356f9e Mon Sep 17 00:00:00 2001 From: iTrooz Date: Wed, 12 Nov 2025 14:53:32 +0100 Subject: [PATCH 1/4] feat: allow to list objects sorted by size --- cmd/ls-main.go | 10 ++++++++++ cmd/ls.go | 26 ++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/cmd/ls-main.go b/cmd/ls-main.go index f6e6d46c8a..1f2d55764a 100644 --- a/cmd/ls-main.go +++ b/cmd/ls-main.go @@ -60,6 +60,10 @@ var ( Name: "zip", Usage: "list files inside zip archive (MinIO servers only)", }, + cli.StringFlag{ + Name: "sort", + Usage: "sort by field. Only possible value for now: size", + }, } ) @@ -177,6 +181,11 @@ func checkListSyntax(cliCtx *cli.Context) ([]string, doListOptions) { timeRef := parseRewindFlag(cliCtx.String("rewind")) + sortBy := cliCtx.String("sort") + if sortBy != "" && sortBy != "size" { + fatalIf(errInvalidArgument().Trace(args...), "Unsupported sort option '"+sortBy+"'. Only 'size' is supported.") + } + if listZip && (withVersions || !timeRef.IsZero()) { fatalIf(errInvalidArgument().Trace(args...), "Zip file listing can only be performed on the latest version") } @@ -189,6 +198,7 @@ func checkListSyntax(cliCtx *cli.Context) ([]string, doListOptions) { withVersions: withVersions, listZip: listZip, filter: storageClasss, + sortBy: sortBy, } return args, opts } diff --git a/cmd/ls.go b/cmd/ls.go index fe23bfc6ef..65693879a3 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -21,6 +21,7 @@ import ( "context" "fmt" "path/filepath" + "slices" "sort" "strings" "time" @@ -198,12 +199,10 @@ func (s summaryMessage) JSON() string { } // Pretty print the list of versions belonging to one object -func printObjectVersions(clntURL ClientURL, ctntVersions []*ClientContent, printAllVersions bool) { +func getObjectVersions(clntURL ClientURL, ctntVersions []*ClientContent, printAllVersions bool) []contentMessage { sortObjectVersions(ctntVersions) msgs := generateContentMessages(clntURL, ctntVersions, printAllVersions) - for _, msg := range msgs { - printMsg(msg) - } + return msgs } type doListOptions struct { @@ -214,6 +213,7 @@ type doListOptions struct { withVersions bool listZip bool filter string + sortBy string } // doList - list all entities inside a folder. @@ -226,6 +226,7 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error { totalObjects int64 ) + objects := []contentMessage{} for content := range clnt.List(ctx, ListOptions{ Recursive: o.isRecursive, Incomplete: o.isIncomplete, @@ -245,9 +246,10 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error { continue } + // Check if we have moved to a new object (or if this is another version of the last iteration's object) if lastPath != content.URL.Path { // Print any object in the current list before reinitializing it - printObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions) + objects = append(objects, getObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions)...) lastPath = content.URL.Path perObjectVersions = []*ClientContent{} } @@ -257,7 +259,19 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error { totalObjects++ } - printObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions) + objects = append(objects, getObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions)...) + + // Sort by size if requested + if o.sortBy == "size" { + slices.SortFunc(objects, func(a, b contentMessage) int { + return int(a.Size - b.Size) + }) + } + + // Print all objects + for _, obj := range objects { + printMsg(obj) + } if o.isSummary { printMsg(summaryMessage{ From 12d0636ca6e6c69899e1e9b4e616358e1faa0328 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Wed, 12 Nov 2025 15:58:05 +0100 Subject: [PATCH 2/4] use sortBy --- cmd/ls-main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/ls-main.go b/cmd/ls-main.go index 1f2d55764a..aea1918f1c 100644 --- a/cmd/ls-main.go +++ b/cmd/ls-main.go @@ -182,7 +182,10 @@ func checkListSyntax(cliCtx *cli.Context) ([]string, doListOptions) { timeRef := parseRewindFlag(cliCtx.String("rewind")) sortBy := cliCtx.String("sort") - if sortBy != "" && sortBy != "size" { + switch sortBy { + case "": + case "size": + default: fatalIf(errInvalidArgument().Trace(args...), "Unsupported sort option '"+sortBy+"'. Only 'size' is supported.") } From 5a3706f8307ef48f5c675f880ae49d97519c4a94 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Wed, 12 Nov 2025 15:59:16 +0100 Subject: [PATCH 3/4] use a if when sorting --- cmd/ls.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/ls.go b/cmd/ls.go index 65693879a3..78af73c2c5 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -264,7 +264,12 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error { // Sort by size if requested if o.sortBy == "size" { slices.SortFunc(objects, func(a, b contentMessage) int { - return int(a.Size - b.Size) + if a.Size < b.Size { + return -1 + } else if a.Size > b.Size { + return 1 + } + return 0 }) } From c6c8794c4a9dba0bf51a777787bc96a28d46fd38 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Wed, 12 Nov 2025 17:05:58 +0100 Subject: [PATCH 4/4] do not use an accumulating list when we just want to print results --- cmd/ls.go | 66 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/cmd/ls.go b/cmd/ls.go index 78af73c2c5..f7845eabd7 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -216,8 +216,8 @@ type doListOptions struct { sortBy string } -// doList - list all entities inside a folder. -func doList(ctx context.Context, clnt Client, o doListOptions) error { +// Will loop over entities listed and call fn() for every object with all their versions +func loopOverObjects(ctx context.Context, clnt Client, fn func(content *[]contentMessage) error, o doListOptions) (int64, int64, error) { var ( lastPath string perObjectVersions []*ClientContent @@ -226,7 +226,6 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error { totalObjects int64 ) - objects := []contentMessage{} for content := range clnt.List(ctx, ListOptions{ Recursive: o.isRecursive, Incomplete: o.isIncomplete, @@ -249,7 +248,8 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error { // Check if we have moved to a new object (or if this is another version of the last iteration's object) if lastPath != content.URL.Path { // Print any object in the current list before reinitializing it - objects = append(objects, getObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions)...) + objects := getObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions) + fn(&objects) lastPath = content.URL.Path perObjectVersions = []*ClientContent{} } @@ -259,23 +259,55 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error { totalObjects++ } - objects = append(objects, getObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions)...) + objects := getObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions) + fn(&objects) + return totalObjects, totalSize, cErr +} - // Sort by size if requested - if o.sortBy == "size" { - slices.SortFunc(objects, func(a, b contentMessage) int { - if a.Size < b.Size { - return -1 - } else if a.Size > b.Size { - return 1 +// doList - list all entities inside a folder. +func doList(ctx context.Context, clnt Client, o doListOptions) error { + var cErr error + isAccumulating := o.sortBy == "size" // should objects be accumulated instead of printed directly + var accumulatedObjects []contentMessage + + var processFn func(objects *[]contentMessage) error + if isAccumulating { + processFn = func(newObjects *[]contentMessage) error { + // Accumulate all objects for sorting later + accumulatedObjects = append(accumulatedObjects, *newObjects...) + return nil + } + } else { + processFn = func(objects *[]contentMessage) error { + // Print objects as they come + for _, obj := range *objects { + printMsg(obj) } - return 0 - }) + return nil + } } - // Print all objects - for _, obj := range objects { - printMsg(obj) + totalObjects, totalSize, cErr := loopOverObjects(ctx, clnt, processFn, o) + + // if isAccumulating is true, objects have been accumulated instead of printed + if isAccumulating { + // Sort if requested + switch o.sortBy { + case "size": + slices.SortFunc(accumulatedObjects, func(a, b contentMessage) int { + if a.Size < b.Size { + return -1 + } else if a.Size > b.Size { + return 1 + } + return 0 + }) + } + + // Print all objects + for _, obj := range accumulatedObjects { + printMsg(obj) + } } if o.isSummary {