Mercurial > public > mercurial-scm > hg
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 |