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