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