Commit 10cf4b26 by Edward Thomson

Merge pull request #2448 from libgit2/cmn/reference-transaction

Introduce reference transactions
parents 8be28acf c327d5db
......@@ -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
......
/*
* 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;
......
......@@ -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
......@@ -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 (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)
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;
}
}
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
......@@ -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;
}
/*
* 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);
}
#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));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment