# HG changeset patch # User Brendan Cully # Date 1182195583 25200 # Node ID 23d9f0e667114d50e8a71645149b00f85771c3f7 # Parent 66ed92ed115a8b89b3facb042b9a51c6ca13e7f0# Parent fff50306e6dd257e1ad88bdad060647d2e274c4d Merge with mpm diff --git a/hgext/mq.py b/hgext/mq.py --- 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, diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- 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] diff --git a/mercurial/commands.py b/mercurial/commands.py --- 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))) diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- 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 ? diff --git a/mercurial/extensions.py b/mercurial/extensions.py --- 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): diff --git a/mercurial/hook.py b/mercurial/hook.py new file mode 100644 --- /dev/null +++ b/mercurial/hook.py @@ -0,0 +1,96 @@ +# hook.py - hook support for mercurial +# +# Copyright 2007 Matt Mackall +# +# 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 + diff --git a/mercurial/ignore.py b/mercurial/ignore.py 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 +# +# 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.''' + diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- 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): diff --git a/mercurial/ui.py b/mercurial/ui.py --- 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. diff --git a/tests/test-dispatch b/tests/test-dispatch --- 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 < $HGRCPATH <