Merge branch 'tg/maint-cache-name-compare' into tg/ce-namelen-field
authorJunio C Hamano <gitster@pobox.com>
Wed, 11 Jul 2012 16:40:25 +0000 (09:40 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 11 Jul 2012 16:40:25 +0000 (09:40 -0700)
* tg/maint-cache-name-compare:
cache_name_compare(): do not truncate while comparing paths

1  2 
read-cache.c
diff --combined read-cache.c
index ef355cc9a89b948688756b8c3681ac607518492b,0cd13aabb96bd0733040f464733579713c963bd1..b645827c06a268ee4721fda5fc8cdcd84775c9ec
@@@ -12,8 -12,6 +12,8 @@@
  #include "commit.h"
  #include "blob.h"
  #include "resolve-undo.h"
 +#include "strbuf.h"
 +#include "varint.h"
  
  static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
  
@@@ -94,7 -92,7 +94,7 @@@ static int ce_compare_data(struct cache
  
        if (fd >= 0) {
                unsigned char sha1[20];
 -              if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name))
 +              if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name, 0))
                        match = hashcmp(sha1, ce->sha1);
                /* index_fd() closed the file descriptor already */
        }
@@@ -159,6 -157,16 +159,6 @@@ static int ce_modified_check_fs(struct 
        return 0;
  }
  
 -static int is_empty_blob_sha1(const unsigned char *sha1)
 -{
 -      static const unsigned char empty_blob_sha1[20] = {
 -              0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b,
 -              0x29,0xae,0x77,0x5a,0xd8,0xc2,0xe4,0x8c,0x53,0x91
 -      };
 -
 -      return !hashcmp(sha1, empty_blob_sha1);
 -}
 -
  static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
  {
        unsigned int changed = 0;
@@@ -397,10 -405,15 +397,15 @@@ int df_name_compare(const char *name1, 
  
  int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2)
  {
-       int len1 = flags1 & CE_NAMEMASK;
-       int len2 = flags2 & CE_NAMEMASK;
-       int len = len1 < len2 ? len1 : len2;
-       int cmp;
+       int len1, len2, len, cmp;
+       len1 = flags1 & CE_NAMEMASK;
+       if (CE_NAMEMASK <= len1)
+               len1 = strlen(name1 + CE_NAMEMASK) + CE_NAMEMASK;
+       len2 = flags2 & CE_NAMEMASK;
+       if (CE_NAMEMASK <= len2)
+               len2 = strlen(name2 + CE_NAMEMASK) + CE_NAMEMASK;
+       len = len1 < len2 ? len1 : len2;
  
        cmp = memcmp(name1, name2, len);
        if (cmp)
@@@ -600,29 -613,6 +605,29 @@@ int add_to_index(struct index_state *is
                ce->ce_mode = ce_mode_from_stat(ent, st_mode);
        }
  
 +      /* When core.ignorecase=true, determine if a directory of the same name but differing
 +       * case already exists within the Git repository.  If it does, ensure the directory
 +       * case of the file being added to the repository matches (is folded into) the existing
 +       * entry's directory case.
 +       */
 +      if (ignore_case) {
 +              const char *startPtr = ce->name;
 +              const char *ptr = startPtr;
 +              while (*ptr) {
 +                      while (*ptr && *ptr != '/')
 +                              ++ptr;
 +                      if (*ptr == '/') {
 +                              struct cache_entry *foundce;
 +                              ++ptr;
 +                              foundce = index_name_exists(&the_index, ce->name, ptr - ce->name, ignore_case);
 +                              if (foundce) {
 +                                      memcpy((void *)startPtr, foundce->name + (startPtr - ce->name), ptr - startPtr);
 +                                      startPtr = ptr;
 +                              }
 +                      }
 +              }
 +      }
 +
        alias = index_name_exists(istate, ce->name, ce_namelen(ce), ignore_case);
        if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
                /* Nothing changed, really */
                return 0;
        }
        if (!intent_only) {
 -              if (index_path(ce->sha1, path, st, 1))
 +              if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT))
                        return error("unable to index file %s", path);
        } else
                record_intent_to_add(ce);
@@@ -698,9 -688,30 +703,9 @@@ int ce_same_name(struct cache_entry *a
        return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
  }
  
 -int ce_path_match(const struct cache_entry *ce, const char **pathspec)
 +int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec)
  {
 -      const char *match, *name;
 -      int len;
 -
 -      if (!pathspec)
 -              return 1;
 -
 -      len = ce_namelen(ce);
 -      name = ce->name;
 -      while ((match = *pathspec++) != NULL) {
 -              int matchlen = strlen(match);
 -              if (matchlen > len)
 -                      continue;
 -              if (memcmp(name, match, matchlen))
 -                      continue;
 -              if (matchlen && name[matchlen-1] == '/')
 -                      return 1;
 -              if (name[matchlen] == '/' || !name[matchlen])
 -                      return 1;
 -              if (!matchlen)
 -                      return 1;
 -      }
 -      return 0;
 +      return match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL);
  }
  
  /*
@@@ -718,12 -729,11 +723,12 @@@ static int verify_dotfile(const char *r
         * has already been discarded, we now test
         * the rest.
         */
 -      switch (*rest) {
 +
        /* "." is not allowed */
 -      case '\0': case '/':
 +      if (*rest == '\0' || is_dir_sep(*rest))
                return 0;
  
 +      switch (*rest) {
        /*
         * ".git" followed by  NUL or slash is bad. This
         * shares the path end test with the ".." case.
                rest += 2;
        /* fallthrough */
        case '.':
 -              if (rest[1] == '\0' || rest[1] == '/')
 +              if (rest[1] == '\0' || is_dir_sep(rest[1]))
                        return 0;
        }
        return 1;
@@@ -746,19 -756,23 +751,19 @@@ int verify_path(const char *path
  {
        char c;
  
 +      if (has_dos_drive_prefix(path))
 +              return 0;
 +
        goto inside;
        for (;;) {
                if (!c)
                        return 1;
 -              if (c == '/') {
 +              if (is_dir_sep(c)) {
  inside:
                        c = *path++;
 -                      switch (c) {
 -                      default:
 -                              continue;
 -                      case '/': case '\0':
 -                              break;
 -                      case '.':
 -                              if (verify_dotfile(path))
 -                                      continue;
 -                      }
 -                      return 0;
 +                      if ((c == '.' && !verify_dotfile(path)) ||
 +                          is_dir_sep(c) || c == '\0')
 +                              return 0;
                }
                c = *path++;
        }
@@@ -993,8 -1007,7 +998,8 @@@ int add_index_entry(struct index_state 
   */
  static struct cache_entry *refresh_cache_ent(struct index_state *istate,
                                             struct cache_entry *ce,
 -                                           unsigned int options, int *err)
 +                                           unsigned int options, int *err,
 +                                           int *changed_ret)
  {
        struct stat st;
        struct cache_entry *updated;
        }
  
        changed = ie_match_stat(istate, ce, &st, options);
 +      if (changed_ret)
 +              *changed_ret = changed;
        if (!changed) {
                /*
                 * The path is unchanged.  If we were told to ignore
  }
  
  static void show_file(const char * fmt, const char * name, int in_porcelain,
 -                    int * first, char *header_msg)
 +                    int * first, const char *header_msg)
  {
        if (in_porcelain && *first && header_msg) {
                printf("%s\n", header_msg);
 -              *first=0;
 +              *first = 0;
        }
        printf(fmt, name);
  }
  
  int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec,
 -                char *seen, char *header_msg)
 +                char *seen, const char *header_msg)
  {
        int i;
        int has_errors = 0;
        int first = 1;
        int in_porcelain = (flags & REFRESH_IN_PORCELAIN);
        unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0;
 -      const char *needs_update_fmt;
 -      const char *needs_merge_fmt;
 -
 -      needs_update_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
 -      needs_merge_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
 +      const char *modified_fmt;
 +      const char *deleted_fmt;
 +      const char *typechange_fmt;
 +      const char *added_fmt;
 +      const char *unmerged_fmt;
 +
 +      modified_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
 +      deleted_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n");
 +      typechange_fmt = (in_porcelain ? "T\t%s\n" : "%s needs update\n");
 +      added_fmt = (in_porcelain ? "A\t%s\n" : "%s needs update\n");
 +      unmerged_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
        for (i = 0; i < istate->cache_nr; i++) {
                struct cache_entry *ce, *new;
                int cache_errno = 0;
 +              int changed = 0;
 +              int filtered = 0;
  
                ce = istate->cache[i];
                if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
                        continue;
  
 +              if (pathspec &&
 +                  !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
 +                      filtered = 1;
 +
                if (ce_stage(ce)) {
                        while ((i < istate->cache_nr) &&
                               ! strcmp(istate->cache[i]->name, ce->name))
                        i--;
                        if (allow_unmerged)
                                continue;
 -                      show_file(needs_merge_fmt, ce->name, in_porcelain, &first, header_msg);
 +                      if (!filtered)
 +                              show_file(unmerged_fmt, ce->name, in_porcelain,
 +                                        &first, header_msg);
                        has_errors = 1;
                        continue;
                }
  
 -              if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
 +              if (filtered)
                        continue;
  
 -              new = refresh_cache_ent(istate, ce, options, &cache_errno);
 +              new = refresh_cache_ent(istate, ce, options, &cache_errno, &changed);
                if (new == ce)
                        continue;
                if (!new) {
 +                      const char *fmt;
 +
                        if (not_new && cache_errno == ENOENT)
                                continue;
                        if (really && cache_errno == EINVAL) {
                        }
                        if (quiet)
                                continue;
 -                      show_file(needs_update_fmt, ce->name, in_porcelain, &first, header_msg);
 +
 +                      if (cache_errno == ENOENT)
 +                              fmt = deleted_fmt;
 +                      else if (ce->ce_flags & CE_INTENT_TO_ADD)
 +                              fmt = added_fmt; /* must be before other checks */
 +                      else if (changed & TYPE_CHANGED)
 +                              fmt = typechange_fmt;
 +                      else
 +                              fmt = modified_fmt;
 +                      show_file(fmt,
 +                                ce->name, in_porcelain, &first, header_msg);
                        has_errors = 1;
                        continue;
                }
  
  static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
  {
 -      return refresh_cache_ent(&the_index, ce, really, NULL);
 +      return refresh_cache_ent(&the_index, ce, really, NULL, NULL);
  }
  
 +
 +/*****************************************************************
 + * Index File I/O
 + *****************************************************************/
 +
 +#define INDEX_FORMAT_DEFAULT 3
 +
 +/*
 + * dev/ino/uid/gid/size are also just tracked to the low 32 bits
 + * Again - this is just a (very strong in practice) heuristic that
 + * the inode hasn't changed.
 + *
 + * We save the fields in big-endian order to allow using the
 + * index file over NFS transparently.
 + */
 +struct ondisk_cache_entry {
 +      struct cache_time ctime;
 +      struct cache_time mtime;
 +      unsigned int dev;
 +      unsigned int ino;
 +      unsigned int mode;
 +      unsigned int uid;
 +      unsigned int gid;
 +      unsigned int size;
 +      unsigned char sha1[20];
 +      unsigned short flags;
 +      char name[FLEX_ARRAY]; /* more */
 +};
 +
 +/*
 + * This struct is used when CE_EXTENDED bit is 1
 + * The struct must match ondisk_cache_entry exactly from
 + * ctime till flags
 + */
 +struct ondisk_cache_entry_extended {
 +      struct cache_time ctime;
 +      struct cache_time mtime;
 +      unsigned int dev;
 +      unsigned int ino;
 +      unsigned int mode;
 +      unsigned int uid;
 +      unsigned int gid;
 +      unsigned int size;
 +      unsigned char sha1[20];
 +      unsigned short flags;
 +      unsigned short flags2;
 +      char name[FLEX_ARRAY]; /* more */
 +};
 +
 +/* These are only used for v3 or lower */
 +#define align_flex_name(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
 +#define ondisk_cache_entry_size(len) align_flex_name(ondisk_cache_entry,len)
 +#define ondisk_cache_entry_extended_size(len) align_flex_name(ondisk_cache_entry_extended,len)
 +#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
 +                          ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
 +                          ondisk_cache_entry_size(ce_namelen(ce)))
 +
  static int verify_hdr(struct cache_header *hdr, unsigned long size)
  {
        git_SHA_CTX c;
        unsigned char sha1[20];
 +      int hdr_version;
  
        if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
                return error("bad signature");
 -      if (hdr->hdr_version != htonl(2) && hdr->hdr_version != htonl(3))
 -              return error("bad index version");
 +      hdr_version = ntohl(hdr->hdr_version);
 +      if (hdr_version < 2 || 4 < hdr_version)
 +              return error("bad index version %d", hdr_version);
        git_SHA1_Init(&c);
        git_SHA1_Update(&c, hdr, size - 20);
        git_SHA1_Final(sha1, &c);
@@@ -1282,115 -1208,63 +1287,115 @@@ int read_index(struct index_state *ista
        return read_index_from(istate, get_index_file());
  }
  
 -static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce)
 +#ifndef NEEDS_ALIGNED_ACCESS
 +#define ntoh_s(var) ntohs(var)
 +#define ntoh_l(var) ntohl(var)
 +#else
 +static inline uint16_t ntoh_s_force_align(void *p)
 +{
 +      uint16_t x;
 +      memcpy(&x, p, sizeof(x));
 +      return ntohs(x);
 +}
 +static inline uint32_t ntoh_l_force_align(void *p)
 +{
 +      uint32_t x;
 +      memcpy(&x, p, sizeof(x));
 +      return ntohl(x);
 +}
 +#define ntoh_s(var) ntoh_s_force_align(&(var))
 +#define ntoh_l(var) ntoh_l_force_align(&(var))
 +#endif
 +
 +static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *ondisk,
 +                                                 unsigned int flags,
 +                                                 const char *name,
 +                                                 size_t len)
  {
 +      struct cache_entry *ce = xmalloc(cache_entry_size(len));
 +
 +      ce->ce_ctime.sec = ntoh_l(ondisk->ctime.sec);
 +      ce->ce_mtime.sec = ntoh_l(ondisk->mtime.sec);
 +      ce->ce_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
 +      ce->ce_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
 +      ce->ce_dev   = ntoh_l(ondisk->dev);
 +      ce->ce_ino   = ntoh_l(ondisk->ino);
 +      ce->ce_mode  = ntoh_l(ondisk->mode);
 +      ce->ce_uid   = ntoh_l(ondisk->uid);
 +      ce->ce_gid   = ntoh_l(ondisk->gid);
 +      ce->ce_size  = ntoh_l(ondisk->size);
 +      ce->ce_flags = flags;
 +      hashcpy(ce->sha1, ondisk->sha1);
 +      memcpy(ce->name, name, len);
 +      ce->name[len] = '\0';
 +      return ce;
 +}
 +
 +/*
 + * Adjacent cache entries tend to share the leading paths, so it makes
 + * sense to only store the differences in later entries.  In the v4
 + * on-disk format of the index, each on-disk cache entry stores the
 + * number of bytes to be stripped from the end of the previous name,
 + * and the bytes to append to the result, to come up with its name.
 + */
 +static unsigned long expand_name_field(struct strbuf *name, const char *cp_)
 +{
 +      const unsigned char *ep, *cp = (const unsigned char *)cp_;
 +      size_t len = decode_varint(&cp);
 +
 +      if (name->len < len)
 +              die("malformed name field in the index");
 +      strbuf_remove(name, name->len - len, len);
 +      for (ep = cp; *ep; ep++)
 +              ; /* find the end */
 +      strbuf_add(name, cp, ep - cp);
 +      return (const char *)ep + 1 - cp_;
 +}
 +
 +static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk,
 +                                          unsigned long *ent_size,
 +                                          struct strbuf *previous_name)
 +{
 +      struct cache_entry *ce;
        size_t len;
        const char *name;
 +      unsigned int flags;
  
 -      ce->ce_ctime.sec = ntohl(ondisk->ctime.sec);
 -      ce->ce_mtime.sec = ntohl(ondisk->mtime.sec);
 -      ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec);
 -      ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec);
 -      ce->ce_dev   = ntohl(ondisk->dev);
 -      ce->ce_ino   = ntohl(ondisk->ino);
 -      ce->ce_mode  = ntohl(ondisk->mode);
 -      ce->ce_uid   = ntohl(ondisk->uid);
 -      ce->ce_gid   = ntohl(ondisk->gid);
 -      ce->ce_size  = ntohl(ondisk->size);
        /* On-disk flags are just 16 bits */
 -      ce->ce_flags = ntohs(ondisk->flags);
 -
 -      hashcpy(ce->sha1, ondisk->sha1);
 -
 -      len = ce->ce_flags & CE_NAMEMASK;
 +      flags = ntoh_s(ondisk->flags);
 +      len = flags & CE_NAMEMASK;
  
 -      if (ce->ce_flags & CE_EXTENDED) {
 +      if (flags & CE_EXTENDED) {
                struct ondisk_cache_entry_extended *ondisk2;
                int extended_flags;
                ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
 -              extended_flags = ntohs(ondisk2->flags2) << 16;
 +              extended_flags = ntoh_s(ondisk2->flags2) << 16;
                /* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
                if (extended_flags & ~CE_EXTENDED_FLAGS)
                        die("Unknown index entry format %08x", extended_flags);
 -              ce->ce_flags |= extended_flags;
 +              flags |= extended_flags;
                name = ondisk2->name;
        }
        else
                name = ondisk->name;
  
 -      if (len == CE_NAMEMASK)
 -              len = strlen(name);
 -      /*
 -       * NEEDSWORK: If the original index is crafted, this copy could
 -       * go unchecked.
 -       */
 -      memcpy(ce->name, name, len + 1);
 -}
 -
 -static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
 -{
 -      long per_entry;
 -
 -      per_entry = sizeof(struct cache_entry) - sizeof(struct ondisk_cache_entry);
 -
 -      /*
 -       * Alignment can cause differences. This should be "alignof", but
 -       * since that's a gcc'ism, just use the size of a pointer.
 -       */
 -      per_entry += sizeof(void *);
 -      return ondisk_size + entries*per_entry;
 +      if (!previous_name) {
 +              /* v3 and earlier */
 +              if (len == CE_NAMEMASK)
 +                      len = strlen(name);
 +              ce = cache_entry_from_ondisk(ondisk, flags, name, len);
 +
 +              *ent_size = ondisk_ce_size(ce);
 +      } else {
 +              unsigned long consumed;
 +              consumed = expand_name_field(previous_name, name);
 +              ce = cache_entry_from_ondisk(ondisk, flags,
 +                                           previous_name->buf,
 +                                           previous_name->len);
 +
 +              *ent_size = (name - ((char *)ondisk)) + consumed;
 +      }
 +      return ce;
  }
  
  /* remember to discard_cache() before reading a different cache! */
@@@ -1398,11 -1272,10 +1403,11 @@@ int read_index_from(struct index_state 
  {
        int fd, i;
        struct stat st;
 -      unsigned long src_offset, dst_offset;
 +      unsigned long src_offset;
        struct cache_header *hdr;
        void *mmap;
        size_t mmap_size;
 +      struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
  
        errno = EBUSY;
        if (istate->initialized)
        if (verify_hdr(hdr, mmap_size) < 0)
                goto unmap;
  
 +      istate->version = ntohl(hdr->hdr_version);
        istate->cache_nr = ntohl(hdr->hdr_entries);
        istate->cache_alloc = alloc_nr(istate->cache_nr);
        istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
 -
 -      /*
 -       * The disk format is actually larger than the in-memory format,
 -       * due to space for nsec etc, so even though the in-memory one
 -       * has room for a few  more flags, we can allocate using the same
 -       * index size
 -       */
 -      istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr));
        istate->initialized = 1;
  
 +      if (istate->version == 4)
 +              previous_name = &previous_name_buf;
 +      else
 +              previous_name = NULL;
 +
        src_offset = sizeof(*hdr);
 -      dst_offset = 0;
        for (i = 0; i < istate->cache_nr; i++) {
                struct ondisk_cache_entry *disk_ce;
                struct cache_entry *ce;
 +              unsigned long consumed;
  
                disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
 -              ce = (struct cache_entry *)((char *)istate->alloc + dst_offset);
 -              convert_from_disk(disk_ce, ce);
 +              ce = create_from_disk(disk_ce, &consumed, previous_name);
                set_index_entry(istate, i, ce);
  
 -              src_offset += ondisk_ce_size(ce);
 -              dst_offset += ce_size(ce);
 +              src_offset += consumed;
        }
 +      strbuf_release(&previous_name_buf);
        istate->timestamp.sec = st.st_mtime;
        istate->timestamp.nsec = ST_MTIME_NSEC(st);
  
@@@ -1491,15 -1367,11 +1496,15 @@@ unmap
  
  int is_index_unborn(struct index_state *istate)
  {
 -      return (!istate->cache_nr && !istate->alloc && !istate->timestamp.sec);
 +      return (!istate->cache_nr && !istate->timestamp.sec);
  }
  
  int discard_index(struct index_state *istate)
  {
 +      int i;
 +
 +      for (i = 0; i < istate->cache_nr; i++)
 +              free(istate->cache[i]);
        resolve_undo_clear_index(istate);
        istate->cache_nr = 0;
        istate->cache_changed = 0;
        istate->name_hash_initialized = 0;
        free_hash(&istate->name_hash);
        cache_tree_free(&(istate->cache_tree));
 -      free(istate->alloc);
 -      istate->alloc = NULL;
        istate->initialized = 0;
  
        /* no need to throw away allocated active_cache */
@@@ -1642,10 -1516,12 +1647,10 @@@ static void ce_smudge_racily_clean_entr
        }
  }
  
 -static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
 +/* Copy miscellaneous fields but not the name */
 +static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
 +                                     struct cache_entry *ce)
  {
 -      int size = ondisk_ce_size(ce);
 -      struct ondisk_cache_entry *ondisk = xcalloc(1, size);
 -      char *name;
 -
        ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
        ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
        ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
                struct ondisk_cache_entry_extended *ondisk2;
                ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
                ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16);
 -              name = ondisk2->name;
 +              return ondisk2->name;
        }
 -      else
 -              name = ondisk->name;
 -      memcpy(name, ce->name, ce_namelen(ce));
 +      else {
 +              return ondisk->name;
 +      }
 +}
 +
 +static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
 +                        struct strbuf *previous_name)
 +{
 +      int size;
 +      struct ondisk_cache_entry *ondisk;
 +      char *name;
 +      int result;
 +
 +      if (!previous_name) {
 +              size = ondisk_ce_size(ce);
 +              ondisk = xcalloc(1, size);
 +              name = copy_cache_entry_to_ondisk(ondisk, ce);
 +              memcpy(name, ce->name, ce_namelen(ce));
 +      } else {
 +              int common, to_remove, prefix_size;
 +              unsigned char to_remove_vi[16];
 +              for (common = 0;
 +                   (ce->name[common] &&
 +                    common < previous_name->len &&
 +                    ce->name[common] == previous_name->buf[common]);
 +                   common++)
 +                      ; /* still matching */
 +              to_remove = previous_name->len - common;
 +              prefix_size = encode_varint(to_remove, to_remove_vi);
 +
 +              if (ce->ce_flags & CE_EXTENDED)
 +                      size = offsetof(struct ondisk_cache_entry_extended, name);
 +              else
 +                      size = offsetof(struct ondisk_cache_entry, name);
 +              size += prefix_size + (ce_namelen(ce) - common + 1);
 +
 +              ondisk = xcalloc(1, size);
 +              name = copy_cache_entry_to_ondisk(ondisk, ce);
 +              memcpy(name, to_remove_vi, prefix_size);
 +              memcpy(name + prefix_size, ce->name + common, ce_namelen(ce) - common);
  
 -      return ce_write(c, fd, ondisk, size);
 +              strbuf_splice(previous_name, common, to_remove,
 +                            ce->name + common, ce_namelen(ce) - common);
 +      }
 +
 +      result = ce_write(c, fd, ondisk, size);
 +      free(ondisk);
 +      return result;
 +}
 +
 +static int has_racy_timestamp(struct index_state *istate)
 +{
 +      int entries = istate->cache_nr;
 +      int i;
 +
 +      for (i = 0; i < entries; i++) {
 +              struct cache_entry *ce = istate->cache[i];
 +              if (is_racy_timestamp(istate, ce))
 +                      return 1;
 +      }
 +      return 0;
 +}
 +
 +/*
 + * Opportunisticly update the index but do not complain if we can't
 + */
 +void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
 +{
 +      if ((istate->cache_changed || has_racy_timestamp(istate)) &&
 +          !write_index(istate, lockfile->fd))
 +              commit_locked_index(lockfile);
 +      else
 +              rollback_lock_file(lockfile);
  }
  
  int write_index(struct index_state *istate, int newfd)
  {
        git_SHA_CTX c;
        struct cache_header hdr;
 -      int i, err, removed, extended;
 +      int i, err, removed, extended, hdr_version;
        struct cache_entry **cache = istate->cache;
        int entries = istate->cache_nr;
        struct stat st;
 +      struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
  
        for (i = removed = extended = 0; i < entries; i++) {
                if (cache[i]->ce_flags & CE_REMOVE)
                }
        }
  
 +      if (!istate->version)
 +              istate->version = INDEX_FORMAT_DEFAULT;
 +
 +      /* demote version 3 to version 2 when the latter suffices */
 +      if (istate->version == 3 || istate->version == 2)
 +              istate->version = extended ? 3 : 2;
 +
 +      hdr_version = istate->version;
 +
        hdr.hdr_signature = htonl(CACHE_SIGNATURE);
 -      /* for extended format, increase version so older git won't try to read it */
 -      hdr.hdr_version = htonl(extended ? 3 : 2);
 +      hdr.hdr_version = htonl(hdr_version);
        hdr.hdr_entries = htonl(entries - removed);
  
        git_SHA1_Init(&c);
        if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
                return -1;
  
 +      previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
        for (i = 0; i < entries; i++) {
                struct cache_entry *ce = cache[i];
                if (ce->ce_flags & CE_REMOVE)
                        continue;
                if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
                        ce_smudge_racily_clean_entry(ce);
 -              if (ce_write_entry(&c, newfd, ce) < 0)
 +              if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
                        return -1;
        }
 +      strbuf_release(&previous_name_buf);
  
        /* Write extension data here */
        if (istate->cache_tree) {