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