63 # Each record of these has info about one file. Hence multiple of them can |
61 # Each record of these has info about one file. Hence multiple of them can |
64 # exists |
62 # exists |
65 ##### |
63 ##### |
66 RECORD_MERGED = b'F' |
64 RECORD_MERGED = b'F' |
67 RECORD_CHANGEDELETE_CONFLICT = b'C' |
65 RECORD_CHANGEDELETE_CONFLICT = b'C' |
68 RECORD_MERGE_DRIVER_MERGE = b'D' |
|
69 # the path was dir on one side of merge and file on another |
66 # the path was dir on one side of merge and file on another |
70 RECORD_PATH_CONFLICT = b'P' |
67 RECORD_PATH_CONFLICT = b'P' |
71 |
68 |
72 ##### |
69 ##### |
73 # possible state which a merge entry can have. These are stored inside top-level |
70 # possible state which a merge entry can have. These are stored inside top-level |
75 ##### |
72 ##### |
76 MERGE_RECORD_UNRESOLVED = b'u' |
73 MERGE_RECORD_UNRESOLVED = b'u' |
77 MERGE_RECORD_RESOLVED = b'r' |
74 MERGE_RECORD_RESOLVED = b'r' |
78 MERGE_RECORD_UNRESOLVED_PATH = b'pu' |
75 MERGE_RECORD_UNRESOLVED_PATH = b'pu' |
79 MERGE_RECORD_RESOLVED_PATH = b'pr' |
76 MERGE_RECORD_RESOLVED_PATH = b'pr' |
80 MERGE_RECORD_DRIVER_RESOLVED = b'd' |
|
81 # represents that the file was automatically merged in favor |
77 # represents that the file was automatically merged in favor |
82 # of other version. This info is used on commit. |
78 # of other version. This info is used on commit. |
83 # This is now deprecated and commit related information is now |
79 # This is now deprecated and commit related information is now |
84 # stored in RECORD_FILE_VALUES |
80 # stored in RECORD_FILE_VALUES |
85 MERGE_RECORD_MERGED_OTHER = b'o' |
81 MERGE_RECORD_MERGED_OTHER = b'o' |
89 # exists |
85 # exists |
90 ##### |
86 ##### |
91 RECORD_OVERRIDE = b't' |
87 RECORD_OVERRIDE = b't' |
92 |
88 |
93 ##### |
89 ##### |
94 # possible states which a merge driver can have. These are stored inside a |
|
95 # RECORD_MERGE_DRIVER_STATE entry |
|
96 ##### |
|
97 MERGE_DRIVER_STATE_UNMARKED = b'u' |
|
98 MERGE_DRIVER_STATE_MARKED = b'm' |
|
99 MERGE_DRIVER_STATE_SUCCESS = b's' |
|
100 |
|
101 ##### |
|
102 # legacy records which are no longer used but kept to prevent breaking BC |
90 # legacy records which are no longer used but kept to prevent breaking BC |
103 ##### |
91 ##### |
104 # This record was release in 5.4 and usage was removed in 5.5 |
92 # This record was release in 5.4 and usage was removed in 5.5 |
105 LEGACY_RECORD_RESOLVED_OTHER = b'R' |
93 LEGACY_RECORD_RESOLVED_OTHER = b'R' |
|
94 # This record was release in 3.7 and usage was removed in 5.6 |
|
95 LEGACY_RECORD_DRIVER_RESOLVED = b'd' |
|
96 # This record was release in 3.7 and usage was removed in 5.6 |
|
97 LEGACY_MERGE_DRIVER_STATE = b'm' |
|
98 # This record was release in 3.7 and usage was removed in 5.6 |
|
99 LEGACY_MERGE_DRIVER_MERGE = b'D' |
106 |
100 |
107 |
101 |
108 ACTION_FORGET = b'f' |
102 ACTION_FORGET = b'f' |
109 ACTION_REMOVE = b'r' |
103 ACTION_REMOVE = b'r' |
110 ACTION_ADD = b'a' |
104 ACTION_ADD = b'a' |
145 |
139 |
146 L: the node of the "local" part of the merge (hexified version) |
140 L: the node of the "local" part of the merge (hexified version) |
147 O: the node of the "other" part of the merge (hexified version) |
141 O: the node of the "other" part of the merge (hexified version) |
148 F: a file to be merged entry |
142 F: a file to be merged entry |
149 C: a change/delete or delete/change conflict |
143 C: a change/delete or delete/change conflict |
150 D: a file that the external merge driver will merge internally |
|
151 (experimental) |
|
152 P: a path conflict (file vs directory) |
144 P: a path conflict (file vs directory) |
153 m: the external merge driver defined for this merge plus its run state |
|
154 (experimental) |
|
155 f: a (filename, dictionary) tuple of optional values for a given file |
145 f: a (filename, dictionary) tuple of optional values for a given file |
156 l: the labels for the parts of the merge. |
146 l: the labels for the parts of the merge. |
157 |
|
158 Merge driver run states (experimental): |
|
159 u: driver-resolved files unmarked -- needs to be run next time we're about |
|
160 to resolve or commit |
|
161 m: driver-resolved files marked -- only needs to be run before commit |
|
162 s: success/skipped -- does not need to be run any more |
|
163 |
147 |
164 Merge record states (stored in self._state, indexed by filename): |
148 Merge record states (stored in self._state, indexed by filename): |
165 u: unresolved conflict |
149 u: unresolved conflict |
166 r: resolved conflict |
150 r: resolved conflict |
167 pu: unresolved path conflict (file conflicts with directory) |
151 pu: unresolved path conflict (file conflicts with directory) |
168 pr: resolved path conflict |
152 pr: resolved path conflict |
169 d: driver-resolved conflict |
|
170 |
153 |
171 The resolve command transitions between 'u' and 'r' for conflicts and |
154 The resolve command transitions between 'u' and 'r' for conflicts and |
172 'pu' and 'pr' for path conflicts. |
155 'pu' and 'pr' for path conflicts. |
173 ''' |
156 ''' |
174 |
157 |
180 self._state = {} |
163 self._state = {} |
181 self._stateextras = collections.defaultdict(dict) |
164 self._stateextras = collections.defaultdict(dict) |
182 self._local = None |
165 self._local = None |
183 self._other = None |
166 self._other = None |
184 self._labels = None |
167 self._labels = None |
185 self._readmergedriver = None |
|
186 self._mdstate = MERGE_DRIVER_STATE_UNMARKED |
|
187 # contains a mapping of form: |
168 # contains a mapping of form: |
188 # {filename : (merge_return_value, action_to_be_performed} |
169 # {filename : (merge_return_value, action_to_be_performed} |
189 # these are results of re-running merge process |
170 # these are results of re-running merge process |
190 # this dict is used to perform actions on dirstate caused by re-running |
171 # this dict is used to perform actions on dirstate caused by re-running |
191 # the merge |
172 # the merge |
197 |
178 |
198 def start(self, node, other, labels=None): |
179 def start(self, node, other, labels=None): |
199 self._local = node |
180 self._local = node |
200 self._other = other |
181 self._other = other |
201 self._labels = labels |
182 self._labels = labels |
202 if self.mergedriver: |
|
203 self._mdstate = MERGE_DRIVER_STATE_SUCCESS |
|
204 |
|
205 @util.propertycache |
|
206 def mergedriver(self): |
|
207 # protect against the following: |
|
208 # - A configures a malicious merge driver in their hgrc, then |
|
209 # pauses the merge |
|
210 # - A edits their hgrc to remove references to the merge driver |
|
211 # - A gives a copy of their entire repo, including .hg, to B |
|
212 # - B inspects .hgrc and finds it to be clean |
|
213 # - B then continues the merge and the malicious merge driver |
|
214 # gets invoked |
|
215 configmergedriver = self._repo.ui.config( |
|
216 b'experimental', b'mergedriver' |
|
217 ) |
|
218 if ( |
|
219 self._readmergedriver is not None |
|
220 and self._readmergedriver != configmergedriver |
|
221 ): |
|
222 raise error.ConfigError( |
|
223 _(b"merge driver changed since merge started"), |
|
224 hint=_(b"revert merge driver change or abort merge"), |
|
225 ) |
|
226 |
|
227 return configmergedriver |
|
228 |
183 |
229 @util.propertycache |
184 @util.propertycache |
230 def local(self): |
185 def local(self): |
231 if self._local is None: |
186 if self._local is None: |
232 msg = b"local accessed but self._local isn't set" |
187 msg = b"local accessed but self._local isn't set" |
328 |
283 |
329 def mark(self, dfile, state): |
284 def mark(self, dfile, state): |
330 self._state[dfile][0] = state |
285 self._state[dfile][0] = state |
331 self._dirty = True |
286 self._dirty = True |
332 |
287 |
333 def mdstate(self): |
|
334 return self._mdstate |
|
335 |
|
336 def unresolved(self): |
288 def unresolved(self): |
337 """Obtain the paths of unresolved files.""" |
289 """Obtain the paths of unresolved files.""" |
338 |
290 |
339 for f, entry in pycompat.iteritems(self._state): |
291 for f, entry in pycompat.iteritems(self._state): |
340 if entry[0] in ( |
292 if entry[0] in ( |
341 MERGE_RECORD_UNRESOLVED, |
293 MERGE_RECORD_UNRESOLVED, |
342 MERGE_RECORD_UNRESOLVED_PATH, |
294 MERGE_RECORD_UNRESOLVED_PATH, |
343 ): |
295 ): |
344 yield f |
296 yield f |
345 |
297 |
346 def driverresolved(self): |
|
347 """Obtain the paths of driver-resolved files.""" |
|
348 |
|
349 for f, entry in self._state.items(): |
|
350 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED: |
|
351 yield f |
|
352 |
|
353 def extras(self, filename): |
298 def extras(self, filename): |
354 return self._stateextras[filename] |
299 return self._stateextras[filename] |
355 |
300 |
356 def _resolve(self, preresolve, dfile, wctx): |
301 def _resolve(self, preresolve, dfile, wctx): |
357 """rerun merge process for file path `dfile`. |
302 """rerun merge process for file path `dfile`. |
358 Returns whether the merge was completed and the return value of merge |
303 Returns whether the merge was completed and the return value of merge |
359 obtained from filemerge._filemerge(). |
304 obtained from filemerge._filemerge(). |
360 """ |
305 """ |
361 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED): |
306 if self[dfile] in ( |
|
307 MERGE_RECORD_RESOLVED, |
|
308 LEGACY_RECORD_DRIVER_RESOLVED, |
|
309 ): |
362 return True, 0 |
310 return True, 0 |
363 stateentry = self._state[dfile] |
311 stateentry = self._state[dfile] |
364 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry |
312 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry |
365 octx = self._repo[self._other] |
313 octx = self._repo[self._other] |
366 extras = self.extras(dfile) |
314 extras = self.extras(dfile) |
488 for f, (r, action) in pycompat.iteritems(self._results): |
436 for f, (r, action) in pycompat.iteritems(self._results): |
489 if action is not None: |
437 if action is not None: |
490 actions[action].append((f, None, b"merge result")) |
438 actions[action].append((f, None, b"merge result")) |
491 return actions |
439 return actions |
492 |
440 |
493 def queueremove(self, f): |
|
494 """queues a file to be removed from the dirstate |
|
495 |
|
496 Meant for use by custom merge drivers.""" |
|
497 self._results[f] = 0, ACTION_REMOVE |
|
498 |
|
499 def queueadd(self, f): |
|
500 """queues a file to be added to the dirstate |
|
501 |
|
502 Meant for use by custom merge drivers.""" |
|
503 self._results[f] = 0, ACTION_ADD |
|
504 |
|
505 def queueget(self, f): |
|
506 """queues a file to be marked modified in the dirstate |
|
507 |
|
508 Meant for use by custom merge drivers.""" |
|
509 self._results[f] = 0, ACTION_GET |
|
510 |
|
511 |
441 |
512 class mergestate(_mergestate_base): |
442 class mergestate(_mergestate_base): |
513 |
443 |
514 statepathv1 = b'merge/state' |
444 statepathv1 = b'merge/state' |
515 statepathv2 = b'merge/state2' |
445 statepathv2 = b'merge/state2' |
533 """Analyse each record content to restore a serialized state from disk |
463 """Analyse each record content to restore a serialized state from disk |
534 |
464 |
535 This function process "record" entry produced by the de-serialization |
465 This function process "record" entry produced by the de-serialization |
536 of on disk file. |
466 of on disk file. |
537 """ |
467 """ |
538 self._mdstate = MERGE_DRIVER_STATE_SUCCESS |
|
539 unsupported = set() |
468 unsupported = set() |
540 records = self._readrecords() |
469 records = self._readrecords() |
541 for rtype, record in records: |
470 for rtype, record in records: |
542 if rtype == RECORD_LOCAL: |
471 if rtype == RECORD_LOCAL: |
543 self._local = bin(record) |
472 self._local = bin(record) |
544 elif rtype == RECORD_OTHER: |
473 elif rtype == RECORD_OTHER: |
545 self._other = bin(record) |
474 self._other = bin(record) |
546 elif rtype == RECORD_MERGE_DRIVER_STATE: |
475 elif rtype == LEGACY_MERGE_DRIVER_STATE: |
547 bits = record.split(b'\0', 1) |
476 pass |
548 mdstate = bits[1] |
|
549 if len(mdstate) != 1 or mdstate not in ( |
|
550 MERGE_DRIVER_STATE_UNMARKED, |
|
551 MERGE_DRIVER_STATE_MARKED, |
|
552 MERGE_DRIVER_STATE_SUCCESS, |
|
553 ): |
|
554 # the merge driver should be idempotent, so just rerun it |
|
555 mdstate = MERGE_DRIVER_STATE_UNMARKED |
|
556 |
|
557 self._readmergedriver = bits[0] |
|
558 self._mdstate = mdstate |
|
559 elif rtype in ( |
477 elif rtype in ( |
560 RECORD_MERGED, |
478 RECORD_MERGED, |
561 RECORD_CHANGEDELETE_CONFLICT, |
479 RECORD_CHANGEDELETE_CONFLICT, |
562 RECORD_PATH_CONFLICT, |
480 RECORD_PATH_CONFLICT, |
563 RECORD_MERGE_DRIVER_MERGE, |
481 LEGACY_MERGE_DRIVER_MERGE, |
564 LEGACY_RECORD_RESOLVED_OTHER, |
482 LEGACY_RECORD_RESOLVED_OTHER, |
565 ): |
483 ): |
566 bits = record.split(b'\0') |
484 bits = record.split(b'\0') |
567 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated |
485 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated |
568 # and we now store related information in _stateextras, so |
486 # and we now store related information in _stateextras, so |
708 |
626 |
709 def _makerecords(self): |
627 def _makerecords(self): |
710 records = [] |
628 records = [] |
711 records.append((RECORD_LOCAL, hex(self._local))) |
629 records.append((RECORD_LOCAL, hex(self._local))) |
712 records.append((RECORD_OTHER, hex(self._other))) |
630 records.append((RECORD_OTHER, hex(self._other))) |
713 if self.mergedriver: |
|
714 records.append( |
|
715 ( |
|
716 RECORD_MERGE_DRIVER_STATE, |
|
717 b'\0'.join([self.mergedriver, self._mdstate]), |
|
718 ) |
|
719 ) |
|
720 # Write out state items. In all cases, the value of the state map entry |
631 # Write out state items. In all cases, the value of the state map entry |
721 # is written as the contents of the record. The record type depends on |
632 # is written as the contents of the record. The record type depends on |
722 # the type of state that is stored, and capital-letter records are used |
633 # the type of state that is stored, and capital-letter records are used |
723 # to prevent older versions of Mercurial that do not support the feature |
634 # to prevent older versions of Mercurial that do not support the feature |
724 # from loading them. |
635 # from loading them. |
725 for filename, v in pycompat.iteritems(self._state): |
636 for filename, v in pycompat.iteritems(self._state): |
726 if v[0] == MERGE_RECORD_DRIVER_RESOLVED: |
637 if v[0] in ( |
727 # Driver-resolved merge. These are stored in 'D' records. |
|
728 records.append( |
|
729 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v)) |
|
730 ) |
|
731 elif v[0] in ( |
|
732 MERGE_RECORD_UNRESOLVED_PATH, |
638 MERGE_RECORD_UNRESOLVED_PATH, |
733 MERGE_RECORD_RESOLVED_PATH, |
639 MERGE_RECORD_RESOLVED_PATH, |
734 ): |
640 ): |
735 # Path conflicts. These are stored in 'P' records. The current |
641 # Path conflicts. These are stored in 'P' records. The current |
736 # resolution state ('pu' or 'pr') is stored within the record. |
642 # resolution state ('pu' or 'pr') is stored within the record. |
812 self._backups[localkey] = fctx.data() |
718 self._backups[localkey] = fctx.data() |
813 |
719 |
814 def _restore_backup(self, fctx, localkey, flags): |
720 def _restore_backup(self, fctx, localkey, flags): |
815 fctx.write(self._backups[localkey], flags) |
721 fctx.write(self._backups[localkey], flags) |
816 |
722 |
817 @util.propertycache |
|
818 def mergedriver(self): |
|
819 configmergedriver = self._repo.ui.config( |
|
820 b'experimental', b'mergedriver' |
|
821 ) |
|
822 if configmergedriver: |
|
823 raise error.InMemoryMergeConflictsError( |
|
824 b"in-memory merge does not support mergedriver" |
|
825 ) |
|
826 return None |
|
827 |
|
828 |
723 |
829 def recordupdates(repo, actions, branchmerge, getfiledata): |
724 def recordupdates(repo, actions, branchmerge, getfiledata): |
830 """record merge actions to the dirstate""" |
725 """record merge actions to the dirstate""" |
831 # remove (must come first) |
726 # remove (must come first) |
832 for f, args, msg in actions.get(ACTION_REMOVE, []): |
727 for f, args, msg in actions.get(ACTION_REMOVE, []): |