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