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