Merge branch 'dk/gc-idx-wo-pack'
authorJeff King <peff@peff.net>
Fri, 20 Nov 2015 11:55:34 +0000 (06:55 -0500)
committerJeff King <peff@peff.net>
Fri, 20 Nov 2015 11:55:34 +0000 (06:55 -0500)
Having a leftover .idx file without corresponding .pack file in
the repository hurts performance; "git gc" learned to prune them.

* dk/gc-idx-wo-pack:
gc: remove garbage .idx files from pack dir
t5304: test cleaning pack garbage
prepare_packed_git(): refactor garbage reporting in pack directory

1  2 
builtin/gc.c
cache.h
path.c
sha1_file.c
diff --combined builtin/gc.c
index df3e454447ea4e4e34c6a7eac7ef54df1d614df6,203265db76bc49bb190b677cd095fd03a2c86063..c583aad6ec2896c8a6ad3b35671e92a3c0478bcd
@@@ -11,7 -11,6 +11,7 @@@
   */
  
  #include "builtin.h"
 +#include "tempfile.h"
  #include "lockfile.h"
  #include "parse-options.h"
  #include "run-command.h"
@@@ -43,9 -42,37 +43,25 @@@ static struct argv_array prune = ARGV_A
  static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
  static struct argv_array rerere = ARGV_ARRAY_INIT;
  
 -static char *pidfile;
 -
 -static void remove_pidfile(void)
 -{
 -      if (pidfile)
 -              unlink(pidfile);
 -}
 -
 -static void remove_pidfile_on_signal(int signo)
 -{
 -      remove_pidfile();
 -      sigchain_pop(signo);
 -      raise(signo);
 -}
 +static struct tempfile pidfile;
 +static struct lock_file log_lock;
  
+ static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
+ static void clean_pack_garbage(void)
+ {
+       int i;
+       for (i = 0; i < pack_garbage.nr; i++)
+               unlink_or_warn(pack_garbage.items[i].string);
+       string_list_clear(&pack_garbage, 0);
+ }
+ static void report_pack_garbage(unsigned seen_bits, const char *path)
+ {
+       if (seen_bits == PACKDIR_FILE_IDX)
+               string_list_append(&pack_garbage, path);
+ }
  static void git_config_date_string(const char *key, const char **output)
  {
        if (git_config_get_string_const(key, output))
        }
  }
  
 +static void process_log_file(void)
 +{
 +      struct stat st;
 +      if (!fstat(get_lock_file_fd(&log_lock), &st) && st.st_size)
 +              commit_lock_file(&log_lock);
 +      else
 +              rollback_lock_file(&log_lock);
 +}
 +
 +static void process_log_file_at_exit(void)
 +{
 +      fflush(stderr);
 +      process_log_file();
 +}
 +
 +static void process_log_file_on_signal(int signo)
 +{
 +      process_log_file();
 +      sigchain_pop(signo);
 +      raise(signo);
 +}
 +
  static void gc_config(void)
  {
        const char *value;
        git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
        git_config_get_bool("gc.autodetach", &detach_auto);
        git_config_date_string("gc.pruneexpire", &prune_expire);
 -      git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire);
 +      git_config_date_string("gc.worktreepruneexpire", &prune_worktrees_expire);
        git_config(git_default_config, NULL);
  }
  
@@@ -210,22 -215,20 +226,22 @@@ static const char *lock_repo_for_gc(in
        uintmax_t pid;
        FILE *fp;
        int fd;
 +      char *pidfile_path;
  
 -      if (pidfile)
 +      if (is_tempfile_active(&pidfile))
                /* already locked */
                return NULL;
  
        if (gethostname(my_host, sizeof(my_host)))
 -              strcpy(my_host, "unknown");
 +              xsnprintf(my_host, sizeof(my_host), "unknown");
  
 -      fd = hold_lock_file_for_update(&lock, git_path("gc.pid"),
 +      pidfile_path = git_pathdup("gc.pid");
 +      fd = hold_lock_file_for_update(&lock, pidfile_path,
                                       LOCK_DIE_ON_ERROR);
        if (!force) {
                static char locking_host[128];
                int should_exit;
 -              fp = fopen(git_path("gc.pid"), "r");
 +              fp = fopen(pidfile_path, "r");
                memset(locking_host, 0, sizeof(locking_host));
                should_exit =
                        fp != NULL &&
                         * running.
                         */
                        time(NULL) - st.st_mtime <= 12 * 3600 &&
 -                      fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 &&
 +                      fscanf(fp, "%"SCNuMAX" %127c", &pid, locking_host) == 2 &&
                        /* be gentle to concurrent "gc" on remote hosts */
                        (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
                if (fp != NULL)
                        if (fd >= 0)
                                rollback_lock_file(&lock);
                        *ret_pid = pid;
 +                      free(pidfile_path);
                        return locking_host;
                }
        }
        write_in_full(fd, sb.buf, sb.len);
        strbuf_release(&sb);
        commit_lock_file(&lock);
 -
 -      pidfile = git_pathdup("gc.pid");
 -      sigchain_push_common(remove_pidfile_on_signal);
 -      atexit(remove_pidfile);
 -
 +      register_tempfile(&pidfile, pidfile_path);
 +      free(pidfile_path);
        return NULL;
  }
  
 +static int report_last_gc_error(void)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int ret;
 +
 +      ret = strbuf_read_file(&sb, git_path("gc.log"), 0);
 +      if (ret > 0)
 +              return error(_("The last gc run reported the following. "
 +                             "Please correct the root cause\n"
 +                             "and remove %s.\n"
 +                             "Automatic cleanup will not be performed "
 +                             "until the file is removed.\n\n"
 +                             "%s"),
 +                           git_path("gc.log"), sb.buf);
 +      strbuf_release(&sb);
 +      return 0;
 +}
 +
  static int gc_before_repack(void)
  {
        if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
@@@ -303,7 -290,6 +319,7 @@@ int cmd_gc(int argc, const char **argv
        int force = 0;
        const char *name;
        pid_t pid;
 +      int daemonized = 0;
  
        struct option builtin_gc_options[] = {
                OPT__QUIET(&quiet, N_("suppress progress reporting")),
                        fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
                }
                if (detach_auto) {
 +                      if (report_last_gc_error())
 +                              return -1;
 +
                        if (gc_before_repack())
                                return -1;
                        /*
                         * failure to daemonize is ok, we'll continue
                         * in foreground
                         */
 -                      daemonize();
 +                      daemonized = !daemonize();
                }
        } else
                add_repack_all_option();
                    name, (uintmax_t)pid);
        }
  
 +      if (daemonized) {
 +              hold_lock_file_for_update(&log_lock,
 +                                        git_path("gc.log"),
 +                                        LOCK_DIE_ON_ERROR);
 +              dup2(get_lock_file_fd(&log_lock), 2);
 +              sigchain_push_common(process_log_file_on_signal);
 +              atexit(process_log_file_at_exit);
 +      }
 +
        if (gc_before_repack())
                return -1;
  
 -      if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
 -              return error(FAILED_RUN, repack.argv[0]);
 +      if (!repository_format_precious_objects) {
 +              if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
 +                      return error(FAILED_RUN, repack.argv[0]);
  
 -      if (prune_expire) {
 -              argv_array_push(&prune, prune_expire);
 -              if (quiet)
 -                      argv_array_push(&prune, "--no-progress");
 -              if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
 -                      return error(FAILED_RUN, prune.argv[0]);
 +              if (prune_expire) {
 +                      argv_array_push(&prune, prune_expire);
 +                      if (quiet)
 +                              argv_array_push(&prune, "--no-progress");
 +                      if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
 +                              return error(FAILED_RUN, prune.argv[0]);
 +              }
        }
  
        if (prune_worktrees_expire) {
        if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
                return error(FAILED_RUN, rerere.argv[0]);
  
+       report_garbage = report_pack_garbage;
+       reprepare_packed_git();
+       if (pack_garbage.nr > 0)
+               clean_pack_garbage();
        if (auto_gc && too_many_loose_objects())
                warning(_("There are too many unreachable loose objects; "
                        "run 'git prune' to remove them."));
diff --combined cache.h
index 3ba0b8f3d7a86eac8839bb175b0291b76013d263,c6de1e9c0e0387956a015271fcf60f737663a038..736abc03a4b2bde987077440453e253150a584f7
+++ b/cache.h
@@@ -397,7 -397,6 +397,7 @@@ static inline enum object_type object_t
  #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
  #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
  #define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
 +#define GIT_REPLACE_REF_BASE_ENVIRONMENT "GIT_REPLACE_REF_BASE"
  #define GITATTRIBUTES_FILE ".gitattributes"
  #define INFOATTRIBUTES_FILE "info/attributes"
  #define ATTRIBUTE_MACRO_PREFIX "[attr]"
@@@ -443,22 -442,11 +443,22 @@@ extern char *get_object_directory(void)
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
  extern int set_git_dir(const char *path);
 +extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
  extern int get_common_dir(struct strbuf *sb, const char *gitdir);
  extern const char *get_git_namespace(void);
  extern const char *strip_namespace(const char *namespaced_ref);
  extern const char *get_git_work_tree(void);
 -extern const char *read_gitfile(const char *path);
 +
 +#define READ_GITFILE_ERR_STAT_FAILED 1
 +#define READ_GITFILE_ERR_NOT_A_FILE 2
 +#define READ_GITFILE_ERR_OPEN_FAILED 3
 +#define READ_GITFILE_ERR_READ_FAILED 4
 +#define READ_GITFILE_ERR_INVALID_FORMAT 5
 +#define READ_GITFILE_ERR_NO_PATH 6
 +#define READ_GITFILE_ERR_NOT_A_REPO 7
 +#define READ_GITFILE_ERR_TOO_LARGE 8
 +extern const char *read_gitfile_gently(const char *path, int *return_error_code);
 +#define read_gitfile(path) read_gitfile_gently((path), NULL)
  extern const char *resolve_gitdir(const char *suspect);
  extern void set_git_work_tree(const char *tree);
  
@@@ -521,8 -509,7 +521,8 @@@ extern int write_locked_index(struct in
  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_dir_exists(struct index_state *istate, const char *name, int namelen);
 +extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
 +extern void adjust_dirname_case(struct index_state *istate, char *name);
  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 */
@@@ -598,6 -585,8 +598,6 @@@ extern void update_index_if_able(struc
  extern int hold_locked_index(struct lock_file *, int);
  extern void set_alternate_index_output(const char *);
  
 -extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags);
 -
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
  extern int trust_ctime;
@@@ -633,7 -622,6 +633,7 @@@ extern unsigned long pack_size_limit_cf
   * been sought but there were none.
   */
  extern int check_replace_refs;
 +extern char *git_replace_ref_base;
  
  extern int fsync_object_files;
  extern int core_preload_index;
@@@ -698,15 -686,8 +698,15 @@@ extern char *notes_ref_name
  
  extern int grafts_replace_parents;
  
 +/*
 + * GIT_REPO_VERSION is the version we write by default. The
 + * _READ variant is the highest number we know how to
 + * handle.
 + */
  #define GIT_REPO_VERSION 0
 +#define GIT_REPO_VERSION_READ 1
  extern int repository_format_version;
 +extern int repository_format_precious_objects;
  extern int check_repository_format(void);
  
  #define MTIME_CHANGED 0x0001
  #define DATA_CHANGED    0x0020
  #define TYPE_CHANGED    0x0040
  
 +/*
 + * Return a statically allocated filename, either generically (mkpath), in
 + * the repository directory (git_path), or in a submodule's repository
 + * directory (git_path_submodule). In all cases, note that the result
 + * may be overwritten by another call to _any_ of the functions. Consider
 + * using the safer "dup" or "strbuf" formats below (in some cases, the
 + * unsafe versions have already been removed).
 + */
 +extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +
  extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
  extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
 +extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 +      __attribute__((format (printf, 2, 3)));
 +extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
 +                                    const char *fmt, ...)
 +      __attribute__((format (printf, 3, 4)));
  extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
  extern char *mkpathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
 -
 -/* Return a statically allocated filename matching the sha1 signature */
 -extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern const char *git_path_submodule(const char *path, const char *fmt, ...)
 +extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
 +
  extern void report_linked_checkout_garbage(void);
  
 +/*
 + * You can define a static memoized git path like:
 + *
 + *    static GIT_PATH_FUNC(git_path_foo, "FOO");
 + *
 + * or use one of the global ones below.
 + */
 +#define GIT_PATH_FUNC(func, filename) \
 +      const char *func(void) \
 +      { \
 +              static char *ret; \
 +              if (!ret) \
 +                      ret = git_pathdup(filename); \
 +              return ret; \
 +      }
 +
 +const char *git_path_cherry_pick_head(void);
 +const char *git_path_revert_head(void);
 +const char *git_path_squash_msg(void);
 +const char *git_path_merge_msg(void);
 +const char *git_path_merge_rr(void);
 +const char *git_path_merge_mode(void);
 +const char *git_path_merge_head(void);
 +const char *git_path_fetch_head(void);
 +const char *git_path_shallow(void);
 +
  /*
   * Return the name of the file in the local object database that would
   * be used to store a loose object with the specified sha1.  The
@@@ -794,24 -736,7 +794,24 @@@ extern char *sha1_pack_name(const unsig
   */
  extern char *sha1_pack_index_name(const unsigned char *sha1);
  
 -extern const char *find_unique_abbrev(const unsigned char *sha1, int);
 +/*
 + * Return an abbreviated sha1 unique within this repository's object database.
 + * The result will be at least `len` characters long, and will be NUL
 + * terminated.
 + *
 + * The non-`_r` version returns a static buffer which will be overwritten by
 + * subsequent calls.
 + *
 + * The `_r` variant writes to a buffer supplied by the caller, which must be at
 + * least `GIT_SHA1_HEXSZ + 1` bytes. The return value is the number of bytes
 + * written (excluding the NUL terminator).
 + *
 + * Note that while this version avoids the static buffer, it is not fully
 + * reentrant, as it calls into other non-reentrant git code.
 + */
 +extern const char *find_unique_abbrev(const unsigned char *sha1, int len);
 +extern int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len);
 +
  extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
  
  static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
@@@ -1010,7 -935,7 +1010,7 @@@ extern int do_check_packed_object_crc
  
  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);
 +extern int finalize_object_file(const char *tmpfile, const char *filename);
  
  extern int has_sha1_pack(const unsigned char *sha1);
  
@@@ -1093,24 -1018,78 +1093,24 @@@ extern int for_each_abbrev(const char *
  extern int get_sha1_hex(const char *hex, unsigned char *sha1);
  extern int get_oid_hex(const char *hex, struct object_id *sha1);
  
 -extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
 -extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
 -extern int read_ref_full(const char *refname, int resolve_flags,
 -                       unsigned char *sha1, int *flags);
 -extern int read_ref(const char *refname, unsigned char *sha1);
 -
  /*
 - * Resolve a reference, recursively following symbolic refererences.
 - *
 - * Store the referred-to object's name in sha1 and return the name of
 - * the non-symbolic reference that ultimately pointed at it.  The
 - * return value, if not NULL, is a pointer into either a static buffer
 - * or the input ref.
 - *
 - * If the reference cannot be resolved to an object, the behavior
 - * depends on the RESOLVE_REF_READING flag:
 - *
 - * - If RESOLVE_REF_READING is set, return NULL.
 - *
 - * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
 - *   the last reference name in the chain, which will either be a non-symbolic
 - *   reference or an undefined reference.  If this is a prelude to
 - *   "writing" to the ref, the return value is the name of the ref
 - *   that will actually be created or changed.
 - *
 - * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
 - * level of symbolic reference.  The value stored in sha1 for a symbolic
 - * reference will always be null_sha1 in this case, and the return
 - * value is the reference that the symref refers to directly.
 - *
 - * If flags is non-NULL, set the value that it points to the
 - * combination of REF_ISPACKED (if the reference was found among the
 - * packed references), REF_ISSYMREF (if the initial reference was a
 - * symbolic reference), REF_BAD_NAME (if the reference name is ill
 - * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN
 - * (if the ref is malformed or has a bad name). See refs.h for more detail
 - * on each flag.
 + * Convert a binary sha1 to its hex equivalent. The `_r` variant is reentrant,
 + * and writes the NUL-terminated output to the buffer `out`, which must be at
 + * least `GIT_SHA1_HEXSZ + 1` bytes, and returns a pointer to out for
 + * convenience.
   *
 - * If ref is not a properly-formatted, normalized reference, return
 - * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
 - * give up and return NULL.
 + * The non-`_r` variant returns a static buffer, but uses a ring of 4
 + * buffers, making it safe to make multiple calls for a single statement, like:
   *
 - * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their
 - * name is invalid according to git-check-ref-format(1).  If the name
 - * is bad then the value stored in sha1 will be null_sha1 and the two
 - * flags REF_ISBROKEN and REF_BAD_NAME will be set.
 - *
 - * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/
 - * directory and do not consist of all caps and underscores cannot be
 - * resolved. The function returns NULL for such ref names.
 - * Caps and underscores refers to the special refs, such as HEAD,
 - * FETCH_HEAD and friends, that all live outside of the refs/ directory.
 + *   printf("%s -> %s", sha1_to_hex(one), sha1_to_hex(two));
   */
 -#define RESOLVE_REF_READING 0x01
 -#define RESOLVE_REF_NO_RECURSE 0x02
 -#define RESOLVE_REF_ALLOW_BAD_NAME 0x04
 -extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
 -extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
 -
 -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 char *sha1_to_hex_r(char *out, const unsigned char *sha1);
 +extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
 +extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
 +
  extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
 -/*
 - * 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);
  
  extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
@@@ -1126,30 -1105,18 +1126,30 @@@ extern void *read_object_with_reference
  extern struct object *peel_to_type(const char *name, int namelen,
                                   struct object *o, enum object_type);
  
 -enum date_mode {
 -      DATE_NORMAL = 0,
 -      DATE_RELATIVE,
 -      DATE_SHORT,
 -      DATE_LOCAL,
 -      DATE_ISO8601,
 -      DATE_ISO8601_STRICT,
 -      DATE_RFC2822,
 -      DATE_RAW
 +struct date_mode {
 +      enum date_mode_type {
 +              DATE_NORMAL = 0,
 +              DATE_RELATIVE,
 +              DATE_SHORT,
 +              DATE_ISO8601,
 +              DATE_ISO8601_STRICT,
 +              DATE_RFC2822,
 +              DATE_STRFTIME,
 +              DATE_RAW
 +      } type;
 +      const char *strftime_fmt;
 +      int local;
  };
  
 -const char *show_date(unsigned long time, int timezone, enum date_mode mode);
 +/*
 + * Convenience helper for passing a constant type, like:
 + *
 + *   show_date(t, tz, DATE_MODE(NORMAL));
 + */
 +#define DATE_MODE(t) date_mode_from_type(DATE_##t)
 +struct date_mode *date_mode_from_type(enum date_mode_type type);
 +
 +const char *show_date(unsigned long time, int timezone, const struct date_mode *mode);
  void show_date_relative(unsigned long time, int tz, const struct timeval *now,
                        struct strbuf *timebuf);
  int parse_date(const char *date, struct strbuf *out);
@@@ -1159,7 -1126,7 +1159,7 @@@ void datestamp(struct strbuf *out)
  #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);
 +void parse_date_format(const char *format, struct date_mode *mode);
  int date_overflows(unsigned long date);
  
  #define IDENT_STRICT         1
@@@ -1196,8 -1163,7 +1196,8 @@@ extern int split_ident_line(struct iden
   * the ident_split. It will also sanity-check the values and produce
   * a well-known sentinel date if they appear bogus.
   */
 -const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
 +const char *show_ident_date(const struct ident_split *id,
 +                          const struct date_mode *mode);
  
  /*
   * Compare split idents for equality or strict ordering. Note that we
@@@ -1289,8 -1255,11 +1289,11 @@@ struct pack_entry 
  
  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);
+ /* A hook to report invalid files in pack directory */
+ #define PACKDIR_FILE_PACK 1
+ #define PACKDIR_FILE_IDX 2
+ #define PACKDIR_FILE_GARBAGE 4
+ extern void (*report_garbage)(unsigned seen_bits, const char *path);
  
  extern void prepare_packed_git(void);
  extern void reprepare_packed_git(void);
@@@ -1315,11 -1284,10 +1318,11 @@@ extern void close_pack_index(struct pac
  
  extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
  extern void close_pack_windows(struct packed_git *);
 +extern void close_all_packs(void);
  extern void unuse_pack(struct pack_window **);
  extern void free_pack_by_name(const char *);
  extern void clear_delta_base_cache(void);
 -extern struct packed_git *add_packed_git(const char *, int, int);
 +extern struct packed_git *add_packed_git(const char *path, size_t path_len, int local);
  
  /*
   * Return the SHA-1 of the nth object within the specified packfile.
@@@ -1470,7 -1438,6 +1473,7 @@@ extern int git_config_with_options(conf
                                   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_parse_maybe_bool(const char *);
  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 *);
@@@ -1482,7 -1449,6 +1485,7 @@@ extern int git_config_pathname(const ch
  extern int git_config_set_in_file(const char *, const char *, const char *);
  extern int git_config_set(const char *, const char *);
  extern int git_config_parse_key(const char *, char **, int *);
 +extern int git_config_key_is_valid(const char *key);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
@@@ -1619,9 -1585,8 +1622,9 @@@ static inline ssize_t write_str_in_full
  {
        return write_in_full(fd, str, strlen(str));
  }
 -__attribute__((format (printf, 3, 4)))
 -extern int write_file(const char *path, int fatal, const char *fmt, ...);
 +
 +extern int write_file(const char *path, const char *fmt, ...);
 +extern int write_file_gently(const char *path, const char *fmt, ...);
  
  /* pager.c */
  extern void setup_pager(void);
diff --combined path.c
index c740c4ff9403bc92df4c439839bd5596ac62e48d,75ec2363fdd94a891e7177fd9469dbc17855ea99..f28ace2963bb3f0f76af0e363add22ed73a5a2c9
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -91,279 -91,59 +91,279 @@@ static void replace_dir(struct strbuf *
                buf->buf[newlen] = '/';
  }
  
 -static const char *common_list[] = {
 -      "/branches", "/hooks", "/info", "!/logs", "/lost-found",
 -      "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
 -      "config", "!gc.pid", "packed-refs", "shallow",
 -      NULL
 +struct common_dir {
 +      /* Not considered garbage for report_linked_checkout_garbage */
 +      unsigned ignore_garbage:1;
 +      unsigned is_dir:1;
 +      /* Not common even though its parent is */
 +      unsigned exclude:1;
 +      const char *dirname;
  };
  
 -static void update_common_dir(struct strbuf *buf, int git_dir_len)
 +static struct common_dir common_list[] = {
 +      { 0, 1, 0, "branches" },
 +      { 0, 1, 0, "hooks" },
 +      { 0, 1, 0, "info" },
 +      { 0, 0, 1, "info/sparse-checkout" },
 +      { 1, 1, 0, "logs" },
 +      { 1, 1, 1, "logs/HEAD" },
 +      { 0, 1, 1, "logs/refs/bisect" },
 +      { 0, 1, 0, "lost-found" },
 +      { 0, 1, 0, "objects" },
 +      { 0, 1, 0, "refs" },
 +      { 0, 1, 1, "refs/bisect" },
 +      { 0, 1, 0, "remotes" },
 +      { 0, 1, 0, "worktrees" },
 +      { 0, 1, 0, "rr-cache" },
 +      { 0, 1, 0, "svn" },
 +      { 0, 0, 0, "config" },
 +      { 1, 0, 0, "gc.pid" },
 +      { 0, 0, 0, "packed-refs" },
 +      { 0, 0, 0, "shallow" },
 +      { 0, 0, 0, NULL }
 +};
 +
 +/*
 + * A compressed trie.  A trie node consists of zero or more characters that
 + * are common to all elements with this prefix, optionally followed by some
 + * children.  If value is not NULL, the trie node is a terminal node.
 + *
 + * For example, consider the following set of strings:
 + * abc
 + * def
 + * definite
 + * definition
 + *
 + * The trie would look look like:
 + * root: len = 0, children a and d non-NULL, value = NULL.
 + *    a: len = 2, contents = bc, value = (data for "abc")
 + *    d: len = 2, contents = ef, children i non-NULL, value = (data for "def")
 + *       i: len = 3, contents = nit, children e and i non-NULL, value = NULL
 + *           e: len = 0, children all NULL, value = (data for "definite")
 + *           i: len = 2, contents = on, children all NULL,
 + *              value = (data for "definition")
 + */
 +struct trie {
 +      struct trie *children[256];
 +      int len;
 +      char *contents;
 +      void *value;
 +};
 +
 +static struct trie *make_trie_node(const char *key, void *value)
  {
 -      char *base = buf->buf + git_dir_len;
 -      const char **p;
 -
 -      if (is_dir_file(base, "logs", "HEAD") ||
 -          is_dir_file(base, "info", "sparse-checkout"))
 -              return; /* keep this in $GIT_DIR */
 -      for (p = common_list; *p; p++) {
 -              const char *path = *p;
 -              int is_dir = 0;
 -              if (*path == '!')
 -                      path++;
 -              if (*path == '/') {
 -                      path++;
 -                      is_dir = 1;
 +      struct trie *new_node = xcalloc(1, sizeof(*new_node));
 +      new_node->len = strlen(key);
 +      if (new_node->len) {
 +              new_node->contents = xmalloc(new_node->len);
 +              memcpy(new_node->contents, key, new_node->len);
 +      }
 +      new_node->value = value;
 +      return new_node;
 +}
 +
 +/*
 + * Add a key/value pair to a trie.  The key is assumed to be \0-terminated.
 + * If there was an existing value for this key, return it.
 + */
 +static void *add_to_trie(struct trie *root, const char *key, void *value)
 +{
 +      struct trie *child;
 +      void *old;
 +      int i;
 +
 +      if (!*key) {
 +              /* we have reached the end of the key */
 +              old = root->value;
 +              root->value = value;
 +              return old;
 +      }
 +
 +      for (i = 0; i < root->len; i++) {
 +              if (root->contents[i] == key[i])
 +                      continue;
 +
 +              /*
 +               * Split this node: child will contain this node's
 +               * existing children.
 +               */
 +              child = malloc(sizeof(*child));
 +              memcpy(child->children, root->children, sizeof(root->children));
 +
 +              child->len = root->len - i - 1;
 +              if (child->len) {
 +                      child->contents = xstrndup(root->contents + i + 1,
 +                                                 child->len);
                }
 -              if (is_dir && dir_prefix(base, path)) {
 -                      replace_dir(buf, git_dir_len, get_git_common_dir());
 -                      return;
 +              child->value = root->value;
 +              root->value = NULL;
 +              root->len = i;
 +
 +              memset(root->children, 0, sizeof(root->children));
 +              root->children[(unsigned char)root->contents[i]] = child;
 +
 +              /* This is the newly-added child. */
 +              root->children[(unsigned char)key[i]] =
 +                      make_trie_node(key + i + 1, value);
 +              return NULL;
 +      }
 +
 +      /* We have matched the entire compressed section */
 +      if (key[i]) {
 +              child = root->children[(unsigned char)key[root->len]];
 +              if (child) {
 +                      return add_to_trie(child, key + root->len + 1, value);
 +              } else {
 +                      child = make_trie_node(key + root->len + 1, value);
 +                      root->children[(unsigned char)key[root->len]] = child;
 +                      return NULL;
                }
 -              if (!is_dir && !strcmp(base, path)) {
 -                      replace_dir(buf, git_dir_len, get_git_common_dir());
 -                      return;
 +      }
 +
 +      old = root->value;
 +      root->value = value;
 +      return old;
 +}
 +
 +typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
 +
 +/*
 + * Search a trie for some key.  Find the longest /-or-\0-terminated
 + * prefix of the key for which the trie contains a value.  Call fn
 + * with the unmatched portion of the key and the found value, and
 + * return its return value.  If there is no such prefix, return -1.
 + *
 + * The key is partially normalized: consecutive slashes are skipped.
 + *
 + * For example, consider the trie containing only [refs,
 + * refs/worktree] (both with values).
 + *
 + * | key             | unmatched  | val from node | return value |
 + * |-----------------|------------|---------------|--------------|
 + * | a               | not called | n/a           | -1           |
 + * | refs            | \0         | refs          | as per fn    |
 + * | refs/           | /          | refs          | as per fn    |
 + * | refs/w          | /w         | refs          | as per fn    |
 + * | refs/worktree   | \0         | refs/worktree | as per fn    |
 + * | refs/worktree/  | /          | refs/worktree | as per fn    |
 + * | refs/worktree/a | /a         | refs/worktree | as per fn    |
 + * |-----------------|------------|---------------|--------------|
 + *
 + */
 +static int trie_find(struct trie *root, const char *key, match_fn fn,
 +                   void *baton)
 +{
 +      int i;
 +      int result;
 +      struct trie *child;
 +
 +      if (!*key) {
 +              /* we have reached the end of the key */
 +              if (root->value && !root->len)
 +                      return fn(key, root->value, baton);
 +              else
 +                      return -1;
 +      }
 +
 +      for (i = 0; i < root->len; i++) {
 +              /* Partial path normalization: skip consecutive slashes. */
 +              if (key[i] == '/' && key[i+1] == '/') {
 +                      key++;
 +                      continue;
                }
 +              if (root->contents[i] != key[i])
 +                      return -1;
        }
 +
 +      /* Matched the entire compressed section */
 +      key += i;
 +      if (!*key)
 +              /* End of key */
 +              return fn(key, root->value, baton);
 +
 +      /* Partial path normalization: skip consecutive slashes */
 +      while (key[0] == '/' && key[1] == '/')
 +              key++;
 +
 +      child = root->children[(unsigned char)*key];
 +      if (child)
 +              result = trie_find(child, key + 1, fn, baton);
 +      else
 +              result = -1;
 +
 +      if (result >= 0 || (*key != '/' && *key != 0))
 +              return result;
 +      if (root->value)
 +              return fn(key, root->value, baton);
 +      else
 +              return -1;
 +}
 +
 +static struct trie common_trie;
 +static int common_trie_done_setup;
 +
 +static void init_common_trie(void)
 +{
 +      struct common_dir *p;
 +
 +      if (common_trie_done_setup)
 +              return;
 +
 +      for (p = common_list; p->dirname; p++)
 +              add_to_trie(&common_trie, p->dirname, p);
 +
 +      common_trie_done_setup = 1;
 +}
 +
 +/*
 + * Helper function for update_common_dir: returns 1 if the dir
 + * prefix is common.
 + */
 +static int check_common(const char *unmatched, void *value, void *baton)
 +{
 +      struct common_dir *dir = value;
 +
 +      if (!dir)
 +              return 0;
 +
 +      if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
 +              return !dir->exclude;
 +
 +      if (!dir->is_dir && unmatched[0] == 0)
 +              return !dir->exclude;
 +
 +      return 0;
 +}
 +
 +static void update_common_dir(struct strbuf *buf, int git_dir_len,
 +                            const char *common_dir)
 +{
 +      char *base = buf->buf + git_dir_len;
 +      init_common_trie();
 +      if (!common_dir)
 +              common_dir = get_git_common_dir();
 +      if (trie_find(&common_trie, base, check_common, NULL) > 0)
 +              replace_dir(buf, git_dir_len, common_dir);
  }
  
  void report_linked_checkout_garbage(void)
  {
        struct strbuf sb = STRBUF_INIT;
 -      const char **p;
 +      const struct common_dir *p;
        int len;
  
        if (!git_common_dir_env)
                return;
        strbuf_addf(&sb, "%s/", get_git_dir());
        len = sb.len;
 -      for (p = common_list; *p; p++) {
 -              const char *path = *p;
 -              if (*path == '!')
 +      for (p = common_list; p->dirname; p++) {
 +              const char *path = p->dirname;
 +              if (p->ignore_garbage)
                        continue;
                strbuf_setlen(&sb, len);
                strbuf_addstr(&sb, path);
                if (file_exists(sb.buf))
-                       report_garbage("unused in linked checkout", sb.buf);
+                       report_garbage(PACKDIR_FILE_GARBAGE, sb.buf);
        }
        strbuf_release(&sb);
  }
@@@ -380,7 -160,7 +380,7 @@@ static void adjust_git_path(struct strb
        else if (git_db_env && dir_prefix(base, "objects"))
                replace_dir(buf, git_dir_len + 7, get_object_directory());
        else if (git_common_dir_env)
 -              update_common_dir(buf, git_dir_len);
 +              update_common_dir(buf, git_dir_len, NULL);
  }
  
  static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
        strbuf_cleanup_path(buf);
  }
  
 +char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 +{
 +      va_list args;
 +      strbuf_reset(buf);
 +      va_start(args, fmt);
 +      do_git_path(buf, fmt, args);
 +      va_end(args);
 +      return buf->buf;
 +}
 +
  void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
  {
        va_list args;
@@@ -454,15 -224,15 +454,15 @@@ const char *mkpath(const char *fmt, ...
        return cleanup_path(pathname->buf);
  }
  
 -const char *git_path_submodule(const char *path, const char *fmt, ...)
 +static void do_submodule_path(struct strbuf *buf, const char *path,
 +                            const char *fmt, va_list args)
  {
 -      struct strbuf *buf = get_pathname();
        const char *git_dir;
 -      va_list args;
 +      struct strbuf git_submodule_common_dir = STRBUF_INIT;
 +      struct strbuf git_submodule_dir = STRBUF_INIT;
  
        strbuf_addstr(buf, path);
 -      if (buf->len && buf->buf[buf->len - 1] != '/')
 -              strbuf_addch(buf, '/');
 +      strbuf_complete(buf, '/');
        strbuf_addstr(buf, ".git");
  
        git_dir = read_gitfile(buf->buf);
                strbuf_addstr(buf, git_dir);
        }
        strbuf_addch(buf, '/');
 +      strbuf_addstr(&git_submodule_dir, buf->buf);
  
 -      va_start(args, fmt);
        strbuf_vaddf(buf, fmt, args);
 -      va_end(args);
 +
 +      if (get_common_dir_noenv(&git_submodule_common_dir, git_submodule_dir.buf))
 +              update_common_dir(buf, git_submodule_dir.len, git_submodule_common_dir.buf);
 +
        strbuf_cleanup_path(buf);
 -      return buf->buf;
 +
 +      strbuf_release(&git_submodule_dir);
 +      strbuf_release(&git_submodule_common_dir);
 +}
 +
 +char *git_pathdup_submodule(const char *path, const char *fmt, ...)
 +{
 +      va_list args;
 +      struct strbuf buf = STRBUF_INIT;
 +      va_start(args, fmt);
 +      do_submodule_path(&buf, path, fmt, args);
 +      va_end(args);
 +      return strbuf_detach(&buf, NULL);
 +}
 +
 +void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
 +                             const char *fmt, ...)
 +{
 +      va_list args;
 +      va_start(args, fmt);
 +      do_submodule_path(buf, path, fmt, args);
 +      va_end(args);
  }
  
  int validate_headref(const char *path)
@@@ -620,8 -366,8 +620,8 @@@ return_null
   */
  const char *enter_repo(const char *path, int strict)
  {
 -      static char used_path[PATH_MAX];
 -      static char validated_path[PATH_MAX];
 +      static struct strbuf validated_path = STRBUF_INIT;
 +      static struct strbuf used_path = STRBUF_INIT;
  
        if (!path)
                return NULL;
                while ((1 < len) && (path[len-1] == '/'))
                        len--;
  
 +              /*
 +               * We can handle arbitrary-sized buffers, but this remains as a
 +               * sanity check on untrusted input.
 +               */
                if (PATH_MAX <= len)
                        return NULL;
 -              strncpy(used_path, path, len); used_path[len] = 0 ;
 -              strcpy(validated_path, used_path);
  
 -              if (used_path[0] == '~') {
 -                      char *newpath = expand_user_path(used_path);
 -                      if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
 -                              free(newpath);
 +              strbuf_reset(&used_path);
 +              strbuf_reset(&validated_path);
 +              strbuf_add(&used_path, path, len);
 +              strbuf_add(&validated_path, path, len);
 +
 +              if (used_path.buf[0] == '~') {
 +                      char *newpath = expand_user_path(used_path.buf);
 +                      if (!newpath)
                                return NULL;
 -                      }
 -                      /*
 -                       * Copy back into the static buffer. A pity
 -                       * since newpath was not bounded, but other
 -                       * branches of the if are limited by PATH_MAX
 -                       * anyway.
 -                       */
 -                      strcpy(used_path, newpath); free(newpath);
 +                      strbuf_attach(&used_path, newpath, strlen(newpath),
 +                                    strlen(newpath));
                }
 -              else if (PATH_MAX - 10 < len)
 -                      return NULL;
 -              len = strlen(used_path);
                for (i = 0; suffix[i]; i++) {
                        struct stat st;
 -                      strcpy(used_path + len, suffix[i]);
 -                      if (!stat(used_path, &st) &&
 +                      size_t baselen = used_path.len;
 +                      strbuf_addstr(&used_path, suffix[i]);
 +                      if (!stat(used_path.buf, &st) &&
                            (S_ISREG(st.st_mode) ||
 -                          (S_ISDIR(st.st_mode) && is_git_directory(used_path)))) {
 -                              strcat(validated_path, suffix[i]);
 +                          (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) {
 +                              strbuf_addstr(&validated_path, suffix[i]);
                                break;
                        }
 +                      strbuf_setlen(&used_path, baselen);
                }
                if (!suffix[i])
                        return NULL;
 -              gitfile = read_gitfile(used_path) ;
 +              gitfile = read_gitfile(used_path.buf);
 +              if (gitfile) {
 +                      strbuf_reset(&used_path);
 +                      strbuf_addstr(&used_path, gitfile);
 +              }
 +              if (chdir(used_path.buf))
 +                      return NULL;
 +              path = validated_path.buf;
 +      }
 +      else {
 +              const char *gitfile = read_gitfile(path);
                if (gitfile)
 -                      strcpy(used_path, gitfile);
 -              if (chdir(used_path))
 +                      path = gitfile;
 +              if (chdir(path))
                        return NULL;
 -              path = validated_path;
        }
 -      else if (chdir(path))
 -              return NULL;
  
 -      if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
 -          validate_headref("HEAD") == 0) {
 +      if (is_git_directory(".")) {
                set_git_dir(".");
                check_repository_format();
                return path;
@@@ -865,7 -606,7 +865,7 @@@ const char *relative_path(const char *i
   */
  const char *remove_leading_path(const char *in, const char *prefix)
  {
 -      static char buf[PATH_MAX + 1];
 +      static struct strbuf buf = STRBUF_INIT;
        int i = 0, j = 0;
  
        if (!prefix || !prefix[0])
                return in;
        while (is_dir_sep(in[j]))
                j++;
 +
 +      strbuf_reset(&buf);
        if (!in[j])
 -              strcpy(buf, ".");
 +              strbuf_addstr(&buf, ".");
        else
 -              strcpy(buf, in + j);
 -      return buf;
 +              strbuf_addstr(&buf, in + j);
 +      return buf.buf;
  }
  
  /*
   * normalized, any time "../" eats up to the prefix_len part,
   * prefix_len is reduced. In the end prefix_len is the remaining
   * prefix that has not been overridden by user pathspec.
 + *
 + * NEEDSWORK: This function doesn't perform normalization w.r.t. trailing '/'.
 + * For everything but the root folder itself, the normalized path should not
 + * end with a '/', then the callers need to be fixed up accordingly.
 + *
   */
  int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
  {
@@@ -1184,13 -918,3 +1184,13 @@@ char *xdg_config_home(const char *filen
                return mkpathdup("%s/.config/git/%s", home, filename);
        return NULL;
  }
 +
 +GIT_PATH_FUNC(git_path_cherry_pick_head, "CHERRY_PICK_HEAD")
 +GIT_PATH_FUNC(git_path_revert_head, "REVERT_HEAD")
 +GIT_PATH_FUNC(git_path_squash_msg, "SQUASH_MSG")
 +GIT_PATH_FUNC(git_path_merge_msg, "MERGE_MSG")
 +GIT_PATH_FUNC(git_path_merge_rr, "MERGE_RR")
 +GIT_PATH_FUNC(git_path_merge_mode, "MERGE_MODE")
 +GIT_PATH_FUNC(git_path_merge_head, "MERGE_HEAD")
 +GIT_PATH_FUNC(git_path_fetch_head, "FETCH_HEAD")
 +GIT_PATH_FUNC(git_path_shallow, "shallow")
diff --combined sha1_file.c
index c5b31de9aa579dde37e5345d207995416f261eed,0c0b6529490a7d5908cd0ed62f17ed33ada86548..3d56746a9be12e78c2b5262948bd411acf3230a2
@@@ -208,25 -208,44 +208,25 @@@ const char *sha1_file_name(const unsign
   * provided by the caller.  which should be "pack" or "idx".
   */
  static char *sha1_get_pack_name(const unsigned char *sha1,
 -                              char **name, char **base, const char *which)
 +                              struct strbuf *buf,
 +                              const char *which)
  {
 -      static const char hex[] = "0123456789abcdef";
 -      char *buf;
 -      int i;
 -
 -      if (!*base) {
 -              const char *sha1_file_directory = get_object_directory();
 -              int len = strlen(sha1_file_directory);
 -              *base = xmalloc(len + 60);
 -              sprintf(*base, "%s/pack/pack-1234567890123456789012345678901234567890.%s",
 -                      sha1_file_directory, which);
 -              *name = *base + len + 11;
 -      }
 -
 -      buf = *name;
 -
 -      for (i = 0; i < 20; i++) {
 -              unsigned int val = *sha1++;
 -              *buf++ = hex[val >> 4];
 -              *buf++ = hex[val & 0xf];
 -      }
 -
 -      return *base;
 +      strbuf_reset(buf);
 +      strbuf_addf(buf, "%s/pack/pack-%s.%s", get_object_directory(),
 +                  sha1_to_hex(sha1), which);
 +      return buf->buf;
  }
  
  char *sha1_pack_name(const unsigned char *sha1)
  {
 -      static char *name, *base;
 -
 -      return sha1_get_pack_name(sha1, &name, &base, "pack");
 +      static struct strbuf buf = STRBUF_INIT;
 +      return sha1_get_pack_name(sha1, &buf, "pack");
  }
  
  char *sha1_pack_index_name(const unsigned char *sha1)
  {
 -      static char *name, *base;
 -
 -      return sha1_get_pack_name(sha1, &name, &base, "idx");
 +      static struct strbuf buf = STRBUF_INIT;
 +      return sha1_get_pack_name(sha1, &buf, "idx");
  }
  
  struct alternate_object_database *alt_odb_list;
@@@ -358,12 -377,15 +358,12 @@@ void read_info_alternates(const char * 
        char *map;
        size_t mapsz;
        struct stat st;
 -      const char alt_file_name[] = "info/alternates";
 -      /* Given that relative_base is no longer than PATH_MAX,
 -         ensure that "path" has enough space to append "/", the
 -         file name, "info/alternates", and a trailing NUL.  */
 -      char path[PATH_MAX + 1 + sizeof alt_file_name];
 +      char *path;
        int fd;
  
 -      sprintf(path, "%s/%s", relative_base, alt_file_name);
 +      path = xstrfmt("%s/info/alternates", relative_base);
        fd = git_open_noatime(path);
 +      free(path);
        if (fd < 0)
                return;
        if (fstat(fd, &st) || (st.st_size == 0)) {
  void add_to_alternates_file(const char *reference)
  {
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 -      int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
 -      const char *alt = mkpath("%s\n", reference);
 -      write_or_die(fd, alt, strlen(alt));
 -      if (commit_lock_file(lock))
 -              die("could not close alternates file");
 -      if (alt_odb_tail)
 -              link_alt_odb_entries(alt, strlen(alt), '\n', NULL, 0);
 +      char *alts = git_pathdup("objects/info/alternates");
 +      FILE *in, *out;
 +
 +      hold_lock_file_for_update(lock, alts, LOCK_DIE_ON_ERROR);
 +      out = fdopen_lock_file(lock, "w");
 +      if (!out)
 +              die_errno("unable to fdopen alternates lockfile");
 +
 +      in = fopen(alts, "r");
 +      if (in) {
 +              struct strbuf line = STRBUF_INIT;
 +              int found = 0;
 +
 +              while (strbuf_getline(&line, in, '\n') != EOF) {
 +                      if (!strcmp(reference, line.buf)) {
 +                              found = 1;
 +                              break;
 +                      }
 +                      fprintf_or_die(out, "%s\n", line.buf);
 +              }
 +
 +              strbuf_release(&line);
 +              fclose(in);
 +
 +              if (found) {
 +                      rollback_lock_file(lock);
 +                      lock = NULL;
 +              }
 +      }
 +      else if (errno != ENOENT)
 +              die_errno("unable to read alternates file");
 +
 +      if (lock) {
 +              fprintf_or_die(out, "%s\n", reference);
 +              if (commit_lock_file(lock))
 +                      die_errno("unable to move new alternates file into place");
 +              if (alt_odb_tail)
 +                      link_alt_odb_entries(reference, strlen(reference), '\n', NULL, 0);
 +      }
 +      free(alts);
  }
  
  int foreach_alt_odb(alt_odb_fn fn, void *cb)
@@@ -652,15 -641,13 +652,15 @@@ static int check_packed_git_idx(const c
  int open_pack_index(struct packed_git *p)
  {
        char *idx_name;
 +      size_t len;
        int ret;
  
        if (p->index_data)
                return 0;
  
 -      idx_name = xstrdup(p->pack_name);
 -      strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx");
 +      if (!strip_suffix(p->pack_name, ".pack", &len))
 +              die("BUG: pack_name does not end in .pack");
 +      idx_name = xstrfmt("%.*s.idx", (int)len, p->pack_name);
        ret = check_packed_git_idx(idx_name, p);
        free(idx_name);
        return ret;
@@@ -769,37 -756,6 +769,37 @@@ void close_pack_windows(struct packed_g
        }
  }
  
 +static int close_pack_fd(struct packed_git *p)
 +{
 +      if (p->pack_fd < 0)
 +              return 0;
 +
 +      close(p->pack_fd);
 +      pack_open_fds--;
 +      p->pack_fd = -1;
 +
 +      return 1;
 +}
 +
 +static void close_pack(struct packed_git *p)
 +{
 +      close_pack_windows(p);
 +      close_pack_fd(p);
 +      close_pack_index(p);
 +}
 +
 +void close_all_packs(void)
 +{
 +      struct packed_git *p;
 +
 +      for (p = packed_git; p; p = p->next)
 +              if (p->do_not_close)
 +                      die("BUG! Want to close pack marked 'do-not-close'");
 +              else
 +                      close_pack(p);
 +}
 +
 +
  /*
   * The LRU pack is the one with the oldest MRU window, preferring packs
   * with no used windows, or the oldest mtime if it has no windows allocated.
@@@ -867,8 -823,12 +867,8 @@@ static int close_one_pack(void
                find_lru_pack(p, &lru_p, &mru_w, &accept_windows_inuse);
        }
  
 -      if (lru_p) {
 -              close(lru_p->pack_fd);
 -              pack_open_fds--;
 -              lru_p->pack_fd = -1;
 -              return 1;
 -      }
 +      if (lru_p)
 +              return close_pack_fd(lru_p);
  
        return 0;
  }
@@@ -908,7 -868,12 +908,7 @@@ void free_pack_by_name(const char *pack
                p = *pp;
                if (strcmp(pack_name, p->pack_name) == 0) {
                        clear_delta_base_cache();
 -                      close_pack_windows(p);
 -                      if (p->pack_fd != -1) {
 -                              close(p->pack_fd);
 -                              pack_open_fds--;
 -                      }
 -                      close_pack_index(p);
 +                      close_pack(p);
                        free(p->bad_object_sha1);
                        *pp = p->next;
                        if (last_found_pack == p)
@@@ -1042,7 -1007,11 +1042,7 @@@ static int open_packed_git(struct packe
  {
        if (!open_packed_git_1(p))
                return 0;
 -      if (p->pack_fd != -1) {
 -              close(p->pack_fd);
 -              pack_open_fds--;
 -              p->pack_fd = -1;
 -      }
 +      close_pack_fd(p);
        return -1;
  }
  
@@@ -1108,8 -1077,11 +1108,8 @@@ unsigned char *use_pack(struct packed_g
                                        p->pack_name,
                                        strerror(errno));
                        if (!win->offset && win->len == p->pack_size
 -                              && !p->do_not_close) {
 -                              close(p->pack_fd);
 -                              pack_open_fds--;
 -                              p->pack_fd = -1;
 -                      }
 +                              && !p->do_not_close)
 +                              close_pack_fd(p);
                        pack_mmap_calls++;
                        pack_open_windows++;
                        if (pack_mapped > peak_pack_mapped)
@@@ -1144,12 -1116,11 +1144,12 @@@ static void try_to_free_pack_memory(siz
        release_pack_memory(size);
  }
  
 -struct packed_git *add_packed_git(const char *path, int path_len, int local)
 +struct packed_git *add_packed_git(const char *path, size_t path_len, int local)
  {
        static int have_set_try_to_free_routine;
        struct stat st;
 -      struct packed_git *p = alloc_packed_git(path_len + 2);
 +      size_t alloc;
 +      struct packed_git *p;
  
        if (!have_set_try_to_free_routine) {
                have_set_try_to_free_routine = 1;
         * Make sure a corresponding .pack file exists and that
         * the index looks sane.
         */
 -      path_len -= strlen(".idx");
 -      if (path_len < 1) {
 -              free(p);
 +      if (!strip_suffix_mem(path, &path_len, ".idx"))
                return NULL;
 -      }
 +
 +      /*
 +       * ".pack" is long enough to hold any suffix we're adding (and
 +       * the use xsnprintf double-checks that)
 +       */
 +      alloc = path_len + strlen(".pack") + 1;
 +      p = alloc_packed_git(alloc);
        memcpy(p->pack_name, path, path_len);
  
 -      strcpy(p->pack_name + path_len, ".keep");
 +      xsnprintf(p->pack_name + path_len, alloc - path_len, ".keep");
        if (!access(p->pack_name, F_OK))
                p->pack_keep = 1;
  
 -      strcpy(p->pack_name + path_len, ".pack");
 +      xsnprintf(p->pack_name + path_len, alloc - path_len, ".pack");
        if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) {
                free(p);
                return NULL;
  struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path)
  {
        const char *path = sha1_pack_name(sha1);
 -      struct packed_git *p = alloc_packed_git(strlen(path) + 1);
 +      int alloc = strlen(path) + 1;
 +      struct packed_git *p = alloc_packed_git(alloc);
  
 -      strcpy(p->pack_name, path);
 +      memcpy(p->pack_name, path, alloc); /* includes NUL */
        hashcpy(p->sha1, sha1);
        if (check_packed_git_idx(idx_path, p)) {
                free(p);
@@@ -1217,27 -1183,16 +1217,16 @@@ void install_packed_git(struct packed_g
        packed_git = pack;
  }
  
- void (*report_garbage)(const char *desc, const char *path);
+ void (*report_garbage)(unsigned seen_bits, const char *path);
  
  static void report_helper(const struct string_list *list,
                          int seen_bits, int first, int last)
  {
-       const char *msg;
-       switch (seen_bits) {
-       case 0:
-               msg = "no corresponding .idx or .pack";
-               break;
-       case 1:
-               msg = "no corresponding .idx";
-               break;
-       case 2:
-               msg = "no corresponding .pack";
-               break;
-       default:
+       if (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX))
                return;
-       }
        for (; first < last; first++)
-               report_garbage(msg, list->items[first].string);
+               report_garbage(seen_bits, list->items[first].string);
  }
  
  static void report_pack_garbage(struct string_list *list)
                if (baselen == -1) {
                        const char *dot = strrchr(path, '.');
                        if (!dot) {
-                               report_garbage("garbage found", path);
+                               report_garbage(PACKDIR_FILE_GARBAGE, path);
                                continue;
                        }
                        baselen = dot - path + 1;
@@@ -1332,7 -1287,7 +1321,7 @@@ static void prepare_packed_git_one(cha
                    ends_with(de->d_name, ".keep"))
                        string_list_append(&garbage, path.buf);
                else
-                       report_garbage("garbage found", path.buf);
+                       report_garbage(PACKDIR_FILE_GARBAGE, path.buf);
        }
        closedir(dir);
        report_pack_garbage(&garbage);
@@@ -1468,7 -1423,7 +1457,7 @@@ int check_sha1_signature(const unsigne
                return -1;
  
        /* Generate the header */
 -      hdrlen = sprintf(hdr, "%s %lu", typename(obj_type), size) + 1;
 +      hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(obj_type), size) + 1;
  
        /* Sha1.. */
        git_SHA1_Init(&c);
@@@ -1495,10 -1450,7 +1484,10 @@@ int git_open_noatime(const char *name
        static int sha1_file_open_flag = O_NOATIME;
  
        for (;;) {
 -              int fd = open(name, O_RDONLY | sha1_file_open_flag);
 +              int fd;
 +
 +              errno = 0;
 +              fd = open(name, O_RDONLY | sha1_file_open_flag);
                if (fd >= 0)
                        return fd;
  
@@@ -2126,7 -2078,7 +2115,7 @@@ static unsigned long pack_entry_hash(st
  {
        unsigned long hash;
  
 -      hash = (unsigned long)p + (unsigned long)base_offset;
 +      hash = (unsigned long)(intptr_t)p + (unsigned long)base_offset;
        hash += (hash >> 8) + (hash >> 16);
        return hash % MAX_DELTA_CACHE;
  }
@@@ -2934,7 -2886,7 +2923,7 @@@ static void write_sha1_file_prepare(con
        git_SHA_CTX c;
  
        /* Generate the header */
 -      *hdrlen = sprintf(hdr, "%s %lu", type, len)+1;
 +      *hdrlen = xsnprintf(hdr, *hdrlen, "%s %lu", type, len)+1;
  
        /* Sha1.. */
        git_SHA1_Init(&c);
  
  /*
   * Move the just written object into its final resting place.
 - * NEEDSWORK: this should be renamed to finalize_temp_file() as
 - * "moving" is only a part of what it does, when no patch between
 - * master to pu changes the call sites of this function.
   */
 -int move_temp_to_file(const char *tmpfile, const char *filename)
 +int finalize_object_file(const char *tmpfile, const char *filename)
  {
        int ret = 0;
  
@@@ -2997,7 -2952,7 +2986,7 @@@ int hash_sha1_file(const void *buf, uns
                     unsigned char *sha1)
  {
        char hdr[32];
 -      int hdrlen;
 +      int hdrlen = sizeof(hdr);
        write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
        return 0;
  }
@@@ -3027,31 -2982,29 +3016,31 @@@ static inline int directory_size(const 
   * We want to avoid cross-directory filename renames, because those
   * can have problems on various filesystems (FAT, NFS, Coda).
   */
 -static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename)
 +static int create_tmpfile(struct strbuf *tmp, const char *filename)
  {
        int fd, dirlen = directory_size(filename);
  
 -      if (dirlen + 20 > bufsiz) {
 -              errno = ENAMETOOLONG;
 -              return -1;
 -      }
 -      memcpy(buffer, filename, dirlen);
 -      strcpy(buffer + dirlen, "tmp_obj_XXXXXX");
 -      fd = git_mkstemp_mode(buffer, 0444);
 +      strbuf_reset(tmp);
 +      strbuf_add(tmp, filename, dirlen);
 +      strbuf_addstr(tmp, "tmp_obj_XXXXXX");
 +      fd = git_mkstemp_mode(tmp->buf, 0444);
        if (fd < 0 && dirlen && errno == ENOENT) {
 -              /* Make sure the directory exists */
 -              memcpy(buffer, filename, dirlen);
 -              buffer[dirlen-1] = 0;
 -              if (mkdir(buffer, 0777) && errno != EEXIST)
 +              /*
 +               * Make sure the directory exists; note that the contents
 +               * of the buffer are undefined after mkstemp returns an
 +               * error, so we have to rewrite the whole buffer from
 +               * scratch.
 +               */
 +              strbuf_reset(tmp);
 +              strbuf_add(tmp, filename, dirlen - 1);
 +              if (mkdir(tmp->buf, 0777) && errno != EEXIST)
                        return -1;
 -              if (adjust_shared_perm(buffer))
 +              if (adjust_shared_perm(tmp->buf))
                        return -1;
  
                /* Try again */
 -              strcpy(buffer + dirlen - 1, "/tmp_obj_XXXXXX");
 -              fd = git_mkstemp_mode(buffer, 0444);
 +              strbuf_addstr(tmp, "/tmp_obj_XXXXXX");
 +              fd = git_mkstemp_mode(tmp->buf, 0444);
        }
        return fd;
  }
@@@ -3064,10 -3017,10 +3053,10 @@@ static int write_loose_object(const uns
        git_zstream stream;
        git_SHA_CTX c;
        unsigned char parano_sha1[20];
 -      static char tmp_file[PATH_MAX];
 +      static struct strbuf tmp_file = STRBUF_INIT;
        const char *filename = sha1_file_name(sha1);
  
 -      fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
 +      fd = create_tmpfile(&tmp_file, filename);
        if (fd < 0) {
                if (errno == EACCES)
                        return error("insufficient permission for adding an object to repository database %s", get_object_directory());
                struct utimbuf utb;
                utb.actime = mtime;
                utb.modtime = mtime;
 -              if (utime(tmp_file, &utb) < 0)
 +              if (utime(tmp_file.buf, &utb) < 0)
                        warning("failed utime() on %s: %s",
 -                              tmp_file, strerror(errno));
 +                              tmp_file.buf, strerror(errno));
        }
  
 -      return move_temp_to_file(tmp_file, filename);
 +      return finalize_object_file(tmp_file.buf, filename);
  }
  
  static int freshen_loose_object(const unsigned char *sha1)
@@@ -3145,7 -3098,7 +3134,7 @@@ static int freshen_packed_object(const 
  int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1)
  {
        char hdr[32];
 -      int hdrlen;
 +      int hdrlen = sizeof(hdr);
  
        /* Normally if we have it in the pack then we do not bother writing
         * it out into .git/objects/??/?{38} file.
@@@ -3163,8 -3116,7 +3152,8 @@@ int hash_sha1_file_literally(const voi
        int hdrlen, status = 0;
  
        /* type string, SP, %lu of the length plus NUL must fit this */
 -      header = xmalloc(strlen(type) + 32);
 +      hdrlen = strlen(type) + 32;
 +      header = xmalloc(hdrlen);
        write_sha1_file_prepare(buf, len, type, sha1, header, &hdrlen);
  
        if (!(flags & HASH_WRITE_OBJECT))
@@@ -3192,7 -3144,7 +3181,7 @@@ int force_object_loose(const unsigned c
        buf = read_packed_sha1(sha1, &type, &len);
        if (!buf)
                return error("cannot read sha1_file for %s", sha1_to_hex(sha1));
 -      hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
 +      hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), len) + 1;
        ret = write_loose_object(sha1, hdr, hdrlen, buf, len, mtime);
        free(buf);