changeset 2196:2a5d8af8eecc

merge with crew
author Benoit Boissinot <benoit.boissinot@ens-lyon.org>
date Thu, 04 May 2006 14:05:44 +0200
parents f027bc2d3f4a (current diff) ee90e5a9197f (diff)
children 5de8b44f0446
files
diffstat 5 files changed, 498 insertions(+), 193 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/bugzilla.py	Thu May 04 14:05:44 2006 +0200
@@ -0,0 +1,293 @@
+# bugzilla.py - bugzilla integration for mercurial
+#
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# hook extension to update comments of bugzilla bugs when changesets
+# that refer to bugs by id are seen.  this hook does not change bug
+# status, only comments.
+#
+# to configure, add items to '[bugzilla]' section of hgrc.
+#
+# to use, configure bugzilla extension and enable like this:
+#
+#   [extensions]
+#   hgext.bugzilla =
+#
+#   [hooks]
+#   # run bugzilla hook on every change pulled or pushed in here
+#   incoming.bugzilla = python:hgext.bugzilla.hook
+#
+# config items:
+#
+# REQUIRED:
+#   host = bugzilla # mysql server where bugzilla database lives
+#   password = **   # user's password
+#   version = 2.16  # version of bugzilla installed
+#
+# OPTIONAL:
+#   bzuser = ...    # bugzilla user id to record comments with
+#   db = bugs       # database to connect to
+#   hgweb = http:// # root of hg web site for browsing commits
+#   notify = ...    # command to run to get bugzilla to send mail
+#   regexp = ...    # regexp to match bug ids (must contain one "()" group)
+#   strip = 0       # number of slashes to strip for url paths
+#   style = ...     # style file to use when formatting comments
+#   template = ...  # template to use when formatting comments
+#   timeout = 5     # database connection timeout (seconds)
+#   user = bugs     # user to connect to database as
+
+from mercurial.demandload import *
+from mercurial.i18n import gettext as _
+from mercurial.node import *
+demandload(globals(), 'cStringIO mercurial:templater,util os re time')
+
+try:
+    import MySQLdb
+except ImportError:
+    raise util.Abort(_('python mysql support not available'))
+
+def buglist(ids):
+    return '(' + ','.join(map(str, ids)) + ')'
+
+class bugzilla_2_16(object):
+    '''support for bugzilla version 2.16.'''
+
+    def __init__(self, ui):
+        self.ui = ui
+        host = self.ui.config('bugzilla', 'host', 'localhost')
+        user = self.ui.config('bugzilla', 'user', 'bugs')
+        passwd = self.ui.config('bugzilla', 'password')
+        db = self.ui.config('bugzilla', 'db', 'bugs')
+        timeout = int(self.ui.config('bugzilla', 'timeout', 5))
+        self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
+                     (host, db, user, '*' * len(passwd)))
+        self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
+                                    db=db, connect_timeout=timeout)
+        self.cursor = self.conn.cursor()
+        self.run('select fieldid from fielddefs where name = "longdesc"')
+        ids = self.cursor.fetchall()
+        if len(ids) != 1:
+            raise util.Abort(_('unknown database schema'))
+        self.longdesc_id = ids[0][0]
+        self.user_ids = {}
+
+    def run(self, *args, **kwargs):
+        '''run a query.'''
+        self.ui.note(_('query: %s %s\n') % (args, kwargs))
+        try:
+            self.cursor.execute(*args, **kwargs)
+        except MySQLdb.MySQLError, err:
+            self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
+            raise
+
+    def filter_real_bug_ids(self, ids):
+        '''filter not-existing bug ids from list.'''
+        self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
+        ids = [c[0] for c in self.cursor.fetchall()]
+        ids.sort()
+        return ids
+
+    def filter_unknown_bug_ids(self, node, ids):
+        '''filter bug ids from list that already refer to this changeset.'''
+
+        self.run('''select bug_id from longdescs where
+                    bug_id in %s and thetext like "%%%s%%"''' %
+                 (buglist(ids), short(node)))
+        unknown = dict.fromkeys(ids)
+        for (id,) in self.cursor.fetchall():
+            self.ui.status(_('bug %d already knows about changeset %s\n') %
+                           (id, short(node)))
+            unknown.pop(id, None)
+        ids = unknown.keys()
+        ids.sort()
+        return ids
+
+    def notify(self, ids):
+        '''tell bugzilla to send mail.'''
+
+        self.ui.status(_('telling bugzilla to send mail:\n'))
+        for id in ids:
+            self.ui.status(_('  bug %s\n') % id)
+            cmd = self.ui.config('bugzilla', 'notify',
+                               'cd /var/www/html/bugzilla && '
+                               './processmail %s nobody@nowhere.com') % id
+            fp = os.popen('(%s) 2>&1' % cmd)
+            out = fp.read()
+            ret = fp.close()
+            if ret:
+                self.ui.warn(out)
+                raise util.Abort(_('bugzilla notify command %s') %
+                                 util.explain_exit(ret)[0])
+        self.ui.status(_('done\n'))
+
+    def get_user_id(self, user):
+        '''look up numeric bugzilla user id.'''
+        try:
+            return self.user_ids[user]
+        except KeyError:
+            try:
+                userid = int(user)
+            except ValueError:
+                self.ui.note(_('looking up user %s\n') % user)
+                self.run('''select userid from profiles
+                            where login_name like %s''', user)
+                all = self.cursor.fetchall()
+                if len(all) != 1:
+                    raise KeyError(user)
+                userid = int(all[0][0])
+            self.user_ids[user] = userid
+            return userid
+
+    def add_comment(self, bugid, text, prefuser):
+        '''add comment to bug. try adding comment as committer of
+        changeset, otherwise as default bugzilla user.'''
+        try:
+            userid = self.get_user_id(prefuser)
+        except KeyError:
+            try:
+                defaultuser = self.ui.config('bugzilla', 'bzuser')
+                userid = self.get_user_id(defaultuser)
+            except KeyError:
+                raise util.Abort(_('cannot find user id for %s or %s') %
+                                 (prefuser, defaultuser))
+        now = time.strftime('%Y-%m-%d %H:%M:%S')
+        self.run('''insert into longdescs
+                    (bug_id, who, bug_when, thetext)
+                    values (%s, %s, %s, %s)''',
+                 (bugid, userid, now, text))
+        self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
+                    values (%s, %s, %s, %s)''',
+                 (bugid, userid, now, self.longdesc_id))
+
+class bugzilla(object):
+    # supported versions of bugzilla. different versions have
+    # different schemas.
+    _versions = {
+        '2.16': bugzilla_2_16,
+        }
+
+    _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
+                       r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
+
+    _bz = None
+
+    def __init__(self, ui, repo):
+        self.ui = ui
+        self.repo = repo
+
+    def bz(self):
+        '''return object that knows how to talk to bugzilla version in
+        use.'''
+
+        if bugzilla._bz is None:
+            bzversion = self.ui.config('bugzilla', 'version')
+            try:
+                bzclass = bugzilla._versions[bzversion]
+            except KeyError:
+                raise util.Abort(_('bugzilla version %s not supported') %
+                                 bzversion)
+            bugzilla._bz = bzclass(self.ui)
+        return bugzilla._bz
+
+    def __getattr__(self, key):
+        return getattr(self.bz(), key)
+
+    _bug_re = None
+    _split_re = None
+
+    def find_bug_ids(self, node, desc):
+        '''find valid bug ids that are referred to in changeset
+        comments and that do not already have references to this
+        changeset.'''
+
+        if bugzilla._bug_re is None:
+            bugzilla._bug_re = re.compile(
+                self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
+                re.IGNORECASE)
+            bugzilla._split_re = re.compile(r'\D+')
+        start = 0
+        ids = {}
+        while True:
+            m = bugzilla._bug_re.search(desc, start)
+            if not m:
+                break
+            start = m.end()
+            for id in bugzilla._split_re.split(m.group(1)):
+                ids[int(id)] = 1
+        ids = ids.keys()
+        if ids:
+            ids = self.filter_real_bug_ids(ids)
+        if ids:
+            ids = self.filter_unknown_bug_ids(node, ids)
+        return ids
+
+    def update(self, bugid, node, changes):
+        '''update bugzilla bug with reference to changeset.'''
+
+        def webroot(root):
+            '''strip leading prefix of repo root and turn into
+            url-safe path.'''
+            count = int(self.ui.config('bugzilla', 'strip', 0))
+            root = util.pconvert(root)
+            while count > 0:
+                c = root.find('/')
+                if c == -1:
+                    break
+                root = root[c+1:]
+                count -= 1
+            return root
+
+        class stringio(object):
+            '''wrap cStringIO.'''
+            def __init__(self):
+                self.fp = cStringIO.StringIO()
+
+            def write(self, *args):
+                for a in args:
+                    self.fp.write(a)
+
+            write_header = write
+
+            def getvalue(self):
+                return self.fp.getvalue()
+
+        mapfile = self.ui.config('bugzilla', 'style')
+        tmpl = self.ui.config('bugzilla', 'template')
+        sio = stringio()
+        t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
+        if not mapfile and not tmpl:
+            tmpl = _('changeset {node|short} in repo {root} refers '
+                     'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
+        if tmpl:
+            tmpl = templater.parsestring(tmpl, quoted=False)
+            t.use_template(tmpl)
+        t.show(changenode=node, changes=changes,
+               bug=str(bugid),
+               hgweb=self.ui.config('bugzilla', 'hgweb'),
+               root=self.repo.root,
+               webroot=webroot(self.repo.root))
+        self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))
+
+def hook(ui, repo, hooktype, node=None, **kwargs):
+    '''add comment to bugzilla for each changeset that refers to a
+    bugzilla bug id. only add a comment once per bug, so same change
+    seen multiple times does not fill bug with duplicate data.'''
+    if node is None:
+        raise util.Abort(_('hook type %s does not pass a changeset id') %
+                         hooktype)
+    try:
+        bz = bugzilla(ui, repo)
+        bin_node = bin(node)
+        changes = repo.changelog.read(bin_node)
+        ids = bz.find_bug_ids(bin_node, changes[4])
+        if ids:
+            for id in ids:
+                bz.update(id, bin_node, changes)
+            bz.notify(ids)
+        return True
+    except MySQLdb.MySQLError, err:
+        raise util.Abort(_('database error: %s') % err[1])
+
--- a/mercurial/commands.py	Thu May 04 14:01:55 2006 +0200
+++ b/mercurial/commands.py	Thu May 04 14:05:44 2006 +0200
@@ -398,194 +398,6 @@
         user = revcache[rev] = ui.shortuser(name)
     return user
 
-class changeset_templater(object):
-    '''use templater module to format changeset information.'''
-
-    def __init__(self, ui, repo, mapfile):
-        self.t = templater.templater(mapfile, templater.common_filters,
-                                     cache={'parent': '{rev}:{node|short} ',
-                                            'manifest': '{rev}:{node|short}'})
-        self.ui = ui
-        self.repo = repo
-
-    def use_template(self, t):
-        '''set template string to use'''
-        self.t.cache['changeset'] = t
-
-    def write(self, thing, header=False):
-        '''write expanded template.
-        uses in-order recursive traverse of iterators.'''
-        for t in thing:
-            if hasattr(t, '__iter__'):
-                self.write(t, header=header)
-            elif header:
-                self.ui.write_header(t)
-            else:
-                self.ui.write(t)
-
-    def write_header(self, thing):
-        self.write(thing, header=True)
-
-    def show(self, rev=0, changenode=None, brinfo=None):
-        '''show a single changeset or file revision'''
-        log = self.repo.changelog
-        if changenode is None:
-            changenode = log.node(rev)
-        elif not rev:
-            rev = log.rev(changenode)
-
-        changes = log.read(changenode)
-
-        def showlist(name, values, plural=None, **args):
-            '''expand set of values.
-            name is name of key in template map.
-            values is list of strings or dicts.
-            plural is plural of name, if not simply name + 's'.
-
-            expansion works like this, given name 'foo'.
-
-            if values is empty, expand 'no_foos'.
-
-            if 'foo' not in template map, return values as a string,
-            joined by space.
-
-            expand 'start_foos'.
-
-            for each value, expand 'foo'. if 'last_foo' in template
-            map, expand it instead of 'foo' for last key.
-
-            expand 'end_foos'.
-            '''
-            if plural: names = plural
-            else: names = name + 's'
-            if not values:
-                noname = 'no_' + names
-                if noname in self.t:
-                    yield self.t(noname, **args)
-                return
-            if name not in self.t:
-                if isinstance(values[0], str):
-                    yield ' '.join(values)
-                else:
-                    for v in values:
-                        yield dict(v, **args)
-                return
-            startname = 'start_' + names
-            if startname in self.t:
-                yield self.t(startname, **args)
-            vargs = args.copy()
-            def one(v, tag=name):
-                try:
-                    vargs.update(v)
-                except (AttributeError, ValueError):
-                    try:
-                        for a, b in v:
-                            vargs[a] = b
-                    except ValueError:
-                        vargs[name] = v
-                return self.t(tag, **vargs)
-            lastname = 'last_' + name
-            if lastname in self.t:
-                last = values.pop()
-            else:
-                last = None
-            for v in values:
-                yield one(v)
-            if last is not None:
-                yield one(last, tag=lastname)
-            endname = 'end_' + names
-            if endname in self.t:
-                yield self.t(endname, **args)
-
-        if brinfo:
-            def showbranches(**args):
-                if changenode in brinfo:
-                    for x in showlist('branch', brinfo[changenode],
-                                      plural='branches', **args):
-                        yield x
-        else:
-            showbranches = ''
-
-        if self.ui.debugflag:
-            def showmanifest(**args):
-                args = args.copy()
-                args.update(dict(rev=self.repo.manifest.rev(changes[0]),
-                                 node=hex(changes[0])))
-                yield self.t('manifest', **args)
-        else:
-            showmanifest = ''
-
-        def showparents(**args):
-            parents = [[('rev', log.rev(p)), ('node', hex(p))]
-                       for p in log.parents(changenode)
-                       if self.ui.debugflag or p != nullid]
-            if (not self.ui.debugflag and len(parents) == 1 and
-                parents[0][0][1] == rev - 1):
-                return
-            for x in showlist('parent', parents, **args):
-                yield x
-
-        def showtags(**args):
-            for x in showlist('tag', self.repo.nodetags(changenode), **args):
-                yield x
-
-        if self.ui.debugflag:
-            files = self.repo.changes(log.parents(changenode)[0], changenode)
-            def showfiles(**args):
-                for x in showlist('file', files[0], **args): yield x
-            def showadds(**args):
-                for x in showlist('file_add', files[1], **args): yield x
-            def showdels(**args):
-                for x in showlist('file_del', files[2], **args): yield x
-        else:
-            def showfiles(**args):
-                for x in showlist('file', changes[3], **args): yield x
-            showadds = ''
-            showdels = ''
-
-        props = {
-            'author': changes[1],
-            'branches': showbranches,
-            'date': changes[2],
-            'desc': changes[4],
-            'file_adds': showadds,
-            'file_dels': showdels,
-            'files': showfiles,
-            'manifest': showmanifest,
-            'node': hex(changenode),
-            'parents': showparents,
-            'rev': rev,
-            'tags': showtags,
-            }
-
-        try:
-            if self.ui.debugflag and 'header_debug' in self.t:
-                key = 'header_debug'
-            elif self.ui.quiet and 'header_quiet' in self.t:
-                key = 'header_quiet'
-            elif self.ui.verbose and 'header_verbose' in self.t:
-                key = 'header_verbose'
-            elif 'header' in self.t:
-                key = 'header'
-            else:
-                key = ''
-            if key:
-                self.write_header(self.t(key, **props))
-            if self.ui.debugflag and 'changeset_debug' in self.t:
-                key = 'changeset_debug'
-            elif self.ui.quiet and 'changeset_quiet' in self.t:
-                key = 'changeset_quiet'
-            elif self.ui.verbose and 'changeset_verbose' in self.t:
-                key = 'changeset_verbose'
-            else:
-                key = 'changeset'
-            self.write(self.t(key, **props))
-        except KeyError, inst:
-            raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
-                                                           inst.args[0]))
-        except SyntaxError, inst:
-            raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
-
 class changeset_printer(object):
     '''show changeset information when templating not requested.'''
 
@@ -672,7 +484,7 @@
                 if not mapname: mapname = templater.templatepath(mapfile)
                 if mapname: mapfile = mapname
         try:
-            t = changeset_templater(ui, repo, mapfile)
+            t = templater.changeset_templater(ui, repo, mapfile)
         except SyntaxError, inst:
             raise util.Abort(inst.args[0])
         if tmpl: t.use_template(tmpl)
--- a/mercurial/localrepo.py	Thu May 04 14:01:55 2006 +0200
+++ b/mercurial/localrepo.py	Thu May 04 14:05:44 2006 +0200
@@ -105,7 +105,7 @@
                                    '("%s" is not callable)') %
                                  (hname, funcname))
             try:
-                r = obj(ui=ui, repo=repo, hooktype=name, **args)
+                r = obj(ui=self.ui, repo=self, hooktype=name, **args)
             except (KeyboardInterrupt, util.SignalInterrupt):
                 raise
             except Exception, exc:
--- a/mercurial/templater.py	Thu May 04 14:01:55 2006 +0200
+++ b/mercurial/templater.py	Thu May 04 14:05:44 2006 +0200
@@ -8,6 +8,7 @@
 import re
 from demandload import demandload
 from i18n import gettext as _
+from node import *
 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
 
 esctable = {
@@ -209,7 +210,7 @@
                 break
             yield text[start:m.start(0)], m.group(1)
             start = m.end(1)
-            
+
     fp = cStringIO.StringIO()
     for para, rest in findparas():
         fp.write(space_re.sub(' ', textwrap.fill(para, width)))
@@ -241,7 +242,7 @@
     r = author.find('>')
     if r == -1: r = None
     return author[author.find('<')+1:r]
-    
+
 def person(author):
     '''get name of author, or else username.'''
     f = author.find('<')
@@ -267,6 +268,7 @@
 
 common_filters = {
     "addbreaks": nl2br,
+    "basename": os.path.basename,
     "age": age,
     "date": lambda x: util.datestr(x),
     "domain": domain,
@@ -292,6 +294,7 @@
 def templatepath(name=None):
     '''return location of template file or directory (if no name).
     returns None if not found.'''
+
     # executable version (py2exe) doesn't support __file__
     if hasattr(sys, 'frozen'):
         module = sys.executable
@@ -303,3 +306,196 @@
         p = os.path.join(os.path.dirname(module), *fl)
         if (name and os.path.exists(p)) or os.path.isdir(p):
             return os.path.normpath(p)
+
+class changeset_templater(object):
+    '''format changeset information.'''
+
+    def __init__(self, ui, repo, mapfile, dest=None):
+        self.t = templater(mapfile, common_filters,
+                           cache={'parent': '{rev}:{node|short} ',
+                                  'manifest': '{rev}:{node|short}'})
+        self.ui = ui
+        self.dest = dest
+        self.repo = repo
+
+    def use_template(self, t):
+        '''set template string to use'''
+        self.t.cache['changeset'] = t
+
+    def write(self, thing, header=False):
+        '''write expanded template.
+        uses in-order recursive traverse of iterators.'''
+        dest = self.dest or self.ui
+        for t in thing:
+            if hasattr(t, '__iter__'):
+                self.write(t, header=header)
+            elif header:
+                dest.write_header(t)
+            else:
+                dest.write(t)
+
+    def write_header(self, thing):
+        self.write(thing, header=True)
+
+    def show(self, rev=0, changenode=None, brinfo=None, changes=None,
+             **props):
+        '''show a single changeset or file revision'''
+        log = self.repo.changelog
+        if changenode is None:
+            changenode = log.node(rev)
+        elif not rev:
+            rev = log.rev(changenode)
+        if changes is None:
+            changes = log.read(changenode)
+
+        def showlist(name, values, plural=None, **args):
+            '''expand set of values.
+            name is name of key in template map.
+            values is list of strings or dicts.
+            plural is plural of name, if not simply name + 's'.
+
+            expansion works like this, given name 'foo'.
+
+            if values is empty, expand 'no_foos'.
+
+            if 'foo' not in template map, return values as a string,
+            joined by space.
+
+            expand 'start_foos'.
+
+            for each value, expand 'foo'. if 'last_foo' in template
+            map, expand it instead of 'foo' for last key.
+
+            expand 'end_foos'.
+            '''
+            if plural: names = plural
+            else: names = name + 's'
+            if not values:
+                noname = 'no_' + names
+                if noname in self.t:
+                    yield self.t(noname, **args)
+                return
+            if name not in self.t:
+                if isinstance(values[0], str):
+                    yield ' '.join(values)
+                else:
+                    for v in values:
+                        yield dict(v, **args)
+                return
+            startname = 'start_' + names
+            if startname in self.t:
+                yield self.t(startname, **args)
+            vargs = args.copy()
+            def one(v, tag=name):
+                try:
+                    vargs.update(v)
+                except (AttributeError, ValueError):
+                    try:
+                        for a, b in v:
+                            vargs[a] = b
+                    except ValueError:
+                        vargs[name] = v
+                return self.t(tag, **vargs)
+            lastname = 'last_' + name
+            if lastname in self.t:
+                last = values.pop()
+            else:
+                last = None
+            for v in values:
+                yield one(v)
+            if last is not None:
+                yield one(last, tag=lastname)
+            endname = 'end_' + names
+            if endname in self.t:
+                yield self.t(endname, **args)
+
+        if brinfo:
+            def showbranches(**args):
+                if changenode in brinfo:
+                    for x in showlist('branch', brinfo[changenode],
+                                      plural='branches', **args):
+                        yield x
+        else:
+            showbranches = ''
+
+        if self.ui.debugflag:
+            def showmanifest(**args):
+                args = args.copy()
+                args.update(dict(rev=self.repo.manifest.rev(changes[0]),
+                                 node=hex(changes[0])))
+                yield self.t('manifest', **args)
+        else:
+            showmanifest = ''
+
+        def showparents(**args):
+            parents = [[('rev', log.rev(p)), ('node', hex(p))]
+                       for p in log.parents(changenode)
+                       if self.ui.debugflag or p != nullid]
+            if (not self.ui.debugflag and len(parents) == 1 and
+                parents[0][0][1] == rev - 1):
+                return
+            for x in showlist('parent', parents, **args):
+                yield x
+
+        def showtags(**args):
+            for x in showlist('tag', self.repo.nodetags(changenode), **args):
+                yield x
+
+        if self.ui.debugflag:
+            files = self.repo.changes(log.parents(changenode)[0], changenode)
+            def showfiles(**args):
+                for x in showlist('file', files[0], **args): yield x
+            def showadds(**args):
+                for x in showlist('file_add', files[1], **args): yield x
+            def showdels(**args):
+                for x in showlist('file_del', files[2], **args): yield x
+        else:
+            def showfiles(**args):
+                for x in showlist('file', changes[3], **args): yield x
+            showadds = ''
+            showdels = ''
+
+        defprops = {
+            'author': changes[1],
+            'branches': showbranches,
+            'date': changes[2],
+            'desc': changes[4],
+            'file_adds': showadds,
+            'file_dels': showdels,
+            'files': showfiles,
+            'manifest': showmanifest,
+            'node': hex(changenode),
+            'parents': showparents,
+            'rev': rev,
+            'tags': showtags,
+            }
+        props = props.copy()
+        props.update(defprops)
+
+        try:
+            if self.ui.debugflag and 'header_debug' in self.t:
+                key = 'header_debug'
+            elif self.ui.quiet and 'header_quiet' in self.t:
+                key = 'header_quiet'
+            elif self.ui.verbose and 'header_verbose' in self.t:
+                key = 'header_verbose'
+            elif 'header' in self.t:
+                key = 'header'
+            else:
+                key = ''
+            if key:
+                self.write_header(self.t(key, **props))
+            if self.ui.debugflag and 'changeset_debug' in self.t:
+                key = 'changeset_debug'
+            elif self.ui.quiet and 'changeset_quiet' in self.t:
+                key = 'changeset_quiet'
+            elif self.ui.verbose and 'changeset_verbose' in self.t:
+                key = 'changeset_verbose'
+            else:
+                key = 'changeset'
+            self.write(self.t(key, **props))
+        except KeyError, inst:
+            raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
+                                                           inst.args[0]))
+        except SyntaxError, inst:
+            raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
--- a/mercurial/util.py	Thu May 04 14:01:55 2006 +0200
+++ b/mercurial/util.py	Thu May 04 14:05:44 2006 +0200
@@ -231,7 +231,7 @@
                 name_st = os.stat(name)
             except OSError:
                 break
-            if os.path.samestat(name_st, root_st):
+            if samestat(name_st, root_st):
                 rel.reverse()
                 name = os.path.join(*rel)
                 audit_path(name)
@@ -561,6 +561,9 @@
     makelock = _makelock_file
     readlock = _readlock_file
 
+    def samestat(s1, s2):
+        return False
+
     def explain_exit(code):
         return _("exited with status %d") % code, code
 
@@ -627,6 +630,7 @@
         return path
 
     normpath = os.path.normpath
+    samestat = os.path.samestat
 
     def makelock(info, pathname):
         try: