alternates: accept double-quoted paths

We read lists of alternates from objects/info/alternates
files (delimited by newline), as well as from the
GIT_ALTERNATE_OBJECT_DIRECTORIES environment variable
(delimited by colon or semi-colon, depending on the
platform).

There's no mechanism for quoting the delimiters, so it's
impossible to specify an alternate path that contains a
colon in the environment, or one that contains a newline in
a file. We've lived with that restriction for ages because
both alternates and filenames with colons are relatively
rare, and it's only a problem when the two meet. But since
722ff7f87 (receive-pack: quarantine objects until
pre-receive accepts, 2016-10-03), which builds on the
alternates system, every push causes the receiver to set
GIT_ALTERNATE_OBJECT_DIRECTORIES internally.

It would be convenient to have some way to quote the
delimiter so that we can represent arbitrary paths.

The simplest thing would be an escape character before a
quoted delimiter (e.g., "\:" as a literal colon). But that
creates a backwards compatibility problem: any path which
uses that escape character is now broken, and we've just
shifted the problem. We could choose an unlikely escape
character (e.g., something from the non-printable ASCII
range), but that's awkward to use.

Instead, let's treat names as unquoted unless they begin
with a double-quote, in which case they are interpreted via
our usual C-stylke quoting rules. This also breaks
backwards-compatibility, but in a smaller way: it only
matters if your file has a double-quote as the very _first_
character in the path (whereas an escape character is a
problem anywhere in the path).  It's also consistent with
many other parts of git, which accept either a bare pathname
or a double-quoted one, and the sender can choose to quote
or not as required.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jeff King 2016-12-12 14:52:22 -05:00 committed by Junio C Hamano
parent 9b519609a6
commit cf3c635210
3 changed files with 60 additions and 11 deletions

View file

@ -859,6 +859,12 @@ Git so take care if using a foreign front-end.
specifies a ":" separated (on Windows ";" separated) list specifies a ":" separated (on Windows ";" separated) list
of Git object directories which can be used to search for Git of Git object directories which can be used to search for Git
objects. New objects will not be written to these directories. 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`:: `GIT_DIR`::
If the `GIT_DIR` environment variable is set then it If the `GIT_DIR` environment variable is set then it

View file

@ -26,6 +26,7 @@
#include "mru.h" #include "mru.h"
#include "list.h" #include "list.h"
#include "mergesort.h" #include "mergesort.h"
#include "quote.h"
#ifndef O_NOATIME #ifndef O_NOATIME
#if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@ -329,13 +330,40 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
return 0; 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, static void link_alt_odb_entries(const char *alt, int len, int sep,
const char *relative_base, int depth) 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 objdirbuf = STRBUF_INIT;
struct strbuf entry = STRBUF_INIT;
if (depth > 5) { if (depth > 5) {
error("%s: ignoring alternate object stores, nesting too deep.", error("%s: ignoring alternate object stores, nesting too deep.",
@ -348,16 +376,13 @@ static void link_alt_odb_entries(const char *alt, int len, int sep,
die("unable to normalize object directory: %s", die("unable to normalize object directory: %s",
objdirbuf.buf); objdirbuf.buf);
alt_copy = xmemdupz(alt, len); while (*alt) {
string_list_split_in_place(&entries, alt_copy, sep, -1); alt = parse_alt_odb_entry(alt, sep, &entry);
for (i = 0; i < entries.nr; i++) { if (!entry.len)
const char *entry = entries.items[i].string;
if (entry[0] == '\0' || entry[0] == '#')
continue; 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); strbuf_release(&entry);
free(alt_copy);
strbuf_release(&objdirbuf); strbuf_release(&objdirbuf);
} }

View file

@ -68,4 +68,22 @@ test_expect_success 'access alternate via relative path (subdir)' '
EOF 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 'broken quoting falls back to interpreting raw' '
mv one.git \"one.git &&
check_obj \"one.git/objects <<-EOF
$one blob
EOF
'
test_done test_done