Skip to content

Commit 7dce19d

Browse files
jlehmanngitster
authored andcommitted
fetch/pull: Add the --recurse-submodules option
Until now you had to call "git submodule update" (without -N|--no-fetch option) or something like "git submodule foreach git fetch" to fetch new commits in populated submodules from their remote. This could lead to "(commits not present)" messages in the output of "git diff --submodule" (which is used by "git gui" and "gitk") after fetching or pulling new commits in the superproject and is an obstacle for implementing recursive checkout of submodules. Also "git submodule update" cannot fetch changes when disconnected, so it was very easy to forget to fetch the submodule changes before disconnecting only to discover later that they are needed. This patch adds the "--recurse-submodules" option to recursively fetch each populated submodule from the url configured in the .git/config of the submodule at the end of each "git fetch" or during "git pull" in the superproject. The submodule paths are taken from the index. The hidden option "--submodule-prefix" is added to "git fetch" to be able to print out the full paths of nested submodules. Signed-off-by: Jens Lehmann <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 515cc01 commit 7dce19d

File tree

6 files changed

+231
-17
lines changed

6 files changed

+231
-17
lines changed

Documentation/fetch-options.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ endif::git-pull[]
6060
flag lets all tags and their associated objects be
6161
downloaded.
6262

63+
--recurse-submodules::
64+
Use this option to fetch new commits of all populated submodules too.
65+
66+
ifndef::git-pull[]
67+
--submodule-prefix=<path>::
68+
Prepend <path> to paths printed in informative messages
69+
such as "Fetching submodule foo". This option is used
70+
internally when recursing over submodules.
71+
endif::git-pull[]
72+
6373
-u::
6474
--update-head-ok::
6575
By default 'git fetch' refuses to update the head which

builtin/fetch.c

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "parse-options.h"
1313
#include "sigchain.h"
1414
#include "transport.h"
15+
#include "submodule.h"
1516

1617
static const char * const builtin_fetch_usage[] = {
1718
"git fetch [<options>] [<repository> [<refspec>...]]",
@@ -28,12 +29,13 @@ enum {
2829
};
2930

3031
static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
31-
static int progress;
32+
static int progress, recurse_submodules;
3233
static int tags = TAGS_DEFAULT;
3334
static const char *depth;
3435
static const char *upload_pack;
3536
static struct strbuf default_rla = STRBUF_INIT;
3637
static struct transport *transport;
38+
static const char *submodule_prefix = "";
3739

3840
static struct option builtin_fetch_options[] = {
3941
OPT__VERBOSITY(&verbosity),
@@ -53,6 +55,8 @@ static struct option builtin_fetch_options[] = {
5355
"do not fetch all tags (--no-tags)", TAGS_UNSET),
5456
OPT_BOOLEAN('p', "prune", &prune,
5557
"prune tracking branches no longer on remote"),
58+
OPT_BOOLEAN(0, "recurse-submodules", &recurse_submodules,
59+
"control recursive fetching of submodules"),
5660
OPT_BOOLEAN(0, "dry-run", &dry_run,
5761
"dry run"),
5862
OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
@@ -61,6 +65,8 @@ static struct option builtin_fetch_options[] = {
6165
OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
6266
OPT_STRING(0, "depth", &depth, "DEPTH",
6367
"deepen history of shallow clone"),
68+
{ OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "DIR",
69+
"prepend this to submodule path output", PARSE_OPT_HIDDEN },
6470
OPT_END()
6571
};
6672

@@ -777,28 +783,36 @@ static int add_remote_or_group(const char *name, struct string_list *list)
777783
return 1;
778784
}
779785

780-
static int fetch_multiple(struct string_list *list)
786+
static void add_options_to_argv(int *argc, const char **argv)
781787
{
782-
int i, result = 0;
783-
const char *argv[11] = { "fetch", "--append" };
784-
int argc = 2;
785-
786788
if (dry_run)
787-
argv[argc++] = "--dry-run";
789+
argv[(*argc)++] = "--dry-run";
788790
if (prune)
789-
argv[argc++] = "--prune";
791+
argv[(*argc)++] = "--prune";
790792
if (update_head_ok)
791-
argv[argc++] = "--update-head-ok";
793+
argv[(*argc)++] = "--update-head-ok";
792794
if (force)
793-
argv[argc++] = "--force";
795+
argv[(*argc)++] = "--force";
794796
if (keep)
795-
argv[argc++] = "--keep";
797+
argv[(*argc)++] = "--keep";
798+
if (recurse_submodules)
799+
argv[(*argc)++] = "--recurse-submodules";
796800
if (verbosity >= 2)
797-
argv[argc++] = "-v";
801+
argv[(*argc)++] = "-v";
798802
if (verbosity >= 1)
799-
argv[argc++] = "-v";
803+
argv[(*argc)++] = "-v";
800804
else if (verbosity < 0)
801-
argv[argc++] = "-q";
805+
argv[(*argc)++] = "-q";
806+
807+
}
808+
809+
static int fetch_multiple(struct string_list *list)
810+
{
811+
int i, result = 0;
812+
const char *argv[12] = { "fetch", "--append" };
813+
int argc = 2;
814+
815+
add_options_to_argv(&argc, argv);
802816

803817
if (!append && !dry_run) {
804818
int errcode = truncate_fetch_head();
@@ -919,6 +933,17 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
919933
}
920934
}
921935

936+
if (!result && recurse_submodules) {
937+
const char *options[10];
938+
int num_options = 0;
939+
gitmodules_config();
940+
git_config(submodule_config, NULL);
941+
add_options_to_argv(&num_options, options);
942+
result = fetch_populated_submodules(num_options, options,
943+
submodule_prefix,
944+
verbosity < 0);
945+
}
946+
922947
/* All names were strdup()ed or strndup()ed */
923948
list.strdup_strings = 1;
924949
string_list_clear(&list, 0);

git-pull.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ test -z "$(git ls-files -u)" || die_conflict
3838
test -f "$GIT_DIR/MERGE_HEAD" && die_merge
3939

4040
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
41-
log_arg= verbosity= progress=
41+
log_arg= verbosity= progress= recurse_submodules=
4242
merge_args=
4343
curr_branch=$(git symbolic-ref -q HEAD)
4444
curr_branch_short="${curr_branch#refs/heads/}"
@@ -105,6 +105,9 @@ do
105105
--no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
106106
rebase=false
107107
;;
108+
--recurse-submodules)
109+
recurse_submodules=--recurse-submodules
110+
;;
108111
--d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
109112
dry_run=--dry-run
110113
;;
@@ -220,7 +223,7 @@ test true = "$rebase" && {
220223
done
221224
}
222225
orig_head=$(git rev-parse -q --verify HEAD)
223-
git fetch $verbosity $progress $dry_run --update-head-ok "$@" || exit 1
226+
git fetch $verbosity $progress $dry_run $recurse_submodules --update-head-ok "$@" || exit 1
224227
test -z "$dry_run" || exit 0
225228

226229
curr_head=$(git rev-parse -q --verify HEAD)

submodule.c

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
6363
}
6464
}
6565

66-
static int submodule_config(const char *var, const char *value, void *cb)
66+
int submodule_config(const char *var, const char *value, void *cb)
6767
{
6868
if (!prefixcmp(var, "submodule."))
6969
return parse_submodule_config_option(var, value);
@@ -229,6 +229,70 @@ void show_submodule_summary(FILE *f, const char *path,
229229
strbuf_release(&sb);
230230
}
231231

232+
int fetch_populated_submodules(int num_options, const char **options,
233+
const char *prefix, int quiet)
234+
{
235+
int i, result = 0, argc = 0;
236+
struct child_process cp;
237+
const char **argv;
238+
struct string_list_item *name_for_path;
239+
const char *work_tree = get_git_work_tree();
240+
if (!work_tree)
241+
return 0;
242+
243+
if (!the_index.initialized)
244+
if (read_cache() < 0)
245+
die("index file corrupt");
246+
247+
argv = xcalloc(num_options + 5, sizeof(const char *));
248+
argv[argc++] = "fetch";
249+
for (i = 0; i < num_options; i++)
250+
argv[argc++] = options[i];
251+
argv[argc++] = "--submodule-prefix";
252+
253+
memset(&cp, 0, sizeof(cp));
254+
cp.argv = argv;
255+
cp.env = local_repo_env;
256+
cp.git_cmd = 1;
257+
cp.no_stdin = 1;
258+
259+
for (i = 0; i < active_nr; i++) {
260+
struct strbuf submodule_path = STRBUF_INIT;
261+
struct strbuf submodule_git_dir = STRBUF_INIT;
262+
struct strbuf submodule_prefix = STRBUF_INIT;
263+
struct cache_entry *ce = active_cache[i];
264+
const char *git_dir, *name;
265+
266+
if (!S_ISGITLINK(ce->ce_mode))
267+
continue;
268+
269+
name = ce->name;
270+
name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name);
271+
if (name_for_path)
272+
name = name_for_path->util;
273+
274+
strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
275+
strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
276+
strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
277+
git_dir = read_gitfile_gently(submodule_git_dir.buf);
278+
if (!git_dir)
279+
git_dir = submodule_git_dir.buf;
280+
if (is_directory(git_dir)) {
281+
if (!quiet)
282+
printf("Fetching submodule %s%s\n", prefix, ce->name);
283+
cp.dir = submodule_path.buf;
284+
argv[argc] = submodule_prefix.buf;
285+
if (run_command(&cp))
286+
result = 1;
287+
}
288+
strbuf_release(&submodule_path);
289+
strbuf_release(&submodule_git_dir);
290+
strbuf_release(&submodule_prefix);
291+
}
292+
free(argv);
293+
return result;
294+
}
295+
232296
unsigned is_submodule_modified(const char *path, int ignore_untracked)
233297
{
234298
ssize_t len;

submodule.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ struct diff_options;
55

66
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
77
const char *path);
8+
int submodule_config(const char *var, const char *value, void *cb);
89
void gitmodules_config();
910
int parse_submodule_config_option(const char *var, const char *value);
1011
void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
1112
void show_submodule_summary(FILE *f, const char *path,
1213
unsigned char one[20], unsigned char two[20],
1314
unsigned dirty_submodule,
1415
const char *del, const char *add, const char *reset);
16+
int fetch_populated_submodules(int num_options, const char **options,
17+
const char *prefix, int quiet);
1518
unsigned is_submodule_modified(const char *path, int ignore_untracked);
1619
int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
1720
const unsigned char a[20], const unsigned char b[20]);

t/t5526-fetch-submodules.sh

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/bin/sh
2+
# Copyright (c) 2010, Jens Lehmann
3+
4+
test_description='Recursive "git fetch" for submodules'
5+
6+
. ./test-lib.sh
7+
8+
pwd=$(pwd)
9+
10+
add_upstream_commit() {
11+
(
12+
cd submodule &&
13+
head1=$(git rev-parse --short HEAD) &&
14+
echo new >> subfile &&
15+
test_tick &&
16+
git add subfile &&
17+
git commit -m new subfile &&
18+
head2=$(git rev-parse --short HEAD) &&
19+
echo "From $pwd/submodule" > ../expect.err &&
20+
echo " $head1..$head2 master -> origin/master" >> ../expect.err
21+
) &&
22+
(
23+
cd deepsubmodule &&
24+
head1=$(git rev-parse --short HEAD) &&
25+
echo new >> deepsubfile &&
26+
test_tick &&
27+
git add deepsubfile &&
28+
git commit -m new deepsubfile &&
29+
head2=$(git rev-parse --short HEAD) &&
30+
echo "From $pwd/deepsubmodule" >> ../expect.err &&
31+
echo " $head1..$head2 master -> origin/master" >> ../expect.err
32+
)
33+
}
34+
35+
test_expect_success setup '
36+
mkdir deepsubmodule &&
37+
(
38+
cd deepsubmodule &&
39+
git init &&
40+
echo deepsubcontent > deepsubfile &&
41+
git add deepsubfile &&
42+
git commit -m new deepsubfile
43+
) &&
44+
mkdir submodule &&
45+
(
46+
cd submodule &&
47+
git init &&
48+
echo subcontent > subfile &&
49+
git add subfile &&
50+
git submodule add "$pwd/deepsubmodule" deepsubmodule &&
51+
git commit -a -m new
52+
) &&
53+
git submodule add "$pwd/submodule" submodule &&
54+
git commit -am initial &&
55+
git clone . downstream &&
56+
(
57+
cd downstream &&
58+
git submodule update --init --recursive
59+
) &&
60+
echo "Fetching submodule submodule" > expect.out &&
61+
echo "Fetching submodule submodule/deepsubmodule" >> expect.out
62+
'
63+
64+
test_expect_success "fetch --recurse-submodules recurses into submodules" '
65+
add_upstream_commit &&
66+
(
67+
cd downstream &&
68+
git fetch --recurse-submodules >../actual.out 2>../actual.err
69+
) &&
70+
test_cmp expect.out actual.out &&
71+
test_cmp expect.err actual.err
72+
'
73+
74+
test_expect_success "fetch alone only fetches superproject" '
75+
add_upstream_commit &&
76+
(
77+
cd downstream &&
78+
git fetch >../actual.out 2>../actual.err
79+
) &&
80+
! test -s actual.out &&
81+
! test -s actual.err
82+
'
83+
84+
test_expect_success "--quiet propagates to submodules" '
85+
(
86+
cd downstream &&
87+
git fetch --recurse-submodules --quiet >../actual.out 2>../actual.err
88+
) &&
89+
! test -s actual.out &&
90+
! test -s actual.err
91+
'
92+
93+
test_expect_success "--dry-run propagates to submodules" '
94+
add_upstream_commit &&
95+
(
96+
cd downstream &&
97+
git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err
98+
) &&
99+
test_cmp expect.out actual.out &&
100+
test_cmp expect.err actual.err &&
101+
(
102+
cd downstream &&
103+
git fetch --recurse-submodules >../actual.out 2>../actual.err
104+
) &&
105+
test_cmp expect.out actual.out &&
106+
test_cmp expect.err actual.err
107+
'
108+
109+
test_done

0 commit comments

Comments
 (0)