@matheusbsbs / matheus-bagliano / commits / ebdc94f3be
Fork 0

revision: --ancestry-path

"rev-list A..H" computes the set of commits that are ancestors of H, but excludes the ones that are ancestors of A. This is useful to see what happened to the history leading to H since A, in the sense that "what does H have that did not exist in A" (e.g. when you have a choice to update to H from A). x---x---A---B---C <-- topic / \ x---x---x---o---o---o---o---M---D---E---F---G <-- dev / \ x---o---o---o---o---o---o---o---o---o---o---o---N---H <-- master The result in the above example would be the commits marked with caps letters (except for A itself, of course), and the ones marked with 'o'. When you want to find out what commits in H are contaminated with the bug introduced by A and need fixing, however, you might want to view only the subset of "A..B" that are actually descendants of A, i.e. excluding the ones marked with 'o'. Introduce a new option --ancestry-path to compute this set with "rev-list --ancestry-path A..B". Note that in practice, you would build a fix immediately on top of A and "git branch --contains A" will give the names of branches that you would need to merge the fix into (i.e. topic, dev and master), so this may not be worth paying the extra cost of postprocessing. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Apr 20, 2010 at 13:48 UTC ebdc94f3bec7ec54babb21b1d785af0cd75b21e6
3 files changed +159
revision.c
+102
@@ -646,6 +646,93 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl
646 return slop-1;
647 }
648
649 +/*
650 + * "rev-list --ancestry-path A..B" computes commits that are ancestors
651 + * of B but not ancestors of A but further limits the result to those
652 + * that are descendants of A. This takes the list of bottom commits and
653 + * the result of "A..B" without --ancestry-path, and limits the latter
654 + * further to the ones that can reach one of the commits in "bottom".
655 + */
656 +static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *list)
657 +{
658 + struct commit_list *p;
659 + struct commit_list *rlist = NULL;
660 + int made_progress;
661 +
662 + /*
663 + * Reverse the list so that it will be likely that we would
664 + * process parents before children.
665 + */
666 + for (p = list; p; p = p->next)
667 + commit_list_insert(p->item, &rlist);
668 +
669 + for (p = bottom; p; p = p->next)
670 + p->item->object.flags |= TMP_MARK;
671 +
672 + /*
673 + * Mark the ones that can reach bottom commits in "list",
674 + * in a bottom-up fashion.
675 + */
676 + do {
677 + made_progress = 0;
678 + for (p = rlist; p; p = p->next) {
679 + struct commit *c = p->item;
680 + struct commit_list *parents;
681 + if (c->object.flags & (TMP_MARK | UNINTERESTING))
682 + continue;
683 + for (parents = c->parents;
684 + parents;
685 + parents = parents->next) {
686 + if (!(parents->item->object.flags & TMP_MARK))
687 + continue;
688 + c->object.flags |= TMP_MARK;
689 + made_progress = 1;
690 + break;
691 + }
692 + }
693 + } while (made_progress);
694 +
695 + /*
696 + * NEEDSWORK: decide if we want to remove parents that are
697 + * not marked with TMP_MARK from commit->parents for commits
698 + * in the resulting list. We may not want to do that, though.
699 + */
700 +
701 + /*
702 + * The ones that are not marked with TMP_MARK are uninteresting
703 + */
704 + for (p = list; p; p = p->next) {
705 + struct commit *c = p->item;
706 + if (c->object.flags & TMP_MARK)
707 + continue;
708 + c->object.flags |= UNINTERESTING;
709 + }
710 +
711 + /* We are done with the TMP_MARK */
712 + for (p = list; p; p = p->next)
713 + p->item->object.flags &= ~TMP_MARK;
714 + for (p = bottom; p; p = p->next)
715 + p->item->object.flags &= ~TMP_MARK;
716 + free_commit_list(rlist);
717 +}
718 +
719 +/*
720 + * Before walking the history, keep the set of "negative" refs the
721 + * caller has asked to exclude.
722 + *
723 + * This is used to compute "rev-list --ancestry-path A..B", as we need
724 + * to filter the result of "A..B" further to the ones that can actually
725 + * reach A.
726 + */
727 +static struct commit_list *collect_bottom_commits(struct commit_list *list)
728 +{
729 + struct commit_list *elem, *bottom = NULL;
730 + for (elem = list; elem; elem = elem->next)
731 + if (elem->item->object.flags & UNINTERESTING)
732 + commit_list_insert(elem->item, &bottom);
733 + return bottom;
734 +}
735 +
736 static int limit_list(struct rev_info *revs)
737 {
738 int slop = SLOP;
@@ -653,6 +740,13 @@ static int limit_list(struct rev_info *revs)
740 struct commit_list *list = revs->commits;
741 struct commit_list *newlist = NULL;
742 struct commit_list **p = &newlist;
743 + struct commit_list *bottom = NULL;
744 +
745 + if (revs->ancestry_path) {
746 + bottom = collect_bottom_commits(list);
747 + if (!bottom)
748 + die("--ancestry-path given but there is no bottom commits");
749 + }
750
751 while (list) {
752 struct commit_list *entry = list;
@@ -694,6 +788,11 @@ static int limit_list(struct rev_info *revs)
788 if (revs->cherry_pick)
789 cherry_pick_list(newlist, revs);
790
791 + if (bottom) {
792 + limit_to_ancestry(bottom, newlist);
793 + free_commit_list(bottom);
794 + }
795 +
796 revs->commits = newlist;
797 return 0;
798 }
@@ -1089,6 +1188,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
1188 revs->min_age = approxidate(arg + 8);
1189 } else if (!strcmp(arg, "--first-parent")) {
1190 revs->first_parent_only = 1;
1191 + } else if (!strcmp(arg, "--ancestry-path")) {
1192 + revs->ancestry_path = 1;
1193 + revs->limited = 1;
1194 } else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
1195 init_reflog_walk(&revs->reflog_info);
1196 } else if (!strcmp(arg, "--default")) {
revision.h
+1
@@ -66,6 +66,7 @@ struct rev_info {
66 reverse_output_stage:1,
67 cherry_pick:1,
68 bisect:1,
69 + ancestry_path:1,
70 first_parent_only:1;
71
72 /* Diff flags */
t/t6019-rev-list-ancestry-path.sh new
+56
@@ -0,0 +1,56 @@
1 +#!/bin/sh
2 +
3 +test_description='--ancestry-path'
4 +
5 +# D---E-------F
6 +# / \ \
7 +# B---C---G---H---I---J
8 +# / \
9 +# A-------K---------------L--M
10 +#
11 +# D..M == E F G H I J K L M
12 +# --ancestry-path D..M == E F H I J L M
13 +
14 +. ./test-lib.sh
15 +
16 +test_merge () {
17 + test_tick &&
18 + git merge -s ours -m "$2" "$1" &&
19 + git tag "$2"
20 +}
21 +
22 +test_expect_success setup '
23 + test_commit A &&
24 + test_commit B &&
25 + test_commit C &&
26 + test_commit D &&
27 + test_commit E &&
28 + test_commit F &&
29 + git reset --hard C &&
30 + test_commit G &&
31 + test_merge E H &&
32 + test_commit I &&
33 + test_merge F J &&
34 + git reset --hard A &&
35 + test_commit K &&
36 + test_merge J L &&
37 + test_commit M
38 +'
39 +
40 +test_expect_success 'rev-list D..M' '
41 + for c in E F G H I J K L M; do echo $c; done >expect &&
42 + git rev-list --format=%s D..M |
43 + sed -e "/^commit /d" |
44 + sort >actual &&
45 + test_cmp expect actual
46 +'
47 +
48 +test_expect_success 'rev-list --ancestry-path D..M' '
49 + for c in E F H I J L M; do echo $c; done >expect &&
50 + git rev-list --ancestry-path --format=%s D..M |
51 + sed -e "/^commit /d" |
52 + sort >actual &&
53 + test_cmp expect actual
54 +'
55 +
56 +test_done