From 464010be859f1b7f5237aae7daebdd74701012d7 Mon Sep 17 00:00:00 2001 From: whisper67265 Date: Mon, 29 Jun 2026 11:32:03 -0600 Subject: [PATCH 1/4] naming clarity: disambiguate process_one_submodule --- .github/workflows/add-submodules.yml | 7 ++++--- .github/workflows/assets/lib.sh | 5 +++-- .github/workflows/assets/translation.sh | 3 ++- .github/workflows/start-translation.yml | 10 +++++----- tests/test_process_submodule.bats | 24 ++++++++++++------------ 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/.github/workflows/add-submodules.yml b/.github/workflows/add-submodules.yml index 032b799..5c47680 100644 --- a/.github/workflows/add-submodules.yml +++ b/.github/workflows/add-submodules.yml @@ -107,7 +107,8 @@ jobs: # ── Per-submodule processing ────────────────────────────────────────── - process_one_submodule() { + # Create one MODULE_ORG mirror repo from a boostorg lib (add-submodules only). + add_one_submodule() { local sub_name="$1" doc_paths if repo_exists "$MODULE_ORG" "$sub_name"; then @@ -185,7 +186,7 @@ jobs: for i in "${!submodule_names[@]}"; do sub="${submodule_names[$i]}" echo "[$(( i + 1 ))/$total] $sub ..." >&2 - if process_one_submodule "$sub"; then + if add_one_submodule "$sub"; then record_submodule_update "$sub" || true else rc=$? @@ -196,7 +197,7 @@ jobs: fi done - # Buckets filled by process_one_submodule. + # Buckets filled by add_one_submodule. print_submodule_processing_summary [[ $submodule_fatal -gt 0 ]] && \ phase_err "$submodule_fatal submodule(s) failed with errors." diff --git a/.github/workflows/assets/lib.sh b/.github/workflows/assets/lib.sh index 5f1fc38..86ec110 100644 --- a/.github/workflows/assets/lib.sh +++ b/.github/workflows/assets/lib.sh @@ -264,7 +264,7 @@ finalize_translations_repo() { # consumed by trigger_weblate (translation.sh). # # SUBMODULE_FATAL (indexed array) -# Submodule names that returned fatal (exit 2) from process_one_submodule. +# Submodule names that returned fatal (exit 2) from add_one_submodule or sync_one_submodule. # # OPEN_PR_SKIP (indexed array) # Submodule names skipped due to an open translation PR (start-translation local). @@ -328,7 +328,8 @@ record_submodule_fatal() { SUBMODULE_FATAL+=("$sub_name") } -# Summary bucket globals; filled by process_one_submodule before print_submodule_processing_summary. +# Summary bucket globals; filled by sync_one_submodule before print_submodule_processing_summary. +# add-submodules uses init_add_submodule_summary_buckets for REPO_EXISTS_SKIP. init_submodule_summary_buckets() { META_MISSING=() NO_DOC_PATHS=() diff --git a/.github/workflows/assets/translation.sh b/.github/workflows/assets/translation.sh index d017163..1771622 100644 --- a/.github/workflows/assets/translation.sh +++ b/.github/workflows/assets/translation.sh @@ -73,7 +73,8 @@ process_local_branch() { return 0 } -process_one_submodule() { +# Sync one existing MODULE_ORG mirror: mirror master and/or local branches. +sync_one_submodule() { local sub_name="$1" doc_paths local phase="${START_PHASE:-}" diff --git a/.github/workflows/start-translation.yml b/.github/workflows/start-translation.yml index cdc9523..0522528 100644 --- a/.github/workflows/start-translation.yml +++ b/.github/workflows/start-translation.yml @@ -163,7 +163,7 @@ jobs: for i in "${!submodule_names[@]}"; do sub="${submodule_names[$i]}" echo "[$(( i + 1 ))/$total] $sub ..." >&2 - if process_one_submodule "$sub"; then + if sync_one_submodule "$sub"; then record_submodule_update "$sub" || true else rc=$? @@ -174,7 +174,7 @@ jobs: fi done - # Buckets filled by process_one_submodule. + # Buckets filled by sync_one_submodule. print_submodule_processing_summary [[ $submodule_fatal -gt 0 ]] && \ phase_err "$submodule_fatal submodule(s) failed with errors." @@ -268,7 +268,7 @@ jobs: echo "Lang code: $LANG_CODE" >&2 - # Read by process_one_submodule in translation.sh (sourced above). + # Read by sync_one_submodule in translation.sh (sourced above). # shellcheck disable=SC2034 lang_codes_arr=("$LANG_CODE") init_add_or_update_lang "$LANG_CODE" @@ -300,7 +300,7 @@ jobs: for i in "${!submodule_names[@]}"; do sub="${submodule_names[$i]}" echo "[$(( i + 1 ))/$total] $sub ..." >&2 - if process_one_submodule "$sub"; then + if sync_one_submodule "$sub"; then record_submodule_update "$sub" || true else rc=$? @@ -311,7 +311,7 @@ jobs: fi done - # Buckets filled by process_one_submodule. + # Buckets filled by sync_one_submodule. print_submodule_processing_summary [[ $submodule_fatal -gt 0 ]] && \ phase_err "$submodule_fatal submodule(s) failed with errors." diff --git a/tests/test_process_submodule.bats b/tests/test_process_submodule.bats index cca35c2..009d096 100644 --- a/tests/test_process_submodule.bats +++ b/tests/test_process_submodule.bats @@ -63,11 +63,11 @@ install_algorithm_process_fixtures() { } } -@test "process_one_submodule: returns 2 when mirror repo missing" { +@test "sync_one_submodule: returns 2 when mirror repo missing" { export MOCK_REPO_VIEW_EXIT=1 set +e - process_one_submodule "missing-lib" + sync_one_submodule "missing-lib" status=$? set -e @@ -75,11 +75,11 @@ install_algorithm_process_fixtures() { [[ " ${ORG_REPO_MISSING[*]} " == *" missing-lib "* ]] } -@test "process_one_submodule: returns 1 when metadata has no doc paths" { +@test "sync_one_submodule: returns 1 when metadata has no doc paths" { export MOCK_LIBRARIES_FIXTURE="$FIXTURES_DIR/libraries-empty.json" set +e - process_one_submodule "algorithm" + sync_one_submodule "algorithm" status=$? set -e @@ -87,11 +87,11 @@ install_algorithm_process_fixtures() { [[ " ${NO_DOC_PATHS[*]} " == *" algorithm "* ]] } -@test "process_one_submodule: returns 0 on success path" { +@test "sync_one_submodule: returns 0 on success path" { install_algorithm_process_fixtures set +e - process_one_submodule "algorithm" + sync_one_submodule "algorithm" status=$? set -e @@ -100,11 +100,11 @@ install_algorithm_process_fixtures() { [ "${#CLONE_URLS[@]}" -eq 2 ] } -@test "process_one_submodule: returns 2 for invalid START_PHASE" { +@test "sync_one_submodule: returns 2 for invalid START_PHASE" { export START_PHASE=mirror set +e - err=$(process_one_submodule "algorithm" 2>&1 >/dev/null) + err=$(sync_one_submodule "algorithm" 2>&1 >/dev/null) status=$? set -e @@ -112,12 +112,12 @@ install_algorithm_process_fixtures() { [[ "$err" == *"invalid START_PHASE='mirror'"* ]] } -@test "process_one_submodule: START_PHASE=mirrors returns after mirror sync" { +@test "sync_one_submodule: START_PHASE=mirrors returns after mirror sync" { export START_PHASE=mirrors install_algorithm_process_fixtures set +e - process_one_submodule "algorithm" + sync_one_submodule "algorithm" status=$? set -e @@ -128,12 +128,12 @@ install_algorithm_process_fixtures() { [[ -z "${add_or_update[en]:-}" ]] } -@test "process_one_submodule: START_PHASE=local skips upstream clone" { +@test "sync_one_submodule: START_PHASE=local skips upstream clone" { export START_PHASE=local install_algorithm_process_fixtures set +e - process_one_submodule "algorithm" + sync_one_submodule "algorithm" status=$? set -e From d34e471372bda426be30ac6ba0433aef56c4f64a Mon Sep 17 00:00:00 2001 From: whisper67265 Date: Mon, 29 Jun 2026 11:40:04 -0600 Subject: [PATCH 2/4] security: pin actions/checkout by SHA in add-submodules.yml --- .github/dependabot.yml | 10 ++++++++++ .github/workflows/add-submodules.yml | 2 +- .github/workflows/assets/create-tag.yml | 2 +- .github/workflows/lint.yml | 4 ++-- .github/workflows/start-translation.yml | 6 +++--- .github/workflows/sync-translation.yml | 4 ++-- 6 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f6faee6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/add-submodules.yml b/.github/workflows/add-submodules.yml index 5c47680..fac83e0 100644 --- a/.github/workflows/add-submodules.yml +++ b/.github/workflows/add-submodules.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.SYNC_TOKEN }} diff --git a/.github/workflows/assets/create-tag.yml b/.github/workflows/assets/create-tag.yml index 5073b65..2367c73 100644 --- a/.github/workflows/assets/create-tag.yml +++ b/.github/workflows/assets/create-tag.yml @@ -48,7 +48,7 @@ jobs: echo "tagname=$TAGNAME" >> "$GITHUB_OUTPUT" - name: Checkout local-{lang_code} branch with tags - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8328259..7bb91f4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false @@ -22,7 +22,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false diff --git a/.github/workflows/start-translation.yml b/.github/workflows/start-translation.yml index 0522528..907cd43 100644 --- a/.github/workflows/start-translation.yml +++ b/.github/workflows/start-translation.yml @@ -42,7 +42,7 @@ jobs: json: ${{ steps.langs.outputs.json }} steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.SYNC_TOKEN }} persist-credentials: false @@ -74,7 +74,7 @@ jobs: cancel-in-progress: false steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.SYNC_TOKEN }} persist-credentials: false @@ -211,7 +211,7 @@ jobs: cancel-in-progress: false steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.SYNC_TOKEN }} persist-credentials: false diff --git a/.github/workflows/sync-translation.yml b/.github/workflows/sync-translation.yml index 1ce7291..d43fa72 100644 --- a/.github/workflows/sync-translation.yml +++ b/.github/workflows/sync-translation.yml @@ -33,7 +33,7 @@ jobs: json: ${{ steps.branches.outputs.json }} steps: - name: Checkout repository (master) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.SYNC_TOKEN }} persist-credentials: false @@ -91,7 +91,7 @@ jobs: cancel-in-progress: false steps: - name: Checkout repository (master) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.SYNC_TOKEN }} fetch-depth: 0 From c9ef00cb5a03663ce50ad5f45f68438713594c23 Mon Sep 17 00:00:00 2001 From: whisper67265 Date: Mon, 29 Jun 2026 13:44:38 -0600 Subject: [PATCH 3/4] fix the first review comments --- .github/workflows/add-submodules.yml | 74 +--------- .github/workflows/assets/add_submodules.sh | 67 +++++++++ scripts/lint.sh | 1 + tests/helpers/common.bash | 13 ++ tests/helpers/git_fixtures.bash | 14 ++ tests/helpers/mock_gh.bash | 13 ++ tests/test_add_one_submodule.bats | 131 ++++++++++++++++++ ...dule.bats => test_sync_one_submodule.bats} | 0 8 files changed, 244 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/assets/add_submodules.sh create mode 100644 tests/test_add_one_submodule.bats rename tests/{test_process_submodule.bats => test_sync_one_submodule.bats} (100%) diff --git a/.github/workflows/add-submodules.yml b/.github/workflows/add-submodules.yml index fac83e0..203cb69 100644 --- a/.github/workflows/add-submodules.yml +++ b/.github/workflows/add-submodules.yml @@ -45,6 +45,8 @@ jobs: source "$GITHUB_WORKSPACE/.github/workflows/assets/env.sh" # shellcheck source=assets/lib.sh source "$GITHUB_WORKSPACE/.github/workflows/assets/lib.sh" + # shellcheck source=assets/add_submodules.sh + source "$GITHUB_WORKSPACE/.github/workflows/assets/add_submodules.sh" init_translation_state init_add_submodule_summary_buckets @@ -64,79 +66,13 @@ jobs: # without embedding tokens in remote URLs. gh auth setup-git - # LIBS_REF: workflow env - # shellcheck disable=SC2153 + # libs_ref and boost_org are read by add_submodules.sh (sourced). + # shellcheck disable=SC2153,SC2034 libs_ref="${LIBS_REF:?}" - # BOOST_ORG: env.sh - # shellcheck disable=SC2153 + # shellcheck disable=SC2153,SC2034 boost_org="${BOOST_ORG:?}" end_phase - # ── Organization repo helpers ──────────────────────────────────────── - - create_repo() { - gh repo create "$1/$2" --public > /dev/null \ - || { echo "Create repo $1/$2 failed" >&2; return 1; } - } - - set_default_branch() { - gh api --method PATCH "repos/$1/$2" -f "default_branch=$3" \ - || { phase_err "set default branch to $3 failed for $1/$2."; return 1; } - } - - create_new_repo_and_push() { - local org="$1" sub_name="$2" sub_clone="$3" repo_url="$4" libs_ref="$5" - create_repo "$org" "$sub_name" || return 2 - git -C "$sub_clone" init || return 2 - set_git_bot_config "$sub_clone" - git -C "$sub_clone" add -A || return 2 - git -C "$sub_clone" commit -m "Create the original documentation of $libs_ref" || return 2 - git -C "$sub_clone" branch -M "$MASTER_BRANCH" || return 2 - git -C "$sub_clone" remote remove origin 2>/dev/null || true - git -C "$sub_clone" remote add origin "$repo_url" || return 2 - git -C "$sub_clone" push -u origin "$MASTER_BRANCH" || return 2 - git -C "$sub_clone" push origin "$MASTER_BRANCH" || return 2 - for lang_code in "${lang_codes_arr[@]}"; do - local local_br="${LOCAL_BRANCH_PREFIX}${lang_code}" - git -C "$sub_clone" checkout -B "$local_br" "$MASTER_BRANCH" || return 2 - add_create_tag_workflow "$sub_clone" || return 2 - git -C "$sub_clone" push -u origin "$local_br" || return 2 - done - set_default_branch "$org" "$sub_name" "$MASTER_BRANCH" || return 2 - } - - # ── Per-submodule processing ────────────────────────────────────────── - - # Create one MODULE_ORG mirror repo from a boostorg lib (add-submodules only). - add_one_submodule() { - local sub_name="$1" doc_paths - - if repo_exists "$MODULE_ORG" "$sub_name"; then - REPO_EXISTS_SKIP+=("$sub_name") - echo " Skipping: $MODULE_ORG/$sub_name already exists." >&2; return 1 - fi - - doc_paths=$(get_doc_paths "$sub_name" "$libs_ref") || { - META_MISSING+=("$sub_name") - echo " No libraries.json." >&2; return 2 - } - [[ -z "$doc_paths" ]] && { - NO_DOC_PATHS+=("$sub_name") - echo " No doc paths in metadata, skipping." >&2; return 1 - } - - local sub_clone="$BOOST_WORK/$sub_name" - clone_repo "https://github.com/${boost_org}/${sub_name}.git" \ - "$libs_ref" "$sub_clone" || { echo " Clone failed." >&2; return 2; } - - local -a paths_arr - mapfile -t paths_arr <<< "$doc_paths" - prune_to_doc_only "$sub_clone" "${paths_arr[@]}" - - local org_repo_url="https://github.com/${MODULE_ORG}/${sub_name}.git" - create_new_repo_and_push "$MODULE_ORG" "$sub_name" "$sub_clone" "$org_repo_url" "$libs_ref" - } - # ── Main ───────────────────────────────────────────────────────────── begin_phase "$PHASE_ENSURE_BRANCHES" "Ensure local branches in translations repo" diff --git a/.github/workflows/assets/add_submodules.sh b/.github/workflows/assets/add_submodules.sh new file mode 100644 index 0000000..3679c9c --- /dev/null +++ b/.github/workflows/assets/add_submodules.sh @@ -0,0 +1,67 @@ +# shellcheck shell=bash +# add-submodules orchestration helpers. +# Source after env.sh and lib.sh. Requires globals: +# MODULE_ORG, MASTER_BRANCH, BOOST_ORG, BOOST_WORK, libs_ref, boost_org, +# lang_codes_arr, REPO_EXISTS_SKIP, META_MISSING, NO_DOC_PATHS, GITHUB_WORKSPACE. +# shellcheck disable=SC2034,SC2154 + +create_repo() { + gh repo create "$1/$2" --public > /dev/null \ + || { echo "Create repo $1/$2 failed" >&2; return 1; } +} + +set_default_branch() { + gh api --method PATCH "repos/$1/$2" -f "default_branch=$3" \ + || { phase_err "set default branch to $3 failed for $1/$2."; return 1; } +} + +create_new_repo_and_push() { + local org="$1" sub_name="$2" sub_clone="$3" repo_url="$4" libs_ref="$5" + create_repo "$org" "$sub_name" || return 2 + git -C "$sub_clone" init || return 2 + set_git_bot_config "$sub_clone" + git -C "$sub_clone" add -A || return 2 + git -C "$sub_clone" commit -m "Create the original documentation of $libs_ref" || return 2 + git -C "$sub_clone" branch -M "$MASTER_BRANCH" || return 2 + git -C "$sub_clone" remote remove origin 2>/dev/null || true + git -C "$sub_clone" remote add origin "$repo_url" || return 2 + git -C "$sub_clone" push -u origin "$MASTER_BRANCH" || return 2 + git -C "$sub_clone" push origin "$MASTER_BRANCH" || return 2 + for lang_code in "${lang_codes_arr[@]}"; do + local local_br="${LOCAL_BRANCH_PREFIX}${lang_code}" + git -C "$sub_clone" checkout -B "$local_br" "$MASTER_BRANCH" || return 2 + add_create_tag_workflow "$sub_clone" || return 2 + git -C "$sub_clone" push -u origin "$local_br" || return 2 + done + set_default_branch "$org" "$sub_name" "$MASTER_BRANCH" || return 2 +} + +# Create one MODULE_ORG mirror repo from a boostorg lib (add-submodules only). +add_one_submodule() { + local sub_name="$1" doc_paths + + if repo_exists "$MODULE_ORG" "$sub_name"; then + REPO_EXISTS_SKIP+=("$sub_name") + echo " Skipping: $MODULE_ORG/$sub_name already exists." >&2; return 1 + fi + + doc_paths=$(get_doc_paths "$sub_name" "$libs_ref") || { + META_MISSING+=("$sub_name") + echo " No libraries.json." >&2; return 2 + } + [[ -z "$doc_paths" ]] && { + NO_DOC_PATHS+=("$sub_name") + echo " No doc paths in metadata, skipping." >&2; return 1 + } + + local sub_clone="$BOOST_WORK/$sub_name" + clone_repo "https://github.com/${boost_org}/${sub_name}.git" \ + "$libs_ref" "$sub_clone" || { echo " Clone failed." >&2; return 2; } + + local -a paths_arr + mapfile -t paths_arr <<< "$doc_paths" + prune_to_doc_only "$sub_clone" "${paths_arr[@]}" + + local org_repo_url="https://github.com/${MODULE_ORG}/${sub_name}.git" + create_new_repo_and_push "$MODULE_ORG" "$sub_name" "$sub_clone" "$org_repo_url" "$libs_ref" +} diff --git a/scripts/lint.sh b/scripts/lint.sh index 0938cad..2d808e1 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -170,6 +170,7 @@ echo "lint: actionlint ${ACTIONLINT_VERSION} ($("$ACTIONLINT_BIN" -version | hea .github/workflows/assets/env.sh \ .github/workflows/assets/lib.sh \ .github/workflows/assets/translation.sh \ + .github/workflows/assets/add_submodules.sh \ scripts/*.sh \ tests/helpers/*.bash diff --git a/tests/helpers/common.bash b/tests/helpers/common.bash index e5ed2b7..20a21cd 100644 --- a/tests/helpers/common.bash +++ b/tests/helpers/common.bash @@ -25,6 +25,13 @@ load_translation() { source "$ASSETS_DIR/translation.sh" } +load_add_submodules() { + load_lib + export GITHUB_WORKSPACE="$REPO_ROOT" + # shellcheck source=/dev/null + source "$ASSETS_DIR/add_submodules.sh" +} + # Run a function and capture its exit code (works under set -e in callers). run_fn() { local errexit_was_on=0 @@ -54,7 +61,13 @@ init_process_globals() { # Fixed values so tests behave the same locally and in CI (GITHUB_REPOSITORY varies). MODULE_ORG="testorg" BOOST_ORG="boostorg" + boost_org="$BOOST_ORG" libs_ref="develop" lang_codes_arr=("en") init_add_or_update_lang "en" } + +init_add_submodule_globals() { + init_process_globals + init_add_submodule_summary_buckets +} diff --git a/tests/helpers/git_fixtures.bash b/tests/helpers/git_fixtures.bash index eb93e49..074fffc 100644 --- a/tests/helpers/git_fixtures.bash +++ b/tests/helpers/git_fixtures.bash @@ -4,6 +4,7 @@ init_git_fixture_root() { GIT_FIXTURE_ROOT="$(mktemp -d)" + export GIT_FIXTURE_ROOT } cleanup_git_fixture_root() { @@ -55,3 +56,16 @@ create_prune_fixture_dir() { echo "wf" >"$dir/.github/workflows/ci.yml" echo "other" >"$dir/minmax/other/data.txt" } + +# Rewrite https://github.com/ pushes to local bare remotes under GIT_FIXTURE_ROOT/remotes/. +configure_github_url_rewrite() { + GITHUB_URL_REWRITE_KEY="url.file://${GIT_FIXTURE_ROOT}/remotes/.insteadOf" + git config --global "$GITHUB_URL_REWRITE_KEY" "https://github.com/" +} + +cleanup_github_url_rewrite() { + if [[ -n "${GITHUB_URL_REWRITE_KEY:-}" ]]; then + git config --global --unset-all "$GITHUB_URL_REWRITE_KEY" 2>/dev/null || true + unset GITHUB_URL_REWRITE_KEY + fi +} diff --git a/tests/helpers/mock_gh.bash b/tests/helpers/mock_gh.bash index 85ca83a..3470a85 100644 --- a/tests/helpers/mock_gh.bash +++ b/tests/helpers/mock_gh.bash @@ -19,8 +19,21 @@ case "$cmd" in if [[ "${1:-}" == "view" ]]; then exit "${MOCK_REPO_VIEW_EXIT:-0}" fi + if [[ "${1:-}" == "create" ]]; then + shift || true + spec="${1:-}" + if [[ -n "${GIT_FIXTURE_ROOT:-}" && -n "$spec" ]]; then + bare="${GIT_FIXTURE_ROOT}/remotes/${spec}.git" + mkdir -p "$(dirname "$bare")" + git init --bare "$bare" >/dev/null 2>&1 + fi + exit 0 + fi ;; api) + if [[ "${1:-}" == "--method" && "${2:-}" == "PATCH" ]]; then + exit 0 + fi api_url="${*}" if [[ "$api_url" == *libraries.json* ]]; then if [[ -n "${MOCK_LIBRARIES_FIXTURE:-}" && -f "$MOCK_LIBRARIES_FIXTURE" ]]; then diff --git a/tests/test_add_one_submodule.bats b/tests/test_add_one_submodule.bats new file mode 100644 index 0000000..9266632 --- /dev/null +++ b/tests/test_add_one_submodule.bats @@ -0,0 +1,131 @@ +#!/usr/bin/env bats + +setup() { + # shellcheck source=tests/helpers/common.bash + source "$BATS_TEST_DIRNAME/helpers/common.bash" + # shellcheck source=tests/helpers/mock_gh.bash + source "$BATS_TEST_DIRNAME/helpers/mock_gh.bash" + # shellcheck source=tests/helpers/git_fixtures.bash + source "$BATS_TEST_DIRNAME/helpers/git_fixtures.bash" + load_add_submodules + init_git_fixture_root + install_mock_gh + reset_mock_gh + init_add_submodule_globals + + WORK_DIR="$(mktemp -d)" + BOOST_WORK="$WORK_DIR/boost" + mkdir -p "$BOOST_WORK" +} + +teardown() { + cleanup_github_url_rewrite + restore_mock_gh + cleanup_git_fixture_root + rm -rf "${WORK_DIR:-}" + unset CLONE_URLS +} + +# Boost bare remote and a clone_repo stub that records URLs. +install_algorithm_add_fixtures() { + libs_ref="$MASTER_BRANCH" + boost_org="$BOOST_ORG" + export MOCK_LIBRARIES_FIXTURE="$FIXTURES_DIR/libraries-single.json" + export MOCK_REPO_VIEW_EXIT=1 + + create_bare_remote_with_clone "boost-algorithm" + boost_bare="$BARE_REMOTE" + boost_work="$WORK_REPO" + mkdir -p "$boost_work/doc" + echo "doc content" >"$boost_work/doc/page.adoc" + git -C "$boost_work" add doc/ + git -C "$boost_work" commit -m "add doc" + git -C "$boost_work" push origin "$MASTER_BRANCH" + + configure_github_url_rewrite + + CLONE_URLS=() + clone_repo() { + local url="$1" branch="$2" dest="$3" keep="${4:-}" + CLONE_URLS+=("$url") + case "$url" in + *"${BOOST_ORG}/algorithm"*) + mkdir -p "$dest" + git clone --branch "$branch" "$boost_bare" "$dest" + [[ "$keep" == "keep" ]] || rm -rf "$dest/.git" + ;; + *) + echo "unexpected clone url: $url" >&2 + return 1 + ;; + esac + } +} + +@test "add_one_submodule: returns 1 when mirror repo already exists" { + export MOCK_REPO_VIEW_EXIT=0 + + set +e + add_one_submodule "algorithm" + status=$? + set -e + + [ "$status" -eq 1 ] + [[ " ${REPO_EXISTS_SKIP[*]} " == *" algorithm "* ]] +} + +@test "add_one_submodule: returns 2 when metadata is missing" { + export MOCK_REPO_VIEW_EXIT=1 + export MOCK_GH_API_EXIT=1 + + set +e + add_one_submodule "algorithm" + status=$? + set -e + + [ "$status" -eq 2 ] + [[ " ${META_MISSING[*]} " == *" algorithm "* ]] +} + +@test "add_one_submodule: returns 1 when metadata has no doc paths" { + export MOCK_REPO_VIEW_EXIT=1 + export MOCK_LIBRARIES_FIXTURE="$FIXTURES_DIR/libraries-empty.json" + + set +e + add_one_submodule "algorithm" + status=$? + set -e + + [ "$status" -eq 1 ] + [[ " ${NO_DOC_PATHS[*]} " == *" algorithm "* ]] +} + +@test "add_one_submodule: returns 2 when clone fails" { + export MOCK_REPO_VIEW_EXIT=1 + export MOCK_LIBRARIES_FIXTURE="$FIXTURES_DIR/libraries-single.json" + + set +e + add_one_submodule "algorithm" + status=$? + set -e + + [ "$status" -eq 2 ] +} + +@test "add_one_submodule: returns 0 on success path" { + install_algorithm_add_fixtures + + set +e + add_one_submodule "algorithm" + status=$? + set -e + + [ "$status" -eq 0 ] + [ "${#CLONE_URLS[@]}" -eq 1 ] + [[ "${CLONE_URLS[0]}" == *"${BOOST_ORG}/algorithm"* ]] + + mirror_bare="$GIT_FIXTURE_ROOT/remotes/${MODULE_ORG}/algorithm.git" + [ -d "$mirror_bare" ] + git -C "$mirror_bare" show-ref --verify --quiet "refs/heads/$MASTER_BRANCH" + git -C "$mirror_bare" show-ref --verify --quiet "refs/heads/${LOCAL_BRANCH_PREFIX}en" +} diff --git a/tests/test_process_submodule.bats b/tests/test_sync_one_submodule.bats similarity index 100% rename from tests/test_process_submodule.bats rename to tests/test_sync_one_submodule.bats From 3967597b640d4e58b8645627ebc4b53501b0849b Mon Sep 17 00:00:00 2001 From: whisper67265 Date: Mon, 29 Jun 2026 13:59:27 -0600 Subject: [PATCH 4/4] fix the coderabbitai review comments --- .github/workflows/assets/add_submodules.sh | 9 +++++---- tests/helpers/mock_gh.bash | 17 +++++++++++++++-- tests/test_add_one_submodule.bats | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/.github/workflows/assets/add_submodules.sh b/.github/workflows/assets/add_submodules.sh index 3679c9c..ba53afa 100644 --- a/.github/workflows/assets/add_submodules.sh +++ b/.github/workflows/assets/add_submodules.sh @@ -7,19 +7,19 @@ create_repo() { gh repo create "$1/$2" --public > /dev/null \ - || { echo "Create repo $1/$2 failed" >&2; return 1; } + || { echo "Create repo $1/$2 failed" >&2; return 2; } } set_default_branch() { gh api --method PATCH "repos/$1/$2" -f "default_branch=$3" \ - || { phase_err "set default branch to $3 failed for $1/$2."; return 1; } + || { phase_err "set default branch to $3 failed for $1/$2."; return 2; } } create_new_repo_and_push() { local org="$1" sub_name="$2" sub_clone="$3" repo_url="$4" libs_ref="$5" create_repo "$org" "$sub_name" || return 2 git -C "$sub_clone" init || return 2 - set_git_bot_config "$sub_clone" + set_git_bot_config "$sub_clone" || return 2 git -C "$sub_clone" add -A || return 2 git -C "$sub_clone" commit -m "Create the original documentation of $libs_ref" || return 2 git -C "$sub_clone" branch -M "$MASTER_BRANCH" || return 2 @@ -63,5 +63,6 @@ add_one_submodule() { prune_to_doc_only "$sub_clone" "${paths_arr[@]}" local org_repo_url="https://github.com/${MODULE_ORG}/${sub_name}.git" - create_new_repo_and_push "$MODULE_ORG" "$sub_name" "$sub_clone" "$org_repo_url" "$libs_ref" + create_new_repo_and_push "$MODULE_ORG" "$sub_name" "$sub_clone" "$org_repo_url" "$libs_ref" \ + || return 2 } diff --git a/tests/helpers/mock_gh.bash b/tests/helpers/mock_gh.bash index 3470a85..c9d7052 100644 --- a/tests/helpers/mock_gh.bash +++ b/tests/helpers/mock_gh.bash @@ -22,6 +22,9 @@ case "$cmd" in if [[ "${1:-}" == "create" ]]; then shift || true spec="${1:-}" + if [[ "${MOCK_REPO_CREATE_EXIT:-0}" -ne 0 ]]; then + exit "${MOCK_REPO_CREATE_EXIT}" + fi if [[ -n "${GIT_FIXTURE_ROOT:-}" && -n "$spec" ]]; then bare="${GIT_FIXTURE_ROOT}/remotes/${spec}.git" mkdir -p "$(dirname "$bare")" @@ -32,7 +35,10 @@ case "$cmd" in ;; api) if [[ "${1:-}" == "--method" && "${2:-}" == "PATCH" ]]; then - exit 0 + if [[ -n "${MOCK_GH_PATCH_LOG:-}" ]]; then + printf '%s\n' "$*" >> "$MOCK_GH_PATCH_LOG" + fi + exit "${MOCK_GH_PATCH_EXIT:-0}" fi api_url="${*}" if [[ "$api_url" == *libraries.json* ]]; then @@ -72,12 +78,19 @@ restore_mock_gh() { fi unset MOCK_GH_DIR _ORIG_PATH unset MOCK_REPO_VIEW_EXIT MOCK_LIBRARIES_FIXTURE MOCK_GH_API_EXIT + unset MOCK_REPO_CREATE_EXIT MOCK_GH_PATCH_LOG MOCK_GH_PATCH_EXIT unset MOCK_PR_LIST_STDOUT MOCK_PR_LIST_STDERR MOCK_PR_LIST_EXIT } reset_mock_gh() { export MOCK_REPO_VIEW_EXIT=0 - unset MOCK_LIBRARIES_FIXTURE MOCK_GH_API_EXIT + export MOCK_GH_PATCH_EXIT=0 + unset MOCK_LIBRARIES_FIXTURE MOCK_GH_API_EXIT MOCK_REPO_CREATE_EXIT unset MOCK_PR_LIST_STDOUT MOCK_PR_LIST_STDERR MOCK_PR_LIST_EXIT export MOCK_PR_LIST_EXIT=0 + if [[ -n "${MOCK_GH_DIR:-}" ]]; then + MOCK_GH_PATCH_LOG="$MOCK_GH_DIR/gh-patch.log" + : > "$MOCK_GH_PATCH_LOG" + export MOCK_GH_PATCH_LOG + fi } diff --git a/tests/test_add_one_submodule.bats b/tests/test_add_one_submodule.bats index 9266632..af1643c 100644 --- a/tests/test_add_one_submodule.bats +++ b/tests/test_add_one_submodule.bats @@ -112,6 +112,18 @@ install_algorithm_add_fixtures() { [ "$status" -eq 2 ] } +@test "add_one_submodule: returns 2 when repo create fails" { + install_algorithm_add_fixtures + export MOCK_REPO_CREATE_EXIT=1 + + set +e + add_one_submodule "algorithm" + status=$? + set -e + + [ "$status" -eq 2 ] +} + @test "add_one_submodule: returns 0 on success path" { install_algorithm_add_fixtures @@ -128,4 +140,8 @@ install_algorithm_add_fixtures() { [ -d "$mirror_bare" ] git -C "$mirror_bare" show-ref --verify --quiet "refs/heads/$MASTER_BRANCH" git -C "$mirror_bare" show-ref --verify --quiet "refs/heads/${LOCAL_BRANCH_PREFIX}en" + + [ -f "$MOCK_GH_PATCH_LOG" ] + grep -Fq "repos/${MODULE_ORG}/algorithm" "$MOCK_GH_PATCH_LOG" + grep -Fq "default_branch=${MASTER_BRANCH}" "$MOCK_GH_PATCH_LOG" }