fetch: extract writing to FETCH_HEAD

When performing a fetch with the default `--write-fetch-head` option, we write all updated references to FETCH_HEAD while the updates are performed. Given that updates are not performed atomically, it means that we we write to FETCH_HEAD even if some or all of the reference updates fail. Given that we simply update FETCH_HEAD ad-hoc with each reference, the logic is completely contained in `store_update_refs` and thus quite hard to extend. This can already be seen by the way we skip writing to the FETCH_HEAD: instead of having a conditional which simply skips writing, we instead open "/dev/null" and needlessly write all updates there. We are about to extend git-fetch(1) to accept an `--atomic` flag which will make the fetch an all-or-nothing operation with regards to the reference updates. This will also require us to make the updates to FETCH_HEAD an all-or-nothing operation, but as explained doing so is not easy with the current layout. This commit thus refactors the wa we write to FETCH_HEAD and pulls out the logic to open, append to, commit and close the file. While this may seem rather over-the top at first, pulling out this logic will make it a lot easier to update the code in a subsequent commit. It also allows us to easily skip writing completely in case `--no-write-fetch-head` was passed. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jan 12, 2021 at 13:27 UTC 58a646a3688ea3c15adaf481e440e9ca2d3bce06
2 files changed +80 -30
builtin/fetch.c
+79 -29
@@ -897,6 +897,73 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
897 return 0;
898 }
899
900 +struct fetch_head {
901 + FILE *fp;
902 +};
903 +
904 +static int open_fetch_head(struct fetch_head *fetch_head)
905 +{
906 + const char *filename = git_path_fetch_head(the_repository);
907 +
908 + if (write_fetch_head) {
909 + fetch_head->fp = fopen(filename, "a");
910 + if (!fetch_head->fp)
911 + return error_errno(_("cannot open %s"), filename);
912 + } else {
913 + fetch_head->fp = NULL;
914 + }
915 +
916 + return 0;
917 +}
918 +
919 +static void append_fetch_head(struct fetch_head *fetch_head,
920 + const struct object_id *old_oid,
921 + enum fetch_head_status fetch_head_status,
922 + const char *note,
923 + const char *url, size_t url_len)
924 +{
925 + char old_oid_hex[GIT_MAX_HEXSZ + 1];
926 + const char *merge_status_marker;
927 + size_t i;
928 +
929 + if (!fetch_head->fp)
930 + return;
931 +
932 + switch (fetch_head_status) {
933 + case FETCH_HEAD_NOT_FOR_MERGE:
934 + merge_status_marker = "not-for-merge";
935 + break;
936 + case FETCH_HEAD_MERGE:
937 + merge_status_marker = "";
938 + break;
939 + default:
940 + /* do not write anything to FETCH_HEAD */
941 + return;
942 + }
943 +
944 + fprintf(fetch_head->fp, "%s\t%s\t%s",
945 + oid_to_hex_r(old_oid_hex, old_oid), merge_status_marker, note);
946 + for (i = 0; i < url_len; ++i)
947 + if ('\n' == url[i])
948 + fputs("\\n", fetch_head->fp);
949 + else
950 + fputc(url[i], fetch_head->fp);
951 + fputc('\n', fetch_head->fp);
952 +}
953 +
954 +static void commit_fetch_head(struct fetch_head *fetch_head)
955 +{
956 + /* Nothing to commit yet. */
957 +}
958 +
959 +static void close_fetch_head(struct fetch_head *fetch_head)
960 +{
961 + if (!fetch_head->fp)
962 + return;
963 +
964 + fclose(fetch_head->fp);
965 +}
966 +
967 static const char warn_show_forced_updates[] =
968 N_("Fetch normally indicates which branches had a forced update,\n"
969 "but that check has been disabled. To re-enable, use '--show-forced-updates'\n"
@@ -909,22 +976,19 @@ N_("It took %.2f seconds to check forced updates. You can use\n"
976 static int store_updated_refs(const char *raw_url, const char *remote_name,
977 int connectivity_checked, struct ref *ref_map)
978 {
912 - FILE *fp;
979 + struct fetch_head fetch_head;
980 struct commit *commit;
981 int url_len, i, rc = 0;
982 struct strbuf note = STRBUF_INIT;
983 const char *what, *kind;
984 struct ref *rm;
985 char *url;
919 - const char *filename = (!write_fetch_head
920 - ? "/dev/null"
921 - : git_path_fetch_head(the_repository));
986 int want_status;
987 int summary_width = transport_summary_width(ref_map);
988
925 - fp = fopen(filename, "a");
926 - if (!fp)
927 - return error_errno(_("cannot open %s"), filename);
989 + rc = open_fetch_head(&fetch_head);
990 + if (rc)
991 + return -1;
992
993 if (raw_url)
994 url = transport_anonymize_url(raw_url);
@@ -953,7 +1017,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
1017 want_status++) {
1018 for (rm = ref_map; rm; rm = rm->next) {
1019 struct ref *ref = NULL;
956 - const char *merge_status_marker = "";
1020
1021 if (rm->status == REF_STATUS_REJECT_SHALLOW) {
1022 if (want_status == FETCH_HEAD_MERGE)
@@ -1011,26 +1074,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
1074 strbuf_addf(&note, "%s ", kind);
1075 strbuf_addf(&note, "'%s' of ", what);
1076 }
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 - }
1077 +
1078 + append_fetch_head(&fetch_head, &rm->old_oid,
1079 + rm->fetch_head_status,
1080 + note.buf, url, url_len);
1081
1082 strbuf_reset(&note);
1083 if (ref) {
@@ -1060,6 +1107,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
1107 }
1108 }
1109
1110 + if (!rc)
1111 + commit_fetch_head(&fetch_head);
1112 +
1113 if (rc & STORE_REF_ERROR_DF_CONFLICT)
1114 error(_("some local refs could not be updated; try running\n"
1115 " 'git remote prune %s' to remove any old, conflicting "
@@ -1077,7 +1127,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
1127 abort:
1128 strbuf_release(&note);
1129 free(url);
1080 - fclose(fp);
1130 + close_fetch_head(&fetch_head);
1131 return rc;
1132 }
1133
remote.h
+1 -1
@@ -134,7 +134,7 @@ struct ref {
134 * should be 0, so that xcalloc'd structures get it
135 * by default.
136 */
137 - enum {
137 + enum fetch_head_status {
138 FETCH_HEAD_MERGE = -1,
139 FETCH_HEAD_NOT_FOR_MERGE = 0,
140 FETCH_HEAD_IGNORE = 1