[PATCH 1 of 6 OBSOLETE-MARKERS] obsolete: introduction obsolete markers

Pierre-Yves David pierre-yves.david at ens-lyon.org
Sat May 12 12:08:49 CDT 2012


# HG changeset patch
# User Pierre-Yves.David at ens-lyon.org
# Date 1336836092 -7200
# Node ID b2cecf48226020c40d448617fc6d421c4e7f6cfe
# Parent  ddd4996740c785cc187766249977ea3ece8c17ab
obsolete: introduction obsolete markers

This changeset add a obsstore attribute on localrepository. We are able to Save
to disk and load to it. Format is describe in the module docstring.

There is still a lot a work to do. In particular using standard transaction to
manage the write and aborting.

diff -r ddd4996740c7 -r b2cecf482260 mercurial/localrepo.py
--- a/mercurial/localrepo.py	Tue May 08 12:05:45 2012 -0500
+++ b/mercurial/localrepo.py	Sat May 12 17:21:32 2012 +0200
@@ -7,7 +7,7 @@
 
 from node import bin, hex, nullid, nullrev, short
 from i18n import _
-import repo, changegroup, subrepo, discovery, pushkey
+import repo, changegroup, subrepo, discovery, pushkey, obsolete
 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
 import lock, transaction, store, encoding
 import scmutil, util, extensions, hook, error, revset
@@ -200,6 +200,14 @@
                     cache[rev] = phase
         return cache
 
+    @storecache('obsmarkers')
+    def obsstore(self):
+        return obsolete.readmarkers(self)
+
+    @property
+    def _dirtyobsstore(self):
+        return 'obsstore' in vars(self) and self.obsstore._new
+
     @storecache('00changelog.i')
     def changelog(self):
         c = changelog.changelog(self.sopener)
@@ -935,6 +943,8 @@
             if self._dirtyphases:
                 phases.writeroots(self)
                 self._dirtyphases = False
+            if self._dirtyobsstore:
+                obsolete.writemarkers(self)
             for k, ce in self._filecache.items():
                 if k == 'dirstate':
                     continue
diff -r ddd4996740c7 -r b2cecf482260 mercurial/obsolete.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/obsolete.py	Sat May 12 17:21:32 2012 +0200
@@ -0,0 +1,163 @@
+"""Code related to Obsolete marker
+
+
+Obsolete marker content
+------------------------------
+
+A marker hold 4 data:
+* obsoleted changeset (1 mandatory node id)
+* replacements changeset (0..n node id)
+* binary flag field (for various use)
+* various metadata (date, user, maybe more)
+
+Obsolete marker storage format
+------------------------------
+
+The file starts with a version header
+* 'I' size of version String
+* '-s' Version string
+
+Remaining content are about markers. Each marker consist in:
+
+    A fixed width header:
+    * 'B' number of remplacements: "nb-R" (in number of nodes)
+    * 'I' metadata size (in Bytes)
+    * 'B' multipurpose Flag Field
+    * '20s' Obsoleted nodeid
+
+    Then Replacements nodeid:
+    * (20s){nb-R}
+
+    Then comes metadata:
+    * (key\0value\0)+
+
+    Final result looks like::
+
+        ">B>I>B20s(20s)*(-s\0-s\0)*
+"""
+import struct
+_pack = struct.pack
+_unpack = struct.unpack
+
+from mercurial import util
+from i18n import _
+
+
+# data used for parsing and writing
+fmversion = 'Alpha'
+fmfixed   = '>BIB20s'
+fmnode = '20s'
+fmfsize = struct.calcsize(fmfixed)
+fnodesize = struct.calcsize(fmnode)
+
+
+def readmarkers(repo):
+    """Read obsolete markers from disk
+
+    (This need to be splitted to allow parsing content from the network)"""
+    store = obsstore()
+    try:
+        f = repo.sopener('obsmarkers', 'rb')
+        try:
+            data = f.read()
+        finally:
+            f.close()
+        ### read format version
+        # XXX need some security around here if the file is really short.
+        # version size
+        off = 0
+        s = struct.calcsize('>Q')
+        cur = data[off:off + s]
+        off += s
+        s, = _unpack('>Q', cur)
+        # version String itself
+        diskversion = data[off:off + s]
+        off += s
+        if diskversion != fmversion:
+            raise util.Abort(_('parsing obsolete marker: unknown version %r')
+                             % diskversion)
+        l = len(data)
+        while off + fmfsize <= l:
+            ### read marker
+            # read fixed part
+            cur = data[off:off + fmfsize]
+            off += fmfsize
+            nbrepl, mdsize, flags, obs = _unpack(fmfixed, cur)
+            # read replacement
+            repls = tuple()
+            if nbrepl:
+                s = (fnodesize * nbrepl)
+                cur = data[off:off + s]
+                repls = _unpack(fmnode * nbrepl, cur)
+                off += s
+            # read metadata
+            # (metadata will be decoded on demand)
+            metadata = data[off:off + mdsize]
+            off += mdsize
+            marker = (obs, repls, flags, metadata)
+            store._load(marker)
+    except IOError:
+        pass
+    return store
+
+
+def writemarkers(repo):
+    """Write obsolete markers to disk
+
+    Transaction logic should be used here. But for now rewritting the whole
+    file is good enough.
+
+    (This need to be splitted to allow parsing content from the network)"""
+    f = repo.sopener('obsmarkers', 'wb', atomictemp=True)
+    store = repo.obsstore
+    try:
+        ### write version header
+        vlen = len(fmversion)
+        f.write(_pack('>Q', vlen))
+        f.write(_pack('%is' % vlen, fmversion))
+        for marker in store._all:
+            ### marker
+            # fixed header
+            obs, repls, flags, metadata = marker
+            nbrepl = len(repls)
+            format = fmfixed + (fmnode * nbrepl)
+            data = [nbrepl, len(metadata), flags, obs]
+            data.extend(repls)
+            f.write(_pack(format, *data))
+            f.write(metadata)
+        store._new[:] = []
+    finally:
+        f.close()
+
+
+class obsstore(object):
+    """store all obsolete marker
+
+    marker are accessible through two mappingi
+
+    :store.obsoleted: obsolete -> marker
+    :store.replacements: replacement -> marker
+    """
+
+    def __init__(self):
+        self.obsoleted = {}
+        self.replacements = {}
+        self._all = []
+        self._new = []
+
+    def add(self, marker):
+        """Add a new marker to the store
+
+        This marker still need to be written to disk"""
+        self._new.append(marker)
+        self._load(marker)
+
+    def _load(self, marker):
+        """load a marker in the store
+
+        The marker do need to be written to disk"""
+        self._all.append(marker)
+        obs, rpls = marker[:2]
+        self.obsoleted.setdefault(obs, set()).add(marker)
+        for rp in rpls:
+            self.replacements.setdefault(rp, set()).add(marker)


More information about the Mercurial-devel mailing list