Skip to content

Commit 5321399

Browse files
peffgitster
authored andcommitted
add: warn when adding an embedded repository
It's an easy mistake to add a repository inside another repository, like: git clone $url git add . The resulting entry is a gitlink, but there's no matching .gitmodules entry. Trying to use "submodule init" (or clone with --recursive) doesn't do anything useful. Prior to v2.13, such an entry caused git-submodule to barf entirely. In v2.13, the entry is considered "inactive" and quietly ignored. Either way, no clone of your repository can do anything useful with the gitlink without the user manually adding the submodule config. In most cases, the user probably meant to either add a real submodule, or they forgot to put the embedded repository in their .gitignore file. Let's issue a warning when we see this case. There are a few things to note: - the warning will go in the git-add porcelain; anybody wanting to do low-level manipulation of the index is welcome to create whatever funny states they want. - we detect the case by looking for a newly added gitlink; updates via "git add submodule" are perfectly reasonable, and this avoids us having to investigate .gitmodules entirely - there's a command-line option to suppress the warning. This is needed for git-submodule itself (which adds the entry before adding any submodule config), but also provides a mechanism for other scripts doing submodule-like things. We could make this a hard error instead of a warning. However, we do add lots of sub-repos in our test suite. It's not _wrong_ to do so. It just creates a state where users may be surprised. Pointing them in the right direction with a gentle hint is probably the best option. There is a config knob that can disable the (long) hint. But I intentionally omitted a config knob to disable the warning entirely. Whether the warning is sensible or not is generally about context, not about the user's preferences. If there's a tool or workflow that adds gitlinks without matching .gitmodules, it should probably be taught about the new command-line option, rather than blanket-disabling the warning. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 41dd433 commit 5321399

File tree

7 files changed

+98
-3
lines changed

7 files changed

+98
-3
lines changed

Documentation/config.txt

+3
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,9 @@ advice.*::
348348
rmHints::
349349
In case of failure in the output of linkgit:git-rm[1],
350350
show directions on how to proceed from the current state.
351+
addEmbeddedRepo::
352+
Advice on what to do when you've accidentally added one
353+
git repo inside of another.
351354
--
352355

353356
core.fileMode::

Documentation/git-add.txt

+7
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,13 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
165165
be ignored, no matter if they are already present in the work
166166
tree or not.
167167

168+
--no-warn-embedded-repo::
169+
By default, `git add` will warn when adding an embedded
170+
repository to the index without using `git submodule add` to
171+
create an entry in `.gitmodules`. This option will suppress the
172+
warning (e.g., if you are manually performing operations on
173+
submodules).
174+
168175
--chmod=(+|-)x::
169176
Override the executable bit of the added files. The executable
170177
bit is only changed in the index, the files on disk are left

advice.c

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ int advice_detached_head = 1;
1515
int advice_set_upstream_failure = 1;
1616
int advice_object_name_warning = 1;
1717
int advice_rm_hints = 1;
18+
int advice_add_embedded_repo = 1;
1819

1920
static struct {
2021
const char *name;
@@ -35,6 +36,7 @@ static struct {
3536
{ "setupstreamfailure", &advice_set_upstream_failure },
3637
{ "objectnamewarning", &advice_object_name_warning },
3738
{ "rmhints", &advice_rm_hints },
39+
{ "addembeddedrepo", &advice_add_embedded_repo },
3840

3941
/* make this an alias for backward compatibility */
4042
{ "pushnonfastforward", &advice_push_update_rejected }

advice.h

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extern int advice_detached_head;
1818
extern int advice_set_upstream_failure;
1919
extern int advice_object_name_warning;
2020
extern int advice_rm_hints;
21+
extern int advice_add_embedded_repo;
2122

2223
int git_default_advice_config(const char *var, const char *value);
2324
__attribute__((format (printf, 1, 2)))

builtin/add.c

+45-1
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ N_("The following paths are ignored by one of your .gitignore files:\n");
249249

250250
static int verbose, show_only, ignored_too, refresh_only;
251251
static int ignore_add_errors, intent_to_add, ignore_missing;
252+
static int warn_on_embedded_repo = 1;
252253

253254
#define ADDREMOVE_DEFAULT 1
254255
static int addremove = ADDREMOVE_DEFAULT;
@@ -282,6 +283,8 @@ static struct option builtin_add_options[] = {
282283
OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
283284
OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
284285
OPT_STRING( 0 , "chmod", &chmod_arg, N_("(+/-)x"), N_("override the executable bit of the listed files")),
286+
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
287+
N_("warn when adding an embedded repository")),
285288
OPT_END(),
286289
};
287290

@@ -295,6 +298,45 @@ static int add_config(const char *var, const char *value, void *cb)
295298
return git_default_config(var, value, cb);
296299
}
297300

301+
static const char embedded_advice[] = N_(
302+
"You've added another git repository inside your current repository.\n"
303+
"Clones of the outer repository will not contain the contents of\n"
304+
"the embedded repository and will not know how to obtain it.\n"
305+
"If you meant to add a submodule, use:\n"
306+
"\n"
307+
" git submodule add <url> %s\n"
308+
"\n"
309+
"If you added this path by mistake, you can remove it from the\n"
310+
"index with:\n"
311+
"\n"
312+
" git rm --cached %s\n"
313+
"\n"
314+
"See \"git help submodule\" for more information."
315+
);
316+
317+
static void check_embedded_repo(const char *path)
318+
{
319+
struct strbuf name = STRBUF_INIT;
320+
321+
if (!warn_on_embedded_repo)
322+
return;
323+
if (!ends_with(path, "/"))
324+
return;
325+
326+
/* Drop trailing slash for aesthetics */
327+
strbuf_addstr(&name, path);
328+
strbuf_strip_suffix(&name, "/");
329+
330+
warning(_("adding embedded git repository: %s"), name.buf);
331+
if (advice_add_embedded_repo) {
332+
advise(embedded_advice, name.buf, name.buf);
333+
/* there may be multiple entries; advise only once */
334+
advice_add_embedded_repo = 0;
335+
}
336+
337+
strbuf_release(&name);
338+
}
339+
298340
static int add_files(struct dir_struct *dir, int flags)
299341
{
300342
int i, exit_status = 0;
@@ -307,12 +349,14 @@ static int add_files(struct dir_struct *dir, int flags)
307349
exit_status = 1;
308350
}
309351

310-
for (i = 0; i < dir->nr; i++)
352+
for (i = 0; i < dir->nr; i++) {
353+
check_embedded_repo(dir->entries[i]->name);
311354
if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
312355
if (!ignore_add_errors)
313356
die(_("adding files failed"));
314357
exit_status = 1;
315358
}
359+
}
316360
return exit_status;
317361
}
318362

git-submodule.sh

+3-2
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ cmd_add()
213213
die "$(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")"
214214
fi
215215

216-
if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1
216+
if test -z "$force" &&
217+
! git add --dry-run --ignore-missing --no-warn-embedded-repo "$sm_path" > /dev/null 2>&1
217218
then
218219
eval_gettextln "The following path is ignored by one of your .gitignore files:
219220
\$sm_path
@@ -267,7 +268,7 @@ or you are unsure what this means choose another name with the '--name' option."
267268
fi
268269
git config submodule."$sm_name".url "$realrepo"
269270

270-
git add $force "$sm_path" ||
271+
git add --no-warn-embedded-repo $force "$sm_path" ||
271272
die "$(eval_gettext "Failed to add submodule '\$sm_path'")"
272273

273274
git config -f .gitmodules submodule."$sm_name".path "$sm_path" &&

t/t7414-submodule-mistakes.sh

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/sh
2+
3+
test_description='handling of common mistakes people may make with submodules'
4+
. ./test-lib.sh
5+
6+
test_expect_success 'create embedded repository' '
7+
git init embed &&
8+
test_commit -C embed one
9+
'
10+
11+
test_expect_success 'git-add on embedded repository warns' '
12+
test_when_finished "git rm --cached -f embed" &&
13+
git add embed 2>stderr &&
14+
test_i18ngrep warning stderr
15+
'
16+
17+
test_expect_success '--no-warn-embedded-repo suppresses warning' '
18+
test_when_finished "git rm --cached -f embed" &&
19+
git add --no-warn-embedded-repo embed 2>stderr &&
20+
test_i18ngrep ! warning stderr
21+
'
22+
23+
test_expect_success 'no warning when updating entry' '
24+
test_when_finished "git rm --cached -f embed" &&
25+
git add embed &&
26+
git -C embed commit --allow-empty -m two &&
27+
git add embed 2>stderr &&
28+
test_i18ngrep ! warning stderr
29+
'
30+
31+
test_expect_success 'submodule add does not warn' '
32+
test_when_finished "git rm -rf submodule .gitmodules" &&
33+
git submodule add ./embed submodule 2>stderr &&
34+
test_i18ngrep ! warning stderr
35+
'
36+
37+
test_done

0 commit comments

Comments
 (0)