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