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