Patchwork [2,of,2] histedit: store backup file before histedit

login
register
mail settings
Submitter Durham Goode
Date April 14, 2015, 5:06 p.m.
Message ID <75d4d58c15fc9c2f8b3a.1429031215@dev2000.prn2.facebook.com>
Download mbox | patch
Permalink /patch/8653/
State Accepted
Delegated to: Augie Fackler
Headers show

Comments

Durham Goode - April 14, 2015, 5:06 p.m.
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1428140263 25200
#      Sat Apr 04 02:37:43 2015 -0700
# Node ID 75d4d58c15fc9c2f8b3a5e9db02f7bc77143300e
# Parent  6289acbbd36ec81eab91ed3b8e32b3354bf8cf84
histedit: store backup file before histedit

It's possible for the user to delete some of the commits they started with
during a histedit, and aborting the histedit doesn't bring them back. Let's
store a backup bundle so we can always recover the stack of commits from before
they began.
Augie Fackler - April 15, 2015, 10:33 p.m.
On Tue, Apr 14, 2015 at 10:06:55AM -0700, Durham Goode wrote:
> # HG changeset patch
> # User Durham Goode <durham@fb.com>
> # Date 1428140263 25200
> #      Sat Apr 04 02:37:43 2015 -0700
> # Node ID 75d4d58c15fc9c2f8b3a5e9db02f7bc77143300e
> # Parent  6289acbbd36ec81eab91ed3b8e32b3354bf8cf84
> histedit: store backup file before histedit

Queued these, thanks.

>
> It's possible for the user to delete some of the commits they started with
> during a histedit, and aborting the histedit doesn't bring them back. Let's
> store a backup bundle so we can always recover the stack of commits from before
> they began.
>
> diff --git a/hgext/histedit.py b/hgext/histedit.py
> --- a/hgext/histedit.py
> +++ b/hgext/histedit.py
> @@ -163,8 +163,10 @@ import sys
>  from mercurial import cmdutil
>  from mercurial import discovery
>  from mercurial import error
> +from mercurial import changegroup
>  from mercurial import copies
>  from mercurial import context
> +from mercurial import exchange
>  from mercurial import extensions
>  from mercurial import hg
>  from mercurial import node
> @@ -206,6 +208,7 @@ class histeditstate(object):
>          self.parentctxnode = parentctxnode
>          self.lock = lock
>          self.wlock = wlock
> +        self.backupfile = None
>          if replacements is None:
>              self.replacements = []
>          else:
> @@ -223,15 +226,17 @@ class histeditstate(object):
>          try:
>              data = pickle.load(fp)
>              parentctxnode, rules, keep, topmost, replacements = data
> +            backupfile = None
>          except pickle.UnpicklingError:
>              data = self._load()
> -            parentctxnode, rules, keep, topmost, replacements = data
> +            parentctxnode, rules, keep, topmost, replacements, backupfile = data
>
>          self.parentctxnode = parentctxnode
>          self.rules = rules
>          self.keep = keep
>          self.topmost = topmost
>          self.replacements = replacements
> +        self.backupfile = backupfile
>
>      def write(self):
>          fp = self.repo.vfs('histedit-state', 'w')
> @@ -246,6 +251,7 @@ class histeditstate(object):
>          for replacement in self.replacements:
>              fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
>                  for r in replacement[1])))
> +        fp.write('%s\n' % self.backupfile)
>          fp.close()
>
>      def _load(self):
> @@ -288,9 +294,12 @@ class histeditstate(object):
>              replacements.append((original, succ))
>              index += 1
>
> +        backupfile = lines[index]
> +        index += 1
> +
>          fp.close()
>
> -        return parentctxnode, rules, keep, topmost, replacements
> +        return parentctxnode, rules, keep, topmost, replacements, backupfile
>
>      def clear(self):
>          self.repo.vfs.unlink('histedit-state')
> @@ -695,6 +704,16 @@ def _histedit(ui, repo, state, *freeargs
>          state.read()
>          mapping, tmpnodes, leafs, _ntm = processreplacement(state)
>          ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
> +
> +        # Recover our old commits if necessary
> +        if not state.topmost in repo and state.backupfile:
> +            backupfile = repo.join(state.backupfile)
> +            f = hg.openpath(ui, backupfile)
> +            gen = exchange.readbundle(ui, f, backupfile)
> +            changegroup.addchangegroup(repo, gen, 'histedit',
> +                                       'bundle:' + backupfile)
> +            os.remove(backupfile)
> +
>          # check whether we should update away
>          parentnodes = [c.node() for c in repo[None].parents()]
>          for n in leafs | set([state.parentctxnode]):
> @@ -753,6 +772,13 @@ def _histedit(ui, repo, state, *freeargs
>          state.topmost = topmost
>          state.replacements = replacements
>
> +        # Create a backup so we can always abort completely.
> +        backupfile = None
> +        if not obsolete.isenabled(repo, obsolete.createmarkersopt):
> +            backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
> +                                        'histedit')
> +        state.backupfile = backupfile
> +
>      while state.rules:
>          state.write()
>          action, ha = state.rules.pop(0)
> diff --git a/tests/test-histedit-edit.t b/tests/test-histedit-edit.t
> --- a/tests/test-histedit-edit.t
> +++ b/tests/test-histedit-edit.t
> @@ -147,6 +147,34 @@ qnew should fail while we're in the midd
>    $ hg cat e
>    a
>
> +Stripping necessary commits should not break --abort
> +
> +  $ hg histedit 1a60820cd1f6 --commands - 2>&1 << EOF| fixbundle
> +  > edit 1a60820cd1f6 wat
> +  > pick a5e1ba2f7afb foobaz
> +  > pick b5f70786f9b0 g
> +  > EOF
> +  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
> +  Make changes as needed, you may commit or record as needed now.
> +  When you are finished, run hg histedit --continue to resume.
> +
> +  $ mv .hg/histedit-state .hg/histedit-state.bak
> +  $ hg strip -q -r b5f70786f9b0
> +  $ mv .hg/histedit-state.bak .hg/histedit-state
> +  $ hg histedit --abort
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 1 changes to 3 files
> +  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +  $ hg log -r .
> +  changeset:   6:b5f70786f9b0
> +  tag:         tip
> +  user:        test
> +  date:        Thu Jan 01 00:00:00 1970 +0000
> +  summary:     f
> +
> +
>  check histedit_source
>
>    $ hg log --debug --rev 5
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel

Patch

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -163,8 +163,10 @@  import sys
 from mercurial import cmdutil
 from mercurial import discovery
 from mercurial import error
+from mercurial import changegroup
 from mercurial import copies
 from mercurial import context
+from mercurial import exchange
 from mercurial import extensions
 from mercurial import hg
 from mercurial import node
@@ -206,6 +208,7 @@  class histeditstate(object):
         self.parentctxnode = parentctxnode
         self.lock = lock
         self.wlock = wlock
+        self.backupfile = None
         if replacements is None:
             self.replacements = []
         else:
@@ -223,15 +226,17 @@  class histeditstate(object):
         try:
             data = pickle.load(fp)
             parentctxnode, rules, keep, topmost, replacements = data
+            backupfile = None
         except pickle.UnpicklingError:
             data = self._load()
-            parentctxnode, rules, keep, topmost, replacements = data
+            parentctxnode, rules, keep, topmost, replacements, backupfile = data
 
         self.parentctxnode = parentctxnode
         self.rules = rules
         self.keep = keep
         self.topmost = topmost
         self.replacements = replacements
+        self.backupfile = backupfile
 
     def write(self):
         fp = self.repo.vfs('histedit-state', 'w')
@@ -246,6 +251,7 @@  class histeditstate(object):
         for replacement in self.replacements:
             fp.write('%s%s\n' % (node.hex(replacement[0]), ''.join(node.hex(r)
                 for r in replacement[1])))
+        fp.write('%s\n' % self.backupfile)
         fp.close()
 
     def _load(self):
@@ -288,9 +294,12 @@  class histeditstate(object):
             replacements.append((original, succ))
             index += 1
 
+        backupfile = lines[index]
+        index += 1
+
         fp.close()
 
-        return parentctxnode, rules, keep, topmost, replacements
+        return parentctxnode, rules, keep, topmost, replacements, backupfile
 
     def clear(self):
         self.repo.vfs.unlink('histedit-state')
@@ -695,6 +704,16 @@  def _histedit(ui, repo, state, *freeargs
         state.read()
         mapping, tmpnodes, leafs, _ntm = processreplacement(state)
         ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
+
+        # Recover our old commits if necessary
+        if not state.topmost in repo and state.backupfile:
+            backupfile = repo.join(state.backupfile)
+            f = hg.openpath(ui, backupfile)
+            gen = exchange.readbundle(ui, f, backupfile)
+            changegroup.addchangegroup(repo, gen, 'histedit',
+                                       'bundle:' + backupfile)
+            os.remove(backupfile)
+
         # check whether we should update away
         parentnodes = [c.node() for c in repo[None].parents()]
         for n in leafs | set([state.parentctxnode]):
@@ -753,6 +772,13 @@  def _histedit(ui, repo, state, *freeargs
         state.topmost = topmost
         state.replacements = replacements
 
+        # Create a backup so we can always abort completely.
+        backupfile = None
+        if not obsolete.isenabled(repo, obsolete.createmarkersopt):
+            backupfile = repair._bundle(repo, [parentctxnode], [topmost], root,
+                                        'histedit')
+        state.backupfile = backupfile
+
     while state.rules:
         state.write()
         action, ha = state.rules.pop(0)
diff --git a/tests/test-histedit-edit.t b/tests/test-histedit-edit.t
--- a/tests/test-histedit-edit.t
+++ b/tests/test-histedit-edit.t
@@ -147,6 +147,34 @@  qnew should fail while we're in the midd
   $ hg cat e
   a
 
+Stripping necessary commits should not break --abort
+
+  $ hg histedit 1a60820cd1f6 --commands - 2>&1 << EOF| fixbundle
+  > edit 1a60820cd1f6 wat
+  > pick a5e1ba2f7afb foobaz
+  > pick b5f70786f9b0 g
+  > EOF
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  Make changes as needed, you may commit or record as needed now.
+  When you are finished, run hg histedit --continue to resume.
+
+  $ mv .hg/histedit-state .hg/histedit-state.bak
+  $ hg strip -q -r b5f70786f9b0
+  $ mv .hg/histedit-state.bak .hg/histedit-state
+  $ hg histedit --abort
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 3 files
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log -r .
+  changeset:   6:b5f70786f9b0
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     f
+  
+
 check histedit_source
 
   $ hg log --debug --rev 5