mercurial/copies.py
changeset 46149 294d5aca4ff5
parent 46148 70a9eb899637
child 46150 a132aa5979ec
equal deleted inserted replaced
46148:70a9eb899637 46149:294d5aca4ff5
   286     repo = a.repo().unfiltered()
   286     repo = a.repo().unfiltered()
   287     children = {}
   287     children = {}
   288 
   288 
   289     cl = repo.changelog
   289     cl = repo.changelog
   290     isancestor = cl.isancestorrev
   290     isancestor = cl.isancestorrev
   291     missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
   291 
   292     mrset = set(missingrevs)
   292     # To track rename from "A" to B, we need to gather all parent → children
       
   293     # edges that are contains in `::B` but not in `::A`.
       
   294     #
       
   295     #
       
   296     # To do so, we need to gather all revisions exclusive¹ to "B" (ie¹: `::b -
       
   297     # ::a`) and also all the "roots point", ie the parents of the exclusive set
       
   298     # that belong to ::a. These are exactly all the revisions needed to express
       
   299     # the parent → children we need to combine.
       
   300     #
       
   301     # [1] actually, we need to gather all the edges within `(::a)::b`, ie:
       
   302     # excluding paths that leads to roots that are not ancestors of `a`. We
       
   303     # keep this out of the explanation because it is hard enough without this special case..
       
   304 
       
   305     parents = cl._uncheckedparentrevs
       
   306     graph_roots = (nullrev, nullrev)
       
   307 
       
   308     ancestors = cl.ancestors([a.rev()], inclusive=True)
       
   309     revs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
   293     roots = set()
   310     roots = set()
   294     for r in missingrevs:
   311     has_graph_roots = False
   295         for p in cl.parentrevs(r):
   312 
   296             if p == nullrev:
   313     # iterate over `only(B, A)`
   297                 continue
   314     for r in revs:
   298             if p not in children:
   315         ps = parents(r)
   299                 children[p] = [r]
   316         if ps == graph_roots:
   300             else:
   317             has_graph_roots = True
   301                 children[p].append(r)
   318         else:
   302             if p not in mrset:
   319             p1, p2 = ps
   303                 roots.add(p)
   320 
       
   321             # find all the "root points" (see larger comment above)
       
   322             if p1 != nullrev and p1 in ancestors:
       
   323                 roots.add(p1)
       
   324             if p2 != nullrev and p2 in ancestors:
       
   325                 roots.add(p2)
   304     if not roots:
   326     if not roots:
   305         # no common revision to track copies from
   327         # no common revision to track copies from
   306         return {}
   328         return {}
   307     min_root = min(roots)
   329     if has_graph_roots:
   308 
   330         # this deal with the special case mentionned in the [1] footnotes. We
   309     from_head = set(
   331         # must filter out revisions that leads to non-common graphroots.
   310         cl.reachableroots(min_root, [b.rev()], list(roots), includepath=True)
   332         roots = list(roots)
   311     )
   333         m = min(roots)
   312 
   334         h = [b.rev()]
   313     iterrevs = set(from_head)
   335         roots_to_head = cl.reachableroots(m, h, roots, includepath=True)
   314     iterrevs &= mrset
   336         roots_to_head = set(roots_to_head)
   315     iterrevs.update(roots)
   337         revs = [r for r in revs if r in roots_to_head]
   316     iterrevs.remove(b.rev())
       
   317     revs = sorted(iterrevs)
       
   318 
   338 
   319     if repo.filecopiesmode == b'changeset-sidedata':
   339     if repo.filecopiesmode == b'changeset-sidedata':
       
   340         # When using side-data, we will process the edges "from" the children.
       
   341         # We iterate over the childre, gathering previous collected data for
       
   342         # the parents. Do know when the parents data is no longer necessary, we
       
   343         # keep a counter of how many children each revision has.
       
   344         #
       
   345         # An interresting property of `children_count` is that it only contains
       
   346         # revision that will be relevant for a edge of the graph. So if a
       
   347         # children has parent not in `children_count`, that edges should not be
       
   348         # processed.
       
   349         children_count = dict((r, 0) for r in roots)
       
   350         for r in revs:
       
   351             for p in cl.parentrevs(r):
       
   352                 if p == nullrev:
       
   353                     continue
       
   354                 children_count[r] = 0
       
   355                 if p in children_count:
       
   356                     children_count[p] += 1
   320         revinfo = _revinfo_getter(repo, match)
   357         revinfo = _revinfo_getter(repo, match)
   321         return _combine_changeset_copies(
   358         return _combine_changeset_copies(
   322             revs, children, b.rev(), revinfo, match, isancestor
   359             revs, children_count, b.rev(), revinfo, match, isancestor
   323         )
   360         )
   324     else:
   361     else:
       
   362         # When not using side-data, we will process the edges "from" the parent.
       
   363         # so we need a full mapping of the parent -> children relation.
       
   364         children = dict((r, []) for r in roots)
       
   365         for r in revs:
       
   366             for p in cl.parentrevs(r):
       
   367                 if p == nullrev:
       
   368                     continue
       
   369                 children[r] = []
       
   370                 if p in children:
       
   371                     children[p].append(r)
       
   372         x = revs.pop()
       
   373         assert x == b.rev()
       
   374         revs.extend(roots)
       
   375         revs.sort()
       
   376 
   325         revinfo = _revinfo_getter_extra(repo)
   377         revinfo = _revinfo_getter_extra(repo)
   326         return _combine_changeset_copies_extra(
   378         return _combine_changeset_copies_extra(
   327             revs, children, b.rev(), revinfo, match, isancestor
   379             revs, children, b.rev(), revinfo, match, isancestor
   328         )
   380         )
   329 
   381 
   330 
   382 
   331 def _combine_changeset_copies(
   383 def _combine_changeset_copies(
   332     revs, children, targetrev, revinfo, match, isancestor
   384     revs, children_count, targetrev, revinfo, match, isancestor
   333 ):
   385 ):
   334     """combine the copies information for each item of iterrevs
   386     """combine the copies information for each item of iterrevs
   335 
   387 
   336     revs: sorted iterable of revision to visit
   388     revs: sorted iterable of revision to visit
   337     children: a {parent: [children]} mapping.
   389     children_count: a {parent: <number-of-relevant-children>} mapping.
   338     targetrev: the final copies destination revision (not in iterrevs)
   390     targetrev: the final copies destination revision (not in iterrevs)
   339     revinfo(rev): a function that return (p1, p2, p1copies, p2copies, removed)
   391     revinfo(rev): a function that return (p1, p2, p1copies, p2copies, removed)
   340     match: a matcher
   392     match: a matcher
   341 
   393 
   342     It returns the aggregated copies information for `targetrev`.
   394     It returns the aggregated copies information for `targetrev`.
   344 
   396 
   345     alwaysmatch = match.always()
   397     alwaysmatch = match.always()
   346 
   398 
   347     if rustmod is not None and alwaysmatch:
   399     if rustmod is not None and alwaysmatch:
   348         return rustmod.combine_changeset_copies(
   400         return rustmod.combine_changeset_copies(
   349             list(revs), children, targetrev, revinfo, isancestor
   401             list(revs), children_count, targetrev, revinfo, isancestor
   350         )
   402         )
   351 
   403 
   352     isancestor = cached_is_ancestor(isancestor)
   404     isancestor = cached_is_ancestor(isancestor)
   353 
   405 
   354     all_copies = {}
   406     all_copies = {}
   355     # iterate over all the "parent" side of copy tracing "edge"
   407     # iterate over all the "children" side of copy tracing "edge"
   356     for r in revs:
   408     for current_rev in revs:
   357         # fetch potential previously computed data for that parent
   409         p1, p2, changes = revinfo(current_rev)
   358         copies = all_copies.pop(r, None)
   410         current_copies = None
   359         if copies is None:
   411 
   360             # this is a root
   412         # iterate over all parents to chain the existing data with the
   361             copies = {}
       
   362 
       
   363         # iterate over all known children to chain the existing data with the
       
   364         # data from the parent → child edge.
   413         # data from the parent → child edge.
   365         for i, c in enumerate(children[r]):
   414         for parent, parent_rev in ((1, p1), (2, p2)):
   366             p1, p2, changes = revinfo(c)
   415             if parent_rev == nullrev:
   367             childcopies = {}
   416                 continue
   368 
   417             remaining_children = children_count.get(parent_rev)
   369             # select the right parent → child edge
   418             if remaining_children is None:
   370             if r == p1:
   419                 continue
   371                 parent = 1
   420             remaining_children -= 1
   372                 if changes is not None:
   421             children_count[parent_rev] = remaining_children
       
   422             if remaining_children:
       
   423                 copies = all_copies.get(parent_rev, None)
       
   424             else:
       
   425                 copies = all_copies.pop(parent_rev, None)
       
   426 
       
   427             if copies is None:
       
   428                 # this is a root
       
   429                 copies = {}
       
   430 
       
   431             newcopies = copies
       
   432             # chain the data in the edge with the existing data
       
   433             if changes is not None:
       
   434                 childcopies = {}
       
   435                 if parent == 1:
   373                     childcopies = changes.copied_from_p1
   436                     childcopies = changes.copied_from_p1
   374             else:
   437                 elif parent == 2:
   375                 assert r == p2
       
   376                 parent = 2
       
   377                 if changes is not None:
       
   378                     childcopies = changes.copied_from_p2
   438                     childcopies = changes.copied_from_p2
   379             if not alwaysmatch:
   439 
   380                 childcopies = {
   440                 if not alwaysmatch:
   381                     dst: src for dst, src in childcopies.items() if match(dst)
   441                     childcopies = {
   382                 }
   442                         dst: src
   383 
   443                         for dst, src in childcopies.items()
   384             # chain the data in the edge with the existing data
   444                         if match(dst)
   385             newcopies = copies
   445                     }
   386             if childcopies:
   446                 if childcopies:
   387                 newcopies = copies.copy()
       
   388                 for dest, source in pycompat.iteritems(childcopies):
       
   389                     prev = copies.get(source)
       
   390                     if prev is not None and prev[1] is not None:
       
   391                         source = prev[1]
       
   392                     newcopies[dest] = (c, source)
       
   393                 assert newcopies is not copies
       
   394             if changes is not None and changes.removed:
       
   395                 if newcopies is copies:
       
   396                     newcopies = copies.copy()
   447                     newcopies = copies.copy()
   397                 for f in changes.removed:
   448                     for dest, source in pycompat.iteritems(childcopies):
   398                     if f in newcopies:
   449                         prev = copies.get(source)
   399                         if newcopies is copies:
   450                         if prev is not None and prev[1] is not None:
   400                             # copy on write to avoid affecting potential other
   451                             source = prev[1]
   401                             # branches.  when there are no other branches, this
   452                         newcopies[dest] = (current_rev, source)
   402                             # could be avoided.
   453                     assert newcopies is not copies
   403                             newcopies = copies.copy()
   454                 if changes.removed:
   404                         newcopies[f] = (c, None)
   455                     if newcopies is copies:
       
   456                         newcopies = copies.copy()
       
   457                     for f in changes.removed:
       
   458                         if f in newcopies:
       
   459                             if newcopies is copies:
       
   460                                 # copy on write to avoid affecting potential other
       
   461                                 # branches.  when there are no other branches, this
       
   462                                 # could be avoided.
       
   463                                 newcopies = copies.copy()
       
   464                             newcopies[f] = (current_rev, None)
   405 
   465 
   406             # check potential need to combine the data from another parent (for
   466             # check potential need to combine the data from another parent (for
   407             # that child). See comment below for details.
   467             # that child). See comment below for details.
   408             othercopies = all_copies.get(c)
   468             if current_copies is None:
   409             if othercopies is None:
   469                 current_copies = newcopies
   410                 all_copies[c] = newcopies
   470             elif current_copies is newcopies:
   411             elif newcopies is othercopies:
       
   412                 # nothing to merge:
   471                 # nothing to merge:
   413                 pass
   472                 pass
   414             else:
   473             else:
   415                 # we are the second parent to work on c, we need to merge our
   474                 # we are the second parent to work on c, we need to merge our
   416                 # work with the other.
   475                 # work with the other.
   417                 #
   476                 #
   418                 # In case of conflict, parent 1 take precedence over parent 2.
   477                 # In case of conflict, parent 1 take precedence over parent 2.
   419                 # This is an arbitrary choice made anew when implementing
   478                 # This is an arbitrary choice made anew when implementing
   420                 # changeset based copies. It was made without regards with
   479                 # changeset based copies. It was made without regards with
   421                 # potential filelog related behavior.
   480                 # potential filelog related behavior.
   422                 if parent == 1:
   481                 assert parent == 2
   423                     if newcopies is copies:
   482                 current_copies = _merge_copies_dict(
   424                         newcopies = copies.copy()
   483                     newcopies, current_copies, isancestor, changes
   425                     minor, major = othercopies, newcopies
   484                 )
   426                 else:
   485         all_copies[current_rev] = current_copies
   427                     # we do not know if the other dict is a copy or not, so we
       
   428                     # need to blindly copy it. Future change should make this
       
   429                     # unnecessary.
       
   430                     minor, major = newcopies, othercopies.copy()
       
   431                 copies = _merge_copies_dict(minor, major, isancestor, changes)
       
   432                 all_copies[c] = copies
       
   433 
   486 
   434     # filter out internal details and return a {dest: source mapping}
   487     # filter out internal details and return a {dest: source mapping}
   435     final_copies = {}
   488     final_copies = {}
   436     for dest, (tt, source) in all_copies[targetrev].items():
   489     for dest, (tt, source) in all_copies[targetrev].items():
   437         if source is not None:
   490         if source is not None: