comparison mercurial/merge.py @ 44687:1b8fd4af3318

mergestate: store about files resolved in favour of other Committing a merge sometimes wrongly creates a new filenode where it can re-use an existing one. This happens because the commit code does it's own calculation and does not know what happened on merge. This starts storing information in mergestate about files which were automatically merged and the other/remote version of file was used. We need this information at commit to pick the filenode parent for the new commit. This issue was found by Pierre-Yves David and idea to store the relevant parts in mergestate is also suggested by him. Somethings which can be further investigated are: 1) refactoring of commit logic more to depend on this information 2) maybe a more generic solution? Differential Revision: https://phab.mercurial-scm.org/D8392
author Pulkit Goyal <7895pulkit@gmail.com>
date Thu, 09 Apr 2020 16:06:03 +0530
parents 9d2b2df2c2ba
children b7808443ed6a
comparison
equal deleted inserted replaced
44686:bca57ad9e630 44687:1b8fd4af3318
62 RECORD_FILE_VALUES = b'f' 62 RECORD_FILE_VALUES = b'f'
63 RECORD_LABELS = b'l' 63 RECORD_LABELS = b'l'
64 RECORD_OVERRIDE = b't' 64 RECORD_OVERRIDE = b't'
65 RECORD_UNSUPPORTED_MANDATORY = b'X' 65 RECORD_UNSUPPORTED_MANDATORY = b'X'
66 RECORD_UNSUPPORTED_ADVISORY = b'x' 66 RECORD_UNSUPPORTED_ADVISORY = b'x'
67 RECORD_RESOLVED_OTHER = b'R'
67 68
68 MERGE_DRIVER_STATE_UNMARKED = b'u' 69 MERGE_DRIVER_STATE_UNMARKED = b'u'
69 MERGE_DRIVER_STATE_MARKED = b'm' 70 MERGE_DRIVER_STATE_MARKED = b'm'
70 MERGE_DRIVER_STATE_SUCCESS = b's' 71 MERGE_DRIVER_STATE_SUCCESS = b's'
71 72
72 MERGE_RECORD_UNRESOLVED = b'u' 73 MERGE_RECORD_UNRESOLVED = b'u'
73 MERGE_RECORD_RESOLVED = b'r' 74 MERGE_RECORD_RESOLVED = b'r'
74 MERGE_RECORD_UNRESOLVED_PATH = b'pu' 75 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
75 MERGE_RECORD_RESOLVED_PATH = b'pr' 76 MERGE_RECORD_RESOLVED_PATH = b'pr'
76 MERGE_RECORD_DRIVER_RESOLVED = b'd' 77 MERGE_RECORD_DRIVER_RESOLVED = b'd'
78 # represents that the file was automatically merged in favor
79 # of other version. This info is used on commit.
80 MERGE_RECORD_MERGED_OTHER = b'o'
77 81
78 ACTION_FORGET = b'f' 82 ACTION_FORGET = b'f'
79 ACTION_REMOVE = b'r' 83 ACTION_REMOVE = b'r'
80 ACTION_ADD = b'a' 84 ACTION_ADD = b'a'
81 ACTION_GET = b'g' 85 ACTION_GET = b'g'
89 ACTION_LOCAL_DIR_RENAME_GET = b'dg' 93 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
90 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm' 94 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
91 ACTION_KEEP = b'k' 95 ACTION_KEEP = b'k'
92 ACTION_EXEC = b'e' 96 ACTION_EXEC = b'e'
93 ACTION_CREATED_MERGE = b'cm' 97 ACTION_CREATED_MERGE = b'cm'
98 # GET the other/remote side and store this info in mergestate
99 ACTION_GET_OTHER_AND_STORE = b'gs'
94 100
95 101
96 class mergestate(object): 102 class mergestate(object):
97 '''track 3-way merge state of individual files 103 '''track 3-way merge state of individual files
98 104
225 elif rtype in ( 231 elif rtype in (
226 RECORD_MERGED, 232 RECORD_MERGED,
227 RECORD_CHANGEDELETE_CONFLICT, 233 RECORD_CHANGEDELETE_CONFLICT,
228 RECORD_PATH_CONFLICT, 234 RECORD_PATH_CONFLICT,
229 RECORD_MERGE_DRIVER_MERGE, 235 RECORD_MERGE_DRIVER_MERGE,
236 RECORD_RESOLVED_OTHER,
230 ): 237 ):
231 bits = record.split(b'\0') 238 bits = record.split(b'\0')
232 self._state[bits[0]] = bits[1:] 239 self._state[bits[0]] = bits[1:]
233 elif rtype == RECORD_FILE_VALUES: 240 elif rtype == RECORD_FILE_VALUES:
234 filename, rawextras = record.split(b'\0', 1) 241 filename, rawextras = record.split(b'\0', 1)
451 # Path conflicts. These are stored in 'P' records. The current 458 # Path conflicts. These are stored in 'P' records. The current
452 # resolution state ('pu' or 'pr') is stored within the record. 459 # resolution state ('pu' or 'pr') is stored within the record.
453 records.append( 460 records.append(
454 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v)) 461 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
455 ) 462 )
463 elif v[0] == MERGE_RECORD_MERGED_OTHER:
464 records.append(
465 (RECORD_RESOLVED_OTHER, b'\0'.join([filename] + v))
466 )
456 elif v[1] == nullhex or v[6] == nullhex: 467 elif v[1] == nullhex or v[6] == nullhex:
457 # Change/Delete or Delete/Change conflicts. These are stored in 468 # Change/Delete or Delete/Change conflicts. These are stored in
458 # 'C' records. v[1] is the local file, and is nullhex when the 469 # 'C' records. v[1] is the local file, and is nullhex when the
459 # file is deleted locally ('dc'). v[6] is the remote file, and 470 # file is deleted locally ('dc'). v[6] is the remote file, and
460 # is nullhex when the file is deleted remotely ('cd'). 471 # is nullhex when the file is deleted remotely ('cd').
549 forigin: origin of the file ('l' or 'r' for local/remote) 560 forigin: origin of the file ('l' or 'r' for local/remote)
550 """ 561 """
551 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin] 562 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
552 self._dirty = True 563 self._dirty = True
553 564
565 def addmergedother(self, path):
566 self._state[path] = [MERGE_RECORD_MERGED_OTHER, nullhex, nullhex]
567 self._dirty = True
568
554 def __contains__(self, dfile): 569 def __contains__(self, dfile):
555 return dfile in self._state 570 return dfile in self._state
556 571
557 def __getitem__(self, dfile): 572 def __getitem__(self, dfile):
558 return self._state[dfile][0] 573 return self._state[dfile][0]
591 return self._stateextras.setdefault(filename, {}) 606 return self._stateextras.setdefault(filename, {})
592 607
593 def _resolve(self, preresolve, dfile, wctx): 608 def _resolve(self, preresolve, dfile, wctx):
594 """rerun merge process for file path `dfile`""" 609 """rerun merge process for file path `dfile`"""
595 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED): 610 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
611 return True, 0
612 if self._state[dfile][0] == MERGE_RECORD_MERGED_OTHER:
596 return True, 0 613 return True, 0
597 stateentry = self._state[dfile] 614 stateentry = self._state[dfile]
598 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry 615 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
599 octx = self._repo[self._other] 616 octx = self._repo[self._other]
600 extras = self.extras(dfile) 617 extras = self.extras(dfile)
1207 1224
1208 Raise an exception if the merge cannot be completed because the repo is 1225 Raise an exception if the merge cannot be completed because the repo is
1209 narrowed. 1226 narrowed.
1210 """ 1227 """
1211 nooptypes = {b'k'} # TODO: handle with nonconflicttypes 1228 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
1212 nonconflicttypes = set(b'a am c cm f g r e'.split()) 1229 nonconflicttypes = set(b'a am c cm f g gs r e'.split())
1213 # We mutate the items in the dict during iteration, so iterate 1230 # We mutate the items in the dict during iteration, so iterate
1214 # over a copy. 1231 # over a copy.
1215 for f, action in list(actions.items()): 1232 for f, action in list(actions.items()):
1216 if narrowmatch(f): 1233 if narrowmatch(f):
1217 pass 1234 pass
1346 (fl2,), 1363 (fl2,),
1347 b'update permissions', 1364 b'update permissions',
1348 ) 1365 )
1349 else: 1366 else:
1350 actions[f] = ( 1367 actions[f] = (
1351 ACTION_GET, 1368 ACTION_GET_OTHER_AND_STORE
1369 if branchmerge
1370 else ACTION_GET,
1352 (fl2, False), 1371 (fl2, False),
1353 b'remote is newer', 1372 b'remote is newer',
1354 ) 1373 )
1355 elif nol and n2 == a: # remote only changed 'x' 1374 elif nol and n2 == a: # remote only changed 'x'
1356 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions') 1375 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions')
1357 elif nol and n1 == a: # local only changed 'x' 1376 elif nol and n1 == a: # local only changed 'x'
1358 actions[f] = (ACTION_GET, (fl1, False), b'remote is newer') 1377 actions[f] = (
1378 ACTION_GET_OTHER_AND_STORE
1379 if branchmerge
1380 else ACTION_GET,
1381 (fl1, False),
1382 b'remote is newer',
1383 )
1359 else: # both changed something 1384 else: # both changed something
1360 actions[f] = ( 1385 actions[f] = (
1361 ACTION_MERGE, 1386 ACTION_MERGE,
1362 (f, f, f, False, pa.node()), 1387 (f, f, f, False, pa.node()),
1363 b'versions differ', 1388 b'versions differ',
1586 if renamedelete is None or len(renamedelete) < len(renamedelete1): 1611 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1587 renamedelete = renamedelete1 1612 renamedelete = renamedelete1
1588 1613
1589 for f, a in sorted(pycompat.iteritems(actions)): 1614 for f, a in sorted(pycompat.iteritems(actions)):
1590 m, args, msg = a 1615 m, args, msg = a
1616 if m == ACTION_GET_OTHER_AND_STORE:
1617 m = ACTION_GET
1591 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m)) 1618 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1592 if f in fbids: 1619 if f in fbids:
1593 d = fbids[f] 1620 d = fbids[f]
1594 if m in d: 1621 if m in d:
1595 d[m].append(a) 1622 d[m].append(a)
1811 ACTION_MERGE, 1838 ACTION_MERGE,
1812 ACTION_EXEC, 1839 ACTION_EXEC,
1813 ACTION_KEEP, 1840 ACTION_KEEP,
1814 ACTION_PATH_CONFLICT, 1841 ACTION_PATH_CONFLICT,
1815 ACTION_PATH_CONFLICT_RESOLVE, 1842 ACTION_PATH_CONFLICT_RESOLVE,
1843 ACTION_GET_OTHER_AND_STORE,
1816 ) 1844 )
1817 } 1845 }
1818 1846
1819 1847
1820 def applyupdates( 1848 def applyupdates(
1833 1861
1834 _prefetchfiles(repo, mctx, actions) 1862 _prefetchfiles(repo, mctx, actions)
1835 1863
1836 updated, merged, removed = 0, 0, 0 1864 updated, merged, removed = 0, 0, 0
1837 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels) 1865 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1866
1867 # add ACTION_GET_OTHER_AND_STORE to mergestate
1868 for e in actions[ACTION_GET_OTHER_AND_STORE]:
1869 ms.addmergedother(e[0])
1870
1838 moves = [] 1871 moves = []
1839 for m, l in actions.items(): 1872 for m, l in actions.items():
1840 l.sort() 1873 l.sort()
1841 1874
1842 # 'cd' and 'dc' actions are treated like other merge conflicts 1875 # 'cd' and 'dc' actions are treated like other merge conflicts
2413 ACTION_GET, 2446 ACTION_GET,
2414 ACTION_KEEP, 2447 ACTION_KEEP,
2415 ACTION_EXEC, 2448 ACTION_EXEC,
2416 ACTION_REMOVE, 2449 ACTION_REMOVE,
2417 ACTION_PATH_CONFLICT_RESOLVE, 2450 ACTION_PATH_CONFLICT_RESOLVE,
2451 ACTION_GET_OTHER_AND_STORE,
2418 ): 2452 ):
2419 msg = _(b"conflicting changes") 2453 msg = _(b"conflicting changes")
2420 hint = _(b"commit or update --clean to discard changes") 2454 hint = _(b"commit or update --clean to discard changes")
2421 raise error.Abort(msg, hint=hint) 2455 raise error.Abort(msg, hint=hint)
2422 2456
2475 for f, (m, args, msg) in pycompat.iteritems(actionbyfile): 2509 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2476 if m not in actions: 2510 if m not in actions:
2477 actions[m] = [] 2511 actions[m] = []
2478 actions[m].append((f, args, msg)) 2512 actions[m].append((f, args, msg))
2479 2513
2514 # ACTION_GET_OTHER_AND_STORE is a ACTION_GET + store in mergestate
2515 for e in actions[ACTION_GET_OTHER_AND_STORE]:
2516 actions[ACTION_GET].append(e)
2517
2480 if not util.fscasesensitive(repo.path): 2518 if not util.fscasesensitive(repo.path):
2481 # check collision between files only in p2 for clean update 2519 # check collision between files only in p2 for clean update
2482 if not branchmerge and ( 2520 if not branchmerge and (
2483 force or not wc.dirty(missing=True, branch=False) 2521 force or not wc.dirty(missing=True, branch=False)
2484 ): 2522 ):