Commit a8b5f116bc39f884c8888adae2fd3f9b96d972c0

Russell Belfer 2013-07-03T17:00:50

Fix example/log.c pathspec handling of merges This fixes the way the example log program decides if a merge commit should be shown when a pathspec is given. Also makes it easier to use the pathspec API to just check "does a tree match anything in the pathspec" without allocating a match list.

diff --git a/examples/log.c b/examples/log.c
index ba411d7..50e81ef 100644
--- a/examples/log.c
+++ b/examples/log.c
@@ -158,15 +158,73 @@ struct log_options {
 	char *committer;
 };
 
+static void print_commit(git_commit *commit)
+{
+	char buf[GIT_OID_HEXSZ + 1];
+	int i, count;
+	const git_signature *sig;
+	const char *scan, *eol;
+
+	git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
+	printf("commit %s\n", buf);
+
+	if ((count = (int)git_commit_parentcount(commit)) > 1) {
+		printf("Merge:");
+		for (i = 0; i < count; ++i) {
+			git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
+			printf(" %s", buf);
+		}
+		printf("\n");
+	}
+
+	if ((sig = git_commit_author(commit)) != NULL) {
+		printf("Author: %s <%s>\n", sig->name, sig->email);
+		print_time(&sig->when, "Date:   ");
+	}
+	printf("\n");
+
+	for (scan = git_commit_message(commit); scan && *scan; ) {
+		for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */;
+
+		printf("    %.*s\n", (int)(eol - scan), scan);
+		scan = *eol ? eol + 1 : NULL;
+	}
+	printf("\n");
+}
+
+static int match_with_parent(
+	git_commit *commit, int i, git_diff_options *opts)
+{
+	git_commit *parent;
+	git_tree *a, *b;
+	git_diff_list *diff;
+	int ndeltas;
+
+	check(git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL);
+	check(git_commit_tree(&a, parent), "Tree for parent", NULL);
+	check(git_commit_tree(&b, commit), "Tree for commit", NULL);
+	check(git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts),
+		  "Checking diff between parent and commit", NULL);
+
+	ndeltas = (int)git_diff_num_deltas(diff);
+
+	git_diff_list_free(diff);
+	git_tree_free(a);
+	git_tree_free(b);
+	git_commit_free(parent);
+
+	return ndeltas > 0;
+}
+
 int main(int argc, char *argv[])
 {
-	int i, count = 0;
+	int i, count = 0, parents;
 	char *a;
 	struct log_state s;
-	git_strarray paths;
+	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
 	git_oid oid;
 	git_commit *commit;
-	char buf[GIT_OID_HEXSZ + 1];
+	git_pathspec *ps = NULL;
 
 	git_threads_init();
 
@@ -200,45 +258,47 @@ int main(int argc, char *argv[])
 	if (!count)
 		add_revision(&s, NULL);
 
-	paths.strings = &argv[i];
-	paths.count   = argc - i;
-
-	while (!git_revwalk_next(&oid, s.walker)) {
-		const git_signature *sig;
-		const char *scan, *eol;
+	diffopts.pathspec.strings = &argv[i];
+	diffopts.pathspec.count   = argc - i;
+	count = 0;
+	if (diffopts.pathspec.count > 0)
+		check(git_pathspec_new(&ps, &diffopts.pathspec),
+			"Building pathspec", NULL);
 
+	for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) {
 		check(git_commit_lookup(&commit, s.repo, &oid),
 			"Failed to look up commit", NULL);
 
-		git_oid_tostr(buf, sizeof(buf), &oid);
-		printf("commit %s\n", buf);
-
-		if ((count = (int)git_commit_parentcount(commit)) > 1) {
-			printf("Merge:");
-			for (i = 0; i < count; ++i) {
-				git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
-				printf(" %s", buf);
+		parents = (int)git_commit_parentcount(commit);
+
+		if (diffopts.pathspec.count > 0) {
+			int unmatched = parents;
+
+			if (parents == 0) {
+				git_tree *tree;
+				check(git_commit_tree(&tree, commit), "Get tree", NULL);
+				if (git_pathspec_match_tree(
+						NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0)
+					unmatched = 1;
+				git_tree_free(tree);
+			} else if (parents == 1) {
+				unmatched = match_with_parent(commit, 0, &diffopts) ? 0 : 1;
+			} else {
+				for (i = 0; i < parents; ++i) {
+					if (match_with_parent(commit, i, &diffopts))
+						unmatched--;
+				}
 			}
-			printf("\n");
-		}
 
-		if ((sig = git_commit_author(commit)) != NULL) {
-			printf("Author: %s <%s>\n", sig->name, sig->email);
-			print_time(&sig->when, "Date:   ");
+			if (unmatched > 0)
+				continue;
 		}
-		printf("\n");
-
-		for (scan = git_commit_message(commit); scan && *scan; ) {
-			for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */;
-
-			printf("    %.*s\n", (int)(eol - scan), scan);
-			scan = *eol ? eol + 1 : NULL;
-		}
-		printf("\n");
 
-		git_commit_free(commit);
+		print_commit(commit);
+		++count;
 	}
 
+	git_pathspec_free(ps);
 	git_revwalk_free(s.walker);
 	git_repository_free(s.repo);
 	git_threads_shutdown();
diff --git a/include/git2.h b/include/git2.h
index 5f9fc48..e8638a8 100644
--- a/include/git2.h
+++ b/include/git2.h
@@ -56,5 +56,6 @@
 #include "git2/message.h"
 #include "git2/pack.h"
 #include "git2/stash.h"
+#include "git2/pathspec.h"
 
 #endif
diff --git a/src/pathspec.c b/src/pathspec.c
index 35421db..021f38f 100644
--- a/src/pathspec.c
+++ b/src/pathspec.c
@@ -311,7 +311,7 @@ static int pathspec_match_from_iterator(
 	git_pathspec *ps)
 {
 	int error = 0;
-	git_pathspec_match_list *m;
+	git_pathspec_match_list *m = NULL;
 	const git_index_entry *entry = NULL;
 	struct pathspec_match_context ctxt;
 	git_vector *patterns = &ps->pathspec;
@@ -322,8 +322,13 @@ static int pathspec_match_from_iterator(
 	uint8_t *used_patterns = NULL;
 	char **file;
 
-	*out = m = pathspec_match_alloc(ps);
-	GITERR_CHECK_ALLOC(m);
+	if (out) {
+		*out = m = pathspec_match_alloc(ps);
+		GITERR_CHECK_ALLOC(m);
+	} else {
+		failures_only = true;
+		find_failures = false;
+	}
 
 	if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0)
 		goto done;
@@ -385,7 +390,7 @@ static int pathspec_match_from_iterator(
 		}
 
 		/* if only looking at failures, exit early or just continue */
-		if (failures_only) {
+		if (failures_only || !out) {
 			if (used_ct == patterns->length)
 				break;
 			continue;
@@ -429,7 +434,7 @@ done:
 
 	if (error < 0) {
 		pathspec_match_free(m);
-		*out = NULL;
+		if (out) *out = NULL;
 	}
 
 	return error;
@@ -456,7 +461,7 @@ int git_pathspec_match_workdir(
 	int error = 0;
 	git_iterator *iter;
 
-	assert(out && repo);
+	assert(repo);
 
 	if (!(error = git_iterator_for_workdir(
 			&iter, repo, pathspec_match_iter_flags(flags), NULL, NULL))) {
@@ -478,7 +483,7 @@ int git_pathspec_match_index(
 	int error = 0;
 	git_iterator *iter;
 
-	assert(out && index);
+	assert(index);
 
 	if (!(error = git_iterator_for_index(
 			&iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) {
@@ -500,7 +505,7 @@ int git_pathspec_match_tree(
 	int error = 0;
 	git_iterator *iter;
 
-	assert(out && tree);
+	assert(tree);
 
 	if (!(error = git_iterator_for_tree(
 			&iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) {