Patchwork [3,of,7,V2,stable] merge: introduce new format for the state file

login
register
mail settings
Submitter Pierre-Yves David
Date Feb. 27, 2014, 11:25 p.m.
Message ID <c54808809e40a0e75fc2.1393543519@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/3792/
State Accepted
Commit 2b7d54e929b4dd10656e904ac50bbe31e92b58e8
Headers show

Comments

Pierre-Yves David - Feb. 27, 2014, 11:25 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1393382226 28800
#      Tue Feb 25 18:37:06 2014 -0800
# Branch stable
# Node ID c54808809e40a0e75fc2ad64ffd76c9e1b05760b
# Parent  fb9472f3f87499c67307304c76fa34735e581230
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.

Patch

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -3,19 +3,45 @@ 
 # 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.
 
+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
         self._read()
     def reset(self, node=None):
@@ -36,41 +62,81 @@  class mergestate(object):
             elif not rtype.islower():
                 raise util.Abort(_('unsupported merge state record:'
                                    % 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]))
                 else:
                     records.append(('F', l[:-1]))
             f.close()
         except IOError, err:
             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 = []
             records.append(("L", hex(self._local)))
             for d, v in self._state.iteritems():
                 records.append(("F", "\0".join([d] + v)))
             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'
         f.write(hex(self._local) + "\n")
         for rtype, data in irecords:
             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())
         self._state[fd] = ['u', hash, fcl.path(), fca.path(),
                            hex(fca.filenode()), fco.path(), fcl.flags()]