Merge branch 'rs/use-strbuf-addbuf' into maint
authorJunio C Hamano <gitster@pobox.com>
Mon, 8 Aug 2016 21:21:42 +0000 (14:21 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 8 Aug 2016 21:21:42 +0000 (14:21 -0700)
Code cleanup.

* rs/use-strbuf-addbuf:
strbuf: avoid calling strbuf_grow() twice in strbuf_addbuf()
use strbuf_addbuf() for appending a strbuf to another

1  2 
dir.c
path.c
strbuf.c
strbuf.h
wt-status.c
diff --combined dir.c
index 6172b3438d356d0359c2158708c718c6694e183a,05b98843ee3eb5ef5cf5f99b3ab5b4a866c274e4..0ea235f3d643d7a9072aca75d7317d1fd7552b4d
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -53,16 -53,24 +53,16 @@@ static enum path_treatment read_directo
        int check_only, const struct path_simplify *simplify);
  static int get_dtype(struct dirent *de, const char *path, int len);
  
 -/* helper string functions with support for the ignore_case flag */
 -int strcmp_icase(const char *a, const char *b)
 +int fspathcmp(const char *a, const char *b)
  {
        return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
  }
  
 -int strncmp_icase(const char *a, const char *b, size_t count)
 +int fspathncmp(const char *a, const char *b, size_t count)
  {
        return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
  }
  
 -int fnmatch_icase(const char *pattern, const char *string, int flags)
 -{
 -      return wildmatch(pattern, string,
 -                       flags | (ignore_case ? WM_CASEFOLD : 0),
 -                       NULL);
 -}
 -
  int git_fnmatch(const struct pathspec_item *item,
                const char *pattern, const char *string,
                int prefix)
@@@ -449,7 -457,7 +449,7 @@@ int no_wildcard(const char *string
  
  void parse_exclude_pattern(const char **pattern,
                           int *patternlen,
 -                         int *flags,
 +                         unsigned *flags,
                           int *nowildcardlen)
  {
        const char *p = *pattern;
@@@ -490,12 -498,17 +490,12 @@@ void add_exclude(const char *string, co
  {
        struct exclude *x;
        int patternlen;
 -      int flags;
 +      unsigned flags;
        int nowildcardlen;
  
        parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
        if (flags & EXC_FLAG_MUSTBEDIR) {
 -              char *s;
 -              x = xmalloc(sizeof(*x) + patternlen + 1);
 -              s = (char *)(x+1);
 -              memcpy(s, string, patternlen);
 -              s[patternlen] = '\0';
 -              x->pattern = s;
 +              FLEXPTR_ALLOC_MEM(x, pattern, string, patternlen);
        } else {
                x = xmalloc(sizeof(*x));
                x->pattern = string;
@@@ -551,7 -564,9 +551,7 @@@ void clear_exclude_list(struct exclude_
        free(el->excludes);
        free(el->filebuf);
  
 -      el->nr = 0;
 -      el->excludes = NULL;
 -      el->filebuf = NULL;
 +      memset(el, 0, sizeof(*el));
  }
  
  static void trim_trailing_spaces(char *buf)
@@@ -612,7 -627,10 +612,7 @@@ static struct untracked_cache_dir *look
        }
  
        uc->dir_created++;
 -      d = xmalloc(sizeof(*d) + len + 1);
 -      memset(d, 0, sizeof(*d));
 -      memcpy(d->name, name, len);
 -      d->name[len] = '\0';
 +      FLEX_ALLOC_MEM(d, name, name, len);
  
        ALLOC_GROW(dir->dirs, dir->dirs_nr + 1, dir->dirs_alloc);
        memmove(dir->dirs + first + 1, dir->dirs + first,
@@@ -681,7 -699,7 +681,7 @@@ static int add_excludes(const char *fna
                        return 0;
                }
                if (buf[size-1] != '\n') {
 -                      buf = xrealloc(buf, size+1);
 +                      buf = xrealloc(buf, st_add(size, 1));
                        buf[size++] = '\n';
                }
        } else {
                        close(fd);
                        return 0;
                }
 -              buf = xmalloc(size+1);
 +              buf = xmallocz(size);
                if (read_in_full(fd, buf, size) != size) {
                        free(buf);
                        close(fd);
@@@ -790,16 -808,16 +790,16 @@@ void add_excludes_from_file(struct dir_
  
  int match_basename(const char *basename, int basenamelen,
                   const char *pattern, int prefix, int patternlen,
 -                 int flags)
 +                 unsigned flags)
  {
        if (prefix == patternlen) {
                if (patternlen == basenamelen &&
 -                  !strncmp_icase(pattern, basename, basenamelen))
 +                  !fspathncmp(pattern, basename, basenamelen))
                        return 1;
        } else if (flags & EXC_FLAG_ENDSWITH) {
                /* "*literal" matching against "fooliteral" */
                if (patternlen - 1 <= basenamelen &&
 -                  !strncmp_icase(pattern + 1,
 +                  !fspathncmp(pattern + 1,
                                   basename + basenamelen - (patternlen - 1),
                                   patternlen - 1))
                        return 1;
  int match_pathname(const char *pathname, int pathlen,
                   const char *base, int baselen,
                   const char *pattern, int prefix, int patternlen,
 -                 int flags)
 +                 unsigned flags)
  {
        const char *name;
        int namelen;
         */
        if (pathlen < baselen + 1 ||
            (baselen && pathname[baselen] != '/') ||
 -          strncmp_icase(pathname, base, baselen))
 +          fspathncmp(pathname, base, baselen))
                return 0;
  
        namelen = baselen ? pathlen - baselen - 1 : pathlen;
                if (prefix > namelen)
                        return 0;
  
 -              if (strncmp_icase(pattern, name, prefix))
 +              if (fspathncmp(pattern, name, prefix))
                        return 0;
                pattern += prefix;
                patternlen -= prefix;
@@@ -883,7 -901,6 +883,7 @@@ static struct exclude *last_exclude_mat
                                                       int *dtype,
                                                       struct exclude_list *el)
  {
 +      struct exclude *exc = NULL; /* undecided */
        int i;
  
        if (!el->nr)
                        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))
 -                      return x;
 +                                 exclude, prefix, x->patternlen, x->flags)) {
 +                      exc = x;
 +                      break;
 +              }
        }
 -      return NULL; /* undecided */
 +      return exc;
  }
  
  /*
@@@ -1151,8 -1164,10 +1151,8 @@@ static struct dir_entry *dir_entry_new(
  {
        struct dir_entry *ent;
  
 -      ent = xmalloc(sizeof(*ent) + len + 1);
 +      FLEX_ALLOC_MEM(ent, name, pathname, len);
        ent->len = len;
 -      memcpy(ent->name, pathname, len);
 -      ent->name[len] = 0;
        return ent;
  }
  
@@@ -1490,7 -1505,8 +1490,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
@@@ -1821,67 -1837,31 +1821,67 @@@ static const char *get_ident_string(voi
                return sb.buf;
        if (uname(&uts) < 0)
                die_errno(_("failed to get kernel name and information"));
 -      strbuf_addf(&sb, "Location %s, system %s %s %s", get_git_work_tree(),
 -                  uts.sysname, uts.release, uts.version);
 +      strbuf_addf(&sb, "Location %s, system %s", get_git_work_tree(),
 +                  uts.sysname);
        return sb.buf;
  }
  
  static int ident_in_untracked(const struct untracked_cache *uc)
  {
 -      const char *end = uc->ident.buf + uc->ident.len;
 -      const char *p   = uc->ident.buf;
 +      /*
 +       * Previous git versions may have saved many NUL separated
 +       * strings in the "ident" field, but it is insane to manage
 +       * many locations, so just take care of the first one.
 +       */
  
 -      for (p = uc->ident.buf; p < end; p += strlen(p) + 1)
 -              if (!strcmp(p, get_ident_string()))
 -                      return 1;
 -      return 0;
 +      return !strcmp(uc->ident.buf, get_ident_string());
  }
  
 -void add_untracked_ident(struct untracked_cache *uc)
 +static void set_untracked_ident(struct untracked_cache *uc)
  {
 -      if (ident_in_untracked(uc))
 -              return;
 +      strbuf_reset(&uc->ident);
        strbuf_addstr(&uc->ident, get_ident_string());
 -      /* this strbuf contains a list of strings, save NUL too */
 +
 +      /*
 +       * This strbuf used to contain a list of NUL separated
 +       * strings, so save NUL too for backward compatibility.
 +       */
        strbuf_addch(&uc->ident, 0);
  }
  
 +static void new_untracked_cache(struct index_state *istate)
 +{
 +      struct untracked_cache *uc = xcalloc(1, sizeof(*uc));
 +      strbuf_init(&uc->ident, 100);
 +      uc->exclude_per_dir = ".gitignore";
 +      /* should be the same flags used by git-status */
 +      uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
 +      set_untracked_ident(uc);
 +      istate->untracked = uc;
 +      istate->cache_changed |= UNTRACKED_CHANGED;
 +}
 +
 +void add_untracked_cache(struct index_state *istate)
 +{
 +      if (!istate->untracked) {
 +              new_untracked_cache(istate);
 +      } else {
 +              if (!ident_in_untracked(istate->untracked)) {
 +                      free_untracked_cache(istate->untracked);
 +                      new_untracked_cache(istate);
 +              }
 +      }
 +}
 +
 +void remove_untracked_cache(struct index_state *istate)
 +{
 +      if (istate->untracked) {
 +              free_untracked_cache(istate->untracked);
 +              istate->untracked = NULL;
 +              istate->cache_changed |= UNTRACKED_CHANGED;
 +      }
 +}
 +
  static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
                                                      int base_len,
                                                      const struct pathspec *pathspec)
                return NULL;
  
        if (!ident_in_untracked(dir->untracked)) {
 -              warning(_("Untracked cache is disabled on this system."));
 +              warning(_("Untracked cache is disabled on this system or location."));
                return NULL;
        }
  
@@@ -2141,7 -2121,8 +2141,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) {
@@@ -2352,19 -2333,20 +2352,19 @@@ void write_untracked_extension(struct s
        struct ondisk_untracked_cache *ouc;
        struct write_data wd;
        unsigned char varbuf[16];
 -      int len = 0, varint_len;
 -      if (untracked->exclude_per_dir)
 -              len = strlen(untracked->exclude_per_dir);
 -      ouc = xmalloc(sizeof(*ouc) + len + 1);
 +      int varint_len;
 +      size_t len = strlen(untracked->exclude_per_dir);
 +
 +      FLEX_ALLOC_MEM(ouc, exclude_per_dir, untracked->exclude_per_dir, len);
        stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat);
        stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat);
        hashcpy(ouc->info_exclude_sha1, untracked->ss_info_exclude.sha1);
        hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);
        ouc->dir_flags = htonl(untracked->dir_flags);
 -      memcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);
  
        varint_len = encode_varint(untracked->ident.len, varbuf);
        strbuf_add(out, varbuf, varint_len);
-       strbuf_add(out, untracked->ident.buf, untracked->ident.len);
+       strbuf_addbuf(out, &untracked->ident);
  
        strbuf_add(out, ouc, ouc_size(len));
        free(ouc);
@@@ -2465,21 -2447,21 +2465,21 @@@ static int read_one_dir(struct untracke
        ud.untracked_alloc = value;
        ud.untracked_nr    = value;
        if (ud.untracked_nr)
 -              ud.untracked = xmalloc(sizeof(*ud.untracked) * ud.untracked_nr);
 +              ALLOC_ARRAY(ud.untracked, ud.untracked_nr);
        data = next;
  
        next = data;
        ud.dirs_alloc = ud.dirs_nr = decode_varint(&next);
        if (next > end)
                return -1;
 -      ud.dirs = xmalloc(sizeof(*ud.dirs) * ud.dirs_nr);
 +      ALLOC_ARRAY(ud.dirs, ud.dirs_nr);
        data = next;
  
        len = strlen((const char *)data);
        next = data + len + 1;
        if (next > rd->end)
                return -1;
 -      *untracked_ = untracked = xmalloc(sizeof(*untracked) + len);
 +      *untracked_ = untracked = xmalloc(st_add(sizeof(*untracked), len));
        memcpy(untracked, &ud, sizeof(ud));
        memcpy(untracked->name, data, len + 1);
        data = next;
@@@ -2592,7 -2574,7 +2592,7 @@@ struct untracked_cache *read_untracked_
        rd.data       = next;
        rd.end        = end;
        rd.index      = 0;
 -      rd.ucd        = xmalloc(sizeof(*rd.ucd) * len);
 +      ALLOC_ARRAY(rd.ucd, len);
  
        if (read_one_dir(&uc->root, &rd) || rd.index != len)
                goto done;
diff --combined path.c
index 259aeed846bd3dac8e10cde30a22ec1ad8697a63,e1bc8f04fe221e3ee14ba3b5cf9606001ee66e6a..17551c483476050325114b8521f2960707855c59
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -5,7 -5,6 +5,7 @@@
  #include "strbuf.h"
  #include "string-list.h"
  #include "dir.h"
 +#include "worktree.h"
  
  static int get_st_mode_bits(const char *path, int *mode)
  {
@@@ -92,274 -91,58 +92,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, const char *common_dir)
 +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 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);
                }
 +              child->value = root->value;
 +              root->value = NULL;
 +              root->len = i;
  
 -              if (!common_dir)
 -                      common_dir = get_git_common_dir();
 +              memset(root->children, 0, sizeof(root->children));
 +              root->children[(unsigned char)root->contents[i]] = child;
  
 -              if (is_dir && dir_prefix(base, path)) {
 -                      replace_dir(buf, git_dir_len, common_dir);
 -                      return;
 +              /* 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, 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);
@@@ -384,11 -167,10 +384,11 @@@ static void adjust_git_path(struct strb
                update_common_dir(buf, git_dir_len, NULL);
  }
  
 -static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 +static void do_git_path(const struct worktree *wt, struct strbuf *buf,
 +                      const char *fmt, va_list args)
  {
        int gitdir_len;
 -      strbuf_addstr(buf, get_git_dir());
 +      strbuf_addstr(buf, get_worktree_git_dir(wt));
        if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
                strbuf_addch(buf, '/');
        gitdir_len = buf->len;
        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(NULL, buf, fmt, args);
 +      va_end(args);
 +      return buf->buf;
 +}
 +
  void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
  {
        va_list args;
        va_start(args, fmt);
 -      do_git_path(sb, fmt, args);
 +      do_git_path(NULL, sb, fmt, args);
        va_end(args);
  }
  
@@@ -420,7 -192,7 +420,7 @@@ const char *git_path(const char *fmt, .
        struct strbuf *pathname = get_pathname();
        va_list args;
        va_start(args, fmt);
 -      do_git_path(pathname, fmt, args);
 +      do_git_path(NULL, pathname, fmt, args);
        va_end(args);
        return pathname->buf;
  }
@@@ -430,7 -202,7 +430,7 @@@ char *git_pathdup(const char *fmt, ...
        struct strbuf path = STRBUF_INIT;
        va_list args;
        va_start(args, fmt);
 -      do_git_path(&path, fmt, args);
 +      do_git_path(NULL, &path, fmt, args);
        va_end(args);
        return strbuf_detach(&path, NULL);
  }
@@@ -456,16 -228,6 +456,16 @@@ const char *mkpath(const char *fmt, ...
        return cleanup_path(pathname->buf);
  }
  
 +const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
 +{
 +      struct strbuf *pathname = get_pathname();
 +      va_list args;
 +      va_start(args, fmt);
 +      do_git_path(wt, pathname, fmt, args);
 +      va_end(args);
 +      return pathname->buf;
 +}
 +
  static void do_submodule_path(struct strbuf *buf, const char *path,
                              const char *fmt, va_list args)
  {
        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_addbuf(&git_submodule_dir, buf);
  
        strbuf_vaddf(buf, fmt, args);
  
@@@ -515,35 -278,6 +515,35 @@@ void strbuf_git_path_submodule(struct s
        va_end(args);
  }
  
 +static void do_git_common_path(struct strbuf *buf,
 +                             const char *fmt,
 +                             va_list args)
 +{
 +      strbuf_addstr(buf, get_git_common_dir());
 +      if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 +              strbuf_addch(buf, '/');
 +      strbuf_vaddf(buf, fmt, args);
 +      strbuf_cleanup_path(buf);
 +}
 +
 +const char *git_common_path(const char *fmt, ...)
 +{
 +      struct strbuf *pathname = get_pathname();
 +      va_list args;
 +      va_start(args, fmt);
 +      do_git_common_path(pathname, fmt, args);
 +      va_end(args);
 +      return pathname->buf;
 +}
 +
 +void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
 +{
 +      va_list args;
 +      va_start(args, fmt);
 +      do_git_common_path(sb, fmt, args);
 +      va_end(args);
 +}
 +
  int validate_headref(const char *path)
  {
        struct stat st;
@@@ -625,9 -359,6 +625,9 @@@ char *expand_user_path(const char *path
                        if (!home)
                                goto return_null;
                        strbuf_addstr(&user_path, home);
 +#ifdef GIT_WINDOWS_NATIVE
 +                      convert_slashes(user_path.buf);
 +#endif
                } else {
                        struct passwd *pw = getpw_str(username, username_len);
                        if (!pw)
@@@ -664,8 -395,8 +664,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);
 +              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 {
                const char *gitfile = read_gitfile(path);
@@@ -743,17 -473,17 +743,17 @@@ static int calc_shared_perm(int mode
  {
        int tweak;
  
 -      if (shared_repository < 0)
 -              tweak = -shared_repository;
 +      if (get_shared_repository() < 0)
 +              tweak = -get_shared_repository();
        else
 -              tweak = shared_repository;
 +              tweak = get_shared_repository();
  
        if (!(mode & S_IWUSR))
                tweak &= ~0222;
        if (mode & S_IXUSR)
                /* Copy read bits to execute bits */
                tweak |= (tweak & 0444) >> 2;
 -      if (shared_repository < 0)
 +      if (get_shared_repository() < 0)
                mode = (mode & ~0777) | tweak;
        else
                mode |= tweak;
@@@ -766,7 -496,7 +766,7 @@@ int adjust_shared_perm(const char *path
  {
        int old_mode, new_mode;
  
 -      if (!shared_repository)
 +      if (!get_shared_repository())
                return 0;
        if (get_st_mode_bits(path, &old_mode) < 0)
                return -1;
        return 0;
  }
  
 +void safe_create_dir(const char *dir, int share)
 +{
 +      if (mkdir(dir, 0777) < 0) {
 +              if (errno != EEXIST) {
 +                      perror(dir);
 +                      exit(1);
 +              }
 +      }
 +      else if (share && adjust_shared_perm(dir))
 +              die(_("Could not make %s writable by group"), dir);
 +}
 +
  static int have_same_root(const char *path1, const char *path2)
  {
        int is_abs1, is_abs2;
@@@ -826,10 -544,13 +826,10 @@@ const char *relative_path(const char *i
        else if (!prefix_len)
                return in;
  
 -      if (have_same_root(in, prefix)) {
 +      if (have_same_root(in, prefix))
                /* bypass dos_drive, for "c:" is identical to "C:" */
 -              if (has_dos_drive_prefix(in)) {
 -                      i = 2;
 -                      j = 2;
 -              }
 -      } else {
 +              i = j = has_dos_drive_prefix(in);
 +      else {
                return in;
        }
  
   */
  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;
  }
  
  /*
  int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
  {
        char *dst0;
 +      int i;
  
 -      if (has_dos_drive_prefix(src)) {
 +      for (i = has_dos_drive_prefix(src); i > 0; i--)
                *dst++ = *src++;
 -              *dst++ = *src++;
 -      }
        dst0 = dst;
  
        if (is_dir_sep(*src)) {
diff --combined strbuf.c
index 1ba600bd780733f7a985c88f797efdd93c1e49fa,600e27ea4a04ad3404641cf6faa772df64beb583..f3bd5719c636d10780e466e39f1145375c4cab68
+++ b/strbuf.c
@@@ -197,6 -197,13 +197,13 @@@ void strbuf_add(struct strbuf *sb, cons
        strbuf_setlen(sb, sb->len + len);
  }
  
+ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)
+ {
+       strbuf_grow(sb, sb2->len);
+       memcpy(sb->buf + sb->len, sb2->buf, sb2->len);
+       strbuf_setlen(sb, sb->len + sb2->len);
+ }
  void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
  {
        strbuf_grow(sb, len);
@@@ -245,8 -252,8 +252,8 @@@ void strbuf_add_commented_lines(struct 
        static char prefix2[2];
  
        if (prefix1[0] != comment_line_char) {
 -              sprintf(prefix1, "%c ", comment_line_char);
 -              sprintf(prefix2, "%c", comment_line_char);
 +              xsnprintf(prefix1, sizeof(prefix1), "%c ", comment_line_char);
 +              xsnprintf(prefix2, sizeof(prefix2), "%c", comment_line_char);
        }
        add_lines(out, prefix1, prefix2, buf, size);
  }
@@@ -384,23 -391,6 +391,23 @@@ ssize_t strbuf_read(struct strbuf *sb, 
        return sb->len - oldlen;
  }
  
 +ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint)
 +{
 +      ssize_t cnt;
 +
 +      strbuf_grow(sb, hint ? hint : 8192);
 +      cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
 +      if (cnt > 0)
 +              strbuf_setlen(sb, sb->len + cnt);
 +      return cnt;
 +}
 +
 +ssize_t strbuf_write(struct strbuf *sb, FILE *f)
 +{
 +      return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0;
 +}
 +
 +
  #define STRBUF_MAXLINK (2*PATH_MAX)
  
  int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
@@@ -487,15 -477,9 +494,15 @@@ int strbuf_getwholeline(struct strbuf *
        if (errno == ENOMEM)
                die("Out of memory, getdelim failed");
  
 -      /* Restore slopbuf that we moved out of the way before */
 +      /*
 +       * Restore strbuf invariants; if getdelim left us with a NULL pointer,
 +       * we can just re-init, but otherwise we should make sure that our
 +       * length is empty, and that the result is NUL-terminated.
 +       */
        if (!sb->buf)
                strbuf_init(sb, 0);
 +      else
 +              strbuf_reset(sb);
        return EOF;
  }
  #else
@@@ -524,37 -508,15 +531,37 @@@ int strbuf_getwholeline(struct strbuf *
  }
  #endif
  
 -int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
 +static int strbuf_getdelim(struct strbuf *sb, FILE *fp, int term)
  {
        if (strbuf_getwholeline(sb, fp, term))
                return EOF;
 -      if (sb->buf[sb->len-1] == term)
 -              strbuf_setlen(sb, sb->len-1);
 +      if (sb->buf[sb->len - 1] == term)
 +              strbuf_setlen(sb, sb->len - 1);
 +      return 0;
 +}
 +
 +int strbuf_getline(struct strbuf *sb, FILE *fp)
 +{
 +      if (strbuf_getwholeline(sb, fp, '\n'))
 +              return EOF;
 +      if (sb->buf[sb->len - 1] == '\n') {
 +              strbuf_setlen(sb, sb->len - 1);
 +              if (sb->len && sb->buf[sb->len - 1] == '\r')
 +                      strbuf_setlen(sb, sb->len - 1);
 +      }
        return 0;
  }
  
 +int strbuf_getline_lf(struct strbuf *sb, FILE *fp)
 +{
 +      return strbuf_getdelim(sb, fp, '\n');
 +}
 +
 +int strbuf_getline_nul(struct strbuf *sb, FILE *fp)
 +{
 +      return strbuf_getdelim(sb, fp, '\0');
 +}
 +
  int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term)
  {
        strbuf_reset(sb);
@@@ -730,7 -692,7 +737,7 @@@ char *xstrdup_tolower(const char *strin
        size_t len, i;
  
        len = strlen(string);
 -      result = xmalloc(len + 1);
 +      result = xmallocz(len);
        for (i = 0; i < len; i++)
                result[i] = tolower(string[i]);
        result[i] = '\0';
@@@ -789,15 -751,6 +796,15 @@@ void strbuf_addftime(struct strbuf *sb
        strbuf_setlen(sb, sb->len + len);
  }
  
 +void strbuf_add_unique_abbrev(struct strbuf *sb, const unsigned char *sha1,
 +                            int abbrev_len)
 +{
 +      int r;
 +      strbuf_grow(sb, GIT_SHA1_HEXSZ + 1);
 +      r = find_unique_abbrev_r(sb->buf + sb->len, sha1, abbrev_len);
 +      strbuf_setlen(sb, sb->len + r);
 +}
 +
  /*
   * Returns the length of a line, without trailing spaces.
   *
diff --combined strbuf.h
index 83c5c98530700de35bf3e3c3ef6692c4135fec4e,45d25d7c4b675038b93c4fe19af72c2e3f983e1e..ba8d5f1d465e5b06274028aeb096e325e73e5de4
+++ b/strbuf.h
@@@ -263,11 -263,7 +263,7 @@@ static inline void strbuf_addstr(struc
  /**
   * Copy the contents of another buffer at the end of the current one.
   */
- static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2)
- {
-       strbuf_grow(sb, sb2->len);
-       strbuf_add(sb, sb2->buf, sb2->len);
- }
+ extern void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2);
  
  /**
   * Copy part of the buffer from a given position till a given length to the
@@@ -354,8 -350,8 +350,8 @@@ extern void strbuf_addftime(struct strb
   *
   * NOTE: The buffer is rewound if the read fails. If -1 is returned,
   * `errno` must be consulted, like you would do for `read(3)`.
 - * `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline()` has the
 - * same behaviour as well.
 + * `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline_*()`
 + * family of functions have the same behaviour as well.
   */
  extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
  
   */
  extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
  
 +/**
 + * Read the contents of a given file descriptor partially by using only one
 + * attempt of xread. The third argument can be used to give a hint about the
 + * file size, to avoid reallocs. Returns the number of new bytes appended to
 + * the sb.
 + */
 +extern ssize_t strbuf_read_once(struct strbuf *, int fd, size_t hint);
 +
  /**
   * Read the contents of a file, specified by its path. The third argument
   * can be used to give a hint about the file size, to avoid reallocs.
 + * Return the number of bytes read or a negative value if some error
 + * occurred while opening or reading the file.
   */
  extern ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
  
  extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
  
  /**
 - * Read a line from a FILE *, overwriting the existing contents
 - * of the strbuf. The second argument specifies the line
 - * terminator character, typically `'\n'`.
 + * Write the whole content of the strbuf to the stream not stopping at
 + * NUL bytes.
 + */
 +extern ssize_t strbuf_write(struct strbuf *sb, FILE *stream);
 +
 +/**
 + * Read a line from a FILE *, overwriting the existing contents of
 + * the strbuf.  The strbuf_getline*() family of functions share
 + * this signature, but have different line termination conventions.
 + *
   * Reading stops after the terminator or at EOF.  The terminator
   * is removed from the buffer before returning.  Returns 0 unless
   * there was nothing left before EOF, in which case it returns `EOF`.
   */
 -extern int strbuf_getline(struct strbuf *, FILE *, int);
 +typedef int (*strbuf_getline_fn)(struct strbuf *, FILE *);
 +
 +/* Uses LF as the line terminator */
 +extern int strbuf_getline_lf(struct strbuf *sb, FILE *fp);
 +
 +/* Uses NUL as the line terminator */
 +extern int strbuf_getline_nul(struct strbuf *sb, FILE *fp);
 +
 +/*
 + * Similar to strbuf_getline_lf(), but additionally treats a CR that
 + * comes immediately before the LF as part of the terminator.
 + * This is the most friendly version to be used to read "text" files
 + * that can come from platforms whose native text format is CRLF
 + * terminated.
 + */
 +extern int strbuf_getline(struct strbuf *, FILE *);
 +
  
  /**
   * Like `strbuf_getline`, but keeps the trailing terminator (if
@@@ -516,14 -479,6 +512,14 @@@ static inline struct strbuf **strbuf_sp
   */
  extern void strbuf_list_free(struct strbuf **);
  
 +/**
 + * Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to
 + * the strbuf `sb`.
 + */
 +extern void strbuf_add_unique_abbrev(struct strbuf *sb,
 +                                   const unsigned char *sha1,
 +                                   int abbrev_len);
 +
  /**
   * Launch the user preferred editor to edit a file and fill the buffer
   * with the file's contents upon the user completing their editing. The
@@@ -541,21 -496,10 +537,21 @@@ extern void strbuf_add_lines(struct str
   */
  extern void strbuf_addstr_xml_quoted(struct strbuf *sb, const char *s);
  
 +/**
 + * "Complete" the contents of `sb` by ensuring that either it ends with the
 + * character `term`, or it is empty.  This can be used, for example,
 + * to ensure that text ends with a newline, but without creating an empty
 + * blank line if there is no content in the first place.
 + */
 +static inline void strbuf_complete(struct strbuf *sb, char term)
 +{
 +      if (sb->len && sb->buf[sb->len - 1] != term)
 +              strbuf_addch(sb, term);
 +}
 +
  static inline void strbuf_complete_line(struct strbuf *sb)
  {
 -      if (sb->len && sb->buf[sb->len - 1] != '\n')
 -              strbuf_addch(sb, '\n');
 +      strbuf_complete(sb, '\n');
  }
  
  extern int strbuf_branchname(struct strbuf *sb, const char *name);
diff --combined wt-status.c
index 4ce4e35ac3f3542c42c9f71e7bb3f0c5e2c67492,51c0d7fea745c05471527a0f28011c4db32dd822..617a28430449b8df19815c31f860338ec98f8c31
@@@ -15,7 -15,6 +15,7 @@@
  #include "column.h"
  #include "strbuf.h"
  #include "utf8.h"
 +#include "worktree.h"
  
  static const char cut_line[] =
  "------------------------ >8 ------------------------\n";
@@@ -951,7 -950,6 +951,7 @@@ static void show_merge_in_progress(stru
                        status_printf_ln(s, color,
                                _("  (fix conflicts and run \"git commit\")"));
        } else {
 +              s-> commitable = 1;
                status_printf_ln(s, color,
                        _("All conflicts fixed but you are still merging."));
                if (s->hints)
@@@ -990,7 -988,7 +990,7 @@@ static char *read_line_from_git_path(co
                strbuf_release(&buf);
                return NULL;
        }
 -      strbuf_getline(&buf, fp, '\n');
 +      strbuf_getline_lf(&buf, fp);
        if (!fclose(fp)) {
                return strbuf_detach(&buf, NULL);
        } else {
@@@ -1062,10 -1060,12 +1062,10 @@@ static void abbrev_sha1_in_line(struct 
                        strbuf_addf(split[1], "%s ", abbrev);
                        strbuf_reset(line);
                        for (i = 0; split[i]; i++)
-                               strbuf_addf(line, "%s", split[i]->buf);
+                               strbuf_addbuf(line, split[i]);
                }
        }
 -      for (i = 0; split[i]; i++)
 -              strbuf_release(split[i]);
 -
 +      strbuf_list_free(split);
  }
  
  static void read_rebase_todolist(const char *fname, struct string_list *lines)
        if (!f)
                die_errno("Could not open file %s for reading",
                          git_path("%s", fname));
 -      while (!strbuf_getline(&line, f, '\n')) {
 +      while (!strbuf_getline_lf(&line, f)) {
                if (line.len && line.buf[0] == comment_line_char)
                        continue;
                strbuf_trim(&line);
@@@ -1264,13 -1264,13 +1264,13 @@@ static void show_bisect_in_progress(str
  /*
   * Extract branch information from rebase/bisect
   */
 -static char *read_and_strip_branch(const char *path)
 +static char *get_branch(const struct worktree *wt, const char *path)
  {
        struct strbuf sb = STRBUF_INIT;
        unsigned char sha1[20];
        const char *branch_name;
  
 -      if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
 +      if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0)
                goto got_nothing;
  
        while (sb.len && sb.buf[sb.len - 1] == '\n')
@@@ -1346,7 -1346,7 +1346,7 @@@ static void wt_status_get_detached_from
            (!hashcmp(cb.nsha1, sha1) ||
             /* perhaps sha1 is a tag, try to dereference to a commit */
             ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
 -            !hashcmp(cb.nsha1, commit->object.sha1)))) {
 +            !hashcmp(cb.nsha1, commit->object.oid.hash)))) {
                const char *from = ref;
                if (!skip_prefix(from, "refs/tags/", &from))
                        skip_prefix(from, "refs/remotes/", &from);
        strbuf_release(&cb.buf);
  }
  
 -void wt_status_get_state(struct wt_status_state *state,
 -                       int get_detached_from)
 +int wt_status_check_rebase(const struct worktree *wt,
 +                         struct wt_status_state *state)
  {
        struct stat st;
 -      unsigned char sha1[20];
  
 -      if (!stat(git_path_merge_head(), &st)) {
 -              state->merge_in_progress = 1;
 -      } else if (!stat(git_path("rebase-apply"), &st)) {
 -              if (!stat(git_path("rebase-apply/applying"), &st)) {
 +      if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) {
 +              if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) {
                        state->am_in_progress = 1;
 -                      if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
 +                      if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size)
                                state->am_empty_patch = 1;
                } else {
                        state->rebase_in_progress = 1;
 -                      state->branch = read_and_strip_branch("rebase-apply/head-name");
 -                      state->onto = read_and_strip_branch("rebase-apply/onto");
 +                      state->branch = get_branch(wt, "rebase-apply/head-name");
 +                      state->onto = get_branch(wt, "rebase-apply/onto");
                }
 -      } else if (!stat(git_path("rebase-merge"), &st)) {
 -              if (!stat(git_path("rebase-merge/interactive"), &st))
 +      } else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) {
 +              if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st))
                        state->rebase_interactive_in_progress = 1;
                else
                        state->rebase_in_progress = 1;
 -              state->branch = read_and_strip_branch("rebase-merge/head-name");
 -              state->onto = read_and_strip_branch("rebase-merge/onto");
 +              state->branch = get_branch(wt, "rebase-merge/head-name");
 +              state->onto = get_branch(wt, "rebase-merge/onto");
 +      } else
 +              return 0;
 +      return 1;
 +}
 +
 +int wt_status_check_bisect(const struct worktree *wt,
 +                         struct wt_status_state *state)
 +{
 +      struct stat st;
 +
 +      if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) {
 +              state->bisect_in_progress = 1;
 +              state->branch = get_branch(wt, "BISECT_START");
 +              return 1;
 +      }
 +      return 0;
 +}
 +
 +void wt_status_get_state(struct wt_status_state *state,
 +                       int get_detached_from)
 +{
 +      struct stat st;
 +      unsigned char sha1[20];
 +
 +      if (!stat(git_path_merge_head(), &st)) {
 +              state->merge_in_progress = 1;
 +      } else if (wt_status_check_rebase(NULL, state)) {
 +              ;               /* all set */
        } else if (!stat(git_path_cherry_pick_head(), &st) &&
                        !get_sha1("CHERRY_PICK_HEAD", sha1)) {
                state->cherry_pick_in_progress = 1;
                hashcpy(state->cherry_pick_head_sha1, sha1);
        }
 -      if (!stat(git_path("BISECT_LOG"), &st)) {
 -              state->bisect_in_progress = 1;
 -              state->branch = read_and_strip_branch("BISECT_START");
 -      }
 +      wt_status_check_bisect(NULL, state);
        if (!stat(git_path_revert_head(), &st) &&
            !get_sha1("REVERT_HEAD", sha1)) {
                state->revert_in_progress = 1;
@@@ -1554,7 -1532,7 +1554,7 @@@ void wt_status_print(struct wt_status *
                        else
                                printf(_("nothing to commit\n"));
                } else
 -                      printf(_("nothing to commit, working directory clean\n"));
 +                      printf(_("nothing to commit, working tree clean\n"));
        }
  }
  
@@@ -1699,10 -1677,10 +1699,10 @@@ static void wt_shortstatus_print_tracki
                color_fprintf(s->fp, header_color, LABEL(N_("behind ")));
                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
        } else if (!num_theirs) {
 -              color_fprintf(s->fp, header_color, LABEL(N_(("ahead "))));
 +              color_fprintf(s->fp, header_color, LABEL(N_("ahead ")));
                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
        } else {
 -              color_fprintf(s->fp, header_color, LABEL(N_(("ahead "))));
 +              color_fprintf(s->fp, header_color, LABEL(N_("ahead ")));
                color_fprintf(s->fp, branch_color_local, "%d", num_ours);
                color_fprintf(s->fp, header_color, ", %s", LABEL(N_("behind ")));
                color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);