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

[RFC PATCH 09/10] submodule: support reading .gitmodules even when it's not checked out

From
Antonio Ospite <ao2@ao2.it>
Date
May 14, 2018, 10:58 UTC
Message-ID
<20180514105823.8378-10-ao2@ao2.it>
In-Reply-To
<20180514105823.8378-1-ao2@ao2.it>

When the .gitmodules file is not available in the working directory, try using HEAD:.gitmodules from the index. This covers the case when the file is part of the repository but for some reason it is not checked out, for example because of a sparse checkout.

This makes it possible to use at least the 'git submodule' commands which *read* the gitmodules configuration file without fully populating the work dir.

Writing to .gitmodules wills still require that the file is checked out, so check for that in config_gitmodules_set.

Signed-off-by: Antonio Ospite <ao2@ao2.it>
---

I am doing the is_gitmodules_hidden() check in the open for now, I am not sure whether it is approprate to do that inside stage_updated_gitmodules.

 builtin/mv.c                |  2 ++
 builtin/rm.c                |  7 +++++--
 builtin/submodule--helper.c | 21 ++++++++++++++++++++-
 cache.h                     |  1 +
 config.c                    | 15 +++++++++++++--
 submodule.c                 | 15 +++++++++++++++
 submodule.h                 |  1 +
 7 files changed, 57 insertions(+), 5 deletions(-)
diff --git a/builtin/mv.c b/builtin/mv.c
index 7a63667d6..41fd9b7be 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -83,6 +83,8 @@ static void prepare_move_submodule(const char *src, int first,
 		die(_("Directory %s is in index and no submodule?"), src);
 	if (!is_staging_gitmodules_ok(&the_index))
 		die(_("Please stage your changes to .gitmodules or stash them to proceed"));
+	if (is_gitmodules_hidden(&the_index))
+		die(_("cannot work with hidden submodule config"));
 	strbuf_addf(&submodule_dotgit, "%s/.git", src);
 	*submodule_gitfile = read_gitfile(submodule_dotgit.buf);
 	if (*submodule_gitfile)
diff --git a/builtin/rm.c b/builtin/rm.c
index 5b6fc7ee8..e3526a342 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -284,9 +284,12 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 		ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
 		list.entry[list.nr].name = xstrdup(ce->name);
 		list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode);
-		if (list.entry[list.nr++].is_submodule &&
-		    !is_staging_gitmodules_ok(&the_index))
+		if (list.entry[list.nr++].is_submodule) {
+		    if (!is_staging_gitmodules_ok(&the_index))
 			die (_("Please stage your changes to .gitmodules or stash them to proceed"));
+		    if (is_gitmodules_hidden(&the_index))
+			die(_("cannot work with hidden submodule config"));
+		}
 	}
 
 	if (pathspec.nr) {
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index de5caa776..b3bdb4b66 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1873,6 +1873,9 @@ static int module_config(int argc, const char **argv, const char *prefix)
 		if (read_cache() < 0)
 			die(_("index file corrupt"));
 
+		if (is_gitmodules_hidden(&the_index))
+			die(_("cannot stage changes to hidden submodule config"));
+
 		stage_updated_gitmodules(&the_index);
 
 		if (write_locked_index(&the_index, &lock_file,
@@ -1897,8 +1900,24 @@ static int module_config(int argc, const char **argv, const char *prefix)
 	}
 
 	/* Equivalent to ACTION_SET in builtin/config.c */
-	if (argc == 3)
+	if (argc == 3) {
+		struct object_id oid;
+
+		/* 
+		 * If the .gitmodules file is not in the work tree but it is
+		 * in the index, stop, as writing new values and staging them
+		 * would blindly overwrite ALL the old content.
+		 *
+		 * Do not use is_gitmodules_hidden() here, to gracefully
+		 * handle the case when .gitmodules is neither in the work
+		 * tree nor in the index, i.e.: a new GITMODULES_FILE is going
+		 * to be created.
+		 */
+		if (!file_exists(GITMODULES_FILE) && get_oid(GITMODULES_BLOB, &oid) >= 0)
+			die(_("cannot change unchecked out .gitmodules, check it out first"));
+
 		return config_gitmodules_set(argv[1], argv[2]);
+	}
 
 	return 0;
 }
diff --git a/cache.h b/cache.h
index 0c1fb9fbc..6d45b0cbb 100644
--- a/cache.h
+++ b/cache.h
@@ -417,6 +417,7 @@ static inline enum object_type object_type(unsigned int mode)
 #define INFOATTRIBUTES_FILE "info/attributes"
 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
 #define GITMODULES_FILE ".gitmodules"
+#define GITMODULES_BLOB "HEAD:.gitmodules"
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
 #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
diff --git a/config.c b/config.c
index 8ffe29330..7d9744622 100644
--- a/config.c
+++ b/config.c
@@ -2184,8 +2184,19 @@ int git_config_get_pathname(const char *key, const char **dest)
 void config_from_gitmodules(config_fn_t fn, struct repository *repo, void *data)
 {
 	if (repo->worktree) {
-		char *file = repo_worktree_path(repo, GITMODULES_FILE);
-		git_config_from_file(fn, file, data);
+		struct git_config_source config_source = { 0 };
+		const struct config_options opts = { 0 };
+		struct object_id oid;
+		char *file;
+
+		file = repo_worktree_path(repo, GITMODULES_FILE);
+		if (file_exists(file))
+			config_source.file = file;
+		else if (get_oid(GITMODULES_BLOB, &oid) >= 0)
+			config_source.blob = GITMODULES_BLOB;
+
+		config_with_options(fn, data, &config_source, &opts);
+
 		free(file);
 	}
 }
diff --git a/submodule.c b/submodule.c
index 7cfae89b6..83d0ca5a6 100644
--- a/submodule.c
+++ b/submodule.c
@@ -50,6 +50,21 @@ int is_gitmodules_unmerged(const struct index_state *istate)
 	return 0;
 }
 
+/*
+ * Check if the .gitmodules file is in the index but it is marked as hidden,
+ * like for example via a sparse checkout.
+ */
+int is_gitmodules_hidden(const struct index_state *istate)
+{
+	int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
+
+	if (pos >= 0) {
+		return ce_skip_worktree(istate->cache[pos]);
+	}
+
+	return 0;
+}
+
 /*
  * Check if the .gitmodules file has unstaged modifications.  This must be
  * checked before allowing modifications to the .gitmodules file with the
diff --git a/submodule.h b/submodule.h
index 8a252e514..4ec9a3781 100644
--- a/submodule.h
+++ b/submodule.h
@@ -34,6 +34,7 @@ struct submodule_update_strategy {
 #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
 
 extern int is_gitmodules_unmerged(const struct index_state *istate);
+extern int is_gitmodules_hidden(const struct index_state *istate);
 extern int is_staging_gitmodules_ok(struct index_state *istate);
 extern int config_gitmodules_set(const char *key, const char *value);
 extern int update_path_in_gitmodules(const char *oldpath, const char *newpath);
-- 
2.17.0
Previous: Antonio OspiteNext: Stefan Beller
Message 24 of 27 in “Make submodules work if .gitmodules is not checked out”
  1. 00/10 Make submodules work if .gitmodules is not checked outAntonio Ospite, May 14, 2018
  2. 03/10 t7411: be nicer to other tests and really clean things upAntonio Ospite, May 14, 2018
  3. Stefan BellerMay 15, 2018
  4. Antonio OspiteJun 20, 2018
  5. 02/10 submodule: factor out a config_gitmodules_set functionAntonio Ospite, May 14, 2018
  6. Stefan BellerMay 15, 2018
  7. Antonio OspiteJun 20, 2018
  8. 04/10 submodule--helper: add a new 'config' subcommandAntonio Ospite, May 14, 2018
  9. Stefan BellerMay 15, 2018
  10. Antonio OspiteJun 20, 2018
  11. 01/10 config: make config_from_gitmodules generally usefulAntonio Ospite, May 14, 2018
  12. Brandon WilliamsMay 14, 2018
  13. Antonio OspiteJun 20, 2018
  14. Stefan BellerMay 15, 2018
  15. Antonio OspiteJun 20, 2018
  16. Stefan BellerJun 20, 2018
  17. Antonio OspiteJun 21, 2018
  18. Stefan BellerJun 21, 2018
  19. 05/10 submodule: use the 'submodule--helper config' commandAntonio Ospite, May 14, 2018
  20. 06/10 submodule--helper: add a '--stage' option to the 'config' sub commandAntonio Ospite, May 14, 2018
  21. 07/10 submodule: use 'submodule--helper config --stage' to stage .gitmodulesAntonio Ospite, May 14, 2018
  22. 08/10 t7506: cleanup .gitmodules properly before setting up new scenarioAntonio Ospite, May 14, 2018
  23. 10/10 t7415: add new test about using HEAD:.gitmodules from the indexAntonio Ospite, May 14, 2018
  24. 09/10 submodule: support reading .gitmodules even when it's not checked outAntonio Ospite, May 14, 2018
  25. Stefan BellerMay 15, 2018
  26. Stefan BellerMay 15, 2018
  27. Junio C HamanoMay 15, 2018

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.