diff options
| author | Patrick Steinhardt <ps@pks.im> | 2019-09-27 11:16:02 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-09-27 11:16:02 +0200 |
| commit | 70325370667370159d5b85690c6dd5db17be3b20 (patch) | |
| tree | cbf865fe35a5b58a9508c9c277d9f5ccb9e82611 | |
| parent | 257dd59d031f169a55afb55d0c34527fac71c11e (diff) | |
| parent | 8c14224103a7f2d254635a340a2a2f8b720b2b21 (diff) | |
| download | libgit2-70325370667370159d5b85690c6dd5db17be3b20.tar.gz | |
Merge pull request #5106 from tiennou/fix/ref-api-fixes
git_refdb API fixes
| -rw-r--r-- | include/git2/sys/refdb_backend.h | 78 | ||||
| -rw-r--r-- | src/refdb.c | 12 | ||||
| -rw-r--r-- | src/refdb_fs.c | 134 |
3 files changed, 158 insertions, 66 deletions
diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index 2ed6efd5a..8e22c4f02 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -58,11 +58,12 @@ struct git_reference_iterator { /** An instance for a custom backend */ struct git_refdb_backend { - unsigned int version; + unsigned int version; /**< The backend API version */ /** - * Queries the refdb backend to determine if the given ref_name - * exists. A refdb implementation must provide this function. + * Queries the refdb backend for the existence of a reference. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(exists)( int *exists, @@ -70,8 +71,9 @@ struct git_refdb_backend { const char *ref_name); /** - * Queries the refdb backend for a given reference. A refdb - * implementation must provide this function. + * Queries the refdb backend for a given reference. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(lookup)( git_reference **out, @@ -88,82 +90,116 @@ struct git_refdb_backend { struct git_refdb_backend *backend, const char *glob); - /* - * Writes the given reference to the refdb. A refdb implementation - * must provide this function. + /** + * Writes the given reference to the refdb. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(write)(git_refdb_backend *backend, const git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old, const char *old_target); + /** + * Rename a reference in the refdb. + * + * A refdb implementation must provide this function. + */ int GIT_CALLBACK(rename)( git_reference **out, git_refdb_backend *backend, const char *old_name, const char *new_name, int force, const git_signature *who, const char *message); /** - * Deletes the given reference (and if necessary its reflog) - * from the refdb. A refdb implementation must provide this - * function. + * Deletes the given reference from the refdb. + * + * If it exists, its reflog should be deleted as well. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(del)(git_refdb_backend *backend, const char *ref_name, const git_oid *old_id, const char *old_target); /** * Suggests that the given refdb compress or optimize its references. - * This mechanism is implementation specific. (For on-disk reference - * databases, this may pack all loose references.) A refdb - * implementation may provide this function; if it is not provided, - * nothing will be done. + * + * This mechanism is implementation specific. For on-disk reference + * databases, this may pack all loose references. + * + * A refdb implementation may provide this function; if it is not + * provided, nothing will be done. */ int GIT_CALLBACK(compress)(git_refdb_backend *backend); /** * Query whether a particular reference has a log (may be empty) + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(has_log)(git_refdb_backend *backend, const char *refname); /** * Make sure a particular reference will have a reflog which * will be appended to on writes. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(ensure_log)(git_refdb_backend *backend, const char *refname); /** * Frees any resources held by the refdb (including the `git_refdb_backend` - * itself). A refdb backend implementation must provide this function. + * itself). + * + * A refdb backend implementation must provide this function. */ void GIT_CALLBACK(free)(git_refdb_backend *backend); /** * Read the reflog for the given reference name. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(reflog_read)(git_reflog **out, git_refdb_backend *backend, const char *name); /** * Write a reflog to disk. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(reflog_write)(git_refdb_backend *backend, git_reflog *reflog); /** - * Rename a reflog + * Rename a reflog. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name); /** * Remove a reflog. + * + * A refdb implementation must provide this function. */ int GIT_CALLBACK(reflog_delete)(git_refdb_backend *backend, const char *name); /** - * Lock a reference. The opaque parameter will be passed to the unlock function + * Lock a reference. + * + * The opaque parameter will be passed to the unlock function. + * + * A refdb implementation may provide this function; if it is not + * provided, the transaction API will fail to work. */ int GIT_CALLBACK(lock)(void **payload_out, git_refdb_backend *backend, const char *refname); /** - * Unlock a reference. Only one of target or symbolic_target - * will be set. success indicates whether to update the - * reference or discard the lock (if it's false) + * Unlock a reference. + * + * Only one of target or symbolic_target will be set. + * `success` will be true if the reference should be update, false if + * the lock must be discarded. + * + * A refdb implementation must provide this function if a `lock` + * implementation is provided. */ int GIT_CALLBACK(unlock)(git_refdb_backend *backend, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message); diff --git a/src/refdb.c b/src/refdb.c index b466153a7..fbbf5193c 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -66,6 +66,18 @@ static void refdb_free_backend(git_refdb *db) int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend) { + GIT_ERROR_CHECK_VERSION(backend, GIT_REFDB_BACKEND_VERSION, "git_refdb_backend"); + + if (!backend->exists || !backend->lookup || !backend->iterator || + !backend->write || !backend->rename || !backend->del || + !backend->has_log || !backend->ensure_log || !backend->free || + !backend->reflog_read || !backend->reflog_write || + !backend->reflog_rename || !backend->reflog_delete || + (backend->lock && !backend->unlock)) { + git_error_set(GIT_ERROR_REFERENCE, "incomplete refdb backend implementation"); + return GIT_EINVALID; + } + refdb_free_backend(db); db->backend = backend; diff --git a/src/refdb_fs.c b/src/refdb_fs.c index a20f1ebcc..9fb66a8e7 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -858,16 +858,17 @@ static int refdb_fs_backend__write_tail( const git_reference *ref, git_filebuf *file, int update_reflog, - const git_signature *who, - const char *message, const git_oid *old_id, - const char *old_target); + const char *old_target, + const git_signature *who, + const char *message); static int refdb_fs_backend__delete_tail( git_refdb_backend *_backend, git_filebuf *file, const char *ref_name, - const git_oid *old_id, const char *old_target); + const git_oid *old_id, + const char *old_target); static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message) @@ -878,7 +879,7 @@ static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, i if (success == 2) error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL); else if (success) - error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, sig, message, NULL, NULL); + error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, NULL, NULL, sig, message); else git_filebuf_cleanup(lock); @@ -1097,6 +1098,35 @@ fail: return error; } +static int packed_delete(refdb_fs_backend *backend, const char *ref_name) +{ + size_t pack_pos; + int error, found = 0; + + if ((error = packed_reload(backend)) < 0) + goto cleanup; + + if ((error = git_sortedcache_wlock(backend->refcache)) < 0) + goto cleanup; + + /* If a packed reference exists, remove it from the packfile and repack if necessary */ + error = git_sortedcache_lookup_index(&pack_pos, backend->refcache, ref_name); + if (error == 0) { + error = git_sortedcache_remove(backend->refcache, pack_pos); + found = 1; + } + if (error == GIT_ENOTFOUND) + error = 0; + + git_sortedcache_wunlock(backend->refcache); + + if (found) + error = packed_write(backend); + +cleanup: + return error; +} + static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message); static int has_reflog(git_repository *repo, const char *name); @@ -1262,7 +1292,7 @@ static int refdb_fs_backend__write( if ((error = loose_lock(&file, backend, ref->name)) < 0) return error; - return refdb_fs_backend__write_tail(_backend, ref, &file, true, who, message, old_id, old_target); + return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message); } static int refdb_fs_backend__write_tail( @@ -1270,10 +1300,10 @@ static int refdb_fs_backend__write_tail( const git_reference *ref, git_filebuf *file, int update_reflog, - const git_signature *who, - const char *message, const git_oid *old_id, - const char *old_target) + const char *old_target, + const git_signature *who, + const char *message) { refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); int error = 0, cmp = 0, should_write; @@ -1323,10 +1353,10 @@ on_error: return error; } -static void refdb_fs_backend__try_delete_empty_ref_hierarchie( +static void refdb_fs_backend__prune_refs( refdb_fs_backend *backend, const char *ref_name, - bool reflog) + const char *prefix) { git_buf relative_path = GIT_BUF_INIT; git_buf base_path = GIT_BUF_INIT; @@ -1344,8 +1374,8 @@ static void refdb_fs_backend__try_delete_empty_ref_hierarchie( git_buf_truncate(&relative_path, commonlen); - if (reflog) { - if (git_buf_join3(&base_path, '/', backend->commonpath, GIT_REFLOG_DIR, git_buf_cstr(&relative_path)) < 0) + if (prefix) { + if (git_buf_join3(&base_path, '/', backend->commonpath, prefix, git_buf_cstr(&relative_path)) < 0) goto cleanup; } else { if (git_buf_joinpath(&base_path, backend->commonpath, git_buf_cstr(&relative_path)) < 0) @@ -1382,6 +1412,25 @@ static int refdb_fs_backend__delete( return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target); } +static int loose_delete(refdb_fs_backend *backend, const char *ref_name) +{ + git_buf loose_path = GIT_BUF_INIT; + int error = 0; + + if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0) + return -1; + + error = p_unlink(loose_path.ptr); + if (error < 0 && errno == ENOENT) + error = GIT_ENOTFOUND; + else if (error != 0) + error = -1; + + git_buf_dispose(&loose_path); + + return error; +} + static int refdb_fs_backend__delete_tail( git_refdb_backend *_backend, git_filebuf *file, @@ -1389,10 +1438,8 @@ static int refdb_fs_backend__delete_tail( const git_oid *old_id, const char *old_target) { refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_buf loose_path = GIT_BUF_INIT; - size_t pack_pos; int error = 0, cmp = 0; - bool loose_deleted = 0; + bool packed_deleted = 0; error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target); if (error < 0) @@ -1404,44 +1451,41 @@ static int refdb_fs_backend__delete_tail( goto cleanup; } - /* If a loose reference exists, remove it from the filesystem */ - if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0) - return -1; - - - error = p_unlink(loose_path.ptr); - if (error < 0 && errno == ENOENT) - error = 0; - else if (error < 0) + /* + * To ensure that an external observer will see either the current ref value + * (because the loose ref still exists), or a missing ref (after the packed-file is + * unlocked, there will be nothing left), we must ensure things happen in the + * following order: + * + * - the packed-ref file is locked and loaded, as well as a loose one, if it exists + * - we optimistically delete a packed ref, keeping track of whether it existed + * - we delete the loose ref, note that we have its .lock + * - the loose ref is "unlocked", then the packed-ref file is rewritten and unlocked + * - we should prune the path components if a loose ref was deleted + * + * Note that, because our packed backend doesn't expose its filesystem lock, + * we might not be able to guarantee that this is what actually happens (ie. + * as our current code never write packed-refs.lock, nothing stops observers + * from grabbing a "stale" value from there). + */ + if ((error = packed_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) goto cleanup; - else if (error == 0) - loose_deleted = 1; - if ((error = packed_reload(backend)) < 0) - goto cleanup; + if (error == 0) + packed_deleted = 1; - /* If a packed reference exists, remove it from the packfile and repack */ - if ((error = git_sortedcache_wlock(backend->refcache)) < 0) + if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) goto cleanup; - if (!(error = git_sortedcache_lookup_index( - &pack_pos, backend->refcache, ref_name))) - error = git_sortedcache_remove(backend->refcache, pack_pos); - - git_sortedcache_wunlock(backend->refcache); - if (error == GIT_ENOTFOUND) { - error = loose_deleted ? 0 : ref_error_notfound(ref_name); + error = packed_deleted ? 0 : ref_error_notfound(ref_name); goto cleanup; } - error = packed_write(backend); - cleanup: - git_buf_dispose(&loose_path); git_filebuf_cleanup(file); - if (loose_deleted) - refdb_fs_backend__try_delete_empty_ref_hierarchie(backend, ref_name, false); + if (error == 0) + refdb_fs_backend__prune_refs(backend, ref_name, ""); return error; } @@ -1904,7 +1948,7 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co !(old && new)) return 0; - /* From here on is_symoblic also means that it's HEAD */ + /* From here on is_symbolic also means that it's HEAD */ if (old) { git_oid_cpy(&old_id, old); @@ -2071,7 +2115,7 @@ static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name if ((error = p_unlink(path.ptr)) < 0) goto out; - refdb_fs_backend__try_delete_empty_ref_hierarchie(backend, name, true); + refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR); out: git_buf_dispose(&path); |
