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