##// END OF EJS Templates
workingctx: eliminate remove function...
Adrian Buehlmann -
r14518:a67e866f default
parent child Browse files
Show More
@@ -1,1224 +1,1228
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
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 for e in table.keys():
27 27 aliases = parsealiases(e)
28 28 found = None
29 29 if cmd in aliases:
30 30 found = cmd
31 31 elif not strict:
32 32 for a in aliases:
33 33 if a.startswith(cmd):
34 34 found = a
35 35 break
36 36 if found is not None:
37 37 if aliases[0].startswith("debug") or found.startswith("debug"):
38 38 debugchoice[found] = (aliases, table[e])
39 39 else:
40 40 choice[found] = (aliases, table[e])
41 41
42 42 if not choice and debugchoice:
43 43 choice = debugchoice
44 44
45 45 return choice
46 46
47 47 def findcmd(cmd, table, strict=True):
48 48 """Return (aliases, command table entry) for command string."""
49 49 choice = findpossible(cmd, table, strict)
50 50
51 51 if cmd in choice:
52 52 return choice[cmd]
53 53
54 54 if len(choice) > 1:
55 55 clist = choice.keys()
56 56 clist.sort()
57 57 raise error.AmbiguousCommand(cmd, clist)
58 58
59 59 if choice:
60 60 return choice.values()[0]
61 61
62 62 raise error.UnknownCommand(cmd)
63 63
64 64 def findrepo(p):
65 65 while not os.path.isdir(os.path.join(p, ".hg")):
66 66 oldp, p = p, os.path.dirname(p)
67 67 if p == oldp:
68 68 return None
69 69
70 70 return p
71 71
72 72 def bailifchanged(repo):
73 73 if repo.dirstate.p2() != nullid:
74 74 raise util.Abort(_('outstanding uncommitted merge'))
75 75 modified, added, removed, deleted = repo.status()[:4]
76 76 if modified or added or removed or deleted:
77 77 raise util.Abort(_("outstanding uncommitted changes"))
78 78
79 79 def logmessage(opts):
80 80 """ get the log message according to -m and -l option """
81 81 message = opts.get('message')
82 82 logfile = opts.get('logfile')
83 83
84 84 if message and logfile:
85 85 raise util.Abort(_('options --message and --logfile are mutually '
86 86 'exclusive'))
87 87 if not message and logfile:
88 88 try:
89 89 if logfile == '-':
90 90 message = sys.stdin.read()
91 91 else:
92 92 message = '\n'.join(util.readfile(logfile).splitlines())
93 93 except IOError, inst:
94 94 raise util.Abort(_("can't read commit message '%s': %s") %
95 95 (logfile, inst.strerror))
96 96 return message
97 97
98 98 def loglimit(opts):
99 99 """get the log limit according to option -l/--limit"""
100 100 limit = opts.get('limit')
101 101 if limit:
102 102 try:
103 103 limit = int(limit)
104 104 except ValueError:
105 105 raise util.Abort(_('limit must be a positive integer'))
106 106 if limit <= 0:
107 107 raise util.Abort(_('limit must be positive'))
108 108 else:
109 109 limit = None
110 110 return limit
111 111
112 112 def makefilename(repo, pat, node,
113 113 total=None, seqno=None, revwidth=None, pathname=None):
114 114 node_expander = {
115 115 'H': lambda: hex(node),
116 116 'R': lambda: str(repo.changelog.rev(node)),
117 117 'h': lambda: short(node),
118 118 }
119 119 expander = {
120 120 '%': lambda: '%',
121 121 'b': lambda: os.path.basename(repo.root),
122 122 }
123 123
124 124 try:
125 125 if node:
126 126 expander.update(node_expander)
127 127 if node:
128 128 expander['r'] = (lambda:
129 129 str(repo.changelog.rev(node)).zfill(revwidth or 0))
130 130 if total is not None:
131 131 expander['N'] = lambda: str(total)
132 132 if seqno is not None:
133 133 expander['n'] = lambda: str(seqno)
134 134 if total is not None and seqno is not None:
135 135 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
136 136 if pathname is not None:
137 137 expander['s'] = lambda: os.path.basename(pathname)
138 138 expander['d'] = lambda: os.path.dirname(pathname) or '.'
139 139 expander['p'] = lambda: pathname
140 140
141 141 newname = []
142 142 patlen = len(pat)
143 143 i = 0
144 144 while i < patlen:
145 145 c = pat[i]
146 146 if c == '%':
147 147 i += 1
148 148 c = pat[i]
149 149 c = expander[c]()
150 150 newname.append(c)
151 151 i += 1
152 152 return ''.join(newname)
153 153 except KeyError, inst:
154 154 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
155 155 inst.args[0])
156 156
157 157 def makefileobj(repo, pat, node=None, total=None,
158 158 seqno=None, revwidth=None, mode='wb', pathname=None):
159 159
160 160 writable = mode not in ('r', 'rb')
161 161
162 162 if not pat or pat == '-':
163 163 fp = writable and sys.stdout or sys.stdin
164 164 return os.fdopen(os.dup(fp.fileno()), mode)
165 165 if hasattr(pat, 'write') and writable:
166 166 return pat
167 167 if hasattr(pat, 'read') and 'r' in mode:
168 168 return pat
169 169 return open(makefilename(repo, pat, node, total, seqno, revwidth,
170 170 pathname),
171 171 mode)
172 172
173 173 def openrevlog(repo, cmd, file_, opts):
174 174 """opens the changelog, manifest, a filelog or a given revlog"""
175 175 cl = opts['changelog']
176 176 mf = opts['manifest']
177 177 msg = None
178 178 if cl and mf:
179 179 msg = _('cannot specify --changelog and --manifest at the same time')
180 180 elif cl or mf:
181 181 if file_:
182 182 msg = _('cannot specify filename with --changelog or --manifest')
183 183 elif not repo:
184 184 msg = _('cannot specify --changelog or --manifest '
185 185 'without a repository')
186 186 if msg:
187 187 raise util.Abort(msg)
188 188
189 189 r = None
190 190 if repo:
191 191 if cl:
192 192 r = repo.changelog
193 193 elif mf:
194 194 r = repo.manifest
195 195 elif file_:
196 196 filelog = repo.file(file_)
197 197 if len(filelog):
198 198 r = filelog
199 199 if not r:
200 200 if not file_:
201 201 raise error.CommandError(cmd, _('invalid arguments'))
202 202 if not os.path.isfile(file_):
203 203 raise util.Abort(_("revlog '%s' not found") % file_)
204 204 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
205 205 file_[:-2] + ".i")
206 206 return r
207 207
208 208 def copy(ui, repo, pats, opts, rename=False):
209 209 # called with the repo lock held
210 210 #
211 211 # hgsep => pathname that uses "/" to separate directories
212 212 # ossep => pathname that uses os.sep to separate directories
213 213 cwd = repo.getcwd()
214 214 targets = {}
215 215 after = opts.get("after")
216 216 dryrun = opts.get("dry_run")
217 217 wctx = repo[None]
218 218
219 219 def walkpat(pat):
220 220 srcs = []
221 221 badstates = after and '?' or '?r'
222 222 m = scmutil.match(repo, [pat], opts, globbed=True)
223 223 for abs in repo.walk(m):
224 224 state = repo.dirstate[abs]
225 225 rel = m.rel(abs)
226 226 exact = m.exact(abs)
227 227 if state in badstates:
228 228 if exact and state == '?':
229 229 ui.warn(_('%s: not copying - file is not managed\n') % rel)
230 230 if exact and state == 'r':
231 231 ui.warn(_('%s: not copying - file has been marked for'
232 232 ' remove\n') % rel)
233 233 continue
234 234 # abs: hgsep
235 235 # rel: ossep
236 236 srcs.append((abs, rel, exact))
237 237 return srcs
238 238
239 239 # abssrc: hgsep
240 240 # relsrc: ossep
241 241 # otarget: ossep
242 242 def copyfile(abssrc, relsrc, otarget, exact):
243 243 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
244 244 reltarget = repo.pathto(abstarget, cwd)
245 245 target = repo.wjoin(abstarget)
246 246 src = repo.wjoin(abssrc)
247 247 state = repo.dirstate[abstarget]
248 248
249 249 scmutil.checkportable(ui, abstarget)
250 250
251 251 # check for collisions
252 252 prevsrc = targets.get(abstarget)
253 253 if prevsrc is not None:
254 254 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
255 255 (reltarget, repo.pathto(abssrc, cwd),
256 256 repo.pathto(prevsrc, cwd)))
257 257 return
258 258
259 259 # check for overwrites
260 260 exists = os.path.lexists(target)
261 261 if not after and exists or after and state in 'mn':
262 262 if not opts['force']:
263 263 ui.warn(_('%s: not overwriting - file exists\n') %
264 264 reltarget)
265 265 return
266 266
267 267 if after:
268 268 if not exists:
269 269 if rename:
270 270 ui.warn(_('%s: not recording move - %s does not exist\n') %
271 271 (relsrc, reltarget))
272 272 else:
273 273 ui.warn(_('%s: not recording copy - %s does not exist\n') %
274 274 (relsrc, reltarget))
275 275 return
276 276 elif not dryrun:
277 277 try:
278 278 if exists:
279 279 os.unlink(target)
280 280 targetdir = os.path.dirname(target) or '.'
281 281 if not os.path.isdir(targetdir):
282 282 os.makedirs(targetdir)
283 283 util.copyfile(src, target)
284 srcexists = True
284 285 except IOError, inst:
285 286 if inst.errno == errno.ENOENT:
286 287 ui.warn(_('%s: deleted in working copy\n') % relsrc)
288 srcexists = False
287 289 else:
288 290 ui.warn(_('%s: cannot copy - %s\n') %
289 291 (relsrc, inst.strerror))
290 292 return True # report a failure
291 293
292 294 if ui.verbose or not exact:
293 295 if rename:
294 296 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
295 297 else:
296 298 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
297 299
298 300 targets[abstarget] = abssrc
299 301
300 302 # fix up dirstate
301 303 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
302 304 dryrun=dryrun, cwd=cwd)
303 305 if rename and not dryrun:
304 wctx.remove([abssrc], not after)
306 if not after and srcexists:
307 util.unlinkpath(repo.wjoin(abssrc))
308 wctx.forget([abssrc])
305 309
306 310 # pat: ossep
307 311 # dest ossep
308 312 # srcs: list of (hgsep, hgsep, ossep, bool)
309 313 # return: function that takes hgsep and returns ossep
310 314 def targetpathfn(pat, dest, srcs):
311 315 if os.path.isdir(pat):
312 316 abspfx = scmutil.canonpath(repo.root, cwd, pat)
313 317 abspfx = util.localpath(abspfx)
314 318 if destdirexists:
315 319 striplen = len(os.path.split(abspfx)[0])
316 320 else:
317 321 striplen = len(abspfx)
318 322 if striplen:
319 323 striplen += len(os.sep)
320 324 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
321 325 elif destdirexists:
322 326 res = lambda p: os.path.join(dest,
323 327 os.path.basename(util.localpath(p)))
324 328 else:
325 329 res = lambda p: dest
326 330 return res
327 331
328 332 # pat: ossep
329 333 # dest ossep
330 334 # srcs: list of (hgsep, hgsep, ossep, bool)
331 335 # return: function that takes hgsep and returns ossep
332 336 def targetpathafterfn(pat, dest, srcs):
333 337 if matchmod.patkind(pat):
334 338 # a mercurial pattern
335 339 res = lambda p: os.path.join(dest,
336 340 os.path.basename(util.localpath(p)))
337 341 else:
338 342 abspfx = scmutil.canonpath(repo.root, cwd, pat)
339 343 if len(abspfx) < len(srcs[0][0]):
340 344 # A directory. Either the target path contains the last
341 345 # component of the source path or it does not.
342 346 def evalpath(striplen):
343 347 score = 0
344 348 for s in srcs:
345 349 t = os.path.join(dest, util.localpath(s[0])[striplen:])
346 350 if os.path.lexists(t):
347 351 score += 1
348 352 return score
349 353
350 354 abspfx = util.localpath(abspfx)
351 355 striplen = len(abspfx)
352 356 if striplen:
353 357 striplen += len(os.sep)
354 358 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
355 359 score = evalpath(striplen)
356 360 striplen1 = len(os.path.split(abspfx)[0])
357 361 if striplen1:
358 362 striplen1 += len(os.sep)
359 363 if evalpath(striplen1) > score:
360 364 striplen = striplen1
361 365 res = lambda p: os.path.join(dest,
362 366 util.localpath(p)[striplen:])
363 367 else:
364 368 # a file
365 369 if destdirexists:
366 370 res = lambda p: os.path.join(dest,
367 371 os.path.basename(util.localpath(p)))
368 372 else:
369 373 res = lambda p: dest
370 374 return res
371 375
372 376
373 377 pats = scmutil.expandpats(pats)
374 378 if not pats:
375 379 raise util.Abort(_('no source or destination specified'))
376 380 if len(pats) == 1:
377 381 raise util.Abort(_('no destination specified'))
378 382 dest = pats.pop()
379 383 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
380 384 if not destdirexists:
381 385 if len(pats) > 1 or matchmod.patkind(pats[0]):
382 386 raise util.Abort(_('with multiple sources, destination must be an '
383 387 'existing directory'))
384 388 if util.endswithsep(dest):
385 389 raise util.Abort(_('destination %s is not a directory') % dest)
386 390
387 391 tfn = targetpathfn
388 392 if after:
389 393 tfn = targetpathafterfn
390 394 copylist = []
391 395 for pat in pats:
392 396 srcs = walkpat(pat)
393 397 if not srcs:
394 398 continue
395 399 copylist.append((tfn(pat, dest, srcs), srcs))
396 400 if not copylist:
397 401 raise util.Abort(_('no files to copy'))
398 402
399 403 errors = 0
400 404 for targetpath, srcs in copylist:
401 405 for abssrc, relsrc, exact in srcs:
402 406 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
403 407 errors += 1
404 408
405 409 if errors:
406 410 ui.warn(_('(consider using --after)\n'))
407 411
408 412 return errors != 0
409 413
410 414 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
411 415 runargs=None, appendpid=False):
412 416 '''Run a command as a service.'''
413 417
414 418 if opts['daemon'] and not opts['daemon_pipefds']:
415 419 # Signal child process startup with file removal
416 420 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
417 421 os.close(lockfd)
418 422 try:
419 423 if not runargs:
420 424 runargs = util.hgcmd() + sys.argv[1:]
421 425 runargs.append('--daemon-pipefds=%s' % lockpath)
422 426 # Don't pass --cwd to the child process, because we've already
423 427 # changed directory.
424 428 for i in xrange(1, len(runargs)):
425 429 if runargs[i].startswith('--cwd='):
426 430 del runargs[i]
427 431 break
428 432 elif runargs[i].startswith('--cwd'):
429 433 del runargs[i:i + 2]
430 434 break
431 435 def condfn():
432 436 return not os.path.exists(lockpath)
433 437 pid = util.rundetached(runargs, condfn)
434 438 if pid < 0:
435 439 raise util.Abort(_('child process failed to start'))
436 440 finally:
437 441 try:
438 442 os.unlink(lockpath)
439 443 except OSError, e:
440 444 if e.errno != errno.ENOENT:
441 445 raise
442 446 if parentfn:
443 447 return parentfn(pid)
444 448 else:
445 449 return
446 450
447 451 if initfn:
448 452 initfn()
449 453
450 454 if opts['pid_file']:
451 455 mode = appendpid and 'a' or 'w'
452 456 fp = open(opts['pid_file'], mode)
453 457 fp.write(str(os.getpid()) + '\n')
454 458 fp.close()
455 459
456 460 if opts['daemon_pipefds']:
457 461 lockpath = opts['daemon_pipefds']
458 462 try:
459 463 os.setsid()
460 464 except AttributeError:
461 465 pass
462 466 os.unlink(lockpath)
463 467 util.hidewindow()
464 468 sys.stdout.flush()
465 469 sys.stderr.flush()
466 470
467 471 nullfd = os.open(util.nulldev, os.O_RDWR)
468 472 logfilefd = nullfd
469 473 if logfile:
470 474 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
471 475 os.dup2(nullfd, 0)
472 476 os.dup2(logfilefd, 1)
473 477 os.dup2(logfilefd, 2)
474 478 if nullfd not in (0, 1, 2):
475 479 os.close(nullfd)
476 480 if logfile and logfilefd not in (0, 1, 2):
477 481 os.close(logfilefd)
478 482
479 483 if runfn:
480 484 return runfn()
481 485
482 486 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
483 487 opts=None):
484 488 '''export changesets as hg patches.'''
485 489
486 490 total = len(revs)
487 491 revwidth = max([len(str(rev)) for rev in revs])
488 492
489 493 def single(rev, seqno, fp):
490 494 ctx = repo[rev]
491 495 node = ctx.node()
492 496 parents = [p.node() for p in ctx.parents() if p]
493 497 branch = ctx.branch()
494 498 if switch_parent:
495 499 parents.reverse()
496 500 prev = (parents and parents[0]) or nullid
497 501
498 502 shouldclose = False
499 503 if not fp:
500 504 fp = makefileobj(repo, template, node, total=total, seqno=seqno,
501 505 revwidth=revwidth, mode='ab')
502 506 if fp != template:
503 507 shouldclose = True
504 508 if fp != sys.stdout and hasattr(fp, 'name'):
505 509 repo.ui.note("%s\n" % fp.name)
506 510
507 511 fp.write("# HG changeset patch\n")
508 512 fp.write("# User %s\n" % ctx.user())
509 513 fp.write("# Date %d %d\n" % ctx.date())
510 514 if branch and branch != 'default':
511 515 fp.write("# Branch %s\n" % branch)
512 516 fp.write("# Node ID %s\n" % hex(node))
513 517 fp.write("# Parent %s\n" % hex(prev))
514 518 if len(parents) > 1:
515 519 fp.write("# Parent %s\n" % hex(parents[1]))
516 520 fp.write(ctx.description().rstrip())
517 521 fp.write("\n\n")
518 522
519 523 for chunk in patch.diff(repo, prev, node, opts=opts):
520 524 fp.write(chunk)
521 525
522 526 if shouldclose:
523 527 fp.close()
524 528
525 529 for seqno, rev in enumerate(revs):
526 530 single(rev, seqno + 1, fp)
527 531
528 532 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
529 533 changes=None, stat=False, fp=None, prefix='',
530 534 listsubrepos=False):
531 535 '''show diff or diffstat.'''
532 536 if fp is None:
533 537 write = ui.write
534 538 else:
535 539 def write(s, **kw):
536 540 fp.write(s)
537 541
538 542 if stat:
539 543 diffopts = diffopts.copy(context=0)
540 544 width = 80
541 545 if not ui.plain():
542 546 width = ui.termwidth()
543 547 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
544 548 prefix=prefix)
545 549 for chunk, label in patch.diffstatui(util.iterlines(chunks),
546 550 width=width,
547 551 git=diffopts.git):
548 552 write(chunk, label=label)
549 553 else:
550 554 for chunk, label in patch.diffui(repo, node1, node2, match,
551 555 changes, diffopts, prefix=prefix):
552 556 write(chunk, label=label)
553 557
554 558 if listsubrepos:
555 559 ctx1 = repo[node1]
556 560 ctx2 = repo[node2]
557 561 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
558 562 if node2 is not None:
559 563 node2 = ctx2.substate[subpath][1]
560 564 submatch = matchmod.narrowmatcher(subpath, match)
561 565 sub.diff(diffopts, node2, submatch, changes=changes,
562 566 stat=stat, fp=fp, prefix=prefix)
563 567
564 568 class changeset_printer(object):
565 569 '''show changeset information when templating not requested.'''
566 570
567 571 def __init__(self, ui, repo, patch, diffopts, buffered):
568 572 self.ui = ui
569 573 self.repo = repo
570 574 self.buffered = buffered
571 575 self.patch = patch
572 576 self.diffopts = diffopts
573 577 self.header = {}
574 578 self.hunk = {}
575 579 self.lastheader = None
576 580 self.footer = None
577 581
578 582 def flush(self, rev):
579 583 if rev in self.header:
580 584 h = self.header[rev]
581 585 if h != self.lastheader:
582 586 self.lastheader = h
583 587 self.ui.write(h)
584 588 del self.header[rev]
585 589 if rev in self.hunk:
586 590 self.ui.write(self.hunk[rev])
587 591 del self.hunk[rev]
588 592 return 1
589 593 return 0
590 594
591 595 def close(self):
592 596 if self.footer:
593 597 self.ui.write(self.footer)
594 598
595 599 def show(self, ctx, copies=None, matchfn=None, **props):
596 600 if self.buffered:
597 601 self.ui.pushbuffer()
598 602 self._show(ctx, copies, matchfn, props)
599 603 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
600 604 else:
601 605 self._show(ctx, copies, matchfn, props)
602 606
603 607 def _show(self, ctx, copies, matchfn, props):
604 608 '''show a single changeset or file revision'''
605 609 changenode = ctx.node()
606 610 rev = ctx.rev()
607 611
608 612 if self.ui.quiet:
609 613 self.ui.write("%d:%s\n" % (rev, short(changenode)),
610 614 label='log.node')
611 615 return
612 616
613 617 log = self.repo.changelog
614 618 date = util.datestr(ctx.date())
615 619
616 620 hexfunc = self.ui.debugflag and hex or short
617 621
618 622 parents = [(p, hexfunc(log.node(p)))
619 623 for p in self._meaningful_parentrevs(log, rev)]
620 624
621 625 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
622 626 label='log.changeset')
623 627
624 628 branch = ctx.branch()
625 629 # don't show the default branch name
626 630 if branch != 'default':
627 631 self.ui.write(_("branch: %s\n") % branch,
628 632 label='log.branch')
629 633 for bookmark in self.repo.nodebookmarks(changenode):
630 634 self.ui.write(_("bookmark: %s\n") % bookmark,
631 635 label='log.bookmark')
632 636 for tag in self.repo.nodetags(changenode):
633 637 self.ui.write(_("tag: %s\n") % tag,
634 638 label='log.tag')
635 639 for parent in parents:
636 640 self.ui.write(_("parent: %d:%s\n") % parent,
637 641 label='log.parent')
638 642
639 643 if self.ui.debugflag:
640 644 mnode = ctx.manifestnode()
641 645 self.ui.write(_("manifest: %d:%s\n") %
642 646 (self.repo.manifest.rev(mnode), hex(mnode)),
643 647 label='ui.debug log.manifest')
644 648 self.ui.write(_("user: %s\n") % ctx.user(),
645 649 label='log.user')
646 650 self.ui.write(_("date: %s\n") % date,
647 651 label='log.date')
648 652
649 653 if self.ui.debugflag:
650 654 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
651 655 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
652 656 files):
653 657 if value:
654 658 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
655 659 label='ui.debug log.files')
656 660 elif ctx.files() and self.ui.verbose:
657 661 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
658 662 label='ui.note log.files')
659 663 if copies and self.ui.verbose:
660 664 copies = ['%s (%s)' % c for c in copies]
661 665 self.ui.write(_("copies: %s\n") % ' '.join(copies),
662 666 label='ui.note log.copies')
663 667
664 668 extra = ctx.extra()
665 669 if extra and self.ui.debugflag:
666 670 for key, value in sorted(extra.items()):
667 671 self.ui.write(_("extra: %s=%s\n")
668 672 % (key, value.encode('string_escape')),
669 673 label='ui.debug log.extra')
670 674
671 675 description = ctx.description().strip()
672 676 if description:
673 677 if self.ui.verbose:
674 678 self.ui.write(_("description:\n"),
675 679 label='ui.note log.description')
676 680 self.ui.write(description,
677 681 label='ui.note log.description')
678 682 self.ui.write("\n\n")
679 683 else:
680 684 self.ui.write(_("summary: %s\n") %
681 685 description.splitlines()[0],
682 686 label='log.summary')
683 687 self.ui.write("\n")
684 688
685 689 self.showpatch(changenode, matchfn)
686 690
687 691 def showpatch(self, node, matchfn):
688 692 if not matchfn:
689 693 matchfn = self.patch
690 694 if matchfn:
691 695 stat = self.diffopts.get('stat')
692 696 diff = self.diffopts.get('patch')
693 697 diffopts = patch.diffopts(self.ui, self.diffopts)
694 698 prev = self.repo.changelog.parents(node)[0]
695 699 if stat:
696 700 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
697 701 match=matchfn, stat=True)
698 702 if diff:
699 703 if stat:
700 704 self.ui.write("\n")
701 705 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
702 706 match=matchfn, stat=False)
703 707 self.ui.write("\n")
704 708
705 709 def _meaningful_parentrevs(self, log, rev):
706 710 """Return list of meaningful (or all if debug) parentrevs for rev.
707 711
708 712 For merges (two non-nullrev revisions) both parents are meaningful.
709 713 Otherwise the first parent revision is considered meaningful if it
710 714 is not the preceding revision.
711 715 """
712 716 parents = log.parentrevs(rev)
713 717 if not self.ui.debugflag and parents[1] == nullrev:
714 718 if parents[0] >= rev - 1:
715 719 parents = []
716 720 else:
717 721 parents = [parents[0]]
718 722 return parents
719 723
720 724
721 725 class changeset_templater(changeset_printer):
722 726 '''format changeset information.'''
723 727
724 728 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
725 729 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
726 730 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
727 731 defaulttempl = {
728 732 'parent': '{rev}:{node|formatnode} ',
729 733 'manifest': '{rev}:{node|formatnode}',
730 734 'file_copy': '{name} ({source})',
731 735 'extra': '{key}={value|stringescape}'
732 736 }
733 737 # filecopy is preserved for compatibility reasons
734 738 defaulttempl['filecopy'] = defaulttempl['file_copy']
735 739 self.t = templater.templater(mapfile, {'formatnode': formatnode},
736 740 cache=defaulttempl)
737 741 self.cache = {}
738 742
739 743 def use_template(self, t):
740 744 '''set template string to use'''
741 745 self.t.cache['changeset'] = t
742 746
743 747 def _meaningful_parentrevs(self, ctx):
744 748 """Return list of meaningful (or all if debug) parentrevs for rev.
745 749 """
746 750 parents = ctx.parents()
747 751 if len(parents) > 1:
748 752 return parents
749 753 if self.ui.debugflag:
750 754 return [parents[0], self.repo['null']]
751 755 if parents[0].rev() >= ctx.rev() - 1:
752 756 return []
753 757 return parents
754 758
755 759 def _show(self, ctx, copies, matchfn, props):
756 760 '''show a single changeset or file revision'''
757 761
758 762 showlist = templatekw.showlist
759 763
760 764 # showparents() behaviour depends on ui trace level which
761 765 # causes unexpected behaviours at templating level and makes
762 766 # it harder to extract it in a standalone function. Its
763 767 # behaviour cannot be changed so leave it here for now.
764 768 def showparents(**args):
765 769 ctx = args['ctx']
766 770 parents = [[('rev', p.rev()), ('node', p.hex())]
767 771 for p in self._meaningful_parentrevs(ctx)]
768 772 return showlist('parent', parents, **args)
769 773
770 774 props = props.copy()
771 775 props.update(templatekw.keywords)
772 776 props['parents'] = showparents
773 777 props['templ'] = self.t
774 778 props['ctx'] = ctx
775 779 props['repo'] = self.repo
776 780 props['revcache'] = {'copies': copies}
777 781 props['cache'] = self.cache
778 782
779 783 # find correct templates for current mode
780 784
781 785 tmplmodes = [
782 786 (True, None),
783 787 (self.ui.verbose, 'verbose'),
784 788 (self.ui.quiet, 'quiet'),
785 789 (self.ui.debugflag, 'debug'),
786 790 ]
787 791
788 792 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
789 793 for mode, postfix in tmplmodes:
790 794 for type in types:
791 795 cur = postfix and ('%s_%s' % (type, postfix)) or type
792 796 if mode and cur in self.t:
793 797 types[type] = cur
794 798
795 799 try:
796 800
797 801 # write header
798 802 if types['header']:
799 803 h = templater.stringify(self.t(types['header'], **props))
800 804 if self.buffered:
801 805 self.header[ctx.rev()] = h
802 806 else:
803 807 if self.lastheader != h:
804 808 self.lastheader = h
805 809 self.ui.write(h)
806 810
807 811 # write changeset metadata, then patch if requested
808 812 key = types['changeset']
809 813 self.ui.write(templater.stringify(self.t(key, **props)))
810 814 self.showpatch(ctx.node(), matchfn)
811 815
812 816 if types['footer']:
813 817 if not self.footer:
814 818 self.footer = templater.stringify(self.t(types['footer'],
815 819 **props))
816 820
817 821 except KeyError, inst:
818 822 msg = _("%s: no key named '%s'")
819 823 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
820 824 except SyntaxError, inst:
821 825 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
822 826
823 827 def show_changeset(ui, repo, opts, buffered=False):
824 828 """show one changeset using template or regular display.
825 829
826 830 Display format will be the first non-empty hit of:
827 831 1. option 'template'
828 832 2. option 'style'
829 833 3. [ui] setting 'logtemplate'
830 834 4. [ui] setting 'style'
831 835 If all of these values are either the unset or the empty string,
832 836 regular display via changeset_printer() is done.
833 837 """
834 838 # options
835 839 patch = False
836 840 if opts.get('patch') or opts.get('stat'):
837 841 patch = scmutil.matchall(repo)
838 842
839 843 tmpl = opts.get('template')
840 844 style = None
841 845 if tmpl:
842 846 tmpl = templater.parsestring(tmpl, quoted=False)
843 847 else:
844 848 style = opts.get('style')
845 849
846 850 # ui settings
847 851 if not (tmpl or style):
848 852 tmpl = ui.config('ui', 'logtemplate')
849 853 if tmpl:
850 854 tmpl = templater.parsestring(tmpl)
851 855 else:
852 856 style = util.expandpath(ui.config('ui', 'style', ''))
853 857
854 858 if not (tmpl or style):
855 859 return changeset_printer(ui, repo, patch, opts, buffered)
856 860
857 861 mapfile = None
858 862 if style and not tmpl:
859 863 mapfile = style
860 864 if not os.path.split(mapfile)[0]:
861 865 mapname = (templater.templatepath('map-cmdline.' + mapfile)
862 866 or templater.templatepath(mapfile))
863 867 if mapname:
864 868 mapfile = mapname
865 869
866 870 try:
867 871 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
868 872 except SyntaxError, inst:
869 873 raise util.Abort(inst.args[0])
870 874 if tmpl:
871 875 t.use_template(tmpl)
872 876 return t
873 877
874 878 def finddate(ui, repo, date):
875 879 """Find the tipmost changeset that matches the given date spec"""
876 880
877 881 df = util.matchdate(date)
878 882 m = scmutil.matchall(repo)
879 883 results = {}
880 884
881 885 def prep(ctx, fns):
882 886 d = ctx.date()
883 887 if df(d[0]):
884 888 results[ctx.rev()] = d
885 889
886 890 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
887 891 rev = ctx.rev()
888 892 if rev in results:
889 893 ui.status(_("Found revision %s from %s\n") %
890 894 (rev, util.datestr(results[rev])))
891 895 return str(rev)
892 896
893 897 raise util.Abort(_("revision matching date not found"))
894 898
895 899 def walkchangerevs(repo, match, opts, prepare):
896 900 '''Iterate over files and the revs in which they changed.
897 901
898 902 Callers most commonly need to iterate backwards over the history
899 903 in which they are interested. Doing so has awful (quadratic-looking)
900 904 performance, so we use iterators in a "windowed" way.
901 905
902 906 We walk a window of revisions in the desired order. Within the
903 907 window, we first walk forwards to gather data, then in the desired
904 908 order (usually backwards) to display it.
905 909
906 910 This function returns an iterator yielding contexts. Before
907 911 yielding each context, the iterator will first call the prepare
908 912 function on each context in the window in forward order.'''
909 913
910 914 def increasing_windows(start, end, windowsize=8, sizelimit=512):
911 915 if start < end:
912 916 while start < end:
913 917 yield start, min(windowsize, end - start)
914 918 start += windowsize
915 919 if windowsize < sizelimit:
916 920 windowsize *= 2
917 921 else:
918 922 while start > end:
919 923 yield start, min(windowsize, start - end - 1)
920 924 start -= windowsize
921 925 if windowsize < sizelimit:
922 926 windowsize *= 2
923 927
924 928 follow = opts.get('follow') or opts.get('follow_first')
925 929
926 930 if not len(repo):
927 931 return []
928 932
929 933 if follow:
930 934 defrange = '%s:0' % repo['.'].rev()
931 935 else:
932 936 defrange = '-1:0'
933 937 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
934 938 if not revs:
935 939 return []
936 940 wanted = set()
937 941 slowpath = match.anypats() or (match.files() and opts.get('removed'))
938 942 fncache = {}
939 943 change = util.cachefunc(repo.changectx)
940 944
941 945 # First step is to fill wanted, the set of revisions that we want to yield.
942 946 # When it does not induce extra cost, we also fill fncache for revisions in
943 947 # wanted: a cache of filenames that were changed (ctx.files()) and that
944 948 # match the file filtering conditions.
945 949
946 950 if not slowpath and not match.files():
947 951 # No files, no patterns. Display all revs.
948 952 wanted = set(revs)
949 953 copies = []
950 954
951 955 if not slowpath:
952 956 # We only have to read through the filelog to find wanted revisions
953 957
954 958 minrev, maxrev = min(revs), max(revs)
955 959 def filerevgen(filelog, last):
956 960 """
957 961 Only files, no patterns. Check the history of each file.
958 962
959 963 Examines filelog entries within minrev, maxrev linkrev range
960 964 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
961 965 tuples in backwards order
962 966 """
963 967 cl_count = len(repo)
964 968 revs = []
965 969 for j in xrange(0, last + 1):
966 970 linkrev = filelog.linkrev(j)
967 971 if linkrev < minrev:
968 972 continue
969 973 # only yield rev for which we have the changelog, it can
970 974 # happen while doing "hg log" during a pull or commit
971 975 if linkrev >= cl_count:
972 976 break
973 977
974 978 parentlinkrevs = []
975 979 for p in filelog.parentrevs(j):
976 980 if p != nullrev:
977 981 parentlinkrevs.append(filelog.linkrev(p))
978 982 n = filelog.node(j)
979 983 revs.append((linkrev, parentlinkrevs,
980 984 follow and filelog.renamed(n)))
981 985
982 986 return reversed(revs)
983 987 def iterfiles():
984 988 for filename in match.files():
985 989 yield filename, None
986 990 for filename_node in copies:
987 991 yield filename_node
988 992 for file_, node in iterfiles():
989 993 filelog = repo.file(file_)
990 994 if not len(filelog):
991 995 if node is None:
992 996 # A zero count may be a directory or deleted file, so
993 997 # try to find matching entries on the slow path.
994 998 if follow:
995 999 raise util.Abort(
996 1000 _('cannot follow nonexistent file: "%s"') % file_)
997 1001 slowpath = True
998 1002 break
999 1003 else:
1000 1004 continue
1001 1005
1002 1006 if node is None:
1003 1007 last = len(filelog) - 1
1004 1008 else:
1005 1009 last = filelog.rev(node)
1006 1010
1007 1011
1008 1012 # keep track of all ancestors of the file
1009 1013 ancestors = set([filelog.linkrev(last)])
1010 1014
1011 1015 # iterate from latest to oldest revision
1012 1016 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1013 1017 if not follow:
1014 1018 if rev > maxrev:
1015 1019 continue
1016 1020 else:
1017 1021 # Note that last might not be the first interesting
1018 1022 # rev to us:
1019 1023 # if the file has been changed after maxrev, we'll
1020 1024 # have linkrev(last) > maxrev, and we still need
1021 1025 # to explore the file graph
1022 1026 if rev not in ancestors:
1023 1027 continue
1024 1028 # XXX insert 1327 fix here
1025 1029 if flparentlinkrevs:
1026 1030 ancestors.update(flparentlinkrevs)
1027 1031
1028 1032 fncache.setdefault(rev, []).append(file_)
1029 1033 wanted.add(rev)
1030 1034 if copied:
1031 1035 copies.append(copied)
1032 1036 if slowpath:
1033 1037 # We have to read the changelog to match filenames against
1034 1038 # changed files
1035 1039
1036 1040 if follow:
1037 1041 raise util.Abort(_('can only follow copies/renames for explicit '
1038 1042 'filenames'))
1039 1043
1040 1044 # The slow path checks files modified in every changeset.
1041 1045 for i in sorted(revs):
1042 1046 ctx = change(i)
1043 1047 matches = filter(match, ctx.files())
1044 1048 if matches:
1045 1049 fncache[i] = matches
1046 1050 wanted.add(i)
1047 1051
1048 1052 class followfilter(object):
1049 1053 def __init__(self, onlyfirst=False):
1050 1054 self.startrev = nullrev
1051 1055 self.roots = set()
1052 1056 self.onlyfirst = onlyfirst
1053 1057
1054 1058 def match(self, rev):
1055 1059 def realparents(rev):
1056 1060 if self.onlyfirst:
1057 1061 return repo.changelog.parentrevs(rev)[0:1]
1058 1062 else:
1059 1063 return filter(lambda x: x != nullrev,
1060 1064 repo.changelog.parentrevs(rev))
1061 1065
1062 1066 if self.startrev == nullrev:
1063 1067 self.startrev = rev
1064 1068 return True
1065 1069
1066 1070 if rev > self.startrev:
1067 1071 # forward: all descendants
1068 1072 if not self.roots:
1069 1073 self.roots.add(self.startrev)
1070 1074 for parent in realparents(rev):
1071 1075 if parent in self.roots:
1072 1076 self.roots.add(rev)
1073 1077 return True
1074 1078 else:
1075 1079 # backwards: all parents
1076 1080 if not self.roots:
1077 1081 self.roots.update(realparents(self.startrev))
1078 1082 if rev in self.roots:
1079 1083 self.roots.remove(rev)
1080 1084 self.roots.update(realparents(rev))
1081 1085 return True
1082 1086
1083 1087 return False
1084 1088
1085 1089 # it might be worthwhile to do this in the iterator if the rev range
1086 1090 # is descending and the prune args are all within that range
1087 1091 for rev in opts.get('prune', ()):
1088 1092 rev = repo.changelog.rev(repo.lookup(rev))
1089 1093 ff = followfilter()
1090 1094 stop = min(revs[0], revs[-1])
1091 1095 for x in xrange(rev, stop - 1, -1):
1092 1096 if ff.match(x):
1093 1097 wanted.discard(x)
1094 1098
1095 1099 # Now that wanted is correctly initialized, we can iterate over the
1096 1100 # revision range, yielding only revisions in wanted.
1097 1101 def iterate():
1098 1102 if follow and not match.files():
1099 1103 ff = followfilter(onlyfirst=opts.get('follow_first'))
1100 1104 def want(rev):
1101 1105 return ff.match(rev) and rev in wanted
1102 1106 else:
1103 1107 def want(rev):
1104 1108 return rev in wanted
1105 1109
1106 1110 for i, window in increasing_windows(0, len(revs)):
1107 1111 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1108 1112 for rev in sorted(nrevs):
1109 1113 fns = fncache.get(rev)
1110 1114 ctx = change(rev)
1111 1115 if not fns:
1112 1116 def fns_generator():
1113 1117 for f in ctx.files():
1114 1118 if match(f):
1115 1119 yield f
1116 1120 fns = fns_generator()
1117 1121 prepare(ctx, fns)
1118 1122 for rev in nrevs:
1119 1123 yield change(rev)
1120 1124 return iterate()
1121 1125
1122 1126 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1123 1127 join = lambda f: os.path.join(prefix, f)
1124 1128 bad = []
1125 1129 oldbad = match.bad
1126 1130 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1127 1131 names = []
1128 1132 wctx = repo[None]
1129 1133 cca = None
1130 1134 abort, warn = scmutil.checkportabilityalert(ui)
1131 1135 if abort or warn:
1132 1136 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1133 1137 for f in repo.walk(match):
1134 1138 exact = match.exact(f)
1135 1139 if exact or f not in repo.dirstate:
1136 1140 if cca:
1137 1141 cca(f)
1138 1142 names.append(f)
1139 1143 if ui.verbose or not exact:
1140 1144 ui.status(_('adding %s\n') % match.rel(join(f)))
1141 1145
1142 1146 if listsubrepos:
1143 1147 for subpath in wctx.substate:
1144 1148 sub = wctx.sub(subpath)
1145 1149 try:
1146 1150 submatch = matchmod.narrowmatcher(subpath, match)
1147 1151 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1148 1152 except error.LookupError:
1149 1153 ui.status(_("skipping missing subrepository: %s\n")
1150 1154 % join(subpath))
1151 1155
1152 1156 if not dryrun:
1153 1157 rejected = wctx.add(names, prefix)
1154 1158 bad.extend(f for f in rejected if f in match.files())
1155 1159 return bad
1156 1160
1157 1161 def commit(ui, repo, commitfunc, pats, opts):
1158 1162 '''commit the specified files or all outstanding changes'''
1159 1163 date = opts.get('date')
1160 1164 if date:
1161 1165 opts['date'] = util.parsedate(date)
1162 1166 message = logmessage(opts)
1163 1167
1164 1168 # extract addremove carefully -- this function can be called from a command
1165 1169 # that doesn't support addremove
1166 1170 if opts.get('addremove'):
1167 1171 scmutil.addremove(repo, pats, opts)
1168 1172
1169 1173 return commitfunc(ui, repo, message, scmutil.match(repo, pats, opts), opts)
1170 1174
1171 1175 def commiteditor(repo, ctx, subs):
1172 1176 if ctx.description():
1173 1177 return ctx.description()
1174 1178 return commitforceeditor(repo, ctx, subs)
1175 1179
1176 1180 def commitforceeditor(repo, ctx, subs):
1177 1181 edittext = []
1178 1182 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1179 1183 if ctx.description():
1180 1184 edittext.append(ctx.description())
1181 1185 edittext.append("")
1182 1186 edittext.append("") # Empty line between message and comments.
1183 1187 edittext.append(_("HG: Enter commit message."
1184 1188 " Lines beginning with 'HG:' are removed."))
1185 1189 edittext.append(_("HG: Leave message empty to abort commit."))
1186 1190 edittext.append("HG: --")
1187 1191 edittext.append(_("HG: user: %s") % ctx.user())
1188 1192 if ctx.p2():
1189 1193 edittext.append(_("HG: branch merge"))
1190 1194 if ctx.branch():
1191 1195 edittext.append(_("HG: branch '%s'") % ctx.branch())
1192 1196 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1193 1197 edittext.extend([_("HG: added %s") % f for f in added])
1194 1198 edittext.extend([_("HG: changed %s") % f for f in modified])
1195 1199 edittext.extend([_("HG: removed %s") % f for f in removed])
1196 1200 if not added and not modified and not removed:
1197 1201 edittext.append(_("HG: no files changed"))
1198 1202 edittext.append("")
1199 1203 # run editor in the repository root
1200 1204 olddir = os.getcwd()
1201 1205 os.chdir(repo.root)
1202 1206 text = repo.ui.edit("\n".join(edittext), ctx.user())
1203 1207 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1204 1208 os.chdir(olddir)
1205 1209
1206 1210 if not text.strip():
1207 1211 raise util.Abort(_("empty commit message"))
1208 1212
1209 1213 return text
1210 1214
1211 1215 def command(table):
1212 1216 '''returns a function object bound to table which can be used as
1213 1217 a decorator for populating table as a command table'''
1214 1218
1215 1219 def cmd(name, options, synopsis=None):
1216 1220 def decorator(func):
1217 1221 if synopsis:
1218 1222 table[name] = func, options[:], synopsis
1219 1223 else:
1220 1224 table[name] = func, options[:]
1221 1225 return func
1222 1226 return decorator
1223 1227
1224 1228 return cmd
@@ -1,1123 +1,1109
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 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 nullid, nullrev, short, hex
9 9 from i18n import _
10 10 import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding
11 11 import os, errno, stat
12 12
13 13 propertycache = util.propertycache
14 14
15 15 class changectx(object):
16 16 """A changecontext object makes access to data related to a particular
17 17 changeset convenient."""
18 18 def __init__(self, repo, changeid=''):
19 19 """changeid is a revision number, node, or tag"""
20 20 if changeid == '':
21 21 changeid = '.'
22 22 self._repo = repo
23 23 if isinstance(changeid, (long, int)):
24 24 self._rev = changeid
25 25 self._node = self._repo.changelog.node(changeid)
26 26 else:
27 27 self._node = self._repo.lookup(changeid)
28 28 self._rev = self._repo.changelog.rev(self._node)
29 29
30 30 def __str__(self):
31 31 return short(self.node())
32 32
33 33 def __int__(self):
34 34 return self.rev()
35 35
36 36 def __repr__(self):
37 37 return "<changectx %s>" % str(self)
38 38
39 39 def __hash__(self):
40 40 try:
41 41 return hash(self._rev)
42 42 except AttributeError:
43 43 return id(self)
44 44
45 45 def __eq__(self, other):
46 46 try:
47 47 return self._rev == other._rev
48 48 except AttributeError:
49 49 return False
50 50
51 51 def __ne__(self, other):
52 52 return not (self == other)
53 53
54 54 def __nonzero__(self):
55 55 return self._rev != nullrev
56 56
57 57 @propertycache
58 58 def _changeset(self):
59 59 return self._repo.changelog.read(self.node())
60 60
61 61 @propertycache
62 62 def _manifest(self):
63 63 return self._repo.manifest.read(self._changeset[0])
64 64
65 65 @propertycache
66 66 def _manifestdelta(self):
67 67 return self._repo.manifest.readdelta(self._changeset[0])
68 68
69 69 @propertycache
70 70 def _parents(self):
71 71 p = self._repo.changelog.parentrevs(self._rev)
72 72 if p[1] == nullrev:
73 73 p = p[:-1]
74 74 return [changectx(self._repo, x) for x in p]
75 75
76 76 @propertycache
77 77 def substate(self):
78 78 return subrepo.state(self, self._repo.ui)
79 79
80 80 def __contains__(self, key):
81 81 return key in self._manifest
82 82
83 83 def __getitem__(self, key):
84 84 return self.filectx(key)
85 85
86 86 def __iter__(self):
87 87 for f in sorted(self._manifest):
88 88 yield f
89 89
90 90 def changeset(self):
91 91 return self._changeset
92 92 def manifest(self):
93 93 return self._manifest
94 94 def manifestnode(self):
95 95 return self._changeset[0]
96 96
97 97 def rev(self):
98 98 return self._rev
99 99 def node(self):
100 100 return self._node
101 101 def hex(self):
102 102 return hex(self._node)
103 103 def user(self):
104 104 return self._changeset[1]
105 105 def date(self):
106 106 return self._changeset[2]
107 107 def files(self):
108 108 return self._changeset[3]
109 109 def description(self):
110 110 return self._changeset[4]
111 111 def branch(self):
112 112 return encoding.tolocal(self._changeset[5].get("branch"))
113 113 def extra(self):
114 114 return self._changeset[5]
115 115 def tags(self):
116 116 return self._repo.nodetags(self._node)
117 117 def bookmarks(self):
118 118 return self._repo.nodebookmarks(self._node)
119 119
120 120 def parents(self):
121 121 """return contexts for each parent changeset"""
122 122 return self._parents
123 123
124 124 def p1(self):
125 125 return self._parents[0]
126 126
127 127 def p2(self):
128 128 if len(self._parents) == 2:
129 129 return self._parents[1]
130 130 return changectx(self._repo, -1)
131 131
132 132 def children(self):
133 133 """return contexts for each child changeset"""
134 134 c = self._repo.changelog.children(self._node)
135 135 return [changectx(self._repo, x) for x in c]
136 136
137 137 def ancestors(self):
138 138 for a in self._repo.changelog.ancestors(self._rev):
139 139 yield changectx(self._repo, a)
140 140
141 141 def descendants(self):
142 142 for d in self._repo.changelog.descendants(self._rev):
143 143 yield changectx(self._repo, d)
144 144
145 145 def _fileinfo(self, path):
146 146 if '_manifest' in self.__dict__:
147 147 try:
148 148 return self._manifest[path], self._manifest.flags(path)
149 149 except KeyError:
150 150 raise error.LookupError(self._node, path,
151 151 _('not found in manifest'))
152 152 if '_manifestdelta' in self.__dict__ or path in self.files():
153 153 if path in self._manifestdelta:
154 154 return self._manifestdelta[path], self._manifestdelta.flags(path)
155 155 node, flag = self._repo.manifest.find(self._changeset[0], path)
156 156 if not node:
157 157 raise error.LookupError(self._node, path,
158 158 _('not found in manifest'))
159 159
160 160 return node, flag
161 161
162 162 def filenode(self, path):
163 163 return self._fileinfo(path)[0]
164 164
165 165 def flags(self, path):
166 166 try:
167 167 return self._fileinfo(path)[1]
168 168 except error.LookupError:
169 169 return ''
170 170
171 171 def filectx(self, path, fileid=None, filelog=None):
172 172 """get a file context from this changeset"""
173 173 if fileid is None:
174 174 fileid = self.filenode(path)
175 175 return filectx(self._repo, path, fileid=fileid,
176 176 changectx=self, filelog=filelog)
177 177
178 178 def ancestor(self, c2):
179 179 """
180 180 return the ancestor context of self and c2
181 181 """
182 182 # deal with workingctxs
183 183 n2 = c2._node
184 184 if n2 is None:
185 185 n2 = c2._parents[0]._node
186 186 n = self._repo.changelog.ancestor(self._node, n2)
187 187 return changectx(self._repo, n)
188 188
189 189 def walk(self, match):
190 190 fset = set(match.files())
191 191 # for dirstate.walk, files=['.'] means "walk the whole tree".
192 192 # follow that here, too
193 193 fset.discard('.')
194 194 for fn in self:
195 195 for ffn in fset:
196 196 # match if the file is the exact name or a directory
197 197 if ffn == fn or fn.startswith("%s/" % ffn):
198 198 fset.remove(ffn)
199 199 break
200 200 if match(fn):
201 201 yield fn
202 202 for fn in sorted(fset):
203 203 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
204 204 yield fn
205 205
206 206 def sub(self, path):
207 207 return subrepo.subrepo(self, path)
208 208
209 209 def diff(self, ctx2=None, match=None, **opts):
210 210 """Returns a diff generator for the given contexts and matcher"""
211 211 if ctx2 is None:
212 212 ctx2 = self.p1()
213 213 if ctx2 is not None and not isinstance(ctx2, changectx):
214 214 ctx2 = self._repo[ctx2]
215 215 diffopts = patch.diffopts(self._repo.ui, opts)
216 216 return patch.diff(self._repo, ctx2.node(), self.node(),
217 217 match=match, opts=diffopts)
218 218
219 219 class filectx(object):
220 220 """A filecontext object makes access to data related to a particular
221 221 filerevision convenient."""
222 222 def __init__(self, repo, path, changeid=None, fileid=None,
223 223 filelog=None, changectx=None):
224 224 """changeid can be a changeset revision, node, or tag.
225 225 fileid can be a file revision or node."""
226 226 self._repo = repo
227 227 self._path = path
228 228
229 229 assert (changeid is not None
230 230 or fileid is not None
231 231 or changectx is not None), \
232 232 ("bad args: changeid=%r, fileid=%r, changectx=%r"
233 233 % (changeid, fileid, changectx))
234 234
235 235 if filelog:
236 236 self._filelog = filelog
237 237
238 238 if changeid is not None:
239 239 self._changeid = changeid
240 240 if changectx is not None:
241 241 self._changectx = changectx
242 242 if fileid is not None:
243 243 self._fileid = fileid
244 244
245 245 @propertycache
246 246 def _changectx(self):
247 247 return changectx(self._repo, self._changeid)
248 248
249 249 @propertycache
250 250 def _filelog(self):
251 251 return self._repo.file(self._path)
252 252
253 253 @propertycache
254 254 def _changeid(self):
255 255 if '_changectx' in self.__dict__:
256 256 return self._changectx.rev()
257 257 else:
258 258 return self._filelog.linkrev(self._filerev)
259 259
260 260 @propertycache
261 261 def _filenode(self):
262 262 if '_fileid' in self.__dict__:
263 263 return self._filelog.lookup(self._fileid)
264 264 else:
265 265 return self._changectx.filenode(self._path)
266 266
267 267 @propertycache
268 268 def _filerev(self):
269 269 return self._filelog.rev(self._filenode)
270 270
271 271 @propertycache
272 272 def _repopath(self):
273 273 return self._path
274 274
275 275 def __nonzero__(self):
276 276 try:
277 277 self._filenode
278 278 return True
279 279 except error.LookupError:
280 280 # file is missing
281 281 return False
282 282
283 283 def __str__(self):
284 284 return "%s@%s" % (self.path(), short(self.node()))
285 285
286 286 def __repr__(self):
287 287 return "<filectx %s>" % str(self)
288 288
289 289 def __hash__(self):
290 290 try:
291 291 return hash((self._path, self._filenode))
292 292 except AttributeError:
293 293 return id(self)
294 294
295 295 def __eq__(self, other):
296 296 try:
297 297 return (self._path == other._path
298 298 and self._filenode == other._filenode)
299 299 except AttributeError:
300 300 return False
301 301
302 302 def __ne__(self, other):
303 303 return not (self == other)
304 304
305 305 def filectx(self, fileid):
306 306 '''opens an arbitrary revision of the file without
307 307 opening a new filelog'''
308 308 return filectx(self._repo, self._path, fileid=fileid,
309 309 filelog=self._filelog)
310 310
311 311 def filerev(self):
312 312 return self._filerev
313 313 def filenode(self):
314 314 return self._filenode
315 315 def flags(self):
316 316 return self._changectx.flags(self._path)
317 317 def filelog(self):
318 318 return self._filelog
319 319
320 320 def rev(self):
321 321 if '_changectx' in self.__dict__:
322 322 return self._changectx.rev()
323 323 if '_changeid' in self.__dict__:
324 324 return self._changectx.rev()
325 325 return self._filelog.linkrev(self._filerev)
326 326
327 327 def linkrev(self):
328 328 return self._filelog.linkrev(self._filerev)
329 329 def node(self):
330 330 return self._changectx.node()
331 331 def hex(self):
332 332 return hex(self.node())
333 333 def user(self):
334 334 return self._changectx.user()
335 335 def date(self):
336 336 return self._changectx.date()
337 337 def files(self):
338 338 return self._changectx.files()
339 339 def description(self):
340 340 return self._changectx.description()
341 341 def branch(self):
342 342 return self._changectx.branch()
343 343 def extra(self):
344 344 return self._changectx.extra()
345 345 def manifest(self):
346 346 return self._changectx.manifest()
347 347 def changectx(self):
348 348 return self._changectx
349 349
350 350 def data(self):
351 351 return self._filelog.read(self._filenode)
352 352 def path(self):
353 353 return self._path
354 354 def size(self):
355 355 return self._filelog.size(self._filerev)
356 356
357 357 def cmp(self, fctx):
358 358 """compare with other file context
359 359
360 360 returns True if different than fctx.
361 361 """
362 362 if (fctx._filerev is None and self._repo._encodefilterpats
363 363 or self.size() == fctx.size()):
364 364 return self._filelog.cmp(self._filenode, fctx.data())
365 365
366 366 return True
367 367
368 368 def renamed(self):
369 369 """check if file was actually renamed in this changeset revision
370 370
371 371 If rename logged in file revision, we report copy for changeset only
372 372 if file revisions linkrev points back to the changeset in question
373 373 or both changeset parents contain different file revisions.
374 374 """
375 375
376 376 renamed = self._filelog.renamed(self._filenode)
377 377 if not renamed:
378 378 return renamed
379 379
380 380 if self.rev() == self.linkrev():
381 381 return renamed
382 382
383 383 name = self.path()
384 384 fnode = self._filenode
385 385 for p in self._changectx.parents():
386 386 try:
387 387 if fnode == p.filenode(name):
388 388 return None
389 389 except error.LookupError:
390 390 pass
391 391 return renamed
392 392
393 393 def parents(self):
394 394 p = self._path
395 395 fl = self._filelog
396 396 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
397 397
398 398 r = self._filelog.renamed(self._filenode)
399 399 if r:
400 400 pl[0] = (r[0], r[1], None)
401 401
402 402 return [filectx(self._repo, p, fileid=n, filelog=l)
403 403 for p, n, l in pl if n != nullid]
404 404
405 405 def p1(self):
406 406 return self.parents()[0]
407 407
408 408 def p2(self):
409 409 p = self.parents()
410 410 if len(p) == 2:
411 411 return p[1]
412 412 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
413 413
414 414 def children(self):
415 415 # hard for renames
416 416 c = self._filelog.children(self._filenode)
417 417 return [filectx(self._repo, self._path, fileid=x,
418 418 filelog=self._filelog) for x in c]
419 419
420 420 def annotate(self, follow=False, linenumber=None):
421 421 '''returns a list of tuples of (ctx, line) for each line
422 422 in the file, where ctx is the filectx of the node where
423 423 that line was last changed.
424 424 This returns tuples of ((ctx, linenumber), line) for each line,
425 425 if "linenumber" parameter is NOT "None".
426 426 In such tuples, linenumber means one at the first appearance
427 427 in the managed file.
428 428 To reduce annotation cost,
429 429 this returns fixed value(False is used) as linenumber,
430 430 if "linenumber" parameter is "False".'''
431 431
432 432 def decorate_compat(text, rev):
433 433 return ([rev] * len(text.splitlines()), text)
434 434
435 435 def without_linenumber(text, rev):
436 436 return ([(rev, False)] * len(text.splitlines()), text)
437 437
438 438 def with_linenumber(text, rev):
439 439 size = len(text.splitlines())
440 440 return ([(rev, i) for i in xrange(1, size + 1)], text)
441 441
442 442 decorate = (((linenumber is None) and decorate_compat) or
443 443 (linenumber and with_linenumber) or
444 444 without_linenumber)
445 445
446 446 def pair(parent, child):
447 447 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
448 448 child[0][b1:b2] = parent[0][a1:a2]
449 449 return child
450 450
451 451 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
452 452 def getctx(path, fileid):
453 453 log = path == self._path and self._filelog or getlog(path)
454 454 return filectx(self._repo, path, fileid=fileid, filelog=log)
455 455 getctx = util.lrucachefunc(getctx)
456 456
457 457 def parents(f):
458 458 # we want to reuse filectx objects as much as possible
459 459 p = f._path
460 460 if f._filerev is None: # working dir
461 461 pl = [(n.path(), n.filerev()) for n in f.parents()]
462 462 else:
463 463 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
464 464
465 465 if follow:
466 466 r = f.renamed()
467 467 if r:
468 468 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
469 469
470 470 return [getctx(p, n) for p, n in pl if n != nullrev]
471 471
472 472 # use linkrev to find the first changeset where self appeared
473 473 if self.rev() != self.linkrev():
474 474 base = self.filectx(self.filerev())
475 475 else:
476 476 base = self
477 477
478 478 # This algorithm would prefer to be recursive, but Python is a
479 479 # bit recursion-hostile. Instead we do an iterative
480 480 # depth-first search.
481 481
482 482 visit = [base]
483 483 hist = {}
484 484 pcache = {}
485 485 needed = {base: 1}
486 486 while visit:
487 487 f = visit[-1]
488 488 if f not in pcache:
489 489 pcache[f] = parents(f)
490 490
491 491 ready = True
492 492 pl = pcache[f]
493 493 for p in pl:
494 494 if p not in hist:
495 495 ready = False
496 496 visit.append(p)
497 497 needed[p] = needed.get(p, 0) + 1
498 498 if ready:
499 499 visit.pop()
500 500 curr = decorate(f.data(), f)
501 501 for p in pl:
502 502 curr = pair(hist[p], curr)
503 503 if needed[p] == 1:
504 504 del hist[p]
505 505 else:
506 506 needed[p] -= 1
507 507
508 508 hist[f] = curr
509 509 pcache[f] = []
510 510
511 511 return zip(hist[base][0], hist[base][1].splitlines(True))
512 512
513 513 def ancestor(self, fc2, actx=None):
514 514 """
515 515 find the common ancestor file context, if any, of self, and fc2
516 516
517 517 If actx is given, it must be the changectx of the common ancestor
518 518 of self's and fc2's respective changesets.
519 519 """
520 520
521 521 if actx is None:
522 522 actx = self.changectx().ancestor(fc2.changectx())
523 523
524 524 # the trivial case: changesets are unrelated, files must be too
525 525 if not actx:
526 526 return None
527 527
528 528 # the easy case: no (relevant) renames
529 529 if fc2.path() == self.path() and self.path() in actx:
530 530 return actx[self.path()]
531 531 acache = {}
532 532
533 533 # prime the ancestor cache for the working directory
534 534 for c in (self, fc2):
535 535 if c._filerev is None:
536 536 pl = [(n.path(), n.filenode()) for n in c.parents()]
537 537 acache[(c._path, None)] = pl
538 538
539 539 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
540 540 def parents(vertex):
541 541 if vertex in acache:
542 542 return acache[vertex]
543 543 f, n = vertex
544 544 if f not in flcache:
545 545 flcache[f] = self._repo.file(f)
546 546 fl = flcache[f]
547 547 pl = [(f, p) for p in fl.parents(n) if p != nullid]
548 548 re = fl.renamed(n)
549 549 if re:
550 550 pl.append(re)
551 551 acache[vertex] = pl
552 552 return pl
553 553
554 554 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
555 555 v = ancestor.ancestor(a, b, parents)
556 556 if v:
557 557 f, n = v
558 558 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
559 559
560 560 return None
561 561
562 562 def ancestors(self):
563 563 visit = {}
564 564 c = self
565 565 while True:
566 566 for parent in c.parents():
567 567 visit[(parent.rev(), parent.node())] = parent
568 568 if not visit:
569 569 break
570 570 c = visit.pop(max(visit))
571 571 yield c
572 572
573 573 class workingctx(changectx):
574 574 """A workingctx object makes access to data related to
575 575 the current working directory convenient.
576 576 date - any valid date string or (unixtime, offset), or None.
577 577 user - username string, or None.
578 578 extra - a dictionary of extra values, or None.
579 579 changes - a list of file lists as returned by localrepo.status()
580 580 or None to use the repository status.
581 581 """
582 582 def __init__(self, repo, text="", user=None, date=None, extra=None,
583 583 changes=None):
584 584 self._repo = repo
585 585 self._rev = None
586 586 self._node = None
587 587 self._text = text
588 588 if date:
589 589 self._date = util.parsedate(date)
590 590 if user:
591 591 self._user = user
592 592 if changes:
593 593 self._status = list(changes[:4])
594 594 self._unknown = changes[4]
595 595 self._ignored = changes[5]
596 596 self._clean = changes[6]
597 597 else:
598 598 self._unknown = None
599 599 self._ignored = None
600 600 self._clean = None
601 601
602 602 self._extra = {}
603 603 if extra:
604 604 self._extra = extra.copy()
605 605 if 'branch' not in self._extra:
606 606 try:
607 607 branch = encoding.fromlocal(self._repo.dirstate.branch())
608 608 except UnicodeDecodeError:
609 609 raise util.Abort(_('branch name not in UTF-8!'))
610 610 self._extra['branch'] = branch
611 611 if self._extra['branch'] == '':
612 612 self._extra['branch'] = 'default'
613 613
614 614 def __str__(self):
615 615 return str(self._parents[0]) + "+"
616 616
617 617 def __repr__(self):
618 618 return "<workingctx %s>" % str(self)
619 619
620 620 def __nonzero__(self):
621 621 return True
622 622
623 623 def __contains__(self, key):
624 624 return self._repo.dirstate[key] not in "?r"
625 625
626 626 @propertycache
627 627 def _manifest(self):
628 628 """generate a manifest corresponding to the working directory"""
629 629
630 630 if self._unknown is None:
631 631 self.status(unknown=True)
632 632
633 633 man = self._parents[0].manifest().copy()
634 634 copied = self._repo.dirstate.copies()
635 635 if len(self._parents) > 1:
636 636 man2 = self.p2().manifest()
637 637 def getman(f):
638 638 if f in man:
639 639 return man
640 640 return man2
641 641 else:
642 642 getman = lambda f: man
643 643 def cf(f):
644 644 f = copied.get(f, f)
645 645 return getman(f).flags(f)
646 646 ff = self._repo.dirstate.flagfunc(cf)
647 647 modified, added, removed, deleted = self._status
648 648 unknown = self._unknown
649 649 for i, l in (("a", added), ("m", modified), ("u", unknown)):
650 650 for f in l:
651 651 orig = copied.get(f, f)
652 652 man[f] = getman(orig).get(orig, nullid) + i
653 653 try:
654 654 man.set(f, ff(f))
655 655 except OSError:
656 656 pass
657 657
658 658 for f in deleted + removed:
659 659 if f in man:
660 660 del man[f]
661 661
662 662 return man
663 663
664 664 def __iter__(self):
665 665 d = self._repo.dirstate
666 666 for f in d:
667 667 if d[f] != 'r':
668 668 yield f
669 669
670 670 @propertycache
671 671 def _status(self):
672 672 return self._repo.status()[:4]
673 673
674 674 @propertycache
675 675 def _user(self):
676 676 return self._repo.ui.username()
677 677
678 678 @propertycache
679 679 def _date(self):
680 680 return util.makedate()
681 681
682 682 @propertycache
683 683 def _parents(self):
684 684 p = self._repo.dirstate.parents()
685 685 if p[1] == nullid:
686 686 p = p[:-1]
687 687 self._parents = [changectx(self._repo, x) for x in p]
688 688 return self._parents
689 689
690 690 def status(self, ignored=False, clean=False, unknown=False):
691 691 """Explicit status query
692 692 Unless this method is used to query the working copy status, the
693 693 _status property will implicitly read the status using its default
694 694 arguments."""
695 695 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
696 696 self._unknown = self._ignored = self._clean = None
697 697 if unknown:
698 698 self._unknown = stat[4]
699 699 if ignored:
700 700 self._ignored = stat[5]
701 701 if clean:
702 702 self._clean = stat[6]
703 703 self._status = stat[:4]
704 704 return stat
705 705
706 706 def manifest(self):
707 707 return self._manifest
708 708 def user(self):
709 709 return self._user or self._repo.ui.username()
710 710 def date(self):
711 711 return self._date
712 712 def description(self):
713 713 return self._text
714 714 def files(self):
715 715 return sorted(self._status[0] + self._status[1] + self._status[2])
716 716
717 717 def modified(self):
718 718 return self._status[0]
719 719 def added(self):
720 720 return self._status[1]
721 721 def removed(self):
722 722 return self._status[2]
723 723 def deleted(self):
724 724 return self._status[3]
725 725 def unknown(self):
726 726 assert self._unknown is not None # must call status first
727 727 return self._unknown
728 728 def ignored(self):
729 729 assert self._ignored is not None # must call status first
730 730 return self._ignored
731 731 def clean(self):
732 732 assert self._clean is not None # must call status first
733 733 return self._clean
734 734 def branch(self):
735 735 return encoding.tolocal(self._extra['branch'])
736 736 def extra(self):
737 737 return self._extra
738 738
739 739 def tags(self):
740 740 t = []
741 741 for p in self.parents():
742 742 t.extend(p.tags())
743 743 return t
744 744
745 745 def bookmarks(self):
746 746 b = []
747 747 for p in self.parents():
748 748 b.extend(p.bookmarks())
749 749 return b
750 750
751 751 def children(self):
752 752 return []
753 753
754 754 def flags(self, path):
755 755 if '_manifest' in self.__dict__:
756 756 try:
757 757 return self._manifest.flags(path)
758 758 except KeyError:
759 759 return ''
760 760
761 761 orig = self._repo.dirstate.copies().get(path, path)
762 762
763 763 def findflag(ctx):
764 764 mnode = ctx.changeset()[0]
765 765 node, flag = self._repo.manifest.find(mnode, orig)
766 766 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
767 767 try:
768 768 return ff(path)
769 769 except OSError:
770 770 pass
771 771
772 772 flag = findflag(self._parents[0])
773 773 if flag is None and len(self.parents()) > 1:
774 774 flag = findflag(self._parents[1])
775 775 if flag is None or self._repo.dirstate[path] == 'r':
776 776 return ''
777 777 return flag
778 778
779 779 def filectx(self, path, filelog=None):
780 780 """get a file context from the working directory"""
781 781 return workingfilectx(self._repo, path, workingctx=self,
782 782 filelog=filelog)
783 783
784 784 def ancestor(self, c2):
785 785 """return the ancestor context of self and c2"""
786 786 return self._parents[0].ancestor(c2) # punt on two parents for now
787 787
788 788 def walk(self, match):
789 789 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
790 790 True, False))
791 791
792 792 def dirty(self, missing=False):
793 793 "check whether a working directory is modified"
794 794 # check subrepos first
795 795 for s in self.substate:
796 796 if self.sub(s).dirty():
797 797 return True
798 798 # check current working dir
799 799 return (self.p2() or self.branch() != self.p1().branch() or
800 800 self.modified() or self.added() or self.removed() or
801 801 (missing and self.deleted()))
802 802
803 803 def add(self, list, prefix=""):
804 804 join = lambda f: os.path.join(prefix, f)
805 805 wlock = self._repo.wlock()
806 806 ui, ds = self._repo.ui, self._repo.dirstate
807 807 try:
808 808 rejected = []
809 809 for f in list:
810 810 scmutil.checkportable(ui, join(f))
811 811 p = self._repo.wjoin(f)
812 812 try:
813 813 st = os.lstat(p)
814 814 except OSError:
815 815 ui.warn(_("%s does not exist!\n") % join(f))
816 816 rejected.append(f)
817 817 continue
818 818 if st.st_size > 10000000:
819 819 ui.warn(_("%s: up to %d MB of RAM may be required "
820 820 "to manage this file\n"
821 821 "(use 'hg revert %s' to cancel the "
822 822 "pending addition)\n")
823 823 % (f, 3 * st.st_size // 1000000, join(f)))
824 824 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
825 825 ui.warn(_("%s not added: only files and symlinks "
826 826 "supported currently\n") % join(f))
827 827 rejected.append(p)
828 828 elif ds[f] in 'amn':
829 829 ui.warn(_("%s already tracked!\n") % join(f))
830 830 elif ds[f] == 'r':
831 831 ds.normallookup(f)
832 832 else:
833 833 ds.add(f)
834 834 return rejected
835 835 finally:
836 836 wlock.release()
837 837
838 838 def forget(self, files):
839 839 wlock = self._repo.wlock()
840 840 try:
841 841 for f in files:
842 842 if self._repo.dirstate[f] != 'a':
843 843 self._repo.dirstate.remove(f)
844 844 elif f not in self._repo.dirstate:
845 845 self._repo.ui.warn(_("%s not tracked!\n") % f)
846 846 else:
847 847 self._repo.dirstate.drop(f)
848 848 finally:
849 849 wlock.release()
850 850
851 851 def ancestors(self):
852 852 for a in self._repo.changelog.ancestors(
853 853 *[p.rev() for p in self._parents]):
854 854 yield changectx(self._repo, a)
855 855
856 def remove(self, list, unlink=False):
857 wlock = self._repo.wlock()
858 try:
859 if unlink:
860 for f in list:
861 try:
862 util.unlinkpath(self._repo.wjoin(f))
863 except OSError, inst:
864 if inst.errno != errno.ENOENT:
865 raise
866 self.forget(list)
867 finally:
868 wlock.release()
869
870 856 def undelete(self, list):
871 857 pctxs = self.parents()
872 858 wlock = self._repo.wlock()
873 859 try:
874 860 for f in list:
875 861 if self._repo.dirstate[f] != 'r':
876 862 self._repo.ui.warn(_("%s not removed!\n") % f)
877 863 else:
878 864 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
879 865 t = fctx.data()
880 866 self._repo.wwrite(f, t, fctx.flags())
881 867 self._repo.dirstate.normal(f)
882 868 finally:
883 869 wlock.release()
884 870
885 871 def copy(self, source, dest):
886 872 p = self._repo.wjoin(dest)
887 873 if not os.path.lexists(p):
888 874 self._repo.ui.warn(_("%s does not exist!\n") % dest)
889 875 elif not (os.path.isfile(p) or os.path.islink(p)):
890 876 self._repo.ui.warn(_("copy failed: %s is not a file or a "
891 877 "symbolic link\n") % dest)
892 878 else:
893 879 wlock = self._repo.wlock()
894 880 try:
895 881 if self._repo.dirstate[dest] in '?r':
896 882 self._repo.dirstate.add(dest)
897 883 self._repo.dirstate.copy(source, dest)
898 884 finally:
899 885 wlock.release()
900 886
901 887 class workingfilectx(filectx):
902 888 """A workingfilectx object makes access to data related to a particular
903 889 file in the working directory convenient."""
904 890 def __init__(self, repo, path, filelog=None, workingctx=None):
905 891 """changeid can be a changeset revision, node, or tag.
906 892 fileid can be a file revision or node."""
907 893 self._repo = repo
908 894 self._path = path
909 895 self._changeid = None
910 896 self._filerev = self._filenode = None
911 897
912 898 if filelog:
913 899 self._filelog = filelog
914 900 if workingctx:
915 901 self._changectx = workingctx
916 902
917 903 @propertycache
918 904 def _changectx(self):
919 905 return workingctx(self._repo)
920 906
921 907 def __nonzero__(self):
922 908 return True
923 909
924 910 def __str__(self):
925 911 return "%s@%s" % (self.path(), self._changectx)
926 912
927 913 def __repr__(self):
928 914 return "<workingfilectx %s>" % str(self)
929 915
930 916 def data(self):
931 917 return self._repo.wread(self._path)
932 918 def renamed(self):
933 919 rp = self._repo.dirstate.copied(self._path)
934 920 if not rp:
935 921 return None
936 922 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
937 923
938 924 def parents(self):
939 925 '''return parent filectxs, following copies if necessary'''
940 926 def filenode(ctx, path):
941 927 return ctx._manifest.get(path, nullid)
942 928
943 929 path = self._path
944 930 fl = self._filelog
945 931 pcl = self._changectx._parents
946 932 renamed = self.renamed()
947 933
948 934 if renamed:
949 935 pl = [renamed + (None,)]
950 936 else:
951 937 pl = [(path, filenode(pcl[0], path), fl)]
952 938
953 939 for pc in pcl[1:]:
954 940 pl.append((path, filenode(pc, path), fl))
955 941
956 942 return [filectx(self._repo, p, fileid=n, filelog=l)
957 943 for p, n, l in pl if n != nullid]
958 944
959 945 def children(self):
960 946 return []
961 947
962 948 def size(self):
963 949 return os.lstat(self._repo.wjoin(self._path)).st_size
964 950 def date(self):
965 951 t, tz = self._changectx.date()
966 952 try:
967 953 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
968 954 except OSError, err:
969 955 if err.errno != errno.ENOENT:
970 956 raise
971 957 return (t, tz)
972 958
973 959 def cmp(self, fctx):
974 960 """compare with other file context
975 961
976 962 returns True if different than fctx.
977 963 """
978 964 # fctx should be a filectx (not a wfctx)
979 965 # invert comparison to reuse the same code path
980 966 return fctx.cmp(self)
981 967
982 968 class memctx(object):
983 969 """Use memctx to perform in-memory commits via localrepo.commitctx().
984 970
985 971 Revision information is supplied at initialization time while
986 972 related files data and is made available through a callback
987 973 mechanism. 'repo' is the current localrepo, 'parents' is a
988 974 sequence of two parent revisions identifiers (pass None for every
989 975 missing parent), 'text' is the commit message and 'files' lists
990 976 names of files touched by the revision (normalized and relative to
991 977 repository root).
992 978
993 979 filectxfn(repo, memctx, path) is a callable receiving the
994 980 repository, the current memctx object and the normalized path of
995 981 requested file, relative to repository root. It is fired by the
996 982 commit function for every file in 'files', but calls order is
997 983 undefined. If the file is available in the revision being
998 984 committed (updated or added), filectxfn returns a memfilectx
999 985 object. If the file was removed, filectxfn raises an
1000 986 IOError. Moved files are represented by marking the source file
1001 987 removed and the new file added with copy information (see
1002 988 memfilectx).
1003 989
1004 990 user receives the committer name and defaults to current
1005 991 repository username, date is the commit date in any format
1006 992 supported by util.parsedate() and defaults to current date, extra
1007 993 is a dictionary of metadata or is left empty.
1008 994 """
1009 995 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1010 996 date=None, extra=None):
1011 997 self._repo = repo
1012 998 self._rev = None
1013 999 self._node = None
1014 1000 self._text = text
1015 1001 self._date = date and util.parsedate(date) or util.makedate()
1016 1002 self._user = user
1017 1003 parents = [(p or nullid) for p in parents]
1018 1004 p1, p2 = parents
1019 1005 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1020 1006 files = sorted(set(files))
1021 1007 self._status = [files, [], [], [], []]
1022 1008 self._filectxfn = filectxfn
1023 1009
1024 1010 self._extra = extra and extra.copy() or {}
1025 1011 if 'branch' not in self._extra:
1026 1012 self._extra['branch'] = 'default'
1027 1013 elif self._extra.get('branch') == '':
1028 1014 self._extra['branch'] = 'default'
1029 1015
1030 1016 def __str__(self):
1031 1017 return str(self._parents[0]) + "+"
1032 1018
1033 1019 def __int__(self):
1034 1020 return self._rev
1035 1021
1036 1022 def __nonzero__(self):
1037 1023 return True
1038 1024
1039 1025 def __getitem__(self, key):
1040 1026 return self.filectx(key)
1041 1027
1042 1028 def p1(self):
1043 1029 return self._parents[0]
1044 1030 def p2(self):
1045 1031 return self._parents[1]
1046 1032
1047 1033 def user(self):
1048 1034 return self._user or self._repo.ui.username()
1049 1035 def date(self):
1050 1036 return self._date
1051 1037 def description(self):
1052 1038 return self._text
1053 1039 def files(self):
1054 1040 return self.modified()
1055 1041 def modified(self):
1056 1042 return self._status[0]
1057 1043 def added(self):
1058 1044 return self._status[1]
1059 1045 def removed(self):
1060 1046 return self._status[2]
1061 1047 def deleted(self):
1062 1048 return self._status[3]
1063 1049 def unknown(self):
1064 1050 return self._status[4]
1065 1051 def ignored(self):
1066 1052 return self._status[5]
1067 1053 def clean(self):
1068 1054 return self._status[6]
1069 1055 def branch(self):
1070 1056 return encoding.tolocal(self._extra['branch'])
1071 1057 def extra(self):
1072 1058 return self._extra
1073 1059 def flags(self, f):
1074 1060 return self[f].flags()
1075 1061
1076 1062 def parents(self):
1077 1063 """return contexts for each parent changeset"""
1078 1064 return self._parents
1079 1065
1080 1066 def filectx(self, path, filelog=None):
1081 1067 """get a file context from the working directory"""
1082 1068 return self._filectxfn(self._repo, self, path)
1083 1069
1084 1070 def commit(self):
1085 1071 """commit context to the repo"""
1086 1072 return self._repo.commitctx(self)
1087 1073
1088 1074 class memfilectx(object):
1089 1075 """memfilectx represents an in-memory file to commit.
1090 1076
1091 1077 See memctx for more details.
1092 1078 """
1093 1079 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1094 1080 """
1095 1081 path is the normalized file path relative to repository root.
1096 1082 data is the file content as a string.
1097 1083 islink is True if the file is a symbolic link.
1098 1084 isexec is True if the file is executable.
1099 1085 copied is the source file path if current file was copied in the
1100 1086 revision being committed, or None."""
1101 1087 self._path = path
1102 1088 self._data = data
1103 1089 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1104 1090 self._copied = None
1105 1091 if copied:
1106 1092 self._copied = (copied, nullid)
1107 1093
1108 1094 def __nonzero__(self):
1109 1095 return True
1110 1096 def __str__(self):
1111 1097 return "%s@%s" % (self.path(), self._changectx)
1112 1098 def path(self):
1113 1099 return self._path
1114 1100 def data(self):
1115 1101 return self._data
1116 1102 def flags(self):
1117 1103 return self._flags
1118 1104 def isexec(self):
1119 1105 return 'x' in self._flags
1120 1106 def islink(self):
1121 1107 return 'l' in self._flags
1122 1108 def renamed(self):
1123 1109 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now