Optimize dirstate walking
This generally cuts the time for hg status/diff in half, from 2s down to 1s.
The main parts I'm trying to optimize are:
1) os.walk stats every file. dirstate.changes then stats every file again.
2) os.walk yields every file and subdir to dirstate.traverse who yields every
file and everything in the dirstate map. dirstate.walk then
filters this mass and yields every file to the caller. There should be
fewer steps in here, and fewer duplicate strings yielded.
3) dirstate.walk runs util.unique on the results from dirstate.traverse,
even though it is also passing things through dirstate.seen to look for
duplicates.
I've turned os.walk into something hg specific that takes all the dirstate
ignore and matching rules into account. The new function also takes an
function arg (statmatch()) the caller supplies to help filter out
files it doesn't care about. dirstate.changes uses this to update state
for each file, avoiding the second stat call.
dirstate.walk is changed to turn the match function it is passed into
a statmatch function. The only real difference is that a statmatch
function takes the stat data as a second parameter. It now calls
dirstate.walkhelper, who requires a statmatch function to be passed.
This fails test-walk, but right now I think this is from a sorting error
fixed by this patch.
Index: crew/mercurial/dirstate.py
===================================================================
# sshrepo.py - ssh repository proxy class for mercurial
#
# Copyright 2005 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
import os, re, select
from node import *
from remoterepo import *
class sshrepository(remoterepository):
def __init__(self, ui, path):
self.url = path
self.ui = ui
m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
if not m:
raise RepoError("couldn't parse destination %s" % path)
self.user = m.group(2)
self.host = m.group(3)
self.port = m.group(5)
self.path = m.group(7) or "."
args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
args = self.port and ("%s -p %s") % (args, self.port) or args
sshcmd = self.ui.config("ui", "ssh", "ssh")
remotecmd = self.ui.config("ui", "remotecmd", "hg")
cmd = "%s %s '%s -R %s serve --stdio'"
cmd = cmd % (sshcmd, args, remotecmd, self.path)
self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
def readerr(self):
while 1:
r,w,x = select.select([self.pipee], [], [], 0)
if not r: break
l = self.pipee.readline()
if not l: break
self.ui.status("remote: ", l)
def __del__(self):
try:
self.pipeo.close()
self.pipei.close()
for l in self.pipee:
self.ui.status("remote: ", l)
self.pipee.close()
except:
pass
def dev(self):
return -1
def do_cmd(self, cmd, **args):
self.ui.debug("sending %s command\n" % cmd)
self.pipeo.write("%s\n" % cmd)
for k, v in args.items():
self.pipeo.write("%s %d\n" % (k, len(v)))
self.pipeo.write(v)
self.pipeo.flush()
return self.pipei
def call(self, cmd, **args):
r = self.do_cmd(cmd, **args)
l = r.readline()
self.readerr()
try:
l = int(l)
except:
raise RepoError("unexpected response '%s'" % l)
return r.read(l)
def lock(self):
self.call("lock")
return remotelock(self)
def unlock(self):
self.call("unlock")
def heads(self):
d = self.call("heads")
try:
return map(bin, d[:-1].split(" "))
except:
raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
def branches(self, nodes):
n = " ".join(map(hex, nodes))
d = self.call("branches", nodes=n)
try:
br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
return br
except:
raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
def between(self, pairs):
n = "\n".join(["-".join(map(hex, p)) for p in pairs])
d = self.call("between", pairs=n)
try:
p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
return p
except:
raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
def changegroup(self, nodes):
n = " ".join(map(hex, nodes))
f = self.do_cmd("changegroup", roots=n)
return self.pipei
def addchangegroup(self, cg):
d = self.call("addchangegroup")
if d:
raise RepoError("push refused: %s", d)
while 1:
d = cg.read(4096)
if not d: break
self.pipeo.write(d)
self.readerr()
self.pipeo.flush()
self.readerr()
l = int(self.pipei.readline())
return self.pipei.read(l) != ""