Skip to content

Commit 2775d87

Browse files
mhaggergitster
authored andcommitted
packed_ref_store: implement reference transactions
Implement the methods needed to support reference transactions for the packed-refs backend. The new methods are not yet used. Signed-off-by: Michael Haggerty <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3bf4f56 commit 2775d87

File tree

2 files changed

+319
-3
lines changed

2 files changed

+319
-3
lines changed

refs/packed-backend.c

Lines changed: 310 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -748,25 +748,332 @@ static int packed_init_db(struct ref_store *ref_store, struct strbuf *err)
748748
return 0;
749749
}
750750

751+
/*
752+
* Write the packed-refs from the cache to the packed-refs tempfile,
753+
* incorporating any changes from `updates`. `updates` must be a
754+
* sorted string list whose keys are the refnames and whose util
755+
* values are `struct ref_update *`. On error, rollback the tempfile,
756+
* write an error message to `err`, and return a nonzero value.
757+
*
758+
* The packfile must be locked before calling this function and will
759+
* remain locked when it is done.
760+
*/
761+
static int write_with_updates(struct packed_ref_store *refs,
762+
struct string_list *updates,
763+
struct strbuf *err)
764+
{
765+
struct ref_iterator *iter = NULL;
766+
size_t i;
767+
int ok;
768+
FILE *out;
769+
struct strbuf sb = STRBUF_INIT;
770+
char *packed_refs_path;
771+
772+
if (!is_lock_file_locked(&refs->lock))
773+
die("BUG: write_with_updates() called while unlocked");
774+
775+
/*
776+
* If packed-refs is a symlink, we want to overwrite the
777+
* symlinked-to file, not the symlink itself. Also, put the
778+
* staging file next to it:
779+
*/
780+
packed_refs_path = get_locked_file_path(&refs->lock);
781+
strbuf_addf(&sb, "%s.new", packed_refs_path);
782+
free(packed_refs_path);
783+
if (create_tempfile(&refs->tempfile, sb.buf) < 0) {
784+
strbuf_addf(err, "unable to create file %s: %s",
785+
sb.buf, strerror(errno));
786+
strbuf_release(&sb);
787+
return -1;
788+
}
789+
strbuf_release(&sb);
790+
791+
out = fdopen_tempfile(&refs->tempfile, "w");
792+
if (!out) {
793+
strbuf_addf(err, "unable to fdopen packed-refs tempfile: %s",
794+
strerror(errno));
795+
goto error;
796+
}
797+
798+
if (fprintf(out, "%s", PACKED_REFS_HEADER) < 0)
799+
goto write_error;
800+
801+
/*
802+
* We iterate in parallel through the current list of refs and
803+
* the list of updates, processing an entry from at least one
804+
* of the lists each time through the loop. When the current
805+
* list of refs is exhausted, set iter to NULL. When the list
806+
* of updates is exhausted, leave i set to updates->nr.
807+
*/
808+
iter = packed_ref_iterator_begin(&refs->base, "",
809+
DO_FOR_EACH_INCLUDE_BROKEN);
810+
if ((ok = ref_iterator_advance(iter)) != ITER_OK)
811+
iter = NULL;
812+
813+
i = 0;
814+
815+
while (iter || i < updates->nr) {
816+
struct ref_update *update = NULL;
817+
int cmp;
818+
819+
if (i >= updates->nr) {
820+
cmp = -1;
821+
} else {
822+
update = updates->items[i].util;
823+
824+
if (!iter)
825+
cmp = +1;
826+
else
827+
cmp = strcmp(iter->refname, update->refname);
828+
}
829+
830+
if (!cmp) {
831+
/*
832+
* There is both an old value and an update
833+
* for this reference. Check the old value if
834+
* necessary:
835+
*/
836+
if ((update->flags & REF_HAVE_OLD)) {
837+
if (is_null_oid(&update->old_oid)) {
838+
strbuf_addf(err, "cannot update ref '%s': "
839+
"reference already exists",
840+
update->refname);
841+
goto error;
842+
} else if (oidcmp(&update->old_oid, iter->oid)) {
843+
strbuf_addf(err, "cannot update ref '%s': "
844+
"is at %s but expected %s",
845+
update->refname,
846+
oid_to_hex(iter->oid),
847+
oid_to_hex(&update->old_oid));
848+
goto error;
849+
}
850+
}
851+
852+
/* Now figure out what to use for the new value: */
853+
if ((update->flags & REF_HAVE_NEW)) {
854+
/*
855+
* The update takes precedence. Skip
856+
* the iterator over the unneeded
857+
* value.
858+
*/
859+
if ((ok = ref_iterator_advance(iter)) != ITER_OK)
860+
iter = NULL;
861+
cmp = +1;
862+
} else {
863+
/*
864+
* The update doesn't actually want to
865+
* change anything. We're done with it.
866+
*/
867+
i++;
868+
cmp = -1;
869+
}
870+
} else if (cmp > 0) {
871+
/*
872+
* There is no old value but there is an
873+
* update for this reference. Make sure that
874+
* the update didn't expect an existing value:
875+
*/
876+
if ((update->flags & REF_HAVE_OLD) &&
877+
!is_null_oid(&update->old_oid)) {
878+
strbuf_addf(err, "cannot update ref '%s': "
879+
"reference is missing but expected %s",
880+
update->refname,
881+
oid_to_hex(&update->old_oid));
882+
goto error;
883+
}
884+
}
885+
886+
if (cmp < 0) {
887+
/* Pass the old reference through. */
888+
889+
struct object_id peeled;
890+
int peel_error = ref_iterator_peel(iter, &peeled);
891+
892+
if (write_packed_entry(out, iter->refname,
893+
iter->oid->hash,
894+
peel_error ? NULL : peeled.hash))
895+
goto write_error;
896+
897+
if ((ok = ref_iterator_advance(iter)) != ITER_OK)
898+
iter = NULL;
899+
} else if (is_null_oid(&update->new_oid)) {
900+
/*
901+
* The update wants to delete the reference,
902+
* and the reference either didn't exist or we
903+
* have already skipped it. So we're done with
904+
* the update (and don't have to write
905+
* anything).
906+
*/
907+
i++;
908+
} else {
909+
struct object_id peeled;
910+
int peel_error = peel_object(update->new_oid.hash,
911+
peeled.hash);
912+
913+
if (write_packed_entry(out, update->refname,
914+
update->new_oid.hash,
915+
peel_error ? NULL : peeled.hash))
916+
goto write_error;
917+
918+
i++;
919+
}
920+
}
921+
922+
if (ok != ITER_DONE) {
923+
strbuf_addf(err, "unable to write packed-refs file: "
924+
"error iterating over old contents");
925+
goto error;
926+
}
927+
928+
if (close_tempfile(&refs->tempfile)) {
929+
strbuf_addf(err, "error closing file %s: %s",
930+
get_tempfile_path(&refs->tempfile),
931+
strerror(errno));
932+
strbuf_release(&sb);
933+
return -1;
934+
}
935+
936+
return 0;
937+
938+
write_error:
939+
strbuf_addf(err, "error writing to %s: %s",
940+
get_tempfile_path(&refs->tempfile), strerror(errno));
941+
942+
error:
943+
if (iter)
944+
ref_iterator_abort(iter);
945+
946+
delete_tempfile(&refs->tempfile);
947+
return -1;
948+
}
949+
950+
struct packed_transaction_backend_data {
951+
/* True iff the transaction owns the packed-refs lock. */
952+
int own_lock;
953+
954+
struct string_list updates;
955+
};
956+
957+
static void packed_transaction_cleanup(struct packed_ref_store *refs,
958+
struct ref_transaction *transaction)
959+
{
960+
struct packed_transaction_backend_data *data = transaction->backend_data;
961+
962+
if (data) {
963+
string_list_clear(&data->updates, 0);
964+
965+
if (is_tempfile_active(&refs->tempfile))
966+
delete_tempfile(&refs->tempfile);
967+
968+
if (data->own_lock && is_lock_file_locked(&refs->lock)) {
969+
packed_refs_unlock(&refs->base);
970+
data->own_lock = 0;
971+
}
972+
973+
free(data);
974+
transaction->backend_data = NULL;
975+
}
976+
977+
transaction->state = REF_TRANSACTION_CLOSED;
978+
}
979+
751980
static int packed_transaction_prepare(struct ref_store *ref_store,
752981
struct ref_transaction *transaction,
753982
struct strbuf *err)
754983
{
755-
die("BUG: not implemented yet");
984+
struct packed_ref_store *refs = packed_downcast(
985+
ref_store,
986+
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
987+
"ref_transaction_prepare");
988+
struct packed_transaction_backend_data *data;
989+
size_t i;
990+
int ret = TRANSACTION_GENERIC_ERROR;
991+
992+
/*
993+
* Note that we *don't* skip transactions with zero updates,
994+
* because such a transaction might be executed for the side
995+
* effect of ensuring that all of the references are peeled.
996+
* If the caller wants to optimize away empty transactions, it
997+
* should do so itself.
998+
*/
999+
1000+
data = xcalloc(1, sizeof(*data));
1001+
string_list_init(&data->updates, 0);
1002+
1003+
transaction->backend_data = data;
1004+
1005+
/*
1006+
* Stick the updates in a string list by refname so that we
1007+
* can sort them:
1008+
*/
1009+
for (i = 0; i < transaction->nr; i++) {
1010+
struct ref_update *update = transaction->updates[i];
1011+
struct string_list_item *item =
1012+
string_list_append(&data->updates, update->refname);
1013+
1014+
/* Store a pointer to update in item->util: */
1015+
item->util = update;
1016+
}
1017+
string_list_sort(&data->updates);
1018+
1019+
if (ref_update_reject_duplicates(&data->updates, err))
1020+
goto failure;
1021+
1022+
if (!is_lock_file_locked(&refs->lock)) {
1023+
if (packed_refs_lock(ref_store, 0, err))
1024+
goto failure;
1025+
data->own_lock = 1;
1026+
}
1027+
1028+
if (write_with_updates(refs, &data->updates, err))
1029+
goto failure;
1030+
1031+
transaction->state = REF_TRANSACTION_PREPARED;
1032+
return 0;
1033+
1034+
failure:
1035+
packed_transaction_cleanup(refs, transaction);
1036+
return ret;
7561037
}
7571038

7581039
static int packed_transaction_abort(struct ref_store *ref_store,
7591040
struct ref_transaction *transaction,
7601041
struct strbuf *err)
7611042
{
762-
die("BUG: not implemented yet");
1043+
struct packed_ref_store *refs = packed_downcast(
1044+
ref_store,
1045+
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
1046+
"ref_transaction_abort");
1047+
1048+
packed_transaction_cleanup(refs, transaction);
1049+
return 0;
7631050
}
7641051

7651052
static int packed_transaction_finish(struct ref_store *ref_store,
7661053
struct ref_transaction *transaction,
7671054
struct strbuf *err)
7681055
{
769-
die("BUG: not implemented yet");
1056+
struct packed_ref_store *refs = packed_downcast(
1057+
ref_store,
1058+
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
1059+
"ref_transaction_finish");
1060+
int ret = TRANSACTION_GENERIC_ERROR;
1061+
char *packed_refs_path;
1062+
1063+
packed_refs_path = get_locked_file_path(&refs->lock);
1064+
if (rename_tempfile(&refs->tempfile, packed_refs_path)) {
1065+
strbuf_addf(err, "error replacing %s: %s",
1066+
refs->path, strerror(errno));
1067+
goto cleanup;
1068+
}
1069+
1070+
clear_packed_ref_cache(refs);
1071+
ret = 0;
1072+
1073+
cleanup:
1074+
free(packed_refs_path);
1075+
packed_transaction_cleanup(refs, transaction);
1076+
return ret;
7701077
}
7711078

7721079
static int packed_initial_transaction_commit(struct ref_store *ref_store,

refs/packed-backend.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
#ifndef REFS_PACKED_BACKEND_H
22
#define REFS_PACKED_BACKEND_H
33

4+
/*
5+
* Support for storing references in a `packed-refs` file.
6+
*
7+
* Note that this backend doesn't check for D/F conflicts, because it
8+
* doesn't care about them. But usually it should be wrapped in a
9+
* `files_ref_store` that prevents D/F conflicts from being created,
10+
* even among packed refs.
11+
*/
12+
413
struct ref_store *packed_ref_store_create(const char *path,
514
unsigned int store_flags);
615

0 commit comments

Comments
 (0)