mercurial/changelog.py
changeset 28487 98d98a645e9d
parent 28315 9e3f505b1d50
child 28489 8939a95064f1
--- a/mercurial/changelog.py	Fri Mar 11 11:51:22 2016 -0500
+++ b/mercurial/changelog.py	Sun Mar 06 14:28:02 2016 -0800
@@ -7,6 +7,8 @@
 
 from __future__ import absolute_import
 
+import collections
+
 from .i18n import _
 from .node import (
     bin,
@@ -136,6 +138,77 @@
         return appender(opener, name, mode, buf)
     return _delay
 
+_changelogrevision = collections.namedtuple('changelogrevision',
+                                            ('manifest', 'user', 'date',
+                                             'files', 'description', 'extra'))
+
+class changelogrevision(object):
+    """Holds results of a parsed changelog revision.
+
+    Changelog revisions consist of multiple pieces of data, including
+    the manifest node, user, and date. This object exposes a view into
+    the parsed object.
+    """
+
+    __slots__ = (
+        'date',
+        'description',
+        'extra',
+        'files',
+        'manifest',
+        'user',
+    )
+
+    def __new__(cls, text):
+        if not text:
+            return _changelogrevision(
+                manifest=nullid,
+                user='',
+                date=(0, 0),
+                files=[],
+                description='',
+                extra=_defaultextra,
+            )
+
+        self = super(changelogrevision, cls).__new__(cls)
+        # We could return here and implement the following as an __init__.
+        # But doing it here is equivalent and saves an extra function call.
+
+        # format used:
+        # nodeid\n        : manifest node in ascii
+        # user\n          : user, no \n or \r allowed
+        # time tz extra\n : date (time is int or float, timezone is int)
+        #                 : extra is metadata, encoded and separated by '\0'
+        #                 : older versions ignore it
+        # files\n\n       : files modified by the cset, no \n or \r allowed
+        # (.*)            : comment (free text, ideally utf-8)
+        #
+        # changelog v0 doesn't use extra
+
+        last = text.index("\n\n")
+        self.description = encoding.tolocal(text[last + 2:])
+        l = text[:last].split('\n')
+        self.manifest = bin(l[0])
+        self.user = encoding.tolocal(l[1])
+
+        tdata = l[2].split(' ', 2)
+        if len(tdata) != 3:
+            time = float(tdata[0])
+            try:
+                # various tools did silly things with the time zone field.
+                timezone = int(tdata[1])
+            except ValueError:
+                timezone = 0
+            self.extra = _defaultextra
+        else:
+            time, timezone = float(tdata[0]), int(tdata[1])
+            self.extra = decodeextra(tdata[2])
+
+        self.date = (time, timezone)
+        self.files = l[3:]
+
+        return self
+
 class changelog(revlog.revlog):
     def __init__(self, opener):
         revlog.revlog.__init__(self, opener, "00changelog.i")
@@ -323,42 +396,34 @@
             revlog.revlog.checkinlinesize(self, tr, fp)
 
     def read(self, node):
-        """
-        format used:
-        nodeid\n        : manifest node in ascii
-        user\n          : user, no \n or \r allowed
-        time tz extra\n : date (time is int or float, timezone is int)
-                        : extra is metadata, encoded and separated by '\0'
-                        : older versions ignore it
-        files\n\n       : files modified by the cset, no \n or \r allowed
-        (.*)            : comment (free text, ideally utf-8)
+        """Obtain data from a parsed changelog revision.
+
+        Returns a 6-tuple of:
 
-        changelog v0 doesn't use extra
+           - manifest node in binary
+           - author/user as a localstr
+           - date as a 2-tuple of (time, timezone)
+           - list of files
+           - commit message as a localstr
+           - dict of extra metadata
+
+        Unless you need to access all fields, consider calling
+        ``changelogrevision`` instead, as it is faster for partial object
+        access.
         """
-        text = self.revision(node)
-        if not text:
-            return nullid, "", (0, 0), [], "", _defaultextra
-        last = text.index("\n\n")
-        desc = encoding.tolocal(text[last + 2:])
-        l = text[:last].split('\n')
-        manifest = bin(l[0])
-        user = encoding.tolocal(l[1])
+        c = changelogrevision(self.revision(node))
+        return (
+            c.manifest,
+            c.user,
+            c.date,
+            c.files,
+            c.description,
+            c.extra
+        )
 
-        tdata = l[2].split(' ', 2)
-        if len(tdata) != 3:
-            time = float(tdata[0])
-            try:
-                # various tools did silly things with the time zone field.
-                timezone = int(tdata[1])
-            except ValueError:
-                timezone = 0
-            extra = _defaultextra
-        else:
-            time, timezone = float(tdata[0]), int(tdata[1])
-            extra = decodeextra(tdata[2])
-
-        files = l[3:]
-        return manifest, user, (time, timezone), files, desc, extra
+    def changelogrevision(self, nodeorrev):
+        """Obtain a ``changelogrevision`` for a node or revision."""
+        return changelogrevision(self.revision(nodeorrev))
 
     def readfiles(self, node):
         """