Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions .github/scripts/addZoektSyncChangelogEntry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env bash

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename the script to use camelCase.

Rename this file to addZoektSyncChangelogEntry.sh. Update the workflow invocation that uses the current path.

As per coding guidelines, files should use camelCase names starting with a lowercase letter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/add-zoekt-sync-changelog-entry.sh at line 1, Rename the
add-zoekt-sync-changelog-entry.sh script to addZoektSyncChangelogEntry.sh, then
update every workflow or repository reference invoking the old path to use the
new camelCase filename.

Source: Coding guidelines


set -euo pipefail

pr_number="${1:-}"
changelog="${CHANGELOG_PATH:-CHANGELOG.md}"

if [[ ! "$pr_number" =~ ^[1-9][0-9]*$ ]]; then
echo "Expected a pull request number, got: $pr_number" >&2
exit 1
fi

pr_url="https://github.com/sourcebot-dev/sourcebot/pull/$pr_number"
entry="- Updated the bundled Zoekt version. [#$pr_number]($pr_url)"

if grep -Fq "$pr_url" "$changelog"; then
echo "Changelog already links to Sourcebot PR #$pr_number."
exit 0
fi

changelog_directory=$(dirname "$changelog")
changelog_basename=$(basename "$changelog")
temporary_file=$(mktemp "$changelog_directory/.${changelog_basename}.XXXXXX")
trap 'rm -f "$temporary_file"' EXIT

if changelog_mode=$(stat -f '%Lp' "$changelog" 2> /dev/null); then
:
else
changelog_mode=$(stat -c '%a' "$changelog")
fi

awk -v entry="$entry" '
function flush_changed_section( last, i) {
last = changed_line_count
while (last > 0 && changed_lines[last] == "") {
last--
}
for (i = 1; i <= last; i++) {
print changed_lines[i]
}
print entry
print ""
changed_line_count = 0
}

$0 == "## [Unreleased]" {
in_unreleased = 1
last_was_blank = 0
print
next
}

in_unreleased && $0 == "### Changed" {
found_changed = 1
in_changed = 1
print
next
}

in_changed && /^##(#)? / {
flush_changed_section()
in_changed = 0
inserted = 1
if ($0 ~ /^## /) {
in_unreleased = 0
}
print
next
}

in_changed {
changed_lines[++changed_line_count] = $0
next
}

in_unreleased && !found_changed && /^### (Deprecated|Removed|Fixed|Security)$/ {
print "### Changed"
print entry
print ""
found_changed = 1
inserted = 1
print
next
}

in_unreleased && !found_changed && /^## / {
print "### Changed"
print entry
print ""
found_changed = 1
inserted = 1
in_unreleased = 0
print
next
}

{
last_was_blank = ($0 == "")
print
}

END {
if (in_changed) {
flush_changed_section()
inserted = 1
} else if (in_unreleased && !found_changed) {
if (!last_was_blank) {
print ""
}
print "### Changed"
print entry
found_changed = 1
inserted = 1
}
if (!found_changed || !inserted) {
exit 2
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
' "$changelog" > "$temporary_file" || {
echo "Unable to add an Unreleased Changed entry to $changelog." >&2
exit 1
}

chmod "$changelog_mode" "$temporary_file"
mv "$temporary_file" "$changelog"
trap - EXIT
echo "Added the changelog entry for Sourcebot PR #$pr_number."
198 changes: 198 additions & 0 deletions .github/scripts/testZoektSync.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#!/usr/bin/env bash

set -euo pipefail

script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
repository_root=$(cd "$script_dir/../.." && pwd)
update_script="$script_dir/updateZoektSubmodule.sh"
changelog_script="$script_dir/addZoektSyncChangelogEntry.sh"
workflow="$repository_root/.github/workflows/syncZoekt.yml"

fail() {
echo "FAIL: $*" >&2
exit 1
}

assert_contains() {
local file=$1
local expected=$2
local description=$3

if ! grep -Fq -- "$expected" "$file"; then
fail "$description"
fi
}

assert_equals() {
local actual=$1
local expected=$2
local description=$3

if [[ "$actual" != "$expected" ]]; then
fail "$description (expected $expected, got $actual)"
fi
}

test_root=$(mktemp -d)
trap 'rm -rf "$test_root"' EXIT

zoekt_remote="$test_root/zoekt.git"
zoekt_upstream="$test_root/zoekt-upstream"
sourcebot_test="$test_root/sourcebot"

git init --quiet --bare --initial-branch=main "$zoekt_remote"
git init --quiet --initial-branch=main "$zoekt_upstream"
git -C "$zoekt_upstream" config user.name "Zoekt Sync Test"
git -C "$zoekt_upstream" config user.email "zoekt-sync-test@example.com"
git -C "$zoekt_upstream" remote add origin "$zoekt_remote"

printf '%s\n' first > "$zoekt_upstream/version.txt"
git -C "$zoekt_upstream" add version.txt
git -C "$zoekt_upstream" commit --quiet -m "first"
first_sha=$(git -C "$zoekt_upstream" rev-parse HEAD)
git -C "$zoekt_upstream" push --quiet --set-upstream origin main

printf '%s\n' second > "$zoekt_upstream/version.txt"
git -C "$zoekt_upstream" commit --quiet -am "second"
second_sha=$(git -C "$zoekt_upstream" rev-parse HEAD)
git -C "$zoekt_upstream" push --quiet origin main

git init --quiet --initial-branch=main "$sourcebot_test"
git -C "$sourcebot_test" config user.name "Zoekt Sync Test"
git -C "$sourcebot_test" config user.email "zoekt-sync-test@example.com"
git -C "$sourcebot_test" -c protocol.file.allow=always \
submodule add --quiet "$zoekt_remote" vendor/zoekt
git -C "$sourcebot_test/vendor/zoekt" checkout --quiet --detach "$first_sha"
git -C "$sourcebot_test" add vendor/zoekt
git -C "$sourcebot_test" commit --quiet -m "pin first Zoekt commit"

(
cd "$sourcebot_test"
"$update_script" "$second_sha"
)
staged_sha=$(git -C "$sourcebot_test" rev-parse :vendor/zoekt)
assert_equals "$staged_sha" "$second_sha" \
"the updater should stage the requested main-branch commit"
git -C "$sourcebot_test" commit --quiet -m "advance Zoekt"

(
cd "$sourcebot_test"
"$update_script" "$first_sha"
)
current_sha=$(git -C "$sourcebot_test" rev-parse HEAD:vendor/zoekt)
assert_equals "$current_sha" "$second_sha" \
"a stale event must not downgrade the Zoekt gitlink"
git -C "$sourcebot_test" diff --quiet || \
fail "a stale event should leave the worktree unchanged"
git -C "$sourcebot_test" diff --cached --quiet || \
fail "a stale event should leave the index unchanged"

git -C "$zoekt_upstream" switch --quiet --detach "$first_sha"
git -C "$zoekt_upstream" switch --quiet -c divergent
printf '%s\n' divergent > "$zoekt_upstream/version.txt"
git -C "$zoekt_upstream" commit --quiet -am "divergent"
divergent_sha=$(git -C "$zoekt_upstream" rev-parse HEAD)
git -C "$zoekt_upstream" push --quiet origin divergent
git -C "$sourcebot_test/vendor/zoekt" fetch --quiet origin divergent

if (
cd "$sourcebot_test"
"$update_script" "$divergent_sha"
) 2> "$test_root/divergent-error.txt"; then
fail "the updater should reject a commit outside origin/main"
fi
assert_contains "$test_root/divergent-error.txt" \
"is not reachable from origin/main" \
"the non-main error should explain the rejected target"

git -C "$sourcebot_test/vendor/zoekt" checkout --quiet --detach "$divergent_sha"
git -C "$sourcebot_test" add vendor/zoekt
git -C "$sourcebot_test" commit --quiet -m "pin divergent Zoekt commit"

if (
cd "$sourcebot_test"
"$update_script" "$second_sha"
) 2> "$test_root/divergent-history-error.txt"; then
fail "the updater should reject divergent current and target commits"
fi
assert_contains "$test_root/divergent-history-error.txt" \
"Refusing to move Zoekt between divergent histories" \
"the divergent-history error should explain the rejected update"

if (
cd "$sourcebot_test"
"$update_script" deadbeef
) 2> "$test_root/invalid-error.txt"; then
fail "the updater should reject abbreviated commit SHAs"
fi
assert_contains "$test_root/invalid-error.txt" \
"Expected a full lowercase Zoekt commit SHA" \
"the invalid-SHA error should explain the required format"

changelog_fixture="$test_root/CHANGELOG.md"
cat > "$changelog_fixture" <<'EOF'
# Changelog

## [Unreleased]

### Added
- Added something.

### Removed
- Removed something.

### Fixed
- Fixed something.

## [1.0.0]
EOF

chmod 640 "$changelog_fixture"
CHANGELOG_PATH="$changelog_fixture" "$changelog_script" 42
assert_contains "$changelog_fixture" \
"### Changed" \
"the changelog helper should create the Changed section when absent"
assert_contains "$changelog_fixture" \
"- Updated the bundled Zoekt version. [#42](https://github.com/sourcebot-dev/sourcebot/pull/42)" \
"the changelog helper should add the generated PR link"
CHANGELOG_PATH="$changelog_fixture" "$changelog_script" 42
entry_count=$(grep -Fc "sourcebot/pull/42" "$changelog_fixture")
assert_equals "$entry_count" 1 \
"the changelog helper should not duplicate an existing PR entry"
if changelog_mode=$(stat -f '%Lp' "$changelog_fixture" 2> /dev/null); then
:
else
changelog_mode=$(stat -c '%a' "$changelog_fixture")
fi
assert_equals "$changelog_mode" 640 \
"the changelog helper should preserve the target file mode"

empty_changelog_fixture="$test_root/EMPTY_CHANGELOG.md"
cat > "$empty_changelog_fixture" <<'EOF'
# Changelog

## [Unreleased]
EOF
CHANGELOG_PATH="$empty_changelog_fixture" "$changelog_script" 43
assert_contains "$empty_changelog_fixture" \
"- Updated the bundled Zoekt version. [#43](https://github.com/sourcebot-dev/sourcebot/pull/43)" \
"the changelog helper should handle an empty Unreleased section at EOF"
Comment on lines +170 to +179

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the generated-PR changelog contract.

The fixture does not verify that the helper creates ### Changed. The workflow checks do not verify that it invokes addZoektSyncChangelogEntry.sh. A regression can produce a malformed changelog or omit the generated PR entry without failing this test.

  • .github/scripts/testZoektSync.sh#L170-L179: Assert that ### Changed is present after processing the empty Unreleased section.
  • .github/scripts/testZoektSync.sh#L188-L195: Assert that syncZoekt.yml invokes .github/scripts/addZoektSyncChangelogEntry.sh "$pr_number".
Proposed test coverage
 CHANGELOG_PATH="$empty_changelog_fixture" "$changelog_script" 43
+assert_contains "$empty_changelog_fixture" \
+  "### Changed" \
+  "the changelog helper should create a Changed section"
 assert_contains "$empty_changelog_fixture" \
   "- Updated the bundled Zoekt version. [`#43`](https://github.com/sourcebot-dev/sourcebot/pull/43)" \
   "the changelog helper should handle an empty Unreleased section at EOF"
+
+assert_contains "$workflow" \
+  '.github/scripts/addZoektSyncChangelogEntry.sh "$pr_number"' \
+  "the receiver should add the generated pull request to the changelog"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
empty_changelog_fixture="$test_root/EMPTY_CHANGELOG.md"
cat > "$empty_changelog_fixture" <<'EOF'
# Changelog
## [Unreleased]
EOF
CHANGELOG_PATH="$empty_changelog_fixture" "$changelog_script" 43
assert_contains "$empty_changelog_fixture" \
"- Updated the bundled Zoekt version. [#43](https://github.com/sourcebot-dev/sourcebot/pull/43)" \
"the changelog helper should handle an empty Unreleased section at EOF"
empty_changelog_fixture="$test_root/EMPTY_CHANGELOG.md"
cat > "$empty_changelog_fixture" <<'EOF'
# Changelog
## [Unreleased]
EOF
CHANGELOG_PATH="$empty_changelog_fixture" "$changelog_script" 43
assert_contains "$empty_changelog_fixture" \
"### Changed" \
"the changelog helper should create a Changed section"
assert_contains "$empty_changelog_fixture" \
"- Updated the bundled Zoekt version. [`#43`](https://github.com/sourcebot-dev/sourcebot/pull/43)" \
"the changelog helper should handle an empty Unreleased section at EOF"
Suggested change
empty_changelog_fixture="$test_root/EMPTY_CHANGELOG.md"
cat > "$empty_changelog_fixture" <<'EOF'
# Changelog
## [Unreleased]
EOF
CHANGELOG_PATH="$empty_changelog_fixture" "$changelog_script" 43
assert_contains "$empty_changelog_fixture" \
"- Updated the bundled Zoekt version. [#43](https://github.com/sourcebot-dev/sourcebot/pull/43)" \
"the changelog helper should handle an empty Unreleased section at EOF"
assert_contains "$workflow" \
'permission-pull-requests: write' \
"the receiver should restrict its app token to required permissions"
assert_contains "$workflow" \
'--force-with-lease=' \
"the receiver should protect updates to its stable automation branch"
assert_contains "$workflow" \
'.github/scripts/updateZoektSubmodule.sh "$ZOEKT_SHA"' \
"the receiver should update the Zoekt submodule"
assert_contains "$workflow" \
'.github/scripts/addZoektSyncChangelogEntry.sh "$pr_number"' \
"the receiver should add the generated pull request to the changelog"
📍 Affects 1 file
  • .github/scripts/testZoektSync.sh#L170-L179 (this comment)
  • .github/scripts/testZoektSync.sh#L188-L195
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/testZoektSync.sh around lines 170 - 179, Strengthen the
tests in .github/scripts/testZoektSync.sh at lines 170-179 by asserting that
processing an empty Unreleased section creates a “### Changed” heading and
retains the generated Zoekt PR entry. At lines 188-195, update the workflow
verification to assert that syncZoekt.yml invokes
.github/scripts/addZoektSyncChangelogEntry.sh with "$pr_number".


ruby -e 'require "yaml"; YAML.parse_file(ARGV.fetch(0))' "$workflow"
assert_contains "$workflow" \
"types: [zoekt-pr-merged]" \
"the receiver should handle only Zoekt merge dispatches"
assert_contains "$workflow" \
'uses: actions/create-github-app-token@v2' \
"the receiver should use a GitHub App token"
assert_contains "$workflow" \
'permission-pull-requests: write' \
"the receiver should restrict its app token to required permissions"
assert_contains "$workflow" \
'--force-with-lease=' \
"the receiver should protect updates to its stable automation branch"
assert_contains "$workflow" \
'.github/scripts/updateZoektSubmodule.sh "$ZOEKT_SHA"' \
"the receiver should validate and stage the requested Zoekt commit"

echo "All Zoekt sync tests passed."
48 changes: 48 additions & 0 deletions .github/scripts/updateZoektSubmodule.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Rename the new files to camelCase.

Both new paths use kebab-case. Rename the files and update all workflow and test references.

  • .github/scripts/update-zoekt-submodule.sh#L1-L1: Rename to updateZoektSubmodule.sh.
  • .github/workflows/sync-zoekt.yml#L1-L1: Rename to syncZoekt.yml.

As per coding guidelines, **/*: “Files should use camelCase names starting with a lowercase letter.”

📍 Affects 2 files
  • .github/scripts/update-zoekt-submodule.sh#L1-L1 (this comment)
  • .github/workflows/sync-zoekt.yml#L1-L1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/update-zoekt-submodule.sh at line 1, Rename
.github/scripts/update-zoekt-submodule.sh to updateZoektSubmodule.sh and
.github/workflows/sync-zoekt.yml to syncZoekt.yml, then update every workflow
and test reference to both paths; no direct content change is required beyond
references.

Source: Coding guidelines


set -euo pipefail

target_sha="${1:-}"
submodule_path="${ZOEKT_SUBMODULE_PATH:-vendor/zoekt}"
remote="${ZOEKT_REMOTE:-origin}"
branch="${ZOEKT_BRANCH:-main}"

if [[ ! "$target_sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "Expected a full lowercase Zoekt commit SHA, got: $target_sha" >&2
exit 1
fi

repository_root=$(git rev-parse --show-toplevel)
cd "$repository_root"

if ! current_sha=$(git rev-parse "HEAD:$submodule_path"); then
echo "Unable to read the $submodule_path gitlink from HEAD." >&2
exit 1
fi

git -C "$submodule_path" fetch --quiet "$remote" "$branch"
remote_head=$(git -C "$submodule_path" rev-parse FETCH_HEAD)

if ! git -C "$submodule_path" cat-file -e "$target_sha^{commit}"; then
echo "Zoekt commit $target_sha does not exist." >&2
exit 1
fi

if ! git -C "$submodule_path" merge-base --is-ancestor "$target_sha" "$remote_head"; then
echo "Zoekt commit $target_sha is not reachable from $remote/$branch." >&2
exit 1
fi

if git -C "$submodule_path" merge-base --is-ancestor "$target_sha" "$current_sha"; then
echo "Zoekt is already at or ahead of $target_sha."
exit 0
fi

if ! git -C "$submodule_path" merge-base --is-ancestor "$current_sha" "$target_sha"; then
echo "Refusing to move Zoekt between divergent histories: $current_sha -> $target_sha." >&2
exit 1
fi

git -C "$submodule_path" checkout --quiet --detach "$target_sha"
git add -- "$submodule_path"
echo "Staged Zoekt update: $current_sha -> $target_sha"
Loading
Loading