Merge branch 'cn/fetch-prune'
authorJunio C Hamano <gitster@pobox.com>
Wed, 26 Oct 2011 23:16:29 +0000 (16:16 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 26 Oct 2011 23:16:29 +0000 (16:16 -0700)
* cn/fetch-prune:
fetch: treat --tags like refs/tags/*:refs/tags/* when pruning
fetch: honor the user-provided refspecs when pruning refs
remote: separate out the remote_find_tracking logic into query_refspecs
t5510: add tests for fetch --prune
fetch: free all the additional refspecs

Conflicts:
remote.c

1  2 
builtin/fetch.c
builtin/remote.c
remote.c
remote.h
diff --combined builtin/fetch.c
index 1adf6c176f31b2ece263bc1f07eee8c7e0b40098,9d481f8ca9d9470311b356e9bd5356fa362ffd6a..91731b909aeb22bf8d4e366b8b92281ac0f9ac0c
@@@ -13,7 -13,6 +13,7 @@@
  #include "sigchain.h"
  #include "transport.h"
  #include "submodule.h"
 +#include "connected.h"
  
  static const char * const builtin_fetch_usage[] = {
        "git fetch [<options>] [<repository> [<refspec>...]]",
@@@ -346,18 -345,6 +346,18 @@@ static int update_local_ref(struct ref 
        }
  }
  
 +static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
 +{
 +      struct ref **rm = cb_data;
 +      struct ref *ref = *rm;
 +
 +      if (!ref)
 +              return -1; /* end of the list */
 +      *rm = ref->next;
 +      hashcpy(sha1, ref->old_sha1);
 +      return 0;
 +}
 +
  static int store_updated_refs(const char *raw_url, const char *remote_name,
                struct ref *ref_map)
  {
                url = transport_anonymize_url(raw_url);
        else
                url = xstrdup("foreign");
 +
 +      rm = ref_map;
 +      if (check_everything_connected(iterate_ref_map, 0, &rm)) {
 +              rc = error(_("%s did not send all necessary objects\n"), url);
 +              goto abort;
 +      }
 +
        for (rm = ref_map; rm; rm = rm->next) {
                struct ref *ref = NULL;
  
                                fprintf(stderr, " %s\n", note);
                }
        }
 -      free(url);
 -      fclose(fp);
 +
        if (rc & STORE_REF_ERROR_DF_CONFLICT)
                error(_("some local refs could not be updated; try running\n"
                      " 'git remote prune %s' to remove any old, conflicting "
                      "branches"), remote_name);
 +
 + abort:
 +      free(url);
 +      fclose(fp);
        return rc;
  }
  
   * We would want to bypass the object transfer altogether if
   * everything we are going to fetch already exists and is connected
   * locally.
 - *
 - * The refs we are going to fetch are in ref_map.  If running
 - *
 - *  $ git rev-list --objects --stdin --not --all
 - *
 - * (feeding all the refs in ref_map on its standard input)
 - * does not error out, that means everything reachable from the
 - * refs we are going to fetch exists and is connected to some of
 - * our existing refs.
   */
  static int quickfetch(struct ref *ref_map)
  {
 -      struct child_process revlist;
 -      struct ref *ref;
 -      int err;
 -      const char *argv[] = {"rev-list",
 -              "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
 +      struct ref *rm = ref_map;
  
        /*
         * If we are deepening a shallow clone we already have these
         */
        if (depth)
                return -1;
 -
 -      if (!ref_map)
 -              return 0;
 -
 -      memset(&revlist, 0, sizeof(revlist));
 -      revlist.argv = argv;
 -      revlist.git_cmd = 1;
 -      revlist.no_stdout = 1;
 -      revlist.no_stderr = 1;
 -      revlist.in = -1;
 -
 -      err = start_command(&revlist);
 -      if (err) {
 -              error(_("could not run rev-list"));
 -              return err;
 -      }
 -
 -      /*
 -       * If rev-list --stdin encounters an unknown commit, it terminates,
 -       * which will cause SIGPIPE in the write loop below.
 -       */
 -      sigchain_push(SIGPIPE, SIG_IGN);
 -
 -      for (ref = ref_map; ref; ref = ref->next) {
 -              if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
 -                  write_str_in_full(revlist.in, "\n") < 0) {
 -                      if (errno != EPIPE && errno != EINVAL)
 -                              error(_("failed write to rev-list: %s"), strerror(errno));
 -                      err = -1;
 -                      break;
 -              }
 -      }
 -
 -      if (close(revlist.in)) {
 -              error(_("failed to close rev-list's stdin: %s"), strerror(errno));
 -              err = -1;
 -      }
 -
 -      sigchain_pop(SIGPIPE);
 -
 -      return finish_command(&revlist) || err;
 +      return check_everything_connected(iterate_ref_map, 1, &rm);
  }
  
  static int fetch_refs(struct transport *transport, struct ref *ref_map)
        return ret;
  }
  
- static int prune_refs(struct transport *transport, struct ref *ref_map)
+ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
  {
        int result = 0;
-       struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+       struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
        const char *dangling_msg = dry_run
                ? _("   (%s will become dangling)\n")
                : _("   (%s has become dangling)\n");
@@@ -704,8 -734,31 +704,31 @@@ static int do_fetch(struct transport *t
                free_refs(ref_map);
                return 1;
        }
-       if (prune)
-               prune_refs(transport, ref_map);
+       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
@@@ -888,7 -941,7 +911,7 @@@ static int fetch_one(struct remote *rem
        atexit(unlock_pack);
        refspec = parse_fetch_refspec(ref_nr, refs);
        exit_code = do_fetch(transport, refspec, ref_nr);
-       free(refspec);
+       free_refspec(ref_nr, refspec);
        transport_disconnect(transport);
        transport = NULL;
        return exit_code;
diff --combined builtin/remote.c
index 44eb8795cd654fa2fce97b9999e6611dd74bf479,de52367927243a059e3b4e454cfc255017b892ad..733514979969f69ad45448a67d981b621bbee146
@@@ -349,7 -349,8 +349,8 @@@ static int get_ref_states(const struct 
                else
                        string_list_append(&states->tracked, abbrev_branch(ref->name));
        }
-       stale_refs = get_stale_heads(states->remote, fetch_map);
+       stale_refs = get_stale_heads(states->remote->fetch,
+                                    states->remote->fetch_refspec_nr, fetch_map);
        for (ref = stale_refs; ref; ref = ref->next) {
                struct string_list_item *item =
                        string_list_append(&states->stale, abbrev_branch(ref->name));
@@@ -389,8 -390,8 +390,8 @@@ static int get_push_ref_states(const st
        local_refs = get_local_heads();
        push_map = copy_ref_list(remote_refs);
  
 -      match_refs(local_refs, &push_map, remote->push_refspec_nr,
 -                 remote->push_refspec, MATCH_REFS_NONE);
 +      match_push_refs(local_refs, &push_map, remote->push_refspec_nr,
 +                      remote->push_refspec, MATCH_REFS_NONE);
  
        states->push.strdup_strings = 1;
        for (ref = push_map; ref; ref = ref->next) {
@@@ -570,7 -571,7 +571,7 @@@ static int read_remote_branches(const c
        unsigned char orig_sha1[20];
        const char *symref;
  
 -      strbuf_addf(&buf, "refs/remotes/%s", rename->old);
 +      strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
        if (!prefixcmp(refname, buf.buf)) {
                item = string_list_append(rename->remote_branches, xstrdup(refname));
                symref = resolve_ref(refname, orig_sha1, 1, &flag);
@@@ -621,11 -622,10 +622,11 @@@ static int mv(int argc, const char **ar
                OPT_END()
        };
        struct remote *oldremote, *newremote;
 -      struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
 +      struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT,
 +              old_remote_context = STRBUF_INIT;
        struct string_list remote_branches = STRING_LIST_INIT_NODUP;
        struct rename_info rename;
 -      int i;
 +      int i, refspec_updated = 0;
  
        if (argc != 3)
                usage_with_options(builtin_remote_rename_usage, options);
        strbuf_addf(&buf, "remote.%s.fetch", rename.new);
        if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
                return error("Could not remove config section '%s'", buf.buf);
 +      strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
        for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
                char *ptr;
  
                strbuf_reset(&buf2);
                strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
 -              ptr = strstr(buf2.buf, rename.old);
 -              if (ptr)
 -                      strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
 -                                      rename.new, strlen(rename.new));
 +              ptr = strstr(buf2.buf, old_remote_context.buf);
 +              if (ptr) {
 +                      refspec_updated = 1;
 +                      strbuf_splice(&buf2,
 +                                    ptr-buf2.buf + strlen(":refs/remotes/"),
 +                                    strlen(rename.old), rename.new,
 +                                    strlen(rename.new));
 +              } else
 +                      warning("Not updating non-default fetch respec\n"
 +                              "\t%s\n"
 +                              "\tPlease update the configuration manually if necessary.",
 +                              buf2.buf);
 +
                if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
                        return error("Could not append '%s'", buf.buf);
        }
                }
        }
  
 +      if (!refspec_updated)
 +              return 0;
 +
        /*
         * First remove symrefs, then rename the rest, finally create
         * the new symrefs.
diff --combined remote.c
index 2f62c9a3ed914152d8071aa11950f3d9c2799122,6ececc4a7703f6d54caf919b1162ed454b45a47e..e2ef99114478c49863809ea5cef46392a5a8c0e9
+++ b/remote.c
@@@ -492,6 -492,23 +492,6 @@@ static void read_config(void
        alias_all_urls();
  }
  
 -/*
 - * We need to make sure the remote-tracking branches are well formed, but a
 - * wildcard refspec in "struct refspec" must have a trailing slash. We
 - * temporarily drop the trailing '/' while calling check_ref_format(),
 - * and put it back.  The caller knows that a CHECK_REF_FORMAT_ONELEVEL
 - * error return is Ok for a wildcard refspec.
 - */
 -static int verify_refname(char *name, int is_glob)
 -{
 -      int result;
 -
 -      result = check_ref_format(name);
 -      if (is_glob && result == CHECK_REF_FORMAT_WILDCARD)
 -              result = CHECK_REF_FORMAT_OK;
 -      return result;
 -}
 -
  /*
   * This function frees a refspec array.
   * Warning: code paths should be checked to ensure that the src
@@@ -515,13 -532,13 +515,13 @@@ static void free_refspecs(struct refspe
  static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify)
  {
        int i;
 -      int st;
        struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
  
        for (i = 0; i < nr_refspec; i++) {
                size_t llen;
                int is_glob;
                const char *lhs, *rhs;
 +              int flags;
  
                is_glob = 0;
  
  
                rs[i].pattern = is_glob;
                rs[i].src = xstrndup(lhs, llen);
 +              flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
  
                if (fetch) {
                        /*
                         */
                        if (!*rs[i].src)
                                ; /* empty is ok */
 -                      else {
 -                              st = verify_refname(rs[i].src, is_glob);
 -                              if (st && st != CHECK_REF_FORMAT_ONELEVEL)
 -                                      goto invalid;
 -                      }
 +                      else if (check_refname_format(rs[i].src, flags))
 +                              goto invalid;
                        /*
                         * RHS
                         * - missing is ok, and is same as empty.
                         * - empty is ok; it means not to store.
                         * - otherwise it must be a valid looking ref.
                         */
 -                      if (!rs[i].dst) {
 +                      if (!rs[i].dst)
                                ; /* ok */
 -                      } else if (!*rs[i].dst) {
 +                      else if (!*rs[i].dst)
                                ; /* ok */
 -                      } else {
 -                              st = verify_refname(rs[i].dst, is_glob);
 -                              if (st && st != CHECK_REF_FORMAT_ONELEVEL)
 -                                      goto invalid;
 -                      }
 +                      else if (check_refname_format(rs[i].dst, flags))
 +                              goto invalid;
                } else {
                        /*
                         * LHS
                        if (!*rs[i].src)
                                ; /* empty is ok */
                        else if (is_glob) {
 -                              st = verify_refname(rs[i].src, is_glob);
 -                              if (st && st != CHECK_REF_FORMAT_ONELEVEL)
 +                              if (check_refname_format(rs[i].src, flags))
                                        goto invalid;
                        }
                        else
                         * - otherwise it must be a valid looking ref.
                         */
                        if (!rs[i].dst) {
 -                              st = verify_refname(rs[i].src, is_glob);
 -                              if (st && st != CHECK_REF_FORMAT_ONELEVEL)
 +                              if (check_refname_format(rs[i].src, flags))
                                        goto invalid;
                        } else if (!*rs[i].dst) {
                                goto invalid;
                        } else {
 -                              st = verify_refname(rs[i].dst, is_glob);
 -                              if (st && st != CHECK_REF_FORMAT_ONELEVEL)
 +                              if (check_refname_format(rs[i].dst, flags))
                                        goto invalid;
                        }
                }
@@@ -803,59 -828,56 +803,56 @@@ static int match_name_with_pattern(cons
        return ret;
  }
  
- char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
-                    const char *name)
+ static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
  {
        int i;
-       char *ret = NULL;
-       for (i = 0; i < nr_refspec; i++) {
-               struct refspec *refspec = refspecs + i;
-               if (refspec->pattern) {
-                       if (match_name_with_pattern(refspec->src, name,
-                                                   refspec->dst, &ret))
-                               return ret;
-               } else if (!strcmp(refspec->src, name))
-                       return xstrdup(refspec->dst);
-       }
-       return NULL;
- }
+       int find_src = !query->src;
  
- int remote_find_tracking(struct remote *remote, struct refspec *refspec)
- {
-       int find_src = refspec->src == NULL;
-       char *needle, **result;
-       int i;
+       if (find_src && !query->dst)
+               return error("query_refspecs: need either src or dst");
  
-       if (find_src) {
-               if (!refspec->dst)
-                       return error("find_tracking: need either src or dst");
-               needle = refspec->dst;
-               result = &refspec->src;
-       } else {
-               needle = refspec->src;
-               result = &refspec->dst;
-       }
+       for (i = 0; i < ref_count; i++) {
+               struct refspec *refspec = &refs[i];
+               const char *key = find_src ? refspec->dst : refspec->src;
+               const char *value = find_src ? refspec->src : refspec->dst;
+               const char *needle = find_src ? query->dst : query->src;
+               char **result = find_src ? &query->src : &query->dst;
  
-       for (i = 0; i < remote->fetch_refspec_nr; i++) {
-               struct refspec *fetch = &remote->fetch[i];
-               const char *key = find_src ? fetch->dst : fetch->src;
-               const char *value = find_src ? fetch->src : fetch->dst;
-               if (!fetch->dst)
+               if (!refspec->dst)
                        continue;
-               if (fetch->pattern) {
+               if (refspec->pattern) {
                        if (match_name_with_pattern(key, needle, value, result)) {
-                               refspec->force = fetch->force;
+                               query->force = refspec->force;
                                return 0;
                        }
                } else if (!strcmp(needle, key)) {
                        *result = xstrdup(value);
-                       refspec->force = fetch->force;
+                       query->force = refspec->force;
                        return 0;
                }
        }
        return -1;
  }
  
+ char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+                    const char *name)
+ {
+       struct refspec query;
+       memset(&query, 0, sizeof(struct refspec));
+       query.src = (char *)name;
+       if (query_refspecs(refspecs, nr_refspec, &query))
+               return NULL;
+       return query.dst;
+ }
+ int remote_find_tracking(struct remote *remote, struct refspec *refspec)
+ {
+       return query_refspecs(remote->fetch, remote->fetch_refspec_nr, refspec);
+ }
  static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
                const char *name)
  {
@@@ -1145,15 -1167,12 +1142,15 @@@ static struct ref **tail_ref(struct re
  }
  
  /*
 - * Note. This is used only by "push"; refspec matching rules for
 - * push and fetch are subtly different, so do not try to reuse it
 - * without thinking.
 + * Given the set of refs the local repository has, the set of refs the
 + * remote repository has, and the refspec used for push, determine
 + * what remote refs we will update and with what value by setting
 + * peer_ref (which object is being pushed) and force (if the push is
 + * forced) in elements of "dst". The function may add new elements to
 + * dst (e.g. pushing to a new branch, done in match_explicit_refs).
   */
 -int match_refs(struct ref *src, struct ref **dst,
 -             int nr_refspec, const char **refspec, int flags)
 +int match_push_refs(struct ref *src, struct ref **dst,
 +                  int nr_refspec, const char **refspec, int flags)
  {
        struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
@@@ -1405,8 -1424,8 +1402,8 @@@ int get_fetch_map(const struct ref *rem
  
        for (rmp = &ref_map; *rmp; ) {
                if ((*rmp)->peer_ref) {
 -                      int st = check_ref_format((*rmp)->peer_ref->name + 5);
 -                      if (st && st != CHECK_REF_FORMAT_ONELEVEL) {
 +                      if (check_refname_format((*rmp)->peer_ref->name + 5,
 +                              REFNAME_ALLOW_ONELEVEL)) {
                                struct ref *ignore = *rmp;
                                error("* Ignoring funny ref '%s' locally",
                                      (*rmp)->peer_ref->name);
@@@ -1598,7 -1617,7 +1595,7 @@@ static int one_local_ref(const char *re
        int len;
  
        /* we already know it starts with refs/ to get here */
 -      if (check_ref_format(refname + 5))
 +      if (check_refname_format(refname + 5, 0))
                return 0;
  
        len = strlen(refname) + 1;
@@@ -1659,36 -1678,47 +1656,47 @@@ struct ref *guess_remote_head(const str
  }
  
  struct stale_heads_info {
-       struct remote *remote;
        struct string_list *ref_names;
        struct ref **stale_refs_tail;
+       struct refspec *refs;
+       int ref_count;
  };
  
  static int get_stale_heads_cb(const char *refname,
        const unsigned char *sha1, int flags, void *cb_data)
  {
        struct stale_heads_info *info = cb_data;
-       struct refspec refspec;
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (!remote_find_tracking(info->remote, &refspec)) {
-               if (!((flags & REF_ISSYMREF) ||
-                   string_list_has_string(info->ref_names, refspec.src))) {
-                       struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
-                       hashcpy(ref->new_sha1, sha1);
-               }
+       struct refspec query;
+       memset(&query, 0, sizeof(struct refspec));
+       query.dst = (char *)refname;
+       if (query_refspecs(info->refs, info->ref_count, &query))
+               return 0; /* No matches */
+       /*
+        * If we did find a suitable refspec and it's not a symref and
+        * it's not in the list of refs that currently exist in that
+        * remote we consider it to be stale.
+        */
+       if (!((flags & REF_ISSYMREF) ||
+             string_list_has_string(info->ref_names, query.src))) {
+               struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
+               hashcpy(ref->new_sha1, sha1);
        }
+       free(query.src);
        return 0;
  }
  
- struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
+ struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map)
  {
        struct ref *ref, *stale_refs = NULL;
        struct string_list ref_names = STRING_LIST_INIT_NODUP;
        struct stale_heads_info info;
-       info.remote = remote;
        info.ref_names = &ref_names;
        info.stale_refs_tail = &stale_refs;
+       info.refs = refs;
+       info.ref_count = ref_count;
        for (ref = fetch_map; ref; ref = ref->next)
                string_list_append(&ref_names, ref->name);
        sort_string_list(&ref_names);
diff --combined remote.h
index 67294778b6a7af3f0fa2c3cb1c0a34cc11d508b1,f2541b5a29a552fb9fab736f5b47a545c2eee383..b3955983ba5caea698a78868abcbb54451b6daa8
+++ b/remote.h
@@@ -96,8 -96,8 +96,8 @@@ void free_refspec(int nr_refspec, struc
  char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
                     const char *name);
  
 -int match_refs(struct ref *src, struct ref **dst,
 -             int nr_refspec, const char **refspec, int all);
 +int match_push_refs(struct ref *src, struct ref **dst,
 +                  int nr_refspec, const char **refspec, int all);
  void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
        int force_update);
  
@@@ -164,6 -164,6 +164,6 @@@ struct ref *guess_remote_head(const str
                              int all);
  
  /* Return refs which no longer exist on remote */
- struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map);
+ struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map);
  
  #endif