merge: introduce new format for the state file stable
authorPierre-Yves David <pierre-yves.david@fb.com>
Tue, 25 Feb 2014 18:37:06 -0800
branchstable
changeset 20590 2b7d54e929b4
parent 20589 31993cd23b11
child 20591 02c60e380fd0
merge: introduce new format for the state file This new format will allow us to address common bugs while doing special merge (graft, backout, rebaseā€¦) and record user choice during conflict resolution. The format is open so we can add more record for future usage. This file still store hexified version of node to help human willing to debug it by hand. The overhead or oversize are not expected be an issue. The old format is still used. It will be written to disk along side the newer format. And at parse time we detect if the data from old version of the mergestate are different from the one in the new version file. If its the same, both have most likely be written at the same time and you can trust the extra data from the new file. If it differs, the old file have been written by an older version of mercurial that did not knew about the new file. In that case we use the content of the old file.
mercurial/merge.py
--- a/mercurial/merge.py	Thu Feb 27 12:59:41 2014 -0800
+++ b/mercurial/merge.py	Tue Feb 25 18:37:06 2014 -0800
@@ -5,15 +5,41 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import struct
+
 from node import nullid, nullrev, hex, bin
 from i18n import _
 from mercurial import obsolete
 import error, util, filemerge, copies, subrepo, worker, dicthelpers
 import errno, os, shutil
 
+_pack = struct.pack
+_unpack = struct.unpack
+
 class mergestate(object):
-    '''track 3-way merge state of individual files'''
-    statepath = "merge/state"
+    '''track 3-way merge state of individual files
+
+    it is stored on disk when needed. Two file are used, one with an old
+    format, one with a new format. Both contains similar data, but the new
+    format can store new kind of field.
+
+    Current new format is a list of arbitrary record of the form:
+
+        [type][length][content]
+
+    Type is a single character, length is a 4 bytes integer, content is an
+    arbitrary suites of bytes of lenght `length`.
+
+    Type should be a letter. Capital letter are mandatory record, Mercurial
+    should abort if they are unknown. lower case record can be safely ignored.
+
+    Currently known record:
+
+    L: the node of the "local" part of the merge (hexified version)
+    F: a file to be merged entry
+    '''
+    statepathv1 = "merge/state"
+    statepathv2 = "merge/state2"
     def __init__(self, repo):
         self._repo = repo
         self._dirty = False
@@ -38,9 +64,19 @@
                                    % rtype))
         self._dirty = False
     def _readrecords(self):
+        v1records = self._readrecordsv1()
+        v2records = self._readrecordsv2()
+        allv2 = set(v2records)
+        for rev in v1records:
+            if rev not in allv2:
+                # v1 file is newer than v2 file, use it
+                return v1records
+        else:
+            return v2records
+    def _readrecordsv1(self):
         records = []
         try:
-            f = self._repo.opener(self.statepath)
+            f = self._repo.opener(self.statepathv1)
             for i, l in enumerate(f):
                 if i == 0:
                     records.append(('L', l[:-1]))
@@ -51,6 +87,26 @@
             if err.errno != errno.ENOENT:
                 raise
         return records
+    def _readrecordsv2(self):
+        records = []
+        try:
+            f = self._repo.opener(self.statepathv2)
+            data = f.read()
+            off = 0
+            end = len(data)
+            while off < end:
+                rtype = data[off]
+                off += 1
+                lenght = _unpack('>I', data[off:(off + 4)])[0]
+                off += 4
+                record = data[off:(off + lenght)]
+                off += lenght
+                records.append((rtype, record))
+            f.close()
+        except IOError, err:
+            if err.errno != errno.ENOENT:
+                raise
+        return records
     def commit(self):
         if self._dirty:
             records = []
@@ -60,7 +116,10 @@
             self._writerecords(records)
             self._dirty = False
     def _writerecords(self, records):
-        f = self._repo.opener(self.statepath, "w")
+        self._writerecordsv1(records)
+        self._writerecordsv2(records)
+    def _writerecordsv1(self, records):
+        f = self._repo.opener(self.statepathv1, "w")
         irecords = iter(records)
         lrecords = irecords.next()
         assert lrecords[0] == 'L'
@@ -69,6 +128,13 @@
             if rtype == "F":
                 f.write("%s\n" % data)
         f.close()
+    def _writerecordsv2(self, records):
+        f = self._repo.opener(self.statepathv2, "w")
+        for key, data in records:
+            assert len(key) == 1
+            format = ">sI%is" % len(data)
+            f.write(_pack(format, key, len(data), data))
+        f.close()
     def add(self, fcl, fco, fca, fd):
         hash = util.sha1(fcl.path()).hexdigest()
         self._repo.opener.write("merge/" + hash, fcl.data())