mirror of
https://git.proxmox.com/git/libgit2
synced 2025-12-31 23:59:11 +00:00
Merge pull request #2448 from libgit2/cmn/reference-transaction
Introduce reference transactions
This commit is contained in:
commit
10cf4b26a0
@ -8,6 +8,11 @@ v0.21 + 1
|
||||
|
||||
* Use a map for the treebuilder, making insertion O(1)
|
||||
|
||||
* Introduce reference transactions, which allow multiple references to
|
||||
be locked at the same time and updates be queued. This also allows
|
||||
us to safely update a reflog with arbitrary contents, as we need to
|
||||
do for stash.
|
||||
|
||||
* LF -> CRLF filter refuses to handle mixed-EOL files
|
||||
|
||||
* LF -> CRLF filter now runs when * text = auto (with Git for Windows 1.9.4)
|
||||
|
||||
@ -102,7 +102,7 @@ GIT_EXTERN(size_t) git_reflog_entrycount(git_reflog *reflog);
|
||||
* equal to 0 (zero) and less than `git_reflog_entrycount()`.
|
||||
* @return the entry; NULL if not found
|
||||
*/
|
||||
GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, size_t idx);
|
||||
GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(const git_reflog *reflog, size_t idx);
|
||||
|
||||
/**
|
||||
* Remove an entry from the reflog by its index
|
||||
|
||||
@ -153,6 +153,19 @@ struct git_refdb_backend {
|
||||
* Remove a reflog.
|
||||
*/
|
||||
int (*reflog_delete)(git_refdb_backend *backend, const char *name);
|
||||
|
||||
/**
|
||||
* Lock a reference. The opaque parameter will be passed to the unlock function
|
||||
*/
|
||||
int (*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)
|
||||
*/
|
||||
int (*unlock)(git_refdb_backend *backend, void *payload, int success, int update_reflog,
|
||||
const git_reference *ref, const git_signature *sig, const char *message);
|
||||
};
|
||||
|
||||
#define GIT_REFDB_BACKEND_VERSION 1
|
||||
|
||||
111
include/git2/transaction.h
Normal file
111
include/git2/transaction.h
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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_git_transaction_h__
|
||||
#define INCLUDE_git_transaction_h__
|
||||
|
||||
#include "common.h"
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* Create a new transaction object
|
||||
*
|
||||
* This does not lock anything, but sets up the transaction object to
|
||||
* know from which repository to lock.
|
||||
*
|
||||
* @param out the resulting transaction
|
||||
* @param repo the repository in which to lock
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transaction_new(git_transaction **out, git_repository *repo);
|
||||
|
||||
/**
|
||||
* Lock a reference
|
||||
*
|
||||
* Lock the specified reference. This is the first step to updating a
|
||||
* reference.
|
||||
*
|
||||
* @param tx the transaction
|
||||
* @param refname the reference to lock
|
||||
* @return 0 or an error message
|
||||
*/
|
||||
GIT_EXTERN(int) git_transaction_lock_ref(git_transaction *tx, const char *refname);
|
||||
|
||||
/**
|
||||
* Set the target of a reference
|
||||
*
|
||||
* Set the target of the specified reference. This reference must be
|
||||
* locked.
|
||||
*
|
||||
* @param tx the transaction
|
||||
* @param refname reference to update
|
||||
* @param target target to set the reference to
|
||||
* @param sig signature to use in the reflog; pass NULL to read the identity from the config
|
||||
* @param msg message to use in the reflog
|
||||
* @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg);
|
||||
|
||||
/**
|
||||
* Set the target of a reference
|
||||
*
|
||||
* Set the target of the specified reference. This reference must be
|
||||
* locked.
|
||||
*
|
||||
* @param tx the transaction
|
||||
* @param refname reference to update
|
||||
* @param target target to set the reference to
|
||||
* @param sig signature to use in the reflog; pass NULL to read the identity from the config
|
||||
* @param msg message to use in the reflog
|
||||
* @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg);
|
||||
|
||||
/**
|
||||
* Set the reflog of a reference
|
||||
*
|
||||
* Set the specified reference's reflog. If this is combined with
|
||||
* setting the target, that update won't be written to the reflog.
|
||||
*
|
||||
* @param tx the transaction
|
||||
* @param refname the reference whose reflog to set
|
||||
* @param reflog the reflog as it should be written out
|
||||
* @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog);
|
||||
|
||||
/**
|
||||
* Remove a reference
|
||||
*
|
||||
* @param tx the transaction
|
||||
* @param refname the reference to remove
|
||||
* @return 0, GIT_ENOTFOUND if the reference is not among the locked ones, or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transaction_remove(git_transaction *tx, const char *refname);
|
||||
|
||||
/**
|
||||
* Commit the changes from the transaction
|
||||
*
|
||||
* Perform the changes that have been queued. The updates will be made
|
||||
* one by one, and the first failure will stop the processing.
|
||||
*
|
||||
* @param tx the transaction
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transaction_commit(git_transaction *tx);
|
||||
|
||||
/**
|
||||
* Free the resources allocated by this transaction
|
||||
*
|
||||
* If any references remain locked, they will be unlocked without any
|
||||
* changes made to them.
|
||||
*
|
||||
* @param tx the transaction
|
||||
*/
|
||||
GIT_EXTERN(void) git_transaction_free(git_transaction *tx);
|
||||
|
||||
GIT_END_DECL
|
||||
#endif
|
||||
@ -171,6 +171,9 @@ typedef struct git_reference git_reference;
|
||||
/** Iterator for references */
|
||||
typedef struct git_reference_iterator git_reference_iterator;
|
||||
|
||||
/** Transactional interface to references */
|
||||
typedef struct git_transaction git_transaction;
|
||||
|
||||
/** Merge heads, the input to merge */
|
||||
typedef struct git_merge_head git_merge_head;
|
||||
|
||||
|
||||
19
src/refdb.c
19
src/refdb.c
@ -242,3 +242,22 @@ int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version)
|
||||
backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_refdb_lock(void **payload, git_refdb *db, const char *refname)
|
||||
{
|
||||
assert(payload && db && refname);
|
||||
|
||||
if (!db->backend->lock) {
|
||||
giterr_set(GITERR_REFERENCE, "backend does not support locking");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return db->backend->lock(payload, db->backend, refname);
|
||||
}
|
||||
|
||||
int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message)
|
||||
{
|
||||
assert(db);
|
||||
|
||||
return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message);
|
||||
}
|
||||
|
||||
@ -51,6 +51,7 @@ int git_refdb_reflog_write(git_reflog *reflog);
|
||||
int git_refdb_has_log(git_refdb *db, const char *refname);
|
||||
int git_refdb_ensure_log(git_refdb *refdb, const char *refname);
|
||||
|
||||
|
||||
int git_refdb_lock(void **payload, git_refdb *db, const char *refname);
|
||||
int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message);
|
||||
|
||||
#endif
|
||||
|
||||
118
src/refdb_fs.c
118
src/refdb_fs.c
@ -745,6 +745,57 @@ static int loose_commit(git_filebuf *file, const git_reference *ref)
|
||||
return git_filebuf_commit(file);
|
||||
}
|
||||
|
||||
static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname)
|
||||
{
|
||||
int error;
|
||||
git_filebuf *lock;
|
||||
refdb_fs_backend *backend = (refdb_fs_backend *) _backend;
|
||||
|
||||
lock = git__calloc(1, sizeof(git_filebuf));
|
||||
GITERR_CHECK_ALLOC(lock);
|
||||
|
||||
if ((error = loose_lock(lock, backend, refname)) < 0) {
|
||||
git__free(lock);
|
||||
return error;
|
||||
}
|
||||
|
||||
*out = lock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int refdb_fs_backend__write_tail(
|
||||
git_refdb_backend *_backend,
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
git_filebuf *lock = (git_filebuf *) payload;
|
||||
int error = 0;
|
||||
|
||||
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);
|
||||
else
|
||||
git_filebuf_cleanup(lock);
|
||||
|
||||
git__free(lock);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find out what object this reference resolves to.
|
||||
*
|
||||
@ -1063,7 +1114,6 @@ cleanup:
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
static int refdb_fs_backend__write(
|
||||
git_refdb_backend *_backend,
|
||||
const git_reference *ref,
|
||||
@ -1075,9 +1125,7 @@ static int refdb_fs_backend__write(
|
||||
{
|
||||
refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
|
||||
git_filebuf file = GIT_FILEBUF_INIT;
|
||||
int error = 0, cmp = 0, should_write;
|
||||
const char *new_target = NULL;
|
||||
const git_oid *new_id = NULL;
|
||||
int error = 0;
|
||||
|
||||
assert(backend);
|
||||
|
||||
@ -1089,6 +1137,24 @@ 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);
|
||||
}
|
||||
|
||||
static int refdb_fs_backend__write_tail(
|
||||
git_refdb_backend *_backend,
|
||||
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)
|
||||
{
|
||||
refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
|
||||
int error = 0, cmp = 0, should_write;
|
||||
const char *new_target = NULL;
|
||||
const git_oid *new_id = NULL;
|
||||
|
||||
if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0)
|
||||
goto on_error;
|
||||
|
||||
@ -1113,20 +1179,22 @@ static int refdb_fs_backend__write(
|
||||
goto on_error; /* not really error */
|
||||
}
|
||||
|
||||
if ((error = should_write_reflog(&should_write, backend->repo, ref->name)) < 0)
|
||||
goto on_error;
|
||||
if (update_reflog) {
|
||||
if ((error = should_write_reflog(&should_write, backend->repo, ref->name)) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (should_write) {
|
||||
if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0)
|
||||
goto on_error;
|
||||
if ((error = maybe_append_head(backend, ref, who, message)) < 0)
|
||||
goto on_error;
|
||||
if (should_write) {
|
||||
if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0)
|
||||
goto on_error;
|
||||
if ((error = maybe_append_head(backend, ref, who, message)) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
|
||||
return loose_commit(&file, ref);
|
||||
return loose_commit(file, ref);
|
||||
|
||||
on_error:
|
||||
git_filebuf_cleanup(&file);
|
||||
git_filebuf_cleanup(file);
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -1136,17 +1204,29 @@ static int refdb_fs_backend__delete(
|
||||
const git_oid *old_id, const char *old_target)
|
||||
{
|
||||
refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
|
||||
git_buf loose_path = GIT_BUF_INIT;
|
||||
size_t pack_pos;
|
||||
git_filebuf file = GIT_FILEBUF_INIT;
|
||||
int error = 0, cmp = 0;
|
||||
bool loose_deleted = 0;
|
||||
int error = 0;
|
||||
|
||||
assert(backend && ref_name);
|
||||
|
||||
if ((error = loose_lock(&file, backend, ref_name)) < 0)
|
||||
return error;
|
||||
|
||||
return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
|
||||
git_buf loose_path = GIT_BUF_INIT;
|
||||
size_t pack_pos;
|
||||
int error = 0, cmp = 0;
|
||||
bool loose_deleted = 0;
|
||||
|
||||
error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
@ -1192,7 +1272,7 @@ static int refdb_fs_backend__delete(
|
||||
error = packed_write(backend);
|
||||
|
||||
cleanup:
|
||||
git_filebuf_cleanup(&file);
|
||||
git_filebuf_cleanup(file);
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -1837,6 +1917,8 @@ int git_refdb_backend_fs(
|
||||
backend->parent.del = &refdb_fs_backend__delete;
|
||||
backend->parent.rename = &refdb_fs_backend__rename;
|
||||
backend->parent.compress = &refdb_fs_backend__compress;
|
||||
backend->parent.lock = &refdb_fs_backend__lock;
|
||||
backend->parent.unlock = &refdb_fs_backend__unlock;
|
||||
backend->parent.has_log = &refdb_reflog_fs__has_log;
|
||||
backend->parent.ensure_log = &refdb_reflog_fs__ensure_log;
|
||||
backend->parent.free = &refdb_fs_backend__free;
|
||||
|
||||
@ -148,7 +148,7 @@ size_t git_reflog_entrycount(git_reflog *reflog)
|
||||
return reflog->entries.length;
|
||||
}
|
||||
|
||||
const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx)
|
||||
const git_reflog_entry * git_reflog_entry_byindex(const git_reflog *reflog, size_t idx)
|
||||
{
|
||||
assert(reflog);
|
||||
|
||||
|
||||
@ -411,7 +411,7 @@ static int reference__create(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int log_signature(git_signature **out, git_repository *repo)
|
||||
int git_reference__log_signature(git_signature **out, git_repository *repo)
|
||||
{
|
||||
int error;
|
||||
git_signature *who;
|
||||
@ -441,7 +441,7 @@ int git_reference_create_matching(
|
||||
assert(id);
|
||||
|
||||
if (!signature) {
|
||||
if ((error = log_signature(&who, repo)) < 0)
|
||||
if ((error = git_reference__log_signature(&who, repo)) < 0)
|
||||
return error;
|
||||
else
|
||||
signature = who;
|
||||
@ -482,7 +482,7 @@ int git_reference_symbolic_create_matching(
|
||||
assert(target);
|
||||
|
||||
if (!signature) {
|
||||
if ((error = log_signature(&who, repo)) < 0)
|
||||
if ((error = git_reference__log_signature(&who, repo)) < 0)
|
||||
return error;
|
||||
else
|
||||
signature = who;
|
||||
|
||||
@ -98,4 +98,6 @@ int git_reference_lookup_resolved(
|
||||
const char *name,
|
||||
int max_deref);
|
||||
|
||||
int git_reference__log_signature(git_signature **out, git_repository *repo);
|
||||
|
||||
#endif
|
||||
|
||||
@ -106,6 +106,30 @@ int git_signature_dup(git_signature **dest, const git_signature *source)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool)
|
||||
{
|
||||
git_signature *signature;
|
||||
|
||||
if (source == NULL)
|
||||
return 0;
|
||||
|
||||
signature = git_pool_mallocz(pool, sizeof(git_signature));
|
||||
GITERR_CHECK_ALLOC(signature);
|
||||
|
||||
signature->name = git_pool_strdup(pool, source->name);
|
||||
GITERR_CHECK_ALLOC(signature->name);
|
||||
|
||||
signature->email = git_pool_strdup(pool, source->email);
|
||||
GITERR_CHECK_ALLOC(signature->email);
|
||||
|
||||
signature->when.time = source->when.time;
|
||||
signature->when.offset = source->when.offset;
|
||||
|
||||
*dest = signature;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_signature_now(git_signature **sig_out, const char *name, const char *email)
|
||||
{
|
||||
time_t now;
|
||||
|
||||
@ -15,4 +15,6 @@
|
||||
int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender);
|
||||
void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig);
|
||||
|
||||
int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool);
|
||||
|
||||
#endif
|
||||
|
||||
30
src/stash.c
30
src/stash.c
@ -15,6 +15,7 @@
|
||||
#include "git2/status.h"
|
||||
#include "git2/checkout.h"
|
||||
#include "git2/index.h"
|
||||
#include "git2/transaction.h"
|
||||
#include "signature.h"
|
||||
|
||||
static int create_error(int error, const char *msg)
|
||||
@ -601,14 +602,21 @@ int git_stash_drop(
|
||||
git_repository *repo,
|
||||
size_t index)
|
||||
{
|
||||
git_reference *stash;
|
||||
git_transaction *tx;
|
||||
git_reference *stash = NULL;
|
||||
git_reflog *reflog = NULL;
|
||||
size_t max;
|
||||
int error;
|
||||
|
||||
if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
|
||||
if ((error = git_transaction_new(&tx, repo)) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = git_transaction_lock_ref(tx, GIT_REFS_STASH_FILE)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
@ -623,29 +631,25 @@ int git_stash_drop(
|
||||
if ((error = git_reflog_drop(reflog, index, true)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_reflog_write(reflog)) < 0)
|
||||
if ((error = git_transaction_set_reflog(tx, GIT_REFS_STASH_FILE, reflog)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (max == 1) {
|
||||
error = git_reference_delete(stash);
|
||||
git_reference_free(stash);
|
||||
stash = NULL;
|
||||
if ((error = git_transaction_remove(tx, GIT_REFS_STASH_FILE)) < 0)
|
||||
goto cleanup;
|
||||
} else if (index == 0) {
|
||||
const git_reflog_entry *entry;
|
||||
|
||||
entry = git_reflog_entry_byindex(reflog, 0);
|
||||
|
||||
git_reference_free(stash);
|
||||
error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1, NULL, NULL);
|
||||
if (error < 0)
|
||||
if ((error = git_transaction_set_target(tx, GIT_REFS_STASH_FILE, &entry->oid_cur, NULL, NULL)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* We need to undo the writing that we just did */
|
||||
error = git_reflog_write(reflog);
|
||||
}
|
||||
|
||||
error = git_transaction_commit(tx);
|
||||
|
||||
cleanup:
|
||||
git_reference_free(stash);
|
||||
git_transaction_free(tx);
|
||||
git_reflog_free(reflog);
|
||||
return error;
|
||||
}
|
||||
|
||||
352
src/transaction.c
Normal file
352
src/transaction.c
Normal file
@ -0,0 +1,352 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "repository.h"
|
||||
#include "strmap.h"
|
||||
#include "refdb.h"
|
||||
#include "pool.h"
|
||||
#include "reflog.h"
|
||||
#include "signature.h"
|
||||
|
||||
#include "git2/signature.h"
|
||||
#include "git2/sys/refs.h"
|
||||
#include "git2/sys/refdb_backend.h"
|
||||
|
||||
GIT__USE_STRMAP;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
void *payload;
|
||||
|
||||
git_ref_t ref_type;
|
||||
union {
|
||||
git_oid id;
|
||||
char *symbolic;
|
||||
} target;
|
||||
git_reflog *reflog;
|
||||
|
||||
const char *message;
|
||||
git_signature *sig;
|
||||
|
||||
unsigned int committed :1,
|
||||
remove :1;
|
||||
} transaction_node;
|
||||
|
||||
struct git_transaction {
|
||||
git_repository *repo;
|
||||
git_refdb *db;
|
||||
|
||||
git_strmap *locks;
|
||||
git_pool pool;
|
||||
};
|
||||
|
||||
int git_transaction_new(git_transaction **out, git_repository *repo)
|
||||
{
|
||||
int error;
|
||||
git_pool pool;
|
||||
git_transaction *tx = NULL;
|
||||
|
||||
assert(out && repo);
|
||||
|
||||
if ((error = git_pool_init(&pool, 1, 0)) < 0)
|
||||
return error;
|
||||
|
||||
tx = git_pool_mallocz(&pool, sizeof(git_transaction));
|
||||
if (!tx) {
|
||||
error = -1;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if ((error = git_strmap_alloc(&tx->locks)) < 0) {
|
||||
error = -1;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if ((error = git_repository_refdb(&tx->db, repo)) < 0)
|
||||
goto on_error;
|
||||
|
||||
memcpy(&tx->pool, &pool, sizeof(git_pool));
|
||||
tx->repo = repo;
|
||||
*out = tx;
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_pool_clear(&pool);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_transaction_lock_ref(git_transaction *tx, const char *refname)
|
||||
{
|
||||
int error;
|
||||
transaction_node *node;
|
||||
|
||||
assert(tx && refname);
|
||||
|
||||
node = git_pool_mallocz(&tx->pool, sizeof(transaction_node));
|
||||
GITERR_CHECK_ALLOC(node);
|
||||
|
||||
node->name = git_pool_strdup(&tx->pool, refname);
|
||||
GITERR_CHECK_ALLOC(node->name);
|
||||
|
||||
if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0)
|
||||
return error;
|
||||
|
||||
git_strmap_insert(tx->locks, node->name, node, error);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int find_locked(transaction_node **out, git_transaction *tx, const char *refname)
|
||||
{
|
||||
git_strmap_iter pos;
|
||||
transaction_node *node;
|
||||
|
||||
pos = git_strmap_lookup_index(tx->locks, refname);
|
||||
if (!git_strmap_valid_index(tx->locks, pos)) {
|
||||
giterr_set(GITERR_REFERENCE, "the specified reference is not locked");
|
||||
return GIT_ENOTFOUND;
|
||||
}
|
||||
|
||||
node = git_strmap_value_at(tx->locks, pos);
|
||||
|
||||
*out = node;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg)
|
||||
{
|
||||
if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0)
|
||||
return -1;
|
||||
|
||||
if (!node->sig) {
|
||||
git_signature *tmp;
|
||||
int error;
|
||||
|
||||
if (git_reference__log_signature(&tmp, tx->repo) < 0)
|
||||
return -1;
|
||||
|
||||
/* make sure the sig we use is in our pool */
|
||||
error = git_signature__pdup(&node->sig, tmp, &tx->pool);
|
||||
git_signature_free(tmp);
|
||||
if (error < 0)
|
||||
return error;
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
node->message = git_pool_strdup(&tx->pool, msg);
|
||||
GITERR_CHECK_ALLOC(node->message);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg)
|
||||
{
|
||||
int error;
|
||||
transaction_node *node;
|
||||
|
||||
assert(tx && refname && target);
|
||||
|
||||
if ((error = find_locked(&node, tx, refname)) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = copy_common(node, tx, sig, msg)) < 0)
|
||||
return error;
|
||||
|
||||
git_oid_cpy(&node->target.id, target);
|
||||
node->ref_type = GIT_REF_OID;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg)
|
||||
{
|
||||
int error;
|
||||
transaction_node *node;
|
||||
|
||||
assert(tx && refname && target);
|
||||
|
||||
if ((error = find_locked(&node, tx, refname)) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = copy_common(node, tx, sig, msg)) < 0)
|
||||
return error;
|
||||
|
||||
node->target.symbolic = git_pool_strdup(&tx->pool, target);
|
||||
GITERR_CHECK_ALLOC(node->target.symbolic);
|
||||
node->ref_type = GIT_REF_SYMBOLIC;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_transaction_remove(git_transaction *tx, const char *refname)
|
||||
{
|
||||
int error;
|
||||
transaction_node *node;
|
||||
|
||||
if ((error = find_locked(&node, tx, refname)) < 0)
|
||||
return error;
|
||||
|
||||
node->remove = true;
|
||||
node->ref_type = GIT_REF_OID; /* the id will be ignored */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dup_reflog(git_reflog **out, const git_reflog *in, git_pool *pool)
|
||||
{
|
||||
git_reflog *reflog;
|
||||
git_reflog_entry *entries;
|
||||
size_t len, i;
|
||||
|
||||
reflog = git_pool_mallocz(pool, sizeof(git_reflog));
|
||||
GITERR_CHECK_ALLOC(reflog);
|
||||
|
||||
reflog->ref_name = git_pool_strdup(pool, in->ref_name);
|
||||
GITERR_CHECK_ALLOC(reflog->ref_name);
|
||||
|
||||
len = in->entries.length;
|
||||
reflog->entries.length = len;
|
||||
reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *));
|
||||
GITERR_CHECK_ALLOC(reflog->entries.contents);
|
||||
|
||||
entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry));
|
||||
GITERR_CHECK_ALLOC(entries);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
const git_reflog_entry *src;
|
||||
git_reflog_entry *tgt;
|
||||
|
||||
tgt = &entries[i];
|
||||
reflog->entries.contents[i] = tgt;
|
||||
|
||||
src = git_vector_get(&in->entries, i);
|
||||
git_oid_cpy(&tgt->oid_old, &src->oid_old);
|
||||
git_oid_cpy(&tgt->oid_cur, &src->oid_cur);
|
||||
|
||||
tgt->msg = git_pool_strdup(pool, src->msg);
|
||||
GITERR_CHECK_ALLOC(tgt->msg);
|
||||
|
||||
if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
*out = reflog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog)
|
||||
{
|
||||
int error;
|
||||
transaction_node *node;
|
||||
|
||||
assert(tx && refname && reflog);
|
||||
|
||||
if ((error = find_locked(&node, tx, refname)) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0)
|
||||
return error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int update_target(git_refdb *db, transaction_node *node)
|
||||
{
|
||||
git_reference *ref;
|
||||
int error, update_reflog;
|
||||
|
||||
if (node->ref_type == GIT_REF_OID) {
|
||||
ref = git_reference__alloc(node->name, &node->target.id, NULL);
|
||||
} else if (node->ref_type == GIT_REF_SYMBOLIC) {
|
||||
ref = git_reference__alloc_symbolic(node->name, node->target.symbolic);
|
||||
} else {
|
||||
assert(0);
|
||||
}
|
||||
|
||||
GITERR_CHECK_ALLOC(ref);
|
||||
update_reflog = node->reflog == NULL;
|
||||
|
||||
if (node->remove) {
|
||||
error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL);
|
||||
} else if (node->ref_type == GIT_REF_OID) {
|
||||
error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message);
|
||||
} else if (node->ref_type == GIT_REF_SYMBOLIC) {
|
||||
error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message);
|
||||
} else {
|
||||
assert(0);
|
||||
}
|
||||
|
||||
git_reference_free(ref);
|
||||
node->committed = true;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_transaction_commit(git_transaction *tx)
|
||||
{
|
||||
transaction_node *node;
|
||||
git_strmap_iter pos;
|
||||
int error;
|
||||
|
||||
assert(tx);
|
||||
|
||||
for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) {
|
||||
if (!git_strmap_has_data(tx->locks, pos))
|
||||
continue;
|
||||
|
||||
node = git_strmap_value_at(tx->locks, pos);
|
||||
if (node->reflog) {
|
||||
if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0)
|
||||
return error;
|
||||
}
|
||||
|
||||
if (node->ref_type != GIT_REF_INVALID) {
|
||||
if ((error = update_target(tx->db, node)) < 0)
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void git_transaction_free(git_transaction *tx)
|
||||
{
|
||||
transaction_node *node;
|
||||
git_pool pool;
|
||||
git_strmap_iter pos;
|
||||
|
||||
assert(tx);
|
||||
|
||||
/* start by unlocking the ones we've left hanging, if any */
|
||||
for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) {
|
||||
if (!git_strmap_has_data(tx->locks, pos))
|
||||
continue;
|
||||
|
||||
node = git_strmap_value_at(tx->locks, pos);
|
||||
if (node->committed)
|
||||
continue;
|
||||
|
||||
git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
git_refdb_free(tx->db);
|
||||
git_strmap_free(tx->locks);
|
||||
|
||||
/* tx is inside the pool, so we need to extract the data */
|
||||
memcpy(&pool, &tx->pool, sizeof(git_pool));
|
||||
git_pool_clear(&pool);
|
||||
}
|
||||
110
tests/refs/transactions.c
Normal file
110
tests/refs/transactions.c
Normal file
@ -0,0 +1,110 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "git2/transaction.h"
|
||||
|
||||
static git_repository *g_repo;
|
||||
static git_transaction *g_tx;
|
||||
|
||||
void test_refs_transactions__initialize(void)
|
||||
{
|
||||
g_repo = cl_git_sandbox_init("testrepo");
|
||||
cl_git_pass(git_transaction_new(&g_tx, g_repo));
|
||||
}
|
||||
|
||||
void test_refs_transactions__cleanup(void)
|
||||
{
|
||||
git_transaction_free(g_tx);
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
void test_refs_transactions__single_ref_oid(void)
|
||||
{
|
||||
git_reference *ref;
|
||||
git_oid id;
|
||||
|
||||
git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
|
||||
|
||||
cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master"));
|
||||
cl_git_pass(git_transaction_set_target(g_tx, "refs/heads/master", &id, NULL, NULL));
|
||||
cl_git_pass(git_transaction_commit(g_tx));
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master"));
|
||||
|
||||
cl_assert(!git_oid_cmp(&id, git_reference_target(ref)));
|
||||
git_reference_free(ref);
|
||||
}
|
||||
|
||||
void test_refs_transactions__single_ref_symbolic(void)
|
||||
{
|
||||
git_reference *ref;
|
||||
|
||||
cl_git_pass(git_transaction_lock_ref(g_tx, "HEAD"));
|
||||
cl_git_pass(git_transaction_set_symbolic_target(g_tx, "HEAD", "refs/heads/foo", NULL, NULL));
|
||||
cl_git_pass(git_transaction_commit(g_tx));
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
|
||||
|
||||
cl_assert_equal_s("refs/heads/foo", git_reference_symbolic_target(ref));
|
||||
git_reference_free(ref);
|
||||
}
|
||||
|
||||
void test_refs_transactions__single_ref_mix_types(void)
|
||||
{
|
||||
git_reference *ref;
|
||||
git_oid id;
|
||||
|
||||
git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
|
||||
|
||||
cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master"));
|
||||
cl_git_pass(git_transaction_lock_ref(g_tx, "HEAD"));
|
||||
cl_git_pass(git_transaction_set_symbolic_target(g_tx, "refs/heads/master", "refs/heads/foo", NULL, NULL));
|
||||
cl_git_pass(git_transaction_set_target(g_tx, "HEAD", &id, NULL, NULL));
|
||||
cl_git_pass(git_transaction_commit(g_tx));
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master"));
|
||||
cl_assert_equal_s("refs/heads/foo", git_reference_symbolic_target(ref));
|
||||
git_reference_free(ref);
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
|
||||
cl_assert(!git_oid_cmp(&id, git_reference_target(ref)));
|
||||
git_reference_free(ref);
|
||||
}
|
||||
|
||||
void test_refs_transactions__single_ref_delete(void)
|
||||
{
|
||||
git_reference *ref;
|
||||
|
||||
cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master"));
|
||||
cl_git_pass(git_transaction_remove(g_tx, "refs/heads/master"));
|
||||
cl_git_pass(git_transaction_commit(g_tx));
|
||||
|
||||
cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo, "refs/heads/master"));
|
||||
}
|
||||
|
||||
void test_refs_transactions__single_create(void)
|
||||
{
|
||||
git_reference *ref;
|
||||
const char *name = "refs/heads/new-branch";
|
||||
git_oid id;
|
||||
|
||||
cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo, name));
|
||||
|
||||
cl_git_pass(git_transaction_lock_ref(g_tx, name));
|
||||
|
||||
git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
|
||||
cl_git_pass(git_transaction_set_target(g_tx, name, &id, NULL, NULL));
|
||||
cl_git_pass(git_transaction_commit(g_tx));
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, g_repo, name));
|
||||
cl_assert(!git_oid_cmp(&id, git_reference_target(ref)));
|
||||
git_reference_free(ref);
|
||||
}
|
||||
|
||||
void test_refs_transactions__unlocked_set(void)
|
||||
{
|
||||
git_oid id;
|
||||
|
||||
cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master"));
|
||||
git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
|
||||
cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/foo", &id, NULL, NULL));
|
||||
cl_git_pass(git_transaction_commit(g_tx));
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user