Mercurial > public > mercurial-scm > hg
comparison mercurial/merge.py @ 20590:2b7d54e929b4 stable
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.
author | Pierre-Yves David <pierre-yves.david@fb.com> |
---|---|
date | Tue, 25 Feb 2014 18:37:06 -0800 |
parents | 31993cd23b11 |
children | 02c60e380fd0 |
comparison
equal
deleted
inserted
replaced
20589:31993cd23b11 | 20590:2b7d54e929b4 |
---|---|
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> | 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> |
4 # | 4 # |
5 # This software may be used and distributed according to the terms of the | 5 # This software may be used and distributed according to the terms of the |
6 # GNU General Public License version 2 or any later version. | 6 # GNU General Public License version 2 or any later version. |
7 | 7 |
8 import struct | |
9 | |
8 from node import nullid, nullrev, hex, bin | 10 from node import nullid, nullrev, hex, bin |
9 from i18n import _ | 11 from i18n import _ |
10 from mercurial import obsolete | 12 from mercurial import obsolete |
11 import error, util, filemerge, copies, subrepo, worker, dicthelpers | 13 import error, util, filemerge, copies, subrepo, worker, dicthelpers |
12 import errno, os, shutil | 14 import errno, os, shutil |
13 | 15 |
16 _pack = struct.pack | |
17 _unpack = struct.unpack | |
18 | |
14 class mergestate(object): | 19 class mergestate(object): |
15 '''track 3-way merge state of individual files''' | 20 '''track 3-way merge state of individual files |
16 statepath = "merge/state" | 21 |
22 it is stored on disk when needed. Two file are used, one with an old | |
23 format, one with a new format. Both contains similar data, but the new | |
24 format can store new kind of field. | |
25 | |
26 Current new format is a list of arbitrary record of the form: | |
27 | |
28 [type][length][content] | |
29 | |
30 Type is a single character, length is a 4 bytes integer, content is an | |
31 arbitrary suites of bytes of lenght `length`. | |
32 | |
33 Type should be a letter. Capital letter are mandatory record, Mercurial | |
34 should abort if they are unknown. lower case record can be safely ignored. | |
35 | |
36 Currently known record: | |
37 | |
38 L: the node of the "local" part of the merge (hexified version) | |
39 F: a file to be merged entry | |
40 ''' | |
41 statepathv1 = "merge/state" | |
42 statepathv2 = "merge/state2" | |
17 def __init__(self, repo): | 43 def __init__(self, repo): |
18 self._repo = repo | 44 self._repo = repo |
19 self._dirty = False | 45 self._dirty = False |
20 self._read() | 46 self._read() |
21 def reset(self, node=None): | 47 def reset(self, node=None): |
36 elif not rtype.islower(): | 62 elif not rtype.islower(): |
37 raise util.Abort(_('unsupported merge state record:' | 63 raise util.Abort(_('unsupported merge state record:' |
38 % rtype)) | 64 % rtype)) |
39 self._dirty = False | 65 self._dirty = False |
40 def _readrecords(self): | 66 def _readrecords(self): |
67 v1records = self._readrecordsv1() | |
68 v2records = self._readrecordsv2() | |
69 allv2 = set(v2records) | |
70 for rev in v1records: | |
71 if rev not in allv2: | |
72 # v1 file is newer than v2 file, use it | |
73 return v1records | |
74 else: | |
75 return v2records | |
76 def _readrecordsv1(self): | |
41 records = [] | 77 records = [] |
42 try: | 78 try: |
43 f = self._repo.opener(self.statepath) | 79 f = self._repo.opener(self.statepathv1) |
44 for i, l in enumerate(f): | 80 for i, l in enumerate(f): |
45 if i == 0: | 81 if i == 0: |
46 records.append(('L', l[:-1])) | 82 records.append(('L', l[:-1])) |
47 else: | 83 else: |
48 records.append(('F', l[:-1])) | 84 records.append(('F', l[:-1])) |
85 f.close() | |
86 except IOError, err: | |
87 if err.errno != errno.ENOENT: | |
88 raise | |
89 return records | |
90 def _readrecordsv2(self): | |
91 records = [] | |
92 try: | |
93 f = self._repo.opener(self.statepathv2) | |
94 data = f.read() | |
95 off = 0 | |
96 end = len(data) | |
97 while off < end: | |
98 rtype = data[off] | |
99 off += 1 | |
100 lenght = _unpack('>I', data[off:(off + 4)])[0] | |
101 off += 4 | |
102 record = data[off:(off + lenght)] | |
103 off += lenght | |
104 records.append((rtype, record)) | |
49 f.close() | 105 f.close() |
50 except IOError, err: | 106 except IOError, err: |
51 if err.errno != errno.ENOENT: | 107 if err.errno != errno.ENOENT: |
52 raise | 108 raise |
53 return records | 109 return records |
58 for d, v in self._state.iteritems(): | 114 for d, v in self._state.iteritems(): |
59 records.append(("F", "\0".join([d] + v))) | 115 records.append(("F", "\0".join([d] + v))) |
60 self._writerecords(records) | 116 self._writerecords(records) |
61 self._dirty = False | 117 self._dirty = False |
62 def _writerecords(self, records): | 118 def _writerecords(self, records): |
63 f = self._repo.opener(self.statepath, "w") | 119 self._writerecordsv1(records) |
120 self._writerecordsv2(records) | |
121 def _writerecordsv1(self, records): | |
122 f = self._repo.opener(self.statepathv1, "w") | |
64 irecords = iter(records) | 123 irecords = iter(records) |
65 lrecords = irecords.next() | 124 lrecords = irecords.next() |
66 assert lrecords[0] == 'L' | 125 assert lrecords[0] == 'L' |
67 f.write(hex(self._local) + "\n") | 126 f.write(hex(self._local) + "\n") |
68 for rtype, data in irecords: | 127 for rtype, data in irecords: |
69 if rtype == "F": | 128 if rtype == "F": |
70 f.write("%s\n" % data) | 129 f.write("%s\n" % data) |
130 f.close() | |
131 def _writerecordsv2(self, records): | |
132 f = self._repo.opener(self.statepathv2, "w") | |
133 for key, data in records: | |
134 assert len(key) == 1 | |
135 format = ">sI%is" % len(data) | |
136 f.write(_pack(format, key, len(data), data)) | |
71 f.close() | 137 f.close() |
72 def add(self, fcl, fco, fca, fd): | 138 def add(self, fcl, fco, fca, fd): |
73 hash = util.sha1(fcl.path()).hexdigest() | 139 hash = util.sha1(fcl.path()).hexdigest() |
74 self._repo.opener.write("merge/" + hash, fcl.data()) | 140 self._repo.opener.write("merge/" + hash, fcl.data()) |
75 self._state[fd] = ['u', hash, fcl.path(), fca.path(), | 141 self._state[fd] = ['u', hash, fcl.path(), fca.path(), |