Mercurial > hg
changeset 20595:710c2755e66a
merge with stable
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Thu, 27 Feb 2014 18:57:03 -0600 |
parents | cb18fe3461b1 (current diff) ba619c50a355 (diff) |
children | 004a1744088d |
files | mercurial/commands.py |
diffstat | 3 files changed, 230 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/commands.py Thu Feb 13 09:18:16 2014 -0800 +++ b/mercurial/commands.py Thu Feb 27 18:57:03 2014 -0600 @@ -4954,7 +4954,6 @@ ms.mark(f, "u") else: wctx = repo[None] - mctx = wctx.parents()[-1] # backup pre-resolve (merge uses .orig for its own purposes) a = repo.wjoin(f) @@ -4963,7 +4962,7 @@ try: # resolve file ui.setconfig('ui', 'forcemerge', opts.get('tool', '')) - if ms.resolve(f, wctx, mctx): + if ms.resolve(f, wctx): ret = 1 finally: ui.setconfig('ui', 'forcemerge', '')
--- 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:
--- a/tests/test-backout.t Thu Feb 13 09:18:16 2014 -0800 +++ b/tests/test-backout.t Thu Feb 27 18:57:03 2014 -0600 @@ -408,3 +408,93 @@ update: (current) $ cd .. + + +Test usage of `hg resolve` in case of conflict +(issue4163) + + $ hg init issue4163 + $ cd issue4163 + $ touch foo + $ hg add foo + $ cat > foo << EOF + > one + > two + > three + > four + > five + > six + > seven + > height + > nine + > ten + > EOF + $ hg ci -m 'initial' + $ cat > foo << EOF + > one + > two + > THREE + > four + > five + > six + > seven + > height + > nine + > ten + > EOF + $ hg ci -m 'capital three' + $ cat > foo << EOF + > one + > two + > THREE + > four + > five + > six + > seven + > height + > nine + > TEN + > EOF + $ hg ci -m 'capital ten' + $ hg backout -r 'desc("capital three")' --tool internal:fail + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges + [1] + $ hg status + $ hg resolve -l # still unresolved + U foo + $ hg summary + parent: 2:b71750c4b0fd tip + capital ten + branch: default + commit: 1 unresolved (clean) + update: (current) + $ hg resolve --all --debug + picked tool 'internal:merge' for foo (binary False symlink False) + merging foo + my foo@b71750c4b0fd+ other foo@a30dd8addae3 ancestor foo@913609522437 + premerge successful + $ hg status + M foo + ? foo.orig + $ hg resolve -l + R foo + $ hg summary + parent: 2:b71750c4b0fd tip + capital ten + branch: default + commit: 1 modified, 1 unknown + update: (current) + $ cat foo + one + two + three + four + five + six + seven + height + nine + TEN + +