Merge branch 'nd/struct-pathspec'
* nd/struct-pathspec: (22 commits) t6004: add pathspec globbing test for log family t7810: overlapping pathspecs and depth limit grep: drop pathspec_matches() in favor of tree_entry_interesting() grep: use writable strbuf from caller for grep_tree() grep: use match_pathspec_depth() for cache/worktree grepping grep: convert to use struct pathspec Convert ce_path_match() to use match_pathspec_depth() Convert ce_path_match() to use struct pathspec struct rev_info: convert prune_data to struct pathspec pathspec: add match_pathspec_depth() tree_entry_interesting(): optimize wildcard matching when base is matched tree_entry_interesting(): support wildcard matching tree_entry_interesting(): fix depth limit with overlapping pathspecs tree_entry_interesting(): support depth limit tree_entry_interesting(): refactor into separate smaller functions diff-tree: convert base+baselen to writable strbuf glossary: define pathspec Move tree_entry_interesting() to tree-walk.c and export it tree_entry_interesting(): remove dependency on struct diff_options Convert struct diff_options to use struct pathspec ...maint
						commit
						d5c87a802d
					
				|  | @ -273,6 +273,29 @@ This commit is referred to as a "merge commit", or sometimes just a | ||||||
| 	<<def_pack,pack>>, to assist in efficiently accessing the contents of a | 	<<def_pack,pack>>, to assist in efficiently accessing the contents of a | ||||||
| 	pack. | 	pack. | ||||||
|  |  | ||||||
|  | [[def_pathspec]]pathspec:: | ||||||
|  |        Pattern used to specify paths. | ||||||
|  | + | ||||||
|  | Pathspecs are used on the command line of "git ls-files", "git | ||||||
|  | ls-tree", "git grep", "git checkout", and many other commands to | ||||||
|  | limit the scope of operations to some subset of the tree or | ||||||
|  | worktree.  See the documentation of each command for whether | ||||||
|  | paths are relative to the current directory or toplevel.  The | ||||||
|  | pathspec syntax is as follows: | ||||||
|  |  | ||||||
|  | * any path matches itself | ||||||
|  | * the pathspec up to the last slash represents a | ||||||
|  |   directory prefix.  The scope of that pathspec is | ||||||
|  |   limited to that subtree. | ||||||
|  | * the rest of the pathspec is a pattern for the remainder | ||||||
|  |   of the pathname.  Paths relative to the directory | ||||||
|  |   prefix will be matched against that pattern using fnmatch(3); | ||||||
|  |   in particular, '*' and '?' _can_ match directory separators. | ||||||
|  | + | ||||||
|  | For example, Documentation/*.jpg will match all .jpg files | ||||||
|  | in the Documentation subtree, | ||||||
|  | including Documentation/chapter_1/figure_1.jpg. | ||||||
|  |  | ||||||
| [[def_parent]]parent:: | [[def_parent]]parent:: | ||||||
| 	A <<def_commit_object,commit object>> contains a (possibly empty) list | 	A <<def_commit_object,commit object>> contains a (possibly empty) list | ||||||
| 	of the logical predecessor(s) in the line of development, i.e. its | 	of the logical predecessor(s) in the line of development, i.e. its | ||||||
|  |  | ||||||
|  | @ -86,7 +86,7 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags) | ||||||
| 	struct rev_info rev; | 	struct rev_info rev; | ||||||
| 	init_revisions(&rev, prefix); | 	init_revisions(&rev, prefix); | ||||||
| 	setup_revisions(0, NULL, &rev, NULL); | 	setup_revisions(0, NULL, &rev, NULL); | ||||||
| 	rev.prune_data = pathspec; | 	init_pathspec(&rev.prune_data, pathspec); | ||||||
| 	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; | 	rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; | ||||||
| 	rev.diffopt.format_callback = update_callback; | 	rev.diffopt.format_callback = update_callback; | ||||||
| 	data.flags = flags; | 	data.flags = flags; | ||||||
|  |  | ||||||
|  | @ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) | ||||||
| 	    (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) | 	    (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) | ||||||
| 		rev.combine_merges = rev.dense_combined_merges = 1; | 		rev.combine_merges = rev.dense_combined_merges = 1; | ||||||
|  |  | ||||||
| 	if (read_cache_preload(rev.diffopt.paths) < 0) { | 	if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { | ||||||
| 		perror("read_cache_preload"); | 		perror("read_cache_preload"); | ||||||
| 		return -1; | 		return -1; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -135,7 +135,7 @@ static int builtin_diff_index(struct rev_info *revs, | ||||||
| 	    revs->max_count != -1 || revs->min_age != -1 || | 	    revs->max_count != -1 || revs->min_age != -1 || | ||||||
| 	    revs->max_age != -1) | 	    revs->max_age != -1) | ||||||
| 		usage(builtin_diff_usage); | 		usage(builtin_diff_usage); | ||||||
| 	if (read_cache_preload(revs->diffopt.paths) < 0) { | 	if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { | ||||||
| 		perror("read_cache_preload"); | 		perror("read_cache_preload"); | ||||||
| 		return -1; | 		return -1; | ||||||
| 	} | 	} | ||||||
|  | @ -237,7 +237,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv | ||||||
| 		revs->combine_merges = revs->dense_combined_merges = 1; | 		revs->combine_merges = revs->dense_combined_merges = 1; | ||||||
|  |  | ||||||
| 	setup_work_tree(); | 	setup_work_tree(); | ||||||
| 	if (read_cache_preload(revs->diffopt.paths) < 0) { | 	if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { | ||||||
| 		perror("read_cache_preload"); | 		perror("read_cache_preload"); | ||||||
| 		return -1; | 		return -1; | ||||||
| 	} | 	} | ||||||
|  | @ -374,14 +374,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix) | ||||||
| 		} | 		} | ||||||
| 		die("unhandled object '%s' given.", name); | 		die("unhandled object '%s' given.", name); | ||||||
| 	} | 	} | ||||||
| 	if (rev.prune_data) { | 	if (rev.prune_data.nr) { | ||||||
| 		const char **pathspec = rev.prune_data; |  | ||||||
| 		while (*pathspec) { |  | ||||||
| 		if (!path) | 		if (!path) | ||||||
| 				path = *pathspec; | 			path = rev.prune_data.items[0].match; | ||||||
| 			paths++; | 		paths += rev.prune_data.nr; | ||||||
| 			pathspec++; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
|  |  | ||||||
|  | @ -651,7 +651,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) | ||||||
| 	if (import_filename) | 	if (import_filename) | ||||||
| 		import_marks(import_filename); | 		import_marks(import_filename); | ||||||
|  |  | ||||||
| 	if (import_filename && revs.prune_data) | 	if (import_filename && revs.prune_data.nr) | ||||||
| 		full_tree = 1; | 		full_tree = 1; | ||||||
|  |  | ||||||
| 	get_tags_and_duplicates(&revs.pending, &extra_refs); | 	get_tags_and_duplicates(&revs.pending, &extra_refs); | ||||||
|  |  | ||||||
							
								
								
									
										192
									
								
								builtin/grep.c
								
								
								
								
							
							
						
						
									
										192
									
								
								builtin/grep.c
								
								
								
								
							|  | @ -329,106 +329,6 @@ static int grep_config(const char *var, const char *value, void *cb) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * Return non-zero if max_depth is negative or path has no more then max_depth |  | ||||||
|  * slashes. |  | ||||||
|  */ |  | ||||||
| static int accept_subdir(const char *path, int max_depth) |  | ||||||
| { |  | ||||||
| 	if (max_depth < 0) |  | ||||||
| 		return 1; |  | ||||||
|  |  | ||||||
| 	while ((path = strchr(path, '/')) != NULL) { |  | ||||||
| 		max_depth--; |  | ||||||
| 		if (max_depth < 0) |  | ||||||
| 			return 0; |  | ||||||
| 		path++; |  | ||||||
| 	} |  | ||||||
| 	return 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * Return non-zero if name is a subdirectory of match and is not too deep. |  | ||||||
|  */ |  | ||||||
| static int is_subdir(const char *name, int namelen, |  | ||||||
| 		const char *match, int matchlen, int max_depth) |  | ||||||
| { |  | ||||||
| 	if (matchlen > namelen || strncmp(name, match, matchlen)) |  | ||||||
| 		return 0; |  | ||||||
|  |  | ||||||
| 	if (name[matchlen] == '\0') /* exact match */ |  | ||||||
| 		return 1; |  | ||||||
|  |  | ||||||
| 	if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/') |  | ||||||
| 		return accept_subdir(name + matchlen + 1, max_depth); |  | ||||||
|  |  | ||||||
| 	return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * git grep pathspecs are somewhat different from diff-tree pathspecs; |  | ||||||
|  * pathname wildcards are allowed. |  | ||||||
|  */ |  | ||||||
| static int pathspec_matches(const char **paths, const char *name, int max_depth) |  | ||||||
| { |  | ||||||
| 	int namelen, i; |  | ||||||
| 	if (!paths || !*paths) |  | ||||||
| 		return accept_subdir(name, max_depth); |  | ||||||
| 	namelen = strlen(name); |  | ||||||
| 	for (i = 0; paths[i]; i++) { |  | ||||||
| 		const char *match = paths[i]; |  | ||||||
| 		int matchlen = strlen(match); |  | ||||||
| 		const char *cp, *meta; |  | ||||||
|  |  | ||||||
| 		if (is_subdir(name, namelen, match, matchlen, max_depth)) |  | ||||||
| 			return 1; |  | ||||||
| 		if (!fnmatch(match, name, 0)) |  | ||||||
| 			return 1; |  | ||||||
| 		if (name[namelen-1] != '/') |  | ||||||
| 			continue; |  | ||||||
|  |  | ||||||
| 		/* We are being asked if the directory ("name") is worth |  | ||||||
| 		 * descending into. |  | ||||||
| 		 * |  | ||||||
| 		 * Find the longest leading directory name that does |  | ||||||
| 		 * not have metacharacter in the pathspec; the name |  | ||||||
| 		 * we are looking at must overlap with that directory. |  | ||||||
| 		 */ |  | ||||||
| 		for (cp = match, meta = NULL; cp - match < matchlen; cp++) { |  | ||||||
| 			char ch = *cp; |  | ||||||
| 			if (ch == '*' || ch == '[' || ch == '?') { |  | ||||||
| 				meta = cp; |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if (!meta) |  | ||||||
| 			meta = cp; /* fully literal */ |  | ||||||
|  |  | ||||||
| 		if (namelen <= meta - match) { |  | ||||||
| 			/* Looking at "Documentation/" and |  | ||||||
| 			 * the pattern says "Documentation/howto/", or |  | ||||||
| 			 * "Documentation/diff*.txt".  The name we |  | ||||||
| 			 * have should match prefix. |  | ||||||
| 			 */ |  | ||||||
| 			if (!memcmp(match, name, namelen)) |  | ||||||
| 				return 1; |  | ||||||
| 			continue; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (meta - match < namelen) { |  | ||||||
| 			/* Looking at "Documentation/howto/" and |  | ||||||
| 			 * the pattern says "Documentation/h*"; |  | ||||||
| 			 * match up to "Do.../h"; this avoids descending |  | ||||||
| 			 * into "Documentation/technical/". |  | ||||||
| 			 */ |  | ||||||
| 			if (!memcmp(match, name, meta - match)) |  | ||||||
| 				return 1; |  | ||||||
| 			continue; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) | static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) | ||||||
| { | { | ||||||
| 	void *data; | 	void *data; | ||||||
|  | @ -581,7 +481,7 @@ static void run_pager(struct grep_opt *opt, const char *prefix) | ||||||
| 	free(argv); | 	free(argv); | ||||||
| } | } | ||||||
|  |  | ||||||
| static int grep_cache(struct grep_opt *opt, const char **paths, int cached) | static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) | ||||||
| { | { | ||||||
| 	int hit = 0; | 	int hit = 0; | ||||||
| 	int nr; | 	int nr; | ||||||
|  | @ -591,7 +491,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) | ||||||
| 		struct cache_entry *ce = active_cache[nr]; | 		struct cache_entry *ce = active_cache[nr]; | ||||||
| 		if (!S_ISREG(ce->ce_mode)) | 		if (!S_ISREG(ce->ce_mode)) | ||||||
| 			continue; | 			continue; | ||||||
| 		if (!pathspec_matches(paths, ce->name, opt->max_depth)) | 		if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) | ||||||
| 			continue; | 			continue; | ||||||
| 		/* | 		/* | ||||||
| 		 * If CE_VALID is on, we assume worktree file and its cache entry | 		 * If CE_VALID is on, we assume worktree file and its cache entry | ||||||
|  | @ -618,44 +518,29 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) | ||||||
| 	return hit; | 	return hit; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int grep_tree(struct grep_opt *opt, const char **paths, | static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, | ||||||
| 		     struct tree_desc *tree, | 		     struct tree_desc *tree, struct strbuf *base, int tn_len) | ||||||
| 		     const char *tree_name, const char *base) |  | ||||||
| { | { | ||||||
| 	int len; | 	int hit = 0, matched = 0; | ||||||
| 	int hit = 0; |  | ||||||
| 	struct name_entry entry; | 	struct name_entry entry; | ||||||
| 	char *down; | 	int old_baselen = base->len; | ||||||
| 	int tn_len = strlen(tree_name); |  | ||||||
| 	struct strbuf pathbuf; |  | ||||||
|  |  | ||||||
| 	strbuf_init(&pathbuf, PATH_MAX + tn_len); |  | ||||||
|  |  | ||||||
| 	if (tn_len) { |  | ||||||
| 		strbuf_add(&pathbuf, tree_name, tn_len); |  | ||||||
| 		strbuf_addch(&pathbuf, ':'); |  | ||||||
| 		tn_len = pathbuf.len; |  | ||||||
| 	} |  | ||||||
| 	strbuf_addstr(&pathbuf, base); |  | ||||||
| 	len = pathbuf.len; |  | ||||||
|  |  | ||||||
| 	while (tree_entry(tree, &entry)) { | 	while (tree_entry(tree, &entry)) { | ||||||
| 		int te_len = tree_entry_len(entry.path, entry.sha1); | 		int te_len = tree_entry_len(entry.path, entry.sha1); | ||||||
| 		pathbuf.len = len; |  | ||||||
| 		strbuf_add(&pathbuf, entry.path, te_len); |  | ||||||
|  |  | ||||||
| 		if (S_ISDIR(entry.mode)) | 		if (matched != 2) { | ||||||
| 			/* Match "abc/" against pathspec to | 			matched = tree_entry_interesting(&entry, base, tn_len, pathspec); | ||||||
| 			 * decide if we want to descend into "abc" | 			if (matched == -1) | ||||||
| 			 * directory. | 				break; /* no more matches */ | ||||||
| 			 */ | 			if (!matched) | ||||||
| 			strbuf_addch(&pathbuf, '/'); | 				continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		down = pathbuf.buf + tn_len; | 		strbuf_add(base, entry.path, te_len); | ||||||
| 		if (!pathspec_matches(paths, down, opt->max_depth)) |  | ||||||
| 			; | 		if (S_ISREG(entry.mode)) { | ||||||
| 		else if (S_ISREG(entry.mode)) | 			hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len); | ||||||
| 			hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len); | 		} | ||||||
| 		else if (S_ISDIR(entry.mode)) { | 		else if (S_ISDIR(entry.mode)) { | ||||||
| 			enum object_type type; | 			enum object_type type; | ||||||
| 			struct tree_desc sub; | 			struct tree_desc sub; | ||||||
|  | @ -666,18 +551,21 @@ static int grep_tree(struct grep_opt *opt, const char **paths, | ||||||
| 			if (!data) | 			if (!data) | ||||||
| 				die("unable to read tree (%s)", | 				die("unable to read tree (%s)", | ||||||
| 				    sha1_to_hex(entry.sha1)); | 				    sha1_to_hex(entry.sha1)); | ||||||
|  |  | ||||||
|  | 			strbuf_addch(base, '/'); | ||||||
| 			init_tree_desc(&sub, data, size); | 			init_tree_desc(&sub, data, size); | ||||||
| 			hit |= grep_tree(opt, paths, &sub, tree_name, down); | 			hit |= grep_tree(opt, pathspec, &sub, base, tn_len); | ||||||
| 			free(data); | 			free(data); | ||||||
| 		} | 		} | ||||||
|  | 		strbuf_setlen(base, old_baselen); | ||||||
|  |  | ||||||
| 		if (hit && opt->status_only) | 		if (hit && opt->status_only) | ||||||
| 			break; | 			break; | ||||||
| 	} | 	} | ||||||
| 	strbuf_release(&pathbuf); |  | ||||||
| 	return hit; | 	return hit; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int grep_object(struct grep_opt *opt, const char **paths, | static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, | ||||||
| 		       struct object *obj, const char *name) | 		       struct object *obj, const char *name) | ||||||
| { | { | ||||||
| 	if (obj->type == OBJ_BLOB) | 	if (obj->type == OBJ_BLOB) | ||||||
|  | @ -686,20 +574,30 @@ static int grep_object(struct grep_opt *opt, const char **paths, | ||||||
| 		struct tree_desc tree; | 		struct tree_desc tree; | ||||||
| 		void *data; | 		void *data; | ||||||
| 		unsigned long size; | 		unsigned long size; | ||||||
| 		int hit; | 		struct strbuf base; | ||||||
|  | 		int hit, len; | ||||||
|  |  | ||||||
| 		data = read_object_with_reference(obj->sha1, tree_type, | 		data = read_object_with_reference(obj->sha1, tree_type, | ||||||
| 						  &size, NULL); | 						  &size, NULL); | ||||||
| 		if (!data) | 		if (!data) | ||||||
| 			die("unable to read tree (%s)", sha1_to_hex(obj->sha1)); | 			die("unable to read tree (%s)", sha1_to_hex(obj->sha1)); | ||||||
|  |  | ||||||
|  | 		len = name ? strlen(name) : 0; | ||||||
|  | 		strbuf_init(&base, PATH_MAX + len + 1); | ||||||
|  | 		if (len) { | ||||||
|  | 			strbuf_add(&base, name, len); | ||||||
|  | 			strbuf_addch(&base, ':'); | ||||||
|  | 		} | ||||||
| 		init_tree_desc(&tree, data, size); | 		init_tree_desc(&tree, data, size); | ||||||
| 		hit = grep_tree(opt, paths, &tree, name, ""); | 		hit = grep_tree(opt, pathspec, &tree, &base, base.len); | ||||||
|  | 		strbuf_release(&base); | ||||||
| 		free(data); | 		free(data); | ||||||
| 		return hit; | 		return hit; | ||||||
| 	} | 	} | ||||||
| 	die("unable to grep from object of type %s", typename(obj->type)); | 	die("unable to grep from object of type %s", typename(obj->type)); | ||||||
| } | } | ||||||
|  |  | ||||||
| static int grep_objects(struct grep_opt *opt, const char **paths, | static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, | ||||||
| 			const struct object_array *list) | 			const struct object_array *list) | ||||||
| { | { | ||||||
| 	unsigned int i; | 	unsigned int i; | ||||||
|  | @ -709,7 +607,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths, | ||||||
| 	for (i = 0; i < nr; i++) { | 	for (i = 0; i < nr; i++) { | ||||||
| 		struct object *real_obj; | 		struct object *real_obj; | ||||||
| 		real_obj = deref_tag(list->objects[i].item, NULL, 0); | 		real_obj = deref_tag(list->objects[i].item, NULL, 0); | ||||||
| 		if (grep_object(opt, paths, real_obj, list->objects[i].name)) { | 		if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) { | ||||||
| 			hit = 1; | 			hit = 1; | ||||||
| 			if (opt->status_only) | 			if (opt->status_only) | ||||||
| 				break; | 				break; | ||||||
|  | @ -718,7 +616,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths, | ||||||
| 	return hit; | 	return hit; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int grep_directory(struct grep_opt *opt, const char **paths) | static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec) | ||||||
| { | { | ||||||
| 	struct dir_struct dir; | 	struct dir_struct dir; | ||||||
| 	int i, hit = 0; | 	int i, hit = 0; | ||||||
|  | @ -726,7 +624,7 @@ static int grep_directory(struct grep_opt *opt, const char **paths) | ||||||
| 	memset(&dir, 0, sizeof(dir)); | 	memset(&dir, 0, sizeof(dir)); | ||||||
| 	setup_standard_excludes(&dir); | 	setup_standard_excludes(&dir); | ||||||
|  |  | ||||||
| 	fill_directory(&dir, paths); | 	fill_directory(&dir, pathspec->raw); | ||||||
| 	for (i = 0; i < dir.nr; i++) { | 	for (i = 0; i < dir.nr; i++) { | ||||||
| 		hit |= grep_file(opt, dir.entries[i]->name); | 		hit |= grep_file(opt, dir.entries[i]->name); | ||||||
| 		if (hit && opt->status_only) | 		if (hit && opt->status_only) | ||||||
|  | @ -832,6 +730,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) | ||||||
| 	struct grep_opt opt; | 	struct grep_opt opt; | ||||||
| 	struct object_array list = OBJECT_ARRAY_INIT; | 	struct object_array list = OBJECT_ARRAY_INIT; | ||||||
| 	const char **paths = NULL; | 	const char **paths = NULL; | ||||||
|  | 	struct pathspec pathspec; | ||||||
| 	struct string_list path_list = STRING_LIST_INIT_NODUP; | 	struct string_list path_list = STRING_LIST_INIT_NODUP; | ||||||
| 	int i; | 	int i; | ||||||
| 	int dummy; | 	int dummy; | ||||||
|  | @ -1059,6 +958,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) | ||||||
| 		paths[0] = prefix; | 		paths[0] = prefix; | ||||||
| 		paths[1] = NULL; | 		paths[1] = NULL; | ||||||
| 	} | 	} | ||||||
|  | 	init_pathspec(&pathspec, paths); | ||||||
|  | 	pathspec.max_depth = opt.max_depth; | ||||||
|  | 	pathspec.recursive = 1; | ||||||
|  |  | ||||||
| 	if (show_in_pager && (cached || list.nr)) | 	if (show_in_pager && (cached || list.nr)) | ||||||
| 		die("--open-files-in-pager only works on the worktree"); | 		die("--open-files-in-pager only works on the worktree"); | ||||||
|  | @ -1089,16 +991,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix) | ||||||
| 			die("--cached cannot be used with --no-index."); | 			die("--cached cannot be used with --no-index."); | ||||||
| 		if (list.nr) | 		if (list.nr) | ||||||
| 			die("--no-index cannot be used with revs."); | 			die("--no-index cannot be used with revs."); | ||||||
| 		hit = grep_directory(&opt, paths); | 		hit = grep_directory(&opt, &pathspec); | ||||||
| 	} else if (!list.nr) { | 	} else if (!list.nr) { | ||||||
| 		if (!cached) | 		if (!cached) | ||||||
| 			setup_work_tree(); | 			setup_work_tree(); | ||||||
|  |  | ||||||
| 		hit = grep_cache(&opt, paths, cached); | 		hit = grep_cache(&opt, &pathspec, cached); | ||||||
| 	} else { | 	} else { | ||||||
| 		if (cached) | 		if (cached) | ||||||
| 			die("both --cached and trees are given."); | 			die("both --cached and trees are given."); | ||||||
| 		hit = grep_objects(&opt, paths, &list); | 		hit = grep_objects(&opt, &pathspec, &list); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (use_threads) | 	if (use_threads) | ||||||
|  |  | ||||||
|  | @ -89,7 +89,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, | ||||||
| 		rev->always_show_header = 0; | 		rev->always_show_header = 0; | ||||||
| 	if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { | 	if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { | ||||||
| 		rev->always_show_header = 0; | 		rev->always_show_header = 0; | ||||||
| 		if (rev->diffopt.nr_paths != 1) | 		if (rev->diffopt.pathspec.nr != 1) | ||||||
| 			usage("git logs can only follow renames on one pathname at a time"); | 			usage("git logs can only follow renames on one pathname at a time"); | ||||||
| 	} | 	} | ||||||
| 	for (i = 1; i < argc; i++) { | 	for (i = 1; i < argc; i++) { | ||||||
|  |  | ||||||
|  | @ -546,7 +546,10 @@ static int do_reupdate(int ac, const char **av, | ||||||
| 	 */ | 	 */ | ||||||
| 	int pos; | 	int pos; | ||||||
| 	int has_head = 1; | 	int has_head = 1; | ||||||
| 	const char **pathspec = get_pathspec(prefix, av + 1); | 	const char **paths = get_pathspec(prefix, av + 1); | ||||||
|  | 	struct pathspec pathspec; | ||||||
|  |  | ||||||
|  | 	init_pathspec(&pathspec, paths); | ||||||
|  |  | ||||||
| 	if (read_ref("HEAD", head_sha1)) | 	if (read_ref("HEAD", head_sha1)) | ||||||
| 		/* If there is no HEAD, that means it is an initial | 		/* If there is no HEAD, that means it is an initial | ||||||
|  | @ -559,7 +562,7 @@ static int do_reupdate(int ac, const char **av, | ||||||
| 		struct cache_entry *old = NULL; | 		struct cache_entry *old = NULL; | ||||||
| 		int save_nr; | 		int save_nr; | ||||||
|  |  | ||||||
| 		if (ce_stage(ce) || !ce_path_match(ce, pathspec)) | 		if (ce_stage(ce) || !ce_path_match(ce, &pathspec)) | ||||||
| 			continue; | 			continue; | ||||||
| 		if (has_head) | 		if (has_head) | ||||||
| 			old = read_one_ent(NULL, head_sha1, | 			old = read_one_ent(NULL, head_sha1, | ||||||
|  | @ -578,6 +581,7 @@ static int do_reupdate(int ac, const char **av, | ||||||
| 		if (save_nr != active_nr) | 		if (save_nr != active_nr) | ||||||
| 			goto redo; | 			goto redo; | ||||||
| 	} | 	} | ||||||
|  | 	free_pathspec(&pathspec); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								cache.h
								
								
								
								
							
							
						
						
									
										17
									
								
								cache.h
								
								
								
								
							|  | @ -500,7 +500,22 @@ extern int index_name_is_other(const struct index_state *, const char *, int); | ||||||
| extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); | extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); | ||||||
| extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); | extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); | ||||||
|  |  | ||||||
| extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); | struct pathspec { | ||||||
|  | 	const char **raw; /* get_pathspec() result, not freed by free_pathspec() */ | ||||||
|  | 	int nr; | ||||||
|  | 	int has_wildcard:1; | ||||||
|  | 	int recursive:1; | ||||||
|  | 	int max_depth; | ||||||
|  | 	struct pathspec_item { | ||||||
|  | 		const char *match; | ||||||
|  | 		int len; | ||||||
|  | 		int has_wildcard:1; | ||||||
|  | 	} *items; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | extern int init_pathspec(struct pathspec *, const char **); | ||||||
|  | extern void free_pathspec(struct pathspec *); | ||||||
|  | extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec); | ||||||
| extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); | extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); | ||||||
| extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); | extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); | ||||||
| extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); | extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); | ||||||
|  |  | ||||||
|  | @ -106,7 +106,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) | ||||||
| 			DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) | 			DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) | ||||||
| 			break; | 			break; | ||||||
|  |  | ||||||
| 		if (!ce_path_match(ce, revs->prune_data)) | 		if (!ce_path_match(ce, &revs->prune_data)) | ||||||
| 			continue; | 			continue; | ||||||
|  |  | ||||||
| 		if (ce_stage(ce)) { | 		if (ce_stage(ce)) { | ||||||
|  | @ -427,7 +427,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) | ||||||
| 	if (tree == o->df_conflict_entry) | 	if (tree == o->df_conflict_entry) | ||||||
| 		tree = NULL; | 		tree = NULL; | ||||||
|  |  | ||||||
| 	if (ce_path_match(idx ? idx : tree, revs->prune_data)) | 	if (ce_path_match(idx ? idx : tree, &revs->prune_data)) | ||||||
| 		do_oneway_diff(o, idx, tree); | 		do_oneway_diff(o, idx, tree); | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
|  | @ -501,7 +501,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) | ||||||
| 	active_nr = dst - active_cache; | 	active_nr = dst - active_cache; | ||||||
|  |  | ||||||
| 	init_revisions(&revs, NULL); | 	init_revisions(&revs, NULL); | ||||||
| 	revs.prune_data = opt->paths; | 	init_pathspec(&revs.prune_data, opt->pathspec.raw); | ||||||
| 	tree = parse_tree_indirect(tree_sha1); | 	tree = parse_tree_indirect(tree_sha1); | ||||||
| 	if (!tree) | 	if (!tree) | ||||||
| 		die("bad tree object %s", sha1_to_hex(tree_sha1)); | 		die("bad tree object %s", sha1_to_hex(tree_sha1)); | ||||||
|  |  | ||||||
|  | @ -231,8 +231,9 @@ void diff_no_index(struct rev_info *revs, | ||||||
|  |  | ||||||
| 	if (prefix) { | 	if (prefix) { | ||||||
| 		int len = strlen(prefix); | 		int len = strlen(prefix); | ||||||
|  | 		const char *paths[3]; | ||||||
|  | 		memset(paths, 0, sizeof(paths)); | ||||||
|  |  | ||||||
| 		revs->diffopt.paths = xcalloc(2, sizeof(char *)); |  | ||||||
| 		for (i = 0; i < 2; i++) { | 		for (i = 0; i < 2; i++) { | ||||||
| 			const char *p = argv[argc - 2 + i]; | 			const char *p = argv[argc - 2 + i]; | ||||||
| 			/* | 			/* | ||||||
|  | @ -242,12 +243,12 @@ void diff_no_index(struct rev_info *revs, | ||||||
| 			p = (strcmp(p, "-") | 			p = (strcmp(p, "-") | ||||||
| 			     ? xstrdup(prefix_filename(prefix, len, p)) | 			     ? xstrdup(prefix_filename(prefix, len, p)) | ||||||
| 			     : p); | 			     : p); | ||||||
| 			revs->diffopt.paths[i] = p; | 			paths[i] = p; | ||||||
| 		} | 		} | ||||||
|  | 		diff_tree_setup_paths(paths, &revs->diffopt); | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 		revs->diffopt.paths = argv + argc - 2; | 		diff_tree_setup_paths(argv + argc - 2, &revs->diffopt); | ||||||
| 	revs->diffopt.nr_paths = 2; |  | ||||||
| 	revs->diffopt.skip_stat_unmatch = 1; | 	revs->diffopt.skip_stat_unmatch = 1; | ||||||
| 	if (!revs->diffopt.output_format) | 	if (!revs->diffopt.output_format) | ||||||
| 		revs->diffopt.output_format = DIFF_FORMAT_PATCH; | 		revs->diffopt.output_format = DIFF_FORMAT_PATCH; | ||||||
|  | @ -259,8 +260,8 @@ void diff_no_index(struct rev_info *revs, | ||||||
| 	if (diff_setup_done(&revs->diffopt) < 0) | 	if (diff_setup_done(&revs->diffopt) < 0) | ||||||
| 		die("diff_setup_done failed"); | 		die("diff_setup_done failed"); | ||||||
|  |  | ||||||
| 	if (queue_diff(&revs->diffopt, revs->diffopt.paths[0], | 	if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0], | ||||||
| 		       revs->diffopt.paths[1])) | 		       revs->diffopt.pathspec.raw[1])) | ||||||
| 		exit(1); | 		exit(1); | ||||||
| 	diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/"); | 	diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/"); | ||||||
| 	diffcore_std(&revs->diffopt); | 	diffcore_std(&revs->diffopt); | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								diff.h
								
								
								
								
							
							
						
						
									
										4
									
								
								diff.h
								
								
								
								
							|  | @ -133,9 +133,7 @@ struct diff_options { | ||||||
| 	FILE *file; | 	FILE *file; | ||||||
| 	int close_file; | 	int close_file; | ||||||
|  |  | ||||||
| 	int nr_paths; | 	struct pathspec pathspec; | ||||||
| 	const char **paths; |  | ||||||
| 	int *pathlens; |  | ||||||
| 	change_fn_t change; | 	change_fn_t change; | ||||||
| 	add_remove_fn_t add_remove; | 	add_remove_fn_t add_remove; | ||||||
| 	diff_format_fn_t format_callback; | 	diff_format_fn_t format_callback; | ||||||
|  |  | ||||||
							
								
								
									
										151
									
								
								dir.c
								
								
								
								
							
							
						
						
									
										151
									
								
								dir.c
								
								
								
								
							|  | @ -87,6 +87,21 @@ int fill_directory(struct dir_struct *dir, const char **pathspec) | ||||||
| 	return len; | 	return len; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int within_depth(const char *name, int namelen, | ||||||
|  | 			int depth, int max_depth) | ||||||
|  | { | ||||||
|  | 	const char *cp = name, *cpe = name + namelen; | ||||||
|  |  | ||||||
|  | 	while (cp < cpe) { | ||||||
|  | 		if (*cp++ != '/') | ||||||
|  | 			continue; | ||||||
|  | 		depth++; | ||||||
|  | 		if (depth > max_depth) | ||||||
|  | 			return 0; | ||||||
|  | 	} | ||||||
|  | 	return 1; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Does 'match' match the given name? |  * Does 'match' match the given name? | ||||||
|  * A match is found if |  * A match is found if | ||||||
|  | @ -184,6 +199,95 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, | ||||||
| 	return retval; | 	return retval; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Does 'match' match the given name? | ||||||
|  |  * A match is found if | ||||||
|  |  * | ||||||
|  |  * (1) the 'match' string is leading directory of 'name', or | ||||||
|  |  * (2) the 'match' string is a wildcard and matches 'name', or | ||||||
|  |  * (3) the 'match' string is exactly the same as 'name'. | ||||||
|  |  * | ||||||
|  |  * and the return value tells which case it was. | ||||||
|  |  * | ||||||
|  |  * It returns 0 when there is no match. | ||||||
|  |  */ | ||||||
|  | static int match_pathspec_item(const struct pathspec_item *item, int prefix, | ||||||
|  | 			       const char *name, int namelen) | ||||||
|  | { | ||||||
|  | 	/* name/namelen has prefix cut off by caller */ | ||||||
|  | 	const char *match = item->match + prefix; | ||||||
|  | 	int matchlen = item->len - prefix; | ||||||
|  |  | ||||||
|  | 	/* If the match was just the prefix, we matched */ | ||||||
|  | 	if (!*match) | ||||||
|  | 		return MATCHED_RECURSIVELY; | ||||||
|  |  | ||||||
|  | 	if (matchlen <= namelen && !strncmp(match, name, matchlen)) { | ||||||
|  | 		if (matchlen == namelen) | ||||||
|  | 			return MATCHED_EXACTLY; | ||||||
|  |  | ||||||
|  | 		if (match[matchlen-1] == '/' || name[matchlen] == '/') | ||||||
|  | 			return MATCHED_RECURSIVELY; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (item->has_wildcard && !fnmatch(match, name, 0)) | ||||||
|  | 		return MATCHED_FNMATCH; | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Given a name and a list of pathspecs, see if the name matches | ||||||
|  |  * any of the pathspecs.  The caller is also interested in seeing | ||||||
|  |  * all pathspec matches some names it calls this function with | ||||||
|  |  * (otherwise the user could have mistyped the unmatched pathspec), | ||||||
|  |  * and a mark is left in seen[] array for pathspec element that | ||||||
|  |  * actually matched anything. | ||||||
|  |  */ | ||||||
|  | int match_pathspec_depth(const struct pathspec *ps, | ||||||
|  | 			 const char *name, int namelen, | ||||||
|  | 			 int prefix, char *seen) | ||||||
|  | { | ||||||
|  | 	int i, retval = 0; | ||||||
|  |  | ||||||
|  | 	if (!ps->nr) { | ||||||
|  | 		if (!ps->recursive || ps->max_depth == -1) | ||||||
|  | 			return MATCHED_RECURSIVELY; | ||||||
|  |  | ||||||
|  | 		if (within_depth(name, namelen, 0, ps->max_depth)) | ||||||
|  | 			return MATCHED_EXACTLY; | ||||||
|  | 		else | ||||||
|  | 			return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	name += prefix; | ||||||
|  | 	namelen -= prefix; | ||||||
|  |  | ||||||
|  | 	for (i = ps->nr - 1; i >= 0; i--) { | ||||||
|  | 		int how; | ||||||
|  | 		if (seen && seen[i] == MATCHED_EXACTLY) | ||||||
|  | 			continue; | ||||||
|  | 		how = match_pathspec_item(ps->items+i, prefix, name, namelen); | ||||||
|  | 		if (ps->recursive && ps->max_depth != -1 && | ||||||
|  | 		    how && how != MATCHED_FNMATCH) { | ||||||
|  | 			int len = ps->items[i].len; | ||||||
|  | 			if (name[len] == '/') | ||||||
|  | 				len++; | ||||||
|  | 			if (within_depth(name+len, namelen-len, 0, ps->max_depth)) | ||||||
|  | 				how = MATCHED_EXACTLY; | ||||||
|  | 			else | ||||||
|  | 				how = 0; | ||||||
|  | 		} | ||||||
|  | 		if (how) { | ||||||
|  | 			if (retval < how) | ||||||
|  | 				retval = how; | ||||||
|  | 			if (seen && seen[i] < how) | ||||||
|  | 				seen[i] = how; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return retval; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int no_wildcard(const char *string) | static int no_wildcard(const char *string) | ||||||
| { | { | ||||||
| 	return string[strcspn(string, "*?[{\\")] == '\0'; | 	return string[strcspn(string, "*?[{\\")] == '\0'; | ||||||
|  | @ -1151,3 +1255,50 @@ int remove_path(const char *name) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int pathspec_item_cmp(const void *a_, const void *b_) | ||||||
|  | { | ||||||
|  | 	struct pathspec_item *a, *b; | ||||||
|  |  | ||||||
|  | 	a = (struct pathspec_item *)a_; | ||||||
|  | 	b = (struct pathspec_item *)b_; | ||||||
|  | 	return strcmp(a->match, b->match); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int init_pathspec(struct pathspec *pathspec, const char **paths) | ||||||
|  | { | ||||||
|  | 	const char **p = paths; | ||||||
|  | 	int i; | ||||||
|  |  | ||||||
|  | 	memset(pathspec, 0, sizeof(*pathspec)); | ||||||
|  | 	if (!p) | ||||||
|  | 		return 0; | ||||||
|  | 	while (*p) | ||||||
|  | 		p++; | ||||||
|  | 	pathspec->raw = paths; | ||||||
|  | 	pathspec->nr = p - paths; | ||||||
|  | 	if (!pathspec->nr) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr); | ||||||
|  | 	for (i = 0; i < pathspec->nr; i++) { | ||||||
|  | 		struct pathspec_item *item = pathspec->items+i; | ||||||
|  | 		const char *path = paths[i]; | ||||||
|  |  | ||||||
|  | 		item->match = path; | ||||||
|  | 		item->len = strlen(path); | ||||||
|  | 		item->has_wildcard = !no_wildcard(path); | ||||||
|  | 		if (item->has_wildcard) | ||||||
|  | 			pathspec->has_wildcard = 1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	qsort(pathspec->items, pathspec->nr, | ||||||
|  | 	      sizeof(struct pathspec_item), pathspec_item_cmp); | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void free_pathspec(struct pathspec *pathspec) | ||||||
|  | { | ||||||
|  | 	free(pathspec->items); | ||||||
|  | 	pathspec->items = NULL; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								dir.h
								
								
								
								
							
							
						
						
									
										4
									
								
								dir.h
								
								
								
								
							|  | @ -65,6 +65,10 @@ struct dir_struct { | ||||||
| #define MATCHED_FNMATCH 2 | #define MATCHED_FNMATCH 2 | ||||||
| #define MATCHED_EXACTLY 3 | #define MATCHED_EXACTLY 3 | ||||||
| extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); | extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); | ||||||
|  | extern int match_pathspec_depth(const struct pathspec *pathspec, | ||||||
|  | 				const char *name, int namelen, | ||||||
|  | 				int prefix, char *seen); | ||||||
|  | extern int within_depth(const char *name, int namelen, int depth, int max_depth); | ||||||
|  |  | ||||||
| extern int fill_directory(struct dir_struct *dir, const char **pathspec); | extern int fill_directory(struct dir_struct *dir, const char **pathspec); | ||||||
| extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec); | extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec); | ||||||
|  |  | ||||||
|  | @ -35,7 +35,9 @@ static void *preload_thread(void *_data) | ||||||
| 	struct index_state *index = p->index; | 	struct index_state *index = p->index; | ||||||
| 	struct cache_entry **cep = index->cache + p->offset; | 	struct cache_entry **cep = index->cache + p->offset; | ||||||
| 	struct cache_def cache; | 	struct cache_def cache; | ||||||
|  | 	struct pathspec pathspec; | ||||||
|  |  | ||||||
|  | 	init_pathspec(&pathspec, p->pathspec); | ||||||
| 	memset(&cache, 0, sizeof(cache)); | 	memset(&cache, 0, sizeof(cache)); | ||||||
| 	nr = p->nr; | 	nr = p->nr; | ||||||
| 	if (nr + p->offset > index->cache_nr) | 	if (nr + p->offset > index->cache_nr) | ||||||
|  | @ -51,7 +53,7 @@ static void *preload_thread(void *_data) | ||||||
| 			continue; | 			continue; | ||||||
| 		if (ce_uptodate(ce)) | 		if (ce_uptodate(ce)) | ||||||
| 			continue; | 			continue; | ||||||
| 		if (!ce_path_match(ce, p->pathspec)) | 		if (!ce_path_match(ce, &pathspec)) | ||||||
| 			continue; | 			continue; | ||||||
| 		if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce))) | 		if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce))) | ||||||
| 			continue; | 			continue; | ||||||
|  | @ -61,6 +63,7 @@ static void *preload_thread(void *_data) | ||||||
| 			continue; | 			continue; | ||||||
| 		ce_mark_uptodate(ce); | 		ce_mark_uptodate(ce); | ||||||
| 	} while (--nr > 0); | 	} while (--nr > 0); | ||||||
|  | 	free_pathspec(&pathspec); | ||||||
| 	return NULL; | 	return NULL; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								read-cache.c
								
								
								
								
							
							
						
						
									
										25
									
								
								read-cache.c
								
								
								
								
							|  | @ -706,30 +706,9 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b) | ||||||
| 	return ce_namelen(b) == len && !memcmp(a->name, b->name, len); | 	return ce_namelen(b) == len && !memcmp(a->name, b->name, len); | ||||||
| } | } | ||||||
|  |  | ||||||
| int ce_path_match(const struct cache_entry *ce, const char **pathspec) | int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec) | ||||||
| { | { | ||||||
| 	const char *match, *name; | 	return match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL); | ||||||
| 	int len; |  | ||||||
|  |  | ||||||
| 	if (!pathspec) |  | ||||||
| 		return 1; |  | ||||||
|  |  | ||||||
| 	len = ce_namelen(ce); |  | ||||||
| 	name = ce->name; |  | ||||||
| 	while ((match = *pathspec++) != NULL) { |  | ||||||
| 		int matchlen = strlen(match); |  | ||||||
| 		if (matchlen > len) |  | ||||||
| 			continue; |  | ||||||
| 		if (memcmp(name, match, matchlen)) |  | ||||||
| 			continue; |  | ||||||
| 		if (matchlen && name[matchlen-1] == '/') |  | ||||||
| 			return 1; |  | ||||||
| 		if (name[matchlen] == '/' || !name[matchlen]) |  | ||||||
| 			return 1; |  | ||||||
| 		if (!matchlen) |  | ||||||
| 			return 1; |  | ||||||
| 	} |  | ||||||
| 	return 0; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								revision.c
								
								
								
								
							
							
						
						
									
										21
									
								
								revision.c
								
								
								
								
							|  | @ -323,7 +323,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct | ||||||
| 		 * tagged commit by specifying both --simplify-by-decoration | 		 * tagged commit by specifying both --simplify-by-decoration | ||||||
| 		 * and pathspec. | 		 * and pathspec. | ||||||
| 		 */ | 		 */ | ||||||
| 		if (!revs->prune_data) | 		if (!revs->prune_data.nr) | ||||||
| 			return REV_TREE_SAME; | 			return REV_TREE_SAME; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -553,11 +553,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) | ||||||
|  |  | ||||||
| 	left_first = left_count < right_count; | 	left_first = left_count < right_count; | ||||||
| 	init_patch_ids(&ids); | 	init_patch_ids(&ids); | ||||||
| 	if (revs->diffopt.nr_paths) { | 	ids.diffopts.pathspec = revs->diffopt.pathspec; | ||||||
| 		ids.diffopts.nr_paths = revs->diffopt.nr_paths; |  | ||||||
| 		ids.diffopts.paths = revs->diffopt.paths; |  | ||||||
| 		ids.diffopts.pathlens = revs->diffopt.pathlens; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/* Compute patch-ids for one side */ | 	/* Compute patch-ids for one side */ | ||||||
| 	for (p = list; p; p = p->next) { | 	for (p = list; p; p = p->next) { | ||||||
|  | @ -973,7 +969,7 @@ static void prepare_show_merge(struct rev_info *revs) | ||||||
| 		struct cache_entry *ce = active_cache[i]; | 		struct cache_entry *ce = active_cache[i]; | ||||||
| 		if (!ce_stage(ce)) | 		if (!ce_stage(ce)) | ||||||
| 			continue; | 			continue; | ||||||
| 		if (ce_path_match(ce, revs->prune_data)) { | 		if (ce_path_match(ce, &revs->prune_data)) { | ||||||
| 			prune_num++; | 			prune_num++; | ||||||
| 			prune = xrealloc(prune, sizeof(*prune) * prune_num); | 			prune = xrealloc(prune, sizeof(*prune) * prune_num); | ||||||
| 			prune[prune_num-2] = ce->name; | 			prune[prune_num-2] = ce->name; | ||||||
|  | @ -983,7 +979,8 @@ static void prepare_show_merge(struct rev_info *revs) | ||||||
| 		       ce_same_name(ce, active_cache[i+1])) | 		       ce_same_name(ce, active_cache[i+1])) | ||||||
| 			i++; | 			i++; | ||||||
| 	} | 	} | ||||||
| 	revs->prune_data = prune; | 	free_pathspec(&revs->prune_data); | ||||||
|  | 	init_pathspec(&revs->prune_data, prune); | ||||||
| 	revs->limited = 1; | 	revs->limited = 1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1620,7 +1617,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (prune_data) | 	if (prune_data) | ||||||
| 		revs->prune_data = get_pathspec(revs->prefix, prune_data); | 		init_pathspec(&revs->prune_data, get_pathspec(revs->prefix, prune_data)); | ||||||
|  |  | ||||||
| 	if (revs->def == NULL) | 	if (revs->def == NULL) | ||||||
| 		revs->def = opt ? opt->def : NULL; | 		revs->def = opt ? opt->def : NULL; | ||||||
|  | @ -1651,13 +1648,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s | ||||||
| 	if (revs->topo_order) | 	if (revs->topo_order) | ||||||
| 		revs->limited = 1; | 		revs->limited = 1; | ||||||
|  |  | ||||||
| 	if (revs->prune_data) { | 	if (revs->prune_data.nr) { | ||||||
| 		diff_tree_setup_paths(revs->prune_data, &revs->pruning); | 		diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning); | ||||||
| 		/* Can't prune commits with rename following: the paths change.. */ | 		/* Can't prune commits with rename following: the paths change.. */ | ||||||
| 		if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) | 		if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) | ||||||
| 			revs->prune = 1; | 			revs->prune = 1; | ||||||
| 		if (!revs->full_diff) | 		if (!revs->full_diff) | ||||||
| 			diff_tree_setup_paths(revs->prune_data, &revs->diffopt); | 			diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt); | ||||||
| 	} | 	} | ||||||
| 	if (revs->combine_merges) | 	if (revs->combine_merges) | ||||||
| 		revs->ignore_merges = 0; | 		revs->ignore_merges = 0; | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ struct rev_info { | ||||||
| 	/* Basic information */ | 	/* Basic information */ | ||||||
| 	const char *prefix; | 	const char *prefix; | ||||||
| 	const char *def; | 	const char *def; | ||||||
| 	void *prune_data; | 	struct pathspec prune_data; | ||||||
| 	unsigned int early_output; | 	unsigned int early_output; | ||||||
|  |  | ||||||
| 	/* Traversal flags */ | 	/* Traversal flags */ | ||||||
|  |  | ||||||
|  | @ -70,4 +70,36 @@ test_expect_success 'diff-tree pathspec' ' | ||||||
| 	test_cmp expected current | 	test_cmp expected current | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 | ||||||
|  |  | ||||||
|  | test_expect_success 'diff-tree with wildcard shows dir also matches' ' | ||||||
|  | 	git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result && | ||||||
|  | 	echo file0 >expected && | ||||||
|  | 	test_cmp expected result | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'diff-tree -r with wildcard' ' | ||||||
|  | 	git diff-tree -r --name-only $EMPTY_TREE $tree -- "*file1" >result && | ||||||
|  | 	echo path1/file1 >expected && | ||||||
|  | 	test_cmp expected result | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'diff-tree with wildcard shows dir also matches' ' | ||||||
|  | 	git diff-tree --name-only $tree $tree2 -- "path1/f*" >result && | ||||||
|  | 	echo path1 >expected && | ||||||
|  | 	test_cmp expected result | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'diff-tree -r with wildcard from beginning' ' | ||||||
|  | 	git diff-tree -r --name-only $tree $tree2 -- "path1/*file1" >result && | ||||||
|  | 	echo path1/file1 >expected && | ||||||
|  | 	test_cmp expected result | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'diff-tree -r with wildcard' ' | ||||||
|  | 	git diff-tree -r --name-only $tree $tree2 -- "path1/f*" >result && | ||||||
|  | 	echo path1/file1 >expected && | ||||||
|  | 	test_cmp expected result | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_done | test_done | ||||||
|  |  | ||||||
|  | @ -1,17 +1,34 @@ | ||||||
| #!/bin/sh | #!/bin/sh | ||||||
|  |  | ||||||
| test_description='git rev-list trivial path optimization test' | test_description='git rev-list trivial path optimization test | ||||||
|  |  | ||||||
|  |    d/z1 | ||||||
|  |    b0                             b1 | ||||||
|  |    o------------------------*----o master | ||||||
|  |   /                        / | ||||||
|  |  o---------o----o----o----o side | ||||||
|  |  a0        c0   c1   a1   c2 | ||||||
|  |  d/f0      d/f1 | ||||||
|  |  d/z0 | ||||||
|  |  | ||||||
|  | ' | ||||||
|  |  | ||||||
| . ./test-lib.sh | . ./test-lib.sh | ||||||
|  |  | ||||||
| test_expect_success setup ' | test_expect_success setup ' | ||||||
| 	echo Hello >a && | 	echo Hello >a && | ||||||
| git add a && | 	mkdir d && | ||||||
| git commit -m "Initial commit" a && | 	echo World >d/f && | ||||||
| initial=$(git rev-parse --verify HEAD) | 	echo World >d/z && | ||||||
|  | 	git add a d && | ||||||
|  | 	test_tick && | ||||||
|  | 	git commit -m "Initial commit" && | ||||||
|  | 	git rev-parse --verify HEAD && | ||||||
|  | 	git tag initial | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success path-optimization ' | test_expect_success path-optimization ' | ||||||
|  | 	test_tick && | ||||||
| 	commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) && | 	commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) && | ||||||
| 	test $(git rev-list $commit | wc -l) = 2 && | 	test $(git rev-list $commit | wc -l) = 2 && | ||||||
| 	test $(git rev-list $commit -- . | wc -l) = 1 | 	test $(git rev-list $commit -- . | wc -l) = 1 | ||||||
|  | @ -20,32 +37,60 @@ test_expect_success path-optimization ' | ||||||
| test_expect_success 'further setup' ' | test_expect_success 'further setup' ' | ||||||
| 	git checkout -b side && | 	git checkout -b side && | ||||||
| 	echo Irrelevant >c && | 	echo Irrelevant >c && | ||||||
| 	git add c && | 	echo Irrelevant >d/f && | ||||||
|  | 	git add c d/f && | ||||||
|  | 	test_tick && | ||||||
| 	git commit -m "Side makes an irrelevant commit" && | 	git commit -m "Side makes an irrelevant commit" && | ||||||
|  | 	git tag side_c0 && | ||||||
| 	echo "More Irrelevancy" >c && | 	echo "More Irrelevancy" >c && | ||||||
| 	git add c && | 	git add c && | ||||||
|  | 	test_tick && | ||||||
| 	git commit -m "Side makes another irrelevant commit" && | 	git commit -m "Side makes another irrelevant commit" && | ||||||
| 	echo Bye >a && | 	echo Bye >a && | ||||||
| 	git add a && | 	git add a && | ||||||
|  | 	test_tick && | ||||||
| 	git commit -m "Side touches a" && | 	git commit -m "Side touches a" && | ||||||
| 	side=$(git rev-parse --verify HEAD) && | 	git tag side_a1 && | ||||||
| 	echo "Yet more Irrelevancy" >c && | 	echo "Yet more Irrelevancy" >c && | ||||||
| 	git add c && | 	git add c && | ||||||
|  | 	test_tick && | ||||||
| 	git commit -m "Side makes yet another irrelevant commit" && | 	git commit -m "Side makes yet another irrelevant commit" && | ||||||
| 	git checkout master && | 	git checkout master && | ||||||
| 	echo Another >b && | 	echo Another >b && | ||||||
| 	git add b && | 	echo Munged >d/z && | ||||||
|  | 	git add b d/z && | ||||||
|  | 	test_tick && | ||||||
| 	git commit -m "Master touches b" && | 	git commit -m "Master touches b" && | ||||||
|  | 	git tag master_b0 && | ||||||
| 	git merge side && | 	git merge side && | ||||||
| 	echo Touched >b && | 	echo Touched >b && | ||||||
| 	git add b && | 	git add b && | ||||||
|  | 	test_tick && | ||||||
| 	git commit -m "Master touches b again" | 	git commit -m "Master touches b again" | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'path optimization 2' ' | test_expect_success 'path optimization 2' ' | ||||||
| 	( echo "$side"; echo "$initial" ) >expected && | 	git rev-parse side_a1 initial >expected && | ||||||
| 	git rev-list HEAD -- a >actual && | 	git rev-list HEAD -- a >actual && | ||||||
| 	test_cmp expected actual | 	test_cmp expected actual | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'pathspec with leading path' ' | ||||||
|  | 	git rev-parse master^ master_b0 side_c0 initial >expected && | ||||||
|  | 	git rev-list HEAD -- d >actual && | ||||||
|  | 	test_cmp expected actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'pathspec with glob (1)' ' | ||||||
|  | 	git rev-parse master^ master_b0 side_c0 initial >expected && | ||||||
|  | 	git rev-list HEAD -- "d/*" >actual && | ||||||
|  | 	test_cmp expected actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'pathspec with glob (2)' ' | ||||||
|  | 	git rev-parse side_c0 initial >expected && | ||||||
|  | 	git rev-list HEAD -- "d/[a-m]*" >actual && | ||||||
|  | 	test_cmp expected actual | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_done | test_done | ||||||
|  |  | ||||||
|  | @ -182,6 +182,24 @@ do | ||||||
| 		test_cmp expected actual | 		test_cmp expected actual | ||||||
| 	' | 	' | ||||||
|  |  | ||||||
|  | 	test_expect_success "grep --max-depth 0 -- . t $L" ' | ||||||
|  | 		{ | ||||||
|  | 			echo ${HC}t/v:1:vvv | ||||||
|  | 			echo ${HC}v:1:vvv | ||||||
|  | 		} >expected && | ||||||
|  | 		git grep --max-depth 0 -n -e vvv $H -- . t >actual && | ||||||
|  | 		test_cmp expected actual | ||||||
|  | 	' | ||||||
|  |  | ||||||
|  | 	test_expect_success "grep --max-depth 0 -- t . $L" ' | ||||||
|  | 		{ | ||||||
|  | 			echo ${HC}t/v:1:vvv | ||||||
|  | 			echo ${HC}v:1:vvv | ||||||
|  | 		} >expected && | ||||||
|  | 		git grep --max-depth 0 -n -e vvv $H -- t . >actual && | ||||||
|  | 		test_cmp expected actual | ||||||
|  | 	' | ||||||
|  |  | ||||||
| done | done | ||||||
|  |  | ||||||
| cat >expected <<EOF | cat >expected <<EOF | ||||||
|  |  | ||||||
							
								
								
									
										280
									
								
								tree-diff.c
								
								
								
								
							
							
						
						
									
										280
									
								
								tree-diff.c
								
								
								
								
							|  | @ -6,34 +6,18 @@ | ||||||
| #include "diffcore.h" | #include "diffcore.h" | ||||||
| #include "tree.h" | #include "tree.h" | ||||||
|  |  | ||||||
| static char *malloc_base(const char *base, int baselen, const char *path, int pathlen) | static void show_entry(struct diff_options *opt, const char *prefix, | ||||||
| { | 		       struct tree_desc *desc, struct strbuf *base); | ||||||
| 	char *newbase = xmalloc(baselen + pathlen + 2); |  | ||||||
| 	memcpy(newbase, base, baselen); |  | ||||||
| 	memcpy(newbase + baselen, path, pathlen); |  | ||||||
| 	memcpy(newbase + baselen + pathlen, "/", 2); |  | ||||||
| 	return newbase; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen) | static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, | ||||||
| { | 			      struct strbuf *base, struct diff_options *opt) | ||||||
| 	char *fullname = xmalloc(baselen + pathlen + 1); |  | ||||||
| 	memcpy(fullname, base, baselen); |  | ||||||
| 	memcpy(fullname + baselen, path, pathlen); |  | ||||||
| 	fullname[baselen + pathlen] = 0; |  | ||||||
| 	return fullname; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, |  | ||||||
| 		       const char *base, int baselen); |  | ||||||
|  |  | ||||||
| static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt) |  | ||||||
| { | { | ||||||
| 	unsigned mode1, mode2; | 	unsigned mode1, mode2; | ||||||
| 	const char *path1, *path2; | 	const char *path1, *path2; | ||||||
| 	const unsigned char *sha1, *sha2; | 	const unsigned char *sha1, *sha2; | ||||||
| 	int cmp, pathlen1, pathlen2; | 	int cmp, pathlen1, pathlen2; | ||||||
| 	char *fullname; | 	int old_baselen = base->len; | ||||||
|  | 	int retval = 0; | ||||||
|  |  | ||||||
| 	sha1 = tree_entry_extract(t1, &path1, &mode1); | 	sha1 = tree_entry_extract(t1, &path1, &mode1); | ||||||
| 	sha2 = tree_entry_extract(t2, &path2, &mode2); | 	sha2 = tree_entry_extract(t2, &path2, &mode2); | ||||||
|  | @ -42,11 +26,11 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const | ||||||
| 	pathlen2 = tree_entry_len(path2, sha2); | 	pathlen2 = tree_entry_len(path2, sha2); | ||||||
| 	cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2); | 	cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2); | ||||||
| 	if (cmp < 0) { | 	if (cmp < 0) { | ||||||
| 		show_entry(opt, "-", t1, base, baselen); | 		show_entry(opt, "-", t1, base); | ||||||
| 		return -1; | 		return -1; | ||||||
| 	} | 	} | ||||||
| 	if (cmp > 0) { | 	if (cmp > 0) { | ||||||
| 		show_entry(opt, "+", t2, base, baselen); | 		show_entry(opt, "+", t2, base); | ||||||
| 		return 1; | 		return 1; | ||||||
| 	} | 	} | ||||||
| 	if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2) | 	if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2) | ||||||
|  | @ -57,149 +41,29 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const | ||||||
| 	 * file, we need to consider it a remove and an add. | 	 * file, we need to consider it a remove and an add. | ||||||
| 	 */ | 	 */ | ||||||
| 	if (S_ISDIR(mode1) != S_ISDIR(mode2)) { | 	if (S_ISDIR(mode1) != S_ISDIR(mode2)) { | ||||||
| 		show_entry(opt, "-", t1, base, baselen); | 		show_entry(opt, "-", t1, base); | ||||||
| 		show_entry(opt, "+", t2, base, baselen); | 		show_entry(opt, "+", t2, base); | ||||||
| 		return 0; | 		return 0; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	strbuf_add(base, path1, pathlen1); | ||||||
| 	if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) { | 	if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) { | ||||||
| 		int retval; |  | ||||||
| 		char *newbase = malloc_base(base, baselen, path1, pathlen1); |  | ||||||
| 		if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { | 		if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { | ||||||
| 			newbase[baselen + pathlen1] = 0; |  | ||||||
| 			opt->change(opt, mode1, mode2, | 			opt->change(opt, mode1, mode2, | ||||||
| 				    sha1, sha2, newbase, 0, 0); | 				    sha1, sha2, base->buf, 0, 0); | ||||||
| 			newbase[baselen + pathlen1] = '/'; |  | ||||||
| 		} | 		} | ||||||
| 		retval = diff_tree_sha1(sha1, sha2, newbase, opt); | 		strbuf_addch(base, '/'); | ||||||
| 		free(newbase); | 		retval = diff_tree_sha1(sha1, sha2, base->buf, opt); | ||||||
| 		return retval; | 	} else { | ||||||
|  | 		opt->change(opt, mode1, mode2, sha1, sha2, base->buf, 0, 0); | ||||||
| 	} | 	} | ||||||
|  | 	strbuf_setlen(base, old_baselen); | ||||||
| 	fullname = malloc_fullname(base, baselen, path1, pathlen1); |  | ||||||
| 	opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0); |  | ||||||
| 	free(fullname); |  | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * Is a tree entry interesting given the pathspec we have? |  | ||||||
|  * |  | ||||||
|  * Pre-condition: baselen == 0 || base[baselen-1] == '/' |  | ||||||
|  * |  | ||||||
|  * Return: |  | ||||||
|  *  - 2 for "yes, and all subsequent entries will be" |  | ||||||
|  *  - 1 for yes |  | ||||||
|  *  - zero for no |  | ||||||
|  *  - negative for "no, and no subsequent entries will be either" |  | ||||||
|  */ |  | ||||||
| static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt) |  | ||||||
| { |  | ||||||
| 	const char *path; |  | ||||||
| 	const unsigned char *sha1; |  | ||||||
| 	unsigned mode; |  | ||||||
| 	int i; |  | ||||||
| 	int pathlen; |  | ||||||
| 	int never_interesting = -1; |  | ||||||
|  |  | ||||||
| 	if (!opt->nr_paths) |  | ||||||
| 		return 2; |  | ||||||
|  |  | ||||||
| 	sha1 = tree_entry_extract(desc, &path, &mode); |  | ||||||
|  |  | ||||||
| 	pathlen = tree_entry_len(path, sha1); |  | ||||||
|  |  | ||||||
| 	for (i = 0; i < opt->nr_paths; i++) { |  | ||||||
| 		const char *match = opt->paths[i]; |  | ||||||
| 		int matchlen = opt->pathlens[i]; |  | ||||||
| 		int m = -1; /* signals that we haven't called strncmp() */ |  | ||||||
|  |  | ||||||
| 		if (baselen >= matchlen) { |  | ||||||
| 			/* If it doesn't match, move along... */ |  | ||||||
| 			if (strncmp(base, match, matchlen)) |  | ||||||
| 				continue; |  | ||||||
|  |  | ||||||
| 			/* |  | ||||||
| 			 * If the base is a subdirectory of a path which |  | ||||||
| 			 * was specified, all of them are interesting. |  | ||||||
| 			 */ |  | ||||||
| 			if (!matchlen || |  | ||||||
| 			    base[matchlen] == '/' || |  | ||||||
| 			    match[matchlen - 1] == '/') |  | ||||||
| 				return 2; |  | ||||||
|  |  | ||||||
| 			/* Just a random prefix match */ |  | ||||||
| 			continue; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		/* Does the base match? */ |  | ||||||
| 		if (strncmp(base, match, baselen)) |  | ||||||
| 			continue; |  | ||||||
|  |  | ||||||
| 		match += baselen; |  | ||||||
| 		matchlen -= baselen; |  | ||||||
|  |  | ||||||
| 		if (never_interesting) { |  | ||||||
| 			/* |  | ||||||
| 			 * We have not seen any match that sorts later |  | ||||||
| 			 * than the current path. |  | ||||||
| 			 */ |  | ||||||
|  |  | ||||||
| 			/* |  | ||||||
| 			 * Does match sort strictly earlier than path |  | ||||||
| 			 * with their common parts? |  | ||||||
| 			 */ |  | ||||||
| 			m = strncmp(match, path, |  | ||||||
| 				    (matchlen < pathlen) ? matchlen : pathlen); |  | ||||||
| 			if (m < 0) |  | ||||||
| 				continue; |  | ||||||
|  |  | ||||||
| 			/* |  | ||||||
| 			 * If we come here even once, that means there is at |  | ||||||
| 			 * least one pathspec that would sort equal to or |  | ||||||
| 			 * later than the path we are currently looking at. |  | ||||||
| 			 * In other words, if we have never reached this point |  | ||||||
| 			 * after iterating all pathspecs, it means all |  | ||||||
| 			 * pathspecs are either outside of base, or inside the |  | ||||||
| 			 * base but sorts strictly earlier than the current |  | ||||||
| 			 * one.  In either case, they will never match the |  | ||||||
| 			 * subsequent entries.  In such a case, we initialized |  | ||||||
| 			 * the variable to -1 and that is what will be |  | ||||||
| 			 * returned, allowing the caller to terminate early. |  | ||||||
| 			 */ |  | ||||||
| 			never_interesting = 0; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (pathlen > matchlen) |  | ||||||
| 			continue; |  | ||||||
|  |  | ||||||
| 		if (matchlen > pathlen) { |  | ||||||
| 			if (match[pathlen] != '/') |  | ||||||
| 				continue; |  | ||||||
| 			if (!S_ISDIR(mode)) |  | ||||||
| 				continue; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (m == -1) |  | ||||||
| 			/* |  | ||||||
| 			 * we cheated and did not do strncmp(), so we do |  | ||||||
| 			 * that here. |  | ||||||
| 			 */ |  | ||||||
| 			m = strncmp(match, path, pathlen); |  | ||||||
|  |  | ||||||
| 		/* |  | ||||||
| 		 * If common part matched earlier then it is a hit, |  | ||||||
| 		 * because we rejected the case where path is not a |  | ||||||
| 		 * leading directory and is shorter than match. |  | ||||||
| 		 */ |  | ||||||
| 		if (!m) |  | ||||||
| 			return 1; |  | ||||||
| 	} |  | ||||||
| 	return never_interesting; /* No matches */ |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* A whole sub-tree went away or appeared */ | /* A whole sub-tree went away or appeared */ | ||||||
| static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen) | static void show_tree(struct diff_options *opt, const char *prefix, | ||||||
|  | 		      struct tree_desc *desc, struct strbuf *base) | ||||||
| { | { | ||||||
| 	int all_interesting = 0; | 	int all_interesting = 0; | ||||||
| 	while (desc->size) { | 	while (desc->size) { | ||||||
|  | @ -208,31 +72,32 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_ | ||||||
| 		if (all_interesting) | 		if (all_interesting) | ||||||
| 			show = 1; | 			show = 1; | ||||||
| 		else { | 		else { | ||||||
| 			show = tree_entry_interesting(desc, base, baselen, | 			show = tree_entry_interesting(&desc->entry, base, 0, | ||||||
| 						      opt); | 						      &opt->pathspec); | ||||||
| 			if (show == 2) | 			if (show == 2) | ||||||
| 				all_interesting = 1; | 				all_interesting = 1; | ||||||
| 		} | 		} | ||||||
| 		if (show < 0) | 		if (show < 0) | ||||||
| 			break; | 			break; | ||||||
| 		if (show) | 		if (show) | ||||||
| 			show_entry(opt, prefix, desc, base, baselen); | 			show_entry(opt, prefix, desc, base); | ||||||
| 		update_tree_entry(desc); | 		update_tree_entry(desc); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| /* A file entry went away or appeared */ | /* A file entry went away or appeared */ | ||||||
| static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, | static void show_entry(struct diff_options *opt, const char *prefix, | ||||||
| 		       const char *base, int baselen) | 		       struct tree_desc *desc, struct strbuf *base) | ||||||
| { | { | ||||||
| 	unsigned mode; | 	unsigned mode; | ||||||
| 	const char *path; | 	const char *path; | ||||||
| 	const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode); | 	const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode); | ||||||
| 	int pathlen = tree_entry_len(path, sha1); | 	int pathlen = tree_entry_len(path, sha1); | ||||||
|  | 	int old_baselen = base->len; | ||||||
|  |  | ||||||
|  | 	strbuf_add(base, path, pathlen); | ||||||
| 	if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) { | 	if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) { | ||||||
| 		enum object_type type; | 		enum object_type type; | ||||||
| 		char *newbase = malloc_base(base, baselen, path, pathlen); |  | ||||||
| 		struct tree_desc inner; | 		struct tree_desc inner; | ||||||
| 		void *tree; | 		void *tree; | ||||||
| 		unsigned long size; | 		unsigned long size; | ||||||
|  | @ -241,28 +106,25 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree | ||||||
| 		if (!tree || type != OBJ_TREE) | 		if (!tree || type != OBJ_TREE) | ||||||
| 			die("corrupt tree sha %s", sha1_to_hex(sha1)); | 			die("corrupt tree sha %s", sha1_to_hex(sha1)); | ||||||
|  |  | ||||||
| 		if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { | 		if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) | ||||||
| 			newbase[baselen + pathlen] = 0; | 			opt->add_remove(opt, *prefix, mode, sha1, base->buf, 0); | ||||||
| 			opt->add_remove(opt, *prefix, mode, sha1, newbase, 0); |  | ||||||
| 			newbase[baselen + pathlen] = '/'; | 		strbuf_addch(base, '/'); | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		init_tree_desc(&inner, tree, size); | 		init_tree_desc(&inner, tree, size); | ||||||
| 		show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen); | 		show_tree(opt, prefix, &inner, base); | ||||||
|  |  | ||||||
| 		free(tree); | 		free(tree); | ||||||
| 		free(newbase); | 	} else | ||||||
| 	} else { | 		opt->add_remove(opt, prefix[0], mode, sha1, base->buf, 0); | ||||||
| 		char *fullname = malloc_fullname(base, baselen, path, pathlen); |  | ||||||
| 		opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0); | 	strbuf_setlen(base, old_baselen); | ||||||
| 		free(fullname); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| static void skip_uninteresting(struct tree_desc *t, const char *base, int baselen, struct diff_options *opt, int *all_interesting) | static void skip_uninteresting(struct tree_desc *t, struct strbuf *base, | ||||||
|  | 			       struct diff_options *opt, int *all_interesting) | ||||||
| { | { | ||||||
| 	while (t->size) { | 	while (t->size) { | ||||||
| 		int show = tree_entry_interesting(t, base, baselen, opt); | 		int show = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec); | ||||||
| 		if (show == 2) | 		if (show == 2) | ||||||
| 			*all_interesting = 1; | 			*all_interesting = 1; | ||||||
| 		if (!show) { | 		if (!show) { | ||||||
|  | @ -276,37 +138,44 @@ static void skip_uninteresting(struct tree_desc *t, const char *base, int basele | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) | int diff_tree(struct tree_desc *t1, struct tree_desc *t2, | ||||||
|  | 	      const char *base_str, struct diff_options *opt) | ||||||
| { | { | ||||||
| 	int baselen = strlen(base); | 	struct strbuf base; | ||||||
|  | 	int baselen = strlen(base_str); | ||||||
| 	int all_t1_interesting = 0; | 	int all_t1_interesting = 0; | ||||||
| 	int all_t2_interesting = 0; | 	int all_t2_interesting = 0; | ||||||
|  |  | ||||||
|  | 	/* Enable recursion indefinitely */ | ||||||
|  | 	opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE); | ||||||
|  | 	opt->pathspec.max_depth = -1; | ||||||
|  |  | ||||||
|  | 	strbuf_init(&base, PATH_MAX); | ||||||
|  | 	strbuf_add(&base, base_str, baselen); | ||||||
|  |  | ||||||
| 	for (;;) { | 	for (;;) { | ||||||
| 		if (DIFF_OPT_TST(opt, QUICK) && | 		if (DIFF_OPT_TST(opt, QUICK) && | ||||||
| 		    DIFF_OPT_TST(opt, HAS_CHANGES)) | 		    DIFF_OPT_TST(opt, HAS_CHANGES)) | ||||||
| 			break; | 			break; | ||||||
| 		if (opt->nr_paths) { | 		if (opt->pathspec.nr) { | ||||||
| 			if (!all_t1_interesting) | 			if (!all_t1_interesting) | ||||||
| 				skip_uninteresting(t1, base, baselen, opt, | 				skip_uninteresting(t1, &base, opt, &all_t1_interesting); | ||||||
| 						   &all_t1_interesting); |  | ||||||
| 			if (!all_t2_interesting) | 			if (!all_t2_interesting) | ||||||
| 				skip_uninteresting(t2, base, baselen, opt, | 				skip_uninteresting(t2, &base, opt, &all_t2_interesting); | ||||||
| 						   &all_t2_interesting); |  | ||||||
| 		} | 		} | ||||||
| 		if (!t1->size) { | 		if (!t1->size) { | ||||||
| 			if (!t2->size) | 			if (!t2->size) | ||||||
| 				break; | 				break; | ||||||
| 			show_entry(opt, "+", t2, base, baselen); | 			show_entry(opt, "+", t2, &base); | ||||||
| 			update_tree_entry(t2); | 			update_tree_entry(t2); | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
| 		if (!t2->size) { | 		if (!t2->size) { | ||||||
| 			show_entry(opt, "-", t1, base, baselen); | 			show_entry(opt, "-", t1, &base); | ||||||
| 			update_tree_entry(t1); | 			update_tree_entry(t1); | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
| 		switch (compare_tree_entry(t1, t2, base, baselen, opt)) { | 		switch (compare_tree_entry(t1, t2, &base, opt)) { | ||||||
| 		case -1: | 		case -1: | ||||||
| 			update_tree_entry(t1); | 			update_tree_entry(t1); | ||||||
| 			continue; | 			continue; | ||||||
|  | @ -319,6 +188,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru | ||||||
| 		} | 		} | ||||||
| 		die("git diff-tree: internal error"); | 		die("git diff-tree: internal error"); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	strbuf_release(&base); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -349,7 +220,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co | ||||||
| 	DIFF_OPT_SET(&diff_opts, RECURSIVE); | 	DIFF_OPT_SET(&diff_opts, RECURSIVE); | ||||||
| 	DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); | 	DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); | ||||||
| 	diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; | 	diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; | ||||||
| 	diff_opts.single_follow = opt->paths[0]; | 	diff_opts.single_follow = opt->pathspec.raw[0]; | ||||||
| 	diff_opts.break_opt = opt->break_opt; | 	diff_opts.break_opt = opt->break_opt; | ||||||
| 	paths[0] = NULL; | 	paths[0] = NULL; | ||||||
| 	diff_tree_setup_paths(paths, &diff_opts); | 	diff_tree_setup_paths(paths, &diff_opts); | ||||||
|  | @ -369,15 +240,16 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co | ||||||
| 		 * diff_queued_diff, we will also use that as the path in | 		 * diff_queued_diff, we will also use that as the path in | ||||||
| 		 * the future! | 		 * the future! | ||||||
| 		 */ | 		 */ | ||||||
| 		if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) { | 		if ((p->status == 'R' || p->status == 'C') && | ||||||
|  | 		    !strcmp(p->two->path, opt->pathspec.raw[0])) { | ||||||
| 			/* Switch the file-pairs around */ | 			/* Switch the file-pairs around */ | ||||||
| 			q->queue[i] = choice; | 			q->queue[i] = choice; | ||||||
| 			choice = p; | 			choice = p; | ||||||
|  |  | ||||||
| 			/* Update the path we use from now on.. */ | 			/* Update the path we use from now on.. */ | ||||||
| 			diff_tree_release_paths(opt); | 			diff_tree_release_paths(opt); | ||||||
| 			opt->paths[0] = xstrdup(p->one->path); | 			opt->pathspec.raw[0] = xstrdup(p->one->path); | ||||||
| 			diff_tree_setup_paths(opt->paths, opt); | 			diff_tree_setup_paths(opt->pathspec.raw, opt); | ||||||
|  |  | ||||||
| 			/* | 			/* | ||||||
| 			 * The caller expects us to return a set of vanilla | 			 * The caller expects us to return a set of vanilla | ||||||
|  | @ -452,36 +324,12 @@ int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_ | ||||||
| 	return retval; | 	return retval; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int count_paths(const char **paths) |  | ||||||
| { |  | ||||||
| 	int i = 0; |  | ||||||
| 	while (*paths++) |  | ||||||
| 		i++; |  | ||||||
| 	return i; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void diff_tree_release_paths(struct diff_options *opt) | void diff_tree_release_paths(struct diff_options *opt) | ||||||
| { | { | ||||||
| 	free(opt->pathlens); | 	free_pathspec(&opt->pathspec); | ||||||
| } | } | ||||||
|  |  | ||||||
| void diff_tree_setup_paths(const char **p, struct diff_options *opt) | void diff_tree_setup_paths(const char **p, struct diff_options *opt) | ||||||
| { | { | ||||||
| 	opt->nr_paths = 0; | 	init_pathspec(&opt->pathspec, p); | ||||||
| 	opt->pathlens = NULL; |  | ||||||
| 	opt->paths = NULL; |  | ||||||
|  |  | ||||||
| 	if (p) { |  | ||||||
| 		int i; |  | ||||||
|  |  | ||||||
| 		opt->paths = p; |  | ||||||
| 		opt->nr_paths = count_paths(p); |  | ||||||
| 		if (opt->nr_paths == 0) { |  | ||||||
| 			opt->pathlens = NULL; |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 		opt->pathlens = xmalloc(opt->nr_paths * sizeof(int)); |  | ||||||
| 		for (i=0; i < opt->nr_paths; i++) |  | ||||||
| 			opt->pathlens[i] = strlen(p[i]); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										184
									
								
								tree-walk.c
								
								
								
								
							
							
						
						
									
										184
									
								
								tree-walk.c
								
								
								
								
							|  | @ -1,6 +1,7 @@ | ||||||
| #include "cache.h" | #include "cache.h" | ||||||
| #include "tree-walk.h" | #include "tree-walk.h" | ||||||
| #include "unpack-trees.h" | #include "unpack-trees.h" | ||||||
|  | #include "dir.h" | ||||||
| #include "tree.h" | #include "tree.h" | ||||||
|  |  | ||||||
| static const char *get_mode(const char *str, unsigned int *modep) | static const char *get_mode(const char *str, unsigned int *modep) | ||||||
|  | @ -455,3 +456,186 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch | ||||||
| 	free(tree); | 	free(tree); | ||||||
| 	return retval; | 	return retval; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int match_entry(const struct name_entry *entry, int pathlen, | ||||||
|  | 		       const char *match, int matchlen, | ||||||
|  | 		       int *never_interesting) | ||||||
|  | { | ||||||
|  | 	int m = -1; /* signals that we haven't called strncmp() */ | ||||||
|  |  | ||||||
|  | 	if (*never_interesting) { | ||||||
|  | 		/* | ||||||
|  | 		 * We have not seen any match that sorts later | ||||||
|  | 		 * than the current path. | ||||||
|  | 		 */ | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 		 * Does match sort strictly earlier than path | ||||||
|  | 		 * with their common parts? | ||||||
|  | 		 */ | ||||||
|  | 		m = strncmp(match, entry->path, | ||||||
|  | 			    (matchlen < pathlen) ? matchlen : pathlen); | ||||||
|  | 		if (m < 0) | ||||||
|  | 			return 0; | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 		 * If we come here even once, that means there is at | ||||||
|  | 		 * least one pathspec that would sort equal to or | ||||||
|  | 		 * later than the path we are currently looking at. | ||||||
|  | 		 * In other words, if we have never reached this point | ||||||
|  | 		 * after iterating all pathspecs, it means all | ||||||
|  | 		 * pathspecs are either outside of base, or inside the | ||||||
|  | 		 * base but sorts strictly earlier than the current | ||||||
|  | 		 * one.  In either case, they will never match the | ||||||
|  | 		 * subsequent entries.  In such a case, we initialized | ||||||
|  | 		 * the variable to -1 and that is what will be | ||||||
|  | 		 * returned, allowing the caller to terminate early. | ||||||
|  | 		 */ | ||||||
|  | 		*never_interesting = 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (pathlen > matchlen) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	if (matchlen > pathlen) { | ||||||
|  | 		if (match[pathlen] != '/') | ||||||
|  | 			return 0; | ||||||
|  | 		if (!S_ISDIR(entry->mode)) | ||||||
|  | 			return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (m == -1) | ||||||
|  | 		/* | ||||||
|  | 		 * we cheated and did not do strncmp(), so we do | ||||||
|  | 		 * that here. | ||||||
|  | 		 */ | ||||||
|  | 		m = strncmp(match, entry->path, pathlen); | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * If common part matched earlier then it is a hit, | ||||||
|  | 	 * because we rejected the case where path is not a | ||||||
|  | 	 * leading directory and is shorter than match. | ||||||
|  | 	 */ | ||||||
|  | 	if (!m) | ||||||
|  | 		return 1; | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int match_dir_prefix(const char *base, int baselen, | ||||||
|  | 			    const char *match, int matchlen) | ||||||
|  | { | ||||||
|  | 	if (strncmp(base, match, matchlen)) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * If the base is a subdirectory of a path which | ||||||
|  | 	 * was specified, all of them are interesting. | ||||||
|  | 	 */ | ||||||
|  | 	if (!matchlen || | ||||||
|  | 	    base[matchlen] == '/' || | ||||||
|  | 	    match[matchlen - 1] == '/') | ||||||
|  | 		return 1; | ||||||
|  |  | ||||||
|  | 	/* Just a random prefix match */ | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Is a tree entry interesting given the pathspec we have? | ||||||
|  |  * | ||||||
|  |  * Pre-condition: either baselen == base_offset (i.e. empty path) | ||||||
|  |  * or base[baselen-1] == '/' (i.e. with trailing slash). | ||||||
|  |  * | ||||||
|  |  * Return: | ||||||
|  |  *  - 2 for "yes, and all subsequent entries will be" | ||||||
|  |  *  - 1 for yes | ||||||
|  |  *  - zero for no | ||||||
|  |  *  - negative for "no, and no subsequent entries will be either" | ||||||
|  |  */ | ||||||
|  | int tree_entry_interesting(const struct name_entry *entry, | ||||||
|  | 			   struct strbuf *base, int base_offset, | ||||||
|  | 			   const struct pathspec *ps) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 	int pathlen, baselen = base->len - base_offset; | ||||||
|  | 	int never_interesting = ps->has_wildcard ? 0 : -1; | ||||||
|  |  | ||||||
|  | 	if (!ps->nr) { | ||||||
|  | 		if (!ps->recursive || ps->max_depth == -1) | ||||||
|  | 			return 2; | ||||||
|  | 		return !!within_depth(base->buf + base_offset, baselen, | ||||||
|  | 				      !!S_ISDIR(entry->mode), | ||||||
|  | 				      ps->max_depth); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pathlen = tree_entry_len(entry->path, entry->sha1); | ||||||
|  |  | ||||||
|  | 	for (i = ps->nr - 1; i >= 0; i--) { | ||||||
|  | 		const struct pathspec_item *item = ps->items+i; | ||||||
|  | 		const char *match = item->match; | ||||||
|  | 		const char *base_str = base->buf + base_offset; | ||||||
|  | 		int matchlen = item->len; | ||||||
|  |  | ||||||
|  | 		if (baselen >= matchlen) { | ||||||
|  | 			/* If it doesn't match, move along... */ | ||||||
|  | 			if (!match_dir_prefix(base_str, baselen, match, matchlen)) | ||||||
|  | 				goto match_wildcards; | ||||||
|  |  | ||||||
|  | 			if (!ps->recursive || ps->max_depth == -1) | ||||||
|  | 				return 2; | ||||||
|  |  | ||||||
|  | 			return !!within_depth(base_str + matchlen + 1, | ||||||
|  | 					      baselen - matchlen - 1, | ||||||
|  | 					      !!S_ISDIR(entry->mode), | ||||||
|  | 					      ps->max_depth); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/* Does the base match? */ | ||||||
|  | 		if (!strncmp(base_str, match, baselen)) { | ||||||
|  | 			if (match_entry(entry, pathlen, | ||||||
|  | 					match + baselen, matchlen - baselen, | ||||||
|  | 					&never_interesting)) | ||||||
|  | 				return 1; | ||||||
|  |  | ||||||
|  | 			if (ps->items[i].has_wildcard) { | ||||||
|  | 				if (!fnmatch(match + baselen, entry->path, 0)) | ||||||
|  | 					return 1; | ||||||
|  |  | ||||||
|  | 				/* | ||||||
|  | 				 * Match all directories. We'll try to | ||||||
|  | 				 * match files later on. | ||||||
|  | 				 */ | ||||||
|  | 				if (ps->recursive && S_ISDIR(entry->mode)) | ||||||
|  | 					return 1; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | match_wildcards: | ||||||
|  | 		if (!ps->items[i].has_wildcard) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 		 * Concatenate base and entry->path into one and do | ||||||
|  | 		 * fnmatch() on it. | ||||||
|  | 		 */ | ||||||
|  |  | ||||||
|  | 		strbuf_add(base, entry->path, pathlen); | ||||||
|  |  | ||||||
|  | 		if (!fnmatch(match, base->buf + base_offset, 0)) { | ||||||
|  | 			strbuf_setlen(base, base_offset + baselen); | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 		strbuf_setlen(base, base_offset + baselen); | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 		 * Match all directories. We'll try to match files | ||||||
|  | 		 * later on. | ||||||
|  | 		 */ | ||||||
|  | 		if (ps->recursive && S_ISDIR(entry->mode)) | ||||||
|  | 			return 1; | ||||||
|  | 	} | ||||||
|  | 	return never_interesting; /* No matches */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -60,4 +60,6 @@ static inline int traverse_path_len(const struct traverse_info *info, const stru | ||||||
| 	return info->pathlen + tree_entry_len(n->path, n->sha1); | 	return info->pathlen + tree_entry_len(n->path, n->sha1); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -323,7 +323,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) | ||||||
|     } |     } | ||||||
| 	rev.diffopt.format_callback = wt_status_collect_changed_cb; | 	rev.diffopt.format_callback = wt_status_collect_changed_cb; | ||||||
| 	rev.diffopt.format_callback_data = s; | 	rev.diffopt.format_callback_data = s; | ||||||
| 	rev.prune_data = s->pathspec; | 	init_pathspec(&rev.prune_data, s->pathspec); | ||||||
| 	run_diff_files(&rev, 0); | 	run_diff_files(&rev, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -348,20 +348,22 @@ static void wt_status_collect_changes_index(struct wt_status *s) | ||||||
| 	rev.diffopt.detect_rename = 1; | 	rev.diffopt.detect_rename = 1; | ||||||
| 	rev.diffopt.rename_limit = 200; | 	rev.diffopt.rename_limit = 200; | ||||||
| 	rev.diffopt.break_opt = 0; | 	rev.diffopt.break_opt = 0; | ||||||
| 	rev.prune_data = s->pathspec; | 	init_pathspec(&rev.prune_data, s->pathspec); | ||||||
| 	run_diff_index(&rev, 1); | 	run_diff_index(&rev, 1); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void wt_status_collect_changes_initial(struct wt_status *s) | static void wt_status_collect_changes_initial(struct wt_status *s) | ||||||
| { | { | ||||||
|  | 	struct pathspec pathspec; | ||||||
| 	int i; | 	int i; | ||||||
|  |  | ||||||
|  | 	init_pathspec(&pathspec, s->pathspec); | ||||||
| 	for (i = 0; i < active_nr; i++) { | 	for (i = 0; i < active_nr; i++) { | ||||||
| 		struct string_list_item *it; | 		struct string_list_item *it; | ||||||
| 		struct wt_status_change_data *d; | 		struct wt_status_change_data *d; | ||||||
| 		struct cache_entry *ce = active_cache[i]; | 		struct cache_entry *ce = active_cache[i]; | ||||||
|  |  | ||||||
| 		if (!ce_path_match(ce, s->pathspec)) | 		if (!ce_path_match(ce, &pathspec)) | ||||||
| 			continue; | 			continue; | ||||||
| 		it = string_list_insert(&s->change, ce->name); | 		it = string_list_insert(&s->change, ce->name); | ||||||
| 		d = it->util; | 		d = it->util; | ||||||
|  | @ -376,6 +378,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s) | ||||||
| 		else | 		else | ||||||
| 			d->index_status = DIFF_STATUS_ADDED; | 			d->index_status = DIFF_STATUS_ADDED; | ||||||
| 	} | 	} | ||||||
|  | 	free_pathspec(&pathspec); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void wt_status_collect_untracked(struct wt_status *s) | static void wt_status_collect_untracked(struct wt_status *s) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano