Merge branch 'jc/cache-tree'
* jc/cache-tree: Avoid "diff-index --cached" optimization under --find-copies-harder Optimize "diff-index --cached" using cache-tree t4007: modernize the style cache-tree.c::cache_tree_find(): simplify internal API write-tree --ignore-cache-treemaint
						commit
						c28a17f270
					
				|  | @ -13,7 +13,7 @@ static const char write_tree_usage[] = | ||||||
|  |  | ||||||
| int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) | int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) | ||||||
| { | { | ||||||
| 	int missing_ok = 0, ret; | 	int flags = 0, ret; | ||||||
| 	const char *prefix = NULL; | 	const char *prefix = NULL; | ||||||
| 	unsigned char sha1[20]; | 	unsigned char sha1[20]; | ||||||
| 	const char *me = "git-write-tree"; | 	const char *me = "git-write-tree"; | ||||||
|  | @ -22,9 +22,15 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) | ||||||
| 	while (1 < argc) { | 	while (1 < argc) { | ||||||
| 		const char *arg = argv[1]; | 		const char *arg = argv[1]; | ||||||
| 		if (!strcmp(arg, "--missing-ok")) | 		if (!strcmp(arg, "--missing-ok")) | ||||||
| 			missing_ok = 1; | 			flags |= WRITE_TREE_MISSING_OK; | ||||||
| 		else if (!prefixcmp(arg, "--prefix=")) | 		else if (!prefixcmp(arg, "--prefix=")) | ||||||
| 			prefix = arg + 9; | 			prefix = arg + 9; | ||||||
|  | 		else if (!prefixcmp(arg, "--ignore-cache-tree")) | ||||||
|  | 			/* | ||||||
|  | 			 * This is only useful for debugging, so I | ||||||
|  | 			 * do not bother documenting it. | ||||||
|  | 			 */ | ||||||
|  | 			flags |= WRITE_TREE_IGNORE_CACHE_TREE; | ||||||
| 		else | 		else | ||||||
| 			usage(write_tree_usage); | 			usage(write_tree_usage); | ||||||
| 		argc--; argv++; | 		argc--; argv++; | ||||||
|  | @ -33,7 +39,7 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) | ||||||
| 	if (argc > 2) | 	if (argc > 2) | ||||||
| 		die("too many options"); | 		die("too many options"); | ||||||
|  |  | ||||||
| 	ret = write_cache_as_tree(sha1, missing_ok, prefix); | 	ret = write_cache_as_tree(sha1, flags, prefix); | ||||||
| 	switch (ret) { | 	switch (ret) { | ||||||
| 	case 0: | 	case 0: | ||||||
| 		printf("%s\n", sha1_to_hex(sha1)); | 		printf("%s\n", sha1_to_hex(sha1)); | ||||||
|  |  | ||||||
							
								
								
									
										44
									
								
								cache-tree.c
								
								
								
								
							
							
						
						
									
										44
									
								
								cache-tree.c
								
								
								
								
							|  | @ -514,6 +514,8 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size) | ||||||
|  |  | ||||||
| static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path) | static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path) | ||||||
| { | { | ||||||
|  | 	if (!it) | ||||||
|  | 		return NULL; | ||||||
| 	while (*path) { | 	while (*path) { | ||||||
| 		const char *slash; | 		const char *slash; | ||||||
| 		struct cache_tree_sub *sub; | 		struct cache_tree_sub *sub; | ||||||
|  | @ -538,28 +540,32 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat | ||||||
| 	return it; | 	return it; | ||||||
| } | } | ||||||
|  |  | ||||||
| int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix) | int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) | ||||||
| { | { | ||||||
| 	int entries, was_valid, newfd; | 	int entries, was_valid, newfd; | ||||||
|  | 	struct lock_file *lock_file; | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
| 	 * We can't free this memory, it becomes part of a linked list | 	 * We can't free this memory, it becomes part of a linked list | ||||||
| 	 * parsed atexit() | 	 * parsed atexit() | ||||||
| 	 */ | 	 */ | ||||||
| 	struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); | 	lock_file = xcalloc(1, sizeof(struct lock_file)); | ||||||
|  |  | ||||||
| 	newfd = hold_locked_index(lock_file, 1); | 	newfd = hold_locked_index(lock_file, 1); | ||||||
|  |  | ||||||
| 	entries = read_cache(); | 	entries = read_cache(); | ||||||
| 	if (entries < 0) | 	if (entries < 0) | ||||||
| 		return WRITE_TREE_UNREADABLE_INDEX; | 		return WRITE_TREE_UNREADABLE_INDEX; | ||||||
|  | 	if (flags & WRITE_TREE_IGNORE_CACHE_TREE) | ||||||
|  | 		cache_tree_free(&(active_cache_tree)); | ||||||
|  |  | ||||||
| 	if (!active_cache_tree) | 	if (!active_cache_tree) | ||||||
| 		active_cache_tree = cache_tree(); | 		active_cache_tree = cache_tree(); | ||||||
|  |  | ||||||
| 	was_valid = cache_tree_fully_valid(active_cache_tree); | 	was_valid = cache_tree_fully_valid(active_cache_tree); | ||||||
|  |  | ||||||
| 	if (!was_valid) { | 	if (!was_valid) { | ||||||
|  | 		int missing_ok = flags & WRITE_TREE_MISSING_OK; | ||||||
|  |  | ||||||
| 		if (cache_tree_update(active_cache_tree, | 		if (cache_tree_update(active_cache_tree, | ||||||
| 				      active_cache, active_nr, | 				      active_cache, active_nr, | ||||||
| 				      missing_ok, 0) < 0) | 				      missing_ok, 0) < 0) | ||||||
|  | @ -625,3 +631,35 @@ void prime_cache_tree(struct cache_tree **it, struct tree *tree) | ||||||
| 	*it = cache_tree(); | 	*it = cache_tree(); | ||||||
| 	prime_cache_tree_rec(*it, tree); | 	prime_cache_tree_rec(*it, tree); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * find the cache_tree that corresponds to the current level without | ||||||
|  |  * exploding the full path into textual form.  The root of the | ||||||
|  |  * cache tree is given as "root", and our current level is "info". | ||||||
|  |  * (1) When at root level, info->prev is NULL, so it is "root" itself. | ||||||
|  |  * (2) Otherwise, find the cache_tree that corresponds to one level | ||||||
|  |  *     above us, and find ourselves in there. | ||||||
|  |  */ | ||||||
|  | static struct cache_tree *find_cache_tree_from_traversal(struct cache_tree *root, | ||||||
|  | 							 struct traverse_info *info) | ||||||
|  | { | ||||||
|  | 	struct cache_tree *our_parent; | ||||||
|  |  | ||||||
|  | 	if (!info->prev) | ||||||
|  | 		return root; | ||||||
|  | 	our_parent = find_cache_tree_from_traversal(root, info->prev); | ||||||
|  | 	return cache_tree_find(our_parent, info->name.path); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int cache_tree_matches_traversal(struct cache_tree *root, | ||||||
|  | 				 struct name_entry *ent, | ||||||
|  | 				 struct traverse_info *info) | ||||||
|  | { | ||||||
|  | 	struct cache_tree *it; | ||||||
|  |  | ||||||
|  | 	it = find_cache_tree_from_traversal(root, info); | ||||||
|  | 	it = cache_tree_find(it, ent->path); | ||||||
|  | 	if (it && it->entry_count > 0 && !hashcmp(ent->sha1, it->sha1)) | ||||||
|  | 		return it->entry_count; | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								cache-tree.h
								
								
								
								
							
							
						
						
									
										10
									
								
								cache-tree.h
								
								
								
								
							|  | @ -2,6 +2,7 @@ | ||||||
| #define CACHE_TREE_H | #define CACHE_TREE_H | ||||||
|  |  | ||||||
| #include "tree.h" | #include "tree.h" | ||||||
|  | #include "tree-walk.h" | ||||||
|  |  | ||||||
| struct cache_tree; | struct cache_tree; | ||||||
| struct cache_tree_sub { | struct cache_tree_sub { | ||||||
|  | @ -30,11 +31,18 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); | ||||||
| int cache_tree_fully_valid(struct cache_tree *); | int cache_tree_fully_valid(struct cache_tree *); | ||||||
| int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int); | int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int); | ||||||
|  |  | ||||||
|  | /* bitmasks to write_cache_as_tree flags */ | ||||||
|  | #define WRITE_TREE_MISSING_OK 1 | ||||||
|  | #define WRITE_TREE_IGNORE_CACHE_TREE 2 | ||||||
|  |  | ||||||
|  | /* error return codes */ | ||||||
| #define WRITE_TREE_UNREADABLE_INDEX (-1) | #define WRITE_TREE_UNREADABLE_INDEX (-1) | ||||||
| #define WRITE_TREE_UNMERGED_INDEX (-2) | #define WRITE_TREE_UNMERGED_INDEX (-2) | ||||||
| #define WRITE_TREE_PREFIX_ERROR (-3) | #define WRITE_TREE_PREFIX_ERROR (-3) | ||||||
|  |  | ||||||
| int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix); | int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix); | ||||||
| void prime_cache_tree(struct cache_tree **, struct tree *); | void prime_cache_tree(struct cache_tree **, struct tree *); | ||||||
|  |  | ||||||
|  | extern int cache_tree_matches_traversal(struct cache_tree *, struct name_entry *ent, struct traverse_info *info); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -446,6 +446,8 @@ int run_diff_index(struct rev_info *revs, int cached) | ||||||
| 	memset(&opts, 0, sizeof(opts)); | 	memset(&opts, 0, sizeof(opts)); | ||||||
| 	opts.head_idx = 1; | 	opts.head_idx = 1; | ||||||
| 	opts.index_only = cached; | 	opts.index_only = cached; | ||||||
|  | 	opts.diff_index_cached = (cached && | ||||||
|  | 				  !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)); | ||||||
| 	opts.merge = 1; | 	opts.merge = 1; | ||||||
| 	opts.fn = oneway_diff; | 	opts.fn = oneway_diff; | ||||||
| 	opts.unpack_data = revs; | 	opts.unpack_data = revs; | ||||||
|  | @ -502,6 +504,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) | ||||||
| 	memset(&opts, 0, sizeof(opts)); | 	memset(&opts, 0, sizeof(opts)); | ||||||
| 	opts.head_idx = 1; | 	opts.head_idx = 1; | ||||||
| 	opts.index_only = 1; | 	opts.index_only = 1; | ||||||
|  | 	opts.diff_index_cached = !DIFF_OPT_TST(opt, FIND_COPIES_HARDER); | ||||||
| 	opts.merge = 1; | 	opts.merge = 1; | ||||||
| 	opts.fn = oneway_diff; | 	opts.fn = oneway_diff; | ||||||
| 	opts.unpack_data = &revs; | 	opts.unpack_data = &revs; | ||||||
|  |  | ||||||
|  | @ -9,32 +9,36 @@ test_description='Rename interaction with pathspec. | ||||||
| . ./test-lib.sh | . ./test-lib.sh | ||||||
| . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash | . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success 'prepare reference tree' ' | ||||||
|     'prepare reference tree' \ | 	mkdir path0 path1 && | ||||||
|     'mkdir path0 path1 && | 	cp "$TEST_DIRECTORY"/../COPYING path0/COPYING && | ||||||
|      cp "$TEST_DIRECTORY"/../COPYING path0/COPYING && | 	git update-index --add path0/COPYING && | ||||||
|      git update-index --add path0/COPYING && | 	tree=$(git write-tree) && | ||||||
|     tree=$(git write-tree) && | 	echo $tree | ||||||
|     echo $tree' | ' | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success 'prepare work tree' ' | ||||||
|     'prepare work tree' \ | 	cp path0/COPYING path1/COPYING && | ||||||
|     'cp path0/COPYING path1/COPYING && | 	git update-index --add --remove path0/COPYING path1/COPYING | ||||||
|      git update-index --add --remove path0/COPYING path1/COPYING' | ' | ||||||
|  |  | ||||||
| # In the tree, there is only path0/COPYING.  In the cache, path0 and | # In the tree, there is only path0/COPYING.  In the cache, path0 and | ||||||
| # path1 both have COPYING and the latter is a copy of path0/COPYING. | # path1 both have COPYING and the latter is a copy of path0/COPYING. | ||||||
| # Comparing the full tree with cache should tell us so. | # Comparing the full tree with cache should tell us so. | ||||||
|  |  | ||||||
| git diff-index -C --find-copies-harder $tree >current |  | ||||||
|  |  | ||||||
| cat >expected <<\EOF | cat >expected <<\EOF | ||||||
| :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100	path0/COPYING	path1/COPYING | :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100	path0/COPYING	path1/COPYING | ||||||
| EOF | EOF | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success 'copy detection' ' | ||||||
|     'validate the result (#1)' \ | 	git diff-index -C --find-copies-harder $tree >current && | ||||||
|     'compare_diff_raw current expected' | 	compare_diff_raw current expected | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'copy detection, cached' ' | ||||||
|  | 	git diff-index -C --find-copies-harder --cached $tree >current && | ||||||
|  | 	compare_diff_raw current expected | ||||||
|  | ' | ||||||
|  |  | ||||||
| # In the tree, there is only path0/COPYING.  In the cache, path0 and | # In the tree, there is only path0/COPYING.  In the cache, path0 and | ||||||
| # path1 both have COPYING and the latter is a copy of path0/COPYING. | # path1 both have COPYING and the latter is a copy of path0/COPYING. | ||||||
|  | @ -42,49 +46,45 @@ test_expect_success \ | ||||||
| # path1/COPYING suddenly appearing from nowhere, not detected as | # path1/COPYING suddenly appearing from nowhere, not detected as | ||||||
| # a copy from path0/COPYING. | # a copy from path0/COPYING. | ||||||
|  |  | ||||||
| git diff-index -C $tree path1 >current |  | ||||||
|  |  | ||||||
| cat >expected <<\EOF | cat >expected <<\EOF | ||||||
| :000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A	path1/COPYING | :000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A	path1/COPYING | ||||||
| EOF | EOF | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success 'copy, limited to a subtree' ' | ||||||
|     'validate the result (#2)' \ | 	git diff-index -C --find-copies-harder $tree path1 >current && | ||||||
|     'compare_diff_raw current expected' | 	compare_diff_raw current expected | ||||||
|  | ' | ||||||
| test_expect_success \ |  | ||||||
|     'tweak work tree' \ |  | ||||||
|     'rm -f path0/COPYING && |  | ||||||
|      git update-index --remove path0/COPYING' |  | ||||||
|  |  | ||||||
|  | test_expect_success 'tweak work tree' ' | ||||||
|  | 	rm -f path0/COPYING && | ||||||
|  | 	git update-index --remove path0/COPYING | ||||||
|  | ' | ||||||
| # In the tree, there is only path0/COPYING.  In the cache, path0 does | # In the tree, there is only path0/COPYING.  In the cache, path0 does | ||||||
| # not have COPYING anymore and path1 has COPYING which is a copy of | # not have COPYING anymore and path1 has COPYING which is a copy of | ||||||
| # path0/COPYING.  Showing the full tree with cache should tell us about | # path0/COPYING.  Showing the full tree with cache should tell us about | ||||||
| # the rename. | # the rename. | ||||||
|  |  | ||||||
| git diff-index -C $tree >current |  | ||||||
|  |  | ||||||
| cat >expected <<\EOF | cat >expected <<\EOF | ||||||
| :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100	path0/COPYING	path1/COPYING | :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100	path0/COPYING	path1/COPYING | ||||||
| EOF | EOF | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success 'rename detection' ' | ||||||
|     'validate the result (#3)' \ | 	git diff-index -C --find-copies-harder $tree >current && | ||||||
|     'compare_diff_raw current expected' | 	compare_diff_raw current expected | ||||||
|  | ' | ||||||
|  |  | ||||||
| # In the tree, there is only path0/COPYING.  In the cache, path0 does | # In the tree, there is only path0/COPYING.  In the cache, path0 does | ||||||
| # not have COPYING anymore and path1 has COPYING which is a copy of | # not have COPYING anymore and path1 has COPYING which is a copy of | ||||||
| # path0/COPYING.  When we say we care only about path1, we should just | # path0/COPYING.  When we say we care only about path1, we should just | ||||||
| # see path1/COPYING appearing from nowhere. | # see path1/COPYING appearing from nowhere. | ||||||
|  |  | ||||||
| git diff-index -C $tree path1 >current |  | ||||||
|  |  | ||||||
| cat >expected <<\EOF | cat >expected <<\EOF | ||||||
| :000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A	path1/COPYING | :000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A	path1/COPYING | ||||||
| EOF | EOF | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success 'rename, limited to a subtree' ' | ||||||
|     'validate the result (#4)' \ | 	git diff-index -C --find-copies-harder $tree path1 >current && | ||||||
|     'compare_diff_raw current expected' | 	compare_diff_raw current expected | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_done | test_done | ||||||
|  |  | ||||||
|  | @ -326,6 +326,23 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str | ||||||
| 			if (src[0]) | 			if (src[0]) | ||||||
| 				conflicts |= 1; | 				conflicts |= 1; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		/* special case: "diff-index --cached" looking at a tree */ | ||||||
|  | 		if (o->diff_index_cached && | ||||||
|  | 		    n == 1 && dirmask == 1 && S_ISDIR(names->mode)) { | ||||||
|  | 			int matches; | ||||||
|  | 			matches = cache_tree_matches_traversal(o->src_index->cache_tree, | ||||||
|  | 							       names, info); | ||||||
|  | 			/* | ||||||
|  | 			 * Everything under the name matches.  Adjust o->pos to | ||||||
|  | 			 * skip the entire hierarchy. | ||||||
|  | 			 */ | ||||||
|  | 			if (matches) { | ||||||
|  | 				o->pos += matches; | ||||||
|  | 				return mask; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		if (traverse_trees_recursive(n, dirmask, conflicts, | 		if (traverse_trees_recursive(n, dirmask, conflicts, | ||||||
| 					     names, info) < 0) | 					     names, info) < 0) | ||||||
| 			return -1; | 			return -1; | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ struct unpack_trees_options { | ||||||
| 		     aggressive:1, | 		     aggressive:1, | ||||||
| 		     skip_unmerged:1, | 		     skip_unmerged:1, | ||||||
| 		     initial_checkout:1, | 		     initial_checkout:1, | ||||||
|  | 		     diff_index_cached:1, | ||||||
| 		     gently:1; | 		     gently:1; | ||||||
| 	const char *prefix; | 	const char *prefix; | ||||||
| 	int pos; | 	int pos; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano