comparison mercurial/context.py @ 42291:a13b30555ffb

context: reuse changectx._copies() in all but workingctx This moves the dirstate-specific _copies() implementation from committablectx into workingctx where it should be (I think all dirstate-specific stuff should be moved into workingctx). The part of changectx._copies() that is for producing changeset-wide copy dicts from the filectxs is moved into basectx so it's reused by the other subclasses. The part of changectx._copies() that's about reading copy information from the changeset remains there. This fixes in-memory rebase (and makes `hg convert` able to write copies to changesets). Differential Revision: https://phab.mercurial-scm.org/D6219
author Martin von Zweigbergk <martinvonz@google.com>
date Fri, 10 May 2019 13:41:42 -0700
parents e79aeb518aa1
children 491855ea9d62
comparison
equal deleted inserted replaced
42290:e79aeb518aa1 42291:a13b30555ffb
270 try: 270 try:
271 return self._fileinfo(path)[1] 271 return self._fileinfo(path)[1]
272 except error.LookupError: 272 except error.LookupError:
273 return '' 273 return ''
274 274
275 def sub(self, path, allowcreate=True):
276 '''return a subrepo for the stored revision of path, never wdir()'''
277 return subrepo.subrepo(self, path, allowcreate=allowcreate)
278
279 def nullsub(self, path, pctx):
280 return subrepo.nullsubrepo(self, path, pctx)
281
282 def workingsub(self, path):
283 '''return a subrepo for the stored revision, or wdir if this is a wdir
284 context.
285 '''
286 return subrepo.subrepo(self, path, allowwdir=True)
287
288 def match(self, pats=None, include=None, exclude=None, default='glob',
289 listsubrepos=False, badfn=None):
290 r = self._repo
291 return matchmod.match(r.root, r.getcwd(), pats,
292 include, exclude, default,
293 auditor=r.nofsauditor, ctx=self,
294 listsubrepos=listsubrepos, badfn=badfn)
295
296 def diff(self, ctx2=None, match=None, changes=None, opts=None,
297 losedatafn=None, pathfn=None, copy=None,
298 copysourcematch=None, hunksfilterfn=None):
299 """Returns a diff generator for the given contexts and matcher"""
300 if ctx2 is None:
301 ctx2 = self.p1()
302 if ctx2 is not None:
303 ctx2 = self._repo[ctx2]
304 return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
305 opts=opts, losedatafn=losedatafn, pathfn=pathfn,
306 copy=copy, copysourcematch=copysourcematch,
307 hunksfilterfn=hunksfilterfn)
308
309 def dirs(self):
310 return self._manifest.dirs()
311
312 def hasdir(self, dir):
313 return self._manifest.hasdir(dir)
314
315 def status(self, other=None, match=None, listignored=False,
316 listclean=False, listunknown=False, listsubrepos=False):
317 """return status of files between two nodes or node and working
318 directory.
319
320 If other is None, compare this node with working directory.
321
322 returns (modified, added, removed, deleted, unknown, ignored, clean)
323 """
324
325 ctx1 = self
326 ctx2 = self._repo[other]
327
328 # This next code block is, admittedly, fragile logic that tests for
329 # reversing the contexts and wouldn't need to exist if it weren't for
330 # the fast (and common) code path of comparing the working directory
331 # with its first parent.
332 #
333 # What we're aiming for here is the ability to call:
334 #
335 # workingctx.status(parentctx)
336 #
337 # If we always built the manifest for each context and compared those,
338 # then we'd be done. But the special case of the above call means we
339 # just copy the manifest of the parent.
340 reversed = False
341 if (not isinstance(ctx1, changectx)
342 and isinstance(ctx2, changectx)):
343 reversed = True
344 ctx1, ctx2 = ctx2, ctx1
345
346 match = self._repo.narrowmatch(match)
347 match = ctx2._matchstatus(ctx1, match)
348 r = scmutil.status([], [], [], [], [], [], [])
349 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
350 listunknown)
351
352 if reversed:
353 # Reverse added and removed. Clear deleted, unknown and ignored as
354 # these make no sense to reverse.
355 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
356 r.clean)
357
358 if listsubrepos:
359 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
360 try:
361 rev2 = ctx2.subrev(subpath)
362 except KeyError:
363 # A subrepo that existed in node1 was deleted between
364 # node1 and node2 (inclusive). Thus, ctx2's substate
365 # won't contain that subpath. The best we can do ignore it.
366 rev2 = None
367 submatch = matchmod.subdirmatcher(subpath, match)
368 s = sub.status(rev2, match=submatch, ignored=listignored,
369 clean=listclean, unknown=listunknown,
370 listsubrepos=True)
371 for rfiles, sfiles in zip(r, s):
372 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
373
374 for l in r:
375 l.sort()
376
377 return r
378
379 class changectx(basectx):
380 """A changecontext object makes access to data related to a particular
381 changeset convenient. It represents a read-only context already present in
382 the repo."""
383 def __init__(self, repo, rev, node):
384 super(changectx, self).__init__(repo)
385 self._rev = rev
386 self._node = node
387
388 def __hash__(self):
389 try:
390 return hash(self._rev)
391 except AttributeError:
392 return id(self)
393
394 def __nonzero__(self):
395 return self._rev != nullrev
396
397 __bool__ = __nonzero__
398
399 @propertycache
400 def _changeset(self):
401 return self._repo.changelog.changelogrevision(self.rev())
402
403 @propertycache
404 def _manifest(self):
405 return self._manifestctx.read()
406
407 @property
408 def _manifestctx(self):
409 return self._repo.manifestlog[self._changeset.manifest]
410
411 @propertycache
412 def _manifestdelta(self):
413 return self._manifestctx.readdelta()
414
415 @propertycache
416 def _parents(self):
417 repo = self._repo
418 p1, p2 = repo.changelog.parentrevs(self._rev)
419 if p2 == nullrev:
420 return [repo[p1]]
421 return [repo[p1], repo[p2]]
422
423 def changeset(self):
424 c = self._changeset
425 return (
426 c.manifest,
427 c.user,
428 c.date,
429 c.files,
430 c.description,
431 c.extra,
432 )
433 def manifestnode(self):
434 return self._changeset.manifest
435
436 def user(self):
437 return self._changeset.user
438 def date(self):
439 return self._changeset.date
440 def files(self):
441 return self._changeset.files
442 @propertycache 275 @propertycache
443 def _copies(self): 276 def _copies(self):
444 source = self._repo.ui.config('experimental', 'copies.read-from')
445 p1copies = self._changeset.p1copies
446 p2copies = self._changeset.p2copies
447 # If config says to get copy metadata only from changeset, then return
448 # that, defaulting to {} if there was no copy metadata.
449 # In compatibility mode, we return copy data from the changeset if
450 # it was recorded there, and otherwise we fall back to getting it from
451 # the filelogs (below).
452 if (source == 'changeset-only' or
453 (source == 'compatibility' and p1copies is not None)):
454 return p1copies or {}, p2copies or {}
455
456 # Otherwise (config said to read only from filelog, or we are in
457 # compatiblity mode and there is not data in the changeset), we get
458 # the copy metadata from the filelogs.
459 p1copies = {} 277 p1copies = {}
460 p2copies = {} 278 p2copies = {}
461 p1 = self.p1() 279 p1 = self.p1()
462 p2 = self.p2() 280 p2 = self.p2()
463 narrowmatch = self._repo.narrowmatch() 281 narrowmatch = self._repo.narrowmatch()
475 return p1copies, p2copies 293 return p1copies, p2copies
476 def p1copies(self): 294 def p1copies(self):
477 return self._copies[0] 295 return self._copies[0]
478 def p2copies(self): 296 def p2copies(self):
479 return self._copies[1] 297 return self._copies[1]
298
299 def sub(self, path, allowcreate=True):
300 '''return a subrepo for the stored revision of path, never wdir()'''
301 return subrepo.subrepo(self, path, allowcreate=allowcreate)
302
303 def nullsub(self, path, pctx):
304 return subrepo.nullsubrepo(self, path, pctx)
305
306 def workingsub(self, path):
307 '''return a subrepo for the stored revision, or wdir if this is a wdir
308 context.
309 '''
310 return subrepo.subrepo(self, path, allowwdir=True)
311
312 def match(self, pats=None, include=None, exclude=None, default='glob',
313 listsubrepos=False, badfn=None):
314 r = self._repo
315 return matchmod.match(r.root, r.getcwd(), pats,
316 include, exclude, default,
317 auditor=r.nofsauditor, ctx=self,
318 listsubrepos=listsubrepos, badfn=badfn)
319
320 def diff(self, ctx2=None, match=None, changes=None, opts=None,
321 losedatafn=None, pathfn=None, copy=None,
322 copysourcematch=None, hunksfilterfn=None):
323 """Returns a diff generator for the given contexts and matcher"""
324 if ctx2 is None:
325 ctx2 = self.p1()
326 if ctx2 is not None:
327 ctx2 = self._repo[ctx2]
328 return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
329 opts=opts, losedatafn=losedatafn, pathfn=pathfn,
330 copy=copy, copysourcematch=copysourcematch,
331 hunksfilterfn=hunksfilterfn)
332
333 def dirs(self):
334 return self._manifest.dirs()
335
336 def hasdir(self, dir):
337 return self._manifest.hasdir(dir)
338
339 def status(self, other=None, match=None, listignored=False,
340 listclean=False, listunknown=False, listsubrepos=False):
341 """return status of files between two nodes or node and working
342 directory.
343
344 If other is None, compare this node with working directory.
345
346 returns (modified, added, removed, deleted, unknown, ignored, clean)
347 """
348
349 ctx1 = self
350 ctx2 = self._repo[other]
351
352 # This next code block is, admittedly, fragile logic that tests for
353 # reversing the contexts and wouldn't need to exist if it weren't for
354 # the fast (and common) code path of comparing the working directory
355 # with its first parent.
356 #
357 # What we're aiming for here is the ability to call:
358 #
359 # workingctx.status(parentctx)
360 #
361 # If we always built the manifest for each context and compared those,
362 # then we'd be done. But the special case of the above call means we
363 # just copy the manifest of the parent.
364 reversed = False
365 if (not isinstance(ctx1, changectx)
366 and isinstance(ctx2, changectx)):
367 reversed = True
368 ctx1, ctx2 = ctx2, ctx1
369
370 match = self._repo.narrowmatch(match)
371 match = ctx2._matchstatus(ctx1, match)
372 r = scmutil.status([], [], [], [], [], [], [])
373 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
374 listunknown)
375
376 if reversed:
377 # Reverse added and removed. Clear deleted, unknown and ignored as
378 # these make no sense to reverse.
379 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
380 r.clean)
381
382 if listsubrepos:
383 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
384 try:
385 rev2 = ctx2.subrev(subpath)
386 except KeyError:
387 # A subrepo that existed in node1 was deleted between
388 # node1 and node2 (inclusive). Thus, ctx2's substate
389 # won't contain that subpath. The best we can do ignore it.
390 rev2 = None
391 submatch = matchmod.subdirmatcher(subpath, match)
392 s = sub.status(rev2, match=submatch, ignored=listignored,
393 clean=listclean, unknown=listunknown,
394 listsubrepos=True)
395 for rfiles, sfiles in zip(r, s):
396 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
397
398 for l in r:
399 l.sort()
400
401 return r
402
403 class changectx(basectx):
404 """A changecontext object makes access to data related to a particular
405 changeset convenient. It represents a read-only context already present in
406 the repo."""
407 def __init__(self, repo, rev, node):
408 super(changectx, self).__init__(repo)
409 self._rev = rev
410 self._node = node
411
412 def __hash__(self):
413 try:
414 return hash(self._rev)
415 except AttributeError:
416 return id(self)
417
418 def __nonzero__(self):
419 return self._rev != nullrev
420
421 __bool__ = __nonzero__
422
423 @propertycache
424 def _changeset(self):
425 return self._repo.changelog.changelogrevision(self.rev())
426
427 @propertycache
428 def _manifest(self):
429 return self._manifestctx.read()
430
431 @property
432 def _manifestctx(self):
433 return self._repo.manifestlog[self._changeset.manifest]
434
435 @propertycache
436 def _manifestdelta(self):
437 return self._manifestctx.readdelta()
438
439 @propertycache
440 def _parents(self):
441 repo = self._repo
442 p1, p2 = repo.changelog.parentrevs(self._rev)
443 if p2 == nullrev:
444 return [repo[p1]]
445 return [repo[p1], repo[p2]]
446
447 def changeset(self):
448 c = self._changeset
449 return (
450 c.manifest,
451 c.user,
452 c.date,
453 c.files,
454 c.description,
455 c.extra,
456 )
457 def manifestnode(self):
458 return self._changeset.manifest
459
460 def user(self):
461 return self._changeset.user
462 def date(self):
463 return self._changeset.date
464 def files(self):
465 return self._changeset.files
466 @propertycache
467 def _copies(self):
468 source = self._repo.ui.config('experimental', 'copies.read-from')
469 p1copies = self._changeset.p1copies
470 p2copies = self._changeset.p2copies
471 # If config says to get copy metadata only from changeset, then return
472 # that, defaulting to {} if there was no copy metadata.
473 # In compatibility mode, we return copy data from the changeset if
474 # it was recorded there, and otherwise we fall back to getting it from
475 # the filelogs (below).
476 if (source == 'changeset-only' or
477 (source == 'compatibility' and p1copies is not None)):
478 return p1copies or {}, p2copies or {}
479
480 # Otherwise (config said to read only from filelog, or we are in
481 # compatiblity mode and there is not data in the changeset), we get
482 # the copy metadata from the filelogs.
483 return super(changectx, self)._copies
480 def description(self): 484 def description(self):
481 return self._changeset.description 485 return self._changeset.description
482 def branch(self): 486 def branch(self):
483 return encoding.tolocal(self._changeset.extra.get("branch")) 487 return encoding.tolocal(self._changeset.extra.get("branch"))
484 def closesbranch(self): 488 def closesbranch(self):
1204 return self._status.added 1208 return self._status.added
1205 def removed(self): 1209 def removed(self):
1206 return self._status.removed 1210 return self._status.removed
1207 def deleted(self): 1211 def deleted(self):
1208 return self._status.deleted 1212 return self._status.deleted
1209 @propertycache
1210 def _copies(self):
1211 p1copies = {}
1212 p2copies = {}
1213 parents = self._repo.dirstate.parents()
1214 p1manifest = self._repo[parents[0]].manifest()
1215 p2manifest = self._repo[parents[1]].manifest()
1216 narrowmatch = self._repo.narrowmatch()
1217 for dst, src in self._repo.dirstate.copies().items():
1218 if not narrowmatch(dst):
1219 continue
1220 if src in p1manifest:
1221 p1copies[dst] = src
1222 elif src in p2manifest:
1223 p2copies[dst] = src
1224 return p1copies, p2copies
1225 def p1copies(self):
1226 return self._copies[0]
1227 def p2copies(self):
1228 return self._copies[1]
1229 def branch(self): 1213 def branch(self):
1230 return encoding.tolocal(self._extra['branch']) 1214 return encoding.tolocal(self._extra['branch'])
1231 def closesbranch(self): 1215 def closesbranch(self):
1232 return 'close' in self._extra 1216 return 'close' in self._extra
1233 def extra(self): 1217 def extra(self):
1577 self._status = s 1561 self._status = s
1578 1562
1579 return s 1563 return s
1580 1564
1581 @propertycache 1565 @propertycache
1566 def _copies(self):
1567 p1copies = {}
1568 p2copies = {}
1569 parents = self._repo.dirstate.parents()
1570 p1manifest = self._repo[parents[0]].manifest()
1571 p2manifest = self._repo[parents[1]].manifest()
1572 narrowmatch = self._repo.narrowmatch()
1573 for dst, src in self._repo.dirstate.copies().items():
1574 if not narrowmatch(dst):
1575 continue
1576 if src in p1manifest:
1577 p1copies[dst] = src
1578 elif src in p2manifest:
1579 p2copies[dst] = src
1580 return p1copies, p2copies
1581 def p1copies(self):
1582 return self._copies[0]
1583 def p2copies(self):
1584 return self._copies[1]
1585
1586 @propertycache
1582 def _manifest(self): 1587 def _manifest(self):
1583 """generate a manifest corresponding to the values in self._status 1588 """generate a manifest corresponding to the values in self._status
1584 1589
1585 This reuse the file nodeid from parent, but we use special node 1590 This reuse the file nodeid from parent, but we use special node
1586 identifiers for added and modified files. This is used by manifests 1591 identifiers for added and modified files. This is used by manifests