##// END OF EJS Templates
mergestate: make in-memory mergestate not clear on-disk mergestate on reset()...
Martin von Zweigbergk -
r46072:f2efc442 default
parent child Browse files
Show More
@@ -1,927 +1,927 b''
1 1 from __future__ import absolute_import
2 2
3 3 import collections
4 4 import errno
5 5 import shutil
6 6 import struct
7 7
8 8 from .i18n import _
9 9 from .node import (
10 10 bin,
11 11 hex,
12 12 nullhex,
13 13 nullid,
14 14 )
15 15 from . import (
16 16 error,
17 17 filemerge,
18 18 pycompat,
19 19 util,
20 20 )
21 21 from .utils import hashutil
22 22
23 23 _pack = struct.pack
24 24 _unpack = struct.unpack
25 25
26 26
27 27 def _droponode(data):
28 28 # used for compatibility for v1
29 29 bits = data.split(b'\0')
30 30 bits = bits[:-2] + bits[-1:]
31 31 return b'\0'.join(bits)
32 32
33 33
34 34 def _filectxorabsent(hexnode, ctx, f):
35 35 if hexnode == nullhex:
36 36 return filemerge.absentfilectx(ctx, f)
37 37 else:
38 38 return ctx[f]
39 39
40 40
41 41 # Merge state record types. See ``mergestate`` docs for more.
42 42
43 43 ####
44 44 # merge records which records metadata about a current merge
45 45 # exists only once in a mergestate
46 46 #####
47 47 RECORD_LOCAL = b'L'
48 48 RECORD_OTHER = b'O'
49 49 # record merge labels
50 50 RECORD_LABELS = b'l'
51 51 # store info about merge driver used and it's state
52 52 RECORD_MERGE_DRIVER_STATE = b'm'
53 53
54 54 #####
55 55 # record extra information about files, with one entry containing info about one
56 56 # file. Hence, multiple of them can exists
57 57 #####
58 58 RECORD_FILE_VALUES = b'f'
59 59
60 60 #####
61 61 # merge records which represents state of individual merges of files/folders
62 62 # These are top level records for each entry containing merge related info.
63 63 # Each record of these has info about one file. Hence multiple of them can
64 64 # exists
65 65 #####
66 66 RECORD_MERGED = b'F'
67 67 RECORD_CHANGEDELETE_CONFLICT = b'C'
68 68 RECORD_MERGE_DRIVER_MERGE = b'D'
69 69 # the path was dir on one side of merge and file on another
70 70 RECORD_PATH_CONFLICT = b'P'
71 71
72 72 #####
73 73 # possible state which a merge entry can have. These are stored inside top-level
74 74 # merge records mentioned just above.
75 75 #####
76 76 MERGE_RECORD_UNRESOLVED = b'u'
77 77 MERGE_RECORD_RESOLVED = b'r'
78 78 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
79 79 MERGE_RECORD_RESOLVED_PATH = b'pr'
80 80 MERGE_RECORD_DRIVER_RESOLVED = b'd'
81 81 # represents that the file was automatically merged in favor
82 82 # of other version. This info is used on commit.
83 83 # This is now deprecated and commit related information is now
84 84 # stored in RECORD_FILE_VALUES
85 85 MERGE_RECORD_MERGED_OTHER = b'o'
86 86
87 87 #####
88 88 # top level record which stores other unknown records. Multiple of these can
89 89 # exists
90 90 #####
91 91 RECORD_OVERRIDE = b't'
92 92
93 93 #####
94 94 # possible states which a merge driver can have. These are stored inside a
95 95 # RECORD_MERGE_DRIVER_STATE entry
96 96 #####
97 97 MERGE_DRIVER_STATE_UNMARKED = b'u'
98 98 MERGE_DRIVER_STATE_MARKED = b'm'
99 99 MERGE_DRIVER_STATE_SUCCESS = b's'
100 100
101 101 #####
102 102 # legacy records which are no longer used but kept to prevent breaking BC
103 103 #####
104 104 # This record was release in 5.4 and usage was removed in 5.5
105 105 LEGACY_RECORD_RESOLVED_OTHER = b'R'
106 106
107 107
108 108 ACTION_FORGET = b'f'
109 109 ACTION_REMOVE = b'r'
110 110 ACTION_ADD = b'a'
111 111 ACTION_GET = b'g'
112 112 ACTION_PATH_CONFLICT = b'p'
113 113 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
114 114 ACTION_ADD_MODIFIED = b'am'
115 115 ACTION_CREATED = b'c'
116 116 ACTION_DELETED_CHANGED = b'dc'
117 117 ACTION_CHANGED_DELETED = b'cd'
118 118 ACTION_MERGE = b'm'
119 119 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
120 120 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
121 121 ACTION_KEEP = b'k'
122 122 # the file was absent on local side before merge and we should
123 123 # keep it absent (absent means file not present, it can be a result
124 124 # of file deletion, rename etc.)
125 125 ACTION_KEEP_ABSENT = b'ka'
126 126 ACTION_EXEC = b'e'
127 127 ACTION_CREATED_MERGE = b'cm'
128 128
129 129
130 130 class _mergestate_base(object):
131 131 '''track 3-way merge state of individual files
132 132
133 133 The merge state is stored on disk when needed. Two files are used: one with
134 134 an old format (version 1), and one with a new format (version 2). Version 2
135 135 stores a superset of the data in version 1, including new kinds of records
136 136 in the future. For more about the new format, see the documentation for
137 137 `_readrecordsv2`.
138 138
139 139 Each record can contain arbitrary content, and has an associated type. This
140 140 `type` should be a letter. If `type` is uppercase, the record is mandatory:
141 141 versions of Mercurial that don't support it should abort. If `type` is
142 142 lowercase, the record can be safely ignored.
143 143
144 144 Currently known records:
145 145
146 146 L: the node of the "local" part of the merge (hexified version)
147 147 O: the node of the "other" part of the merge (hexified version)
148 148 F: a file to be merged entry
149 149 C: a change/delete or delete/change conflict
150 150 D: a file that the external merge driver will merge internally
151 151 (experimental)
152 152 P: a path conflict (file vs directory)
153 153 m: the external merge driver defined for this merge plus its run state
154 154 (experimental)
155 155 f: a (filename, dictionary) tuple of optional values for a given file
156 156 l: the labels for the parts of the merge.
157 157
158 158 Merge driver run states (experimental):
159 159 u: driver-resolved files unmarked -- needs to be run next time we're about
160 160 to resolve or commit
161 161 m: driver-resolved files marked -- only needs to be run before commit
162 162 s: success/skipped -- does not need to be run any more
163 163
164 164 Merge record states (stored in self._state, indexed by filename):
165 165 u: unresolved conflict
166 166 r: resolved conflict
167 167 pu: unresolved path conflict (file conflicts with directory)
168 168 pr: resolved path conflict
169 169 d: driver-resolved conflict
170 170
171 171 The resolve command transitions between 'u' and 'r' for conflicts and
172 172 'pu' and 'pr' for path conflicts.
173 173 '''
174 174
175 175 def __init__(self, repo):
176 176 """Initialize the merge state.
177 177
178 178 Do not use this directly! Instead call read() or clean()."""
179 179 self._repo = repo
180 180 self._state = {}
181 181 self._stateextras = collections.defaultdict(dict)
182 182 self._local = None
183 183 self._other = None
184 184 self._labels = None
185 185 self._readmergedriver = None
186 186 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
187 187 # contains a mapping of form:
188 188 # {filename : (merge_return_value, action_to_be_performed}
189 189 # these are results of re-running merge process
190 190 # this dict is used to perform actions on dirstate caused by re-running
191 191 # the merge
192 192 self._results = {}
193 193 self._dirty = False
194 194
195 195 def reset(self):
196 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
196 pass
197 197
198 198 def start(self, node, other, labels=None):
199 199 self._local = node
200 200 self._other = other
201 201 self._labels = labels
202 202 if self.mergedriver:
203 203 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
204 204
205 205 @util.propertycache
206 206 def mergedriver(self):
207 207 # protect against the following:
208 208 # - A configures a malicious merge driver in their hgrc, then
209 209 # pauses the merge
210 210 # - A edits their hgrc to remove references to the merge driver
211 211 # - A gives a copy of their entire repo, including .hg, to B
212 212 # - B inspects .hgrc and finds it to be clean
213 213 # - B then continues the merge and the malicious merge driver
214 214 # gets invoked
215 215 configmergedriver = self._repo.ui.config(
216 216 b'experimental', b'mergedriver'
217 217 )
218 218 if (
219 219 self._readmergedriver is not None
220 220 and self._readmergedriver != configmergedriver
221 221 ):
222 222 raise error.ConfigError(
223 223 _(b"merge driver changed since merge started"),
224 224 hint=_(b"revert merge driver change or abort merge"),
225 225 )
226 226
227 227 return configmergedriver
228 228
229 229 @util.propertycache
230 230 def local(self):
231 231 if self._local is None:
232 232 msg = b"local accessed but self._local isn't set"
233 233 raise error.ProgrammingError(msg)
234 234 return self._local
235 235
236 236 @util.propertycache
237 237 def localctx(self):
238 238 return self._repo[self.local]
239 239
240 240 @util.propertycache
241 241 def other(self):
242 242 if self._other is None:
243 243 msg = b"other accessed but self._other isn't set"
244 244 raise error.ProgrammingError(msg)
245 245 return self._other
246 246
247 247 @util.propertycache
248 248 def otherctx(self):
249 249 return self._repo[self.other]
250 250
251 251 def active(self):
252 252 """Whether mergestate is active.
253 253
254 254 Returns True if there appears to be mergestate. This is a rough proxy
255 255 for "is a merge in progress."
256 256 """
257 257 return bool(self._local) or bool(self._state)
258 258
259 259 def commit(self):
260 260 """Write current state on disk (if necessary)"""
261 261 if self._dirty:
262 262 records = self._makerecords()
263 263 self._writerecords(records)
264 264 self._dirty = False
265 265
266 266 def _makerecords(self):
267 267 records = []
268 268 records.append((RECORD_LOCAL, hex(self._local)))
269 269 records.append((RECORD_OTHER, hex(self._other)))
270 270 if self.mergedriver:
271 271 records.append(
272 272 (
273 273 RECORD_MERGE_DRIVER_STATE,
274 274 b'\0'.join([self.mergedriver, self._mdstate]),
275 275 )
276 276 )
277 277 # Write out state items. In all cases, the value of the state map entry
278 278 # is written as the contents of the record. The record type depends on
279 279 # the type of state that is stored, and capital-letter records are used
280 280 # to prevent older versions of Mercurial that do not support the feature
281 281 # from loading them.
282 282 for filename, v in pycompat.iteritems(self._state):
283 283 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
284 284 # Driver-resolved merge. These are stored in 'D' records.
285 285 records.append(
286 286 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
287 287 )
288 288 elif v[0] in (
289 289 MERGE_RECORD_UNRESOLVED_PATH,
290 290 MERGE_RECORD_RESOLVED_PATH,
291 291 ):
292 292 # Path conflicts. These are stored in 'P' records. The current
293 293 # resolution state ('pu' or 'pr') is stored within the record.
294 294 records.append(
295 295 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
296 296 )
297 297 elif v[1] == nullhex or v[6] == nullhex:
298 298 # Change/Delete or Delete/Change conflicts. These are stored in
299 299 # 'C' records. v[1] is the local file, and is nullhex when the
300 300 # file is deleted locally ('dc'). v[6] is the remote file, and
301 301 # is nullhex when the file is deleted remotely ('cd').
302 302 records.append(
303 303 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
304 304 )
305 305 else:
306 306 # Normal files. These are stored in 'F' records.
307 307 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
308 308 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
309 309 rawextras = b'\0'.join(
310 310 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
311 311 )
312 312 records.append(
313 313 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
314 314 )
315 315 if self._labels is not None:
316 316 labels = b'\0'.join(self._labels)
317 317 records.append((RECORD_LABELS, labels))
318 318 return records
319 319
320 320 @staticmethod
321 321 def getlocalkey(path):
322 322 """hash the path of a local file context for storage in the .hg/merge
323 323 directory."""
324 324
325 325 return hex(hashutil.sha1(path).digest())
326 326
327 327 def _make_backup(self, fctx, localkey):
328 328 raise NotImplementedError()
329 329
330 330 def _restore_backup(self, fctx, localkey, flags):
331 331 raise NotImplementedError()
332 332
333 333 def add(self, fcl, fco, fca, fd):
334 334 """add a new (potentially?) conflicting file the merge state
335 335 fcl: file context for local,
336 336 fco: file context for remote,
337 337 fca: file context for ancestors,
338 338 fd: file path of the resulting merge.
339 339
340 340 note: also write the local version to the `.hg/merge` directory.
341 341 """
342 342 if fcl.isabsent():
343 343 localkey = nullhex
344 344 else:
345 345 localkey = mergestate.getlocalkey(fcl.path())
346 346 self._make_backup(fcl, localkey)
347 347 self._state[fd] = [
348 348 MERGE_RECORD_UNRESOLVED,
349 349 localkey,
350 350 fcl.path(),
351 351 fca.path(),
352 352 hex(fca.filenode()),
353 353 fco.path(),
354 354 hex(fco.filenode()),
355 355 fcl.flags(),
356 356 ]
357 357 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
358 358 self._dirty = True
359 359
360 360 def addpathconflict(self, path, frename, forigin):
361 361 """add a new conflicting path to the merge state
362 362 path: the path that conflicts
363 363 frename: the filename the conflicting file was renamed to
364 364 forigin: origin of the file ('l' or 'r' for local/remote)
365 365 """
366 366 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
367 367 self._dirty = True
368 368
369 369 def addcommitinfo(self, path, data):
370 370 """ stores information which is required at commit
371 371 into _stateextras """
372 372 self._stateextras[path].update(data)
373 373 self._dirty = True
374 374
375 375 def __contains__(self, dfile):
376 376 return dfile in self._state
377 377
378 378 def __getitem__(self, dfile):
379 379 return self._state[dfile][0]
380 380
381 381 def __iter__(self):
382 382 return iter(sorted(self._state))
383 383
384 384 def files(self):
385 385 return self._state.keys()
386 386
387 387 def mark(self, dfile, state):
388 388 self._state[dfile][0] = state
389 389 self._dirty = True
390 390
391 391 def mdstate(self):
392 392 return self._mdstate
393 393
394 394 def unresolved(self):
395 395 """Obtain the paths of unresolved files."""
396 396
397 397 for f, entry in pycompat.iteritems(self._state):
398 398 if entry[0] in (
399 399 MERGE_RECORD_UNRESOLVED,
400 400 MERGE_RECORD_UNRESOLVED_PATH,
401 401 ):
402 402 yield f
403 403
404 404 def driverresolved(self):
405 405 """Obtain the paths of driver-resolved files."""
406 406
407 407 for f, entry in self._state.items():
408 408 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
409 409 yield f
410 410
411 411 def extras(self, filename):
412 412 return self._stateextras[filename]
413 413
414 414 def _resolve(self, preresolve, dfile, wctx):
415 415 """rerun merge process for file path `dfile`.
416 416 Returns whether the merge was completed and the return value of merge
417 417 obtained from filemerge._filemerge().
418 418 """
419 419 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
420 420 return True, 0
421 421 stateentry = self._state[dfile]
422 422 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
423 423 octx = self._repo[self._other]
424 424 extras = self.extras(dfile)
425 425 anccommitnode = extras.get(b'ancestorlinknode')
426 426 if anccommitnode:
427 427 actx = self._repo[anccommitnode]
428 428 else:
429 429 actx = None
430 430 fcd = _filectxorabsent(localkey, wctx, dfile)
431 431 fco = _filectxorabsent(onode, octx, ofile)
432 432 # TODO: move this to filectxorabsent
433 433 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
434 434 # "premerge" x flags
435 435 flo = fco.flags()
436 436 fla = fca.flags()
437 437 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
438 438 if fca.node() == nullid and flags != flo:
439 439 if preresolve:
440 440 self._repo.ui.warn(
441 441 _(
442 442 b'warning: cannot merge flags for %s '
443 443 b'without common ancestor - keeping local flags\n'
444 444 )
445 445 % afile
446 446 )
447 447 elif flags == fla:
448 448 flags = flo
449 449 if preresolve:
450 450 # restore local
451 451 if localkey != nullhex:
452 452 self._restore_backup(wctx[dfile], localkey, flags)
453 453 else:
454 454 wctx[dfile].remove(ignoremissing=True)
455 455 complete, merge_ret, deleted = filemerge.premerge(
456 456 self._repo,
457 457 wctx,
458 458 self._local,
459 459 lfile,
460 460 fcd,
461 461 fco,
462 462 fca,
463 463 labels=self._labels,
464 464 )
465 465 else:
466 466 complete, merge_ret, deleted = filemerge.filemerge(
467 467 self._repo,
468 468 wctx,
469 469 self._local,
470 470 lfile,
471 471 fcd,
472 472 fco,
473 473 fca,
474 474 labels=self._labels,
475 475 )
476 476 if merge_ret is None:
477 477 # If return value of merge is None, then there are no real conflict
478 478 del self._state[dfile]
479 479 self._stateextras.pop(dfile, None)
480 480 self._dirty = True
481 481 elif not merge_ret:
482 482 self.mark(dfile, MERGE_RECORD_RESOLVED)
483 483
484 484 if complete:
485 485 action = None
486 486 if deleted:
487 487 if fcd.isabsent():
488 488 # dc: local picked. Need to drop if present, which may
489 489 # happen on re-resolves.
490 490 action = ACTION_FORGET
491 491 else:
492 492 # cd: remote picked (or otherwise deleted)
493 493 action = ACTION_REMOVE
494 494 else:
495 495 if fcd.isabsent(): # dc: remote picked
496 496 action = ACTION_GET
497 497 elif fco.isabsent(): # cd: local picked
498 498 if dfile in self.localctx:
499 499 action = ACTION_ADD_MODIFIED
500 500 else:
501 501 action = ACTION_ADD
502 502 # else: regular merges (no action necessary)
503 503 self._results[dfile] = merge_ret, action
504 504
505 505 return complete, merge_ret
506 506
507 507 def preresolve(self, dfile, wctx):
508 508 """run premerge process for dfile
509 509
510 510 Returns whether the merge is complete, and the exit code."""
511 511 return self._resolve(True, dfile, wctx)
512 512
513 513 def resolve(self, dfile, wctx):
514 514 """run merge process (assuming premerge was run) for dfile
515 515
516 516 Returns the exit code of the merge."""
517 517 return self._resolve(False, dfile, wctx)[1]
518 518
519 519 def counts(self):
520 520 """return counts for updated, merged and removed files in this
521 521 session"""
522 522 updated, merged, removed = 0, 0, 0
523 523 for r, action in pycompat.itervalues(self._results):
524 524 if r is None:
525 525 updated += 1
526 526 elif r == 0:
527 527 if action == ACTION_REMOVE:
528 528 removed += 1
529 529 else:
530 530 merged += 1
531 531 return updated, merged, removed
532 532
533 533 def unresolvedcount(self):
534 534 """get unresolved count for this merge (persistent)"""
535 535 return len(list(self.unresolved()))
536 536
537 537 def actions(self):
538 538 """return lists of actions to perform on the dirstate"""
539 539 actions = {
540 540 ACTION_REMOVE: [],
541 541 ACTION_FORGET: [],
542 542 ACTION_ADD: [],
543 543 ACTION_ADD_MODIFIED: [],
544 544 ACTION_GET: [],
545 545 }
546 546 for f, (r, action) in pycompat.iteritems(self._results):
547 547 if action is not None:
548 548 actions[action].append((f, None, b"merge result"))
549 549 return actions
550 550
551 551 def queueremove(self, f):
552 552 """queues a file to be removed from the dirstate
553 553
554 554 Meant for use by custom merge drivers."""
555 555 self._results[f] = 0, ACTION_REMOVE
556 556
557 557 def queueadd(self, f):
558 558 """queues a file to be added to the dirstate
559 559
560 560 Meant for use by custom merge drivers."""
561 561 self._results[f] = 0, ACTION_ADD
562 562
563 563 def queueget(self, f):
564 564 """queues a file to be marked modified in the dirstate
565 565
566 566 Meant for use by custom merge drivers."""
567 567 self._results[f] = 0, ACTION_GET
568 568
569 569
570 570 class mergestate(_mergestate_base):
571 571
572 572 statepathv1 = b'merge/state'
573 573 statepathv2 = b'merge/state2'
574 574
575 575 @staticmethod
576 576 def clean(repo):
577 577 """Initialize a brand new merge state, removing any existing state on
578 578 disk."""
579 579 ms = mergestate(repo)
580 580 ms.reset()
581 581 return ms
582 582
583 583 @staticmethod
584 584 def read(repo):
585 585 """Initialize the merge state, reading it from disk."""
586 586 ms = mergestate(repo)
587 587 ms._read()
588 588 return ms
589 589
590 590 def _read(self):
591 591 """Analyse each record content to restore a serialized state from disk
592 592
593 593 This function process "record" entry produced by the de-serialization
594 594 of on disk file.
595 595 """
596 596 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
597 597 unsupported = set()
598 598 records = self._readrecords()
599 599 for rtype, record in records:
600 600 if rtype == RECORD_LOCAL:
601 601 self._local = bin(record)
602 602 elif rtype == RECORD_OTHER:
603 603 self._other = bin(record)
604 604 elif rtype == RECORD_MERGE_DRIVER_STATE:
605 605 bits = record.split(b'\0', 1)
606 606 mdstate = bits[1]
607 607 if len(mdstate) != 1 or mdstate not in (
608 608 MERGE_DRIVER_STATE_UNMARKED,
609 609 MERGE_DRIVER_STATE_MARKED,
610 610 MERGE_DRIVER_STATE_SUCCESS,
611 611 ):
612 612 # the merge driver should be idempotent, so just rerun it
613 613 mdstate = MERGE_DRIVER_STATE_UNMARKED
614 614
615 615 self._readmergedriver = bits[0]
616 616 self._mdstate = mdstate
617 617 elif rtype in (
618 618 RECORD_MERGED,
619 619 RECORD_CHANGEDELETE_CONFLICT,
620 620 RECORD_PATH_CONFLICT,
621 621 RECORD_MERGE_DRIVER_MERGE,
622 622 LEGACY_RECORD_RESOLVED_OTHER,
623 623 ):
624 624 bits = record.split(b'\0')
625 625 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated
626 626 # and we now store related information in _stateextras, so
627 627 # lets write to _stateextras directly
628 628 if bits[1] == MERGE_RECORD_MERGED_OTHER:
629 629 self._stateextras[bits[0]][b'filenode-source'] = b'other'
630 630 else:
631 631 self._state[bits[0]] = bits[1:]
632 632 elif rtype == RECORD_FILE_VALUES:
633 633 filename, rawextras = record.split(b'\0', 1)
634 634 extraparts = rawextras.split(b'\0')
635 635 extras = {}
636 636 i = 0
637 637 while i < len(extraparts):
638 638 extras[extraparts[i]] = extraparts[i + 1]
639 639 i += 2
640 640
641 641 self._stateextras[filename] = extras
642 642 elif rtype == RECORD_LABELS:
643 643 labels = record.split(b'\0', 2)
644 644 self._labels = [l for l in labels if len(l) > 0]
645 645 elif not rtype.islower():
646 646 unsupported.add(rtype)
647 647
648 648 if unsupported:
649 649 raise error.UnsupportedMergeRecords(unsupported)
650 650
651 651 def _readrecords(self):
652 652 """Read merge state from disk and return a list of record (TYPE, data)
653 653
654 654 We read data from both v1 and v2 files and decide which one to use.
655 655
656 656 V1 has been used by version prior to 2.9.1 and contains less data than
657 657 v2. We read both versions and check if no data in v2 contradicts
658 658 v1. If there is not contradiction we can safely assume that both v1
659 659 and v2 were written at the same time and use the extract data in v2. If
660 660 there is contradiction we ignore v2 content as we assume an old version
661 661 of Mercurial has overwritten the mergestate file and left an old v2
662 662 file around.
663 663
664 664 returns list of record [(TYPE, data), ...]"""
665 665 v1records = self._readrecordsv1()
666 666 v2records = self._readrecordsv2()
667 667 if self._v1v2match(v1records, v2records):
668 668 return v2records
669 669 else:
670 670 # v1 file is newer than v2 file, use it
671 671 # we have to infer the "other" changeset of the merge
672 672 # we cannot do better than that with v1 of the format
673 673 mctx = self._repo[None].parents()[-1]
674 674 v1records.append((RECORD_OTHER, mctx.hex()))
675 675 # add place holder "other" file node information
676 676 # nobody is using it yet so we do no need to fetch the data
677 677 # if mctx was wrong `mctx[bits[-2]]` may fails.
678 678 for idx, r in enumerate(v1records):
679 679 if r[0] == RECORD_MERGED:
680 680 bits = r[1].split(b'\0')
681 681 bits.insert(-2, b'')
682 682 v1records[idx] = (r[0], b'\0'.join(bits))
683 683 return v1records
684 684
685 685 def _v1v2match(self, v1records, v2records):
686 686 oldv2 = set() # old format version of v2 record
687 687 for rec in v2records:
688 688 if rec[0] == RECORD_LOCAL:
689 689 oldv2.add(rec)
690 690 elif rec[0] == RECORD_MERGED:
691 691 # drop the onode data (not contained in v1)
692 692 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
693 693 for rec in v1records:
694 694 if rec not in oldv2:
695 695 return False
696 696 else:
697 697 return True
698 698
699 699 def _readrecordsv1(self):
700 700 """read on disk merge state for version 1 file
701 701
702 702 returns list of record [(TYPE, data), ...]
703 703
704 704 Note: the "F" data from this file are one entry short
705 705 (no "other file node" entry)
706 706 """
707 707 records = []
708 708 try:
709 709 f = self._repo.vfs(self.statepathv1)
710 710 for i, l in enumerate(f):
711 711 if i == 0:
712 712 records.append((RECORD_LOCAL, l[:-1]))
713 713 else:
714 714 records.append((RECORD_MERGED, l[:-1]))
715 715 f.close()
716 716 except IOError as err:
717 717 if err.errno != errno.ENOENT:
718 718 raise
719 719 return records
720 720
721 721 def _readrecordsv2(self):
722 722 """read on disk merge state for version 2 file
723 723
724 724 This format is a list of arbitrary records of the form:
725 725
726 726 [type][length][content]
727 727
728 728 `type` is a single character, `length` is a 4 byte integer, and
729 729 `content` is an arbitrary byte sequence of length `length`.
730 730
731 731 Mercurial versions prior to 3.7 have a bug where if there are
732 732 unsupported mandatory merge records, attempting to clear out the merge
733 733 state with hg update --clean or similar aborts. The 't' record type
734 734 works around that by writing out what those versions treat as an
735 735 advisory record, but later versions interpret as special: the first
736 736 character is the 'real' record type and everything onwards is the data.
737 737
738 738 Returns list of records [(TYPE, data), ...]."""
739 739 records = []
740 740 try:
741 741 f = self._repo.vfs(self.statepathv2)
742 742 data = f.read()
743 743 off = 0
744 744 end = len(data)
745 745 while off < end:
746 746 rtype = data[off : off + 1]
747 747 off += 1
748 748 length = _unpack(b'>I', data[off : (off + 4)])[0]
749 749 off += 4
750 750 record = data[off : (off + length)]
751 751 off += length
752 752 if rtype == RECORD_OVERRIDE:
753 753 rtype, record = record[0:1], record[1:]
754 754 records.append((rtype, record))
755 755 f.close()
756 756 except IOError as err:
757 757 if err.errno != errno.ENOENT:
758 758 raise
759 759 return records
760 760
761 761 def _writerecords(self, records):
762 762 """Write current state on disk (both v1 and v2)"""
763 763 self._writerecordsv1(records)
764 764 self._writerecordsv2(records)
765 765
766 766 def _writerecordsv1(self, records):
767 767 """Write current state on disk in a version 1 file"""
768 768 f = self._repo.vfs(self.statepathv1, b'wb')
769 769 irecords = iter(records)
770 770 lrecords = next(irecords)
771 771 assert lrecords[0] == RECORD_LOCAL
772 772 f.write(hex(self._local) + b'\n')
773 773 for rtype, data in irecords:
774 774 if rtype == RECORD_MERGED:
775 775 f.write(b'%s\n' % _droponode(data))
776 776 f.close()
777 777
778 778 def _writerecordsv2(self, records):
779 779 """Write current state on disk in a version 2 file
780 780
781 781 See the docstring for _readrecordsv2 for why we use 't'."""
782 782 # these are the records that all version 2 clients can read
783 783 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
784 784 f = self._repo.vfs(self.statepathv2, b'wb')
785 785 for key, data in records:
786 786 assert len(key) == 1
787 787 if key not in allowlist:
788 788 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
789 789 format = b'>sI%is' % len(data)
790 790 f.write(_pack(format, key, len(data), data))
791 791 f.close()
792 792
793 793 def _make_backup(self, fctx, localkey):
794 794 self._repo.vfs.write(b'merge/' + localkey, fctx.data())
795 795
796 796 def _restore_backup(self, fctx, localkey, flags):
797 797 with self._repo.vfs(b'merge/' + localkey) as f:
798 798 fctx.write(f.read(), flags)
799 799
800 800 def reset(self):
801 801 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
802 802
803 803
804 804 class memmergestate(_mergestate_base):
805 805 def __init__(self, repo):
806 806 super(memmergestate, self).__init__(repo)
807 807 self._backups = {}
808 808
809 809 def _make_backup(self, fctx, localkey):
810 810 self._backups[localkey] = fctx.data()
811 811
812 812 def _restore_backup(self, fctx, localkey, flags):
813 813 fctx.write(self._backups[localkey], flags)
814 814
815 815 @util.propertycache
816 816 def mergedriver(self):
817 817 configmergedriver = self._repo.ui.config(
818 818 b'experimental', b'mergedriver'
819 819 )
820 820 if configmergedriver:
821 821 raise error.InMemoryMergeConflictsError(
822 822 b"in-memory merge does not support mergedriver"
823 823 )
824 824 return None
825 825
826 826 def commit(self):
827 827 pass
828 828
829 829
830 830 def recordupdates(repo, actions, branchmerge, getfiledata):
831 831 """record merge actions to the dirstate"""
832 832 # remove (must come first)
833 833 for f, args, msg in actions.get(ACTION_REMOVE, []):
834 834 if branchmerge:
835 835 repo.dirstate.remove(f)
836 836 else:
837 837 repo.dirstate.drop(f)
838 838
839 839 # forget (must come first)
840 840 for f, args, msg in actions.get(ACTION_FORGET, []):
841 841 repo.dirstate.drop(f)
842 842
843 843 # resolve path conflicts
844 844 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
845 845 (f0, origf0) = args
846 846 repo.dirstate.add(f)
847 847 repo.dirstate.copy(origf0, f)
848 848 if f0 == origf0:
849 849 repo.dirstate.remove(f0)
850 850 else:
851 851 repo.dirstate.drop(f0)
852 852
853 853 # re-add
854 854 for f, args, msg in actions.get(ACTION_ADD, []):
855 855 repo.dirstate.add(f)
856 856
857 857 # re-add/mark as modified
858 858 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
859 859 if branchmerge:
860 860 repo.dirstate.normallookup(f)
861 861 else:
862 862 repo.dirstate.add(f)
863 863
864 864 # exec change
865 865 for f, args, msg in actions.get(ACTION_EXEC, []):
866 866 repo.dirstate.normallookup(f)
867 867
868 868 # keep
869 869 for f, args, msg in actions.get(ACTION_KEEP, []):
870 870 pass
871 871
872 872 # keep deleted
873 873 for f, args, msg in actions.get(ACTION_KEEP_ABSENT, []):
874 874 pass
875 875
876 876 # get
877 877 for f, args, msg in actions.get(ACTION_GET, []):
878 878 if branchmerge:
879 879 repo.dirstate.otherparent(f)
880 880 else:
881 881 parentfiledata = getfiledata[f] if getfiledata else None
882 882 repo.dirstate.normal(f, parentfiledata=parentfiledata)
883 883
884 884 # merge
885 885 for f, args, msg in actions.get(ACTION_MERGE, []):
886 886 f1, f2, fa, move, anc = args
887 887 if branchmerge:
888 888 # We've done a branch merge, mark this file as merged
889 889 # so that we properly record the merger later
890 890 repo.dirstate.merge(f)
891 891 if f1 != f2: # copy/rename
892 892 if move:
893 893 repo.dirstate.remove(f1)
894 894 if f1 != f:
895 895 repo.dirstate.copy(f1, f)
896 896 else:
897 897 repo.dirstate.copy(f2, f)
898 898 else:
899 899 # We've update-merged a locally modified file, so
900 900 # we set the dirstate to emulate a normal checkout
901 901 # of that file some time in the past. Thus our
902 902 # merge will appear as a normal local file
903 903 # modification.
904 904 if f2 == f: # file not locally copied/moved
905 905 repo.dirstate.normallookup(f)
906 906 if move:
907 907 repo.dirstate.drop(f1)
908 908
909 909 # directory rename, move local
910 910 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
911 911 f0, flag = args
912 912 if branchmerge:
913 913 repo.dirstate.add(f)
914 914 repo.dirstate.remove(f0)
915 915 repo.dirstate.copy(f0, f)
916 916 else:
917 917 repo.dirstate.normal(f)
918 918 repo.dirstate.drop(f0)
919 919
920 920 # directory rename, get
921 921 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
922 922 f0, flag = args
923 923 if branchmerge:
924 924 repo.dirstate.add(f)
925 925 repo.dirstate.copy(f0, f)
926 926 else:
927 927 repo.dirstate.normal(f)
General Comments 0
You need to be logged in to leave comments. Login now