##// END OF EJS Templates
copies: fix the changeset based algorithm regarding merge...
marmoute -
r45252:45f3f35c default
parent child Browse files
Show More
@@ -183,10 +183,27 def _revinfogetter(repo):
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 * ismerged: a callback to know if file was merged in that revision
186 187 """
187 188 cl = repo.changelog
188 189 parents = cl.parentrevs
189 190
191 def get_ismerged(rev):
192 ctx = repo[rev]
193
194 def ismerged(path):
195 if path not in ctx.files():
196 return False
197 fctx = ctx[path]
198 parents = fctx._filelog.parents(fctx._filenode)
199 nb_parents = 0
200 for n in parents:
201 if n != node.nullid:
202 nb_parents += 1
203 return nb_parents >= 2
204
205 return ismerged
206
190 207 if repo.filecopiesmode == b'changeset-sidedata':
191 208 changelogrevision = cl.changelogrevision
192 209 flags = cl.flags
@@ -218,6 +235,7 def _revinfogetter(repo):
218 235
219 236 def revinfo(rev):
220 237 p1, p2 = parents(rev)
238 value = None
221 239 if flags(rev) & REVIDX_SIDEDATA:
222 240 e = merge_caches.pop(rev, None)
223 241 if e is not None:
@@ -228,12 +246,22 def _revinfogetter(repo):
228 246 removed = c.filesremoved
229 247 if p1 != node.nullrev and p2 != node.nullrev:
230 248 # XXX some case we over cache, IGNORE
231 merge_caches[rev] = (p1, p2, p1copies, p2copies, removed)
249 value = merge_caches[rev] = (
250 p1,
251 p2,
252 p1copies,
253 p2copies,
254 removed,
255 get_ismerged(rev),
256 )
232 257 else:
233 258 p1copies = {}
234 259 p2copies = {}
235 260 removed = []
236 return p1, p2, p1copies, p2copies, removed
261
262 if value is None:
263 value = (p1, p2, p1copies, p2copies, removed, get_ismerged(rev))
264 return value
237 265
238 266 else:
239 267
@@ -242,7 +270,7 def _revinfogetter(repo):
242 270 ctx = repo[rev]
243 271 p1copies, p2copies = ctx._copies
244 272 removed = ctx.filesremoved()
245 return p1, p2, p1copies, p2copies, removed
273 return p1, p2, p1copies, p2copies, removed, get_ismerged(rev)
246 274
247 275 return revinfo
248 276
@@ -256,6 +284,7 def _changesetforwardcopies(a, b, match)
256 284 revinfo = _revinfogetter(repo)
257 285
258 286 cl = repo.changelog
287 isancestor = cl.isancestorrev # XXX we should had chaching to this.
259 288 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
260 289 mrset = set(missingrevs)
261 290 roots = set()
@@ -283,10 +312,14 def _changesetforwardcopies(a, b, match)
283 312 iterrevs.update(roots)
284 313 iterrevs.remove(b.rev())
285 314 revs = sorted(iterrevs)
286 return _combinechangesetcopies(revs, children, b.rev(), revinfo, match)
315 return _combinechangesetcopies(
316 revs, children, b.rev(), revinfo, match, isancestor
317 )
287 318
288 319
289 def _combinechangesetcopies(revs, children, targetrev, revinfo, match):
320 def _combinechangesetcopies(
321 revs, children, targetrev, revinfo, match, isancestor
322 ):
290 323 """combine the copies information for each item of iterrevs
291 324
292 325 revs: sorted iterable of revision to visit
@@ -305,7 +338,7 def _combinechangesetcopies(revs, childr
305 338 # this is a root
306 339 copies = {}
307 340 for i, c in enumerate(children[r]):
308 p1, p2, p1copies, p2copies, removed = revinfo(c)
341 p1, p2, p1copies, p2copies, removed, ismerged = revinfo(c)
309 342 if r == p1:
310 343 parent = 1
311 344 childcopies = p1copies
@@ -319,9 +352,12 def _combinechangesetcopies(revs, childr
319 352 }
320 353 newcopies = copies
321 354 if childcopies:
322 newcopies = _chain(newcopies, childcopies)
323 # _chain makes a copies, we can avoid doing so in some
324 # simple/linear cases.
355 newcopies = copies.copy()
356 for dest, source in pycompat.iteritems(childcopies):
357 prev = copies.get(source)
358 if prev is not None and prev[1] is not None:
359 source = prev[1]
360 newcopies[dest] = (c, source)
325 361 assert newcopies is not copies
326 362 for f in removed:
327 363 if f in newcopies:
@@ -330,7 +366,7 def _combinechangesetcopies(revs, childr
330 366 # branches. when there are no other branches, this
331 367 # could be avoided.
332 368 newcopies = copies.copy()
333 del newcopies[f]
369 newcopies[f] = (c, None)
334 370 othercopies = all_copies.get(c)
335 371 if othercopies is None:
336 372 all_copies[c] = newcopies
@@ -338,21 +374,55 def _combinechangesetcopies(revs, childr
338 374 # we are the second parent to work on c, we need to merge our
339 375 # work with the other.
340 376 #
341 # Unlike when copies are stored in the filelog, we consider
342 # it a copy even if the destination already existed on the
343 # other branch. It's simply too expensive to check if the
344 # file existed in the manifest.
345 #
346 377 # In case of conflict, parent 1 take precedence over parent 2.
347 378 # This is an arbitrary choice made anew when implementing
348 379 # changeset based copies. It was made without regards with
349 380 # potential filelog related behavior.
350 381 if parent == 1:
351 othercopies.update(newcopies)
382 _merge_copies_dict(
383 othercopies, newcopies, isancestor, ismerged
384 )
352 385 else:
353 newcopies.update(othercopies)
386 _merge_copies_dict(
387 newcopies, othercopies, isancestor, ismerged
388 )
354 389 all_copies[c] = newcopies
355 return all_copies[targetrev]
390
391 final_copies = {}
392 for dest, (tt, source) in all_copies[targetrev].items():
393 if source is not None:
394 final_copies[dest] = source
395 return final_copies
396
397
398 def _merge_copies_dict(minor, major, isancestor, ismerged):
399 """merge two copies-mapping together, minor and major
400
401 In case of conflict, value from "major" will be picked.
402
403 - `isancestors(low_rev, high_rev)`: callable return True if `low_rev` is an
404 ancestors of `high_rev`,
405
406 - `ismerged(path)`: callable return True if `path` have been merged in the
407 current revision,
408 """
409 for dest, value in major.items():
410 other = minor.get(dest)
411 if other is None:
412 minor[dest] = value
413 else:
414 new_tt = value[0]
415 other_tt = other[0]
416 if value[1] == other[1]:
417 continue
418 # content from "major" wins, unless it is older
419 # than the branch point or there is a merge
420 if (
421 new_tt == other_tt
422 or not isancestor(new_tt, other_tt)
423 or ismerged(dest)
424 ):
425 minor[dest] = value
356 426
357 427
358 428 def _forwardcopies(a, b, base=None, match=None):
@@ -1,3 +1,5
1 #testcases filelog compatibility sidedata
2
1 3 =====================================================
2 4 Test Copy tracing for chain of copies involving merge
3 5 =====================================================
@@ -6,6 +8,7 This test files covers copies/rename cas
6 8 are involved. It cheks we do not have unwanted update of behavior and that the
7 9 different options to retrieve copies behave correctly.
8 10
11
9 12 Setup
10 13 =====
11 14
@@ -18,6 +21,22 use git diff to see rename
18 21 > logtemplate={rev} {desc}\n
19 22 > EOF
20 23
24 #if compatibility
25 $ cat >> $HGRCPATH << EOF
26 > [experimental]
27 > copies.read-from = compatibility
28 > EOF
29 #endif
30
31 #if sidedata
32 $ cat >> $HGRCPATH << EOF
33 > [format]
34 > exp-use-side-data = yes
35 > exp-use-copies-side-data-changeset = yes
36 > EOF
37 #endif
38
39
21 40 $ hg init repo-chain
22 41 $ cd repo-chain
23 42
@@ -453,17 +472,26 Comparing with a merge with colliding re
453 472 0 4 0dd616bc7ab1 000000000000 000000000000
454 473 1 10 6da5a2eecb9c 000000000000 000000000000
455 474 2 19 eb806e34ef6b 0dd616bc7ab1 6da5a2eecb9c
475
476 # Here the filelog based implementation is not looking at the rename
477 # information (because the file exist on both side). However the changelog
478 # based on works fine. We have different output.
479
456 480 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAEm-0")'
457 481 M f
482 b (no-filelog !)
458 483 R b
459 484 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEAm-0")'
460 485 M f
486 b (no-filelog !)
461 487 R b
462 488 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAEm-0")'
463 489 M f
490 d (no-filelog !)
464 491 R d
465 492 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEAm-0")'
466 493 M f
494 d (no-filelog !)
467 495 R d
468 496 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")'
469 497 A f
@@ -473,6 +501,18 Comparing with a merge with colliding re
473 501 A f
474 502 b
475 503 R b
504
505 # From here, we run status against revision where both source file exists.
506 #
507 # The filelog based implementation picks an arbitrary side based on revision
508 # numbers. So the same side "wins" whatever the parents order is. This is
509 # sub-optimal because depending on revision numbers means the result can be
510 # different from one repository to the next.
511 #
512 # The changeset based algorithm use the parent order to break tie on conflicting
513 # information and will have a different order depending on who is p1 and p2.
514 # That order is stable accross repositories. (data from p1 prevails)
515
476 516 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mAEm-0")'
477 517 A f
478 518 d
@@ -480,7 +520,8 Comparing with a merge with colliding re
480 520 R d
481 521 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mEAm-0")'
482 522 A f
483 d
523 d (filelog !)
524 b (no-filelog !)
484 525 R b
485 526 R d
486 527 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm-0")'
@@ -490,7 +531,8 Comparing with a merge with colliding re
490 531 R b
491 532 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm-0")'
492 533 A f
493 a
534 a (filelog !)
535 b (no-filelog !)
494 536 R a
495 537 R b
496 538
@@ -563,21 +605,25 The overwriting should take over. Howeve
563 605 R h
564 606 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBFm-0")'
565 607 M d
608 h (no-filelog !)
566 609 R h
567 610 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBFm-0")'
568 611 M b
569 612 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBFm-0")'
570 613 M b
571 614 M d
615 i (no-filelog !)
572 616 R i
573 617 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFBm-0")'
574 618 M d
619 h (no-filelog !)
575 620 R h
576 621 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFBm-0")'
577 622 M b
578 623 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFBm-0")'
579 624 M b
580 625 M d
626 i (no-filelog !)
581 627 R i
582 628
583 629 The following graphlog is wrong, the "a -> c -> d" chain was overwritten and should not appear.
@@ -645,9 +691,15 consider history and rename on both bran
645 691 |
646 692 o 0 i-0 initial commit: a b h
647 693
694 One side of the merge have a long history with rename. The other side of the
695 merge point to a new file with a smaller history. Each side is "valid".
696
697 (and again the filelog based algorithm only explore one, with a pick based on
698 revision numbers)
699
648 700 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDGm-0")'
649 701 A d
650 a
702 a (filelog !)
651 703 R a
652 704 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGDm-0")'
653 705 A d
@@ -740,7 +792,8 Note:
740 792
741 793 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm-0")'
742 794 A d
743 a
795 h (no-filelog !)
796 a (filelog !)
744 797 R a
745 798 R h
746 799 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm-0")'
@@ -754,15 +807,19 Note:
754 807 M d
755 808 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFGm-0")'
756 809 M d
810 i (no-filelog !)
757 811 R i
758 812 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mGFm-0")'
759 813 M d
814 i (no-filelog !)
760 815 R i
761 816 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mFGm-0")'
762 817 M d
818 h (no-filelog !)
763 819 R h
764 820 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGFm-0")'
765 821 M d
822 h (no-filelog !)
766 823 R h
767 824
768 825 $ hg log -Gfr 'desc("mFGm-0")' d
General Comments 0
You need to be logged in to leave comments. Login now