##// END OF EJS Templates
mergestate: add accessors for local and other nodeid, not just contexts...
Martin von Zweigbergk -
r44696:b1069b36 default
parent child Browse files
Show More
@@ -1,2734 +1,2742 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import shutil
12 12 import stat
13 13 import struct
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 addednodeid,
18 18 bin,
19 19 hex,
20 20 modifiednodeid,
21 21 nullhex,
22 22 nullid,
23 23 nullrev,
24 24 )
25 25 from .pycompat import delattr
26 26 from .thirdparty import attr
27 27 from . import (
28 28 copies,
29 29 encoding,
30 30 error,
31 31 filemerge,
32 32 match as matchmod,
33 33 obsutil,
34 34 pathutil,
35 35 pycompat,
36 36 scmutil,
37 37 subrepoutil,
38 38 util,
39 39 worker,
40 40 )
41 41 from .utils import hashutil
42 42
43 43 _pack = struct.pack
44 44 _unpack = struct.unpack
45 45
46 46
47 47 def _droponode(data):
48 48 # used for compatibility for v1
49 49 bits = data.split(b'\0')
50 50 bits = bits[:-2] + bits[-1:]
51 51 return b'\0'.join(bits)
52 52
53 53
54 54 # Merge state record types. See ``mergestate`` docs for more.
55 55 RECORD_LOCAL = b'L'
56 56 RECORD_OTHER = b'O'
57 57 RECORD_MERGED = b'F'
58 58 RECORD_CHANGEDELETE_CONFLICT = b'C'
59 59 RECORD_MERGE_DRIVER_MERGE = b'D'
60 60 RECORD_PATH_CONFLICT = b'P'
61 61 RECORD_MERGE_DRIVER_STATE = b'm'
62 62 RECORD_FILE_VALUES = b'f'
63 63 RECORD_LABELS = b'l'
64 64 RECORD_OVERRIDE = b't'
65 65 RECORD_UNSUPPORTED_MANDATORY = b'X'
66 66 RECORD_UNSUPPORTED_ADVISORY = b'x'
67 67
68 68 MERGE_DRIVER_STATE_UNMARKED = b'u'
69 69 MERGE_DRIVER_STATE_MARKED = b'm'
70 70 MERGE_DRIVER_STATE_SUCCESS = b's'
71 71
72 72 MERGE_RECORD_UNRESOLVED = b'u'
73 73 MERGE_RECORD_RESOLVED = b'r'
74 74 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
75 75 MERGE_RECORD_RESOLVED_PATH = b'pr'
76 76 MERGE_RECORD_DRIVER_RESOLVED = b'd'
77 77
78 78 ACTION_FORGET = b'f'
79 79 ACTION_REMOVE = b'r'
80 80 ACTION_ADD = b'a'
81 81 ACTION_GET = b'g'
82 82 ACTION_PATH_CONFLICT = b'p'
83 83 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
84 84 ACTION_ADD_MODIFIED = b'am'
85 85 ACTION_CREATED = b'c'
86 86 ACTION_DELETED_CHANGED = b'dc'
87 87 ACTION_CHANGED_DELETED = b'cd'
88 88 ACTION_MERGE = b'm'
89 89 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
90 90 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
91 91 ACTION_KEEP = b'k'
92 92 ACTION_EXEC = b'e'
93 93 ACTION_CREATED_MERGE = b'cm'
94 94
95 95
96 96 class mergestate(object):
97 97 '''track 3-way merge state of individual files
98 98
99 99 The merge state is stored on disk when needed. Two files are used: one with
100 100 an old format (version 1), and one with a new format (version 2). Version 2
101 101 stores a superset of the data in version 1, including new kinds of records
102 102 in the future. For more about the new format, see the documentation for
103 103 `_readrecordsv2`.
104 104
105 105 Each record can contain arbitrary content, and has an associated type. This
106 106 `type` should be a letter. If `type` is uppercase, the record is mandatory:
107 107 versions of Mercurial that don't support it should abort. If `type` is
108 108 lowercase, the record can be safely ignored.
109 109
110 110 Currently known records:
111 111
112 112 L: the node of the "local" part of the merge (hexified version)
113 113 O: the node of the "other" part of the merge (hexified version)
114 114 F: a file to be merged entry
115 115 C: a change/delete or delete/change conflict
116 116 D: a file that the external merge driver will merge internally
117 117 (experimental)
118 118 P: a path conflict (file vs directory)
119 119 m: the external merge driver defined for this merge plus its run state
120 120 (experimental)
121 121 f: a (filename, dictionary) tuple of optional values for a given file
122 122 X: unsupported mandatory record type (used in tests)
123 123 x: unsupported advisory record type (used in tests)
124 124 l: the labels for the parts of the merge.
125 125
126 126 Merge driver run states (experimental):
127 127 u: driver-resolved files unmarked -- needs to be run next time we're about
128 128 to resolve or commit
129 129 m: driver-resolved files marked -- only needs to be run before commit
130 130 s: success/skipped -- does not need to be run any more
131 131
132 132 Merge record states (stored in self._state, indexed by filename):
133 133 u: unresolved conflict
134 134 r: resolved conflict
135 135 pu: unresolved path conflict (file conflicts with directory)
136 136 pr: resolved path conflict
137 137 d: driver-resolved conflict
138 138
139 139 The resolve command transitions between 'u' and 'r' for conflicts and
140 140 'pu' and 'pr' for path conflicts.
141 141 '''
142 142
143 143 statepathv1 = b'merge/state'
144 144 statepathv2 = b'merge/state2'
145 145
146 146 @staticmethod
147 147 def clean(repo, node=None, other=None, labels=None):
148 148 """Initialize a brand new merge state, removing any existing state on
149 149 disk."""
150 150 ms = mergestate(repo)
151 151 ms.reset(node, other, labels)
152 152 return ms
153 153
154 154 @staticmethod
155 155 def read(repo):
156 156 """Initialize the merge state, reading it from disk."""
157 157 ms = mergestate(repo)
158 158 ms._read()
159 159 return ms
160 160
161 161 def __init__(self, repo):
162 162 """Initialize the merge state.
163 163
164 164 Do not use this directly! Instead call read() or clean()."""
165 165 self._repo = repo
166 166 self._dirty = False
167 167 self._labels = None
168 168
169 169 def reset(self, node=None, other=None, labels=None):
170 170 self._state = {}
171 171 self._stateextras = {}
172 172 self._local = None
173 173 self._other = None
174 174 self._labels = labels
175 175 for var in ('localctx', 'otherctx'):
176 176 if var in vars(self):
177 177 delattr(self, var)
178 178 if node:
179 179 self._local = node
180 180 self._other = other
181 181 self._readmergedriver = None
182 182 if self.mergedriver:
183 183 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
184 184 else:
185 185 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
186 186 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
187 187 self._results = {}
188 188 self._dirty = False
189 189
190 190 def _read(self):
191 191 """Analyse each record content to restore a serialized state from disk
192 192
193 193 This function process "record" entry produced by the de-serialization
194 194 of on disk file.
195 195 """
196 196 self._state = {}
197 197 self._stateextras = {}
198 198 self._local = None
199 199 self._other = None
200 200 for var in ('localctx', 'otherctx'):
201 201 if var in vars(self):
202 202 delattr(self, var)
203 203 self._readmergedriver = None
204 204 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
205 205 unsupported = set()
206 206 records = self._readrecords()
207 207 for rtype, record in records:
208 208 if rtype == RECORD_LOCAL:
209 209 self._local = bin(record)
210 210 elif rtype == RECORD_OTHER:
211 211 self._other = bin(record)
212 212 elif rtype == RECORD_MERGE_DRIVER_STATE:
213 213 bits = record.split(b'\0', 1)
214 214 mdstate = bits[1]
215 215 if len(mdstate) != 1 or mdstate not in (
216 216 MERGE_DRIVER_STATE_UNMARKED,
217 217 MERGE_DRIVER_STATE_MARKED,
218 218 MERGE_DRIVER_STATE_SUCCESS,
219 219 ):
220 220 # the merge driver should be idempotent, so just rerun it
221 221 mdstate = MERGE_DRIVER_STATE_UNMARKED
222 222
223 223 self._readmergedriver = bits[0]
224 224 self._mdstate = mdstate
225 225 elif rtype in (
226 226 RECORD_MERGED,
227 227 RECORD_CHANGEDELETE_CONFLICT,
228 228 RECORD_PATH_CONFLICT,
229 229 RECORD_MERGE_DRIVER_MERGE,
230 230 ):
231 231 bits = record.split(b'\0')
232 232 self._state[bits[0]] = bits[1:]
233 233 elif rtype == RECORD_FILE_VALUES:
234 234 filename, rawextras = record.split(b'\0', 1)
235 235 extraparts = rawextras.split(b'\0')
236 236 extras = {}
237 237 i = 0
238 238 while i < len(extraparts):
239 239 extras[extraparts[i]] = extraparts[i + 1]
240 240 i += 2
241 241
242 242 self._stateextras[filename] = extras
243 243 elif rtype == RECORD_LABELS:
244 244 labels = record.split(b'\0', 2)
245 245 self._labels = [l for l in labels if len(l) > 0]
246 246 elif not rtype.islower():
247 247 unsupported.add(rtype)
248 248 self._results = {}
249 249 self._dirty = False
250 250
251 251 if unsupported:
252 252 raise error.UnsupportedMergeRecords(unsupported)
253 253
254 254 def _readrecords(self):
255 255 """Read merge state from disk and return a list of record (TYPE, data)
256 256
257 257 We read data from both v1 and v2 files and decide which one to use.
258 258
259 259 V1 has been used by version prior to 2.9.1 and contains less data than
260 260 v2. We read both versions and check if no data in v2 contradicts
261 261 v1. If there is not contradiction we can safely assume that both v1
262 262 and v2 were written at the same time and use the extract data in v2. If
263 263 there is contradiction we ignore v2 content as we assume an old version
264 264 of Mercurial has overwritten the mergestate file and left an old v2
265 265 file around.
266 266
267 267 returns list of record [(TYPE, data), ...]"""
268 268 v1records = self._readrecordsv1()
269 269 v2records = self._readrecordsv2()
270 270 if self._v1v2match(v1records, v2records):
271 271 return v2records
272 272 else:
273 273 # v1 file is newer than v2 file, use it
274 274 # we have to infer the "other" changeset of the merge
275 275 # we cannot do better than that with v1 of the format
276 276 mctx = self._repo[None].parents()[-1]
277 277 v1records.append((RECORD_OTHER, mctx.hex()))
278 278 # add place holder "other" file node information
279 279 # nobody is using it yet so we do no need to fetch the data
280 280 # if mctx was wrong `mctx[bits[-2]]` may fails.
281 281 for idx, r in enumerate(v1records):
282 282 if r[0] == RECORD_MERGED:
283 283 bits = r[1].split(b'\0')
284 284 bits.insert(-2, b'')
285 285 v1records[idx] = (r[0], b'\0'.join(bits))
286 286 return v1records
287 287
288 288 def _v1v2match(self, v1records, v2records):
289 289 oldv2 = set() # old format version of v2 record
290 290 for rec in v2records:
291 291 if rec[0] == RECORD_LOCAL:
292 292 oldv2.add(rec)
293 293 elif rec[0] == RECORD_MERGED:
294 294 # drop the onode data (not contained in v1)
295 295 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
296 296 for rec in v1records:
297 297 if rec not in oldv2:
298 298 return False
299 299 else:
300 300 return True
301 301
302 302 def _readrecordsv1(self):
303 303 """read on disk merge state for version 1 file
304 304
305 305 returns list of record [(TYPE, data), ...]
306 306
307 307 Note: the "F" data from this file are one entry short
308 308 (no "other file node" entry)
309 309 """
310 310 records = []
311 311 try:
312 312 f = self._repo.vfs(self.statepathv1)
313 313 for i, l in enumerate(f):
314 314 if i == 0:
315 315 records.append((RECORD_LOCAL, l[:-1]))
316 316 else:
317 317 records.append((RECORD_MERGED, l[:-1]))
318 318 f.close()
319 319 except IOError as err:
320 320 if err.errno != errno.ENOENT:
321 321 raise
322 322 return records
323 323
324 324 def _readrecordsv2(self):
325 325 """read on disk merge state for version 2 file
326 326
327 327 This format is a list of arbitrary records of the form:
328 328
329 329 [type][length][content]
330 330
331 331 `type` is a single character, `length` is a 4 byte integer, and
332 332 `content` is an arbitrary byte sequence of length `length`.
333 333
334 334 Mercurial versions prior to 3.7 have a bug where if there are
335 335 unsupported mandatory merge records, attempting to clear out the merge
336 336 state with hg update --clean or similar aborts. The 't' record type
337 337 works around that by writing out what those versions treat as an
338 338 advisory record, but later versions interpret as special: the first
339 339 character is the 'real' record type and everything onwards is the data.
340 340
341 341 Returns list of records [(TYPE, data), ...]."""
342 342 records = []
343 343 try:
344 344 f = self._repo.vfs(self.statepathv2)
345 345 data = f.read()
346 346 off = 0
347 347 end = len(data)
348 348 while off < end:
349 349 rtype = data[off : off + 1]
350 350 off += 1
351 351 length = _unpack(b'>I', data[off : (off + 4)])[0]
352 352 off += 4
353 353 record = data[off : (off + length)]
354 354 off += length
355 355 if rtype == RECORD_OVERRIDE:
356 356 rtype, record = record[0:1], record[1:]
357 357 records.append((rtype, record))
358 358 f.close()
359 359 except IOError as err:
360 360 if err.errno != errno.ENOENT:
361 361 raise
362 362 return records
363 363
364 364 @util.propertycache
365 365 def mergedriver(self):
366 366 # protect against the following:
367 367 # - A configures a malicious merge driver in their hgrc, then
368 368 # pauses the merge
369 369 # - A edits their hgrc to remove references to the merge driver
370 370 # - A gives a copy of their entire repo, including .hg, to B
371 371 # - B inspects .hgrc and finds it to be clean
372 372 # - B then continues the merge and the malicious merge driver
373 373 # gets invoked
374 374 configmergedriver = self._repo.ui.config(
375 375 b'experimental', b'mergedriver'
376 376 )
377 377 if (
378 378 self._readmergedriver is not None
379 379 and self._readmergedriver != configmergedriver
380 380 ):
381 381 raise error.ConfigError(
382 382 _(b"merge driver changed since merge started"),
383 383 hint=_(b"revert merge driver change or abort merge"),
384 384 )
385 385
386 386 return configmergedriver
387 387
388 388 @util.propertycache
389 def localctx(self):
389 def local(self):
390 390 if self._local is None:
391 msg = b"localctx accessed but self._local isn't set"
391 msg = b"local accessed but self._local isn't set"
392 392 raise error.ProgrammingError(msg)
393 return self._repo[self._local]
393 return self._local
394
395 @util.propertycache
396 def localctx(self):
397 return self._repo[self.local]
398
399 @util.propertycache
400 def other(self):
401 if self._other is None:
402 msg = b"other accessed but self._other isn't set"
403 raise error.ProgrammingError(msg)
404 return self._other
394 405
395 406 @util.propertycache
396 407 def otherctx(self):
397 if self._other is None:
398 msg = b"otherctx accessed but self._other isn't set"
399 raise error.ProgrammingError(msg)
400 return self._repo[self._other]
408 return self._repo[self.other]
401 409
402 410 def active(self):
403 411 """Whether mergestate is active.
404 412
405 413 Returns True if there appears to be mergestate. This is a rough proxy
406 414 for "is a merge in progress."
407 415 """
408 416 # Check local variables before looking at filesystem for performance
409 417 # reasons.
410 418 return (
411 419 bool(self._local)
412 420 or bool(self._state)
413 421 or self._repo.vfs.exists(self.statepathv1)
414 422 or self._repo.vfs.exists(self.statepathv2)
415 423 )
416 424
417 425 def commit(self):
418 426 """Write current state on disk (if necessary)"""
419 427 if self._dirty:
420 428 records = self._makerecords()
421 429 self._writerecords(records)
422 430 self._dirty = False
423 431
424 432 def _makerecords(self):
425 433 records = []
426 434 records.append((RECORD_LOCAL, hex(self._local)))
427 435 records.append((RECORD_OTHER, hex(self._other)))
428 436 if self.mergedriver:
429 437 records.append(
430 438 (
431 439 RECORD_MERGE_DRIVER_STATE,
432 440 b'\0'.join([self.mergedriver, self._mdstate]),
433 441 )
434 442 )
435 443 # Write out state items. In all cases, the value of the state map entry
436 444 # is written as the contents of the record. The record type depends on
437 445 # the type of state that is stored, and capital-letter records are used
438 446 # to prevent older versions of Mercurial that do not support the feature
439 447 # from loading them.
440 448 for filename, v in pycompat.iteritems(self._state):
441 449 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
442 450 # Driver-resolved merge. These are stored in 'D' records.
443 451 records.append(
444 452 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
445 453 )
446 454 elif v[0] in (
447 455 MERGE_RECORD_UNRESOLVED_PATH,
448 456 MERGE_RECORD_RESOLVED_PATH,
449 457 ):
450 458 # Path conflicts. These are stored in 'P' records. The current
451 459 # resolution state ('pu' or 'pr') is stored within the record.
452 460 records.append(
453 461 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
454 462 )
455 463 elif v[1] == nullhex or v[6] == nullhex:
456 464 # Change/Delete or Delete/Change conflicts. These are stored in
457 465 # 'C' records. v[1] is the local file, and is nullhex when the
458 466 # file is deleted locally ('dc'). v[6] is the remote file, and
459 467 # is nullhex when the file is deleted remotely ('cd').
460 468 records.append(
461 469 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
462 470 )
463 471 else:
464 472 # Normal files. These are stored in 'F' records.
465 473 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
466 474 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
467 475 rawextras = b'\0'.join(
468 476 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
469 477 )
470 478 records.append(
471 479 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
472 480 )
473 481 if self._labels is not None:
474 482 labels = b'\0'.join(self._labels)
475 483 records.append((RECORD_LABELS, labels))
476 484 return records
477 485
478 486 def _writerecords(self, records):
479 487 """Write current state on disk (both v1 and v2)"""
480 488 self._writerecordsv1(records)
481 489 self._writerecordsv2(records)
482 490
483 491 def _writerecordsv1(self, records):
484 492 """Write current state on disk in a version 1 file"""
485 493 f = self._repo.vfs(self.statepathv1, b'wb')
486 494 irecords = iter(records)
487 495 lrecords = next(irecords)
488 496 assert lrecords[0] == RECORD_LOCAL
489 497 f.write(hex(self._local) + b'\n')
490 498 for rtype, data in irecords:
491 499 if rtype == RECORD_MERGED:
492 500 f.write(b'%s\n' % _droponode(data))
493 501 f.close()
494 502
495 503 def _writerecordsv2(self, records):
496 504 """Write current state on disk in a version 2 file
497 505
498 506 See the docstring for _readrecordsv2 for why we use 't'."""
499 507 # these are the records that all version 2 clients can read
500 508 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
501 509 f = self._repo.vfs(self.statepathv2, b'wb')
502 510 for key, data in records:
503 511 assert len(key) == 1
504 512 if key not in allowlist:
505 513 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
506 514 format = b'>sI%is' % len(data)
507 515 f.write(_pack(format, key, len(data), data))
508 516 f.close()
509 517
510 518 @staticmethod
511 519 def getlocalkey(path):
512 520 """hash the path of a local file context for storage in the .hg/merge
513 521 directory."""
514 522
515 523 return hex(hashutil.sha1(path).digest())
516 524
517 525 def add(self, fcl, fco, fca, fd):
518 526 """add a new (potentially?) conflicting file the merge state
519 527 fcl: file context for local,
520 528 fco: file context for remote,
521 529 fca: file context for ancestors,
522 530 fd: file path of the resulting merge.
523 531
524 532 note: also write the local version to the `.hg/merge` directory.
525 533 """
526 534 if fcl.isabsent():
527 535 localkey = nullhex
528 536 else:
529 537 localkey = mergestate.getlocalkey(fcl.path())
530 538 self._repo.vfs.write(b'merge/' + localkey, fcl.data())
531 539 self._state[fd] = [
532 540 MERGE_RECORD_UNRESOLVED,
533 541 localkey,
534 542 fcl.path(),
535 543 fca.path(),
536 544 hex(fca.filenode()),
537 545 fco.path(),
538 546 hex(fco.filenode()),
539 547 fcl.flags(),
540 548 ]
541 549 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
542 550 self._dirty = True
543 551
544 552 def addpath(self, path, frename, forigin):
545 553 """add a new conflicting path to the merge state
546 554 path: the path that conflicts
547 555 frename: the filename the conflicting file was renamed to
548 556 forigin: origin of the file ('l' or 'r' for local/remote)
549 557 """
550 558 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
551 559 self._dirty = True
552 560
553 561 def __contains__(self, dfile):
554 562 return dfile in self._state
555 563
556 564 def __getitem__(self, dfile):
557 565 return self._state[dfile][0]
558 566
559 567 def __iter__(self):
560 568 return iter(sorted(self._state))
561 569
562 570 def files(self):
563 571 return self._state.keys()
564 572
565 573 def mark(self, dfile, state):
566 574 self._state[dfile][0] = state
567 575 self._dirty = True
568 576
569 577 def mdstate(self):
570 578 return self._mdstate
571 579
572 580 def unresolved(self):
573 581 """Obtain the paths of unresolved files."""
574 582
575 583 for f, entry in pycompat.iteritems(self._state):
576 584 if entry[0] in (
577 585 MERGE_RECORD_UNRESOLVED,
578 586 MERGE_RECORD_UNRESOLVED_PATH,
579 587 ):
580 588 yield f
581 589
582 590 def driverresolved(self):
583 591 """Obtain the paths of driver-resolved files."""
584 592
585 593 for f, entry in self._state.items():
586 594 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
587 595 yield f
588 596
589 597 def extras(self, filename):
590 598 return self._stateextras.setdefault(filename, {})
591 599
592 600 def _resolve(self, preresolve, dfile, wctx):
593 601 """rerun merge process for file path `dfile`"""
594 602 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
595 603 return True, 0
596 604 stateentry = self._state[dfile]
597 605 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
598 606 octx = self._repo[self._other]
599 607 extras = self.extras(dfile)
600 608 anccommitnode = extras.get(b'ancestorlinknode')
601 609 if anccommitnode:
602 610 actx = self._repo[anccommitnode]
603 611 else:
604 612 actx = None
605 613 fcd = self._filectxorabsent(localkey, wctx, dfile)
606 614 fco = self._filectxorabsent(onode, octx, ofile)
607 615 # TODO: move this to filectxorabsent
608 616 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
609 617 # "premerge" x flags
610 618 flo = fco.flags()
611 619 fla = fca.flags()
612 620 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
613 621 if fca.node() == nullid and flags != flo:
614 622 if preresolve:
615 623 self._repo.ui.warn(
616 624 _(
617 625 b'warning: cannot merge flags for %s '
618 626 b'without common ancestor - keeping local flags\n'
619 627 )
620 628 % afile
621 629 )
622 630 elif flags == fla:
623 631 flags = flo
624 632 if preresolve:
625 633 # restore local
626 634 if localkey != nullhex:
627 635 f = self._repo.vfs(b'merge/' + localkey)
628 636 wctx[dfile].write(f.read(), flags)
629 637 f.close()
630 638 else:
631 639 wctx[dfile].remove(ignoremissing=True)
632 640 complete, r, deleted = filemerge.premerge(
633 641 self._repo,
634 642 wctx,
635 643 self._local,
636 644 lfile,
637 645 fcd,
638 646 fco,
639 647 fca,
640 648 labels=self._labels,
641 649 )
642 650 else:
643 651 complete, r, deleted = filemerge.filemerge(
644 652 self._repo,
645 653 wctx,
646 654 self._local,
647 655 lfile,
648 656 fcd,
649 657 fco,
650 658 fca,
651 659 labels=self._labels,
652 660 )
653 661 if r is None:
654 662 # no real conflict
655 663 del self._state[dfile]
656 664 self._stateextras.pop(dfile, None)
657 665 self._dirty = True
658 666 elif not r:
659 667 self.mark(dfile, MERGE_RECORD_RESOLVED)
660 668
661 669 if complete:
662 670 action = None
663 671 if deleted:
664 672 if fcd.isabsent():
665 673 # dc: local picked. Need to drop if present, which may
666 674 # happen on re-resolves.
667 675 action = ACTION_FORGET
668 676 else:
669 677 # cd: remote picked (or otherwise deleted)
670 678 action = ACTION_REMOVE
671 679 else:
672 680 if fcd.isabsent(): # dc: remote picked
673 681 action = ACTION_GET
674 682 elif fco.isabsent(): # cd: local picked
675 683 if dfile in self.localctx:
676 684 action = ACTION_ADD_MODIFIED
677 685 else:
678 686 action = ACTION_ADD
679 687 # else: regular merges (no action necessary)
680 688 self._results[dfile] = r, action
681 689
682 690 return complete, r
683 691
684 692 def _filectxorabsent(self, hexnode, ctx, f):
685 693 if hexnode == nullhex:
686 694 return filemerge.absentfilectx(ctx, f)
687 695 else:
688 696 return ctx[f]
689 697
690 698 def preresolve(self, dfile, wctx):
691 699 """run premerge process for dfile
692 700
693 701 Returns whether the merge is complete, and the exit code."""
694 702 return self._resolve(True, dfile, wctx)
695 703
696 704 def resolve(self, dfile, wctx):
697 705 """run merge process (assuming premerge was run) for dfile
698 706
699 707 Returns the exit code of the merge."""
700 708 return self._resolve(False, dfile, wctx)[1]
701 709
702 710 def counts(self):
703 711 """return counts for updated, merged and removed files in this
704 712 session"""
705 713 updated, merged, removed = 0, 0, 0
706 714 for r, action in pycompat.itervalues(self._results):
707 715 if r is None:
708 716 updated += 1
709 717 elif r == 0:
710 718 if action == ACTION_REMOVE:
711 719 removed += 1
712 720 else:
713 721 merged += 1
714 722 return updated, merged, removed
715 723
716 724 def unresolvedcount(self):
717 725 """get unresolved count for this merge (persistent)"""
718 726 return len(list(self.unresolved()))
719 727
720 728 def actions(self):
721 729 """return lists of actions to perform on the dirstate"""
722 730 actions = {
723 731 ACTION_REMOVE: [],
724 732 ACTION_FORGET: [],
725 733 ACTION_ADD: [],
726 734 ACTION_ADD_MODIFIED: [],
727 735 ACTION_GET: [],
728 736 }
729 737 for f, (r, action) in pycompat.iteritems(self._results):
730 738 if action is not None:
731 739 actions[action].append((f, None, b"merge result"))
732 740 return actions
733 741
734 742 def recordactions(self):
735 743 """record remove/add/get actions in the dirstate"""
736 744 branchmerge = self._repo.dirstate.p2() != nullid
737 745 recordupdates(self._repo, self.actions(), branchmerge, None)
738 746
739 747 def queueremove(self, f):
740 748 """queues a file to be removed from the dirstate
741 749
742 750 Meant for use by custom merge drivers."""
743 751 self._results[f] = 0, ACTION_REMOVE
744 752
745 753 def queueadd(self, f):
746 754 """queues a file to be added to the dirstate
747 755
748 756 Meant for use by custom merge drivers."""
749 757 self._results[f] = 0, ACTION_ADD
750 758
751 759 def queueget(self, f):
752 760 """queues a file to be marked modified in the dirstate
753 761
754 762 Meant for use by custom merge drivers."""
755 763 self._results[f] = 0, ACTION_GET
756 764
757 765
758 766 def _getcheckunknownconfig(repo, section, name):
759 767 config = repo.ui.config(section, name)
760 768 valid = [b'abort', b'ignore', b'warn']
761 769 if config not in valid:
762 770 validstr = b', '.join([b"'" + v + b"'" for v in valid])
763 771 raise error.ConfigError(
764 772 _(b"%s.%s not valid ('%s' is none of %s)")
765 773 % (section, name, config, validstr)
766 774 )
767 775 return config
768 776
769 777
770 778 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
771 779 if wctx.isinmemory():
772 780 # Nothing to do in IMM because nothing in the "working copy" can be an
773 781 # unknown file.
774 782 #
775 783 # Note that we should bail out here, not in ``_checkunknownfiles()``,
776 784 # because that function does other useful work.
777 785 return False
778 786
779 787 if f2 is None:
780 788 f2 = f
781 789 return (
782 790 repo.wvfs.audit.check(f)
783 791 and repo.wvfs.isfileorlink(f)
784 792 and repo.dirstate.normalize(f) not in repo.dirstate
785 793 and mctx[f2].cmp(wctx[f])
786 794 )
787 795
788 796
789 797 class _unknowndirschecker(object):
790 798 """
791 799 Look for any unknown files or directories that may have a path conflict
792 800 with a file. If any path prefix of the file exists as a file or link,
793 801 then it conflicts. If the file itself is a directory that contains any
794 802 file that is not tracked, then it conflicts.
795 803
796 804 Returns the shortest path at which a conflict occurs, or None if there is
797 805 no conflict.
798 806 """
799 807
800 808 def __init__(self):
801 809 # A set of paths known to be good. This prevents repeated checking of
802 810 # dirs. It will be updated with any new dirs that are checked and found
803 811 # to be safe.
804 812 self._unknowndircache = set()
805 813
806 814 # A set of paths that are known to be absent. This prevents repeated
807 815 # checking of subdirectories that are known not to exist. It will be
808 816 # updated with any new dirs that are checked and found to be absent.
809 817 self._missingdircache = set()
810 818
811 819 def __call__(self, repo, wctx, f):
812 820 if wctx.isinmemory():
813 821 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
814 822 return False
815 823
816 824 # Check for path prefixes that exist as unknown files.
817 825 for p in reversed(list(pathutil.finddirs(f))):
818 826 if p in self._missingdircache:
819 827 return
820 828 if p in self._unknowndircache:
821 829 continue
822 830 if repo.wvfs.audit.check(p):
823 831 if (
824 832 repo.wvfs.isfileorlink(p)
825 833 and repo.dirstate.normalize(p) not in repo.dirstate
826 834 ):
827 835 return p
828 836 if not repo.wvfs.lexists(p):
829 837 self._missingdircache.add(p)
830 838 return
831 839 self._unknowndircache.add(p)
832 840
833 841 # Check if the file conflicts with a directory containing unknown files.
834 842 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
835 843 # Does the directory contain any files that are not in the dirstate?
836 844 for p, dirs, files in repo.wvfs.walk(f):
837 845 for fn in files:
838 846 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
839 847 relf = repo.dirstate.normalize(relf, isknown=True)
840 848 if relf not in repo.dirstate:
841 849 return f
842 850 return None
843 851
844 852
845 853 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
846 854 """
847 855 Considers any actions that care about the presence of conflicting unknown
848 856 files. For some actions, the result is to abort; for others, it is to
849 857 choose a different action.
850 858 """
851 859 fileconflicts = set()
852 860 pathconflicts = set()
853 861 warnconflicts = set()
854 862 abortconflicts = set()
855 863 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
856 864 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
857 865 pathconfig = repo.ui.configbool(
858 866 b'experimental', b'merge.checkpathconflicts'
859 867 )
860 868 if not force:
861 869
862 870 def collectconflicts(conflicts, config):
863 871 if config == b'abort':
864 872 abortconflicts.update(conflicts)
865 873 elif config == b'warn':
866 874 warnconflicts.update(conflicts)
867 875
868 876 checkunknowndirs = _unknowndirschecker()
869 877 for f, (m, args, msg) in pycompat.iteritems(actions):
870 878 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
871 879 if _checkunknownfile(repo, wctx, mctx, f):
872 880 fileconflicts.add(f)
873 881 elif pathconfig and f not in wctx:
874 882 path = checkunknowndirs(repo, wctx, f)
875 883 if path is not None:
876 884 pathconflicts.add(path)
877 885 elif m == ACTION_LOCAL_DIR_RENAME_GET:
878 886 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
879 887 fileconflicts.add(f)
880 888
881 889 allconflicts = fileconflicts | pathconflicts
882 890 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
883 891 unknownconflicts = allconflicts - ignoredconflicts
884 892 collectconflicts(ignoredconflicts, ignoredconfig)
885 893 collectconflicts(unknownconflicts, unknownconfig)
886 894 else:
887 895 for f, (m, args, msg) in pycompat.iteritems(actions):
888 896 if m == ACTION_CREATED_MERGE:
889 897 fl2, anc = args
890 898 different = _checkunknownfile(repo, wctx, mctx, f)
891 899 if repo.dirstate._ignore(f):
892 900 config = ignoredconfig
893 901 else:
894 902 config = unknownconfig
895 903
896 904 # The behavior when force is True is described by this table:
897 905 # config different mergeforce | action backup
898 906 # * n * | get n
899 907 # * y y | merge -
900 908 # abort y n | merge - (1)
901 909 # warn y n | warn + get y
902 910 # ignore y n | get y
903 911 #
904 912 # (1) this is probably the wrong behavior here -- we should
905 913 # probably abort, but some actions like rebases currently
906 914 # don't like an abort happening in the middle of
907 915 # merge.update.
908 916 if not different:
909 917 actions[f] = (ACTION_GET, (fl2, False), b'remote created')
910 918 elif mergeforce or config == b'abort':
911 919 actions[f] = (
912 920 ACTION_MERGE,
913 921 (f, f, None, False, anc),
914 922 b'remote differs from untracked local',
915 923 )
916 924 elif config == b'abort':
917 925 abortconflicts.add(f)
918 926 else:
919 927 if config == b'warn':
920 928 warnconflicts.add(f)
921 929 actions[f] = (ACTION_GET, (fl2, True), b'remote created')
922 930
923 931 for f in sorted(abortconflicts):
924 932 warn = repo.ui.warn
925 933 if f in pathconflicts:
926 934 if repo.wvfs.isfileorlink(f):
927 935 warn(_(b"%s: untracked file conflicts with directory\n") % f)
928 936 else:
929 937 warn(_(b"%s: untracked directory conflicts with file\n") % f)
930 938 else:
931 939 warn(_(b"%s: untracked file differs\n") % f)
932 940 if abortconflicts:
933 941 raise error.Abort(
934 942 _(
935 943 b"untracked files in working directory "
936 944 b"differ from files in requested revision"
937 945 )
938 946 )
939 947
940 948 for f in sorted(warnconflicts):
941 949 if repo.wvfs.isfileorlink(f):
942 950 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
943 951 else:
944 952 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
945 953
946 954 for f, (m, args, msg) in pycompat.iteritems(actions):
947 955 if m == ACTION_CREATED:
948 956 backup = (
949 957 f in fileconflicts
950 958 or f in pathconflicts
951 959 or any(p in pathconflicts for p in pathutil.finddirs(f))
952 960 )
953 961 (flags,) = args
954 962 actions[f] = (ACTION_GET, (flags, backup), msg)
955 963
956 964
957 965 def _forgetremoved(wctx, mctx, branchmerge):
958 966 """
959 967 Forget removed files
960 968
961 969 If we're jumping between revisions (as opposed to merging), and if
962 970 neither the working directory nor the target rev has the file,
963 971 then we need to remove it from the dirstate, to prevent the
964 972 dirstate from listing the file when it is no longer in the
965 973 manifest.
966 974
967 975 If we're merging, and the other revision has removed a file
968 976 that is not present in the working directory, we need to mark it
969 977 as removed.
970 978 """
971 979
972 980 actions = {}
973 981 m = ACTION_FORGET
974 982 if branchmerge:
975 983 m = ACTION_REMOVE
976 984 for f in wctx.deleted():
977 985 if f not in mctx:
978 986 actions[f] = m, None, b"forget deleted"
979 987
980 988 if not branchmerge:
981 989 for f in wctx.removed():
982 990 if f not in mctx:
983 991 actions[f] = ACTION_FORGET, None, b"forget removed"
984 992
985 993 return actions
986 994
987 995
988 996 def _checkcollision(repo, wmf, actions):
989 997 """
990 998 Check for case-folding collisions.
991 999 """
992 1000
993 1001 # If the repo is narrowed, filter out files outside the narrowspec.
994 1002 narrowmatch = repo.narrowmatch()
995 1003 if not narrowmatch.always():
996 1004 wmf = wmf.matches(narrowmatch)
997 1005 if actions:
998 1006 narrowactions = {}
999 1007 for m, actionsfortype in pycompat.iteritems(actions):
1000 1008 narrowactions[m] = []
1001 1009 for (f, args, msg) in actionsfortype:
1002 1010 if narrowmatch(f):
1003 1011 narrowactions[m].append((f, args, msg))
1004 1012 actions = narrowactions
1005 1013
1006 1014 # build provisional merged manifest up
1007 1015 pmmf = set(wmf)
1008 1016
1009 1017 if actions:
1010 1018 # KEEP and EXEC are no-op
1011 1019 for m in (
1012 1020 ACTION_ADD,
1013 1021 ACTION_ADD_MODIFIED,
1014 1022 ACTION_FORGET,
1015 1023 ACTION_GET,
1016 1024 ACTION_CHANGED_DELETED,
1017 1025 ACTION_DELETED_CHANGED,
1018 1026 ):
1019 1027 for f, args, msg in actions[m]:
1020 1028 pmmf.add(f)
1021 1029 for f, args, msg in actions[ACTION_REMOVE]:
1022 1030 pmmf.discard(f)
1023 1031 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1024 1032 f2, flags = args
1025 1033 pmmf.discard(f2)
1026 1034 pmmf.add(f)
1027 1035 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1028 1036 pmmf.add(f)
1029 1037 for f, args, msg in actions[ACTION_MERGE]:
1030 1038 f1, f2, fa, move, anc = args
1031 1039 if move:
1032 1040 pmmf.discard(f1)
1033 1041 pmmf.add(f)
1034 1042
1035 1043 # check case-folding collision in provisional merged manifest
1036 1044 foldmap = {}
1037 1045 for f in pmmf:
1038 1046 fold = util.normcase(f)
1039 1047 if fold in foldmap:
1040 1048 raise error.Abort(
1041 1049 _(b"case-folding collision between %s and %s")
1042 1050 % (f, foldmap[fold])
1043 1051 )
1044 1052 foldmap[fold] = f
1045 1053
1046 1054 # check case-folding of directories
1047 1055 foldprefix = unfoldprefix = lastfull = b''
1048 1056 for fold, f in sorted(foldmap.items()):
1049 1057 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
1050 1058 # the folded prefix matches but actual casing is different
1051 1059 raise error.Abort(
1052 1060 _(b"case-folding collision between %s and directory of %s")
1053 1061 % (lastfull, f)
1054 1062 )
1055 1063 foldprefix = fold + b'/'
1056 1064 unfoldprefix = f + b'/'
1057 1065 lastfull = f
1058 1066
1059 1067
1060 1068 def driverpreprocess(repo, ms, wctx, labels=None):
1061 1069 """run the preprocess step of the merge driver, if any
1062 1070
1063 1071 This is currently not implemented -- it's an extension point."""
1064 1072 return True
1065 1073
1066 1074
1067 1075 def driverconclude(repo, ms, wctx, labels=None):
1068 1076 """run the conclude step of the merge driver, if any
1069 1077
1070 1078 This is currently not implemented -- it's an extension point."""
1071 1079 return True
1072 1080
1073 1081
1074 1082 def _filesindirs(repo, manifest, dirs):
1075 1083 """
1076 1084 Generator that yields pairs of all the files in the manifest that are found
1077 1085 inside the directories listed in dirs, and which directory they are found
1078 1086 in.
1079 1087 """
1080 1088 for f in manifest:
1081 1089 for p in pathutil.finddirs(f):
1082 1090 if p in dirs:
1083 1091 yield f, p
1084 1092 break
1085 1093
1086 1094
1087 1095 def checkpathconflicts(repo, wctx, mctx, actions):
1088 1096 """
1089 1097 Check if any actions introduce path conflicts in the repository, updating
1090 1098 actions to record or handle the path conflict accordingly.
1091 1099 """
1092 1100 mf = wctx.manifest()
1093 1101
1094 1102 # The set of local files that conflict with a remote directory.
1095 1103 localconflicts = set()
1096 1104
1097 1105 # The set of directories that conflict with a remote file, and so may cause
1098 1106 # conflicts if they still contain any files after the merge.
1099 1107 remoteconflicts = set()
1100 1108
1101 1109 # The set of directories that appear as both a file and a directory in the
1102 1110 # remote manifest. These indicate an invalid remote manifest, which
1103 1111 # can't be updated to cleanly.
1104 1112 invalidconflicts = set()
1105 1113
1106 1114 # The set of directories that contain files that are being created.
1107 1115 createdfiledirs = set()
1108 1116
1109 1117 # The set of files deleted by all the actions.
1110 1118 deletedfiles = set()
1111 1119
1112 1120 for f, (m, args, msg) in actions.items():
1113 1121 if m in (
1114 1122 ACTION_CREATED,
1115 1123 ACTION_DELETED_CHANGED,
1116 1124 ACTION_MERGE,
1117 1125 ACTION_CREATED_MERGE,
1118 1126 ):
1119 1127 # This action may create a new local file.
1120 1128 createdfiledirs.update(pathutil.finddirs(f))
1121 1129 if mf.hasdir(f):
1122 1130 # The file aliases a local directory. This might be ok if all
1123 1131 # the files in the local directory are being deleted. This
1124 1132 # will be checked once we know what all the deleted files are.
1125 1133 remoteconflicts.add(f)
1126 1134 # Track the names of all deleted files.
1127 1135 if m == ACTION_REMOVE:
1128 1136 deletedfiles.add(f)
1129 1137 if m == ACTION_MERGE:
1130 1138 f1, f2, fa, move, anc = args
1131 1139 if move:
1132 1140 deletedfiles.add(f1)
1133 1141 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1134 1142 f2, flags = args
1135 1143 deletedfiles.add(f2)
1136 1144
1137 1145 # Check all directories that contain created files for path conflicts.
1138 1146 for p in createdfiledirs:
1139 1147 if p in mf:
1140 1148 if p in mctx:
1141 1149 # A file is in a directory which aliases both a local
1142 1150 # and a remote file. This is an internal inconsistency
1143 1151 # within the remote manifest.
1144 1152 invalidconflicts.add(p)
1145 1153 else:
1146 1154 # A file is in a directory which aliases a local file.
1147 1155 # We will need to rename the local file.
1148 1156 localconflicts.add(p)
1149 1157 if p in actions and actions[p][0] in (
1150 1158 ACTION_CREATED,
1151 1159 ACTION_DELETED_CHANGED,
1152 1160 ACTION_MERGE,
1153 1161 ACTION_CREATED_MERGE,
1154 1162 ):
1155 1163 # The file is in a directory which aliases a remote file.
1156 1164 # This is an internal inconsistency within the remote
1157 1165 # manifest.
1158 1166 invalidconflicts.add(p)
1159 1167
1160 1168 # Rename all local conflicting files that have not been deleted.
1161 1169 for p in localconflicts:
1162 1170 if p not in deletedfiles:
1163 1171 ctxname = bytes(wctx).rstrip(b'+')
1164 1172 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1165 1173 actions[pnew] = (
1166 1174 ACTION_PATH_CONFLICT_RESOLVE,
1167 1175 (p,),
1168 1176 b'local path conflict',
1169 1177 )
1170 1178 actions[p] = (ACTION_PATH_CONFLICT, (pnew, b'l'), b'path conflict')
1171 1179
1172 1180 if remoteconflicts:
1173 1181 # Check if all files in the conflicting directories have been removed.
1174 1182 ctxname = bytes(mctx).rstrip(b'+')
1175 1183 for f, p in _filesindirs(repo, mf, remoteconflicts):
1176 1184 if f not in deletedfiles:
1177 1185 m, args, msg = actions[p]
1178 1186 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1179 1187 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1180 1188 # Action was merge, just update target.
1181 1189 actions[pnew] = (m, args, msg)
1182 1190 else:
1183 1191 # Action was create, change to renamed get action.
1184 1192 fl = args[0]
1185 1193 actions[pnew] = (
1186 1194 ACTION_LOCAL_DIR_RENAME_GET,
1187 1195 (p, fl),
1188 1196 b'remote path conflict',
1189 1197 )
1190 1198 actions[p] = (
1191 1199 ACTION_PATH_CONFLICT,
1192 1200 (pnew, ACTION_REMOVE),
1193 1201 b'path conflict',
1194 1202 )
1195 1203 remoteconflicts.remove(p)
1196 1204 break
1197 1205
1198 1206 if invalidconflicts:
1199 1207 for p in invalidconflicts:
1200 1208 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
1201 1209 raise error.Abort(_(b"destination manifest contains path conflicts"))
1202 1210
1203 1211
1204 1212 def _filternarrowactions(narrowmatch, branchmerge, actions):
1205 1213 """
1206 1214 Filters out actions that can ignored because the repo is narrowed.
1207 1215
1208 1216 Raise an exception if the merge cannot be completed because the repo is
1209 1217 narrowed.
1210 1218 """
1211 1219 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
1212 1220 nonconflicttypes = set(b'a am c cm f g r e'.split())
1213 1221 # We mutate the items in the dict during iteration, so iterate
1214 1222 # over a copy.
1215 1223 for f, action in list(actions.items()):
1216 1224 if narrowmatch(f):
1217 1225 pass
1218 1226 elif not branchmerge:
1219 1227 del actions[f] # just updating, ignore changes outside clone
1220 1228 elif action[0] in nooptypes:
1221 1229 del actions[f] # merge does not affect file
1222 1230 elif action[0] in nonconflicttypes:
1223 1231 raise error.Abort(
1224 1232 _(
1225 1233 b'merge affects file \'%s\' outside narrow, '
1226 1234 b'which is not yet supported'
1227 1235 )
1228 1236 % f,
1229 1237 hint=_(b'merging in the other direction may work'),
1230 1238 )
1231 1239 else:
1232 1240 raise error.Abort(
1233 1241 _(b'conflict in file \'%s\' is outside narrow clone') % f
1234 1242 )
1235 1243
1236 1244
1237 1245 def manifestmerge(
1238 1246 repo,
1239 1247 wctx,
1240 1248 p2,
1241 1249 pa,
1242 1250 branchmerge,
1243 1251 force,
1244 1252 matcher,
1245 1253 acceptremote,
1246 1254 followcopies,
1247 1255 forcefulldiff=False,
1248 1256 ):
1249 1257 """
1250 1258 Merge wctx and p2 with ancestor pa and generate merge action list
1251 1259
1252 1260 branchmerge and force are as passed in to update
1253 1261 matcher = matcher to filter file lists
1254 1262 acceptremote = accept the incoming changes without prompting
1255 1263 """
1256 1264 if matcher is not None and matcher.always():
1257 1265 matcher = None
1258 1266
1259 1267 # manifests fetched in order are going to be faster, so prime the caches
1260 1268 [
1261 1269 x.manifest()
1262 1270 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
1263 1271 ]
1264 1272
1265 1273 branch_copies1 = copies.branch_copies()
1266 1274 branch_copies2 = copies.branch_copies()
1267 1275 diverge = {}
1268 1276 if followcopies:
1269 1277 branch_copies1, branch_copies2, diverge = copies.mergecopies(
1270 1278 repo, wctx, p2, pa
1271 1279 )
1272 1280
1273 1281 boolbm = pycompat.bytestr(bool(branchmerge))
1274 1282 boolf = pycompat.bytestr(bool(force))
1275 1283 boolm = pycompat.bytestr(bool(matcher))
1276 1284 repo.ui.note(_(b"resolving manifests\n"))
1277 1285 repo.ui.debug(
1278 1286 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
1279 1287 )
1280 1288 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1281 1289
1282 1290 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1283 1291 copied1 = set(branch_copies1.copy.values())
1284 1292 copied1.update(branch_copies1.movewithdir.values())
1285 1293 copied2 = set(branch_copies2.copy.values())
1286 1294 copied2.update(branch_copies2.movewithdir.values())
1287 1295
1288 1296 if b'.hgsubstate' in m1 and wctx.rev() is None:
1289 1297 # Check whether sub state is modified, and overwrite the manifest
1290 1298 # to flag the change. If wctx is a committed revision, we shouldn't
1291 1299 # care for the dirty state of the working directory.
1292 1300 if any(wctx.sub(s).dirty() for s in wctx.substate):
1293 1301 m1[b'.hgsubstate'] = modifiednodeid
1294 1302
1295 1303 # Don't use m2-vs-ma optimization if:
1296 1304 # - ma is the same as m1 or m2, which we're just going to diff again later
1297 1305 # - The caller specifically asks for a full diff, which is useful during bid
1298 1306 # merge.
1299 1307 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
1300 1308 # Identify which files are relevant to the merge, so we can limit the
1301 1309 # total m1-vs-m2 diff to just those files. This has significant
1302 1310 # performance benefits in large repositories.
1303 1311 relevantfiles = set(ma.diff(m2).keys())
1304 1312
1305 1313 # For copied and moved files, we need to add the source file too.
1306 1314 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
1307 1315 if copyvalue in relevantfiles:
1308 1316 relevantfiles.add(copykey)
1309 1317 for movedirkey in branch_copies1.movewithdir:
1310 1318 relevantfiles.add(movedirkey)
1311 1319 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1312 1320 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1313 1321
1314 1322 diff = m1.diff(m2, match=matcher)
1315 1323
1316 1324 actions = {}
1317 1325 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
1318 1326 if n1 and n2: # file exists on both local and remote side
1319 1327 if f not in ma:
1320 1328 # TODO: what if they're renamed from different sources?
1321 1329 fa = branch_copies1.copy.get(
1322 1330 f, None
1323 1331 ) or branch_copies2.copy.get(f, None)
1324 1332 if fa is not None:
1325 1333 actions[f] = (
1326 1334 ACTION_MERGE,
1327 1335 (f, f, fa, False, pa.node()),
1328 1336 b'both renamed from %s' % fa,
1329 1337 )
1330 1338 else:
1331 1339 actions[f] = (
1332 1340 ACTION_MERGE,
1333 1341 (f, f, None, False, pa.node()),
1334 1342 b'both created',
1335 1343 )
1336 1344 else:
1337 1345 a = ma[f]
1338 1346 fla = ma.flags(f)
1339 1347 nol = b'l' not in fl1 + fl2 + fla
1340 1348 if n2 == a and fl2 == fla:
1341 1349 actions[f] = (ACTION_KEEP, (), b'remote unchanged')
1342 1350 elif n1 == a and fl1 == fla: # local unchanged - use remote
1343 1351 if n1 == n2: # optimization: keep local content
1344 1352 actions[f] = (
1345 1353 ACTION_EXEC,
1346 1354 (fl2,),
1347 1355 b'update permissions',
1348 1356 )
1349 1357 else:
1350 1358 actions[f] = (
1351 1359 ACTION_GET,
1352 1360 (fl2, False),
1353 1361 b'remote is newer',
1354 1362 )
1355 1363 elif nol and n2 == a: # remote only changed 'x'
1356 1364 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions')
1357 1365 elif nol and n1 == a: # local only changed 'x'
1358 1366 actions[f] = (ACTION_GET, (fl1, False), b'remote is newer')
1359 1367 else: # both changed something
1360 1368 actions[f] = (
1361 1369 ACTION_MERGE,
1362 1370 (f, f, f, False, pa.node()),
1363 1371 b'versions differ',
1364 1372 )
1365 1373 elif n1: # file exists only on local side
1366 1374 if f in copied2:
1367 1375 pass # we'll deal with it on m2 side
1368 1376 elif (
1369 1377 f in branch_copies1.movewithdir
1370 1378 ): # directory rename, move local
1371 1379 f2 = branch_copies1.movewithdir[f]
1372 1380 if f2 in m2:
1373 1381 actions[f2] = (
1374 1382 ACTION_MERGE,
1375 1383 (f, f2, None, True, pa.node()),
1376 1384 b'remote directory rename, both created',
1377 1385 )
1378 1386 else:
1379 1387 actions[f2] = (
1380 1388 ACTION_DIR_RENAME_MOVE_LOCAL,
1381 1389 (f, fl1),
1382 1390 b'remote directory rename - move from %s' % f,
1383 1391 )
1384 1392 elif f in branch_copies1.copy:
1385 1393 f2 = branch_copies1.copy[f]
1386 1394 actions[f] = (
1387 1395 ACTION_MERGE,
1388 1396 (f, f2, f2, False, pa.node()),
1389 1397 b'local copied/moved from %s' % f2,
1390 1398 )
1391 1399 elif f in ma: # clean, a different, no remote
1392 1400 if n1 != ma[f]:
1393 1401 if acceptremote:
1394 1402 actions[f] = (ACTION_REMOVE, None, b'remote delete')
1395 1403 else:
1396 1404 actions[f] = (
1397 1405 ACTION_CHANGED_DELETED,
1398 1406 (f, None, f, False, pa.node()),
1399 1407 b'prompt changed/deleted',
1400 1408 )
1401 1409 elif n1 == addednodeid:
1402 1410 # This extra 'a' is added by working copy manifest to mark
1403 1411 # the file as locally added. We should forget it instead of
1404 1412 # deleting it.
1405 1413 actions[f] = (ACTION_FORGET, None, b'remote deleted')
1406 1414 else:
1407 1415 actions[f] = (ACTION_REMOVE, None, b'other deleted')
1408 1416 elif n2: # file exists only on remote side
1409 1417 if f in copied1:
1410 1418 pass # we'll deal with it on m1 side
1411 1419 elif f in branch_copies2.movewithdir:
1412 1420 f2 = branch_copies2.movewithdir[f]
1413 1421 if f2 in m1:
1414 1422 actions[f2] = (
1415 1423 ACTION_MERGE,
1416 1424 (f2, f, None, False, pa.node()),
1417 1425 b'local directory rename, both created',
1418 1426 )
1419 1427 else:
1420 1428 actions[f2] = (
1421 1429 ACTION_LOCAL_DIR_RENAME_GET,
1422 1430 (f, fl2),
1423 1431 b'local directory rename - get from %s' % f,
1424 1432 )
1425 1433 elif f in branch_copies2.copy:
1426 1434 f2 = branch_copies2.copy[f]
1427 1435 if f2 in m2:
1428 1436 actions[f] = (
1429 1437 ACTION_MERGE,
1430 1438 (f2, f, f2, False, pa.node()),
1431 1439 b'remote copied from %s' % f2,
1432 1440 )
1433 1441 else:
1434 1442 actions[f] = (
1435 1443 ACTION_MERGE,
1436 1444 (f2, f, f2, True, pa.node()),
1437 1445 b'remote moved from %s' % f2,
1438 1446 )
1439 1447 elif f not in ma:
1440 1448 # local unknown, remote created: the logic is described by the
1441 1449 # following table:
1442 1450 #
1443 1451 # force branchmerge different | action
1444 1452 # n * * | create
1445 1453 # y n * | create
1446 1454 # y y n | create
1447 1455 # y y y | merge
1448 1456 #
1449 1457 # Checking whether the files are different is expensive, so we
1450 1458 # don't do that when we can avoid it.
1451 1459 if not force:
1452 1460 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1453 1461 elif not branchmerge:
1454 1462 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1455 1463 else:
1456 1464 actions[f] = (
1457 1465 ACTION_CREATED_MERGE,
1458 1466 (fl2, pa.node()),
1459 1467 b'remote created, get or merge',
1460 1468 )
1461 1469 elif n2 != ma[f]:
1462 1470 df = None
1463 1471 for d in branch_copies1.dirmove:
1464 1472 if f.startswith(d):
1465 1473 # new file added in a directory that was moved
1466 1474 df = branch_copies1.dirmove[d] + f[len(d) :]
1467 1475 break
1468 1476 if df is not None and df in m1:
1469 1477 actions[df] = (
1470 1478 ACTION_MERGE,
1471 1479 (df, f, f, False, pa.node()),
1472 1480 b'local directory rename - respect move '
1473 1481 b'from %s' % f,
1474 1482 )
1475 1483 elif acceptremote:
1476 1484 actions[f] = (ACTION_CREATED, (fl2,), b'remote recreating')
1477 1485 else:
1478 1486 actions[f] = (
1479 1487 ACTION_DELETED_CHANGED,
1480 1488 (None, f, f, False, pa.node()),
1481 1489 b'prompt deleted/changed',
1482 1490 )
1483 1491
1484 1492 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1485 1493 # If we are merging, look for path conflicts.
1486 1494 checkpathconflicts(repo, wctx, p2, actions)
1487 1495
1488 1496 narrowmatch = repo.narrowmatch()
1489 1497 if not narrowmatch.always():
1490 1498 # Updates "actions" in place
1491 1499 _filternarrowactions(narrowmatch, branchmerge, actions)
1492 1500
1493 1501 renamedelete = branch_copies1.renamedelete
1494 1502 renamedelete.update(branch_copies2.renamedelete)
1495 1503
1496 1504 return actions, diverge, renamedelete
1497 1505
1498 1506
1499 1507 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1500 1508 """Resolves false conflicts where the nodeid changed but the content
1501 1509 remained the same."""
1502 1510 # We force a copy of actions.items() because we're going to mutate
1503 1511 # actions as we resolve trivial conflicts.
1504 1512 for f, (m, args, msg) in list(actions.items()):
1505 1513 if (
1506 1514 m == ACTION_CHANGED_DELETED
1507 1515 and f in ancestor
1508 1516 and not wctx[f].cmp(ancestor[f])
1509 1517 ):
1510 1518 # local did change but ended up with same content
1511 1519 actions[f] = ACTION_REMOVE, None, b'prompt same'
1512 1520 elif (
1513 1521 m == ACTION_DELETED_CHANGED
1514 1522 and f in ancestor
1515 1523 and not mctx[f].cmp(ancestor[f])
1516 1524 ):
1517 1525 # remote did change but ended up with same content
1518 1526 del actions[f] # don't get = keep local deleted
1519 1527
1520 1528
1521 1529 def calculateupdates(
1522 1530 repo,
1523 1531 wctx,
1524 1532 mctx,
1525 1533 ancestors,
1526 1534 branchmerge,
1527 1535 force,
1528 1536 acceptremote,
1529 1537 followcopies,
1530 1538 matcher=None,
1531 1539 mergeforce=False,
1532 1540 ):
1533 1541 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1534 1542 # Avoid cycle.
1535 1543 from . import sparse
1536 1544
1537 1545 if len(ancestors) == 1: # default
1538 1546 actions, diverge, renamedelete = manifestmerge(
1539 1547 repo,
1540 1548 wctx,
1541 1549 mctx,
1542 1550 ancestors[0],
1543 1551 branchmerge,
1544 1552 force,
1545 1553 matcher,
1546 1554 acceptremote,
1547 1555 followcopies,
1548 1556 )
1549 1557 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1550 1558
1551 1559 else: # only when merge.preferancestor=* - the default
1552 1560 repo.ui.note(
1553 1561 _(b"note: merging %s and %s using bids from ancestors %s\n")
1554 1562 % (
1555 1563 wctx,
1556 1564 mctx,
1557 1565 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1558 1566 )
1559 1567 )
1560 1568
1561 1569 # Call for bids
1562 1570 fbids = (
1563 1571 {}
1564 1572 ) # mapping filename to bids (action method to list af actions)
1565 1573 diverge, renamedelete = None, None
1566 1574 for ancestor in ancestors:
1567 1575 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1568 1576 actions, diverge1, renamedelete1 = manifestmerge(
1569 1577 repo,
1570 1578 wctx,
1571 1579 mctx,
1572 1580 ancestor,
1573 1581 branchmerge,
1574 1582 force,
1575 1583 matcher,
1576 1584 acceptremote,
1577 1585 followcopies,
1578 1586 forcefulldiff=True,
1579 1587 )
1580 1588 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1581 1589
1582 1590 # Track the shortest set of warning on the theory that bid
1583 1591 # merge will correctly incorporate more information
1584 1592 if diverge is None or len(diverge1) < len(diverge):
1585 1593 diverge = diverge1
1586 1594 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1587 1595 renamedelete = renamedelete1
1588 1596
1589 1597 for f, a in sorted(pycompat.iteritems(actions)):
1590 1598 m, args, msg = a
1591 1599 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1592 1600 if f in fbids:
1593 1601 d = fbids[f]
1594 1602 if m in d:
1595 1603 d[m].append(a)
1596 1604 else:
1597 1605 d[m] = [a]
1598 1606 else:
1599 1607 fbids[f] = {m: [a]}
1600 1608
1601 1609 # Pick the best bid for each file
1602 1610 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1603 1611 actions = {}
1604 1612 for f, bids in sorted(fbids.items()):
1605 1613 # bids is a mapping from action method to list af actions
1606 1614 # Consensus?
1607 1615 if len(bids) == 1: # all bids are the same kind of method
1608 1616 m, l = list(bids.items())[0]
1609 1617 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1610 1618 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1611 1619 actions[f] = l[0]
1612 1620 continue
1613 1621 # If keep is an option, just do it.
1614 1622 if ACTION_KEEP in bids:
1615 1623 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1616 1624 actions[f] = bids[ACTION_KEEP][0]
1617 1625 continue
1618 1626 # If there are gets and they all agree [how could they not?], do it.
1619 1627 if ACTION_GET in bids:
1620 1628 ga0 = bids[ACTION_GET][0]
1621 1629 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1622 1630 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1623 1631 actions[f] = ga0
1624 1632 continue
1625 1633 # TODO: Consider other simple actions such as mode changes
1626 1634 # Handle inefficient democrazy.
1627 1635 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1628 1636 for m, l in sorted(bids.items()):
1629 1637 for _f, args, msg in l:
1630 1638 repo.ui.note(b' %s -> %s\n' % (msg, m))
1631 1639 # Pick random action. TODO: Instead, prompt user when resolving
1632 1640 m, l = list(bids.items())[0]
1633 1641 repo.ui.warn(
1634 1642 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1635 1643 )
1636 1644 actions[f] = l[0]
1637 1645 continue
1638 1646 repo.ui.note(_(b'end of auction\n\n'))
1639 1647
1640 1648 if wctx.rev() is None:
1641 1649 fractions = _forgetremoved(wctx, mctx, branchmerge)
1642 1650 actions.update(fractions)
1643 1651
1644 1652 prunedactions = sparse.filterupdatesactions(
1645 1653 repo, wctx, mctx, branchmerge, actions
1646 1654 )
1647 1655 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1648 1656
1649 1657 return prunedactions, diverge, renamedelete
1650 1658
1651 1659
1652 1660 def _getcwd():
1653 1661 try:
1654 1662 return encoding.getcwd()
1655 1663 except OSError as err:
1656 1664 if err.errno == errno.ENOENT:
1657 1665 return None
1658 1666 raise
1659 1667
1660 1668
1661 1669 def batchremove(repo, wctx, actions):
1662 1670 """apply removes to the working directory
1663 1671
1664 1672 yields tuples for progress updates
1665 1673 """
1666 1674 verbose = repo.ui.verbose
1667 1675 cwd = _getcwd()
1668 1676 i = 0
1669 1677 for f, args, msg in actions:
1670 1678 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1671 1679 if verbose:
1672 1680 repo.ui.note(_(b"removing %s\n") % f)
1673 1681 wctx[f].audit()
1674 1682 try:
1675 1683 wctx[f].remove(ignoremissing=True)
1676 1684 except OSError as inst:
1677 1685 repo.ui.warn(
1678 1686 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1679 1687 )
1680 1688 if i == 100:
1681 1689 yield i, f
1682 1690 i = 0
1683 1691 i += 1
1684 1692 if i > 0:
1685 1693 yield i, f
1686 1694
1687 1695 if cwd and not _getcwd():
1688 1696 # cwd was removed in the course of removing files; print a helpful
1689 1697 # warning.
1690 1698 repo.ui.warn(
1691 1699 _(
1692 1700 b"current directory was removed\n"
1693 1701 b"(consider changing to repo root: %s)\n"
1694 1702 )
1695 1703 % repo.root
1696 1704 )
1697 1705
1698 1706
1699 1707 def batchget(repo, mctx, wctx, wantfiledata, actions):
1700 1708 """apply gets to the working directory
1701 1709
1702 1710 mctx is the context to get from
1703 1711
1704 1712 Yields arbitrarily many (False, tuple) for progress updates, followed by
1705 1713 exactly one (True, filedata). When wantfiledata is false, filedata is an
1706 1714 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1707 1715 mtime) of the file f written for each action.
1708 1716 """
1709 1717 filedata = {}
1710 1718 verbose = repo.ui.verbose
1711 1719 fctx = mctx.filectx
1712 1720 ui = repo.ui
1713 1721 i = 0
1714 1722 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1715 1723 for f, (flags, backup), msg in actions:
1716 1724 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1717 1725 if verbose:
1718 1726 repo.ui.note(_(b"getting %s\n") % f)
1719 1727
1720 1728 if backup:
1721 1729 # If a file or directory exists with the same name, back that
1722 1730 # up. Otherwise, look to see if there is a file that conflicts
1723 1731 # with a directory this file is in, and if so, back that up.
1724 1732 conflicting = f
1725 1733 if not repo.wvfs.lexists(f):
1726 1734 for p in pathutil.finddirs(f):
1727 1735 if repo.wvfs.isfileorlink(p):
1728 1736 conflicting = p
1729 1737 break
1730 1738 if repo.wvfs.lexists(conflicting):
1731 1739 orig = scmutil.backuppath(ui, repo, conflicting)
1732 1740 util.rename(repo.wjoin(conflicting), orig)
1733 1741 wfctx = wctx[f]
1734 1742 wfctx.clearunknown()
1735 1743 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1736 1744 size = wfctx.write(
1737 1745 fctx(f).data(),
1738 1746 flags,
1739 1747 backgroundclose=True,
1740 1748 atomictemp=atomictemp,
1741 1749 )
1742 1750 if wantfiledata:
1743 1751 s = wfctx.lstat()
1744 1752 mode = s.st_mode
1745 1753 mtime = s[stat.ST_MTIME]
1746 1754 filedata[f] = (mode, size, mtime) # for dirstate.normal
1747 1755 if i == 100:
1748 1756 yield False, (i, f)
1749 1757 i = 0
1750 1758 i += 1
1751 1759 if i > 0:
1752 1760 yield False, (i, f)
1753 1761 yield True, filedata
1754 1762
1755 1763
1756 1764 def _prefetchfiles(repo, ctx, actions):
1757 1765 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1758 1766 of merge actions. ``ctx`` is the context being merged in."""
1759 1767
1760 1768 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1761 1769 # don't touch the context to be merged in. 'cd' is skipped, because
1762 1770 # changed/deleted never resolves to something from the remote side.
1763 1771 oplist = [
1764 1772 actions[a]
1765 1773 for a in (
1766 1774 ACTION_GET,
1767 1775 ACTION_DELETED_CHANGED,
1768 1776 ACTION_LOCAL_DIR_RENAME_GET,
1769 1777 ACTION_MERGE,
1770 1778 )
1771 1779 ]
1772 1780 prefetch = scmutil.prefetchfiles
1773 1781 matchfiles = scmutil.matchfiles
1774 1782 prefetch(
1775 1783 repo,
1776 1784 [ctx.rev()],
1777 1785 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1778 1786 )
1779 1787
1780 1788
1781 1789 @attr.s(frozen=True)
1782 1790 class updateresult(object):
1783 1791 updatedcount = attr.ib()
1784 1792 mergedcount = attr.ib()
1785 1793 removedcount = attr.ib()
1786 1794 unresolvedcount = attr.ib()
1787 1795
1788 1796 def isempty(self):
1789 1797 return not (
1790 1798 self.updatedcount
1791 1799 or self.mergedcount
1792 1800 or self.removedcount
1793 1801 or self.unresolvedcount
1794 1802 )
1795 1803
1796 1804
1797 1805 def emptyactions():
1798 1806 """create an actions dict, to be populated and passed to applyupdates()"""
1799 1807 return dict(
1800 1808 (m, [])
1801 1809 for m in (
1802 1810 ACTION_ADD,
1803 1811 ACTION_ADD_MODIFIED,
1804 1812 ACTION_FORGET,
1805 1813 ACTION_GET,
1806 1814 ACTION_CHANGED_DELETED,
1807 1815 ACTION_DELETED_CHANGED,
1808 1816 ACTION_REMOVE,
1809 1817 ACTION_DIR_RENAME_MOVE_LOCAL,
1810 1818 ACTION_LOCAL_DIR_RENAME_GET,
1811 1819 ACTION_MERGE,
1812 1820 ACTION_EXEC,
1813 1821 ACTION_KEEP,
1814 1822 ACTION_PATH_CONFLICT,
1815 1823 ACTION_PATH_CONFLICT_RESOLVE,
1816 1824 )
1817 1825 )
1818 1826
1819 1827
1820 1828 def applyupdates(
1821 1829 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1822 1830 ):
1823 1831 """apply the merge action list to the working directory
1824 1832
1825 1833 wctx is the working copy context
1826 1834 mctx is the context to be merged into the working copy
1827 1835
1828 1836 Return a tuple of (counts, filedata), where counts is a tuple
1829 1837 (updated, merged, removed, unresolved) that describes how many
1830 1838 files were affected by the update, and filedata is as described in
1831 1839 batchget.
1832 1840 """
1833 1841
1834 1842 _prefetchfiles(repo, mctx, actions)
1835 1843
1836 1844 updated, merged, removed = 0, 0, 0
1837 1845 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1838 1846 moves = []
1839 1847 for m, l in actions.items():
1840 1848 l.sort()
1841 1849
1842 1850 # 'cd' and 'dc' actions are treated like other merge conflicts
1843 1851 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1844 1852 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1845 1853 mergeactions.extend(actions[ACTION_MERGE])
1846 1854 for f, args, msg in mergeactions:
1847 1855 f1, f2, fa, move, anc = args
1848 1856 if f == b'.hgsubstate': # merged internally
1849 1857 continue
1850 1858 if f1 is None:
1851 1859 fcl = filemerge.absentfilectx(wctx, fa)
1852 1860 else:
1853 1861 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1854 1862 fcl = wctx[f1]
1855 1863 if f2 is None:
1856 1864 fco = filemerge.absentfilectx(mctx, fa)
1857 1865 else:
1858 1866 fco = mctx[f2]
1859 1867 actx = repo[anc]
1860 1868 if fa in actx:
1861 1869 fca = actx[fa]
1862 1870 else:
1863 1871 # TODO: move to absentfilectx
1864 1872 fca = repo.filectx(f1, fileid=nullrev)
1865 1873 ms.add(fcl, fco, fca, f)
1866 1874 if f1 != f and move:
1867 1875 moves.append(f1)
1868 1876
1869 1877 # remove renamed files after safely stored
1870 1878 for f in moves:
1871 1879 if wctx[f].lexists():
1872 1880 repo.ui.debug(b"removing %s\n" % f)
1873 1881 wctx[f].audit()
1874 1882 wctx[f].remove()
1875 1883
1876 1884 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP)
1877 1885 progress = repo.ui.makeprogress(
1878 1886 _(b'updating'), unit=_(b'files'), total=numupdates
1879 1887 )
1880 1888
1881 1889 if [a for a in actions[ACTION_REMOVE] if a[0] == b'.hgsubstate']:
1882 1890 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1883 1891
1884 1892 # record path conflicts
1885 1893 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1886 1894 f1, fo = args
1887 1895 s = repo.ui.status
1888 1896 s(
1889 1897 _(
1890 1898 b"%s: path conflict - a file or link has the same name as a "
1891 1899 b"directory\n"
1892 1900 )
1893 1901 % f
1894 1902 )
1895 1903 if fo == b'l':
1896 1904 s(_(b"the local file has been renamed to %s\n") % f1)
1897 1905 else:
1898 1906 s(_(b"the remote file has been renamed to %s\n") % f1)
1899 1907 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1900 1908 ms.addpath(f, f1, fo)
1901 1909 progress.increment(item=f)
1902 1910
1903 1911 # When merging in-memory, we can't support worker processes, so set the
1904 1912 # per-item cost at 0 in that case.
1905 1913 cost = 0 if wctx.isinmemory() else 0.001
1906 1914
1907 1915 # remove in parallel (must come before resolving path conflicts and getting)
1908 1916 prog = worker.worker(
1909 1917 repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE]
1910 1918 )
1911 1919 for i, item in prog:
1912 1920 progress.increment(step=i, item=item)
1913 1921 removed = len(actions[ACTION_REMOVE])
1914 1922
1915 1923 # resolve path conflicts (must come before getting)
1916 1924 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1917 1925 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1918 1926 (f0,) = args
1919 1927 if wctx[f0].lexists():
1920 1928 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1921 1929 wctx[f].audit()
1922 1930 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1923 1931 wctx[f0].remove()
1924 1932 progress.increment(item=f)
1925 1933
1926 1934 # get in parallel.
1927 1935 threadsafe = repo.ui.configbool(
1928 1936 b'experimental', b'worker.wdir-get-thread-safe'
1929 1937 )
1930 1938 prog = worker.worker(
1931 1939 repo.ui,
1932 1940 cost,
1933 1941 batchget,
1934 1942 (repo, mctx, wctx, wantfiledata),
1935 1943 actions[ACTION_GET],
1936 1944 threadsafe=threadsafe,
1937 1945 hasretval=True,
1938 1946 )
1939 1947 getfiledata = {}
1940 1948 for final, res in prog:
1941 1949 if final:
1942 1950 getfiledata = res
1943 1951 else:
1944 1952 i, item = res
1945 1953 progress.increment(step=i, item=item)
1946 1954 updated = len(actions[ACTION_GET])
1947 1955
1948 1956 if [a for a in actions[ACTION_GET] if a[0] == b'.hgsubstate']:
1949 1957 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1950 1958
1951 1959 # forget (manifest only, just log it) (must come first)
1952 1960 for f, args, msg in actions[ACTION_FORGET]:
1953 1961 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1954 1962 progress.increment(item=f)
1955 1963
1956 1964 # re-add (manifest only, just log it)
1957 1965 for f, args, msg in actions[ACTION_ADD]:
1958 1966 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1959 1967 progress.increment(item=f)
1960 1968
1961 1969 # re-add/mark as modified (manifest only, just log it)
1962 1970 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1963 1971 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1964 1972 progress.increment(item=f)
1965 1973
1966 1974 # keep (noop, just log it)
1967 1975 for f, args, msg in actions[ACTION_KEEP]:
1968 1976 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1969 1977 # no progress
1970 1978
1971 1979 # directory rename, move local
1972 1980 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1973 1981 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1974 1982 progress.increment(item=f)
1975 1983 f0, flags = args
1976 1984 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1977 1985 wctx[f].audit()
1978 1986 wctx[f].write(wctx.filectx(f0).data(), flags)
1979 1987 wctx[f0].remove()
1980 1988 updated += 1
1981 1989
1982 1990 # local directory rename, get
1983 1991 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1984 1992 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1985 1993 progress.increment(item=f)
1986 1994 f0, flags = args
1987 1995 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1988 1996 wctx[f].write(mctx.filectx(f0).data(), flags)
1989 1997 updated += 1
1990 1998
1991 1999 # exec
1992 2000 for f, args, msg in actions[ACTION_EXEC]:
1993 2001 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1994 2002 progress.increment(item=f)
1995 2003 (flags,) = args
1996 2004 wctx[f].audit()
1997 2005 wctx[f].setflags(b'l' in flags, b'x' in flags)
1998 2006 updated += 1
1999 2007
2000 2008 # the ordering is important here -- ms.mergedriver will raise if the merge
2001 2009 # driver has changed, and we want to be able to bypass it when overwrite is
2002 2010 # True
2003 2011 usemergedriver = not overwrite and mergeactions and ms.mergedriver
2004 2012
2005 2013 if usemergedriver:
2006 2014 if wctx.isinmemory():
2007 2015 raise error.InMemoryMergeConflictsError(
2008 2016 b"in-memory merge does not support mergedriver"
2009 2017 )
2010 2018 ms.commit()
2011 2019 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
2012 2020 # the driver might leave some files unresolved
2013 2021 unresolvedf = set(ms.unresolved())
2014 2022 if not proceed:
2015 2023 # XXX setting unresolved to at least 1 is a hack to make sure we
2016 2024 # error out
2017 2025 return updateresult(
2018 2026 updated, merged, removed, max(len(unresolvedf), 1)
2019 2027 )
2020 2028 newactions = []
2021 2029 for f, args, msg in mergeactions:
2022 2030 if f in unresolvedf:
2023 2031 newactions.append((f, args, msg))
2024 2032 mergeactions = newactions
2025 2033
2026 2034 try:
2027 2035 # premerge
2028 2036 tocomplete = []
2029 2037 for f, args, msg in mergeactions:
2030 2038 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
2031 2039 progress.increment(item=f)
2032 2040 if f == b'.hgsubstate': # subrepo states need updating
2033 2041 subrepoutil.submerge(
2034 2042 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
2035 2043 )
2036 2044 continue
2037 2045 wctx[f].audit()
2038 2046 complete, r = ms.preresolve(f, wctx)
2039 2047 if not complete:
2040 2048 numupdates += 1
2041 2049 tocomplete.append((f, args, msg))
2042 2050
2043 2051 # merge
2044 2052 for f, args, msg in tocomplete:
2045 2053 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
2046 2054 progress.increment(item=f, total=numupdates)
2047 2055 ms.resolve(f, wctx)
2048 2056
2049 2057 finally:
2050 2058 ms.commit()
2051 2059
2052 2060 unresolved = ms.unresolvedcount()
2053 2061
2054 2062 if (
2055 2063 usemergedriver
2056 2064 and not unresolved
2057 2065 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS
2058 2066 ):
2059 2067 if not driverconclude(repo, ms, wctx, labels=labels):
2060 2068 # XXX setting unresolved to at least 1 is a hack to make sure we
2061 2069 # error out
2062 2070 unresolved = max(unresolved, 1)
2063 2071
2064 2072 ms.commit()
2065 2073
2066 2074 msupdated, msmerged, msremoved = ms.counts()
2067 2075 updated += msupdated
2068 2076 merged += msmerged
2069 2077 removed += msremoved
2070 2078
2071 2079 extraactions = ms.actions()
2072 2080 if extraactions:
2073 2081 mfiles = set(a[0] for a in actions[ACTION_MERGE])
2074 2082 for k, acts in pycompat.iteritems(extraactions):
2075 2083 actions[k].extend(acts)
2076 2084 if k == ACTION_GET and wantfiledata:
2077 2085 # no filedata until mergestate is updated to provide it
2078 2086 for a in acts:
2079 2087 getfiledata[a[0]] = None
2080 2088 # Remove these files from actions[ACTION_MERGE] as well. This is
2081 2089 # important because in recordupdates, files in actions[ACTION_MERGE]
2082 2090 # are processed after files in other actions, and the merge driver
2083 2091 # might add files to those actions via extraactions above. This can
2084 2092 # lead to a file being recorded twice, with poor results. This is
2085 2093 # especially problematic for actions[ACTION_REMOVE] (currently only
2086 2094 # possible with the merge driver in the initial merge process;
2087 2095 # interrupted merges don't go through this flow).
2088 2096 #
2089 2097 # The real fix here is to have indexes by both file and action so
2090 2098 # that when the action for a file is changed it is automatically
2091 2099 # reflected in the other action lists. But that involves a more
2092 2100 # complex data structure, so this will do for now.
2093 2101 #
2094 2102 # We don't need to do the same operation for 'dc' and 'cd' because
2095 2103 # those lists aren't consulted again.
2096 2104 mfiles.difference_update(a[0] for a in acts)
2097 2105
2098 2106 actions[ACTION_MERGE] = [
2099 2107 a for a in actions[ACTION_MERGE] if a[0] in mfiles
2100 2108 ]
2101 2109
2102 2110 progress.complete()
2103 2111 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
2104 2112 return updateresult(updated, merged, removed, unresolved), getfiledata
2105 2113
2106 2114
2107 2115 def recordupdates(repo, actions, branchmerge, getfiledata):
2108 2116 """record merge actions to the dirstate"""
2109 2117 # remove (must come first)
2110 2118 for f, args, msg in actions.get(ACTION_REMOVE, []):
2111 2119 if branchmerge:
2112 2120 repo.dirstate.remove(f)
2113 2121 else:
2114 2122 repo.dirstate.drop(f)
2115 2123
2116 2124 # forget (must come first)
2117 2125 for f, args, msg in actions.get(ACTION_FORGET, []):
2118 2126 repo.dirstate.drop(f)
2119 2127
2120 2128 # resolve path conflicts
2121 2129 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
2122 2130 (f0,) = args
2123 2131 origf0 = repo.dirstate.copied(f0) or f0
2124 2132 repo.dirstate.add(f)
2125 2133 repo.dirstate.copy(origf0, f)
2126 2134 if f0 == origf0:
2127 2135 repo.dirstate.remove(f0)
2128 2136 else:
2129 2137 repo.dirstate.drop(f0)
2130 2138
2131 2139 # re-add
2132 2140 for f, args, msg in actions.get(ACTION_ADD, []):
2133 2141 repo.dirstate.add(f)
2134 2142
2135 2143 # re-add/mark as modified
2136 2144 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
2137 2145 if branchmerge:
2138 2146 repo.dirstate.normallookup(f)
2139 2147 else:
2140 2148 repo.dirstate.add(f)
2141 2149
2142 2150 # exec change
2143 2151 for f, args, msg in actions.get(ACTION_EXEC, []):
2144 2152 repo.dirstate.normallookup(f)
2145 2153
2146 2154 # keep
2147 2155 for f, args, msg in actions.get(ACTION_KEEP, []):
2148 2156 pass
2149 2157
2150 2158 # get
2151 2159 for f, args, msg in actions.get(ACTION_GET, []):
2152 2160 if branchmerge:
2153 2161 repo.dirstate.otherparent(f)
2154 2162 else:
2155 2163 parentfiledata = getfiledata[f] if getfiledata else None
2156 2164 repo.dirstate.normal(f, parentfiledata=parentfiledata)
2157 2165
2158 2166 # merge
2159 2167 for f, args, msg in actions.get(ACTION_MERGE, []):
2160 2168 f1, f2, fa, move, anc = args
2161 2169 if branchmerge:
2162 2170 # We've done a branch merge, mark this file as merged
2163 2171 # so that we properly record the merger later
2164 2172 repo.dirstate.merge(f)
2165 2173 if f1 != f2: # copy/rename
2166 2174 if move:
2167 2175 repo.dirstate.remove(f1)
2168 2176 if f1 != f:
2169 2177 repo.dirstate.copy(f1, f)
2170 2178 else:
2171 2179 repo.dirstate.copy(f2, f)
2172 2180 else:
2173 2181 # We've update-merged a locally modified file, so
2174 2182 # we set the dirstate to emulate a normal checkout
2175 2183 # of that file some time in the past. Thus our
2176 2184 # merge will appear as a normal local file
2177 2185 # modification.
2178 2186 if f2 == f: # file not locally copied/moved
2179 2187 repo.dirstate.normallookup(f)
2180 2188 if move:
2181 2189 repo.dirstate.drop(f1)
2182 2190
2183 2191 # directory rename, move local
2184 2192 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
2185 2193 f0, flag = args
2186 2194 if branchmerge:
2187 2195 repo.dirstate.add(f)
2188 2196 repo.dirstate.remove(f0)
2189 2197 repo.dirstate.copy(f0, f)
2190 2198 else:
2191 2199 repo.dirstate.normal(f)
2192 2200 repo.dirstate.drop(f0)
2193 2201
2194 2202 # directory rename, get
2195 2203 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
2196 2204 f0, flag = args
2197 2205 if branchmerge:
2198 2206 repo.dirstate.add(f)
2199 2207 repo.dirstate.copy(f0, f)
2200 2208 else:
2201 2209 repo.dirstate.normal(f)
2202 2210
2203 2211
2204 2212 UPDATECHECK_ABORT = b'abort' # handled at higher layers
2205 2213 UPDATECHECK_NONE = b'none'
2206 2214 UPDATECHECK_LINEAR = b'linear'
2207 2215 UPDATECHECK_NO_CONFLICT = b'noconflict'
2208 2216
2209 2217
2210 2218 def update(
2211 2219 repo,
2212 2220 node,
2213 2221 branchmerge,
2214 2222 force,
2215 2223 ancestor=None,
2216 2224 mergeancestor=False,
2217 2225 labels=None,
2218 2226 matcher=None,
2219 2227 mergeforce=False,
2220 2228 updatecheck=None,
2221 2229 wc=None,
2222 2230 ):
2223 2231 """
2224 2232 Perform a merge between the working directory and the given node
2225 2233
2226 2234 node = the node to update to
2227 2235 branchmerge = whether to merge between branches
2228 2236 force = whether to force branch merging or file overwriting
2229 2237 matcher = a matcher to filter file lists (dirstate not updated)
2230 2238 mergeancestor = whether it is merging with an ancestor. If true,
2231 2239 we should accept the incoming changes for any prompts that occur.
2232 2240 If false, merging with an ancestor (fast-forward) is only allowed
2233 2241 between different named branches. This flag is used by rebase extension
2234 2242 as a temporary fix and should be avoided in general.
2235 2243 labels = labels to use for base, local and other
2236 2244 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
2237 2245 this is True, then 'force' should be True as well.
2238 2246
2239 2247 The table below shows all the behaviors of the update command given the
2240 2248 -c/--check and -C/--clean or no options, whether the working directory is
2241 2249 dirty, whether a revision is specified, and the relationship of the parent
2242 2250 rev to the target rev (linear or not). Match from top first. The -n
2243 2251 option doesn't exist on the command line, but represents the
2244 2252 experimental.updatecheck=noconflict option.
2245 2253
2246 2254 This logic is tested by test-update-branches.t.
2247 2255
2248 2256 -c -C -n -m dirty rev linear | result
2249 2257 y y * * * * * | (1)
2250 2258 y * y * * * * | (1)
2251 2259 y * * y * * * | (1)
2252 2260 * y y * * * * | (1)
2253 2261 * y * y * * * | (1)
2254 2262 * * y y * * * | (1)
2255 2263 * * * * * n n | x
2256 2264 * * * * n * * | ok
2257 2265 n n n n y * y | merge
2258 2266 n n n n y y n | (2)
2259 2267 n n n y y * * | merge
2260 2268 n n y n y * * | merge if no conflict
2261 2269 n y n n y * * | discard
2262 2270 y n n n y * * | (3)
2263 2271
2264 2272 x = can't happen
2265 2273 * = don't-care
2266 2274 1 = incompatible options (checked in commands.py)
2267 2275 2 = abort: uncommitted changes (commit or update --clean to discard changes)
2268 2276 3 = abort: uncommitted changes (checked in commands.py)
2269 2277
2270 2278 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
2271 2279 to repo[None] if None is passed.
2272 2280
2273 2281 Return the same tuple as applyupdates().
2274 2282 """
2275 2283 # Avoid cycle.
2276 2284 from . import sparse
2277 2285
2278 2286 # This function used to find the default destination if node was None, but
2279 2287 # that's now in destutil.py.
2280 2288 assert node is not None
2281 2289 if not branchmerge and not force:
2282 2290 # TODO: remove the default once all callers that pass branchmerge=False
2283 2291 # and force=False pass a value for updatecheck. We may want to allow
2284 2292 # updatecheck='abort' to better suppport some of these callers.
2285 2293 if updatecheck is None:
2286 2294 updatecheck = UPDATECHECK_LINEAR
2287 2295 if updatecheck not in (
2288 2296 UPDATECHECK_NONE,
2289 2297 UPDATECHECK_LINEAR,
2290 2298 UPDATECHECK_NO_CONFLICT,
2291 2299 ):
2292 2300 raise ValueError(
2293 2301 r'Invalid updatecheck %r (can accept %r)'
2294 2302 % (
2295 2303 updatecheck,
2296 2304 (
2297 2305 UPDATECHECK_NONE,
2298 2306 UPDATECHECK_LINEAR,
2299 2307 UPDATECHECK_NO_CONFLICT,
2300 2308 ),
2301 2309 )
2302 2310 )
2303 2311 with repo.wlock():
2304 2312 if wc is None:
2305 2313 wc = repo[None]
2306 2314 pl = wc.parents()
2307 2315 p1 = pl[0]
2308 2316 p2 = repo[node]
2309 2317 if ancestor is not None:
2310 2318 pas = [repo[ancestor]]
2311 2319 else:
2312 2320 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
2313 2321 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2314 2322 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2315 2323 else:
2316 2324 pas = [p1.ancestor(p2, warn=branchmerge)]
2317 2325
2318 2326 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2319 2327
2320 2328 overwrite = force and not branchmerge
2321 2329 ### check phase
2322 2330 if not overwrite:
2323 2331 if len(pl) > 1:
2324 2332 raise error.Abort(_(b"outstanding uncommitted merge"))
2325 2333 ms = mergestate.read(repo)
2326 2334 if list(ms.unresolved()):
2327 2335 raise error.Abort(
2328 2336 _(b"outstanding merge conflicts"),
2329 2337 hint=_(b"use 'hg resolve' to resolve"),
2330 2338 )
2331 2339 if branchmerge:
2332 2340 if pas == [p2]:
2333 2341 raise error.Abort(
2334 2342 _(
2335 2343 b"merging with a working directory ancestor"
2336 2344 b" has no effect"
2337 2345 )
2338 2346 )
2339 2347 elif pas == [p1]:
2340 2348 if not mergeancestor and wc.branch() == p2.branch():
2341 2349 raise error.Abort(
2342 2350 _(b"nothing to merge"),
2343 2351 hint=_(b"use 'hg update' or check 'hg heads'"),
2344 2352 )
2345 2353 if not force and (wc.files() or wc.deleted()):
2346 2354 raise error.Abort(
2347 2355 _(b"uncommitted changes"),
2348 2356 hint=_(b"use 'hg status' to list changes"),
2349 2357 )
2350 2358 if not wc.isinmemory():
2351 2359 for s in sorted(wc.substate):
2352 2360 wc.sub(s).bailifchanged()
2353 2361
2354 2362 elif not overwrite:
2355 2363 if p1 == p2: # no-op update
2356 2364 # call the hooks and exit early
2357 2365 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
2358 2366 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
2359 2367 return updateresult(0, 0, 0, 0)
2360 2368
2361 2369 if updatecheck == UPDATECHECK_LINEAR and pas not in (
2362 2370 [p1],
2363 2371 [p2],
2364 2372 ): # nonlinear
2365 2373 dirty = wc.dirty(missing=True)
2366 2374 if dirty:
2367 2375 # Branching is a bit strange to ensure we do the minimal
2368 2376 # amount of call to obsutil.foreground.
2369 2377 foreground = obsutil.foreground(repo, [p1.node()])
2370 2378 # note: the <node> variable contains a random identifier
2371 2379 if repo[node].node() in foreground:
2372 2380 pass # allow updating to successors
2373 2381 else:
2374 2382 msg = _(b"uncommitted changes")
2375 2383 hint = _(b"commit or update --clean to discard changes")
2376 2384 raise error.UpdateAbort(msg, hint=hint)
2377 2385 else:
2378 2386 # Allow jumping branches if clean and specific rev given
2379 2387 pass
2380 2388
2381 2389 if overwrite:
2382 2390 pas = [wc]
2383 2391 elif not branchmerge:
2384 2392 pas = [p1]
2385 2393
2386 2394 # deprecated config: merge.followcopies
2387 2395 followcopies = repo.ui.configbool(b'merge', b'followcopies')
2388 2396 if overwrite:
2389 2397 followcopies = False
2390 2398 elif not pas[0]:
2391 2399 followcopies = False
2392 2400 if not branchmerge and not wc.dirty(missing=True):
2393 2401 followcopies = False
2394 2402
2395 2403 ### calculate phase
2396 2404 actionbyfile, diverge, renamedelete = calculateupdates(
2397 2405 repo,
2398 2406 wc,
2399 2407 p2,
2400 2408 pas,
2401 2409 branchmerge,
2402 2410 force,
2403 2411 mergeancestor,
2404 2412 followcopies,
2405 2413 matcher=matcher,
2406 2414 mergeforce=mergeforce,
2407 2415 )
2408 2416
2409 2417 if updatecheck == UPDATECHECK_NO_CONFLICT:
2410 2418 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2411 2419 if m not in (
2412 2420 ACTION_GET,
2413 2421 ACTION_KEEP,
2414 2422 ACTION_EXEC,
2415 2423 ACTION_REMOVE,
2416 2424 ACTION_PATH_CONFLICT_RESOLVE,
2417 2425 ):
2418 2426 msg = _(b"conflicting changes")
2419 2427 hint = _(b"commit or update --clean to discard changes")
2420 2428 raise error.Abort(msg, hint=hint)
2421 2429
2422 2430 # Prompt and create actions. Most of this is in the resolve phase
2423 2431 # already, but we can't handle .hgsubstate in filemerge or
2424 2432 # subrepoutil.submerge yet so we have to keep prompting for it.
2425 2433 if b'.hgsubstate' in actionbyfile:
2426 2434 f = b'.hgsubstate'
2427 2435 m, args, msg = actionbyfile[f]
2428 2436 prompts = filemerge.partextras(labels)
2429 2437 prompts[b'f'] = f
2430 2438 if m == ACTION_CHANGED_DELETED:
2431 2439 if repo.ui.promptchoice(
2432 2440 _(
2433 2441 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2434 2442 b"use (c)hanged version or (d)elete?"
2435 2443 b"$$ &Changed $$ &Delete"
2436 2444 )
2437 2445 % prompts,
2438 2446 0,
2439 2447 ):
2440 2448 actionbyfile[f] = (ACTION_REMOVE, None, b'prompt delete')
2441 2449 elif f in p1:
2442 2450 actionbyfile[f] = (
2443 2451 ACTION_ADD_MODIFIED,
2444 2452 None,
2445 2453 b'prompt keep',
2446 2454 )
2447 2455 else:
2448 2456 actionbyfile[f] = (ACTION_ADD, None, b'prompt keep')
2449 2457 elif m == ACTION_DELETED_CHANGED:
2450 2458 f1, f2, fa, move, anc = args
2451 2459 flags = p2[f2].flags()
2452 2460 if (
2453 2461 repo.ui.promptchoice(
2454 2462 _(
2455 2463 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2456 2464 b"use (c)hanged version or leave (d)eleted?"
2457 2465 b"$$ &Changed $$ &Deleted"
2458 2466 )
2459 2467 % prompts,
2460 2468 0,
2461 2469 )
2462 2470 == 0
2463 2471 ):
2464 2472 actionbyfile[f] = (
2465 2473 ACTION_GET,
2466 2474 (flags, False),
2467 2475 b'prompt recreating',
2468 2476 )
2469 2477 else:
2470 2478 del actionbyfile[f]
2471 2479
2472 2480 # Convert to dictionary-of-lists format
2473 2481 actions = emptyactions()
2474 2482 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2475 2483 if m not in actions:
2476 2484 actions[m] = []
2477 2485 actions[m].append((f, args, msg))
2478 2486
2479 2487 if not util.fscasesensitive(repo.path):
2480 2488 # check collision between files only in p2 for clean update
2481 2489 if not branchmerge and (
2482 2490 force or not wc.dirty(missing=True, branch=False)
2483 2491 ):
2484 2492 _checkcollision(repo, p2.manifest(), None)
2485 2493 else:
2486 2494 _checkcollision(repo, wc.manifest(), actions)
2487 2495
2488 2496 # divergent renames
2489 2497 for f, fl in sorted(pycompat.iteritems(diverge)):
2490 2498 repo.ui.warn(
2491 2499 _(
2492 2500 b"note: possible conflict - %s was renamed "
2493 2501 b"multiple times to:\n"
2494 2502 )
2495 2503 % f
2496 2504 )
2497 2505 for nf in sorted(fl):
2498 2506 repo.ui.warn(b" %s\n" % nf)
2499 2507
2500 2508 # rename and delete
2501 2509 for f, fl in sorted(pycompat.iteritems(renamedelete)):
2502 2510 repo.ui.warn(
2503 2511 _(
2504 2512 b"note: possible conflict - %s was deleted "
2505 2513 b"and renamed to:\n"
2506 2514 )
2507 2515 % f
2508 2516 )
2509 2517 for nf in sorted(fl):
2510 2518 repo.ui.warn(b" %s\n" % nf)
2511 2519
2512 2520 ### apply phase
2513 2521 if not branchmerge: # just jump to the new rev
2514 2522 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
2515 2523 # If we're doing a partial update, we need to skip updating
2516 2524 # the dirstate.
2517 2525 always = matcher is None or matcher.always()
2518 2526 updatedirstate = always and not wc.isinmemory()
2519 2527 if updatedirstate:
2520 2528 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2521 2529 # note that we're in the middle of an update
2522 2530 repo.vfs.write(b'updatestate', p2.hex())
2523 2531
2524 2532 # Advertise fsmonitor when its presence could be useful.
2525 2533 #
2526 2534 # We only advertise when performing an update from an empty working
2527 2535 # directory. This typically only occurs during initial clone.
2528 2536 #
2529 2537 # We give users a mechanism to disable the warning in case it is
2530 2538 # annoying.
2531 2539 #
2532 2540 # We only allow on Linux and MacOS because that's where fsmonitor is
2533 2541 # considered stable.
2534 2542 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
2535 2543 fsmonitorthreshold = repo.ui.configint(
2536 2544 b'fsmonitor', b'warn_update_file_count'
2537 2545 )
2538 2546 try:
2539 2547 # avoid cycle: extensions -> cmdutil -> merge
2540 2548 from . import extensions
2541 2549
2542 2550 extensions.find(b'fsmonitor')
2543 2551 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
2544 2552 # We intentionally don't look at whether fsmonitor has disabled
2545 2553 # itself because a) fsmonitor may have already printed a warning
2546 2554 # b) we only care about the config state here.
2547 2555 except KeyError:
2548 2556 fsmonitorenabled = False
2549 2557
2550 2558 if (
2551 2559 fsmonitorwarning
2552 2560 and not fsmonitorenabled
2553 2561 and p1.node() == nullid
2554 2562 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2555 2563 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
2556 2564 ):
2557 2565 repo.ui.warn(
2558 2566 _(
2559 2567 b'(warning: large working directory being used without '
2560 2568 b'fsmonitor enabled; enable fsmonitor to improve performance; '
2561 2569 b'see "hg help -e fsmonitor")\n'
2562 2570 )
2563 2571 )
2564 2572
2565 2573 wantfiledata = updatedirstate and not branchmerge
2566 2574 stats, getfiledata = applyupdates(
2567 2575 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
2568 2576 )
2569 2577
2570 2578 if updatedirstate:
2571 2579 with repo.dirstate.parentchange():
2572 2580 repo.setparents(fp1, fp2)
2573 2581 recordupdates(repo, actions, branchmerge, getfiledata)
2574 2582 # update completed, clear state
2575 2583 util.unlink(repo.vfs.join(b'updatestate'))
2576 2584
2577 2585 if not branchmerge:
2578 2586 repo.dirstate.setbranch(p2.branch())
2579 2587
2580 2588 # If we're updating to a location, clean up any stale temporary includes
2581 2589 # (ex: this happens during hg rebase --abort).
2582 2590 if not branchmerge:
2583 2591 sparse.prunetemporaryincludes(repo)
2584 2592
2585 2593 if updatedirstate:
2586 2594 repo.hook(
2587 2595 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2588 2596 )
2589 2597 return stats
2590 2598
2591 2599
2592 2600 def graft(
2593 2601 repo,
2594 2602 ctx,
2595 2603 base=None,
2596 2604 labels=None,
2597 2605 keepparent=False,
2598 2606 keepconflictparent=False,
2599 2607 wctx=None,
2600 2608 ):
2601 2609 """Do a graft-like merge.
2602 2610
2603 2611 This is a merge where the merge ancestor is chosen such that one
2604 2612 or more changesets are grafted onto the current changeset. In
2605 2613 addition to the merge, this fixes up the dirstate to include only
2606 2614 a single parent (if keepparent is False) and tries to duplicate any
2607 2615 renames/copies appropriately.
2608 2616
2609 2617 ctx - changeset to rebase
2610 2618 base - merge base, or ctx.p1() if not specified
2611 2619 labels - merge labels eg ['local', 'graft']
2612 2620 keepparent - keep second parent if any
2613 2621 keepconflictparent - if unresolved, keep parent used for the merge
2614 2622
2615 2623 """
2616 2624 # If we're grafting a descendant onto an ancestor, be sure to pass
2617 2625 # mergeancestor=True to update. This does two things: 1) allows the merge if
2618 2626 # the destination is the same as the parent of the ctx (so we can use graft
2619 2627 # to copy commits), and 2) informs update that the incoming changes are
2620 2628 # newer than the destination so it doesn't prompt about "remote changed foo
2621 2629 # which local deleted".
2622 2630 wctx = wctx or repo[None]
2623 2631 pctx = wctx.p1()
2624 2632 base = base or ctx.p1()
2625 2633 mergeancestor = repo.changelog.isancestor(pctx.node(), ctx.node())
2626 2634
2627 2635 stats = update(
2628 2636 repo,
2629 2637 ctx.node(),
2630 2638 True,
2631 2639 True,
2632 2640 base.node(),
2633 2641 mergeancestor=mergeancestor,
2634 2642 labels=labels,
2635 2643 wc=wctx,
2636 2644 )
2637 2645
2638 2646 if keepconflictparent and stats.unresolvedcount:
2639 2647 pother = ctx.node()
2640 2648 else:
2641 2649 pother = nullid
2642 2650 parents = ctx.parents()
2643 2651 if keepparent and len(parents) == 2 and base in parents:
2644 2652 parents.remove(base)
2645 2653 pother = parents[0].node()
2646 2654 # Never set both parents equal to each other
2647 2655 if pother == pctx.node():
2648 2656 pother = nullid
2649 2657
2650 2658 if wctx.isinmemory():
2651 2659 wctx.setparents(pctx.node(), pother)
2652 2660 # fix up dirstate for copies and renames
2653 2661 copies.graftcopies(wctx, ctx, base)
2654 2662 else:
2655 2663 with repo.dirstate.parentchange():
2656 2664 repo.setparents(pctx.node(), pother)
2657 2665 repo.dirstate.write(repo.currenttransaction())
2658 2666 # fix up dirstate for copies and renames
2659 2667 copies.graftcopies(wctx, ctx, base)
2660 2668 return stats
2661 2669
2662 2670
2663 2671 def purge(
2664 2672 repo,
2665 2673 matcher,
2666 2674 ignored=False,
2667 2675 removeemptydirs=True,
2668 2676 removefiles=True,
2669 2677 abortonerror=False,
2670 2678 noop=False,
2671 2679 ):
2672 2680 """Purge the working directory of untracked files.
2673 2681
2674 2682 ``matcher`` is a matcher configured to scan the working directory -
2675 2683 potentially a subset.
2676 2684
2677 2685 ``ignored`` controls whether ignored files should also be purged.
2678 2686
2679 2687 ``removeemptydirs`` controls whether empty directories should be removed.
2680 2688
2681 2689 ``removefiles`` controls whether files are removed.
2682 2690
2683 2691 ``abortonerror`` causes an exception to be raised if an error occurs
2684 2692 deleting a file or directory.
2685 2693
2686 2694 ``noop`` controls whether to actually remove files. If not defined, actions
2687 2695 will be taken.
2688 2696
2689 2697 Returns an iterable of relative paths in the working directory that were
2690 2698 or would be removed.
2691 2699 """
2692 2700
2693 2701 def remove(removefn, path):
2694 2702 try:
2695 2703 removefn(path)
2696 2704 except OSError:
2697 2705 m = _(b'%s cannot be removed') % path
2698 2706 if abortonerror:
2699 2707 raise error.Abort(m)
2700 2708 else:
2701 2709 repo.ui.warn(_(b'warning: %s\n') % m)
2702 2710
2703 2711 # There's no API to copy a matcher. So mutate the passed matcher and
2704 2712 # restore it when we're done.
2705 2713 oldtraversedir = matcher.traversedir
2706 2714
2707 2715 res = []
2708 2716
2709 2717 try:
2710 2718 if removeemptydirs:
2711 2719 directories = []
2712 2720 matcher.traversedir = directories.append
2713 2721
2714 2722 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2715 2723
2716 2724 if removefiles:
2717 2725 for f in sorted(status.unknown + status.ignored):
2718 2726 if not noop:
2719 2727 repo.ui.note(_(b'removing file %s\n') % f)
2720 2728 remove(repo.wvfs.unlink, f)
2721 2729 res.append(f)
2722 2730
2723 2731 if removeemptydirs:
2724 2732 for f in sorted(directories, reverse=True):
2725 2733 if matcher(f) and not repo.wvfs.listdir(f):
2726 2734 if not noop:
2727 2735 repo.ui.note(_(b'removing directory %s\n') % f)
2728 2736 remove(repo.wvfs.rmdir, f)
2729 2737 res.append(f)
2730 2738
2731 2739 return res
2732 2740
2733 2741 finally:
2734 2742 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now