##// END OF EJS Templates
localrepo: factor out _manifestmatch logic for workingctx
Sean Farley -
r21468:870ddcf2 default
parent child Browse files
Show More
@@ -1,1500 +1,1517 b''
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, bin
9 9 from i18n import _
10 10 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 11 import match as matchmod
12 12 import os, errno, stat
13 13 import obsolete as obsmod
14 14 import repoview
15 15 import fileset
16 16
17 17 propertycache = util.propertycache
18 18
19 19 class basectx(object):
20 20 """A basectx object represents the common logic for its children:
21 21 changectx: read-only context that is already present in the repo,
22 22 workingctx: a context that represents the working directory and can
23 23 be committed,
24 24 memctx: a context that represents changes in-memory and can also
25 25 be committed."""
26 26 def __new__(cls, repo, changeid='', *args, **kwargs):
27 27 if isinstance(changeid, basectx):
28 28 return changeid
29 29
30 30 o = super(basectx, cls).__new__(cls)
31 31
32 32 o._repo = repo
33 33 o._rev = nullrev
34 34 o._node = nullid
35 35
36 36 return o
37 37
38 38 def __str__(self):
39 39 return short(self.node())
40 40
41 41 def __int__(self):
42 42 return self.rev()
43 43
44 44 def __repr__(self):
45 45 return "<%s %s>" % (type(self).__name__, str(self))
46 46
47 47 def __eq__(self, other):
48 48 try:
49 49 return type(self) == type(other) and self._rev == other._rev
50 50 except AttributeError:
51 51 return False
52 52
53 53 def __ne__(self, other):
54 54 return not (self == other)
55 55
56 56 def __contains__(self, key):
57 57 return key in self._manifest
58 58
59 59 def __getitem__(self, key):
60 60 return self.filectx(key)
61 61
62 62 def __iter__(self):
63 63 for f in sorted(self._manifest):
64 64 yield f
65 65
66 66 def _manifestmatches(self, match, s):
67 67 """generate a new manifest filtered by the match argument
68 68
69 69 This method is for internal use only and mainly exists to provide an
70 70 object oriented way for other contexts to customize the manifest
71 71 generation.
72 72 """
73 73 mf = self.manifest().copy()
74 74 if match.always():
75 75 return mf
76 76 for fn in mf.keys():
77 77 if not match(fn):
78 78 del mf[fn]
79 79 return mf
80 80
81 81 @propertycache
82 82 def substate(self):
83 83 return subrepo.state(self, self._repo.ui)
84 84
85 85 def rev(self):
86 86 return self._rev
87 87 def node(self):
88 88 return self._node
89 89 def hex(self):
90 90 return hex(self.node())
91 91 def manifest(self):
92 92 return self._manifest
93 93 def phasestr(self):
94 94 return phases.phasenames[self.phase()]
95 95 def mutable(self):
96 96 return self.phase() > phases.public
97 97
98 98 def getfileset(self, expr):
99 99 return fileset.getfileset(self, expr)
100 100
101 101 def obsolete(self):
102 102 """True if the changeset is obsolete"""
103 103 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
104 104
105 105 def extinct(self):
106 106 """True if the changeset is extinct"""
107 107 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
108 108
109 109 def unstable(self):
110 110 """True if the changeset is not obsolete but it's ancestor are"""
111 111 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
112 112
113 113 def bumped(self):
114 114 """True if the changeset try to be a successor of a public changeset
115 115
116 116 Only non-public and non-obsolete changesets may be bumped.
117 117 """
118 118 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
119 119
120 120 def divergent(self):
121 121 """Is a successors of a changeset with multiple possible successors set
122 122
123 123 Only non-public and non-obsolete changesets may be divergent.
124 124 """
125 125 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
126 126
127 127 def troubled(self):
128 128 """True if the changeset is either unstable, bumped or divergent"""
129 129 return self.unstable() or self.bumped() or self.divergent()
130 130
131 131 def troubles(self):
132 132 """return the list of troubles affecting this changesets.
133 133
134 134 Troubles are returned as strings. possible values are:
135 135 - unstable,
136 136 - bumped,
137 137 - divergent.
138 138 """
139 139 troubles = []
140 140 if self.unstable():
141 141 troubles.append('unstable')
142 142 if self.bumped():
143 143 troubles.append('bumped')
144 144 if self.divergent():
145 145 troubles.append('divergent')
146 146 return troubles
147 147
148 148 def parents(self):
149 149 """return contexts for each parent changeset"""
150 150 return self._parents
151 151
152 152 def p1(self):
153 153 return self._parents[0]
154 154
155 155 def p2(self):
156 156 if len(self._parents) == 2:
157 157 return self._parents[1]
158 158 return changectx(self._repo, -1)
159 159
160 160 def _fileinfo(self, path):
161 161 if '_manifest' in self.__dict__:
162 162 try:
163 163 return self._manifest[path], self._manifest.flags(path)
164 164 except KeyError:
165 165 raise error.ManifestLookupError(self._node, path,
166 166 _('not found in manifest'))
167 167 if '_manifestdelta' in self.__dict__ or path in self.files():
168 168 if path in self._manifestdelta:
169 169 return (self._manifestdelta[path],
170 170 self._manifestdelta.flags(path))
171 171 node, flag = self._repo.manifest.find(self._changeset[0], path)
172 172 if not node:
173 173 raise error.ManifestLookupError(self._node, path,
174 174 _('not found in manifest'))
175 175
176 176 return node, flag
177 177
178 178 def filenode(self, path):
179 179 return self._fileinfo(path)[0]
180 180
181 181 def flags(self, path):
182 182 try:
183 183 return self._fileinfo(path)[1]
184 184 except error.LookupError:
185 185 return ''
186 186
187 187 def sub(self, path):
188 188 return subrepo.subrepo(self, path)
189 189
190 190 def match(self, pats=[], include=None, exclude=None, default='glob'):
191 191 r = self._repo
192 192 return matchmod.match(r.root, r.getcwd(), pats,
193 193 include, exclude, default,
194 194 auditor=r.auditor, ctx=self)
195 195
196 196 def diff(self, ctx2=None, match=None, **opts):
197 197 """Returns a diff generator for the given contexts and matcher"""
198 198 if ctx2 is None:
199 199 ctx2 = self.p1()
200 200 if ctx2 is not None:
201 201 ctx2 = self._repo[ctx2]
202 202 diffopts = patch.diffopts(self._repo.ui, opts)
203 203 return patch.diff(self._repo, ctx2.node(), self.node(),
204 204 match=match, opts=diffopts)
205 205
206 206 @propertycache
207 207 def _dirs(self):
208 208 return scmutil.dirs(self._manifest)
209 209
210 210 def dirs(self):
211 211 return self._dirs
212 212
213 213 def dirty(self):
214 214 return False
215 215
216 216 def makememctx(repo, parents, text, user, date, branch, files, store,
217 217 editor=None):
218 218 def getfilectx(repo, memctx, path):
219 219 data, (islink, isexec), copied = store.getfile(path)
220 220 return memfilectx(path, data, islink=islink, isexec=isexec,
221 221 copied=copied)
222 222 extra = {}
223 223 if branch:
224 224 extra['branch'] = encoding.fromlocal(branch)
225 225 ctx = memctx(repo, parents, text, files, getfilectx, user,
226 226 date, extra, editor)
227 227 return ctx
228 228
229 229 class changectx(basectx):
230 230 """A changecontext object makes access to data related to a particular
231 231 changeset convenient. It represents a read-only context already present in
232 232 the repo."""
233 233 def __init__(self, repo, changeid=''):
234 234 """changeid is a revision number, node, or tag"""
235 235
236 236 # since basectx.__new__ already took care of copying the object, we
237 237 # don't need to do anything in __init__, so we just exit here
238 238 if isinstance(changeid, basectx):
239 239 return
240 240
241 241 if changeid == '':
242 242 changeid = '.'
243 243 self._repo = repo
244 244
245 245 if isinstance(changeid, int):
246 246 try:
247 247 self._node = repo.changelog.node(changeid)
248 248 except IndexError:
249 249 raise error.RepoLookupError(
250 250 _("unknown revision '%s'") % changeid)
251 251 self._rev = changeid
252 252 return
253 253 if isinstance(changeid, long):
254 254 changeid = str(changeid)
255 255 if changeid == '.':
256 256 self._node = repo.dirstate.p1()
257 257 self._rev = repo.changelog.rev(self._node)
258 258 return
259 259 if changeid == 'null':
260 260 self._node = nullid
261 261 self._rev = nullrev
262 262 return
263 263 if changeid == 'tip':
264 264 self._node = repo.changelog.tip()
265 265 self._rev = repo.changelog.rev(self._node)
266 266 return
267 267 if len(changeid) == 20:
268 268 try:
269 269 self._node = changeid
270 270 self._rev = repo.changelog.rev(changeid)
271 271 return
272 272 except LookupError:
273 273 pass
274 274
275 275 try:
276 276 r = int(changeid)
277 277 if str(r) != changeid:
278 278 raise ValueError
279 279 l = len(repo.changelog)
280 280 if r < 0:
281 281 r += l
282 282 if r < 0 or r >= l:
283 283 raise ValueError
284 284 self._rev = r
285 285 self._node = repo.changelog.node(r)
286 286 return
287 287 except (ValueError, OverflowError, IndexError):
288 288 pass
289 289
290 290 if len(changeid) == 40:
291 291 try:
292 292 self._node = bin(changeid)
293 293 self._rev = repo.changelog.rev(self._node)
294 294 return
295 295 except (TypeError, LookupError):
296 296 pass
297 297
298 298 if changeid in repo._bookmarks:
299 299 self._node = repo._bookmarks[changeid]
300 300 self._rev = repo.changelog.rev(self._node)
301 301 return
302 302 if changeid in repo._tagscache.tags:
303 303 self._node = repo._tagscache.tags[changeid]
304 304 self._rev = repo.changelog.rev(self._node)
305 305 return
306 306 try:
307 307 self._node = repo.branchtip(changeid)
308 308 self._rev = repo.changelog.rev(self._node)
309 309 return
310 310 except error.RepoLookupError:
311 311 pass
312 312
313 313 self._node = repo.changelog._partialmatch(changeid)
314 314 if self._node is not None:
315 315 self._rev = repo.changelog.rev(self._node)
316 316 return
317 317
318 318 # lookup failed
319 319 # check if it might have come from damaged dirstate
320 320 #
321 321 # XXX we could avoid the unfiltered if we had a recognizable exception
322 322 # for filtered changeset access
323 323 if changeid in repo.unfiltered().dirstate.parents():
324 324 raise error.Abort(_("working directory has unknown parent '%s'!")
325 325 % short(changeid))
326 326 try:
327 327 if len(changeid) == 20:
328 328 changeid = hex(changeid)
329 329 except TypeError:
330 330 pass
331 331 raise error.RepoLookupError(
332 332 _("unknown revision '%s'") % changeid)
333 333
334 334 def __hash__(self):
335 335 try:
336 336 return hash(self._rev)
337 337 except AttributeError:
338 338 return id(self)
339 339
340 340 def __nonzero__(self):
341 341 return self._rev != nullrev
342 342
343 343 @propertycache
344 344 def _changeset(self):
345 345 return self._repo.changelog.read(self.rev())
346 346
347 347 @propertycache
348 348 def _manifest(self):
349 349 return self._repo.manifest.read(self._changeset[0])
350 350
351 351 @propertycache
352 352 def _manifestdelta(self):
353 353 return self._repo.manifest.readdelta(self._changeset[0])
354 354
355 355 @propertycache
356 356 def _parents(self):
357 357 p = self._repo.changelog.parentrevs(self._rev)
358 358 if p[1] == nullrev:
359 359 p = p[:-1]
360 360 return [changectx(self._repo, x) for x in p]
361 361
362 362 def changeset(self):
363 363 return self._changeset
364 364 def manifestnode(self):
365 365 return self._changeset[0]
366 366
367 367 def user(self):
368 368 return self._changeset[1]
369 369 def date(self):
370 370 return self._changeset[2]
371 371 def files(self):
372 372 return self._changeset[3]
373 373 def description(self):
374 374 return self._changeset[4]
375 375 def branch(self):
376 376 return encoding.tolocal(self._changeset[5].get("branch"))
377 377 def closesbranch(self):
378 378 return 'close' in self._changeset[5]
379 379 def extra(self):
380 380 return self._changeset[5]
381 381 def tags(self):
382 382 return self._repo.nodetags(self._node)
383 383 def bookmarks(self):
384 384 return self._repo.nodebookmarks(self._node)
385 385 def phase(self):
386 386 return self._repo._phasecache.phase(self._repo, self._rev)
387 387 def hidden(self):
388 388 return self._rev in repoview.filterrevs(self._repo, 'visible')
389 389
390 390 def children(self):
391 391 """return contexts for each child changeset"""
392 392 c = self._repo.changelog.children(self._node)
393 393 return [changectx(self._repo, x) for x in c]
394 394
395 395 def ancestors(self):
396 396 for a in self._repo.changelog.ancestors([self._rev]):
397 397 yield changectx(self._repo, a)
398 398
399 399 def descendants(self):
400 400 for d in self._repo.changelog.descendants([self._rev]):
401 401 yield changectx(self._repo, d)
402 402
403 403 def filectx(self, path, fileid=None, filelog=None):
404 404 """get a file context from this changeset"""
405 405 if fileid is None:
406 406 fileid = self.filenode(path)
407 407 return filectx(self._repo, path, fileid=fileid,
408 408 changectx=self, filelog=filelog)
409 409
410 410 def ancestor(self, c2, warn=False):
411 411 """
412 412 return the "best" ancestor context of self and c2
413 413 """
414 414 # deal with workingctxs
415 415 n2 = c2._node
416 416 if n2 is None:
417 417 n2 = c2._parents[0]._node
418 418 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
419 419 if not cahs:
420 420 anc = nullid
421 421 elif len(cahs) == 1:
422 422 anc = cahs[0]
423 423 else:
424 424 for r in self._repo.ui.configlist('merge', 'preferancestor'):
425 425 ctx = changectx(self._repo, r)
426 426 anc = ctx.node()
427 427 if anc in cahs:
428 428 break
429 429 else:
430 430 anc = self._repo.changelog.ancestor(self._node, n2)
431 431 if warn:
432 432 self._repo.ui.status(
433 433 (_("note: using %s as ancestor of %s and %s\n") %
434 434 (short(anc), short(self._node), short(n2))) +
435 435 ''.join(_(" alternatively, use --config "
436 436 "merge.preferancestor=%s\n") %
437 437 short(n) for n in sorted(cahs) if n != anc))
438 438 return changectx(self._repo, anc)
439 439
440 440 def descendant(self, other):
441 441 """True if other is descendant of this changeset"""
442 442 return self._repo.changelog.descendant(self._rev, other._rev)
443 443
444 444 def walk(self, match):
445 445 fset = set(match.files())
446 446 # for dirstate.walk, files=['.'] means "walk the whole tree".
447 447 # follow that here, too
448 448 fset.discard('.')
449 449
450 450 # avoid the entire walk if we're only looking for specific files
451 451 if fset and not match.anypats():
452 452 if util.all([fn in self for fn in fset]):
453 453 for fn in sorted(fset):
454 454 if match(fn):
455 455 yield fn
456 456 raise StopIteration
457 457
458 458 for fn in self:
459 459 if fn in fset:
460 460 # specified pattern is the exact name
461 461 fset.remove(fn)
462 462 if match(fn):
463 463 yield fn
464 464 for fn in sorted(fset):
465 465 if fn in self._dirs:
466 466 # specified pattern is a directory
467 467 continue
468 468 match.bad(fn, _('no such file in rev %s') % self)
469 469
470 470 class basefilectx(object):
471 471 """A filecontext object represents the common logic for its children:
472 472 filectx: read-only access to a filerevision that is already present
473 473 in the repo,
474 474 workingfilectx: a filecontext that represents files from the working
475 475 directory,
476 476 memfilectx: a filecontext that represents files in-memory."""
477 477 def __new__(cls, repo, path, *args, **kwargs):
478 478 return super(basefilectx, cls).__new__(cls)
479 479
480 480 @propertycache
481 481 def _filelog(self):
482 482 return self._repo.file(self._path)
483 483
484 484 @propertycache
485 485 def _changeid(self):
486 486 if '_changeid' in self.__dict__:
487 487 return self._changeid
488 488 elif '_changectx' in self.__dict__:
489 489 return self._changectx.rev()
490 490 else:
491 491 return self._filelog.linkrev(self._filerev)
492 492
493 493 @propertycache
494 494 def _filenode(self):
495 495 if '_fileid' in self.__dict__:
496 496 return self._filelog.lookup(self._fileid)
497 497 else:
498 498 return self._changectx.filenode(self._path)
499 499
500 500 @propertycache
501 501 def _filerev(self):
502 502 return self._filelog.rev(self._filenode)
503 503
504 504 @propertycache
505 505 def _repopath(self):
506 506 return self._path
507 507
508 508 def __nonzero__(self):
509 509 try:
510 510 self._filenode
511 511 return True
512 512 except error.LookupError:
513 513 # file is missing
514 514 return False
515 515
516 516 def __str__(self):
517 517 return "%s@%s" % (self.path(), self._changectx)
518 518
519 519 def __repr__(self):
520 520 return "<%s %s>" % (type(self).__name__, str(self))
521 521
522 522 def __hash__(self):
523 523 try:
524 524 return hash((self._path, self._filenode))
525 525 except AttributeError:
526 526 return id(self)
527 527
528 528 def __eq__(self, other):
529 529 try:
530 530 return (type(self) == type(other) and self._path == other._path
531 531 and self._filenode == other._filenode)
532 532 except AttributeError:
533 533 return False
534 534
535 535 def __ne__(self, other):
536 536 return not (self == other)
537 537
538 538 def filerev(self):
539 539 return self._filerev
540 540 def filenode(self):
541 541 return self._filenode
542 542 def flags(self):
543 543 return self._changectx.flags(self._path)
544 544 def filelog(self):
545 545 return self._filelog
546 546 def rev(self):
547 547 return self._changeid
548 548 def linkrev(self):
549 549 return self._filelog.linkrev(self._filerev)
550 550 def node(self):
551 551 return self._changectx.node()
552 552 def hex(self):
553 553 return self._changectx.hex()
554 554 def user(self):
555 555 return self._changectx.user()
556 556 def date(self):
557 557 return self._changectx.date()
558 558 def files(self):
559 559 return self._changectx.files()
560 560 def description(self):
561 561 return self._changectx.description()
562 562 def branch(self):
563 563 return self._changectx.branch()
564 564 def extra(self):
565 565 return self._changectx.extra()
566 566 def phase(self):
567 567 return self._changectx.phase()
568 568 def phasestr(self):
569 569 return self._changectx.phasestr()
570 570 def manifest(self):
571 571 return self._changectx.manifest()
572 572 def changectx(self):
573 573 return self._changectx
574 574
575 575 def path(self):
576 576 return self._path
577 577
578 578 def isbinary(self):
579 579 try:
580 580 return util.binary(self.data())
581 581 except IOError:
582 582 return False
583 583
584 584 def cmp(self, fctx):
585 585 """compare with other file context
586 586
587 587 returns True if different than fctx.
588 588 """
589 589 if (fctx._filerev is None
590 590 and (self._repo._encodefilterpats
591 591 # if file data starts with '\1\n', empty metadata block is
592 592 # prepended, which adds 4 bytes to filelog.size().
593 593 or self.size() - 4 == fctx.size())
594 594 or self.size() == fctx.size()):
595 595 return self._filelog.cmp(self._filenode, fctx.data())
596 596
597 597 return True
598 598
599 599 def parents(self):
600 600 p = self._path
601 601 fl = self._filelog
602 602 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
603 603
604 604 r = self._filelog.renamed(self._filenode)
605 605 if r:
606 606 pl[0] = (r[0], r[1], None)
607 607
608 608 return [filectx(self._repo, p, fileid=n, filelog=l)
609 609 for p, n, l in pl if n != nullid]
610 610
611 611 def p1(self):
612 612 return self.parents()[0]
613 613
614 614 def p2(self):
615 615 p = self.parents()
616 616 if len(p) == 2:
617 617 return p[1]
618 618 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
619 619
620 620 def annotate(self, follow=False, linenumber=None, diffopts=None):
621 621 '''returns a list of tuples of (ctx, line) for each line
622 622 in the file, where ctx is the filectx of the node where
623 623 that line was last changed.
624 624 This returns tuples of ((ctx, linenumber), line) for each line,
625 625 if "linenumber" parameter is NOT "None".
626 626 In such tuples, linenumber means one at the first appearance
627 627 in the managed file.
628 628 To reduce annotation cost,
629 629 this returns fixed value(False is used) as linenumber,
630 630 if "linenumber" parameter is "False".'''
631 631
632 632 def decorate_compat(text, rev):
633 633 return ([rev] * len(text.splitlines()), text)
634 634
635 635 def without_linenumber(text, rev):
636 636 return ([(rev, False)] * len(text.splitlines()), text)
637 637
638 638 def with_linenumber(text, rev):
639 639 size = len(text.splitlines())
640 640 return ([(rev, i) for i in xrange(1, size + 1)], text)
641 641
642 642 decorate = (((linenumber is None) and decorate_compat) or
643 643 (linenumber and with_linenumber) or
644 644 without_linenumber)
645 645
646 646 def pair(parent, child):
647 647 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
648 648 refine=True)
649 649 for (a1, a2, b1, b2), t in blocks:
650 650 # Changed blocks ('!') or blocks made only of blank lines ('~')
651 651 # belong to the child.
652 652 if t == '=':
653 653 child[0][b1:b2] = parent[0][a1:a2]
654 654 return child
655 655
656 656 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
657 657
658 658 def parents(f):
659 659 pl = f.parents()
660 660
661 661 # Don't return renamed parents if we aren't following.
662 662 if not follow:
663 663 pl = [p for p in pl if p.path() == f.path()]
664 664
665 665 # renamed filectx won't have a filelog yet, so set it
666 666 # from the cache to save time
667 667 for p in pl:
668 668 if not '_filelog' in p.__dict__:
669 669 p._filelog = getlog(p.path())
670 670
671 671 return pl
672 672
673 673 # use linkrev to find the first changeset where self appeared
674 674 if self.rev() != self.linkrev():
675 675 base = self.filectx(self.filenode())
676 676 else:
677 677 base = self
678 678
679 679 # This algorithm would prefer to be recursive, but Python is a
680 680 # bit recursion-hostile. Instead we do an iterative
681 681 # depth-first search.
682 682
683 683 visit = [base]
684 684 hist = {}
685 685 pcache = {}
686 686 needed = {base: 1}
687 687 while visit:
688 688 f = visit[-1]
689 689 pcached = f in pcache
690 690 if not pcached:
691 691 pcache[f] = parents(f)
692 692
693 693 ready = True
694 694 pl = pcache[f]
695 695 for p in pl:
696 696 if p not in hist:
697 697 ready = False
698 698 visit.append(p)
699 699 if not pcached:
700 700 needed[p] = needed.get(p, 0) + 1
701 701 if ready:
702 702 visit.pop()
703 703 reusable = f in hist
704 704 if reusable:
705 705 curr = hist[f]
706 706 else:
707 707 curr = decorate(f.data(), f)
708 708 for p in pl:
709 709 if not reusable:
710 710 curr = pair(hist[p], curr)
711 711 if needed[p] == 1:
712 712 del hist[p]
713 713 del needed[p]
714 714 else:
715 715 needed[p] -= 1
716 716
717 717 hist[f] = curr
718 718 pcache[f] = []
719 719
720 720 return zip(hist[base][0], hist[base][1].splitlines(True))
721 721
722 722 def ancestors(self, followfirst=False):
723 723 visit = {}
724 724 c = self
725 725 cut = followfirst and 1 or None
726 726 while True:
727 727 for parent in c.parents()[:cut]:
728 728 visit[(parent.rev(), parent.node())] = parent
729 729 if not visit:
730 730 break
731 731 c = visit.pop(max(visit))
732 732 yield c
733 733
734 734 class filectx(basefilectx):
735 735 """A filecontext object makes access to data related to a particular
736 736 filerevision convenient."""
737 737 def __init__(self, repo, path, changeid=None, fileid=None,
738 738 filelog=None, changectx=None):
739 739 """changeid can be a changeset revision, node, or tag.
740 740 fileid can be a file revision or node."""
741 741 self._repo = repo
742 742 self._path = path
743 743
744 744 assert (changeid is not None
745 745 or fileid is not None
746 746 or changectx is not None), \
747 747 ("bad args: changeid=%r, fileid=%r, changectx=%r"
748 748 % (changeid, fileid, changectx))
749 749
750 750 if filelog is not None:
751 751 self._filelog = filelog
752 752
753 753 if changeid is not None:
754 754 self._changeid = changeid
755 755 if changectx is not None:
756 756 self._changectx = changectx
757 757 if fileid is not None:
758 758 self._fileid = fileid
759 759
760 760 @propertycache
761 761 def _changectx(self):
762 762 try:
763 763 return changectx(self._repo, self._changeid)
764 764 except error.RepoLookupError:
765 765 # Linkrev may point to any revision in the repository. When the
766 766 # repository is filtered this may lead to `filectx` trying to build
767 767 # `changectx` for filtered revision. In such case we fallback to
768 768 # creating `changectx` on the unfiltered version of the reposition.
769 769 # This fallback should not be an issue because `changectx` from
770 770 # `filectx` are not used in complex operations that care about
771 771 # filtering.
772 772 #
773 773 # This fallback is a cheap and dirty fix that prevent several
774 774 # crashes. It does not ensure the behavior is correct. However the
775 775 # behavior was not correct before filtering either and "incorrect
776 776 # behavior" is seen as better as "crash"
777 777 #
778 778 # Linkrevs have several serious troubles with filtering that are
779 779 # complicated to solve. Proper handling of the issue here should be
780 780 # considered when solving linkrev issue are on the table.
781 781 return changectx(self._repo.unfiltered(), self._changeid)
782 782
783 783 def filectx(self, fileid):
784 784 '''opens an arbitrary revision of the file without
785 785 opening a new filelog'''
786 786 return filectx(self._repo, self._path, fileid=fileid,
787 787 filelog=self._filelog)
788 788
789 789 def data(self):
790 790 return self._filelog.read(self._filenode)
791 791 def size(self):
792 792 return self._filelog.size(self._filerev)
793 793
794 794 def renamed(self):
795 795 """check if file was actually renamed in this changeset revision
796 796
797 797 If rename logged in file revision, we report copy for changeset only
798 798 if file revisions linkrev points back to the changeset in question
799 799 or both changeset parents contain different file revisions.
800 800 """
801 801
802 802 renamed = self._filelog.renamed(self._filenode)
803 803 if not renamed:
804 804 return renamed
805 805
806 806 if self.rev() == self.linkrev():
807 807 return renamed
808 808
809 809 name = self.path()
810 810 fnode = self._filenode
811 811 for p in self._changectx.parents():
812 812 try:
813 813 if fnode == p.filenode(name):
814 814 return None
815 815 except error.LookupError:
816 816 pass
817 817 return renamed
818 818
819 819 def children(self):
820 820 # hard for renames
821 821 c = self._filelog.children(self._filenode)
822 822 return [filectx(self._repo, self._path, fileid=x,
823 823 filelog=self._filelog) for x in c]
824 824
825 825 class committablectx(basectx):
826 826 """A committablectx object provides common functionality for a context that
827 827 wants the ability to commit, e.g. workingctx or memctx."""
828 828 def __init__(self, repo, text="", user=None, date=None, extra=None,
829 829 changes=None):
830 830 self._repo = repo
831 831 self._rev = None
832 832 self._node = None
833 833 self._text = text
834 834 if date:
835 835 self._date = util.parsedate(date)
836 836 if user:
837 837 self._user = user
838 838 if changes:
839 839 self._status = list(changes[:4])
840 840 self._unknown = changes[4]
841 841 self._ignored = changes[5]
842 842 self._clean = changes[6]
843 843 else:
844 844 self._unknown = None
845 845 self._ignored = None
846 846 self._clean = None
847 847
848 848 self._extra = {}
849 849 if extra:
850 850 self._extra = extra.copy()
851 851 if 'branch' not in self._extra:
852 852 try:
853 853 branch = encoding.fromlocal(self._repo.dirstate.branch())
854 854 except UnicodeDecodeError:
855 855 raise util.Abort(_('branch name not in UTF-8!'))
856 856 self._extra['branch'] = branch
857 857 if self._extra['branch'] == '':
858 858 self._extra['branch'] = 'default'
859 859
860 860 def __str__(self):
861 861 return str(self._parents[0]) + "+"
862 862
863 863 def __nonzero__(self):
864 864 return True
865 865
866 866 def __contains__(self, key):
867 867 return self._repo.dirstate[key] not in "?r"
868 868
869 869 def _buildflagfunc(self):
870 870 # Create a fallback function for getting file flags when the
871 871 # filesystem doesn't support them
872 872
873 873 copiesget = self._repo.dirstate.copies().get
874 874
875 875 if len(self._parents) < 2:
876 876 # when we have one parent, it's easy: copy from parent
877 877 man = self._parents[0].manifest()
878 878 def func(f):
879 879 f = copiesget(f, f)
880 880 return man.flags(f)
881 881 else:
882 882 # merges are tricky: we try to reconstruct the unstored
883 883 # result from the merge (issue1802)
884 884 p1, p2 = self._parents
885 885 pa = p1.ancestor(p2)
886 886 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
887 887
888 888 def func(f):
889 889 f = copiesget(f, f) # may be wrong for merges with copies
890 890 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
891 891 if fl1 == fl2:
892 892 return fl1
893 893 if fl1 == fla:
894 894 return fl2
895 895 if fl2 == fla:
896 896 return fl1
897 897 return '' # punt for conflicts
898 898
899 899 return func
900 900
901 901 @propertycache
902 902 def _flagfunc(self):
903 903 return self._repo.dirstate.flagfunc(self._buildflagfunc)
904 904
905 905 @propertycache
906 906 def _manifest(self):
907 907 """generate a manifest corresponding to the working directory"""
908 908
909 909 man = self._parents[0].manifest().copy()
910 910 if len(self._parents) > 1:
911 911 man2 = self.p2().manifest()
912 912 def getman(f):
913 913 if f in man:
914 914 return man
915 915 return man2
916 916 else:
917 917 getman = lambda f: man
918 918
919 919 copied = self._repo.dirstate.copies()
920 920 ff = self._flagfunc
921 921 modified, added, removed, deleted = self._status
922 922 for i, l in (("a", added), ("m", modified)):
923 923 for f in l:
924 924 orig = copied.get(f, f)
925 925 man[f] = getman(orig).get(orig, nullid) + i
926 926 try:
927 927 man.set(f, ff(f))
928 928 except OSError:
929 929 pass
930 930
931 931 for f in deleted + removed:
932 932 if f in man:
933 933 del man[f]
934 934
935 935 return man
936 936
937 937 @propertycache
938 938 def _status(self):
939 939 return self._repo.status()[:4]
940 940
941 941 @propertycache
942 942 def _user(self):
943 943 return self._repo.ui.username()
944 944
945 945 @propertycache
946 946 def _date(self):
947 947 return util.makedate()
948 948
949 949 def user(self):
950 950 return self._user or self._repo.ui.username()
951 951 def date(self):
952 952 return self._date
953 953 def description(self):
954 954 return self._text
955 955 def files(self):
956 956 return sorted(self._status[0] + self._status[1] + self._status[2])
957 957
958 958 def modified(self):
959 959 return self._status[0]
960 960 def added(self):
961 961 return self._status[1]
962 962 def removed(self):
963 963 return self._status[2]
964 964 def deleted(self):
965 965 return self._status[3]
966 966 def unknown(self):
967 967 assert self._unknown is not None # must call status first
968 968 return self._unknown
969 969 def ignored(self):
970 970 assert self._ignored is not None # must call status first
971 971 return self._ignored
972 972 def clean(self):
973 973 assert self._clean is not None # must call status first
974 974 return self._clean
975 975 def branch(self):
976 976 return encoding.tolocal(self._extra['branch'])
977 977 def closesbranch(self):
978 978 return 'close' in self._extra
979 979 def extra(self):
980 980 return self._extra
981 981
982 982 def tags(self):
983 983 t = []
984 984 for p in self.parents():
985 985 t.extend(p.tags())
986 986 return t
987 987
988 988 def bookmarks(self):
989 989 b = []
990 990 for p in self.parents():
991 991 b.extend(p.bookmarks())
992 992 return b
993 993
994 994 def phase(self):
995 995 phase = phases.draft # default phase to draft
996 996 for p in self.parents():
997 997 phase = max(phase, p.phase())
998 998 return phase
999 999
1000 1000 def hidden(self):
1001 1001 return False
1002 1002
1003 1003 def children(self):
1004 1004 return []
1005 1005
1006 1006 def flags(self, path):
1007 1007 if '_manifest' in self.__dict__:
1008 1008 try:
1009 1009 return self._manifest.flags(path)
1010 1010 except KeyError:
1011 1011 return ''
1012 1012
1013 1013 try:
1014 1014 return self._flagfunc(path)
1015 1015 except OSError:
1016 1016 return ''
1017 1017
1018 1018 def ancestor(self, c2):
1019 1019 """return the ancestor context of self and c2"""
1020 1020 return self._parents[0].ancestor(c2) # punt on two parents for now
1021 1021
1022 1022 def walk(self, match):
1023 1023 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1024 1024 True, False))
1025 1025
1026 1026 def ancestors(self):
1027 1027 for a in self._repo.changelog.ancestors(
1028 1028 [p.rev() for p in self._parents]):
1029 1029 yield changectx(self._repo, a)
1030 1030
1031 1031 def markcommitted(self, node):
1032 1032 """Perform post-commit cleanup necessary after committing this ctx
1033 1033
1034 1034 Specifically, this updates backing stores this working context
1035 1035 wraps to reflect the fact that the changes reflected by this
1036 1036 workingctx have been committed. For example, it marks
1037 1037 modified and added files as normal in the dirstate.
1038 1038
1039 1039 """
1040 1040
1041 1041 for f in self.modified() + self.added():
1042 1042 self._repo.dirstate.normal(f)
1043 1043 for f in self.removed():
1044 1044 self._repo.dirstate.drop(f)
1045 1045 self._repo.dirstate.setparents(node)
1046 1046
1047 1047 def dirs(self):
1048 1048 return self._repo.dirstate.dirs()
1049 1049
1050 1050 class workingctx(committablectx):
1051 1051 """A workingctx object makes access to data related to
1052 1052 the current working directory convenient.
1053 1053 date - any valid date string or (unixtime, offset), or None.
1054 1054 user - username string, or None.
1055 1055 extra - a dictionary of extra values, or None.
1056 1056 changes - a list of file lists as returned by localrepo.status()
1057 1057 or None to use the repository status.
1058 1058 """
1059 1059 def __init__(self, repo, text="", user=None, date=None, extra=None,
1060 1060 changes=None):
1061 1061 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1062 1062
1063 1063 def __iter__(self):
1064 1064 d = self._repo.dirstate
1065 1065 for f in d:
1066 1066 if d[f] != 'r':
1067 1067 yield f
1068 1068
1069 1069 @propertycache
1070 1070 def _parents(self):
1071 1071 p = self._repo.dirstate.parents()
1072 1072 if p[1] == nullid:
1073 1073 p = p[:-1]
1074 1074 return [changectx(self._repo, x) for x in p]
1075 1075
1076 1076 def filectx(self, path, filelog=None):
1077 1077 """get a file context from the working directory"""
1078 1078 return workingfilectx(self._repo, path, workingctx=self,
1079 1079 filelog=filelog)
1080 1080
1081 1081 def dirty(self, missing=False, merge=True, branch=True):
1082 1082 "check whether a working directory is modified"
1083 1083 # check subrepos first
1084 1084 for s in sorted(self.substate):
1085 1085 if self.sub(s).dirty():
1086 1086 return True
1087 1087 # check current working dir
1088 1088 return ((merge and self.p2()) or
1089 1089 (branch and self.branch() != self.p1().branch()) or
1090 1090 self.modified() or self.added() or self.removed() or
1091 1091 (missing and self.deleted()))
1092 1092
1093 1093 def add(self, list, prefix=""):
1094 1094 join = lambda f: os.path.join(prefix, f)
1095 1095 wlock = self._repo.wlock()
1096 1096 ui, ds = self._repo.ui, self._repo.dirstate
1097 1097 try:
1098 1098 rejected = []
1099 1099 lstat = self._repo.wvfs.lstat
1100 1100 for f in list:
1101 1101 scmutil.checkportable(ui, join(f))
1102 1102 try:
1103 1103 st = lstat(f)
1104 1104 except OSError:
1105 1105 ui.warn(_("%s does not exist!\n") % join(f))
1106 1106 rejected.append(f)
1107 1107 continue
1108 1108 if st.st_size > 10000000:
1109 1109 ui.warn(_("%s: up to %d MB of RAM may be required "
1110 1110 "to manage this file\n"
1111 1111 "(use 'hg revert %s' to cancel the "
1112 1112 "pending addition)\n")
1113 1113 % (f, 3 * st.st_size // 1000000, join(f)))
1114 1114 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1115 1115 ui.warn(_("%s not added: only files and symlinks "
1116 1116 "supported currently\n") % join(f))
1117 1117 rejected.append(f)
1118 1118 elif ds[f] in 'amn':
1119 1119 ui.warn(_("%s already tracked!\n") % join(f))
1120 1120 elif ds[f] == 'r':
1121 1121 ds.normallookup(f)
1122 1122 else:
1123 1123 ds.add(f)
1124 1124 return rejected
1125 1125 finally:
1126 1126 wlock.release()
1127 1127
1128 1128 def forget(self, files, prefix=""):
1129 1129 join = lambda f: os.path.join(prefix, f)
1130 1130 wlock = self._repo.wlock()
1131 1131 try:
1132 1132 rejected = []
1133 1133 for f in files:
1134 1134 if f not in self._repo.dirstate:
1135 1135 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1136 1136 rejected.append(f)
1137 1137 elif self._repo.dirstate[f] != 'a':
1138 1138 self._repo.dirstate.remove(f)
1139 1139 else:
1140 1140 self._repo.dirstate.drop(f)
1141 1141 return rejected
1142 1142 finally:
1143 1143 wlock.release()
1144 1144
1145 1145 def undelete(self, list):
1146 1146 pctxs = self.parents()
1147 1147 wlock = self._repo.wlock()
1148 1148 try:
1149 1149 for f in list:
1150 1150 if self._repo.dirstate[f] != 'r':
1151 1151 self._repo.ui.warn(_("%s not removed!\n") % f)
1152 1152 else:
1153 1153 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1154 1154 t = fctx.data()
1155 1155 self._repo.wwrite(f, t, fctx.flags())
1156 1156 self._repo.dirstate.normal(f)
1157 1157 finally:
1158 1158 wlock.release()
1159 1159
1160 1160 def copy(self, source, dest):
1161 1161 try:
1162 1162 st = self._repo.wvfs.lstat(dest)
1163 1163 except OSError, err:
1164 1164 if err.errno != errno.ENOENT:
1165 1165 raise
1166 1166 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1167 1167 return
1168 1168 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1169 1169 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1170 1170 "symbolic link\n") % dest)
1171 1171 else:
1172 1172 wlock = self._repo.wlock()
1173 1173 try:
1174 1174 if self._repo.dirstate[dest] in '?r':
1175 1175 self._repo.dirstate.add(dest)
1176 1176 self._repo.dirstate.copy(source, dest)
1177 1177 finally:
1178 1178 wlock.release()
1179 1179
1180 1180 def _filtersuspectsymlink(self, files):
1181 1181 if not files or self._repo.dirstate._checklink:
1182 1182 return files
1183 1183
1184 1184 # Symlink placeholders may get non-symlink-like contents
1185 1185 # via user error or dereferencing by NFS or Samba servers,
1186 1186 # so we filter out any placeholders that don't look like a
1187 1187 # symlink
1188 1188 sane = []
1189 1189 for f in files:
1190 1190 if self.flags(f) == 'l':
1191 1191 d = self[f].data()
1192 1192 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1193 1193 self._repo.ui.debug('ignoring suspect symlink placeholder'
1194 1194 ' "%s"\n' % f)
1195 1195 continue
1196 1196 sane.append(f)
1197 1197 return sane
1198 1198
1199 1199 def _checklookup(self, files):
1200 1200 # check for any possibly clean files
1201 1201 if not files:
1202 1202 return [], []
1203 1203
1204 1204 modified = []
1205 1205 fixup = []
1206 1206 pctx = self._parents[0]
1207 1207 # do a full compare of any files that might have changed
1208 1208 for f in sorted(files):
1209 1209 if (f not in pctx or self.flags(f) != pctx.flags(f)
1210 1210 or pctx[f].cmp(self[f])):
1211 1211 modified.append(f)
1212 1212 else:
1213 1213 fixup.append(f)
1214 1214
1215 1215 # update dirstate for files that are actually clean
1216 1216 if fixup:
1217 1217 try:
1218 1218 # updating the dirstate is optional
1219 1219 # so we don't wait on the lock
1220 1220 normal = self._repo.dirstate.normal
1221 1221 wlock = self._repo.wlock(False)
1222 1222 try:
1223 1223 for f in fixup:
1224 1224 normal(f)
1225 1225 finally:
1226 1226 wlock.release()
1227 1227 except error.LockError:
1228 1228 pass
1229 1229 return modified, fixup
1230 1230
1231 def _manifestmatches(self, match, s):
1232 """Slow path for workingctx
1233
1234 The fast path is when we compare the working directory to its parent
1235 which means this function is comparing with a non-parent; therefore we
1236 need to build a manifest and return what matches.
1237 """
1238 mf = self._repo['.']._manifestmatches(match, s)
1239 modified, added, removed = s[0:3]
1240 for f in modified + added:
1241 mf[f] = None
1242 mf.set(f, self.flags(f))
1243 for f in removed:
1244 if f in mf:
1245 del mf[f]
1246 return mf
1247
1231 1248 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1232 1249 unknown=False):
1233 1250 '''Gets the status from the dirstate -- internal use only.'''
1234 1251 listignored, listclean, listunknown = ignored, clean, unknown
1235 1252 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1236 1253 subrepos = []
1237 1254 if '.hgsub' in self:
1238 1255 subrepos = sorted(self.substate)
1239 1256 s = self._repo.dirstate.status(match, subrepos, listignored,
1240 1257 listclean, listunknown)
1241 1258 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1242 1259
1243 1260 # check for any possibly clean files
1244 1261 if cmp:
1245 1262 modified2, fixup = self._checklookup(cmp)
1246 1263 modified += modified2
1247 1264
1248 1265 # update dirstate for files that are actually clean
1249 1266 if fixup and listclean:
1250 1267 clean += fixup
1251 1268
1252 1269 return [modified, added, removed, deleted, unknown, ignored, clean]
1253 1270
1254 1271 def status(self, ignored=False, clean=False, unknown=False, match=None):
1255 1272 """Explicit status query
1256 1273 Unless this method is used to query the working copy status, the
1257 1274 _status property will implicitly read the status using its default
1258 1275 arguments."""
1259 1276 listignored, listclean, listunknown = ignored, clean, unknown
1260 1277 s = self._dirstatestatus(match=match, ignored=listignored,
1261 1278 clean=listclean, unknown=listunknown)
1262 1279 modified, added, removed, deleted, unknown, ignored, clean = s
1263 1280
1264 1281 modified = self._filtersuspectsymlink(modified)
1265 1282
1266 1283 self._unknown = self._ignored = self._clean = None
1267 1284 if listunknown:
1268 1285 self._unknown = unknown
1269 1286 if listignored:
1270 1287 self._ignored = ignored
1271 1288 if listclean:
1272 1289 self._clean = clean
1273 1290 self._status = modified, added, removed, deleted
1274 1291
1275 1292 return modified, added, removed, deleted, unknown, ignored, clean
1276 1293
1277 1294
1278 1295 class committablefilectx(basefilectx):
1279 1296 """A committablefilectx provides common functionality for a file context
1280 1297 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1281 1298 def __init__(self, repo, path, filelog=None, ctx=None):
1282 1299 self._repo = repo
1283 1300 self._path = path
1284 1301 self._changeid = None
1285 1302 self._filerev = self._filenode = None
1286 1303
1287 1304 if filelog is not None:
1288 1305 self._filelog = filelog
1289 1306 if ctx:
1290 1307 self._changectx = ctx
1291 1308
1292 1309 def __nonzero__(self):
1293 1310 return True
1294 1311
1295 1312 def parents(self):
1296 1313 '''return parent filectxs, following copies if necessary'''
1297 1314 def filenode(ctx, path):
1298 1315 return ctx._manifest.get(path, nullid)
1299 1316
1300 1317 path = self._path
1301 1318 fl = self._filelog
1302 1319 pcl = self._changectx._parents
1303 1320 renamed = self.renamed()
1304 1321
1305 1322 if renamed:
1306 1323 pl = [renamed + (None,)]
1307 1324 else:
1308 1325 pl = [(path, filenode(pcl[0], path), fl)]
1309 1326
1310 1327 for pc in pcl[1:]:
1311 1328 pl.append((path, filenode(pc, path), fl))
1312 1329
1313 1330 return [filectx(self._repo, p, fileid=n, filelog=l)
1314 1331 for p, n, l in pl if n != nullid]
1315 1332
1316 1333 def children(self):
1317 1334 return []
1318 1335
1319 1336 class workingfilectx(committablefilectx):
1320 1337 """A workingfilectx object makes access to data related to a particular
1321 1338 file in the working directory convenient."""
1322 1339 def __init__(self, repo, path, filelog=None, workingctx=None):
1323 1340 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1324 1341
1325 1342 @propertycache
1326 1343 def _changectx(self):
1327 1344 return workingctx(self._repo)
1328 1345
1329 1346 def data(self):
1330 1347 return self._repo.wread(self._path)
1331 1348 def renamed(self):
1332 1349 rp = self._repo.dirstate.copied(self._path)
1333 1350 if not rp:
1334 1351 return None
1335 1352 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1336 1353
1337 1354 def size(self):
1338 1355 return self._repo.wvfs.lstat(self._path).st_size
1339 1356 def date(self):
1340 1357 t, tz = self._changectx.date()
1341 1358 try:
1342 1359 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1343 1360 except OSError, err:
1344 1361 if err.errno != errno.ENOENT:
1345 1362 raise
1346 1363 return (t, tz)
1347 1364
1348 1365 def cmp(self, fctx):
1349 1366 """compare with other file context
1350 1367
1351 1368 returns True if different than fctx.
1352 1369 """
1353 1370 # fctx should be a filectx (not a workingfilectx)
1354 1371 # invert comparison to reuse the same code path
1355 1372 return fctx.cmp(self)
1356 1373
1357 1374 class memctx(object):
1358 1375 """Use memctx to perform in-memory commits via localrepo.commitctx().
1359 1376
1360 1377 Revision information is supplied at initialization time while
1361 1378 related files data and is made available through a callback
1362 1379 mechanism. 'repo' is the current localrepo, 'parents' is a
1363 1380 sequence of two parent revisions identifiers (pass None for every
1364 1381 missing parent), 'text' is the commit message and 'files' lists
1365 1382 names of files touched by the revision (normalized and relative to
1366 1383 repository root).
1367 1384
1368 1385 filectxfn(repo, memctx, path) is a callable receiving the
1369 1386 repository, the current memctx object and the normalized path of
1370 1387 requested file, relative to repository root. It is fired by the
1371 1388 commit function for every file in 'files', but calls order is
1372 1389 undefined. If the file is available in the revision being
1373 1390 committed (updated or added), filectxfn returns a memfilectx
1374 1391 object. If the file was removed, filectxfn raises an
1375 1392 IOError. Moved files are represented by marking the source file
1376 1393 removed and the new file added with copy information (see
1377 1394 memfilectx).
1378 1395
1379 1396 user receives the committer name and defaults to current
1380 1397 repository username, date is the commit date in any format
1381 1398 supported by util.parsedate() and defaults to current date, extra
1382 1399 is a dictionary of metadata or is left empty.
1383 1400 """
1384 1401 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1385 1402 date=None, extra=None, editor=False):
1386 1403 self._repo = repo
1387 1404 self._rev = None
1388 1405 self._node = None
1389 1406 self._text = text
1390 1407 self._date = date and util.parsedate(date) or util.makedate()
1391 1408 self._user = user
1392 1409 parents = [(p or nullid) for p in parents]
1393 1410 p1, p2 = parents
1394 1411 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1395 1412 files = sorted(set(files))
1396 1413 self._status = [files, [], [], [], []]
1397 1414 self._filectxfn = filectxfn
1398 1415
1399 1416 self._extra = extra and extra.copy() or {}
1400 1417 if self._extra.get('branch', '') == '':
1401 1418 self._extra['branch'] = 'default'
1402 1419
1403 1420 if editor:
1404 1421 self._text = editor(self._repo, self, [])
1405 1422 self._repo.savecommitmessage(self._text)
1406 1423
1407 1424 def __str__(self):
1408 1425 return str(self._parents[0]) + "+"
1409 1426
1410 1427 def __int__(self):
1411 1428 return self._rev
1412 1429
1413 1430 def __nonzero__(self):
1414 1431 return True
1415 1432
1416 1433 def __getitem__(self, key):
1417 1434 return self.filectx(key)
1418 1435
1419 1436 def p1(self):
1420 1437 return self._parents[0]
1421 1438 def p2(self):
1422 1439 return self._parents[1]
1423 1440
1424 1441 def user(self):
1425 1442 return self._user or self._repo.ui.username()
1426 1443 def date(self):
1427 1444 return self._date
1428 1445 def description(self):
1429 1446 return self._text
1430 1447 def files(self):
1431 1448 return self.modified()
1432 1449 def modified(self):
1433 1450 return self._status[0]
1434 1451 def added(self):
1435 1452 return self._status[1]
1436 1453 def removed(self):
1437 1454 return self._status[2]
1438 1455 def deleted(self):
1439 1456 return self._status[3]
1440 1457 def unknown(self):
1441 1458 return self._status[4]
1442 1459 def ignored(self):
1443 1460 return self._status[5]
1444 1461 def clean(self):
1445 1462 return self._status[6]
1446 1463 def branch(self):
1447 1464 return encoding.tolocal(self._extra['branch'])
1448 1465 def extra(self):
1449 1466 return self._extra
1450 1467 def flags(self, f):
1451 1468 return self[f].flags()
1452 1469
1453 1470 def parents(self):
1454 1471 """return contexts for each parent changeset"""
1455 1472 return self._parents
1456 1473
1457 1474 def filectx(self, path, filelog=None):
1458 1475 """get a file context from the working directory"""
1459 1476 return self._filectxfn(self._repo, self, path)
1460 1477
1461 1478 def commit(self):
1462 1479 """commit context to the repo"""
1463 1480 return self._repo.commitctx(self)
1464 1481
1465 1482 class memfilectx(object):
1466 1483 """memfilectx represents an in-memory file to commit.
1467 1484
1468 1485 See memctx for more details.
1469 1486 """
1470 1487 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1471 1488 """
1472 1489 path is the normalized file path relative to repository root.
1473 1490 data is the file content as a string.
1474 1491 islink is True if the file is a symbolic link.
1475 1492 isexec is True if the file is executable.
1476 1493 copied is the source file path if current file was copied in the
1477 1494 revision being committed, or None."""
1478 1495 self._path = path
1479 1496 self._data = data
1480 1497 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1481 1498 self._copied = None
1482 1499 if copied:
1483 1500 self._copied = (copied, nullid)
1484 1501
1485 1502 def __nonzero__(self):
1486 1503 return True
1487 1504 def __str__(self):
1488 1505 return "%s@%s" % (self.path(), self._changectx)
1489 1506 def path(self):
1490 1507 return self._path
1491 1508 def data(self):
1492 1509 return self._data
1493 1510 def flags(self):
1494 1511 return self._flags
1495 1512 def isexec(self):
1496 1513 return 'x' in self._flags
1497 1514 def islink(self):
1498 1515 return 'l' in self._flags
1499 1516 def renamed(self):
1500 1517 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now