--- /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: