##// END OF EJS Templates
patch: add 'extra' argument to makememctx...
Laurent Charignon -
r25303:b7876b8f default
parent child Browse files
Show More
@@ -1,1909 +1,1910 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 copy, os, errno, stat
13 13 import obsolete as obsmod
14 14 import repoview
15 15 import fileset
16 16 import revlog
17 17
18 18 propertycache = util.propertycache
19 19
20 20 # Phony node value to stand-in for new files in some uses of
21 21 # manifests. Manifests support 21-byte hashes for nodes which are
22 22 # dirty in the working copy.
23 23 _newnode = '!' * 21
24 24
25 25 class basectx(object):
26 26 """A basectx object represents the common logic for its children:
27 27 changectx: read-only context that is already present in the repo,
28 28 workingctx: a context that represents the working directory and can
29 29 be committed,
30 30 memctx: a context that represents changes in-memory and can also
31 31 be committed."""
32 32 def __new__(cls, repo, changeid='', *args, **kwargs):
33 33 if isinstance(changeid, basectx):
34 34 return changeid
35 35
36 36 o = super(basectx, cls).__new__(cls)
37 37
38 38 o._repo = repo
39 39 o._rev = nullrev
40 40 o._node = nullid
41 41
42 42 return o
43 43
44 44 def __str__(self):
45 45 return short(self.node())
46 46
47 47 def __int__(self):
48 48 return self.rev()
49 49
50 50 def __repr__(self):
51 51 return "<%s %s>" % (type(self).__name__, str(self))
52 52
53 53 def __eq__(self, other):
54 54 try:
55 55 return type(self) == type(other) and self._rev == other._rev
56 56 except AttributeError:
57 57 return False
58 58
59 59 def __ne__(self, other):
60 60 return not (self == other)
61 61
62 62 def __contains__(self, key):
63 63 return key in self._manifest
64 64
65 65 def __getitem__(self, key):
66 66 return self.filectx(key)
67 67
68 68 def __iter__(self):
69 69 return iter(self._manifest)
70 70
71 71 def _manifestmatches(self, match, s):
72 72 """generate a new manifest filtered by the match argument
73 73
74 74 This method is for internal use only and mainly exists to provide an
75 75 object oriented way for other contexts to customize the manifest
76 76 generation.
77 77 """
78 78 return self.manifest().matches(match)
79 79
80 80 def _matchstatus(self, other, match):
81 81 """return match.always if match is none
82 82
83 83 This internal method provides a way for child objects to override the
84 84 match operator.
85 85 """
86 86 return match or matchmod.always(self._repo.root, self._repo.getcwd())
87 87
88 88 def _buildstatus(self, other, s, match, listignored, listclean,
89 89 listunknown):
90 90 """build a status with respect to another context"""
91 91 # Load earliest manifest first for caching reasons. More specifically,
92 92 # if you have revisions 1000 and 1001, 1001 is probably stored as a
93 93 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
94 94 # 1000 and cache it so that when you read 1001, we just need to apply a
95 95 # delta to what's in the cache. So that's one full reconstruction + one
96 96 # delta application.
97 97 if self.rev() is not None and self.rev() < other.rev():
98 98 self.manifest()
99 99 mf1 = other._manifestmatches(match, s)
100 100 mf2 = self._manifestmatches(match, s)
101 101
102 102 modified, added = [], []
103 103 removed = []
104 104 clean = []
105 105 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
106 106 deletedset = set(deleted)
107 107 d = mf1.diff(mf2, clean=listclean)
108 108 for fn, value in d.iteritems():
109 109 if fn in deletedset:
110 110 continue
111 111 if value is None:
112 112 clean.append(fn)
113 113 continue
114 114 (node1, flag1), (node2, flag2) = value
115 115 if node1 is None:
116 116 added.append(fn)
117 117 elif node2 is None:
118 118 removed.append(fn)
119 119 elif node2 != _newnode:
120 120 # The file was not a new file in mf2, so an entry
121 121 # from diff is really a difference.
122 122 modified.append(fn)
123 123 elif self[fn].cmp(other[fn]):
124 124 # node2 was newnode, but the working file doesn't
125 125 # match the one in mf1.
126 126 modified.append(fn)
127 127 else:
128 128 clean.append(fn)
129 129
130 130 if removed:
131 131 # need to filter files if they are already reported as removed
132 132 unknown = [fn for fn in unknown if fn not in mf1]
133 133 ignored = [fn for fn in ignored if fn not in mf1]
134 134 # if they're deleted, don't report them as removed
135 135 removed = [fn for fn in removed if fn not in deletedset]
136 136
137 137 return scmutil.status(modified, added, removed, deleted, unknown,
138 138 ignored, clean)
139 139
140 140 @propertycache
141 141 def substate(self):
142 142 return subrepo.state(self, self._repo.ui)
143 143
144 144 def subrev(self, subpath):
145 145 return self.substate[subpath][1]
146 146
147 147 def rev(self):
148 148 return self._rev
149 149 def node(self):
150 150 return self._node
151 151 def hex(self):
152 152 return hex(self.node())
153 153 def manifest(self):
154 154 return self._manifest
155 155 def repo(self):
156 156 return self._repo
157 157 def phasestr(self):
158 158 return phases.phasenames[self.phase()]
159 159 def mutable(self):
160 160 return self.phase() > phases.public
161 161
162 162 def getfileset(self, expr):
163 163 return fileset.getfileset(self, expr)
164 164
165 165 def obsolete(self):
166 166 """True if the changeset is obsolete"""
167 167 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
168 168
169 169 def extinct(self):
170 170 """True if the changeset is extinct"""
171 171 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
172 172
173 173 def unstable(self):
174 174 """True if the changeset is not obsolete but it's ancestor are"""
175 175 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
176 176
177 177 def bumped(self):
178 178 """True if the changeset try to be a successor of a public changeset
179 179
180 180 Only non-public and non-obsolete changesets may be bumped.
181 181 """
182 182 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
183 183
184 184 def divergent(self):
185 185 """Is a successors of a changeset with multiple possible successors set
186 186
187 187 Only non-public and non-obsolete changesets may be divergent.
188 188 """
189 189 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
190 190
191 191 def troubled(self):
192 192 """True if the changeset is either unstable, bumped or divergent"""
193 193 return self.unstable() or self.bumped() or self.divergent()
194 194
195 195 def troubles(self):
196 196 """return the list of troubles affecting this changesets.
197 197
198 198 Troubles are returned as strings. possible values are:
199 199 - unstable,
200 200 - bumped,
201 201 - divergent.
202 202 """
203 203 troubles = []
204 204 if self.unstable():
205 205 troubles.append('unstable')
206 206 if self.bumped():
207 207 troubles.append('bumped')
208 208 if self.divergent():
209 209 troubles.append('divergent')
210 210 return troubles
211 211
212 212 def parents(self):
213 213 """return contexts for each parent changeset"""
214 214 return self._parents
215 215
216 216 def p1(self):
217 217 return self._parents[0]
218 218
219 219 def p2(self):
220 220 if len(self._parents) == 2:
221 221 return self._parents[1]
222 222 return changectx(self._repo, -1)
223 223
224 224 def _fileinfo(self, path):
225 225 if '_manifest' in self.__dict__:
226 226 try:
227 227 return self._manifest[path], self._manifest.flags(path)
228 228 except KeyError:
229 229 raise error.ManifestLookupError(self._node, path,
230 230 _('not found in manifest'))
231 231 if '_manifestdelta' in self.__dict__ or path in self.files():
232 232 if path in self._manifestdelta:
233 233 return (self._manifestdelta[path],
234 234 self._manifestdelta.flags(path))
235 235 node, flag = self._repo.manifest.find(self._changeset[0], path)
236 236 if not node:
237 237 raise error.ManifestLookupError(self._node, path,
238 238 _('not found in manifest'))
239 239
240 240 return node, flag
241 241
242 242 def filenode(self, path):
243 243 return self._fileinfo(path)[0]
244 244
245 245 def flags(self, path):
246 246 try:
247 247 return self._fileinfo(path)[1]
248 248 except error.LookupError:
249 249 return ''
250 250
251 251 def sub(self, path):
252 252 return subrepo.subrepo(self, path)
253 253
254 254 def match(self, pats=[], include=None, exclude=None, default='glob',
255 255 listsubrepos=False):
256 256 r = self._repo
257 257 return matchmod.match(r.root, r.getcwd(), pats,
258 258 include, exclude, default,
259 259 auditor=r.auditor, ctx=self,
260 260 listsubrepos=listsubrepos)
261 261
262 262 def diff(self, ctx2=None, match=None, **opts):
263 263 """Returns a diff generator for the given contexts and matcher"""
264 264 if ctx2 is None:
265 265 ctx2 = self.p1()
266 266 if ctx2 is not None:
267 267 ctx2 = self._repo[ctx2]
268 268 diffopts = patch.diffopts(self._repo.ui, opts)
269 269 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
270 270
271 271 def dirs(self):
272 272 return self._manifest.dirs()
273 273
274 274 def hasdir(self, dir):
275 275 return self._manifest.hasdir(dir)
276 276
277 277 def dirty(self, missing=False, merge=True, branch=True):
278 278 return False
279 279
280 280 def status(self, other=None, match=None, listignored=False,
281 281 listclean=False, listunknown=False, listsubrepos=False):
282 282 """return status of files between two nodes or node and working
283 283 directory.
284 284
285 285 If other is None, compare this node with working directory.
286 286
287 287 returns (modified, added, removed, deleted, unknown, ignored, clean)
288 288 """
289 289
290 290 ctx1 = self
291 291 ctx2 = self._repo[other]
292 292
293 293 # This next code block is, admittedly, fragile logic that tests for
294 294 # reversing the contexts and wouldn't need to exist if it weren't for
295 295 # the fast (and common) code path of comparing the working directory
296 296 # with its first parent.
297 297 #
298 298 # What we're aiming for here is the ability to call:
299 299 #
300 300 # workingctx.status(parentctx)
301 301 #
302 302 # If we always built the manifest for each context and compared those,
303 303 # then we'd be done. But the special case of the above call means we
304 304 # just copy the manifest of the parent.
305 305 reversed = False
306 306 if (not isinstance(ctx1, changectx)
307 307 and isinstance(ctx2, changectx)):
308 308 reversed = True
309 309 ctx1, ctx2 = ctx2, ctx1
310 310
311 311 match = ctx2._matchstatus(ctx1, match)
312 312 r = scmutil.status([], [], [], [], [], [], [])
313 313 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
314 314 listunknown)
315 315
316 316 if reversed:
317 317 # Reverse added and removed. Clear deleted, unknown and ignored as
318 318 # these make no sense to reverse.
319 319 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
320 320 r.clean)
321 321
322 322 if listsubrepos:
323 323 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
324 324 rev2 = ctx2.subrev(subpath)
325 325 try:
326 326 submatch = matchmod.narrowmatcher(subpath, match)
327 327 s = sub.status(rev2, match=submatch, ignored=listignored,
328 328 clean=listclean, unknown=listunknown,
329 329 listsubrepos=True)
330 330 for rfiles, sfiles in zip(r, s):
331 331 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
332 332 except error.LookupError:
333 333 self._repo.ui.status(_("skipping missing "
334 334 "subrepository: %s\n") % subpath)
335 335
336 336 for l in r:
337 337 l.sort()
338 338
339 339 return r
340 340
341 341
342 342 def makememctx(repo, parents, text, user, date, branch, files, store,
343 editor=None):
343 editor=None, extra=None):
344 344 def getfilectx(repo, memctx, path):
345 345 data, mode, copied = store.getfile(path)
346 346 if data is None:
347 347 return None
348 348 islink, isexec = mode
349 349 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
350 350 copied=copied, memctx=memctx)
351 if extra is None:
351 352 extra = {}
352 353 if branch:
353 354 extra['branch'] = encoding.fromlocal(branch)
354 355 ctx = memctx(repo, parents, text, files, getfilectx, user,
355 356 date, extra, editor)
356 357 return ctx
357 358
358 359 class changectx(basectx):
359 360 """A changecontext object makes access to data related to a particular
360 361 changeset convenient. It represents a read-only context already present in
361 362 the repo."""
362 363 def __init__(self, repo, changeid=''):
363 364 """changeid is a revision number, node, or tag"""
364 365
365 366 # since basectx.__new__ already took care of copying the object, we
366 367 # don't need to do anything in __init__, so we just exit here
367 368 if isinstance(changeid, basectx):
368 369 return
369 370
370 371 if changeid == '':
371 372 changeid = '.'
372 373 self._repo = repo
373 374
374 375 try:
375 376 if isinstance(changeid, int):
376 377 self._node = repo.changelog.node(changeid)
377 378 self._rev = changeid
378 379 return
379 380 if isinstance(changeid, long):
380 381 changeid = str(changeid)
381 382 if changeid == 'null':
382 383 self._node = nullid
383 384 self._rev = nullrev
384 385 return
385 386 if changeid == 'tip':
386 387 self._node = repo.changelog.tip()
387 388 self._rev = repo.changelog.rev(self._node)
388 389 return
389 390 if changeid == '.' or changeid == repo.dirstate.p1():
390 391 # this is a hack to delay/avoid loading obsmarkers
391 392 # when we know that '.' won't be hidden
392 393 self._node = repo.dirstate.p1()
393 394 self._rev = repo.unfiltered().changelog.rev(self._node)
394 395 return
395 396 if len(changeid) == 20:
396 397 try:
397 398 self._node = changeid
398 399 self._rev = repo.changelog.rev(changeid)
399 400 return
400 401 except error.FilteredRepoLookupError:
401 402 raise
402 403 except LookupError:
403 404 pass
404 405
405 406 try:
406 407 r = int(changeid)
407 408 if str(r) != changeid:
408 409 raise ValueError
409 410 l = len(repo.changelog)
410 411 if r < 0:
411 412 r += l
412 413 if r < 0 or r >= l:
413 414 raise ValueError
414 415 self._rev = r
415 416 self._node = repo.changelog.node(r)
416 417 return
417 418 except error.FilteredIndexError:
418 419 raise
419 420 except (ValueError, OverflowError, IndexError):
420 421 pass
421 422
422 423 if len(changeid) == 40:
423 424 try:
424 425 self._node = bin(changeid)
425 426 self._rev = repo.changelog.rev(self._node)
426 427 return
427 428 except error.FilteredLookupError:
428 429 raise
429 430 except (TypeError, LookupError):
430 431 pass
431 432
432 433 # lookup bookmarks through the name interface
433 434 try:
434 435 self._node = repo.names.singlenode(repo, changeid)
435 436 self._rev = repo.changelog.rev(self._node)
436 437 return
437 438 except KeyError:
438 439 pass
439 440 except error.FilteredRepoLookupError:
440 441 raise
441 442 except error.RepoLookupError:
442 443 pass
443 444
444 445 self._node = repo.unfiltered().changelog._partialmatch(changeid)
445 446 if self._node is not None:
446 447 self._rev = repo.changelog.rev(self._node)
447 448 return
448 449
449 450 # lookup failed
450 451 # check if it might have come from damaged dirstate
451 452 #
452 453 # XXX we could avoid the unfiltered if we had a recognizable
453 454 # exception for filtered changeset access
454 455 if changeid in repo.unfiltered().dirstate.parents():
455 456 msg = _("working directory has unknown parent '%s'!")
456 457 raise error.Abort(msg % short(changeid))
457 458 try:
458 459 if len(changeid) == 20:
459 460 changeid = hex(changeid)
460 461 except TypeError:
461 462 pass
462 463 except (error.FilteredIndexError, error.FilteredLookupError,
463 464 error.FilteredRepoLookupError):
464 465 if repo.filtername.startswith('visible'):
465 466 msg = _("hidden revision '%s'") % changeid
466 467 hint = _('use --hidden to access hidden revisions')
467 468 raise error.FilteredRepoLookupError(msg, hint=hint)
468 469 msg = _("filtered revision '%s' (not in '%s' subset)")
469 470 msg %= (changeid, repo.filtername)
470 471 raise error.FilteredRepoLookupError(msg)
471 472 except IndexError:
472 473 pass
473 474 raise error.RepoLookupError(
474 475 _("unknown revision '%s'") % changeid)
475 476
476 477 def __hash__(self):
477 478 try:
478 479 return hash(self._rev)
479 480 except AttributeError:
480 481 return id(self)
481 482
482 483 def __nonzero__(self):
483 484 return self._rev != nullrev
484 485
485 486 @propertycache
486 487 def _changeset(self):
487 488 return self._repo.changelog.read(self.rev())
488 489
489 490 @propertycache
490 491 def _manifest(self):
491 492 return self._repo.manifest.read(self._changeset[0])
492 493
493 494 @propertycache
494 495 def _manifestdelta(self):
495 496 return self._repo.manifest.readdelta(self._changeset[0])
496 497
497 498 @propertycache
498 499 def _parents(self):
499 500 p = self._repo.changelog.parentrevs(self._rev)
500 501 if p[1] == nullrev:
501 502 p = p[:-1]
502 503 return [changectx(self._repo, x) for x in p]
503 504
504 505 def changeset(self):
505 506 return self._changeset
506 507 def manifestnode(self):
507 508 return self._changeset[0]
508 509
509 510 def user(self):
510 511 return self._changeset[1]
511 512 def date(self):
512 513 return self._changeset[2]
513 514 def files(self):
514 515 return self._changeset[3]
515 516 def description(self):
516 517 return self._changeset[4]
517 518 def branch(self):
518 519 return encoding.tolocal(self._changeset[5].get("branch"))
519 520 def closesbranch(self):
520 521 return 'close' in self._changeset[5]
521 522 def extra(self):
522 523 return self._changeset[5]
523 524 def tags(self):
524 525 return self._repo.nodetags(self._node)
525 526 def bookmarks(self):
526 527 return self._repo.nodebookmarks(self._node)
527 528 def phase(self):
528 529 return self._repo._phasecache.phase(self._repo, self._rev)
529 530 def hidden(self):
530 531 return self._rev in repoview.filterrevs(self._repo, 'visible')
531 532
532 533 def children(self):
533 534 """return contexts for each child changeset"""
534 535 c = self._repo.changelog.children(self._node)
535 536 return [changectx(self._repo, x) for x in c]
536 537
537 538 def ancestors(self):
538 539 for a in self._repo.changelog.ancestors([self._rev]):
539 540 yield changectx(self._repo, a)
540 541
541 542 def descendants(self):
542 543 for d in self._repo.changelog.descendants([self._rev]):
543 544 yield changectx(self._repo, d)
544 545
545 546 def filectx(self, path, fileid=None, filelog=None):
546 547 """get a file context from this changeset"""
547 548 if fileid is None:
548 549 fileid = self.filenode(path)
549 550 return filectx(self._repo, path, fileid=fileid,
550 551 changectx=self, filelog=filelog)
551 552
552 553 def ancestor(self, c2, warn=False):
553 554 """return the "best" ancestor context of self and c2
554 555
555 556 If there are multiple candidates, it will show a message and check
556 557 merge.preferancestor configuration before falling back to the
557 558 revlog ancestor."""
558 559 # deal with workingctxs
559 560 n2 = c2._node
560 561 if n2 is None:
561 562 n2 = c2._parents[0]._node
562 563 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
563 564 if not cahs:
564 565 anc = nullid
565 566 elif len(cahs) == 1:
566 567 anc = cahs[0]
567 568 else:
568 569 for r in self._repo.ui.configlist('merge', 'preferancestor'):
569 570 try:
570 571 ctx = changectx(self._repo, r)
571 572 except error.RepoLookupError:
572 573 continue
573 574 anc = ctx.node()
574 575 if anc in cahs:
575 576 break
576 577 else:
577 578 anc = self._repo.changelog.ancestor(self._node, n2)
578 579 if warn:
579 580 self._repo.ui.status(
580 581 (_("note: using %s as ancestor of %s and %s\n") %
581 582 (short(anc), short(self._node), short(n2))) +
582 583 ''.join(_(" alternatively, use --config "
583 584 "merge.preferancestor=%s\n") %
584 585 short(n) for n in sorted(cahs) if n != anc))
585 586 return changectx(self._repo, anc)
586 587
587 588 def descendant(self, other):
588 589 """True if other is descendant of this changeset"""
589 590 return self._repo.changelog.descendant(self._rev, other._rev)
590 591
591 592 def walk(self, match):
592 593 '''Generates matching file names.'''
593 594
594 595 # Override match.bad method to have message with nodeid
595 596 match = copy.copy(match)
596 597 oldbad = match.bad
597 598 def bad(fn, msg):
598 599 # The manifest doesn't know about subrepos, so don't complain about
599 600 # paths into valid subrepos.
600 601 if any(fn == s or fn.startswith(s + '/')
601 602 for s in self.substate):
602 603 return
603 604 oldbad(fn, _('no such file in rev %s') % self)
604 605 match.bad = bad
605 606
606 607 return self._manifest.walk(match)
607 608
608 609 def matches(self, match):
609 610 return self.walk(match)
610 611
611 612 class basefilectx(object):
612 613 """A filecontext object represents the common logic for its children:
613 614 filectx: read-only access to a filerevision that is already present
614 615 in the repo,
615 616 workingfilectx: a filecontext that represents files from the working
616 617 directory,
617 618 memfilectx: a filecontext that represents files in-memory."""
618 619 def __new__(cls, repo, path, *args, **kwargs):
619 620 return super(basefilectx, cls).__new__(cls)
620 621
621 622 @propertycache
622 623 def _filelog(self):
623 624 return self._repo.file(self._path)
624 625
625 626 @propertycache
626 627 def _changeid(self):
627 628 if '_changeid' in self.__dict__:
628 629 return self._changeid
629 630 elif '_changectx' in self.__dict__:
630 631 return self._changectx.rev()
631 632 elif '_descendantrev' in self.__dict__:
632 633 # this file context was created from a revision with a known
633 634 # descendant, we can (lazily) correct for linkrev aliases
634 635 return self._adjustlinkrev(self._path, self._filelog,
635 636 self._filenode, self._descendantrev)
636 637 else:
637 638 return self._filelog.linkrev(self._filerev)
638 639
639 640 @propertycache
640 641 def _filenode(self):
641 642 if '_fileid' in self.__dict__:
642 643 return self._filelog.lookup(self._fileid)
643 644 else:
644 645 return self._changectx.filenode(self._path)
645 646
646 647 @propertycache
647 648 def _filerev(self):
648 649 return self._filelog.rev(self._filenode)
649 650
650 651 @propertycache
651 652 def _repopath(self):
652 653 return self._path
653 654
654 655 def __nonzero__(self):
655 656 try:
656 657 self._filenode
657 658 return True
658 659 except error.LookupError:
659 660 # file is missing
660 661 return False
661 662
662 663 def __str__(self):
663 664 return "%s@%s" % (self.path(), self._changectx)
664 665
665 666 def __repr__(self):
666 667 return "<%s %s>" % (type(self).__name__, str(self))
667 668
668 669 def __hash__(self):
669 670 try:
670 671 return hash((self._path, self._filenode))
671 672 except AttributeError:
672 673 return id(self)
673 674
674 675 def __eq__(self, other):
675 676 try:
676 677 return (type(self) == type(other) and self._path == other._path
677 678 and self._filenode == other._filenode)
678 679 except AttributeError:
679 680 return False
680 681
681 682 def __ne__(self, other):
682 683 return not (self == other)
683 684
684 685 def filerev(self):
685 686 return self._filerev
686 687 def filenode(self):
687 688 return self._filenode
688 689 def flags(self):
689 690 return self._changectx.flags(self._path)
690 691 def filelog(self):
691 692 return self._filelog
692 693 def rev(self):
693 694 return self._changeid
694 695 def linkrev(self):
695 696 return self._filelog.linkrev(self._filerev)
696 697 def node(self):
697 698 return self._changectx.node()
698 699 def hex(self):
699 700 return self._changectx.hex()
700 701 def user(self):
701 702 return self._changectx.user()
702 703 def date(self):
703 704 return self._changectx.date()
704 705 def files(self):
705 706 return self._changectx.files()
706 707 def description(self):
707 708 return self._changectx.description()
708 709 def branch(self):
709 710 return self._changectx.branch()
710 711 def extra(self):
711 712 return self._changectx.extra()
712 713 def phase(self):
713 714 return self._changectx.phase()
714 715 def phasestr(self):
715 716 return self._changectx.phasestr()
716 717 def manifest(self):
717 718 return self._changectx.manifest()
718 719 def changectx(self):
719 720 return self._changectx
720 721 def repo(self):
721 722 return self._repo
722 723
723 724 def path(self):
724 725 return self._path
725 726
726 727 def isbinary(self):
727 728 try:
728 729 return util.binary(self.data())
729 730 except IOError:
730 731 return False
731 732 def isexec(self):
732 733 return 'x' in self.flags()
733 734 def islink(self):
734 735 return 'l' in self.flags()
735 736
736 737 def cmp(self, fctx):
737 738 """compare with other file context
738 739
739 740 returns True if different than fctx.
740 741 """
741 742 if (fctx._filerev is None
742 743 and (self._repo._encodefilterpats
743 744 # if file data starts with '\1\n', empty metadata block is
744 745 # prepended, which adds 4 bytes to filelog.size().
745 746 or self.size() - 4 == fctx.size())
746 747 or self.size() == fctx.size()):
747 748 return self._filelog.cmp(self._filenode, fctx.data())
748 749
749 750 return True
750 751
751 752 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
752 753 """return the first ancestor of <srcrev> introducing <fnode>
753 754
754 755 If the linkrev of the file revision does not point to an ancestor of
755 756 srcrev, we'll walk down the ancestors until we find one introducing
756 757 this file revision.
757 758
758 759 :repo: a localrepository object (used to access changelog and manifest)
759 760 :path: the file path
760 761 :fnode: the nodeid of the file revision
761 762 :filelog: the filelog of this path
762 763 :srcrev: the changeset revision we search ancestors from
763 764 :inclusive: if true, the src revision will also be checked
764 765 """
765 766 repo = self._repo
766 767 cl = repo.unfiltered().changelog
767 768 ma = repo.manifest
768 769 # fetch the linkrev
769 770 fr = filelog.rev(fnode)
770 771 lkr = filelog.linkrev(fr)
771 772 # hack to reuse ancestor computation when searching for renames
772 773 memberanc = getattr(self, '_ancestrycontext', None)
773 774 iteranc = None
774 775 if srcrev is None:
775 776 # wctx case, used by workingfilectx during mergecopy
776 777 revs = [p.rev() for p in self._repo[None].parents()]
777 778 inclusive = True # we skipped the real (revless) source
778 779 else:
779 780 revs = [srcrev]
780 781 if memberanc is None:
781 782 memberanc = iteranc = cl.ancestors(revs, lkr,
782 783 inclusive=inclusive)
783 784 # check if this linkrev is an ancestor of srcrev
784 785 if lkr not in memberanc:
785 786 if iteranc is None:
786 787 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
787 788 for a in iteranc:
788 789 ac = cl.read(a) # get changeset data (we avoid object creation)
789 790 if path in ac[3]: # checking the 'files' field.
790 791 # The file has been touched, check if the content is
791 792 # similar to the one we search for.
792 793 if fnode == ma.readfast(ac[0]).get(path):
793 794 return a
794 795 # In theory, we should never get out of that loop without a result.
795 796 # But if manifest uses a buggy file revision (not children of the
796 797 # one it replaces) we could. Such a buggy situation will likely
797 798 # result is crash somewhere else at to some point.
798 799 return lkr
799 800
800 801 def introrev(self):
801 802 """return the rev of the changeset which introduced this file revision
802 803
803 804 This method is different from linkrev because it take into account the
804 805 changeset the filectx was created from. It ensures the returned
805 806 revision is one of its ancestors. This prevents bugs from
806 807 'linkrev-shadowing' when a file revision is used by multiple
807 808 changesets.
808 809 """
809 810 lkr = self.linkrev()
810 811 attrs = vars(self)
811 812 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
812 813 if noctx or self.rev() == lkr:
813 814 return self.linkrev()
814 815 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
815 816 self.rev(), inclusive=True)
816 817
817 818 def _parentfilectx(self, path, fileid, filelog):
818 819 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
819 820 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
820 821 if '_changeid' in vars(self) or '_changectx' in vars(self):
821 822 # If self is associated with a changeset (probably explicitly
822 823 # fed), ensure the created filectx is associated with a
823 824 # changeset that is an ancestor of self.changectx.
824 825 # This lets us later use _adjustlinkrev to get a correct link.
825 826 fctx._descendantrev = self.rev()
826 827 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
827 828 elif '_descendantrev' in vars(self):
828 829 # Otherwise propagate _descendantrev if we have one associated.
829 830 fctx._descendantrev = self._descendantrev
830 831 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
831 832 return fctx
832 833
833 834 def parents(self):
834 835 _path = self._path
835 836 fl = self._filelog
836 837 parents = self._filelog.parents(self._filenode)
837 838 pl = [(_path, node, fl) for node in parents if node != nullid]
838 839
839 840 r = fl.renamed(self._filenode)
840 841 if r:
841 842 # - In the simple rename case, both parent are nullid, pl is empty.
842 843 # - In case of merge, only one of the parent is null id and should
843 844 # be replaced with the rename information. This parent is -always-
844 845 # the first one.
845 846 #
846 847 # As null id have always been filtered out in the previous list
847 848 # comprehension, inserting to 0 will always result in "replacing
848 849 # first nullid parent with rename information.
849 850 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
850 851
851 852 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
852 853
853 854 def p1(self):
854 855 return self.parents()[0]
855 856
856 857 def p2(self):
857 858 p = self.parents()
858 859 if len(p) == 2:
859 860 return p[1]
860 861 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
861 862
862 863 def annotate(self, follow=False, linenumber=None, diffopts=None):
863 864 '''returns a list of tuples of (ctx, line) for each line
864 865 in the file, where ctx is the filectx of the node where
865 866 that line was last changed.
866 867 This returns tuples of ((ctx, linenumber), line) for each line,
867 868 if "linenumber" parameter is NOT "None".
868 869 In such tuples, linenumber means one at the first appearance
869 870 in the managed file.
870 871 To reduce annotation cost,
871 872 this returns fixed value(False is used) as linenumber,
872 873 if "linenumber" parameter is "False".'''
873 874
874 875 if linenumber is None:
875 876 def decorate(text, rev):
876 877 return ([rev] * len(text.splitlines()), text)
877 878 elif linenumber:
878 879 def decorate(text, rev):
879 880 size = len(text.splitlines())
880 881 return ([(rev, i) for i in xrange(1, size + 1)], text)
881 882 else:
882 883 def decorate(text, rev):
883 884 return ([(rev, False)] * len(text.splitlines()), text)
884 885
885 886 def pair(parent, child):
886 887 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
887 888 refine=True)
888 889 for (a1, a2, b1, b2), t in blocks:
889 890 # Changed blocks ('!') or blocks made only of blank lines ('~')
890 891 # belong to the child.
891 892 if t == '=':
892 893 child[0][b1:b2] = parent[0][a1:a2]
893 894 return child
894 895
895 896 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
896 897
897 898 def parents(f):
898 899 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
899 900 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
900 901 # from the topmost introrev (= srcrev) down to p.linkrev() if it
901 902 # isn't an ancestor of the srcrev.
902 903 f._changeid
903 904 pl = f.parents()
904 905
905 906 # Don't return renamed parents if we aren't following.
906 907 if not follow:
907 908 pl = [p for p in pl if p.path() == f.path()]
908 909
909 910 # renamed filectx won't have a filelog yet, so set it
910 911 # from the cache to save time
911 912 for p in pl:
912 913 if not '_filelog' in p.__dict__:
913 914 p._filelog = getlog(p.path())
914 915
915 916 return pl
916 917
917 918 # use linkrev to find the first changeset where self appeared
918 919 base = self
919 920 introrev = self.introrev()
920 921 if self.rev() != introrev:
921 922 base = self.filectx(self.filenode(), changeid=introrev)
922 923 if getattr(base, '_ancestrycontext', None) is None:
923 924 cl = self._repo.changelog
924 925 if introrev is None:
925 926 # wctx is not inclusive, but works because _ancestrycontext
926 927 # is used to test filelog revisions
927 928 ac = cl.ancestors([p.rev() for p in base.parents()],
928 929 inclusive=True)
929 930 else:
930 931 ac = cl.ancestors([introrev], inclusive=True)
931 932 base._ancestrycontext = ac
932 933
933 934 # This algorithm would prefer to be recursive, but Python is a
934 935 # bit recursion-hostile. Instead we do an iterative
935 936 # depth-first search.
936 937
937 938 visit = [base]
938 939 hist = {}
939 940 pcache = {}
940 941 needed = {base: 1}
941 942 while visit:
942 943 f = visit[-1]
943 944 pcached = f in pcache
944 945 if not pcached:
945 946 pcache[f] = parents(f)
946 947
947 948 ready = True
948 949 pl = pcache[f]
949 950 for p in pl:
950 951 if p not in hist:
951 952 ready = False
952 953 visit.append(p)
953 954 if not pcached:
954 955 needed[p] = needed.get(p, 0) + 1
955 956 if ready:
956 957 visit.pop()
957 958 reusable = f in hist
958 959 if reusable:
959 960 curr = hist[f]
960 961 else:
961 962 curr = decorate(f.data(), f)
962 963 for p in pl:
963 964 if not reusable:
964 965 curr = pair(hist[p], curr)
965 966 if needed[p] == 1:
966 967 del hist[p]
967 968 del needed[p]
968 969 else:
969 970 needed[p] -= 1
970 971
971 972 hist[f] = curr
972 973 pcache[f] = []
973 974
974 975 return zip(hist[base][0], hist[base][1].splitlines(True))
975 976
976 977 def ancestors(self, followfirst=False):
977 978 visit = {}
978 979 c = self
979 980 if followfirst:
980 981 cut = 1
981 982 else:
982 983 cut = None
983 984
984 985 while True:
985 986 for parent in c.parents()[:cut]:
986 987 visit[(parent.linkrev(), parent.filenode())] = parent
987 988 if not visit:
988 989 break
989 990 c = visit.pop(max(visit))
990 991 yield c
991 992
992 993 class filectx(basefilectx):
993 994 """A filecontext object makes access to data related to a particular
994 995 filerevision convenient."""
995 996 def __init__(self, repo, path, changeid=None, fileid=None,
996 997 filelog=None, changectx=None):
997 998 """changeid can be a changeset revision, node, or tag.
998 999 fileid can be a file revision or node."""
999 1000 self._repo = repo
1000 1001 self._path = path
1001 1002
1002 1003 assert (changeid is not None
1003 1004 or fileid is not None
1004 1005 or changectx is not None), \
1005 1006 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1006 1007 % (changeid, fileid, changectx))
1007 1008
1008 1009 if filelog is not None:
1009 1010 self._filelog = filelog
1010 1011
1011 1012 if changeid is not None:
1012 1013 self._changeid = changeid
1013 1014 if changectx is not None:
1014 1015 self._changectx = changectx
1015 1016 if fileid is not None:
1016 1017 self._fileid = fileid
1017 1018
1018 1019 @propertycache
1019 1020 def _changectx(self):
1020 1021 try:
1021 1022 return changectx(self._repo, self._changeid)
1022 1023 except error.FilteredRepoLookupError:
1023 1024 # Linkrev may point to any revision in the repository. When the
1024 1025 # repository is filtered this may lead to `filectx` trying to build
1025 1026 # `changectx` for filtered revision. In such case we fallback to
1026 1027 # creating `changectx` on the unfiltered version of the reposition.
1027 1028 # This fallback should not be an issue because `changectx` from
1028 1029 # `filectx` are not used in complex operations that care about
1029 1030 # filtering.
1030 1031 #
1031 1032 # This fallback is a cheap and dirty fix that prevent several
1032 1033 # crashes. It does not ensure the behavior is correct. However the
1033 1034 # behavior was not correct before filtering either and "incorrect
1034 1035 # behavior" is seen as better as "crash"
1035 1036 #
1036 1037 # Linkrevs have several serious troubles with filtering that are
1037 1038 # complicated to solve. Proper handling of the issue here should be
1038 1039 # considered when solving linkrev issue are on the table.
1039 1040 return changectx(self._repo.unfiltered(), self._changeid)
1040 1041
1041 1042 def filectx(self, fileid, changeid=None):
1042 1043 '''opens an arbitrary revision of the file without
1043 1044 opening a new filelog'''
1044 1045 return filectx(self._repo, self._path, fileid=fileid,
1045 1046 filelog=self._filelog, changeid=changeid)
1046 1047
1047 1048 def data(self):
1048 1049 try:
1049 1050 return self._filelog.read(self._filenode)
1050 1051 except error.CensoredNodeError:
1051 1052 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1052 1053 return ""
1053 1054 raise util.Abort(_("censored node: %s") % short(self._filenode),
1054 1055 hint=_("set censor.policy to ignore errors"))
1055 1056
1056 1057 def size(self):
1057 1058 return self._filelog.size(self._filerev)
1058 1059
1059 1060 def renamed(self):
1060 1061 """check if file was actually renamed in this changeset revision
1061 1062
1062 1063 If rename logged in file revision, we report copy for changeset only
1063 1064 if file revisions linkrev points back to the changeset in question
1064 1065 or both changeset parents contain different file revisions.
1065 1066 """
1066 1067
1067 1068 renamed = self._filelog.renamed(self._filenode)
1068 1069 if not renamed:
1069 1070 return renamed
1070 1071
1071 1072 if self.rev() == self.linkrev():
1072 1073 return renamed
1073 1074
1074 1075 name = self.path()
1075 1076 fnode = self._filenode
1076 1077 for p in self._changectx.parents():
1077 1078 try:
1078 1079 if fnode == p.filenode(name):
1079 1080 return None
1080 1081 except error.LookupError:
1081 1082 pass
1082 1083 return renamed
1083 1084
1084 1085 def children(self):
1085 1086 # hard for renames
1086 1087 c = self._filelog.children(self._filenode)
1087 1088 return [filectx(self._repo, self._path, fileid=x,
1088 1089 filelog=self._filelog) for x in c]
1089 1090
1090 1091 class committablectx(basectx):
1091 1092 """A committablectx object provides common functionality for a context that
1092 1093 wants the ability to commit, e.g. workingctx or memctx."""
1093 1094 def __init__(self, repo, text="", user=None, date=None, extra=None,
1094 1095 changes=None):
1095 1096 self._repo = repo
1096 1097 self._rev = None
1097 1098 self._node = None
1098 1099 self._text = text
1099 1100 if date:
1100 1101 self._date = util.parsedate(date)
1101 1102 if user:
1102 1103 self._user = user
1103 1104 if changes:
1104 1105 self._status = changes
1105 1106
1106 1107 self._extra = {}
1107 1108 if extra:
1108 1109 self._extra = extra.copy()
1109 1110 if 'branch' not in self._extra:
1110 1111 try:
1111 1112 branch = encoding.fromlocal(self._repo.dirstate.branch())
1112 1113 except UnicodeDecodeError:
1113 1114 raise util.Abort(_('branch name not in UTF-8!'))
1114 1115 self._extra['branch'] = branch
1115 1116 if self._extra['branch'] == '':
1116 1117 self._extra['branch'] = 'default'
1117 1118
1118 1119 def __str__(self):
1119 1120 return str(self._parents[0]) + "+"
1120 1121
1121 1122 def __nonzero__(self):
1122 1123 return True
1123 1124
1124 1125 def _buildflagfunc(self):
1125 1126 # Create a fallback function for getting file flags when the
1126 1127 # filesystem doesn't support them
1127 1128
1128 1129 copiesget = self._repo.dirstate.copies().get
1129 1130
1130 1131 if len(self._parents) < 2:
1131 1132 # when we have one parent, it's easy: copy from parent
1132 1133 man = self._parents[0].manifest()
1133 1134 def func(f):
1134 1135 f = copiesget(f, f)
1135 1136 return man.flags(f)
1136 1137 else:
1137 1138 # merges are tricky: we try to reconstruct the unstored
1138 1139 # result from the merge (issue1802)
1139 1140 p1, p2 = self._parents
1140 1141 pa = p1.ancestor(p2)
1141 1142 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1142 1143
1143 1144 def func(f):
1144 1145 f = copiesget(f, f) # may be wrong for merges with copies
1145 1146 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1146 1147 if fl1 == fl2:
1147 1148 return fl1
1148 1149 if fl1 == fla:
1149 1150 return fl2
1150 1151 if fl2 == fla:
1151 1152 return fl1
1152 1153 return '' # punt for conflicts
1153 1154
1154 1155 return func
1155 1156
1156 1157 @propertycache
1157 1158 def _flagfunc(self):
1158 1159 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1159 1160
1160 1161 @propertycache
1161 1162 def _manifest(self):
1162 1163 """generate a manifest corresponding to the values in self._status
1163 1164
1164 1165 This reuse the file nodeid from parent, but we append an extra letter
1165 1166 when modified. Modified files get an extra 'm' while added files get
1166 1167 an extra 'a'. This is used by manifests merge to see that files
1167 1168 are different and by update logic to avoid deleting newly added files.
1168 1169 """
1169 1170
1170 1171 man1 = self._parents[0].manifest()
1171 1172 man = man1.copy()
1172 1173 if len(self._parents) > 1:
1173 1174 man2 = self.p2().manifest()
1174 1175 def getman(f):
1175 1176 if f in man1:
1176 1177 return man1
1177 1178 return man2
1178 1179 else:
1179 1180 getman = lambda f: man1
1180 1181
1181 1182 copied = self._repo.dirstate.copies()
1182 1183 ff = self._flagfunc
1183 1184 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1184 1185 for f in l:
1185 1186 orig = copied.get(f, f)
1186 1187 man[f] = getman(orig).get(orig, nullid) + i
1187 1188 try:
1188 1189 man.setflag(f, ff(f))
1189 1190 except OSError:
1190 1191 pass
1191 1192
1192 1193 for f in self._status.deleted + self._status.removed:
1193 1194 if f in man:
1194 1195 del man[f]
1195 1196
1196 1197 return man
1197 1198
1198 1199 @propertycache
1199 1200 def _status(self):
1200 1201 return self._repo.status()
1201 1202
1202 1203 @propertycache
1203 1204 def _user(self):
1204 1205 return self._repo.ui.username()
1205 1206
1206 1207 @propertycache
1207 1208 def _date(self):
1208 1209 return util.makedate()
1209 1210
1210 1211 def subrev(self, subpath):
1211 1212 return None
1212 1213
1213 1214 def manifestnode(self):
1214 1215 return None
1215 1216 def user(self):
1216 1217 return self._user or self._repo.ui.username()
1217 1218 def date(self):
1218 1219 return self._date
1219 1220 def description(self):
1220 1221 return self._text
1221 1222 def files(self):
1222 1223 return sorted(self._status.modified + self._status.added +
1223 1224 self._status.removed)
1224 1225
1225 1226 def modified(self):
1226 1227 return self._status.modified
1227 1228 def added(self):
1228 1229 return self._status.added
1229 1230 def removed(self):
1230 1231 return self._status.removed
1231 1232 def deleted(self):
1232 1233 return self._status.deleted
1233 1234 def branch(self):
1234 1235 return encoding.tolocal(self._extra['branch'])
1235 1236 def closesbranch(self):
1236 1237 return 'close' in self._extra
1237 1238 def extra(self):
1238 1239 return self._extra
1239 1240
1240 1241 def tags(self):
1241 1242 t = []
1242 1243 for p in self.parents():
1243 1244 t.extend(p.tags())
1244 1245 return t
1245 1246
1246 1247 def bookmarks(self):
1247 1248 b = []
1248 1249 for p in self.parents():
1249 1250 b.extend(p.bookmarks())
1250 1251 return b
1251 1252
1252 1253 def phase(self):
1253 1254 phase = phases.draft # default phase to draft
1254 1255 for p in self.parents():
1255 1256 phase = max(phase, p.phase())
1256 1257 return phase
1257 1258
1258 1259 def hidden(self):
1259 1260 return False
1260 1261
1261 1262 def children(self):
1262 1263 return []
1263 1264
1264 1265 def flags(self, path):
1265 1266 if '_manifest' in self.__dict__:
1266 1267 try:
1267 1268 return self._manifest.flags(path)
1268 1269 except KeyError:
1269 1270 return ''
1270 1271
1271 1272 try:
1272 1273 return self._flagfunc(path)
1273 1274 except OSError:
1274 1275 return ''
1275 1276
1276 1277 def ancestor(self, c2):
1277 1278 """return the "best" ancestor context of self and c2"""
1278 1279 return self._parents[0].ancestor(c2) # punt on two parents for now
1279 1280
1280 1281 def walk(self, match):
1281 1282 '''Generates matching file names.'''
1282 1283 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1283 1284 True, False))
1284 1285
1285 1286 def matches(self, match):
1286 1287 return sorted(self._repo.dirstate.matches(match))
1287 1288
1288 1289 def ancestors(self):
1289 1290 for p in self._parents:
1290 1291 yield p
1291 1292 for a in self._repo.changelog.ancestors(
1292 1293 [p.rev() for p in self._parents]):
1293 1294 yield changectx(self._repo, a)
1294 1295
1295 1296 def markcommitted(self, node):
1296 1297 """Perform post-commit cleanup necessary after committing this ctx
1297 1298
1298 1299 Specifically, this updates backing stores this working context
1299 1300 wraps to reflect the fact that the changes reflected by this
1300 1301 workingctx have been committed. For example, it marks
1301 1302 modified and added files as normal in the dirstate.
1302 1303
1303 1304 """
1304 1305
1305 1306 self._repo.dirstate.beginparentchange()
1306 1307 for f in self.modified() + self.added():
1307 1308 self._repo.dirstate.normal(f)
1308 1309 for f in self.removed():
1309 1310 self._repo.dirstate.drop(f)
1310 1311 self._repo.dirstate.setparents(node)
1311 1312 self._repo.dirstate.endparentchange()
1312 1313
1313 1314 class workingctx(committablectx):
1314 1315 """A workingctx object makes access to data related to
1315 1316 the current working directory convenient.
1316 1317 date - any valid date string or (unixtime, offset), or None.
1317 1318 user - username string, or None.
1318 1319 extra - a dictionary of extra values, or None.
1319 1320 changes - a list of file lists as returned by localrepo.status()
1320 1321 or None to use the repository status.
1321 1322 """
1322 1323 def __init__(self, repo, text="", user=None, date=None, extra=None,
1323 1324 changes=None):
1324 1325 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1325 1326
1326 1327 def __iter__(self):
1327 1328 d = self._repo.dirstate
1328 1329 for f in d:
1329 1330 if d[f] != 'r':
1330 1331 yield f
1331 1332
1332 1333 def __contains__(self, key):
1333 1334 return self._repo.dirstate[key] not in "?r"
1334 1335
1335 1336 @propertycache
1336 1337 def _parents(self):
1337 1338 p = self._repo.dirstate.parents()
1338 1339 if p[1] == nullid:
1339 1340 p = p[:-1]
1340 1341 return [changectx(self._repo, x) for x in p]
1341 1342
1342 1343 def filectx(self, path, filelog=None):
1343 1344 """get a file context from the working directory"""
1344 1345 return workingfilectx(self._repo, path, workingctx=self,
1345 1346 filelog=filelog)
1346 1347
1347 1348 def dirty(self, missing=False, merge=True, branch=True):
1348 1349 "check whether a working directory is modified"
1349 1350 # check subrepos first
1350 1351 for s in sorted(self.substate):
1351 1352 if self.sub(s).dirty():
1352 1353 return True
1353 1354 # check current working dir
1354 1355 return ((merge and self.p2()) or
1355 1356 (branch and self.branch() != self.p1().branch()) or
1356 1357 self.modified() or self.added() or self.removed() or
1357 1358 (missing and self.deleted()))
1358 1359
1359 1360 def add(self, list, prefix=""):
1360 1361 join = lambda f: os.path.join(prefix, f)
1361 1362 wlock = self._repo.wlock()
1362 1363 ui, ds = self._repo.ui, self._repo.dirstate
1363 1364 try:
1364 1365 rejected = []
1365 1366 lstat = self._repo.wvfs.lstat
1366 1367 for f in list:
1367 1368 scmutil.checkportable(ui, join(f))
1368 1369 try:
1369 1370 st = lstat(f)
1370 1371 except OSError:
1371 1372 ui.warn(_("%s does not exist!\n") % join(f))
1372 1373 rejected.append(f)
1373 1374 continue
1374 1375 if st.st_size > 10000000:
1375 1376 ui.warn(_("%s: up to %d MB of RAM may be required "
1376 1377 "to manage this file\n"
1377 1378 "(use 'hg revert %s' to cancel the "
1378 1379 "pending addition)\n")
1379 1380 % (f, 3 * st.st_size // 1000000, join(f)))
1380 1381 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1381 1382 ui.warn(_("%s not added: only files and symlinks "
1382 1383 "supported currently\n") % join(f))
1383 1384 rejected.append(f)
1384 1385 elif ds[f] in 'amn':
1385 1386 ui.warn(_("%s already tracked!\n") % join(f))
1386 1387 elif ds[f] == 'r':
1387 1388 ds.normallookup(f)
1388 1389 else:
1389 1390 ds.add(f)
1390 1391 return rejected
1391 1392 finally:
1392 1393 wlock.release()
1393 1394
1394 1395 def forget(self, files, prefix=""):
1395 1396 join = lambda f: os.path.join(prefix, f)
1396 1397 wlock = self._repo.wlock()
1397 1398 try:
1398 1399 rejected = []
1399 1400 for f in files:
1400 1401 if f not in self._repo.dirstate:
1401 1402 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1402 1403 rejected.append(f)
1403 1404 elif self._repo.dirstate[f] != 'a':
1404 1405 self._repo.dirstate.remove(f)
1405 1406 else:
1406 1407 self._repo.dirstate.drop(f)
1407 1408 return rejected
1408 1409 finally:
1409 1410 wlock.release()
1410 1411
1411 1412 def undelete(self, list):
1412 1413 pctxs = self.parents()
1413 1414 wlock = self._repo.wlock()
1414 1415 try:
1415 1416 for f in list:
1416 1417 if self._repo.dirstate[f] != 'r':
1417 1418 self._repo.ui.warn(_("%s not removed!\n") % f)
1418 1419 else:
1419 1420 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1420 1421 t = fctx.data()
1421 1422 self._repo.wwrite(f, t, fctx.flags())
1422 1423 self._repo.dirstate.normal(f)
1423 1424 finally:
1424 1425 wlock.release()
1425 1426
1426 1427 def copy(self, source, dest):
1427 1428 try:
1428 1429 st = self._repo.wvfs.lstat(dest)
1429 1430 except OSError, err:
1430 1431 if err.errno != errno.ENOENT:
1431 1432 raise
1432 1433 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1433 1434 return
1434 1435 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1435 1436 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1436 1437 "symbolic link\n") % dest)
1437 1438 else:
1438 1439 wlock = self._repo.wlock()
1439 1440 try:
1440 1441 if self._repo.dirstate[dest] in '?':
1441 1442 self._repo.dirstate.add(dest)
1442 1443 elif self._repo.dirstate[dest] in 'r':
1443 1444 self._repo.dirstate.normallookup(dest)
1444 1445 self._repo.dirstate.copy(source, dest)
1445 1446 finally:
1446 1447 wlock.release()
1447 1448
1448 1449 def match(self, pats=[], include=None, exclude=None, default='glob',
1449 1450 listsubrepos=False):
1450 1451 r = self._repo
1451 1452
1452 1453 # Only a case insensitive filesystem needs magic to translate user input
1453 1454 # to actual case in the filesystem.
1454 1455 if not util.checkcase(r.root):
1455 1456 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1456 1457 exclude, default, r.auditor, self,
1457 1458 listsubrepos=listsubrepos)
1458 1459 return matchmod.match(r.root, r.getcwd(), pats,
1459 1460 include, exclude, default,
1460 1461 auditor=r.auditor, ctx=self,
1461 1462 listsubrepos=listsubrepos)
1462 1463
1463 1464 def _filtersuspectsymlink(self, files):
1464 1465 if not files or self._repo.dirstate._checklink:
1465 1466 return files
1466 1467
1467 1468 # Symlink placeholders may get non-symlink-like contents
1468 1469 # via user error or dereferencing by NFS or Samba servers,
1469 1470 # so we filter out any placeholders that don't look like a
1470 1471 # symlink
1471 1472 sane = []
1472 1473 for f in files:
1473 1474 if self.flags(f) == 'l':
1474 1475 d = self[f].data()
1475 1476 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1476 1477 self._repo.ui.debug('ignoring suspect symlink placeholder'
1477 1478 ' "%s"\n' % f)
1478 1479 continue
1479 1480 sane.append(f)
1480 1481 return sane
1481 1482
1482 1483 def _checklookup(self, files):
1483 1484 # check for any possibly clean files
1484 1485 if not files:
1485 1486 return [], []
1486 1487
1487 1488 modified = []
1488 1489 fixup = []
1489 1490 pctx = self._parents[0]
1490 1491 # do a full compare of any files that might have changed
1491 1492 for f in sorted(files):
1492 1493 if (f not in pctx or self.flags(f) != pctx.flags(f)
1493 1494 or pctx[f].cmp(self[f])):
1494 1495 modified.append(f)
1495 1496 else:
1496 1497 fixup.append(f)
1497 1498
1498 1499 # update dirstate for files that are actually clean
1499 1500 if fixup:
1500 1501 try:
1501 1502 # updating the dirstate is optional
1502 1503 # so we don't wait on the lock
1503 1504 # wlock can invalidate the dirstate, so cache normal _after_
1504 1505 # taking the lock
1505 1506 wlock = self._repo.wlock(False)
1506 1507 normal = self._repo.dirstate.normal
1507 1508 try:
1508 1509 for f in fixup:
1509 1510 normal(f)
1510 1511 finally:
1511 1512 wlock.release()
1512 1513 except error.LockError:
1513 1514 pass
1514 1515 return modified, fixup
1515 1516
1516 1517 def _manifestmatches(self, match, s):
1517 1518 """Slow path for workingctx
1518 1519
1519 1520 The fast path is when we compare the working directory to its parent
1520 1521 which means this function is comparing with a non-parent; therefore we
1521 1522 need to build a manifest and return what matches.
1522 1523 """
1523 1524 mf = self._repo['.']._manifestmatches(match, s)
1524 1525 for f in s.modified + s.added:
1525 1526 mf[f] = _newnode
1526 1527 mf.setflag(f, self.flags(f))
1527 1528 for f in s.removed:
1528 1529 if f in mf:
1529 1530 del mf[f]
1530 1531 return mf
1531 1532
1532 1533 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1533 1534 unknown=False):
1534 1535 '''Gets the status from the dirstate -- internal use only.'''
1535 1536 listignored, listclean, listunknown = ignored, clean, unknown
1536 1537 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1537 1538 subrepos = []
1538 1539 if '.hgsub' in self:
1539 1540 subrepos = sorted(self.substate)
1540 1541 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1541 1542 listclean, listunknown)
1542 1543
1543 1544 # check for any possibly clean files
1544 1545 if cmp:
1545 1546 modified2, fixup = self._checklookup(cmp)
1546 1547 s.modified.extend(modified2)
1547 1548
1548 1549 # update dirstate for files that are actually clean
1549 1550 if fixup and listclean:
1550 1551 s.clean.extend(fixup)
1551 1552
1552 1553 if match.always():
1553 1554 # cache for performance
1554 1555 if s.unknown or s.ignored or s.clean:
1555 1556 # "_status" is cached with list*=False in the normal route
1556 1557 self._status = scmutil.status(s.modified, s.added, s.removed,
1557 1558 s.deleted, [], [], [])
1558 1559 else:
1559 1560 self._status = s
1560 1561
1561 1562 return s
1562 1563
1563 1564 def _buildstatus(self, other, s, match, listignored, listclean,
1564 1565 listunknown):
1565 1566 """build a status with respect to another context
1566 1567
1567 1568 This includes logic for maintaining the fast path of status when
1568 1569 comparing the working directory against its parent, which is to skip
1569 1570 building a new manifest if self (working directory) is not comparing
1570 1571 against its parent (repo['.']).
1571 1572 """
1572 1573 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1573 1574 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1574 1575 # might have accidentally ended up with the entire contents of the file
1575 1576 # they are supposed to be linking to.
1576 1577 s.modified[:] = self._filtersuspectsymlink(s.modified)
1577 1578 if other != self._repo['.']:
1578 1579 s = super(workingctx, self)._buildstatus(other, s, match,
1579 1580 listignored, listclean,
1580 1581 listunknown)
1581 1582 return s
1582 1583
1583 1584 def _matchstatus(self, other, match):
1584 1585 """override the match method with a filter for directory patterns
1585 1586
1586 1587 We use inheritance to customize the match.bad method only in cases of
1587 1588 workingctx since it belongs only to the working directory when
1588 1589 comparing against the parent changeset.
1589 1590
1590 1591 If we aren't comparing against the working directory's parent, then we
1591 1592 just use the default match object sent to us.
1592 1593 """
1593 1594 superself = super(workingctx, self)
1594 1595 match = superself._matchstatus(other, match)
1595 1596 if other != self._repo['.']:
1596 1597 def bad(f, msg):
1597 1598 # 'f' may be a directory pattern from 'match.files()',
1598 1599 # so 'f not in ctx1' is not enough
1599 1600 if f not in other and not other.hasdir(f):
1600 1601 self._repo.ui.warn('%s: %s\n' %
1601 1602 (self._repo.dirstate.pathto(f), msg))
1602 1603 match.bad = bad
1603 1604 return match
1604 1605
1605 1606 class committablefilectx(basefilectx):
1606 1607 """A committablefilectx provides common functionality for a file context
1607 1608 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1608 1609 def __init__(self, repo, path, filelog=None, ctx=None):
1609 1610 self._repo = repo
1610 1611 self._path = path
1611 1612 self._changeid = None
1612 1613 self._filerev = self._filenode = None
1613 1614
1614 1615 if filelog is not None:
1615 1616 self._filelog = filelog
1616 1617 if ctx:
1617 1618 self._changectx = ctx
1618 1619
1619 1620 def __nonzero__(self):
1620 1621 return True
1621 1622
1622 1623 def linkrev(self):
1623 1624 # linked to self._changectx no matter if file is modified or not
1624 1625 return self.rev()
1625 1626
1626 1627 def parents(self):
1627 1628 '''return parent filectxs, following copies if necessary'''
1628 1629 def filenode(ctx, path):
1629 1630 return ctx._manifest.get(path, nullid)
1630 1631
1631 1632 path = self._path
1632 1633 fl = self._filelog
1633 1634 pcl = self._changectx._parents
1634 1635 renamed = self.renamed()
1635 1636
1636 1637 if renamed:
1637 1638 pl = [renamed + (None,)]
1638 1639 else:
1639 1640 pl = [(path, filenode(pcl[0], path), fl)]
1640 1641
1641 1642 for pc in pcl[1:]:
1642 1643 pl.append((path, filenode(pc, path), fl))
1643 1644
1644 1645 return [self._parentfilectx(p, fileid=n, filelog=l)
1645 1646 for p, n, l in pl if n != nullid]
1646 1647
1647 1648 def children(self):
1648 1649 return []
1649 1650
1650 1651 class workingfilectx(committablefilectx):
1651 1652 """A workingfilectx object makes access to data related to a particular
1652 1653 file in the working directory convenient."""
1653 1654 def __init__(self, repo, path, filelog=None, workingctx=None):
1654 1655 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1655 1656
1656 1657 @propertycache
1657 1658 def _changectx(self):
1658 1659 return workingctx(self._repo)
1659 1660
1660 1661 def data(self):
1661 1662 return self._repo.wread(self._path)
1662 1663 def renamed(self):
1663 1664 rp = self._repo.dirstate.copied(self._path)
1664 1665 if not rp:
1665 1666 return None
1666 1667 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1667 1668
1668 1669 def size(self):
1669 1670 return self._repo.wvfs.lstat(self._path).st_size
1670 1671 def date(self):
1671 1672 t, tz = self._changectx.date()
1672 1673 try:
1673 1674 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1674 1675 except OSError, err:
1675 1676 if err.errno != errno.ENOENT:
1676 1677 raise
1677 1678 return (t, tz)
1678 1679
1679 1680 def cmp(self, fctx):
1680 1681 """compare with other file context
1681 1682
1682 1683 returns True if different than fctx.
1683 1684 """
1684 1685 # fctx should be a filectx (not a workingfilectx)
1685 1686 # invert comparison to reuse the same code path
1686 1687 return fctx.cmp(self)
1687 1688
1688 1689 def remove(self, ignoremissing=False):
1689 1690 """wraps unlink for a repo's working directory"""
1690 1691 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1691 1692
1692 1693 def write(self, data, flags):
1693 1694 """wraps repo.wwrite"""
1694 1695 self._repo.wwrite(self._path, data, flags)
1695 1696
1696 1697 class workingcommitctx(workingctx):
1697 1698 """A workingcommitctx object makes access to data related to
1698 1699 the revision being committed convenient.
1699 1700
1700 1701 This hides changes in the working directory, if they aren't
1701 1702 committed in this context.
1702 1703 """
1703 1704 def __init__(self, repo, changes,
1704 1705 text="", user=None, date=None, extra=None):
1705 1706 super(workingctx, self).__init__(repo, text, user, date, extra,
1706 1707 changes)
1707 1708
1708 1709 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1709 1710 unknown=False):
1710 1711 """Return matched files only in ``self._status``
1711 1712
1712 1713 Uncommitted files appear "clean" via this context, even if
1713 1714 they aren't actually so in the working directory.
1714 1715 """
1715 1716 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1716 1717 if clean:
1717 1718 clean = [f for f in self._manifest if f not in self._changedset]
1718 1719 else:
1719 1720 clean = []
1720 1721 return scmutil.status([f for f in self._status.modified if match(f)],
1721 1722 [f for f in self._status.added if match(f)],
1722 1723 [f for f in self._status.removed if match(f)],
1723 1724 [], [], [], clean)
1724 1725
1725 1726 @propertycache
1726 1727 def _changedset(self):
1727 1728 """Return the set of files changed in this context
1728 1729 """
1729 1730 changed = set(self._status.modified)
1730 1731 changed.update(self._status.added)
1731 1732 changed.update(self._status.removed)
1732 1733 return changed
1733 1734
1734 1735 class memctx(committablectx):
1735 1736 """Use memctx to perform in-memory commits via localrepo.commitctx().
1736 1737
1737 1738 Revision information is supplied at initialization time while
1738 1739 related files data and is made available through a callback
1739 1740 mechanism. 'repo' is the current localrepo, 'parents' is a
1740 1741 sequence of two parent revisions identifiers (pass None for every
1741 1742 missing parent), 'text' is the commit message and 'files' lists
1742 1743 names of files touched by the revision (normalized and relative to
1743 1744 repository root).
1744 1745
1745 1746 filectxfn(repo, memctx, path) is a callable receiving the
1746 1747 repository, the current memctx object and the normalized path of
1747 1748 requested file, relative to repository root. It is fired by the
1748 1749 commit function for every file in 'files', but calls order is
1749 1750 undefined. If the file is available in the revision being
1750 1751 committed (updated or added), filectxfn returns a memfilectx
1751 1752 object. If the file was removed, filectxfn raises an
1752 1753 IOError. Moved files are represented by marking the source file
1753 1754 removed and the new file added with copy information (see
1754 1755 memfilectx).
1755 1756
1756 1757 user receives the committer name and defaults to current
1757 1758 repository username, date is the commit date in any format
1758 1759 supported by util.parsedate() and defaults to current date, extra
1759 1760 is a dictionary of metadata or is left empty.
1760 1761 """
1761 1762
1762 1763 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1763 1764 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1764 1765 # this field to determine what to do in filectxfn.
1765 1766 _returnnoneformissingfiles = True
1766 1767
1767 1768 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1768 1769 date=None, extra=None, editor=False):
1769 1770 super(memctx, self).__init__(repo, text, user, date, extra)
1770 1771 self._rev = None
1771 1772 self._node = None
1772 1773 parents = [(p or nullid) for p in parents]
1773 1774 p1, p2 = parents
1774 1775 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1775 1776 files = sorted(set(files))
1776 1777 self._files = files
1777 1778 self.substate = {}
1778 1779
1779 1780 # if store is not callable, wrap it in a function
1780 1781 if not callable(filectxfn):
1781 1782 def getfilectx(repo, memctx, path):
1782 1783 fctx = filectxfn[path]
1783 1784 # this is weird but apparently we only keep track of one parent
1784 1785 # (why not only store that instead of a tuple?)
1785 1786 copied = fctx.renamed()
1786 1787 if copied:
1787 1788 copied = copied[0]
1788 1789 return memfilectx(repo, path, fctx.data(),
1789 1790 islink=fctx.islink(), isexec=fctx.isexec(),
1790 1791 copied=copied, memctx=memctx)
1791 1792 self._filectxfn = getfilectx
1792 1793 else:
1793 1794 # "util.cachefunc" reduces invocation of possibly expensive
1794 1795 # "filectxfn" for performance (e.g. converting from another VCS)
1795 1796 self._filectxfn = util.cachefunc(filectxfn)
1796 1797
1797 1798 if extra:
1798 1799 self._extra = extra.copy()
1799 1800 else:
1800 1801 self._extra = {}
1801 1802
1802 1803 if self._extra.get('branch', '') == '':
1803 1804 self._extra['branch'] = 'default'
1804 1805
1805 1806 if editor:
1806 1807 self._text = editor(self._repo, self, [])
1807 1808 self._repo.savecommitmessage(self._text)
1808 1809
1809 1810 def filectx(self, path, filelog=None):
1810 1811 """get a file context from the working directory
1811 1812
1812 1813 Returns None if file doesn't exist and should be removed."""
1813 1814 return self._filectxfn(self._repo, self, path)
1814 1815
1815 1816 def commit(self):
1816 1817 """commit context to the repo"""
1817 1818 return self._repo.commitctx(self)
1818 1819
1819 1820 @propertycache
1820 1821 def _manifest(self):
1821 1822 """generate a manifest based on the return values of filectxfn"""
1822 1823
1823 1824 # keep this simple for now; just worry about p1
1824 1825 pctx = self._parents[0]
1825 1826 man = pctx.manifest().copy()
1826 1827
1827 1828 for f in self._status.modified:
1828 1829 p1node = nullid
1829 1830 p2node = nullid
1830 1831 p = pctx[f].parents() # if file isn't in pctx, check p2?
1831 1832 if len(p) > 0:
1832 1833 p1node = p[0].node()
1833 1834 if len(p) > 1:
1834 1835 p2node = p[1].node()
1835 1836 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1836 1837
1837 1838 for f in self._status.added:
1838 1839 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1839 1840
1840 1841 for f in self._status.removed:
1841 1842 if f in man:
1842 1843 del man[f]
1843 1844
1844 1845 return man
1845 1846
1846 1847 @propertycache
1847 1848 def _status(self):
1848 1849 """Calculate exact status from ``files`` specified at construction
1849 1850 """
1850 1851 man1 = self.p1().manifest()
1851 1852 p2 = self._parents[1]
1852 1853 # "1 < len(self._parents)" can't be used for checking
1853 1854 # existence of the 2nd parent, because "memctx._parents" is
1854 1855 # explicitly initialized by the list, of which length is 2.
1855 1856 if p2.node() != nullid:
1856 1857 man2 = p2.manifest()
1857 1858 managing = lambda f: f in man1 or f in man2
1858 1859 else:
1859 1860 managing = lambda f: f in man1
1860 1861
1861 1862 modified, added, removed = [], [], []
1862 1863 for f in self._files:
1863 1864 if not managing(f):
1864 1865 added.append(f)
1865 1866 elif self[f]:
1866 1867 modified.append(f)
1867 1868 else:
1868 1869 removed.append(f)
1869 1870
1870 1871 return scmutil.status(modified, added, removed, [], [], [], [])
1871 1872
1872 1873 class memfilectx(committablefilectx):
1873 1874 """memfilectx represents an in-memory file to commit.
1874 1875
1875 1876 See memctx and committablefilectx for more details.
1876 1877 """
1877 1878 def __init__(self, repo, path, data, islink=False,
1878 1879 isexec=False, copied=None, memctx=None):
1879 1880 """
1880 1881 path is the normalized file path relative to repository root.
1881 1882 data is the file content as a string.
1882 1883 islink is True if the file is a symbolic link.
1883 1884 isexec is True if the file is executable.
1884 1885 copied is the source file path if current file was copied in the
1885 1886 revision being committed, or None."""
1886 1887 super(memfilectx, self).__init__(repo, path, None, memctx)
1887 1888 self._data = data
1888 1889 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1889 1890 self._copied = None
1890 1891 if copied:
1891 1892 self._copied = (copied, nullid)
1892 1893
1893 1894 def data(self):
1894 1895 return self._data
1895 1896 def size(self):
1896 1897 return len(self.data())
1897 1898 def flags(self):
1898 1899 return self._flags
1899 1900 def renamed(self):
1900 1901 return self._copied
1901 1902
1902 1903 def remove(self, ignoremissing=False):
1903 1904 """wraps unlink for a repo's working directory"""
1904 1905 # need to figure out what to do here
1905 1906 del self._changectx[self._path]
1906 1907
1907 1908 def write(self, data, flags):
1908 1909 """wraps repo.wwrite"""
1909 1910 self._data = data
General Comments 0
You need to be logged in to leave comments. Login now