Prevent buffer overflows when path is too long
[gitweb.git] / builtin / fetch.c
index d784b2e6947f1b5ace0016d92258e29377e9d1a7..3d978eb58ec5cc1e2629db10562319c666bc031e 100644 (file)
@@ -30,13 +30,18 @@ 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;
 static const char *upload_pack;
 static struct strbuf default_rla = STRBUF_INIT;
-static struct transport *transport;
+static struct transport *gtransport;
+static struct transport *gsecondary;
 static const char *submodule_prefix = "";
 static const char *recurse_submodules_default;
 
@@ -54,30 +59,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"),
@@ -95,8 +109,10 @@ static struct option builtin_fetch_options[] = {
 
 static void unlock_pack(void)
 {
-       if (transport)
-               transport_unlock_pack(transport);
+       if (gtransport)
+               transport_unlock_pack(gtransport);
+       if (gsecondary)
+               transport_unlock_pack(gsecondary);
 }
 
 static void unlock_pack_on_signal(int signo)
@@ -144,48 +160,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 (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 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;
@@ -222,11 +346,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
@@ -246,7 +380,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;
@@ -595,106 +730,6 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
        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);
@@ -720,6 +755,48 @@ static int truncate_fetch_head(void)
        return 0;
 }
 
+static void set_option(struct transport *transport, const char *name, const char *value)
+{
+       int r = transport_set_option(transport, name, value);
+       if (r < 0)
+               die(_("Option \"%s\" value \"%s\" is not valid for %s"),
+                   name, value, transport->url);
+       if (r > 0)
+               warning(_("Option \"%s\" is ignored for %s\n"),
+                       name, transport->url);
+}
+
+static struct transport *prepare_transport(struct remote *remote)
+{
+       struct transport *transport;
+       transport = transport_get(remote, NULL);
+       transport_set_verbosity(transport, verbosity, progress);
+       if (upload_pack)
+               set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack);
+       if (keep)
+               set_option(transport, TRANS_OPT_KEEP, "yes");
+       if (depth)
+               set_option(transport, TRANS_OPT_DEPTH, depth);
+       return transport;
+}
+
+static void backfill_tags(struct transport *transport, struct ref *ref_map)
+{
+       if (transport->cannot_reuse) {
+               gsecondary = prepare_transport(transport->remote);
+               transport = gsecondary;
+       }
+
+       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+       transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+       fetch_refs(transport, ref_map);
+
+       if (gsecondary) {
+               transport_disconnect(gsecondary);
+               gsecondary = NULL;
+       }
+}
+
 static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
 {
@@ -771,28 +848,17 @@ static int do_fetch(struct transport *transport,
                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) {
+               /*
+                * 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);
                } else {
-                       prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map);
+                       prune_refs(transport->remote->fetch,
+                                  transport->remote->fetch_refspec_nr,
+                                  ref_map);
                }
        }
        free_refs(ref_map);
@@ -803,11 +869,8 @@ static int do_fetch(struct transport *transport,
                struct ref **tail = &ref_map;
                ref_map = NULL;
                find_non_local_tags(transport, &ref_map, &tail);
-               if (ref_map) {
-                       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
-                       transport_set_option(transport, TRANS_OPT_DEPTH, "0");
-                       fetch_refs(transport, ref_map);
-               }
+               if (ref_map)
+                       backfill_tags(transport, ref_map);
                free_refs(ref_map);
        }
 
@@ -816,17 +879,6 @@ static int do_fetch(struct transport *transport,
        return retcode;
 }
 
-static void set_option(const char *name, const char *value)
-{
-       int r = transport_set_option(transport, name, value);
-       if (r < 0)
-               die(_("Option \"%s\" value \"%s\" is not valid for %s"),
-                       name, value, transport->url);
-       if (r > 0)
-               warning(_("Option \"%s\" is ignored for %s\n"),
-                       name, transport->url);
-}
-
 static int get_one_remote_for_fetch(struct remote *remote, void *priv)
 {
        struct string_list *list = priv;
@@ -882,8 +934,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)
@@ -949,14 +1001,17 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
                die(_("No remote repository specified.  Please, specify either a URL or a\n"
                    "remote name from which new revisions should be fetched."));
 
-       transport = transport_get(remote, NULL);
-       transport_set_verbosity(transport, verbosity, progress);
-       if (upload_pack)
-               set_option(TRANS_OPT_UPLOADPACK, upload_pack);
-       if (keep)
-               set_option(TRANS_OPT_KEEP, "yes");
-       if (depth)
-               set_option(TRANS_OPT_DEPTH, depth);
+       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;
@@ -983,10 +1038,10 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
        sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
        refspec = parse_fetch_refspec(ref_nr, refs);
-       exit_code = do_fetch(transport, refspec, ref_nr);
+       exit_code = do_fetch(gtransport, refspec, ref_nr);
        free_refspec(ref_nr, refspec);
-       transport_disconnect(transport);
-       transport = NULL;
+       transport_disconnect(gtransport);
+       gtransport = NULL;
        return exit_code;
 }
 
@@ -1007,6 +1062,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);