{"thread":{"id":"34379","subject":"[RFC/PATCH] fetch: make --prune configurable","startedAt":"2013-07-08T12:56:57Z","lastAt":"2013-07-12T22:38:46Z","messageCount":5,"participants":["Michael Schubert","Junio C Hamano","John Keeping","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"222817","messageId":"1373288217-20580-1-git-send-email-mschub@elegosoft.com","threadId":"34379","inReplyTo":null,"subject":"[RFC/PATCH] fetch: make --prune configurable","fromName":"Michael Schubert","fromEmail":"mschub@elegosoft.com","sentAt":"2013-07-08T12:56:57Z","receivedAt":"2013-07-08T12:56:57Z","isPatch":true,"sender":{"key":"mschub@elegosoft.com","avatar":null},"body":"$gmane/201715 brought up the idea to fetch --prune by default.\nSince --prune is a \"potentially destructive operation\" (Git doesn't\nkeep reflogs for deleted references yet), we don't want to prune\nwithout users consent.\n\nTo accommodate users who want to either prune always or when fetching\nfrom a particular remote, add two new configuration variables\n\"fetch.prune\" and \"remote.<name>.prune\":\n\n - \"fetch.prune\" allows to enable prune for all fetch operations.\n\n - \"remote.<name>.prune\" allows to change the behaviour per remote.\n\nSigned-off-by: Michael Schubert <mschub@elegosoft.com>\n---\n\nhttp://thread.gmane.org/gmane.comp.version-control.git/201715\n\n\n Documentation/config.txt |  9 +++++++++\n builtin/fetch.c          | 28 +++++++++++++++++++++++++---\n remote.c                 |  2 ++\n remote.h                 |  1 +\n t/t5510-fetch.sh         | 38 ++++++++++++++++++++++++++++++++++++++\n 5 files changed, 75 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex b4d4887..74e8026 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1067,6 +1067,10 @@ fetch.unpackLimit::\n \tespecially on slow filesystems.  If not set, the value of\n \t`transfer.unpackLimit` is used instead.\n \n+fetch.prune::\n+\tIf true, fetch will automatically behave as if the `--prune`\n+\toption was given on the command line.\n+\n format.attach::\n \tEnable multipart/mixed attachments as the default for\n \t'format-patch'.  The value can also be a double quoted string\n@@ -2010,6 +2014,11 @@ remote.<name>.vcs::\n \tSetting this to a value <vcs> will cause Git to interact with\n \tthe remote with the git-remote-<vcs> helper.\n \n+remote.<name>.prune::\n+\tWhen set to true, fetching from this remote by default will also\n+\tremove any remote-tracking branches which no longer exist on the\n+\tremote (as if the `--prune` option was give on the command line).\n+\n remotes.<group>::\n \tThe list of remotes which are fetched by \"git remote update\n \t<group>\".  See linkgit:git-remote[1].\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d784b2e..3953317 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -30,7 +30,14 @@ enum {\n \tTAGS_SET = 2\n };\n \n-static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;\n+enum {\n+\tPRUNE_UNSET = 0,\n+\tPRUNE_DEFAULT = 1,\n+\tPRUNE_FORCE = 2\n+};\n+\n+static int prune = PRUNE_DEFAULT;\n+static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;\n static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static int tags = TAGS_DEFAULT, unshallow;\n static const char *depth;\n@@ -54,6 +61,17 @@ static int option_parse_recurse_submodules(const struct option *opt,\n \treturn 0;\n }\n \n+static int git_fetch_config(const char *k, const char *v, void *cb)\n+{\n+\tif (!strcmp(k, \"fetch.prune\")) {\n+\t\tint boolval = git_config_bool(k, v);\n+\t\tif (boolval)\n+\t\t\tprune = PRUNE_FORCE;\n+\t\treturn 0;\n+\t}\n+\treturn git_default_config(k, v, cb);\n+}\n+\n static struct option builtin_fetch_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOLEAN(0, \"all\", &all,\n@@ -770,7 +788,7 @@ static int do_fetch(struct transport *transport,\n \t\tretcode = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (prune) {\n+\tif (prune == PRUNE_FORCE || (transport->remote->prune && prune)) {\n \t\t/* If --tags was specified, pretend the user gave us the canonical tags refspec */\n \t\tif (tags == TAGS_SET) {\n \t\t\tconst char *tags_str = \"refs/tags/*:refs/tags/*\";\n@@ -882,8 +900,10 @@ static void add_options_to_argv(struct argv_array *argv)\n {\n \tif (dry_run)\n \t\targv_array_push(argv, \"--dry-run\");\n-\tif (prune)\n+\tif (prune == PRUNE_FORCE)\n \t\targv_array_push(argv, \"--prune\");\n+\telse if (prune == PRUNE_UNSET)\n+\t\targv_array_push(argv, \"--no-prune\");\n \tif (update_head_ok)\n \t\targv_array_push(argv, \"--update-head-ok\");\n \tif (force)\n@@ -1007,6 +1027,8 @@ 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_fetch_config, NULL);\n+\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_fetch_options, builtin_fetch_usage, 0);\n \ndiff --git a/remote.c b/remote.c\nindex 6f57830..e6f2acb 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -404,6 +404,8 @@ static int handle_config(const char *key, const char *value, void *cb)\n \t\tremote->skip_default_update = git_config_bool(key, value);\n \telse if (!strcmp(subkey, \".skipfetchall\"))\n \t\tremote->skip_default_update = git_config_bool(key, value);\n+\telse if (!strcmp(subkey, \".prune\"))\n+\t\tremote->prune = git_config_bool(key, value);\n \telse if (!strcmp(subkey, \".url\")) {\n \t\tconst char *v;\n \t\tif (git_config_string(&v, key, value))\ndiff --git a/remote.h b/remote.h\nindex cf56724..4db3498 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -40,6 +40,7 @@ struct remote {\n \tint fetch_tags;\n \tint skip_default_update;\n \tint mirror;\n+\tint prune;\n \n \tconst char *receivepack;\n \tconst char *uploadpack;\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex fde6891..f3d63ca 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -497,6 +497,44 @@ test_expect_success \"should be able to fetch with duplicate refspecs\" '\n \t)\n '\n \n+test_expect_success 'fetch should prune when fetch.prune is true' '\n+  cd \"$D\" &&\n+  git branch somebranch &&\n+  (\n+    cd one &&\n+    git fetch &&\n+    test -f .git/refs/remotes/origin/somebranch\n+  ) &&\n+  git branch -d somebranch &&\n+  (\n+    cd one &&\n+    git config fetch.prune true &&\n+    git fetch --no-prune &&\n+    test -f .git/refs/remotes/origin/somebranch &&\n+    git fetch &&\n+    ! test -f .git/refs/remotes/origin/somebranch\n+  )\n+'\n+\n+test_expect_success 'fetch should prune when remote.<name>.prune is true' '\n+  cd \"$D\" &&\n+  git branch somebranch &&\n+  (\n+    cd one &&\n+    git fetch &&\n+    test -f .git/refs/remotes/origin/somebranch\n+  ) &&\n+  git branch -d somebranch &&\n+  (\n+    cd one &&\n+    git config remote.origin.prune true &&\n+    git fetch --no-prune &&\n+    test -f .git/refs/remotes/origin/somebranch &&\n+    git fetch &&\n+    ! test -f .git/refs/remotes/origin/somebranch\n+  )\n+'\n+\n test_expect_success 'all boundary commits are excluded' '\n \ttest_commit base &&\n \ttest_commit oneside &&\n-- \n1.8.3.2.734.gbcd3b20\n"},{"id":"222853","messageId":"7vk3l0zypa.fsf@alter.siamese.dyndns.org","threadId":"34379","inReplyTo":"1373288217-20580-1-git-send-email-mschub@elegosoft.com","subject":"Re: [RFC/PATCH] fetch: make --prune configurable","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-08T18:36:01Z","receivedAt":"2013-07-08T18:36:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Schubert <mschub@elegosoft.com> writes:\n\n> $gmane/201715 brought up the idea to fetch --prune by default.\n\nWhen you can summarize it in a few lines, e.g.\n\n    Without \"git fetch --prune\", remote-tracking branches for a branch\n    the other side already has removed will stay forever.  Some people\n    want to always run \"git fetch --prune\".\n\nplease refrain from forcing people to go to the web while reading\nlogs.\n\n> Since --prune is a \"potentially destructive operation\" (Git doesn't\n> keep reflogs for deleted references yet), we don't want to prune\n> without users consent.\n>\n> To accommodate users who want to either prune always or when fetching\n> from a particular remote, add two new configuration variables\n> \"fetch.prune\" and \"remote.<name>.prune\":\n>\n>  - \"fetch.prune\" allows to enable prune for all fetch operations.\n>\n>  - \"remote.<name>.prune\" allows to change the behaviour per remote.\n>\n\nAdd:\n\n    \"git fetch --no-prune\" from the command line will defeat the\n    configured default for safety.\n\n(I didn't check if your patch already does so, though).\n\nOther than that, the log message looks good.\n\nThanks for starting to work on this.\n\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index b4d4887..74e8026 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -1067,6 +1067,10 @@ fetch.unpackLimit::\n>  \tespecially on slow filesystems.  If not set, the value of\n>  \t`transfer.unpackLimit` is used instead.\n>  \n> +fetch.prune::\n> +\tIf true, fetch will automatically behave as if the `--prune`\n> +\toption was given on the command line.\n> +\n>  format.attach::\n>  \tEnable multipart/mixed attachments as the default for\n>  \t'format-patch'.  The value can also be a double quoted string\n> @@ -2010,6 +2014,11 @@ remote.<name>.vcs::\n>  \tSetting this to a value <vcs> will cause Git to interact with\n>  \tthe remote with the git-remote-<vcs> helper.\n>  \n> +remote.<name>.prune::\n> +\tWhen set to true, fetching from this remote by default will also\n> +\tremove any remote-tracking branches which no longer exist on the\n> +\tremote (as if the `--prune` option was give on the command line).\n\nWe may want to say something about interaction between the two\nvariables.  E.g. fetch.prune=true, remote.origin.prune=false would\nhopefully not to prune when you are fetching from your 'origin', and\nfetch.prune=false, remote.origin.prune=true would.\n\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index d784b2e..3953317 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -30,7 +30,14 @@ enum {\n>  \tTAGS_SET = 2\n>  };\n>  \n> -static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;\n> +enum {\n> +\tPRUNE_UNSET = 0,\n> +\tPRUNE_DEFAULT = 1,\n> +\tPRUNE_FORCE = 2\n> +};\n> +\n> +static int prune = PRUNE_DEFAULT;\n\nI find this unconventional in that usually _UNSET means \"the user\nhasn't explicitly said anything about what she wants\" (hence\ntypically a variable is initialized to that value).  Also I am not\nsure what \"FORCE\" means.\n\nIf this were \n\nenum {\n\tPRUNE_UNSET = -1,\n\tPRUNE_NO = 0,\n\tPRUNE_YES = 1\n};\n\nthen I would understand, but at that point, that is a typical\nsetting for a boolean variable, so we could just use -1/0/1.\n\n> +static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;\n>  static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n>  static int tags = TAGS_DEFAULT, unshallow;\n>  static const char *depth;\n> @@ -54,6 +61,17 @@ static int option_parse_recurse_submodules(const struct option *opt,\n>  \treturn 0;\n>  }\n>  \n> +static int git_fetch_config(const char *k, const char *v, void *cb)\n> +{\n> +\tif (!strcmp(k, \"fetch.prune\")) {\n> +\t\tint boolval = git_config_bool(k, v);\n> +\t\tif (boolval)\n> +\t\t\tprune = PRUNE_FORCE;\n> +\t\treturn 0;\n\nThis is not good, is it?  Imagine fetch.prune=true in ~/.gitconfig\nand fetch.prune=false in $GIT_DIR/config; I'd expect the more\nspecific one to set \"prune\" back to non-FORCE value.\n\nAs you do not have transport available before you process\nparse_options(), I think you need two variables, \"prune\" that is\nused to determine what happens (same as in the code before your\npatch) and a new one \"fetch_prune_config\" that records what we read\nfrom the fetch.prune configuration.\n\nSo I _suspect_ the interaction between the configuration parser and\nthe command line option parser should look more like this, in order\nto implement the correct order of precedence:\n\n\tstatic int fetch_prune_config = -1; /* unspecified */\n        static int prune = -1; /* unspecified */\n\t#define PRUNE_BY_DEFAULT 0\n\n\t...\n\n        /* set \"fetch_prune_config\" */\n\tgit_config(git_fetch_config);\n\t--> git_fetch_config():\n\t\tif (!strcmp(k, \"fetch.prune\")) {\n\t\t\tfetch_prune_config = git_config_bool(k, v);\n\t\t\treturn 0;\n\t\t}\n\n\t...\n\n        /* set \"prune\" */\n        parse_options();\n\n        --> fetch_one();\n\t\ttransport_get();\n\n                if (prune < 0) {\n\t\t\t/* no command line request; combine configs */\n\t\t\tif (0 <= transport->remote->prune)\n\t\t\t\tprune = transport->remote->prune;\n\t\t\telse if (0 <= fetch_prune_config)\n                        \tprune = fetch_prune_config;\n\t\t\telse\n                        \tprune = PRUNE_BY_DEFAULT;\n\t\t}\n\nYou would need to update make_remote() to initialise its .prune\nmember with -1 (unspecified).\n\nYou also need to change OPT_BOOLEAN() to OPT_BOOL() for the command\nline processing for \"prune\", as the former is a \"count up\" that is\nuseful for things like \"-v\", \"-v -v\", etc.\n\n> +\t}\n> +\treturn git_default_config(k, v, cb);\n\nWhat kind of random configuration are we expecting to read from and\naffect our execution by falling back to the default config?\n\n>  static struct option builtin_fetch_options[] = {\n>  \tOPT__VERBOSITY(&verbosity),\n>  \tOPT_BOOLEAN(0, \"all\", &all,\n> @@ -770,7 +788,7 @@ static int do_fetch(struct transport *transport,\n>  \t\tretcode = 1;\n>  \t\tgoto cleanup;\n>  \t}\n> -\tif (prune) {\n> +\tif (prune == PRUNE_FORCE || (transport->remote->prune && prune)) {\n\nI cannot offhand see how this is correct with your code (without the\nabove \"you need two variables\" suggestion).  It reads to me:\n\n\tIf fetch.prune is set, we set it to PRUNE_FORCE in the\n        configuration parser, and in that case we ignore everything\n        else and go ahead to prune.\n\nwhich does not sound right.  fetch.prune=yes remote.$name.prune=no\nshould not prune, fetch.prune=yes with --no-prune from the command\nline should not prune, etc.  But I may be misreading this complex\nboolean expression in the patch.\n\nAnd with the suggested change, this can stay \n\n\tif (prune)\n\nwithout having to do any mental gymnastics here.\n\n> @@ -882,8 +900,10 @@ static void add_options_to_argv(struct argv_array *argv)\n>  {\n>  \tif (dry_run)\n>  \t\targv_array_push(argv, \"--dry-run\");\n> -\tif (prune)\n> +\tif (prune == PRUNE_FORCE)\n>  \t\targv_array_push(argv, \"--prune\");\n> +\telse if (prune == PRUNE_UNSET)\n> +\t\targv_array_push(argv, \"--no-prune\");\n>  \tif (update_head_ok)\n>  \t\targv_array_push(argv, \"--update-head-ok\");\n>  \tif (force)\n\nAnd I think this hunk can go with the suggested change.\n\n> @@ -1007,6 +1027,8 @@ 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_fetch_config, NULL);\n> +\n>  \targc = parse_options(argc, argv, prefix,\n>  \t\t\t     builtin_fetch_options, builtin_fetch_usage, 0);\n>  \n> diff --git a/remote.c b/remote.c\n> index 6f57830..e6f2acb 100644\n> --- a/remote.c\n> +++ b/remote.c\n> @@ -404,6 +404,8 @@ static int handle_config(const char *key, const char *value, void *cb)\n>  \t\tremote->skip_default_update = git_config_bool(key, value);\n>  \telse if (!strcmp(subkey, \".skipfetchall\"))\n>  \t\tremote->skip_default_update = git_config_bool(key, value);\n> +\telse if (!strcmp(subkey, \".prune\"))\n> +\t\tremote->prune = git_config_bool(key, value);\n>  \telse if (!strcmp(subkey, \".url\")) {\n>  \t\tconst char *v;\n>  \t\tif (git_config_string(&v, key, value))\n> diff --git a/remote.h b/remote.h\n> index cf56724..4db3498 100644\n> --- a/remote.h\n> +++ b/remote.h\n> @@ -40,6 +40,7 @@ struct remote {\n>  \tint fetch_tags;\n>  \tint skip_default_update;\n>  \tint mirror;\n> +\tint prune;\n>  \n>  \tconst char *receivepack;\n>  \tconst char *uploadpack;\n> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n> index fde6891..f3d63ca 100755\n> --- a/t/t5510-fetch.sh\n> +++ b/t/t5510-fetch.sh\n> @@ -497,6 +497,44 @@ test_expect_success \"should be able to fetch with duplicate refspecs\" '\n>  \t)\n>  '\n>  \n> +test_expect_success 'fetch should prune when fetch.prune is true' '\n> +  cd \"$D\" &&\n> +  git branch somebranch &&\n> +  (\n> +    cd one &&\n> +    git fetch &&\n> +    test -f .git/refs/remotes/origin/somebranch\n> +  ) &&\n> +  git branch -d somebranch &&\n> +  (\n> +    cd one &&\n> +    git config fetch.prune true &&\n> +    git fetch --no-prune &&\n> +    test -f .git/refs/remotes/origin/somebranch &&\n> +    git fetch &&\n> +    ! test -f .git/refs/remotes/origin/somebranch\n> +  )\n> +'\n\nOK, \"fetch.prune < --no-prune\" and  \"fetch.prune alone\" are tested\nwith this.  \"fetch.prune=no\" with \"--prune\" is not tested.\n\n> +test_expect_success 'fetch should prune when remote.<name>.prune is true' '\n> +  cd \"$D\" &&\n> +  git branch somebranch &&\n> +  (\n> +    cd one &&\n> +    git fetch &&\n> +    test -f .git/refs/remotes/origin/somebranch\n\nDepending on the success/failure of the previous test, we do not\nknow what value fetch.prune is set in this repository.  What are we\ntesting here?  By using \"test_unconfig\" to clear the variables you\ncare about before starting each test, you would make it clear what\nexactly you are testing.\n\nThere are at least 9 combinations of settings we should be testing.\nA naive matrix:\n\n\tfetch.prune\t\tset to false or not set, set to true\n        remote.origin.prune\tset to false or not set, set to true\n\tcommand line\t\t--no-prune, --prune, (neither)\n\nwill give us 12 combinations (for completeness, you may want to test\nfetch.prune that is not set at all and fetch.prune explicitly set to\nfalse separately, but let's not go overboard), but we presumably\nhave been testing cases where neither fetch.prune or remote.*.prune\nis set, so we would need to only test cases where at least one of\nthe configuration variables is set (that would make 3 cases),\nmultiplied with the command line combinations.\n\n\n> +  ) &&\n> +  git branch -d somebranch &&\n> +  (\n> +    cd one &&\n> +    git config remote.origin.prune true &&\n> +    git fetch --no-prune &&\n> +    test -f .git/refs/remotes/origin/somebranch &&\n> +    git fetch &&\n> +    ! test -f .git/refs/remotes/origin/somebranch\n> +  )\n> +'\n> +\n>  test_expect_success 'all boundary commits are excluded' '\n>  \ttest_commit base &&\n>  \ttest_commit oneside &&\n"},{"id":"222855","messageId":"20130708190125.GX9161@serenity.lan","threadId":"34379","inReplyTo":"1373288217-20580-1-git-send-email-mschub@elegosoft.com","subject":"Re: [RFC/PATCH] fetch: make --prune configurable","fromName":"John Keeping","fromEmail":"john@keeping.me.uk","sentAt":"2013-07-08T19:01:25Z","receivedAt":"2013-07-08T19:01:25Z","isPatch":true,"sender":{"key":"john@keeping.me.uk","avatar":"https://avatars.githubusercontent.com/u/1702081?v=4"},"body":"On Mon, Jul 08, 2013 at 02:56:57PM +0200, Michael Schubert wrote:\n> $gmane/201715 brought up the idea to fetch --prune by default.\n> Since --prune is a \"potentially destructive operation\" (Git doesn't\n> keep reflogs for deleted references yet), we don't want to prune\n> without users consent.\n> \n> To accommodate users who want to either prune always or when fetching\n> from a particular remote, add two new configuration variables\n> \"fetch.prune\" and \"remote.<name>.prune\":\n> \n>  - \"fetch.prune\" allows to enable prune for all fetch operations.\n> \n>  - \"remote.<name>.prune\" allows to change the behaviour per remote.\n\nShould this be \"remote.<name>.pruneFetch\"?  I'd quite like to be able to\nconfigure --prune for git-push as well (I just haven't got around to\nactually doing anything about it yet...) and it might be better to be\nexplicit in the remote.<name> section from the start.\n\nI'm not sure it's necessary since we already have \"remote\" and\n\"pushremote\" so we could have \"prune\" and \"pushprune\" but perhaps it's\nworth considering.\n"},{"id":"222872","messageId":"20130709035008.GA27903@sigill.intra.peff.net","threadId":"34379","inReplyTo":"1373288217-20580-1-git-send-email-mschub@elegosoft.com","subject":"Re: [RFC/PATCH] fetch: make --prune configurable","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-07-09T03:50:08Z","receivedAt":"2013-07-09T03:50:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 08, 2013 at 02:56:57PM +0200, Michael Schubert wrote:\n\n> $gmane/201715 brought up the idea to fetch --prune by default.\n> Since --prune is a \"potentially destructive operation\" (Git doesn't\n> keep reflogs for deleted references yet), we don't want to prune\n> without users consent.\n> \n> To accommodate users who want to either prune always or when fetching\n> from a particular remote, add two new configuration variables\n> \"fetch.prune\" and \"remote.<name>.prune\":\n> \n>  - \"fetch.prune\" allows to enable prune for all fetch operations.\n> \n>  - \"remote.<name>.prune\" allows to change the behaviour per remote.\n\nThanks. As the person who brought up the destructive nature of --prune\nin the thread you mentioned, I have no problem at all with doing\nsomething like this, where the user gets to make the choice. And it is\neven a good building block if we later do have deleted-branch reflogs;\nwe can just flip the default from \"off\" to \"on\".\n\nIn the meantime, I don't know if it is worth mentioning in the\ndocumentation that the remote branches are hard to get back. On the one\nhand, it is the (or at least a) reason why the default is not \"on\". But\nit is also far from the only place refs get deleted, so I don't know if\nit is worth calling attention to it specifically.\n\n-Peff\n"},{"id":"223228","messageId":"7vppunietl.fsf@alter.siamese.dyndns.org","threadId":"34379","inReplyTo":"7vk3l0zypa.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCH] fetch: make --prune configurable","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-12T22:38:46Z","receivedAt":"2013-07-12T22:38:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Here is my previous review comments in a squashable patch form.  The\nresult seems to pass all 27 combinations (fetch.prune, remote.*.prune\nand command line all are tristate yes/no/unspecified).\n\nWithout the fix-up in *.c files, three combinations seem to fail.\n\n Documentation/config.txt |   3 +-\n builtin/fetch.c          |  41 +++++++++-------\n remote.c                 |   1 +\n t/t5510-fetch.sh         | 118 ++++++++++++++++++++++++++++++++---------------\n 4 files changed, 108 insertions(+), 55 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex fc39f3a..e4ce7c4 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1051,7 +1051,7 @@ fetch.unpackLimit::\n \n fetch.prune::\n \tIf true, fetch will automatically behave as if the `--prune`\n-\toption was given on the command line.\n+\toption was given on the command line.  See also `remote.<name>.prune`.\n \n format.attach::\n \tEnable multipart/mixed attachments as the default for\n@@ -1992,6 +1992,7 @@ remote.<name>.prune::\n \tWhen set to true, fetching from this remote by default will also\n \tremove any remote-tracking branches which no longer exist on the\n \tremote (as if the `--prune` option was give on the command line).\n+\tOverrides `fetch.prune` settings, if any.\n \n remotes.<group>::\n \tThe list of remotes which are fetched by \"git remote update\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 082450b..08ab948 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -30,13 +30,10 @@ enum {\n \tTAGS_SET = 2\n };\n \n-enum {\n-\tPRUNE_UNSET = 0,\n-\tPRUNE_DEFAULT = 1,\n-\tPRUNE_FORCE = 2\n-};\n+static int fetch_prune_config = -1; /* unspecified */\n+static int prune = -1; /* unspecified */\n+#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */\n \n-static int prune = PRUNE_DEFAULT;\n static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;\n static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static int tags = TAGS_DEFAULT, unshallow;\n@@ -64,12 +61,10 @@ static int option_parse_recurse_submodules(const struct option *opt,\n static int git_fetch_config(const char *k, const char *v, void *cb)\n {\n \tif (!strcmp(k, \"fetch.prune\")) {\n-\t\tint boolval = git_config_bool(k, v);\n-\t\tif (boolval)\n-\t\t\tprune = PRUNE_FORCE;\n+\t\tfetch_prune_config = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn 0;\n }\n \n static struct option builtin_fetch_options[] = {\n@@ -87,8 +82,8 @@ static struct option builtin_fetch_options[] = {\n \t\t    N_(\"fetch all tags and associated objects\"), TAGS_SET),\n \tOPT_SET_INT('n', NULL, &tags,\n \t\t    N_(\"do not fetch all tags (--no-tags)\"), TAGS_UNSET),\n-\tOPT_BOOLEAN('p', \"prune\", &prune,\n-\t\t    N_(\"prune remote-tracking branches no longer on remote\")),\n+\tOPT_BOOL('p', \"prune\", &prune,\n+\t\t N_(\"prune remote-tracking branches no longer on remote\")),\n \t{ OPTION_CALLBACK, 0, \"recurse-submodules\", NULL, N_(\"on-demand\"),\n \t\t    N_(\"control recursive fetching of submodules\"),\n \t\t    PARSE_OPT_OPTARG, option_parse_recurse_submodules },\n@@ -756,8 +751,11 @@ static int do_fetch(struct transport *transport,\n \t\tfree_refs(ref_map);\n \t\treturn 1;\n \t}\n-\tif (prune == PRUNE_FORCE || (transport->remote->prune && prune)) {\n-\t\t/* If --tags was specified, pretend the user gave us the canonical tags refspec */\n+\tif (prune) {\n+\t\t/*\n+\t\t * If --tags was specified, pretend that the user gave us\n+\t\t * the canonical tags refspec\n+\t\t */\n \t\tif (tags == TAGS_SET) {\n \t\t\tconst char *tags_str = \"refs/tags/*:refs/tags/*\";\n \t\t\tstruct refspec *tags_refspec, *refspec;\n@@ -866,10 +864,8 @@ static void add_options_to_argv(struct argv_array *argv)\n {\n \tif (dry_run)\n \t\targv_array_push(argv, \"--dry-run\");\n-\tif (prune == PRUNE_FORCE)\n+\tif (prune > 0)\n \t\targv_array_push(argv, \"--prune\");\n-\telse if (prune == PRUNE_UNSET)\n-\t\targv_array_push(argv, \"--no-prune\");\n \tif (update_head_ok)\n \t\targv_array_push(argv, \"--update-head-ok\");\n \tif (force)\n@@ -936,6 +932,17 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)\n \t\t    \"remote name from which new revisions should be fetched.\"));\n \n \ttransport = transport_get(remote, NULL);\n+\n+\tif (prune < 0) {\n+\t\t/* no command line request */\n+\t\tif (0 <= transport->remote->prune)\n+\t\t\tprune = transport->remote->prune;\n+\t\telse if (0 <= fetch_prune_config)\n+\t\t\tprune = fetch_prune_config;\n+\t\telse\n+\t\t\tprune = PRUNE_BY_DEFAULT;\n+\t}\n+\n \ttransport_set_verbosity(transport, verbosity, progress);\n \tif (upload_pack)\n \t\tset_option(TRANS_OPT_UPLOADPACK, upload_pack);\ndiff --git a/remote.c b/remote.c\nindex d0ddbef..89be211 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -148,6 +148,7 @@ static struct remote *make_remote(const char *name, int len)\n \t}\n \n \tret = xcalloc(1, sizeof(struct remote));\n+\tret->prune = -1;  /* unspecified */\n \tALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);\n \tremotes[remotes_nr++] = ret;\n \tif (len)\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 33fe3d5..019535f 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -471,43 +471,87 @@ test_expect_success \"should be able to fetch with duplicate refspecs\" '\n \t)\n '\n \n-test_expect_success 'fetch should prune when fetch.prune is true' '\n-  cd \"$D\" &&\n-  git branch somebranch &&\n-  (\n-    cd one &&\n-    git fetch &&\n-    test -f .git/refs/remotes/origin/somebranch\n-  ) &&\n-  git branch -d somebranch &&\n-  (\n-    cd one &&\n-    git config fetch.prune true &&\n-    git fetch --no-prune &&\n-    test -f .git/refs/remotes/origin/somebranch &&\n-    git fetch &&\n-    ! test -f .git/refs/remotes/origin/somebranch\n-  )\n-'\n-\n-test_expect_success 'fetch should prune when remote.<name>.prune is true' '\n-  cd \"$D\" &&\n-  git branch somebranch &&\n-  (\n-    cd one &&\n-    git fetch &&\n-    test -f .git/refs/remotes/origin/somebranch\n-  ) &&\n-  git branch -d somebranch &&\n-  (\n-    cd one &&\n-    git config remote.origin.prune true &&\n-    git fetch --no-prune &&\n-    test -f .git/refs/remotes/origin/somebranch &&\n-    git fetch &&\n-    ! test -f .git/refs/remotes/origin/somebranch\n-  )\n-'\n+# configured prune tests\n+\n+set_config_tristate () {\n+\t# var=$1 val=$2\n+\tcase \"$2\" in\n+\tunset)  test_unconfig \"$1\" ;;\n+\t*)\tgit config \"$1\" \"$2\" ;;\n+\tesac\n+}\n+\n+test_configured_prune () {\n+\tfetch_prune=$1 remote_origin_prune=$2 cmdline=$3 expected=$4\n+\n+\ttest_expect_success \"prune fetch.prune=$1 remote.origin.prune=$2${3:+ $3}; $4\" '\n+\t\t# make sure a newbranch is there in . and also in one\n+\t\tgit branch -f newbranch &&\n+\t\t(\n+\t\t\tcd one &&\n+\t\t\ttest_unconfig fetch.prune &&\n+\t\t\ttest_unconfig remote.origin.prune &&\n+\t\t\tgit fetch &&\n+\t\t\tgit rev-parse --verify refs/remotes/origin/newbranch\n+\t\t)\n+\n+\t\t# now remove it\n+\t\tgit branch -d newbranch &&\n+\n+\t\t# then test\n+\t\t(\n+\t\t\tcd one &&\n+\t\t\tset_config_tristate fetch.prune $fetch_prune &&\n+\t\t\tset_config_tristate remote.origin.prune $remote_origin_prune &&\n+\n+\t\t\tgit fetch $cmdline &&\n+\t\t\tcase \"$expected\" in\n+\t\t\tpruned)\n+\t\t\t\ttest_must_fail git rev-parse --verify refs/remotes/origin/newbranch\n+\t\t\t\t;;\n+\t\t\tkept)\n+\t\t\t\tgit rev-parse --verify refs/remotes/origin/newbranch\n+\t\t\t\t;;\n+\t\t\tesac\n+\t\t)\n+\t'\n+}\n+\n+test_configured_prune unset unset \"\"\t\tkept\n+test_configured_prune unset unset \"--no-prune\"\tkept\n+test_configured_prune unset unset \"--prune\"\tpruned\n+\n+test_configured_prune false unset \"\"\t\tkept\n+test_configured_prune false unset \"--no-prune\"\tkept\n+test_configured_prune false unset \"--prune\"\tpruned\n+\n+test_configured_prune true  unset \"\"\t\tpruned\n+test_configured_prune true  unset \"--prune\"\tpruned\n+test_configured_prune true  unset \"--no-prune\"\tkept\n+\n+test_configured_prune unset false \"\"\t\tkept\n+test_configured_prune unset false \"--no-prune\"\tkept\n+test_configured_prune unset false \"--prune\"\tpruned\n+\n+test_configured_prune false false \"\"\t\tkept\n+test_configured_prune false false \"--no-prune\"\tkept\n+test_configured_prune false false \"--prune\"\tpruned\n+\n+test_configured_prune true  false \"\"\t\tkept\n+test_configured_prune true  false \"--prune\"\tpruned\n+test_configured_prune true  false \"--no-prune\"\tkept\n+\n+test_configured_prune unset true  \"\"\t\tpruned\n+test_configured_prune unset true  \"--no-prune\"\tkept\n+test_configured_prune unset true  \"--prune\"\tpruned\n+\n+test_configured_prune false true  \"\"\t\tpruned\n+test_configured_prune false true  \"--no-prune\"\tkept\n+test_configured_prune false true  \"--prune\"\tpruned\n+\n+test_configured_prune true  true  \"\"\t\tpruned\n+test_configured_prune true  true  \"--prune\"\tpruned\n+test_configured_prune true  true  \"--no-prune\"\tkept\n \n test_expect_success 'all boundary commits are excluded' '\n \ttest_commit base &&\n-- \n1.8.3.2-941-gda9c3c8\n"}]}