{"thread":{"id":"21534","subject":"[RFC/PATCH 1/4] Teach the --all option to 'git fetch'","startedAt":"2009-11-08T15:45:42Z","lastAt":"2009-11-08T21:08:11Z","messageCount":2,"participants":["Björn Gustavsson","Paolo Bonzini"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"127082","messageId":"4AF6E7A6.5060209@gmail.com","threadId":"21534","inReplyTo":null,"subject":"[RFC/PATCH 1/4] Teach the --all option to 'git fetch'","fromName":"Björn Gustavsson","fromEmail":"bgustavsson@gmail.com","sentAt":"2009-11-08T15:45:42Z","receivedAt":"2009-11-08T15:45:42Z","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/git-fetch.txt |    5 ++\n builtin-fetch.c             |  149 +++++++++++++++++++++++++++++++++++++-----\n t/t5514-fetch-all.sh        |   76 ++++++++++++++++++++++\n 3 files changed, 212 insertions(+), 18 deletions(-)\n create mode 100755 t/t5514-fetch-all.sh\n\ndiff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt\nindex f2483d6..9172454 100644\n--- a/Documentation/git-fetch.txt\n+++ b/Documentation/git-fetch.txt\n@@ -10,6 +10,8 @@ SYNOPSIS\n --------\n 'git fetch' <options> <repository> <refspec>...\n \n+'git fetch' --all <options>\n+\n \n DESCRIPTION\n -----------\n@@ -31,6 +33,9 @@ branches you are not interested in, you will not get them.\n \n OPTIONS\n -------\n+--all::\n+\tFetch all remotes.\n+\n include::fetch-options.txt[]\n \n include::pull-fetch-param.txt[]\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex a35a6f8..a520c1b 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -14,6 +14,7 @@\n \n static const char * const builtin_fetch_usage[] = {\n \t\"git fetch [options] [<repository> <refspec>...]\",\n+\t\"git fetch --all [options]\",\n \tNULL\n };\n \n@@ -23,7 +24,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 +33,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 +683,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 +781,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 +811,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/t5514-fetch-all.sh b/t/t5514-fetch-all.sh\nnew file mode 100755\nindex 0000000..25244bf\n--- /dev/null\n+++ b/t/t5514-fetch-all.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"},{"id":"127093","messageId":"hd7bvr$aul$2@ger.gmane.org","threadId":"21534","inReplyTo":"4AF6E7A6.5060209@gmail.com","subject":"Re: [RFC/PATCH 1/4] Teach the --all option to 'git fetch'","fromName":"Paolo Bonzini","fromEmail":"bonzini@gnu.org","sentAt":"2009-11-08T21:08:11Z","receivedAt":"2009-11-08T21:08:11Z","isPatch":true,"sender":{"key":"bonzini@gnu.org","avatar":"https://avatars.githubusercontent.com/u/42082?v=4"},"body":"On 11/08/2009 04:45 PM, Björn Gustavsson wrote:\n> 'git remote' is meant for managing remotes and 'git fetch' is meant\n> for actually fetching data from remote repositories. Therefore, it is\n> not logical that you must use 'git remote update' to fetch from\n> more than one repository at once.\n>\n> Add the --all option to 'git fetch', to tell it to attempt to fetch\n> from all remotes. Also, if --all is not given, the<repository>\n> argument is allowed to be the name of a group, to allow fetching\n> from all repositories in the group.\n>\n> Other options except -v and -q are silently ignored.\n\nI'm not sure about the naming of the option.  The reason is that while \nthere is no \"git remote push\", it makes sense (at least in this case \nsymmetry) between \"git fetch\" and \"git push\".  I would love to have a \nway to push to multiple remotes, for one thing. :-)\n\nWhat about having \"git fetch --multiple\" without arguments do the same \nthing as \"git fetch --all\" (possibly renaming skipFetchAll to \nskipFetchDefault)?\n\nPaolo\n"}]}