mirror of
https://github.com/git/git
synced 2024-09-17 23:41:33 +00:00
Merge branch 'mh/packed-ref-transactions'
Implement transactional update to the packed-ref representation of references. * mh/packed-ref-transactions: files_transaction_finish(): delete reflogs before references packed-backend: rip out some now-unused code files_ref_store: use a transaction to update packed refs t1404: demonstrate two problems with reference transactions files_initial_transaction_commit(): use a transaction for packed refs prune_refs(): also free the linked list files_pack_refs(): use a reference transaction to write packed refs packed_delete_refs(): implement method packed_ref_store: implement reference transactions struct ref_transaction: add a place for backends to store data packed-backend: don't adjust the reference count on lock/unlock
This commit is contained in:
commit
07f0542da3
|
@ -1041,11 +1041,17 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
|
||||||
strbuf_release(&err);
|
strbuf_release(&err);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prune_refs(struct files_ref_store *refs, struct ref_to_prune *r)
|
/*
|
||||||
|
* Prune the loose versions of the references in the linked list
|
||||||
|
* `*refs_to_prune`, freeing the entries in the list as we go.
|
||||||
|
*/
|
||||||
|
static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_to_prune)
|
||||||
{
|
{
|
||||||
while (r) {
|
while (*refs_to_prune) {
|
||||||
|
struct ref_to_prune *r = *refs_to_prune;
|
||||||
|
*refs_to_prune = r->next;
|
||||||
prune_ref(refs, r);
|
prune_ref(refs, r);
|
||||||
r = r->next;
|
free(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1084,6 +1090,11 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
|
||||||
int ok;
|
int ok;
|
||||||
struct ref_to_prune *refs_to_prune = NULL;
|
struct ref_to_prune *refs_to_prune = NULL;
|
||||||
struct strbuf err = STRBUF_INIT;
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
struct ref_transaction *transaction;
|
||||||
|
|
||||||
|
transaction = ref_store_transaction_begin(refs->packed_ref_store, &err);
|
||||||
|
if (!transaction)
|
||||||
|
return -1;
|
||||||
|
|
||||||
packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err);
|
packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err);
|
||||||
|
|
||||||
|
@ -1099,12 +1110,14 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create an entry in the packed-refs cache equivalent
|
* Add a reference creation for this reference to the
|
||||||
* to the one from the loose ref cache, except that
|
* packed-refs transaction:
|
||||||
* we don't copy the peeled status, because we want it
|
|
||||||
* to be re-peeled.
|
|
||||||
*/
|
*/
|
||||||
add_packed_ref(refs->packed_ref_store, iter->refname, iter->oid);
|
if (ref_transaction_update(transaction, iter->refname,
|
||||||
|
iter->oid->hash, NULL,
|
||||||
|
REF_NODEREF, NULL, &err))
|
||||||
|
die("failure preparing to create packed reference %s: %s",
|
||||||
|
iter->refname, err.buf);
|
||||||
|
|
||||||
/* Schedule the loose reference for pruning if requested. */
|
/* Schedule the loose reference for pruning if requested. */
|
||||||
if ((flags & PACK_REFS_PRUNE)) {
|
if ((flags & PACK_REFS_PRUNE)) {
|
||||||
|
@ -1118,11 +1131,14 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
|
||||||
if (ok != ITER_DONE)
|
if (ok != ITER_DONE)
|
||||||
die("error while iterating over references");
|
die("error while iterating over references");
|
||||||
|
|
||||||
if (commit_packed_refs(refs->packed_ref_store, &err))
|
if (ref_transaction_commit(transaction, &err))
|
||||||
die("unable to overwrite old ref-pack file: %s", err.buf);
|
die("unable to write new packed-refs: %s", err.buf);
|
||||||
|
|
||||||
|
ref_transaction_free(transaction);
|
||||||
|
|
||||||
packed_refs_unlock(refs->packed_ref_store);
|
packed_refs_unlock(refs->packed_ref_store);
|
||||||
|
|
||||||
prune_refs(refs, refs_to_prune);
|
prune_refs(refs, &refs_to_prune);
|
||||||
strbuf_release(&err);
|
strbuf_release(&err);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1141,7 +1157,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg,
|
||||||
if (packed_refs_lock(refs->packed_ref_store, 0, &err))
|
if (packed_refs_lock(refs->packed_ref_store, 0, &err))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
if (repack_without_refs(refs->packed_ref_store, refnames, &err)) {
|
if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) {
|
||||||
packed_refs_unlock(refs->packed_ref_store);
|
packed_refs_unlock(refs->packed_ref_store);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
@ -2431,13 +2447,22 @@ static int lock_ref_for_update(struct files_ref_store *refs,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct files_transaction_backend_data {
|
||||||
|
struct ref_transaction *packed_transaction;
|
||||||
|
int packed_refs_locked;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unlock any references in `transaction` that are still locked, and
|
* Unlock any references in `transaction` that are still locked, and
|
||||||
* mark the transaction closed.
|
* mark the transaction closed.
|
||||||
*/
|
*/
|
||||||
static void files_transaction_cleanup(struct ref_transaction *transaction)
|
static void files_transaction_cleanup(struct files_ref_store *refs,
|
||||||
|
struct ref_transaction *transaction)
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t i;
|
||||||
|
struct files_transaction_backend_data *backend_data =
|
||||||
|
transaction->backend_data;
|
||||||
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
|
||||||
for (i = 0; i < transaction->nr; i++) {
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
struct ref_update *update = transaction->updates[i];
|
struct ref_update *update = transaction->updates[i];
|
||||||
|
@ -2449,6 +2474,17 @@ static void files_transaction_cleanup(struct ref_transaction *transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (backend_data->packed_transaction &&
|
||||||
|
ref_transaction_abort(backend_data->packed_transaction, &err)) {
|
||||||
|
error("error aborting transaction: %s", err.buf);
|
||||||
|
strbuf_release(&err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backend_data->packed_refs_locked)
|
||||||
|
packed_refs_unlock(refs->packed_ref_store);
|
||||||
|
|
||||||
|
free(backend_data);
|
||||||
|
|
||||||
transaction->state = REF_TRANSACTION_CLOSED;
|
transaction->state = REF_TRANSACTION_CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2465,12 +2501,17 @@ static int files_transaction_prepare(struct ref_store *ref_store,
|
||||||
char *head_ref = NULL;
|
char *head_ref = NULL;
|
||||||
int head_type;
|
int head_type;
|
||||||
struct object_id head_oid;
|
struct object_id head_oid;
|
||||||
|
struct files_transaction_backend_data *backend_data;
|
||||||
|
struct ref_transaction *packed_transaction = NULL;
|
||||||
|
|
||||||
assert(err);
|
assert(err);
|
||||||
|
|
||||||
if (!transaction->nr)
|
if (!transaction->nr)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
|
backend_data = xcalloc(1, sizeof(*backend_data));
|
||||||
|
transaction->backend_data = backend_data;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fail if a refname appears more than once in the
|
* Fail if a refname appears more than once in the
|
||||||
* transaction. (If we end up splitting up any updates using
|
* transaction. (If we end up splitting up any updates using
|
||||||
|
@ -2537,6 +2578,41 @@ static int files_transaction_prepare(struct ref_store *ref_store,
|
||||||
head_ref, &affected_refnames, err);
|
head_ref, &affected_refnames, err);
|
||||||
if (ret)
|
if (ret)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if (update->flags & REF_DELETING &&
|
||||||
|
!(update->flags & REF_LOG_ONLY) &&
|
||||||
|
!(update->flags & REF_ISPRUNING)) {
|
||||||
|
/*
|
||||||
|
* This reference has to be deleted from
|
||||||
|
* packed-refs if it exists there.
|
||||||
|
*/
|
||||||
|
if (!packed_transaction) {
|
||||||
|
packed_transaction = ref_store_transaction_begin(
|
||||||
|
refs->packed_ref_store, err);
|
||||||
|
if (!packed_transaction) {
|
||||||
|
ret = TRANSACTION_GENERIC_ERROR;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
backend_data->packed_transaction =
|
||||||
|
packed_transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref_transaction_add_update(
|
||||||
|
packed_transaction, update->refname,
|
||||||
|
update->flags & ~REF_HAVE_OLD,
|
||||||
|
update->new_oid.hash, update->old_oid.hash,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packed_transaction) {
|
||||||
|
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
|
||||||
|
ret = TRANSACTION_GENERIC_ERROR;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
backend_data->packed_refs_locked = 1;
|
||||||
|
ret = ref_transaction_prepare(packed_transaction, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
@ -2544,7 +2620,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
|
||||||
string_list_clear(&affected_refnames, 0);
|
string_list_clear(&affected_refnames, 0);
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
files_transaction_cleanup(transaction);
|
files_transaction_cleanup(refs, transaction);
|
||||||
else
|
else
|
||||||
transaction->state = REF_TRANSACTION_PREPARED;
|
transaction->state = REF_TRANSACTION_PREPARED;
|
||||||
|
|
||||||
|
@ -2559,9 +2635,10 @@ static int files_transaction_finish(struct ref_store *ref_store,
|
||||||
files_downcast(ref_store, 0, "ref_transaction_finish");
|
files_downcast(ref_store, 0, "ref_transaction_finish");
|
||||||
size_t i;
|
size_t i;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
|
|
||||||
struct string_list_item *ref_to_delete;
|
|
||||||
struct strbuf sb = STRBUF_INIT;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
|
struct files_transaction_backend_data *backend_data;
|
||||||
|
struct ref_transaction *packed_transaction;
|
||||||
|
|
||||||
|
|
||||||
assert(err);
|
assert(err);
|
||||||
|
|
||||||
|
@ -2570,6 +2647,9 @@ static int files_transaction_finish(struct ref_store *ref_store,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backend_data = transaction->backend_data;
|
||||||
|
packed_transaction = backend_data->packed_transaction;
|
||||||
|
|
||||||
/* Perform updates first so live commits remain referenced */
|
/* Perform updates first so live commits remain referenced */
|
||||||
for (i = 0; i < transaction->nr; i++) {
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
struct ref_update *update = transaction->updates[i];
|
struct ref_update *update = transaction->updates[i];
|
||||||
|
@ -2605,7 +2685,44 @@ static int files_transaction_finish(struct ref_store *ref_store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Perform deletes now that updates are safely completed */
|
|
||||||
|
/*
|
||||||
|
* Now that updates are safely completed, we can perform
|
||||||
|
* deletes. First delete the reflogs of any references that
|
||||||
|
* will be deleted, since (in the unexpected event of an
|
||||||
|
* error) leaving a reference without a reflog is less bad
|
||||||
|
* than leaving a reflog without a reference (the latter is a
|
||||||
|
* mildly invalid repository state):
|
||||||
|
*/
|
||||||
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
|
struct ref_update *update = transaction->updates[i];
|
||||||
|
if (update->flags & REF_DELETING &&
|
||||||
|
!(update->flags & REF_LOG_ONLY) &&
|
||||||
|
!(update->flags & REF_ISPRUNING)) {
|
||||||
|
strbuf_reset(&sb);
|
||||||
|
files_reflog_path(refs, &sb, update->refname);
|
||||||
|
if (!unlink_or_warn(sb.buf))
|
||||||
|
try_remove_empty_parents(refs, update->refname,
|
||||||
|
REMOVE_EMPTY_PARENTS_REFLOG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Perform deletes now that updates are safely completed.
|
||||||
|
*
|
||||||
|
* First delete any packed versions of the references, while
|
||||||
|
* retaining the packed-refs lock:
|
||||||
|
*/
|
||||||
|
if (packed_transaction) {
|
||||||
|
ret = ref_transaction_commit(packed_transaction, err);
|
||||||
|
ref_transaction_free(packed_transaction);
|
||||||
|
packed_transaction = NULL;
|
||||||
|
backend_data->packed_transaction = NULL;
|
||||||
|
if (ret)
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now delete the loose versions of the references: */
|
||||||
for (i = 0; i < transaction->nr; i++) {
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
struct ref_update *update = transaction->updates[i];
|
struct ref_update *update = transaction->updates[i];
|
||||||
struct ref_lock *lock = update->backend_data;
|
struct ref_lock *lock = update->backend_data;
|
||||||
|
@ -2623,39 +2740,13 @@ static int files_transaction_finish(struct ref_store *ref_store,
|
||||||
}
|
}
|
||||||
update->flags |= REF_DELETED_LOOSE;
|
update->flags |= REF_DELETED_LOOSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(update->flags & REF_ISPRUNING))
|
|
||||||
string_list_append(&refs_to_delete,
|
|
||||||
lock->ref_name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
|
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repack_without_refs(refs->packed_ref_store, &refs_to_delete, err)) {
|
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
|
||||||
packed_refs_unlock(refs->packed_ref_store);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
packed_refs_unlock(refs->packed_ref_store);
|
|
||||||
|
|
||||||
/* Delete the reflogs of any references that were deleted: */
|
|
||||||
for_each_string_list_item(ref_to_delete, &refs_to_delete) {
|
|
||||||
strbuf_reset(&sb);
|
|
||||||
files_reflog_path(refs, &sb, ref_to_delete->string);
|
|
||||||
if (!unlink_or_warn(sb.buf))
|
|
||||||
try_remove_empty_parents(refs, ref_to_delete->string,
|
|
||||||
REMOVE_EMPTY_PARENTS_REFLOG);
|
|
||||||
}
|
|
||||||
|
|
||||||
clear_loose_ref_cache(refs);
|
clear_loose_ref_cache(refs);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
files_transaction_cleanup(transaction);
|
files_transaction_cleanup(refs, transaction);
|
||||||
|
|
||||||
for (i = 0; i < transaction->nr; i++) {
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
struct ref_update *update = transaction->updates[i];
|
struct ref_update *update = transaction->updates[i];
|
||||||
|
@ -2673,7 +2764,6 @@ static int files_transaction_finish(struct ref_store *ref_store,
|
||||||
}
|
}
|
||||||
|
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
string_list_clear(&refs_to_delete, 0);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2681,7 +2771,10 @@ static int files_transaction_abort(struct ref_store *ref_store,
|
||||||
struct ref_transaction *transaction,
|
struct ref_transaction *transaction,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
files_transaction_cleanup(transaction);
|
struct files_ref_store *refs =
|
||||||
|
files_downcast(ref_store, 0, "ref_transaction_abort");
|
||||||
|
|
||||||
|
files_transaction_cleanup(refs, transaction);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2703,6 +2796,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
|
||||||
size_t i;
|
size_t i;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
|
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
|
||||||
|
struct ref_transaction *packed_transaction = NULL;
|
||||||
|
|
||||||
assert(err);
|
assert(err);
|
||||||
|
|
||||||
|
@ -2735,6 +2829,12 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
|
||||||
&affected_refnames))
|
&affected_refnames))
|
||||||
die("BUG: initial ref transaction called with existing refs");
|
die("BUG: initial ref transaction called with existing refs");
|
||||||
|
|
||||||
|
packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err);
|
||||||
|
if (!packed_transaction) {
|
||||||
|
ret = TRANSACTION_GENERIC_ERROR;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < transaction->nr; i++) {
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
struct ref_update *update = transaction->updates[i];
|
struct ref_update *update = transaction->updates[i];
|
||||||
|
|
||||||
|
@ -2747,6 +2847,15 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
|
||||||
ret = TRANSACTION_NAME_CONFLICT;
|
ret = TRANSACTION_NAME_CONFLICT;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a reference creation for this reference to the
|
||||||
|
* packed-refs transaction:
|
||||||
|
*/
|
||||||
|
ref_transaction_add_update(packed_transaction, update->refname,
|
||||||
|
update->flags & ~REF_HAVE_OLD,
|
||||||
|
update->new_oid.hash, update->old_oid.hash,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
|
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
|
||||||
|
@ -2754,21 +2863,14 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < transaction->nr; i++) {
|
if (initial_ref_transaction_commit(packed_transaction, err)) {
|
||||||
struct ref_update *update = transaction->updates[i];
|
|
||||||
|
|
||||||
if ((update->flags & REF_HAVE_NEW) &&
|
|
||||||
!is_null_oid(&update->new_oid))
|
|
||||||
add_packed_ref(refs->packed_ref_store, update->refname,
|
|
||||||
&update->new_oid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commit_packed_refs(refs->packed_ref_store, err)) {
|
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = TRANSACTION_GENERIC_ERROR;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
if (packed_transaction)
|
||||||
|
ref_transaction_free(packed_transaction);
|
||||||
packed_refs_unlock(refs->packed_ref_store);
|
packed_refs_unlock(refs->packed_ref_store);
|
||||||
transaction->state = REF_TRANSACTION_CLOSED;
|
transaction->state = REF_TRANSACTION_CLOSED;
|
||||||
string_list_clear(&affected_refnames, 0);
|
string_list_clear(&affected_refnames, 0);
|
||||||
|
|
|
@ -91,19 +91,6 @@ struct ref_store *packed_ref_store_create(const char *path,
|
||||||
return ref_store;
|
return ref_store;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Die if refs is not the main ref store. caller is used in any
|
|
||||||
* necessary error messages.
|
|
||||||
*/
|
|
||||||
static void packed_assert_main_repository(struct packed_ref_store *refs,
|
|
||||||
const char *caller)
|
|
||||||
{
|
|
||||||
if (refs->store_flags & REF_STORE_MAIN)
|
|
||||||
return;
|
|
||||||
|
|
||||||
die("BUG: operation %s only allowed for main ref store", caller);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Downcast `ref_store` to `packed_ref_store`. Die if `ref_store` is
|
* Downcast `ref_store` to `packed_ref_store`. Die if `ref_store` is
|
||||||
* not a `packed_ref_store`. Also die if `packed_ref_store` doesn't
|
* not a `packed_ref_store`. Also die if `packed_ref_store` doesn't
|
||||||
|
@ -321,40 +308,6 @@ static struct ref_dir *get_packed_refs(struct packed_ref_store *refs)
|
||||||
return get_packed_ref_dir(get_packed_ref_cache(refs));
|
return get_packed_ref_dir(get_packed_ref_cache(refs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Add or overwrite a reference in the in-memory packed reference
|
|
||||||
* cache. This may only be called while the packed-refs file is locked
|
|
||||||
* (see packed_refs_lock()). To actually write the packed-refs file,
|
|
||||||
* call commit_packed_refs().
|
|
||||||
*/
|
|
||||||
void add_packed_ref(struct ref_store *ref_store,
|
|
||||||
const char *refname, const struct object_id *oid)
|
|
||||||
{
|
|
||||||
struct packed_ref_store *refs =
|
|
||||||
packed_downcast(ref_store, REF_STORE_WRITE,
|
|
||||||
"add_packed_ref");
|
|
||||||
struct ref_dir *packed_refs;
|
|
||||||
struct ref_entry *packed_entry;
|
|
||||||
|
|
||||||
if (!is_lock_file_locked(&refs->lock))
|
|
||||||
die("BUG: packed refs not locked");
|
|
||||||
|
|
||||||
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
|
|
||||||
die("Reference has invalid format: '%s'", refname);
|
|
||||||
|
|
||||||
packed_refs = get_packed_refs(refs);
|
|
||||||
packed_entry = find_ref_entry(packed_refs, refname);
|
|
||||||
if (packed_entry) {
|
|
||||||
/* Overwrite the existing entry: */
|
|
||||||
oidcpy(&packed_entry->u.value.oid, oid);
|
|
||||||
packed_entry->flag = REF_ISPACKED;
|
|
||||||
oidclr(&packed_entry->u.value.peeled);
|
|
||||||
} else {
|
|
||||||
packed_entry = create_ref_entry(refname, oid, REF_ISPACKED);
|
|
||||||
add_ref_entry(packed_refs, packed_entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return the ref_entry for the given refname from the packed
|
* Return the ref_entry for the given refname from the packed
|
||||||
* references. If it does not exist, return NULL.
|
* references. If it does not exist, return NULL.
|
||||||
|
@ -525,7 +478,6 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
|
||||||
"packed_refs_lock");
|
"packed_refs_lock");
|
||||||
static int timeout_configured = 0;
|
static int timeout_configured = 0;
|
||||||
static int timeout_value = 1000;
|
static int timeout_value = 1000;
|
||||||
struct packed_ref_cache *packed_ref_cache;
|
|
||||||
|
|
||||||
if (!timeout_configured) {
|
if (!timeout_configured) {
|
||||||
git_config_get_int("core.packedrefstimeout", &timeout_value);
|
git_config_get_int("core.packedrefstimeout", &timeout_value);
|
||||||
|
@ -561,9 +513,11 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
|
||||||
*/
|
*/
|
||||||
validate_packed_ref_cache(refs);
|
validate_packed_ref_cache(refs);
|
||||||
|
|
||||||
packed_ref_cache = get_packed_ref_cache(refs);
|
/*
|
||||||
/* Increment the reference count to prevent it from being freed: */
|
* Now make sure that the packed-refs file as it exists in the
|
||||||
acquire_packed_ref_cache(packed_ref_cache);
|
* locked state is loaded into the cache:
|
||||||
|
*/
|
||||||
|
get_packed_ref_cache(refs);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,7 +531,6 @@ void packed_refs_unlock(struct ref_store *ref_store)
|
||||||
if (!is_lock_file_locked(&refs->lock))
|
if (!is_lock_file_locked(&refs->lock))
|
||||||
die("BUG: packed_refs_unlock() called when not locked");
|
die("BUG: packed_refs_unlock() called when not locked");
|
||||||
rollback_lock_file(&refs->lock);
|
rollback_lock_file(&refs->lock);
|
||||||
release_packed_ref_cache(refs->cache);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int packed_refs_is_locked(struct ref_store *ref_store)
|
int packed_refs_is_locked(struct ref_store *ref_store)
|
||||||
|
@ -597,29 +550,35 @@ int packed_refs_is_locked(struct ref_store *ref_store)
|
||||||
static const char PACKED_REFS_HEADER[] =
|
static const char PACKED_REFS_HEADER[] =
|
||||||
"# pack-refs with: peeled fully-peeled \n";
|
"# pack-refs with: peeled fully-peeled \n";
|
||||||
|
|
||||||
/*
|
static int packed_init_db(struct ref_store *ref_store, struct strbuf *err)
|
||||||
* Write the current version of the packed refs cache from memory to
|
|
||||||
* disk. The packed-refs file must already be locked for writing (see
|
|
||||||
* packed_refs_lock()). Return zero on success. On errors, rollback
|
|
||||||
* the lockfile, write an error message to `err`, and return a nonzero
|
|
||||||
* value.
|
|
||||||
*/
|
|
||||||
int commit_packed_refs(struct ref_store *ref_store, struct strbuf *err)
|
|
||||||
{
|
{
|
||||||
struct packed_ref_store *refs =
|
/* Nothing to do. */
|
||||||
packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN,
|
return 0;
|
||||||
"commit_packed_refs");
|
}
|
||||||
struct packed_ref_cache *packed_ref_cache =
|
|
||||||
get_packed_ref_cache(refs);
|
/*
|
||||||
|
* Write the packed-refs from the cache to the packed-refs tempfile,
|
||||||
|
* incorporating any changes from `updates`. `updates` must be a
|
||||||
|
* sorted string list whose keys are the refnames and whose util
|
||||||
|
* values are `struct ref_update *`. On error, rollback the tempfile,
|
||||||
|
* write an error message to `err`, and return a nonzero value.
|
||||||
|
*
|
||||||
|
* The packfile must be locked before calling this function and will
|
||||||
|
* remain locked when it is done.
|
||||||
|
*/
|
||||||
|
static int write_with_updates(struct packed_ref_store *refs,
|
||||||
|
struct string_list *updates,
|
||||||
|
struct strbuf *err)
|
||||||
|
{
|
||||||
|
struct ref_iterator *iter = NULL;
|
||||||
|
size_t i;
|
||||||
int ok;
|
int ok;
|
||||||
int ret = -1;
|
|
||||||
struct strbuf sb = STRBUF_INIT;
|
|
||||||
FILE *out;
|
FILE *out;
|
||||||
struct ref_iterator *iter;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
char *packed_refs_path;
|
char *packed_refs_path;
|
||||||
|
|
||||||
if (!is_lock_file_locked(&refs->lock))
|
if (!is_lock_file_locked(&refs->lock))
|
||||||
die("BUG: commit_packed_refs() called when unlocked");
|
die("BUG: write_with_updates() called while unlocked");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If packed-refs is a symlink, we want to overwrite the
|
* If packed-refs is a symlink, we want to overwrite the
|
||||||
|
@ -628,12 +587,13 @@ int commit_packed_refs(struct ref_store *ref_store, struct strbuf *err)
|
||||||
*/
|
*/
|
||||||
packed_refs_path = get_locked_file_path(&refs->lock);
|
packed_refs_path = get_locked_file_path(&refs->lock);
|
||||||
strbuf_addf(&sb, "%s.new", packed_refs_path);
|
strbuf_addf(&sb, "%s.new", packed_refs_path);
|
||||||
|
free(packed_refs_path);
|
||||||
refs->tempfile = create_tempfile(sb.buf);
|
refs->tempfile = create_tempfile(sb.buf);
|
||||||
if (!refs->tempfile) {
|
if (!refs->tempfile) {
|
||||||
strbuf_addf(err, "unable to create file %s: %s",
|
strbuf_addf(err, "unable to create file %s: %s",
|
||||||
sb.buf, strerror(errno));
|
sb.buf, strerror(errno));
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
goto out;
|
return -1;
|
||||||
}
|
}
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
|
|
||||||
|
@ -644,131 +604,286 @@ int commit_packed_refs(struct ref_store *ref_store, struct strbuf *err)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fprintf(out, "%s", PACKED_REFS_HEADER) < 0) {
|
if (fprintf(out, "%s", PACKED_REFS_HEADER) < 0)
|
||||||
strbuf_addf(err, "error writing to %s: %s",
|
goto write_error;
|
||||||
get_tempfile_path(refs->tempfile), strerror(errno));
|
|
||||||
goto error;
|
/*
|
||||||
|
* We iterate in parallel through the current list of refs and
|
||||||
|
* the list of updates, processing an entry from at least one
|
||||||
|
* of the lists each time through the loop. When the current
|
||||||
|
* list of refs is exhausted, set iter to NULL. When the list
|
||||||
|
* of updates is exhausted, leave i set to updates->nr.
|
||||||
|
*/
|
||||||
|
iter = packed_ref_iterator_begin(&refs->base, "",
|
||||||
|
DO_FOR_EACH_INCLUDE_BROKEN);
|
||||||
|
if ((ok = ref_iterator_advance(iter)) != ITER_OK)
|
||||||
|
iter = NULL;
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
while (iter || i < updates->nr) {
|
||||||
|
struct ref_update *update = NULL;
|
||||||
|
int cmp;
|
||||||
|
|
||||||
|
if (i >= updates->nr) {
|
||||||
|
cmp = -1;
|
||||||
|
} else {
|
||||||
|
update = updates->items[i].util;
|
||||||
|
|
||||||
|
if (!iter)
|
||||||
|
cmp = +1;
|
||||||
|
else
|
||||||
|
cmp = strcmp(iter->refname, update->refname);
|
||||||
}
|
}
|
||||||
|
|
||||||
iter = cache_ref_iterator_begin(packed_ref_cache->cache, NULL, 0);
|
if (!cmp) {
|
||||||
while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
|
/*
|
||||||
|
* There is both an old value and an update
|
||||||
|
* for this reference. Check the old value if
|
||||||
|
* necessary:
|
||||||
|
*/
|
||||||
|
if ((update->flags & REF_HAVE_OLD)) {
|
||||||
|
if (is_null_oid(&update->old_oid)) {
|
||||||
|
strbuf_addf(err, "cannot update ref '%s': "
|
||||||
|
"reference already exists",
|
||||||
|
update->refname);
|
||||||
|
goto error;
|
||||||
|
} else if (oidcmp(&update->old_oid, iter->oid)) {
|
||||||
|
strbuf_addf(err, "cannot update ref '%s': "
|
||||||
|
"is at %s but expected %s",
|
||||||
|
update->refname,
|
||||||
|
oid_to_hex(iter->oid),
|
||||||
|
oid_to_hex(&update->old_oid));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now figure out what to use for the new value: */
|
||||||
|
if ((update->flags & REF_HAVE_NEW)) {
|
||||||
|
/*
|
||||||
|
* The update takes precedence. Skip
|
||||||
|
* the iterator over the unneeded
|
||||||
|
* value.
|
||||||
|
*/
|
||||||
|
if ((ok = ref_iterator_advance(iter)) != ITER_OK)
|
||||||
|
iter = NULL;
|
||||||
|
cmp = +1;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* The update doesn't actually want to
|
||||||
|
* change anything. We're done with it.
|
||||||
|
*/
|
||||||
|
i++;
|
||||||
|
cmp = -1;
|
||||||
|
}
|
||||||
|
} else if (cmp > 0) {
|
||||||
|
/*
|
||||||
|
* There is no old value but there is an
|
||||||
|
* update for this reference. Make sure that
|
||||||
|
* the update didn't expect an existing value:
|
||||||
|
*/
|
||||||
|
if ((update->flags & REF_HAVE_OLD) &&
|
||||||
|
!is_null_oid(&update->old_oid)) {
|
||||||
|
strbuf_addf(err, "cannot update ref '%s': "
|
||||||
|
"reference is missing but expected %s",
|
||||||
|
update->refname,
|
||||||
|
oid_to_hex(&update->old_oid));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmp < 0) {
|
||||||
|
/* Pass the old reference through. */
|
||||||
|
|
||||||
struct object_id peeled;
|
struct object_id peeled;
|
||||||
int peel_error = ref_iterator_peel(iter, &peeled);
|
int peel_error = ref_iterator_peel(iter, &peeled);
|
||||||
|
|
||||||
if (write_packed_entry(out, iter->refname, iter->oid->hash,
|
if (write_packed_entry(out, iter->refname,
|
||||||
peel_error ? NULL : peeled.hash)) {
|
iter->oid->hash,
|
||||||
strbuf_addf(err, "error writing to %s: %s",
|
peel_error ? NULL : peeled.hash))
|
||||||
get_tempfile_path(refs->tempfile),
|
goto write_error;
|
||||||
strerror(errno));
|
|
||||||
ref_iterator_abort(iter);
|
if ((ok = ref_iterator_advance(iter)) != ITER_OK)
|
||||||
goto error;
|
iter = NULL;
|
||||||
|
} else if (is_null_oid(&update->new_oid)) {
|
||||||
|
/*
|
||||||
|
* The update wants to delete the reference,
|
||||||
|
* and the reference either didn't exist or we
|
||||||
|
* have already skipped it. So we're done with
|
||||||
|
* the update (and don't have to write
|
||||||
|
* anything).
|
||||||
|
*/
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
struct object_id peeled;
|
||||||
|
int peel_error = peel_object(update->new_oid.hash,
|
||||||
|
peeled.hash);
|
||||||
|
|
||||||
|
if (write_packed_entry(out, update->refname,
|
||||||
|
update->new_oid.hash,
|
||||||
|
peel_error ? NULL : peeled.hash))
|
||||||
|
goto write_error;
|
||||||
|
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ok != ITER_DONE) {
|
if (ok != ITER_DONE) {
|
||||||
strbuf_addf(err, "unable to rewrite packed-refs file: "
|
strbuf_addf(err, "unable to write packed-refs file: "
|
||||||
"error iterating over old contents");
|
"error iterating over old contents");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rename_tempfile(&refs->tempfile, packed_refs_path)) {
|
if (close_tempfile_gently(refs->tempfile)) {
|
||||||
strbuf_addf(err, "error replacing %s: %s",
|
strbuf_addf(err, "error closing file %s: %s",
|
||||||
refs->path, strerror(errno));
|
get_tempfile_path(refs->tempfile),
|
||||||
goto out;
|
strerror(errno));
|
||||||
|
strbuf_release(&sb);
|
||||||
|
delete_tempfile(&refs->tempfile);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
return 0;
|
||||||
goto out;
|
|
||||||
|
write_error:
|
||||||
|
strbuf_addf(err, "error writing to %s: %s",
|
||||||
|
get_tempfile_path(refs->tempfile), strerror(errno));
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
if (iter)
|
||||||
|
ref_iterator_abort(iter);
|
||||||
|
|
||||||
|
delete_tempfile(&refs->tempfile);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct packed_transaction_backend_data {
|
||||||
|
/* True iff the transaction owns the packed-refs lock. */
|
||||||
|
int own_lock;
|
||||||
|
|
||||||
|
struct string_list updates;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void packed_transaction_cleanup(struct packed_ref_store *refs,
|
||||||
|
struct ref_transaction *transaction)
|
||||||
|
{
|
||||||
|
struct packed_transaction_backend_data *data = transaction->backend_data;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
string_list_clear(&data->updates, 0);
|
||||||
|
|
||||||
|
if (is_tempfile_active(refs->tempfile))
|
||||||
delete_tempfile(&refs->tempfile);
|
delete_tempfile(&refs->tempfile);
|
||||||
|
|
||||||
out:
|
if (data->own_lock && is_lock_file_locked(&refs->lock)) {
|
||||||
free(packed_refs_path);
|
packed_refs_unlock(&refs->base);
|
||||||
return ret;
|
data->own_lock = 0;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Rewrite the packed-refs file, omitting any refs listed in
|
|
||||||
* 'refnames'. On error, leave packed-refs unchanged, write an error
|
|
||||||
* message to 'err', and return a nonzero value. The packed refs lock
|
|
||||||
* must be held when calling this function; it will still be held when
|
|
||||||
* the function returns.
|
|
||||||
*
|
|
||||||
* The refs in 'refnames' needn't be sorted. `err` must not be NULL.
|
|
||||||
*/
|
|
||||||
int repack_without_refs(struct ref_store *ref_store,
|
|
||||||
struct string_list *refnames, struct strbuf *err)
|
|
||||||
{
|
|
||||||
struct packed_ref_store *refs =
|
|
||||||
packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN,
|
|
||||||
"repack_without_refs");
|
|
||||||
struct ref_dir *packed;
|
|
||||||
struct string_list_item *refname;
|
|
||||||
int needs_repacking = 0, removed = 0;
|
|
||||||
|
|
||||||
packed_assert_main_repository(refs, "repack_without_refs");
|
|
||||||
assert(err);
|
|
||||||
|
|
||||||
if (!is_lock_file_locked(&refs->lock))
|
|
||||||
die("BUG: repack_without_refs called without holding lock");
|
|
||||||
|
|
||||||
/* Look for a packed ref */
|
|
||||||
for_each_string_list_item(refname, refnames) {
|
|
||||||
if (get_packed_ref(refs, refname->string)) {
|
|
||||||
needs_repacking = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avoid locking if we have nothing to do */
|
free(data);
|
||||||
if (!needs_repacking)
|
transaction->backend_data = NULL;
|
||||||
return 0; /* no refname exists in packed refs */
|
|
||||||
|
|
||||||
packed = get_packed_refs(refs);
|
|
||||||
|
|
||||||
/* Remove refnames from the cache */
|
|
||||||
for_each_string_list_item(refname, refnames)
|
|
||||||
if (remove_entry_from_dir(packed, refname->string) != -1)
|
|
||||||
removed = 1;
|
|
||||||
if (!removed) {
|
|
||||||
/*
|
|
||||||
* All packed entries disappeared while we were
|
|
||||||
* acquiring the lock.
|
|
||||||
*/
|
|
||||||
clear_packed_ref_cache(refs);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write what remains */
|
transaction->state = REF_TRANSACTION_CLOSED;
|
||||||
return commit_packed_refs(&refs->base, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int packed_init_db(struct ref_store *ref_store, struct strbuf *err)
|
|
||||||
{
|
|
||||||
/* Nothing to do. */
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int packed_transaction_prepare(struct ref_store *ref_store,
|
static int packed_transaction_prepare(struct ref_store *ref_store,
|
||||||
struct ref_transaction *transaction,
|
struct ref_transaction *transaction,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
die("BUG: not implemented yet");
|
struct packed_ref_store *refs = packed_downcast(
|
||||||
|
ref_store,
|
||||||
|
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
|
||||||
|
"ref_transaction_prepare");
|
||||||
|
struct packed_transaction_backend_data *data;
|
||||||
|
size_t i;
|
||||||
|
int ret = TRANSACTION_GENERIC_ERROR;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that we *don't* skip transactions with zero updates,
|
||||||
|
* because such a transaction might be executed for the side
|
||||||
|
* effect of ensuring that all of the references are peeled.
|
||||||
|
* If the caller wants to optimize away empty transactions, it
|
||||||
|
* should do so itself.
|
||||||
|
*/
|
||||||
|
|
||||||
|
data = xcalloc(1, sizeof(*data));
|
||||||
|
string_list_init(&data->updates, 0);
|
||||||
|
|
||||||
|
transaction->backend_data = data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stick the updates in a string list by refname so that we
|
||||||
|
* can sort them:
|
||||||
|
*/
|
||||||
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
|
struct ref_update *update = transaction->updates[i];
|
||||||
|
struct string_list_item *item =
|
||||||
|
string_list_append(&data->updates, update->refname);
|
||||||
|
|
||||||
|
/* Store a pointer to update in item->util: */
|
||||||
|
item->util = update;
|
||||||
|
}
|
||||||
|
string_list_sort(&data->updates);
|
||||||
|
|
||||||
|
if (ref_update_reject_duplicates(&data->updates, err))
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
if (!is_lock_file_locked(&refs->lock)) {
|
||||||
|
if (packed_refs_lock(ref_store, 0, err))
|
||||||
|
goto failure;
|
||||||
|
data->own_lock = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write_with_updates(refs, &data->updates, err))
|
||||||
|
goto failure;
|
||||||
|
|
||||||
|
transaction->state = REF_TRANSACTION_PREPARED;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
failure:
|
||||||
|
packed_transaction_cleanup(refs, transaction);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int packed_transaction_abort(struct ref_store *ref_store,
|
static int packed_transaction_abort(struct ref_store *ref_store,
|
||||||
struct ref_transaction *transaction,
|
struct ref_transaction *transaction,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
die("BUG: not implemented yet");
|
struct packed_ref_store *refs = packed_downcast(
|
||||||
|
ref_store,
|
||||||
|
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
|
||||||
|
"ref_transaction_abort");
|
||||||
|
|
||||||
|
packed_transaction_cleanup(refs, transaction);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int packed_transaction_finish(struct ref_store *ref_store,
|
static int packed_transaction_finish(struct ref_store *ref_store,
|
||||||
struct ref_transaction *transaction,
|
struct ref_transaction *transaction,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
die("BUG: not implemented yet");
|
struct packed_ref_store *refs = packed_downcast(
|
||||||
|
ref_store,
|
||||||
|
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
|
||||||
|
"ref_transaction_finish");
|
||||||
|
int ret = TRANSACTION_GENERIC_ERROR;
|
||||||
|
char *packed_refs_path;
|
||||||
|
|
||||||
|
packed_refs_path = get_locked_file_path(&refs->lock);
|
||||||
|
if (rename_tempfile(&refs->tempfile, packed_refs_path)) {
|
||||||
|
strbuf_addf(err, "error replacing %s: %s",
|
||||||
|
refs->path, strerror(errno));
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_packed_ref_cache(refs);
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
free(packed_refs_path);
|
||||||
|
packed_transaction_cleanup(refs, transaction);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int packed_initial_transaction_commit(struct ref_store *ref_store,
|
static int packed_initial_transaction_commit(struct ref_store *ref_store,
|
||||||
|
@ -781,7 +896,50 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store,
|
||||||
static int packed_delete_refs(struct ref_store *ref_store, const char *msg,
|
static int packed_delete_refs(struct ref_store *ref_store, const char *msg,
|
||||||
struct string_list *refnames, unsigned int flags)
|
struct string_list *refnames, unsigned int flags)
|
||||||
{
|
{
|
||||||
die("BUG: not implemented yet");
|
struct packed_ref_store *refs =
|
||||||
|
packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs");
|
||||||
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
struct ref_transaction *transaction;
|
||||||
|
struct string_list_item *item;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
(void)refs; /* We need the check above, but don't use the variable */
|
||||||
|
|
||||||
|
if (!refnames->nr)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since we don't check the references' old_oids, the
|
||||||
|
* individual updates can't fail, so we can pack all of the
|
||||||
|
* updates into a single transaction.
|
||||||
|
*/
|
||||||
|
|
||||||
|
transaction = ref_store_transaction_begin(ref_store, &err);
|
||||||
|
if (!transaction)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
for_each_string_list_item(item, refnames) {
|
||||||
|
if (ref_transaction_delete(transaction, item->string, NULL,
|
||||||
|
flags, msg, &err)) {
|
||||||
|
warning(_("could not delete reference %s: %s"),
|
||||||
|
item->string, err.buf);
|
||||||
|
strbuf_reset(&err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ref_transaction_commit(transaction, &err);
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
if (refnames->nr == 1)
|
||||||
|
error(_("could not delete reference %s: %s"),
|
||||||
|
refnames->items[0].string, err.buf);
|
||||||
|
else
|
||||||
|
error(_("could not delete references: %s"), err.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ref_transaction_free(transaction);
|
||||||
|
strbuf_release(&err);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int packed_pack_refs(struct ref_store *ref_store, unsigned int flags)
|
static int packed_pack_refs(struct ref_store *ref_store, unsigned int flags)
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
#ifndef REFS_PACKED_BACKEND_H
|
#ifndef REFS_PACKED_BACKEND_H
|
||||||
#define REFS_PACKED_BACKEND_H
|
#define REFS_PACKED_BACKEND_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Support for storing references in a `packed-refs` file.
|
||||||
|
*
|
||||||
|
* Note that this backend doesn't check for D/F conflicts, because it
|
||||||
|
* doesn't care about them. But usually it should be wrapped in a
|
||||||
|
* `files_ref_store` that prevents D/F conflicts from being created,
|
||||||
|
* even among packed refs.
|
||||||
|
*/
|
||||||
|
|
||||||
struct ref_store *packed_ref_store_create(const char *path,
|
struct ref_store *packed_ref_store_create(const char *path,
|
||||||
unsigned int store_flags);
|
unsigned int store_flags);
|
||||||
|
|
||||||
|
@ -14,12 +23,4 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
|
||||||
void packed_refs_unlock(struct ref_store *ref_store);
|
void packed_refs_unlock(struct ref_store *ref_store);
|
||||||
int packed_refs_is_locked(struct ref_store *ref_store);
|
int packed_refs_is_locked(struct ref_store *ref_store);
|
||||||
|
|
||||||
void add_packed_ref(struct ref_store *ref_store,
|
|
||||||
const char *refname, const struct object_id *oid);
|
|
||||||
|
|
||||||
int commit_packed_refs(struct ref_store *ref_store, struct strbuf *err);
|
|
||||||
|
|
||||||
int repack_without_refs(struct ref_store *ref_store,
|
|
||||||
struct string_list *refnames, struct strbuf *err);
|
|
||||||
|
|
||||||
#endif /* REFS_PACKED_BACKEND_H */
|
#endif /* REFS_PACKED_BACKEND_H */
|
||||||
|
|
|
@ -242,6 +242,7 @@ struct ref_transaction {
|
||||||
size_t alloc;
|
size_t alloc;
|
||||||
size_t nr;
|
size_t nr;
|
||||||
enum ref_transaction_state state;
|
enum ref_transaction_state state;
|
||||||
|
void *backend_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -404,4 +404,77 @@ test_expect_success 'broken reference blocks indirect create' '
|
||||||
test_cmp expected output.err
|
test_cmp expected output.err
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'no bogus intermediate values during delete' '
|
||||||
|
prefix=refs/slow-transaction &&
|
||||||
|
# Set up a reference with differing loose and packed versions:
|
||||||
|
git update-ref $prefix/foo $C &&
|
||||||
|
git pack-refs --all &&
|
||||||
|
git update-ref $prefix/foo $D &&
|
||||||
|
git for-each-ref $prefix >unchanged &&
|
||||||
|
# Now try to update the reference, but hold the `packed-refs` lock
|
||||||
|
# for a while to see what happens while the process is blocked:
|
||||||
|
: >.git/packed-refs.lock &&
|
||||||
|
test_when_finished "rm -f .git/packed-refs.lock" &&
|
||||||
|
{
|
||||||
|
# Note: the following command is intentionally run in the
|
||||||
|
# background. We increase the timeout so that `update-ref`
|
||||||
|
# attempts to acquire the `packed-refs` lock for longer than
|
||||||
|
# it takes for us to do the check then delete it:
|
||||||
|
git -c core.packedrefstimeout=3000 update-ref -d $prefix/foo &
|
||||||
|
} &&
|
||||||
|
pid2=$! &&
|
||||||
|
# Give update-ref plenty of time to get to the point where it tries
|
||||||
|
# to lock packed-refs:
|
||||||
|
sleep 1 &&
|
||||||
|
# Make sure that update-ref did not complete despite the lock:
|
||||||
|
kill -0 $pid2 &&
|
||||||
|
# Verify that the reference still has its old value:
|
||||||
|
sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) &&
|
||||||
|
case "$sha1" in
|
||||||
|
$D)
|
||||||
|
# This is what we hope for; it means that nothing
|
||||||
|
# user-visible has changed yet.
|
||||||
|
: ;;
|
||||||
|
undefined)
|
||||||
|
# This is not correct; it means the deletion has happened
|
||||||
|
# already even though update-ref should not have been
|
||||||
|
# able to acquire the lock yet.
|
||||||
|
echo "$prefix/foo deleted prematurely" &&
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
$C)
|
||||||
|
# This value should never be seen. Probably the loose
|
||||||
|
# reference has been deleted but the packed reference
|
||||||
|
# is still there:
|
||||||
|
echo "$prefix/foo incorrectly observed to be C" &&
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# WTF?
|
||||||
|
echo "unexpected value observed for $prefix/foo: $sha1" &&
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac >out &&
|
||||||
|
rm -f .git/packed-refs.lock &&
|
||||||
|
wait $pid2 &&
|
||||||
|
test_must_be_empty out &&
|
||||||
|
test_must_fail git rev-parse --verify --quiet $prefix/foo
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'delete fails cleanly if packed-refs file is locked' '
|
||||||
|
prefix=refs/locked-packed-refs &&
|
||||||
|
# Set up a reference with differing loose and packed versions:
|
||||||
|
git update-ref $prefix/foo $C &&
|
||||||
|
git pack-refs --all &&
|
||||||
|
git update-ref $prefix/foo $D &&
|
||||||
|
git for-each-ref $prefix >unchanged &&
|
||||||
|
# Now try to delete it while the `packed-refs` lock is held:
|
||||||
|
: >.git/packed-refs.lock &&
|
||||||
|
test_when_finished "rm -f .git/packed-refs.lock" &&
|
||||||
|
test_must_fail git update-ref -d $prefix/foo >out 2>err &&
|
||||||
|
git for-each-ref $prefix >actual &&
|
||||||
|
test_i18ngrep "Unable to create $Q.*packed-refs.lock$Q: File exists" err &&
|
||||||
|
test_cmp unchanged actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in a new issue