This configuration variable comes into effect when 'git clone' is invoked inside an existing git repository's worktree. When set, instead of cloning the given repository as-is, it relocates the gitdir of the repository to the path specified by this variable. This setting is especially useful when working with submodules. Signed-off-by: Ramkumar Ramachandra <artagnon@xxxxxxxxx> --- Okay, so this is part of my evil plan to make 'git add' DTRT wrt submodules, and deprecate 'git submodule add' (I have some code written down, but this is a prerequisite: I don't like the .git/modules nonsense). Unfortunately, this patch is in pathetic shape and is an RFC for three reasons: 1. I've used setup_git_directory_gently() at the start of builtin/clone.c to check if I'm inside a git directory. This breaks a lot of existing tests (I'm yet to understand these failures fully). 2. setup_git_directory_gently() has the side-effect of changing the current directory and calling set_git_work_tree(), both of which must be done away with if we want the rest of clone.c to work. I've hacked around the issue in a very dirty manner. What is the solution to this? 3. I don't know how to test the case "clone.submoduleGitDir has no effect outside a git repository", because our entire test environment is a git repository. Even if I remove the .git directory, we're still inside the soure tree's git repository. What do I do about this? Even if we decide that this patch is fundamentally unworkable, we should try to fix this issue so that we can verify that a plain 'git clone' works outside a git repository. Thanks for reading. And I'm truly sorry for making you read through such ugly code. Documentation/config.txt | 11 +++++++++++ builtin/clone.c | 33 ++++++++++++++++++++++++++++++++- environment.c | 11 ----------- t/t5702-clone-options.sh | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 12 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 3d750e0..aac26c3 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -798,6 +798,17 @@ clean.requireForce:: A boolean to make git-clean do nothing unless given -f or -n. Defaults to true. +clone.submoduleGitDir:: + An absolute path on the filesystem where gitdirs of submodules + should be stored away safely. When not set, a 'git clone' + executed inside a git repository will do exactly what it does + outside a git repository. When set, a 'git clone' executed + inside a git repository will create the worktree in place of + the full repository, and put the object store in a + subdirectory of clone.submoduleGitDir, choosing the name to be + the "humanish" part of the source repository (`repo.git` for + `/path/to/repo.git` and `foo.git` for `host.xz:foo/.git`). + color.branch:: A boolean to enable/disable color in the output of linkgit:git-branch[1]. May be set to `always`, diff --git a/builtin/clone.c b/builtin/clone.c index f9c380e..4a845a4 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -44,6 +44,7 @@ static char *option_template, *option_depth; static char *option_origin = NULL; static char *option_branch = NULL; static const char *real_git_dir; +static const char *submodule_gitdir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress = -1; @@ -707,12 +708,22 @@ static void write_refspec_config(const char* src_ref_prefix, strbuf_release(&value); } +static int git_clone_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "clone.submodulegitdir")) { + git_config_string(&submodule_gitdir, var, value); + return 0; + } + return git_default_config(var, value, cb); +} + int cmd_clone(int argc, const char **argv, const char *prefix) { int is_bundle = 0, is_local; struct stat buf; const char *repo_name, *repo, *work_tree, *git_dir; - char *path, *dir; + char *path, *dir, *dest_git_dir; + char cwd[PATH_MAX]; int dest_exists; const struct ref *refs, *remote_head; const struct ref *remote_head_points_at; @@ -725,6 +736,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const char *src_ref_prefix = "refs/heads/"; struct remote *remote; int err = 0, complete_refs_before_fetch = 1; + int nongit = 1; struct refspec *refspec; const char *fetch_pattern; @@ -732,6 +744,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) junk_pid = getpid(); packet_trace_identity("clone"); + + /* setup_git_directory_gently without changing directories */ + getcwd(cwd, sizeof(cwd) - 1); + setup_git_directory_gently(&nongit); + chdir(cwd); + + git_config(git_clone_config, NULL); + argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); @@ -785,6 +805,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die(_("destination path '%s' already exists and is not " "an empty directory."), dir); + if (!nongit && submodule_gitdir) { + char *user_path = expand_user_path(submodule_gitdir); + if (!user_path) + die(_("Unable to expand path in clone.submoduleGitDir: %s"), submodule_gitdir); + dest_git_dir = mkpathdup("%s/%s.git", user_path, dir); + if (!stat(dest_git_dir, &buf) && !is_empty_dir(dest_git_dir)) + die(_("destination path '%s' already exists and is not " + "an empty directory."), dest_git_dir); + real_git_dir = dest_git_dir; + } + strbuf_addf(&reflog_msg, "clone: from %s", repo); if (option_bare) diff --git a/environment.c b/environment.c index e2e75c1..9dce4c7 100644 --- a/environment.c +++ b/environment.c @@ -182,8 +182,6 @@ const char *strip_namespace(const char *namespaced_ref) return namespaced_ref + namespace_len; } -static int git_work_tree_initialized; - /* * Note. This works only before you used a work tree. This was added * primarily to support git-clone to work in a new repository it just @@ -191,15 +189,6 @@ static int git_work_tree_initialized; */ void set_git_work_tree(const char *new_work_tree) { - if (git_work_tree_initialized) { - new_work_tree = real_path(new_work_tree); - if (strcmp(new_work_tree, work_tree)) - die("internal error: work tree has already been set\n" - "Current worktree: %s\nNew worktree: %s", - work_tree, new_work_tree); - return; - } - git_work_tree_initialized = 1; work_tree = xstrdup(real_path(new_work_tree)); } diff --git a/t/t5702-clone-options.sh b/t/t5702-clone-options.sh index 02cb024..9b845d8 100755 --- a/t/t5702-clone-options.sh +++ b/t/t5702-clone-options.sh @@ -33,4 +33,45 @@ test_expect_success 'redirected clone -v' ' ' +test_expect_success 'clone.submoduleGitDir takes effect in a git repository' ' + cd ~ && + rm -rf bare newrepo superproject && + mkdir bare && + bare_path="$(pwd)/bare" && + git init newrepo && + ( + cd newrepo && + echo quux >foo && + git add foo && + git commit -m "Add foo" + ) && + git init superproject && + cd superproject && + test_config clone.submoduleGitDir "$bare_path" && + git clone ../newrepo && + test_path_is_file newrepo/.git && + cd ../bare/newrepo.git && + git rev-parse --is-bare-repository +' + +test_expect_success 'clone.submoduleGitDir path get tilde-expansion' ' + cd ~ && + rm -rf bare newrepo superproject && + mkdir bare && + git init newrepo && + ( + cd newrepo && + echo quux >foo && + git add foo && + git commit -m "Add foo" + ) && + git init superproject && + cd superproject && + test_config clone.submoduleGitDir ~/bare && + git clone ../newrepo && + test_path_is_file newrepo/.git && + cd ../bare/newrepo.git && + git rev-parse --is-bare-repository +' + test_done -- 1.8.2.1.389.gcaa7d79.dirty -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html