changeset 44856 | b7808443ed6a |
parent 44687 | 1b8fd4af3318 |
child 44939 | 818b4f19ef23 |
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()) |