From 227210d54ef655ce0bda992a5a823f8a51d848c8 Mon Sep 17 00:00:00 2001 From: whisper67265 Date: Tue, 30 Jun 2026 10:10:27 -0600 Subject: [PATCH 1/3] test depth: cover force-push-with-lease rejection path in finalize_translations_local --- .github/workflows/assets/lib.sh | 15 +++++ tests/helpers/git_fixtures.bash | 107 ++++++++++++++++++++++++++++++++ tests/test_lib.bats | 99 +++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) diff --git a/.github/workflows/assets/lib.sh b/.github/workflows/assets/lib.sh index 86ec110..a280f82 100644 --- a/.github/workflows/assets/lib.sh +++ b/.github/workflows/assets/lib.sh @@ -198,8 +198,13 @@ update_translations_submodule() { fi } +git_push_supports_force_with_lease() { + git push -h 2>&1 | grep -qF 'force-with-lease' +} + commit_and_push_translations_branch() { local dir="$1" branch="$2" libs_ref="$3" force="${4:-false}" + local push_rc remote_sha git -C "$dir" status --short if git -C "$dir" diff --cached --quiet; then echo " No staged submodule changes on $branch; skipping commit." >&2 @@ -207,7 +212,17 @@ commit_and_push_translations_branch() { git -C "$dir" commit -m "Update libs submodules to $libs_ref" fi if [[ "$force" == "true" ]]; then + if ! git_push_supports_force_with_lease; then + phase_err "git push --force-with-lease is not supported; upgrade Git to 2.8+" + return 1 + fi git -C "$dir" push --force-with-lease origin "$branch" + push_rc=$? + if [[ $push_rc -ne 0 ]]; then + remote_sha=$(git -C "$dir" ls-remote --heads origin "$branch" | awk '{print $1}') + phase_err "force-with-lease push rejected for branch $branch (remote HEAD=${remote_sha:-unknown}); remote may have advanced concurrently — re-run after fetch or resolve manually." + return "$push_rc" + fi else git -C "$dir" push origin "$branch" fi diff --git a/tests/helpers/git_fixtures.bash b/tests/helpers/git_fixtures.bash index 074fffc..525b474 100644 --- a/tests/helpers/git_fixtures.bash +++ b/tests/helpers/git_fixtures.bash @@ -69,3 +69,110 @@ cleanup_github_url_rewrite() { unset GITHUB_URL_REWRITE_KEY fi } + +# Push a new commit onto a remote branch from a side clone (concurrent writer simulation). +push_commit_to_remote_branch() { + local bare="$1" branch="$2" message="${3:-concurrent advance}" + local tmp + tmp="$(mktemp -d)" + git clone "$bare" "$tmp" + git -C "$tmp" config user.email "test@test.local" + git -C "$tmp" config user.name "Test" + git -C "$tmp" checkout "$branch" + echo "$message" >>"$tmp/README" + git -C "$tmp" add README + git -C "$tmp" commit -m "$message" + git -C "$tmp" push origin "$branch" + rm -rf "$tmp" +} + +# Prepend a git wrapper to PATH that advances remote branch before push --force-with-lease. +install_git_push_pre_hook() { + local bare="$1" branch="$2" message="${3:-concurrent advance}" + local wrapper_dir="$GIT_FIXTURE_ROOT/bin" + REAL_GIT="$(command -v git)" + export REAL_GIT + export GIT_HOOK_BARE_REMOTE="$bare" GIT_HOOK_BRANCH="$branch" GIT_HOOK_MESSAGE="$message" + mkdir -p "$wrapper_dir" + cat >"$wrapper_dir/git" <<'EOF' +#!/usr/bin/env bash +if [[ "${1:-}" == "-C" && "${3:-}" == "push" && "$*" == *"--force-with-lease"* ]]; then + tmp="$(mktemp -d)" + "$REAL_GIT" clone "$GIT_HOOK_BARE_REMOTE" "$tmp" + "$REAL_GIT" -C "$tmp" config user.email "test@test.local" + "$REAL_GIT" -C "$tmp" config user.name "Test" + "$REAL_GIT" -C "$tmp" checkout "$GIT_HOOK_BRANCH" + echo "$GIT_HOOK_MESSAGE" >>"$tmp/README" + "$REAL_GIT" -C "$tmp" add README + "$REAL_GIT" -C "$tmp" commit -m "$GIT_HOOK_MESSAGE" + "$REAL_GIT" -C "$tmp" push origin "$GIT_HOOK_BRANCH" + rm -rf "$tmp" +fi +exec "$REAL_GIT" "$@" +EOF + chmod +x "$wrapper_dir/git" + GIT_WRAPPER_DIR="$wrapper_dir" + export PATH="$wrapper_dir:$PATH" +} + +restore_git_push_pre_hook() { + if [[ -n "${GIT_WRAPPER_DIR:-}" ]]; then + PATH="${PATH#"$GIT_WRAPPER_DIR:"}" + export PATH + rm -rf "$GIT_WRAPPER_DIR" + unset GIT_WRAPPER_DIR REAL_GIT + unset GIT_HOOK_BARE_REMOTE GIT_HOOK_BRANCH GIT_HOOK_MESSAGE GIT_FETCH_COUNTER_FILE + fi +} + +# Prepend a git wrapper that hides --force-with-lease from push -h (simulates pre-2.8 git). +install_git_without_force_with_lease() { + local wrapper_dir="$GIT_FIXTURE_ROOT/bin" + REAL_GIT="$(command -v git)" + export REAL_GIT + mkdir -p "$wrapper_dir" + cat >"$wrapper_dir/git" <<'EOF' +#!/usr/bin/env bash +if [[ "${1:-}" == "push" && "${2:-}" == "-h" ]]; then + "$REAL_GIT" push -h 2>&1 | grep -v 'force-with-lease' + exit 0 +fi +exec "$REAL_GIT" "$@" +EOF + chmod +x "$wrapper_dir/git" + GIT_WRAPPER_DIR="$wrapper_dir" + export PATH="$wrapper_dir:$PATH" +} + +# Prepend a git wrapper that counts fetch invocations and optionally injects concurrent push. +install_git_fetch_counter() { + local counter_file="$1" + local bare="${2:-}" branch="${3:-}" message="${4:-concurrent advance}" + local wrapper_dir="$GIT_FIXTURE_ROOT/bin" + REAL_GIT="$(command -v git)" + export REAL_GIT GIT_FETCH_COUNTER_FILE="$counter_file" + export GIT_HOOK_BARE_REMOTE="$bare" GIT_HOOK_BRANCH="$branch" GIT_HOOK_MESSAGE="$message" + mkdir -p "$wrapper_dir" + cat >"$wrapper_dir/git" <<'EOF' +#!/usr/bin/env bash +if [[ "${1:-}" == "-C" && "${3:-}" == "fetch" ]]; then + echo 1 >>"$GIT_FETCH_COUNTER_FILE" +fi +if [[ "${1:-}" == "-C" && "${3:-}" == "push" && "$*" == *"--force-with-lease"* && -n "${GIT_HOOK_BARE_REMOTE:-}" ]]; then + tmp="$(mktemp -d)" + "$REAL_GIT" clone "$GIT_HOOK_BARE_REMOTE" "$tmp" + "$REAL_GIT" -C "$tmp" config user.email "test@test.local" + "$REAL_GIT" -C "$tmp" config user.name "Test" + "$REAL_GIT" -C "$tmp" checkout "$GIT_HOOK_BRANCH" + echo "$GIT_HOOK_MESSAGE" >>"$tmp/README" + "$REAL_GIT" -C "$tmp" add README + "$REAL_GIT" -C "$tmp" commit -m "$GIT_HOOK_MESSAGE" + "$REAL_GIT" -C "$tmp" push origin "$GIT_HOOK_BRANCH" + rm -rf "$tmp" +fi +exec "$REAL_GIT" "$@" +EOF + chmod +x "$wrapper_dir/git" + GIT_WRAPPER_DIR="$wrapper_dir" + export PATH="$wrapper_dir:$PATH" +} diff --git a/tests/test_lib.bats b/tests/test_lib.bats index 888eac9..22d1a9c 100644 --- a/tests/test_lib.bats +++ b/tests/test_lib.bats @@ -295,6 +295,105 @@ setup() { cleanup_git_fixture_root } +@test "finalize_translations_local: rejects stale force-with-lease push" { + # shellcheck source=tests/helpers/git_fixtures.bash + source "$BATS_TEST_DIRNAME/helpers/git_fixtures.bash" + init_git_fixture_root + create_bare_remote_with_clone "translations" + create_remote_branch "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" "$MASTER_BRANCH" + trans_dir="$GIT_FIXTURE_ROOT/translations-work" + git clone "$BARE_REMOTE" "$trans_dir" + set_git_bot_config "$trans_dir" + + UPDATES=("algorithm") + update_translations_submodule() { :; } + + install_git_push_pre_hook "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" "concurrent advance" + + local stderr_file="$BATS_TMPDIR/finalize-lease-stderr" + set +e + finalize_translations_local "$trans_dir" "develop" "en" 2>"$stderr_file" + rc=$? + set -e + + restore_git_push_pre_hook + + [ "$rc" -ne 0 ] + remote_sha=$(git ls-remote --heads "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" | awk '{print $1}') + grep -q "${LOCAL_BRANCH_PREFIX}en" "$stderr_file" + grep -q "$remote_sha" "$stderr_file" + grep -q "force-with-lease push rejected" "$stderr_file" + [ -z "$(git -C "$trans_dir" status --porcelain)" ] + [ ! -f "$trans_dir/.git/index.lock" ] + git --git-dir="$BARE_REMOTE" show -s --format=%s "$remote_sha" | grep -q "concurrent advance" + + cleanup_git_fixture_root +} + +@test "finalize_translations_local: fail-fast on lease rejection (no retry)" { + # shellcheck source=tests/helpers/git_fixtures.bash + source "$BATS_TEST_DIRNAME/helpers/git_fixtures.bash" + init_git_fixture_root + create_bare_remote_with_clone "translations" + create_remote_branch "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" "$MASTER_BRANCH" + trans_dir="$GIT_FIXTURE_ROOT/translations-work" + git clone "$BARE_REMOTE" "$trans_dir" + set_git_bot_config "$trans_dir" + + UPDATES=("algorithm") + update_translations_submodule() { :; } + + local fetch_counter="$BATS_TMPDIR/fetch-count" + : >"$fetch_counter" + install_git_fetch_counter "$fetch_counter" "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" "concurrent advance" + + remote_sha_before=$(git ls-remote --heads "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" | awk '{print $1}') + + set +e + finalize_translations_local "$trans_dir" "develop" "en" 2>/dev/null + rc=$? + set -e + + restore_git_push_pre_hook + + [ "$rc" -ne 0 ] + [ "$(wc -l <"$fetch_counter")" -eq 1 ] + remote_sha_after=$(git ls-remote --heads "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" | awk '{print $1}') + [ "$remote_sha_before" != "$remote_sha_after" ] + git --git-dir="$BARE_REMOTE" show -s --format=%s "$remote_sha_after" | grep -q "concurrent advance" + + cleanup_git_fixture_root +} + +@test "commit_and_push_translations_branch: clear error when force-with-lease unsupported" { + # shellcheck source=tests/helpers/git_fixtures.bash + source "$BATS_TEST_DIRNAME/helpers/git_fixtures.bash" + init_git_fixture_root + create_bare_remote_with_clone "translations" + create_remote_branch "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" "$MASTER_BRANCH" + trans_dir="$GIT_FIXTURE_ROOT/translations-work" + git clone "$BARE_REMOTE" "$trans_dir" + set_git_bot_config "$trans_dir" + git -C "$trans_dir" checkout "${LOCAL_BRANCH_PREFIX}en" + + install_git_without_force_with_lease + + local stderr_file="$BATS_TMPDIR/push-unsupported-stderr" + set +e + commit_and_push_translations_branch "$trans_dir" "${LOCAL_BRANCH_PREFIX}en" "develop" true \ + 2>"$stderr_file" + rc=$? + set -e + + restore_git_push_pre_hook + + [ "$rc" -ne 0 ] + grep -q "force-with-lease is not supported" "$stderr_file" + grep -q "upgrade Git to 2.8+" "$stderr_file" + + cleanup_git_fixture_root +} + @test "begin_phase and end_phase: emit group markers and track CURRENT_PHASE" { local out_file="$BATS_TMPDIR/phase.out" From 2c046ac18296f5fe8f9a975be500a902f433c6ff Mon Sep 17 00:00:00 2001 From: whisper67265 Date: Tue, 30 Jun 2026 11:04:32 -0600 Subject: [PATCH 2/3] concurrency group: add for add-submodules.yml master push --- .github/workflows/add-submodules.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/add-submodules.yml b/.github/workflows/add-submodules.yml index ba81a94..03a43c5 100644 --- a/.github/workflows/add-submodules.yml +++ b/.github/workflows/add-submodules.yml @@ -21,6 +21,10 @@ on: # Must match EVENT_ADD_SUBMODULES in assets/env.sh types: [add-submodules] +concurrency: + group: add-submodules-${{ github.ref }} + cancel-in-progress: true + jobs: add-submodules: runs-on: ubuntu-latest From d019229c262aa01a6fa7c2e4bd2f9b72ff37f18c Mon Sep 17 00:00:00 2001 From: whisper67265 Date: Tue, 30 Jun 2026 11:51:03 -0600 Subject: [PATCH 3/3] fix the coderabbitai review comments --- .github/workflows/assets/lib.sh | 9 +++++---- tests/test_lib.bats | 23 ++++++++--------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/assets/lib.sh b/.github/workflows/assets/lib.sh index a280f82..f9da4f2 100644 --- a/.github/workflows/assets/lib.sh +++ b/.github/workflows/assets/lib.sh @@ -213,12 +213,13 @@ commit_and_push_translations_branch() { fi if [[ "$force" == "true" ]]; then if ! git_push_supports_force_with_lease; then - phase_err "git push --force-with-lease is not supported; upgrade Git to 2.8+" + phase_err "git push --force-with-lease is not supported by this Git installation" return 1 fi - git -C "$dir" push --force-with-lease origin "$branch" - push_rc=$? - if [[ $push_rc -ne 0 ]]; then + if git -C "$dir" push --force-with-lease origin "$branch"; then + : + else + push_rc=$? remote_sha=$(git -C "$dir" ls-remote --heads origin "$branch" | awk '{print $1}') phase_err "force-with-lease push rejected for branch $branch (remote HEAD=${remote_sha:-unknown}); remote may have advanced concurrently — re-run after fetch or resolve manually." return "$push_rc" diff --git a/tests/test_lib.bats b/tests/test_lib.bats index 22d1a9c..a04afa3 100644 --- a/tests/test_lib.bats +++ b/tests/test_lib.bats @@ -6,6 +6,13 @@ setup() { load_lib } +teardown() { + # shellcheck source=tests/helpers/git_fixtures.bash + source "$BATS_TEST_DIRNAME/helpers/git_fixtures.bash" + restore_git_push_pre_hook + cleanup_git_fixture_root +} + @test "parse_list: empty input produces no output" { run parse_list "" [ "$status" -eq 0 ] @@ -242,8 +249,6 @@ setup() { finalize_translations_local "$trans_dir" "develop" "en" [ "${#SYNC_CALLS[@]}" -eq 1 ] [ "${SYNC_CALLS[0]}" = "branch=${LOCAL_BRANCH_PREFIX}en force=true" ] - - cleanup_git_fixture_root } @test "finalize_translations_repo: syncs master then each local branch" { @@ -316,8 +321,6 @@ setup() { rc=$? set -e - restore_git_push_pre_hook - [ "$rc" -ne 0 ] remote_sha=$(git ls-remote --heads "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" | awk '{print $1}') grep -q "${LOCAL_BRANCH_PREFIX}en" "$stderr_file" @@ -326,8 +329,6 @@ setup() { [ -z "$(git -C "$trans_dir" status --porcelain)" ] [ ! -f "$trans_dir/.git/index.lock" ] git --git-dir="$BARE_REMOTE" show -s --format=%s "$remote_sha" | grep -q "concurrent advance" - - cleanup_git_fixture_root } @test "finalize_translations_local: fail-fast on lease rejection (no retry)" { @@ -354,15 +355,11 @@ setup() { rc=$? set -e - restore_git_push_pre_hook - [ "$rc" -ne 0 ] [ "$(wc -l <"$fetch_counter")" -eq 1 ] remote_sha_after=$(git ls-remote --heads "$BARE_REMOTE" "${LOCAL_BRANCH_PREFIX}en" | awk '{print $1}') [ "$remote_sha_before" != "$remote_sha_after" ] git --git-dir="$BARE_REMOTE" show -s --format=%s "$remote_sha_after" | grep -q "concurrent advance" - - cleanup_git_fixture_root } @test "commit_and_push_translations_branch: clear error when force-with-lease unsupported" { @@ -385,13 +382,9 @@ setup() { rc=$? set -e - restore_git_push_pre_hook - [ "$rc" -ne 0 ] grep -q "force-with-lease is not supported" "$stderr_file" - grep -q "upgrade Git to 2.8+" "$stderr_file" - - cleanup_git_fixture_root + grep -q "not supported by this Git installation" "$stderr_file" } @test "begin_phase and end_phase: emit group markers and track CURRENT_PHASE" {