##// 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 b' def _revinfogetter(repo):'
183 * p1copies: mapping of copies from p1
183 * p1copies: mapping of copies from p1
184 * p2copies: mapping of copies from p2
184 * p2copies: mapping of copies from p2
185 * removed: a list of removed files
185 * removed: a list of removed files
186 * ismerged: a callback to know if file was merged in that revision
186 """
187 """
187 cl = repo.changelog
188 cl = repo.changelog
188 parents = cl.parentrevs
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 if repo.filecopiesmode == b'changeset-sidedata':
207 if repo.filecopiesmode == b'changeset-sidedata':
191 changelogrevision = cl.changelogrevision
208 changelogrevision = cl.changelogrevision
192 flags = cl.flags
209 flags = cl.flags
@@ -218,6 +235,7 b' def _revinfogetter(repo):'
218
235
219 def revinfo(rev):
236 def revinfo(rev):
220 p1, p2 = parents(rev)
237 p1, p2 = parents(rev)
238 value = None
221 if flags(rev) & REVIDX_SIDEDATA:
239 if flags(rev) & REVIDX_SIDEDATA:
222 e = merge_caches.pop(rev, None)
240 e = merge_caches.pop(rev, None)
223 if e is not None:
241 if e is not None:
@@ -228,12 +246,22 b' def _revinfogetter(repo):'
228 removed = c.filesremoved
246 removed = c.filesremoved
229 if p1 != node.nullrev and p2 != node.nullrev:
247 if p1 != node.nullrev and p2 != node.nullrev:
230 # XXX some case we over cache, IGNORE
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 else:
257 else:
233 p1copies = {}
258 p1copies = {}
234 p2copies = {}
259 p2copies = {}
235 removed = []
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 else:
266 else:
239
267
@@ -242,7 +270,7 b' def _revinfogetter(repo):'
242 ctx = repo[rev]
270 ctx = repo[rev]
243 p1copies, p2copies = ctx._copies
271 p1copies, p2copies = ctx._copies
244 removed = ctx.filesremoved()
272 removed = ctx.filesremoved()
245 return p1, p2, p1copies, p2copies, removed
273 return p1, p2, p1copies, p2copies, removed, get_ismerged(rev)
246
274
247 return revinfo
275 return revinfo
248
276
@@ -256,6 +284,7 b' def _changesetforwardcopies(a, b, match)'
256 revinfo = _revinfogetter(repo)
284 revinfo = _revinfogetter(repo)
257
285
258 cl = repo.changelog
286 cl = repo.changelog
287 isancestor = cl.isancestorrev # XXX we should had chaching to this.
259 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
288 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
260 mrset = set(missingrevs)
289 mrset = set(missingrevs)
261 roots = set()
290 roots = set()
@@ -283,10 +312,14 b' def _changesetforwardcopies(a, b, match)'
283 iterrevs.update(roots)
312 iterrevs.update(roots)
284 iterrevs.remove(b.rev())
313 iterrevs.remove(b.rev())
285 revs = sorted(iterrevs)
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 """combine the copies information for each item of iterrevs
323 """combine the copies information for each item of iterrevs
291
324
292 revs: sorted iterable of revision to visit
325 revs: sorted iterable of revision to visit
@@ -305,7 +338,7 b' def _combinechangesetcopies(revs, childr'
305 # this is a root
338 # this is a root
306 copies = {}
339 copies = {}
307 for i, c in enumerate(children[r]):
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 if r == p1:
342 if r == p1:
310 parent = 1
343 parent = 1
311 childcopies = p1copies
344 childcopies = p1copies
@@ -319,9 +352,12 b' def _combinechangesetcopies(revs, childr'
319 }
352 }
320 newcopies = copies
353 newcopies = copies
321 if childcopies:
354 if childcopies:
322 newcopies = _chain(newcopies, childcopies)
355 newcopies = copies.copy()
323 # _chain makes a copies, we can avoid doing so in some
356 for dest, source in pycompat.iteritems(childcopies):
324 # simple/linear cases.
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 assert newcopies is not copies
361 assert newcopies is not copies
326 for f in removed:
362 for f in removed:
327 if f in newcopies:
363 if f in newcopies:
@@ -330,7 +366,7 b' def _combinechangesetcopies(revs, childr'
330 # branches. when there are no other branches, this
366 # branches. when there are no other branches, this
331 # could be avoided.
367 # could be avoided.
332 newcopies = copies.copy()
368 newcopies = copies.copy()
333 del newcopies[f]
369 newcopies[f] = (c, None)
334 othercopies = all_copies.get(c)
370 othercopies = all_copies.get(c)
335 if othercopies is None:
371 if othercopies is None:
336 all_copies[c] = newcopies
372 all_copies[c] = newcopies
@@ -338,21 +374,55 b' def _combinechangesetcopies(revs, childr'
338 # we are the second parent to work on c, we need to merge our
374 # we are the second parent to work on c, we need to merge our
339 # work with the other.
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 # In case of conflict, parent 1 take precedence over parent 2.
377 # In case of conflict, parent 1 take precedence over parent 2.
347 # This is an arbitrary choice made anew when implementing
378 # This is an arbitrary choice made anew when implementing
348 # changeset based copies. It was made without regards with
379 # changeset based copies. It was made without regards with
349 # potential filelog related behavior.
380 # potential filelog related behavior.
350 if parent == 1:
381 if parent == 1:
351 othercopies.update(newcopies)
382 _merge_copies_dict(
383 othercopies, newcopies, isancestor, ismerged
384 )
352 else:
385 else:
353 newcopies.update(othercopies)
386 _merge_copies_dict(
387 newcopies, othercopies, isancestor, ismerged
388 )
354 all_copies[c] = newcopies
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 def _forwardcopies(a, b, base=None, match=None):
428 def _forwardcopies(a, b, base=None, match=None):
@@ -1,3 +1,5 b''
1 #testcases filelog compatibility sidedata
2
1 =====================================================
3 =====================================================
2 Test Copy tracing for chain of copies involving merge
4 Test Copy tracing for chain of copies involving merge
3 =====================================================
5 =====================================================
@@ -6,6 +8,7 b' This test files covers copies/rename cas'
6 are involved. It cheks we do not have unwanted update of behavior and that the
8 are involved. It cheks we do not have unwanted update of behavior and that the
7 different options to retrieve copies behave correctly.
9 different options to retrieve copies behave correctly.
8
10
11
9 Setup
12 Setup
10 =====
13 =====
11
14
@@ -18,6 +21,22 b' use git diff to see rename'
18 > logtemplate={rev} {desc}\n
21 > logtemplate={rev} {desc}\n
19 > EOF
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 $ hg init repo-chain
40 $ hg init repo-chain
22 $ cd repo-chain
41 $ cd repo-chain
23
42
@@ -453,17 +472,26 b' Comparing with a merge with colliding re'
453 0 4 0dd616bc7ab1 000000000000 000000000000
472 0 4 0dd616bc7ab1 000000000000 000000000000
454 1 10 6da5a2eecb9c 000000000000 000000000000
473 1 10 6da5a2eecb9c 000000000000 000000000000
455 2 19 eb806e34ef6b 0dd616bc7ab1 6da5a2eecb9c
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 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAEm-0")'
480 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAEm-0")'
457 M f
481 M f
482 b (no-filelog !)
458 R b
483 R b
459 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEAm-0")'
484 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEAm-0")'
460 M f
485 M f
486 b (no-filelog !)
461 R b
487 R b
462 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAEm-0")'
488 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAEm-0")'
463 M f
489 M f
490 d (no-filelog !)
464 R d
491 R d
465 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEAm-0")'
492 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEAm-0")'
466 M f
493 M f
494 d (no-filelog !)
467 R d
495 R d
468 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")'
496 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")'
469 A f
497 A f
@@ -473,6 +501,18 b' Comparing with a merge with colliding re'
473 A f
501 A f
474 b
502 b
475 R b
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 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mAEm-0")'
516 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mAEm-0")'
477 A f
517 A f
478 d
518 d
@@ -480,7 +520,8 b' Comparing with a merge with colliding re'
480 R d
520 R d
481 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mEAm-0")'
521 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mEAm-0")'
482 A f
522 A f
483 d
523 d (filelog !)
524 b (no-filelog !)
484 R b
525 R b
485 R d
526 R d
486 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm-0")'
527 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm-0")'
@@ -490,7 +531,8 b' Comparing with a merge with colliding re'
490 R b
531 R b
491 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm-0")'
532 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm-0")'
492 A f
533 A f
493 a
534 a (filelog !)
535 b (no-filelog !)
494 R a
536 R a
495 R b
537 R b
496
538
@@ -563,21 +605,25 b' The overwriting should take over. Howeve'
563 R h
605 R h
564 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBFm-0")'
606 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBFm-0")'
565 M d
607 M d
608 h (no-filelog !)
566 R h
609 R h
567 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBFm-0")'
610 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBFm-0")'
568 M b
611 M b
569 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBFm-0")'
612 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBFm-0")'
570 M b
613 M b
571 M d
614 M d
615 i (no-filelog !)
572 R i
616 R i
573 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFBm-0")'
617 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFBm-0")'
574 M d
618 M d
619 h (no-filelog !)
575 R h
620 R h
576 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFBm-0")'
621 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFBm-0")'
577 M b
622 M b
578 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFBm-0")'
623 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFBm-0")'
579 M b
624 M b
580 M d
625 M d
626 i (no-filelog !)
581 R i
627 R i
582
628
583 The following graphlog is wrong, the "a -> c -> d" chain was overwritten and should not appear.
629 The following graphlog is wrong, the "a -> c -> d" chain was overwritten and should not appear.
@@ -645,9 +691,15 b' consider history and rename on both bran'
645 |
691 |
646 o 0 i-0 initial commit: a b h
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 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDGm-0")'
700 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDGm-0")'
649 A d
701 A d
650 a
702 a (filelog !)
651 R a
703 R a
652 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGDm-0")'
704 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGDm-0")'
653 A d
705 A d
@@ -740,7 +792,8 b' Note:'
740
792
741 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm-0")'
793 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm-0")'
742 A d
794 A d
743 a
795 h (no-filelog !)
796 a (filelog !)
744 R a
797 R a
745 R h
798 R h
746 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm-0")'
799 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm-0")'
@@ -754,15 +807,19 b' Note:'
754 M d
807 M d
755 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFGm-0")'
808 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFGm-0")'
756 M d
809 M d
810 i (no-filelog !)
757 R i
811 R i
758 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mGFm-0")'
812 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mGFm-0")'
759 M d
813 M d
814 i (no-filelog !)
760 R i
815 R i
761 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mFGm-0")'
816 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mFGm-0")'
762 M d
817 M d
818 h (no-filelog !)
763 R h
819 R h
764 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGFm-0")'
820 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGFm-0")'
765 M d
821 M d
822 h (no-filelog !)
766 R h
823 R h
767
824
768 $ hg log -Gfr 'desc("mFGm-0")' d
825 $ hg log -Gfr 'desc("mFGm-0")' d
General Comments 0
You need to be logged in to leave comments. Login now