# HG changeset patch # User Pierre-Yves David # Date 2014-02-26 02:37:06 # Node ID 2b7d54e929b4dd10656e904ac50bbe31e92b58e8 # Parent 31993cd23b11334e1f323b2e4488ad038d4b4137 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. diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -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 @@ class mergestate(object): % 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 @@ class mergestate(object): 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 @@ class mergestate(object): 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 @@ class mergestate(object): 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())