Skip to content

Commit 4349831

Browse files
Merge pull request #37 from vivster7/viv/fetch-merge-ref-fallback
Add retries when pulling the merge refspec
2 parents f04724b + 4e7833f commit 4349831

4 files changed

Lines changed: 122 additions & 14 deletions

File tree

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,32 @@ The cleanup removes `.git/config.worktree` and unsets `extensions.worktreeConfig
5656

5757
Enable verbose logging with bash execution tracing (`set -x`). This shows each command being executed and can help debug issues with ssh-keyscan, git operations, or other checkout problems. When enabled, you'll see detailed output including command arguments and any error messages from underlying tools.
5858

59+
#### `merge_ref_retry_attempts` (integer)
60+
61+
How many times to try fetching the GitHub pull-request merge ref (`refs/pull/<n>/merge`) when using merge-ref checkout (see `BUILDKITE_PULL_REQUEST_USING_MERGE_REFSPEC` below). Defaults to `3`. Between attempts the hook waits 2 seconds after the first failure and 5 seconds after later failures. Set to `0` to skip merge-ref fetch attempts and fall back immediately.
62+
5963
#### `post_checkout` (object)
6064

6165
Options that run after the sparse checkout completes, in the `post-checkout` hook.
6266

63-
##### `unshallow` ('true' or 'false')
67+
#### `unshallow` ('true' or 'false')
6468

6569
Convert the shallow clone into a full-depth clone by running `git fetch --unshallow origin` after checkout. This is useful when your build requires full git history (for example, changelog generation, `git log`, or `git blame`). If the repository is already unshallow, this step is skipped.
6670

71+
## Environment Variables
72+
73+
### BUILDKITE_PULL_REQUEST_USING_MERGE_REFSPEC
74+
When `BUILDKITE_PULL_REQUEST_USING_MERGE_REFSPEC=true`, the plugin will retry the
75+
GitHub merge ref checkout if it sees the known "missing merge ref" failure, up to
76+
`merge_ref_retry_attempts` times (default `3`). Between attempts it waits 2 seconds
77+
after the first failure and 5 seconds after subsequent failures. If the merge ref
78+
is still unavailable, it falls back to the normal non-merge-ref target
79+
(`BUILDKITE_BRANCH` when `BUILDKITE_COMMIT=HEAD`, otherwise `BUILDKITE_COMMIT`).
80+
81+
This retry logic only applies to the specific merge-ref-not-ready error. Other
82+
`git fetch` failures still fail immediately.
83+
84+
6785
## Example
6886

6987
Below is an example of using sparse-checkout plugin.

hooks/checkout

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ fi
3030
SKIP_SSH_KEYSCAN_OPTION="$(plugin_read_config SKIP_SSH_KEYSCAN "false")"
3131
CLEAN_CHECKOUT_OPTION="$(plugin_read_config CLEAN_CHECKOUT "false")"
3232

33+
MERGE_REF_RETRY_ATTEMPTS="$(plugin_read_config MERGE_REF_RETRY_ATTEMPTS "3")"
34+
3335
if [[ "${CLEAN_CHECKOUT_OPTION}" = "true" ]]; then
3436
log_warning "clean_checkout is enabled - this will destroy any local changes and reset the repository state"
3537
fi
@@ -98,27 +100,83 @@ fi
98100

99101
FETCH_FLAGS+=(--depth 1 origin)
100102

103+
FETCH_OUTPUT=""
104+
105+
is_missing_merge_ref_error() {
106+
local merge_ref="refs/pull/${BUILDKITE_PULL_REQUEST}/merge"
107+
[[ "${FETCH_OUTPUT}" == *"couldn't find remote ref ${merge_ref}"* ]] \
108+
|| [[ "${FETCH_OUTPUT}" == *"not our ref ${merge_ref}"* ]]
109+
}
110+
111+
# Try to fetch merge ref. Returns 0 on success, 1 if merge ref is missing (retry),
112+
# otherwise logs and exits with git's status.
113+
try_fetch_merge_ref() {
114+
local merge_ref="$1"
115+
if FETCH_OUTPUT=$(git fetch "${FETCH_FLAGS[@]}" "${merge_ref}" 2>&1); then
116+
[[ -n "${FETCH_OUTPUT}" ]] && printf '%s\n' "${FETCH_OUTPUT}"
117+
return 0
118+
else
119+
local status=$?
120+
[[ -n "${FETCH_OUTPUT}" ]] && printf '%s\n' "${FETCH_OUTPUT}" >&2
121+
if is_missing_merge_ref_error; then
122+
return 1
123+
else
124+
log_error "Failed to fetch merge ref for PR #${BUILDKITE_PULL_REQUEST} from origin"
125+
exit "${status}"
126+
fi
127+
fi
128+
}
129+
101130
# Determine if we should use the pull request merge refspec
102131
USE_MERGE_REFSPEC="false"
103132
if [[ "${BUILDKITE_PULL_REQUEST_USING_MERGE_REFSPEC:-false}" = "true" ]] \
104133
&& [[ -n "${BUILDKITE_PULL_REQUEST:-}" ]] \
105134
&& [[ "${BUILDKITE_PULL_REQUEST}" != "false" ]]; then
106135
USE_MERGE_REFSPEC="true"
107-
FETCH_FLAGS+=("refs/pull/${BUILDKITE_PULL_REQUEST}/merge")
108-
elif [[ ${BUILDKITE_COMMIT} = "HEAD" ]]; then
109-
FETCH_FLAGS+=("${BUILDKITE_BRANCH}")
110-
else
111-
FETCH_FLAGS+=("${BUILDKITE_COMMIT}")
112-
fi
136+
MERGE_REF="refs/pull/${BUILDKITE_PULL_REQUEST}/merge"
113137

114-
if [[ "${USE_MERGE_REFSPEC}" = "true" ]]; then
115138
log_info "Fetching merge ref for PR #${BUILDKITE_PULL_REQUEST} from origin"
116-
else
117-
log_info "Fetching ${BUILDKITE_COMMIT} from origin"
139+
140+
fetch_succeeded=false
141+
for ((attempt = 1; attempt <= MERGE_REF_RETRY_ATTEMPTS; attempt++)); do
142+
if try_fetch_merge_ref "${MERGE_REF}"; then
143+
fetch_succeeded=true
144+
break
145+
fi
146+
147+
if [[ "${attempt}" -lt "${MERGE_REF_RETRY_ATTEMPTS}" ]]; then
148+
if [[ "${attempt}" -eq 1 ]]; then
149+
log_warning "Merge ref ${MERGE_REF} was not available yet; retrying in 2s"
150+
sleep 2
151+
else
152+
log_warning "Merge ref ${MERGE_REF} was not available yet; retrying in 5s"
153+
sleep 5
154+
fi
155+
fi
156+
done
157+
158+
if [[ "${fetch_succeeded}" != "true" ]]; then
159+
if [[ "${BUILDKITE_COMMIT}" = "HEAD" ]]; then
160+
log_warning "Merge ref ${MERGE_REF} was still unavailable; falling back to branch ${BUILDKITE_BRANCH}"
161+
else
162+
log_warning "Merge ref ${MERGE_REF} was still unavailable; falling back to ${BUILDKITE_COMMIT}"
163+
fi
164+
USE_MERGE_REFSPEC="false"
165+
fi
118166
fi
119-
if ! git fetch "${FETCH_FLAGS[@]}"; then
120-
log_error "Failed to fetch ${BUILDKITE_COMMIT} from origin"
121-
exit 1
167+
168+
if [[ "${USE_MERGE_REFSPEC}" != "true" ]]; then
169+
if [[ ${BUILDKITE_COMMIT} = "HEAD" ]]; then
170+
FETCH_FLAGS+=("${BUILDKITE_BRANCH}")
171+
else
172+
FETCH_FLAGS+=("${BUILDKITE_COMMIT}")
173+
fi
174+
175+
log_info "Fetching ${BUILDKITE_COMMIT} from origin"
176+
if ! git fetch "${FETCH_FLAGS[@]}"; then
177+
log_error "Failed to fetch ${BUILDKITE_COMMIT} from origin"
178+
exit 1
179+
fi
122180
fi
123181
log_success "Fetch completed successfully"
124182

@@ -144,5 +202,4 @@ else
144202
exit 1
145203
fi
146204
fi
147-
148205
log_success "Sparse checkout completed successfully"

plugin.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ configuration:
1919
cleanup_sparse_state:
2020
type: boolean
2121
default: false
22+
merge_ref_retry_attempts:
23+
type: integer
24+
default: 3
2225
verbose:
2326
type: boolean
2427
default: false

tests/checkout.bats

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,36 @@ setup() {
185185
unstub git
186186
}
187187

188+
@test "Retries missing merge ref before succeeding" {
189+
export BUILDKITE_PULL_REQUEST_USING_MERGE_REFSPEC="true"
190+
export BUILDKITE_PULL_REQUEST="123"
191+
export BUILDKITE_COMMIT="abc123"
192+
193+
stub ssh-keyscan "* : echo 'keyscan'"
194+
stub sleep \
195+
"2 : true" \
196+
"5 : true"
197+
stub git \
198+
"clean * : echo 'git clean'" \
199+
"fetch --depth 1 origin refs/pull/123/merge : echo \"fatal: couldn't find remote ref refs/pull/123/merge\" >&2; exit 1" \
200+
"fetch --depth 1 origin refs/pull/123/merge : echo \"fatal: couldn't find remote ref refs/pull/123/merge\" >&2; exit 1" \
201+
"fetch --depth 1 origin refs/pull/123/merge : echo 'git fetch merge refspec'" \
202+
"sparse-checkout set * * : echo 'git sparse-checkout'" \
203+
"checkout FETCH_HEAD : echo 'checkout fetch_head'"
204+
205+
run "$PWD"/hooks/checkout
206+
207+
assert_success
208+
assert_output --partial 'retrying in 2s'
209+
assert_output --partial 'retrying in 5s'
210+
assert_output --partial 'git fetch merge refspec'
211+
assert_output --partial 'checkout fetch_head'
212+
213+
unstub sleep
214+
unstub ssh-keyscan
215+
unstub git
216+
}
217+
188218
@test "Does not use merge refspec when BUILDKITE_PULL_REQUEST is false" {
189219
export BUILDKITE_PULL_REQUEST_USING_MERGE_REFSPEC="true"
190220
export BUILDKITE_PULL_REQUEST="false"

0 commit comments

Comments
 (0)