##// END OF EJS Templates
merge: start using the per-side copy dicts...
Martin von Zweigbergk -
r44661:45f0d1cd default draft
parent child Browse files
Show More
@@ -1,1165 +1,1167 b''
1 1 # copies.py - copy detection for Mercurial
2 2 #
3 3 # Copyright 2008 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 multiprocessing
12 12 import os
13 13
14 14 from .i18n import _
15 15
16 16
17 17 from .revlogutils.flagutil import REVIDX_SIDEDATA
18 18
19 19 from . import (
20 20 error,
21 21 match as matchmod,
22 22 node,
23 23 pathutil,
24 24 pycompat,
25 25 util,
26 26 )
27 27
28 28 from .revlogutils import sidedata as sidedatamod
29 29
30 30 from .utils import stringutil
31 31
32 32
33 33 def _filter(src, dst, t):
34 34 """filters out invalid copies after chaining"""
35 35
36 36 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
37 37 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
38 38 # in the following table (not including trivial cases). For example, case 2
39 39 # is where a file existed in 'src' and remained under that name in 'mid' and
40 40 # then was renamed between 'mid' and 'dst'.
41 41 #
42 42 # case src mid dst result
43 43 # 1 x y - -
44 44 # 2 x y y x->y
45 45 # 3 x y x -
46 46 # 4 x y z x->z
47 47 # 5 - x y -
48 48 # 6 x x y x->y
49 49 #
50 50 # _chain() takes care of chaining the copies in 'a' and 'b', but it
51 51 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
52 52 # between 5 and 6, so it includes all cases in its result.
53 53 # Cases 1, 3, and 5 are then removed by _filter().
54 54
55 55 for k, v in list(t.items()):
56 56 # remove copies from files that didn't exist
57 57 if v not in src:
58 58 del t[k]
59 59 # remove criss-crossed copies
60 60 elif k in src and v in dst:
61 61 del t[k]
62 62 # remove copies to files that were then removed
63 63 elif k not in dst:
64 64 del t[k]
65 65
66 66
67 67 def _chain(prefix, suffix):
68 68 """chain two sets of copies 'prefix' and 'suffix'"""
69 69 result = prefix.copy()
70 70 for key, value in pycompat.iteritems(suffix):
71 71 result[key] = prefix.get(value, value)
72 72 return result
73 73
74 74
75 75 def _tracefile(fctx, am, basemf):
76 76 """return file context that is the ancestor of fctx present in ancestor
77 77 manifest am
78 78
79 79 Note: we used to try and stop after a given limit, however checking if that
80 80 limit is reached turned out to be very expensive. we are better off
81 81 disabling that feature."""
82 82
83 83 for f in fctx.ancestors():
84 84 path = f.path()
85 85 if am.get(path, None) == f.filenode():
86 86 return path
87 87 if basemf and basemf.get(path, None) == f.filenode():
88 88 return path
89 89
90 90
91 91 def _dirstatecopies(repo, match=None):
92 92 ds = repo.dirstate
93 93 c = ds.copies().copy()
94 94 for k in list(c):
95 95 if ds[k] not in b'anm' or (match and not match(k)):
96 96 del c[k]
97 97 return c
98 98
99 99
100 100 def _computeforwardmissing(a, b, match=None):
101 101 """Computes which files are in b but not a.
102 102 This is its own function so extensions can easily wrap this call to see what
103 103 files _forwardcopies is about to process.
104 104 """
105 105 ma = a.manifest()
106 106 mb = b.manifest()
107 107 return mb.filesnotin(ma, match=match)
108 108
109 109
110 110 def usechangesetcentricalgo(repo):
111 111 """Checks if we should use changeset-centric copy algorithms"""
112 112 if repo.filecopiesmode == b'changeset-sidedata':
113 113 return True
114 114 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
115 115 changesetsource = (b'changeset-only', b'compatibility')
116 116 return readfrom in changesetsource
117 117
118 118
119 119 def _committedforwardcopies(a, b, base, match):
120 120 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
121 121 # files might have to be traced back to the fctx parent of the last
122 122 # one-side-only changeset, but not further back than that
123 123 repo = a._repo
124 124
125 125 if usechangesetcentricalgo(repo):
126 126 return _changesetforwardcopies(a, b, match)
127 127
128 128 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
129 129 dbg = repo.ui.debug
130 130 if debug:
131 131 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
132 132 am = a.manifest()
133 133 basemf = None if base is None else base.manifest()
134 134
135 135 # find where new files came from
136 136 # we currently don't try to find where old files went, too expensive
137 137 # this means we can miss a case like 'hg rm b; hg cp a b'
138 138 cm = {}
139 139
140 140 # Computing the forward missing is quite expensive on large manifests, since
141 141 # it compares the entire manifests. We can optimize it in the common use
142 142 # case of computing what copies are in a commit versus its parent (like
143 143 # during a rebase or histedit). Note, we exclude merge commits from this
144 144 # optimization, since the ctx.files() for a merge commit is not correct for
145 145 # this comparison.
146 146 forwardmissingmatch = match
147 147 if b.p1() == a and b.p2().node() == node.nullid:
148 148 filesmatcher = matchmod.exact(b.files())
149 149 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
150 150 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
151 151
152 152 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
153 153
154 154 if debug:
155 155 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
156 156
157 157 for f in sorted(missing):
158 158 if debug:
159 159 dbg(b'debug.copies: tracing file: %s\n' % f)
160 160 fctx = b[f]
161 161 fctx._ancestrycontext = ancestrycontext
162 162
163 163 if debug:
164 164 start = util.timer()
165 165 opath = _tracefile(fctx, am, basemf)
166 166 if opath:
167 167 if debug:
168 168 dbg(b'debug.copies: rename of: %s\n' % opath)
169 169 cm[f] = opath
170 170 if debug:
171 171 dbg(
172 172 b'debug.copies: time: %f seconds\n'
173 173 % (util.timer() - start)
174 174 )
175 175 return cm
176 176
177 177
178 178 def _revinfogetter(repo):
179 179 """return a function that return multiple data given a <rev>"i
180 180
181 181 * p1: revision number of first parent
182 182 * p2: revision number of first parent
183 183 * p1copies: mapping of copies from p1
184 184 * p2copies: mapping of copies from p2
185 185 * removed: a list of removed files
186 186 """
187 187 cl = repo.changelog
188 188 parents = cl.parentrevs
189 189
190 190 if repo.filecopiesmode == b'changeset-sidedata':
191 191 changelogrevision = cl.changelogrevision
192 192 flags = cl.flags
193 193
194 194 # A small cache to avoid doing the work twice for merges
195 195 #
196 196 # In the vast majority of cases, if we ask information for a revision
197 197 # about 1 parent, we'll later ask it for the other. So it make sense to
198 198 # keep the information around when reaching the first parent of a merge
199 199 # and dropping it after it was provided for the second parents.
200 200 #
201 201 # It exists cases were only one parent of the merge will be walked. It
202 202 # happens when the "destination" the copy tracing is descendant from a
203 203 # new root, not common with the "source". In that case, we will only walk
204 204 # through merge parents that are descendant of changesets common
205 205 # between "source" and "destination".
206 206 #
207 207 # With the current case implementation if such changesets have a copy
208 208 # information, we'll keep them in memory until the end of
209 209 # _changesetforwardcopies. We don't expect the case to be frequent
210 210 # enough to matters.
211 211 #
212 212 # In addition, it would be possible to reach pathological case, were
213 213 # many first parent are met before any second parent is reached. In
214 214 # that case the cache could grow. If this even become an issue one can
215 215 # safely introduce a maximum cache size. This would trade extra CPU/IO
216 216 # time to save memory.
217 217 merge_caches = {}
218 218
219 219 def revinfo(rev):
220 220 p1, p2 = parents(rev)
221 221 if flags(rev) & REVIDX_SIDEDATA:
222 222 e = merge_caches.pop(rev, None)
223 223 if e is not None:
224 224 return e
225 225 c = changelogrevision(rev)
226 226 p1copies = c.p1copies
227 227 p2copies = c.p2copies
228 228 removed = c.filesremoved
229 229 if p1 != node.nullrev and p2 != node.nullrev:
230 230 # XXX some case we over cache, IGNORE
231 231 merge_caches[rev] = (p1, p2, p1copies, p2copies, removed)
232 232 else:
233 233 p1copies = {}
234 234 p2copies = {}
235 235 removed = []
236 236 return p1, p2, p1copies, p2copies, removed
237 237
238 238 else:
239 239
240 240 def revinfo(rev):
241 241 p1, p2 = parents(rev)
242 242 ctx = repo[rev]
243 243 p1copies, p2copies = ctx._copies
244 244 removed = ctx.filesremoved()
245 245 return p1, p2, p1copies, p2copies, removed
246 246
247 247 return revinfo
248 248
249 249
250 250 def _changesetforwardcopies(a, b, match):
251 251 if a.rev() in (node.nullrev, b.rev()):
252 252 return {}
253 253
254 254 repo = a.repo().unfiltered()
255 255 children = {}
256 256 revinfo = _revinfogetter(repo)
257 257
258 258 cl = repo.changelog
259 259 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
260 260 mrset = set(missingrevs)
261 261 roots = set()
262 262 for r in missingrevs:
263 263 for p in cl.parentrevs(r):
264 264 if p == node.nullrev:
265 265 continue
266 266 if p not in children:
267 267 children[p] = [r]
268 268 else:
269 269 children[p].append(r)
270 270 if p not in mrset:
271 271 roots.add(p)
272 272 if not roots:
273 273 # no common revision to track copies from
274 274 return {}
275 275 min_root = min(roots)
276 276
277 277 from_head = set(
278 278 cl.reachableroots(min_root, [b.rev()], list(roots), includepath=True)
279 279 )
280 280
281 281 iterrevs = set(from_head)
282 282 iterrevs &= mrset
283 283 iterrevs.update(roots)
284 284 iterrevs.remove(b.rev())
285 285 revs = sorted(iterrevs)
286 286 return _combinechangesetcopies(revs, children, b.rev(), revinfo, match)
287 287
288 288
289 289 def _combinechangesetcopies(revs, children, targetrev, revinfo, match):
290 290 """combine the copies information for each item of iterrevs
291 291
292 292 revs: sorted iterable of revision to visit
293 293 children: a {parent: [children]} mapping.
294 294 targetrev: the final copies destination revision (not in iterrevs)
295 295 revinfo(rev): a function that return (p1, p2, p1copies, p2copies, removed)
296 296 match: a matcher
297 297
298 298 It returns the aggregated copies information for `targetrev`.
299 299 """
300 300 all_copies = {}
301 301 alwaysmatch = match.always()
302 302 for r in revs:
303 303 copies = all_copies.pop(r, None)
304 304 if copies is None:
305 305 # this is a root
306 306 copies = {}
307 307 for i, c in enumerate(children[r]):
308 308 p1, p2, p1copies, p2copies, removed = revinfo(c)
309 309 if r == p1:
310 310 parent = 1
311 311 childcopies = p1copies
312 312 else:
313 313 assert r == p2
314 314 parent = 2
315 315 childcopies = p2copies
316 316 if not alwaysmatch:
317 317 childcopies = {
318 318 dst: src for dst, src in childcopies.items() if match(dst)
319 319 }
320 320 newcopies = copies
321 321 if childcopies:
322 322 newcopies = _chain(newcopies, childcopies)
323 323 # _chain makes a copies, we can avoid doing so in some
324 324 # simple/linear cases.
325 325 assert newcopies is not copies
326 326 for f in removed:
327 327 if f in newcopies:
328 328 if newcopies is copies:
329 329 # copy on write to avoid affecting potential other
330 330 # branches. when there are no other branches, this
331 331 # could be avoided.
332 332 newcopies = copies.copy()
333 333 del newcopies[f]
334 334 othercopies = all_copies.get(c)
335 335 if othercopies is None:
336 336 all_copies[c] = newcopies
337 337 else:
338 338 # we are the second parent to work on c, we need to merge our
339 339 # work with the other.
340 340 #
341 341 # Unlike when copies are stored in the filelog, we consider
342 342 # it a copy even if the destination already existed on the
343 343 # other branch. It's simply too expensive to check if the
344 344 # file existed in the manifest.
345 345 #
346 346 # In case of conflict, parent 1 take precedence over parent 2.
347 347 # This is an arbitrary choice made anew when implementing
348 348 # changeset based copies. It was made without regards with
349 349 # potential filelog related behavior.
350 350 if parent == 1:
351 351 othercopies.update(newcopies)
352 352 else:
353 353 newcopies.update(othercopies)
354 354 all_copies[c] = newcopies
355 355 return all_copies[targetrev]
356 356
357 357
358 358 def _forwardcopies(a, b, base=None, match=None):
359 359 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
360 360
361 361 if base is None:
362 362 base = a
363 363 match = a.repo().narrowmatch(match)
364 364 # check for working copy
365 365 if b.rev() is None:
366 366 cm = _committedforwardcopies(a, b.p1(), base, match)
367 367 # combine copies from dirstate if necessary
368 368 copies = _chain(cm, _dirstatecopies(b._repo, match))
369 369 else:
370 370 copies = _committedforwardcopies(a, b, base, match)
371 371 return copies
372 372
373 373
374 374 def _backwardrenames(a, b, match):
375 375 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
376 376 return {}
377 377
378 378 # Even though we're not taking copies into account, 1:n rename situations
379 379 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
380 380 # arbitrarily pick one of the renames.
381 381 # We don't want to pass in "match" here, since that would filter
382 382 # the destination by it. Since we're reversing the copies, we want
383 383 # to filter the source instead.
384 384 f = _forwardcopies(b, a)
385 385 r = {}
386 386 for k, v in sorted(pycompat.iteritems(f)):
387 387 if match and not match(v):
388 388 continue
389 389 # remove copies
390 390 if v in a:
391 391 continue
392 392 r[v] = k
393 393 return r
394 394
395 395
396 396 def pathcopies(x, y, match=None):
397 397 """find {dst@y: src@x} copy mapping for directed compare"""
398 398 repo = x._repo
399 399 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
400 400 if debug:
401 401 repo.ui.debug(
402 402 b'debug.copies: searching copies from %s to %s\n' % (x, y)
403 403 )
404 404 if x == y or not x or not y:
405 405 return {}
406 406 a = y.ancestor(x)
407 407 if a == x:
408 408 if debug:
409 409 repo.ui.debug(b'debug.copies: search mode: forward\n')
410 410 if y.rev() is None and x == y.p1():
411 411 # short-circuit to avoid issues with merge states
412 412 return _dirstatecopies(repo, match)
413 413 copies = _forwardcopies(x, y, match=match)
414 414 elif a == y:
415 415 if debug:
416 416 repo.ui.debug(b'debug.copies: search mode: backward\n')
417 417 copies = _backwardrenames(x, y, match=match)
418 418 else:
419 419 if debug:
420 420 repo.ui.debug(b'debug.copies: search mode: combined\n')
421 421 base = None
422 422 if a.rev() != node.nullrev:
423 423 base = x
424 424 copies = _chain(
425 425 _backwardrenames(x, a, match=match),
426 426 _forwardcopies(a, y, base, match=match),
427 427 )
428 428 _filter(x, y, copies)
429 429 return copies
430 430
431 431
432 432 def mergecopies(repo, c1, c2, base):
433 433 """
434 434 Finds moves and copies between context c1 and c2 that are relevant for
435 435 merging. 'base' will be used as the merge base.
436 436
437 437 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
438 438 files that were moved/ copied in one merge parent and modified in another.
439 439 For example:
440 440
441 441 o ---> 4 another commit
442 442 |
443 443 | o ---> 3 commit that modifies a.txt
444 444 | /
445 445 o / ---> 2 commit that moves a.txt to b.txt
446 446 |/
447 447 o ---> 1 merge base
448 448
449 449 If we try to rebase revision 3 on revision 4, since there is no a.txt in
450 450 revision 4, and if user have copytrace disabled, we prints the following
451 451 message:
452 452
453 453 ```other changed <file> which local deleted```
454 454
455 455 Returns a tuple where:
456 456
457 457 "branch_copies" an instance of branch_copies.
458 458
459 459 "diverge" is a mapping of source name -> list of destination names
460 460 for divergent renames.
461 461
462 462 This function calls different copytracing algorithms based on config.
463 463 """
464 464 # avoid silly behavior for update from empty dir
465 465 if not c1 or not c2 or c1 == c2:
466 return branch_copies(), {}
466 return branch_copies(), branch_copies(), {}
467 467
468 468 narrowmatch = c1.repo().narrowmatch()
469 469
470 470 # avoid silly behavior for parent -> working dir
471 471 if c2.node() is None and c1.node() == repo.dirstate.p1():
472 return branch_copies(_dirstatecopies(repo, narrowmatch)), {}
472 return (
473 branch_copies(_dirstatecopies(repo, narrowmatch)),
474 branch_copies(),
475 {},
476 )
473 477
474 478 copytracing = repo.ui.config(b'experimental', b'copytrace')
475 479 if stringutil.parsebool(copytracing) is False:
476 480 # stringutil.parsebool() returns None when it is unable to parse the
477 481 # value, so we should rely on making sure copytracing is on such cases
478 return branch_copies(), {}
482 return branch_copies(), branch_copies(), {}
479 483
480 484 if usechangesetcentricalgo(repo):
481 485 # The heuristics don't make sense when we need changeset-centric algos
482 486 return _fullcopytracing(repo, c1, c2, base)
483 487
484 488 # Copy trace disabling is explicitly below the node == p1 logic above
485 489 # because the logic above is required for a simple copy to be kept across a
486 490 # rebase.
487 491 if copytracing == b'heuristics':
488 492 # Do full copytracing if only non-public revisions are involved as
489 493 # that will be fast enough and will also cover the copies which could
490 494 # be missed by heuristics
491 495 if _isfullcopytraceable(repo, c1, base):
492 496 return _fullcopytracing(repo, c1, c2, base)
493 497 return _heuristicscopytracing(repo, c1, c2, base)
494 498 else:
495 499 return _fullcopytracing(repo, c1, c2, base)
496 500
497 501
498 502 def _isfullcopytraceable(repo, c1, base):
499 503 """ Checks that if base, source and destination are all no-public branches,
500 504 if yes let's use the full copytrace algorithm for increased capabilities
501 505 since it will be fast enough.
502 506
503 507 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
504 508 number of changesets from c1 to base such that if number of changesets are
505 509 more than the limit, full copytracing algorithm won't be used.
506 510 """
507 511 if c1.rev() is None:
508 512 c1 = c1.p1()
509 513 if c1.mutable() and base.mutable():
510 514 sourcecommitlimit = repo.ui.configint(
511 515 b'experimental', b'copytrace.sourcecommitlimit'
512 516 )
513 517 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
514 518 return commits < sourcecommitlimit
515 519 return False
516 520
517 521
518 522 def _checksinglesidecopies(
519 523 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
520 524 ):
521 525 if src not in m2:
522 526 # deleted on side 2
523 527 if src not in m1:
524 528 # renamed on side 1, deleted on side 2
525 529 renamedelete[src] = dsts1
526 530 elif m2[src] != mb[src]:
527 531 if not _related(c2[src], base[src]):
528 532 return
529 533 # modified on side 2
530 534 for dst in dsts1:
531 535 if dst not in m2:
532 536 # dst not added on side 2 (handle as regular
533 537 # "both created" case in manifestmerge otherwise)
534 538 copy[dst] = src
535 539
536 540
537 541 class branch_copies(object):
538 542 """Information about copies made on one side of a merge/graft.
539 543
540 544 "copy" is a mapping from destination name -> source name,
541 545 where source is in c1 and destination is in c2 or vice-versa.
542 546
543 547 "movewithdir" is a mapping from source name -> destination name,
544 548 where the file at source present in one context but not the other
545 549 needs to be moved to destination by the merge process, because the
546 550 other context moved the directory it is in.
547 551
548 552 "renamedelete" is a mapping of source name -> list of destination
549 553 names for files deleted in c1 that were renamed in c2 or vice-versa.
550 554
551 555 "dirmove" is a mapping of detected source dir -> destination dir renames.
552 556 This is needed for handling changes to new files previously grafted into
553 557 renamed directories.
554 558 """
555 559
556 560 def __init__(
557 561 self, copy=None, renamedelete=None, dirmove=None, movewithdir=None
558 562 ):
559 563 self.copy = {} if copy is None else copy
560 564 self.renamedelete = {} if renamedelete is None else renamedelete
561 565 self.dirmove = {} if dirmove is None else dirmove
562 566 self.movewithdir = {} if movewithdir is None else movewithdir
563 567
564 568
565 569 def _fullcopytracing(repo, c1, c2, base):
566 570 """ The full copytracing algorithm which finds all the new files that were
567 571 added from merge base up to the top commit and for each file it checks if
568 572 this file was copied from another file.
569 573
570 574 This is pretty slow when a lot of changesets are involved but will track all
571 575 the copies.
572 576 """
573 577 m1 = c1.manifest()
574 578 m2 = c2.manifest()
575 579 mb = base.manifest()
576 580
577 581 copies1 = pathcopies(base, c1)
578 582 copies2 = pathcopies(base, c2)
579 583
580 584 if not (copies1 or copies2):
581 return branch_copies(), {}
585 return branch_copies(), branch_copies(), {}
582 586
583 587 inversecopies1 = {}
584 588 inversecopies2 = {}
585 589 for dst, src in copies1.items():
586 590 inversecopies1.setdefault(src, []).append(dst)
587 591 for dst, src in copies2.items():
588 592 inversecopies2.setdefault(src, []).append(dst)
589 593
590 594 copy1 = {}
591 595 copy2 = {}
592 596 diverge = {}
593 597 renamedelete1 = {}
594 598 renamedelete2 = {}
595 599 allsources = set(inversecopies1) | set(inversecopies2)
596 600 for src in allsources:
597 601 dsts1 = inversecopies1.get(src)
598 602 dsts2 = inversecopies2.get(src)
599 603 if dsts1 and dsts2:
600 604 # copied/renamed on both sides
601 605 if src not in m1 and src not in m2:
602 606 # renamed on both sides
603 607 dsts1 = set(dsts1)
604 608 dsts2 = set(dsts2)
605 609 # If there's some overlap in the rename destinations, we
606 610 # consider it not divergent. For example, if side 1 copies 'a'
607 611 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
608 612 # and 'd' and deletes 'a'.
609 613 if dsts1 & dsts2:
610 614 for dst in dsts1 & dsts2:
611 615 copy1[dst] = src
612 616 copy2[dst] = src
613 617 else:
614 618 diverge[src] = sorted(dsts1 | dsts2)
615 619 elif src in m1 and src in m2:
616 620 # copied on both sides
617 621 dsts1 = set(dsts1)
618 622 dsts2 = set(dsts2)
619 623 for dst in dsts1 & dsts2:
620 624 copy1[dst] = src
621 625 copy2[dst] = src
622 626 # TODO: Handle cases where it was renamed on one side and copied
623 627 # on the other side
624 628 elif dsts1:
625 629 # copied/renamed only on side 1
626 630 _checksinglesidecopies(
627 631 src, dsts1, m1, m2, mb, c2, base, copy1, renamedelete1
628 632 )
629 633 elif dsts2:
630 634 # copied/renamed only on side 2
631 635 _checksinglesidecopies(
632 636 src, dsts2, m2, m1, mb, c1, base, copy2, renamedelete2
633 637 )
634 638
635 639 # find interesting file sets from manifests
636 640 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
637 641 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
638 642 u1 = sorted(addedinm1 - addedinm2)
639 643 u2 = sorted(addedinm2 - addedinm1)
640 644
641 645 header = b" unmatched files in %s"
642 646 if u1:
643 647 repo.ui.debug(b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1)))
644 648 if u2:
645 649 repo.ui.debug(b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2)))
646 650
647 651 if repo.ui.debugflag:
648 652 renamedeleteset = set()
649 653 divergeset = set()
650 654 for dsts in diverge.values():
651 655 divergeset.update(dsts)
652 656 for dsts in renamedelete1.values():
653 657 renamedeleteset.update(dsts)
654 658 for dsts in renamedelete2.values():
655 659 renamedeleteset.update(dsts)
656 660
657 661 repo.ui.debug(
658 662 b" all copies found (* = to merge, ! = divergent, "
659 663 b"% = renamed and deleted):\n"
660 664 )
661 665 for side, copies in (("local", copies1), ("remote", copies2)):
662 666 if not copies:
663 667 continue
664 668 repo.ui.debug(b" on %s side:\n" % side)
665 669 for f in sorted(copies):
666 670 note = b""
667 671 if f in copy1 or f in copy2:
668 672 note += b"*"
669 673 if f in divergeset:
670 674 note += b"!"
671 675 if f in renamedeleteset:
672 676 note += b"%"
673 677 repo.ui.debug(
674 678 b" src: '%s' -> dst: '%s' %s\n" % (copies[f], f, note)
675 679 )
676 680 del renamedeleteset
677 681 del divergeset
678 682
679 683 repo.ui.debug(b" checking for directory renames\n")
680 684
681 685 dirmove1, movewithdir2 = _dir_renames(repo, c1, copy1, copies1, u2)
682 686 dirmove2, movewithdir1 = _dir_renames(repo, c2, copy2, copies2, u1)
683 687
684 copy1.update(copy2)
685 renamedelete1.update(renamedelete2)
686 movewithdir1.update(movewithdir2)
687 dirmove1.update(dirmove2)
688 branch_copies1 = branch_copies(copy1, renamedelete1, dirmove1, movewithdir1)
689 branch_copies2 = branch_copies(copy2, renamedelete2, dirmove2, movewithdir2)
688 690
689 return branch_copies(copy1, renamedelete1, dirmove1, movewithdir1), diverge
691 return branch_copies1, branch_copies2, diverge
690 692
691 693
692 694 def _dir_renames(repo, ctx, copy, fullcopy, addedfiles):
693 695 """Finds moved directories and files that should move with them.
694 696
695 697 ctx: the context for one of the sides
696 698 copy: files copied on the same side (as ctx)
697 699 fullcopy: files copied on the same side (as ctx), including those that
698 700 merge.manifestmerge() won't care about
699 701 addedfiles: added files on the other side (compared to ctx)
700 702 """
701 703 # generate a directory move map
702 704 d = ctx.dirs()
703 705 invalid = set()
704 706 dirmove = {}
705 707
706 708 # examine each file copy for a potential directory move, which is
707 709 # when all the files in a directory are moved to a new directory
708 710 for dst, src in pycompat.iteritems(fullcopy):
709 711 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
710 712 if dsrc in invalid:
711 713 # already seen to be uninteresting
712 714 continue
713 715 elif dsrc in d and ddst in d:
714 716 # directory wasn't entirely moved locally
715 717 invalid.add(dsrc)
716 718 elif dsrc in dirmove and dirmove[dsrc] != ddst:
717 719 # files from the same directory moved to two different places
718 720 invalid.add(dsrc)
719 721 else:
720 722 # looks good so far
721 723 dirmove[dsrc] = ddst
722 724
723 725 for i in invalid:
724 726 if i in dirmove:
725 727 del dirmove[i]
726 728 del d, invalid
727 729
728 730 if not dirmove:
729 731 return {}, {}
730 732
731 733 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
732 734
733 735 for d in dirmove:
734 736 repo.ui.debug(
735 737 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
736 738 )
737 739
738 740 movewithdir = {}
739 741 # check unaccounted nonoverlapping files against directory moves
740 742 for f in addedfiles:
741 743 if f not in fullcopy:
742 744 for d in dirmove:
743 745 if f.startswith(d):
744 746 # new file added in a directory that was moved, move it
745 747 df = dirmove[d] + f[len(d) :]
746 748 if df not in copy:
747 749 movewithdir[f] = df
748 750 repo.ui.debug(
749 751 b" pending file src: '%s' -> dst: '%s'\n"
750 752 % (f, df)
751 753 )
752 754 break
753 755
754 756 return dirmove, movewithdir
755 757
756 758
757 759 def _heuristicscopytracing(repo, c1, c2, base):
758 760 """ Fast copytracing using filename heuristics
759 761
760 762 Assumes that moves or renames are of following two types:
761 763
762 764 1) Inside a directory only (same directory name but different filenames)
763 765 2) Move from one directory to another
764 766 (same filenames but different directory names)
765 767
766 768 Works only when there are no merge commits in the "source branch".
767 769 Source branch is commits from base up to c2 not including base.
768 770
769 771 If merge is involved it fallbacks to _fullcopytracing().
770 772
771 773 Can be used by setting the following config:
772 774
773 775 [experimental]
774 776 copytrace = heuristics
775 777
776 778 In some cases the copy/move candidates found by heuristics can be very large
777 779 in number and that will make the algorithm slow. The number of possible
778 780 candidates to check can be limited by using the config
779 781 `experimental.copytrace.movecandidateslimit` which defaults to 100.
780 782 """
781 783
782 784 if c1.rev() is None:
783 785 c1 = c1.p1()
784 786 if c2.rev() is None:
785 787 c2 = c2.p1()
786 788
787 copies = {}
788
789 789 changedfiles = set()
790 790 m1 = c1.manifest()
791 791 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
792 792 # If base is not in c2 branch, we switch to fullcopytracing
793 793 repo.ui.debug(
794 794 b"switching to full copytracing as base is not "
795 795 b"an ancestor of c2\n"
796 796 )
797 797 return _fullcopytracing(repo, c1, c2, base)
798 798
799 799 ctx = c2
800 800 while ctx != base:
801 801 if len(ctx.parents()) == 2:
802 802 # To keep things simple let's not handle merges
803 803 repo.ui.debug(b"switching to full copytracing because of merges\n")
804 804 return _fullcopytracing(repo, c1, c2, base)
805 805 changedfiles.update(ctx.files())
806 806 ctx = ctx.p1()
807 807
808 copies2 = {}
808 809 cp = _forwardcopies(base, c2)
809 810 for dst, src in pycompat.iteritems(cp):
810 811 if src in m1:
811 copies[dst] = src
812 copies2[dst] = src
812 813
813 814 # file is missing if it isn't present in the destination, but is present in
814 815 # the base and present in the source.
815 816 # Presence in the base is important to exclude added files, presence in the
816 817 # source is important to exclude removed files.
817 818 filt = lambda f: f not in m1 and f in base and f in c2
818 819 missingfiles = [f for f in changedfiles if filt(f)]
819 820
821 copies1 = {}
820 822 if missingfiles:
821 823 basenametofilename = collections.defaultdict(list)
822 824 dirnametofilename = collections.defaultdict(list)
823 825
824 826 for f in m1.filesnotin(base.manifest()):
825 827 basename = os.path.basename(f)
826 828 dirname = os.path.dirname(f)
827 829 basenametofilename[basename].append(f)
828 830 dirnametofilename[dirname].append(f)
829 831
830 832 for f in missingfiles:
831 833 basename = os.path.basename(f)
832 834 dirname = os.path.dirname(f)
833 835 samebasename = basenametofilename[basename]
834 836 samedirname = dirnametofilename[dirname]
835 837 movecandidates = samebasename + samedirname
836 838 # f is guaranteed to be present in c2, that's why
837 839 # c2.filectx(f) won't fail
838 840 f2 = c2.filectx(f)
839 841 # we can have a lot of candidates which can slow down the heuristics
840 842 # config value to limit the number of candidates moves to check
841 843 maxcandidates = repo.ui.configint(
842 844 b'experimental', b'copytrace.movecandidateslimit'
843 845 )
844 846
845 847 if len(movecandidates) > maxcandidates:
846 848 repo.ui.status(
847 849 _(
848 850 b"skipping copytracing for '%s', more "
849 851 b"candidates than the limit: %d\n"
850 852 )
851 853 % (f, len(movecandidates))
852 854 )
853 855 continue
854 856
855 857 for candidate in movecandidates:
856 858 f1 = c1.filectx(candidate)
857 859 if _related(f1, f2):
858 860 # if there are a few related copies then we'll merge
859 861 # changes into all of them. This matches the behaviour
860 862 # of upstream copytracing
861 copies[candidate] = f
863 copies1[candidate] = f
862 864
863 return branch_copies(copies), {}
865 return branch_copies(copies1), branch_copies(copies2), {}
864 866
865 867
866 868 def _related(f1, f2):
867 869 """return True if f1 and f2 filectx have a common ancestor
868 870
869 871 Walk back to common ancestor to see if the two files originate
870 872 from the same file. Since workingfilectx's rev() is None it messes
871 873 up the integer comparison logic, hence the pre-step check for
872 874 None (f1 and f2 can only be workingfilectx's initially).
873 875 """
874 876
875 877 if f1 == f2:
876 878 return True # a match
877 879
878 880 g1, g2 = f1.ancestors(), f2.ancestors()
879 881 try:
880 882 f1r, f2r = f1.linkrev(), f2.linkrev()
881 883
882 884 if f1r is None:
883 885 f1 = next(g1)
884 886 if f2r is None:
885 887 f2 = next(g2)
886 888
887 889 while True:
888 890 f1r, f2r = f1.linkrev(), f2.linkrev()
889 891 if f1r > f2r:
890 892 f1 = next(g1)
891 893 elif f2r > f1r:
892 894 f2 = next(g2)
893 895 else: # f1 and f2 point to files in the same linkrev
894 896 return f1 == f2 # true if they point to the same file
895 897 except StopIteration:
896 898 return False
897 899
898 900
899 901 def graftcopies(wctx, ctx, base):
900 902 """reproduce copies between base and ctx in the wctx
901 903
902 904 Unlike mergecopies(), this function will only consider copies between base
903 905 and ctx; it will ignore copies between base and wctx. Also unlike
904 906 mergecopies(), this function will apply copies to the working copy (instead
905 907 of just returning information about the copies). That makes it cheaper
906 908 (especially in the common case of base==ctx.p1()) and useful also when
907 909 experimental.copytrace=off.
908 910
909 911 merge.update() will have already marked most copies, but it will only
910 912 mark copies if it thinks the source files are related (see
911 913 merge._related()). It will also not mark copies if the file wasn't modified
912 914 on the local side. This function adds the copies that were "missed"
913 915 by merge.update().
914 916 """
915 917 new_copies = pathcopies(base, ctx)
916 918 _filter(wctx.p1(), wctx, new_copies)
917 919 for dst, src in pycompat.iteritems(new_copies):
918 920 wctx[dst].markcopied(src)
919 921
920 922
921 923 def computechangesetfilesadded(ctx):
922 924 """return the list of files added in a changeset
923 925 """
924 926 added = []
925 927 for f in ctx.files():
926 928 if not any(f in p for p in ctx.parents()):
927 929 added.append(f)
928 930 return added
929 931
930 932
931 933 def computechangesetfilesremoved(ctx):
932 934 """return the list of files removed in a changeset
933 935 """
934 936 removed = []
935 937 for f in ctx.files():
936 938 if f not in ctx:
937 939 removed.append(f)
938 940 return removed
939 941
940 942
941 943 def computechangesetcopies(ctx):
942 944 """return the copies data for a changeset
943 945
944 946 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
945 947
946 948 Each dictionnary are in the form: `{newname: oldname}`
947 949 """
948 950 p1copies = {}
949 951 p2copies = {}
950 952 p1 = ctx.p1()
951 953 p2 = ctx.p2()
952 954 narrowmatch = ctx._repo.narrowmatch()
953 955 for dst in ctx.files():
954 956 if not narrowmatch(dst) or dst not in ctx:
955 957 continue
956 958 copied = ctx[dst].renamed()
957 959 if not copied:
958 960 continue
959 961 src, srcnode = copied
960 962 if src in p1 and p1[src].filenode() == srcnode:
961 963 p1copies[dst] = src
962 964 elif src in p2 and p2[src].filenode() == srcnode:
963 965 p2copies[dst] = src
964 966 return p1copies, p2copies
965 967
966 968
967 969 def encodecopies(files, copies):
968 970 items = []
969 971 for i, dst in enumerate(files):
970 972 if dst in copies:
971 973 items.append(b'%d\0%s' % (i, copies[dst]))
972 974 if len(items) != len(copies):
973 975 raise error.ProgrammingError(
974 976 b'some copy targets missing from file list'
975 977 )
976 978 return b"\n".join(items)
977 979
978 980
979 981 def decodecopies(files, data):
980 982 try:
981 983 copies = {}
982 984 if not data:
983 985 return copies
984 986 for l in data.split(b'\n'):
985 987 strindex, src = l.split(b'\0')
986 988 i = int(strindex)
987 989 dst = files[i]
988 990 copies[dst] = src
989 991 return copies
990 992 except (ValueError, IndexError):
991 993 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
992 994 # used different syntax for the value.
993 995 return None
994 996
995 997
996 998 def encodefileindices(files, subset):
997 999 subset = set(subset)
998 1000 indices = []
999 1001 for i, f in enumerate(files):
1000 1002 if f in subset:
1001 1003 indices.append(b'%d' % i)
1002 1004 return b'\n'.join(indices)
1003 1005
1004 1006
1005 1007 def decodefileindices(files, data):
1006 1008 try:
1007 1009 subset = []
1008 1010 if not data:
1009 1011 return subset
1010 1012 for strindex in data.split(b'\n'):
1011 1013 i = int(strindex)
1012 1014 if i < 0 or i >= len(files):
1013 1015 return None
1014 1016 subset.append(files[i])
1015 1017 return subset
1016 1018 except (ValueError, IndexError):
1017 1019 # Perhaps someone had chosen the same key name (e.g. "added") and
1018 1020 # used different syntax for the value.
1019 1021 return None
1020 1022
1021 1023
1022 1024 def _getsidedata(srcrepo, rev):
1023 1025 ctx = srcrepo[rev]
1024 1026 filescopies = computechangesetcopies(ctx)
1025 1027 filesadded = computechangesetfilesadded(ctx)
1026 1028 filesremoved = computechangesetfilesremoved(ctx)
1027 1029 sidedata = {}
1028 1030 if any([filescopies, filesadded, filesremoved]):
1029 1031 sortedfiles = sorted(ctx.files())
1030 1032 p1copies, p2copies = filescopies
1031 1033 p1copies = encodecopies(sortedfiles, p1copies)
1032 1034 p2copies = encodecopies(sortedfiles, p2copies)
1033 1035 filesadded = encodefileindices(sortedfiles, filesadded)
1034 1036 filesremoved = encodefileindices(sortedfiles, filesremoved)
1035 1037 if p1copies:
1036 1038 sidedata[sidedatamod.SD_P1COPIES] = p1copies
1037 1039 if p2copies:
1038 1040 sidedata[sidedatamod.SD_P2COPIES] = p2copies
1039 1041 if filesadded:
1040 1042 sidedata[sidedatamod.SD_FILESADDED] = filesadded
1041 1043 if filesremoved:
1042 1044 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
1043 1045 return sidedata
1044 1046
1045 1047
1046 1048 def getsidedataadder(srcrepo, destrepo):
1047 1049 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
1048 1050 if pycompat.iswindows or not use_w:
1049 1051 return _get_simple_sidedata_adder(srcrepo, destrepo)
1050 1052 else:
1051 1053 return _get_worker_sidedata_adder(srcrepo, destrepo)
1052 1054
1053 1055
1054 1056 def _sidedata_worker(srcrepo, revs_queue, sidedata_queue, tokens):
1055 1057 """The function used by worker precomputing sidedata
1056 1058
1057 1059 It read an input queue containing revision numbers
1058 1060 It write in an output queue containing (rev, <sidedata-map>)
1059 1061
1060 1062 The `None` input value is used as a stop signal.
1061 1063
1062 1064 The `tokens` semaphore is user to avoid having too many unprocessed
1063 1065 entries. The workers needs to acquire one token before fetching a task.
1064 1066 They will be released by the consumer of the produced data.
1065 1067 """
1066 1068 tokens.acquire()
1067 1069 rev = revs_queue.get()
1068 1070 while rev is not None:
1069 1071 data = _getsidedata(srcrepo, rev)
1070 1072 sidedata_queue.put((rev, data))
1071 1073 tokens.acquire()
1072 1074 rev = revs_queue.get()
1073 1075 # processing of `None` is completed, release the token.
1074 1076 tokens.release()
1075 1077
1076 1078
1077 1079 BUFF_PER_WORKER = 50
1078 1080
1079 1081
1080 1082 def _get_worker_sidedata_adder(srcrepo, destrepo):
1081 1083 """The parallel version of the sidedata computation
1082 1084
1083 1085 This code spawn a pool of worker that precompute a buffer of sidedata
1084 1086 before we actually need them"""
1085 1087 # avoid circular import copies -> scmutil -> worker -> copies
1086 1088 from . import worker
1087 1089
1088 1090 nbworkers = worker._numworkers(srcrepo.ui)
1089 1091
1090 1092 tokens = multiprocessing.BoundedSemaphore(nbworkers * BUFF_PER_WORKER)
1091 1093 revsq = multiprocessing.Queue()
1092 1094 sidedataq = multiprocessing.Queue()
1093 1095
1094 1096 assert srcrepo.filtername is None
1095 1097 # queue all tasks beforehand, revision numbers are small and it make
1096 1098 # synchronisation simpler
1097 1099 #
1098 1100 # Since the computation for each node can be quite expensive, the overhead
1099 1101 # of using a single queue is not revelant. In practice, most computation
1100 1102 # are fast but some are very expensive and dominate all the other smaller
1101 1103 # cost.
1102 1104 for r in srcrepo.changelog.revs():
1103 1105 revsq.put(r)
1104 1106 # queue the "no more tasks" markers
1105 1107 for i in range(nbworkers):
1106 1108 revsq.put(None)
1107 1109
1108 1110 allworkers = []
1109 1111 for i in range(nbworkers):
1110 1112 args = (srcrepo, revsq, sidedataq, tokens)
1111 1113 w = multiprocessing.Process(target=_sidedata_worker, args=args)
1112 1114 allworkers.append(w)
1113 1115 w.start()
1114 1116
1115 1117 # dictionnary to store results for revision higher than we one we are
1116 1118 # looking for. For example, if we need the sidedatamap for 42, and 43 is
1117 1119 # received, when shelve 43 for later use.
1118 1120 staging = {}
1119 1121
1120 1122 def sidedata_companion(revlog, rev):
1121 1123 sidedata = {}
1122 1124 if util.safehasattr(revlog, b'filteredrevs'): # this is a changelog
1123 1125 # Is the data previously shelved ?
1124 1126 sidedata = staging.pop(rev, None)
1125 1127 if sidedata is None:
1126 1128 # look at the queued result until we find the one we are lookig
1127 1129 # for (shelve the other ones)
1128 1130 r, sidedata = sidedataq.get()
1129 1131 while r != rev:
1130 1132 staging[r] = sidedata
1131 1133 r, sidedata = sidedataq.get()
1132 1134 tokens.release()
1133 1135 return False, (), sidedata
1134 1136
1135 1137 return sidedata_companion
1136 1138
1137 1139
1138 1140 def _get_simple_sidedata_adder(srcrepo, destrepo):
1139 1141 """The simple version of the sidedata computation
1140 1142
1141 1143 It just compute it in the same thread on request"""
1142 1144
1143 1145 def sidedatacompanion(revlog, rev):
1144 1146 sidedata = {}
1145 1147 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
1146 1148 sidedata = _getsidedata(srcrepo, rev)
1147 1149 return False, (), sidedata
1148 1150
1149 1151 return sidedatacompanion
1150 1152
1151 1153
1152 1154 def getsidedataremover(srcrepo, destrepo):
1153 1155 def sidedatacompanion(revlog, rev):
1154 1156 f = ()
1155 1157 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
1156 1158 if revlog.flags(rev) & REVIDX_SIDEDATA:
1157 1159 f = (
1158 1160 sidedatamod.SD_P1COPIES,
1159 1161 sidedatamod.SD_P2COPIES,
1160 1162 sidedatamod.SD_FILESADDED,
1161 1163 sidedatamod.SD_FILESREMOVED,
1162 1164 )
1163 1165 return False, f, {}
1164 1166
1165 1167 return sidedatacompanion
@@ -1,2711 +1,2721 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 errno
11 11 import shutil
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 bin,
19 19 hex,
20 20 modifiednodeid,
21 21 nullhex,
22 22 nullid,
23 23 nullrev,
24 24 )
25 25 from .pycompat import delattr
26 26 from .thirdparty import attr
27 27 from . import (
28 28 copies,
29 29 encoding,
30 30 error,
31 31 filemerge,
32 32 match as matchmod,
33 33 obsutil,
34 34 pathutil,
35 35 pycompat,
36 36 scmutil,
37 37 subrepoutil,
38 38 util,
39 39 worker,
40 40 )
41 41 from .utils import hashutil
42 42
43 43 _pack = struct.pack
44 44 _unpack = struct.unpack
45 45
46 46
47 47 def _droponode(data):
48 48 # used for compatibility for v1
49 49 bits = data.split(b'\0')
50 50 bits = bits[:-2] + bits[-1:]
51 51 return b'\0'.join(bits)
52 52
53 53
54 54 # Merge state record types. See ``mergestate`` docs for more.
55 55 RECORD_LOCAL = b'L'
56 56 RECORD_OTHER = b'O'
57 57 RECORD_MERGED = b'F'
58 58 RECORD_CHANGEDELETE_CONFLICT = b'C'
59 59 RECORD_MERGE_DRIVER_MERGE = b'D'
60 60 RECORD_PATH_CONFLICT = b'P'
61 61 RECORD_MERGE_DRIVER_STATE = b'm'
62 62 RECORD_FILE_VALUES = b'f'
63 63 RECORD_LABELS = b'l'
64 64 RECORD_OVERRIDE = b't'
65 65 RECORD_UNSUPPORTED_MANDATORY = b'X'
66 66 RECORD_UNSUPPORTED_ADVISORY = b'x'
67 67
68 68 MERGE_DRIVER_STATE_UNMARKED = b'u'
69 69 MERGE_DRIVER_STATE_MARKED = b'm'
70 70 MERGE_DRIVER_STATE_SUCCESS = b's'
71 71
72 72 MERGE_RECORD_UNRESOLVED = b'u'
73 73 MERGE_RECORD_RESOLVED = b'r'
74 74 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
75 75 MERGE_RECORD_RESOLVED_PATH = b'pr'
76 76 MERGE_RECORD_DRIVER_RESOLVED = b'd'
77 77
78 78 ACTION_FORGET = b'f'
79 79 ACTION_REMOVE = b'r'
80 80 ACTION_ADD = b'a'
81 81 ACTION_GET = b'g'
82 82 ACTION_PATH_CONFLICT = b'p'
83 83 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
84 84 ACTION_ADD_MODIFIED = b'am'
85 85 ACTION_CREATED = b'c'
86 86 ACTION_DELETED_CHANGED = b'dc'
87 87 ACTION_CHANGED_DELETED = b'cd'
88 88 ACTION_MERGE = b'm'
89 89 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
90 90 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
91 91 ACTION_KEEP = b'k'
92 92 ACTION_EXEC = b'e'
93 93 ACTION_CREATED_MERGE = b'cm'
94 94
95 95
96 96 class mergestate(object):
97 97 '''track 3-way merge state of individual files
98 98
99 99 The merge state is stored on disk when needed. Two files are used: one with
100 100 an old format (version 1), and one with a new format (version 2). Version 2
101 101 stores a superset of the data in version 1, including new kinds of records
102 102 in the future. For more about the new format, see the documentation for
103 103 `_readrecordsv2`.
104 104
105 105 Each record can contain arbitrary content, and has an associated type. This
106 106 `type` should be a letter. If `type` is uppercase, the record is mandatory:
107 107 versions of Mercurial that don't support it should abort. If `type` is
108 108 lowercase, the record can be safely ignored.
109 109
110 110 Currently known records:
111 111
112 112 L: the node of the "local" part of the merge (hexified version)
113 113 O: the node of the "other" part of the merge (hexified version)
114 114 F: a file to be merged entry
115 115 C: a change/delete or delete/change conflict
116 116 D: a file that the external merge driver will merge internally
117 117 (experimental)
118 118 P: a path conflict (file vs directory)
119 119 m: the external merge driver defined for this merge plus its run state
120 120 (experimental)
121 121 f: a (filename, dictionary) tuple of optional values for a given file
122 122 X: unsupported mandatory record type (used in tests)
123 123 x: unsupported advisory record type (used in tests)
124 124 l: the labels for the parts of the merge.
125 125
126 126 Merge driver run states (experimental):
127 127 u: driver-resolved files unmarked -- needs to be run next time we're about
128 128 to resolve or commit
129 129 m: driver-resolved files marked -- only needs to be run before commit
130 130 s: success/skipped -- does not need to be run any more
131 131
132 132 Merge record states (stored in self._state, indexed by filename):
133 133 u: unresolved conflict
134 134 r: resolved conflict
135 135 pu: unresolved path conflict (file conflicts with directory)
136 136 pr: resolved path conflict
137 137 d: driver-resolved conflict
138 138
139 139 The resolve command transitions between 'u' and 'r' for conflicts and
140 140 'pu' and 'pr' for path conflicts.
141 141 '''
142 142
143 143 statepathv1 = b'merge/state'
144 144 statepathv2 = b'merge/state2'
145 145
146 146 @staticmethod
147 147 def clean(repo, node=None, other=None, labels=None):
148 148 """Initialize a brand new merge state, removing any existing state on
149 149 disk."""
150 150 ms = mergestate(repo)
151 151 ms.reset(node, other, labels)
152 152 return ms
153 153
154 154 @staticmethod
155 155 def read(repo):
156 156 """Initialize the merge state, reading it from disk."""
157 157 ms = mergestate(repo)
158 158 ms._read()
159 159 return ms
160 160
161 161 def __init__(self, repo):
162 162 """Initialize the merge state.
163 163
164 164 Do not use this directly! Instead call read() or clean()."""
165 165 self._repo = repo
166 166 self._dirty = False
167 167 self._labels = None
168 168
169 169 def reset(self, node=None, other=None, labels=None):
170 170 self._state = {}
171 171 self._stateextras = {}
172 172 self._local = None
173 173 self._other = None
174 174 self._labels = labels
175 175 for var in ('localctx', 'otherctx'):
176 176 if var in vars(self):
177 177 delattr(self, var)
178 178 if node:
179 179 self._local = node
180 180 self._other = other
181 181 self._readmergedriver = None
182 182 if self.mergedriver:
183 183 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
184 184 else:
185 185 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
186 186 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
187 187 self._results = {}
188 188 self._dirty = False
189 189
190 190 def _read(self):
191 191 """Analyse each record content to restore a serialized state from disk
192 192
193 193 This function process "record" entry produced by the de-serialization
194 194 of on disk file.
195 195 """
196 196 self._state = {}
197 197 self._stateextras = {}
198 198 self._local = None
199 199 self._other = None
200 200 for var in ('localctx', 'otherctx'):
201 201 if var in vars(self):
202 202 delattr(self, var)
203 203 self._readmergedriver = None
204 204 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
205 205 unsupported = set()
206 206 records = self._readrecords()
207 207 for rtype, record in records:
208 208 if rtype == RECORD_LOCAL:
209 209 self._local = bin(record)
210 210 elif rtype == RECORD_OTHER:
211 211 self._other = bin(record)
212 212 elif rtype == RECORD_MERGE_DRIVER_STATE:
213 213 bits = record.split(b'\0', 1)
214 214 mdstate = bits[1]
215 215 if len(mdstate) != 1 or mdstate not in (
216 216 MERGE_DRIVER_STATE_UNMARKED,
217 217 MERGE_DRIVER_STATE_MARKED,
218 218 MERGE_DRIVER_STATE_SUCCESS,
219 219 ):
220 220 # the merge driver should be idempotent, so just rerun it
221 221 mdstate = MERGE_DRIVER_STATE_UNMARKED
222 222
223 223 self._readmergedriver = bits[0]
224 224 self._mdstate = mdstate
225 225 elif rtype in (
226 226 RECORD_MERGED,
227 227 RECORD_CHANGEDELETE_CONFLICT,
228 228 RECORD_PATH_CONFLICT,
229 229 RECORD_MERGE_DRIVER_MERGE,
230 230 ):
231 231 bits = record.split(b'\0')
232 232 self._state[bits[0]] = bits[1:]
233 233 elif rtype == RECORD_FILE_VALUES:
234 234 filename, rawextras = record.split(b'\0', 1)
235 235 extraparts = rawextras.split(b'\0')
236 236 extras = {}
237 237 i = 0
238 238 while i < len(extraparts):
239 239 extras[extraparts[i]] = extraparts[i + 1]
240 240 i += 2
241 241
242 242 self._stateextras[filename] = extras
243 243 elif rtype == RECORD_LABELS:
244 244 labels = record.split(b'\0', 2)
245 245 self._labels = [l for l in labels if len(l) > 0]
246 246 elif not rtype.islower():
247 247 unsupported.add(rtype)
248 248 self._results = {}
249 249 self._dirty = False
250 250
251 251 if unsupported:
252 252 raise error.UnsupportedMergeRecords(unsupported)
253 253
254 254 def _readrecords(self):
255 255 """Read merge state from disk and return a list of record (TYPE, data)
256 256
257 257 We read data from both v1 and v2 files and decide which one to use.
258 258
259 259 V1 has been used by version prior to 2.9.1 and contains less data than
260 260 v2. We read both versions and check if no data in v2 contradicts
261 261 v1. If there is not contradiction we can safely assume that both v1
262 262 and v2 were written at the same time and use the extract data in v2. If
263 263 there is contradiction we ignore v2 content as we assume an old version
264 264 of Mercurial has overwritten the mergestate file and left an old v2
265 265 file around.
266 266
267 267 returns list of record [(TYPE, data), ...]"""
268 268 v1records = self._readrecordsv1()
269 269 v2records = self._readrecordsv2()
270 270 if self._v1v2match(v1records, v2records):
271 271 return v2records
272 272 else:
273 273 # v1 file is newer than v2 file, use it
274 274 # we have to infer the "other" changeset of the merge
275 275 # we cannot do better than that with v1 of the format
276 276 mctx = self._repo[None].parents()[-1]
277 277 v1records.append((RECORD_OTHER, mctx.hex()))
278 278 # add place holder "other" file node information
279 279 # nobody is using it yet so we do no need to fetch the data
280 280 # if mctx was wrong `mctx[bits[-2]]` may fails.
281 281 for idx, r in enumerate(v1records):
282 282 if r[0] == RECORD_MERGED:
283 283 bits = r[1].split(b'\0')
284 284 bits.insert(-2, b'')
285 285 v1records[idx] = (r[0], b'\0'.join(bits))
286 286 return v1records
287 287
288 288 def _v1v2match(self, v1records, v2records):
289 289 oldv2 = set() # old format version of v2 record
290 290 for rec in v2records:
291 291 if rec[0] == RECORD_LOCAL:
292 292 oldv2.add(rec)
293 293 elif rec[0] == RECORD_MERGED:
294 294 # drop the onode data (not contained in v1)
295 295 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
296 296 for rec in v1records:
297 297 if rec not in oldv2:
298 298 return False
299 299 else:
300 300 return True
301 301
302 302 def _readrecordsv1(self):
303 303 """read on disk merge state for version 1 file
304 304
305 305 returns list of record [(TYPE, data), ...]
306 306
307 307 Note: the "F" data from this file are one entry short
308 308 (no "other file node" entry)
309 309 """
310 310 records = []
311 311 try:
312 312 f = self._repo.vfs(self.statepathv1)
313 313 for i, l in enumerate(f):
314 314 if i == 0:
315 315 records.append((RECORD_LOCAL, l[:-1]))
316 316 else:
317 317 records.append((RECORD_MERGED, l[:-1]))
318 318 f.close()
319 319 except IOError as err:
320 320 if err.errno != errno.ENOENT:
321 321 raise
322 322 return records
323 323
324 324 def _readrecordsv2(self):
325 325 """read on disk merge state for version 2 file
326 326
327 327 This format is a list of arbitrary records of the form:
328 328
329 329 [type][length][content]
330 330
331 331 `type` is a single character, `length` is a 4 byte integer, and
332 332 `content` is an arbitrary byte sequence of length `length`.
333 333
334 334 Mercurial versions prior to 3.7 have a bug where if there are
335 335 unsupported mandatory merge records, attempting to clear out the merge
336 336 state with hg update --clean or similar aborts. The 't' record type
337 337 works around that by writing out what those versions treat as an
338 338 advisory record, but later versions interpret as special: the first
339 339 character is the 'real' record type and everything onwards is the data.
340 340
341 341 Returns list of records [(TYPE, data), ...]."""
342 342 records = []
343 343 try:
344 344 f = self._repo.vfs(self.statepathv2)
345 345 data = f.read()
346 346 off = 0
347 347 end = len(data)
348 348 while off < end:
349 349 rtype = data[off : off + 1]
350 350 off += 1
351 351 length = _unpack(b'>I', data[off : (off + 4)])[0]
352 352 off += 4
353 353 record = data[off : (off + length)]
354 354 off += length
355 355 if rtype == RECORD_OVERRIDE:
356 356 rtype, record = record[0:1], record[1:]
357 357 records.append((rtype, record))
358 358 f.close()
359 359 except IOError as err:
360 360 if err.errno != errno.ENOENT:
361 361 raise
362 362 return records
363 363
364 364 @util.propertycache
365 365 def mergedriver(self):
366 366 # protect against the following:
367 367 # - A configures a malicious merge driver in their hgrc, then
368 368 # pauses the merge
369 369 # - A edits their hgrc to remove references to the merge driver
370 370 # - A gives a copy of their entire repo, including .hg, to B
371 371 # - B inspects .hgrc and finds it to be clean
372 372 # - B then continues the merge and the malicious merge driver
373 373 # gets invoked
374 374 configmergedriver = self._repo.ui.config(
375 375 b'experimental', b'mergedriver'
376 376 )
377 377 if (
378 378 self._readmergedriver is not None
379 379 and self._readmergedriver != configmergedriver
380 380 ):
381 381 raise error.ConfigError(
382 382 _(b"merge driver changed since merge started"),
383 383 hint=_(b"revert merge driver change or abort merge"),
384 384 )
385 385
386 386 return configmergedriver
387 387
388 388 @util.propertycache
389 389 def localctx(self):
390 390 if self._local is None:
391 391 msg = b"localctx accessed but self._local isn't set"
392 392 raise error.ProgrammingError(msg)
393 393 return self._repo[self._local]
394 394
395 395 @util.propertycache
396 396 def otherctx(self):
397 397 if self._other is None:
398 398 msg = b"otherctx accessed but self._other isn't set"
399 399 raise error.ProgrammingError(msg)
400 400 return self._repo[self._other]
401 401
402 402 def active(self):
403 403 """Whether mergestate is active.
404 404
405 405 Returns True if there appears to be mergestate. This is a rough proxy
406 406 for "is a merge in progress."
407 407 """
408 408 # Check local variables before looking at filesystem for performance
409 409 # reasons.
410 410 return (
411 411 bool(self._local)
412 412 or bool(self._state)
413 413 or self._repo.vfs.exists(self.statepathv1)
414 414 or self._repo.vfs.exists(self.statepathv2)
415 415 )
416 416
417 417 def commit(self):
418 418 """Write current state on disk (if necessary)"""
419 419 if self._dirty:
420 420 records = self._makerecords()
421 421 self._writerecords(records)
422 422 self._dirty = False
423 423
424 424 def _makerecords(self):
425 425 records = []
426 426 records.append((RECORD_LOCAL, hex(self._local)))
427 427 records.append((RECORD_OTHER, hex(self._other)))
428 428 if self.mergedriver:
429 429 records.append(
430 430 (
431 431 RECORD_MERGE_DRIVER_STATE,
432 432 b'\0'.join([self.mergedriver, self._mdstate]),
433 433 )
434 434 )
435 435 # Write out state items. In all cases, the value of the state map entry
436 436 # is written as the contents of the record. The record type depends on
437 437 # the type of state that is stored, and capital-letter records are used
438 438 # to prevent older versions of Mercurial that do not support the feature
439 439 # from loading them.
440 440 for filename, v in pycompat.iteritems(self._state):
441 441 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
442 442 # Driver-resolved merge. These are stored in 'D' records.
443 443 records.append(
444 444 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
445 445 )
446 446 elif v[0] in (
447 447 MERGE_RECORD_UNRESOLVED_PATH,
448 448 MERGE_RECORD_RESOLVED_PATH,
449 449 ):
450 450 # Path conflicts. These are stored in 'P' records. The current
451 451 # resolution state ('pu' or 'pr') is stored within the record.
452 452 records.append(
453 453 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
454 454 )
455 455 elif v[1] == nullhex or v[6] == nullhex:
456 456 # Change/Delete or Delete/Change conflicts. These are stored in
457 457 # 'C' records. v[1] is the local file, and is nullhex when the
458 458 # file is deleted locally ('dc'). v[6] is the remote file, and
459 459 # is nullhex when the file is deleted remotely ('cd').
460 460 records.append(
461 461 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
462 462 )
463 463 else:
464 464 # Normal files. These are stored in 'F' records.
465 465 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
466 466 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
467 467 rawextras = b'\0'.join(
468 468 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
469 469 )
470 470 records.append(
471 471 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
472 472 )
473 473 if self._labels is not None:
474 474 labels = b'\0'.join(self._labels)
475 475 records.append((RECORD_LABELS, labels))
476 476 return records
477 477
478 478 def _writerecords(self, records):
479 479 """Write current state on disk (both v1 and v2)"""
480 480 self._writerecordsv1(records)
481 481 self._writerecordsv2(records)
482 482
483 483 def _writerecordsv1(self, records):
484 484 """Write current state on disk in a version 1 file"""
485 485 f = self._repo.vfs(self.statepathv1, b'wb')
486 486 irecords = iter(records)
487 487 lrecords = next(irecords)
488 488 assert lrecords[0] == RECORD_LOCAL
489 489 f.write(hex(self._local) + b'\n')
490 490 for rtype, data in irecords:
491 491 if rtype == RECORD_MERGED:
492 492 f.write(b'%s\n' % _droponode(data))
493 493 f.close()
494 494
495 495 def _writerecordsv2(self, records):
496 496 """Write current state on disk in a version 2 file
497 497
498 498 See the docstring for _readrecordsv2 for why we use 't'."""
499 499 # these are the records that all version 2 clients can read
500 500 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
501 501 f = self._repo.vfs(self.statepathv2, b'wb')
502 502 for key, data in records:
503 503 assert len(key) == 1
504 504 if key not in allowlist:
505 505 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
506 506 format = b'>sI%is' % len(data)
507 507 f.write(_pack(format, key, len(data), data))
508 508 f.close()
509 509
510 510 @staticmethod
511 511 def getlocalkey(path):
512 512 """hash the path of a local file context for storage in the .hg/merge
513 513 directory."""
514 514
515 515 return hex(hashutil.sha1(path).digest())
516 516
517 517 def add(self, fcl, fco, fca, fd):
518 518 """add a new (potentially?) conflicting file the merge state
519 519 fcl: file context for local,
520 520 fco: file context for remote,
521 521 fca: file context for ancestors,
522 522 fd: file path of the resulting merge.
523 523
524 524 note: also write the local version to the `.hg/merge` directory.
525 525 """
526 526 if fcl.isabsent():
527 527 localkey = nullhex
528 528 else:
529 529 localkey = mergestate.getlocalkey(fcl.path())
530 530 self._repo.vfs.write(b'merge/' + localkey, fcl.data())
531 531 self._state[fd] = [
532 532 MERGE_RECORD_UNRESOLVED,
533 533 localkey,
534 534 fcl.path(),
535 535 fca.path(),
536 536 hex(fca.filenode()),
537 537 fco.path(),
538 538 hex(fco.filenode()),
539 539 fcl.flags(),
540 540 ]
541 541 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
542 542 self._dirty = True
543 543
544 544 def addpath(self, path, frename, forigin):
545 545 """add a new conflicting path to the merge state
546 546 path: the path that conflicts
547 547 frename: the filename the conflicting file was renamed to
548 548 forigin: origin of the file ('l' or 'r' for local/remote)
549 549 """
550 550 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
551 551 self._dirty = True
552 552
553 553 def __contains__(self, dfile):
554 554 return dfile in self._state
555 555
556 556 def __getitem__(self, dfile):
557 557 return self._state[dfile][0]
558 558
559 559 def __iter__(self):
560 560 return iter(sorted(self._state))
561 561
562 562 def files(self):
563 563 return self._state.keys()
564 564
565 565 def mark(self, dfile, state):
566 566 self._state[dfile][0] = state
567 567 self._dirty = True
568 568
569 569 def mdstate(self):
570 570 return self._mdstate
571 571
572 572 def unresolved(self):
573 573 """Obtain the paths of unresolved files."""
574 574
575 575 for f, entry in pycompat.iteritems(self._state):
576 576 if entry[0] in (
577 577 MERGE_RECORD_UNRESOLVED,
578 578 MERGE_RECORD_UNRESOLVED_PATH,
579 579 ):
580 580 yield f
581 581
582 582 def driverresolved(self):
583 583 """Obtain the paths of driver-resolved files."""
584 584
585 585 for f, entry in self._state.items():
586 586 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
587 587 yield f
588 588
589 589 def extras(self, filename):
590 590 return self._stateextras.setdefault(filename, {})
591 591
592 592 def _resolve(self, preresolve, dfile, wctx):
593 593 """rerun merge process for file path `dfile`"""
594 594 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
595 595 return True, 0
596 596 stateentry = self._state[dfile]
597 597 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
598 598 octx = self._repo[self._other]
599 599 extras = self.extras(dfile)
600 600 anccommitnode = extras.get(b'ancestorlinknode')
601 601 if anccommitnode:
602 602 actx = self._repo[anccommitnode]
603 603 else:
604 604 actx = None
605 605 fcd = self._filectxorabsent(localkey, wctx, dfile)
606 606 fco = self._filectxorabsent(onode, octx, ofile)
607 607 # TODO: move this to filectxorabsent
608 608 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
609 609 # "premerge" x flags
610 610 flo = fco.flags()
611 611 fla = fca.flags()
612 612 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
613 613 if fca.node() == nullid and flags != flo:
614 614 if preresolve:
615 615 self._repo.ui.warn(
616 616 _(
617 617 b'warning: cannot merge flags for %s '
618 618 b'without common ancestor - keeping local flags\n'
619 619 )
620 620 % afile
621 621 )
622 622 elif flags == fla:
623 623 flags = flo
624 624 if preresolve:
625 625 # restore local
626 626 if localkey != nullhex:
627 627 f = self._repo.vfs(b'merge/' + localkey)
628 628 wctx[dfile].write(f.read(), flags)
629 629 f.close()
630 630 else:
631 631 wctx[dfile].remove(ignoremissing=True)
632 632 complete, r, deleted = filemerge.premerge(
633 633 self._repo,
634 634 wctx,
635 635 self._local,
636 636 lfile,
637 637 fcd,
638 638 fco,
639 639 fca,
640 640 labels=self._labels,
641 641 )
642 642 else:
643 643 complete, r, deleted = filemerge.filemerge(
644 644 self._repo,
645 645 wctx,
646 646 self._local,
647 647 lfile,
648 648 fcd,
649 649 fco,
650 650 fca,
651 651 labels=self._labels,
652 652 )
653 653 if r is None:
654 654 # no real conflict
655 655 del self._state[dfile]
656 656 self._stateextras.pop(dfile, None)
657 657 self._dirty = True
658 658 elif not r:
659 659 self.mark(dfile, MERGE_RECORD_RESOLVED)
660 660
661 661 if complete:
662 662 action = None
663 663 if deleted:
664 664 if fcd.isabsent():
665 665 # dc: local picked. Need to drop if present, which may
666 666 # happen on re-resolves.
667 667 action = ACTION_FORGET
668 668 else:
669 669 # cd: remote picked (or otherwise deleted)
670 670 action = ACTION_REMOVE
671 671 else:
672 672 if fcd.isabsent(): # dc: remote picked
673 673 action = ACTION_GET
674 674 elif fco.isabsent(): # cd: local picked
675 675 if dfile in self.localctx:
676 676 action = ACTION_ADD_MODIFIED
677 677 else:
678 678 action = ACTION_ADD
679 679 # else: regular merges (no action necessary)
680 680 self._results[dfile] = r, action
681 681
682 682 return complete, r
683 683
684 684 def _filectxorabsent(self, hexnode, ctx, f):
685 685 if hexnode == nullhex:
686 686 return filemerge.absentfilectx(ctx, f)
687 687 else:
688 688 return ctx[f]
689 689
690 690 def preresolve(self, dfile, wctx):
691 691 """run premerge process for dfile
692 692
693 693 Returns whether the merge is complete, and the exit code."""
694 694 return self._resolve(True, dfile, wctx)
695 695
696 696 def resolve(self, dfile, wctx):
697 697 """run merge process (assuming premerge was run) for dfile
698 698
699 699 Returns the exit code of the merge."""
700 700 return self._resolve(False, dfile, wctx)[1]
701 701
702 702 def counts(self):
703 703 """return counts for updated, merged and removed files in this
704 704 session"""
705 705 updated, merged, removed = 0, 0, 0
706 706 for r, action in pycompat.itervalues(self._results):
707 707 if r is None:
708 708 updated += 1
709 709 elif r == 0:
710 710 if action == ACTION_REMOVE:
711 711 removed += 1
712 712 else:
713 713 merged += 1
714 714 return updated, merged, removed
715 715
716 716 def unresolvedcount(self):
717 717 """get unresolved count for this merge (persistent)"""
718 718 return len(list(self.unresolved()))
719 719
720 720 def actions(self):
721 721 """return lists of actions to perform on the dirstate"""
722 722 actions = {
723 723 ACTION_REMOVE: [],
724 724 ACTION_FORGET: [],
725 725 ACTION_ADD: [],
726 726 ACTION_ADD_MODIFIED: [],
727 727 ACTION_GET: [],
728 728 }
729 729 for f, (r, action) in pycompat.iteritems(self._results):
730 730 if action is not None:
731 731 actions[action].append((f, None, b"merge result"))
732 732 return actions
733 733
734 734 def recordactions(self):
735 735 """record remove/add/get actions in the dirstate"""
736 736 branchmerge = self._repo.dirstate.p2() != nullid
737 737 recordupdates(self._repo, self.actions(), branchmerge, None)
738 738
739 739 def queueremove(self, f):
740 740 """queues a file to be removed from the dirstate
741 741
742 742 Meant for use by custom merge drivers."""
743 743 self._results[f] = 0, ACTION_REMOVE
744 744
745 745 def queueadd(self, f):
746 746 """queues a file to be added to the dirstate
747 747
748 748 Meant for use by custom merge drivers."""
749 749 self._results[f] = 0, ACTION_ADD
750 750
751 751 def queueget(self, f):
752 752 """queues a file to be marked modified in the dirstate
753 753
754 754 Meant for use by custom merge drivers."""
755 755 self._results[f] = 0, ACTION_GET
756 756
757 757
758 758 def _getcheckunknownconfig(repo, section, name):
759 759 config = repo.ui.config(section, name)
760 760 valid = [b'abort', b'ignore', b'warn']
761 761 if config not in valid:
762 762 validstr = b', '.join([b"'" + v + b"'" for v in valid])
763 763 raise error.ConfigError(
764 764 _(b"%s.%s not valid ('%s' is none of %s)")
765 765 % (section, name, config, validstr)
766 766 )
767 767 return config
768 768
769 769
770 770 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
771 771 if wctx.isinmemory():
772 772 # Nothing to do in IMM because nothing in the "working copy" can be an
773 773 # unknown file.
774 774 #
775 775 # Note that we should bail out here, not in ``_checkunknownfiles()``,
776 776 # because that function does other useful work.
777 777 return False
778 778
779 779 if f2 is None:
780 780 f2 = f
781 781 return (
782 782 repo.wvfs.audit.check(f)
783 783 and repo.wvfs.isfileorlink(f)
784 784 and repo.dirstate.normalize(f) not in repo.dirstate
785 785 and mctx[f2].cmp(wctx[f])
786 786 )
787 787
788 788
789 789 class _unknowndirschecker(object):
790 790 """
791 791 Look for any unknown files or directories that may have a path conflict
792 792 with a file. If any path prefix of the file exists as a file or link,
793 793 then it conflicts. If the file itself is a directory that contains any
794 794 file that is not tracked, then it conflicts.
795 795
796 796 Returns the shortest path at which a conflict occurs, or None if there is
797 797 no conflict.
798 798 """
799 799
800 800 def __init__(self):
801 801 # A set of paths known to be good. This prevents repeated checking of
802 802 # dirs. It will be updated with any new dirs that are checked and found
803 803 # to be safe.
804 804 self._unknowndircache = set()
805 805
806 806 # A set of paths that are known to be absent. This prevents repeated
807 807 # checking of subdirectories that are known not to exist. It will be
808 808 # updated with any new dirs that are checked and found to be absent.
809 809 self._missingdircache = set()
810 810
811 811 def __call__(self, repo, wctx, f):
812 812 if wctx.isinmemory():
813 813 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
814 814 return False
815 815
816 816 # Check for path prefixes that exist as unknown files.
817 817 for p in reversed(list(pathutil.finddirs(f))):
818 818 if p in self._missingdircache:
819 819 return
820 820 if p in self._unknowndircache:
821 821 continue
822 822 if repo.wvfs.audit.check(p):
823 823 if (
824 824 repo.wvfs.isfileorlink(p)
825 825 and repo.dirstate.normalize(p) not in repo.dirstate
826 826 ):
827 827 return p
828 828 if not repo.wvfs.lexists(p):
829 829 self._missingdircache.add(p)
830 830 return
831 831 self._unknowndircache.add(p)
832 832
833 833 # Check if the file conflicts with a directory containing unknown files.
834 834 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
835 835 # Does the directory contain any files that are not in the dirstate?
836 836 for p, dirs, files in repo.wvfs.walk(f):
837 837 for fn in files:
838 838 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
839 839 relf = repo.dirstate.normalize(relf, isknown=True)
840 840 if relf not in repo.dirstate:
841 841 return f
842 842 return None
843 843
844 844
845 845 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
846 846 """
847 847 Considers any actions that care about the presence of conflicting unknown
848 848 files. For some actions, the result is to abort; for others, it is to
849 849 choose a different action.
850 850 """
851 851 fileconflicts = set()
852 852 pathconflicts = set()
853 853 warnconflicts = set()
854 854 abortconflicts = set()
855 855 unknownconfig = _getcheckunknownconfig(repo, b'merge', b'checkunknown')
856 856 ignoredconfig = _getcheckunknownconfig(repo, b'merge', b'checkignored')
857 857 pathconfig = repo.ui.configbool(
858 858 b'experimental', b'merge.checkpathconflicts'
859 859 )
860 860 if not force:
861 861
862 862 def collectconflicts(conflicts, config):
863 863 if config == b'abort':
864 864 abortconflicts.update(conflicts)
865 865 elif config == b'warn':
866 866 warnconflicts.update(conflicts)
867 867
868 868 checkunknowndirs = _unknowndirschecker()
869 869 for f, (m, args, msg) in pycompat.iteritems(actions):
870 870 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
871 871 if _checkunknownfile(repo, wctx, mctx, f):
872 872 fileconflicts.add(f)
873 873 elif pathconfig and f not in wctx:
874 874 path = checkunknowndirs(repo, wctx, f)
875 875 if path is not None:
876 876 pathconflicts.add(path)
877 877 elif m == ACTION_LOCAL_DIR_RENAME_GET:
878 878 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
879 879 fileconflicts.add(f)
880 880
881 881 allconflicts = fileconflicts | pathconflicts
882 882 ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
883 883 unknownconflicts = allconflicts - ignoredconflicts
884 884 collectconflicts(ignoredconflicts, ignoredconfig)
885 885 collectconflicts(unknownconflicts, unknownconfig)
886 886 else:
887 887 for f, (m, args, msg) in pycompat.iteritems(actions):
888 888 if m == ACTION_CREATED_MERGE:
889 889 fl2, anc = args
890 890 different = _checkunknownfile(repo, wctx, mctx, f)
891 891 if repo.dirstate._ignore(f):
892 892 config = ignoredconfig
893 893 else:
894 894 config = unknownconfig
895 895
896 896 # The behavior when force is True is described by this table:
897 897 # config different mergeforce | action backup
898 898 # * n * | get n
899 899 # * y y | merge -
900 900 # abort y n | merge - (1)
901 901 # warn y n | warn + get y
902 902 # ignore y n | get y
903 903 #
904 904 # (1) this is probably the wrong behavior here -- we should
905 905 # probably abort, but some actions like rebases currently
906 906 # don't like an abort happening in the middle of
907 907 # merge.update.
908 908 if not different:
909 909 actions[f] = (ACTION_GET, (fl2, False), b'remote created')
910 910 elif mergeforce or config == b'abort':
911 911 actions[f] = (
912 912 ACTION_MERGE,
913 913 (f, f, None, False, anc),
914 914 b'remote differs from untracked local',
915 915 )
916 916 elif config == b'abort':
917 917 abortconflicts.add(f)
918 918 else:
919 919 if config == b'warn':
920 920 warnconflicts.add(f)
921 921 actions[f] = (ACTION_GET, (fl2, True), b'remote created')
922 922
923 923 for f in sorted(abortconflicts):
924 924 warn = repo.ui.warn
925 925 if f in pathconflicts:
926 926 if repo.wvfs.isfileorlink(f):
927 927 warn(_(b"%s: untracked file conflicts with directory\n") % f)
928 928 else:
929 929 warn(_(b"%s: untracked directory conflicts with file\n") % f)
930 930 else:
931 931 warn(_(b"%s: untracked file differs\n") % f)
932 932 if abortconflicts:
933 933 raise error.Abort(
934 934 _(
935 935 b"untracked files in working directory "
936 936 b"differ from files in requested revision"
937 937 )
938 938 )
939 939
940 940 for f in sorted(warnconflicts):
941 941 if repo.wvfs.isfileorlink(f):
942 942 repo.ui.warn(_(b"%s: replacing untracked file\n") % f)
943 943 else:
944 944 repo.ui.warn(_(b"%s: replacing untracked files in directory\n") % f)
945 945
946 946 for f, (m, args, msg) in pycompat.iteritems(actions):
947 947 if m == ACTION_CREATED:
948 948 backup = (
949 949 f in fileconflicts
950 950 or f in pathconflicts
951 951 or any(p in pathconflicts for p in pathutil.finddirs(f))
952 952 )
953 953 (flags,) = args
954 954 actions[f] = (ACTION_GET, (flags, backup), msg)
955 955
956 956
957 957 def _forgetremoved(wctx, mctx, branchmerge):
958 958 """
959 959 Forget removed files
960 960
961 961 If we're jumping between revisions (as opposed to merging), and if
962 962 neither the working directory nor the target rev has the file,
963 963 then we need to remove it from the dirstate, to prevent the
964 964 dirstate from listing the file when it is no longer in the
965 965 manifest.
966 966
967 967 If we're merging, and the other revision has removed a file
968 968 that is not present in the working directory, we need to mark it
969 969 as removed.
970 970 """
971 971
972 972 actions = {}
973 973 m = ACTION_FORGET
974 974 if branchmerge:
975 975 m = ACTION_REMOVE
976 976 for f in wctx.deleted():
977 977 if f not in mctx:
978 978 actions[f] = m, None, b"forget deleted"
979 979
980 980 if not branchmerge:
981 981 for f in wctx.removed():
982 982 if f not in mctx:
983 983 actions[f] = ACTION_FORGET, None, b"forget removed"
984 984
985 985 return actions
986 986
987 987
988 988 def _checkcollision(repo, wmf, actions):
989 989 """
990 990 Check for case-folding collisions.
991 991 """
992 992
993 993 # If the repo is narrowed, filter out files outside the narrowspec.
994 994 narrowmatch = repo.narrowmatch()
995 995 if not narrowmatch.always():
996 996 wmf = wmf.matches(narrowmatch)
997 997 if actions:
998 998 narrowactions = {}
999 999 for m, actionsfortype in pycompat.iteritems(actions):
1000 1000 narrowactions[m] = []
1001 1001 for (f, args, msg) in actionsfortype:
1002 1002 if narrowmatch(f):
1003 1003 narrowactions[m].append((f, args, msg))
1004 1004 actions = narrowactions
1005 1005
1006 1006 # build provisional merged manifest up
1007 1007 pmmf = set(wmf)
1008 1008
1009 1009 if actions:
1010 1010 # KEEP and EXEC are no-op
1011 1011 for m in (
1012 1012 ACTION_ADD,
1013 1013 ACTION_ADD_MODIFIED,
1014 1014 ACTION_FORGET,
1015 1015 ACTION_GET,
1016 1016 ACTION_CHANGED_DELETED,
1017 1017 ACTION_DELETED_CHANGED,
1018 1018 ):
1019 1019 for f, args, msg in actions[m]:
1020 1020 pmmf.add(f)
1021 1021 for f, args, msg in actions[ACTION_REMOVE]:
1022 1022 pmmf.discard(f)
1023 1023 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1024 1024 f2, flags = args
1025 1025 pmmf.discard(f2)
1026 1026 pmmf.add(f)
1027 1027 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1028 1028 pmmf.add(f)
1029 1029 for f, args, msg in actions[ACTION_MERGE]:
1030 1030 f1, f2, fa, move, anc = args
1031 1031 if move:
1032 1032 pmmf.discard(f1)
1033 1033 pmmf.add(f)
1034 1034
1035 1035 # check case-folding collision in provisional merged manifest
1036 1036 foldmap = {}
1037 1037 for f in pmmf:
1038 1038 fold = util.normcase(f)
1039 1039 if fold in foldmap:
1040 1040 raise error.Abort(
1041 1041 _(b"case-folding collision between %s and %s")
1042 1042 % (f, foldmap[fold])
1043 1043 )
1044 1044 foldmap[fold] = f
1045 1045
1046 1046 # check case-folding of directories
1047 1047 foldprefix = unfoldprefix = lastfull = b''
1048 1048 for fold, f in sorted(foldmap.items()):
1049 1049 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
1050 1050 # the folded prefix matches but actual casing is different
1051 1051 raise error.Abort(
1052 1052 _(b"case-folding collision between %s and directory of %s")
1053 1053 % (lastfull, f)
1054 1054 )
1055 1055 foldprefix = fold + b'/'
1056 1056 unfoldprefix = f + b'/'
1057 1057 lastfull = f
1058 1058
1059 1059
1060 1060 def driverpreprocess(repo, ms, wctx, labels=None):
1061 1061 """run the preprocess step of the merge driver, if any
1062 1062
1063 1063 This is currently not implemented -- it's an extension point."""
1064 1064 return True
1065 1065
1066 1066
1067 1067 def driverconclude(repo, ms, wctx, labels=None):
1068 1068 """run the conclude step of the merge driver, if any
1069 1069
1070 1070 This is currently not implemented -- it's an extension point."""
1071 1071 return True
1072 1072
1073 1073
1074 1074 def _filesindirs(repo, manifest, dirs):
1075 1075 """
1076 1076 Generator that yields pairs of all the files in the manifest that are found
1077 1077 inside the directories listed in dirs, and which directory they are found
1078 1078 in.
1079 1079 """
1080 1080 for f in manifest:
1081 1081 for p in pathutil.finddirs(f):
1082 1082 if p in dirs:
1083 1083 yield f, p
1084 1084 break
1085 1085
1086 1086
1087 1087 def checkpathconflicts(repo, wctx, mctx, actions):
1088 1088 """
1089 1089 Check if any actions introduce path conflicts in the repository, updating
1090 1090 actions to record or handle the path conflict accordingly.
1091 1091 """
1092 1092 mf = wctx.manifest()
1093 1093
1094 1094 # The set of local files that conflict with a remote directory.
1095 1095 localconflicts = set()
1096 1096
1097 1097 # The set of directories that conflict with a remote file, and so may cause
1098 1098 # conflicts if they still contain any files after the merge.
1099 1099 remoteconflicts = set()
1100 1100
1101 1101 # The set of directories that appear as both a file and a directory in the
1102 1102 # remote manifest. These indicate an invalid remote manifest, which
1103 1103 # can't be updated to cleanly.
1104 1104 invalidconflicts = set()
1105 1105
1106 1106 # The set of directories that contain files that are being created.
1107 1107 createdfiledirs = set()
1108 1108
1109 1109 # The set of files deleted by all the actions.
1110 1110 deletedfiles = set()
1111 1111
1112 1112 for f, (m, args, msg) in actions.items():
1113 1113 if m in (
1114 1114 ACTION_CREATED,
1115 1115 ACTION_DELETED_CHANGED,
1116 1116 ACTION_MERGE,
1117 1117 ACTION_CREATED_MERGE,
1118 1118 ):
1119 1119 # This action may create a new local file.
1120 1120 createdfiledirs.update(pathutil.finddirs(f))
1121 1121 if mf.hasdir(f):
1122 1122 # The file aliases a local directory. This might be ok if all
1123 1123 # the files in the local directory are being deleted. This
1124 1124 # will be checked once we know what all the deleted files are.
1125 1125 remoteconflicts.add(f)
1126 1126 # Track the names of all deleted files.
1127 1127 if m == ACTION_REMOVE:
1128 1128 deletedfiles.add(f)
1129 1129 if m == ACTION_MERGE:
1130 1130 f1, f2, fa, move, anc = args
1131 1131 if move:
1132 1132 deletedfiles.add(f1)
1133 1133 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1134 1134 f2, flags = args
1135 1135 deletedfiles.add(f2)
1136 1136
1137 1137 # Check all directories that contain created files for path conflicts.
1138 1138 for p in createdfiledirs:
1139 1139 if p in mf:
1140 1140 if p in mctx:
1141 1141 # A file is in a directory which aliases both a local
1142 1142 # and a remote file. This is an internal inconsistency
1143 1143 # within the remote manifest.
1144 1144 invalidconflicts.add(p)
1145 1145 else:
1146 1146 # A file is in a directory which aliases a local file.
1147 1147 # We will need to rename the local file.
1148 1148 localconflicts.add(p)
1149 1149 if p in actions and actions[p][0] in (
1150 1150 ACTION_CREATED,
1151 1151 ACTION_DELETED_CHANGED,
1152 1152 ACTION_MERGE,
1153 1153 ACTION_CREATED_MERGE,
1154 1154 ):
1155 1155 # The file is in a directory which aliases a remote file.
1156 1156 # This is an internal inconsistency within the remote
1157 1157 # manifest.
1158 1158 invalidconflicts.add(p)
1159 1159
1160 1160 # Rename all local conflicting files that have not been deleted.
1161 1161 for p in localconflicts:
1162 1162 if p not in deletedfiles:
1163 1163 ctxname = bytes(wctx).rstrip(b'+')
1164 1164 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1165 1165 actions[pnew] = (
1166 1166 ACTION_PATH_CONFLICT_RESOLVE,
1167 1167 (p,),
1168 1168 b'local path conflict',
1169 1169 )
1170 1170 actions[p] = (ACTION_PATH_CONFLICT, (pnew, b'l'), b'path conflict')
1171 1171
1172 1172 if remoteconflicts:
1173 1173 # Check if all files in the conflicting directories have been removed.
1174 1174 ctxname = bytes(mctx).rstrip(b'+')
1175 1175 for f, p in _filesindirs(repo, mf, remoteconflicts):
1176 1176 if f not in deletedfiles:
1177 1177 m, args, msg = actions[p]
1178 1178 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1179 1179 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1180 1180 # Action was merge, just update target.
1181 1181 actions[pnew] = (m, args, msg)
1182 1182 else:
1183 1183 # Action was create, change to renamed get action.
1184 1184 fl = args[0]
1185 1185 actions[pnew] = (
1186 1186 ACTION_LOCAL_DIR_RENAME_GET,
1187 1187 (p, fl),
1188 1188 b'remote path conflict',
1189 1189 )
1190 1190 actions[p] = (
1191 1191 ACTION_PATH_CONFLICT,
1192 1192 (pnew, ACTION_REMOVE),
1193 1193 b'path conflict',
1194 1194 )
1195 1195 remoteconflicts.remove(p)
1196 1196 break
1197 1197
1198 1198 if invalidconflicts:
1199 1199 for p in invalidconflicts:
1200 1200 repo.ui.warn(_(b"%s: is both a file and a directory\n") % p)
1201 1201 raise error.Abort(_(b"destination manifest contains path conflicts"))
1202 1202
1203 1203
1204 1204 def _filternarrowactions(narrowmatch, branchmerge, actions):
1205 1205 """
1206 1206 Filters out actions that can ignored because the repo is narrowed.
1207 1207
1208 1208 Raise an exception if the merge cannot be completed because the repo is
1209 1209 narrowed.
1210 1210 """
1211 1211 nooptypes = {b'k'} # TODO: handle with nonconflicttypes
1212 1212 nonconflicttypes = set(b'a am c cm f g r e'.split())
1213 1213 # We mutate the items in the dict during iteration, so iterate
1214 1214 # over a copy.
1215 1215 for f, action in list(actions.items()):
1216 1216 if narrowmatch(f):
1217 1217 pass
1218 1218 elif not branchmerge:
1219 1219 del actions[f] # just updating, ignore changes outside clone
1220 1220 elif action[0] in nooptypes:
1221 1221 del actions[f] # merge does not affect file
1222 1222 elif action[0] in nonconflicttypes:
1223 1223 raise error.Abort(
1224 1224 _(
1225 1225 b'merge affects file \'%s\' outside narrow, '
1226 1226 b'which is not yet supported'
1227 1227 )
1228 1228 % f,
1229 1229 hint=_(b'merging in the other direction may work'),
1230 1230 )
1231 1231 else:
1232 1232 raise error.Abort(
1233 1233 _(b'conflict in file \'%s\' is outside narrow clone') % f
1234 1234 )
1235 1235
1236 1236
1237 1237 def manifestmerge(
1238 1238 repo,
1239 1239 wctx,
1240 1240 p2,
1241 1241 pa,
1242 1242 branchmerge,
1243 1243 force,
1244 1244 matcher,
1245 1245 acceptremote,
1246 1246 followcopies,
1247 1247 forcefulldiff=False,
1248 1248 ):
1249 1249 """
1250 1250 Merge wctx and p2 with ancestor pa and generate merge action list
1251 1251
1252 1252 branchmerge and force are as passed in to update
1253 1253 matcher = matcher to filter file lists
1254 1254 acceptremote = accept the incoming changes without prompting
1255 1255 """
1256 1256 if matcher is not None and matcher.always():
1257 1257 matcher = None
1258 1258
1259 1259 # manifests fetched in order are going to be faster, so prime the caches
1260 1260 [
1261 1261 x.manifest()
1262 1262 for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
1263 1263 ]
1264 1264
1265 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1265 branch_copies1 = copies.branch_copies()
1266 branch_copies2 = copies.branch_copies()
1267 diverge = {}
1266 1268 if followcopies:
1267 branch_copies, diverge = copies.mergecopies(repo, wctx, p2, pa)
1268 copy = branch_copies.copy
1269 renamedelete = branch_copies.renamedelete
1270 dirmove = branch_copies.dirmove
1271 movewithdir = branch_copies.movewithdir
1269 branch_copies1, branch_copies2, diverge = copies.mergecopies(
1270 repo, wctx, p2, pa
1271 )
1272 1272
1273 1273 boolbm = pycompat.bytestr(bool(branchmerge))
1274 1274 boolf = pycompat.bytestr(bool(force))
1275 1275 boolm = pycompat.bytestr(bool(matcher))
1276 1276 repo.ui.note(_(b"resolving manifests\n"))
1277 1277 repo.ui.debug(
1278 1278 b" branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
1279 1279 )
1280 1280 repo.ui.debug(b" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1281 1281
1282 1282 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1283 copied = set(copy.values())
1284 copied.update(movewithdir.values())
1283 copied1 = set(branch_copies1.copy.values())
1284 copied1.update(branch_copies1.movewithdir.values())
1285 copied2 = set(branch_copies2.copy.values())
1286 copied2.update(branch_copies2.movewithdir.values())
1285 1287
1286 1288 if b'.hgsubstate' in m1 and wctx.rev() is None:
1287 1289 # Check whether sub state is modified, and overwrite the manifest
1288 1290 # to flag the change. If wctx is a committed revision, we shouldn't
1289 1291 # care for the dirty state of the working directory.
1290 1292 if any(wctx.sub(s).dirty() for s in wctx.substate):
1291 1293 m1[b'.hgsubstate'] = modifiednodeid
1292 1294
1293 1295 # Don't use m2-vs-ma optimization if:
1294 1296 # - ma is the same as m1 or m2, which we're just going to diff again later
1295 1297 # - The caller specifically asks for a full diff, which is useful during bid
1296 1298 # merge.
1297 1299 if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
1298 1300 # Identify which files are relevant to the merge, so we can limit the
1299 1301 # total m1-vs-m2 diff to just those files. This has significant
1300 1302 # performance benefits in large repositories.
1301 1303 relevantfiles = set(ma.diff(m2).keys())
1302 1304
1303 1305 # For copied and moved files, we need to add the source file too.
1304 for copykey, copyvalue in pycompat.iteritems(copy):
1306 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
1305 1307 if copyvalue in relevantfiles:
1306 1308 relevantfiles.add(copykey)
1307 for movedirkey in movewithdir:
1309 for movedirkey in branch_copies1.movewithdir:
1308 1310 relevantfiles.add(movedirkey)
1309 1311 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1310 1312 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1311 1313
1312 1314 diff = m1.diff(m2, match=matcher)
1313 1315
1314 1316 actions = {}
1315 1317 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
1316 1318 if n1 and n2: # file exists on both local and remote side
1317 1319 if f not in ma:
1318 fa = copy.get(f, None)
1320 # TODO: what if they're renamed from different sources?
1321 fa = branch_copies1.copy.get(
1322 f, None
1323 ) or branch_copies2.copy.get(f, None)
1319 1324 if fa is not None:
1320 1325 actions[f] = (
1321 1326 ACTION_MERGE,
1322 1327 (f, f, fa, False, pa.node()),
1323 1328 b'both renamed from %s' % fa,
1324 1329 )
1325 1330 else:
1326 1331 actions[f] = (
1327 1332 ACTION_MERGE,
1328 1333 (f, f, None, False, pa.node()),
1329 1334 b'both created',
1330 1335 )
1331 1336 else:
1332 1337 a = ma[f]
1333 1338 fla = ma.flags(f)
1334 1339 nol = b'l' not in fl1 + fl2 + fla
1335 1340 if n2 == a and fl2 == fla:
1336 1341 actions[f] = (ACTION_KEEP, (), b'remote unchanged')
1337 1342 elif n1 == a and fl1 == fla: # local unchanged - use remote
1338 1343 if n1 == n2: # optimization: keep local content
1339 1344 actions[f] = (
1340 1345 ACTION_EXEC,
1341 1346 (fl2,),
1342 1347 b'update permissions',
1343 1348 )
1344 1349 else:
1345 1350 actions[f] = (
1346 1351 ACTION_GET,
1347 1352 (fl2, False),
1348 1353 b'remote is newer',
1349 1354 )
1350 1355 elif nol and n2 == a: # remote only changed 'x'
1351 1356 actions[f] = (ACTION_EXEC, (fl2,), b'update permissions')
1352 1357 elif nol and n1 == a: # local only changed 'x'
1353 1358 actions[f] = (ACTION_GET, (fl1, False), b'remote is newer')
1354 1359 else: # both changed something
1355 1360 actions[f] = (
1356 1361 ACTION_MERGE,
1357 1362 (f, f, f, False, pa.node()),
1358 1363 b'versions differ',
1359 1364 )
1360 1365 elif n1: # file exists only on local side
1361 if f in copied:
1366 if f in copied2:
1362 1367 pass # we'll deal with it on m2 side
1363 elif f in movewithdir: # directory rename, move local
1364 f2 = movewithdir[f]
1368 elif (
1369 f in branch_copies1.movewithdir
1370 ): # directory rename, move local
1371 f2 = branch_copies1.movewithdir[f]
1365 1372 if f2 in m2:
1366 1373 actions[f2] = (
1367 1374 ACTION_MERGE,
1368 1375 (f, f2, None, True, pa.node()),
1369 1376 b'remote directory rename, both created',
1370 1377 )
1371 1378 else:
1372 1379 actions[f2] = (
1373 1380 ACTION_DIR_RENAME_MOVE_LOCAL,
1374 1381 (f, fl1),
1375 1382 b'remote directory rename - move from %s' % f,
1376 1383 )
1377 elif f in copy:
1378 f2 = copy[f]
1384 elif f in branch_copies1.copy:
1385 f2 = branch_copies1.copy[f]
1379 1386 actions[f] = (
1380 1387 ACTION_MERGE,
1381 1388 (f, f2, f2, False, pa.node()),
1382 1389 b'local copied/moved from %s' % f2,
1383 1390 )
1384 1391 elif f in ma: # clean, a different, no remote
1385 1392 if n1 != ma[f]:
1386 1393 if acceptremote:
1387 1394 actions[f] = (ACTION_REMOVE, None, b'remote delete')
1388 1395 else:
1389 1396 actions[f] = (
1390 1397 ACTION_CHANGED_DELETED,
1391 1398 (f, None, f, False, pa.node()),
1392 1399 b'prompt changed/deleted',
1393 1400 )
1394 1401 elif n1 == addednodeid:
1395 1402 # This extra 'a' is added by working copy manifest to mark
1396 1403 # the file as locally added. We should forget it instead of
1397 1404 # deleting it.
1398 1405 actions[f] = (ACTION_FORGET, None, b'remote deleted')
1399 1406 else:
1400 1407 actions[f] = (ACTION_REMOVE, None, b'other deleted')
1401 1408 elif n2: # file exists only on remote side
1402 if f in copied:
1409 if f in copied1:
1403 1410 pass # we'll deal with it on m1 side
1404 elif f in movewithdir:
1405 f2 = movewithdir[f]
1411 elif f in branch_copies2.movewithdir:
1412 f2 = branch_copies2.movewithdir[f]
1406 1413 if f2 in m1:
1407 1414 actions[f2] = (
1408 1415 ACTION_MERGE,
1409 1416 (f2, f, None, False, pa.node()),
1410 1417 b'local directory rename, both created',
1411 1418 )
1412 1419 else:
1413 1420 actions[f2] = (
1414 1421 ACTION_LOCAL_DIR_RENAME_GET,
1415 1422 (f, fl2),
1416 1423 b'local directory rename - get from %s' % f,
1417 1424 )
1418 elif f in copy:
1419 f2 = copy[f]
1425 elif f in branch_copies2.copy:
1426 f2 = branch_copies2.copy[f]
1420 1427 if f2 in m2:
1421 1428 actions[f] = (
1422 1429 ACTION_MERGE,
1423 1430 (f2, f, f2, False, pa.node()),
1424 1431 b'remote copied from %s' % f2,
1425 1432 )
1426 1433 else:
1427 1434 actions[f] = (
1428 1435 ACTION_MERGE,
1429 1436 (f2, f, f2, True, pa.node()),
1430 1437 b'remote moved from %s' % f2,
1431 1438 )
1432 1439 elif f not in ma:
1433 1440 # local unknown, remote created: the logic is described by the
1434 1441 # following table:
1435 1442 #
1436 1443 # force branchmerge different | action
1437 1444 # n * * | create
1438 1445 # y n * | create
1439 1446 # y y n | create
1440 1447 # y y y | merge
1441 1448 #
1442 1449 # Checking whether the files are different is expensive, so we
1443 1450 # don't do that when we can avoid it.
1444 1451 if not force:
1445 1452 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1446 1453 elif not branchmerge:
1447 1454 actions[f] = (ACTION_CREATED, (fl2,), b'remote created')
1448 1455 else:
1449 1456 actions[f] = (
1450 1457 ACTION_CREATED_MERGE,
1451 1458 (fl2, pa.node()),
1452 1459 b'remote created, get or merge',
1453 1460 )
1454 1461 elif n2 != ma[f]:
1455 1462 df = None
1456 for d in dirmove:
1463 for d in branch_copies1.dirmove:
1457 1464 if f.startswith(d):
1458 1465 # new file added in a directory that was moved
1459 df = dirmove[d] + f[len(d) :]
1466 df = branch_copies1.dirmove[d] + f[len(d) :]
1460 1467 break
1461 1468 if df is not None and df in m1:
1462 1469 actions[df] = (
1463 1470 ACTION_MERGE,
1464 1471 (df, f, f, False, pa.node()),
1465 1472 b'local directory rename - respect move '
1466 1473 b'from %s' % f,
1467 1474 )
1468 1475 elif acceptremote:
1469 1476 actions[f] = (ACTION_CREATED, (fl2,), b'remote recreating')
1470 1477 else:
1471 1478 actions[f] = (
1472 1479 ACTION_DELETED_CHANGED,
1473 1480 (None, f, f, False, pa.node()),
1474 1481 b'prompt deleted/changed',
1475 1482 )
1476 1483
1477 1484 if repo.ui.configbool(b'experimental', b'merge.checkpathconflicts'):
1478 1485 # If we are merging, look for path conflicts.
1479 1486 checkpathconflicts(repo, wctx, p2, actions)
1480 1487
1481 1488 narrowmatch = repo.narrowmatch()
1482 1489 if not narrowmatch.always():
1483 1490 # Updates "actions" in place
1484 1491 _filternarrowactions(narrowmatch, branchmerge, actions)
1485 1492
1493 renamedelete = branch_copies1.renamedelete
1494 renamedelete.update(branch_copies2.renamedelete)
1495
1486 1496 return actions, diverge, renamedelete
1487 1497
1488 1498
1489 1499 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1490 1500 """Resolves false conflicts where the nodeid changed but the content
1491 1501 remained the same."""
1492 1502 # We force a copy of actions.items() because we're going to mutate
1493 1503 # actions as we resolve trivial conflicts.
1494 1504 for f, (m, args, msg) in list(actions.items()):
1495 1505 if (
1496 1506 m == ACTION_CHANGED_DELETED
1497 1507 and f in ancestor
1498 1508 and not wctx[f].cmp(ancestor[f])
1499 1509 ):
1500 1510 # local did change but ended up with same content
1501 1511 actions[f] = ACTION_REMOVE, None, b'prompt same'
1502 1512 elif (
1503 1513 m == ACTION_DELETED_CHANGED
1504 1514 and f in ancestor
1505 1515 and not mctx[f].cmp(ancestor[f])
1506 1516 ):
1507 1517 # remote did change but ended up with same content
1508 1518 del actions[f] # don't get = keep local deleted
1509 1519
1510 1520
1511 1521 def calculateupdates(
1512 1522 repo,
1513 1523 wctx,
1514 1524 mctx,
1515 1525 ancestors,
1516 1526 branchmerge,
1517 1527 force,
1518 1528 acceptremote,
1519 1529 followcopies,
1520 1530 matcher=None,
1521 1531 mergeforce=False,
1522 1532 ):
1523 1533 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1524 1534 # Avoid cycle.
1525 1535 from . import sparse
1526 1536
1527 1537 if len(ancestors) == 1: # default
1528 1538 actions, diverge, renamedelete = manifestmerge(
1529 1539 repo,
1530 1540 wctx,
1531 1541 mctx,
1532 1542 ancestors[0],
1533 1543 branchmerge,
1534 1544 force,
1535 1545 matcher,
1536 1546 acceptremote,
1537 1547 followcopies,
1538 1548 )
1539 1549 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1540 1550
1541 1551 else: # only when merge.preferancestor=* - the default
1542 1552 repo.ui.note(
1543 1553 _(b"note: merging %s and %s using bids from ancestors %s\n")
1544 1554 % (
1545 1555 wctx,
1546 1556 mctx,
1547 1557 _(b' and ').join(pycompat.bytestr(anc) for anc in ancestors),
1548 1558 )
1549 1559 )
1550 1560
1551 1561 # Call for bids
1552 1562 fbids = (
1553 1563 {}
1554 1564 ) # mapping filename to bids (action method to list af actions)
1555 1565 diverge, renamedelete = None, None
1556 1566 for ancestor in ancestors:
1557 1567 repo.ui.note(_(b'\ncalculating bids for ancestor %s\n') % ancestor)
1558 1568 actions, diverge1, renamedelete1 = manifestmerge(
1559 1569 repo,
1560 1570 wctx,
1561 1571 mctx,
1562 1572 ancestor,
1563 1573 branchmerge,
1564 1574 force,
1565 1575 matcher,
1566 1576 acceptremote,
1567 1577 followcopies,
1568 1578 forcefulldiff=True,
1569 1579 )
1570 1580 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1571 1581
1572 1582 # Track the shortest set of warning on the theory that bid
1573 1583 # merge will correctly incorporate more information
1574 1584 if diverge is None or len(diverge1) < len(diverge):
1575 1585 diverge = diverge1
1576 1586 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1577 1587 renamedelete = renamedelete1
1578 1588
1579 1589 for f, a in sorted(pycompat.iteritems(actions)):
1580 1590 m, args, msg = a
1581 1591 repo.ui.debug(b' %s: %s -> %s\n' % (f, msg, m))
1582 1592 if f in fbids:
1583 1593 d = fbids[f]
1584 1594 if m in d:
1585 1595 d[m].append(a)
1586 1596 else:
1587 1597 d[m] = [a]
1588 1598 else:
1589 1599 fbids[f] = {m: [a]}
1590 1600
1591 1601 # Pick the best bid for each file
1592 1602 repo.ui.note(_(b'\nauction for merging merge bids\n'))
1593 1603 actions = {}
1594 1604 for f, bids in sorted(fbids.items()):
1595 1605 # bids is a mapping from action method to list af actions
1596 1606 # Consensus?
1597 1607 if len(bids) == 1: # all bids are the same kind of method
1598 1608 m, l = list(bids.items())[0]
1599 1609 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1600 1610 repo.ui.note(_(b" %s: consensus for %s\n") % (f, m))
1601 1611 actions[f] = l[0]
1602 1612 continue
1603 1613 # If keep is an option, just do it.
1604 1614 if ACTION_KEEP in bids:
1605 1615 repo.ui.note(_(b" %s: picking 'keep' action\n") % f)
1606 1616 actions[f] = bids[ACTION_KEEP][0]
1607 1617 continue
1608 1618 # If there are gets and they all agree [how could they not?], do it.
1609 1619 if ACTION_GET in bids:
1610 1620 ga0 = bids[ACTION_GET][0]
1611 1621 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1612 1622 repo.ui.note(_(b" %s: picking 'get' action\n") % f)
1613 1623 actions[f] = ga0
1614 1624 continue
1615 1625 # TODO: Consider other simple actions such as mode changes
1616 1626 # Handle inefficient democrazy.
1617 1627 repo.ui.note(_(b' %s: multiple bids for merge action:\n') % f)
1618 1628 for m, l in sorted(bids.items()):
1619 1629 for _f, args, msg in l:
1620 1630 repo.ui.note(b' %s -> %s\n' % (msg, m))
1621 1631 # Pick random action. TODO: Instead, prompt user when resolving
1622 1632 m, l = list(bids.items())[0]
1623 1633 repo.ui.warn(
1624 1634 _(b' %s: ambiguous merge - picked %s action\n') % (f, m)
1625 1635 )
1626 1636 actions[f] = l[0]
1627 1637 continue
1628 1638 repo.ui.note(_(b'end of auction\n\n'))
1629 1639
1630 1640 if wctx.rev() is None:
1631 1641 fractions = _forgetremoved(wctx, mctx, branchmerge)
1632 1642 actions.update(fractions)
1633 1643
1634 1644 prunedactions = sparse.filterupdatesactions(
1635 1645 repo, wctx, mctx, branchmerge, actions
1636 1646 )
1637 1647 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1638 1648
1639 1649 return prunedactions, diverge, renamedelete
1640 1650
1641 1651
1642 1652 def _getcwd():
1643 1653 try:
1644 1654 return encoding.getcwd()
1645 1655 except OSError as err:
1646 1656 if err.errno == errno.ENOENT:
1647 1657 return None
1648 1658 raise
1649 1659
1650 1660
1651 1661 def batchremove(repo, wctx, actions):
1652 1662 """apply removes to the working directory
1653 1663
1654 1664 yields tuples for progress updates
1655 1665 """
1656 1666 verbose = repo.ui.verbose
1657 1667 cwd = _getcwd()
1658 1668 i = 0
1659 1669 for f, args, msg in actions:
1660 1670 repo.ui.debug(b" %s: %s -> r\n" % (f, msg))
1661 1671 if verbose:
1662 1672 repo.ui.note(_(b"removing %s\n") % f)
1663 1673 wctx[f].audit()
1664 1674 try:
1665 1675 wctx[f].remove(ignoremissing=True)
1666 1676 except OSError as inst:
1667 1677 repo.ui.warn(
1668 1678 _(b"update failed to remove %s: %s!\n") % (f, inst.strerror)
1669 1679 )
1670 1680 if i == 100:
1671 1681 yield i, f
1672 1682 i = 0
1673 1683 i += 1
1674 1684 if i > 0:
1675 1685 yield i, f
1676 1686
1677 1687 if cwd and not _getcwd():
1678 1688 # cwd was removed in the course of removing files; print a helpful
1679 1689 # warning.
1680 1690 repo.ui.warn(
1681 1691 _(
1682 1692 b"current directory was removed\n"
1683 1693 b"(consider changing to repo root: %s)\n"
1684 1694 )
1685 1695 % repo.root
1686 1696 )
1687 1697
1688 1698
1689 1699 def batchget(repo, mctx, wctx, wantfiledata, actions):
1690 1700 """apply gets to the working directory
1691 1701
1692 1702 mctx is the context to get from
1693 1703
1694 1704 Yields arbitrarily many (False, tuple) for progress updates, followed by
1695 1705 exactly one (True, filedata). When wantfiledata is false, filedata is an
1696 1706 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1697 1707 mtime) of the file f written for each action.
1698 1708 """
1699 1709 filedata = {}
1700 1710 verbose = repo.ui.verbose
1701 1711 fctx = mctx.filectx
1702 1712 ui = repo.ui
1703 1713 i = 0
1704 1714 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1705 1715 for f, (flags, backup), msg in actions:
1706 1716 repo.ui.debug(b" %s: %s -> g\n" % (f, msg))
1707 1717 if verbose:
1708 1718 repo.ui.note(_(b"getting %s\n") % f)
1709 1719
1710 1720 if backup:
1711 1721 # If a file or directory exists with the same name, back that
1712 1722 # up. Otherwise, look to see if there is a file that conflicts
1713 1723 # with a directory this file is in, and if so, back that up.
1714 1724 conflicting = f
1715 1725 if not repo.wvfs.lexists(f):
1716 1726 for p in pathutil.finddirs(f):
1717 1727 if repo.wvfs.isfileorlink(p):
1718 1728 conflicting = p
1719 1729 break
1720 1730 if repo.wvfs.lexists(conflicting):
1721 1731 orig = scmutil.backuppath(ui, repo, conflicting)
1722 1732 util.rename(repo.wjoin(conflicting), orig)
1723 1733 wfctx = wctx[f]
1724 1734 wfctx.clearunknown()
1725 1735 atomictemp = ui.configbool(b"experimental", b"update.atomic-file")
1726 1736 size = wfctx.write(
1727 1737 fctx(f).data(),
1728 1738 flags,
1729 1739 backgroundclose=True,
1730 1740 atomictemp=atomictemp,
1731 1741 )
1732 1742 if wantfiledata:
1733 1743 s = wfctx.lstat()
1734 1744 mode = s.st_mode
1735 1745 mtime = s[stat.ST_MTIME]
1736 1746 filedata[f] = (mode, size, mtime) # for dirstate.normal
1737 1747 if i == 100:
1738 1748 yield False, (i, f)
1739 1749 i = 0
1740 1750 i += 1
1741 1751 if i > 0:
1742 1752 yield False, (i, f)
1743 1753 yield True, filedata
1744 1754
1745 1755
1746 1756 def _prefetchfiles(repo, ctx, actions):
1747 1757 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1748 1758 of merge actions. ``ctx`` is the context being merged in."""
1749 1759
1750 1760 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1751 1761 # don't touch the context to be merged in. 'cd' is skipped, because
1752 1762 # changed/deleted never resolves to something from the remote side.
1753 1763 oplist = [
1754 1764 actions[a]
1755 1765 for a in (
1756 1766 ACTION_GET,
1757 1767 ACTION_DELETED_CHANGED,
1758 1768 ACTION_LOCAL_DIR_RENAME_GET,
1759 1769 ACTION_MERGE,
1760 1770 )
1761 1771 ]
1762 1772 prefetch = scmutil.prefetchfiles
1763 1773 matchfiles = scmutil.matchfiles
1764 1774 prefetch(
1765 1775 repo,
1766 1776 [ctx.rev()],
1767 1777 matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
1768 1778 )
1769 1779
1770 1780
1771 1781 @attr.s(frozen=True)
1772 1782 class updateresult(object):
1773 1783 updatedcount = attr.ib()
1774 1784 mergedcount = attr.ib()
1775 1785 removedcount = attr.ib()
1776 1786 unresolvedcount = attr.ib()
1777 1787
1778 1788 def isempty(self):
1779 1789 return not (
1780 1790 self.updatedcount
1781 1791 or self.mergedcount
1782 1792 or self.removedcount
1783 1793 or self.unresolvedcount
1784 1794 )
1785 1795
1786 1796
1787 1797 def emptyactions():
1788 1798 """create an actions dict, to be populated and passed to applyupdates()"""
1789 1799 return dict(
1790 1800 (m, [])
1791 1801 for m in (
1792 1802 ACTION_ADD,
1793 1803 ACTION_ADD_MODIFIED,
1794 1804 ACTION_FORGET,
1795 1805 ACTION_GET,
1796 1806 ACTION_CHANGED_DELETED,
1797 1807 ACTION_DELETED_CHANGED,
1798 1808 ACTION_REMOVE,
1799 1809 ACTION_DIR_RENAME_MOVE_LOCAL,
1800 1810 ACTION_LOCAL_DIR_RENAME_GET,
1801 1811 ACTION_MERGE,
1802 1812 ACTION_EXEC,
1803 1813 ACTION_KEEP,
1804 1814 ACTION_PATH_CONFLICT,
1805 1815 ACTION_PATH_CONFLICT_RESOLVE,
1806 1816 )
1807 1817 )
1808 1818
1809 1819
1810 1820 def applyupdates(
1811 1821 repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
1812 1822 ):
1813 1823 """apply the merge action list to the working directory
1814 1824
1815 1825 wctx is the working copy context
1816 1826 mctx is the context to be merged into the working copy
1817 1827
1818 1828 Return a tuple of (counts, filedata), where counts is a tuple
1819 1829 (updated, merged, removed, unresolved) that describes how many
1820 1830 files were affected by the update, and filedata is as described in
1821 1831 batchget.
1822 1832 """
1823 1833
1824 1834 _prefetchfiles(repo, mctx, actions)
1825 1835
1826 1836 updated, merged, removed = 0, 0, 0
1827 1837 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1828 1838 moves = []
1829 1839 for m, l in actions.items():
1830 1840 l.sort()
1831 1841
1832 1842 # 'cd' and 'dc' actions are treated like other merge conflicts
1833 1843 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1834 1844 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1835 1845 mergeactions.extend(actions[ACTION_MERGE])
1836 1846 for f, args, msg in mergeactions:
1837 1847 f1, f2, fa, move, anc = args
1838 1848 if f == b'.hgsubstate': # merged internally
1839 1849 continue
1840 1850 if f1 is None:
1841 1851 fcl = filemerge.absentfilectx(wctx, fa)
1842 1852 else:
1843 1853 repo.ui.debug(b" preserving %s for resolve of %s\n" % (f1, f))
1844 1854 fcl = wctx[f1]
1845 1855 if f2 is None:
1846 1856 fco = filemerge.absentfilectx(mctx, fa)
1847 1857 else:
1848 1858 fco = mctx[f2]
1849 1859 actx = repo[anc]
1850 1860 if fa in actx:
1851 1861 fca = actx[fa]
1852 1862 else:
1853 1863 # TODO: move to absentfilectx
1854 1864 fca = repo.filectx(f1, fileid=nullrev)
1855 1865 ms.add(fcl, fco, fca, f)
1856 1866 if f1 != f and move:
1857 1867 moves.append(f1)
1858 1868
1859 1869 # remove renamed files after safely stored
1860 1870 for f in moves:
1861 1871 if wctx[f].lexists():
1862 1872 repo.ui.debug(b"removing %s\n" % f)
1863 1873 wctx[f].audit()
1864 1874 wctx[f].remove()
1865 1875
1866 1876 numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP)
1867 1877 progress = repo.ui.makeprogress(
1868 1878 _(b'updating'), unit=_(b'files'), total=numupdates
1869 1879 )
1870 1880
1871 1881 if [a for a in actions[ACTION_REMOVE] if a[0] == b'.hgsubstate']:
1872 1882 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1873 1883
1874 1884 # record path conflicts
1875 1885 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1876 1886 f1, fo = args
1877 1887 s = repo.ui.status
1878 1888 s(
1879 1889 _(
1880 1890 b"%s: path conflict - a file or link has the same name as a "
1881 1891 b"directory\n"
1882 1892 )
1883 1893 % f
1884 1894 )
1885 1895 if fo == b'l':
1886 1896 s(_(b"the local file has been renamed to %s\n") % f1)
1887 1897 else:
1888 1898 s(_(b"the remote file has been renamed to %s\n") % f1)
1889 1899 s(_(b"resolve manually then use 'hg resolve --mark %s'\n") % f)
1890 1900 ms.addpath(f, f1, fo)
1891 1901 progress.increment(item=f)
1892 1902
1893 1903 # When merging in-memory, we can't support worker processes, so set the
1894 1904 # per-item cost at 0 in that case.
1895 1905 cost = 0 if wctx.isinmemory() else 0.001
1896 1906
1897 1907 # remove in parallel (must come before resolving path conflicts and getting)
1898 1908 prog = worker.worker(
1899 1909 repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE]
1900 1910 )
1901 1911 for i, item in prog:
1902 1912 progress.increment(step=i, item=item)
1903 1913 removed = len(actions[ACTION_REMOVE])
1904 1914
1905 1915 # resolve path conflicts (must come before getting)
1906 1916 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1907 1917 repo.ui.debug(b" %s: %s -> pr\n" % (f, msg))
1908 1918 (f0,) = args
1909 1919 if wctx[f0].lexists():
1910 1920 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1911 1921 wctx[f].audit()
1912 1922 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1913 1923 wctx[f0].remove()
1914 1924 progress.increment(item=f)
1915 1925
1916 1926 # get in parallel.
1917 1927 threadsafe = repo.ui.configbool(
1918 1928 b'experimental', b'worker.wdir-get-thread-safe'
1919 1929 )
1920 1930 prog = worker.worker(
1921 1931 repo.ui,
1922 1932 cost,
1923 1933 batchget,
1924 1934 (repo, mctx, wctx, wantfiledata),
1925 1935 actions[ACTION_GET],
1926 1936 threadsafe=threadsafe,
1927 1937 hasretval=True,
1928 1938 )
1929 1939 getfiledata = {}
1930 1940 for final, res in prog:
1931 1941 if final:
1932 1942 getfiledata = res
1933 1943 else:
1934 1944 i, item = res
1935 1945 progress.increment(step=i, item=item)
1936 1946 updated = len(actions[ACTION_GET])
1937 1947
1938 1948 if [a for a in actions[ACTION_GET] if a[0] == b'.hgsubstate']:
1939 1949 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1940 1950
1941 1951 # forget (manifest only, just log it) (must come first)
1942 1952 for f, args, msg in actions[ACTION_FORGET]:
1943 1953 repo.ui.debug(b" %s: %s -> f\n" % (f, msg))
1944 1954 progress.increment(item=f)
1945 1955
1946 1956 # re-add (manifest only, just log it)
1947 1957 for f, args, msg in actions[ACTION_ADD]:
1948 1958 repo.ui.debug(b" %s: %s -> a\n" % (f, msg))
1949 1959 progress.increment(item=f)
1950 1960
1951 1961 # re-add/mark as modified (manifest only, just log it)
1952 1962 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1953 1963 repo.ui.debug(b" %s: %s -> am\n" % (f, msg))
1954 1964 progress.increment(item=f)
1955 1965
1956 1966 # keep (noop, just log it)
1957 1967 for f, args, msg in actions[ACTION_KEEP]:
1958 1968 repo.ui.debug(b" %s: %s -> k\n" % (f, msg))
1959 1969 # no progress
1960 1970
1961 1971 # directory rename, move local
1962 1972 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1963 1973 repo.ui.debug(b" %s: %s -> dm\n" % (f, msg))
1964 1974 progress.increment(item=f)
1965 1975 f0, flags = args
1966 1976 repo.ui.note(_(b"moving %s to %s\n") % (f0, f))
1967 1977 wctx[f].audit()
1968 1978 wctx[f].write(wctx.filectx(f0).data(), flags)
1969 1979 wctx[f0].remove()
1970 1980 updated += 1
1971 1981
1972 1982 # local directory rename, get
1973 1983 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1974 1984 repo.ui.debug(b" %s: %s -> dg\n" % (f, msg))
1975 1985 progress.increment(item=f)
1976 1986 f0, flags = args
1977 1987 repo.ui.note(_(b"getting %s to %s\n") % (f0, f))
1978 1988 wctx[f].write(mctx.filectx(f0).data(), flags)
1979 1989 updated += 1
1980 1990
1981 1991 # exec
1982 1992 for f, args, msg in actions[ACTION_EXEC]:
1983 1993 repo.ui.debug(b" %s: %s -> e\n" % (f, msg))
1984 1994 progress.increment(item=f)
1985 1995 (flags,) = args
1986 1996 wctx[f].audit()
1987 1997 wctx[f].setflags(b'l' in flags, b'x' in flags)
1988 1998 updated += 1
1989 1999
1990 2000 # the ordering is important here -- ms.mergedriver will raise if the merge
1991 2001 # driver has changed, and we want to be able to bypass it when overwrite is
1992 2002 # True
1993 2003 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1994 2004
1995 2005 if usemergedriver:
1996 2006 if wctx.isinmemory():
1997 2007 raise error.InMemoryMergeConflictsError(
1998 2008 b"in-memory merge does not support mergedriver"
1999 2009 )
2000 2010 ms.commit()
2001 2011 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
2002 2012 # the driver might leave some files unresolved
2003 2013 unresolvedf = set(ms.unresolved())
2004 2014 if not proceed:
2005 2015 # XXX setting unresolved to at least 1 is a hack to make sure we
2006 2016 # error out
2007 2017 return updateresult(
2008 2018 updated, merged, removed, max(len(unresolvedf), 1)
2009 2019 )
2010 2020 newactions = []
2011 2021 for f, args, msg in mergeactions:
2012 2022 if f in unresolvedf:
2013 2023 newactions.append((f, args, msg))
2014 2024 mergeactions = newactions
2015 2025
2016 2026 try:
2017 2027 # premerge
2018 2028 tocomplete = []
2019 2029 for f, args, msg in mergeactions:
2020 2030 repo.ui.debug(b" %s: %s -> m (premerge)\n" % (f, msg))
2021 2031 progress.increment(item=f)
2022 2032 if f == b'.hgsubstate': # subrepo states need updating
2023 2033 subrepoutil.submerge(
2024 2034 repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
2025 2035 )
2026 2036 continue
2027 2037 wctx[f].audit()
2028 2038 complete, r = ms.preresolve(f, wctx)
2029 2039 if not complete:
2030 2040 numupdates += 1
2031 2041 tocomplete.append((f, args, msg))
2032 2042
2033 2043 # merge
2034 2044 for f, args, msg in tocomplete:
2035 2045 repo.ui.debug(b" %s: %s -> m (merge)\n" % (f, msg))
2036 2046 progress.increment(item=f, total=numupdates)
2037 2047 ms.resolve(f, wctx)
2038 2048
2039 2049 finally:
2040 2050 ms.commit()
2041 2051
2042 2052 unresolved = ms.unresolvedcount()
2043 2053
2044 2054 if (
2045 2055 usemergedriver
2046 2056 and not unresolved
2047 2057 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS
2048 2058 ):
2049 2059 if not driverconclude(repo, ms, wctx, labels=labels):
2050 2060 # XXX setting unresolved to at least 1 is a hack to make sure we
2051 2061 # error out
2052 2062 unresolved = max(unresolved, 1)
2053 2063
2054 2064 ms.commit()
2055 2065
2056 2066 msupdated, msmerged, msremoved = ms.counts()
2057 2067 updated += msupdated
2058 2068 merged += msmerged
2059 2069 removed += msremoved
2060 2070
2061 2071 extraactions = ms.actions()
2062 2072 if extraactions:
2063 2073 mfiles = set(a[0] for a in actions[ACTION_MERGE])
2064 2074 for k, acts in pycompat.iteritems(extraactions):
2065 2075 actions[k].extend(acts)
2066 2076 if k == ACTION_GET and wantfiledata:
2067 2077 # no filedata until mergestate is updated to provide it
2068 2078 for a in acts:
2069 2079 getfiledata[a[0]] = None
2070 2080 # Remove these files from actions[ACTION_MERGE] as well. This is
2071 2081 # important because in recordupdates, files in actions[ACTION_MERGE]
2072 2082 # are processed after files in other actions, and the merge driver
2073 2083 # might add files to those actions via extraactions above. This can
2074 2084 # lead to a file being recorded twice, with poor results. This is
2075 2085 # especially problematic for actions[ACTION_REMOVE] (currently only
2076 2086 # possible with the merge driver in the initial merge process;
2077 2087 # interrupted merges don't go through this flow).
2078 2088 #
2079 2089 # The real fix here is to have indexes by both file and action so
2080 2090 # that when the action for a file is changed it is automatically
2081 2091 # reflected in the other action lists. But that involves a more
2082 2092 # complex data structure, so this will do for now.
2083 2093 #
2084 2094 # We don't need to do the same operation for 'dc' and 'cd' because
2085 2095 # those lists aren't consulted again.
2086 2096 mfiles.difference_update(a[0] for a in acts)
2087 2097
2088 2098 actions[ACTION_MERGE] = [
2089 2099 a for a in actions[ACTION_MERGE] if a[0] in mfiles
2090 2100 ]
2091 2101
2092 2102 progress.complete()
2093 2103 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
2094 2104 return updateresult(updated, merged, removed, unresolved), getfiledata
2095 2105
2096 2106
2097 2107 def recordupdates(repo, actions, branchmerge, getfiledata):
2098 2108 """record merge actions to the dirstate"""
2099 2109 # remove (must come first)
2100 2110 for f, args, msg in actions.get(ACTION_REMOVE, []):
2101 2111 if branchmerge:
2102 2112 repo.dirstate.remove(f)
2103 2113 else:
2104 2114 repo.dirstate.drop(f)
2105 2115
2106 2116 # forget (must come first)
2107 2117 for f, args, msg in actions.get(ACTION_FORGET, []):
2108 2118 repo.dirstate.drop(f)
2109 2119
2110 2120 # resolve path conflicts
2111 2121 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
2112 2122 (f0,) = args
2113 2123 origf0 = repo.dirstate.copied(f0) or f0
2114 2124 repo.dirstate.add(f)
2115 2125 repo.dirstate.copy(origf0, f)
2116 2126 if f0 == origf0:
2117 2127 repo.dirstate.remove(f0)
2118 2128 else:
2119 2129 repo.dirstate.drop(f0)
2120 2130
2121 2131 # re-add
2122 2132 for f, args, msg in actions.get(ACTION_ADD, []):
2123 2133 repo.dirstate.add(f)
2124 2134
2125 2135 # re-add/mark as modified
2126 2136 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
2127 2137 if branchmerge:
2128 2138 repo.dirstate.normallookup(f)
2129 2139 else:
2130 2140 repo.dirstate.add(f)
2131 2141
2132 2142 # exec change
2133 2143 for f, args, msg in actions.get(ACTION_EXEC, []):
2134 2144 repo.dirstate.normallookup(f)
2135 2145
2136 2146 # keep
2137 2147 for f, args, msg in actions.get(ACTION_KEEP, []):
2138 2148 pass
2139 2149
2140 2150 # get
2141 2151 for f, args, msg in actions.get(ACTION_GET, []):
2142 2152 if branchmerge:
2143 2153 repo.dirstate.otherparent(f)
2144 2154 else:
2145 2155 parentfiledata = getfiledata[f] if getfiledata else None
2146 2156 repo.dirstate.normal(f, parentfiledata=parentfiledata)
2147 2157
2148 2158 # merge
2149 2159 for f, args, msg in actions.get(ACTION_MERGE, []):
2150 2160 f1, f2, fa, move, anc = args
2151 2161 if branchmerge:
2152 2162 # We've done a branch merge, mark this file as merged
2153 2163 # so that we properly record the merger later
2154 2164 repo.dirstate.merge(f)
2155 2165 if f1 != f2: # copy/rename
2156 2166 if move:
2157 2167 repo.dirstate.remove(f1)
2158 2168 if f1 != f:
2159 2169 repo.dirstate.copy(f1, f)
2160 2170 else:
2161 2171 repo.dirstate.copy(f2, f)
2162 2172 else:
2163 2173 # We've update-merged a locally modified file, so
2164 2174 # we set the dirstate to emulate a normal checkout
2165 2175 # of that file some time in the past. Thus our
2166 2176 # merge will appear as a normal local file
2167 2177 # modification.
2168 2178 if f2 == f: # file not locally copied/moved
2169 2179 repo.dirstate.normallookup(f)
2170 2180 if move:
2171 2181 repo.dirstate.drop(f1)
2172 2182
2173 2183 # directory rename, move local
2174 2184 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
2175 2185 f0, flag = args
2176 2186 if branchmerge:
2177 2187 repo.dirstate.add(f)
2178 2188 repo.dirstate.remove(f0)
2179 2189 repo.dirstate.copy(f0, f)
2180 2190 else:
2181 2191 repo.dirstate.normal(f)
2182 2192 repo.dirstate.drop(f0)
2183 2193
2184 2194 # directory rename, get
2185 2195 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
2186 2196 f0, flag = args
2187 2197 if branchmerge:
2188 2198 repo.dirstate.add(f)
2189 2199 repo.dirstate.copy(f0, f)
2190 2200 else:
2191 2201 repo.dirstate.normal(f)
2192 2202
2193 2203
2194 2204 UPDATECHECK_ABORT = b'abort' # handled at higher layers
2195 2205 UPDATECHECK_NONE = b'none'
2196 2206 UPDATECHECK_LINEAR = b'linear'
2197 2207 UPDATECHECK_NO_CONFLICT = b'noconflict'
2198 2208
2199 2209
2200 2210 def update(
2201 2211 repo,
2202 2212 node,
2203 2213 branchmerge,
2204 2214 force,
2205 2215 ancestor=None,
2206 2216 mergeancestor=False,
2207 2217 labels=None,
2208 2218 matcher=None,
2209 2219 mergeforce=False,
2210 2220 updatecheck=None,
2211 2221 wc=None,
2212 2222 ):
2213 2223 """
2214 2224 Perform a merge between the working directory and the given node
2215 2225
2216 2226 node = the node to update to
2217 2227 branchmerge = whether to merge between branches
2218 2228 force = whether to force branch merging or file overwriting
2219 2229 matcher = a matcher to filter file lists (dirstate not updated)
2220 2230 mergeancestor = whether it is merging with an ancestor. If true,
2221 2231 we should accept the incoming changes for any prompts that occur.
2222 2232 If false, merging with an ancestor (fast-forward) is only allowed
2223 2233 between different named branches. This flag is used by rebase extension
2224 2234 as a temporary fix and should be avoided in general.
2225 2235 labels = labels to use for base, local and other
2226 2236 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
2227 2237 this is True, then 'force' should be True as well.
2228 2238
2229 2239 The table below shows all the behaviors of the update command given the
2230 2240 -c/--check and -C/--clean or no options, whether the working directory is
2231 2241 dirty, whether a revision is specified, and the relationship of the parent
2232 2242 rev to the target rev (linear or not). Match from top first. The -n
2233 2243 option doesn't exist on the command line, but represents the
2234 2244 experimental.updatecheck=noconflict option.
2235 2245
2236 2246 This logic is tested by test-update-branches.t.
2237 2247
2238 2248 -c -C -n -m dirty rev linear | result
2239 2249 y y * * * * * | (1)
2240 2250 y * y * * * * | (1)
2241 2251 y * * y * * * | (1)
2242 2252 * y y * * * * | (1)
2243 2253 * y * y * * * | (1)
2244 2254 * * y y * * * | (1)
2245 2255 * * * * * n n | x
2246 2256 * * * * n * * | ok
2247 2257 n n n n y * y | merge
2248 2258 n n n n y y n | (2)
2249 2259 n n n y y * * | merge
2250 2260 n n y n y * * | merge if no conflict
2251 2261 n y n n y * * | discard
2252 2262 y n n n y * * | (3)
2253 2263
2254 2264 x = can't happen
2255 2265 * = don't-care
2256 2266 1 = incompatible options (checked in commands.py)
2257 2267 2 = abort: uncommitted changes (commit or update --clean to discard changes)
2258 2268 3 = abort: uncommitted changes (checked in commands.py)
2259 2269
2260 2270 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
2261 2271 to repo[None] if None is passed.
2262 2272
2263 2273 Return the same tuple as applyupdates().
2264 2274 """
2265 2275 # Avoid cycle.
2266 2276 from . import sparse
2267 2277
2268 2278 # This function used to find the default destination if node was None, but
2269 2279 # that's now in destutil.py.
2270 2280 assert node is not None
2271 2281 if not branchmerge and not force:
2272 2282 # TODO: remove the default once all callers that pass branchmerge=False
2273 2283 # and force=False pass a value for updatecheck. We may want to allow
2274 2284 # updatecheck='abort' to better suppport some of these callers.
2275 2285 if updatecheck is None:
2276 2286 updatecheck = UPDATECHECK_LINEAR
2277 2287 if updatecheck not in (
2278 2288 UPDATECHECK_NONE,
2279 2289 UPDATECHECK_LINEAR,
2280 2290 UPDATECHECK_NO_CONFLICT,
2281 2291 ):
2282 2292 raise ValueError(
2283 2293 r'Invalid updatecheck %r (can accept %r)'
2284 2294 % (
2285 2295 updatecheck,
2286 2296 (
2287 2297 UPDATECHECK_NONE,
2288 2298 UPDATECHECK_LINEAR,
2289 2299 UPDATECHECK_NO_CONFLICT,
2290 2300 ),
2291 2301 )
2292 2302 )
2293 2303 with repo.wlock():
2294 2304 if wc is None:
2295 2305 wc = repo[None]
2296 2306 pl = wc.parents()
2297 2307 p1 = pl[0]
2298 2308 p2 = repo[node]
2299 2309 if ancestor is not None:
2300 2310 pas = [repo[ancestor]]
2301 2311 else:
2302 2312 if repo.ui.configlist(b'merge', b'preferancestor') == [b'*']:
2303 2313 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2304 2314 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2305 2315 else:
2306 2316 pas = [p1.ancestor(p2, warn=branchmerge)]
2307 2317
2308 2318 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2309 2319
2310 2320 overwrite = force and not branchmerge
2311 2321 ### check phase
2312 2322 if not overwrite:
2313 2323 if len(pl) > 1:
2314 2324 raise error.Abort(_(b"outstanding uncommitted merge"))
2315 2325 ms = mergestate.read(repo)
2316 2326 if list(ms.unresolved()):
2317 2327 raise error.Abort(
2318 2328 _(b"outstanding merge conflicts"),
2319 2329 hint=_(b"use 'hg resolve' to resolve"),
2320 2330 )
2321 2331 if branchmerge:
2322 2332 if pas == [p2]:
2323 2333 raise error.Abort(
2324 2334 _(
2325 2335 b"merging with a working directory ancestor"
2326 2336 b" has no effect"
2327 2337 )
2328 2338 )
2329 2339 elif pas == [p1]:
2330 2340 if not mergeancestor and wc.branch() == p2.branch():
2331 2341 raise error.Abort(
2332 2342 _(b"nothing to merge"),
2333 2343 hint=_(b"use 'hg update' or check 'hg heads'"),
2334 2344 )
2335 2345 if not force and (wc.files() or wc.deleted()):
2336 2346 raise error.Abort(
2337 2347 _(b"uncommitted changes"),
2338 2348 hint=_(b"use 'hg status' to list changes"),
2339 2349 )
2340 2350 if not wc.isinmemory():
2341 2351 for s in sorted(wc.substate):
2342 2352 wc.sub(s).bailifchanged()
2343 2353
2344 2354 elif not overwrite:
2345 2355 if p1 == p2: # no-op update
2346 2356 # call the hooks and exit early
2347 2357 repo.hook(b'preupdate', throw=True, parent1=xp2, parent2=b'')
2348 2358 repo.hook(b'update', parent1=xp2, parent2=b'', error=0)
2349 2359 return updateresult(0, 0, 0, 0)
2350 2360
2351 2361 if updatecheck == UPDATECHECK_LINEAR and pas not in (
2352 2362 [p1],
2353 2363 [p2],
2354 2364 ): # nonlinear
2355 2365 dirty = wc.dirty(missing=True)
2356 2366 if dirty:
2357 2367 # Branching is a bit strange to ensure we do the minimal
2358 2368 # amount of call to obsutil.foreground.
2359 2369 foreground = obsutil.foreground(repo, [p1.node()])
2360 2370 # note: the <node> variable contains a random identifier
2361 2371 if repo[node].node() in foreground:
2362 2372 pass # allow updating to successors
2363 2373 else:
2364 2374 msg = _(b"uncommitted changes")
2365 2375 hint = _(b"commit or update --clean to discard changes")
2366 2376 raise error.UpdateAbort(msg, hint=hint)
2367 2377 else:
2368 2378 # Allow jumping branches if clean and specific rev given
2369 2379 pass
2370 2380
2371 2381 if overwrite:
2372 2382 pas = [wc]
2373 2383 elif not branchmerge:
2374 2384 pas = [p1]
2375 2385
2376 2386 # deprecated config: merge.followcopies
2377 2387 followcopies = repo.ui.configbool(b'merge', b'followcopies')
2378 2388 if overwrite:
2379 2389 followcopies = False
2380 2390 elif not pas[0]:
2381 2391 followcopies = False
2382 2392 if not branchmerge and not wc.dirty(missing=True):
2383 2393 followcopies = False
2384 2394
2385 2395 ### calculate phase
2386 2396 actionbyfile, diverge, renamedelete = calculateupdates(
2387 2397 repo,
2388 2398 wc,
2389 2399 p2,
2390 2400 pas,
2391 2401 branchmerge,
2392 2402 force,
2393 2403 mergeancestor,
2394 2404 followcopies,
2395 2405 matcher=matcher,
2396 2406 mergeforce=mergeforce,
2397 2407 )
2398 2408
2399 2409 if updatecheck == UPDATECHECK_NO_CONFLICT:
2400 2410 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2401 2411 if m not in (
2402 2412 ACTION_GET,
2403 2413 ACTION_KEEP,
2404 2414 ACTION_EXEC,
2405 2415 ACTION_REMOVE,
2406 2416 ACTION_PATH_CONFLICT_RESOLVE,
2407 2417 ):
2408 2418 msg = _(b"conflicting changes")
2409 2419 hint = _(b"commit or update --clean to discard changes")
2410 2420 raise error.Abort(msg, hint=hint)
2411 2421
2412 2422 # Prompt and create actions. Most of this is in the resolve phase
2413 2423 # already, but we can't handle .hgsubstate in filemerge or
2414 2424 # subrepoutil.submerge yet so we have to keep prompting for it.
2415 2425 if b'.hgsubstate' in actionbyfile:
2416 2426 f = b'.hgsubstate'
2417 2427 m, args, msg = actionbyfile[f]
2418 2428 prompts = filemerge.partextras(labels)
2419 2429 prompts[b'f'] = f
2420 2430 if m == ACTION_CHANGED_DELETED:
2421 2431 if repo.ui.promptchoice(
2422 2432 _(
2423 2433 b"local%(l)s changed %(f)s which other%(o)s deleted\n"
2424 2434 b"use (c)hanged version or (d)elete?"
2425 2435 b"$$ &Changed $$ &Delete"
2426 2436 )
2427 2437 % prompts,
2428 2438 0,
2429 2439 ):
2430 2440 actionbyfile[f] = (ACTION_REMOVE, None, b'prompt delete')
2431 2441 elif f in p1:
2432 2442 actionbyfile[f] = (
2433 2443 ACTION_ADD_MODIFIED,
2434 2444 None,
2435 2445 b'prompt keep',
2436 2446 )
2437 2447 else:
2438 2448 actionbyfile[f] = (ACTION_ADD, None, b'prompt keep')
2439 2449 elif m == ACTION_DELETED_CHANGED:
2440 2450 f1, f2, fa, move, anc = args
2441 2451 flags = p2[f2].flags()
2442 2452 if (
2443 2453 repo.ui.promptchoice(
2444 2454 _(
2445 2455 b"other%(o)s changed %(f)s which local%(l)s deleted\n"
2446 2456 b"use (c)hanged version or leave (d)eleted?"
2447 2457 b"$$ &Changed $$ &Deleted"
2448 2458 )
2449 2459 % prompts,
2450 2460 0,
2451 2461 )
2452 2462 == 0
2453 2463 ):
2454 2464 actionbyfile[f] = (
2455 2465 ACTION_GET,
2456 2466 (flags, False),
2457 2467 b'prompt recreating',
2458 2468 )
2459 2469 else:
2460 2470 del actionbyfile[f]
2461 2471
2462 2472 # Convert to dictionary-of-lists format
2463 2473 actions = emptyactions()
2464 2474 for f, (m, args, msg) in pycompat.iteritems(actionbyfile):
2465 2475 if m not in actions:
2466 2476 actions[m] = []
2467 2477 actions[m].append((f, args, msg))
2468 2478
2469 2479 if not util.fscasesensitive(repo.path):
2470 2480 # check collision between files only in p2 for clean update
2471 2481 if not branchmerge and (
2472 2482 force or not wc.dirty(missing=True, branch=False)
2473 2483 ):
2474 2484 _checkcollision(repo, p2.manifest(), None)
2475 2485 else:
2476 2486 _checkcollision(repo, wc.manifest(), actions)
2477 2487
2478 2488 # divergent renames
2479 2489 for f, fl in sorted(pycompat.iteritems(diverge)):
2480 2490 repo.ui.warn(
2481 2491 _(
2482 2492 b"note: possible conflict - %s was renamed "
2483 2493 b"multiple times to:\n"
2484 2494 )
2485 2495 % f
2486 2496 )
2487 2497 for nf in sorted(fl):
2488 2498 repo.ui.warn(b" %s\n" % nf)
2489 2499
2490 2500 # rename and delete
2491 2501 for f, fl in sorted(pycompat.iteritems(renamedelete)):
2492 2502 repo.ui.warn(
2493 2503 _(
2494 2504 b"note: possible conflict - %s was deleted "
2495 2505 b"and renamed to:\n"
2496 2506 )
2497 2507 % f
2498 2508 )
2499 2509 for nf in sorted(fl):
2500 2510 repo.ui.warn(b" %s\n" % nf)
2501 2511
2502 2512 ### apply phase
2503 2513 if not branchmerge: # just jump to the new rev
2504 2514 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, b''
2505 2515 # If we're doing a partial update, we need to skip updating
2506 2516 # the dirstate.
2507 2517 always = matcher is None or matcher.always()
2508 2518 updatedirstate = always and not wc.isinmemory()
2509 2519 if updatedirstate:
2510 2520 repo.hook(b'preupdate', throw=True, parent1=xp1, parent2=xp2)
2511 2521 # note that we're in the middle of an update
2512 2522 repo.vfs.write(b'updatestate', p2.hex())
2513 2523
2514 2524 # Advertise fsmonitor when its presence could be useful.
2515 2525 #
2516 2526 # We only advertise when performing an update from an empty working
2517 2527 # directory. This typically only occurs during initial clone.
2518 2528 #
2519 2529 # We give users a mechanism to disable the warning in case it is
2520 2530 # annoying.
2521 2531 #
2522 2532 # We only allow on Linux and MacOS because that's where fsmonitor is
2523 2533 # considered stable.
2524 2534 fsmonitorwarning = repo.ui.configbool(b'fsmonitor', b'warn_when_unused')
2525 2535 fsmonitorthreshold = repo.ui.configint(
2526 2536 b'fsmonitor', b'warn_update_file_count'
2527 2537 )
2528 2538 try:
2529 2539 # avoid cycle: extensions -> cmdutil -> merge
2530 2540 from . import extensions
2531 2541
2532 2542 extensions.find(b'fsmonitor')
2533 2543 fsmonitorenabled = repo.ui.config(b'fsmonitor', b'mode') != b'off'
2534 2544 # We intentionally don't look at whether fsmonitor has disabled
2535 2545 # itself because a) fsmonitor may have already printed a warning
2536 2546 # b) we only care about the config state here.
2537 2547 except KeyError:
2538 2548 fsmonitorenabled = False
2539 2549
2540 2550 if (
2541 2551 fsmonitorwarning
2542 2552 and not fsmonitorenabled
2543 2553 and p1.node() == nullid
2544 2554 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2545 2555 and pycompat.sysplatform.startswith((b'linux', b'darwin'))
2546 2556 ):
2547 2557 repo.ui.warn(
2548 2558 _(
2549 2559 b'(warning: large working directory being used without '
2550 2560 b'fsmonitor enabled; enable fsmonitor to improve performance; '
2551 2561 b'see "hg help -e fsmonitor")\n'
2552 2562 )
2553 2563 )
2554 2564
2555 2565 wantfiledata = updatedirstate and not branchmerge
2556 2566 stats, getfiledata = applyupdates(
2557 2567 repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
2558 2568 )
2559 2569
2560 2570 if updatedirstate:
2561 2571 with repo.dirstate.parentchange():
2562 2572 repo.setparents(fp1, fp2)
2563 2573 recordupdates(repo, actions, branchmerge, getfiledata)
2564 2574 # update completed, clear state
2565 2575 util.unlink(repo.vfs.join(b'updatestate'))
2566 2576
2567 2577 if not branchmerge:
2568 2578 repo.dirstate.setbranch(p2.branch())
2569 2579
2570 2580 # If we're updating to a location, clean up any stale temporary includes
2571 2581 # (ex: this happens during hg rebase --abort).
2572 2582 if not branchmerge:
2573 2583 sparse.prunetemporaryincludes(repo)
2574 2584
2575 2585 if updatedirstate:
2576 2586 repo.hook(
2577 2587 b'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
2578 2588 )
2579 2589 return stats
2580 2590
2581 2591
2582 2592 def graft(
2583 2593 repo, ctx, base, labels=None, keepparent=False, keepconflictparent=False
2584 2594 ):
2585 2595 """Do a graft-like merge.
2586 2596
2587 2597 This is a merge where the merge ancestor is chosen such that one
2588 2598 or more changesets are grafted onto the current changeset. In
2589 2599 addition to the merge, this fixes up the dirstate to include only
2590 2600 a single parent (if keepparent is False) and tries to duplicate any
2591 2601 renames/copies appropriately.
2592 2602
2593 2603 ctx - changeset to rebase
2594 2604 base - merge base, usually ctx.p1()
2595 2605 labels - merge labels eg ['local', 'graft']
2596 2606 keepparent - keep second parent if any
2597 2607 keepconflictparent - if unresolved, keep parent used for the merge
2598 2608
2599 2609 """
2600 2610 # If we're grafting a descendant onto an ancestor, be sure to pass
2601 2611 # mergeancestor=True to update. This does two things: 1) allows the merge if
2602 2612 # the destination is the same as the parent of the ctx (so we can use graft
2603 2613 # to copy commits), and 2) informs update that the incoming changes are
2604 2614 # newer than the destination so it doesn't prompt about "remote changed foo
2605 2615 # which local deleted".
2606 2616 wctx = repo[None]
2607 2617 pctx = wctx.p1()
2608 2618 mergeancestor = repo.changelog.isancestor(pctx.node(), ctx.node())
2609 2619
2610 2620 stats = update(
2611 2621 repo,
2612 2622 ctx.node(),
2613 2623 True,
2614 2624 True,
2615 2625 base.node(),
2616 2626 mergeancestor=mergeancestor,
2617 2627 labels=labels,
2618 2628 )
2619 2629
2620 2630 if keepconflictparent and stats.unresolvedcount:
2621 2631 pother = ctx.node()
2622 2632 else:
2623 2633 pother = nullid
2624 2634 parents = ctx.parents()
2625 2635 if keepparent and len(parents) == 2 and base in parents:
2626 2636 parents.remove(base)
2627 2637 pother = parents[0].node()
2628 2638 # Never set both parents equal to each other
2629 2639 if pother == pctx.node():
2630 2640 pother = nullid
2631 2641
2632 2642 with repo.dirstate.parentchange():
2633 2643 repo.setparents(pctx.node(), pother)
2634 2644 repo.dirstate.write(repo.currenttransaction())
2635 2645 # fix up dirstate for copies and renames
2636 2646 copies.graftcopies(wctx, ctx, base)
2637 2647 return stats
2638 2648
2639 2649
2640 2650 def purge(
2641 2651 repo,
2642 2652 matcher,
2643 2653 ignored=False,
2644 2654 removeemptydirs=True,
2645 2655 removefiles=True,
2646 2656 abortonerror=False,
2647 2657 noop=False,
2648 2658 ):
2649 2659 """Purge the working directory of untracked files.
2650 2660
2651 2661 ``matcher`` is a matcher configured to scan the working directory -
2652 2662 potentially a subset.
2653 2663
2654 2664 ``ignored`` controls whether ignored files should also be purged.
2655 2665
2656 2666 ``removeemptydirs`` controls whether empty directories should be removed.
2657 2667
2658 2668 ``removefiles`` controls whether files are removed.
2659 2669
2660 2670 ``abortonerror`` causes an exception to be raised if an error occurs
2661 2671 deleting a file or directory.
2662 2672
2663 2673 ``noop`` controls whether to actually remove files. If not defined, actions
2664 2674 will be taken.
2665 2675
2666 2676 Returns an iterable of relative paths in the working directory that were
2667 2677 or would be removed.
2668 2678 """
2669 2679
2670 2680 def remove(removefn, path):
2671 2681 try:
2672 2682 removefn(path)
2673 2683 except OSError:
2674 2684 m = _(b'%s cannot be removed') % path
2675 2685 if abortonerror:
2676 2686 raise error.Abort(m)
2677 2687 else:
2678 2688 repo.ui.warn(_(b'warning: %s\n') % m)
2679 2689
2680 2690 # There's no API to copy a matcher. So mutate the passed matcher and
2681 2691 # restore it when we're done.
2682 2692 oldtraversedir = matcher.traversedir
2683 2693
2684 2694 res = []
2685 2695
2686 2696 try:
2687 2697 if removeemptydirs:
2688 2698 directories = []
2689 2699 matcher.traversedir = directories.append
2690 2700
2691 2701 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2692 2702
2693 2703 if removefiles:
2694 2704 for f in sorted(status.unknown + status.ignored):
2695 2705 if not noop:
2696 2706 repo.ui.note(_(b'removing file %s\n') % f)
2697 2707 remove(repo.wvfs.unlink, f)
2698 2708 res.append(f)
2699 2709
2700 2710 if removeemptydirs:
2701 2711 for f in sorted(directories, reverse=True):
2702 2712 if matcher(f) and not repo.wvfs.listdir(f):
2703 2713 if not noop:
2704 2714 repo.ui.note(_(b'removing directory %s\n') % f)
2705 2715 remove(repo.wvfs.rmdir, f)
2706 2716 res.append(f)
2707 2717
2708 2718 return res
2709 2719
2710 2720 finally:
2711 2721 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now