217 self._local = node |
199 self._local = node |
218 self._other = other |
200 self._other = other |
219 self._labels = labels |
201 self._labels = labels |
220 if self.mergedriver: |
202 if self.mergedriver: |
221 self._mdstate = MERGE_DRIVER_STATE_SUCCESS |
203 self._mdstate = MERGE_DRIVER_STATE_SUCCESS |
222 |
|
223 def _read(self): |
|
224 """Analyse each record content to restore a serialized state from disk |
|
225 |
|
226 This function process "record" entry produced by the de-serialization |
|
227 of on disk file. |
|
228 """ |
|
229 self._mdstate = MERGE_DRIVER_STATE_SUCCESS |
|
230 unsupported = set() |
|
231 records = self._readrecords() |
|
232 for rtype, record in records: |
|
233 if rtype == RECORD_LOCAL: |
|
234 self._local = bin(record) |
|
235 elif rtype == RECORD_OTHER: |
|
236 self._other = bin(record) |
|
237 elif rtype == RECORD_MERGE_DRIVER_STATE: |
|
238 bits = record.split(b'\0', 1) |
|
239 mdstate = bits[1] |
|
240 if len(mdstate) != 1 or mdstate not in ( |
|
241 MERGE_DRIVER_STATE_UNMARKED, |
|
242 MERGE_DRIVER_STATE_MARKED, |
|
243 MERGE_DRIVER_STATE_SUCCESS, |
|
244 ): |
|
245 # the merge driver should be idempotent, so just rerun it |
|
246 mdstate = MERGE_DRIVER_STATE_UNMARKED |
|
247 |
|
248 self._readmergedriver = bits[0] |
|
249 self._mdstate = mdstate |
|
250 elif rtype in ( |
|
251 RECORD_MERGED, |
|
252 RECORD_CHANGEDELETE_CONFLICT, |
|
253 RECORD_PATH_CONFLICT, |
|
254 RECORD_MERGE_DRIVER_MERGE, |
|
255 LEGACY_RECORD_RESOLVED_OTHER, |
|
256 ): |
|
257 bits = record.split(b'\0') |
|
258 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated |
|
259 # and we now store related information in _stateextras, so |
|
260 # lets write to _stateextras directly |
|
261 if bits[1] == MERGE_RECORD_MERGED_OTHER: |
|
262 self._stateextras[bits[0]][b'filenode-source'] = b'other' |
|
263 else: |
|
264 self._state[bits[0]] = bits[1:] |
|
265 elif rtype == RECORD_FILE_VALUES: |
|
266 filename, rawextras = record.split(b'\0', 1) |
|
267 extraparts = rawextras.split(b'\0') |
|
268 extras = {} |
|
269 i = 0 |
|
270 while i < len(extraparts): |
|
271 extras[extraparts[i]] = extraparts[i + 1] |
|
272 i += 2 |
|
273 |
|
274 self._stateextras[filename] = extras |
|
275 elif rtype == RECORD_LABELS: |
|
276 labels = record.split(b'\0', 2) |
|
277 self._labels = [l for l in labels if len(l) > 0] |
|
278 elif not rtype.islower(): |
|
279 unsupported.add(rtype) |
|
280 |
|
281 if unsupported: |
|
282 raise error.UnsupportedMergeRecords(unsupported) |
|
283 |
|
284 def _readrecords(self): |
|
285 """Read merge state from disk and return a list of record (TYPE, data) |
|
286 |
|
287 We read data from both v1 and v2 files and decide which one to use. |
|
288 |
|
289 V1 has been used by version prior to 2.9.1 and contains less data than |
|
290 v2. We read both versions and check if no data in v2 contradicts |
|
291 v1. If there is not contradiction we can safely assume that both v1 |
|
292 and v2 were written at the same time and use the extract data in v2. If |
|
293 there is contradiction we ignore v2 content as we assume an old version |
|
294 of Mercurial has overwritten the mergestate file and left an old v2 |
|
295 file around. |
|
296 |
|
297 returns list of record [(TYPE, data), ...]""" |
|
298 v1records = self._readrecordsv1() |
|
299 v2records = self._readrecordsv2() |
|
300 if self._v1v2match(v1records, v2records): |
|
301 return v2records |
|
302 else: |
|
303 # v1 file is newer than v2 file, use it |
|
304 # we have to infer the "other" changeset of the merge |
|
305 # we cannot do better than that with v1 of the format |
|
306 mctx = self._repo[None].parents()[-1] |
|
307 v1records.append((RECORD_OTHER, mctx.hex())) |
|
308 # add place holder "other" file node information |
|
309 # nobody is using it yet so we do no need to fetch the data |
|
310 # if mctx was wrong `mctx[bits[-2]]` may fails. |
|
311 for idx, r in enumerate(v1records): |
|
312 if r[0] == RECORD_MERGED: |
|
313 bits = r[1].split(b'\0') |
|
314 bits.insert(-2, b'') |
|
315 v1records[idx] = (r[0], b'\0'.join(bits)) |
|
316 return v1records |
|
317 |
|
318 def _v1v2match(self, v1records, v2records): |
|
319 oldv2 = set() # old format version of v2 record |
|
320 for rec in v2records: |
|
321 if rec[0] == RECORD_LOCAL: |
|
322 oldv2.add(rec) |
|
323 elif rec[0] == RECORD_MERGED: |
|
324 # drop the onode data (not contained in v1) |
|
325 oldv2.add((RECORD_MERGED, _droponode(rec[1]))) |
|
326 for rec in v1records: |
|
327 if rec not in oldv2: |
|
328 return False |
|
329 else: |
|
330 return True |
|
331 |
|
332 def _readrecordsv1(self): |
|
333 """read on disk merge state for version 1 file |
|
334 |
|
335 returns list of record [(TYPE, data), ...] |
|
336 |
|
337 Note: the "F" data from this file are one entry short |
|
338 (no "other file node" entry) |
|
339 """ |
|
340 records = [] |
|
341 try: |
|
342 f = self._repo.vfs(self.statepathv1) |
|
343 for i, l in enumerate(f): |
|
344 if i == 0: |
|
345 records.append((RECORD_LOCAL, l[:-1])) |
|
346 else: |
|
347 records.append((RECORD_MERGED, l[:-1])) |
|
348 f.close() |
|
349 except IOError as err: |
|
350 if err.errno != errno.ENOENT: |
|
351 raise |
|
352 return records |
|
353 |
|
354 def _readrecordsv2(self): |
|
355 """read on disk merge state for version 2 file |
|
356 |
|
357 This format is a list of arbitrary records of the form: |
|
358 |
|
359 [type][length][content] |
|
360 |
|
361 `type` is a single character, `length` is a 4 byte integer, and |
|
362 `content` is an arbitrary byte sequence of length `length`. |
|
363 |
|
364 Mercurial versions prior to 3.7 have a bug where if there are |
|
365 unsupported mandatory merge records, attempting to clear out the merge |
|
366 state with hg update --clean or similar aborts. The 't' record type |
|
367 works around that by writing out what those versions treat as an |
|
368 advisory record, but later versions interpret as special: the first |
|
369 character is the 'real' record type and everything onwards is the data. |
|
370 |
|
371 Returns list of records [(TYPE, data), ...].""" |
|
372 records = [] |
|
373 try: |
|
374 f = self._repo.vfs(self.statepathv2) |
|
375 data = f.read() |
|
376 off = 0 |
|
377 end = len(data) |
|
378 while off < end: |
|
379 rtype = data[off : off + 1] |
|
380 off += 1 |
|
381 length = _unpack(b'>I', data[off : (off + 4)])[0] |
|
382 off += 4 |
|
383 record = data[off : (off + length)] |
|
384 off += length |
|
385 if rtype == RECORD_OVERRIDE: |
|
386 rtype, record = record[0:1], record[1:] |
|
387 records.append((rtype, record)) |
|
388 f.close() |
|
389 except IOError as err: |
|
390 if err.errno != errno.ENOENT: |
|
391 raise |
|
392 return records |
|
393 |
204 |
394 @util.propertycache |
205 @util.propertycache |
395 def mergedriver(self): |
206 def mergedriver(self): |
396 # protect against the following: |
207 # protect against the following: |
397 # - A configures a malicious merge driver in their hgrc, then |
208 # - A configures a malicious merge driver in their hgrc, then |
504 if self._labels is not None: |
315 if self._labels is not None: |
505 labels = b'\0'.join(self._labels) |
316 labels = b'\0'.join(self._labels) |
506 records.append((RECORD_LABELS, labels)) |
317 records.append((RECORD_LABELS, labels)) |
507 return records |
318 return records |
508 |
319 |
509 def _writerecords(self, records): |
|
510 """Write current state on disk (both v1 and v2)""" |
|
511 self._writerecordsv1(records) |
|
512 self._writerecordsv2(records) |
|
513 |
|
514 def _writerecordsv1(self, records): |
|
515 """Write current state on disk in a version 1 file""" |
|
516 f = self._repo.vfs(self.statepathv1, b'wb') |
|
517 irecords = iter(records) |
|
518 lrecords = next(irecords) |
|
519 assert lrecords[0] == RECORD_LOCAL |
|
520 f.write(hex(self._local) + b'\n') |
|
521 for rtype, data in irecords: |
|
522 if rtype == RECORD_MERGED: |
|
523 f.write(b'%s\n' % _droponode(data)) |
|
524 f.close() |
|
525 |
|
526 def _writerecordsv2(self, records): |
|
527 """Write current state on disk in a version 2 file |
|
528 |
|
529 See the docstring for _readrecordsv2 for why we use 't'.""" |
|
530 # these are the records that all version 2 clients can read |
|
531 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED) |
|
532 f = self._repo.vfs(self.statepathv2, b'wb') |
|
533 for key, data in records: |
|
534 assert len(key) == 1 |
|
535 if key not in allowlist: |
|
536 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data) |
|
537 format = b'>sI%is' % len(data) |
|
538 f.write(_pack(format, key, len(data), data)) |
|
539 f.close() |
|
540 |
|
541 @staticmethod |
320 @staticmethod |
542 def getlocalkey(path): |
321 def getlocalkey(path): |
543 """hash the path of a local file context for storage in the .hg/merge |
322 """hash the path of a local file context for storage in the .hg/merge |
544 directory.""" |
323 directory.""" |
545 |
324 |
546 return hex(hashutil.sha1(path).digest()) |
325 return hex(hashutil.sha1(path).digest()) |
547 |
326 |
548 def _make_backup(self, fctx, localkey): |
327 def _make_backup(self, fctx, localkey): |
549 self._repo.vfs.write(b'merge/' + localkey, fctx.data()) |
328 raise NotImplementedError() |
550 |
329 |
551 def _restore_backup(self, fctx, localkey, flags): |
330 def _restore_backup(self, fctx, localkey, flags): |
552 with self._repo.vfs(b'merge/' + localkey) as f: |
331 raise NotImplementedError() |
553 fctx.write(f.read(), flags) |
|
554 |
332 |
555 def add(self, fcl, fco, fca, fd): |
333 def add(self, fcl, fco, fca, fd): |
556 """add a new (potentially?) conflicting file the merge state |
334 """add a new (potentially?) conflicting file the merge state |
557 fcl: file context for local, |
335 fcl: file context for local, |
558 fco: file context for remote, |
336 fco: file context for remote, |
787 |
565 |
788 Meant for use by custom merge drivers.""" |
566 Meant for use by custom merge drivers.""" |
789 self._results[f] = 0, ACTION_GET |
567 self._results[f] = 0, ACTION_GET |
790 |
568 |
791 |
569 |
|
570 class mergestate(_mergestate_base): |
|
571 |
|
572 statepathv1 = b'merge/state' |
|
573 statepathv2 = b'merge/state2' |
|
574 |
|
575 @staticmethod |
|
576 def clean(repo): |
|
577 """Initialize a brand new merge state, removing any existing state on |
|
578 disk.""" |
|
579 ms = mergestate(repo) |
|
580 ms.reset() |
|
581 return ms |
|
582 |
|
583 @staticmethod |
|
584 def read(repo): |
|
585 """Initialize the merge state, reading it from disk.""" |
|
586 ms = mergestate(repo) |
|
587 ms._read() |
|
588 return ms |
|
589 |
|
590 def _read(self): |
|
591 """Analyse each record content to restore a serialized state from disk |
|
592 |
|
593 This function process "record" entry produced by the de-serialization |
|
594 of on disk file. |
|
595 """ |
|
596 self._mdstate = MERGE_DRIVER_STATE_SUCCESS |
|
597 unsupported = set() |
|
598 records = self._readrecords() |
|
599 for rtype, record in records: |
|
600 if rtype == RECORD_LOCAL: |
|
601 self._local = bin(record) |
|
602 elif rtype == RECORD_OTHER: |
|
603 self._other = bin(record) |
|
604 elif rtype == RECORD_MERGE_DRIVER_STATE: |
|
605 bits = record.split(b'\0', 1) |
|
606 mdstate = bits[1] |
|
607 if len(mdstate) != 1 or mdstate not in ( |
|
608 MERGE_DRIVER_STATE_UNMARKED, |
|
609 MERGE_DRIVER_STATE_MARKED, |
|
610 MERGE_DRIVER_STATE_SUCCESS, |
|
611 ): |
|
612 # the merge driver should be idempotent, so just rerun it |
|
613 mdstate = MERGE_DRIVER_STATE_UNMARKED |
|
614 |
|
615 self._readmergedriver = bits[0] |
|
616 self._mdstate = mdstate |
|
617 elif rtype in ( |
|
618 RECORD_MERGED, |
|
619 RECORD_CHANGEDELETE_CONFLICT, |
|
620 RECORD_PATH_CONFLICT, |
|
621 RECORD_MERGE_DRIVER_MERGE, |
|
622 LEGACY_RECORD_RESOLVED_OTHER, |
|
623 ): |
|
624 bits = record.split(b'\0') |
|
625 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated |
|
626 # and we now store related information in _stateextras, so |
|
627 # lets write to _stateextras directly |
|
628 if bits[1] == MERGE_RECORD_MERGED_OTHER: |
|
629 self._stateextras[bits[0]][b'filenode-source'] = b'other' |
|
630 else: |
|
631 self._state[bits[0]] = bits[1:] |
|
632 elif rtype == RECORD_FILE_VALUES: |
|
633 filename, rawextras = record.split(b'\0', 1) |
|
634 extraparts = rawextras.split(b'\0') |
|
635 extras = {} |
|
636 i = 0 |
|
637 while i < len(extraparts): |
|
638 extras[extraparts[i]] = extraparts[i + 1] |
|
639 i += 2 |
|
640 |
|
641 self._stateextras[filename] = extras |
|
642 elif rtype == RECORD_LABELS: |
|
643 labels = record.split(b'\0', 2) |
|
644 self._labels = [l for l in labels if len(l) > 0] |
|
645 elif not rtype.islower(): |
|
646 unsupported.add(rtype) |
|
647 |
|
648 if unsupported: |
|
649 raise error.UnsupportedMergeRecords(unsupported) |
|
650 |
|
651 def _readrecords(self): |
|
652 """Read merge state from disk and return a list of record (TYPE, data) |
|
653 |
|
654 We read data from both v1 and v2 files and decide which one to use. |
|
655 |
|
656 V1 has been used by version prior to 2.9.1 and contains less data than |
|
657 v2. We read both versions and check if no data in v2 contradicts |
|
658 v1. If there is not contradiction we can safely assume that both v1 |
|
659 and v2 were written at the same time and use the extract data in v2. If |
|
660 there is contradiction we ignore v2 content as we assume an old version |
|
661 of Mercurial has overwritten the mergestate file and left an old v2 |
|
662 file around. |
|
663 |
|
664 returns list of record [(TYPE, data), ...]""" |
|
665 v1records = self._readrecordsv1() |
|
666 v2records = self._readrecordsv2() |
|
667 if self._v1v2match(v1records, v2records): |
|
668 return v2records |
|
669 else: |
|
670 # v1 file is newer than v2 file, use it |
|
671 # we have to infer the "other" changeset of the merge |
|
672 # we cannot do better than that with v1 of the format |
|
673 mctx = self._repo[None].parents()[-1] |
|
674 v1records.append((RECORD_OTHER, mctx.hex())) |
|
675 # add place holder "other" file node information |
|
676 # nobody is using it yet so we do no need to fetch the data |
|
677 # if mctx was wrong `mctx[bits[-2]]` may fails. |
|
678 for idx, r in enumerate(v1records): |
|
679 if r[0] == RECORD_MERGED: |
|
680 bits = r[1].split(b'\0') |
|
681 bits.insert(-2, b'') |
|
682 v1records[idx] = (r[0], b'\0'.join(bits)) |
|
683 return v1records |
|
684 |
|
685 def _v1v2match(self, v1records, v2records): |
|
686 oldv2 = set() # old format version of v2 record |
|
687 for rec in v2records: |
|
688 if rec[0] == RECORD_LOCAL: |
|
689 oldv2.add(rec) |
|
690 elif rec[0] == RECORD_MERGED: |
|
691 # drop the onode data (not contained in v1) |
|
692 oldv2.add((RECORD_MERGED, _droponode(rec[1]))) |
|
693 for rec in v1records: |
|
694 if rec not in oldv2: |
|
695 return False |
|
696 else: |
|
697 return True |
|
698 |
|
699 def _readrecordsv1(self): |
|
700 """read on disk merge state for version 1 file |
|
701 |
|
702 returns list of record [(TYPE, data), ...] |
|
703 |
|
704 Note: the "F" data from this file are one entry short |
|
705 (no "other file node" entry) |
|
706 """ |
|
707 records = [] |
|
708 try: |
|
709 f = self._repo.vfs(self.statepathv1) |
|
710 for i, l in enumerate(f): |
|
711 if i == 0: |
|
712 records.append((RECORD_LOCAL, l[:-1])) |
|
713 else: |
|
714 records.append((RECORD_MERGED, l[:-1])) |
|
715 f.close() |
|
716 except IOError as err: |
|
717 if err.errno != errno.ENOENT: |
|
718 raise |
|
719 return records |
|
720 |
|
721 def _readrecordsv2(self): |
|
722 """read on disk merge state for version 2 file |
|
723 |
|
724 This format is a list of arbitrary records of the form: |
|
725 |
|
726 [type][length][content] |
|
727 |
|
728 `type` is a single character, `length` is a 4 byte integer, and |
|
729 `content` is an arbitrary byte sequence of length `length`. |
|
730 |
|
731 Mercurial versions prior to 3.7 have a bug where if there are |
|
732 unsupported mandatory merge records, attempting to clear out the merge |
|
733 state with hg update --clean or similar aborts. The 't' record type |
|
734 works around that by writing out what those versions treat as an |
|
735 advisory record, but later versions interpret as special: the first |
|
736 character is the 'real' record type and everything onwards is the data. |
|
737 |
|
738 Returns list of records [(TYPE, data), ...].""" |
|
739 records = [] |
|
740 try: |
|
741 f = self._repo.vfs(self.statepathv2) |
|
742 data = f.read() |
|
743 off = 0 |
|
744 end = len(data) |
|
745 while off < end: |
|
746 rtype = data[off : off + 1] |
|
747 off += 1 |
|
748 length = _unpack(b'>I', data[off : (off + 4)])[0] |
|
749 off += 4 |
|
750 record = data[off : (off + length)] |
|
751 off += length |
|
752 if rtype == RECORD_OVERRIDE: |
|
753 rtype, record = record[0:1], record[1:] |
|
754 records.append((rtype, record)) |
|
755 f.close() |
|
756 except IOError as err: |
|
757 if err.errno != errno.ENOENT: |
|
758 raise |
|
759 return records |
|
760 |
|
761 def _writerecords(self, records): |
|
762 """Write current state on disk (both v1 and v2)""" |
|
763 self._writerecordsv1(records) |
|
764 self._writerecordsv2(records) |
|
765 |
|
766 def _writerecordsv1(self, records): |
|
767 """Write current state on disk in a version 1 file""" |
|
768 f = self._repo.vfs(self.statepathv1, b'wb') |
|
769 irecords = iter(records) |
|
770 lrecords = next(irecords) |
|
771 assert lrecords[0] == RECORD_LOCAL |
|
772 f.write(hex(self._local) + b'\n') |
|
773 for rtype, data in irecords: |
|
774 if rtype == RECORD_MERGED: |
|
775 f.write(b'%s\n' % _droponode(data)) |
|
776 f.close() |
|
777 |
|
778 def _writerecordsv2(self, records): |
|
779 """Write current state on disk in a version 2 file |
|
780 |
|
781 See the docstring for _readrecordsv2 for why we use 't'.""" |
|
782 # these are the records that all version 2 clients can read |
|
783 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED) |
|
784 f = self._repo.vfs(self.statepathv2, b'wb') |
|
785 for key, data in records: |
|
786 assert len(key) == 1 |
|
787 if key not in allowlist: |
|
788 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data) |
|
789 format = b'>sI%is' % len(data) |
|
790 f.write(_pack(format, key, len(data), data)) |
|
791 f.close() |
|
792 |
|
793 def _make_backup(self, fctx, localkey): |
|
794 self._repo.vfs.write(b'merge/' + localkey, fctx.data()) |
|
795 |
|
796 def _restore_backup(self, fctx, localkey, flags): |
|
797 with self._repo.vfs(b'merge/' + localkey) as f: |
|
798 fctx.write(f.read(), flags) |
|
799 |
|
800 def reset(self): |
|
801 shutil.rmtree(self._repo.vfs.join(b'merge'), True) |
|
802 |
|
803 |
792 def recordupdates(repo, actions, branchmerge, getfiledata): |
804 def recordupdates(repo, actions, branchmerge, getfiledata): |
793 """record merge actions to the dirstate""" |
805 """record merge actions to the dirstate""" |
794 # remove (must come first) |
806 # remove (must come first) |
795 for f, args, msg in actions.get(ACTION_REMOVE, []): |
807 for f, args, msg in actions.get(ACTION_REMOVE, []): |
796 if branchmerge: |
808 if branchmerge: |