##// END OF EJS Templates
merge: refactor code to advise fsmonitor in separate function...
Pulkit Goyal -
r45727:796b63b0 default
parent child Browse files
Show More
@@ -1,2082 +1,2088 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 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 file was locally added. We should forget it instead of
729 729 # deleting it.
730 730 actions[f] = (
731 731 mergestatemod.ACTION_FORGET,
732 732 None,
733 733 b'remote deleted',
734 734 )
735 735 else:
736 736 actions[f] = (
737 737 mergestatemod.ACTION_REMOVE,
738 738 None,
739 739 b'other deleted',
740 740 )
741 741 elif n2: # file exists only on remote side
742 742 if f in copied1:
743 743 pass # we'll deal with it on m1 side
744 744 elif f in branch_copies2.movewithdir:
745 745 f2 = branch_copies2.movewithdir[f]
746 746 if f2 in m1:
747 747 actions[f2] = (
748 748 mergestatemod.ACTION_MERGE,
749 749 (f2, f, None, False, pa.node()),
750 750 b'local directory rename, both created',
751 751 )
752 752 else:
753 753 actions[f2] = (
754 754 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
755 755 (f, fl2),
756 756 b'local directory rename - get from %s' % f,
757 757 )
758 758 elif f in branch_copies2.copy:
759 759 f2 = branch_copies2.copy[f]
760 760 if f2 in m2:
761 761 actions[f] = (
762 762 mergestatemod.ACTION_MERGE,
763 763 (f2, f, f2, False, pa.node()),
764 764 b'remote copied from %s' % f2,
765 765 )
766 766 else:
767 767 actions[f] = (
768 768 mergestatemod.ACTION_MERGE,
769 769 (f2, f, f2, True, pa.node()),
770 770 b'remote moved from %s' % f2,
771 771 )
772 772 elif f not in ma:
773 773 # local unknown, remote created: the logic is described by the
774 774 # following table:
775 775 #
776 776 # force branchmerge different | action
777 777 # n * * | create
778 778 # y n * | create
779 779 # y y n | create
780 780 # y y y | merge
781 781 #
782 782 # Checking whether the files are different is expensive, so we
783 783 # don't do that when we can avoid it.
784 784 if not force:
785 785 actions[f] = (
786 786 mergestatemod.ACTION_CREATED,
787 787 (fl2,),
788 788 b'remote created',
789 789 )
790 790 elif not branchmerge:
791 791 actions[f] = (
792 792 mergestatemod.ACTION_CREATED,
793 793 (fl2,),
794 794 b'remote created',
795 795 )
796 796 else:
797 797 actions[f] = (
798 798 mergestatemod.ACTION_CREATED_MERGE,
799 799 (fl2, pa.node()),
800 800 b'remote created, get or merge',
801 801 )
802 802 elif n2 != ma[f]:
803 803 df = None
804 804 for d in branch_copies1.dirmove:
805 805 if f.startswith(d):
806 806 # new file added in a directory that was moved
807 807 df = branch_copies1.dirmove[d] + f[len(d) :]
808 808 break
809 809 if df is not None and df in m1:
810 810 actions[df] = (
811 811 mergestatemod.ACTION_MERGE,
812 812 (df, f, f, False, pa.node()),
813 813 b'local directory rename - respect move '
814 814 b'from %s' % f,
815 815 )
816 816 elif acceptremote:
817 817 actions[f] = (
818 818 mergestatemod.ACTION_CREATED,
819 819 (fl2,),
820 820 b'remote recreating',
821 821 )
822 822 else:
823 823 actions[f] = (
824 824 mergestatemod.ACTION_DELETED_CHANGED,
825 825 (None, f, f, False, pa.node()),
826 826 b'prompt deleted/changed',
827 827 )
828 828
829 829 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
830 830 # If we are merging, look for path conflicts.
831 831 checkpathconflicts(repo, wctx, p2, actions)
832 832
833 833 narrowmatch = repo.narrowmatch()
834 834 if not narrowmatch.always():
835 835 # Updates "actions" in place
836 836 _filternarrowactions(narrowmatch, branchmerge, actions)
837 837
838 838 renamedelete = branch_copies1.renamedelete
839 839 renamedelete.update(branch_copies2.renamedelete)
840 840
841 841 return actions, diverge, renamedelete
842 842
843 843
844 844 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
845 845 """Resolves false conflicts where the nodeid changed but the content
846 846 remained the same."""
847 847 # We force a copy of actions.items() because we're going to mutate
848 848 # actions as we resolve trivial conflicts.
849 849 for f, (m, args, msg) in list(actions.items()):
850 850 if (
851 851 m == mergestatemod.ACTION_CHANGED_DELETED
852 852 and f in ancestor
853 853 and not wctx[f].cmp(ancestor[f])
854 854 ):
855 855 # local did change but ended up with same content
856 856 actions[f] = mergestatemod.ACTION_REMOVE, None, b'prompt same'
857 857 elif (
858 858 m == mergestatemod.ACTION_DELETED_CHANGED
859 859 and f in ancestor
860 860 and not mctx[f].cmp(ancestor[f])
861 861 ):
862 862 # remote did change but ended up with same content
863 863 del actions[f] # don't get = keep local deleted
864 864
865 865
866 866 def calculateupdates(
867 867 repo,
868 868 wctx,
869 869 mctx,
870 870 ancestors,
871 871 branchmerge,
872 872 force,
873 873 acceptremote,
874 874 followcopies,
875 875 matcher=None,
876 876 mergeforce=False,
877 877 ):
878 878 """Calculate the actions needed to merge mctx into wctx using ancestors"""
879 879 # Avoid cycle.
880 880 from . import sparse
881 881
882 882 if len(ancestors) == 1: # default
883 883 actions, diverge, renamedelete = manifestmerge(
884 884 repo,
885 885 wctx,
886 886 mctx,
887 887 ancestors[0],
888 888 branchmerge,
889 889 force,
890 890 matcher,
891 891 acceptremote,
892 892 followcopies,
893 893 )
894 894 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
895 895
896 896 else: # only when merge.preferancestor=* - the default
897 897 repo.ui.note(
898 898 _(b"note: merging %s and %s using bids from ancestors %s\n")
899 899 % (
900 900 wctx,
901 901 mctx,
902 902 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
903 903 )
904 904 )
905 905
906 906 # Call for bids
907 907 fbids = (
908 908 {}
909 909 ) # mapping filename to bids (action method to list af actions)
910 910 diverge, renamedelete = None, None
911 911 for ancestor in ancestors:
912 912 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
913 913 actions, diverge1, renamedelete1 = manifestmerge(
914 914 repo,
915 915 wctx,
916 916 mctx,
917 917 ancestor,
918 918 branchmerge,
919 919 force,
920 920 matcher,
921 921 acceptremote,
922 922 followcopies,
923 923 forcefulldiff=True,
924 924 )
925 925 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
926 926
927 927 # Track the shortest set of warning on the theory that bid
928 928 # merge will correctly incorporate more information
929 929 if diverge is None or len(diverge1) < len(diverge):
930 930 diverge = diverge1
931 931 if renamedelete is None or len(renamedelete) < len(renamedelete1):
932 932 renamedelete = renamedelete1
933 933
934 934 for f, a in sorted(pycompat.iteritems(actions)):
935 935 m, args, msg = a
936 936 if m == mergestatemod.ACTION_GET_OTHER_AND_STORE:
937 937 m = mergestatemod.ACTION_GET
938 938 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
939 939 if f in fbids:
940 940 d = fbids[f]
941 941 if m in d:
942 942 d[m].append(a)
943 943 else:
944 944 d[m] = [a]
945 945 else:
946 946 fbids[f] = {m: [a]}
947 947
948 948 # Pick the best bid for each file
949 949 repo.ui.note(_(b'\nauction for merging merge bids\n'))
950 950 actions = {}
951 951 for f, bids in sorted(fbids.items()):
952 952 # bids is a mapping from action method to list af actions
953 953 # Consensus?
954 954 if len(bids) == 1: # all bids are the same kind of method
955 955 m, l = list(bids.items())[0]
956 956 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
957 957 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
958 958 actions[f] = l[0]
959 959 continue
960 960 # If keep is an option, just do it.
961 961 if mergestatemod.ACTION_KEEP in bids:
962 962 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
963 963 actions[f] = bids[mergestatemod.ACTION_KEEP][0]
964 964 continue
965 965 # If there are gets and they all agree [how could they not?], do it.
966 966 if mergestatemod.ACTION_GET in bids:
967 967 ga0 = bids[mergestatemod.ACTION_GET][0]
968 968 if all(a == ga0 for a in bids[mergestatemod.ACTION_GET][1:]):
969 969 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
970 970 actions[f] = ga0
971 971 continue
972 972 # TODO: Consider other simple actions such as mode changes
973 973 # Handle inefficient democrazy.
974 974 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
975 975 for m, l in sorted(bids.items()):
976 976 for _f, args, msg in l:
977 977 repo.ui.note(b' %s -> %s\n' % (msg, m))
978 978 # Pick random action. TODO: Instead, prompt user when resolving
979 979 m, l = list(bids.items())[0]
980 980 repo.ui.warn(
981 981 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
982 982 )
983 983 actions[f] = l[0]
984 984 continue
985 985 repo.ui.note(_(b'end of auction\n\n'))
986 986
987 987 if wctx.rev() is None:
988 988 fractions = _forgetremoved(wctx, mctx, branchmerge)
989 989 actions.update(fractions)
990 990
991 991 prunedactions = sparse.filterupdatesactions(
992 992 repo, wctx, mctx, branchmerge, actions
993 993 )
994 994 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
995 995
996 996 return prunedactions, diverge, renamedelete
997 997
998 998
999 999 def _getcwd():
1000 1000 try:
1001 1001 return encoding.getcwd()
1002 1002 except OSError as err:
1003 1003 if err.errno == errno.ENOENT:
1004 1004 return None
1005 1005 raise
1006 1006
1007 1007
1008 1008 def batchremove(repo, wctx, actions):
1009 1009 """apply removes to the working directory
1010 1010
1011 1011 yields tuples for progress updates
1012 1012 """
1013 1013 verbose = repo.ui.verbose
1014 1014 cwd = _getcwd()
1015 1015 i = 0
1016 1016 for f, args, msg in actions:
1017 1017 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1018 1018 if verbose:
1019 1019 repo.ui.note(_(b"removing %s\n") % f)
1020 1020 wctx[f].audit()
1021 1021 try:
1022 1022 wctx[f].remove(ignoremissing=True)
1023 1023 except OSError as inst:
1024 1024 repo.ui.warn(
1025 1025 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1026 1026 )
1027 1027 if i == 100:
1028 1028 yield i, f
1029 1029 i = 0
1030 1030 i += 1
1031 1031 if i > 0:
1032 1032 yield i, f
1033 1033
1034 1034 if cwd and not _getcwd():
1035 1035 # cwd was removed in the course of removing files; print a helpful
1036 1036 # warning.
1037 1037 repo.ui.warn(
1038 1038 _(
1039 1039 b"current directory was removed\n"
1040 1040 b"(consider changing to repo root: %s)\n"
1041 1041 )
1042 1042 % repo.root
1043 1043 )
1044 1044
1045 1045
1046 1046 def batchget(repo, mctx, wctx, wantfiledata, actions):
1047 1047 """apply gets to the working directory
1048 1048
1049 1049 mctx is the context to get from
1050 1050
1051 1051 Yields arbitrarily many (False, tuple) for progress updates, followed by
1052 1052 exactly one (True, filedata). When wantfiledata is false, filedata is an
1053 1053 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1054 1054 mtime) of the file f written for each action.
1055 1055 """
1056 1056 filedata = {}
1057 1057 verbose = repo.ui.verbose
1058 1058 fctx = mctx.filectx
1059 1059 ui = repo.ui
1060 1060 i = 0
1061 1061 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1062 1062 for f, (flags, backup), msg in actions:
1063 1063 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1064 1064 if verbose:
1065 1065 repo.ui.note(_(b"getting %s\n") % f)
1066 1066
1067 1067 if backup:
1068 1068 # If a file or directory exists with the same name, back that
1069 1069 # up. Otherwise, look to see if there is a file that conflicts
1070 1070 # with a directory this file is in, and if so, back that up.
1071 1071 conflicting = f
1072 1072 if not repo.wvfs.lexists(f):
1073 1073 for p in pathutil.finddirs(f):
1074 1074 if repo.wvfs.isfileorlink(p):
1075 1075 conflicting = p
1076 1076 break
1077 1077 if repo.wvfs.lexists(conflicting):
1078 1078 orig = scmutil.backuppath(ui, repo, conflicting)
1079 1079 util.rename(repo.wjoin(conflicting), orig)
1080 1080 wfctx = wctx[f]
1081 1081 wfctx.clearunknown()
1082 1082 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1083 1083 size = wfctx.write(
1084 1084 fctx(f).data(),
1085 1085 flags,
1086 1086 backgroundclose=True,
1087 1087 atomictemp=atomictemp,
1088 1088 )
1089 1089 if wantfiledata:
1090 1090 s = wfctx.lstat()
1091 1091 mode = s.st_mode
1092 1092 mtime = s[stat.ST_MTIME]
1093 1093 filedata[f] = (mode, size, mtime) # for dirstate.normal
1094 1094 if i == 100:
1095 1095 yield False, (i, f)
1096 1096 i = 0
1097 1097 i += 1
1098 1098 if i > 0:
1099 1099 yield False, (i, f)
1100 1100 yield True, filedata
1101 1101
1102 1102
1103 1103 def _prefetchfiles(repo, ctx, actions):
1104 1104 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1105 1105 of merge actions. ``ctx`` is the context being merged in."""
1106 1106
1107 1107 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1108 1108 # don't touch the context to be merged in. 'cd' is skipped, because
1109 1109 # changed/deleted never resolves to something from the remote side.
1110 1110 oplist = [
1111 1111 actions[a]
1112 1112 for a in (
1113 1113 mergestatemod.ACTION_GET,
1114 1114 mergestatemod.ACTION_DELETED_CHANGED,
1115 1115 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1116 1116 mergestatemod.ACTION_MERGE,
1117 1117 )
1118 1118 ]
1119 1119 prefetch = scmutil.prefetchfiles
1120 1120 matchfiles = scmutil.matchfiles
1121 1121 prefetch(
1122 1122 repo,
1123 1123 [
1124 1124 (
1125 1125 ctx.rev(),
1126 1126 matchfiles(
1127 1127 repo, [f for sublist in oplist for f, args, msg in sublist]
1128 1128 ),
1129 1129 )
1130 1130 ],
1131 1131 )
1132 1132
1133 1133
1134 1134 @attr.s(frozen=True)
1135 1135 class updateresult(object):
1136 1136 updatedcount = attr.ib()
1137 1137 mergedcount = attr.ib()
1138 1138 removedcount = attr.ib()
1139 1139 unresolvedcount = attr.ib()
1140 1140
1141 1141 def isempty(self):
1142 1142 return not (
1143 1143 self.updatedcount
1144 1144 or self.mergedcount
1145 1145 or self.removedcount
1146 1146 or self.unresolvedcount
1147 1147 )
1148 1148
1149 1149
1150 1150 def emptyactions():
1151 1151 """create an actions dict, to be populated and passed to applyupdates()"""
1152 1152 return {
1153 1153 m: []
1154 1154 for m in (
1155 1155 mergestatemod.ACTION_ADD,
1156 1156 mergestatemod.ACTION_ADD_MODIFIED,
1157 1157 mergestatemod.ACTION_FORGET,
1158 1158 mergestatemod.ACTION_GET,
1159 1159 mergestatemod.ACTION_CHANGED_DELETED,
1160 1160 mergestatemod.ACTION_DELETED_CHANGED,
1161 1161 mergestatemod.ACTION_REMOVE,
1162 1162 mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL,
1163 1163 mergestatemod.ACTION_LOCAL_DIR_RENAME_GET,
1164 1164 mergestatemod.ACTION_MERGE,
1165 1165 mergestatemod.ACTION_EXEC,
1166 1166 mergestatemod.ACTION_KEEP,
1167 1167 mergestatemod.ACTION_PATH_CONFLICT,
1168 1168 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1169 1169 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1170 1170 )
1171 1171 }
1172 1172
1173 1173
1174 1174 def applyupdates(
1175 1175 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1176 1176 ):
1177 1177 """apply the merge action list to the working directory
1178 1178
1179 1179 wctx is the working copy context
1180 1180 mctx is the context to be merged into the working copy
1181 1181
1182 1182 Return a tuple of (counts, filedata), where counts is a tuple
1183 1183 (updated, merged, removed, unresolved) that describes how many
1184 1184 files were affected by the update, and filedata is as described in
1185 1185 batchget.
1186 1186 """
1187 1187
1188 1188 _prefetchfiles(repo, mctx, actions)
1189 1189
1190 1190 updated, merged, removed = 0, 0, 0
1191 1191 ms = mergestatemod.mergestate.clean(
1192 1192 repo, wctx.p1().node(), mctx.node(), labels
1193 1193 )
1194 1194
1195 1195 # add ACTION_GET_OTHER_AND_STORE to mergestate
1196 1196 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1197 1197 ms.addmergedother(e[0])
1198 1198
1199 1199 moves = []
1200 1200 for m, l in actions.items():
1201 1201 l.sort()
1202 1202
1203 1203 # 'cd' and 'dc' actions are treated like other merge conflicts
1204 1204 mergeactions = sorted(actions[mergestatemod.ACTION_CHANGED_DELETED])
1205 1205 mergeactions.extend(sorted(actions[mergestatemod.ACTION_DELETED_CHANGED]))
1206 1206 mergeactions.extend(actions[mergestatemod.ACTION_MERGE])
1207 1207 for f, args, msg in mergeactions:
1208 1208 f1, f2, fa, move, anc = args
1209 1209 if f == b'.hgsubstate': # merged internally
1210 1210 continue
1211 1211 if f1 is None:
1212 1212 fcl = filemerge.absentfilectx(wctx, fa)
1213 1213 else:
1214 1214 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1215 1215 fcl = wctx[f1]
1216 1216 if f2 is None:
1217 1217 fco = filemerge.absentfilectx(mctx, fa)
1218 1218 else:
1219 1219 fco = mctx[f2]
1220 1220 actx = repo[anc]
1221 1221 if fa in actx:
1222 1222 fca = actx[fa]
1223 1223 else:
1224 1224 # TODO: move to absentfilectx
1225 1225 fca = repo.filectx(f1, fileid=nullrev)
1226 1226 ms.add(fcl, fco, fca, f)
1227 1227 if f1 != f and move:
1228 1228 moves.append(f1)
1229 1229
1230 1230 # remove renamed files after safely stored
1231 1231 for f in moves:
1232 1232 if wctx[f].lexists():
1233 1233 repo.ui.debug(b"removing %s\n" % f)
1234 1234 wctx[f].audit()
1235 1235 wctx[f].remove()
1236 1236
1237 1237 numupdates = sum(
1238 1238 len(l) for m, l in actions.items() if m != mergestatemod.ACTION_KEEP
1239 1239 )
1240 1240 progress = repo.ui.makeprogress(
1241 1241 _(b'updating'), unit=_(b'files'), total=numupdates
1242 1242 )
1243 1243
1244 1244 if [
1245 1245 a
1246 1246 for a in actions[mergestatemod.ACTION_REMOVE]
1247 1247 if a[0] == b'.hgsubstate'
1248 1248 ]:
1249 1249 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1250 1250
1251 1251 # record path conflicts
1252 1252 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT]:
1253 1253 f1, fo = args
1254 1254 s = repo.ui.status
1255 1255 s(
1256 1256 _(
1257 1257 b"%s: path conflict - a file or link has the same name as a "
1258 1258 b"directory\n"
1259 1259 )
1260 1260 % f
1261 1261 )
1262 1262 if fo == b'l':
1263 1263 s(_(b"the local file has been renamed to %s\n") % f1)
1264 1264 else:
1265 1265 s(_(b"the remote file has been renamed to %s\n") % f1)
1266 1266 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1267 1267 ms.addpathconflict(f, f1, fo)
1268 1268 progress.increment(item=f)
1269 1269
1270 1270 # When merging in-memory, we can't support worker processes, so set the
1271 1271 # per-item cost at 0 in that case.
1272 1272 cost = 0 if wctx.isinmemory() else 0.001
1273 1273
1274 1274 # remove in parallel (must come before resolving path conflicts and getting)
1275 1275 prog = worker.worker(
1276 1276 repo.ui,
1277 1277 cost,
1278 1278 batchremove,
1279 1279 (repo, wctx),
1280 1280 actions[mergestatemod.ACTION_REMOVE],
1281 1281 )
1282 1282 for i, item in prog:
1283 1283 progress.increment(step=i, item=item)
1284 1284 removed = len(actions[mergestatemod.ACTION_REMOVE])
1285 1285
1286 1286 # resolve path conflicts (must come before getting)
1287 1287 for f, args, msg in actions[mergestatemod.ACTION_PATH_CONFLICT_RESOLVE]:
1288 1288 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1289 1289 (f0, origf0) = args
1290 1290 if wctx[f0].lexists():
1291 1291 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1292 1292 wctx[f].audit()
1293 1293 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1294 1294 wctx[f0].remove()
1295 1295 progress.increment(item=f)
1296 1296
1297 1297 # get in parallel.
1298 1298 threadsafe = repo.ui.configbool(
1299 1299 b'experimental', b'worker.wdir-get-thread-safe'
1300 1300 )
1301 1301 prog = worker.worker(
1302 1302 repo.ui,
1303 1303 cost,
1304 1304 batchget,
1305 1305 (repo, mctx, wctx, wantfiledata),
1306 1306 actions[mergestatemod.ACTION_GET],
1307 1307 threadsafe=threadsafe,
1308 1308 hasretval=True,
1309 1309 )
1310 1310 getfiledata = {}
1311 1311 for final, res in prog:
1312 1312 if final:
1313 1313 getfiledata = res
1314 1314 else:
1315 1315 i, item = res
1316 1316 progress.increment(step=i, item=item)
1317 1317 updated = len(actions[mergestatemod.ACTION_GET])
1318 1318
1319 1319 if [a for a in actions[mergestatemod.ACTION_GET] if a[0] == b'.hgsubstate']:
1320 1320 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1321 1321
1322 1322 # forget (manifest only, just log it) (must come first)
1323 1323 for f, args, msg in actions[mergestatemod.ACTION_FORGET]:
1324 1324 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1325 1325 progress.increment(item=f)
1326 1326
1327 1327 # re-add (manifest only, just log it)
1328 1328 for f, args, msg in actions[mergestatemod.ACTION_ADD]:
1329 1329 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1330 1330 progress.increment(item=f)
1331 1331
1332 1332 # re-add/mark as modified (manifest only, just log it)
1333 1333 for f, args, msg in actions[mergestatemod.ACTION_ADD_MODIFIED]:
1334 1334 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1335 1335 progress.increment(item=f)
1336 1336
1337 1337 # keep (noop, just log it)
1338 1338 for f, args, msg in actions[mergestatemod.ACTION_KEEP]:
1339 1339 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1340 1340 # no progress
1341 1341
1342 1342 # directory rename, move local
1343 1343 for f, args, msg in actions[mergestatemod.ACTION_DIR_RENAME_MOVE_LOCAL]:
1344 1344 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1345 1345 progress.increment(item=f)
1346 1346 f0, flags = args
1347 1347 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1348 1348 wctx[f].audit()
1349 1349 wctx[f].write(wctx.filectx(f0).data(), flags)
1350 1350 wctx[f0].remove()
1351 1351 updated += 1
1352 1352
1353 1353 # local directory rename, get
1354 1354 for f, args, msg in actions[mergestatemod.ACTION_LOCAL_DIR_RENAME_GET]:
1355 1355 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1356 1356 progress.increment(item=f)
1357 1357 f0, flags = args
1358 1358 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1359 1359 wctx[f].write(mctx.filectx(f0).data(), flags)
1360 1360 updated += 1
1361 1361
1362 1362 # exec
1363 1363 for f, args, msg in actions[mergestatemod.ACTION_EXEC]:
1364 1364 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1365 1365 progress.increment(item=f)
1366 1366 (flags,) = args
1367 1367 wctx[f].audit()
1368 1368 wctx[f].setflags(b'l' in flags, b'x' in flags)
1369 1369 updated += 1
1370 1370
1371 1371 # the ordering is important here -- ms.mergedriver will raise if the merge
1372 1372 # driver has changed, and we want to be able to bypass it when overwrite is
1373 1373 # True
1374 1374 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1375 1375
1376 1376 if usemergedriver:
1377 1377 if wctx.isinmemory():
1378 1378 raise error.InMemoryMergeConflictsError(
1379 1379 b"in-memory merge does not support mergedriver"
1380 1380 )
1381 1381 ms.commit()
1382 1382 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1383 1383 # the driver might leave some files unresolved
1384 1384 unresolvedf = set(ms.unresolved())
1385 1385 if not proceed:
1386 1386 # XXX setting unresolved to at least 1 is a hack to make sure we
1387 1387 # error out
1388 1388 return updateresult(
1389 1389 updated, merged, removed, max(len(unresolvedf), 1)
1390 1390 )
1391 1391 newactions = []
1392 1392 for f, args, msg in mergeactions:
1393 1393 if f in unresolvedf:
1394 1394 newactions.append((f, args, msg))
1395 1395 mergeactions = newactions
1396 1396
1397 1397 try:
1398 1398 # premerge
1399 1399 tocomplete = []
1400 1400 for f, args, msg in mergeactions:
1401 1401 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
1402 1402 progress.increment(item=f)
1403 1403 if f == b'.hgsubstate': # subrepo states need updating
1404 1404 subrepoutil.submerge(
1405 1405 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
1406 1406 )
1407 1407 continue
1408 1408 wctx[f].audit()
1409 1409 complete, r = ms.preresolve(f, wctx)
1410 1410 if not complete:
1411 1411 numupdates += 1
1412 1412 tocomplete.append((f, args, msg))
1413 1413
1414 1414 # merge
1415 1415 for f, args, msg in tocomplete:
1416 1416 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
1417 1417 progress.increment(item=f, total=numupdates)
1418 1418 ms.resolve(f, wctx)
1419 1419
1420 1420 finally:
1421 1421 ms.commit()
1422 1422
1423 1423 unresolved = ms.unresolvedcount()
1424 1424
1425 1425 if (
1426 1426 usemergedriver
1427 1427 and not unresolved
1428 1428 and ms.mdstate() != mergestatemod.MERGE_DRIVER_STATE_SUCCESS
1429 1429 ):
1430 1430 if not driverconclude(repo, ms, wctx, labels=labels):
1431 1431 # XXX setting unresolved to at least 1 is a hack to make sure we
1432 1432 # error out
1433 1433 unresolved = max(unresolved, 1)
1434 1434
1435 1435 ms.commit()
1436 1436
1437 1437 msupdated, msmerged, msremoved = ms.counts()
1438 1438 updated += msupdated
1439 1439 merged += msmerged
1440 1440 removed += msremoved
1441 1441
1442 1442 extraactions = ms.actions()
1443 1443 if extraactions:
1444 1444 mfiles = {a[0] for a in actions[mergestatemod.ACTION_MERGE]}
1445 1445 for k, acts in pycompat.iteritems(extraactions):
1446 1446 actions[k].extend(acts)
1447 1447 if k == mergestatemod.ACTION_GET and wantfiledata:
1448 1448 # no filedata until mergestate is updated to provide it
1449 1449 for a in acts:
1450 1450 getfiledata[a[0]] = None
1451 1451 # Remove these files from actions[ACTION_MERGE] as well. This is
1452 1452 # important because in recordupdates, files in actions[ACTION_MERGE]
1453 1453 # are processed after files in other actions, and the merge driver
1454 1454 # might add files to those actions via extraactions above. This can
1455 1455 # lead to a file being recorded twice, with poor results. This is
1456 1456 # especially problematic for actions[ACTION_REMOVE] (currently only
1457 1457 # possible with the merge driver in the initial merge process;
1458 1458 # interrupted merges don't go through this flow).
1459 1459 #
1460 1460 # The real fix here is to have indexes by both file and action so
1461 1461 # that when the action for a file is changed it is automatically
1462 1462 # reflected in the other action lists. But that involves a more
1463 1463 # complex data structure, so this will do for now.
1464 1464 #
1465 1465 # We don't need to do the same operation for 'dc' and 'cd' because
1466 1466 # those lists aren't consulted again.
1467 1467 mfiles.difference_update(a[0] for a in acts)
1468 1468
1469 1469 actions[mergestatemod.ACTION_MERGE] = [
1470 1470 a for a in actions[mergestatemod.ACTION_MERGE] if a[0] in mfiles
1471 1471 ]
1472 1472
1473 1473 progress.complete()
1474 1474 assert len(getfiledata) == (
1475 1475 len(actions[mergestatemod.ACTION_GET]) if wantfiledata else 0
1476 1476 )
1477 1477 return updateresult(updated, merged, removed, unresolved), getfiledata
1478 1478
1479 1479
1480 def _advertisefsmonitor(repo, num_gets, p1node):
1481 # Advertise fsmonitor when its presence could be useful.
1482 #
1483 # We only advertise when performing an update from an empty working
1484 # directory. This typically only occurs during initial clone.
1485 #
1486 # We give users a mechanism to disable the warning in case it is
1487 # annoying.
1488 #
1489 # We only allow on Linux and MacOS because that's where fsmonitor is
1490 # considered stable.
1491 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1492 fsmonitorthreshold = repo.ui.configint(
1493 b'fsmonitor', b'warn_update_file_count'
1494 )
1495 try:
1496 # avoid cycle: extensions -> cmdutil -> merge
1497 from . import extensions
1498
1499 extensions.find(b'fsmonitor')
1500 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1501 # We intentionally don't look at whether fsmonitor has disabled
1502 # itself because a) fsmonitor may have already printed a warning
1503 # b) we only care about the config state here.
1504 except KeyError:
1505 fsmonitorenabled = False
1506
1507 if (
1508 fsmonitorwarning
1509 and not fsmonitorenabled
1510 and p1node == nullid
1511 and num_gets >= fsmonitorthreshold
1512 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1513 ):
1514 repo.ui.warn(
1515 _(
1516 b'(warning: large working directory being used without '
1517 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1518 b'see "hg help -e fsmonitor")\n'
1519 )
1520 )
1521
1522
1480 1523 UPDATECHECK_ABORT = b'abort' # handled at higher layers
1481 1524 UPDATECHECK_NONE = b'none'
1482 1525 UPDATECHECK_LINEAR = b'linear'
1483 1526 UPDATECHECK_NO_CONFLICT = b'noconflict'
1484 1527
1485 1528
1486 1529 def update(
1487 1530 repo,
1488 1531 node,
1489 1532 branchmerge,
1490 1533 force,
1491 1534 ancestor=None,
1492 1535 mergeancestor=False,
1493 1536 labels=None,
1494 1537 matcher=None,
1495 1538 mergeforce=False,
1496 1539 updatedirstate=True,
1497 1540 updatecheck=None,
1498 1541 wc=None,
1499 1542 ):
1500 1543 """
1501 1544 Perform a merge between the working directory and the given node
1502 1545
1503 1546 node = the node to update to
1504 1547 branchmerge = whether to merge between branches
1505 1548 force = whether to force branch merging or file overwriting
1506 1549 matcher = a matcher to filter file lists (dirstate not updated)
1507 1550 mergeancestor = whether it is merging with an ancestor. If true,
1508 1551 we should accept the incoming changes for any prompts that occur.
1509 1552 If false, merging with an ancestor (fast-forward) is only allowed
1510 1553 between different named branches. This flag is used by rebase extension
1511 1554 as a temporary fix and should be avoided in general.
1512 1555 labels = labels to use for base, local and other
1513 1556 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1514 1557 this is True, then 'force' should be True as well.
1515 1558
1516 1559 The table below shows all the behaviors of the update command given the
1517 1560 -c/--check and -C/--clean or no options, whether the working directory is
1518 1561 dirty, whether a revision is specified, and the relationship of the parent
1519 1562 rev to the target rev (linear or not). Match from top first. The -n
1520 1563 option doesn't exist on the command line, but represents the
1521 1564 experimental.updatecheck=noconflict option.
1522 1565
1523 1566 This logic is tested by test-update-branches.t.
1524 1567
1525 1568 -c -C -n -m dirty rev linear | result
1526 1569 y y * * * * * | (1)
1527 1570 y * y * * * * | (1)
1528 1571 y * * y * * * | (1)
1529 1572 * y y * * * * | (1)
1530 1573 * y * y * * * | (1)
1531 1574 * * y y * * * | (1)
1532 1575 * * * * * n n | x
1533 1576 * * * * n * * | ok
1534 1577 n n n n y * y | merge
1535 1578 n n n n y y n | (2)
1536 1579 n n n y y * * | merge
1537 1580 n n y n y * * | merge if no conflict
1538 1581 n y n n y * * | discard
1539 1582 y n n n y * * | (3)
1540 1583
1541 1584 x = can't happen
1542 1585 * = don't-care
1543 1586 1 = incompatible options (checked in commands.py)
1544 1587 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1545 1588 3 = abort: uncommitted changes (checked in commands.py)
1546 1589
1547 1590 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1548 1591 to repo[None] if None is passed.
1549 1592
1550 1593 Return the same tuple as applyupdates().
1551 1594 """
1552 1595 # Avoid cycle.
1553 1596 from . import sparse
1554 1597
1555 1598 # This function used to find the default destination if node was None, but
1556 1599 # that's now in destutil.py.
1557 1600 assert node is not None
1558 1601 if not branchmerge and not force:
1559 1602 # TODO: remove the default once all callers that pass branchmerge=False
1560 1603 # and force=False pass a value for updatecheck. We may want to allow
1561 1604 # updatecheck='abort' to better suppport some of these callers.
1562 1605 if updatecheck is None:
1563 1606 updatecheck = UPDATECHECK_LINEAR
1564 1607 if updatecheck not in (
1565 1608 UPDATECHECK_NONE,
1566 1609 UPDATECHECK_LINEAR,
1567 1610 UPDATECHECK_NO_CONFLICT,
1568 1611 ):
1569 1612 raise ValueError(
1570 1613 r'Invalid updatecheck %r (can accept %r)'
1571 1614 % (
1572 1615 updatecheck,
1573 1616 (
1574 1617 UPDATECHECK_NONE,
1575 1618 UPDATECHECK_LINEAR,
1576 1619 UPDATECHECK_NO_CONFLICT,
1577 1620 ),
1578 1621 )
1579 1622 )
1580 1623 if wc is not None and wc.isinmemory():
1581 1624 maybe_wlock = util.nullcontextmanager()
1582 1625 else:
1583 1626 maybe_wlock = repo.wlock()
1584 1627 with maybe_wlock:
1585 1628 if wc is None:
1586 1629 wc = repo[None]
1587 1630 pl = wc.parents()
1588 1631 p1 = pl[0]
1589 1632 p2 = repo[node]
1590 1633 if ancestor is not None:
1591 1634 pas = [repo[ancestor]]
1592 1635 else:
1593 1636 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
1594 1637 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1595 1638 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1596 1639 else:
1597 1640 pas = [p1.ancestor(p2, warn=branchmerge)]
1598 1641
1599 1642 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1600 1643
1601 1644 overwrite = force and not branchmerge
1602 1645 ### check phase
1603 1646 if not overwrite:
1604 1647 if len(pl) > 1:
1605 1648 raise error.Abort(_(b"outstanding uncommitted merge"))
1606 1649 ms = mergestatemod.mergestate.read(repo)
1607 1650 if list(ms.unresolved()):
1608 1651 raise error.Abort(
1609 1652 _(b"outstanding merge conflicts"),
1610 1653 hint=_(b"use 'hg resolve' to resolve"),
1611 1654 )
1612 1655 if branchmerge:
1613 1656 if pas == [p2]:
1614 1657 raise error.Abort(
1615 1658 _(
1616 1659 b"merging with a working directory ancestor"
1617 1660 b" has no effect"
1618 1661 )
1619 1662 )
1620 1663 elif pas == [p1]:
1621 1664 if not mergeancestor and wc.branch() == p2.branch():
1622 1665 raise error.Abort(
1623 1666 _(b"nothing to merge"),
1624 1667 hint=_(b"use 'hg update' or check 'hg heads'"),
1625 1668 )
1626 1669 if not force and (wc.files() or wc.deleted()):
1627 1670 raise error.Abort(
1628 1671 _(b"uncommitted changes"),
1629 1672 hint=_(b"use 'hg status' to list changes"),
1630 1673 )
1631 1674 if not wc.isinmemory():
1632 1675 for s in sorted(wc.substate):
1633 1676 wc.sub(s).bailifchanged()
1634 1677
1635 1678 elif not overwrite:
1636 1679 if p1 == p2: # no-op update
1637 1680 # call the hooks and exit early
1638 1681 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
1639 1682 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
1640 1683 return updateresult(0, 0, 0, 0)
1641 1684
1642 1685 if updatecheck == UPDATECHECK_LINEAR and pas not in (
1643 1686 [p1],
1644 1687 [p2],
1645 1688 ): # nonlinear
1646 1689 dirty = wc.dirty(missing=True)
1647 1690 if dirty:
1648 1691 # Branching is a bit strange to ensure we do the minimal
1649 1692 # amount of call to obsutil.foreground.
1650 1693 foreground = obsutil.foreground(repo, [p1.node()])
1651 1694 # note: the <node> variable contains a random identifier
1652 1695 if repo[node].node() in foreground:
1653 1696 pass # allow updating to successors
1654 1697 else:
1655 1698 msg = _(b"uncommitted changes")
1656 1699 hint = _(b"commit or update --clean to discard changes")
1657 1700 raise error.UpdateAbort(msg, hint=hint)
1658 1701 else:
1659 1702 # Allow jumping branches if clean and specific rev given
1660 1703 pass
1661 1704
1662 1705 if overwrite:
1663 1706 pas = [wc]
1664 1707 elif not branchmerge:
1665 1708 pas = [p1]
1666 1709
1667 1710 # deprecated config: merge.followcopies
1668 1711 followcopies = repo.ui.configbool(b'merge', b'followcopies')
1669 1712 if overwrite:
1670 1713 followcopies = False
1671 1714 elif not pas[0]:
1672 1715 followcopies = False
1673 1716 if not branchmerge and not wc.dirty(missing=True):
1674 1717 followcopies = False
1675 1718
1676 1719 ### calculate phase
1677 1720 actionbyfile, diverge, renamedelete = calculateupdates(
1678 1721 repo,
1679 1722 wc,
1680 1723 p2,
1681 1724 pas,
1682 1725 branchmerge,
1683 1726 force,
1684 1727 mergeancestor,
1685 1728 followcopies,
1686 1729 matcher=matcher,
1687 1730 mergeforce=mergeforce,
1688 1731 )
1689 1732
1690 1733 if updatecheck == UPDATECHECK_NO_CONFLICT:
1691 1734 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1692 1735 if m not in (
1693 1736 mergestatemod.ACTION_GET,
1694 1737 mergestatemod.ACTION_KEEP,
1695 1738 mergestatemod.ACTION_EXEC,
1696 1739 mergestatemod.ACTION_REMOVE,
1697 1740 mergestatemod.ACTION_PATH_CONFLICT_RESOLVE,
1698 1741 mergestatemod.ACTION_GET_OTHER_AND_STORE,
1699 1742 ):
1700 1743 msg = _(b"conflicting changes")
1701 1744 hint = _(b"commit or update --clean to discard changes")
1702 1745 raise error.Abort(msg, hint=hint)
1703 1746
1704 1747 # Prompt and create actions. Most of this is in the resolve phase
1705 1748 # already, but we can't handle .hgsubstate in filemerge or
1706 1749 # subrepoutil.submerge yet so we have to keep prompting for it.
1707 1750 if b'.hgsubstate' in actionbyfile:
1708 1751 f = b'.hgsubstate'
1709 1752 m, args, msg = actionbyfile[f]
1710 1753 prompts = filemerge.partextras(labels)
1711 1754 prompts[b'f'] = f
1712 1755 if m == mergestatemod.ACTION_CHANGED_DELETED:
1713 1756 if repo.ui.promptchoice(
1714 1757 _(
1715 1758 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
1716 1759 b"use (c)hanged version or (d)elete?"
1717 1760 b"$$ &Changed $$ &Delete"
1718 1761 )
1719 1762 % prompts,
1720 1763 0,
1721 1764 ):
1722 1765 actionbyfile[f] = (
1723 1766 mergestatemod.ACTION_REMOVE,
1724 1767 None,
1725 1768 b'prompt delete',
1726 1769 )
1727 1770 elif f in p1:
1728 1771 actionbyfile[f] = (
1729 1772 mergestatemod.ACTION_ADD_MODIFIED,
1730 1773 None,
1731 1774 b'prompt keep',
1732 1775 )
1733 1776 else:
1734 1777 actionbyfile[f] = (
1735 1778 mergestatemod.ACTION_ADD,
1736 1779 None,
1737 1780 b'prompt keep',
1738 1781 )
1739 1782 elif m == mergestatemod.ACTION_DELETED_CHANGED:
1740 1783 f1, f2, fa, move, anc = args
1741 1784 flags = p2[f2].flags()
1742 1785 if (
1743 1786 repo.ui.promptchoice(
1744 1787 _(
1745 1788 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
1746 1789 b"use (c)hanged version or leave (d)eleted?"
1747 1790 b"$$ &Changed $$ &Deleted"
1748 1791 )
1749 1792 % prompts,
1750 1793 0,
1751 1794 )
1752 1795 == 0
1753 1796 ):
1754 1797 actionbyfile[f] = (
1755 1798 mergestatemod.ACTION_GET,
1756 1799 (flags, False),
1757 1800 b'prompt recreating',
1758 1801 )
1759 1802 else:
1760 1803 del actionbyfile[f]
1761 1804
1762 1805 # Convert to dictionary-of-lists format
1763 1806 actions = emptyactions()
1764 1807 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
1765 1808 if m not in actions:
1766 1809 actions[m] = []
1767 1810 actions[m].append((f, args, msg))
1768 1811
1769 1812 # ACTION_GET_OTHER_AND_STORE is a mergestatemod.ACTION_GET + store in mergestate
1770 1813 for e in actions[mergestatemod.ACTION_GET_OTHER_AND_STORE]:
1771 1814 actions[mergestatemod.ACTION_GET].append(e)
1772 1815
1773 1816 if not util.fscasesensitive(repo.path):
1774 1817 # check collision between files only in p2 for clean update
1775 1818 if not branchmerge and (
1776 1819 force or not wc.dirty(missing=True, branch=False)
1777 1820 ):
1778 1821 _checkcollision(repo, p2.manifest(), None)
1779 1822 else:
1780 1823 _checkcollision(repo, wc.manifest(), actions)
1781 1824
1782 1825 # divergent renames
1783 1826 for f, fl in sorted(pycompat.iteritems(diverge)):
1784 1827 repo.ui.warn(
1785 1828 _(
1786 1829 b"note: possible conflict - %s was renamed "
1787 1830 b"multiple times to:\n"
1788 1831 )
1789 1832 % f
1790 1833 )
1791 1834 for nf in sorted(fl):
1792 1835 repo.ui.warn(b" %s\n" % nf)
1793 1836
1794 1837 # rename and delete
1795 1838 for f, fl in sorted(pycompat.iteritems(renamedelete)):
1796 1839 repo.ui.warn(
1797 1840 _(
1798 1841 b"note: possible conflict - %s was deleted "
1799 1842 b"and renamed to:\n"
1800 1843 )
1801 1844 % f
1802 1845 )
1803 1846 for nf in sorted(fl):
1804 1847 repo.ui.warn(b" %s\n" % nf)
1805 1848
1806 1849 ### apply phase
1807 1850 if not branchmerge: # just jump to the new rev
1808 1851 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
1809 1852 # If we're doing a partial update, we need to skip updating
1810 1853 # the dirstate.
1811 1854 always = matcher is None or matcher.always()
1812 1855 updatedirstate = updatedirstate and always and not wc.isinmemory()
1813 1856 if updatedirstate:
1814 1857 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
1815 1858 # note that we're in the middle of an update
1816 1859 repo.vfs.write(b'updatestate', p2.hex())
1817 1860
1818 # Advertise fsmonitor when its presence could be useful.
1819 #
1820 # We only advertise when performing an update from an empty working
1821 # directory. This typically only occurs during initial clone.
1822 #
1823 # We give users a mechanism to disable the warning in case it is
1824 # annoying.
1825 #
1826 # We only allow on Linux and MacOS because that's where fsmonitor is
1827 # considered stable.
1828 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
1829 fsmonitorthreshold = repo.ui.configint(
1830 b'fsmonitor', b'warn_update_file_count'
1861 _advertisefsmonitor(
1862 repo, len(actions[mergestatemod.ACTION_GET]), p1.node()
1831 1863 )
1832 try:
1833 # avoid cycle: extensions -> cmdutil -> merge
1834 from . import extensions
1835
1836 extensions.find(b'fsmonitor')
1837 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
1838 # We intentionally don't look at whether fsmonitor has disabled
1839 # itself because a) fsmonitor may have already printed a warning
1840 # b) we only care about the config state here.
1841 except KeyError:
1842 fsmonitorenabled = False
1843
1844 if (
1845 fsmonitorwarning
1846 and not fsmonitorenabled
1847 and p1.node() == nullid
1848 and len(actions[mergestatemod.ACTION_GET]) >= fsmonitorthreshold
1849 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
1850 ):
1851 repo.ui.warn(
1852 _(
1853 b'(warning: large working directory being used without '
1854 b'fsmonitor enabled; enable fsmonitor to improve performance; '
1855 b'see "hg help -e fsmonitor")\n'
1856 )
1857 )
1858 1864
1859 1865 wantfiledata = updatedirstate and not branchmerge
1860 1866 stats, getfiledata = applyupdates(
1861 1867 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
1862 1868 )
1863 1869
1864 1870 if updatedirstate:
1865 1871 with repo.dirstate.parentchange():
1866 1872 repo.setparents(fp1, fp2)
1867 1873 mergestatemod.recordupdates(
1868 1874 repo, actions, branchmerge, getfiledata
1869 1875 )
1870 1876 # update completed, clear state
1871 1877 util.unlink(repo.vfs.join(b'updatestate'))
1872 1878
1873 1879 if not branchmerge:
1874 1880 repo.dirstate.setbranch(p2.branch())
1875 1881
1876 1882 # If we're updating to a location, clean up any stale temporary includes
1877 1883 # (ex: this happens during hg rebase --abort).
1878 1884 if not branchmerge:
1879 1885 sparse.prunetemporaryincludes(repo)
1880 1886
1881 1887 if updatedirstate:
1882 1888 repo.hook(
1883 1889 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
1884 1890 )
1885 1891 return stats
1886 1892
1887 1893
1888 1894 def merge(ctx, labels=None, force=False, wc=None):
1889 1895 """Merge another topological branch into the working copy.
1890 1896
1891 1897 force = whether the merge was run with 'merge --force' (deprecated)
1892 1898 """
1893 1899
1894 1900 return update(
1895 1901 ctx.repo(),
1896 1902 ctx.rev(),
1897 1903 labels=labels,
1898 1904 branchmerge=True,
1899 1905 force=force,
1900 1906 mergeforce=force,
1901 1907 wc=wc,
1902 1908 )
1903 1909
1904 1910
1905 1911 def clean_update(ctx, wc=None):
1906 1912 """Do a clean update to the given commit.
1907 1913
1908 1914 This involves updating to the commit and discarding any changes in the
1909 1915 working copy.
1910 1916 """
1911 1917 return update(ctx.repo(), ctx.rev(), branchmerge=False, force=True, wc=wc)
1912 1918
1913 1919
1914 1920 def revert_to(ctx, matcher=None, wc=None):
1915 1921 """Revert the working copy to the given commit.
1916 1922
1917 1923 The working copy will keep its current parent(s) but its content will
1918 1924 be the same as in the given commit.
1919 1925 """
1920 1926
1921 1927 return update(
1922 1928 ctx.repo(),
1923 1929 ctx.rev(),
1924 1930 branchmerge=False,
1925 1931 force=True,
1926 1932 updatedirstate=False,
1927 1933 matcher=matcher,
1928 1934 wc=wc,
1929 1935 )
1930 1936
1931 1937
1932 1938 def graft(
1933 1939 repo,
1934 1940 ctx,
1935 1941 base=None,
1936 1942 labels=None,
1937 1943 keepparent=False,
1938 1944 keepconflictparent=False,
1939 1945 wctx=None,
1940 1946 ):
1941 1947 """Do a graft-like merge.
1942 1948
1943 1949 This is a merge where the merge ancestor is chosen such that one
1944 1950 or more changesets are grafted onto the current changeset. In
1945 1951 addition to the merge, this fixes up the dirstate to include only
1946 1952 a single parent (if keepparent is False) and tries to duplicate any
1947 1953 renames/copies appropriately.
1948 1954
1949 1955 ctx - changeset to rebase
1950 1956 base - merge base, or ctx.p1() if not specified
1951 1957 labels - merge labels eg ['local', 'graft']
1952 1958 keepparent - keep second parent if any
1953 1959 keepconflictparent - if unresolved, keep parent used for the merge
1954 1960
1955 1961 """
1956 1962 # If we're grafting a descendant onto an ancestor, be sure to pass
1957 1963 # mergeancestor=True to update. This does two things: 1) allows the merge if
1958 1964 # the destination is the same as the parent of the ctx (so we can use graft
1959 1965 # to copy commits), and 2) informs update that the incoming changes are
1960 1966 # newer than the destination so it doesn't prompt about "remote changed foo
1961 1967 # which local deleted".
1962 1968 # We also pass mergeancestor=True when base is the same revision as p1. 2)
1963 1969 # doesn't matter as there can't possibly be conflicts, but 1) is necessary.
1964 1970 wctx = wctx or repo[None]
1965 1971 pctx = wctx.p1()
1966 1972 base = base or ctx.p1()
1967 1973 mergeancestor = (
1968 1974 repo.changelog.isancestor(pctx.node(), ctx.node())
1969 1975 or pctx.rev() == base.rev()
1970 1976 )
1971 1977
1972 1978 stats = update(
1973 1979 repo,
1974 1980 ctx.node(),
1975 1981 True,
1976 1982 True,
1977 1983 base.node(),
1978 1984 mergeancestor=mergeancestor,
1979 1985 labels=labels,
1980 1986 wc=wctx,
1981 1987 )
1982 1988
1983 1989 if keepconflictparent and stats.unresolvedcount:
1984 1990 pother = ctx.node()
1985 1991 else:
1986 1992 pother = nullid
1987 1993 parents = ctx.parents()
1988 1994 if keepparent and len(parents) == 2 and base in parents:
1989 1995 parents.remove(base)
1990 1996 pother = parents[0].node()
1991 1997 # Never set both parents equal to each other
1992 1998 if pother == pctx.node():
1993 1999 pother = nullid
1994 2000
1995 2001 if wctx.isinmemory():
1996 2002 wctx.setparents(pctx.node(), pother)
1997 2003 # fix up dirstate for copies and renames
1998 2004 copies.graftcopies(wctx, ctx, base)
1999 2005 else:
2000 2006 with repo.dirstate.parentchange():
2001 2007 repo.setparents(pctx.node(), pother)
2002 2008 repo.dirstate.write(repo.currenttransaction())
2003 2009 # fix up dirstate for copies and renames
2004 2010 copies.graftcopies(wctx, ctx, base)
2005 2011 return stats
2006 2012
2007 2013
2008 2014 def purge(
2009 2015 repo,
2010 2016 matcher,
2011 2017 unknown=True,
2012 2018 ignored=False,
2013 2019 removeemptydirs=True,
2014 2020 removefiles=True,
2015 2021 abortonerror=False,
2016 2022 noop=False,
2017 2023 ):
2018 2024 """Purge the working directory of untracked files.
2019 2025
2020 2026 ``matcher`` is a matcher configured to scan the working directory -
2021 2027 potentially a subset.
2022 2028
2023 2029 ``unknown`` controls whether unknown files should be purged.
2024 2030
2025 2031 ``ignored`` controls whether ignored files should be purged.
2026 2032
2027 2033 ``removeemptydirs`` controls whether empty directories should be removed.
2028 2034
2029 2035 ``removefiles`` controls whether files are removed.
2030 2036
2031 2037 ``abortonerror`` causes an exception to be raised if an error occurs
2032 2038 deleting a file or directory.
2033 2039
2034 2040 ``noop`` controls whether to actually remove files. If not defined, actions
2035 2041 will be taken.
2036 2042
2037 2043 Returns an iterable of relative paths in the working directory that were
2038 2044 or would be removed.
2039 2045 """
2040 2046
2041 2047 def remove(removefn, path):
2042 2048 try:
2043 2049 removefn(path)
2044 2050 except OSError:
2045 2051 m = _(b'%s cannot be removed') % path
2046 2052 if abortonerror:
2047 2053 raise error.Abort(m)
2048 2054 else:
2049 2055 repo.ui.warn(_(b'warning: %s\n') % m)
2050 2056
2051 2057 # There's no API to copy a matcher. So mutate the passed matcher and
2052 2058 # restore it when we're done.
2053 2059 oldtraversedir = matcher.traversedir
2054 2060
2055 2061 res = []
2056 2062
2057 2063 try:
2058 2064 if removeemptydirs:
2059 2065 directories = []
2060 2066 matcher.traversedir = directories.append
2061 2067
2062 2068 status = repo.status(match=matcher, ignored=ignored, unknown=unknown)
2063 2069
2064 2070 if removefiles:
2065 2071 for f in sorted(status.unknown + status.ignored):
2066 2072 if not noop:
2067 2073 repo.ui.note(_(b'removing file %s\n') % f)
2068 2074 remove(repo.wvfs.unlink, f)
2069 2075 res.append(f)
2070 2076
2071 2077 if removeemptydirs:
2072 2078 for f in sorted(directories, reverse=True):
2073 2079 if matcher(f) and not repo.wvfs.listdir(f):
2074 2080 if not noop:
2075 2081 repo.ui.note(_(b'removing directory %s\n') % f)
2076 2082 remove(repo.wvfs.rmdir, f)
2077 2083 res.append(f)
2078 2084
2079 2085 return res
2080 2086
2081 2087 finally:
2082 2088 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now