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