comparison mercurial/merge.py @ 28634:3ceac01bc29f

merge: save merge part labels for later reuse We permit the caller of merge operations to supply labels for the merge parts ("local", "other", and optionally "base"). These labels are used in conflict markers to reduce confusion; however, the labels were not persistent, so 'hg resolve' would lose the labels. Store the labels in the mergestate.
author Simon Farnsworth <simonfar@fb.com>
date Sat, 19 Mar 2016 18:37:10 -0700
parents 5408e532e50a
children bdc86e178255
comparison
equal deleted inserted replaced
28633:e35d7f131483 28634:3ceac01bc29f
66 m: the external merge driver defined for this merge plus its run state 66 m: the external merge driver defined for this merge plus its run state
67 (experimental) 67 (experimental)
68 f: a (filename, dictonary) tuple of optional values for a given file 68 f: a (filename, dictonary) tuple of optional values for a given file
69 X: unsupported mandatory record type (used in tests) 69 X: unsupported mandatory record type (used in tests)
70 x: unsupported advisory record type (used in tests) 70 x: unsupported advisory record type (used in tests)
71 l: the labels for the parts of the merge.
71 72
72 Merge driver run states (experimental): 73 Merge driver run states (experimental):
73 u: driver-resolved files unmarked -- needs to be run next time we're about 74 u: driver-resolved files unmarked -- needs to be run next time we're about
74 to resolve or commit 75 to resolve or commit
75 m: driver-resolved files marked -- only needs to be run before commit 76 m: driver-resolved files marked -- only needs to be run before commit
78 ''' 79 '''
79 statepathv1 = 'merge/state' 80 statepathv1 = 'merge/state'
80 statepathv2 = 'merge/state2' 81 statepathv2 = 'merge/state2'
81 82
82 @staticmethod 83 @staticmethod
83 def clean(repo, node=None, other=None): 84 def clean(repo, node=None, other=None, labels=None):
84 """Initialize a brand new merge state, removing any existing state on 85 """Initialize a brand new merge state, removing any existing state on
85 disk.""" 86 disk."""
86 ms = mergestate(repo) 87 ms = mergestate(repo)
87 ms.reset(node, other) 88 ms.reset(node, other, labels)
88 return ms 89 return ms
89 90
90 @staticmethod 91 @staticmethod
91 def read(repo): 92 def read(repo):
92 """Initialize the merge state, reading it from disk.""" 93 """Initialize the merge state, reading it from disk."""
98 """Initialize the merge state. 99 """Initialize the merge state.
99 100
100 Do not use this directly! Instead call read() or clean().""" 101 Do not use this directly! Instead call read() or clean()."""
101 self._repo = repo 102 self._repo = repo
102 self._dirty = False 103 self._dirty = False
103 104 self._labels = None
104 def reset(self, node=None, other=None): 105
106 def reset(self, node=None, other=None, labels=None):
105 self._state = {} 107 self._state = {}
106 self._stateextras = {} 108 self._stateextras = {}
107 self._local = None 109 self._local = None
108 self._other = None 110 self._other = None
111 self._labels = labels
109 for var in ('localctx', 'otherctx'): 112 for var in ('localctx', 'otherctx'):
110 if var in vars(self): 113 if var in vars(self):
111 delattr(self, var) 114 delattr(self, var)
112 if node: 115 if node:
113 self._local = node 116 self._local = node
163 while i < len(extraparts): 166 while i < len(extraparts):
164 extras[extraparts[i]] = extraparts[i + 1] 167 extras[extraparts[i]] = extraparts[i + 1]
165 i += 2 168 i += 2
166 169
167 self._stateextras[filename] = extras 170 self._stateextras[filename] = extras
171 elif rtype == 'l':
172 labels = record.split('\0', 2)
173 self._labels = [l for l in labels if len(l) > 0]
168 elif not rtype.islower(): 174 elif not rtype.islower():
169 unsupported.add(rtype) 175 unsupported.add(rtype)
170 self._results = {} 176 self._results = {}
171 self._dirty = False 177 self._dirty = False
172 178
351 records.append(('F', '\0'.join([d] + v))) 357 records.append(('F', '\0'.join([d] + v)))
352 for filename, extras in sorted(self._stateextras.iteritems()): 358 for filename, extras in sorted(self._stateextras.iteritems()):
353 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in 359 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
354 extras.iteritems()) 360 extras.iteritems())
355 records.append(('f', '%s\0%s' % (filename, rawextras))) 361 records.append(('f', '%s\0%s' % (filename, rawextras)))
362 if self._labels is not None:
363 labels = '\0'.join(self._labels)
364 records.append(('l', labels))
356 return records 365 return records
357 366
358 def _writerecords(self, records): 367 def _writerecords(self, records):
359 """Write current state on disk (both v1 and v2)""" 368 """Write current state on disk (both v1 and v2)"""
360 self._writerecordsv1(records) 369 self._writerecordsv1(records)
442 yield f 451 yield f
443 452
444 def extras(self, filename): 453 def extras(self, filename):
445 return self._stateextras.setdefault(filename, {}) 454 return self._stateextras.setdefault(filename, {})
446 455
447 def _resolve(self, preresolve, dfile, wctx, labels=None): 456 def _resolve(self, preresolve, dfile, wctx):
448 """rerun merge process for file path `dfile`""" 457 """rerun merge process for file path `dfile`"""
449 if self[dfile] in 'rd': 458 if self[dfile] in 'rd':
450 return True, 0 459 return True, 0
451 stateentry = self._state[dfile] 460 stateentry = self._state[dfile]
452 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry 461 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
479 f.close() 488 f.close()
480 else: 489 else:
481 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True) 490 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
482 complete, r, deleted = filemerge.premerge(self._repo, self._local, 491 complete, r, deleted = filemerge.premerge(self._repo, self._local,
483 lfile, fcd, fco, fca, 492 lfile, fcd, fco, fca,
484 labels=labels) 493 labels=self._labels)
485 else: 494 else:
486 complete, r, deleted = filemerge.filemerge(self._repo, self._local, 495 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
487 lfile, fcd, fco, fca, 496 lfile, fcd, fco, fca,
488 labels=labels) 497 labels=self._labels)
489 if r is None: 498 if r is None:
490 # no real conflict 499 # no real conflict
491 del self._state[dfile] 500 del self._state[dfile]
492 self._stateextras.pop(dfile, None) 501 self._stateextras.pop(dfile, None)
493 self._dirty = True 502 self._dirty = True
521 if hexnode == nullhex: 530 if hexnode == nullhex:
522 return filemerge.absentfilectx(ctx, f) 531 return filemerge.absentfilectx(ctx, f)
523 else: 532 else:
524 return ctx[f] 533 return ctx[f]
525 534
526 def preresolve(self, dfile, wctx, labels=None): 535 def preresolve(self, dfile, wctx):
527 """run premerge process for dfile 536 """run premerge process for dfile
528 537
529 Returns whether the merge is complete, and the exit code.""" 538 Returns whether the merge is complete, and the exit code."""
530 return self._resolve(True, dfile, wctx, labels=labels) 539 return self._resolve(True, dfile, wctx)
531 540
532 def resolve(self, dfile, wctx, labels=None): 541 def resolve(self, dfile, wctx):
533 """run merge process (assuming premerge was run) for dfile 542 """run merge process (assuming premerge was run) for dfile
534 543
535 Returns the exit code of the merge.""" 544 Returns the exit code of the merge."""
536 return self._resolve(False, dfile, wctx, labels=labels)[1] 545 return self._resolve(False, dfile, wctx)[1]
537 546
538 def counts(self): 547 def counts(self):
539 """return counts for updated, merged and removed files in this 548 """return counts for updated, merged and removed files in this
540 session""" 549 session"""
541 updated, merged, removed = 0, 0, 0 550 updated, merged, removed = 0, 0, 0
1092 Return a tuple of counts (updated, merged, removed, unresolved) that 1101 Return a tuple of counts (updated, merged, removed, unresolved) that
1093 describes how many files were affected by the update. 1102 describes how many files were affected by the update.
1094 """ 1103 """
1095 1104
1096 updated, merged, removed = 0, 0, 0 1105 updated, merged, removed = 0, 0, 0
1097 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node()) 1106 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1098 moves = [] 1107 moves = []
1099 for m, l in actions.items(): 1108 for m, l in actions.items():
1100 l.sort() 1109 l.sort()
1101 1110
1102 # 'cd' and 'dc' actions are treated like other merge conflicts 1111 # 'cd' and 'dc' actions are treated like other merge conflicts
1245 if f == '.hgsubstate': # subrepo states need updating 1254 if f == '.hgsubstate': # subrepo states need updating
1246 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), 1255 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1247 overwrite) 1256 overwrite)
1248 continue 1257 continue
1249 audit(f) 1258 audit(f)
1250 complete, r = ms.preresolve(f, wctx, labels=labels) 1259 complete, r = ms.preresolve(f, wctx)
1251 if not complete: 1260 if not complete:
1252 numupdates += 1 1261 numupdates += 1
1253 tocomplete.append((f, args, msg)) 1262 tocomplete.append((f, args, msg))
1254 1263
1255 # merge 1264 # merge
1256 for f, args, msg in tocomplete: 1265 for f, args, msg in tocomplete:
1257 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg)) 1266 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1258 z += 1 1267 z += 1
1259 progress(_updating, z, item=f, total=numupdates, unit=_files) 1268 progress(_updating, z, item=f, total=numupdates, unit=_files)
1260 ms.resolve(f, wctx, labels=labels) 1269 ms.resolve(f, wctx)
1261 1270
1262 ms.commit() 1271 ms.commit()
1263 1272
1264 unresolved = ms.unresolvedcount() 1273 unresolved = ms.unresolvedcount()
1265 1274