/* * 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 "refs.h" #include "hash.h" #include "repository.h" #include "fileops.h" #define MAX_NESTING_LEVEL 5 static const int default_table_size = 32; static uint32_t reftable_hash(const void *key, int hash_id) { static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { 2147483647, 0x5d20bb23, 0x7daaab3c }; return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); } static int check_refname(const char *name) { /* * TODO: To be implemented * Check if the given name is a valid name * for a reference */ return name ? GIT_SUCCESS : GIT_ERROR; } static void reference_free(git_reference *reference) { if (reference == NULL) return; if (reference->name) free(reference->name); if (reference->type == GIT_REF_SYMBOLIC) free(reference->target.ref); free(reference); } int git_reference_new(git_reference **ref_out, git_repository *repo) { git_reference *reference = NULL; assert(ref_out && repo); reference = git__malloc(sizeof(git_reference)); if (reference == NULL) return GIT_ENOMEM; memset(reference, 0x0, sizeof(git_reference)); reference->type = GIT_REF_INVALID; reference->owner = repo; *ref_out = reference; return GIT_SUCCESS; } static int parse_sym_ref(git_reference *ref, gitfo_buf *file_content) { const unsigned int header_len = strlen(GIT_SYMREF); const char *refname_start; char *eol; refname_start = (const char *)file_content->data; if (file_content->len < (header_len + 1)) return GIT_EREFCORRUPTED; /* * Assume we have already checked for the header * before calling this function */ refname_start += header_len; ref->target.ref = git__strdup(refname_start); if (ref->target.ref == NULL) return GIT_ENOMEM; /* remove newline at the end of file */ eol = strchr(ref->target.ref, '\n'); if (eol == NULL) return GIT_EREFCORRUPTED; *eol = '\0'; if (eol[-1] == '\r') eol[-1] = '\0'; ref->type = GIT_REF_SYMBOLIC; return GIT_SUCCESS; } static int parse_oid_ref(git_reference *ref, gitfo_buf *file_content) { char *buffer; buffer = (char *)file_content->data; /* File format: 40 chars (OID) + newline */ if (file_content->len < GIT_OID_HEXSZ + 1) return GIT_EREFCORRUPTED; if (git_oid_mkstr(&ref->target.oid, buffer) < GIT_SUCCESS) return GIT_EREFCORRUPTED; buffer = buffer + GIT_OID_HEXSZ; if (*buffer == '\r') buffer++; if (*buffer != '\n') return GIT_EREFCORRUPTED; ref->type = GIT_REF_OID; return GIT_SUCCESS; } static int read_loose_ref(gitfo_buf *file_content, const char *name, const char *repo_path) { int error = GIT_SUCCESS; char ref_path[GIT_PATH_MAX]; /* Determine the full path of the ref */ git__joinpath(ref_path, repo_path, name); /* Does it even exist ? */ if (gitfo_exists(ref_path) < GIT_SUCCESS) return GIT_ENOTFOUND; /* A ref can not be a directory */ if (!gitfo_isdir(ref_path)) return GIT_ENOTFOUND; if (file_content != NULL) error = gitfo_read_file(file_content, ref_path); return error; } static int lookup_loose_ref( git_reference **ref_out, git_repository *repo, const char *name) { int error = GIT_SUCCESS; gitfo_buf ref_file = GITFO_BUF_INIT; git_reference *ref = NULL; *ref_out = NULL; error = read_loose_ref(&ref_file, name, repo->path_repository); if (error < GIT_SUCCESS) goto cleanup; error = git_reference_new(&ref, repo); if (error < GIT_SUCCESS) goto cleanup; ref->name = git__strdup(name); if (ref->name == NULL) { error = GIT_ENOMEM; goto cleanup; } if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) error = parse_sym_ref(ref, &ref_file); else error = parse_oid_ref(ref, &ref_file); if (error < GIT_SUCCESS) goto cleanup; error = git_hashtable_insert(repo->references.cache, ref->name, ref); if (error < GIT_SUCCESS) goto cleanup; *ref_out = ref; return GIT_SUCCESS; cleanup: gitfo_free_buf(&ref_file); reference_free(ref); return error; } static int read_packed_refs(gitfo_buf *packfile, const char *repo_path) { char ref_path[GIT_PATH_MAX]; /* Determine the full path of the file */ git__joinpath(ref_path, repo_path, GIT_PACKEDREFS_FILE); /* Does it even exist ? */ if (gitfo_exists(ref_path) < GIT_SUCCESS) return GIT_ENOTFOUND; return gitfo_read_file(packfile, ref_path); } static int parse_packed_line_peel( git_reference **ref_out, const git_reference *tag_ref, const char **buffer_out, const char *buffer_end) { git_oid oid; const char *buffer = *buffer_out + 1; assert(buffer[-1] == '^'); /* Ensure it's not the first entry of the file */ if (tag_ref == NULL) return GIT_EPACKEDREFSCORRUPTED; /* Ensure reference is a tag */ if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) return GIT_EPACKEDREFSCORRUPTED; if (buffer + GIT_OID_HEXSZ >= buffer_end) return GIT_EPACKEDREFSCORRUPTED; /* Is this a valid object id? */ if (git_oid_mkstr(&oid, buffer) < GIT_SUCCESS) return GIT_EPACKEDREFSCORRUPTED; buffer = buffer + GIT_OID_HEXSZ; if (*buffer == '\r') buffer++; if (*buffer != '\n') return GIT_EPACKEDREFSCORRUPTED; *buffer_out = buffer + 1; /* * TODO: do we need the packed line? * Right now we don't, so we don't create a new * reference. */ *ref_out = NULL; return GIT_SUCCESS; } static int parse_packed_line( git_reference **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end) { git_reference *ref; const char *buffer = *buffer_out; const char *refname_begin, *refname_end; int error = GIT_SUCCESS; int refname_len; error = git_reference_new(&ref, repo); if (error < GIT_SUCCESS) goto cleanup; refname_begin = (buffer + GIT_OID_HEXSZ + 1); if (refname_begin >= buffer_end || refname_begin[-1] != ' ') { error = GIT_EPACKEDREFSCORRUPTED; goto cleanup; } /* Is this a valid object id? */ if ((error = git_oid_mkstr(&ref->target.oid, buffer)) < GIT_SUCCESS) goto cleanup; refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); if (refname_end == NULL) { error = GIT_EPACKEDREFSCORRUPTED; goto cleanup; } refname_len = refname_end - refname_begin; ref->name = git__malloc(refname_len + 1); if (ref->name == NULL) { error = GIT_ENOMEM; goto cleanup; } memcpy(ref->name, refname_begin, refname_len); ref->name[refname_len] = 0; if (ref->name[refname_len - 1] == '\r') ref->name[refname_len - 1] = 0; ref->type = GIT_REF_OID; ref->packed = 1; *ref_out = ref; *buffer_out = refname_end + 1; return GIT_SUCCESS; cleanup: reference_free(ref); return error; } static int parse_packed_refs(git_refcache *ref_cache, git_repository *repo) { int error = GIT_SUCCESS; gitfo_buf packfile = GITFO_BUF_INIT; const char *buffer_start, *buffer_end; error = read_packed_refs(&packfile, repo->path_repository); if (error < GIT_SUCCESS) goto cleanup; buffer_start = (const char *)packfile.data; buffer_end = (const char *)(buffer_start) + packfile.len; /* Does the header look like valid? */ if (git__prefixcmp((const char *)(buffer_start), GIT_PACKEDREFS_HEADER)) { error = GIT_EPACKEDREFSCORRUPTED; goto cleanup; } /* Let's skip the header */ buffer_start += strlen(GIT_PACKEDREFS_HEADER); if (*buffer_start == '\r') buffer_start++; if (*buffer_start != '\n') return GIT_EPACKEDREFSCORRUPTED; buffer_start++; while (buffer_start < buffer_end) { git_reference *ref = NULL; git_reference *ref_tag = NULL; error = parse_packed_line(&ref, repo, &buffer_start, buffer_end); if (error < GIT_SUCCESS) goto cleanup; if (buffer_start[0] == '^') { error = parse_packed_line_peel(&ref_tag, ref, &buffer_start, buffer_end); if (error < GIT_SUCCESS) goto cleanup; } /* * If a loose reference exists with the same name, * we assume that the loose reference is more up-to-date. * We don't need to cache this ref from the packfile. */ if (read_loose_ref(NULL, ref->name, repo->path_repository) == GIT_SUCCESS) { reference_free(ref); reference_free(ref_tag); continue; } error = git_hashtable_insert(ref_cache->cache, ref->name, ref); if (error < GIT_SUCCESS) { reference_free(ref); reference_free(ref_tag); goto cleanup; } } ref_cache->pack_loaded = 1; cleanup: gitfo_free_buf(&packfile); return error; } void git_reference_set_oid(git_reference *ref, const git_oid *id) { if (ref->type == GIT_REF_SYMBOLIC) free(ref->target.ref); git_oid_cpy(&ref->target.oid, id); ref->type = GIT_REF_OID; ref->modified = 1; } void git_reference_set_target(git_reference *ref, const char *target) { if (ref->type == GIT_REF_SYMBOLIC) free(ref->target.ref); ref->target.ref = git__strdup(target); ref->type = GIT_REF_SYMBOLIC; ref->modified = 1; } void git_reference_set_name(git_reference *ref, const char *name) { if (ref->name != NULL) { git_hashtable_remove(ref->owner->references.cache, ref->name); free(ref->name); } ref->name = git__strdup(name); git_hashtable_insert(ref->owner->references.cache, ref->name, ref); ref->modified = 1; } const git_oid *git_reference_oid(git_reference *ref) { assert(ref); if (ref->type != GIT_REF_OID) return NULL; return &ref->target.oid; } const char *git_reference_target(git_reference *ref) { if (ref->type != GIT_REF_SYMBOLIC) return NULL; return ref->target.ref; } git_rtype git_reference_type(git_reference *ref) { assert(ref); return ref->type; } const char *git_reference_name(git_reference *ref) { assert(ref); return ref->name; } git_repository *git_reference_owner(git_reference *ref) { assert(ref); return ref->owner; } int git_reference_resolve(git_reference **resolved_ref, git_reference *ref) { git_repository *repo; int error, i; assert(resolved_ref && ref); *resolved_ref = NULL; repo = ref->owner; for (i = 0; i < MAX_NESTING_LEVEL; ++i) { if (ref->type == GIT_REF_OID) { *resolved_ref = ref; return GIT_SUCCESS; } if ((error = git_repository_lookup_ref(&ref, repo, ref->target.ref)) < GIT_SUCCESS) return error; } return GIT_ETOONESTEDSYMREF; } int git_reference_write(git_reference *ref) { git_filebuf file; char ref_path[GIT_PATH_MAX]; int error, contents_size; char *ref_contents = NULL; if (ref->type == GIT_REF_INVALID || ref->name == NULL) return GIT_EMISSINGOBJDATA; if (ref->modified == 0) return GIT_SUCCESS; if ((error = check_refname(ref->name)) < GIT_SUCCESS) return error; git__joinpath(ref_path, ref->owner->path_repository, ref->name); if ((error = git_filebuf_open(&file, ref_path, 0)) < GIT_SUCCESS) goto error_cleanup; if (ref->type == GIT_REF_OID) { contents_size = GIT_OID_HEXSZ + 1; ref_contents = git__malloc(contents_size); if (ref_contents == NULL) { error = GIT_ENOMEM; goto error_cleanup; } git_oid_fmt(ref_contents, &ref->target.oid); ref_contents[contents_size - 1] = '\n'; } else { /* GIT_REF_SYMBOLIC */ contents_size = strlen(GIT_SYMREF) + strlen(ref->target.ref) + 1; ref_contents = git__malloc(contents_size); if (ref_contents == NULL) { error = GIT_ENOMEM; goto error_cleanup; } strcpy(ref_contents, GIT_SYMREF); strcat(ref_contents, ref->target.ref); ref_contents[contents_size - 1] = '\n'; } if ((error = git_filebuf_write(&file, ref_contents, contents_size)) < GIT_SUCCESS) goto error_cleanup; free(ref_contents); error = git_filebuf_commit(&file); if (error == GIT_SUCCESS) ref->modified = 0; return error; error_cleanup: free(ref_contents); git_filebuf_cleanup(&file); return error; } int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, const char *name) { int error; assert(ref_out && repo && name); *ref_out = NULL; error = check_refname(name); if (error < GIT_SUCCESS) return error; /* * First, check if the reference is on the local cache; * references on the cache are assured to be up-to-date */ *ref_out = git_hashtable_lookup(repo->references.cache, name); if (*ref_out != NULL) return GIT_SUCCESS; /* * Then check if there is a loose file for that reference. * If the file exists, we parse it and store it on the * cache. */ error = lookup_loose_ref(ref_out, repo, name); if (error == GIT_SUCCESS) return GIT_SUCCESS; if (error != GIT_ENOTFOUND) return error; /* * Check if we have loaded the packed references. * If the packed references have been loaded, they would be * stored already on the cache: that means that the ref * we are looking for doesn't exist. * * If they haven't been loaded yet, we load the packfile * and check if our reference is inside of it. */ if (!repo->references.pack_loaded) { /* load all the packed references */ error = parse_packed_refs(&repo->references, repo); if (error < GIT_SUCCESS) return error; /* check the cache again -- hopefully the reference will be there */ *ref_out = git_hashtable_lookup(repo->references.cache, name); if (*ref_out != NULL) return GIT_SUCCESS; } /* The reference doesn't exist anywhere */ return GIT_ENOTFOUND; } int git_repository__refcache_init(git_refcache *refs) { assert(refs); refs->cache = git_hashtable_alloc( default_table_size, reftable_hash, (git_hash_keyeq_ptr)strcmp); return refs->cache ? GIT_SUCCESS : GIT_ENOMEM; } void git_repository__refcache_free(git_refcache *refs) { const char *ref_name; git_reference *reference; assert(refs); GIT_HASHTABLE_FOREACH(refs->cache, ref_name, reference, reference_free(reference) ); git_hashtable_free(refs->cache); }