##// END OF EJS Templates
Make changeset_printer respect ui diffopts
Benoit Boissinot -
r4714:a7412937 default
parent child Browse files
Show More
@@ -1,1236 +1,1237
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 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 11 import mdiff, bdiff, util, templater, patch, commands, hg, lock, time
12 12 import fancyopts, revlog, version, extensions, hook
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, argv0=None):
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, argv0=argv0)
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 ImportError, inst:
119 119 m = str(inst).split()[-1]
120 120 ui.warn(_("abort: could not import module %s!\n" % m))
121 121 if m in "mpatch bdiff".split():
122 122 ui.warn(_("(did you forget to compile extensions?)\n"))
123 123 elif m in "zlib".split():
124 124 ui.warn(_("(is your Python install correct?)\n"))
125 125
126 126 except util.Abort, inst:
127 127 ui.warn(_("abort: %s\n") % inst)
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, argv0=None):
259 259 # remember how to call 'hg' before changing the working dir
260 260 util.set_hgexecutable(argv0)
261 261
262 262 # check for cwd first
263 263 cwd = earlygetopt(['--cwd'], args)
264 264 if cwd:
265 265 os.chdir(cwd)
266 266
267 267 # read the local repository .hgrc into a local ui object
268 268 path = findrepo() or ""
269 269 if not path:
270 270 lui = ui
271 271 if path:
272 272 try:
273 273 lui = commands.ui.ui(parentui=ui)
274 274 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
275 275 except IOError:
276 276 pass
277 277
278 278 # now we can expand paths, even ones in .hg/hgrc
279 279 rpath = earlygetopt(["-R", "--repository", "--repo"], args)
280 280 if rpath:
281 281 path = lui.expandpath(rpath)
282 282 lui = commands.ui.ui(parentui=ui)
283 283 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
284 284
285 285 extensions.loadall(lui)
286 286 # check for fallback encoding
287 287 fallback = lui.config('ui', 'fallbackencoding')
288 288 if fallback:
289 289 util._fallbackencoding = fallback
290 290
291 291 fullargs = args
292 292 cmd, func, args, options, cmdoptions = parse(ui, args)
293 293
294 294 if options["encoding"]:
295 295 util._encoding = options["encoding"]
296 296 if options["encodingmode"]:
297 297 util._encodingmode = options["encodingmode"]
298 298 if options["time"]:
299 299 def get_times():
300 300 t = os.times()
301 301 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
302 302 t = (t[0], t[1], t[2], t[3], time.clock())
303 303 return t
304 304 s = get_times()
305 305 def print_time():
306 306 t = get_times()
307 307 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
308 308 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
309 309 atexit.register(print_time)
310 310
311 311 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
312 312 not options["noninteractive"], options["traceback"],
313 313 parseconfig(options["config"]))
314 314
315 315 if options['help']:
316 316 return commands.help_(ui, cmd, options['version'])
317 317 elif options['version']:
318 318 return commands.version_(ui)
319 319 elif not cmd:
320 320 return commands.help_(ui, 'shortlist')
321 321
322 322 repo = None
323 323 if cmd not in commands.norepo.split():
324 324 try:
325 325 repo = hg.repository(ui, path=path)
326 326 ui = repo.ui
327 327 if not repo.local():
328 328 raise util.Abort(_("repository '%s' is not local") % path)
329 329 except hg.RepoError:
330 330 if cmd not in commands.optionalrepo.split():
331 331 if not path:
332 332 raise hg.RepoError(_("There is no Mercurial repository here"
333 333 " (.hg not found)"))
334 334 raise
335 335 d = lambda: func(ui, repo, *args, **cmdoptions)
336 336 else:
337 337 d = lambda: func(ui, *args, **cmdoptions)
338 338
339 339 # run pre-hook, and abort if it fails
340 340 ret = hook.hook(ui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
341 341 if ret:
342 342 return ret
343 343 ret = runcommand(ui, options, cmd, d)
344 344 # run post-hook, passing command result
345 345 hook.hook(ui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
346 346 result = ret)
347 347 return ret
348 348
349 349 def runcommand(ui, options, cmd, cmdfunc):
350 350 def checkargs():
351 351 try:
352 352 return cmdfunc()
353 353 except TypeError, inst:
354 354 # was this an argument error?
355 355 tb = traceback.extract_tb(sys.exc_info()[2])
356 356 if len(tb) != 2: # no
357 357 raise
358 358 raise ParseError(cmd, _("invalid arguments"))
359 359
360 360 if options['profile']:
361 361 import hotshot, hotshot.stats
362 362 prof = hotshot.Profile("hg.prof")
363 363 try:
364 364 try:
365 365 return prof.runcall(checkargs)
366 366 except:
367 367 try:
368 368 ui.warn(_('exception raised - generating '
369 369 'profile anyway\n'))
370 370 except:
371 371 pass
372 372 raise
373 373 finally:
374 374 prof.close()
375 375 stats = hotshot.stats.load("hg.prof")
376 376 stats.strip_dirs()
377 377 stats.sort_stats('time', 'calls')
378 378 stats.print_stats(40)
379 379 elif options['lsprof']:
380 380 try:
381 381 from mercurial import lsprof
382 382 except ImportError:
383 383 raise util.Abort(_(
384 384 'lsprof not available - install from '
385 385 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
386 386 p = lsprof.Profiler()
387 387 p.enable(subcalls=True)
388 388 try:
389 389 return checkargs()
390 390 finally:
391 391 p.disable()
392 392 stats = lsprof.Stats(p.getstats())
393 393 stats.sort()
394 394 stats.pprint(top=10, file=sys.stderr, climit=5)
395 395 else:
396 396 return checkargs()
397 397
398 398 def bail_if_changed(repo):
399 399 modified, added, removed, deleted = repo.status()[:4]
400 400 if modified or added or removed or deleted:
401 401 raise util.Abort(_("outstanding uncommitted changes"))
402 402
403 403 def logmessage(opts):
404 404 """ get the log message according to -m and -l option """
405 405 message = opts['message']
406 406 logfile = opts['logfile']
407 407
408 408 if message and logfile:
409 409 raise util.Abort(_('options --message and --logfile are mutually '
410 410 'exclusive'))
411 411 if not message and logfile:
412 412 try:
413 413 if logfile == '-':
414 414 message = sys.stdin.read()
415 415 else:
416 416 message = open(logfile).read()
417 417 except IOError, inst:
418 418 raise util.Abort(_("can't read commit message '%s': %s") %
419 419 (logfile, inst.strerror))
420 420 return message
421 421
422 422 def setremoteconfig(ui, opts):
423 423 "copy remote options to ui tree"
424 424 if opts.get('ssh'):
425 425 ui.setconfig("ui", "ssh", opts['ssh'])
426 426 if opts.get('remotecmd'):
427 427 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
428 428
429 429 def parseurl(url, revs):
430 430 '''parse url#branch, returning url, branch + revs'''
431 431
432 432 if '#' not in url:
433 433 return url, (revs or None)
434 434
435 435 url, rev = url.split('#', 1)
436 436 return url, revs + [rev]
437 437
438 438 def revpair(repo, revs):
439 439 '''return pair of nodes, given list of revisions. second item can
440 440 be None, meaning use working dir.'''
441 441
442 442 def revfix(repo, val, defval):
443 443 if not val and val != 0 and defval is not None:
444 444 val = defval
445 445 return repo.lookup(val)
446 446
447 447 if not revs:
448 448 return repo.dirstate.parents()[0], None
449 449 end = None
450 450 if len(revs) == 1:
451 451 if revrangesep in revs[0]:
452 452 start, end = revs[0].split(revrangesep, 1)
453 453 start = revfix(repo, start, 0)
454 454 end = revfix(repo, end, repo.changelog.count() - 1)
455 455 else:
456 456 start = revfix(repo, revs[0], None)
457 457 elif len(revs) == 2:
458 458 if revrangesep in revs[0] or revrangesep in revs[1]:
459 459 raise util.Abort(_('too many revisions specified'))
460 460 start = revfix(repo, revs[0], None)
461 461 end = revfix(repo, revs[1], None)
462 462 else:
463 463 raise util.Abort(_('too many revisions specified'))
464 464 return start, end
465 465
466 466 def revrange(repo, revs):
467 467 """Yield revision as strings from a list of revision specifications."""
468 468
469 469 def revfix(repo, val, defval):
470 470 if not val and val != 0 and defval is not None:
471 471 return defval
472 472 return repo.changelog.rev(repo.lookup(val))
473 473
474 474 seen, l = {}, []
475 475 for spec in revs:
476 476 if revrangesep in spec:
477 477 start, end = spec.split(revrangesep, 1)
478 478 start = revfix(repo, start, 0)
479 479 end = revfix(repo, end, repo.changelog.count() - 1)
480 480 step = start > end and -1 or 1
481 481 for rev in xrange(start, end+step, step):
482 482 if rev in seen:
483 483 continue
484 484 seen[rev] = 1
485 485 l.append(rev)
486 486 else:
487 487 rev = revfix(repo, spec, None)
488 488 if rev in seen:
489 489 continue
490 490 seen[rev] = 1
491 491 l.append(rev)
492 492
493 493 return l
494 494
495 495 def make_filename(repo, pat, node,
496 496 total=None, seqno=None, revwidth=None, pathname=None):
497 497 node_expander = {
498 498 'H': lambda: hex(node),
499 499 'R': lambda: str(repo.changelog.rev(node)),
500 500 'h': lambda: short(node),
501 501 }
502 502 expander = {
503 503 '%': lambda: '%',
504 504 'b': lambda: os.path.basename(repo.root),
505 505 }
506 506
507 507 try:
508 508 if node:
509 509 expander.update(node_expander)
510 510 if node and revwidth is not None:
511 511 expander['r'] = (lambda:
512 512 str(repo.changelog.rev(node)).zfill(revwidth))
513 513 if total is not None:
514 514 expander['N'] = lambda: str(total)
515 515 if seqno is not None:
516 516 expander['n'] = lambda: str(seqno)
517 517 if total is not None and seqno is not None:
518 518 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
519 519 if pathname is not None:
520 520 expander['s'] = lambda: os.path.basename(pathname)
521 521 expander['d'] = lambda: os.path.dirname(pathname) or '.'
522 522 expander['p'] = lambda: pathname
523 523
524 524 newname = []
525 525 patlen = len(pat)
526 526 i = 0
527 527 while i < patlen:
528 528 c = pat[i]
529 529 if c == '%':
530 530 i += 1
531 531 c = pat[i]
532 532 c = expander[c]()
533 533 newname.append(c)
534 534 i += 1
535 535 return ''.join(newname)
536 536 except KeyError, inst:
537 537 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
538 538 inst.args[0])
539 539
540 540 def make_file(repo, pat, node=None,
541 541 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
542 542 if not pat or pat == '-':
543 543 return 'w' in mode and sys.stdout or sys.stdin
544 544 if hasattr(pat, 'write') and 'w' in mode:
545 545 return pat
546 546 if hasattr(pat, 'read') and 'r' in mode:
547 547 return pat
548 548 return open(make_filename(repo, pat, node, total, seqno, revwidth,
549 549 pathname),
550 550 mode)
551 551
552 552 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
553 553 cwd = repo.getcwd()
554 554 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
555 555 opts.get('exclude'), globbed=globbed,
556 556 default=default)
557 557
558 558 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
559 559 default=None):
560 560 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
561 561 default=default)
562 562 exact = dict.fromkeys(files)
563 563 cwd = repo.getcwd()
564 564 for src, fn in repo.walk(node=node, files=files, match=matchfn,
565 565 badmatch=badmatch):
566 566 yield src, fn, repo.pathto(fn, cwd), fn in exact
567 567
568 568 def findrenames(repo, added=None, removed=None, threshold=0.5):
569 569 '''find renamed files -- yields (before, after, score) tuples'''
570 570 if added is None or removed is None:
571 571 added, removed = repo.status()[1:3]
572 572 ctx = repo.changectx()
573 573 for a in added:
574 574 aa = repo.wread(a)
575 575 bestname, bestscore = None, threshold
576 576 for r in removed:
577 577 rr = ctx.filectx(r).data()
578 578
579 579 # bdiff.blocks() returns blocks of matching lines
580 580 # count the number of bytes in each
581 581 equal = 0
582 582 alines = mdiff.splitnewlines(aa)
583 583 matches = bdiff.blocks(aa, rr)
584 584 for x1,x2,y1,y2 in matches:
585 585 for line in alines[x1:x2]:
586 586 equal += len(line)
587 587
588 588 lengths = len(aa) + len(rr)
589 589 if lengths:
590 590 myscore = equal*2.0 / lengths
591 591 if myscore >= bestscore:
592 592 bestname, bestscore = r, myscore
593 593 if bestname:
594 594 yield bestname, a, bestscore
595 595
596 596 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
597 597 similarity=None):
598 598 if dry_run is None:
599 599 dry_run = opts.get('dry_run')
600 600 if similarity is None:
601 601 similarity = float(opts.get('similarity') or 0)
602 602 add, remove = [], []
603 603 mapping = {}
604 604 for src, abs, rel, exact in walk(repo, pats, opts):
605 605 target = repo.wjoin(abs)
606 606 if src == 'f' and repo.dirstate.state(abs) == '?':
607 607 add.append(abs)
608 608 mapping[abs] = rel, exact
609 609 if repo.ui.verbose or not exact:
610 610 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
611 611 if repo.dirstate.state(abs) != 'r' and not util.lexists(target):
612 612 remove.append(abs)
613 613 mapping[abs] = rel, exact
614 614 if repo.ui.verbose or not exact:
615 615 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
616 616 if not dry_run:
617 617 repo.add(add, wlock=wlock)
618 618 repo.remove(remove, wlock=wlock)
619 619 if similarity > 0:
620 620 for old, new, score in findrenames(repo, add, remove, similarity):
621 621 oldrel, oldexact = mapping[old]
622 622 newrel, newexact = mapping[new]
623 623 if repo.ui.verbose or not oldexact or not newexact:
624 624 repo.ui.status(_('recording removal of %s as rename to %s '
625 625 '(%d%% similar)\n') %
626 626 (oldrel, newrel, score * 100))
627 627 if not dry_run:
628 628 repo.copy(old, new, wlock=wlock)
629 629
630 630 def service(opts, parentfn=None, initfn=None, runfn=None):
631 631 '''Run a command as a service.'''
632 632
633 633 if opts['daemon'] and not opts['daemon_pipefds']:
634 634 rfd, wfd = os.pipe()
635 635 args = sys.argv[:]
636 636 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
637 637 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
638 638 args[0], args)
639 639 os.close(wfd)
640 640 os.read(rfd, 1)
641 641 if parentfn:
642 642 return parentfn(pid)
643 643 else:
644 644 os._exit(0)
645 645
646 646 if initfn:
647 647 initfn()
648 648
649 649 if opts['pid_file']:
650 650 fp = open(opts['pid_file'], 'w')
651 651 fp.write(str(os.getpid()) + '\n')
652 652 fp.close()
653 653
654 654 if opts['daemon_pipefds']:
655 655 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
656 656 os.close(rfd)
657 657 try:
658 658 os.setsid()
659 659 except AttributeError:
660 660 pass
661 661 os.write(wfd, 'y')
662 662 os.close(wfd)
663 663 sys.stdout.flush()
664 664 sys.stderr.flush()
665 665 fd = os.open(util.nulldev, os.O_RDWR)
666 666 if fd != 0: os.dup2(fd, 0)
667 667 if fd != 1: os.dup2(fd, 1)
668 668 if fd != 2: os.dup2(fd, 2)
669 669 if fd not in (0, 1, 2): os.close(fd)
670 670
671 671 if runfn:
672 672 return runfn()
673 673
674 674 class changeset_printer(object):
675 675 '''show changeset information when templating not requested.'''
676 676
677 677 def __init__(self, ui, repo, patch, buffered):
678 678 self.ui = ui
679 679 self.repo = repo
680 680 self.buffered = buffered
681 681 self.patch = patch
682 682 self.header = {}
683 683 self.hunk = {}
684 684 self.lastheader = None
685 685
686 686 def flush(self, rev):
687 687 if rev in self.header:
688 688 h = self.header[rev]
689 689 if h != self.lastheader:
690 690 self.lastheader = h
691 691 self.ui.write(h)
692 692 del self.header[rev]
693 693 if rev in self.hunk:
694 694 self.ui.write(self.hunk[rev])
695 695 del self.hunk[rev]
696 696 return 1
697 697 return 0
698 698
699 699 def show(self, rev=0, changenode=None, copies=(), **props):
700 700 if self.buffered:
701 701 self.ui.pushbuffer()
702 702 self._show(rev, changenode, copies, props)
703 703 self.hunk[rev] = self.ui.popbuffer()
704 704 else:
705 705 self._show(rev, changenode, copies, props)
706 706
707 707 def _show(self, rev, changenode, copies, props):
708 708 '''show a single changeset or file revision'''
709 709 log = self.repo.changelog
710 710 if changenode is None:
711 711 changenode = log.node(rev)
712 712 elif not rev:
713 713 rev = log.rev(changenode)
714 714
715 715 if self.ui.quiet:
716 716 self.ui.write("%d:%s\n" % (rev, short(changenode)))
717 717 return
718 718
719 719 changes = log.read(changenode)
720 720 date = util.datestr(changes[2])
721 721 extra = changes[5]
722 722 branch = extra.get("branch")
723 723
724 724 hexfunc = self.ui.debugflag and hex or short
725 725
726 726 parents = log.parentrevs(rev)
727 727 if not self.ui.debugflag:
728 728 if parents[1] == nullrev:
729 729 if parents[0] >= rev - 1:
730 730 parents = []
731 731 else:
732 732 parents = [parents[0]]
733 733 parents = [(p, hexfunc(log.node(p))) for p in parents]
734 734
735 735 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
736 736
737 737 # don't show the default branch name
738 738 if branch != 'default':
739 739 branch = util.tolocal(branch)
740 740 self.ui.write(_("branch: %s\n") % branch)
741 741 for tag in self.repo.nodetags(changenode):
742 742 self.ui.write(_("tag: %s\n") % tag)
743 743 for parent in parents:
744 744 self.ui.write(_("parent: %d:%s\n") % parent)
745 745
746 746 if self.ui.debugflag:
747 747 self.ui.write(_("manifest: %d:%s\n") %
748 748 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
749 749 self.ui.write(_("user: %s\n") % changes[1])
750 750 self.ui.write(_("date: %s\n") % date)
751 751
752 752 if self.ui.debugflag:
753 753 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
754 754 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
755 755 files):
756 756 if value:
757 757 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
758 758 elif changes[3] and self.ui.verbose:
759 759 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
760 760 if copies and self.ui.verbose:
761 761 copies = ['%s (%s)' % c for c in copies]
762 762 self.ui.write(_("copies: %s\n") % ' '.join(copies))
763 763
764 764 if extra and self.ui.debugflag:
765 765 extraitems = extra.items()
766 766 extraitems.sort()
767 767 for key, value in extraitems:
768 768 self.ui.write(_("extra: %s=%s\n")
769 769 % (key, value.encode('string_escape')))
770 770
771 771 description = changes[4].strip()
772 772 if description:
773 773 if self.ui.verbose:
774 774 self.ui.write(_("description:\n"))
775 775 self.ui.write(description)
776 776 self.ui.write("\n\n")
777 777 else:
778 778 self.ui.write(_("summary: %s\n") %
779 779 description.splitlines()[0])
780 780 self.ui.write("\n")
781 781
782 782 self.showpatch(changenode)
783 783
784 784 def showpatch(self, node):
785 785 if self.patch:
786 786 prev = self.repo.changelog.parents(node)[0]
787 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
787 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
788 opts=patch.diffopts(self.ui))
788 789 self.ui.write("\n")
789 790
790 791 class changeset_templater(changeset_printer):
791 792 '''format changeset information.'''
792 793
793 794 def __init__(self, ui, repo, patch, mapfile, buffered):
794 795 changeset_printer.__init__(self, ui, repo, patch, buffered)
795 796 filters = templater.common_filters.copy()
796 797 filters['formatnode'] = (ui.debugflag and (lambda x: x)
797 798 or (lambda x: x[:12]))
798 799 self.t = templater.templater(mapfile, filters,
799 800 cache={
800 801 'parent': '{rev}:{node|formatnode} ',
801 802 'manifest': '{rev}:{node|formatnode}',
802 803 'filecopy': '{name} ({source})'})
803 804
804 805 def use_template(self, t):
805 806 '''set template string to use'''
806 807 self.t.cache['changeset'] = t
807 808
808 809 def _show(self, rev, changenode, copies, props):
809 810 '''show a single changeset or file revision'''
810 811 log = self.repo.changelog
811 812 if changenode is None:
812 813 changenode = log.node(rev)
813 814 elif not rev:
814 815 rev = log.rev(changenode)
815 816
816 817 changes = log.read(changenode)
817 818
818 819 def showlist(name, values, plural=None, **args):
819 820 '''expand set of values.
820 821 name is name of key in template map.
821 822 values is list of strings or dicts.
822 823 plural is plural of name, if not simply name + 's'.
823 824
824 825 expansion works like this, given name 'foo'.
825 826
826 827 if values is empty, expand 'no_foos'.
827 828
828 829 if 'foo' not in template map, return values as a string,
829 830 joined by space.
830 831
831 832 expand 'start_foos'.
832 833
833 834 for each value, expand 'foo'. if 'last_foo' in template
834 835 map, expand it instead of 'foo' for last key.
835 836
836 837 expand 'end_foos'.
837 838 '''
838 839 if plural: names = plural
839 840 else: names = name + 's'
840 841 if not values:
841 842 noname = 'no_' + names
842 843 if noname in self.t:
843 844 yield self.t(noname, **args)
844 845 return
845 846 if name not in self.t:
846 847 if isinstance(values[0], str):
847 848 yield ' '.join(values)
848 849 else:
849 850 for v in values:
850 851 yield dict(v, **args)
851 852 return
852 853 startname = 'start_' + names
853 854 if startname in self.t:
854 855 yield self.t(startname, **args)
855 856 vargs = args.copy()
856 857 def one(v, tag=name):
857 858 try:
858 859 vargs.update(v)
859 860 except (AttributeError, ValueError):
860 861 try:
861 862 for a, b in v:
862 863 vargs[a] = b
863 864 except ValueError:
864 865 vargs[name] = v
865 866 return self.t(tag, **vargs)
866 867 lastname = 'last_' + name
867 868 if lastname in self.t:
868 869 last = values.pop()
869 870 else:
870 871 last = None
871 872 for v in values:
872 873 yield one(v)
873 874 if last is not None:
874 875 yield one(last, tag=lastname)
875 876 endname = 'end_' + names
876 877 if endname in self.t:
877 878 yield self.t(endname, **args)
878 879
879 880 def showbranches(**args):
880 881 branch = changes[5].get("branch")
881 882 if branch != 'default':
882 883 branch = util.tolocal(branch)
883 884 return showlist('branch', [branch], plural='branches', **args)
884 885
885 886 def showparents(**args):
886 887 parents = [[('rev', log.rev(p)), ('node', hex(p))]
887 888 for p in log.parents(changenode)
888 889 if self.ui.debugflag or p != nullid]
889 890 if (not self.ui.debugflag and len(parents) == 1 and
890 891 parents[0][0][1] == rev - 1):
891 892 return
892 893 return showlist('parent', parents, **args)
893 894
894 895 def showtags(**args):
895 896 return showlist('tag', self.repo.nodetags(changenode), **args)
896 897
897 898 def showextras(**args):
898 899 extras = changes[5].items()
899 900 extras.sort()
900 901 for key, value in extras:
901 902 args = args.copy()
902 903 args.update(dict(key=key, value=value))
903 904 yield self.t('extra', **args)
904 905
905 906 def showcopies(**args):
906 907 c = [{'name': x[0], 'source': x[1]} for x in copies]
907 908 return showlist('file_copy', c, plural='file_copies', **args)
908 909
909 910 if self.ui.debugflag:
910 911 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
911 912 def showfiles(**args):
912 913 return showlist('file', files[0], **args)
913 914 def showadds(**args):
914 915 return showlist('file_add', files[1], **args)
915 916 def showdels(**args):
916 917 return showlist('file_del', files[2], **args)
917 918 def showmanifest(**args):
918 919 args = args.copy()
919 920 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
920 921 node=hex(changes[0])))
921 922 return self.t('manifest', **args)
922 923 else:
923 924 def showfiles(**args):
924 925 return showlist('file', changes[3], **args)
925 926 showadds = ''
926 927 showdels = ''
927 928 showmanifest = ''
928 929
929 930 defprops = {
930 931 'author': changes[1],
931 932 'branches': showbranches,
932 933 'date': changes[2],
933 934 'desc': changes[4],
934 935 'file_adds': showadds,
935 936 'file_dels': showdels,
936 937 'files': showfiles,
937 938 'file_copies': showcopies,
938 939 'manifest': showmanifest,
939 940 'node': hex(changenode),
940 941 'parents': showparents,
941 942 'rev': rev,
942 943 'tags': showtags,
943 944 'extras': showextras,
944 945 }
945 946 props = props.copy()
946 947 props.update(defprops)
947 948
948 949 try:
949 950 if self.ui.debugflag and 'header_debug' in self.t:
950 951 key = 'header_debug'
951 952 elif self.ui.quiet and 'header_quiet' in self.t:
952 953 key = 'header_quiet'
953 954 elif self.ui.verbose and 'header_verbose' in self.t:
954 955 key = 'header_verbose'
955 956 elif 'header' in self.t:
956 957 key = 'header'
957 958 else:
958 959 key = ''
959 960 if key:
960 961 h = templater.stringify(self.t(key, **props))
961 962 if self.buffered:
962 963 self.header[rev] = h
963 964 else:
964 965 self.ui.write(h)
965 966 if self.ui.debugflag and 'changeset_debug' in self.t:
966 967 key = 'changeset_debug'
967 968 elif self.ui.quiet and 'changeset_quiet' in self.t:
968 969 key = 'changeset_quiet'
969 970 elif self.ui.verbose and 'changeset_verbose' in self.t:
970 971 key = 'changeset_verbose'
971 972 else:
972 973 key = 'changeset'
973 974 self.ui.write(templater.stringify(self.t(key, **props)))
974 975 self.showpatch(changenode)
975 976 except KeyError, inst:
976 977 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
977 978 inst.args[0]))
978 979 except SyntaxError, inst:
979 980 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
980 981
981 982 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
982 983 """show one changeset using template or regular display.
983 984
984 985 Display format will be the first non-empty hit of:
985 986 1. option 'template'
986 987 2. option 'style'
987 988 3. [ui] setting 'logtemplate'
988 989 4. [ui] setting 'style'
989 990 If all of these values are either the unset or the empty string,
990 991 regular display via changeset_printer() is done.
991 992 """
992 993 # options
993 994 patch = False
994 995 if opts.get('patch'):
995 996 patch = matchfn or util.always
996 997
997 998 tmpl = opts.get('template')
998 999 mapfile = None
999 1000 if tmpl:
1000 1001 tmpl = templater.parsestring(tmpl, quoted=False)
1001 1002 else:
1002 1003 mapfile = opts.get('style')
1003 1004 # ui settings
1004 1005 if not mapfile:
1005 1006 tmpl = ui.config('ui', 'logtemplate')
1006 1007 if tmpl:
1007 1008 tmpl = templater.parsestring(tmpl)
1008 1009 else:
1009 1010 mapfile = ui.config('ui', 'style')
1010 1011
1011 1012 if tmpl or mapfile:
1012 1013 if mapfile:
1013 1014 if not os.path.split(mapfile)[0]:
1014 1015 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1015 1016 or templater.templatepath(mapfile))
1016 1017 if mapname: mapfile = mapname
1017 1018 try:
1018 1019 t = changeset_templater(ui, repo, patch, mapfile, buffered)
1019 1020 except SyntaxError, inst:
1020 1021 raise util.Abort(inst.args[0])
1021 1022 if tmpl: t.use_template(tmpl)
1022 1023 return t
1023 1024 return changeset_printer(ui, repo, patch, buffered)
1024 1025
1025 1026 def finddate(ui, repo, date):
1026 1027 """Find the tipmost changeset that matches the given date spec"""
1027 1028 df = util.matchdate(date + " to " + date)
1028 1029 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1029 1030 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
1030 1031 results = {}
1031 1032 for st, rev, fns in changeiter:
1032 1033 if st == 'add':
1033 1034 d = get(rev)[2]
1034 1035 if df(d[0]):
1035 1036 results[rev] = d
1036 1037 elif st == 'iter':
1037 1038 if rev in results:
1038 1039 ui.status("Found revision %s from %s\n" %
1039 1040 (rev, util.datestr(results[rev])))
1040 1041 return str(rev)
1041 1042
1042 1043 raise util.Abort(_("revision matching date not found"))
1043 1044
1044 1045 def walkchangerevs(ui, repo, pats, change, opts):
1045 1046 '''Iterate over files and the revs they changed in.
1046 1047
1047 1048 Callers most commonly need to iterate backwards over the history
1048 1049 it is interested in. Doing so has awful (quadratic-looking)
1049 1050 performance, so we use iterators in a "windowed" way.
1050 1051
1051 1052 We walk a window of revisions in the desired order. Within the
1052 1053 window, we first walk forwards to gather data, then in the desired
1053 1054 order (usually backwards) to display it.
1054 1055
1055 1056 This function returns an (iterator, matchfn) tuple. The iterator
1056 1057 yields 3-tuples. They will be of one of the following forms:
1057 1058
1058 1059 "window", incrementing, lastrev: stepping through a window,
1059 1060 positive if walking forwards through revs, last rev in the
1060 1061 sequence iterated over - use to reset state for the current window
1061 1062
1062 1063 "add", rev, fns: out-of-order traversal of the given file names
1063 1064 fns, which changed during revision rev - use to gather data for
1064 1065 possible display
1065 1066
1066 1067 "iter", rev, None: in-order traversal of the revs earlier iterated
1067 1068 over with "add" - use to display data'''
1068 1069
1069 1070 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1070 1071 if start < end:
1071 1072 while start < end:
1072 1073 yield start, min(windowsize, end-start)
1073 1074 start += windowsize
1074 1075 if windowsize < sizelimit:
1075 1076 windowsize *= 2
1076 1077 else:
1077 1078 while start > end:
1078 1079 yield start, min(windowsize, start-end-1)
1079 1080 start -= windowsize
1080 1081 if windowsize < sizelimit:
1081 1082 windowsize *= 2
1082 1083
1083 1084 files, matchfn, anypats = matchpats(repo, pats, opts)
1084 1085 follow = opts.get('follow') or opts.get('follow_first')
1085 1086
1086 1087 if repo.changelog.count() == 0:
1087 1088 return [], matchfn
1088 1089
1089 1090 if follow:
1090 1091 defrange = '%s:0' % repo.changectx().rev()
1091 1092 else:
1092 1093 defrange = 'tip:0'
1093 1094 revs = revrange(repo, opts['rev'] or [defrange])
1094 1095 wanted = {}
1095 1096 slowpath = anypats or opts.get('removed')
1096 1097 fncache = {}
1097 1098
1098 1099 if not slowpath and not files:
1099 1100 # No files, no patterns. Display all revs.
1100 1101 wanted = dict.fromkeys(revs)
1101 1102 copies = []
1102 1103 if not slowpath:
1103 1104 # Only files, no patterns. Check the history of each file.
1104 1105 def filerevgen(filelog, node):
1105 1106 cl_count = repo.changelog.count()
1106 1107 if node is None:
1107 1108 last = filelog.count() - 1
1108 1109 else:
1109 1110 last = filelog.rev(node)
1110 1111 for i, window in increasing_windows(last, nullrev):
1111 1112 revs = []
1112 1113 for j in xrange(i - window, i + 1):
1113 1114 n = filelog.node(j)
1114 1115 revs.append((filelog.linkrev(n),
1115 1116 follow and filelog.renamed(n)))
1116 1117 revs.reverse()
1117 1118 for rev in revs:
1118 1119 # only yield rev for which we have the changelog, it can
1119 1120 # happen while doing "hg log" during a pull or commit
1120 1121 if rev[0] < cl_count:
1121 1122 yield rev
1122 1123 def iterfiles():
1123 1124 for filename in files:
1124 1125 yield filename, None
1125 1126 for filename_node in copies:
1126 1127 yield filename_node
1127 1128 minrev, maxrev = min(revs), max(revs)
1128 1129 for file_, node in iterfiles():
1129 1130 filelog = repo.file(file_)
1130 1131 # A zero count may be a directory or deleted file, so
1131 1132 # try to find matching entries on the slow path.
1132 1133 if filelog.count() == 0:
1133 1134 slowpath = True
1134 1135 break
1135 1136 for rev, copied in filerevgen(filelog, node):
1136 1137 if rev <= maxrev:
1137 1138 if rev < minrev:
1138 1139 break
1139 1140 fncache.setdefault(rev, [])
1140 1141 fncache[rev].append(file_)
1141 1142 wanted[rev] = 1
1142 1143 if follow and copied:
1143 1144 copies.append(copied)
1144 1145 if slowpath:
1145 1146 if follow:
1146 1147 raise util.Abort(_('can only follow copies/renames for explicit '
1147 1148 'file names'))
1148 1149
1149 1150 # The slow path checks files modified in every changeset.
1150 1151 def changerevgen():
1151 1152 for i, window in increasing_windows(repo.changelog.count()-1,
1152 1153 nullrev):
1153 1154 for j in xrange(i - window, i + 1):
1154 1155 yield j, change(j)[3]
1155 1156
1156 1157 for rev, changefiles in changerevgen():
1157 1158 matches = filter(matchfn, changefiles)
1158 1159 if matches:
1159 1160 fncache[rev] = matches
1160 1161 wanted[rev] = 1
1161 1162
1162 1163 class followfilter:
1163 1164 def __init__(self, onlyfirst=False):
1164 1165 self.startrev = nullrev
1165 1166 self.roots = []
1166 1167 self.onlyfirst = onlyfirst
1167 1168
1168 1169 def match(self, rev):
1169 1170 def realparents(rev):
1170 1171 if self.onlyfirst:
1171 1172 return repo.changelog.parentrevs(rev)[0:1]
1172 1173 else:
1173 1174 return filter(lambda x: x != nullrev,
1174 1175 repo.changelog.parentrevs(rev))
1175 1176
1176 1177 if self.startrev == nullrev:
1177 1178 self.startrev = rev
1178 1179 return True
1179 1180
1180 1181 if rev > self.startrev:
1181 1182 # forward: all descendants
1182 1183 if not self.roots:
1183 1184 self.roots.append(self.startrev)
1184 1185 for parent in realparents(rev):
1185 1186 if parent in self.roots:
1186 1187 self.roots.append(rev)
1187 1188 return True
1188 1189 else:
1189 1190 # backwards: all parents
1190 1191 if not self.roots:
1191 1192 self.roots.extend(realparents(self.startrev))
1192 1193 if rev in self.roots:
1193 1194 self.roots.remove(rev)
1194 1195 self.roots.extend(realparents(rev))
1195 1196 return True
1196 1197
1197 1198 return False
1198 1199
1199 1200 # it might be worthwhile to do this in the iterator if the rev range
1200 1201 # is descending and the prune args are all within that range
1201 1202 for rev in opts.get('prune', ()):
1202 1203 rev = repo.changelog.rev(repo.lookup(rev))
1203 1204 ff = followfilter()
1204 1205 stop = min(revs[0], revs[-1])
1205 1206 for x in xrange(rev, stop-1, -1):
1206 1207 if ff.match(x) and x in wanted:
1207 1208 del wanted[x]
1208 1209
1209 1210 def iterate():
1210 1211 if follow and not files:
1211 1212 ff = followfilter(onlyfirst=opts.get('follow_first'))
1212 1213 def want(rev):
1213 1214 if ff.match(rev) and rev in wanted:
1214 1215 return True
1215 1216 return False
1216 1217 else:
1217 1218 def want(rev):
1218 1219 return rev in wanted
1219 1220
1220 1221 for i, window in increasing_windows(0, len(revs)):
1221 1222 yield 'window', revs[0] < revs[-1], revs[-1]
1222 1223 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1223 1224 srevs = list(nrevs)
1224 1225 srevs.sort()
1225 1226 for rev in srevs:
1226 1227 fns = fncache.get(rev)
1227 1228 if not fns:
1228 1229 def fns_generator():
1229 1230 for f in change(rev)[3]:
1230 1231 if matchfn(f):
1231 1232 yield f
1232 1233 fns = fns_generator()
1233 1234 yield 'add', rev, fns
1234 1235 for rev in nrevs:
1235 1236 yield 'iter', rev, None
1236 1237 return iterate(), matchfn
General Comments 0
You need to be logged in to leave comments. Login now