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@xxxxxx> --- 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