Merge branch 'fc/at-head'
authorJunio C Hamano <gitster@pobox.com>
Tue, 11 Jun 2013 20:31:23 +0000 (13:31 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 11 Jun 2013 20:31:23 +0000 (13:31 -0700)
Instead of typing four capital letters "HEAD", you can say "@"
instead.

* fc/at-head:
sha1_name: compare variable with constant, not constant with variable
Add new @ shortcut for HEAD
sha1_name: refactor reinterpret()
sha1_name: check @{-N} errors sooner
sha1_name: reorganize get_sha1_basic()
sha1_name: don't waste cycles in the @-parsing loop
sha1_name: remove unnecessary braces
sha1_name: remove no-op
tests: at-combinations: @{N} versus HEAD@{N}
tests: at-combinations: increase coverage
tests: at-combinations: improve nonsense()
tests: at-combinations: check ref names directly
tests: at-combinations: simplify setup

1  2 
Documentation/git-check-ref-format.txt
refs.c
sha1_name.c
index a49be1bab49ddcdb65e4b45b578bd183a94657a5,e8035ece4213f4a056651cf20f570256cccb1e54..fc02959ba4ab1ae6acc3dc5f707e870e42144f1c
@@@ -54,6 -54,8 +54,8 @@@ Git imposes the following rules on how 
  
  . They cannot contain a sequence `@{`.
  
+ . They cannot be the single character `@`.
  . They cannot contain a `\`.
  
  These rules make it easy for shell script based tools to parse
@@@ -83,7 -85,8 +85,7 @@@ typed the branch name
  
  OPTIONS
  -------
 ---allow-onelevel::
 ---no-allow-onelevel::
 +--[no-]allow-onelevel::
        Controls whether one-level refnames are accepted (i.e.,
        refnames that do not contain multiple `/`-separated
        components).  The default is `--no-allow-onelevel`.
diff --combined refs.c
index d17931a8bcd5322ab95f92094a009433f14d250a,4e70b3eca037a084fae0eae00386cd040305ce21..42a7e17f6bdf6f03f56a012c87c137b80452ea17
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -72,6 -72,10 +72,10 @@@ int check_refname_format(const char *re
  {
        int component_len, component_count = 0;
  
+       if (!strcmp(refname, "@"))
+               /* Refname is a single character '@'. */
+               return -1;
        while (1) {
                /* We are at the start of a path component. */
                component_len = check_refname_component(refname, flags);
@@@ -109,20 -113,7 +113,20 @@@ struct ref_entry
   * (ref_entry->flag & REF_DIR) is zero.
   */
  struct ref_value {
 +      /*
 +       * The name of the object to which this reference resolves
 +       * (which may be a tag object).  If REF_ISBROKEN, this is
 +       * null.  If REF_ISSYMREF, then this is the name of the object
 +       * referred to by the last reference in the symlink chain.
 +       */
        unsigned char sha1[20];
 +
 +      /*
 +       * If REF_KNOWS_PEELED, then this field holds the peeled value
 +       * of this reference, or null if the reference is known not to
 +       * be peelable.  See the documentation for peel_ref() for an
 +       * exact definition of "peelable".
 +       */
        unsigned char peeled[20];
  };
  
@@@ -171,17 -162,7 +175,17 @@@ struct ref_dir 
        struct ref_entry **entries;
  };
  
 -/* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */
 +/*
 + * Bit values for ref_entry::flag.  REF_ISSYMREF=0x01,
 + * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
 + * refs.h.
 + */
 +
 +/*
 + * The field ref_entry->u.value.peeled of this value entry contains
 + * the correct peeled value for the reference, which might be
 + * null_sha1 if the reference is not a tag or if it is broken.
 + */
  #define REF_KNOWS_PEELED 0x08
  
  /* ref_entry represents a directory of references */
@@@ -366,17 -347,18 +370,17 @@@ static int ref_entry_cmp_sslice(const v
  }
  
  /*
 - * Return the entry with the given refname from the ref_dir
 - * (non-recursively), sorting dir if necessary.  Return NULL if no
 - * such entry is found.  dir must already be complete.
 + * Return the index of the entry with the given refname from the
 + * ref_dir (non-recursively), sorting dir if necessary.  Return -1 if
 + * no such entry is found.  dir must already be complete.
   */
 -static struct ref_entry *search_ref_dir(struct ref_dir *dir,
 -                                      const char *refname, size_t len)
 +static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len)
  {
        struct ref_entry **r;
        struct string_slice key;
  
        if (refname == NULL || !dir->nr)
 -              return NULL;
 +              return -1;
  
        sort_ref_dir(dir);
        key.len = len;
                    ref_entry_cmp_sslice);
  
        if (r == NULL)
 -              return NULL;
 +              return -1;
  
 -      return *r;
 +      return r - dir->entries;
  }
  
  /*
@@@ -401,9 -383,8 +405,9 @@@ static struct ref_dir *search_for_subdi
                                         const char *subdirname, size_t len,
                                         int mkdir)
  {
 -      struct ref_entry *entry = search_ref_dir(dir, subdirname, len);
 -      if (!entry) {
 +      int entry_index = search_ref_dir(dir, subdirname, len);
 +      struct ref_entry *entry;
 +      if (entry_index == -1) {
                if (!mkdir)
                        return NULL;
                /*
                 */
                entry = create_dir_entry(dir->ref_cache, subdirname, len, 0);
                add_entry_to_dir(dir, entry);
 +      } else {
 +              entry = dir->entries[entry_index];
        }
        return get_ref_dir(entry);
  }
@@@ -454,67 -433,12 +458,67 @@@ static struct ref_dir *find_containing_
   */
  static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname)
  {
 +      int entry_index;
        struct ref_entry *entry;
        dir = find_containing_dir(dir, refname, 0);
        if (!dir)
                return NULL;
 -      entry = search_ref_dir(dir, refname, strlen(refname));
 -      return (entry && !(entry->flag & REF_DIR)) ? entry : NULL;
 +      entry_index = search_ref_dir(dir, refname, strlen(refname));
 +      if (entry_index == -1)
 +              return NULL;
 +      entry = dir->entries[entry_index];
 +      return (entry->flag & REF_DIR) ? NULL : entry;
 +}
 +
 +/*
 + * Remove the entry with the given name from dir, recursing into
 + * subdirectories as necessary.  If refname is the name of a directory
 + * (i.e., ends with '/'), then remove the directory and its contents.
 + * If the removal was successful, return the number of entries
 + * remaining in the directory entry that contained the deleted entry.
 + * If the name was not found, return -1.  Please note that this
 + * function only deletes the entry from the cache; it does not delete
 + * it from the filesystem or ensure that other cache entries (which
 + * might be symbolic references to the removed entry) are updated.
 + * Nor does it remove any containing dir entries that might be made
 + * empty by the removal.  dir must represent the top-level directory
 + * and must already be complete.
 + */
 +static int remove_entry(struct ref_dir *dir, const char *refname)
 +{
 +      int refname_len = strlen(refname);
 +      int entry_index;
 +      struct ref_entry *entry;
 +      int is_dir = refname[refname_len - 1] == '/';
 +      if (is_dir) {
 +              /*
 +               * refname represents a reference directory.  Remove
 +               * the trailing slash; otherwise we will get the
 +               * directory *representing* refname rather than the
 +               * one *containing* it.
 +               */
 +              char *dirname = xmemdupz(refname, refname_len - 1);
 +              dir = find_containing_dir(dir, dirname, 0);
 +              free(dirname);
 +      } else {
 +              dir = find_containing_dir(dir, refname, 0);
 +      }
 +      if (!dir)
 +              return -1;
 +      entry_index = search_ref_dir(dir, refname, refname_len);
 +      if (entry_index == -1)
 +              return -1;
 +      entry = dir->entries[entry_index];
 +
 +      memmove(&dir->entries[entry_index],
 +              &dir->entries[entry_index + 1],
 +              (dir->nr - entry_index - 1) * sizeof(*dir->entries)
 +              );
 +      dir->nr--;
 +      if (dir->sorted > entry_index)
 +              dir->sorted--;
 +      free_ref_entry(entry);
 +      return dir->nr;
  }
  
  /*
@@@ -583,64 -507,27 +587,64 @@@ static void sort_ref_dir(struct ref_di
        dir->sorted = dir->nr = i;
  }
  
 -#define DO_FOR_EACH_INCLUDE_BROKEN 01
 +/* Include broken references in a do_for_each_ref*() iteration: */
 +#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
 +
 +/*
 + * Return true iff the reference described by entry can be resolved to
 + * an object in the database.  Emit a warning if the referred-to
 + * object does not exist.
 + */
 +static int ref_resolves_to_object(struct ref_entry *entry)
 +{
 +      if (entry->flag & REF_ISBROKEN)
 +              return 0;
 +      if (!has_sha1_file(entry->u.value.sha1)) {
 +              error("%s does not point to a valid object!", entry->name);
 +              return 0;
 +      }
 +      return 1;
 +}
  
 +/*
 + * current_ref is a performance hack: when iterating over references
 + * using the for_each_ref*() functions, current_ref is set to the
 + * current reference's entry before calling the callback function.  If
 + * the callback function calls peel_ref(), then peel_ref() first
 + * checks whether the reference to be peeled is the current reference
 + * (it usually is) and if so, returns that reference's peeled version
 + * if it is available.  This avoids a refname lookup in a common case.
 + */
  static struct ref_entry *current_ref;
  
 -static int do_one_ref(const char *base, each_ref_fn fn, int trim,
 -                    int flags, void *cb_data, struct ref_entry *entry)
 +typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data);
 +
 +struct ref_entry_cb {
 +      const char *base;
 +      int trim;
 +      int flags;
 +      each_ref_fn *fn;
 +      void *cb_data;
 +};
 +
 +/*
 + * Handle one reference in a do_for_each_ref*()-style iteration,
 + * calling an each_ref_fn for each entry.
 + */
 +static int do_one_ref(struct ref_entry *entry, void *cb_data)
  {
 +      struct ref_entry_cb *data = cb_data;
        int retval;
 -      if (prefixcmp(entry->name, base))
 +      if (prefixcmp(entry->name, data->base))
 +              return 0;
 +
 +      if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
 +            !ref_resolves_to_object(entry))
                return 0;
  
 -      if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
 -              if (entry->flag & REF_ISBROKEN)
 -                      return 0; /* ignore broken refs e.g. dangling symref */
 -              if (!has_sha1_file(entry->u.value.sha1)) {
 -                      error("%s does not point to a valid object!", entry->name);
 -                      return 0;
 -              }
 -      }
        current_ref = entry;
 -      retval = fn(entry->name + trim, entry->u.value.sha1, entry->flag, cb_data);
 +      retval = data->fn(entry->name + data->trim, entry->u.value.sha1,
 +                        entry->flag, data->cb_data);
        current_ref = NULL;
        return retval;
  }
   * Call fn for each reference in dir that has index in the range
   * offset <= index < dir->nr.  Recurse into subdirectories that are in
   * that index range, sorting them before iterating.  This function
 - * does not sort dir itself; it should be sorted beforehand.
 + * does not sort dir itself; it should be sorted beforehand.  fn is
 + * called for all references, including broken ones.
   */
 -static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
 -                                const char *base,
 -                                each_ref_fn fn, int trim, int flags, void *cb_data)
 +static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset,
 +                                  each_ref_entry_fn fn, void *cb_data)
  {
        int i;
        assert(dir->sorted == dir->nr);
                if (entry->flag & REF_DIR) {
                        struct ref_dir *subdir = get_ref_dir(entry);
                        sort_ref_dir(subdir);
 -                      retval = do_for_each_ref_in_dir(subdir, 0,
 -                                                      base, fn, trim, flags, cb_data);
 +                      retval = do_for_each_entry_in_dir(subdir, 0, fn, cb_data);
                } else {
 -                      retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
 +                      retval = fn(entry, cb_data);
                }
                if (retval)
                        return retval;
   * by refname.  Recurse into subdirectories.  If a value entry appears
   * in both dir1 and dir2, then only process the version that is in
   * dir2.  The input dirs must already be sorted, but subdirs will be
 - * sorted as needed.
 + * sorted as needed.  fn is called for all references, including
 + * broken ones.
   */
 -static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
 -                                 struct ref_dir *dir2,
 -                                 const char *base, each_ref_fn fn, int trim,
 -                                 int flags, void *cb_data)
 +static int do_for_each_entry_in_dirs(struct ref_dir *dir1,
 +                                   struct ref_dir *dir2,
 +                                   each_ref_entry_fn fn, void *cb_data)
  {
        int retval;
        int i1 = 0, i2 = 0;
                struct ref_entry *e1, *e2;
                int cmp;
                if (i1 == dir1->nr) {
 -                      return do_for_each_ref_in_dir(dir2, i2,
 -                                                    base, fn, trim, flags, cb_data);
 +                      return do_for_each_entry_in_dir(dir2, i2, fn, cb_data);
                }
                if (i2 == dir2->nr) {
 -                      return do_for_each_ref_in_dir(dir1, i1,
 -                                                    base, fn, trim, flags, cb_data);
 +                      return do_for_each_entry_in_dir(dir1, i1, fn, cb_data);
                }
                e1 = dir1->entries[i1];
                e2 = dir2->entries[i2];
                                struct ref_dir *subdir2 = get_ref_dir(e2);
                                sort_ref_dir(subdir1);
                                sort_ref_dir(subdir2);
 -                              retval = do_for_each_ref_in_dirs(
 -                                              subdir1, subdir2,
 -                                              base, fn, trim, flags, cb_data);
 +                              retval = do_for_each_entry_in_dirs(
 +                                              subdir1, subdir2, fn, cb_data);
                                i1++;
                                i2++;
                        } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) {
                                /* Both are references; ignore the one from dir1. */
 -                              retval = do_one_ref(base, fn, trim, flags, cb_data, e2);
 +                              retval = fn(e2, cb_data);
                                i1++;
                                i2++;
                        } else {
                        if (e->flag & REF_DIR) {
                                struct ref_dir *subdir = get_ref_dir(e);
                                sort_ref_dir(subdir);
 -                              retval = do_for_each_ref_in_dir(
 -                                              subdir, 0,
 -                                              base, fn, trim, flags, cb_data);
 +                              retval = do_for_each_entry_in_dir(
 +                                              subdir, 0, fn, cb_data);
                        } else {
 -                              retval = do_one_ref(base, fn, trim, flags, cb_data, e);
 +                              retval = fn(e, cb_data);
                        }
                }
                if (retval)
                        return retval;
        }
 -      if (i1 < dir1->nr)
 -              return do_for_each_ref_in_dir(dir1, i1,
 -                                            base, fn, trim, flags, cb_data);
 -      if (i2 < dir2->nr)
 -              return do_for_each_ref_in_dir(dir2, i2,
 -                                            base, fn, trim, flags, cb_data);
 -      return 0;
  }
  
  /*
@@@ -766,13 -665,14 +770,13 @@@ struct name_conflict_cb 
        const char *conflicting_refname;
  };
  
 -static int name_conflict_fn(const char *existingrefname, const unsigned char *sha1,
 -                          int flags, void *cb_data)
 +static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
  {
        struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
 -      if (data->oldrefname && !strcmp(data->oldrefname, existingrefname))
 +      if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
                return 0;
 -      if (names_conflict(data->refname, existingrefname)) {
 -              data->conflicting_refname = existingrefname;
 +      if (names_conflict(data->refname, entry->name)) {
 +              data->conflicting_refname = entry->name;
                return 1;
        }
        return 0;
  
  /*
   * Return true iff a reference named refname could be created without
 - * conflicting with the name of an existing reference in array.  If
 + * conflicting with the name of an existing reference in dir.  If
   * oldrefname is non-NULL, ignore potential conflicts with oldrefname
   * (e.g., because oldrefname is scheduled for deletion in the same
   * operation).
@@@ -794,7 -694,9 +798,7 @@@ static int is_refname_available(const c
        data.conflicting_refname = NULL;
  
        sort_ref_dir(dir);
 -      if (do_for_each_ref_in_dir(dir, 0, "", name_conflict_fn,
 -                                 0, DO_FOR_EACH_INCLUDE_BROKEN,
 -                                 &data)) {
 +      if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
                error("'%s' exists; cannot create '%s'",
                      data.conflicting_refname, refname);
                return 0;
@@@ -810,13 -712,9 +814,13 @@@ static struct ref_cache 
        struct ref_cache *next;
        struct ref_entry *loose;
        struct ref_entry *packed;
 -      /* The submodule name, or "" for the main repo. */
 -      char name[FLEX_ARRAY];
 -} *ref_cache;
 +      /*
 +       * The submodule name, or "" for the main repo.  We allocate
 +       * length 1 rather than FLEX_ARRAY so that the main ref_cache
 +       * is initialized correctly.
 +       */
 +      char name[1];
 +} ref_cache, *submodule_ref_caches;
  
  static void clear_packed_ref_cache(struct ref_cache *refs)
  {
@@@ -854,18 -752,18 +858,18 @@@ static struct ref_cache *create_ref_cac
   */
  static struct ref_cache *get_ref_cache(const char *submodule)
  {
 -      struct ref_cache *refs = ref_cache;
 -      if (!submodule)
 -              submodule = "";
 -      while (refs) {
 +      struct ref_cache *refs;
 +
 +      if (!submodule || !*submodule)
 +              return &ref_cache;
 +
 +      for (refs = submodule_ref_caches; refs; refs = refs->next)
                if (!strcmp(submodule, refs->name))
                        return refs;
 -              refs = refs->next;
 -      }
  
        refs = create_ref_cache(submodule);
 -      refs->next = ref_cache;
 -      ref_cache = refs;
 +      refs->next = submodule_ref_caches;
 +      submodule_ref_caches = refs;
        return refs;
  }
  
@@@ -876,16 -774,6 +880,16 @@@ void invalidate_ref_cache(const char *s
        clear_loose_ref_cache(refs);
  }
  
 +/* The length of a peeled reference line in packed-refs, including EOL: */
 +#define PEELED_LINE_LENGTH 42
 +
 +/*
 + * The packed-refs header line that we write out.  Perhaps other
 + * traits will be added later.  The trailing space is required.
 + */
 +static const char PACKED_REFS_HEADER[] =
 +      "# pack-refs with: peeled fully-peeled \n";
 +
  /*
   * Parse one line from a packed-refs file.  Write the SHA1 to sha1.
   * Return a pointer to the refname within the line (null-terminated),
@@@ -978,8 -866,8 +982,8 @@@ static void read_packed_refs(FILE *f, s
                }
                if (last &&
                    refline[0] == '^' &&
 -                  strlen(refline) == 42 &&
 -                  refline[41] == '\n' &&
 +                  strlen(refline) == PEELED_LINE_LENGTH &&
 +                  refline[PEELED_LINE_LENGTH - 1] == '\n' &&
                    !get_sha1_hex(refline + 1, sha1)) {
                        hashcpy(last->u.value.peeled, sha1);
                        /*
@@@ -1014,8 -902,8 +1018,8 @@@ static struct ref_dir *get_packed_refs(
  
  void add_packed_ref(const char *refname, const unsigned char *sha1)
  {
 -      add_ref(get_packed_refs(get_ref_cache(NULL)),
 -                      create_ref_entry(refname, sha1, REF_ISPACKED, 1));
 +      add_ref(get_packed_refs(&ref_cache),
 +              create_ref_entry(refname, sha1, REF_ISPACKED, 1));
  }
  
  /*
@@@ -1185,12 -1073,18 +1189,12 @@@ int resolve_gitlink_ref(const char *pat
  }
  
  /*
 - * Try to read ref from the packed references.  On success, set sha1
 - * and return 0; otherwise, return -1.
 + * Return the ref_entry for the given refname from the packed
 + * references.  If it does not exist, return NULL.
   */
 -static int get_packed_ref(const char *refname, unsigned char *sha1)
 +static struct ref_entry *get_packed_ref(const char *refname)
  {
 -      struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
 -      struct ref_entry *entry = find_ref(packed, refname);
 -      if (entry) {
 -              hashcpy(sha1, entry->u.value.sha1);
 -              return 0;
 -      }
 -      return -1;
 +      return find_ref(get_packed_refs(&ref_cache), refname);
  }
  
  const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
                git_snpath(path, sizeof(path), "%s", refname);
  
                if (lstat(path, &st) < 0) {
 +                      struct ref_entry *entry;
 +
                        if (errno != ENOENT)
                                return NULL;
                        /*
                         * The loose reference file does not exist;
                         * check for a packed reference.
                         */
 -                      if (!get_packed_ref(refname, sha1)) {
 +                      entry = get_packed_ref(refname);
 +                      if (entry) {
 +                              hashcpy(sha1, entry->u.value.sha1);
                                if (flag)
                                        *flag |= REF_ISPACKED;
                                return refname;
@@@ -1345,130 -1235,54 +1349,130 @@@ static int filter_refs(const char *refn
        return filter->fn(refname, sha1, flags, filter->cb_data);
  }
  
 +enum peel_status {
 +      /* object was peeled successfully: */
 +      PEEL_PEELED = 0,
 +
 +      /*
 +       * object cannot be peeled because the named object (or an
 +       * object referred to by a tag in the peel chain), does not
 +       * exist.
 +       */
 +      PEEL_INVALID = -1,
 +
 +      /* object cannot be peeled because it is not a tag: */
 +      PEEL_NON_TAG = -2,
 +
 +      /* ref_entry contains no peeled value because it is a symref: */
 +      PEEL_IS_SYMREF = -3,
 +
 +      /*
 +       * ref_entry cannot be peeled because it is broken (i.e., the
 +       * symbolic reference cannot even be resolved to an object
 +       * name):
 +       */
 +      PEEL_BROKEN = -4
 +};
 +
 +/*
 + * Peel the named object; i.e., if the object is a tag, resolve the
 + * tag recursively until a non-tag is found.  If successful, store the
 + * result to sha1 and return PEEL_PEELED.  If the object is not a tag
 + * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,
 + * and leave sha1 unchanged.
 + */
 +static enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
 +{
 +      struct object *o = lookup_unknown_object(name);
 +
 +      if (o->type == OBJ_NONE) {
 +              int type = sha1_object_info(name, NULL);
 +              if (type < 0)
 +                      return PEEL_INVALID;
 +              o->type = type;
 +      }
 +
 +      if (o->type != OBJ_TAG)
 +              return PEEL_NON_TAG;
 +
 +      o = deref_tag_noverify(o);
 +      if (!o)
 +              return PEEL_INVALID;
 +
 +      hashcpy(sha1, o->sha1);
 +      return PEEL_PEELED;
 +}
 +
 +/*
 + * Peel the entry (if possible) and return its new peel_status.  If
 + * repeel is true, re-peel the entry even if there is an old peeled
 + * value that is already stored in it.
 + *
 + * It is OK to call this function with a packed reference entry that
 + * might be stale and might even refer to an object that has since
 + * been garbage-collected.  In such a case, if the entry has
 + * REF_KNOWS_PEELED then leave the status unchanged and return
 + * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID.
 + */
 +static enum peel_status peel_entry(struct ref_entry *entry, int repeel)
 +{
 +      enum peel_status status;
 +
 +      if (entry->flag & REF_KNOWS_PEELED) {
 +              if (repeel) {
 +                      entry->flag &= ~REF_KNOWS_PEELED;
 +                      hashclr(entry->u.value.peeled);
 +              } else {
 +                      return is_null_sha1(entry->u.value.peeled) ?
 +                              PEEL_NON_TAG : PEEL_PEELED;
 +              }
 +      }
 +      if (entry->flag & REF_ISBROKEN)
 +              return PEEL_BROKEN;
 +      if (entry->flag & REF_ISSYMREF)
 +              return PEEL_IS_SYMREF;
 +
 +      status = peel_object(entry->u.value.sha1, entry->u.value.peeled);
 +      if (status == PEEL_PEELED || status == PEEL_NON_TAG)
 +              entry->flag |= REF_KNOWS_PEELED;
 +      return status;
 +}
 +
  int peel_ref(const char *refname, unsigned char *sha1)
  {
        int flag;
        unsigned char base[20];
 -      struct object *o;
  
        if (current_ref && (current_ref->name == refname
 -              || !strcmp(current_ref->name, refname))) {
 -              if (current_ref->flag & REF_KNOWS_PEELED) {
 -                      if (is_null_sha1(current_ref->u.value.peeled))
 -                          return -1;
 -                      hashcpy(sha1, current_ref->u.value.peeled);
 -                      return 0;
 -              }
 -              hashcpy(base, current_ref->u.value.sha1);
 -              goto fallback;
 +                          || !strcmp(current_ref->name, refname))) {
 +              if (peel_entry(current_ref, 0))
 +                      return -1;
 +              hashcpy(sha1, current_ref->u.value.peeled);
 +              return 0;
        }
  
        if (read_ref_full(refname, base, 1, &flag))
                return -1;
  
 -      if ((flag & REF_ISPACKED)) {
 -              struct ref_dir *dir = get_packed_refs(get_ref_cache(NULL));
 -              struct ref_entry *r = find_ref(dir, refname);
 -
 -              if (r != NULL && r->flag & REF_KNOWS_PEELED) {
 +      /*
 +       * If the reference is packed, read its ref_entry from the
 +       * cache in the hope that we already know its peeled value.
 +       * We only try this optimization on packed references because
 +       * (a) forcing the filling of the loose reference cache could
 +       * be expensive and (b) loose references anyway usually do not
 +       * have REF_KNOWS_PEELED.
 +       */
 +      if (flag & REF_ISPACKED) {
 +              struct ref_entry *r = get_packed_ref(refname);
 +              if (r) {
 +                      if (peel_entry(r, 0))
 +                              return -1;
                        hashcpy(sha1, r->u.value.peeled);
                        return 0;
                }
        }
  
 -fallback:
 -      o = lookup_unknown_object(base);
 -      if (o->type == OBJ_NONE) {
 -              int type = sha1_object_info(base, NULL);
 -              if (type < 0)
 -                      return -1;
 -              o->type = type;
 -      }
 -
 -      if (o->type == OBJ_TAG) {
 -              o = deref_tag_noverify(o);
 -              if (o) {
 -                      hashcpy(sha1, o->sha1);
 -                      return 0;
 -              }
 -      }
 -      return -1;
 +      return peel_object(base, sha1);
  }
  
  struct warn_if_dangling_data {
@@@ -1506,16 -1320,10 +1510,16 @@@ void warn_dangling_symref(FILE *fp, con
        for_each_rawref(warn_if_dangling_symref, &data);
  }
  
 -static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
 -                         int trim, int flags, void *cb_data)
 +/*
 + * Call fn for each reference in the specified ref_cache, omitting
 + * references not in the containing_dir of base.  fn is called for all
 + * references, including broken ones.  If fn ever returns a non-zero
 + * value, stop the iteration and return that value; otherwise, return
 + * 0.
 + */
 +static int do_for_each_entry(struct ref_cache *refs, const char *base,
 +                           each_ref_entry_fn fn, void *cb_data)
  {
 -      struct ref_cache *refs = get_ref_cache(submodule);
        struct ref_dir *packed_dir = get_packed_refs(refs);
        struct ref_dir *loose_dir = get_loose_refs(refs);
        int retval = 0;
        if (packed_dir && loose_dir) {
                sort_ref_dir(packed_dir);
                sort_ref_dir(loose_dir);
 -              retval = do_for_each_ref_in_dirs(
 -                              packed_dir, loose_dir,
 -                              base, fn, trim, flags, cb_data);
 +              retval = do_for_each_entry_in_dirs(
 +                              packed_dir, loose_dir, fn, cb_data);
        } else if (packed_dir) {
                sort_ref_dir(packed_dir);
 -              retval = do_for_each_ref_in_dir(
 -                              packed_dir, 0,
 -                              base, fn, trim, flags, cb_data);
 +              retval = do_for_each_entry_in_dir(
 +                              packed_dir, 0, fn, cb_data);
        } else if (loose_dir) {
                sort_ref_dir(loose_dir);
 -              retval = do_for_each_ref_in_dir(
 -                              loose_dir, 0,
 -                              base, fn, trim, flags, cb_data);
 +              retval = do_for_each_entry_in_dir(
 +                              loose_dir, 0, fn, cb_data);
        }
  
        return retval;
  }
  
 +/*
 + * Call fn for each reference in the specified ref_cache for which the
 + * refname begins with base.  If trim is non-zero, then trim that many
 + * characters off the beginning of each refname before passing the
 + * refname to fn.  flags can be DO_FOR_EACH_INCLUDE_BROKEN to include
 + * broken references in the iteration.  If fn ever returns a non-zero
 + * value, stop the iteration and return that value; otherwise, return
 + * 0.
 + */
 +static int do_for_each_ref(struct ref_cache *refs, const char *base,
 +                         each_ref_fn fn, int trim, int flags, void *cb_data)
 +{
 +      struct ref_entry_cb data;
 +      data.base = base;
 +      data.trim = trim;
 +      data.flags = flags;
 +      data.fn = fn;
 +      data.cb_data = cb_data;
 +
 +      return do_for_each_entry(refs, base, do_one_ref, &data);
 +}
 +
  static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
  {
        unsigned char sha1[20];
@@@ -1595,23 -1384,23 +1599,23 @@@ int head_ref_submodule(const char *subm
  
  int for_each_ref(each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
 +      return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data);
  }
  
  int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
 +      return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data);
  }
  
  int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
 +      return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
  }
  
  int for_each_ref_in_submodule(const char *submodule, const char *prefix,
                each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
 +      return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);
  }
  
  int for_each_tag_ref(each_ref_fn fn, void *cb_data)
@@@ -1646,7 -1435,7 +1650,7 @@@ int for_each_remote_ref_submodule(cons
  
  int for_each_replace_ref(each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
 +      return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data);
  }
  
  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
@@@ -1669,7 -1458,7 +1673,7 @@@ int for_each_namespaced_ref(each_ref_f
        struct strbuf buf = STRBUF_INIT;
        int ret;
        strbuf_addf(&buf, "%srefs/", get_git_namespace());
 -      ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
 +      ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data);
        strbuf_release(&buf);
        return ret;
  }
@@@ -1711,7 -1500,7 +1715,7 @@@ int for_each_glob_ref(each_ref_fn fn, c
  
  int for_each_rawref(each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(NULL, "", fn, 0,
 +      return do_for_each_ref(&ref_cache, "", fn, 0,
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
  }
  
@@@ -1917,7 -1706,7 +1921,7 @@@ static struct ref_lock *lock_ref_sha1_b
         * name is a proper prefix of our refname.
         */
        if (missing &&
 -           !is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) {
 +           !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {
                last_errno = ENOTDIR;
                goto error_return;
        }
@@@ -1969,224 -1758,47 +1973,224 @@@ struct ref_lock *lock_any_ref_for_updat
        return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
  }
  
 -struct repack_without_ref_sb {
 -      const char *refname;
 -      int fd;
 -};
 -
 -static int repack_without_ref_fn(const char *refname, const unsigned char *sha1,
 -                               int flags, void *cb_data)
 +/*
 + * Write an entry to the packed-refs file for the specified refname.
 + * If peeled is non-NULL, write it as the entry's peeled value.
 + */
 +static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
 +                             unsigned char *peeled)
  {
 -      struct repack_without_ref_sb *data = cb_data;
        char line[PATH_MAX + 100];
        int len;
  
 -      if (!strcmp(data->refname, refname))
 -              return 0;
        len = snprintf(line, sizeof(line), "%s %s\n",
                       sha1_to_hex(sha1), refname);
        /* this should not happen but just being defensive */
        if (len > sizeof(line))
                die("too long a refname '%s'", refname);
 -      write_or_die(data->fd, line, len);
 +      write_or_die(fd, line, len);
 +
 +      if (peeled) {
 +              if (snprintf(line, sizeof(line), "^%s\n",
 +                           sha1_to_hex(peeled)) != PEELED_LINE_LENGTH)
 +                      die("internal error");
 +              write_or_die(fd, line, PEELED_LINE_LENGTH);
 +      }
 +}
 +
 +struct ref_to_prune {
 +      struct ref_to_prune *next;
 +      unsigned char sha1[20];
 +      char name[FLEX_ARRAY];
 +};
 +
 +struct pack_refs_cb_data {
 +      unsigned int flags;
 +      struct ref_to_prune *ref_to_prune;
 +      int fd;
 +};
 +
 +static int pack_one_ref(struct ref_entry *entry, void *cb_data)
 +{
 +      struct pack_refs_cb_data *cb = cb_data;
 +      enum peel_status peel_status;
 +      int is_tag_ref = !prefixcmp(entry->name, "refs/tags/");
 +
 +      /* ALWAYS pack refs that were already packed or are tags */
 +      if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref &&
 +          !(entry->flag & REF_ISPACKED))
 +              return 0;
 +
 +      /* Do not pack symbolic or broken refs: */
 +      if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry))
 +              return 0;
 +
 +      peel_status = peel_entry(entry, 1);
 +      if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
 +              die("internal error peeling reference %s (%s)",
 +                  entry->name, sha1_to_hex(entry->u.value.sha1));
 +      write_packed_entry(cb->fd, entry->name, entry->u.value.sha1,
 +                         peel_status == PEEL_PEELED ?
 +                         entry->u.value.peeled : NULL);
 +
 +      /* If the ref was already packed, there is no need to prune it. */
 +      if ((cb->flags & PACK_REFS_PRUNE) && !(entry->flag & REF_ISPACKED)) {
 +              int namelen = strlen(entry->name) + 1;
 +              struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
 +              hashcpy(n->sha1, entry->u.value.sha1);
 +              strcpy(n->name, entry->name);
 +              n->next = cb->ref_to_prune;
 +              cb->ref_to_prune = n;
 +      }
        return 0;
  }
  
 +/*
 + * Remove empty parents, but spare refs/ and immediate subdirs.
 + * Note: munges *name.
 + */
 +static void try_remove_empty_parents(char *name)
 +{
 +      char *p, *q;
 +      int i;
 +      p = name;
 +      for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
 +              while (*p && *p != '/')
 +                      p++;
 +              /* tolerate duplicate slashes; see check_refname_format() */
 +              while (*p == '/')
 +                      p++;
 +      }
 +      for (q = p; *q; q++)
 +              ;
 +      while (1) {
 +              while (q > p && *q != '/')
 +                      q--;
 +              while (q > p && *(q-1) == '/')
 +                      q--;
 +              if (q == p)
 +                      break;
 +              *q = '\0';
 +              if (rmdir(git_path("%s", name)))
 +                      break;
 +      }
 +}
 +
 +/* make sure nobody touched the ref, and unlink */
 +static void prune_ref(struct ref_to_prune *r)
 +{
 +      struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
 +
 +      if (lock) {
 +              unlink_or_warn(git_path("%s", r->name));
 +              unlock_ref(lock);
 +              try_remove_empty_parents(r->name);
 +      }
 +}
 +
 +static void prune_refs(struct ref_to_prune *r)
 +{
 +      while (r) {
 +              prune_ref(r);
 +              r = r->next;
 +      }
 +}
 +
  static struct lock_file packlock;
  
 -static int repack_without_ref(const char *refname)
 +int pack_refs(unsigned int flags)
 +{
 +      struct pack_refs_cb_data cbdata;
 +
 +      memset(&cbdata, 0, sizeof(cbdata));
 +      cbdata.flags = flags;
 +
 +      cbdata.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"),
 +                                            LOCK_DIE_ON_ERROR);
 +
 +      write_or_die(cbdata.fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
 +
 +      do_for_each_entry(&ref_cache, "", pack_one_ref, &cbdata);
 +      if (commit_lock_file(&packlock) < 0)
 +              die_errno("unable to overwrite old ref-pack file");
 +      prune_refs(cbdata.ref_to_prune);
 +      return 0;
 +}
 +
 +static int repack_ref_fn(struct ref_entry *entry, void *cb_data)
  {
 -      struct repack_without_ref_sb data;
 -      struct ref_cache *refs = get_ref_cache(NULL);
 -      struct ref_dir *packed = get_packed_refs(refs);
 -      if (find_ref(packed, refname) == NULL)
 +      int *fd = cb_data;
 +      enum peel_status peel_status;
 +
 +      if (entry->flag & REF_ISBROKEN) {
 +              /* This shouldn't happen to packed refs. */
 +              error("%s is broken!", entry->name);
                return 0;
 -      data.refname = refname;
 -      data.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
 -      if (data.fd < 0) {
 +      }
 +      if (!has_sha1_file(entry->u.value.sha1)) {
 +              unsigned char sha1[20];
 +              int flags;
 +
 +              if (read_ref_full(entry->name, sha1, 0, &flags))
 +                      /* We should at least have found the packed ref. */
 +                      die("Internal error");
 +              if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED))
 +                      /*
 +                       * This packed reference is overridden by a
 +                       * loose reference, so it is OK that its value
 +                       * is no longer valid; for example, it might
 +                       * refer to an object that has been garbage
 +                       * collected.  For this purpose we don't even
 +                       * care whether the loose reference itself is
 +                       * invalid, broken, symbolic, etc.  Silently
 +                       * omit the packed reference from the output.
 +                       */
 +                      return 0;
 +              /*
 +               * There is no overriding loose reference, so the fact
 +               * that this reference doesn't refer to a valid object
 +               * indicates some kind of repository corruption.
 +               * Report the problem, then omit the reference from
 +               * the output.
 +               */
 +              error("%s does not point to a valid object!", entry->name);
 +              return 0;
 +      }
 +
 +      peel_status = peel_entry(entry, 0);
 +      write_packed_entry(*fd, entry->name, entry->u.value.sha1,
 +                         peel_status == PEEL_PEELED ?
 +                         entry->u.value.peeled : NULL);
 +
 +      return 0;
 +}
 +
 +static int repack_without_ref(const char *refname)
 +{
 +      int fd;
 +      struct ref_dir *packed;
 +
 +      if (!get_packed_ref(refname))
 +              return 0; /* refname does not exist in packed refs */
 +
 +      fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
 +      if (fd < 0) {
                unable_to_lock_error(git_path("packed-refs"), errno);
                return error("cannot delete '%s' from packed refs", refname);
        }
 -      clear_packed_ref_cache(refs);
 -      packed = get_packed_refs(refs);
 -      do_for_each_ref_in_dir(packed, 0, "", repack_without_ref_fn, 0, 0, &data);
 +      clear_packed_ref_cache(&ref_cache);
 +      packed = get_packed_refs(&ref_cache);
 +      /* Remove refname from the cache. */
 +      if (remove_entry(packed, refname) == -1) {
 +              /*
 +               * The packed entry disappeared while we were
 +               * acquiring the lock.
 +               */
 +              rollback_lock_file(&packlock);
 +              return 0;
 +      }
 +      write_or_die(fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
 +      do_for_each_entry_in_dir(packed, 0, repack_ref_fn, &fd);
        return commit_lock_file(&packlock);
  }
  
@@@ -2215,7 -1827,7 +2219,7 @@@ int delete_ref(const char *refname, con
        ret |= repack_without_ref(lock->ref_name);
  
        unlink_or_warn(git_path("logs/%s", lock->ref_name));
 -      invalidate_ref_cache(NULL);
 +      clear_loose_ref_cache(&ref_cache);
        unlock_ref(lock);
        return ret;
  }
@@@ -2237,6 -1849,7 +2241,6 @@@ int rename_ref(const char *oldrefname, 
        struct stat loginfo;
        int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
        const char *symref = NULL;
 -      struct ref_cache *refs = get_ref_cache(NULL);
  
        if (log && S_ISLNK(loginfo.st_mode))
                return error("reflog for %s is a symlink", oldrefname);
        if (!symref)
                return error("refname %s not found", oldrefname);
  
 -      if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs)))
 +      if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))
                return 1;
  
 -      if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs)))
 +      if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))
                return 1;
  
        if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
@@@ -2507,7 -2120,7 +2511,7 @@@ int write_ref_sha1(struct ref_lock *loc
                unlock_ref(lock);
                return -1;
        }
 -      clear_loose_ref_cache(get_ref_cache(NULL));
 +      clear_loose_ref_cache(&ref_cache);
        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 sha1_name.c
index a695d16d8c244700e28d220061818e59ee187000,5a58720d85be0af90a32490e31831df5bc7f1ae8..90419efe1052406381af3e35b70dc996fe8533ce
@@@ -431,41 -431,30 +431,49 @@@ static inline int upstream_mark(const c
  }
  
  static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
+ static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf);
  
  static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
  {
        static const char *warn_msg = "refname '%.*s' is ambiguous.";
 +      static const char *object_name_msg = N_(
 +      "Git normally never creates a ref that ends with 40 hex characters\n"
 +      "because it will be ignored when you just specify 40-hex. These refs\n"
 +      "may be created by mistake. For example,\n"
 +      "\n"
 +      "  git checkout -b $br $(git rev-parse ...)\n"
 +      "\n"
 +      "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
 +      "examine these refs and maybe delete them. Turn this message off by\n"
 +      "running \"git config advice.object_name_warning false\"");
 +      unsigned char tmp_sha1[20];
        char *real_ref = NULL;
        int refs_found = 0;
-       int at, reflog_len;
+       int at, reflog_len, nth_prior = 0;
  
 -      if (len == 40 && !get_sha1_hex(str, sha1))
 +      if (len == 40 && !get_sha1_hex(str, sha1)) {
 +              refs_found = dwim_ref(str, len, tmp_sha1, &real_ref);
 +              if (refs_found > 0 && warn_ambiguous_refs) {
 +                      warning(warn_msg, len, str);
 +                      if (advice_object_name_warning)
 +                              fprintf(stderr, "%s\n", _(object_name_msg));
 +              }
 +              free(real_ref);
                return 0;
 +      }
  
        /* basic@{time or number or -number} format to query ref-log */
        reflog_len = at = 0;
        if (len && str[len-1] == '}') {
-               for (at = len-2; at >= 0; at--) {
+               for (at = len-4; at >= 0; at--) {
                        if (str[at] == '@' && str[at+1] == '{') {
+                               if (str[at+2] == '-') {
+                                       if (at != 0)
+                                               /* @{-N} not at start */
+                                               return -1;
+                                       nth_prior = 1;
+                                       continue;
+                               }
                                if (!upstream_mark(str + at, len - at)) {
                                        reflog_len = (len-1) - (at+2);
                                        len = at;
        if (len && ambiguous_path(str, len))
                return -1;
  
-       if (!len && reflog_len) {
+       if (nth_prior) {
                struct strbuf buf = STRBUF_INIT;
-               int ret;
-               /* try the @{-N} syntax for n-th checkout */
-               ret = interpret_branch_name(str+at, &buf);
-               if (ret > 0) {
-                       /* substitute this branch name and restart */
-                       return get_sha1_1(buf.buf, buf.len, sha1, 0);
-               } else if (ret == 0) {
-                       return -1;
+               int detached;
+               if (interpret_nth_prior_checkout(str, &buf) > 0) {
+                       detached = (buf.len == 40 && !get_sha1_hex(buf.buf, sha1));
+                       strbuf_release(&buf);
+                       if (detached)
+                               return 0;
                }
+       }
+       if (!len && reflog_len)
                /* allow "@{...}" to mean the current branch reflog */
                refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
-       else if (reflog_len)
+       else if (reflog_len)
                refs_found = dwim_log(str, len, sha1, &real_ref);
        else
                refs_found = dwim_ref(str, len, sha1, &real_ref);
        if (!refs_found)
                return -1;
  
 -      if (warn_ambiguous_refs && refs_found > 1)
 +      if (warn_ambiguous_refs &&
 +          (refs_found > 1 ||
 +           !get_short_sha1(str, len, tmp_sha1, GET_SHA1_QUIETLY)))
                warning(warn_msg, len, str);
  
        if (reflog_len) {
                unsigned long co_time;
                int co_tz, co_cnt;
  
-               /* a @{-N} placed anywhere except the start is an error */
-               if (str[at+2] == '-')
-                       return -1;
                /* Is it asking for N-th entry, or approxidate? */
                for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
                        char ch = str[at+2+i];
                }
                if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
                                &co_time, &co_tz, &co_cnt)) {
 +                      if (!len) {
 +                              if (!prefixcmp(real_ref, "refs/heads/")) {
 +                                      str = real_ref + 11;
 +                                      len = strlen(real_ref + 11);
 +                              } else {
 +                                      /* detached HEAD */
 +                                      str = "HEAD";
 +                                      len = 4;
 +                              }
 +                      }
                        if (at_time)
                                warning("Log for '%.*s' only goes "
                                        "back to %s.", len, str,
                                        show_date(co_time, co_tz, DATE_RFC2822));
                        else {
 -                              free(real_ref);
                                die("Log for '%.*s' only has %d entries.",
                                    len, str, co_cnt);
                        }
@@@ -996,6 -972,38 +1002,38 @@@ int get_sha1_mb(const char *name, unsig
        return st;
  }
  
+ /* parse @something syntax, when 'something' is not {.*} */
+ static int interpret_empty_at(const char *name, int namelen, int len, struct strbuf *buf)
+ {
+       if (len || name[1] == '{')
+               return -1;
+       strbuf_reset(buf);
+       strbuf_add(buf, "HEAD", 4);
+       return 1;
+ }
+ static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
+ {
+       /* we have extra data, which might need further processing */
+       struct strbuf tmp = STRBUF_INIT;
+       int used = buf->len;
+       int ret;
+       strbuf_add(buf, name + len, namelen - len);
+       ret = interpret_branch_name(buf->buf, &tmp);
+       /* that data was not interpreted, remove our cruft */
+       if (ret < 0) {
+               strbuf_setlen(buf, used);
+               return len;
+       }
+       strbuf_reset(buf);
+       strbuf_addbuf(buf, &tmp);
+       strbuf_release(&tmp);
+       /* tweak for size of {-N} versus expanded ref name */
+       return ret - used + len;
+ }
  /*
   * This reads short-hand syntax that not only evaluates to a commit
   * object name, but also can act as if the end user spelled the name
@@@ -1025,36 -1033,27 +1063,27 @@@ int interpret_branch_name(const char *n
        int len = interpret_nth_prior_checkout(name, buf);
        int tmp_len;
  
-       if (!len)
+       if (!len) {
                return len; /* syntax Ok, not enough switches */
-       if (0 < len && len == namelen)
-               return len; /* consumed all */
-       else if (0 < len) {
-               /* we have extra data, which might need further processing */
-               struct strbuf tmp = STRBUF_INIT;
-               int used = buf->len;
-               int ret;
-               strbuf_add(buf, name + len, namelen - len);
-               ret = interpret_branch_name(buf->buf, &tmp);
-               /* that data was not interpreted, remove our cruft */
-               if (ret < 0) {
-                       strbuf_setlen(buf, used);
-                       return len;
-               }
-               strbuf_reset(buf);
-               strbuf_addbuf(buf, &tmp);
-               strbuf_release(&tmp);
-               /* tweak for size of {-N} versus expanded ref name */
-               return ret - used + len;
+       } else if (len > 0) {
+               if (len == namelen)
+                       return len; /* consumed all */
+               else
+                       return reinterpret(name, namelen, len, buf);
        }
  
        cp = strchr(name, '@');
        if (!cp)
                return -1;
+       len = interpret_empty_at(name, namelen, cp - name, buf);
+       if (len > 0)
+               return reinterpret(name, namelen, len, buf);
        tmp_len = upstream_mark(cp, namelen - (cp - name));
        if (!tmp_len)
                return -1;
        len = cp + tmp_len - name;
        cp = xstrndup(name, cp - name);
        upstream = branch_get(*cp ? cp : NULL);
         * points to something different than a branch.
         */
        if (!upstream)
 -              return error(_("HEAD does not point to a branch"));
 +              die(_("HEAD does not point to a branch"));
        if (!upstream->merge || !upstream->merge[0]->dst) {
                if (!ref_exists(upstream->refname))
 -                      return error(_("No such branch: '%s'"), cp);
 -              if (!upstream->merge)
 -                      return error(_("No upstream configured for branch '%s'"),
 -                                   upstream->name);
 -              return error(
 +                      die(_("No such branch: '%s'"), cp);
 +              if (!upstream->merge) {
 +                      die(_("No upstream configured for branch '%s'"),
 +                              upstream->name);
 +              }
 +              die(
                        _("Upstream branch '%s' not stored as a remote-tracking branch"),
                        upstream->merge[0]->src);
        }
  int strbuf_branchname(struct strbuf *sb, const char *name)
  {
        int len = strlen(name);
 -      if (interpret_branch_name(name, sb) == len)
 +      int used = interpret_branch_name(name, sb);
 +
 +      if (used == len)
                return 0;
 -      strbuf_add(sb, name, len);
 +      if (used < 0)
 +              used = 0;
 +      strbuf_add(sb, name + used, len - used);
        return len;
  }