diff options
author | Sami Hiltunen <shiltunen@gitlab.com> | 2022-03-03 13:45:04 +0300 |
---|---|---|
committer | Sami Hiltunen <shiltunen@gitlab.com> | 2022-03-03 13:45:04 +0300 |
commit | 5800949732ddaf48f31cf56353a0117e50d90571 (patch) | |
tree | dda342f6d4af7a2e0761cd6972f9af6629da18cb | |
parent | ef4ac6950bbd00487178dd6b75d71892d0cc9af0 (diff) | |
parent | b547b368c8f584e9aabe8eef9342f99440b0c248 (diff) |
Merge branch 'pks-git-v2.35.1.gl0' into 'master'
git: Add support for bundled Git v2.35.1.gl0
Closes #4067 and #4068
See merge request gitlab-org/gitaly!4352
18 files changed, 1950 insertions, 37 deletions
@@ -89,17 +89,6 @@ PROTOC_GEN_GO_GRPC_VERSION?= 1.1.0 GIT2GO_VERSION ?= v33 LIBGIT2_VERSION ?= v1.3.0 -# The default version is used in case the caller does not set the variable or -# if it is either set to the empty string or "default". -ifeq (${GIT_VERSION:default=},) - override GIT_VERSION := v2.33.1 - GIT_APPLY_DEFAULT_PATCHES := YesPlease -else - # Support both vX.Y.Z and X.Y.Z version patterns, since callers across GitLab - # use both. - override GIT_VERSION := $(shell echo ${GIT_VERSION} | awk '/^[0-9]\.[0-9]+\.[0-9]+$$/ { printf "v" } { print $$1 }') -endif - # protoc target PROTOC_REPO_URL ?= https://github.com/protocolbuffers/protobuf PROTOC_SOURCE_DIR ?= ${DEPENDENCY_DIR}/protobuf/source @@ -119,7 +108,6 @@ GIT_REPO_URL ?= https://gitlab.com/gitlab-org/gitlab-git.git # The default prefix specifies where Git will be installed to if no GIT_PREFIX # was given. This directory will be cleaned up before we install into it. GIT_DEFAULT_PREFIX := ${DEPENDENCY_DIR}/git/install -GIT_SOURCE_DIR := ${DEPENDENCY_DIR}/git/source GIT_QUIET := ifeq (${Q},@) GIT_QUIET = --quiet @@ -129,7 +117,11 @@ GIT_EXECUTABLES += git GIT_EXECUTABLES += git-remote-http GIT_EXECUTABLES += git-http-backend -ifdef GIT_APPLY_DEFAULT_PATCHES +# The default version is used in case the caller does not set the variable or +# if it is either set to the empty string or "default". +ifeq (${GIT_VERSION:default=},) + override GIT_VERSION := v2.33.1 + # Before adding custom patches, please read doc/PROCESS.md#Patching-git # first to make sure your patches meet our acceptance criteria. Patches # must be put into `_support/git-patches`. @@ -185,6 +177,10 @@ ifdef GIT_APPLY_DEFAULT_PATCHES # then this should be undefined. Otherwise, it must be set to at least # `gl1` given that `0` is the "default" GitLab patch level. GIT_EXTRA_VERSION := gl3 +else + # Support both vX.Y.Z and X.Y.Z version patterns, since callers across GitLab + # use both. + override GIT_VERSION := $(shell echo ${GIT_VERSION} | awk '/^[0-9]\.[0-9]+\.[0-9]+$$/ { printf "v" } { print $$1 }') endif ifeq ($(origin GIT_BUILD_OPTIONS),undefined) @@ -360,12 +356,16 @@ install: build .PHONY: build-bundled-git ## Build bundled Git binaries. -build-bundled-git: $(patsubst %,${BUILD_DIR}/bin/gitaly-%,${GIT_EXECUTABLES}) +build-bundled-git: build-bundled-git-v2.33.1.gl2 build-bundled-git-v2.35.1.gl1 +build-bundled-git-v2.33.1.gl2: $(patsubst %,${BUILD_DIR}/bin/gitaly-%,${GIT_EXECUTABLES}) +build-bundled-git-v2.35.1.gl1: $(patsubst %,${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1,${GIT_EXECUTABLES}) .PHONY: install-bundled-git ## Install bundled Git binaries. The target directory can be modified by ## setting PREFIX and DESTDIR. -install-bundled-git: $(patsubst %,${INSTALL_DEST_DIR}/gitaly-%,${GIT_EXECUTABLES}) +install-bundled-git: install-bundled-git-v2.33.1.gl2 install-bundled-git-v2.35.1.gl1 +install-bundled-git-v2.33.1.gl2: $(patsubst %,${INSTALL_DEST_DIR}/gitaly-%,${GIT_EXECUTABLES}) +install-bundled-git-v2.35.1.gl1: $(patsubst %,${INSTALL_DEST_DIR}/gitaly-%-v2.35.1.gl1,${GIT_EXECUTABLES}) ifdef WITH_BUNDLED_GIT build: build-bundled-git @@ -591,7 +591,7 @@ ${DEPENDENCY_DIR}: | ${BUILD_DIR} .PHONY: dependency-version ${DEPENDENCY_DIR}/libgit2.version: dependency-version | ${DEPENDENCY_DIR} ${Q}[ x"$$(cat "$@" 2>/dev/null)" = x"${LIBGIT2_VERSION} ${LIBGIT2_BUILD_OPTIONS}" ] || >$@ echo -n "${LIBGIT2_VERSION} ${LIBGIT2_BUILD_OPTIONS}" -${DEPENDENCY_DIR}/git.version: dependency-version | ${DEPENDENCY_DIR} +${DEPENDENCY_DIR}/git-%.version: dependency-version | ${DEPENDENCY_DIR} ${Q}[ x"$$(cat "$@" 2>/dev/null)" = x"${GIT_VERSION}.${GIT_EXTRA_VERSION} ${GIT_BUILD_OPTIONS} ${GIT_PATCHES}" ] || >$@ echo -n "${GIT_VERSION}.${GIT_EXTRA_VERSION} ${GIT_BUILD_OPTIONS} ${GIT_PATCHES}" ${DEPENDENCY_DIR}/protoc.version: dependency-version | ${DEPENDENCY_DIR} ${Q}[ x"$$(cat "$@" 2>/dev/null)" = x"${PROTOC_VERSION} ${PROTOC_BUILD_OPTIONS}" ] || >$@ echo -n "${PROTOC_VERSION} ${PROTOC_BUILD_OPTIONS}" @@ -616,45 +616,81 @@ ${LIBGIT2_INSTALL_DIR}/lib/libgit2.a: ${DEPENDENCY_DIR}/libgit2.version # build binaries inside of it, we cannot depend on it directly or we'd # otherwise try to rebuild all targets depending on it whenever we build # something else. We thus depend on the Makefile instead. -${GIT_SOURCE_DIR}/Makefile: ${DEPENDENCY_DIR}/git.version - ${Q}${GIT} -c init.defaultBranch=master init ${GIT_QUIET} ${GIT_SOURCE_DIR} - ${Q}${GIT} -C "${GIT_SOURCE_DIR}" config remote.origin.url ${GIT_REPO_URL} - ${Q}${GIT} -C "${GIT_SOURCE_DIR}" config remote.origin.tagOpt --no-tags - ${Q}${GIT} -C "${GIT_SOURCE_DIR}" fetch --depth 1 ${GIT_QUIET} origin ${GIT_VERSION} - ${Q}${GIT} -C "${GIT_SOURCE_DIR}" reset --hard - ${Q}${GIT} -C "${GIT_SOURCE_DIR}" checkout ${GIT_QUIET} --detach FETCH_HEAD -ifdef GIT_PATCHES - ${Q}${GIT} -C "${GIT_SOURCE_DIR}" apply $(addprefix "${SOURCE_DIR}"/_support/git-patches/,${GIT_PATCHES}) -endif +${DEPENDENCY_DIR}/git-%/Makefile: ${DEPENDENCY_DIR}/git-%.version + ${Q}${GIT} -c init.defaultBranch=master init ${GIT_QUIET} "${@D}" + ${Q}${GIT} -C "${@D}" config remote.origin.url ${GIT_REPO_URL} + ${Q}${GIT} -C "${@D}" config remote.origin.tagOpt --no-tags + ${Q}${GIT} -C "${@D}" fetch --depth 1 ${GIT_QUIET} origin ${GIT_VERSION} + ${Q}${GIT} -C "${@D}" reset --hard + ${Q}${GIT} -C "${@D}" checkout ${GIT_QUIET} --detach FETCH_HEAD + ${Q}if test -n "${GIT_PATCHES}"; then ${GIT} -C "${@D}" apply $(addprefix "${SOURCE_DIR}"/_support/git-patches/,${GIT_PATCHES}); fi @ # We're writing the version into the "version" file in Git's own source @ # directory. If it exists, Git's Makefile will pick it up and use it as @ # the version instead of auto-detecting via git-describe(1). -ifdef GIT_EXTRA_VERSION - ${Q}echo ${GIT_VERSION}.${GIT_EXTRA_VERSION} >"${GIT_SOURCE_DIR}"/version -else - ${Q}rm -f "${GIT_SOURCE_DIR}"/version -endif + ${Q}if test -n "${GIT_EXTRA_VERSION}"; then echo ${GIT_VERSION}.${GIT_EXTRA_VERSION} >"${@D}"/version; else rm -f "${@D}"/version; fi ${Q}touch $@ -${GIT_SOURCE_DIR}/%: ${GIT_SOURCE_DIR}/Makefile - ${Q}env -u PROFILE -u MAKEFLAGS -u GIT_VERSION ${MAKE} -C ${GIT_SOURCE_DIR} -j$(shell nproc) prefix=${GIT_PREFIX} ${GIT_BUILD_OPTIONS} $(notdir $@) +$(patsubst %,${DEPENDENCY_DIR}/git-\%/%,${GIT_EXECUTABLES}): ${DEPENDENCY_DIR}/git-%/Makefile + ${Q}env -u PROFILE -u MAKEFLAGS -u GIT_VERSION ${MAKE} -C "${@D}" -j$(shell nproc) prefix=${GIT_PREFIX} ${GIT_BUILD_OPTIONS} ${GIT_EXECUTABLES} ${Q}touch $@ -${GIT_PREFIX}/bin/git: ${GIT_SOURCE_DIR}/Makefile +${GIT_PREFIX}/bin/git: ${DEPENDENCY_DIR}/git-${GIT_VERSION}.${GIT_EXTRA_VERSION}/Makefile @ # Remove the Git installation first in case GIT_PREFIX is the default @ # prefix which always points into our build directory. This is done so @ # we never end up with mixed Git installations on developer machines. @ # We cannot ever remove GIT_PREFIX though in case they're different @ # because it may point to a user-controlled directory. ${Q}if [ "x${GIT_DEFAULT_PREFIX}" = "x${GIT_PREFIX}" ]; then rm -rf "${GIT_DEFAULT_PREFIX}"; fi - ${Q}env -u PROFILE -u MAKEFLAGS -u GIT_VERSION ${MAKE} -C ${GIT_SOURCE_DIR} -j$(shell nproc) prefix=${GIT_PREFIX} ${GIT_BUILD_OPTIONS} install + ${Q}env -u PROFILE -u MAKEFLAGS -u GIT_VERSION ${MAKE} -C "$(<D)" -j$(shell nproc) prefix=${GIT_PREFIX} ${GIT_BUILD_OPTIONS} install ${Q}touch $@ -${BUILD_DIR}/bin/gitaly-%: ${GIT_SOURCE_DIR}/% | ${BUILD_DIR}/bin +${BUILD_DIR}/bin/gitaly-%: ${DEPENDENCY_DIR}/git-${GIT_VERSION}.${GIT_EXTRA_VERSION}/% | ${BUILD_DIR}/bin + ${Q}install $< $@ + +# Speed up fetches by making better use of the commit-graph and by not +# computing the output-width if not requested. Merged into next via +# 2b331293fb (Merge branch 'ps/fetch-optim-with-commit-graph' into next, +# 2022-02-14). +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES := 0019-fetch-pack-use-commit-graph-when-computing-cutoff.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0020-fetch-skip-computing-output-width-when-not-printing-.patch + +# Skip execution of the reference-transaction hook a second time via the +# packed-refs backend in case loose references are deleted. This will +# eventually make a workaround obsolete where we had to filter out all +# invocations of the hook where we only saw force-deletions of references such +# that we don't depend on whether refs are packed or not. Merged into main via +# 991b4d47f0 (Merge branch +# 'ps/avoid-unnecessary-hook-invocation-with-packed-refs', 2022-02-18). +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0021-refs-extract-packed_refs_delete_refs-to-allow-contro.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0022-refs-allow-passing-flags-when-beginning-transactions.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0023-refs-allow-skipping-the-reference-transaction-hook.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0024-refs-demonstrate-excessive-execution-of-the-referenc.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0025-refs-do-not-execute-reference-transaction-hook-on-pa.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0026-refs-skip-hooks-when-deleting-uncovered-packed-refs.patch + +# Fix atomicity of git-fetch(1) to also cover pruning of references and +# backfilling of tags. Previously, each reference modified via any of both +# means would have created its own transaction and thus led to multiple hook +# invocations. Merged into next via 3824153b23 (Merge branch 'ps/fetch-atomic' +# into next, 2022-02-18). The first patch is unrelated, but required to fix a +# merge conflict. It has been merged to main via c73d46b3a8 (Merge branch +# 'tg/fetch-prune-exit-code-fix', 2022-02-11). +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0027-fetch-prune-exit-with-error-if-pruning-fails.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0028-fetch-increase-test-coverage-of-fetches.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0029-fetch-backfill-tags-before-setting-upstream.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0030-fetch-control-lifecycle-of-FETCH_HEAD-in-a-single-pl.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0031-fetch-report-errors-when-backfilling-tags-fails.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0032-refs-add-interface-to-iterate-over-queued-transactio.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0033-fetch-make-atomic-flag-cover-backfilling-of-tags.patch +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_PATCHES += 0034-fetch-make-atomic-flag-cover-pruning-of-refs.patch + +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_VERSION = v2.35.1 +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: override GIT_EXTRA_VERSION = gl1 +${BUILD_DIR}/bin/gitaly-%-v2.35.1.gl1: ${DEPENDENCY_DIR}/git-v2.35.1.gl1/% | ${BUILD_DIR}/bin ${Q}install $< $@ ${INSTALL_DEST_DIR}/gitaly-%: ${BUILD_DIR}/bin/gitaly-% - ${Q}mkdir -p $(@D) + ${Q}mkdir -p ${@D} ${Q}install $< $@ ${PROTOC}: ${DEPENDENCY_DIR}/protoc.version | ${TOOLS_DIR} diff --git a/_support/git-patches/0021-refs-extract-packed_refs_delete_refs-to-allow-contro.patch b/_support/git-patches/0021-refs-extract-packed_refs_delete_refs-to-allow-contro.patch new file mode 100644 index 000000000..47dd3e41c --- /dev/null +++ b/_support/git-patches/0021-refs-extract-packed_refs_delete_refs-to-allow-contro.patch @@ -0,0 +1,156 @@ +From c74f385fb46855ac0db222b6845ddb95e6a36264 Mon Sep 17 00:00:00 2001 +Message-Id: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Mon, 17 Jan 2022 09:12:31 +0100 +Subject: [PATCH 21/34] refs: extract packed_refs_delete_refs() to allow + control of transaction + +When deleting loose refs, then we also have to delete the refs in the +packed backend. This is done by calling `refs_delete_refs()`, which +then uses the packed-backend's logic to delete refs. This doesn't allow +us to exercise any control over the reference transaction which is being +created in the packed backend, which is required in a subsequent commit. + +Extract a new function `packed_refs_delete_refs()`, which hosts most of +the logic to delete refs except for creating the transaction itself. +Like this, we can easily create the transaction in the files backend +and thus exert more control over it. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 69840cc0f7b4f3352903bd2b8f3de7077916c26b) +--- + refs/files-backend.c | 13 ++++++++++--- + refs/packed-backend.c | 26 ++++++++++++++++++++------ + refs/packed-backend.h | 7 +++++++ + 3 files changed, 37 insertions(+), 9 deletions(-) + +diff --git a/refs/files-backend.c b/refs/files-backend.c +index 43a3b882d7..18baea4c6a 100644 +--- a/refs/files-backend.c ++++ b/refs/files-backend.c +@@ -1244,6 +1244,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, + { + struct files_ref_store *refs = + files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); ++ struct ref_transaction *transaction = NULL; + struct strbuf err = STRBUF_INIT; + int i, result = 0; + +@@ -1253,10 +1254,14 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, + if (packed_refs_lock(refs->packed_ref_store, 0, &err)) + goto error; + +- if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { +- packed_refs_unlock(refs->packed_ref_store); ++ transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); ++ if (!transaction) ++ goto error; ++ ++ result = packed_refs_delete_refs(refs->packed_ref_store, ++ transaction, msg, refnames, flags); ++ if (result) + goto error; +- } + + packed_refs_unlock(refs->packed_ref_store); + +@@ -1267,6 +1272,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, + result |= error(_("could not remove reference %s"), refname); + } + ++ ref_transaction_free(transaction); + strbuf_release(&err); + return result; + +@@ -1283,6 +1289,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, + else + error(_("could not delete references: %s"), err.buf); + ++ ref_transaction_free(transaction); + strbuf_release(&err); + return -1; + } +diff --git a/refs/packed-backend.c b/refs/packed-backend.c +index d91a2018f6..960b43ff76 100644 +--- a/refs/packed-backend.c ++++ b/refs/packed-backend.c +@@ -1521,15 +1521,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, + static int packed_delete_refs(struct ref_store *ref_store, const char *msg, + struct string_list *refnames, unsigned int flags) + { +- 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; + +@@ -1543,6 +1538,26 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, + if (!transaction) + return -1; + ++ ret = packed_refs_delete_refs(ref_store, transaction, ++ msg, refnames, flags); ++ ++ ref_transaction_free(transaction); ++ return ret; ++} ++ ++int packed_refs_delete_refs(struct ref_store *ref_store, ++ struct ref_transaction *transaction, ++ const char *msg, ++ struct string_list *refnames, ++ unsigned int flags) ++{ ++ struct strbuf err = STRBUF_INIT; ++ struct string_list_item *item; ++ int ret; ++ ++ /* Assert that the ref store refers to a packed backend. */ ++ packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); ++ + for_each_string_list_item(item, refnames) { + if (ref_transaction_delete(transaction, item->string, NULL, + flags, msg, &err)) { +@@ -1562,7 +1577,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, + error(_("could not delete references: %s"), err.buf); + } + +- ref_transaction_free(transaction); + strbuf_release(&err); + return ret; + } +diff --git a/refs/packed-backend.h b/refs/packed-backend.h +index 9dd8a344c3..52e0490753 100644 +--- a/refs/packed-backend.h ++++ b/refs/packed-backend.h +@@ -3,6 +3,7 @@ + + struct repository; + struct ref_transaction; ++struct string_list; + + /* + * Support for storing references in a `packed-refs` file. +@@ -27,6 +28,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) + void packed_refs_unlock(struct ref_store *ref_store); + int packed_refs_is_locked(struct ref_store *ref_store); + ++int packed_refs_delete_refs(struct ref_store *ref_store, ++ struct ref_transaction *transaction, ++ const char *msg, ++ struct string_list *refnames, ++ unsigned int flags); ++ + /* + * Return true if `transaction` really needs to be carried out against + * the specified packed_ref_store, or false if it can be skipped +-- +2.35.1 + diff --git a/_support/git-patches/0022-refs-allow-passing-flags-when-beginning-transactions.patch b/_support/git-patches/0022-refs-allow-passing-flags-when-beginning-transactions.patch new file mode 100644 index 000000000..8038daca2 --- /dev/null +++ b/_support/git-patches/0022-refs-allow-passing-flags-when-beginning-transactions.patch @@ -0,0 +1,183 @@ +From 60d0682e02db5c197c09bc4cd5a643dc2269cca2 Mon Sep 17 00:00:00 2001 +Message-Id: <60d0682e02db5c197c09bc4cd5a643dc2269cca2.1646206541.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Mon, 17 Jan 2022 09:12:35 +0100 +Subject: [PATCH 22/34] refs: allow passing flags when beginning transactions + +We do not currently have any flags when creating reference transactions, +but we'll add one to disable execution of the reference transaction hook +in some cases. + +Allow passing flags to `ref_store_transaction_begin()` to prepare for +this change. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit fbe73f61cbc29f6c4a85478cf792c37dbe5aa26c) +--- + refs.c | 8 +++++--- + refs.h | 3 ++- + refs/files-backend.c | 10 +++++----- + refs/packed-backend.c | 2 +- + refs/refs-internal.h | 1 + + sequencer.c | 2 +- + 6 files changed, 15 insertions(+), 11 deletions(-) + +diff --git a/refs.c b/refs.c +index addb26293b..f498d232e5 100644 +--- a/refs.c ++++ b/refs.c +@@ -800,7 +800,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + +- transaction = ref_store_transaction_begin(refs, &err); ++ transaction = ref_store_transaction_begin(refs, 0, &err); + if (!transaction || + ref_transaction_delete(transaction, refname, old_oid, + flags, msg, &err) || +@@ -1005,6 +1005,7 @@ int read_ref_at(struct ref_store *refs, const char *refname, + } + + struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, ++ unsigned int flags, + struct strbuf *err) + { + struct ref_transaction *tr; +@@ -1012,12 +1013,13 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + + CALLOC_ARRAY(tr, 1); + tr->ref_store = refs; ++ tr->flags = flags; + return tr; + } + + struct ref_transaction *ref_transaction_begin(struct strbuf *err) + { +- return ref_store_transaction_begin(get_main_ref_store(the_repository), err); ++ return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err); + } + + void ref_transaction_free(struct ref_transaction *transaction) +@@ -1156,7 +1158,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, + struct strbuf err = STRBUF_INIT; + int ret = 0; + +- t = ref_store_transaction_begin(refs, &err); ++ t = ref_store_transaction_begin(refs, 0, &err); + if (!t || + ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, + &err) || +diff --git a/refs.h b/refs.h +index 8f91a7f9ff..3a141d7066 100644 +--- a/refs.h ++++ b/refs.h +@@ -231,7 +231,7 @@ char *repo_default_branch_name(struct repository *r, int quiet); + * struct strbuf err = STRBUF_INIT; + * int ret = 0; + * +- * transaction = ref_store_transaction_begin(refs, &err); ++ * transaction = ref_store_transaction_begin(refs, 0, &err); + * if (!transaction || + * ref_transaction_update(...) || + * ref_transaction_create(...) || +@@ -573,6 +573,7 @@ enum action_on_err { + * be freed by calling ref_transaction_free(). + */ + struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, ++ unsigned int flags, + struct strbuf *err); + struct ref_transaction *ref_transaction_begin(struct strbuf *err); + +diff --git a/refs/files-backend.c b/refs/files-backend.c +index 18baea4c6a..758d12a0fa 100644 +--- a/refs/files-backend.c ++++ b/refs/files-backend.c +@@ -1116,7 +1116,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) + if (check_refname_format(r->name, 0)) + return; + +- transaction = ref_store_transaction_begin(&refs->base, &err); ++ transaction = ref_store_transaction_begin(&refs->base, 0, &err); + if (!transaction) + goto cleanup; + ref_transaction_add_update( +@@ -1187,7 +1187,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) + struct strbuf err = STRBUF_INIT; + struct ref_transaction *transaction; + +- transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); ++ transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + if (!transaction) + return -1; + +@@ -1254,7 +1254,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, + if (packed_refs_lock(refs->packed_ref_store, 0, &err)) + goto error; + +- transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); ++ transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); + if (!transaction) + goto error; + +@@ -2769,7 +2769,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, + */ + if (!packed_transaction) { + packed_transaction = ref_store_transaction_begin( +- refs->packed_ref_store, err); ++ refs->packed_ref_store, 0, err); + if (!packed_transaction) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; +@@ -3040,7 +3040,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, + &affected_refnames)) + BUG("initial ref transaction called with existing refs"); + +- packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err); ++ packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); + if (!packed_transaction) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; +diff --git a/refs/packed-backend.c b/refs/packed-backend.c +index 960b43ff76..27dd8c3922 100644 +--- a/refs/packed-backend.c ++++ b/refs/packed-backend.c +@@ -1534,7 +1534,7 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, + * updates into a single transaction. + */ + +- transaction = ref_store_transaction_begin(ref_store, &err); ++ transaction = ref_store_transaction_begin(ref_store, 0, &err); + if (!transaction) + return -1; + +diff --git a/refs/refs-internal.h b/refs/refs-internal.h +index 7ff6fba4f0..6e15db3ca4 100644 +--- a/refs/refs-internal.h ++++ b/refs/refs-internal.h +@@ -213,6 +213,7 @@ struct ref_transaction { + size_t nr; + enum ref_transaction_state state; + void *backend_data; ++ unsigned int flags; + }; + + /* +diff --git a/sequencer.c b/sequencer.c +index 5213d16e97..5a2b609557 100644 +--- a/sequencer.c ++++ b/sequencer.c +@@ -3588,7 +3588,7 @@ static int do_label(struct repository *r, const char *name, int len) + strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); + strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); + +- transaction = ref_store_transaction_begin(refs, &err); ++ transaction = ref_store_transaction_begin(refs, 0, &err); + if (!transaction) { + error("%s", err.buf); + ret = -1; +-- +2.35.1 + diff --git a/_support/git-patches/0023-refs-allow-skipping-the-reference-transaction-hook.patch b/_support/git-patches/0023-refs-allow-skipping-the-reference-transaction-hook.patch new file mode 100644 index 000000000..cd194f8aa --- /dev/null +++ b/_support/git-patches/0023-refs-allow-skipping-the-reference-transaction-hook.patch @@ -0,0 +1,60 @@ +From 4eccd16b45516df5ab02288d0c50c16e03cc44e4 Mon Sep 17 00:00:00 2001 +Message-Id: <4eccd16b45516df5ab02288d0c50c16e03cc44e4.1646206541.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Mon, 17 Jan 2022 09:12:39 +0100 +Subject: [PATCH 23/34] refs: allow skipping the reference-transaction hook + +The reference-transaction hook is executing whenever we prepare, commit +or abort a reference transaction. While this is mostly intentional, in +case of the files backend we're leaking the implementation detail that +the store is in fact a composite store with one loose and one packed +backend to the caller. So while we want to execute the hook for all +logical updates, executing it for such implementation details is +unexpected. + +Prepare for a fix by adding a new flag which allows to skip execution of +the hook. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 958fbc74e3d0fcc88b2065190e23db556a963644) +--- + refs.c | 3 +++ + refs.h | 5 +++++ + 2 files changed, 8 insertions(+) + +diff --git a/refs.c b/refs.c +index f498d232e5..435b90b1ec 100644 +--- a/refs.c ++++ b/refs.c +@@ -2084,6 +2084,9 @@ static int run_transaction_hook(struct ref_transaction *transaction, + const char *hook; + int ret = 0, i; + ++ if (transaction->flags & REF_TRANSACTION_SKIP_HOOK) ++ return 0; ++ + hook = find_hook("reference-transaction"); + if (!hook) + return ret; +diff --git a/refs.h b/refs.h +index 3a141d7066..a015354fd6 100644 +--- a/refs.h ++++ b/refs.h +@@ -568,6 +568,11 @@ enum action_on_err { + UPDATE_REFS_QUIET_ON_ERR + }; + ++/* ++ * Skip executing the reference-transaction hook. ++ */ ++#define REF_TRANSACTION_SKIP_HOOK (1 << 0) ++ + /* + * Begin a reference transaction. The reference transaction must + * be freed by calling ref_transaction_free(). +-- +2.35.1 + diff --git a/_support/git-patches/0024-refs-demonstrate-excessive-execution-of-the-referenc.patch b/_support/git-patches/0024-refs-demonstrate-excessive-execution-of-the-referenc.patch new file mode 100644 index 000000000..aa6d96a3e --- /dev/null +++ b/_support/git-patches/0024-refs-demonstrate-excessive-execution-of-the-referenc.patch @@ -0,0 +1,97 @@ +From 079f96ed16bb70f99fbe810b1646b1709bb82871 Mon Sep 17 00:00:00 2001 +Message-Id: <079f96ed16bb70f99fbe810b1646b1709bb82871.1646206541.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Mon, 17 Jan 2022 09:12:44 +0100 +Subject: [PATCH 24/34] refs: demonstrate excessive execution of the + reference-transaction hook + +Add tests which demonstate that we're executing the +reference-transaction hook too often in some cases, which thus leaks +implementation details about the reference store's implementation +itself. Behaviour will be fixed in follow-up commits. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 2ce825436268d6409bee8ebb5f5500b7ff172b1e) +--- + t/t1416-ref-transaction-hooks.sh | 64 ++++++++++++++++++++++++++++++++ + 1 file changed, 64 insertions(+) + +diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh +index 6c941027a8..0567fbdf0b 100755 +--- a/t/t1416-ref-transaction-hooks.sh ++++ b/t/t1416-ref-transaction-hooks.sh +@@ -136,4 +136,68 @@ test_expect_success 'interleaving hook calls succeed' ' + test_cmp expect target-repo.git/actual + ' + ++test_expect_success 'hook does not get called on packing refs' ' ++ # Pack references first such that we are in a known state. ++ git pack-refs --all && ++ ++ write_script .git/hooks/reference-transaction <<-\EOF && ++ echo "$@" >>actual ++ cat >>actual ++ EOF ++ rm -f actual && ++ ++ git update-ref refs/heads/unpacked-ref $POST_OID && ++ git pack-refs --all && ++ ++ # We only expect a single hook invocation, which is the call to ++ # git-update-ref(1). But currently, packing refs will also trigger the ++ # hook. ++ cat >expect <<-EOF && ++ prepared ++ $ZERO_OID $POST_OID refs/heads/unpacked-ref ++ committed ++ $ZERO_OID $POST_OID refs/heads/unpacked-ref ++ prepared ++ $ZERO_OID $POST_OID refs/heads/unpacked-ref ++ committed ++ $ZERO_OID $POST_OID refs/heads/unpacked-ref ++ prepared ++ $POST_OID $ZERO_OID refs/heads/unpacked-ref ++ committed ++ $POST_OID $ZERO_OID refs/heads/unpacked-ref ++ EOF ++ ++ test_cmp expect actual ++' ++ ++test_expect_success 'deleting packed ref calls hook once' ' ++ # Create a reference and pack it. ++ git update-ref refs/heads/to-be-deleted $POST_OID && ++ git pack-refs --all && ++ ++ write_script .git/hooks/reference-transaction <<-\EOF && ++ echo "$@" >>actual ++ cat >>actual ++ EOF ++ rm -f actual && ++ ++ git update-ref -d refs/heads/to-be-deleted $POST_OID && ++ ++ # We only expect a single hook invocation, which is the logical ++ # deletion. But currently, we see two interleaving transactions, once ++ # for deleting the loose refs and once for deleting the packed ref. ++ cat >expect <<-EOF && ++ prepared ++ $ZERO_OID $ZERO_OID refs/heads/to-be-deleted ++ prepared ++ $POST_OID $ZERO_OID refs/heads/to-be-deleted ++ committed ++ $ZERO_OID $ZERO_OID refs/heads/to-be-deleted ++ committed ++ $POST_OID $ZERO_OID refs/heads/to-be-deleted ++ EOF ++ ++ test_cmp expect actual ++' ++ + test_done +-- +2.35.1 + diff --git a/_support/git-patches/0025-refs-do-not-execute-reference-transaction-hook-on-pa.patch b/_support/git-patches/0025-refs-do-not-execute-reference-transaction-hook-on-pa.patch new file mode 100644 index 000000000..f2e7c06ed --- /dev/null +++ b/_support/git-patches/0025-refs-do-not-execute-reference-transaction-hook-on-pa.patch @@ -0,0 +1,81 @@ +From ef35ab926309bf406d3871679c576c718708a93b Mon Sep 17 00:00:00 2001 +Message-Id: <ef35ab926309bf406d3871679c576c718708a93b.1646206541.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Mon, 17 Jan 2022 09:12:48 +0100 +Subject: [PATCH 25/34] refs: do not execute reference-transaction hook on + packing refs + +The reference-transaction hook is supposed to track logical changes to +references, but it currently also gets executed when packing refs in a +repository. This is unexpected and ultimately not all that useful: +packing refs is not supposed to result in any user-visible change to the +refs' state, and it ultimately is an implementation detail of how refs +stores work. + +Fix this excessive execution of the hook when packing refs. + +Reported-by: Waleed Khan <me@waleedkhan.name> +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit ffad9941383465553bf26d88050f3243726f30df) +--- + refs/files-backend.c | 6 ++++-- + t/t1416-ref-transaction-hooks.sh | 11 +---------- + 2 files changed, 5 insertions(+), 12 deletions(-) + +diff --git a/refs/files-backend.c b/refs/files-backend.c +index 758d12a0fa..19f43e8d29 100644 +--- a/refs/files-backend.c ++++ b/refs/files-backend.c +@@ -1116,7 +1116,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) + if (check_refname_format(r->name, 0)) + return; + +- transaction = ref_store_transaction_begin(&refs->base, 0, &err); ++ transaction = ref_store_transaction_begin(&refs->base, ++ REF_TRANSACTION_SKIP_HOOK, &err); + if (!transaction) + goto cleanup; + ref_transaction_add_update( +@@ -1187,7 +1188,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) + struct strbuf err = STRBUF_INIT; + struct ref_transaction *transaction; + +- transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); ++ transaction = ref_store_transaction_begin(refs->packed_ref_store, ++ REF_TRANSACTION_SKIP_HOOK, &err); + if (!transaction) + return -1; + +diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh +index 0567fbdf0b..f9d3d5213f 100755 +--- a/t/t1416-ref-transaction-hooks.sh ++++ b/t/t1416-ref-transaction-hooks.sh +@@ -150,21 +150,12 @@ test_expect_success 'hook does not get called on packing refs' ' + git pack-refs --all && + + # We only expect a single hook invocation, which is the call to +- # git-update-ref(1). But currently, packing refs will also trigger the +- # hook. ++ # git-update-ref(1). + cat >expect <<-EOF && + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref +- prepared +- $ZERO_OID $POST_OID refs/heads/unpacked-ref +- committed +- $ZERO_OID $POST_OID refs/heads/unpacked-ref +- prepared +- $POST_OID $ZERO_OID refs/heads/unpacked-ref +- committed +- $POST_OID $ZERO_OID refs/heads/unpacked-ref + EOF + + test_cmp expect actual +-- +2.35.1 + diff --git a/_support/git-patches/0026-refs-skip-hooks-when-deleting-uncovered-packed-refs.patch b/_support/git-patches/0026-refs-skip-hooks-when-deleting-uncovered-packed-refs.patch new file mode 100644 index 000000000..3b21bf489 --- /dev/null +++ b/_support/git-patches/0026-refs-skip-hooks-when-deleting-uncovered-packed-refs.patch @@ -0,0 +1,99 @@ +From d56f2a0e1eed4d0a39b99638117e0c8259e5078d Mon Sep 17 00:00:00 2001 +Message-Id: <d56f2a0e1eed4d0a39b99638117e0c8259e5078d.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Mon, 17 Jan 2022 09:12:53 +0100 +Subject: [PATCH 26/34] refs: skip hooks when deleting uncovered packed refs + +When deleting refs from the loose-files refs backend, then we need to be +careful to also delete the same ref from the packed refs backend, if it +exists. If we don't, then deleting the loose ref would "uncover" the +packed ref. We thus always have to queue up deletions of refs for both +the loose and the packed refs backend. This is done in two separate +transactions, where the end result is that the reference-transaction +hook is executed twice for the deleted refs. + +This behaviour is quite misleading: it's exposing implementation details +of how the files backend works to the user, in contrast to the logical +updates that we'd really want to expose via the hook. Worse yet, whether +the hook gets executed once or twice depends on how well-packed the +repository is: if the ref only exists as a loose ref, then we execute it +once, otherwise if it is also packed then we execute it twice. + +Fix this behaviour and don't execute the reference-transaction hook at +all when refs in the packed-refs backend if it's driven by the files +backend. This works as expected even in case the refs to be deleted only +exist in the packed-refs backend because the loose-backend always queues +refs in its own transaction even if they don't exist such that they can +be locked for concurrent creation. And it also does the right thing in +case neither of the backends has the ref because that would cause the +transaction to fail completely. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 2ed1b64ebdeefc7f9473ae159fb45ff0c6cf121a) +--- + refs/files-backend.c | 9 ++++++--- + t/t1416-ref-transaction-hooks.sh | 7 +------ + 2 files changed, 7 insertions(+), 9 deletions(-) + +diff --git a/refs/files-backend.c b/refs/files-backend.c +index 19f43e8d29..c931dd479c 100644 +--- a/refs/files-backend.c ++++ b/refs/files-backend.c +@@ -1256,7 +1256,8 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, + if (packed_refs_lock(refs->packed_ref_store, 0, &err)) + goto error; + +- transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, &err); ++ transaction = ref_store_transaction_begin(refs->packed_ref_store, ++ REF_TRANSACTION_SKIP_HOOK, &err); + if (!transaction) + goto error; + +@@ -2771,7 +2772,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, + */ + if (!packed_transaction) { + packed_transaction = ref_store_transaction_begin( +- refs->packed_ref_store, 0, err); ++ refs->packed_ref_store, ++ REF_TRANSACTION_SKIP_HOOK, err); + if (!packed_transaction) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; +@@ -3042,7 +3044,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, + &affected_refnames)) + BUG("initial ref transaction called with existing refs"); + +- packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, 0, err); ++ packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, ++ REF_TRANSACTION_SKIP_HOOK, err); + if (!packed_transaction) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; +diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh +index f9d3d5213f..4e1e84a91f 100755 +--- a/t/t1416-ref-transaction-hooks.sh ++++ b/t/t1416-ref-transaction-hooks.sh +@@ -175,16 +175,11 @@ test_expect_success 'deleting packed ref calls hook once' ' + git update-ref -d refs/heads/to-be-deleted $POST_OID && + + # We only expect a single hook invocation, which is the logical +- # deletion. But currently, we see two interleaving transactions, once +- # for deleting the loose refs and once for deleting the packed ref. ++ # deletion. + cat >expect <<-EOF && +- prepared +- $ZERO_OID $ZERO_OID refs/heads/to-be-deleted + prepared + $POST_OID $ZERO_OID refs/heads/to-be-deleted + committed +- $ZERO_OID $ZERO_OID refs/heads/to-be-deleted +- committed + $POST_OID $ZERO_OID refs/heads/to-be-deleted + EOF + +-- +2.35.1 + diff --git a/_support/git-patches/0027-fetch-prune-exit-with-error-if-pruning-fails.patch b/_support/git-patches/0027-fetch-prune-exit-with-error-if-pruning-fails.patch new file mode 100644 index 000000000..a6a75c054 --- /dev/null +++ b/_support/git-patches/0027-fetch-prune-exit-with-error-if-pruning-fails.patch @@ -0,0 +1,74 @@ +From 05bb5009a6bded848b30bb82cc02e6bcb90a8fc3 Mon Sep 17 00:00:00 2001 +Message-Id: <05bb5009a6bded848b30bb82cc02e6bcb90a8fc3.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Thomas Gummerer <t.gummerer@gmail.com> +Date: Mon, 31 Jan 2022 13:30:47 +0000 +Subject: [PATCH 27/34] fetch --prune: exit with error if pruning fails + +When pruning refs fails, we print an error to stderr, but still +exit 0 from 'git fetch'. Since this is a genuine error, fetch +should be exiting with some non-zero exit code. Make it so. + +The --prune option was introduced in f360d844de ("builtin-fetch: add +--prune option", 2009-11-10). Unfortunately it's unclear from that +commit whether ignoring the exit code was an oversight or +intentional, but it feels like an oversight. + +Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de> +Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit c9e04d905edb5487c43b03304704e8d1248f9ac0) +--- + builtin/fetch.c | 10 ++++++---- + t/t5510-fetch.sh | 11 +++++++++++ + 2 files changed, 17 insertions(+), 4 deletions(-) + +diff --git a/builtin/fetch.c b/builtin/fetch.c +index ebbde5d56d..f5a64c7351 100644 +--- a/builtin/fetch.c ++++ b/builtin/fetch.c +@@ -1613,12 +1613,14 @@ static int do_fetch(struct transport *transport, + * don't care whether --tags was specified. + */ + if (rs->nr) { +- prune_refs(rs, ref_map, transport->url); ++ retcode = prune_refs(rs, ref_map, transport->url); + } else { +- prune_refs(&transport->remote->fetch, +- ref_map, +- transport->url); ++ retcode = prune_refs(&transport->remote->fetch, ++ ref_map, ++ transport->url); + } ++ if (retcode != 0) ++ retcode = 1; + } + if (fetch_and_consume_refs(transport, ref_map, worktrees)) { + free_refs(ref_map); +diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh +index 20f7110ec1..ef0da0a63b 100755 +--- a/t/t5510-fetch.sh ++++ b/t/t5510-fetch.sh +@@ -164,6 +164,17 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' + git rev-parse sometag + ' + ++test_expect_success REFFILES 'fetch --prune fails to delete branches' ' ++ cd "$D" && ++ git clone . prune-fail && ++ cd prune-fail && ++ git update-ref refs/remotes/origin/extrabranch main && ++ : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds && ++ >.git/packed-refs.new && ++ ++ test_must_fail git fetch --prune origin ++' ++ + test_expect_success 'fetch --atomic works with a single branch' ' + test_when_finished "rm -rf \"$D\"/atomic" && + +-- +2.35.1 + diff --git a/_support/git-patches/0028-fetch-increase-test-coverage-of-fetches.patch b/_support/git-patches/0028-fetch-increase-test-coverage-of-fetches.patch new file mode 100644 index 000000000..412a908ef --- /dev/null +++ b/_support/git-patches/0028-fetch-increase-test-coverage-of-fetches.patch @@ -0,0 +1,169 @@ +From e6b86c1dcd01137afe71fe2c303efcf587b01abf Mon Sep 17 00:00:00 2001 +Message-Id: <e6b86c1dcd01137afe71fe2c303efcf587b01abf.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Feb 2022 14:04:16 +0100 +Subject: [PATCH 28/34] fetch: increase test coverage of fetches + +When using git-fetch(1) with the `--atomic` flag the expectation is that +either all of the references are updated, or alternatively none are in +case the fetch fails. While we already have tests for this, we do not +have any tests which exercise atomicity either when pruning deleted refs +or when backfilling tags. This gap in test coverage hides that we indeed +don't handle atomicity correctly for both of these cases. + +Add test cases which cover these testing gaps to demonstrate the broken +behaviour. Note that tests are not marked as `test_expect_failure`: this +is done to explicitly demonstrate the current known-wrong behaviour, and +they will be fixed up as soon as we fix the underlying bugs. + +While at it this commit also adds another test case which demonstrates +that backfilling of tags does not return an error code in case the +backfill fails. This bug will also be fixed by a subsequent commit. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 2a0cafd464709cfa22fe7249290c644a2a26c520) +--- + t/t5503-tagfollow.sh | 81 ++++++++++++++++++++++++++++++++++++++++++++ + t/t5510-fetch.sh | 33 ++++++++++++++++++ + 2 files changed, 114 insertions(+) + +diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh +index 195fc64dd4..6ffe2a5719 100755 +--- a/t/t5503-tagfollow.sh ++++ b/t/t5503-tagfollow.sh +@@ -160,4 +160,85 @@ test_expect_success 'new clone fetch main and tags' ' + test_cmp expect actual + ' + ++test_expect_success 'atomic fetch with failing backfill' ' ++ git init clone3 && ++ ++ # We want to test whether a failure when backfilling tags correctly ++ # aborts the complete transaction when `--atomic` is passed: we should ++ # neither create the branch nor should we create the tag when either ++ # one of both fails to update correctly. ++ # ++ # To trigger failure we simply abort when backfilling a tag. ++ write_script clone3/.git/hooks/reference-transaction <<-\EOF && ++ while read oldrev newrev reference ++ do ++ if test "$reference" = refs/tags/tag1 ++ then ++ exit 1 ++ fi ++ done ++ EOF ++ ++ test_must_fail git -C clone3 fetch --atomic .. $B:refs/heads/something && ++ ++ # Creation of the tag has failed, so ideally refs/heads/something ++ # should not exist. The fact that it does demonstrates that there is ++ # a bug in the `--atomic` flag. ++ test $B = "$(git -C clone3 rev-parse --verify refs/heads/something)" ++' ++ ++test_expect_success 'atomic fetch with backfill should use single transaction' ' ++ git init clone4 && ++ ++ # Fetching with the `--atomic` flag should update all references in a ++ # single transaction, including backfilled tags. We thus expect to see ++ # a single reference transaction for the created branch and tags. ++ cat >expected <<-EOF && ++ prepared ++ $ZERO_OID $B refs/heads/something ++ $ZERO_OID $S refs/tags/tag2 ++ committed ++ $ZERO_OID $B refs/heads/something ++ $ZERO_OID $S refs/tags/tag2 ++ prepared ++ $ZERO_OID $T refs/tags/tag1 ++ committed ++ $ZERO_OID $T refs/tags/tag1 ++ EOF ++ ++ write_script clone4/.git/hooks/reference-transaction <<-\EOF && ++ ( echo "$*" && cat ) >>actual ++ EOF ++ ++ git -C clone4 fetch --atomic .. $B:refs/heads/something && ++ test_cmp expected clone4/actual ++' ++ ++test_expect_success 'backfill failure causes command to fail' ' ++ git init clone5 && ++ ++ write_script clone5/.git/hooks/reference-transaction <<-EOF && ++ while read oldrev newrev reference ++ do ++ if test "\$reference" = refs/tags/tag1 ++ then ++ # Create a nested tag below the actual tag we ++ # wanted to write, which causes a D/F conflict ++ # later when we want to commit refs/tags/tag1. ++ # We cannot just `exit 1` here given that this ++ # would cause us to die immediately. ++ git update-ref refs/tags/tag1/nested $B ++ exit \$! ++ fi ++ done ++ EOF ++ ++ # Even though we fail to create refs/tags/tag1 the below command ++ # unexpectedly succeeds. ++ git -C clone5 fetch .. $B:refs/heads/something && ++ test $B = $(git -C clone5 rev-parse --verify refs/heads/something) && ++ test $S = $(git -C clone5 rev-parse --verify tag2) && ++ test_must_fail git -C clone5 rev-parse --verify tag1 ++' ++ + test_done +diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh +index ef0da0a63b..70d51f343b 100755 +--- a/t/t5510-fetch.sh ++++ b/t/t5510-fetch.sh +@@ -343,6 +343,39 @@ test_expect_success 'fetch --atomic --append appends to FETCH_HEAD' ' + test_cmp expected atomic/.git/FETCH_HEAD + ' + ++test_expect_success 'fetch --atomic --prune executes a single reference transaction only' ' ++ test_when_finished "rm -rf \"$D\"/atomic" && ++ ++ cd "$D" && ++ git branch scheduled-for-deletion && ++ git clone . atomic && ++ git branch -D scheduled-for-deletion && ++ git branch new-branch && ++ head_oid=$(git rev-parse HEAD) && ++ ++ # Fetching with the `--atomic` flag should update all references in a ++ # single transaction. It is currently missing coverage of pruned ++ # references though, and as a result those may be committed to disk ++ # even if updating references fails later. ++ cat >expected <<-EOF && ++ prepared ++ $ZERO_OID $ZERO_OID refs/remotes/origin/scheduled-for-deletion ++ committed ++ $ZERO_OID $ZERO_OID refs/remotes/origin/scheduled-for-deletion ++ prepared ++ $ZERO_OID $head_oid refs/remotes/origin/new-branch ++ committed ++ $ZERO_OID $head_oid refs/remotes/origin/new-branch ++ EOF ++ ++ write_script atomic/.git/hooks/reference-transaction <<-\EOF && ++ ( echo "$*" && cat ) >>actual ++ EOF ++ ++ git -C atomic fetch --atomic --prune origin && ++ test_cmp expected atomic/actual ++' ++ + test_expect_success '--refmap="" ignores configured refspec' ' + cd "$TRASH_DIRECTORY" && + git clone "$D" remote-refs && +-- +2.35.1 + diff --git a/_support/git-patches/0029-fetch-backfill-tags-before-setting-upstream.patch b/_support/git-patches/0029-fetch-backfill-tags-before-setting-upstream.patch new file mode 100644 index 000000000..360bd10c2 --- /dev/null +++ b/_support/git-patches/0029-fetch-backfill-tags-before-setting-upstream.patch @@ -0,0 +1,116 @@ +From 6bb95bf1ec7cb06eca5161fe7dcf02cfabd8de83 Mon Sep 17 00:00:00 2001 +Message-Id: <6bb95bf1ec7cb06eca5161fe7dcf02cfabd8de83.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Feb 2022 14:04:20 +0100 +Subject: [PATCH 29/34] fetch: backfill tags before setting upstream + +The fetch code flow is a bit hard to understand right now: + + 1. We optionally prune all references which have vanished on the + remote side. + 2. We fetch and update all other references locally. + 3. We update the upstream branch in the gitconfig. + 4. We backfill tags pointing into the history we have just fetched. + +It is quite confusing that we fetch objects and update references in +both (2) and (4), which is further stressed by the point that we use a +`skip` goto label to jump from (3) to (4) in case we fail to update the +gitconfig as expected. + +Reorder the code to first update all local references, and only after we +have done so update the upstream branch information. This improves the +code flow and furthermore makes it easier to refactor the way we update +references together. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit efbade066083eb0a8ccee5a8290cd3fc834705f3) +--- + builtin/fetch.c | 35 ++++++++++++++++++----------------- + 1 file changed, 18 insertions(+), 17 deletions(-) + +diff --git a/builtin/fetch.c b/builtin/fetch.c +index f5a64c7351..4ae1afb918 100644 +--- a/builtin/fetch.c ++++ b/builtin/fetch.c +@@ -1539,7 +1539,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map, + static int do_fetch(struct transport *transport, + struct refspec *rs) + { +- struct ref *ref_map; ++ struct ref *ref_map = NULL; + int autotags = (transport->remote->fetch_tags == 1); + int retcode = 0; + const struct ref *remote_refs; +@@ -1623,11 +1623,24 @@ static int do_fetch(struct transport *transport, + retcode = 1; + } + if (fetch_and_consume_refs(transport, ref_map, worktrees)) { +- free_refs(ref_map); + retcode = 1; + goto cleanup; + } + ++ /* ++ * If neither --no-tags nor --tags was specified, do automated tag ++ * following. ++ */ ++ if (tags == TAGS_DEFAULT && autotags) { ++ struct ref *tags_ref_map = NULL, **tail = &tags_ref_map; ++ ++ find_non_local_tags(remote_refs, &tags_ref_map, &tail); ++ if (tags_ref_map) ++ backfill_tags(transport, tags_ref_map, worktrees); ++ ++ free_refs(tags_ref_map); ++ } ++ + if (set_upstream) { + struct branch *branch = branch_get("HEAD"); + struct ref *rm; +@@ -1647,7 +1660,7 @@ static int do_fetch(struct transport *transport, + if (!rm->peer_ref) { + if (source_ref) { + warning(_("multiple branches detected, incompatible with --set-upstream")); +- goto skip; ++ goto cleanup; + } else { + source_ref = rm; + } +@@ -1661,7 +1674,7 @@ static int do_fetch(struct transport *transport, + warning(_("could not set upstream of HEAD to '%s' from '%s' when " + "it does not point to any branch."), + shortname, transport->remote->name); +- goto skip; ++ goto cleanup; + } + + if (!strcmp(source_ref->name, "HEAD") || +@@ -1681,21 +1694,9 @@ static int do_fetch(struct transport *transport, + "you need to specify exactly one branch with the --set-upstream option")); + } + } +-skip: +- free_refs(ref_map); +- +- /* if neither --no-tags nor --tags was specified, do automated tag +- * following ... */ +- if (tags == TAGS_DEFAULT && autotags) { +- struct ref **tail = &ref_map; +- ref_map = NULL; +- find_non_local_tags(remote_refs, &ref_map, &tail); +- if (ref_map) +- backfill_tags(transport, ref_map, worktrees); +- free_refs(ref_map); +- } + + cleanup: ++ free_refs(ref_map); + free_worktrees(worktrees); + return retcode; + } +-- +2.35.1 + diff --git a/_support/git-patches/0030-fetch-control-lifecycle-of-FETCH_HEAD-in-a-single-pl.patch b/_support/git-patches/0030-fetch-control-lifecycle-of-FETCH_HEAD-in-a-single-pl.patch new file mode 100644 index 000000000..dc9d1364a --- /dev/null +++ b/_support/git-patches/0030-fetch-control-lifecycle-of-FETCH_HEAD-in-a-single-pl.patch @@ -0,0 +1,170 @@ +From ded9df2b091da730b28e0aa20b667f3ee2ccaffa Mon Sep 17 00:00:00 2001 +Message-Id: <ded9df2b091da730b28e0aa20b667f3ee2ccaffa.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Feb 2022 14:04:24 +0100 +Subject: [PATCH 30/34] fetch: control lifecycle of FETCH_HEAD in a single + place + +There are two different locations where we're appending to FETCH_HEAD: +first when storing updated references, and second when backfilling tags. +Both times we open the file, append to it and then commit it into place, +which is essentially duplicate work. + +Improve the lifecycle of updating FETCH_HEAD by opening and committing +it once in `do_fetch()`, where we pass the structure down to the code +which wants to append to it. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 2983cec0f26b7409ccc2dd5710b40ff4809cd4b1) +--- + builtin/fetch.c | 35 +++++++++++++++++++---------------- + 1 file changed, 19 insertions(+), 16 deletions(-) + +diff --git a/builtin/fetch.c b/builtin/fetch.c +index 4ae1afb918..897fa247d9 100644 +--- a/builtin/fetch.c ++++ b/builtin/fetch.c +@@ -1083,9 +1083,8 @@ N_("it took %.2f seconds to check forced updates; you can use\n" + + static int store_updated_refs(const char *raw_url, const char *remote_name, + int connectivity_checked, struct ref *ref_map, +- struct worktree **worktrees) ++ struct fetch_head *fetch_head, struct worktree **worktrees) + { +- struct fetch_head fetch_head; + int url_len, i, rc = 0; + struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; + struct ref_transaction *transaction = NULL; +@@ -1095,10 +1094,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, + int want_status; + int summary_width = 0; + +- rc = open_fetch_head(&fetch_head); +- if (rc) +- return -1; +- + if (verbosity >= 0) + summary_width = transport_summary_width(ref_map); + +@@ -1208,7 +1203,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, + strbuf_addf(¬e, "'%s' of ", what); + } + +- append_fetch_head(&fetch_head, &rm->old_oid, ++ append_fetch_head(fetch_head, &rm->old_oid, + rm->fetch_head_status, + note.buf, url, url_len); + +@@ -1248,9 +1243,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, + } + } + +- if (!rc) +- commit_fetch_head(&fetch_head); +- + if (rc & STORE_REF_ERROR_DF_CONFLICT) + error(_("some local refs could not be updated; try running\n" + " 'git remote prune %s' to remove any old, conflicting " +@@ -1270,7 +1262,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, + strbuf_release(&err); + ref_transaction_free(transaction); + free(url); +- close_fetch_head(&fetch_head); + return rc; + } + +@@ -1311,6 +1302,7 @@ static int check_exist_and_connected(struct ref *ref_map) + + static int fetch_and_consume_refs(struct transport *transport, + struct ref *ref_map, ++ struct fetch_head *fetch_head, + struct worktree **worktrees) + { + int connectivity_checked = 1; +@@ -1333,7 +1325,7 @@ static int fetch_and_consume_refs(struct transport *transport, + + trace2_region_enter("fetch", "consume_refs", the_repository); + ret = store_updated_refs(transport->url, transport->remote->name, +- connectivity_checked, ref_map, worktrees); ++ connectivity_checked, ref_map, fetch_head, worktrees); + trace2_region_leave("fetch", "consume_refs", the_repository); + + out: +@@ -1506,7 +1498,9 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) + return transport; + } + +-static void backfill_tags(struct transport *transport, struct ref *ref_map, ++static void backfill_tags(struct transport *transport, ++ struct ref *ref_map, ++ struct fetch_head *fetch_head, + struct worktree **worktrees) + { + int cannot_reuse; +@@ -1528,7 +1522,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map, + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); + transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); +- fetch_and_consume_refs(transport, ref_map, worktrees); ++ fetch_and_consume_refs(transport, ref_map, fetch_head, worktrees); + + if (gsecondary) { + transport_disconnect(gsecondary); +@@ -1547,6 +1541,7 @@ static int do_fetch(struct transport *transport, + TRANSPORT_LS_REFS_OPTIONS_INIT; + int must_list_refs = 1; + struct worktree **worktrees = get_worktrees(); ++ struct fetch_head fetch_head = { 0 }; + + if (tags == TAGS_DEFAULT) { + if (transport->remote->fetch_tags == 2) +@@ -1604,6 +1599,10 @@ static int do_fetch(struct transport *transport, + if (!update_head_ok) + check_not_current_branch(ref_map, worktrees); + ++ retcode = open_fetch_head(&fetch_head); ++ if (retcode) ++ goto cleanup; ++ + if (tags == TAGS_DEFAULT && autotags) + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); + if (prune) { +@@ -1622,7 +1621,8 @@ static int do_fetch(struct transport *transport, + if (retcode != 0) + retcode = 1; + } +- if (fetch_and_consume_refs(transport, ref_map, worktrees)) { ++ ++ if (fetch_and_consume_refs(transport, ref_map, &fetch_head, worktrees)) { + retcode = 1; + goto cleanup; + } +@@ -1636,11 +1636,13 @@ static int do_fetch(struct transport *transport, + + find_non_local_tags(remote_refs, &tags_ref_map, &tail); + if (tags_ref_map) +- backfill_tags(transport, tags_ref_map, worktrees); ++ backfill_tags(transport, tags_ref_map, &fetch_head, worktrees); + + free_refs(tags_ref_map); + } + ++ commit_fetch_head(&fetch_head); ++ + if (set_upstream) { + struct branch *branch = branch_get("HEAD"); + struct ref *rm; +@@ -1696,6 +1698,7 @@ static int do_fetch(struct transport *transport, + } + + cleanup: ++ close_fetch_head(&fetch_head); + free_refs(ref_map); + free_worktrees(worktrees); + return retcode; +-- +2.35.1 + diff --git a/_support/git-patches/0031-fetch-report-errors-when-backfilling-tags-fails.patch b/_support/git-patches/0031-fetch-report-errors-when-backfilling-tags-fails.patch new file mode 100644 index 000000000..a2cfd7b3e --- /dev/null +++ b/_support/git-patches/0031-fetch-report-errors-when-backfilling-tags-fails.patch @@ -0,0 +1,100 @@ +From ab0fa78af6741c409979889d7596042eb03192b5 Mon Sep 17 00:00:00 2001 +Message-Id: <ab0fa78af6741c409979889d7596042eb03192b5.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Feb 2022 14:04:28 +0100 +Subject: [PATCH 31/34] fetch: report errors when backfilling tags fails + +When the backfilling of tags fails we do not report this error to the +caller, but only report it implicitly at a later point when reporting +updated references. This leaves callers unable to act upon the +information of whether the backfilling succeeded or not. + +Refactor the function to return an error code and pass it up the +callstack. This causes us to correctly propagate the error back to the +user of git-fetch(1). + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 62091b4c87a199c172556f15c5662c6c3679e9cd) +--- + builtin/fetch.c | 26 ++++++++++++++++++-------- + t/t5503-tagfollow.sh | 4 +--- + 2 files changed, 19 insertions(+), 11 deletions(-) + +diff --git a/builtin/fetch.c b/builtin/fetch.c +index 897fa247d9..2d0ffb9859 100644 +--- a/builtin/fetch.c ++++ b/builtin/fetch.c +@@ -1498,12 +1498,12 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) + return transport; + } + +-static void backfill_tags(struct transport *transport, +- struct ref *ref_map, +- struct fetch_head *fetch_head, +- struct worktree **worktrees) ++static int backfill_tags(struct transport *transport, ++ struct ref *ref_map, ++ struct fetch_head *fetch_head, ++ struct worktree **worktrees) + { +- int cannot_reuse; ++ int retcode, cannot_reuse; + + /* + * Once we have set TRANS_OPT_DEEPEN_SINCE, we can't unset it +@@ -1522,12 +1522,14 @@ static void backfill_tags(struct transport *transport, + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); + transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); +- fetch_and_consume_refs(transport, ref_map, fetch_head, worktrees); ++ retcode = fetch_and_consume_refs(transport, ref_map, fetch_head, worktrees); + + if (gsecondary) { + transport_disconnect(gsecondary); + gsecondary = NULL; + } ++ ++ return retcode; + } + + static int do_fetch(struct transport *transport, +@@ -1635,8 +1637,16 @@ static int do_fetch(struct transport *transport, + struct ref *tags_ref_map = NULL, **tail = &tags_ref_map; + + find_non_local_tags(remote_refs, &tags_ref_map, &tail); +- if (tags_ref_map) +- backfill_tags(transport, tags_ref_map, &fetch_head, worktrees); ++ if (tags_ref_map) { ++ /* ++ * If backfilling of tags fails then we want to tell ++ * the user so, but we have to continue regardless to ++ * populate upstream information of the references we ++ * have already fetched above. ++ */ ++ if (backfill_tags(transport, tags_ref_map, &fetch_head, worktrees)) ++ retcode = 1; ++ } + + free_refs(tags_ref_map); + } +diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh +index 6ffe2a5719..c057c49e80 100755 +--- a/t/t5503-tagfollow.sh ++++ b/t/t5503-tagfollow.sh +@@ -233,9 +233,7 @@ test_expect_success 'backfill failure causes command to fail' ' + done + EOF + +- # Even though we fail to create refs/tags/tag1 the below command +- # unexpectedly succeeds. +- git -C clone5 fetch .. $B:refs/heads/something && ++ test_must_fail git -C clone5 fetch .. $B:refs/heads/something && + test $B = $(git -C clone5 rev-parse --verify refs/heads/something) && + test $S = $(git -C clone5 rev-parse --verify tag2) && + test_must_fail git -C clone5 rev-parse --verify tag1 +-- +2.35.1 + diff --git a/_support/git-patches/0032-refs-add-interface-to-iterate-over-queued-transactio.patch b/_support/git-patches/0032-refs-add-interface-to-iterate-over-queued-transactio.patch new file mode 100644 index 000000000..cc6a3e320 --- /dev/null +++ b/_support/git-patches/0032-refs-add-interface-to-iterate-over-queued-transactio.patch @@ -0,0 +1,98 @@ +From 7c90ce62cd5ab5f769ec789eff038b43e8f5d910 Mon Sep 17 00:00:00 2001 +Message-Id: <7c90ce62cd5ab5f769ec789eff038b43e8f5d910.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Feb 2022 14:04:32 +0100 +Subject: [PATCH 32/34] refs: add interface to iterate over queued + transactional updates + +There is no way for a caller to see whether a reference update has +already been queued up for a given reference transaction. There are +multiple alternatives to provide this functionality: + + - We may add a function that simply tells us whether a specific + reference has already been queued. If implemented naively then + this would potentially be quadratic in runtime behaviour if this + question is asked repeatedly because we have to iterate over all + references every time. The alternative would be to add a hashmap + of all queued reference updates to speed up the lookup, but this + adds overhead to all callers. + + - We may add a flag to `ref_transaction_add_update()` that causes it + to skip duplicates, but this has the same runtime concerns as the + first alternative. + + - We may add an interface which lets callers collect all updates + which have already been queued such that he can avoid re-adding + them. This is the most flexible approach and puts the burden on + the caller, but also allows us to not impact any of the existing + callsites which don't need this information. + +This commit implements the last approach: it allows us to compute the +map of already-queued updates once up front such that we can then skip +all subsequent references which are already part of this map. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 4f2ba2d06a7dd7e84e105a2779a7f07549d04231) +--- + refs.c | 16 ++++++++++++++++ + refs.h | 14 ++++++++++++++ + 2 files changed, 30 insertions(+) + +diff --git a/refs.c b/refs.c +index 435b90b1ec..5d9cb306ad 100644 +--- a/refs.c ++++ b/refs.c +@@ -2434,6 +2434,22 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction, + return refs->be->initial_transaction_commit(refs, transaction, err); + } + ++void ref_transaction_for_each_queued_update(struct ref_transaction *transaction, ++ ref_transaction_for_each_queued_update_fn cb, ++ void *cb_data) ++{ ++ int i; ++ ++ for (i = 0; i < transaction->nr; i++) { ++ struct ref_update *update = transaction->updates[i]; ++ ++ cb(update->refname, ++ (update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL, ++ (update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL, ++ cb_data); ++ } ++} ++ + int refs_delete_refs(struct ref_store *refs, const char *logmsg, + struct string_list *refnames, unsigned int flags) + { +diff --git a/refs.h b/refs.h +index a015354fd6..be5947f111 100644 +--- a/refs.h ++++ b/refs.h +@@ -781,6 +781,20 @@ int ref_transaction_abort(struct ref_transaction *transaction, + int initial_ref_transaction_commit(struct ref_transaction *transaction, + struct strbuf *err); + ++/* ++ * Execute the given callback function for each of the reference updates which ++ * have been queued in the given transaction. `old_oid` and `new_oid` may be ++ * `NULL` pointers depending on whether the update has these object IDs set or ++ * not. ++ */ ++typedef void ref_transaction_for_each_queued_update_fn(const char *refname, ++ const struct object_id *old_oid, ++ const struct object_id *new_oid, ++ void *cb_data); ++void ref_transaction_for_each_queued_update(struct ref_transaction *transaction, ++ ref_transaction_for_each_queued_update_fn cb, ++ void *cb_data); ++ + /* + * Free `*transaction` and all associated data. + */ +-- +2.35.1 + diff --git a/_support/git-patches/0033-fetch-make-atomic-flag-cover-backfilling-of-tags.patch b/_support/git-patches/0033-fetch-make-atomic-flag-cover-backfilling-of-tags.patch new file mode 100644 index 000000000..9e136f838 --- /dev/null +++ b/_support/git-patches/0033-fetch-make-atomic-flag-cover-backfilling-of-tags.patch @@ -0,0 +1,310 @@ +From 3a62f32960d2df2451003f2561a4f14d459991ec Mon Sep 17 00:00:00 2001 +Message-Id: <3a62f32960d2df2451003f2561a4f14d459991ec.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Feb 2022 14:04:36 +0100 +Subject: [PATCH 33/34] fetch: make `--atomic` flag cover backfilling of tags + +When fetching references from a remote we by default also fetch all tags +which point into the history we have fetched. This is a separate step +performed after updating local references because it requires us to walk +over the history on the client-side to determine whether the remote has +announced any tags which point to one of the fetched commits. + +This backfilling of tags isn't covered by the `--atomic` flag: right +now, it only applies to the step where we update our local references. +This is an oversight at the time the flag was introduced: its purpose is +to either update all references or none, but right now we happily update +local references even in the case where backfilling failed. + +Fix this by pulling up creation of the reference transaction such that +we can pass the same transaction to both the code which updates local +references and to the code which backfills tags. This allows us to only +commit the transaction in case both actions succeed. + +Note that we also have to start passing the transaction into +`find_non_local_tags()`: this function is responsible for finding all +tags which we need to backfill. Right now, it will happily return tags +which have already been updated with our local references. But when we +use a single transaction for both local references and backfilling then +it may happen that we try to queue the same reference update twice to +the transaction, which consequently triggers a bug. We thus have to skip +over any tags which have already been queued. + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit b3a804663c4682f6df55dd6703f8f8af9a7c6ab5) +--- + builtin/fetch.c | 92 +++++++++++++++++++++++++++++++------------- + t/t5503-tagfollow.sh | 11 ++---- + 2 files changed, 69 insertions(+), 34 deletions(-) + +diff --git a/builtin/fetch.c b/builtin/fetch.c +index 2d0ffb9859..e996a74862 100644 +--- a/builtin/fetch.c ++++ b/builtin/fetch.c +@@ -348,7 +348,19 @@ static void clear_item(struct refname_hash_entry *item) + item->ignore = 1; + } + ++ ++static void add_already_queued_tags(const char *refname, ++ const struct object_id *old_oid, ++ const struct object_id *new_oid, ++ void *cb_data) ++{ ++ struct hashmap *queued_tags = cb_data; ++ if (starts_with(refname, "refs/tags/") && new_oid) ++ (void) refname_hash_add(queued_tags, refname, new_oid); ++} ++ + static void find_non_local_tags(const struct ref *refs, ++ struct ref_transaction *transaction, + struct ref **head, + struct ref ***tail) + { +@@ -366,6 +378,16 @@ static void find_non_local_tags(const struct ref *refs, + create_fetch_oidset(head, &fetch_oids); + + for_each_ref(add_one_refname, &existing_refs); ++ ++ /* ++ * If we already have a transaction, then we need to filter out all ++ * tags which have already been queued up. ++ */ ++ if (transaction) ++ ref_transaction_for_each_queued_update(transaction, ++ add_already_queued_tags, ++ &existing_refs); ++ + for (ref = refs; ref; ref = ref->next) { + if (!starts_with(ref->name, "refs/tags/")) + continue; +@@ -599,7 +621,7 @@ static struct ref *get_ref_map(struct remote *remote, + /* also fetch all tags */ + get_fetch_map(remote_refs, tag_refspec, &tail, 0); + else if (tags == TAGS_DEFAULT && *autotags) +- find_non_local_tags(remote_refs, &ref_map, &tail); ++ find_non_local_tags(remote_refs, NULL, &ref_map, &tail); + + /* Now append any refs to be updated opportunistically: */ + *tail = orefs; +@@ -1082,12 +1104,12 @@ N_("it took %.2f seconds to check forced updates; you can use\n" + "to avoid this check\n"); + + static int store_updated_refs(const char *raw_url, const char *remote_name, +- int connectivity_checked, struct ref *ref_map, ++ int connectivity_checked, ++ struct ref_transaction *transaction, struct ref *ref_map, + struct fetch_head *fetch_head, struct worktree **worktrees) + { + int url_len, i, rc = 0; + struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; +- struct ref_transaction *transaction = NULL; + const char *what, *kind; + struct ref *rm; + char *url; +@@ -1112,14 +1134,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, + } + } + +- if (atomic_fetch) { +- transaction = ref_transaction_begin(&err); +- if (!transaction) { +- error("%s", err.buf); +- goto abort; +- } +- } +- + prepare_format_display(ref_map); + + /* +@@ -1235,14 +1249,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, + } + } + +- if (!rc && transaction) { +- rc = ref_transaction_commit(transaction, &err); +- if (rc) { +- error("%s", err.buf); +- goto abort; +- } +- } +- + if (rc & STORE_REF_ERROR_DF_CONFLICT) + error(_("some local refs could not be updated; try running\n" + " 'git remote prune %s' to remove any old, conflicting " +@@ -1260,7 +1266,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, + abort: + strbuf_release(¬e); + strbuf_release(&err); +- ref_transaction_free(transaction); + free(url); + return rc; + } +@@ -1301,6 +1306,7 @@ static int check_exist_and_connected(struct ref *ref_map) + } + + static int fetch_and_consume_refs(struct transport *transport, ++ struct ref_transaction *transaction, + struct ref *ref_map, + struct fetch_head *fetch_head, + struct worktree **worktrees) +@@ -1325,7 +1331,8 @@ static int fetch_and_consume_refs(struct transport *transport, + + trace2_region_enter("fetch", "consume_refs", the_repository); + ret = store_updated_refs(transport->url, transport->remote->name, +- connectivity_checked, ref_map, fetch_head, worktrees); ++ connectivity_checked, transaction, ref_map, ++ fetch_head, worktrees); + trace2_region_leave("fetch", "consume_refs", the_repository); + + out: +@@ -1499,6 +1506,7 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) + } + + static int backfill_tags(struct transport *transport, ++ struct ref_transaction *transaction, + struct ref *ref_map, + struct fetch_head *fetch_head, + struct worktree **worktrees) +@@ -1522,7 +1530,7 @@ static int backfill_tags(struct transport *transport, + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); + transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); +- retcode = fetch_and_consume_refs(transport, ref_map, fetch_head, worktrees); ++ retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head, worktrees); + + if (gsecondary) { + transport_disconnect(gsecondary); +@@ -1535,6 +1543,7 @@ static int backfill_tags(struct transport *transport, + static int do_fetch(struct transport *transport, + struct refspec *rs) + { ++ struct ref_transaction *transaction = NULL; + struct ref *ref_map = NULL; + int autotags = (transport->remote->fetch_tags == 1); + int retcode = 0; +@@ -1544,6 +1553,7 @@ static int do_fetch(struct transport *transport, + int must_list_refs = 1; + struct worktree **worktrees = get_worktrees(); + struct fetch_head fetch_head = { 0 }; ++ struct strbuf err = STRBUF_INIT; + + if (tags == TAGS_DEFAULT) { + if (transport->remote->fetch_tags == 2) +@@ -1605,6 +1615,14 @@ static int do_fetch(struct transport *transport, + if (retcode) + goto cleanup; + ++ if (atomic_fetch) { ++ transaction = ref_transaction_begin(&err); ++ if (!transaction) { ++ retcode = error("%s", err.buf); ++ goto cleanup; ++ } ++ } ++ + if (tags == TAGS_DEFAULT && autotags) + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); + if (prune) { +@@ -1624,7 +1642,7 @@ static int do_fetch(struct transport *transport, + retcode = 1; + } + +- if (fetch_and_consume_refs(transport, ref_map, &fetch_head, worktrees)) { ++ if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head, worktrees)) { + retcode = 1; + goto cleanup; + } +@@ -1636,21 +1654,37 @@ static int do_fetch(struct transport *transport, + if (tags == TAGS_DEFAULT && autotags) { + struct ref *tags_ref_map = NULL, **tail = &tags_ref_map; + +- find_non_local_tags(remote_refs, &tags_ref_map, &tail); ++ find_non_local_tags(remote_refs, transaction, &tags_ref_map, &tail); + if (tags_ref_map) { + /* + * If backfilling of tags fails then we want to tell + * the user so, but we have to continue regardless to + * populate upstream information of the references we +- * have already fetched above. ++ * have already fetched above. The exception though is ++ * when `--atomic` is passed: in that case we'll abort ++ * the transaction and don't commit anything. + */ +- if (backfill_tags(transport, tags_ref_map, &fetch_head, worktrees)) ++ if (backfill_tags(transport, transaction, tags_ref_map, ++ &fetch_head, worktrees)) + retcode = 1; + } + + free_refs(tags_ref_map); + } + ++ if (transaction) { ++ if (retcode) ++ goto cleanup; ++ ++ retcode = ref_transaction_commit(transaction, &err); ++ if (retcode) { ++ error("%s", err.buf); ++ ref_transaction_free(transaction); ++ transaction = NULL; ++ goto cleanup; ++ } ++ } ++ + commit_fetch_head(&fetch_head); + + if (set_upstream) { +@@ -1708,7 +1742,13 @@ static int do_fetch(struct transport *transport, + } + + cleanup: ++ if (retcode && transaction) { ++ ref_transaction_abort(transaction, &err); ++ error("%s", err.buf); ++ } ++ + close_fetch_head(&fetch_head); ++ strbuf_release(&err); + free_refs(ref_map); + free_worktrees(worktrees); + return retcode; +diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh +index c057c49e80..e72fdc2534 100755 +--- a/t/t5503-tagfollow.sh ++++ b/t/t5503-tagfollow.sh +@@ -180,11 +180,8 @@ test_expect_success 'atomic fetch with failing backfill' ' + EOF + + test_must_fail git -C clone3 fetch --atomic .. $B:refs/heads/something && +- +- # Creation of the tag has failed, so ideally refs/heads/something +- # should not exist. The fact that it does demonstrates that there is +- # a bug in the `--atomic` flag. +- test $B = "$(git -C clone3 rev-parse --verify refs/heads/something)" ++ test_must_fail git -C clone3 rev-parse --verify refs/heads/something && ++ test_must_fail git -C clone3 rev-parse --verify refs/tags/tag2 + ' + + test_expect_success 'atomic fetch with backfill should use single transaction' ' +@@ -197,12 +194,10 @@ test_expect_success 'atomic fetch with backfill should use single transaction' ' + prepared + $ZERO_OID $B refs/heads/something + $ZERO_OID $S refs/tags/tag2 ++ $ZERO_OID $T refs/tags/tag1 + committed + $ZERO_OID $B refs/heads/something + $ZERO_OID $S refs/tags/tag2 +- prepared +- $ZERO_OID $T refs/tags/tag1 +- committed + $ZERO_OID $T refs/tags/tag1 + EOF + +-- +2.35.1 + diff --git a/_support/git-patches/0034-fetch-make-atomic-flag-cover-pruning-of-refs.patch b/_support/git-patches/0034-fetch-make-atomic-flag-cover-pruning-of-refs.patch new file mode 100644 index 000000000..e0f50a7e1 --- /dev/null +++ b/_support/git-patches/0034-fetch-make-atomic-flag-cover-pruning-of-refs.patch @@ -0,0 +1,149 @@ +From c1334339eccde428151dc9e544ab2ec4d0178cad Mon Sep 17 00:00:00 2001 +Message-Id: <c1334339eccde428151dc9e544ab2ec4d0178cad.1646206542.git.ps@pks.im> +In-Reply-To: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +References: <c74f385fb46855ac0db222b6845ddb95e6a36264.1646206541.git.ps@pks.im> +From: Patrick Steinhardt <ps@pks.im> +Date: Thu, 17 Feb 2022 14:04:41 +0100 +Subject: [PATCH 34/34] fetch: make `--atomic` flag cover pruning of refs +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +When fetching with the `--prune` flag we will delete any local +references matching the fetch refspec which have disappeared on the +remote. This step is not currently covered by the `--atomic` flag: we +delete branches even though updating of local references has failed, +which means that the fetch is not an all-or-nothing operation. + +Fix this bug by passing in the global transaction into `prune_refs()`: +if one is given, then we'll only queue up deletions and not commit them +right away. + +This change also improves performance when pruning many branches in a +repository with a big packed-refs file: every references is pruned in +its own transaction, which means that we potentially have to rewrite +the packed-refs files for every single reference we're about to prune. + +The following benchmark demonstrates this: it performs a pruning fetch +from a repository with a single reference into a repository with 100k +references, which causes us to prune all but one reference. This is of +course a very artificial setup, but serves to demonstrate the impact of +only having to write the packed-refs file once: + + Benchmark 1: git fetch --prune --atomic +refs/*:refs/* (HEAD~) + Time (mean ± σ): 2.366 s ± 0.021 s [User: 0.858 s, System: 1.508 s] + Range (min … max): 2.328 s … 2.407 s 10 runs + + Benchmark 2: git fetch --prune --atomic +refs/*:refs/* (HEAD) + Time (mean ± σ): 1.369 s ± 0.017 s [User: 0.715 s, System: 0.641 s] + Range (min … max): 1.346 s … 1.400 s 10 runs + + Summary + 'git fetch --prune --atomic +refs/*:refs/* (HEAD)' ran + 1.73 ± 0.03 times faster than 'git fetch --prune --atomic +refs/*:refs/* (HEAD~)' + +Signed-off-by: Patrick Steinhardt <ps@pks.im> +Signed-off-by: Junio C Hamano <gitster@pobox.com> +(cherry picked from commit 583bc419235cedc6a2ba12593f058a9f812b9594) +--- + builtin/fetch.c | 30 ++++++++++++++++++++++-------- + t/t5510-fetch.sh | 8 ++------ + 2 files changed, 24 insertions(+), 14 deletions(-) + +diff --git a/builtin/fetch.c b/builtin/fetch.c +index e996a74862..25cc0b906e 100644 +--- a/builtin/fetch.c ++++ b/builtin/fetch.c +@@ -1340,11 +1340,14 @@ static int fetch_and_consume_refs(struct transport *transport, + return ret; + } + +-static int prune_refs(struct refspec *rs, struct ref *ref_map, ++static int prune_refs(struct refspec *rs, ++ struct ref_transaction *transaction, ++ struct ref *ref_map, + const char *raw_url) + { + int url_len, i, result = 0; + struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map); ++ struct strbuf err = STRBUF_INIT; + char *url; + const char *dangling_msg = dry_run + ? _(" (%s will become dangling)") +@@ -1364,13 +1367,22 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map, + url_len = i - 3; + + if (!dry_run) { +- struct string_list refnames = STRING_LIST_INIT_NODUP; ++ if (transaction) { ++ for (ref = stale_refs; ref; ref = ref->next) { ++ result = ref_transaction_delete(transaction, ref->name, NULL, 0, ++ "fetch: prune", &err); ++ if (result) ++ goto cleanup; ++ } ++ } else { ++ struct string_list refnames = STRING_LIST_INIT_NODUP; + +- for (ref = stale_refs; ref; ref = ref->next) +- string_list_append(&refnames, ref->name); ++ for (ref = stale_refs; ref; ref = ref->next) ++ string_list_append(&refnames, ref->name); + +- result = delete_refs("fetch: prune", &refnames, 0); +- string_list_clear(&refnames, 0); ++ result = delete_refs("fetch: prune", &refnames, 0); ++ string_list_clear(&refnames, 0); ++ } + } + + if (verbosity >= 0) { +@@ -1391,6 +1403,8 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map, + } + } + ++cleanup: ++ strbuf_release(&err); + free(url); + free_refs(stale_refs); + return result; +@@ -1632,10 +1646,10 @@ static int do_fetch(struct transport *transport, + * don't care whether --tags was specified. + */ + if (rs->nr) { +- retcode = prune_refs(rs, ref_map, transport->url); ++ retcode = prune_refs(rs, transaction, ref_map, transport->url); + } else { + retcode = prune_refs(&transport->remote->fetch, +- ref_map, ++ transaction, ref_map, + transport->url); + } + if (retcode != 0) +diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh +index 70d51f343b..48e14e2dab 100755 +--- a/t/t5510-fetch.sh ++++ b/t/t5510-fetch.sh +@@ -354,17 +354,13 @@ test_expect_success 'fetch --atomic --prune executes a single reference transact + head_oid=$(git rev-parse HEAD) && + + # Fetching with the `--atomic` flag should update all references in a +- # single transaction. It is currently missing coverage of pruned +- # references though, and as a result those may be committed to disk +- # even if updating references fails later. ++ # single transaction. + cat >expected <<-EOF && + prepared + $ZERO_OID $ZERO_OID refs/remotes/origin/scheduled-for-deletion +- committed +- $ZERO_OID $ZERO_OID refs/remotes/origin/scheduled-for-deletion +- prepared + $ZERO_OID $head_oid refs/remotes/origin/new-branch + committed ++ $ZERO_OID $ZERO_OID refs/remotes/origin/scheduled-for-deletion + $ZERO_OID $head_oid refs/remotes/origin/new-branch + EOF + +-- +2.35.1 + diff --git a/internal/git/execution_environment.go b/internal/git/execution_environment.go index d140c6f44..a13f1dfad 100644 --- a/internal/git/execution_environment.go +++ b/internal/git/execution_environment.go @@ -26,6 +26,13 @@ var ( // case `IsEnabled()` returns `false` though. ExecutionEnvironmentConstructors = []ExecutionEnvironmentConstructor{ BundledGitEnvironmentConstructor{ + Suffix: "-v2.35.1.gl1", + FeatureFlags: []featureflag.FeatureFlag{ + featureflag.UseBundledGit, + featureflag.GitV2351WithFetchSpeedups, + }, + }, + BundledGitEnvironmentConstructor{ // This is the current default bundled Git environment, which does not yet // have a version suffix. Suffix: "", diff --git a/internal/metadata/featureflag/ff_git_v2351_with_fetch_speedups.go b/internal/metadata/featureflag/ff_git_v2351_with_fetch_speedups.go new file mode 100644 index 000000000..3c3610b9e --- /dev/null +++ b/internal/metadata/featureflag/ff_git_v2351_with_fetch_speedups.go @@ -0,0 +1,5 @@ +package featureflag + +// GitV2351WithFetchSpeedups will enable the use of Git v2.35.1 with patches speeding up mirror +// fetches in repositories with many references. +var GitV2351WithFetchSpeedups = NewFeatureFlag("git_v2351_with_fetch_speedups", false) diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go index a9d51ef98..496404cba 100644 --- a/internal/testhelper/testhelper.go +++ b/internal/testhelper/testhelper.go @@ -180,6 +180,9 @@ func ContextWithoutCancel(opts ...ContextOpt) context.Context { // and thus it's not feasible to inject the feature flag everywhere. Instead, we just use // one of both randomly. ctx = featureflag.ContextWithFeatureFlag(ctx, featureflag.UseBundledGit, mrand.Int()%2 == 0) + // Same as with the preceding feature flag, this flag is checked whenever we execute a Git + // command. + ctx = featureflag.ContextWithFeatureFlag(ctx, featureflag.GitV2351WithFetchSpeedups, mrand.Int()%2 == 0) for _, opt := range opts { ctx = opt(ctx) |