git diff too slow for a file
[gitweb.git] / builtin-fetch.c
index c903c66d47c52d7e585866ab3182ec1eb69408b0..007dabf62f2574fc9214d6fb2a961619ad305a6b 100644 (file)
 #include "sigchain.h"
 
 static const char * const builtin_fetch_usage[] = {
-       "git fetch [options] [<repository> <refspec>...]",
-       "git fetch [options] <group>",
-       "git fetch --multiple [options] [<repository> | <group>]...",
-       "git fetch --all [options]",
+       "git fetch [<options>] [<repository> [<refspec>...]]",
+       "git fetch [<options>] <group>",
+       "git fetch --multiple [<options>] [<repository> | <group>]...",
+       "git fetch --all [<options>]",
        NULL
 };
 
@@ -26,7 +26,7 @@ enum {
        TAGS_SET = 2
 };
 
-static int all, append, force, keep, multiple, update_head_ok, verbosity;
+static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
 static int tags = TAGS_DEFAULT;
 static const char *depth;
 static const char *upload_pack;
@@ -49,6 +49,10 @@ static struct option builtin_fetch_options[] = {
                    "fetch all tags and associated objects", TAGS_SET),
        OPT_SET_INT('n', NULL, &tags,
                    "do not fetch all tags (--no-tags)", TAGS_UNSET),
+       OPT_BOOLEAN('p', "prune", &prune,
+                   "prune tracking branches no longer on remote"),
+       OPT_BOOLEAN(0, "dry-run", &dry_run,
+                   "dry run"),
        OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
        OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
                    "allow updating of HEAD ref"),
@@ -100,10 +104,8 @@ static void add_merge_config(struct ref **head,
                 * there is no entry in the resulting FETCH_HEAD marked
                 * for merging.
                 */
+               memset(&refspec, 0, sizeof(refspec));
                refspec.src = branch->merge[i]->src;
-               refspec.dst = NULL;
-               refspec.pattern = 0;
-               refspec.force = 0;
                get_fetch_map(remote_refs, &refspec, tail, 1);
                for (rm = *old_tail; rm; rm = rm->next)
                        rm->merge = 1;
@@ -185,6 +187,8 @@ static int s_update_ref(const char *action,
        char *rla = getenv("GIT_REFLOG_ACTION");
        static struct ref_lock *lock;
 
+       if (dry_run)
+               return 0;
        if (!rla)
                rla = default_rla.buf;
        snprintf(msg, sizeof(msg), "%s: %s", rla, action);
@@ -276,7 +280,7 @@ static int update_local_ref(struct ref *ref,
                strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
                strcat(quickref, "..");
                strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
-               r = s_update_ref("fast forward", ref, 1);
+               r = s_update_ref("fast-forward", ref, 1);
                sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
                        SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
                        pretty_ref, r ? "  (unable to update local ref)" : "");
@@ -294,7 +298,7 @@ static int update_local_ref(struct ref *ref,
                        r ? "unable to update local ref" : "forced update");
                return r;
        } else {
-               sprintf(display, "! %-*s %-*s -> %s  (non fast forward)",
+               sprintf(display, "! %-*s %-*s -> %s  (non-fast-forward)",
                        SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
                        pretty_ref);
                return 1;
@@ -310,13 +314,16 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
        char note[1024];
        const char *what, *kind;
        struct ref *rm;
-       char *url, *filename = git_path("FETCH_HEAD");
+       char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
 
        fp = fopen(filename, "a");
        if (!fp)
                return error("cannot open %s: %s\n", filename, strerror(errno));
 
-       url = transport_anonymize_url(raw_url);
+       if (raw_url)
+               url = transport_anonymize_url(raw_url);
+       else
+               url = xstrdup("foreign");
        for (rm = ref_map; rm; rm = rm->next) {
                struct ref *ref = NULL;
 
@@ -380,9 +387,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                fputc(url[i], fp);
                fputc('\n', fp);
 
-               if (ref)
+               if (ref) {
                        rc |= update_local_ref(ref, what, note);
-               else
+                       free(ref);
+               } else
                        sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
                                SUMMARY_WIDTH, *kind ? kind : "branch",
                                 REFCOL_WIDTH, *what ? what : "HEAD");
@@ -492,11 +500,34 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
        return ret;
 }
 
+static int prune_refs(struct transport *transport, struct ref *ref_map)
+{
+       int result = 0;
+       struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+       const char *dangling_msg = dry_run
+               ? "   (%s will become dangling)\n"
+               : "   (%s has become dangling)\n";
+
+       for (ref = stale_refs; ref; ref = ref->next) {
+               if (!dry_run)
+                       result |= delete_ref(ref->name, NULL, 0);
+               if (verbosity >= 0) {
+                       fprintf(stderr, " x %-*s %-*s -> %s\n",
+                               SUMMARY_WIDTH, "[deleted]",
+                               REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
+                       warn_dangling_symref(stderr, dangling_msg, ref->name);
+               }
+       }
+       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;
-       string_list_insert(refname, list);
+       struct string_list_item *item = string_list_insert(refname, list);
+       item->util = (void *)sha1;
        return 0;
 }
 
@@ -511,57 +542,98 @@ static int will_fetch(struct ref **head, const unsigned char *sha1)
        return 0;
 }
 
+struct tag_data {
+       struct ref **head;
+       struct ref ***tail;
+};
+
+static int add_to_tail(struct string_list_item *item, void *cb_data)
+{
+       struct tag_data *data = (struct tag_data *)cb_data;
+       struct ref *rm = NULL;
+
+       /* We have already decided to ignore this item */
+       if (!item->util)
+               return 0;
+
+       rm = alloc_ref(item->string);
+       rm->peer_ref = alloc_ref(item->string);
+       hashcpy(rm->old_sha1, item->util);
+
+       **data->tail = rm;
+       *data->tail = &rm->next;
+
+       return 0;
+}
+
 static void find_non_local_tags(struct transport *transport,
                        struct ref **head,
                        struct ref ***tail)
 {
        struct string_list existing_refs = { NULL, 0, 0, 0 };
-       struct string_list new_refs = { NULL, 0, 0, 1 };
-       char *ref_name;
-       int ref_name_len;
-       const unsigned char *ref_sha1;
-       const struct ref *tag_ref;
-       struct ref *rm = NULL;
+       struct string_list remote_refs = { NULL, 0, 0, 0 };
+       struct tag_data data = {head, tail};
        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;
 
-               ref_name = xstrdup(ref->name);
-               ref_name_len = strlen(ref_name);
-               ref_sha1 = ref->old_sha1;
-
-               if (!strcmp(ref_name + ref_name_len - 3, "^{}")) {
-                       ref_name[ref_name_len - 3] = 0;
-                       tag_ref = transport_get_remote_refs(transport);
-                       while (tag_ref) {
-                               if (!strcmp(tag_ref->name, ref_name)) {
-                                       ref_sha1 = tag_ref->old_sha1;
-                                       break;
-                               }
-                               tag_ref = tag_ref->next;
-                       }
+               /*
+                * 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 (!string_list_has_string(&existing_refs, ref_name) &&
-                   !string_list_has_string(&new_refs, ref_name) &&
-                   (has_sha1_file(ref->old_sha1) ||
-                    will_fetch(head, ref->old_sha1))) {
-                       string_list_insert(ref_name, &new_refs);
+               /*
+                * 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;
 
-                       rm = alloc_ref(ref_name);
-                       rm->peer_ref = alloc_ref(ref_name);
-                       hashcpy(rm->old_sha1, ref_sha1);
+               item = NULL;
 
-                       **tail = rm;
-                       *tail = &rm->next;
-               }
-               free(ref_name);
+               /* 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(ref->name, &remote_refs);
+               item->util = (void *)ref->old_sha1;
        }
        string_list_clear(&existing_refs, 0);
-       string_list_clear(&new_refs, 0);
+
+       /*
+        * 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, call
+        * add_to_tail to add them to the list of refs to be fetched
+        */
+       for_each_string_list(add_to_tail, &remote_refs, &data);
+
+       string_list_clear(&remote_refs, 0);
 }
 
 static void check_not_current_branch(struct ref *ref_map)
@@ -578,12 +650,28 @@ static void check_not_current_branch(struct ref *ref_map)
                            "of non-bare repository", current_branch->refname);
 }
 
+static int truncate_fetch_head(void)
+{
+       char *filename = git_path("FETCH_HEAD");
+       FILE *fp = fopen(filename, "w");
+
+       if (!fp)
+               return error("cannot open %s: %s\n", filename, strerror(errno));
+       fclose(fp);
+       return 0;
+}
+
 static int do_fetch(struct transport *transport,
                    struct refspec *refs, int ref_count)
 {
+       struct string_list existing_refs = { NULL, 0, 0, 0 };
+       struct string_list_item *peer_item = NULL;
        struct ref *ref_map;
        struct ref *rm;
        int autotags = (transport->remote->fetch_tags == 1);
+
+       for_each_ref(add_existing, &existing_refs);
+
        if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
                tags = TAGS_SET;
        if (transport->remote->fetch_tags == -1)
@@ -593,12 +681,10 @@ static int do_fetch(struct transport *transport,
                die("Don't know how to fetch from %s", transport->url);
 
        /* if not appending, truncate FETCH_HEAD */
-       if (!append) {
-               char *filename = git_path("FETCH_HEAD");
-               FILE *fp = fopen(filename, "w");
-               if (!fp)
-                       return error("cannot open %s: %s\n", filename, strerror(errno));
-               fclose(fp);
+       if (!append && !dry_run) {
+               int errcode = truncate_fetch_head();
+               if (errcode)
+                       return errcode;
        }
 
        ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
@@ -606,8 +692,13 @@ static int do_fetch(struct transport *transport,
                check_not_current_branch(ref_map);
 
        for (rm = ref_map; rm; rm = rm->next) {
-               if (rm->peer_ref)
-                       read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
+               if (rm->peer_ref) {
+                       peer_item = string_list_lookup(rm->peer_ref->name,
+                                                      &existing_refs);
+                       if (peer_item)
+                               hashcpy(rm->peer_ref->old_sha1,
+                                       peer_item->util);
+               }
        }
 
        if (tags == TAGS_DEFAULT && autotags)
@@ -616,6 +707,8 @@ static int do_fetch(struct transport *transport,
                free_refs(ref_map);
                return 1;
        }
+       if (prune)
+               prune_refs(transport, ref_map);
        free_refs(ref_map);
 
        /* if neither --no-tags nor --tags was specified, do automated tag
@@ -649,7 +742,8 @@ static void set_option(const char *name, const char *value)
 static int get_one_remote_for_fetch(struct remote *remote, void *priv)
 {
        struct string_list *list = priv;
-       string_list_append(remote->name, list);
+       if (!remote->skip_default_update)
+               string_list_append(remote->name, list);
        return 0;
 }
 
@@ -698,9 +792,19 @@ static int add_remote_or_group(const char *name, struct string_list *list)
 static int fetch_multiple(struct string_list *list)
 {
        int i, result = 0;
-       const char *argv[] = { "fetch", NULL, NULL, NULL, NULL };
-       int argc = 1;
-
+       const char *argv[11] = { "fetch", "--append" };
+       int argc = 2;
+
+       if (dry_run)
+               argv[argc++] = "--dry-run";
+       if (prune)
+               argv[argc++] = "--prune";
+       if (update_head_ok)
+               argv[argc++] = "--update-head-ok";
+       if (force)
+               argv[argc++] = "--force";
+       if (keep)
+               argv[argc++] = "--keep";
        if (verbosity >= 2)
                argv[argc++] = "-v";
        if (verbosity >= 1)
@@ -708,9 +812,16 @@ static int fetch_multiple(struct string_list *list)
        else if (verbosity < 0)
                argv[argc++] = "-q";
 
+       if (!append && !dry_run) {
+               int errcode = truncate_fetch_head();
+               if (errcode)
+                       return errcode;
+       }
+
        for (i = 0; i < list->nr; i++) {
                const char *name = list->items[i].string;
                argv[argc] = name;
+               argv[argc + 1] = NULL;
                if (verbosity >= 0)
                        printf("Fetching %s\n", name);
                if (run_command_v_opt(argv, RUN_GIT_CMD)) {
@@ -732,9 +843,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
        if (!remote)
                die("Where do you want to fetch from today?");
 
-       transport = transport_get(remote, remote->url[0]);
+       transport = transport_get(remote, NULL);
        if (verbosity >= 2)
-               transport->verbose = 1;
+               transport->verbose = verbosity <= 3 ? verbosity : 3;
        if (verbosity < 0)
                transport->verbose = -1;
        if (upload_pack)