From b976f3c2c228413d124be8fea3280a44bd5e3136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 19 Aug 2013 13:01:49 +0200 Subject: [PATCH] reflog: move the reflog implementation into refdb_fs References and their logs are logically coupled, let's make it so in the code by moving the fs-based reflog implementation to live next to the fs-based refs one. As part of the change, make the function take names rather than references, as only the names are relevant when looking up and handling reflogs. --- include/git2/reflog.h | 19 +- include/git2/sys/refdb_backend.h | 32 +++ include/git2/sys/reflog.h | 21 ++ src/refdb.c | 16 ++ src/refdb.h | 4 + src/refdb_fs.c | 448 +++++++++++++++++++++++++++++++ src/reflog.c | 447 +++--------------------------- src/reflog.h | 7 +- src/refs.c | 2 +- src/revparse.c | 4 +- src/stash.c | 11 +- tests-clar/refs/reflog/drop.c | 13 +- tests-clar/refs/reflog/reflog.c | 23 +- tests-clar/stash/drop.c | 4 +- 14 files changed, 600 insertions(+), 451 deletions(-) create mode 100644 include/git2/sys/reflog.h diff --git a/include/git2/reflog.h b/include/git2/reflog.h index 4944530af..b31472c37 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -31,10 +31,11 @@ GIT_BEGIN_DECL * git_reflog_free(). * * @param out pointer to reflog - * @param ref reference to read the reflog for + * @param repo the repostiory + * @param name reference to look up * @return 0 or an error code */ -GIT_EXTERN(int) git_reflog_read(git_reflog **out, const git_reference *ref); +GIT_EXTERN(int) git_reflog_read(git_reflog **out, git_repository *repo, const char *name); /** * Write an existing in-memory reflog object back to disk @@ -59,26 +60,28 @@ GIT_EXTERN(int) git_reflog_write(git_reflog *reflog); GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *id, const git_signature *committer, const char *msg); /** - * Rename the reflog for the given reference + * Rename a reflog * * The reflog to be renamed is expected to already exist * * The new name will be checked for validity. * See `git_reference_create_symbolic()` for rules about valid names. * - * @param ref the reference - * @param name the new name of the reference + * @param repo the repository + * @param old_name the old name of the reference + * @param new_name the new name of the reference * @return 0 on success, GIT_EINVALIDSPEC or an error code */ -GIT_EXTERN(int) git_reflog_rename(git_reference *ref, const char *name); +GIT_EXTERN(int) git_reflog_rename(git_repository *repo, const char *old_name, const char *name); /** * Delete the reflog for the given reference * - * @param ref the reference + * @param repo the repository + * @param name the reflog to delete * @return 0 or an error code */ -GIT_EXTERN(int) git_reflog_delete(git_reference *ref); +GIT_EXTERN(int) git_reflog_delete(git_repository *repo, const char *name); /** * Get the number of log entries in a reflog diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index addaa86fd..93c4a521a 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -119,6 +119,38 @@ struct git_refdb_backend { * provide this function; if it is not provided, nothing will be done. */ void (*free)(git_refdb_backend *backend); + + /** + * Read the reflog for the given reference name. + */ + int (*reflog_read)(git_reflog **out, git_refdb_backend *backend, const char *name); + + /** + * Write a reflog to disk. + */ + int (*reflog_write)(git_refdb_backend *backend, git_reflog *reflog); + + /** + * Append an entry to the given reflog + */ + int (*reflog_append)(git_refdb_backend *backend, git_reflog *reflog, + const git_oid *new_oid, const git_signature *committer, + const char *msg); + + /** + * Rename a reflog + */ + int (*reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name); + + /** + * Drop an entry from the reflog + */ + int (*reflog_drop)(git_refdb_backend *_backend, git_reflog *reflog, + size_t idx, int rewrite_previous_entry); + /** + * Remove a reflog. + */ + int (*reflog_delete)(git_refdb_backend *backend, const char *name); }; #define GIT_REFDB_BACKEND_VERSION 1 diff --git a/include/git2/sys/reflog.h b/include/git2/sys/reflog.h new file mode 100644 index 000000000..c9d0041b9 --- /dev/null +++ b/include/git2/sys/reflog.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sys_git_reflog_h__ +#define INCLUDE_sys_git_reflog_h__ + +#include "git2/common.h" +#include "git2/types.h" +#include "git2/oid.h" + +GIT_BEGIN_DECL + +GIT_EXTERN(git_reflog_entry *) git_reflog_entry__alloc(void); +GIT_EXTERN(void) git_reflog_entry__free(git_reflog_entry *entry); + +GIT_END_DECL + +#endif diff --git a/src/refdb.c b/src/refdb.c index 7df61a577..adb58806e 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -16,6 +16,7 @@ #include "hash.h" #include "refdb.h" #include "refs.h" +#include "reflog.h" int git_refdb_new(git_refdb **out, git_repository *repo) { @@ -203,3 +204,18 @@ int git_refdb_delete(struct git_refdb *db, const char *ref_name) assert(db && db->backend); return db->backend->del(db->backend, ref_name); } + +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name) +{ + int error; + + assert(db && db->backend); + + if ((error = db->backend->reflog_read(out, db->backend, name)) < 0) + return error; + + GIT_REFCOUNT_INC(db); + (*out)->db = db; + + return 0; +} diff --git a/src/refdb.h b/src/refdb.h index 3aea37b62..0ee60d911 100644 --- a/src/refdb.h +++ b/src/refdb.h @@ -43,4 +43,8 @@ void git_refdb_iterator_free(git_reference_iterator *iter); int git_refdb_write(git_refdb *refdb, git_reference *ref, int force); int git_refdb_delete(git_refdb *refdb, const char *ref_name); +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name); +int git_refdb_reflog_write(git_reflog *reflog); + + #endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 0ba711eb7..4d695721d 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 #include #include #include #include +#include GIT__USE_STRMAP; @@ -1067,6 +1069,446 @@ 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__append(git_refdb_backend *_backend, git_reflog *reflog, + const git_oid *new_oid, const git_signature *committer, const char *msg) +{ + git_reflog_entry *entry; + const git_reflog_entry *previous; + const char *newline; + + assert(_backend && reflog && new_oid && committer); + + entry = git__calloc(1, sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(entry); + + if ((entry->committer = git_signature_dup(committer)) == NULL) + goto cleanup; + + if (msg != NULL) { + if ((entry->msg = git__strdup(msg)) == NULL) + goto cleanup; + + newline = strchr(msg, '\n'); + + if (newline) { + if (newline[1] != '\0') { + giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); + goto cleanup; + } + + entry->msg[newline - msg] = '\0'; + } + } + + previous = git_reflog_entry_byindex(reflog, 0); + + if (previous == NULL) + git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); + else + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + + git_oid_cpy(&entry->oid_cur, new_oid); + + if (git_vector_insert(&reflog->entries, entry) < 0) + goto cleanup; + + return 0; + +cleanup: + git_reflog_entry__free(entry); + return -1; +} + +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__drop(git_refdb_backend *_backend, git_reflog *reflog, + size_t idx, int rewrite_previous_entry) +{ + size_t entrycount; + git_reflog_entry *entry, *previous; + + assert(_backend && reflog); + + entrycount = git_reflog_entrycount(reflog); + + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + + if (entry == NULL) { + giterr_set(GITERR_REFERENCE, "No reflog entry at index "PRIuZ, idx); + return GIT_ENOTFOUND; + } + + git_reflog_entry__free(entry); + + if (git_vector_remove( + &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0) + return -1; + + if (!rewrite_previous_entry) + return 0; + + /* No need to rewrite anything when removing the most recent entry */ + if (idx == 0) + return 0; + + /* Have the latest entry just been dropped? */ + if (entrycount == 1) + return 0; + + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); + + /* If the oldest entry has just been removed... */ + if (idx == entrycount - 1) { + /* ...clear the oid_old member of the "new" oldest entry */ + if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) + return -1; + + return 0; + } + + previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + + return 0; +} + +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) @@ -1100,6 +1542,12 @@ 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_append = &refdb_reflog_fs__append; + backend->parent.reflog_rename = &refdb_reflog_fs__rename; + backend->parent.reflog_drop = &refdb_reflog_fs__drop; + backend->parent.reflog_delete = &refdb_reflog_fs__delete; *backend_out = (git_refdb_backend *)backend; return 0; diff --git a/src/reflog.c b/src/reflog.c index a6752f618..c10ae9fd4 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -9,82 +9,16 @@ #include "repository.h" #include "filebuf.h" #include "signature.h" +#include "refdb.h" -static int reflog_init(git_reflog **reflog, const git_reference *ref) +#include + +git_reflog_entry *git_reflog_entry__alloc(void) { - git_reflog *log; - - *reflog = NULL; - - log = git__calloc(1, sizeof(git_reflog)); - GITERR_CHECK_ALLOC(log); - - log->ref_name = git__strdup(ref->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; - } - - log->owner = git_reference_owner(ref); - *reflog = log; - - return 0; + return git__calloc(1, sizeof(git_reflog_entry)); } -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 reflog_entry_new(git_reflog_entry **entry) -{ - git_reflog_entry *e; - - assert(entry); - - e = git__malloc(sizeof(git_reflog_entry)); - GITERR_CHECK_ALLOC(e); - - memset(e, 0, sizeof(git_reflog_entry)); - - *entry = e; - - return 0; -} - -static void reflog_entry_free(git_reflog_entry *entry) +void git_reflog_entry__free(git_reflog_entry *entry) { git_signature_free(entry->committer); @@ -92,75 +26,6 @@ static void reflog_entry_free(git_reflog_entry *entry) git__free(entry); } -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) { - if (reflog_entry_new(&entry) < 0) - return -1; - - 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) - reflog_entry_free(entry); - - return -1; -} - void git_reflog_free(git_reflog *reflog) { size_t i; @@ -169,10 +34,13 @@ void git_reflog_free(git_reflog *reflog) if (reflog == NULL) return; + if (reflog->db) + GIT_REFCOUNT_DEC(reflog->db, git_refdb__free); + for (i=0; i < reflog->entries.length; i++) { entry = git_vector_get(&reflog->entries, i); - reflog_entry_free(entry); + git_reflog_entry__free(entry); } git_vector_free(&reflog->entries); @@ -180,247 +48,61 @@ void git_reflog_free(git_reflog *reflog) git__free(reflog); } -static int retrieve_reflog_path(git_buf *path, const git_reference *ref) +int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name) { - return git_buf_join_n(path, '/', 3, - git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name); -} + git_refdb *refdb; + int error; -static int create_new_reflog_file(const char *filepath) -{ - int fd, error; + assert(reflog && repo && name); - if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 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); -} - -int git_reflog_read(git_reflog **reflog, const git_reference *ref) -{ - int error = -1; - git_buf log_path = GIT_BUF_INIT; - git_buf log_file = GIT_BUF_INIT; - git_reflog *log = NULL; - - assert(reflog && ref); - - *reflog = NULL; - - if (reflog_init(&log, ref) < 0) - return -1; - - if (retrieve_reflog_path(&log_path, ref) < 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; - - *reflog = log; - goto success; - -cleanup: - git_reflog_free(log); - -success: - git_buf_free(&log_file); - git_buf_free(&log_path); - - return error; + return git_refdb_reflog_read(reflog, refdb, name); } int git_reflog_write(git_reflog *reflog) { - int error = -1; - unsigned int i; - git_reflog_entry *entry; - git_buf log_path = GIT_BUF_INIT; - git_buf log = GIT_BUF_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; + git_refdb *db; - assert(reflog); + assert(reflog && reflog->db); - if (git_buf_join_n(&log_path, '/', 3, - git_repository_path(reflog->owner), GIT_REFLOG_DIR, 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; + db = reflog->db; + return db->backend->reflog_write(db->backend, reflog); } int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) { - git_reflog_entry *entry; - const git_reflog_entry *previous; - const char *newline; + git_refdb *db; - assert(reflog && new_oid && committer); + assert(reflog && reflog->db && new_oid && committer); - if (reflog_entry_new(&entry) < 0) - return -1; + db = reflog->db; - if ((entry->committer = git_signature_dup(committer)) == NULL) - goto cleanup; - - if (msg != NULL) { - if ((entry->msg = git__strdup(msg)) == NULL) - goto cleanup; - - newline = strchr(msg, '\n'); - - if (newline) { - if (newline[1] != '\0') { - giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); - goto cleanup; - } - - entry->msg[newline - msg] = '\0'; - } - } - - previous = git_reflog_entry_byindex(reflog, 0); - - if (previous == NULL) - git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); - else - git_oid_cpy(&entry->oid_old, &previous->oid_cur); - - git_oid_cpy(&entry->oid_cur, new_oid); - - if (git_vector_insert(&reflog->entries, entry) < 0) - goto cleanup; - - return 0; - -cleanup: - reflog_entry_free(entry); - return -1; + return db->backend->reflog_append(db->backend, reflog, new_oid, committer, msg); } -int git_reflog_rename(git_reference *ref, 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; - - assert(ref && new_name); - - if ((error = git_reference__normalize_name( - &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0) - return error; - - if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0) - return -1; - - if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->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; -} - -int git_reflog_delete(git_reference *ref) +int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name) { + git_refdb *refdb; int error; - git_buf path = GIT_BUF_INIT; - error = retrieve_reflog_path(&path, ref); + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; - if (!error && git_path_exists(path.ptr)) - error = p_unlink(path.ptr); + return refdb->backend->reflog_rename(refdb->backend, old_name, new_name); +} - git_buf_free(&path); +int git_reflog_delete(git_repository *repo, const char *name) +{ + git_refdb *refdb; + int error; - return error; + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; + + return refdb->backend->reflog_delete(refdb->backend, name); } size_t git_reflog_entrycount(git_reflog *reflog) @@ -429,11 +111,6 @@ size_t git_reflog_entrycount(git_reflog *reflog) return reflog->entries.length; } -GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) -{ - return (total - 1) - idx; -} - const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx) { assert(reflog); @@ -474,50 +151,10 @@ int git_reflog_drop( size_t idx, int rewrite_previous_entry) { - size_t entrycount; - git_reflog_entry *entry, *previous; + git_refdb *db; - assert(reflog); + assert(reflog && reflog->db); - entrycount = git_reflog_entrycount(reflog); - - entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); - - if (entry == NULL) { - giterr_set(GITERR_REFERENCE, "No reflog entry at index %"PRIuZ, idx); - return GIT_ENOTFOUND; - } - - reflog_entry_free(entry); - - if (git_vector_remove( - &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0) - return -1; - - if (!rewrite_previous_entry) - return 0; - - /* No need to rewrite anything when removing the most recent entry */ - if (idx == 0) - return 0; - - /* Have the latest entry just been dropped? */ - if (entrycount == 1) - return 0; - - entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); - - /* If the oldest entry has just been removed... */ - if (idx == entrycount - 1) { - /* ...clear the oid_old member of the "new" oldest entry */ - if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) - return -1; - - return 0; - } - - previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); - git_oid_cpy(&entry->oid_old, &previous->oid_cur); - - return 0; + db = reflog->db; + return db->backend->reflog_drop(db->backend, reflog, idx, rewrite_previous_entry); } diff --git a/src/reflog.h b/src/reflog.h index 9444ebd10..2d31ae47d 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -27,9 +27,14 @@ struct git_reflog_entry { }; struct git_reflog { + git_refdb *db; char *ref_name; - git_repository *owner; git_vector entries; }; +GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) +{ + return (total - 1) - idx; +} + #endif /* INCLUDE_reflog_h__ */ diff --git a/src/refs.c b/src/refs.c index c045ab9dc..abec356b5 100644 --- a/src/refs.c +++ b/src/refs.c @@ -467,7 +467,7 @@ int git_reference_rename( if (reference_has_log < 0) return reference_has_log; - if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0) + if (reference_has_log && (error = git_reflog_rename(git_reference_owner(ref), git_reference_name(ref), new_name)) < 0) return error; return 0; diff --git a/src/revparse.c b/src/revparse.c index e470a954d..c120b466f 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -160,7 +160,7 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out, if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) goto cleanup; - if (git_reflog_read(&reflog, ref) < 0) + if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0) goto cleanup; numentries = git_reflog_entrycount(reflog); @@ -208,7 +208,7 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t ide const git_reflog_entry *entry; bool search_by_pos = (identifier <= 100000000); - if (git_reflog_read(&reflog, ref) < 0) + if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0) return -1; numentries = git_reflog_entrycount(reflog); diff --git a/src/stash.c b/src/stash.c index 7742eee19..ffdbd2e29 100644 --- a/src/stash.c +++ b/src/stash.c @@ -436,14 +436,16 @@ static int update_reflog( const git_signature *stasher, const char *message) { - git_reference *stash = NULL; + git_reference *stash; git_reflog *reflog = NULL; int error; if ((error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1)) < 0) goto cleanup; - if ((error = git_reflog_read(&reflog, stash)) < 0) + git_reference_free(stash); + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE) < 0)) goto cleanup; if ((error = git_reflog_append(reflog, w_commit_oid, stasher, message)) < 0) @@ -453,7 +455,6 @@ static int update_reflog( goto cleanup; cleanup: - git_reference_free(stash); git_reflog_free(reflog); return error; } @@ -599,7 +600,7 @@ int git_stash_foreach( if (error < 0) goto cleanup; - if ((error = git_reflog_read(&reflog, stash)) < 0) + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) goto cleanup; max = git_reflog_entrycount(reflog); @@ -633,7 +634,7 @@ int git_stash_drop( if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) return error; - if ((error = git_reflog_read(&reflog, stash)) < 0) + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) goto cleanup; max = git_reflog_entrycount(reflog); diff --git a/tests-clar/refs/reflog/drop.c b/tests-clar/refs/reflog/drop.c index 21cc847bf..916bd9933 100644 --- a/tests-clar/refs/reflog/drop.c +++ b/tests-clar/refs/reflog/drop.c @@ -8,15 +8,10 @@ static size_t entrycount; void test_refs_reflog_drop__initialize(void) { - git_reference *ref; - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - git_reflog_read(&g_reflog, ref); + git_reflog_read(&g_reflog, g_repo, "HEAD"); entrycount = git_reflog_entrycount(g_reflog); - - git_reference_free(ref); } void test_refs_reflog_drop__cleanup(void) @@ -106,19 +101,15 @@ void test_refs_reflog_drop__can_drop_all_the_entries(void) void test_refs_reflog_drop__can_persist_deletion_on_disk(void) { - git_reference *ref; - cl_assert(entrycount > 2); - cl_git_pass(git_reference_lookup(&ref, g_repo, g_reflog->ref_name)); cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); cl_git_pass(git_reflog_write(g_reflog)); git_reflog_free(g_reflog); - git_reflog_read(&g_reflog, ref); - git_reference_free(ref); + git_reflog_read(&g_reflog, g_repo, "HEAD"); cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); } diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c index 095cabf04..327c85799 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -50,7 +50,7 @@ void test_refs_reflog_reflog__append_then_read(void) cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); - cl_git_pass(git_reflog_read(&reflog, ref)); + cl_git_pass(git_reflog_read(&reflog, g_repo, new_ref)); cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline")); cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); @@ -65,7 +65,7 @@ void test_refs_reflog_reflog__append_then_read(void) cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); /* Read and parse the reflog for this branch */ - cl_git_pass(git_reflog_read(&reflog, lookedup_ref)); + cl_git_pass(git_reflog_read(&reflog, repo2, new_ref)); cl_assert_equal_i(2, (int)git_reflog_entrycount(reflog)); entry = git_reflog_entry_byindex(reflog, 1); @@ -133,21 +133,18 @@ void test_refs_reflog_reflog__reference_has_reflog(void) void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void) { - git_reference *subtrees; git_reflog *reflog; + const char *refname = "refs/heads/subtrees"; git_buf subtrees_log_path = GIT_BUF_INIT; - cl_git_pass(git_reference_lookup(&subtrees, g_repo, "refs/heads/subtrees")); - - git_buf_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, git_reference_name(subtrees)); + git_buf_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname); cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&subtrees_log_path))); - cl_git_pass(git_reflog_read(&reflog, subtrees)); + cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); cl_assert_equal_i(0, (int)git_reflog_entrycount(reflog)); git_reflog_free(reflog); - git_reference_free(subtrees); git_buf_free(&subtrees_log_path); } @@ -158,7 +155,7 @@ void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) git_reflog *reflog; cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); - cl_git_pass(git_reflog_read(&reflog, master)); + cl_git_pass(git_reflog_read(&reflog, g_repo, "refs/heads/master")); cl_git_pass(git_reflog_write(reflog)); @@ -175,12 +172,6 @@ void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) void test_refs_reflog_reflog__renaming_with_an_invalid_name_returns_EINVALIDSPEC(void) { - git_reference *master; - - cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_reflog_rename(master, "refs/heads/Inv@{id")); - - git_reference_free(master); + git_reflog_rename(g_repo, "refs/heads/master", "refs/heads/Inv@{id")); } diff --git a/tests-clar/stash/drop.c b/tests-clar/stash/drop.c index 59413f01e..63ff0377c 100644 --- a/tests-clar/stash/drop.c +++ b/tests-clar/stash/drop.c @@ -102,7 +102,7 @@ void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void) cl_git_pass(git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)); - cl_git_pass(git_reflog_read(&reflog, stash)); + cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)); entry = git_reflog_entry_byindex(reflog, 1); git_oid_cpy(&oid, git_reflog_entry_id_old(entry)); @@ -112,7 +112,7 @@ void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void) cl_git_pass(git_stash_drop(repo, 1)); - cl_git_pass(git_reflog_read(&reflog, stash)); + cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)); entry = git_reflog_entry_byindex(reflog, 0); cl_assert_equal_i(0, git_oid_cmp(&oid, git_reflog_entry_id_old(entry)));