Merge branch 'js/remote-improvements'
authorJunio C Hamano <gitster@pobox.com>
Wed, 18 Mar 2009 01:55:06 +0000 (18:55 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 18 Mar 2009 01:55:06 +0000 (18:55 -0700)
* js/remote-improvements: (23 commits)
builtin-remote.c: no "commented out" code, please
builtin-remote: new show output style for push refspecs
builtin-remote: new show output style
remote: make guess_remote_head() use exact HEAD lookup if it is available
builtin-remote: add set-head subcommand
builtin-remote: teach show to display remote HEAD
builtin-remote: fix two inconsistencies in the output of "show <remote>"
builtin-remote: make get_remote_ref_states() always populate states.tracked
builtin-remote: rename variables and eliminate redundant function call
builtin-remote: remove unused code in get_ref_states
builtin-remote: refactor duplicated cleanup code
string-list: new for_each_string_list() function
remote: make match_refs() not short-circuit
remote: make match_refs() copy src ref before assigning to peer_ref
remote: let guess_remote_head() optionally return all matches
remote: make copy_ref() perform a deep copy
remote: simplify guess_remote_head()
move locate_head() to remote.c
move duplicated ref_newer() to remote.c
move duplicated get_local_heads() to remote.c
...

Conflicts:
builtin-clone.c

1  2 
Makefile
builtin-clone.c
builtin-remote.c
cache.h
contrib/completion/git-completion.bash
http-push.c
remote.c
t/t5540-http-push.sh
diff --combined Makefile
index 216adb9d051992b2474c952b4998b921ad081702,744ab4ff8f567bc484498f50d651f25e6a137a90..b96c2b316f7a00646b337baae995af88f0464272
+++ b/Makefile
@@@ -126,12 -126,6 +126,12 @@@ all:
  # randomly break unless your underlying filesystem supports those sub-second
  # times (my ext3 doesn't).
  #
 +# Define USE_ST_TIMESPEC if your "struct stat" uses "st_ctimespec" instead of
 +# "st_ctim"
 +#
 +# Define NO_NSEC if your "struct stat" does not have "st_ctim.tv_nsec"
 +# available.  This automatically turns USE_NSEC off.
 +#
  # Define USE_STDEV below if you want git to care about the underlying device
  # change being considered an inode change from the update-index perspective.
  #
@@@ -663,7 -657,6 +663,7 @@@ ifeq ($(uname_S),Darwin
        endif
        NO_MEMMEM = YesPlease
        THREADED_DELTA_SEARCH = YesPlease
 +      USE_ST_TIMESPEC = YesPlease
  endif
  ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@@ -741,7 -734,6 +741,7 @@@ ifeq ($(uname_S),AIX
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
        NO_STRLCPY = YesPlease
 +      NO_NSEC = YesPlease
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
        INTERNAL_QSORT = UnfortunatelyYes
        NEEDS_LIBICONV=YesPlease
@@@ -807,7 -799,6 +807,7 @@@ ifneq (,$(findstring MINGW,$(uname_S))
        RUNTIME_PREFIX = YesPlease
        NO_POSIX_ONLY_PROGRAMS = YesPlease
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
 +      NO_NSEC = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
        COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
@@@ -929,15 -920,6 +929,15 @@@ endi
  ifdef NO_ST_BLOCKS_IN_STRUCT_STAT
        BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT
  endif
 +ifdef USE_NSEC
 +      BASIC_CFLAGS += -DUSE_NSEC
 +endif
 +ifdef USE_ST_TIMESPEC
 +      BASIC_CFLAGS += -DUSE_ST_TIMESPEC
 +endif
 +ifdef NO_NSEC
 +      BASIC_CFLAGS += -DNO_NSEC
 +endif
  ifdef NO_C99_FORMAT
        BASIC_CFLAGS += -DNO_C99_FORMAT
  endif
@@@ -1381,6 -1363,7 +1381,7 @@@ GIT-CFLAGS: .FORCE-GIT-CFLAG
  GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
+       @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
  
  ### Detect Tck/Tk interpreter path changes
  ifndef NO_TCLTK
@@@ -1658,27 -1641,3 +1659,27 @@@ check-docs:
  check-builtins::
        ./check-builtins.sh
  
 +### Test suite coverage testing
 +#
 +.PHONY: coverage coverage-clean coverage-build coverage-report
 +
 +coverage:
 +      $(MAKE) coverage-build
 +      $(MAKE) coverage-report
 +
 +coverage-clean:
 +      rm -f *.gcda *.gcno
 +
 +COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs
 +COVERAGE_LDFLAGS = $(CFLAGS)  -O0 -lgcov
 +
 +coverage-build: coverage-clean
 +      $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all
 +      $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \
 +              -j1 test
 +
 +coverage-report:
 +      gcov -b *.c
 +      grep '^function.*called 0 ' *.c.gcov \
 +              | sed -e 's/\([^:]*\)\.gcov: *function \([^ ]*\) called.*/\1: \2/' \
 +              | tee coverage-untested-functions
diff --combined builtin-clone.c
index 39523cee309f16268130427f8209f9a5be0cdc70,3146ca87f88e8f187c47b4a375c19b499c024b33..efbc804a3d1c1f9edb1c8c21bc382f8c1c3836ff
@@@ -20,7 -20,7 +20,8 @@@
  #include "dir.h"
  #include "pack-refs.h"
  #include "sigchain.h"
+ #include "remote.h"
 +#include "run-command.h"
  
  /*
   * Overall FIXMEs:
@@@ -294,43 -294,6 +295,6 @@@ static void remove_junk_on_signal(int s
        raise(signo);
  }
  
- static const struct ref *locate_head(const struct ref *refs,
-                                    const struct ref *mapped_refs,
-                                    const struct ref **remote_head_p)
- {
-       const struct ref *remote_head = NULL;
-       const struct ref *remote_master = NULL;
-       const struct ref *r;
-       for (r = refs; r; r = r->next)
-               if (!strcmp(r->name, "HEAD"))
-                       remote_head = r;
-       for (r = mapped_refs; r; r = r->next)
-               if (!strcmp(r->name, "refs/heads/master"))
-                       remote_master = r;
-       if (remote_head_p)
-               *remote_head_p = remote_head;
-       /* If there's no HEAD value at all, never mind. */
-       if (!remote_head)
-               return NULL;
-       /* If refs/heads/master could be right, it is. */
-       if (remote_master && !hashcmp(remote_master->old_sha1,
-                                     remote_head->old_sha1))
-               return remote_master;
-       /* Look for another ref that points there */
-       for (r = mapped_refs; r; r = r->next)
-               if (r != remote_head &&
-                   !hashcmp(r->old_sha1, remote_head->old_sha1))
-                       return r;
-       /* Nothing is the same */
-       return NULL;
- }
  static struct ref *write_remote_refs(const struct ref *refs,
                struct refspec *refspec, const char *reflog)
  {
@@@ -366,6 -329,8 +330,6 @@@ static void install_branch_config(cons
  
  int cmd_clone(int argc, const char **argv, const char *prefix)
  {
 -      int use_local_hardlinks = 1;
 -      int use_separate_remote = 1;
        int is_bundle = 0;
        struct stat buf;
        const char *repo_name, *repo, *work_tree, *git_dir;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
        char *src_ref_prefix = "refs/heads/";
 +      int err = 0;
  
        struct refspec refspec;
  
        if (argc == 0)
                die("You must specify a repository to clone.");
  
 -      if (option_no_hardlinks)
 -              use_local_hardlinks = 0;
 -
        if (option_mirror)
                option_bare = 1;
  
                        die("--bare and --origin %s options are incompatible.",
                            option_origin);
                option_no_checkout = 1;
 -              use_separate_remote = 0;
        }
  
        if (!option_origin)
  
                mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
  
-               head_points_at = locate_head(refs, mapped_refs, &remote_head);
+               remote_head = find_ref_by_name(refs, "HEAD");
+               head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
        }
        else {
                warning("You appear to have cloned an empty repository.");
                if (write_cache(fd, active_cache, active_nr) ||
                    commit_locked_index(lock_file))
                        die("unable to write new index file");
 +
 +              err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
 +                              sha1_to_hex(remote_head->old_sha1), "1", NULL);
        }
  
        strbuf_release(&reflog_msg);
        strbuf_release(&key);
        strbuf_release(&value);
        junk_pid = 0;
 -      return 0;
 +      return err;
  }
diff --combined builtin-remote.c
index e171096ece8cd041f7e6a08ca80af444786b50cc,7b31e554e97c320562ab3c03c3de140e69cffb37..e445b8ba38372f538e618578548fc765a9ec7d17
@@@ -12,12 -12,17 +12,17 @@@ static const char * const builtin_remot
        "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
        "git remote rename <old> <new>",
        "git remote rm <name>",
+       "git remote set-head <name> [-a | -d | <branch>]",
        "git remote show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
        "git remote [-v | --verbose] update [group]",
        NULL
  };
  
+ #define GET_REF_STATES (1<<0)
+ #define GET_HEAD_NAMES (1<<1)
+ #define GET_PUSH_REF_STATES (1<<2)
  static int verbose;
  
  static int show_all(void);
@@@ -143,8 -148,9 +148,9 @@@ static int add(int argc, const char **a
  }
  
  struct branch_info {
-       char *remote;
+       char *remote_name;
        struct string_list merge;
+       int rebase;
  };
  
  static struct string_list branch_list;
@@@ -161,10 -167,11 +167,11 @@@ static const char *abbrev_ref(const cha
  static int config_read_branches(const char *key, const char *value, void *cb)
  {
        if (!prefixcmp(key, "branch.")) {
+               const char *orig_key = key;
                char *name;
                struct string_list_item *item;
                struct branch_info *info;
-               enum { REMOTE, MERGE } type;
+               enum { REMOTE, MERGE, REBASE } type;
  
                key += 7;
                if (!postfixcmp(key, ".remote")) {
                } else if (!postfixcmp(key, ".merge")) {
                        name = xstrndup(key, strlen(key) - 6);
                        type = MERGE;
+               } else if (!postfixcmp(key, ".rebase")) {
+                       name = xstrndup(key, strlen(key) - 7);
+                       type = REBASE;
                } else
                        return 0;
  
                        item->util = xcalloc(sizeof(struct branch_info), 1);
                info = item->util;
                if (type == REMOTE) {
-                       if (info->remote)
-                               warning("more than one branch.%s", key);
-                       info->remote = xstrdup(value);
-               } else {
+                       if (info->remote_name)
+                               warning("more than one %s", orig_key);
+                       info->remote_name = xstrdup(value);
+               } else if (type == MERGE) {
                        char *space = strchr(value, ' ');
                        value = abbrev_branch(value);
                        while (space) {
                                space = strchr(value, ' ');
                        }
                        string_list_append(xstrdup(value), &info->merge);
-               }
+               } else
+                       info->rebase = git_config_bool(orig_key, value);
        }
        return 0;
  }
@@@ -206,12 -217,12 +217,12 @@@ static void read_branches(void
        if (branch_list.nr)
                return;
        git_config(config_read_branches, NULL);
-       sort_string_list(&branch_list);
  }
  
  struct ref_states {
        struct remote *remote;
-       struct string_list new, stale, tracked;
+       struct string_list new, stale, tracked, heads, push;
+       int queried;
  };
  
  static int handle_one_branch(const char *refname,
                const char *name = abbrev_branch(refspec.src);
                /* symbolic refs pointing nowhere were handled already */
                if ((flags & REF_ISSYMREF) ||
-                               unsorted_string_list_has_string(&states->tracked,
-                                       name) ||
-                               unsorted_string_list_has_string(&states->new,
-                                       name))
+                   string_list_has_string(&states->tracked, name) ||
+                   string_list_has_string(&states->new, name))
                        return 0;
                item = string_list_append(name, &states->stale);
                item->util = xstrdup(refname);
        return 0;
  }
  
- static int get_ref_states(const struct ref *ref, struct ref_states *states)
+ static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
  {
        struct ref *fetch_map = NULL, **tail = &fetch_map;
+       struct ref *ref;
        int i;
  
        for (i = 0; i < states->remote->fetch_refspec_nr; i++)
-               if (get_fetch_map(ref, states->remote->fetch + i, &tail, 1))
+               if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
                        die("Could not get fetch map for refspec %s",
                                states->remote->fetch_refspec[i]);
  
        states->new.strdup_strings = states->tracked.strdup_strings = 1;
        for (ref = fetch_map; ref; ref = ref->next) {
-               struct string_list *target = &states->tracked;
                unsigned char sha1[20];
-               void *util = NULL;
                if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
-                       target = &states->new;
-               else {
-                       target = &states->tracked;
-                       if (hashcmp(sha1, ref->new_sha1))
-                               util = &states;
-               }
-               string_list_append(abbrev_branch(ref->name), target)->util = util;
+                       string_list_append(abbrev_branch(ref->name), &states->new);
+               else
+                       string_list_append(abbrev_branch(ref->name), &states->tracked);
        }
        free_refs(fetch_map);
  
+       sort_string_list(&states->new);
+       sort_string_list(&states->tracked);
        for_each_ref(handle_one_branch, states);
        sort_string_list(&states->stale);
  
        return 0;
  }
  
+ struct push_info {
+       char *dest;
+       int forced;
+       enum {
+               PUSH_STATUS_CREATE = 0,
+               PUSH_STATUS_DELETE,
+               PUSH_STATUS_UPTODATE,
+               PUSH_STATUS_FASTFORWARD,
+               PUSH_STATUS_OUTOFDATE,
+               PUSH_STATUS_NOTQUERIED,
+       } status;
+ };
+ static int get_push_ref_states(const struct ref *remote_refs,
+       struct ref_states *states)
+ {
+       struct remote *remote = states->remote;
+       struct ref *ref, *local_refs, *push_map, **push_tail;
+       if (remote->mirror)
+               return 0;
+       local_refs = get_local_heads();
+       ref = push_map = copy_ref_list(remote_refs);
+       while (ref->next)
+               ref = ref->next;
+       push_tail = &ref->next;
+       match_refs(local_refs, push_map, &push_tail, remote->push_refspec_nr,
+                  remote->push_refspec, MATCH_REFS_NONE);
+       states->push.strdup_strings = 1;
+       for (ref = push_map; ref; ref = ref->next) {
+               struct string_list_item *item;
+               struct push_info *info;
+               if (!ref->peer_ref)
+                       continue;
+               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+               item = string_list_append(abbrev_branch(ref->peer_ref->name),
+                                         &states->push);
+               item->util = xcalloc(sizeof(struct push_info), 1);
+               info = item->util;
+               info->forced = ref->force;
+               info->dest = xstrdup(abbrev_branch(ref->name));
+               if (is_null_sha1(ref->new_sha1)) {
+                       info->status = PUSH_STATUS_DELETE;
+               } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
+                       info->status = PUSH_STATUS_UPTODATE;
+               else if (is_null_sha1(ref->old_sha1))
+                       info->status = PUSH_STATUS_CREATE;
+               else if (has_sha1_file(ref->old_sha1) &&
+                        ref_newer(ref->new_sha1, ref->old_sha1))
+                       info->status = PUSH_STATUS_FASTFORWARD;
+               else
+                       info->status = PUSH_STATUS_OUTOFDATE;
+       }
+       free_refs(local_refs);
+       free_refs(push_map);
+       return 0;
+ }
+ static int get_push_ref_states_noquery(struct ref_states *states)
+ {
+       int i;
+       struct remote *remote = states->remote;
+       struct string_list_item *item;
+       struct push_info *info;
+       if (remote->mirror)
+               return 0;
+       states->push.strdup_strings = 1;
+       if (!remote->push_refspec_nr) {
+               item = string_list_append("(matching)", &states->push);
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->status = PUSH_STATUS_NOTQUERIED;
+               info->dest = xstrdup(item->string);
+       }
+       for (i = 0; i < remote->push_refspec_nr; i++) {
+               struct refspec *spec = remote->push + i;
+               char buf[PATH_MAX];
+               if (spec->matching)
+                       item = string_list_append("(matching)", &states->push);
+               else if (spec->pattern) {
+                       snprintf(buf, (sizeof(buf)), "%s*", spec->src);
+                       item = string_list_append(buf, &states->push);
+                       snprintf(buf, (sizeof(buf)), "%s*", spec->dst);
+               } else if (strlen(spec->src))
+                       item = string_list_append(spec->src, &states->push);
+               else
+                       item = string_list_append("(delete)", &states->push);
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->forced = spec->force;
+               info->status = PUSH_STATUS_NOTQUERIED;
+               if (spec->pattern)
+                       info->dest = xstrdup(buf);
+               else
+                       info->dest = xstrdup(spec->dst ? spec->dst : item->string);
+       }
+       return 0;
+ }
+ static int get_head_names(const struct ref *remote_refs, struct ref_states *states)
+ {
+       struct ref *ref, *matches;
+       struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+       struct refspec refspec;
+       refspec.force = 0;
+       refspec.pattern = 1;
+       refspec.src = refspec.dst = "refs/heads/";
+       states->heads.strdup_strings = 1;
+       get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+       matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+                                   fetch_map, 1);
+       for(ref = matches; ref; ref = ref->next)
+               string_list_append(abbrev_branch(ref->name), &states->heads);
+       free_refs(fetch_map);
+       free_refs(matches);
+       return 0;
+ }
  struct known_remote {
        struct known_remote *next;
        struct remote *remote;
@@@ -466,7 -598,7 +598,7 @@@ static int mv(int argc, const char **ar
        for (i = 0; i < branch_list.nr; i++) {
                struct string_list_item *item = branch_list.items + i;
                struct branch_info *info = item->util;
-               if (info->remote && !strcmp(info->remote, rename.old)) {
+               if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
                        strbuf_reset(&buf);
                        strbuf_addf(&buf, "branch.%s.remote", item->string);
                        if (git_config_set(buf.buf, rename.new)) {
                struct string_list_item *item = remote_branches.items + i;
                int flag = 0;
                unsigned char sha1[20];
 -              const char *symref;
  
 -              symref = resolve_ref(item->string, sha1, 1, &flag);
 +              resolve_ref(item->string, sha1, 1, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
                if (delete_ref(item->string, NULL, REF_NODEREF))
@@@ -575,7 -708,7 +707,7 @@@ static int rm(int argc, const char **ar
        for (i = 0; i < branch_list.nr; i++) {
                struct string_list_item *item = branch_list.items + i;
                struct branch_info *info = item->util;
-               if (info->remote && !strcmp(info->remote, remote->name)) {
+               if (info->remote_name && !strcmp(info->remote_name, remote->name)) {
                        const char *keys[] = { "remote", "merge", NULL }, **k;
                        for (k = keys; *k; k++) {
                                strbuf_reset(&buf);
        return result;
  }
  
- static void show_list(const char *title, struct string_list *list,
-                     const char *extra_arg)
+ void clear_push_info(void *util, const char *string)
  {
-       int i;
+       struct push_info *info = util;
+       free(info->dest);
+       free(info);
+ }
  
-       if (!list->nr)
-               return;
+ static void free_remote_ref_states(struct ref_states *states)
+ {
+       string_list_clear(&states->new, 0);
+       string_list_clear(&states->stale, 0);
+       string_list_clear(&states->tracked, 0);
+       string_list_clear(&states->heads, 0);
+       string_list_clear_func(&states->push, clear_push_info);
+ }
  
-       printf(title, list->nr > 1 ? "es" : "", extra_arg);
-       printf("\n");
-       for (i = 0; i < list->nr; i++)
-               printf("    %s\n", list->items[i].string);
+ static int append_ref_to_tracked_list(const char *refname,
+       const unsigned char *sha1, int flags, void *cb_data)
+ {
+       struct ref_states *states = cb_data;
+       struct refspec refspec;
+       if (flags & REF_ISSYMREF)
+               return 0;
+       memset(&refspec, 0, sizeof(refspec));
+       refspec.dst = (char *)refname;
+       if (!remote_find_tracking(states->remote, &refspec))
+               string_list_append(abbrev_branch(refspec.src), &states->tracked);
+       return 0;
  }
  
  static int get_remote_ref_states(const char *name,
                                 int query)
  {
        struct transport *transport;
-       const struct ref *ref;
+       const struct ref *remote_refs;
  
        states->remote = remote_get(name);
        if (!states->remote)
        if (query) {
                transport = transport_get(NULL, states->remote->url_nr > 0 ?
                        states->remote->url[0] : NULL);
-               ref = transport_get_remote_refs(transport);
+               remote_refs = transport_get_remote_refs(transport);
                transport_disconnect(transport);
  
-               get_ref_states(ref, states);
+               states->queried = 1;
+               if (query & GET_REF_STATES)
+                       get_ref_states(remote_refs, states);
+               if (query & GET_HEAD_NAMES)
+                       get_head_names(remote_refs, states);
+               if (query & GET_PUSH_REF_STATES)
+                       get_push_ref_states(remote_refs, states);
+       } else {
+               for_each_ref(append_ref_to_tracked_list, states);
+               sort_string_list(&states->tracked);
+               get_push_ref_states_noquery(states);
        }
  
        return 0;
  }
  
- static int append_ref_to_tracked_list(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
+ struct show_info {
+       struct string_list *list;
+       struct ref_states *states;
+       int width, width2;
+       int any_rebase;
+ };
+ int add_remote_to_show_info(struct string_list_item *item, void *cb_data)
  {
-       struct ref_states *states = cb_data;
-       struct refspec refspec;
+       struct show_info *info = cb_data;
+       int n = strlen(item->string);
+       if (n > info->width)
+               info->width = n;
+       string_list_insert(item->string, info->list);
+       return 0;
+ }
  
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (!remote_find_tracking(states->remote, &refspec))
-               string_list_append(abbrev_branch(refspec.src), &states->tracked);
+ int show_remote_info_item(struct string_list_item *item, void *cb_data)
+ {
+       struct show_info *info = cb_data;
+       struct ref_states *states = info->states;
+       const char *name = item->string;
+       if (states->queried) {
+               const char *fmt = "%s";
+               const char *arg = "";
+               if (string_list_has_string(&states->new, name)) {
+                       fmt = " new (next fetch will store in remotes/%s)";
+                       arg = states->remote->name;
+               } else if (string_list_has_string(&states->tracked, name))
+                       arg = " tracked";
+               else if (string_list_has_string(&states->stale, name))
+                       arg = " stale (use 'git remote prune' to remove)";
+               else
+                       arg = " ???";
+               printf("    %-*s", info->width, name);
+               printf(fmt, arg);
+               printf("\n");
+       } else
+               printf("    %s\n", name);
+       return 0;
+ }
+ int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data)
+ {
+       struct show_info *show_info = cb_data;
+       struct ref_states *states = show_info->states;
+       struct branch_info *branch_info = branch_item->util;
+       struct string_list_item *item;
+       int n;
+       if (!branch_info->merge.nr || !branch_info->remote_name ||
+           strcmp(states->remote->name, branch_info->remote_name))
+               return 0;
+       if ((n = strlen(branch_item->string)) > show_info->width)
+               show_info->width = n;
+       if (branch_info->rebase)
+               show_info->any_rebase = 1;
+       item = string_list_insert(branch_item->string, show_info->list);
+       item->util = branch_info;
+       return 0;
+ }
+ int show_local_info_item(struct string_list_item *item, void *cb_data)
+ {
+       struct show_info *show_info = cb_data;
+       struct branch_info *branch_info = item->util;
+       struct string_list *merge = &branch_info->merge;
+       const char *also;
+       int i;
+       if (branch_info->rebase && branch_info->merge.nr > 1) {
+               error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+                       item->string);
+               return 0;
+       }
+       printf("    %-*s ", show_info->width, item->string);
+       if (branch_info->rebase) {
+               printf("rebases onto remote %s\n", merge->items[0].string);
+               return 0;
+       } else if (show_info->any_rebase) {
+               printf(" merges with remote %s\n", merge->items[0].string);
+               also = "    and with remote";
+       } else {
+               printf("merges with remote %s\n", merge->items[0].string);
+               also = "   and with remote";
+       }
+       for (i = 1; i < merge->nr; i++)
+               printf("    %-*s %s %s\n", show_info->width, "", also,
+                      merge->items[i].string);
+       return 0;
+ }
  
+ int add_push_to_show_info(struct string_list_item *push_item, void *cb_data)
+ {
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = push_item->util;
+       struct string_list_item *item;
+       int n;
+       if ((n = strlen(push_item->string)) > show_info->width)
+               show_info->width = n;
+       if ((n = strlen(push_info->dest)) > show_info->width2)
+               show_info->width2 = n;
+       item = string_list_append(push_item->string, show_info->list);
+       item->util = push_item->util;
+       return 0;
+ }
+ int show_push_info_item(struct string_list_item *item, void *cb_data)
+ {
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = item->util;
+       char *src = item->string, *status = NULL;
+       switch (push_info->status) {
+       case PUSH_STATUS_CREATE:
+               status = "create";
+               break;
+       case PUSH_STATUS_DELETE:
+               status = "delete";
+               src = "(none)";
+               break;
+       case PUSH_STATUS_UPTODATE:
+               status = "up to date";
+               break;
+       case PUSH_STATUS_FASTFORWARD:
+               status = "fast forwardable";
+               break;
+       case PUSH_STATUS_OUTOFDATE:
+               status = "local out of date";
+               break;
+       case PUSH_STATUS_NOTQUERIED:
+               break;
+       }
+       if (status)
+               printf("    %-*s %s to %-*s (%s)\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       show_info->width2, push_info->dest, status);
+       else
+               printf("    %-*s %s to %s\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       push_info->dest);
        return 0;
  }
  
  static int show(int argc, const char **argv)
  {
-       int no_query = 0, result = 0;
+       int no_query = 0, result = 0, query_flag = 0;
        struct option options[] = {
                OPT_GROUP("show specific options"),
                OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
                OPT_END()
        };
        struct ref_states states;
+       struct string_list info_list = { NULL, 0, 0, 0 };
+       struct show_info info;
  
        argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
  
        if (argc < 1)
                return show_all();
  
+       if (!no_query)
+               query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
        memset(&states, 0, sizeof(states));
+       memset(&info, 0, sizeof(info));
+       info.states = &states;
+       info.list = &info_list;
        for (; argc; argc--, argv++) {
                int i;
  
-               get_remote_ref_states(*argv, &states, !no_query);
+               get_remote_ref_states(*argv, &states, query_flag);
  
                printf("* remote %s\n  URL: %s\n", *argv,
                        states.remote->url_nr > 0 ?
                                states.remote->url[0] : "(no URL)");
-               for (i = 0; i < branch_list.nr; i++) {
-                       struct string_list_item *branch = branch_list.items + i;
-                       struct branch_info *info = branch->util;
-                       int j;
-                       if (!info->merge.nr || strcmp(*argv, info->remote))
-                               continue;
-                       printf("  Remote branch%s merged with 'git pull' "
-                               "while on branch %s\n   ",
-                               info->merge.nr > 1 ? "es" : "",
-                               branch->string);
-                       for (j = 0; j < info->merge.nr; j++)
-                               printf(" %s", info->merge.items[j].string);
-                       printf("\n");
+               if (no_query)
+                       printf("  HEAD branch: (not queried)\n");
+               else if (!states.heads.nr)
+                       printf("  HEAD branch: (unknown)\n");
+               else if (states.heads.nr == 1)
+                       printf("  HEAD branch: %s\n", states.heads.items[0].string);
+               else {
+                       printf("  HEAD branch (remote HEAD is ambiguous,"
+                              " may be one of the following):\n");
+                       for (i = 0; i < states.heads.nr; i++)
+                               printf("    %s\n", states.heads.items[i].string);
                }
  
-               if (!no_query) {
-                       show_list("  New remote branch%s (next fetch "
-                               "will store in remotes/%s)",
-                               &states.new, states.remote->name);
-                       show_list("  Stale tracking branch%s (use 'git remote "
-                               "prune')", &states.stale, "");
-               }
+               /* remote branch info */
+               info.width = 0;
+               for_each_string_list(add_remote_to_show_info, &states.new, &info);
+               for_each_string_list(add_remote_to_show_info, &states.tracked, &info);
+               for_each_string_list(add_remote_to_show_info, &states.stale, &info);
+               if (info.list->nr)
+                       printf("  Remote branch%s:%s\n",
+                              info.list->nr > 1 ? "es" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(show_remote_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+               /* git pull info */
+               info.width = 0;
+               info.any_rebase = 0;
+               for_each_string_list(add_local_to_show_info, &branch_list, &info);
+               if (info.list->nr)
+                       printf("  Local branch%s configured for 'git pull':\n",
+                              info.list->nr > 1 ? "es" : "");
+               for_each_string_list(show_local_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+               /* git push info */
+               if (states.remote->mirror)
+                       printf("  Local refs will be mirrored by 'git push'\n");
+               info.width = info.width2 = 0;
+               for_each_string_list(add_push_to_show_info, &states.push, &info);
+               sort_string_list(info.list);
+               if (info.list->nr)
+                       printf("  Local ref%s configured for 'git push'%s:\n",
+                               info.list->nr > 1 ? "s" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(show_push_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+               free_remote_ref_states(&states);
+       }
  
-               if (no_query)
-                       for_each_ref(append_ref_to_tracked_list, &states);
-               show_list("  Tracked remote branch%s", &states.tracked, "");
-               if (states.remote->push_refspec_nr) {
-                       printf("  Local branch%s pushed with 'git push'\n",
-                               states.remote->push_refspec_nr > 1 ?
-                                       "es" : "");
-                       for (i = 0; i < states.remote->push_refspec_nr; i++) {
-                               struct refspec *spec = states.remote->push + i;
-                               printf("    %s%s%s%s\n",
-                                      spec->force ? "+" : "",
-                                      abbrev_branch(spec->src),
-                                      spec->dst ? ":" : "",
-                                      spec->dst ? abbrev_branch(spec->dst) : "");
-                       }
-               }
+       return result;
+ }
  
-               /* NEEDSWORK: free remote */
-               string_list_clear(&states.new, 0);
-               string_list_clear(&states.stale, 0);
-               string_list_clear(&states.tracked, 0);
+ static int set_head(int argc, const char **argv)
+ {
+       int i, opt_a = 0, opt_d = 0, result = 0;
+       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+       char *head_name = NULL;
+       struct option options[] = {
+               OPT_GROUP("set-head specific options"),
+               OPT_BOOLEAN('a', "auto", &opt_a,
+                           "set refs/remotes/<name>/HEAD according to remote"),
+               OPT_BOOLEAN('d', "delete", &opt_d,
+                           "delete refs/remotes/<name>/HEAD"),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
+       if (argc)
+               strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+       if (!opt_a && !opt_d && argc == 2) {
+               head_name = xstrdup(argv[1]);
+       } else if (opt_a && !opt_d && argc == 1) {
+               struct ref_states states;
+               memset(&states, 0, sizeof(states));
+               get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
+               if (!states.heads.nr)
+                       result |= error("Cannot determine remote HEAD");
+               else if (states.heads.nr > 1) {
+                       result |= error("Multiple remote HEAD branches. "
+                                       "Please choose one explicitly with:");
+                       for (i = 0; i < states.heads.nr; i++)
+                               fprintf(stderr, "  git remote set-head %s %s\n",
+                                       argv[0], states.heads.items[i].string);
+               } else
+                       head_name = xstrdup(states.heads.items[0].string);
+               free_remote_ref_states(&states);
+       } else if (opt_d && !opt_a && argc == 1) {
+               if (delete_ref(buf.buf, NULL, REF_NODEREF))
+                       result |= error("Could not delete %s", buf.buf);
+       } else
+               usage_with_options(builtin_remote_usage, options);
+       if (head_name) {
+               unsigned char sha1[20];
+               strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+               /* make sure it's valid */
+               if (!resolve_ref(buf2.buf, sha1, 1, NULL))
+                       result |= error("Not a valid ref: %s", buf2.buf);
+               else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
+                       result |= error("Could not setup %s", buf.buf);
+               if (opt_a)
+                       printf("%s/HEAD set to %s\n", argv[0], head_name);
+               free(head_name);
        }
  
+       strbuf_release(&buf);
+       strbuf_release(&buf2);
        return result;
  }
  
@@@ -770,7 -1138,7 +1137,7 @@@ static int prune(int argc, const char *
        for (; argc; argc--, argv++) {
                int i;
  
-               get_remote_ref_states(*argv, &states, 1);
+               get_remote_ref_states(*argv, &states, GET_REF_STATES);
  
                if (states.stale.nr) {
                        printf("Pruning %s\n", *argv);
                        warn_dangling_symref(dangling_msg, refname);
                }
  
-               /* NEEDSWORK: free remote */
-               string_list_clear(&states.new, 0);
-               string_list_clear(&states.stale, 0);
-               string_list_clear(&states.tracked, 0);
+               free_remote_ref_states(&states);
        }
  
        return result;
@@@ -919,6 -1284,8 +1283,8 @@@ int cmd_remote(int argc, const char **a
                result = mv(argc, argv);
        else if (!strcmp(argv[0], "rm"))
                result = rm(argc, argv);
+       else if (!strcmp(argv[0], "set-head"))
+               result = set_head(argc, argv);
        else if (!strcmp(argv[0], "show"))
                result = show(argc, argv);
        else if (!strcmp(argv[0], "prune"))
diff --combined cache.h
index bd4c390d577f0ad1175afb868a0e4ef7a02006d1,609380d93500fc8de451ea62b56e9fdc53900752..39789ee94a2f2e7dd1600115f4033ccf0e52c07b
+++ b/cache.h
@@@ -140,8 -140,8 +140,8 @@@ struct ondisk_cache_entry_extended 
  };
  
  struct cache_entry {
 -      unsigned int ce_ctime;
 -      unsigned int ce_mtime;
 +      struct cache_time ce_ctime;
 +      struct cache_time ce_mtime;
        unsigned int ce_dev;
        unsigned int ce_ino;
        unsigned int ce_mode;
@@@ -282,7 -282,7 +282,7 @@@ struct index_state 
        struct cache_entry **cache;
        unsigned int cache_nr, cache_alloc, cache_changed;
        struct cache_tree *cache_tree;
 -      time_t timestamp;
 +      struct cache_time timestamp;
        void *alloc;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
@@@ -428,7 -428,7 +428,7 @@@ extern int read_index_preload(struct in
  extern int read_index_from(struct index_state *, const char *path);
  extern int is_index_unborn(struct index_state *);
  extern int read_index_unmerged(struct index_state *);
 -extern int write_index(const struct index_state *, int newfd);
 +extern int write_index(struct index_state *, int newfd);
  extern int discard_index(struct index_state *);
  extern int unmerged_index(const struct index_state *);
  extern int verify_path(const char *path);
@@@ -443,7 -443,6 +443,7 @@@ extern int add_index_entry(struct index
  extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
  extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
  extern int remove_index_entry_at(struct index_state *, int pos);
 +extern void remove_marked_cache_entries(struct index_state *istate);
  extern int remove_file_from_index(struct index_state *, const char *path);
  #define ADD_CACHE_VERBOSE 1
  #define ADD_CACHE_PRETEND 2
@@@ -645,8 -644,7 +645,8 @@@ extern int check_sha1_signature(const u
  
  extern int move_temp_to_file(const char *tmpfile, const char *filename);
  
 -extern int has_sha1_pack(const unsigned char *sha1, const char **ignore);
 +extern int has_sha1_pack(const unsigned char *sha1);
 +extern int has_sha1_kept_pack(const unsigned char *sha1);
  extern int has_sha1_file(const unsigned char *sha1);
  extern int has_loose_object_nonlocal(const unsigned char *sha1);
  
@@@ -726,13 -724,11 +726,13 @@@ struct checkout 
  };
  
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
 -extern int has_symlink_leading_path(int len, const char *name);
 -extern int has_symlink_or_noent_leading_path(int len, const char *name);
 -extern int has_dirs_only_path(int len, const char *name, int prefix_len);
 -extern void invalidate_lstat_cache(int len, const char *name);
 +extern int has_symlink_leading_path(const char *name, int len);
 +extern int has_symlink_or_noent_leading_path(const char *name, int len);
 +extern int has_dirs_only_path(const char *name, int len, int prefix_len);
 +extern void invalidate_lstat_cache(const char *name, int len);
  extern void clear_lstat_cache(void);
 +extern void schedule_dir_for_removal(const char *name, int len);
 +extern void remove_scheduled_dirs(void);
  
  extern struct alternate_object_database {
        struct alternate_object_database *next;
@@@ -805,7 -801,7 +805,7 @@@ struct ref 
  #define REF_HEADS     (1u << 1)
  #define REF_TAGS      (1u << 2)
  
- extern struct ref *find_ref_by_name(struct ref *list, const char *name);
+ extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
  
  #define CONNECT_VERBOSE       (1u << 0)
  extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
@@@ -843,6 -839,7 +843,6 @@@ extern void *unpack_entry(struct packed
  extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
  extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
  extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
 -extern int matches_pack_name(struct packed_git *p, const char *name);
  
  /* Dumb servers support */
  extern int update_server_info(int);
index 271b911f7a1300057c0b100c1bdfe5d68a0e3248,15b938b902fc842fd1adac662be1023cd2839268..ed235f759645c0f657d47b3f0d73a3bcfbf6b1d6
@@@ -62,7 -62,7 +62,7 @@@ esa
  __gitdir ()
  {
        if [ -z "${1-}" ]; then
 -              if [ -n "$__git_dir" ]; then
 +              if [ -n "${__git_dir-}" ]; then
                        echo "$__git_dir"
                elif [ -d .git ]; then
                        echo .git
  # returns text to add to bash PS1 prompt (includes branch name)
  __git_ps1 ()
  {
 -      local g="$(git rev-parse --git-dir 2>/dev/null)"
 +      local g="$(__gitdir)"
        if [ -n "$g" ]; then
                local r
                local b
 -              if [ -d "$g/rebase-apply" ]
 -              then
 -                      if test -f "$g/rebase-apply/rebasing"
 -                      then
 +              if [ -d "$g/rebase-apply" ]; then
 +                      if [ -f "$g/rebase-apply/rebasing" ]; then
                                r="|REBASE"
 -                      elif test -f "$g/rebase-apply/applying"
 -                      then
 +              elif [ -f "$g/rebase-apply/applying" ]; then
                                r="|AM"
                        else
                                r="|AM/REBASE"
                        fi
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
 -              elif [ -f "$g/rebase-merge/interactive" ]
 -              then
 +              elif [ -f "$g/rebase-merge/interactive" ]; then
                        r="|REBASE-i"
                        b="$(cat "$g/rebase-merge/head-name")"
 -              elif [ -d "$g/rebase-merge" ]
 -              then
 +              elif [ -d "$g/rebase-merge" ]; then
                        r="|REBASE-m"
                        b="$(cat "$g/rebase-merge/head-name")"
 -              elif [ -f "$g/MERGE_HEAD" ]
 -              then
 +              elif [ -f "$g/MERGE_HEAD" ]; then
                        r="|MERGING"
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
 -                      if [ -f "$g/BISECT_LOG" ]
 -                      then
 +                      if [ -f "$g/BISECT_LOG" ]; then
                                r="|BISECTING"
                        fi
 -                      if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
 -                      then
 -                              if ! b="$(git describe --exact-match HEAD 2>/dev/null)"
 -                              then
 -                                      b="$(cut -c1-7 "$g/HEAD")..."
 +                      if ! b="$(git symbolic-ref HEAD 2>/dev/null)"; then
 +                              if ! b="$(git describe --exact-match HEAD 2>/dev/null)"; then
 +                                      if [ -r "$g/HEAD" ]; then
 +                                              b="$(cut -c1-7 "$g/HEAD")..."
 +                                      fi
                                fi
                        fi
                fi
  
                local w
                local i
 +              local c
  
 -              if test -n "${GIT_PS1_SHOWDIRTYSTATE-}"; then
 -                      if test "$(git config --bool bash.showDirtyState)" != "false"; then
 -                              git diff --no-ext-diff --ignore-submodules \
 -                                      --quiet --exit-code || w="*"
 -                              if git rev-parse --quiet --verify HEAD >/dev/null; then
 -                                      git diff-index --cached --quiet \
 -                                              --ignore-submodules HEAD -- || i="+"
 -                              else
 -                                      i="#"
 +              if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
 +                      if [ "true" = "$(git config --bool core.bare 2>/dev/null)" ]; then
 +                              c="BARE:"
 +                      else
 +                              b="GIT_DIR!"
 +                      fi
 +              elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
 +                      if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
 +                              if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
 +                                      git diff --no-ext-diff --ignore-submodules \
 +                                              --quiet --exit-code || w="*"
 +                                      if git rev-parse --quiet --verify HEAD >/dev/null; then
 +                                              git diff-index --cached --quiet \
 +                                                      --ignore-submodules HEAD -- || i="+"
 +                                      else
 +                                              i="#"
 +                                      fi
                                fi
                        fi
                fi
  
 -              if [ -n "${1-}" ]; then
 -                      printf "$1" "${b##refs/heads/}$w$i$r"
 -              else
 -                      printf " (%s)" "${b##refs/heads/}$w$i$r"
 +              if [ -n "$b" ]; then
 +                      if [ -n "${1-}" ]; then
 +                              printf "$1" "$c${b##refs/heads/}$w$i$r"
 +                      else
 +                              printf " (%s)" "$c${b##refs/heads/}$w$i$r"
 +                      fi
                fi
        fi
  }
@@@ -303,7 -299,7 +303,7 @@@ __git_remotes (
  
  __git_merge_strategies ()
  {
 -      if [ -n "$__git_merge_strategylist" ]; then
 +      if [ -n "${__git_merge_strategylist-}" ]; then
                echo "$__git_merge_strategylist"
                return
        fi
@@@ -387,88 -383,9 +387,88 @@@ __git_complete_revlist (
        esac
  }
  
 +__git_complete_remote_or_refspec ()
 +{
 +      local cmd="${COMP_WORDS[1]}"
 +      local cur="${COMP_WORDS[COMP_CWORD]}"
 +      local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
 +      while [ $c -lt $COMP_CWORD ]; do
 +              i="${COMP_WORDS[c]}"
 +              case "$i" in
 +              --all|--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
 +              -*) ;;
 +              *) remote="$i"; break ;;
 +              esac
 +              c=$((++c))
 +      done
 +      if [ -z "$remote" ]; then
 +              __gitcomp "$(__git_remotes)"
 +              return
 +      fi
 +      if [ $no_complete_refspec = 1 ]; then
 +              COMPREPLY=()
 +              return
 +      fi
 +      [ "$remote" = "." ] && remote=
 +      case "$cur" in
 +      *:*)
 +              case "$COMP_WORDBREAKS" in
 +              *:*) : great ;;
 +              *)   pfx="${cur%%:*}:" ;;
 +              esac
 +              cur="${cur#*:}"
 +              lhs=0
 +              ;;
 +      +*)
 +              pfx="+"
 +              cur="${cur#+}"
 +              ;;
 +      esac
 +      case "$cmd" in
 +      fetch)
 +              if [ $lhs = 1 ]; then
 +                      __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur"
 +              else
 +                      __gitcomp "$(__git_refs)" "$pfx" "$cur"
 +              fi
 +              ;;
 +      pull)
 +              if [ $lhs = 1 ]; then
 +                      __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
 +              else
 +                      __gitcomp "$(__git_refs)" "$pfx" "$cur"
 +              fi
 +              ;;
 +      push)
 +              if [ $lhs = 1 ]; then
 +                      __gitcomp "$(__git_refs)" "$pfx" "$cur"
 +              else
 +                      __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
 +              fi
 +              ;;
 +      esac
 +}
 +
 +__git_complete_strategy ()
 +{
 +      case "${COMP_WORDS[COMP_CWORD-1]}" in
 +      -s|--strategy)
 +              __gitcomp "$(__git_merge_strategies)"
 +              return 0
 +      esac
 +      local cur="${COMP_WORDS[COMP_CWORD]}"
 +      case "$cur" in
 +      --strategy=*)
 +              __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
 +              return 0
 +              ;;
 +      esac
 +      return 1
 +}
 +
  __git_all_commands ()
  {
 -      if [ -n "$__git_all_commandlist" ]; then
 +      if [ -n "${__git_all_commandlist-}" ]; then
                echo "$__git_all_commandlist"
                return
        fi
@@@ -486,7 -403,7 +486,7 @@@ __git_all_commandlist="$(__git_all_comm
  
  __git_porcelain_commands ()
  {
 -      if [ -n "$__git_porcelain_commandlist" ]; then
 +      if [ -n "${__git_porcelain_commandlist-}" ]; then
                echo "$__git_porcelain_commandlist"
                return
        fi
@@@ -909,21 -826,27 +909,21 @@@ _git_diff (
        __git_complete_file
  }
  
 +__git_fetch_options="
 +      --quiet --verbose --append --upload-pack --force --keep --depth=
 +      --tags --no-tags
 +"
 +
  _git_fetch ()
  {
        local cur="${COMP_WORDS[COMP_CWORD]}"
 -
 -      if [ "$COMP_CWORD" = 2 ]; then
 -              __gitcomp "$(__git_remotes)"
 -      else
 -              case "$cur" in
 -              *:*)
 -                      local pfx=""
 -                      case "$COMP_WORDBREAKS" in
 -                      *:*) : great ;;
 -                      *)   pfx="${cur%%:*}:" ;;
 -                      esac
 -                      __gitcomp "$(__git_refs)" "$pfx" "${cur#*:}"
 -                      ;;
 -              *)
 -                      __gitcomp "$(__git_refs2 "${COMP_WORDS[2]}")"
 -                      ;;
 -              esac
 -      fi
 +      case "$cur" in
 +      --*)
 +              __gitcomp "$__git_fetch_options"
 +              return
 +              ;;
 +      esac
 +      __git_complete_remote_or_refspec
  }
  
  _git_format_patch ()
@@@ -1091,11 -1014,6 +1091,11 @@@ _git_log (
                        " "" "${cur##--pretty=}"
                return
                ;;
 +      --format=*)
 +              __gitcomp "$__git_log_pretty_formats
 +                      " "" "${cur##--format=}"
 +              return
 +              ;;
        --date=*)
                __gitcomp "
                        relative iso8601 rfc2822 short local default
                        --follow
                        --abbrev-commit --abbrev=
                        --relative-date --date=
 -                      --pretty=
 +                      --pretty= --format= --oneline
                        --cherry-pick
                        --graph
                        --decorate
        __git_complete_revlist
  }
  
 +__git_merge_options="
 +      --no-commit --no-stat --log --no-log --squash --strategy
 +      --commit --stat --no-squash --ff --no-ff
 +"
 +
  _git_merge ()
  {
 +      __git_complete_strategy && return
 +
        local cur="${COMP_WORDS[COMP_CWORD]}"
 -      case "${COMP_WORDS[COMP_CWORD-1]}" in
 -      -s|--strategy)
 -              __gitcomp "$(__git_merge_strategies)"
 -              return
 -      esac
        case "$cur" in
 -      --strategy=*)
 -              __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
 -              return
 -              ;;
        --*)
 -              __gitcomp "
 -                      --no-commit --no-stat --log --no-log --squash --strategy
 -                      --commit --stat --no-squash --ff --no-ff
 -                      "
 +              __gitcomp "$__git_merge_options"
                return
        esac
        __gitcomp "$(__git_refs)"
@@@ -1188,44 -1111,40 +1188,44 @@@ _git_name_rev (
  
  _git_pull ()
  {
 -      local cur="${COMP_WORDS[COMP_CWORD]}"
 +      __git_complete_strategy && return
  
 -      if [ "$COMP_CWORD" = 2 ]; then
 -              __gitcomp "$(__git_remotes)"
 -      else
 -              __gitcomp "$(__git_refs "${COMP_WORDS[2]}")"
 -      fi
 +      local cur="${COMP_WORDS[COMP_CWORD]}"
 +      case "$cur" in
 +      --*)
 +              __gitcomp "
 +                      --rebase --no-rebase
 +                      $__git_merge_options
 +                      $__git_fetch_options
 +              "
 +              return
 +              ;;
 +      esac
 +      __git_complete_remote_or_refspec
  }
  
  _git_push ()
  {
        local cur="${COMP_WORDS[COMP_CWORD]}"
 -
 -      if [ "$COMP_CWORD" = 2 ]; then
 +      case "${COMP_WORDS[COMP_CWORD-1]}" in
 +      --repo)
                __gitcomp "$(__git_remotes)"
 -      else
 -              case "$cur" in
 -              *:*)
 -                      local pfx=""
 -                      case "$COMP_WORDBREAKS" in
 -                      *:*) : great ;;
 -                      *)   pfx="${cur%%:*}:" ;;
 -                      esac
 -
 -                      __gitcomp "$(__git_refs "${COMP_WORDS[2]}")" "$pfx" "${cur#*:}"
 -                      ;;
 -              +*)
 -                      __gitcomp "$(__git_refs)" + "${cur#+}"
 -                      ;;
 -              *)
 -                      __gitcomp "$(__git_refs)"
 -                      ;;
 -              esac
 -      fi
 +              return
 +      esac
 +      case "$cur" in
 +      --repo=*)
 +              __gitcomp "$(__git_remotes)" "" "${cur##--repo=}"
 +              return
 +              ;;
 +      --*)
 +              __gitcomp "
 +                      --all --mirror --tags --dry-run --force --verbose
 +                      --receive-pack= --repo=
 +              "
 +              return
 +              ;;
 +      esac
 +      __git_complete_remote_or_refspec
  }
  
  _git_rebase ()
                __gitcomp "--continue --skip --abort"
                return
        fi
 -      case "${COMP_WORDS[COMP_CWORD-1]}" in
 -      -s|--strategy)
 -              __gitcomp "$(__git_merge_strategies)"
 -              return
 -      esac
 +      __git_complete_strategy && return
        case "$cur" in
 -      --strategy=*)
 -              __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
 -              return
 -              ;;
        --*)
                __gitcomp "--onto --merge --strategy --interactive"
                return
@@@ -1516,7 -1443,7 +1516,7 @@@ _git_config (
  
  _git_remote ()
  {
-       local subcommands="add rename rm show prune update"
+       local subcommands="add rename rm show prune update set-head"
        local subcommand="$(__git_find_subcommand "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
@@@ -1614,13 -1541,8 +1614,13 @@@ _git_show (
                        " "" "${cur##--pretty=}"
                return
                ;;
 +      --format=*)
 +              __gitcomp "$__git_log_pretty_formats
 +                      " "" "${cur##--format=}"
 +              return
 +              ;;
        --*)
 -              __gitcomp "--pretty=
 +              __gitcomp "--pretty= --format=
                        $__git_diff_common_options
                        "
                return
@@@ -1919,7 -1841,7 +1919,7 @@@ _gitk (
        __git_has_doubledash && return
  
        local cur="${COMP_WORDS[COMP_CWORD]}"
 -      local g="$(git rev-parse --git-dir 2>/dev/null)"
 +      local g="$(__gitdir)"
        local merge=""
        if [ -f $g/MERGE_HEAD ]; then
                merge="--merge"
diff --combined http-push.c
index 671569594e3f8c08ce7727cd7a2db68aaf1de9c6,392533a017e560a7b6ac7c1b484ee2f608982a27..48e5f38fe0f8848ffe0941d48eae10b8edc14dd8
@@@ -816,7 -816,7 +816,7 @@@ static void finish_request(struct trans
  #ifdef USE_CURL_MULTI
  static int fill_active_slot(void *unused)
  {
 -      struct transfer_request *request = request_queue_head;
 +      struct transfer_request *request;
  
        if (aborted)
                return 0;
@@@ -1792,21 -1792,8 +1792,8 @@@ static int update_remote(unsigned char 
        return 1;
  }
  
- static struct ref *local_refs, **local_tail;
  static struct ref *remote_refs, **remote_tail;
  
- static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
- {
-       struct ref *ref;
-       int len = strlen(refname) + 1;
-       ref = xcalloc(1, sizeof(*ref) + len);
-       hashcpy(ref->new_sha1, sha1);
-       memcpy(ref->name, refname, len);
-       *local_tail = ref;
-       local_tail = &ref->next;
-       return 0;
- }
  static void one_remote_ref(char *refname)
  {
        struct ref *ref;
        remote_tail = &ref->next;
  }
  
- static void get_local_heads(void)
- {
-       local_tail = &local_refs;
-       for_each_ref(one_local_ref, NULL);
- }
  static void get_dav_remote_heads(void)
  {
        remote_tail = &remote_refs;
@@@ -1862,55 -1843,6 +1843,6 @@@ static int is_zero_sha1(const unsigned 
        return 1;
  }
  
- static void unmark_and_free(struct commit_list *list, unsigned int mark)
- {
-       while (list) {
-               struct commit_list *temp = list;
-               temp->item->object.flags &= ~mark;
-               list = temp->next;
-               free(temp);
-       }
- }
- static int ref_newer(const unsigned char *new_sha1,
-                    const unsigned char *old_sha1)
- {
-       struct object *o;
-       struct commit *old, *new;
-       struct commit_list *list, *used;
-       int found = 0;
-       /* Both new and old must be commit-ish and new is descendant of
-        * old.  Otherwise we require --force.
-        */
-       o = deref_tag(parse_object(old_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-       old = (struct commit *) o;
-       o = deref_tag(parse_object(new_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-       new = (struct commit *) o;
-       if (parse_commit(new) < 0)
-               return 0;
-       used = list = NULL;
-       commit_list_insert(new, &list);
-       while (list) {
-               new = pop_most_recent_commit(&list, TMP_MARK);
-               commit_list_insert(new, &used);
-               if (new == old) {
-                       found = 1;
-                       break;
-               }
-       }
-       unmark_and_free(list, TMP_MARK);
-       unmark_and_free(used, TMP_MARK);
-       return found;
- }
  static void add_remote_info_ref(struct remote_ls_ctx *ls)
  {
        struct strbuf *buf = (struct strbuf *)ls->userData;
@@@ -2195,7 -2127,7 +2127,7 @@@ int main(int argc, char **argv
        int rc = 0;
        int i;
        int new_refs;
-       struct ref *ref;
+       struct ref *ref, *local_refs;
        char *rewritten_url = NULL;
  
        git_extract_argv0_path(argv[0]);
                fetch_indices();
  
        /* Get a list of all local and remote heads to validate refspecs */
-       get_local_heads();
+       local_refs = get_local_heads();
        fprintf(stderr, "Fetching remote heads...\n");
        get_dav_remote_heads();
  
diff --combined remote.c
index 7efaa023b985745b5555e35f8d3a8169870571bd,9b8522db35367e5a698bebfcac9c3a107bfa5298..7172835790de6e0ac27a19a14c22350e90cdab65
+++ b/remote.c
@@@ -5,6 -5,7 +5,7 @@@
  #include "diff.h"
  #include "revision.h"
  #include "dir.h"
+ #include "tag.h"
  
  static struct refspec s_tag_refspec = {
        0,
@@@ -495,7 -496,7 +496,7 @@@ static struct refspec *parse_refspec_in
                int is_glob;
                const char *lhs, *rhs;
  
 -              llen = is_glob = 0;
 +              is_glob = 0;
  
                lhs = refspec[i];
                if (*lhs == '+') {
@@@ -778,10 -779,18 +779,18 @@@ struct ref *alloc_ref(const char *name
  
  static struct ref *copy_ref(const struct ref *ref)
  {
-       struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
-       memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1);
-       ret->next = NULL;
-       return ret;
+       struct ref *cpy;
+       size_t len;
+       if (!ref)
+               return NULL;
+       len = strlen(ref->name);
+       cpy = xmalloc(sizeof(struct ref) + len + 1);
+       memcpy(cpy, ref, sizeof(struct ref) + len + 1);
+       cpy->next = NULL;
+       cpy->symref = ref->symref ? xstrdup(ref->symref) : NULL;
+       cpy->remote_status = ref->remote_status ? xstrdup(ref->remote_status) : NULL;
+       cpy->peer_ref = copy_ref(ref->peer_ref);
+       return cpy;
  }
  
  struct ref *copy_ref_list(const struct ref *ref)
@@@ -800,6 -809,7 +809,7 @@@ static void free_ref(struct ref *ref
  {
        if (!ref)
                return;
+       free_ref(ref->peer_ref);
        free(ref->remote_status);
        free(ref->symref);
        free(ref);
@@@ -810,7 -820,6 +820,6 @@@ void free_refs(struct ref *ref
        struct ref *next;
        while (ref) {
                next = ref->next;
-               free(ref->peer_ref);
                free_ref(ref);
                ref = next;
        }
@@@ -927,6 -936,7 +936,7 @@@ static int match_explicit(struct ref *s
                          struct refspec *rs)
  {
        struct ref *matched_src, *matched_dst;
+       int copy_src;
  
        const char *dst_value = rs->dst;
        char *dst_guess;
        matched_src = matched_dst = NULL;
        switch (count_refspec_match(rs->src, src, &matched_src)) {
        case 1:
+               copy_src = 1;
                break;
        case 0:
                /* The source could be in the get_sha1() format
                matched_src = try_explicit_object_name(rs->src);
                if (!matched_src)
                        return error("src refspec %s does not match any.", rs->src);
+               copy_src = 0;
                break;
        default:
                return error("src refspec %s matches more than one.", rs->src);
                return error("dst ref %s receives from more than one src.",
                      matched_dst->name);
        else {
-               matched_dst->peer_ref = matched_src;
+               matched_dst->peer_ref = copy_src ? copy_ref(matched_src) : matched_src;
                matched_dst->force = rs->force;
        }
        return 0;
@@@ -1040,6 -1052,7 +1052,7 @@@ int match_refs(struct ref *src, struct 
        struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
        int send_mirror = flags & MATCH_REFS_MIRROR;
+       int errs;
        static const char *default_refspec[] = { ":", 0 };
  
        if (!nr_refspec) {
                refspec = default_refspec;
        }
        rs = parse_push_refspec(nr_refspec, (const char **) refspec);
-       if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
-               return -1;
+       errs = match_explicit_refs(src, dst, dst_tail, rs, nr_refspec);
  
        /* pick the remainder */
        for ( ; src; src = src->next) {
                        dst_peer = make_linked_ref(dst_name, dst_tail);
                        hashcpy(dst_peer->new_sha1, src->new_sha1);
                }
-               dst_peer->peer_ref = src;
+               dst_peer->peer_ref = copy_ref(src);
                dst_peer->force = pat->force;
        free_name:
                free(dst_name);
        }
+       if (errs)
+               return -1;
        return 0;
  }
  
@@@ -1269,6 -1283,54 +1283,54 @@@ int resolve_remote_symref(struct ref *r
        return 1;
  }
  
+ static void unmark_and_free(struct commit_list *list, unsigned int mark)
+ {
+       while (list) {
+               struct commit_list *temp = list;
+               temp->item->object.flags &= ~mark;
+               list = temp->next;
+               free(temp);
+       }
+ }
+ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
+ {
+       struct object *o;
+       struct commit *old, *new;
+       struct commit_list *list, *used;
+       int found = 0;
+       /* Both new and old must be commit-ish and new is descendant of
+        * old.  Otherwise we require --force.
+        */
+       o = deref_tag(parse_object(old_sha1), NULL, 0);
+       if (!o || o->type != OBJ_COMMIT)
+               return 0;
+       old = (struct commit *) o;
+       o = deref_tag(parse_object(new_sha1), NULL, 0);
+       if (!o || o->type != OBJ_COMMIT)
+               return 0;
+       new = (struct commit *) o;
+       if (parse_commit(new) < 0)
+               return 0;
+       used = list = NULL;
+       commit_list_insert(new, &list);
+       while (list) {
+               new = pop_most_recent_commit(&list, TMP_MARK);
+               commit_list_insert(new, &used);
+               if (new == old) {
+                       found = 1;
+                       break;
+               }
+       }
+       unmark_and_free(list, TMP_MARK);
+       unmark_and_free(used, TMP_MARK);
+       return found;
+ }
  /*
   * Return true if there is anything to report, otherwise false.
   */
@@@ -1376,3 -1438,68 +1438,68 @@@ int format_tracking_info(struct branch 
                            base, num_ours, num_theirs);
        return 1;
  }
+ static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+       struct ref ***local_tail = cb_data;
+       struct ref *ref;
+       int len;
+       /* we already know it starts with refs/ to get here */
+       if (check_ref_format(refname + 5))
+               return 0;
+       len = strlen(refname) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       hashcpy(ref->new_sha1, sha1);
+       memcpy(ref->name, refname, len);
+       **local_tail = ref;
+       *local_tail = &ref->next;
+       return 0;
+ }
+ struct ref *get_local_heads(void)
+ {
+       struct ref *local_refs, **local_tail = &local_refs;
+       for_each_ref(one_local_ref, &local_tail);
+       return local_refs;
+ }
+ struct ref *guess_remote_head(const struct ref *head,
+                             const struct ref *refs,
+                             int all)
+ {
+       const struct ref *r;
+       struct ref *list = NULL;
+       struct ref **tail = &list;
+       if (!head)
+               return NULL;
+       /*
+        * Some transports support directly peeking at
+        * where HEAD points; if that is the case, then
+        * we don't have to guess.
+        */
+       if (head->symref)
+               return copy_ref(find_ref_by_name(refs, head->symref));
+       /* If refs/heads/master could be right, it is. */
+       if (!all) {
+               r = find_ref_by_name(refs, "refs/heads/master");
+               if (r && !hashcmp(r->old_sha1, head->old_sha1))
+                       return copy_ref(r);
+       }
+       /* Look for another ref that points there */
+       for (r = refs; r; r = r->next) {
+               if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) {
+                       *tail = copy_ref(r);
+                       tail = &((*tail)->next);
+                       if (!all)
+                               break;
+               }
+       }
+       return list;
+ }
diff --combined t/t5540-http-push.sh
index 10e5fd0d5a5c38eaa102d88e607f2585e1157526,cefab4543a3cd093b1cde2aa23f96ae2fa2de692..c46592f03de2adfbb04f81ad2167e33a161d6d1b
@@@ -11,6 -11,7 +11,7 @@@ This test runs various sanity checks o
  
  ROOT_PATH="$PWD"
  LIB_HTTPD_DAV=t
+ LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
  
  if git http-push > /dev/null 2>&1 || [ $? -eq 128 ]
  then
  fi
  
  . "$TEST_DIRECTORY"/lib-httpd.sh
- if ! start_httpd >&3 2>&4
- then
-       say "skipping test, web server setup failed"
-       test_done
-       exit
- fi
+ start_httpd
  
  test_expect_success 'setup remote repository' '
        cd "$ROOT_PATH" &&
@@@ -94,15 -89,10 +89,15 @@@ test_expect_success 'MKCOL sends direct
  
  '
  
 -test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
 +x1="[0-9a-f]"
 +x2="$x1$x1"
 +x5="$x1$x1$x1$x1$x1"
 +x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
 +x40="$x38$x2"
  
 -      grep -P "\"(?:PUT|MOVE) .+objects/[\da-z]{2}/[\da-z]{38}_[\da-z\-]{40} HTTP/[0-9.]+\" 20\d" \
 -              < "$HTTPD_ROOT_PATH"/access.log
 +test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
 +      sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log |
 +      grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] "
  
  '