Show More
@@ -288,40 +288,92 b' def _changesetforwardcopies(a, b, match)' | |||||
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 |
|
|
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 | |
@@ -329,12 +381,12 b' def _changesetforwardcopies(a, b, match)' | |||||
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: |
|
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 | |
@@ -346,69 +398,76 b' def _combine_changeset_copies(' | |||||
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 " |
|
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 = |
|
410 | current_copies = None | |
359 | if copies is None: |
|
|||
360 | # this is a root |
|
|||
361 | copies = {} |
|
|||
362 |
|
411 | |||
363 |
# iterate over all |
|
412 | # iterate over all parents 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 | |
|
417 | remaining_children = children_count.get(parent_rev) | |||
|
418 | if remaining_children is None: | |||
|
419 | continue | |||
|
420 | remaining_children -= 1 | |||
|
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) | |||
368 |
|
426 | |||
369 | # select the right parent → child edge |
|
427 | if copies is None: | |
370 | if r == p1: |
|
428 | # this is a root | |
371 |
|
|
429 | copies = {} | |
372 | if changes is not None: |
|
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 |
|
|
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: |
|
|||
380 | childcopies = { |
|
|||
381 | dst: src for dst, src in childcopies.items() if match(dst) |
|
|||
382 | } |
|
|||
383 |
|
439 | |||
384 | # chain the data in the edge with the existing data |
|
440 | if not alwaysmatch: | |
385 |
|
|
441 | childcopies = { | |
386 | if childcopies: |
|
442 | dst: src | |
387 | newcopies = copies.copy() |
|
443 | for dst, src in childcopies.items() | |
388 | for dest, source in pycompat.iteritems(childcopies): |
|
444 | if match(dst) | |
389 |
|
|
445 | } | |
390 | if prev is not None and prev[1] is not None: |
|
446 | if childcopies: | |
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 |
|
|
449 | prev = copies.get(source) | |
399 |
if |
|
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 |
|
|
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 |
|
|
469 | current_copies = newcopies | |
410 |
|
|
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: | |
@@ -419,17 +478,11 b' def _combine_changeset_copies(' | |||||
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 |
|
|
481 | assert parent == 2 | |
423 |
|
|
482 | current_copies = _merge_copies_dict( | |
424 |
|
|
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 = {} |
@@ -1,6 +1,7 b'' | |||||
1 | use crate::utils::hg_path::HgPath; |
|
1 | use crate::utils::hg_path::HgPath; | |
2 | use crate::utils::hg_path::HgPathBuf; |
|
2 | use crate::utils::hg_path::HgPathBuf; | |
3 | use crate::Revision; |
|
3 | use crate::Revision; | |
|
4 | use crate::NULL_REVISION; | |||
4 |
|
5 | |||
5 | use im_rc::ordmap::DiffItem; |
|
6 | use im_rc::ordmap::DiffItem; | |
6 | use im_rc::ordmap::OrdMap; |
|
7 | use im_rc::ordmap::OrdMap; | |
@@ -328,7 +329,7 b' enum Parent {' | |||||
328 | /// ancestor of another |
|
329 | /// ancestor of another | |
329 | pub fn combine_changeset_copies<A: Fn(Revision, Revision) -> bool, D>( |
|
330 | pub fn combine_changeset_copies<A: Fn(Revision, Revision) -> bool, D>( | |
330 | revs: Vec<Revision>, |
|
331 | revs: Vec<Revision>, | |
331 |
children: HashMap<Revision, |
|
332 | mut children_count: HashMap<Revision, usize>, | |
332 | target_rev: Revision, |
|
333 | target_rev: Revision, | |
333 | rev_info: RevInfoMaker<D>, |
|
334 | rev_info: RevInfoMaker<D>, | |
334 | is_ancestor: &A, |
|
335 | is_ancestor: &A, | |
@@ -337,57 +338,69 b' pub fn combine_changeset_copies<A: Fn(Re' | |||||
337 | let mut oracle = AncestorOracle::new(is_ancestor); |
|
338 | let mut oracle = AncestorOracle::new(is_ancestor); | |
338 |
|
339 | |||
339 | for rev in revs { |
|
340 | for rev in revs { | |
340 | // Retrieve data computed in a previous iteration |
|
341 | let mut d: DataHolder<D> = DataHolder { data: None }; | |
341 | let copies = all_copies.remove(&rev); |
|
342 | let (p1, p2, changes) = rev_info(rev, &mut d); | |
342 | let copies = match copies { |
|
|||
343 | Some(c) => c, |
|
|||
344 | None => TimeStampedPathCopies::default(), // root of the walked set |
|
|||
345 | }; |
|
|||
346 |
|
||||
347 | let current_children = match children.get(&rev) { |
|
|||
348 | Some(c) => c, |
|
|||
349 | None => panic!("inconsistent `revs` and `children`"), |
|
|||
350 | }; |
|
|||
351 |
|
||||
352 | for child in current_children { |
|
|||
353 | // We will chain the copies information accumulated for `rev` with |
|
|||
354 | // the individual copies information for each of its children. |
|
|||
355 | // Creating a new PathCopies for each `rev` → `children` vertex. |
|
|||
356 | let mut d: DataHolder<D> = DataHolder { data: None }; |
|
|||
357 | let (p1, p2, changes) = rev_info(*child, &mut d); |
|
|||
358 |
|
343 | |||
359 | let parent = if rev == p1 { |
|
344 | // We will chain the copies information accumulated for the parent with | |
360 | Parent::FirstParent |
|
345 | // the individual copies information the curent revision. Creating a | |
361 | } else { |
|
346 | // new TimeStampedPath for each `rev` → `children` vertex. | |
362 | assert_eq!(rev, p2); |
|
347 | let mut copies: Option<TimeStampedPathCopies> = None; | |
363 | Parent::SecondParent |
|
348 | if p1 != NULL_REVISION { | |
364 | }; |
|
349 | // Retrieve data computed in a previous iteration | |
365 | let new_copies = |
|
350 | let parent_copies = get_and_clean_parent_copies( | |
366 | add_from_changes(&copies, &changes, parent, *child); |
|
351 | &mut all_copies, | |
|
352 | &mut children_count, | |||
|
353 | p1, | |||
|
354 | ); | |||
|
355 | if let Some(parent_copies) = parent_copies { | |||
|
356 | // combine it with data for that revision | |||
|
357 | let vertex_copies = add_from_changes( | |||
|
358 | &parent_copies, | |||
|
359 | &changes, | |||
|
360 | Parent::FirstParent, | |||
|
361 | rev, | |||
|
362 | ); | |||
|
363 | // keep that data around for potential later combination | |||
|
364 | copies = Some(vertex_copies); | |||
|
365 | } | |||
|
366 | } | |||
|
367 | if p2 != NULL_REVISION { | |||
|
368 | // Retrieve data computed in a previous iteration | |||
|
369 | let parent_copies = get_and_clean_parent_copies( | |||
|
370 | &mut all_copies, | |||
|
371 | &mut children_count, | |||
|
372 | p2, | |||
|
373 | ); | |||
|
374 | if let Some(parent_copies) = parent_copies { | |||
|
375 | // combine it with data for that revision | |||
|
376 | let vertex_copies = add_from_changes( | |||
|
377 | &parent_copies, | |||
|
378 | &changes, | |||
|
379 | Parent::SecondParent, | |||
|
380 | rev, | |||
|
381 | ); | |||
367 |
|
382 | |||
368 | // Merge has two parents needs to combines their copy information. |
|
383 | copies = match copies { | |
369 | // |
|
384 | None => Some(vertex_copies), | |
370 | // If the vertex from the other parent was already processed, we |
|
385 | // Merge has two parents needs to combines their copy | |
371 | // will have a value for the child ready to be used. We need to |
|
386 | // information. | |
372 | // grab it and combine it with the one we already |
|
387 | // | |
373 | // computed. If not we can simply store the newly |
|
388 | // If we got data from both parents, We need to combine | |
374 | // computed data. The processing happening at |
|
389 | // them. | |
375 | // the time of the second parent will take care of combining the |
|
390 | Some(copies) => Some(merge_copies_dict( | |
376 | // two TimeStampedPathCopies instance. |
|
391 | vertex_copies, | |
377 | match all_copies.remove(child) { |
|
392 | copies, | |
378 |
|
|
393 | &changes, | |
379 | all_copies.insert(child, new_copies); |
|
394 | &mut oracle, | |
380 |
|
|
395 | )), | |
381 | Some(other_copies) => { |
|
396 | }; | |
382 | let (minor, major) = match parent { |
|
397 | } | |
383 | Parent::FirstParent => (other_copies, new_copies), |
|
398 | } | |
384 | Parent::SecondParent => (new_copies, other_copies), |
|
399 | match copies { | |
385 | }; |
|
400 | Some(copies) => { | |
386 | let merged_copies = |
|
401 | all_copies.insert(rev, copies); | |
387 | merge_copies_dict(minor, major, &changes, &mut oracle); |
|
402 | } | |
388 | all_copies.insert(child, merged_copies); |
|
403 | _ => {} | |
389 | } |
|
|||
390 | }; |
|
|||
391 | } |
|
404 | } | |
392 | } |
|
405 | } | |
393 |
|
406 | |||
@@ -405,6 +418,32 b' pub fn combine_changeset_copies<A: Fn(Re' | |||||
405 | result |
|
418 | result | |
406 | } |
|
419 | } | |
407 |
|
420 | |||
|
421 | /// fetch previous computed information | |||
|
422 | /// | |||
|
423 | /// If no other children are expected to need this information, we drop it from | |||
|
424 | /// the cache. | |||
|
425 | /// | |||
|
426 | /// If parent is not part of the set we are expected to walk, return None. | |||
|
427 | fn get_and_clean_parent_copies( | |||
|
428 | all_copies: &mut HashMap<Revision, TimeStampedPathCopies>, | |||
|
429 | children_count: &mut HashMap<Revision, usize>, | |||
|
430 | parent_rev: Revision, | |||
|
431 | ) -> Option<TimeStampedPathCopies> { | |||
|
432 | let count = children_count.get_mut(&parent_rev)?; | |||
|
433 | *count -= 1; | |||
|
434 | if *count == 0 { | |||
|
435 | match all_copies.remove(&parent_rev) { | |||
|
436 | Some(c) => Some(c), | |||
|
437 | None => Some(TimeStampedPathCopies::default()), | |||
|
438 | } | |||
|
439 | } else { | |||
|
440 | match all_copies.get(&parent_rev) { | |||
|
441 | Some(c) => Some(c.clone()), | |||
|
442 | None => Some(TimeStampedPathCopies::default()), | |||
|
443 | } | |||
|
444 | } | |||
|
445 | } | |||
|
446 | ||||
408 | /// Combine ChangedFiles with some existing PathCopies information and return |
|
447 | /// Combine ChangedFiles with some existing PathCopies information and return | |
409 | /// the result |
|
448 | /// the result | |
410 | fn add_from_changes( |
|
449 | fn add_from_changes( |
@@ -23,7 +23,7 b' use hg::Revision;' | |||||
23 | pub fn combine_changeset_copies_wrapper( |
|
23 | pub fn combine_changeset_copies_wrapper( | |
24 | py: Python, |
|
24 | py: Python, | |
25 | revs: PyList, |
|
25 | revs: PyList, | |
26 | children: PyDict, |
|
26 | children_count: PyDict, | |
27 | target_rev: Revision, |
|
27 | target_rev: Revision, | |
28 | rev_info: PyObject, |
|
28 | rev_info: PyObject, | |
29 | is_ancestor: PyObject, |
|
29 | is_ancestor: PyObject, | |
@@ -93,20 +93,15 b' pub fn combine_changeset_copies_wrapper(' | |||||
93 |
|
93 | |||
94 | (p1, p2, files) |
|
94 | (p1, p2, files) | |
95 | }); |
|
95 | }); | |
96 | let children: PyResult<_> = children |
|
96 | let children_count: PyResult<_> = children_count | |
97 | .items(py) |
|
97 | .items(py) | |
98 | .iter() |
|
98 | .iter() | |
99 | .map(|(k, v)| { |
|
99 | .map(|(k, v)| Ok((k.extract(py)?, v.extract(py)?))) | |
100 | let v: &PyList = v.cast_as(py)?; |
|
|||
101 | let v: PyResult<_> = |
|
|||
102 | v.iter(py).map(|child| Ok(child.extract(py)?)).collect(); |
|
|||
103 | Ok((k.extract(py)?, v?)) |
|
|||
104 | }) |
|
|||
105 | .collect(); |
|
100 | .collect(); | |
106 |
|
101 | |||
107 | let res = combine_changeset_copies( |
|
102 | let res = combine_changeset_copies( | |
108 | revs?, |
|
103 | revs?, | |
109 | children?, |
|
104 | children_count?, | |
110 | target_rev, |
|
105 | target_rev, | |
111 | rev_info_maker, |
|
106 | rev_info_maker, | |
112 | &is_ancestor_wrap, |
|
107 | &is_ancestor_wrap, |
General Comments 0
You need to be logged in to leave comments.
Login now