Merge branch 'jc/pull-signed-tag'
authorJunio C Hamano <gitster@pobox.com>
Fri, 9 Dec 2011 21:37:09 +0000 (13:37 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 9 Dec 2011 21:37:09 +0000 (13:37 -0800)
* jc/pull-signed-tag:
  commit-tree: teach -m/-F options to read logs from elsewhere
  commit-tree: update the command line parsing
  commit: teach --amend to carry forward extra headers
  merge: force edit and no-ff mode when merging a tag object
  commit: copy merged signed tags to headers of merge commit
  merge: record tag objects without peeling in MERGE_HEAD
  merge: make usage of commit->util more extensible
  fmt-merge-msg: Add contents of merged tag in the merge message
  fmt-merge-msg: package options into a structure
  fmt-merge-msg: avoid early returns
  refs DWIMmery: use the same rule for both "git fetch" and others
  fetch: allow "git fetch $there v1.0" to fetch a tag
  merge: notice local merging of tags and keep it unwrapped
  fetch: do not store peeled tag object names in FETCH_HEAD
  Split GPG interface into its own helper library

Conflicts:
builtin/fmt-merge-msg.c
builtin/merge.c

1  2 
Makefile
builtin/commit.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/merge.c
cache.h
refs.c
t/t5510-fetch.sh

diff --combined Makefile
+++ b/Makefile
@@@ -57,8 -57,8 +57,8 @@@ all:
  #
  # Define NO_STRLCPY if you don't have strlcpy.
  #
 -# Define NO_STRTOUMAX if you don't have strtoumax in the C library.
 -# If your compiler also does not support long long or does not have
 +# Define NO_STRTOUMAX if you don't have both strtoimax and strtoumax in the
 +# C library. If your compiler also does not support long long or does not have
  # strtoull, define NO_STRTOULL.
  #
  # Define NO_SETENV if you don't have setenv in the C library.
  #   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
  #   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
  #
 +# Define COMPUTE_HEADER_DEPENDENCIES to "yes" if you want dependencies on
 +# header files to be automatically computed, to avoid rebuilding objects when
 +# an unrelated header file changes.  Define it to "no" to use the hard-coded
 +# dependency rules.  The default is "auto", which means to use computed header
 +# dependencies if your compiler is detected to support it.
 +#
  # Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
  # dependency rules.
  #
@@@ -521,10 -515,9 +521,10 @@@ LIB_H += compat/mingw.
  LIB_H += compat/obstack.h
  LIB_H += compat/win32/pthread.h
  LIB_H += compat/win32/syslog.h
 -LIB_H += compat/win32/sys/poll.h
 +LIB_H += compat/win32/poll.h
  LIB_H += compat/win32/dirent.h
  LIB_H += connected.h
 +LIB_H += convert.h
  LIB_H += csum-file.h
  LIB_H += decorate.h
  LIB_H += delta.h
@@@ -532,10 -525,10 +532,11 @@@ LIB_H += diffcore.
  LIB_H += diff.h
  LIB_H += dir.h
  LIB_H += exec_cmd.h
 +LIB_H += fmt-merge-msg.h
  LIB_H += fsck.h
  LIB_H += gettext.h
  LIB_H += git-compat-util.h
+ LIB_H += gpg-interface.h
  LIB_H += graph.h
  LIB_H += grep.h
  LIB_H += hash.h
@@@ -629,6 -622,7 +630,7 @@@ LIB_OBJS += entry.
  LIB_OBJS += environment.o
  LIB_OBJS += exec_cmd.o
  LIB_OBJS += fsck.o
+ LIB_OBJS += gpg-interface.o
  LIB_OBJS += graph.o
  LIB_OBJS += grep.o
  LIB_OBJS += hash.o
@@@ -1096,7 -1090,6 +1098,7 @@@ ifeq ($(uname_S),Windows
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
 +      NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_IPV6 = YesPlease
        NO_SETENV = YesPlease
        BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
        COMPAT_OBJS = compat/msvc.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
 -              compat/win32/sys/poll.o compat/win32/dirent.o
 +              compat/win32/poll.o compat/win32/dirent.o
        COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
        BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
        EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
@@@ -1190,7 -1183,6 +1192,7 @@@ ifneq (,$(findstring MINGW,$(uname_S))
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
 +      NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
 -              compat/win32/sys/poll.o compat/win32/dirent.o
 +              compat/win32/poll.o compat/win32/dirent.o
        EXTLIBS += -lws2_32
        PTHREAD_LIBS =
        X = .exe
@@@ -1253,32 -1245,21 +1255,32 @@@ endi
  endif
  
  ifdef CHECK_HEADER_DEPENDENCIES
 -COMPUTE_HEADER_DEPENDENCIES =
 +COMPUTE_HEADER_DEPENDENCIES = no
  USE_COMPUTED_HEADER_DEPENDENCIES =
 -else
 +endif
 +
  ifndef COMPUTE_HEADER_DEPENDENCIES
 +COMPUTE_HEADER_DEPENDENCIES = auto
 +endif
 +
 +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto)
  dep_check = $(shell $(CC) $(ALL_CFLAGS) \
        -c -MF /dev/null -MMD -MP -x c /dev/null -o /dev/null 2>&1; \
        echo $$?)
  ifeq ($(dep_check),0)
 -COMPUTE_HEADER_DEPENDENCIES=YesPlease
 -endif
 +override COMPUTE_HEADER_DEPENDENCIES = yes
 +else
 +override COMPUTE_HEADER_DEPENDENCIES = no
  endif
  endif
  
 -ifdef COMPUTE_HEADER_DEPENDENCIES
 +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
  USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease
 +else
 +ifneq ($(COMPUTE_HEADER_DEPENDENCIES),no)
 +$(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \
 +(not "$(COMPUTE_HEADER_DEPENDENCIES)"))
 +endif
  endif
  
  ifdef SANE_TOOL_PATH
@@@ -1479,7 -1460,7 +1481,7 @@@ ifdef NO_STRLCP
  endif
  ifdef NO_STRTOUMAX
        COMPAT_CFLAGS += -DNO_STRTOUMAX
 -      COMPAT_OBJS += compat/strtoumax.o
 +      COMPAT_OBJS += compat/strtoumax.o compat/strtoimax.o
  endif
  ifdef NO_STRTOULL
        COMPAT_CFLAGS += -DNO_STRTOULL
@@@ -1925,7 -1906,7 +1927,7 @@@ OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(
  dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
  dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
  
 -ifdef COMPUTE_HEADER_DEPENDENCIES
 +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
  $(dep_dirs):
        @mkdir -p $@
  
@@@ -1938,7 -1919,7 +1940,7 @@@ Please unset CHECK_HEADER_DEPENDENCIES 
  endif
  endif
  
 -ifndef COMPUTE_HEADER_DEPENDENCIES
 +ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
  ifndef CHECK_HEADER_DEPENDENCIES
  dep_dirs =
  missing_dep_dirs =
@@@ -2028,13 -2009,13 +2030,13 @@@ builtin/branch.o builtin/checkout.o bui
  builtin/bundle.o bundle.o transport.o: bundle.h
  builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
  builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
 -builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h
 +builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
  builtin/send-pack.o transport.o: send-pack.h
  builtin/log.o builtin/shortlog.o: shortlog.h
  builtin/prune.o builtin/reflog.o reachable.o: reachable.h
  builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
  builtin/tar-tree.o archive-tar.o: tar.h
 -connect.o transport.o http-backend.o: url.h
 +connect.o transport.o url.o http-backend.o: url.h
  http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
  http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
  
@@@ -2148,21 -2129,17 +2150,21 @@@ po/git.pot: $(LOCALIZED_C
  
  pot: po/git.pot
  
 +FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
 +                      $(FIND) . \( -name .git -type d -prune \) \
 +                              -o \( -name '*.[hcS]' -type f -print \) )
 +
  $(ETAGS_TARGET): FORCE
        $(RM) $(ETAGS_TARGET)
 -      $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET)
 +      $(FIND_SOURCE_FILES) | xargs etags -a -o $(ETAGS_TARGET)
  
  tags: FORCE
        $(RM) tags
 -      $(FIND) . -name '*.[hcS]' -print | xargs ctags -a
 +      $(FIND_SOURCE_FILES) | xargs ctags -a
  
  cscope:
        $(RM) cscope*
 -      $(FIND) . -name '*.[hcS]' -print | xargs cscope -b
 +      $(FIND_SOURCE_FILES) | xargs cscope -b
  
  ### Detect prefix changes
  TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
diff --combined builtin/commit.c
@@@ -1382,6 -1382,7 +1382,7 @@@ int cmd_commit(int argc, const char **a
        int allow_fast_forward = 1;
        struct wt_status s;
        struct commit *current_head = NULL;
+       struct commit_extra_header *extra = NULL;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
                        pptr = &commit_list_insert(c->item, pptr)->next;
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
-               struct commit *commit;
                FILE *fp;
  
                if (!reflog_msg)
                        die_errno(_("could not open '%s' for reading"),
                                  git_path("MERGE_HEAD"));
                while (strbuf_getline(&m, fp, '\n') != EOF) {
-                       unsigned char sha1[20];
-                       if (get_sha1_hex(m.buf, sha1) < 0)
+                       struct commit *parent;
+                       parent = get_merge_parent(m.buf);
+                       if (!parent)
                                die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
-                       commit = lookup_commit_or_die(sha1, "MERGE_HEAD");
-                       pptr = &commit_list_insert(commit, pptr)->next;
+                       pptr = &commit_list_insert(parent, pptr)->next;
                }
                fclose(fp);
                strbuf_release(&m);
                exit(1);
        }
  
-       if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
-                       author_ident.buf)) {
+       if (amend)
+               extra = read_commit_extra_headers(current_head);
+       if (commit_tree_extended(sb.buf, active_cache_tree->sha1, parents, sha1,
+                                author_ident.buf, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
        strbuf_release(&author_ident);
+       free_commit_extra_headers(extra);
  
        ref_lock = lock_any_ref_for_update("HEAD",
                                           !current_head
        }
  
        unlink(git_path("CHERRY_PICK_HEAD"));
 +      unlink(git_path("REVERT_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
diff --combined builtin/fetch.c
@@@ -436,8 -436,7 +436,7 @@@ static int store_updated_refs(const cha
                }
                note[note_len] = '\0';
                fprintf(fp, "%s\t%s\t%s",
-                       sha1_to_hex(commit ? commit->object.sha1 :
-                                   rm->old_sha1),
+                       sha1_to_hex(rm->old_sha1),
                        rm->merge ? "" : "not-for-merge",
                        note);
                for (i = 0; i < url_len; ++i)
@@@ -510,10 -509,10 +509,10 @@@ static int fetch_refs(struct transport 
        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,31 -703,8 +703,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
@@@ -911,7 -887,7 +910,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/fmt-merge-msg.c
@@@ -5,41 -5,34 +5,43 @@@
  #include "revision.h"
  #include "tag.h"
  #include "string-list.h"
 +#include "branch.h"
 +#include "fmt-merge-msg.h"
+ #include "gpg-interface.h"
  
  static const char * const fmt_merge_msg_usage[] = {
        "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
        NULL
  };
  
 -static int shortlog_len;
 +static int use_branch_desc;
  
 -static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
 +int fmt_merge_msg_config(const char *key, const char *value, void *cb)
  {
        if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
                int is_bool;
 -              shortlog_len = git_config_bool_or_int(key, value, &is_bool);
 -              if (!is_bool && shortlog_len < 0)
 +              merge_log_config = git_config_bool_or_int(key, value, &is_bool);
 +              if (!is_bool && merge_log_config < 0)
                        return error("%s: negative length %s", key, value);
 -              if (is_bool && shortlog_len)
 -                      shortlog_len = DEFAULT_MERGE_LOG_LEN;
 +              if (is_bool && merge_log_config)
 +                      merge_log_config = DEFAULT_MERGE_LOG_LEN;
 +      } else if (!strcmp(key, "merge.branchdesc")) {
 +              use_branch_desc = git_config_bool(key, value);
        }
        return 0;
  }
  
+ /* merge data per repository where the merged tips came from */
  struct src_data {
        struct string_list branch, tag, r_branch, generic;
        int head_status;
  };
  
 +struct origin_data {
 +      unsigned char sha1[20];
 +      unsigned is_local_branch:1;
 +};
 +
  static void init_src_data(struct src_data *data)
  {
        data->branch.strdup_strings = 1;
@@@ -54,7 -47,7 +56,7 @@@ static struct string_list origins = STR
  static int handle_line(char *line)
  {
        int i, len = strlen(line);
 -      unsigned char *sha1;
 +      struct origin_data *origin_data;
        char *src, *origin;
        struct src_data *src_data;
        struct string_list_item *item;
                return 2;
  
        line[40] = 0;
 -      sha1 = xmalloc(20);
 -      i = get_sha1(line, sha1);
 +      origin_data = xcalloc(1, sizeof(struct origin_data));
 +      i = get_sha1(line, origin_data->sha1);
        line[40] = '\t';
 -      if (i)
 +      if (i) {
 +              free(origin_data);
                return 3;
 +      }
  
        if (line[len - 1] == '\n')
                line[len - 1] = 0;
        line += 42;
  
+       /*
+        * At this point, line points at the beginning of comment e.g.
+        * "branch 'frotz' of git://that/repository.git".
+        * Find the repository name and point it with src.
+        */
        src = strstr(line, " of ");
        if (src) {
                *src = 0;
                origin = src;
                src_data->head_status |= 1;
        } else if (!prefixcmp(line, "branch ")) {
 +              origin_data->is_local_branch = 1;
                origin = line + 7;
                string_list_append(&src_data->branch, origin);
                src_data->head_status |= 2;
                sprintf(new_origin, "%s of %s", origin, src);
                origin = new_origin;
        }
 -      string_list_append(&origins, origin)->util = sha1;
 +      if (strcmp(".", src))
 +              origin_data->is_local_branch = 0;
 +      string_list_append(&origins, origin)->util = origin_data;
        return 0;
  }
  
@@@ -154,30 -147,9 +161,30 @@@ static void print_joined(const char *si
        }
  }
  
 -static void shortlog(const char *name, unsigned char *sha1,
 -              struct commit *head, struct rev_info *rev, int limit,
 -              struct strbuf *out)
 +static void add_branch_desc(struct strbuf *out, const char *name)
 +{
 +      struct strbuf desc = STRBUF_INIT;
 +
 +      if (!read_branch_desc(&desc, name)) {
 +              const char *bp = desc.buf;
 +              while (*bp) {
 +                      const char *ep = strchrnul(bp, '\n');
 +                      if (*ep)
 +                              ep++;
 +                      strbuf_addf(out, "  : %.*s", (int)(ep - bp), bp);
 +                      bp = ep;
 +              }
 +              if (out->buf[out->len - 1] != '\n')
 +                      strbuf_addch(out, '\n');
 +      }
 +      strbuf_release(&desc);
 +}
 +
 +static void shortlog(const char *name,
 +                   struct origin_data *origin_data,
 +                   struct commit *head,
 +                   struct rev_info *rev, int limit,
 +                   struct strbuf *out)
  {
        int i, count = 0;
        struct commit *commit;
        struct string_list subjects = STRING_LIST_INIT_DUP;
        int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
        struct strbuf sb = STRBUF_INIT;
 +      const unsigned char *sha1 = origin_data->sha1;
  
        branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
        if (!branch || branch->type != OBJ_COMMIT)
        else
                strbuf_addf(out, "\n* %s:\n", name);
  
 +      if (origin_data->is_local_branch && use_branch_desc)
 +              add_branch_desc(out, name);
 +
        for (i = 0; i < subjects.nr; i++)
                if (i >= limit)
                        strbuf_addf(out, "  ...\n");
        string_list_clear(&subjects, 0);
  }
  
- static void do_fmt_merge_msg_title(struct strbuf *out,
+ static void fmt_merge_msg_title(struct strbuf *out,
        const char *current_branch) {
        int i = 0;
        char *sep = "";
                strbuf_addf(out, " into %s\n", current_branch);
  }
  
- static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
-       struct strbuf *out, int shortlog_len) {
+ static void fmt_tag_signature(struct strbuf *tagbuf,
+                             struct strbuf *sig,
+                             const char *buf,
+                             unsigned long len)
+ {
+       const char *tag_body = strstr(buf, "\n\n");
+       if (tag_body) {
+               tag_body += 2;
+               strbuf_add(tagbuf, tag_body, buf + len - tag_body);
+       }
+       strbuf_complete_line(tagbuf);
+       strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
+ }
+ static void fmt_merge_msg_sigs(struct strbuf *out)
+ {
+       int i, tag_number = 0, first_tag = 0;
+       struct strbuf tagbuf = STRBUF_INIT;
+       for (i = 0; i < origins.nr; i++) {
+               unsigned char *sha1 = origins.items[i].util;
+               enum object_type type;
+               unsigned long size, len;
+               char *buf = read_sha1_file(sha1, &type, &size);
+               struct strbuf sig = STRBUF_INIT;
+               if (!buf || type != OBJ_TAG)
+                       goto next;
+               len = parse_signature(buf, size);
+               if (size == len)
+                       ; /* merely annotated */
+               else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) {
+                       if (!sig.len)
+                               strbuf_addstr(&sig, "gpg verification failed.\n");
+               }
+               if (!tag_number++) {
+                       fmt_tag_signature(&tagbuf, &sig, buf, len);
+                       first_tag = i;
+               } else {
+                       if (tag_number == 2) {
+                               struct strbuf tagline = STRBUF_INIT;
+                               strbuf_addf(&tagline, "\n# %s\n",
+                                           origins.items[first_tag].string);
+                               strbuf_insert(&tagbuf, 0, tagline.buf,
+                                             tagline.len);
+                               strbuf_release(&tagline);
+                       }
+                       strbuf_addf(&tagbuf, "\n# %s\n",
+                                   origins.items[i].string);
+                       fmt_tag_signature(&tagbuf, &sig, buf, len);
+               }
+               strbuf_release(&sig);
+       next:
+               free(buf);
+       }
+       if (tagbuf.len) {
+               strbuf_addch(out, '\n');
+               strbuf_addbuf(out, &tagbuf);
+       }
+       strbuf_release(&tagbuf);
+ }
+ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+                 struct fmt_merge_msg_opts *opts)
+ {
        int i = 0, pos = 0;
        unsigned char head_sha1[20];
        const char *current_branch;
                        die ("Error in line %d: %.*s", i, len, p);
        }
  
-       if (!srcs.nr)
-               return 0;
+       if (opts->add_title && srcs.nr)
+               fmt_merge_msg_title(out, current_branch);
  
-       if (merge_title)
-               do_fmt_merge_msg_title(out, current_branch);
+       if (origins.nr)
+               fmt_merge_msg_sigs(out);
  
-       if (shortlog_len) {
+       if (opts->shortlog_len) {
                struct commit *head;
                struct rev_info rev;
  
                        strbuf_addch(out, '\n');
  
                for (i = 0; i < origins.nr; i++)
 -                      shortlog(origins.items[i].string, origins.items[i].util,
 +                      shortlog(origins.items[i].string,
 +                               origins.items[i].util,
-                                head, &rev, shortlog_len, out);
+                                head, &rev, opts->shortlog_len, out);
        }
-       return 0;
- }
  
- int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
-                 int merge_title, int shortlog_len) {
-       return do_fmt_merge_msg(merge_title, in, out, shortlog_len);
+       strbuf_complete_line(out);
+       return 0;
  }
  
  int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
  {
        const char *inpath = NULL;
        const char *message = NULL;
 +      int shortlog_len = -1;
        struct option options[] = {
                { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
                  "populate log with at most <n> entries from shortlog",
        FILE *in = stdin;
        struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
        int ret;
+       struct fmt_merge_msg_opts opts;
  
        git_config(fmt_merge_msg_config, NULL);
        argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
                             0);
        if (argc > 0)
                usage_with_options(fmt_merge_msg_usage, options);
 -
        if (shortlog_len < 0)
 -              die("Negative --log=%d", shortlog_len);
 +              shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
-       if (message && !shortlog_len) {
-               char nl = '\n';
-               write_in_full(STDOUT_FILENO, message, strlen(message));
-               write_in_full(STDOUT_FILENO, &nl, 1);
-               return 0;
-       }
-       if (shortlog_len < 0)
-               die("Negative --log=%d", shortlog_len);
  
        if (inpath && strcmp(inpath, "-")) {
                in = fopen(inpath, "r");
  
        if (message)
                strbuf_addstr(&output, message);
-       ret = fmt_merge_msg(&input, &output,
-                           message ? 0 : 1,
-                           shortlog_len);
  
+       memset(&opts, 0, sizeof(opts));
+       opts.add_title = !message;
+       opts.shortlog_len = shortlog_len;
+       ret = fmt_merge_msg(&input, &output, &opts);
        if (ret)
                return ret;
        write_in_full(STDOUT_FILENO, output.buf, output.len);
diff --combined builtin/merge.c
@@@ -26,7 -26,6 +26,7 @@@
  #include "merge-recursive.h"
  #include "resolve-undo.h"
  #include "remote.h"
 +#include "fmt-merge-msg.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -45,7 -44,7 +45,7 @@@ static const char * const builtin_merge
        NULL
  };
  
 -static int show_diffstat = 1, shortlog_len, squash;
 +static int show_diffstat = 1, shortlog_len = -1, squash;
  static int option_commit = 1, allow_fast_forward = 1;
  static int fast_forward_only, option_edit;
  static int allow_trivial = 1, have_message;
@@@ -317,15 -316,13 +317,15 @@@ static void squash_message(struct commi
        struct rev_info rev;
        struct strbuf out = STRBUF_INIT;
        struct commit_list *j;
 +      const char *filename;
        int fd;
        struct pretty_print_context ctx = {0};
  
        printf(_("Squash commit -- not updating HEAD\n"));
 -      fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
 +      filename = git_path("SQUASH_MSG");
 +      fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
 -              die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG"));
 +              die_errno(_("Could not write to '%s'"), filename);
  
        init_revisions(&rev, NULL);
        rev.ignore_merges = 1;
@@@ -409,20 -406,10 +409,10 @@@ static void finish(struct commit *head_
        strbuf_release(&reflog_message);
  }
  
- static struct object *want_commit(const char *name)
- {
-       struct object *obj;
-       unsigned char sha1[20];
-       if (get_sha1(name, sha1))
-               return NULL;
-       obj = parse_object(sha1);
-       return peel_to_type(name, 0, obj, OBJ_COMMIT);
- }
  /* Get the name for the merge commit's message. */
  static void merge_name(const char *remote, struct strbuf *msg)
  {
-       struct object *remote_head;
+       struct commit *remote_head;
        unsigned char branch_head[20], buf_sha[20];
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        remote = bname.buf;
  
        memset(branch_head, 0, sizeof(branch_head));
-       remote_head = want_commit(remote);
+       remote_head = get_merge_parent(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
  
                                    sha1_to_hex(branch_head), remote);
                        goto cleanup;
                }
+               if (!prefixcmp(found_ref, "refs/tags/")) {
+                       strbuf_addf(msg, "%s\t\ttag '%s' of .\n",
+                                   sha1_to_hex(branch_head), remote);
+                       goto cleanup;
+               }
                if (!prefixcmp(found_ref, "refs/remotes/")) {
                        strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
                                    sha1_to_hex(branch_head), remote);
                if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
                        strbuf_addf(msg,
                                    "%s\t\tbranch '%s'%s of .\n",
-                                   sha1_to_hex(remote_head->sha1),
+                                   sha1_to_hex(remote_head->object.sha1),
                                    truname.buf + 11,
                                    (early ? " (early part)" : ""));
                        strbuf_release(&truname);
  
        if (!strcmp(remote, "FETCH_HEAD") &&
                        !access(git_path("FETCH_HEAD"), R_OK)) {
 +              const char *filename;
                FILE *fp;
                struct strbuf line = STRBUF_INIT;
                char *ptr;
  
 -              fp = fopen(git_path("FETCH_HEAD"), "r");
 +              filename = git_path("FETCH_HEAD");
 +              fp = fopen(filename, "r");
                if (!fp)
                        die_errno(_("could not open '%s' for reading"),
 -                                git_path("FETCH_HEAD"));
 +                                filename);
                strbuf_getline(&line, fp, '\n');
                fclose(fp);
                ptr = strstr(line.buf, "\tnot-for-merge\t");
                goto cleanup;
        }
        strbuf_addf(msg, "%s\t\tcommit '%s'\n",
-               sha1_to_hex(remote_head->sha1), remote);
+               sha1_to_hex(remote_head->object.sha1), remote);
  cleanup:
        strbuf_release(&buf);
        strbuf_release(&bname);
@@@ -543,8 -533,6 +538,8 @@@ static void parse_branch_merge_options(
  
  static int git_merge_config(const char *k, const char *v, void *cb)
  {
 +      int status;
 +
        if (branch && !prefixcmp(k, "branch.") &&
                !prefixcmp(k + 7, branch) &&
                !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
                return git_config_string(&pull_octopus, k, v);
        else if (!strcmp(k, "merge.renormalize"))
                option_renormalize = git_config_bool(k, v);
 -      else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
 -              int is_bool;
 -              shortlog_len = git_config_bool_or_int(k, v, &is_bool);
 -              if (!is_bool && shortlog_len < 0)
 -                      return error(_("%s: negative length %s"), k, v);
 -              if (is_bool && shortlog_len)
 -                      shortlog_len = DEFAULT_MERGE_LOG_LEN;
 -              return 0;
 -      } else if (!strcmp(k, "merge.ff")) {
 +      else if (!strcmp(k, "merge.ff")) {
                int boolval = git_config_maybe_bool(k, v);
                if (0 <= boolval) {
                        allow_fast_forward = boolval;
                default_to_upstream = git_config_bool(k, v);
                return 0;
        }
 +      status = fmt_merge_msg_config(k, v, cb);
 +      if (status)
 +              return status;
        return git_diff_ui_config(k, v, cb);
  }
  
@@@ -716,7 -709,7 +711,7 @@@ static int try_merge_strategy(const cha
                                die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
  
                o.branch1 = head_arg;
-               o.branch2 = remoteheads->item->util;
+               o.branch2 = merge_remote_util(remoteheads->item)->name;
  
                for (j = common; j; j = j->next)
                        commit_list_insert(j->item, &reversed);
@@@ -773,7 -766,7 +768,7 @@@ int checkout_fast_forward(const unsigne
        memset(&t, 0, sizeof(t));
        memset(&dir, 0, sizeof(dir));
        dir.flags |= DIR_SHOW_IGNORED;
 -      dir.exclude_per_dir = ".gitignore";
 +      setup_standard_excludes(&dir);
        opts.dir = &dir;
  
        opts.head_idx = 1;
@@@ -849,22 -842,20 +844,22 @@@ static void add_strategies(const char *
  
  static void write_merge_msg(struct strbuf *msg)
  {
 -      int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
 +      const char *filename = git_path("MERGE_MSG");
 +      int fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
                die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MSG"));
 +                        filename);
        if (write_in_full(fd, msg->buf, msg->len) != msg->len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
  }
  
  static void read_merge_msg(struct strbuf *msg)
  {
 +      const char *filename = git_path("MERGE_MSG");
        strbuf_reset(msg);
 -      if (strbuf_read_file(msg, git_path("MERGE_MSG"), 0) < 0)
 -              die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
 +      if (strbuf_read_file(msg, filename, 0) < 0)
 +              die_errno(_("Could not read from '%s'"), filename);
  }
  
  static void write_merge_state(void);
@@@ -952,14 -943,13 +947,14 @@@ static int finish_automerge(struct comm
  
  static int suggest_conflicts(int renormalizing)
  {
 +      const char *filename;
        FILE *fp;
        int pos;
  
 -      fp = fopen(git_path("MERGE_MSG"), "a");
 +      filename = git_path("MERGE_MSG");
 +      fp = fopen(filename, "a");
        if (!fp)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MSG"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        fprintf(fp, "\nConflicts:\n");
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@@ -1051,33 -1041,38 +1046,40 @@@ static int setup_with_upstream(const ch
  
  static void write_merge_state(void)
  {
 +      const char *filename;
        int fd;
        struct commit_list *j;
        struct strbuf buf = STRBUF_INIT;
  
-       for (j = remoteheads; j; j = j->next)
-               strbuf_addf(&buf, "%s\n",
-                       sha1_to_hex(j->item->object.sha1));
+       for (j = remoteheads; j; j = j->next) {
+               unsigned const char *sha1;
+               struct commit *c = j->item;
+               if (c->util && merge_remote_util(c)->obj) {
+                       sha1 = merge_remote_util(c)->obj->sha1;
+               } else {
+                       sha1 = c->object.sha1;
+               }
+               strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+       }
 -      fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
 +      filename = git_path("MERGE_HEAD");
 +      fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_HEAD"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
        strbuf_addch(&merge_msg, '\n');
        write_merge_msg(&merge_msg);
 -      fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
 +
 +      filename = git_path("MERGE_MODE");
 +      fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd < 0)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MODE"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        strbuf_reset(&buf);
        if (!allow_fast_forward)
                strbuf_addf(&buf, "no-ff");
        if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
  }
  
@@@ -1116,8 -1111,6 +1118,8 @@@ int cmd_merge(int argc, const char **ar
                parse_branch_merge_options(branch_mergeoptions);
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
                        builtin_merge_usage, 0);
 +      if (shortlog_len < 0)
 +              shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
  
        if (verbosity < 0 && show_progress == -1)
                show_progress = 0;
                die(_("You cannot combine --no-ff with --ff-only."));
  
        if (!abort_current_merge) {
 -              if (!argc && default_to_upstream)
 -                      argc = setup_with_upstream(&argv);
 -              else if (argc == 1 && !strcmp(argv[0], "-"))
 +              if (!argc) {
 +                      if (default_to_upstream)
 +                              argc = setup_with_upstream(&argv);
 +                      else
 +                              die(_("No commit specified and merge.defaultToUpstream not set."));
 +              } else if (argc == 1 && !strcmp(argv[0], "-"))
                        argv[0] = "@{-1}";
        }
        if (!argc)
                argv += 2;
                argc -= 2;
        } else if (!head_commit) {
-               struct object *remote_head;
+               struct commit *remote_head;
                /*
                 * If the merged head is a valid one there is no reason
                 * to forbid "git merge" into a branch yet to be born.
                if (!allow_fast_forward)
                        die(_("Non-fast-forward commit does not make sense into "
                            "an empty head"));
-               remote_head = want_commit(argv[0]);
+               remote_head = get_merge_parent(argv[0]);
                if (!remote_head)
                        die(_("%s - not something we can merge"), argv[0]);
-               read_empty(remote_head->sha1, 0);
-               update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
-                               DIE_ON_ERR);
+               read_empty(remote_head->object.sha1, 0);
+               update_ref("initial pull", "HEAD", remote_head->object.sha1,
+                          NULL, 0, DIE_ON_ERR);
                return 0;
        } else {
                struct strbuf merge_names = STRBUF_INIT;
                head_arg = "HEAD";
  
                /*
-                * All the rest are the commits being merged;
-                * prepare the standard merge summary message to
-                * be appended to the given message.  If remote
-                * is invalid we will die later in the common
-                * codepath so we discard the error in this
-                * loop.
+                * All the rest are the commits being merged; prepare
+                * the standard merge summary message to be appended
+                * to the given message.
                 */
                for (i = 0; i < argc; i++)
                        merge_name(argv[i], &merge_names);
  
                if (!have_message || shortlog_len) {
-                       fmt_merge_msg(&merge_names, &merge_msg, !have_message,
-                                     shortlog_len);
+                       struct fmt_merge_msg_opts opts;
+                       memset(&opts, 0, sizeof(opts));
+                       opts.add_title = !have_message;
+                       opts.shortlog_len = shortlog_len;
+                       fmt_merge_msg(&merge_names, &merge_msg, &opts);
                        if (merge_msg.len)
                                strbuf_setlen(&merge_msg, merge_msg.len - 1);
                }
        strbuf_reset(&buf);
  
        for (i = 0; i < argc; i++) {
-               struct object *o;
-               struct commit *commit;
-               o = want_commit(argv[i]);
-               if (!o)
+               struct commit *commit = get_merge_parent(argv[i]);
+               if (!commit)
                        die(_("%s - not something we can merge"), argv[i]);
-               commit = lookup_commit(o->sha1);
-               commit->util = (void *)argv[i];
                remotes = &commit_list_insert(commit, remotes)->next;
-               strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+               strbuf_addf(&buf, "GITHEAD_%s",
+                           sha1_to_hex(commit->object.sha1));
                setenv(buf.buf, argv[i], 1);
                strbuf_reset(&buf);
+               if (merge_remote_util(commit) &&
+                   merge_remote_util(commit)->obj &&
+                   merge_remote_util(commit)->obj->type == OBJ_TAG) {
+                       option_edit = 1;
+                       allow_fast_forward = 0;
+               }
        }
  
        if (!use_strategies) {
                        !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
-               struct object *o;
+               struct commit *commit;
                char hex[41];
  
                strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
                if (have_message)
                        strbuf_addstr(&msg,
                                " (no commit created; -m option ignored)");
-               o = want_commit(sha1_to_hex(remoteheads->item->object.sha1));
-               if (!o)
+               commit = remoteheads->item;
+               if (!commit)
                        return 1;
  
-               if (checkout_fast_forward(head_commit->object.sha1, remoteheads->item->object.sha1))
+               if (checkout_fast_forward(head_commit->object.sha1,
+                                         commit->object.sha1))
                        return 1;
  
-               finish(head_commit, o->sha1, msg.buf);
+               finish(head_commit, commit->object.sha1, msg.buf);
                drop_save();
                return 0;
        } else if (!remoteheads->next && common->next)
diff --combined cache.h
+++ b/cache.h
@@@ -306,7 -306,7 +306,7 @@@ static inline unsigned int canon_mode(u
  }
  
  #define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
 -#define cache_entry_size(len) flexible_size(cache_entry,len)
 +#define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1)
  #define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
  #define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
  
@@@ -316,6 -316,7 +316,6 @@@ struct index_state 
        struct string_list *resolve_undo;
        struct cache_tree *cache_tree;
        struct cache_time timestamp;
 -      void *alloc;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
        struct hash_table name_hash;
@@@ -735,7 -736,7 +735,7 @@@ int safe_create_leading_directories(cha
  int safe_create_leading_directories_const(const char *path);
  int mkdir_in_gitdir(const char *path);
  extern char *expand_user_path(const char *path);
 -char *enter_repo(char *path, int strict);
 +const char *enter_repo(const char *path, int strict);
  static inline int is_absolute_path(const char *path)
  {
        return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
@@@ -872,7 -873,7 +872,7 @@@ extern int get_sha1_mb(const char *str
  
  extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
  extern const char *ref_rev_parse_rules[];
- extern const char *ref_fetch_rules[];
+ #define ref_fetch_rules ref_rev_parse_rules
  
  extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
  extern int validate_headref(const char *ref);
@@@ -1056,7 -1057,6 +1056,7 @@@ extern struct packed_git *add_packed_gi
  extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
  extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t);
  extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
 +extern int is_pack_valid(struct packed_git *);
  extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
  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);
diff --combined refs.c
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -4,8 -4,9 +4,8 @@@
  #include "tag.h"
  #include "dir.h"
  
 -/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
 -#define REF_KNOWS_PEELED 04
 -#define REF_BROKEN 010
 +/* ISSYMREF=0x01, ISPACKED=0x02 and ISBROKEN=0x04 are public interfaces */
 +#define REF_KNOWS_PEELED 0x10
  
  struct ref_entry {
        unsigned char flag; /* ISSYMREF? ISPACKED? */
@@@ -48,7 -49,7 +48,7 @@@ static const char *parse_ref_line(char 
  }
  
  static void add_ref(const char *name, const unsigned char *sha1,
 -                  int flag, struct ref_array *refs,
 +                  int flag, int check_name, struct ref_array *refs,
                    struct ref_entry **new_entry)
  {
        int len;
@@@ -59,8 -60,7 +59,8 @@@
        entry = xmalloc(sizeof(struct ref_entry) + len);
        hashcpy(entry->sha1, sha1);
        hashclr(entry->peeled);
 -      if (check_refname_format(name, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
 +      if (check_name &&
 +          check_refname_format(name, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
                die("Reference has invalid format: '%s'", name);
        memcpy(entry->name, name, len);
        entry->flag = flag;
@@@ -134,15 -134,15 +134,15 @@@ static struct ref_entry *search_ref_arr
   * Future: need to be in "struct repository"
   * when doing a full libification.
   */
 -static struct cached_refs {
 -      struct cached_refs *next;
 +static struct ref_cache {
 +      struct ref_cache *next;
        char did_loose;
        char did_packed;
        struct ref_array loose;
        struct ref_array packed;
        /* The submodule name, or "" for the main repo. */
        char name[FLEX_ARRAY];
 -} *cached_refs;
 +} *ref_cache;
  
  static struct ref_entry *current_ref;
  
@@@ -158,41 -158,36 +158,41 @@@ static void free_ref_array(struct ref_a
        array->refs = NULL;
  }
  
 -static void clear_cached_refs(struct cached_refs *ca)
 +static void clear_packed_ref_cache(struct ref_cache *refs)
  {
 -      if (ca->did_loose)
 -              free_ref_array(&ca->loose);
 -      if (ca->did_packed)
 -              free_ref_array(&ca->packed);
 -      ca->did_loose = ca->did_packed = 0;
 +      if (refs->did_packed)
 +              free_ref_array(&refs->packed);
 +      refs->did_packed = 0;
  }
  
 -static struct cached_refs *create_cached_refs(const char *submodule)
 +static void clear_loose_ref_cache(struct ref_cache *refs)
 +{
 +      if (refs->did_loose)
 +              free_ref_array(&refs->loose);
 +      refs->did_loose = 0;
 +}
 +
 +static struct ref_cache *create_ref_cache(const char *submodule)
  {
        int len;
 -      struct cached_refs *refs;
 +      struct ref_cache *refs;
        if (!submodule)
                submodule = "";
        len = strlen(submodule) + 1;
 -      refs = xcalloc(1, sizeof(struct cached_refs) + len);
 +      refs = xcalloc(1, sizeof(struct ref_cache) + len);
        memcpy(refs->name, submodule, len);
        return refs;
  }
  
  /*
 - * Return a pointer to a cached_refs for the specified submodule. For
 + * Return a pointer to a ref_cache for the specified submodule. For
   * the main repository, use submodule==NULL. The returned structure
   * will be allocated and initialized but not necessarily populated; it
   * should not be freed.
   */
 -static struct cached_refs *get_cached_refs(const char *submodule)
 +static struct ref_cache *get_ref_cache(const char *submodule)
  {
 -      struct cached_refs *refs = cached_refs;
 +      struct ref_cache *refs = ref_cache;
        if (!submodule)
                submodule = "";
        while (refs) {
                refs = refs->next;
        }
  
 -      refs = create_cached_refs(submodule);
 -      refs->next = cached_refs;
 -      cached_refs = refs;
 +      refs = create_ref_cache(submodule);
 +      refs->next = ref_cache;
 +      ref_cache = refs;
        return refs;
  }
  
 -static void invalidate_cached_refs(void)
 +void invalidate_ref_cache(const char *submodule)
  {
 -      struct cached_refs *refs = cached_refs;
 -      while (refs) {
 -              clear_cached_refs(refs);
 -              refs = refs->next;
 -      }
 +      struct ref_cache *refs = get_ref_cache(submodule);
 +      clear_packed_ref_cache(refs);
 +      clear_loose_ref_cache(refs);
  }
  
  static void read_packed_refs(FILE *f, struct ref_array *array)
  
                name = parse_ref_line(refline, sha1);
                if (name) {
 -                      add_ref(name, sha1, flag, array, &last);
 +                      add_ref(name, sha1, flag, 1, array, &last);
                        continue;
                }
                if (last &&
  
  void add_extra_ref(const char *name, const unsigned char *sha1, int flag)
  {
 -      add_ref(name, sha1, flag, &extra_refs, NULL);
 +      add_ref(name, sha1, flag, 0, &extra_refs, NULL);
  }
  
  void clear_extra_refs(void)
  
  static struct ref_array *get_packed_refs(const char *submodule)
  {
 -      struct cached_refs *refs = get_cached_refs(submodule);
 +      struct ref_cache *refs = get_ref_cache(submodule);
  
        if (!refs->did_packed) {
                const char *packed_refs_file;
@@@ -332,13 -329,14 +332,13 @@@ static void get_ref_dir(const char *sub
                                flag = 0;
                                if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
                                        hashclr(sha1);
 -                                      flag |= REF_BROKEN;
 +                                      flag |= REF_ISBROKEN;
                                }
 -                      } else
 -                              if (!resolve_ref(ref, sha1, 1, &flag)) {
 -                                      hashclr(sha1);
 -                                      flag |= REF_BROKEN;
 -                              }
 -                      add_ref(ref, sha1, flag, array, NULL);
 +                      } else if (!resolve_ref(ref, sha1, 1, &flag)) {
 +                              hashclr(sha1);
 +                              flag |= REF_ISBROKEN;
 +                      }
 +                      add_ref(ref, sha1, flag, 1, array, NULL);
                }
                free(ref);
                closedir(dir);
@@@ -381,7 -379,7 +381,7 @@@ void warn_dangling_symref(FILE *fp, con
  
  static struct ref_array *get_loose_refs(const char *submodule)
  {
 -      struct cached_refs *refs = get_cached_refs(submodule);
 +      struct ref_cache *refs = get_ref_cache(submodule);
  
        if (!refs->did_loose) {
                get_ref_dir(submodule, "refs", &refs->loose);
@@@ -503,6 -501,7 +503,6 @@@ const char *resolve_ref(const char *ref
        ssize_t len;
        char buffer[256];
        static char ref_buffer[256];
 -      char path[PATH_MAX];
  
        if (flag)
                *flag = 0;
                return NULL;
  
        for (;;) {
 +              char path[PATH_MAX];
                struct stat st;
                char *buf;
                int fd;
                 */
                if (prefixcmp(buffer, "ref:"))
                        break;
 +              if (flag)
 +                      *flag |= REF_ISSYMREF;
                buf = buffer + 4;
                while (isspace(*buf))
                        buf++;
                if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
 -                      warning("symbolic reference in %s is formatted incorrectly",
 -                              path);
 +                      if (flag)
 +                              *flag |= REF_ISBROKEN;
                        return NULL;
                }
                ref = strcpy(ref_buffer, buf);
 -              if (flag)
 -                      *flag |= REF_ISSYMREF;
        }
        /* Please note that FETCH_HEAD has a second line containing other data. */
        if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) {
 -              warning("reference in %s is formatted incorrectly", path);
 +              if (flag)
 +                      *flag |= REF_ISBROKEN;
                return NULL;
        }
        return ref;
@@@ -627,8 -624,8 +627,8 @@@ static int do_one_ref(const char *base
                return 0;
  
        if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
 -              if (entry->flag & REF_BROKEN)
 -                      return 0; /* ignore dangling symref */
 +              if (entry->flag & REF_ISBROKEN)
 +                      return 0; /* ignore broken refs e.g. dangling symref */
                if (!has_sha1_file(entry->sha1)) {
                        error("%s does not point to a valid object!", entry->name);
                        return 0;
@@@ -998,13 -995,6 +998,6 @@@ const char *ref_rev_parse_rules[] = 
        NULL
  };
  
- const char *ref_fetch_rules[] = {
-       "%.*s",
-       "refs/%.*s",
-       "refs/heads/%.*s",
-       NULL
- };
  int refname_match(const char *abbrev_name, const char *full_name, const char **rules)
  {
        const char **p;
@@@ -1078,94 -1068,6 +1071,94 @@@ static int is_refname_available(const c
        return 1;
  }
  
 +/*
 + * *string and *len will only be substituted, and *string returned (for
 + * later free()ing) if the string passed in is a magic short-hand form
 + * to name a branch.
 + */
 +static char *substitute_branch_name(const char **string, int *len)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      int ret = interpret_branch_name(*string, &buf);
 +
 +      if (ret == *len) {
 +              size_t size;
 +              *string = strbuf_detach(&buf, &size);
 +              *len = size;
 +              return (char *)*string;
 +      }
 +
 +      return NULL;
 +}
 +
 +int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 +{
 +      char *last_branch = substitute_branch_name(&str, &len);
 +      const char **p, *r;
 +      int refs_found = 0;
 +
 +      *ref = NULL;
 +      for (p = ref_rev_parse_rules; *p; p++) {
 +              char fullref[PATH_MAX];
 +              unsigned char sha1_from_ref[20];
 +              unsigned char *this_result;
 +              int flag;
 +
 +              this_result = refs_found ? sha1_from_ref : sha1;
 +              mksnpath(fullref, sizeof(fullref), *p, len, str);
 +              r = resolve_ref(fullref, this_result, 1, &flag);
 +              if (r) {
 +                      if (!refs_found++)
 +                              *ref = xstrdup(r);
 +                      if (!warn_ambiguous_refs)
 +                              break;
 +              } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) {
 +                      warning("ignoring dangling symref %s.", fullref);
 +              } else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {
 +                      warning("ignoring broken ref %s.", fullref);
 +              }
 +      }
 +      free(last_branch);
 +      return refs_found;
 +}
 +
 +int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 +{
 +      char *last_branch = substitute_branch_name(&str, &len);
 +      const char **p;
 +      int logs_found = 0;
 +
 +      *log = NULL;
 +      for (p = ref_rev_parse_rules; *p; p++) {
 +              struct stat st;
 +              unsigned char hash[20];
 +              char path[PATH_MAX];
 +              const char *ref, *it;
 +
 +              mksnpath(path, sizeof(path), *p, len, str);
 +              ref = resolve_ref(path, hash, 1, NULL);
 +              if (!ref)
 +                      continue;
 +              if (!stat(git_path("logs/%s", path), &st) &&
 +                  S_ISREG(st.st_mode))
 +                      it = path;
 +              else if (strcmp(ref, path) &&
 +                       !stat(git_path("logs/%s", ref), &st) &&
 +                       S_ISREG(st.st_mode))
 +                      it = ref;
 +              else
 +                      continue;
 +              if (!logs_found++) {
 +                      *log = xstrdup(it);
 +                      hashcpy(sha1, hash);
 +              }
 +              if (!warn_ambiguous_refs)
 +                      break;
 +      }
 +      free(last_branch);
 +      return logs_found;
 +}
 +
  static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
  {
        char *ref_file;
@@@ -1329,7 -1231,7 +1322,7 @@@ int delete_ref(const char *refname, con
        ret |= repack_without_ref(refname);
  
        unlink_or_warn(git_path("logs/%s", lock->ref_name));
 -      invalidate_cached_refs();
 +      invalidate_ref_cache(NULL);
        unlock_ref(lock);
        return ret;
  }
  
  int rename_ref(const char *oldref, const char *newref, const char *logmsg)
  {
 -      static const char renamed_ref[] = "RENAMED-REF";
        unsigned char sha1[20], orig_sha1[20];
        int flag = 0, logmoved = 0;
        struct ref_lock *lock;
        if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))
                return 1;
  
 -      lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
 -      if (!lock)
 -              return error("unable to lock %s", renamed_ref);
 -      lock->force_write = 1;
 -      if (write_ref_sha1(lock, orig_sha1, logmsg))
 -              return error("unable to save current sha1 in %s", renamed_ref);
 -
        if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG)))
                return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
                        oldref, strerror(errno));
@@@ -1620,7 -1530,7 +1613,7 @@@ int write_ref_sha1(struct ref_lock *loc
                unlock_ref(lock);
                return -1;
        }
 -      invalidate_cached_refs();
 +      clear_loose_ref_cache(get_ref_cache(NULL));
        if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
            (strcmp(lock->ref_name, lock->orig_ref_name) &&
             log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
diff --combined t/t5510-fetch.sh
@@@ -76,56 -76,6 +76,56 @@@ test_expect_success "fetch test for-mer
        cut -f -2 .git/FETCH_HEAD >actual &&
        test_cmp expected actual'
  
 +test_expect_success 'fetch --prune on its own works as expected' '
 +      cd "$D" &&
 +      git clone . prune &&
 +      cd prune &&
 +      git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
 +
 +      git fetch --prune origin &&
 +      test_must_fail git rev-parse origin/extrabranch
 +'
 +
 +test_expect_success 'fetch --prune with a branch name keeps branches' '
 +      cd "$D" &&
 +      git clone . prune-branch &&
 +      cd prune-branch &&
 +      git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
 +
 +      git fetch --prune origin master &&
 +      git rev-parse origin/extrabranch
 +'
 +
 +test_expect_success 'fetch --prune with a namespace keeps other namespaces' '
 +      cd "$D" &&
 +      git clone . prune-namespace &&
 +      cd prune-namespace &&
 +
 +      git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
 +      git rev-parse origin/master
 +'
 +
 +test_expect_success 'fetch --prune --tags does not delete the remote-tracking branches' '
 +      cd "$D" &&
 +      git clone . prune-tags &&
 +      cd prune-tags &&
 +      git fetch origin refs/heads/master:refs/tags/sometag &&
 +
 +      git fetch --prune --tags origin &&
 +      git rev-parse origin/master &&
 +      test_must_fail git rev-parse somebranch
 +'
 +
 +test_expect_success 'fetch --prune --tags with branch does not delete other remote-tracking branches' '
 +      cd "$D" &&
 +      git clone . prune-tags-branch &&
 +      cd prune-tags-branch &&
 +      git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
 +
 +      git fetch --prune --tags origin master &&
 +      git rev-parse origin/extrabranch
 +'
 +
  test_expect_success 'fetch tags when there is no tags' '
  
      cd "$D" &&
@@@ -166,7 -116,7 +166,7 @@@ test_expect_success 'fetch must not res
  
  '
  
- test_expect_success 'fetch must not resolve short remote name' '
+ test_expect_success 'fetch can now resolve short remote name' '
  
        cd "$D" &&
        git update-ref refs/remotes/six/HEAD HEAD &&
        cd six &&
        git init &&
  
-       test_must_fail git fetch .. six:six
+       git fetch .. six:six
  '
  
  test_expect_success 'create bundle 1' '