commit d1cda8264f62723250746b16cb897832a99c537b from: Stefan Sperling date: Mon Nov 06 17:53:05 2017 UTC parse commit objects commit - 6332351956c375744e908c1f895721ba3a5157d2 commit + d1cda8264f62723250746b16cb897832a99c537b blob - 325073050fc88a4c277d9fab2d2a72433701503f blob + ae0f38629814f9c9648b74a1a15ef0a6500e7e9e --- include/got_error.h +++ include/got_error.h @@ -26,6 +26,8 @@ #define GOT_ERR_DECOMPRESSION 0x0008 #define GOT_ERR_NO_SPACE 0x0009 #define GOT_ERR_BAD_OBJ_HDR 0x0010 +#define GOT_ERR_OBJ_TYPE 0x0011 +#define GOT_ERR_BAD_OBJ_DATA 0x0012 static const struct got_error { int code; @@ -42,6 +44,8 @@ static const struct got_error { { GOT_ERR_DECOMPRESSION,"decompression failed" }, { GOT_ERR_NO_SPACE, "buffer too small" }, { GOT_ERR_BAD_OBJ_HDR, "bad object header" }, + { GOT_ERR_OBJ_TYPE, "wrong type of object" }, + { GOT_ERR_BAD_OBJ_DATA, "bad object data" }, }; const struct got_error * got_error(int code); blob - e0280c719c9da46b5e6b4d3497f5dbf157b7fc49 blob + dda5b9eff685434839544f6b14a1e25a990e7142 --- include/got_object.h +++ include/got_object.h @@ -18,14 +18,44 @@ struct got_object_id { u_int8_t sha1[SHA1_DIGEST_LENGTH]; }; +struct got_blob_object { + char *dummy; +}; + +struct got_tree_object { + char *dummy; +}; + +struct got_parent_id { + SIMPLEQ_ENTRY(got_parent_id) entry; + struct got_object_id id; +}; + +SIMPLEQ_HEAD(got_parent_id_list, got_parent_id); + +struct got_commit_object { + struct got_object_id tree_id; + unsigned int nparents; + SIMPLEQ_HEAD(, got_parent_id) parent_ids; + char *author; + char *committer; + char *logmsg; +}; + struct got_object { int type; #define GOT_OBJ_TYPE_COMMIT 1 #define GOT_OBJ_TYPE_TREE 2 #define GOT_OBJ_TYPE_BLOB 3 + size_t hdrlen; size_t size; struct got_object_id id; + union { + struct got_blob_object blob; + struct got_tree_object tree; + struct got_commit_object commit; + } obj; }; struct got_repository; @@ -34,3 +64,7 @@ const char * got_object_id_str(struct got_object_id *, const struct got_error *got_object_open(struct got_object **, struct got_repository *, struct got_object_id *); void got_object_close(struct got_object *); +const struct got_error *got_object_commit_open(struct got_commit_object **, + struct got_repository *, struct got_object *); +void got_object_commit_close(struct got_commit_object *); + blob - /dev/null blob + 668013da54ca7a913d9378e2413398b9dd4a0185 (mode 644) --- /dev/null +++ include/got_sha1.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2017 Stefan Sperling + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +int got_parse_sha1_digest(uint8_t *, const char *); blob - 254d635660b60064ce399812b785835832117b72 blob + f24b83d937d8f42d22bbce3b29d61394aca28678 --- lib/object.c +++ lib/object.c @@ -14,6 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + #include #include #include @@ -25,6 +27,7 @@ #include "got_error.h" #include "got_object.h" #include "got_repository.h" +#include "got_sha1.h" #ifndef MIN #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) @@ -38,6 +41,11 @@ #define GOT_OBJ_TAG_TREE "tree" #define GOT_OBJ_TAG_BLOB "blob" +#define GOT_COMMIT_TAG_TREE "tree " +#define GOT_COMMIT_TAG_PARENT "parent " +#define GOT_COMMIT_TAG_AUTHOR "author " +#define GOT_COMMIT_TAG_COMMITTER "committer " + const char * got_object_id_str(struct got_object_id *id, char *buf, size_t size) { @@ -148,7 +156,7 @@ inflate_read(struct got_zstream_buf *zb, FILE *f, size } static const struct got_error * -parse_obj_header(struct got_object **obj, char *buf, size_t len) +parse_object_header(struct got_object **obj, char *buf, size_t len) { const char *obj_tags[] = { GOT_OBJ_TAG_COMMIT, @@ -161,13 +169,15 @@ parse_obj_header(struct got_object **obj, char *buf, s GOT_OBJ_TYPE_BLOB, }; int type = 0; - size_t size = 0; + size_t size = 0, hdrlen = 0; int i; char *p = strchr(buf, '\0'); if (p == NULL) return got_error(GOT_ERR_BAD_OBJ_HDR); + hdrlen = strlen(buf) + 1 /* '\0' */; + for (i = 0; i < nitems(obj_tags); i++) { const char *tag = obj_tags[i]; size_t tlen = strlen(tag); @@ -190,6 +200,7 @@ parse_obj_header(struct got_object **obj, char *buf, s *obj = calloc(1, sizeof(**obj)); (*obj)->type = type; + (*obj)->hdrlen = hdrlen; (*obj)->size = size; return NULL; } @@ -201,7 +212,6 @@ read_object_header(struct got_object **obj, struct got const struct got_error *err; FILE *f; struct got_zstream_buf zb; - char *p; size_t outlen; int i, ret; @@ -219,40 +229,50 @@ read_object_header(struct got_object **obj, struct got if (err) goto done; - err = parse_obj_header(obj, zb.outbuf, outlen); + err = parse_object_header(obj, zb.outbuf, outlen); done: inflate_end(&zb); fclose(f); return err; } -const struct got_error * -got_object_open(struct got_object **obj, struct got_repository *repo, - struct got_object_id *id) +static const struct got_error * +object_path(char **path, struct got_object_id *id, + struct got_repository *repo) { const struct got_error *err = NULL; - char *path_objects = got_repo_get_path_objects(repo); char hex[SHA1_DIGEST_STRING_LENGTH]; - char *path = NULL; + char *path_objects = got_repo_get_path_objects(repo); if (path_objects == NULL) return got_error(GOT_ERR_NO_MEM); got_object_id_str(id, hex, sizeof(hex)); - if (asprintf(&path, "%s/%.2x/%s", - path_objects, id->sha1[0], hex + 2) == -1) { + if (asprintf(path, "%s/%.2x/%s", path_objects, + id->sha1[0], hex + 2) == -1) err = got_error(GOT_ERR_NO_MEM); - goto done; - } + free(path_objects); + return err; +} + +const struct got_error * +got_object_open(struct got_object **obj, struct got_repository *repo, + struct got_object_id *id) +{ + const struct got_error *err = NULL; + char *path = NULL; + + err = object_path(&path, id, repo); + if (err) + return err; + err = read_object_header(obj, repo, path); if (err == NULL) memcpy((*obj)->id.sha1, id->sha1, SHA1_DIGEST_LENGTH); - done: free(path); - free(path_objects); return err; } @@ -260,4 +280,215 @@ void got_object_close(struct got_object *obj) { free(obj); +} + +static int +commit_object_valid(struct got_commit_object *commit) +{ + int i; + int n; + + if (commit == NULL) + return 0; + + n = 0; + for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { + if (commit->tree_id.sha1[i] == 0) + n++; + } + if (n == SHA1_DIGEST_LENGTH) + return 0; + + return 1; } + +static const struct got_error * +parse_commit_object(struct got_commit_object **commit, char *buf, size_t len) +{ + const struct got_error *err = NULL; + char *s = buf; + size_t tlen; + ssize_t remain = (ssize_t)len; + + *commit = calloc(1, sizeof(**commit)); + if (*commit == NULL) + return got_error(GOT_ERR_NO_MEM); + + SIMPLEQ_INIT(&(*commit)->parent_ids); + + tlen = strlen(GOT_COMMIT_TAG_TREE); + if (strncmp(s, GOT_COMMIT_TAG_TREE, tlen) == 0) { + remain -= tlen; + if (remain < SHA1_DIGEST_STRING_LENGTH) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + s += tlen; + if (!got_parse_sha1_digest((*commit)->tree_id.sha1, s)) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + remain -= SHA1_DIGEST_STRING_LENGTH; + s += SHA1_DIGEST_STRING_LENGTH; + } else { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + + tlen = strlen(GOT_COMMIT_TAG_PARENT); + while (strncmp(s, GOT_COMMIT_TAG_PARENT, tlen) == 0) { + struct got_parent_id *pid; + + remain -= tlen; + if (remain < SHA1_DIGEST_STRING_LENGTH) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + + pid = calloc(1, sizeof(*pid)); + if (pid == NULL) { + err = got_error(GOT_ERR_NO_MEM); + goto done; + } + s += tlen; + if (!got_parse_sha1_digest(pid->id.sha1, s)) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + SIMPLEQ_INSERT_TAIL(&(*commit)->parent_ids, pid, entry); + (*commit)->nparents++; + + s += SHA1_DIGEST_STRING_LENGTH; + } + + tlen = strlen(GOT_COMMIT_TAG_AUTHOR); + if (strncmp(s, GOT_COMMIT_TAG_AUTHOR, tlen) == 0) { + char *p; + + remain -= tlen; + if (remain <= 0) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + s += tlen; + p = strchr(s, '\n'); + if (p == NULL) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + *p = '\0'; + (*commit)->author = strdup(s); + if ((*commit)->author == NULL) { + err = got_error(GOT_ERR_NO_MEM); + goto done; + } + s += strlen((*commit)->author) + 1; + } + + tlen = strlen(GOT_COMMIT_TAG_COMMITTER); + if (strncmp(s, GOT_COMMIT_TAG_COMMITTER, tlen) == 0) { + char *p; + + remain -= tlen; + if (remain <= 0) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + s += tlen; + p = strchr(s, '\n'); + if (p == NULL) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + *p = '\0'; + (*commit)->committer = strdup(s); + if ((*commit)->committer == NULL) { + err = got_error(GOT_ERR_NO_MEM); + goto done; + } + s += strlen((*commit)->committer) + 1; + } + + (*commit)->logmsg = strdup(s); +done: + if (err) + got_object_commit_close(*commit); + return err; +} + +static const struct got_error * +read_commit_object(struct got_commit_object **commit, + struct got_repository *repo, struct got_object *obj, const char *path) +{ + const struct got_error *err = NULL; + FILE *f; + struct got_zstream_buf zb; + size_t len; + char *p; + int i, ret; + + f = fopen(path, "rb"); + if (f == NULL) + return got_error(GOT_ERR_BAD_PATH); + + err = inflate_init(&zb, 8192); + if (err) { + fclose(f); + return err; + } + + do { + err = inflate_read(&zb, f, &len); + if (err || len == 0) + break; + } while (len < obj->hdrlen + obj->size); + + if (len < obj->hdrlen + obj->size) { + err = got_error(GOT_ERR_BAD_OBJ_DATA); + goto done; + } + + /* Skip object header. */ + len -= obj->hdrlen; + err = parse_commit_object(commit, zb.outbuf + obj->hdrlen, len); +done: + inflate_end(&zb); + fclose(f); + return err; +} + +const struct got_error * +got_object_commit_open(struct got_commit_object **commit, + struct got_repository *repo, struct got_object *obj) +{ + const struct got_error *err = NULL; + char *path = NULL; + + if (obj->type != GOT_OBJ_TYPE_COMMIT) + return got_error(GOT_ERR_OBJ_TYPE); + + err = object_path(&path, &obj->id, repo); + if (err) + return err; + + err = read_commit_object(commit, repo, obj, path); + free(path); + return err; +} + +void +got_object_commit_close(struct got_commit_object *commit) +{ + struct got_parent_id *pid; + + while (!SIMPLEQ_EMPTY(&commit->parent_ids)) { + pid = SIMPLEQ_FIRST(&commit->parent_ids); + SIMPLEQ_REMOVE_HEAD(&commit->parent_ids, entry); + free(pid); + } + + free(commit->author); + free(commit->committer); + free(commit->logmsg); + free(commit); +} blob - 19e8815ba5c3b4d7548b9705f9b9beba009b6cb5 blob + b299fd1c82f09e8d7eb1da05f0288b581998f571 --- lib/refs.c +++ lib/refs.c @@ -15,18 +15,19 @@ */ #include +#include + #include #include #include #include #include -#include -#include #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_refs.h" +#include "got_sha1.h" #include "path.h" @@ -56,45 +57,6 @@ parse_symref(struct got_reference **ref, const char *n symref->name = symref_name; symref->ref = symref_ref; return NULL; -} - -static int -parse_xdigit(uint8_t *val, const char *hex) -{ - char *ep; - long lval; - - errno = 0; - lval = strtol(hex, &ep, 16); - if (hex[0] == '\0' || *ep != '\0') - return 0; - if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) - return 0; - - *val = (uint8_t)lval; - return 1; -} - -static int -parse_sha1_digest(uint8_t *digest, const char *line) -{ - uint8_t b = 0; - char hex[3] = {'\0', '\0', '\0'}; - int i, j; - - for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { - if (line[0] == '\0' || line[1] == '\0') - return 0; - for (j = 0; j < 2; j++) { - hex[j] = *line; - line++; - } - if (!parse_xdigit(&b, hex)) - return 0; - digest[i] = b; - } - - return 1; } static const struct got_error * @@ -112,7 +74,7 @@ parse_ref_line(struct got_reference **ref, const char if (ref_name == NULL) return got_error(GOT_ERR_NO_MEM); - if (!parse_sha1_digest(digest, line)) + if (!got_parse_sha1_digest(digest, line)) return got_error(GOT_ERR_NOT_REF); *ref = calloc(1, sizeof(**ref)); blob - /dev/null blob + e6058f894daa451ecd640fb5d2a0e836c8237a19 (mode 644) --- /dev/null +++ lib/sha1.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Stefan Sperling + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +static int +parse_xdigit(uint8_t *val, const char *hex) +{ + char *ep; + long lval; + + errno = 0; + lval = strtol(hex, &ep, 16); + if (hex[0] == '\0' || *ep != '\0') + return 0; + if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) + return 0; + + *val = (uint8_t)lval; + return 1; +} + +int +got_parse_sha1_digest(uint8_t *digest, const char *line) +{ + uint8_t b = 0; + char hex[3] = {'\0', '\0', '\0'}; + int i, j; + + for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { + if (line[0] == '\0' || line[1] == '\0') + return 0; + for (j = 0; j < 2; j++) { + hex[j] = *line; + line++; + } + if (!parse_xdigit(&b, hex)) + return 0; + digest[i] = b; + } + + return 1; +} blob - 091f43b0a03fb2b5ddc634dc5e8fcd36ccd7dff6 blob + de7eb21b21c7823a753261aadf7cba35c9580fbf --- regress/repository/Makefile +++ regress/repository/Makefile @@ -1,7 +1,7 @@ .PATH:${.CURDIR}/../../lib PROG = repository_test -SRCS = path.c repository.c error.c refs.c object.c repository_test.c +SRCS = path.c repository.c error.c refs.c object.c sha1.c repository_test.c CPPFLAGS = -I${.CURDIR}/../../include LDADD = -lutil -lz blob - aeb8b920a4ed73c542162adf523aa4110f9cfd8e blob + 45451895935d5938eef7e4317784c4e8e90671bd --- regress/repository/repository_test.c +++ regress/repository/repository_test.c @@ -14,6 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + #include #include #include @@ -53,6 +55,24 @@ repo_read_object_header(const char *repo_path) if (err != NULL || obj == NULL) return 0; printf("object type=%d size=%lu\n", obj->type, obj->size); + if (obj->type == GOT_OBJ_TYPE_COMMIT) { + struct got_commit_object *commit; + struct got_parent_id *pid; + + err = got_object_commit_open(&commit, repo, obj); + if (err != NULL || commit == NULL) + return 0; + printf("tree: %s\n", + got_object_id_str(&commit->tree_id, buf, sizeof(buf))); + printf("parent%s: ", (commit->nparents == 1) ? "" : "s"); + SIMPLEQ_FOREACH(pid, &commit->parent_ids, entry) { + printf("%s\n", + got_object_id_str(&pid->id, buf, sizeof(buf))); + } + printf("author: %s\n", commit->author); + printf("committer: %s\n", commit->committer); + printf("log: %s\n", commit->logmsg); + } got_object_close(obj); free(id); got_ref_close(head_ref);