Merge branch 'sl/clean-d-ignored-fix' into maint
"git clean -d" used to clean directories that has ignored files, even though the command should not lose ignored ones without "-x". "git status --ignored" did not list ignored and untracked files without "-uall". These have been corrected. * sl/clean-d-ignored-fix: clean: teach clean -d to preserve ignored paths dir: expose cmp_name() and check_contains() dir: hide untracked contents of untracked dirs dir: recurse into untracked dirs for ignored files t7061: status --ignored should search untracked dirs t7300: clean -d should skip dirs with ignored filesmaint
						commit
						f4683b4e9c
					
				|  | @ -33,6 +33,12 @@ The notable options are: | ||||||
| 	Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]` | 	Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]` | ||||||
| 	in addition to untracked files in `entries[]`. | 	in addition to untracked files in `entries[]`. | ||||||
|  |  | ||||||
|  | `DIR_KEEP_UNTRACKED_CONTENTS`::: | ||||||
|  |  | ||||||
|  | 	Only has meaning if `DIR_SHOW_IGNORED_TOO` is also set; if this is set, the | ||||||
|  | 	untracked contents of untracked directories are also returned in | ||||||
|  | 	`entries[]`. | ||||||
|  |  | ||||||
| `DIR_COLLECT_IGNORED`::: | `DIR_COLLECT_IGNORED`::: | ||||||
|  |  | ||||||
| 	Special mode for git-add. Return ignored files in `ignored[]` and | 	Special mode for git-add. Return ignored files in `ignored[]` and | ||||||
|  |  | ||||||
|  | @ -857,6 +857,38 @@ static void interactive_main_loop(void) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static void correct_untracked_entries(struct dir_struct *dir) | ||||||
|  | { | ||||||
|  | 	int src, dst, ign; | ||||||
|  |  | ||||||
|  | 	for (src = dst = ign = 0; src < dir->nr; src++) { | ||||||
|  | 		/* skip paths in ignored[] that cannot be inside entries[src] */ | ||||||
|  | 		while (ign < dir->ignored_nr && | ||||||
|  | 		       0 <= cmp_dir_entry(&dir->entries[src], &dir->ignored[ign])) | ||||||
|  | 			ign++; | ||||||
|  |  | ||||||
|  | 		if (ign < dir->ignored_nr && | ||||||
|  | 		    check_dir_entry_contains(dir->entries[src], dir->ignored[ign])) { | ||||||
|  | 			/* entries[src] contains an ignored path, so we drop it */ | ||||||
|  | 			free(dir->entries[src]); | ||||||
|  | 		} else { | ||||||
|  | 			struct dir_entry *ent = dir->entries[src++]; | ||||||
|  |  | ||||||
|  | 			/* entries[src] does not contain an ignored path, so we keep it */ | ||||||
|  | 			dir->entries[dst++] = ent; | ||||||
|  |  | ||||||
|  | 			/* then discard paths in entries[] contained inside entries[src] */ | ||||||
|  | 			while (src < dir->nr && | ||||||
|  | 			       check_dir_entry_contains(ent, dir->entries[src])) | ||||||
|  | 				free(dir->entries[src++]); | ||||||
|  |  | ||||||
|  | 			/* compensate for the outer loop's loop control */ | ||||||
|  | 			src--; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	dir->nr = dst; | ||||||
|  | } | ||||||
|  |  | ||||||
| int cmd_clean(int argc, const char **argv, const char *prefix) | int cmd_clean(int argc, const char **argv, const char *prefix) | ||||||
| { | { | ||||||
| 	int i, res; | 	int i, res; | ||||||
|  | @ -916,6 +948,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) | ||||||
|  |  | ||||||
| 	dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; | 	dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; | ||||||
|  |  | ||||||
|  | 	if (remove_directories) | ||||||
|  | 		dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS; | ||||||
|  |  | ||||||
| 	if (read_cache() < 0) | 	if (read_cache() < 0) | ||||||
| 		die(_("index file corrupt")); | 		die(_("index file corrupt")); | ||||||
|  |  | ||||||
|  | @ -931,6 +966,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) | ||||||
| 		       prefix, argv); | 		       prefix, argv); | ||||||
|  |  | ||||||
| 	fill_directory(&dir, &pathspec); | 	fill_directory(&dir, &pathspec); | ||||||
|  | 	correct_untracked_entries(&dir); | ||||||
|  |  | ||||||
| 	for (i = 0; i < dir.nr; i++) { | 	for (i = 0; i < dir.nr; i++) { | ||||||
| 		struct dir_entry *ent = dir.entries[i]; | 		struct dir_entry *ent = dir.entries[i]; | ||||||
|  | @ -958,6 +994,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix) | ||||||
| 		string_list_append(&del_list, rel); | 		string_list_append(&del_list, rel); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	for (i = 0; i < dir.nr; i++) | ||||||
|  | 		free(dir.entries[i]); | ||||||
|  |  | ||||||
|  | 	for (i = 0; i < dir.ignored_nr; i++) | ||||||
|  | 		free(dir.ignored[i]); | ||||||
|  |  | ||||||
| 	if (interactive && del_list.nr > 0) | 	if (interactive && del_list.nr > 0) | ||||||
| 		interactive_main_loop(); | 		interactive_main_loop(); | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										43
									
								
								dir.c
								
								
								
								
							
							
						
						
									
										43
									
								
								dir.c
								
								
								
								
							|  | @ -1784,7 +1784,10 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, | ||||||
| 			dir_state = state; | 			dir_state = state; | ||||||
|  |  | ||||||
| 		/* recurse into subdir if instructed by treat_path */ | 		/* recurse into subdir if instructed by treat_path */ | ||||||
| 		if (state == path_recurse) { | 		if ((state == path_recurse) || | ||||||
|  | 			((state == path_untracked) && | ||||||
|  | 			 (dir->flags & DIR_SHOW_IGNORED_TOO) && | ||||||
|  | 			 (get_dtype(cdir.de, path.buf, path.len) == DT_DIR))) { | ||||||
| 			struct untracked_cache_dir *ud; | 			struct untracked_cache_dir *ud; | ||||||
| 			ud = lookup_untracked(dir->untracked, untracked, | 			ud = lookup_untracked(dir->untracked, untracked, | ||||||
| 					      path.buf + baselen, | 					      path.buf + baselen, | ||||||
|  | @ -1839,7 +1842,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, | ||||||
| 	return dir_state; | 	return dir_state; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int cmp_name(const void *p1, const void *p2) | int cmp_dir_entry(const void *p1, const void *p2) | ||||||
| { | { | ||||||
| 	const struct dir_entry *e1 = *(const struct dir_entry **)p1; | 	const struct dir_entry *e1 = *(const struct dir_entry **)p1; | ||||||
| 	const struct dir_entry *e2 = *(const struct dir_entry **)p2; | 	const struct dir_entry *e2 = *(const struct dir_entry **)p2; | ||||||
|  | @ -1847,6 +1850,14 @@ static int cmp_name(const void *p1, const void *p2) | ||||||
| 	return name_compare(e1->name, e1->len, e2->name, e2->len); | 	return name_compare(e1->name, e1->len, e2->name, e2->len); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* check if *out lexically strictly contains *in */ | ||||||
|  | int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in) | ||||||
|  | { | ||||||
|  | 	return (out->len < in->len) && | ||||||
|  | 		(out->name[out->len - 1] == '/') && | ||||||
|  | 		!memcmp(out->name, in->name, out->len); | ||||||
|  | } | ||||||
|  |  | ||||||
| static int treat_leading_path(struct dir_struct *dir, | static int treat_leading_path(struct dir_struct *dir, | ||||||
| 			      const char *path, int len, | 			      const char *path, int len, | ||||||
| 			      const struct pathspec *pathspec) | 			      const struct pathspec *pathspec) | ||||||
|  | @ -2060,8 +2071,32 @@ int read_directory(struct dir_struct *dir, const char *path, | ||||||
| 		dir->untracked = NULL; | 		dir->untracked = NULL; | ||||||
| 	if (!len || treat_leading_path(dir, path, len, pathspec)) | 	if (!len || treat_leading_path(dir, path, len, pathspec)) | ||||||
| 		read_directory_recursive(dir, path, len, untracked, 0, pathspec); | 		read_directory_recursive(dir, path, len, untracked, 0, pathspec); | ||||||
| 	QSORT(dir->entries, dir->nr, cmp_name); | 	QSORT(dir->entries, dir->nr, cmp_dir_entry); | ||||||
| 	QSORT(dir->ignored, dir->ignored_nr, cmp_name); | 	QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry); | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * If DIR_SHOW_IGNORED_TOO is set, read_directory_recursive() will | ||||||
|  | 	 * also pick up untracked contents of untracked dirs; by default | ||||||
|  | 	 * we discard these, but given DIR_KEEP_UNTRACKED_CONTENTS we do not. | ||||||
|  | 	 */ | ||||||
|  | 	if ((dir->flags & DIR_SHOW_IGNORED_TOO) && | ||||||
|  | 		     !(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) { | ||||||
|  | 		int i, j; | ||||||
|  |  | ||||||
|  | 		/* remove from dir->entries untracked contents of untracked dirs */ | ||||||
|  | 		for (i = j = 0; j < dir->nr; j++) { | ||||||
|  | 			if (i && | ||||||
|  | 			    check_dir_entry_contains(dir->entries[i - 1], dir->entries[j])) { | ||||||
|  | 				free(dir->entries[j]); | ||||||
|  | 				dir->entries[j] = NULL; | ||||||
|  | 			} else { | ||||||
|  | 				dir->entries[i++] = dir->entries[j]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		dir->nr = i; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if (dir->untracked) { | 	if (dir->untracked) { | ||||||
| 		static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS); | 		static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS); | ||||||
| 		trace_printf_key(&trace_untracked_stats, | 		trace_printf_key(&trace_untracked_stats, | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								dir.h
								
								
								
								
							
							
						
						
									
										6
									
								
								dir.h
								
								
								
								
							|  | @ -151,7 +151,8 @@ struct dir_struct { | ||||||
| 		DIR_NO_GITLINKS = 1<<3, | 		DIR_NO_GITLINKS = 1<<3, | ||||||
| 		DIR_COLLECT_IGNORED = 1<<4, | 		DIR_COLLECT_IGNORED = 1<<4, | ||||||
| 		DIR_SHOW_IGNORED_TOO = 1<<5, | 		DIR_SHOW_IGNORED_TOO = 1<<5, | ||||||
| 		DIR_COLLECT_KILLED_ONLY = 1<<6 | 		DIR_COLLECT_KILLED_ONLY = 1<<6, | ||||||
|  | 		DIR_KEEP_UNTRACKED_CONTENTS = 1<<7 | ||||||
| 	} flags; | 	} flags; | ||||||
| 	struct dir_entry **entries; | 	struct dir_entry **entries; | ||||||
| 	struct dir_entry **ignored; | 	struct dir_entry **ignored; | ||||||
|  | @ -326,6 +327,9 @@ static inline int dir_path_match(const struct dir_entry *ent, | ||||||
| 			      has_trailing_dir); | 			      has_trailing_dir); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int cmp_dir_entry(const void *p1, const void *p2); | ||||||
|  | int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in); | ||||||
|  |  | ||||||
| void untracked_cache_invalidate_path(struct index_state *, const char *); | void untracked_cache_invalidate_path(struct index_state *, const char *); | ||||||
| void untracked_cache_remove_from_index(struct index_state *, const char *); | void untracked_cache_remove_from_index(struct index_state *, const char *); | ||||||
| void untracked_cache_add_to_index(struct index_state *, const char *); | void untracked_cache_add_to_index(struct index_state *, const char *); | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ cat >expected <<\EOF | ||||||
| ?? actual | ?? actual | ||||||
| ?? expected | ?? expected | ||||||
| ?? untracked/ | ?? untracked/ | ||||||
|  | !! untracked/ignored | ||||||
| EOF | EOF | ||||||
|  |  | ||||||
| test_expect_success 'status untracked directory with --ignored' ' | test_expect_success 'status untracked directory with --ignored' ' | ||||||
|  |  | ||||||
|  | @ -653,4 +653,20 @@ test_expect_success 'git clean -d respects pathspecs (pathspec is prefix of dir) | ||||||
| 	test_path_is_dir foobar | 	test_path_is_dir foobar | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'git clean -d skips untracked dirs containing ignored files' ' | ||||||
|  | 	echo /foo/bar >.gitignore && | ||||||
|  | 	echo ignoreme >>.gitignore && | ||||||
|  | 	rm -rf foo && | ||||||
|  | 	mkdir -p foo/a/aa/aaa foo/b/bb/bbb && | ||||||
|  | 	touch foo/bar foo/baz foo/a/aa/ignoreme foo/b/ignoreme foo/b/bb/1 foo/b/bb/2 && | ||||||
|  | 	git clean -df && | ||||||
|  | 	test_path_is_dir foo && | ||||||
|  | 	test_path_is_file foo/bar && | ||||||
|  | 	test_path_is_missing foo/baz && | ||||||
|  | 	test_path_is_file foo/a/aa/ignoreme && | ||||||
|  | 	test_path_is_missing foo/a/aa/aaa && | ||||||
|  | 	test_path_is_file foo/b/ignoreme && | ||||||
|  | 	test_path_is_missing foo/b/bb | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_done | test_done | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano