Merge branch 'jk/war-on-sprintf'
authorJunio C Hamano <gitster@pobox.com>
Tue, 20 Oct 2015 22:24:00 +0000 (15:24 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 20 Oct 2015 22:24:01 +0000 (15:24 -0700)
Many allocations that is manually counted (correctly) that are
followed by strcpy/sprintf have been replaced with a less error
prone constructs such as xstrfmt.

Macintosh-specific breakage was noticed and corrected in this
reroll.

* jk/war-on-sprintf: (70 commits)
name-rev: use strip_suffix to avoid magic numbers
use strbuf_complete to conditionally append slash
fsck: use for_each_loose_file_in_objdir
Makefile: drop D_INO_IN_DIRENT build knob
fsck: drop inode-sorting code
convert strncpy to memcpy
notes: document length of fanout path with a constant
color: add color_set helper for copying raw colors
prefer memcpy to strcpy
help: clean up kfmclient munging
receive-pack: simplify keep_arg computation
avoid sprintf and strcpy with flex arrays
use alloc_ref rather than hand-allocating "struct ref"
color: add overflow checks for parsing colors
drop strcpy in favor of raw sha1_to_hex
use sha1_to_hex_r() instead of strcpy
daemon: use cld->env_array when re-spawning
stat_tracking_info: convert to argv_array
http-push: use an argv_array for setup_revisions
fetch-pack: use argv_array for index-pack / unpack-objects
...

20 files changed:
1  2 
Makefile
builtin/blame.c
builtin/fsck.c
builtin/gc.c
builtin/ls-remote.c
cache.h
connect.c
diff.c
dir.c
fast-import.c
git-compat-util.h
http.c
ll-merge.c
path.c
ref-filter.c
refs.c
setup.c
sha1_file.c
submodule.c
transport.c
diff --combined Makefile
index 36bc54ccd13f8c8048a9fe00f3b2763fdf81278a,2f350cae3538e23af8caf4b89f849d06bae2f286..0d9f5dddbc68e2f7fc2c5c5a2f27f2c30466a32e
+++ b/Makefile
@@@ -74,8 -74,6 +74,6 @@@ all:
  # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
  # it specifies.
  #
- # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
- #
  # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
  # d_type in struct dirent (Cygwin 1.5, fixed in Cygwin 1.7).
  #
@@@ -375,9 -373,6 +373,9 @@@ ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS
  ALL_LDFLAGS = $(LDFLAGS)
  STRIP ?= strip
  
 +# Create as necessary, replace existing, make ranlib unneeded.
 +ARFLAGS = rcs
 +
  # Among the variables below, these:
  #   gitexecdir
  #   template_dir
@@@ -905,7 -900,6 +903,7 @@@ BUILTIN_OBJS += builtin/shortlog.
  BUILTIN_OBJS += builtin/show-branch.o
  BUILTIN_OBJS += builtin/show-ref.o
  BUILTIN_OBJS += builtin/stripspace.o
 +BUILTIN_OBJS += builtin/submodule--helper.o
  BUILTIN_OBJS += builtin/symbolic-ref.o
  BUILTIN_OBJS += builtin/tag.o
  BUILTIN_OBJS += builtin/unpack-file.o
@@@ -1164,9 -1158,6 +1162,6 @@@ endi
  ifdef NO_D_TYPE_IN_DIRENT
        BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
  endif
- ifdef NO_D_INO_IN_DIRENT
-       BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
- endif
  ifdef NO_GECOS_IN_PWENT
        BASIC_CFLAGS += -DNO_GECOS_IN_PWENT
  endif
@@@ -1469,13 -1460,13 +1464,13 @@@ endi
  QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
  QUIET_SUBDIR1  =
  
 -ifneq ($(findstring $(MAKEFLAGS),w),w)
 +ifneq ($(findstring w,$(MAKEFLAGS)),w)
  PRINT_DIR = --no-print-directory
  else # "make -w"
  NO_SUBDIR = :
  endif
  
 -ifneq ($(findstring $(MAKEFLAGS),s),s)
 +ifneq ($(findstring s,$(MAKEFLAGS)),s)
  ifndef V
        QUIET_CC       = @echo '   ' CC $@;
        QUIET_AR       = @echo '   ' AR $@;
@@@ -1999,13 -1990,13 +1994,13 @@@ $(REMOTE_CURL_PRIMARY): remote-curl.o h
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
  
  $(LIB_FILE): $(LIB_OBJS)
 -      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
 +      $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
  
  $(XDIFF_LIB): $(XDIFF_OBJS)
 -      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
 +      $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
  
  $(VCSSVN_LIB): $(VCSSVN_OBJS)
 -      $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
 +      $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
  
  export DEFAULT_EDITOR DEFAULT_PAGER
  
diff --combined builtin/blame.c
index 203a981cd083a0ea971c7315f60f04e721d1e468,e70fb6dac3bd24ce4ea8abf355c1c50c9a669329..6fc7bff9a37e7b9acf24eb65546dfb8e3c87c8fb
@@@ -459,12 -459,13 +459,13 @@@ static void queue_blames(struct scorebo
  static struct origin *make_origin(struct commit *commit, const char *path)
  {
        struct origin *o;
-       o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+       size_t pathlen = strlen(path) + 1;
+       o = xcalloc(1, sizeof(*o) + pathlen);
        o->commit = commit;
        o->refcnt = 1;
        o->next = commit->util;
        commit->util = o;
-       strcpy(o->path, path);
+       memcpy(o->path, path, pathlen); /* includes NUL */
        return o;
  }
  
@@@ -974,10 -975,7 +975,10 @@@ static void pass_blame_to_parent(struc
        fill_origin_blob(&sb->revs->diffopt, target, &file_o);
        num_get_patch++;
  
 -      diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
 +      if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d))
 +              die("unable to generate diff (%s -> %s)",
 +                  sha1_to_hex(parent->commit->object.sha1),
 +                  sha1_to_hex(target->commit->object.sha1));
        /* The rest are the same as the parent */
        blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
        *d.dstq = NULL;
@@@ -1123,9 -1121,7 +1124,9 @@@ static void find_copy_in_blob(struct sc
         * file_p partially may match that image.
         */
        memset(split, 0, sizeof(struct blame_entry [3]));
 -      diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
 +      if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d))
 +              die("unable to generate diff (%s)",
 +                  sha1_to_hex(parent->commit->object.sha1));
        /* remainder, if any, all match the preimage */
        handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
  }
@@@ -1371,15 -1367,8 +1372,15 @@@ static void pass_whole_blame(struct sco
   */
  static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
  {
 -      if (!reverse)
 +      if (!reverse) {
 +              if (revs->first_parent_only &&
 +                  commit->parents &&
 +                  commit->parents->next) {
 +                      free_commit_list(commit->parents->next);
 +                      commit->parents->next = NULL;
 +              }
                return commit->parents;
 +      }
        return lookup_decoration(&revs->children, &commit->object);
  }
  
@@@ -1879,9 -1868,9 +1880,9 @@@ static void emit_porcelain(struct score
        int cnt;
        const char *cp;
        struct origin *suspect = ent->suspect;
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
  
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       sha1_to_hex_r(hex, suspect->commit->object.sha1);
        printf("%s %d %d %d\n",
               hex,
               ent->s_lno + 1,
@@@ -1917,11 -1906,11 +1918,11 @@@ static void emit_other(struct scoreboar
        const char *cp;
        struct origin *suspect = ent->suspect;
        struct commit_info ci;
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
  
        get_commit_info(suspect->commit, &ci, 1);
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       sha1_to_hex_r(hex, suspect->commit->object.sha1);
  
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
@@@ -2612,6 -2601,7 +2613,6 @@@ parse_done
                   fewer display columns. */
                blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
                break;
 -      case DATE_LOCAL:
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
        }
        else if (contents_from)
                die("--contents and --children do not blend well.");
 +      else if (revs.first_parent_only)
 +              die("combining --first-parent and --reverse is not supported");
        else {
                final_commit_name = prepare_initial(&sb);
                sb.commits.compare = compare_commits_by_reverse_commit_date;
diff --combined builtin/fsck.c
index b9a74f0cf6e169073994e466110fd1bde9febba7,d50efd5e8c158f945a4d0c02d3086927a3ab617b..8b8bb42c51d13ce9263c0a6fd30fa36810fef78c
@@@ -38,16 -38,7 +38,8 @@@ static int show_dangling = 1
  #define ERROR_OBJECT 01
  #define ERROR_REACHABLE 02
  #define ERROR_PACK 04
 +#define ERROR_REFS 010
  
- #ifdef NO_D_INO_IN_DIRENT
- #define SORT_DIRENT 0
- #define DIRENT_SORT_HINT(de) 0
- #else
- #define SORT_DIRENT 1
- #define DIRENT_SORT_HINT(de) ((de)->d_ino)
- #endif
  static int fsck_config(const char *var, const char *value, void *cb)
  {
        if (strcmp(var, "fsck.skiplist") == 0) {
@@@ -374,102 -365,6 +366,6 @@@ static int fsck_obj_buffer(const unsign
        return fsck_obj(obj);
  }
  
- /*
-  * This is the sorting chunk size: make it reasonably
-  * big so that we can sort well..
-  */
- #define MAX_SHA1_ENTRIES (1024)
- struct sha1_entry {
-       unsigned long ino;
-       unsigned char sha1[20];
- };
- static struct {
-       unsigned long nr;
-       struct sha1_entry *entry[MAX_SHA1_ENTRIES];
- } sha1_list;
- static int ino_compare(const void *_a, const void *_b)
- {
-       const struct sha1_entry *a = _a, *b = _b;
-       unsigned long ino1 = a->ino, ino2 = b->ino;
-       return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
- }
- static void fsck_sha1_list(void)
- {
-       int i, nr = sha1_list.nr;
-       if (SORT_DIRENT)
-               qsort(sha1_list.entry, nr,
-                     sizeof(struct sha1_entry *), ino_compare);
-       for (i = 0; i < nr; i++) {
-               struct sha1_entry *entry = sha1_list.entry[i];
-               unsigned char *sha1 = entry->sha1;
-               sha1_list.entry[i] = NULL;
-               if (fsck_sha1(sha1))
-                       errors_found |= ERROR_OBJECT;
-               free(entry);
-       }
-       sha1_list.nr = 0;
- }
- static void add_sha1_list(unsigned char *sha1, unsigned long ino)
- {
-       struct sha1_entry *entry = xmalloc(sizeof(*entry));
-       int nr;
-       entry->ino = ino;
-       hashcpy(entry->sha1, sha1);
-       nr = sha1_list.nr;
-       if (nr == MAX_SHA1_ENTRIES) {
-               fsck_sha1_list();
-               nr = 0;
-       }
-       sha1_list.entry[nr] = entry;
-       sha1_list.nr = ++nr;
- }
- static inline int is_loose_object_file(struct dirent *de,
-                                      char *name, unsigned char *sha1)
- {
-       if (strlen(de->d_name) != 38)
-               return 0;
-       memcpy(name + 2, de->d_name, 39);
-       return !get_sha1_hex(name, sha1);
- }
- static void fsck_dir(int i, char *path)
- {
-       DIR *dir = opendir(path);
-       struct dirent *de;
-       char name[100];
-       if (!dir)
-               return;
-       if (verbose)
-               fprintf(stderr, "Checking directory %s\n", path);
-       sprintf(name, "%02x", i);
-       while ((de = readdir(dir)) != NULL) {
-               unsigned char sha1[20];
-               if (is_dot_or_dotdot(de->d_name))
-                       continue;
-               if (is_loose_object_file(de, name, sha1)) {
-                       add_sha1_list(sha1, DIRENT_SORT_HINT(de));
-                       continue;
-               }
-               if (starts_with(de->d_name, "tmp_obj_"))
-                       continue;
-               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
-       }
-       closedir(dir);
- }
  static int default_refs;
  
  static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1)
@@@ -522,10 -417,8 +418,10 @@@ static int fsck_handle_ref(const char *
                /* We'll continue with the rest despite the error.. */
                return 0;
        }
 -      if (obj->type != OBJ_COMMIT && is_branch(refname))
 +      if (obj->type != OBJ_COMMIT && is_branch(refname)) {
                error("%s: not a commit", refname);
 +              errors_found |= ERROR_REFS;
 +      }
        default_refs++;
        obj->used = 1;
        mark_object_reachable(obj);
@@@ -559,9 -452,28 +455,28 @@@ static void get_default_heads(void
        }
  }
  
+ static int fsck_loose(const unsigned char *sha1, const char *path, void *data)
+ {
+       if (fsck_sha1(sha1))
+               errors_found |= ERROR_OBJECT;
+       return 0;
+ }
+ static int fsck_cruft(const char *basename, const char *path, void *data)
+ {
+       if (!starts_with(basename, "tmp_obj_"))
+               fprintf(stderr, "bad sha1 file: %s\n", path);
+       return 0;
+ }
+ static int fsck_subdir(int nr, const char *path, void *progress)
+ {
+       display_progress(progress, nr + 1);
+       return 0;
+ }
  static void fsck_object_dir(const char *path)
  {
-       int i;
        struct progress *progress = NULL;
  
        if (verbose)
  
        if (show_progress)
                progress = start_progress(_("Checking object directories"), 256);
-       for (i = 0; i < 256; i++) {
-               static char dir[4096];
-               sprintf(dir, "%s/%02x", path, i);
-               fsck_dir(i, dir);
-               display_progress(progress, i+1);
-       }
+       for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,
+                                     progress);
+       display_progress(progress, 256);
        stop_progress(&progress);
-       fsck_sha1_list();
  }
  
  static int fsck_head_link(void)
                fprintf(stderr, "Checking HEAD link\n");
  
        head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag);
 -      if (!head_points_at)
 +      if (!head_points_at) {
 +              errors_found |= ERROR_REFS;
                return error("Invalid HEAD");
 +      }
        if (!strcmp(head_points_at, "HEAD"))
                /* detached HEAD */
                null_is_error = 1;
 -      else if (!starts_with(head_points_at, "refs/heads/"))
 +      else if (!starts_with(head_points_at, "refs/heads/")) {
 +              errors_found |= ERROR_REFS;
                return error("HEAD points to something strange (%s)",
                             head_points_at);
 +      }
        if (is_null_oid(&head_oid)) {
 -              if (null_is_error)
 +              if (null_is_error) {
 +                      errors_found |= ERROR_REFS;
                        return error("HEAD: detached HEAD points at nothing");
 +              }
                fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
                        head_points_at + 11);
        }
@@@ -624,7 -527,6 +536,7 @@@ static int fsck_cache_tree(struct cache
                if (!obj) {
                        error("%s: invalid sha1 pointer in cache-tree",
                              sha1_to_hex(it->sha1));
 +                      errors_found |= ERROR_REFS;
                        return 1;
                }
                obj->used = 1;
@@@ -688,16 -590,18 +600,18 @@@ int cmd_fsck(int argc, const char **arg
        git_config(fsck_config, NULL);
  
        fsck_head_link();
-       if (!connectivity_only)
+       if (!connectivity_only) {
                fsck_object_dir(get_object_directory());
  
-       prepare_alt_odb();
-       for (alt = alt_odb_list; alt; alt = alt->next) {
-               char namebuf[PATH_MAX];
-               int namelen = alt->name - alt->base;
-               memcpy(namebuf, alt->base, namelen);
-               namebuf[namelen - 1] = 0;
-               fsck_object_dir(namebuf);
+               prepare_alt_odb();
+               for (alt = alt_odb_list; alt; alt = alt->next) {
+                       /* directory name, minus trailing slash */
+                       size_t namelen = alt->name - alt->base - 1;
+                       struct strbuf name = STRBUF_INIT;
+                       strbuf_add(&name, alt->base, namelen);
+                       fsck_object_dir(name.buf);
+                       strbuf_release(&name);
+               }
        }
  
        if (check_full) {
diff --combined builtin/gc.c
index 9ff0204940640d72bff2cfc95f084be371d6e80e,57584bc99faf49afe96caa16cee3c04f5ad64be5..eeeb21b1c46dae897ec9db51e58f8d15fc4f915e
@@@ -44,7 -44,6 +44,7 @@@ static struct argv_array prune_worktree
  static struct argv_array rerere = ARGV_ARRAY_INIT;
  
  static struct tempfile pidfile;
 +static struct lock_file log_lock;
  
  static void git_config_date_string(const char *key, const char **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;
@@@ -217,7 -194,7 +217,7 @@@ static const char *lock_repo_for_gc(in
                return NULL;
  
        if (gethostname(my_host, sizeof(my_host)))
-               strcpy(my_host, "unknown");
+               xsnprintf(my_host, sizeof(my_host), "unknown");
  
        pidfile_path = git_pathdup("gc.pid");
        fd = hold_lock_file_for_update(&lock, 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 -262,6 +303,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;
  
diff --combined builtin/ls-remote.c
index 5e9d5450b79713524eeeb981e4685725951cd9d2,5b6d679a639d2376fe24f3ff748d3853696700b2..a31024900b0a812c94dfa86024cdf45df36b96a1
@@@ -4,7 -4,7 +4,7 @@@
  #include "remote.h"
  
  static const char ls_remote_usage[] =
 -"git ls-remote [--heads] [--tags]  [-u <exec> | --upload-pack <exec>]\n"
 +"git ls-remote [--heads] [--tags]  [--upload-pack=<exec>]\n"
  "                     [-q | --quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]";
  
  /*
@@@ -93,12 -93,8 +93,8 @@@ int cmd_ls_remote(int argc, const char 
        if (argv[i]) {
                int j;
                pattern = xcalloc(argc - i + 1, sizeof(const char *));
-               for (j = i; j < argc; j++) {
-                       int len = strlen(argv[j]);
-                       char *p = xmalloc(len + 3);
-                       sprintf(p, "*/%s", argv[j]);
-                       pattern[j - i] = p;
-               }
+               for (j = i; j < argc; j++)
+                       pattern[j - i] = xstrfmt("*/%s", argv[j]);
        }
        remote = remote_get(dest);
        if (!remote) {
diff --combined cache.h
index 44c02600008ff5b2858a0b212b84cd917739cd64,d206d645508a4b5467bc9ba1cf7fe8a5cdd6f8fa..f735d14dc29310f07ee48ad54d1584c881f97110
+++ b/cache.h
@@@ -443,7 -443,6 +443,7 @@@ 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);
@@@ -724,6 -723,8 +724,8 @@@ extern char *mksnpath(char *buf, size_
        __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)));
@@@ -784,7 -785,24 +786,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)
@@@ -1066,6 -1084,18 +1085,18 @@@ 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);
  
+ /*
+  * 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.
+  *
+  * 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:
+  *
+  *   printf("%s -> %s", sha1_to_hex(one), sha1_to_hex(two));
+  */
+ 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 */
  
@@@ -1092,6 -1122,7 +1123,6 @@@ struct date_mode 
                DATE_NORMAL = 0,
                DATE_RELATIVE,
                DATE_SHORT,
 -              DATE_LOCAL,
                DATE_ISO8601,
                DATE_ISO8601_STRICT,
                DATE_RFC2822,
                DATE_RAW
        } type;
        const char *strftime_fmt;
 +      int local;
  };
  
  /*
@@@ -1276,11 -1306,10 +1307,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.
diff --combined connect.c
index d3283b8a4f3e8ed8451e723ce8b39d468aaa3dd3,1d5c5e005557ae63e31685b1d4fc8c3a3a0b9ada..108f5ab60e0ab122f0a86612ba9e4875ded29f26
+++ b/connect.c
@@@ -9,7 -9,6 +9,7 @@@
  #include "url.h"
  #include "string-list.h"
  #include "sha1-array.h"
 +#include "transport.h"
  
  static char *server_capabilities;
  static const char *parse_feature_value(const char *, const char *, int *);
@@@ -255,7 -254,7 +255,7 @@@ static const char *prot_name(enum proto
                case PROTO_GIT:
                        return "git";
                default:
 -                      return "unkown protocol";
 +                      return "unknown protocol";
        }
  }
  
@@@ -333,7 -332,7 +333,7 @@@ static const char *ai_name(const struc
        static char addr[NI_MAXHOST];
        if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0,
                        NI_NUMERICHOST) != 0)
-               strcpy(addr, "(unknown)");
+               xsnprintf(addr, sizeof(addr), "(unknown)");
  
        return addr;
  }
@@@ -695,8 -694,6 +695,8 @@@ struct child_process *git_connect(int f
                else
                        target_host = xstrdup(hostandport);
  
 +              transport_check_allowed("git");
 +
                /* These underlying connection commands die() if they
                 * cannot connect.
                 */
                strbuf_addch(&cmd, ' ');
                sq_quote_buf(&cmd, path);
  
 +              /* remove repo-local variables from the environment */
 +              conn->env = local_repo_env;
 +              conn->use_shell = 1;
                conn->in = conn->out = -1;
                if (protocol == PROTO_SSH) {
                        const char *ssh;
 -                      int putty, tortoiseplink = 0;
 +                      int putty = 0, tortoiseplink = 0;
                        char *ssh_host = hostandport;
                        const char *port = NULL;
 +                      transport_check_allowed("ssh");
                        get_host_and_port(&ssh_host, &port);
  
                        if (!port)
                        }
  
                        ssh = getenv("GIT_SSH_COMMAND");
 -                      if (ssh) {
 -                              conn->use_shell = 1;
 -                              putty = 0;
 -                      } else {
 +                      if (!ssh) {
                                const char *base;
                                char *ssh_dup;
  
 +                              /*
 +                               * GIT_SSH is the no-shell version of
 +                               * GIT_SSH_COMMAND (and must remain so for
 +                               * historical compatibility).
 +                               */
 +                              conn->use_shell = 0;
 +
                                ssh = getenv("GIT_SSH");
                                if (!ssh)
                                        ssh = "ssh";
  
                                tortoiseplink = !strcasecmp(base, "tortoiseplink") ||
                                        !strcasecmp(base, "tortoiseplink.exe");
 -                              putty = !strcasecmp(base, "plink") ||
 -                                      !strcasecmp(base, "plink.exe") || tortoiseplink;
 +                              putty = tortoiseplink ||
 +                                      !strcasecmp(base, "plink") ||
 +                                      !strcasecmp(base, "plink.exe");
  
                                free(ssh_dup);
                        }
                        }
                        argv_array_push(&conn->args, ssh_host);
                } else {
 -                      /* remove repo-local variables from the environment */
 -                      conn->env = local_repo_env;
 -                      conn->use_shell = 1;
 +                      transport_check_allowed("file");
                }
                argv_array_push(&conn->args, cmd.buf);
  
diff --combined diff.c
index 46260ed7a1d521cf95342caff83a825b34959bdc,2a37378ec82773c337505b7f0c4309bf8f4b72ca..835a12e84d65b3fa9d680d55b452f7d748a340b1
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -322,7 -322,7 +322,7 @@@ static struct diff_tempfile 
         */
        const char *name;
  
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
        char mode[10];
  
        /*
@@@ -1041,9 -1041,8 +1041,9 @@@ static void diff_words_show(struct diff
        xpp.flags = 0;
        /* as only the hunk header will be parsed, we need a 0-context */
        xecfg.ctxlen = 0;
 -      xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
 -                    &xpp, &xecfg);
 +      if (xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
 +                        &xpp, &xecfg))
 +              die("unable to generate word diff");
        free(minus.ptr);
        free(plus.ptr);
        if (diff_words->current_plus != diff_words->plus.text.ptr +
@@@ -2450,9 -2449,8 +2450,9 @@@ static void builtin_diff(const char *na
                        xecfg.ctxlen = strtoul(v, NULL, 10);
                if (o->word_diff)
                        init_diff_words_data(&ecbdata, o, one, two);
 -              xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
 -                            &xpp, &xecfg);
 +              if (xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
 +                                &xpp, &xecfg))
 +                      die("unable to generate diff for %s", one->path);
                if (o->word_diff)
                        free_diff_words_data(&ecbdata);
                if (textconv_one)
@@@ -2529,9 -2527,8 +2529,9 @@@ static void builtin_diffstat(const cha
                xpp.flags = o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
 -              xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
 -                            &xpp, &xecfg);
 +              if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
 +                                &xpp, &xecfg))
 +                      die("unable to generate diffstat for %s", one->path);
        }
  
        diff_free_filespec_data(one);
@@@ -2577,9 -2574,8 +2577,9 @@@ static void builtin_checkdiff(const cha
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
                xpp.flags = 0;
 -              xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
 -                            &xpp, &xecfg);
 +              if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
 +                                &xpp, &xecfg))
 +                      die("unable to generate checkdiff for %s", one->path);
  
                if (data.ws_rule & WS_BLANK_AT_EOF) {
                        struct emit_callback ecbdata;
@@@ -2882,9 -2878,8 +2882,8 @@@ static void prep_temp_blob(const char *
                die_errno("unable to write temp-file");
        close_tempfile(&temp->tempfile);
        temp->name = get_tempfile_path(&temp->tempfile);
-       strcpy(temp->hex, sha1_to_hex(sha1));
-       temp->hex[40] = 0;
-       sprintf(temp->mode, "%06o", mode);
+       sha1_to_hex_r(temp->hex, sha1);
+       xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
        strbuf_release(&buf);
        strbuf_release(&template);
        free(path_dup);
@@@ -2901,8 -2896,8 +2900,8 @@@ static struct diff_tempfile *prepare_te
                 * a '+' entry produces this for file-1.
                 */
                temp->name = "/dev/null";
-               strcpy(temp->hex, ".");
-               strcpy(temp->mode, ".");
+               xsnprintf(temp->hex, sizeof(temp->hex), ".");
+               xsnprintf(temp->mode, sizeof(temp->mode), ".");
                return temp;
        }
  
                        /* we can borrow from the file in the work tree */
                        temp->name = name;
                        if (!one->sha1_valid)
-                               strcpy(temp->hex, sha1_to_hex(null_sha1));
+                               sha1_to_hex_r(temp->hex, null_sha1);
                        else
-                               strcpy(temp->hex, sha1_to_hex(one->sha1));
+                               sha1_to_hex_r(temp->hex, one->sha1);
                        /* Even though we may sometimes borrow the
                         * contents from the work tree, we always want
                         * one->mode.  mode is trustworthy even when
                         * !(one->sha1_valid), as long as
                         * DIFF_FILE_VALID(one).
                         */
-                       sprintf(temp->mode, "%06o", one->mode);
+                       xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode);
                }
                return temp;
        }
@@@ -4085,9 -4080,9 +4084,9 @@@ const char *diff_unique_abbrev(const un
        if (abblen < 37) {
                static char hex[41];
                if (len < abblen && abblen <= len + 2)
-                       sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
+                       xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
                else
-                       sprintf(hex, "%s...", abbrev);
+                       xsnprintf(hex, sizeof(hex), "%s...", abbrev);
                return hex;
        }
        return sha1_to_hex(sha1);
@@@ -4514,10 -4509,8 +4513,10 @@@ static int diff_get_patch_id(struct dif
                xpp.flags = 0;
                xecfg.ctxlen = 3;
                xecfg.flags = 0;
 -              xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
 -                            &xpp, &xecfg);
 +              if (xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
 +                                &xpp, &xecfg))
 +                      return error("unable to generate patch-id diff for %s",
 +                                   p->one->path);
        }
  
        git_SHA1_Final(sha1, &ctx);
diff --combined dir.c
index fba938b70166a9397d072bf1e05a393375a0dbc5,79fdad84251368db1e851814f2a6f9064076e0d6..109ceeaed3fb899c0e98a7abfc2f38a6cfa7fe0e
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -882,25 -882,6 +882,25 @@@ int match_pathname(const char *pathname
                 */
                if (!patternlen && !namelen)
                        return 1;
 +              /*
 +               * This can happen when we ignore some exclude rules
 +               * on directories in other to see if negative rules
 +               * may match. E.g.
 +               *
 +               * /abc
 +               * !/abc/def/ghi
 +               *
 +               * The pattern of interest is "/abc". On the first
 +               * try, we should match path "abc" with this pattern
 +               * in the "if" statement right above, but the caller
 +               * ignores it.
 +               *
 +               * On the second try with paths within "abc",
 +               * e.g. "abc/xyz", we come here and try to match it
 +               * with "/abc".
 +               */
 +              if (!patternlen && namelen && *name == '/')
 +                      return 1;
        }
  
        return fnmatch_icase_mem(pattern, patternlen,
                                 WM_PATHNAME) == 0;
  }
  
 +/*
 + * Return non-zero if pathname is a directory and an ancestor of the
 + * literal path in a (negative) pattern. This is used to keep
 + * descending in "foo" and "foo/bar" when the pattern is
 + * "!foo/bar/.gitignore". "foo/notbar" will not be descended however.
 + */
 +static int match_neg_path(const char *pathname, int pathlen, int *dtype,
 +                        const char *base, int baselen,
 +                        const char *pattern, int prefix, int patternlen,
 +                        int flags)
 +{
 +      assert((flags & EXC_FLAG_NEGATIVE) && !(flags & EXC_FLAG_NODIR));
 +
 +      if (*dtype == DT_UNKNOWN)
 +              *dtype = get_dtype(NULL, pathname, pathlen);
 +      if (*dtype != DT_DIR)
 +              return 0;
 +
 +      if (*pattern == '/') {
 +              pattern++;
 +              patternlen--;
 +              prefix--;
 +      }
 +
 +      if (baselen) {
 +              if (((pathlen < baselen && base[pathlen] == '/') ||
 +                   pathlen == baselen) &&
 +                  !strncmp_icase(pathname, base, pathlen))
 +                      return 1;
 +              pathname += baselen + 1;
 +              pathlen  -= baselen + 1;
 +      }
 +
 +
 +      if (prefix &&
 +          ((pathlen < prefix && pattern[pathlen] == '/') &&
 +           !strncmp_icase(pathname, pattern, pathlen)))
 +              return 1;
 +
 +      return 0;
 +}
 +
  /*
   * Scan the given exclude list in reverse to see whether pathname
   * should be ignored.  The first match (i.e. the last on the list), if
@@@ -962,8 -901,7 +962,8 @@@ static struct exclude *last_exclude_mat
                                                       int *dtype,
                                                       struct exclude_list *el)
  {
 -      int i;
 +      struct exclude *exc = NULL; /* undecided */
 +      int i, matched_negative_path = 0;
  
        if (!el->nr)
                return NULL;    /* undefined */
                        if (match_basename(basename,
                                           pathlen - (basename - pathname),
                                           exclude, prefix, x->patternlen,
 -                                         x->flags))
 -                              return x;
 +                                         x->flags)) {
 +                              exc = x;
 +                              break;
 +                      }
                        continue;
                }
  
                assert(x->baselen == 0 || x->base[x->baselen - 1] == '/');
                if (match_pathname(pathname, pathlen,
                                   x->base, x->baselen ? x->baselen - 1 : 0,
 +                                 exclude, prefix, x->patternlen, x->flags)) {
 +                      exc = x;
 +                      break;
 +              }
 +
 +              if ((x->flags & EXC_FLAG_NEGATIVE) && !matched_negative_path &&
 +                  match_neg_path(pathname, pathlen, dtype, x->base,
 +                                 x->baselen ? x->baselen - 1 : 0,
                                   exclude, prefix, x->patternlen, x->flags))
 -                      return x;
 +                      matched_negative_path = 1;
        }
 -      return NULL; /* undecided */
 +      if (exc &&
 +          !(exc->flags & EXC_FLAG_NEGATIVE) &&
 +          !(exc->flags & EXC_FLAG_NODIR) &&
 +          matched_negative_path)
 +              exc = NULL;
 +      return exc;
  }
  
  /*
@@@ -1596,8 -1519,7 +1596,7 @@@ static enum path_treatment treat_path_f
        }
        strbuf_addstr(path, cdir->ucd->name);
        /* treat_one_path() does this before it calls treat_directory() */
-       if (path->buf[path->len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
        if (cdir->ucd->check_only)
                /*
                 * check_only is set as a result of treat_directory() getting
@@@ -2107,15 -2029,6 +2106,15 @@@ int file_exists(const char *f
        return lstat(f, &sb) == 0;
  }
  
 +static int cmp_icase(char a, char b)
 +{
 +      if (a == b)
 +              return 0;
 +      if (ignore_case)
 +              return toupper(a) - toupper(b);
 +      return a - b;
 +}
 +
  /*
   * Given two normalized paths (a trailing slash is ok), if subdir is
   * outside dir, return -1.  Otherwise return the offset in subdir that
@@@ -2127,7 -2040,7 +2126,7 @@@ int dir_inside_of(const char *subdir, c
  
        assert(dir && subdir && *dir && *subdir);
  
 -      while (*dir && *subdir && *dir == *subdir) {
 +      while (*dir && *subdir && !cmp_icase(*dir, *subdir)) {
                dir++;
                subdir++;
                offset++;
@@@ -2212,8 -2125,7 +2211,7 @@@ static int remove_dir_recurse(struct st
                else
                        return -1;
        }
-       if (path->buf[original_len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
  
        len = path->len;
        while ((e = readdir(dir)) != NULL) {
diff --combined fast-import.c
index adcbfc67dca7aecd8f1680f3e30b05dcfa400b59,4d01efcb9d1e41062d76e88a4f928ceb50f79ca8..e3b421d5149bc3a2ad58ca8df98fdf2c924ed7c0
@@@ -424,7 -424,7 +424,7 @@@ static void write_crash_report(const ch
        fprintf(rpt, "fast-import crash report:\n");
        fprintf(rpt, "    fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
        fprintf(rpt, "    parent process     : %"PRIuMAX"\n", (uintmax_t) getppid());
 -      fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(LOCAL)));
 +      fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(ISO8601)));
        fputc('\n', rpt);
  
        fputs("fatal: ", rpt);
@@@ -644,8 -644,9 +644,9 @@@ static void *pool_calloc(size_t count, 
  
  static char *pool_strdup(const char *s)
  {
-       char *r = pool_alloc(strlen(s) + 1);
-       strcpy(r, s);
+       size_t len = strlen(s) + 1;
+       char *r = pool_alloc(len);
+       memcpy(r, s, len);
        return r;
  }
  
@@@ -702,7 -703,7 +703,7 @@@ static struct atom_str *to_atom(const c
  
        c = pool_alloc(sizeof(struct atom_str) + len + 1);
        c->str_len = len;
-       strncpy(c->str_dat, s, len);
+       memcpy(c->str_dat, s, len);
        c->str_dat[len] = 0;
        c->next_atom = atom_table[hc];
        atom_table[hc] = c;
@@@ -863,13 -864,15 +864,15 @@@ static void start_packfile(void
  {
        static char tmp_file[PATH_MAX];
        struct packed_git *p;
+       int namelen;
        struct pack_header hdr;
        int pack_fd;
  
        pack_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
                              "pack/tmp_pack_XXXXXX");
-       p = xcalloc(1, sizeof(*p) + strlen(tmp_file) + 2);
-       strcpy(p->pack_name, tmp_file);
+       namelen = strlen(tmp_file) + 2;
+       p = xcalloc(1, sizeof(*p) + namelen);
+       xsnprintf(p->pack_name, namelen, "%s", tmp_file);
        p->pack_fd = pack_fd;
        p->do_not_close = 1;
        pack_file = sha1fd(pack_fd, p->pack_name);
@@@ -1035,8 -1038,8 +1038,8 @@@ static int store_object
        git_SHA_CTX c;
        git_zstream s;
  
-       hdrlen = sprintf((char *)hdr,"%s %lu", typename(type),
-               (unsigned long)dat->len) + 1;
+       hdrlen = xsnprintf((char *)hdr, sizeof(hdr), "%s %lu",
+                          typename(type), (unsigned long)dat->len) + 1;
        git_SHA1_Init(&c);
        git_SHA1_Update(&c, hdr, hdrlen);
        git_SHA1_Update(&c, dat->buf, dat->len);
diff --combined git-compat-util.h
index 1df82fa598daa131c9e84023083a014ac3ee3f9f,9a3e5591cb4fd032ed9447ef09873b5da2f32266..88964f7886b1f6d7445b77a26eceb65d48c3b6b8
@@@ -229,7 -229,7 +229,7 @@@ typedef unsigned long uintptr_t
  #else
  #define precompose_str(in,i_nfd2nfc)
  #define precompose_argv(c,v)
- #define probe_utf8_pathname_composition(a,b)
+ #define probe_utf8_pathname_composition()
  #endif
  
  #ifdef MKDIR_WO_TRAILING_SLASH
@@@ -744,6 -744,9 +744,9 @@@ static inline size_t xsize_t(off_t len
        return (size_t)len;
  }
  
+ __attribute__((format (printf, 3, 4)))
+ extern int xsnprintf(char *dst, size_t max, const char *fmt, ...);
  /* in ctype.c, for kwset users */
  extern const unsigned char tolower_trans_tbl[256];
  
@@@ -814,9 -817,6 +817,9 @@@ static inline int strtoul_ui(char cons
        char *p;
  
        errno = 0;
 +      /* negative values would be accepted by strtoul */
 +      if (strchr(s, '-'))
 +              return -1;
        ul = strtoul(s, &p, base);
        if (errno || *p || p == s || (unsigned int) ul != ul)
                return -1;
diff --combined http.c
index 0f924a8b48f3e30fd4a646ef48acb8d066988a53,e0ff876cd96c2ee6bdab82081370e091e8d311e2..7da76edda16abea8a53299f2770ad2dce55c1b60
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -9,7 -9,6 +9,7 @@@
  #include "version.h"
  #include "pkt-line.h"
  #include "gettext.h"
 +#include "transport.h"
  
  int active_requests;
  int http_is_verbose;
@@@ -357,7 -356,6 +357,7 @@@ static void set_curl_keepalive(CURL *c
  static CURL *get_curl_handle(void)
  {
        CURL *result = curl_easy_init();
 +      long allowed_protocols = 0;
  
        if (!result)
                die("curl_easy_init failed");
        }
  
        curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
 +      curl_easy_setopt(result, CURLOPT_MAXREDIRS, 20);
  #if LIBCURL_VERSION_NUM >= 0x071301
        curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
  #elif LIBCURL_VERSION_NUM >= 0x071101
        curl_easy_setopt(result, CURLOPT_POST301, 1);
  #endif
 +#if LIBCURL_VERSION_NUM >= 0x071304
 +      if (is_transport_allowed("http"))
 +              allowed_protocols |= CURLPROTO_HTTP;
 +      if (is_transport_allowed("https"))
 +              allowed_protocols |= CURLPROTO_HTTPS;
 +      if (is_transport_allowed("ftp"))
 +              allowed_protocols |= CURLPROTO_FTP;
 +      if (is_transport_allowed("ftps"))
 +              allowed_protocols |= CURLPROTO_FTPS;
 +      curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols);
 +#else
 +      if (transport_restrict_protocols())
 +              warning("protocol restrictions not applied to curl redirects because\n"
 +                      "your curl version is too old (>= 7.19.4)");
 +#endif
  
        if (getenv("GIT_CURL_VERBOSE"))
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
@@@ -1122,7 -1104,7 +1122,7 @@@ static void write_accept_language(struc
                     decimal_places++, max_q *= 10)
                        ;
  
-               sprintf(q_format, ";q=0.%%0%dd", decimal_places);
+               xsnprintf(q_format, sizeof(q_format), ";q=0.%%0%dd", decimal_places);
  
                strbuf_addstr(buf, "Accept-Language: ");
  
@@@ -1529,6 -1511,7 +1529,7 @@@ int finish_http_pack_request(struct htt
        struct packed_git **lst;
        struct packed_git *p = preq->target;
        char *tmp_idx;
+       size_t len;
        struct child_process ip = CHILD_PROCESS_INIT;
        const char *ip_argv[8];
  
                lst = &((*lst)->next);
        *lst = (*lst)->next;
  
-       tmp_idx = xstrdup(preq->tmpfile);
-       strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"),
-              ".idx.temp");
+       if (!strip_suffix(preq->tmpfile, ".pack.temp", &len))
+               die("BUG: pack tmpfile does not end in .pack.temp?");
+       tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile);
  
        ip_argv[0] = "index-pack";
        ip_argv[1] = "-o";
@@@ -1619,7 -1602,7 +1620,7 @@@ struct http_pack_request *new_http_pack
                        fprintf(stderr,
                                "Resuming fetch of pack %s at byte %ld\n",
                                sha1_to_hex(target->sha1), prev_posn);
-               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               xsnprintf(range, sizeof(range), "Range: bytes=%ld-", prev_posn);
                preq->range_header = curl_slist_append(NULL, range);
                curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
                        preq->range_header);
@@@ -1779,7 -1762,7 +1780,7 @@@ struct http_object_request *new_http_ob
                        fprintf(stderr,
                                "Resuming fetch of object %s at byte %ld\n",
                                hex, prev_posn);
-               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               xsnprintf(range, sizeof(range), "Range: bytes=%ld-", prev_posn);
                range_header = curl_slist_append(range_header, range);
                curl_easy_setopt(freq->slot->curl,
                                 CURLOPT_HTTPHEADER, range_header);
diff --combined ll-merge.c
index bf83290793059dc42d89db07a12e3bc3c14a48dc,56f73b393256091194d88eaf9d1b87776dc06dff..0338630fc2a5378ed33afa828db4940b34aabe82
@@@ -89,10 -89,7 +89,10 @@@ static int ll_xdl_merge(const struct ll
        xmparam_t xmp;
        assert(opts);
  
 -      if (buffer_is_binary(orig->ptr, orig->size) ||
 +      if (orig->size > MAX_XDIFF_SIZE ||
 +          src1->size > MAX_XDIFF_SIZE ||
 +          src2->size > MAX_XDIFF_SIZE ||
 +          buffer_is_binary(orig->ptr, orig->size) ||
            buffer_is_binary(src1->ptr, src1->size) ||
            buffer_is_binary(src2->ptr, src2->size)) {
                return ll_binary_merge(drv_unused, result,
@@@ -145,11 -142,11 +145,11 @@@ static struct ll_merge_driver ll_merge_
        { "union", "built-in union merge", ll_union_merge },
  };
  
- static void create_temp(mmfile_t *src, char *path)
+ static void create_temp(mmfile_t *src, char *path, size_t len)
  {
        int fd;
  
-       strcpy(path, ".merge_file_XXXXXX");
+       xsnprintf(path, len, ".merge_file_XXXXXX");
        fd = xmkstemp(path);
        if (write_in_full(fd, src->ptr, src->size) != src->size)
                die_errno("unable to write temp-file");
@@@ -190,10 -187,10 +190,10 @@@ static int ll_ext_merge(const struct ll
  
        result->ptr = NULL;
        result->size = 0;
-       create_temp(orig, temp[0]);
-       create_temp(src1, temp[1]);
-       create_temp(src2, temp[2]);
-       sprintf(temp[3], "%d", marker_size);
+       create_temp(orig, temp[0], sizeof(temp[0]));
+       create_temp(src1, temp[1], sizeof(temp[1]));
+       create_temp(src2, temp[2], sizeof(temp[2]));
+       xsnprintf(temp[3], sizeof(temp[3]), "%d", marker_size);
  
        strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
  
diff --combined path.c
index 1352952616667a11c40060119f0e3a8da87a22f2,c105a9e083307395871ccf3b615c92ad0754fdd7..c740c4ff9403bc92df4c439839bd5596ac62e48d
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -91,274 -91,54 +91,274 @@@ 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);
@@@ -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;
@@@ -448,12 -238,9 +458,11 @@@ static void do_submodule_path(struct st
                              const char *fmt, va_list args)
  {
        const char *git_dir;
 +      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);
  
        strbuf_vaddf(buf, fmt, 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);
 +
 +      strbuf_release(&git_submodule_dir);
 +      strbuf_release(&git_submodule_common_dir);
  }
  
  char *git_pathdup_submodule(const char *path, const char *fmt, ...)
@@@ -611,8 -390,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);
-               if (gitfile)
-                       strcpy(used_path, gitfile);
-               if (chdir(used_path))
 -              gitfile = read_gitfile(used_path.buf) ;
++              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;
+               path = validated_path.buf;
        }
 -      else if (chdir(path))
 -              return NULL;
 +      else {
 +              const char *gitfile = read_gitfile(path);
 +              if (gitfile)
 +                      path = gitfile;
 +              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;
@@@ -855,7 -631,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)
  {
diff --combined ref-filter.c
index dbd8fcec24da01f682460d42110b06b3c33b599f,1f718704e79c5f659f0988446f2d545cccb1bc86..1194f10ed60f2bb476e1d95d0cc11b4ad4265c7f
@@@ -9,10 -9,6 +9,10 @@@
  #include "tag.h"
  #include "quote.h"
  #include "ref-filter.h"
 +#include "revision.h"
 +#include "utf8.h"
 +#include "git-compat-util.h"
 +#include "version.h"
  
  typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
  
@@@ -47,48 -43,15 +47,48 @@@ static struct 
        { "subject" },
        { "body" },
        { "contents" },
 -      { "contents:subject" },
 -      { "contents:body" },
 -      { "contents:signature" },
        { "upstream" },
        { "push" },
        { "symref" },
        { "flag" },
        { "HEAD" },
        { "color" },
 +      { "align" },
 +      { "end" },
 +};
 +
 +#define REF_FORMATTING_STATE_INIT  { 0, NULL }
 +
 +struct align {
 +      align_type position;
 +      unsigned int width;
 +};
 +
 +struct contents {
 +      unsigned int lines;
 +      struct object_id oid;
 +};
 +
 +struct ref_formatting_stack {
 +      struct ref_formatting_stack *prev;
 +      struct strbuf output;
 +      void (*at_end)(struct ref_formatting_stack *stack);
 +      void *at_end_data;
 +};
 +
 +struct ref_formatting_state {
 +      int quote_style;
 +      struct ref_formatting_stack *stack;
 +};
 +
 +struct atom_value {
 +      const char *s;
 +      union {
 +              struct align align;
 +              struct contents contents;
 +      } u;
 +      void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
 +      unsigned long ul; /* used for sorting when not FIELD_STR */
  };
  
  /*
@@@ -160,120 -123,6 +160,120 @@@ int parse_ref_filter_atom(const char *a
        return at;
  }
  
 +static void quote_formatting(struct strbuf *s, const char *str, int quote_style)
 +{
 +      switch (quote_style) {
 +      case QUOTE_NONE:
 +              strbuf_addstr(s, str);
 +              break;
 +      case QUOTE_SHELL:
 +              sq_quote_buf(s, str);
 +              break;
 +      case QUOTE_PERL:
 +              perl_quote_buf(s, str);
 +              break;
 +      case QUOTE_PYTHON:
 +              python_quote_buf(s, str);
 +              break;
 +      case QUOTE_TCL:
 +              tcl_quote_buf(s, str);
 +              break;
 +      }
 +}
 +
 +static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
 +{
 +      /*
 +       * Quote formatting is only done when the stack has a single
 +       * element. Otherwise quote formatting is done on the
 +       * element's entire output strbuf when the %(end) atom is
 +       * encountered.
 +       */
 +      if (!state->stack->prev)
 +              quote_formatting(&state->stack->output, v->s, state->quote_style);
 +      else
 +              strbuf_addstr(&state->stack->output, v->s);
 +}
 +
 +static void push_stack_element(struct ref_formatting_stack **stack)
 +{
 +      struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack));
 +
 +      strbuf_init(&s->output, 0);
 +      s->prev = *stack;
 +      *stack = s;
 +}
 +
 +static void pop_stack_element(struct ref_formatting_stack **stack)
 +{
 +      struct ref_formatting_stack *current = *stack;
 +      struct ref_formatting_stack *prev = current->prev;
 +
 +      if (prev)
 +              strbuf_addbuf(&prev->output, &current->output);
 +      strbuf_release(&current->output);
 +      free(current);
 +      *stack = prev;
 +}
 +
 +static void end_align_handler(struct ref_formatting_stack *stack)
 +{
 +      struct align *align = (struct align *)stack->at_end_data;
 +      struct strbuf s = STRBUF_INIT;
 +
 +      strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
 +      strbuf_swap(&stack->output, &s);
 +      strbuf_release(&s);
 +}
 +
 +static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
 +{
 +      struct ref_formatting_stack *new;
 +
 +      push_stack_element(&state->stack);
 +      new = state->stack;
 +      new->at_end = end_align_handler;
 +      new->at_end_data = &atomv->u.align;
 +}
 +
 +static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
 +{
 +      struct ref_formatting_stack *current = state->stack;
 +      struct strbuf s = STRBUF_INIT;
 +
 +      if (!current->at_end)
 +              die(_("format: %%(end) atom used without corresponding atom"));
 +      current->at_end(current);
 +
 +      /*
 +       * Perform quote formatting when the stack element is that of
 +       * a supporting atom. If nested then perform quote formatting
 +       * only on the topmost supporting atom.
 +       */
 +      if (!state->stack->prev->prev) {
 +              quote_formatting(&s, current->output.buf, state->quote_style);
 +              strbuf_swap(&current->output, &s);
 +      }
 +      strbuf_release(&s);
 +      pop_stack_element(&state->stack);
 +}
 +
 +static int match_atom_name(const char *name, const char *atom_name, const char **val)
 +{
 +      const char *body;
 +
 +      if (!skip_prefix(name, atom_name, &body))
 +              return 0; /* doesn't even begin with "atom_name" */
 +      if (!body[0]) {
 +              *val = NULL; /* %(atom_name) and no customization */
 +              return 1;
 +      }
 +      if (body[0] != ':')
 +              return 0; /* "atom_namefoo" is not "atom_name" or "atom_name:..." */
 +      *val = body + 1; /* "atom_name:val" */
 +      return 1;
 +}
 +
  /*
   * In a format string, find the next occurrence of %(atom).
   */
@@@ -343,9 -192,7 +343,7 @@@ static int grab_objectname(const char *
                            struct atom_value *v)
  {
        if (!strcmp(name, "objectname")) {
-               char *s = xmalloc(41);
-               strcpy(s, sha1_to_hex(sha1));
-               v->s = s;
+               v->s = xstrdup(sha1_to_hex(sha1));
                return 1;
        }
        if (!strcmp(name, "objectname:short")) {
@@@ -370,10 -217,8 +368,8 @@@ static void grab_common_values(struct a
                if (!strcmp(name, "objecttype"))
                        v->s = typename(obj->type);
                else if (!strcmp(name, "objectsize")) {
-                       char *s = xmalloc(40);
-                       sprintf(s, "%lu", sz);
                        v->ul = sz;
-                       v->s = s;
+                       v->s = xstrfmt("%lu", sz);
                }
                else if (deref)
                        grab_objectname(name, obj->sha1, v);
@@@ -397,11 -242,8 +393,8 @@@ static void grab_tag_values(struct atom
                        v->s = tag->tag;
                else if (!strcmp(name, "type") && tag->tagged)
                        v->s = typename(tag->tagged->type);
-               else if (!strcmp(name, "object") && tag->tagged) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(tag->tagged->sha1));
-                       v->s = s;
-               }
+               else if (!strcmp(name, "object") && tag->tagged)
+                       v->s = xstrdup(sha1_to_hex(tag->tagged->sha1));
        }
  }
  
@@@ -419,32 -261,22 +412,22 @@@ static void grab_commit_values(struct a
                if (deref)
                        name++;
                if (!strcmp(name, "tree")) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
-                       v->s = s;
+                       v->s = xstrdup(sha1_to_hex(commit->tree->object.sha1));
                }
-               if (!strcmp(name, "numparent")) {
-                       char *s = xmalloc(40);
+               else if (!strcmp(name, "numparent")) {
                        v->ul = commit_list_count(commit->parents);
-                       sprintf(s, "%lu", v->ul);
-                       v->s = s;
+                       v->s = xstrfmt("%lu", v->ul);
                }
                else if (!strcmp(name, "parent")) {
-                       int num = commit_list_count(commit->parents);
-                       int i;
                        struct commit_list *parents;
-                       char *s = xmalloc(41 * num + 1);
-                       v->s = s;
-                       for (i = 0, parents = commit->parents;
-                            parents;
-                            parents = parents->next, i = i + 41) {
+                       struct strbuf s = STRBUF_INIT;
+                       for (parents = commit->parents; parents; parents = parents->next) {
                                struct commit *parent = parents->item;
-                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
-                               if (parents->next)
-                                       s[i+40] = ' ';
+                               if (parents != commit->parents)
+                                       strbuf_addch(&s, ' ');
+                               strbuf_addstr(&s, sha1_to_hex(parent->object.sha1));
                        }
-                       if (!i)
-                               *s = '\0';
+                       v->s = strbuf_detach(&s, NULL);
                }
        }
  }
@@@ -648,30 -480,6 +631,30 @@@ static void find_subpos(const char *buf
        *nonsiglen = *sig - buf;
  }
  
 +/*
 + * If 'lines' is greater than 0, append that many lines from the given
 + * 'buf' of length 'size' to the given strbuf.
 + */
 +static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines)
 +{
 +      int i;
 +      const char *sp, *eol;
 +      size_t len;
 +
 +      sp = buf;
 +
 +      for (i = 0; i < lines && sp < buf + size; i++) {
 +              if (i)
 +                      strbuf_addstr(out, "\n    ");
 +              eol = memchr(sp, '\n', size - (sp - buf));
 +              len = eol ? eol - sp : size - (sp - buf);
 +              strbuf_add(out, sp, len);
 +              if (!eol)
 +                      break;
 +              sp = eol + 1;
 +      }
 +}
 +
  /* See grab_values */
  static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
  {
        for (i = 0; i < used_atom_cnt; i++) {
                const char *name = used_atom[i];
                struct atom_value *v = &val[i];
 +              const char *valp = NULL;
                if (!!deref != (*name == '*'))
                        continue;
                if (deref)
                    strcmp(name, "contents") &&
                    strcmp(name, "contents:subject") &&
                    strcmp(name, "contents:body") &&
 -                  strcmp(name, "contents:signature"))
 +                  strcmp(name, "contents:signature") &&
 +                  !starts_with(name, "contents:lines="))
                        continue;
                if (!subpos)
                        find_subpos(buf, sz,
                        v->s = xmemdupz(sigpos, siglen);
                else if (!strcmp(name, "contents"))
                        v->s = xstrdup(subpos);
 +              else if (skip_prefix(name, "contents:lines=", &valp)) {
 +                      struct strbuf s = STRBUF_INIT;
 +                      const char *contents_end = bodylen + bodypos - siglen;
 +
 +                      if (strtoul_ui(valp, 10, &v->u.contents.lines))
 +                              die(_("positive value expected contents:lines=%s"), valp);
 +                      /*  Size is the length of the message after removing the signature */
 +                      append_lines(&s, subpos, contents_end - subpos, v->u.contents.lines);
 +                      v->s = strbuf_detach(&s, NULL);
 +              }
        }
  }
  
@@@ -808,11 -604,8 +791,11 @@@ static void populate_value(struct ref_a
                int deref = 0;
                const char *refname;
                const char *formatp;
 +              const char *valp;
                struct branch *branch = NULL;
  
 +              v->handler = append_atom;
 +
                if (*name == '*') {
                        deref = 1;
                        name++;
                        refname = branch_get_push(branch, NULL);
                        if (!refname)
                                continue;
 -              } else if (starts_with(name, "color:")) {
 +              } else if (match_atom_name(name, "color", &valp)) {
                        char color[COLOR_MAXLEN] = "";
  
 -                      if (color_parse(name + 6, color) < 0)
 +                      if (!valp)
 +                              die(_("expected format: %%(color:<color>)"));
 +                      if (color_parse(valp, color) < 0)
                                die(_("unable to parse format"));
                        v->s = xstrdup(color);
                        continue;
                        else
                                v->s = " ";
                        continue;
 +              } else if (match_atom_name(name, "align", &valp)) {
 +                      struct align *align = &v->u.align;
 +                      struct strbuf **s, **to_free;
 +                      int width = -1;
 +
 +                      if (!valp)
 +                              die(_("expected format: %%(align:<width>,<position>)"));
 +
 +                      /*
 +                       * TODO: Implement a function similar to strbuf_split_str()
 +                       * which would omit the separator from the end of each value.
 +                       */
 +                      s = to_free = strbuf_split_str(valp, ',', 0);
 +
 +                      align->position = ALIGN_LEFT;
 +
 +                      while (*s) {
 +                              /*  Strip trailing comma */
 +                              if (s[1])
 +                                      strbuf_setlen(s[0], s[0]->len - 1);
 +                              if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))
 +                                      ;
 +                              else if (!strcmp(s[0]->buf, "left"))
 +                                      align->position = ALIGN_LEFT;
 +                              else if (!strcmp(s[0]->buf, "right"))
 +                                      align->position = ALIGN_RIGHT;
 +                              else if (!strcmp(s[0]->buf, "middle"))
 +                                      align->position = ALIGN_MIDDLE;
 +                              else
 +                                      die(_("improper format entered align:%s"), s[0]->buf);
 +                              s++;
 +                      }
 +
 +                      if (width < 0)
 +                              die(_("positive width expected with the %%(align) atom"));
 +                      align->width = width;
 +                      strbuf_list_free(to_free);
 +                      v->handler = align_atom_handler;
 +                      continue;
 +              } else if (!strcmp(name, "end")) {
 +                      v->handler = end_atom_handler;
 +                      continue;
                } else
                        continue;
  
                        else if (!strcmp(formatp, "track") &&
                                 (starts_with(name, "upstream") ||
                                  starts_with(name, "push"))) {
-                               char buf[40];
  
                                if (stat_tracking_info(branch, &num_ours,
                                                       &num_theirs, NULL))
  
                                if (!num_ours && !num_theirs)
                                        v->s = "";
-                               else if (!num_ours) {
-                                       sprintf(buf, "[behind %d]", num_theirs);
-                                       v->s = xstrdup(buf);
-                               } else if (!num_theirs) {
-                                       sprintf(buf, "[ahead %d]", num_ours);
-                                       v->s = xstrdup(buf);
-                               } else {
-                                       sprintf(buf, "[ahead %d, behind %d]",
-                                               num_ours, num_theirs);
-                                       v->s = xstrdup(buf);
-                               }
+                               else if (!num_ours)
+                                       v->s = xstrfmt("[behind %d]", num_theirs);
+                               else if (!num_theirs)
+                                       v->s = xstrfmt("[ahead %d]", num_ours);
+                               else
+                                       v->s = xstrfmt("[ahead %d, behind %d]",
+                                                      num_ours, num_theirs);
                                continue;
                        } else if (!strcmp(formatp, "trackshort") &&
                                   (starts_with(name, "upstream") ||
  
                if (!deref)
                        v->s = refname;
-               else {
-                       int len = strlen(refname);
-                       char *s = xmalloc(len + 4);
-                       sprintf(s, "%s^{}", refname);
-                       v->s = s;
-               }
+               else
+                       v->s = xstrfmt("%s^{}", refname);
        }
  
        for (i = 0; i < used_atom_cnt; i++) {
@@@ -1051,143 -791,11 +1025,143 @@@ static void get_ref_atom_value(struct r
        *v = &ref->value[atom];
  }
  
 +enum contains_result {
 +      CONTAINS_UNKNOWN = -1,
 +      CONTAINS_NO = 0,
 +      CONTAINS_YES = 1
 +};
 +
 +/*
 + * Mimicking the real stack, this stack lives on the heap, avoiding stack
 + * overflows.
 + *
 + * At each recursion step, the stack items points to the commits whose
 + * ancestors are to be inspected.
 + */
 +struct contains_stack {
 +      int nr, alloc;
 +      struct contains_stack_entry {
 +              struct commit *commit;
 +              struct commit_list *parents;
 +      } *contains_stack;
 +};
 +
 +static int in_commit_list(const struct commit_list *want, struct commit *c)
 +{
 +      for (; want; want = want->next)
 +              if (!hashcmp(want->item->object.sha1, c->object.sha1))
 +                      return 1;
 +      return 0;
 +}
 +
 +/*
 + * Test whether the candidate or one of its parents is contained in the list.
 + * Do not recurse to find out, though, but return -1 if inconclusive.
 + */
 +static enum contains_result contains_test(struct commit *candidate,
 +                          const struct commit_list *want)
 +{
 +      /* was it previously marked as containing a want commit? */
 +      if (candidate->object.flags & TMP_MARK)
 +              return 1;
 +      /* or marked as not possibly containing a want commit? */
 +      if (candidate->object.flags & UNINTERESTING)
 +              return 0;
 +      /* or are we it? */
 +      if (in_commit_list(want, candidate)) {
 +              candidate->object.flags |= TMP_MARK;
 +              return 1;
 +      }
 +
 +      if (parse_commit(candidate) < 0)
 +              return 0;
 +
 +      return -1;
 +}
 +
 +static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
 +{
 +      ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
 +      contains_stack->contains_stack[contains_stack->nr].commit = candidate;
 +      contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
 +}
 +
 +static enum contains_result contains_tag_algo(struct commit *candidate,
 +              const struct commit_list *want)
 +{
 +      struct contains_stack contains_stack = { 0, 0, NULL };
 +      int result = contains_test(candidate, want);
 +
 +      if (result != CONTAINS_UNKNOWN)
 +              return result;
 +
 +      push_to_contains_stack(candidate, &contains_stack);
 +      while (contains_stack.nr) {
 +              struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
 +              struct commit *commit = entry->commit;
 +              struct commit_list *parents = entry->parents;
 +
 +              if (!parents) {
 +                      commit->object.flags |= UNINTERESTING;
 +                      contains_stack.nr--;
 +              }
 +              /*
 +               * If we just popped the stack, parents->item has been marked,
 +               * therefore contains_test will return a meaningful 0 or 1.
 +               */
 +              else switch (contains_test(parents->item, want)) {
 +              case CONTAINS_YES:
 +                      commit->object.flags |= TMP_MARK;
 +                      contains_stack.nr--;
 +                      break;
 +              case CONTAINS_NO:
 +                      entry->parents = parents->next;
 +                      break;
 +              case CONTAINS_UNKNOWN:
 +                      push_to_contains_stack(parents->item, &contains_stack);
 +                      break;
 +              }
 +      }
 +      free(contains_stack.contains_stack);
 +      return contains_test(candidate, want);
 +}
 +
 +static int commit_contains(struct ref_filter *filter, struct commit *commit)
 +{
 +      if (filter->with_commit_tag_algo)
 +              return contains_tag_algo(commit, filter->with_commit);
 +      return is_descendant_of(commit, filter->with_commit);
 +}
 +
 +/*
 + * Return 1 if the refname matches one of the patterns, otherwise 0.
 + * A pattern can be a literal prefix (e.g. a refname "refs/heads/master"
 + * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref
 + * matches "refs/heads/mas*", too).
 + */
 +static int match_pattern(const char **patterns, const char *refname)
 +{
 +      /*
 +       * When no '--format' option is given we need to skip the prefix
 +       * for matching refs of tags and branches.
 +       */
 +      (void)(skip_prefix(refname, "refs/tags/", &refname) ||
 +             skip_prefix(refname, "refs/heads/", &refname) ||
 +             skip_prefix(refname, "refs/remotes/", &refname) ||
 +             skip_prefix(refname, "refs/", &refname));
 +
 +      for (; *patterns; patterns++) {
 +              if (!wildmatch(*patterns, refname, 0, NULL))
 +                      return 1;
 +      }
 +      return 0;
 +}
 +
  /*
   * Return 1 if the refname matches one of the patterns, otherwise 0.
   * A pattern can be path prefix (e.g. a refname "refs/heads/master"
 - * matches a pattern "refs/heads/") or a wildcard (e.g. the same ref
 - * matches "refs/heads/m*",too).
 + * matches a pattern "refs/heads/" but not "refs/heads/m") or a
 + * wildcard (e.g. the same ref matches "refs/heads/m*", too).
   */
  static int match_name_as_path(const char **pattern, const char *refname)
  {
        return 0;
  }
  
 +/* Return 1 if the refname matches one of the patterns, otherwise 0. */
 +static int filter_pattern_match(struct ref_filter *filter, const char *refname)
 +{
 +      if (!*filter->name_patterns)
 +              return 1; /* No pattern always matches */
 +      if (filter->match_as_path)
 +              return match_name_as_path(filter->name_patterns, refname);
 +      return match_pattern(filter->name_patterns, refname);
 +}
 +
 +/*
 + * Given a ref (sha1, refname), check if the ref belongs to the array
 + * of sha1s. If the given ref is a tag, check if the given tag points
 + * at one of the sha1s in the given sha1 array.
 + * the given sha1_array.
 + * NEEDSWORK:
 + * 1. Only a single level of inderection is obtained, we might want to
 + * change this to account for multiple levels (e.g. annotated tags
 + * pointing to annotated tags pointing to a commit.)
 + * 2. As the refs are cached we might know what refname peels to without
 + * the need to parse the object via parse_object(). peel_ref() might be a
 + * more efficient alternative to obtain the pointee.
 + */
 +static const unsigned char *match_points_at(struct sha1_array *points_at,
 +                                          const unsigned char *sha1,
 +                                          const char *refname)
 +{
 +      const unsigned char *tagged_sha1 = NULL;
 +      struct object *obj;
 +
 +      if (sha1_array_lookup(points_at, sha1) >= 0)
 +              return sha1;
 +      obj = parse_object(sha1);
 +      if (!obj)
 +              die(_("malformed object at '%s'"), refname);
 +      if (obj->type == OBJ_TAG)
 +              tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
 +      if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
 +              return tagged_sha1;
 +      return NULL;
 +}
 +
  /* Allocate space for a new ref_array_item and copy the objectname and flag to it */
  static struct ref_array_item *new_ref_array_item(const char *refname,
                                                 const unsigned char *objectname,
        return ref;
  }
  
 +static int filter_ref_kind(struct ref_filter *filter, const char *refname)
 +{
 +      unsigned int i;
 +
 +      static struct {
 +              const char *prefix;
 +              unsigned int kind;
 +      } ref_kind[] = {
 +              { "refs/heads/" , FILTER_REFS_BRANCHES },
 +              { "refs/remotes/" , FILTER_REFS_REMOTES },
 +              { "refs/tags/", FILTER_REFS_TAGS}
 +      };
 +
 +      if (filter->kind == FILTER_REFS_BRANCHES ||
 +          filter->kind == FILTER_REFS_REMOTES ||
 +          filter->kind == FILTER_REFS_TAGS)
 +              return filter->kind;
 +      else if (!strcmp(refname, "HEAD"))
 +              return FILTER_REFS_DETACHED_HEAD;
 +
 +      for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
 +              if (starts_with(refname, ref_kind[i].prefix))
 +                      return ref_kind[i].kind;
 +      }
 +
 +      return FILTER_REFS_OTHERS;
 +}
 +
  /*
   * A call-back given to for_each_ref().  Filter refs and keep them for
   * later object processing.
@@@ -1302,8 -840,6 +1276,8 @@@ static int ref_filter_handler(const cha
        struct ref_filter_cbdata *ref_cbdata = cb_data;
        struct ref_filter *filter = ref_cbdata->filter;
        struct ref_array_item *ref;
 +      struct commit *commit = NULL;
 +      unsigned int kind;
  
        if (flag & REF_BAD_NAME) {
                warning("ignoring ref with broken name %s", refname);
                return 0;
        }
  
 -      if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
 +      /* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
 +      kind = filter_ref_kind(filter, refname);
 +      if (!(kind & filter->kind))
 +              return 0;
 +
 +      if (!filter_pattern_match(filter, refname))
 +              return 0;
 +
 +      if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
                return 0;
  
 +      /*
 +       * A merge filter is applied on refs pointing to commits. Hence
 +       * obtain the commit using the 'oid' available and discard all
 +       * non-commits early. The actual filtering is done later.
 +       */
 +      if (filter->merge_commit || filter->with_commit || filter->verbose) {
 +              commit = lookup_commit_reference_gently(oid->hash, 1);
 +              if (!commit)
 +                      return 0;
 +              /* We perform the filtering for the '--contains' option */
 +              if (filter->with_commit &&
 +                  !commit_contains(filter, commit))
 +                      return 0;
 +      }
 +
        /*
         * We do not open the object yet; sort may only need refname
         * to do its job and the resulting list may yet to be pruned
         * by maxcount logic.
         */
        ref = new_ref_array_item(refname, oid->hash, flag);
 +      ref->commit = commit;
  
        REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
        ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
 +      ref->kind = kind;
        return 0;
  }
  
@@@ -1374,50 -885,6 +1348,50 @@@ void ref_array_clear(struct ref_array *
        array->nr = array->alloc = 0;
  }
  
 +static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
 +{
 +      struct rev_info revs;
 +      int i, old_nr;
 +      struct ref_filter *filter = ref_cbdata->filter;
 +      struct ref_array *array = ref_cbdata->array;
 +      struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
 +
 +      init_revisions(&revs, NULL);
 +
 +      for (i = 0; i < array->nr; i++) {
 +              struct ref_array_item *item = array->items[i];
 +              add_pending_object(&revs, &item->commit->object, item->refname);
 +              to_clear[i] = item->commit;
 +      }
 +
 +      filter->merge_commit->object.flags |= UNINTERESTING;
 +      add_pending_object(&revs, &filter->merge_commit->object, "");
 +
 +      revs.limited = 1;
 +      if (prepare_revision_walk(&revs))
 +              die(_("revision walk setup failed"));
 +
 +      old_nr = array->nr;
 +      array->nr = 0;
 +
 +      for (i = 0; i < old_nr; i++) {
 +              struct ref_array_item *item = array->items[i];
 +              struct commit *commit = item->commit;
 +
 +              int is_merged = !!(commit->object.flags & UNINTERESTING);
 +
 +              if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
 +                      array->items[array->nr++] = array->items[i];
 +              else
 +                      free_array_item(item);
 +      }
 +
 +      for (i = 0; i < old_nr; i++)
 +              clear_commit_marks(to_clear[i], ALL_REV_FLAGS);
 +      clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
 +      free(to_clear);
 +}
 +
  /*
   * API for filtering a set of refs. Based on the type of refs the user
   * has requested, we iterate through those refs and apply filters
  int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
  {
        struct ref_filter_cbdata ref_cbdata;
 +      int ret = 0;
 +      unsigned int broken = 0;
  
        ref_cbdata.array = array;
        ref_cbdata.filter = filter;
  
 -      if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
 -              return for_each_rawref(ref_filter_handler, &ref_cbdata);
 -      else if (type & FILTER_REFS_ALL)
 -              return for_each_ref(ref_filter_handler, &ref_cbdata);
 -      else
 +      if (type & FILTER_REFS_INCLUDE_BROKEN)
 +              broken = 1;
 +      filter->kind = type & FILTER_REFS_KIND_MASK;
 +
 +      /*  Simple per-ref filtering */
 +      if (!filter->kind)
                die("filter_refs: invalid type");
 -      return 0;
 +      else {
 +              /*
 +               * For common cases where we need only branches or remotes or tags,
 +               * we only iterate through those refs. If a mix of refs is needed,
 +               * we iterate over all refs and filter out required refs with the help
 +               * of filter_ref_kind().
 +               */
 +              if (filter->kind == FILTER_REFS_BRANCHES)
 +                      ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken);
 +              else if (filter->kind == FILTER_REFS_REMOTES)
 +                      ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken);
 +              else if (filter->kind == FILTER_REFS_TAGS)
 +                      ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
 +              else if (filter->kind & FILTER_REFS_ALL)
 +                      ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken);
 +              if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
 +                      head_ref(ref_filter_handler, &ref_cbdata);
 +      }
 +
 +
 +      /*  Filters that need revision walking */
 +      if (filter->merge_commit)
 +              do_merge_filter(&ref_cbdata);
 +
 +      return ret;
  }
  
  static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
  
        get_ref_atom_value(a, s->atom, &va);
        get_ref_atom_value(b, s->atom, &vb);
 -      switch (cmp_type) {
 -      case FIELD_STR:
 +      if (s->version)
 +              cmp = versioncmp(va->s, vb->s);
 +      else if (cmp_type == FIELD_STR)
                cmp = strcmp(va->s, vb->s);
 -              break;
 -      default:
 +      else {
                if (va->ul < vb->ul)
                        cmp = -1;
                else if (va->ul == vb->ul)
                        cmp = 0;
                else
                        cmp = 1;
 -              break;
        }
 +
        return (s->reverse) ? -cmp : cmp;
  }
  
@@@ -1512,6 -952,32 +1486,6 @@@ void ref_array_sort(struct ref_sorting 
        qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
  }
  
 -static void print_value(struct atom_value *v, int quote_style)
 -{
 -      struct strbuf sb = STRBUF_INIT;
 -      switch (quote_style) {
 -      case QUOTE_NONE:
 -              fputs(v->s, stdout);
 -              break;
 -      case QUOTE_SHELL:
 -              sq_quote_buf(&sb, v->s);
 -              break;
 -      case QUOTE_PERL:
 -              perl_quote_buf(&sb, v->s);
 -              break;
 -      case QUOTE_PYTHON:
 -              python_quote_buf(&sb, v->s);
 -              break;
 -      case QUOTE_TCL:
 -              tcl_quote_buf(&sb, v->s);
 -              break;
 -      }
 -      if (quote_style != QUOTE_NONE) {
 -              fputs(sb.buf, stdout);
 -              strbuf_release(&sb);
 -      }
 -}
 -
  static int hex1(char ch)
  {
        if ('0' <= ch && ch <= '9')
@@@ -1530,10 -996,8 +1504,10 @@@ static int hex2(const char *cp
                return -1;
  }
  
 -static void emit(const char *cp, const char *ep)
 +static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
  {
 +      struct strbuf *s = &state->stack->output;
 +
        while (*cp && (!ep || cp < ep)) {
                if (*cp == '%') {
                        if (cp[1] == '%')
                        else {
                                int ch = hex2(cp + 1);
                                if (0 <= ch) {
 -                                      putchar(ch);
 +                                      strbuf_addch(s, ch);
                                        cp += 3;
                                        continue;
                                }
                        }
                }
 -              putchar(*cp);
 +              strbuf_addch(s, *cp);
                cp++;
        }
  }
  void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
  {
        const char *cp, *sp, *ep;
 +      struct strbuf *final_buf;
 +      struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
 +
 +      state.quote_style = quote_style;
 +      push_stack_element(&state.stack);
  
        for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
                struct atom_value *atomv;
  
                ep = strchr(sp, ')');
                if (cp < sp)
 -                      emit(cp, sp);
 +                      append_literal(cp, sp, &state);
                get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
 -              print_value(atomv, quote_style);
 +              atomv->handler(atomv, &state);
        }
        if (*cp) {
                sp = cp + strlen(cp);
 -              emit(cp, sp);
 +              append_literal(cp, sp, &state);
        }
        if (need_color_reset_at_eol) {
                struct atom_value resetv;
                if (color_parse("reset", color) < 0)
                        die("BUG: couldn't parse 'reset' as a color");
                resetv.s = color;
 -              print_value(&resetv, quote_style);
 +              append_atom(&resetv, &state);
        }
 +      if (state.stack->prev)
 +              die(_("format: %%(end) atom missing"));
 +      final_buf = &state.stack->output;
 +      fwrite(final_buf->buf, 1, final_buf->len, stdout);
 +      pop_stack_element(&state.stack);
        putchar('\n');
  }
  
@@@ -1620,29 -1074,7 +1594,29 @@@ int parse_opt_ref_sorting(const struct 
                s->reverse = 1;
                arg++;
        }
 +      if (skip_prefix(arg, "version:", &arg) ||
 +          skip_prefix(arg, "v:", &arg))
 +              s->version = 1;
        len = strlen(arg);
        s->atom = parse_ref_filter_atom(arg, arg+len);
        return 0;
  }
 +
 +int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
 +{
 +      struct ref_filter *rf = opt->value;
 +      unsigned char sha1[20];
 +
 +      rf->merge = starts_with(opt->long_name, "no")
 +              ? REF_FILTER_MERGED_OMIT
 +              : REF_FILTER_MERGED_INCLUDE;
 +
 +      if (get_sha1(arg, sha1))
 +              die(_("malformed object name %s"), arg);
 +
 +      rf->merge_commit = lookup_commit_reference_gently(sha1, 0);
 +      if (!rf->merge_commit)
 +              return opterror(opt, "must point to a commit", 0);
 +
 +      return 0;
 +}
diff --combined refs.c
index 91c88bad4a764e20c810276fc5b9c1689ba328c3,b2a922945d2a5e6adc96da2ba96ba1be90c03cce..132eff52ca4092eae4c2e1c0f86a8c380f88778a
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -304,11 -304,6 +304,11 @@@ struct ref_entry 
  };
  
  static void read_loose_refs(const char *dirname, struct ref_dir *dir);
 +static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len);
 +static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache,
 +                                        const char *dirname, size_t len,
 +                                        int incomplete);
 +static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
  
  static struct ref_dir *get_ref_dir(struct ref_entry *entry)
  {
        dir = &entry->u.subdir;
        if (entry->flag & REF_INCOMPLETE) {
                read_loose_refs(entry->name, dir);
 +
 +              /*
 +               * Manually add refs/bisect, which, being
 +               * per-worktree, might not appear in the directory
 +               * listing for refs/ in the main repo.
 +               */
 +              if (!strcmp(entry->name, "refs/")) {
 +                      int pos = search_ref_dir(dir, "refs/bisect/", 12);
 +                      if (pos < 0) {
 +                              struct ref_entry *child_entry;
 +                              child_entry = create_dir_entry(dir->ref_cache,
 +                                                             "refs/bisect/",
 +                                                             12, 1);
 +                              add_entry_to_dir(dir, child_entry);
 +                              read_loose_refs("refs/bisect",
 +                                              &child_entry->u.subdir);
 +                      }
 +              }
                entry->flag &= ~REF_INCOMPLETE;
        }
        return dir;
@@@ -1602,16 -1579,15 +1602,15 @@@ static int resolve_missing_loose_ref(co
  }
  
  /* This function needs to return a meaningful errno on failure */
- static const char *resolve_ref_unsafe_1(const char *refname,
-                                       int resolve_flags,
-                                       unsigned char *sha1,
-                                       int *flags,
-                                       struct strbuf *sb_path)
+ static const char *resolve_ref_1(const char *refname,
+                                int resolve_flags,
+                                unsigned char *sha1,
+                                int *flags,
+                                struct strbuf *sb_refname,
+                                struct strbuf *sb_path,
+                                struct strbuf *sb_contents)
  {
        int depth = MAXDEPTH;
-       ssize_t len;
-       char buffer[256];
-       static char refname_buffer[256];
        int bad_name = 0;
  
        if (flags)
  
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
-                       len = readlink(path, buffer, sizeof(buffer)-1);
-                       if (len < 0) {
+                       strbuf_reset(sb_contents);
+                       if (strbuf_readlink(sb_contents, path, 0) < 0) {
                                if (errno == ENOENT || errno == EINVAL)
                                        /* inconsistent with lstat; retry */
                                        goto stat_ref;
                                else
                                        return NULL;
                        }
-                       buffer[len] = 0;
-                       if (starts_with(buffer, "refs/") &&
-                                       !check_refname_format(buffer, 0)) {
-                               strcpy(refname_buffer, buffer);
-                               refname = refname_buffer;
+                       if (starts_with(sb_contents->buf, "refs/") &&
+                           !check_refname_format(sb_contents->buf, 0)) {
+                               strbuf_swap(sb_refname, sb_contents);
+                               refname = sb_refname->buf;
                                if (flags)
                                        *flags |= REF_ISSYMREF;
                                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
                        else
                                return NULL;
                }
-               len = read_in_full(fd, buffer, sizeof(buffer)-1);
-               if (len < 0) {
+               strbuf_reset(sb_contents);
+               if (strbuf_read(sb_contents, fd, 256) < 0) {
                        int save_errno = errno;
                        close(fd);
                        errno = save_errno;
                        return NULL;
                }
                close(fd);
-               while (len && isspace(buffer[len-1]))
-                       len--;
-               buffer[len] = '\0';
+               strbuf_rtrim(sb_contents);
  
                /*
                 * Is it a symbolic ref?
                 */
-               if (!starts_with(buffer, "ref:")) {
+               if (!starts_with(sb_contents->buf, "ref:")) {
                        /*
                         * Please note that FETCH_HEAD has a second
                         * line containing other data.
                         */
-                       if (get_sha1_hex(buffer, sha1) ||
-                           (buffer[40] != '\0' && !isspace(buffer[40]))) {
+                       if (get_sha1_hex(sb_contents->buf, sha1) ||
+                           (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) {
                                if (flags)
                                        *flags |= REF_ISBROKEN;
                                errno = EINVAL;
                }
                if (flags)
                        *flags |= REF_ISSYMREF;
-               buf = buffer + 4;
+               buf = sb_contents->buf + 4;
                while (isspace(*buf))
                        buf++;
-               refname = strcpy(refname_buffer, buf);
+               strbuf_reset(sb_refname);
+               strbuf_addstr(sb_refname, buf);
+               refname = sb_refname->buf;
                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
                        hashclr(sha1);
                        return refname;
  const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               unsigned char *sha1, int *flags)
  {
+       static struct strbuf sb_refname = STRBUF_INIT;
+       struct strbuf sb_contents = STRBUF_INIT;
        struct strbuf sb_path = STRBUF_INIT;
-       const char *ret = resolve_ref_unsafe_1(refname, resolve_flags,
-                                              sha1, flags, &sb_path);
+       const char *ret;
+       ret = resolve_ref_1(refname, resolve_flags, sha1, flags,
+                           &sb_refname, &sb_path, &sb_contents);
        strbuf_release(&sb_path);
+       strbuf_release(&sb_contents);
        return ret;
  }
  
@@@ -2131,15 -2111,6 +2134,15 @@@ int for_each_ref_in(const char *prefix
        return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
  }
  
 +int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
 +{
 +      unsigned int flag = 0;
 +
 +      if (broken)
 +              flag = DO_FOR_EACH_INCLUDE_BROKEN;
 +      return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);
 +}
 +
  int for_each_ref_in_submodule(const char *submodule, const char *prefix,
                each_ref_fn fn, void *cb_data)
  {
@@@ -2222,8 -2193,7 +2225,7 @@@ int for_each_glob_ref_in(each_ref_fn fn
  
        if (!has_glob_specials(pattern)) {
                /* Append implied '/' '*' if not present. */
-               if (real_pattern.buf[real_pattern.len - 1] != '/')
-                       strbuf_addch(&real_pattern, '/');
+               strbuf_complete(&real_pattern, '/');
                /* No need to check for '*', there is none. */
                strbuf_addch(&real_pattern, '*');
        }
@@@ -2681,8 -2651,6 +2683,8 @@@ struct pack_refs_cb_data 
        struct ref_to_prune *ref_to_prune;
  };
  
 +static int is_per_worktree_ref(const char *refname);
 +
  /*
   * An each_ref_entry_fn that is run over loose references only.  If
   * the loose reference can be packed, add an entry in the packed ref
@@@ -2696,10 -2664,6 +2698,10 @@@ static int pack_if_possible_fn(struct r
        struct ref_entry *packed_entry;
        int is_tag_ref = starts_with(entry->name, "refs/tags/");
  
 +      /* Do not pack per-worktree refs: */
 +      if (is_per_worktree_ref(entry->name))
 +              return 0;
 +
        /* ALWAYS pack tags */
        if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
                return 0;
                int namelen = strlen(entry->name) + 1;
                struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
                hashcpy(n->sha1, entry->u.value.oid.hash);
-               strcpy(n->name, entry->name);
+               memcpy(n->name, entry->name, namelen); /* includes NUL */
                n->next = cb->ref_to_prune;
                cb->ref_to_prune = n;
        }
@@@ -2894,8 -2858,7 +2896,8 @@@ static int delete_ref_loose(struct ref_
  
  static int is_per_worktree_ref(const char *refname)
  {
 -      return !strcmp(refname, "HEAD");
 +      return !strcmp(refname, "HEAD") ||
 +              starts_with(refname, "refs/bisect/");
  }
  
  static int is_pseudoref_syntax(const char *refname)
@@@ -3365,10 -3328,10 +3367,10 @@@ static int log_ref_write_fd(int fd, con
        msglen = msg ? strlen(msg) : 0;
        maxlen = strlen(committer) + msglen + 100;
        logrec = xmalloc(maxlen);
-       len = sprintf(logrec, "%s %s %s\n",
-                     sha1_to_hex(old_sha1),
-                     sha1_to_hex(new_sha1),
-                     committer);
+       len = xsnprintf(logrec, maxlen, "%s %s %s\n",
+                       sha1_to_hex(old_sha1),
+                       sha1_to_hex(new_sha1),
+                       committer);
        if (msglen)
                len += copy_msg(logrec + len - 1, msg) - 1;
  
@@@ -4020,10 -3983,10 +4022,10 @@@ void ref_transaction_free(struct ref_tr
  static struct ref_update *add_update(struct ref_transaction *transaction,
                                     const char *refname)
  {
-       size_t len = strlen(refname);
-       struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
+       size_t len = strlen(refname) + 1;
+       struct ref_update *update = xcalloc(1, sizeof(*update) + len);
  
-       strcpy((char *)update->refname, refname);
+       memcpy((char *)update->refname, refname, len); /* includes NUL */
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
        transaction->updates[transaction->nr++] = update;
        return update;
diff --combined setup.c
index e41e5e1a82e3cbd9e0168e158b336f621116adc7,2b64cbbbfac60ac2db169ea19c03c891f0333864..b2644716911c3ee9594ca1e72d5fb30900d9331e
+++ b/setup.c
@@@ -99,10 -99,7 +99,7 @@@ char *prefix_path_gently(const char *pr
                        return NULL;
                }
        } else {
-               sanitized = xmalloc(len + strlen(path) + 1);
-               if (len)
-                       memcpy(sanitized, prefix, len);
-               strcpy(sanitized + len, path);
+               sanitized = xstrfmt("%.*s%s", len, prefix, path);
                if (remaining_prefix)
                        *remaining_prefix = len;
                if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) {
@@@ -228,22 -225,15 +225,22 @@@ void verify_non_filename(const char *pr
  }
  
  int get_common_dir(struct strbuf *sb, const char *gitdir)
 +{
 +      const char *git_env_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
 +      if (git_env_common_dir) {
 +              strbuf_addstr(sb, git_env_common_dir);
 +              return 1;
 +      } else {
 +              return get_common_dir_noenv(sb, gitdir);
 +      }
 +}
 +
 +int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)
  {
        struct strbuf data = STRBUF_INIT;
        struct strbuf path = STRBUF_INIT;
 -      const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
        int ret = 0;
 -      if (git_common_dir) {
 -              strbuf_addstr(sb, git_common_dir);
 -              return 1;
 -      }
 +
        strbuf_addf(&path, "%s/commondir", gitdir);
        if (file_exists(path.buf)) {
                if (strbuf_read_file(&data, path.buf, 0) <= 0)
@@@ -475,11 -465,8 +472,8 @@@ const char *read_gitfile_gently(const c
  
        if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {
                size_t pathlen = slash+1 - path;
-               size_t dirlen = pathlen + len - 8;
-               dir = xmalloc(dirlen + 1);
-               strncpy(dir, path, pathlen);
-               strncpy(dir + pathlen, buf + 8, len - 8);
-               dir[dirlen] = '\0';
+               dir = xstrfmt("%.*s%.*s", (int)pathlen, path,
+                             (int)(len - 8), buf + 8);
                free(buf);
                buf = dir;
        }
diff --combined sha1_file.c
index ca699d7beb067bff83ea15f0a0e724916f33549b,cc3de244ebfd5f12cde8180e17cb8ec320c981dd..50896ff1eb5ce3f216b31658b4a03d6e7fdc5adb
@@@ -208,44 -208,25 +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;
@@@ -671,13 -652,15 +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;
@@@ -786,37 -769,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.
@@@ -884,8 -836,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;
  }
@@@ -925,7 -881,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)
@@@ -1059,7 -1020,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;
  }
  
@@@ -1125,8 -1090,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)
@@@ -1161,11 -1129,12 +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);
@@@ -1479,7 -1453,7 +1468,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);
@@@ -2945,7 -2919,7 +2934,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);
@@@ -3008,7 -2982,7 +2997,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;
  }
@@@ -3038,29 -3012,31 +3027,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;
  }
@@@ -3073,10 -3049,10 +3064,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 finalize_object_file(tmp_file, filename);
+       return finalize_object_file(tmp_file.buf, filename);
  }
  
  static int freshen_loose_object(const unsigned char *sha1)
@@@ -3154,7 -3130,7 +3145,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.
@@@ -3172,7 -3148,8 +3163,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))
@@@ -3200,7 -3177,7 +3192,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);
  
diff --combined submodule.c
index 5e5a46fe2ab765ff5ecec01a9bc46351fe596837,c480ed53b446e17ec3a25624846b018e4ae7c9f2..5879cfb158f0b581a54a56de5de44af5c2d0a700
@@@ -122,8 -122,16 +122,9 @@@ static int add_submodule_odb(const cha
        struct strbuf objects_directory = STRBUF_INIT;
        struct alternate_object_database *alt_odb;
        int ret = 0;
 -      const char *git_dir;
+       int alloc;
  
 -      strbuf_addf(&objects_directory, "%s/.git", path);
 -      git_dir = read_gitfile(objects_directory.buf);
 -      if (git_dir) {
 -              strbuf_reset(&objects_directory);
 -              strbuf_addstr(&objects_directory, git_dir);
 -      }
 -      strbuf_addstr(&objects_directory, "/objects/");
 +      strbuf_git_path_submodule(&objects_directory, path, "objects/");
        if (!is_directory(objects_directory.buf)) {
                ret = -1;
                goto done;
                                        objects_directory.len))
                        goto done;
  
-       alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb));
+       alloc = objects_directory.len + 42; /* for "12/345..." sha1 */
+       alt_odb = xmalloc(sizeof(*alt_odb) + alloc);
        alt_odb->next = alt_odb_list;
-       strcpy(alt_odb->base, objects_directory.buf);
+       xsnprintf(alt_odb->base, alloc, "%s", objects_directory.buf);
        alt_odb->name = alt_odb->base + objects_directory.len;
        alt_odb->name[2] = '/';
        alt_odb->name[40] = '\0';
diff --combined transport.c
index 863eb524f9087302be6b99c89f071195dd1258b8,3b47d493d1ab8a001f4b1d84ff110ddd43005da5..23b2ed6f0cf6f521c4d5afa6c1cffe0786d29a5f
@@@ -654,23 -654,24 +654,24 @@@ static void print_ok_ref_status(struct 
                        "[new branch]"),
                        ref, ref->peer_ref, NULL, porcelain);
        else {
-               char quickref[84];
+               struct strbuf quickref = STRBUF_INIT;
                char type;
                const char *msg;
  
-               strcpy(quickref, status_abbrev(ref->old_sha1));
+               strbuf_addstr(&quickref, status_abbrev(ref->old_sha1));
                if (ref->forced_update) {
-                       strcat(quickref, "...");
+                       strbuf_addstr(&quickref, "...");
                        type = '+';
                        msg = "forced update";
                } else {
-                       strcat(quickref, "..");
+                       strbuf_addstr(&quickref, "..");
                        type = ' ';
                        msg = NULL;
                }
-               strcat(quickref, status_abbrev(ref->new_sha1));
+               strbuf_addstr(&quickref, status_abbrev(ref->new_sha1));
  
-               print_ref_status(type, quickref, ref, ref->peer_ref, msg, porcelain);
+               print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg, porcelain);
+               strbuf_release(&quickref);
        }
  }
  
@@@ -915,42 -916,6 +916,42 @@@ static int external_specification_len(c
        return strchr(url, ':') - url;
  }
  
 +static const struct string_list *protocol_whitelist(void)
 +{
 +      static int enabled = -1;
 +      static struct string_list allowed = STRING_LIST_INIT_DUP;
 +
 +      if (enabled < 0) {
 +              const char *v = getenv("GIT_ALLOW_PROTOCOL");
 +              if (v) {
 +                      string_list_split(&allowed, v, ':', -1);
 +                      string_list_sort(&allowed);
 +                      enabled = 1;
 +              } else {
 +                      enabled = 0;
 +              }
 +      }
 +
 +      return enabled ? &allowed : NULL;
 +}
 +
 +int is_transport_allowed(const char *type)
 +{
 +      const struct string_list *allowed = protocol_whitelist();
 +      return !allowed || string_list_has_string(allowed, type);
 +}
 +
 +void transport_check_allowed(const char *type)
 +{
 +      if (!is_transport_allowed(type))
 +              die("transport '%s' not allowed", type);
 +}
 +
 +int transport_restrict_protocols(void)
 +{
 +      return !!protocol_whitelist();
 +}
 +
  struct transport *transport_get(struct remote *remote, const char *url)
  {
        const char *helper;
        if (helper) {
                transport_helper_init(ret, helper);
        } else if (starts_with(url, "rsync:")) {
 +              transport_check_allowed("rsync");
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
                ret->smart_options = NULL;
        } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
 +              transport_check_allowed("file");
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->fetch = fetch_refs_from_bundle;
                || starts_with(url, "ssh://")
                || starts_with(url, "git+ssh://")
                || starts_with(url, "ssh+git://")) {
 -              /* These are builtin smart transports. */
 +              /*
 +               * These are builtin smart transports; "allowed" transports
 +               * will be checked individually in git_connect.
 +               */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->set_option = NULL;