зеркало из https://github.com/microsoft/git.git
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:
Коммит
13f4f04692
|
@ -298,7 +298,7 @@ static int fsck_obj(struct object *obj)
|
||||||
|
|
||||||
if (fsck_walk(obj, mark_used, NULL))
|
if (fsck_walk(obj, mark_used, NULL))
|
||||||
objerror(obj, "broken links");
|
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;
|
return -1;
|
||||||
|
|
||||||
if (obj->type == OBJ_TREE) {
|
if (obj->type == OBJ_TREE) {
|
||||||
|
|
|
@ -779,7 +779,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
|
||||||
if (!obj)
|
if (!obj)
|
||||||
die(_("invalid %s"), typename(type));
|
die(_("invalid %s"), typename(type));
|
||||||
if (do_fsck_object &&
|
if (do_fsck_object &&
|
||||||
fsck_object(obj, 1, fsck_error_function))
|
fsck_object(obj, buf, size, 1,
|
||||||
|
fsck_error_function))
|
||||||
die(_("Error in object"));
|
die(_("Error in object"));
|
||||||
if (fsck_walk(obj, mark_link, NULL))
|
if (fsck_walk(obj, mark_link, NULL))
|
||||||
die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
|
die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
|
||||||
|
|
|
@ -164,10 +164,10 @@ static unsigned nr_objects;
|
||||||
* Called only from check_object() after it verified this object
|
* Called only from check_object() after it verified this object
|
||||||
* is Ok.
|
* 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];
|
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)
|
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));
|
die("failed to write object %s", sha1_to_hex(obj->sha1));
|
||||||
obj->flags |= FLAG_WRITTEN;
|
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)
|
static int check_object(struct object *obj, int type, void *data)
|
||||||
{
|
{
|
||||||
|
struct obj_buffer *obj_buf;
|
||||||
|
|
||||||
if (!obj)
|
if (!obj)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
@ -198,11 +200,15 @@ static int check_object(struct object *obj, int type, void *data)
|
||||||
return 0;
|
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");
|
die("Error in object");
|
||||||
if (fsck_walk(obj, check_object, NULL))
|
if (fsck_walk(obj, check_object, NULL))
|
||||||
die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
|
die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
|
||||||
write_cached_object(obj);
|
write_cached_object(obj, obj_buf);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
133
fsck.c
133
fsck.c
|
@ -6,6 +6,7 @@
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
#include "fsck.h"
|
#include "fsck.h"
|
||||||
|
#include "refs.h"
|
||||||
|
|
||||||
static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
|
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;
|
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)
|
static int fsck_ident(const char **ident, struct object *obj, fsck_error error_func)
|
||||||
{
|
{
|
||||||
char *end;
|
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,
|
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];
|
unsigned char tree_sha1[20], sha1[20];
|
||||||
struct commit_graft *graft;
|
struct commit_graft *graft;
|
||||||
unsigned parent_count, parent_line_count = 0;
|
unsigned parent_count, parent_line_count = 0;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
if (require_end_of_header(buffer, size, &commit->object, error_func))
|
||||||
|
return -1;
|
||||||
|
|
||||||
if (!skip_prefix(buffer, "tree ", &buffer))
|
if (!skip_prefix(buffer, "tree ", &buffer))
|
||||||
return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line");
|
return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line");
|
||||||
if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n')
|
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;
|
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);
|
const char *buffer = data ? data : get_commit_buffer(commit, &size);
|
||||||
int ret = fsck_commit_buffer(commit, buffer, error_func);
|
int ret = fsck_commit_buffer(commit, buffer, size, error_func);
|
||||||
unuse_commit_buffer(commit, buffer);
|
if (!data)
|
||||||
|
unuse_commit_buffer(commit, buffer);
|
||||||
return ret;
|
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;
|
struct object *tagged = tag->tagged;
|
||||||
|
|
||||||
if (!tagged)
|
if (!tagged)
|
||||||
return error_func(&tag->object, FSCK_ERROR, "could not load tagged object");
|
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)
|
if (!obj)
|
||||||
return error_func(obj, FSCK_ERROR, "no valid object to fsck");
|
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)
|
if (obj->type == OBJ_TREE)
|
||||||
return fsck_tree((struct tree *) obj, strict, error_func);
|
return fsck_tree((struct tree *) obj, strict, error_func);
|
||||||
if (obj->type == OBJ_COMMIT)
|
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)
|
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)",
|
return error_func(obj, FSCK_ERROR, "unknown type '%d' (internal fsck error)",
|
||||||
obj->type);
|
obj->type);
|
||||||
|
|
4
fsck.h
4
fsck.h
|
@ -28,6 +28,8 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...);
|
||||||
* 0 everything OK
|
* 0 everything OK
|
||||||
*/
|
*/
|
||||||
int fsck_walk(struct object *obj, fsck_walk_func walk, void *data);
|
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
|
#endif
|
||||||
|
|
11
object.c
11
object.c
|
@ -33,13 +33,20 @@ const char *typename(unsigned int type)
|
||||||
return object_type_strings[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;
|
int i;
|
||||||
|
|
||||||
|
if (len < 0)
|
||||||
|
len = strlen(str);
|
||||||
|
|
||||||
for (i = 1; i < ARRAY_SIZE(object_type_strings); i++)
|
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;
|
return i;
|
||||||
|
|
||||||
|
if (gentle)
|
||||||
|
return -1;
|
||||||
|
|
||||||
die("invalid object type \"%s\"", str);
|
die("invalid object type \"%s\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
object.h
3
object.h
|
@ -53,7 +53,8 @@ struct object {
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const char *typename(unsigned int type);
|
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.
|
* Return the current number of buckets in the object hashmap.
|
||||||
|
|
|
@ -214,6 +214,25 @@ test_expect_success 'tag pointing to something else than its type' '
|
||||||
test_must_fail git fsck --tags
|
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' '
|
test_expect_success 'cleaned up' '
|
||||||
git fsck >actual 2>&1 &&
|
git fsck >actual 2>&1 &&
|
||||||
test_cmp empty actual
|
test_cmp empty actual
|
||||||
|
|
|
@ -243,4 +243,23 @@ test_expect_success 'running index-pack in the object store' '
|
||||||
test -f .git/objects/pack/pack-${pack1}.idx
|
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
|
test_done
|
||||||
|
|
Загрузка…
Ссылка в новой задаче