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