comparison mercurial/merge.py @ 44856:b7808443ed6a

mergestate: split out merge state handling code from main merge module There's already some pretty reasonable encapsulation here, but I want to make the mergestate storage a property of the context so memctx instances can do a reasonable thing. This is the first step in a reshuffle to make that easier. Differential Revision: https://phab.mercurial-scm.org/D8550
author Augie Fackler <augie@google.com>
date Mon, 18 May 2020 14:59:59 -0400
parents 1b8fd4af3318
children 818b4f19ef23
comparison
equal deleted inserted replaced
44855:1d2d353e5c4a 44856:b7808443ed6a
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from __future__ import absolute_import 8 from __future__ import absolute_import
9 9
10 import errno 10 import errno
11 import shutil
12 import stat 11 import stat
13 import struct 12 import struct
14 13
15 from .i18n import _ 14 from .i18n import _
16 from .node import ( 15 from .node import (
17 addednodeid, 16 addednodeid,
18 bin,
19 hex,
20 modifiednodeid, 17 modifiednodeid,
21 nullhex,
22 nullid, 18 nullid,
23 nullrev, 19 nullrev,
24 ) 20 )
25 from .pycompat import delattr
26 from .thirdparty import attr 21 from .thirdparty import attr
27 from . import ( 22 from . import (
28 copies, 23 copies,
29 encoding, 24 encoding,
30 error, 25 error,
31 filemerge, 26 filemerge,
32 match as matchmod, 27 match as matchmod,
28 mergestate as mergestatemod,
33 obsutil, 29 obsutil,
34 pathutil, 30 pathutil,
35 pycompat, 31 pycompat,
36 scmutil, 32 scmutil,
37 subrepoutil, 33 subrepoutil,
38 util, 34 util,
39 worker, 35 worker,
40 ) 36 )
41 from .utils import hashutil
42 37
43 _pack = struct.pack 38 _pack = struct.pack
44 _unpack = struct.unpack 39 _unpack = struct.unpack
45
46
47 def _droponode(data):
48 # used for compatibility for v1
49 bits = data.split(b'\0')
50 bits = bits[:-2] + bits[-1:]
51 return b'\0'.join(bits)
52
53
54 # Merge state record types. See ``mergestate`` docs for more.
55 RECORD_LOCAL = b'L'
56 RECORD_OTHER = b'O'
57 RECORD_MERGED = b'F'
58 RECORD_CHANGEDELETE_CONFLICT = b'C'
59 RECORD_MERGE_DRIVER_MERGE = b'D'
60 RECORD_PATH_CONFLICT = b'P'
61 RECORD_MERGE_DRIVER_STATE = b'm'
62 RECORD_FILE_VALUES = b'f'
63 RECORD_LABELS = b'l'
64 RECORD_OVERRIDE = b't'
65 RECORD_UNSUPPORTED_MANDATORY = b'X'
66 RECORD_UNSUPPORTED_ADVISORY = b'x'
67 RECORD_RESOLVED_OTHER = b'R'
68
69 MERGE_DRIVER_STATE_UNMARKED = b'u'
70 MERGE_DRIVER_STATE_MARKED = b'm'
71 MERGE_DRIVER_STATE_SUCCESS = b's'
72
73 MERGE_RECORD_UNRESOLVED = b'u'
74 MERGE_RECORD_RESOLVED = b'r'
75 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
76 MERGE_RECORD_RESOLVED_PATH = b'pr'
77 MERGE_RECORD_DRIVER_RESOLVED = b'd'
78 # represents that the file was automatically merged in favor
79 # of other version. This info is used on commit.
80 MERGE_RECORD_MERGED_OTHER = b'o'
81
82 ACTION_FORGET = b'f'
83 ACTION_REMOVE = b'r'
84 ACTION_ADD = b'a'
85 ACTION_GET = b'g'
86 ACTION_PATH_CONFLICT = b'p'
87 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
88 ACTION_ADD_MODIFIED = b'am'
89 ACTION_CREATED = b'c'
90 ACTION_DELETED_CHANGED = b'dc'
91 ACTION_CHANGED_DELETED = b'cd'
92 ACTION_MERGE = b'm'
93 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
94 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
95 ACTION_KEEP = b'k'
96 ACTION_EXEC = b'e'
97 ACTION_CREATED_MERGE = b'cm'
98 # GET the other/remote side and store this info in mergestate
99 ACTION_GET_OTHER_AND_STORE = b'gs'
100
101
102 class mergestate(object):
103 '''track 3-way merge state of individual files
104
105 The merge state is stored on disk when needed. Two files are used: one with
106 an old format (version 1), and one with a new format (version 2). Version 2
107 stores a superset of the data in version 1, including new kinds of records
108 in the future. For more about the new format, see the documentation for
109 `_readrecordsv2`.
110
111 Each record can contain arbitrary content, and has an associated type. This
112 `type` should be a letter. If `type` is uppercase, the record is mandatory:
113 versions of Mercurial that don't support it should abort. If `type` is
114 lowercase, the record can be safely ignored.
115
116 Currently known records:
117
118 L: the node of the "local" part of the merge (hexified version)
119 O: the node of the "other" part of the merge (hexified version)
120 F: a file to be merged entry
121 C: a change/delete or delete/change conflict
122 D: a file that the external merge driver will merge internally
123 (experimental)
124 P: a path conflict (file vs directory)
125 m: the external merge driver defined for this merge plus its run state
126 (experimental)
127 f: a (filename, dictionary) tuple of optional values for a given file
128 X: unsupported mandatory record type (used in tests)
129 x: unsupported advisory record type (used in tests)
130 l: the labels for the parts of the merge.
131
132 Merge driver run states (experimental):
133 u: driver-resolved files unmarked -- needs to be run next time we're about
134 to resolve or commit
135 m: driver-resolved files marked -- only needs to be run before commit
136 s: success/skipped -- does not need to be run any more
137
138 Merge record states (stored in self._state, indexed by filename):
139 u: unresolved conflict
140 r: resolved conflict
141 pu: unresolved path conflict (file conflicts with directory)
142 pr: resolved path conflict
143 d: driver-resolved conflict
144
145 The resolve command transitions between 'u' and 'r' for conflicts and
146 'pu' and 'pr' for path conflicts.
147 '''
148
149 statepathv1 = b'merge/state'
150 statepathv2 = b'merge/state2'
151
152 @staticmethod
153 def clean(repo, node=None, other=None, labels=None):
154 """Initialize a brand new merge state, removing any existing state on
155 disk."""
156 ms = mergestate(repo)
157 ms.reset(node, other, labels)
158 return ms
159
160 @staticmethod
161 def read(repo):
162 """Initialize the merge state, reading it from disk."""
163 ms = mergestate(repo)
164 ms._read()
165 return ms
166
167 def __init__(self, repo):
168 """Initialize the merge state.
169
170 Do not use this directly! Instead call read() or clean()."""
171 self._repo = repo
172 self._dirty = False
173 self._labels = None
174
175 def reset(self, node=None, other=None, labels=None):
176 self._state = {}
177 self._stateextras = {}
178 self._local = None
179 self._other = None
180 self._labels = labels
181 for var in ('localctx', 'otherctx'):
182 if var in vars(self):
183 delattr(self, var)
184 if node:
185 self._local = node
186 self._other = other
187 self._readmergedriver = None
188 if self.mergedriver:
189 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
190 else:
191 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
192 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
193 self._results = {}
194 self._dirty = False
195
196 def _read(self):
197 """Analyse each record content to restore a serialized state from disk
198
199 This function process "record" entry produced by the de-serialization
200 of on disk file.
201 """
202 self._state = {}
203 self._stateextras = {}
204 self._local = None
205 self._other = None
206 for var in ('localctx', 'otherctx'):
207 if var in vars(self):
208 delattr(self, var)
209 self._readmergedriver = None
210 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
211 unsupported = set()
212 records = self._readrecords()
213 for rtype, record in records:
214 if rtype == RECORD_LOCAL:
215 self._local = bin(record)
216 elif rtype == RECORD_OTHER:
217 self._other = bin(record)
218 elif rtype == RECORD_MERGE_DRIVER_STATE:
219 bits = record.split(b'\0', 1)
220 mdstate = bits[1]
221 if len(mdstate) != 1 or mdstate not in (
222 MERGE_DRIVER_STATE_UNMARKED,
223 MERGE_DRIVER_STATE_MARKED,
224 MERGE_DRIVER_STATE_SUCCESS,
225 ):
226 # the merge driver should be idempotent, so just rerun it
227 mdstate = MERGE_DRIVER_STATE_UNMARKED
228
229 self._readmergedriver = bits[0]
230 self._mdstate = mdstate
231 elif rtype in (
232 RECORD_MERGED,
233 RECORD_CHANGEDELETE_CONFLICT,
234 RECORD_PATH_CONFLICT,
235 RECORD_MERGE_DRIVER_MERGE,
236 RECORD_RESOLVED_OTHER,
237 ):
238 bits = record.split(b'\0')
239 self._state[bits[0]] = bits[1:]
240 elif rtype == RECORD_FILE_VALUES:
241 filename, rawextras = record.split(b'\0', 1)
242 extraparts = rawextras.split(b'\0')
243 extras = {}
244 i = 0
245 while i < len(extraparts):
246 extras[extraparts[i]] = extraparts[i + 1]
247 i += 2
248
249 self._stateextras[filename] = extras
250 elif rtype == RECORD_LABELS:
251 labels = record.split(b'\0', 2)
252 self._labels = [l for l in labels if len(l) > 0]
253 elif not rtype.islower():
254 unsupported.add(rtype)
255 self._results = {}
256 self._dirty = False
257
258 if unsupported:
259 raise error.UnsupportedMergeRecords(unsupported)
260
261 def _readrecords(self):
262 """Read merge state from disk and return a list of record (TYPE, data)
263
264 We read data from both v1 and v2 files and decide which one to use.
265
266 V1 has been used by version prior to 2.9.1 and contains less data than
267 v2. We read both versions and check if no data in v2 contradicts
268 v1. If there is not contradiction we can safely assume that both v1
269 and v2 were written at the same time and use the extract data in v2. If
270 there is contradiction we ignore v2 content as we assume an old version
271 of Mercurial has overwritten the mergestate file and left an old v2
272 file around.
273
274 returns list of record [(TYPE, data), ...]"""
275 v1records = self._readrecordsv1()
276 v2records = self._readrecordsv2()
277 if self._v1v2match(v1records, v2records):
278 return v2records
279 else:
280 # v1 file is newer than v2 file, use it
281 # we have to infer the "other" changeset of the merge
282 # we cannot do better than that with v1 of the format
283 mctx = self._repo[None].parents()[-1]
284 v1records.append((RECORD_OTHER, mctx.hex()))
285 # add place holder "other" file node information
286 # nobody is using it yet so we do no need to fetch the data
287 # if mctx was wrong `mctx[bits[-2]]` may fails.
288 for idx, r in enumerate(v1records):
289 if r[0] == RECORD_MERGED:
290 bits = r[1].split(b'\0')
291 bits.insert(-2, b'')
292 v1records[idx] = (r[0], b'\0'.join(bits))
293 return v1records
294
295 def _v1v2match(self, v1records, v2records):
296 oldv2 = set() # old format version of v2 record
297 for rec in v2records:
298 if rec[0] == RECORD_LOCAL:
299 oldv2.add(rec)
300 elif rec[0] == RECORD_MERGED:
301 # drop the onode data (not contained in v1)
302 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
303 for rec in v1records:
304 if rec not in oldv2:
305 return False
306 else:
307 return True
308
309 def _readrecordsv1(self):
310 """read on disk merge state for version 1 file
311
312 returns list of record [(TYPE, data), ...]
313
314 Note: the "F" data from this file are one entry short
315 (no "other file node" entry)
316 """
317 records = []
318 try:
319 f = self._repo.vfs(self.statepathv1)
320 for i, l in enumerate(f):
321 if i == 0:
322 records.append((RECORD_LOCAL, l[:-1]))
323 else:
324 records.append((RECORD_MERGED, l[:-1]))
325 f.close()
326 except IOError as err:
327 if err.errno != errno.ENOENT:
328 raise
329 return records
330
331 def _readrecordsv2(self):
332 """read on disk merge state for version 2 file
333
334 This format is a list of arbitrary records of the form:
335
336 [type][length][content]
337
338 `type` is a single character, `length` is a 4 byte integer, and
339 `content` is an arbitrary byte sequence of length `length`.
340
341 Mercurial versions prior to 3.7 have a bug where if there are
342 unsupported mandatory merge records, attempting to clear out the merge
343 state with hg update --clean or similar aborts. The 't' record type
344 works around that by writing out what those versions treat as an
345 advisory record, but later versions interpret as special: the first
346 character is the 'real' record type and everything onwards is the data.
347
348 Returns list of records [(TYPE, data), ...]."""
349 records = []
350 try:
351 f = self._repo.vfs(self.statepathv2)
352 data = f.read()
353 off = 0
354 end = len(data)
355 while off < end:
356 rtype = data[off : off + 1]
357 off += 1
358 length = _unpack(b'>I', data[off : (off + 4)])[0]
359 off += 4
360 record = data[off : (off + length)]
361 off += length
362 if rtype == RECORD_OVERRIDE:
363 rtype, record = record[0:1], record[1:]
364 records.append((rtype, record))
365 f.close()
366 except IOError as err:
367 if err.errno != errno.ENOENT:
368 raise
369 return records
370
371 @util.propertycache
372 def mergedriver(self):
373 # protect against the following:
374 # - A configures a malicious merge driver in their hgrc, then
375 # pauses the merge
376 # - A edits their hgrc to remove references to the merge driver
377 # - A gives a copy of their entire repo, including .hg, to B
378 # - B inspects .hgrc and finds it to be clean
379 # - B then continues the merge and the malicious merge driver
380 # gets invoked
381 configmergedriver = self._repo.ui.config(
382 b'experimental', b'mergedriver'
383 )
384 if (
385 self._readmergedriver is not None
386 and self._readmergedriver != configmergedriver
387 ):
388 raise error.ConfigError(
389 _(b"merge driver changed since merge started"),
390 hint=_(b"revert merge driver change or abort merge"),
391 )
392
393 return configmergedriver
394
395 @util.propertycache
396 def local(self):
397 if self._local is None:
398 msg = b"local accessed but self._local isn't set"
399 raise error.ProgrammingError(msg)
400 return self._local
401
402 @util.propertycache
403 def localctx(self):
404 return self._repo[self.local]
405
406 @util.propertycache
407 def other(self):
408 if self._other is None:
409 msg = b"other accessed but self._other isn't set"
410 raise error.ProgrammingError(msg)
411 return self._other
412
413 @util.propertycache
414 def otherctx(self):
415 return self._repo[self.other]
416
417 def active(self):
418 """Whether mergestate is active.
419
420 Returns True if there appears to be mergestate. This is a rough proxy
421 for "is a merge in progress."
422 """
423 return bool(self._local) or bool(self._state)
424
425 def commit(self):
426 """Write current state on disk (if necessary)"""
427 if self._dirty:
428 records = self._makerecords()
429 self._writerecords(records)
430 self._dirty = False
431
432 def _makerecords(self):
433 records = []
434 records.append((RECORD_LOCAL, hex(self._local)))
435 records.append((RECORD_OTHER, hex(self._other)))
436 if self.mergedriver:
437 records.append(
438 (
439 RECORD_MERGE_DRIVER_STATE,
440 b'\0'.join([self.mergedriver, self._mdstate]),
441 )
442 )
443 # Write out state items. In all cases, the value of the state map entry
444 # is written as the contents of the record. The record type depends on
445 # the type of state that is stored, and capital-letter records are used
446 # to prevent older versions of Mercurial that do not support the feature
447 # from loading them.
448 for filename, v in pycompat.iteritems(self._state):
449 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
450 # Driver-resolved merge. These are stored in 'D' records.
451 records.append(
452 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
453 )
454 elif v[0] in (
455 MERGE_RECORD_UNRESOLVED_PATH,
456 MERGE_RECORD_RESOLVED_PATH,
457 ):
458 # Path conflicts. These are stored in 'P' records. The current
459 # resolution state ('pu' or 'pr') is stored within the record.
460 records.append(
461 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
462 )
463 elif v[0] == MERGE_RECORD_MERGED_OTHER:
464 records.append(
465 (RECORD_RESOLVED_OTHER, b'\0'.join([filename] + v))
466 )
467 elif v[1] == nullhex or v[6] == nullhex:
468 # Change/Delete or Delete/Change conflicts. These are stored in
469 # 'C' records. v[1] is the local file, and is nullhex when the
470 # file is deleted locally ('dc'). v[6] is the remote file, and
471 # is nullhex when the file is deleted remotely ('cd').
472 records.append(
473 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
474 )
475 else:
476 # Normal files. These are stored in 'F' records.
477 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
478 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
479 rawextras = b'\0'.join(
480 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
481 )
482 records.append(
483 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
484 )
485 if self._labels is not None:
486 labels = b'\0'.join(self._labels)
487 records.append((RECORD_LABELS, labels))
488 return records
489
490 def _writerecords(self, records):
491 """Write current state on disk (both v1 and v2)"""
492 self._writerecordsv1(records)
493 self._writerecordsv2(records)
494
495 def _writerecordsv1(self, records):
496 """Write current state on disk in a version 1 file"""
497 f = self._repo.vfs(self.statepathv1, b'wb')
498 irecords = iter(records)
499 lrecords = next(irecords)
500 assert lrecords[0] == RECORD_LOCAL
501 f.write(hex(self._local) + b'\n')
502 for rtype, data in irecords:
503 if rtype == RECORD_MERGED:
504 f.write(b'%s\n' % _droponode(data))
505 f.close()
506
507 def _writerecordsv2(self, records):
508 """Write current state on disk in a version 2 file
509
510 See the docstring for _readrecordsv2 for why we use 't'."""
511 # these are the records that all version 2 clients can read
512 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
513 f = self._repo.vfs(self.statepathv2, b'wb')
514 for key, data in records:
515 assert len(key) == 1
516 if key not in allowlist:
517 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
518 format = b'>sI%is' % len(data)
519 f.write(_pack(format, key, len(data), data))
520 f.close()
521
522 @staticmethod
523 def getlocalkey(path):
524 """hash the path of a local file context for storage in the .hg/merge
525 directory."""
526
527 return hex(hashutil.sha1(path).digest())
528
529 def add(self, fcl, fco, fca, fd):
530 """add a new (potentially?) conflicting file the merge state
531 fcl: file context for local,
532 fco: file context for remote,
533 fca: file context for ancestors,
534 fd: file path of the resulting merge.
535
536 note: also write the local version to the `.hg/merge` directory.
537 """
538 if fcl.isabsent():
539 localkey = nullhex
540 else:
541 localkey = mergestate.getlocalkey(fcl.path())
542 self._repo.vfs.write(b'merge/' + localkey, fcl.data())
543 self._state[fd] = [
544 MERGE_RECORD_UNRESOLVED,
545 localkey,
546 fcl.path(),
547 fca.path(),
548 hex(fca.filenode()),
549 fco.path(),
550 hex(fco.filenode()),
551 fcl.flags(),
552 ]
553 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
554 self._dirty = True
555
556 def addpath(self, path, frename, forigin):
557 """add a new conflicting path to the merge state
558 path: the path that conflicts
559 frename: the filename the conflicting file was renamed to
560 forigin: origin of the file ('l' or 'r' for local/remote)
561 """
562 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
563 self._dirty = True
564
565 def addmergedother(self, path):
566 self._state[path] = [MERGE_RECORD_MERGED_OTHER, nullhex, nullhex]
567 self._dirty = True
568
569 def __contains__(self, dfile):
570 return dfile in self._state
571
572 def __getitem__(self, dfile):
573 return self._state[dfile][0]
574
575 def __iter__(self):
576 return iter(sorted(self._state))
577
578 def files(self):
579 return self._state.keys()
580
581 def mark(self, dfile, state):
582 self._state[dfile][0] = state
583 self._dirty = True
584
585 def mdstate(self):
586 return self._mdstate
587
588 def unresolved(self):
589 """Obtain the paths of unresolved files."""
590
591 for f, entry in pycompat.iteritems(self._state):
592 if entry[0] in (
593 MERGE_RECORD_UNRESOLVED,
594 MERGE_RECORD_UNRESOLVED_PATH,
595 ):
596 yield f
597
598 def driverresolved(self):
599 """Obtain the paths of driver-resolved files."""
600
601 for f, entry in self._state.items():
602 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
603 yield f
604
605 def extras(self, filename):
606 return self._stateextras.setdefault(filename, {})
607
608 def _resolve(self, preresolve, dfile, wctx):
609 """rerun merge process for file path `dfile`"""
610 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
611 return True, 0
612 if self._state[dfile][0] == MERGE_RECORD_MERGED_OTHER:
613 return True, 0
614 stateentry = self._state[dfile]
615 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
616 octx = self._repo[self._other]
617 extras = self.extras(dfile)
618 anccommitnode = extras.get(b'ancestorlinknode')
619 if anccommitnode:
620 actx = self._repo[anccommitnode]
621 else:
622 actx = None
623 fcd = self._filectxorabsent(localkey, wctx, dfile)
624 fco = self._filectxorabsent(onode, octx, ofile)
625 # TODO: move this to filectxorabsent
626 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
627 # "premerge" x flags
628 flo = fco.flags()
629 fla = fca.flags()
630 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
631 if fca.node() == nullid and flags != flo:
632 if preresolve:
633 self._repo.ui.warn(
634 _(
635 b'warning: cannot merge flags for %s '
636 b'without common ancestor - keeping local flags\n'
637 )
638 % afile
639 )
640 elif flags == fla:
641 flags = flo
642 if preresolve:
643 # restore local
644 if localkey != nullhex:
645 f = self._repo.vfs(b'merge/' + localkey)
646 wctx[dfile].write(f.read(), flags)
647 f.close()
648 else:
649 wctx[dfile].remove(ignoremissing=True)
650 complete, r, deleted = filemerge.premerge(
651 self._repo,
652 wctx,
653 self._local,
654 lfile,
655 fcd,
656 fco,
657 fca,
658 labels=self._labels,
659 )
660 else:
661 complete, r, deleted = filemerge.filemerge(
662 self._repo,
663 wctx,
664 self._local,
665 lfile,
666 fcd,
667 fco,
668 fca,
669 labels=self._labels,
670 )
671 if r is None:
672 # no real conflict
673 del self._state[dfile]
674 self._stateextras.pop(dfile, None)
675 self._dirty = True
676 elif not r:
677 self.mark(dfile, MERGE_RECORD_RESOLVED)
678
679 if complete:
680 action = None
681 if deleted:
682 if fcd.isabsent():
683 # dc: local picked. Need to drop if present, which may
684 # happen on re-resolves.
685 action = ACTION_FORGET
686 else:
687 # cd: remote picked (or otherwise deleted)
688 action = ACTION_REMOVE
689 else:
690 if fcd.isabsent(): # dc: remote picked
691 action = ACTION_GET
692 elif fco.isabsent(): # cd: local picked
693 if dfile in self.localctx:
694 action = ACTION_ADD_MODIFIED
695 else:
696 action = ACTION_ADD
697 # else: regular merges (no action necessary)
698 self._results[dfile] = r, action
699
700 return complete, r
701
702 def _filectxorabsent(self, hexnode, ctx, f):
703 if hexnode == nullhex:
704 return filemerge.absentfilectx(ctx, f)
705 else:
706 return ctx[f]
707
708 def preresolve(self, dfile, wctx):
709 """run premerge process for dfile
710
711 Returns whether the merge is complete, and the exit code."""
712 return self._resolve(True, dfile, wctx)
713
714 def resolve(self, dfile, wctx):
715 """run merge process (assuming premerge was run) for dfile
716
717 Returns the exit code of the merge."""
718 return self._resolve(False, dfile, wctx)[1]
719
720 def counts(self):
721 """return counts for updated, merged and removed files in this
722 session"""
723 updated, merged, removed = 0, 0, 0
724 for r, action in pycompat.itervalues(self._results):
725 if r is None:
726 updated += 1
727 elif r == 0:
728 if action == ACTION_REMOVE:
729 removed += 1
730 else:
731 merged += 1
732 return updated, merged, removed
733
734 def unresolvedcount(self):
735 """get unresolved count for this merge (persistent)"""
736 return len(list(self.unresolved()))
737
738 def actions(self):
739 """return lists of actions to perform on the dirstate"""
740 actions = {
741 ACTION_REMOVE: [],
742 ACTION_FORGET: [],
743 ACTION_ADD: [],
744 ACTION_ADD_MODIFIED: [],
745 ACTION_GET: [],
746 }
747 for f, (r, action) in pycompat.iteritems(self._results):
748 if action is not None:
749 actions[action].append((f, None, b"merge result"))
750 return actions
751
752 def recordactions(self):
753 """record remove/add/get actions in the dirstate"""
754 branchmerge = self._repo.dirstate.p2() != nullid
755 recordupdates(self._repo, self.actions(), branchmerge, None)
756
757 def queueremove(self, f):
758 """queues a file to be removed from the dirstate
759
760 Meant for use by custom merge drivers."""
761 self._results[f] = 0, ACTION_REMOVE
762
763 def queueadd(self, f):
764 """queues a file to be added to the dirstate
765
766 Meant for use by custom merge drivers."""
767 self._results[f] = 0, ACTION_ADD
768
769 def queueget(self, f):
770 """queues a file to be marked modified in the dirstate
771
772 Meant for use by custom merge drivers."""
773 self._results[f] = 0, ACTION_GET
774 40
775 41
776 def _getcheckunknownconfig(repo, section, name): 42 def _getcheckunknownconfig(repo, section, name):
777 config = repo.ui.config(section, name) 43 config = repo.ui.config(section, name)
778 valid = [b'abort', b'ignore', b'warn'] 44 valid = [b'abort', b'ignore', b'warn']
883 elif config == b'warn': 149 elif config == b'warn':
884 warnconflicts.update(conflicts) 150 warnconflicts.update(conflicts)
885 151
886 checkunknowndirs = _unknowndirschecker() 152 checkunknowndirs = _unknowndirschecker()
887 for f, (m, args, msg) in pycompat.iteritems(actions): 153 for f, (m, args, msg) in pycompat.iteritems(actions):
888 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED): 154 if m in (
155 mergestatemod.ACTION_CREATED,
156 mergestatemod.ACTION_DELETED_CHANGED,
157 ):
889 if _checkunknownfile(repo, wctx, mctx, f): 158 if _checkunknownfile(repo, wctx, mctx, f):
890 fileconflicts.add(f) 159 fileconflicts.add(f)
891 elif pathconfig and f not in wctx: 160 elif pathconfig and f not in wctx:
892 path = checkunknowndirs(repo, wctx, f) 161 path = checkunknowndirs(repo, wctx, f)
893 if path is not None: 162 if path is not None:
894 pathconflicts.add(path) 163 pathconflicts.add(path)
895 elif m == ACTION_LOCAL_DIR_RENAME_GET: 164 elif m == mergestatemod.ACTION_LOCAL_DIR_RENAME_GET:
896 if _checkunknownfile(repo, wctx, mctx, f, args[0]): 165 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
897 fileconflicts.add(f) 166 fileconflicts.add(f)
898 167
899 allconflicts = fileconflicts | pathconflicts 168 allconflicts = fileconflicts | pathconflicts
900 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)} 169 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
901 unknownconflicts = allconflicts - ignoredconflicts 170 unknownconflicts = allconflicts - ignoredconflicts
902 collectconflicts(ignoredconflicts, ignoredconfig) 171 collectconflicts(ignoredconflicts, ignoredconfig)
903 collectconflicts(unknownconflicts, unknownconfig) 172 collectconflicts(unknownconflicts, unknownconfig)
904 else: 173 else:
905 for f, (m, args, msg) in pycompat.iteritems(actions): 174 for f, (m, args, msg) in pycompat.iteritems(actions):
906 if m == ACTION_CREATED_MERGE: 175 if m == mergestatemod.ACTION_CREATED_MERGE:
907 fl2, anc = args 176 fl2, anc = args
908 different = _checkunknownfile(repo, wctx, mctx, f) 177 different = _checkunknownfile(repo, wctx, mctx, f)
909 if repo.dirstate._ignore(f): 178 if repo.dirstate._ignore(f):
910 config = ignoredconfig 179 config = ignoredconfig
911 else: 180 else:
922 # (1) this is probably the wrong behavior here -- we should 191 # (1) this is probably the wrong behavior here -- we should
923 # probably abort, but some actions like rebases currently 192 # probably abort, but some actions like rebases currently
924 # don't like an abort happening in the middle of 193 # don't like an abort happening in the middle of
925 # merge.update. 194 # merge.update.
926 if not different: 195 if not different:
927 actions[f] = (ACTION_GET, (fl2, False), b'remote created') 196 actions[f] = (
197 mergestatemod.ACTION_GET,
198 (fl2, False),
199 b'remote created',
200 )
928 elif mergeforce or config == b'abort': 201 elif mergeforce or config == b'abort':
929 actions[f] = ( 202 actions[f] = (
930 ACTION_MERGE, 203 mergestatemod.ACTION_MERGE,
931 (f, f, None, False, anc), 204 (f, f, None, False, anc),
932 b'remote differs from untracked local', 205 b'remote differs from untracked local',
933 ) 206 )
934 elif config == b'abort': 207 elif config == b'abort':
935 abortconflicts.add(f) 208 abortconflicts.add(f)
936 else: 209 else:
937 if config == b'warn': 210 if config == b'warn':
938 warnconflicts.add(f) 211 warnconflicts.add(f)
939 actions[f] = (ACTION_GET, (fl2, True), b'remote created') 212 actions[f] = (
213 mergestatemod.ACTION_GET,
214 (fl2, True),
215 b'remote created',
216 )
940 217
941 for f in sorted(abortconflicts): 218 for f in sorted(abortconflicts):
942 warn = repo.ui.warn 219 warn = repo.ui.warn
943 if f in pathconflicts: 220 if f in pathconflicts:
944 if repo.wvfs.isfileorlink(f): 221 if repo.wvfs.isfileorlink(f):
960 repo.ui.warn(_(b"%s: replacing untracked file\n") % f) 237 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
961 else: 238 else:
962 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f) 239 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
963 240
964 for f, (m, args, msg) in pycompat.iteritems(actions): 241 for f, (m, args, msg) in pycompat.iteritems(actions):
965 if m == ACTION_CREATED: 242 if m == mergestatemod.ACTION_CREATED:
966 backup = ( 243 backup = (
967 f in fileconflicts 244 f in fileconflicts
968 or f in pathconflicts 245 or f in pathconflicts
969 or any(p in pathconflicts for p in pathutil.finddirs(f)) 246 or any(p in pathconflicts for p in pathutil.finddirs(f))
970 ) 247 )
971 (flags,) = args 248 (flags,) = args
972 actions[f] = (ACTION_GET, (flags, backup), msg) 249 actions[f] = (mergestatemod.ACTION_GET, (flags, backup), msg)
973 250
974 251
975 def _forgetremoved(wctx, mctx, branchmerge): 252 def _forgetremoved(wctx, mctx, branchmerge):
976 """ 253 """
977 Forget removed files 254 Forget removed files
986 that is not present in the working directory, we need to mark it 263 that is not present in the working directory, we need to mark it
987 as removed. 264 as removed.
988 """ 265 """
989 266
990 actions = {} 267 actions = {}
991 m = ACTION_FORGET 268 m = mergestatemod.ACTION_FORGET
992 if branchmerge: 269 if branchmerge:
993 m = ACTION_REMOVE 270 m = mergestatemod.ACTION_REMOVE
994 for f in wctx.deleted(): 271 for f in wctx.deleted():
995 if f not in mctx: 272 if f not in mctx:
996 actions[f] = m, None, b"forget deleted" 273 actions[f] = m, None, b"forget deleted"
997 274
998 if not branchmerge: 275 if not branchmerge:
999 for f in wctx.removed(): 276 for f in wctx.removed():
1000 if f not in mctx: 277 if f not in mctx:
1001 actions[f] = ACTION_FORGET, None, b"forget removed" 278 actions[f] = (
279 mergestatemod.ACTION_FORGET,
280 None,
281 b"forget removed",
282 )
1002 283
1003 return actions 284 return actions
1004 285
1005 286
1006 def _checkcollision(repo, wmf, actions): 287 def _checkcollision(repo, wmf, actions):
1024 pmmf = set(wmf) 305 pmmf = set(wmf)
1025 306
1026 if actions: 307 if actions:
1027 # KEEP and EXEC are no-op 308 # KEEP and EXEC are no-op
1028 for m in ( 309 for m in (
1029 ACTION_ADD, 310 mergestatemod.ACTION_ADD,
1030 ACTION_ADD_MODIFIED, 311 mergestatemod.ACTION_ADD_MODIFIED,
1031 ACTION_FORGET, 312 mergestatemod.ACTION_FORGET,
1032 ACTION_GET, 313 mergestatemod.ACTION_GET,
1033 ACTION_CHANGED_DELETED, 314 mergestatemod.ACTION_CHANGED_DELETED,
1034 ACTION_DELETED_CHANGED, 315 mergestatemod.ACTION_DELETED_CHANGED,
1035 ): 316 ):
1036 for f, args, msg in actions[m]: 317 for f, args, msg in actions[m]:
1037 pmmf.add(f) 318 pmmf.add(f)
1038 for f, args, msg in actions[ACTION_REMOVE]: 319 for f, args, msg in actions[mergestatemod.ACTION_REMOVE]:
1039 pmmf.discard(f) 320 pmmf.discard(f)
1040 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]: 321 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1041 f2, flags = args 322 f2, flags = args
1042 pmmf.discard(f2) 323 pmmf.discard(f2)
1043 pmmf.add(f) 324 pmmf.add(f)
1044 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]: 325 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1045 pmmf.add(f) 326 pmmf.add(f)
1046 for f, args, msg in actions[ACTION_MERGE]: 327 for f, args, msg in actions[mergestatemod.ACTION_MERGE]:
1047 f1, f2, fa, move, anc = args 328 f1, f2, fa, move, anc = args
1048 if move: 329 if move:
1049 pmmf.discard(f1) 330 pmmf.discard(f1)
1050 pmmf.add(f) 331 pmmf.add(f)
1051 332
1126 # The set of files deleted by all the actions. 407 # The set of files deleted by all the actions.
1127 deletedfiles = set() 408 deletedfiles = set()
1128 409
1129 for f, (m, args, msg) in actions.items(): 410 for f, (m, args, msg) in actions.items():
1130 if m in ( 411 if m in (
1131 ACTION_CREATED, 412 mergestatemod.ACTION_CREATED,
1132 ACTION_DELETED_CHANGED, 413 mergestatemod.ACTION_DELETED_CHANGED,
1133 ACTION_MERGE, 414 mergestatemod.ACTION_MERGE,
1134 ACTION_CREATED_MERGE, 415 mergestatemod.ACTION_CREATED_MERGE,
1135 ): 416 ):
1136 # This action may create a new local file. 417 # This action may create a new local file.
1137 createdfiledirs.update(pathutil.finddirs(f)) 418 createdfiledirs.update(pathutil.finddirs(f))
1138 if mf.hasdir(f): 419 if mf.hasdir(f):
1139 # The file aliases a local directory. This might be ok if all 420 # The file aliases a local directory. This might be ok if all
1140 # the files in the local directory are being deleted. This 421 # the files in the local directory are being deleted. This
1141 # will be checked once we know what all the deleted files are. 422 # will be checked once we know what all the deleted files are.
1142 remoteconflicts.add(f) 423 remoteconflicts.add(f)
1143 # Track the names of all deleted files. 424 # Track the names of all deleted files.
1144 if m == ACTION_REMOVE: 425 if m == mergestatemod.ACTION_REMOVE:
1145 deletedfiles.add(f) 426 deletedfiles.add(f)
1146 if m == ACTION_MERGE: 427 if m == mergestatemod.ACTION_MERGE:
1147 f1, f2, fa, move, anc = args 428 f1, f2, fa, move, anc = args
1148 if move: 429 if move:
1149 deletedfiles.add(f1) 430 deletedfiles.add(f1)
1150 if m == ACTION_DIR_RENAME_MOVE_LOCAL: 431 if m == mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL:
1151 f2, flags = args 432 f2, flags = args
1152 deletedfiles.add(f2) 433 deletedfiles.add(f2)
1153 434
1154 # Check all directories that contain created files for path conflicts. 435 # Check all directories that contain created files for path conflicts.
1155 for p in createdfiledirs: 436 for p in createdfiledirs:
1162 else: 443 else:
1163 # A file is in a directory which aliases a local file. 444 # A file is in a directory which aliases a local file.
1164 # We will need to rename the local file. 445 # We will need to rename the local file.
1165 localconflicts.add(p) 446 localconflicts.add(p)
1166 if p in actions and actions[p][0] in ( 447 if p in actions and actions[p][0] in (
1167 ACTION_CREATED, 448 mergestatemod.ACTION_CREATED,
1168 ACTION_DELETED_CHANGED, 449 mergestatemod.ACTION_DELETED_CHANGED,
1169 ACTION_MERGE, 450 mergestatemod.ACTION_MERGE,
1170 ACTION_CREATED_MERGE, 451 mergestatemod.ACTION_CREATED_MERGE,
1171 ): 452 ):
1172 # The file is in a directory which aliases a remote file. 453 # The file is in a directory which aliases a remote file.
1173 # This is an internal inconsistency within the remote 454 # This is an internal inconsistency within the remote
1174 # manifest. 455 # manifest.
1175 invalidconflicts.add(p) 456 invalidconflicts.add(p)
1178 for p in localconflicts: 459 for p in localconflicts:
1179 if p not in deletedfiles: 460 if p not in deletedfiles:
1180 ctxname = bytes(wctx).rstrip(b'+') 461 ctxname = bytes(wctx).rstrip(b'+')
1181 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) 462 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1182 actions[pnew] = ( 463 actions[pnew] = (
1183 ACTION_PATH_CONFLICT_RESOLVE, 464 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1184 (p,), 465 (p,),
1185 b'local path conflict', 466 b'local path conflict',
1186 ) 467 )
1187 actions[p] = (ACTION_PATH_CONFLICT, (pnew, b'l'), b'path conflict') 468 actions[p] = (
469 mergestatemod.ACTION_PATH_CONFLICT,
470 (pnew, b'l'),
471 b'path conflict',
472 )
1188 473
1189 if remoteconflicts: 474 if remoteconflicts:
1190 # Check if all files in the conflicting directories have been removed. 475 # Check if all files in the conflicting directories have been removed.
1191 ctxname = bytes(mctx).rstrip(b'+') 476 ctxname = bytes(mctx).rstrip(b'+')
1192 for f, p in _filesindirs(repo, mf, remoteconflicts): 477 for f, p in _filesindirs(repo, mf, remoteconflicts):
1193 if f not in deletedfiles: 478 if f not in deletedfiles:
1194 m, args, msg = actions[p] 479 m, args, msg = actions[p]
1195 pnew = util.safename(p, ctxname, wctx, set(actions.keys())) 480 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1196 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE): 481 if m in (
482 mergestatemod.ACTION_DELETED_CHANGED,
483 mergestatemod.ACTION_MERGE,
484 ):
1197 # Action was merge, just update target. 485 # Action was merge, just update target.
1198 actions[pnew] = (m, args, msg) 486 actions[pnew] = (m, args, msg)
1199 else: 487 else:
1200 # Action was create, change to renamed get action. 488 # Action was create, change to renamed get action.
1201 fl = args[0] 489 fl = args[0]
1202 actions[pnew] = ( 490 actions[pnew] = (
1203 ACTION_LOCAL_DIR_RENAME_GET, 491 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1204 (p, fl), 492 (p, fl),
1205 b'remote path conflict', 493 b'remote path conflict',
1206 ) 494 )
1207 actions[p] = ( 495 actions[p] = (
1208 ACTION_PATH_CONFLICT, 496 mergestatemod.ACTION_PATH_CONFLICT,
1209 (pnew, ACTION_REMOVE), 497 (pnew, mergestatemod.ACTION_REMOVE),
1210 b'path conflict', 498 b'path conflict',
1211 ) 499 )
1212 remoteconflicts.remove(p) 500 remoteconflicts.remove(p)
1213 break 501 break
1214 502
1338 fa = branch_copies1.copy.get( 626 fa = branch_copies1.copy.get(
1339 f, None 627 f, None
1340 ) or branch_copies2.copy.get(f, None) 628 ) or branch_copies2.copy.get(f, None)
1341 if fa is not None: 629 if fa is not None:
1342 actions[f] = ( 630 actions[f] = (
1343 ACTION_MERGE, 631 mergestatemod.ACTION_MERGE,
1344 (f, f, fa, False, pa.node()), 632 (f, f, fa, False, pa.node()),
1345 b'both renamed from %s' % fa, 633 b'both renamed from %s' % fa,
1346 ) 634 )
1347 else: 635 else:
1348 actions[f] = ( 636 actions[f] = (
1349 ACTION_MERGE, 637 mergestatemod.ACTION_MERGE,
1350 (f, f, None, False, pa.node()), 638 (f, f, None, False, pa.node()),
1351 b'both created', 639 b'both created',
1352 ) 640 )
1353 else: 641 else:
1354 a = ma[f] 642 a = ma[f]
1355 fla = ma.flags(f) 643 fla = ma.flags(f)
1356 nol = b'l' not in fl1 + fl2 + fla 644 nol = b'l' not in fl1 + fl2 + fla
1357 if n2 == a and fl2 == fla: 645 if n2 == a and fl2 == fla:
1358 actions[f] = (ACTION_KEEP, (), b'remote unchanged') 646 actions[f] = (
647 mergestatemod.ACTION_KEEP,
648 (),
649 b'remote unchanged',
650 )
1359 elif n1 == a and fl1 == fla: # local unchanged - use remote 651 elif n1 == a and fl1 == fla: # local unchanged - use remote
1360 if n1 == n2: # optimization: keep local content 652 if n1 == n2: # optimization: keep local content
1361 actions[f] = ( 653 actions[f] = (
1362 ACTION_EXEC, 654 mergestatemod.ACTION_EXEC,
1363 (fl2,), 655 (fl2,),
1364 b'update permissions', 656 b'update permissions',
1365 ) 657 )
1366 else: 658 else:
1367 actions[f] = ( 659 actions[f] = (
1368 ACTION_GET_OTHER_AND_STORE 660 mergestatemod.ACTION_GET_OTHER_AND_STORE
1369 if branchmerge 661 if branchmerge
1370 else ACTION_GET, 662 else mergestatemod.ACTION_GET,
1371 (fl2, False), 663 (fl2, False),
1372 b'remote is newer', 664 b'remote is newer',
1373 ) 665 )
1374 elif nol and n2 == a: # remote only changed 'x' 666 elif nol and n2 == a: # remote only changed 'x'
1375 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions') 667 actions[f] = (
668 mergestatemod.ACTION_EXEC,
669 (fl2,),
670 b'update permissions',
671 )
1376 elif nol and n1 == a: # local only changed 'x' 672 elif nol and n1 == a: # local only changed 'x'
1377 actions[f] = ( 673 actions[f] = (
1378 ACTION_GET_OTHER_AND_STORE 674 mergestatemod.ACTION_GET_OTHER_AND_STORE
1379 if branchmerge 675 if branchmerge
1380 else ACTION_GET, 676 else mergestatemod.ACTION_GET,
1381 (fl1, False), 677 (fl1, False),
1382 b'remote is newer', 678 b'remote is newer',
1383 ) 679 )
1384 else: # both changed something 680 else: # both changed something
1385 actions[f] = ( 681 actions[f] = (
1386 ACTION_MERGE, 682 mergestatemod.ACTION_MERGE,
1387 (f, f, f, False, pa.node()), 683 (f, f, f, False, pa.node()),
1388 b'versions differ', 684 b'versions differ',
1389 ) 685 )
1390 elif n1: # file exists only on local side 686 elif n1: # file exists only on local side
1391 if f in copied2: 687 if f in copied2:
1394 f in branch_copies1.movewithdir 690 f in branch_copies1.movewithdir
1395 ): # directory rename, move local 691 ): # directory rename, move local
1396 f2 = branch_copies1.movewithdir[f] 692 f2 = branch_copies1.movewithdir[f]
1397 if f2 in m2: 693 if f2 in m2:
1398 actions[f2] = ( 694 actions[f2] = (
1399 ACTION_MERGE, 695 mergestatemod.ACTION_MERGE,
1400 (f, f2, None, True, pa.node()), 696 (f, f2, None, True, pa.node()),
1401 b'remote directory rename, both created', 697 b'remote directory rename, both created',
1402 ) 698 )
1403 else: 699 else:
1404 actions[f2] = ( 700 actions[f2] = (
1405 ACTION_DIR_RENAME_MOVE_LOCAL, 701 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1406 (f, fl1), 702 (f, fl1),
1407 b'remote directory rename - move from %s' % f, 703 b'remote directory rename - move from %s' % f,
1408 ) 704 )
1409 elif f in branch_copies1.copy: 705 elif f in branch_copies1.copy:
1410 f2 = branch_copies1.copy[f] 706 f2 = branch_copies1.copy[f]
1411 actions[f] = ( 707 actions[f] = (
1412 ACTION_MERGE, 708 mergestatemod.ACTION_MERGE,
1413 (f, f2, f2, False, pa.node()), 709 (f, f2, f2, False, pa.node()),
1414 b'local copied/moved from %s' % f2, 710 b'local copied/moved from %s' % f2,
1415 ) 711 )
1416 elif f in ma: # clean, a different, no remote 712 elif f in ma: # clean, a different, no remote
1417 if n1 != ma[f]: 713 if n1 != ma[f]:
1418 if acceptremote: 714 if acceptremote:
1419 actions[f] = (ACTION_REMOVE, None, b'remote delete') 715 actions[f] = (
716 mergestatemod.ACTION_REMOVE,
717 None,
718 b'remote delete',
719 )
1420 else: 720 else:
1421 actions[f] = ( 721 actions[f] = (
1422 ACTION_CHANGED_DELETED, 722 mergestatemod.ACTION_CHANGED_DELETED,
1423 (f, None, f, False, pa.node()), 723 (f, None, f, False, pa.node()),
1424 b'prompt changed/deleted', 724 b'prompt changed/deleted',
1425 ) 725 )
1426 elif n1 == addednodeid: 726 elif n1 == addednodeid:
1427 # This extra 'a' is added by working copy manifest to mark 727 # This extra 'a' is added by working copy manifest to mark
1428 # the file as locally added. We should forget it instead of 728 # the file as locally added. We should forget it instead of
1429 # deleting it. 729 # deleting it.
1430 actions[f] = (ACTION_FORGET, None, b'remote deleted') 730 actions[f] = (
731 mergestatemod.ACTION_FORGET,
732 None,
733 b'remote deleted',
734 )
1431 else: 735 else:
1432 actions[f] = (ACTION_REMOVE, None, b'other deleted') 736 actions[f] = (
737 mergestatemod.ACTION_REMOVE,
738 None,
739 b'other deleted',
740 )
1433 elif n2: # file exists only on remote side 741 elif n2: # file exists only on remote side
1434 if f in copied1: 742 if f in copied1:
1435 pass # we'll deal with it on m1 side 743 pass # we'll deal with it on m1 side
1436 elif f in branch_copies2.movewithdir: 744 elif f in branch_copies2.movewithdir:
1437 f2 = branch_copies2.movewithdir[f] 745 f2 = branch_copies2.movewithdir[f]
1438 if f2 in m1: 746 if f2 in m1:
1439 actions[f2] = ( 747 actions[f2] = (
1440 ACTION_MERGE, 748 mergestatemod.ACTION_MERGE,
1441 (f2, f, None, False, pa.node()), 749 (f2, f, None, False, pa.node()),
1442 b'local directory rename, both created', 750 b'local directory rename, both created',
1443 ) 751 )
1444 else: 752 else:
1445 actions[f2] = ( 753 actions[f2] = (
1446 ACTION_LOCAL_DIR_RENAME_GET, 754 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1447 (f, fl2), 755 (f, fl2),
1448 b'local directory rename - get from %s' % f, 756 b'local directory rename - get from %s' % f,
1449 ) 757 )
1450 elif f in branch_copies2.copy: 758 elif f in branch_copies2.copy:
1451 f2 = branch_copies2.copy[f] 759 f2 = branch_copies2.copy[f]
1452 if f2 in m2: 760 if f2 in m2:
1453 actions[f] = ( 761 actions[f] = (
1454 ACTION_MERGE, 762 mergestatemod.ACTION_MERGE,
1455 (f2, f, f2, False, pa.node()), 763 (f2, f, f2, False, pa.node()),
1456 b'remote copied from %s' % f2, 764 b'remote copied from %s' % f2,
1457 ) 765 )
1458 else: 766 else:
1459 actions[f] = ( 767 actions[f] = (
1460 ACTION_MERGE, 768 mergestatemod.ACTION_MERGE,
1461 (f2, f, f2, True, pa.node()), 769 (f2, f, f2, True, pa.node()),
1462 b'remote moved from %s' % f2, 770 b'remote moved from %s' % f2,
1463 ) 771 )
1464 elif f not in ma: 772 elif f not in ma:
1465 # local unknown, remote created: the logic is described by the 773 # local unknown, remote created: the logic is described by the
1472 # y y y | merge 780 # y y y | merge
1473 # 781 #
1474 # Checking whether the files are different is expensive, so we 782 # Checking whether the files are different is expensive, so we
1475 # don't do that when we can avoid it. 783 # don't do that when we can avoid it.
1476 if not force: 784 if not force:
1477 actions[f] = (ACTION_CREATED, (fl2,), b'remote created') 785 actions[f] = (
786 mergestatemod.ACTION_CREATED,
787 (fl2,),
788 b'remote created',
789 )
1478 elif not branchmerge: 790 elif not branchmerge:
1479 actions[f] = (ACTION_CREATED, (fl2,), b'remote created') 791 actions[f] = (
792 mergestatemod.ACTION_CREATED,
793 (fl2,),
794 b'remote created',
795 )
1480 else: 796 else:
1481 actions[f] = ( 797 actions[f] = (
1482 ACTION_CREATED_MERGE, 798 mergestatemod.ACTION_CREATED_MERGE,
1483 (fl2, pa.node()), 799 (fl2, pa.node()),
1484 b'remote created, get or merge', 800 b'remote created, get or merge',
1485 ) 801 )
1486 elif n2 != ma[f]: 802 elif n2 != ma[f]:
1487 df = None 803 df = None
1490 # new file added in a directory that was moved 806 # new file added in a directory that was moved
1491 df = branch_copies1.dirmove[d] + f[len(d) :] 807 df = branch_copies1.dirmove[d] + f[len(d) :]
1492 break 808 break
1493 if df is not None and df in m1: 809 if df is not None and df in m1:
1494 actions[df] = ( 810 actions[df] = (
1495 ACTION_MERGE, 811 mergestatemod.ACTION_MERGE,
1496 (df, f, f, False, pa.node()), 812 (df, f, f, False, pa.node()),
1497 b'local directory rename - respect move ' 813 b'local directory rename - respect move '
1498 b'from %s' % f, 814 b'from %s' % f,
1499 ) 815 )
1500 elif acceptremote: 816 elif acceptremote:
1501 actions[f] = (ACTION_CREATED, (fl2,), b'remote recreating') 817 actions[f] = (
818 mergestatemod.ACTION_CREATED,
819 (fl2,),
820 b'remote recreating',
821 )
1502 else: 822 else:
1503 actions[f] = ( 823 actions[f] = (
1504 ACTION_DELETED_CHANGED, 824 mergestatemod.ACTION_DELETED_CHANGED,
1505 (None, f, f, False, pa.node()), 825 (None, f, f, False, pa.node()),
1506 b'prompt deleted/changed', 826 b'prompt deleted/changed',
1507 ) 827 )
1508 828
1509 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'): 829 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1526 remained the same.""" 846 remained the same."""
1527 # We force a copy of actions.items() because we're going to mutate 847 # We force a copy of actions.items() because we're going to mutate
1528 # actions as we resolve trivial conflicts. 848 # actions as we resolve trivial conflicts.
1529 for f, (m, args, msg) in list(actions.items()): 849 for f, (m, args, msg) in list(actions.items()):
1530 if ( 850 if (
1531 m == ACTION_CHANGED_DELETED 851 m == mergestatemod.ACTION_CHANGED_DELETED
1532 and f in ancestor 852 and f in ancestor
1533 and not wctx[f].cmp(ancestor[f]) 853 and not wctx[f].cmp(ancestor[f])
1534 ): 854 ):
1535 # local did change but ended up with same content 855 # local did change but ended up with same content
1536 actions[f] = ACTION_REMOVE, None, b'prompt same' 856 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
1537 elif ( 857 elif (
1538 m == ACTION_DELETED_CHANGED 858 m == mergestatemod.ACTION_DELETED_CHANGED
1539 and f in ancestor 859 and f in ancestor
1540 and not mctx[f].cmp(ancestor[f]) 860 and not mctx[f].cmp(ancestor[f])
1541 ): 861 ):
1542 # remote did change but ended up with same content 862 # remote did change but ended up with same content
1543 del actions[f] # don't get = keep local deleted 863 del actions[f] # don't get = keep local deleted
1611 if renamedelete is None or len(renamedelete) < len(renamedelete1): 931 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1612 renamedelete = renamedelete1 932 renamedelete = renamedelete1
1613 933
1614 for f, a in sorted(pycompat.iteritems(actions)): 934 for f, a in sorted(pycompat.iteritems(actions)):
1615 m, args, msg = a 935 m, args, msg = a
1616 if m == ACTION_GET_OTHER_AND_STORE: 936 if m == mergestatemod.ACTION_GET_OTHER_AND_STORE:
1617 m = ACTION_GET 937 m = mergestatemod.ACTION_GET
1618 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m)) 938 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1619 if f in fbids: 939 if f in fbids:
1620 d = fbids[f] 940 d = fbids[f]
1621 if m in d: 941 if m in d:
1622 d[m].append(a) 942 d[m].append(a)
1636 if all(a == l[0] for a in l[1:]): # len(bids) is > 1 956 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1637 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m)) 957 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1638 actions[f] = l[0] 958 actions[f] = l[0]
1639 continue 959 continue
1640 # If keep is an option, just do it. 960 # If keep is an option, just do it.
1641 if ACTION_KEEP in bids: 961 if mergestatemod.ACTION_KEEP in bids:
1642 repo.ui.note(_(b" %s: picking 'keep' action\n") % f) 962 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1643 actions[f] = bids[ACTION_KEEP][0] 963 actions[f] = bids[mergestatemod.ACTION_KEEP][0]
1644 continue 964 continue
1645 # If there are gets and they all agree [how could they not?], do it. 965 # If there are gets and they all agree [how could they not?], do it.
1646 if ACTION_GET in bids: 966 if mergestatemod.ACTION_GET in bids:
1647 ga0 = bids[ACTION_GET][0] 967 ga0 = bids[mergestatemod.ACTION_GET][0]
1648 if all(a == ga0 for a in bids[ACTION_GET][1:]): 968 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
1649 repo.ui.note(_(b" %s: picking 'get' action\n") % f) 969 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1650 actions[f] = ga0 970 actions[f] = ga0
1651 continue 971 continue
1652 # TODO: Consider other simple actions such as mode changes 972 # TODO: Consider other simple actions such as mode changes
1653 # Handle inefficient democrazy. 973 # Handle inefficient democrazy.
1788 # don't touch the context to be merged in. 'cd' is skipped, because 1108 # don't touch the context to be merged in. 'cd' is skipped, because
1789 # changed/deleted never resolves to something from the remote side. 1109 # changed/deleted never resolves to something from the remote side.
1790 oplist = [ 1110 oplist = [
1791 actions[a] 1111 actions[a]
1792 for a in ( 1112 for a in (
1793 ACTION_GET, 1113 mergestatemod.ACTION_GET,
1794 ACTION_DELETED_CHANGED, 1114 mergestatemod.ACTION_DELETED_CHANGED,
1795 ACTION_LOCAL_DIR_RENAME_GET, 1115 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1796 ACTION_MERGE, 1116 mergestatemod.ACTION_MERGE,
1797 ) 1117 )
1798 ] 1118 ]
1799 prefetch = scmutil.prefetchfiles 1119 prefetch = scmutil.prefetchfiles
1800 matchfiles = scmutil.matchfiles 1120 matchfiles = scmutil.matchfiles
1801 prefetch( 1121 prefetch(
1824 def emptyactions(): 1144 def emptyactions():
1825 """create an actions dict, to be populated and passed to applyupdates()""" 1145 """create an actions dict, to be populated and passed to applyupdates()"""
1826 return { 1146 return {
1827 m: [] 1147 m: []
1828 for m in ( 1148 for m in (
1829 ACTION_ADD, 1149 mergestatemod.ACTION_ADD,
1830 ACTION_ADD_MODIFIED, 1150 mergestatemod.ACTION_ADD_MODIFIED,
1831 ACTION_FORGET, 1151 mergestatemod.ACTION_FORGET,
1832 ACTION_GET, 1152 mergestatemod.ACTION_GET,
1833 ACTION_CHANGED_DELETED, 1153 mergestatemod.ACTION_CHANGED_DELETED,
1834 ACTION_DELETED_CHANGED, 1154 mergestatemod.ACTION_DELETED_CHANGED,
1835 ACTION_REMOVE, 1155 mergestatemod.ACTION_REMOVE,
1836 ACTION_DIR_RENAME_MOVE_LOCAL, 1156 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1837 ACTION_LOCAL_DIR_RENAME_GET, 1157 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1838 ACTION_MERGE, 1158 mergestatemod.ACTION_MERGE,
1839 ACTION_EXEC, 1159 mergestatemod.ACTION_EXEC,
1840 ACTION_KEEP, 1160 mergestatemod.ACTION_KEEP,
1841 ACTION_PATH_CONFLICT, 1161 mergestatemod.ACTION_PATH_CONFLICT,
1842 ACTION_PATH_CONFLICT_RESOLVE, 1162 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1843 ACTION_GET_OTHER_AND_STORE, 1163 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1844 ) 1164 )
1845 } 1165 }
1846 1166
1847 1167
1848 def applyupdates( 1168 def applyupdates(
1860 """ 1180 """
1861 1181
1862 _prefetchfiles(repo, mctx, actions) 1182 _prefetchfiles(repo, mctx, actions)
1863 1183
1864 updated, merged, removed = 0, 0, 0 1184 updated, merged, removed = 0, 0, 0
1865 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels) 1185 ms = mergestatemod.mergestate.clean(
1186 repo, wctx.p1().node(), mctx.node(), labels
1187 )
1866 1188
1867 # add ACTION_GET_OTHER_AND_STORE to mergestate 1189 # add ACTION_GET_OTHER_AND_STORE to mergestate
1868 for e in actions[ACTION_GET_OTHER_AND_STORE]: 1190 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1869 ms.addmergedother(e[0]) 1191 ms.addmergedother(e[0])
1870 1192
1871 moves = [] 1193 moves = []
1872 for m, l in actions.items(): 1194 for m, l in actions.items():
1873 l.sort() 1195 l.sort()
1874 1196
1875 # 'cd' and 'dc' actions are treated like other merge conflicts 1197 # 'cd' and 'dc' actions are treated like other merge conflicts
1876 mergeactions = sorted(actions[ACTION_CHANGED_DELETED]) 1198 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1877 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED])) 1199 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1878 mergeactions.extend(actions[ACTION_MERGE]) 1200 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1879 for f, args, msg in mergeactions: 1201 for f, args, msg in mergeactions:
1880 f1, f2, fa, move, anc = args 1202 f1, f2, fa, move, anc = args
1881 if f == b'.hgsubstate': # merged internally 1203 if f == b'.hgsubstate': # merged internally
1882 continue 1204 continue
1883 if f1 is None: 1205 if f1 is None:
1904 if wctx[f].lexists(): 1226 if wctx[f].lexists():
1905 repo.ui.debug(b"removing %s\n" % f) 1227 repo.ui.debug(b"removing %s\n" % f)
1906 wctx[f].audit() 1228 wctx[f].audit()
1907 wctx[f].remove() 1229 wctx[f].remove()
1908 1230
1909 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP) 1231 numupdates = sum(
1232 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1233 )
1910 progress = repo.ui.makeprogress( 1234 progress = repo.ui.makeprogress(
1911 _(b'updating'), unit=_(b'files'), total=numupdates 1235 _(b'updating'), unit=_(b'files'), total=numupdates
1912 ) 1236 )
1913 1237
1914 if [a for a in actions[ACTION_REMOVE] if a[0] == b'.hgsubstate']: 1238 if [
1239 a
1240 for a in actions[mergestatemod.ACTION_REMOVE]
1241 if a[0] == b'.hgsubstate'
1242 ]:
1915 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) 1243 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1916 1244
1917 # record path conflicts 1245 # record path conflicts
1918 for f, args, msg in actions[ACTION_PATH_CONFLICT]: 1246 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1919 f1, fo = args 1247 f1, fo = args
1920 s = repo.ui.status 1248 s = repo.ui.status
1921 s( 1249 s(
1922 _( 1250 _(
1923 b"%s: path conflict - a file or link has the same name as a " 1251 b"%s: path conflict - a file or link has the same name as a "
1937 # per-item cost at 0 in that case. 1265 # per-item cost at 0 in that case.
1938 cost = 0 if wctx.isinmemory() else 0.001 1266 cost = 0 if wctx.isinmemory() else 0.001
1939 1267
1940 # remove in parallel (must come before resolving path conflicts and getting) 1268 # remove in parallel (must come before resolving path conflicts and getting)
1941 prog = worker.worker( 1269 prog = worker.worker(
1942 repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE] 1270 repo.ui,
1271 cost,
1272 batchremove,
1273 (repo, wctx),
1274 actions[mergestatemod.ACTION_REMOVE],
1943 ) 1275 )
1944 for i, item in prog: 1276 for i, item in prog:
1945 progress.increment(step=i, item=item) 1277 progress.increment(step=i, item=item)
1946 removed = len(actions[ACTION_REMOVE]) 1278 removed = len(actions[mergestatemod.ACTION_REMOVE])
1947 1279
1948 # resolve path conflicts (must come before getting) 1280 # resolve path conflicts (must come before getting)
1949 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]: 1281 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1950 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg)) 1282 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1951 (f0,) = args 1283 (f0,) = args
1952 if wctx[f0].lexists(): 1284 if wctx[f0].lexists():
1953 repo.ui.note(_(b"moving %s to %s\n") % (f0, f)) 1285 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1954 wctx[f].audit() 1286 wctx[f].audit()
1963 prog = worker.worker( 1295 prog = worker.worker(
1964 repo.ui, 1296 repo.ui,
1965 cost, 1297 cost,
1966 batchget, 1298 batchget,
1967 (repo, mctx, wctx, wantfiledata), 1299 (repo, mctx, wctx, wantfiledata),
1968 actions[ACTION_GET], 1300 actions[mergestatemod.ACTION_GET],
1969 threadsafe=threadsafe, 1301 threadsafe=threadsafe,
1970 hasretval=True, 1302 hasretval=True,
1971 ) 1303 )
1972 getfiledata = {} 1304 getfiledata = {}
1973 for final, res in prog: 1305 for final, res in prog:
1974 if final: 1306 if final:
1975 getfiledata = res 1307 getfiledata = res
1976 else: 1308 else:
1977 i, item = res 1309 i, item = res
1978 progress.increment(step=i, item=item) 1310 progress.increment(step=i, item=item)
1979 updated = len(actions[ACTION_GET]) 1311 updated = len(actions[mergestatemod.ACTION_GET])
1980 1312
1981 if [a for a in actions[ACTION_GET] if a[0] == b'.hgsubstate']: 1313 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1982 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) 1314 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1983 1315
1984 # forget (manifest only, just log it) (must come first) 1316 # forget (manifest only, just log it) (must come first)
1985 for f, args, msg in actions[ACTION_FORGET]: 1317 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1986 repo.ui.debug(b" %s: %s -> f\n" % (f, msg)) 1318 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1987 progress.increment(item=f) 1319 progress.increment(item=f)
1988 1320
1989 # re-add (manifest only, just log it) 1321 # re-add (manifest only, just log it)
1990 for f, args, msg in actions[ACTION_ADD]: 1322 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1991 repo.ui.debug(b" %s: %s -> a\n" % (f, msg)) 1323 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1992 progress.increment(item=f) 1324 progress.increment(item=f)
1993 1325
1994 # re-add/mark as modified (manifest only, just log it) 1326 # re-add/mark as modified (manifest only, just log it)
1995 for f, args, msg in actions[ACTION_ADD_MODIFIED]: 1327 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1996 repo.ui.debug(b" %s: %s -> am\n" % (f, msg)) 1328 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1997 progress.increment(item=f) 1329 progress.increment(item=f)
1998 1330
1999 # keep (noop, just log it) 1331 # keep (noop, just log it)
2000 for f, args, msg in actions[ACTION_KEEP]: 1332 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
2001 repo.ui.debug(b" %s: %s -> k\n" % (f, msg)) 1333 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
2002 # no progress 1334 # no progress
2003 1335
2004 # directory rename, move local 1336 # directory rename, move local
2005 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]: 1337 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
2006 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg)) 1338 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
2007 progress.increment(item=f) 1339 progress.increment(item=f)
2008 f0, flags = args 1340 f0, flags = args
2009 repo.ui.note(_(b"moving %s to %s\n") % (f0, f)) 1341 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
2010 wctx[f].audit() 1342 wctx[f].audit()
2011 wctx[f].write(wctx.filectx(f0).data(), flags) 1343 wctx[f].write(wctx.filectx(f0).data(), flags)
2012 wctx[f0].remove() 1344 wctx[f0].remove()
2013 updated += 1 1345 updated += 1
2014 1346
2015 # local directory rename, get 1347 # local directory rename, get
2016 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]: 1348 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
2017 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg)) 1349 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
2018 progress.increment(item=f) 1350 progress.increment(item=f)
2019 f0, flags = args 1351 f0, flags = args
2020 repo.ui.note(_(b"getting %s to %s\n") % (f0, f)) 1352 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
2021 wctx[f].write(mctx.filectx(f0).data(), flags) 1353 wctx[f].write(mctx.filectx(f0).data(), flags)
2022 updated += 1 1354 updated += 1
2023 1355
2024 # exec 1356 # exec
2025 for f, args, msg in actions[ACTION_EXEC]: 1357 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
2026 repo.ui.debug(b" %s: %s -> e\n" % (f, msg)) 1358 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
2027 progress.increment(item=f) 1359 progress.increment(item=f)
2028 (flags,) = args 1360 (flags,) = args
2029 wctx[f].audit() 1361 wctx[f].audit()
2030 wctx[f].setflags(b'l' in flags, b'x' in flags) 1362 wctx[f].setflags(b'l' in flags, b'x' in flags)
2085 unresolved = ms.unresolvedcount() 1417 unresolved = ms.unresolvedcount()
2086 1418
2087 if ( 1419 if (
2088 usemergedriver 1420 usemergedriver
2089 and not unresolved 1421 and not unresolved
2090 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS 1422 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
2091 ): 1423 ):
2092 if not driverconclude(repo, ms, wctx, labels=labels): 1424 if not driverconclude(repo, ms, wctx, labels=labels):
2093 # XXX setting unresolved to at least 1 is a hack to make sure we 1425 # XXX setting unresolved to at least 1 is a hack to make sure we
2094 # error out 1426 # error out
2095 unresolved = max(unresolved, 1) 1427 unresolved = max(unresolved, 1)
2101 merged += msmerged 1433 merged += msmerged
2102 removed += msremoved 1434 removed += msremoved
2103 1435
2104 extraactions = ms.actions() 1436 extraactions = ms.actions()
2105 if extraactions: 1437 if extraactions:
2106 mfiles = {a[0] for a in actions[ACTION_MERGE]} 1438 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
2107 for k, acts in pycompat.iteritems(extraactions): 1439 for k, acts in pycompat.iteritems(extraactions):
2108 actions[k].extend(acts) 1440 actions[k].extend(acts)
2109 if k == ACTION_GET and wantfiledata: 1441 if k == mergestatemod.ACTION_GET and wantfiledata:
2110 # no filedata until mergestate is updated to provide it 1442 # no filedata until mergestate is updated to provide it
2111 for a in acts: 1443 for a in acts:
2112 getfiledata[a[0]] = None 1444 getfiledata[a[0]] = None
2113 # Remove these files from actions[ACTION_MERGE] as well. This is 1445 # Remove these files from actions[ACTION_MERGE] as well. This is
2114 # important because in recordupdates, files in actions[ACTION_MERGE] 1446 # important because in recordupdates, files in actions[ACTION_MERGE]
2126 # 1458 #
2127 # We don't need to do the same operation for 'dc' and 'cd' because 1459 # We don't need to do the same operation for 'dc' and 'cd' because
2128 # those lists aren't consulted again. 1460 # those lists aren't consulted again.
2129 mfiles.difference_update(a[0] for a in acts) 1461 mfiles.difference_update(a[0] for a in acts)
2130 1462
2131 actions[ACTION_MERGE] = [ 1463 actions[mergestatemod.ACTION_MERGE] = [
2132 a for a in actions[ACTION_MERGE] if a[0] in mfiles 1464 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
2133 ] 1465 ]
2134 1466
2135 progress.complete() 1467 progress.complete()
2136 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0) 1468 assert len(getfiledata) == (
1469 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1470 )
2137 return updateresult(updated, merged, removed, unresolved), getfiledata 1471 return updateresult(updated, merged, removed, unresolved), getfiledata
2138
2139
2140 def recordupdates(repo, actions, branchmerge, getfiledata):
2141 """record merge actions to the dirstate"""
2142 # remove (must come first)
2143 for f, args, msg in actions.get(ACTION_REMOVE, []):
2144 if branchmerge:
2145 repo.dirstate.remove(f)
2146 else:
2147 repo.dirstate.drop(f)
2148
2149 # forget (must come first)
2150 for f, args, msg in actions.get(ACTION_FORGET, []):
2151 repo.dirstate.drop(f)
2152
2153 # resolve path conflicts
2154 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
2155 (f0,) = args
2156 origf0 = repo.dirstate.copied(f0) or f0
2157 repo.dirstate.add(f)
2158 repo.dirstate.copy(origf0, f)
2159 if f0 == origf0:
2160 repo.dirstate.remove(f0)
2161 else:
2162 repo.dirstate.drop(f0)
2163
2164 # re-add
2165 for f, args, msg in actions.get(ACTION_ADD, []):
2166 repo.dirstate.add(f)
2167
2168 # re-add/mark as modified
2169 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
2170 if branchmerge:
2171 repo.dirstate.normallookup(f)
2172 else:
2173 repo.dirstate.add(f)
2174
2175 # exec change
2176 for f, args, msg in actions.get(ACTION_EXEC, []):
2177 repo.dirstate.normallookup(f)
2178
2179 # keep
2180 for f, args, msg in actions.get(ACTION_KEEP, []):
2181 pass
2182
2183 # get
2184 for f, args, msg in actions.get(ACTION_GET, []):
2185 if branchmerge:
2186 repo.dirstate.otherparent(f)
2187 else:
2188 parentfiledata = getfiledata[f] if getfiledata else None
2189 repo.dirstate.normal(f, parentfiledata=parentfiledata)
2190
2191 # merge
2192 for f, args, msg in actions.get(ACTION_MERGE, []):
2193 f1, f2, fa, move, anc = args
2194 if branchmerge:
2195 # We've done a branch merge, mark this file as merged
2196 # so that we properly record the merger later
2197 repo.dirstate.merge(f)
2198 if f1 != f2: # copy/rename
2199 if move:
2200 repo.dirstate.remove(f1)
2201 if f1 != f:
2202 repo.dirstate.copy(f1, f)
2203 else:
2204 repo.dirstate.copy(f2, f)
2205 else:
2206 # We've update-merged a locally modified file, so
2207 # we set the dirstate to emulate a normal checkout
2208 # of that file some time in the past. Thus our
2209 # merge will appear as a normal local file
2210 # modification.
2211 if f2 == f: # file not locally copied/moved
2212 repo.dirstate.normallookup(f)
2213 if move:
2214 repo.dirstate.drop(f1)
2215
2216 # directory rename, move local
2217 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
2218 f0, flag = args
2219 if branchmerge:
2220 repo.dirstate.add(f)
2221 repo.dirstate.remove(f0)
2222 repo.dirstate.copy(f0, f)
2223 else:
2224 repo.dirstate.normal(f)
2225 repo.dirstate.drop(f0)
2226
2227 # directory rename, get
2228 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
2229 f0, flag = args
2230 if branchmerge:
2231 repo.dirstate.add(f)
2232 repo.dirstate.copy(f0, f)
2233 else:
2234 repo.dirstate.normal(f)
2235 1472
2236 1473
2237 UPDATECHECK_ABORT = b'abort' # handled at higher layers 1474 UPDATECHECK_ABORT = b'abort' # handled at higher layers
2238 UPDATECHECK_NONE = b'none' 1475 UPDATECHECK_NONE = b'none'
2239 UPDATECHECK_LINEAR = b'linear' 1476 UPDATECHECK_LINEAR = b'linear'
2354 overwrite = force and not branchmerge 1591 overwrite = force and not branchmerge
2355 ### check phase 1592 ### check phase
2356 if not overwrite: 1593 if not overwrite:
2357 if len(pl) > 1: 1594 if len(pl) > 1:
2358 raise error.Abort(_(b"outstanding uncommitted merge")) 1595 raise error.Abort(_(b"outstanding uncommitted merge"))
2359 ms = mergestate.read(repo) 1596 ms = mergestatemod.mergestate.read(repo)
2360 if list(ms.unresolved()): 1597 if list(ms.unresolved()):
2361 raise error.Abort( 1598 raise error.Abort(
2362 _(b"outstanding merge conflicts"), 1599 _(b"outstanding merge conflicts"),
2363 hint=_(b"use 'hg resolve' to resolve"), 1600 hint=_(b"use 'hg resolve' to resolve"),
2364 ) 1601 )
2441 ) 1678 )
2442 1679
2443 if updatecheck == UPDATECHECK_NO_CONFLICT: 1680 if updatecheck == UPDATECHECK_NO_CONFLICT:
2444 for f, (m, args, msg) in pycompat.iteritems(actionbyfile): 1681 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2445 if m not in ( 1682 if m not in (
2446 ACTION_GET, 1683 mergestatemod.ACTION_GET,
2447 ACTION_KEEP, 1684 mergestatemod.ACTION_KEEP,
2448 ACTION_EXEC, 1685 mergestatemod.ACTION_EXEC,
2449 ACTION_REMOVE, 1686 mergestatemod.ACTION_REMOVE,
2450 ACTION_PATH_CONFLICT_RESOLVE, 1687 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
2451 ACTION_GET_OTHER_AND_STORE, 1688 mergestatemod.ACTION_GET_OTHER_AND_STORE,
2452 ): 1689 ):
2453 msg = _(b"conflicting changes") 1690 msg = _(b"conflicting changes")
2454 hint = _(b"commit or update --clean to discard changes") 1691 hint = _(b"commit or update --clean to discard changes")
2455 raise error.Abort(msg, hint=hint) 1692 raise error.Abort(msg, hint=hint)
2456 1693
2460 if b'.hgsubstate' in actionbyfile: 1697 if b'.hgsubstate' in actionbyfile:
2461 f = b'.hgsubstate' 1698 f = b'.hgsubstate'
2462 m, args, msg = actionbyfile[f] 1699 m, args, msg = actionbyfile[f]
2463 prompts = filemerge.partextras(labels) 1700 prompts = filemerge.partextras(labels)
2464 prompts[b'f'] = f 1701 prompts[b'f'] = f
2465 if m == ACTION_CHANGED_DELETED: 1702 if m == mergestatemod.ACTION_CHANGED_DELETED:
2466 if repo.ui.promptchoice( 1703 if repo.ui.promptchoice(
2467 _( 1704 _(
2468 b"local%(l)s changed %(f)s which other%(o)s deleted\n" 1705 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2469 b"use (c)hanged version or (d)elete?" 1706 b"use (c)hanged version or (d)elete?"
2470 b"$$ &Changed $$ &Delete" 1707 b"$$ &Changed $$ &Delete"
2471 ) 1708 )
2472 % prompts, 1709 % prompts,
2473 0, 1710 0,
2474 ): 1711 ):
2475 actionbyfile[f] = (ACTION_REMOVE, None, b'prompt delete') 1712 actionbyfile[f] = (
1713 mergestatemod.ACTION_REMOVE,
1714 None,
1715 b'prompt delete',
1716 )
2476 elif f in p1: 1717 elif f in p1:
2477 actionbyfile[f] = ( 1718 actionbyfile[f] = (
2478 ACTION_ADD_MODIFIED, 1719 mergestatemod.ACTION_ADD_MODIFIED,
2479 None, 1720 None,
2480 b'prompt keep', 1721 b'prompt keep',
2481 ) 1722 )
2482 else: 1723 else:
2483 actionbyfile[f] = (ACTION_ADD, None, b'prompt keep') 1724 actionbyfile[f] = (
2484 elif m == ACTION_DELETED_CHANGED: 1725 mergestatemod.ACTION_ADD,
1726 None,
1727 b'prompt keep',
1728 )
1729 elif m == mergestatemod.ACTION_DELETED_CHANGED:
2485 f1, f2, fa, move, anc = args 1730 f1, f2, fa, move, anc = args
2486 flags = p2[f2].flags() 1731 flags = p2[f2].flags()
2487 if ( 1732 if (
2488 repo.ui.promptchoice( 1733 repo.ui.promptchoice(
2489 _( 1734 _(
2495 0, 1740 0,
2496 ) 1741 )
2497 == 0 1742 == 0
2498 ): 1743 ):
2499 actionbyfile[f] = ( 1744 actionbyfile[f] = (
2500 ACTION_GET, 1745 mergestatemod.ACTION_GET,
2501 (flags, False), 1746 (flags, False),
2502 b'prompt recreating', 1747 b'prompt recreating',
2503 ) 1748 )
2504 else: 1749 else:
2505 del actionbyfile[f] 1750 del actionbyfile[f]
2509 for f, (m, args, msg) in pycompat.iteritems(actionbyfile): 1754 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2510 if m not in actions: 1755 if m not in actions:
2511 actions[m] = [] 1756 actions[m] = []
2512 actions[m].append((f, args, msg)) 1757 actions[m].append((f, args, msg))
2513 1758
2514 # ACTION_GET_OTHER_AND_STORE is a ACTION_GET + store in mergestate 1759 # ACTION_GET_OTHER_AND_STORE is a mergestatemod.ACTION_GET + store in mergestate
2515 for e in actions[ACTION_GET_OTHER_AND_STORE]: 1760 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
2516 actions[ACTION_GET].append(e) 1761 actions[mergestatemod.ACTION_GET].append(e)
2517 1762
2518 if not util.fscasesensitive(repo.path): 1763 if not util.fscasesensitive(repo.path):
2519 # check collision between files only in p2 for clean update 1764 # check collision between files only in p2 for clean update
2520 if not branchmerge and ( 1765 if not branchmerge and (
2521 force or not wc.dirty(missing=True, branch=False) 1766 force or not wc.dirty(missing=True, branch=False)
2588 1833
2589 if ( 1834 if (
2590 fsmonitorwarning 1835 fsmonitorwarning
2591 and not fsmonitorenabled 1836 and not fsmonitorenabled
2592 and p1.node() == nullid 1837 and p1.node() == nullid
2593 and len(actions[ACTION_GET]) >= fsmonitorthreshold 1838 and len(actions[mergestatemod.ACTION_GET]) >= fsmonitorthreshold
2594 and pycompat.sysplatform.startswith((b'linux', b'darwin')) 1839 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
2595 ): 1840 ):
2596 repo.ui.warn( 1841 repo.ui.warn(
2597 _( 1842 _(
2598 b'(warning: large working directory being used without ' 1843 b'(warning: large working directory being used without '
2607 ) 1852 )
2608 1853
2609 if updatedirstate: 1854 if updatedirstate:
2610 with repo.dirstate.parentchange(): 1855 with repo.dirstate.parentchange():
2611 repo.setparents(fp1, fp2) 1856 repo.setparents(fp1, fp2)
2612 recordupdates(repo, actions, branchmerge, getfiledata) 1857 mergestatemod.recordupdates(
1858 repo, actions, branchmerge, getfiledata
1859 )
2613 # update completed, clear state 1860 # update completed, clear state
2614 util.unlink(repo.vfs.join(b'updatestate')) 1861 util.unlink(repo.vfs.join(b'updatestate'))
2615 1862
2616 if not branchmerge: 1863 if not branchmerge:
2617 repo.dirstate.setbranch(p2.branch()) 1864 repo.dirstate.setbranch(p2.branch())