Merge branch 'jk/commit-dates-parsing-fix'
authorJunio C Hamano <gitster@pobox.com>
Fri, 14 Mar 2014 21:25:44 +0000 (14:25 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 14 Mar 2014 21:25:44 +0000 (14:25 -0700)
Tighten codepaths that parse timestamps in commit objects.

* jk/commit-dates-parsing-fix:
show_ident_date: fix tz range check
log: do not segfault on gmtime errors
log: handle integer overflow in timestamps
date: check date overflow against time_t
fsck: report integer overflow in author timestamps
t4212: test bogus timestamps with git-log

1  2 
cache.h
date.c
pretty.c
t/t4212-log-corrupt.sh
diff --combined cache.h
index bb0097e929b1411458ea9194bf3d7c0c56fb2f5a,9a2c377b73f1097ff426c64cf3002c98856c6dee..5ce6dfeddb7ba356b7c469fcf86d00ecef83be9c
+++ b/cache.h
@@@ -3,7 -3,7 +3,7 @@@
  
  #include "git-compat-util.h"
  #include "strbuf.h"
 -#include "hash.h"
 +#include "hashmap.h"
  #include "advice.h"
  #include "gettext.h"
  #include "convert.h"
@@@ -34,7 -34,6 +34,7 @@@ int git_inflate(git_zstream *, int flus
  
  void git_deflate_init(git_zstream *, int level);
  void git_deflate_init_gzip(git_zstream *, int level);
 +void git_deflate_init_raw(git_zstream *, int level);
  void git_deflate_end(git_zstream *);
  int git_deflate_abort(git_zstream *);
  int git_deflate_end_gently(git_zstream *);
@@@ -101,9 -100,9 +101,9 @@@ unsigned long git_deflate_bound(git_zst
  
  #define CACHE_SIGNATURE 0x44495243    /* "DIRC" */
  struct cache_header {
 -      unsigned int hdr_signature;
 -      unsigned int hdr_version;
 -      unsigned int hdr_entries;
 +      uint32_t hdr_signature;
 +      uint32_t hdr_version;
 +      uint32_t hdr_entries;
  };
  
  #define INDEX_FORMAT_LB 2
   * check it for equality in the 32 bits we save.
   */
  struct cache_time {
 -      unsigned int sec;
 -      unsigned int nsec;
 +      uint32_t sec;
 +      uint32_t 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 hashmap_entry ent;
 +      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];
 -      struct cache_entry *next;
        char name[FLEX_ARRAY]; /* more */
  };
  
  #define CE_ADDED             (1 << 19)
  
  #define CE_HASHED            (1 << 20)
 -#define CE_UNHASHED          (1 << 21)
  #define CE_WT_REMOVE         (1 << 22) /* remove in work directory */
  #define CE_CONFLICTED        (1 << 23)
  
  #define CE_UNPACKED          (1 << 24)
  #define CE_NEW_SKIP_WORKTREE (1 << 25)
  
 +/* used to temporarily mark paths matched by pathspecs */
 +#define CE_MATCHED           (1 << 26)
 +
  /*
   * Extended on-disk flags
   */
  #error "CE_EXTENDED_FLAGS out of range"
  #endif
  
 +struct pathspec;
 +
  /*
   * Copy the sha1 and stat state of a cache entry from one to
   * 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;
 +      unsigned int state = dst->ce_flags & CE_HASHED;
  
        /* Don't copy hash chain and name */
 -      memcpy(dst, src, offsetof(struct cache_entry, next));
 +      memcpy(&dst->ce_stat_data, &src->ce_stat_data,
 +                      offsetof(struct cache_entry, name) -
 +                      offsetof(struct cache_entry, ce_stat_data));
  
        /* Restore the hash state */
 -      dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
 +      dst->ce_flags = (dst->ce_flags & ~CE_HASHED) | state;
  }
  
  static inline unsigned create_ce_flags(unsigned stage)
@@@ -229,8 -218,7 +229,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) &&
@@@ -277,8 -265,8 +277,8 @@@ struct index_state 
        struct cache_time timestamp;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
 -      struct hash_table name_hash;
 -      struct hash_table dir_hash;
 +      struct hashmap name_hash;
 +      struct hashmap dir_hash;
  };
  
  extern struct index_state the_index;
@@@ -314,13 -302,11 +314,13 @@@ extern void free_name_hash(struct index
  #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL)
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
 -#define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
 +#define cache_dir_exists(name, namelen) index_dir_exists(&the_index, (name), (namelen))
 +#define cache_file_exists(name, namelen, igncase) index_file_exists(&the_index, (name), (namelen), (igncase))
  #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
  #define resolve_undo_clear() resolve_undo_clear_index(&the_index)
  #define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at)
  #define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec)
 +#define read_blob_data_from_cache(path, sz) read_blob_data_from_index(&the_index, (path), (sz))
  #endif
  
  enum object_type {
@@@ -353,7 -339,6 +353,7 @@@ static inline enum object_type object_t
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
  #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
 +#define GIT_SHALLOW_FILE_ENVIRONMENT "GIT_SHALLOW_FILE"
  #define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
  #define CONFIG_ENVIRONMENT "GIT_CONFIG"
  #define CONFIG_DATA_ENVIRONMENT "GIT_CONFIG_PARAMETERS"
  #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
  #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
  #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
 +#define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
 +#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
 +#define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
  
  /*
   * This environment variable is expected to contain a boolean indicating
@@@ -398,6 -380,7 +398,6 @@@ extern int is_bare_repository(void)
  extern int is_inside_git_dir(void);
  extern char *git_work_tree_cfg;
  extern int is_inside_work_tree(void);
 -extern int have_git_dir(void);
  extern const char *get_git_dir(void);
  extern int is_git_directory(const char *path);
  extern char *get_object_directory(void);
@@@ -418,7 -401,6 +418,7 @@@ extern void setup_work_tree(void)
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
  extern char *prefix_path(const char *prefix, int len, const char *path);
 +extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path);
  extern const char *prefix_filename(const char *prefix, int len, const char *path);
  extern int check_filename(const char *prefix, const char *name);
  extern void verify_filename(const char *prefix,
@@@ -432,9 -414,6 +432,9 @@@ 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);
 +extern int daemonize(void);
 +
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
  
  /* Initialize and use the cache information */
  extern int read_index(struct index_state *);
 -extern int read_index_preload(struct index_state *, const char **pathspec);
 +extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
  extern int read_index_from(struct index_state *, const char *path);
  extern int is_index_unborn(struct index_state *);
  extern int read_index_unmerged(struct index_state *);
@@@ -465,8 -444,7 +465,8 @@@ extern int write_index(struct index_sta
  extern int discard_index(struct index_state *);
  extern int unmerged_index(const struct index_state *);
  extern int verify_path(const char *path);
 -extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase);
 +extern struct cache_entry *index_dir_exists(struct index_state *istate, const char *name, int namelen);
 +extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
  extern int index_name_pos(const struct index_state *, const char *name, int namelen);
  #define ADD_CACHE_OK_TO_ADD 1         /* Ok to add */
  #define ADD_CACHE_OK_TO_REPLACE 2     /* Ok to replace file/directory */
@@@ -485,11 -463,9 +485,11 @@@ extern int remove_file_from_index(struc
  #define ADD_CACHE_INTENT 16
  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 struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options);
 +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
 +extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
  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 *);
  
  /* do stat comparison even if CE_VALID is true */
  #define CE_MATCH_IGNORE_VALID         01
  #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);
 -
 -#define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
 -
 -struct pathspec {
 -      const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
 -      int nr;
 -      unsigned int has_wildcard:1;
 -      unsigned int recursive:1;
 -      int max_depth;
 -      struct pathspec_item {
 -              const char *match;
 -              int len;
 -              int nowildcard_len;
 -              int flags;
 -      } *items;
 -};
 -
 -extern int init_pathspec(struct pathspec *, const char **);
 -extern void free_pathspec(struct pathspec *);
 -extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
 -
 -extern int limit_pathspec_to_literal(void);
 +/* ignore non-existent files during stat update  */
 +#define CE_MATCH_IGNORE_MISSING               0x08
 +/* enable stat refresh */
 +#define CE_MATCH_REFRESH              0x10
 +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 HASH_WRITE_OBJECT 1
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
  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 */
  #define REFRESH_IGNORE_MISSING        0x0008  /* ignore non-existent */
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
 -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
 +extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
  
  struct lock_file {
        struct lock_file *next;
@@@ -568,7 -547,6 +568,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;
@@@ -737,30 -715,10 +737,30 @@@ enum sharedrepo 
        PERM_EVERYBODY      = 0664
  };
  int git_config_perm(const char *var, const char *value);
 -int set_shared_perm(const char *path, int mode);
 -#define adjust_shared_perm(path) set_shared_perm((path), 0)
 -int safe_create_leading_directories(char *path);
 -int safe_create_leading_directories_const(const char *path);
 +int adjust_shared_perm(const char *path);
 +
 +/*
 + * Create the directory containing the named path, using care to be
 + * somewhat safe against races.  Return one of the scld_error values
 + * to indicate success/failure.
 + *
 + * SCLD_VANISHED indicates that one of the ancestor directories of the
 + * path existed at one point during the function call and then
 + * suddenly vanished, probably because another process pruned the
 + * directory while we were working.  To be robust against this kind of
 + * race, callers might want to try invoking the function again when it
 + * returns SCLD_VANISHED.
 + */
 +enum scld_error {
 +      SCLD_OK = 0,
 +      SCLD_FAILED = -1,
 +      SCLD_PERMS = -2,
 +      SCLD_EXISTS = -3,
 +      SCLD_VANISHED = -4
 +};
 +enum scld_error safe_create_leading_directories(char *path);
 +enum scld_error safe_create_leading_directories_const(const char *path);
 +
  int mkdir_in_gitdir(const char *path);
  extern void home_config_paths(char **global, char **xdg, char *file);
  extern char *expand_user_path(const char *path);
@@@ -773,9 -731,7 +773,9 @@@ 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 *remove_leading_path(const char *in, const char *prefix);
 +const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
 +int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
  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);
@@@ -783,11 -739,11 +783,11 @@@ int daemon_avoid_alias(const char *path
  int offset_1st_component(const char *path);
  
  /* object replacement */
 -#define READ_SHA1_FILE_REPLACE 1
 +#define LOOKUP_REPLACE_OBJECT 1
  extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
  static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
  {
 -      return read_sha1_file_extended(sha1, type, size, READ_SHA1_FILE_REPLACE);
 +      return read_sha1_file_extended(sha1, type, size, LOOKUP_REPLACE_OBJECT);
  }
  extern const unsigned char *do_lookup_replace_object(const unsigned char *sha1);
  static inline const unsigned char *lookup_replace_object(const unsigned char *sha1)
                return sha1;
        return do_lookup_replace_object(sha1);
  }
 +static inline const unsigned char *lookup_replace_object_extended(const unsigned char *sha1, unsigned flag)
 +{
 +      if (!(flag & LOOKUP_REPLACE_OBJECT))
 +              return sha1;
 +      return lookup_replace_object(sha1);
 +}
  
  /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
  extern int sha1_object_info(const unsigned char *, unsigned long *);
@@@ -809,7 -759,6 +809,7 @@@ extern int hash_sha1_file(const void *b
  extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
  extern int force_object_loose(const unsigned char *sha1, time_t mtime);
 +extern int git_open_noatime(const char *name);
  extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
  extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
  extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
  /* 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);
@@@ -914,15 -866,12 +914,15 @@@ extern char *resolve_refdup(const char 
  
  extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
  extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 -extern int interpret_branch_name(const char *str, struct strbuf *);
 +extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
 -extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
 -extern const char *ref_rev_parse_rules[];
 -#define ref_fetch_rules ref_rev_parse_rules
 +/*
 + * Return true iff abbrev_name is a possible abbreviation for
 + * full_name according to the rules defined by ref_rev_parse_rules in
 + * refs.c.
 + */
 +extern int refname_match(const char *abbrev_name, const char *full_name);
  
  extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
  extern int validate_headref(const char *ref);
@@@ -955,12 -904,12 +955,13 @@@ 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 *);
  unsigned long approxidate_relative(const char *date, const struct timeval *now);
  enum date_mode parse_date_format(const char *format);
+ int date_overflows(unsigned long date);
  
  #define IDENT_STRICT         1
  #define IDENT_NO_DATE        2
@@@ -990,15 -939,6 +991,15 @@@ struct ident_split 
   */
  extern int split_ident_line(struct ident_split *, const char *, int);
  
 +/*
 + * Compare split idents for equality or strict ordering. Note that we
 + * compare only the ident part of the line, ignoring any timestamp.
 + *
 + * Because there are two fields, we must choose one as the primary key; we
 + * currently arbitrarily pick the email.
 + */
 +extern int ident_cmp(const struct ident_split *, const struct ident_split *);
 +
  struct checkout {
        const char *base_dir;
        int base_dir_len;
                 refresh_cache:1;
  };
  
 +#define TEMPORARY_FILENAME_LENGTH 25
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
  
  struct cache_def {
@@@ -1072,11 -1011,55 +1073,11 @@@ struct pack_entry 
        struct packed_git *p;
  };
  
 -struct ref {
 -      struct ref *next;
 -      unsigned char old_sha1[20];
 -      unsigned char new_sha1[20];
 -      char *symref;
 -      unsigned int
 -              force:1,
 -              forced_update:1,
 -              merge:1,
 -              deletion:1;
 -      enum {
 -              REF_STATUS_NONE = 0,
 -              REF_STATUS_OK,
 -              REF_STATUS_REJECT_NONFASTFORWARD,
 -              REF_STATUS_REJECT_ALREADY_EXISTS,
 -              REF_STATUS_REJECT_NODELETE,
 -              REF_STATUS_REJECT_FETCH_FIRST,
 -              REF_STATUS_REJECT_NEEDS_FORCE,
 -              REF_STATUS_UPTODATE,
 -              REF_STATUS_REMOTE_REJECT,
 -              REF_STATUS_EXPECTING_REPORT
 -      } status;
 -      char *remote_status;
 -      struct ref *peer_ref; /* when renaming */
 -      char name[FLEX_ARRAY]; /* more */
 -};
 -
 -#define REF_NORMAL    (1u << 0)
 -#define REF_HEADS     (1u << 1)
 -#define REF_TAGS      (1u << 2)
 -
 -extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 -
 -#define CONNECT_VERBOSE       (1u << 0)
 -extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
 -extern int finish_connect(struct child_process *conn);
 -extern int git_connection_is_socket(struct child_process *conn);
 -struct extra_have_objects {
 -      int nr, alloc;
 -      unsigned char (*array)[20];
 -};
 -extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
 -extern int server_supports(const char *feature);
 -extern int parse_feature_request(const char *features, const char *feature);
 -extern const char *server_feature_value(const char *feature, int *len_ret);
 -extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
 -
  extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
  
 +/* A hook for count-objects to report invalid files in pack directory */
 +extern void (*report_garbage)(const char *desc, const char *path);
 +
  extern void prepare_packed_git(void);
  extern void reprepare_packed_git(void);
  extern void install_packed_git(struct packed_git *pack);
@@@ -1104,10 -1087,7 +1105,10 @@@ extern int unpack_object_header(struct 
  
  struct object_info {
        /* Request */
 +      enum object_type *typep;
        unsigned long *sizep;
 +      unsigned long *disk_sizep;
 +      unsigned char *delta_base_sha1;
  
        /* Response */
        enum {
                } packed;
        } u;
  };
 -extern int sha1_object_info_extended(const unsigned char *, struct object_info *);
 +extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);
  
  /* Dumb servers support */
  extern int update_server_info(int);
  #define CONFIG_INVALID_PATTERN 6
  #define CONFIG_GENERIC_ERROR 7
  
 +struct git_config_source {
 +      unsigned int use_stdin:1;
 +      const char *file;
 +      const char *blob;
 +};
 +
  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);
 +                                 struct git_config_source *config_source,
 +                                 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 *);
 +extern int64_t git_config_int64(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
  extern int git_config_bool_or_int(const char *, const char *, int *);
  extern int git_config_bool(const char *, const char *);
@@@ -1275,8 -1245,6 +1276,8 @@@ __attribute__((format (printf, 2, 3))
  extern void trace_argv_printf(const char **argv, const char *format, ...);
  extern void trace_repo_setup(const char *prefix);
  extern int trace_want(const char *key);
 +__attribute__((format (printf, 2, 3)))
 +extern void trace_printf_key(const char *key, const char *fmt, ...);
  extern void trace_strbuf(const char *key, const struct strbuf *buf);
  
  void packet_trace_identity(const char *prog);
   * return 0 if success, 1 - if addition of a file failed and
   * ADD_FILES_IGNORE_ERRORS was specified in flags
   */
 -int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
 +int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
  
  /* diff.c */
  extern int diff_auto_refresh_index;
@@@ -1320,7 -1288,7 +1321,7 @@@ extern int ws_blank_line(const char *li
  #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
 -int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix);
 +int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
  char *alias_lookup(const char *alias);
@@@ -1347,31 -1315,4 +1348,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 date.c
index 83b4166344b31ea615603acaeb95a873f2c05077,f64bbeb8237acceaca565c010fdf50b3385ead8f..e1a2cee5688a64555d7820e8e02e3e86839c9641
--- 1/date.c
--- 2/date.c
+++ b/date.c
@@@ -184,8 -184,10 +184,10 @@@ const char *show_date(unsigned long tim
                tz = local_tzoffset(time);
  
        tm = time_to_tm(time, tz);
-       if (!tm)
-               return NULL;
+       if (!tm) {
+               tm = time_to_tm(0, 0);
+               tz = 0;
+       }
  
        strbuf_reset(&timebuf);
        if (mode == DATE_SHORT)
@@@ -383,7 -385,7 +385,7 @@@ static int is_date(int year, int month
                 * sense to specify timestamp way into the future.  Make
                 * sure it is not later than ten days from now...
                 */
 -              if (now + 10*24*3600 < specified)
 +              if ((specified != -1) && (now + 10*24*3600 < specified))
                        return 0;
                tm->tm_mon = r->tm_mon;
                tm->tm_mday = r->tm_mday;
@@@ -694,14 -696,8 +696,14 @@@ int parse_date_basic(const char *date, 
  
        /* mktime uses local timezone */
        *timestamp = tm_to_time_t(&tm);
 -      if (*offset == -1)
 -              *offset = ((time_t)*timestamp - mktime(&tm)) / 60;
 +      if (*offset == -1) {
 +              time_t temp_time = mktime(&tm);
 +              if ((time_t)*timestamp > temp_time) {
 +                      *offset = ((time_t)*timestamp - temp_time) / 60;
 +              } else {
 +                      *offset = -(int)((temp_time - (time_t)*timestamp) / 60);
 +              }
 +      }
  
        if (*timestamp == -1)
                return -1;
        return 0; /* success */
  }
  
 +int parse_expiry_date(const char *date, unsigned long *timestamp)
 +{
 +      int errors = 0;
 +
 +      if (!strcmp(date, "never") || !strcmp(date, "false"))
 +              *timestamp = 0;
 +      else if (!strcmp(date, "all") || !strcmp(date, "now"))
 +              /*
 +               * We take over "now" here, which usually translates
 +               * to the current timestamp.  This is because the user
 +               * really means to expire everything she has done in
 +               * the past, and by definition reflogs are the record
 +               * of the past, and there is nothing from the future
 +               * to be kept.
 +               */
 +              *timestamp = ULONG_MAX;
 +      else
 +              *timestamp = approxidate_careful(date, &errors);
 +
 +      return errors;
 +}
 +
  int parse_date(const char *date, char *result, int maxlen)
  {
        unsigned long timestamp;
@@@ -907,7 -881,7 +909,7 @@@ static const char *approxidate_alpha(co
        const char *end = date;
        int i;
  
 -      while (isalpha(*++end));
 +      while (isalpha(*++end))
                ;
  
        for (i = 0; i < 12; i++) {
@@@ -1113,3 -1087,20 +1115,20 @@@ unsigned long approxidate_careful(cons
        gettimeofday(&tv, NULL);
        return approxidate_str(date, &tv, error_ret);
  }
+ int date_overflows(unsigned long t)
+ {
+       time_t sys;
+       /* If we overflowed our unsigned long, that's bad... */
+       if (t == ULONG_MAX)
+               return 1;
+       /*
+        * ...but we also are going to feed the result to system
+        * functions that expect time_t, which is often "signed long".
+        * Make sure that we fit into time_t, as well.
+        */
+       sys = t;
+       return t != sys || (t < 1) != (sys < 1);
+ }
diff --combined pretty.c
index 87db08bd749e74fef0b2372ab71ea98baf57b322,4d4c1e95625e5758efc8945a67a8ca0776b219fc..6e266ddf2749ab1b2388f72e3a1101e98fa656cc
+++ b/pretty.c
@@@ -40,7 -40,7 +40,7 @@@ static int git_pretty_formats_config(co
        const char *fmt;
        int i;
  
 -      if (prefixcmp(var, "pretty."))
 +      if (!starts_with(var, "pretty."))
                return 0;
  
        name = var + strlen("pretty.");
@@@ -67,7 -67,7 +67,7 @@@
        commit_format->name = xstrdup(name);
        commit_format->format = CMIT_FMT_USERFORMAT;
        git_config_string(&fmt, var, value);
 -      if (!prefixcmp(fmt, "format:") || !prefixcmp(fmt, "tformat:")) {
 +      if (starts_with(fmt, "format:") || starts_with(fmt, "tformat:")) {
                commit_format->is_tformat = fmt[0] == 't';
                fmt = strchr(fmt, ':') + 1;
        } else if (strchr(fmt, '%'))
@@@ -115,7 -115,7 +115,7 @@@ static struct cmt_fmt_map *find_commit_
        for (i = 0; i < commit_formats_len; i++) {
                size_t match_len;
  
 -              if (prefixcmp(commit_formats[i].name, sought))
 +              if (!starts_with(commit_formats[i].name, sought))
                        continue;
  
                match_len = strlen(commit_formats[i].name);
@@@ -151,7 -151,7 +151,7 @@@ void get_commit_format(const char *arg
                rev->commit_format = CMIT_FMT_DEFAULT;
                return;
        }
 -      if (!prefixcmp(arg, "format:") || !prefixcmp(arg, "tformat:")) {
 +      if (starts_with(arg, "format:") || starts_with(arg, "tformat:")) {
                save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
                return;
        }
@@@ -397,20 -397,29 +397,26 @@@ static const char *show_ident_date(cons
                                   enum date_mode mode)
  {
        unsigned long date = 0;
-       int tz = 0;
+       long tz = 0;
  
        if (ident->date_begin && ident->date_end)
                date = strtoul(ident->date_begin, NULL, 10);
-       if (ident->tz_begin && ident->tz_end)
-               tz = strtol(ident->tz_begin, NULL, 10);
+       if (date_overflows(date))
+               date = 0;
+       else {
+               if (ident->tz_begin && ident->tz_end)
+                       tz = strtol(ident->tz_begin, NULL, 10);
+               if (tz >= INT_MAX || tz <= INT_MIN)
+                       tz = 0;
+       }
        return show_date(date, tz, mode);
  }
  
 -void pp_user_info(const struct pretty_print_context *pp,
 +void pp_user_info(struct pretty_print_context *pp,
                  const char *what, struct strbuf *sb,
                  const char *line, const char *encoding)
  {
 -      struct strbuf name;
 -      struct strbuf mail;
        struct ident_split ident;
 -      int linelen;
        char *line_end;
        const char *mailbuf, *namebuf;
        size_t namelen, maillen;
        if (pp->fmt == CMIT_FMT_ONELINE)
                return;
  
 -      line_end = strchr(line, '\n');
 -      if (!line_end) {
 -              line_end = strchr(line, '\0');
 -              if (!line_end)
 -                      return;
 -      }
 -
 -      linelen = ++line_end - line;
 -      if (split_ident_line(&ident, line, linelen))
 +      line_end = strchrnul(line, '\n');
 +      if (split_ident_line(&ident, line, line_end - line))
                return;
  
 -
        mailbuf = ident.mail_begin;
        maillen = ident.mail_end - ident.mail_begin;
        namebuf = ident.name_begin;
        if (pp->mailmap)
                map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
  
 -      strbuf_init(&mail, 0);
 -      strbuf_init(&name, 0);
 -
 -      strbuf_add(&mail, mailbuf, maillen);
 -      strbuf_add(&name, namebuf, namelen);
 -
 -      namelen = name.len + mail.len + 3; /* ' ' + '<' + '>' */
 -
        if (pp->fmt == CMIT_FMT_EMAIL) {
 +              if (pp->from_ident && ident_cmp(pp->from_ident, &ident)) {
 +                      struct strbuf buf = STRBUF_INIT;
 +
 +                      strbuf_addstr(&buf, "From: ");
 +                      strbuf_add(&buf, namebuf, namelen);
 +                      strbuf_addstr(&buf, " <");
 +                      strbuf_add(&buf, mailbuf, maillen);
 +                      strbuf_addstr(&buf, ">\n");
 +                      string_list_append(&pp->in_body_headers,
 +                                         strbuf_detach(&buf, NULL));
 +
 +                      mailbuf = pp->from_ident->mail_begin;
 +                      maillen = pp->from_ident->mail_end - mailbuf;
 +                      namebuf = pp->from_ident->name_begin;
 +                      namelen = pp->from_ident->name_end - namebuf;
 +              }
 +
                strbuf_addstr(sb, "From: ");
 -              if (needs_rfc2047_encoding(name.buf, name.len, RFC2047_ADDRESS)) {
 -                      add_rfc2047(sb, name.buf, name.len,
 +              if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) {
 +                      add_rfc2047(sb, namebuf, namelen,
                                    encoding, RFC2047_ADDRESS);
                        max_length = 76; /* per rfc2047 */
 -              } else if (needs_rfc822_quoting(name.buf, name.len)) {
 +              } else if (needs_rfc822_quoting(namebuf, namelen)) {
                        struct strbuf quoted = STRBUF_INIT;
 -                      add_rfc822_quoted(&quoted, name.buf, name.len);
 +                      add_rfc822_quoted(&quoted, namebuf, namelen);
                        strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len,
                                                        -6, 1, max_length);
                        strbuf_release(&quoted);
                } else {
 -                      strbuf_add_wrapped_bytes(sb, name.buf, name.len,
 +                      strbuf_add_wrapped_bytes(sb, namebuf, namelen,
                                                 -6, 1, max_length);
                }
 -              if (namelen - name.len + last_line_length(sb) > max_length)
 -                      strbuf_addch(sb, '\n');
  
 -              strbuf_addf(sb, " <%s>\n", mail.buf);
 +              if (max_length <
 +                  last_line_length(sb) + strlen(" <") + maillen + strlen(">"))
 +                      strbuf_addch(sb, '\n');
 +              strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf);
        } else {
 -              strbuf_addf(sb, "%s: %.*s%s <%s>\n", what,
 -                            (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0,
 -                            "    ", name.buf, mail.buf);
 +              strbuf_addf(sb, "%s: %.*s%.*s <%.*s>\n", what,
 +                          (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, "    ",
 +                          (int)namelen, namebuf, (int)maillen, mailbuf);
        }
  
 -      strbuf_release(&mail);
 -      strbuf_release(&name);
 -
        switch (pp->fmt) {
        case CMIT_FMT_MEDIUM:
                strbuf_addf(sb, "Date:   %s\n",
  static int is_empty_line(const char *line, int *len_p)
  {
        int len = *len_p;
 -      while (len && isspace(line[len-1]))
 +      while (len && isspace(line[len - 1]))
                len--;
        *len_p = len;
        return !len;
@@@ -602,7 -612,6 +608,7 @@@ static char *replace_encoding_header(ch
  }
  
  char *logmsg_reencode(const struct commit *commit,
 +                    char **commit_encoding,
                      const char *output_encoding)
  {
        static const char *utf8 = "UTF-8";
                            sha1_to_hex(commit->object.sha1), typename(type));
        }
  
 -      if (!output_encoding || !*output_encoding)
 +      if (!output_encoding || !*output_encoding) {
 +              if (commit_encoding)
 +                      *commit_encoding =
 +                              get_header(commit, msg, "encoding");
                return msg;
 +      }
        encoding = get_header(commit, msg, "encoding");
 +      if (commit_encoding)
 +              *commit_encoding = encoding;
        use_encoding = encoding ? encoding : utf8;
        if (same_encoding(use_encoding, output_encoding)) {
                /*
        if (out)
                out = replace_encoding_header(out, output_encoding);
  
 -      free(encoding);
 +      if (!commit_encoding)
 +              free(encoding);
        /*
         * If the re-encoding failed, out might be NULL here; in that
         * case we just return the commit message verbatim.
@@@ -768,38 -770,26 +774,38 @@@ struct chunk 
        size_t len;
  };
  
 +enum flush_type {
 +      no_flush,
 +      flush_right,
 +      flush_left,
 +      flush_left_and_steal,
 +      flush_both
 +};
 +
 +enum trunc_type {
 +      trunc_none,
 +      trunc_left,
 +      trunc_middle,
 +      trunc_right
 +};
 +
  struct format_commit_context {
        const struct commit *commit;
        const struct pretty_print_context *pretty_ctx;
        unsigned commit_header_parsed:1;
        unsigned commit_message_parsed:1;
 -      unsigned commit_signature_parsed:1;
 -      struct {
 -              char *gpg_output;
 -              char *gpg_status;
 -              char good_bad;
 -              char *signer;
 -              char *key;
 -      } signature;
 +      struct signature_check signature_check;
 +      enum flush_type flush_type;
 +      enum trunc_type truncate;
        char *message;
 +      char *commit_encoding;
        size_t width, indent1, indent2;
 +      int auto_color;
 +      int padding;
  
        /* These offsets are relative to the start of the commit message. */
        struct chunk author;
        struct chunk committer;
 -      struct chunk encoding;
        size_t message_off;
        size_t subject_off;
        size_t body_off;
@@@ -840,12 -830,15 +846,12 @@@ static void parse_commit_header(struct 
  
                if (i == eol) {
                        break;
 -              } else if (!prefixcmp(msg + i, "author ")) {
 +              } else if (starts_with(msg + i, "author ")) {
                        context->author.off = i + 7;
                        context->author.len = eol - i - 7;
 -              } else if (!prefixcmp(msg + i, "committer ")) {
 +              } else if (starts_with(msg + i, "committer ")) {
                        context->committer.off = i + 10;
                        context->committer.len = eol - i - 10;
 -              } else if (!prefixcmp(msg + i, "encoding ")) {
 -                      context->encoding.off = i + 9;
 -                      context->encoding.len = eol - i - 9;
                }
                i = eol;
        }
@@@ -926,6 -919,23 +932,6 @@@ static void parse_commit_message(struc
        c->commit_message_parsed = 1;
  }
  
 -static void format_decoration(struct strbuf *sb, const struct commit *commit)
 -{
 -      struct name_decoration *d;
 -      const char *prefix = " (";
 -
 -      load_ref_decorations(DECORATE_SHORT_REFS);
 -      d = lookup_decoration(&name_decoration, &commit->object);
 -      while (d) {
 -              strbuf_addstr(sb, prefix);
 -              prefix = ", ";
 -              strbuf_addstr(sb, d->name);
 -              d = d->next;
 -      }
 -      if (prefix[0] == ',')
 -              strbuf_addch(sb, ')');
 -}
 -
  static void strbuf_wrap(struct strbuf *sb, size_t pos,
                        size_t width, size_t indent1, size_t indent2)
  {
@@@ -955,6 -965,64 +961,6 @@@ static void rewrap_message_tail(struct 
        c->indent2 = new_indent2;
  }
  
 -static struct {
 -      char result;
 -      const char *check;
 -} signature_check[] = {
 -      { 'G', "\n[GNUPG:] GOODSIG " },
 -      { 'B', "\n[GNUPG:] BADSIG " },
 -};
 -
 -static void parse_signature_lines(struct format_commit_context *ctx)
 -{
 -      const char *buf = ctx->signature.gpg_status;
 -      int i;
 -
 -      for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
 -              const char *found = strstr(buf, signature_check[i].check);
 -              const char *next;
 -              if (!found)
 -                      continue;
 -              ctx->signature.good_bad = signature_check[i].result;
 -              found += strlen(signature_check[i].check);
 -              ctx->signature.key = xmemdupz(found, 16);
 -              found += 17;
 -              next = strchrnul(found, '\n');
 -              ctx->signature.signer = xmemdupz(found, next - found);
 -              break;
 -      }
 -}
 -
 -static void parse_commit_signature(struct format_commit_context *ctx)
 -{
 -      struct strbuf payload = STRBUF_INIT;
 -      struct strbuf signature = STRBUF_INIT;
 -      struct strbuf gpg_output = STRBUF_INIT;
 -      struct strbuf gpg_status = STRBUF_INIT;
 -      int status;
 -
 -      ctx->commit_signature_parsed = 1;
 -
 -      if (parse_signed_commit(ctx->commit->object.sha1,
 -                              &payload, &signature) <= 0)
 -              goto out;
 -      status = verify_signed_buffer(payload.buf, payload.len,
 -                                    signature.buf, signature.len,
 -                                    &gpg_output, &gpg_status);
 -      if (status && !gpg_output.len)
 -              goto out;
 -      ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
 -      ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL);
 -      parse_signature_lines(ctx);
 -
 - out:
 -      strbuf_release(&gpg_status);
 -      strbuf_release(&gpg_output);
 -      strbuf_release(&payload);
 -      strbuf_release(&signature);
 -}
 -
 -
  static int format_reflog_person(struct strbuf *sb,
                                char part,
                                struct reflog_walk_info *log,
        return format_person_part(sb, part, ident, strlen(ident), dmode);
  }
  
 -static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 +static size_t parse_color(struct strbuf *sb, /* in UTF-8 */
 +                        const char *placeholder,
 +                        struct format_commit_context *c)
 +{
 +      if (placeholder[1] == '(') {
 +              const char *begin = placeholder + 2;
 +              const char *end = strchr(begin, ')');
 +              char color[COLOR_MAXLEN];
 +
 +              if (!end)
 +                      return 0;
 +              if (starts_with(begin, "auto,")) {
 +                      if (!want_color(c->pretty_ctx->color))
 +                              return end - placeholder + 1;
 +                      begin += 5;
 +              }
 +              color_parse_mem(begin,
 +                              end - begin,
 +                              "--pretty format", color);
 +              strbuf_addstr(sb, color);
 +              return end - placeholder + 1;
 +      }
 +      if (starts_with(placeholder + 1, "red")) {
 +              strbuf_addstr(sb, GIT_COLOR_RED);
 +              return 4;
 +      } else if (starts_with(placeholder + 1, "green")) {
 +              strbuf_addstr(sb, GIT_COLOR_GREEN);
 +              return 6;
 +      } else if (starts_with(placeholder + 1, "blue")) {
 +              strbuf_addstr(sb, GIT_COLOR_BLUE);
 +              return 5;
 +      } else if (starts_with(placeholder + 1, "reset")) {
 +              strbuf_addstr(sb, GIT_COLOR_RESET);
 +              return 6;
 +      } else
 +              return 0;
 +}
 +
 +static size_t parse_padding_placeholder(struct strbuf *sb,
 +                                      const char *placeholder,
 +                                      struct format_commit_context *c)
 +{
 +      const char *ch = placeholder;
 +      enum flush_type flush_type;
 +      int to_column = 0;
 +
 +      switch (*ch++) {
 +      case '<':
 +              flush_type = flush_right;
 +              break;
 +      case '>':
 +              if (*ch == '<') {
 +                      flush_type = flush_both;
 +                      ch++;
 +              } else if (*ch == '>') {
 +                      flush_type = flush_left_and_steal;
 +                      ch++;
 +              } else
 +                      flush_type = flush_left;
 +              break;
 +      default:
 +              return 0;
 +      }
 +
 +      /* the next value means "wide enough to that column" */
 +      if (*ch == '|') {
 +              to_column = 1;
 +              ch++;
 +      }
 +
 +      if (*ch == '(') {
 +              const char *start = ch + 1;
 +              const char *end = start + strcspn(start, ",)");
 +              char *next;
 +              int width;
 +              if (!end || end == start)
 +                      return 0;
 +              width = strtoul(start, &next, 10);
 +              if (next == start || width == 0)
 +                      return 0;
 +              c->padding = to_column ? -width : width;
 +              c->flush_type = flush_type;
 +
 +              if (*end == ',') {
 +                      start = end + 1;
 +                      end = strchr(start, ')');
 +                      if (!end || end == start)
 +                              return 0;
 +                      if (starts_with(start, "trunc)"))
 +                              c->truncate = trunc_right;
 +                      else if (starts_with(start, "ltrunc)"))
 +                              c->truncate = trunc_left;
 +                      else if (starts_with(start, "mtrunc)"))
 +                              c->truncate = trunc_middle;
 +                      else
 +                              return 0;
 +              } else
 +                      c->truncate = trunc_none;
 +
 +              return end - placeholder + 1;
 +      }
 +      return 0;
 +}
 +
 +static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 +                              const char *placeholder,
                                void *context)
  {
        struct format_commit_context *c = context;
        /* these are independent of the commit */
        switch (placeholder[0]) {
        case 'C':
 -              if (placeholder[1] == '(') {
 -                      const char *begin = placeholder + 2;
 -                      const char *end = strchr(begin, ')');
 -                      char color[COLOR_MAXLEN];
 -
 -                      if (!end)
 -                              return 0;
 -                      if (!prefixcmp(begin, "auto,")) {
 -                              if (!want_color(c->pretty_ctx->color))
 -                                      return end - placeholder + 1;
 -                              begin += 5;
 -                      }
 -                      color_parse_mem(begin,
 -                                      end - begin,
 -                                      "--pretty format", color);
 -                      strbuf_addstr(sb, color);
 -                      return end - placeholder + 1;
 +              if (starts_with(placeholder + 1, "(auto)")) {
 +                      c->auto_color = 1;
 +                      return 7; /* consumed 7 bytes, "C(auto)" */
 +              } else {
 +                      int ret = parse_color(sb, placeholder, c);
 +                      if (ret)
 +                              c->auto_color = 0;
 +                      /*
 +                       * Otherwise, we decided to treat %C<unknown>
 +                       * as a literal string, and the previous
 +                       * %C(auto) is still valid.
 +                       */
 +                      return ret;
                }
 -              if (!prefixcmp(placeholder + 1, "red")) {
 -                      strbuf_addstr(sb, GIT_COLOR_RED);
 -                      return 4;
 -              } else if (!prefixcmp(placeholder + 1, "green")) {
 -                      strbuf_addstr(sb, GIT_COLOR_GREEN);
 -                      return 6;
 -              } else if (!prefixcmp(placeholder + 1, "blue")) {
 -                      strbuf_addstr(sb, GIT_COLOR_BLUE);
 -                      return 5;
 -              } else if (!prefixcmp(placeholder + 1, "reset")) {
 -                      strbuf_addstr(sb, GIT_COLOR_RESET);
 -                      return 6;
 -              } else
 -                      return 0;
        case 'n':               /* newline */
                strbuf_addch(sb, '\n');
                return 1;
                        return end - placeholder + 1;
                } else
                        return 0;
 +
 +      case '<':
 +      case '>':
 +              return parse_padding_placeholder(sb, placeholder, c);
        }
  
        /* these depend on the commit */
  
        switch (placeholder[0]) {
        case 'H':               /* commit hash */
 +              strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
                strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
 +              strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
                return 1;
        case 'h':               /* abbreviated commit hash */
 -              if (add_again(sb, &c->abbrev_commit_hash))
 +              strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
 +              if (add_again(sb, &c->abbrev_commit_hash)) {
 +                      strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
                        return 1;
 +              }
                strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
                                                     c->pretty_ctx->abbrev));
 +              strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
                c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
                return 1;
        case 'T':               /* tree hash */
                strbuf_addstr(sb, get_revision_mark(NULL, commit));
                return 1;
        case 'd':
 -              format_decoration(sb, commit);
 +              load_ref_decorations(DECORATE_SHORT_REFS);
 +              format_decorations(sb, commit, c->auto_color);
                return 1;
        case 'g':               /* reflog info */
                switch(placeholder[1]) {
        }
  
        if (placeholder[0] == 'G') {
 -              if (!c->commit_signature_parsed)
 -                      parse_commit_signature(c);
 +              if (!c->signature_check.result)
 +                      check_commit_signature(c->commit, &(c->signature_check));
                switch (placeholder[1]) {
                case 'G':
 -                      if (c->signature.gpg_output)
 -                              strbuf_addstr(sb, c->signature.gpg_output);
 +                      if (c->signature_check.gpg_output)
 +                              strbuf_addstr(sb, c->signature_check.gpg_output);
                        break;
                case '?':
 -                      switch (c->signature.good_bad) {
 +                      switch (c->signature_check.result) {
                        case 'G':
                        case 'B':
 -                              strbuf_addch(sb, c->signature.good_bad);
 +                      case 'U':
 +                      case 'N':
 +                              strbuf_addch(sb, c->signature_check.result);
                        }
                        break;
                case 'S':
 -                      if (c->signature.signer)
 -                              strbuf_addstr(sb, c->signature.signer);
 +                      if (c->signature_check.signer)
 +                              strbuf_addstr(sb, c->signature_check.signer);
                        break;
                case 'K':
 -                      if (c->signature.key)
 -                              strbuf_addstr(sb, c->signature.key);
 +                      if (c->signature_check.key)
 +                              strbuf_addstr(sb, c->signature_check.key);
                        break;
                }
                return 2;
                                   msg + c->committer.off, c->committer.len,
                                   c->pretty_ctx->date_mode);
        case 'e':       /* encoding */
 -              strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
 +              if (c->commit_encoding)
 +                      strbuf_addstr(sb, c->commit_encoding);
                return 1;
        case 'B':       /* raw body */
                /* message_off is always left at the initial newline */
        return 0;       /* unknown placeholder */
  }
  
 -static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
 +static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
 +                                  const char *placeholder,
 +                                  struct format_commit_context *c)
 +{
 +      struct strbuf local_sb = STRBUF_INIT;
 +      int total_consumed = 0, len, padding = c->padding;
 +      if (padding < 0) {
 +              const char *start = strrchr(sb->buf, '\n');
 +              int occupied;
 +              if (!start)
 +                      start = sb->buf;
 +              occupied = utf8_strnwidth(start, -1, 1);
 +              padding = (-padding) - occupied;
 +      }
 +      while (1) {
 +              int modifier = *placeholder == 'C';
 +              int consumed = format_commit_one(&local_sb, placeholder, c);
 +              total_consumed += consumed;
 +
 +              if (!modifier)
 +                      break;
 +
 +              placeholder += consumed;
 +              if (*placeholder != '%')
 +                      break;
 +              placeholder++;
 +              total_consumed++;
 +      }
 +      len = utf8_strnwidth(local_sb.buf, -1, 1);
 +
 +      if (c->flush_type == flush_left_and_steal) {
 +              const char *ch = sb->buf + sb->len - 1;
 +              while (len > padding && ch > sb->buf) {
 +                      const char *p;
 +                      if (*ch == ' ') {
 +                              ch--;
 +                              padding++;
 +                              continue;
 +                      }
 +                      /* check for trailing ansi sequences */
 +                      if (*ch != 'm')
 +                              break;
 +                      p = ch - 1;
 +                      while (ch - p < 10 && *p != '\033')
 +                              p--;
 +                      if (*p != '\033' ||
 +                          ch + 1 - p != display_mode_esc_sequence_len(p))
 +                              break;
 +                      /*
 +                       * got a good ansi sequence, put it back to
 +                       * local_sb as we're cutting sb
 +                       */
 +                      strbuf_insert(&local_sb, 0, p, ch + 1 - p);
 +                      ch = p - 1;
 +              }
 +              strbuf_setlen(sb, ch + 1 - sb->buf);
 +              c->flush_type = flush_left;
 +      }
 +
 +      if (len > padding) {
 +              switch (c->truncate) {
 +              case trunc_left:
 +                      strbuf_utf8_replace(&local_sb,
 +                                          0, len - (padding - 2),
 +                                          "..");
 +                      break;
 +              case trunc_middle:
 +                      strbuf_utf8_replace(&local_sb,
 +                                          padding / 2 - 1,
 +                                          len - (padding - 2),
 +                                          "..");
 +                      break;
 +              case trunc_right:
 +                      strbuf_utf8_replace(&local_sb,
 +                                          padding - 2, len - (padding - 2),
 +                                          "..");
 +                      break;
 +              case trunc_none:
 +                      break;
 +              }
 +              strbuf_addstr(sb, local_sb.buf);
 +      } else {
 +              int sb_len = sb->len, offset = 0;
 +              if (c->flush_type == flush_left)
 +                      offset = padding - len;
 +              else if (c->flush_type == flush_both)
 +                      offset = (padding - len) / 2;
 +              /*
 +               * we calculate padding in columns, now
 +               * convert it back to chars
 +               */
 +              padding = padding - len + local_sb.len;
 +              strbuf_grow(sb, padding);
 +              strbuf_setlen(sb, sb_len + padding);
 +              memset(sb->buf + sb_len, ' ', sb->len - sb_len);
 +              memcpy(sb->buf + sb_len + offset, local_sb.buf,
 +                     local_sb.len);
 +      }
 +      strbuf_release(&local_sb);
 +      c->flush_type = no_flush;
 +      return total_consumed;
 +}
 +
 +static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
 +                               const char *placeholder,
                                 void *context)
  {
        int consumed;
                placeholder++;
  
        orig_len = sb->len;
 -      consumed = format_commit_one(sb, placeholder, context);
 +      if (((struct format_commit_context *)context)->flush_type != no_flush)
 +              consumed = format_and_pad_commit(sb, placeholder, context);
 +      else
 +              consumed = format_commit_one(sb, placeholder, context);
        if (magic == NO_MAGIC)
                return consumed;
  
@@@ -1495,43 -1355,22 +1501,43 @@@ void format_commit_message(const struc
  {
        struct format_commit_context context;
        const char *output_enc = pretty_ctx->output_encoding;
 +      const char *utf8 = "UTF-8";
  
        memset(&context, 0, sizeof(context));
        context.commit = commit;
        context.pretty_ctx = pretty_ctx;
        context.wrap_start = sb->len;
 -      context.message = logmsg_reencode(commit, output_enc);
 +      context.message = logmsg_reencode(commit,
 +                                        &context.commit_encoding,
 +                                        output_enc);
  
        strbuf_expand(sb, format, format_commit_item, &context);
        rewrap_message_tail(sb, &context, 0, 0, 0);
  
 +      if (output_enc) {
 +              if (same_encoding(utf8, output_enc))
 +                      output_enc = NULL;
 +      } else {
 +              if (context.commit_encoding &&
 +                  !same_encoding(context.commit_encoding, utf8))
 +                      output_enc = context.commit_encoding;
 +      }
 +
 +      if (output_enc) {
 +              int outsz;
 +              char *out = reencode_string_len(sb->buf, sb->len,
 +                                              output_enc, utf8, &outsz);
 +              if (out)
 +                      strbuf_attach(sb, out, outsz, outsz + 1);
 +      }
 +
 +      free(context.commit_encoding);
        logmsg_free(context.message, commit);
 -      free(context.signature.gpg_output);
 -      free(context.signature.signer);
 +      free(context.signature_check.gpg_output);
 +      free(context.signature_check.signer);
  }
  
 -static void pp_header(const struct pretty_print_context *pp,
 +static void pp_header(struct pretty_print_context *pp,
                      const char *encoding,
                      const struct commit *commit,
                      const char **msg_p,
                        continue;
                }
  
 -              if (!prefixcmp(line, "parent ")) {
 +              if (starts_with(line, "parent ")) {
                        if (linelen != 48)
                                die("bad parent line in commit");
                        continue;
                 * FULL shows both authors but not dates.
                 * FULLER shows both authors and dates.
                 */
 -              if (!prefixcmp(line, "author ")) {
 +              if (starts_with(line, "author ")) {
                        strbuf_grow(sb, linelen + 80);
                        pp_user_info(pp, "Author", sb, line + 7, encoding);
                }
 -              if (!prefixcmp(line, "committer ") &&
 +              if (starts_with(line, "committer ") &&
                    (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
                        pp_user_info(pp, "Commit", sb, line + 10, encoding);
        }
  }
  
 -void pp_title_line(const struct pretty_print_context *pp,
 +void pp_title_line(struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
                   const char *encoding,
        }
        strbuf_addch(sb, '\n');
  
 +      if (need_8bit_cte == 0) {
 +              int i;
 +              for (i = 0; i < pp->in_body_headers.nr; i++) {
 +                      if (has_non_ascii(pp->in_body_headers.items[i].string)) {
 +                              need_8bit_cte = 1;
 +                              break;
 +                      }
 +              }
 +      }
 +
        if (need_8bit_cte > 0) {
                const char *header_fmt =
                        "MIME-Version: 1.0\n"
        if (pp->fmt == CMIT_FMT_EMAIL) {
                strbuf_addch(sb, '\n');
        }
 +
 +      if (pp->in_body_headers.nr) {
 +              int i;
 +              for (i = 0; i < pp->in_body_headers.nr; i++) {
 +                      strbuf_addstr(sb, pp->in_body_headers.items[i].string);
 +                      free(pp->in_body_headers.items[i].string);
 +              }
 +              string_list_clear(&pp->in_body_headers, 0);
 +              strbuf_addch(sb, '\n');
 +      }
 +
        strbuf_release(&title);
  }
  
 -void pp_remainder(const struct pretty_print_context *pp,
 +void pp_remainder(struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent)
        }
  }
  
 -void pretty_print_commit(const struct pretty_print_context *pp,
 +void pretty_print_commit(struct pretty_print_context *pp,
                         const struct commit *commit,
                         struct strbuf *sb)
  {
        }
  
        encoding = get_log_output_encoding();
 -      msg = reencoded = logmsg_reencode(commit, encoding);
 +      msg = reencoded = logmsg_reencode(commit, NULL, encoding);
  
        if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
                indent = 0;
diff --combined t/t4212-log-corrupt.sh
index 93c7c366cfeffa46cc72665f6ce676bd37c3c2fe,85c6df4ec9834d9d3ad6c6b98dc9dfe952f906f5..3fa171541a161e0a5549c316b7c85608eb2d93d6
@@@ -13,16 -13,11 +13,16 @@@ test_expect_success 'setup' 
        git update-ref refs/heads/broken_email $(cat broken_email.hash)
  '
  
 +test_expect_success 'fsck notices broken commit' '
 +      git fsck 2>actual &&
 +      test_i18ngrep invalid.author actual
 +'
 +
  test_expect_success 'git log with broken author email' '
        {
                echo commit $(cat broken_email.hash)
                echo "Author: A U Thor <author@example.com>"
 -              echo "Date:   Thu Jan 1 00:00:00 1970 +0000"
 +              echo "Date:   Thu Apr 7 15:13:13 2005 -0700"
                echo
                echo "    foo"
        } >expect.out &&
@@@ -35,7 -30,7 +35,7 @@@
  '
  
  test_expect_success 'git log --format with broken author email' '
 -      echo "A U Thor+author@example.com+" >expect.out &&
 +      echo "A U Thor+author@example.com+Thu Apr 7 15:13:13 2005 -0700" >expect.out &&
        : >expect.err &&
  
        git log --format="%an+%ae+%ad" broken_email >actual.out 2>actual.err &&
        test_cmp expect.err actual.err
  '
  
+ munge_author_date () {
+       git cat-file commit "$1" >commit.orig &&
+       sed "s/^\(author .*>\) [0-9]*/\1 $2/" <commit.orig >commit.munge &&
+       git hash-object -w -t commit commit.munge
+ }
+ test_expect_success 'unparsable dates produce sentinel value' '
+       commit=$(munge_author_date HEAD totally_bogus) &&
+       echo "Date:   Thu Jan 1 00:00:00 1970 +0000" >expect &&
+       git log -1 $commit >actual.full &&
+       grep Date <actual.full >actual &&
+       test_cmp expect actual
+ '
+ test_expect_success 'unparsable dates produce sentinel value (%ad)' '
+       commit=$(munge_author_date HEAD totally_bogus) &&
+       echo >expect &&
+       git log -1 --format=%ad $commit >actual
+       test_cmp expect actual
+ '
+ # date is 2^64 + 1
+ test_expect_success 'date parser recognizes integer overflow' '
+       commit=$(munge_author_date HEAD 18446744073709551617) &&
+       echo "Thu Jan 1 00:00:00 1970 +0000" >expect &&
+       git log -1 --format=%ad $commit >actual &&
+       test_cmp expect actual
+ '
+ # date is 2^64 - 2
+ test_expect_success 'date parser recognizes time_t overflow' '
+       commit=$(munge_author_date HEAD 18446744073709551614) &&
+       echo "Thu Jan 1 00:00:00 1970 +0000" >expect &&
+       git log -1 --format=%ad $commit >actual &&
+       test_cmp expect actual
+ '
+ # date is within 2^63-1, but enough to choke glibc's gmtime
+ test_expect_success 'absurdly far-in-future dates produce sentinel' '
+       commit=$(munge_author_date HEAD 999999999999999999) &&
+       echo "Thu Jan 1 00:00:00 1970 +0000" >expect &&
+       git log -1 --format=%ad $commit >actual &&
+       test_cmp expect actual
+ '
  test_done