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