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