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 |