Merge branch 'lt/gitlink'
authorJunio C Hamano <junkio@cox.net>
Sun, 22 Apr 2007 00:21:10 +0000 (17:21 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 22 Apr 2007 00:21:10 +0000 (17:21 -0700)
* lt/gitlink:
Tests for core subproject support
Expose subprojects as special files to "git diff" machinery
Fix some "git ls-files -o" fallout from gitlinks
Teach "git-read-tree -u" to check out submodules as a directory
Teach git list-objects logic to not follow gitlinks
Fix gitlink index entry filesystem matching
Teach "git-read-tree -u" to check out submodules as a directory
Teach git list-objects logic not to follow gitlinks
Don't show gitlink directories when we want "other" files
Teach git-update-index about gitlinks
Teach directory traversal about subprojects
Fix thinko in subproject entry sorting
Teach core object handling functions about gitlinks
Teach "fsck" not to follow subproject links
Add "S_IFDIRLNK" file mode infrastructure for git links
Add 'resolve_gitlink_ref()' helper function
Avoid overflowing name buffer in deep directory structures
diff-lib: use ce_mode_from_stat() rather than messing with modes manually

1  2 
builtin-fsck.c
builtin-update-index.c
cache.h
diff-lib.c
refs.c
sha1_file.c
diff --combined builtin-fsck.c
index f480e700e9607068a8d949a520b6760593a522f7,f22de8deaac9b064702518cc51aa61f5d8f2ac92..fcb8ed5af1bc50df19a2f533989aa9673ea166e8
@@@ -253,6 -253,7 +253,7 @@@ static int fsck_tree(struct tree *item
                case S_IFREG | 0644:
                case S_IFLNK:
                case S_IFDIR:
+               case S_IFDIRLNK:
                        break;
                /*
                 * This is nonstandard, but we had a few of these
@@@ -534,7 -535,7 +535,7 @@@ static void get_default_heads(void
         * "show_unreachable" flag.
         */
        if (!default_refs) {
 -              error("No default references");
 +              fprintf(stderr, "notice: No default references\n");
                show_unreachable = 0;
        }
  }
@@@ -554,23 -555,15 +555,23 @@@ static int fsck_head_link(void
  {
        unsigned char sha1[20];
        int flag;
 -      const char *head_points_at = resolve_ref("HEAD", sha1, 1, &flag);
 -
 -      if (!head_points_at || !(flag & REF_ISSYMREF))
 -              return error("HEAD is not a symbolic ref");
 -      if (prefixcmp(head_points_at, "refs/heads/"))
 +      int null_is_error = 0;
 +      const char *head_points_at = resolve_ref("HEAD", sha1, 0, &flag);
 +
 +      if (!head_points_at)
 +              return error("Invalid HEAD");
 +      if (!strcmp(head_points_at, "HEAD"))
 +              /* detached HEAD */
 +              null_is_error = 1;
 +      else if (prefixcmp(head_points_at, "refs/heads/"))
                return error("HEAD points to something strange (%s)",
                             head_points_at);
 -      if (is_null_sha1(sha1))
 -              return error("HEAD: not a valid git pointer");
 +      if (is_null_sha1(sha1)) {
 +              if (null_is_error)
 +                      return error("HEAD: detached HEAD points at nothing");
 +              fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
 +                      head_points_at + 11);
 +      }
        return 0;
  }
  
@@@ -661,7 -654,7 +662,7 @@@ int cmd_fsck(int argc, char **argv, con
                        verify_pack(p, 0);
  
                for (p = packed_git; p; p = p->next) {
 -                      uint32_t i, num = num_packed_objects(p);
 +                      uint32_t i, num = p->num_objects;
                        for (i = 0; i < num; i++)
                                fsck_sha1(nth_packed_object_sha1(p, i));
                }
                int i;
                read_cache();
                for (i = 0; i < active_nr; i++) {
-                       struct blob *blob = lookup_blob(active_cache[i]->sha1);
+                       unsigned int mode;
+                       struct blob *blob;
                        struct object *obj;
+                       mode = ntohl(active_cache[i]->ce_mode);
+                       if (S_ISDIRLNK(mode))
+                               continue;
+                       blob = lookup_blob(active_cache[i]->sha1);
                        if (!blob)
                                continue;
                        obj = &blob->object;
diff --combined builtin-update-index.c
index e5541df28423c4297187f5b9d42c5362fba5de29,c4eaa94998e5459158cd942dd9f13bae9924c1eb..8f9899178b1755ee0acd4a34ee185a4f54fb4219
@@@ -9,6 -9,7 +9,7 @@@
  #include "cache-tree.h"
  #include "tree-walk.h"
  #include "builtin.h"
+ #include "refs.h"
  
  /*
   * Default to not allowing changes to the list of files. The
@@@ -60,78 -61,153 +61,153 @@@ static int mark_valid(const char *path
        return -1;
  }
  
- static int process_file(const char *path)
+ static int remove_one_path(const char *path)
  {
-       int size, namelen, option, status;
-       struct cache_entry *ce;
-       struct stat st;
-       status = lstat(path, &st);
+       if (!allow_remove)
+               return error("%s: does not exist and --remove not passed", path);
+       if (remove_file_from_cache(path))
+               return error("%s: cannot remove from the index", path);
+       return 0;
+ }
  
-       /* We probably want to do this in remove_file_from_cache() and
-        * add_cache_entry() instead...
-        */
-       cache_tree_invalidate_path(active_cache_tree, path);
+ /*
+  * Handle a path that couldn't be lstat'ed. It's either:
+  *  - missing file (ENOENT or ENOTDIR). That's ok if we're
+  *    supposed to be removing it and the removal actually
+  *    succeeds.
+  *  - permission error. That's never ok.
+  */
+ static int process_lstat_error(const char *path, int err)
+ {
+       if (err == ENOENT || err == ENOTDIR)
+               return remove_one_path(path);
+       return error("lstat(\"%s\"): %s", path, strerror(errno));
+ }
  
-       if (status < 0 || S_ISDIR(st.st_mode)) {
-               /* When we used to have "path" and now we want to add
-                * "path/file", we need a way to remove "path" before
-                * being able to add "path/file".  However,
-                * "git-update-index --remove path" would not work.
-                * --force-remove can be used but this is more user
-                * friendly, especially since we can do the opposite
-                * case just fine without --force-remove.
-                */
-               if (status == 0 || (errno == ENOENT || errno == ENOTDIR)) {
-                       if (allow_remove) {
-                               if (remove_file_from_cache(path))
-                                       return error("%s: cannot remove from the index",
-                                                    path);
-                               else
-                                       return 0;
-                       } else if (status < 0) {
-                               return error("%s: does not exist and --remove not passed",
-                                            path);
-                       }
-               }
-               if (0 == status)
-                       return error("%s: is a directory - add files inside instead",
-                                    path);
-               else
-                       return error("lstat(\"%s\"): %s", path,
-                                    strerror(errno));
-       }
+ static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
+ {
+       int option, size = cache_entry_size(len);
+       struct cache_entry *ce = xcalloc(1, size);
  
-       namelen = strlen(path);
-       size = cache_entry_size(namelen);
-       ce = xcalloc(1, size);
-       memcpy(ce->name, path, namelen);
-       ce->ce_flags = htons(namelen);
-       fill_stat_cache_info(ce, &st);
-       if (trust_executable_bit && has_symlinks)
-               ce->ce_mode = create_ce_mode(st.st_mode);
-       else {
-               /* If there is an existing entry, pick the mode bits and type
-                * from it, otherwise assume unexecutable regular file.
-                */
-               struct cache_entry *ent;
-               int pos = cache_name_pos(path, namelen);
-               ent = (0 <= pos) ? active_cache[pos] : NULL;
-               ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
-       }
+       memcpy(ce->name, path, len);
+       ce->ce_flags = htons(len);
+       fill_stat_cache_info(ce, st);
+       ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
  
-       if (index_path(ce->sha1, path, &st, !info_only))
+       if (index_path(ce->sha1, path, st, !info_only))
                return -1;
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
        if (add_cache_entry(ce, option))
-               return error("%s: cannot add to the index - missing --add option?",
-                            path);
+               return error("%s: cannot add to the index - missing --add option?", path);
        return 0;
  }
  
+ /*
+  * Handle a path that was a directory. Four cases:
+  *
+  *  - it's already a gitlink in the index, and we keep it that
+  *    way, and update it if we can (if we cannot find the HEAD,
+  *    we're going to keep it unchanged in the index!)
+  *
+  *  - it's a *file* in the index, in which case it should be
+  *    removed as a file if removal is allowed, since it doesn't
+  *    exist as such any more. If removal isn't allowed, it's
+  *    an error.
+  *
+  *    (NOTE! This is old and arguably fairly strange behaviour.
+  *    We might want to make this an error unconditionally, and
+  *    use "--force-remove" if you actually want to force removal).
+  *
+  *  - it used to exist as a subdirectory (ie multiple files with
+  *    this particular prefix) in the index, in which case it's wrong
+  *    to try to update it as a directory.
+  *
+  *  - it doesn't exist at all in the index, but it is a valid
+  *    git directory, and it should be *added* as a gitlink.
+  */
+ static int process_directory(const char *path, int len, struct stat *st)
+ {
+       unsigned char sha1[20];
+       int pos = cache_name_pos(path, len);
+       /* Exact match: file or existing gitlink */
+       if (pos >= 0) {
+               struct cache_entry *ce = active_cache[pos];
+               if (S_ISDIRLNK(ntohl(ce->ce_mode))) {
+                       /* Do nothing to the index if there is no HEAD! */
+                       if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
+                               return 0;
+                       return add_one_path(ce, path, len, st);
+               }
+               /* Should this be an unconditional error? */
+               return remove_one_path(path);
+       }
+       /* Inexact match: is there perhaps a subdirectory match? */
+       pos = -pos-1;
+       while (pos < active_nr) {
+               struct cache_entry *ce = active_cache[pos++];
+               if (strncmp(ce->name, path, len))
+                       break;
+               if (ce->name[len] > '/')
+                       break;
+               if (ce->name[len] < '/')
+                       continue;
+               /* Subdirectory match - error out */
+               return error("%s: is a directory - add individual files instead", path);
+       }
+       /* No match - should we add it as a gitlink? */
+       if (!resolve_gitlink_ref(path, "HEAD", sha1))
+               return add_one_path(NULL, path, len, st);
+       /* Error out. */
+       return error("%s: is a directory - add files inside instead", path);
+ }
+ /*
+  * Process a regular file
+  */
+ static int process_file(const char *path, int len, struct stat *st)
+ {
+       int pos = cache_name_pos(path, len);
+       struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
+       if (ce && S_ISDIRLNK(ntohl(ce->ce_mode)))
+               return error("%s is already a gitlink, not replacing", path);
+       return add_one_path(ce, path, len, st);
+ }
+ static int process_path(const char *path)
+ {
+       int len;
+       struct stat st;
+       /* We probably want to do this in remove_file_from_cache() and
+        * add_cache_entry() instead...
+        */
+       cache_tree_invalidate_path(active_cache_tree, path);
+       /*
+        * First things first: get the stat information, to decide
+        * what to do about the pathname!
+        */
+       if (lstat(path, &st) < 0)
+               return process_lstat_error(path, errno);
+       len = strlen(path);
+       if (S_ISDIR(st.st_mode))
+               return process_directory(path, len, &st);
+       return process_file(path, len, &st);
+ }
  static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                         const char *path, int stage)
  {
@@@ -210,8 -286,8 +286,8 @@@ static void update_one(const char *path
                report("remove '%s'", path);
                goto free_return;
        }
-       if (process_file(p))
-               die("Unable to process file %s", path);
+       if (process_path(p))
+               die("Unable to process path %s", path);
        report("add '%s'", path);
   free_return:
        if (p < path || p > path + strlen(path))
@@@ -227,7 -303,6 +303,7 @@@ static void read_index_info(int line_te
                char *path_name;
                unsigned char sha1[20];
                unsigned int mode;
 +              unsigned long ul;
                int stage;
  
                /* This reads lines formatted in one of three formats:
                if (buf.eof)
                        break;
  
 -              mode = strtoul(buf.buf, &ptr, 8);
 -              if (ptr == buf.buf || *ptr != ' ')
 +              errno = 0;
 +              ul = strtoul(buf.buf, &ptr, 8);
 +              if (ptr == buf.buf || *ptr != ' '
 +                  || errno || (unsigned int) ul != ul)
                        goto bad_line;
 +              mode = ul;
  
                tab = strchr(ptr, '\t');
                if (!tab || tab - ptr < 41)
@@@ -551,7 -623,7 +627,7 @@@ int cmd_update_index(int argc, const ch
                                if (i+3 >= argc)
                                        die("git-update-index: --cacheinfo <mode> <sha1> <path>");
  
 -                              if ((sscanf(argv[i+1], "%o", &mode) != 1) ||
 +                              if (strtoul_ui(argv[i+1], 8, &mode) ||
                                    get_sha1_hex(argv[i+2], sha1) ||
                                    add_cacheinfo(mode, sha1, argv[i+3], 0))
                                        die("git-update-index: --cacheinfo"
diff --combined cache.h
index ead119609a493a15b7463df8462140729b78957a,1b3d00ee1111200dbdfe0287ab169a67a88be8ec..8747d01c6048ca87fbaafc04ab9bfdab3dc0e57e
+++ b/cache.h
  #define DTYPE(de)     DT_UNKNOWN
  #endif
  
+ /*
+  * A "directory link" is a link to another git directory.
+  *
+  * The value 0160000 is not normally a valid mode, and
+  * also just happens to be S_IFDIR + S_IFLNK
+  *
+  * NOTE! We *really* shouldn't depend on the S_IFxxx macros
+  * always having the same values everywhere. We should use
+  * our internal git values for these things, and then we can
+  * translate that to the OS-specific value. It just so
+  * happens that everybody shares the same bit representation
+  * in the UNIX world (and apparently wider too..)
+  */
+ #define S_IFDIRLNK    0160000
+ #define S_ISDIRLNK(m) (((m) & S_IFMT) == S_IFDIRLNK)
  /*
   * Intensive research over the course of many years has shown that
   * port 9418 is totally unused by anything else. Or
@@@ -104,6 -120,8 +120,8 @@@ static inline unsigned int create_ce_mo
  {
        if (S_ISLNK(mode))
                return htonl(S_IFLNK);
+       if (S_ISDIR(mode) || S_ISDIRLNK(mode))
+               return htonl(S_IFDIRLNK);
        return htonl(S_IFREG | ce_permissions(mode));
  }
  static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
  }
  #define canon_mode(mode) \
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
-       S_ISLNK(mode) ? S_IFLNK : S_IFDIR)
+       S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFDIRLNK)
  
  #define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
  
@@@ -217,7 -235,7 +235,7 @@@ extern int commit_locked_index(struct l
  extern void set_alternate_index_output(const char *);
  
  extern void rollback_lock_file(struct lock_file *);
 -extern int delete_ref(const char *, unsigned char *sha1);
 +extern int delete_ref(const char *, const unsigned char *sha1);
  
  /* Environment bits from configuration mechanism */
  extern int use_legacy_headers;
@@@ -376,12 -394,11 +394,12 @@@ struct pack_window 
  extern struct packed_git {
        struct packed_git *next;
        struct pack_window *windows;
 -      const void *index_data;
 -      off_t index_size;
        off_t pack_size;
 -      time_t mtime;
 +      const void *index_data;
 +      size_t index_size;
 +      uint32_t num_objects;
        int index_version;
 +      time_t mtime;
        int pack_fd;
        int pack_local;
        unsigned char sha1[20];
@@@ -432,11 -449,11 +450,11 @@@ extern void pack_report(void)
  extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
  extern void unuse_pack(struct pack_window **);
  extern struct packed_git *add_packed_git(const char *, int, int);
 -extern uint32_t num_packed_objects(const struct packed_git *p);
  extern const unsigned char *nth_packed_object_sha1(const struct packed_git *, uint32_t);
  extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
  extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
  extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 +extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
  extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
  
  /* Dumb servers support */
@@@ -473,8 -490,8 +491,8 @@@ extern int pager_in_use
  extern int pager_use_color;
  
  /* base85 */
 -int decode_85(char *dst, char *line, int linelen);
 -void encode_85(char *buf, unsigned char *data, int bytes);
 +int decode_85(char *dst, const char *line, int linelen);
 +void encode_85(char *buf, const unsigned char *data, int bytes);
  
  /* alloc.c */
  struct blob;
diff --combined diff-lib.c
index 7531e20c784c44c0b5d3ecb2057638874a09ce6c,c6d127346ab38b3b73285f61d2dc6874ab1dc7ac..07f4e8106a51384d2236b182438472884c300da6
@@@ -142,34 -142,18 +142,34 @@@ static int queue_diff(struct diff_optio
        }
  }
  
 +/*
 + * Does the path name a blob in the working tree, or a directory
 + * in the working tree?
 + */
  static int is_in_index(const char *path)
  {
 -      int len = strlen(path);
 -      int pos = cache_name_pos(path, len);
 -      char c;
 -
 -      if (pos < 0)
 -              return 0;
 -      if (strncmp(active_cache[pos]->name, path, len))
 -              return 0;
 -      c = active_cache[pos]->name[len];
 -      return c == '\0' || c == '/';
 +      int len, pos;
 +      struct cache_entry *ce;
 +
 +      len = strlen(path);
 +      while (path[len-1] == '/')
 +              len--;
 +      if (!len)
 +              return 1; /* "." */
 +      pos = cache_name_pos(path, len);
 +      if (0 <= pos)
 +              return 1;
 +      pos = -1 - pos;
 +      while (pos < active_nr) {
 +              ce = active_cache[pos++];
 +              if (ce_namelen(ce) <= len ||
 +                  strncmp(ce->name, path, len) ||
 +                  (ce->name[len] > '/'))
 +                      break; /* path cannot be a prefix */
 +              if (ce->name[len] == '/')
 +                      return 1;
 +      }
 +      return 0;
  }
  
  static int handle_diff_files_args(struct rev_info *revs,
@@@ -373,7 -357,7 +373,7 @@@ int run_diff_files(struct rev_info *rev
                                        continue;
                        }
                        else
-                               dpath->mode = canon_mode(st.st_mode);
+                               dpath->mode = ntohl(ce_mode_from_stat(ce, st.st_mode));
  
                        while (i < entries) {
                                struct cache_entry *nce = active_cache[i];
                                        int mode = ntohl(nce->ce_mode);
                                        num_compare_stages++;
                                        hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
-                                       dpath->parent[stage-2].mode =
-                                               canon_mode(mode);
+                                       dpath->parent[stage-2].mode = ntohl(ce_mode_from_stat(nce, mode));
                                        dpath->parent[stage-2].status =
                                                DIFF_STATUS_MODIFIED;
                                }
                if (!changed && !revs->diffopt.find_copies_harder)
                        continue;
                oldmode = ntohl(ce->ce_mode);
-               newmode = canon_mode(st.st_mode);
-               if (!trust_executable_bit &&
-                   S_ISREG(newmode) && S_ISREG(oldmode) &&
-                   ((newmode ^ oldmode) == 0111))
-                       newmode = oldmode;
-               else if (!has_symlinks &&
-                   S_ISREG(newmode) && S_ISLNK(oldmode))
-                       newmode = oldmode;
+               newmode = ntohl(ce_mode_from_stat(ce, st.st_mode));
                diff_change(&revs->diffopt, oldmode, newmode,
                            ce->sha1, (changed ? null_sha1 : ce->sha1),
                            ce->name, NULL);
diff --combined refs.c
index f9b88020038487eb5dbc76bbfefd7fba15fc87fd,11a67a8c86d5fa1cfe2468568c60f4391300f2ee..89876bff871d007a6675f5790ce8cb34fe21fb39
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -47,7 -47,22 +47,7 @@@ static struct ref_list *add_ref(const c
                                struct ref_list **new_entry)
  {
        int len;
 -      struct ref_list **p = &list, *entry;
 -
 -      /* Find the place to insert the ref into.. */
 -      while ((entry = *p) != NULL) {
 -              int cmp = strcmp(entry->name, name);
 -              if (cmp > 0)
 -                      break;
 -
 -              /* Same as existing entry? */
 -              if (!cmp) {
 -                      if (new_entry)
 -                              *new_entry = entry;
 -                      return list;
 -              }
 -              p = &entry->next;
 -      }
 +      struct ref_list *entry;
  
        /* Allocate it and add it in.. */
        len = strlen(name) + 1;
        hashclr(entry->peeled);
        memcpy(entry->name, name, len);
        entry->flag = flag;
 -      entry->next = *p;
 -      *p = entry;
 +      entry->next = list;
        if (new_entry)
                *new_entry = entry;
 -      return list;
 +      return entry;
 +}
 +
 +/* merge sort the ref list */
 +static struct ref_list *sort_ref_list(struct ref_list *list)
 +{
 +      int psize, qsize, last_merge_count, cmp;
 +      struct ref_list *p, *q, *l, *e;
 +      struct ref_list *new_list = list;
 +      int k = 1;
 +      int merge_count = 0;
 +
 +      if (!list)
 +              return list;
 +
 +      do {
 +              last_merge_count = merge_count;
 +              merge_count = 0;
 +
 +              psize = 0;
 +
 +              p = new_list;
 +              q = new_list;
 +              new_list = NULL;
 +              l = NULL;
 +
 +              while (p) {
 +                      merge_count++;
 +
 +                      while (psize < k && q->next) {
 +                              q = q->next;
 +                              psize++;
 +                      }
 +                      qsize = k;
 +
 +                      while ((psize > 0) || (qsize > 0 && q)) {
 +                              if (qsize == 0 || !q) {
 +                                      e = p;
 +                                      p = p->next;
 +                                      psize--;
 +                              } else if (psize == 0) {
 +                                      e = q;
 +                                      q = q->next;
 +                                      qsize--;
 +                              } else {
 +                                      cmp = strcmp(q->name, p->name);
 +                                      if (cmp < 0) {
 +                                              e = q;
 +                                              q = q->next;
 +                                              qsize--;
 +                                      } else if (cmp > 0) {
 +                                              e = p;
 +                                              p = p->next;
 +                                              psize--;
 +                                      } else {
 +                                              if (hashcmp(q->sha1, p->sha1))
 +                                                      die("Duplicated ref, and SHA1s don't match: %s",
 +                                                          q->name);
 +                                              warning("Duplicated ref: %s", q->name);
 +                                              e = q;
 +                                              q = q->next;
 +                                              qsize--;
 +                                              free(e);
 +                                              e = p;
 +                                              p = p->next;
 +                                              psize--;
 +                                      }
 +                              }
 +
 +                              e->next = NULL;
 +
 +                              if (l)
 +                                      l->next = e;
 +                              if (!new_list)
 +                                      new_list = e;
 +                              l = e;
 +                      }
 +
 +                      p = q;
 +              };
 +
 +              k = k * 2;
 +      } while ((last_merge_count != merge_count) || (last_merge_count != 1));
 +
 +      return new_list;
  }
  
  /*
@@@ -210,7 -142,7 +210,7 @@@ static void read_packed_refs(FILE *f, s
                    !get_sha1_hex(refline + 1, sha1))
                        hashcpy(last->peeled, sha1);
        }
 -      cached_refs->packed = list;
 +      cached_refs->packed = sort_ref_list(list);
  }
  
  static struct ref_list *get_packed_refs(void)
@@@ -269,7 -201,7 +269,7 @@@ static struct ref_list *get_ref_dir(con
                free(ref);
                closedir(dir);
        }
 -      return list;
 +      return sort_ref_list(list);
  }
  
  static struct ref_list *get_loose_refs(void)
  
  /* We allow "recursive" symbolic refs. Only within reason, though */
  #define MAXDEPTH 5
+ #define MAXREFLEN (1024)
+ static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result)
+ {
+       FILE *f;
+       struct cached_refs refs;
+       struct ref_list *ref;
+       int retval;
+       strcpy(name + pathlen, "packed-refs");
+       f = fopen(name, "r");
+       if (!f)
+               return -1;
+       read_packed_refs(f, &refs);
+       fclose(f);
+       ref = refs.packed;
+       retval = -1;
+       while (ref) {
+               if (!strcmp(ref->name, refname)) {
+                       retval = 0;
+                       memcpy(result, ref->sha1, 20);
+                       break;
+               }
+               ref = ref->next;
+       }
+       free_ref_list(refs.packed);
+       return retval;
+ }
+ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *refname, unsigned char *result, int recursion)
+ {
+       int fd, len = strlen(refname);
+       char buffer[128], *p;
+       if (recursion > MAXDEPTH || len > MAXREFLEN)
+               return -1;
+       memcpy(name + pathlen, refname, len+1);
+       fd = open(name, O_RDONLY);
+       if (fd < 0)
+               return resolve_gitlink_packed_ref(name, pathlen, refname, result);
+       len = read(fd, buffer, sizeof(buffer)-1);
+       close(fd);
+       if (len < 0)
+               return -1;
+       while (len && isspace(buffer[len-1]))
+               len--;
+       buffer[len] = 0;
+       /* Was it a detached head or an old-fashioned symlink? */
+       if (!get_sha1_hex(buffer, result))
+               return 0;
+       /* Symref? */
+       if (strncmp(buffer, "ref:", 4))
+               return -1;
+       p = buffer + 4;
+       while (isspace(*p))
+               p++;
+       return resolve_gitlink_ref_recursive(name, pathlen, p, result, recursion+1);
+ }
+ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *result)
+ {
+       int len = strlen(path), retval;
+       char *gitdir;
+       while (len && path[len-1] == '/')
+               len--;
+       if (!len)
+               return -1;
+       gitdir = xmalloc(len + MAXREFLEN + 8);
+       memcpy(gitdir, path, len);
+       memcpy(gitdir + len, "/.git/", 7);
+       retval = resolve_gitlink_ref_recursive(gitdir, len+6, refname, result, 0);
+       free(gitdir);
+       return retval;
+ }
  
  const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
  {
@@@ -773,7 -785,7 +853,7 @@@ static int repack_without_ref(const cha
        return commit_lock_file(&packlock);
  }
  
 -int delete_ref(const char *refname, unsigned char *sha1)
 +int delete_ref(const char *refname, const unsigned char *sha1)
  {
        struct ref_lock *lock;
        int err, i, ret = 0, flag = 0;
diff --combined sha1_file.c
index 5dac4666b6c38b6fd32799faeeb86715cbd6618e,ab915faa6b5a7dc0967f8890f706ba81c1c35f44..0c0fcc597d2a1b91a006745c44d3234d1f198bd7
@@@ -13,6 -13,7 +13,7 @@@
  #include "commit.h"
  #include "tag.h"
  #include "tree.h"
+ #include "refs.h"
  
  #ifndef O_NOATIME
  #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@@ -437,7 -438,7 +438,7 @@@ static int check_packed_git_idx(const c
        void *idx_map;
        struct pack_idx_header *hdr;
        size_t idx_size;
 -      uint32_t nr, i, *index;
 +      uint32_t version, nr, i, *index;
        int fd = open(path, O_RDONLY);
        struct stat st;
  
        idx_map = xmmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
  
 -      /* a future index format would start with this, as older git
 -       * binaries would fail the non-monotonic index check below.
 -       * give a nicer warning to the user if we can.
 -       */
        hdr = idx_map;
        if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) {
 -              munmap(idx_map, idx_size);
 -              return error("index file %s is a newer version"
 -                      " and is not supported by this binary"
 -                      " (try upgrading GIT to a newer version)",
 -                      path);
 -      }
 +              version = ntohl(hdr->idx_version);
 +              if (version < 2 || version > 2) {
 +                      munmap(idx_map, idx_size);
 +                      return error("index file %s is version %d"
 +                                   " and is not supported by this binary"
 +                                   " (try upgrading GIT to a newer version)",
 +                                   path, version);
 +              }
 +      } else
 +              version = 1;
  
        nr = 0;
        index = idx_map;
 +      if (version > 1)
 +              index += 2;  /* skip index header */
        for (i = 0; i < 256; i++) {
                uint32_t n = ntohl(index[i]);
                if (n < nr) {
                nr = n;
        }
  
 -      /*
 -       * Total size:
 -       *  - 256 index entries 4 bytes each
 -       *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
 -       *  - 20-byte SHA1 of the packfile
 -       *  - 20-byte SHA1 file checksum
 -       */
 -      if (idx_size != 4*256 + nr * 24 + 20 + 20) {
 -              munmap(idx_map, idx_size);
 -              return error("wrong index file size in %s", path);
 +      if (version == 1) {
 +              /*
 +               * Total size:
 +               *  - 256 index entries 4 bytes each
 +               *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
 +               *  - 20-byte SHA1 of the packfile
 +               *  - 20-byte SHA1 file checksum
 +               */
 +              if (idx_size != 4*256 + nr * 24 + 20 + 20) {
 +                      munmap(idx_map, idx_size);
 +                      return error("wrong index file size in %s", path);
 +              }
 +      } else if (version == 2) {
 +              /*
 +               * Minimum size:
 +               *  - 8 bytes of header
 +               *  - 256 index entries 4 bytes each
 +               *  - 20-byte sha1 entry * nr
 +               *  - 4-byte crc entry * nr
 +               *  - 4-byte offset entry * nr
 +               *  - 20-byte SHA1 of the packfile
 +               *  - 20-byte SHA1 file checksum
 +               * And after the 4-byte offset table might be a
 +               * variable sized table containing 8-byte entries
 +               * for offsets larger than 2^31.
 +               */
 +              unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20;
 +              if (idx_size < min_size || idx_size > min_size + (nr - 1)*8) {
 +                      munmap(idx_map, idx_size);
 +                      return error("wrong index file size in %s", path);
 +              }
 +              if (idx_size != min_size) {
 +                      /* make sure we can deal with large pack offsets */
 +                      off_t x = 0x7fffffffUL, y = 0xffffffffUL;
 +                      if (x > (x + 1) || y > (y + 1)) {
 +                              munmap(idx_map, idx_size);
 +                              return error("pack too large for current definition of off_t in %s", path);
 +                      }
 +              }
        }
  
 -      p->index_version = 1;
 +      p->index_version = version;
        p->index_data = idx_map;
        p->index_size = idx_size;
 +      p->num_objects = nr;
        return 0;
  }
  
@@@ -637,11 -606,11 +638,11 @@@ static int open_packed_git_1(struct pac
                        p->pack_name, ntohl(hdr.hdr_version));
  
        /* Verify the pack matches its index. */
 -      if (num_packed_objects(p) != ntohl(hdr.hdr_entries))
 +      if (p->num_objects != ntohl(hdr.hdr_entries))
                return error("packfile %s claims to have %u objects"
 -                      " while index size indicates %u objects",
 -                      p->pack_name, ntohl(hdr.hdr_entries),
 -                      num_packed_objects(p));
 +                           " while index indicates %u objects",
 +                           p->pack_name, ntohl(hdr.hdr_entries),
 +                           p->num_objects);
        if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1)
                return error("end of packfile %s is unavailable", p->pack_name);
        if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1))
@@@ -1160,43 -1129,6 +1161,43 @@@ static void *unpack_sha1_file(void *map
        return unpack_sha1_rest(&stream, hdr, *size, sha1);
  }
  
 +unsigned long get_size_from_delta(struct packed_git *p,
 +                                struct pack_window **w_curs,
 +                                off_t curpos)
 +{
 +      const unsigned char *data;
 +      unsigned char delta_head[20], *in;
 +      z_stream stream;
 +      int st;
 +
 +      memset(&stream, 0, sizeof(stream));
 +      stream.next_out = delta_head;
 +      stream.avail_out = sizeof(delta_head);
 +
 +      inflateInit(&stream);
 +      do {
 +              in = use_pack(p, w_curs, curpos, &stream.avail_in);
 +              stream.next_in = in;
 +              st = inflate(&stream, Z_FINISH);
 +              curpos += stream.next_in - in;
 +      } while ((st == Z_OK || st == Z_BUF_ERROR) &&
 +               stream.total_out < sizeof(delta_head));
 +      inflateEnd(&stream);
 +      if ((st != Z_STREAM_END) && stream.total_out != sizeof(delta_head))
 +              die("delta data unpack-initial failed");
 +
 +      /* Examine the initial part of the delta to figure out
 +       * the result size.
 +       */
 +      data = delta_head;
 +
 +      /* ignore base size */
 +      get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
 +
 +      /* Read the result size */
 +      return get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
 +}
 +
  static off_t get_delta_base(struct packed_git *p,
                                    struct pack_window **w_curs,
                                    off_t *curpos,
                base_offset = c & 127;
                while (c & 128) {
                        base_offset += 1;
 -                      if (!base_offset || base_offset & ~(~0UL >> 7))
 +                      if (!base_offset || MSB(base_offset, 7))
                                die("offset value overflow for delta base object");
                        c = base_info[used++];
                        base_offset = (base_offset << 7) + (c & 127);
@@@ -1260,8 -1192,40 +1261,8 @@@ static int packed_delta_info(struct pac
         * based on a base with a wrong size.  This saves tons of
         * inflate() calls.
         */
 -      if (sizep) {
 -              const unsigned char *data;
 -              unsigned char delta_head[20], *in;
 -              z_stream stream;
 -              int st;
 -
 -              memset(&stream, 0, sizeof(stream));
 -              stream.next_out = delta_head;
 -              stream.avail_out = sizeof(delta_head);
 -
 -              inflateInit(&stream);
 -              do {
 -                      in = use_pack(p, w_curs, curpos, &stream.avail_in);
 -                      stream.next_in = in;
 -                      st = inflate(&stream, Z_FINISH);
 -                      curpos += stream.next_in - in;
 -              } while ((st == Z_OK || st == Z_BUF_ERROR)
 -                      && stream.total_out < sizeof(delta_head));
 -              inflateEnd(&stream);
 -              if ((st != Z_STREAM_END) &&
 -                  stream.total_out != sizeof(delta_head))
 -                      die("delta data unpack-initial failed");
 -
 -              /* Examine the initial part of the delta to figure out
 -               * the result size.
 -               */
 -              data = delta_head;
 -
 -              /* ignore base size */
 -              get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
 -
 -              /* Read the result size */
 -              *sizep = get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
 -      }
 +      if (sizep)
 +              *sizep = get_size_from_delta(p, w_curs, curpos);
  
        return type;
  }
@@@ -1563,60 -1527,37 +1564,60 @@@ void *unpack_entry(struct packed_git *p
        return data;
  }
  
 -uint32_t num_packed_objects(const struct packed_git *p)
 +const unsigned char *nth_packed_object_sha1(const struct packed_git *p,
 +                                          uint32_t n)
  {
 -      /* See check_packed_git_idx() */
 -      return (uint32_t)((p->index_size - 20 - 20 - 4*256) / 24);
 +      const unsigned char *index = p->index_data;
 +      if (n >= p->num_objects)
 +              return NULL;
 +      index += 4 * 256;
 +      if (p->index_version == 1) {
 +              return index + 24 * n + 4;
 +      } else {
 +              index += 8;
 +              return index + 20 * n;
 +      }
  }
  
 -const unsigned char *nth_packed_object_sha1(const struct packed_git *p,
 -                                          uint32_t n)
 +static off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n)
  {
        const unsigned char *index = p->index_data;
        index += 4 * 256;
 -      if (num_packed_objects(p) <= n)
 -              return NULL;
 -      return index + 24 * n + 4;
 +      if (p->index_version == 1) {
 +              return ntohl(*((uint32_t *)(index + 24 * n)));
 +      } else {
 +              uint32_t off;
 +              index += 8 + p->num_objects * (20 + 4);
 +              off = ntohl(*((uint32_t *)(index + 4 * n)));
 +              if (!(off & 0x80000000))
 +                      return off;
 +              index += p->num_objects * 4 + (off & 0x7fffffff) * 8;
 +              return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) |
 +                                 ntohl(*((uint32_t *)(index + 4)));
 +      }
  }
  
  off_t find_pack_entry_one(const unsigned char *sha1,
                                  struct packed_git *p)
  {
        const uint32_t *level1_ofs = p->index_data;
 -      int hi = ntohl(level1_ofs[*sha1]);
 -      int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
        const unsigned char *index = p->index_data;
 +      unsigned hi, lo;
  
 +      if (p->index_version > 1) {
 +              level1_ofs += 2;
 +              index += 8;
 +      }
        index += 4 * 256;
 +      hi = ntohl(level1_ofs[*sha1]);
 +      lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
  
        do {
 -              int mi = (lo + hi) / 2;
 -              int cmp = hashcmp(index + 24 * mi + 4, sha1);
 +              unsigned mi = (lo + hi) / 2;
 +              unsigned x = (p->index_version > 1) ? (mi * 20) : (mi * 24 + 4);
 +              int cmp = hashcmp(index + x, sha1);
                if (!cmp)
 -                      return ntohl(*((uint32_t *)((char *)index + (24 * mi))));
 +                      return nth_packed_object_offset(p, mi);
                if (cmp > 0)
                        hi = mi;
                else
@@@ -2392,6 -2333,8 +2393,8 @@@ int index_path(unsigned char *sha1, con
                                     path);
                free(target);
                break;
+       case S_IFDIR:
+               return resolve_gitlink_ref(path, "HEAD", sha1);
        default:
                return error("%s: unsupported file type", path);
        }