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