changeset 4624:23d9f0e66711

Merge with mpm
author Brendan Cully <brendan@kublai.com>
date Mon, 18 Jun 2007 12:39:43 -0700
parents 66ed92ed115a (current diff) fff50306e6dd (diff)
children eaf87cd19337 eb99af2d845e
files
diffstat 12 files changed, 418 insertions(+), 419 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -453,8 +453,8 @@ class queue:
             try:
                 tr.abort()
             finally:
-                repo.reload()
-                repo.wreload()
+                repo.invalidate()
+                repo.dirstate.invalidate()
             raise
 
     def _apply(self, tr, repo, series, list=False, update_status=True,
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -117,14 +117,6 @@ def runcatch(ui, args):
             ui.warn("\n%r\n" % util.ellipsis(inst[1]))
     except util.Abort, inst:
         ui.warn(_("abort: %s\n") % inst)
-    except TypeError, inst:
-        # was this an argument error?
-        tb = traceback.extract_tb(sys.exc_info()[2])
-        if len(tb) > 2: # no
-            raise
-        ui.debug(inst, "\n")
-        ui.warn(_("%s: invalid arguments\n") % cmd)
-        commands.help_(ui, cmd)
     except SystemExit, inst:
         # Commands shouldn't sys.exit directly, but give a return code.
         # Just in case catch this and and pass exit code to caller.
@@ -261,14 +253,12 @@ def dispatch(ui, args):
     if cwd:
         os.chdir(cwd)
 
-    extensions.loadall(ui)
-    ui.addreadhook(extensions.loadall)
-
     # read the local repository .hgrc into a local ui object
     # this will trigger its extensions to load
     path = earlygetopt(["-R", "--repository", "--repo"], args)
     if not path:
         path = findrepo() or ""
+    lui = ui
     if path:
         try:
             lui = commands.ui.ui(parentui=ui)
@@ -276,6 +266,12 @@ def dispatch(ui, args):
         except IOError:
             pass
 
+    extensions.loadall(lui)
+    # check for fallback encoding
+    fallback = lui.config('ui', 'fallbackencoding')
+    if fallback:
+        util._fallbackencoding = fallback
+
     cmd, func, args, options, cmdoptions = parse(ui, args)
 
     if options["encoding"]:
@@ -320,15 +316,25 @@ def dispatch(ui, args):
     else:
         d = lambda: func(ui, *args, **cmdoptions)
 
-    return runcommand(ui, options, d)
+    return runcommand(ui, options, cmd, d)
 
-def runcommand(ui, options, cmdfunc):
+def runcommand(ui, options, cmd, cmdfunc):
+    def checkargs():
+        try:
+            return cmdfunc()
+        except TypeError, inst:
+            # was this an argument error?
+            tb = traceback.extract_tb(sys.exc_info()[2])
+            if len(tb) != 2: # no
+                raise
+            raise ParseError(cmd, _("invalid arguments"))
+
     if options['profile']:
         import hotshot, hotshot.stats
         prof = hotshot.Profile("hg.prof")
         try:
             try:
-                return prof.runcall(cmdfunc)
+                return prof.runcall(checkargs)
             except:
                 try:
                     ui.warn(_('exception raised - generating '
@@ -352,14 +358,14 @@ def runcommand(ui, options, cmdfunc):
         p = lsprof.Profiler()
         p.enable(subcalls=True)
         try:
-            return cmdfunc()
+            return checkargs()
         finally:
             p.disable()
             stats = lsprof.Stats(p.getstats())
             stats.sort()
             stats.pprint(top=10, file=sys.stderr, climit=5)
     else:
-        return cmdfunc()
+        return checkargs()
 
 def bail_if_changed(repo):
     modified, added, removed, deleted = repo.status()[:4]
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -677,10 +677,7 @@ def debugrebuildstate(ui, repo, rev=""):
 def debugcheckstate(ui, repo):
     """validate the correctness of the current dirstate"""
     parent1, parent2 = repo.dirstate.parents()
-    repo.dirstate.read()
-    dc = repo.dirstate.map
-    keys = dc.keys()
-    keys.sort()
+    dc = repo.dirstate
     m1 = repo.changectx(parent1).manifest()
     m2 = repo.changectx(parent2).manifest()
     errors = 0
@@ -749,11 +746,8 @@ def debugsetparents(ui, repo, rev1, rev2
 
 def debugstate(ui, repo):
     """show the contents of the current dirstate"""
-    repo.dirstate.read()
-    dc = repo.dirstate.map
-    keys = dc.keys()
-    keys.sort()
-    for file_ in keys:
+    dc = repo.dirstate
+    for file_ in dc:
         if dc[file_][3] == -1:
             # Pad or slice to locale representation
             locale_len = len(time.strftime("%x %X", time.localtime(0)))
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -9,34 +9,69 @@ of the GNU General Public License, incor
 
 from node import *
 from i18n import _
-import struct, os, time, bisect, stat, strutil, util, re, errno
+import struct, os, time, bisect, stat, strutil, util, re, errno, ignore
 import cStringIO
 
+_unknown = ('?', 0, 0, 0)
+_format = ">cllll"
+
 class dirstate(object):
-    format = ">cllll"
 
     def __init__(self, opener, ui, root):
-        self.opener = opener
-        self.root = root
-        self.dirty = 0
-        self.ui = ui
-        self.map = None
-        self.fp = None
-        self.pl = None
-        self.dirs = None
-        self.copymap = {}
-        self.ignorefunc = None
-        self._branch = None
-        self._slash = None
+        self._opener = opener
+        self._root = root
+        self._dirty = 0
+        self._ui = ui
+
+    def __getattr__(self, name):
+        if name == '_map':
+            self._read()
+            return self._map
+        elif name == '_copymap':
+            self._read()
+            return self._copymap
+        elif name == '_branch':
+            try:
+                self._branch = self._opener("branch").read().strip()\
+                               or "default"
+            except IOError:
+                self._branch = "default"
+            return self._branch
+        elif name == '_pl':
+            self._pl = [nullid, nullid]
+            try:
+                st = self._opener("dirstate").read(40)
+                if len(st) == 40:
+                    self._pl = st[:20], st[20:40]
+            except IOError, err:
+                if err.errno != errno.ENOENT: raise
+            return self._pl
+        elif name == '_dirs':
+            self._dirs = {}
+            for f in self._map:
+                self._incpath(f)
+            return self._dirs
+        elif name == '_ignore':
+            files = [self.wjoin('.hgignore')]
+            for name, path in self._ui.configitems("ui"):
+                if name == 'ignore' or name.startswith('ignore.'):
+                    files.append(os.path.expanduser(path))
+            self._ignore = ignore.ignore(self._root, files, self._ui.warn)
+            return self._ignore
+        elif name == '_slash':
+            self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
+            return self._slash
+        else:
+            raise AttributeError, name
 
     def wjoin(self, f):
-        return os.path.join(self.root, f)
+        return os.path.join(self._root, f)
 
     def getcwd(self):
         cwd = os.getcwd()
-        if cwd == self.root: return ''
-        # self.root ends with a path separator if self.root is '/' or 'C:\'
-        rootsep = self.root
+        if cwd == self._root: return ''
+        # self._root ends with a path separator if self._root is '/' or 'C:\'
+        rootsep = self._root
         if not rootsep.endswith(os.sep):
             rootsep += os.sep
         if cwd.startswith(rootsep):
@@ -48,177 +83,71 @@ class dirstate(object):
     def pathto(self, f, cwd=None):
         if cwd is None:
             cwd = self.getcwd()
-        path = util.pathto(self.root, cwd, f)
-        if self._slash is None:
-            self._slash = self.ui.configbool('ui', 'slash') and os.sep != '/'
+        path = util.pathto(self._root, cwd, f)
         if self._slash:
-            path = path.replace(os.sep, '/')
+            return path.replace(os.sep, '/')
         return path
 
-    def hgignore(self):
-        '''return the contents of .hgignore files as a list of patterns.
-
-        the files parsed for patterns include:
-        .hgignore in the repository root
-        any additional files specified in the [ui] section of ~/.hgrc
-
-        trailing white space is dropped.
-        the escape character is backslash.
-        comments start with #.
-        empty lines are skipped.
-
-        lines can be of the following formats:
-
-        syntax: regexp # defaults following lines to non-rooted regexps
-        syntax: glob   # defaults following lines to non-rooted globs
-        re:pattern     # non-rooted regular expression
-        glob:pattern   # non-rooted glob
-        pattern        # pattern of the current default type'''
-        syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
-        def parselines(fp):
-            for line in fp:
-                if not line.endswith('\n'):
-                    line += '\n'
-                escape = False
-                for i in xrange(len(line)):
-                    if escape: escape = False
-                    elif line[i] == '\\': escape = True
-                    elif line[i] == '#': break
-                line = line[:i].rstrip()
-                if line: yield line
-        repoignore = self.wjoin('.hgignore')
-        files = [repoignore]
-        files.extend(self.ui.hgignorefiles())
-        pats = {}
-        for f in files:
-            try:
-                pats[f] = []
-                fp = open(f)
-                syntax = 'relre:'
-                for line in parselines(fp):
-                    if line.startswith('syntax:'):
-                        s = line[7:].strip()
-                        try:
-                            syntax = syntaxes[s]
-                        except KeyError:
-                            self.ui.warn(_("%s: ignoring invalid "
-                                           "syntax '%s'\n") % (f, s))
-                        continue
-                    pat = syntax + line
-                    for s in syntaxes.values():
-                        if line.startswith(s):
-                            pat = line
-                            break
-                    pats[f].append(pat)
-            except IOError, inst:
-                if f != repoignore:
-                    self.ui.warn(_("skipping unreadable ignore file"
-                                   " '%s': %s\n") % (f, inst.strerror))
-        return pats
-
-    def ignore(self, fn):
-        '''default match function used by dirstate and
-        localrepository.  this honours the repository .hgignore file
-        and any other files specified in the [ui] section of .hgrc.'''
-        if not self.ignorefunc:
-            ignore = self.hgignore()
-            allpats = []
-            [allpats.extend(patlist) for patlist in ignore.values()]
-            if allpats:
-                try:
-                    files, self.ignorefunc, anypats = (
-                        util.matcher(self.root, inc=allpats, src='.hgignore'))
-                except util.Abort:
-                    # Re-raise an exception where the src is the right file
-                    for f, patlist in ignore.items():
-                        files, self.ignorefunc, anypats = (
-                            util.matcher(self.root, inc=patlist, src=f))
-            else:
-                self.ignorefunc = util.never
-        return self.ignorefunc(fn)
-
     def __del__(self):
-        if self.dirty:
-            self.write()
+        self.write()
 
     def __getitem__(self, key):
-        try:
-            return self.map[key]
-        except TypeError:
-            self.lazyread()
-            return self[key]
-
-    _unknown = ('?', 0, 0, 0)
-
-    def get(self, key):
-        try:
-            return self[key]
-        except KeyError:
-            return self._unknown
+        return self._map[key]
 
     def __contains__(self, key):
-        self.lazyread()
-        return key in self.map
+        return key in self._map
+
+    def __iter__(self):
+        a = self._map.keys()
+        a.sort()
+        for x in a:
+            yield x
 
     def parents(self):
-        if self.pl is None:
-            self.pl = [nullid, nullid]
-            try:
-                self.fp = self.opener('dirstate')
-                st = self.fp.read(40)
-                if len(st) == 40:
-                    self.pl = st[:20], st[20:40]
-            except IOError, err:
-                if err.errno != errno.ENOENT: raise
-        return self.pl
+        return self._pl
 
     def branch(self):
-        if not self._branch:
-            try:
-                self._branch = self.opener("branch").read().strip()\
-                               or "default"
-            except IOError:
-                self._branch = "default"
         return self._branch
 
     def markdirty(self):
-        if not self.dirty:
-            self.dirty = 1
+        self._dirty = 1
 
     def setparents(self, p1, p2=nullid):
-        self.lazyread()
         self.markdirty()
-        self.pl = p1, p2
+        self._pl = p1, p2
 
     def setbranch(self, branch):
         self._branch = branch
-        self.opener("branch", "w").write(branch + '\n')
+        self._opener("branch", "w").write(branch + '\n')
 
     def state(self, key):
-        try:
-            return self[key][0]
-        except KeyError:
-            return "?"
+        return self._map.get(key, ("?",))[0]
 
-    def lazyread(self):
-        if self.map is None:
-            self.read()
+    def _read(self):
+        self._map = {}
+        self._copymap = {}
+        self._pl = [nullid, nullid]
+        try:
+            st = self._opener("dirstate").read()
+        except IOError, err:
+            if err.errno != errno.ENOENT: raise
+            return
+        if not st:
+            return
 
-    def parse(self, st):
-        self.pl = [st[:20], st[20: 40]]
+        self._pl = [st[:20], st[20: 40]]
 
         # deref fields so they will be local in loop
-        map = self.map
-        copymap = self.copymap
-        format = self.format
+        dmap = self._map
+        copymap = self._copymap
         unpack = struct.unpack
 
         pos = 40
-        e_size = struct.calcsize(format)
+        e_size = struct.calcsize(_format)
 
         while pos < len(st):
             newpos = pos + e_size
-            e = unpack(format, st[pos:newpos])
+            e = unpack(_format, st[pos:newpos])
             l = e[4]
             pos = newpos
             newpos = pos + l
@@ -226,80 +155,49 @@ class dirstate(object):
             if '\0' in f:
                 f, c = f.split('\0')
                 copymap[f] = c
-            map[f] = e[:4]
+            dmap[f] = e[:4]
             pos = newpos
 
-    def read(self):
-        self.map = {}
-        self.pl = [nullid, nullid]
-        try:
-            if self.fp:
-                self.fp.seek(0)
-                st = self.fp.read()
-                self.fp = None
-            else:
-                st = self.opener("dirstate").read()
-            if st:
-                self.parse(st)
-        except IOError, err:
-            if err.errno != errno.ENOENT: raise
-
-    def reload(self):
-        def mtime():
-            m = self.map and self.map.get('.hgignore')
-            return m and m[-1]
-
-        old_mtime = self.ignorefunc and mtime()
-        self.read()
-        if old_mtime != mtime():
-            self.ignorefunc = None
+    def invalidate(self):
+        for a in "_map _copymap _branch pl _dirs _ignore".split():
+            if hasattr(self, a):
+                self.__delattr__(a)
 
     def copy(self, source, dest):
-        self.lazyread()
         self.markdirty()
-        self.copymap[dest] = source
+        self._copymap[dest] = source
 
     def copied(self, file):
-        return self.copymap.get(file, None)
+        return self._copymap.get(file, None)
 
     def copies(self):
-        return self.copymap
+        return self._copymap
 
-    def initdirs(self):
-        if self.dirs is None:
-            self.dirs = {}
-            for f in self.map:
-                self.updatedirs(f, 1)
+    def _incpath(self, path):
+        for c in strutil.findall(path, '/'):
+            pc = path[:c]
+            self._dirs.setdefault(pc, 0)
+            self._dirs[pc] += 1
 
-    def updatedirs(self, path, delta):
-        if self.dirs is not None:
-            for c in strutil.findall(path, '/'):
-                pc = path[:c]
-                self.dirs.setdefault(pc, 0)
-                self.dirs[pc] += delta
+    def _decpath(self, path):
+        for c in strutil.findall(path, '/'):
+            pc = path[:c]
+            self._dirs.setdefault(pc, 0)
+            self._dirs[pc] -= 1
 
-    def checkinterfering(self, files):
-        def prefixes(f):
-            for c in strutil.rfindall(f, '/'):
-                yield f[:c]
-        self.lazyread()
-        self.initdirs()
-        seendirs = {}
-        for f in files:
-            # shadows
-            if self.dirs.get(f):
-                raise util.Abort(_('directory named %r already in dirstate') %
-                                 f)
-            for d in prefixes(f):
-                if d in seendirs:
-                    break
-                if d in self.map:
-                    raise util.Abort(_('file named %r already in dirstate') %
-                                     d)
-                seendirs[d] = True
-            # disallowed
-            if '\r' in f or '\n' in f:
-                raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
+    def _incpathcheck(self, f):
+        if '\r' in f or '\n' in f:
+            raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
+        # shadows
+        if f in self._dirs:
+            raise util.Abort(_('directory named %r already in dirstate') % f)
+        for c in strutil.rfindall(f, '/'):
+            d = f[:c]
+            if d in self._dirs:
+                break
+            if d in self._map:
+                raise util.Abort(_('file named %r already in dirstate') % d)
+        self._incpath(f)
 
     def update(self, files, state, **kw):
         ''' current states:
@@ -309,70 +207,60 @@ class dirstate(object):
         a  marked for addition'''
 
         if not files: return
-        self.lazyread()
         self.markdirty()
-        if state == "a":
-            self.initdirs()
-            self.checkinterfering(files)
         for f in files:
+            if self._copymap.has_key(f):
+                del self._copymap[f]
+
             if state == "r":
-                self.map[f] = ('r', 0, 0, 0)
-                self.updatedirs(f, -1)
+                self._map[f] = ('r', 0, 0, 0)
+                self._decpath(f)
+                continue
             else:
                 if state == "a":
-                    self.updatedirs(f, 1)
+                    self._incpathcheck(f)
                 s = os.lstat(self.wjoin(f))
                 st_size = kw.get('st_size', s.st_size)
                 st_mtime = kw.get('st_mtime', s.st_mtime)
-                self.map[f] = (state, s.st_mode, st_size, st_mtime)
-            if self.copymap.has_key(f):
-                del self.copymap[f]
+                self._map[f] = (state, s.st_mode, st_size, st_mtime)
 
     def forget(self, files):
         if not files: return
-        self.lazyread()
         self.markdirty()
-        self.initdirs()
         for f in files:
             try:
-                del self.map[f]
-                self.updatedirs(f, -1)
+                del self._map[f]
+                self._decpath(f)
             except KeyError:
-                self.ui.warn(_("not in dirstate: %s!\n") % f)
+                self._ui.warn(_("not in dirstate: %s!\n") % f)
                 pass
 
-    def clear(self):
-        self.map = {}
-        self.copymap = {}
-        self.dirs = None
-        self.markdirty()
-
     def rebuild(self, parent, files):
-        self.clear()
+        self.invalidate()
         for f in files:
             if files.execf(f):
-                self.map[f] = ('n', 0777, -1, 0)
+                self._map[f] = ('n', 0777, -1, 0)
             else:
-                self.map[f] = ('n', 0666, -1, 0)
-        self.pl = (parent, nullid)
+                self._map[f] = ('n', 0666, -1, 0)
+        self._pl = (parent, nullid)
         self.markdirty()
 
     def write(self):
-        if not self.dirty:
+        if not self._dirty:
             return
         cs = cStringIO.StringIO()
-        cs.write("".join(self.pl))
-        for f, e in self.map.iteritems():
+        cs.write("".join(self._pl))
+        for f, e in self._map.iteritems():
             c = self.copied(f)
             if c:
                 f = f + "\0" + c
-            e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
+            e = struct.pack(_format, e[0], e[1], e[2], e[3], len(f))
             cs.write(e)
             cs.write(f)
-        st = self.opener("dirstate", "w", atomictemp=True)
+        st = self._opener("dirstate", "w", atomictemp=True)
         st.write(cs.getvalue())
         st.rename()
-        self.dirty = 0
+        self._dirty = 0
 
     def filterfiles(self, files):
         ret = {}
@@ -380,16 +268,16 @@ class dirstate(object):
 
         for x in files:
             if x == '.':
-                return self.map.copy()
-            if x not in self.map:
+                return self._map.copy()
+            if x not in self._map:
                 unknown.append(x)
             else:
-                ret[x] = self.map[x]
+                ret[x] = self._map[x]
 
         if not unknown:
             return ret
 
-        b = self.map.keys()
+        b = self._map.keys()
         b.sort()
         blen = len(b)
 
@@ -398,13 +286,13 @@ class dirstate(object):
             while bs < blen:
                 s = b[bs]
                 if len(s) > len(x) and s.startswith(x):
-                    ret[s] = self.map[s]
+                    ret[s] = self._map[s]
                 else:
                     break
                 bs += 1
         return ret
 
-    def supported_type(self, f, st, verbose=False):
+    def _supported(self, f, st, verbose=False):
         if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
             return True
         if verbose:
@@ -414,7 +302,7 @@ class dirstate(object):
             elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
             elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
             elif stat.S_ISDIR(st.st_mode): kind = _('directory')
-            self.ui.warn(_('%s: unsupported file type (type is %s)\n')
+            self._ui.warn(_('%s: unsupported file type (type is %s)\n')
                          % (self.pathto(f), kind))
         return False
 
@@ -438,29 +326,28 @@ class dirstate(object):
 
         and st is the stat result if the file was found in the directory.
         '''
-        self.lazyread()
 
         # walk all files by default
         if not files:
             files = ['.']
-            dc = self.map.copy()
+            dc = self._map.copy()
         else:
             files = util.unique(files)
             dc = self.filterfiles(files)
 
         def imatch(file_):
-            if file_ not in dc and self.ignore(file_):
+            if file_ not in dc and self._ignore(file_):
                 return False
             return match(file_)
 
-        ignore = self.ignore
+        ignore = self._ignore
         if ignored:
             imatch = match
             ignore = util.never
 
-        # self.root may end with a path separator when self.root == '/'
-        common_prefix_len = len(self.root)
-        if not self.root.endswith(os.sep):
+        # self._root may end with a path separator when self._root == '/'
+        common_prefix_len = len(self._root)
+        if not self._root.endswith(os.sep):
             common_prefix_len += 1
         # recursion free walker, faster than os.walk.
         def findfiles(s):
@@ -498,7 +385,7 @@ class dirstate(object):
                         if imatch(np) and np in dc:
                             yield 'm', np, st
                     elif imatch(np):
-                        if self.supported_type(np, st):
+                        if self._supported(np, st):
                             yield 'f', np, st
                         elif np in dc:
                             yield 'm', np, st
@@ -523,7 +410,7 @@ class dirstate(object):
                         break
                 if not found:
                     if inst.errno != errno.ENOENT or not badmatch:
-                        self.ui.warn('%s: %s\n' % (self.pathto(ff),
+                        self._ui.warn('%s: %s\n' % (self.pathto(ff),
                                                    inst.strerror))
                     elif badmatch and badmatch(ff) and imatch(nf):
                         yield 'b', ff, None
@@ -536,7 +423,7 @@ class dirstate(object):
                     yield e
             else:
                 if not seen(nf) and match(nf):
-                    if self.supported_type(ff, st, verbose=True):
+                    if self._supported(ff, st, verbose=True):
                         yield 'f', nf, st
                     elif ff in dc:
                         yield 'm', nf, st
@@ -558,7 +445,7 @@ class dirstate(object):
             try:
                 type_, mode, size, time = self[fn]
             except KeyError:
-                if list_ignored and self.ignore(fn):
+                if list_ignored and self._ignore(fn):
                     ignored.append(fn)
                 else:
                     unknown.append(fn)
@@ -573,7 +460,7 @@ class dirstate(object):
                             raise
                         st = None
                     # We need to re-check that it is a valid file
-                    if st and self.supported_type(fn, st):
+                    if st and self._supported(fn, st):
                         nonexistent = False
                 # XXX: what to do with file no longer present in the fs
                 # who are not removed in the dirstate ?
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -63,7 +63,10 @@ def load(ui, name, path):
     commands.table.update(cmdtable)
 
 def loadall(ui):
-    for name, path in ui.extensions():
+    result = ui.configitems("extensions")
+    for i, (name, path) in enumerate(result):
+        if path:
+                path = os.path.expanduser(path)
         try:
             load(ui, name, path)
         except (util.SignalInterrupt, KeyboardInterrupt):
new file mode 100644
--- /dev/null
+++ b/mercurial/hook.py
@@ -0,0 +1,96 @@
+# hook.py - hook support for mercurial
+#
+# Copyright 2007 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.
+
+from i18n import _
+import util
+
+def _pythonhook(ui, repo, name, hname, funcname, args, throw):
+    '''call python hook. hook is callable object, looked up as
+    name in python module. if callable returns "true", hook
+    fails, else passes. if hook raises exception, treated as
+    hook failure. exception propagates if throw is "true".
+
+    reason for "true" meaning "hook failed" is so that
+    unmodified commands (e.g. mercurial.commands.update) can
+    be run as hooks without wrappers to convert return values.'''
+
+    ui.note(_("calling hook %s: %s\n") % (hname, funcname))
+    obj = funcname
+    if not callable(obj):
+        d = funcname.rfind('.')
+        if d == -1:
+            raise util.Abort(_('%s hook is invalid ("%s" not in '
+                               'a module)') % (hname, funcname))
+        modname = funcname[:d]
+        try:
+            obj = __import__(modname)
+        except ImportError:
+            try:
+                # extensions are loaded with hgext_ prefix
+                obj = __import__("hgext_%s" % modname)
+            except ImportError:
+                raise util.Abort(_('%s hook is invalid '
+                                   '(import of "%s" failed)') %
+                                 (hname, modname))
+        try:
+            for p in funcname.split('.')[1:]:
+                obj = getattr(obj, p)
+        except AttributeError, err:
+            raise util.Abort(_('%s hook is invalid '
+                               '("%s" is not defined)') %
+                             (hname, funcname))
+        if not callable(obj):
+            raise util.Abort(_('%s hook is invalid '
+                               '("%s" is not callable)') %
+                             (hname, funcname))
+    try:
+        r = obj(ui=ui, repo=repo, hooktype=name, **args)
+    except (KeyboardInterrupt, util.SignalInterrupt):
+        raise
+    except Exception, exc:
+        if isinstance(exc, util.Abort):
+            ui.warn(_('error: %s hook failed: %s\n') %
+                         (hname, exc.args[0]))
+        else:
+            ui.warn(_('error: %s hook raised an exception: '
+                           '%s\n') % (hname, exc))
+        if throw:
+            raise
+        ui.print_exc()
+        return True
+    if r:
+        if throw:
+            raise util.Abort(_('%s hook failed') % hname)
+        ui.warn(_('warning: %s hook failed\n') % hname)
+    return r
+
+def _exthook(ui, repo, name, cmd, args, throw):
+    ui.note(_("running hook %s: %s\n") % (name, cmd))
+    env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
+    r = util.system(cmd, environ=env, cwd=repo.root)
+    if r:
+        desc, r = util.explain_exit(r)
+        if throw:
+            raise util.Abort(_('%s hook %s') % (name, desc))
+        ui.warn(_('warning: %s hook %s\n') % (name, desc))
+    return r
+
+def hook(ui, repo, name, throw=False, **args):
+    r = False
+    hooks = [(hname, cmd) for hname, cmd in ui.configitems("hooks")
+             if hname.split(".", 1)[0] == name and cmd]
+    hooks.sort()
+    for hname, cmd in hooks:
+        if callable(cmd):
+            r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
+        elif cmd.startswith('python:'):
+            r = _pythonhook(ui, repo, name, hname, cmd[7:].strip(),
+                            args, throw) or r
+        else:
+            r = _exthook(ui, repo, hname, cmd, args, throw) or r
+    return r
+
new file mode 100644
--- /dev/null
+++ b/mercurial/ignore.py
@@ -0,0 +1,90 @@
+# ignore.py - ignored file handling for mercurial
+#
+# Copyright 2007 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.
+
+from i18n import _
+import util
+
+def _parselines(fp):
+    for line in fp:
+        if not line.endswith('\n'):
+            line += '\n'
+        escape = False
+        for i in xrange(len(line)):
+            if escape: escape = False
+            elif line[i] == '\\': escape = True
+            elif line[i] == '#': break
+        line = line[:i].rstrip()
+        if line:
+            yield line
+
+def ignore(root, files, warn):
+    '''return the contents of .hgignore files as a list of patterns.
+
+    the files parsed for patterns include:
+    .hgignore in the repository root
+    any additional files specified in the [ui] section of ~/.hgrc
+
+    trailing white space is dropped.
+    the escape character is backslash.
+    comments start with #.
+    empty lines are skipped.
+
+    lines can be of the following formats:
+
+    syntax: regexp # defaults following lines to non-rooted regexps
+    syntax: glob   # defaults following lines to non-rooted globs
+    re:pattern     # non-rooted regular expression
+    glob:pattern   # non-rooted glob
+    pattern        # pattern of the current default type'''
+
+    syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
+    pats = {}
+    for f in files:
+        try:
+            pats[f] = []
+            fp = open(f)
+            syntax = 'relre:'
+            for line in _parselines(fp):
+                if line.startswith('syntax:'):
+                    s = line[7:].strip()
+                    try:
+                        syntax = syntaxes[s]
+                    except KeyError:
+                        warn(_("%s: ignoring invalid syntax '%s'\n") % (f, s))
+                    continue
+                pat = syntax + line
+                for s in syntaxes.values():
+                    if line.startswith(s):
+                        pat = line
+                        break
+                pats[f].append(pat)
+        except IOError, inst:
+            if f != files[0]:
+                warn(_("skipping unreadable ignore file '%s': %s\n") %
+                     (f, inst.strerror))
+
+    allpats = []
+    [allpats.extend(patlist) for patlist in pats.values()]
+    if not allpats:
+        return util.never
+
+    try:
+        files, ignorefunc, anypats = (
+            util.matcher(root, inc=allpats, src='.hgignore'))
+    except util.Abort:
+        # Re-raise an exception where the src is the right file
+        for f, patlist in pats.items():
+            files, ignorefunc, anypats = (
+                util.matcher(root, inc=patlist, src=f))
+
+    return ignorefunc
+
+
+    '''default match function used by dirstate and
+    localrepository.  this honours the repository .hgignore file
+    and any other files specified in the [ui] section of .hgrc.'''
+
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -10,7 +10,7 @@ from i18n import _
 import repo, changegroup
 import changelog, dirstate, filelog, manifest, context
 import re, lock, transaction, tempfile, stat, mdiff, errno, ui
-import os, revlog, time, util
+import os, revlog, time, util, extensions, hook
 
 class localrepository(repo.repository):
     capabilities = ('lookup', 'changegroupsubset')
@@ -76,13 +76,10 @@ class localrepository(repo.repository):
         self.ui = ui.ui(parentui=parentui)
         try:
             self.ui.readconfig(self.join("hgrc"), self.root)
+            extensions.loadall(self.ui)
         except IOError:
             pass
 
-        fallback = self.ui.config('ui', 'fallbackencoding')
-        if fallback:
-            util._fallbackencoding = fallback
-
         self.tagscache = None
         self.branchcache = None
         self.nodetagscache = None
@@ -108,89 +105,7 @@ class localrepository(repo.repository):
         return 'file:' + self.root
 
     def hook(self, name, throw=False, **args):
-        def callhook(hname, funcname):
-            '''call python hook. hook is callable object, looked up as
-            name in python module. if callable returns "true", hook
-            fails, else passes. if hook raises exception, treated as
-            hook failure. exception propagates if throw is "true".
-
-            reason for "true" meaning "hook failed" is so that
-            unmodified commands (e.g. mercurial.commands.update) can
-            be run as hooks without wrappers to convert return values.'''
-
-            self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
-            obj = funcname
-            if not callable(obj):
-                d = funcname.rfind('.')
-                if d == -1:
-                    raise util.Abort(_('%s hook is invalid ("%s" not in '
-                                       'a module)') % (hname, funcname))
-                modname = funcname[:d]
-                try:
-                    obj = __import__(modname)
-                except ImportError:
-                    try:
-                        # extensions are loaded with hgext_ prefix
-                        obj = __import__("hgext_%s" % modname)
-                    except ImportError:
-                        raise util.Abort(_('%s hook is invalid '
-                                           '(import of "%s" failed)') %
-                                         (hname, modname))
-                try:
-                    for p in funcname.split('.')[1:]:
-                        obj = getattr(obj, p)
-                except AttributeError, err:
-                    raise util.Abort(_('%s hook is invalid '
-                                       '("%s" is not defined)') %
-                                     (hname, funcname))
-                if not callable(obj):
-                    raise util.Abort(_('%s hook is invalid '
-                                       '("%s" is not callable)') %
-                                     (hname, funcname))
-            try:
-                r = obj(ui=self.ui, repo=self, hooktype=name, **args)
-            except (KeyboardInterrupt, util.SignalInterrupt):
-                raise
-            except Exception, exc:
-                if isinstance(exc, util.Abort):
-                    self.ui.warn(_('error: %s hook failed: %s\n') %
-                                 (hname, exc.args[0]))
-                else:
-                    self.ui.warn(_('error: %s hook raised an exception: '
-                                   '%s\n') % (hname, exc))
-                if throw:
-                    raise
-                self.ui.print_exc()
-                return True
-            if r:
-                if throw:
-                    raise util.Abort(_('%s hook failed') % hname)
-                self.ui.warn(_('warning: %s hook failed\n') % hname)
-            return r
-
-        def runhook(name, cmd):
-            self.ui.note(_("running hook %s: %s\n") % (name, cmd))
-            env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
-            r = util.system(cmd, environ=env, cwd=self.root)
-            if r:
-                desc, r = util.explain_exit(r)
-                if throw:
-                    raise util.Abort(_('%s hook %s') % (name, desc))
-                self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
-            return r
-
-        r = False
-        hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
-                 if hname.split(".", 1)[0] == name and cmd]
-        hooks.sort()
-        for hname, cmd in hooks:
-            if callable(cmd):
-                r = callhook(hname, cmd) or r
-            elif cmd.startswith('python:'):
-                r = callhook(hname, cmd[7:].strip()) or r
-            else:
-                r = runhook(hname, cmd) or r
-        return r
+        return hook.hook(self.ui, self, name, throw, **args)
 
     tag_disallowed = ':\r\n'
 
@@ -586,7 +501,7 @@ class localrepository(repo.repository):
         if os.path.exists(self.sjoin("journal")):
             self.ui.status(_("rolling back interrupted transaction\n"))
             transaction.rollback(self.sopener, self.sjoin("journal"))
-            self.reload()
+            self.invalidate()
             return True
         else:
             self.ui.warn(_("no interrupted transaction available\n"))
@@ -601,17 +516,15 @@ class localrepository(repo.repository):
             self.ui.status(_("rolling back last transaction\n"))
             transaction.rollback(self.sopener, self.sjoin("undo"))
             util.rename(self.join("undo.dirstate"), self.join("dirstate"))
-            self.reload()
-            self.wreload()
+            self.invalidate()
+            self.dirstate.invalidate()
         else:
             self.ui.warn(_("no rollback information available\n"))
 
-    def wreload(self):
-        self.dirstate.reload()
-
-    def reload(self):
-        self.changelog.load()
-        self.manifest.load()
+    def invalidate(self):
+        for a in "changelog manifest".split():
+            if hasattr(self, a):
+                self.__delattr__(a)
         self.tagscache = None
         self.nodetagscache = None
 
@@ -632,12 +545,13 @@ class localrepository(repo.repository):
         return l
 
     def lock(self, wait=1):
-        return self.do_lock(self.sjoin("lock"), wait, acquirefn=self.reload,
+        return self.do_lock(self.sjoin("lock"), wait,
+                            acquirefn=self.invalidate,
                             desc=_('repository %s') % self.origroot)
 
     def wlock(self, wait=1):
         return self.do_lock(self.join("wlock"), wait, self.dirstate.write,
-                            self.wreload,
+                            self.dirstate.invalidate,
                             desc=_('working directory of %s') % self.origroot)
 
     def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist):
@@ -1932,7 +1846,7 @@ class localrepository(repo.repository):
         self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
                        (util.bytecount(total_bytes), elapsed,
                         util.bytecount(total_bytes / elapsed)))
-        self.reload()
+        self.invalidate()
         return len(self.heads()) + 1
 
     def clone(self, remote, heads=[], stream=False):
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -32,7 +32,6 @@ class ui(object):
         if parentui is None:
             # this is the parent of all ui children
             self.parentui = None
-            self.readhooks = []
             self.quiet = quiet
             self.verbose = verbose
             self.debugflag = debug
@@ -52,7 +51,6 @@ class ui(object):
         else:
             # parentui may point to an ui object which is already a child
             self.parentui = parentui.parentui or parentui
-            self.readhooks = self.parentui.readhooks[:]
             self.trusted_users = parentui.trusted_users.copy()
             self.trusted_groups = parentui.trusted_groups.copy()
             self.cdata = dupconfig(self.parentui.cdata)
@@ -154,11 +152,6 @@ class ui(object):
         if root is None:
             root = os.path.expanduser('~')
         self.fixconfig(root=root)
-        for hook in self.readhooks:
-            hook(self)
-
-    def addreadhook(self, hook):
-        self.readhooks.append(hook)
 
     def readsections(self, filename, *sections):
         """Read filename and add only the specified sections to the config data
@@ -319,20 +312,6 @@ class ui(object):
             for name, value in self.configitems(section, untrusted):
                 yield section, name, str(value).replace('\n', '\\n')
 
-    def extensions(self):
-        result = self.configitems("extensions")
-        for i, (key, value) in enumerate(result):
-            if value:
-                result[i] = (key, os.path.expanduser(value))
-        return result
-
-    def hgignorefiles(self):
-        result = []
-        for key, value in self.configitems("ui"):
-            if key == 'ignore' or key.startswith('ignore.'):
-                result.append(os.path.expanduser(value))
-        return result
-
     def username(self):
         """Return default username to be used in commits.
 
--- a/tests/test-dispatch
+++ b/tests/test-dispatch
@@ -6,6 +6,9 @@ cd a
 echo a > a
 hg ci -Ama -d '0 0'
 
+echo "# missing arg"
+hg cat
+
 echo '% [defaults]'
 hg cat a
 cat > $HGRCPATH <<EOF
@@ -13,3 +16,4 @@ cat > $HGRCPATH <<EOF
 cat = -v
 EOF
 hg cat a
+
--- a/tests/test-dispatch.out
+++ b/tests/test-dispatch.out
@@ -1,4 +1,30 @@
 adding a
+# missing arg
+hg cat: invalid arguments
+hg cat [OPTION]... FILE...
+
+output the current or given revision of files
+
+    Print the specified files as they were at the given revision.
+    If no revision is given, the parent of the working directory is used,
+    or tip if no revision is checked out.
+
+    Output may be to a file, in which case the name of the file is
+    given using a format string.  The formatting rules are the same as
+    for the export command, with the following additions:
+
+    %s   basename of file being printed
+    %d   dirname of file being printed, or '.' if in repo root
+    %p   root-relative path name of file being printed
+
+options:
+
+ -o --output   print output to file with formatted name
+ -r --rev      print the given revision
+ -I --include  include names matching the given patterns
+ -X --exclude  exclude names matching the given patterns
+
+use "hg -v help cat" to show global options
 % [defaults]
 a
 a
--- a/tests/test-extension.out
+++ b/tests/test-extension.out
@@ -1,5 +1,5 @@
 uisetup called
-ui.parentui is None
+ui.parentui isnot None
 reposetup called for a
 ui == repo.ui
 Foo
@@ -15,7 +15,7 @@ ui.parentui is None
 Bar
 % module/__init__.py-style
 uisetup called
-ui.parentui is None
+ui.parentui isnot None
 reposetup called for a
 ui == repo.ui
 Foo