Commit cf3c6352 authored by Jeff King's avatar Jeff King Committed by Junio C Hamano

alternates: accept double-quoted paths

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

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
722ff7f8 (receive-pack: quarantine objects until
pre-receive accepts, 2016-10-03), which builds on the
alternates system, every push causes the receiver to set

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: default avatarJeff King <[email protected]>
Signed-off-by: default avatarJunio C Hamano <[email protected]>
parent 9b519609
......@@ -859,6 +859,12 @@ Git so take care if using a foreign front-end.
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`.
If the `GIT_DIR` environment variable is set then it
......@@ -26,6 +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 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
return 0;
static const char *parse_alt_odb_entry(const char *string,
int sep,
struct strbuf *out)
const char *end;
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)
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.",
......@@ -348,16 +376,13 @@ static void link_alt_odb_entries(const char *alt, int len, int sep,
die("unable to normalize object directory: %s",
alt_copy = xmemdupz(alt, len);
string_list_split_in_place(&entries, alt_copy, sep, -1);
for (i = 0; i <; 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)
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);
......@@ -68,4 +68,22 @@ test_expect_success 'access alternate via relative path (subdir)' '
# set variables outside test to avoid quote insanity; the \057 is '/',
# which doesn't need quoting, but just confirms that de-quoting
# is working.
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
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment