Sync with 1.8.1 maintenance track
authorJunio C Hamano <gitster@pobox.com>
Wed, 3 Apr 2013 16:18:01 +0000 (09:18 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 3 Apr 2013 16:18:01 +0000 (09:18 -0700)
* maint-1.8.1:
Start preparing for 1.8.1.6
git-tag(1): we tag HEAD by default
Fix revision walk for commits with the same dates
t2003: work around path mangling issue on Windows
pack-refs: add fully-peeled trait
pack-refs: write peeled entry for non-tags
use parse_object_or_die instead of die("bad object")
avoid segfaults on parse_object failure
entry: fix filter lookup
t2003: modernize style
name-hash.c: fix endless loop with core.ignorecase=true

1  2 
Documentation/git-tag.txt
builtin/grep.c
cache.h
name-hash.c
read-cache.c
refs.c
revision.c
index e3032c46c637f39a5a3dd965f122365ece197a5e,ea28e39c1b7a8e78b11561ea4b1ff6ccb8a42f92..b21aa87fe87a26f738c971f8c3dffdcf44fb13cd
@@@ -126,6 -126,12 +126,12 @@@ This option is only applicable when lis
        linkgit:git-check-ref-format[1].  Some of these checks
        may restrict the characters allowed in a tag name.
  
+ <commit>::
+ <object>::
+       The object that the new tag will refer to, usually a commit.
+       Defaults to HEAD.
  CONFIGURATION
  -------------
  By default, 'git tag' in sign-with-default mode (-s) will use your
@@@ -242,7 -248,7 +248,7 @@@ $ git pull git://git..../proj.git maste
  In such a case, you do not want to automatically follow the other
  person's tags.
  
 -One important aspect of git is its distributed nature, which
 +One important aspect of Git is its distributed nature, which
  largely means there is no inherent "upstream" or
  "downstream" in the system.  On the face of it, the above
  example might seem to indicate that the tag namespace is owned
diff --combined builtin/grep.c
index 8025964987553b8f1ef21b8319b2085d6d31bb0c,e8f0f92cf739c87c19d2be1722b6093ddbed7330..159e65d47a41b56634bc3fb480f9c051d40e692c
@@@ -820,11 -820,7 +820,9 @@@ int cmd_grep(int argc, const char **arg
                unsigned char sha1[20];
                /* Is it a rev? */
                if (!get_sha1(arg, sha1)) {
-                       struct object *object = parse_object(sha1);
-                       if (!object)
-                               die(_("bad object %s"), arg);
+                       struct object *object = parse_object_or_die(sha1, arg);
 +                      if (!seen_dashdash)
 +                              verify_non_filename(prefix, arg);
                        add_object_array(object, arg, &list);
                        continue;
                }
diff --combined cache.h
index e493563f4c07e6adcd00a1b2476926d69a4a67f8,3622e18415ee407d59e6270424d635b5f1df126c..898e346b0c08486c9251dd936e00008a7e44930e
+++ b/cache.h
@@@ -131,7 -131,6 +131,6 @@@ struct cache_entry 
        unsigned int ce_namelen;
        unsigned char sha1[20];
        struct cache_entry *next;
-       struct cache_entry *dir_next;
        char name[FLEX_ARRAY]; /* more */
  };
  
@@@ -267,25 -266,15 +266,15 @@@ struct index_state 
        unsigned name_hash_initialized : 1,
                 initialized : 1;
        struct hash_table name_hash;
+       struct hash_table dir_hash;
  };
  
  extern struct index_state the_index;
  
  /* Name hashing */
  extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
- /*
-  * We don't actually *remove* it, we can just mark it invalid so that
-  * we won't find it in lookups.
-  *
-  * Not only would we have to search the lists (simple enough), but
-  * we'd also have to rehash other hash buckets in case this makes the
-  * hash bucket empty (common). So it's much better to just mark
-  * it.
-  */
- static inline void remove_name_hash(struct cache_entry *ce)
- {
-       ce->ce_flags |= CE_UNHASHED;
- }
+ extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
+ extern void free_name_hash(struct index_state *istate);
  
  
  #ifndef NO_THE_INDEX_COMPATIBILITY_MACROS
@@@ -362,7 -351,6 +351,7 @@@ static inline enum object_type object_t
  #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
  #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
  #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
 +#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
  
  /*
   * Repository-local GIT_* environment variables
@@@ -474,8 -462,6 +463,8 @@@ extern int index_name_is_other(const st
  extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
  extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
  
 +#define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
 +
  struct pathspec {
        const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
        int nr;
        struct pathspec_item {
                const char *match;
                int len;
 -              unsigned int use_wildcard:1;
 +              int nowildcard_len;
 +              int flags;
        } *items;
  };
  
@@@ -494,8 -479,6 +483,8 @@@ extern int init_pathspec(struct pathspe
  extern void free_pathspec(struct pathspec *);
  extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
  
 +extern int limit_pathspec_to_literal(void);
 +
  #define HASH_WRITE_OBJECT 1
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
@@@ -536,7 -519,6 +525,7 @@@ extern int delete_ref(const char *, con
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
  extern int trust_ctime;
 +extern int check_stat;
  extern int quote_path_fully;
  extern int has_symlinks;
  extern int minimum_abbrev, default_abbrev;
@@@ -563,12 -545,6 +552,12 @@@ extern int core_preload_index
  extern int core_apply_sparse_checkout;
  extern int precomposed_unicode;
  
 +/*
 + * The character that begins a commented line in user-editable file
 + * that is subject to stripspace.
 + */
 +extern char comment_line_char;
 +
  enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
@@@ -1013,19 -989,15 +1002,19 @@@ struct ref 
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
        char *symref;
 -      unsigned int force:1,
 +      unsigned int
 +              force:1,
 +              forced_update:1,
                merge:1,
 -              nonfastforward:1,
                deletion:1;
        enum {
                REF_STATUS_NONE = 0,
                REF_STATUS_OK,
                REF_STATUS_REJECT_NONFASTFORWARD,
 +              REF_STATUS_REJECT_ALREADY_EXISTS,
                REF_STATUS_REJECT_NODELETE,
 +              REF_STATUS_REJECT_FETCH_FIRST,
 +              REF_STATUS_REJECT_NEEDS_FORCE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
                REF_STATUS_EXPECTING_REPORT
@@@ -1154,9 -1126,6 +1143,9 @@@ extern int check_repository_format_vers
  extern int git_env_bool(const char *, int);
  extern int git_config_system(void);
  extern int config_error_nonbool(const char *);
 +#if defined(__GNUC__) && ! defined(__clang__)
 +#define config_error_nonbool(s) (config_error_nonbool(s), -1)
 +#endif
  extern const char *get_log_output_encoding(void);
  extern const char *get_commit_output_encoding(void);
  
@@@ -1170,28 -1139,12 +1159,28 @@@ struct config_include_data 
  #define CONFIG_INCLUDE_INIT { 0 }
  extern int git_config_include(const char *name, const char *value, void *data);
  
 +/*
 + * Match and parse a config key of the form:
 + *
 + *   section.(subsection.)?key
 + *
 + * (i.e., what gets handed to a config_fn_t). The caller provides the section;
 + * we return -1 if it does not match, 0 otherwise. The subsection and key
 + * out-parameters are filled by the function (and subsection is NULL if it is
 + * missing).
 + */
 +extern int parse_config_key(const char *var,
 +                          const char *section,
 +                          const char **subsection, int *subsection_len,
 +                          const char **key);
 +
  extern int committer_ident_sufficiently_given(void);
  extern int author_ident_sufficiently_given(void);
  
  extern const char *git_commit_encoding;
  extern const char *git_log_output_encoding;
  extern const char *git_mailmap_file;
 +extern const char *git_mailmap_blob;
  
  /* IO helper functions */
  extern void maybe_flush_or_die(FILE *, const char *);
diff --combined name-hash.c
index 942c45962252eba4c6f88b4f4f7593c9247749ae,91241336f827090848a019990253e20725ff62cb..6d7e1980c62f96fc3614ddd130ce25a765fceae5
@@@ -24,46 -24,104 +24,104 @@@ static unsigned int hash_name(const cha
  {
        unsigned int hash = 0x123;
  
 -      do {
 +      while (namelen--) {
                unsigned char c = *name++;
                c = icase_hash(c);
                hash = hash*101 + c;
 -      } while (--namelen);
 +      }
        return hash;
  }
  
- static void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce)
+ struct dir_entry {
+       struct dir_entry *next;
+       struct dir_entry *parent;
+       struct cache_entry *ce;
+       int nr;
+       unsigned int namelen;
+ };
+ static struct dir_entry *find_dir_entry(struct index_state *istate,
+               const char *name, unsigned int namelen)
+ {
+       unsigned int hash = hash_name(name, namelen);
+       struct dir_entry *dir;
+       for (dir = lookup_hash(hash, &istate->dir_hash); dir; dir = dir->next)
+               if (dir->namelen == namelen &&
+                   !strncasecmp(dir->ce->name, name, namelen))
+                       return dir;
+       return NULL;
+ }
+ static struct dir_entry *hash_dir_entry(struct index_state *istate,
+               struct cache_entry *ce, int namelen)
  {
        /*
         * Throw each directory component in the hash for quick lookup
         * during a git status. Directory components are stored with their
         * closing slash.  Despite submodules being a directory, they never
         * reach this point, because they are stored without a closing slash
-        * in the cache.
+        * in index_state.name_hash (as ordinary cache_entries).
         *
-        * Note that the cache_entry stored with the directory does not
-        * represent the directory itself.  It is a pointer to an existing
-        * filename, and its only purpose is to represent existence of the
-        * directory in the cache.  It is very possible multiple directory
-        * hash entries may point to the same cache_entry.
+        * Note that the cache_entry stored with the dir_entry merely
+        * supplies the name of the directory (up to dir_entry.namelen). We
+        * track the number of 'active' files in a directory in dir_entry.nr,
+        * so we can tell if the directory is still relevant, e.g. for git
+        * status. However, if cache_entries are removed, we cannot pinpoint
+        * an exact cache_entry that's still active. It is very possible that
+        * multiple dir_entries point to the same cache_entry.
         */
-       unsigned int hash;
-       void **pos;
+       struct dir_entry *dir;
+       /* get length of parent directory */
+       while (namelen > 0 && !is_dir_sep(ce->name[namelen - 1]))
+               namelen--;
+       if (namelen <= 0)
+               return NULL;
+       /* lookup existing entry for that directory */
+       dir = find_dir_entry(istate, ce->name, namelen);
+       if (!dir) {
+               /* not found, create it and add to hash table */
+               void **pdir;
+               unsigned int hash = hash_name(ce->name, namelen);
  
-       const char *ptr = ce->name;
-       while (*ptr) {
-               while (*ptr && *ptr != '/')
-                       ++ptr;
-               if (*ptr == '/') {
-                       ++ptr;
-                       hash = hash_name(ce->name, ptr - ce->name);
-                       pos = insert_hash(hash, ce, &istate->name_hash);
-                       if (pos) {
-                               ce->dir_next = *pos;
-                               *pos = ce;
-                       }
+               dir = xcalloc(1, sizeof(struct dir_entry));
+               dir->namelen = namelen;
+               dir->ce = ce;
+               pdir = insert_hash(hash, dir, &istate->dir_hash);
+               if (pdir) {
+                       dir->next = *pdir;
+                       *pdir = dir;
                }
+               /* recursively add missing parent directories */
+               dir->parent = hash_dir_entry(istate, ce, namelen - 1);
        }
+       return dir;
+ }
+ static void add_dir_entry(struct index_state *istate, struct cache_entry *ce)
+ {
+       /* Add reference to the directory entry (and parents if 0). */
+       struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce));
+       while (dir && !(dir->nr++))
+               dir = dir->parent;
+ }
+ static void remove_dir_entry(struct index_state *istate, struct cache_entry *ce)
+ {
+       /*
+        * Release reference to the directory entry (and parents if 0).
+        *
+        * Note: we do not remove / free the entry because there's no
+        * hash.[ch]::remove_hash and dir->next may point to other entries
+        * that are still valid, so we must not free the memory.
+        */
+       struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce));
+       while (dir && dir->nr && !(--dir->nr))
+               dir = dir->parent;
  }
  
  static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
        if (ce->ce_flags & CE_HASHED)
                return;
        ce->ce_flags |= CE_HASHED;
-       ce->next = ce->dir_next = NULL;
+       ce->next = NULL;
        hash = hash_name(ce->name, ce_namelen(ce));
        pos = insert_hash(hash, ce, &istate->name_hash);
        if (pos) {
                *pos = ce;
        }
  
-       if (ignore_case)
-               hash_index_entry_directories(istate, ce);
+       if (ignore_case && !(ce->ce_flags & CE_UNHASHED))
+               add_dir_entry(istate, ce);
  }
  
  static void lazy_init_name_hash(struct index_state *istate)
  
  void add_name_hash(struct index_state *istate, struct cache_entry *ce)
  {
+       /* if already hashed, add reference to directory entries */
+       if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_STATE_MASK)
+               add_dir_entry(istate, ce);
        ce->ce_flags &= ~CE_UNHASHED;
        if (istate->name_hash_initialized)
                hash_index_entry(istate, ce);
  }
  
+ /*
+  * We don't actually *remove* it, we can just mark it invalid so that
+  * we won't find it in lookups.
+  *
+  * Not only would we have to search the lists (simple enough), but
+  * we'd also have to rehash other hash buckets in case this makes the
+  * hash bucket empty (common). So it's much better to just mark
+  * it.
+  */
+ void remove_name_hash(struct index_state *istate, struct cache_entry *ce)
+ {
+       /* if already hashed, release reference to directory entries */
+       if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_HASHED)
+               remove_dir_entry(istate, ce);
+       ce->ce_flags |= CE_UNHASHED;
+ }
  static int slow_same_name(const char *name1, int len1, const char *name2, int len2)
  {
        if (len1 != len2)
@@@ -137,18 -217,7 +217,7 @@@ static int same_name(const struct cache
        if (!icase)
                return 0;
  
-       /*
-        * If the entry we're comparing is a filename (no trailing slash), then compare
-        * the lengths exactly.
-        */
-       if (name[namelen - 1] != '/')
-               return slow_same_name(name, namelen, ce->name, len);
-       /*
-        * For a directory, we point to an arbitrary cache_entry filename.  Just
-        * make sure the directory portion matches.
-        */
-       return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len);
+       return slow_same_name(name, namelen, ce->name, len);
  }
  
  struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)
                        if (same_name(ce, name, namelen, icase))
                                return ce;
                }
-               if (icase && name[namelen - 1] == '/')
-                       ce = ce->dir_next;
-               else
-                       ce = ce->next;
+               ce = ce->next;
        }
  
        /*
-        * Might be a submodule.  Despite submodules being directories,
+        * When looking for a directory (trailing '/'), it might be a
+        * submodule or a directory. Despite submodules being directories,
         * they are stored in the name hash without a closing slash.
-        * When ignore_case is 1, directories are stored in the name hash
-        * with their closing slash.
+        * When ignore_case is 1, directories are stored in a separate hash
+        * table *with* their closing slash.
         *
         * The side effect of this storage technique is we have need to
+        * lookup the directory in a separate hash table, and if not found
         * remove the slash from name and perform the lookup again without
         * the slash.  If a match is made, S_ISGITLINK(ce->mode) will be
         * true.
         */
        if (icase && name[namelen - 1] == '/') {
+               struct dir_entry *dir = find_dir_entry(istate, name, namelen);
+               if (dir && dir->nr)
+                       return dir->ce;
                ce = index_name_exists(istate, name, namelen - 1, icase);
                if (ce && S_ISGITLINK(ce->ce_mode))
                        return ce;
        }
        return NULL;
  }
+ static int free_dir_entry(void *entry, void *unused)
+ {
+       struct dir_entry *dir = entry;
+       while (dir) {
+               struct dir_entry *next = dir->next;
+               free(dir);
+               dir = next;
+       }
+       return 0;
+ }
+ void free_name_hash(struct index_state *istate)
+ {
+       if (!istate->name_hash_initialized)
+               return;
+       istate->name_hash_initialized = 0;
+       if (ignore_case)
+               /* free directory entries */
+               for_each_hash(&istate->dir_hash, free_dir_entry, NULL);
+       free_hash(&istate->name_hash);
+       free_hash(&istate->dir_hash);
+ }
diff --combined read-cache.c
index 670a06bc7996da83650d47a72685315f918e26b2,b4d08254d656117960d45158bcde695ceac33254..5a9704f4e5b974a46bc5486373a26816c5b3b4bd
@@@ -46,7 -46,7 +46,7 @@@ static void replace_index_entry(struct 
  {
        struct cache_entry *old = istate->cache[nr];
  
-       remove_name_hash(old);
+       remove_name_hash(istate, old);
        set_index_entry(istate, nr, ce);
        istate->cache_changed = 1;
  }
@@@ -197,25 -197,21 +197,25 @@@ static int ce_match_stat_basic(struct c
        }
        if (ce->ce_mtime.sec != (unsigned int)st->st_mtime)
                changed |= MTIME_CHANGED;
 -      if (trust_ctime && ce->ce_ctime.sec != (unsigned int)st->st_ctime)
 +      if (trust_ctime && check_stat &&
 +          ce->ce_ctime.sec != (unsigned int)st->st_ctime)
                changed |= CTIME_CHANGED;
  
  #ifdef USE_NSEC
 -      if (ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
 +      if (check_stat && ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
                changed |= MTIME_CHANGED;
 -      if (trust_ctime && ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
 +      if (trust_ctime && check_stat &&
 +          ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
                changed |= CTIME_CHANGED;
  #endif
  
 -      if (ce->ce_uid != (unsigned int) st->st_uid ||
 -          ce->ce_gid != (unsigned int) st->st_gid)
 -              changed |= OWNER_CHANGED;
 -      if (ce->ce_ino != (unsigned int) st->st_ino)
 -              changed |= INODE_CHANGED;
 +      if (check_stat) {
 +              if (ce->ce_uid != (unsigned int) st->st_uid ||
 +                      ce->ce_gid != (unsigned int) st->st_gid)
 +                      changed |= OWNER_CHANGED;
 +              if (ce->ce_ino != (unsigned int) st->st_ino)
 +                      changed |= INODE_CHANGED;
 +      }
  
  #ifdef USE_STDEV
        /*
         * clients will have different views of what "device"
         * the filesystem is on
         */
 -      if (ce->ce_dev != (unsigned int) st->st_dev)
 -              changed |= INODE_CHANGED;
 +      if (check_stat && ce->ce_dev != (unsigned int) st->st_dev)
 +                      changed |= INODE_CHANGED;
  #endif
  
        if (ce->ce_size != (unsigned int) st->st_size)
@@@ -460,7 -456,7 +460,7 @@@ int remove_index_entry_at(struct index_
        struct cache_entry *ce = istate->cache[pos];
  
        record_resolve_undo(istate, ce);
-       remove_name_hash(ce);
+       remove_name_hash(istate, ce);
        istate->cache_changed = 1;
        istate->cache_nr--;
        if (pos >= istate->cache_nr)
@@@ -483,7 -479,7 +483,7 @@@ void remove_marked_cache_entries(struc
  
        for (i = j = 0; i < istate->cache_nr; i++) {
                if (ce_array[i]->ce_flags & CE_REMOVE)
-                       remove_name_hash(ce_array[i]);
+                       remove_name_hash(istate, ce_array[i]);
                else
                        ce_array[j++] = ce_array[i];
        }
@@@ -1515,8 -1511,7 +1515,7 @@@ int discard_index(struct index_state *i
        istate->cache_changed = 0;
        istate->timestamp.sec = 0;
        istate->timestamp.nsec = 0;
-       istate->name_hash_initialized = 0;
-       free_hash(&istate->name_hash);
+       free_name_hash(istate);
        cache_tree_free(&(istate->cache_tree));
        istate->initialized = 0;
  
diff --combined refs.c
index 175b9fcaa25eba2ad02564b32eba04c3351978c5,6770e962a94cc4f1709fe7e7741f0a896a416aed..e2b760d0baffd6db6b49e6014a58efa062f1a119
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -3,7 -3,6 +3,7 @@@
  #include "object.h"
  #include "tag.h"
  #include "dir.h"
 +#include "string-list.h"
  
  /*
   * Make sure "ref" is something reasonable to have under ".git/refs/";
@@@ -334,12 -333,14 +334,12 @@@ struct string_slice 
  
  static int ref_entry_cmp_sslice(const void *key_, const void *ent_)
  {
 -      struct string_slice *key = (struct string_slice *)key_;
 -      struct ref_entry *ent = *(struct ref_entry **)ent_;
 -      int entlen = strlen(ent->name);
 -      int cmplen = key->len < entlen ? key->len : entlen;
 -      int cmp = memcmp(key->str, ent->name, cmplen);
 +      const struct string_slice *key = key_;
 +      const struct ref_entry *ent = *(const struct ref_entry * const *)ent_;
 +      int cmp = strncmp(key->str, ent->name, key->len);
        if (cmp)
                return cmp;
 -      return key->len - entlen;
 +      return '\0' - (unsigned char)ent->name[key->len];
  }
  
  /*
@@@ -803,11 -804,38 +803,38 @@@ static const char *parse_ref_line(char 
        return line;
  }
  
+ /*
+  * Read f, which is a packed-refs file, into dir.
+  *
+  * A comment line of the form "# pack-refs with: " may contain zero or
+  * more traits. We interpret the traits as follows:
+  *
+  *   No traits:
+  *
+  *      Probably no references are peeled. But if the file contains a
+  *      peeled value for a reference, we will use it.
+  *
+  *   peeled:
+  *
+  *      References under "refs/tags/", if they *can* be peeled, *are*
+  *      peeled in this file. References outside of "refs/tags/" are
+  *      probably not peeled even if they could have been, but if we find
+  *      a peeled value for such a reference we will use it.
+  *
+  *   fully-peeled:
+  *
+  *      All references in the file that can be peeled are peeled.
+  *      Inversely (and this is more important), any references in the
+  *      file for which no peeled value is recorded is not peelable. This
+  *      trait should typically be written alongside "peeled" for
+  *      compatibility with older clients, but we do not require it
+  *      (i.e., "peeled" is a no-op if "fully-peeled" is set).
+  */
  static void read_packed_refs(FILE *f, struct ref_dir *dir)
  {
        struct ref_entry *last = NULL;
        char refline[PATH_MAX];
-       int flag = REF_ISPACKED;
+       enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
  
        while (fgets(refline, sizeof(refline), f)) {
                unsigned char sha1[20];
  
                if (!strncmp(refline, header, sizeof(header)-1)) {
                        const char *traits = refline + sizeof(header) - 1;
-                       if (strstr(traits, " peeled "))
-                               flag |= REF_KNOWS_PEELED;
+                       if (strstr(traits, " fully-peeled "))
+                               peeled = PEELED_FULLY;
+                       else if (strstr(traits, " peeled "))
+                               peeled = PEELED_TAGS;
                        /* perhaps other traits later as well */
                        continue;
                }
  
                refname = parse_ref_line(refline, sha1);
                if (refname) {
-                       last = create_ref_entry(refname, sha1, flag, 1);
+                       last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
+                       if (peeled == PEELED_FULLY ||
+                           (peeled == PEELED_TAGS && !prefixcmp(refname, "refs/tags/")))
+                               last->flag |= REF_KNOWS_PEELED;
                        add_ref(dir, last);
                        continue;
                }
                    refline[0] == '^' &&
                    strlen(refline) == 42 &&
                    refline[41] == '\n' &&
-                   !get_sha1_hex(refline + 1, sha1))
+                   !get_sha1_hex(refline + 1, sha1)) {
                        hashcpy(last->u.value.peeled, sha1);
+                       /*
+                        * Regardless of what the file header said,
+                        * we definitely know the value of *this*
+                        * reference:
+                        */
+                       last->flag |= REF_KNOWS_PEELED;
+               }
        }
  }
  
@@@ -2555,46 -2595,3 +2594,46 @@@ char *shorten_unambiguous_ref(const cha
        free(short_name);
        return xstrdup(refname);
  }
 +
 +static struct string_list *hide_refs;
 +
 +int parse_hide_refs_config(const char *var, const char *value, const char *section)
 +{
 +      if (!strcmp("transfer.hiderefs", var) ||
 +          /* NEEDSWORK: use parse_config_key() once both are merged */
 +          (!prefixcmp(var, section) && var[strlen(section)] == '.' &&
 +           !strcmp(var + strlen(section), ".hiderefs"))) {
 +              char *ref;
 +              int len;
 +
 +              if (!value)
 +                      return config_error_nonbool(var);
 +              ref = xstrdup(value);
 +              len = strlen(ref);
 +              while (len && ref[len - 1] == '/')
 +                      ref[--len] = '\0';
 +              if (!hide_refs) {
 +                      hide_refs = xcalloc(1, sizeof(*hide_refs));
 +                      hide_refs->strdup_strings = 1;
 +              }
 +              string_list_append(hide_refs, ref);
 +      }
 +      return 0;
 +}
 +
 +int ref_is_hidden(const char *refname)
 +{
 +      struct string_list_item *item;
 +
 +      if (!hide_refs)
 +              return 0;
 +      for_each_string_list_item(item, hide_refs) {
 +              int len;
 +              if (prefixcmp(refname, item->string))
 +                      continue;
 +              len = strlen(item->string);
 +              if (!refname[len] || refname[len] == '/')
 +                      return 1;
 +      }
 +      return 0;
 +}
diff --combined revision.c
index ef6020541282770b9edc4c921cadb7ab1506da56,59b26c7e99724a7589bc3fcc37486ee3d1bb2ef5..cf620c6b3693e2d18b2cacd33574a03b34e4c48e
@@@ -13,7 -13,6 +13,7 @@@
  #include "decorate.h"
  #include "log-tree.h"
  #include "string-list.h"
 +#include "mailmap.h"
  
  volatile show_early_output_fn_t show_early_output;
  
@@@ -709,7 -708,7 +709,7 @@@ static int still_interesting(struct com
         * Does the destination list contain entries with a date
         * before the source list? Definitely _not_ done.
         */
-       if (date < src->item->date)
+       if (date <= src->item->date)
                return SLOP;
  
        /*
@@@ -2220,58 -2219,10 +2220,58 @@@ static int rewrite_parents(struct rev_i
        return 0;
  }
  
 +static int commit_rewrite_person(struct strbuf *buf, const char *what, struct string_list *mailmap)
 +{
 +      char *person, *endp;
 +      size_t len, namelen, maillen;
 +      const char *name;
 +      const char *mail;
 +      struct ident_split ident;
 +
 +      person = strstr(buf->buf, what);
 +      if (!person)
 +              return 0;
 +
 +      person += strlen(what);
 +      endp = strchr(person, '\n');
 +      if (!endp)
 +              return 0;
 +
 +      len = endp - person;
 +
 +      if (split_ident_line(&ident, person, len))
 +              return 0;
 +
 +      mail = ident.mail_begin;
 +      maillen = ident.mail_end - ident.mail_begin;
 +      name = ident.name_begin;
 +      namelen = ident.name_end - ident.name_begin;
 +
 +      if (map_user(mailmap, &mail, &maillen, &name, &namelen)) {
 +              struct strbuf namemail = STRBUF_INIT;
 +
 +              strbuf_addf(&namemail, "%.*s <%.*s>",
 +                          (int)namelen, name, (int)maillen, mail);
 +
 +              strbuf_splice(buf, ident.name_begin - buf->buf,
 +                            ident.mail_end - ident.name_begin + 1,
 +                            namemail.buf, namemail.len);
 +
 +              strbuf_release(&namemail);
 +
 +              return 1;
 +      }
 +
 +      return 0;
 +}
 +
  static int commit_match(struct commit *commit, struct rev_info *opt)
  {
        int retval;
 +      const char *encoding;
 +      char *message;
        struct strbuf buf = STRBUF_INIT;
 +
        if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list)
                return 1;
  
                strbuf_addch(&buf, '\n');
        }
  
 +      /*
 +       * We grep in the user's output encoding, under the assumption that it
 +       * is the encoding they are most likely to write their grep pattern
 +       * for. In addition, it means we will match the "notes" encoding below,
 +       * so we will not end up with a buffer that has two different encodings
 +       * in it.
 +       */
 +      encoding = get_log_output_encoding();
 +      message = logmsg_reencode(commit, encoding);
 +
        /* Copy the commit to temporary if we are using "fake" headers */
        if (buf.len)
 -              strbuf_addstr(&buf, commit->buffer);
 +              strbuf_addstr(&buf, message);
 +
 +      if (opt->grep_filter.header_list && opt->mailmap) {
 +              if (!buf.len)
 +                      strbuf_addstr(&buf, message);
 +
 +              commit_rewrite_person(&buf, "\nauthor ", opt->mailmap);
 +              commit_rewrite_person(&buf, "\ncommitter ", opt->mailmap);
 +      }
  
        /* Append "fake" message parts as needed */
        if (opt->show_notes) {
                if (!buf.len)
 -                      strbuf_addstr(&buf, commit->buffer);
 -              format_display_notes(commit->object.sha1, &buf,
 -                                   get_log_output_encoding(), 1);
 +                      strbuf_addstr(&buf, message);
 +              format_display_notes(commit->object.sha1, &buf, encoding, 1);
        }
  
 -      /* Find either in the commit object, or in the temporary */
 +      /* Find either in the original commit message, or in the temporary */
        if (buf.len)
                retval = grep_buffer(&opt->grep_filter, buf.buf, buf.len);
        else
                retval = grep_buffer(&opt->grep_filter,
 -                                   commit->buffer, strlen(commit->buffer));
 +                                   message, strlen(message));
        strbuf_release(&buf);
 +      logmsg_free(message, commit);
        return retval;
  }