From f3fdc66b32aa6e00108f5fffb0e884f69fa3f784 Mon Sep 17 00:00:00 2001
From: Michael Eischer <michael.eischer@fau.de>
Date: Sun, 7 Aug 2022 12:48:06 +0200
Subject: [PATCH] restic: Use stable sorting in snapshot policy

sort.Sort is not guaranteed to be stable. Go 1.19 has changed the
sorting algorithm which resulted in changes of the sort order. When
comparing snapshots with identical timestamp but different paths and
tags lists, there is not meaningful order among them. So just keep their
order stable.
---
 internal/restic/snapshot_policy.go            |   2 +-
 .../restic/testdata/policy_keep_snapshots_0   | 108 +++++++++---------
 .../restic/testdata/policy_keep_snapshots_18  |  96 ++++++++--------
 .../restic/testdata/policy_keep_snapshots_19  |  96 ++++++++--------
 .../restic/testdata/policy_keep_snapshots_20  | 100 ++++++++--------
 .../restic/testdata/policy_keep_snapshots_26  | 108 +++++++++---------
 .../restic/testdata/policy_keep_snapshots_29  | 108 +++++++++---------
 .../restic/testdata/policy_keep_snapshots_3   |  62 +++++-----
 .../restic/testdata/policy_keep_snapshots_4   |  62 +++++-----
 9 files changed, 371 insertions(+), 371 deletions(-)

diff --git a/internal/restic/snapshot_policy.go b/internal/restic/snapshot_policy.go
index 8ce6cb2e3..3271140aa 100644
--- a/internal/restic/snapshot_policy.go
+++ b/internal/restic/snapshot_policy.go
@@ -190,7 +190,7 @@ type KeepReason struct {
 // according to the policy p. list is sorted in the process. reasons contains
 // the reasons to keep each snapshot, it is in the same order as keep.
 func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reasons []KeepReason) {
-	sort.Sort(list)
+	sort.Stable(list)
 
 	if p.Empty() {
 		for _, sn := range list {
diff --git a/internal/restic/testdata/policy_keep_snapshots_0 b/internal/restic/testdata/policy_keep_snapshots_0
index 1290b88cf..11ca587c8 100644
--- a/internal/restic/testdata/policy_keep_snapshots_0
+++ b/internal/restic/testdata/policy_keep_snapshots_0
@@ -150,27 +150,6 @@
       "tree": null,
       "paths": null
     },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": [
-        "path1",
-        "path2"
-      ],
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
     {
       "time": "2015-10-22T10:20:30Z",
       "tree": null,
@@ -185,6 +164,27 @@
         "bar"
       ]
     },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": [
+        "path1",
+        "path2"
+      ],
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
     {
       "time": "2015-10-20T10:20:30Z",
       "tree": null,
@@ -911,39 +911,6 @@
       ],
       "counters": {}
     },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": [
-          "path1",
-          "path2"
-        ],
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "policy is empty"
-      ],
-      "counters": {}
-    },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "policy is empty"
-      ],
-      "counters": {}
-    },
     {
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
@@ -970,6 +937,39 @@
       ],
       "counters": {}
     },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "policy is empty"
+      ],
+      "counters": {}
+    },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": [
+          "path1",
+          "path2"
+        ],
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "policy is empty"
+      ],
+      "counters": {}
+    },
     {
       "snapshot": {
         "time": "2015-10-20T10:20:30Z",
diff --git a/internal/restic/testdata/policy_keep_snapshots_18 b/internal/restic/testdata/policy_keep_snapshots_18
index cf63c45b8..feb9ac20b 100644
--- a/internal/restic/testdata/policy_keep_snapshots_18
+++ b/internal/restic/testdata/policy_keep_snapshots_18
@@ -1,5 +1,23 @@
 {
   "keep": [
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
     {
       "time": "2015-10-22T10:20:30Z",
       "tree": null,
@@ -12,24 +30,6 @@
         "bar"
       ]
     },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
     {
       "time": "2014-11-15T10:20:30Z",
       "tree": null,
@@ -153,6 +153,36 @@
     }
   ],
   "reasons": [
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "has tags [foo]"
+      ],
+      "counters": {}
+    },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "has tags [foo]"
+      ],
+      "counters": {}
+    },
     {
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
@@ -171,36 +201,6 @@
       ],
       "counters": {}
     },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "has tags [foo]"
-      ],
-      "counters": {}
-    },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "has tags [foo]"
-      ],
-      "counters": {}
-    },
     {
       "snapshot": {
         "time": "2014-11-15T10:20:30Z",
diff --git a/internal/restic/testdata/policy_keep_snapshots_19 b/internal/restic/testdata/policy_keep_snapshots_19
index 81a438313..440315b10 100644
--- a/internal/restic/testdata/policy_keep_snapshots_19
+++ b/internal/restic/testdata/policy_keep_snapshots_19
@@ -1,5 +1,23 @@
 {
   "keep": [
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
     {
       "time": "2015-10-22T10:20:30Z",
       "tree": null,
@@ -12,24 +30,6 @@
         "bar"
       ]
     },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
     {
       "time": "2014-11-15T10:20:30Z",
       "tree": null,
@@ -41,6 +41,36 @@
     }
   ],
   "reasons": [
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "has tags [foo, bar]"
+      ],
+      "counters": {}
+    },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "has tags [foo, bar]"
+      ],
+      "counters": {}
+    },
     {
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
@@ -59,36 +89,6 @@
       ],
       "counters": {}
     },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "has tags [foo, bar]"
-      ],
-      "counters": {}
-    },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "has tags [foo, bar]"
-      ],
-      "counters": {}
-    },
     {
       "snapshot": {
         "time": "2014-11-15T10:20:30Z",
diff --git a/internal/restic/testdata/policy_keep_snapshots_20 b/internal/restic/testdata/policy_keep_snapshots_20
index a57fcf024..f4febe82c 100644
--- a/internal/restic/testdata/policy_keep_snapshots_20
+++ b/internal/restic/testdata/policy_keep_snapshots_20
@@ -1,5 +1,23 @@
 {
   "keep": [
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
     {
       "time": "2015-10-22T10:20:30Z",
       "tree": null,
@@ -12,24 +30,6 @@
         "bar"
       ]
     },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
     {
       "time": "2014-11-15T10:20:30Z",
       "tree": null,
@@ -161,6 +161,38 @@
     }
   ],
   "reasons": [
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "has tags [foo]",
+        "has tags [bar]"
+      ],
+      "counters": {}
+    },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "has tags [foo]",
+        "has tags [bar]"
+      ],
+      "counters": {}
+    },
     {
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
@@ -180,38 +212,6 @@
       ],
       "counters": {}
     },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "has tags [foo]",
-        "has tags [bar]"
-      ],
-      "counters": {}
-    },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "has tags [foo]",
-        "has tags [bar]"
-      ],
-      "counters": {}
-    },
     {
       "snapshot": {
         "time": "2014-11-15T10:20:30Z",
diff --git a/internal/restic/testdata/policy_keep_snapshots_26 b/internal/restic/testdata/policy_keep_snapshots_26
index 61703f8fe..af3bc0ced 100644
--- a/internal/restic/testdata/policy_keep_snapshots_26
+++ b/internal/restic/testdata/policy_keep_snapshots_26
@@ -150,27 +150,6 @@
       "tree": null,
       "paths": null
     },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": [
-        "path1",
-        "path2"
-      ],
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
     {
       "time": "2015-10-22T10:20:30Z",
       "tree": null,
@@ -185,6 +164,27 @@
         "bar"
       ]
     },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": [
+        "path1",
+        "path2"
+      ],
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
     {
       "time": "2015-10-20T10:20:30Z",
       "tree": null,
@@ -662,39 +662,6 @@
       ],
       "counters": {}
     },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": [
-          "path1",
-          "path2"
-        ],
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "within 1y1m1d"
-      ],
-      "counters": {}
-    },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "within 1y1m1d"
-      ],
-      "counters": {}
-    },
     {
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
@@ -721,6 +688,39 @@
       ],
       "counters": {}
     },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "within 1y1m1d"
+      ],
+      "counters": {}
+    },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": [
+          "path1",
+          "path2"
+        ],
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "within 1y1m1d"
+      ],
+      "counters": {}
+    },
     {
       "snapshot": {
         "time": "2015-10-20T10:20:30Z",
diff --git a/internal/restic/testdata/policy_keep_snapshots_29 b/internal/restic/testdata/policy_keep_snapshots_29
index 172d3000f..2a82ecabd 100644
--- a/internal/restic/testdata/policy_keep_snapshots_29
+++ b/internal/restic/testdata/policy_keep_snapshots_29
@@ -150,27 +150,6 @@
       "tree": null,
       "paths": null
     },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": [
-        "path1",
-        "path2"
-      ],
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
     {
       "time": "2015-10-22T10:20:30Z",
       "tree": null,
@@ -185,6 +164,27 @@
         "bar"
       ]
     },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": [
+        "path1",
+        "path2"
+      ],
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
     {
       "time": "2015-10-20T10:20:30Z",
       "tree": null,
@@ -691,39 +691,6 @@
       ],
       "counters": {}
     },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": [
-          "path1",
-          "path2"
-        ],
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "within 1y2m3d3h"
-      ],
-      "counters": {}
-    },
-    {
-      "snapshot": {
-        "time": "2015-10-22T10:20:30Z",
-        "tree": null,
-        "paths": null,
-        "tags": [
-          "foo",
-          "bar"
-        ]
-      },
-      "matches": [
-        "within 1y2m3d3h"
-      ],
-      "counters": {}
-    },
     {
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
@@ -750,6 +717,39 @@
       ],
       "counters": {}
     },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "within 1y2m3d3h"
+      ],
+      "counters": {}
+    },
+    {
+      "snapshot": {
+        "time": "2015-10-22T10:20:30Z",
+        "tree": null,
+        "paths": [
+          "path1",
+          "path2"
+        ],
+        "tags": [
+          "foo",
+          "bar"
+        ]
+      },
+      "matches": [
+        "within 1y2m3d3h"
+      ],
+      "counters": {}
+    },
     {
       "snapshot": {
         "time": "2015-10-20T10:20:30Z",
diff --git a/internal/restic/testdata/policy_keep_snapshots_3 b/internal/restic/testdata/policy_keep_snapshots_3
index 265e52130..b497c6902 100644
--- a/internal/restic/testdata/policy_keep_snapshots_3
+++ b/internal/restic/testdata/policy_keep_snapshots_3
@@ -150,27 +150,6 @@
       "tree": null,
       "paths": null
     },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": [
-        "path1",
-        "path2"
-      ],
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
     {
       "time": "2015-10-22T10:20:30Z",
       "tree": null,
@@ -185,6 +164,27 @@
         "bar"
       ]
     },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": [
+        "path1",
+        "path2"
+      ],
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
     {
       "time": "2015-10-20T10:20:30Z",
       "tree": null,
@@ -955,14 +955,7 @@
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
         "tree": null,
-        "paths": [
-          "path1",
-          "path2"
-        ],
-        "tags": [
-          "foo",
-          "bar"
-        ]
+        "paths": null
       },
       "matches": [
         "last snapshot"
@@ -992,7 +985,11 @@
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
         "tree": null,
-        "paths": null
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
       },
       "matches": [
         "last snapshot"
@@ -1005,7 +1002,10 @@
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
         "tree": null,
-        "paths": null,
+        "paths": [
+          "path1",
+          "path2"
+        ],
         "tags": [
           "foo",
           "bar"
diff --git a/internal/restic/testdata/policy_keep_snapshots_4 b/internal/restic/testdata/policy_keep_snapshots_4
index 8657da8c8..ff572d6a0 100644
--- a/internal/restic/testdata/policy_keep_snapshots_4
+++ b/internal/restic/testdata/policy_keep_snapshots_4
@@ -150,27 +150,6 @@
       "tree": null,
       "paths": null
     },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": [
-        "path1",
-        "path2"
-      ],
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
-    {
-      "time": "2015-10-22T10:20:30Z",
-      "tree": null,
-      "paths": null,
-      "tags": [
-        "foo",
-        "bar"
-      ]
-    },
     {
       "time": "2015-10-22T10:20:30Z",
       "tree": null,
@@ -185,6 +164,27 @@
         "bar"
       ]
     },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": null,
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
+    {
+      "time": "2015-10-22T10:20:30Z",
+      "tree": null,
+      "paths": [
+        "path1",
+        "path2"
+      ],
+      "tags": [
+        "foo",
+        "bar"
+      ]
+    },
     {
       "time": "2015-10-20T10:20:30Z",
       "tree": null,
@@ -975,14 +975,7 @@
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
         "tree": null,
-        "paths": [
-          "path1",
-          "path2"
-        ],
-        "tags": [
-          "foo",
-          "bar"
-        ]
+        "paths": null
       },
       "matches": [
         "last snapshot"
@@ -1012,7 +1005,11 @@
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
         "tree": null,
-        "paths": null
+        "paths": null,
+        "tags": [
+          "foo",
+          "bar"
+        ]
       },
       "matches": [
         "last snapshot"
@@ -1025,7 +1022,10 @@
       "snapshot": {
         "time": "2015-10-22T10:20:30Z",
         "tree": null,
-        "paths": null,
+        "paths": [
+          "path1",
+          "path2"
+        ],
         "tags": [
           "foo",
           "bar"