{"thread":{"id":"34427","subject":"[PATCH v2] fetch: make --prune configurable","startedAt":"2013-07-13T09:36:24Z","lastAt":"2013-07-13T09:36:24Z","messageCount":1,"participants":["Michael Schubert"],"isPatch":true,"patchVersion":2,"patchTotal":null},"messages":[{"id":"223243","messageId":"1373708184-22672-1-git-send-email-mschub@elegosoft.com","threadId":"34427","inReplyTo":null,"subject":"[PATCH v2] fetch: make --prune configurable","fromName":"Michael Schubert","fromEmail":"mschub@elegosoft.com","sentAt":"2013-07-13T09:36:24Z","receivedAt":"2013-07-13T09:36:24Z","isPatch":true,"sender":{"key":"mschub@elegosoft.com","avatar":null},"body":"Without \"git fetch --prune\", remote-tracking branches for a branch\nthe other side already has removed will stay forever.  Some people\nwant to always run \"git fetch --prune\".\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\nThe latter will naturally override the former, and the --[no-]prune\noption from the command line will override the configured default.\n\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, so this configuration will not be on by\ndefault.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Michael Schubert <mschub@elegosoft.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\nJunio, thank you for your detailed feedback and writing the patch.\n\nI didn't find time during the week to write a v2 and I don't feel I\nshould take any credit here, so please feel free to take authorship\ncompletely.\n\nThanks.\n\n\n Documentation/config.txt | 10 ++++++\n builtin/fetch.c          | 39 ++++++++++++++++++++---\n remote.c                 |  3 ++\n remote.h                 |  1 +\n t/t5510-fetch.sh         | 82 ++++++++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 130 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6e53fc5..e4ce7c4 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1049,6 +1049,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.  See also `remote.<name>.prune`.\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@@ -1984,6 +1988,12 @@ 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+\tOverrides `fetch.prune` settings, if any.\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 4b6b1df..08ab948 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -30,7 +30,11 @@ enum {\n \tTAGS_SET = 2\n };\n \n-static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;\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 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 +58,15 @@ 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\tfetch_prune_config = git_config_bool(k, v);\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n static struct option builtin_fetch_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOLEAN(0, \"all\", &all,\n@@ -69,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@@ -739,7 +752,10 @@ static int do_fetch(struct transport *transport,\n \t\treturn 1;\n \t}\n \tif (prune) {\n-\t\t/* If --tags was specified, pretend the user gave us the canonical tags refspec */\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@@ -848,7 +864,7 @@ 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 > 0)\n \t\targv_array_push(argv, \"--prune\");\n \tif (update_head_ok)\n \t\targv_array_push(argv, \"--update-head-ok\");\n@@ -916,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);\n@@ -973,6 +1000,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 68eb99b..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)\n@@ -419,6 +420,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 d7a19a1..019535f 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -471,6 +471,88 @@ test_expect_success \"should be able to fetch with duplicate refspecs\" '\n \t)\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 \ttest_commit oneside &&\n-- \n1.8.3.1.g243ed41\n"}]}