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