--- a/mercurial/merge.py Thu Feb 13 09:18:16 2014 -0800
+++ b/mercurial/merge.py Thu Feb 27 18:57:03 2014 -0600
@@ -5,52 +5,172 @@
# 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
+
+def _droponode(data):
+ # used for compatibility for v1
+ bits = data.split("\0")
+ bits = bits[:-2] + bits[-1:]
+ return "\0".join(bits)
+
class mergestate(object):
- '''track 3-way merge state of individual files'''
+ '''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)
+ O: the node of the "other" 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):
+ def reset(self, node=None, other=None):
self._state = {}
if node:
self._local = node
+ self._other = other
shutil.rmtree(self._repo.join("merge"), True)
self._dirty = False
def _read(self):
self._state = {}
+ records = self._readrecords()
+ for rtype, record in records:
+ if rtype == 'L':
+ self._local = bin(record)
+ elif rtype == 'O':
+ self._other = bin(record)
+ elif rtype == "F":
+ bits = record.split("\0")
+ self._state[bits[0]] = bits[1:]
+ elif not rtype.islower():
+ raise util.Abort(_('unsupported merge state record:'
+ % rtype))
+ self._dirty = False
+ def _readrecords(self):
+ v1records = self._readrecordsv1()
+ v2records = self._readrecordsv2()
+ oldv2 = set() # old format version of v2 record
+ for rec in v2records:
+ if rec[0] == 'L':
+ oldv2.add(rec)
+ elif rec[0] == 'F':
+ # drop the onode data (not contained in v1)
+ oldv2.add(('F', _droponode(rec[1])))
+ for rec in v1records:
+ if rec not in oldv2:
+ # v1 file is newer than v2 file, use it
+ # we have to infer the "other" changeset of the merge
+ # we cannot do better than that with v1 of the format
+ mctx = self._repo[None].parents()[-1]
+ v1records.append(('O', mctx.hex()))
+ # add place holder "other" file node information
+ # nobody is using it yet so we do no need to fetch the data
+ # if mctx was wrong `mctx[bits[-2]]` may fails.
+ for idx, r in enumerate(v1records):
+ if r[0] == 'F':
+ bits = r[1].split("\0")
+ bits.insert(-2, '')
+ v1records[idx] = (r[0], "\0".join(bits))
+ return v1records
+ else:
+ return v2records
+ def _readrecordsv1(self):
+ records = []
try:
- f = self._repo.opener("merge/state")
+ f = self._repo.opener(self.statepathv1)
for i, l in enumerate(f):
if i == 0:
- self._local = bin(l[:-1])
+ records.append(('L', l[:-1]))
else:
- bits = l[:-1].split("\0")
- self._state[bits[0]] = bits[1:]
+ records.append(('F', l[:-1]))
f.close()
except IOError, err:
if err.errno != errno.ENOENT:
raise
- self._dirty = False
+ 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:
- f = self._repo.opener("merge/state", "w")
- f.write(hex(self._local) + "\n")
+ records = []
+ records.append(("L", hex(self._local)))
+ records.append(("O", hex(self._other)))
for d, v in self._state.iteritems():
- f.write("\0".join([d] + v) + "\n")
- f.close()
+ records.append(("F", "\0".join([d] + v)))
+ self._writerecords(records)
self._dirty = False
+ def _writerecords(self, records):
+ 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" % _droponode(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()]
+ self._state[fd] = ['u', hash, fcl.path(),
+ fca.path(), hex(fca.filenode()),
+ fco.path(), hex(fco.filenode()),
+ fcl.flags()]
self._dirty = True
def __contains__(self, dfile):
return dfile in self._state
@@ -66,10 +186,12 @@
def mark(self, dfile, state):
self._state[dfile][0] = state
self._dirty = True
- def resolve(self, dfile, wctx, octx):
+ def resolve(self, dfile, wctx):
if self[dfile] == 'r':
return 0
- state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
+ stateentry = self._state[dfile]
+ state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
+ octx = self._repo[self._other]
fcd = wctx[dfile]
fco = octx[ofile]
fca = self._repo.filectx(afile, fileid=anode)
@@ -440,7 +562,7 @@
updated, merged, removed, unresolved = 0, 0, 0, 0
ms = mergestate(repo)
- ms.reset(wctx.p1().node())
+ ms.reset(wctx.p1().node(), mctx.node())
moves = []
actions.sort(key=actionkey)
@@ -520,7 +642,7 @@
overwrite)
continue
audit(fd)
- r = ms.resolve(fd, wctx, mctx)
+ r = ms.resolve(fd, wctx)
if r is not None and r > 0:
unresolved += 1
else: