{"thread":{"id":"35129","subject":"[PATCH try2] fetch: add new fetch.default configuration","startedAt":"2013-10-12T07:07:05Z","lastAt":"2013-10-12T07:07:05Z","messageCount":1,"participants":["Felipe Contreras"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"228863","messageId":"1381561625-20624-1-git-send-email-felipe.contreras@gmail.com","threadId":"35129","inReplyTo":null,"subject":"[PATCH try2] fetch: add new fetch.default configuration","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2013-10-12T07:07:05Z","receivedAt":"2013-10-12T07:07:05Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"When the user has an upstream branch configured to track a remote\ntracking branch:\n\n  % git checkout --set-upstream-to github/master\n\nDoing a 'git fetch' without any arguments would try to fetch 'github',\nbecause it's configured as current branch's remote\n(branch.<current>.remote).\n\nHowever, if we do something slightly different:\n\n  % git checkout --set-upstream-to master\n\nDoing a 'git fetch' without any arguments would try to fetch the remote\n'.', for the same reason.\n\nBut, unlike the in the first instance, the second command would not\nupdate any remote tracking branch, in fact, it would not update any\nbranch at all. The only effect is that it would update FETCH_HEAD.\n\nFETCH_HEAD is a special symbol that is used to store the result of a\nfetch. It can be used by other commands of git, specially 'git merge'.\n\nIt is however, barely mentioned in the documentation, and can be\nconsidered a plumbing concept at best, that few Git users would benefit\nfrom using, and indeed, quite likely very few use it, or are even aware\nof it.\n\nSo when the user is presented with a message like this:\n\n  % git fetch\n  From .\n   * branch            master     -> FETCH_HEAD\n\nThe vast majority of them would have absolutely no idea what's going on.\nMany of them would probably just shrug, and manually specify the remote\nthe want to fetch from, which is 'origin' in many cases, *if* they\nmanage to notice the '.' at all.\n\nIf the user has say, twenty branches, ten with a local upstream\n(branch.<name>.remote = '.'), and ten without an upstream. The user\nmight get used to typing 'git fetch' and expect Git to fetch from\n'origin' which would happen 50% of the time, but the other 50%, the user\nwould be forced to specify the remote.\n\nThis inconsistency would be simply one more to join the list of\nincomprehensible quirks the user has to put up when using Git, so quite\nlikely he simply gets used to it, only to complain later in forums or\nsocial media, outside of the dwelling distance of the typical Git\ndeveloper.\n\nBut we can do better.\n\nWe can add a new 'fetch.default' configuration variable (one more to\njoin the many necessary to force Git to behave in sane manner), so the\nuser can specify what she wants to be performed when the remote is not\nspecified in the 'git fetch' command.\n\nThis configuration would probably be welcomed by 99% of the user\npopulation, who have no clue what FETCH_HEAD is, and do set local\nupstream branches, only to find out weird inconsistent behavior\ndepending on which branch they happen to be sitting at.\n\nWe add documentation and testing as expected, and also introduce other\nchanges necessary to make the configuration enter into effect when it's\nneeded.\n\nThe default (FETCH_DEFAULT_UNSPECIFIED), allows the current behavior to\nbe unmodified, so remote_get(NULL) is called, and the current rules to\ndetermine the default branch remain.\n\nThe new option \"simple\" allows Git to always fetch from \"origin\", which\nis what most users would expect, and it's 100% consistent.\n\nAlternatively, the user can manually specify the current behavior with\n\"current\" (alluding to the current branch), so that the behavior changes\ndepending on which branch the user happens to be sitting at. This would\nallow the user to retain this behavior, if she so wishes,\nin case Git developers regain their senses and set a default that most\nusers would appreciate (\"simple\").\n\nIf the user wants, for whatever strange reason swimming in his/her head,\nhe can still fetch from a local ('.') remote (even stating that as an\nEnglish sentence doesn't make any sense).\n\n  % git fetch .\n  From .\n   * branch            master     -> FETCH_HEAD\n\nAnd to any number of weird hacks with FETCH_HEAD.\n\nThe average user lucky enough to find the 'fetch.default' configuration,\nhowever, would never have to enter the word of \"local remote\"\nstrangeness, and would remain comfortably in the place where locals are\nlocals, and remotes are remotes, and 'git fetch' is always consistent,\nand always does something useful.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config.txt    |  9 +++++++++\n Documentation/git-fetch.txt |  4 ++++\n builtin/fetch.c             | 10 +++++++++-\n cache.h                     |  7 +++++++\n config.c                    | 21 +++++++++++++++++++++\n environment.c               |  1 +\n t/t5513-fetch-track.sh      | 43 +++++++++++++++++++++++++++++++++++++++++++\n 7 files changed, 94 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex ec57a15..fdf3c77 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1892,6 +1892,15 @@ to `simple`.\n \n --\n \n+fetch.default::\n+\tDefines the action `git fetch` should take if no remote is specified\n+\ton the command line. Possible values are:\n++\n+--\n+* `current` - fetch from the upstream of the current branch.\n+* `simple` - always fetch from `origin`.\n+--\n+\n rebase.stat::\n \tWhether to show a diffstat of what changed upstream since the last\n \trebase. False by default.\ndiff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt\nindex e08a028..5c4afdc 100644\n--- a/Documentation/git-fetch.txt\n+++ b/Documentation/git-fetch.txt\n@@ -37,6 +37,10 @@ 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+When no remote is specified, by the default the `origin` remote will be used,\n+unless there's an upstream branch configured for the current branch. This can\n+be configured with `fetch.default`.\n+\n OPTIONS\n -------\n include::fetch-options.txt[]\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d784b2e..78aada3 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1007,6 +1007,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tfor (i = 1; i < argc; i++)\n \t\tstrbuf_addf(&default_rla, \" %s\", argv[i]);\n \n+\tgit_config(git_default_config, NULL);\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_fetch_options, builtin_fetch_usage, 0);\n \n@@ -1040,7 +1041,14 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\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\tswitch (fetch_default) {\n+\t\tcase FETCH_DEFAULT_SIMPLE:\n+\t\t\tremote = remote_get(\"origin\");\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tremote = remote_get(NULL);\n+\t\t\tbreak;\n+\t\t}\n \t\tresult = fetch_one(remote, argc, argv);\n \t} else if (multiple) {\n \t\t/* All arguments are assumed to be remotes or groups */\ndiff --git a/cache.h b/cache.h\nindex 85b544f..bbb8406 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -627,9 +627,16 @@ enum push_default_type {\n \tPUSH_DEFAULT_UNSPECIFIED\n };\n \n+enum fetch_default_type {\n+\tFETCH_DEFAULT_UNSPECIFIED = 0,\n+\tFETCH_DEFAULT_SIMPLE,\n+\tFETCH_DEFAULT_CURRENT\n+};\n+\n extern enum branch_track git_branch_track;\n extern enum rebase_setup_type autorebase;\n extern enum push_default_type push_default;\n+extern enum fetch_default_type fetch_default;\n \n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\ndiff --git a/config.c b/config.c\nindex e13a7b6..5c71dfa 100644\n--- a/config.c\n+++ b/config.c\n@@ -906,6 +906,24 @@ static int git_default_push_config(const char *var, const char *value)\n \treturn 0;\n }\n \n+static int git_default_fetch_config(const char *var, const char *value)\n+{\n+\tif (strcmp(var, \"fetch.default\"))\n+\t\treturn 0;\n+\n+\tif (!value)\n+\t\treturn config_error_nonbool(var);\n+\telse if (!strcmp(value, \"simple\"))\n+\t\tfetch_default = FETCH_DEFAULT_SIMPLE;\n+\telse if (!strcmp(value, \"current\"))\n+\t\tfetch_default = FETCH_DEFAULT_CURRENT;\n+\telse {\n+\t\terror(\"Malformed value for %s: %s\", var, value);\n+\t\treturn error(\"Must be simple, or current.\");\n+\t}\n+\treturn 0;\n+}\n+\n static int git_default_mailmap_config(const char *var, const char *value)\n {\n \tif (!strcmp(var, \"mailmap.file\"))\n@@ -934,6 +952,9 @@ int git_default_config(const char *var, const char *value, void *dummy)\n \tif (!prefixcmp(var, \"push.\"))\n \t\treturn git_default_push_config(var, value);\n \n+\tif (!prefixcmp(var, \"fetch.\"))\n+\t\treturn git_default_fetch_config(var, value);\n+\n \tif (!prefixcmp(var, \"mailmap.\"))\n \t\treturn git_default_mailmap_config(var, value);\n \ndiff --git a/environment.c b/environment.c\nindex 5398c36..556d367 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -51,6 +51,7 @@ unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;\n enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;\n enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n+enum fetch_default_type fetch_default = FETCH_DEFAULT_UNSPECIFIED;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\ndiff --git a/t/t5513-fetch-track.sh b/t/t5513-fetch-track.sh\nindex 65d1e05..dcd4a8b 100755\n--- a/t/t5513-fetch-track.sh\n+++ b/t/t5513-fetch-track.sh\n@@ -27,4 +27,47 @@ test_expect_success fetch '\n \t)\n '\n \n+cat >expected <<EOF\n+236e830928a4295f5473416501dd777933bb778e\t\tbranch 'master' of .\n+EOF\n+\n+test_expect_success 'fetch default' '\n+\ttest_when_finished \"rm -rf another\" &&\n+\n+\t(\n+\t\ttest_create_repo another &&\n+\t\tcd another &&\n+\t\tgit remote add origin .. &&\n+\t\techo test > file &&\n+\t\tgit add . &&\n+\t\tgit commit -m test &&\n+\t\tgit checkout -t -b local-tracking master &&\n+\t\tgit fetch &&\n+\t\ttest_cmp ../expected .git/FETCH_HEAD\n+\t)\n+'\n+cat >expected <<EOF\n+9d34b142e42f6b3dbab46dd4b9bc515e0ab16101\tnot-for-merge\tbranch 'b-0' of ..\n+9d34b142e42f6b3dbab46dd4b9bc515e0ab16101\tnot-for-merge\tbranch 'b/one' of ..\n+9d34b142e42f6b3dbab46dd4b9bc515e0ab16101\tnot-for-merge\tbranch 'b1' of ..\n+9d34b142e42f6b3dbab46dd4b9bc515e0ab16101\tnot-for-merge\tbranch 'master' of ..\n+EOF\n+\n+test_expect_success 'fetch default simple' '\n+\ttest_when_finished \"rm -rf another\" &&\n+\n+\t(\n+\t\ttest_create_repo another &&\n+\t\tcd another &&\n+\t\tgit config fetch.default simple &&\n+\t\tgit remote add origin .. &&\n+\t\techo test > file &&\n+\t\tgit add . &&\n+\t\tgit commit -m test &&\n+\t\tgit checkout -t -b local-tracking master &&\n+\t\tgit fetch &&\n+\t\ttest_cmp ../expected .git/FETCH_HEAD\n+\t)\n+'\n+\n test_done\n-- \n1.8.4-fc\n"}]}