Merge branch 'jl/fetch-submodule-recursive'
authorJunio C Hamano <gitster@pobox.com>
Thu, 16 Dec 2010 20:57:15 +0000 (12:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 16 Dec 2010 20:57:15 +0000 (12:57 -0800)
* jl/fetch-submodule-recursive:
fetch_populated_submodules(): document dynamic allocation
Submodules: Add the "fetchRecurseSubmodules" config option
Add the 'fetch.recurseSubmodules' config setting
fetch/pull: Add the --recurse-submodules option

Conflicts:
builtin/fetch.c

Documentation/config.txt
Documentation/fetch-options.txt
Documentation/gitmodules.txt
builtin/fetch.c
git-pull.sh
submodule.c
submodule.h
t/t5526-fetch-submodules.sh [new file with mode: 0755]
index 488a27cd03b99362c5abbba8884c46e1704055ec..bf9479e4eb49c971b9502d072dd9be18c838ab21 100644 (file)
@@ -897,6 +897,11 @@ diff.wordRegex::
        sequences that match the regular expression are "words", all other
        characters are *ignorable* whitespace.
 
+fetch.recurseSubmodules::
+       A boolean value which changes the behavior for fetch and pull, the
+       default is to not recursively fetch populated sumodules unless
+       configured otherwise.
+
 fetch.unpackLimit::
        If the number of objects fetched over the git native
        transfer is below this
@@ -1811,6 +1816,13 @@ submodule.<name>.update::
        URL and other values found in the `.gitmodules` file.  See
        linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
 
+submodule.<name>.fetchRecurseSubmodules::
+       This option can be used to enable/disable recursive fetching of this
+       submodule. It can be overriden by using the --[no-]recurse-submodules
+       command line option to "git fetch" and "git pull".
+       This setting will override that from in the linkgit:gitmodules[5]
+       file.
+
 submodule.<name>.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
index 678675ccdf06dbe0e97d80bb3fbc8985155d2fc0..695696da1b6e78639014388e1d4ac8bf09a0e12d 100644 (file)
@@ -66,6 +66,17 @@ ifndef::git-pull[]
        linkgit:git-config[1].
 endif::git-pull[]
 
+--[no-]recurse-submodules::
+       This option controls if new commits of all populated submodules should
+       be fetched too (see linkgit:git-config[1] and linkgit:gitmodules[5]).
+
+ifndef::git-pull[]
+--submodule-prefix=<path>::
+       Prepend <path> to paths printed in informative messages
+       such as "Fetching submodule foo".  This option is used
+       internally when recursing over submodules.
+endif::git-pull[]
+
 -u::
 --update-head-ok::
        By default 'git fetch' refuses to update the head which
index bcffd95ada574a8fb3c6e62e539e736a17170414..6c93202e735e7b7caad89120245050e0a06e5537 100644 (file)
@@ -44,6 +44,14 @@ submodule.<name>.update::
        This config option is overridden if 'git submodule update' is given
        the '--merge' or '--rebase' options.
 
+submodule.<name>.fetchRecurseSubmodules::
+       This option can be used 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-]recurse-submodules" option to "git fetch" and "git pull"..
+
 submodule.<name>.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
index 6bcce55c0cdb6686e0585bf11107559ba61a5700..357f3cdbbfd601e2ce3f1261a4d6b30c1257cda4 100644 (file)
@@ -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 [<options>] [<repository> [<refspec>...]]",
@@ -27,13 +28,20 @@ enum {
        TAGS_SET = 2
 };
 
+enum {
+       RECURSE_SUBMODULES_OFF = 0,
+       RECURSE_SUBMODULES_DEFAULT = 1,
+       RECURSE_SUBMODULES_ON = 2
+};
+
 static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int progress;
+static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int tags = TAGS_DEFAULT;
 static const char *depth;
 static const char *upload_pack;
 static struct strbuf default_rla = STRBUF_INIT;
 static struct transport *transport;
+static const char *submodule_prefix = "";
 
 static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
@@ -52,6 +60,9 @@ static struct option builtin_fetch_options[] = {
                    "do not fetch all tags (--no-tags)", TAGS_UNSET),
        OPT_BOOLEAN('p', "prune", &prune,
                    "prune remote-tracking branches no longer on remote"),
+       OPT_SET_INT(0, "recurse-submodules", &recurse_submodules,
+                   "control recursive fetching of submodules",
+                   RECURSE_SUBMODULES_ON),
        OPT_BOOLEAN(0, "dry-run", &dry_run,
                    "dry run"),
        OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
@@ -60,6 +71,8 @@ static struct option builtin_fetch_options[] = {
        OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
        OPT_STRING(0, "depth", &depth, "DEPTH",
                   "deepen history of shallow clone"),
+       { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "DIR",
+                  "prepend this to submodule path output", PARSE_OPT_HIDDEN },
        OPT_END()
 };
 
@@ -783,28 +796,36 @@ static int add_remote_or_group(const char *name, struct string_list *list)
        return 1;
 }
 
-static int fetch_multiple(struct string_list *list)
+static void add_options_to_argv(int *argc, const char **argv)
 {
-       int i, result = 0;
-       const char *argv[11] = { "fetch", "--append" };
-       int argc = 2;
-
        if (dry_run)
-               argv[argc++] = "--dry-run";
+               argv[(*argc)++] = "--dry-run";
        if (prune)
-               argv[argc++] = "--prune";
+               argv[(*argc)++] = "--prune";
        if (update_head_ok)
-               argv[argc++] = "--update-head-ok";
+               argv[(*argc)++] = "--update-head-ok";
        if (force)
-               argv[argc++] = "--force";
+               argv[(*argc)++] = "--force";
        if (keep)
-               argv[argc++] = "--keep";
+               argv[(*argc)++] = "--keep";
+       if (recurse_submodules == RECURSE_SUBMODULES_ON)
+               argv[(*argc)++] = "--recurse-submodules";
        if (verbosity >= 2)
-               argv[argc++] = "-v";
+               argv[(*argc)++] = "-v";
        if (verbosity >= 1)
-               argv[argc++] = "-v";
+               argv[(*argc)++] = "-v";
        else if (verbosity < 0)
-               argv[argc++] = "-q";
+               argv[(*argc)++] = "-q";
+
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+       int i, result = 0;
+       const char *argv[12] = { "fetch", "--append" };
+       int argc = 2;
+
+       add_options_to_argv(&argc, argv);
 
        if (!append && !dry_run) {
                int errcode = truncate_fetch_head();
@@ -925,6 +946,21 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                }
        }
 
+       if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
+               const char *options[10];
+               int num_options = 0;
+               /* Set recursion as default when we already are recursing */
+               if (submodule_prefix[0])
+                       set_config_fetch_recurse_submodules(1);
+               gitmodules_config();
+               git_config(submodule_config, NULL);
+               add_options_to_argv(&num_options, options);
+               result = fetch_populated_submodules(num_options, options,
+                                                   submodule_prefix,
+                                                   recurse_submodules == RECURSE_SUBMODULES_ON,
+                                                   verbosity < 0);
+       }
+
        /* All names were strdup()ed or strndup()ed */
        list.strdup_strings = 1;
        string_list_clear(&list, 0);
index 20a3bbea07c259ec1f19353b2888fde20e8b67da..eb87f49062b70d707adebca4013809822123608b 100755 (executable)
@@ -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= recurse_submodules=
 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
                ;;
+       --recurse-submodules)
+               recurse_submodules=--recurse-submodules
+               ;;
+       --no-recurse-submodules)
+               recurse_submodules=--no-recurse-submodules
+               ;;
        --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
                dry_run=--dry-run
                ;;
@@ -217,7 +223,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 $recurse_submodules --update-head-ok "$@" || exit 1
 test -z "$dry_run" || exit 0
 
 curr_head=$(git rev-parse -q --verify HEAD)
index 91a47587478ae0550be8f41c00cb1749c85834f4..6f1c10722f744f4a27f18dae4867b15ecbf57d49 100644 (file)
@@ -10,7 +10,9 @@
 #include "string-list.h"
 
 struct string_list config_name_for_path;
+struct string_list config_fetch_recurse_submodules_for_name;
 struct string_list config_ignore_for_name;
+static int config_fetch_recurse_submodules;
 
 static int add_submodule_odb(const char *path)
 {
@@ -63,10 +65,14 @@ 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);
+       else if (!strcmp(var, "fetch.recursesubmodules")) {
+               config_fetch_recurse_submodules = git_config_bool(var, value);
+               return 0;
+       }
        return 0;
 }
 
@@ -100,6 +106,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 > 23) && !strcmp(var + len - 23, ".fetchrecursesubmodules")) {
+               strbuf_add(&submodname, var, len - 23);
+               config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, submodname.buf);
+               if (!config)
+                       config = string_list_append(&config_fetch_recurse_submodules_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 +243,89 @@ void show_submodule_summary(FILE *f, const char *path,
        strbuf_release(&sb);
 }
 
+void set_config_fetch_recurse_submodules(int value)
+{
+       config_fetch_recurse_submodules = value;
+}
+
+int fetch_populated_submodules(int num_options, const char **options,
+                              const char *prefix, int ignore_config,
+                              int quiet)
+{
+       int i, result = 0, argc = 0;
+       struct child_process cp;
+       const char **argv;
+       struct string_list_item *name_for_path;
+       const char *work_tree = get_git_work_tree();
+       if (!work_tree)
+               return 0;
+
+       if (!the_index.initialized)
+               if (read_cache() < 0)
+                       die("index file corrupt");
+
+       /* 4: "fetch" (options) "--submodule-prefix" prefix NULL */
+       argv = xcalloc(num_options + 4, sizeof(const char *));
+       argv[argc++] = "fetch";
+       for (i = 0; i < num_options; i++)
+               argv[argc++] = options[i];
+       argv[argc++] = "--submodule-prefix";
+
+       memset(&cp, 0, sizeof(cp));
+       cp.argv = argv;
+       cp.env = local_repo_env;
+       cp.git_cmd = 1;
+       cp.no_stdin = 1;
+
+       for (i = 0; i < active_nr; i++) {
+               struct strbuf submodule_path = STRBUF_INIT;
+               struct strbuf submodule_git_dir = STRBUF_INIT;
+               struct strbuf submodule_prefix = STRBUF_INIT;
+               struct cache_entry *ce = active_cache[i];
+               const char *git_dir, *name;
+
+               if (!S_ISGITLINK(ce->ce_mode))
+                       continue;
+
+               name = ce->name;
+               name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name);
+               if (name_for_path)
+                       name = name_for_path->util;
+
+               if (!ignore_config) {
+                       struct string_list_item *fetch_recurse_submodules_option;
+                       fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name);
+                       if (fetch_recurse_submodules_option) {
+                               if (!fetch_recurse_submodules_option->util)
+                                       continue;
+                       } else {
+                               if (!config_fetch_recurse_submodules)
+                                       continue;
+                       }
+               }
+
+               strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
+               strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
+               strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
+               git_dir = read_gitfile_gently(submodule_git_dir.buf);
+               if (!git_dir)
+                       git_dir = submodule_git_dir.buf;
+               if (is_directory(git_dir)) {
+                       if (!quiet)
+                               printf("Fetching submodule %s%s\n", prefix, ce->name);
+                       cp.dir = submodule_path.buf;
+                       argv[argc] = submodule_prefix.buf;
+                       if (run_command(&cp))
+                               result = 1;
+               }
+               strbuf_release(&submodule_path);
+               strbuf_release(&submodule_git_dir);
+               strbuf_release(&submodule_prefix);
+       }
+       free(argv);
+       return result;
+}
+
 unsigned is_submodule_modified(const char *path, int ignore_untracked)
 {
        ssize_t len;
index 386f410a66d9c431a61c0e26c773f1b3452f0d09..4729023aa5bbd7d7c95981b995c379407ba6423d 100644 (file)
@@ -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,10 @@ 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);
+void set_config_fetch_recurse_submodules(int value);
+int fetch_populated_submodules(int num_options, const char **options,
+                              const char *prefix, int ignore_config,
+                              int quiet);
 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 (executable)
index 0000000..884a5e5
--- /dev/null
@@ -0,0 +1,195 @@
+#!/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
+       ) &&
+       (
+               cd deepsubmodule &&
+               head1=$(git rev-parse --short HEAD) &&
+               echo new >> deepsubfile &&
+               test_tick &&
+               git add deepsubfile &&
+               git commit -m new deepsubfile &&
+               head2=$(git rev-parse --short HEAD) &&
+               echo "From $pwd/deepsubmodule" >> ../expect.err &&
+               echo "   $head1..$head2  master     -> origin/master" >> ../expect.err
+       )
+}
+
+test_expect_success setup '
+       mkdir deepsubmodule &&
+       (
+               cd deepsubmodule &&
+               git init &&
+               echo deepsubcontent > deepsubfile &&
+               git add deepsubfile &&
+               git commit -m new deepsubfile
+       ) &&
+       mkdir submodule &&
+       (
+               cd submodule &&
+               git init &&
+               echo subcontent > subfile &&
+               git add subfile &&
+               git submodule add "$pwd/deepsubmodule" deepsubmodule &&
+               git commit -a -m new
+       ) &&
+       git submodule add "$pwd/submodule" submodule &&
+       git commit -am initial &&
+       git clone . downstream &&
+       (
+               cd downstream &&
+               git submodule update --init --recursive
+       ) &&
+       echo "Fetching submodule submodule" > expect.out &&
+       echo "Fetching submodule submodule/deepsubmodule" >> expect.out
+'
+
+test_expect_success "fetch --recurse-submodules recurses into submodules" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git fetch --recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "fetch alone only fetches superproject" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git fetch >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "fetch --no-recurse-submodules only fetches superproject" '
+       (
+               cd downstream &&
+               git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses into submodules" '
+       (
+               cd downstream &&
+               git config -f .gitmodules submodule.submodule.fetchRecurseSubmodules true &&
+               git fetch >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "--no-recurse-submodules overrides .gitmodules config" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "using fetchRecurseSubmodules=false in .git/config overrides setting in .gitmodules" '
+       (
+               cd downstream &&
+               git config submodule.submodule.fetchRecurseSubmodules false &&
+               git fetch >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setting from .git/config" '
+       (
+               cd downstream &&
+               git fetch --recurse-submodules >../actual.out 2>../actual.err &&
+               git config -f --unset .gitmodules submodule.submodule.fetchRecurseSubmodules true &&
+               git config --unset submodule.submodule.fetchRecurseSubmodules
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "--quiet propagates to submodules" '
+       (
+               cd downstream &&
+               git fetch --recurse-submodules --quiet >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_expect_success "--dry-run propagates to submodules" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err &&
+       (
+               cd downstream &&
+               git fetch --recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "recurseSubmodules=true propagates into submodules" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git config fetch.recurseSubmodules true
+               git fetch >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "--recurse-submodules overrides config in submodule" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               (
+                       cd submodule &&
+                       git config fetch.recurseSubmodules false
+               ) &&
+               git fetch --recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       test_cmp expect.out actual.out &&
+       test_cmp expect.err actual.err
+'
+
+test_expect_success "--no-recurse-submodules overrides config setting" '
+       add_upstream_commit &&
+       (
+               cd downstream &&
+               git config fetch.recurseSubmodules true
+               git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+       ) &&
+       ! test -s actual.out &&
+       ! test -s actual.err
+'
+
+test_done