@@ -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