##// END OF EJS Templates
merge with crew
Benoit Boissinot -
r2196:2a5d8af8 merge default
parent child Browse files
Show More
@@ -0,0 +1,293 b''
1 # bugzilla.py - bugzilla integration for mercurial
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7 #
8 # hook extension to update comments of bugzilla bugs when changesets
9 # that refer to bugs by id are seen. this hook does not change bug
10 # status, only comments.
11 #
12 # to configure, add items to '[bugzilla]' section of hgrc.
13 #
14 # to use, configure bugzilla extension and enable like this:
15 #
16 # [extensions]
17 # hgext.bugzilla =
18 #
19 # [hooks]
20 # # run bugzilla hook on every change pulled or pushed in here
21 # incoming.bugzilla = python:hgext.bugzilla.hook
22 #
23 # config items:
24 #
25 # REQUIRED:
26 # host = bugzilla # mysql server where bugzilla database lives
27 # password = ** # user's password
28 # version = 2.16 # version of bugzilla installed
29 #
30 # OPTIONAL:
31 # bzuser = ... # bugzilla user id to record comments with
32 # db = bugs # database to connect to
33 # hgweb = http:// # root of hg web site for browsing commits
34 # notify = ... # command to run to get bugzilla to send mail
35 # regexp = ... # regexp to match bug ids (must contain one "()" group)
36 # strip = 0 # number of slashes to strip for url paths
37 # style = ... # style file to use when formatting comments
38 # template = ... # template to use when formatting comments
39 # timeout = 5 # database connection timeout (seconds)
40 # user = bugs # user to connect to database as
41
42 from mercurial.demandload import *
43 from mercurial.i18n import gettext as _
44 from mercurial.node import *
45 demandload(globals(), 'cStringIO mercurial:templater,util os re time')
46
47 try:
48 import MySQLdb
49 except ImportError:
50 raise util.Abort(_('python mysql support not available'))
51
52 def buglist(ids):
53 return '(' + ','.join(map(str, ids)) + ')'
54
55 class bugzilla_2_16(object):
56 '''support for bugzilla version 2.16.'''
57
58 def __init__(self, ui):
59 self.ui = ui
60 host = self.ui.config('bugzilla', 'host', 'localhost')
61 user = self.ui.config('bugzilla', 'user', 'bugs')
62 passwd = self.ui.config('bugzilla', 'password')
63 db = self.ui.config('bugzilla', 'db', 'bugs')
64 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
65 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
66 (host, db, user, '*' * len(passwd)))
67 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
68 db=db, connect_timeout=timeout)
69 self.cursor = self.conn.cursor()
70 self.run('select fieldid from fielddefs where name = "longdesc"')
71 ids = self.cursor.fetchall()
72 if len(ids) != 1:
73 raise util.Abort(_('unknown database schema'))
74 self.longdesc_id = ids[0][0]
75 self.user_ids = {}
76
77 def run(self, *args, **kwargs):
78 '''run a query.'''
79 self.ui.note(_('query: %s %s\n') % (args, kwargs))
80 try:
81 self.cursor.execute(*args, **kwargs)
82 except MySQLdb.MySQLError, err:
83 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
84 raise
85
86 def filter_real_bug_ids(self, ids):
87 '''filter not-existing bug ids from list.'''
88 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
89 ids = [c[0] for c in self.cursor.fetchall()]
90 ids.sort()
91 return ids
92
93 def filter_unknown_bug_ids(self, node, ids):
94 '''filter bug ids from list that already refer to this changeset.'''
95
96 self.run('''select bug_id from longdescs where
97 bug_id in %s and thetext like "%%%s%%"''' %
98 (buglist(ids), short(node)))
99 unknown = dict.fromkeys(ids)
100 for (id,) in self.cursor.fetchall():
101 self.ui.status(_('bug %d already knows about changeset %s\n') %
102 (id, short(node)))
103 unknown.pop(id, None)
104 ids = unknown.keys()
105 ids.sort()
106 return ids
107
108 def notify(self, ids):
109 '''tell bugzilla to send mail.'''
110
111 self.ui.status(_('telling bugzilla to send mail:\n'))
112 for id in ids:
113 self.ui.status(_(' bug %s\n') % id)
114 cmd = self.ui.config('bugzilla', 'notify',
115 'cd /var/www/html/bugzilla && '
116 './processmail %s nobody@nowhere.com') % id
117 fp = os.popen('(%s) 2>&1' % cmd)
118 out = fp.read()
119 ret = fp.close()
120 if ret:
121 self.ui.warn(out)
122 raise util.Abort(_('bugzilla notify command %s') %
123 util.explain_exit(ret)[0])
124 self.ui.status(_('done\n'))
125
126 def get_user_id(self, user):
127 '''look up numeric bugzilla user id.'''
128 try:
129 return self.user_ids[user]
130 except KeyError:
131 try:
132 userid = int(user)
133 except ValueError:
134 self.ui.note(_('looking up user %s\n') % user)
135 self.run('''select userid from profiles
136 where login_name like %s''', user)
137 all = self.cursor.fetchall()
138 if len(all) != 1:
139 raise KeyError(user)
140 userid = int(all[0][0])
141 self.user_ids[user] = userid
142 return userid
143
144 def add_comment(self, bugid, text, prefuser):
145 '''add comment to bug. try adding comment as committer of
146 changeset, otherwise as default bugzilla user.'''
147 try:
148 userid = self.get_user_id(prefuser)
149 except KeyError:
150 try:
151 defaultuser = self.ui.config('bugzilla', 'bzuser')
152 userid = self.get_user_id(defaultuser)
153 except KeyError:
154 raise util.Abort(_('cannot find user id for %s or %s') %
155 (prefuser, defaultuser))
156 now = time.strftime('%Y-%m-%d %H:%M:%S')
157 self.run('''insert into longdescs
158 (bug_id, who, bug_when, thetext)
159 values (%s, %s, %s, %s)''',
160 (bugid, userid, now, text))
161 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
162 values (%s, %s, %s, %s)''',
163 (bugid, userid, now, self.longdesc_id))
164
165 class bugzilla(object):
166 # supported versions of bugzilla. different versions have
167 # different schemas.
168 _versions = {
169 '2.16': bugzilla_2_16,
170 }
171
172 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
173 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
174
175 _bz = None
176
177 def __init__(self, ui, repo):
178 self.ui = ui
179 self.repo = repo
180
181 def bz(self):
182 '''return object that knows how to talk to bugzilla version in
183 use.'''
184
185 if bugzilla._bz is None:
186 bzversion = self.ui.config('bugzilla', 'version')
187 try:
188 bzclass = bugzilla._versions[bzversion]
189 except KeyError:
190 raise util.Abort(_('bugzilla version %s not supported') %
191 bzversion)
192 bugzilla._bz = bzclass(self.ui)
193 return bugzilla._bz
194
195 def __getattr__(self, key):
196 return getattr(self.bz(), key)
197
198 _bug_re = None
199 _split_re = None
200
201 def find_bug_ids(self, node, desc):
202 '''find valid bug ids that are referred to in changeset
203 comments and that do not already have references to this
204 changeset.'''
205
206 if bugzilla._bug_re is None:
207 bugzilla._bug_re = re.compile(
208 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
209 re.IGNORECASE)
210 bugzilla._split_re = re.compile(r'\D+')
211 start = 0
212 ids = {}
213 while True:
214 m = bugzilla._bug_re.search(desc, start)
215 if not m:
216 break
217 start = m.end()
218 for id in bugzilla._split_re.split(m.group(1)):
219 ids[int(id)] = 1
220 ids = ids.keys()
221 if ids:
222 ids = self.filter_real_bug_ids(ids)
223 if ids:
224 ids = self.filter_unknown_bug_ids(node, ids)
225 return ids
226
227 def update(self, bugid, node, changes):
228 '''update bugzilla bug with reference to changeset.'''
229
230 def webroot(root):
231 '''strip leading prefix of repo root and turn into
232 url-safe path.'''
233 count = int(self.ui.config('bugzilla', 'strip', 0))
234 root = util.pconvert(root)
235 while count > 0:
236 c = root.find('/')
237 if c == -1:
238 break
239 root = root[c+1:]
240 count -= 1
241 return root
242
243 class stringio(object):
244 '''wrap cStringIO.'''
245 def __init__(self):
246 self.fp = cStringIO.StringIO()
247
248 def write(self, *args):
249 for a in args:
250 self.fp.write(a)
251
252 write_header = write
253
254 def getvalue(self):
255 return self.fp.getvalue()
256
257 mapfile = self.ui.config('bugzilla', 'style')
258 tmpl = self.ui.config('bugzilla', 'template')
259 sio = stringio()
260 t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
261 if not mapfile and not tmpl:
262 tmpl = _('changeset {node|short} in repo {root} refers '
263 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
264 if tmpl:
265 tmpl = templater.parsestring(tmpl, quoted=False)
266 t.use_template(tmpl)
267 t.show(changenode=node, changes=changes,
268 bug=str(bugid),
269 hgweb=self.ui.config('bugzilla', 'hgweb'),
270 root=self.repo.root,
271 webroot=webroot(self.repo.root))
272 self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))
273
274 def hook(ui, repo, hooktype, node=None, **kwargs):
275 '''add comment to bugzilla for each changeset that refers to a
276 bugzilla bug id. only add a comment once per bug, so same change
277 seen multiple times does not fill bug with duplicate data.'''
278 if node is None:
279 raise util.Abort(_('hook type %s does not pass a changeset id') %
280 hooktype)
281 try:
282 bz = bugzilla(ui, repo)
283 bin_node = bin(node)
284 changes = repo.changelog.read(bin_node)
285 ids = bz.find_bug_ids(bin_node, changes[4])
286 if ids:
287 for id in ids:
288 bz.update(id, bin_node, changes)
289 bz.notify(ids)
290 return True
291 except MySQLdb.MySQLError, err:
292 raise util.Abort(_('database error: %s') % err[1])
293
@@ -398,194 +398,6 b' def trimuser(ui, name, rev, revcache):'
398 user = revcache[rev] = ui.shortuser(name)
398 user = revcache[rev] = ui.shortuser(name)
399 return user
399 return user
400
400
401 class changeset_templater(object):
402 '''use templater module to format changeset information.'''
403
404 def __init__(self, ui, repo, mapfile):
405 self.t = templater.templater(mapfile, templater.common_filters,
406 cache={'parent': '{rev}:{node|short} ',
407 'manifest': '{rev}:{node|short}'})
408 self.ui = ui
409 self.repo = repo
410
411 def use_template(self, t):
412 '''set template string to use'''
413 self.t.cache['changeset'] = t
414
415 def write(self, thing, header=False):
416 '''write expanded template.
417 uses in-order recursive traverse of iterators.'''
418 for t in thing:
419 if hasattr(t, '__iter__'):
420 self.write(t, header=header)
421 elif header:
422 self.ui.write_header(t)
423 else:
424 self.ui.write(t)
425
426 def write_header(self, thing):
427 self.write(thing, header=True)
428
429 def show(self, rev=0, changenode=None, brinfo=None):
430 '''show a single changeset or file revision'''
431 log = self.repo.changelog
432 if changenode is None:
433 changenode = log.node(rev)
434 elif not rev:
435 rev = log.rev(changenode)
436
437 changes = log.read(changenode)
438
439 def showlist(name, values, plural=None, **args):
440 '''expand set of values.
441 name is name of key in template map.
442 values is list of strings or dicts.
443 plural is plural of name, if not simply name + 's'.
444
445 expansion works like this, given name 'foo'.
446
447 if values is empty, expand 'no_foos'.
448
449 if 'foo' not in template map, return values as a string,
450 joined by space.
451
452 expand 'start_foos'.
453
454 for each value, expand 'foo'. if 'last_foo' in template
455 map, expand it instead of 'foo' for last key.
456
457 expand 'end_foos'.
458 '''
459 if plural: names = plural
460 else: names = name + 's'
461 if not values:
462 noname = 'no_' + names
463 if noname in self.t:
464 yield self.t(noname, **args)
465 return
466 if name not in self.t:
467 if isinstance(values[0], str):
468 yield ' '.join(values)
469 else:
470 for v in values:
471 yield dict(v, **args)
472 return
473 startname = 'start_' + names
474 if startname in self.t:
475 yield self.t(startname, **args)
476 vargs = args.copy()
477 def one(v, tag=name):
478 try:
479 vargs.update(v)
480 except (AttributeError, ValueError):
481 try:
482 for a, b in v:
483 vargs[a] = b
484 except ValueError:
485 vargs[name] = v
486 return self.t(tag, **vargs)
487 lastname = 'last_' + name
488 if lastname in self.t:
489 last = values.pop()
490 else:
491 last = None
492 for v in values:
493 yield one(v)
494 if last is not None:
495 yield one(last, tag=lastname)
496 endname = 'end_' + names
497 if endname in self.t:
498 yield self.t(endname, **args)
499
500 if brinfo:
501 def showbranches(**args):
502 if changenode in brinfo:
503 for x in showlist('branch', brinfo[changenode],
504 plural='branches', **args):
505 yield x
506 else:
507 showbranches = ''
508
509 if self.ui.debugflag:
510 def showmanifest(**args):
511 args = args.copy()
512 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
513 node=hex(changes[0])))
514 yield self.t('manifest', **args)
515 else:
516 showmanifest = ''
517
518 def showparents(**args):
519 parents = [[('rev', log.rev(p)), ('node', hex(p))]
520 for p in log.parents(changenode)
521 if self.ui.debugflag or p != nullid]
522 if (not self.ui.debugflag and len(parents) == 1 and
523 parents[0][0][1] == rev - 1):
524 return
525 for x in showlist('parent', parents, **args):
526 yield x
527
528 def showtags(**args):
529 for x in showlist('tag', self.repo.nodetags(changenode), **args):
530 yield x
531
532 if self.ui.debugflag:
533 files = self.repo.changes(log.parents(changenode)[0], changenode)
534 def showfiles(**args):
535 for x in showlist('file', files[0], **args): yield x
536 def showadds(**args):
537 for x in showlist('file_add', files[1], **args): yield x
538 def showdels(**args):
539 for x in showlist('file_del', files[2], **args): yield x
540 else:
541 def showfiles(**args):
542 for x in showlist('file', changes[3], **args): yield x
543 showadds = ''
544 showdels = ''
545
546 props = {
547 'author': changes[1],
548 'branches': showbranches,
549 'date': changes[2],
550 'desc': changes[4],
551 'file_adds': showadds,
552 'file_dels': showdels,
553 'files': showfiles,
554 'manifest': showmanifest,
555 'node': hex(changenode),
556 'parents': showparents,
557 'rev': rev,
558 'tags': showtags,
559 }
560
561 try:
562 if self.ui.debugflag and 'header_debug' in self.t:
563 key = 'header_debug'
564 elif self.ui.quiet and 'header_quiet' in self.t:
565 key = 'header_quiet'
566 elif self.ui.verbose and 'header_verbose' in self.t:
567 key = 'header_verbose'
568 elif 'header' in self.t:
569 key = 'header'
570 else:
571 key = ''
572 if key:
573 self.write_header(self.t(key, **props))
574 if self.ui.debugflag and 'changeset_debug' in self.t:
575 key = 'changeset_debug'
576 elif self.ui.quiet and 'changeset_quiet' in self.t:
577 key = 'changeset_quiet'
578 elif self.ui.verbose and 'changeset_verbose' in self.t:
579 key = 'changeset_verbose'
580 else:
581 key = 'changeset'
582 self.write(self.t(key, **props))
583 except KeyError, inst:
584 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
585 inst.args[0]))
586 except SyntaxError, inst:
587 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
588
589 class changeset_printer(object):
401 class changeset_printer(object):
590 '''show changeset information when templating not requested.'''
402 '''show changeset information when templating not requested.'''
591
403
@@ -672,7 +484,7 b' def show_changeset(ui, repo, opts):'
672 if not mapname: mapname = templater.templatepath(mapfile)
484 if not mapname: mapname = templater.templatepath(mapfile)
673 if mapname: mapfile = mapname
485 if mapname: mapfile = mapname
674 try:
486 try:
675 t = changeset_templater(ui, repo, mapfile)
487 t = templater.changeset_templater(ui, repo, mapfile)
676 except SyntaxError, inst:
488 except SyntaxError, inst:
677 raise util.Abort(inst.args[0])
489 raise util.Abort(inst.args[0])
678 if tmpl: t.use_template(tmpl)
490 if tmpl: t.use_template(tmpl)
@@ -105,7 +105,7 b' class localrepository(object):'
105 '("%s" is not callable)') %
105 '("%s" is not callable)') %
106 (hname, funcname))
106 (hname, funcname))
107 try:
107 try:
108 r = obj(ui=ui, repo=repo, hooktype=name, **args)
108 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
109 except (KeyboardInterrupt, util.SignalInterrupt):
109 except (KeyboardInterrupt, util.SignalInterrupt):
110 raise
110 raise
111 except Exception, exc:
111 except Exception, exc:
@@ -8,6 +8,7 b''
8 import re
8 import re
9 from demandload import demandload
9 from demandload import demandload
10 from i18n import gettext as _
10 from i18n import gettext as _
11 from node import *
11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
12 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
12
13
13 esctable = {
14 esctable = {
@@ -209,7 +210,7 b' def fill(text, width):'
209 break
210 break
210 yield text[start:m.start(0)], m.group(1)
211 yield text[start:m.start(0)], m.group(1)
211 start = m.end(1)
212 start = m.end(1)
212
213
213 fp = cStringIO.StringIO()
214 fp = cStringIO.StringIO()
214 for para, rest in findparas():
215 for para, rest in findparas():
215 fp.write(space_re.sub(' ', textwrap.fill(para, width)))
216 fp.write(space_re.sub(' ', textwrap.fill(para, width)))
@@ -241,7 +242,7 b' def email(author):'
241 r = author.find('>')
242 r = author.find('>')
242 if r == -1: r = None
243 if r == -1: r = None
243 return author[author.find('<')+1:r]
244 return author[author.find('<')+1:r]
244
245
245 def person(author):
246 def person(author):
246 '''get name of author, or else username.'''
247 '''get name of author, or else username.'''
247 f = author.find('<')
248 f = author.find('<')
@@ -267,6 +268,7 b' def indent(text, prefix):'
267
268
268 common_filters = {
269 common_filters = {
269 "addbreaks": nl2br,
270 "addbreaks": nl2br,
271 "basename": os.path.basename,
270 "age": age,
272 "age": age,
271 "date": lambda x: util.datestr(x),
273 "date": lambda x: util.datestr(x),
272 "domain": domain,
274 "domain": domain,
@@ -292,6 +294,7 b' common_filters = {'
292 def templatepath(name=None):
294 def templatepath(name=None):
293 '''return location of template file or directory (if no name).
295 '''return location of template file or directory (if no name).
294 returns None if not found.'''
296 returns None if not found.'''
297
295 # executable version (py2exe) doesn't support __file__
298 # executable version (py2exe) doesn't support __file__
296 if hasattr(sys, 'frozen'):
299 if hasattr(sys, 'frozen'):
297 module = sys.executable
300 module = sys.executable
@@ -303,3 +306,196 b' def templatepath(name=None):'
303 p = os.path.join(os.path.dirname(module), *fl)
306 p = os.path.join(os.path.dirname(module), *fl)
304 if (name and os.path.exists(p)) or os.path.isdir(p):
307 if (name and os.path.exists(p)) or os.path.isdir(p):
305 return os.path.normpath(p)
308 return os.path.normpath(p)
309
310 class changeset_templater(object):
311 '''format changeset information.'''
312
313 def __init__(self, ui, repo, mapfile, dest=None):
314 self.t = templater(mapfile, common_filters,
315 cache={'parent': '{rev}:{node|short} ',
316 'manifest': '{rev}:{node|short}'})
317 self.ui = ui
318 self.dest = dest
319 self.repo = repo
320
321 def use_template(self, t):
322 '''set template string to use'''
323 self.t.cache['changeset'] = t
324
325 def write(self, thing, header=False):
326 '''write expanded template.
327 uses in-order recursive traverse of iterators.'''
328 dest = self.dest or self.ui
329 for t in thing:
330 if hasattr(t, '__iter__'):
331 self.write(t, header=header)
332 elif header:
333 dest.write_header(t)
334 else:
335 dest.write(t)
336
337 def write_header(self, thing):
338 self.write(thing, header=True)
339
340 def show(self, rev=0, changenode=None, brinfo=None, changes=None,
341 **props):
342 '''show a single changeset or file revision'''
343 log = self.repo.changelog
344 if changenode is None:
345 changenode = log.node(rev)
346 elif not rev:
347 rev = log.rev(changenode)
348 if changes is None:
349 changes = log.read(changenode)
350
351 def showlist(name, values, plural=None, **args):
352 '''expand set of values.
353 name is name of key in template map.
354 values is list of strings or dicts.
355 plural is plural of name, if not simply name + 's'.
356
357 expansion works like this, given name 'foo'.
358
359 if values is empty, expand 'no_foos'.
360
361 if 'foo' not in template map, return values as a string,
362 joined by space.
363
364 expand 'start_foos'.
365
366 for each value, expand 'foo'. if 'last_foo' in template
367 map, expand it instead of 'foo' for last key.
368
369 expand 'end_foos'.
370 '''
371 if plural: names = plural
372 else: names = name + 's'
373 if not values:
374 noname = 'no_' + names
375 if noname in self.t:
376 yield self.t(noname, **args)
377 return
378 if name not in self.t:
379 if isinstance(values[0], str):
380 yield ' '.join(values)
381 else:
382 for v in values:
383 yield dict(v, **args)
384 return
385 startname = 'start_' + names
386 if startname in self.t:
387 yield self.t(startname, **args)
388 vargs = args.copy()
389 def one(v, tag=name):
390 try:
391 vargs.update(v)
392 except (AttributeError, ValueError):
393 try:
394 for a, b in v:
395 vargs[a] = b
396 except ValueError:
397 vargs[name] = v
398 return self.t(tag, **vargs)
399 lastname = 'last_' + name
400 if lastname in self.t:
401 last = values.pop()
402 else:
403 last = None
404 for v in values:
405 yield one(v)
406 if last is not None:
407 yield one(last, tag=lastname)
408 endname = 'end_' + names
409 if endname in self.t:
410 yield self.t(endname, **args)
411
412 if brinfo:
413 def showbranches(**args):
414 if changenode in brinfo:
415 for x in showlist('branch', brinfo[changenode],
416 plural='branches', **args):
417 yield x
418 else:
419 showbranches = ''
420
421 if self.ui.debugflag:
422 def showmanifest(**args):
423 args = args.copy()
424 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
425 node=hex(changes[0])))
426 yield self.t('manifest', **args)
427 else:
428 showmanifest = ''
429
430 def showparents(**args):
431 parents = [[('rev', log.rev(p)), ('node', hex(p))]
432 for p in log.parents(changenode)
433 if self.ui.debugflag or p != nullid]
434 if (not self.ui.debugflag and len(parents) == 1 and
435 parents[0][0][1] == rev - 1):
436 return
437 for x in showlist('parent', parents, **args):
438 yield x
439
440 def showtags(**args):
441 for x in showlist('tag', self.repo.nodetags(changenode), **args):
442 yield x
443
444 if self.ui.debugflag:
445 files = self.repo.changes(log.parents(changenode)[0], changenode)
446 def showfiles(**args):
447 for x in showlist('file', files[0], **args): yield x
448 def showadds(**args):
449 for x in showlist('file_add', files[1], **args): yield x
450 def showdels(**args):
451 for x in showlist('file_del', files[2], **args): yield x
452 else:
453 def showfiles(**args):
454 for x in showlist('file', changes[3], **args): yield x
455 showadds = ''
456 showdels = ''
457
458 defprops = {
459 'author': changes[1],
460 'branches': showbranches,
461 'date': changes[2],
462 'desc': changes[4],
463 'file_adds': showadds,
464 'file_dels': showdels,
465 'files': showfiles,
466 'manifest': showmanifest,
467 'node': hex(changenode),
468 'parents': showparents,
469 'rev': rev,
470 'tags': showtags,
471 }
472 props = props.copy()
473 props.update(defprops)
474
475 try:
476 if self.ui.debugflag and 'header_debug' in self.t:
477 key = 'header_debug'
478 elif self.ui.quiet and 'header_quiet' in self.t:
479 key = 'header_quiet'
480 elif self.ui.verbose and 'header_verbose' in self.t:
481 key = 'header_verbose'
482 elif 'header' in self.t:
483 key = 'header'
484 else:
485 key = ''
486 if key:
487 self.write_header(self.t(key, **props))
488 if self.ui.debugflag and 'changeset_debug' in self.t:
489 key = 'changeset_debug'
490 elif self.ui.quiet and 'changeset_quiet' in self.t:
491 key = 'changeset_quiet'
492 elif self.ui.verbose and 'changeset_verbose' in self.t:
493 key = 'changeset_verbose'
494 else:
495 key = 'changeset'
496 self.write(self.t(key, **props))
497 except KeyError, inst:
498 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
499 inst.args[0]))
500 except SyntaxError, inst:
501 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
@@ -231,7 +231,7 b' def canonpath(root, cwd, myname):'
231 name_st = os.stat(name)
231 name_st = os.stat(name)
232 except OSError:
232 except OSError:
233 break
233 break
234 if os.path.samestat(name_st, root_st):
234 if samestat(name_st, root_st):
235 rel.reverse()
235 rel.reverse()
236 name = os.path.join(*rel)
236 name = os.path.join(*rel)
237 audit_path(name)
237 audit_path(name)
@@ -561,6 +561,9 b" if os.name == 'nt':"
561 makelock = _makelock_file
561 makelock = _makelock_file
562 readlock = _readlock_file
562 readlock = _readlock_file
563
563
564 def samestat(s1, s2):
565 return False
566
564 def explain_exit(code):
567 def explain_exit(code):
565 return _("exited with status %d") % code, code
568 return _("exited with status %d") % code, code
566
569
@@ -627,6 +630,7 b' else:'
627 return path
630 return path
628
631
629 normpath = os.path.normpath
632 normpath = os.path.normpath
633 samestat = os.path.samestat
630
634
631 def makelock(info, pathname):
635 def makelock(info, pathname):
632 try:
636 try:
General Comments 0
You need to be logged in to leave comments. Login now