45 # used for compatibility for v1 |
45 # used for compatibility for v1 |
46 bits = data.split('\0') |
46 bits = data.split('\0') |
47 bits = bits[:-2] + bits[-1:] |
47 bits = bits[:-2] + bits[-1:] |
48 return '\0'.join(bits) |
48 return '\0'.join(bits) |
49 |
49 |
|
50 # Merge state record types. See ``mergestate`` docs for more. |
|
51 RECORD_LOCAL = b'L' |
|
52 RECORD_OTHER = b'O' |
|
53 RECORD_MERGED = b'F' |
|
54 RECORD_CHANGEDELETE_CONFLICT = b'C' |
|
55 RECORD_MERGE_DRIVER_MERGE = b'D' |
|
56 RECORD_PATH_CONFLICT = b'P' |
|
57 RECORD_MERGE_DRIVER_STATE = b'm' |
|
58 RECORD_FILE_VALUES = b'f' |
|
59 RECORD_LABELS = b'l' |
|
60 RECORD_OVERRIDE = b't' |
|
61 RECORD_UNSUPPORTED_MANDATORY = b'X' |
|
62 RECORD_UNSUPPORTED_ADVISORY = b'x' |
|
63 |
50 class mergestate(object): |
64 class mergestate(object): |
51 '''track 3-way merge state of individual files |
65 '''track 3-way merge state of individual files |
52 |
66 |
53 The merge state is stored on disk when needed. Two files are used: one with |
67 The merge state is stored on disk when needed. Two files are used: one with |
54 an old format (version 1), and one with a new format (version 2). Version 2 |
68 an old format (version 1), and one with a new format (version 2). Version 2 |
156 self._readmergedriver = None |
170 self._readmergedriver = None |
157 self._mdstate = 's' |
171 self._mdstate = 's' |
158 unsupported = set() |
172 unsupported = set() |
159 records = self._readrecords() |
173 records = self._readrecords() |
160 for rtype, record in records: |
174 for rtype, record in records: |
161 if rtype == 'L': |
175 if rtype == RECORD_LOCAL: |
162 self._local = bin(record) |
176 self._local = bin(record) |
163 elif rtype == 'O': |
177 elif rtype == RECORD_OTHER: |
164 self._other = bin(record) |
178 self._other = bin(record) |
165 elif rtype == 'm': |
179 elif rtype == RECORD_MERGE_DRIVER_STATE: |
166 bits = record.split('\0', 1) |
180 bits = record.split('\0', 1) |
167 mdstate = bits[1] |
181 mdstate = bits[1] |
168 if len(mdstate) != 1 or mdstate not in 'ums': |
182 if len(mdstate) != 1 or mdstate not in 'ums': |
169 # the merge driver should be idempotent, so just rerun it |
183 # the merge driver should be idempotent, so just rerun it |
170 mdstate = 'u' |
184 mdstate = 'u' |
171 |
185 |
172 self._readmergedriver = bits[0] |
186 self._readmergedriver = bits[0] |
173 self._mdstate = mdstate |
187 self._mdstate = mdstate |
174 elif rtype in 'FDCP': |
188 elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT, |
|
189 RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE): |
175 bits = record.split('\0') |
190 bits = record.split('\0') |
176 self._state[bits[0]] = bits[1:] |
191 self._state[bits[0]] = bits[1:] |
177 elif rtype == 'f': |
192 elif rtype == RECORD_FILE_VALUES: |
178 filename, rawextras = record.split('\0', 1) |
193 filename, rawextras = record.split('\0', 1) |
179 extraparts = rawextras.split('\0') |
194 extraparts = rawextras.split('\0') |
180 extras = {} |
195 extras = {} |
181 i = 0 |
196 i = 0 |
182 while i < len(extraparts): |
197 while i < len(extraparts): |
183 extras[extraparts[i]] = extraparts[i + 1] |
198 extras[extraparts[i]] = extraparts[i + 1] |
184 i += 2 |
199 i += 2 |
185 |
200 |
186 self._stateextras[filename] = extras |
201 self._stateextras[filename] = extras |
187 elif rtype == 'l': |
202 elif rtype == RECORD_LABELS: |
188 labels = record.split('\0', 2) |
203 labels = record.split('\0', 2) |
189 self._labels = [l for l in labels if len(l) > 0] |
204 self._labels = [l for l in labels if len(l) > 0] |
190 elif not rtype.islower(): |
205 elif not rtype.islower(): |
191 unsupported.add(rtype) |
206 unsupported.add(rtype) |
192 self._results = {} |
207 self._results = {} |
216 else: |
231 else: |
217 # v1 file is newer than v2 file, use it |
232 # v1 file is newer than v2 file, use it |
218 # we have to infer the "other" changeset of the merge |
233 # we have to infer the "other" changeset of the merge |
219 # we cannot do better than that with v1 of the format |
234 # we cannot do better than that with v1 of the format |
220 mctx = self._repo[None].parents()[-1] |
235 mctx = self._repo[None].parents()[-1] |
221 v1records.append(('O', mctx.hex())) |
236 v1records.append((RECORD_OTHER, mctx.hex())) |
222 # add place holder "other" file node information |
237 # add place holder "other" file node information |
223 # nobody is using it yet so we do no need to fetch the data |
238 # nobody is using it yet so we do no need to fetch the data |
224 # if mctx was wrong `mctx[bits[-2]]` may fails. |
239 # if mctx was wrong `mctx[bits[-2]]` may fails. |
225 for idx, r in enumerate(v1records): |
240 for idx, r in enumerate(v1records): |
226 if r[0] == 'F': |
241 if r[0] == RECORD_MERGED: |
227 bits = r[1].split('\0') |
242 bits = r[1].split('\0') |
228 bits.insert(-2, '') |
243 bits.insert(-2, '') |
229 v1records[idx] = (r[0], '\0'.join(bits)) |
244 v1records[idx] = (r[0], '\0'.join(bits)) |
230 return v1records |
245 return v1records |
231 |
246 |
232 def _v1v2match(self, v1records, v2records): |
247 def _v1v2match(self, v1records, v2records): |
233 oldv2 = set() # old format version of v2 record |
248 oldv2 = set() # old format version of v2 record |
234 for rec in v2records: |
249 for rec in v2records: |
235 if rec[0] == 'L': |
250 if rec[0] == RECORD_LOCAL: |
236 oldv2.add(rec) |
251 oldv2.add(rec) |
237 elif rec[0] == 'F': |
252 elif rec[0] == RECORD_MERGED: |
238 # drop the onode data (not contained in v1) |
253 # drop the onode data (not contained in v1) |
239 oldv2.add(('F', _droponode(rec[1]))) |
254 oldv2.add((RECORD_MERGED, _droponode(rec[1]))) |
240 for rec in v1records: |
255 for rec in v1records: |
241 if rec not in oldv2: |
256 if rec not in oldv2: |
242 return False |
257 return False |
243 else: |
258 else: |
244 return True |
259 return True |
357 self._writerecords(records) |
372 self._writerecords(records) |
358 self._dirty = False |
373 self._dirty = False |
359 |
374 |
360 def _makerecords(self): |
375 def _makerecords(self): |
361 records = [] |
376 records = [] |
362 records.append(('L', hex(self._local))) |
377 records.append((RECORD_LOCAL, hex(self._local))) |
363 records.append(('O', hex(self._other))) |
378 records.append((RECORD_OTHER, hex(self._other))) |
364 if self.mergedriver: |
379 if self.mergedriver: |
365 records.append(('m', '\0'.join([ |
380 records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([ |
366 self.mergedriver, self._mdstate]))) |
381 self.mergedriver, self._mdstate]))) |
367 # Write out state items. In all cases, the value of the state map entry |
382 # Write out state items. In all cases, the value of the state map entry |
368 # is written as the contents of the record. The record type depends on |
383 # is written as the contents of the record. The record type depends on |
369 # the type of state that is stored, and capital-letter records are used |
384 # the type of state that is stored, and capital-letter records are used |
370 # to prevent older versions of Mercurial that do not support the feature |
385 # to prevent older versions of Mercurial that do not support the feature |
371 # from loading them. |
386 # from loading them. |
372 for filename, v in self._state.iteritems(): |
387 for filename, v in self._state.iteritems(): |
373 if v[0] == 'd': |
388 if v[0] == 'd': |
374 # Driver-resolved merge. These are stored in 'D' records. |
389 # Driver-resolved merge. These are stored in 'D' records. |
375 records.append(('D', '\0'.join([filename] + v))) |
390 records.append((RECORD_MERGE_DRIVER_MERGE, |
|
391 '\0'.join([filename] + v))) |
376 elif v[0] in ('pu', 'pr'): |
392 elif v[0] in ('pu', 'pr'): |
377 # Path conflicts. These are stored in 'P' records. The current |
393 # Path conflicts. These are stored in 'P' records. The current |
378 # resolution state ('pu' or 'pr') is stored within the record. |
394 # resolution state ('pu' or 'pr') is stored within the record. |
379 records.append(('P', '\0'.join([filename] + v))) |
395 records.append((RECORD_PATH_CONFLICT, |
|
396 '\0'.join([filename] + v))) |
380 elif v[1] == nullhex or v[6] == nullhex: |
397 elif v[1] == nullhex or v[6] == nullhex: |
381 # Change/Delete or Delete/Change conflicts. These are stored in |
398 # Change/Delete or Delete/Change conflicts. These are stored in |
382 # 'C' records. v[1] is the local file, and is nullhex when the |
399 # 'C' records. v[1] is the local file, and is nullhex when the |
383 # file is deleted locally ('dc'). v[6] is the remote file, and |
400 # file is deleted locally ('dc'). v[6] is the remote file, and |
384 # is nullhex when the file is deleted remotely ('cd'). |
401 # is nullhex when the file is deleted remotely ('cd'). |
385 records.append(('C', '\0'.join([filename] + v))) |
402 records.append((RECORD_CHANGEDELETE_CONFLICT, |
|
403 '\0'.join([filename] + v))) |
386 else: |
404 else: |
387 # Normal files. These are stored in 'F' records. |
405 # Normal files. These are stored in 'F' records. |
388 records.append(('F', '\0'.join([filename] + v))) |
406 records.append((RECORD_MERGED, |
|
407 '\0'.join([filename] + v))) |
389 for filename, extras in sorted(self._stateextras.iteritems()): |
408 for filename, extras in sorted(self._stateextras.iteritems()): |
390 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in |
409 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in |
391 extras.iteritems()) |
410 extras.iteritems()) |
392 records.append(('f', '%s\0%s' % (filename, rawextras))) |
411 records.append((RECORD_FILE_VALUES, |
|
412 '%s\0%s' % (filename, rawextras))) |
393 if self._labels is not None: |
413 if self._labels is not None: |
394 labels = '\0'.join(self._labels) |
414 labels = '\0'.join(self._labels) |
395 records.append(('l', labels)) |
415 records.append((RECORD_LABELS, labels)) |
396 return records |
416 return records |
397 |
417 |
398 def _writerecords(self, records): |
418 def _writerecords(self, records): |
399 """Write current state on disk (both v1 and v2)""" |
419 """Write current state on disk (both v1 and v2)""" |
400 self._writerecordsv1(records) |
420 self._writerecordsv1(records) |
403 def _writerecordsv1(self, records): |
423 def _writerecordsv1(self, records): |
404 """Write current state on disk in a version 1 file""" |
424 """Write current state on disk in a version 1 file""" |
405 f = self._repo.vfs(self.statepathv1, 'wb') |
425 f = self._repo.vfs(self.statepathv1, 'wb') |
406 irecords = iter(records) |
426 irecords = iter(records) |
407 lrecords = next(irecords) |
427 lrecords = next(irecords) |
408 assert lrecords[0] == 'L' |
428 assert lrecords[0] == RECORD_LOCAL |
409 f.write(hex(self._local) + '\n') |
429 f.write(hex(self._local) + '\n') |
410 for rtype, data in irecords: |
430 for rtype, data in irecords: |
411 if rtype == 'F': |
431 if rtype == RECORD_MERGED: |
412 f.write('%s\n' % _droponode(data)) |
432 f.write('%s\n' % _droponode(data)) |
413 f.close() |
433 f.close() |
414 |
434 |
415 def _writerecordsv2(self, records): |
435 def _writerecordsv2(self, records): |
416 """Write current state on disk in a version 2 file |
436 """Write current state on disk in a version 2 file |
417 |
437 |
418 See the docstring for _readrecordsv2 for why we use 't'.""" |
438 See the docstring for _readrecordsv2 for why we use 't'.""" |
419 # these are the records that all version 2 clients can read |
439 # these are the records that all version 2 clients can read |
420 whitelist = 'LOF' |
440 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED) |
421 f = self._repo.vfs(self.statepathv2, 'wb') |
441 f = self._repo.vfs(self.statepathv2, 'wb') |
422 for key, data in records: |
442 for key, data in records: |
423 assert len(key) == 1 |
443 assert len(key) == 1 |
424 if key not in whitelist: |
444 if key not in allowlist: |
425 key, data = 't', '%s%s' % (key, data) |
445 key, data = RECORD_OVERRIDE, '%s%s' % (key, data) |
426 format = '>sI%is' % len(data) |
446 format = '>sI%is' % len(data) |
427 f.write(_pack(format, key, len(data), data)) |
447 f.write(_pack(format, key, len(data), data)) |
428 f.close() |
448 f.close() |
429 |
449 |
430 def add(self, fcl, fco, fca, fd): |
450 def add(self, fcl, fco, fca, fd): |