diff --git a/Documentation/config.txt b/Documentation/config.txt index 1766ed1320..d11f4c7a42 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1787,6 +1787,12 @@ submodule..update:: URL and other values found in the `.gitmodules` file. See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details. +submodule..fetch:: + A boolean to enable/disable recursive fetching of this submodule. It can + be overriden by using the --[no-]recursive command line option to "git + fetch" and "git pull". This setting overrides any setting made in + .gitmodules for this submodule. + submodule..ignore:: Defines under what circumstances "git status" and the diff family show a submodule as modified. When set to "all", it will never be considered diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 470ac31396..aff4dafd83 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -64,6 +64,12 @@ endif::git-pull[] specified with the remote..tagopt setting. See linkgit:git-config[1]. +--[no-]recursive:: + By default new commits of all populated submodules will be fetched + too. This option can be used to disable/enable recursive fetching of + submodules regardless of the 'fetch' configuration setting (see + linkgit:git-config[1] or linkgit:gitmodules[5]). + -u:: --update-head-ok:: By default 'git fetch' refuses to update the head which diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index bcffd95ada..febfef46a5 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -44,6 +44,14 @@ submodule..update:: This config option is overridden if 'git submodule update' is given the '--merge' or '--rebase' options. +submodule..fetch:: + A boolean to enable/disable recursive fetching of this submodule. + If this option is also present in the submodules entry in .git/config of + the superproject, the setting there will override the one found in + .gitmodules. + Both settings can be overriden on the command line by using the + "--[no-]recursive" option to "git fetch" and "git pull".. + submodule..ignore:: Defines under what circumstances "git status" and the diff family show a submodule as modified. When set to "all", it will never be considered diff --git a/builtin/fetch.c b/builtin/fetch.c index 6fc5047703..757478a7f8 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -12,6 +12,7 @@ #include "parse-options.h" #include "sigchain.h" #include "transport.h" +#include "submodule.h" static const char * const builtin_fetch_usage[] = { "git fetch [] [ [...]]", @@ -27,7 +28,13 @@ enum { TAGS_SET = 2 }; -static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; +enum { + RECURSIVE_UNSET = 0, + RECURSIVE_DEFAULT = 1, + RECURSIVE_SET = 2 +}; + +static int all, append, dry_run, force, keep, multiple, prune, recursive = RECURSIVE_DEFAULT, update_head_ok, verbosity; static int progress; static int tags = TAGS_DEFAULT; static const char *depth; @@ -53,6 +60,8 @@ static struct option builtin_fetch_options[] = { "do not fetch all tags (--no-tags)", TAGS_UNSET), OPT_BOOLEAN('p', "prune", &prune, "prune tracking branches no longer on remote"), + OPT_SET_INT(0, "recursive", &recursive, + "control recursive fetching of submodules", RECURSIVE_SET), OPT_BOOLEAN(0, "dry-run", &dry_run, "dry run"), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), @@ -926,6 +935,12 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } } + if (!result && recursive) { + gitmodules_config(); + git_config(submodule_config, NULL); + result = fetch_populated_submodules(recursive == RECURSIVE_SET); + } + /* All names were strdup()ed or strndup()ed */ list.strdup_strings = 1; string_list_clear(&list, 0); diff --git a/git-pull.sh b/git-pull.sh index 8eb74d45de..4bc8a60c64 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -38,7 +38,7 @@ test -z "$(git ls-files -u)" || die_conflict test -f "$GIT_DIR/MERGE_HEAD" && die_merge strategy_args= diffstat= no_commit= squash= no_ff= ff_only= -log_arg= verbosity= progress= +log_arg= verbosity= progress= recursive= merge_args= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short="${curr_branch#refs/heads/}" @@ -105,6 +105,12 @@ do --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase) rebase=false ;; + --recursive) + recursive=--recursive + ;; + --no-recursive) + recursive=--no-recursive + ;; --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run) dry_run=--dry-run ;; @@ -220,7 +226,7 @@ test true = "$rebase" && { done } orig_head=$(git rev-parse -q --verify HEAD) -git fetch $verbosity $progress $dry_run --update-head-ok "$@" || exit 1 +git fetch $verbosity $progress $dry_run $recursive --update-head-ok "$@" || exit 1 test -z "$dry_run" || exit 0 curr_head=$(git rev-parse -q --verify HEAD) diff --git a/submodule.c b/submodule.c index 91a4758747..23806383fd 100644 --- a/submodule.c +++ b/submodule.c @@ -10,6 +10,7 @@ #include "string-list.h" struct string_list config_name_for_path; +struct string_list config_fetch_for_name; struct string_list config_ignore_for_name; static int add_submodule_odb(const char *path) @@ -63,7 +64,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, } } -static int submodule_config(const char *var, const char *value, void *cb) +int submodule_config(const char *var, const char *value, void *cb) { if (!prefixcmp(var, "submodule.")) return parse_submodule_config_option(var, value); @@ -100,6 +101,14 @@ int parse_submodule_config_option(const char *var, const char *value) config = string_list_append(&config_name_for_path, xstrdup(value)); config->util = strbuf_detach(&submodname, NULL); strbuf_release(&submodname); + } else if ((len > 5) && !strcmp(var + len - 6, ".fetch")) { + strbuf_add(&submodname, var, len - 6); + config = unsorted_string_list_lookup(&config_fetch_for_name, submodname.buf); + if (!config) + config = string_list_append(&config_fetch_for_name, + strbuf_detach(&submodname, NULL)); + config->util = git_config_bool(var, value) ? (void *)1 : NULL; + strbuf_release(&submodname); } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) { if (strcmp(value, "untracked") && strcmp(value, "dirty") && strcmp(value, "all") && strcmp(value, "none")) { @@ -229,6 +238,55 @@ void show_submodule_summary(FILE *f, const char *path, strbuf_release(&sb); } +int fetch_populated_submodules(int forced) +{ + int result = 0; + struct child_process cp; + const char *argv[] = { + "fetch", + NULL, + }; + struct string_list_item *name_for_path; + const char *work_tree = get_git_work_tree(); + if (!work_tree) + return 0; + + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = local_repo_env; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.out = -1; + + for_each_string_list_item(name_for_path, &config_name_for_path) { + struct strbuf submodule_path = STRBUF_INIT; + struct strbuf submodule_git_dir = STRBUF_INIT; + const char *git_dir; + + if (!forced) { + struct string_list_item *fetch_option; + fetch_option = unsorted_string_list_lookup(&config_fetch_for_name, name_for_path->util); + if (fetch_option && !fetch_option->util) + continue; + } + + strbuf_addf(&submodule_path, "%s/%s", work_tree, name_for_path->string); + strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf); + git_dir = read_gitfile_gently(submodule_git_dir.buf); + if (!git_dir) + git_dir = submodule_git_dir.buf; + if (is_directory(git_dir)) { + printf("Fetching submodule %s\n", name_for_path->string); + cp.dir = submodule_path.buf; + if (run_command(&cp)) + result = 1; + } + strbuf_release(&submodule_path); + strbuf_release(&submodule_git_dir); + } + return result; +} + unsigned is_submodule_modified(const char *path, int ignore_untracked) { ssize_t len; diff --git a/submodule.h b/submodule.h index 386f410a66..9e6257edf1 100644 --- a/submodule.h +++ b/submodule.h @@ -5,6 +5,7 @@ struct diff_options; void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); +int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(); int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); @@ -12,6 +13,7 @@ void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *del, const char *add, const char *reset); +int fetch_populated_submodules(int forced); unsigned is_submodule_modified(const char *path, int ignore_untracked); int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], const unsigned char a[20], const unsigned char b[20]); diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh new file mode 100755 index 0000000000..489ef1a933 --- /dev/null +++ b/t/t5526-fetch-submodules.sh @@ -0,0 +1,104 @@ +#!/bin/sh +# Copyright (c) 2010, Jens Lehmann + +test_description='Recursive "git fetch" for submodules' + +. ./test-lib.sh + +pwd=$(pwd) + +add_upstream_commit() { + ( + cd submodule && + head1=$(git rev-parse --short HEAD) && + echo new >> subfile && + test_tick && + git add subfile && + git commit -m new subfile && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/submodule" > ../expect.err + echo " $head1..$head2 master -> origin/master" >> ../expect.err + ) +} + +test_expect_success setup ' + mkdir submodule && + ( + cd submodule && + git init && + echo subcontent > subfile && + git add subfile && + git commit -m new subfile + ) && + git submodule add "$pwd/submodule" submodule && + git commit -am initial && + git clone . downstream && + ( + cd downstream && + git submodule init && + git submodule update + ) && + echo "Fetching submodule submodule" > expect.out +' + +test_expect_success "fetch recurses into submodules" ' + add_upstream_commit && + ( + cd downstream && + git fetch >../actual.out 2>../actual.err + ) && + test_cmp expect.out actual.out && + test_cmp expect.err actual.err +' + +test_expect_success "fetch --no-recursive only fetches superproject" ' + ( + cd downstream && + git fetch --no-recursive >../actual.out 2>../actual.err + ) && + ! test -s actual.out && + ! test -s actual.err +' + +test_expect_success "using fetch=false in .gitmodules only fetches superproject" ' + ( + cd downstream && + git config -f .gitmodules submodule.submodule.fetch false && + git fetch >../actual.out 2>../actual.err + ) && + ! test -s actual.out && + ! test -s actual.err +' + +test_expect_success "--recursive overrides .gitmodules config" ' + add_upstream_commit && + ( + cd downstream && + git fetch --recursive >../actual.out 2>../actual.err + ) && + test_cmp expect.out actual.out && + test_cmp expect.err actual.err +' + +test_expect_success "using fetch=true in .git/config overrides setting in .gitmodules" ' + add_upstream_commit && + ( + cd downstream && + git config submodule.submodule.fetch true && + git fetch >../actual.out 2>../actual.err + ) && + test_cmp expect.out actual.out && + test_cmp expect.err actual.err +' + +test_expect_success "--no-recursive overrides fetch setting from .git/config" ' + add_upstream_commit && + ( + cd downstream && + git fetch --no-recursive >../actual.out 2>../actual.err + ) && + ! test -s actual.out && + ! test -s actual.err +' + +test_done diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index 02522f9627..6f3e966b05 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -50,7 +50,7 @@ test_expect_success 'change submodule url' ' test_expect_success '"git submodule sync" should update submodule URLs' ' (cd super-clone && - git pull && + git pull --no-recursive && git submodule sync ) && test -d "$(git config -f super-clone/submodule/.git/config \