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