Merge branch 'js/fsck-tag-validation'

Teach "git fsck" to inspect the contents of annotated tag objects.

* js/fsck-tag-validation:
  Make sure that index-pack --strict checks tag objects
  Add regression tests for stricter tag fsck'ing
  fsck: check tag objects' headers
  Make sure fsck_commit_buffer() does not run out of the buffer
  fsck_object(): allow passing object data separately from the object itself
  Refactor type_from_string() to allow continuing after detecting an error
This commit is contained in:
Junio C Hamano 2014-09-26 14:39:43 -07:00
commit 13f4f04692
9 changed files with 188 additions and 20 deletions

View file

@ -298,7 +298,7 @@ static int fsck_obj(struct object *obj)
if (fsck_walk(obj, mark_used, NULL))
objerror(obj, "broken links");
if (fsck_object(obj, check_strict, fsck_error_func))
if (fsck_object(obj, NULL, 0, check_strict, fsck_error_func))
return -1;
if (obj->type == OBJ_TREE) {

View file

@ -779,7 +779,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
if (!obj)
die(_("invalid %s"), typename(type));
if (do_fsck_object &&
fsck_object(obj, 1, fsck_error_function))
fsck_object(obj, buf, size, 1,
fsck_error_function))
die(_("Error in object"));
if (fsck_walk(obj, mark_link, NULL))
die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));

View file

@ -164,10 +164,10 @@ static unsigned nr_objects;
* Called only from check_object() after it verified this object
* is Ok.
*/
static void write_cached_object(struct object *obj)
static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf)
{
unsigned char sha1[20];
struct obj_buffer *obj_buf = lookup_object_buffer(obj);
if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0)
die("failed to write object %s", sha1_to_hex(obj->sha1));
obj->flags |= FLAG_WRITTEN;
@ -180,6 +180,8 @@ static void write_cached_object(struct object *obj)
*/
static int check_object(struct object *obj, int type, void *data)
{
struct obj_buffer *obj_buf;
if (!obj)
return 1;
@ -198,11 +200,15 @@ static int check_object(struct object *obj, int type, void *data)
return 0;
}
if (fsck_object(obj, 1, fsck_error_function))
obj_buf = lookup_object_buffer(obj);
if (!obj_buf)
die("Whoops! Cannot find object '%s'", sha1_to_hex(obj->sha1));
if (fsck_object(obj, obj_buf->buffer, obj_buf->size, 1,
fsck_error_function))
die("Error in object");
if (fsck_walk(obj, check_object, NULL))
die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
write_cached_object(obj);
write_cached_object(obj, obj_buf);
return 0;
}

133
fsck.c
View file

@ -6,6 +6,7 @@
#include "commit.h"
#include "tag.h"
#include "fsck.h"
#include "refs.h"
static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
{
@ -237,6 +238,26 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
return retval;
}
static int require_end_of_header(const void *data, unsigned long size,
struct object *obj, fsck_error error_func)
{
const char *buffer = (const char *)data;
unsigned long i;
for (i = 0; i < size; i++) {
switch (buffer[i]) {
case '\0':
return error_func(obj, FSCK_ERROR,
"unterminated header: NUL at offset %d", i);
case '\n':
if (i + 1 < size && buffer[i + 1] == '\n')
return 0;
}
}
return error_func(obj, FSCK_ERROR, "unterminated header");
}
static int fsck_ident(const char **ident, struct object *obj, fsck_error error_func)
{
char *end;
@ -277,13 +298,16 @@ static int fsck_ident(const char **ident, struct object *obj, fsck_error error_f
}
static int fsck_commit_buffer(struct commit *commit, const char *buffer,
fsck_error error_func)
unsigned long size, fsck_error error_func)
{
unsigned char tree_sha1[20], sha1[20];
struct commit_graft *graft;
unsigned parent_count, parent_line_count = 0;
int err;
if (require_end_of_header(buffer, size, &commit->object, error_func))
return -1;
if (!skip_prefix(buffer, "tree ", &buffer))
return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line");
if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n')
@ -322,24 +346,111 @@ static int fsck_commit_buffer(struct commit *commit, const char *buffer,
return 0;
}
static int fsck_commit(struct commit *commit, fsck_error error_func)
static int fsck_commit(struct commit *commit, const char *data,
unsigned long size, fsck_error error_func)
{
const char *buffer = get_commit_buffer(commit, NULL);
int ret = fsck_commit_buffer(commit, buffer, error_func);
unuse_commit_buffer(commit, buffer);
const char *buffer = data ? data : get_commit_buffer(commit, &size);
int ret = fsck_commit_buffer(commit, buffer, size, error_func);
if (!data)
unuse_commit_buffer(commit, buffer);
return ret;
}
static int fsck_tag(struct tag *tag, fsck_error error_func)
static int fsck_tag_buffer(struct tag *tag, const char *data,
unsigned long size, fsck_error error_func)
{
unsigned char sha1[20];
int ret = 0;
const char *buffer;
char *to_free = NULL, *eol;
struct strbuf sb = STRBUF_INIT;
if (data)
buffer = data;
else {
enum object_type type;
buffer = to_free =
read_sha1_file(tag->object.sha1, &type, &size);
if (!buffer)
return error_func(&tag->object, FSCK_ERROR,
"cannot read tag object");
if (type != OBJ_TAG) {
ret = error_func(&tag->object, FSCK_ERROR,
"expected tag got %s",
typename(type));
goto done;
}
}
if (require_end_of_header(buffer, size, &tag->object, error_func))
goto done;
if (!skip_prefix(buffer, "object ", &buffer)) {
ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'object' line");
goto done;
}
if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') {
ret = error_func(&tag->object, FSCK_ERROR, "invalid 'object' line format - bad sha1");
goto done;
}
buffer += 41;
if (!skip_prefix(buffer, "type ", &buffer)) {
ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'type' line");
goto done;
}
eol = strchr(buffer, '\n');
if (!eol) {
ret = error_func(&tag->object, FSCK_ERROR, "invalid format - unexpected end after 'type' line");
goto done;
}
if (type_from_string_gently(buffer, eol - buffer, 1) < 0)
ret = error_func(&tag->object, FSCK_ERROR, "invalid 'type' value");
if (ret)
goto done;
buffer = eol + 1;
if (!skip_prefix(buffer, "tag ", &buffer)) {
ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'tag' line");
goto done;
}
eol = strchr(buffer, '\n');
if (!eol) {
ret = error_func(&tag->object, FSCK_ERROR, "invalid format - unexpected end after 'type' line");
goto done;
}
strbuf_addf(&sb, "refs/tags/%.*s", (int)(eol - buffer), buffer);
if (check_refname_format(sb.buf, 0))
error_func(&tag->object, FSCK_WARN, "invalid 'tag' name: %s", buffer);
buffer = eol + 1;
if (!skip_prefix(buffer, "tagger ", &buffer))
/* early tags do not contain 'tagger' lines; warn only */
error_func(&tag->object, FSCK_WARN, "invalid format - expected 'tagger' line");
else
ret = fsck_ident(&buffer, &tag->object, error_func);
done:
strbuf_release(&sb);
free(to_free);
return ret;
}
static int fsck_tag(struct tag *tag, const char *data,
unsigned long size, fsck_error error_func)
{
struct object *tagged = tag->tagged;
if (!tagged)
return error_func(&tag->object, FSCK_ERROR, "could not load tagged object");
return 0;
return fsck_tag_buffer(tag, data, size, error_func);
}
int fsck_object(struct object *obj, int strict, fsck_error error_func)
int fsck_object(struct object *obj, void *data, unsigned long size,
int strict, fsck_error error_func)
{
if (!obj)
return error_func(obj, FSCK_ERROR, "no valid object to fsck");
@ -349,9 +460,11 @@ int fsck_object(struct object *obj, int strict, fsck_error error_func)
if (obj->type == OBJ_TREE)
return fsck_tree((struct tree *) obj, strict, error_func);
if (obj->type == OBJ_COMMIT)
return fsck_commit((struct commit *) obj, error_func);
return fsck_commit((struct commit *) obj, (const char *) data,
size, error_func);
if (obj->type == OBJ_TAG)
return fsck_tag((struct tag *) obj, error_func);
return fsck_tag((struct tag *) obj, (const char *) data,
size, error_func);
return error_func(obj, FSCK_ERROR, "unknown type '%d' (internal fsck error)",
obj->type);

4
fsck.h
View file

@ -28,6 +28,8 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...);
* 0 everything OK
*/
int fsck_walk(struct object *obj, fsck_walk_func walk, void *data);
int fsck_object(struct object *obj, int strict, fsck_error error_func);
/* If NULL is passed for data, we assume the object is local and read it. */
int fsck_object(struct object *obj, void *data, unsigned long size,
int strict, fsck_error error_func);
#endif

View file

@ -33,13 +33,20 @@ const char *typename(unsigned int type)
return object_type_strings[type];
}
int type_from_string(const char *str)
int type_from_string_gently(const char *str, ssize_t len, int gentle)
{
int i;
if (len < 0)
len = strlen(str);
for (i = 1; i < ARRAY_SIZE(object_type_strings); i++)
if (!strcmp(str, object_type_strings[i]))
if (!strncmp(str, object_type_strings[i], len))
return i;
if (gentle)
return -1;
die("invalid object type \"%s\"", str);
}

View file

@ -53,7 +53,8 @@ struct object {
};
extern const char *typename(unsigned int type);
extern int type_from_string(const char *str);
extern int type_from_string_gently(const char *str, ssize_t, int gentle);
#define type_from_string(str) type_from_string_gently(str, -1, 0)
/*
* Return the current number of buckets in the object hashmap.

View file

@ -214,6 +214,25 @@ test_expect_success 'tag pointing to something else than its type' '
test_must_fail git fsck --tags
'
test_expect_success 'tag with incorrect tag name & missing tagger' '
sha=$(git rev-parse HEAD) &&
cat >wrong-tag <<-EOF &&
object $sha
type commit
tag wrong name format
This is an invalid tag.
EOF
tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
test_when_finished "remove_object $tag" &&
echo $tag >.git/refs/tags/wrong &&
test_when_finished "git update-ref -d refs/tags/wrong" &&
git fsck --tags 2>out &&
grep "invalid .tag. name" out &&
grep "expected .tagger. line" out
'
test_expect_success 'cleaned up' '
git fsck >actual 2>&1 &&
test_cmp empty actual

View file

@ -243,4 +243,23 @@ test_expect_success 'running index-pack in the object store' '
test -f .git/objects/pack/pack-${pack1}.idx
'
test_expect_success 'index-pack --strict warns upon missing tagger in tag' '
sha=$(git rev-parse HEAD) &&
cat >wrong-tag <<EOF &&
object $sha
type commit
tag guten tag
This is an invalid tag.
EOF
tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
pack1=$(echo $tag $sha | git pack-objects tag-test) &&
echo remove tag object &&
thirtyeight=${tag#??} &&
rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight &&
git index-pack --strict tag-test-${pack1}.pack 2>err &&
grep "^error:.* expected .tagger. line" err
'
test_done