Patchwork [V2] strip: make --keep option not set all dirstate times to 0

login
register
mail settings
Submitter Durham Goode
Date March 8, 2013, 9:26 p.m.
Message ID <72a36624c166b4234b20.1362778017@dev350.prn1.facebook.com>
Download mbox | patch
Permalink /patch/1085/
State Accepted
Commit e74704c33e24b841301b91508a4275e9571998fa
Headers show

Comments

Durham Goode - March 8, 2013, 9:26 p.m.
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1362629589 28800
#      Wed Mar 06 20:13:09 2013 -0800
# Node ID 72a36624c166b4234b20240e86dca8f1b217559a
# Parent  2b1729b20820c0eeb0857bb224d009db698faeef
strip: make --keep option not set all dirstate times to 0

hg strip -k was using dirstate.rebuild() which reset all the dirstate
entries timestamps to 0.  This meant that the next time hg status was
run every file was considered to be 'unsure', which caused it to do
expensive read operations on every filelog. On a repo with >150,000
files it took 70 seconds when everything was in memory.  From a cold
cache it took several minutes.

The fix is to only reset files that have changed between the working
context and the destination context.

For reference, --keep means the working directory is left alone during
the strip. We have users wanting to use this operation to store their
work-in-progress as a commit on a branch while they go work on another
branch, then come back later and be able to uncommit that work and
continue working.  They currently use 'git reset HARD^' to accomplish
this in git.
Durham Goode - March 8, 2013, 9:31 p.m.
This uses a slightly different algorithm to determine the changed files.
In this version 'hg strip -k .' takes 3.9 seconds, versus the previous
patch took 5.9s.  Without the patch at all it takes 3.4s (but has the huge
cost for the next command, as I mentioned before).
Bryan O'Sullivan - March 10, 2013, 12:24 a.m.
On Fri, Mar 8, 2013 at 1:26 PM, Durham Goode <durham@fb.com> wrote:

> strip: make --keep option not set all dirstate times to 0
>

Applied, thanks.

Patch

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -3037,7 +3037,22 @@ 
         wlock = repo.wlock()
         try:
             urev = repo.mq.qparents(repo, revs[0])
-            repo.dirstate.rebuild(urev, repo[urev].manifest())
+            uctx = repo[urev]
+
+            # only reset the dirstate for files that would actually change
+            # between the working context and uctx
+            descendantrevs = repo.revs("%s::." % uctx.rev())
+            changedfiles = []
+            for rev in descendantrevs:
+                # blindy reset the files, regardless of what actually changed
+                changedfiles.extend(repo[rev].files())
+
+            # reset files that only changed in the dirstate too
+            dirstate = repo.dirstate
+            dirchanges = [f for f in dirstate if dirstate[f] != 'n']
+            changedfiles.extend(dirchanges)
+
+            repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
             repo.dirstate.write()
             update = False
         finally:
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -498,13 +498,18 @@ 
         self._lastnormaltime = 0
         self._dirty = True
 
-    def rebuild(self, parent, files):
+    def rebuild(self, parent, allfiles, changedfiles=None):
+        changedfiles = changedfiles or allfiles
+        oldmap = self._map
         self.clear()
-        for f in files:
-            if 'x' in files.flags(f):
-                self._map[f] = ('n', 0777, -1, 0)
+        for f in allfiles:
+            if f not in changedfiles:
+                self._map[f] = oldmap[f]
             else:
-                self._map[f] = ('n', 0666, -1, 0)
+                if 'x' in allfiles.flags(f):
+                    self._map[f] = ('n', 0777, -1, 0)
+                else:
+                    self._map[f] = ('n', 0666, -1, 0)
         self._pl = (parent, nullid)
         self._dirty = True
 
diff --git a/tests/test-mq-strip.t b/tests/test-mq-strip.t
--- a/tests/test-mq-strip.t
+++ b/tests/test-mq-strip.t
@@ -420,6 +420,25 @@ 
   $ hg status
   M bar
   ? b
+
+Strip adds, removes, modifies with --keep
+
+  $ touch b
+  $ hg add b
+  $ hg commit -mb
+  $ touch c
+  $ hg add c
+  $ hg rm bar
+  $ hg commit -mc
+  $ echo b > b
+  $ echo d > d
+  $ hg strip --keep tip
+  saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
+  $ hg status
+  M b
+  ! bar
+  ? c
+  ? d
   $ cd ..
 
 stripping many nodes on a complex graph (issue3299)