All of lore.kernel.org
 help / color / mirror / Atom feed
* Getting 'git log' (or something else) to show me the relevant sub-graph?
@ 2010-04-20 14:49 Johan Herland
  2010-04-20 15:18 ` Michael J Gruber
  2010-04-20 20:45 ` Junio C Hamano
  0 siblings, 2 replies; 11+ messages in thread
From: Johan Herland @ 2010-04-20 14:49 UTC (permalink / raw)
  To: git

Hi,

Consider the following (simplified) history:

---O---O---O---O---O---O---O---O---O---O---O---O---M2--H  <-- mainline
    \                                             /
     O---O---O---O---O---O---O---M1--D---E---F---G  <-- dev-branch
              \                 /
               O---O---A---B---C  <-- topic-branch

Now, assume that I have bisected my way through to 'A', and found that 
it introduces some bug. Now, I'm interested in visualizing the path 
that this bug "travelled" to get into "mainline", i.e. the following 
sub-graph:

                          --M2--H  <-- mainline
                           /
        --M1--D---E---F---G  <-- dev-branch
         /
A---B---C  <-- topic-branch

In other words, I'm interested in the following log (with decorations):

H (mainline)
M2
G (dev-branch)
F
E
D
M1
C (topic-branch)
B
A

I have unsuccessfully dug through the 'git log' documentation to figure 
out if it can produce this log, so I'm now throwing the question to the 
almighty knowledge of the mailing list...

Here are some of my closest attempts, so far:

- git branch --contains A
	gives me "topic-branch", "dev-branch" and "mainline", which is
	relevant, but incomplete.

- git log --oneline --decorate --graph A^..mainline
	gives me a log/graph where I can search for A and then use the graph
	to trace the way back up to "mainline", but it still displays a lot of
	uninteresting commits (ancestors of M1 and M2) that I have to
	disregard. Although this is ok once in a while, the problem is common
	enough (and the real-world graphs complicated enough), that I'd like a
	better solution, if possible.

I guess what I'm looking for is something similar to --first-parent, 
except instead of the _first_ parent, it should follow the _relevant_ 
parent, as far as the relationship between A and "mainline" is 
concerned.

In set-theory terms I guess what I want is "that which is both an 
ancestor of H, and a descendant of A (inclusive)", but I don't know how 
to explain this to 'git log'.


Thanks for any help,

...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Getting 'git log' (or something else) to show me the relevant sub-graph?
  2010-04-20 14:49 Getting 'git log' (or something else) to show me the relevant sub-graph? Johan Herland
@ 2010-04-20 15:18 ` Michael J Gruber
  2010-04-20 20:45 ` Junio C Hamano
  1 sibling, 0 replies; 11+ messages in thread
From: Michael J Gruber @ 2010-04-20 15:18 UTC (permalink / raw)
  To: Johan Herland; +Cc: git

Johan Herland venit, vidit, dixit 20.04.2010 16:49:
> Hi,
> 
> Consider the following (simplified) history:
> 
> ---O---O---O---O---O---O---O---O---O---O---O---O---M2--H  <-- mainline
>     \                                             /
>      O---O---O---O---O---O---O---M1--D---E---F---G  <-- dev-branch
>               \                 /
>                O---O---A---B---C  <-- topic-branch
> 
> Now, assume that I have bisected my way through to 'A', and found that 
> it introduces some bug. Now, I'm interested in visualizing the path 
> that this bug "travelled" to get into "mainline", i.e. the following 
> sub-graph:
> 
>                           --M2--H  <-- mainline
>                            /
>         --M1--D---E---F---G  <-- dev-branch
>          /
> A---B---C  <-- topic-branch
> 
> In other words, I'm interested in the following log (with decorations):
> 
> H (mainline)
> M2
> G (dev-branch)
> F
> E
> D
> M1
> C (topic-branch)
> B
> A
> 
> I have unsuccessfully dug through the 'git log' documentation to figure 
> out if it can produce this log, so I'm now throwing the question to the 
> almighty knowledge of the mailing list...
> 
> Here are some of my closest attempts, so far:
> 
> - git branch --contains A
> 	gives me "topic-branch", "dev-branch" and "mainline", which is
> 	relevant, but incomplete.
> 
> - git log --oneline --decorate --graph A^..mainline
> 	gives me a log/graph where I can search for A and then use the graph
> 	to trace the way back up to "mainline", but it still displays a lot of
> 	uninteresting commits (ancestors of M1 and M2) that I have to
> 	disregard. Although this is ok once in a while, the problem is common
> 	enough (and the real-world graphs complicated enough), that I'd like a
> 	better solution, if possible.
> 
> I guess what I'm looking for is something similar to --first-parent, 
> except instead of the _first_ parent, it should follow the _relevant_ 
> parent, as far as the relationship between A and "mainline" is 
> concerned.
> 
> In set-theory terms I guess what I want is "that which is both an 
> ancestor of H, and a descendant of A (inclusive)", but I don't know how 
> to explain this to 'git log'.

This would require a reverse tree walker for the first half of that set
intersection. It would come in handy in many situations, but we don't
have it.

Mathematically speaking it is trivial to associate to a directed graph
the graph with opposite orientation. Then we could walk as usual...

But, in fact, we do not "walk then intersect", but the walker implements
the intersection, so having that other dag would not even help.

Maybe we can teach the walker to mark commits as uninteresting which do
not have A in their ancestry?

Michael

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: Getting 'git log' (or something else) to show me the relevant sub-graph?
  2010-04-20 14:49 Getting 'git log' (or something else) to show me the relevant sub-graph? Johan Herland
  2010-04-20 15:18 ` Michael J Gruber
@ 2010-04-20 20:45 ` Junio C Hamano
  2010-04-20 22:10   ` [PATCH] revision: --ancestry-path Junio C Hamano
  1 sibling, 1 reply; 11+ messages in thread
From: Junio C Hamano @ 2010-04-20 20:45 UTC (permalink / raw)
  To: Johan Herland; +Cc: git

Johan Herland <johan@herland.net> writes:

> Now, assume that I have bisected my way through to 'A', and found that 
> it introduces some bug. Now, I'm interested in visualizing the path 
> that this bug "travelled" to get into "mainline", i.e. the following 
> sub-graph:
>
>                           --M2--H  <-- mainline
>                            /
>         --M1--D---E---F---G  <-- dev-branch
>          /
> A---B---C  <-- topic-branch

Interesting.  I am looking into it ;-)

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH] revision: --ancestry-path
  2010-04-20 20:45 ` Junio C Hamano
@ 2010-04-20 22:10   ` Junio C Hamano
  2010-04-21  7:34     ` Johan Herland
  0 siblings, 1 reply; 11+ messages in thread
From: Junio C Hamano @ 2010-04-20 22:10 UTC (permalink / raw)
  To: Johan Herland; +Cc: git, Michael J Gruber

This is somewhat a rough-cut, as I haven't thought through possible
interactions this new option may have with "rev-list --parents" and
"rev-list --graph" (see "NEEDSWORK" in revision.c), but I'll leave that to
people who are more interested in this topic than myself ;-).

-- >8 --
"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>
---
 revision.c                        |  106 +++++++++++++++++++++++++++++++++++++
 revision.h                        |    1 +
 t/t6019-rev-list-ancestry-path.sh |   56 +++++++++++++++++++
 3 files changed, 163 insertions(+), 0 deletions(-)
 create mode 100755 t/t6019-rev-list-ancestry-path.sh

diff --git a/revision.c b/revision.c
index f4b8b38..cb91094 100644
--- a/revision.c
+++ b/revision.c
@@ -646,6 +646,99 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl
 	return slop-1;
 }
 
+/*
+ * "rev-list --ancestry-path A..B" computes commits that are ancestors
+ * of B but not ancestors of A but further limits the result to those
+ * that are descendants of A.  This takes the list of bottom commits and
+ * the result of "A..B" without --ancestry-path, and limits the latter
+ * further to the ones that can reach one of the commits in "bottom".
+ */
+static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *list)
+{
+	struct commit_list *p;
+	struct commit_list *rlist = NULL;
+	int made_progress;
+
+	/*
+	 * Clear TMP_MARK and reverse the list so that it will be likely
+	 * that we would process parents before children.
+	 */
+	for (p = list; p; p = p->next) {
+		p->item->object.flags &= ~TMP_MARK;
+		commit_list_insert(p->item, &rlist);
+	}
+
+	for (p = bottom; p; p = p->next)
+		p->item->object.flags |= TMP_MARK;
+
+	/*
+	 * Mark the ones that can reach bottom commits in "list",
+	 * in a bottom-up fashion.
+	 */
+	do {
+		made_progress = 0;
+		for (p = rlist; p; p = p->next) {
+			struct commit *c = p->item;
+			struct commit_list *parents;
+			if (c->object.flags & (TMP_MARK | UNINTERESTING))
+				continue;
+			for (parents = c->parents;
+			     parents;
+			     parents = parents->next) {
+				if (!(parents->item->object.flags & TMP_MARK))
+					continue;
+				c->object.flags |= TMP_MARK;
+				made_progress = 1;
+				break;
+			}
+		}
+	} while (made_progress);
+
+	/*
+	 * NEEDSWORK: decide if we want to remove parents that are
+	 * not marked with TMP_MARK from commit->parents for commits
+	 * in the resulting list.  We may not want to do that, though.
+	 */
+
+	/*
+	 * The ones that are not marked with TMP_MARK are uninteresting
+	 */
+	for (p = list; p; p = p->next) {
+		struct commit *c = p->item;
+		if (c->object.flags & TMP_MARK) {
+			c->object.flags &= ~TMP_MARK;
+			continue;
+		}
+		c->object.flags |= UNINTERESTING;
+	}
+
+	/* Release the bottom list */
+	while (bottom) {
+		p = bottom->next;
+		bottom->item->object.flags &= ~TMP_MARK;
+		free(bottom);
+		bottom = p;
+	}
+	free_commit_list(rlist);
+}
+
+/*
+ * Before walking the history, keep the set of "negative" refs the
+ * caller has asked to exclude.
+ *
+ * This is used to compute "rev-list --ancestry-path A..B", as we need
+ * to filter the result of "A..B" further to the ones that can actually
+ * reach A.
+ */
+static struct commit_list *collect_bottom_commits(struct commit_list *list)
+{
+	struct commit_list *elem, *bottom = NULL;
+	for (elem = list; elem; elem = elem->next)
+		if (elem->item->object.flags & UNINTERESTING)
+			commit_list_insert(elem->item, &bottom);
+	return bottom;
+}
+
 static int limit_list(struct rev_info *revs)
 {
 	int slop = SLOP;
@@ -653,6 +746,13 @@ static int limit_list(struct rev_info *revs)
 	struct commit_list *list = revs->commits;
 	struct commit_list *newlist = NULL;
 	struct commit_list **p = &newlist;
+	struct commit_list *bottom = NULL;
+
+	if (revs->ancestry_path) {
+		bottom = collect_bottom_commits(list);
+		if (!bottom)
+			die("--ancestry-path given but there is no bottom commits");
+	}
 
 	while (list) {
 		struct commit_list *entry = list;
@@ -694,6 +794,9 @@ static int limit_list(struct rev_info *revs)
 	if (revs->cherry_pick)
 		cherry_pick_list(newlist, revs);
 
+	if (bottom)
+		limit_to_ancestry(bottom, newlist);
+
 	revs->commits = newlist;
 	return 0;
 }
@@ -1089,6 +1192,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->min_age = approxidate(arg + 8);
 	} else if (!strcmp(arg, "--first-parent")) {
 		revs->first_parent_only = 1;
+	} else if (!strcmp(arg, "--ancestry-path")) {
+		revs->ancestry_path = 1;
+		revs->limited = 1;
 	} else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
 		init_reflog_walk(&revs->reflog_info);
 	} else if (!strcmp(arg, "--default")) {
diff --git a/revision.h b/revision.h
index 568f1c9..855464f 100644
--- a/revision.h
+++ b/revision.h
@@ -66,6 +66,7 @@ struct rev_info {
 			reverse_output_stage:1,
 			cherry_pick:1,
 			bisect:1,
+			ancestry_path:1,
 			first_parent_only:1;
 
 	/* Diff flags */
diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh
new file mode 100755
index 0000000..0230724
--- /dev/null
+++ b/t/t6019-rev-list-ancestry-path.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+test_description='--ancestry-path'
+
+#          D---E-------F
+#         /     \       \
+#    B---C---G---H---I---J
+#   /                     \
+#  A-------K---------------L--M
+#
+#  D..M                 == E F G H I J K L M
+#  --ancestry-path D..M == E F H I J L M
+
+. ./test-lib.sh
+
+test_merge () {
+	test_tick &&
+	git merge -s ours -m "$2" "$1" &&
+	git tag "$2"
+}
+
+test_expect_success setup '
+	test_commit A &&
+	test_commit B &&
+	test_commit C &&
+	test_commit D &&
+	test_commit E &&
+	test_commit F &&
+	git reset --hard C &&
+	test_commit G &&
+	test_merge E H &&
+	test_commit I &&
+	test_merge F J &&
+	git reset --hard A &&
+	test_commit K &&
+	test_merge J L &&
+	test_commit M
+'
+
+test_expect_success 'rev-list D..M' '
+	for c in E F G H I J K L M; do echo $c; done >expect &&
+	git rev-list --format=%s D..M |
+	sed -e "/^commit /d" |
+	sort >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path D..M' '
+	for c in E F H I J L M; do echo $c; done >expect &&
+	git rev-list --ancestry-path --format=%s D..M |
+	sed -e "/^commit /d" |
+	sort >actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
1.7.1.rc2.265.g8743f

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH] revision: --ancestry-path
  2010-04-20 22:10   ` [PATCH] revision: --ancestry-path Junio C Hamano
@ 2010-04-21  7:34     ` Johan Herland
  2010-04-21  7:47       ` Johannes Sixt
  0 siblings, 1 reply; 11+ messages in thread
From: Johan Herland @ 2010-04-21  7:34 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Michael J Gruber

On Wednesday 21 April 2010, Junio C Hamano wrote:
> This is somewhat a rough-cut, as I haven't thought through possible
> interactions this new option may have with "rev-list --parents" and
> "rev-list --graph" (see "NEEDSWORK" in revision.c), but I'll leave that
> to people who are more interested in this topic than myself ;-).

Thanks! :)

I can confirm that this patch works with my original (more complicated) 
scenario. I've also played around with combinations of --ancestry-path, --
graph and --parents (and even --boundary), and AFAICS, the new option does 
not clobber the other options, and (IMHO) it all behaves correctly in the 
scenarios I've tested. Consider it:

Tested-by: Johan Herland <johan@herland.net>

I've also reviewed the patch, and it all looks good to me (i.e. "Acked-by", 
except I'm not super-familiar in the relevant area of the code).

What more should I do to push this forwards towards 'next'?


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] revision: --ancestry-path
  2010-04-21  7:34     ` Johan Herland
@ 2010-04-21  7:47       ` Johannes Sixt
  2010-04-21  8:01         ` Junio C Hamano
  0 siblings, 1 reply; 11+ messages in thread
From: Johannes Sixt @ 2010-04-21  7:47 UTC (permalink / raw)
  To: Johan Herland; +Cc: Junio C Hamano, git, Michael J Gruber

Am 4/21/2010 9:34, schrieb Johan Herland:
> I can confirm that this patch works with my original (more complicated) 
> scenario. I've also played around with combinations of --ancestry-path, --
> graph and --parents (and even --boundary), and AFAICS, the new option does 
> not clobber the other options, and (IMHO) it all behaves correctly in the 
> scenarios I've tested.

How about possible interactions with --reverse; --date-order/--topo-order,
--parents (important for gitk); --simplify-by-decoration (useful for your
problem that triggered this); --full-history and --simplify-merges with
path limiting?

-- Hannes

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] revision: --ancestry-path
  2010-04-21  7:47       ` Johannes Sixt
@ 2010-04-21  8:01         ` Junio C Hamano
  2010-04-21  8:19           ` [PATCH v2] " Junio C Hamano
                             ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Junio C Hamano @ 2010-04-21  8:01 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: Johan Herland, Junio C Hamano, git, Michael J Gruber

Johannes Sixt <j.sixt@viscovery.net> writes:

> Am 4/21/2010 9:34, schrieb Johan Herland:
>> I can confirm that this patch works with my original (more complicated) 
>> scenario. I've also played around with combinations of --ancestry-path, --
>> graph and --parents (and even --boundary), and AFAICS, the new option does 
>> not clobber the other options, and (IMHO) it all behaves correctly in the 
>> scenarios I've tested.
>
> How about possible interactions with --reverse; --date-order/--topo-order,
> --parents (important for gitk); --simplify-by-decoration (useful for your
> problem that triggered this); --full-history and --simplify-merges with
> path limiting?

These are all good points.

I am reasonably sure that parents (specifically, "rewrite_parents") is
broken.  The new function should cull parents that do not appear on the
ancestry path from merges (that is what "NEEDSWORK" is about).  It may or
may not break gitk, though---these off-path parents are shown as parents
of an on-path merge but will be marked as UNINTERESTING.

I do not think reverse/date-order/topo-order would be affected by this
change, as they only change the presentation order of what limit_list()
returns;

Also simplify-merges and full-history should be Ok, as they control what
is done in the current logic in limit_list(), which is an input to the new
logic, meaning that the new logic will work on already simplified history.

This is not a new problem, but I strongly suspect that cherry-pick is
broken the same way wrt "rewrite_parents".

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2] revision: --ancestry-path
  2010-04-21  8:01         ` Junio C Hamano
@ 2010-04-21  8:19           ` Junio C Hamano
  2010-04-21  8:46           ` [PATCH] " Johan Herland
  2010-04-21  8:49           ` Junio C Hamano
  2 siblings, 0 replies; 11+ messages in thread
From: Junio C Hamano @ 2010-04-21  8:19 UTC (permalink / raw)
  To: Johannes Sixt, Johan Herland; +Cc: git, Michael J Gruber

"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>
---
 * This simplifies the handling of TMP_MARK (the contract among the
   functions is that this is used only locally and the user is responsible
   for cleaning them up after use, so there is no need to clear the mark
   upfront), and also makes it responsibility of the caller to free the
   "bottom" list.

   Interdiff since v1 looks like this.

    diff -u b/revision.c b/revision.c
    --- b/revision.c
    +++ b/revision.c
    @@ -660,13 +660,11 @@
     	int made_progress;
     
     	/*
    -	 * Clear TMP_MARK and reverse the list so that it will be likely
    -	 * that we would process parents before children.
    +	 * Reverse the list so that it will be likely that we would
    +	 * process parents before children.
     	 */
    -	for (p = list; p; p = p->next) {
    -		p->item->object.flags &= ~TMP_MARK;
    +	for (p = list; p; p = p->next)
     		commit_list_insert(p->item, &rlist);
    -	}
     
     	for (p = bottom; p; p = p->next)
     		p->item->object.flags |= TMP_MARK;
    @@ -705,20 +703,16 @@
     	 */
     	for (p = list; p; p = p->next) {
     		struct commit *c = p->item;
    -		if (c->object.flags & TMP_MARK) {
    -			c->object.flags &= ~TMP_MARK;
    +		if (c->object.flags & TMP_MARK)
     			continue;
    -		}
     		c->object.flags |= UNINTERESTING;
     	}
     
    -	/* Release the bottom list */
    -	while (bottom) {
    -		p = bottom->next;
    -		bottom->item->object.flags &= ~TMP_MARK;
    -		free(bottom);
    -		bottom = p;
    -	}
    +	/* We are done with the TMP_MARK */
    +	for (p = list; p; p = p->next)
    +		p->item->object.flags &= ~TMP_MARK;
    +	for (p = bottom; p; p = p->next)
    +		p->item->object.flags &= ~TMP_MARK;
     	free_commit_list(rlist);
     }
     
    @@ -794,8 +788,10 @@
     	if (revs->cherry_pick)
     		cherry_pick_list(newlist, revs);
     
    -	if (bottom)
    +	if (bottom) {
     		limit_to_ancestry(bottom, newlist);
    +		free_commit_list(bottom);
    +	}
     
     	revs->commits = newlist;
     	return 0;

 revision.c                        |  102 +++++++++++++++++++++++++++++++++++++
 revision.h                        |    1 +
 t/t6019-rev-list-ancestry-path.sh |   56 ++++++++++++++++++++
 3 files changed, 159 insertions(+), 0 deletions(-)
 create mode 100755 t/t6019-rev-list-ancestry-path.sh

diff --git a/revision.c b/revision.c
index f4b8b38..71fec3c 100644
--- a/revision.c
+++ b/revision.c
@@ -646,6 +646,93 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl
 	return slop-1;
 }
 
+/*
+ * "rev-list --ancestry-path A..B" computes commits that are ancestors
+ * of B but not ancestors of A but further limits the result to those
+ * that are descendants of A.  This takes the list of bottom commits and
+ * the result of "A..B" without --ancestry-path, and limits the latter
+ * further to the ones that can reach one of the commits in "bottom".
+ */
+static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *list)
+{
+	struct commit_list *p;
+	struct commit_list *rlist = NULL;
+	int made_progress;
+
+	/*
+	 * Reverse the list so that it will be likely that we would
+	 * process parents before children.
+	 */
+	for (p = list; p; p = p->next)
+		commit_list_insert(p->item, &rlist);
+
+	for (p = bottom; p; p = p->next)
+		p->item->object.flags |= TMP_MARK;
+
+	/*
+	 * Mark the ones that can reach bottom commits in "list",
+	 * in a bottom-up fashion.
+	 */
+	do {
+		made_progress = 0;
+		for (p = rlist; p; p = p->next) {
+			struct commit *c = p->item;
+			struct commit_list *parents;
+			if (c->object.flags & (TMP_MARK | UNINTERESTING))
+				continue;
+			for (parents = c->parents;
+			     parents;
+			     parents = parents->next) {
+				if (!(parents->item->object.flags & TMP_MARK))
+					continue;
+				c->object.flags |= TMP_MARK;
+				made_progress = 1;
+				break;
+			}
+		}
+	} while (made_progress);
+
+	/*
+	 * NEEDSWORK: decide if we want to remove parents that are
+	 * not marked with TMP_MARK from commit->parents for commits
+	 * in the resulting list.  We may not want to do that, though.
+	 */
+
+	/*
+	 * The ones that are not marked with TMP_MARK are uninteresting
+	 */
+	for (p = list; p; p = p->next) {
+		struct commit *c = p->item;
+		if (c->object.flags & TMP_MARK)
+			continue;
+		c->object.flags |= UNINTERESTING;
+	}
+
+	/* We are done with the TMP_MARK */
+	for (p = list; p; p = p->next)
+		p->item->object.flags &= ~TMP_MARK;
+	for (p = bottom; p; p = p->next)
+		p->item->object.flags &= ~TMP_MARK;
+	free_commit_list(rlist);
+}
+
+/*
+ * Before walking the history, keep the set of "negative" refs the
+ * caller has asked to exclude.
+ *
+ * This is used to compute "rev-list --ancestry-path A..B", as we need
+ * to filter the result of "A..B" further to the ones that can actually
+ * reach A.
+ */
+static struct commit_list *collect_bottom_commits(struct commit_list *list)
+{
+	struct commit_list *elem, *bottom = NULL;
+	for (elem = list; elem; elem = elem->next)
+		if (elem->item->object.flags & UNINTERESTING)
+			commit_list_insert(elem->item, &bottom);
+	return bottom;
+}
+
 static int limit_list(struct rev_info *revs)
 {
 	int slop = SLOP;
@@ -653,6 +740,13 @@ static int limit_list(struct rev_info *revs)
 	struct commit_list *list = revs->commits;
 	struct commit_list *newlist = NULL;
 	struct commit_list **p = &newlist;
+	struct commit_list *bottom = NULL;
+
+	if (revs->ancestry_path) {
+		bottom = collect_bottom_commits(list);
+		if (!bottom)
+			die("--ancestry-path given but there is no bottom commits");
+	}
 
 	while (list) {
 		struct commit_list *entry = list;
@@ -694,6 +788,11 @@ static int limit_list(struct rev_info *revs)
 	if (revs->cherry_pick)
 		cherry_pick_list(newlist, revs);
 
+	if (bottom) {
+		limit_to_ancestry(bottom, newlist);
+		free_commit_list(bottom);
+	}
+
 	revs->commits = newlist;
 	return 0;
 }
@@ -1089,6 +1188,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->min_age = approxidate(arg + 8);
 	} else if (!strcmp(arg, "--first-parent")) {
 		revs->first_parent_only = 1;
+	} else if (!strcmp(arg, "--ancestry-path")) {
+		revs->ancestry_path = 1;
+		revs->limited = 1;
 	} else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
 		init_reflog_walk(&revs->reflog_info);
 	} else if (!strcmp(arg, "--default")) {
diff --git a/revision.h b/revision.h
index 568f1c9..855464f 100644
--- a/revision.h
+++ b/revision.h
@@ -66,6 +66,7 @@ struct rev_info {
 			reverse_output_stage:1,
 			cherry_pick:1,
 			bisect:1,
+			ancestry_path:1,
 			first_parent_only:1;
 
 	/* Diff flags */
diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh
new file mode 100755
index 0000000..0230724
--- /dev/null
+++ b/t/t6019-rev-list-ancestry-path.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+test_description='--ancestry-path'
+
+#          D---E-------F
+#         /     \       \
+#    B---C---G---H---I---J
+#   /                     \
+#  A-------K---------------L--M
+#
+#  D..M                 == E F G H I J K L M
+#  --ancestry-path D..M == E F H I J L M
+
+. ./test-lib.sh
+
+test_merge () {
+	test_tick &&
+	git merge -s ours -m "$2" "$1" &&
+	git tag "$2"
+}
+
+test_expect_success setup '
+	test_commit A &&
+	test_commit B &&
+	test_commit C &&
+	test_commit D &&
+	test_commit E &&
+	test_commit F &&
+	git reset --hard C &&
+	test_commit G &&
+	test_merge E H &&
+	test_commit I &&
+	test_merge F J &&
+	git reset --hard A &&
+	test_commit K &&
+	test_merge J L &&
+	test_commit M
+'
+
+test_expect_success 'rev-list D..M' '
+	for c in E F G H I J K L M; do echo $c; done >expect &&
+	git rev-list --format=%s D..M |
+	sed -e "/^commit /d" |
+	sort >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path D..M' '
+	for c in E F H I J L M; do echo $c; done >expect &&
+	git rev-list --ancestry-path --format=%s D..M |
+	sed -e "/^commit /d" |
+	sort >actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
1.7.1.rc2.265.g8743f

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH] revision: --ancestry-path
  2010-04-21  8:01         ` Junio C Hamano
  2010-04-21  8:19           ` [PATCH v2] " Junio C Hamano
@ 2010-04-21  8:46           ` Johan Herland
  2010-04-21  9:04             ` Junio C Hamano
  2010-04-21  8:49           ` Junio C Hamano
  2 siblings, 1 reply; 11+ messages in thread
From: Johan Herland @ 2010-04-21  8:46 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Sixt, Michael J Gruber

On Wednesday 21 April 2010, Junio C Hamano wrote:
> Johannes Sixt <j.sixt@viscovery.net> writes:
> > Am 4/21/2010 9:34, schrieb Johan Herland:
> >> I can confirm that this patch works with my original (more
> >> complicated) scenario. I've also played around with combinations of
> >> --ancestry-path, -- graph and --parents (and even --boundary), and
> >> AFAICS, the new option does not clobber the other options, and (IMHO)
> >> it all behaves correctly in the scenarios I've tested.
> > 
> > How about possible interactions with --reverse;
> > --date-order/--topo-order, --parents (important for gitk);
> > --simplify-by-decoration (useful for your problem that triggered
> > this); --full-history and --simplify-merges with path limiting?
> 
> These are all good points.
> 
> I am reasonably sure that parents (specifically, "rewrite_parents") is
> broken.  The new function should cull parents that do not appear on the
> ancestry path from merges (that is what "NEEDSWORK" is about).  It may or
> may not break gitk, though---these off-path parents are shown as parents
> of an on-path merge but will be marked as UNINTERESTING.

FWIW, I added the following patch to 'gitk', and then ran it against the 
t6019 test repo as follows:

	gitk --ancestry-path D..M

...and the resulting graph is exactly what I'd expect; showing the 
uninteresting parents (D, G and K) as "hollow" nodes.


diff --git a/gitk-git/gitk b/gitk-git/gitk
index 1b0e09a..7749d2a 100644
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -190,7 +190,7 @@ proc parseviewargs {n arglist} {
            "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
            "--remove-empty" - "--first-parent" - "--cherry-pick" -
            "-S*" - "--pickaxe-all" - "--pickaxe-regex" -
-           "--simplify-by-decoration" {
+           "--simplify-by-decoration" - "--ancestry-path" {
                # These mean that we get a subset of the commits
                set filtered 1
                lappend glflags $arg


> I do not think reverse/date-order/topo-order would be affected by this
> change, as they only change the presentation order of what limit_list()
> returns;
> 
> Also simplify-merges and full-history should be Ok, as they control what
> is done in the current logic in limit_list(), which is an input to the
> new logic, meaning that the new logic will work on already simplified
> history.
> 
> This is not a new problem, but I strongly suspect that cherry-pick is
> broken the same way wrt "rewrite_parents".

I'm not very familiar with "rewrite_parents", nor do I know exactly how it 
should affect/interoperate with --ancestry-path in all cases, but running

	git rev-list D..M -- M.t

produces one commit (M), whereas

	git rev-list --ancestry-path D..M -- M.t

produces nothing, so I suspect there is something not quite right here.


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH] revision: --ancestry-path
  2010-04-21  8:01         ` Junio C Hamano
  2010-04-21  8:19           ` [PATCH v2] " Junio C Hamano
  2010-04-21  8:46           ` [PATCH] " Johan Herland
@ 2010-04-21  8:49           ` Junio C Hamano
  2 siblings, 0 replies; 11+ messages in thread
From: Junio C Hamano @ 2010-04-21  8:49 UTC (permalink / raw)
  To: Johannes Sixt, Johan Herland; +Cc: git, Michael J Gruber

Junio C Hamano <gitster@pobox.com> writes:

> I am reasonably sure that parents (specifically, "rewrite_parents") is
> broken.  The new function should cull parents that do not appear on the
> ancestry path from merges (that is what "NEEDSWORK" is about).  It may or
> may not break gitk, though---these off-path parents are shown as parents
> of an on-path merge but will be marked as UNINTERESTING.

Thinking about it a bit more, I think this is Ok, as all the ancestors on
a side branch that is off-path are marked uninteresting, and we do show
uninteresting parents in the output of an interesting commit.  In fact, if
you do "rev-list --parents HEAD^..HEAD -- .", on a non-merge non-empty
commit, you will see uninteresting HEAD^ shown as the parent of HEAD.

> This is not a new problem, but I strongly suspect that cherry-pick is
> broken the same way wrt "rewrite_parents".

This is a different and a real issue.  By marking ones that are duplicate
of commits from the other side as SHOWN, we will spit out a disconnected
history.  It should rewrite the parent list so that children of a commit
that is removed due to being a duplicate from the other side point at an
ancestor of that removed commit to keep the history connected.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] revision: --ancestry-path
  2010-04-21  8:46           ` [PATCH] " Johan Herland
@ 2010-04-21  9:04             ` Junio C Hamano
  0 siblings, 0 replies; 11+ messages in thread
From: Junio C Hamano @ 2010-04-21  9:04 UTC (permalink / raw)
  To: Johan Herland; +Cc: git, Johannes Sixt, Michael J Gruber

Johan Herland <johan@herland.net> writes:

> I'm not very familiar with "rewrite_parents", nor do I know exactly how it 
> should affect/interoperate with --ancestry-path in all cases, but running
>
> 	git rev-list D..M -- M.t
>
> produces one commit (M), whereas
>
> 	git rev-list --ancestry-path D..M -- M.t
>
> produces nothing, so I suspect there is something not quite right here.

Merge simplification gets in our way big time.

          D---E-------F
         /     \       \
    B---C---G---H---I---J
   /                     \
  A-------K---------------L--M

While traversing the history from M, we notice that M's parent is L, and
then further we notice that neither the transition between J to L nor
between K to L change the named path M.t, so we simplify the merge L to
have K as its sole parent.  This makes M appear disconnected from D's
decendant chain, and causes the TMP_MARK reverse traversal not to work as
intended.

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2010-04-21  9:04 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-04-20 14:49 Getting 'git log' (or something else) to show me the relevant sub-graph? Johan Herland
2010-04-20 15:18 ` Michael J Gruber
2010-04-20 20:45 ` Junio C Hamano
2010-04-20 22:10   ` [PATCH] revision: --ancestry-path Junio C Hamano
2010-04-21  7:34     ` Johan Herland
2010-04-21  7:47       ` Johannes Sixt
2010-04-21  8:01         ` Junio C Hamano
2010-04-21  8:19           ` [PATCH v2] " Junio C Hamano
2010-04-21  8:46           ` [PATCH] " Johan Herland
2010-04-21  9:04             ` Junio C Hamano
2010-04-21  8:49           ` Junio C Hamano

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.