diff --git a/include/git2/commit.h b/include/git2/commit.h index c09b34843..3687d9460 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -135,6 +135,16 @@ GIT_EXTERN(const git_signature *) git_commit_author(git_commit *commit); */ GIT_EXTERN(int) git_commit_tree(git_tree **tree_out, git_commit *commit); +/** + * Get the id of the tree pointed to by a commit. This differs from + * `git_commit_tree` in that no attempts are made to fetch an object + * from the ODB. + * + * @param commit a previously loaded commit. + * @return the id of tree pointed to by commit. + */ +GIT_EXTERN(const git_oid *) git_commit_tree_oid(git_commit *commit); + /** * Get the number of parents of this commit * @@ -153,6 +163,16 @@ GIT_EXTERN(unsigned int) git_commit_parentcount(git_commit *commit); */ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n); +/** + * Get the oid of a specified parent for a commit. This is different from + * `git_commit_parent`, which will attempt to load the parent commit from + * the ODB. + * + * @param commit a previously loaded commit. + * @param n the position of the parent (from 0 to `parentcount`) + * @return the id of the parent, NULL on error. + */ +GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned int n); /** * Create a new commit in the repository diff --git a/include/git2/common.h b/include/git2/common.h index 7cb98b824..ab267e620 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -164,6 +164,12 @@ /** A reference with this name already exists */ #define GIT_EEXISTS (GIT_ERROR - 23) +/** The given integer literal is too large to be parsed */ +#define GIT_EOVERFLOW (GIT_ERROR - 24) + +/** The given literal is not a valid number */ +#define GIT_ENOTNUM (GIT_ERROR - 25) + GIT_BEGIN_DECL typedef struct { diff --git a/include/git2/index.h b/include/git2/index.h index 599512f8a..8a84f507b 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -44,6 +44,46 @@ GIT_BEGIN_DECL #define GIT_IDXENTRY_VALID (0x8000) #define GIT_IDXENTRY_STAGESHIFT 12 +/* + * Flags are divided into two parts: in-memory flags and + * on-disk ones. Flags in GIT_IDXENTRY_EXTENDED_FLAGS + * will get saved on-disk. + * + * In-memory only flags: + */ +#define GIT_IDXENTRY_UPDATE (1 << 16) +#define GIT_IDXENTRY_REMOVE (1 << 17) +#define GIT_IDXENTRY_UPTODATE (1 << 18) +#define GIT_IDXENTRY_ADDED (1 << 19) + +#define GIT_IDXENTRY_HASHED (1 << 20) +#define GIT_IDXENTRY_UNHASHED (1 << 21) +#define GIT_IDXENTRY_WT_REMOVE (1 << 22) /* remove in work directory */ +#define GIT_IDXENTRY_CONFLICTED (1 << 23) + +#define GIT_IDXENTRY_UNPACKED (1 << 24) +#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 25) + +/* + * Extended on-disk flags: + */ +#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 29) +#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 30) +/* GIT_IDXENTRY_EXTENDED2 is for future extension */ +#define GIT_IDXENTRY_EXTENDED2 (1 << 31) + +#define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) + +/* + * Safeguard to avoid saving wrong flags: + * - GIT_IDXENTRY_EXTENDED2 won't get saved until its semantic is known + * - Bits in 0x0000FFFF have been saved in flags already + * - Bits in 0x003F0000 are currently in-memory flags + */ +#if GIT_IDXENTRY_EXTENDED_FLAGS & 0x803FFFFF +#error "GIT_IDXENTRY_EXTENDED_FLAGS out of range" +#endif + /** Time used in a git index entry */ typedef struct { git_time_t seconds; diff --git a/include/git2/repository.h b/include/git2/repository.h index 00c1f20d0..c47fcfc9a 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -182,6 +182,36 @@ GIT_EXTERN(void) git_repository_free(git_repository *repo); */ GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare); +/** + * Check if a repository is empty + * + * An empty repository has just been initialized and contains + * no commits. + * + * @param repo Repo to test + * @return 1 if the repository is empty, 0 if it isn't, error code + * if the repository is corrupted + */ +GIT_EXTERN(int) git_repository_is_empty(git_repository *repo); + +/** + * Get the normalized path to the git repository. + * + * @param repo a repository object + * @return absolute path to the git directory + */ +GIT_EXTERN(const char *) git_repository_path(git_repository *repo); + +/** + * Get the normalized path to the working directory of the repository. + * + * If the repository is bare, there is no working directory and NULL we be returned. + * + * @param repo a repository object + * @return NULL if the repository is bare; absolute path to the working directory otherwise. + */ +GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/signature.h b/include/git2/signature.h index 40412a45f..44d1f285e 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -41,19 +41,30 @@ GIT_BEGIN_DECL * Create a new action signature. The signature must be freed * manually or using git_signature_free * - * @name name of the person - * @email email of the person - * @time time when the action happened - * @offset timezone offset in minutes for the time + * @param name name of the person + * @param mail email of the person + * @param time time when the action happened + * @param offset timezone offset in minutes for the time * @return the new sig, NULL on out of memory */ GIT_EXTERN(git_signature *) git_signature_new(const char *name, const char *email, git_time_t time, int offset); +/** + * Create a new action signature with a timestamp of 'now'. The + * signature must be freed manually or using git_signature_free + * + * @param name name of the person + * @param email email of the person + * @return the new sig, NULL on out of memory + */ +GIT_EXTERN(git_signature *) git_signature_now(const char *name, const char *email); + + /** * Create a copy of an existing signature. * * All internal strings are also duplicated. - * @sig signature to duplicated + * @param sig signature to duplicated * @return a copy of sig, NULL on out of memory */ GIT_EXTERN(git_signature *) git_signature_dup(const git_signature *sig); @@ -61,7 +72,7 @@ GIT_EXTERN(git_signature *) git_signature_dup(const git_signature *sig); /** * Free an existing signature * - * @sig signature to free + * @param sig signature to free */ GIT_EXTERN(void) git_signature_free(git_signature *sig); diff --git a/src/backends/hiredis.c b/src/backends/hiredis.c new file mode 100644 index 000000000..707412bf6 --- /dev/null +++ b/src/backends/hiredis.c @@ -0,0 +1,200 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "git2/object.h" +#include "hash.h" +#include "odb.h" + +#include "git2/odb_backend.h" + +#ifdef GIT2_HIREDIS_BACKEND + +#include + +typedef struct { + git_odb_backend parent; + + redisContext *db; +} hiredis_backend; + +int hiredis_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) { + hiredis_backend *backend; + int error; + redisReply *reply; + + assert(len_p && type_p && _backend && oid); + + backend = (hiredis_backend *) _backend; + error = GIT_ERROR; + + reply = redisCommand(backend->db, "HMGET %b %s %s", oid->id, GIT_OID_RAWSZ, + "type", "size"); + + if (reply->type == REDIS_REPLY_ARRAY) { + if (reply->element[0]->type != REDIS_REPLY_NIL && + reply->element[0]->type != REDIS_REPLY_NIL) { + *type_p = (git_otype) atoi(reply->element[0]->str); + *len_p = (size_t) atoi(reply->element[1]->str); + error = GIT_SUCCESS; + } else { + error = GIT_ENOTFOUND; + } + } else { + error = GIT_ERROR; + } + + freeReplyObject(reply); + return error; +} + +int hiredis_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) { + hiredis_backend *backend; + int error; + redisReply *reply; + + assert(data_p && len_p && type_p && _backend && oid); + + backend = (hiredis_backend *) _backend; + error = GIT_ERROR; + + reply = redisCommand(backend->db, "HMGET %b %s %s %s", oid->id, GIT_OID_RAWSZ, + "type", "size", "data"); + + if (reply->type == REDIS_REPLY_ARRAY) { + if (reply->element[0]->type != REDIS_REPLY_NIL && + reply->element[1]->type != REDIS_REPLY_NIL && + reply->element[2]->type != REDIS_REPLY_NIL) { + *type_p = (git_otype) atoi(reply->element[0]->str); + *len_p = (size_t) atoi(reply->element[1]->str); + *data_p = git__malloc(*len_p); + if (*data_p == NULL) { + error = GIT_ENOMEM; + } else { + memcpy(*data_p, reply->element[2]->str, *len_p); + error = GIT_SUCCESS; + } + } else { + error = GIT_ENOTFOUND; + } + } else { + error = GIT_ERROR; + } + + freeReplyObject(reply); + return error; +} + +int hiredis_backend__exists(git_odb_backend *_backend, const git_oid *oid) { + hiredis_backend *backend; + int found; + redisReply *reply; + + assert(_backend && oid); + + backend = (hiredis_backend *) _backend; + found = 0; + + reply = redisCommand(backend->db, "exists %b", oid->id, GIT_OID_RAWSZ); + if (reply->type != REDIS_REPLY_NIL && reply->type != REDIS_REPLY_ERROR) + found = 1; + + + freeReplyObject(reply); + return found; +} + +int hiredis_backend__write(git_oid *id, git_odb_backend *_backend, const void *data, size_t len, git_otype type) { + hiredis_backend *backend; + int error; + redisReply *reply; + + assert(id && _backend && data); + + backend = (hiredis_backend *) _backend; + error = GIT_ERROR; + + if ((error = git_odb_hash(id, data, len, type)) < 0) + return error; + + reply = redisCommand(backend->db, "HMSET %b " + "type %d " + "size %d " + "data %b ", id->id, GIT_OID_RAWSZ, + (int) type, len, data, len); + error = reply->type == REDIS_REPLY_ERROR ? GIT_ERROR : GIT_SUCCESS; + + freeReplyObject(reply); + return error; +} + +void hiredis_backend__free(git_odb_backend *_backend) { + hiredis_backend *backend; + assert(_backend); + backend = (hiredis_backend *) _backend; + + redisFree(backend->db); + + free(backend); +} + +int git_odb_backend_hiredis(git_odb_backend **backend_out, const char *host, int port) { + hiredis_backend *backend; + + backend = git__calloc(1, sizeof (hiredis_backend)); + if (backend == NULL) + return GIT_ENOMEM; + + + backend->db = redisConnect(host, port); + if (backend->db->err) + goto cleanup; + + backend->parent.read = &hiredis_backend__read; + backend->parent.read_header = &hiredis_backend__read_header; + backend->parent.write = &hiredis_backend__write; + backend->parent.exists = &hiredis_backend__exists; + backend->parent.free = &hiredis_backend__free; + + *backend_out = (git_odb_backend *) backend; + + return GIT_SUCCESS; +cleanup: + free(backend); + return GIT_ERROR; +} + +#else + +int git_odb_backend_hiredis(git_odb_backend ** GIT_UNUSED(backend_out), + const char *GIT_UNUSED(host), int GIT_UNUSED(port)) { + GIT_UNUSED_ARG(backend_out); + GIT_UNUSED_ARG(host); + GIT_UNUSED_ARG(port); + return GIT_ENOTIMPLEMENTED; +} + + +#endif /* HAVE_HIREDIS */ diff --git a/src/commit.c b/src/commit.c index 9fc3f0767..9621703c3 100644 --- a/src/commit.c +++ b/src/commit.c @@ -318,6 +318,7 @@ GIT_COMMIT_GETTER(const char *, message_short, commit->message_short) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length) +GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid); int git_commit_tree(git_tree **tree_out, git_commit *commit) @@ -338,4 +339,9 @@ int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) return git_commit_lookup(parent, commit->object.repo, parent_oid); } +const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n) +{ + assert(commit); + return git_vector_get(&commit->parent_oids, n); +} diff --git a/src/errors.c b/src/errors.c index 5e59f8205..1ef2baadb 100644 --- a/src/errors.c +++ b/src/errors.c @@ -29,7 +29,9 @@ static struct { {GIT_EREVWALKOVER, "The revision walker is empty; there are no more commits left to iterate"}, {GIT_EINVALIDREFSTATE, "The state of the reference is not valid"}, {GIT_ENOTIMPLEMENTED, "This feature has not been implemented yet"}, - {GIT_EEXISTS, "A reference with this name already exists"} + {GIT_EEXISTS, "A reference with this name already exists"}, + {GIT_EOVERFLOW, "The given integer literal is too large to be parsed"}, + {GIT_ENOTNUM, "The given literal is not a valid number"}, }; const char *git_strerror(int num) diff --git a/src/filebuf.c b/src/filebuf.c index dff9373f6..eb93424ef 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -178,7 +178,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) if (flags & GIT_FILEBUF_DEFLATE_CONTENTS) { /* Initialize the ZLib stream */ - if (deflateInit(&file->zs, Z_DEFAULT_COMPRESSION) != Z_OK) { + if (deflateInit(&file->zs, Z_BEST_SPEED) != Z_OK) { error = GIT_EZLIB; goto cleanup; } diff --git a/src/index.c b/src/index.c index 6a31dd5cb..68bb9e2b9 100644 --- a/src/index.c +++ b/src/index.c @@ -411,6 +411,7 @@ static git_index_tree *read_tree_internal( { git_index_tree *tree; const char *name_start, *buffer; + long count; if ((tree = git__malloc(sizeof(git_index_tree))) == NULL) return NULL; @@ -429,12 +430,22 @@ static git_index_tree *read_tree_internal( goto error_cleanup; /* Blank-terminated ASCII decimal number of entries in this tree */ - tree->entries = strtol(buffer, (char **)&buffer, 10); + if (git__strtol32(&count, buffer, &buffer, 10) < GIT_SUCCESS || + count < 0) + goto error_cleanup; + + tree->entries = (size_t)count; + if (*buffer != ' ' || ++buffer >= buffer_end) goto error_cleanup; /* Number of children of the tree, newline-terminated */ - tree->children_count = strtol(buffer, (char **)&buffer, 10); + if (git__strtol32(&count, buffer, &buffer, 10) < GIT_SUCCESS || + count < 0) + goto error_cleanup; + + tree->children_count = (size_t)count; + if (*buffer != '\n' || ++buffer >= buffer_end) goto error_cleanup; diff --git a/src/refs.c b/src/refs.c index 9661988cc..00b9ff6b2 100644 --- a/src/refs.c +++ b/src/refs.c @@ -746,7 +746,7 @@ static int packed_write_ref(reference_oid *ref, git_filebuf *file) */ static int packed_find_peel(reference_oid *ref) { - git_tag *tag; + git_object *object; int error; if (ref->ref.type & GIT_REF_HAS_PEEL) @@ -760,25 +760,32 @@ static int packed_find_peel(reference_oid *ref) return GIT_SUCCESS; /* - * Find the tag in the repository. The tag must exist, - * otherwise this reference is broken and we shouldn't - * pack it. + * Find the tagged object in the repository */ - error = git_tag_lookup(&tag, ref->ref.owner, &ref->oid); + error = git_object_lookup(&object, ref->ref.owner, &ref->oid, GIT_OBJ_ANY); if (error < GIT_SUCCESS) return GIT_EOBJCORRUPTED; /* - * Find the object pointed at by this tag + * If the tagged object is a Tag object, we need to resolve it; + * if the ref is actually a 'weak' ref, we don't need to resolve + * anything. */ - git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag)); - ref->ref.type |= GIT_REF_HAS_PEEL; + if (git_object_type(object) == GIT_OBJ_TAG) { + git_tag *tag = (git_tag *)object; - /* - * The reference has now cached the resolved OID, and is - * marked at such. When written to the packfile, it'll be - * accompanied by this resolved oid - */ + /* + * Find the object pointed at by this tag + */ + git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag)); + ref->ref.type |= GIT_REF_HAS_PEEL; + + /* + * The reference has now cached the resolved OID, and is + * marked at such. When written to the packfile, it'll be + * accompanied by this resolved oid + */ + } return GIT_SUCCESS; } @@ -1419,6 +1426,10 @@ int git_reference_delete(git_reference *ref) assert(ref); if (ref->type & GIT_REF_PACKED) { + /* load the existing packfile */ + if ((error = packed_load(ref->owner)) < GIT_SUCCESS) + return error; + git_hashtable_remove(ref->owner->references.packfile, ref->name); error = packed_write(ref->owner); } else { diff --git a/src/repository.c b/src/repository.c index abbbd126a..c428b00af 100644 --- a/src/repository.c +++ b/src/repository.c @@ -473,3 +473,29 @@ cleanup: return error; } +int git_repository_is_empty(git_repository *repo) +{ + git_reference *head, *branch; + int error; + + error = git_reference_lookup(&head, repo, "HEAD"); + if (error < GIT_SUCCESS) + return error; + + if (git_reference_type(head) != GIT_REF_SYMBOLIC) + return GIT_EOBJCORRUPTED; + + return git_reference_resolve(&branch, head) == GIT_SUCCESS ? 0 : 1; +} + +const char *git_repository_path(git_repository *repo) +{ + assert(repo); + return repo->path_repository; +} + +const char *git_repository_workdir(git_repository *repo) +{ + assert(repo); + return repo->path_workdir; +} diff --git a/src/revwalk.c b/src/revwalk.c index 73bb060f5..b62b09961 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -191,6 +191,7 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo unsigned char *parents_start; int i, parents = 0; + long commit_time; buffer += STRLEN("tree ") + GIT_OID_HEXSZ + 1; @@ -227,10 +228,10 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo if (buffer == NULL) return GIT_EOBJCORRUPTED; - commit->time = strtol((char *)buffer + 2, NULL, 10); - if (commit->time == 0) + if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < GIT_SUCCESS) return GIT_EOBJCORRUPTED; + commit->time = (time_t)commit_time; commit->parsed = 1; return GIT_SUCCESS; } diff --git a/src/signature.c b/src/signature.c index 0c99755d4..e8014620a 100644 --- a/src/signature.c +++ b/src/signature.c @@ -65,14 +65,47 @@ git_signature *git_signature_dup(const git_signature *sig) return git_signature_new(sig->name, sig->email, sig->when.time, sig->when.offset); } - -static int parse_timezone_offset(const char *buffer, int *offset_out) +git_signature *git_signature_now(const char *name, const char *email) { - int offset, dec_offset; + time_t now; + time_t offset; + struct tm *utc_tm, *local_tm; + +#ifndef GIT_WIN32 + struct tm _utc, _local; +#endif + + time(&now); + + /** + * On Win32, `gmtime_r` doesn't exist but + * `gmtime` is threadsafe, so we can use that + */ +#ifdef GIT_WIN32 + utc_tm = gmtime(&now); + local_tm = localtime(&now); +#else + utc_tm = gmtime_r(&now, &_utc); + local_tm = localtime_r(&now, &_local); +#endif + + offset = mktime(local_tm) - mktime(utc_tm); + offset /= 60; + + /* mktime takes care of setting tm_isdst correctly */ + if (local_tm->tm_isdst) + offset += 60; + + return git_signature_new(name, email, now, (int)offset); +} + +static int parse_timezone_offset(const char *buffer, long *offset_out) +{ + long offset, dec_offset; int mins, hours; - const char* offset_start; - char* offset_end; + const char *offset_start; + const char *offset_end; offset_start = buffer + 1; @@ -84,7 +117,8 @@ static int parse_timezone_offset(const char *buffer, int *offset_out) if (offset_start[0] != '-' && offset_start[0] != '+') return GIT_EOBJCORRUPTED; - dec_offset = strtol(offset_start + 1, &offset_end, 10); + if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < GIT_SUCCESS) + return GIT_EOBJCORRUPTED; if (offset_end - offset_start != 5) return GIT_EOBJCORRUPTED; @@ -117,7 +151,7 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, int name_length, email_length; const char *buffer = *buffer_out; const char *line_end, *name_end, *email_end; - int offset = 0; + long offset = 0, time; memset(sig, 0x0, sizeof(git_signature)); @@ -139,6 +173,9 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, name_length = name_end - buffer - 1; sig->name = git__malloc(name_length + 1); + if (sig->name == NULL) + return GIT_ENOMEM; + memcpy(sig->name, buffer, name_length); sig->name[name_length] = 0; buffer = name_end + 1; @@ -152,6 +189,9 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, email_length = email_end - buffer; sig->email = git__malloc(email_length + 1); + if (sig->name == NULL) + return GIT_ENOMEM; + memcpy(sig->email, buffer, email_length); sig->email[email_length] = 0; buffer = email_end + 1; @@ -159,11 +199,11 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, if (buffer >= line_end) return GIT_EOBJCORRUPTED; - sig->when.time = strtol(buffer, (char **)&buffer, 10); - - if (sig->when.time == 0) + if (git__strtol32(&time, buffer, &buffer, 10) < GIT_SUCCESS) return GIT_EOBJCORRUPTED; + sig->when.time = (time_t)time; + if (parse_timezone_offset(buffer, &offset) < GIT_SUCCESS) return GIT_EOBJCORRUPTED; diff --git a/src/tag.c b/src/tag.c index ef58e0982..a3400957f 100644 --- a/src/tag.c +++ b/src/tag.c @@ -131,12 +131,17 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer text_len = search - buffer; tag->tag_name = git__malloc(text_len + 1); + if (tag->tag_name == NULL) + return GIT_ENOMEM; + memcpy(tag->tag_name, buffer, text_len); tag->tag_name[text_len] = '\0'; buffer = search + 1; tag->tagger = git__malloc(sizeof(git_signature)); + if (tag->tagger == NULL) + return GIT_ENOMEM; if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ")) != 0) { free(tag->tag_name); @@ -147,6 +152,9 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer text_len = buffer_end - ++buffer; tag->message = git__malloc(text_len + 1); + if (tag->message == NULL) + return GIT_ENOMEM; + memcpy(tag->message, buffer, text_len); tag->message[text_len] = '\0'; @@ -209,6 +217,17 @@ static int tag_create( return error; } + if (!git_odb_exists(repo->db, target)) + return GIT_ENOTFOUND; + + /* Try to find out what the type is */ + if (target_type == GIT_OBJ_ANY) { + size_t _unused; + error = git_odb_read_header(&_unused, &target_type, repo->db, target); + if (error < GIT_SUCCESS) + return error; + } + type_str = git_object_type2string(target_type); tagger_str_len = git_signature__write(&tagger_str, "tagger", tagger); @@ -260,7 +279,6 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu { git_tag tag; int error; - git_object *obj; assert(oid && buffer); @@ -269,15 +287,8 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu if ((error = parse_tag_buffer(&tag, buffer, buffer + strlen(buffer))) < GIT_SUCCESS) return error; - error = git_object_lookup(&obj, repo, &tag.target, tag.type); - if (error < GIT_SUCCESS) - goto cleanup; + error = git_tag_create(oid, repo, tag.tag_name, &tag.target, tag.type, tag.tagger, tag.message); - error = git_tag_create_o(oid, repo, tag.tag_name, obj, tag.tagger, tag.message); - - git_object_close(obj); - -cleanup: git_signature_free(tag.tagger); free(tag.tag_name); free(tag.message); diff --git a/src/tree.c b/src/tree.c index 693ed1caa..64f81d780 100644 --- a/src/tree.c +++ b/src/tree.c @@ -150,7 +150,8 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf if (git_vector_insert(&tree->entries, entry) < GIT_SUCCESS) return GIT_ENOMEM; - entry->attr = strtol(buffer, (char **)&buffer, 8); + if (git__strtol32((long *)&entry->attr, buffer, &buffer, 8) < GIT_SUCCESS) + return GIT_EOBJCORRUPTED; if (*buffer++ != ' ') { error = GIT_EOBJCORRUPTED; @@ -364,7 +365,7 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con git_oid_cpy(&entry->oid, id); entry->attr = attributes; - if (pos != GIT_ENOTFOUND) { + if (pos == GIT_ENOTFOUND) { if (git_vector_insert(&bld->entries, entry) < 0) return GIT_ENOMEM; } diff --git a/src/util.c b/src/util.c index 995daf314..55a7ab2a9 100644 --- a/src/util.c +++ b/src/util.c @@ -2,6 +2,7 @@ #include "common.h" #include #include +#include void git_strarray_free(git_strarray *array) { @@ -12,6 +13,84 @@ void git_strarray_free(git_strarray *array) free(array->strings); } +int git__strtol32(long *result, const char *nptr, const char **endptr, int base) +{ + const char *p; + long n, nn; + int c, ovfl, v, neg, ndig; + + p = nptr; + neg = 0; + n = 0; + ndig = 0; + ovfl = 0; + + /* + * White space + */ + while (isspace(*p)) + p++; + + /* + * Sign + */ + if (*p == '-' || *p == '+') + if (*p++ == '-') + neg = 1; + + /* + * Base + */ + if (base == 0) { + if (*p != '0') + base = 10; + else { + base = 8; + if (p[1] == 'x' || p[1] == 'X') { + p += 2; + base = 16; + } + } + } else if (base == 16 && *p == '0') { + if (p[1] == 'x' || p[1] == 'X') + p += 2; + } else if (base < 0 || 36 < base) + goto Return; + + /* + * Non-empty sequence of digits + */ + for (;; p++,ndig++) { + c = *p; + v = base; + if ('0'<=c && c<='9') + v = c - '0'; + else if ('a'<=c && c<='z') + v = c - 'a' + 10; + else if ('A'<=c && c<='Z') + v = c - 'A' + 10; + if (v >= base) + break; + nn = n*base + v; + if (nn < n) + ovfl = 1; + n = nn; + } + +Return: + if (ndig == 0) + return GIT_ENOTNUM; + + if (endptr) + *endptr = p; + + if (ovfl) + return GIT_EOVERFLOW; + + *result = neg ? -n : n; + return GIT_SUCCESS; +} + int git__fmt(char *buf, size_t buf_sz, const char *fmt, ...) { va_list va; diff --git a/src/util.h b/src/util.h index f477b64fd..3c606493f 100644 --- a/src/util.h +++ b/src/util.h @@ -22,6 +22,8 @@ extern int git__fmt(char *, size_t, const char *, ...) extern int git__prefixcmp(const char *str, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); +extern int git__strtol32(long *n, const char *buff, const char **end_buf, int base); + /* * The dirname() function shall take a pointer to a character string * that contains a pathname, and return a pointer to a string that is a diff --git a/tests/resources/testrepo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/testrepo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 index 03770969a..697c94c92 100644 Binary files a/tests/resources/testrepo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 and b/tests/resources/testrepo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 differ diff --git a/tests/resources/testrepo.git/refs/tags/point_to_blob b/tests/resources/testrepo.git/refs/tags/point_to_blob new file mode 100644 index 000000000..f874a3ffc Binary files /dev/null and b/tests/resources/testrepo.git/refs/tags/point_to_blob differ diff --git a/tests/t00-core.c b/tests/t00-core.c index 4cb111428..ab9a683a7 100644 --- a/tests/t00-core.c +++ b/tests/t00-core.c @@ -233,7 +233,7 @@ BEGIN_TEST(path3, "prettify and validate a path to a file") must_fail(ensure_file_path_normalized("d1/s1///s2/..//../s3/", NULL, 0)); must_pass(ensure_file_path_normalized("d1/s1//../s2/../../d2", "d2", CWD_AS_PREFIX | PATH_AS_SUFFIX)); must_fail(ensure_file_path_normalized("dir/sub/../", NULL, 0)); - must_pass(ensure_file_path_normalized("../../a/../../b/c/d/../../e", "b/e", PATH_AS_SUFFIX)); + must_pass(ensure_file_path_normalized("../a/../b/c/d/../../e", "b/e", PATH_AS_SUFFIX)); must_fail(ensure_file_path_normalized("....", NULL, 0)); must_fail(ensure_file_path_normalized("...", NULL, 0)); must_fail(ensure_file_path_normalized("./...", NULL, 0)); @@ -309,7 +309,7 @@ BEGIN_TEST(path4, "validate and prettify a path to a folder") must_pass(ensure_dir_path_normalized("d1/s1///s2/..//../s3/", "d1/s3/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); must_pass(ensure_dir_path_normalized("d1/s1//../s2/../../d2", "d2/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); must_pass(ensure_dir_path_normalized("dir/sub/../", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("../../a/../../b/c/d/../../e", "b/e/", PATH_AS_SUFFIX)); + must_pass(ensure_dir_path_normalized("../a/../b/c/d/../../e", "b/e/", PATH_AS_SUFFIX)); must_fail(ensure_dir_path_normalized("....", NULL, 0)); must_fail(ensure_dir_path_normalized("...", NULL, 0)); must_fail(ensure_dir_path_normalized("./...", NULL, 0)); diff --git a/tests/t08-tag.c b/tests/t08-tag.c index e7016488f..de67fdd93 100644 --- a/tests/t08-tag.c +++ b/tests/t08-tag.c @@ -119,13 +119,10 @@ BEGIN_TEST(write0, "write a tag to the repository and read it again") END_TEST -BEGIN_TEST(write1, "write a tag to the repository which points to an unknown oid and read it again") +BEGIN_TEST(write1, "write a tag to the repository which points to an unknown oid should fail") git_repository *repo; - git_tag *tag; git_oid target_id, tag_id; const git_signature *tagger; - git_reference *ref_tag; - git_object *zombie; must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); @@ -135,7 +132,7 @@ BEGIN_TEST(write1, "write a tag to the repository which points to an unknown oid tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); must_be_true(tagger != NULL); - must_pass(git_tag_create( + must_fail(git_tag_create( &tag_id, /* out id */ repo, "the-zombie-tag", @@ -146,17 +143,6 @@ BEGIN_TEST(write1, "write a tag to the repository which points to an unknown oid git_signature_free((git_signature *)tagger); - must_pass(git_tag_lookup(&tag, repo, &tag_id)); - - /* The non existent target can not be looked up */ - must_fail(git_tag_target(&zombie, tag)); - - must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/the-zombie-tag")); - - must_pass(git_reference_delete(ref_tag)); - must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)tag)); - - git_tag_close(tag); git_repository_free(repo); END_TEST diff --git a/tests/t09-tree.c b/tests/t09-tree.c index e4252dbca..bd88642fa 100644 --- a/tests/t09-tree.c +++ b/tests/t09-tree.c @@ -29,6 +29,10 @@ static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; +static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92"; +static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7"; +static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1"; + #if 0 static int print_tree(git_repository *repo, const git_oid *tree_oid, int depth) { @@ -126,11 +130,33 @@ BEGIN_TEST(write0, "write a tree from an index") END_TEST #endif +BEGIN_TEST(write2, "write a tree from a memory") + git_repository *repo; + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid, rid, id2; + + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); + git_oid_mkstr(&id, first_tree); + git_oid_mkstr(&id2, second_tree); + git_oid_mkstr(&bid, blob_oid); + + //create a second tree from first tree using `git_treebuilder_insert` on REPOSITORY_FOLDER. + must_pass(git_tree_lookup(&tree, repo, &id)); + must_pass(git_treebuilder_create(&builder, tree)); + must_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); + must_pass(git_treebuilder_write(&rid,repo,builder)); + + must_be_true(git_oid_cmp(&rid, &id2) == 0); + close_temp_repo(repo); +END_TEST + BEGIN_SUITE(tree) //ADD_TEST(print0); ADD_TEST(read0); ADD_TEST(read1); //ADD_TEST(write0); //ADD_TEST(write1); + ADD_TEST(write2); END_SUITE diff --git a/tests/t10-refs.c b/tests/t10-refs.c index 2797cd8ac..a6a560193 100644 --- a/tests/t10-refs.c +++ b/tests/t10-refs.c @@ -456,6 +456,11 @@ BEGIN_TEST(pack1, "create a packfile from all the loose rn a repo") must_be_true((reference->type & GIT_REF_PACKED) == 0); must_be_true(strcmp(reference->name, loose_tag_ref_name) == 0); + /* + * We are now trying to pack also a loose reference + * called `points_to_blob`, to make sure we can properly + * pack weak tags + */ must_pass(git_reference_packall(repo)); /* Ensure the packed-refs file exists */ @@ -877,10 +882,10 @@ BEGIN_TEST(list0, "try to list all the references in our test repo") printf("# %s\n", ref_list.strings[i]); }*/ - /* We have exactly 7 refs in total if we include the packed ones: + /* We have exactly 8 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - must_be_true(ref_list.count == 7); + must_be_true(ref_list.count == 8); git_strarray_free(&ref_list); git_repository_free(repo); diff --git a/tests/t12-repo.c b/tests/t12-repo.c index adf20cfd7..70dba4255 100644 --- a/tests/t12-repo.c +++ b/tests/t12-repo.c @@ -194,6 +194,8 @@ BEGIN_TEST(open0, "Open a bare repository that has just been initialized by git" must_pass(remove_placeholders(TEMP_REPO_FOLDER, "dummy-marker.txt")); must_pass(git_repository_open(&repo, TEMP_REPO_FOLDER)); + must_be_true(git_repository_path(repo) != NULL); + must_be_true(git_repository_workdir(repo) == NULL); git_repository_free(repo); must_pass(rmdir_recurs(TEMP_REPO_FOLDER)); @@ -211,6 +213,8 @@ BEGIN_TEST(open1, "Open a standard repository that has just been initialized by must_pass(remove_placeholders(DEST_REPOSITORY_FOLDER, "dummy-marker.txt")); must_pass(git_repository_open(&repo, DEST_REPOSITORY_FOLDER)); + must_be_true(git_repository_path(repo) != NULL); + must_be_true(git_repository_workdir(repo) != NULL); git_repository_free(repo); must_pass(rmdir_recurs(TEMP_REPO_FOLDER)); @@ -244,6 +248,19 @@ BEGIN_TEST(open2, "Open a bare repository with a relative path escaping out of t rmdir_recurs(TEMP_REPO_FOLDER); END_TEST +BEGIN_TEST(empty0, "test if a repository is empty or not") + + git_repository *repo_empty, *repo_normal; + + must_pass(git_repository_open(&repo_normal, REPOSITORY_FOLDER)); + must_be_true(git_repository_is_empty(repo_normal) == 0); + git_repository_free(repo_normal); + + must_pass(git_repository_open(&repo_empty, EMPTY_BARE_REPOSITORY_FOLDER)); + must_be_true(git_repository_is_empty(repo_empty) == 1); + git_repository_free(repo_empty); +END_TEST + BEGIN_SUITE(repository) ADD_TEST(odb0); ADD_TEST(odb1); @@ -253,5 +270,6 @@ BEGIN_SUITE(repository) ADD_TEST(open0); ADD_TEST(open1); ADD_TEST(open2); + ADD_TEST(empty0); END_SUITE diff --git a/tests/t14-hiredis.c b/tests/t14-hiredis.c new file mode 100644 index 000000000..c743f7d48 --- /dev/null +++ b/tests/t14-hiredis.c @@ -0,0 +1,123 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "test_lib.h" +#include "odb.h" + +#ifdef GIT2_HIREDIS_BACKEND +#include "t03-data.h" +#include "fileops.h" +#include "git2/odb_backend.h" + + +static int cmp_objects(git_odb_object *odb_obj, git_rawobj *raw) +{ + if (raw->type != git_odb_object_type(odb_obj)) + return -1; + + if (raw->len != git_odb_object_size(odb_obj)) + return -1; + + if ((raw->len > 0) && (memcmp(raw->data, git_odb_object_data(odb_obj), raw->len) != 0)) + return -1; + + return 0; +} + +static git_odb *open_hiredis_odb(void) +{ + git_odb *odb; + git_odb_backend *hiredis; + + if (git_odb_new(&odb) < GIT_SUCCESS) + return NULL; + + if (git_odb_backend_hiredis(&hiredis, "127.0.0.1", 6379) < GIT_SUCCESS) + return NULL; + + if (git_odb_add_backend(odb, hiredis, 0) < GIT_SUCCESS) + return NULL; + + return odb; +} + +#define TEST_WRITE(PTR) {\ + git_odb *db; \ + git_oid id1, id2; \ + git_odb_object *obj; \ + db = open_hiredis_odb(); \ + must_be_true(db != NULL); \ + must_pass(git_oid_mkstr(&id1, PTR.id)); \ + must_pass(git_odb_write(&id2, db, PTR##_obj.data, PTR##_obj.len, PTR##_obj.type)); \ + must_be_true(git_oid_cmp(&id1, &id2) == 0); \ + must_pass(git_odb_read(&obj, db, &id1)); \ + must_pass(cmp_objects(obj, &PTR##_obj)); \ + git_odb_object_close(obj); \ + git_odb_close(db); \ +} + +BEGIN_TEST(hiredis0, "write a commit, read it back (hiredis backend)") + TEST_WRITE(commit); +END_TEST + +BEGIN_TEST(hiredis1, "write a tree, read it back (hiredis backend)") + TEST_WRITE(tree); +END_TEST + +BEGIN_TEST(hiredis2, "write a tag, read it back (hiredis backend)") + TEST_WRITE(tag); +END_TEST + +BEGIN_TEST(hiredis3, "write a zero-byte entry, read it back (hiredis backend)") + TEST_WRITE(zero); +END_TEST + +BEGIN_TEST(hiredis4, "write a one-byte entry, read it back (hiredis backend)") + TEST_WRITE(one); +END_TEST + +BEGIN_TEST(hiredis5, "write a two-byte entry, read it back (hiredis backend)") + TEST_WRITE(two); +END_TEST + +BEGIN_TEST(hiredis6, "write some bytes in an entry, read it back (hiredis backend)") + TEST_WRITE(some); +END_TEST + + +BEGIN_SUITE(hiredis) + ADD_TEST(hiredis0); + ADD_TEST(hiredis1); + ADD_TEST(hiredis2); + ADD_TEST(hiredis3); + ADD_TEST(hiredis4); + ADD_TEST(hiredis5); + ADD_TEST(hiredis6); +END_SUITE + +#else /* no hiredis builtin */ +BEGIN_SUITE(hiredis) + /* empty */ +END_SUITE +#endif diff --git a/tests/t14-config.c b/tests/t15-config.c similarity index 100% rename from tests/t14-config.c rename to tests/t15-config.c diff --git a/tests/test_main.c b/tests/test_main.c index c99722e80..0f5f16a26 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -41,6 +41,7 @@ DECLARE_SUITE(tag); DECLARE_SUITE(tree); DECLARE_SUITE(refs); DECLARE_SUITE(sqlite); +DECLARE_SUITE(hiredis); DECLARE_SUITE(repository); DECLARE_SUITE(threads); DECLARE_SUITE(config); @@ -60,6 +61,7 @@ static libgit2_suite suite_methods[]= { SUITE_NAME(sqlite), SUITE_NAME(repository), SUITE_NAME(threads), + SUITE_NAME(hiredis), SUITE_NAME(config), }; diff --git a/wscript b/wscript index dd9040658..f0b81a023 100644 --- a/wscript +++ b/wscript @@ -1,3 +1,4 @@ +from __future__ import with_statement from waflib.Context import Context from waflib.Build import BuildContext, CleanContext, \ InstallContext, UninstallContext @@ -16,7 +17,7 @@ CFLAGS_WIN32_L = ['/RELEASE'] # used for /both/ debug and release builds. # sets the module's checksum in the header. CFLAGS_WIN32_L_DBG = ['/DEBUG'] -ALL_LIBS = ['crypto', 'pthread', 'sqlite3'] +ALL_LIBS = ['crypto', 'pthread', 'sqlite3', 'hiredis'] def options(opt): opt.load('compiler_c') @@ -31,6 +32,8 @@ PPC optimized version (ppc) or the SHA1 functions from OpenSSL (openssl)") help='Select target architecture (ia64, x64, x86, x86_amd64, x86_ia64)') opt.add_option('--with-sqlite', action='store_true', default=False, dest='use_sqlite', help='Enable sqlite support') + opt.add_option('--with-hiredis', action='store_true', default=False, + dest='use_hiredis', help='Enable redis support using hiredis') opt.add_option('--threadsafe', action='store_true', default=False, help='Make libgit2 thread-safe (requires pthreads)') @@ -72,6 +75,12 @@ def configure(conf): lib='sqlite3', uselib_store='sqlite3', install_path=None, mandatory=False): conf.env.DEFINES += ['GIT2_SQLITE_BACKEND'] + # check for hiredis + if conf.options.use_hiredis and conf.check_cc( + lib='hiredis', uselib_store='hiredis', install_path=None, mandatory=False): + conf.env.DEFINES += ['GIT2_HIREDIS_BACKEND'] + + if conf.options.sha1 not in ['openssl', 'ppc', 'builtin']: conf.fatal('Invalid SHA1 option')