Merge branch 'jk/quote-env-path-list-component'
authorJunio C Hamano <gitster@pobox.com>
Wed, 21 Dec 2016 22:55:02 +0000 (14:55 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 21 Dec 2016 22:55:02 +0000 (14:55 -0800)
A recent update to receive-pack to make it easier to drop garbage
objects made it clear that GIT_ALTERNATE_OBJECT_DIRECTORIES cannot
have a pathname with a colon in it (no surprise!), and this in turn
made it impossible to push into a repository at such a path. This
has been fixed by introducing a quoting mechanism used when
appending such a path to the colon-separated list.

* jk/quote-env-path-list-component:
t5615-alternate-env: double-quotes in file names do not work on Windows
t5547-push-quarantine: run the path separator test on Windows, too
tmp-objdir: quote paths we add to alternates
alternates: accept double-quoted paths

1  2 
Documentation/git.txt
sha1_file.c
t/t5615-alternate-env.sh
diff --combined Documentation/git.txt
index af191c51b1d63d995cfba0784de36d98a23de838,5f7826bf39557b5a8780e8b93e2e50c664d84c86..98033302bb97ef802f7050e02a1727a878bb79aa
@@@ -13,7 -13,6 +13,7 @@@ SYNOPSI
      [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
      [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
      [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
 +    [--super-prefix=<path>]
      <command> [<args>]
  
  DESCRIPTION
@@@ -44,15 -43,9 +44,15 @@@ unreleased) version of Git, that is ava
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
 -* link:v2.10.1/git.html[documentation for release 2.10.1]
 +* link:v2.11.0/git.html[documentation for release 2.11]
  
  * release notes for
 +  link:RelNotes/2.11.0.txt[2.11].
 +
 +* link:v2.10.2/git.html[documentation for release 2.10.2]
 +
 +* release notes for
 +  link:RelNotes/2.10.2.txt[2.10.2],
    link:RelNotes/2.10.1.txt[2.10.1],
    link:RelNotes/2.10.0.txt[2.10].
  
@@@ -609,11 -602,6 +609,11 @@@ foo.bar= ...`) sets `foo.bar` to the em
        details.  Equivalent to setting the `GIT_NAMESPACE` environment
        variable.
  
 +--super-prefix=<path>::
 +      Currently for internal use only.  Set a prefix which gives a path from
 +      above a repository down to its root.  One use is to give submodules
 +      context about the superproject that invoked it.
 +
  --bare::
        Treat the repository as a bare repository.  If GIT_DIR
        environment is not set, it is set to the current working
@@@ -871,6 -859,12 +871,12 @@@ Git so take care if using a foreign fro
        specifies a ":" separated (on Windows ";" separated) list
        of Git object directories which can be used to search for Git
        objects. New objects will not be written to these directories.
+ +
+       Entries that begin with `"` (double-quote) will be interpreted
+       as C-style quoted paths, removing leading and trailing
+       double-quotes and respecting backslash escapes. E.g., the value
+       `"path-with-\"-and-:-in-it":vanilla-path` has two paths:
+       `path-with-"-and-:-in-it` and `vanilla-path`.
  
  `GIT_DIR`::
        If the `GIT_DIR` environment variable is set then it
diff --combined sha1_file.c
index 9c86d1924a23e4c81369f34ec8e1058c2dd15c1f,fc6d864fda8c7c9c2b6bbae53b3d97a9a88d7f27..1173071859dae68f72cc72efb20f816152d3eabc
@@@ -26,6 -26,7 +26,7 @@@
  #include "mru.h"
  #include "list.h"
  #include "mergesort.h"
+ #include "quote.h"
  
  #ifndef O_NOATIME
  #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@@ -329,13 -330,40 +330,40 @@@ static int link_alt_odb_entry(const cha
        return 0;
  }
  
+ static const char *parse_alt_odb_entry(const char *string,
+                                      int sep,
+                                      struct strbuf *out)
+ {
+       const char *end;
+       strbuf_reset(out);
+       if (*string == '#') {
+               /* comment; consume up to next separator */
+               end = strchrnul(string, sep);
+       } else if (*string == '"' && !unquote_c_style(out, string, &end)) {
+               /*
+                * quoted path; unquote_c_style has copied the
+                * data for us and set "end". Broken quoting (e.g.,
+                * an entry that doesn't end with a quote) falls
+                * back to the unquoted case below.
+                */
+       } else {
+               /* normal, unquoted path */
+               end = strchrnul(string, sep);
+               strbuf_add(out, string, end - string);
+       }
+       if (*end)
+               end++;
+       return end;
+ }
  static void link_alt_odb_entries(const char *alt, int len, int sep,
                                 const char *relative_base, int depth)
  {
-       struct string_list entries = STRING_LIST_INIT_NODUP;
-       char *alt_copy;
-       int i;
        struct strbuf objdirbuf = STRBUF_INIT;
+       struct strbuf entry = STRBUF_INIT;
  
        if (depth > 5) {
                error("%s: ignoring alternate object stores, nesting too deep.",
                die("unable to normalize object directory: %s",
                    objdirbuf.buf);
  
-       alt_copy = xmemdupz(alt, len);
-       string_list_split_in_place(&entries, alt_copy, sep, -1);
-       for (i = 0; i < entries.nr; i++) {
-               const char *entry = entries.items[i].string;
-               if (entry[0] == '\0' || entry[0] == '#')
+       while (*alt) {
+               alt = parse_alt_odb_entry(alt, sep, &entry);
+               if (!entry.len)
                        continue;
-               link_alt_odb_entry(entry, relative_base, depth, objdirbuf.buf);
+               link_alt_odb_entry(entry.buf, relative_base, depth, objdirbuf.buf);
        }
-       string_list_clear(&entries, 0);
-       free(alt_copy);
+       strbuf_release(&entry);
        strbuf_release(&objdirbuf);
  }
  
@@@ -370,7 -395,7 +395,7 @@@ void read_info_alternates(const char * 
        int fd;
  
        path = xstrfmt("%s/info/alternates", relative_base);
 -      fd = git_open_noatime(path);
 +      fd = git_open(path);
        free(path);
        if (fd < 0)
                return;
@@@ -663,7 -688,7 +688,7 @@@ static int check_packed_git_idx(const c
        struct pack_idx_header *hdr;
        size_t idx_size;
        uint32_t version, nr, i, *index;
 -      int fd = git_open_noatime(path);
 +      int fd = git_open(path);
        struct stat st;
  
        if (fd < 0)
@@@ -1069,7 -1094,7 +1094,7 @@@ static int open_packed_git_1(struct pac
        while (pack_max_fds <= pack_open_fds && close_one_pack())
                ; /* nothing */
  
 -      p->pack_fd = git_open_noatime(p->pack_name);
 +      p->pack_fd = git_open(p->pack_name);
        if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
                return -1;
        pack_open_fds++;
@@@ -1410,32 -1435,6 +1435,32 @@@ static void prepare_packed_git_one(cha
        strbuf_release(&path);
  }
  
 +static int approximate_object_count_valid;
 +
 +/*
 + * Give a fast, rough count of the number of objects in the repository. This
 + * ignores loose objects completely. If you have a lot of them, then either
 + * you should repack because your performance will be awful, or they are
 + * all unreachable objects about to be pruned, in which case they're not really
 + * interesting as a measure of repo size in the first place.
 + */
 +unsigned long approximate_object_count(void)
 +{
 +      static unsigned long count;
 +      if (!approximate_object_count_valid) {
 +              struct packed_git *p;
 +
 +              prepare_packed_git();
 +              count = 0;
 +              for (p = packed_git; p; p = p->next) {
 +                      if (open_pack_index(p))
 +                              continue;
 +                      count += p->num_objects;
 +              }
 +      }
 +      return count;
 +}
 +
  static void *get_next_packed_git(const void *p)
  {
        return ((const struct packed_git *)p)->next;
@@@ -1507,7 -1506,6 +1532,7 @@@ void prepare_packed_git(void
  
  void reprepare_packed_git(void)
  {
 +      approximate_object_count_valid = 0;
        prepare_packed_git_run_once = 0;
        prepare_packed_git();
  }
@@@ -1586,9 -1584,9 +1611,9 @@@ int check_sha1_signature(const unsigne
        return hashcmp(sha1, real_sha1) ? -1 : 0;
  }
  
 -int git_open_noatime(const char *name)
 +int git_open(const char *name)
  {
 -      static int sha1_file_open_flag = O_NOATIME;
 +      static int sha1_file_open_flag = O_NOATIME | O_CLOEXEC;
  
        for (;;) {
                int fd;
                if (fd >= 0)
                        return fd;
  
 -              /* Might the failure be due to O_NOATIME? */
 -              if (errno != ENOENT && sha1_file_open_flag) {
 -                      sha1_file_open_flag = 0;
 +              /* Try again w/o O_CLOEXEC: the kernel might not support it */
 +              if ((sha1_file_open_flag & O_CLOEXEC) && errno == EINVAL) {
 +                      sha1_file_open_flag &= ~O_CLOEXEC;
                        continue;
                }
  
 +              /* Might the failure be due to O_NOATIME? */
 +              if (errno != ENOENT && (sha1_file_open_flag & O_NOATIME)) {
 +                      sha1_file_open_flag &= ~O_NOATIME;
 +                      continue;
 +              }
                return -1;
        }
  }
@@@ -1637,7 -1630,7 +1662,7 @@@ static int open_sha1_file(const unsigne
        struct alternate_object_database *alt;
        int most_interesting_errno;
  
 -      fd = git_open_noatime(sha1_file_name(sha1));
 +      fd = git_open(sha1_file_name(sha1));
        if (fd >= 0)
                return fd;
        most_interesting_errno = errno;
        prepare_alt_odb();
        for (alt = alt_odb_list; alt; alt = alt->next) {
                const char *path = alt_sha1_path(alt, sha1);
 -              fd = git_open_noatime(path);
 +              fd = git_open(path);
                if (fd >= 0)
                        return fd;
                if (most_interesting_errno == ENOENT)
@@@ -1884,9 -1877,11 +1909,9 @@@ static int parse_sha1_header_extended(c
  
  int parse_sha1_header(const char *hdr, unsigned long *sizep)
  {
 -      struct object_info oi;
 +      struct object_info oi = OBJECT_INFO_INIT;
  
        oi.sizep = sizep;
 -      oi.typename = NULL;
 -      oi.typep = NULL;
        return parse_sha1_header_extended(hdr, &oi, LOOKUP_REPLACE_OBJECT);
  }
  
@@@ -2124,8 -2119,8 +2149,8 @@@ unwind
        goto out;
  }
  
 -static int packed_object_info(struct packed_git *p, off_t obj_offset,
 -                            struct object_info *oi)
 +int packed_object_info(struct packed_git *p, off_t obj_offset,
 +                     struct object_info *oi)
  {
        struct pack_window *w_curs = NULL;
        unsigned long size;
@@@ -2896,7 -2891,7 +2921,7 @@@ int sha1_object_info_extended(const uns
  int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
  {
        enum object_type type;
 -      struct object_info oi = {NULL};
 +      struct object_info oi = OBJECT_INFO_INIT;
  
        oi.typep = &type;
        oi.sizep = sizep;
@@@ -3367,11 -3362,6 +3392,11 @@@ int has_object_file(const struct object
        return has_sha1_file(oid->hash);
  }
  
 +int has_object_file_with_flags(const struct object_id *oid, int flags)
 +{
 +      return has_sha1_file_with_flags(oid->hash, flags);
 +}
 +
  static void check_tree(const void *buf, size_t size)
  {
        struct tree_desc desc;
diff --combined t/t5615-alternate-env.sh
index eec4137ca5b392743e29068f4fffae1ffb78d812,79628db3ec08841de2fbba2c262c5bd1bfcd3720..26ebb0375deb67be38974c7c5610f090a5a2a9aa
@@@ -31,14 -31,14 +31,14 @@@ test_expect_success 'objects inaccessib
  '
  
  test_expect_success 'access alternate via absolute path' '
 -      check_obj "$(pwd)/one.git/objects" <<-EOF
 +      check_obj "$PWD/one.git/objects" <<-EOF
        $one blob
        $two missing
        EOF
  '
  
  test_expect_success 'access multiple alternates' '
 -      check_obj "$(pwd)/one.git/objects:$(pwd)/two.git/objects" <<-EOF
 +      check_obj "$PWD/one.git/objects:$PWD/two.git/objects" <<-EOF
        $one blob
        $two blob
        EOF
@@@ -68,4 -68,22 +68,22 @@@ test_expect_success 'access alternate v
        EOF
  '
  
+ # set variables outside test to avoid quote insanity; the \057 is '/',
+ # which doesn't need quoting, but just confirms that de-quoting
+ # is working.
+ quoted='"one.git\057objects"'
+ unquoted='two.git/objects'
+ test_expect_success 'mix of quoted and unquoted alternates' '
+       check_obj "$quoted:$unquoted" <<-EOF
+       $one blob
+       $two blob
+ '
+ test_expect_success !MINGW 'broken quoting falls back to interpreting raw' '
+       mv one.git \"one.git &&
+       check_obj \"one.git/objects <<-EOF
+       $one blob
+       EOF
+ '
  test_done