diff options
Diffstat (limited to 'src/refdb_fs.c')
-rw-r--r-- | src/refdb_fs.c | 365 |
1 files changed, 362 insertions, 3 deletions
diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 0ba711eb7..7ce09ba55 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -16,12 +16,14 @@ #include "refdb_fs.h" #include "iterator.h" #include "sortedcache.h" +#include "signature.h" #include <git2/tag.h> #include <git2/object.h> #include <git2/refdb.h> #include <git2/sys/refdb_backend.h> #include <git2/sys/refs.h> +#include <git2/sys/reflog.h> GIT__USE_STRMAP; @@ -56,6 +58,8 @@ typedef struct refdb_fs_backend { git_sortedcache *refcache; int peeling_mode; + git_iterator_flag_t iterator_flags; + uint32_t direach_flags; } refdb_fs_backend; static int packref_cmp(const void *a_, const void *b_) @@ -269,7 +273,8 @@ static int _dirent_loose_load(void *data, git_buf *full_path) return 0; if (git_path_isdir(full_path->ptr)) - return git_path_direach(full_path, _dirent_loose_load, backend); + return git_path_direach( + full_path, backend->direach_flags, _dirent_loose_load, backend); file_path = full_path->ptr + strlen(backend->path); @@ -295,7 +300,8 @@ static int packed_loadloose(refdb_fs_backend *backend) * This will overwrite any old packed entries with their * updated loose versions */ - error = git_path_direach(&refs_path, _dirent_loose_load, backend); + error = git_path_direach( + &refs_path, backend->direach_flags, _dirent_loose_load, backend); git_buf_free(&refs_path); @@ -468,7 +474,7 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 || (error = git_iterator_for_filesystem( - &fsit, git_buf_cstr(&path), 0, NULL, NULL)) < 0) { + &fsit, path.ptr, backend->iterator_flags, NULL, NULL)) < 0) { git_buf_free(&path); return error; } @@ -1067,10 +1073,350 @@ static int setup_namespace(git_buf *path, git_repository *repo) return 0; } +static int reflog_alloc(git_reflog **reflog, const char *name) +{ + git_reflog *log; + + *reflog = NULL; + + log = git__calloc(1, sizeof(git_reflog)); + GITERR_CHECK_ALLOC(log); + + log->ref_name = git__strdup(name); + GITERR_CHECK_ALLOC(log->ref_name); + + if (git_vector_init(&log->entries, 0, NULL) < 0) { + git__free(log->ref_name); + git__free(log); + return -1; + } + + *reflog = log; + + return 0; +} + +static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) +{ + const char *ptr; + git_reflog_entry *entry; + +#define seek_forward(_increase) do { \ + if (_increase >= buf_size) { \ + giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \ + goto fail; \ + } \ + buf += _increase; \ + buf_size -= _increase; \ + } while (0) + + while (buf_size > GIT_REFLOG_SIZE_MIN) { + entry = git__calloc(1, sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(entry); + + entry->committer = git__malloc(sizeof(git_signature)); + GITERR_CHECK_ALLOC(entry->committer); + + if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0) + goto fail; + seek_forward(GIT_OID_HEXSZ + 1); + + if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0) + goto fail; + seek_forward(GIT_OID_HEXSZ + 1); + + ptr = buf; + + /* Seek forward to the end of the signature. */ + while (*buf && *buf != '\t' && *buf != '\n') + seek_forward(1); + + if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0) + goto fail; + + if (*buf == '\t') { + /* We got a message. Read everything till we reach LF. */ + seek_forward(1); + ptr = buf; + + while (*buf && *buf != '\n') + seek_forward(1); + + entry->msg = git__strndup(ptr, buf - ptr); + GITERR_CHECK_ALLOC(entry->msg); + } else + entry->msg = NULL; + + while (*buf && *buf == '\n' && buf_size > 1) + seek_forward(1); + + if (git_vector_insert(&log->entries, entry) < 0) + goto fail; + } + + return 0; + +#undef seek_forward + +fail: + if (entry) + git_reflog_entry__free(entry); + + return -1; +} + +static int create_new_reflog_file(const char *filepath) +{ + int fd, error; + + if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + return error; + + if ((fd = p_open(filepath, + O_WRONLY | O_CREAT | O_TRUNC, + GIT_REFLOG_FILE_MODE)) < 0) + return -1; + + return p_close(fd); +} + +GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name) +{ + return git_buf_join_n(path, '/', 3, repo->path_repository, GIT_REFLOG_DIR, name); +} + +static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name) +{ + int error = -1; + git_buf log_path = GIT_BUF_INIT; + git_buf log_file = GIT_BUF_INIT; + git_reflog *log = NULL; + git_repository *repo; + refdb_fs_backend *backend; + + assert(out && _backend && name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if (reflog_alloc(&log, name) < 0) + return -1; + + if (retrieve_reflog_path(&log_path, repo, name) < 0) + goto cleanup; + + error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path)); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if ((error == GIT_ENOTFOUND) && + ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0)) + goto cleanup; + + if ((error = reflog_parse(log, + git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) + goto cleanup; + + *out = log; + goto success; + +cleanup: + git_reflog_free(log); + +success: + git_buf_free(&log_file); + git_buf_free(&log_path); + + return error; +} + +static int serialize_reflog_entry( + git_buf *buf, + const git_oid *oid_old, + const git_oid *oid_new, + const git_signature *committer, + const char *msg) +{ + char raw_old[GIT_OID_HEXSZ+1]; + char raw_new[GIT_OID_HEXSZ+1]; + + git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); + git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); + + git_buf_clear(buf); + + git_buf_puts(buf, raw_old); + git_buf_putc(buf, ' '); + git_buf_puts(buf, raw_new); + + git_signature__writebuf(buf, " ", committer); + + /* drop trailing LF */ + git_buf_rtrim(buf); + + if (msg) { + git_buf_putc(buf, '\t'); + git_buf_puts(buf, msg); + } + + git_buf_putc(buf, '\n'); + + return git_buf_oom(buf); +} + +static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog) +{ + int error = -1; + unsigned int i; + git_reflog_entry *entry; + git_repository *repo; + refdb_fs_backend *backend; + git_buf log_path = GIT_BUF_INIT; + git_buf log = GIT_BUF_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + + assert(_backend && reflog); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if (retrieve_reflog_path(&log_path, repo, reflog->ref_name) < 0) + return -1; + + if (!git_path_isfile(git_buf_cstr(&log_path))) { + giterr_set(GITERR_INVALID, + "Log file for reference '%s' doesn't exist.", reflog->ref_name); + goto cleanup; + } + + if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0) + goto cleanup; + + git_vector_foreach(&reflog->entries, i, entry) { + if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: + git_buf_free(&log); + git_buf_free(&log_path); + return error; +} + +static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) +{ + int error = 0, fd; + git_buf old_path = GIT_BUF_INIT; + git_buf new_path = GIT_BUF_INIT; + git_buf temp_path = GIT_BUF_INIT; + git_buf normalized = GIT_BUF_INIT; + git_repository *repo; + refdb_fs_backend *backend; + + assert(_backend && old_name && new_name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if ((error = git_reference__normalize_name( + &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0) + return error; + + if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0) + return -1; + + if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0) + return -1; + + if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0) + return -1; + + /* + * Move the reflog to a temporary place. This two-phase renaming is required + * in order to cope with funny renaming use cases when one tries to move a reference + * to a partially colliding namespace: + * - a/b -> a/b/c + * - a/b/c/d -> a/b/c + */ + if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0) + return -1; + + if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path))) < 0) { + error = -1; + goto cleanup; + } + + p_close(fd); + + if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) { + giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); + error = -1; + goto cleanup; + } + + if (git_path_isdir(git_buf_cstr(&new_path)) && + (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { + error = -1; + goto cleanup; + } + + if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { + error = -1; + goto cleanup; + } + + if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) { + giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); + error = -1; + } + +cleanup: + git_buf_free(&temp_path); + git_buf_free(&old_path); + git_buf_free(&new_path); + git_buf_free(&normalized); + + return error; +} + +static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name) +{ + int error; + git_buf path = GIT_BUF_INIT; + + git_repository *repo; + refdb_fs_backend *backend; + + assert(_backend && name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + error = retrieve_reflog_path(&path, repo, name); + + if (!error && git_path_exists(path.ptr)) + error = p_unlink(path.ptr); + + git_buf_free(&path); + + return error; + +} + int git_refdb_backend_fs( git_refdb_backend **backend_out, git_repository *repository) { + int t = 0; git_buf path = GIT_BUF_INIT; refdb_fs_backend *backend; @@ -1092,6 +1438,15 @@ int git_refdb_backend_fs( git_buf_free(&path); + if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) { + backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; + backend->direach_flags |= GIT_PATH_DIR_IGNORE_CASE; + } + if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) { + backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE; + } + backend->parent.exists = &refdb_fs_backend__exists; backend->parent.lookup = &refdb_fs_backend__lookup; backend->parent.iterator = &refdb_fs_backend__iterator; @@ -1100,6 +1455,10 @@ int git_refdb_backend_fs( backend->parent.rename = &refdb_fs_backend__rename; backend->parent.compress = &refdb_fs_backend__compress; backend->parent.free = &refdb_fs_backend__free; + backend->parent.reflog_read = &refdb_reflog_fs__read; + backend->parent.reflog_write = &refdb_reflog_fs__write; + backend->parent.reflog_rename = &refdb_reflog_fs__rename; + backend->parent.reflog_delete = &refdb_reflog_fs__delete; *backend_out = (git_refdb_backend *)backend; return 0; |