Skip to content

Commit f29bfda

Browse files
committed
feat(ci): sync GitCode release assets
1 parent 9a8201c commit f29bfda

1 file changed

Lines changed: 370 additions & 0 deletions

File tree

.github/workflows/sync-gitcode.yml

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,41 @@ jobs:
174174
prerelease: $prerelease
175175
}' > gitcode-release.json
176176
177+
- name: Download GitHub release assets
178+
id: github_assets
179+
env:
180+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
181+
RELEASE_TAG: ${{ inputs.release_tag }}
182+
run: |
183+
set -euo pipefail
184+
185+
mkdir -p github-release-assets
186+
187+
asset_count="$(jq -r '.assets | length' github-release.json)"
188+
echo "count=$asset_count" >> "$GITHUB_OUTPUT"
189+
190+
if [ "$asset_count" = "0" ]; then
191+
echo "has_assets=false" >> "$GITHUB_OUTPUT"
192+
echo "GitHub release ${RELEASE_TAG} has no uploaded assets."
193+
exit 0
194+
fi
195+
196+
gh release download "$RELEASE_TAG" \
197+
--repo "${{ github.repository }}" \
198+
--dir github-release-assets
199+
200+
downloaded_count="$(find github-release-assets -maxdepth 1 -type f | wc -l | tr -d ' ')"
201+
echo "downloaded=$downloaded_count" >> "$GITHUB_OUTPUT"
202+
203+
if [ "$downloaded_count" = "0" ]; then
204+
echo "::error::GitHub release ${RELEASE_TAG} reports assets but none were downloaded."
205+
exit 1
206+
fi
207+
208+
echo "has_assets=true" >> "$GITHUB_OUTPUT"
209+
echo "Downloaded GitHub release assets:"
210+
find github-release-assets -maxdepth 1 -type f -printf '%f\n' | sort
211+
177212
- name: Create or update GitCode release
178213
env:
179214
GITCODE_API_TOKEN: ${{ secrets.GITCODE_API_TOKEN }}
@@ -225,3 +260,338 @@ jobs:
225260
- name: Print synced release summary
226261
run: |
227262
jq '{ tag_name, name, prerelease, html_url, url }' gitcode-release-result.json || cat gitcode-release-result.json
263+
264+
- name: Get GitCode release upload target
265+
if: steps.github_assets.outputs.has_assets == 'true'
266+
env:
267+
GITCODE_API_TOKEN: ${{ secrets.GITCODE_API_TOKEN }}
268+
GITCODE_OWNER: ${{ steps.gitcode_repo.outputs.owner }}
269+
GITCODE_REPO: ${{ steps.gitcode_repo.outputs.repo }}
270+
RELEASE_TAG: ${{ inputs.release_tag }}
271+
run: |
272+
set -euo pipefail
273+
274+
upload_api_url="https://api.gitcode.com/api/v5/repos/${GITCODE_OWNER}/${GITCODE_REPO}/releases/${RELEASE_TAG}/upload_url"
275+
276+
status_code="$(curl -sS -o gitcode-upload-url.json -w "%{http_code}" \
277+
-H "PRIVATE-TOKEN: ${GITCODE_API_TOKEN}" \
278+
"$upload_api_url")"
279+
280+
if [ "$status_code" != "200" ]; then
281+
echo "::error::Failed to get GitCode release upload target: HTTP ${status_code}"
282+
cat gitcode-upload-url.json
283+
exit 1
284+
fi
285+
286+
echo "GitCode upload target summary:"
287+
jq '
288+
if type == "string" then
289+
{
290+
upload_url: (split("?")[0])
291+
}
292+
else
293+
{
294+
method: (.method // .data.method // null),
295+
upload_url: ((.upload_url // .url // .data.upload_url // .data.url // "") | split("?")[0]),
296+
header_keys: ((.headers // .data.headers // {}) | keys),
297+
field_keys: ((.fields // .form_data // .data.fields // .data.form_data // {}) | keys)
298+
}
299+
end
300+
' gitcode-upload-url.json
301+
302+
- name: Upload GitHub release assets to GitCode
303+
if: steps.github_assets.outputs.has_assets == 'true'
304+
env:
305+
GITCODE_API_TOKEN: ${{ secrets.GITCODE_API_TOKEN }}
306+
GITCODE_OWNER: ${{ steps.gitcode_repo.outputs.owner }}
307+
GITCODE_REPO: ${{ steps.gitcode_repo.outputs.repo }}
308+
RELEASE_TAG: ${{ inputs.release_tag }}
309+
run: |
310+
set -euo pipefail
311+
312+
release_api_url="https://api.gitcode.com/api/v5/repos/${GITCODE_OWNER}/${GITCODE_REPO}/releases/tags/${RELEASE_TAG}"
313+
314+
curl --fail-with-body -sS \
315+
-H "PRIVATE-TOKEN: ${GITCODE_API_TOKEN}" \
316+
"$release_api_url" > gitcode-release-state.json
317+
318+
jq -r '.assets[]?.name' gitcode-release-state.json | sort -u > gitcode-existing-assets.txt || true
319+
touch gitcode-existing-assets.txt
320+
321+
upload_url="$(jq -r '
322+
if type == "string" then
323+
.
324+
elif .upload_url? then
325+
.upload_url
326+
elif .url? then
327+
.url
328+
elif .data.upload_url? then
329+
.data.upload_url
330+
elif .data.url? then
331+
.data.url
332+
else
333+
empty
334+
end
335+
' gitcode-upload-url.json)"
336+
337+
if [ -z "$upload_url" ]; then
338+
echo "::error::Unable to determine GitCode upload URL from upload target payload."
339+
cat gitcode-upload-url.json
340+
exit 1
341+
fi
342+
343+
request_method="$(jq -r '
344+
if .method? then
345+
.method
346+
elif .data.method? then
347+
.data.method
348+
else
349+
empty
350+
end
351+
' gitcode-upload-url.json | tr '[:lower:]' '[:upper:]')"
352+
353+
if [ -z "$request_method" ]; then
354+
request_method="PUT"
355+
fi
356+
357+
jq -c '
358+
if .headers? then
359+
.headers
360+
elif .data.headers? then
361+
.data.headers
362+
else
363+
{}
364+
end
365+
' gitcode-upload-url.json > gitcode-upload-headers.json
366+
367+
jq -c '
368+
if .fields? then
369+
.fields
370+
elif .form_data? then
371+
.form_data
372+
elif .data.fields? then
373+
.data.fields
374+
elif .data.form_data? then
375+
.data.form_data
376+
else
377+
{}
378+
end
379+
' gitcode-upload-url.json > gitcode-upload-fields.json
380+
381+
url_encode() {
382+
jq -rn --arg value "$1" '$value|@uri'
383+
}
384+
385+
render_url_for_file() {
386+
local base_url="$1"
387+
local file_name="$2"
388+
local encoded_name
389+
encoded_name="$(url_encode "$file_name")"
390+
391+
if [[ "$base_url" == *"{filename}"* ]]; then
392+
printf '%s' "${base_url//\{filename\}/$encoded_name}"
393+
return
394+
fi
395+
396+
if [[ "$base_url" == *"{file_name}"* ]]; then
397+
printf '%s' "${base_url//\{file_name\}/$encoded_name}"
398+
return
399+
fi
400+
401+
if [[ "$base_url" == *":filename"* ]]; then
402+
printf '%s' "${base_url//:filename/$encoded_name}"
403+
return
404+
fi
405+
406+
if [[ "$base_url" == *":file_name"* ]]; then
407+
printf '%s' "${base_url//:file_name/$encoded_name}"
408+
return
409+
fi
410+
411+
printf '%s' "$base_url"
412+
}
413+
414+
append_file_name_to_url() {
415+
local base_url="$1"
416+
local file_name="$2"
417+
local encoded_name
418+
encoded_name="$(url_encode "$file_name")"
419+
420+
if [[ "$base_url" == *\?* ]]; then
421+
printf '%s&filename=%s' "$base_url" "$encoded_name"
422+
elif [[ "$base_url" == */ ]]; then
423+
printf '%s%s' "$base_url" "$encoded_name"
424+
else
425+
printf '%s/%s' "$base_url" "$encoded_name"
426+
fi
427+
}
428+
429+
collect_header_args() {
430+
jq -r 'to_entries[] | @base64' gitcode-upload-headers.json | while read -r entry; do
431+
key="$(printf '%s' "$entry" | base64 -d | jq -r '.key')"
432+
value="$(printf '%s' "$entry" | base64 -d | jq -r '.value')"
433+
printf '%s\0' "-H" "${key}: ${value}"
434+
done
435+
}
436+
437+
collect_form_args() {
438+
jq -r 'to_entries[] | @base64' gitcode-upload-fields.json | while read -r entry; do
439+
key="$(printf '%s' "$entry" | base64 -d | jq -r '.key')"
440+
value="$(printf '%s' "$entry" | base64 -d | jq -r '.value')"
441+
printf '%s\0' "-F" "${key}=${value}"
442+
done
443+
}
444+
445+
mapfile -d '' -t extra_header_args < <(collect_header_args || true)
446+
mapfile -d '' -t extra_form_args < <(collect_form_args || true)
447+
448+
try_upload_request() {
449+
local mode="$1"
450+
local file_path="$2"
451+
local target_url="$3"
452+
local response_file="$4"
453+
shift 4
454+
455+
local status_code
456+
status_code="$(curl -sS -o "$response_file" -w "%{http_code}" "$@" "$target_url" || true)"
457+
458+
if [[ "$status_code" =~ ^2[0-9][0-9]$ ]]; then
459+
return 0
460+
fi
461+
462+
echo "Upload attempt failed (${mode}) with HTTP ${status_code}"
463+
if [ -s "$response_file" ]; then
464+
cat "$response_file"
465+
fi
466+
467+
return 1
468+
}
469+
470+
upload_asset() {
471+
local file_path="$1"
472+
local file_name
473+
file_name="$(basename "$file_path")"
474+
475+
if grep -Fxq "$file_name" gitcode-existing-assets.txt; then
476+
echo "Skipping existing GitCode asset: $file_name"
477+
return 0
478+
fi
479+
480+
local primary_url
481+
local secondary_url
482+
local response_file
483+
primary_url="$(render_url_for_file "$upload_url" "$file_name")"
484+
secondary_url="$(append_file_name_to_url "$upload_url" "$file_name")"
485+
response_file="gitcode-upload-response-${file_name}.log"
486+
487+
echo "Uploading GitCode asset: $file_name"
488+
489+
if [ "${#extra_form_args[@]}" -gt 0 ]; then
490+
if try_upload_request \
491+
"multipart-with-fields" \
492+
"$file_path" \
493+
"$primary_url" \
494+
"$response_file" \
495+
-X POST \
496+
"${extra_header_args[@]}" \
497+
"${extra_form_args[@]}" \
498+
-F "file=@${file_path};filename=${file_name}"; then
499+
echo "Uploaded ${file_name} via multipart-with-fields."
500+
return 0
501+
fi
502+
fi
503+
504+
if try_upload_request \
505+
"${request_method}-binary-primary" \
506+
"$file_path" \
507+
"$primary_url" \
508+
"$response_file" \
509+
-X "$request_method" \
510+
"${extra_header_args[@]}" \
511+
-H "Content-Type: application/octet-stream" \
512+
--upload-file "$file_path"; then
513+
echo "Uploaded ${file_name} via ${request_method} binary."
514+
return 0
515+
fi
516+
517+
if [ "$secondary_url" != "$primary_url" ]; then
518+
if try_upload_request \
519+
"${request_method}-binary-secondary" \
520+
"$file_path" \
521+
"$secondary_url" \
522+
"$response_file" \
523+
-X "$request_method" \
524+
"${extra_header_args[@]}" \
525+
-H "Content-Type: application/octet-stream" \
526+
--upload-file "$file_path"; then
527+
echo "Uploaded ${file_name} via ${request_method} binary with filename-appended URL."
528+
return 0
529+
fi
530+
fi
531+
532+
if try_upload_request \
533+
"multipart-file-primary" \
534+
"$file_path" \
535+
"$primary_url" \
536+
"$response_file" \
537+
-X POST \
538+
"${extra_header_args[@]}" \
539+
-F "file=@${file_path};filename=${file_name}"; then
540+
echo "Uploaded ${file_name} via multipart file upload."
541+
return 0
542+
fi
543+
544+
if [ "$secondary_url" != "$primary_url" ]; then
545+
if try_upload_request \
546+
"multipart-file-secondary" \
547+
"$file_path" \
548+
"$secondary_url" \
549+
"$response_file" \
550+
-X POST \
551+
"${extra_header_args[@]}" \
552+
-F "file=@${file_path};filename=${file_name}"; then
553+
echo "Uploaded ${file_name} via multipart file upload with filename-appended URL."
554+
return 0
555+
fi
556+
fi
557+
558+
echo "::error::Failed to upload GitCode asset: ${file_name}"
559+
exit 1
560+
}
561+
562+
find github-release-assets -maxdepth 1 -type f -print0 | while IFS= read -r -d '' asset_path; do
563+
upload_asset "$asset_path"
564+
done
565+
566+
for attempt in 1 2 3 4 5; do
567+
curl --fail-with-body -sS \
568+
-H "PRIVATE-TOKEN: ${GITCODE_API_TOKEN}" \
569+
"$release_api_url" > gitcode-release-verify.json
570+
571+
jq -r '.assets[]?.name' gitcode-release-verify.json | sort -u > gitcode-release-assets.txt || true
572+
touch gitcode-release-assets.txt
573+
574+
missing_count=0
575+
576+
while IFS= read -r file_name; do
577+
[ -n "$file_name" ] || continue
578+
if ! grep -Fxq "$file_name" gitcode-release-assets.txt; then
579+
echo "Asset not visible on GitCode yet: $file_name"
580+
missing_count=$((missing_count + 1))
581+
fi
582+
done < <(find github-release-assets -maxdepth 1 -type f -printf '%f\n' | sort)
583+
584+
if [ "$missing_count" = "0" ]; then
585+
echo "All GitHub release assets are now visible on GitCode."
586+
exit 0
587+
fi
588+
589+
if [ "$attempt" -lt 5 ]; then
590+
sleep $((attempt * 2))
591+
fi
592+
done
593+
594+
echo "::error::Uploaded assets were not all visible on GitCode after verification retries."
595+
echo "Current GitCode asset list:"
596+
cat gitcode-release-assets.txt
597+
exit 1

0 commit comments

Comments
 (0)