Skip to content

Commit da62f78

Browse files
stefanbellergitster
authored andcommitted
submodule: fixup nested submodules after moving the submodule
connect_work_tree_and_git_dir is used to connect a submodule worktree with its git directory and vice versa after events that require a reconnection such as moving around the working tree. As submodules can have nested submodules themselves, we'd also want to fix the nested submodules when asked to. Add an option to recurse into the nested submodules and connect them as well. As submodules are identified by their name (which determines their git directory in relation to their superproject's git directory) internally and by their path in the working tree of the superproject, we need to make sure that the mapping of name <-> path is kept intact. We can do that in the git-mv command by writing out the gitmodules file first and then forcing a reload of the submodule config machinery. Signed-off-by: Stefan Beller <[email protected]> Reviewed-by: Jonathan Tan <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0c89fdd commit da62f78

File tree

8 files changed

+83
-15
lines changed

8 files changed

+83
-15
lines changed

builtin/mv.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
275275
die_errno(_("renaming '%s' failed"), src);
276276
}
277277
if (submodule_gitfile[i]) {
278-
if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR)
279-
connect_work_tree_and_git_dir(dst, submodule_gitfile[i]);
280278
if (!update_path_in_gitmodules(src, dst))
281279
gitmodules_modified = 1;
280+
if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR)
281+
connect_work_tree_and_git_dir(dst,
282+
submodule_gitfile[i],
283+
1);
282284
}
283285

284286
if (mode == WORKING_DIRECTORY)

builtin/submodule--helper.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,8 +1260,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
12601260
strbuf_reset(&sb);
12611261
}
12621262

1263-
/* Connect module worktree and git dir */
1264-
connect_work_tree_and_git_dir(path, sm_gitdir);
1263+
connect_work_tree_and_git_dir(path, sm_gitdir, 0);
12651264

12661265
p = git_pathdup_submodule(path, "config");
12671266
if (!p)

dir.c

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "varint.h"
2020
#include "ewah/ewok.h"
2121
#include "fsmonitor.h"
22+
#include "submodule-config.h"
2223

2324
/*
2425
* Tells read_directory_recursive how a file or directory should be treated.
@@ -2988,8 +2989,57 @@ void untracked_cache_add_to_index(struct index_state *istate,
29882989
untracked_cache_invalidate_path(istate, path);
29892990
}
29902991

2991-
/* Update gitfile and core.worktree setting to connect work tree and git dir */
2992-
void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_)
2992+
static void connect_wt_gitdir_in_nested(const char *sub_worktree,
2993+
const char *sub_gitdir)
2994+
{
2995+
int i;
2996+
struct repository subrepo;
2997+
struct strbuf sub_wt = STRBUF_INIT;
2998+
struct strbuf sub_gd = STRBUF_INIT;
2999+
3000+
const struct submodule *sub;
3001+
3002+
/* If the submodule has no working tree, we can ignore it. */
3003+
if (repo_init(&subrepo, sub_gitdir, sub_worktree))
3004+
return;
3005+
3006+
if (repo_read_index(&subrepo) < 0)
3007+
die("index file corrupt in repo %s", subrepo.gitdir);
3008+
3009+
for (i = 0; i < subrepo.index->cache_nr; i++) {
3010+
const struct cache_entry *ce = subrepo.index->cache[i];
3011+
3012+
if (!S_ISGITLINK(ce->ce_mode))
3013+
continue;
3014+
3015+
while (i + 1 < subrepo.index->cache_nr &&
3016+
!strcmp(ce->name, subrepo.index->cache[i + 1]->name))
3017+
/*
3018+
* Skip entries with the same name in different stages
3019+
* to make sure an entry is returned only once.
3020+
*/
3021+
i++;
3022+
3023+
sub = submodule_from_path(&subrepo, &null_oid, ce->name);
3024+
if (!sub || !is_submodule_active(&subrepo, ce->name))
3025+
/* .gitmodules broken or inactive sub */
3026+
continue;
3027+
3028+
strbuf_reset(&sub_wt);
3029+
strbuf_reset(&sub_gd);
3030+
strbuf_addf(&sub_wt, "%s/%s", sub_worktree, sub->path);
3031+
strbuf_addf(&sub_gd, "%s/modules/%s", sub_gitdir, sub->name);
3032+
3033+
connect_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf, 1);
3034+
}
3035+
strbuf_release(&sub_wt);
3036+
strbuf_release(&sub_gd);
3037+
repo_clear(&subrepo);
3038+
}
3039+
3040+
void connect_work_tree_and_git_dir(const char *work_tree_,
3041+
const char *git_dir_,
3042+
int recurse_into_nested)
29933043
{
29943044
struct strbuf gitfile_sb = STRBUF_INIT;
29953045
struct strbuf cfg_sb = STRBUF_INIT;
@@ -3019,6 +3069,10 @@ void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_)
30193069
strbuf_release(&gitfile_sb);
30203070
strbuf_release(&cfg_sb);
30213071
strbuf_release(&rel_path);
3072+
3073+
if (recurse_into_nested)
3074+
connect_wt_gitdir_in_nested(work_tree, git_dir);
3075+
30223076
free(work_tree);
30233077
free(git_dir);
30243078
}
@@ -3032,5 +3086,5 @@ void relocate_gitdir(const char *path, const char *old_git_dir, const char *new_
30323086
die_errno(_("could not migrate git directory from '%s' to '%s'"),
30333087
old_git_dir, new_git_dir);
30343088

3035-
connect_work_tree_and_git_dir(path, new_git_dir);
3089+
connect_work_tree_and_git_dir(path, new_git_dir, 0);
30363090
}

dir.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,17 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
359359
void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);
360360
void add_untracked_cache(struct index_state *istate);
361361
void remove_untracked_cache(struct index_state *istate);
362-
extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
362+
363+
/*
364+
* Connect a worktree to a git directory by creating (or overwriting) a
365+
* '.git' file containing the location of the git directory. In the git
366+
* directory set the core.worktree setting to indicate where the worktree is.
367+
* When `recurse_into_nested` is set, recurse into any nested submodules,
368+
* connecting them as well.
369+
*/
370+
extern void connect_work_tree_and_git_dir(const char *work_tree,
371+
const char *git_dir,
372+
int recurse_into_nested);
363373
extern void relocate_gitdir(const char *path,
364374
const char *old_git_dir,
365375
const char *new_git_dir);

repository.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,9 @@ static int read_and_verify_repository_format(struct repository_format *format,
135135
* Initialize 'repo' based on the provided 'gitdir'.
136136
* Return 0 upon success and a non-zero value upon failure.
137137
*/
138-
static int repo_init(struct repository *repo,
139-
const char *gitdir,
140-
const char *worktree)
138+
int repo_init(struct repository *repo,
139+
const char *gitdir,
140+
const char *worktree)
141141
{
142142
struct repository_format format;
143143
memset(repo, 0, sizeof(*repo));

repository.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ extern void repo_set_gitdir(struct repository *repo,
9797
extern void repo_set_worktree(struct repository *repo, const char *path);
9898
extern void repo_set_hash_algo(struct repository *repo, int algo);
9999
extern void initialize_the_repository(void);
100+
extern int repo_init(struct repository *r,
101+
const char *gitdir,
102+
const char *worktree);
100103
extern int repo_submodule_init(struct repository *submodule,
101104
struct repository *superproject,
102105
const char *path);

submodule.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,7 +1625,7 @@ int submodule_move_head(const char *path,
16251625
} else {
16261626
char *gitdir = xstrfmt("%s/modules/%s",
16271627
get_git_common_dir(), sub->name);
1628-
connect_work_tree_and_git_dir(path, gitdir);
1628+
connect_work_tree_and_git_dir(path, gitdir, 0);
16291629
free(gitdir);
16301630

16311631
/* make sure the index is clean as well */
@@ -1635,7 +1635,7 @@ int submodule_move_head(const char *path,
16351635
if (old && (flags & SUBMODULE_MOVE_HEAD_FORCE)) {
16361636
char *gitdir = xstrfmt("%s/modules/%s",
16371637
get_git_common_dir(), sub->name);
1638-
connect_work_tree_and_git_dir(path, gitdir);
1638+
connect_work_tree_and_git_dir(path, gitdir, 1);
16391639
free(gitdir);
16401640
}
16411641
}
@@ -1948,7 +1948,7 @@ void absorb_git_dir_into_superproject(const char *prefix,
19481948
if (!sub)
19491949
die(_("could not lookup name for submodule '%s'"), path);
19501950
connect_work_tree_and_git_dir(path,
1951-
git_path("modules/%s", sub->name));
1951+
git_path("modules/%s", sub->name), 0);
19521952
} else {
19531953
/* Is it already absorbed into the superprojects git dir? */
19541954
char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);

t/t7001-mv.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ test_expect_success 'moving a submodule in nested directories' '
491491
test_cmp expect actual
492492
'
493493

494-
test_expect_failure 'moving nested submodules' '
494+
test_expect_success 'moving nested submodules' '
495495
git commit -am "cleanup commit" &&
496496
mkdir sub_nested_nested &&
497497
(cd sub_nested_nested &&

0 commit comments

Comments
 (0)