changeset 2567:2748253b49c2

Merge context patches
author Matt Mackall <mpm@selenic.com>
date Wed, 05 Jul 2006 13:28:25 -0500
parents bf67d0f6531c (current diff) d8560b458f76 (diff)
children 52ce0d6bc375 82e3b2966862
files mercurial/commands.py mercurial/localrepo.py
diffstat 4 files changed, 162 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -248,11 +248,11 @@ def revrange(ui, repo, revs):
             seen[rev] = 1
             yield str(rev)
 
-def make_filename(repo, r, pat, node=None,
+def make_filename(repo, pat, node,
                   total=None, seqno=None, revwidth=None, pathname=None):
     node_expander = {
         'H': lambda: hex(node),
-        'R': lambda: str(r.rev(node)),
+        'R': lambda: str(repo.changelog.rev(node)),
         'h': lambda: short(node),
         }
     expander = {
@@ -292,7 +292,7 @@ def make_filename(repo, r, pat, node=Non
         raise util.Abort(_("invalid format spec '%%%s' in output file name"),
                     inst.args[0])
 
-def make_file(repo, r, pat, node=None,
+def make_file(repo, pat, node=None,
               total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
     if not pat or pat == '-':
         return 'w' in mode and sys.stdout or sys.stdin
@@ -300,7 +300,7 @@ def make_file(repo, r, pat, node=None,
         return pat
     if hasattr(pat, 'read') and 'r' in mode:
         return pat
-    return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
+    return open(make_filename(repo, pat, node, total, seqno, revwidth,
                               pathname),
                 mode)
 
@@ -741,15 +741,18 @@ def annotate(ui, repo, *pats, **opts):
 
     ucache = {}
     def getname(rev):
-        cl = repo.changelog.read(repo.changelog.node(rev))
-        return trimuser(ui, cl[1], rev, ucache)
+        try:
+            return ucache[rev]
+        except:
+            u = trimuser(ui, repo.changectx(rev).user(), rev, ucache)
+            ucache[rev] = u
+            return u
 
     dcache = {}
     def getdate(rev):
         datestr = dcache.get(rev)
         if datestr is None:
-            cl = repo.changelog.read(repo.changelog.node(rev))
-            datestr = dcache[rev] = util.datestr(cl[2])
+            datestr = dcache[rev] = util.datestr(repo.changectx(rev).date())
         return datestr
 
     if not pats:
@@ -760,20 +763,15 @@ def annotate(ui, repo, *pats, **opts):
     if not opts['user'] and not opts['changeset'] and not opts['date']:
         opts['number'] = 1
 
-    if opts['rev']:
-        node = repo.changelog.lookup(opts['rev'])
-    else:
-        node = repo.dirstate.parents()[0]
-    change = repo.changelog.read(node)
-    mmap = repo.manifest.read(change[0])
+    ctx = repo.changectx(opts['rev'] or repo.dirstate.parents()[0])
 
     for src, abs, rel, exact in walk(repo, pats, opts, node=node):
-        f = repo.file(abs)
-        if not opts['text'] and util.binary(f.read(mmap[abs])):
+        fctx = ctx.filectx(abs)
+        if not opts['text'] and util.binary(fctx.data()):
             ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
             continue
 
-        lines = f.annotate(mmap[abs])
+        lines = fctx.annotate()
         pieces = []
 
         for o, f in opmap:
@@ -819,7 +817,7 @@ def archive(ui, repo, dest, **opts):
             raise util.Abort(_('uncommitted merge - please provide a '
                                'specific revision'))
 
-    dest = make_filename(repo, repo.changelog, dest, node)
+    dest = make_filename(repo, dest, node)
     if os.path.realpath(dest) == repo.root:
         raise util.Abort(_('repository root cannot be destination'))
     dummy, matchfn, dummy = matchpats(repo, [], opts)
@@ -830,7 +828,7 @@ def archive(ui, repo, dest, **opts):
             raise util.Abort(_('cannot archive plain files to stdout'))
         dest = sys.stdout
         if not prefix: prefix = os.path.basename(repo.root) + '-%h'
-    prefix = make_filename(repo, repo.changelog, prefix, node)
+    prefix = make_filename(repo, prefix, node)
     archival.archive(repo, dest, node, kind, not opts['no_decode'],
                      matchfn, prefix)
 
@@ -920,19 +918,10 @@ def cat(ui, repo, file1, *pats, **opts):
     %d   dirname of file being printed, or '.' if in repo root
     %p   root-relative path name of file being printed
     """
-    mf = {}
-    rev = opts['rev']
-    if rev:
-        node = repo.lookup(rev)
-    else:
-        node = repo.changelog.tip()
-    change = repo.changelog.read(node)
-    mf = repo.manifest.read(change[0])
-    for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
-        r = repo.file(abs)
-        n = mf[abs]
-        fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
-        fp.write(r.read(n))
+    ctx = repo.changectx(opts['rev'] or -1)
+    for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, ctx.node()):
+        fp = make_file(repo, opts['output'], ctx.node(), pathname=abs)
+        fp.write(ctx.filectx(abs).data())
 
 def clone(ui, source, dest=None, **opts):
     """make a copy of an existing repository
@@ -1501,8 +1490,7 @@ def doexport(ui, repo, changeset, seqno,
     prev = (parents and parents[0]) or nullid
     change = repo.changelog.read(node)
 
-    fp = make_file(repo, repo.changelog, opts['output'],
-                   node=node, total=total, seqno=seqno,
+    fp = make_file(repo, opts['output'], node, total=total, seqno=seqno,
                    revwidth=revwidth)
     if fp != sys.stdout:
         ui.note("%s\n" % fp.name)
new file mode 100644
--- /dev/null
+++ b/mercurial/context.py
@@ -0,0 +1,124 @@
+# context.py - changeset and file context objects for mercurial
+#
+# Copyright 2005 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+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"""
+        self._repo = repo
+        self._id = changeid
+
+        self._node = self._repo.lookup(self._id)
+        self._rev = self._repo.changelog.rev(self._node)
+
+    def changeset(self):
+        try:
+            return self._changeset
+        except AttributeError:
+            self._changeset = self._repo.changelog.read(self.node())
+            return self._changeset
+
+    def manifest(self):
+        try:
+            return self._manifest
+        except AttributeError:
+            self._manifest = self._repo.manifest.read(self.changeset()[0])
+            return self._manifest
+
+    def rev(self): return self._rev
+    def node(self): return self._node
+    def user(self): return self.changeset()[1]
+    def date(self): return self.changeset()[2]
+    def changedfiles(self): return self.changeset()[3]
+    def description(self): return self.changeset()[4]
+
+    def parents(self):
+        """return contexts for each parent changeset"""
+        p = self.repo.changelog.parents(self._node)
+        return [ changectx(self._repo, x) for x in p ]
+
+    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 filenode(self, path):
+        node, flag = self._repo.manifest.find(self.changeset()[0], path)
+        return node
+
+    def filectx(self, path):
+        """get a file context from this changeset"""
+        return filectx(self._repo, path, fileid=self.filenode(path))
+
+    def filectxs(self):
+        """generate a file context for each file in this changeset's
+           manifest"""
+        mf = self.manifest()
+        m = mf.keys()
+        m.sort()
+        for f in m:
+            yield self.filectx(f, fileid=mf[f])
+
+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):
+        """changeid can be a changeset revision, node, or tag.
+           fileid can be a file revision or node."""
+        self._repo = repo
+        self._path = path
+        self._id = changeid
+        self._fileid = fileid
+
+        if self._id:
+            # if given a changeset id, go ahead and look up the file
+            self._changeset = changectx(repo, self._id)
+            node, flag = self._repo.manifest.find(self._changeset[0], path)
+            self._node = node
+            self._filelog = self.repo.file(self._path)
+        elif self._fileid:
+            # else be lazy
+            self._filelog = self._repo.file(self._path)
+            self._filenode = self._filelog.lookup(self._fileid)
+        self._filerev = self._filelog.rev(self._filenode)
+
+    def changeset(self):
+        try:
+            return self._changeset
+        except AttributeError:
+            self._changeset = self._repo.changelog.read(self.node())
+            return self._changeset
+
+    def filerev(self): return self._filerev
+    def filenode(self): return self._filenode
+    def filelog(self): return self._filelog
+
+    def rev(self): return self.changeset().rev()
+    def node(self): return self.changeset().node()
+    def user(self): return self.changeset().user()
+    def date(self): return self.changeset().date()
+    def files(self): return self.changeset().files()
+    def description(self): return self.changeset().description()
+    def manifest(self): return self.changeset().manifest()
+
+    def data(self): return self._filelog.read(self._filenode)
+    def metadata(self): return self._filelog.readmeta(self._filenode)
+    def renamed(self): return self._filelog.renamed(self._filenode)
+
+    def parents(self):
+        # need to fix for renames
+        p = self._filelog.parents(self._filenode)
+        return [ filectx(self._repo, self._path, fileid=x) for x in p ]
+
+    def children(self):
+        # hard for renames
+        c = self._filelog.children(self._filenode)
+        return [ filectx(self._repo, self._path, fileid=x) for x in c ]
+
+    def annotate(self):
+        return self._filelog.annotate(self._filenode)
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -9,7 +9,7 @@ from node import *
 from i18n import gettext as _
 from demandload import *
 demandload(globals(), "appendfile changegroup")
-demandload(globals(), "changelog dirstate filelog manifest repo")
+demandload(globals(), "changelog dirstate filelog manifest repo context")
 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
 demandload(globals(), "os revlog util")
 
@@ -259,6 +259,14 @@ class localrepository(object):
             f = f[1:]
         return filelog.filelog(self.opener, f, self.revlogversion)
 
+    def changectx(self, changeid):
+        return context.changectx(self, changeid)
+
+    def filectx(self, path, changeid=None, fileid=None):
+        """changeid can be a changeset revision, node, or tag.
+           fileid can be a file revision or node."""
+        return context.filectx(self, path, changeid, fileid)
+
     def getcwd(self):
         return self.dirstate.getcwd()
 
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -743,6 +743,13 @@ class revlog(object):
 
     def lookup(self, id):
         """locate a node based on revision number or subset of hex nodeid"""
+        if id in self.nodemap:
+            return id
+        if type(id) == type(0):
+            rev = id
+            if rev < 0: rev = self.count() + rev
+            if rev < 0 or rev >= self.count(): return None
+            return self.node(rev)
         try:
             rev = int(id)
             if str(rev) != id: raise ValueError