Merge branch 'jk/ref-paranoia'
The ref iteration code used to optionally allow dangling refs to be shown, which has been tightened up. * jk/ref-paranoia: refs: drop "broken" flag from for_each_fullref_in() ref-filter: drop broken-ref code entirely ref-filter: stop setting FILTER_REFS_INCLUDE_BROKEN repack, prune: drop GIT_REF_PARANOIA settings refs: turn on GIT_REF_PARANOIA by default refs: omit dangling symrefs when using GIT_REF_PARANOIA refs: add DO_FOR_EACH_OMIT_DANGLING_SYMREFS flag refs-internal.h: reorganize DO_FOR_EACH_* flag documentation refs-internal.h: move DO_FOR_EACH_* flags next to each other t5312: be more assertive about command failure t5312: test non-destructive repack t5312: create bogus ref as necessary t5312: drop "verbose" helper t5600: provide detached HEAD for corruption failures t5516: don't use HEAD ref for invalid ref-deletion tests t7900: clean up some more broken refsmaint
						commit
						f6c075ad71
					
				|  | @ -867,15 +867,16 @@ for full details. | ||||||
| 	end user, to be recorded in the body of the reflog. | 	end user, to be recorded in the body of the reflog. | ||||||
|  |  | ||||||
| `GIT_REF_PARANOIA`:: | `GIT_REF_PARANOIA`:: | ||||||
| 	If set to `1`, include broken or badly named refs when iterating | 	If set to `0`, ignore broken or badly named refs when iterating | ||||||
| 	over lists of refs. In a normal, non-corrupted repository, this | 	over lists of refs. Normally Git will try to include any such | ||||||
| 	does nothing. However, enabling it may help git to detect and | 	refs, which may cause some operations to fail. This is usually | ||||||
| 	abort some operations in the presence of broken refs. Git sets | 	preferable, as potentially destructive operations (e.g., | ||||||
| 	this variable automatically when performing destructive | 	linkgit:git-prune[1]) are better off aborting rather than | ||||||
| 	operations like linkgit:git-prune[1]. You should not need to set | 	ignoring broken refs (and thus considering the history they | ||||||
| 	it yourself unless you want to be paranoid about making sure | 	point to as not worth saving). The default value is `1` (i.e., | ||||||
| 	an operation has touched every ref (e.g., because you are | 	be paranoid about detecting and aborting all operations). You | ||||||
| 	cloning a repository to make a backup). | 	should not normally need to set this to `0`, but it may be | ||||||
|  | 	useful when trying to salvage data from a corrupted repository. | ||||||
|  |  | ||||||
| `GIT_ALLOW_PROTOCOL`:: | `GIT_ALLOW_PROTOCOL`:: | ||||||
| 	If set to a colon-separated list of protocols, behave as if | 	If set to a colon-separated list of protocols, behave as if | ||||||
|  |  | ||||||
|  | @ -427,7 +427,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin | ||||||
|  |  | ||||||
| 	memset(&array, 0, sizeof(array)); | 	memset(&array, 0, sizeof(array)); | ||||||
|  |  | ||||||
| 	filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); | 	filter_refs(&array, filter, filter->kind); | ||||||
|  |  | ||||||
| 	if (filter->verbose) | 	if (filter->verbose) | ||||||
| 		maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); | 		maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); | ||||||
|  |  | ||||||
|  | @ -77,7 +77,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) | ||||||
|  |  | ||||||
| 	filter.name_patterns = argv; | 	filter.name_patterns = argv; | ||||||
| 	filter.match_as_path = 1; | 	filter.match_as_path = 1; | ||||||
| 	filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); | 	filter_refs(&array, &filter, FILTER_REFS_ALL); | ||||||
| 	ref_array_sort(sorting, &array); | 	ref_array_sort(sorting, &array); | ||||||
|  |  | ||||||
| 	if (!maxcount || array.nr < maxcount) | 	if (!maxcount || array.nr < maxcount) | ||||||
|  |  | ||||||
|  | @ -143,7 +143,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix) | ||||||
| 	expire = TIME_MAX; | 	expire = TIME_MAX; | ||||||
| 	save_commit_buffer = 0; | 	save_commit_buffer = 0; | ||||||
| 	read_replace_refs = 0; | 	read_replace_refs = 0; | ||||||
| 	ref_paranoia = 1; |  | ||||||
| 	repo_init_revisions(the_repository, &revs, prefix); | 	repo_init_revisions(the_repository, &revs, prefix); | ||||||
|  |  | ||||||
| 	argc = parse_options(argc, argv, prefix, options, prune_usage, 0); | 	argc = parse_options(argc, argv, prefix, options, prune_usage, 0); | ||||||
|  |  | ||||||
|  | @ -586,15 +586,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix) | ||||||
| 				strvec_pushf(&cmd.args, | 				strvec_pushf(&cmd.args, | ||||||
| 					     "--unpack-unreachable=%s", | 					     "--unpack-unreachable=%s", | ||||||
| 					     unpack_unreachable); | 					     unpack_unreachable); | ||||||
| 				strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); |  | ||||||
| 			} else if (pack_everything & LOOSEN_UNREACHABLE) { | 			} else if (pack_everything & LOOSEN_UNREACHABLE) { | ||||||
| 				strvec_push(&cmd.args, | 				strvec_push(&cmd.args, | ||||||
| 					    "--unpack-unreachable"); | 					    "--unpack-unreachable"); | ||||||
| 			} else if (keep_unreachable) { | 			} else if (keep_unreachable) { | ||||||
| 				strvec_push(&cmd.args, "--keep-unreachable"); | 				strvec_push(&cmd.args, "--keep-unreachable"); | ||||||
| 				strvec_push(&cmd.args, "--pack-loose-unreachable"); | 				strvec_push(&cmd.args, "--pack-loose-unreachable"); | ||||||
| 			} else { |  | ||||||
| 				strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} else if (geometry) { | 	} else if (geometry) { | ||||||
|  |  | ||||||
|  | @ -863,8 +863,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 			if (!strcmp(arg, "--bisect")) { | 			if (!strcmp(arg, "--bisect")) { | ||||||
| 				for_each_fullref_in("refs/bisect/bad", show_reference, NULL, 0); | 				for_each_fullref_in("refs/bisect/bad", show_reference, NULL); | ||||||
| 				for_each_fullref_in("refs/bisect/good", anti_reference, NULL, 0); | 				for_each_fullref_in("refs/bisect/good", anti_reference, NULL); | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 			if (opt_with_value(arg, "--branches", &arg)) { | 			if (opt_with_value(arg, "--branches", &arg)) { | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								cache.h
								
								
								
								
							
							
						
						
									
										8
									
								
								cache.h
								
								
								
								
							|  | @ -994,14 +994,6 @@ extern const char *core_fsmonitor; | ||||||
| extern int core_apply_sparse_checkout; | extern int core_apply_sparse_checkout; | ||||||
| extern int core_sparse_checkout_cone; | extern int core_sparse_checkout_cone; | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * Include broken refs in all ref iterations, which will |  | ||||||
|  * generally choke dangerous operations rather than letting |  | ||||||
|  * them silently proceed without taking the broken ref into |  | ||||||
|  * account. |  | ||||||
|  */ |  | ||||||
| extern int ref_paranoia; |  | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Returns the boolean value of $GIT_OPTIONAL_LOCKS (or the default value). |  * Returns the boolean value of $GIT_OPTIONAL_LOCKS (or the default value). | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
|  | @ -31,7 +31,6 @@ int prefer_symlink_refs; | ||||||
| int is_bare_repository_cfg = -1; /* unspecified */ | int is_bare_repository_cfg = -1; /* unspecified */ | ||||||
| int warn_ambiguous_refs = 1; | int warn_ambiguous_refs = 1; | ||||||
| int warn_on_object_refname_ambiguity = 1; | int warn_on_object_refname_ambiguity = 1; | ||||||
| int ref_paranoia = -1; |  | ||||||
| int repository_format_precious_objects; | int repository_format_precious_objects; | ||||||
| int repository_format_worktree_config; | int repository_format_worktree_config; | ||||||
| const char *git_commit_encoding; | const char *git_commit_encoding; | ||||||
|  |  | ||||||
|  | @ -189,7 +189,7 @@ int ls_refs(struct repository *r, struct packet_reader *request) | ||||||
| 	if (!data.prefixes.nr) | 	if (!data.prefixes.nr) | ||||||
| 		strvec_push(&data.prefixes, ""); | 		strvec_push(&data.prefixes, ""); | ||||||
| 	for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v, | 	for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v, | ||||||
| 				     send_ref, &data, 0); | 				     send_ref, &data); | ||||||
| 	packet_fflush(stdout); | 	packet_fflush(stdout); | ||||||
| 	strvec_clear(&data.prefixes); | 	strvec_clear(&data.prefixes); | ||||||
| 	strbuf_release(&data.buf); | 	strbuf_release(&data.buf); | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								ref-filter.c
								
								
								
								
							
							
						
						
									
										22
									
								
								ref-filter.c
								
								
								
								
							|  | @ -2100,8 +2100,7 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname) | ||||||
|  */ |  */ | ||||||
| static int for_each_fullref_in_pattern(struct ref_filter *filter, | static int for_each_fullref_in_pattern(struct ref_filter *filter, | ||||||
| 				       each_ref_fn cb, | 				       each_ref_fn cb, | ||||||
| 				       void *cb_data, | 				       void *cb_data) | ||||||
| 				       int broken) |  | ||||||
| { | { | ||||||
| 	if (!filter->match_as_path) { | 	if (!filter->match_as_path) { | ||||||
| 		/* | 		/* | ||||||
|  | @ -2109,7 +2108,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, | ||||||
| 		 * prefixes like "refs/heads/" etc. are stripped off, | 		 * prefixes like "refs/heads/" etc. are stripped off, | ||||||
| 		 * so we have to look at everything: | 		 * so we have to look at everything: | ||||||
| 		 */ | 		 */ | ||||||
| 		return for_each_fullref_in("", cb, cb_data, broken); | 		return for_each_fullref_in("", cb, cb_data); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (filter->ignore_case) { | 	if (filter->ignore_case) { | ||||||
|  | @ -2118,16 +2117,16 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, | ||||||
| 		 * so just return everything and let the caller | 		 * so just return everything and let the caller | ||||||
| 		 * sort it out. | 		 * sort it out. | ||||||
| 		 */ | 		 */ | ||||||
| 		return for_each_fullref_in("", cb, cb_data, broken); | 		return for_each_fullref_in("", cb, cb_data); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (!filter->name_patterns[0]) { | 	if (!filter->name_patterns[0]) { | ||||||
| 		/* no patterns; we have to look at everything */ | 		/* no patterns; we have to look at everything */ | ||||||
| 		return for_each_fullref_in("", cb, cb_data, broken); | 		return for_each_fullref_in("", cb, cb_data); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return for_each_fullref_in_prefixes(NULL, filter->name_patterns, | 	return for_each_fullref_in_prefixes(NULL, filter->name_patterns, | ||||||
| 					    cb, cb_data, broken); | 					    cb, cb_data); | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  | @ -2405,13 +2404,10 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int | ||||||
| { | { | ||||||
| 	struct ref_filter_cbdata ref_cbdata; | 	struct ref_filter_cbdata ref_cbdata; | ||||||
| 	int ret = 0; | 	int ret = 0; | ||||||
| 	unsigned int broken = 0; |  | ||||||
|  |  | ||||||
| 	ref_cbdata.array = array; | 	ref_cbdata.array = array; | ||||||
| 	ref_cbdata.filter = filter; | 	ref_cbdata.filter = filter; | ||||||
|  |  | ||||||
| 	if (type & FILTER_REFS_INCLUDE_BROKEN) |  | ||||||
| 		broken = 1; |  | ||||||
| 	filter->kind = type & FILTER_REFS_KIND_MASK; | 	filter->kind = type & FILTER_REFS_KIND_MASK; | ||||||
|  |  | ||||||
| 	init_contains_cache(&ref_cbdata.contains_cache); | 	init_contains_cache(&ref_cbdata.contains_cache); | ||||||
|  | @ -2428,13 +2424,13 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int | ||||||
| 		 * of filter_ref_kind(). | 		 * of filter_ref_kind(). | ||||||
| 		 */ | 		 */ | ||||||
| 		if (filter->kind == FILTER_REFS_BRANCHES) | 		if (filter->kind == FILTER_REFS_BRANCHES) | ||||||
| 			ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken); | 			ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata); | ||||||
| 		else if (filter->kind == FILTER_REFS_REMOTES) | 		else if (filter->kind == FILTER_REFS_REMOTES) | ||||||
| 			ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken); | 			ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata); | ||||||
| 		else if (filter->kind == FILTER_REFS_TAGS) | 		else if (filter->kind == FILTER_REFS_TAGS) | ||||||
| 			ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken); | 			ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata); | ||||||
| 		else if (filter->kind & FILTER_REFS_ALL) | 		else if (filter->kind & FILTER_REFS_ALL) | ||||||
| 			ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata, broken); | 			ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata); | ||||||
| 		if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD)) | 		if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD)) | ||||||
| 			head_ref(ref_filter_handler, &ref_cbdata); | 			head_ref(ref_filter_handler, &ref_cbdata); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -13,7 +13,6 @@ | ||||||
| #define QUOTE_PYTHON 4 | #define QUOTE_PYTHON 4 | ||||||
| #define QUOTE_TCL 8 | #define QUOTE_TCL 8 | ||||||
|  |  | ||||||
| #define FILTER_REFS_INCLUDE_BROKEN 0x0001 |  | ||||||
| #define FILTER_REFS_TAGS           0x0002 | #define FILTER_REFS_TAGS           0x0002 | ||||||
| #define FILTER_REFS_BRANCHES       0x0004 | #define FILTER_REFS_BRANCHES       0x0004 | ||||||
| #define FILTER_REFS_REMOTES        0x0008 | #define FILTER_REFS_REMOTES        0x0008 | ||||||
|  |  | ||||||
							
								
								
									
										38
									
								
								refs.c
								
								
								
								
							
							
						
						
									
										38
									
								
								refs.c
								
								
								
								
							|  | @ -1409,14 +1409,21 @@ int head_ref(each_ref_fn fn, void *cb_data) | ||||||
|  |  | ||||||
| struct ref_iterator *refs_ref_iterator_begin( | struct ref_iterator *refs_ref_iterator_begin( | ||||||
| 		struct ref_store *refs, | 		struct ref_store *refs, | ||||||
| 		const char *prefix, int trim, int flags) | 		const char *prefix, int trim, | ||||||
|  | 		enum do_for_each_ref_flags flags) | ||||||
| { | { | ||||||
| 	struct ref_iterator *iter; | 	struct ref_iterator *iter; | ||||||
|  |  | ||||||
|  | 	if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) { | ||||||
|  | 		static int ref_paranoia = -1; | ||||||
|  |  | ||||||
| 		if (ref_paranoia < 0) | 		if (ref_paranoia < 0) | ||||||
| 		ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0); | 			ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1); | ||||||
| 	if (ref_paranoia) | 		if (ref_paranoia) { | ||||||
| 			flags |= DO_FOR_EACH_INCLUDE_BROKEN; | 			flags |= DO_FOR_EACH_INCLUDE_BROKEN; | ||||||
|  | 			flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	iter = refs->be->iterator_begin(refs, prefix, flags); | 	iter = refs->be->iterator_begin(refs, prefix, flags); | ||||||
|  |  | ||||||
|  | @ -1475,7 +1482,8 @@ static int do_for_each_ref_helper(struct repository *r, | ||||||
| } | } | ||||||
|  |  | ||||||
| static int do_for_each_ref(struct ref_store *refs, const char *prefix, | static int do_for_each_ref(struct ref_store *refs, const char *prefix, | ||||||
| 			   each_ref_fn fn, int trim, int flags, void *cb_data) | 			   each_ref_fn fn, int trim, | ||||||
|  | 			   enum do_for_each_ref_flags flags, void *cb_data) | ||||||
| { | { | ||||||
| 	struct ref_iterator *iter; | 	struct ref_iterator *iter; | ||||||
| 	struct do_for_each_ref_help hp = { fn, cb_data }; | 	struct do_for_each_ref_help hp = { fn, cb_data }; | ||||||
|  | @ -1510,25 +1518,16 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) | ||||||
| 	return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data); | 	return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data); | ||||||
| } | } | ||||||
|  |  | ||||||
| int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken) | int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data) | ||||||
| { | { | ||||||
| 	unsigned int flag = 0; |  | ||||||
|  |  | ||||||
| 	if (broken) |  | ||||||
| 		flag = DO_FOR_EACH_INCLUDE_BROKEN; |  | ||||||
| 	return do_for_each_ref(get_main_ref_store(the_repository), | 	return do_for_each_ref(get_main_ref_store(the_repository), | ||||||
| 			       prefix, fn, 0, flag, cb_data); | 			       prefix, fn, 0, 0, cb_data); | ||||||
| } | } | ||||||
|  |  | ||||||
| int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, | int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, | ||||||
| 			     each_ref_fn fn, void *cb_data, | 			     each_ref_fn fn, void *cb_data) | ||||||
| 			     unsigned int broken) |  | ||||||
| { | { | ||||||
| 	unsigned int flag = 0; | 	return do_for_each_ref(refs, prefix, fn, 0, 0, cb_data); | ||||||
|  |  | ||||||
| 	if (broken) |  | ||||||
| 		flag = DO_FOR_EACH_INCLUDE_BROKEN; |  | ||||||
| 	return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data) | int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data) | ||||||
|  | @ -1620,8 +1619,7 @@ static void find_longest_prefixes(struct string_list *out, | ||||||
|  |  | ||||||
| int for_each_fullref_in_prefixes(const char *namespace, | int for_each_fullref_in_prefixes(const char *namespace, | ||||||
| 				 const char **patterns, | 				 const char **patterns, | ||||||
| 				 each_ref_fn fn, void *cb_data, | 				 each_ref_fn fn, void *cb_data) | ||||||
| 				 unsigned int broken) |  | ||||||
| { | { | ||||||
| 	struct string_list prefixes = STRING_LIST_INIT_DUP; | 	struct string_list prefixes = STRING_LIST_INIT_DUP; | ||||||
| 	struct string_list_item *prefix; | 	struct string_list_item *prefix; | ||||||
|  | @ -1636,7 +1634,7 @@ int for_each_fullref_in_prefixes(const char *namespace, | ||||||
|  |  | ||||||
| 	for_each_string_list_item(prefix, &prefixes) { | 	for_each_string_list_item(prefix, &prefixes) { | ||||||
| 		strbuf_addstr(&buf, prefix->string); | 		strbuf_addstr(&buf, prefix->string); | ||||||
| 		ret = for_each_fullref_in(buf.buf, fn, cb_data, broken); | 		ret = for_each_fullref_in(buf.buf, fn, cb_data); | ||||||
| 		if (ret) | 		if (ret) | ||||||
| 			break; | 			break; | ||||||
| 		strbuf_setlen(&buf, namespace_len); | 		strbuf_setlen(&buf, namespace_len); | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								refs.h
								
								
								
								
							
							
						
						
									
										9
									
								
								refs.h
								
								
								
								
							|  | @ -342,10 +342,8 @@ int for_each_ref(each_ref_fn fn, void *cb_data); | ||||||
| int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data); | int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data); | ||||||
|  |  | ||||||
| int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, | int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, | ||||||
| 			     each_ref_fn fn, void *cb_data, | 			     each_ref_fn fn, void *cb_data); | ||||||
| 			     unsigned int broken); | int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data); | ||||||
| int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, |  | ||||||
| 			unsigned int broken); |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * iterate all refs in "patterns" by partitioning patterns into disjoint sets |  * iterate all refs in "patterns" by partitioning patterns into disjoint sets | ||||||
|  | @ -354,8 +352,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, | ||||||
|  * callers should be prepared to ignore references that they did not ask for. |  * callers should be prepared to ignore references that they did not ask for. | ||||||
|  */ |  */ | ||||||
| int for_each_fullref_in_prefixes(const char *namespace, const char **patterns, | int for_each_fullref_in_prefixes(const char *namespace, const char **patterns, | ||||||
| 				 each_ref_fn fn, void *cb_data, | 				 each_ref_fn fn, void *cb_data); | ||||||
| 				 unsigned int broken); |  | ||||||
| /** | /** | ||||||
|  * iterate refs from the respective area. |  * iterate refs from the respective area. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
|  | @ -744,6 +744,11 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator) | ||||||
| 		    ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE) | 		    ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE) | ||||||
| 			continue; | 			continue; | ||||||
|  |  | ||||||
|  | 		if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) && | ||||||
|  | 		    (iter->iter0->flags & REF_ISSYMREF) && | ||||||
|  | 		    (iter->iter0->flags & REF_ISBROKEN)) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
| 		if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && | 		if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && | ||||||
| 		    !ref_resolves_to_object(iter->iter0->refname, | 		    !ref_resolves_to_object(iter->iter0->refname, | ||||||
| 					    iter->iter0->oid, | 					    iter->iter0->oid, | ||||||
|  |  | ||||||
|  | @ -245,8 +245,36 @@ int refs_rename_ref_available(struct ref_store *refs, | ||||||
| /* We allow "recursive" symbolic refs. Only within reason, though */ | /* We allow "recursive" symbolic refs. Only within reason, though */ | ||||||
| #define SYMREF_MAXDEPTH 5 | #define SYMREF_MAXDEPTH 5 | ||||||
|  |  | ||||||
| /* Include broken references in a do_for_each_ref*() iteration: */ | /* | ||||||
| #define DO_FOR_EACH_INCLUDE_BROKEN 0x01 |  * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(), | ||||||
|  |  * which feeds it). | ||||||
|  |  */ | ||||||
|  | enum do_for_each_ref_flags { | ||||||
|  | 	/* | ||||||
|  | 	 * Include broken references in a do_for_each_ref*() iteration, which | ||||||
|  | 	 * would normally be omitted. This includes both refs that point to | ||||||
|  | 	 * missing objects (a true repository corruption), ones with illegal | ||||||
|  | 	 * names (which we prefer not to expose to callers), as well as | ||||||
|  | 	 * dangling symbolic refs (i.e., those that point to a non-existent | ||||||
|  | 	 * ref; this is not a corruption, but as they have no valid oid, we | ||||||
|  | 	 * omit them from normal iteration results). | ||||||
|  | 	 */ | ||||||
|  | 	DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0), | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * Only include per-worktree refs in a do_for_each_ref*() iteration. | ||||||
|  | 	 * Normally this will be used with a files ref_store, since that's | ||||||
|  | 	 * where all reference backends will presumably store their | ||||||
|  | 	 * per-worktree refs. | ||||||
|  | 	 */ | ||||||
|  | 	DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1), | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * Omit dangling symrefs from output; this only has an effect with | ||||||
|  | 	 * INCLUDE_BROKEN, since they are otherwise not included at all. | ||||||
|  | 	 */ | ||||||
|  | 	DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2), | ||||||
|  | }; | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Reference iterators |  * Reference iterators | ||||||
|  | @ -349,16 +377,12 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator); | ||||||
|  * Return an iterator that goes over each reference in `refs` for |  * Return an iterator that goes over each reference in `refs` for | ||||||
|  * which the refname begins with prefix. If trim is non-zero, then |  * which the refname begins with prefix. If trim is non-zero, then | ||||||
|  * trim that many characters off the beginning of each refname. |  * trim that many characters off the beginning of each refname. | ||||||
|  * The output is ordered by refname. The following flags are supported: |  * The output is ordered by refname. | ||||||
|  * |  | ||||||
|  * DO_FOR_EACH_INCLUDE_BROKEN: include broken references in |  | ||||||
|  *         the iteration. |  | ||||||
|  * |  | ||||||
|  * DO_FOR_EACH_PER_WORKTREE_ONLY: only produce REF_TYPE_PER_WORKTREE refs. |  | ||||||
|  */ |  */ | ||||||
| struct ref_iterator *refs_ref_iterator_begin( | struct ref_iterator *refs_ref_iterator_begin( | ||||||
| 		struct ref_store *refs, | 		struct ref_store *refs, | ||||||
| 		const char *prefix, int trim, int flags); | 		const char *prefix, int trim, | ||||||
|  | 		enum do_for_each_ref_flags flags); | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * A callback function used to instruct merge_ref_iterator how to |  * A callback function used to instruct merge_ref_iterator how to | ||||||
|  | @ -446,10 +470,8 @@ void base_ref_iterator_free(struct ref_iterator *iter); | ||||||
| /* | /* | ||||||
|  * backend-specific implementation of ref_iterator_advance. For symrefs, the |  * backend-specific implementation of ref_iterator_advance. For symrefs, the | ||||||
|  * function should set REF_ISSYMREF, and it should also dereference the symref |  * function should set REF_ISSYMREF, and it should also dereference the symref | ||||||
|  * to provide the OID referent. If DO_FOR_EACH_INCLUDE_BROKEN is set, symrefs |  * to provide the OID referent. It should respect do_for_each_ref_flags | ||||||
|  * with non-existent referents and refs pointing to non-existent object names |  * that were passed to refs_ref_iterator_begin(). | ||||||
|  * should also be returned. If DO_FOR_EACH_PER_WORKTREE_ONLY, only |  | ||||||
|  * REF_TYPE_PER_WORKTREE refs should be returned. |  | ||||||
|  */ |  */ | ||||||
| typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator); | typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator); | ||||||
|  |  | ||||||
|  | @ -498,14 +520,6 @@ int do_for_each_repo_ref_iterator(struct repository *r, | ||||||
| 				  struct ref_iterator *iter, | 				  struct ref_iterator *iter, | ||||||
| 				  each_repo_ref_fn fn, void *cb_data); | 				  each_repo_ref_fn fn, void *cb_data); | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * Only include per-worktree refs in a do_for_each_ref*() iteration. |  | ||||||
|  * Normally this will be used with a files ref_store, since that's |  | ||||||
|  * where all reference backends will presumably store their |  | ||||||
|  * per-worktree refs. |  | ||||||
|  */ |  | ||||||
| #define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02 |  | ||||||
|  |  | ||||||
| struct ref_store; | struct ref_store; | ||||||
|  |  | ||||||
| /* refs backends */ | /* refs backends */ | ||||||
|  |  | ||||||
|  | @ -2548,7 +2548,7 @@ static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn, | ||||||
| 	struct strbuf bisect_refs = STRBUF_INIT; | 	struct strbuf bisect_refs = STRBUF_INIT; | ||||||
| 	int status; | 	int status; | ||||||
| 	strbuf_addf(&bisect_refs, "refs/bisect/%s", term); | 	strbuf_addf(&bisect_refs, "refs/bisect/%s", term); | ||||||
| 	status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data, 0); | 	status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data); | ||||||
| 	strbuf_release(&bisect_refs); | 	strbuf_release(&bisect_refs); | ||||||
| 	return status; | 	return status; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -170,7 +170,7 @@ test_expect_success 'for-each-ref emits warnings for broken names' ' | ||||||
| 	! grep -e "badname" output && | 	! grep -e "badname" output && | ||||||
| 	! grep -e "broken\.\.\.symref" output && | 	! grep -e "broken\.\.\.symref" output && | ||||||
| 	test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error && | 	test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error && | ||||||
| 	test_i18ngrep "ignoring broken ref refs/heads/badname" error && | 	test_i18ngrep ! "ignoring broken ref refs/heads/badname" error && | ||||||
| 	test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error | 	test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @ -7,6 +7,9 @@ if we see, for example, a ref with a bogus name, it is OK either to | ||||||
| bail out or to proceed using it as a reachable tip, but it is _not_ | bail out or to proceed using it as a reachable tip, but it is _not_ | ||||||
| OK to proceed as if it did not exist. Otherwise we might silently | OK to proceed as if it did not exist. Otherwise we might silently | ||||||
| delete objects that cannot be recovered. | delete objects that cannot be recovered. | ||||||
|  |  | ||||||
|  | Note that we do assert command failure in these cases, because that is | ||||||
|  | what currently happens. If that changes, these tests should be revisited. | ||||||
| ' | ' | ||||||
| GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main | GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main | ||||||
| export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME | export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME | ||||||
|  | @ -18,39 +21,58 @@ test_expect_success 'disable reflogs' ' | ||||||
| 	git reflog expire --expire=all --all | 	git reflog expire --expire=all --all | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | create_bogus_ref () { | ||||||
|  | 	test_when_finished 'rm -f .git/refs/heads/bogus..name' && | ||||||
|  | 	echo $bogus >.git/refs/heads/bogus..name | ||||||
|  | } | ||||||
|  |  | ||||||
| test_expect_success 'create history reachable only from a bogus-named ref' ' | test_expect_success 'create history reachable only from a bogus-named ref' ' | ||||||
| 	test_tick && git commit --allow-empty -m main && | 	test_tick && git commit --allow-empty -m main && | ||||||
| 	base=$(git rev-parse HEAD) && | 	base=$(git rev-parse HEAD) && | ||||||
| 	test_tick && git commit --allow-empty -m bogus && | 	test_tick && git commit --allow-empty -m bogus && | ||||||
| 	bogus=$(git rev-parse HEAD) && | 	bogus=$(git rev-parse HEAD) && | ||||||
| 	git cat-file commit $bogus >saved && | 	git cat-file commit $bogus >saved && | ||||||
| 	echo $bogus >.git/refs/heads/bogus..name && |  | ||||||
| 	git reset --hard HEAD^ | 	git reset --hard HEAD^ | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'pruning does not drop bogus object' ' | test_expect_success 'pruning does not drop bogus object' ' | ||||||
| 	test_when_finished "git hash-object -w -t commit saved" && | 	test_when_finished "git hash-object -w -t commit saved" && | ||||||
| 	test_might_fail git prune --expire=now && | 	create_bogus_ref && | ||||||
| 	verbose git cat-file -e $bogus | 	test_must_fail git prune --expire=now && | ||||||
|  | 	git cat-file -e $bogus | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'put bogus object into pack' ' | test_expect_success 'put bogus object into pack' ' | ||||||
| 	git tag reachable $bogus && | 	git tag reachable $bogus && | ||||||
| 	git repack -ad && | 	git repack -ad && | ||||||
| 	git tag -d reachable && | 	git tag -d reachable && | ||||||
| 	verbose git cat-file -e $bogus | 	git cat-file -e $bogus | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'non-destructive repack bails on bogus ref' ' | ||||||
|  | 	create_bogus_ref && | ||||||
|  | 	test_must_fail git repack -adk | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'GIT_REF_PARANOIA=0 overrides safety' ' | ||||||
|  | 	create_bogus_ref && | ||||||
|  | 	GIT_REF_PARANOIA=0 git repack -adk | ||||||
|  | ' | ||||||
|  |  | ||||||
|  |  | ||||||
| test_expect_success 'destructive repack keeps packed object' ' | test_expect_success 'destructive repack keeps packed object' ' | ||||||
| 	test_might_fail git repack -Ad --unpack-unreachable=now && | 	create_bogus_ref && | ||||||
| 	verbose git cat-file -e $bogus && | 	test_must_fail git repack -Ad --unpack-unreachable=now && | ||||||
| 	test_might_fail git repack -ad && | 	git cat-file -e $bogus && | ||||||
| 	verbose git cat-file -e $bogus | 	test_must_fail git repack -ad && | ||||||
|  | 	git cat-file -e $bogus | ||||||
| ' | ' | ||||||
|  |  | ||||||
| # subsequent tests will have different corruptions | test_expect_success 'destructive repack not confused by dangling symref' ' | ||||||
| test_expect_success 'clean up bogus ref' ' | 	test_when_finished "git symbolic-ref -d refs/heads/dangling" && | ||||||
| 	rm .git/refs/heads/bogus..name | 	git symbolic-ref refs/heads/dangling refs/heads/does-not-exist && | ||||||
|  | 	git repack -ad && | ||||||
|  | 	test_must_fail git cat-file -e $bogus | ||||||
| ' | ' | ||||||
|  |  | ||||||
| # We create two new objects here, "one" and "two". Our | # We create two new objects here, "one" and "two". Our | ||||||
|  | @ -77,8 +99,8 @@ test_expect_success 'create history with missing tip commit' ' | ||||||
|  |  | ||||||
| test_expect_success 'pruning with a corrupted tip does not drop history' ' | test_expect_success 'pruning with a corrupted tip does not drop history' ' | ||||||
| 	test_when_finished "git hash-object -w -t commit saved" && | 	test_when_finished "git hash-object -w -t commit saved" && | ||||||
| 	test_might_fail git prune --expire=now && | 	test_must_fail git prune --expire=now && | ||||||
| 	verbose git cat-file -e $recoverable | 	git cat-file -e $recoverable | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'pack-refs does not silently delete broken loose ref' ' | test_expect_success 'pack-refs does not silently delete broken loose ref' ' | ||||||
|  |  | ||||||
|  | @ -662,10 +662,10 @@ test_expect_success 'push does not update local refs on failure' ' | ||||||
|  |  | ||||||
| test_expect_success 'allow deleting an invalid remote ref' ' | test_expect_success 'allow deleting an invalid remote ref' ' | ||||||
|  |  | ||||||
| 	mk_test testrepo heads/main && | 	mk_test testrepo heads/branch && | ||||||
| 	rm -f testrepo/.git/objects/??/* && | 	rm -f testrepo/.git/objects/??/* && | ||||||
| 	git push testrepo :refs/heads/main && | 	git push testrepo :refs/heads/branch && | ||||||
| 	(cd testrepo && test_must_fail git rev-parse --verify refs/heads/main) | 	(cd testrepo && test_must_fail git rev-parse --verify refs/heads/branch) | ||||||
|  |  | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | @ -706,25 +706,26 @@ test_expect_success 'pushing valid refs triggers post-receive and post-update ho | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'deleting dangling ref triggers hooks with correct args' ' | test_expect_success 'deleting dangling ref triggers hooks with correct args' ' | ||||||
| 	mk_test_with_hooks testrepo heads/main && | 	mk_test_with_hooks testrepo heads/branch && | ||||||
|  | 	orig=$(git -C testrepo rev-parse refs/heads/branch) && | ||||||
| 	rm -f testrepo/.git/objects/??/* && | 	rm -f testrepo/.git/objects/??/* && | ||||||
| 	git push testrepo :refs/heads/main && | 	git push testrepo :refs/heads/branch && | ||||||
| 	( | 	( | ||||||
| 		cd testrepo/.git && | 		cd testrepo/.git && | ||||||
| 		cat >pre-receive.expect <<-EOF && | 		cat >pre-receive.expect <<-EOF && | ||||||
| 		$ZERO_OID $ZERO_OID refs/heads/main | 		$orig $ZERO_OID refs/heads/branch | ||||||
| 		EOF | 		EOF | ||||||
|  |  | ||||||
| 		cat >update.expect <<-EOF && | 		cat >update.expect <<-EOF && | ||||||
| 		refs/heads/main $ZERO_OID $ZERO_OID | 		refs/heads/branch $orig $ZERO_OID | ||||||
| 		EOF | 		EOF | ||||||
|  |  | ||||||
| 		cat >post-receive.expect <<-EOF && | 		cat >post-receive.expect <<-EOF && | ||||||
| 		$ZERO_OID $ZERO_OID refs/heads/main | 		$orig $ZERO_OID refs/heads/branch | ||||||
| 		EOF | 		EOF | ||||||
|  |  | ||||||
| 		cat >post-update.expect <<-EOF && | 		cat >post-update.expect <<-EOF && | ||||||
| 		refs/heads/main | 		refs/heads/branch | ||||||
| 		EOF | 		EOF | ||||||
|  |  | ||||||
| 		test_cmp pre-receive.expect pre-receive.actual && | 		test_cmp pre-receive.expect pre-receive.actual && | ||||||
|  |  | ||||||
|  | @ -35,7 +35,9 @@ test_expect_success 'create a repo to clone' ' | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'create objects in repo for later corruption' ' | test_expect_success 'create objects in repo for later corruption' ' | ||||||
| 	test_commit -C foo file | 	test_commit -C foo file && | ||||||
|  | 	git -C foo checkout --detach && | ||||||
|  | 	test_commit -C foo detached | ||||||
| ' | ' | ||||||
|  |  | ||||||
| # source repository given to git clone should be relative to the | # source repository given to git clone should be relative to the | ||||||
|  |  | ||||||
|  | @ -276,7 +276,7 @@ test_expect_success 'incremental-repack task' ' | ||||||
|  |  | ||||||
| 	# Delete refs that have not been repacked in these packs. | 	# Delete refs that have not been repacked in these packs. | ||||||
| 	git for-each-ref --format="delete %(refname)" \ | 	git for-each-ref --format="delete %(refname)" \ | ||||||
| 		refs/prefetch refs/tags >refs && | 		refs/prefetch refs/tags refs/remotes >refs && | ||||||
| 	git update-ref --stdin <refs && | 	git update-ref --stdin <refs && | ||||||
|  |  | ||||||
| 	# Replace the object directory with this pack layout. | 	# Replace the object directory with this pack layout. | ||||||
|  | @ -285,6 +285,10 @@ test_expect_success 'incremental-repack task' ' | ||||||
| 	ls $packDir/*.pack >packs-before && | 	ls $packDir/*.pack >packs-before && | ||||||
| 	test_line_count = 3 packs-before && | 	test_line_count = 3 packs-before && | ||||||
|  |  | ||||||
|  | 	# make sure we do not have any broken refs that were | ||||||
|  | 	# missed in the deletion above | ||||||
|  | 	git for-each-ref && | ||||||
|  |  | ||||||
| 	# the job repacks the two into a new pack, but does not | 	# the job repacks the two into a new pack, but does not | ||||||
| 	# delete the old ones. | 	# delete the old ones. | ||||||
| 	git maintenance run --task=incremental-repack && | 	git maintenance run --task=incremental-repack && | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano