Mercurial > public > mercurial-scm > hg
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 |