Skip to content

Sync

Sync #103

Workflow file for this run

name: Sync
on:
workflow_dispatch:
# inputs:
# repo:
# type: string
# description: 'Target repo'
# required: false
# default: ''
schedule:
- cron: '34 12 * * *'
push:
concurrency:
group: sync
# for beacon-biosignals/matrix-output
# permissions:
# actions: read
# contents: read
jobs:
prepare_matrix:
runs-on: ubuntu-slim
outputs:
chunks: ${{ steps.split.outputs.matrix }}
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v6
with:
ref: split
path: ./split
- name: Split
id: split
run: |
PARALLEL=20
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
rm -rf split/*
cat repos.json | jq -c '.[]' | shuf | split -n l/$PARALLEL /dev/stdin split/
cd split
git add -A
git commit --allow-empty -am 'split'
git push
main:
runs-on: ubuntu-latest
needs: prepare_matrix
strategy:
fail-fast: false
matrix:
split: [aa,ab,ac,ad,ae,af,ag,ah,ai,aj,ak,al,am,an,ao,ap,aq,ar,as,at]
# outputs:
# failed: ${{ steps.matrix-output.outputs.json }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/checkout@v6
with:
ref: split
path: ./split
- name: List targets
env:
SPLIT_CHUNK: ${{ matrix.split }}
run: cat split/$SPLIT_CHUNK
# - uses: jlumbroso/free-disk-space@main
# with:
# tool-cache: true
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: CodeLinaro-mirror
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
- run: df -h
- name: Sync
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
SPLIT_CHUNK: ${{ matrix.split }}
APP_ID: ${{ secrets.APP_ID }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
CLIENT_ID: ${{ secrets.CLIENT_ID }}
INSTALLATION_ID: ${{ secrets.INSTALLATION_ID }}
run: |
# set -x
REPO_DIR="$(pwd)"
MAX_JOBS=15
LAST_UPDATE=$(date +%s)
CHUNK_DATA=$(cat split/$SPLIT_CHUNK)
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
git config --global url."https://x-access-token:$GH_TOKEN@github.com/".insteadOf "https://github.com/"
git config --global pack.packSizeLimit 100m
git config --global pack.window 0
git config --global pack.depth 0
# not required
git config --global init.defaultBranch main
tmp_fifo=$(mktemp -u)
mkfifo "$tmp_fifo"
exec 3<>"$tmp_fifo"
# rm "$tmp_fifo"
for ((i=0; i<MAX_JOBS; i++)); do
echo >&3
done
sudo mkdir /mnt/sync
sudo touch /mnt/failed-repos
sudo chown runner:runner -R /mnt/sync /mnt/failed-repos
function check_and_refresh_token() {
local now=$(date +%s)
if (( now - LAST_UPDATE > 3000 )); then
echo "--- Token expired or near expiry. Refreshing... ---"
local token=$($REPO_DIR/gh_token_refresh.sh)
if [ "$token" = "null" ]; then
echo "Failed to refresh token"
return 1
fi
git config --global --remove-section "url.https://x-access-token:${GH_TOKEN}@github.com/" 2>/dev/null || true
export GH_TOKEN="$token"
git config --global url."https://x-access-token:$GH_TOKEN@github.com/".insteadOf "https://github.com/"
LAST_UPDATE=$now
echo "--- Token refreshed. ---"
fi
}
function log() {
# echo "[$repo]" "${@}"
echo "${@}"
}
function sync_mirror() {
set -e
url=$(jq -r '.clone_url' <<< "$config")
repo=$(jq -r '.name' <<< "$config" | tr '/' '_')
description=$(jq -r '.description // ""' <<< "$config" | head -n1)
first="0"
mkdir "$repo" && cd "$repo"
log "Check if the mirror repository is created"
set +e
curl -I --fail --silent --show-error -o /dev/null "https://github.com/CodeLinaro-mirror/$repo"
ret="$?"
set -e
if [ "$ret" -ne 0 ]; then
log "Creating target repo"
gh repo create "CodeLinaro-mirror/$repo" --homepage "$url" --disable-issues --disable-wiki --public --description "$description"
first="1"
fi
log "Compare remotes"
git ls-remote --quiet --heads --tags "$url" > remote-src &
git ls-remote --quiet --heads --tags "https://github.com/CodeLinaro-mirror/$repo" > remote-dist &
wait
set +e
diff remote-src remote-dist > /dev/null
ret="$?"
set -e
if [ "$ret" -eq 0 ]; then
log "Mirror is synchronized. skip"
return
fi
log "Cloning source repo"
mkdir repo-dir && cd repo-dir
git init --bare
git remote add --mirror=fetch origin "$url"
git remote add mirror "https://github.com/CodeLinaro-mirror/$repo"
git config unset --all remote.origin.fetch
git config unset --all remote.mirror.fetch
git config set --append remote.origin.fetch '+refs/heads/*:refs/heads/*'
git config set --append remote.origin.fetch "^refs/heads/refs/*"
git config set --append remote.mirror.fetch '+refs/heads/*:refs/heads/*'
git config set --append remote.origin.fetch '+refs/tags/*:refs/tags/*'
git config set --append remote.mirror.fetch '+refs/tags/*:refs/tags/*'
if [ "$first" -eq 0 ]; then
log "Fetching from mirror"
git fetch --quiet mirror --filter=blob:none
log "Fetching from origin"
git fetch --quiet --prune --prune-tags origin
else
log "First time sync. full cloning"
git fetch --quiet origin
fi
log "Pushing to mirror repo"
set +e
git push --quiet --mirror mirror
ret="$?"
set -e
if [ "$ret" -ne 0 ]; then
log "Failed to push, fallback"
BRANCHES=$(git for-each-ref --format='%(refname:lstrip=2)' refs/heads/)
for CURRENT_BRANCH in $BRANCHES; do
log "=== Processing branch: $CURRENT_BRANCH ==="
BATCH=0
MAX_BATCH=100000
MIN_BATCH=1
TOTAL_ORIG=$(git rev-list --count "refs/heads/$CURRENT_BRANCH")
PROCESSED=0
while true; do
check_and_refresh_token
MIRROR_TIP=$(git ls-remote mirror "refs/heads/$CURRENT_BRANCH" | awk '{print $1}')
if [ -n "$MIRROR_TIP" ]; then
mapfile -t COMMITS < <(git rev-list --reverse --ancestry-path "${MIRROR_TIP}..refs/heads/${CURRENT_BRANCH}")
else
mapfile -t COMMITS < <(git rev-list --reverse "refs/heads/$CURRENT_BRANCH")
fi
TOTAL=${#COMMITS[@]}
if [ "$TOTAL" -eq 0 ]; then
log "Already up-to-date. skip"
break
fi
log "Total new commits: $TOTAL (mirror tip: ${MIRROR_TIP:-none})"
if [ "$BATCH" -eq 0 ]; then
CURRENT_TRY=$TOTAL
else
CURRENT_TRY=$(( TOTAL < BATCH ? TOTAL : BATCH ))
fi
TARGET_REV=${COMMITS[$((CURRENT_TRY - 1))]}
log "--- Attempting: $CURRENT_TRY commits (Progress (approx): $PROCESSED/$TOTAL_ORIG) ---"
if git push mirror "$TARGET_REV:refs/heads/$CURRENT_BRANCH"; then
log ">> Success."
PROCESSED=$((PROCESSED + CURRENT_TRY))
if [ "$BATCH" -eq 0 ]; then
BATCH=$CURRENT_TRY
fi
BATCH=$((BATCH * 12 / 10 + 3))
[ $BATCH -gt $MAX_BATCH ] && BATCH=$MAX_BATCH
log ">> Increasing batch size to $BATCH for next step."
else
log ">> Failed! Reducing batch size..."
if [ "$BATCH" -eq 0 ]; then
BATCH=$((CURRENT_TRY / 2))
else
if [ $BATCH -gt $CURRENT_TRY ]; then
BATCH=$CURRENT_TRY
else
if [ $BATCH -le 3 ]; then
BATCH=$((BATCH - 1))
else
BATCH=$((BATCH / 2))
fi
fi
fi
if [ $BATCH -lt $MIN_BATCH ]; then
if [ $CURRENT_TRY -eq 1 ]; then
log "Fatal: Single commit push failed. Check for large files or remote limits."
return 1
fi
BATCH=$MIN_BATCH
fi
log ">> Retrying with smaller batch: $BATCH"
fi
done
done
gh repo edit "CodeLinaro-mirror/$repo" --default-branch "$( git remote show origin | grep "^ HEAD branch: " | cut -c 16- )"
git push mirror --mirror
fi
gh repo edit "CodeLinaro-mirror/$repo" --default-branch "$( git remote show origin | grep "^ HEAD branch: " | cut -c 16- )"
}
TOTAL_COUNT=$(grep -c '^' <<< "$CHUNK_DATA")
CURRENT=0
cd /mnt/sync
while IFS= read -r config; do
[ -z "$config" ] && continue
read -u 3
CURRENT=$((CURRENT + 1))
check_and_refresh_token
export config
(
start_time=$(date +%s.%N)
repo=$(jq -r '.name' <<< "$config" | tr '/' '_')
status="OK"
echo "::group::$repo"
log "Start"
if grep -xF "/$repo" "$REPO_DIR/skip.list"; then
log "Done (Skip) ($CURRENT/$TOTAL_COUNT)"
echo >&3
exit
fi
if ! sync_mirror "$config"; then
log "Failed"
echo "$repo" >> /mnt/failed-repos
status="Failed"
fi
echo >&3
# log "Cleaning"
rm -rf "/mnt/sync/$repo" &
end_time=$(date +%s.%N)
log "Done ($status) ($CURRENT/$TOTAL_COUNT) ($(echo "$end_time - $start_time" | bc) sec)"
echo "::endgroup::"
)
# )&
done <<< "$CHUNK_DATA"
echo "Waiting for all jobs to complete"
sleep 0.5
for ((i=0; i<MAX_JOBS; i++)); do
echo "waiting job: $i"
read -u 3
done
echo "done"
exec 3>&-
exec 3<&-
rm "$tmp_fifo"
- name: List failed repos
if: always()
id: failed
run: |
cat /mnt/failed-repos
# echo "list=$(cat /mnt/failed-repos)" >> $GITHUB_OUTPUT
if [[ -s /mnt/failed-repos ]]; then
exit 1
fi