eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/context.py
changeset 69 c6bca38c1cbf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/context.py	Sat Jan 08 11:20:57 2011 +0530
@@ -0,0 +1,1098 @@
+# context.py - changeset and file context objects for mercurial
+#
+# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from node import nullid, nullrev, short, hex
+from i18n import _
+import ancestor, bdiff, error, util, subrepo, patch
+import os, errno, stat
+
+propertycache = util.propertycache
+
+class changectx(object):
+    """A changecontext object makes access to data related to a particular
+    changeset convenient."""
+    def __init__(self, repo, changeid=''):
+        """changeid is a revision number, node, or tag"""
+        if changeid == '':
+            changeid = '.'
+        self._repo = repo
+        if isinstance(changeid, (long, int)):
+            self._rev = changeid
+            self._node = self._repo.changelog.node(changeid)
+        else:
+            self._node = self._repo.lookup(changeid)
+            self._rev = self._repo.changelog.rev(self._node)
+
+    def __str__(self):
+        return short(self.node())
+
+    def __int__(self):
+        return self.rev()
+
+    def __repr__(self):
+        return "<changectx %s>" % str(self)
+
+    def __hash__(self):
+        try:
+            return hash(self._rev)
+        except AttributeError:
+            return id(self)
+
+    def __eq__(self, other):
+        try:
+            return self._rev == other._rev
+        except AttributeError:
+            return False
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __nonzero__(self):
+        return self._rev != nullrev
+
+    @propertycache
+    def _changeset(self):
+        return self._repo.changelog.read(self.node())
+
+    @propertycache
+    def _manifest(self):
+        return self._repo.manifest.read(self._changeset[0])
+
+    @propertycache
+    def _manifestdelta(self):
+        return self._repo.manifest.readdelta(self._changeset[0])
+
+    @propertycache
+    def _parents(self):
+        p = self._repo.changelog.parentrevs(self._rev)
+        if p[1] == nullrev:
+            p = p[:-1]
+        return [changectx(self._repo, x) for x in p]
+
+    @propertycache
+    def substate(self):
+        return subrepo.state(self, self._repo.ui)
+
+    def __contains__(self, key):
+        return key in self._manifest
+
+    def __getitem__(self, key):
+        return self.filectx(key)
+
+    def __iter__(self):
+        for f in sorted(self._manifest):
+            yield f
+
+    def changeset(self):
+        return self._changeset
+    def manifest(self):
+        return self._manifest
+    def manifestnode(self):
+        return self._changeset[0]
+
+    def rev(self):
+        return self._rev
+    def node(self):
+        return self._node
+    def hex(self):
+        return hex(self._node)
+    def user(self):
+        return self._changeset[1]
+    def date(self):
+        return self._changeset[2]
+    def files(self):
+        return self._changeset[3]
+    def description(self):
+        return self._changeset[4]
+    def branch(self):
+        return self._changeset[5].get("branch")
+    def extra(self):
+        return self._changeset[5]
+    def tags(self):
+        return self._repo.nodetags(self._node)
+
+    def parents(self):
+        """return contexts for each parent changeset"""
+        return self._parents
+
+    def p1(self):
+        return self._parents[0]
+
+    def p2(self):
+        if len(self._parents) == 2:
+            return self._parents[1]
+        return changectx(self._repo, -1)
+
+    def children(self):
+        """return contexts for each child changeset"""
+        c = self._repo.changelog.children(self._node)
+        return [changectx(self._repo, x) for x in c]
+
+    def ancestors(self):
+        for a in self._repo.changelog.ancestors(self._rev):
+            yield changectx(self._repo, a)
+
+    def descendants(self):
+        for d in self._repo.changelog.descendants(self._rev):
+            yield changectx(self._repo, d)
+
+    def _fileinfo(self, path):
+        if '_manifest' in self.__dict__:
+            try:
+                return self._manifest[path], self._manifest.flags(path)
+            except KeyError:
+                raise error.LookupError(self._node, path,
+                                        _('not found in manifest'))
+        if '_manifestdelta' in self.__dict__ or path in self.files():
+            if path in self._manifestdelta:
+                return self._manifestdelta[path], self._manifestdelta.flags(path)
+        node, flag = self._repo.manifest.find(self._changeset[0], path)
+        if not node:
+            raise error.LookupError(self._node, path,
+                                    _('not found in manifest'))
+
+        return node, flag
+
+    def filenode(self, path):
+        return self._fileinfo(path)[0]
+
+    def flags(self, path):
+        try:
+            return self._fileinfo(path)[1]
+        except error.LookupError:
+            return ''
+
+    def filectx(self, path, fileid=None, filelog=None):
+        """get a file context from this changeset"""
+        if fileid is None:
+            fileid = self.filenode(path)
+        return filectx(self._repo, path, fileid=fileid,
+                       changectx=self, filelog=filelog)
+
+    def ancestor(self, c2):
+        """
+        return the ancestor context of self and c2
+        """
+        # deal with workingctxs
+        n2 = c2._node
+        if n2 == None:
+            n2 = c2._parents[0]._node
+        n = self._repo.changelog.ancestor(self._node, n2)
+        return changectx(self._repo, n)
+
+    def walk(self, match):
+        fset = set(match.files())
+        # for dirstate.walk, files=['.'] means "walk the whole tree".
+        # follow that here, too
+        fset.discard('.')
+        for fn in self:
+            for ffn in fset:
+                # match if the file is the exact name or a directory
+                if ffn == fn or fn.startswith("%s/" % ffn):
+                    fset.remove(ffn)
+                    break
+            if match(fn):
+                yield fn
+        for fn in sorted(fset):
+            if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
+                yield fn
+
+    def sub(self, path):
+        return subrepo.subrepo(self, path)
+
+    def diff(self, ctx2=None, match=None, **opts):
+        """Returns a diff generator for the given contexts and matcher"""
+        if ctx2 is None:
+            ctx2 = self.p1()
+        if ctx2 is not None and not isinstance(ctx2, changectx):
+            ctx2 = self._repo[ctx2]
+        diffopts = patch.diffopts(self._repo.ui, opts)
+        return patch.diff(self._repo, ctx2.node(), self.node(),
+                          match=match, opts=diffopts)
+
+class filectx(object):
+    """A filecontext object makes access to data related to a particular
+       filerevision convenient."""
+    def __init__(self, repo, path, changeid=None, fileid=None,
+                 filelog=None, changectx=None):
+        """changeid can be a changeset revision, node, or tag.
+           fileid can be a file revision or node."""
+        self._repo = repo
+        self._path = path
+
+        assert (changeid is not None
+                or fileid is not None
+                or changectx is not None), \
+                ("bad args: changeid=%r, fileid=%r, changectx=%r"
+                 % (changeid, fileid, changectx))
+
+        if filelog:
+            self._filelog = filelog
+
+        if changeid is not None:
+            self._changeid = changeid
+        if changectx is not None:
+            self._changectx = changectx
+        if fileid is not None:
+            self._fileid = fileid
+
+    @propertycache
+    def _changectx(self):
+        return changectx(self._repo, self._changeid)
+
+    @propertycache
+    def _filelog(self):
+        return self._repo.file(self._path)
+
+    @propertycache
+    def _changeid(self):
+        if '_changectx' in self.__dict__:
+            return self._changectx.rev()
+        else:
+            return self._filelog.linkrev(self._filerev)
+
+    @propertycache
+    def _filenode(self):
+        if '_fileid' in self.__dict__:
+            return self._filelog.lookup(self._fileid)
+        else:
+            return self._changectx.filenode(self._path)
+
+    @propertycache
+    def _filerev(self):
+        return self._filelog.rev(self._filenode)
+
+    @propertycache
+    def _repopath(self):
+        return self._path
+
+    def __nonzero__(self):
+        try:
+            self._filenode
+            return True
+        except error.LookupError:
+            # file is missing
+            return False
+
+    def __str__(self):
+        return "%s@%s" % (self.path(), short(self.node()))
+
+    def __repr__(self):
+        return "<filectx %s>" % str(self)
+
+    def __hash__(self):
+        try:
+            return hash((self._path, self._filenode))
+        except AttributeError:
+            return id(self)
+
+    def __eq__(self, other):
+        try:
+            return (self._path == other._path
+                    and self._filenode == other._filenode)
+        except AttributeError:
+            return False
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def filectx(self, fileid):
+        '''opens an arbitrary revision of the file without
+        opening a new filelog'''
+        return filectx(self._repo, self._path, fileid=fileid,
+                       filelog=self._filelog)
+
+    def filerev(self):
+        return self._filerev
+    def filenode(self):
+        return self._filenode
+    def flags(self):
+        return self._changectx.flags(self._path)
+    def filelog(self):
+        return self._filelog
+
+    def rev(self):
+        if '_changectx' in self.__dict__:
+            return self._changectx.rev()
+        if '_changeid' in self.__dict__:
+            return self._changectx.rev()
+        return self._filelog.linkrev(self._filerev)
+
+    def linkrev(self):
+        return self._filelog.linkrev(self._filerev)
+    def node(self):
+        return self._changectx.node()
+    def hex(self):
+        return hex(self.node())
+    def user(self):
+        return self._changectx.user()
+    def date(self):
+        return self._changectx.date()
+    def files(self):
+        return self._changectx.files()
+    def description(self):
+        return self._changectx.description()
+    def branch(self):
+        return self._changectx.branch()
+    def extra(self):
+        return self._changectx.extra()
+    def manifest(self):
+        return self._changectx.manifest()
+    def changectx(self):
+        return self._changectx
+
+    def data(self):
+        return self._filelog.read(self._filenode)
+    def path(self):
+        return self._path
+    def size(self):
+        return self._filelog.size(self._filerev)
+
+    def cmp(self, fctx):
+        """compare with other file context
+
+        returns True if different than fctx.
+        """
+        if (fctx._filerev is None and self._repo._encodefilterpats
+            or self.size() == fctx.size()):
+            return self._filelog.cmp(self._filenode, fctx.data())
+
+        return True
+
+    def renamed(self):
+        """check if file was actually renamed in this changeset revision
+
+        If rename logged in file revision, we report copy for changeset only
+        if file revisions linkrev points back to the changeset in question
+        or both changeset parents contain different file revisions.
+        """
+
+        renamed = self._filelog.renamed(self._filenode)
+        if not renamed:
+            return renamed
+
+        if self.rev() == self.linkrev():
+            return renamed
+
+        name = self.path()
+        fnode = self._filenode
+        for p in self._changectx.parents():
+            try:
+                if fnode == p.filenode(name):
+                    return None
+            except error.LookupError:
+                pass
+        return renamed
+
+    def parents(self):
+        p = self._path
+        fl = self._filelog
+        pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
+
+        r = self._filelog.renamed(self._filenode)
+        if r:
+            pl[0] = (r[0], r[1], None)
+
+        return [filectx(self._repo, p, fileid=n, filelog=l)
+                for p, n, l in pl if n != nullid]
+
+    def children(self):
+        # hard for renames
+        c = self._filelog.children(self._filenode)
+        return [filectx(self._repo, self._path, fileid=x,
+                        filelog=self._filelog) for x in c]
+
+    def annotate(self, follow=False, linenumber=None):
+        '''returns a list of tuples of (ctx, line) for each line
+        in the file, where ctx is the filectx of the node where
+        that line was last changed.
+        This returns tuples of ((ctx, linenumber), line) for each line,
+        if "linenumber" parameter is NOT "None".
+        In such tuples, linenumber means one at the first appearance
+        in the managed file.
+        To reduce annotation cost,
+        this returns fixed value(False is used) as linenumber,
+        if "linenumber" parameter is "False".'''
+
+        def decorate_compat(text, rev):
+            return ([rev] * len(text.splitlines()), text)
+
+        def without_linenumber(text, rev):
+            return ([(rev, False)] * len(text.splitlines()), text)
+
+        def with_linenumber(text, rev):
+            size = len(text.splitlines())
+            return ([(rev, i) for i in xrange(1, size + 1)], text)
+
+        decorate = (((linenumber is None) and decorate_compat) or
+                    (linenumber and with_linenumber) or
+                    without_linenumber)
+
+        def pair(parent, child):
+            for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
+                child[0][b1:b2] = parent[0][a1:a2]
+            return child
+
+        getlog = util.lrucachefunc(lambda x: self._repo.file(x))
+        def getctx(path, fileid):
+            log = path == self._path and self._filelog or getlog(path)
+            return filectx(self._repo, path, fileid=fileid, filelog=log)
+        getctx = util.lrucachefunc(getctx)
+
+        def parents(f):
+            # we want to reuse filectx objects as much as possible
+            p = f._path
+            if f._filerev is None: # working dir
+                pl = [(n.path(), n.filerev()) for n in f.parents()]
+            else:
+                pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
+
+            if follow:
+                r = f.renamed()
+                if r:
+                    pl[0] = (r[0], getlog(r[0]).rev(r[1]))
+
+            return [getctx(p, n) for p, n in pl if n != nullrev]
+
+        # use linkrev to find the first changeset where self appeared
+        if self.rev() != self.linkrev():
+            base = self.filectx(self.filerev())
+        else:
+            base = self
+
+        # find all ancestors
+        needed = {base: 1}
+        visit = [base]
+        files = [base._path]
+        while visit:
+            f = visit.pop(0)
+            for p in parents(f):
+                if p not in needed:
+                    needed[p] = 1
+                    visit.append(p)
+                    if p._path not in files:
+                        files.append(p._path)
+                else:
+                    # count how many times we'll use this
+                    needed[p] += 1
+
+        # sort by revision (per file) which is a topological order
+        visit = []
+        for f in files:
+            visit.extend(n for n in needed if n._path == f)
+
+        hist = {}
+        for f in sorted(visit, key=lambda x: x.rev()):
+            curr = decorate(f.data(), f)
+            for p in parents(f):
+                curr = pair(hist[p], curr)
+                # trim the history of unneeded revs
+                needed[p] -= 1
+                if not needed[p]:
+                    del hist[p]
+            hist[f] = curr
+
+        return zip(hist[f][0], hist[f][1].splitlines(True))
+
+    def ancestor(self, fc2, actx=None):
+        """
+        find the common ancestor file context, if any, of self, and fc2
+
+        If actx is given, it must be the changectx of the common ancestor
+        of self's and fc2's respective changesets.
+        """
+
+        if actx is None:
+            actx = self.changectx().ancestor(fc2.changectx())
+
+        # the trivial case: changesets are unrelated, files must be too
+        if not actx:
+            return None
+
+        # the easy case: no (relevant) renames
+        if fc2.path() == self.path() and self.path() in actx:
+            return actx[self.path()]
+        acache = {}
+
+        # prime the ancestor cache for the working directory
+        for c in (self, fc2):
+            if c._filerev is None:
+                pl = [(n.path(), n.filenode()) for n in c.parents()]
+                acache[(c._path, None)] = pl
+
+        flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
+        def parents(vertex):
+            if vertex in acache:
+                return acache[vertex]
+            f, n = vertex
+            if f not in flcache:
+                flcache[f] = self._repo.file(f)
+            fl = flcache[f]
+            pl = [(f, p) for p in fl.parents(n) if p != nullid]
+            re = fl.renamed(n)
+            if re:
+                pl.append(re)
+            acache[vertex] = pl
+            return pl
+
+        a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
+        v = ancestor.ancestor(a, b, parents)
+        if v:
+            f, n = v
+            return filectx(self._repo, f, fileid=n, filelog=flcache[f])
+
+        return None
+
+    def ancestors(self):
+        seen = set(str(self))
+        visit = [self]
+        while visit:
+            for parent in visit.pop(0).parents():
+                s = str(parent)
+                if s not in seen:
+                    visit.append(parent)
+                    seen.add(s)
+                    yield parent
+
+class workingctx(changectx):
+    """A workingctx object makes access to data related to
+    the current working directory convenient.
+    date - any valid date string or (unixtime, offset), or None.
+    user - username string, or None.
+    extra - a dictionary of extra values, or None.
+    changes - a list of file lists as returned by localrepo.status()
+               or None to use the repository status.
+    """
+    def __init__(self, repo, text="", user=None, date=None, extra=None,
+                 changes=None):
+        self._repo = repo
+        self._rev = None
+        self._node = None
+        self._text = text
+        if date:
+            self._date = util.parsedate(date)
+        if user:
+            self._user = user
+        if changes:
+            self._status = list(changes[:4])
+            self._unknown = changes[4]
+            self._ignored = changes[5]
+            self._clean = changes[6]
+        else:
+            self._unknown = None
+            self._ignored = None
+            self._clean = None
+
+        self._extra = {}
+        if extra:
+            self._extra = extra.copy()
+        if 'branch' not in self._extra:
+            branch = self._repo.dirstate.branch()
+            try:
+                branch = branch.decode('UTF-8').encode('UTF-8')
+            except UnicodeDecodeError:
+                raise util.Abort(_('branch name not in UTF-8!'))
+            self._extra['branch'] = branch
+        if self._extra['branch'] == '':
+            self._extra['branch'] = 'default'
+
+    def __str__(self):
+        return str(self._parents[0]) + "+"
+
+    def __nonzero__(self):
+        return True
+
+    def __contains__(self, key):
+        return self._repo.dirstate[key] not in "?r"
+
+    @propertycache
+    def _manifest(self):
+        """generate a manifest corresponding to the working directory"""
+
+        if self._unknown is None:
+            self.status(unknown=True)
+
+        man = self._parents[0].manifest().copy()
+        copied = self._repo.dirstate.copies()
+        if len(self._parents) > 1:
+            man2 = self.p2().manifest()
+            def getman(f):
+                if f in man:
+                    return man
+                return man2
+        else:
+            getman = lambda f: man
+        def cf(f):
+            f = copied.get(f, f)
+            return getman(f).flags(f)
+        ff = self._repo.dirstate.flagfunc(cf)
+        modified, added, removed, deleted = self._status
+        unknown = self._unknown
+        for i, l in (("a", added), ("m", modified), ("u", unknown)):
+            for f in l:
+                orig = copied.get(f, f)
+                man[f] = getman(orig).get(orig, nullid) + i
+                try:
+                    man.set(f, ff(f))
+                except OSError:
+                    pass
+
+        for f in deleted + removed:
+            if f in man:
+                del man[f]
+
+        return man
+
+    @propertycache
+    def _status(self):
+        return self._repo.status()[:4]
+
+    @propertycache
+    def _user(self):
+        return self._repo.ui.username()
+
+    @propertycache
+    def _date(self):
+        return util.makedate()
+
+    @propertycache
+    def _parents(self):
+        p = self._repo.dirstate.parents()
+        if p[1] == nullid:
+            p = p[:-1]
+        self._parents = [changectx(self._repo, x) for x in p]
+        return self._parents
+
+    def status(self, ignored=False, clean=False, unknown=False):
+        """Explicit status query
+        Unless this method is used to query the working copy status, the
+        _status property will implicitly read the status using its default
+        arguments."""
+        stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
+        self._unknown = self._ignored = self._clean = None
+        if unknown:
+            self._unknown = stat[4]
+        if ignored:
+            self._ignored = stat[5]
+        if clean:
+            self._clean = stat[6]
+        self._status = stat[:4]
+        return stat
+
+    def manifest(self):
+        return self._manifest
+    def user(self):
+        return self._user or self._repo.ui.username()
+    def date(self):
+        return self._date
+    def description(self):
+        return self._text
+    def files(self):
+        return sorted(self._status[0] + self._status[1] + self._status[2])
+
+    def modified(self):
+        return self._status[0]
+    def added(self):
+        return self._status[1]
+    def removed(self):
+        return self._status[2]
+    def deleted(self):
+        return self._status[3]
+    def unknown(self):
+        assert self._unknown is not None  # must call status first
+        return self._unknown
+    def ignored(self):
+        assert self._ignored is not None  # must call status first
+        return self._ignored
+    def clean(self):
+        assert self._clean is not None  # must call status first
+        return self._clean
+    def branch(self):
+        return self._extra['branch']
+    def extra(self):
+        return self._extra
+
+    def tags(self):
+        t = []
+        [t.extend(p.tags()) for p in self.parents()]
+        return t
+
+    def children(self):
+        return []
+
+    def flags(self, path):
+        if '_manifest' in self.__dict__:
+            try:
+                return self._manifest.flags(path)
+            except KeyError:
+                return ''
+
+        orig = self._repo.dirstate.copies().get(path, path)
+
+        def findflag(ctx):
+            mnode = ctx.changeset()[0]
+            node, flag = self._repo.manifest.find(mnode, orig)
+            ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
+            try:
+                return ff(path)
+            except OSError:
+                pass
+
+        flag = findflag(self._parents[0])
+        if flag is None and len(self.parents()) > 1:
+            flag = findflag(self._parents[1])
+        if flag is None or self._repo.dirstate[path] == 'r':
+            return ''
+        return flag
+
+    def filectx(self, path, filelog=None):
+        """get a file context from the working directory"""
+        return workingfilectx(self._repo, path, workingctx=self,
+                              filelog=filelog)
+
+    def ancestor(self, c2):
+        """return the ancestor context of self and c2"""
+        return self._parents[0].ancestor(c2) # punt on two parents for now
+
+    def walk(self, match):
+        return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
+                                               True, False))
+
+    def dirty(self, missing=False):
+        "check whether a working directory is modified"
+        # check subrepos first
+        for s in self.substate:
+            if self.sub(s).dirty():
+                return True
+        # check current working dir
+        return (self.p2() or self.branch() != self.p1().branch() or
+                self.modified() or self.added() or self.removed() or
+                (missing and self.deleted()))
+
+    def add(self, list, prefix=""):
+        join = lambda f: os.path.join(prefix, f)
+        wlock = self._repo.wlock()
+        ui, ds = self._repo.ui, self._repo.dirstate
+        try:
+            rejected = []
+            for f in list:
+                p = self._repo.wjoin(f)
+                try:
+                    st = os.lstat(p)
+                except:
+                    ui.warn(_("%s does not exist!\n") % join(f))
+                    rejected.append(f)
+                    continue
+                if st.st_size > 10000000:
+                    ui.warn(_("%s: up to %d MB of RAM may be required "
+                              "to manage this file\n"
+                              "(use 'hg revert %s' to cancel the "
+                              "pending addition)\n")
+                              % (f, 3 * st.st_size // 1000000, join(f)))
+                if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
+                    ui.warn(_("%s not added: only files and symlinks "
+                              "supported currently\n") % join(f))
+                    rejected.append(p)
+                elif ds[f] in 'amn':
+                    ui.warn(_("%s already tracked!\n") % join(f))
+                elif ds[f] == 'r':
+                    ds.normallookup(f)
+                else:
+                    ds.add(f)
+            return rejected
+        finally:
+            wlock.release()
+
+    def forget(self, list):
+        wlock = self._repo.wlock()
+        try:
+            for f in list:
+                if self._repo.dirstate[f] != 'a':
+                    self._repo.ui.warn(_("%s not added!\n") % f)
+                else:
+                    self._repo.dirstate.forget(f)
+        finally:
+            wlock.release()
+
+    def ancestors(self):
+        for a in self._repo.changelog.ancestors(
+            *[p.rev() for p in self._parents]):
+            yield changectx(self._repo, a)
+
+    def remove(self, list, unlink=False):
+        if unlink:
+            for f in list:
+                try:
+                    util.unlink(self._repo.wjoin(f))
+                except OSError, inst:
+                    if inst.errno != errno.ENOENT:
+                        raise
+        wlock = self._repo.wlock()
+        try:
+            for f in list:
+                if unlink and os.path.lexists(self._repo.wjoin(f)):
+                    self._repo.ui.warn(_("%s still exists!\n") % f)
+                elif self._repo.dirstate[f] == 'a':
+                    self._repo.dirstate.forget(f)
+                elif f not in self._repo.dirstate:
+                    self._repo.ui.warn(_("%s not tracked!\n") % f)
+                else:
+                    self._repo.dirstate.remove(f)
+        finally:
+            wlock.release()
+
+    def undelete(self, list):
+        pctxs = self.parents()
+        wlock = self._repo.wlock()
+        try:
+            for f in list:
+                if self._repo.dirstate[f] != 'r':
+                    self._repo.ui.warn(_("%s not removed!\n") % f)
+                else:
+                    fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
+                    t = fctx.data()
+                    self._repo.wwrite(f, t, fctx.flags())
+                    self._repo.dirstate.normal(f)
+        finally:
+            wlock.release()
+
+    def copy(self, source, dest):
+        p = self._repo.wjoin(dest)
+        if not os.path.lexists(p):
+            self._repo.ui.warn(_("%s does not exist!\n") % dest)
+        elif not (os.path.isfile(p) or os.path.islink(p)):
+            self._repo.ui.warn(_("copy failed: %s is not a file or a "
+                                 "symbolic link\n") % dest)
+        else:
+            wlock = self._repo.wlock()
+            try:
+                if self._repo.dirstate[dest] in '?r':
+                    self._repo.dirstate.add(dest)
+                self._repo.dirstate.copy(source, dest)
+            finally:
+                wlock.release()
+
+class workingfilectx(filectx):
+    """A workingfilectx object makes access to data related to a particular
+       file in the working directory convenient."""
+    def __init__(self, repo, path, filelog=None, workingctx=None):
+        """changeid can be a changeset revision, node, or tag.
+           fileid can be a file revision or node."""
+        self._repo = repo
+        self._path = path
+        self._changeid = None
+        self._filerev = self._filenode = None
+
+        if filelog:
+            self._filelog = filelog
+        if workingctx:
+            self._changectx = workingctx
+
+    @propertycache
+    def _changectx(self):
+        return workingctx(self._repo)
+
+    def __nonzero__(self):
+        return True
+
+    def __str__(self):
+        return "%s@%s" % (self.path(), self._changectx)
+
+    def data(self):
+        return self._repo.wread(self._path)
+    def renamed(self):
+        rp = self._repo.dirstate.copied(self._path)
+        if not rp:
+            return None
+        return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
+
+    def parents(self):
+        '''return parent filectxs, following copies if necessary'''
+        def filenode(ctx, path):
+            return ctx._manifest.get(path, nullid)
+
+        path = self._path
+        fl = self._filelog
+        pcl = self._changectx._parents
+        renamed = self.renamed()
+
+        if renamed:
+            pl = [renamed + (None,)]
+        else:
+            pl = [(path, filenode(pcl[0], path), fl)]
+
+        for pc in pcl[1:]:
+            pl.append((path, filenode(pc, path), fl))
+
+        return [filectx(self._repo, p, fileid=n, filelog=l)
+                for p, n, l in pl if n != nullid]
+
+    def children(self):
+        return []
+
+    def size(self):
+        return os.lstat(self._repo.wjoin(self._path)).st_size
+    def date(self):
+        t, tz = self._changectx.date()
+        try:
+            return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
+        except OSError, err:
+            if err.errno != errno.ENOENT:
+                raise
+            return (t, tz)
+
+    def cmp(self, fctx):
+        """compare with other file context
+
+        returns True if different than fctx.
+        """
+        # fctx should be a filectx (not a wfctx)
+        # invert comparison to reuse the same code path
+        return fctx.cmp(self)
+
+class memctx(object):
+    """Use memctx to perform in-memory commits via localrepo.commitctx().
+
+    Revision information is supplied at initialization time while
+    related files data and is made available through a callback
+    mechanism.  'repo' is the current localrepo, 'parents' is a
+    sequence of two parent revisions identifiers (pass None for every
+    missing parent), 'text' is the commit message and 'files' lists
+    names of files touched by the revision (normalized and relative to
+    repository root).
+
+    filectxfn(repo, memctx, path) is a callable receiving the
+    repository, the current memctx object and the normalized path of
+    requested file, relative to repository root. It is fired by the
+    commit function for every file in 'files', but calls order is
+    undefined. If the file is available in the revision being
+    committed (updated or added), filectxfn returns a memfilectx
+    object. If the file was removed, filectxfn raises an
+    IOError. Moved files are represented by marking the source file
+    removed and the new file added with copy information (see
+    memfilectx).
+
+    user receives the committer name and defaults to current
+    repository username, date is the commit date in any format
+    supported by util.parsedate() and defaults to current date, extra
+    is a dictionary of metadata or is left empty.
+    """
+    def __init__(self, repo, parents, text, files, filectxfn, user=None,
+                 date=None, extra=None):
+        self._repo = repo
+        self._rev = None
+        self._node = None
+        self._text = text
+        self._date = date and util.parsedate(date) or util.makedate()
+        self._user = user
+        parents = [(p or nullid) for p in parents]
+        p1, p2 = parents
+        self._parents = [changectx(self._repo, p) for p in (p1, p2)]
+        files = sorted(set(files))
+        self._status = [files, [], [], [], []]
+        self._filectxfn = filectxfn
+
+        self._extra = extra and extra.copy() or {}
+        if 'branch' not in self._extra:
+            self._extra['branch'] = 'default'
+        elif self._extra.get('branch') == '':
+            self._extra['branch'] = 'default'
+
+    def __str__(self):
+        return str(self._parents[0]) + "+"
+
+    def __int__(self):
+        return self._rev
+
+    def __nonzero__(self):
+        return True
+
+    def __getitem__(self, key):
+        return self.filectx(key)
+
+    def p1(self):
+        return self._parents[0]
+    def p2(self):
+        return self._parents[1]
+
+    def user(self):
+        return self._user or self._repo.ui.username()
+    def date(self):
+        return self._date
+    def description(self):
+        return self._text
+    def files(self):
+        return self.modified()
+    def modified(self):
+        return self._status[0]
+    def added(self):
+        return self._status[1]
+    def removed(self):
+        return self._status[2]
+    def deleted(self):
+        return self._status[3]
+    def unknown(self):
+        return self._status[4]
+    def ignored(self):
+        return self._status[5]
+    def clean(self):
+        return self._status[6]
+    def branch(self):
+        return self._extra['branch']
+    def extra(self):
+        return self._extra
+    def flags(self, f):
+        return self[f].flags()
+
+    def parents(self):
+        """return contexts for each parent changeset"""
+        return self._parents
+
+    def filectx(self, path, filelog=None):
+        """get a file context from the working directory"""
+        return self._filectxfn(self._repo, self, path)
+
+    def commit(self):
+        """commit context to the repo"""
+        return self._repo.commitctx(self)
+
+class memfilectx(object):
+    """memfilectx represents an in-memory file to commit.
+
+    See memctx for more details.
+    """
+    def __init__(self, path, data, islink=False, isexec=False, copied=None):
+        """
+        path is the normalized file path relative to repository root.
+        data is the file content as a string.
+        islink is True if the file is a symbolic link.
+        isexec is True if the file is executable.
+        copied is the source file path if current file was copied in the
+        revision being committed, or None."""
+        self._path = path
+        self._data = data
+        self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
+        self._copied = None
+        if copied:
+            self._copied = (copied, nullid)
+
+    def __nonzero__(self):
+        return True
+    def __str__(self):
+        return "%s@%s" % (self.path(), self._changectx)
+    def path(self):
+        return self._path
+    def data(self):
+        return self._data
+    def flags(self):
+        return self._flags
+    def isexec(self):
+        return 'x' in self._flags
+    def islink(self):
+        return 'l' in self._flags
+    def renamed(self):
+        return self._copied