Repository: restic/restic
Pull request #5184 (2024-12-16 15:20:17)

Issue 4433: Ability to define sort order for output of find command
cmd_find: ability to define sort order for output of find command: Add option --reverse

<!--
Thank you very much for contributing code or documentation to restic! Please
fill out the following questions to make it easier for us to review your
changes.
-->

What does this PR change? What problem does it solve?
-----------------------------------------------------
Added ability to sort output of find command.

<!--
Describe the changes and their purpose here, as detailed as needed.
-->

Was the change previously discussed in an issue or on the forum?
----------------------------------------------------------------

<!--
Link issues and relevant forum posts here.

If this PR resolves an issue on GitHub, use "Closes #1234" so that the issue
is closed automatically when this PR is merged.
-->
https://github.com/restic/restic/issues/4433
Checklist
---------

<!--
You do not need to check all the boxes below all at once. Feel free to take
your time and add more commits. If you're done and ready for review, please
check the last box. Enable a checkbox by replacing [ ] with [x].

Please always follow these steps:
- Read the [contribution guidelines](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#providing-patches).
- Enable [maintainer edits](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
- Run `gofmt` on the code in all commits.
- Format all commit messages in the same style as [the other commits in the repository](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#git-commits).
-->

- [x] I have added tests for all code changes.
- [ ] I have added documentation for relevant changes (in the manual).
- [x] There's a new file in `changelog/unreleased/` that describes the changes for our users (see [template](https://github.com/restic/restic/blob/master/changelog/TEMPLATE)).
- [x] I'm done! This pull request is ready for review.


wplapper: > As in #5183 please rebase the PR instead of adding merge commits.

@MichaelEischer: as I pointed out before, I don't know how to rebase. I am a really beginner when it comes to git. @rawtaz promised me a chat, but I have no idea how I can contact him. 
wplapper: Integration test added.

Patch:
diff --git a/changelog/unreleased/issue-4433 b/changelog/unreleased/issue-4433
new file mode 100644
index 000000000..b6aefbe8e
--- /dev/null
+++ b/changelog/unreleased/issue-4433
@@ -0,0 +1,9 @@
+Enhancement: Sort `find` output from newest to oldest and add `--reverse` option
+
+The old output behaviour was to sort snapshots from oldest to newest.
+The new sorting order is from newest to oldest. If one wants to revert to the
+old behaviour, use the option --reverse.
+
+https://github.com/restic/restic/issues/4433
+https://github.com/restic/restic/pull/5184
+
diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go
index 2e06fa00c..2fcef5741 100644
--- a/cmd/restic/cmd_find.go
+++ b/cmd/restic/cmd_find.go
@@ -22,7 +22,9 @@ var cmdFind = &cobra.Command{
 	Long: `
 The "find" command searches for files or directories in snapshots stored in the
 repo.
-It can also be used to search for restic blobs or trees for troubleshooting.`,
+It can also be used to search for restic blobs or trees for troubleshooting.
+The default sort option for the snapshots is youngest to oldest. To sort the
+output from oldest to youngest specify --reverse.`,
 	Example: `restic find config.json
 restic find --json "*.yml" "*.json"
 restic find --json --blob 420f620f b46ebe8a ddd38656
@@ -56,6 +58,7 @@ type FindOptions struct {
 	CaseInsensitive    bool
 	ListLong           bool
 	HumanReadable      bool
+	Reverse            bool
 	restic.SnapshotFilter
 }
 
@@ -73,6 +76,7 @@ func init() {
 	f.BoolVar(&findOptions.PackID, "pack", false, "pattern is a pack-ID")
 	f.BoolVar(&findOptions.ShowPackID, "show-pack-id", false, "display the pack-ID the blobs belong to (with --blob or --tree)")
 	f.BoolVarP(&findOptions.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern")
+	f.BoolVarP(&findOptions.Reverse, "reverse", "R", false, "reverse sort order oldest to newest")
 	f.BoolVarP(&findOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
 	f.BoolVar(&findOptions.HumanReadable, "human-readable", false, "print sizes in human readable format")
 
@@ -637,7 +641,10 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
 	}
 
 	sort.Slice(filteredSnapshots, func(i, j int) bool {
-		return filteredSnapshots[i].Time.Before(filteredSnapshots[j].Time)
+		if opts.Reverse {
+			return filteredSnapshots[i].Time.Before(filteredSnapshots[j].Time)
+		}
+		return filteredSnapshots[i].Time.After(filteredSnapshots[j].Time)
 	})
 
 	for _, sn := range filteredSnapshots {


Test patch:
diff --git a/cmd/restic/cmd_find_integration_test.go b/cmd/restic/cmd_find_integration_test.go
index 7e35cb141..95799749a 100644
--- a/cmd/restic/cmd_find_integration_test.go
+++ b/cmd/restic/cmd_find_integration_test.go
@@ -28,7 +28,6 @@ func TestFind(t *testing.T) {
 	opts := BackupOptions{}
 
 	testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
-	testRunCheck(t, env.gopts)
 
 	results := testRunFind(t, false, FindOptions{}, env.gopts, "unexistingfile")
 	rtest.Assert(t, len(results) == 0, "unexisting file found in repo (%v)", datafile)
@@ -91,3 +90,44 @@ func TestFindJSON(t *testing.T) {
 	rtest.Assert(t, len(matches[0].Matches) == 3, "expected 3 files to match (%v)", matches[0].Matches)
 	rtest.Assert(t, matches[0].Hits == 3, "expected hits to show 3 matches (%v)", datafile)
 }
+
+func TestFindSorting(t *testing.T) {
+	env, cleanup := withTestEnvironment(t)
+	defer cleanup()
+
+	datafile := testSetupBackupData(t, env)
+	opts := BackupOptions{}
+
+	// first backup
+	testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
+	sn1 := testListSnapshots(t, env.gopts, 1)[0]
+
+	// second backup
+	testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
+	snapshots := testListSnapshots(t, env.gopts, 2)
+	// get id of new snapshot without depending on file order returned by filesystem
+	sn2 := snapshots[0]
+	if sn1.Equal(sn2) {
+		sn2 = snapshots[1]
+	}
+
+	// first restic find - with default FindOptions{}
+	results := testRunFind(t, true, FindOptions{}, env.gopts, "testfile")
+	lines := strings.Split(string(results), "\n")
+	rtest.Assert(t, len(lines) == 2, "expected two files found in repo (%v), found %d", datafile, len(lines))
+	matches := []testMatches{}
+	rtest.OK(t, json.Unmarshal(results, &matches))
+
+	// run second restic find with --reverse, sort oldest to newest
+	resultsReverse := testRunFind(t, true, FindOptions{Reverse: true}, env.gopts, "testfile")
+	lines = strings.Split(string(resultsReverse), "\n")
+	rtest.Assert(t, len(lines) == 2, "expected two files found in repo (%v), found %d", datafile, len(lines))
+	matchesReverse := []testMatches{}
+	rtest.OK(t, json.Unmarshal(resultsReverse, &matchesReverse))
+
+	// compare result sets
+	rtest.Assert(t, sn1.String() == matchesReverse[0].SnapshotID, "snapshot[0] must match old snapshot")
+	rtest.Assert(t, sn2.String() == matchesReverse[1].SnapshotID, "snapshot[1] must match new snapshot")
+	rtest.Assert(t, matches[0].SnapshotID == matchesReverse[1].SnapshotID, "matches should be sorted 1")
+	rtest.Assert(t, matches[1].SnapshotID == matchesReverse[0].SnapshotID, "matches should be sorted 2")
+}