##// END OF EJS Templates
merge: don't grab wlock when merging in memory...
Martin von Zweigbergk -
r45536:d1471dbb default
parent child Browse files
Show More
@@ -1,2073 +1,2077
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 stat
12 12 import struct
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 addednodeid,
17 17 modifiednodeid,
18 18 nullid,
19 19 nullrev,
20 20 )
21 21 from .thirdparty import attr
22 22 from . import (
23 23 copies,
24 24 encoding,
25 25 error,
26 26 filemerge,
27 27 match as matchmod,
28 28 mergestate as mergestatemod,
29 29 obsutil,
30 30 pathutil,
31 31 pycompat,
32 32 scmutil,
33 33 subrepoutil,
34 34 util,
35 35 worker,
36 36 )
37 37
38 38 _pack = struct.pack
39 39 _unpack = struct.unpack
40 40
41 41
42 42 def _getcheckunknownconfig(repo, section, name):
43 43 config = repo.ui.config(section, name)
44 44 valid = [b'abort', b'ignore', b'warn']
45 45 if config not in valid:
46 46 validstr = b', '.join([b"'" + v + b"'" for v in valid])
47 47 raise error.ConfigError(
48 48 _(b"%s.%s not valid ('%s' is none of %s)")
49 49 % (section, name, config, validstr)
50 50 )
51 51 return config
52 52
53 53
54 54 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
55 55 if wctx.isinmemory():
56 56 # Nothing to do in IMM because nothing in the "working copy" can be an
57 57 # unknown file.
58 58 #
59 59 # Note that we should bail out here, not in ``_checkunknownfiles()``,
60 60 # because that function does other useful work.
61 61 return False
62 62
63 63 if f2 is None:
64 64 f2 = f
65 65 return (
66 66 repo.wvfs.audit.check(f)
67 67 and repo.wvfs.isfileorlink(f)
68 68 and repo.dirstate.normalize(f) not in repo.dirstate
69 69 and mctx[f2].cmp(wctx[f])
70 70 )
71 71
72 72
73 73 class _unknowndirschecker(object):
74 74 """
75 75 Look for any unknown files or directories that may have a path conflict
76 76 with a file. If any path prefix of the file exists as a file or link,
77 77 then it conflicts. If the file itself is a directory that contains any
78 78 file that is not tracked, then it conflicts.
79 79
80 80 Returns the shortest path at which a conflict occurs, or None if there is
81 81 no conflict.
82 82 """
83 83
84 84 def __init__(self):
85 85 # A set of paths known to be good. This prevents repeated checking of
86 86 # dirs. It will be updated with any new dirs that are checked and found
87 87 # to be safe.
88 88 self._unknowndircache = set()
89 89
90 90 # A set of paths that are known to be absent. This prevents repeated
91 91 # checking of subdirectories that are known not to exist. It will be
92 92 # updated with any new dirs that are checked and found to be absent.
93 93 self._missingdircache = set()
94 94
95 95 def __call__(self, repo, wctx, f):
96 96 if wctx.isinmemory():
97 97 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
98 98 return False
99 99
100 100 # Check for path prefixes that exist as unknown files.
101 101 for p in reversed(list(pathutil.finddirs(f))):
102 102 if p in self._missingdircache:
103 103 return
104 104 if p in self._unknowndircache:
105 105 continue
106 106 if repo.wvfs.audit.check(p):
107 107 if (
108 108 repo.wvfs.isfileorlink(p)
109 109 and repo.dirstate.normalize(p) not in repo.dirstate
110 110 ):
111 111 return p
112 112 if not repo.wvfs.lexists(p):
113 113 self._missingdircache.add(p)
114 114 return
115 115 self._unknowndircache.add(p)
116 116
117 117 # Check if the file conflicts with a directory containing unknown files.
118 118 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
119 119 # Does the directory contain any files that are not in the dirstate?
120 120 for p, dirs, files in repo.wvfs.walk(f):
121 121 for fn in files:
122 122 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
123 123 relf = repo.dirstate.normalize(relf, isknown=True)
124 124 if relf not in repo.dirstate:
125 125 return f
126 126 return None
127 127
128 128
129 129 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
130 130 """
131 131 Considers any actions that care about the presence of conflicting unknown
132 132 files. For some actions, the result is to abort; for others, it is to
133 133 choose a different action.
134 134 """
135 135 fileconflicts = set()
136 136 pathconflicts = set()
137 137 warnconflicts = set()
138 138 abortconflicts = set()
139 139 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
140 140 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
141 141 pathconfig = repo.ui.configbool(
142 142 b'experimental', b'merge.checkpathconflicts'
143 143 )
144 144 if not force:
145 145
146 146 def collectconflicts(conflicts, config):
147 147 if config == b'abort':
148 148 abortconflicts.update(conflicts)
149 149 elif config == b'warn':
150 150 warnconflicts.update(conflicts)
151 151
152 152 checkunknowndirs = _unknowndirschecker()
153 153 for f, (m, args, msg) in pycompat.iteritems(actions):
154 154 if m in (
155 155 mergestatemod.ACTION_CREATED,
156 156 mergestatemod.ACTION_DELETED_CHANGED,
157 157 ):
158 158 if _checkunknownfile(repo, wctx, mctx, f):
159 159 fileconflicts.add(f)
160 160 elif pathconfig and f not in wctx:
161 161 path = checkunknowndirs(repo, wctx, f)
162 162 if path is not None:
163 163 pathconflicts.add(path)
164 164 elif m == mergestatemod.ACTION_LOCAL_DIR_RENAME_GET:
165 165 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
166 166 fileconflicts.add(f)
167 167
168 168 allconflicts = fileconflicts | pathconflicts
169 169 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
170 170 unknownconflicts = allconflicts - ignoredconflicts
171 171 collectconflicts(ignoredconflicts, ignoredconfig)
172 172 collectconflicts(unknownconflicts, unknownconfig)
173 173 else:
174 174 for f, (m, args, msg) in pycompat.iteritems(actions):
175 175 if m == mergestatemod.ACTION_CREATED_MERGE:
176 176 fl2, anc = args
177 177 different = _checkunknownfile(repo, wctx, mctx, f)
178 178 if repo.dirstate._ignore(f):
179 179 config = ignoredconfig
180 180 else:
181 181 config = unknownconfig
182 182
183 183 # The behavior when force is True is described by this table:
184 184 # config different mergeforce | action backup
185 185 # * n * | get n
186 186 # * y y | merge -
187 187 # abort y n | merge - (1)
188 188 # warn y n | warn + get y
189 189 # ignore y n | get y
190 190 #
191 191 # (1) this is probably the wrong behavior here -- we should
192 192 # probably abort, but some actions like rebases currently
193 193 # don't like an abort happening in the middle of
194 194 # merge.update.
195 195 if not different:
196 196 actions[f] = (
197 197 mergestatemod.ACTION_GET,
198 198 (fl2, False),
199 199 b'remote created',
200 200 )
201 201 elif mergeforce or config == b'abort':
202 202 actions[f] = (
203 203 mergestatemod.ACTION_MERGE,
204 204 (f, f, None, False, anc),
205 205 b'remote differs from untracked local',
206 206 )
207 207 elif config == b'abort':
208 208 abortconflicts.add(f)
209 209 else:
210 210 if config == b'warn':
211 211 warnconflicts.add(f)
212 212 actions[f] = (
213 213 mergestatemod.ACTION_GET,
214 214 (fl2, True),
215 215 b'remote created',
216 216 )
217 217
218 218 for f in sorted(abortconflicts):
219 219 warn = repo.ui.warn
220 220 if f in pathconflicts:
221 221 if repo.wvfs.isfileorlink(f):
222 222 warn(_(b"%s: untracked file conflicts with directory\n") % f)
223 223 else:
224 224 warn(_(b"%s: untracked directory conflicts with file\n") % f)
225 225 else:
226 226 warn(_(b"%s: untracked file differs\n") % f)
227 227 if abortconflicts:
228 228 raise error.Abort(
229 229 _(
230 230 b"untracked files in working directory "
231 231 b"differ from files in requested revision"
232 232 )
233 233 )
234 234
235 235 for f in sorted(warnconflicts):
236 236 if repo.wvfs.isfileorlink(f):
237 237 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
238 238 else:
239 239 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
240 240
241 241 for f, (m, args, msg) in pycompat.iteritems(actions):
242 242 if m == mergestatemod.ACTION_CREATED:
243 243 backup = (
244 244 f in fileconflicts
245 245 or f in pathconflicts
246 246 or any(p in pathconflicts for p in pathutil.finddirs(f))
247 247 )
248 248 (flags,) = args
249 249 actions[f] = (mergestatemod.ACTION_GET, (flags, backup), msg)
250 250
251 251
252 252 def _forgetremoved(wctx, mctx, branchmerge):
253 253 """
254 254 Forget removed files
255 255
256 256 If we're jumping between revisions (as opposed to merging), and if
257 257 neither the working directory nor the target rev has the file,
258 258 then we need to remove it from the dirstate, to prevent the
259 259 dirstate from listing the file when it is no longer in the
260 260 manifest.
261 261
262 262 If we're merging, and the other revision has removed a file
263 263 that is not present in the working directory, we need to mark it
264 264 as removed.
265 265 """
266 266
267 267 actions = {}
268 268 m = mergestatemod.ACTION_FORGET
269 269 if branchmerge:
270 270 m = mergestatemod.ACTION_REMOVE
271 271 for f in wctx.deleted():
272 272 if f not in mctx:
273 273 actions[f] = m, None, b"forget deleted"
274 274
275 275 if not branchmerge:
276 276 for f in wctx.removed():
277 277 if f not in mctx:
278 278 actions[f] = (
279 279 mergestatemod.ACTION_FORGET,
280 280 None,
281 281 b"forget removed",
282 282 )
283 283
284 284 return actions
285 285
286 286
287 287 def _checkcollision(repo, wmf, actions):
288 288 """
289 289 Check for case-folding collisions.
290 290 """
291 291 # If the repo is narrowed, filter out files outside the narrowspec.
292 292 narrowmatch = repo.narrowmatch()
293 293 if not narrowmatch.always():
294 294 pmmf = set(wmf.walk(narrowmatch))
295 295 if actions:
296 296 narrowactions = {}
297 297 for m, actionsfortype in pycompat.iteritems(actions):
298 298 narrowactions[m] = []
299 299 for (f, args, msg) in actionsfortype:
300 300 if narrowmatch(f):
301 301 narrowactions[m].append((f, args, msg))
302 302 actions = narrowactions
303 303 else:
304 304 # build provisional merged manifest up
305 305 pmmf = set(wmf)
306 306
307 307 if actions:
308 308 # KEEP and EXEC are no-op
309 309 for m in (
310 310 mergestatemod.ACTION_ADD,
311 311 mergestatemod.ACTION_ADD_MODIFIED,
312 312 mergestatemod.ACTION_FORGET,
313 313 mergestatemod.ACTION_GET,
314 314 mergestatemod.ACTION_CHANGED_DELETED,
315 315 mergestatemod.ACTION_DELETED_CHANGED,
316 316 ):
317 317 for f, args, msg in actions[m]:
318 318 pmmf.add(f)
319 319 for f, args, msg in actions[mergestatemod.ACTION_REMOVE]:
320 320 pmmf.discard(f)
321 321 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
322 322 f2, flags = args
323 323 pmmf.discard(f2)
324 324 pmmf.add(f)
325 325 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
326 326 pmmf.add(f)
327 327 for f, args, msg in actions[mergestatemod.ACTION_MERGE]:
328 328 f1, f2, fa, move, anc = args
329 329 if move:
330 330 pmmf.discard(f1)
331 331 pmmf.add(f)
332 332
333 333 # check case-folding collision in provisional merged manifest
334 334 foldmap = {}
335 335 for f in pmmf:
336 336 fold = util.normcase(f)
337 337 if fold in foldmap:
338 338 raise error.Abort(
339 339 _(b"case-folding collision between %s and %s")
340 340 % (f, foldmap[fold])
341 341 )
342 342 foldmap[fold] = f
343 343
344 344 # check case-folding of directories
345 345 foldprefix = unfoldprefix = lastfull = b''
346 346 for fold, f in sorted(foldmap.items()):
347 347 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
348 348 # the folded prefix matches but actual casing is different
349 349 raise error.Abort(
350 350 _(b"case-folding collision between %s and directory of %s")
351 351 % (lastfull, f)
352 352 )
353 353 foldprefix = fold + b'/'
354 354 unfoldprefix = f + b'/'
355 355 lastfull = f
356 356
357 357
358 358 def driverpreprocess(repo, ms, wctx, labels=None):
359 359 """run the preprocess step of the merge driver, if any
360 360
361 361 This is currently not implemented -- it's an extension point."""
362 362 return True
363 363
364 364
365 365 def driverconclude(repo, ms, wctx, labels=None):
366 366 """run the conclude step of the merge driver, if any
367 367
368 368 This is currently not implemented -- it's an extension point."""
369 369 return True
370 370
371 371
372 372 def _filesindirs(repo, manifest, dirs):
373 373 """
374 374 Generator that yields pairs of all the files in the manifest that are found
375 375 inside the directories listed in dirs, and which directory they are found
376 376 in.
377 377 """
378 378 for f in manifest:
379 379 for p in pathutil.finddirs(f):
380 380 if p in dirs:
381 381 yield f, p
382 382 break
383 383
384 384
385 385 def checkpathconflicts(repo, wctx, mctx, actions):
386 386 """
387 387 Check if any actions introduce path conflicts in the repository, updating
388 388 actions to record or handle the path conflict accordingly.
389 389 """
390 390 mf = wctx.manifest()
391 391
392 392 # The set of local files that conflict with a remote directory.
393 393 localconflicts = set()
394 394
395 395 # The set of directories that conflict with a remote file, and so may cause
396 396 # conflicts if they still contain any files after the merge.
397 397 remoteconflicts = set()
398 398
399 399 # The set of directories that appear as both a file and a directory in the
400 400 # remote manifest. These indicate an invalid remote manifest, which
401 401 # can't be updated to cleanly.
402 402 invalidconflicts = set()
403 403
404 404 # The set of directories that contain files that are being created.
405 405 createdfiledirs = set()
406 406
407 407 # The set of files deleted by all the actions.
408 408 deletedfiles = set()
409 409
410 410 for f, (m, args, msg) in actions.items():
411 411 if m in (
412 412 mergestatemod.ACTION_CREATED,
413 413 mergestatemod.ACTION_DELETED_CHANGED,
414 414 mergestatemod.ACTION_MERGE,
415 415 mergestatemod.ACTION_CREATED_MERGE,
416 416 ):
417 417 # This action may create a new local file.
418 418 createdfiledirs.update(pathutil.finddirs(f))
419 419 if mf.hasdir(f):
420 420 # The file aliases a local directory. This might be ok if all
421 421 # the files in the local directory are being deleted. This
422 422 # will be checked once we know what all the deleted files are.
423 423 remoteconflicts.add(f)
424 424 # Track the names of all deleted files.
425 425 if m == mergestatemod.ACTION_REMOVE:
426 426 deletedfiles.add(f)
427 427 if m == mergestatemod.ACTION_MERGE:
428 428 f1, f2, fa, move, anc = args
429 429 if move:
430 430 deletedfiles.add(f1)
431 431 if m == mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL:
432 432 f2, flags = args
433 433 deletedfiles.add(f2)
434 434
435 435 # Check all directories that contain created files for path conflicts.
436 436 for p in createdfiledirs:
437 437 if p in mf:
438 438 if p in mctx:
439 439 # A file is in a directory which aliases both a local
440 440 # and a remote file. This is an internal inconsistency
441 441 # within the remote manifest.
442 442 invalidconflicts.add(p)
443 443 else:
444 444 # A file is in a directory which aliases a local file.
445 445 # We will need to rename the local file.
446 446 localconflicts.add(p)
447 447 if p in actions and actions[p][0] in (
448 448 mergestatemod.ACTION_CREATED,
449 449 mergestatemod.ACTION_DELETED_CHANGED,
450 450 mergestatemod.ACTION_MERGE,
451 451 mergestatemod.ACTION_CREATED_MERGE,
452 452 ):
453 453 # The file is in a directory which aliases a remote file.
454 454 # This is an internal inconsistency within the remote
455 455 # manifest.
456 456 invalidconflicts.add(p)
457 457
458 458 # Rename all local conflicting files that have not been deleted.
459 459 for p in localconflicts:
460 460 if p not in deletedfiles:
461 461 ctxname = bytes(wctx).rstrip(b'+')
462 462 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
463 463 porig = wctx[p].copysource() or p
464 464 actions[pnew] = (
465 465 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
466 466 (p, porig),
467 467 b'local path conflict',
468 468 )
469 469 actions[p] = (
470 470 mergestatemod.ACTION_PATH_CONFLICT,
471 471 (pnew, b'l'),
472 472 b'path conflict',
473 473 )
474 474
475 475 if remoteconflicts:
476 476 # Check if all files in the conflicting directories have been removed.
477 477 ctxname = bytes(mctx).rstrip(b'+')
478 478 for f, p in _filesindirs(repo, mf, remoteconflicts):
479 479 if f not in deletedfiles:
480 480 m, args, msg = actions[p]
481 481 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
482 482 if m in (
483 483 mergestatemod.ACTION_DELETED_CHANGED,
484 484 mergestatemod.ACTION_MERGE,
485 485 ):
486 486 # Action was merge, just update target.
487 487 actions[pnew] = (m, args, msg)
488 488 else:
489 489 # Action was create, change to renamed get action.
490 490 fl = args[0]
491 491 actions[pnew] = (
492 492 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
493 493 (p, fl),
494 494 b'remote path conflict',
495 495 )
496 496 actions[p] = (
497 497 mergestatemod.ACTION_PATH_CONFLICT,
498 498 (pnew, mergestatemod.ACTION_REMOVE),
499 499 b'path conflict',
500 500 )
501 501 remoteconflicts.remove(p)
502 502 break
503 503
504 504 if invalidconflicts:
505 505 for p in invalidconflicts:
506 506 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
507 507 raise error.Abort(_(b"destination manifest contains path conflicts"))
508 508
509 509
510 510 def _filternarrowactions(narrowmatch, branchmerge, actions):
511 511 """
512 512 Filters out actions that can ignored because the repo is narrowed.
513 513
514 514 Raise an exception if the merge cannot be completed because the repo is
515 515 narrowed.
516 516 """
517 517 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
518 518 nonconflicttypes = set(b'a am c cm f g gs r e'.split())
519 519 # We mutate the items in the dict during iteration, so iterate
520 520 # over a copy.
521 521 for f, action in list(actions.items()):
522 522 if narrowmatch(f):
523 523 pass
524 524 elif not branchmerge:
525 525 del actions[f] # just updating, ignore changes outside clone
526 526 elif action[0] in nooptypes:
527 527 del actions[f] # merge does not affect file
528 528 elif action[0] in nonconflicttypes:
529 529 raise error.Abort(
530 530 _(
531 531 b'merge affects file \'%s\' outside narrow, '
532 532 b'which is not yet supported'
533 533 )
534 534 % f,
535 535 hint=_(b'merging in the other direction may work'),
536 536 )
537 537 else:
538 538 raise error.Abort(
539 539 _(b'conflict in file \'%s\' is outside narrow clone') % f
540 540 )
541 541
542 542
543 543 def manifestmerge(
544 544 repo,
545 545 wctx,
546 546 p2,
547 547 pa,
548 548 branchmerge,
549 549 force,
550 550 matcher,
551 551 acceptremote,
552 552 followcopies,
553 553 forcefulldiff=False,
554 554 ):
555 555 """
556 556 Merge wctx and p2 with ancestor pa and generate merge action list
557 557
558 558 branchmerge and force are as passed in to update
559 559 matcher = matcher to filter file lists
560 560 acceptremote = accept the incoming changes without prompting
561 561 """
562 562 if matcher is not None and matcher.always():
563 563 matcher = None
564 564
565 565 # manifests fetched in order are going to be faster, so prime the caches
566 566 [
567 567 x.manifest()
568 568 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
569 569 ]
570 570
571 571 branch_copies1 = copies.branch_copies()
572 572 branch_copies2 = copies.branch_copies()
573 573 diverge = {}
574 574 if followcopies:
575 575 branch_copies1, branch_copies2, diverge = copies.mergecopies(
576 576 repo, wctx, p2, pa
577 577 )
578 578
579 579 boolbm = pycompat.bytestr(bool(branchmerge))
580 580 boolf = pycompat.bytestr(bool(force))
581 581 boolm = pycompat.bytestr(bool(matcher))
582 582 repo.ui.note(_(b"resolving manifests\n"))
583 583 repo.ui.debug(
584 584 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
585 585 )
586 586 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
587 587
588 588 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
589 589 copied1 = set(branch_copies1.copy.values())
590 590 copied1.update(branch_copies1.movewithdir.values())
591 591 copied2 = set(branch_copies2.copy.values())
592 592 copied2.update(branch_copies2.movewithdir.values())
593 593
594 594 if b'.hgsubstate' in m1 and wctx.rev() is None:
595 595 # Check whether sub state is modified, and overwrite the manifest
596 596 # to flag the change. If wctx is a committed revision, we shouldn't
597 597 # care for the dirty state of the working directory.
598 598 if any(wctx.sub(s).dirty() for s in wctx.substate):
599 599 m1[b'.hgsubstate'] = modifiednodeid
600 600
601 601 # Don't use m2-vs-ma optimization if:
602 602 # - ma is the same as m1 or m2, which we're just going to diff again later
603 603 # - The caller specifically asks for a full diff, which is useful during bid
604 604 # merge.
605 605 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
606 606 # Identify which files are relevant to the merge, so we can limit the
607 607 # total m1-vs-m2 diff to just those files. This has significant
608 608 # performance benefits in large repositories.
609 609 relevantfiles = set(ma.diff(m2).keys())
610 610
611 611 # For copied and moved files, we need to add the source file too.
612 612 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
613 613 if copyvalue in relevantfiles:
614 614 relevantfiles.add(copykey)
615 615 for movedirkey in branch_copies1.movewithdir:
616 616 relevantfiles.add(movedirkey)
617 617 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
618 618 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
619 619
620 620 diff = m1.diff(m2, match=matcher)
621 621
622 622 actions = {}
623 623 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
624 624 if n1 and n2: # file exists on both local and remote side
625 625 if f not in ma:
626 626 # TODO: what if they're renamed from different sources?
627 627 fa = branch_copies1.copy.get(
628 628 f, None
629 629 ) or branch_copies2.copy.get(f, None)
630 630 if fa is not None:
631 631 actions[f] = (
632 632 mergestatemod.ACTION_MERGE,
633 633 (f, f, fa, False, pa.node()),
634 634 b'both renamed from %s' % fa,
635 635 )
636 636 else:
637 637 actions[f] = (
638 638 mergestatemod.ACTION_MERGE,
639 639 (f, f, None, False, pa.node()),
640 640 b'both created',
641 641 )
642 642 else:
643 643 a = ma[f]
644 644 fla = ma.flags(f)
645 645 nol = b'l' not in fl1 + fl2 + fla
646 646 if n2 == a and fl2 == fla:
647 647 actions[f] = (
648 648 mergestatemod.ACTION_KEEP,
649 649 (),
650 650 b'remote unchanged',
651 651 )
652 652 elif n1 == a and fl1 == fla: # local unchanged - use remote
653 653 if n1 == n2: # optimization: keep local content
654 654 actions[f] = (
655 655 mergestatemod.ACTION_EXEC,
656 656 (fl2,),
657 657 b'update permissions',
658 658 )
659 659 else:
660 660 actions[f] = (
661 661 mergestatemod.ACTION_GET_OTHER_AND_STORE
662 662 if branchmerge
663 663 else mergestatemod.ACTION_GET,
664 664 (fl2, False),
665 665 b'remote is newer',
666 666 )
667 667 elif nol and n2 == a: # remote only changed 'x'
668 668 actions[f] = (
669 669 mergestatemod.ACTION_EXEC,
670 670 (fl2,),
671 671 b'update permissions',
672 672 )
673 673 elif nol and n1 == a: # local only changed 'x'
674 674 actions[f] = (
675 675 mergestatemod.ACTION_GET_OTHER_AND_STORE
676 676 if branchmerge
677 677 else mergestatemod.ACTION_GET,
678 678 (fl1, False),
679 679 b'remote is newer',
680 680 )
681 681 else: # both changed something
682 682 actions[f] = (
683 683 mergestatemod.ACTION_MERGE,
684 684 (f, f, f, False, pa.node()),
685 685 b'versions differ',
686 686 )
687 687 elif n1: # file exists only on local side
688 688 if f in copied2:
689 689 pass # we'll deal with it on m2 side
690 690 elif (
691 691 f in branch_copies1.movewithdir
692 692 ): # directory rename, move local
693 693 f2 = branch_copies1.movewithdir[f]
694 694 if f2 in m2:
695 695 actions[f2] = (
696 696 mergestatemod.ACTION_MERGE,
697 697 (f, f2, None, True, pa.node()),
698 698 b'remote directory rename, both created',
699 699 )
700 700 else:
701 701 actions[f2] = (
702 702 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
703 703 (f, fl1),
704 704 b'remote directory rename - move from %s' % f,
705 705 )
706 706 elif f in branch_copies1.copy:
707 707 f2 = branch_copies1.copy[f]
708 708 actions[f] = (
709 709 mergestatemod.ACTION_MERGE,
710 710 (f, f2, f2, False, pa.node()),
711 711 b'local copied/moved from %s' % f2,
712 712 )
713 713 elif f in ma: # clean, a different, no remote
714 714 if n1 != ma[f]:
715 715 if acceptremote:
716 716 actions[f] = (
717 717 mergestatemod.ACTION_REMOVE,
718 718 None,
719 719 b'remote delete',
720 720 )
721 721 else:
722 722 actions[f] = (
723 723 mergestatemod.ACTION_CHANGED_DELETED,
724 724 (f, None, f, False, pa.node()),
725 725 b'prompt changed/deleted',
726 726 )
727 727 elif n1 == addednodeid:
728 728 # This extra 'a' is added by working copy manifest to mark
729 729 # the file as locally added. We should forget it instead of
730 730 # deleting it.
731 731 actions[f] = (
732 732 mergestatemod.ACTION_FORGET,
733 733 None,
734 734 b'remote deleted',
735 735 )
736 736 else:
737 737 actions[f] = (
738 738 mergestatemod.ACTION_REMOVE,
739 739 None,
740 740 b'other deleted',
741 741 )
742 742 elif n2: # file exists only on remote side
743 743 if f in copied1:
744 744 pass # we'll deal with it on m1 side
745 745 elif f in branch_copies2.movewithdir:
746 746 f2 = branch_copies2.movewithdir[f]
747 747 if f2 in m1:
748 748 actions[f2] = (
749 749 mergestatemod.ACTION_MERGE,
750 750 (f2, f, None, False, pa.node()),
751 751 b'local directory rename, both created',
752 752 )
753 753 else:
754 754 actions[f2] = (
755 755 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
756 756 (f, fl2),
757 757 b'local directory rename - get from %s' % f,
758 758 )
759 759 elif f in branch_copies2.copy:
760 760 f2 = branch_copies2.copy[f]
761 761 if f2 in m2:
762 762 actions[f] = (
763 763 mergestatemod.ACTION_MERGE,
764 764 (f2, f, f2, False, pa.node()),
765 765 b'remote copied from %s' % f2,
766 766 )
767 767 else:
768 768 actions[f] = (
769 769 mergestatemod.ACTION_MERGE,
770 770 (f2, f, f2, True, pa.node()),
771 771 b'remote moved from %s' % f2,
772 772 )
773 773 elif f not in ma:
774 774 # local unknown, remote created: the logic is described by the
775 775 # following table:
776 776 #
777 777 # force branchmerge different | action
778 778 # n * * | create
779 779 # y n * | create
780 780 # y y n | create
781 781 # y y y | merge
782 782 #
783 783 # Checking whether the files are different is expensive, so we
784 784 # don't do that when we can avoid it.
785 785 if not force:
786 786 actions[f] = (
787 787 mergestatemod.ACTION_CREATED,
788 788 (fl2,),
789 789 b'remote created',
790 790 )
791 791 elif not branchmerge:
792 792 actions[f] = (
793 793 mergestatemod.ACTION_CREATED,
794 794 (fl2,),
795 795 b'remote created',
796 796 )
797 797 else:
798 798 actions[f] = (
799 799 mergestatemod.ACTION_CREATED_MERGE,
800 800 (fl2, pa.node()),
801 801 b'remote created, get or merge',
802 802 )
803 803 elif n2 != ma[f]:
804 804 df = None
805 805 for d in branch_copies1.dirmove:
806 806 if f.startswith(d):
807 807 # new file added in a directory that was moved
808 808 df = branch_copies1.dirmove[d] + f[len(d) :]
809 809 break
810 810 if df is not None and df in m1:
811 811 actions[df] = (
812 812 mergestatemod.ACTION_MERGE,
813 813 (df, f, f, False, pa.node()),
814 814 b'local directory rename - respect move '
815 815 b'from %s' % f,
816 816 )
817 817 elif acceptremote:
818 818 actions[f] = (
819 819 mergestatemod.ACTION_CREATED,
820 820 (fl2,),
821 821 b'remote recreating',
822 822 )
823 823 else:
824 824 actions[f] = (
825 825 mergestatemod.ACTION_DELETED_CHANGED,
826 826 (None, f, f, False, pa.node()),
827 827 b'prompt deleted/changed',
828 828 )
829 829
830 830 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
831 831 # If we are merging, look for path conflicts.
832 832 checkpathconflicts(repo, wctx, p2, actions)
833 833
834 834 narrowmatch = repo.narrowmatch()
835 835 if not narrowmatch.always():
836 836 # Updates "actions" in place
837 837 _filternarrowactions(narrowmatch, branchmerge, actions)
838 838
839 839 renamedelete = branch_copies1.renamedelete
840 840 renamedelete.update(branch_copies2.renamedelete)
841 841
842 842 return actions, diverge, renamedelete
843 843
844 844
845 845 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
846 846 """Resolves false conflicts where the nodeid changed but the content
847 847 remained the same."""
848 848 # We force a copy of actions.items() because we're going to mutate
849 849 # actions as we resolve trivial conflicts.
850 850 for f, (m, args, msg) in list(actions.items()):
851 851 if (
852 852 m == mergestatemod.ACTION_CHANGED_DELETED
853 853 and f in ancestor
854 854 and not wctx[f].cmp(ancestor[f])
855 855 ):
856 856 # local did change but ended up with same content
857 857 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
858 858 elif (
859 859 m == mergestatemod.ACTION_DELETED_CHANGED
860 860 and f in ancestor
861 861 and not mctx[f].cmp(ancestor[f])
862 862 ):
863 863 # remote did change but ended up with same content
864 864 del actions[f] # don't get = keep local deleted
865 865
866 866
867 867 def calculateupdates(
868 868 repo,
869 869 wctx,
870 870 mctx,
871 871 ancestors,
872 872 branchmerge,
873 873 force,
874 874 acceptremote,
875 875 followcopies,
876 876 matcher=None,
877 877 mergeforce=False,
878 878 ):
879 879 """Calculate the actions needed to merge mctx into wctx using ancestors"""
880 880 # Avoid cycle.
881 881 from . import sparse
882 882
883 883 if len(ancestors) == 1: # default
884 884 actions, diverge, renamedelete = manifestmerge(
885 885 repo,
886 886 wctx,
887 887 mctx,
888 888 ancestors[0],
889 889 branchmerge,
890 890 force,
891 891 matcher,
892 892 acceptremote,
893 893 followcopies,
894 894 )
895 895 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
896 896
897 897 else: # only when merge.preferancestor=* - the default
898 898 repo.ui.note(
899 899 _(b"note: merging %s and %s using bids from ancestors %s\n")
900 900 % (
901 901 wctx,
902 902 mctx,
903 903 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
904 904 )
905 905 )
906 906
907 907 # Call for bids
908 908 fbids = (
909 909 {}
910 910 ) # mapping filename to bids (action method to list af actions)
911 911 diverge, renamedelete = None, None
912 912 for ancestor in ancestors:
913 913 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
914 914 actions, diverge1, renamedelete1 = manifestmerge(
915 915 repo,
916 916 wctx,
917 917 mctx,
918 918 ancestor,
919 919 branchmerge,
920 920 force,
921 921 matcher,
922 922 acceptremote,
923 923 followcopies,
924 924 forcefulldiff=True,
925 925 )
926 926 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
927 927
928 928 # Track the shortest set of warning on the theory that bid
929 929 # merge will correctly incorporate more information
930 930 if diverge is None or len(diverge1) < len(diverge):
931 931 diverge = diverge1
932 932 if renamedelete is None or len(renamedelete) < len(renamedelete1):
933 933 renamedelete = renamedelete1
934 934
935 935 for f, a in sorted(pycompat.iteritems(actions)):
936 936 m, args, msg = a
937 937 if m == mergestatemod.ACTION_GET_OTHER_AND_STORE:
938 938 m = mergestatemod.ACTION_GET
939 939 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
940 940 if f in fbids:
941 941 d = fbids[f]
942 942 if m in d:
943 943 d[m].append(a)
944 944 else:
945 945 d[m] = [a]
946 946 else:
947 947 fbids[f] = {m: [a]}
948 948
949 949 # Pick the best bid for each file
950 950 repo.ui.note(_(b'\nauction for merging merge bids\n'))
951 951 actions = {}
952 952 for f, bids in sorted(fbids.items()):
953 953 # bids is a mapping from action method to list af actions
954 954 # Consensus?
955 955 if len(bids) == 1: # all bids are the same kind of method
956 956 m, l = list(bids.items())[0]
957 957 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
958 958 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
959 959 actions[f] = l[0]
960 960 continue
961 961 # If keep is an option, just do it.
962 962 if mergestatemod.ACTION_KEEP in bids:
963 963 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
964 964 actions[f] = bids[mergestatemod.ACTION_KEEP][0]
965 965 continue
966 966 # If there are gets and they all agree [how could they not?], do it.
967 967 if mergestatemod.ACTION_GET in bids:
968 968 ga0 = bids[mergestatemod.ACTION_GET][0]
969 969 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
970 970 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
971 971 actions[f] = ga0
972 972 continue
973 973 # TODO: Consider other simple actions such as mode changes
974 974 # Handle inefficient democrazy.
975 975 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
976 976 for m, l in sorted(bids.items()):
977 977 for _f, args, msg in l:
978 978 repo.ui.note(b' %s -> %s\n' % (msg, m))
979 979 # Pick random action. TODO: Instead, prompt user when resolving
980 980 m, l = list(bids.items())[0]
981 981 repo.ui.warn(
982 982 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
983 983 )
984 984 actions[f] = l[0]
985 985 continue
986 986 repo.ui.note(_(b'end of auction\n\n'))
987 987
988 988 if wctx.rev() is None:
989 989 fractions = _forgetremoved(wctx, mctx, branchmerge)
990 990 actions.update(fractions)
991 991
992 992 prunedactions = sparse.filterupdatesactions(
993 993 repo, wctx, mctx, branchmerge, actions
994 994 )
995 995 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
996 996
997 997 return prunedactions, diverge, renamedelete
998 998
999 999
1000 1000 def _getcwd():
1001 1001 try:
1002 1002 return encoding.getcwd()
1003 1003 except OSError as err:
1004 1004 if err.errno == errno.ENOENT:
1005 1005 return None
1006 1006 raise
1007 1007
1008 1008
1009 1009 def batchremove(repo, wctx, actions):
1010 1010 """apply removes to the working directory
1011 1011
1012 1012 yields tuples for progress updates
1013 1013 """
1014 1014 verbose = repo.ui.verbose
1015 1015 cwd = _getcwd()
1016 1016 i = 0
1017 1017 for f, args, msg in actions:
1018 1018 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1019 1019 if verbose:
1020 1020 repo.ui.note(_(b"removing %s\n") % f)
1021 1021 wctx[f].audit()
1022 1022 try:
1023 1023 wctx[f].remove(ignoremissing=True)
1024 1024 except OSError as inst:
1025 1025 repo.ui.warn(
1026 1026 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1027 1027 )
1028 1028 if i == 100:
1029 1029 yield i, f
1030 1030 i = 0
1031 1031 i += 1
1032 1032 if i > 0:
1033 1033 yield i, f
1034 1034
1035 1035 if cwd and not _getcwd():
1036 1036 # cwd was removed in the course of removing files; print a helpful
1037 1037 # warning.
1038 1038 repo.ui.warn(
1039 1039 _(
1040 1040 b"current directory was removed\n"
1041 1041 b"(consider changing to repo root: %s)\n"
1042 1042 )
1043 1043 % repo.root
1044 1044 )
1045 1045
1046 1046
1047 1047 def batchget(repo, mctx, wctx, wantfiledata, actions):
1048 1048 """apply gets to the working directory
1049 1049
1050 1050 mctx is the context to get from
1051 1051
1052 1052 Yields arbitrarily many (False, tuple) for progress updates, followed by
1053 1053 exactly one (True, filedata). When wantfiledata is false, filedata is an
1054 1054 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1055 1055 mtime) of the file f written for each action.
1056 1056 """
1057 1057 filedata = {}
1058 1058 verbose = repo.ui.verbose
1059 1059 fctx = mctx.filectx
1060 1060 ui = repo.ui
1061 1061 i = 0
1062 1062 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1063 1063 for f, (flags, backup), msg in actions:
1064 1064 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1065 1065 if verbose:
1066 1066 repo.ui.note(_(b"getting %s\n") % f)
1067 1067
1068 1068 if backup:
1069 1069 # If a file or directory exists with the same name, back that
1070 1070 # up. Otherwise, look to see if there is a file that conflicts
1071 1071 # with a directory this file is in, and if so, back that up.
1072 1072 conflicting = f
1073 1073 if not repo.wvfs.lexists(f):
1074 1074 for p in pathutil.finddirs(f):
1075 1075 if repo.wvfs.isfileorlink(p):
1076 1076 conflicting = p
1077 1077 break
1078 1078 if repo.wvfs.lexists(conflicting):
1079 1079 orig = scmutil.backuppath(ui, repo, conflicting)
1080 1080 util.rename(repo.wjoin(conflicting), orig)
1081 1081 wfctx = wctx[f]
1082 1082 wfctx.clearunknown()
1083 1083 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1084 1084 size = wfctx.write(
1085 1085 fctx(f).data(),
1086 1086 flags,
1087 1087 backgroundclose=True,
1088 1088 atomictemp=atomictemp,
1089 1089 )
1090 1090 if wantfiledata:
1091 1091 s = wfctx.lstat()
1092 1092 mode = s.st_mode
1093 1093 mtime = s[stat.ST_MTIME]
1094 1094 filedata[f] = (mode, size, mtime) # for dirstate.normal
1095 1095 if i == 100:
1096 1096 yield False, (i, f)
1097 1097 i = 0
1098 1098 i += 1
1099 1099 if i > 0:
1100 1100 yield False, (i, f)
1101 1101 yield True, filedata
1102 1102
1103 1103
1104 1104 def _prefetchfiles(repo, ctx, actions):
1105 1105 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1106 1106 of merge actions. ``ctx`` is the context being merged in."""
1107 1107
1108 1108 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1109 1109 # don't touch the context to be merged in. 'cd' is skipped, because
1110 1110 # changed/deleted never resolves to something from the remote side.
1111 1111 oplist = [
1112 1112 actions[a]
1113 1113 for a in (
1114 1114 mergestatemod.ACTION_GET,
1115 1115 mergestatemod.ACTION_DELETED_CHANGED,
1116 1116 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1117 1117 mergestatemod.ACTION_MERGE,
1118 1118 )
1119 1119 ]
1120 1120 prefetch = scmutil.prefetchfiles
1121 1121 matchfiles = scmutil.matchfiles
1122 1122 prefetch(
1123 1123 repo,
1124 1124 [ctx.rev()],
1125 1125 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1126 1126 )
1127 1127
1128 1128
1129 1129 @attr.s(frozen=True)
1130 1130 class updateresult(object):
1131 1131 updatedcount = attr.ib()
1132 1132 mergedcount = attr.ib()
1133 1133 removedcount = attr.ib()
1134 1134 unresolvedcount = attr.ib()
1135 1135
1136 1136 def isempty(self):
1137 1137 return not (
1138 1138 self.updatedcount
1139 1139 or self.mergedcount
1140 1140 or self.removedcount
1141 1141 or self.unresolvedcount
1142 1142 )
1143 1143
1144 1144
1145 1145 def emptyactions():
1146 1146 """create an actions dict, to be populated and passed to applyupdates()"""
1147 1147 return {
1148 1148 m: []
1149 1149 for m in (
1150 1150 mergestatemod.ACTION_ADD,
1151 1151 mergestatemod.ACTION_ADD_MODIFIED,
1152 1152 mergestatemod.ACTION_FORGET,
1153 1153 mergestatemod.ACTION_GET,
1154 1154 mergestatemod.ACTION_CHANGED_DELETED,
1155 1155 mergestatemod.ACTION_DELETED_CHANGED,
1156 1156 mergestatemod.ACTION_REMOVE,
1157 1157 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1158 1158 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1159 1159 mergestatemod.ACTION_MERGE,
1160 1160 mergestatemod.ACTION_EXEC,
1161 1161 mergestatemod.ACTION_KEEP,
1162 1162 mergestatemod.ACTION_PATH_CONFLICT,
1163 1163 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1164 1164 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1165 1165 )
1166 1166 }
1167 1167
1168 1168
1169 1169 def applyupdates(
1170 1170 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1171 1171 ):
1172 1172 """apply the merge action list to the working directory
1173 1173
1174 1174 wctx is the working copy context
1175 1175 mctx is the context to be merged into the working copy
1176 1176
1177 1177 Return a tuple of (counts, filedata), where counts is a tuple
1178 1178 (updated, merged, removed, unresolved) that describes how many
1179 1179 files were affected by the update, and filedata is as described in
1180 1180 batchget.
1181 1181 """
1182 1182
1183 1183 _prefetchfiles(repo, mctx, actions)
1184 1184
1185 1185 updated, merged, removed = 0, 0, 0
1186 1186 ms = mergestatemod.mergestate.clean(
1187 1187 repo, wctx.p1().node(), mctx.node(), labels
1188 1188 )
1189 1189
1190 1190 # add ACTION_GET_OTHER_AND_STORE to mergestate
1191 1191 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1192 1192 ms.addmergedother(e[0])
1193 1193
1194 1194 moves = []
1195 1195 for m, l in actions.items():
1196 1196 l.sort()
1197 1197
1198 1198 # 'cd' and 'dc' actions are treated like other merge conflicts
1199 1199 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1200 1200 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1201 1201 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1202 1202 for f, args, msg in mergeactions:
1203 1203 f1, f2, fa, move, anc = args
1204 1204 if f == b'.hgsubstate': # merged internally
1205 1205 continue
1206 1206 if f1 is None:
1207 1207 fcl = filemerge.absentfilectx(wctx, fa)
1208 1208 else:
1209 1209 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1210 1210 fcl = wctx[f1]
1211 1211 if f2 is None:
1212 1212 fco = filemerge.absentfilectx(mctx, fa)
1213 1213 else:
1214 1214 fco = mctx[f2]
1215 1215 actx = repo[anc]
1216 1216 if fa in actx:
1217 1217 fca = actx[fa]
1218 1218 else:
1219 1219 # TODO: move to absentfilectx
1220 1220 fca = repo.filectx(f1, fileid=nullrev)
1221 1221 ms.add(fcl, fco, fca, f)
1222 1222 if f1 != f and move:
1223 1223 moves.append(f1)
1224 1224
1225 1225 # remove renamed files after safely stored
1226 1226 for f in moves:
1227 1227 if wctx[f].lexists():
1228 1228 repo.ui.debug(b"removing %s\n" % f)
1229 1229 wctx[f].audit()
1230 1230 wctx[f].remove()
1231 1231
1232 1232 numupdates = sum(
1233 1233 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1234 1234 )
1235 1235 progress = repo.ui.makeprogress(
1236 1236 _(b'updating'), unit=_(b'files'), total=numupdates
1237 1237 )
1238 1238
1239 1239 if [
1240 1240 a
1241 1241 for a in actions[mergestatemod.ACTION_REMOVE]
1242 1242 if a[0] == b'.hgsubstate'
1243 1243 ]:
1244 1244 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1245 1245
1246 1246 # record path conflicts
1247 1247 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1248 1248 f1, fo = args
1249 1249 s = repo.ui.status
1250 1250 s(
1251 1251 _(
1252 1252 b"%s: path conflict - a file or link has the same name as a "
1253 1253 b"directory\n"
1254 1254 )
1255 1255 % f
1256 1256 )
1257 1257 if fo == b'l':
1258 1258 s(_(b"the local file has been renamed to %s\n") % f1)
1259 1259 else:
1260 1260 s(_(b"the remote file has been renamed to %s\n") % f1)
1261 1261 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1262 1262 ms.addpath(f, f1, fo)
1263 1263 progress.increment(item=f)
1264 1264
1265 1265 # When merging in-memory, we can't support worker processes, so set the
1266 1266 # per-item cost at 0 in that case.
1267 1267 cost = 0 if wctx.isinmemory() else 0.001
1268 1268
1269 1269 # remove in parallel (must come before resolving path conflicts and getting)
1270 1270 prog = worker.worker(
1271 1271 repo.ui,
1272 1272 cost,
1273 1273 batchremove,
1274 1274 (repo, wctx),
1275 1275 actions[mergestatemod.ACTION_REMOVE],
1276 1276 )
1277 1277 for i, item in prog:
1278 1278 progress.increment(step=i, item=item)
1279 1279 removed = len(actions[mergestatemod.ACTION_REMOVE])
1280 1280
1281 1281 # resolve path conflicts (must come before getting)
1282 1282 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1283 1283 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1284 1284 (f0, origf0) = args
1285 1285 if wctx[f0].lexists():
1286 1286 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1287 1287 wctx[f].audit()
1288 1288 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1289 1289 wctx[f0].remove()
1290 1290 progress.increment(item=f)
1291 1291
1292 1292 # get in parallel.
1293 1293 threadsafe = repo.ui.configbool(
1294 1294 b'experimental', b'worker.wdir-get-thread-safe'
1295 1295 )
1296 1296 prog = worker.worker(
1297 1297 repo.ui,
1298 1298 cost,
1299 1299 batchget,
1300 1300 (repo, mctx, wctx, wantfiledata),
1301 1301 actions[mergestatemod.ACTION_GET],
1302 1302 threadsafe=threadsafe,
1303 1303 hasretval=True,
1304 1304 )
1305 1305 getfiledata = {}
1306 1306 for final, res in prog:
1307 1307 if final:
1308 1308 getfiledata = res
1309 1309 else:
1310 1310 i, item = res
1311 1311 progress.increment(step=i, item=item)
1312 1312 updated = len(actions[mergestatemod.ACTION_GET])
1313 1313
1314 1314 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1315 1315 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1316 1316
1317 1317 # forget (manifest only, just log it) (must come first)
1318 1318 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1319 1319 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1320 1320 progress.increment(item=f)
1321 1321
1322 1322 # re-add (manifest only, just log it)
1323 1323 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1324 1324 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1325 1325 progress.increment(item=f)
1326 1326
1327 1327 # re-add/mark as modified (manifest only, just log it)
1328 1328 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1329 1329 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1330 1330 progress.increment(item=f)
1331 1331
1332 1332 # keep (noop, just log it)
1333 1333 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1334 1334 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1335 1335 # no progress
1336 1336
1337 1337 # directory rename, move local
1338 1338 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1339 1339 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1340 1340 progress.increment(item=f)
1341 1341 f0, flags = args
1342 1342 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1343 1343 wctx[f].audit()
1344 1344 wctx[f].write(wctx.filectx(f0).data(), flags)
1345 1345 wctx[f0].remove()
1346 1346 updated += 1
1347 1347
1348 1348 # local directory rename, get
1349 1349 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1350 1350 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1351 1351 progress.increment(item=f)
1352 1352 f0, flags = args
1353 1353 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1354 1354 wctx[f].write(mctx.filectx(f0).data(), flags)
1355 1355 updated += 1
1356 1356
1357 1357 # exec
1358 1358 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1359 1359 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1360 1360 progress.increment(item=f)
1361 1361 (flags,) = args
1362 1362 wctx[f].audit()
1363 1363 wctx[f].setflags(b'l' in flags, b'x' in flags)
1364 1364 updated += 1
1365 1365
1366 1366 # the ordering is important here -- ms.mergedriver will raise if the merge
1367 1367 # driver has changed, and we want to be able to bypass it when overwrite is
1368 1368 # True
1369 1369 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1370 1370
1371 1371 if usemergedriver:
1372 1372 if wctx.isinmemory():
1373 1373 raise error.InMemoryMergeConflictsError(
1374 1374 b"in-memory merge does not support mergedriver"
1375 1375 )
1376 1376 ms.commit()
1377 1377 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1378 1378 # the driver might leave some files unresolved
1379 1379 unresolvedf = set(ms.unresolved())
1380 1380 if not proceed:
1381 1381 # XXX setting unresolved to at least 1 is a hack to make sure we
1382 1382 # error out
1383 1383 return updateresult(
1384 1384 updated, merged, removed, max(len(unresolvedf), 1)
1385 1385 )
1386 1386 newactions = []
1387 1387 for f, args, msg in mergeactions:
1388 1388 if f in unresolvedf:
1389 1389 newactions.append((f, args, msg))
1390 1390 mergeactions = newactions
1391 1391
1392 1392 try:
1393 1393 # premerge
1394 1394 tocomplete = []
1395 1395 for f, args, msg in mergeactions:
1396 1396 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1397 1397 progress.increment(item=f)
1398 1398 if f == b'.hgsubstate': # subrepo states need updating
1399 1399 subrepoutil.submerge(
1400 1400 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1401 1401 )
1402 1402 continue
1403 1403 wctx[f].audit()
1404 1404 complete, r = ms.preresolve(f, wctx)
1405 1405 if not complete:
1406 1406 numupdates += 1
1407 1407 tocomplete.append((f, args, msg))
1408 1408
1409 1409 # merge
1410 1410 for f, args, msg in tocomplete:
1411 1411 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1412 1412 progress.increment(item=f, total=numupdates)
1413 1413 ms.resolve(f, wctx)
1414 1414
1415 1415 finally:
1416 1416 ms.commit()
1417 1417
1418 1418 unresolved = ms.unresolvedcount()
1419 1419
1420 1420 if (
1421 1421 usemergedriver
1422 1422 and not unresolved
1423 1423 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1424 1424 ):
1425 1425 if not driverconclude(repo, ms, wctx, labels=labels):
1426 1426 # XXX setting unresolved to at least 1 is a hack to make sure we
1427 1427 # error out
1428 1428 unresolved = max(unresolved, 1)
1429 1429
1430 1430 ms.commit()
1431 1431
1432 1432 msupdated, msmerged, msremoved = ms.counts()
1433 1433 updated += msupdated
1434 1434 merged += msmerged
1435 1435 removed += msremoved
1436 1436
1437 1437 extraactions = ms.actions()
1438 1438 if extraactions:
1439 1439 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1440 1440 for k, acts in pycompat.iteritems(extraactions):
1441 1441 actions[k].extend(acts)
1442 1442 if k == mergestatemod.ACTION_GET and wantfiledata:
1443 1443 # no filedata until mergestate is updated to provide it
1444 1444 for a in acts:
1445 1445 getfiledata[a[0]] = None
1446 1446 # Remove these files from actions[ACTION_MERGE] as well. This is
1447 1447 # important because in recordupdates, files in actions[ACTION_MERGE]
1448 1448 # are processed after files in other actions, and the merge driver
1449 1449 # might add files to those actions via extraactions above. This can
1450 1450 # lead to a file being recorded twice, with poor results. This is
1451 1451 # especially problematic for actions[ACTION_REMOVE] (currently only
1452 1452 # possible with the merge driver in the initial merge process;
1453 1453 # interrupted merges don't go through this flow).
1454 1454 #
1455 1455 # The real fix here is to have indexes by both file and action so
1456 1456 # that when the action for a file is changed it is automatically
1457 1457 # reflected in the other action lists. But that involves a more
1458 1458 # complex data structure, so this will do for now.
1459 1459 #
1460 1460 # We don't need to do the same operation for 'dc' and 'cd' because
1461 1461 # those lists aren't consulted again.
1462 1462 mfiles.difference_update(a[0] for a in acts)
1463 1463
1464 1464 actions[mergestatemod.ACTION_MERGE] = [
1465 1465 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1466 1466 ]
1467 1467
1468 1468 progress.complete()
1469 1469 assert len(getfiledata) == (
1470 1470 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1471 1471 )
1472 1472 return updateresult(updated, merged, removed, unresolved), getfiledata
1473 1473
1474 1474
1475 1475 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1476 1476 UPDATECHECK_NONE = b'none'
1477 1477 UPDATECHECK_LINEAR = b'linear'
1478 1478 UPDATECHECK_NO_CONFLICT = b'noconflict'
1479 1479
1480 1480
1481 1481 def update(
1482 1482 repo,
1483 1483 node,
1484 1484 branchmerge,
1485 1485 force,
1486 1486 ancestor=None,
1487 1487 mergeancestor=False,
1488 1488 labels=None,
1489 1489 matcher=None,
1490 1490 mergeforce=False,
1491 1491 updatedirstate=True,
1492 1492 updatecheck=None,
1493 1493 wc=None,
1494 1494 ):
1495 1495 """
1496 1496 Perform a merge between the working directory and the given node
1497 1497
1498 1498 node = the node to update to
1499 1499 branchmerge = whether to merge between branches
1500 1500 force = whether to force branch merging or file overwriting
1501 1501 matcher = a matcher to filter file lists (dirstate not updated)
1502 1502 mergeancestor = whether it is merging with an ancestor. If true,
1503 1503 we should accept the incoming changes for any prompts that occur.
1504 1504 If false, merging with an ancestor (fast-forward) is only allowed
1505 1505 between different named branches. This flag is used by rebase extension
1506 1506 as a temporary fix and should be avoided in general.
1507 1507 labels = labels to use for base, local and other
1508 1508 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1509 1509 this is True, then 'force' should be True as well.
1510 1510
1511 1511 The table below shows all the behaviors of the update command given the
1512 1512 -c/--check and -C/--clean or no options, whether the working directory is
1513 1513 dirty, whether a revision is specified, and the relationship of the parent
1514 1514 rev to the target rev (linear or not). Match from top first. The -n
1515 1515 option doesn't exist on the command line, but represents the
1516 1516 experimental.updatecheck=noconflict option.
1517 1517
1518 1518 This logic is tested by test-update-branches.t.
1519 1519
1520 1520 -c -C -n -m dirty rev linear | result
1521 1521 y y * * * * * | (1)
1522 1522 y * y * * * * | (1)
1523 1523 y * * y * * * | (1)
1524 1524 * y y * * * * | (1)
1525 1525 * y * y * * * | (1)
1526 1526 * * y y * * * | (1)
1527 1527 * * * * * n n | x
1528 1528 * * * * n * * | ok
1529 1529 n n n n y * y | merge
1530 1530 n n n n y y n | (2)
1531 1531 n n n y y * * | merge
1532 1532 n n y n y * * | merge if no conflict
1533 1533 n y n n y * * | discard
1534 1534 y n n n y * * | (3)
1535 1535
1536 1536 x = can't happen
1537 1537 * = don't-care
1538 1538 1 = incompatible options (checked in commands.py)
1539 1539 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1540 1540 3 = abort: uncommitted changes (checked in commands.py)
1541 1541
1542 1542 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1543 1543 to repo[None] if None is passed.
1544 1544
1545 1545 Return the same tuple as applyupdates().
1546 1546 """
1547 1547 # Avoid cycle.
1548 1548 from . import sparse
1549 1549
1550 1550 # This function used to find the default destination if node was None, but
1551 1551 # that's now in destutil.py.
1552 1552 assert node is not None
1553 1553 if not branchmerge and not force:
1554 1554 # TODO: remove the default once all callers that pass branchmerge=False
1555 1555 # and force=False pass a value for updatecheck. We may want to allow
1556 1556 # updatecheck='abort' to better suppport some of these callers.
1557 1557 if updatecheck is None:
1558 1558 updatecheck = UPDATECHECK_LINEAR
1559 1559 if updatecheck not in (
1560 1560 UPDATECHECK_NONE,
1561 1561 UPDATECHECK_LINEAR,
1562 1562 UPDATECHECK_NO_CONFLICT,
1563 1563 ):
1564 1564 raise ValueError(
1565 1565 r'Invalid updatecheck %r (can accept %r)'
1566 1566 % (
1567 1567 updatecheck,
1568 1568 (
1569 1569 UPDATECHECK_NONE,
1570 1570 UPDATECHECK_LINEAR,
1571 1571 UPDATECHECK_NO_CONFLICT,
1572 1572 ),
1573 1573 )
1574 1574 )
1575 with repo.wlock():
1575 if wc is not None and wc.isinmemory():
1576 maybe_wlock = util.nullcontextmanager()
1577 else:
1578 maybe_wlock = repo.wlock()
1579 with maybe_wlock:
1576 1580 if wc is None:
1577 1581 wc = repo[None]
1578 1582 pl = wc.parents()
1579 1583 p1 = pl[0]
1580 1584 p2 = repo[node]
1581 1585 if ancestor is not None:
1582 1586 pas = [repo[ancestor]]
1583 1587 else:
1584 1588 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1585 1589 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1586 1590 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1587 1591 else:
1588 1592 pas = [p1.ancestor(p2, warn=branchmerge)]
1589 1593
1590 1594 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1591 1595
1592 1596 overwrite = force and not branchmerge
1593 1597 ### check phase
1594 1598 if not overwrite:
1595 1599 if len(pl) > 1:
1596 1600 raise error.Abort(_(b"outstanding uncommitted merge"))
1597 1601 ms = mergestatemod.mergestate.read(repo)
1598 1602 if list(ms.unresolved()):
1599 1603 raise error.Abort(
1600 1604 _(b"outstanding merge conflicts"),
1601 1605 hint=_(b"use 'hg resolve' to resolve"),
1602 1606 )
1603 1607 if branchmerge:
1604 1608 if pas == [p2]:
1605 1609 raise error.Abort(
1606 1610 _(
1607 1611 b"merging with a working directory ancestor"
1608 1612 b" has no effect"
1609 1613 )
1610 1614 )
1611 1615 elif pas == [p1]:
1612 1616 if not mergeancestor and wc.branch() == p2.branch():
1613 1617 raise error.Abort(
1614 1618 _(b"nothing to merge"),
1615 1619 hint=_(b"use 'hg update' or check 'hg heads'"),
1616 1620 )
1617 1621 if not force and (wc.files() or wc.deleted()):
1618 1622 raise error.Abort(
1619 1623 _(b"uncommitted changes"),
1620 1624 hint=_(b"use 'hg status' to list changes"),
1621 1625 )
1622 1626 if not wc.isinmemory():
1623 1627 for s in sorted(wc.substate):
1624 1628 wc.sub(s).bailifchanged()
1625 1629
1626 1630 elif not overwrite:
1627 1631 if p1 == p2: # no-op update
1628 1632 # call the hooks and exit early
1629 1633 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1630 1634 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1631 1635 return updateresult(0, 0, 0, 0)
1632 1636
1633 1637 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1634 1638 [p1],
1635 1639 [p2],
1636 1640 ): # nonlinear
1637 1641 dirty = wc.dirty(missing=True)
1638 1642 if dirty:
1639 1643 # Branching is a bit strange to ensure we do the minimal
1640 1644 # amount of call to obsutil.foreground.
1641 1645 foreground = obsutil.foreground(repo, [p1.node()])
1642 1646 # note: the <node> variable contains a random identifier
1643 1647 if repo[node].node() in foreground:
1644 1648 pass # allow updating to successors
1645 1649 else:
1646 1650 msg = _(b"uncommitted changes")
1647 1651 hint = _(b"commit or update --clean to discard changes")
1648 1652 raise error.UpdateAbort(msg, hint=hint)
1649 1653 else:
1650 1654 # Allow jumping branches if clean and specific rev given
1651 1655 pass
1652 1656
1653 1657 if overwrite:
1654 1658 pas = [wc]
1655 1659 elif not branchmerge:
1656 1660 pas = [p1]
1657 1661
1658 1662 # deprecated config: merge.followcopies
1659 1663 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1660 1664 if overwrite:
1661 1665 followcopies = False
1662 1666 elif not pas[0]:
1663 1667 followcopies = False
1664 1668 if not branchmerge and not wc.dirty(missing=True):
1665 1669 followcopies = False
1666 1670
1667 1671 ### calculate phase
1668 1672 actionbyfile, diverge, renamedelete = calculateupdates(
1669 1673 repo,
1670 1674 wc,
1671 1675 p2,
1672 1676 pas,
1673 1677 branchmerge,
1674 1678 force,
1675 1679 mergeancestor,
1676 1680 followcopies,
1677 1681 matcher=matcher,
1678 1682 mergeforce=mergeforce,
1679 1683 )
1680 1684
1681 1685 if updatecheck == UPDATECHECK_NO_CONFLICT:
1682 1686 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1683 1687 if m not in (
1684 1688 mergestatemod.ACTION_GET,
1685 1689 mergestatemod.ACTION_KEEP,
1686 1690 mergestatemod.ACTION_EXEC,
1687 1691 mergestatemod.ACTION_REMOVE,
1688 1692 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1689 1693 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1690 1694 ):
1691 1695 msg = _(b"conflicting changes")
1692 1696 hint = _(b"commit or update --clean to discard changes")
1693 1697 raise error.Abort(msg, hint=hint)
1694 1698
1695 1699 # Prompt and create actions. Most of this is in the resolve phase
1696 1700 # already, but we can't handle .hgsubstate in filemerge or
1697 1701 # subrepoutil.submerge yet so we have to keep prompting for it.
1698 1702 if b'.hgsubstate' in actionbyfile:
1699 1703 f = b'.hgsubstate'
1700 1704 m, args, msg = actionbyfile[f]
1701 1705 prompts = filemerge.partextras(labels)
1702 1706 prompts[b'f'] = f
1703 1707 if m == mergestatemod.ACTION_CHANGED_DELETED:
1704 1708 if repo.ui.promptchoice(
1705 1709 _(
1706 1710 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1707 1711 b"use (c)hanged version or (d)elete?"
1708 1712 b"$$ &Changed $$ &Delete"
1709 1713 )
1710 1714 % prompts,
1711 1715 0,
1712 1716 ):
1713 1717 actionbyfile[f] = (
1714 1718 mergestatemod.ACTION_REMOVE,
1715 1719 None,
1716 1720 b'prompt delete',
1717 1721 )
1718 1722 elif f in p1:
1719 1723 actionbyfile[f] = (
1720 1724 mergestatemod.ACTION_ADD_MODIFIED,
1721 1725 None,
1722 1726 b'prompt keep',
1723 1727 )
1724 1728 else:
1725 1729 actionbyfile[f] = (
1726 1730 mergestatemod.ACTION_ADD,
1727 1731 None,
1728 1732 b'prompt keep',
1729 1733 )
1730 1734 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1731 1735 f1, f2, fa, move, anc = args
1732 1736 flags = p2[f2].flags()
1733 1737 if (
1734 1738 repo.ui.promptchoice(
1735 1739 _(
1736 1740 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1737 1741 b"use (c)hanged version or leave (d)eleted?"
1738 1742 b"$$ &Changed $$ &Deleted"
1739 1743 )
1740 1744 % prompts,
1741 1745 0,
1742 1746 )
1743 1747 == 0
1744 1748 ):
1745 1749 actionbyfile[f] = (
1746 1750 mergestatemod.ACTION_GET,
1747 1751 (flags, False),
1748 1752 b'prompt recreating',
1749 1753 )
1750 1754 else:
1751 1755 del actionbyfile[f]
1752 1756
1753 1757 # Convert to dictionary-of-lists format
1754 1758 actions = emptyactions()
1755 1759 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1756 1760 if m not in actions:
1757 1761 actions[m] = []
1758 1762 actions[m].append((f, args, msg))
1759 1763
1760 1764 # ACTION_GET_OTHER_AND_STORE is a mergestatemod.ACTION_GET + store in mergestate
1761 1765 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1762 1766 actions[mergestatemod.ACTION_GET].append(e)
1763 1767
1764 1768 if not util.fscasesensitive(repo.path):
1765 1769 # check collision between files only in p2 for clean update
1766 1770 if not branchmerge and (
1767 1771 force or not wc.dirty(missing=True, branch=False)
1768 1772 ):
1769 1773 _checkcollision(repo, p2.manifest(), None)
1770 1774 else:
1771 1775 _checkcollision(repo, wc.manifest(), actions)
1772 1776
1773 1777 # divergent renames
1774 1778 for f, fl in sorted(pycompat.iteritems(diverge)):
1775 1779 repo.ui.warn(
1776 1780 _(
1777 1781 b"note: possible conflict - %s was renamed "
1778 1782 b"multiple times to:\n"
1779 1783 )
1780 1784 % f
1781 1785 )
1782 1786 for nf in sorted(fl):
1783 1787 repo.ui.warn(b" %s\n" % nf)
1784 1788
1785 1789 # rename and delete
1786 1790 for f, fl in sorted(pycompat.iteritems(renamedelete)):
1787 1791 repo.ui.warn(
1788 1792 _(
1789 1793 b"note: possible conflict - %s was deleted "
1790 1794 b"and renamed to:\n"
1791 1795 )
1792 1796 % f
1793 1797 )
1794 1798 for nf in sorted(fl):
1795 1799 repo.ui.warn(b" %s\n" % nf)
1796 1800
1797 1801 ### apply phase
1798 1802 if not branchmerge: # just jump to the new rev
1799 1803 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1800 1804 # If we're doing a partial update, we need to skip updating
1801 1805 # the dirstate.
1802 1806 always = matcher is None or matcher.always()
1803 1807 updatedirstate = updatedirstate and always and not wc.isinmemory()
1804 1808 if updatedirstate:
1805 1809 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1806 1810 # note that we're in the middle of an update
1807 1811 repo.vfs.write(b'updatestate', p2.hex())
1808 1812
1809 1813 # Advertise fsmonitor when its presence could be useful.
1810 1814 #
1811 1815 # We only advertise when performing an update from an empty working
1812 1816 # directory. This typically only occurs during initial clone.
1813 1817 #
1814 1818 # We give users a mechanism to disable the warning in case it is
1815 1819 # annoying.
1816 1820 #
1817 1821 # We only allow on Linux and MacOS because that's where fsmonitor is
1818 1822 # considered stable.
1819 1823 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1820 1824 fsmonitorthreshold = repo.ui.configint(
1821 1825 b'fsmonitor', b'warn_update_file_count'
1822 1826 )
1823 1827 try:
1824 1828 # avoid cycle: extensions -> cmdutil -> merge
1825 1829 from . import extensions
1826 1830
1827 1831 extensions.find(b'fsmonitor')
1828 1832 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1829 1833 # We intentionally don't look at whether fsmonitor has disabled
1830 1834 # itself because a) fsmonitor may have already printed a warning
1831 1835 # b) we only care about the config state here.
1832 1836 except KeyError:
1833 1837 fsmonitorenabled = False
1834 1838
1835 1839 if (
1836 1840 fsmonitorwarning
1837 1841 and not fsmonitorenabled
1838 1842 and p1.node() == nullid
1839 1843 and len(actions[mergestatemod.ACTION_GET]) >= fsmonitorthreshold
1840 1844 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1841 1845 ):
1842 1846 repo.ui.warn(
1843 1847 _(
1844 1848 b'(warning: large working directory being used without '
1845 1849 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1846 1850 b'see "hg help -e fsmonitor")\n'
1847 1851 )
1848 1852 )
1849 1853
1850 1854 wantfiledata = updatedirstate and not branchmerge
1851 1855 stats, getfiledata = applyupdates(
1852 1856 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
1853 1857 )
1854 1858
1855 1859 if updatedirstate:
1856 1860 with repo.dirstate.parentchange():
1857 1861 repo.setparents(fp1, fp2)
1858 1862 mergestatemod.recordupdates(
1859 1863 repo, actions, branchmerge, getfiledata
1860 1864 )
1861 1865 # update completed, clear state
1862 1866 util.unlink(repo.vfs.join(b'updatestate'))
1863 1867
1864 1868 if not branchmerge:
1865 1869 repo.dirstate.setbranch(p2.branch())
1866 1870
1867 1871 # If we're updating to a location, clean up any stale temporary includes
1868 1872 # (ex: this happens during hg rebase --abort).
1869 1873 if not branchmerge:
1870 1874 sparse.prunetemporaryincludes(repo)
1871 1875
1872 1876 if updatedirstate:
1873 1877 repo.hook(
1874 1878 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
1875 1879 )
1876 1880 return stats
1877 1881
1878 1882
1879 1883 def merge(ctx, labels=None, force=False, wc=None):
1880 1884 """Merge another topological branch into the working copy.
1881 1885
1882 1886 force = whether the merge was run with 'merge --force' (deprecated)
1883 1887 """
1884 1888
1885 1889 return update(
1886 1890 ctx.repo(),
1887 1891 ctx.rev(),
1888 1892 labels=labels,
1889 1893 branchmerge=True,
1890 1894 force=force,
1891 1895 mergeforce=force,
1892 1896 wc=wc,
1893 1897 )
1894 1898
1895 1899
1896 1900 def clean_update(ctx, wc=None):
1897 1901 """Do a clean update to the given commit.
1898 1902
1899 1903 This involves updating to the commit and discarding any changes in the
1900 1904 working copy.
1901 1905 """
1902 1906 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
1903 1907
1904 1908
1905 1909 def revert_to(ctx, matcher=None, wc=None):
1906 1910 """Revert the working copy to the given commit.
1907 1911
1908 1912 The working copy will keep its current parent(s) but its content will
1909 1913 be the same as in the given commit.
1910 1914 """
1911 1915
1912 1916 return update(
1913 1917 ctx.repo(),
1914 1918 ctx.rev(),
1915 1919 branchmerge=False,
1916 1920 force=True,
1917 1921 updatedirstate=False,
1918 1922 matcher=matcher,
1919 1923 wc=wc,
1920 1924 )
1921 1925
1922 1926
1923 1927 def graft(
1924 1928 repo,
1925 1929 ctx,
1926 1930 base=None,
1927 1931 labels=None,
1928 1932 keepparent=False,
1929 1933 keepconflictparent=False,
1930 1934 wctx=None,
1931 1935 ):
1932 1936 """Do a graft-like merge.
1933 1937
1934 1938 This is a merge where the merge ancestor is chosen such that one
1935 1939 or more changesets are grafted onto the current changeset. In
1936 1940 addition to the merge, this fixes up the dirstate to include only
1937 1941 a single parent (if keepparent is False) and tries to duplicate any
1938 1942 renames/copies appropriately.
1939 1943
1940 1944 ctx - changeset to rebase
1941 1945 base - merge base, or ctx.p1() if not specified
1942 1946 labels - merge labels eg ['local', 'graft']
1943 1947 keepparent - keep second parent if any
1944 1948 keepconflictparent - if unresolved, keep parent used for the merge
1945 1949
1946 1950 """
1947 1951 # If we're grafting a descendant onto an ancestor, be sure to pass
1948 1952 # mergeancestor=True to update. This does two things: 1) allows the merge if
1949 1953 # the destination is the same as the parent of the ctx (so we can use graft
1950 1954 # to copy commits), and 2) informs update that the incoming changes are
1951 1955 # newer than the destination so it doesn't prompt about "remote changed foo
1952 1956 # which local deleted".
1953 1957 # We also pass mergeancestor=True when base is the same revision as p1. 2)
1954 1958 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
1955 1959 wctx = wctx or repo[None]
1956 1960 pctx = wctx.p1()
1957 1961 base = base or ctx.p1()
1958 1962 mergeancestor = (
1959 1963 repo.changelog.isancestor(pctx.node(), ctx.node())
1960 1964 or pctx.rev() == base.rev()
1961 1965 )
1962 1966
1963 1967 stats = update(
1964 1968 repo,
1965 1969 ctx.node(),
1966 1970 True,
1967 1971 True,
1968 1972 base.node(),
1969 1973 mergeancestor=mergeancestor,
1970 1974 labels=labels,
1971 1975 wc=wctx,
1972 1976 )
1973 1977
1974 1978 if keepconflictparent and stats.unresolvedcount:
1975 1979 pother = ctx.node()
1976 1980 else:
1977 1981 pother = nullid
1978 1982 parents = ctx.parents()
1979 1983 if keepparent and len(parents) == 2 and base in parents:
1980 1984 parents.remove(base)
1981 1985 pother = parents[0].node()
1982 1986 # Never set both parents equal to each other
1983 1987 if pother == pctx.node():
1984 1988 pother = nullid
1985 1989
1986 1990 if wctx.isinmemory():
1987 1991 wctx.setparents(pctx.node(), pother)
1988 1992 # fix up dirstate for copies and renames
1989 1993 copies.graftcopies(wctx, ctx, base)
1990 1994 else:
1991 1995 with repo.dirstate.parentchange():
1992 1996 repo.setparents(pctx.node(), pother)
1993 1997 repo.dirstate.write(repo.currenttransaction())
1994 1998 # fix up dirstate for copies and renames
1995 1999 copies.graftcopies(wctx, ctx, base)
1996 2000 return stats
1997 2001
1998 2002
1999 2003 def purge(
2000 2004 repo,
2001 2005 matcher,
2002 2006 unknown=True,
2003 2007 ignored=False,
2004 2008 removeemptydirs=True,
2005 2009 removefiles=True,
2006 2010 abortonerror=False,
2007 2011 noop=False,
2008 2012 ):
2009 2013 """Purge the working directory of untracked files.
2010 2014
2011 2015 ``matcher`` is a matcher configured to scan the working directory -
2012 2016 potentially a subset.
2013 2017
2014 2018 ``unknown`` controls whether unknown files should be purged.
2015 2019
2016 2020 ``ignored`` controls whether ignored files should be purged.
2017 2021
2018 2022 ``removeemptydirs`` controls whether empty directories should be removed.
2019 2023
2020 2024 ``removefiles`` controls whether files are removed.
2021 2025
2022 2026 ``abortonerror`` causes an exception to be raised if an error occurs
2023 2027 deleting a file or directory.
2024 2028
2025 2029 ``noop`` controls whether to actually remove files. If not defined, actions
2026 2030 will be taken.
2027 2031
2028 2032 Returns an iterable of relative paths in the working directory that were
2029 2033 or would be removed.
2030 2034 """
2031 2035
2032 2036 def remove(removefn, path):
2033 2037 try:
2034 2038 removefn(path)
2035 2039 except OSError:
2036 2040 m = _(b'%s cannot be removed') % path
2037 2041 if abortonerror:
2038 2042 raise error.Abort(m)
2039 2043 else:
2040 2044 repo.ui.warn(_(b'warning: %s\n') % m)
2041 2045
2042 2046 # There's no API to copy a matcher. So mutate the passed matcher and
2043 2047 # restore it when we're done.
2044 2048 oldtraversedir = matcher.traversedir
2045 2049
2046 2050 res = []
2047 2051
2048 2052 try:
2049 2053 if removeemptydirs:
2050 2054 directories = []
2051 2055 matcher.traversedir = directories.append
2052 2056
2053 2057 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2054 2058
2055 2059 if removefiles:
2056 2060 for f in sorted(status.unknown + status.ignored):
2057 2061 if not noop:
2058 2062 repo.ui.note(_(b'removing file %s\n') % f)
2059 2063 remove(repo.wvfs.unlink, f)
2060 2064 res.append(f)
2061 2065
2062 2066 if removeemptydirs:
2063 2067 for f in sorted(directories, reverse=True):
2064 2068 if matcher(f) and not repo.wvfs.listdir(f):
2065 2069 if not noop:
2066 2070 repo.ui.note(_(b'removing directory %s\n') % f)
2067 2071 remove(repo.wvfs.rmdir, f)
2068 2072 res.append(f)
2069 2073
2070 2074 return res
2071 2075
2072 2076 finally:
2073 2077 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now