Commits

Anonymous committed 631ba30 Merge

Merge branches 'jc/sb' and 'jc/mb'

Comments (0)

Files changed (4)

  * Now, list does not have any interesting commit.  So we find the newest
  * commit from the result list that is not marked uninteresting.  Which is
  * commit B.
+ *
+ *
+ * Another pathological example how this thing can fail to mark an ancestor
+ * of a merge base as UNINTERESTING without the postprocessing phase.
+ *
+ *		  2
+ *		  H
+ *	    1    / \
+ *	    G   A   \
+ *	    |\ /     \ 
+ *	    | B       \
+ *	    |  \       \
+ *	     \  C       F
+ *	      \  \     / 
+ *	       \  D   /   
+ *		\ |  /
+ *		 \| /
+ *		  E
+ *
+ *	 list			A B C D E F G H
+ *	 G1 H2			- - - - - - 1 2
+ *	 H2 E1 B1		- 1 - - 1 - 1 2
+ *	 F2 E1 B1 A2		2 1 - - 1 2 1 2
+ *	 E3 B1 A2		2 1 - - 3 2 1 2
+ *	 B1 A2			2 1 - - 3 2 1 2
+ *	 C1 A2			2 1 1 - 3 2 1 2
+ *	 D1 A2			2 1 1 1 3 2 1 2
+ *	 A2			2 1 1 1 3 2 1 2
+ *	 B3			2 3 1 1 3 2 1 2
+ *	 C7			2 3 7 1 3 2 1 2
+ *
+ * At this point, unfortunately, everybody in the list is
+ * uninteresting, so we fail to complete the following two
+ * steps to fully marking uninteresting commits.
+ *
+ *	 D7			2 3 7 7 3 2 1 2
+ *	 E7			2 3 7 7 7 2 1 2
+ *
+ * and we end up showing E as an interesting merge base.
  */
 
 static int show_all = 0;
 
+static void mark_reachable_commits(struct commit_list *result,
+				   struct commit_list *list)
+{
+	struct commit_list *tmp;
+
+	/*
+	 * Postprocess to fully contaminate the well.
+	 */
+	for (tmp = result; tmp; tmp = tmp->next) {
+		struct commit *c = tmp->item;
+		/* Reinject uninteresting ones to list,
+		 * so we can scan their parents.
+		 */
+		if (c->object.flags & UNINTERESTING)
+			commit_list_insert(c, &list);
+	}
+	while (list) {
+		struct commit *c = list->item;
+		struct commit_list *parents;
+
+		tmp = list;
+		list = list->next;
+		free(tmp);
+
+		/* Anything taken out of the list is uninteresting, so
+		 * mark all its parents uninteresting.  We do not
+		 * parse new ones (we already parsed all the relevant
+		 * ones).
+		 */
+		parents = c->parents;
+		while (parents) {
+			struct commit *p = parents->item;
+			parents = parents->next;
+			if (!(p->object.flags & UNINTERESTING)) {
+				p->object.flags |= UNINTERESTING;
+				commit_list_insert(p, &list);
+			}
+		}
+	}
+}
+
 static int merge_base(struct commit *rev1, struct commit *rev2)
 {
 	struct commit_list *list = NULL;
 	struct commit_list *result = NULL;
+	struct commit_list *tmp = NULL;
 
 	if (rev1 == rev2) {
 		printf("%s\n", sha1_to_hex(rev1->object.sha1));
 
 	while (interesting(list)) {
 		struct commit *commit = list->item;
-		struct commit_list *tmp = list, *parents;
+		struct commit_list *parents;
 		int flags = commit->object.flags & 7;
 
+		tmp = list;
 		list = list->next;
 		free(tmp);
 		if (flags == 3) {
 	if (!result)
 		return 1;
 
+	if (result->next && list)
+		mark_reachable_commits(result, list);
+
 	while (result) {
 		struct commit *commit = result->item;
 		result = result->next;
 
 	while (*list_p) {
 		struct commit_list *parents;
+		int still_interesting = !!interesting(*list_p);
 		struct commit *commit = pop_one_commit(list_p);
 		int flags = commit->object.flags & all_mask;
-		int still_interesting = !!interesting(*list_p);
 
-		if (!still_interesting && extra < 0)
+		if (!still_interesting && extra <= 0)
 			break;
 
 		mark_seen(commit, seen_p);
 			parents = parents->next;
 			if ((this_flag & flags) == flags)
 				continue;
-			parse_commit(p);
+			if (!p->object.parsed)
+				parse_commit(p);
 			if (mark_seen(p, seen_p) && !still_interesting)
 				extra--;
 			p->object.flags |= flags;
 			insert_by_date(p, list_p);
 		}
 	}
+
+	/*
+	 * Postprocess to complete well-poisoning.
+	 *
+	 * At this point we have all the commits we have seen in
+	 * seen_p list (which happens to be sorted chronologically but
+	 * it does not really matter).  Mark anything that can be
+	 * reached from uninteresting commits not interesting.
+	 */
+	for (;;) {
+		int changed = 0;
+		struct commit_list *s;
+		for (s = *seen_p; s; s = s->next) {
+			struct commit *c = s->item;
+			struct commit_list *parents;
+
+			if (((c->object.flags & all_revs) != all_revs) &&
+			    !(c->object.flags & UNINTERESTING))
+				continue;
+
+			/* The current commit is either a merge base or
+			 * already uninteresting one.  Mark its parents
+			 * as uninteresting commits _only_ if they are
+			 * already parsed.  No reason to find new ones
+			 * here.
+			 */
+			parents = c->parents;
+			while (parents) {
+				struct commit *p = parents->item;
+				parents = parents->next;
+				if (!(p->object.flags & UNINTERESTING)) {
+					p->object.flags |= UNINTERESTING;
+					changed = 1;
+				}
+			}
+		}
+		if (!changed)
+			break;
+	}
 }
 
 static void show_one_commit(struct commit *commit, int no_name)
 {
-	char pretty[128], *cp;
+	char pretty[256], *cp;
 	struct commit_name *name = commit->object.util;
 	if (commit->object.parsed)
 		pretty_print_commit(CMIT_FMT_ONELINE, commit->buffer, ~0,
 	unsigned int rev_mask[MAX_REVS];
 	int num_rev, i, extra = 0;
 	int all_heads = 0, all_tags = 0;
-	int all_mask, all_revs, shown_merge_point;
+	int all_mask, all_revs;
 	char head_path[128];
 	const char *head_path_p;
 	int head_path_len;
 	int independent = 0;
 	int no_name = 0;
 	int sha1_name = 0;
+	int shown_merge_point = 0;
+	int topo_order = 0;
 
 	setup_git_directory();
 
 			merge_base = 1;
 		else if (!strcmp(arg, "--independent"))
 			independent = 1;
+		else if (!strcmp(arg, "--topo-order"))
+			topo_order = 1;
 		else
 			usage(show_branch_usage);
 		ac--; av++;
 		exit(0);
 
 	/* Sort topologically */
-	sort_in_topological_order(&seen);
+	if (topo_order)
+		sort_in_topological_order(&seen);
 
 	/* Give names to commits */
 	if (!sha1_name && !no_name)
 
 	all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
 	all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
-	shown_merge_point = 0;
 
 	while (seen) {
 		struct commit *commit = pop_one_commit(&seen);
 		int this_flag = commit->object.flags;
-		int is_merge_point = (this_flag & all_revs) == all_revs;
 
-		if (is_merge_point)
-			shown_merge_point = 1;
+		shown_merge_point |= ((this_flag & all_revs) == all_revs);
 
 		if (1 < num_rev) {
 			for (i = 0; i < num_rev; i++)
 			putchar(' ');
 		}
 		show_one_commit(commit, no_name);
-		if (shown_merge_point && is_merge_point)
-			if (--extra < 0)
-				break;
+
+		if (shown_merge_point && --extra < 0)
+			break;
 	}
 	return 0;
 }

t/t1200-tutorial.sh

 ++ [mybranch] Some work.
 EOF
 
-git show-branch master mybranch > show-branch.output
+git show-branch --topo-order master mybranch > show-branch.output
 test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output'
 
 git checkout mybranch
 ++ [master] Merged "mybranch" changes.
 EOF
 
-git show-branch master mybranch > show-branch2.output
+git show-branch --topo-order master mybranch > show-branch2.output
 test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output'
 
 # TODO: test git fetch

t/t6010-merge-base.sh

+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Merge base computation.
+'
+
+. ./test-lib.sh
+
+T=$(git-write-tree)
+
+M=1130000000
+Z=+0000
+
+export GIT_COMMITTER_EMAIL=git@comm.iter.xz
+export GIT_COMMITTER_NAME='C O Mmiter'
+export GIT_AUTHOR_NAME='A U Thor'
+export GIT_AUTHOR_EMAIL=git@au.thor.xz
+
+doit() {
+	OFFSET=$1; shift
+	NAME=$1; shift
+	PARENTS=
+	for P
+	do
+		PARENTS="${PARENTS}-p $P "
+	done
+	GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z"
+	GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE
+	export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+	commit=$(echo $NAME | git-commit-tree $T $PARENTS)
+	echo $commit >.git/refs/tags/$NAME
+	echo $commit
+}
+
+# Setup...
+E=$(doit 5 E)
+D=$(doit 4 D $E)
+F=$(doit 6 F $E)
+C=$(doit 3 C $D)
+B=$(doit 2 B $C)
+A=$(doit 1 A $B)
+G=$(doit 7 G $B $E)
+H=$(doit 8 H $A $F)
+
+test_expect_success 'compute merge-base (single)' \
+    'MB=$(git-merge-base G H) &&
+     expr "$(git-name-rev "$MB")" : "[0-9a-f]* B"'
+
+test_expect_success 'compute merge-base (all)' \
+    'MB=$(git-merge-base --all G H) &&
+     expr "$(git-name-rev "$MB")" : "[0-9a-f]* B"'
+
+test_expect_success 'compute merge-base with show-branch' \
+    'MB=$(git-show-branch --merge-base G H) &&
+     expr "$(git-name-rev "$MB")" : "[0-9a-f]* B"'
+
+test_done