# Makefile for Gitaly

# You can override options by creating a "config.mak" file in Gitaly's root
# directory.
-include config.mak

# Unexport environment variables which have an effect on Git itself.
# We need to keep GIT_PREFIX because it's used to determine where our
# self-built Git should be installed into. It's probably not going to
# matter much though.
unexport $(filter-out GIT_PREFIX,$(shell git rev-parse --local-env-vars))

# Call `make V=1` in order to print commands verbosely.
ifeq ($(V),1)
    Q =
else
    Q = @
endif

SHELL = /usr/bin/env bash -eo pipefail

# Host information
OS   := $(shell uname)
ARCH := $(shell uname -m)

# Directories
SOURCE_DIR := $(shell realpath $(abspath $(dir $(lastword ${MAKEFILE_LIST}))))
BUILD_DIR        := ${SOURCE_DIR}/_build
PROTO_DEST_DIR   := ${SOURCE_DIR}/proto/go
DEPENDENCY_DIR   := ${BUILD_DIR}/deps
TOOLS_DIR        := ${BUILD_DIR}/tools

# These variables may be overridden at runtime by top-level make
## The prefix where Gitaly binaries will be installed to. Binaries will end up
## in ${PREFIX}/bin by default.
PREFIX           ?= /usr/local
prefix           ?= ${PREFIX}
exec_prefix      ?= ${prefix}
bindir           ?= ${exec_prefix}/bin
INSTALL_DEST_DIR := ${DESTDIR}${bindir}
## The prefix where Git will be installed to.
GIT_PREFIX       ?= ${PREFIX}

# Tools
GIT                         := $(shell command -v git)
GOIMPORTS                   := ${TOOLS_DIR}/goimports
GOFUMPT                     := ${TOOLS_DIR}/gofumpt
GOLANGCI_LINT               := ${TOOLS_DIR}/golangci-lint
PROTOLINT                   := ${TOOLS_DIR}/protolint
GO_LICENSES                 := ${TOOLS_DIR}/go-licenses
PROTOC                      := ${TOOLS_DIR}/protoc
PROTOC_GEN_GO               := ${TOOLS_DIR}/protoc-gen-go
PROTOC_GEN_GO_GRPC          := ${TOOLS_DIR}/protoc-gen-go-grpc
PROTOC_GEN_GITALY_LINT      := ${TOOLS_DIR}/protoc-gen-gitaly-lint
PROTOC_GEN_GITALY_PROTOLIST := ${TOOLS_DIR}/protoc-gen-gitaly-protolist
PROTOC_GEN_DOC              := ${TOOLS_DIR}/protoc-gen-doc
GOTESTSUM                   := ${TOOLS_DIR}/gotestsum
GOCOVER_COBERTURA           := ${TOOLS_DIR}/gocover-cobertura
DELVE                       := ${TOOLS_DIR}/dlv
GOVULNCHECK                 := ${TOOLS_DIR}/govulncheck

# Go toolchain options
## Ensure that newer toolchains aren't downloaded and used automatically. See the discussion
## at https://gitlab.com/gitlab-org/gitaly/-/merge_requests/7913#note_2524556226
export GOTOOLCHAIN = local

# Tool options
GOLANGCI_LINT_OPTIONS ?=
GOLANGCI_LINT_CONFIG  ?= ${SOURCE_DIR}/.golangci.yml

# Build information
GITALY_PACKAGE    := $(shell go list -m 2>/dev/null || echo unknown)
GITALY_VERSION    := $(shell ${GIT} describe --match v* 2>/dev/null | sed 's/^v//' || cat ${SOURCE_DIR}/VERSION 2>/dev/null || echo unknown)
GO_LDFLAGS        := -X ${GITALY_PACKAGE}/internal/version.version=${GITALY_VERSION}
SERVER_BUILD_TAGS := continuous_profiler_stackdriver

## FIPS_MODE controls whether to build Gitaly and dependencies in FIPS mode.
## Set this to a non-empty value to enable it.
FIPS_MODE ?=

ifdef FIPS_MODE
    SERVER_BUILD_TAGS := ${SERVER_BUILD_TAGS},fips

    # Build Git with the OpenSSL backend for SHA256 in case FIPS-mode is
    # requested. Note that we explicitly don't do the same for SHA1: we
    # instead use SHA1DC to protect users against the SHAttered attack.
    GIT_FIPS_BUILD_OPTIONS := OPENSSL_SHA256=YesPlease
    GIT_FIPS_MESON_BUILD_OPTIONS := -Dsha256_backend=openssl

    # Go 1.19+ now requires GOEXPERIMENT=boringcrypto for FIPS compilation.
    # See https://github.com/golang/go/issues/51940 for more details.
    BORINGCRYPTO_SUPPORT := $(shell GOEXPERIMENT=boringcrypto go version > /dev/null 2>&1; echo $$?)
    ifeq ($(BORINGCRYPTO_SUPPORT), 0)
        export GOEXPERIMENT=boringcrypto
    endif

    export GITALY_TESTING_ENABLE_FIPS := YesPlease
endif

# protoc target
PROTOC_VERSION      ?= v30.2
PROTOC_REPO_URL     ?= https://github.com/protocolbuffers/protobuf
PROTOC_SOURCE_DIR   ?= ${DEPENDENCY_DIR}/protobuf/source
PROTOC_BUILD_DIR    ?= ${DEPENDENCY_DIR}/protobuf/build
PROTOC_INSTALL_DIR  ?= ${DEPENDENCY_DIR}/protobuf/install

ifeq ($(origin PROTOC_BUILD_OPTIONS),undefined)
    ## Build options for protoc.
    PROTOC_BUILD_OPTIONS ?=
    PROTOC_BUILD_OPTIONS += -DBUILD_SHARED_LIBS=NO
    PROTOC_BUILD_OPTIONS += -DCMAKE_INSTALL_PREFIX=${PROTOC_INSTALL_DIR}
    PROTOC_BUILD_OPTIONS += -Dprotobuf_BUILD_TESTS=OFF
    PROTOC_BUILD_OPTIONS += -DCMAKE_CXX_STANDARD=17
endif

# This target is a part of the pipeline because some of Gitaly's protobufs consist of etcd's raftpb.
RAFTPB_REPO_URL     ?= https://github.com/etcd-io/raft
RAFTPB_SOURCE_DIR   ?= ${DEPENDENCY_DIR}/raft
# gogoproto is a dependency of raftpb.
GOGOPROTO_REPO_URL     ?= https://github.com/gogo/protobuf
GOGOPROTO_SOURCE_DIR   ?= ${DEPENDENCY_DIR}/gogo-protobuf

# Factorize all protobuf includes in a single variable
PROTOC_INCLUDE ?=
PROTOC_INCLUDE += -I ${SOURCE_DIR}/proto
PROTOC_INCLUDE += -I ${PROTOC_INSTALL_DIR}/include
PROTOC_INCLUDE += -I ${RAFTPB_SOURCE_DIR}
PROTOC_INCLUDE += -I ${GOGOPROTO_SOURCE_DIR}

# Git target
GIT_REPO_URL       ?= https://gitlab.com/gitlab-org/git.git
GIT_QUIET          :=
ifeq (${Q},@)
    GIT_QUIET = --quiet
endif

GIT_EXECUTABLES += git
GIT_EXECUTABLES += git-remote-http
GIT_EXECUTABLES += git-http-backend

# == Git version configuration ==
#
# By default, Git binaries are compiled and then embedded inside of the Gitaly binary. They
# are then unpacked at runtime and executed as normal. This is known as "bundled Git", and
# is done so we maintain end-to-end control of the specific version of Git we execute at
# runtime. Multiple versions of Git can be bundled simultaneously and their usage can be
# feature-flagged.
#
# The variables below control how Git is compiled.
#
# GIT_VERSION allows a non-bundled version of Git to be used. This is defined by the nightly
# tests which exercise the `next` and `master` branches, but can also serve as an override to
# test Gitaly against any arbitrary revision in the Git source.
GIT_VERSION ?=
#
# GIT_VERSION_MASTER is a commit hash from Git’s master branch, typically between 7–14 days old.
# Do not modify the format, it's automatically updated by renovate-gitlab-bot. The timestamp
# is used for version comparison by renovate.
# renovate: 1767067102000
GIT_VERSION_MASTER ?= a37bb2ae6c6659cf7cefd0412759fca5202a823d
GIT_VERSION_PREV ?= aea8cc3a10c325a22a75e2d4f582db959d3854ae
#
#
# OVERRIDE_GIT_VERSION allows you to specify a custom semver value to be reported by the
# `git --version` command. This affects bundled and non-bundled Git, and can be used whenever
# whenever the GIT_VERSION* variables are set to a revision that is not a semver value. It
# can also be left blank, and Git will compute the version during its own build.
OVERRIDE_GIT_VERSION ?=

ifeq (${GIT_VERSION:default=},)
    override GIT_VERSION := ${GIT_VERSION_MASTER}
    # When GIT_VERSION is not explicitly set, we default to bundled Git.
	export WITH_BUNDLED_GIT = 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

ifeq ($(origin GIT_BUILD_OPTIONS),undefined)
    ## Build options for Git.
    GIT_BUILD_OPTIONS ?=
    # activate developer checks
    GIT_BUILD_OPTIONS += DEVELOPER=1
    # but don't cause warnings to fail the build
    GIT_BUILD_OPTIONS += DEVOPTS=no-error
    GIT_BUILD_OPTIONS += USE_LIBPCRE=YesPlease
    GIT_BUILD_OPTIONS += NO_PERL=YesPlease
    GIT_BUILD_OPTIONS += NO_EXPAT=YesPlease
    GIT_BUILD_OPTIONS += NO_TCLTK=YesPlease
    GIT_BUILD_OPTIONS += NO_GETTEXT=YesPlease
    GIT_BUILD_OPTIONS += NO_PYTHON=YesPlease
    PCRE_PC=libpcre2-8
    ifeq ($(shell pkg-config --exists ${PCRE_PC} && echo exists),exists)
	    GIT_BUILD_OPTIONS += LIBPCREDIR=$(shell pkg-config ${PCRE_PC} --variable prefix)
    endif
    ## Build options used for Git when building with Meson.
    GIT_MESON_BUILD_OPTIONS ?=
    GIT_MESON_BUILD_OPTIONS += -Dprefix="${GIT_PREFIX}"
    GIT_MESON_BUILD_OPTIONS += -Dbuildtype=debugoptimized
    GIT_MESON_BUILD_OPTIONS += -Dcurl=enabled
    GIT_MESON_BUILD_OPTIONS += -Dexpat=disabled
    GIT_MESON_BUILD_OPTIONS += -Dgettext=disabled
    GIT_MESON_BUILD_OPTIONS += -Dgitweb=disabled
    GIT_MESON_BUILD_OPTIONS += -Diconv=enabled
    GIT_MESON_BUILD_OPTIONS += -Dpcre2=enabled
    GIT_MESON_BUILD_OPTIONS += -Dperl=disabled
    GIT_MESON_BUILD_OPTIONS += -Dpython=disabled
    GIT_MESON_BUILD_OPTIONS += -Dtests=false
    GIT_MESON_BUILD_OPTIONS += -Dwrap_mode=nofallback

    # Use non-collision-detecting SHA1 implementation in non-cryptographic scenarios
    # to improve performance. This is only enabled for Linux platforms.
    ifeq ($(OS),Linux)
	    GIT_MESON_BUILD_OPTIONS += -Dsha1_unsafe_backend=openssl
	    GIT_BUILD_OPTIONS += OPENSSL_SHA1_UNSAFE=YesPlease
    endif
endif

ifdef GIT_APPEND_BUILD_OPTIONS
	GIT_BUILD_OPTIONS += ${GIT_APPEND_BUILD_OPTIONS}
endif

ifdef GIT_APPEND_MESON_BUILD_OPTIONS
	GIT_MESON_BUILD_OPTIONS += ${GIT_APPEND_MESON_BUILD_OPTIONS}
endif

ifdef GIT_FIPS_BUILD_OPTIONS
	GIT_BUILD_OPTIONS += ${GIT_FIPS_BUILD_OPTIONS}
	GIT_MESON_BUILD_OPTIONS += ${GIT_FIPS_MESON_BUILD_OPTIONS}
endif

# git-filter-repo target
GIT_FILTER_REPO                      ?= ${BUILD_DIR}/bin/git-filter-repo
GIT_FILTER_REPO_VERSION              ?= v2.47.0
GIT_FILTER_REPO_REPO_URL             ?= https://github.com/newren/git-filter-repo
GIT_FILTER_REPO_SOURCE_DIR           ?= ${DEPENDENCY_DIR}/git-filter-repo


# These variables control test options and artifacts
## List of Go packages which shall be tested.
## Go packages to test when using the test-go target.
TEST_PACKAGES     ?= ./...
## Test options passed to `go test`.
TEST_OPTIONS      ?= -count=1 -p=4
## Specify the output format used to print tests ["standard-verbose", "standard-quiet", "short"]
TEST_FORMAT       ?= short
## Specify the location where the JUnit-style format shall be written to.
TEST_JUNIT_REPORT ?= ${BUILD_DIR}/reports/tests-junit.xml
## Specify the location where the full JSON report shall be written to.
TEST_JSON_REPORT  ?=
## Specify the output directory for test coverage reports.
TEST_COVERAGE_DIR ?= ${BUILD_DIR}/cover
## Directory where all runtime test data is being created.
TEST_TMP_DIR      ?=
## Custom options for gosumtest. These options are different from TEST_OPTIONS
GOTESTSUM_OPTIONS ?=
## Directory where Gitaly should write logs to during test execution.
TEST_LOG_DIR	  ?=
TEST_REPO_DIR     := ${BUILD_DIR}/testrepos
BENCHMARK_REPO    := ${TEST_REPO_DIR}/benchmark.git
## Options to pass to the script which builds the Gitaly gem
BUILD_GEM_OPTIONS ?=
## Options to override the name of Gitaly gem
BUILD_GEM_NAME ?= gitaly

ifdef CI
	# We introduced this flag in fb75a2f11 (test: Enable the --rerun-fails flag, 2025-03-13) and specified
	# it unconditionally. However, it's better to limit the reruns to CI only.
	GOTESTSUM_OPTIONS += --rerun-fails --packages '$(TEST_PACKAGES)'
endif

# Git binaries that are eventually embedded into the Gitaly binary.
GIT_PACKED_EXECUTABLES       = $(addprefix ${BUILD_DIR}/bin/gitaly-, $(addsuffix -master, ${GIT_EXECUTABLES})) \
                                $(addprefix ${BUILD_DIR}/bin/gitaly-, $(addsuffix -prev, ${GIT_EXECUTABLES}))

# All executables provided by Gitaly.
GITALY_EXECUTABLES           = $(addprefix ${BUILD_DIR}/bin/,$(notdir $(shell find ${SOURCE_DIR}/cmd -mindepth 1 -maxdepth 1 -type d -print)))
# All executables packed inside the Gitaly binary.
GITALY_PACKED_EXECUTABLES    = $(filter %gitaly-hooks %gitaly-gpg %gitaly-ssh %gitaly-lfs-smudge, ${GITALY_EXECUTABLES})

# All executables that should be installed.
GITALY_INSTALLED_EXECUTABLES = $(filter-out ${GITALY_PACKED_EXECUTABLES}, ${GITALY_EXECUTABLES})
# Find all Go source files.
find_go_sources              = $(shell find ${SOURCE_DIR} -type d \( -path "${SOURCE_DIR}/_*" -o -path "${SOURCE_DIR}/proto" \) -prune -o -type f -name '*.go' -print | sort -u)

# run_go_tests will execute Go tests with all required parameters. Its
# behaviour can be modified via the following variables:
#
# TEST_OPTIONS: any additional options
# TEST_PACKAGES: packages which shall be tested
# TEST_LOG_DIR: specify the output log dir. By default, all logs will be discarded
run_go_tests = PATH='${SOURCE_DIR}/internal/testhelper/testdata/home/bin:${PATH}' \
    TEST_TMP_DIR='${TEST_TMP_DIR}' \
    TEST_LOG_DIR='${TEST_LOG_DIR}' \
    ${GOTESTSUM} --format ${TEST_FORMAT} --junitfile '${TEST_JUNIT_REPORT}' --jsonfile '${TEST_JSON_REPORT}' ${GOTESTSUM_OPTIONS} -- -ldflags '${GO_LDFLAGS}' -tags '${SERVER_BUILD_TAGS}' ${TEST_OPTIONS} ${TEST_PACKAGES}

## Test options passed to `dlv test`.
DEBUG_OPTIONS      ?= $(patsubst -%,-test.%,${TEST_OPTIONS})

# debug_go_tests will execute Go tests from a single package in the delve debugger.
# Its behaviour can be modified via the following variable:
#
# DEBUG_OPTIONS: any additional options, will default to TEST_OPTIONS if not set.
debug_go_tests = PATH='${SOURCE_DIR}/internal/testhelper/testdata/home/bin:${PATH}' \
    TEST_TMP_DIR='${TEST_TMP_DIR}' \
    ${DELVE} test --build-flags="-ldflags '${GO_LDFLAGS}' -tags '${SERVER_BUILD_TAGS}'" ${TEST_PACKAGES} -- ${DEBUG_OPTIONS}

unexport GOROOT
## GOCACHE_MAX_SIZE_KB is the maximum size of Go's build cache in kilobytes before it is cleaned up.
GOCACHE_MAX_SIZE_KB              ?= 5000000
export GOCACHE                   ?= ${BUILD_DIR}/cache
export GOPROXY                   ?= https://proxy.golang.org|https://proxy.golang.org
export PATH                      := ${BUILD_DIR}/bin:${PATH}

# By default, intermediate targets get deleted automatically after a successful
# build. We do not want that though: there's some precious intermediate targets
# like our `*.version` targets which are required in order to determine whether
# a dependency needs to be rebuilt. By specifying `.SECONDARY`, intermediate
# targets will never get deleted automatically.
.SECONDARY:

.PHONY: all
## Default target which builds Gitaly.
all: build

.PHONY: .FORCE
.FORCE:

## Print help about available targets and variables.
help:
	@echo "usage: make [<target>...] [<variable>=<value>...]"
	@echo ""
	@echo "These are the available targets:"
	@echo ""

	@ # Match all targets which have preceding `## ` comments.
	${Q}awk '/^## / { sub(/^##/, "", $$0) ; desc = desc $$0 ; next } \
		 /^[[:alpha:]][[:alnum:]_-]+:/ && desc { print "  " $$1 desc } \
		 { desc = "" }' $(MAKEFILE_LIST) | sort | column -s: -t

	${Q}echo ""
	${Q}echo "These are common variables which can be overridden in config.mak"
	${Q}echo "or by passing them to make directly as environment variables:"
	${Q}echo ""

	@ # Match all variables which have preceding `## ` comments and which are assigned via `?=`.
	${Q}awk '/^[[:space:]]*## / { sub(/^[[:space:]]*##/,"",$$0) ; desc = desc $$0 ; next } \
		 /^[[:space:]]*[[:alpha:]][[:alnum:]_-]+[[:space:]]*\?=/ && desc { print "  "$$1 ":" desc } \
		 { desc = "" }' $(MAKEFILE_LIST) | sort | column -s: -t

.PHONY: build
## Build Go binaries.
build: ${GITALY_INSTALLED_EXECUTABLES}

.PHONY: install
## Install Gitaly binaries. The target directory can be modified by setting PREFIX and DESTDIR.
install: build
	${Q}mkdir -p ${INSTALL_DEST_DIR}
	install ${GITALY_INSTALLED_EXECUTABLES} "${INSTALL_DEST_DIR}"

.PHONY: build-bundled-git
## Build bundled Git binaries.
build-bundled-git: build-bundled-git-master build-bundled-git-prev
build-bundled-git-master: $(patsubst %,${BUILD_DIR}/bin/gitaly-%-master,${GIT_EXECUTABLES})
build-bundled-git-prev: $(patsubst %,${BUILD_DIR}/bin/gitaly-%-prev,${GIT_EXECUTABLES})

.PHONY: install-bundled-git
## Install bundled Git binaries. The target directory can be modified by
## setting PREFIX and DESTDIR.
install-bundled-git: install-bundled-git-master install-bundled-git-prev
install-bundled-git-master: $(patsubst %,${INSTALL_DEST_DIR}/gitaly-%-master,${GIT_EXECUTABLES})
install-bundled-git-prev: $(patsubst %,${INSTALL_DEST_DIR}/gitaly-%-prev,${GIT_EXECUTABLES})

ifdef WITH_BUNDLED_GIT
build: build-bundled-git
prepare-tests: build-bundled-git
install: install-bundled-git

else
prepare-tests: ${DEPENDENCY_DIR}/git-distribution/git

export GITALY_TESTING_GIT_BINARY ?= ${DEPENDENCY_DIR}/git-distribution/bin-wrappers/git
endif

## Enable testing with the SHA256 object format.
TEST_WITH_SHA256 ?=
ifdef TEST_WITH_SHA256
export GITALY_TEST_WITH_SHA256 = YesPlease
endif

## Enable generating test coverage.
TEST_WITH_COVERAGE ?=
ifdef TEST_WITH_COVERAGE
override TEST_OPTIONS := ${TEST_OPTIONS} -coverprofile "${TEST_COVERAGE_DIR}/all.merged"
prepare-tests: ${GOCOVER_COBERTURA} ${TEST_COVERAGE_DIR}

.PHONY: ${TEST_COVERAGE_DIR}
${TEST_COVERAGE_DIR}:
	${Q}rm -rf "${TEST_COVERAGE_DIR}"
	${Q}mkdir -p "${TEST_COVERAGE_DIR}"

# sed is used below to convert file paths to repository root relative paths.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/217664
run_go_tests += \
	&& go tool cover -html  "${TEST_COVERAGE_DIR}/all.merged" -o "${TEST_COVERAGE_DIR}/all.html" \
	&& ${GOCOVER_COBERTURA} <"${TEST_COVERAGE_DIR}/all.merged" | \
	sed 's;filename=\"$(shell go list -m)/;filename=\";g' >"${TEST_COVERAGE_DIR}/cobertura.xml"
endif

.PHONY: prepare-tests
prepare-tests: ${GOTESTSUM} ${GITALY_PACKED_EXECUTABLES} ${GIT_FILTER_REPO} ${GIT_PACKED_EXECUTABLES}
	${Q}mkdir -p "$(dir ${TEST_JUNIT_REPORT})"

.PHONY: prepare-debug
prepare-debug: ${DELVE}

.PHONY: test
## Run Go tests.
test: test-go

.PHONY: test-gitaly-linters
## Test Go tests in tools/golangci-lint/gitaly folder
## That folder has its own go.mod file. Hence tests must run the context of that folder.
test-gitaly-linters: TEST_PACKAGES := .
test-gitaly-linters: ${GOTESTSUM}
	${Q}cd ${SOURCE_DIR}/tools/golangci-lint/gitaly && $(call run_go_tests)

.PHONY: test-mod-validator
## Test Go tests in tools/mod-validator folder
## That folder has its own go.mod file. Hence tests must run the context of that folder.
test-mod-validator: TEST_PACKAGES := .
test-mod-validator: ${GOTESTSUM}
	${Q}cd ${SOURCE_DIR}/tools/mod-validator && $(call run_go_tests)

.PHONY: test-go
## Run Go tests.
test-go: prepare-tests ${GOCOVER_COBERTURA}
	${Q}$(call run_go_tests)

.PHONY: debug-test-go
## Run Go tests in delve debugger.
debug-test-go: prepare-tests prepare-debug
	${Q}$(call debug_go_tests)

.PHONY: test
## Run Go benchmarks.
bench: override TEST_FORMAT := standard-verbose
bench: override TEST_OPTIONS := -bench=. -run=^$ ${TEST_OPTIONS}
bench: ${BENCHMARK_REPO} prepare-tests
	${Q}$(call run_go_tests)

.PHONY: test-with-reftable
## Run Go tests with git's reftable backend.
## Since the reftable code isn't tagged in Git yet, to run it locally
## we have to specify the git version too:
## 'GIT_DEFAULT_REF_FORMAT=reftable OVERRIDE_GIT_VERSION="v99.99.99" GIT_VERSION="master" make test-go'
test-with-reftable: export GITALY_TEST_REF_FORMAT = reftable
test-with-reftable: test-go

.PHONY: test-with-git-master
## Run Go tests with git master.
test-with-git-master: export GITALY_TEST_GIT_MASTER = YesPlease
test-with-git-master: test-go

.PHONY: test-with-git-prev
## Run Go tests with git previous version.
test-with-git-prev: export GITALY_TEST_GIT_PREV = YesPlease
test-with-git-prev: test-go

.PHONY: test-with-sha256
## Run Go tests with SHA256 repositories.
test-with-sha256: export GITALY_TEST_WITH_SHA256 = YesPlease
test-with-sha256: test-go

.PHONY: test-with-praefect
## Run Go tests with Praefect.
test-with-praefect: export GITALY_TEST_WITH_PRAEFECT = YesPlease
test-with-praefect: test-go

.PHONY: test-wal
## Run Go tests with write-ahead logging enabled.
test-wal: export GITALY_TEST_WAL = YesPlease
test-wal: test-go

.PHONY: test-raft
## Run Go tests with write-ahead logging + Raft enabled.
test-raft: export GITALY_TEST_WAL = YesPlease
test-raft: export GITALY_TEST_RAFT = YesPlease
test-raft: test-go

.PHONY: test-with-praefect-wal
## Run Go tests with write-ahead logging and Praefect enabled.
test-with-praefect-wal: export GITALY_TEST_WAL = YesPlease
test-with-praefect-wal: test-with-praefect

.PHONY: race-go
## Run Go tests with race detection enabled.
race-go: override TEST_OPTIONS := ${TEST_OPTIONS} -race
race-go: test-go

## Running the verify steps in parallel make it difficult to read the output
## when there are failures.
.NOTPARALLEL: verify

.PHONY: verify
## Verify that various files conform to our expectations.
verify: check-mod-tidy notice-up-to-date check-proto lint

.PHONY: check-mod-tidy
check-mod-tidy:
	${Q}${GIT} diff --quiet --exit-code go.mod go.sum || (echo "error: uncommitted changes in go.mod or go.sum" && exit 1)
	${Q}go mod tidy
	${Q}${GIT} diff --quiet --exit-code go.mod go.sum || (echo "error: uncommitted changes in go.mod or go.sum" && exit 1)

${TOOLS_DIR}/gitaly-linters.so: ${SOURCE_DIR}/tools/golangci-lint/go.sum $(wildcard ${SOURCE_DIR}/tools/golangci-lint/gitaly/*.go)
	${Q}go build -buildmode=plugin -o '$@' -modfile ${SOURCE_DIR}/tools/golangci-lint/go.mod $(filter-out $<,$^)

.PHONY: lint
## Run Go linter.
lint: ${GOLANGCI_LINT} ${GITALY_PACKED_EXECUTABLES} ${GIT_PACKED_EXECUTABLES} ${TOOLS_DIR}/gitaly-linters.so lint-gitaly-linters lint-go-mod
	${Q}${GOLANGCI_LINT} run --build-tags "${SERVER_BUILD_TAGS}" --config ${GOLANGCI_LINT_CONFIG} ${GOLANGCI_LINT_OPTIONS}

.PHONY: lint-go-mod
## Lint Golang dependencies
lint-go-mod:
	${Q}go run ${SOURCE_DIR}/tools/mod-validator/main.go --file ${SOURCE_DIR}/go.mod

.PHONY: lint-fix
## Run Go linter and write back fixes to the files (not supported by all linters).
lint-fix: ${GOLANGCI_LINT} ${GITALY_PACKED_EXECUTABLES} ${GIT_PACKED_EXECUTABLES} ${TOOLS_DIR}/gitaly-linters.so
	${Q}${GOLANGCI_LINT} run --fix --build-tags "${SERVER_BUILD_TAGS}" --config ${GOLANGCI_LINT_CONFIG} ${GOLANGCI_LINT_OPTIONS}

.PHONY: lint-docs
## Run markdownlint-cli2-config to lint the documentation and lychee to check for broken links.
lint-docs:
	${Q}markdownlint-cli2 README.md REVIEWING.md STYLE.md doc/**/*.md doc/*.md
	${Q}vale --minAlertLevel error README.md REVIEWING.md STYLE.md doc
	${Q}lychee --version
	${Q}lychee --offline --include-fragments README.md REVIEWING.md STYLE.md doc/**/*.md doc/*.md

.PHONY: lint-docs-fix
## Run markdownlint-cli2-config to lint and fix the documentation.
lint-docs-fix:
	${Q}markdownlint-cli2 README.md REVIEWING.md STYLE.md doc/**.md --fix

.PHONY: lint-gitaly-linters
## Test Go tests in tools/golangci-lint/gitaly folder
## That folder has its own go.mod file. Hence linter must run the context of that folder.
lint-gitaly-linters: ${GOLANGCI_LINT} ${TOOLS_DIR}/gitaly-linters.so
	${Q}cd ${SOURCE_DIR}/tools/golangci-lint/gitaly && ${GOLANGCI_LINT} run --config ${GOLANGCI_LINT_CONFIG} .

.PHONY: format
## Run Go formatter and adjust imports.
format: ${GOIMPORTS} ${GOFUMPT}
	${Q}${GOIMPORTS} -w -l $(call find_go_sources)
	${Q}${GOFUMPT} -w $(call find_go_sources)

.PHONY: notice-up-to-date
notice-up-to-date: ${BUILD_DIR}/NOTICE
	${Q}${GIT} diff --exit-code ${BUILD_DIR}/NOTICE ${SOURCE_DIR}/NOTICE || (echo >&2 "NOTICE requires update: 'make notice'" && exit 1)

.PHONY: notice
## Regenerate the NOTICE file.
notice: ${SOURCE_DIR}/NOTICE

.PHONY: clean
## Clean up the build artifacts.
clean:
	rm -rf ${BUILD_DIR}

.PHONY: proto
## Regenerate protobuf definitions.
proto: ${PROTOC} ${PROTOC_GEN_GO} ${PROTOC_GEN_GO_GRPC} ${PROTOC_GEN_GITALY_PROTOLIST} ${DEPENDENCY_DIR}/raftpb
	${Q}rm -rf ${PROTO_DEST_DIR} && mkdir -p ${PROTO_DEST_DIR}/gitalypb
	${Q}${PROTOC} \
		--plugin=${PROTOC_GEN_GO} \
		--plugin=${PROTOC_GEN_GO_GRPC} \
		--plugin=${PROTOC_GEN_GITALY_PROTOLIST} \
		--go_opt=paths=source_relative \
		--go_opt=Mraftpb/raft.proto=go.etcd.io/raft/v3/raftpb \
		--go-grpc_opt=paths=source_relative \
		--go-grpc_opt=Mraftpb/raft.proto=go.etcd.io/raft/v3/raftpb \
		--go_out=${PROTO_DEST_DIR}/gitalypb \
		--gitaly-protolist_out=proto_dir=${SOURCE_DIR}/proto,gitalypb_dir=${PROTO_DEST_DIR}/gitalypb:${SOURCE_DIR} \
		--go-grpc_out=${PROTO_DEST_DIR}/gitalypb \
		${PROTOC_INCLUDE} \
		${SOURCE_DIR}/proto/*.proto \
		${SOURCE_DIR}/proto/testproto/*.proto

.PHONY: check-proto
check-proto: no-proto-changes lint-proto

.PHONY: lint-proto
lint-proto: ${PROTOC} ${PROTOLINT} ${PROTOC_GEN_GITALY_LINT} proto
	${Q}${PROTOLINT} lint -config_dir_path=${SOURCE_DIR}/proto ${SOURCE_DIR}/proto/*.proto

.PHONY: build-proto-gem
## Build the Ruby Gem that contains Gitaly's Protobuf definitions.
build-proto-gem: ${DEPENDENCY_DIR}/raftpb
	${Q}rm -rf "${BUILD_DIR}/${BUILD_GEM_NAME}.gem" && mkdir -p ${BUILD_DIR}
	${Q}rm -rf "${BUILD_DIR}/${BUILD_GEM_NAME}-gem" && mkdir -p ${BUILD_DIR}/${BUILD_GEM_NAME}-gem
	${Q}cd "${SOURCE_DIR}"/tools/protogem && bundle install
	${Q}GOGOPROTO_SOURCE_DIR="${GOGOPROTO_SOURCE_DIR}" RAFTPB_SOURCE_DIR="${RAFTPB_SOURCE_DIR}" "${SOURCE_DIR}"/tools/protogem/build-proto-gem -o "${BUILD_DIR}/${BUILD_GEM_NAME}.gem" --name ${BUILD_GEM_NAME} --working-dir ${BUILD_DIR}/${BUILD_GEM_NAME}-gem ${BUILD_GEM_OPTIONS}

.PHONY: publish-proto-gem
## Build and publish the Ruby Gem that contains Gitaly's Protobuf definitions.
publish-proto-gem: build-proto-gem
	${Q}gem push "${BUILD_DIR}/gitaly.gem"

.PHONY: build-proto-docs
## Build HTML documentation for Gitaly Protobuf definitions.
build-proto-docs: ${PROTOC} ${DEPENDENCY_DIR}/raftpb ${PROTOC_GEN_DOC}
	${Q}rm -rf ${BUILD_DIR}/proto-docs && mkdir -p ${BUILD_DIR}/proto-docs
	${Q}${PROTOC} ${PROTOC_INCLUDE} --doc_out=${BUILD_DIR}/proto-docs --doc_opt=html,index.html --plugin=protoc-gen-doc=${PROTOC_GEN_DOC} ${SOURCE_DIR}/proto/*.proto

.PHONY: build-protoset
## Build a compiled Protoset file which can be used with grpcui/grpcurl.
## https://github.com/fullstorydev/grpcui?tab=readme-ov-file#protoset-files
## Example usage: grpcui -plaintext -unix -protoset <gitaly.protoset> <gitaly.socket>
build-protoset: proto
	${Q}rm -rf ${BUILD_DIR}/protoset && mkdir -p ${BUILD_DIR}/protoset
	${Q}${PROTOC} \
		--descriptor_set_out=${BUILD_DIR}/protoset/gitaly.protoset \
		--include_imports \
		${PROTOC_INCLUDE} \
		${SOURCE_DIR}/proto/*.proto

.PHONY: govulncheck
## pipefail is set in the SHELL config, but we don't care about govulncheck's exit code here.
govulncheck: ${GOVULNCHECK}
	${Q}(${GOVULNCHECK} ./... || true) | go run ${SOURCE_DIR}/tools/govulncheck-filter/main.go

.PHONY: no-changes
no-changes:
	${Q}${GIT} diff --exit-code

.PHONY: no-proto-changes
no-proto-changes: PROTO_DEST_DIR := ${BUILD_DIR}/proto-changes
no-proto-changes: proto | ${BUILD_DIR}
	${Q}${GIT} diff --no-index --exit-code -- "${SOURCE_DIR}/proto/go" "${PROTO_DEST_DIR}"

.PHONY: dump-database-schema
## Dump the clean database schema of Praefect into a file.
dump-database-schema: build
	${Q}"${SOURCE_DIR}"/_support/generate-praefect-schema >"${SOURCE_DIR}"/_support/praefect-schema.sql

.PHONY: upgrade-module
upgrade-module:
	${Q}go run ${SOURCE_DIR}/tools/module-updater/main.go -dir . -from=${FROM_MODULE} -to=${TO_MODULE}
	${Q}${MAKE} proto

.PHONY: git
# This target is deprecated and will eventually be removed.
git: install-git

.PHONY: build-git
## Build Git distribution.
ifdef USE_MESON
build-git: ${DEPENDENCY_DIR}/git-distribution/build/git
else
build-git: ${DEPENDENCY_DIR}/git-distribution/git
endif

.PHONY: install-git
## Install Git distribution.
ifdef USE_MESON
install-git: build-git
	${Q}meson install -C "${DEPENDENCY_DIR}/git-distribution/build"
else
install-git: build-git
	${Q}env -u PROFILE -u MAKEFLAGS -u GIT_VERSION ${MAKE} -C "${DEPENDENCY_DIR}/git-distribution" -j$(shell nproc) prefix=${GIT_PREFIX} ${GIT_BUILD_OPTIONS} install
endif


${SOURCE_DIR}/NOTICE: ${BUILD_DIR}/NOTICE
	${Q}mv $< $@

${BUILD_DIR}/NOTICE: ${GO_LICENSES} ${GITALY_PACKED_EXECUTABLES}
	${Q}rm -rf ${BUILD_DIR}/licenses
	${Q}GOOS=linux GOFLAGS="-tags=${SERVER_BUILD_TAGS}" ${GO_LICENSES} save ${SOURCE_DIR}/... --save_path=${BUILD_DIR}/licenses
	${Q}go run ${SOURCE_DIR}/tools/noticegen/noticegen.go -source ${BUILD_DIR}/licenses -template ${SOURCE_DIR}/tools/noticegen/notice.template > ${BUILD_DIR}/NOTICE

${BUILD_DIR}:
	${Q}mkdir -p ${BUILD_DIR}
${BUILD_DIR}/bin: | ${BUILD_DIR}
	${Q}mkdir -p ${BUILD_DIR}/bin
${TOOLS_DIR}: | ${BUILD_DIR}
	${Q}mkdir -p ${TOOLS_DIR}
${DEPENDENCY_DIR}: | ${BUILD_DIR}
	${Q}mkdir -p ${DEPENDENCY_DIR}

# These targets build a full Git distribution with the Makefile...
${DEPENDENCY_DIR}/git-distribution/git: ${DEPENDENCY_DIR}/git-distribution/Makefile
	${Q}env -u PROFILE -u MAKEFLAGS -u GIT_VERSION ${MAKE} -C "$(<D)" -j$(shell nproc) prefix=${GIT_PREFIX} ${GIT_BUILD_OPTIONS}
	${Q}touch $@
# ... and with Meson.
${DEPENDENCY_DIR}/git-distribution/build/git: ${DEPENDENCY_DIR}/git-distribution/Makefile
	${Q}rm -rf "${DEPENDENCY_DIR}/git-distribution/build"
	${Q}meson setup "${DEPENDENCY_DIR}/git-distribution" "${DEPENDENCY_DIR}/git-distribution/build" ${GIT_MESON_BUILD_OPTIONS}
	${Q}meson compile -C "${DEPENDENCY_DIR}/git-distribution/build"
	${Q}touch $@

# These targets build specific releases of Git.
${BUILD_DIR}/bin/gitaly-%-prev: override GIT_VERSION = ${GIT_VERSION_PREV}
${BUILD_DIR}/bin/gitaly-%-master: override GIT_VERSION = ${GIT_VERSION_MASTER}

ifdef USE_MESON
${BUILD_DIR}/bin/gitaly-%-prev: ${DEPENDENCY_DIR}/git-prev/build/% | ${BUILD_DIR}/bin
	${Q}install $< $@
${BUILD_DIR}/bin/gitaly-%-master: ${DEPENDENCY_DIR}/git-master/build/% | ${BUILD_DIR}/bin
	${Q}install $< $@
else
${BUILD_DIR}/bin/gitaly-%-prev: ${DEPENDENCY_DIR}/git-prev/% | ${BUILD_DIR}/bin
	${Q}install $< $@
${BUILD_DIR}/bin/gitaly-%-master: ${DEPENDENCY_DIR}/git-master/% | ${BUILD_DIR}/bin
	${Q}install $< $@
endif

# clear-go-build-cache-if-needed cleans the Go build cache if it exceeds the maximum size as
# configured in GOCACHE_MAX_SIZE_KB.
.PHONY: clear-go-build-cache-if-needed
clear-go-build-cache-if-needed:
	${Q}if [ -d ${GOCACHE} ] && [ $$(du -sk ${GOCACHE} | cut -f 1) -gt ${GOCACHE_MAX_SIZE_KB} ]; then go clean --cache; fi

${BUILD_DIR}/bin/gitaly:   build-bundled-git
${BUILD_DIR}/bin/gitaly:   GO_BUILD_TAGS = ${SERVER_BUILD_TAGS}
${BUILD_DIR}/bin/gitaly:   ${GITALY_PACKED_EXECUTABLES} ${GIT_PACKED_EXECUTABLES}
${BUILD_DIR}/bin/praefect: GO_BUILD_TAGS = ${SERVER_BUILD_TAGS}

## Here we are resetting GO_BUILD_TAGS to an empty list of tags for all binaries
## except `gitaly` and `praefect`. Without this override, all binaries in
## GITALY_PACKED_EXECUTABLES will be build with SERVER_BUILD_TAGS, which we
## don't want. See MR here for more details:
## https://gitlab.com/gitlab-org/gitaly/-/merge_requests/8173
${GITALY_PACKED_EXECUTABLES}: GO_BUILD_TAGS =

${GITALY_EXECUTABLES}: ${BUILD_DIR}/bin/%: clear-go-build-cache-if-needed .FORCE
	${Q}cd ${SOURCE_DIR} && go build -o "$@" -ldflags '-B gobuildid ${GO_LDFLAGS}' -tags "${GO_BUILD_TAGS}" $(addprefix ${SOURCE_DIR}/cmd/,$(@F))

# This is a build hack to avoid excessive rebuilding of targets. Instead of
# depending on the Makefile, we start to depend on tool versions as defined in
# the Makefile. Like this, we only rebuild if the tool versions actually
# change. The dependency on the phony target is required to always rebuild
# these targets.
.PHONY: dependency-version
${DEPENDENCY_DIR}/git-%.version: dependency-version | ${DEPENDENCY_DIR}
	${Q}[ x"$$(cat "$@" 2>/dev/null)" = x"${GIT_VERSION} ${GIT_BUILD_OPTIONS} ${GIT_MESON_BUILD_OPTIONS} ${USE_MESON}" ] || >$@ echo -n "${GIT_VERSION} ${GIT_BUILD_OPTIONS} ${GIT_MESON_BUILD_OPTIONS} ${USE_MESON}"
${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}"
${DEPENDENCY_DIR}/git-filter-repo.version: dependency-version | ${DEPENDENCY_DIR}
	${Q}[ x"$$(cat "$@" 2>/dev/null)" = x"${GIT_FILTER_REPO_VERSION}" ] || >$@ echo -n "${GIT_FILTER_REPO_VERSION}"
# This target is responsible for checking out Git sources. In theory, we'd only
# need to depend on the source directory. But given that the source directory
# always changes when anything inside of it changes, like when we for example
# 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.
${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
ifeq ($(OVERRIDE_GIT_VERSION),)
	${Q}rm -f "${@D}"/version
else
	@ # 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).
	${Q}echo ${OVERRIDE_GIT_VERSION} >"${@D}"/version
endif
	${Q}touch $@

$(patsubst %,${DEPENDENCY_DIR}/git-\%/build/%,${GIT_EXECUTABLES}): ${DEPENDENCY_DIR}/git-%/Makefile
	${Q}rm -rf "$(dir ${@D})"/build
	${Q}meson setup "$(dir ${@D})" "$(dir ${@D})"/build ${GIT_MESON_BUILD_OPTIONS}
	${Q}meson compile -C "$(dir ${@D})/build" $(patsubst %,%:executable,${GIT_EXECUTABLES})
	${Q}touch $@

$(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 $@

${INSTALL_DEST_DIR}/gitaly-%: ${BUILD_DIR}/bin/gitaly-%
	${Q}mkdir -p ${@D}
	${Q}install $< $@

${PROTOC}: ${DEPENDENCY_DIR}/protoc.version | ${TOOLS_DIR}
	${Q}${GIT} -c init.defaultBranch=master init ${GIT_QUIET} "${PROTOC_SOURCE_DIR}"
	${Q}${GIT} -C "${PROTOC_SOURCE_DIR}" config remote.origin.url ${PROTOC_REPO_URL}
	${Q}${GIT} -C "${PROTOC_SOURCE_DIR}" config remote.origin.tagOpt --no-tags
	${Q}${GIT} -C "${PROTOC_SOURCE_DIR}" fetch --depth 1 ${GIT_QUIET} origin ${PROTOC_VERSION}
	${Q}${GIT} -C "${PROTOC_SOURCE_DIR}" checkout ${GIT_QUIET} --detach FETCH_HEAD
	${Q}${GIT} -C "${PROTOC_SOURCE_DIR}" submodule update --init --recursive
	${Q}rm -rf "${PROTOC_BUILD_DIR}"
	${Q}cmake "${PROTOC_SOURCE_DIR}" -B "${PROTOC_BUILD_DIR}" ${PROTOC_BUILD_OPTIONS}
	${Q}cmake --build "${PROTOC_BUILD_DIR}" --target install -- -j $(shell nproc)
	${Q}cp "${PROTOC_INSTALL_DIR}"/bin/protoc ${PROTOC}


${PROTOC_GEN_GITALY_LINT}: proto | ${TOOLS_DIR}
	${Q}go build -o $@ ${SOURCE_DIR}/tools/protoc-gen-gitaly-lint

${PROTOC_GEN_GITALY_PROTOLIST}: | ${TOOLS_DIR}
	${Q}go build -o $@ ${SOURCE_DIR}/tools/protoc-gen-gitaly-protolist

${DEPENDENCY_DIR}/gogoproto:
	${Q}${GIT} -c init.defaultBranch=master init ${GIT_QUIET} "${GOGOPROTO_SOURCE_DIR}"
	${Q}${GIT} -C "${GOGOPROTO_SOURCE_DIR}" config remote.origin.url ${GOGOPROTO_REPO_URL}
	${Q}${GIT} -C "${GOGOPROTO_SOURCE_DIR}" config remote.origin.tagOpt --no-tags
	${Q}${GIT} -C "${GOGOPROTO_SOURCE_DIR}" fetch --depth 1 ${GIT_QUIET} origin master
	${Q}${GIT} -C "${GOGOPROTO_SOURCE_DIR}" checkout ${GIT_QUIET} --detach FETCH_HEAD

${DEPENDENCY_DIR}/raftpb: ${DEPENDENCY_DIR}/gogoproto
	${Q}${GIT} -c init.defaultBranch=master init ${GIT_QUIET} "${RAFTPB_SOURCE_DIR}"
	${Q}${GIT} -C "${RAFTPB_SOURCE_DIR}" config remote.origin.url ${RAFTPB_REPO_URL}
	${Q}${GIT} -C "${RAFTPB_SOURCE_DIR}" config remote.origin.tagOpt --no-tags
	${Q}${GIT} -C "${RAFTPB_SOURCE_DIR}" fetch --depth 1 ${GIT_QUIET} origin main
	${Q}${GIT} -C "${RAFTPB_SOURCE_DIR}" checkout ${GIT_QUIET} --detach FETCH_HEAD


${GIT_FILTER_REPO}: ${DEPENDENCY_DIR}/git-filter-repo.version | ${BUILD_DIR}/bin
	${Q}${GIT} -c init.defaultBranch=master init ${GIT_QUIET} "${GIT_FILTER_REPO_SOURCE_DIR}"
	${Q}${GIT} -C "${GIT_FILTER_REPO_SOURCE_DIR}" config remote.origin.url ${GIT_FILTER_REPO_REPO_URL}
	${Q}${GIT} -C "${GIT_FILTER_REPO_SOURCE_DIR}" config remote.origin.tagOpt --no-tags
	${Q}${GIT} -C "${GIT_FILTER_REPO_SOURCE_DIR}" fetch --depth 1 ${GIT_QUIET} origin ${GIT_FILTER_REPO_VERSION}
	${Q}${GIT} -C "${GIT_FILTER_REPO_SOURCE_DIR}" reset --hard
	${Q}${GIT} -C "${GIT_FILTER_REPO_SOURCE_DIR}" checkout ${GIT_QUIET} --detach FETCH_HEAD
	${Q}cp "${GIT_FILTER_REPO_SOURCE_DIR}/git-filter-repo" ${GIT_FILTER_REPO}

# External tools
${TOOLS_DIR}/%: ${SOURCE_DIR}/tools/%/tool.go ${SOURCE_DIR}/tools/%/go.mod ${SOURCE_DIR}/tools/%/go.sum | ${TOOLS_DIR}
	${Q}GOBIN=${TOOLS_DIR} go install -modfile ${SOURCE_DIR}/tools/$*/go.mod ${TOOL_PACKAGE}

${GOCOVER_COBERTURA}: TOOL_PACKAGE = github.com/t-yuki/gocover-cobertura
${GOFUMPT}:           TOOL_PACKAGE = mvdan.cc/gofumpt
${GOIMPORTS}:         TOOL_PACKAGE = golang.org/x/tools/cmd/goimports
${GOLANGCI_LINT}:     TOOL_PACKAGE = github.com/golangci/golangci-lint/v2/cmd/golangci-lint
${PROTOLINT}:         TOOL_PACKAGE = github.com/yoheimuta/protolint/cmd/protolint
${GOTESTSUM}:         TOOL_PACKAGE = gotest.tools/gotestsum
${GO_LICENSES}:       TOOL_PACKAGE = github.com/google/go-licenses
${PROTOC_GEN_GO}:     TOOL_PACKAGE = google.golang.org/protobuf/cmd/protoc-gen-go
${PROTOC_GEN_GO_GRPC}:TOOL_PACKAGE = google.golang.org/grpc/cmd/protoc-gen-go-grpc
${PROTOC_GEN_DOC}:    TOOL_PACKAGE = github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc
${DELVE}:             TOOL_PACKAGE = github.com/go-delve/delve/cmd/dlv
${GOVULNCHECK}:       TOOL_PACKAGE = golang.org/x/vuln/cmd/govulncheck
${GOVULNCHECK}:       TOOL_PACKAGE = golang.org/x/vuln/cmd/govulncheck

${BENCHMARK_REPO}:
	${GIT} clone --mirror ${GIT_QUIET} https://gitlab.com/gitlab-org/gitlab.git $@
