Skip to content

Commit 8c9dc57

Browse files
feat: automate Zoekt submodule sync
1 parent 08835dc commit 8c9dc57

5 files changed

Lines changed: 510 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
pr_number="${1:-}"
6+
changelog="${CHANGELOG_PATH:-CHANGELOG.md}"
7+
8+
if [[ ! "$pr_number" =~ ^[1-9][0-9]*$ ]]; then
9+
echo "Expected a pull request number, got: $pr_number" >&2
10+
exit 1
11+
fi
12+
13+
pr_url="https://github.com/sourcebot-dev/sourcebot/pull/$pr_number"
14+
entry="- Updated the bundled Zoekt version. [#$pr_number]($pr_url)"
15+
16+
if grep -Fq "$pr_url" "$changelog"; then
17+
echo "Changelog already links to Sourcebot PR #$pr_number."
18+
exit 0
19+
fi
20+
21+
temporary_file=$(mktemp)
22+
trap 'rm -f "$temporary_file"' EXIT
23+
24+
awk -v entry="$entry" '
25+
function flush_changed_section( last, i) {
26+
last = changed_line_count
27+
while (last > 0 && changed_lines[last] == "") {
28+
last--
29+
}
30+
for (i = 1; i <= last; i++) {
31+
print changed_lines[i]
32+
}
33+
print entry
34+
print ""
35+
changed_line_count = 0
36+
}
37+
38+
$0 == "## [Unreleased]" {
39+
in_unreleased = 1
40+
print
41+
next
42+
}
43+
44+
in_unreleased && $0 == "### Changed" {
45+
found_changed = 1
46+
in_changed = 1
47+
print
48+
next
49+
}
50+
51+
in_changed && /^##(#)? / {
52+
flush_changed_section()
53+
in_changed = 0
54+
inserted = 1
55+
if ($0 ~ /^## /) {
56+
in_unreleased = 0
57+
}
58+
print
59+
next
60+
}
61+
62+
in_changed {
63+
changed_lines[++changed_line_count] = $0
64+
next
65+
}
66+
67+
in_unreleased && !found_changed && /^### (Deprecated|Removed|Fixed|Security)$/ {
68+
print "### Changed"
69+
print entry
70+
print ""
71+
found_changed = 1
72+
inserted = 1
73+
print
74+
next
75+
}
76+
77+
in_unreleased && !found_changed && /^## / {
78+
print "### Changed"
79+
print entry
80+
print ""
81+
found_changed = 1
82+
inserted = 1
83+
in_unreleased = 0
84+
print
85+
next
86+
}
87+
88+
{ print }
89+
90+
END {
91+
if (in_changed) {
92+
flush_changed_section()
93+
inserted = 1
94+
}
95+
if (!found_changed || !inserted) {
96+
exit 2
97+
}
98+
}
99+
' "$changelog" > "$temporary_file" || {
100+
echo "Unable to add an Unreleased Changed entry to $changelog." >&2
101+
exit 1
102+
}
103+
104+
mv "$temporary_file" "$changelog"
105+
trap - EXIT
106+
echo "Added the changelog entry for Sourcebot PR #$pr_number."

.github/scripts/test-zoekt-sync.sh

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
6+
repository_root=$(cd "$script_dir/../.." && pwd)
7+
update_script="$script_dir/update-zoekt-submodule.sh"
8+
changelog_script="$script_dir/add-zoekt-sync-changelog-entry.sh"
9+
workflow="$repository_root/.github/workflows/sync-zoekt.yml"
10+
11+
fail() {
12+
echo "FAIL: $*" >&2
13+
exit 1
14+
}
15+
16+
assert_contains() {
17+
local file=$1
18+
local expected=$2
19+
local description=$3
20+
21+
if ! grep -Fq -- "$expected" "$file"; then
22+
fail "$description"
23+
fi
24+
}
25+
26+
assert_equals() {
27+
local actual=$1
28+
local expected=$2
29+
local description=$3
30+
31+
if [[ "$actual" != "$expected" ]]; then
32+
fail "$description (expected $expected, got $actual)"
33+
fi
34+
}
35+
36+
test_root=$(mktemp -d)
37+
trap 'rm -rf "$test_root"' EXIT
38+
39+
zoekt_remote="$test_root/zoekt.git"
40+
zoekt_upstream="$test_root/zoekt-upstream"
41+
sourcebot_test="$test_root/sourcebot"
42+
43+
git init --quiet --bare "$zoekt_remote"
44+
git init --quiet --initial-branch=main "$zoekt_upstream"
45+
git -C "$zoekt_upstream" config user.name "Zoekt Sync Test"
46+
git -C "$zoekt_upstream" config user.email "zoekt-sync-test@example.com"
47+
git -C "$zoekt_upstream" remote add origin "$zoekt_remote"
48+
49+
printf '%s\n' first > "$zoekt_upstream/version.txt"
50+
git -C "$zoekt_upstream" add version.txt
51+
git -C "$zoekt_upstream" commit --quiet -m "first"
52+
first_sha=$(git -C "$zoekt_upstream" rev-parse HEAD)
53+
git -C "$zoekt_upstream" push --quiet --set-upstream origin main
54+
55+
printf '%s\n' second > "$zoekt_upstream/version.txt"
56+
git -C "$zoekt_upstream" commit --quiet -am "second"
57+
second_sha=$(git -C "$zoekt_upstream" rev-parse HEAD)
58+
git -C "$zoekt_upstream" push --quiet origin main
59+
60+
git init --quiet --initial-branch=main "$sourcebot_test"
61+
git -C "$sourcebot_test" config user.name "Zoekt Sync Test"
62+
git -C "$sourcebot_test" config user.email "zoekt-sync-test@example.com"
63+
git -C "$sourcebot_test" -c protocol.file.allow=always \
64+
submodule add --quiet "$zoekt_remote" vendor/zoekt
65+
git -C "$sourcebot_test/vendor/zoekt" checkout --quiet --detach "$first_sha"
66+
git -C "$sourcebot_test" add vendor/zoekt
67+
git -C "$sourcebot_test" commit --quiet -m "pin first Zoekt commit"
68+
69+
(
70+
cd "$sourcebot_test"
71+
"$update_script" "$second_sha"
72+
)
73+
staged_sha=$(git -C "$sourcebot_test" rev-parse :vendor/zoekt)
74+
assert_equals "$staged_sha" "$second_sha" \
75+
"the updater should stage the requested main-branch commit"
76+
git -C "$sourcebot_test" commit --quiet -m "advance Zoekt"
77+
78+
(
79+
cd "$sourcebot_test"
80+
"$update_script" "$first_sha"
81+
)
82+
current_sha=$(git -C "$sourcebot_test" rev-parse HEAD:vendor/zoekt)
83+
assert_equals "$current_sha" "$second_sha" \
84+
"a stale event must not downgrade the Zoekt gitlink"
85+
git -C "$sourcebot_test" diff --quiet || \
86+
fail "a stale event should leave the worktree unchanged"
87+
88+
git -C "$zoekt_upstream" switch --quiet --detach "$first_sha"
89+
git -C "$zoekt_upstream" switch --quiet -c divergent
90+
printf '%s\n' divergent > "$zoekt_upstream/version.txt"
91+
git -C "$zoekt_upstream" commit --quiet -am "divergent"
92+
divergent_sha=$(git -C "$zoekt_upstream" rev-parse HEAD)
93+
git -C "$zoekt_upstream" push --quiet origin divergent
94+
git -C "$sourcebot_test/vendor/zoekt" fetch --quiet origin divergent
95+
96+
if (
97+
cd "$sourcebot_test"
98+
"$update_script" "$divergent_sha"
99+
) 2> "$test_root/divergent-error.txt"; then
100+
fail "the updater should reject a commit outside origin/main"
101+
fi
102+
assert_contains "$test_root/divergent-error.txt" \
103+
"is not reachable from origin/main" \
104+
"the divergent-history error should explain the rejected target"
105+
106+
if (
107+
cd "$sourcebot_test"
108+
"$update_script" deadbeef
109+
) 2> "$test_root/invalid-error.txt"; then
110+
fail "the updater should reject abbreviated commit SHAs"
111+
fi
112+
assert_contains "$test_root/invalid-error.txt" \
113+
"Expected a full lowercase Zoekt commit SHA" \
114+
"the invalid-SHA error should explain the required format"
115+
116+
changelog_fixture="$test_root/CHANGELOG.md"
117+
cat > "$changelog_fixture" <<'EOF'
118+
# Changelog
119+
120+
## [Unreleased]
121+
122+
### Added
123+
- Added something.
124+
125+
### Removed
126+
- Removed something.
127+
128+
### Fixed
129+
- Fixed something.
130+
131+
## [1.0.0]
132+
EOF
133+
134+
CHANGELOG_PATH="$changelog_fixture" "$changelog_script" 42
135+
assert_contains "$changelog_fixture" \
136+
"### Changed" \
137+
"the changelog helper should create the Changed section when absent"
138+
assert_contains "$changelog_fixture" \
139+
"- Updated the bundled Zoekt version. [#42](https://github.com/sourcebot-dev/sourcebot/pull/42)" \
140+
"the changelog helper should add the generated PR link"
141+
CHANGELOG_PATH="$changelog_fixture" "$changelog_script" 42
142+
entry_count=$(grep -Fc "sourcebot/pull/42" "$changelog_fixture")
143+
assert_equals "$entry_count" 1 \
144+
"the changelog helper should not duplicate an existing PR entry"
145+
146+
ruby -e 'require "yaml"; YAML.parse_file(ARGV.fetch(0))' "$workflow"
147+
assert_contains "$workflow" \
148+
"types: [zoekt-pr-merged]" \
149+
"the receiver should handle only Zoekt merge dispatches"
150+
assert_contains "$workflow" \
151+
'uses: actions/create-github-app-token@v2' \
152+
"the receiver should use a GitHub App token"
153+
assert_contains "$workflow" \
154+
'--force-with-lease=' \
155+
"the receiver should protect updates to its stable automation branch"
156+
assert_contains "$workflow" \
157+
'.github/scripts/update-zoekt-submodule.sh "$ZOEKT_SHA"' \
158+
"the receiver should validate and stage the requested Zoekt commit"
159+
160+
echo "All Zoekt sync tests passed."
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
target_sha="${1:-}"
6+
submodule_path="${ZOEKT_SUBMODULE_PATH:-vendor/zoekt}"
7+
remote="${ZOEKT_REMOTE:-origin}"
8+
branch="${ZOEKT_BRANCH:-main}"
9+
10+
if [[ ! "$target_sha" =~ ^[0-9a-f]{40}$ ]]; then
11+
echo "Expected a full lowercase Zoekt commit SHA, got: $target_sha" >&2
12+
exit 1
13+
fi
14+
15+
repository_root=$(git rev-parse --show-toplevel)
16+
cd "$repository_root"
17+
18+
if ! current_sha=$(git rev-parse "HEAD:$submodule_path"); then
19+
echo "Unable to read the $submodule_path gitlink from HEAD." >&2
20+
exit 1
21+
fi
22+
23+
git -C "$submodule_path" fetch --quiet "$remote" "$branch"
24+
remote_head=$(git -C "$submodule_path" rev-parse FETCH_HEAD)
25+
26+
if ! git -C "$submodule_path" cat-file -e "$target_sha^{commit}"; then
27+
echo "Zoekt commit $target_sha does not exist." >&2
28+
exit 1
29+
fi
30+
31+
if ! git -C "$submodule_path" merge-base --is-ancestor "$target_sha" "$remote_head"; then
32+
echo "Zoekt commit $target_sha is not reachable from $remote/$branch." >&2
33+
exit 1
34+
fi
35+
36+
if git -C "$submodule_path" merge-base --is-ancestor "$target_sha" "$current_sha"; then
37+
echo "Zoekt is already at or ahead of $target_sha."
38+
exit 0
39+
fi
40+
41+
if ! git -C "$submodule_path" merge-base --is-ancestor "$current_sha" "$target_sha"; then
42+
echo "Refusing to move Zoekt between divergent histories: $current_sha -> $target_sha." >&2
43+
exit 1
44+
fi
45+
46+
git -C "$submodule_path" checkout --quiet --detach "$target_sha"
47+
git add -- "$submodule_path"
48+
echo "Staged Zoekt update: $current_sha -> $target_sha"

0 commit comments

Comments
 (0)