@matheusbsbs / matheus-bagliano / commits / 7dce19d374
Fork 0

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 <Jens.Lehmann@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jens Lehmann committed Nov 12, 2010 at 13:54 UTC 7dce19d374a37932e9d4c3a6202af407cf5114eb
6 files changed +231 -17
Documentation/fetch-options.txt
+10
@@ -60,6 +60,16 @@ endif::git-pull[]
60 flag lets all tags and their associated objects be
61 downloaded.
62
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 +
73 -u::
74 --update-head-ok::
75 By default 'git fetch' refuses to update the head which
builtin/fetch.c
+39 -14
@@ -12,6 +12,7 @@
12 #include "parse-options.h"
13 #include "sigchain.h"
14 #include "transport.h"
15 +#include "submodule.h"
16
17 static const char * const builtin_fetch_usage[] = {
18 "git fetch [<options>] [<repository> [<refspec>...]]",
@@ -28,12 +29,13 @@ enum {
29 };
30
31 static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
31 -static int progress;
32 +static int progress, recurse_submodules;
33 static int tags = TAGS_DEFAULT;
34 static const char *depth;
35 static const char *upload_pack;
36 static struct strbuf default_rla = STRBUF_INIT;
37 static struct transport *transport;
38 +static const char *submodule_prefix = "";
39
40 static struct option builtin_fetch_options[] = {
41 OPT__VERBOSITY(&verbosity),
@@ -53,6 +55,8 @@ static struct option builtin_fetch_options[] = {
55 "do not fetch all tags (--no-tags)", TAGS_UNSET),
56 OPT_BOOLEAN('p', "prune", &prune,
57 "prune tracking branches no longer on remote"),
58 + OPT_BOOLEAN(0, "recurse-submodules", &recurse_submodules,
59 + "control recursive fetching of submodules"),
60 OPT_BOOLEAN(0, "dry-run", &dry_run,
61 "dry run"),
62 OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
@@ -61,6 +65,8 @@ static struct option builtin_fetch_options[] = {
65 OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
66 OPT_STRING(0, "depth", &depth, "DEPTH",
67 "deepen history of shallow clone"),
68 + { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "DIR",
69 + "prepend this to submodule path output", PARSE_OPT_HIDDEN },
70 OPT_END()
71 };
72
@@ -777,28 +783,36 @@ static int add_remote_or_group(const char *name, struct string_list *list)
783 return 1;
784 }
785
780 -static int fetch_multiple(struct string_list *list)
786 +static void add_options_to_argv(int *argc, const char **argv)
787 {
782 - int i, result = 0;
783 - const char *argv[11] = { "fetch", "--append" };
784 - int argc = 2;
785 -
788 if (dry_run)
787 - argv[argc++] = "--dry-run";
789 + argv[(*argc)++] = "--dry-run";
790 if (prune)
789 - argv[argc++] = "--prune";
791 + argv[(*argc)++] = "--prune";
792 if (update_head_ok)
791 - argv[argc++] = "--update-head-ok";
793 + argv[(*argc)++] = "--update-head-ok";
794 if (force)
793 - argv[argc++] = "--force";
795 + argv[(*argc)++] = "--force";
796 if (keep)
795 - argv[argc++] = "--keep";
797 + argv[(*argc)++] = "--keep";
798 + if (recurse_submodules)
799 + argv[(*argc)++] = "--recurse-submodules";
800 if (verbosity >= 2)
797 - argv[argc++] = "-v";
801 + argv[(*argc)++] = "-v";
802 if (verbosity >= 1)
799 - argv[argc++] = "-v";
803 + argv[(*argc)++] = "-v";
804 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);
816
817 if (!append && !dry_run) {
818 int errcode = truncate_fetch_head();
@@ -919,6 +933,17 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
933 }
934 }
935
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 +
947 /* All names were strdup()ed or strndup()ed */
948 list.strdup_strings = 1;
949 string_list_clear(&list, 0);
git-pull.sh
+5 -2
@@ -38,7 +38,7 @@ test -z "$(git ls-files -u)" || die_conflict
38 test -f "$GIT_DIR/MERGE_HEAD" && die_merge
39
40 strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
41 -log_arg= verbosity= progress=
41 +log_arg= verbosity= progress= recurse_submodules=
42 merge_args=
43 curr_branch=$(git symbolic-ref -q HEAD)
44 curr_branch_short="${curr_branch#refs/heads/}"
@@ -105,6 +105,9 @@ do
105 --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
106 rebase=false
107 ;;
108 + --recurse-submodules)
109 + recurse_submodules=--recurse-submodules
110 + ;;
111 --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
112 dry_run=--dry-run
113 ;;
@@ -220,7 +223,7 @@ test true = "$rebase" && {
223 done
224 }
225 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
227 test -z "$dry_run" || exit 0
228
229 curr_head=$(git rev-parse -q --verify HEAD)
submodule.c
+65 -1
@@ -63,7 +63,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
63 }
64 }
65
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)
67 {
68 if (!prefixcmp(var, "submodule."))
69 return parse_submodule_config_option(var, value);
@@ -229,6 +229,70 @@ void show_submodule_summary(FILE *f, const char *path,
229 strbuf_release(&sb);
230 }
231
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 +
296 unsigned is_submodule_modified(const char *path, int ignore_untracked)
297 {
298 ssize_t len;
submodule.h
+3
@@ -5,6 +5,7 @@ struct diff_options;
5
6 void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
7 const char *path);
8 +int submodule_config(const char *var, const char *value, void *cb);
9 void gitmodules_config();
10 int parse_submodule_config_option(const char *var, const char *value);
11 void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
@@ -12,6 +13,8 @@ void show_submodule_summary(FILE *f, const char *path,
13 unsigned char one[20], unsigned char two[20],
14 unsigned dirty_submodule,
15 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);
18 unsigned is_submodule_modified(const char *path, int ignore_untracked);
19 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
20 const unsigned char a[20], const unsigned char b[20]);
t/t5526-fetch-submodules.sh new
+109
@@ -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