Merge branch 'sb/submodule-clone-rr'
"git clone --resurse-submodules --reference $path $URL" is a way to reduce network transfer cost by borrowing objects in an existing $path repository when cloning the superproject from $URL; it learned to also peek into $path for presense of corresponding repositories of submodules and borrow objects from there when able. * sb/submodule-clone-rr: clone: recursive and reference option triggers submodule alternates clone: implement optional references clone: clarify option_reference as required clone: factor out checking for an alternate path submodule--helper update-clone: allow multiple references submodule--helper module-clone: allow multiple references t7408: merge short tests, factor out testing method t7408: modernize stylemaint
						commit
						02c6c14d6c
					
				|  | @ -2853,6 +2853,18 @@ submodule.fetchJobs:: | ||||||
| 	in parallel. A value of 0 will give some reasonable default. | 	in parallel. A value of 0 will give some reasonable default. | ||||||
| 	If unset, it defaults to 1. | 	If unset, it defaults to 1. | ||||||
|  |  | ||||||
|  | submodule.alternateLocation:: | ||||||
|  | 	Specifies how the submodules obtain alternates when submodules are | ||||||
|  | 	cloned. Possible values are `no`, `superproject`. | ||||||
|  | 	By default `no` is assumed, which doesn't add references. When the | ||||||
|  | 	value is set to `superproject` the submodule to be cloned computes | ||||||
|  | 	its alternates location relative to the superprojects alternate. | ||||||
|  |  | ||||||
|  | submodule.alternateErrorStrategy | ||||||
|  | 	Specifies how to treat errors with the alternates for a submodule | ||||||
|  | 	as computed via `submodule.alternateLocation`. Possible values are | ||||||
|  | 	`ignore`, `info`, `die`. Default is `die`. | ||||||
|  |  | ||||||
| tag.forceSignAnnotated:: | tag.forceSignAnnotated:: | ||||||
| 	A boolean to specify whether annotated tags created should be GPG signed. | 	A boolean to specify whether annotated tags created should be GPG signed. | ||||||
| 	If `--annotate` is specified on the command line, it takes | 	If `--annotate` is specified on the command line, it takes | ||||||
|  |  | ||||||
|  | @ -90,13 +90,16 @@ If you want to break the dependency of a repository cloned with `-s` on | ||||||
| its source repository, you can simply run `git repack -a` to copy all | its source repository, you can simply run `git repack -a` to copy all | ||||||
| objects from the source repository into a pack in the cloned repository. | objects from the source repository into a pack in the cloned repository. | ||||||
|  |  | ||||||
| --reference <repository>:: | --reference[-if-able] <repository>:: | ||||||
| 	If the reference repository is on the local machine, | 	If the reference repository is on the local machine, | ||||||
| 	automatically setup `.git/objects/info/alternates` to | 	automatically setup `.git/objects/info/alternates` to | ||||||
| 	obtain objects from the reference repository.  Using | 	obtain objects from the reference repository.  Using | ||||||
| 	an already existing repository as an alternate will | 	an already existing repository as an alternate will | ||||||
| 	require fewer objects to be copied from the repository | 	require fewer objects to be copied from the repository | ||||||
| 	being cloned, reducing network and local storage costs. | 	being cloned, reducing network and local storage costs. | ||||||
|  | 	When using the `--reference-if-able`, a non existing | ||||||
|  | 	directory is skipped with a warning instead of aborting | ||||||
|  | 	the clone. | ||||||
| + | + | ||||||
| *NOTE*: see the NOTE for the `--shared` option, and also the | *NOTE*: see the NOTE for the `--shared` option, and also the | ||||||
| `--dissociate` option. | `--dissociate` option. | ||||||
|  |  | ||||||
|  | @ -50,7 +50,8 @@ static int option_verbosity; | ||||||
| static int option_progress = -1; | static int option_progress = -1; | ||||||
| static enum transport_family family; | static enum transport_family family; | ||||||
| static struct string_list option_config = STRING_LIST_INIT_NODUP; | static struct string_list option_config = STRING_LIST_INIT_NODUP; | ||||||
| static struct string_list option_reference = STRING_LIST_INIT_NODUP; | static struct string_list option_required_reference = STRING_LIST_INIT_NODUP; | ||||||
|  | static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP; | ||||||
| static int option_dissociate; | static int option_dissociate; | ||||||
| static int max_jobs = -1; | static int max_jobs = -1; | ||||||
|  |  | ||||||
|  | @ -79,8 +80,10 @@ static struct option builtin_clone_options[] = { | ||||||
| 		    N_("number of submodules cloned in parallel")), | 		    N_("number of submodules cloned in parallel")), | ||||||
| 	OPT_STRING(0, "template", &option_template, N_("template-directory"), | 	OPT_STRING(0, "template", &option_template, N_("template-directory"), | ||||||
| 		   N_("directory from which templates will be used")), | 		   N_("directory from which templates will be used")), | ||||||
| 	OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"), | 	OPT_STRING_LIST(0, "reference", &option_required_reference, N_("repo"), | ||||||
| 			N_("reference repository")), | 			N_("reference repository")), | ||||||
|  | 	OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference, | ||||||
|  | 			N_("repo"), N_("reference repository")), | ||||||
| 	OPT_BOOL(0, "dissociate", &option_dissociate, | 	OPT_BOOL(0, "dissociate", &option_dissociate, | ||||||
| 		 N_("use --reference only while cloning")), | 		 N_("use --reference only while cloning")), | ||||||
| 	OPT_STRING('o', "origin", &option_origin, N_("name"), | 	OPT_STRING('o', "origin", &option_origin, N_("name"), | ||||||
|  | @ -282,50 +285,37 @@ static void strip_trailing_slashes(char *dir) | ||||||
|  |  | ||||||
| static int add_one_reference(struct string_list_item *item, void *cb_data) | static int add_one_reference(struct string_list_item *item, void *cb_data) | ||||||
| { | { | ||||||
| 	char *ref_git; | 	struct strbuf err = STRBUF_INIT; | ||||||
| 	const char *repo; | 	int *required = cb_data; | ||||||
| 	struct strbuf alternate = STRBUF_INIT; | 	char *ref_git = compute_alternate_path(item->string, &err); | ||||||
|  |  | ||||||
| 	/* Beware: read_gitfile(), real_path() and mkpath() return static buffer */ | 	if (!ref_git) { | ||||||
| 	ref_git = xstrdup(real_path(item->string)); | 		if (*required) | ||||||
|  | 			die("%s", err.buf); | ||||||
| 	repo = read_gitfile(ref_git); | 		else | ||||||
| 	if (!repo) | 			fprintf(stderr, | ||||||
| 		repo = read_gitfile(mkpath("%s/.git", ref_git)); | 				_("info: Could not add alternate for '%s': %s\n"), | ||||||
| 	if (repo) { | 				item->string, err.buf); | ||||||
| 		free(ref_git); | 	} else { | ||||||
| 		ref_git = xstrdup(repo); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) { |  | ||||||
| 		char *ref_git_git = mkpathdup("%s/.git", ref_git); |  | ||||||
| 		free(ref_git); |  | ||||||
| 		ref_git = ref_git_git; |  | ||||||
| 	} else if (!is_directory(mkpath("%s/objects", ref_git))) { |  | ||||||
| 		struct strbuf sb = STRBUF_INIT; | 		struct strbuf sb = STRBUF_INIT; | ||||||
| 		if (get_common_dir(&sb, ref_git)) | 		strbuf_addf(&sb, "%s/objects", ref_git); | ||||||
| 			die(_("reference repository '%s' as a linked checkout is not supported yet."), | 		add_to_alternates_file(sb.buf); | ||||||
| 			    item->string); | 		strbuf_release(&sb); | ||||||
| 		die(_("reference repository '%s' is not a local repository."), |  | ||||||
| 		    item->string); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (!access(mkpath("%s/shallow", ref_git), F_OK)) | 	strbuf_release(&err); | ||||||
| 		die(_("reference repository '%s' is shallow"), item->string); |  | ||||||
|  |  | ||||||
| 	if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) |  | ||||||
| 		die(_("reference repository '%s' is grafted"), item->string); |  | ||||||
|  |  | ||||||
| 	strbuf_addf(&alternate, "%s/objects", ref_git); |  | ||||||
| 	add_to_alternates_file(alternate.buf); |  | ||||||
| 	strbuf_release(&alternate); |  | ||||||
| 	free(ref_git); | 	free(ref_git); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void setup_reference(void) | static void setup_reference(void) | ||||||
| { | { | ||||||
| 	for_each_string_list(&option_reference, add_one_reference, NULL); | 	int required = 1; | ||||||
|  | 	for_each_string_list(&option_required_reference, | ||||||
|  | 			     add_one_reference, &required); | ||||||
|  | 	required = 0; | ||||||
|  | 	for_each_string_list(&option_optional_reference, | ||||||
|  | 			     add_one_reference, &required); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void copy_alternates(struct strbuf *src, struct strbuf *dst, | static void copy_alternates(struct strbuf *src, struct strbuf *dst, | ||||||
|  | @ -957,6 +947,25 @@ int cmd_clone(int argc, const char **argv, const char *prefix) | ||||||
| 		else | 		else | ||||||
| 			fprintf(stderr, _("Cloning into '%s'...\n"), dir); | 			fprintf(stderr, _("Cloning into '%s'...\n"), dir); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if (option_recursive) { | ||||||
|  | 		if (option_required_reference.nr && | ||||||
|  | 		    option_optional_reference.nr) | ||||||
|  | 			die(_("clone --recursive is not compatible with " | ||||||
|  | 			      "both --reference and --reference-if-able")); | ||||||
|  | 		else if (option_required_reference.nr) { | ||||||
|  | 			string_list_append(&option_config, | ||||||
|  | 				"submodule.alternateLocation=superproject"); | ||||||
|  | 			string_list_append(&option_config, | ||||||
|  | 				"submodule.alternateErrorStrategy=die"); | ||||||
|  | 		} else if (option_optional_reference.nr) { | ||||||
|  | 			string_list_append(&option_config, | ||||||
|  | 				"submodule.alternateLocation=superproject"); | ||||||
|  | 			string_list_append(&option_config, | ||||||
|  | 				"submodule.alternateErrorStrategy=info"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	init_db(option_template, INIT_DB_QUIET); | 	init_db(option_template, INIT_DB_QUIET); | ||||||
| 	write_config(&option_config); | 	write_config(&option_config); | ||||||
|  |  | ||||||
|  | @ -977,7 +986,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) | ||||||
| 	git_config_set(key.buf, repo); | 	git_config_set(key.buf, repo); | ||||||
| 	strbuf_reset(&key); | 	strbuf_reset(&key); | ||||||
|  |  | ||||||
| 	if (option_reference.nr) | 	if (option_required_reference.nr || option_optional_reference.nr) | ||||||
| 		setup_reference(); | 		setup_reference(); | ||||||
|  |  | ||||||
| 	fetch_pattern = value.buf; | 	fetch_pattern = value.buf; | ||||||
|  |  | ||||||
|  | @ -442,7 +442,7 @@ static int module_name(int argc, const char **argv, const char *prefix) | ||||||
| } | } | ||||||
|  |  | ||||||
| static int clone_submodule(const char *path, const char *gitdir, const char *url, | static int clone_submodule(const char *path, const char *gitdir, const char *url, | ||||||
| 			   const char *depth, const char *reference, int quiet) | 			   const char *depth, struct string_list *reference, int quiet) | ||||||
| { | { | ||||||
| 	struct child_process cp = CHILD_PROCESS_INIT; | 	struct child_process cp = CHILD_PROCESS_INIT; | ||||||
|  |  | ||||||
|  | @ -452,8 +452,12 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url | ||||||
| 		argv_array_push(&cp.args, "--quiet"); | 		argv_array_push(&cp.args, "--quiet"); | ||||||
| 	if (depth && *depth) | 	if (depth && *depth) | ||||||
| 		argv_array_pushl(&cp.args, "--depth", depth, NULL); | 		argv_array_pushl(&cp.args, "--depth", depth, NULL); | ||||||
| 	if (reference && *reference) | 	if (reference->nr) { | ||||||
| 		argv_array_pushl(&cp.args, "--reference", reference, NULL); | 		struct string_list_item *item; | ||||||
|  | 		for_each_string_list_item(item, reference) | ||||||
|  | 			argv_array_pushl(&cp.args, "--reference", | ||||||
|  | 					 item->string, NULL); | ||||||
|  | 	} | ||||||
| 	if (gitdir && *gitdir) | 	if (gitdir && *gitdir) | ||||||
| 		argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); | 		argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); | ||||||
|  |  | ||||||
|  | @ -467,15 +471,114 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url | ||||||
| 	return run_command(&cp); | 	return run_command(&cp); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | struct submodule_alternate_setup { | ||||||
|  | 	const char *submodule_name; | ||||||
|  | 	enum SUBMODULE_ALTERNATE_ERROR_MODE { | ||||||
|  | 		SUBMODULE_ALTERNATE_ERROR_DIE, | ||||||
|  | 		SUBMODULE_ALTERNATE_ERROR_INFO, | ||||||
|  | 		SUBMODULE_ALTERNATE_ERROR_IGNORE | ||||||
|  | 	} error_mode; | ||||||
|  | 	struct string_list *reference; | ||||||
|  | }; | ||||||
|  | #define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ | ||||||
|  | 	SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } | ||||||
|  |  | ||||||
|  | static int add_possible_reference_from_superproject( | ||||||
|  | 		struct alternate_object_database *alt, void *sas_cb) | ||||||
|  | { | ||||||
|  | 	struct submodule_alternate_setup *sas = sas_cb; | ||||||
|  |  | ||||||
|  | 	/* directory name, minus trailing slash */ | ||||||
|  | 	size_t namelen = alt->name - alt->base - 1; | ||||||
|  | 	struct strbuf name = STRBUF_INIT; | ||||||
|  | 	strbuf_add(&name, alt->base, namelen); | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * If the alternate object store is another repository, try the | ||||||
|  | 	 * standard layout with .git/modules/<name>/objects | ||||||
|  | 	 */ | ||||||
|  | 	if (ends_with(name.buf, ".git/objects")) { | ||||||
|  | 		char *sm_alternate; | ||||||
|  | 		struct strbuf sb = STRBUF_INIT; | ||||||
|  | 		struct strbuf err = STRBUF_INIT; | ||||||
|  | 		strbuf_add(&sb, name.buf, name.len - strlen("objects")); | ||||||
|  | 		/* | ||||||
|  | 		 * We need to end the new path with '/' to mark it as a dir, | ||||||
|  | 		 * otherwise a submodule name containing '/' will be broken | ||||||
|  | 		 * as the last part of a missing submodule reference would | ||||||
|  | 		 * be taken as a file name. | ||||||
|  | 		 */ | ||||||
|  | 		strbuf_addf(&sb, "modules/%s/", sas->submodule_name); | ||||||
|  |  | ||||||
|  | 		sm_alternate = compute_alternate_path(sb.buf, &err); | ||||||
|  | 		if (sm_alternate) { | ||||||
|  | 			string_list_append(sas->reference, xstrdup(sb.buf)); | ||||||
|  | 			free(sm_alternate); | ||||||
|  | 		} else { | ||||||
|  | 			switch (sas->error_mode) { | ||||||
|  | 			case SUBMODULE_ALTERNATE_ERROR_DIE: | ||||||
|  | 				die(_("submodule '%s' cannot add alternate: %s"), | ||||||
|  | 				    sas->submodule_name, err.buf); | ||||||
|  | 			case SUBMODULE_ALTERNATE_ERROR_INFO: | ||||||
|  | 				fprintf(stderr, _("submodule '%s' cannot add alternate: %s"), | ||||||
|  | 					sas->submodule_name, err.buf); | ||||||
|  | 			case SUBMODULE_ALTERNATE_ERROR_IGNORE: | ||||||
|  | 				; /* nothing */ | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		strbuf_release(&sb); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	strbuf_release(&name); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void prepare_possible_alternates(const char *sm_name, | ||||||
|  | 		struct string_list *reference) | ||||||
|  | { | ||||||
|  | 	char *sm_alternate = NULL, *error_strategy = NULL; | ||||||
|  | 	struct submodule_alternate_setup sas = SUBMODULE_ALTERNATE_SETUP_INIT; | ||||||
|  |  | ||||||
|  | 	git_config_get_string("submodule.alternateLocation", &sm_alternate); | ||||||
|  | 	if (!sm_alternate) | ||||||
|  | 		return; | ||||||
|  |  | ||||||
|  | 	git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); | ||||||
|  |  | ||||||
|  | 	if (!error_strategy) | ||||||
|  | 		error_strategy = xstrdup("die"); | ||||||
|  |  | ||||||
|  | 	sas.submodule_name = sm_name; | ||||||
|  | 	sas.reference = reference; | ||||||
|  | 	if (!strcmp(error_strategy, "die")) | ||||||
|  | 		sas.error_mode = SUBMODULE_ALTERNATE_ERROR_DIE; | ||||||
|  | 	else if (!strcmp(error_strategy, "info")) | ||||||
|  | 		sas.error_mode = SUBMODULE_ALTERNATE_ERROR_INFO; | ||||||
|  | 	else if (!strcmp(error_strategy, "ignore")) | ||||||
|  | 		sas.error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE; | ||||||
|  | 	else | ||||||
|  | 		die(_("Value '%s' for submodule.alternateErrorStrategy is not recognized"), error_strategy); | ||||||
|  |  | ||||||
|  | 	if (!strcmp(sm_alternate, "superproject")) | ||||||
|  | 		foreach_alt_odb(add_possible_reference_from_superproject, &sas); | ||||||
|  | 	else if (!strcmp(sm_alternate, "no")) | ||||||
|  | 		; /* do nothing */ | ||||||
|  | 	else | ||||||
|  | 		die(_("Value '%s' for submodule.alternateLocation is not recognized"), sm_alternate); | ||||||
|  |  | ||||||
|  | 	free(sm_alternate); | ||||||
|  | 	free(error_strategy); | ||||||
|  | } | ||||||
|  |  | ||||||
| static int module_clone(int argc, const char **argv, const char *prefix) | static int module_clone(int argc, const char **argv, const char *prefix) | ||||||
| { | { | ||||||
| 	const char *name = NULL, *url = NULL; | 	const char *name = NULL, *url = NULL, *depth = NULL; | ||||||
| 	const char *reference = NULL, *depth = NULL; |  | ||||||
| 	int quiet = 0; | 	int quiet = 0; | ||||||
| 	FILE *submodule_dot_git; | 	FILE *submodule_dot_git; | ||||||
| 	char *p, *path = NULL, *sm_gitdir; | 	char *p, *path = NULL, *sm_gitdir; | ||||||
| 	struct strbuf rel_path = STRBUF_INIT; | 	struct strbuf rel_path = STRBUF_INIT; | ||||||
| 	struct strbuf sb = STRBUF_INIT; | 	struct strbuf sb = STRBUF_INIT; | ||||||
|  | 	struct string_list reference = STRING_LIST_INIT_NODUP; | ||||||
|  |  | ||||||
| 	struct option module_clone_options[] = { | 	struct option module_clone_options[] = { | ||||||
| 		OPT_STRING(0, "prefix", &prefix, | 		OPT_STRING(0, "prefix", &prefix, | ||||||
|  | @ -490,8 +593,8 @@ static int module_clone(int argc, const char **argv, const char *prefix) | ||||||
| 		OPT_STRING(0, "url", &url, | 		OPT_STRING(0, "url", &url, | ||||||
| 			   N_("string"), | 			   N_("string"), | ||||||
| 			   N_("url where to clone the submodule from")), | 			   N_("url where to clone the submodule from")), | ||||||
| 		OPT_STRING(0, "reference", &reference, | 		OPT_STRING_LIST(0, "reference", &reference, | ||||||
| 			   N_("string"), | 			   N_("repo"), | ||||||
| 			   N_("reference repository")), | 			   N_("reference repository")), | ||||||
| 		OPT_STRING(0, "depth", &depth, | 		OPT_STRING(0, "depth", &depth, | ||||||
| 			   N_("string"), | 			   N_("string"), | ||||||
|  | @ -527,7 +630,10 @@ static int module_clone(int argc, const char **argv, const char *prefix) | ||||||
| 	if (!file_exists(sm_gitdir)) { | 	if (!file_exists(sm_gitdir)) { | ||||||
| 		if (safe_create_leading_directories_const(sm_gitdir) < 0) | 		if (safe_create_leading_directories_const(sm_gitdir) < 0) | ||||||
| 			die(_("could not create directory '%s'"), sm_gitdir); | 			die(_("could not create directory '%s'"), sm_gitdir); | ||||||
| 		if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet)) |  | ||||||
|  | 		prepare_possible_alternates(name, &reference); | ||||||
|  |  | ||||||
|  | 		if (clone_submodule(path, sm_gitdir, url, depth, &reference, quiet)) | ||||||
| 			die(_("clone of '%s' into submodule path '%s' failed"), | 			die(_("clone of '%s' into submodule path '%s' failed"), | ||||||
| 			    url, path); | 			    url, path); | ||||||
| 	} else { | 	} else { | ||||||
|  | @ -579,7 +685,7 @@ struct submodule_update_clone { | ||||||
| 	/* configuration parameters which are passed on to the children */ | 	/* configuration parameters which are passed on to the children */ | ||||||
| 	int quiet; | 	int quiet; | ||||||
| 	int recommend_shallow; | 	int recommend_shallow; | ||||||
| 	const char *reference; | 	struct string_list references; | ||||||
| 	const char *depth; | 	const char *depth; | ||||||
| 	const char *recursive_prefix; | 	const char *recursive_prefix; | ||||||
| 	const char *prefix; | 	const char *prefix; | ||||||
|  | @ -595,7 +701,8 @@ struct submodule_update_clone { | ||||||
| 	int failed_clones_nr, failed_clones_alloc; | 	int failed_clones_nr, failed_clones_alloc; | ||||||
| }; | }; | ||||||
| #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ | #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ | ||||||
| 	SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, NULL, NULL, NULL, NULL, \ | 	SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, STRING_LIST_INIT_DUP, \ | ||||||
|  | 	NULL, NULL, NULL, \ | ||||||
| 	STRING_LIST_INIT_DUP, 0, NULL, 0, 0} | 	STRING_LIST_INIT_DUP, 0, NULL, 0, 0} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @ -705,8 +812,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, | ||||||
| 	argv_array_pushl(&child->args, "--path", sub->path, NULL); | 	argv_array_pushl(&child->args, "--path", sub->path, NULL); | ||||||
| 	argv_array_pushl(&child->args, "--name", sub->name, NULL); | 	argv_array_pushl(&child->args, "--name", sub->name, NULL); | ||||||
| 	argv_array_pushl(&child->args, "--url", url, NULL); | 	argv_array_pushl(&child->args, "--url", url, NULL); | ||||||
| 	if (suc->reference) | 	if (suc->references.nr) { | ||||||
| 		argv_array_push(&child->args, suc->reference); | 		struct string_list_item *item; | ||||||
|  | 		for_each_string_list_item(item, &suc->references) | ||||||
|  | 			argv_array_pushl(&child->args, "--reference", item->string, NULL); | ||||||
|  | 	} | ||||||
| 	if (suc->depth) | 	if (suc->depth) | ||||||
| 		argv_array_push(&child->args, suc->depth); | 		argv_array_push(&child->args, suc->depth); | ||||||
|  |  | ||||||
|  | @ -829,7 +939,7 @@ static int update_clone(int argc, const char **argv, const char *prefix) | ||||||
| 		OPT_STRING(0, "update", &update, | 		OPT_STRING(0, "update", &update, | ||||||
| 			   N_("string"), | 			   N_("string"), | ||||||
| 			   N_("rebase, merge, checkout or none")), | 			   N_("rebase, merge, checkout or none")), | ||||||
| 		OPT_STRING(0, "reference", &suc.reference, N_("repo"), | 		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"), | ||||||
| 			   N_("reference repository")), | 			   N_("reference repository")), | ||||||
| 		OPT_STRING(0, "depth", &suc.depth, "<depth>", | 		OPT_STRING(0, "depth", &suc.depth, "<depth>", | ||||||
| 			   N_("Create a shallow clone truncated to the " | 			   N_("Create a shallow clone truncated to the " | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								cache.h
								
								
								
								
							
							
						
						
									
										1
									
								
								cache.h
								
								
								
								
							|  | @ -1344,6 +1344,7 @@ extern struct alternate_object_database { | ||||||
| } *alt_odb_list; | } *alt_odb_list; | ||||||
| extern void prepare_alt_odb(void); | extern void prepare_alt_odb(void); | ||||||
| extern void read_info_alternates(const char * relative_base, int depth); | extern void read_info_alternates(const char * relative_base, int depth); | ||||||
|  | extern char *compute_alternate_path(const char *path, struct strbuf *err); | ||||||
| extern void add_to_alternates_file(const char *reference); | extern void add_to_alternates_file(const char *reference); | ||||||
| typedef int alt_odb_fn(struct alternate_object_database *, void *); | typedef int alt_odb_fn(struct alternate_object_database *, void *); | ||||||
| extern int foreach_alt_odb(alt_odb_fn, void*); | extern int foreach_alt_odb(alt_odb_fn, void*); | ||||||
|  |  | ||||||
|  | @ -576,7 +576,7 @@ cmd_update() | ||||||
| 		${wt_prefix:+--prefix "$wt_prefix"} \ | 		${wt_prefix:+--prefix "$wt_prefix"} \ | ||||||
| 		${prefix:+--recursive-prefix "$prefix"} \ | 		${prefix:+--recursive-prefix "$prefix"} \ | ||||||
| 		${update:+--update "$update"} \ | 		${update:+--update "$update"} \ | ||||||
| 		${reference:+--reference "$reference"} \ | 		${reference:+"$reference"} \ | ||||||
| 		${depth:+--depth "$depth"} \ | 		${depth:+--depth "$depth"} \ | ||||||
| 		${recommend_shallow:+"$recommend_shallow"} \ | 		${recommend_shallow:+"$recommend_shallow"} \ | ||||||
| 		${jobs:+$jobs} \ | 		${jobs:+$jobs} \ | ||||||
|  |  | ||||||
							
								
								
									
										76
									
								
								sha1_file.c
								
								
								
								
							
							
						
						
									
										76
									
								
								sha1_file.c
								
								
								
								
							|  | @ -419,6 +419,82 @@ void add_to_alternates_file(const char *reference) | ||||||
| 	free(alts); | 	free(alts); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Compute the exact path an alternate is at and returns it. In case of | ||||||
|  |  * error NULL is returned and the human readable error is added to `err` | ||||||
|  |  * `path` may be relative and should point to $GITDIR. | ||||||
|  |  * `err` must not be null. | ||||||
|  |  */ | ||||||
|  | char *compute_alternate_path(const char *path, struct strbuf *err) | ||||||
|  | { | ||||||
|  | 	char *ref_git = NULL; | ||||||
|  | 	const char *repo, *ref_git_s; | ||||||
|  | 	int seen_error = 0; | ||||||
|  |  | ||||||
|  | 	ref_git_s = real_path_if_valid(path); | ||||||
|  | 	if (!ref_git_s) { | ||||||
|  | 		seen_error = 1; | ||||||
|  | 		strbuf_addf(err, _("path '%s' does not exist"), path); | ||||||
|  | 		goto out; | ||||||
|  | 	} else | ||||||
|  | 		/* | ||||||
|  | 		 * Beware: read_gitfile(), real_path() and mkpath() | ||||||
|  | 		 * return static buffer | ||||||
|  | 		 */ | ||||||
|  | 		ref_git = xstrdup(ref_git_s); | ||||||
|  |  | ||||||
|  | 	repo = read_gitfile(ref_git); | ||||||
|  | 	if (!repo) | ||||||
|  | 		repo = read_gitfile(mkpath("%s/.git", ref_git)); | ||||||
|  | 	if (repo) { | ||||||
|  | 		free(ref_git); | ||||||
|  | 		ref_git = xstrdup(repo); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) { | ||||||
|  | 		char *ref_git_git = mkpathdup("%s/.git", ref_git); | ||||||
|  | 		free(ref_git); | ||||||
|  | 		ref_git = ref_git_git; | ||||||
|  | 	} else if (!is_directory(mkpath("%s/objects", ref_git))) { | ||||||
|  | 		struct strbuf sb = STRBUF_INIT; | ||||||
|  | 		seen_error = 1; | ||||||
|  | 		if (get_common_dir(&sb, ref_git)) { | ||||||
|  | 			strbuf_addf(err, | ||||||
|  | 				    _("reference repository '%s' as a linked " | ||||||
|  | 				      "checkout is not supported yet."), | ||||||
|  | 				    path); | ||||||
|  | 			goto out; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		strbuf_addf(err, _("reference repository '%s' is not a " | ||||||
|  | 					"local repository."), path); | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!access(mkpath("%s/shallow", ref_git), F_OK)) { | ||||||
|  | 		strbuf_addf(err, _("reference repository '%s' is shallow"), | ||||||
|  | 			    path); | ||||||
|  | 		seen_error = 1; | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) { | ||||||
|  | 		strbuf_addf(err, | ||||||
|  | 			    _("reference repository '%s' is grafted"), | ||||||
|  | 			    path); | ||||||
|  | 		seen_error = 1; | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | out: | ||||||
|  | 	if (seen_error) { | ||||||
|  | 		free(ref_git); | ||||||
|  | 		ref_git = NULL; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ref_git; | ||||||
|  | } | ||||||
|  |  | ||||||
| int foreach_alt_odb(alt_odb_fn fn, void *cb) | int foreach_alt_odb(alt_odb_fn fn, void *cb) | ||||||
| { | { | ||||||
| 	struct alternate_object_database *ent; | 	struct alternate_object_database *ent; | ||||||
|  |  | ||||||
|  | @ -8,74 +8,121 @@ test_description='test clone --reference' | ||||||
|  |  | ||||||
| base_dir=$(pwd) | base_dir=$(pwd) | ||||||
|  |  | ||||||
| U=$base_dir/UPLOAD_LOG | test_alternate_is_used () { | ||||||
|  | 	alternates_file="$1" && | ||||||
|  | 	working_dir="$2" && | ||||||
|  | 	test_line_count = 1 "$alternates_file" && | ||||||
|  | 	echo "0 objects, 0 kilobytes" >expect && | ||||||
|  | 	git -C "$working_dir" count-objects >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | } | ||||||
|  |  | ||||||
| test_expect_success 'preparing first repository' \ | test_expect_success 'preparing first repository' ' | ||||||
| 'test_create_repo A && cd A && | 	test_create_repo A && | ||||||
| echo first > file1 && | 	( | ||||||
| git add file1 && | 		cd A && | ||||||
| git commit -m A-initial' | 		echo first >file1 && | ||||||
|  | 		git add file1 && | ||||||
|  | 		git commit -m A-initial | ||||||
|  | 	) | ||||||
|  | ' | ||||||
|  |  | ||||||
| cd "$base_dir" | test_expect_success 'preparing second repository' ' | ||||||
|  | 	git clone A B && | ||||||
|  | 	( | ||||||
|  | 		cd B && | ||||||
|  | 		echo second >file2 && | ||||||
|  | 		git add file2 && | ||||||
|  | 		git commit -m B-addition && | ||||||
|  | 		git repack -a -d && | ||||||
|  | 		git prune | ||||||
|  | 	) | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_expect_success 'preparing second repository' \ | test_expect_success 'preparing superproject' ' | ||||||
| 'git clone A B && cd B && | 	test_create_repo super && | ||||||
| echo second > file2 && | 	( | ||||||
| git add file2 && | 		cd super && | ||||||
| git commit -m B-addition && | 		echo file >file && | ||||||
| git repack -a -d && | 		git add file && | ||||||
| git prune' | 		git commit -m B-super-initial | ||||||
|  | 	) | ||||||
|  | ' | ||||||
|  |  | ||||||
| cd "$base_dir" | test_expect_success 'submodule add --reference uses alternates' ' | ||||||
|  | 	( | ||||||
|  | 		cd super && | ||||||
|  | 		git submodule add --reference ../B "file://$base_dir/A" sub && | ||||||
|  | 		git commit -m B-super-added && | ||||||
|  | 		git repack -ad | ||||||
|  | 	) && | ||||||
|  | 	test_alternate_is_used super/.git/modules/sub/objects/info/alternates super/sub | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_expect_success 'preparing superproject' \ | test_expect_success 'that reference gets used with add' ' | ||||||
| 'test_create_repo super && cd super && | 	( | ||||||
| echo file > file && | 		cd super/sub && | ||||||
| git add file && | 		echo "0 objects, 0 kilobytes" >expected && | ||||||
| git commit -m B-super-initial' | 		git count-objects >current && | ||||||
|  | 		diff expected current | ||||||
|  | 	) | ||||||
|  | ' | ||||||
|  |  | ||||||
| cd "$base_dir" | # The tests up to this point, and repositories created by them | ||||||
|  | # (A, B, super and super/sub), are about setting up the stage | ||||||
|  | # for subsequent tests and meant to be kept throughout the | ||||||
|  | # remainder of the test. | ||||||
|  | # Tests from here on, if they create their own test repository, | ||||||
|  | # are expected to clean after themselves. | ||||||
|  |  | ||||||
| test_expect_success 'submodule add --reference' \ | test_expect_success 'updating superproject keeps alternates' ' | ||||||
| 'cd super && git submodule add --reference ../B "file://$base_dir/A" sub && | 	test_when_finished "rm -rf super-clone" && | ||||||
| git commit -m B-super-added' | 	git clone super super-clone && | ||||||
|  | 	git -C super-clone submodule update --init --reference ../B && | ||||||
|  | 	test_alternate_is_used super-clone/.git/modules/sub/objects/info/alternates super-clone/sub | ||||||
|  | ' | ||||||
|  |  | ||||||
| cd "$base_dir" | test_expect_success 'submodules use alternates when cloning a superproject' ' | ||||||
|  | 	test_when_finished "rm -rf super-clone" && | ||||||
|  | 	git clone --reference super --recursive super super-clone && | ||||||
|  | 	( | ||||||
|  | 		cd super-clone && | ||||||
|  | 		# test superproject has alternates setup correctly | ||||||
|  | 		test_alternate_is_used .git/objects/info/alternates . && | ||||||
|  | 		# test submodule has correct setup | ||||||
|  | 		test_alternate_is_used .git/modules/sub/objects/info/alternates sub | ||||||
|  | 	) | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_expect_success 'after add: existence of info/alternates' \ | test_expect_success 'missing submodule alternate fails clone and submodule update' ' | ||||||
| 'test_line_count = 1 super/.git/modules/sub/objects/info/alternates' | 	test_when_finished "rm -rf super-clone" && | ||||||
|  | 	git clone super super2 && | ||||||
|  | 	test_must_fail git clone --recursive --reference super2 super2 super-clone && | ||||||
|  | 	( | ||||||
|  | 		cd super-clone && | ||||||
|  | 		# test superproject has alternates setup correctly | ||||||
|  | 		test_alternate_is_used .git/objects/info/alternates . && | ||||||
|  | 		# update of the submodule succeeds | ||||||
|  | 		test_must_fail git submodule update --init && | ||||||
|  | 		# and we have no alternates: | ||||||
|  | 		test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub && | ||||||
|  | 		test_must_fail test_path_is_file sub/file1 | ||||||
|  | 	) | ||||||
|  | ' | ||||||
|  |  | ||||||
| cd "$base_dir" | test_expect_success 'ignoring missing submodule alternates passes clone and submodule update' ' | ||||||
|  | 	test_when_finished "rm -rf super-clone" && | ||||||
| test_expect_success 'that reference gets used with add' \ | 	git clone --reference-if-able super2 --recursive super2 super-clone && | ||||||
| 'cd super/sub && | 	( | ||||||
| echo "0 objects, 0 kilobytes" > expected && | 		cd super-clone && | ||||||
| git count-objects > current && | 		# test superproject has alternates setup correctly | ||||||
| diff expected current' | 		test_alternate_is_used .git/objects/info/alternates . && | ||||||
|  | 		# update of the submodule succeeds | ||||||
| cd "$base_dir" | 		git submodule update --init && | ||||||
|  | 		# and we have no alternates: | ||||||
| test_expect_success 'cloning superproject' \ | 		test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub && | ||||||
| 'git clone super super-clone' | 		test_path_is_file sub/file1 | ||||||
|  | 	) | ||||||
| cd "$base_dir" | ' | ||||||
|  |  | ||||||
| test_expect_success 'update with reference' \ |  | ||||||
| 'cd super-clone && git submodule update --init --reference ../B' |  | ||||||
|  |  | ||||||
| cd "$base_dir" |  | ||||||
|  |  | ||||||
| test_expect_success 'after update: existence of info/alternates' \ |  | ||||||
| 'test_line_count = 1 super-clone/.git/modules/sub/objects/info/alternates' |  | ||||||
|  |  | ||||||
| cd "$base_dir" |  | ||||||
|  |  | ||||||
| test_expect_success 'that reference gets used with update' \ |  | ||||||
| 'cd super-clone/sub && |  | ||||||
| echo "0 objects, 0 kilobytes" > expected && |  | ||||||
| git count-objects > current && |  | ||||||
| diff expected current' |  | ||||||
|  |  | ||||||
| cd "$base_dir" |  | ||||||
|  |  | ||||||
| test_done | test_done | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano