gc: do not repack promisor packfiles
Teach gc to stop traversal at promisor objects, and to leave promisor packfiles alone. This has the effect of only repacking non-promisor packfiles, and preserves the distinction between promisor packfiles and non-promisor packfiles. Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									df11e19648
								
							
						
					
					
						commit
						0c16cd499d
					
				|  | @ -255,6 +255,17 @@ a missing object is encountered.  This is the default action. | |||
| The form '--missing=allow-any' will allow object traversal to continue | ||||
| if a missing object is encountered.  Missing objects will silently be | ||||
| omitted from the results. | ||||
| + | ||||
| The form '--missing=allow-promisor' is like 'allow-any', but will only | ||||
| allow object traversal to continue for EXPECTED promisor missing objects. | ||||
| Unexpected missing object will raise an error. | ||||
|  | ||||
| --exclude-promisor-objects:: | ||||
| 	Omit objects that are known to be in the promisor remote.  (This | ||||
| 	option has the purpose of operating only on locally created objects, | ||||
| 	so that when we repack, we still maintain a distinction between | ||||
| 	locally created objects [without .promisor] and objects from the | ||||
| 	promisor remote [with .promisor].)  This is used with partial clone. | ||||
|  | ||||
| SEE ALSO | ||||
| -------- | ||||
|  |  | |||
|  | @ -458,6 +458,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) | |||
| 			argv_array_push(&prune, prune_expire); | ||||
| 			if (quiet) | ||||
| 				argv_array_push(&prune, "--no-progress"); | ||||
| 			if (repository_format_partial_clone) | ||||
| 				argv_array_push(&prune, | ||||
| 						"--exclude-promisor-objects"); | ||||
| 			if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) | ||||
| 				return error(FAILED_RUN, prune.argv[0]); | ||||
| 		} | ||||
|  |  | |||
|  | @ -75,6 +75,8 @@ static int use_bitmap_index = -1; | |||
| static int write_bitmap_index; | ||||
| static uint16_t write_bitmap_options; | ||||
|  | ||||
| static int exclude_promisor_objects; | ||||
|  | ||||
| static unsigned long delta_cache_size = 0; | ||||
| static unsigned long max_delta_cache_size = 256 * 1024 * 1024; | ||||
| static unsigned long cache_max_small_delta_size = 1000; | ||||
|  | @ -86,6 +88,7 @@ static struct list_objects_filter_options filter_options; | |||
| enum missing_action { | ||||
| 	MA_ERROR = 0,      /* fail if any missing objects are encountered */ | ||||
| 	MA_ALLOW_ANY,      /* silently allow ALL missing objects */ | ||||
| 	MA_ALLOW_PROMISOR, /* silently allow all missing PROMISOR objects */ | ||||
| }; | ||||
| static enum missing_action arg_missing_action; | ||||
| static show_object_fn fn_show_object; | ||||
|  | @ -2577,6 +2580,20 @@ static void show_object__ma_allow_any(struct object *obj, const char *name, void | |||
| 	show_object(obj, name, data); | ||||
| } | ||||
|  | ||||
| static void show_object__ma_allow_promisor(struct object *obj, const char *name, void *data) | ||||
| { | ||||
| 	assert(arg_missing_action == MA_ALLOW_PROMISOR); | ||||
|  | ||||
| 	/* | ||||
| 	 * Quietly ignore EXPECTED missing objects.  This avoids problems with | ||||
| 	 * staging them now and getting an odd error later. | ||||
| 	 */ | ||||
| 	if (!has_object_file(&obj->oid) && is_promisor_object(&obj->oid)) | ||||
| 		return; | ||||
|  | ||||
| 	show_object(obj, name, data); | ||||
| } | ||||
|  | ||||
| static int option_parse_missing_action(const struct option *opt, | ||||
| 				       const char *arg, int unset) | ||||
| { | ||||
|  | @ -2591,10 +2608,18 @@ static int option_parse_missing_action(const struct option *opt, | |||
|  | ||||
| 	if (!strcmp(arg, "allow-any")) { | ||||
| 		arg_missing_action = MA_ALLOW_ANY; | ||||
| 		fetch_if_missing = 0; | ||||
| 		fn_show_object = show_object__ma_allow_any; | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	if (!strcmp(arg, "allow-promisor")) { | ||||
| 		arg_missing_action = MA_ALLOW_PROMISOR; | ||||
| 		fetch_if_missing = 0; | ||||
| 		fn_show_object = show_object__ma_allow_promisor; | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	die(_("invalid value for --missing")); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -3008,6 +3033,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) | |||
| 		{ OPTION_CALLBACK, 0, "missing", NULL, N_("action"), | ||||
| 		  N_("handling for missing objects"), PARSE_OPT_NONEG, | ||||
| 		  option_parse_missing_action }, | ||||
| 		OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects, | ||||
| 			 N_("do not pack objects in promisor packfiles")), | ||||
| 		OPT_END(), | ||||
| 	}; | ||||
|  | ||||
|  | @ -3053,6 +3080,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) | |||
| 		argv_array_push(&rp, "--unpacked"); | ||||
| 	} | ||||
|  | ||||
| 	if (exclude_promisor_objects) { | ||||
| 		use_internal_rev_list = 1; | ||||
| 		fetch_if_missing = 0; | ||||
| 		argv_array_push(&rp, "--exclude-promisor-objects"); | ||||
| 	} | ||||
|  | ||||
| 	if (!reuse_object) | ||||
| 		reuse_delta = 0; | ||||
| 	if (pack_compression_level == -1) | ||||
|  |  | |||
|  | @ -101,12 +101,15 @@ int cmd_prune(int argc, const char **argv, const char *prefix) | |||
| { | ||||
| 	struct rev_info revs; | ||||
| 	struct progress *progress = NULL; | ||||
| 	int exclude_promisor_objects = 0; | ||||
| 	const struct option options[] = { | ||||
| 		OPT__DRY_RUN(&show_only, N_("do not remove, show only")), | ||||
| 		OPT__VERBOSE(&verbose, N_("report pruned objects")), | ||||
| 		OPT_BOOL(0, "progress", &show_progress, N_("show progress")), | ||||
| 		OPT_EXPIRY_DATE(0, "expire", &expire, | ||||
| 				N_("expire objects older than <time>")), | ||||
| 		OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects, | ||||
| 			 N_("limit traversal to objects outside promisor packfiles")), | ||||
| 		OPT_END() | ||||
| 	}; | ||||
| 	char *s; | ||||
|  | @ -139,6 +142,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix) | |||
| 		show_progress = isatty(2); | ||||
| 	if (show_progress) | ||||
| 		progress = start_delayed_progress(_("Checking connectivity"), 0); | ||||
| 	if (exclude_promisor_objects) { | ||||
| 		fetch_if_missing = 0; | ||||
| 		revs.exclude_promisor_objects = 1; | ||||
| 	} | ||||
|  | ||||
| 	mark_reachable_objects(&revs, 1, expire, progress); | ||||
| 	stop_progress(&progress); | ||||
|  |  | |||
|  | @ -83,7 +83,8 @@ static void remove_pack_on_signal(int signo) | |||
|  | ||||
| /* | ||||
|  * Adds all packs hex strings to the fname list, which do not | ||||
|  * have a corresponding .keep file. | ||||
|  * have a corresponding .keep or .promisor file. These packs are not to | ||||
|  * be kept if we are going to pack everything into one file. | ||||
|  */ | ||||
| static void get_non_kept_pack_filenames(struct string_list *fname_list) | ||||
| { | ||||
|  | @ -101,7 +102,8 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list) | |||
|  | ||||
| 		fname = xmemdupz(e->d_name, len); | ||||
|  | ||||
| 		if (!file_exists(mkpath("%s/%s.keep", packdir, fname))) | ||||
| 		if (!file_exists(mkpath("%s/%s.keep", packdir, fname)) && | ||||
| 		    !file_exists(mkpath("%s/%s.promisor", packdir, fname))) | ||||
| 			string_list_append_nodup(fname_list, fname); | ||||
| 		else | ||||
| 			free(fname); | ||||
|  | @ -232,6 +234,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) | |||
| 	argv_array_push(&cmd.args, "--all"); | ||||
| 	argv_array_push(&cmd.args, "--reflog"); | ||||
| 	argv_array_push(&cmd.args, "--indexed-objects"); | ||||
| 	if (repository_format_partial_clone) | ||||
| 		argv_array_push(&cmd.args, "--exclude-promisor-objects"); | ||||
| 	if (window) | ||||
| 		argv_array_pushf(&cmd.args, "--window=%s", window); | ||||
| 	if (window_memory) | ||||
|  |  | |||
|  | @ -10,14 +10,16 @@ delete_object () { | |||
|  | ||||
| pack_as_from_promisor () { | ||||
| 	HASH=$(git -C repo pack-objects .git/objects/pack/pack) && | ||||
| 	>repo/.git/objects/pack/pack-$HASH.promisor | ||||
| 	>repo/.git/objects/pack/pack-$HASH.promisor && | ||||
| 	echo $HASH | ||||
| } | ||||
|  | ||||
| promise_and_delete () { | ||||
| 	HASH=$(git -C repo rev-parse "$1") && | ||||
| 	git -C repo tag -a -m message my_annotated_tag "$HASH" && | ||||
| 	git -C repo rev-parse my_annotated_tag | pack_as_from_promisor && | ||||
| 	git -C repo tag -d my_annotated_tag && | ||||
| 	# tag -d prints a message to stdout, so redirect it | ||||
| 	git -C repo tag -d my_annotated_tag >/dev/null && | ||||
| 	delete_object repo "$HASH" | ||||
| } | ||||
|  | ||||
|  | @ -261,6 +263,54 @@ test_expect_success 'rev-list accepts missing and promised objects on command li | |||
| 	git -C repo rev-list --exclude-promisor-objects --objects "$COMMIT" "$TREE" "$BLOB" | ||||
| ' | ||||
|  | ||||
| test_expect_success 'gc does not repack promisor objects' ' | ||||
| 	rm -rf repo && | ||||
| 	test_create_repo repo && | ||||
| 	test_commit -C repo my_commit && | ||||
|  | ||||
| 	TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) && | ||||
| 	HASH=$(printf "$TREE_HASH\n" | pack_as_from_promisor) && | ||||
|  | ||||
| 	git -C repo config core.repositoryformatversion 1 && | ||||
| 	git -C repo config extensions.partialclone "arbitrary string" && | ||||
| 	git -C repo gc && | ||||
|  | ||||
| 	# Ensure that the promisor packfile still exists, and remove it | ||||
| 	test -e repo/.git/objects/pack/pack-$HASH.pack && | ||||
| 	rm repo/.git/objects/pack/pack-$HASH.* && | ||||
|  | ||||
| 	# Ensure that the single other pack contains the commit, but not the tree | ||||
| 	ls repo/.git/objects/pack/pack-*.pack >packlist && | ||||
| 	test_line_count = 1 packlist && | ||||
| 	git verify-pack repo/.git/objects/pack/pack-*.pack -v >out && | ||||
| 	grep "$(git -C repo rev-parse HEAD)" out && | ||||
| 	! grep "$TREE_HASH" out | ||||
| ' | ||||
|  | ||||
| test_expect_success 'gc stops traversal when a missing but promised object is reached' ' | ||||
| 	rm -rf repo && | ||||
| 	test_create_repo repo && | ||||
| 	test_commit -C repo my_commit && | ||||
|  | ||||
| 	TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) && | ||||
| 	HASH=$(promise_and_delete $TREE_HASH) && | ||||
|  | ||||
| 	git -C repo config core.repositoryformatversion 1 && | ||||
| 	git -C repo config extensions.partialclone "arbitrary string" && | ||||
| 	git -C repo gc && | ||||
|  | ||||
| 	# Ensure that the promisor packfile still exists, and remove it | ||||
| 	test -e repo/.git/objects/pack/pack-$HASH.pack && | ||||
| 	rm repo/.git/objects/pack/pack-$HASH.* && | ||||
|  | ||||
| 	# Ensure that the single other pack contains the commit, but not the tree | ||||
| 	ls repo/.git/objects/pack/pack-*.pack >packlist && | ||||
| 	test_line_count = 1 packlist && | ||||
| 	git verify-pack repo/.git/objects/pack/pack-*.pack -v >out && | ||||
| 	grep "$(git -C repo rev-parse HEAD)" out && | ||||
| 	! grep "$TREE_HASH" out | ||||
| ' | ||||
|  | ||||
| LIB_HTTPD_PORT=12345  # default port, 410, cannot be used as non-root | ||||
| . "$TEST_DIRECTORY"/lib-httpd.sh | ||||
| start_httpd | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Jonathan Tan
						Jonathan Tan