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) { if (!arg) {
struct reftable_addition *addition; struct reftable_addition *addition;


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


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


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


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


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


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


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


ret = reftable_stack_new_addition(&add, be->stack, ret = reftable_stack_addition_new(&add, be->stack,
&reftable_be_write_options(refs)->opts, &reftable_be_write_options(refs)->opts);
REFTABLE_STACK_NEW_ADDITION_RELOAD);
if (ret < 0) if (ret < 0)
goto done; 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. */ /* holds a transaction to add tables at the top of a stack. */
struct reftable_addition; 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 * 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_* * effect, the ref database is locked.
* flags.
*/ */
int reftable_stack_new_addition(struct reftable_addition **dest, int reftable_stack_addition_new(struct reftable_addition **dest,
struct reftable_stack *st, struct reftable_stack *st,
const struct reftable_write_options *opts, const struct reftable_write_options *opts);
unsigned int flags);


/* Adds a reftable to transaction. */ /* Adds a reftable to transaction. */
int reftable_addition_add(struct reftable_addition *add, 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 * Add a new table to the stack. The write_table function must call
* reftable_writer_set_limits, add refs and return an error value. * 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 reftable_stack_add(struct reftable_stack *st,
int (*write_table)(struct reftable_writer *wr, int (*write_table)(struct reftable_writer *wr,
void *write_arg), void *write_arg),
void *write_arg, void *write_arg,
const struct reftable_write_options *opts, const struct reftable_write_options *opts);
unsigned flags);


struct reftable_iterator; struct reftable_iterator;



View File

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


p->list_lock = REFTABLE_FLOCK_INIT;

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


/* /*
* Check whether the given stack is up-to-date with what we have in memory. * 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 * Returns 0 if so, 1 if the stack is out-of-date or a negative error code
* otherwise. * otherwise.
*/ */
static int stack_uptodate(struct reftable_stack *st) static int stack_uptodate(struct reftable_stack *st, int skip_if_locked)
{ {
char **names = NULL; char **names = NULL;
int err; 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 * When we have cached stat information available then we use it to
* verify whether the file has been rewritten. * verify whether the file has been rewritten.
@ -621,17 +630,23 @@ done:


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


struct reftable_addition { struct reftable_addition {
struct reftable_flock tables_list_lock;
struct reftable_stack *stack; struct reftable_stack *stack;
struct reftable_write_options opts; 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; char **new_tables;
size_t new_tables_len, new_tables_cap; size_t new_tables_len, new_tables_cap;
uint64_t next_update_index; 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_len = 0;
add->new_tables_cap = 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); reftable_buf_release(&nm);
} }


static int reftable_stack_init_addition(struct reftable_addition *add, static int reftable_stack_init_addition(struct reftable_addition *add,
struct reftable_stack *st, struct reftable_stack *st,
const struct reftable_write_options *opts, const struct reftable_write_options *opts)
unsigned int flags)
{ {
struct reftable_buf lock_file_name = REFTABLE_BUF_INIT; struct reftable_buf lock_file_name = REFTABLE_BUF_INIT;
int err; int err;
@ -670,31 +686,28 @@ static int reftable_stack_init_addition(struct reftable_addition *add,
if (opts) if (opts)
add->opts = *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); add->opts.lock_timeout_ms);
if (err < 0) if (err < 0)
goto done; goto done;
add->locked = 1;


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


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


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


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


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


@ -787,7 +792,7 @@ int reftable_addition_commit(struct reftable_addition *add)
goto done; 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); table_list.buf, table_list.len);
reftable_buf_release(&table_list); reftable_buf_release(&table_list);
if (err < 0) { if (err < 0) {
@ -795,17 +800,18 @@ int reftable_addition_commit(struct reftable_addition *add)
goto done; goto done;
} }


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


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


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


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


@ -852,7 +857,7 @@ int reftable_stack_new_addition(struct reftable_addition **dest,
if (!*dest) if (!*dest)
return REFTABLE_OUT_OF_MEMORY_ERROR; 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) { if (err) {
reftable_free(*dest); reftable_free(*dest);
*dest = NULL; *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 * we could check that relevant tables still exist. But for now it's
* good enough to just abort. * good enough to just abort.
*/ */
err = stack_uptodate(st); err = stack_uptodate(st, 0);
if (err < 0) if (err < 0)
goto done; goto done;
if (err > 0) { 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 * tables with our compacted version. If they don't, then we need to
* abort. * abort.
*/ */
err = stack_uptodate(st); err = stack_uptodate(st, 0);
if (err < 0) if (err < 0)
goto done; goto done;
if (err > 0) { 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) int reftable_stack_clean(struct reftable_stack *st)
{ {
struct reftable_addition *add = NULL; struct reftable_addition *add = NULL;
int err = reftable_stack_new_addition(&add, st, NULL, 0); int err = reftable_stack_addition_new(&add, st, NULL);
if (err < 0) {
goto done;
}

err = reftable_stack_reload(st);
if (err < 0) { if (err < 0) {
goto done; goto done;
} }

View File

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


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


struct reftable_stack { struct reftable_stack {
@ -18,6 +17,12 @@ struct reftable_stack {
char *list_file; char *list_file;
int list_fd; 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; char *reftable_dir;


struct reftable_stack_options opts; 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_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);


cl_assert_equal_i(reftable_stack_add(st, 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); err = reftable_new_stack(&st, dir, NULL);
cl_assert(!err); 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); cl_assert(!err);


err = reftable_stack_read_ref(st, ref.refname, &dest); 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(&st1, dir, NULL), 0);
cl_assert_equal_i(reftable_new_stack(&st2, 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, 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, cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
&ref2, NULL, 0), REFTABLE_OUTDATED_ERROR); &ref2, NULL), 0);
cl_assert_equal_i(reftable_stack_reload(st2), 0);
cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
&ref2, NULL, 0), 0);
reftable_stack_destroy(st1); reftable_stack_destroy(st1);
reftable_stack_destroy(st2); reftable_stack_destroy(st2);
clear_dir(dir); clear_dir(dir);
@ -260,7 +257,7 @@ void test_reftable_stack__transaction_api(void)


reftable_addition_destroy(add); 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, cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
&ref), 0); &ref), 0);
cl_assert_equal_i(reftable_addition_commit(add), 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(&st1, dir, NULL), 0);
cl_assert_equal_i(reftable_new_stack(&st2, 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, cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
&refs[0]), 0); &refs[0]), 0);
cl_assert_equal_i(reftable_addition_commit(add), 0); cl_assert_equal_i(reftable_addition_commit(add), 0);
reftable_addition_destroy(add); reftable_addition_destroy(add);


/* /*
* The second stack is now outdated, which we should notice. We do not * The second stack is now outdated, but it should automatically reload it
* create the addition and lock the stack by default, but allow the * with the newer updates.
* reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.
*/ */
cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, 0), cl_assert_equal_i(reftable_stack_addition_new(&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_addition_add(add, write_test_ref, cl_assert_equal_i(reftable_addition_add(add, write_test_ref,
&refs[1]), 0); &refs[1]), 0);
cl_assert_equal_i(reftable_addition_commit(add), 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 * we can ensure that we indeed honor this setting and have
* better control over when exactly auto compaction runs. * better control over when exactly auto compaction runs.
*/ */
cl_assert_equal_i(reftable_stack_new_addition(&add, cl_assert_equal_i(reftable_stack_addition_new(&add,
st, &write_opts, 0), 0); st, &write_opts), 0);
cl_assert_equal_i(reftable_addition_add(add, cl_assert_equal_i(reftable_addition_add(add,
write_test_ref, &ref), 0); write_test_ref, &ref), 0);
cl_assert_equal_i(reftable_addition_commit(add), 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_new_stack(&st, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref, 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->merged->tables_len, 1);
cl_assert_equal_i(st->stats.attempts, 0); cl_assert_equal_i(st->stats.attempts, 0);
cl_assert_equal_i(st->stats.failures, 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); write_file_buf(table_path.buf, "", 0);


ref.update_index = 2; 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(!err);
cl_assert_equal_i(st->merged->tables_len, 2); cl_assert_equal_i(st->merged->tables_len, 2);
cl_assert_equal_i(st->stats.attempts, 1); 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_new_stack(&st, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref, 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, 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); reftable_stack_destroy(st);
clear_dir(dir); 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); cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0);
for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)
cl_assert_equal_i(reftable_stack_add(st, write_error, cl_assert_equal_i(reftable_stack_add(st, write_error,
&i, NULL, 0), i); &i, NULL), i);


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


for (i = 0; i < N; i++) for (i = 0; i < N; i++)
cl_assert_equal_i(reftable_stack_add(st, write_test_ref, 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++) { for (i = 0; i < N; i++) {
struct write_log_arg arg = { struct write_log_arg arg = {
@ -521,7 +514,7 @@ void test_reftable_stack__add(void)
.update_index = reftable_stack_next_update_index(st), .update_index = reftable_stack_next_update_index(st),
}; };
cl_assert_equal_i(reftable_stack_add(st, write_test_log, 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); 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++) for (i = 0; i < N; i++)
cl_assert_equal_i(reftable_stack_add(st, write_test_ref, 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++) { for (i = 0; i < N; i++) {
struct write_log_arg arg = { 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, 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); 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"; input.value.update.message = (char *) "one\ntwo";
cl_assert_equal_i(reftable_stack_add(st, write_test_log, 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"; input.value.update.message = (char *) "one";
cl_assert_equal_i(reftable_stack_add(st, write_test_log, 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, cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
&dest), 0); &dest), 0);
cl_assert_equal_s(dest.value.update.message, "one\n"); 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"; input.value.update.message = (char *) "two\n";
arg.update_index = 2; arg.update_index = 2;
cl_assert_equal_i(reftable_stack_add(st, write_test_log, 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, cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
&dest), 0); &dest), 0);
cl_assert_equal_s(dest.value.update.message, "two\n"); 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++) for (i = 0; i < N; i++)
cl_assert_equal_i(reftable_stack_add(st, write_test_ref, 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++) { for (i = 0; i < N; i++) {
struct write_log_arg arg = { struct write_log_arg arg = {
@ -755,7 +748,7 @@ void test_reftable_stack__tombstone(void)
.update_index = reftable_stack_next_update_index(st), .update_index = reftable_stack_next_update_index(st),
}; };
cl_assert_equal_i(reftable_stack_add(st, write_test_log, 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", 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_new_stack(&st, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref, 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. */ /* can't read it with the wrong hash ID. */
cl_assert_equal_i(reftable_new_stack(&st32, dir, 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), .update_index = reftable_stack_next_update_index(st),
}; };
cl_assert_equal_i(reftable_stack_add(st, write_test_log, 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); 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_new_stack(&st, dir, NULL), 0);
cl_assert_equal_i(reftable_stack_add(st, write_nothing, 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); cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0);
clear_dir(dir); clear_dir(dir);
reftable_stack_destroy(st); 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); 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); cl_assert(!err);


err = reftable_stack_auto_compact(st, &opts); 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); 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(!err);


cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5)); 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; ref.refname = buf;


cl_assert_equal_i(reftable_stack_add(st, write_test_ref, 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 * 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); 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 * 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); reftable_stack_destroy(st);
clear_dir(dir); 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);
}