mercurial/context.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43085 eef9a2d67051
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
   199     def matchfileset(self, expr, badfn=None):
   199     def matchfileset(self, expr, badfn=None):
   200         return fileset.match(self, expr, badfn=badfn)
   200         return fileset.match(self, expr, badfn=badfn)
   201 
   201 
   202     def obsolete(self):
   202     def obsolete(self):
   203         """True if the changeset is obsolete"""
   203         """True if the changeset is obsolete"""
   204         return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
   204         return self.rev() in obsmod.getrevs(self._repo, b'obsolete')
   205 
   205 
   206     def extinct(self):
   206     def extinct(self):
   207         """True if the changeset is extinct"""
   207         """True if the changeset is extinct"""
   208         return self.rev() in obsmod.getrevs(self._repo, 'extinct')
   208         return self.rev() in obsmod.getrevs(self._repo, b'extinct')
   209 
   209 
   210     def orphan(self):
   210     def orphan(self):
   211         """True if the changeset is not obsolete, but its ancestor is"""
   211         """True if the changeset is not obsolete, but its ancestor is"""
   212         return self.rev() in obsmod.getrevs(self._repo, 'orphan')
   212         return self.rev() in obsmod.getrevs(self._repo, b'orphan')
   213 
   213 
   214     def phasedivergent(self):
   214     def phasedivergent(self):
   215         """True if the changeset tries to be a successor of a public changeset
   215         """True if the changeset tries to be a successor of a public changeset
   216 
   216 
   217         Only non-public and non-obsolete changesets may be phase-divergent.
   217         Only non-public and non-obsolete changesets may be phase-divergent.
   218         """
   218         """
   219         return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
   219         return self.rev() in obsmod.getrevs(self._repo, b'phasedivergent')
   220 
   220 
   221     def contentdivergent(self):
   221     def contentdivergent(self):
   222         """Is a successor of a changeset with multiple possible successor sets
   222         """Is a successor of a changeset with multiple possible successor sets
   223 
   223 
   224         Only non-public and non-obsolete changesets may be content-divergent.
   224         Only non-public and non-obsolete changesets may be content-divergent.
   225         """
   225         """
   226         return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
   226         return self.rev() in obsmod.getrevs(self._repo, b'contentdivergent')
   227 
   227 
   228     def isunstable(self):
   228     def isunstable(self):
   229         """True if the changeset is either orphan, phase-divergent or
   229         """True if the changeset is either orphan, phase-divergent or
   230         content-divergent"""
   230         content-divergent"""
   231         return self.orphan() or self.phasedivergent() or self.contentdivergent()
   231         return self.orphan() or self.phasedivergent() or self.contentdivergent()
   238         - phase-divergent,
   238         - phase-divergent,
   239         - content-divergent.
   239         - content-divergent.
   240         """
   240         """
   241         instabilities = []
   241         instabilities = []
   242         if self.orphan():
   242         if self.orphan():
   243             instabilities.append('orphan')
   243             instabilities.append(b'orphan')
   244         if self.phasedivergent():
   244         if self.phasedivergent():
   245             instabilities.append('phase-divergent')
   245             instabilities.append(b'phase-divergent')
   246         if self.contentdivergent():
   246         if self.contentdivergent():
   247             instabilities.append('content-divergent')
   247             instabilities.append(b'content-divergent')
   248         return instabilities
   248         return instabilities
   249 
   249 
   250     def parents(self):
   250     def parents(self):
   251         """return contexts for each parent changeset"""
   251         """return contexts for each parent changeset"""
   252         return self._parents
   252         return self._parents
   264         if r'_manifest' in self.__dict__:
   264         if r'_manifest' in self.__dict__:
   265             try:
   265             try:
   266                 return self._manifest[path], self._manifest.flags(path)
   266                 return self._manifest[path], self._manifest.flags(path)
   267             except KeyError:
   267             except KeyError:
   268                 raise error.ManifestLookupError(
   268                 raise error.ManifestLookupError(
   269                     self._node, path, _('not found in manifest')
   269                     self._node, path, _(b'not found in manifest')
   270                 )
   270                 )
   271         if r'_manifestdelta' in self.__dict__ or path in self.files():
   271         if r'_manifestdelta' in self.__dict__ or path in self.files():
   272             if path in self._manifestdelta:
   272             if path in self._manifestdelta:
   273                 return (
   273                 return (
   274                     self._manifestdelta[path],
   274                     self._manifestdelta[path],
   277         mfl = self._repo.manifestlog
   277         mfl = self._repo.manifestlog
   278         try:
   278         try:
   279             node, flag = mfl[self._changeset.manifest].find(path)
   279             node, flag = mfl[self._changeset.manifest].find(path)
   280         except KeyError:
   280         except KeyError:
   281             raise error.ManifestLookupError(
   281             raise error.ManifestLookupError(
   282                 self._node, path, _('not found in manifest')
   282                 self._node, path, _(b'not found in manifest')
   283             )
   283             )
   284 
   284 
   285         return node, flag
   285         return node, flag
   286 
   286 
   287     def filenode(self, path):
   287     def filenode(self, path):
   289 
   289 
   290     def flags(self, path):
   290     def flags(self, path):
   291         try:
   291         try:
   292             return self._fileinfo(path)[1]
   292             return self._fileinfo(path)[1]
   293         except error.LookupError:
   293         except error.LookupError:
   294             return ''
   294             return b''
   295 
   295 
   296     @propertycache
   296     @propertycache
   297     def _copies(self):
   297     def _copies(self):
   298         return copies.computechangesetcopies(self)
   298         return copies.computechangesetcopies(self)
   299 
   299 
   319     def match(
   319     def match(
   320         self,
   320         self,
   321         pats=None,
   321         pats=None,
   322         include=None,
   322         include=None,
   323         exclude=None,
   323         exclude=None,
   324         default='glob',
   324         default=b'glob',
   325         listsubrepos=False,
   325         listsubrepos=False,
   326         badfn=None,
   326         badfn=None,
   327     ):
   327     ):
   328         r = self._repo
   328         r = self._repo
   329         return matchmod.match(
   329         return matchmod.match(
   444                     clean=listclean,
   444                     clean=listclean,
   445                     unknown=listunknown,
   445                     unknown=listunknown,
   446                     listsubrepos=True,
   446                     listsubrepos=True,
   447                 )
   447                 )
   448                 for rfiles, sfiles in zip(r, s):
   448                 for rfiles, sfiles in zip(r, s):
   449                     rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
   449                     rfiles.extend(b"%s/%s" % (subpath, f) for f in sfiles)
   450 
   450 
   451         for l in r:
   451         for l in r:
   452             l.sort()
   452             l.sort()
   453 
   453 
   454         return r
   454         return r
   527         modified.difference_update(self.filesadded())
   527         modified.difference_update(self.filesadded())
   528         modified.difference_update(self.filesremoved())
   528         modified.difference_update(self.filesremoved())
   529         return sorted(modified)
   529         return sorted(modified)
   530 
   530 
   531     def filesadded(self):
   531     def filesadded(self):
   532         source = self._repo.ui.config('experimental', 'copies.read-from')
   532         source = self._repo.ui.config(b'experimental', b'copies.read-from')
   533         filesadded = self._changeset.filesadded
   533         filesadded = self._changeset.filesadded
   534         if source == 'changeset-only':
   534         if source == b'changeset-only':
   535             if filesadded is None:
   535             if filesadded is None:
   536                 filesadded = []
   536                 filesadded = []
   537         elif source == 'compatibility':
   537         elif source == b'compatibility':
   538             if filesadded is None:
   538             if filesadded is None:
   539                 filesadded = scmutil.computechangesetfilesadded(self)
   539                 filesadded = scmutil.computechangesetfilesadded(self)
   540         else:
   540         else:
   541             filesadded = scmutil.computechangesetfilesadded(self)
   541             filesadded = scmutil.computechangesetfilesadded(self)
   542         return filesadded
   542         return filesadded
   543 
   543 
   544     def filesremoved(self):
   544     def filesremoved(self):
   545         source = self._repo.ui.config('experimental', 'copies.read-from')
   545         source = self._repo.ui.config(b'experimental', b'copies.read-from')
   546         filesremoved = self._changeset.filesremoved
   546         filesremoved = self._changeset.filesremoved
   547         if source == 'changeset-only':
   547         if source == b'changeset-only':
   548             if filesremoved is None:
   548             if filesremoved is None:
   549                 filesremoved = []
   549                 filesremoved = []
   550         elif source == 'compatibility':
   550         elif source == b'compatibility':
   551             if filesremoved is None:
   551             if filesremoved is None:
   552                 filesremoved = scmutil.computechangesetfilesremoved(self)
   552                 filesremoved = scmutil.computechangesetfilesremoved(self)
   553         else:
   553         else:
   554             filesremoved = scmutil.computechangesetfilesremoved(self)
   554             filesremoved = scmutil.computechangesetfilesremoved(self)
   555         return filesremoved
   555         return filesremoved
   556 
   556 
   557     @propertycache
   557     @propertycache
   558     def _copies(self):
   558     def _copies(self):
   559         source = self._repo.ui.config('experimental', 'copies.read-from')
   559         source = self._repo.ui.config(b'experimental', b'copies.read-from')
   560         p1copies = self._changeset.p1copies
   560         p1copies = self._changeset.p1copies
   561         p2copies = self._changeset.p2copies
   561         p2copies = self._changeset.p2copies
   562         # If config says to get copy metadata only from changeset, then return
   562         # If config says to get copy metadata only from changeset, then return
   563         # that, defaulting to {} if there was no copy metadata.
   563         # that, defaulting to {} if there was no copy metadata.
   564         # In compatibility mode, we return copy data from the changeset if
   564         # In compatibility mode, we return copy data from the changeset if
   565         # it was recorded there, and otherwise we fall back to getting it from
   565         # it was recorded there, and otherwise we fall back to getting it from
   566         # the filelogs (below).
   566         # the filelogs (below).
   567         if source == 'changeset-only':
   567         if source == b'changeset-only':
   568             if p1copies is None:
   568             if p1copies is None:
   569                 p1copies = {}
   569                 p1copies = {}
   570             if p2copies is None:
   570             if p2copies is None:
   571                 p2copies = {}
   571                 p2copies = {}
   572         elif source == 'compatibility':
   572         elif source == b'compatibility':
   573             if p1copies is None:
   573             if p1copies is None:
   574                 # we are in compatiblity mode and there is not data in the
   574                 # we are in compatiblity mode and there is not data in the
   575                 # changeset), we get the copy metadata from the filelogs.
   575                 # changeset), we get the copy metadata from the filelogs.
   576                 p1copies, p2copies = super(changectx, self)._copies
   576                 p1copies, p2copies = super(changectx, self)._copies
   577         else:
   577         else:
   582 
   582 
   583     def description(self):
   583     def description(self):
   584         return self._changeset.description
   584         return self._changeset.description
   585 
   585 
   586     def branch(self):
   586     def branch(self):
   587         return encoding.tolocal(self._changeset.extra.get("branch"))
   587         return encoding.tolocal(self._changeset.extra.get(b"branch"))
   588 
   588 
   589     def closesbranch(self):
   589     def closesbranch(self):
   590         return 'close' in self._changeset.extra
   590         return b'close' in self._changeset.extra
   591 
   591 
   592     def extra(self):
   592     def extra(self):
   593         """Return a dict of extra information."""
   593         """Return a dict of extra information."""
   594         return self._changeset.extra
   594         return self._changeset.extra
   595 
   595 
   603 
   603 
   604     def phase(self):
   604     def phase(self):
   605         return self._repo._phasecache.phase(self._repo, self._rev)
   605         return self._repo._phasecache.phase(self._repo, self._rev)
   606 
   606 
   607     def hidden(self):
   607     def hidden(self):
   608         return self._rev in repoview.filterrevs(self._repo, 'visible')
   608         return self._rev in repoview.filterrevs(self._repo, b'visible')
   609 
   609 
   610     def isinmemory(self):
   610     def isinmemory(self):
   611         return False
   611         return False
   612 
   612 
   613     def children(self):
   613     def children(self):
   654             anc = nullid
   654             anc = nullid
   655         elif len(cahs) == 1:
   655         elif len(cahs) == 1:
   656             anc = cahs[0]
   656             anc = cahs[0]
   657         else:
   657         else:
   658             # experimental config: merge.preferancestor
   658             # experimental config: merge.preferancestor
   659             for r in self._repo.ui.configlist('merge', 'preferancestor'):
   659             for r in self._repo.ui.configlist(b'merge', b'preferancestor'):
   660                 try:
   660                 try:
   661                     ctx = scmutil.revsymbol(self._repo, r)
   661                     ctx = scmutil.revsymbol(self._repo, r)
   662                 except error.RepoLookupError:
   662                 except error.RepoLookupError:
   663                     continue
   663                     continue
   664                 anc = ctx.node()
   664                 anc = ctx.node()
   667             else:
   667             else:
   668                 anc = self._repo.changelog.ancestor(self._node, n2)
   668                 anc = self._repo.changelog.ancestor(self._node, n2)
   669             if warn:
   669             if warn:
   670                 self._repo.ui.status(
   670                 self._repo.ui.status(
   671                     (
   671                     (
   672                         _("note: using %s as ancestor of %s and %s\n")
   672                         _(b"note: using %s as ancestor of %s and %s\n")
   673                         % (short(anc), short(self._node), short(n2))
   673                         % (short(anc), short(self._node), short(n2))
   674                     )
   674                     )
   675                     + ''.join(
   675                     + b''.join(
   676                         _(
   676                         _(
   677                             "      alternatively, use --config "
   677                             b"      alternatively, use --config "
   678                             "merge.preferancestor=%s\n"
   678                             b"merge.preferancestor=%s\n"
   679                         )
   679                         )
   680                         % short(n)
   680                         % short(n)
   681                         for n in sorted(cahs)
   681                         for n in sorted(cahs)
   682                         if n != anc
   682                         if n != anc
   683                     )
   683                     )
   693 
   693 
   694         # Wrap match.bad method to have message with nodeid
   694         # Wrap match.bad method to have message with nodeid
   695         def bad(fn, msg):
   695         def bad(fn, msg):
   696             # The manifest doesn't know about subrepos, so don't complain about
   696             # The manifest doesn't know about subrepos, so don't complain about
   697             # paths into valid subrepos.
   697             # paths into valid subrepos.
   698             if any(fn == s or fn.startswith(s + '/') for s in self.substate):
   698             if any(fn == s or fn.startswith(s + b'/') for s in self.substate):
   699                 return
   699                 return
   700             match.bad(fn, _('no such file in rev %s') % self)
   700             match.bad(fn, _(b'no such file in rev %s') % self)
   701 
   701 
   702         m = matchmod.badmatch(self._repo.narrowmatch(match), bad)
   702         m = matchmod.badmatch(self._repo.narrowmatch(match), bad)
   703         return self._manifest.walk(m)
   703         return self._manifest.walk(m)
   704 
   704 
   705     def matches(self, match):
   705     def matches(self, match):
   755 
   755 
   756     __bool__ = __nonzero__
   756     __bool__ = __nonzero__
   757 
   757 
   758     def __bytes__(self):
   758     def __bytes__(self):
   759         try:
   759         try:
   760             return "%s@%s" % (self.path(), self._changectx)
   760             return b"%s@%s" % (self.path(), self._changectx)
   761         except error.LookupError:
   761         except error.LookupError:
   762             return "%s@???" % self.path()
   762             return b"%s@???" % self.path()
   763 
   763 
   764     __str__ = encoding.strmethod(__bytes__)
   764     __str__ = encoding.strmethod(__bytes__)
   765 
   765 
   766     def __repr__(self):
   766     def __repr__(self):
   767         return r"<%s %s>" % (type(self).__name__, str(self))
   767         return r"<%s %s>" % (type(self).__name__, str(self))
   869             return stringutil.binary(self.data())
   869             return stringutil.binary(self.data())
   870         except IOError:
   870         except IOError:
   871             return False
   871             return False
   872 
   872 
   873     def isexec(self):
   873     def isexec(self):
   874         return 'x' in self.flags()
   874         return b'x' in self.flags()
   875 
   875 
   876     def islink(self):
   876     def islink(self):
   877         return 'l' in self.flags()
   877         return b'l' in self.flags()
   878 
   878 
   879     def isabsent(self):
   879     def isabsent(self):
   880         """whether this filectx represents a file not in self._changectx
   880         """whether this filectx represents a file not in self._changectx
   881 
   881 
   882         This is mainly for merge code to detect change/delete conflicts. This is
   882         This is mainly for merge code to detect change/delete conflicts. This is
   893         if fctx._customcmp:
   893         if fctx._customcmp:
   894             return fctx.cmp(self)
   894             return fctx.cmp(self)
   895 
   895 
   896         if self._filenode is None:
   896         if self._filenode is None:
   897             raise error.ProgrammingError(
   897             raise error.ProgrammingError(
   898                 'filectx.cmp() must be reimplemented if not backed by revlog'
   898                 b'filectx.cmp() must be reimplemented if not backed by revlog'
   899             )
   899             )
   900 
   900 
   901         if fctx._filenode is None:
   901         if fctx._filenode is None:
   902             if self._repo._encodefilterpats:
   902             if self._repo._encodefilterpats:
   903                 # can't rely on size() because wdir content may be decoded
   903                 # can't rely on size() because wdir content may be decoded
  1160         self._repo = repo
  1160         self._repo = repo
  1161         self._path = path
  1161         self._path = path
  1162 
  1162 
  1163         assert (
  1163         assert (
  1164             changeid is not None or fileid is not None or changectx is not None
  1164             changeid is not None or fileid is not None or changectx is not None
  1165         ), "bad args: changeid=%r, fileid=%r, changectx=%r" % (
  1165         ), b"bad args: changeid=%r, fileid=%r, changectx=%r" % (
  1166             changeid,
  1166             changeid,
  1167             fileid,
  1167             fileid,
  1168             changectx,
  1168             changectx,
  1169         )
  1169         )
  1170 
  1170 
  1221 
  1221 
  1222     def data(self):
  1222     def data(self):
  1223         try:
  1223         try:
  1224             return self._filelog.read(self._filenode)
  1224             return self._filelog.read(self._filenode)
  1225         except error.CensoredNodeError:
  1225         except error.CensoredNodeError:
  1226             if self._repo.ui.config("censor", "policy") == "ignore":
  1226             if self._repo.ui.config(b"censor", b"policy") == b"ignore":
  1227                 return ""
  1227                 return b""
  1228             raise error.Abort(
  1228             raise error.Abort(
  1229                 _("censored node: %s") % short(self._filenode),
  1229                 _(b"censored node: %s") % short(self._filenode),
  1230                 hint=_("set censor.policy to ignore errors"),
  1230                 hint=_(b"set censor.policy to ignore errors"),
  1231             )
  1231             )
  1232 
  1232 
  1233     def size(self):
  1233     def size(self):
  1234         return self._filelog.size(self._filerev)
  1234         return self._filelog.size(self._filerev)
  1235 
  1235 
  1273     wants the ability to commit, e.g. workingctx or memctx."""
  1273     wants the ability to commit, e.g. workingctx or memctx."""
  1274 
  1274 
  1275     def __init__(
  1275     def __init__(
  1276         self,
  1276         self,
  1277         repo,
  1277         repo,
  1278         text="",
  1278         text=b"",
  1279         user=None,
  1279         user=None,
  1280         date=None,
  1280         date=None,
  1281         extra=None,
  1281         extra=None,
  1282         changes=None,
  1282         changes=None,
  1283         branch=None,
  1283         branch=None,
  1295 
  1295 
  1296         self._extra = {}
  1296         self._extra = {}
  1297         if extra:
  1297         if extra:
  1298             self._extra = extra.copy()
  1298             self._extra = extra.copy()
  1299         if branch is not None:
  1299         if branch is not None:
  1300             self._extra['branch'] = encoding.fromlocal(branch)
  1300             self._extra[b'branch'] = encoding.fromlocal(branch)
  1301         if not self._extra.get('branch'):
  1301         if not self._extra.get(b'branch'):
  1302             self._extra['branch'] = 'default'
  1302             self._extra[b'branch'] = b'default'
  1303 
  1303 
  1304     def __bytes__(self):
  1304     def __bytes__(self):
  1305         return bytes(self._parents[0]) + "+"
  1305         return bytes(self._parents[0]) + b"+"
  1306 
  1306 
  1307     __str__ = encoding.strmethod(__bytes__)
  1307     __str__ = encoding.strmethod(__bytes__)
  1308 
  1308 
  1309     def __nonzero__(self):
  1309     def __nonzero__(self):
  1310         return True
  1310         return True
  1320         return self._repo.ui.username()
  1320         return self._repo.ui.username()
  1321 
  1321 
  1322     @propertycache
  1322     @propertycache
  1323     def _date(self):
  1323     def _date(self):
  1324         ui = self._repo.ui
  1324         ui = self._repo.ui
  1325         date = ui.configdate('devel', 'default-date')
  1325         date = ui.configdate(b'devel', b'default-date')
  1326         if date is None:
  1326         if date is None:
  1327             date = dateutil.makedate()
  1327             date = dateutil.makedate()
  1328         return date
  1328         return date
  1329 
  1329 
  1330     def subrev(self, subpath):
  1330     def subrev(self, subpath):
  1362     filesmodified = modified
  1362     filesmodified = modified
  1363     filesadded = added
  1363     filesadded = added
  1364     filesremoved = removed
  1364     filesremoved = removed
  1365 
  1365 
  1366     def branch(self):
  1366     def branch(self):
  1367         return encoding.tolocal(self._extra['branch'])
  1367         return encoding.tolocal(self._extra[b'branch'])
  1368 
  1368 
  1369     def closesbranch(self):
  1369     def closesbranch(self):
  1370         return 'close' in self._extra
  1370         return b'close' in self._extra
  1371 
  1371 
  1372     def extra(self):
  1372     def extra(self):
  1373         return self._extra
  1373         return self._extra
  1374 
  1374 
  1375     def isinmemory(self):
  1375     def isinmemory(self):
  1431     changes - a list of file lists as returned by localrepo.status()
  1431     changes - a list of file lists as returned by localrepo.status()
  1432                or None to use the repository status.
  1432                or None to use the repository status.
  1433     """
  1433     """
  1434 
  1434 
  1435     def __init__(
  1435     def __init__(
  1436         self, repo, text="", user=None, date=None, extra=None, changes=None
  1436         self, repo, text=b"", user=None, date=None, extra=None, changes=None
  1437     ):
  1437     ):
  1438         branch = None
  1438         branch = None
  1439         if not extra or 'branch' not in extra:
  1439         if not extra or b'branch' not in extra:
  1440             try:
  1440             try:
  1441                 branch = repo.dirstate.branch()
  1441                 branch = repo.dirstate.branch()
  1442             except UnicodeDecodeError:
  1442             except UnicodeDecodeError:
  1443                 raise error.Abort(_('branch name not in UTF-8!'))
  1443                 raise error.Abort(_(b'branch name not in UTF-8!'))
  1444         super(workingctx, self).__init__(
  1444         super(workingctx, self).__init__(
  1445             repo, text, user, date, extra, changes, branch=branch
  1445             repo, text, user, date, extra, changes, branch=branch
  1446         )
  1446         )
  1447 
  1447 
  1448     def __iter__(self):
  1448     def __iter__(self):
  1449         d = self._repo.dirstate
  1449         d = self._repo.dirstate
  1450         for f in d:
  1450         for f in d:
  1451             if d[f] != 'r':
  1451             if d[f] != b'r':
  1452                 yield f
  1452                 yield f
  1453 
  1453 
  1454     def __contains__(self, key):
  1454     def __contains__(self, key):
  1455         return self._repo.dirstate[key] not in "?r"
  1455         return self._repo.dirstate[key] not in b"?r"
  1456 
  1456 
  1457     def hex(self):
  1457     def hex(self):
  1458         return wdirhex
  1458         return wdirhex
  1459 
  1459 
  1460     @propertycache
  1460     @propertycache
  1499                     return fl1
  1499                     return fl1
  1500                 if fl1 == fla:
  1500                 if fl1 == fla:
  1501                     return fl2
  1501                     return fl2
  1502                 if fl2 == fla:
  1502                 if fl2 == fla:
  1503                     return fl1
  1503                     return fl1
  1504                 return ''  # punt for conflicts
  1504                 return b''  # punt for conflicts
  1505 
  1505 
  1506         return func
  1506         return func
  1507 
  1507 
  1508     @propertycache
  1508     @propertycache
  1509     def _flagfunc(self):
  1509     def _flagfunc(self):
  1512     def flags(self, path):
  1512     def flags(self, path):
  1513         if r'_manifest' in self.__dict__:
  1513         if r'_manifest' in self.__dict__:
  1514             try:
  1514             try:
  1515                 return self._manifest.flags(path)
  1515                 return self._manifest.flags(path)
  1516             except KeyError:
  1516             except KeyError:
  1517                 return ''
  1517                 return b''
  1518 
  1518 
  1519         try:
  1519         try:
  1520             return self._flagfunc(path)
  1520             return self._flagfunc(path)
  1521         except OSError:
  1521         except OSError:
  1522             return ''
  1522             return b''
  1523 
  1523 
  1524     def filectx(self, path, filelog=None):
  1524     def filectx(self, path, filelog=None):
  1525         """get a file context from the working directory"""
  1525         """get a file context from the working directory"""
  1526         return workingfilectx(
  1526         return workingfilectx(
  1527             self._repo, path, workingctx=self, filelog=filelog
  1527             self._repo, path, workingctx=self, filelog=filelog
  1528         )
  1528         )
  1529 
  1529 
  1530     def dirty(self, missing=False, merge=True, branch=True):
  1530     def dirty(self, missing=False, merge=True, branch=True):
  1531         "check whether a working directory is modified"
  1531         b"check whether a working directory is modified"
  1532         # check subrepos first
  1532         # check subrepos first
  1533         for s in sorted(self.substate):
  1533         for s in sorted(self.substate):
  1534             if self.sub(s).dirty(missing=missing):
  1534             if self.sub(s).dirty(missing=missing):
  1535                 return True
  1535                 return True
  1536         # check current working dir
  1536         # check current working dir
  1541             or self.added()
  1541             or self.added()
  1542             or self.removed()
  1542             or self.removed()
  1543             or (missing and self.deleted())
  1543             or (missing and self.deleted())
  1544         )
  1544         )
  1545 
  1545 
  1546     def add(self, list, prefix=""):
  1546     def add(self, list, prefix=b""):
  1547         with self._repo.wlock():
  1547         with self._repo.wlock():
  1548             ui, ds = self._repo.ui, self._repo.dirstate
  1548             ui, ds = self._repo.ui, self._repo.dirstate
  1549             uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
  1549             uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
  1550             rejected = []
  1550             rejected = []
  1551             lstat = self._repo.wvfs.lstat
  1551             lstat = self._repo.wvfs.lstat
  1555                 # Windows, since it contains the drive letter and colon.
  1555                 # Windows, since it contains the drive letter and colon.
  1556                 scmutil.checkportable(ui, os.path.join(prefix, f))
  1556                 scmutil.checkportable(ui, os.path.join(prefix, f))
  1557                 try:
  1557                 try:
  1558                     st = lstat(f)
  1558                     st = lstat(f)
  1559                 except OSError:
  1559                 except OSError:
  1560                     ui.warn(_("%s does not exist!\n") % uipath(f))
  1560                     ui.warn(_(b"%s does not exist!\n") % uipath(f))
  1561                     rejected.append(f)
  1561                     rejected.append(f)
  1562                     continue
  1562                     continue
  1563                 limit = ui.configbytes('ui', 'large-file-limit')
  1563                 limit = ui.configbytes(b'ui', b'large-file-limit')
  1564                 if limit != 0 and st.st_size > limit:
  1564                 if limit != 0 and st.st_size > limit:
  1565                     ui.warn(
  1565                     ui.warn(
  1566                         _(
  1566                         _(
  1567                             "%s: up to %d MB of RAM may be required "
  1567                             b"%s: up to %d MB of RAM may be required "
  1568                             "to manage this file\n"
  1568                             b"to manage this file\n"
  1569                             "(use 'hg revert %s' to cancel the "
  1569                             b"(use 'hg revert %s' to cancel the "
  1570                             "pending addition)\n"
  1570                             b"pending addition)\n"
  1571                         )
  1571                         )
  1572                         % (f, 3 * st.st_size // 1000000, uipath(f))
  1572                         % (f, 3 * st.st_size // 1000000, uipath(f))
  1573                     )
  1573                     )
  1574                 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
  1574                 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
  1575                     ui.warn(
  1575                     ui.warn(
  1576                         _(
  1576                         _(
  1577                             "%s not added: only files and symlinks "
  1577                             b"%s not added: only files and symlinks "
  1578                             "supported currently\n"
  1578                             b"supported currently\n"
  1579                         )
  1579                         )
  1580                         % uipath(f)
  1580                         % uipath(f)
  1581                     )
  1581                     )
  1582                     rejected.append(f)
  1582                     rejected.append(f)
  1583                 elif ds[f] in 'amn':
  1583                 elif ds[f] in b'amn':
  1584                     ui.warn(_("%s already tracked!\n") % uipath(f))
  1584                     ui.warn(_(b"%s already tracked!\n") % uipath(f))
  1585                 elif ds[f] == 'r':
  1585                 elif ds[f] == b'r':
  1586                     ds.normallookup(f)
  1586                     ds.normallookup(f)
  1587                 else:
  1587                 else:
  1588                     ds.add(f)
  1588                     ds.add(f)
  1589             return rejected
  1589             return rejected
  1590 
  1590 
  1591     def forget(self, files, prefix=""):
  1591     def forget(self, files, prefix=b""):
  1592         with self._repo.wlock():
  1592         with self._repo.wlock():
  1593             ds = self._repo.dirstate
  1593             ds = self._repo.dirstate
  1594             uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
  1594             uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
  1595             rejected = []
  1595             rejected = []
  1596             for f in files:
  1596             for f in files:
  1597                 if f not in ds:
  1597                 if f not in ds:
  1598                     self._repo.ui.warn(_("%s not tracked!\n") % uipath(f))
  1598                     self._repo.ui.warn(_(b"%s not tracked!\n") % uipath(f))
  1599                     rejected.append(f)
  1599                     rejected.append(f)
  1600                 elif ds[f] != 'a':
  1600                 elif ds[f] != b'a':
  1601                     ds.remove(f)
  1601                     ds.remove(f)
  1602                 else:
  1602                 else:
  1603                     ds.drop(f)
  1603                     ds.drop(f)
  1604             return rejected
  1604             return rejected
  1605 
  1605 
  1608             st = self._repo.wvfs.lstat(dest)
  1608             st = self._repo.wvfs.lstat(dest)
  1609         except OSError as err:
  1609         except OSError as err:
  1610             if err.errno != errno.ENOENT:
  1610             if err.errno != errno.ENOENT:
  1611                 raise
  1611                 raise
  1612             self._repo.ui.warn(
  1612             self._repo.ui.warn(
  1613                 _("%s does not exist!\n") % self._repo.dirstate.pathto(dest)
  1613                 _(b"%s does not exist!\n") % self._repo.dirstate.pathto(dest)
  1614             )
  1614             )
  1615             return
  1615             return
  1616         if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
  1616         if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
  1617             self._repo.ui.warn(
  1617             self._repo.ui.warn(
  1618                 _("copy failed: %s is not a file or a " "symbolic link\n")
  1618                 _(b"copy failed: %s is not a file or a " b"symbolic link\n")
  1619                 % self._repo.dirstate.pathto(dest)
  1619                 % self._repo.dirstate.pathto(dest)
  1620             )
  1620             )
  1621         else:
  1621         else:
  1622             with self._repo.wlock():
  1622             with self._repo.wlock():
  1623                 ds = self._repo.dirstate
  1623                 ds = self._repo.dirstate
  1624                 if ds[dest] in '?':
  1624                 if ds[dest] in b'?':
  1625                     ds.add(dest)
  1625                     ds.add(dest)
  1626                 elif ds[dest] in 'r':
  1626                 elif ds[dest] in b'r':
  1627                     ds.normallookup(dest)
  1627                     ds.normallookup(dest)
  1628                 ds.copy(source, dest)
  1628                 ds.copy(source, dest)
  1629 
  1629 
  1630     def match(
  1630     def match(
  1631         self,
  1631         self,
  1632         pats=None,
  1632         pats=None,
  1633         include=None,
  1633         include=None,
  1634         exclude=None,
  1634         exclude=None,
  1635         default='glob',
  1635         default=b'glob',
  1636         listsubrepos=False,
  1636         listsubrepos=False,
  1637         badfn=None,
  1637         badfn=None,
  1638     ):
  1638     ):
  1639         r = self._repo
  1639         r = self._repo
  1640 
  1640 
  1663         # via user error or dereferencing by NFS or Samba servers,
  1663         # via user error or dereferencing by NFS or Samba servers,
  1664         # so we filter out any placeholders that don't look like a
  1664         # so we filter out any placeholders that don't look like a
  1665         # symlink
  1665         # symlink
  1666         sane = []
  1666         sane = []
  1667         for f in files:
  1667         for f in files:
  1668             if self.flags(f) == 'l':
  1668             if self.flags(f) == b'l':
  1669                 d = self[f].data()
  1669                 d = self[f].data()
  1670                 if (
  1670                 if (
  1671                     d == ''
  1671                     d == b''
  1672                     or len(d) >= 1024
  1672                     or len(d) >= 1024
  1673                     or '\n' in d
  1673                     or b'\n' in d
  1674                     or stringutil.binary(d)
  1674                     or stringutil.binary(d)
  1675                 ):
  1675                 ):
  1676                     self._repo.ui.debug(
  1676                     self._repo.ui.debug(
  1677                         'ignoring suspect symlink placeholder' ' "%s"\n' % f
  1677                         b'ignoring suspect symlink placeholder' b' "%s"\n' % f
  1678                     )
  1678                     )
  1679                     continue
  1679                     continue
  1680             sane.append(f)
  1680             sane.append(f)
  1681         return sane
  1681         return sane
  1682 
  1682 
  1744                         # in this case, writing changes out breaks
  1744                         # in this case, writing changes out breaks
  1745                         # consistency, because .hg/dirstate was
  1745                         # consistency, because .hg/dirstate was
  1746                         # already changed simultaneously after last
  1746                         # already changed simultaneously after last
  1747                         # caching (see also issue5584 for detail)
  1747                         # caching (see also issue5584 for detail)
  1748                         self._repo.ui.debug(
  1748                         self._repo.ui.debug(
  1749                             'skip updating dirstate: ' 'identity mismatch\n'
  1749                             b'skip updating dirstate: ' b'identity mismatch\n'
  1750                         )
  1750                         )
  1751             except error.LockError:
  1751             except error.LockError:
  1752                 pass
  1752                 pass
  1753             finally:
  1753             finally:
  1754                 # Even if the wlock couldn't be grabbed, clear out the list.
  1754                 # Even if the wlock couldn't be grabbed, clear out the list.
  1755                 self._repo.clearpostdsstatus()
  1755                 self._repo.clearpostdsstatus()
  1756 
  1756 
  1757     def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
  1757     def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
  1758         '''Gets the status from the dirstate -- internal use only.'''
  1758         '''Gets the status from the dirstate -- internal use only.'''
  1759         subrepos = []
  1759         subrepos = []
  1760         if '.hgsub' in self:
  1760         if b'.hgsub' in self:
  1761             subrepos = sorted(self.substate)
  1761             subrepos = sorted(self.substate)
  1762         cmp, s = self._repo.dirstate.status(
  1762         cmp, s = self._repo.dirstate.status(
  1763             match, subrepos, ignored=ignored, clean=clean, unknown=unknown
  1763             match, subrepos, ignored=ignored, clean=clean, unknown=unknown
  1764         )
  1764         )
  1765 
  1765 
  1853         s = self._dirstatestatus(match, listignored, listclean, listunknown)
  1853         s = self._dirstatestatus(match, listignored, listclean, listunknown)
  1854         # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
  1854         # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
  1855         # might have accidentally ended up with the entire contents of the file
  1855         # might have accidentally ended up with the entire contents of the file
  1856         # they are supposed to be linking to.
  1856         # they are supposed to be linking to.
  1857         s.modified[:] = self._filtersuspectsymlink(s.modified)
  1857         s.modified[:] = self._filtersuspectsymlink(s.modified)
  1858         if other != self._repo['.']:
  1858         if other != self._repo[b'.']:
  1859             s = super(workingctx, self)._buildstatus(
  1859             s = super(workingctx, self)._buildstatus(
  1860                 other, s, match, listignored, listclean, listunknown
  1860                 other, s, match, listignored, listclean, listunknown
  1861             )
  1861             )
  1862         return s
  1862         return s
  1863 
  1863 
  1869         comparing against the parent changeset.
  1869         comparing against the parent changeset.
  1870 
  1870 
  1871         If we aren't comparing against the working directory's parent, then we
  1871         If we aren't comparing against the working directory's parent, then we
  1872         just use the default match object sent to us.
  1872         just use the default match object sent to us.
  1873         """
  1873         """
  1874         if other != self._repo['.']:
  1874         if other != self._repo[b'.']:
  1875 
  1875 
  1876             def bad(f, msg):
  1876             def bad(f, msg):
  1877                 # 'f' may be a directory pattern from 'match.files()',
  1877                 # 'f' may be a directory pattern from 'match.files()',
  1878                 # so 'f not in ctx1' is not enough
  1878                 # so 'f not in ctx1' is not enough
  1879                 if f not in other and not other.hasdir(f):
  1879                 if f not in other and not other.hasdir(f):
  1880                     self._repo.ui.warn(
  1880                     self._repo.ui.warn(
  1881                         '%s: %s\n' % (self._repo.dirstate.pathto(f), msg)
  1881                         b'%s: %s\n' % (self._repo.dirstate.pathto(f), msg)
  1882                     )
  1882                     )
  1883 
  1883 
  1884             match.bad = bad
  1884             match.bad = bad
  1885         return match
  1885         return match
  1886 
  1886 
  1896         )
  1896         )
  1897 
  1897 
  1898     def matches(self, match):
  1898     def matches(self, match):
  1899         match = self._repo.narrowmatch(match)
  1899         match = self._repo.narrowmatch(match)
  1900         ds = self._repo.dirstate
  1900         ds = self._repo.dirstate
  1901         return sorted(f for f in ds.matches(match) if ds[f] != 'r')
  1901         return sorted(f for f in ds.matches(match) if ds[f] != b'r')
  1902 
  1902 
  1903     def markcommitted(self, node):
  1903     def markcommitted(self, node):
  1904         with self._repo.dirstate.parentchange():
  1904         with self._repo.dirstate.parentchange():
  1905             for f in self.modified() + self.added():
  1905             for f in self.modified() + self.added():
  1906                 self._repo.dirstate.normal(f)
  1906                 self._repo.dirstate.normal(f)
  2025         # invert comparison to reuse the same code path
  2025         # invert comparison to reuse the same code path
  2026         return fctx.cmp(self)
  2026         return fctx.cmp(self)
  2027 
  2027 
  2028     def remove(self, ignoremissing=False):
  2028     def remove(self, ignoremissing=False):
  2029         """wraps unlink for a repo's working directory"""
  2029         """wraps unlink for a repo's working directory"""
  2030         rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
  2030         rmdir = self._repo.ui.configbool(b'experimental', b'removeemptydirs')
  2031         self._repo.wvfs.unlinkpath(
  2031         self._repo.wvfs.unlinkpath(
  2032             self._path, ignoremissing=ignoremissing, rmdir=rmdir
  2032             self._path, ignoremissing=ignoremissing, rmdir=rmdir
  2033         )
  2033         )
  2034 
  2034 
  2035     def write(self, data, flags, backgroundclose=False, **kwargs):
  2035     def write(self, data, flags, backgroundclose=False, **kwargs):
  2047         ``write()`` can be called successfully.
  2047         ``write()`` can be called successfully.
  2048         """
  2048         """
  2049         wvfs = self._repo.wvfs
  2049         wvfs = self._repo.wvfs
  2050         f = self._path
  2050         f = self._path
  2051         wvfs.audit(f)
  2051         wvfs.audit(f)
  2052         if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
  2052         if self._repo.ui.configbool(
       
  2053             b'experimental', b'merge.checkpathconflicts'
       
  2054         ):
  2053             # remove files under the directory as they should already be
  2055             # remove files under the directory as they should already be
  2054             # warned and backed up
  2056             # warned and backed up
  2055             if wvfs.isdir(f) and not wvfs.islink(f):
  2057             if wvfs.isdir(f) and not wvfs.islink(f):
  2056                 wvfs.rmtree(f, forcibly=True)
  2058                 wvfs.rmtree(f, forcibly=True)
  2057             for p in reversed(list(util.finddirs(f))):
  2059             for p in reversed(list(util.finddirs(f))):
  2090         self._wrappedctx = wrappedctx
  2092         self._wrappedctx = wrappedctx
  2091         self._parents = [wrappedctx]
  2093         self._parents = [wrappedctx]
  2092         # Drop old manifest cache as it is now out of date.
  2094         # Drop old manifest cache as it is now out of date.
  2093         # This is necessary when, e.g., rebasing several nodes with one
  2095         # This is necessary when, e.g., rebasing several nodes with one
  2094         # ``overlayworkingctx`` (e.g. with --collapse).
  2096         # ``overlayworkingctx`` (e.g. with --collapse).
  2095         util.clearcachedproperty(self, '_manifest')
  2097         util.clearcachedproperty(self, b'_manifest')
  2096 
  2098 
  2097     def data(self, path):
  2099     def data(self, path):
  2098         if self.isdirty(path):
  2100         if self.isdirty(path):
  2099             if self._cache[path]['exists']:
  2101             if self._cache[path][b'exists']:
  2100                 if self._cache[path]['data'] is not None:
  2102                 if self._cache[path][b'data'] is not None:
  2101                     return self._cache[path]['data']
  2103                     return self._cache[path][b'data']
  2102                 else:
  2104                 else:
  2103                     # Must fallback here, too, because we only set flags.
  2105                     # Must fallback here, too, because we only set flags.
  2104                     return self._wrappedctx[path].data()
  2106                     return self._wrappedctx[path].data()
  2105             else:
  2107             else:
  2106                 raise error.ProgrammingError(
  2108                 raise error.ProgrammingError(
  2107                     "No such file or directory: %s" % path
  2109                     b"No such file or directory: %s" % path
  2108                 )
  2110                 )
  2109         else:
  2111         else:
  2110             return self._wrappedctx[path].data()
  2112             return self._wrappedctx[path].data()
  2111 
  2113 
  2112     @propertycache
  2114     @propertycache
  2126         return man
  2128         return man
  2127 
  2129 
  2128     @propertycache
  2130     @propertycache
  2129     def _flagfunc(self):
  2131     def _flagfunc(self):
  2130         def f(path):
  2132         def f(path):
  2131             return self._cache[path]['flags']
  2133             return self._cache[path][b'flags']
  2132 
  2134 
  2133         return f
  2135         return f
  2134 
  2136 
  2135     def files(self):
  2137     def files(self):
  2136         return sorted(self.added() + self.modified() + self.removed())
  2138         return sorted(self.added() + self.modified() + self.removed())
  2137 
  2139 
  2138     def modified(self):
  2140     def modified(self):
  2139         return [
  2141         return [
  2140             f
  2142             f
  2141             for f in self._cache.keys()
  2143             for f in self._cache.keys()
  2142             if self._cache[f]['exists'] and self._existsinparent(f)
  2144             if self._cache[f][b'exists'] and self._existsinparent(f)
  2143         ]
  2145         ]
  2144 
  2146 
  2145     def added(self):
  2147     def added(self):
  2146         return [
  2148         return [
  2147             f
  2149             f
  2148             for f in self._cache.keys()
  2150             for f in self._cache.keys()
  2149             if self._cache[f]['exists'] and not self._existsinparent(f)
  2151             if self._cache[f][b'exists'] and not self._existsinparent(f)
  2150         ]
  2152         ]
  2151 
  2153 
  2152     def removed(self):
  2154     def removed(self):
  2153         return [
  2155         return [
  2154             f
  2156             f
  2155             for f in self._cache.keys()
  2157             for f in self._cache.keys()
  2156             if not self._cache[f]['exists'] and self._existsinparent(f)
  2158             if not self._cache[f][b'exists'] and self._existsinparent(f)
  2157         ]
  2159         ]
  2158 
  2160 
  2159     def p1copies(self):
  2161     def p1copies(self):
  2160         copies = self._repo._wrappedctx.p1copies().copy()
  2162         copies = self._repo._wrappedctx.p1copies().copy()
  2161         narrowmatch = self._repo.narrowmatch()
  2163         narrowmatch = self._repo.narrowmatch()
  2162         for f in self._cache.keys():
  2164         for f in self._cache.keys():
  2163             if not narrowmatch(f):
  2165             if not narrowmatch(f):
  2164                 continue
  2166                 continue
  2165             copies.pop(f, None)  # delete if it exists
  2167             copies.pop(f, None)  # delete if it exists
  2166             source = self._cache[f]['copied']
  2168             source = self._cache[f][b'copied']
  2167             if source:
  2169             if source:
  2168                 copies[f] = source
  2170                 copies[f] = source
  2169         return copies
  2171         return copies
  2170 
  2172 
  2171     def p2copies(self):
  2173     def p2copies(self):
  2173         narrowmatch = self._repo.narrowmatch()
  2175         narrowmatch = self._repo.narrowmatch()
  2174         for f in self._cache.keys():
  2176         for f in self._cache.keys():
  2175             if not narrowmatch(f):
  2177             if not narrowmatch(f):
  2176                 continue
  2178                 continue
  2177             copies.pop(f, None)  # delete if it exists
  2179             copies.pop(f, None)  # delete if it exists
  2178             source = self._cache[f]['copied']
  2180             source = self._cache[f][b'copied']
  2179             if source:
  2181             if source:
  2180                 copies[f] = source
  2182                 copies[f] = source
  2181         return copies
  2183         return copies
  2182 
  2184 
  2183     def isinmemory(self):
  2185     def isinmemory(self):
  2184         return True
  2186         return True
  2185 
  2187 
  2186     def filedate(self, path):
  2188     def filedate(self, path):
  2187         if self.isdirty(path):
  2189         if self.isdirty(path):
  2188             return self._cache[path]['date']
  2190             return self._cache[path][b'date']
  2189         else:
  2191         else:
  2190             return self._wrappedctx[path].date()
  2192             return self._wrappedctx[path].date()
  2191 
  2193 
  2192     def markcopied(self, path, origin):
  2194     def markcopied(self, path, origin):
  2193         self._markdirty(
  2195         self._markdirty(
  2198             copied=origin,
  2200             copied=origin,
  2199         )
  2201         )
  2200 
  2202 
  2201     def copydata(self, path):
  2203     def copydata(self, path):
  2202         if self.isdirty(path):
  2204         if self.isdirty(path):
  2203             return self._cache[path]['copied']
  2205             return self._cache[path][b'copied']
  2204         else:
  2206         else:
  2205             return None
  2207             return None
  2206 
  2208 
  2207     def flags(self, path):
  2209     def flags(self, path):
  2208         if self.isdirty(path):
  2210         if self.isdirty(path):
  2209             if self._cache[path]['exists']:
  2211             if self._cache[path][b'exists']:
  2210                 return self._cache[path]['flags']
  2212                 return self._cache[path][b'flags']
  2211             else:
  2213             else:
  2212                 raise error.ProgrammingError(
  2214                 raise error.ProgrammingError(
  2213                     "No such file or directory: %s" % self._path
  2215                     b"No such file or directory: %s" % self._path
  2214                 )
  2216                 )
  2215         else:
  2217         else:
  2216             return self._wrappedctx[path].flags()
  2218             return self._wrappedctx[path].flags()
  2217 
  2219 
  2218     def __contains__(self, key):
  2220     def __contains__(self, key):
  2219         if key in self._cache:
  2221         if key in self._cache:
  2220             return self._cache[key]['exists']
  2222             return self._cache[key][b'exists']
  2221         return key in self.p1()
  2223         return key in self.p1()
  2222 
  2224 
  2223     def _existsinparent(self, path):
  2225     def _existsinparent(self, path):
  2224         try:
  2226         try:
  2225             # ``commitctx` raises a ``ManifestLookupError`` if a path does not
  2227             # ``commitctx` raises a ``ManifestLookupError`` if a path does not
  2239         """
  2241         """
  2240 
  2242 
  2241         def fail(path, component):
  2243         def fail(path, component):
  2242             # p1() is the base and we're receiving "writes" for p2()'s
  2244             # p1() is the base and we're receiving "writes" for p2()'s
  2243             # files.
  2245             # files.
  2244             if 'l' in self.p1()[component].flags():
  2246             if b'l' in self.p1()[component].flags():
  2245                 raise error.Abort(
  2247                 raise error.Abort(
  2246                     "error: %s conflicts with symlink %s "
  2248                     b"error: %s conflicts with symlink %s "
  2247                     "in %d." % (path, component, self.p1().rev())
  2249                     b"in %d." % (path, component, self.p1().rev())
  2248                 )
  2250                 )
  2249             else:
  2251             else:
  2250                 raise error.Abort(
  2252                 raise error.Abort(
  2251                     "error: '%s' conflicts with file '%s' in "
  2253                     b"error: '%s' conflicts with file '%s' in "
  2252                     "%d." % (path, component, self.p1().rev())
  2254                     b"%d." % (path, component, self.p1().rev())
  2253                 )
  2255                 )
  2254 
  2256 
  2255         # Test that each new directory to be created to write this path from p2
  2257         # Test that each new directory to be created to write this path from p2
  2256         # is not a file in p1.
  2258         # is not a file in p1.
  2257         components = path.split('/')
  2259         components = path.split(b'/')
  2258         for i in pycompat.xrange(len(components)):
  2260         for i in pycompat.xrange(len(components)):
  2259             component = "/".join(components[0:i])
  2261             component = b"/".join(components[0:i])
  2260             if component in self:
  2262             if component in self:
  2261                 fail(path, component)
  2263                 fail(path, component)
  2262 
  2264 
  2263         # Test the other direction -- that this path from p2 isn't a directory
  2265         # Test the other direction -- that this path from p2 isn't a directory
  2264         # in p1 (test that p1 doesn't have any paths matching `path/*`).
  2266         # in p1 (test that p1 doesn't have any paths matching `path/*`).
  2271             # omit the files which are deleted in current IMM wctx
  2273             # omit the files which are deleted in current IMM wctx
  2272             mfiles = [m for m in mfiles if m in self]
  2274             mfiles = [m for m in mfiles if m in self]
  2273             if not mfiles:
  2275             if not mfiles:
  2274                 return
  2276                 return
  2275             raise error.Abort(
  2277             raise error.Abort(
  2276                 "error: file '%s' cannot be written because "
  2278                 b"error: file '%s' cannot be written because "
  2277                 " '%s/' is a directory in %s (containing %d "
  2279                 b" '%s/' is a directory in %s (containing %d "
  2278                 "entries: %s)"
  2280                 b"entries: %s)"
  2279                 % (path, path, self.p1(), len(mfiles), ', '.join(mfiles))
  2281                 % (path, path, self.p1(), len(mfiles), b', '.join(mfiles))
  2280             )
  2282             )
  2281 
  2283 
  2282     def write(self, path, data, flags='', **kwargs):
  2284     def write(self, path, data, flags=b'', **kwargs):
  2283         if data is None:
  2285         if data is None:
  2284             raise error.ProgrammingError("data must be non-None")
  2286             raise error.ProgrammingError(b"data must be non-None")
  2285         self._auditconflicts(path)
  2287         self._auditconflicts(path)
  2286         self._markdirty(
  2288         self._markdirty(
  2287             path, exists=True, data=data, date=dateutil.makedate(), flags=flags
  2289             path, exists=True, data=data, date=dateutil.makedate(), flags=flags
  2288         )
  2290         )
  2289 
  2291 
  2290     def setflags(self, path, l, x):
  2292     def setflags(self, path, l, x):
  2291         flag = ''
  2293         flag = b''
  2292         if l:
  2294         if l:
  2293             flag = 'l'
  2295             flag = b'l'
  2294         elif x:
  2296         elif x:
  2295             flag = 'x'
  2297             flag = b'x'
  2296         self._markdirty(path, exists=True, date=dateutil.makedate(), flags=flag)
  2298         self._markdirty(path, exists=True, date=dateutil.makedate(), flags=flag)
  2297 
  2299 
  2298     def remove(self, path):
  2300     def remove(self, path):
  2299         self._markdirty(path, exists=False)
  2301         self._markdirty(path, exists=False)
  2300 
  2302 
  2304         """
  2306         """
  2305         if self.isdirty(path):
  2307         if self.isdirty(path):
  2306             # If this path exists and is a symlink, "follow" it by calling
  2308             # If this path exists and is a symlink, "follow" it by calling
  2307             # exists on the destination path.
  2309             # exists on the destination path.
  2308             if (
  2310             if (
  2309                 self._cache[path]['exists']
  2311                 self._cache[path][b'exists']
  2310                 and 'l' in self._cache[path]['flags']
  2312                 and b'l' in self._cache[path][b'flags']
  2311             ):
  2313             ):
  2312                 return self.exists(self._cache[path]['data'].strip())
  2314                 return self.exists(self._cache[path][b'data'].strip())
  2313             else:
  2315             else:
  2314                 return self._cache[path]['exists']
  2316                 return self._cache[path][b'exists']
  2315 
  2317 
  2316         return self._existsinparent(path)
  2318         return self._existsinparent(path)
  2317 
  2319 
  2318     def lexists(self, path):
  2320     def lexists(self, path):
  2319         """lexists returns True if the path exists"""
  2321         """lexists returns True if the path exists"""
  2320         if self.isdirty(path):
  2322         if self.isdirty(path):
  2321             return self._cache[path]['exists']
  2323             return self._cache[path][b'exists']
  2322 
  2324 
  2323         return self._existsinparent(path)
  2325         return self._existsinparent(path)
  2324 
  2326 
  2325     def size(self, path):
  2327     def size(self, path):
  2326         if self.isdirty(path):
  2328         if self.isdirty(path):
  2327             if self._cache[path]['exists']:
  2329             if self._cache[path][b'exists']:
  2328                 return len(self._cache[path]['data'])
  2330                 return len(self._cache[path][b'data'])
  2329             else:
  2331             else:
  2330                 raise error.ProgrammingError(
  2332                 raise error.ProgrammingError(
  2331                     "No such file or directory: %s" % self._path
  2333                     b"No such file or directory: %s" % self._path
  2332                 )
  2334                 )
  2333         return self._wrappedctx[path].size()
  2335         return self._wrappedctx[path].size()
  2334 
  2336 
  2335     def tomemctx(
  2337     def tomemctx(
  2336         self,
  2338         self,
  2361             parents = (self._repo[parents[0]], self._repo[parents[1]])
  2363             parents = (self._repo[parents[0]], self._repo[parents[1]])
  2362 
  2364 
  2363         files = self.files()
  2365         files = self.files()
  2364 
  2366 
  2365         def getfile(repo, memctx, path):
  2367         def getfile(repo, memctx, path):
  2366             if self._cache[path]['exists']:
  2368             if self._cache[path][b'exists']:
  2367                 return memfilectx(
  2369                 return memfilectx(
  2368                     repo,
  2370                     repo,
  2369                     memctx,
  2371                     memctx,
  2370                     path,
  2372                     path,
  2371                     self._cache[path]['data'],
  2373                     self._cache[path][b'data'],
  2372                     'l' in self._cache[path]['flags'],
  2374                     b'l' in self._cache[path][b'flags'],
  2373                     'x' in self._cache[path]['flags'],
  2375                     b'x' in self._cache[path][b'flags'],
  2374                     self._cache[path]['copied'],
  2376                     self._cache[path][b'copied'],
  2375                 )
  2377                 )
  2376             else:
  2378             else:
  2377                 # Returning None, but including the path in `files`, is
  2379                 # Returning None, but including the path in `files`, is
  2378                 # necessary for memctx to register a deletion.
  2380                 # necessary for memctx to register a deletion.
  2379                 return None
  2381                 return None
  2422         for path in self._cache.keys():
  2424         for path in self._cache.keys():
  2423             cache = self._cache[path]
  2425             cache = self._cache[path]
  2424             try:
  2426             try:
  2425                 underlying = self._wrappedctx[path]
  2427                 underlying = self._wrappedctx[path]
  2426                 if (
  2428                 if (
  2427                     underlying.data() == cache['data']
  2429                     underlying.data() == cache[b'data']
  2428                     and underlying.flags() == cache['flags']
  2430                     and underlying.flags() == cache[b'flags']
  2429                 ):
  2431                 ):
  2430                     keys.append(path)
  2432                     keys.append(path)
  2431             except error.ManifestLookupError:
  2433             except error.ManifestLookupError:
  2432                 # Path not in the underlying manifest (created).
  2434                 # Path not in the underlying manifest (created).
  2433                 continue
  2435                 continue
  2435         for path in keys:
  2437         for path in keys:
  2436             del self._cache[path]
  2438             del self._cache[path]
  2437         return keys
  2439         return keys
  2438 
  2440 
  2439     def _markdirty(
  2441     def _markdirty(
  2440         self, path, exists, data=None, date=None, flags='', copied=None
  2442         self, path, exists, data=None, date=None, flags=b'', copied=None
  2441     ):
  2443     ):
  2442         # data not provided, let's see if we already have some; if not, let's
  2444         # data not provided, let's see if we already have some; if not, let's
  2443         # grab it from our underlying context, so that we always have data if
  2445         # grab it from our underlying context, so that we always have data if
  2444         # the file is marked as existing.
  2446         # the file is marked as existing.
  2445         if exists and data is None:
  2447         if exists and data is None:
  2446             oldentry = self._cache.get(path) or {}
  2448             oldentry = self._cache.get(path) or {}
  2447             data = oldentry.get('data')
  2449             data = oldentry.get(b'data')
  2448             if data is None:
  2450             if data is None:
  2449                 data = self._wrappedctx[path].data()
  2451                 data = self._wrappedctx[path].data()
  2450 
  2452 
  2451         self._cache[path] = {
  2453         self._cache[path] = {
  2452             'exists': exists,
  2454             b'exists': exists,
  2453             'data': data,
  2455             b'data': data,
  2454             'date': date,
  2456             b'date': date,
  2455             'flags': flags,
  2457             b'flags': flags,
  2456             'copied': copied,
  2458             b'copied': copied,
  2457         }
  2459         }
  2458 
  2460 
  2459     def filectx(self, path, filelog=None):
  2461     def filectx(self, path, filelog=None):
  2460         return overlayworkingfilectx(
  2462         return overlayworkingfilectx(
  2461             self._repo, path, parent=self, filelog=filelog
  2463             self._repo, path, parent=self, filelog=filelog
  2525     This hides changes in the working directory, if they aren't
  2527     This hides changes in the working directory, if they aren't
  2526     committed in this context.
  2528     committed in this context.
  2527     """
  2529     """
  2528 
  2530 
  2529     def __init__(
  2531     def __init__(
  2530         self, repo, changes, text="", user=None, date=None, extra=None
  2532         self, repo, changes, text=b"", user=None, date=None, extra=None
  2531     ):
  2533     ):
  2532         super(workingcommitctx, self).__init__(
  2534         super(workingcommitctx, self).__init__(
  2533             repo, text, user, date, extra, changes
  2535             repo, text, user, date, extra, changes
  2534         )
  2536         )
  2535 
  2537 
  2779         copied is the source file path if current file was copied in the
  2781         copied is the source file path if current file was copied in the
  2780         revision being committed, or None."""
  2782         revision being committed, or None."""
  2781         super(memfilectx, self).__init__(repo, path, None, changectx)
  2783         super(memfilectx, self).__init__(repo, path, None, changectx)
  2782         self._data = data
  2784         self._data = data
  2783         if islink:
  2785         if islink:
  2784             self._flags = 'l'
  2786             self._flags = b'l'
  2785         elif isexec:
  2787         elif isexec:
  2786             self._flags = 'x'
  2788             self._flags = b'x'
  2787         else:
  2789         else:
  2788             self._flags = ''
  2790             self._flags = b''
  2789         self._copysource = copysource
  2791         self._copysource = copysource
  2790 
  2792 
  2791     def copysource(self):
  2793     def copysource(self):
  2792         return self._copysource
  2794         return self._copysource
  2793 
  2795 
  2928         self._path = path
  2930         self._path = path
  2929 
  2931 
  2930     def cmp(self, fctx):
  2932     def cmp(self, fctx):
  2931         # filecmp follows symlinks whereas `cmp` should not, so skip the fast
  2933         # filecmp follows symlinks whereas `cmp` should not, so skip the fast
  2932         # path if either side is a symlink.
  2934         # path if either side is a symlink.
  2933         symlinks = 'l' in self.flags() or 'l' in fctx.flags()
  2935         symlinks = b'l' in self.flags() or b'l' in fctx.flags()
  2934         if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
  2936         if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
  2935             # Add a fast-path for merge if both sides are disk-backed.
  2937             # Add a fast-path for merge if both sides are disk-backed.
  2936             # Note that filecmp uses the opposite return values (True if same)
  2938             # Note that filecmp uses the opposite return values (True if same)
  2937             # from our cmp functions (True if different).
  2939             # from our cmp functions (True if different).
  2938             return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
  2940             return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
  2940 
  2942 
  2941     def path(self):
  2943     def path(self):
  2942         return self._path
  2944         return self._path
  2943 
  2945 
  2944     def flags(self):
  2946     def flags(self):
  2945         return ''
  2947         return b''
  2946 
  2948 
  2947     def data(self):
  2949     def data(self):
  2948         return util.readfile(self._path)
  2950         return util.readfile(self._path)
  2949 
  2951 
  2950     def decodeddata(self):
  2952     def decodeddata(self):
  2951         with open(self._path, "rb") as f:
  2953         with open(self._path, b"rb") as f:
  2952             return f.read()
  2954             return f.read()
  2953 
  2955 
  2954     def remove(self):
  2956     def remove(self):
  2955         util.unlink(self._path)
  2957         util.unlink(self._path)
  2956 
  2958 
  2957     def write(self, data, flags, **kwargs):
  2959     def write(self, data, flags, **kwargs):
  2958         assert not flags
  2960         assert not flags
  2959         with open(self._path, "wb") as f:
  2961         with open(self._path, b"wb") as f:
  2960             f.write(data)
  2962             f.write(data)