[PATCH] shelve: keep old backups if timestamp can't decide exact order of them

FUJIWARA Katsunori foozy at lares.dti.ne.jp
Mon Jul 13 09:52:01 CDT 2015

# HG changeset patch
# User FUJIWARA Katsunori <foozy at lares.dti.ne.jp>
# Date 1436798052 -32400
#      Mon Jul 13 23:34:12 2015 +0900
# Node ID 472626d23c5867eaf461189d6a3def53a8098125
# Parent  026105c442d7f20d239c1e5e5fac1cfa080323da
shelve: keep old backups if timestamp can't decide exact order of them

Before this patch, backups to be discarded are decided by steps below
at 'hg unshelve' or so:

  1. list '(st_mtime, filename)' tuples of each backups up
  2. sort list of these tuples, and
  3. discard backups other than 'maxbackups' ones at the end of list

This doesn't work well in the case below:

  - "sort by name" order differs from actual backup-ing order, and
  - some of backups have same timestamp

For example, 'test-shelve.t' satisfies the former condition:

  - 'default-01' < 'default-1' in "sort by name" order
  - 'default-1'  < 'default-01' in actual backup-ing order

Then, 'default-01' is discarded instead of 'default-1' unexpectedly,
if they have same timestamp. This failure appears occasionally,
because the most important condition "same timestamp" is timing

To avoid such unexpected discarding, this patch keeps old backups if
timestamp can't decide exact order of them.

Timestamp of the border backup (= the oldest one of recent
'maxbackups' ones) as 'bordermtime' is used to examine whether
timestamp can decide exact order of backups.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -163,7 +163,14 @@
     maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
     hgfiles = [f for f in vfs.listdir() if f.endswith('.hg')]
     hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
+    if 0 < maxbackups and maxbackups < len(hgfiles):
+        bordermtime = hgfiles[-maxbackups][0]
+    else:
+        bordermtime = None
     for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
+        if mtime == bordermtime:
+            # keep it, because timestamp can't decide exact order of backups
+            continue
         base = f[:-3]
         for ext in 'hg patch'.split():
@@ -558,6 +565,12 @@
     backup directory. Only the N most recent backups are kept. N
     defaults to 10 but can be overridden using the shelve.maxbackups
     configuration option.
+    .. container:: verbose
+       Timestamp in seconds is used to decide order of backups. More
+       than ``maxbackups`` backups are kept, if same timestamp
+       prevents from deciding exact order of them, for safety.
     abortf = opts['abort']
     continuef = opts['continue']
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -185,6 +185,16 @@
 apply it and make sure our state is as expected
+(this also tests that same timestamp prevents backups from being
+removed, even though there are more than 'maxbackups' backups)
+  $ f -t .hg/shelve-backup/default.hg
+  .hg/shelve-backup/default.hg: file
+  $ touch -t 200001010000 .hg/shelve-backup/default.hg
+  $ f -t .hg/shelve-backup/default-1.hg
+  .hg/shelve-backup/default-1.hg: file
+  $ touch -t 200001010000 .hg/shelve-backup/default-1.hg
   $ hg unshelve
   unshelving change 'default-01'
   $ hg status -C
@@ -196,6 +206,17 @@
   R b/b
   $ hg shelve -l
+(both of default.hg and default-1.hg should be still kept, because it
+is difficult to decide actual order of them from same timestamp)
+  $ ls .hg/shelve-backup/
+  default-01.hg
+  default-01.patch
+  default-1.hg
+  default-1.patch
+  default.hg
+  default.patch
   $ hg unshelve
   abort: no shelved changes to apply!

More information about the Mercurial-devel mailing list