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