{"thread":{"id":"21549","subject":"[PATCH 1/5] Teach the --all option to 'git fetch'","startedAt":"2009-11-09T20:09:56Z","lastAt":"2009-11-09T20:09:56Z","messageCount":1,"participants":["Björn Gustavsson"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"127192","messageId":"4AF87714.9050606@gmail.com","threadId":"21549","inReplyTo":null,"subject":"[PATCH 1/5] Teach the --all option to 'git fetch'","fromName":"Björn Gustavsson","fromEmail":"bgustavsson@gmail.com","sentAt":"2009-11-09T20:09:56Z","receivedAt":"2009-11-09T20:09:56Z","isPatch":true,"sender":{"key":"bgustavsson@gmail.com","avatar":"https://avatars.githubusercontent.com/u/74840?v=4"},"body":"'git remote' is meant for managing remotes and 'git fetch' is meant\nfor actually fetching data from remote repositories. Therefore, it is\nnot logical that you must use 'git remote update' to fetch from\nmore than one repository at once.\n\nAdd the --all option to 'git fetch', to tell it to attempt to fetch\nfrom all remotes. Also, if --all is not given, the <repository>\nargument is allowed to be the name of a group, to allow fetching\nfrom all repositories in the group.\n\nOther options except -v and -q are silently ignored.\n\nSigned-off-by: Björn Gustavsson <bgustavsson@gmail.com>\n---\n Documentation/fetch-options.txt    |    3 +\n Documentation/git-fetch.txt        |   12 +++-\n Documentation/pull-fetch-param.txt |    7 ++\n builtin-fetch.c                    |  150 +++++++++++++++++++++++++++++++----\n t/t5506-remote-groups.sh           |   21 +++++-\n t/t5514-fetch-multiple.sh          |   76 ++++++++++++++++++\n 6 files changed, 247 insertions(+), 22 deletions(-)\n create mode 100755 t/t5514-fetch-multiple.sh\n\ndiff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt\nindex 2886874..93d73c3 100644\n--- a/Documentation/fetch-options.txt\n+++ b/Documentation/fetch-options.txt\n@@ -1,3 +1,6 @@\n+--all::\n+\tFetch all remotes.\n+\n -a::\n --append::\n \tAppend ref names and object names of fetched refs to the\ndiff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt\nindex f2483d6..3616466 100644\n--- a/Documentation/git-fetch.txt\n+++ b/Documentation/git-fetch.txt\n@@ -10,11 +10,15 @@ SYNOPSIS\n --------\n 'git fetch' <options> <repository> <refspec>...\n \n+'git fetch' <options> <group>\n+\n+'git fetch' --all <options>\n+\n \n DESCRIPTION\n -----------\n-Fetches named heads or tags from another repository, along with\n-the objects necessary to complete them.\n+Fetches named heads or tags from one or more other repositories,\n+along with the objects necessary to complete them.\n \n The ref names and their object names of fetched refs are stored\n in `.git/FETCH_HEAD`.  This information is left for a later merge\n@@ -28,6 +32,10 @@ pointed by remote tags that it does not yet have, then fetch\n those missing tags.  If the other end has tags that point at\n branches you are not interested in, you will not get them.\n \n+'git fetch' can fetch from either a single named repository, or\n+or from several repositories at once if <group> is given and\n+there is a remotes.<group> entry in the configuration file.\n+(See linkgit:git-config[1]).\n \n OPTIONS\n -------\ndiff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt\nindex f9811f2..712b91a 100644\n--- a/Documentation/pull-fetch-param.txt\n+++ b/Documentation/pull-fetch-param.txt\n@@ -4,6 +4,13 @@\n \t(see the section <<URLS,GIT URLS>> below) or the name\n \tof a remote (see the section <<REMOTES,REMOTES>> below).\n \n+ifndef::git-pull[]\n+<group>::\n+\tA name referring to a list of repositories as the value\n+\tof remotes.<group> in the configuration file.\n+\t(See linkgit:git-config[1]).\n+endif::git-pull[]\n+\n <refspec>::\n \tThe format of a <refspec> parameter is an optional plus\n \t`{plus}`, followed by the source ref <src>, followed\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex a35a6f8..2bf51cc 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -14,6 +14,8 @@\n \n static const char * const builtin_fetch_usage[] = {\n \t\"git fetch [options] [<repository> <refspec>...]\",\n+\t\"git fetch [options] <group>\",\n+\t\"git fetch --all [options]\",\n \tNULL\n };\n \n@@ -23,7 +25,7 @@ enum {\n \tTAGS_SET = 2\n };\n \n-static int append, force, keep, update_head_ok, verbosity;\n+static int all, append, force, keep, update_head_ok, verbosity;\n static int tags = TAGS_DEFAULT;\n static const char *depth;\n static const char *upload_pack;\n@@ -32,6 +34,8 @@ static struct transport *transport;\n \n static struct option builtin_fetch_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n+\tOPT_BOOLEAN(0, \"all\", &all,\n+\t\t    \"fetch from all remotes\"),\n \tOPT_BOOLEAN('a', \"append\", &append,\n \t\t    \"append to .git/FETCH_HEAD instead of overwriting\"),\n \tOPT_STRING(0, \"upload-pack\", &upload_pack, \"PATH\",\n@@ -680,27 +684,89 @@ static void set_option(const char *name, const char *value)\n \t\t\tname, transport->url);\n }\n \n-int cmd_fetch(int argc, const char **argv, const char *prefix)\n+static int get_one_remote_for_fetch(struct remote *remote, void *priv)\n+{\n+\tstruct string_list *list = priv;\n+\tstring_list_append(remote->name, list);\n+\treturn 0;\n+}\n+\n+struct remote_group_data {\n+\tconst char *name;\n+\tstruct string_list *list;\n+};\n+\n+static int get_remote_group(const char *key, const char *value, void *priv)\n+{\n+\tstruct remote_group_data *g = priv;\n+\n+\tif (!prefixcmp(key, \"remotes.\") &&\n+\t\t\t!strcmp(key + 8, g->name)) {\n+\t\t/* split list by white space */\n+\t\tint space = strcspn(value, \" \\t\\n\");\n+\t\twhile (*value) {\n+\t\t\tif (space > 1) {\n+\t\t\t\tstring_list_append(xstrndup(value, space),\n+\t\t\t\t\t\t   g->list);\n+\t\t\t}\n+\t\t\tvalue += space + (value[space] != '\\0');\n+\t\t\tspace = strcspn(value, \" \\t\\n\");\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int add_remote_or_group(const char *name, struct string_list *list)\n+{\n+\tint prev_nr = list->nr;\n+\tstruct remote_group_data g = { name, list };\n+\n+\tgit_config(get_remote_group, &g);\n+\tif (list->nr == prev_nr) {\n+\t\tstruct remote *remote;\n+\t\tif (!remote_is_configured(name))\n+\t\t\treturn 0;\n+\t\tremote = remote_get(name);\n+\t\tstring_list_append(remote->name, list);\n+\t}\n+\treturn 1;\n+}\n+\n+static int fetch_multiple(struct string_list *list)\n+{\n+\tint i, result = 0;\n+\tconst char *argv[] = { \"fetch\", NULL, NULL, NULL, NULL };\n+\tint argc = 1;\n+\n+\tif (verbosity >= 2)\n+\t\targv[argc++] = \"-v\";\n+\tif (verbosity >= 1)\n+\t\targv[argc++] = \"-v\";\n+\telse if (verbosity < 0)\n+\t\targv[argc++] = \"-q\";\n+\n+\tfor (i = 0; i < list->nr; i++) {\n+\t\tconst char *name = list->items[i].string;\n+\t\targv[argc] = name;\n+\t\tif (verbosity >= 0)\n+\t\t\tprintf(\"Fetching %s\\n\", name);\n+\t\tif (run_command_v_opt(argv, RUN_GIT_CMD)) {\n+\t\t\terror(\"Could not fetch %s\", name);\n+\t\t\tresult = 1;\n+\t\t}\n+\t}\n+\n+\treturn result;\n+}\n+\n+static int fetch_one(struct remote *remote, int argc, const char **argv)\n {\n-\tstruct remote *remote;\n \tint i;\n \tstatic const char **refs = NULL;\n \tint ref_nr = 0;\n \tint exit_code;\n \n-\t/* Record the command line for the reflog */\n-\tstrbuf_addstr(&default_rla, \"fetch\");\n-\tfor (i = 1; i < argc; i++)\n-\t\tstrbuf_addf(&default_rla, \" %s\", argv[i]);\n-\n-\targc = parse_options(argc, argv, prefix,\n-\t\t\t     builtin_fetch_options, builtin_fetch_usage, 0);\n-\n-\tif (argc == 0)\n-\t\tremote = remote_get(NULL);\n-\telse\n-\t\tremote = remote_get(argv[0]);\n-\n \tif (!remote)\n \t\tdie(\"Where do you want to fetch from today?\");\n \n@@ -716,10 +782,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tif (depth)\n \t\tset_option(TRANS_OPT_DEPTH, depth);\n \n-\tif (argc > 1) {\n+\tif (argc > 0) {\n \t\tint j = 0;\n \t\trefs = xcalloc(argc + 1, sizeof(const char *));\n-\t\tfor (i = 1; i < argc; i++) {\n+\t\tfor (i = 0; i < argc; i++) {\n \t\t\tif (!strcmp(argv[i], \"tag\")) {\n \t\t\t\tchar *ref;\n \t\t\t\ti++;\n@@ -746,3 +812,51 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \ttransport = NULL;\n \treturn exit_code;\n }\n+\n+int cmd_fetch(int argc, const char **argv, const char *prefix)\n+{\n+\tint i;\n+\tstruct string_list list = { NULL, 0, 0, 0 };\n+\tstruct remote *remote;\n+\tint result = 0;\n+\n+\t/* Record the command line for the reflog */\n+\tstrbuf_addstr(&default_rla, \"fetch\");\n+\tfor (i = 1; i < argc; i++)\n+\t\tstrbuf_addf(&default_rla, \" %s\", argv[i]);\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_fetch_options, builtin_fetch_usage, 0);\n+\n+\tif (all) {\n+\t\tif (argc == 1)\n+\t\t\tdie(\"fetch --all does not take a repository argument\");\n+\t\telse if (argc > 1)\n+\t\t\tdie(\"fetch --all does not make sense with refspecs\");\n+\t\t(void) for_each_remote(get_one_remote_for_fetch, &list);\n+\t\tresult = fetch_multiple(&list);\n+\t} else if (argc == 0) {\n+\t\t/* No arguments -- use default remote */\n+\t\tremote = remote_get(NULL);\n+\t\tresult = fetch_one(remote, argc, argv);\n+\t} else {\n+\t\t/* Single remote or group */\n+\t\t(void) add_remote_or_group(argv[0], &list);\n+\t\tif (list.nr > 1) {\n+\t\t\t/* More than one remote */\n+\t\t\tif (argc > 1)\n+\t\t\t\tdie(\"Fetching a group and specifying refspecs does not make sense\");\n+\t\t\tresult = fetch_multiple(&list);\n+\t\t} else {\n+\t\t\t/* Zero or one remotes */\n+\t\t\tremote = remote_get(argv[0]);\n+\t\t\tresult = fetch_one(remote, argc-1, argv+1);\n+\t\t}\n+\t}\n+\n+\t/* All names were strdup()ed or strndup()ed */\n+\tlist.strdup_strings = 1;\n+\tstring_list_clear(&list, 0);\n+\n+\treturn result;\n+}\ndiff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh\nindex 2a1806b..b7b7dda 100755\n--- a/t/t5506-remote-groups.sh\n+++ b/t/t5506-remote-groups.sh\n@@ -51,7 +51,7 @@ test_expect_success 'nonexistant group produces error' '\n \t! repo_fetched two\n '\n \n-test_expect_success 'updating group updates all members' '\n+test_expect_success 'updating group updates all members (remote update)' '\n \tmark group-all &&\n \tupdate_repos &&\n \tgit config --add remotes.all one &&\n@@ -61,7 +61,15 @@ test_expect_success 'updating group updates all members' '\n \trepo_fetched two\n '\n \n-test_expect_success 'updating group does not update non-members' '\n+test_expect_success 'updating group updates all members (fetch)' '\n+\tmark fetch-group-all &&\n+\tupdate_repos &&\n+\tgit fetch all &&\n+\trepo_fetched one &&\n+\trepo_fetched two\n+'\n+\n+test_expect_success 'updating group does not update non-members (remote update)' '\n \tmark group-some &&\n \tupdate_repos &&\n \tgit config --add remotes.some one &&\n@@ -70,6 +78,15 @@ test_expect_success 'updating group does not update non-members' '\n \t! repo_fetched two\n '\n \n+test_expect_success 'updating group does not update non-members (fetch)' '\n+\tmark fetch-group-some &&\n+\tupdate_repos &&\n+\tgit config --add remotes.some one &&\n+\tgit remote update some &&\n+\trepo_fetched one &&\n+\t! repo_fetched two\n+'\n+\n test_expect_success 'updating remote name updates that remote' '\n \tmark remote-name &&\n \tupdate_repos &&\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nnew file mode 100755\nindex 0000000..25244bf\n--- /dev/null\n+++ b/t/t5514-fetch-multiple.sh\n@@ -0,0 +1,76 @@\n+#!/bin/sh\n+\n+test_description='fetch --all works correctly'\n+\n+. ./test-lib.sh\n+\n+setup_repository () {\n+\tmkdir \"$1\" && (\n+\tcd \"$1\" &&\n+\tgit init &&\n+\t>file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m \"Initial\" &&\n+\tgit checkout -b side &&\n+\t>elif &&\n+\tgit add elif &&\n+\ttest_tick &&\n+\tgit commit -m \"Second\" &&\n+\tgit checkout master\n+\t)\n+}\n+\n+test_expect_success setup '\n+\tsetup_repository one &&\n+\tsetup_repository two &&\n+\t(\n+\t\tcd two && git branch another\n+\t) &&\n+\tgit clone --mirror two three\n+\tgit clone one test\n+'\n+\n+cat > test/expect << EOF\n+  one/master\n+  one/side\n+  origin/HEAD -> origin/master\n+  origin/master\n+  origin/side\n+  three/another\n+  three/master\n+  three/side\n+  two/another\n+  two/master\n+  two/side\n+EOF\n+\n+test_expect_success 'git fetch --all' '\n+\t(cd test &&\n+\t git remote add one ../one &&\n+\t git remote add two ../two &&\n+\t git remote add three ../three &&\n+\t git fetch --all &&\n+\t git branch -r > output &&\n+\t test_cmp expect output)\n+'\n+\n+test_expect_success 'git fetch --all should continue if a remote has errors' '\n+\t(git clone one test2 &&\n+\t cd test2 &&\n+\t git remote add bad ../non-existing &&\n+\t git remote add one ../one &&\n+\t git remote add two ../two &&\n+\t git remote add three ../three &&\n+\t test_must_fail git fetch --all &&\n+\t git branch -r > output &&\n+\t test_cmp ../test/expect output)\n+'\n+\n+test_expect_success 'git fetch --all does not allow non-option arguments' '\n+\t(cd test &&\n+\t test_must_fail git fetch --all origin &&\n+\t test_must_fail git fetch --all origin master)\n+'\n+\n+test_done\n-- \n1.6.5.1.69.g36942\n"}]}