{"thread":{"id":"21493","subject":"[PATCH/RFC 0/3] teach fetch --prune","startedAt":"2009-11-06T05:10:38Z","lastAt":"2009-11-06T07:05:27Z","messageCount":5,"participants":["Jay Soffian","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"126949","messageId":"1257484241-27219-1-git-send-email-jaysoffian@gmail.com","threadId":"21493","inReplyTo":null,"subject":"[PATCH/RFC 0/3] teach fetch --prune","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-06T05:10:38Z","receivedAt":"2009-11-06T05:10:38Z","isPatch":true,"sender":{"key":"jaysoffian@gmail.com","avatar":"https://avatars.githubusercontent.com/u/155970?v=4"},"body":"This is just a start so I can get some feedback. Some things still missing:\n\n1) \"git remote prune <remote>\" calls warn_dangling_symref(), but \n\"git fetch --prune\" does not. I ran out of time tonight to refactor \nwarn_dangling_symref() to do something more intelligent than just spew to\nstdout (which doesn't get along with fetch, which spews to stderr...).\n\n2) Perhaps \"git remote update --prune\" should be refactored to call \n\"git fetch --prune\". If so, then fetch should gain a \"--prune-only\" option\nso that \"git remote prune\" can just call \"got fetch --prune-only\".\n\n3) Perhaps add a config option for users who wish to prune by default.\n\nThoughts, comments, flames?\n\nJay Soffian (3):\n  remote: refactor some logic into get_stale_heads()\n  builtin-fetch: add --dry-run option\n  builtin-fetch: add --prune option\n\n builtin-fetch.c  |   30 +++++++++++++++++++++++++++---\n builtin-remote.c |   32 ++++++++------------------------\n remote.c         |   40 ++++++++++++++++++++++++++++++++++++++++\n remote.h         |    3 +++\n 4 files changed, 78 insertions(+), 27 deletions(-)\n"},{"id":"126951","messageId":"1257484241-27219-2-git-send-email-jaysoffian@gmail.com","threadId":"21493","inReplyTo":"1257484241-27219-1-git-send-email-jaysoffian@gmail.com","subject":"[PATCH/RFC 1/3] remote: refactor some logic into get_stale_heads()","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-06T05:10:39Z","receivedAt":"2009-11-06T05:10:39Z","isPatch":true,"sender":{"key":"jaysoffian@gmail.com","avatar":"https://avatars.githubusercontent.com/u/155970?v=4"},"body":"Move the logic in builtin-remote.c which determines which local heads are stale\nto remote.c so it can be used by other builtins.\n---\n builtin-remote.c |   32 ++++++++------------------------\n remote.c         |   40 ++++++++++++++++++++++++++++++++++++++++\n remote.h         |    3 +++\n 3 files changed, 51 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin-remote.c b/builtin-remote.c\nindex 0777dd7..b48267b 100644\n--- a/builtin-remote.c\n+++ b/builtin-remote.c\n@@ -227,32 +227,10 @@ struct ref_states {\n \tint queried;\n };\n \n-static int handle_one_branch(const char *refname,\n-\tconst unsigned char *sha1, int flags, void *cb_data)\n-{\n-\tstruct ref_states *states = cb_data;\n-\tstruct refspec refspec;\n-\n-\tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n-\tif (!remote_find_tracking(states->remote, &refspec)) {\n-\t\tstruct string_list_item *item;\n-\t\tconst char *name = abbrev_branch(refspec.src);\n-\t\t/* symbolic refs pointing nowhere were handled already */\n-\t\tif ((flags & REF_ISSYMREF) ||\n-\t\t    string_list_has_string(&states->tracked, name) ||\n-\t\t    string_list_has_string(&states->new, name))\n-\t\t\treturn 0;\n-\t\titem = string_list_append(name, &states->stale);\n-\t\titem->util = xstrdup(refname);\n-\t}\n-\treturn 0;\n-}\n-\n static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)\n {\n \tstruct ref *fetch_map = NULL, **tail = &fetch_map;\n-\tstruct ref *ref;\n+\tstruct ref *ref, *stale_refs;\n \tint i;\n \n \tfor (i = 0; i < states->remote->fetch_refspec_nr; i++)\n@@ -268,11 +246,17 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat\n \t\telse\n \t\t\tstring_list_append(abbrev_branch(ref->name), &states->tracked);\n \t}\n+\tstale_refs = get_stale_heads(states->remote, fetch_map);\n+\tfor (ref = stale_refs; ref; ref = ref->next) {\n+\t\tstruct string_list_item *item =\n+\t\t\tstring_list_append(abbrev_branch(ref->name), &states->stale);\n+\t\titem->util = xstrdup(ref->name);\n+\t}\n+\tfree_refs(stale_refs);\n \tfree_refs(fetch_map);\n \n \tsort_string_list(&states->new);\n \tsort_string_list(&states->tracked);\n-\tfor_each_ref(handle_one_branch, states);\n \tsort_string_list(&states->stale);\n \n \treturn 0;\ndiff --git a/remote.c b/remote.c\nindex 73d33f2..ee48b49 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -6,6 +6,7 @@\n #include \"revision.h\"\n #include \"dir.h\"\n #include \"tag.h\"\n+#include \"string-list.h\"\n \n static struct refspec s_tag_refspec = {\n \t0,\n@@ -1586,3 +1587,42 @@ struct ref *guess_remote_head(const struct ref *head,\n \n \treturn list;\n }\n+\n+struct stale_heads_info {\n+\tstruct remote *remote;\n+\tstruct string_list *ref_names;\n+\tstruct ref **stale_refs_tail;\n+};\n+\n+static int get_stale_heads_cb(const char *refname,\n+\tconst unsigned char *sha1, int flags, void *cb_data)\n+{\n+\tstruct stale_heads_info *info = cb_data;\n+\tstruct refspec refspec;\n+\tmemset(&refspec, 0, sizeof(refspec));\n+\trefspec.dst = (char *)refname;\n+\tif (!remote_find_tracking(info->remote, &refspec)) {\n+\t\tif (!((flags & REF_ISSYMREF) ||\n+\t\t    string_list_has_string(info->ref_names, refspec.src))) {\n+\t\t\tstruct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);\n+\t\t\thashcpy(ref->new_sha1, sha1);\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)\n+{\n+\tstruct ref *ref, *stale_refs = NULL;\n+\tstruct string_list ref_names = { NULL, 0, 0, 0 };\n+\tstruct stale_heads_info info;\n+\tinfo.remote = remote;\n+\tinfo.ref_names = &ref_names;\n+\tinfo.stale_refs_tail = &stale_refs;\n+\tfor (ref = fetch_map; ref; ref = ref->next)\n+\t\tstring_list_append(ref->name, &ref_names);\n+\tsort_string_list(&ref_names);\n+\tfor_each_ref(get_stale_heads_cb, &info);\n+\tstring_list_clear(&ref_names, 0);\n+\treturn stale_refs;\n+}\ndiff --git a/remote.h b/remote.h\nindex 5db8420..d0aba81 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -154,4 +154,7 @@ struct ref *guess_remote_head(const struct ref *head,\n \t\t\t      const struct ref *refs,\n \t\t\t      int all);\n \n+/* Return refs which no longer exist on remote */\n+struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map);\n+\n #endif\n-- \n1.6.4.2\n"},{"id":"126952","messageId":"1257484241-27219-3-git-send-email-jaysoffian@gmail.com","threadId":"21493","inReplyTo":"1257484241-27219-2-git-send-email-jaysoffian@gmail.com","subject":"[PATCH/RFC 2/3] builtin-fetch: add --dry-run option","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-06T05:10:40Z","receivedAt":"2009-11-06T05:10:40Z","isPatch":true,"sender":{"key":"jaysoffian@gmail.com","avatar":"https://avatars.githubusercontent.com/u/155970?v=4"},"body":"Teach fetch --dry-run. Unfortunately OPT__DRY_RUN() cannot be used as fetch\nalready uses \"-n\" for something else.\n---\n builtin-fetch.c |   10 +++++++---\n 1 files changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex cb48c57..985b36b 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -23,7 +23,7 @@ enum {\n \tTAGS_SET = 2\n };\n \n-static int append, force, keep, update_head_ok, verbosity;\n+static int append, dry_run, force, keep, update_head_ok, verbosity;\n static int tags = TAGS_DEFAULT;\n static const char *depth;\n static const char *upload_pack;\n@@ -42,6 +42,8 @@ static struct option builtin_fetch_options[] = {\n \t\t    \"fetch all tags and associated objects\", TAGS_SET),\n \tOPT_SET_INT('n', NULL, &tags,\n \t\t    \"do not fetch all tags (--no-tags)\", TAGS_UNSET),\n+\tOPT_BOOLEAN(0, \"dry-run\", &dry_run,\n+\t\t    \"dry run\"),\n \tOPT_BOOLEAN('k', \"keep\", &keep, \"keep downloaded pack\"),\n \tOPT_BOOLEAN('u', \"update-head-ok\", &update_head_ok,\n \t\t    \"allow updating of HEAD ref\"),\n@@ -178,6 +180,8 @@ static int s_update_ref(const char *action,\n \tchar *rla = getenv(\"GIT_REFLOG_ACTION\");\n \tstatic struct ref_lock *lock;\n \n+\tif (dry_run)\n+\t\treturn 0;\n \tif (!rla)\n \t\trla = default_rla.buf;\n \tsnprintf(msg, sizeof(msg), \"%s: %s\", rla, action);\n@@ -303,7 +307,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,\n \tchar note[1024];\n \tconst char *what, *kind;\n \tstruct ref *rm;\n-\tchar *url, *filename = git_path(\"FETCH_HEAD\");\n+\tchar *url, *filename = dry_run ? \"/dev/null\" : git_path(\"FETCH_HEAD\");\n \n \tfp = fopen(filename, \"a\");\n \tif (!fp)\n@@ -586,7 +590,7 @@ static int do_fetch(struct transport *transport,\n \t\tdie(\"Don't know how to fetch from %s\", transport->url);\n \n \t/* if not appending, truncate FETCH_HEAD */\n-\tif (!append) {\n+\tif (!append && !dry_run) {\n \t\tchar *filename = git_path(\"FETCH_HEAD\");\n \t\tFILE *fp = fopen(filename, \"w\");\n \t\tif (!fp)\n-- \n1.6.4.2\n"},{"id":"126950","messageId":"1257484241-27219-4-git-send-email-jaysoffian@gmail.com","threadId":"21493","inReplyTo":"1257484241-27219-3-git-send-email-jaysoffian@gmail.com","subject":"[PATCH/RFC 3/3] builtin-fetch: add --prune option","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-06T05:10:41Z","receivedAt":"2009-11-06T05:10:41Z","isPatch":true,"sender":{"key":"jaysoffian@gmail.com","avatar":"https://avatars.githubusercontent.com/u/155970?v=4"},"body":"Teach fetch --prune, as an alternative to git remote prune.\n---\n builtin-fetch.c |   22 +++++++++++++++++++++-\n 1 files changed, 21 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex 985b36b..e8a5b9b 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -23,7 +23,7 @@ enum {\n \tTAGS_SET = 2\n };\n \n-static int append, dry_run, force, keep, update_head_ok, verbosity;\n+static int append, dry_run, force, keep, prune, update_head_ok, verbosity;\n static int tags = TAGS_DEFAULT;\n static const char *depth;\n static const char *upload_pack;\n@@ -42,6 +42,8 @@ static struct option builtin_fetch_options[] = {\n \t\t    \"fetch all tags and associated objects\", TAGS_SET),\n \tOPT_SET_INT('n', NULL, &tags,\n \t\t    \"do not fetch all tags (--no-tags)\", TAGS_UNSET),\n+\tOPT_BOOLEAN('p', \"prune\", &prune,\n+\t\t    \"prune tracking branches no longer on remote\"),\n \tOPT_BOOLEAN(0, \"dry-run\", &dry_run,\n \t\t    \"dry run\"),\n \tOPT_BOOLEAN('k', \"keep\", &keep, \"keep downloaded pack\"),\n@@ -489,6 +491,22 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)\n \treturn ret;\n }\n \n+static int prune_refs(struct transport *transport, struct ref *ref_map)\n+{\n+\tint result = 0;\n+\tstruct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);\n+\tfor (ref = stale_refs; ref; ref = ref->next) {\n+\t\tif (!dry_run)\n+\t\t\tresult |= delete_ref(ref->name, NULL, 0);\n+\t\tif (verbosity >= 0)\n+\t\t\tfprintf(stderr, \" x %-*s %-*s -> %s\\n\",\n+\t\t\t\tSUMMARY_WIDTH, \"[deleted]\",\n+\t\t\t\tREFCOL_WIDTH, \"(none)\", prettify_refname(ref->name));\n+\t}\n+\tfree_refs(stale_refs);\n+\treturn 0;\n+}\n+\n static int add_existing(const char *refname, const unsigned char *sha1,\n \t\t\tint flag, void *cbdata)\n {\n@@ -613,6 +631,8 @@ static int do_fetch(struct transport *transport,\n \t\tfree_refs(ref_map);\n \t\treturn 1;\n \t}\n+\tif (prune)\n+\t\tprune_refs(transport, ref_map);\n \tfree_refs(ref_map);\n \n \t/* if neither --no-tags nor --tags was specified, do automated tag\n-- \n1.6.4.2\n"},{"id":"126954","messageId":"7vhbt83zgo.fsf@alter.siamese.dyndns.org","threadId":"21493","inReplyTo":"1257484241-27219-1-git-send-email-jaysoffian@gmail.com","subject":"Re: [PATCH/RFC 0/3] teach fetch --prune","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-11-06T07:05:27Z","receivedAt":"2009-11-06T07:05:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jay Soffian <jaysoffian@gmail.com> writes:\n\n> This is just a start so I can get some feedback. Some things still missing:\n>\n> 1) \"git remote prune <remote>\" calls warn_dangling_symref(), but \n> \"git fetch --prune\" does not. I ran out of time tonight to refactor \n> warn_dangling_symref() to do something more intelligent than just spew to\n> stdout (which doesn't get along with fetch, which spews to stderr...).\n>\n> 2) Perhaps \"git remote update --prune\" should be refactored to call \n> \"git fetch --prune\". If so, then fetch should gain a \"--prune-only\" option\n> so that \"git remote prune\" can just call \"got fetch --prune-only\".\n>\n> 3) Perhaps add a config option for users who wish to prune by default.\n>\n> Thoughts, comments, flames?\n\nI usually refrain from talking about multi-year long term plans, because I\ndo not have one, but in a longer term (across 1.7.0 boundary and beyond),\nthe general direction would be:\n\n - \"fetch\" will eventually prune by default; I expect we will have a\n   configuration option \"fetch.autoprune = yes\" to allow early adopters to\n   let it prune until 1.7.0, and in 1.7.0 we will change the default for\n   the configuration variable to \"yes\", i.e. we prune unless the user\n   explicitly declines with \"fetch.autoprune = no\".\n\n - In general, \"remote\" should go back to its roots of being the\n   management interface to [remote \"nick\"] configuration section.  We\n   should start planning to remove extra features that were piled on top\n   of the original \"remote definition management tool\".  \"update/prune\"\n   should have been the duty of \"fetch\" in the first place, but they were\n   added to \"remote\" primarily because it was easier to do so (\"remote\"\n   used to be script but \"fetch\" was already written in \"C\").\n\n   This means two things:\n\n   * \"remote prune\" that only prunes without updating remaining tracking\n     will be largely become unnecessary [*1*], once we have a way to tell\n     \"fetch\" to prune at the same time.  As soon as \"fetch --prune\"\n     becomes available, \"remote prune\" should become an alias to it.  And\n     \"remote prune\" itself should eventually be removed.\n\n   * \"remote update\" that runs \"fetch\" for multiple remotes should be\n     deprecated and eventually removed.  \"remote update\" is a band-aid\n     that exists only because \"fetch\" started as a strange chimera between\n     plumbing and Porcelain, and we did not want to add too much features\n     to it.  \"fetch\" itself should learn to do the \"from multiple places\"\n     part as a full-fledged Porcelain.\n\nOf course, a removal of a subcommand (\"remote update\" and \"remote prune\")\nwill have to happen way after 1.7.0 boundary, but the above should be the\nlonger term direction.\n\nDon't worry about keeping the \"only prune without updating\" misfeature. If\nomission of it simplifies what you are trying to do, it is Ok if \"git\nprune\" becomes a synonym to \"git update --prune\" aka \"git fetch --prune\"\nand starts updating the tracking refs.\n\n[Footnote]\n\n*1* \"remote prune\" is a band-aid that exists only because \"fetch\"\ncurrently has no-way to prune at the same time.\n\nOne could argue that a user may want to prune _only_ stale refs without\ngetting the state of remaining refs updated to his tracking refs.  That\ncertainly is _possible_ with \"remote prune\", but being possible and being\nsensible are two different things.  \n\nWhat is the reason the user does not want to update a tracking ref that\ncorresponds to a branch that remains at the remote, when he runs \"prune\nonly\" version if it existed?  It cannot be because the branch may have\nbeen rewound and you may lose the only remaining history---the user is\nactively asking to prune and is willing to lose the history of stale\nbranches.\n\nIn other words, once we have \"fetch --prune\" (either default or\noptional---the important part is that there is a way to cause fetch to do\nthis), there is no sane reason to have a separate command that only prunes\nwithout updating the remaining tracking refs.\n"}]}