fast-import: tighten parsing of datarefs
The syntax for the use of mark references in fast-import demands either a SP (space) or LF (end-of-line) after a mark reference. Fast-import does not complain when garbage appears after a mark reference in some cases. Factor out parsing of mark references and complain if errant characters are found. Also be a little more careful when parsing "inline" and SHA1s, complaining if extra characters appear or if the form of the dataref is unrecognized. Buggy input can cause fast-import to produce the wrong output, silently, without error. This makes it difficult to track down buggy generators of fast-import streams. An example is seen in the last line of this commit command: commit refs/heads/S2 committer Name <name@example.com> 1112912893 -0400 data <<COMMIT commit message COMMIT from :1M 100644 :103 hello.c It is missing a newline and should be: [...] from :1 M 100644 :103 hello.c What fast-import does is to produce a commit with the same contents for hello.c as in refs/heads/S2^. What the buggy program was expecting was the contents of blob :103. While the resulting commit graph looked correct, the contents in some commits were wrong. Signed-off-by: Pete Wyckoff <pw@padd.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:

committed by
Junio C Hamano

parent
cb2ed324fc
commit
06454cb9a3
110
fast-import.c
110
fast-import.c
@ -2207,6 +2207,59 @@ static uintmax_t change_note_fanout(struct tree_entry *root,
|
||||
return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a pointer into a string, parse a mark reference:
|
||||
*
|
||||
* idnum ::= ':' bigint;
|
||||
*
|
||||
* Return the first character after the value in *endptr.
|
||||
*
|
||||
* Complain if the following character is not what is expected,
|
||||
* either a space or end of the string.
|
||||
*/
|
||||
static uintmax_t parse_mark_ref(const char *p, char **endptr)
|
||||
{
|
||||
uintmax_t mark;
|
||||
|
||||
assert(*p == ':');
|
||||
p++;
|
||||
mark = strtoumax(p, endptr, 10);
|
||||
if (*endptr == p)
|
||||
die("No value after ':' in mark: %s", command_buf.buf);
|
||||
return mark;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the mark reference, and complain if this is not the end of
|
||||
* the string.
|
||||
*/
|
||||
static uintmax_t parse_mark_ref_eol(const char *p)
|
||||
{
|
||||
char *end;
|
||||
uintmax_t mark;
|
||||
|
||||
mark = parse_mark_ref(p, &end);
|
||||
if (*end != '\0')
|
||||
die("Garbage after mark: %s", command_buf.buf);
|
||||
return mark;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the mark reference, demanding a trailing space. Return a
|
||||
* pointer to the space.
|
||||
*/
|
||||
static uintmax_t parse_mark_ref_space(const char **p)
|
||||
{
|
||||
uintmax_t mark;
|
||||
char *end;
|
||||
|
||||
mark = parse_mark_ref(*p, &end);
|
||||
if (*end != ' ')
|
||||
die("Missing space after mark: %s", command_buf.buf);
|
||||
*p = end;
|
||||
return mark;
|
||||
}
|
||||
|
||||
static void file_change_m(struct branch *b)
|
||||
{
|
||||
const char *p = command_buf.buf + 2;
|
||||
@ -2235,21 +2288,21 @@ static void file_change_m(struct branch *b)
|
||||
}
|
||||
|
||||
if (*p == ':') {
|
||||
char *x;
|
||||
oe = find_mark(strtoumax(p + 1, &x, 10));
|
||||
oe = find_mark(parse_mark_ref_space(&p));
|
||||
hashcpy(sha1, oe->idx.sha1);
|
||||
p = x;
|
||||
} else if (!prefixcmp(p, "inline")) {
|
||||
} else if (!prefixcmp(p, "inline ")) {
|
||||
inline_data = 1;
|
||||
p += 6;
|
||||
p += strlen("inline"); /* advance to space */
|
||||
} else {
|
||||
if (get_sha1_hex(p, sha1))
|
||||
die("Invalid SHA1: %s", command_buf.buf);
|
||||
die("Invalid dataref: %s", command_buf.buf);
|
||||
oe = find_object(sha1);
|
||||
p += 40;
|
||||
if (*p != ' ')
|
||||
die("Missing space after SHA1: %s", command_buf.buf);
|
||||
}
|
||||
if (*p++ != ' ')
|
||||
die("Missing space after SHA1: %s", command_buf.buf);
|
||||
assert(*p == ' ');
|
||||
p++; /* skip space */
|
||||
|
||||
strbuf_reset(&uq);
|
||||
if (!unquote_c_style(&uq, p, &endp)) {
|
||||
@ -2407,21 +2460,21 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout)
|
||||
/* Now parse the notemodify command. */
|
||||
/* <dataref> or 'inline' */
|
||||
if (*p == ':') {
|
||||
char *x;
|
||||
oe = find_mark(strtoumax(p + 1, &x, 10));
|
||||
oe = find_mark(parse_mark_ref_space(&p));
|
||||
hashcpy(sha1, oe->idx.sha1);
|
||||
p = x;
|
||||
} else if (!prefixcmp(p, "inline")) {
|
||||
} else if (!prefixcmp(p, "inline ")) {
|
||||
inline_data = 1;
|
||||
p += 6;
|
||||
p += strlen("inline"); /* advance to space */
|
||||
} else {
|
||||
if (get_sha1_hex(p, sha1))
|
||||
die("Invalid SHA1: %s", command_buf.buf);
|
||||
die("Invalid dataref: %s", command_buf.buf);
|
||||
oe = find_object(sha1);
|
||||
p += 40;
|
||||
if (*p != ' ')
|
||||
die("Missing space after SHA1: %s", command_buf.buf);
|
||||
}
|
||||
if (*p++ != ' ')
|
||||
die("Missing space after SHA1: %s", command_buf.buf);
|
||||
assert(*p == ' ');
|
||||
p++; /* skip space */
|
||||
|
||||
/* <committish> */
|
||||
s = lookup_branch(p);
|
||||
@ -2430,7 +2483,7 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout)
|
||||
die("Can't add a note on empty branch.");
|
||||
hashcpy(commit_sha1, s->sha1);
|
||||
} else if (*p == ':') {
|
||||
uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
|
||||
uintmax_t commit_mark = parse_mark_ref_eol(p);
|
||||
struct object_entry *commit_oe = find_mark(commit_mark);
|
||||
if (commit_oe->type != OBJ_COMMIT)
|
||||
die("Mark :%" PRIuMAX " not a commit", commit_mark);
|
||||
@ -2537,7 +2590,7 @@ static int parse_from(struct branch *b)
|
||||
hashcpy(b->branch_tree.versions[0].sha1, t);
|
||||
hashcpy(b->branch_tree.versions[1].sha1, t);
|
||||
} else if (*from == ':') {
|
||||
uintmax_t idnum = strtoumax(from + 1, NULL, 10);
|
||||
uintmax_t idnum = parse_mark_ref_eol(from);
|
||||
struct object_entry *oe = find_mark(idnum);
|
||||
if (oe->type != OBJ_COMMIT)
|
||||
die("Mark :%" PRIuMAX " not a commit", idnum);
|
||||
@ -2572,7 +2625,7 @@ static struct hash_list *parse_merge(unsigned int *count)
|
||||
if (s)
|
||||
hashcpy(n->sha1, s->sha1);
|
||||
else if (*from == ':') {
|
||||
uintmax_t idnum = strtoumax(from + 1, NULL, 10);
|
||||
uintmax_t idnum = parse_mark_ref_eol(from);
|
||||
struct object_entry *oe = find_mark(idnum);
|
||||
if (oe->type != OBJ_COMMIT)
|
||||
die("Mark :%" PRIuMAX " not a commit", idnum);
|
||||
@ -2735,7 +2788,7 @@ static void parse_new_tag(void)
|
||||
type = OBJ_COMMIT;
|
||||
} else if (*from == ':') {
|
||||
struct object_entry *oe;
|
||||
from_mark = strtoumax(from + 1, NULL, 10);
|
||||
from_mark = parse_mark_ref_eol(from);
|
||||
oe = find_mark(from_mark);
|
||||
type = oe->type;
|
||||
hashcpy(sha1, oe->idx.sha1);
|
||||
@ -2867,18 +2920,13 @@ static void parse_cat_blob(void)
|
||||
/* cat-blob SP <object> LF */
|
||||
p = command_buf.buf + strlen("cat-blob ");
|
||||
if (*p == ':') {
|
||||
char *x;
|
||||
oe = find_mark(strtoumax(p + 1, &x, 10));
|
||||
if (x == p + 1)
|
||||
die("Invalid mark: %s", command_buf.buf);
|
||||
oe = find_mark(parse_mark_ref_eol(p));
|
||||
if (!oe)
|
||||
die("Unknown mark: %s", command_buf.buf);
|
||||
if (*x)
|
||||
die("Garbage after mark: %s", command_buf.buf);
|
||||
hashcpy(sha1, oe->idx.sha1);
|
||||
} else {
|
||||
if (get_sha1_hex(p, sha1))
|
||||
die("Invalid SHA1: %s", command_buf.buf);
|
||||
die("Invalid dataref: %s", command_buf.buf);
|
||||
if (p[40])
|
||||
die("Garbage after SHA1: %s", command_buf.buf);
|
||||
oe = find_object(sha1);
|
||||
@ -2944,17 +2992,13 @@ static struct object_entry *parse_treeish_dataref(const char **p)
|
||||
struct object_entry *e;
|
||||
|
||||
if (**p == ':') { /* <mark> */
|
||||
char *endptr;
|
||||
e = find_mark(strtoumax(*p + 1, &endptr, 10));
|
||||
if (endptr == *p + 1)
|
||||
die("Invalid mark: %s", command_buf.buf);
|
||||
e = find_mark(parse_mark_ref_space(p));
|
||||
if (!e)
|
||||
die("Unknown mark: %s", command_buf.buf);
|
||||
*p = endptr;
|
||||
hashcpy(sha1, e->idx.sha1);
|
||||
} else { /* <sha1> */
|
||||
if (get_sha1_hex(*p, sha1))
|
||||
die("Invalid SHA1: %s", command_buf.buf);
|
||||
die("Invalid dataref: %s", command_buf.buf);
|
||||
e = find_object(sha1);
|
||||
*p += 40;
|
||||
}
|
||||
|
Reference in New Issue
Block a user