diff --git a/cmake/ecbuild_bundle.cmake b/cmake/ecbuild_bundle.cmake index a6d0535d..6abab368 100644 --- a/cmake/ecbuild_bundle.cmake +++ b/cmake/ecbuild_bundle.cmake @@ -130,6 +130,12 @@ endmacro() # To switch off a subproject when building a bundle, set the CMake variable # ``BUNDLE_SKIP_`` where ``PNAME`` is the capitalised project name. # +# Note: BRANCH, TAG, UPDATE, NOREMOTE, MANUAL, RECURSIVE, and SHALLOW are not +# parsed by ecbuild_bundle itself. They are forwarded verbatim to ecbuild_git +# via the unparsed-arguments mechanism (``${_PAR_UNPARSED_ARGUMENTS}``). Any +# future keyword added to ecbuild_bundle must not clash with an ecbuild_git +# keyword unless intentional forwarding is desired. +# ############################################################################## macro( ecbuild_bundle ) diff --git a/cmake/ecbuild_git.cmake b/cmake/ecbuild_git.cmake index 6a35113d..f898cdfa 100644 --- a/cmake/ecbuild_git.cmake +++ b/cmake/ecbuild_git.cmake @@ -65,7 +65,9 @@ endif() # Do a shallow clone (``--depth 1``) on initial checkout. # When combined with RECURSIVE, submodules are also fetched at depth 1. # Cannot be combined with TAG when it's a commit ID (SHA). -# SHALLOW is not switchable and will fail if UPDATE is requested on an existing shallow clone. +# When using the SHALLOW option, attempting to switch branch/tag will result in a failure. +# The SHALLOW option will fail if used in combination with UPDATE. +# The SHALLOW option will fail if used in combination with NOREMOTE. # ############################################################################## @@ -85,7 +87,11 @@ function( ecbuild_git ) endif() if( _PAR_UPDATE AND _PAR_SHALLOW ) - ecbuild_warn("UPDATE and SHALLOW conflict — shallow clones aren't switchable; UPDATE may be ignored or fail.") + ecbuild_critical("Cannot pass both UPDATE and SHALLOW in ecbuild_git.") + endif() + + if( _PAR_NOREMOTE AND _PAR_SHALLOW ) + ecbuild_critical("Cannot pass both NOREMOTE and SHALLOW in ecbuild_git.") endif() if(_PAR_UNPARSED_ARGUMENTS) @@ -224,7 +230,9 @@ function( ecbuild_git ) # fetching latest tags and branches if( _PAR_SHALLOW ) - ecbuild_info("${_PAR_DIR} is SHALLOW : Skipping fetch") + ecbuild_info("${_PAR_DIR} is a shallow clone\n" + " * Skipping fetch in order to preserve shallow history.\n" + " * Run 'git fetch --unshallow' manually if full history is needed.") elseif( NOT _PAR_NOREMOTE ) ecbuild_info("git fetch --all @ ${ABS_PAR_DIR}") diff --git a/tests/ecbuild_bundle_shallow/CMakeLists.txt b/tests/ecbuild_bundle_shallow/CMakeLists.txt index 74a82e34..0802d74e 100644 --- a/tests/ecbuild_bundle_shallow/CMakeLists.txt +++ b/tests/ecbuild_bundle_shallow/CMakeLists.txt @@ -1,8 +1,38 @@ ecbuild_add_test( - TARGET test_ecbuild_bundle_shallow + TARGET test_ecbuild_bundle_shallow_recursive TYPE SCRIPT - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run-test.sh + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run-test-shallow-recursive.sh + ENVIRONMENT + CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} + CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} + CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} +) + +ecbuild_add_test( + TARGET test_ecbuild_bundle_shallow_no_recursive + TYPE SCRIPT + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run-test-shallow-no-recursive.sh + ENVIRONMENT + CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} + CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} + CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} +) + +ecbuild_add_test( + TARGET test_ecbuild_bundle_shallow_update_fails + TYPE SCRIPT + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run-test-invalid-update.sh + ENVIRONMENT + CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} + CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} + CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} +) + +ecbuild_add_test( + TARGET test_ecbuild_bundle_shallow_noremote_fails + TYPE SCRIPT + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run-test-invalid-noremote.sh ENVIRONMENT CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/ecbuild_bundle_shallow/run-test-invalid-noremote.sh b/tests/ecbuild_bundle_shallow/run-test-invalid-noremote.sh new file mode 100755 index 00000000..8db29901 --- /dev/null +++ b/tests/ecbuild_bundle_shallow/run-test-invalid-noremote.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ECBUILD_PATH=${CMAKE_SOURCE_DIR}/bin +BINARY_TEST_DIR=${CMAKE_CURRENT_BINARY_DIR} + +# +# Redirect HOME to a per-job temp directory so that every git process +# (including subprocesses spawned internally by git-submodule) reads a private +# ~/.gitconfig. This avoids lock contention on the real ~/.gitconfig when +# parallel CI jobs run, and works on all git versions. +# +# GIT_CONFIG_GLOBAL (requires git >= 2.32) and GIT_CONFIG_COUNT (not forwarded +# by git-submodule to its child processes) are not viable alternatives here. +# +# Required on git >= 2.38.1 and on distros that have backported CVE-2022-39253 +# (e.g. Debian 11's git 2.30.2). +# +_tmp_home=$(mktemp -d) +export HOME="${_tmp_home}" +trap 'rm -rf "${_tmp_home}"' EXIT +pushd "${HOME}" > /dev/null +git config --global protocol.file.allow always +git config --global user.name "Test User" +git config --global user.email "test@user" +popd > /dev/null + +# Add ecbuild to path +export PATH=$ECBUILD_PATH:$PATH + +# ---- cleanup ----------------------------------------- +[[ -n "${BINARY_TEST_DIR}" ]] || { echo "BINARY_TEST_DIR is not set"; exit 1; } +rm -rf "${BINARY_TEST_DIR}/workspace-invalid-noremote" + +WORKSPACE="${BINARY_TEST_DIR}/workspace-invalid-noremote" +BUNDLE_DIR="${WORKSPACE}/bundle" + +# ---- create bundle: SHALLOW + NOREMOTE (invalid combination) - +# ecbuild_critical fires before any git operation, so the URL need not exist. +mkdir -p "${BUNDLE_DIR}" +cat > "${BUNDLE_DIR}/CMakeLists.txt" < "${BUNDLE_DIR}/VERSION" <&1 ) +cmake_status=$? +set -e + +if [[ "${cmake_status}" -eq 0 ]]; then + echo "cmake configure should have failed for SHALLOW+NOREMOTE but succeeded: FAIL" + exit 1 +fi + +if echo "${output}" | grep -q "Cannot pass both NOREMOTE and SHALLOW"; then + echo "SHALLOW+NOREMOTE correctly rejected: PASS" +else + echo "cmake failed but expected error message not found: FAIL" + echo "${output}" + exit 1 +fi diff --git a/tests/ecbuild_bundle_shallow/run-test-invalid-update.sh b/tests/ecbuild_bundle_shallow/run-test-invalid-update.sh new file mode 100755 index 00000000..ca836207 --- /dev/null +++ b/tests/ecbuild_bundle_shallow/run-test-invalid-update.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ECBUILD_PATH=${CMAKE_SOURCE_DIR}/bin +BINARY_TEST_DIR=${CMAKE_CURRENT_BINARY_DIR} + +# +# Redirect HOME to a per-job temp directory so that every git process +# (including subprocesses spawned internally by git-submodule) reads a private +# ~/.gitconfig. This avoids lock contention on the real ~/.gitconfig when +# parallel CI jobs run, and works on all git versions. +# +# GIT_CONFIG_GLOBAL (requires git >= 2.32) and GIT_CONFIG_COUNT (not forwarded +# by git-submodule to its child processes) are not viable alternatives here. +# +# Required on git >= 2.38.1 and on distros that have backported CVE-2022-39253 +# (e.g. Debian 11's git 2.30.2). +# +_tmp_home=$(mktemp -d) +export HOME="${_tmp_home}" +trap 'rm -rf "${_tmp_home}"' EXIT +pushd "${HOME}" > /dev/null +git config --global protocol.file.allow always +git config --global user.name "Test User" +git config --global user.email "test@user" +popd > /dev/null + +# Add ecbuild to path +export PATH=$ECBUILD_PATH:$PATH + +# ---- cleanup ----------------------------------------- +[[ -n "${BINARY_TEST_DIR}" ]] || { echo "BINARY_TEST_DIR is not set"; exit 1; } +rm -rf "${BINARY_TEST_DIR}/workspace-invalid-update" + +WORKSPACE="${BINARY_TEST_DIR}/workspace-invalid-update" +BUNDLE_DIR="${WORKSPACE}/bundle" + +# ---- create bundle: SHALLOW + UPDATE (invalid combination) --- +# ecbuild_critical fires before any git operation, so the URL need not exist. +mkdir -p "${BUNDLE_DIR}" +cat > "${BUNDLE_DIR}/CMakeLists.txt" < "${BUNDLE_DIR}/VERSION" <&1 ) +cmake_status=$? +set -e + +if [[ "${cmake_status}" -eq 0 ]]; then + echo "cmake configure should have failed for SHALLOW+UPDATE but succeeded: FAIL" + exit 1 +fi + +if echo "${output}" | grep -q "Cannot pass both UPDATE and SHALLOW"; then + echo "SHALLOW+UPDATE correctly rejected: PASS" +else + echo "cmake failed but expected error message not found: FAIL" + echo "${output}" + exit 1 +fi diff --git a/tests/ecbuild_bundle_shallow/run-test-shallow-no-recursive.sh b/tests/ecbuild_bundle_shallow/run-test-shallow-no-recursive.sh new file mode 100755 index 00000000..3c1357a5 --- /dev/null +++ b/tests/ecbuild_bundle_shallow/run-test-shallow-no-recursive.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ECBUILD_PATH=${CMAKE_SOURCE_DIR}/bin +SOURCE_TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR} +BINARY_TEST_DIR=${CMAKE_CURRENT_BINARY_DIR} + +# +# Redirect HOME to a per-job temp directory so that every git process +# (including subprocesses spawned internally by git-submodule) reads a private +# ~/.gitconfig. This avoids lock contention on the real ~/.gitconfig when +# parallel CI jobs run, and works on all git versions. +# +# GIT_CONFIG_GLOBAL (requires git >= 2.32) and GIT_CONFIG_COUNT (not forwarded +# by git-submodule to its child processes) are not viable alternatives here. +# +# Required on git >= 2.38.1 and on distros that have backported CVE-2022-39253 +# (e.g. Debian 11's git 2.30.2). +# +_tmp_home=$(mktemp -d) +export HOME="${_tmp_home}" +trap 'rm -rf "${_tmp_home}"' EXIT +pushd "${HOME}" > /dev/null +git config --global protocol.file.allow always +git config --global user.name "Test User" +git config --global user.email "test@user" +popd > /dev/null + +# Add ecbuild to path +export PATH=$ECBUILD_PATH:$PATH + +# ---- cleanup ----------------------------------------- +[[ -n "${BINARY_TEST_DIR}" ]] || { echo "BINARY_TEST_DIR is not set"; exit 1; } +rm -rf "${BINARY_TEST_DIR}/workspace-shallow-only" + +WORKSPACE="${BINARY_TEST_DIR}/workspace-shallow-only" +BUNDLE_DIR="${WORKSPACE}/bundle" + +# ---- setup umbrella project (with submodules) -------- +cd "${BINARY_TEST_DIR}" +bash "${SOURCE_TEST_DIR}/setup-umbrella-project.sh" "${WORKSPACE}/projects" + +BARE_DIR="${WORKSPACE}/bare-repos" +UMBRELLA_GIT="file://${BARE_DIR}/umbrella.git" + +# ---- create bundle: SHALLOW only (no RECURSIVE) ------ +mkdir -p "${BUNDLE_DIR}" +cat > "${BUNDLE_DIR}/CMakeLists.txt" < "${BUNDLE_DIR}/VERSION" <= 2.32) and GIT_CONFIG_COUNT (not forwarded +# by git-submodule to its child processes) are not viable alternatives here. +# +# Required on git >= 2.38.1 and on distros that have backported CVE-2022-39253 +# (e.g. Debian 11's git 2.30.2). +# +_tmp_home=$(mktemp -d) +export HOME="${_tmp_home}" +trap 'rm -rf "${_tmp_home}"' EXIT +pushd "${HOME}" > /dev/null +git config --global protocol.file.allow always +git config --global user.name "Test User" +git config --global user.email "test@user" +popd > /dev/null + +# Add ecbuild to path +export PATH=$ECBUILD_PATH:$PATH + +# ---- cleanup ----------------------------------------- +[[ -n "${BINARY_TEST_DIR}" ]] || { echo "BINARY_TEST_DIR is not set"; exit 1; } +rm -rf "${BINARY_TEST_DIR}/workspace" + +# ---- setup umbrella project (with submodules) -------- +cd "${BINARY_TEST_DIR}" +bash "${SOURCE_TEST_DIR}/setup-umbrella-project.sh" \ + "${BINARY_TEST_DIR}/workspace/projects" + +# ---- setup umbrella bundle --------------------------- +cd "${BINARY_TEST_DIR}" +bash "${SOURCE_TEST_DIR}/setup-umbrella-bundle.sh" \ + "${BINARY_TEST_DIR}/workspace/bundle" \ + "${BINARY_TEST_DIR}/workspace/projects/umbrella" + +# ---- configure umbrella bundle ----------------------- +cd ${BINARY_TEST_DIR}/workspace +mkdir build +cd build +ecbuild --prefix=$(pwd)/install -- ../bundle + +# ---- check shallowness (umbrella + submodules) ------- +PASS=1 + +cd "${BINARY_TEST_DIR}/workspace/bundle/umbrella" +if [[ "$(git rev-parse --is-shallow-repository)" != "true" ]]; then + echo "Umbrella is not shallow: FAIL" + PASS=0 +else + echo "Umbrella is shallow: PASS" +fi + +for sub in alpha beta gamma; do + cd "${BINARY_TEST_DIR}/workspace/bundle/umbrella/${sub}" + if [[ "$(git rev-parse --is-shallow-repository)" != "true" ]]; then + echo "Submodule $sub is not shallow: FAIL" + PASS=0 + else + echo "Submodule $sub is shallow: PASS" + fi +done + +exit $(( 1 - PASS )) diff --git a/tests/ecbuild_bundle_shallow/run-test.sh b/tests/ecbuild_bundle_shallow/run-test.sh deleted file mode 100755 index 12d415e1..00000000 --- a/tests/ecbuild_bundle_shallow/run-test.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -HERE="$( cd $( dirname "${BASH_SOURCE[0]}" ) && pwd -P )" - -ECBUILD_PATH=${CMAKE_SOURCE_DIR}/bin -SOURCE_TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR} -BINARY_TEST_DIR=${CMAKE_CURRENT_BINARY_DIR} - -# Git 2.38.1+ blocks local file:// submodule clones by default. -# Allow them for the duration of this script only. -export GIT_CONFIG_COUNT=1 -export GIT_CONFIG_KEY_0=protocol.file.allow -export GIT_CONFIG_VALUE_0=always - -# Add ecbuild to path -export PATH=$ECBUILD_PATH:$PATH - -# ---- cleanup ----------------------------------------- -rm -rf "${BINARY_TEST_DIR}/workspace" - -# ---- setup umbrella project (with submodules) -------- -cd "${BINARY_TEST_DIR}" -bash "${SOURCE_TEST_DIR}/setup-umbrella-project.sh" \ - "${BINARY_TEST_DIR}/workspace/projects" - -# ---- setup umbrella bundle --------------------------- -cd "${BINARY_TEST_DIR}" -bash "${SOURCE_TEST_DIR}/setup-umbrella-bundle.sh" \ - "${BINARY_TEST_DIR}/workspace/bundle" \ - "${BINARY_TEST_DIR}/workspace/projects/umbrella" - -# ---- configure umbrella bundle ----------------------- -cd ${BINARY_TEST_DIR}/workspace -mkdir build -cd build -ecbuild --prefix=$(pwd)/install -- ../bundle - -# ---- check shallowness (umbrella) -------------------- -cd ${BINARY_TEST_DIR}/workspace/bundle/umbrella - -if [[ "$(git rev-parse --is-shallow-repository)" == "true" ]]; then - echo "Submodule is shallow: PASS" - exit 0 -else - echo "Submodule is not shallow: FAIL" - exit 1 -fi - -# ---- check shallowness (umbrella submodules) --------- -for sub in alpha beta gamma; do - cd "${BINARY_TEST_DIR}/workspace/bundle/umbrella/${sub}" - - if [[ "$(git rev-parse --is-shallow-repository)" == "true" ]]; then - echo "Submodule $sub is shallow: PASS" - else - echo "Submodule $sub is not shallow: FAIL" - exit 1 - fi -done diff --git a/tests/ecbuild_bundle_shallow/setup-umbrella-project.sh b/tests/ecbuild_bundle_shallow/setup-umbrella-project.sh index f81dbf89..8ba651f3 100644 --- a/tests/ecbuild_bundle_shallow/setup-umbrella-project.sh +++ b/tests/ecbuild_bundle_shallow/setup-umbrella-project.sh @@ -33,8 +33,8 @@ for proj in "${PROJECTS[@]}"; do mkdir -p "$proj" cd "$proj" git init -b main - git config user.email "demo@example.com" - git config user.name "Demo User" + git config --local user.email "demo@example.com" + git config --local user.name "Demo User" # Initial commit with README cat > README.md < Setting up umbrella project: $UMBRELLA" mkdir -p "$UMBRELLA" cd "$UMBRELLA" git init -b main -git config user.email "demo@example.com" -git config user.name "Demo User" +git config --local user.email "demo@example.com" +git config --local user.name "Demo User" # Initial README cat > README.md </dev/null 2>&1 - git submodule add "file://$BARE_DIR/$proj.git" "$proj" + git clone --bare "$BASE_DIR/$proj" "$BARE_DIR/$proj.git" >/dev/null + git -c protocol.file.allow=always submodule add "file://$BARE_DIR/$proj.git" "$proj" done commit "Add submodules: alpha, beta, gamma" @@ -119,5 +120,7 @@ Replace the URLs in \`.gitmodules\` with real remote URLs before sharing. EOF commit "docs: note about submodule remote URLs" +# # IMPORTANT: bare repo to ensure shallow clone behavior is honored -git clone --bare "$(pwd)" "$BARE_DIR/$UMBRELLA.git" >/dev/null 2>&1 +# +git clone --bare "$(pwd)" "$BARE_DIR/$UMBRELLA.git" >/dev/null