Merge branch 'jc/attr'
authorJunio C Hamano <junkio@cox.net>
Sun, 22 Apr 2007 00:38:00 +0000 (17:38 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 22 Apr 2007 00:38:00 +0000 (17:38 -0700)
* 'jc/attr': (28 commits)
  lockfile: record the primary process.
  convert.c: restructure the attribute checking part.
  Fix bogus linked-list management for user defined merge drivers.
  Simplify calling of CR/LF conversion routines
  Document gitattributes(5)
  Update 'crlf' attribute semantics.
  Documentation: support manual section (5) - file formats.
  Simplify code to find recursive merge driver.
  Counto-fix in merge-recursive
  Fix funny types used in attribute value representation
  Allow low-level driver to specify different behaviour during internal merge.
  Custom low-level merge driver: change the configuration scheme.
  Allow the default low-level merge driver to be configured.
  Custom low-level merge driver support.
  Add a demonstration/test of customized merge.
  Allow specifying specialized merge-backend per path.
  merge-recursive: separate out xdl_merge() interface.
  Allow more than true/false to attributes.
  Document git-check-attr
  Change attribute negation marker from '!' to '-'.
  ...

1  2 
.gitignore
Documentation/config.txt
Makefile
builtin-apply.c
cache.h
diff.c
entry.c
merge-recursive.c
sha1_file.c

diff --combined .gitignore
@@@ -16,6 -16,7 +16,7 @@@ git-blam
  git-branch
  git-bundle
  git-cat-file
+ git-check-attr
  git-check-ref-format
  git-checkout
  git-checkout-index
@@@ -149,7 -150,6 +150,7 @@@ test-chmtim
  test-date
  test-delta
  test-dump-cache-tree
 +test-genrandom
  test-match-trees
  common-cmds.h
  *.tar.gz
diff --combined Documentation/config.txt
@@@ -423,34 -423,8 +423,34 @@@ gitcvs.allbinary:
        causes the client to treat all files as binary files which suppresses
        any newline munging it otherwise might do. A work-around for the
        fact that there is no way yet to set single files to mode '-kb'.
 +
 +gitcvs.dbname::
 +      Database used by git-cvsserver to cache revision information
 +      derived from the git repository. The exact meaning depends on the
 +      used database driver, for SQLite (which is the default driver) this
 +      is a filename. Supports variable substitution (see
 +      gitlink:git-cvsserver[1] for details). May not contain semicolons (`;`).
 +      Default: '%Ggitcvs.%m.sqlite'
 +
 +gitcvs.dbdriver::
 +      Used Perl DBI driver. You can specify any available driver
 +        for this here, but it might not work. git-cvsserver is tested
 +      with 'DBD::SQLite', reported to work with 'DBD::Pg', and
 +      reported *not* to work with 'DBD::mysql'. Experimental feature.
 +      May not contain double colons (`:`). Default: 'SQLite'.
        See gitlink:git-cvsserver[1].
  
 +gitcvs.dbuser, gitcvs.dbpass::
 +      Database user and password. Only useful if setting 'gitcvs.dbdriver',
 +      since SQLite has no concept of database users and/or passwords.
 +      'gitcvs.dbuser' supports variable substitution (see
 +      gitlink:git-cvsserver[1] for details).
 +
 +All gitcvs variables except for 'gitcvs.allbinary' can also specifed
 +as 'gitcvs.<access_method>.<varname>' (where 'access_method' is one
 +of "ext" and "pserver") to make them apply only for the given access
 +method.
 +
  http.sslVerify::
        Whether to verify the SSL certificate when fetching or pushing
        over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
@@@ -525,6 -499,19 +525,19 @@@ merge.verbosity:
        conflicts, 2 outputs conflicts and file changes.  Level 5 and
        above outputs debugging information.  The default is level 2.
  
+ merge.<driver>.name::
+       Defines a human readable name for a custom low-level
+       merge driver.  See gitlink:gitattributes[5] for details.
+ merge.<driver>.driver::
+       Defines the command that implements a custom low-level
+       merge driver.  See gitlink:gitattributes[5] for details.
+ merge.<driver>.recursive::
+       Names a low-level merge driver to be used when
+       performing an internal merge between common ancestors.
+       See gitlink:gitattributes[5] for details.
  pack.window::
        The size of the window used by gitlink:git-pack-objects[1] when no
        window size is given on the command line. Defaults to 10.
diff --combined Makefile
+++ b/Makefile
@@@ -283,7 -283,7 +283,7 @@@ LIB_H = 
        diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
        tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
-       utf8.h reflog-walk.h patch-ids.h decorate.h
 -      utf8.h reflog-walk.h patch-ids.h attr.h
++      utf8.h reflog-walk.h patch-ids.h attr.h decorate.h
  
  DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@@ -305,7 -305,7 +305,7 @@@ LIB_OBJS = 
        write_or_die.o trace.o list-objects.o grep.o match-trees.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
-       convert.o decorate.o
 -      convert.o attr.o
++      convert.o attr.o decorate.o
  
  BUILTIN_OBJS = \
        builtin-add.o \
        builtin-branch.o \
        builtin-bundle.o \
        builtin-cat-file.o \
+       builtin-check-attr.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
        builtin-commit-tree.o \
@@@ -933,7 -934,7 +934,7 @@@ endi
  
  export NO_SVN_TESTS
  
 -test: all test-chmtime$X
 +test: all test-chmtime$X test-genrandom$X
        $(MAKE) -C t/ all
  
  test-date$X: test-date.c date.o ctype.o
@@@ -954,9 -955,6 +955,9 @@@ test-match-trees$X: test-match-trees.o 
  test-chmtime$X: test-chmtime.c
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
  
 +test-genrandom$X: test-genrandom.c
 +      $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
 +
  check-sha1:: test-sha1$X
        ./test-sha1.sh
  
@@@ -1032,9 -1030,10 +1033,10 @@@ dist-doc
        gzip -n -9 -f $(htmldocs).tar
        :
        rm -fr .doc-tmp-dir
-       mkdir .doc-tmp-dir .doc-tmp-dir/man1 .doc-tmp-dir/man7
+       mkdir -p .doc-tmp-dir/man1 .doc-tmp-dir/man5 .doc-tmp-dir/man7
        $(MAKE) -C Documentation DESTDIR=./ \
                man1dir=../.doc-tmp-dir/man1 \
+               man5dir=../.doc-tmp-dir/man5 \
                man7dir=../.doc-tmp-dir/man7 \
                install
        cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
  
  clean:
        rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
 -              test-chmtime$X $(LIB_FILE) $(XDIFF_LIB)
 +              test-chmtime$X test-genrandom$X $(LIB_FILE) $(XDIFF_LIB)
        rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X
        rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
        rm -rf autom4te.cache
diff --combined builtin-apply.c
@@@ -1475,8 -1475,8 +1475,8 @@@ static int read_old_data(struct stat *s
                }
                close(fd);
                nsize = got;
-               nbuf = buf;
-               if (convert_to_git(path, &nbuf, &nsize)) {
+               nbuf = convert_to_git(path, buf, &nsize);
+               if (nbuf) {
                        free(buf);
                        *buf_p = nbuf;
                        *alloc_p = nsize;
@@@ -2355,9 -2355,8 +2355,8 @@@ static void add_index_file(const char *
  
  static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
  {
-       int fd, converted;
+       int fd;
        char *nbuf;
-       unsigned long nsize;
  
        if (has_symlinks && S_ISLNK(mode))
                /* Although buf:size is counted string, it also is NUL
        if (fd < 0)
                return -1;
  
-       nsize = size;
-       nbuf = (char *) buf;
-       converted = convert_to_working_tree(path, &nbuf, &nsize);
-       if (converted) {
+       nbuf = convert_to_working_tree(path, buf, &size);
+       if (nbuf)
                buf = nbuf;
-               size = nsize;
-       }
        while (size) {
                int written = xwrite(fd, buf, size);
                if (written < 0)
        }
        if (close(fd) < 0)
                die("closing file %s: %s", path, strerror(errno));
-       if (converted)
+       if (nbuf)
                free(nbuf);
        return 0;
  }
@@@ -2416,7 -2412,8 +2412,7 @@@ static void create_one_file(char *path
                 * used to be.
                 */
                struct stat st;
 -              errno = 0;
 -              if (!lstat(path, &st) && S_ISDIR(st.st_mode) && !rmdir(path))
 +              if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path)))
                        errno = EEXIST;
        }
  
diff --combined cache.h
+++ 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
@@@ -120,8 -104,6 +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)
  
@@@ -169,6 -151,9 +169,9 @@@ enum object_type 
  #define CONFIG_ENVIRONMENT "GIT_CONFIG"
  #define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
  #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
+ #define GITATTRIBUTES_FILE ".gitattributes"
+ #define INFOATTRIBUTES_FILE "info/attributes"
+ #define ATTRIBUTE_MACRO_PREFIX "[attr]"
  
  extern int is_bare_repository_cfg;
  extern int is_bare_repository(void);
@@@ -224,6 -209,7 +227,7 @@@ extern int refresh_cache(unsigned int f
  
  struct lock_file {
        struct lock_file *next;
+       pid_t owner;
        char on_list;
        char filename[PATH_MAX];
  };
@@@ -235,7 -221,7 +239,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;
@@@ -394,12 -380,11 +398,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];
@@@ -450,11 -435,11 +454,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 */
@@@ -512,8 -497,8 +516,8 @@@ extern void trace_printf(const char *fo
  extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
  
  /* convert.c */
- extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep);
- extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep);
+ extern char *convert_to_git(const char *path, const char *src, unsigned long *sizep);
+ extern char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep);
  
  /* match-trees.c */
  void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
diff --combined diff.c
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -8,6 -8,7 +8,7 @@@
  #include "delta.h"
  #include "xdiff-interface.h"
  #include "color.h"
+ #include "attr.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -1051,13 -1052,44 +1052,44 @@@ static void emit_binary_diff(mmfile_t *
        emit_binary_diff_body(two, one);
  }
  
+ static void setup_diff_attr_check(struct git_attr_check *check)
+ {
+       static struct git_attr *attr_diff;
+       if (!attr_diff)
+               attr_diff = git_attr("diff", 4);
+       check->attr = attr_diff;
+ }
  #define FIRST_FEW_BYTES 8000
- static int mmfile_is_binary(mmfile_t *mf)
+ static int file_is_binary(struct diff_filespec *one)
  {
-       long sz = mf->size;
+       unsigned long sz;
+       struct git_attr_check attr_diff_check;
+       setup_diff_attr_check(&attr_diff_check);
+       if (!git_checkattr(one->path, 1, &attr_diff_check)) {
+               const char *value = attr_diff_check.value;
+               if (ATTR_TRUE(value))
+                       return 0;
+               else if (ATTR_FALSE(value))
+                       return 1;
+               else if (ATTR_UNSET(value))
+                       ;
+               else
+                       die("unknown value %s given to 'diff' attribute",
+                           value);
+       }
+       if (!one->data) {
+               if (!DIFF_FILE_VALID(one))
+                       return 0;
+               diff_populate_filespec(one, 0);
+       }
+       sz = one->size;
        if (FIRST_FEW_BYTES < sz)
                sz = FIRST_FEW_BYTES;
-       return !!memchr(mf->ptr, 0, sz);
+       return !!memchr(one->data, 0, sz);
  }
  
  static void builtin_diff(const char *name_a,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
-       if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) {
+       if (!o->text && (file_is_binary(one) || file_is_binary(two))) {
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
@@@ -1190,7 -1222,7 +1222,7 @@@ static void builtin_diffstat(const cha
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
-       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) {
+       if (file_is_binary(one) || file_is_binary(two)) {
                data->is_binary = 1;
                data->added = mf2.size;
                data->deleted = mf1.size;
@@@ -1228,7 -1260,7 +1260,7 @@@ static void builtin_checkdiff(const cha
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
-       if (mmfile_is_binary(&mf2))
+       if (file_is_binary(two))
                return;
        else {
                /* Crazy xdl interfaces.. */
@@@ -1397,22 -1429,6 +1429,22 @@@ static int populate_from_stdin(struct d
        return 0;
  }
  
 +static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 +{
 +      int len;
 +      char *data = xmalloc(100);
 +      len = snprintf(data, 100,
 +              "Subproject commit %s\n", sha1_to_hex(s->sha1));
 +      s->data = data;
 +      s->size = len;
 +      s->should_free = 1;
 +      if (size_only) {
 +              s->data = NULL;
 +              free(data);
 +      }
 +      return 0;
 +}
 +
  /*
   * While doing rename detection and pickaxe operation, we may need to
   * grab the data for the blob (or file) for our own in-core comparison.
@@@ -1431,10 -1447,6 +1463,10 @@@ int diff_populate_filespec(struct diff_
  
        if (s->data)
                return err;
 +
 +      if (S_ISDIRLNK(s->mode))
 +              return diff_populate_gitlink(s, size_only);
 +
        if (!s->sha1_valid ||
            reuse_worktree_file(s->path, s->sha1, 0)) {
                struct stat st;
                /*
                 * Convert from working tree format to canonical git format
                 */
-               buf = s->data;
                size = s->size;
-               if (convert_to_git(s->path, &buf, &size)) {
+               buf = convert_to_git(s->path, s->data, &size);
+               if (buf) {
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
                        s->data = buf;
@@@ -1825,8 -1837,8 +1857,8 @@@ static void run_diff(struct diff_filepa
  
                if (o->binary) {
                        mmfile_t mf;
-                       if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
-                           (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
+                       if ((!fill_mmfile(&mf, one) && file_is_binary(one)) ||
+                           (!fill_mmfile(&mf, two) && file_is_binary(two)))
                                abbrev = 40;
                }
                len += snprintf(msg + len, sizeof(msg) - len,
@@@ -2721,7 -2733,7 +2753,7 @@@ static int diff_get_patch_id(struct dif
                        return error("unable to read files to diff");
  
                /* Maybe hash p->two? into the patch id? */
-               if (mmfile_is_binary(&mf2))
+               if (file_is_binary(p->two))
                        continue;
  
                len1 = remove_space(p->one->path, strlen(p->one->path));
diff --combined entry.c
+++ b/entry.c
@@@ -62,33 -62,25 +62,33 @@@ static int create_file(const char *path
        return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
  }
  
 +static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned long *size)
 +{
 +      enum object_type type;
 +      void *new = read_sha1_file(ce->sha1, &type, size);
 +
 +      if (new) {
 +              if (type == OBJ_BLOB)
 +                      return new;
 +              free(new);
 +      }
 +      return NULL;
 +}
 +
  static int write_entry(struct cache_entry *ce, char *path, struct checkout *state, int to_tempfile)
  {
        int fd;
 -      void *new;
 -      unsigned long size;
        long wrote;
 -      enum object_type type;
  
 -      new = read_sha1_file(ce->sha1, &type, &size);
 -      if (!new || type != OBJ_BLOB) {
 -              if (new)
 -                      free(new);
 -              return error("git-checkout-index: unable to read sha1 file of %s (%s)",
 -                      path, sha1_to_hex(ce->sha1));
 -      }
        switch (ntohl(ce->ce_mode) & S_IFMT) {
 -              char *buf;
 +              char *buf, *new;
-               unsigned long size, nsize;
++              unsigned long size;
  
        case S_IFREG:
 +              new = read_blob_entry(ce, path, &size);
 +              if (!new)
 +                      return error("git-checkout-index: unable to read sha1 file of %s (%s)",
 +                              path, sha1_to_hex(ce->sha1));
                if (to_tempfile) {
                        strcpy(path, ".merge_file_XXXXXX");
                        fd = mkstemp(path);
                /*
                 * Convert from git internal format to working tree format
                 */
-               buf = new;
-               nsize = size;
-               if (convert_to_working_tree(ce->name, &buf, &nsize)) {
+               buf = convert_to_working_tree(ce->name, new, &size);
+               if (buf) {
                        free(new);
                        new = buf;
-                       size = nsize;
                }
  
                wrote = write_in_full(fd, new, size);
                        return error("git-checkout-index: unable to write file %s", path);
                break;
        case S_IFLNK:
 +              new = read_blob_entry(ce, path, &size);
 +              if (!new)
 +                      return error("git-checkout-index: unable to read sha1 file of %s (%s)",
 +                              path, sha1_to_hex(ce->sha1));
                if (to_tempfile || !has_symlinks) {
                        if (to_tempfile) {
                                strcpy(path, ".merge_link_XXXXXX");
                                                 "symlink %s (%s)", path, strerror(errno));
                }
                break;
 +      case S_IFDIRLNK:
 +              if (to_tempfile)
 +                      return error("git-checkout-index: cannot create temporary subproject %s", path);
 +              if (mkdir(path, 0777) < 0)
 +                      return error("git-checkout-index: cannot create subproject directory %s", path);
 +              break;
        default:
 -              free(new);
                return error("git-checkout-index: unknown file mode for %s", path);
        }
  
@@@ -195,9 -176,6 +193,9 @@@ int checkout_entry(struct cache_entry *
                 */
                unlink(path);
                if (S_ISDIR(st.st_mode)) {
 +                      /* If it is a gitlink, leave it alone! */
 +                      if (S_ISDIRLNK(ntohl(ce->ce_mode)))
 +                              return 0;
                        if (!state->force)
                                return error("%s is a directory", path);
                        remove_subtree(path);
diff --combined merge-recursive.c
@@@ -15,6 -15,8 +15,8 @@@
  #include "unpack-trees.h"
  #include "path-list.h"
  #include "xdiff-interface.h"
+ #include "interpolate.h"
+ #include "attr.h"
  
  static int subtree_merge;
  
@@@ -95,6 -97,11 +97,6 @@@ static struct path_list current_directo
  static int call_depth = 0;
  static int verbosity = 2;
  static int buffer_output = 1;
 -static int do_progress = 1;
 -static unsigned last_percent;
 -static unsigned merged_cnt;
 -static unsigned total_cnt;
 -static volatile sig_atomic_t progress_update;
  static struct output_buffer *output_list, *output_end;
  
  static int show (int v)
@@@ -169,6 -176,39 +171,6 @@@ static void output_commit_title(struct 
        }
  }
  
 -static void progress_interval(int signum)
 -{
 -      progress_update = 1;
 -}
 -
 -static void setup_progress_signal(void)
 -{
 -      struct sigaction sa;
 -      struct itimerval v;
 -
 -      memset(&sa, 0, sizeof(sa));
 -      sa.sa_handler = progress_interval;
 -      sigemptyset(&sa.sa_mask);
 -      sa.sa_flags = SA_RESTART;
 -      sigaction(SIGALRM, &sa, NULL);
 -
 -      v.it_interval.tv_sec = 1;
 -      v.it_interval.tv_usec = 0;
 -      v.it_value = v.it_interval;
 -      setitimer(ITIMER_REAL, &v, NULL);
 -}
 -
 -static void display_progress()
 -{
 -      unsigned percent = total_cnt ? merged_cnt * 100 / total_cnt : 0;
 -      if (progress_update || percent != last_percent) {
 -              fprintf(stderr, "%4u%% (%u/%u) done\r",
 -                      percent, merged_cnt, total_cnt);
 -              progress_update = 0;
 -              last_percent = percent;
 -      }
 -}
 -
  static struct cache_entry *make_cache_entry(unsigned int mode,
                const unsigned char *sha1, const char *path, int stage, int refresh)
  {
@@@ -339,11 -379,14 +341,11 @@@ static struct path_list *get_unmerged(v
        int i;
  
        unmerged->strdup_paths = 1;
 -      total_cnt += active_nr;
  
 -      for (i = 0; i < active_nr; i++, merged_cnt++) {
 +      for (i = 0; i < active_nr; i++) {
                struct path_list_item *item;
                struct stage_data *e;
                struct cache_entry *ce = active_cache[i];
 -              if (do_progress)
 -                      display_progress();
                if (!ce_stage(ce))
                        continue;
  
@@@ -533,31 -576,6 +535,31 @@@ static void flush_buffer(int fd, const 
        }
  }
  
 +static int make_room_for_path(const char *path)
 +{
 +      int status;
 +      const char *msg = "failed to create path '%s'%s";
 +
 +      status = mkdir_p(path, 0777);
 +      if (status) {
 +              if (status == -3) {
 +                      /* something else exists */
 +                      error(msg, path, ": perhaps a D/F conflict?");
 +                      return -1;
 +              }
 +              die(msg, path, "");
 +      }
 +
 +      /* Successful unlink is good.. */
 +      if (!unlink(path))
 +              return 0;
 +      /* .. and so is no existing file */
 +      if (errno == ENOENT)
 +              return 0;
 +      /* .. but not some other error (who really cares what?) */
 +      return error(msg, path, ": perhaps a D/F conflict?");
 +}
 +
  static void update_file_flags(const unsigned char *sha,
                              unsigned mode,
                              const char *path,
                if (type != OBJ_BLOB)
                        die("blob expected for %s '%s'", sha1_to_hex(sha), path);
  
 +              if (make_room_for_path(path) < 0) {
 +                      update_wd = 0;
 +                      goto update_index;
 +              }
                if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
                        int fd;
 -                      if (mkdir_p(path, 0777))
 -                              die("failed to create path %s: %s", path, strerror(errno));
 -                      unlink(path);
                        if (mode & 0100)
                                mode = 0777;
                        else
                        die("do not know what to do with %06o %s '%s'",
                            mode, sha1_to_hex(sha), path);
        }
 + update_index:
        if (update_cache)
                add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
  }
@@@ -645,6 -661,384 +647,384 @@@ static void fill_mm(const unsigned cha
        mm->size = size;
  }
  
+ /*
+  * Customizable low-level merge drivers support.
+  */
+ struct ll_merge_driver;
+ typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+                          const char *path,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          mmbuffer_t *result);
+ struct ll_merge_driver {
+       const char *name;
+       const char *description;
+       ll_merge_fn fn;
+       const char *recursive;
+       struct ll_merge_driver *next;
+       char *cmdline;
+ };
+ /*
+  * Built-in low-levels
+  */
+ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+                       const char *path_unused,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       mmbuffer_t *result)
+ {
+       xpparam_t xpp;
+       memset(&xpp, 0, sizeof(xpp));
+       return xdl_merge(orig,
+                        src1, name1,
+                        src2, name2,
+                        &xpp, XDL_MERGE_ZEALOUS,
+                        result);
+ }
+ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+                         const char *path_unused,
+                         mmfile_t *orig,
+                         mmfile_t *src1, const char *name1,
+                         mmfile_t *src2, const char *name2,
+                         mmbuffer_t *result)
+ {
+       char *src, *dst;
+       long size;
+       const int marker_size = 7;
+       int status = ll_xdl_merge(drv_unused, path_unused,
+                                 orig, src1, NULL, src2, NULL, result);
+       if (status <= 0)
+               return status;
+       size = result->size;
+       src = dst = result->ptr;
+       while (size) {
+               char ch;
+               if ((marker_size < size) &&
+                   (*src == '<' || *src == '=' || *src == '>')) {
+                       int i;
+                       ch = *src;
+                       for (i = 0; i < marker_size; i++)
+                               if (src[i] != ch)
+                                       goto not_a_marker;
+                       if (src[marker_size] != '\n')
+                               goto not_a_marker;
+                       src += marker_size + 1;
+                       size -= marker_size + 1;
+                       continue;
+               }
+       not_a_marker:
+               do {
+                       ch = *src++;
+                       *dst++ = ch;
+                       size--;
+               } while (ch != '\n' && size);
+       }
+       result->size = dst - result->ptr;
+       return 0;
+ }
+ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+                          const char *path_unused,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          mmbuffer_t *result)
+ {
+       /*
+        * The tentative merge result is "ours" for the final round,
+        * or common ancestor for an internal merge.  Still return
+        * "conflicted merge" status.
+        */
+       mmfile_t *stolen = index_only ? orig : src1;
+       result->ptr = stolen->ptr;
+       result->size = stolen->size;
+       stolen->ptr = NULL;
+       return 1;
+ }
+ #define LL_BINARY_MERGE 0
+ #define LL_TEXT_MERGE 1
+ #define LL_UNION_MERGE 2
+ static struct ll_merge_driver ll_merge_drv[] = {
+       { "binary", "built-in binary merge", ll_binary_merge },
+       { "text", "built-in 3-way text merge", ll_xdl_merge },
+       { "union", "built-in union merge", ll_union_merge },
+ };
+ static void create_temp(mmfile_t *src, char *path)
+ {
+       int fd;
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = mkstemp(path);
+       if (fd < 0)
+               die("unable to create temp-file");
+       if (write_in_full(fd, src->ptr, src->size) != src->size)
+               die("unable to write temp-file");
+       close(fd);
+ }
+ /*
+  * User defined low-level merge driver support.
+  */
+ static int ll_ext_merge(const struct ll_merge_driver *fn,
+                       const char *path,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       mmbuffer_t *result)
+ {
+       char temp[3][50];
+       char cmdbuf[2048];
+       struct interp table[] = {
+               { "%O" },
+               { "%A" },
+               { "%B" },
+       };
+       struct child_process child;
+       const char *args[20];
+       int status, fd, i;
+       struct stat st;
+       if (fn->cmdline == NULL)
+               die("custom merge driver %s lacks command line.", fn->name);
+       result->ptr = NULL;
+       result->size = 0;
+       create_temp(orig, temp[0]);
+       create_temp(src1, temp[1]);
+       create_temp(src2, temp[2]);
+       interp_set_entry(table, 0, temp[0]);
+       interp_set_entry(table, 1, temp[1]);
+       interp_set_entry(table, 2, temp[2]);
+       output(1, "merging %s using %s", path,
+              fn->description ? fn->description : fn->name);
+       interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
+       memset(&child, 0, sizeof(child));
+       child.argv = args;
+       args[0] = "sh";
+       args[1] = "-c";
+       args[2] = cmdbuf;
+       args[3] = NULL;
+       status = run_command(&child);
+       if (status < -ERR_RUN_COMMAND_FORK)
+               ; /* failure in run-command */
+       else
+               status = -status;
+       fd = open(temp[1], O_RDONLY);
+       if (fd < 0)
+               goto bad;
+       if (fstat(fd, &st))
+               goto close_bad;
+       result->size = st.st_size;
+       result->ptr = xmalloc(result->size + 1);
+       if (read_in_full(fd, result->ptr, result->size) != result->size) {
+               free(result->ptr);
+               result->ptr = NULL;
+               result->size = 0;
+       }
+  close_bad:
+       close(fd);
+  bad:
+       for (i = 0; i < 3; i++)
+               unlink(temp[i]);
+       return status;
+ }
+ /*
+  * merge.default and merge.driver configuration items
+  */
+ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
+ static const char *default_ll_merge;
+ static int read_merge_config(const char *var, const char *value)
+ {
+       struct ll_merge_driver *fn;
+       const char *ep, *name;
+       int namelen;
+       if (!strcmp(var, "merge.default")) {
+               if (value)
+                       default_ll_merge = strdup(value);
+               return 0;
+       }
+       /*
+        * We are not interested in anything but "merge.<name>.variable";
+        * especially, we do not want to look at variables such as
+        * "merge.summary", "merge.tool", and "merge.verbosity".
+        */
+       if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
+               return 0;
+       /*
+        * Find existing one as we might be processing merge.<name>.var2
+        * after seeing merge.<name>.var1.
+        */
+       name = var + 6;
+       namelen = ep - name;
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
+                       break;
+       if (!fn) {
+               char *namebuf;
+               fn = xcalloc(1, sizeof(struct ll_merge_driver));
+               namebuf = xmalloc(namelen + 1);
+               memcpy(namebuf, name, namelen);
+               namebuf[namelen] = 0;
+               fn->name = namebuf;
+               fn->fn = ll_ext_merge;
+               fn->next = NULL;
+               *ll_user_merge_tail = fn;
+               ll_user_merge_tail = &(fn->next);
+       }
+       ep++;
+       if (!strcmp("name", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               fn->description = strdup(value);
+               return 0;
+       }
+       if (!strcmp("driver", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               /*
+                * merge.<name>.driver specifies the command line:
+                *
+                *      command-line
+                *
+                * The command-line will be interpolated with the following
+                * tokens and is given to the shell:
+                *
+                *    %O - temporary file name for the merge base.
+                *    %A - temporary file name for our version.
+                *    %B - temporary file name for the other branches' version.
+                *
+                * The external merge driver should write the results in the
+                * file named by %A, and signal that it has done with zero exit
+                * status.
+                */
+               fn->cmdline = strdup(value);
+               return 0;
+       }
+       if (!strcmp("recursive", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               fn->recursive = strdup(value);
+               return 0;
+       }
+       return 0;
+ }
+ static void initialize_ll_merge(void)
+ {
+       if (ll_user_merge_tail)
+               return;
+       ll_user_merge_tail = &ll_user_merge;
+       git_config(read_merge_config);
+ }
+ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
+ {
+       struct ll_merge_driver *fn;
+       const char *name;
+       int i;
+       initialize_ll_merge();
+       if (ATTR_TRUE(merge_attr))
+               return &ll_merge_drv[LL_TEXT_MERGE];
+       else if (ATTR_FALSE(merge_attr))
+               return &ll_merge_drv[LL_BINARY_MERGE];
+       else if (ATTR_UNSET(merge_attr)) {
+               if (!default_ll_merge)
+                       return &ll_merge_drv[LL_TEXT_MERGE];
+               else
+                       name = default_ll_merge;
+       }
+       else
+               name = merge_attr;
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strcmp(fn->name, name))
+                       return fn;
+       for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
+               if (!strcmp(ll_merge_drv[i].name, name))
+                       return &ll_merge_drv[i];
+       /* default to the 3-way */
+       return &ll_merge_drv[LL_TEXT_MERGE];
+ }
+ static const char *git_path_check_merge(const char *path)
+ {
+       static struct git_attr_check attr_merge_check;
+       if (!attr_merge_check.attr)
+               attr_merge_check.attr = git_attr("merge", 5);
+       if (git_checkattr(path, 1, &attr_merge_check))
+               return NULL;
+       return attr_merge_check.value;
+ }
+ static int ll_merge(mmbuffer_t *result_buf,
+                   struct diff_filespec *o,
+                   struct diff_filespec *a,
+                   struct diff_filespec *b,
+                   const char *branch1,
+                   const char *branch2)
+ {
+       mmfile_t orig, src1, src2;
+       char *name1, *name2;
+       int merge_status;
+       const char *ll_driver_name;
+       const struct ll_merge_driver *driver;
+       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
+       fill_mm(o->sha1, &orig);
+       fill_mm(a->sha1, &src1);
+       fill_mm(b->sha1, &src2);
+       ll_driver_name = git_path_check_merge(a->path);
+       driver = find_ll_merge_driver(ll_driver_name);
+       if (index_only && driver->recursive)
+               driver = find_ll_merge_driver(driver->recursive);
+       merge_status = driver->fn(driver, a->path,
+                                 &orig, &src1, name1, &src2, name2,
+                                 result_buf);
+       free(name1);
+       free(name2);
+       free(orig.ptr);
+       free(src1.ptr);
+       free(src2.ptr);
+       return merge_status;
+ }
  static struct merge_file_info merge_file(struct diff_filespec *o,
                struct diff_filespec *a, struct diff_filespec *b,
                const char *branch1, const char *branch2)
                else if (sha_eq(b->sha1, o->sha1))
                        hashcpy(result.sha, a->sha1);
                else if (S_ISREG(a->mode)) {
-                       mmfile_t orig, src1, src2;
                        mmbuffer_t result_buf;
-                       xpparam_t xpp;
-                       char *name1, *name2;
                        int merge_status;
  
-                       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
-                       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
-                       fill_mm(o->sha1, &orig);
-                       fill_mm(a->sha1, &src1);
-                       fill_mm(b->sha1, &src2);
-                       memset(&xpp, 0, sizeof(xpp));
-                       merge_status = xdl_merge(&orig,
-                                                &src1, name1,
-                                                &src2, name2,
-                                                &xpp, XDL_MERGE_ZEALOUS,
-                                                &result_buf);
-                       free(name1);
-                       free(name2);
-                       free(orig.ptr);
-                       free(src1.ptr);
-                       free(src2.ptr);
+                       merge_status = ll_merge(&result_buf, o, a, b,
+                                               branch1, branch2);
  
                        if ((merge_status < 0) || !result_buf.ptr)
                                die("Failed to execute internal merge");
@@@ -1004,9 -1379,9 +1365,9 @@@ static int process_renames(struct path_
        return clean_merge;
  }
  
 -static unsigned char *has_sha(const unsigned char *sha)
 +static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
  {
 -      return is_null_sha1(sha) ? NULL: (unsigned char *)sha;
 +      return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
  }
  
  /* Per entry merge function */
@@@ -1019,12 -1394,12 +1380,12 @@@ static int process_entry(const char *pa
        print_index_entry("\tpath: ", entry);
        */
        int clean_merge = 1;
 -      unsigned char *o_sha = has_sha(entry->stages[1].sha);
 -      unsigned char *a_sha = has_sha(entry->stages[2].sha);
 -      unsigned char *b_sha = has_sha(entry->stages[3].sha);
        unsigned o_mode = entry->stages[1].mode;
        unsigned a_mode = entry->stages[2].mode;
        unsigned b_mode = entry->stages[3].mode;
 +      unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
 +      unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
 +      unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
  
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                                update_file_flags(mfi.sha, mfi.mode, path,
                                              0 /* update_cache */, 1 /* update_working_directory */);
                }
 +      } else if (!o_sha && !a_sha && !b_sha) {
 +              /*
 +               * this entry was deleted altogether. a_mode == 0 means
 +               * we had that path and want to actively remove it.
 +               */
 +              remove_file(1, path, !a_mode);
        } else
                die("Fatal merge failure, shouldn't happen.");
  
@@@ -1177,12 -1546,15 +1538,12 @@@ static int merge_trees(struct tree *hea
                re_merge = get_renames(merge, common, head, merge, entries);
                clean = process_renames(re_head, re_merge,
                                branch1, branch2);
 -              total_cnt += entries->nr;
 -              for (i = 0; i < entries->nr; i++, merged_cnt++) {
 +              for (i = 0; i < entries->nr; i++) {
                        const char *path = entries->items[i].path;
                        struct stage_data *e = entries->items[i].util;
                        if (!e->processed
                                && !process_entry(path, e, branch1, branch2))
                                clean = 0;
 -                      if (do_progress)
 -                              display_progress();
                }
  
                path_list_clear(re_merge, 0);
@@@ -1290,6 -1662,15 +1651,6 @@@ static int merge(struct commit *h1
                commit_list_insert(h1, &(*result)->parents);
                commit_list_insert(h2, &(*result)->parents->next);
        }
 -      if (!call_depth && do_progress) {
 -              /* Make sure we end at 100% */
 -              if (!total_cnt)
 -                      total_cnt = 1;
 -              merged_cnt = total_cnt;
 -              progress_update = 1;
 -              display_progress();
 -              fputc('\n', stderr);
 -      }
        flush_output();
        return clean;
  }
@@@ -1366,8 -1747,12 +1727,8 @@@ int main(int argc, char *argv[]
        }
        if (argc - i != 3) /* "--" "<head>" "<remote>" */
                die("Not handling anything other than two heads merge.");
 -      if (verbosity >= 5) {
 +      if (verbosity >= 5)
                buffer_output = 0;
 -              do_progress = 0;
 -      }
 -      else
 -              do_progress = isatty(1);
  
        branch1 = argv[++i];
        branch2 = argv[++i];
        branch1 = better_branch_name(branch1);
        branch2 = better_branch_name(branch2);
  
 -      if (do_progress)
 -              setup_progress_signal();
        if (show(3))
                printf("Merging %s with %s\n", branch1, branch2);
  
diff --combined sha1_file.c
@@@ -13,7 -13,6 +13,7 @@@
  #include "commit.h"
  #include "tag.h"
  #include "tree.h"
 +#include "refs.h"
  
  #ifndef O_NOATIME
  #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@@ -438,7 -437,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;
  }
  
@@@ -638,11 -605,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))
@@@ -1161,43 -1128,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);
@@@ -1261,8 -1191,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;
  }
@@@ -1564,60 -1526,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
@@@ -2338,10 -2277,9 +2338,9 @@@ int index_fd(unsigned char *sha1, int f
         */
        if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
                unsigned long nsize = size;
-               char *nbuf = buf;
-               if (convert_to_git(path, &nbuf, &nsize)) {
-                       if (size)
-                               munmap(buf, size);
+               char *nbuf = convert_to_git(path, buf, &nsize);
+               if (nbuf) {
+                       munmap(buf, size);
                        size = nsize;
                        buf = nbuf;
                        re_allocated = 1;
@@@ -2393,8 -2331,6 +2392,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);
        }