submodule: add a helper to check if it is safe to write to .gitmodules

Introduce a helper function named is_writing_gitmodules_ok() to verify that the .gitmodules file is safe to write. The function name follows the scheme of is_staging_gitmodules_ok(). The two symbolic constants GITMODULES_INDEX and GITMODULES_HEAD are used to get help from the C preprocessor in preventing typos, especially for future users. This is in preparation for a future change which teaches git how to read .gitmodules from the index or from the current branch if the file is not available in the working tree. The rationale behind the check is that writing to .gitmodules requires the file to be present in the working tree, unless a brand new .gitmodules is being created (in which case the .gitmodules file would not exist at all: neither in the working tree nor in the index or in the current branch). Expose the functionality also via a "submodule-helper config --check-writeable" command, as git scripts may want to perform the check before modifying submodules configuration. Signed-off-by: Antonio Ospite <ao2@ao2.it> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Antonio Ospite committed Oct 5, 2018 at 15:05 UTC b5c259f226ba06f64b399ff4c6b843542352395f
5 files changed +75 -1
builtin/submodule--helper.c
+23 -1
@@ -2005,6 +2005,28 @@ static int check_name(int argc, const char **argv, const char *prefix)
2005
2006 static int module_config(int argc, const char **argv, const char *prefix)
2007 {
2008 + enum {
2009 + CHECK_WRITEABLE = 1
2010 + } command = 0;
2011 +
2012 + struct option module_config_options[] = {
2013 + OPT_CMDMODE(0, "check-writeable", &command,
2014 + N_("check if it is safe to write to the .gitmodules file"),
2015 + CHECK_WRITEABLE),
2016 + OPT_END()
2017 + };
2018 + const char *const git_submodule_helper_usage[] = {
2019 + N_("git submodule--helper config name [value]"),
2020 + N_("git submodule--helper config --check-writeable"),
2021 + NULL
2022 + };
2023 +
2024 + argc = parse_options(argc, argv, prefix, module_config_options,
2025 + git_submodule_helper_usage, PARSE_OPT_KEEP_ARGV0);
2026 +
2027 + if (argc == 1 && command == CHECK_WRITEABLE)
2028 + return is_writing_gitmodules_ok() ? 0 : -1;
2029 +
2030 /* Equivalent to ACTION_GET in builtin/config.c */
2031 if (argc == 2)
2032 return print_config_from_gitmodules(the_repository, argv[1]);
@@ -2013,7 +2035,7 @@ static int module_config(int argc, const char **argv, const char *prefix)
2035 if (argc == 3)
2036 return config_set_in_gitmodules_file_gently(argv[1], argv[2]);
2037
2016 - die("submodule--helper config takes 1 or 2 arguments: name [value]");
2038 + usage_with_options(git_submodule_helper_usage, module_config_options);
2039 }
2040
2041 #define SUPPORT_SUPER_PREFIX (1<<0)
cache.h
+2
@@ -486,6 +486,8 @@ static inline enum object_type object_type(unsigned int mode)
486 #define INFOATTRIBUTES_FILE "info/attributes"
487 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
488 #define GITMODULES_FILE ".gitmodules"
489 +#define GITMODULES_INDEX ":.gitmodules"
490 +#define GITMODULES_HEAD "HEAD:.gitmodules"
491 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
492 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
493 #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
submodule.c
+18
@@ -50,6 +50,24 @@ int is_gitmodules_unmerged(const struct index_state *istate)
50 return 0;
51 }
52
53 +/*
54 + * Check if the .gitmodules file is safe to write.
55 + *
56 + * Writing to the .gitmodules file requires that the file exists in the
57 + * working tree or, if it doesn't, that a brand new .gitmodules file is going
58 + * to be created (i.e. it's neither in the index nor in the current branch).
59 + *
60 + * It is not safe to write to .gitmodules if it's not in the working tree but
61 + * it is in the index or in the current branch, because writing new values
62 + * (and staging them) would blindly overwrite ALL the old content.
63 + */
64 +int is_writing_gitmodules_ok(void)
65 +{
66 + struct object_id oid;
67 + return file_exists(GITMODULES_FILE) ||
68 + (get_oid(GITMODULES_INDEX, &oid) < 0 && get_oid(GITMODULES_HEAD, &oid) < 0);
69 +}
70 +
71 /*
72 * Check if the .gitmodules file has unstaged modifications. This must be
73 * checked before allowing modifications to the .gitmodules file with the
submodule.h
+1
@@ -40,6 +40,7 @@ struct submodule_update_strategy {
40 #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
41
42 int is_gitmodules_unmerged(const struct index_state *istate);
43 +int is_writing_gitmodules_ok(void);
44 int is_staging_gitmodules_ok(struct index_state *istate);
45 int update_path_in_gitmodules(const char *oldpath, const char *newpath);
46 int remove_path_from_gitmodules(const char *path);
t/t7411-submodule-config.sh
+31
@@ -161,4 +161,35 @@ test_expect_success 'overwriting unstaged submodules config with "submodule--hel
161 )
162 '
163
164 +test_expect_success 'writeable .gitmodules when it is in the working tree' '
165 + git -C super submodule--helper config --check-writeable
166 +'
167 +
168 +test_expect_success 'writeable .gitmodules when it is nowhere in the repository' '
169 + ORIG=$(git -C super rev-parse HEAD) &&
170 + test_when_finished "git -C super reset --hard $ORIG" &&
171 + (cd super &&
172 + git rm .gitmodules &&
173 + git commit -m "remove .gitmodules from the current branch" &&
174 + git submodule--helper config --check-writeable
175 + )
176 +'
177 +
178 +test_expect_success 'non-writeable .gitmodules when it is in the index but not in the working tree' '
179 + test_when_finished "git -C super checkout .gitmodules" &&
180 + (cd super &&
181 + rm -f .gitmodules &&
182 + test_must_fail git submodule--helper config --check-writeable
183 + )
184 +'
185 +
186 +test_expect_success 'non-writeable .gitmodules when it is in the current branch but not in the index' '
187 + ORIG=$(git -C super rev-parse HEAD) &&
188 + test_when_finished "git -C super reset --hard $ORIG" &&
189 + (cd super &&
190 + git rm .gitmodules &&
191 + test_must_fail git submodule--helper config --check-writeable
192 + )
193 +'
194 +
195 test_done