##// END OF EJS Templates
cmdutil: add missing "time" import.
Patrick Mezard -
r4598:b25ee3f8 default
parent child Browse files
Show More
@@ -1,1202 +1,1202 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex
11 import mdiff, bdiff, util, templater, patch, commands, hg, lock
11 import mdiff, bdiff, util, templater, patch, commands, hg, lock, time
12 12 import fancyopts, revlog, version, extensions
13 13
14 14 revrangesep = ':'
15 15
16 16 class UnknownCommand(Exception):
17 17 """Exception raised if command is not in the command table."""
18 18 class AmbiguousCommand(Exception):
19 19 """Exception raised if command shortcut matches more than one command."""
20 20 class ParseError(Exception):
21 21 """Exception raised on errors in parsing the command line."""
22 22
23 23 def runcatch(ui, args):
24 24 def catchterm(*args):
25 25 raise util.SignalInterrupt
26 26
27 27 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
28 28 num = getattr(signal, name, None)
29 29 if num: signal.signal(num, catchterm)
30 30
31 31 try:
32 32 try:
33 33 # enter the debugger before command execution
34 34 if '--debugger' in args:
35 35 pdb.set_trace()
36 36 try:
37 37 return dispatch(ui, args)
38 38 finally:
39 39 ui.flush()
40 40 except:
41 41 # enter the debugger when we hit an exception
42 42 if '--debugger' in args:
43 43 pdb.post_mortem(sys.exc_info()[2])
44 44 ui.print_exc()
45 45 raise
46 46
47 47 except ParseError, inst:
48 48 if inst.args[0]:
49 49 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
50 50 commands.help_(ui, inst.args[0])
51 51 else:
52 52 ui.warn(_("hg: %s\n") % inst.args[1])
53 53 commands.help_(ui, 'shortlist')
54 54 except AmbiguousCommand, inst:
55 55 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
56 56 (inst.args[0], " ".join(inst.args[1])))
57 57 except UnknownCommand, inst:
58 58 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
59 59 commands.help_(ui, 'shortlist')
60 60 except hg.RepoError, inst:
61 61 ui.warn(_("abort: %s!\n") % inst)
62 62 except lock.LockHeld, inst:
63 63 if inst.errno == errno.ETIMEDOUT:
64 64 reason = _('timed out waiting for lock held by %s') % inst.locker
65 65 else:
66 66 reason = _('lock held by %s') % inst.locker
67 67 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
68 68 except lock.LockUnavailable, inst:
69 69 ui.warn(_("abort: could not lock %s: %s\n") %
70 70 (inst.desc or inst.filename, inst.strerror))
71 71 except revlog.RevlogError, inst:
72 72 ui.warn(_("abort: %s!\n") % inst)
73 73 except util.SignalInterrupt:
74 74 ui.warn(_("killed!\n"))
75 75 except KeyboardInterrupt:
76 76 try:
77 77 ui.warn(_("interrupted!\n"))
78 78 except IOError, inst:
79 79 if inst.errno == errno.EPIPE:
80 80 if ui.debugflag:
81 81 ui.warn(_("\nbroken pipe\n"))
82 82 else:
83 83 raise
84 84 except socket.error, inst:
85 85 ui.warn(_("abort: %s\n") % inst[1])
86 86 except IOError, inst:
87 87 if hasattr(inst, "code"):
88 88 ui.warn(_("abort: %s\n") % inst)
89 89 elif hasattr(inst, "reason"):
90 90 try: # usually it is in the form (errno, strerror)
91 91 reason = inst.reason.args[1]
92 92 except: # it might be anything, for example a string
93 93 reason = inst.reason
94 94 ui.warn(_("abort: error: %s\n") % reason)
95 95 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
96 96 if ui.debugflag:
97 97 ui.warn(_("broken pipe\n"))
98 98 elif getattr(inst, "strerror", None):
99 99 if getattr(inst, "filename", None):
100 100 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
101 101 else:
102 102 ui.warn(_("abort: %s\n") % inst.strerror)
103 103 else:
104 104 raise
105 105 except OSError, inst:
106 106 if getattr(inst, "filename", None):
107 107 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
108 108 else:
109 109 ui.warn(_("abort: %s\n") % inst.strerror)
110 110 except util.UnexpectedOutput, inst:
111 111 ui.warn(_("abort: %s") % inst[0])
112 112 if not isinstance(inst[1], basestring):
113 113 ui.warn(" %r\n" % (inst[1],))
114 114 elif not inst[1]:
115 115 ui.warn(_(" empty string\n"))
116 116 else:
117 117 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
118 118 except util.Abort, inst:
119 119 ui.warn(_("abort: %s\n") % inst)
120 120 except TypeError, inst:
121 121 # was this an argument error?
122 122 tb = traceback.extract_tb(sys.exc_info()[2])
123 123 if len(tb) > 2: # no
124 124 raise
125 125 ui.debug(inst, "\n")
126 126 ui.warn(_("%s: invalid arguments\n") % cmd)
127 127 commands.help_(ui, cmd)
128 128 except SystemExit, inst:
129 129 # Commands shouldn't sys.exit directly, but give a return code.
130 130 # Just in case catch this and and pass exit code to caller.
131 131 return inst.code
132 132 except:
133 133 ui.warn(_("** unknown exception encountered, details follow\n"))
134 134 ui.warn(_("** report bug details to "
135 135 "http://www.selenic.com/mercurial/bts\n"))
136 136 ui.warn(_("** or mercurial@selenic.com\n"))
137 137 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
138 138 % version.get_version())
139 139 raise
140 140
141 141 return -1
142 142
143 143 def findpossible(ui, cmd):
144 144 """
145 145 Return cmd -> (aliases, command table entry)
146 146 for each matching command.
147 147 Return debug commands (or their aliases) only if no normal command matches.
148 148 """
149 149 choice = {}
150 150 debugchoice = {}
151 151 for e in commands.table.keys():
152 152 aliases = e.lstrip("^").split("|")
153 153 found = None
154 154 if cmd in aliases:
155 155 found = cmd
156 156 elif not ui.config("ui", "strict"):
157 157 for a in aliases:
158 158 if a.startswith(cmd):
159 159 found = a
160 160 break
161 161 if found is not None:
162 162 if aliases[0].startswith("debug") or found.startswith("debug"):
163 163 debugchoice[found] = (aliases, commands.table[e])
164 164 else:
165 165 choice[found] = (aliases, commands.table[e])
166 166
167 167 if not choice and debugchoice:
168 168 choice = debugchoice
169 169
170 170 return choice
171 171
172 172 def findcmd(ui, cmd):
173 173 """Return (aliases, command table entry) for command string."""
174 174 choice = findpossible(ui, cmd)
175 175
176 176 if choice.has_key(cmd):
177 177 return choice[cmd]
178 178
179 179 if len(choice) > 1:
180 180 clist = choice.keys()
181 181 clist.sort()
182 182 raise AmbiguousCommand(cmd, clist)
183 183
184 184 if choice:
185 185 return choice.values()[0]
186 186
187 187 raise UnknownCommand(cmd)
188 188
189 189 def findrepo():
190 190 p = os.getcwd()
191 191 while not os.path.isdir(os.path.join(p, ".hg")):
192 192 oldp, p = p, os.path.dirname(p)
193 193 if p == oldp:
194 194 return None
195 195
196 196 return p
197 197
198 198 def parse(ui, args):
199 199 options = {}
200 200 cmdoptions = {}
201 201
202 202 try:
203 203 args = fancyopts.fancyopts(args, commands.globalopts, options)
204 204 except fancyopts.getopt.GetoptError, inst:
205 205 raise ParseError(None, inst)
206 206
207 207 if args:
208 208 cmd, args = args[0], args[1:]
209 209 aliases, i = findcmd(ui, cmd)
210 210 cmd = aliases[0]
211 211 defaults = ui.config("defaults", cmd)
212 212 if defaults:
213 213 args = shlex.split(defaults) + args
214 214 c = list(i[1])
215 215 else:
216 216 cmd = None
217 217 c = []
218 218
219 219 # combine global options into local
220 220 for o in commands.globalopts:
221 221 c.append((o[0], o[1], options[o[1]], o[3]))
222 222
223 223 try:
224 224 args = fancyopts.fancyopts(args, c, cmdoptions)
225 225 except fancyopts.getopt.GetoptError, inst:
226 226 raise ParseError(cmd, inst)
227 227
228 228 # separate global options back out
229 229 for o in commands.globalopts:
230 230 n = o[1]
231 231 options[n] = cmdoptions[n]
232 232 del cmdoptions[n]
233 233
234 234 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
235 235
236 236 def parseconfig(config):
237 237 """parse the --config options from the command line"""
238 238 parsed = []
239 239 for cfg in config:
240 240 try:
241 241 name, value = cfg.split('=', 1)
242 242 section, name = name.split('.', 1)
243 243 if not section or not name:
244 244 raise IndexError
245 245 parsed.append((section, name, value))
246 246 except (IndexError, ValueError):
247 247 raise util.Abort(_('malformed --config option: %s') % cfg)
248 248 return parsed
249 249
250 250 def earlygetopt(aliases, args):
251 251 if "--" in args:
252 252 args = args[:args.index("--")]
253 253 for opt in aliases:
254 254 if opt in args:
255 255 return args[args.index(opt) + 1]
256 256 return None
257 257
258 258 def dispatch(ui, args):
259 259 # check for cwd first
260 260 cwd = earlygetopt(['--cwd'], args)
261 261 if cwd:
262 262 os.chdir(cwd)
263 263
264 264 extensions.loadall(ui)
265 265 ui.addreadhook(extensions.loadall)
266 266
267 267 # read the local repository .hgrc into a local ui object
268 268 # this will trigger its extensions to load
269 269 path = earlygetopt(["-R", "--repository", "--repo"], args)
270 270 if not path:
271 271 path = findrepo() or ""
272 272 if path:
273 273 try:
274 274 lui = commands.ui.ui(parentui=ui)
275 275 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
276 276 except IOError:
277 277 pass
278 278
279 279 cmd, func, args, options, cmdoptions = parse(ui, args)
280 280
281 281 if options["encoding"]:
282 282 util._encoding = options["encoding"]
283 283 if options["encodingmode"]:
284 284 util._encodingmode = options["encodingmode"]
285 285 if options["time"]:
286 286 def get_times():
287 287 t = os.times()
288 288 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
289 289 t = (t[0], t[1], t[2], t[3], time.clock())
290 290 return t
291 291 s = get_times()
292 292 def print_time():
293 293 t = get_times()
294 294 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
295 295 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
296 296 atexit.register(print_time)
297 297
298 298 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
299 299 not options["noninteractive"], options["traceback"],
300 300 parseconfig(options["config"]))
301 301
302 302 if options['help']:
303 303 return commands.help_(ui, cmd, options['version'])
304 304 elif options['version']:
305 305 return commands.version_(ui)
306 306 elif not cmd:
307 307 return commands.help_(ui, 'shortlist')
308 308
309 309 if cmd not in commands.norepo.split():
310 310 repo = None
311 311 try:
312 312 repo = hg.repository(ui, path=path)
313 313 ui = repo.ui
314 314 if not repo.local():
315 315 raise util.Abort(_("repository '%s' is not local") % path)
316 316 except hg.RepoError:
317 317 if cmd not in commands.optionalrepo.split():
318 318 raise
319 319 d = lambda: func(ui, repo, *args, **cmdoptions)
320 320 else:
321 321 d = lambda: func(ui, *args, **cmdoptions)
322 322
323 323 return runcommand(ui, options, d)
324 324
325 325 def runcommand(ui, options, cmdfunc):
326 326 if options['profile']:
327 327 import hotshot, hotshot.stats
328 328 prof = hotshot.Profile("hg.prof")
329 329 try:
330 330 try:
331 331 return prof.runcall(cmdfunc)
332 332 except:
333 333 try:
334 334 ui.warn(_('exception raised - generating '
335 335 'profile anyway\n'))
336 336 except:
337 337 pass
338 338 raise
339 339 finally:
340 340 prof.close()
341 341 stats = hotshot.stats.load("hg.prof")
342 342 stats.strip_dirs()
343 343 stats.sort_stats('time', 'calls')
344 344 stats.print_stats(40)
345 345 elif options['lsprof']:
346 346 try:
347 347 from mercurial import lsprof
348 348 except ImportError:
349 349 raise util.Abort(_(
350 350 'lsprof not available - install from '
351 351 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
352 352 p = lsprof.Profiler()
353 353 p.enable(subcalls=True)
354 354 try:
355 355 return cmdfunc()
356 356 finally:
357 357 p.disable()
358 358 stats = lsprof.Stats(p.getstats())
359 359 stats.sort()
360 360 stats.pprint(top=10, file=sys.stderr, climit=5)
361 361 else:
362 362 return cmdfunc()
363 363
364 364 def bail_if_changed(repo):
365 365 modified, added, removed, deleted = repo.status()[:4]
366 366 if modified or added or removed or deleted:
367 367 raise util.Abort(_("outstanding uncommitted changes"))
368 368
369 369 def logmessage(opts):
370 370 """ get the log message according to -m and -l option """
371 371 message = opts['message']
372 372 logfile = opts['logfile']
373 373
374 374 if message and logfile:
375 375 raise util.Abort(_('options --message and --logfile are mutually '
376 376 'exclusive'))
377 377 if not message and logfile:
378 378 try:
379 379 if logfile == '-':
380 380 message = sys.stdin.read()
381 381 else:
382 382 message = open(logfile).read()
383 383 except IOError, inst:
384 384 raise util.Abort(_("can't read commit message '%s': %s") %
385 385 (logfile, inst.strerror))
386 386 return message
387 387
388 388 def setremoteconfig(ui, opts):
389 389 "copy remote options to ui tree"
390 390 if opts.get('ssh'):
391 391 ui.setconfig("ui", "ssh", opts['ssh'])
392 392 if opts.get('remotecmd'):
393 393 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
394 394
395 395 def parseurl(url, revs):
396 396 '''parse url#branch, returning url, branch + revs'''
397 397
398 398 if '#' not in url:
399 399 return url, (revs or None)
400 400
401 401 url, rev = url.split('#', 1)
402 402 return url, revs + [rev]
403 403
404 404 def revpair(repo, revs):
405 405 '''return pair of nodes, given list of revisions. second item can
406 406 be None, meaning use working dir.'''
407 407
408 408 def revfix(repo, val, defval):
409 409 if not val and val != 0 and defval is not None:
410 410 val = defval
411 411 return repo.lookup(val)
412 412
413 413 if not revs:
414 414 return repo.dirstate.parents()[0], None
415 415 end = None
416 416 if len(revs) == 1:
417 417 if revrangesep in revs[0]:
418 418 start, end = revs[0].split(revrangesep, 1)
419 419 start = revfix(repo, start, 0)
420 420 end = revfix(repo, end, repo.changelog.count() - 1)
421 421 else:
422 422 start = revfix(repo, revs[0], None)
423 423 elif len(revs) == 2:
424 424 if revrangesep in revs[0] or revrangesep in revs[1]:
425 425 raise util.Abort(_('too many revisions specified'))
426 426 start = revfix(repo, revs[0], None)
427 427 end = revfix(repo, revs[1], None)
428 428 else:
429 429 raise util.Abort(_('too many revisions specified'))
430 430 return start, end
431 431
432 432 def revrange(repo, revs):
433 433 """Yield revision as strings from a list of revision specifications."""
434 434
435 435 def revfix(repo, val, defval):
436 436 if not val and val != 0 and defval is not None:
437 437 return defval
438 438 return repo.changelog.rev(repo.lookup(val))
439 439
440 440 seen, l = {}, []
441 441 for spec in revs:
442 442 if revrangesep in spec:
443 443 start, end = spec.split(revrangesep, 1)
444 444 start = revfix(repo, start, 0)
445 445 end = revfix(repo, end, repo.changelog.count() - 1)
446 446 step = start > end and -1 or 1
447 447 for rev in xrange(start, end+step, step):
448 448 if rev in seen:
449 449 continue
450 450 seen[rev] = 1
451 451 l.append(rev)
452 452 else:
453 453 rev = revfix(repo, spec, None)
454 454 if rev in seen:
455 455 continue
456 456 seen[rev] = 1
457 457 l.append(rev)
458 458
459 459 return l
460 460
461 461 def make_filename(repo, pat, node,
462 462 total=None, seqno=None, revwidth=None, pathname=None):
463 463 node_expander = {
464 464 'H': lambda: hex(node),
465 465 'R': lambda: str(repo.changelog.rev(node)),
466 466 'h': lambda: short(node),
467 467 }
468 468 expander = {
469 469 '%': lambda: '%',
470 470 'b': lambda: os.path.basename(repo.root),
471 471 }
472 472
473 473 try:
474 474 if node:
475 475 expander.update(node_expander)
476 476 if node and revwidth is not None:
477 477 expander['r'] = (lambda:
478 478 str(repo.changelog.rev(node)).zfill(revwidth))
479 479 if total is not None:
480 480 expander['N'] = lambda: str(total)
481 481 if seqno is not None:
482 482 expander['n'] = lambda: str(seqno)
483 483 if total is not None and seqno is not None:
484 484 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
485 485 if pathname is not None:
486 486 expander['s'] = lambda: os.path.basename(pathname)
487 487 expander['d'] = lambda: os.path.dirname(pathname) or '.'
488 488 expander['p'] = lambda: pathname
489 489
490 490 newname = []
491 491 patlen = len(pat)
492 492 i = 0
493 493 while i < patlen:
494 494 c = pat[i]
495 495 if c == '%':
496 496 i += 1
497 497 c = pat[i]
498 498 c = expander[c]()
499 499 newname.append(c)
500 500 i += 1
501 501 return ''.join(newname)
502 502 except KeyError, inst:
503 503 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
504 504 inst.args[0])
505 505
506 506 def make_file(repo, pat, node=None,
507 507 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
508 508 if not pat or pat == '-':
509 509 return 'w' in mode and sys.stdout or sys.stdin
510 510 if hasattr(pat, 'write') and 'w' in mode:
511 511 return pat
512 512 if hasattr(pat, 'read') and 'r' in mode:
513 513 return pat
514 514 return open(make_filename(repo, pat, node, total, seqno, revwidth,
515 515 pathname),
516 516 mode)
517 517
518 518 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
519 519 cwd = repo.getcwd()
520 520 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
521 521 opts.get('exclude'), globbed=globbed,
522 522 default=default)
523 523
524 524 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
525 525 default=None):
526 526 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
527 527 default=default)
528 528 exact = dict.fromkeys(files)
529 529 cwd = repo.getcwd()
530 530 for src, fn in repo.walk(node=node, files=files, match=matchfn,
531 531 badmatch=badmatch):
532 532 yield src, fn, repo.pathto(fn, cwd), fn in exact
533 533
534 534 def findrenames(repo, added=None, removed=None, threshold=0.5):
535 535 '''find renamed files -- yields (before, after, score) tuples'''
536 536 if added is None or removed is None:
537 537 added, removed = repo.status()[1:3]
538 538 ctx = repo.changectx()
539 539 for a in added:
540 540 aa = repo.wread(a)
541 541 bestname, bestscore = None, threshold
542 542 for r in removed:
543 543 rr = ctx.filectx(r).data()
544 544
545 545 # bdiff.blocks() returns blocks of matching lines
546 546 # count the number of bytes in each
547 547 equal = 0
548 548 alines = mdiff.splitnewlines(aa)
549 549 matches = bdiff.blocks(aa, rr)
550 550 for x1,x2,y1,y2 in matches:
551 551 for line in alines[x1:x2]:
552 552 equal += len(line)
553 553
554 554 lengths = len(aa) + len(rr)
555 555 if lengths:
556 556 myscore = equal*2.0 / lengths
557 557 if myscore >= bestscore:
558 558 bestname, bestscore = r, myscore
559 559 if bestname:
560 560 yield bestname, a, bestscore
561 561
562 562 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
563 563 similarity=None):
564 564 if dry_run is None:
565 565 dry_run = opts.get('dry_run')
566 566 if similarity is None:
567 567 similarity = float(opts.get('similarity') or 0)
568 568 add, remove = [], []
569 569 mapping = {}
570 570 for src, abs, rel, exact in walk(repo, pats, opts):
571 571 target = repo.wjoin(abs)
572 572 if src == 'f' and repo.dirstate.state(abs) == '?':
573 573 add.append(abs)
574 574 mapping[abs] = rel, exact
575 575 if repo.ui.verbose or not exact:
576 576 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
577 577 if repo.dirstate.state(abs) != 'r' and not util.lexists(target):
578 578 remove.append(abs)
579 579 mapping[abs] = rel, exact
580 580 if repo.ui.verbose or not exact:
581 581 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
582 582 if not dry_run:
583 583 repo.add(add, wlock=wlock)
584 584 repo.remove(remove, wlock=wlock)
585 585 if similarity > 0:
586 586 for old, new, score in findrenames(repo, add, remove, similarity):
587 587 oldrel, oldexact = mapping[old]
588 588 newrel, newexact = mapping[new]
589 589 if repo.ui.verbose or not oldexact or not newexact:
590 590 repo.ui.status(_('recording removal of %s as rename to %s '
591 591 '(%d%% similar)\n') %
592 592 (oldrel, newrel, score * 100))
593 593 if not dry_run:
594 594 repo.copy(old, new, wlock=wlock)
595 595
596 596 def service(opts, parentfn=None, initfn=None, runfn=None):
597 597 '''Run a command as a service.'''
598 598
599 599 if opts['daemon'] and not opts['daemon_pipefds']:
600 600 rfd, wfd = os.pipe()
601 601 args = sys.argv[:]
602 602 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
603 603 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
604 604 args[0], args)
605 605 os.close(wfd)
606 606 os.read(rfd, 1)
607 607 if parentfn:
608 608 return parentfn(pid)
609 609 else:
610 610 os._exit(0)
611 611
612 612 if initfn:
613 613 initfn()
614 614
615 615 if opts['pid_file']:
616 616 fp = open(opts['pid_file'], 'w')
617 617 fp.write(str(os.getpid()) + '\n')
618 618 fp.close()
619 619
620 620 if opts['daemon_pipefds']:
621 621 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
622 622 os.close(rfd)
623 623 try:
624 624 os.setsid()
625 625 except AttributeError:
626 626 pass
627 627 os.write(wfd, 'y')
628 628 os.close(wfd)
629 629 sys.stdout.flush()
630 630 sys.stderr.flush()
631 631 fd = os.open(util.nulldev, os.O_RDWR)
632 632 if fd != 0: os.dup2(fd, 0)
633 633 if fd != 1: os.dup2(fd, 1)
634 634 if fd != 2: os.dup2(fd, 2)
635 635 if fd not in (0, 1, 2): os.close(fd)
636 636
637 637 if runfn:
638 638 return runfn()
639 639
640 640 class changeset_printer(object):
641 641 '''show changeset information when templating not requested.'''
642 642
643 643 def __init__(self, ui, repo, patch, buffered):
644 644 self.ui = ui
645 645 self.repo = repo
646 646 self.buffered = buffered
647 647 self.patch = patch
648 648 self.header = {}
649 649 self.hunk = {}
650 650 self.lastheader = None
651 651
652 652 def flush(self, rev):
653 653 if rev in self.header:
654 654 h = self.header[rev]
655 655 if h != self.lastheader:
656 656 self.lastheader = h
657 657 self.ui.write(h)
658 658 del self.header[rev]
659 659 if rev in self.hunk:
660 660 self.ui.write(self.hunk[rev])
661 661 del self.hunk[rev]
662 662 return 1
663 663 return 0
664 664
665 665 def show(self, rev=0, changenode=None, copies=(), **props):
666 666 if self.buffered:
667 667 self.ui.pushbuffer()
668 668 self._show(rev, changenode, copies, props)
669 669 self.hunk[rev] = self.ui.popbuffer()
670 670 else:
671 671 self._show(rev, changenode, copies, props)
672 672
673 673 def _show(self, rev, changenode, copies, props):
674 674 '''show a single changeset or file revision'''
675 675 log = self.repo.changelog
676 676 if changenode is None:
677 677 changenode = log.node(rev)
678 678 elif not rev:
679 679 rev = log.rev(changenode)
680 680
681 681 if self.ui.quiet:
682 682 self.ui.write("%d:%s\n" % (rev, short(changenode)))
683 683 return
684 684
685 685 changes = log.read(changenode)
686 686 date = util.datestr(changes[2])
687 687 extra = changes[5]
688 688 branch = extra.get("branch")
689 689
690 690 hexfunc = self.ui.debugflag and hex or short
691 691
692 692 parents = log.parentrevs(rev)
693 693 if not self.ui.debugflag:
694 694 if parents[1] == nullrev:
695 695 if parents[0] >= rev - 1:
696 696 parents = []
697 697 else:
698 698 parents = [parents[0]]
699 699 parents = [(p, hexfunc(log.node(p))) for p in parents]
700 700
701 701 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
702 702
703 703 # don't show the default branch name
704 704 if branch != 'default':
705 705 branch = util.tolocal(branch)
706 706 self.ui.write(_("branch: %s\n") % branch)
707 707 for tag in self.repo.nodetags(changenode):
708 708 self.ui.write(_("tag: %s\n") % tag)
709 709 for parent in parents:
710 710 self.ui.write(_("parent: %d:%s\n") % parent)
711 711
712 712 if self.ui.debugflag:
713 713 self.ui.write(_("manifest: %d:%s\n") %
714 714 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
715 715 self.ui.write(_("user: %s\n") % changes[1])
716 716 self.ui.write(_("date: %s\n") % date)
717 717
718 718 if self.ui.debugflag:
719 719 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
720 720 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
721 721 files):
722 722 if value:
723 723 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
724 724 elif changes[3] and self.ui.verbose:
725 725 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
726 726 if copies and self.ui.verbose:
727 727 copies = ['%s (%s)' % c for c in copies]
728 728 self.ui.write(_("copies: %s\n") % ' '.join(copies))
729 729
730 730 if extra and self.ui.debugflag:
731 731 extraitems = extra.items()
732 732 extraitems.sort()
733 733 for key, value in extraitems:
734 734 self.ui.write(_("extra: %s=%s\n")
735 735 % (key, value.encode('string_escape')))
736 736
737 737 description = changes[4].strip()
738 738 if description:
739 739 if self.ui.verbose:
740 740 self.ui.write(_("description:\n"))
741 741 self.ui.write(description)
742 742 self.ui.write("\n\n")
743 743 else:
744 744 self.ui.write(_("summary: %s\n") %
745 745 description.splitlines()[0])
746 746 self.ui.write("\n")
747 747
748 748 self.showpatch(changenode)
749 749
750 750 def showpatch(self, node):
751 751 if self.patch:
752 752 prev = self.repo.changelog.parents(node)[0]
753 753 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
754 754 self.ui.write("\n")
755 755
756 756 class changeset_templater(changeset_printer):
757 757 '''format changeset information.'''
758 758
759 759 def __init__(self, ui, repo, patch, mapfile, buffered):
760 760 changeset_printer.__init__(self, ui, repo, patch, buffered)
761 761 filters = templater.common_filters.copy()
762 762 filters['formatnode'] = (ui.debugflag and (lambda x: x)
763 763 or (lambda x: x[:12]))
764 764 self.t = templater.templater(mapfile, filters,
765 765 cache={
766 766 'parent': '{rev}:{node|formatnode} ',
767 767 'manifest': '{rev}:{node|formatnode}',
768 768 'filecopy': '{name} ({source})'})
769 769
770 770 def use_template(self, t):
771 771 '''set template string to use'''
772 772 self.t.cache['changeset'] = t
773 773
774 774 def _show(self, rev, changenode, copies, props):
775 775 '''show a single changeset or file revision'''
776 776 log = self.repo.changelog
777 777 if changenode is None:
778 778 changenode = log.node(rev)
779 779 elif not rev:
780 780 rev = log.rev(changenode)
781 781
782 782 changes = log.read(changenode)
783 783
784 784 def showlist(name, values, plural=None, **args):
785 785 '''expand set of values.
786 786 name is name of key in template map.
787 787 values is list of strings or dicts.
788 788 plural is plural of name, if not simply name + 's'.
789 789
790 790 expansion works like this, given name 'foo'.
791 791
792 792 if values is empty, expand 'no_foos'.
793 793
794 794 if 'foo' not in template map, return values as a string,
795 795 joined by space.
796 796
797 797 expand 'start_foos'.
798 798
799 799 for each value, expand 'foo'. if 'last_foo' in template
800 800 map, expand it instead of 'foo' for last key.
801 801
802 802 expand 'end_foos'.
803 803 '''
804 804 if plural: names = plural
805 805 else: names = name + 's'
806 806 if not values:
807 807 noname = 'no_' + names
808 808 if noname in self.t:
809 809 yield self.t(noname, **args)
810 810 return
811 811 if name not in self.t:
812 812 if isinstance(values[0], str):
813 813 yield ' '.join(values)
814 814 else:
815 815 for v in values:
816 816 yield dict(v, **args)
817 817 return
818 818 startname = 'start_' + names
819 819 if startname in self.t:
820 820 yield self.t(startname, **args)
821 821 vargs = args.copy()
822 822 def one(v, tag=name):
823 823 try:
824 824 vargs.update(v)
825 825 except (AttributeError, ValueError):
826 826 try:
827 827 for a, b in v:
828 828 vargs[a] = b
829 829 except ValueError:
830 830 vargs[name] = v
831 831 return self.t(tag, **vargs)
832 832 lastname = 'last_' + name
833 833 if lastname in self.t:
834 834 last = values.pop()
835 835 else:
836 836 last = None
837 837 for v in values:
838 838 yield one(v)
839 839 if last is not None:
840 840 yield one(last, tag=lastname)
841 841 endname = 'end_' + names
842 842 if endname in self.t:
843 843 yield self.t(endname, **args)
844 844
845 845 def showbranches(**args):
846 846 branch = changes[5].get("branch")
847 847 if branch != 'default':
848 848 branch = util.tolocal(branch)
849 849 return showlist('branch', [branch], plural='branches', **args)
850 850
851 851 def showparents(**args):
852 852 parents = [[('rev', log.rev(p)), ('node', hex(p))]
853 853 for p in log.parents(changenode)
854 854 if self.ui.debugflag or p != nullid]
855 855 if (not self.ui.debugflag and len(parents) == 1 and
856 856 parents[0][0][1] == rev - 1):
857 857 return
858 858 return showlist('parent', parents, **args)
859 859
860 860 def showtags(**args):
861 861 return showlist('tag', self.repo.nodetags(changenode), **args)
862 862
863 863 def showextras(**args):
864 864 extras = changes[5].items()
865 865 extras.sort()
866 866 for key, value in extras:
867 867 args = args.copy()
868 868 args.update(dict(key=key, value=value))
869 869 yield self.t('extra', **args)
870 870
871 871 def showcopies(**args):
872 872 c = [{'name': x[0], 'source': x[1]} for x in copies]
873 873 return showlist('file_copy', c, plural='file_copies', **args)
874 874
875 875 if self.ui.debugflag:
876 876 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
877 877 def showfiles(**args):
878 878 return showlist('file', files[0], **args)
879 879 def showadds(**args):
880 880 return showlist('file_add', files[1], **args)
881 881 def showdels(**args):
882 882 return showlist('file_del', files[2], **args)
883 883 def showmanifest(**args):
884 884 args = args.copy()
885 885 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
886 886 node=hex(changes[0])))
887 887 return self.t('manifest', **args)
888 888 else:
889 889 def showfiles(**args):
890 890 return showlist('file', changes[3], **args)
891 891 showadds = ''
892 892 showdels = ''
893 893 showmanifest = ''
894 894
895 895 defprops = {
896 896 'author': changes[1],
897 897 'branches': showbranches,
898 898 'date': changes[2],
899 899 'desc': changes[4],
900 900 'file_adds': showadds,
901 901 'file_dels': showdels,
902 902 'files': showfiles,
903 903 'file_copies': showcopies,
904 904 'manifest': showmanifest,
905 905 'node': hex(changenode),
906 906 'parents': showparents,
907 907 'rev': rev,
908 908 'tags': showtags,
909 909 'extras': showextras,
910 910 }
911 911 props = props.copy()
912 912 props.update(defprops)
913 913
914 914 try:
915 915 if self.ui.debugflag and 'header_debug' in self.t:
916 916 key = 'header_debug'
917 917 elif self.ui.quiet and 'header_quiet' in self.t:
918 918 key = 'header_quiet'
919 919 elif self.ui.verbose and 'header_verbose' in self.t:
920 920 key = 'header_verbose'
921 921 elif 'header' in self.t:
922 922 key = 'header'
923 923 else:
924 924 key = ''
925 925 if key:
926 926 h = templater.stringify(self.t(key, **props))
927 927 if self.buffered:
928 928 self.header[rev] = h
929 929 else:
930 930 self.ui.write(h)
931 931 if self.ui.debugflag and 'changeset_debug' in self.t:
932 932 key = 'changeset_debug'
933 933 elif self.ui.quiet and 'changeset_quiet' in self.t:
934 934 key = 'changeset_quiet'
935 935 elif self.ui.verbose and 'changeset_verbose' in self.t:
936 936 key = 'changeset_verbose'
937 937 else:
938 938 key = 'changeset'
939 939 self.ui.write(templater.stringify(self.t(key, **props)))
940 940 self.showpatch(changenode)
941 941 except KeyError, inst:
942 942 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
943 943 inst.args[0]))
944 944 except SyntaxError, inst:
945 945 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
946 946
947 947 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
948 948 """show one changeset using template or regular display.
949 949
950 950 Display format will be the first non-empty hit of:
951 951 1. option 'template'
952 952 2. option 'style'
953 953 3. [ui] setting 'logtemplate'
954 954 4. [ui] setting 'style'
955 955 If all of these values are either the unset or the empty string,
956 956 regular display via changeset_printer() is done.
957 957 """
958 958 # options
959 959 patch = False
960 960 if opts.get('patch'):
961 961 patch = matchfn or util.always
962 962
963 963 tmpl = opts.get('template')
964 964 mapfile = None
965 965 if tmpl:
966 966 tmpl = templater.parsestring(tmpl, quoted=False)
967 967 else:
968 968 mapfile = opts.get('style')
969 969 # ui settings
970 970 if not mapfile:
971 971 tmpl = ui.config('ui', 'logtemplate')
972 972 if tmpl:
973 973 tmpl = templater.parsestring(tmpl)
974 974 else:
975 975 mapfile = ui.config('ui', 'style')
976 976
977 977 if tmpl or mapfile:
978 978 if mapfile:
979 979 if not os.path.split(mapfile)[0]:
980 980 mapname = (templater.templatepath('map-cmdline.' + mapfile)
981 981 or templater.templatepath(mapfile))
982 982 if mapname: mapfile = mapname
983 983 try:
984 984 t = changeset_templater(ui, repo, patch, mapfile, buffered)
985 985 except SyntaxError, inst:
986 986 raise util.Abort(inst.args[0])
987 987 if tmpl: t.use_template(tmpl)
988 988 return t
989 989 return changeset_printer(ui, repo, patch, buffered)
990 990
991 991 def finddate(ui, repo, date):
992 992 """Find the tipmost changeset that matches the given date spec"""
993 993 df = util.matchdate(date + " to " + date)
994 994 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
995 995 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
996 996 results = {}
997 997 for st, rev, fns in changeiter:
998 998 if st == 'add':
999 999 d = get(rev)[2]
1000 1000 if df(d[0]):
1001 1001 results[rev] = d
1002 1002 elif st == 'iter':
1003 1003 if rev in results:
1004 1004 ui.status("Found revision %s from %s\n" %
1005 1005 (rev, util.datestr(results[rev])))
1006 1006 return str(rev)
1007 1007
1008 1008 raise util.Abort(_("revision matching date not found"))
1009 1009
1010 1010 def walkchangerevs(ui, repo, pats, change, opts):
1011 1011 '''Iterate over files and the revs they changed in.
1012 1012
1013 1013 Callers most commonly need to iterate backwards over the history
1014 1014 it is interested in. Doing so has awful (quadratic-looking)
1015 1015 performance, so we use iterators in a "windowed" way.
1016 1016
1017 1017 We walk a window of revisions in the desired order. Within the
1018 1018 window, we first walk forwards to gather data, then in the desired
1019 1019 order (usually backwards) to display it.
1020 1020
1021 1021 This function returns an (iterator, matchfn) tuple. The iterator
1022 1022 yields 3-tuples. They will be of one of the following forms:
1023 1023
1024 1024 "window", incrementing, lastrev: stepping through a window,
1025 1025 positive if walking forwards through revs, last rev in the
1026 1026 sequence iterated over - use to reset state for the current window
1027 1027
1028 1028 "add", rev, fns: out-of-order traversal of the given file names
1029 1029 fns, which changed during revision rev - use to gather data for
1030 1030 possible display
1031 1031
1032 1032 "iter", rev, None: in-order traversal of the revs earlier iterated
1033 1033 over with "add" - use to display data'''
1034 1034
1035 1035 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1036 1036 if start < end:
1037 1037 while start < end:
1038 1038 yield start, min(windowsize, end-start)
1039 1039 start += windowsize
1040 1040 if windowsize < sizelimit:
1041 1041 windowsize *= 2
1042 1042 else:
1043 1043 while start > end:
1044 1044 yield start, min(windowsize, start-end-1)
1045 1045 start -= windowsize
1046 1046 if windowsize < sizelimit:
1047 1047 windowsize *= 2
1048 1048
1049 1049 files, matchfn, anypats = matchpats(repo, pats, opts)
1050 1050 follow = opts.get('follow') or opts.get('follow_first')
1051 1051
1052 1052 if repo.changelog.count() == 0:
1053 1053 return [], matchfn
1054 1054
1055 1055 if follow:
1056 1056 defrange = '%s:0' % repo.changectx().rev()
1057 1057 else:
1058 1058 defrange = 'tip:0'
1059 1059 revs = revrange(repo, opts['rev'] or [defrange])
1060 1060 wanted = {}
1061 1061 slowpath = anypats or opts.get('removed')
1062 1062 fncache = {}
1063 1063
1064 1064 if not slowpath and not files:
1065 1065 # No files, no patterns. Display all revs.
1066 1066 wanted = dict.fromkeys(revs)
1067 1067 copies = []
1068 1068 if not slowpath:
1069 1069 # Only files, no patterns. Check the history of each file.
1070 1070 def filerevgen(filelog, node):
1071 1071 cl_count = repo.changelog.count()
1072 1072 if node is None:
1073 1073 last = filelog.count() - 1
1074 1074 else:
1075 1075 last = filelog.rev(node)
1076 1076 for i, window in increasing_windows(last, nullrev):
1077 1077 revs = []
1078 1078 for j in xrange(i - window, i + 1):
1079 1079 n = filelog.node(j)
1080 1080 revs.append((filelog.linkrev(n),
1081 1081 follow and filelog.renamed(n)))
1082 1082 revs.reverse()
1083 1083 for rev in revs:
1084 1084 # only yield rev for which we have the changelog, it can
1085 1085 # happen while doing "hg log" during a pull or commit
1086 1086 if rev[0] < cl_count:
1087 1087 yield rev
1088 1088 def iterfiles():
1089 1089 for filename in files:
1090 1090 yield filename, None
1091 1091 for filename_node in copies:
1092 1092 yield filename_node
1093 1093 minrev, maxrev = min(revs), max(revs)
1094 1094 for file_, node in iterfiles():
1095 1095 filelog = repo.file(file_)
1096 1096 # A zero count may be a directory or deleted file, so
1097 1097 # try to find matching entries on the slow path.
1098 1098 if filelog.count() == 0:
1099 1099 slowpath = True
1100 1100 break
1101 1101 for rev, copied in filerevgen(filelog, node):
1102 1102 if rev <= maxrev:
1103 1103 if rev < minrev:
1104 1104 break
1105 1105 fncache.setdefault(rev, [])
1106 1106 fncache[rev].append(file_)
1107 1107 wanted[rev] = 1
1108 1108 if follow and copied:
1109 1109 copies.append(copied)
1110 1110 if slowpath:
1111 1111 if follow:
1112 1112 raise util.Abort(_('can only follow copies/renames for explicit '
1113 1113 'file names'))
1114 1114
1115 1115 # The slow path checks files modified in every changeset.
1116 1116 def changerevgen():
1117 1117 for i, window in increasing_windows(repo.changelog.count()-1,
1118 1118 nullrev):
1119 1119 for j in xrange(i - window, i + 1):
1120 1120 yield j, change(j)[3]
1121 1121
1122 1122 for rev, changefiles in changerevgen():
1123 1123 matches = filter(matchfn, changefiles)
1124 1124 if matches:
1125 1125 fncache[rev] = matches
1126 1126 wanted[rev] = 1
1127 1127
1128 1128 class followfilter:
1129 1129 def __init__(self, onlyfirst=False):
1130 1130 self.startrev = nullrev
1131 1131 self.roots = []
1132 1132 self.onlyfirst = onlyfirst
1133 1133
1134 1134 def match(self, rev):
1135 1135 def realparents(rev):
1136 1136 if self.onlyfirst:
1137 1137 return repo.changelog.parentrevs(rev)[0:1]
1138 1138 else:
1139 1139 return filter(lambda x: x != nullrev,
1140 1140 repo.changelog.parentrevs(rev))
1141 1141
1142 1142 if self.startrev == nullrev:
1143 1143 self.startrev = rev
1144 1144 return True
1145 1145
1146 1146 if rev > self.startrev:
1147 1147 # forward: all descendants
1148 1148 if not self.roots:
1149 1149 self.roots.append(self.startrev)
1150 1150 for parent in realparents(rev):
1151 1151 if parent in self.roots:
1152 1152 self.roots.append(rev)
1153 1153 return True
1154 1154 else:
1155 1155 # backwards: all parents
1156 1156 if not self.roots:
1157 1157 self.roots.extend(realparents(self.startrev))
1158 1158 if rev in self.roots:
1159 1159 self.roots.remove(rev)
1160 1160 self.roots.extend(realparents(rev))
1161 1161 return True
1162 1162
1163 1163 return False
1164 1164
1165 1165 # it might be worthwhile to do this in the iterator if the rev range
1166 1166 # is descending and the prune args are all within that range
1167 1167 for rev in opts.get('prune', ()):
1168 1168 rev = repo.changelog.rev(repo.lookup(rev))
1169 1169 ff = followfilter()
1170 1170 stop = min(revs[0], revs[-1])
1171 1171 for x in xrange(rev, stop-1, -1):
1172 1172 if ff.match(x) and x in wanted:
1173 1173 del wanted[x]
1174 1174
1175 1175 def iterate():
1176 1176 if follow and not files:
1177 1177 ff = followfilter(onlyfirst=opts.get('follow_first'))
1178 1178 def want(rev):
1179 1179 if ff.match(rev) and rev in wanted:
1180 1180 return True
1181 1181 return False
1182 1182 else:
1183 1183 def want(rev):
1184 1184 return rev in wanted
1185 1185
1186 1186 for i, window in increasing_windows(0, len(revs)):
1187 1187 yield 'window', revs[0] < revs[-1], revs[-1]
1188 1188 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1189 1189 srevs = list(nrevs)
1190 1190 srevs.sort()
1191 1191 for rev in srevs:
1192 1192 fns = fncache.get(rev)
1193 1193 if not fns:
1194 1194 def fns_generator():
1195 1195 for f in change(rev)[3]:
1196 1196 if matchfn(f):
1197 1197 yield f
1198 1198 fns = fns_generator()
1199 1199 yield 'add', rev, fns
1200 1200 for rev in nrevs:
1201 1201 yield 'iter', rev, None
1202 1202 return iterate(), matchfn
General Comments 0
You need to be logged in to leave comments. Login now