Merge branch 'kb/perf-trace'
authorJunio C Hamano <gitster@pobox.com>
Tue, 22 Jul 2014 17:59:18 +0000 (10:59 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 22 Jul 2014 17:59:19 +0000 (10:59 -0700)
* kb/perf-trace:
api-trace.txt: add trace API documentation
progress: simplify performance measurement by using getnanotime()
wt-status: simplify performance measurement by using getnanotime()
git: add performance tracing for git's main() function to debug scripts
trace: add trace_performance facility to debug performance issues
trace: add high resolution timer function to debug performance issues
trace: add 'file:line' to all trace output
trace: move code around, in preparation to file:line output
trace: add current timestamp to all trace output
trace: disable additional trace output for unit tests
trace: add infrastructure to augment trace output with additional info
sha1_file: change GIT_TRACE_PACK_ACCESS logging to use trace API
Documentation/git.txt: improve documentation of 'GIT_TRACE*' variables
trace: improve trace performance
trace: remove redundant printf format attribute
trace: consistently name the format parameter
trace: move trace declarations from cache.h to new trace.h

1  2 
Documentation/git.txt
Makefile
builtin/receive-pack.c
cache.h
commit.h
config.mak.uname
git-compat-util.h
git.c
sha1_file.c
t/test-lib.sh
wt-status.c
diff --combined Documentation/git.txt
index d0ddfcb0aa151cc6530d9edae36d81e68a9bf37f,fcb8afa200698211b5c644a6827a31da2eae4b06..05857c96ff2598dc39a426300b52697049f25997
@@@ -43,11 -43,9 +43,11 @@@ unreleased) version of Git, that is ava
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
 -* link:v2.0.0/git.html[documentation for release 2.0]
 +* link:v2.0.2/git.html[documentation for release 2.0.2]
  
  * release notes for
 +  link:RelNotes/2.0.2.txt[2.0.2],
 +  link:RelNotes/2.0.1.txt[2.0.1],
    link:RelNotes/2.0.0.txt[2.0.0].
  
  * link:v1.9.4/git.html[documentation for release 1.9.4]
@@@ -906,31 -904,54 +906,54 @@@ for further details
        based on whether stdout appears to be redirected to a file or not.
  
  'GIT_TRACE'::
-       If this variable is set to "1", "2" or "true" (comparison
-       is case insensitive), Git will print `trace:` messages on
-       stderr telling about alias expansion, built-in command
-       execution and external command execution.
-       If this variable is set to an integer value greater than 1
-       and lower than 10 (strictly) then Git will interpret this
-       value as an open file descriptor and will try to write the
-       trace messages into this file descriptor.
-       Alternatively, if this variable is set to an absolute path
-       (starting with a '/' character), Git will interpret this
-       as a file path and will try to write the trace messages
-       into it.
+       Enables general trace messages, e.g. alias expansion, built-in
+       command execution and external command execution.
+ +
+ If this variable is set to "1", "2" or "true" (comparison
+ is case insensitive), trace messages will be printed to
+ stderr.
+ +
+ If the variable is set to an integer value greater than 2
+ and lower than 10 (strictly) then Git will interpret this
+ value as an open file descriptor and will try to write the
+ trace messages into this file descriptor.
+ +
+ Alternatively, if the variable is set to an absolute path
+ (starting with a '/' character), Git will interpret this
+ as a file path and will try to write the trace messages
+ into it.
+ +
+ Unsetting the variable, or setting it to empty, "0" or
+ "false" (case insensitive) disables trace messages.
  
  'GIT_TRACE_PACK_ACCESS'::
-       If this variable is set to a path, a file will be created at
-       the given path logging all accesses to any packs. For each
+       Enables trace messages for all accesses to any packs. For each
        access, the pack file name and an offset in the pack is
        recorded. This may be helpful for troubleshooting some
        pack-related performance problems.
+       See 'GIT_TRACE' for available trace output options.
  
  'GIT_TRACE_PACKET'::
-       If this variable is set, it shows a trace of all packets
-       coming in or out of a given program. This can help with
-       debugging object negotiation or other protocol issues. Tracing
-       is turned off at a packet starting with "PACK".
+       Enables trace messages for all packets coming in or out of a
+       given program. This can help with debugging object negotiation
+       or other protocol issues. Tracing is turned off at a packet
+       starting with "PACK".
+       See 'GIT_TRACE' for available trace output options.
+ 'GIT_TRACE_PERFORMANCE'::
+       Enables performance related trace messages, e.g. total execution
+       time of each Git command.
+       See 'GIT_TRACE' for available trace output options.
+ 'GIT_TRACE_SETUP'::
+       Enables trace messages printing the .git, working tree and current
+       working directory after Git has completed its setup phase.
+       See 'GIT_TRACE' for available trace output options.
+ 'GIT_TRACE_SHALLOW'::
+       Enables trace messages that can help debugging fetching /
+       cloning of shallow repositories.
+       See 'GIT_TRACE' for available trace output options.
  
  GIT_LITERAL_PATHSPECS::
        Setting this variable to `1` will cause Git to treat all
diff --combined Makefile
index a49acb5ec621709e43714670289029571c60eed7,80f4390abb2cb1dedf9ce49cd7fb0a40db6663da..2320de592e6dbc545866e6bfef09a05f660c2c14
+++ b/Makefile
@@@ -340,6 -340,8 +340,8 @@@ all:
  #
  # Define GMTIME_UNRELIABLE_ERRORS if your gmtime() function does not
  # return NULL when it receives a bogus time_t.
+ #
+ # Define HAVE_CLOCK_GETTIME if your platform has clock_gettime in librt.
  
  GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@@ -552,7 -554,6 +554,7 @@@ TEST_PROGRAMS_NEED_X += test-ctyp
  TEST_PROGRAMS_NEED_X += test-date
  TEST_PROGRAMS_NEED_X += test-delta
  TEST_PROGRAMS_NEED_X += test-dump-cache-tree
 +TEST_PROGRAMS_NEED_X += test-dump-split-index
  TEST_PROGRAMS_NEED_X += test-genrandom
  TEST_PROGRAMS_NEED_X += test-hashmap
  TEST_PROGRAMS_NEED_X += test-index-version
@@@ -876,7 -877,6 +878,7 @@@ LIB_OBJS += sha1_name.
  LIB_OBJS += shallow.o
  LIB_OBJS += sideband.o
  LIB_OBJS += sigchain.o
 +LIB_OBJS += split-index.o
  LIB_OBJS += strbuf.o
  LIB_OBJS += streaming.o
  LIB_OBJS += string-list.o
@@@ -1001,7 -1001,6 +1003,7 @@@ BUILTIN_OBJS += builtin/update-ref.
  BUILTIN_OBJS += builtin/update-server-info.o
  BUILTIN_OBJS += builtin/upload-archive.o
  BUILTIN_OBJS += builtin/var.o
 +BUILTIN_OBJS += builtin/verify-commit.o
  BUILTIN_OBJS += builtin/verify-pack.o
  BUILTIN_OBJS += builtin/verify-tag.o
  BUILTIN_OBJS += builtin/write-tree.o
@@@ -1500,6 -1499,11 +1502,11 @@@ ifdef GMTIME_UNRELIABLE_ERROR
        BASIC_CFLAGS += -DGMTIME_UNRELIABLE_ERRORS
  endif
  
+ ifdef HAVE_CLOCK_GETTIME
+       BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME
+       EXTLIBS += -lrt
+ endif
  ifeq ($(TCLTK_PATH),)
  NO_TCLTK = NoThanks
  endif
@@@ -1555,13 -1559,13 +1562,13 @@@ endi
  PROFILE_DIR := $(CURDIR)
  
  ifeq ("$(PROFILE)","GEN")
 -      CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1
 +      BASIC_CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1
        EXTLIBS += -lgcov
        export CCACHE_DISABLE = t
        V = 1
  else
  ifneq ("$(PROFILE)","")
 -      CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1
 +      BASIC_CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1
        export CCACHE_DISABLE = t
        V = 1
  endif
@@@ -1646,20 -1650,12 +1653,20 @@@ SHELL = $(SHELL_PATH
  all:: shell_compatibility_test
  
  ifeq "$(PROFILE)" "BUILD"
 -ifeq ($(filter all,$(MAKECMDGOALS)),all)
 -all:: profile-clean
 +all:: profile
 +endif
 +
 +profile:: profile-clean
        $(MAKE) PROFILE=GEN all
        $(MAKE) PROFILE=GEN -j1 test
 -endif
 -endif
 +      $(MAKE) PROFILE=GEN -j1 perf
 +      $(MAKE) PROFILE=USE all
 +
 +profile-fast: profile-clean
 +      $(MAKE) PROFILE=GEN all
 +      $(MAKE) PROFILE=GEN -j1 perf
 +      $(MAKE) PROFILE=USE all
 +
  
  all:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
  ifneq (,$X)
@@@ -2346,12 -2342,6 +2353,12 @@@ mergetools_instdir_SQ = $(subst ','\'',
  
  install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
  
 +profile-install: profile
 +      $(MAKE) install
 +
 +profile-fast-install: profile-fast
 +      $(MAKE) install
 +
  install: all
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
diff --combined builtin/receive-pack.c
index 18458e81c6351d53afe1a50efff183889e5ab81b,145105068999634a43d172b31d5a6c8dc7d12572..92561bffc1ea4ba11d3fd45aa3e216edda3461f7
@@@ -438,7 -438,7 +438,7 @@@ static int update_shallow_ref(struct co
        uint32_t mask = 1 << (cmd->index % 32);
        int i;
  
-       trace_printf_key("GIT_TRACE_SHALLOW",
+       trace_printf_key(&trace_shallow,
                         "shallow: update_shallow_ref %s\n", cmd->ref_name);
        for (i = 0; i < si->shallow->nr; i++)
                if (si->used_shallow[i] &&
@@@ -614,9 -614,12 +614,9 @@@ static void run_update_post_hook(struc
        argv[0] = hook;
  
        for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
 -              char *p;
                if (cmd->error_string || cmd->did_not_exist)
                        continue;
 -              p = xmalloc(strlen(cmd->ref_name) + 1);
 -              strcpy(p, cmd->ref_name);
 -              argv[argc] = p;
 +              argv[argc] = xstrdup(cmd->ref_name);
                argc++;
        }
        argv[argc] = NULL;
diff --combined cache.h
index 6e9a0a6d0cf9f2ee076f1bc490eb2e2fc52cbcaa,466f6b3471d335f87db60d26f259034bb80585bb..8ae30d5a8510d246921de7ea1bf1300ee11dd1bf
+++ b/cache.h
@@@ -7,6 -7,7 +7,7 @@@
  #include "advice.h"
  #include "gettext.h"
  #include "convert.h"
+ #include "trace.h"
  
  #include SHA1_HEADER
  #ifndef git_SHA_CTX
@@@ -150,7 -151,6 +151,7 @@@ struct cache_entry 
        unsigned int ce_mode;
        unsigned int ce_flags;
        unsigned int ce_namelen;
 +      unsigned int index;     /* for link extension */
        unsigned char sha1[20];
        char name[FLEX_ARRAY]; /* more */
  };
  #define CE_STAGESHIFT 12
  
  /*
 - * Range 0xFFFF0000 in ce_flags is divided into
 + * Range 0xFFFF0FFF in ce_flags is divided into
   * two parts: in-memory flags and on-disk ones.
   * Flags in CE_EXTENDED_FLAGS will get saved on-disk
   * if you want to save a new flag, add it in
  /* used to temporarily mark paths matched by pathspecs */
  #define CE_MATCHED           (1 << 26)
  
 +#define CE_UPDATE_IN_BASE    (1 << 27)
 +#define CE_STRIP_NAME        (1 << 28)
 +
  /*
   * Extended on-disk flags
   */
@@@ -287,22 -284,12 +288,22 @@@ static inline unsigned int canon_mode(u
  
  #define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1)
  
 +#define SOMETHING_CHANGED     (1 << 0) /* unclassified changes go here */
 +#define CE_ENTRY_CHANGED      (1 << 1)
 +#define CE_ENTRY_REMOVED      (1 << 2)
 +#define CE_ENTRY_ADDED                (1 << 3)
 +#define RESOLVE_UNDO_CHANGED  (1 << 4)
 +#define CACHE_TREE_CHANGED    (1 << 5)
 +#define SPLIT_INDEX_ORDERED   (1 << 6)
 +
 +struct split_index;
  struct index_state {
        struct cache_entry **cache;
        unsigned int version;
        unsigned int cache_nr, cache_alloc, cache_changed;
        struct string_list *resolve_undo;
        struct cache_tree *cache_tree;
 +      struct split_index *split_index;
        struct cache_time timestamp;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
@@@ -331,6 -318,7 +332,6 @@@ extern void free_name_hash(struct index
  #define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec))
  #define is_cache_unborn() is_index_unborn(&the_index)
  #define read_cache_unmerged() read_index_unmerged(&the_index)
 -#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
  #define discard_cache() discard_index(&the_index)
  #define unmerged_cache() unmerged_index(&the_index)
  #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
@@@ -485,17 -473,12 +486,17 @@@ extern int daemonize(void)
        } while (0)
  
  /* Initialize and use the cache information */
 +struct lock_file;
  extern int read_index(struct index_state *);
  extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
 +extern int do_read_index(struct index_state *istate, const char *path,
 +                       int must_exist); /* for testting only! */
  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 *);
 -extern int write_index(struct index_state *, int newfd);
 +#define COMMIT_LOCK           (1 << 0)
 +#define CLOSE_LOCK            (1 << 1)
 +extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
  extern int discard_index(struct index_state *);
  extern int unmerged_index(const struct index_state *);
  extern int verify_path(const char *path);
@@@ -507,7 -490,6 +508,7 @@@ extern int index_name_pos(const struct 
  #define ADD_CACHE_SKIP_DFCHECK 4      /* Ok to skip DF conflict checks */
  #define ADD_CACHE_JUST_APPEND 8               /* Append only; tree.c::read_tree() */
  #define ADD_CACHE_NEW_ONLY 16         /* Do not replace existing ones */
 +#define ADD_CACHE_KEEP_CACHE_TREE 32  /* Do not invalidate cache-tree */
  extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
  extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
  extern int remove_index_entry_at(struct index_state *, int pos);
@@@ -578,8 -560,6 +579,8 @@@ struct lock_file 
  #define LOCK_DIE_ON_ERROR 1
  #define LOCK_NODEREF 2
  extern int unable_to_lock_error(const char *path, int err);
 +extern void unable_to_lock_message(const char *path, int err,
 +                                 struct strbuf *buf);
  extern NORETURN void unable_to_lock_index_die(const char *path, int err);
  extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
  extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
@@@ -587,6 -567,7 +588,6 @@@ extern int commit_lock_file(struct lock
  extern void update_index_if_able(struct index_state *, struct lock_file *);
  
  extern int hold_locked_index(struct lock_file *, int);
 -extern int commit_locked_index(struct lock_file *);
  extern void set_alternate_index_output(const char *);
  extern int close_lock_file(struct lock_file *);
  extern void rollback_lock_file(struct lock_file *);
@@@ -997,7 -978,7 +998,7 @@@ extern int read_ref(const char *refname
   * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
   * give up and return NULL.
   *
 - * errno is sometimes set on errors, but not always.
 + * errno is set to something meaningful on error.
   */
  extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);
  extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);
@@@ -1019,7 -1000,7 +1020,7 @@@ extern int validate_headref(const char 
  
  extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
  extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
 -extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
 +extern int name_compare(const char *name1, size_t len1, const char *name2, size_t len2);
  extern int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2);
  
  extern void *read_object_with_reference(const unsigned char *sha1,
@@@ -1098,7 -1079,6 +1099,7 @@@ const char *show_ident_date(const struc
  extern int ident_cmp(const struct ident_split *, const struct ident_split *);
  
  struct checkout {
 +      struct index_state *istate;
        const char *base_dir;
        int base_dir_len;
        unsigned force:1,
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
  
  struct cache_def {
 -      char path[PATH_MAX + 1];
 -      int len;
 +      struct strbuf path;
        int flags;
        int track_flags;
        int prefix_len_stat_func;
  };
 +#define CACHE_DEF_INIT { STRBUF_INIT, 0, 0, 0 }
 +static inline void cache_def_clear(struct cache_def *cache)
 +{
 +      strbuf_release(&cache->path);
 +}
  
  extern int has_symlink_leading_path(const char *name, int len);
  extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
@@@ -1402,17 -1378,7 +1403,7 @@@ extern void *alloc_tag_node(void)
  extern void *alloc_object_node(void);
  extern void alloc_report(void);
  
- /* trace.c */
- __attribute__((format (printf, 1, 2)))
- extern void trace_printf(const char *format, ...);
- __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);
+ /* pkt-line.c */
  void packet_trace_identity(const char *prog);
  
  /* add */
diff --combined commit.h
index b695aa4a5bfc7917da1cd3e00a295439fe905020,08ef643f20cc6b449e0a1f73a27c26a4dbb03dfc..f43079c0c3a980664d155b41fa729f68cd647713
+++ b/commit.h
@@@ -20,6 -20,7 +20,6 @@@ struct commit 
        unsigned long date;
        struct commit_list *parents;
        struct tree *tree;
 -      char *buffer;
  };
  
  extern int save_commit_buffer;
@@@ -50,44 -51,6 +50,44 @@@ int parse_commit_buffer(struct commit *
  int parse_commit(struct commit *item);
  void parse_commit_or_die(struct commit *item);
  
 +/*
 + * Associate an object buffer with the commit. The ownership of the
 + * memory is handed over to the commit, and must be free()-able.
 + */
 +void set_commit_buffer(struct commit *, void *buffer, unsigned long size);
 +
 +/*
 + * Get any cached object buffer associated with the commit. Returns NULL
 + * if none. The resulting memory should not be freed.
 + */
 +const void *get_cached_commit_buffer(const struct commit *, unsigned long *size);
 +
 +/*
 + * Get the commit's object contents, either from cache or by reading the object
 + * from disk. The resulting memory should not be modified, and must be given
 + * to unuse_commit_buffer when the caller is done.
 + */
 +const void *get_commit_buffer(const struct commit *, unsigned long *size);
 +
 +/*
 + * Tell the commit subsytem that we are done with a particular commit buffer.
 + * The commit and buffer should be the input and return value, respectively,
 + * from an earlier call to get_commit_buffer.  The buffer may or may not be
 + * freed by this call; callers should not access the memory afterwards.
 + */
 +void unuse_commit_buffer(const struct commit *, const void *buffer);
 +
 +/*
 + * Free any cached object buffer associated with the commit.
 + */
 +void free_commit_buffer(struct commit *);
 +
 +/*
 + * Disassociate any cached object buffer from the commit, but do not free it.
 + * The buffer (or NULL, if none) is returned.
 + */
 +const void *detach_commit_buffer(struct commit *, unsigned long *sizep);
 +
  /* Find beginning and length of commit subject. */
  int find_commit_subject(const char *commit_buffer, const char **subject);
  
@@@ -152,9 -115,10 +152,9 @@@ struct userformat_want 
  
  extern int has_non_ascii(const char *text);
  struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 -extern char *logmsg_reencode(const struct commit *commit,
 -                           char **commit_encoding,
 -                           const char *output_encoding);
 -extern void logmsg_free(char *msg, const struct commit *commit);
 +extern const char *logmsg_reencode(const struct commit *commit,
 +                                 char **commit_encoding,
 +                                 const char *output_encoding);
  extern void get_commit_format(const char *arg, struct rev_info *);
  extern const char *format_subject(struct strbuf *sb, const char *msg,
                                  const char *line_separator);
@@@ -271,6 -235,7 +271,7 @@@ extern void assign_shallow_commits_to_r
                                           int *ref_status);
  extern int delayed_reachability_test(struct shallow_info *si, int c);
  extern void prune_shallow(int show_only);
+ extern struct trace_key trace_shallow;
  
  int is_descendant_of(struct commit *, struct commit_list *);
  int in_merge_bases(struct commit *, struct commit *);
@@@ -297,13 -262,11 +298,13 @@@ struct commit_extra_header 
  extern void append_merge_tag_headers(struct commit_list *parents,
                                     struct commit_extra_header ***tail);
  
 -extern int commit_tree(const struct strbuf *msg, const unsigned char *tree,
 +extern int commit_tree(const char *msg, size_t msg_len,
 +                     const unsigned char *tree,
                       struct commit_list *parents, unsigned char *ret,
                       const char *author, const char *sign_commit);
  
 -extern int commit_tree_extended(const struct strbuf *msg, const unsigned char *tree,
 +extern int commit_tree_extended(const char *msg, size_t msg_len,
 +                              const unsigned char *tree,
                                struct commit_list *parents, unsigned char *ret,
                                const char *author, const char *sign_commit,
                                struct commit_extra_header *);
@@@ -312,11 -275,6 +313,11 @@@ extern struct commit_extra_header *read
  
  extern void free_commit_extra_headers(struct commit_extra_header *extra);
  
 +typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
 +                               void *cb_data);
 +
 +extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);
 +
  struct merge_remote_desc {
        struct object *obj; /* the named object, could be a tag */
        const char *name;
   */
  struct commit *get_merge_parent(const char *name);
  
 -extern int parse_signed_commit(const unsigned char *sha1,
 +extern int parse_signed_commit(const struct commit *commit,
                               struct strbuf *message, struct strbuf *signature);
  extern void print_commit_list(struct commit_list *list,
                              const char *format_cur,
diff --combined config.mak.uname
index 8131c81985d97c23d19c7ef489f0db988f6fbbc7,dad261832949d2abcf1c4b0e71ce84d4852df00b..00cf4c6b832c19143bd7c8f8015f793f8142c548
@@@ -34,6 -34,7 +34,7 @@@ ifeq ($(uname_S),Linux
        HAVE_PATHS_H = YesPlease
        LIBC_CONTAINS_LIBINTL = YesPlease
        HAVE_DEV_TTY = YesPlease
+       HAVE_CLOCK_GETTIME = YesPlease
  endif
  ifeq ($(uname_S),GNU/kFreeBSD)
        HAVE_ALLOCA_H = YesPlease
@@@ -354,7 -355,6 +355,7 @@@ ifeq ($(uname_S),Windows
        NO_POSIX_GOODIES = UnfortunatelyYes
        NATIVE_CRLF = YesPlease
        DEFAULT_HELP_FORMAT = html
 +      NO_D_INO_IN_DIRENT = YesPlease
  
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
@@@ -504,7 -504,6 +505,7 @@@ ifneq (,$(findstring MINGW,$(uname_S))
        NO_INET_NTOP = YesPlease
        NO_POSIX_GOODIES = UnfortunatelyYes
        DEFAULT_HELP_FORMAT = html
 +      NO_D_INO_IN_DIRENT = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -D_USE_32BIT_TIME_T -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
diff --combined git-compat-util.h
index 63d72db5539d47cd7064536a2cb7497dcdade58e,4dd065d7f31a8a69d73fa21391b79f56b1bcd3a4..26e92f19cf6c82cfd55fcc7ba6de244c82c557c5
@@@ -291,12 -291,10 +291,12 @@@ extern char *gitbasename(char *)
  #else
  #define NORETURN
  #define NORETURN_PTR
 +#ifndef __GNUC__
  #ifndef __attribute__
  #define __attribute__(x)
  #endif
  #endif
 +#endif
  
  /* The sentinel attribute is valid from gcc version 4.0 */
  #if defined(__GNUC__) && (__GNUC__ >= 4)
@@@ -349,66 -347,15 +349,66 @@@ extern void set_error_routine(void (*ro
  extern void set_die_is_recursing_routine(int (*routine)(void));
  
  extern int starts_with(const char *str, const char *prefix);
 -extern int ends_with(const char *str, const char *suffix);
  
 -static inline const char *skip_prefix(const char *str, const char *prefix)
 +/*
 + * If the string "str" begins with the string found in "prefix", return 1.
 + * The "out" parameter is set to "str + strlen(prefix)" (i.e., to the point in
 + * the string right after the prefix).
 + *
 + * Otherwise, return 0 and leave "out" untouched.
 + *
 + * Examples:
 + *
 + *   [extract branch name, fail if not a branch]
 + *   if (!skip_prefix(ref, "refs/heads/", &branch)
 + *    return -1;
 + *
 + *   [skip prefix if present, otherwise use whole string]
 + *   skip_prefix(name, "refs/heads/", &name);
 + */
 +static inline int skip_prefix(const char *str, const char *prefix,
 +                            const char **out)
  {
        do {
 -              if (!*prefix)
 -                      return str;
 +              if (!*prefix) {
 +                      *out = str;
 +                      return 1;
 +              }
        } while (*str++ == *prefix++);
 -      return NULL;
 +      return 0;
 +}
 +
 +/*
 + * If buf ends with suffix, return 1 and subtract the length of the suffix
 + * from *len. Otherwise, return 0 and leave *len untouched.
 + */
 +static inline int strip_suffix_mem(const char *buf, size_t *len,
 +                                 const char *suffix)
 +{
 +      size_t suflen = strlen(suffix);
 +      if (*len < suflen || memcmp(buf + (*len - suflen), suffix, suflen))
 +              return 0;
 +      *len -= suflen;
 +      return 1;
 +}
 +
 +/*
 + * If str ends with suffix, return 1 and set *len to the size of the string
 + * without the suffix. Otherwise, return 0 and set *len to the size of the
 + * string.
 + *
 + * Note that we do _not_ NUL-terminate str to the new length.
 + */
 +static inline int strip_suffix(const char *str, const char *suffix, size_t *len)
 +{
 +      *len = strlen(str);
 +      return strip_suffix_mem(str, len, suffix);
 +}
 +
 +static inline int ends_with(const char *str, const char *suffix)
 +{
 +      size_t len;
 +      return strip_suffix(str, suffix, &len);
  }
  
  #if defined(NO_MMAP) || defined(USE_WIN32_MMAP)
@@@ -615,6 -562,13 +615,6 @@@ static inline size_t xsize_t(off_t len
        return (size_t)len;
  }
  
 -static inline int has_extension(const char *filename, const char *ext)
 -{
 -      size_t len = strlen(filename);
 -      size_t extlen = strlen(ext);
 -      return len > extlen && !memcmp(filename + len - extlen, ext, extlen);
 -}
 -
  /* in ctype.c, for kwset users */
  extern const char tolower_trans_tbl[256];
  
@@@ -731,17 -685,6 +731,17 @@@ void git_qsort(void *base, size_t nmemb
  #endif
  #endif
  
 +#if defined(__GNUC__) && defined(__x86_64__)
 +#include <emmintrin.h>
 +/*
 + * This is the system memory page size; it's used so that we can read
 + * outside the bounds of an allocation without segfaulting.
 + */
 +#ifndef PAGE_SIZE
 +#define PAGE_SIZE 4096
 +#endif
 +#endif
 +
  #ifdef UNRELIABLE_FSTAT
  #define fstat_is_reliable() 0
  #else
  #endif
  #endif
  
+ #if defined(__GNUC__) || (_MSC_VER >= 1400)
+ #define HAVE_VARIADIC_MACROS 1
+ #endif
  /*
   * Preserves errno, prints a message, but gives no warning for ENOENT.
   * Always returns the return value of unlink(2).
diff --combined git.c
index 5b6c7611be93c535491bbc9ba027e3acbad0db8f,d4daeb860466ee9dcb5306a9d2893adfa448a4b8..9c495198317bf31ff40785a48bf0a44253548098
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -20,43 -20,6 +20,43 @@@ const char git_more_info_string[] 
  
  static struct startup_info git_startup_info;
  static int use_pager = -1;
 +static char orig_cwd[PATH_MAX];
 +static const char *env_names[] = {
 +      GIT_DIR_ENVIRONMENT,
 +      GIT_WORK_TREE_ENVIRONMENT,
 +      GIT_IMPLICIT_WORK_TREE_ENVIRONMENT,
 +      GIT_PREFIX_ENVIRONMENT
 +};
 +static char *orig_env[4];
 +static int saved_environment;
 +
 +static void save_env(void)
 +{
 +      int i;
 +      if (saved_environment)
 +              return;
 +      saved_environment = 1;
 +      if (!getcwd(orig_cwd, sizeof(orig_cwd)))
 +              die_errno("cannot getcwd");
 +      for (i = 0; i < ARRAY_SIZE(env_names); i++) {
 +              orig_env[i] = getenv(env_names[i]);
 +              if (orig_env[i])
 +                      orig_env[i] = xstrdup(orig_env[i]);
 +      }
 +}
 +
 +static void restore_env(void)
 +{
 +      int i;
 +      if (*orig_cwd && chdir(orig_cwd))
 +              die_errno("could not move to %s", orig_cwd);
 +      for (i = 0; i < ARRAY_SIZE(env_names); i++) {
 +              if (orig_env[i])
 +                      setenv(env_names[i], orig_env[i], 1);
 +              else
 +                      unsetenv(env_names[i]);
 +      }
 +}
  
  static void commit_pager_choice(void) {
        switch (use_pager) {
@@@ -91,7 -54,8 +91,7 @@@ static int handle_options(const char **
                /*
                 * Check remaining flags.
                 */
 -              if (starts_with(cmd, "--exec-path")) {
 -                      cmd += 11;
 +              if (skip_prefix(cmd, "--exec-path", &cmd)) {
                        if (*cmd == '=')
                                git_set_argv_exec_path(cmd + 1);
                        else {
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (starts_with(cmd, "--git-dir=")) {
 -                      setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
 +              } else if (skip_prefix(cmd, "--git-dir=", &cmd)) {
 +                      setenv(GIT_DIR_ENVIRONMENT, cmd, 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--namespace")) {
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (starts_with(cmd, "--namespace=")) {
 -                      setenv(GIT_NAMESPACE_ENVIRONMENT, cmd + 12, 1);
 +              } else if (skip_prefix(cmd, "--namespace=", &cmd)) {
 +                      setenv(GIT_NAMESPACE_ENVIRONMENT, cmd, 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--work-tree")) {
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (starts_with(cmd, "--work-tree=")) {
 -                      setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
 +              } else if (skip_prefix(cmd, "--work-tree=", &cmd)) {
 +                      setenv(GIT_WORK_TREE_ENVIRONMENT, cmd, 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--bare")) {
@@@ -308,7 -272,6 +308,7 @@@ static int handle_alias(int *argcp, con
   * RUN_SETUP for reading from the configuration file.
   */
  #define NEED_WORK_TREE                (1<<3)
 +#define NO_SETUP              (1<<4)
  
  struct cmd_struct {
        const char *cmd;
@@@ -389,7 -352,7 +389,7 @@@ static struct cmd_struct commands[] = 
        { "cherry", cmd_cherry, RUN_SETUP },
        { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
        { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
 -      { "clone", cmd_clone },
 +      { "clone", cmd_clone, NO_SETUP },
        { "column", cmd_column, RUN_SETUP_GENTLY },
        { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
        { "commit-tree", cmd_commit_tree, RUN_SETUP },
        { "hash-object", cmd_hash_object },
        { "help", cmd_help },
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
 -      { "init", cmd_init_db },
 -      { "init-db", cmd_init_db },
 +      { "init", cmd_init_db, NO_SETUP },
 +      { "init-db", cmd_init_db, NO_SETUP },
        { "log", cmd_log, RUN_SETUP },
        { "ls-files", cmd_ls_files, RUN_SETUP },
        { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
        { "upload-archive", cmd_upload_archive },
        { "upload-archive--writer", cmd_upload_archive_writer },
        { "var", cmd_var, RUN_SETUP_GENTLY },
 +      { "verify-commit", cmd_verify_commit, RUN_SETUP },
        { "verify-pack", cmd_verify_pack },
        { "verify-tag", cmd_verify_tag, RUN_SETUP },
        { "version", cmd_version },
@@@ -522,10 -484,6 +522,10 @@@ static void handle_builtin(int argc, co
                struct cmd_struct *p = commands+i;
                if (strcmp(p->cmd, cmd))
                        continue;
 +              if (saved_environment && (p->option & NO_SETUP)) {
 +                      restore_env();
 +                      break;
 +              }
                exit(run_builtin(p, argc, argv));
        }
  }
@@@ -581,10 -539,7 +581,10 @@@ static int run_argv(int *argcp, const c
                 * of overriding "git log" with "git show" by having
                 * alias.log = show
                 */
 -              if (done_alias || !handle_alias(argcp, argv))
 +              if (done_alias)
 +                      break;
 +              save_env();
 +              if (!handle_alias(argcp, argv))
                        break;
                done_alias = 1;
        }
@@@ -613,6 -568,8 +613,8 @@@ int main(int argc, char **av
  
        git_setup_gettext();
  
+       trace_command_performance(argv);
        /*
         * "git-xxxx" is the same as "git xxxx", but we obviously:
         *
         * So we just directly call the builtin handler, and die if
         * that one cannot handle it.
         */
 -      if (starts_with(cmd, "git-")) {
 -              cmd += 4;
 +      if (skip_prefix(cmd, "git-", &cmd)) {
                argv[0] = cmd;
                handle_builtin(argc, argv);
                die("cannot handle %s as a builtin", cmd);
        argc--;
        handle_options(&argv, &argc, NULL);
        if (argc > 0) {
 -              if (starts_with(argv[0], "--"))
 -                      argv[0] += 2;
 +              /* translate --help and --version into commands */
 +              skip_prefix(argv[0], "--", &argv[0]);
        } else {
                /* The user didn't specify a command; give them help */
                commit_pager_choice();
diff --combined sha1_file.c
index ea6150892aef3416ef28c2daf74ab5288da3b7c3,7a110b513e07a7b0e3a92313e0fb2438bac8c0fb..3f70b1d86aaa8e0648d8a3c6ebb6aca701ae7475
@@@ -36,9 -36,6 +36,6 @@@ 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
@@@ -268,9 -265,9 +265,9 @@@ static struct alternate_object_databas
   * SHA1, an extra slash for the first level indirection, and the
   * terminating NUL.
   */
 -static int link_alt_odb_entry(const char *entry, const char *relative_base, int depth)
 +static int link_alt_odb_entry(const char *entry, const char *relative_base,
 +      int depth, const char *normalized_objdir)
  {
 -      const char *objdir = get_object_directory();
        struct alternate_object_database *ent;
        struct alternate_object_database *alt;
        int pfxlen, entlen;
         * thing twice, or object directory itself.
         */
        for (alt = alt_odb_list; alt; alt = alt->next) {
 -              if (!memcmp(ent->base, alt->base, pfxlen)) {
 +              if (pfxlen == alt->name - alt->base - 1 &&
 +                  !memcmp(ent->base, alt->base, pfxlen)) {
                        free(ent);
                        return -1;
                }
        }
 -      if (!strcmp(ent->base, objdir)) {
 +      if (!strcmp_icase(ent->base, normalized_objdir)) {
                free(ent);
                return -1;
        }
@@@ -345,7 -341,6 +342,7 @@@ static void link_alt_odb_entries(const 
        struct string_list entries = STRING_LIST_INIT_NODUP;
        char *alt_copy;
        int i;
 +      struct strbuf objdirbuf = STRBUF_INIT;
  
        if (depth > 5) {
                error("%s: ignoring alternate object stores, nesting too deep.",
                return;
        }
  
 +      strbuf_addstr(&objdirbuf, absolute_path(get_object_directory()));
 +      normalize_path_copy(objdirbuf.buf, objdirbuf.buf);
 +
        alt_copy = xmemdupz(alt, len);
        string_list_split_in_place(&entries, alt_copy, sep, -1);
        for (i = 0; i < entries.nr; i++) {
                        error("%s: ignoring relative alternate object store %s",
                                        relative_base, entry);
                } else {
 -                      link_alt_odb_entry(entry, relative_base, depth);
 +                      link_alt_odb_entry(entry, relative_base, depth, objdirbuf.buf);
                }
        }
        string_list_clear(&entries, 0);
        free(alt_copy);
 +      strbuf_release(&objdirbuf);
  }
  
  void read_info_alternates(const char * relative_base, int depth)
@@@ -1183,42 -1174,48 +1180,42 @@@ static void report_pack_garbage(struct 
  
  static void prepare_packed_git_one(char *objdir, int local)
  {
 -      /* Ensure that this buffer is large enough so that we can
 -         append "/pack/" without clobbering the stack even if
 -         strlen(objdir) were PATH_MAX.  */
 -      char path[PATH_MAX + 1 + 4 + 1 + 1];
 -      int len;
 +      struct strbuf path = STRBUF_INIT;
 +      size_t dirnamelen;
        DIR *dir;
        struct dirent *de;
        struct string_list garbage = STRING_LIST_INIT_DUP;
  
 -      sprintf(path, "%s/pack", objdir);
 -      len = strlen(path);
 -      dir = opendir(path);
 +      strbuf_addstr(&path, objdir);
 +      strbuf_addstr(&path, "/pack");
 +      dir = opendir(path.buf);
        if (!dir) {
                if (errno != ENOENT)
                        error("unable to open object pack directory: %s: %s",
 -                            path, strerror(errno));
 +                            path.buf, strerror(errno));
 +              strbuf_release(&path);
                return;
        }
 -      path[len++] = '/';
 +      strbuf_addch(&path, '/');
 +      dirnamelen = path.len;
        while ((de = readdir(dir)) != NULL) {
 -              int namelen = strlen(de->d_name);
                struct packed_git *p;
 -
 -              if (len + namelen + 1 > sizeof(path)) {
 -                      if (report_garbage) {
 -                              struct strbuf sb = STRBUF_INIT;
 -                              strbuf_addf(&sb, "%.*s/%s", len - 1, path, de->d_name);
 -                              report_garbage("path too long", sb.buf);
 -                              strbuf_release(&sb);
 -                      }
 -                      continue;
 -              }
 +              size_t base_len;
  
                if (is_dot_or_dotdot(de->d_name))
                        continue;
  
 -              strcpy(path + len, de->d_name);
 +              strbuf_setlen(&path, dirnamelen);
 +              strbuf_addstr(&path, de->d_name);
  
 -              if (has_extension(de->d_name, ".idx")) {
 +              base_len = path.len;
 +              if (strip_suffix_mem(path.buf, &base_len, ".idx")) {
                        /* Don't reopen a pack we already have. */
                        for (p = packed_git; p; p = p->next) {
 -                              if (!memcmp(path, p->pack_name, len + namelen - 4))
 +                              size_t len;
 +                              if (strip_suffix(p->pack_name, ".pack", &len) &&
 +                                  len == base_len &&
 +                                  !memcmp(p->pack_name, path.buf, len))
                                        break;
                        }
                        if (p == NULL &&
                             * See if it really is a valid .idx file with
                             * corresponding .pack file that we can map.
                             */
 -                          (p = add_packed_git(path, len + namelen, local)) != NULL)
 +                          (p = add_packed_git(path.buf, path.len, local)) != NULL)
                                install_packed_git(p);
                }
  
                if (!report_garbage)
                        continue;
  
 -              if (has_extension(de->d_name, ".idx") ||
 -                  has_extension(de->d_name, ".pack") ||
 -                  has_extension(de->d_name, ".bitmap") ||
 -                  has_extension(de->d_name, ".keep"))
 -                      string_list_append(&garbage, path);
 +              if (ends_with(de->d_name, ".idx") ||
 +                  ends_with(de->d_name, ".pack") ||
 +                  ends_with(de->d_name, ".bitmap") ||
 +                  ends_with(de->d_name, ".keep"))
 +                      string_list_append(&garbage, path.buf);
                else
 -                      report_garbage("garbage found", path);
 +                      report_garbage("garbage found", path.buf);
        }
        closedir(dir);
        report_pack_garbage(&garbage);
        string_list_clear(&garbage, 0);
 +      strbuf_release(&path);
  }
  
  static int sort_pack(const void *a_, const void *b_)
@@@ -2086,27 -2082,9 +2083,9 @@@ static void *read_object(const unsigne
  
  static void write_pack_access_log(struct packed_git *p, off_t obj_offset)
  {
-       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 = no_log_pack_access;
-                       return;
-               }
-       }
-       fprintf(log_file, "%s %"PRIuMAX"\n",
-               p->pack_name, (uintmax_t)obj_offset);
-       fflush(log_file);
+       static struct trace_key pack_access = TRACE_KEY_INIT(PACK_ACCESS);
+       trace_printf_key(&pack_access, "%s %"PRIuMAX"\n",
+                        p->pack_name, (uintmax_t)obj_offset);
  }
  
  int do_check_packed_object_crc;
@@@ -2131,8 -2109,7 +2110,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 != no_log_pack_access)
-               write_pack_access_log(p, obj_offset);
+       write_pack_access_log(p, obj_offset);
  
        /* PHASE 1: drill down to the innermost base object */
        for (;;) {
diff --combined t/test-lib.sh
index a4795373a6a2e1f247b3cef9880f2a82309b3594,e37da5a22049cda4c957698648e7eff8b924cb10..b1bc65bfb564ca85d35c42071f825360d1998392
@@@ -109,6 -109,10 +109,10 @@@ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAM
  export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
  export EDITOR
  
+ # Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output
+ GIT_TRACE_BARE=1
+ export GIT_TRACE_BARE
  if test -n "${TEST_GIT_INDEX_VERSION:+isset}"
  then
        GIT_INDEX_VERSION="$TEST_GIT_INDEX_VERSION"
@@@ -976,14 -980,6 +980,14 @@@ test_lazy_prereq AUTOIDENT 
        git var GIT_AUTHOR_IDENT
  '
  
 +test_lazy_prereq EXPENSIVE '
 +      test -n "$GIT_TEST_LONG"
 +'
 +
 +test_lazy_prereq USR_BIN_TIME '
 +      test -x /usr/bin/time
 +'
 +
  # When the tests are run as root, permission tests will report that
  # things are writable when they shouldn't be.
  test -w / || test_set_prereq SANITY
diff --combined wt-status.c
index 882cfe9fb050efe84e2330b9aea76685c6a7f891,dfdc018f1b03960096e8bb0d6f91d2011dd59df6..27da5296be253844e0f2bc8d5996faf343192828
@@@ -574,14 -574,11 +574,11 @@@ static void wt_status_collect_untracked
  {
        int i;
        struct dir_struct dir;
-       struct timeval t_begin;
+       uint64_t t_begin = getnanotime();
  
        if (!s->show_untracked_files)
                return;
  
-       if (advice_status_u_option)
-               gettimeofday(&t_begin, NULL);
        memset(&dir, 0, sizeof(dir));
        if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
                dir.flags |=
        free(dir.ignored);
        clear_directory(&dir);
  
-       if (advice_status_u_option) {
-               struct timeval t_end;
-               gettimeofday(&t_end, NULL);
-               s->untracked_in_ms =
-                       (uint64_t)t_end.tv_sec * 1000 + t_end.tv_usec / 1000 -
-                       ((uint64_t)t_begin.tv_sec * 1000 + t_begin.tv_usec / 1000);
-       }
+       if (advice_status_u_option)
+               s->untracked_in_ms = (getnanotime() - t_begin) / 1000000;
  }
  
  void wt_status_collect(struct wt_status *s)
@@@ -734,34 -726,37 +726,34 @@@ static void wt_status_print_changed(str
  static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
  {
        struct child_process sm_summary;
 -      char summary_limit[64];
 -      char index[PATH_MAX];
 -      const char *env[] = { NULL, NULL };
 +      struct argv_array env = ARGV_ARRAY_INIT;
        struct argv_array argv = ARGV_ARRAY_INIT;
        struct strbuf cmd_stdout = STRBUF_INIT;
        struct strbuf summary = STRBUF_INIT;
        char *summary_content;
        size_t len;
  
 -      sprintf(summary_limit, "%d", s->submodule_summary);
 -      snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
 +      argv_array_pushf(&env, "GIT_INDEX_FILE=%s", s->index_file);
  
 -      env[0] = index;
        argv_array_push(&argv, "submodule");
        argv_array_push(&argv, "summary");
        argv_array_push(&argv, uncommitted ? "--files" : "--cached");
        argv_array_push(&argv, "--for-status");
        argv_array_push(&argv, "--summary-limit");
 -      argv_array_push(&argv, summary_limit);
 +      argv_array_pushf(&argv, "%d", s->submodule_summary);
        if (!uncommitted)
                argv_array_push(&argv, s->amend ? "HEAD^" : "HEAD");
  
        memset(&sm_summary, 0, sizeof(sm_summary));
        sm_summary.argv = argv.argv;
 -      sm_summary.env = env;
 +      sm_summary.env = env.argv;
        sm_summary.git_cmd = 1;
        sm_summary.no_stdin = 1;
        fflush(s->fp);
        sm_summary.out = -1;
  
        run_command(&sm_summary);
 +      argv_array_clear(&env);
        argv_array_clear(&argv);
  
        len = strbuf_read(&cmd_stdout, sm_summary.out, 1024);