fetch --prune: Run prune before fetching
[gitweb.git] / builtin / fetch.c
index 564705555b897bde0a1a2c3fad3c1f1b39903b53..09825c84d731014e075a6f77791f0681b7c4efb0 100644 (file)
@@ -30,7 +30,11 @@ enum {
        TAGS_SET = 2
 };
 
-static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
+static int fetch_prune_config = -1; /* unspecified */
+static int prune = -1; /* unspecified */
+#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
+
+static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
 static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
 static int tags = TAGS_DEFAULT, unshallow;
 static const char *depth;
@@ -40,6 +44,7 @@ static struct transport *gtransport;
 static struct transport *gsecondary;
 static const char *submodule_prefix = "";
 static const char *recurse_submodules_default;
+static int shown_url = 0;
 
 static int option_parse_recurse_submodules(const struct option *opt,
                                   const char *arg, int unset)
@@ -55,30 +60,39 @@ static int option_parse_recurse_submodules(const struct option *opt,
        return 0;
 }
 
+static int git_fetch_config(const char *k, const char *v, void *cb)
+{
+       if (!strcmp(k, "fetch.prune")) {
+               fetch_prune_config = git_config_bool(k, v);
+               return 0;
+       }
+       return 0;
+}
+
 static struct option builtin_fetch_options[] = {
        OPT__VERBOSITY(&verbosity),
-       OPT_BOOLEAN(0, "all", &all,
-                   N_("fetch from all remotes")),
-       OPT_BOOLEAN('a', "append", &append,
-                   N_("append to .git/FETCH_HEAD instead of overwriting")),
+       OPT_BOOL(0, "all", &all,
+                N_("fetch from all remotes")),
+       OPT_BOOL('a', "append", &append,
+                N_("append to .git/FETCH_HEAD instead of overwriting")),
        OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
                   N_("path to upload pack on remote end")),
        OPT__FORCE(&force, N_("force overwrite of local branch")),
-       OPT_BOOLEAN('m', "multiple", &multiple,
-                   N_("fetch from multiple remotes")),
+       OPT_BOOL('m', "multiple", &multiple,
+                N_("fetch from multiple remotes")),
        OPT_SET_INT('t', "tags", &tags,
                    N_("fetch all tags and associated objects"), TAGS_SET),
        OPT_SET_INT('n', NULL, &tags,
                    N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
-       OPT_BOOLEAN('p', "prune", &prune,
-                   N_("prune remote-tracking branches no longer on remote")),
+       OPT_BOOL('p', "prune", &prune,
+                N_("prune remote-tracking branches no longer on remote")),
        { OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"),
                    N_("control recursive fetching of submodules"),
                    PARSE_OPT_OPTARG, option_parse_recurse_submodules },
-       OPT_BOOLEAN(0, "dry-run", &dry_run,
-                   N_("dry run")),
-       OPT_BOOLEAN('k', "keep", &keep, N_("keep downloaded pack")),
-       OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+       OPT_BOOL(0, "dry-run", &dry_run,
+                N_("dry run")),
+       OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")),
+       OPT_BOOL('u', "update-head-ok", &update_head_ok,
                    N_("allow updating of HEAD ref")),
        OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
        OPT_STRING(0, "depth", &depth, N_("depth"),
@@ -147,48 +161,156 @@ static void add_merge_config(struct ref **head,
        }
 }
 
+static int add_existing(const char *refname, const unsigned char *sha1,
+                       int flag, void *cbdata)
+{
+       struct string_list *list = (struct string_list *)cbdata;
+       struct string_list_item *item = string_list_insert(list, refname);
+       item->util = xmalloc(20);
+       hashcpy(item->util, sha1);
+       return 0;
+}
+
+static int will_fetch(struct ref **head, const unsigned char *sha1)
+{
+       struct ref *rm = *head;
+       while (rm) {
+               if (!hashcmp(rm->old_sha1, sha1))
+                       return 1;
+               rm = rm->next;
+       }
+       return 0;
+}
+
 static void find_non_local_tags(struct transport *transport,
                        struct ref **head,
-                       struct ref ***tail);
+                       struct ref ***tail)
+{
+       struct string_list existing_refs = STRING_LIST_INIT_DUP;
+       struct string_list remote_refs = STRING_LIST_INIT_NODUP;
+       const struct ref *ref;
+       struct string_list_item *item = NULL;
+
+       for_each_ref(add_existing, &existing_refs);
+       for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
+               if (!starts_with(ref->name, "refs/tags/"))
+                       continue;
+
+               /*
+                * The peeled ref always follows the matching base
+                * ref, so if we see a peeled ref that we don't want
+                * to fetch then we can mark the ref entry in the list
+                * as one to ignore by setting util to NULL.
+                */
+               if (ends_with(ref->name, "^{}")) {
+                       if (item && !has_sha1_file(ref->old_sha1) &&
+                           !will_fetch(head, ref->old_sha1) &&
+                           !has_sha1_file(item->util) &&
+                           !will_fetch(head, item->util))
+                               item->util = NULL;
+                       item = NULL;
+                       continue;
+               }
+
+               /*
+                * If item is non-NULL here, then we previously saw a
+                * ref not followed by a peeled reference, so we need
+                * to check if it is a lightweight tag that we want to
+                * fetch.
+                */
+               if (item && !has_sha1_file(item->util) &&
+                   !will_fetch(head, item->util))
+                       item->util = NULL;
+
+               item = NULL;
+
+               /* skip duplicates and refs that we already have */
+               if (string_list_has_string(&remote_refs, ref->name) ||
+                   string_list_has_string(&existing_refs, ref->name))
+                       continue;
+
+               item = string_list_insert(&remote_refs, ref->name);
+               item->util = (void *)ref->old_sha1;
+       }
+       string_list_clear(&existing_refs, 1);
+
+       /*
+        * We may have a final lightweight tag that needs to be
+        * checked to see if it needs fetching.
+        */
+       if (item && !has_sha1_file(item->util) &&
+           !will_fetch(head, item->util))
+               item->util = NULL;
+
+       /*
+        * For all the tags in the remote_refs string list,
+        * add them to the list of refs to be fetched
+        */
+       for_each_string_list_item(item, &remote_refs) {
+               /* Unless we have already decided to ignore this item... */
+               if (item->util)
+               {
+                       struct ref *rm = alloc_ref(item->string);
+                       rm->peer_ref = alloc_ref(item->string);
+                       hashcpy(rm->old_sha1, item->util);
+                       **tail = rm;
+                       *tail = &rm->next;
+               }
+       }
+
+       string_list_clear(&remote_refs, 0);
+}
 
 static struct ref *get_ref_map(struct transport *transport,
-                              struct refspec *refs, int ref_count, int tags,
-                              int *autotags)
+                              struct refspec *refspecs, int refspec_count,
+                              int tags, int *autotags)
 {
        int i;
        struct ref *rm;
        struct ref *ref_map = NULL;
        struct ref **tail = &ref_map;
 
-       const struct ref *remote_refs = transport_get_remote_refs(transport);
+       /* opportunistically-updated references: */
+       struct ref *orefs = NULL, **oref_tail = &orefs;
 
-       if (ref_count || tags == TAGS_SET) {
-               struct ref **old_tail;
+       const struct ref *remote_refs = transport_get_remote_refs(transport);
 
-               for (i = 0; i < ref_count; i++) {
-                       get_fetch_map(remote_refs, &refs[i], &tail, 0);
-                       if (refs[i].dst && refs[i].dst[0])
+       if (refspec_count) {
+               for (i = 0; i < refspec_count; i++) {
+                       get_fetch_map(remote_refs, &refspecs[i], &tail, 0);
+                       if (refspecs[i].dst && refspecs[i].dst[0])
                                *autotags = 1;
                }
-               /* Merge everything on the command line, but not --tags */
+               /* Merge everything on the command line (but not --tags) */
                for (rm = ref_map; rm; rm = rm->next)
                        rm->fetch_head_status = FETCH_HEAD_MERGE;
-               if (tags == TAGS_SET)
-                       get_fetch_map(remote_refs, tag_refspec, &tail, 0);
 
                /*
-                * For any refs that we happen to be fetching via command-line
-                * arguments, take the opportunity to update their configured
-                * counterparts. However, we do not want to mention these
-                * entries in FETCH_HEAD at all, as they would simply be
-                * duplicates of existing entries.
+                * For any refs that we happen to be fetching via
+                * command-line arguments, the destination ref might
+                * have been missing or have been different than the
+                * remote-tracking ref that would be derived from the
+                * configured refspec.  In these cases, we want to
+                * take the opportunity to update their configured
+                * remote-tracking reference.  However, we do not want
+                * to mention these entries in FETCH_HEAD at all, as
+                * they would simply be duplicates of existing
+                * entries, so we set them FETCH_HEAD_IGNORE below.
+                *
+                * We compute these entries now, based only on the
+                * refspecs specified on the command line.  But we add
+                * them to the list following the refspecs resulting
+                * from the tags option so that one of the latter,
+                * which has FETCH_HEAD_NOT_FOR_MERGE, is not removed
+                * by ref_remove_duplicates() in favor of one of these
+                * opportunistic entries with FETCH_HEAD_IGNORE.
                 */
-               old_tail = tail;
                for (i = 0; i < transport->remote->fetch_refspec_nr; i++)
                        get_fetch_map(ref_map, &transport->remote->fetch[i],
-                                     &tail, 1);
-               for (rm = *old_tail; rm; rm = rm->next)
-                       rm->fetch_head_status = FETCH_HEAD_IGNORE;
+                                     &oref_tail, 1);
+
+               if (tags == TAGS_SET)
+                       get_fetch_map(remote_refs, tag_refspec, &tail, 0);
        } else {
                /* Use the defaults */
                struct remote *remote = transport->remote;
@@ -225,11 +347,21 @@ static struct ref *get_ref_map(struct transport *transport,
                        tail = &ref_map->next;
                }
        }
-       if (tags == TAGS_DEFAULT && *autotags)
+
+       if (tags == TAGS_SET)
+               /* also fetch all tags */
+               get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+       else if (tags == TAGS_DEFAULT && *autotags)
                find_non_local_tags(transport, &ref_map, &tail);
-       ref_remove_duplicates(ref_map);
 
-       return ref_map;
+       /* Now append any refs to be updated opportunistically: */
+       *tail = orefs;
+       for (rm = orefs; rm; rm = rm->next) {
+               rm->fetch_head_status = FETCH_HEAD_IGNORE;
+               tail = &rm->next;
+       }
+
+       return ref_remove_duplicates(ref_map);
 }
 
 #define STORE_REF_ERROR_OTHER 1
@@ -249,7 +381,8 @@ static int s_update_ref(const char *action,
                rla = default_rla.buf;
        snprintf(msg, sizeof(msg), "%s: %s", rla, action);
        lock = lock_any_ref_for_update(ref->name,
-                                      check_old ? ref->old_sha1 : NULL, 0);
+                                      check_old ? ref->old_sha1 : NULL,
+                                      0, NULL);
        if (!lock)
                return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
                                          STORE_REF_ERROR_OTHER;
@@ -299,7 +432,7 @@ static int update_local_ref(struct ref *ref,
        }
 
        if (!is_null_sha1(ref->old_sha1) &&
-           !prefixcmp(ref->name, "refs/tags/")) {
+           starts_with(ref->name, "refs/tags/")) {
                int r;
                r = s_update_ref("updating tag", ref, 0);
                strbuf_addf(display, "%c %-*s %-*s -> %s%s",
@@ -322,10 +455,10 @@ static int update_local_ref(struct ref *ref,
                 * more likely to follow a standard layout.
                 */
                const char *name = remote_ref ? remote_ref->name : "";
-               if (!prefixcmp(name, "refs/tags/")) {
+               if (starts_with(name, "refs/tags/")) {
                        msg = "storing tag";
                        what = _("[new tag]");
-               } else if (!prefixcmp(name, "refs/heads/")) {
+               } else if (starts_with(name, "refs/heads/")) {
                        msg = "storing head";
                        what = _("[new branch]");
                } else {
@@ -403,7 +536,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 {
        FILE *fp;
        struct commit *commit;
-       int url_len, i, shown_url = 0, rc = 0;
+       int url_len, i, rc = 0;
        struct strbuf note = STRBUF_INIT;
        const char *what, *kind;
        struct ref *rm;
@@ -457,15 +590,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                kind = "";
                                what = "";
                        }
-                       else if (!prefixcmp(rm->name, "refs/heads/")) {
+                       else if (starts_with(rm->name, "refs/heads/")) {
                                kind = "branch";
                                what = rm->name + 11;
                        }
-                       else if (!prefixcmp(rm->name, "refs/tags/")) {
+                       else if (starts_with(rm->name, "refs/tags/")) {
                                kind = "tag";
                                what = rm->name + 10;
                        }
-                       else if (!prefixcmp(rm->name, "refs/remotes/")) {
+                       else if (starts_with(rm->name, "refs/remotes/")) {
                                kind = "remote-tracking branch";
                                what = rm->name + 13;
                        }
@@ -576,17 +709,36 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
        return ret;
 }
 
-static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
+static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
+               const char *raw_url)
 {
-       int result = 0;
+       int url_len, i, result = 0;
        struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
+       char *url;
        const char *dangling_msg = dry_run
                ? _("   (%s will become dangling)")
                : _("   (%s has become dangling)");
 
+       if (raw_url)
+               url = transport_anonymize_url(raw_url);
+       else
+               url = xstrdup("foreign");
+
+       url_len = strlen(url);
+       for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
+               ;
+
+       url_len = i + 1;
+       if (4 < i && !strncmp(".git", url + i - 3, 4))
+               url_len = i - 3;
+
        for (ref = stale_refs; ref; ref = ref->next) {
                if (!dry_run)
                        result |= delete_ref(ref->name, NULL, 0);
+               if (verbosity >= 0 && !shown_url) {
+                       fprintf(stderr, _("From %.*s\n"), url_len, url);
+                       shown_url = 1;
+               }
                if (verbosity >= 0) {
                        fprintf(stderr, " x %-*s %-*s -> %s\n",
                                TRANSPORT_SUMMARY(_("[deleted]")),
@@ -594,110 +746,11 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
                        warn_dangling_symref(stderr, dangling_msg, ref->name);
                }
        }
+       free(url);
        free_refs(stale_refs);
        return result;
 }
 
-static int add_existing(const char *refname, const unsigned char *sha1,
-                       int flag, void *cbdata)
-{
-       struct string_list *list = (struct string_list *)cbdata;
-       struct string_list_item *item = string_list_insert(list, refname);
-       item->util = xmalloc(20);
-       hashcpy(item->util, sha1);
-       return 0;
-}
-
-static int will_fetch(struct ref **head, const unsigned char *sha1)
-{
-       struct ref *rm = *head;
-       while (rm) {
-               if (!hashcmp(rm->old_sha1, sha1))
-                       return 1;
-               rm = rm->next;
-       }
-       return 0;
-}
-
-static void find_non_local_tags(struct transport *transport,
-                       struct ref **head,
-                       struct ref ***tail)
-{
-       struct string_list existing_refs = STRING_LIST_INIT_DUP;
-       struct string_list remote_refs = STRING_LIST_INIT_NODUP;
-       const struct ref *ref;
-       struct string_list_item *item = NULL;
-
-       for_each_ref(add_existing, &existing_refs);
-       for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
-               if (prefixcmp(ref->name, "refs/tags/"))
-                       continue;
-
-               /*
-                * The peeled ref always follows the matching base
-                * ref, so if we see a peeled ref that we don't want
-                * to fetch then we can mark the ref entry in the list
-                * as one to ignore by setting util to NULL.
-                */
-               if (!suffixcmp(ref->name, "^{}")) {
-                       if (item && !has_sha1_file(ref->old_sha1) &&
-                           !will_fetch(head, ref->old_sha1) &&
-                           !has_sha1_file(item->util) &&
-                           !will_fetch(head, item->util))
-                               item->util = NULL;
-                       item = NULL;
-                       continue;
-               }
-
-               /*
-                * If item is non-NULL here, then we previously saw a
-                * ref not followed by a peeled reference, so we need
-                * to check if it is a lightweight tag that we want to
-                * fetch.
-                */
-               if (item && !has_sha1_file(item->util) &&
-                   !will_fetch(head, item->util))
-                       item->util = NULL;
-
-               item = NULL;
-
-               /* skip duplicates and refs that we already have */
-               if (string_list_has_string(&remote_refs, ref->name) ||
-                   string_list_has_string(&existing_refs, ref->name))
-                       continue;
-
-               item = string_list_insert(&remote_refs, ref->name);
-               item->util = (void *)ref->old_sha1;
-       }
-       string_list_clear(&existing_refs, 1);
-
-       /*
-        * We may have a final lightweight tag that needs to be
-        * checked to see if it needs fetching.
-        */
-       if (item && !has_sha1_file(item->util) &&
-           !will_fetch(head, item->util))
-               item->util = NULL;
-
-       /*
-        * For all the tags in the remote_refs string list,
-        * add them to the list of refs to be fetched
-        */
-       for_each_string_list_item(item, &remote_refs) {
-               /* Unless we have already decided to ignore this item... */
-               if (item->util)
-               {
-                       struct ref *rm = alloc_ref(item->string);
-                       rm->peer_ref = alloc_ref(item->string);
-                       hashcpy(rm->old_sha1, item->util);
-                       **tail = rm;
-                       *tail = &rm->next;
-               }
-       }
-
-       string_list_clear(&remote_refs, 0);
-}
-
 static void check_not_current_branch(struct ref *ref_map)
 {
        struct branch *current_branch = branch_get(NULL);
@@ -810,36 +863,26 @@ static int do_fetch(struct transport *transport,
 
        if (tags == TAGS_DEFAULT && autotags)
                transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+       if (prune) {
+               /*
+                * We only prune based on refspecs specified
+                * explicitly (via command line or configuration); we
+                * don't care whether --tags was specified.
+                */
+               if (ref_count) {
+                       prune_refs(refs, ref_count, ref_map, transport->url);
+               } else {
+                       prune_refs(transport->remote->fetch,
+                                  transport->remote->fetch_refspec_nr,
+                                  ref_map,
+                                  transport->url);
+               }
+       }
        if (fetch_refs(transport, ref_map)) {
                free_refs(ref_map);
                retcode = 1;
                goto cleanup;
        }
-       if (prune) {
-               /* If --tags was specified, pretend the user gave us the canonical tags refspec */
-               if (tags == TAGS_SET) {
-                       const char *tags_str = "refs/tags/*:refs/tags/*";
-                       struct refspec *tags_refspec, *refspec;
-
-                       /* Copy the refspec and add the tags to it */
-                       refspec = xcalloc(ref_count + 1, sizeof(struct refspec));
-                       tags_refspec = parse_fetch_refspec(1, &tags_str);
-                       memcpy(refspec, refs, ref_count * sizeof(struct refspec));
-                       memcpy(&refspec[ref_count], tags_refspec, sizeof(struct refspec));
-                       ref_count++;
-
-                       prune_refs(refspec, ref_count, ref_map);
-
-                       ref_count--;
-                       /* The rest of the strings belong to fetch_one */
-                       free_refspec(1, tags_refspec);
-                       free(refspec);
-               } else if (ref_count) {
-                       prune_refs(refs, ref_count, ref_map);
-               } else {
-                       prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map);
-               }
-       }
        free_refs(ref_map);
 
        /* if neither --no-tags nor --tags was specified, do automated tag
@@ -875,7 +918,7 @@ static int get_remote_group(const char *key, const char *value, void *priv)
 {
        struct remote_group_data *g = priv;
 
-       if (!prefixcmp(key, "remotes.") &&
+       if (starts_with(key, "remotes.") &&
                        !strcmp(key + 8, g->name)) {
                /* split list by white space */
                int space = strcspn(value, " \t\n");
@@ -913,8 +956,8 @@ static void add_options_to_argv(struct argv_array *argv)
 {
        if (dry_run)
                argv_array_push(argv, "--dry-run");
-       if (prune)
-               argv_array_push(argv, "--prune");
+       if (prune != -1)
+               argv_array_push(argv, prune ? "--prune" : "--no-prune");
        if (update_head_ok)
                argv_array_push(argv, "--update-head-ok");
        if (force)
@@ -981,6 +1024,17 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
                    "remote name from which new revisions should be fetched."));
 
        gtransport = prepare_transport(remote);
+
+       if (prune < 0) {
+               /* no command line request */
+               if (0 <= gtransport->remote->prune)
+                       prune = gtransport->remote->prune;
+               else if (0 <= fetch_prune_config)
+                       prune = fetch_prune_config;
+               else
+                       prune = PRUNE_BY_DEFAULT;
+       }
+
        if (argc > 0) {
                int j = 0;
                refs = xcalloc(argc + 1, sizeof(const char *));
@@ -1030,6 +1084,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        for (i = 1; i < argc; i++)
                strbuf_addf(&default_rla, " %s", argv[i]);
 
+       git_config(git_fetch_config, NULL);
+
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
 
@@ -1045,6 +1101,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                }
        }
 
+       /* no need to be strict, transport_set_option() will validate it again */
+       if (depth && atoi(depth) < 1)
+               die(_("depth %s is not a positive number"), depth);
+
        if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
                if (recurse_submodules_default) {
                        int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);