Merge branch 'jc/post-simplify' into tr/rev-list-docs
* jc/post-simplify: Topo-sort before --simplify-merges revision traversal: show full history with merge simplification revision.c: whitespace fix Conflicts: Documentation/rev-list-options.txt
This commit is contained in:
		| @ -193,6 +193,12 @@ endif::git-rev-list[] | ||||
|  | ||||
| 	Stop when a given path disappears from the tree. | ||||
|  | ||||
| --simplify-merges:: | ||||
|  | ||||
| 	Simplify away commits that did not change the given paths, similar | ||||
| 	to `--full-history`, and further remove merges none of whose | ||||
| 	parent history changes the given paths. | ||||
|  | ||||
| --no-merges:: | ||||
|  | ||||
| 	Do not print commits with more than one parent. | ||||
|  | ||||
							
								
								
									
										172
									
								
								revision.c
									
									
									
									
									
								
							
							
						
						
									
										172
									
								
								revision.c
									
									
									
									
									
								
							| @ -489,7 +489,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, | ||||
| 			p->object.flags |= SEEN; | ||||
| 			insert_by_date_cached(p, list, cached_base, cache_ptr); | ||||
| 		} | ||||
| 		if(revs->first_parent_only) | ||||
| 		if (revs->first_parent_only) | ||||
| 			break; | ||||
| 	} | ||||
| 	return 0; | ||||
| @ -1045,6 +1045,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg | ||||
| 	} else if (!strcmp(arg, "--topo-order")) { | ||||
| 		revs->lifo = 1; | ||||
| 		revs->topo_order = 1; | ||||
| 	} else if (!strcmp(arg, "--simplify-merges")) { | ||||
| 		revs->simplify_merges = 1; | ||||
| 		revs->rewrite_parents = 1; | ||||
| 		revs->simplify_history = 0; | ||||
| 		revs->limited = 1; | ||||
| 	} else if (!strcmp(arg, "--date-order")) { | ||||
| 		revs->lifo = 0; | ||||
| 		revs->topo_order = 1; | ||||
| @ -1378,6 +1383,149 @@ static void add_child(struct rev_info *revs, struct commit *parent, struct commi | ||||
| 	l->next = add_decoration(&revs->children, &parent->object, l); | ||||
| } | ||||
|  | ||||
| static int remove_duplicate_parents(struct commit *commit) | ||||
| { | ||||
| 	struct commit_list **pp, *p; | ||||
| 	int surviving_parents; | ||||
|  | ||||
| 	/* Examine existing parents while marking ones we have seen... */ | ||||
| 	pp = &commit->parents; | ||||
| 	while ((p = *pp) != NULL) { | ||||
| 		struct commit *parent = p->item; | ||||
| 		if (parent->object.flags & TMP_MARK) { | ||||
| 			*pp = p->next; | ||||
| 			continue; | ||||
| 		} | ||||
| 		parent->object.flags |= TMP_MARK; | ||||
| 		pp = &p->next; | ||||
| 	} | ||||
| 	/* count them while clearing the temporary mark */ | ||||
| 	surviving_parents = 0; | ||||
| 	for (p = commit->parents; p; p = p->next) { | ||||
| 		p->item->object.flags &= ~TMP_MARK; | ||||
| 		surviving_parents++; | ||||
| 	} | ||||
| 	return surviving_parents; | ||||
| } | ||||
|  | ||||
| static struct commit_list **simplify_one(struct commit *commit, struct commit_list **tail) | ||||
| { | ||||
| 	struct commit_list *p; | ||||
| 	int cnt; | ||||
|  | ||||
| 	/* | ||||
| 	 * We store which commit each one simplifies to in its util field. | ||||
| 	 * Have we handled this one? | ||||
| 	 */ | ||||
| 	if (commit->util) | ||||
| 		return tail; | ||||
|  | ||||
| 	/* | ||||
| 	 * An UNINTERESTING commit simplifies to itself, so does a | ||||
| 	 * root commit.  We do not rewrite parents of such commit | ||||
| 	 * anyway. | ||||
| 	 */ | ||||
| 	if ((commit->object.flags & UNINTERESTING) || !commit->parents) { | ||||
| 		commit->util = commit; | ||||
| 		return tail; | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Do we know what commit all of our parents should be rewritten to? | ||||
| 	 * Otherwise we are not ready to rewrite this one yet. | ||||
| 	 */ | ||||
| 	for (cnt = 0, p = commit->parents; p; p = p->next) { | ||||
| 		if (!p->item->util) { | ||||
| 			tail = &commit_list_insert(p->item, tail)->next; | ||||
| 			cnt++; | ||||
| 		} | ||||
| 	} | ||||
| 	if (cnt) | ||||
| 		return tail; | ||||
|  | ||||
| 	/* | ||||
| 	 * Rewrite our list of parents. | ||||
| 	 */ | ||||
| 	for (p = commit->parents; p; p = p->next) | ||||
| 		p->item = p->item->util; | ||||
| 	cnt = remove_duplicate_parents(commit); | ||||
|  | ||||
| 	/* | ||||
| 	 * It is possible that we are a merge and one side branch | ||||
| 	 * does not have any commit that touches the given paths; | ||||
| 	 * in such a case, the immediate parents will be rewritten | ||||
| 	 * to different commits. | ||||
| 	 * | ||||
| 	 *      o----X		X: the commit we are looking at; | ||||
| 	 *     /    /		o: a commit that touches the paths; | ||||
| 	 * ---o----' | ||||
| 	 * | ||||
| 	 * Further reduce the parents by removing redundant parents. | ||||
| 	 */ | ||||
| 	if (1 < cnt) { | ||||
| 		struct commit_list *h = reduce_heads(commit->parents); | ||||
| 		cnt = commit_list_count(h); | ||||
| 		free_commit_list(commit->parents); | ||||
| 		commit->parents = h; | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * A commit simplifies to itself if it is a root, if it is | ||||
| 	 * UNINTERESTING, if it touches the given paths, or if it is a | ||||
| 	 * merge and its parents simplifies to more than one commits | ||||
| 	 * (the first two cases are already handled at the beginning of | ||||
| 	 * this function). | ||||
| 	 * | ||||
| 	 * Otherwise, it simplifies to what its sole parent simplifies to. | ||||
| 	 */ | ||||
| 	if (!cnt || | ||||
| 	    (commit->object.flags & UNINTERESTING) || | ||||
| 	    !(commit->object.flags & TREESAME) || | ||||
| 	    (1 < cnt)) | ||||
| 		commit->util = commit; | ||||
| 	else | ||||
| 		commit->util = commit->parents->item->util; | ||||
| 	return tail; | ||||
| } | ||||
|  | ||||
| static void simplify_merges(struct rev_info *revs) | ||||
| { | ||||
| 	struct commit_list *list; | ||||
| 	struct commit_list *yet_to_do, **tail; | ||||
|  | ||||
| 	sort_in_topological_order(&revs->commits, revs->lifo); | ||||
|  | ||||
| 	/* feed the list reversed */ | ||||
| 	yet_to_do = NULL; | ||||
| 	for (list = revs->commits; list; list = list->next) | ||||
| 		commit_list_insert(list->item, &yet_to_do); | ||||
| 	while (yet_to_do) { | ||||
| 		list = yet_to_do; | ||||
| 		yet_to_do = NULL; | ||||
| 		tail = &yet_to_do; | ||||
| 		while (list) { | ||||
| 			struct commit *commit = list->item; | ||||
| 			struct commit_list *next = list->next; | ||||
| 			free(list); | ||||
| 			list = next; | ||||
| 			tail = simplify_one(commit, tail); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* clean up the result, removing the simplified ones */ | ||||
| 	list = revs->commits; | ||||
| 	revs->commits = NULL; | ||||
| 	tail = &revs->commits; | ||||
| 	while (list) { | ||||
| 		struct commit *commit = list->item; | ||||
| 		struct commit_list *next = list->next; | ||||
| 		free(list); | ||||
| 		list = next; | ||||
| 		if (commit->util == commit) | ||||
| 			tail = &commit_list_insert(commit, tail)->next; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void set_children(struct rev_info *revs) | ||||
| { | ||||
| 	struct commit_list *l; | ||||
| @ -1418,6 +1566,8 @@ int prepare_revision_walk(struct rev_info *revs) | ||||
| 			return -1; | ||||
| 	if (revs->topo_order) | ||||
| 		sort_in_topological_order(&revs->commits, revs->lifo); | ||||
| 	if (revs->simplify_merges) | ||||
| 		simplify_merges(revs); | ||||
| 	if (revs->children.name) | ||||
| 		set_children(revs); | ||||
| 	return 0; | ||||
| @ -1450,26 +1600,6 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void remove_duplicate_parents(struct commit *commit) | ||||
| { | ||||
| 	struct commit_list **pp, *p; | ||||
|  | ||||
| 	/* Examine existing parents while marking ones we have seen... */ | ||||
| 	pp = &commit->parents; | ||||
| 	while ((p = *pp) != NULL) { | ||||
| 		struct commit *parent = p->item; | ||||
| 		if (parent->object.flags & TMP_MARK) { | ||||
| 			*pp = p->next; | ||||
| 			continue; | ||||
| 		} | ||||
| 		parent->object.flags |= TMP_MARK; | ||||
| 		pp = &p->next; | ||||
| 	} | ||||
| 	/* ... and clear the temporary mark */ | ||||
| 	for (p = commit->parents; p; p = p->next) | ||||
| 		p->item->object.flags &= ~TMP_MARK; | ||||
| } | ||||
|  | ||||
| static int rewrite_parents(struct rev_info *revs, struct commit *commit) | ||||
| { | ||||
| 	struct commit_list **pp = &commit->parents; | ||||
|  | ||||
| @ -41,6 +41,7 @@ struct rev_info { | ||||
| 			simplify_history:1, | ||||
| 			lifo:1, | ||||
| 			topo_order:1, | ||||
| 			simplify_merges:1, | ||||
| 			tag_objects:1, | ||||
| 			tree_objects:1, | ||||
| 			blob_objects:1, | ||||
|  | ||||
							
								
								
									
										93
									
								
								t/t6012-rev-list-simplify.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										93
									
								
								t/t6012-rev-list-simplify.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,93 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| test_description='merge simplification' | ||||
|  | ||||
| . ./test-lib.sh | ||||
|  | ||||
| note () { | ||||
| 	git tag "$1" | ||||
| } | ||||
|  | ||||
| _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' | ||||
| _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" | ||||
|  | ||||
| unnote () { | ||||
| 	git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g" | ||||
| } | ||||
|  | ||||
| test_expect_success setup ' | ||||
| 	echo "Hi there" >file && | ||||
| 	git add file && | ||||
| 	test_tick && git commit -m "Initial file" && | ||||
| 	note A && | ||||
|  | ||||
| 	git branch other-branch && | ||||
|  | ||||
| 	echo "Hello" >file && | ||||
| 	git add file && | ||||
| 	test_tick && git commit -m "Modified file" && | ||||
| 	note B && | ||||
|  | ||||
| 	git checkout other-branch && | ||||
|  | ||||
| 	echo "Hello" >file && | ||||
| 	git add file && | ||||
| 	test_tick && git commit -m "Modified the file identically" && | ||||
| 	note C && | ||||
|  | ||||
| 	echo "This is a stupid example" >another-file && | ||||
| 	git add another-file && | ||||
| 	test_tick && git commit -m "Add another file" && | ||||
| 	note D && | ||||
|  | ||||
| 	test_tick && git merge -m "merge" master && | ||||
| 	note E && | ||||
|  | ||||
| 	echo "Yet another" >elif && | ||||
| 	git add elif && | ||||
| 	test_tick && git commit -m "Irrelevant change" && | ||||
| 	note F && | ||||
|  | ||||
| 	git checkout master && | ||||
| 	echo "Yet another" >elif && | ||||
| 	git add elif && | ||||
| 	test_tick && git commit -m "Another irrelevant change" && | ||||
| 	note G && | ||||
|  | ||||
| 	test_tick && git merge -m "merge" other-branch && | ||||
| 	note H && | ||||
|  | ||||
| 	echo "Final change" >file && | ||||
| 	test_tick && git commit -a -m "Final change" && | ||||
| 	note I | ||||
| ' | ||||
|  | ||||
| FMT='tformat:%P 	%H | %s' | ||||
|  | ||||
| check_result () { | ||||
| 	for c in $1 | ||||
| 	do | ||||
| 		echo "$c" | ||||
| 	done >expect && | ||||
| 	shift && | ||||
| 	param="$*" && | ||||
| 	test_expect_success "log $param" ' | ||||
| 		git log --pretty="$FMT" --parents $param | | ||||
| 		unnote >actual && | ||||
| 		sed -e "s/^.*	\([^ ]*\) .*/\1/" >check <actual && | ||||
| 		test_cmp expect check || { | ||||
| 			cat actual | ||||
| 			false | ||||
| 		} | ||||
| 	' | ||||
| } | ||||
|  | ||||
| check_result 'I H G F E D C B A' --full-history | ||||
| check_result 'I H E C B A' --full-history -- file | ||||
| check_result 'I H E C B A' --full-history --topo-order -- file | ||||
| check_result 'I H E C B A' --full-history --date-order -- file | ||||
| check_result 'I E C B A' --simplify-merges -- file | ||||
| check_result 'I B A' -- file | ||||
| check_result 'I B A' --topo-order -- file | ||||
|  | ||||
| test_done | ||||
		Reference in New Issue
	
	Block a user
	 Junio C Hamano
					Junio C Hamano