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