Merge branch 'kn/reftable-optimize-reloading' into jch

The reftable code has been optimized to avoid an unnecessary
stat/reload of the stack when an addition already holds the
list_file lock, reducing the number of newfstatat syscalls from
linear to constant when writing refs.

* kn/reftable-optimize-reloading:
  reftable/stack: avoid reloading the stack when already locked
  reftable/stack: move list lock to `struct reftable_stack`
  reftable/stack: rename reftable_stack_new_addition()
  reftable/stack: remove `REFTABLE_STACK_NEW_ADDITION_RELOAD`
jch
Junio C Hamano 2026-08-31 11:11:58 -07:00
commit 411e51a37d
5 changed files with 119 additions and 110 deletions

View File

@ -1001,9 +1001,8 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,
if (!arg) {
struct reftable_addition *addition;

ret = reftable_stack_new_addition(&addition, be->stack,
&reftable_be_write_options(refs)->opts,
REFTABLE_STACK_NEW_ADDITION_RELOAD);
ret = reftable_stack_addition_new(&addition, be->stack,
&reftable_be_write_options(refs)->opts);
if (ret) {
if (ret == REFTABLE_LOCK_ERROR)
strbuf_addstr(err, "cannot lock references");
@ -2009,8 +2008,7 @@ static int reftable_be_rename_ref(struct ref_store *ref_store,
if (ret)
goto done;
ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
&reftable_be_write_options(refs)->opts,
REFTABLE_STACK_NEW_ADDITION_RELOAD);
&reftable_be_write_options(refs)->opts);

done:
assert(ret != REFTABLE_API_ERROR);
@ -2040,8 +2038,7 @@ static int reftable_be_copy_ref(struct ref_store *ref_store,
if (ret)
goto done;
ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
&reftable_be_write_options(refs)->opts,
REFTABLE_STACK_NEW_ADDITION_RELOAD);
&reftable_be_write_options(refs)->opts);

done:
assert(ret != REFTABLE_API_ERROR);
@ -2423,8 +2420,7 @@ static int reftable_be_create_reflog(struct ref_store *ref_store,
arg.stack = be->stack;

ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg,
&reftable_be_write_options(refs)->opts,
REFTABLE_STACK_NEW_ADDITION_RELOAD);
&reftable_be_write_options(refs)->opts);

done:
return ret;
@ -2498,8 +2494,7 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store,
arg.stack = be->stack;

ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg,
&reftable_be_write_options(refs)->opts,
REFTABLE_STACK_NEW_ADDITION_RELOAD);
&reftable_be_write_options(refs)->opts);

assert(ret != REFTABLE_API_ERROR);
return ret;
@ -2620,9 +2615,8 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
if (ret < 0)
goto done;

ret = reftable_stack_new_addition(&add, be->stack,
&reftable_be_write_options(refs)->opts,
REFTABLE_STACK_NEW_ADDITION_RELOAD);
ret = reftable_stack_addition_new(&add, be->stack,
&reftable_be_write_options(refs)->opts);
if (ret < 0)
goto done;


View File

@ -58,22 +58,13 @@ uint64_t reftable_stack_next_update_index(struct reftable_stack *st);
/* holds a transaction to add tables at the top of a stack. */
struct reftable_addition;

enum {
/*
* Reload the stack when the stack is out-of-date after locking it.
*/
REFTABLE_STACK_NEW_ADDITION_RELOAD = (1 << 0),
};

/*
* returns a new transaction to add reftables to the given stack. As a side
* effect, the ref database is locked. Accepts REFTABLE_STACK_NEW_ADDITION_*
* flags.
* effect, the ref database is locked.
*/
int reftable_stack_new_addition(struct reftable_addition **dest,
int reftable_stack_addition_new(struct reftable_addition **dest,
struct reftable_stack *st,
const struct reftable_write_options *opts,
unsigned int flags);
const struct reftable_write_options *opts);

/* Adds a reftable to transaction. */
int reftable_addition_add(struct reftable_addition *add,
@ -93,14 +84,12 @@ void reftable_addition_destroy(struct reftable_addition *add);
/*
* Add a new table to the stack. The write_table function must call
* reftable_writer_set_limits, add refs and return an error value.
* The flags are passed through to `reftable_stack_new_addition()`.
*/
int reftable_stack_add(struct reftable_stack *st,
int (*write_table)(struct reftable_writer *wr,
void *write_arg),
void *write_arg,
const struct reftable_write_options *opts,
unsigned flags);
const struct reftable_write_options *opts);

struct reftable_iterator;


View File

@ -536,6 +536,8 @@ int reftable_new_stack(struct reftable_stack **dest, const char *dir,
goto out;
}

p->list_lock = REFTABLE_FLOCK_INIT;

err = reftable_stack_reload_maybe_reuse(p, 1);
if (err < 0)
goto out;
@ -551,14 +553,21 @@ out:

/*
* Check whether the given stack is up-to-date with what we have in memory.
* If skip_if_locked is set skip stack reloading if the stack is currently
* locked. Stack reloading must _not_ be skipped right after obtaining the
* lock, to check for concurrent updates which may have happened.
*
* Returns 0 if so, 1 if the stack is out-of-date or a negative error code
* otherwise.
*/
static int stack_uptodate(struct reftable_stack *st)
static int stack_uptodate(struct reftable_stack *st, int skip_if_locked)
{
char **names = NULL;
int err;

if (skip_if_locked && st->list_lock.fd != -1)
return 0;

/*
* When we have cached stat information available then we use it to
* verify whether the file has been rewritten.
@ -621,17 +630,23 @@ done:

int reftable_stack_reload(struct reftable_stack *st)
{
int err = stack_uptodate(st);
int err = stack_uptodate(st, 1);
if (err > 0)
return reftable_stack_reload_maybe_reuse(st, 1);
return err;
}

struct reftable_addition {
struct reftable_flock tables_list_lock;
struct reftable_stack *stack;
struct reftable_write_options opts;

/*
* While the list lock is acquired on the stack, we need to distinguish
* which 'reftable_addition' is responsible for the lock. This avoids
* clearing the lock of another 'reftable_addition'.
*/
unsigned int locked : 1;

char **new_tables;
size_t new_tables_len, new_tables_cap;
uint64_t next_update_index;
@ -653,14 +668,15 @@ static void reftable_addition_close(struct reftable_addition *add)
add->new_tables_len = 0;
add->new_tables_cap = 0;

flock_release(&add->tables_list_lock);
if (add->locked)
flock_release(&add->stack->list_lock);
add->locked = 0;
reftable_buf_release(&nm);
}

static int reftable_stack_init_addition(struct reftable_addition *add,
struct reftable_stack *st,
const struct reftable_write_options *opts,
unsigned int flags)
const struct reftable_write_options *opts)
{
struct reftable_buf lock_file_name = REFTABLE_BUF_INIT;
int err;
@ -670,31 +686,28 @@ static int reftable_stack_init_addition(struct reftable_addition *add,
if (opts)
add->opts = *opts;

err = flock_acquire(&add->tables_list_lock, st->list_file,
err = flock_acquire(&add->stack->list_lock, st->list_file,
add->opts.lock_timeout_ms);
if (err < 0)
goto done;
add->locked = 1;

if (add->opts.default_permissions) {
if (chmod(add->tables_list_lock.path,
if (chmod(add->stack->list_lock.path,
add->opts.default_permissions) < 0) {
err = REFTABLE_IO_ERROR;
goto done;
}
}

err = stack_uptodate(st);
err = stack_uptodate(st, 0);
if (err < 0)
goto done;
if (err > 0 && flags & REFTABLE_STACK_NEW_ADDITION_RELOAD) {
if (err > 0) {
err = reftable_stack_reload_maybe_reuse(add->stack, 1);
if (err)
goto done;
}
if (err > 0) {
err = REFTABLE_OUTDATED_ERROR;
goto done;
}

add->next_update_index = reftable_stack_next_update_index(st);
done:
@ -708,13 +721,12 @@ static int stack_try_add(struct reftable_stack *st,
int (*write_table)(struct reftable_writer *wr,
void *arg),
void *arg,
const struct reftable_write_options *opts,
unsigned flags)
const struct reftable_write_options *opts)
{
struct reftable_addition add;
int err;

err = reftable_stack_init_addition(&add, st, opts, flags);
err = reftable_stack_init_addition(&add, st, opts);
if (err < 0)
goto done;

@ -731,17 +743,10 @@ done:
int reftable_stack_add(struct reftable_stack *st,
int (*write)(struct reftable_writer *wr, void *arg),
void *arg,
const struct reftable_write_options *opts,
unsigned flags)
const struct reftable_write_options *opts)
{
int err = stack_try_add(st, write, arg, opts, flags);
int err = stack_try_add(st, write, arg, opts);
if (err < 0) {
if (err == REFTABLE_OUTDATED_ERROR) {
/* Ignore error return, we want to propagate
REFTABLE_OUTDATED_ERROR.
*/
reftable_stack_reload(st);
}
return err;
}

@ -787,7 +792,7 @@ int reftable_addition_commit(struct reftable_addition *add)
goto done;
}

err = reftable_write_data(add->tables_list_lock.fd,
err = reftable_write_data(add->stack->list_lock.fd,
table_list.buf, table_list.len);
reftable_buf_release(&table_list);
if (err < 0) {
@ -795,17 +800,18 @@ int reftable_addition_commit(struct reftable_addition *add)
goto done;
}

err = fsync(add->tables_list_lock.fd);
err = fsync(add->stack->list_lock.fd);
if (err < 0) {
err = REFTABLE_IO_ERROR;
goto done;
}

err = flock_commit(&add->tables_list_lock);
err = flock_commit(&add->stack->list_lock);
if (err < 0) {
err = REFTABLE_IO_ERROR;
goto done;
}
add->locked = 0;

/* success, no more state to clean up. */
for (i = 0; i < add->new_tables_len; i++)
@ -841,10 +847,9 @@ done:
return err;
}

int reftable_stack_new_addition(struct reftable_addition **dest,
int reftable_stack_addition_new(struct reftable_addition **dest,
struct reftable_stack *st,
const struct reftable_write_options *opts,
unsigned int flags)
const struct reftable_write_options *opts)
{
int err;

@ -852,7 +857,7 @@ int reftable_stack_new_addition(struct reftable_addition **dest,
if (!*dest)
return REFTABLE_OUT_OF_MEMORY_ERROR;

err = reftable_stack_init_addition(*dest, st, opts, flags);
err = reftable_stack_init_addition(*dest, st, opts);
if (err) {
reftable_free(*dest);
*dest = NULL;
@ -1202,7 +1207,7 @@ static int stack_compact_range(struct reftable_stack *st,
* we could check that relevant tables still exist. But for now it's
* good enough to just abort.
*/
err = stack_uptodate(st);
err = stack_uptodate(st, 0);
if (err < 0)
goto done;
if (err > 0) {
@ -1321,7 +1326,7 @@ static int stack_compact_range(struct reftable_stack *st,
* tables with our compacted version. If they don't, then we need to
* abort.
*/
err = stack_uptodate(st);
err = stack_uptodate(st, 0);
if (err < 0)
goto done;
if (err > 0) {
@ -1840,12 +1845,7 @@ static int reftable_stack_clean_locked(struct reftable_stack *st)
int reftable_stack_clean(struct reftable_stack *st)
{
struct reftable_addition *add = NULL;
int err = reftable_stack_new_addition(&add, st, NULL, 0);
if (err < 0) {
goto done;
}

err = reftable_stack_reload(st);
int err = reftable_stack_addition_new(&add, st, NULL);
if (err < 0) {
goto done;
}

View File

@ -10,7 +10,6 @@
#define STACK_H

#include "system.h"
#include "reftable-writer.h"
#include "reftable-stack.h"

struct reftable_stack {
@ -18,6 +17,12 @@ struct reftable_stack {
char *list_file;
int list_fd;

/*
* Set while an addition holds the stack locked. Used by
* stack_uptodate() to skip reload checks while locked.
*/
struct reftable_flock list_lock;

char *reftable_dir;

struct reftable_stack_options opts;

View File

@ -127,7 +127,7 @@ static void write_n_ref_tables(struct reftable_stack *st,
cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);

cl_assert_equal_i(reftable_stack_add(st,
&write_test_ref, &ref, &opts, 0), 0);
&write_test_ref, &ref, &opts), 0);
}
}

@ -168,7 +168,7 @@ void test_reftable_stack__add_one(void)
err = reftable_new_stack(&st, dir, NULL);
cl_assert(!err);

err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0);
err = reftable_stack_add(st, write_test_ref, &ref, &opts);
cl_assert(!err);

err = reftable_stack_read_ref(st, ref.refname, &dest);
@ -231,12 +231,9 @@ void test_reftable_stack__uptodate(void)
cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);
cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st1, write_test_ref,
&ref1, NULL, 0), 0);
&ref1, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
&ref2, NULL, 0), REFTABLE_OUTDATED_ERROR);
cl_assert_equal_i(reftable_stack_reload(st2), 0);
cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
&ref2, NULL, 0), 0);
&ref2, NULL), 0);
reftable_stack_destroy(st1);
reftable_stack_destroy(st2);
clear_dir(dir);
@ -260,7 +257,7 @@ void test_reftable_stack__transaction_api(void)

reftable_addition_destroy(add);

cl_assert_equal_i(reftable_stack_new_addition(&add, st, NULL, 0), 0);
cl_assert_equal_i(reftable_stack_addition_new(&add, st, NULL), 0);
cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
&ref), 0);
cl_assert_equal_i(reftable_addition_commit(add), 0);
@ -301,21 +298,17 @@ void test_reftable_stack__transaction_with_reload(void)

cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0);
cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_new_addition(&add, st1, NULL, 0), 0);
cl_assert_equal_i(reftable_stack_addition_new(&add, st1, NULL), 0);
cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
&refs[0]), 0);
cl_assert_equal_i(reftable_addition_commit(add), 0);
reftable_addition_destroy(add);

/*
* The second stack is now outdated, which we should notice. We do not
* create the addition and lock the stack by default, but allow the
* reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.
* The second stack is now outdated, but it should automatically reload it
* with the newer updates.
*/
cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, 0),
REFTABLE_OUTDATED_ERROR);
cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL,
REFTABLE_STACK_NEW_ADDITION_RELOAD), 0);
cl_assert_equal_i(reftable_stack_addition_new(&add, st2, NULL), 0);
cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
&refs[1]), 0);
cl_assert_equal_i(reftable_addition_commit(add), 0);
@ -362,8 +355,8 @@ void test_reftable_stack__transaction_api_performs_auto_compaction(void)
* we can ensure that we indeed honor this setting and have
* better control over when exactly auto compaction runs.
*/
cl_assert_equal_i(reftable_stack_new_addition(&add,
st, &write_opts, 0), 0);
cl_assert_equal_i(reftable_stack_addition_new(&add,
st, &write_opts), 0);
cl_assert_equal_i(reftable_addition_add(add,
write_test_ref, &ref), 0);
cl_assert_equal_i(reftable_addition_commit(add), 0);
@ -400,7 +393,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)

cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
&ref, NULL, 0), 0);
&ref, NULL), 0);
cl_assert_equal_i(st->merged->tables_len, 1);
cl_assert_equal_i(st->stats.attempts, 0);
cl_assert_equal_i(st->stats.failures, 0);
@ -418,7 +411,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)
write_file_buf(table_path.buf, "", 0);

ref.update_index = 2;
err = reftable_stack_add(st, write_test_ref, &ref, NULL, 0);
err = reftable_stack_add(st, write_test_ref, &ref, NULL);
cl_assert(!err);
cl_assert_equal_i(st->merged->tables_len, 2);
cl_assert_equal_i(st->stats.attempts, 1);
@ -453,9 +446,9 @@ void test_reftable_stack__update_index_check(void)

cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
&ref1, NULL, 0), 0);
&ref1, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
&ref2, NULL, 0), REFTABLE_API_ERROR);
&ref2, NULL), REFTABLE_API_ERROR);
reftable_stack_destroy(st);
clear_dir(dir);
}
@ -469,7 +462,7 @@ void test_reftable_stack__lock_failure(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)
cl_assert_equal_i(reftable_stack_add(st, write_error,
&i, NULL, 0), i);
&i, NULL), i);

reftable_stack_destroy(st);
clear_dir(dir);
@ -513,7 +506,7 @@ void test_reftable_stack__add(void)

for (i = 0; i < N; i++)
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
&refs[i], &opts, 0), 0);
&refs[i], &opts), 0);

for (i = 0; i < N; i++) {
struct write_log_arg arg = {
@ -521,7 +514,7 @@ void test_reftable_stack__add(void)
.update_index = reftable_stack_next_update_index(st),
};
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
&arg, &opts, 0), 0);
&arg, &opts), 0);
}

cl_assert_equal_i(reftable_stack_compact_all(st, &opts, NULL), 0);
@ -604,7 +597,7 @@ void test_reftable_stack__iterator(void)

for (i = 0; i < N; i++)
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
&refs[i], NULL, 0), 0);
&refs[i], NULL), 0);

for (i = 0; i < N; i++) {
struct write_log_arg arg = {
@ -613,7 +606,7 @@ void test_reftable_stack__iterator(void)
};

cl_assert_equal_i(reftable_stack_add(st, write_test_log,
&arg, NULL, 0), 0);
&arg, NULL), 0);
}

reftable_stack_init_ref_iterator(st, &it);
@ -685,11 +678,11 @@ void test_reftable_stack__log_normalize(void)

input.value.update.message = (char *) "one\ntwo";
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
&arg, NULL, 0), REFTABLE_API_ERROR);
&arg, NULL), REFTABLE_API_ERROR);

input.value.update.message = (char *) "one";
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
&arg, NULL, 0), 0);
&arg, NULL), 0);
cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
&dest), 0);
cl_assert_equal_s(dest.value.update.message, "one\n");
@ -697,7 +690,7 @@ void test_reftable_stack__log_normalize(void)
input.value.update.message = (char *) "two\n";
arg.update_index = 2;
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
&arg, NULL, 0), 0);
&arg, NULL), 0);
cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
&dest), 0);
cl_assert_equal_s(dest.value.update.message, "two\n");
@ -747,7 +740,7 @@ void test_reftable_stack__tombstone(void)
}
for (i = 0; i < N; i++)
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
&refs[i], NULL, 0), 0);
&refs[i], NULL), 0);

for (i = 0; i < N; i++) {
struct write_log_arg arg = {
@ -755,7 +748,7 @@ void test_reftable_stack__tombstone(void)
.update_index = reftable_stack_next_update_index(st),
};
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
&arg, NULL, 0), 0);
&arg, NULL), 0);
}

cl_assert_equal_i(reftable_stack_read_ref(st, "branch",
@ -801,7 +794,7 @@ void test_reftable_stack__hash_id(void)

cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
&ref, NULL, 0), 0);
&ref, NULL), 0);

/* can't read it with the wrong hash ID. */
cl_assert_equal_i(reftable_new_stack(&st32, dir,
@ -869,7 +862,7 @@ void test_reftable_stack__reflog_expire(void)
.update_index = reftable_stack_next_update_index(st),
};
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
&arg, NULL, 0), 0);
&arg, NULL), 0);
}

cl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0);
@ -908,7 +901,7 @@ void test_reftable_stack__empty_add(void)

cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_nothing,
NULL, NULL, 0), 0);
NULL, NULL), 0);
cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
clear_dir(dir);
reftable_stack_destroy(st);
@ -947,7 +940,7 @@ void test_reftable_stack__auto_compaction(void)
};
snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);

err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0);
err = reftable_stack_add(st, write_test_ref, &ref, &opts);
cl_assert(!err);

err = reftable_stack_auto_compact(st, &opts);
@ -983,7 +976,7 @@ void test_reftable_stack__auto_compaction_factor(void)
};
xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);

err = reftable_stack_add(st, &write_test_ref, &ref, &opts, 0);
err = reftable_stack_add(st, &write_test_ref, &ref, &opts);
cl_assert(!err);

cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5));
@ -1064,7 +1057,7 @@ void test_reftable_stack__add_performs_auto_compaction(void)
ref.refname = buf;

cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
&ref, &write_opts, 0), 0);
&ref, &write_opts), 0);

/*
* The stack length should grow continuously for all runs where
@ -1303,7 +1296,7 @@ void test_reftable_stack__invalid_limit_updates(void)

reftable_addition_destroy(add);

cl_assert_equal_i(reftable_stack_new_addition(&add, st, &opts, 0), 0);
cl_assert_equal_i(reftable_stack_addition_new(&add, st, &opts), 0);

/*
* write_limits_after_ref also updates the update indexes after adding
@ -1317,3 +1310,31 @@ void test_reftable_stack__invalid_limit_updates(void)
reftable_stack_destroy(st);
clear_dir(dir);
}

void test_reftable_stack__two_additions(void)
{
struct reftable_stack *st = NULL;
char *dir = get_tmp_dir(__LINE__);
struct reftable_addition *add1 = NULL;
struct reftable_addition *add2 = NULL;

struct reftable_ref_record ref = {
.refname = (char *) "HEAD",
.update_index = 1,
.value_type = REFTABLE_REF_SYMREF,
.value.symref = (char *) "master",
};

cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);

cl_assert_equal_i(reftable_stack_addition_new(&add1, st, NULL), 0);
cl_assert_equal_i(reftable_stack_addition_new(&add2, st, NULL), REFTABLE_LOCK_ERROR);

cl_assert_equal_i(reftable_addition_add(add1, write_test_ref, &ref), 0);

cl_assert_equal_i(reftable_addition_commit(add1), 0);

reftable_addition_destroy(add1);
reftable_stack_destroy(st);
clear_dir(dir);
}