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