{"thread":{"id":"21556","subject":"[PATCH 0/4] Teach fetch --prune and --dry run options","startedAt":"2009-11-10T05:03:30Z","lastAt":"2009-11-10T05:03:34Z","messageCount":5,"participants":["Jay Soffian"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"127209","messageId":"1257829414-52316-1-git-send-email-jaysoffian@gmail.com","threadId":"21556","inReplyTo":null,"subject":"[PATCH 0/4] Teach fetch --prune and --dry run options","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-10T05:03:30Z","receivedAt":"2009-11-10T05:03:30Z","isPatch":true,"sender":{"key":"jaysoffian@gmail.com","avatar":"https://avatars.githubusercontent.com/u/155970?v=4"},"body":"(Darn it, I left the SOB off. This is a resend of the series with SOB. Sorry\nfor the noise...)\n\nThis series builds on bg/fetch-multi, by teaching fetch --prune, re-enabling\n\"remote update --prune\" and ensuring its corresponding test now passes.\n\nAs a convenience to users used to \"remote prune\" supporting the \"--dry-run\"\noption, we also teach this option to fetch since it's trivial to do so.\n\nI've left out re-implementing \"remote prune\" as a synonym for \"update --prune\"\nsince it's not a 1:1 match. And as per Junio's comments, teaching fetch a \n\"--prune-only\" option would be a UI wart.\n\nJay Soffian (4):\n  remote: refactor some logic into get_stale_heads()\n  teach warn_dangling_symref to take a FILE argument\n  builtin-fetch: add --prune option\n  builtin-fetch: add --dry-run option\n\n Documentation/fetch-options.txt |    9 ++++++++\n builtin-fetch.c                 |   42 +++++++++++++++++++++++++++++++++++---\n builtin-remote.c                |   38 +++++++++++------------------------\n refs.c                          |    7 +++--\n refs.h                          |    2 +-\n remote.c                        |   40 +++++++++++++++++++++++++++++++++++++\n remote.h                        |    3 ++\n t/t5505-remote.sh               |    2 +-\n 8 files changed, 108 insertions(+), 35 deletions(-)\n"},{"id":"127210","messageId":"1257829414-52316-2-git-send-email-jaysoffian@gmail.com","threadId":"21556","inReplyTo":"1257829414-52316-1-git-send-email-jaysoffian@gmail.com","subject":"[PATCH 1/4] remote: refactor some logic into get_stale_heads()","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-10T05:03:31Z","receivedAt":"2009-11-10T05:03:31Z","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\nSigned-off-by: Jay Soffian <jaysoffian@gmail.com>\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 fd7e0b2..0ea4e60 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 beaf9fb..eae5866 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@@ -1587,3 +1588,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":"127213","messageId":"1257829414-52316-3-git-send-email-jaysoffian@gmail.com","threadId":"21556","inReplyTo":"1257829414-52316-2-git-send-email-jaysoffian@gmail.com","subject":"[PATCH 2/4] teach warn_dangling_symref to take a FILE argument","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-10T05:03:32Z","receivedAt":"2009-11-10T05:03:32Z","isPatch":true,"sender":{"key":"jaysoffian@gmail.com","avatar":"https://avatars.githubusercontent.com/u/155970?v=4"},"body":"Different callers of warn_dangling_symref() may want to control whether its\noutput goes to stdout or stderr so let it take a FILE argument.\n\nSigned-off-by: Jay Soffian <jaysoffian@gmail.com>\n---\n builtin-remote.c |    2 +-\n refs.c           |    7 ++++---\n refs.h           |    2 +-\n 3 files changed, 6 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin-remote.c b/builtin-remote.c\nindex 0ea4e60..e67b89f 100644\n--- a/builtin-remote.c\n+++ b/builtin-remote.c\n@@ -1166,7 +1166,7 @@ static int prune_remote(const char *remote, int dry_run)\n \n \t\tprintf(\" * [%s] %s\\n\", dry_run ? \"would prune\" : \"pruned\",\n \t\t       abbrev_ref(refname, \"refs/remotes/\"));\n-\t\twarn_dangling_symref(dangling_msg, refname);\n+\t\twarn_dangling_symref(stdout, dangling_msg, refname);\n \t}\n \n \tfree_remote_ref_states(&states);\ndiff --git a/refs.c b/refs.c\nindex 808f56b..3e73a0a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -286,6 +286,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)\n }\n \n struct warn_if_dangling_data {\n+\tFILE *fp;\n \tconst char *refname;\n \tconst char *msg_fmt;\n };\n@@ -304,13 +305,13 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha\n \tif (!resolves_to || strcmp(resolves_to, d->refname))\n \t\treturn 0;\n \n-\tprintf(d->msg_fmt, refname);\n+\tfprintf(d->fp, d->msg_fmt, refname);\n \treturn 0;\n }\n \n-void warn_dangling_symref(const char *msg_fmt, const char *refname)\n+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)\n {\n-\tstruct warn_if_dangling_data data = { refname, msg_fmt };\n+\tstruct warn_if_dangling_data data = { fp, refname, msg_fmt };\n \tfor_each_rawref(warn_if_dangling_symref, &data);\n }\n \ndiff --git a/refs.h b/refs.h\nindex 777b5b7..e141991 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -29,7 +29,7 @@ extern int for_each_replace_ref(each_ref_fn, void *);\n /* can be used to learn about broken ref and symref */\n extern int for_each_rawref(each_ref_fn, void *);\n \n-extern void warn_dangling_symref(const char *msg_fmt, const char *refname);\n+extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);\n \n /*\n  * Extra refs will be listed by for_each_ref() before any actual refs\n-- \n1.6.4.2\n"},{"id":"127212","messageId":"1257829414-52316-4-git-send-email-jaysoffian@gmail.com","threadId":"21556","inReplyTo":"1257829414-52316-3-git-send-email-jaysoffian@gmail.com","subject":"[PATCH 3/4] builtin-fetch: add --prune option","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-10T05:03:33Z","receivedAt":"2009-11-10T05:03:33Z","isPatch":true,"sender":{"key":"jaysoffian@gmail.com","avatar":"https://avatars.githubusercontent.com/u/155970?v=4"},"body":"Teach fetch to cull stale remote tracking branches after fetching via --prune.\n\nWe can now enable the \"--prune\" option to \"remote update\", which was recently\nre-implemented as an invocation of git fetch.\n\nSigned-off-by: Jay Soffian <jaysoffian@gmail.com>\n---\n Documentation/fetch-options.txt |    4 ++++\n builtin-fetch.c                 |   32 ++++++++++++++++++++++++++++++--\n builtin-remote.c                |    4 +++-\n t/t5505-remote.sh               |    2 +-\n 4 files changed, 38 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt\nindex 8b0cf58..500637a 100644\n--- a/Documentation/fetch-options.txt\n+++ b/Documentation/fetch-options.txt\n@@ -28,6 +28,10 @@ ifndef::git-pull[]\n --multiple::\n \tAllow several <repository> and <group> arguments to be\n \tspecified. No <refspec>s may be specified.\n+\n+--prune::\n+\tAfter fetching, remove any remote tracking branches which\n+\tno longer exist\ton the remote.\n endif::git-pull[]\n \n ifdef::git-pull[]\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex 945dfd8..fd31072 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -26,7 +26,7 @@ enum {\n \tTAGS_SET = 2\n };\n \n-static int all, append, force, keep, multiple, update_head_ok, verbosity;\n+static int all, append, force, keep, multiple, prune, update_head_ok, verbosity;\n static int tags = TAGS_DEFAULT;\n static const char *depth;\n static const char *upload_pack;\n@@ -49,6 +49,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('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@@ -492,6 +494,28 @@ 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+\tconst char *dangling_msg = dry_run\n+\t\t? \"   (%s will become dangling)\\n\"\n+\t\t: \"   (%s has become dangling)\\n\";\n+\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\t\twarn_dangling_symref(stderr, dangling_msg, ref->name);\n+\t\t}\n+\t}\n+\tfree_refs(stale_refs);\n+\treturn result;\n+}\n+\n static int add_existing(const char *refname, const unsigned char *sha1,\n \t\t\tint flag, void *cbdata)\n {\n@@ -657,6 +681,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@@ -740,9 +766,11 @@ static int add_remote_or_group(const char *name, struct string_list *list)\n static int fetch_multiple(struct string_list *list)\n {\n \tint i, result = 0;\n-\tconst char *argv[] = { \"fetch\", NULL, NULL, NULL, NULL };\n+\tconst char *argv[] = { \"fetch\", NULL, NULL, NULL, NULL, NULL };\n \tint argc = 1;\n \n+\tif (prune)\n+\t\targv[argc++] = \"--prune\";\n \tif (verbosity >= 2)\n \t\targv[argc++] = \"-v\";\n \tif (verbosity >= 1)\ndiff --git a/builtin-remote.c b/builtin-remote.c\nindex e67b89f..fb0d66d 100644\n--- a/builtin-remote.c\n+++ b/builtin-remote.c\n@@ -1195,13 +1195,15 @@ static int update(int argc, const char **argv)\n \tint fetch_argc = 0;\n \tint default_defined = 0;\n \n-\tfetch_argv = xmalloc(sizeof(char *) * (argc+4));\n+\tfetch_argv = xmalloc(sizeof(char *) * (argc+5));\n \n \targc = parse_options(argc, argv, NULL, options, builtin_remote_usage,\n \t\t\t     PARSE_OPT_KEEP_ARGV0);\n \n \tfetch_argv[fetch_argc++] = \"fetch\";\n \n+\tif (prune)\n+\t\tfetch_argv[fetch_argc++] = \"--prune\";\n \tif (verbose)\n \t\tfetch_argv[fetch_argc++] = \"-v\";\n \tif (argc < 2) {\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 562cca2..e931ce6 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -365,7 +365,7 @@ test_expect_success 'update with arguments' '\n \n '\n \n-test_expect_failure 'update --prune' '\n+test_expect_success 'update --prune' '\n \n \t(cd one &&\n \t git branch -m side2 side3) &&\n-- \n1.6.4.2\n"},{"id":"127211","messageId":"1257829414-52316-5-git-send-email-jaysoffian@gmail.com","threadId":"21556","inReplyTo":"1257829414-52316-4-git-send-email-jaysoffian@gmail.com","subject":"[PATCH 4/4] builtin-fetch: add --dry-run option","fromName":"Jay Soffian","fromEmail":"jaysoffian@gmail.com","sentAt":"2009-11-10T05:03:34Z","receivedAt":"2009-11-10T05:03:34Z","isPatch":true,"sender":{"key":"jaysoffian@gmail.com","avatar":"https://avatars.githubusercontent.com/u/155970?v=4"},"body":"Teach fetch --dry-run as users of \"git remote prune\" switching to \"git fetch\n--prune\" may expect it. Unfortunately OPT__DRY_RUN() cannot be used as fetch\nalready uses \"-n\" for something else.\n\nSigned-off-by: Jay Soffian <jaysoffian@gmail.com>\n---\n Documentation/fetch-options.txt |    5 +++++\n builtin-fetch.c                 |   14 ++++++++++----\n 2 files changed, 15 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt\nindex 500637a..ab6419f 100644\n--- a/Documentation/fetch-options.txt\n+++ b/Documentation/fetch-options.txt\n@@ -12,6 +12,11 @@\n \t`git clone` with `--depth=<depth>` option (see linkgit:git-clone[1])\n \tby the specified number of commits.\n \n+ifndef::git-pull[]\n+--dry-run::\n+\tShow what would be done, without making any changes.\n+endif::git-pull[]\n+\n -f::\n --force::\n \tWhen 'git-fetch' is used with `<rbranch>:<lbranch>`\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex fd31072..8082d36 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -26,7 +26,7 @@ enum {\n \tTAGS_SET = 2\n };\n \n-static int all, append, force, keep, multiple, prune, update_head_ok, verbosity;\n+static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;\n static int tags = TAGS_DEFAULT;\n static const char *depth;\n static const char *upload_pack;\n@@ -51,6 +51,8 @@ static struct option builtin_fetch_options[] = {\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 \tOPT_BOOLEAN('u', \"update-head-ok\", &update_head_ok,\n \t\t    \"allow updating of HEAD ref\"),\n@@ -187,6 +189,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@@ -312,7 +316,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@@ -658,7 +662,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@@ -766,9 +770,11 @@ static int add_remote_or_group(const char *name, struct string_list *list)\n static int fetch_multiple(struct string_list *list)\n {\n \tint i, result = 0;\n-\tconst char *argv[] = { \"fetch\", NULL, NULL, NULL, NULL, NULL };\n+\tconst char *argv[] = { \"fetch\", NULL, NULL, NULL, NULL, NULL, NULL };\n \tint argc = 1;\n \n+\tif (dry_run)\n+\t\targv[argc++] = \"--dry-run\";\n \tif (prune)\n \t\targv[argc++] = \"--prune\";\n \tif (verbosity >= 2)\n-- \n1.6.4.2\n"}]}