]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'js/fsck-tag-validation'
authorJunio C Hamano <gitster@pobox.com>
Fri, 26 Sep 2014 21:39:43 +0000 (14:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 26 Sep 2014 21:39:43 +0000 (14:39 -0700)
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

builtin/fsck.c
builtin/index-pack.c
builtin/unpack-objects.c
fsck.c
fsck.h
object.c
object.h
t/t1450-fsck.sh
t/t5302-pack-index.sh

index 0928a98a71ca2b764d4fbb59976f1c4bdc421dde..e9ba576c1fe85cab505eb13fc20ae68dcfc19ff3 100644 (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) {
index eebf1a8fc21fd551f3917ad23ab5a45dff302f23..f89df017c200fbf7e2d5ec016d5f85975f1e7109 100644 (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));
index 99cde45879401350355083f773a2ff2b78113d1a..855d94b90ba019ff19832ff49a76d6cc0077e956 100644 (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;
 }
 
diff --git a/fsck.c b/fsck.c
index 56156fff44a3556e2fa0b691ab103612a4678a3e..2fffa434a5763abb6d7895ee8c7582307766ee62 100644 (file)
--- a/fsck.c
+++ b/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);
-       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);
diff --git a/fsck.h b/fsck.h
index 1e4f527318ea4b4d701a27e08d68f3551173f176..d1e6387a44e223b6d9438c7c35dc9efaa52f7d48 100644 (file)
--- a/fsck.h
+++ b/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
index a16b9f9e936d060ba190b6c7e82ff5f25795f490..aedac243f0db05351a1ec7ae8b5ed297e51121e8 100644 (file)
--- a/object.c
+++ b/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);
 }
 
index 5e8d8ee5485a5825c4dc0a0bba1911061723e161..e028ced74c6c50459376d778c8bb9bae1d2808c3 100644 (file)
--- a/object.h
+++ b/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.
index b52397afd3352b873c9c7fabb91eb850aaf0db3b..c23408ec07ae0743504806fcfbb43a41c643006d 100755 (executable)
@@ -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
index 4bbb718751738c3e913c25160d0fff52d8350bba..61bc8da56028625fa2525cc5b067d40367f2b1c6 100755 (executable)
@@ -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