eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/context.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # context.py - changeset and file context objects for mercurial
       
     2 #
       
     3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 from node import nullid, nullrev, short, hex
       
     9 from i18n import _
       
    10 import ancestor, bdiff, error, util, subrepo, patch
       
    11 import os, errno, stat
       
    12 
       
    13 propertycache = util.propertycache
       
    14 
       
    15 class changectx(object):
       
    16     """A changecontext object makes access to data related to a particular
       
    17     changeset convenient."""
       
    18     def __init__(self, repo, changeid=''):
       
    19         """changeid is a revision number, node, or tag"""
       
    20         if changeid == '':
       
    21             changeid = '.'
       
    22         self._repo = repo
       
    23         if isinstance(changeid, (long, int)):
       
    24             self._rev = changeid
       
    25             self._node = self._repo.changelog.node(changeid)
       
    26         else:
       
    27             self._node = self._repo.lookup(changeid)
       
    28             self._rev = self._repo.changelog.rev(self._node)
       
    29 
       
    30     def __str__(self):
       
    31         return short(self.node())
       
    32 
       
    33     def __int__(self):
       
    34         return self.rev()
       
    35 
       
    36     def __repr__(self):
       
    37         return "<changectx %s>" % str(self)
       
    38 
       
    39     def __hash__(self):
       
    40         try:
       
    41             return hash(self._rev)
       
    42         except AttributeError:
       
    43             return id(self)
       
    44 
       
    45     def __eq__(self, other):
       
    46         try:
       
    47             return self._rev == other._rev
       
    48         except AttributeError:
       
    49             return False
       
    50 
       
    51     def __ne__(self, other):
       
    52         return not (self == other)
       
    53 
       
    54     def __nonzero__(self):
       
    55         return self._rev != nullrev
       
    56 
       
    57     @propertycache
       
    58     def _changeset(self):
       
    59         return self._repo.changelog.read(self.node())
       
    60 
       
    61     @propertycache
       
    62     def _manifest(self):
       
    63         return self._repo.manifest.read(self._changeset[0])
       
    64 
       
    65     @propertycache
       
    66     def _manifestdelta(self):
       
    67         return self._repo.manifest.readdelta(self._changeset[0])
       
    68 
       
    69     @propertycache
       
    70     def _parents(self):
       
    71         p = self._repo.changelog.parentrevs(self._rev)
       
    72         if p[1] == nullrev:
       
    73             p = p[:-1]
       
    74         return [changectx(self._repo, x) for x in p]
       
    75 
       
    76     @propertycache
       
    77     def substate(self):
       
    78         return subrepo.state(self, self._repo.ui)
       
    79 
       
    80     def __contains__(self, key):
       
    81         return key in self._manifest
       
    82 
       
    83     def __getitem__(self, key):
       
    84         return self.filectx(key)
       
    85 
       
    86     def __iter__(self):
       
    87         for f in sorted(self._manifest):
       
    88             yield f
       
    89 
       
    90     def changeset(self):
       
    91         return self._changeset
       
    92     def manifest(self):
       
    93         return self._manifest
       
    94     def manifestnode(self):
       
    95         return self._changeset[0]
       
    96 
       
    97     def rev(self):
       
    98         return self._rev
       
    99     def node(self):
       
   100         return self._node
       
   101     def hex(self):
       
   102         return hex(self._node)
       
   103     def user(self):
       
   104         return self._changeset[1]
       
   105     def date(self):
       
   106         return self._changeset[2]
       
   107     def files(self):
       
   108         return self._changeset[3]
       
   109     def description(self):
       
   110         return self._changeset[4]
       
   111     def branch(self):
       
   112         return self._changeset[5].get("branch")
       
   113     def extra(self):
       
   114         return self._changeset[5]
       
   115     def tags(self):
       
   116         return self._repo.nodetags(self._node)
       
   117 
       
   118     def parents(self):
       
   119         """return contexts for each parent changeset"""
       
   120         return self._parents
       
   121 
       
   122     def p1(self):
       
   123         return self._parents[0]
       
   124 
       
   125     def p2(self):
       
   126         if len(self._parents) == 2:
       
   127             return self._parents[1]
       
   128         return changectx(self._repo, -1)
       
   129 
       
   130     def children(self):
       
   131         """return contexts for each child changeset"""
       
   132         c = self._repo.changelog.children(self._node)
       
   133         return [changectx(self._repo, x) for x in c]
       
   134 
       
   135     def ancestors(self):
       
   136         for a in self._repo.changelog.ancestors(self._rev):
       
   137             yield changectx(self._repo, a)
       
   138 
       
   139     def descendants(self):
       
   140         for d in self._repo.changelog.descendants(self._rev):
       
   141             yield changectx(self._repo, d)
       
   142 
       
   143     def _fileinfo(self, path):
       
   144         if '_manifest' in self.__dict__:
       
   145             try:
       
   146                 return self._manifest[path], self._manifest.flags(path)
       
   147             except KeyError:
       
   148                 raise error.LookupError(self._node, path,
       
   149                                         _('not found in manifest'))
       
   150         if '_manifestdelta' in self.__dict__ or path in self.files():
       
   151             if path in self._manifestdelta:
       
   152                 return self._manifestdelta[path], self._manifestdelta.flags(path)
       
   153         node, flag = self._repo.manifest.find(self._changeset[0], path)
       
   154         if not node:
       
   155             raise error.LookupError(self._node, path,
       
   156                                     _('not found in manifest'))
       
   157 
       
   158         return node, flag
       
   159 
       
   160     def filenode(self, path):
       
   161         return self._fileinfo(path)[0]
       
   162 
       
   163     def flags(self, path):
       
   164         try:
       
   165             return self._fileinfo(path)[1]
       
   166         except error.LookupError:
       
   167             return ''
       
   168 
       
   169     def filectx(self, path, fileid=None, filelog=None):
       
   170         """get a file context from this changeset"""
       
   171         if fileid is None:
       
   172             fileid = self.filenode(path)
       
   173         return filectx(self._repo, path, fileid=fileid,
       
   174                        changectx=self, filelog=filelog)
       
   175 
       
   176     def ancestor(self, c2):
       
   177         """
       
   178         return the ancestor context of self and c2
       
   179         """
       
   180         # deal with workingctxs
       
   181         n2 = c2._node
       
   182         if n2 == None:
       
   183             n2 = c2._parents[0]._node
       
   184         n = self._repo.changelog.ancestor(self._node, n2)
       
   185         return changectx(self._repo, n)
       
   186 
       
   187     def walk(self, match):
       
   188         fset = set(match.files())
       
   189         # for dirstate.walk, files=['.'] means "walk the whole tree".
       
   190         # follow that here, too
       
   191         fset.discard('.')
       
   192         for fn in self:
       
   193             for ffn in fset:
       
   194                 # match if the file is the exact name or a directory
       
   195                 if ffn == fn or fn.startswith("%s/" % ffn):
       
   196                     fset.remove(ffn)
       
   197                     break
       
   198             if match(fn):
       
   199                 yield fn
       
   200         for fn in sorted(fset):
       
   201             if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
       
   202                 yield fn
       
   203 
       
   204     def sub(self, path):
       
   205         return subrepo.subrepo(self, path)
       
   206 
       
   207     def diff(self, ctx2=None, match=None, **opts):
       
   208         """Returns a diff generator for the given contexts and matcher"""
       
   209         if ctx2 is None:
       
   210             ctx2 = self.p1()
       
   211         if ctx2 is not None and not isinstance(ctx2, changectx):
       
   212             ctx2 = self._repo[ctx2]
       
   213         diffopts = patch.diffopts(self._repo.ui, opts)
       
   214         return patch.diff(self._repo, ctx2.node(), self.node(),
       
   215                           match=match, opts=diffopts)
       
   216 
       
   217 class filectx(object):
       
   218     """A filecontext object makes access to data related to a particular
       
   219        filerevision convenient."""
       
   220     def __init__(self, repo, path, changeid=None, fileid=None,
       
   221                  filelog=None, changectx=None):
       
   222         """changeid can be a changeset revision, node, or tag.
       
   223            fileid can be a file revision or node."""
       
   224         self._repo = repo
       
   225         self._path = path
       
   226 
       
   227         assert (changeid is not None
       
   228                 or fileid is not None
       
   229                 or changectx is not None), \
       
   230                 ("bad args: changeid=%r, fileid=%r, changectx=%r"
       
   231                  % (changeid, fileid, changectx))
       
   232 
       
   233         if filelog:
       
   234             self._filelog = filelog
       
   235 
       
   236         if changeid is not None:
       
   237             self._changeid = changeid
       
   238         if changectx is not None:
       
   239             self._changectx = changectx
       
   240         if fileid is not None:
       
   241             self._fileid = fileid
       
   242 
       
   243     @propertycache
       
   244     def _changectx(self):
       
   245         return changectx(self._repo, self._changeid)
       
   246 
       
   247     @propertycache
       
   248     def _filelog(self):
       
   249         return self._repo.file(self._path)
       
   250 
       
   251     @propertycache
       
   252     def _changeid(self):
       
   253         if '_changectx' in self.__dict__:
       
   254             return self._changectx.rev()
       
   255         else:
       
   256             return self._filelog.linkrev(self._filerev)
       
   257 
       
   258     @propertycache
       
   259     def _filenode(self):
       
   260         if '_fileid' in self.__dict__:
       
   261             return self._filelog.lookup(self._fileid)
       
   262         else:
       
   263             return self._changectx.filenode(self._path)
       
   264 
       
   265     @propertycache
       
   266     def _filerev(self):
       
   267         return self._filelog.rev(self._filenode)
       
   268 
       
   269     @propertycache
       
   270     def _repopath(self):
       
   271         return self._path
       
   272 
       
   273     def __nonzero__(self):
       
   274         try:
       
   275             self._filenode
       
   276             return True
       
   277         except error.LookupError:
       
   278             # file is missing
       
   279             return False
       
   280 
       
   281     def __str__(self):
       
   282         return "%s@%s" % (self.path(), short(self.node()))
       
   283 
       
   284     def __repr__(self):
       
   285         return "<filectx %s>" % str(self)
       
   286 
       
   287     def __hash__(self):
       
   288         try:
       
   289             return hash((self._path, self._filenode))
       
   290         except AttributeError:
       
   291             return id(self)
       
   292 
       
   293     def __eq__(self, other):
       
   294         try:
       
   295             return (self._path == other._path
       
   296                     and self._filenode == other._filenode)
       
   297         except AttributeError:
       
   298             return False
       
   299 
       
   300     def __ne__(self, other):
       
   301         return not (self == other)
       
   302 
       
   303     def filectx(self, fileid):
       
   304         '''opens an arbitrary revision of the file without
       
   305         opening a new filelog'''
       
   306         return filectx(self._repo, self._path, fileid=fileid,
       
   307                        filelog=self._filelog)
       
   308 
       
   309     def filerev(self):
       
   310         return self._filerev
       
   311     def filenode(self):
       
   312         return self._filenode
       
   313     def flags(self):
       
   314         return self._changectx.flags(self._path)
       
   315     def filelog(self):
       
   316         return self._filelog
       
   317 
       
   318     def rev(self):
       
   319         if '_changectx' in self.__dict__:
       
   320             return self._changectx.rev()
       
   321         if '_changeid' in self.__dict__:
       
   322             return self._changectx.rev()
       
   323         return self._filelog.linkrev(self._filerev)
       
   324 
       
   325     def linkrev(self):
       
   326         return self._filelog.linkrev(self._filerev)
       
   327     def node(self):
       
   328         return self._changectx.node()
       
   329     def hex(self):
       
   330         return hex(self.node())
       
   331     def user(self):
       
   332         return self._changectx.user()
       
   333     def date(self):
       
   334         return self._changectx.date()
       
   335     def files(self):
       
   336         return self._changectx.files()
       
   337     def description(self):
       
   338         return self._changectx.description()
       
   339     def branch(self):
       
   340         return self._changectx.branch()
       
   341     def extra(self):
       
   342         return self._changectx.extra()
       
   343     def manifest(self):
       
   344         return self._changectx.manifest()
       
   345     def changectx(self):
       
   346         return self._changectx
       
   347 
       
   348     def data(self):
       
   349         return self._filelog.read(self._filenode)
       
   350     def path(self):
       
   351         return self._path
       
   352     def size(self):
       
   353         return self._filelog.size(self._filerev)
       
   354 
       
   355     def cmp(self, fctx):
       
   356         """compare with other file context
       
   357 
       
   358         returns True if different than fctx.
       
   359         """
       
   360         if (fctx._filerev is None and self._repo._encodefilterpats
       
   361             or self.size() == fctx.size()):
       
   362             return self._filelog.cmp(self._filenode, fctx.data())
       
   363 
       
   364         return True
       
   365 
       
   366     def renamed(self):
       
   367         """check if file was actually renamed in this changeset revision
       
   368 
       
   369         If rename logged in file revision, we report copy for changeset only
       
   370         if file revisions linkrev points back to the changeset in question
       
   371         or both changeset parents contain different file revisions.
       
   372         """
       
   373 
       
   374         renamed = self._filelog.renamed(self._filenode)
       
   375         if not renamed:
       
   376             return renamed
       
   377 
       
   378         if self.rev() == self.linkrev():
       
   379             return renamed
       
   380 
       
   381         name = self.path()
       
   382         fnode = self._filenode
       
   383         for p in self._changectx.parents():
       
   384             try:
       
   385                 if fnode == p.filenode(name):
       
   386                     return None
       
   387             except error.LookupError:
       
   388                 pass
       
   389         return renamed
       
   390 
       
   391     def parents(self):
       
   392         p = self._path
       
   393         fl = self._filelog
       
   394         pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
       
   395 
       
   396         r = self._filelog.renamed(self._filenode)
       
   397         if r:
       
   398             pl[0] = (r[0], r[1], None)
       
   399 
       
   400         return [filectx(self._repo, p, fileid=n, filelog=l)
       
   401                 for p, n, l in pl if n != nullid]
       
   402 
       
   403     def children(self):
       
   404         # hard for renames
       
   405         c = self._filelog.children(self._filenode)
       
   406         return [filectx(self._repo, self._path, fileid=x,
       
   407                         filelog=self._filelog) for x in c]
       
   408 
       
   409     def annotate(self, follow=False, linenumber=None):
       
   410         '''returns a list of tuples of (ctx, line) for each line
       
   411         in the file, where ctx is the filectx of the node where
       
   412         that line was last changed.
       
   413         This returns tuples of ((ctx, linenumber), line) for each line,
       
   414         if "linenumber" parameter is NOT "None".
       
   415         In such tuples, linenumber means one at the first appearance
       
   416         in the managed file.
       
   417         To reduce annotation cost,
       
   418         this returns fixed value(False is used) as linenumber,
       
   419         if "linenumber" parameter is "False".'''
       
   420 
       
   421         def decorate_compat(text, rev):
       
   422             return ([rev] * len(text.splitlines()), text)
       
   423 
       
   424         def without_linenumber(text, rev):
       
   425             return ([(rev, False)] * len(text.splitlines()), text)
       
   426 
       
   427         def with_linenumber(text, rev):
       
   428             size = len(text.splitlines())
       
   429             return ([(rev, i) for i in xrange(1, size + 1)], text)
       
   430 
       
   431         decorate = (((linenumber is None) and decorate_compat) or
       
   432                     (linenumber and with_linenumber) or
       
   433                     without_linenumber)
       
   434 
       
   435         def pair(parent, child):
       
   436             for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
       
   437                 child[0][b1:b2] = parent[0][a1:a2]
       
   438             return child
       
   439 
       
   440         getlog = util.lrucachefunc(lambda x: self._repo.file(x))
       
   441         def getctx(path, fileid):
       
   442             log = path == self._path and self._filelog or getlog(path)
       
   443             return filectx(self._repo, path, fileid=fileid, filelog=log)
       
   444         getctx = util.lrucachefunc(getctx)
       
   445 
       
   446         def parents(f):
       
   447             # we want to reuse filectx objects as much as possible
       
   448             p = f._path
       
   449             if f._filerev is None: # working dir
       
   450                 pl = [(n.path(), n.filerev()) for n in f.parents()]
       
   451             else:
       
   452                 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
       
   453 
       
   454             if follow:
       
   455                 r = f.renamed()
       
   456                 if r:
       
   457                     pl[0] = (r[0], getlog(r[0]).rev(r[1]))
       
   458 
       
   459             return [getctx(p, n) for p, n in pl if n != nullrev]
       
   460 
       
   461         # use linkrev to find the first changeset where self appeared
       
   462         if self.rev() != self.linkrev():
       
   463             base = self.filectx(self.filerev())
       
   464         else:
       
   465             base = self
       
   466 
       
   467         # find all ancestors
       
   468         needed = {base: 1}
       
   469         visit = [base]
       
   470         files = [base._path]
       
   471         while visit:
       
   472             f = visit.pop(0)
       
   473             for p in parents(f):
       
   474                 if p not in needed:
       
   475                     needed[p] = 1
       
   476                     visit.append(p)
       
   477                     if p._path not in files:
       
   478                         files.append(p._path)
       
   479                 else:
       
   480                     # count how many times we'll use this
       
   481                     needed[p] += 1
       
   482 
       
   483         # sort by revision (per file) which is a topological order
       
   484         visit = []
       
   485         for f in files:
       
   486             visit.extend(n for n in needed if n._path == f)
       
   487 
       
   488         hist = {}
       
   489         for f in sorted(visit, key=lambda x: x.rev()):
       
   490             curr = decorate(f.data(), f)
       
   491             for p in parents(f):
       
   492                 curr = pair(hist[p], curr)
       
   493                 # trim the history of unneeded revs
       
   494                 needed[p] -= 1
       
   495                 if not needed[p]:
       
   496                     del hist[p]
       
   497             hist[f] = curr
       
   498 
       
   499         return zip(hist[f][0], hist[f][1].splitlines(True))
       
   500 
       
   501     def ancestor(self, fc2, actx=None):
       
   502         """
       
   503         find the common ancestor file context, if any, of self, and fc2
       
   504 
       
   505         If actx is given, it must be the changectx of the common ancestor
       
   506         of self's and fc2's respective changesets.
       
   507         """
       
   508 
       
   509         if actx is None:
       
   510             actx = self.changectx().ancestor(fc2.changectx())
       
   511 
       
   512         # the trivial case: changesets are unrelated, files must be too
       
   513         if not actx:
       
   514             return None
       
   515 
       
   516         # the easy case: no (relevant) renames
       
   517         if fc2.path() == self.path() and self.path() in actx:
       
   518             return actx[self.path()]
       
   519         acache = {}
       
   520 
       
   521         # prime the ancestor cache for the working directory
       
   522         for c in (self, fc2):
       
   523             if c._filerev is None:
       
   524                 pl = [(n.path(), n.filenode()) for n in c.parents()]
       
   525                 acache[(c._path, None)] = pl
       
   526 
       
   527         flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
       
   528         def parents(vertex):
       
   529             if vertex in acache:
       
   530                 return acache[vertex]
       
   531             f, n = vertex
       
   532             if f not in flcache:
       
   533                 flcache[f] = self._repo.file(f)
       
   534             fl = flcache[f]
       
   535             pl = [(f, p) for p in fl.parents(n) if p != nullid]
       
   536             re = fl.renamed(n)
       
   537             if re:
       
   538                 pl.append(re)
       
   539             acache[vertex] = pl
       
   540             return pl
       
   541 
       
   542         a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
       
   543         v = ancestor.ancestor(a, b, parents)
       
   544         if v:
       
   545             f, n = v
       
   546             return filectx(self._repo, f, fileid=n, filelog=flcache[f])
       
   547 
       
   548         return None
       
   549 
       
   550     def ancestors(self):
       
   551         seen = set(str(self))
       
   552         visit = [self]
       
   553         while visit:
       
   554             for parent in visit.pop(0).parents():
       
   555                 s = str(parent)
       
   556                 if s not in seen:
       
   557                     visit.append(parent)
       
   558                     seen.add(s)
       
   559                     yield parent
       
   560 
       
   561 class workingctx(changectx):
       
   562     """A workingctx object makes access to data related to
       
   563     the current working directory convenient.
       
   564     date - any valid date string or (unixtime, offset), or None.
       
   565     user - username string, or None.
       
   566     extra - a dictionary of extra values, or None.
       
   567     changes - a list of file lists as returned by localrepo.status()
       
   568                or None to use the repository status.
       
   569     """
       
   570     def __init__(self, repo, text="", user=None, date=None, extra=None,
       
   571                  changes=None):
       
   572         self._repo = repo
       
   573         self._rev = None
       
   574         self._node = None
       
   575         self._text = text
       
   576         if date:
       
   577             self._date = util.parsedate(date)
       
   578         if user:
       
   579             self._user = user
       
   580         if changes:
       
   581             self._status = list(changes[:4])
       
   582             self._unknown = changes[4]
       
   583             self._ignored = changes[5]
       
   584             self._clean = changes[6]
       
   585         else:
       
   586             self._unknown = None
       
   587             self._ignored = None
       
   588             self._clean = None
       
   589 
       
   590         self._extra = {}
       
   591         if extra:
       
   592             self._extra = extra.copy()
       
   593         if 'branch' not in self._extra:
       
   594             branch = self._repo.dirstate.branch()
       
   595             try:
       
   596                 branch = branch.decode('UTF-8').encode('UTF-8')
       
   597             except UnicodeDecodeError:
       
   598                 raise util.Abort(_('branch name not in UTF-8!'))
       
   599             self._extra['branch'] = branch
       
   600         if self._extra['branch'] == '':
       
   601             self._extra['branch'] = 'default'
       
   602 
       
   603     def __str__(self):
       
   604         return str(self._parents[0]) + "+"
       
   605 
       
   606     def __nonzero__(self):
       
   607         return True
       
   608 
       
   609     def __contains__(self, key):
       
   610         return self._repo.dirstate[key] not in "?r"
       
   611 
       
   612     @propertycache
       
   613     def _manifest(self):
       
   614         """generate a manifest corresponding to the working directory"""
       
   615 
       
   616         if self._unknown is None:
       
   617             self.status(unknown=True)
       
   618 
       
   619         man = self._parents[0].manifest().copy()
       
   620         copied = self._repo.dirstate.copies()
       
   621         if len(self._parents) > 1:
       
   622             man2 = self.p2().manifest()
       
   623             def getman(f):
       
   624                 if f in man:
       
   625                     return man
       
   626                 return man2
       
   627         else:
       
   628             getman = lambda f: man
       
   629         def cf(f):
       
   630             f = copied.get(f, f)
       
   631             return getman(f).flags(f)
       
   632         ff = self._repo.dirstate.flagfunc(cf)
       
   633         modified, added, removed, deleted = self._status
       
   634         unknown = self._unknown
       
   635         for i, l in (("a", added), ("m", modified), ("u", unknown)):
       
   636             for f in l:
       
   637                 orig = copied.get(f, f)
       
   638                 man[f] = getman(orig).get(orig, nullid) + i
       
   639                 try:
       
   640                     man.set(f, ff(f))
       
   641                 except OSError:
       
   642                     pass
       
   643 
       
   644         for f in deleted + removed:
       
   645             if f in man:
       
   646                 del man[f]
       
   647 
       
   648         return man
       
   649 
       
   650     @propertycache
       
   651     def _status(self):
       
   652         return self._repo.status()[:4]
       
   653 
       
   654     @propertycache
       
   655     def _user(self):
       
   656         return self._repo.ui.username()
       
   657 
       
   658     @propertycache
       
   659     def _date(self):
       
   660         return util.makedate()
       
   661 
       
   662     @propertycache
       
   663     def _parents(self):
       
   664         p = self._repo.dirstate.parents()
       
   665         if p[1] == nullid:
       
   666             p = p[:-1]
       
   667         self._parents = [changectx(self._repo, x) for x in p]
       
   668         return self._parents
       
   669 
       
   670     def status(self, ignored=False, clean=False, unknown=False):
       
   671         """Explicit status query
       
   672         Unless this method is used to query the working copy status, the
       
   673         _status property will implicitly read the status using its default
       
   674         arguments."""
       
   675         stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
       
   676         self._unknown = self._ignored = self._clean = None
       
   677         if unknown:
       
   678             self._unknown = stat[4]
       
   679         if ignored:
       
   680             self._ignored = stat[5]
       
   681         if clean:
       
   682             self._clean = stat[6]
       
   683         self._status = stat[:4]
       
   684         return stat
       
   685 
       
   686     def manifest(self):
       
   687         return self._manifest
       
   688     def user(self):
       
   689         return self._user or self._repo.ui.username()
       
   690     def date(self):
       
   691         return self._date
       
   692     def description(self):
       
   693         return self._text
       
   694     def files(self):
       
   695         return sorted(self._status[0] + self._status[1] + self._status[2])
       
   696 
       
   697     def modified(self):
       
   698         return self._status[0]
       
   699     def added(self):
       
   700         return self._status[1]
       
   701     def removed(self):
       
   702         return self._status[2]
       
   703     def deleted(self):
       
   704         return self._status[3]
       
   705     def unknown(self):
       
   706         assert self._unknown is not None  # must call status first
       
   707         return self._unknown
       
   708     def ignored(self):
       
   709         assert self._ignored is not None  # must call status first
       
   710         return self._ignored
       
   711     def clean(self):
       
   712         assert self._clean is not None  # must call status first
       
   713         return self._clean
       
   714     def branch(self):
       
   715         return self._extra['branch']
       
   716     def extra(self):
       
   717         return self._extra
       
   718 
       
   719     def tags(self):
       
   720         t = []
       
   721         [t.extend(p.tags()) for p in self.parents()]
       
   722         return t
       
   723 
       
   724     def children(self):
       
   725         return []
       
   726 
       
   727     def flags(self, path):
       
   728         if '_manifest' in self.__dict__:
       
   729             try:
       
   730                 return self._manifest.flags(path)
       
   731             except KeyError:
       
   732                 return ''
       
   733 
       
   734         orig = self._repo.dirstate.copies().get(path, path)
       
   735 
       
   736         def findflag(ctx):
       
   737             mnode = ctx.changeset()[0]
       
   738             node, flag = self._repo.manifest.find(mnode, orig)
       
   739             ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
       
   740             try:
       
   741                 return ff(path)
       
   742             except OSError:
       
   743                 pass
       
   744 
       
   745         flag = findflag(self._parents[0])
       
   746         if flag is None and len(self.parents()) > 1:
       
   747             flag = findflag(self._parents[1])
       
   748         if flag is None or self._repo.dirstate[path] == 'r':
       
   749             return ''
       
   750         return flag
       
   751 
       
   752     def filectx(self, path, filelog=None):
       
   753         """get a file context from the working directory"""
       
   754         return workingfilectx(self._repo, path, workingctx=self,
       
   755                               filelog=filelog)
       
   756 
       
   757     def ancestor(self, c2):
       
   758         """return the ancestor context of self and c2"""
       
   759         return self._parents[0].ancestor(c2) # punt on two parents for now
       
   760 
       
   761     def walk(self, match):
       
   762         return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
       
   763                                                True, False))
       
   764 
       
   765     def dirty(self, missing=False):
       
   766         "check whether a working directory is modified"
       
   767         # check subrepos first
       
   768         for s in self.substate:
       
   769             if self.sub(s).dirty():
       
   770                 return True
       
   771         # check current working dir
       
   772         return (self.p2() or self.branch() != self.p1().branch() or
       
   773                 self.modified() or self.added() or self.removed() or
       
   774                 (missing and self.deleted()))
       
   775 
       
   776     def add(self, list, prefix=""):
       
   777         join = lambda f: os.path.join(prefix, f)
       
   778         wlock = self._repo.wlock()
       
   779         ui, ds = self._repo.ui, self._repo.dirstate
       
   780         try:
       
   781             rejected = []
       
   782             for f in list:
       
   783                 p = self._repo.wjoin(f)
       
   784                 try:
       
   785                     st = os.lstat(p)
       
   786                 except:
       
   787                     ui.warn(_("%s does not exist!\n") % join(f))
       
   788                     rejected.append(f)
       
   789                     continue
       
   790                 if st.st_size > 10000000:
       
   791                     ui.warn(_("%s: up to %d MB of RAM may be required "
       
   792                               "to manage this file\n"
       
   793                               "(use 'hg revert %s' to cancel the "
       
   794                               "pending addition)\n")
       
   795                               % (f, 3 * st.st_size // 1000000, join(f)))
       
   796                 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
       
   797                     ui.warn(_("%s not added: only files and symlinks "
       
   798                               "supported currently\n") % join(f))
       
   799                     rejected.append(p)
       
   800                 elif ds[f] in 'amn':
       
   801                     ui.warn(_("%s already tracked!\n") % join(f))
       
   802                 elif ds[f] == 'r':
       
   803                     ds.normallookup(f)
       
   804                 else:
       
   805                     ds.add(f)
       
   806             return rejected
       
   807         finally:
       
   808             wlock.release()
       
   809 
       
   810     def forget(self, list):
       
   811         wlock = self._repo.wlock()
       
   812         try:
       
   813             for f in list:
       
   814                 if self._repo.dirstate[f] != 'a':
       
   815                     self._repo.ui.warn(_("%s not added!\n") % f)
       
   816                 else:
       
   817                     self._repo.dirstate.forget(f)
       
   818         finally:
       
   819             wlock.release()
       
   820 
       
   821     def ancestors(self):
       
   822         for a in self._repo.changelog.ancestors(
       
   823             *[p.rev() for p in self._parents]):
       
   824             yield changectx(self._repo, a)
       
   825 
       
   826     def remove(self, list, unlink=False):
       
   827         if unlink:
       
   828             for f in list:
       
   829                 try:
       
   830                     util.unlink(self._repo.wjoin(f))
       
   831                 except OSError, inst:
       
   832                     if inst.errno != errno.ENOENT:
       
   833                         raise
       
   834         wlock = self._repo.wlock()
       
   835         try:
       
   836             for f in list:
       
   837                 if unlink and os.path.lexists(self._repo.wjoin(f)):
       
   838                     self._repo.ui.warn(_("%s still exists!\n") % f)
       
   839                 elif self._repo.dirstate[f] == 'a':
       
   840                     self._repo.dirstate.forget(f)
       
   841                 elif f not in self._repo.dirstate:
       
   842                     self._repo.ui.warn(_("%s not tracked!\n") % f)
       
   843                 else:
       
   844                     self._repo.dirstate.remove(f)
       
   845         finally:
       
   846             wlock.release()
       
   847 
       
   848     def undelete(self, list):
       
   849         pctxs = self.parents()
       
   850         wlock = self._repo.wlock()
       
   851         try:
       
   852             for f in list:
       
   853                 if self._repo.dirstate[f] != 'r':
       
   854                     self._repo.ui.warn(_("%s not removed!\n") % f)
       
   855                 else:
       
   856                     fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
       
   857                     t = fctx.data()
       
   858                     self._repo.wwrite(f, t, fctx.flags())
       
   859                     self._repo.dirstate.normal(f)
       
   860         finally:
       
   861             wlock.release()
       
   862 
       
   863     def copy(self, source, dest):
       
   864         p = self._repo.wjoin(dest)
       
   865         if not os.path.lexists(p):
       
   866             self._repo.ui.warn(_("%s does not exist!\n") % dest)
       
   867         elif not (os.path.isfile(p) or os.path.islink(p)):
       
   868             self._repo.ui.warn(_("copy failed: %s is not a file or a "
       
   869                                  "symbolic link\n") % dest)
       
   870         else:
       
   871             wlock = self._repo.wlock()
       
   872             try:
       
   873                 if self._repo.dirstate[dest] in '?r':
       
   874                     self._repo.dirstate.add(dest)
       
   875                 self._repo.dirstate.copy(source, dest)
       
   876             finally:
       
   877                 wlock.release()
       
   878 
       
   879 class workingfilectx(filectx):
       
   880     """A workingfilectx object makes access to data related to a particular
       
   881        file in the working directory convenient."""
       
   882     def __init__(self, repo, path, filelog=None, workingctx=None):
       
   883         """changeid can be a changeset revision, node, or tag.
       
   884            fileid can be a file revision or node."""
       
   885         self._repo = repo
       
   886         self._path = path
       
   887         self._changeid = None
       
   888         self._filerev = self._filenode = None
       
   889 
       
   890         if filelog:
       
   891             self._filelog = filelog
       
   892         if workingctx:
       
   893             self._changectx = workingctx
       
   894 
       
   895     @propertycache
       
   896     def _changectx(self):
       
   897         return workingctx(self._repo)
       
   898 
       
   899     def __nonzero__(self):
       
   900         return True
       
   901 
       
   902     def __str__(self):
       
   903         return "%s@%s" % (self.path(), self._changectx)
       
   904 
       
   905     def data(self):
       
   906         return self._repo.wread(self._path)
       
   907     def renamed(self):
       
   908         rp = self._repo.dirstate.copied(self._path)
       
   909         if not rp:
       
   910             return None
       
   911         return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
       
   912 
       
   913     def parents(self):
       
   914         '''return parent filectxs, following copies if necessary'''
       
   915         def filenode(ctx, path):
       
   916             return ctx._manifest.get(path, nullid)
       
   917 
       
   918         path = self._path
       
   919         fl = self._filelog
       
   920         pcl = self._changectx._parents
       
   921         renamed = self.renamed()
       
   922 
       
   923         if renamed:
       
   924             pl = [renamed + (None,)]
       
   925         else:
       
   926             pl = [(path, filenode(pcl[0], path), fl)]
       
   927 
       
   928         for pc in pcl[1:]:
       
   929             pl.append((path, filenode(pc, path), fl))
       
   930 
       
   931         return [filectx(self._repo, p, fileid=n, filelog=l)
       
   932                 for p, n, l in pl if n != nullid]
       
   933 
       
   934     def children(self):
       
   935         return []
       
   936 
       
   937     def size(self):
       
   938         return os.lstat(self._repo.wjoin(self._path)).st_size
       
   939     def date(self):
       
   940         t, tz = self._changectx.date()
       
   941         try:
       
   942             return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
       
   943         except OSError, err:
       
   944             if err.errno != errno.ENOENT:
       
   945                 raise
       
   946             return (t, tz)
       
   947 
       
   948     def cmp(self, fctx):
       
   949         """compare with other file context
       
   950 
       
   951         returns True if different than fctx.
       
   952         """
       
   953         # fctx should be a filectx (not a wfctx)
       
   954         # invert comparison to reuse the same code path
       
   955         return fctx.cmp(self)
       
   956 
       
   957 class memctx(object):
       
   958     """Use memctx to perform in-memory commits via localrepo.commitctx().
       
   959 
       
   960     Revision information is supplied at initialization time while
       
   961     related files data and is made available through a callback
       
   962     mechanism.  'repo' is the current localrepo, 'parents' is a
       
   963     sequence of two parent revisions identifiers (pass None for every
       
   964     missing parent), 'text' is the commit message and 'files' lists
       
   965     names of files touched by the revision (normalized and relative to
       
   966     repository root).
       
   967 
       
   968     filectxfn(repo, memctx, path) is a callable receiving the
       
   969     repository, the current memctx object and the normalized path of
       
   970     requested file, relative to repository root. It is fired by the
       
   971     commit function for every file in 'files', but calls order is
       
   972     undefined. If the file is available in the revision being
       
   973     committed (updated or added), filectxfn returns a memfilectx
       
   974     object. If the file was removed, filectxfn raises an
       
   975     IOError. Moved files are represented by marking the source file
       
   976     removed and the new file added with copy information (see
       
   977     memfilectx).
       
   978 
       
   979     user receives the committer name and defaults to current
       
   980     repository username, date is the commit date in any format
       
   981     supported by util.parsedate() and defaults to current date, extra
       
   982     is a dictionary of metadata or is left empty.
       
   983     """
       
   984     def __init__(self, repo, parents, text, files, filectxfn, user=None,
       
   985                  date=None, extra=None):
       
   986         self._repo = repo
       
   987         self._rev = None
       
   988         self._node = None
       
   989         self._text = text
       
   990         self._date = date and util.parsedate(date) or util.makedate()
       
   991         self._user = user
       
   992         parents = [(p or nullid) for p in parents]
       
   993         p1, p2 = parents
       
   994         self._parents = [changectx(self._repo, p) for p in (p1, p2)]
       
   995         files = sorted(set(files))
       
   996         self._status = [files, [], [], [], []]
       
   997         self._filectxfn = filectxfn
       
   998 
       
   999         self._extra = extra and extra.copy() or {}
       
  1000         if 'branch' not in self._extra:
       
  1001             self._extra['branch'] = 'default'
       
  1002         elif self._extra.get('branch') == '':
       
  1003             self._extra['branch'] = 'default'
       
  1004 
       
  1005     def __str__(self):
       
  1006         return str(self._parents[0]) + "+"
       
  1007 
       
  1008     def __int__(self):
       
  1009         return self._rev
       
  1010 
       
  1011     def __nonzero__(self):
       
  1012         return True
       
  1013 
       
  1014     def __getitem__(self, key):
       
  1015         return self.filectx(key)
       
  1016 
       
  1017     def p1(self):
       
  1018         return self._parents[0]
       
  1019     def p2(self):
       
  1020         return self._parents[1]
       
  1021 
       
  1022     def user(self):
       
  1023         return self._user or self._repo.ui.username()
       
  1024     def date(self):
       
  1025         return self._date
       
  1026     def description(self):
       
  1027         return self._text
       
  1028     def files(self):
       
  1029         return self.modified()
       
  1030     def modified(self):
       
  1031         return self._status[0]
       
  1032     def added(self):
       
  1033         return self._status[1]
       
  1034     def removed(self):
       
  1035         return self._status[2]
       
  1036     def deleted(self):
       
  1037         return self._status[3]
       
  1038     def unknown(self):
       
  1039         return self._status[4]
       
  1040     def ignored(self):
       
  1041         return self._status[5]
       
  1042     def clean(self):
       
  1043         return self._status[6]
       
  1044     def branch(self):
       
  1045         return self._extra['branch']
       
  1046     def extra(self):
       
  1047         return self._extra
       
  1048     def flags(self, f):
       
  1049         return self[f].flags()
       
  1050 
       
  1051     def parents(self):
       
  1052         """return contexts for each parent changeset"""
       
  1053         return self._parents
       
  1054 
       
  1055     def filectx(self, path, filelog=None):
       
  1056         """get a file context from the working directory"""
       
  1057         return self._filectxfn(self._repo, self, path)
       
  1058 
       
  1059     def commit(self):
       
  1060         """commit context to the repo"""
       
  1061         return self._repo.commitctx(self)
       
  1062 
       
  1063 class memfilectx(object):
       
  1064     """memfilectx represents an in-memory file to commit.
       
  1065 
       
  1066     See memctx for more details.
       
  1067     """
       
  1068     def __init__(self, path, data, islink=False, isexec=False, copied=None):
       
  1069         """
       
  1070         path is the normalized file path relative to repository root.
       
  1071         data is the file content as a string.
       
  1072         islink is True if the file is a symbolic link.
       
  1073         isexec is True if the file is executable.
       
  1074         copied is the source file path if current file was copied in the
       
  1075         revision being committed, or None."""
       
  1076         self._path = path
       
  1077         self._data = data
       
  1078         self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
       
  1079         self._copied = None
       
  1080         if copied:
       
  1081             self._copied = (copied, nullid)
       
  1082 
       
  1083     def __nonzero__(self):
       
  1084         return True
       
  1085     def __str__(self):
       
  1086         return "%s@%s" % (self.path(), self._changectx)
       
  1087     def path(self):
       
  1088         return self._path
       
  1089     def data(self):
       
  1090         return self._data
       
  1091     def flags(self):
       
  1092         return self._flags
       
  1093     def isexec(self):
       
  1094         return 'x' in self._flags
       
  1095     def islink(self):
       
  1096         return 'l' in self._flags
       
  1097     def renamed(self):
       
  1098         return self._copied