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: |