{"thread":{"id":"21528","subject":"[PATCH] Teach the --all option to 'git fetch'","startedAt":"2009-11-08T08:34:45Z","lastAt":"2009-11-08T15:18:34Z","messageCount":3,"participants":["Björn Gustavsson","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"127066","messageId":"4AF682A5.5020500@gmail.com","threadId":"21528","inReplyTo":null,"subject":"[PATCH] Teach the --all option to 'git fetch'","fromName":"Björn Gustavsson","fromEmail":"bgustavsson@gmail.com","sentAt":"2009-11-08T08:34:45Z","receivedAt":"2009-11-08T08:34:45Z","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\nseveral repositories at once. (Junio called 'git remote update'\na \"half-baked UI experiment that failed\" in topic 130891 in Gmane.)\n\nAdd the --all option to 'git fetch', to tell it to attempt to fetch\nfrom all remotes. (The configuration variable skipDefaultUpdate for\nthe remote will NOT be consulted.)\n\nOther options except -v and -q are silently ignored.\n\nSigned-off-by: Björn Gustavsson <bgustavsson@gmail.com>\n---\nSee \n\n  http://thread.gmane.org/gmane.comp.version-control.git/130819/focus=130891\n\nfor a previous discussion.\n\nMy implementation is deliberately minimal:\n\n* There is no way to configure that certain remotes should be skipped by\n  the --all option. I have never used skipDefaultUpdate with 'git remote\n  update'. If there is a real need for that feature, it can easily be added.\n  (But it should not use the existing skipDefaultUpdate configuration\n  variable, as the name does not make sense for 'git fetch --all'.)\n\n* All options except -q and -v are silently ignored. It might be useful\n  to support some more options if there is a real need for them.\n  (Perhaps --keep or --no-tags?)\n\n Documentation/git-fetch.txt |    5 +++\n builtin-fetch.c             |   80 +++++++++++++++++++++++++++++++++++++-----\n t/t5514-fetch-all.sh        |   76 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 151 insertions(+), 10 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..c1c3c46 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,7 +683,53 @@ 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+static int fetch_all(int argc)\n+{\n+\tint i, result = 0;\n+\tstruct string_list list = { NULL, 0, 0, 0 };\n+\tconst char *argv[] = { \"fetch\", NULL, NULL, NULL, NULL };\n+\n+\tif (argc == 1)\n+\t\tdie(\"fetch --all does not take a repository argument\");\n+\telse if (argc > 1)\n+\t\tdie(\"fetch --all does not make sense with refspecs\");\n+\n+\targc = 1;\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+\tresult = for_each_remote(get_one_remote_for_fetch, &list);\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+\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+}\n+\n+static int fetch_one(int argc, const char **argv)\n {\n \tstruct remote *remote;\n \tint i;\n@@ -688,14 +737,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\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@@ -746,3 +787,22 @@ 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+\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\treturn fetch_all(argc);\n+\t} else {\n+\t\treturn fetch_one(argc, argv);\n+\t}\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":"127068","messageId":"7vaayxfji4.fsf@alter.siamese.dyndns.org","threadId":"21528","inReplyTo":"4AF682A5.5020500@gmail.com","subject":"Re: [PATCH] Teach the --all option to 'git fetch'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-11-08T09:33:55Z","receivedAt":"2009-11-08T09:33:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Björn Gustavsson <bgustavsson@gmail.com> writes:\n\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> several repositories at once. (Junio called 'git remote update'\n> a \"half-baked UI experiment that failed\" in topic 130891 in Gmane.)\n\nPlease drop the \"because I said so\" part, as I made myself clear in the\nmessage I was speaking as mere one-of-participants to the project, not as\nthe maintainer.\n\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> +static int fetch_all(int argc)\n> +{\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> +}\n> +\n> ...\n> +int cmd_fetch(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint i;\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\treturn fetch_all(argc);\n> +\t} else {\n> +\t\treturn fetch_one(argc, argv);\n> +\t}\n> +}\n\nVery nice.  I like the simplicity of this.\n\nHopefully after the parse_options() we can inspect the \"repo\" argument to\nsee if it names remote groups and transplant the support for that from\n\"remote update\" codepath into this, right?\n"},{"id":"127080","messageId":"6672d0160911080718i2a4ff6e0mf6dfd9c5fc5ef45b@mail.gmail.com","threadId":"21528","inReplyTo":"7vaayxfji4.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] Teach the --all option to 'git fetch'","fromName":"Björn Gustavsson","fromEmail":"bgustavsson@gmail.com","sentAt":"2009-11-08T15:18:34Z","receivedAt":"2009-11-08T15:18:34Z","isPatch":true,"sender":{"key":"bgustavsson@gmail.com","avatar":"https://avatars.githubusercontent.com/u/74840?v=4"},"body":"2009/11/8 Junio C Hamano <gitster@pobox.com>:\n> Björn Gustavsson <bgustavsson@gmail.com> writes:\n>\n> Please drop the \"because I said so\" part, as I made myself clear in the\n> message I was speaking as mere one-of-participants to the project, not as\n> the maintainer.\n\nOK.\n\n> Very nice.  I like the simplicity of this.\n\nThanks!\n\n> Hopefully after the parse_options() we can inspect the \"repo\" argument to\n> see if it names remote groups and transplant the support for that from\n> \"remote update\" codepath into this, right?\n\nYes, it can be done, but it will lose some of its simplicity. I'll send a new\npatch series that do that soon.\n\n/Björn\n\n-- \nBjörn Gustavsson, Erlang/OTP, Ericsson AB\n"}]}