git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC/PATCH] clone: introduce clone.submoduleGitDir to relocate $GITDIR

From
Ramkumar Ramachandra <artagnon@gmail.com>
Date
Apr 13, 2013, 19:23 UTC
Message-ID
<1365881007-25731-1-git-send-email-artagnon@gmail.com>

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@gmail.com>
---
 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
Next: Junio C Hamano
Message 1 of 41 in “clone: introduce clone.submoduleGitDir to relocate $GITDIR”
  1. clone: introduce clone.submoduleGitDir to relocate $GITDIRRamkumar Ramachandra, Apr 13, 2013
  2. Junio C HamanoApr 15, 2013
  3. Junio C HamanoApr 15, 2013
  4. Ramkumar RamachandraApr 15, 2013
  5. Junio C HamanoApr 15, 2013
  6. Ramkumar RamachandraApr 15, 2013
  7. Ramkumar RamachandraApr 15, 2013
  8. Ramkumar RamachandraApr 15, 2013
  9. Duy NguyenApr 15, 2013
  10. Ramkumar RamachandraApr 15, 2013
  11. Junio C HamanoApr 15, 2013
  12. Ramkumar RamachandraApr 15, 2013
  13. Marc BranchaudApr 15, 2013
  14. Junio C HamanoApr 15, 2013
  15. Ramkumar RamachandraApr 15, 2013
  16. Jeff KingApr 15, 2013
  17. Junio C HamanoApr 15, 2013
  18. Ramkumar RamachandraApr 16, 2013
  19. Marc BranchaudApr 16, 2013
  20. Marc BranchaudApr 15, 2013
  21. Ramkumar RamachandraApr 16, 2013
  22. Marc BranchaudApr 16, 2013
  23. Marc BranchaudApr 15, 2013
  24. Junio C HamanoApr 15, 2013
  25. Marc BranchaudApr 15, 2013
  26. Junio C HamanoApr 15, 2013
  27. Ramkumar RamachandraApr 16, 2013
  28. Marc BranchaudApr 16, 2013
  29. Ramkumar RamachandraApr 15, 2013
  30. Jonathan NiederApr 16, 2013
  31. Ramkumar RamachandraApr 16, 2013
  32. Junio C HamanoApr 16, 2013
  33. Jonathan NiederApr 17, 2013
  34. Duy NguyenApr 17, 2013
  35. Ramkumar RamachandraApr 17, 2013
  36. Duy NguyenApr 17, 2013
  37. Ramkumar RamachandraApr 17, 2013
  38. Duy NguyenApr 17, 2013
  39. Ramkumar RamachandraApr 17, 2013
  40. Duy NguyenApr 17, 2013
  41. Junio C HamanoApr 17, 2013

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.