Skip to content

Commit 60ecad0

Browse files
committed
Merge branch 'ps/fetch-atomic'
"git fetch" learns to treat ref updates atomically in all-or-none fashion, just like "git push" does, with the new "--atomic" option. * ps/fetch-atomic: fetch: implement support for atomic reference updates fetch: allow passing a transaction to `s_update_ref()` fetch: refactor `s_update_ref` to use common exit path fetch: use strbuf to format FETCH_HEAD updates fetch: extract writing to FETCH_HEAD
2 parents b69bed2 + c7b190d commit 60ecad0

File tree

4 files changed

+340
-58
lines changed

4 files changed

+340
-58
lines changed

Documentation/fetch-options.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
existing contents of `.git/FETCH_HEAD`. Without this
88
option old data in `.git/FETCH_HEAD` will be overwritten.
99

10+
--atomic::
11+
Use an atomic transaction to update local refs. Either all refs are
12+
updated, or on error, no refs are updated.
13+
1014
--depth=<depth>::
1115
Limit fetching to the specified number of commits from the tip of
1216
each remote branch history. If fetching to a 'shallow' repository

builtin/fetch.c

Lines changed: 167 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ static int enable_auto_gc = 1;
6363
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
6464
static int max_jobs = -1, submodule_fetch_jobs_config = -1;
6565
static int fetch_parallel_config = 1;
66+
static int atomic_fetch;
6667
static enum transport_family family;
6768
static const char *depth;
6869
static const char *deepen_since;
@@ -144,6 +145,8 @@ static struct option builtin_fetch_options[] = {
144145
N_("set upstream for git pull/fetch")),
145146
OPT_BOOL('a', "append", &append,
146147
N_("append to .git/FETCH_HEAD instead of overwriting")),
148+
OPT_BOOL(0, "atomic", &atomic_fetch,
149+
N_("use atomic transaction to update references")),
147150
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
148151
N_("path to upload pack on remote end")),
149152
OPT__FORCE(&force, N_("force overwrite of local reference"), 0),
@@ -583,45 +586,62 @@ static struct ref *get_ref_map(struct remote *remote,
583586

584587
static int s_update_ref(const char *action,
585588
struct ref *ref,
589+
struct ref_transaction *transaction,
586590
int check_old)
587591
{
588592
char *msg;
589593
char *rla = getenv("GIT_REFLOG_ACTION");
590-
struct ref_transaction *transaction;
594+
struct ref_transaction *our_transaction = NULL;
591595
struct strbuf err = STRBUF_INIT;
592-
int ret, df_conflict = 0;
596+
int ret;
593597

594598
if (dry_run)
595599
return 0;
596600
if (!rla)
597601
rla = default_rla.buf;
598602
msg = xstrfmt("%s: %s", rla, action);
599603

600-
transaction = ref_transaction_begin(&err);
601-
if (!transaction ||
602-
ref_transaction_update(transaction, ref->name,
603-
&ref->new_oid,
604-
check_old ? &ref->old_oid : NULL,
605-
0, msg, &err))
606-
goto fail;
604+
/*
605+
* If no transaction was passed to us, we manage the transaction
606+
* ourselves. Otherwise, we trust the caller to handle the transaction
607+
* lifecycle.
608+
*/
609+
if (!transaction) {
610+
transaction = our_transaction = ref_transaction_begin(&err);
611+
if (!transaction) {
612+
ret = STORE_REF_ERROR_OTHER;
613+
goto out;
614+
}
615+
}
607616

608-
ret = ref_transaction_commit(transaction, &err);
617+
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
618+
check_old ? &ref->old_oid : NULL,
619+
0, msg, &err);
609620
if (ret) {
610-
df_conflict = (ret == TRANSACTION_NAME_CONFLICT);
611-
goto fail;
621+
ret = STORE_REF_ERROR_OTHER;
622+
goto out;
612623
}
613624

614-
ref_transaction_free(transaction);
615-
strbuf_release(&err);
616-
free(msg);
617-
return 0;
618-
fail:
619-
ref_transaction_free(transaction);
620-
error("%s", err.buf);
625+
if (our_transaction) {
626+
switch (ref_transaction_commit(our_transaction, &err)) {
627+
case 0:
628+
break;
629+
case TRANSACTION_NAME_CONFLICT:
630+
ret = STORE_REF_ERROR_DF_CONFLICT;
631+
goto out;
632+
default:
633+
ret = STORE_REF_ERROR_OTHER;
634+
goto out;
635+
}
636+
}
637+
638+
out:
639+
ref_transaction_free(our_transaction);
640+
if (ret)
641+
error("%s", err.buf);
621642
strbuf_release(&err);
622643
free(msg);
623-
return df_conflict ? STORE_REF_ERROR_DF_CONFLICT
624-
: STORE_REF_ERROR_OTHER;
644+
return ret;
625645
}
626646

627647
static int refcol_width = 10;
@@ -759,6 +779,7 @@ static void format_display(struct strbuf *display, char code,
759779
}
760780

761781
static int update_local_ref(struct ref *ref,
782+
struct ref_transaction *transaction,
762783
const char *remote,
763784
const struct ref *remote_ref,
764785
struct strbuf *display,
@@ -799,7 +820,7 @@ static int update_local_ref(struct ref *ref,
799820
starts_with(ref->name, "refs/tags/")) {
800821
if (force || ref->force) {
801822
int r;
802-
r = s_update_ref("updating tag", ref, 0);
823+
r = s_update_ref("updating tag", ref, transaction, 0);
803824
format_display(display, r ? '!' : 't', _("[tag update]"),
804825
r ? _("unable to update local ref") : NULL,
805826
remote, pretty_ref, summary_width);
@@ -836,7 +857,7 @@ static int update_local_ref(struct ref *ref,
836857
what = _("[new ref]");
837858
}
838859

839-
r = s_update_ref(msg, ref, 0);
860+
r = s_update_ref(msg, ref, transaction, 0);
840861
format_display(display, r ? '!' : '*', what,
841862
r ? _("unable to update local ref") : NULL,
842863
remote, pretty_ref, summary_width);
@@ -858,7 +879,7 @@ static int update_local_ref(struct ref *ref,
858879
strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
859880
strbuf_addstr(&quickref, "..");
860881
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
861-
r = s_update_ref("fast-forward", ref, 1);
882+
r = s_update_ref("fast-forward", ref, transaction, 1);
862883
format_display(display, r ? '!' : ' ', quickref.buf,
863884
r ? _("unable to update local ref") : NULL,
864885
remote, pretty_ref, summary_width);
@@ -870,7 +891,7 @@ static int update_local_ref(struct ref *ref,
870891
strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
871892
strbuf_addstr(&quickref, "...");
872893
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
873-
r = s_update_ref("forced-update", ref, 1);
894+
r = s_update_ref("forced-update", ref, transaction, 1);
874895
format_display(display, r ? '!' : '+', quickref.buf,
875896
r ? _("unable to update local ref") : _("forced update"),
876897
remote, pretty_ref, summary_width);
@@ -897,6 +918,89 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
897918
return 0;
898919
}
899920

921+
struct fetch_head {
922+
FILE *fp;
923+
struct strbuf buf;
924+
};
925+
926+
static int open_fetch_head(struct fetch_head *fetch_head)
927+
{
928+
const char *filename = git_path_fetch_head(the_repository);
929+
930+
if (write_fetch_head) {
931+
fetch_head->fp = fopen(filename, "a");
932+
if (!fetch_head->fp)
933+
return error_errno(_("cannot open %s"), filename);
934+
strbuf_init(&fetch_head->buf, 0);
935+
} else {
936+
fetch_head->fp = NULL;
937+
}
938+
939+
return 0;
940+
}
941+
942+
static void append_fetch_head(struct fetch_head *fetch_head,
943+
const struct object_id *old_oid,
944+
enum fetch_head_status fetch_head_status,
945+
const char *note,
946+
const char *url, size_t url_len)
947+
{
948+
char old_oid_hex[GIT_MAX_HEXSZ + 1];
949+
const char *merge_status_marker;
950+
size_t i;
951+
952+
if (!fetch_head->fp)
953+
return;
954+
955+
switch (fetch_head_status) {
956+
case FETCH_HEAD_NOT_FOR_MERGE:
957+
merge_status_marker = "not-for-merge";
958+
break;
959+
case FETCH_HEAD_MERGE:
960+
merge_status_marker = "";
961+
break;
962+
default:
963+
/* do not write anything to FETCH_HEAD */
964+
return;
965+
}
966+
967+
strbuf_addf(&fetch_head->buf, "%s\t%s\t%s",
968+
oid_to_hex_r(old_oid_hex, old_oid), merge_status_marker, note);
969+
for (i = 0; i < url_len; ++i)
970+
if ('\n' == url[i])
971+
strbuf_addstr(&fetch_head->buf, "\\n");
972+
else
973+
strbuf_addch(&fetch_head->buf, url[i]);
974+
strbuf_addch(&fetch_head->buf, '\n');
975+
976+
/*
977+
* When using an atomic fetch, we do not want to update FETCH_HEAD if
978+
* any of the reference updates fails. We thus have to write all
979+
* updates to a buffer first and only commit it as soon as all
980+
* references have been successfully updated.
981+
*/
982+
if (!atomic_fetch) {
983+
strbuf_write(&fetch_head->buf, fetch_head->fp);
984+
strbuf_reset(&fetch_head->buf);
985+
}
986+
}
987+
988+
static void commit_fetch_head(struct fetch_head *fetch_head)
989+
{
990+
if (!fetch_head->fp || !atomic_fetch)
991+
return;
992+
strbuf_write(&fetch_head->buf, fetch_head->fp);
993+
}
994+
995+
static void close_fetch_head(struct fetch_head *fetch_head)
996+
{
997+
if (!fetch_head->fp)
998+
return;
999+
1000+
fclose(fetch_head->fp);
1001+
strbuf_release(&fetch_head->buf);
1002+
}
1003+
9001004
static const char warn_show_forced_updates[] =
9011005
N_("Fetch normally indicates which branches had a forced update,\n"
9021006
"but that check has been disabled. To re-enable, use '--show-forced-updates'\n"
@@ -909,22 +1013,20 @@ N_("It took %.2f seconds to check forced updates. You can use\n"
9091013
static int store_updated_refs(const char *raw_url, const char *remote_name,
9101014
int connectivity_checked, struct ref *ref_map)
9111015
{
912-
FILE *fp;
1016+
struct fetch_head fetch_head;
9131017
struct commit *commit;
9141018
int url_len, i, rc = 0;
915-
struct strbuf note = STRBUF_INIT;
1019+
struct strbuf note = STRBUF_INIT, err = STRBUF_INIT;
1020+
struct ref_transaction *transaction = NULL;
9161021
const char *what, *kind;
9171022
struct ref *rm;
9181023
char *url;
919-
const char *filename = (!write_fetch_head
920-
? "/dev/null"
921-
: git_path_fetch_head(the_repository));
9221024
int want_status;
9231025
int summary_width = transport_summary_width(ref_map);
9241026

925-
fp = fopen(filename, "a");
926-
if (!fp)
927-
return error_errno(_("cannot open %s"), filename);
1027+
rc = open_fetch_head(&fetch_head);
1028+
if (rc)
1029+
return -1;
9281030

9291031
if (raw_url)
9301032
url = transport_anonymize_url(raw_url);
@@ -941,6 +1043,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
9411043
}
9421044
}
9431045

1046+
if (atomic_fetch) {
1047+
transaction = ref_transaction_begin(&err);
1048+
if (!transaction) {
1049+
error("%s", err.buf);
1050+
goto abort;
1051+
}
1052+
}
1053+
9441054
prepare_format_display(ref_map);
9451055

9461056
/*
@@ -953,7 +1063,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
9531063
want_status++) {
9541064
for (rm = ref_map; rm; rm = rm->next) {
9551065
struct ref *ref = NULL;
956-
const char *merge_status_marker = "";
9571066

9581067
if (rm->status == REF_STATUS_REJECT_SHALLOW) {
9591068
if (want_status == FETCH_HEAD_MERGE)
@@ -1011,31 +1120,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
10111120
strbuf_addf(&note, "%s ", kind);
10121121
strbuf_addf(&note, "'%s' of ", what);
10131122
}
1014-
switch (rm->fetch_head_status) {
1015-
case FETCH_HEAD_NOT_FOR_MERGE:
1016-
merge_status_marker = "not-for-merge";
1017-
/* fall-through */
1018-
case FETCH_HEAD_MERGE:
1019-
fprintf(fp, "%s\t%s\t%s",
1020-
oid_to_hex(&rm->old_oid),
1021-
merge_status_marker,
1022-
note.buf);
1023-
for (i = 0; i < url_len; ++i)
1024-
if ('\n' == url[i])
1025-
fputs("\\n", fp);
1026-
else
1027-
fputc(url[i], fp);
1028-
fputc('\n', fp);
1029-
break;
1030-
default:
1031-
/* do not write anything to FETCH_HEAD */
1032-
break;
1033-
}
1123+
1124+
append_fetch_head(&fetch_head, &rm->old_oid,
1125+
rm->fetch_head_status,
1126+
note.buf, url, url_len);
10341127

10351128
strbuf_reset(&note);
10361129
if (ref) {
1037-
rc |= update_local_ref(ref, what, rm, &note,
1038-
summary_width);
1130+
rc |= update_local_ref(ref, transaction, what,
1131+
rm, &note, summary_width);
10391132
free(ref);
10401133
} else if (write_fetch_head || dry_run) {
10411134
/*
@@ -1060,6 +1153,17 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
10601153
}
10611154
}
10621155

1156+
if (!rc && transaction) {
1157+
rc = ref_transaction_commit(transaction, &err);
1158+
if (rc) {
1159+
error("%s", err.buf);
1160+
goto abort;
1161+
}
1162+
}
1163+
1164+
if (!rc)
1165+
commit_fetch_head(&fetch_head);
1166+
10631167
if (rc & STORE_REF_ERROR_DF_CONFLICT)
10641168
error(_("some local refs could not be updated; try running\n"
10651169
" 'git remote prune %s' to remove any old, conflicting "
@@ -1076,8 +1180,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
10761180

10771181
abort:
10781182
strbuf_release(&note);
1183+
strbuf_release(&err);
1184+
ref_transaction_free(transaction);
10791185
free(url);
1080-
fclose(fp);
1186+
close_fetch_head(&fetch_head);
10811187
return rc;
10821188
}
10831189

@@ -1887,6 +1993,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
18871993
die(_("--filter can only be used with the remote "
18881994
"configured in extensions.partialclone"));
18891995

1996+
if (atomic_fetch)
1997+
die(_("--atomic can only be used when fetching "
1998+
"from one remote"));
1999+
18902000
if (stdin_refspecs)
18912001
die(_("--stdin can only be used when fetching "
18922002
"from one remote"));

remote.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ struct ref {
134134
* should be 0, so that xcalloc'd structures get it
135135
* by default.
136136
*/
137-
enum {
137+
enum fetch_head_status {
138138
FETCH_HEAD_MERGE = -1,
139139
FETCH_HEAD_NOT_FOR_MERGE = 0,
140140
FETCH_HEAD_IGNORE = 1

0 commit comments

Comments
 (0)