Merge branch 'jk/cat-file-batch-optim'
authorJunio C Hamano <gitster@pobox.com>
Thu, 25 Jul 2013 02:21:21 +0000 (19:21 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 25 Jul 2013 02:21:21 +0000 (19:21 -0700)
If somebody wants to only know on-disk footprint of an object
without having to know its type or payload size, we can bypass a
lot of code to cheaply learn it.

* jk/cat-file-batch-optim:
Fix some sparse warnings
sha1_object_info_extended: pass object_info to helpers
sha1_object_info_extended: make type calculation optional
packed_object_info: make type lookup optional
packed_object_info: hoist delta type resolution to helper
sha1_loose_object_info: make type lookup optional
sha1_object_info_extended: rename "status" to "type"
cat-file: disable object/refname ambiguity check for batch mode

1  2 
cache.h
environment.c
sha1_file.c
sha1_name.c
streaming.c
diff --combined cache.h
index 4c606ce28bcd59956a47b2881436047bc7a5ed90,6882bbe6c3f36f8626ce8b1791d44cc1b99b4e16..3a69638bd919278468e579ca15efbd592eff1399
+++ b/cache.h
@@@ -119,19 -119,15 +119,19 @@@ struct cache_time 
        unsigned int nsec;
  };
  
 +struct stat_data {
 +      struct cache_time sd_ctime;
 +      struct cache_time sd_mtime;
 +      unsigned int sd_dev;
 +      unsigned int sd_ino;
 +      unsigned int sd_uid;
 +      unsigned int sd_gid;
 +      unsigned int sd_size;
 +};
 +
  struct cache_entry {
 -      struct cache_time ce_ctime;
 -      struct cache_time ce_mtime;
 -      unsigned int ce_dev;
 -      unsigned int ce_ino;
 +      struct stat_data ce_stat_data;
        unsigned int ce_mode;
 -      unsigned int ce_uid;
 -      unsigned int ce_gid;
 -      unsigned int ce_size;
        unsigned int ce_flags;
        unsigned int ce_namelen;
        unsigned char sha1[20];
   * another. But we never change the name, or the hash state!
   */
  #define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
 -static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
 +static inline void copy_cache_entry(struct cache_entry *dst,
 +                                  const struct cache_entry *src)
  {
        unsigned int state = dst->ce_flags & CE_STATE_MASK;
  
@@@ -227,8 -222,7 +227,8 @@@ static inline unsigned int create_ce_mo
                return S_IFGITLINK;
        return S_IFREG | ce_permissions(mode);
  }
 -static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
 +static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
 +                                           unsigned int mode)
  {
        extern int trust_executable_bit, has_symlinks;
        if (!has_symlinks && S_ISREG(mode) &&
@@@ -425,8 -419,6 +425,8 @@@ extern int path_inside_repo(const char 
  extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
 +extern void sanitize_stdfds(void);
 +
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
@@@ -478,7 -470,7 +478,7 @@@ extern int remove_file_from_index(struc
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern int index_name_is_other(const struct index_state *, const char *, int);
  extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
  
  #define CE_MATCH_RACY_IS_DIRTY                02
  /* do stat comparison even if CE_SKIP_WORKTREE is true */
  #define CE_MATCH_IGNORE_SKIP_WORKTREE 04
 -extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 -extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  
  #define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
  
@@@ -517,21 -509,6 +517,21 @@@ extern int limit_pathspec_to_literal(vo
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
 +
 +/*
 + * Record to sd the data from st that we use to check whether a file
 + * might have changed.
 + */
 +extern void fill_stat_data(struct stat_data *sd, struct stat *st);
 +
 +/*
 + * Return 0 if st is consistent with a file not having been changed
 + * since sd was filled.  If there are differences, return a
 + * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
 + * INODE_CHANGED, and DATA_CHANGED.
 + */
 +extern int match_stat_data(const struct stat_data *sd, struct stat *st);
 +
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
  #define REFRESH_REALLY                0x0001  /* ignore_valid */
@@@ -577,6 -554,7 +577,7 @@@ extern int assume_unchanged
  extern int prefer_symlink_refs;
  extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
+ extern int warn_on_object_refname_ambiguity;
  extern int shared_repository;
  extern const char *apply_default_whitespace;
  extern const char *apply_default_ignorewhitespace;
@@@ -760,7 -738,7 +761,7 @@@ int is_directory(const char *)
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
  const char *absolute_path(const char *path);
 -const char *relative_path(const char *abs, const char *base);
 +const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
  int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
@@@ -795,6 -773,9 +796,6 @@@ extern int parse_sha1_header(const cha
  /* global flag to enable extra checks when accessing packed objects */
  extern int do_check_packed_object_crc;
  
 -/* for development: log offset of pack access */
 -extern const char *log_pack_access;
 -
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
  extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@@ -930,7 -911,6 +931,7 @@@ void show_date_relative(unsigned long t
                        struct strbuf *timebuf);
  int parse_date(const char *date, char *buf, int bufsize);
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
 +int parse_expiry_date(const char *date, unsigned long *timestamp);
  void datestamp(char *buf, int bufsize);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
@@@ -1045,21 -1025,9 +1046,21 @@@ struct ref 
        unsigned int
                force:1,
                forced_update:1,
 -              merge:1,
                deletion:1,
                matched:1;
 +
 +      /*
 +       * Order is important here, as we write to FETCH_HEAD
 +       * in numeric order. And the default NOT_FOR_MERGE
 +       * should be 0, so that xcalloc'd structures get it
 +       * by default.
 +       */
 +      enum {
 +              FETCH_HEAD_MERGE = -1,
 +              FETCH_HEAD_NOT_FOR_MERGE = 0,
 +              FETCH_HEAD_IGNORE = 1
 +      } fetch_head_status;
 +
        enum {
                REF_STATUS_NONE = 0,
                REF_STATUS_OK,
@@@ -1131,6 -1099,7 +1132,7 @@@ extern int unpack_object_header(struct 
  
  struct object_info {
        /* Request */
+       enum object_type *typep;
        unsigned long *sizep;
        unsigned long *disk_sizep;
  
@@@ -1176,15 -1145,11 +1178,15 @@@ extern int update_server_info(int)
  typedef int (*config_fn_t)(const char *, const char *, void *);
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
 +extern int git_config_from_buf(config_fn_t fn, const char *name,
 +                             const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern int git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
 -                                 const char *filename, int respect_includes);
 +                                 const char *filename,
 +                                 const char *blob_ref,
 +                                 int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
@@@ -1364,31 -1329,4 +1366,31 @@@ int checkout_fast_forward(const unsigne
  
  int sane_execvp(const char *file, char *const argv[]);
  
 +/*
 + * A struct to encapsulate the concept of whether a file has changed
 + * since we last checked it. This uses criteria similar to those used
 + * for the index.
 + */
 +struct stat_validity {
 +      struct stat_data *sd;
 +};
 +
 +void stat_validity_clear(struct stat_validity *sv);
 +
 +/*
 + * Returns 1 if the path is a regular file (or a symlink to a regular
 + * file) and matches the saved stat_validity, 0 otherwise.  A missing
 + * or inaccessible file is considered a match if the struct was just
 + * initialized, or if the previous update found an inaccessible file.
 + */
 +int stat_validity_check(struct stat_validity *sv, const char *path);
 +
 +/*
 + * Update the stat_validity from a file opened at descriptor fd. If
 + * the file is missing, inaccessible, or not a regular file, then
 + * future calls to stat_validity_check will match iff one of those
 + * conditions continues to be true.
 + */
 +void stat_validity_update(struct stat_validity *sv, int fd);
 +
  #endif /* CACHE_H */
diff --combined environment.c
index 0cb67b22cf5a8754daf429b3dbe45ef3e663650b,1a99e472516b2d67cf6f10dcd33589e3c9fd3d46..5398c36dd4dc2c1d7ded3f92c217a99a240a0a23
@@@ -22,6 -22,7 +22,7 @@@ int prefer_symlink_refs
  int is_bare_repository_cfg = -1; /* unspecified */
  int log_all_ref_updates = -1; /* unspecified */
  int warn_ambiguous_refs = 1;
+ int warn_on_object_refname_ambiguity = 1;
  int repository_format_version;
  const char *git_commit_encoding;
  const char *git_log_output_encoding;
@@@ -37,6 -38,7 +38,6 @@@ size_t packed_git_window_size = DEFAULT
  size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
  size_t delta_base_cache_limit = 16 * 1024 * 1024;
  unsigned long big_file_threshold = 512 * 1024 * 1024;
 -const char *log_pack_access;
  const char *pager_program;
  int pager_use_color = 1;
  const char *editor_program;
diff --combined sha1_file.c
index 4c2365f48f7ce361e009d5f4d9323184f6782d34,452e4647b387799ebed343b100236b1cba9b0104..8e27db1bd2b49f28b235fcd7e18a0dda43a1f045
@@@ -36,9 -36,6 +36,9 @@@ static inline uintmax_t sz_fmt(size_t s
  
  const unsigned char null_sha1[20];
  
 +static const char *no_log_pack_access = "no_log_pack_access";
 +static const char *log_pack_access;
 +
  /*
   * This is meant to hold a *small* number of objects that you would
   * want read_sha1_file() to be able to return, but yet you do not want
@@@ -1306,6 -1303,26 +1306,26 @@@ static int git_open_noatime(const char 
        }
  }
  
+ static int stat_sha1_file(const unsigned char *sha1, struct stat *st)
+ {
+       char *name = sha1_file_name(sha1);
+       struct alternate_object_database *alt;
+       if (!lstat(name, st))
+               return 0;
+       prepare_alt_odb();
+       errno = ENOENT;
+       for (alt = alt_odb_list; alt; alt = alt->next) {
+               name = alt->name;
+               fill_sha1_path(name, sha1);
+               if (!lstat(alt->base, st))
+                       return 0;
+       }
+       return -1;
+ }
  static int open_sha1_file(const unsigned char *sha1)
  {
        int fd;
@@@ -1693,52 -1710,21 +1713,21 @@@ static int retry_bad_packed_offset(stru
        return type;
  }
  
  #define POI_STACK_PREALLOC 64
  
- static int packed_object_info(struct packed_git *p, off_t obj_offset,
-                             unsigned long *sizep, int *rtype,
-                             unsigned long *disk_sizep)
+ static enum object_type packed_to_object_type(struct packed_git *p,
+                                             off_t obj_offset,
+                                             enum object_type type,
+                                             struct pack_window **w_curs,
+                                             off_t curpos)
  {
-       struct pack_window *w_curs = NULL;
-       unsigned long size;
-       off_t curpos = obj_offset;
-       enum object_type type;
        off_t small_poi_stack[POI_STACK_PREALLOC];
        off_t *poi_stack = small_poi_stack;
        int poi_stack_nr = 0, poi_stack_alloc = POI_STACK_PREALLOC;
  
-       type = unpack_object_header(p, &w_curs, &curpos, &size);
-       if (rtype)
-               *rtype = type; /* representation type */
-       if (sizep) {
-               if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
-                       off_t tmp_pos = curpos;
-                       off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos,
-                                                          type, obj_offset);
-                       if (!base_offset) {
-                               type = OBJ_BAD;
-                               goto out;
-                       }
-                       *sizep = get_size_from_delta(p, &w_curs, tmp_pos);
-                       if (*sizep == 0) {
-                               type = OBJ_BAD;
-                               goto out;
-                       }
-               } else {
-                       *sizep = size;
-               }
-       }
-       if (disk_sizep) {
-               struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
-               *disk_sizep = revidx[1].offset - obj_offset;
-       }
        while (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
                off_t base_offset;
+               unsigned long size;
                /* Push the object we're going to leave behind */
                if (poi_stack_nr >= poi_stack_alloc && poi_stack == small_poi_stack) {
                        poi_stack_alloc = alloc_nr(poi_stack_nr);
                }
                poi_stack[poi_stack_nr++] = obj_offset;
                /* If parsing the base offset fails, just unwind */
-               base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
+               base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
                if (!base_offset)
                        goto unwind;
                curpos = obj_offset = base_offset;
-               type = unpack_object_header(p, &w_curs, &curpos, &size);
+               type = unpack_object_header(p, w_curs, &curpos, &size);
                if (type <= OBJ_NONE) {
                        /* If getting the base itself fails, we first
                         * retry the base, otherwise unwind */
  out:
        if (poi_stack != small_poi_stack)
                free(poi_stack);
-       unuse_pack(&w_curs);
        return type;
  
  unwind:
        goto out;
  }
  
+ static int packed_object_info(struct packed_git *p, off_t obj_offset,
+                             struct object_info *oi)
+ {
+       struct pack_window *w_curs = NULL;
+       unsigned long size;
+       off_t curpos = obj_offset;
+       enum object_type type;
+       /*
+        * We always get the representation type, but only convert it to
+        * a "real" type later if the caller is interested.
+        */
+       type = unpack_object_header(p, &w_curs, &curpos, &size);
+       if (oi->sizep) {
+               if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
+                       off_t tmp_pos = curpos;
+                       off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos,
+                                                          type, obj_offset);
+                       if (!base_offset) {
+                               type = OBJ_BAD;
+                               goto out;
+                       }
+                       *oi->sizep = get_size_from_delta(p, &w_curs, tmp_pos);
+                       if (*oi->sizep == 0) {
+                               type = OBJ_BAD;
+                               goto out;
+                       }
+               } else {
+                       *oi->sizep = size;
+               }
+       }
+       if (oi->disk_sizep) {
+               struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
+               *oi->disk_sizep = revidx[1].offset - obj_offset;
+       }
+       if (oi->typep) {
+               *oi->typep = packed_to_object_type(p, obj_offset, type, &w_curs, curpos);
+               if (*oi->typep < 0) {
+                       type = OBJ_BAD;
+                       goto out;
+               }
+       }
+ out:
+       unuse_pack(&w_curs);
+       return type;
+ }
  static void *unpack_compressed_entry(struct packed_git *p,
                                    struct pack_window **w_curs,
                                    off_t curpos,
@@@ -1965,19 -2001,12 +2004,19 @@@ static void write_pack_access_log(struc
  {
        static FILE *log_file;
  
 +      if (!log_pack_access)
 +              log_pack_access = getenv("GIT_TRACE_PACK_ACCESS");
 +      if (!log_pack_access)
 +              log_pack_access = no_log_pack_access;
 +      if (log_pack_access == no_log_pack_access)
 +              return;
 +
        if (!log_file) {
                log_file = fopen(log_pack_access, "w");
                if (!log_file) {
                        error("cannot open pack access log '%s' for writing: %s",
                              log_pack_access, strerror(errno));
 -                      log_pack_access = NULL;
 +                      log_pack_access = no_log_pack_access;
                        return;
                }
        }
@@@ -2008,7 -2037,7 +2047,7 @@@ void *unpack_entry(struct packed_git *p
        int delta_stack_nr = 0, delta_stack_alloc = UNPACK_ENTRY_STACK_PREALLOC;
        int base_from_cache = 0;
  
 -      if (log_pack_access)
 +      if (log_pack_access != no_log_pack_access)
                write_pack_access_log(p, obj_offset);
  
        /* PHASE 1: drill down to the innermost base object */
                data = patch_delta(base, base_size,
                                   delta_data, delta_size,
                                   &size);
 +
 +              /*
 +               * We could not apply the delta; warn the user, but keep going.
 +               * Our failure will be noticed either in the next iteration of
 +               * the loop, or if this is the final delta, in the caller when
 +               * we return NULL. Those code paths will take care of making
 +               * a more explicit warning and retrying with another copy of
 +               * the object.
 +               */
                if (!data)
 -                      die("failed to apply delta");
 +                      error("failed to apply delta");
  
 -              free (delta_data);
 +              free(delta_data);
        }
  
        *final_type = type;
@@@ -2363,8 -2383,8 +2402,8 @@@ struct packed_git *find_sha1_pack(cons
  
  }
  
- static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizep,
-                                 unsigned long *disk_sizep)
+ static int sha1_loose_object_info(const unsigned char *sha1,
+                                 struct object_info *oi)
  {
        int status;
        unsigned long mapsize, size;
        git_zstream stream;
        char hdr[32];
  
+       /*
+        * If we don't care about type or size, then we don't
+        * need to look inside the object at all.
+        */
+       if (!oi->typep && !oi->sizep) {
+               if (oi->disk_sizep) {
+                       struct stat st;
+                       if (stat_sha1_file(sha1, &st) < 0)
+                               return -1;
+                       *oi->disk_sizep = st.st_size;
+               }
+               return 0;
+       }
        map = map_sha1_file(sha1, &mapsize);
        if (!map)
 -              return error("unable to find %s", sha1_to_hex(sha1));
 +              return -1;
-       if (disk_sizep)
-               *disk_sizep = mapsize;
+       if (oi->disk_sizep)
+               *oi->disk_sizep = mapsize;
        if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
                status = error("unable to unpack %s header",
                               sha1_to_hex(sha1));
        else if ((status = parse_sha1_header(hdr, &size)) < 0)
                status = error("unable to parse %s header", sha1_to_hex(sha1));
-       else if (sizep)
-               *sizep = size;
+       else if (oi->sizep)
+               *oi->sizep = size;
        git_inflate_end(&stream);
        munmap(map, mapsize);
-       return status;
+       if (oi->typep)
+               *oi->typep = status;
+       return 0;
  }
  
  /* returns enum object_type or negative */
@@@ -2394,37 -2430,37 +2449,37 @@@ int sha1_object_info_extended(const uns
  {
        struct cached_object *co;
        struct pack_entry e;
-       int status, rtype;
+       int rtype;
  
        co = find_cached_object(sha1);
        if (co) {
+               if (oi->typep)
+                       *(oi->typep) = co->type;
                if (oi->sizep)
                        *(oi->sizep) = co->size;
                if (oi->disk_sizep)
                        *(oi->disk_sizep) = 0;
                oi->whence = OI_CACHED;
-               return co->type;
+               return 0;
        }
  
        if (!find_pack_entry(sha1, &e)) {
                /* Most likely it's a loose object. */
-               status = sha1_loose_object_info(sha1, oi->sizep, oi->disk_sizep);
-               if (status >= 0) {
+               if (!sha1_loose_object_info(sha1, oi)) {
                        oi->whence = OI_LOOSE;
-                       return status;
+                       return 0;
                }
  
                /* Not a loose object; someone else may have just packed it. */
                reprepare_packed_git();
                if (!find_pack_entry(sha1, &e))
-                       return status;
+                       return -1;
        }
  
-       status = packed_object_info(e.p, e.offset, oi->sizep, &rtype,
-                                   oi->disk_sizep);
-       if (status < 0) {
+       rtype = packed_object_info(e.p, e.offset, oi);
+       if (rtype < 0) {
                mark_bad_packed_object(e.p, sha1);
-               status = sha1_object_info_extended(sha1, oi);
+               return sha1_object_info_extended(sha1, oi);
        } else if (in_delta_base_cache(e.p, e.offset)) {
                oi->whence = OI_DBCACHED;
        } else {
                                         rtype == OBJ_OFS_DELTA);
        }
  
-       return status;
+       return 0;
  }
  
  int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
  {
-       struct object_info oi = {0};
+       enum object_type type;
+       struct object_info oi = {NULL};
  
+       oi.typep = &type;
        oi.sizep = sizep;
-       return sha1_object_info_extended(sha1, &oi);
+       if (sha1_object_info_extended(sha1, &oi) < 0)
+               return -1;
+       return type;
  }
  
  static void *read_packed_sha1(const unsigned char *sha1,
diff --combined sha1_name.c
index 543bf9d9eceb81f97107437b30f4f150270b949a,c1957f35c182af9bf91658fa37602cf4071c7c37..0cf0c28a6fb737ccdb2b93c9b7d01570ec018f37
@@@ -241,7 -241,7 +241,7 @@@ static int disambiguate_committish_only
                return 0;
  
        /* We need to do this the hard way... */
 -      obj = deref_tag(lookup_object(sha1), NULL, 0);
 +      obj = deref_tag(parse_object(sha1), NULL, 0);
        if (obj && obj->type == OBJ_COMMIT)
                return 1;
        return 0;
@@@ -431,7 -431,6 +431,7 @@@ static inline int upstream_mark(const c
  }
  
  static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
 +static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf);
  
  static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
  {
        unsigned char tmp_sha1[20];
        char *real_ref = NULL;
        int refs_found = 0;
 -      int at, reflog_len;
 +      int at, reflog_len, nth_prior = 0;
  
        if (len == 40 && !get_sha1_hex(str, sha1)) {
-               refs_found = dwim_ref(str, len, tmp_sha1, &real_ref);
-               if (refs_found > 0 && warn_ambiguous_refs) {
-                       warning(warn_msg, len, str);
-                       if (advice_object_name_warning)
-                               fprintf(stderr, "%s\n", _(object_name_msg));
+               if (warn_on_object_refname_ambiguity) {
+                       refs_found = dwim_ref(str, len, tmp_sha1, &real_ref);
+                       if (refs_found > 0 && warn_ambiguous_refs) {
+                               warning(warn_msg, len, str);
+                               if (advice_object_name_warning)
+                                       fprintf(stderr, "%s\n", _(object_name_msg));
+                       }
+                       free(real_ref);
                }
-               free(real_ref);
                return 0;
        }
  
        /* basic@{time or number or -number} format to query ref-log */
        reflog_len = at = 0;
        if (len && str[len-1] == '}') {
 -              for (at = len-2; at >= 0; at--) {
 +              for (at = len-4; at >= 0; at--) {
                        if (str[at] == '@' && str[at+1] == '{') {
 +                              if (str[at+2] == '-') {
 +                                      if (at != 0)
 +                                              /* @{-N} not at start */
 +                                              return -1;
 +                                      nth_prior = 1;
 +                                      continue;
 +                              }
                                if (!upstream_mark(str + at, len - at)) {
                                        reflog_len = (len-1) - (at+2);
                                        len = at;
        if (len && ambiguous_path(str, len))
                return -1;
  
 -      if (!len && reflog_len) {
 +      if (nth_prior) {
                struct strbuf buf = STRBUF_INIT;
 -              int ret;
 -              /* try the @{-N} syntax for n-th checkout */
 -              ret = interpret_branch_name(str+at, &buf);
 -              if (ret > 0) {
 -                      /* substitute this branch name and restart */
 -                      return get_sha1_1(buf.buf, buf.len, sha1, 0);
 -              } else if (ret == 0) {
 -                      return -1;
 +              int detached;
 +
 +              if (interpret_nth_prior_checkout(str, &buf) > 0) {
 +                      detached = (buf.len == 40 && !get_sha1_hex(buf.buf, sha1));
 +                      strbuf_release(&buf);
 +                      if (detached)
 +                              return 0;
                }
 +      }
 +
 +      if (!len && reflog_len)
                /* allow "@{...}" to mean the current branch reflog */
                refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
 -      else if (reflog_len)
 +      else if (reflog_len)
                refs_found = dwim_log(str, len, sha1, &real_ref);
        else
                refs_found = dwim_ref(str, len, sha1, &real_ref);
                unsigned long co_time;
                int co_tz, co_cnt;
  
 -              /* a @{-N} placed anywhere except the start is an error */
 -              if (str[at+2] == '-')
 -                      return -1;
 -
                /* Is it asking for N-th entry, or approxidate? */
                for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
                        char ch = str[at+2+i];
                }
                if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
                                &co_time, &co_tz, &co_cnt)) {
 +                      if (!len) {
 +                              if (!prefixcmp(real_ref, "refs/heads/")) {
 +                                      str = real_ref + 11;
 +                                      len = strlen(real_ref + 11);
 +                              } else {
 +                                      /* detached HEAD */
 +                                      str = "HEAD";
 +                                      len = 4;
 +                              }
 +                      }
                        if (at_time)
                                warning("Log for '%.*s' only goes "
                                        "back to %s.", len, str,
                                        show_date(co_time, co_tz, DATE_RFC2822));
                        else {
 -                              free(real_ref);
                                die("Log for '%.*s' only has %d entries.",
                                    len, str, co_cnt);
                        }
@@@ -1002,38 -989,6 +1004,38 @@@ int get_sha1_mb(const char *name, unsig
        return st;
  }
  
 +/* parse @something syntax, when 'something' is not {.*} */
 +static int interpret_empty_at(const char *name, int namelen, int len, struct strbuf *buf)
 +{
 +      if (len || name[1] == '{')
 +              return -1;
 +
 +      strbuf_reset(buf);
 +      strbuf_add(buf, "HEAD", 4);
 +      return 1;
 +}
 +
 +static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
 +{
 +      /* we have extra data, which might need further processing */
 +      struct strbuf tmp = STRBUF_INIT;
 +      int used = buf->len;
 +      int ret;
 +
 +      strbuf_add(buf, name + len, namelen - len);
 +      ret = interpret_branch_name(buf->buf, &tmp);
 +      /* that data was not interpreted, remove our cruft */
 +      if (ret < 0) {
 +              strbuf_setlen(buf, used);
 +              return len;
 +      }
 +      strbuf_reset(buf);
 +      strbuf_addbuf(buf, &tmp);
 +      strbuf_release(&tmp);
 +      /* tweak for size of {-N} versus expanded ref name */
 +      return ret - used + len;
 +}
 +
  /*
   * This reads short-hand syntax that not only evaluates to a commit
   * object name, but also can act as if the end user spelled the name
@@@ -1063,27 -1018,36 +1065,27 @@@ int interpret_branch_name(const char *n
        int len = interpret_nth_prior_checkout(name, buf);
        int tmp_len;
  
 -      if (!len)
 +      if (!len) {
                return len; /* syntax Ok, not enough switches */
 -      if (0 < len && len == namelen)
 -              return len; /* consumed all */
 -      else if (0 < len) {
 -              /* we have extra data, which might need further processing */
 -              struct strbuf tmp = STRBUF_INIT;
 -              int used = buf->len;
 -              int ret;
 -
 -              strbuf_add(buf, name + len, namelen - len);
 -              ret = interpret_branch_name(buf->buf, &tmp);
 -              /* that data was not interpreted, remove our cruft */
 -              if (ret < 0) {
 -                      strbuf_setlen(buf, used);
 -                      return len;
 -              }
 -              strbuf_reset(buf);
 -              strbuf_addbuf(buf, &tmp);
 -              strbuf_release(&tmp);
 -              /* tweak for size of {-N} versus expanded ref name */
 -              return ret - used + len;
 +      } else if (len > 0) {
 +              if (len == namelen)
 +                      return len; /* consumed all */
 +              else
 +                      return reinterpret(name, namelen, len, buf);
        }
  
        cp = strchr(name, '@');
        if (!cp)
                return -1;
 +
 +      len = interpret_empty_at(name, namelen, cp - name, buf);
 +      if (len > 0)
 +              return reinterpret(name, namelen, len, buf);
 +
        tmp_len = upstream_mark(cp, namelen - (cp - name));
        if (!tmp_len)
                return -1;
 +
        len = cp + tmp_len - name;
        cp = xstrndup(name, cp - name);
        upstream = branch_get(*cp ? cp : NULL);
         * points to something different than a branch.
         */
        if (!upstream)
 -              return error(_("HEAD does not point to a branch"));
 +              die(_("HEAD does not point to a branch"));
        if (!upstream->merge || !upstream->merge[0]->dst) {
                if (!ref_exists(upstream->refname))
 -                      return error(_("No such branch: '%s'"), cp);
 -              if (!upstream->merge)
 -                      return error(_("No upstream configured for branch '%s'"),
 -                                   upstream->name);
 -              return error(
 +                      die(_("No such branch: '%s'"), cp);
 +              if (!upstream->merge) {
 +                      die(_("No upstream configured for branch '%s'"),
 +                              upstream->name);
 +              }
 +              die(
                        _("Upstream branch '%s' not stored as a remote-tracking branch"),
                        upstream->merge[0]->src);
        }
  int strbuf_branchname(struct strbuf *sb, const char *name)
  {
        int len = strlen(name);
 -      if (interpret_branch_name(name, sb) == len)
 +      int used = interpret_branch_name(name, sb);
 +
 +      if (used == len)
                return 0;
 -      strbuf_add(sb, name, len);
 +      if (used < 0)
 +              used = 0;
 +      strbuf_add(sb, name + used, len - used);
        return len;
  }
  
@@@ -1234,7 -1193,7 +1236,7 @@@ static void diagnose_invalid_index_path
                                        const char *filename)
  {
        struct stat st;
 -      struct cache_entry *ce;
 +      const struct cache_entry *ce;
        int pos;
        unsigned namelen = strlen(filename);
        unsigned fullnamelen;
@@@ -1328,7 -1287,7 +1330,7 @@@ static int get_sha1_with_context_1(cons
         */
        if (name[0] == ':') {
                int stage = 0;
 -              struct cache_entry *ce;
 +              const struct cache_entry *ce;
                char *new_path = NULL;
                int pos;
                if (!only_to_die && namelen > 2 && name[1] == '/') {
diff --combined streaming.c
index efbc3babf157ab2c355c80eabeacea5cd6d77dab,acc07a6ff10cbd951945be56217e52b219661414..debe904523252ae4fd2e18784641575c3f828dd2
@@@ -111,11 -111,11 +111,11 @@@ static enum input_source istream_source
        unsigned long size;
        int status;
  
+       oi->typep = type;
        oi->sizep = &size;
        status = sha1_object_info_extended(sha1, oi);
        if (status < 0)
                return stream_error;
-       *type = status;
  
        switch (oi->whence) {
        case OI_LOOSE:
@@@ -135,7 -135,7 +135,7 @@@ struct git_istream *open_istream(const 
                                 struct stream_filter *filter)
  {
        struct git_istream *st;
-       struct object_info oi = {0};
+       struct object_info oi = {NULL};
        const unsigned char *real = lookup_replace_object(sha1);
        enum input_source src = istream_source(real, type, &oi);
  
                        return NULL;
                }
        }
 -      if (st && filter) {
 +      if (filter) {
                /* Add "&& !is_null_stream_filter(filter)" for performance */
                struct git_istream *nst = attach_stream_filter(st, filter);
                if (!nst)