[PATCH] shelve: always backup shelves instead of deleting them

Colin Chan colinchan at fb.com
Wed Jun 24 14:16:48 CDT 2015


# HG changeset patch
# User Colin Chan <colinchan at fb.com>
# Date 1434989752 25200
#      Mon Jun 22 09:15:52 2015 -0700
# Node ID 7a02a3b3941cb62203b6efc145e58c543e8b8909
# Parent  7fdd1782fc4ee9da87d8af13e806dc9055db2c38
shelve: always backup shelves instead of deleting them

Instead of being deleted, shelve files are now moved into the
.hg/shelve-backup directory. This is designed to be very similar to how strip
saves backpus into .ht/strip-backup. The goal is to prevent data loss especially
when using unshelve (e.g., http://bz.selenic.com/show_bug.cgi?id=4732) at the
expense of using more disk space over time.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -22,6 +22,7 @@
 """
 
 import collections
+import itertools
 from mercurial.i18n import _
 from mercurial.node import nullid, nullrev, bin, hex
 from mercurial import changegroup, cmdutil, scmutil, phases, commands
@@ -48,6 +49,7 @@
         self.repo = repo
         self.name = name
         self.vfs = scmutil.vfs(repo.join('shelved'))
+        self.backupvfs = scmutil.vfs(repo.join('shelve-backup'))
         self.ui = self.repo.ui
         if filetype:
             self.fname = name + '.' + filetype
@@ -60,8 +62,21 @@
     def filename(self):
         return self.vfs.join(self.fname)
 
-    def unlink(self):
-        util.unlink(self.filename())
+    def backupfilename(self):
+        def gennames(base):
+            yield base
+            for i in itertools.count(1):
+                yield '%s-%d' % (base, i)
+
+        name = self.backupvfs.join(self.fname)
+        for n in gennames(name):
+            if not self.backupvfs.exists(n):
+                return n
+
+    def movetobackup(self):
+        if not self.backupvfs.isdir():
+            self.backupvfs.makedir()
+        util.rename(self.filename(), self.backupfilename())
 
     def stat(self):
         return self.vfs.stat(self.fname)
@@ -281,7 +296,7 @@
         for (name, _type) in repo.vfs.readdir('shelved'):
             suffix = name.rsplit('.', 1)[-1]
             if suffix in ('hg', 'patch'):
-                shelvedfile(repo, name).unlink()
+                shelvedfile(repo, name).movetobackup()
     finally:
         lockmod.release(wlock)
 
@@ -293,7 +308,7 @@
     try:
         for name in pats:
             for suffix in 'hg patch'.split():
-                shelvedfile(repo, name, suffix).unlink()
+                shelvedfile(repo, name, suffix).movetobackup()
     except OSError, err:
         if err.errno != errno.ENOENT:
             raise
@@ -442,7 +457,7 @@
     """remove related files after an unshelve"""
     if not opts['keep']:
         for filetype in 'hg patch'.split():
-            shelvedfile(repo, name, filetype).unlink()
+            shelvedfile(repo, name, filetype).movetobackup()
 
 def unshelvecontinue(ui, repo, state, opts):
     """subcommand to continue an in-progress unshelve"""
@@ -505,18 +520,19 @@
     restore. If none is given, the most recent shelved change is used.
 
     If a shelved change is applied successfully, the bundle that
-    contains the shelved changes is deleted afterwards.
+    contains the shelved changes is moved to a backup location
+    (.hg/shelve-backup).
 
     Since you can restore a shelved change on top of an arbitrary
     commit, it is possible that unshelving will result in a conflict
     between your changes and the commits you are unshelving onto. If
     this occurs, you must resolve the conflict, then use
     ``--continue`` to complete the unshelve operation. (The bundle
-    will not be deleted until you successfully complete the unshelve.)
+    will not be moved until you successfully complete the unshelve.)
 
     (Alternatively, you can use ``--abort`` to abandon an unshelve
     that causes a conflict. This reverts the unshelved changes, and
-    does not delete the bundle.)
+    leaves the bundle in place.)
     """
     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
@@ -85,6 +85,12 @@
   nothing changed
   [1]
 
+make sure shelve files were backed up
+
+  $ ls .hg/shelve-backup
+  default.hg
+  default.patch
+
 create an mq patch - shelving should work fine with a patch applied
 
   $ echo n > n
@@ -154,6 +160,14 @@
   $ hg shelve -d default
   $ hg qfinish -a -q
 
+ensure shelve backups aren't overwritten
+
+  $ ls .hg/shelve-backup/
+  default.hg
+  default.hg-1
+  default.patch
+  default.patch-1
+
 local edits should not prevent a shelved change from applying
 
   $ printf "z\na\n" > a/a


More information about the Mercurial-devel mailing list