зеркало из 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))
|
||||
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) {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
131
fsck.c
131
fsck.c
|
@ -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);
|
||||
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
4
fsck.h
|
@ -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
|
||||
|
|
11
object.c
11
object.c
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
3
object.h
3
object.h
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче