diff --git a/.gitea/workflows/backup.yml b/.gitea/workflows/backup.yml index 03094bd..757d377 100644 --- a/.gitea/workflows/backup.yml +++ b/.gitea/workflows/backup.yml @@ -31,9 +31,10 @@ jobs: run: | set -euo pipefail echo "Refreshing mirror repos on 172.16.10.191 ..." - ssh engdev@172.16.10.191 'set -euo pipefail; shopt -s nullglob; for repo in */.git; do dir="${repo%/.git}"; echo "Updating ${dir}"; (cd "${dir}" && git fetch --mirror --prune); done' + ssh engdev@172.16.10.191 'set -euo pipefail; export GIT_PROGRESS_DELAY=0 GIT_FLUSH=1; shopt -s nullglob; for repo in *.git; do [ -d "${repo}" ] || continue; echo "Updating ${repo}"; (cd "${repo}" && git fetch --progress --mirror --prune); done' - name: Backup Branches (pre-scan → decision → execution) + continue-on-error: true env: BASE_GITEA_TOKEN: ${{ secrets.BASE_GITEA_TOKEN }} BASE_GITEA_URL: ${{ vars.BASE_GITEA_URL }} # e.g., https://gitea.example.com @@ -52,6 +53,8 @@ jobs: NOTIFY_WEBHOOK="${NOTIFY_WEBHOOK:-}" SYNC_TAGS="${SYNC_TAGS:-true}" TARGET_SEED_DEPTH="${TARGET_SEED_DEPTH:-50}" + export GIT_PROGRESS_DELAY=0 + export GIT_FLUSH=1 if ! [[ "${TARGET_SEED_DEPTH}" =~ ^[0-9]+$ ]] || (( TARGET_SEED_DEPTH <= 0 )); then echo "::warning::TARGET_SEED_DEPTH(${TARGET_SEED_DEPTH}) is invalid; resetting to 50" TARGET_SEED_DEPTH=50 @@ -64,13 +67,14 @@ jobs: TS_KST=$(TZ=Asia/Seoul date '+%Y%m%d_%H%M%S') REPORT_DIR="${ROOT_DIR}/backup_reports" mkdir -p "${REPORT_DIR}" - SOURCE_HEADS_FILE="${REPORT_DIR}/source_heads_${TS_KST}.tsv" - TARGET_HEADS_FILE="${REPORT_DIR}/target_heads_${TS_KST}.tsv" - DECISIONS_FILE="${REPORT_DIR}/decisions_${TS_KST}.tsv" - - echo -e "source_repo\tbranch\tcommit" > "${SOURCE_HEADS_FILE}" - echo -e "target_repo\tbranch\tcommit\texists" > "${TARGET_HEADS_FILE}" - echo -e "source_repo\tbranch\talias\tresolved_repo\tsource_commit\ttarget_commit\tdecision\tnote" > "${DECISIONS_FILE}" + DECISIONS_LOG="${REPORT_DIR}/decisions_${TS_KST}.log" + TIMINGS_LOG="${REPORT_DIR}/timings_${TS_KST}.log" + REPORT_MD="${REPORT_DIR}/report_${TS_KST}.md" + echo "REPORT_TS=${TS_KST}" >> "${GITHUB_ENV}" + echo "REPORT_DIR=${REPORT_DIR}" >> "${GITHUB_ENV}" + echo "SOURCE_SSH_HOST=${SOURCE_SSH_HOST}" >> "${GITHUB_ENV}" + : > "${DECISIONS_LOG}" + : > "${TIMINGS_LOG}" notify_status() { local status="$1" repo="$2" branch="$3" mode="$4" start_epoch="$5" reason="${6:-}" details="${7:-}" @@ -143,6 +147,28 @@ jobs: fi } + append_decision_row() { + local note="$1" + printf "%s|%s|%s|%s|%s|%s|%s|%s\n" "${source_repo}" "${branch_name}" "${alias_name}" "${repo_name}" "${source_commit}" "${target_commit}" "${decision}" "${note}" >> "${DECISIONS_LOG}" + } + + append_timing_row() { + local status="$1" note="$2" + local end_ts="${exec_end_epoch:-$(date +%s)}" + local dec_ts="${decision_epoch:-$start_epoch}" + local exec_start_ts="${exec_start_epoch:-}" + local total_dur=$(( end_ts - start_epoch )) + local exec_dur=0 + if [[ -n "${exec_start_ts}" ]]; then + exec_dur=$(( end_ts - exec_start_ts )) + fi + printf "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s\n" \ + "${source_repo}" "${branch_name}" "${alias_name}" "${repo_name}" \ + "${source_commit}" "${target_commit}" "${decision}" "${status}" \ + "${start_epoch}" "${dec_ts}" "${exec_start_ts}" "${end_ts}" \ + "${total_dur}" "${exec_dur}" "${note}" >> "${TIMINGS_LOG}" + } + map_repo_name() { local branch_name="$1" alias_name="$2" resolved resolved="${branch_name}" @@ -227,14 +253,14 @@ jobs: local source_repo="$1" cache_dir="${CACHE_BASE}/${source_repo//\//_}.git" if [[ ! -d "${cache_dir}" ]]; then echo "Initializing local cache for ${source_repo} at ${cache_dir}" - if ! git clone --mirror "${SOURCE_SSH_HOST}:${source_repo}" "${cache_dir}"; then - echo "::warning::Failed to clone cache for ${source_repo}" + if ! git clone --progress --mirror "${SOURCE_SSH_HOST}:${source_repo}" "${cache_dir}"; then + echo "::warning::Failed to clone cache for ${source_repo} (${SOURCE_SSH_HOST}:${source_repo})" return 1 fi else echo "Refreshing cache for ${source_repo}" - if ! git -C "${cache_dir}" fetch --mirror --prune; then - echo "::warning::Failed to refresh cache for ${source_repo}" + if ! git -C "${cache_dir}" fetch --progress --mirror --prune; then + echo "::warning::Failed to refresh cache for ${source_repo} (${SOURCE_SSH_HOST}:${source_repo})" return 1 fi fi @@ -252,13 +278,13 @@ jobs: source_repo_url="${SOURCE_SSH_HOST}:${source_repo}" if [[ -n "${cache_dir}" && -d "${cache_dir}" ]]; then echo " - using cache: ${cache_dir}" - remote_output=$(git -C "${cache_dir}" for-each-ref --format='%(objectname)\t%(refname)' 'refs/heads/*') || { - echo "::warning::Failed to read heads from cache ${cache_dir}; falling back to remote." + if ! remote_output=$(git -C "${cache_dir}" for-each-ref --format='%(objectname)\t%(refname)' 'refs/heads/*'); then + echo "::warning::Failed to read heads from cache ${cache_dir}; falling back to remote ${source_repo_url}." remote_output=$(git ls-remote --heads "${source_repo_url}") || { echo "::warning::Failed to ls-remote ${source_repo_url}. Entries for this repo may fail." continue } - } + fi else echo " - ${source_repo_url}" if ! remote_output=$(git ls-remote --heads "${source_repo_url}"); then @@ -266,6 +292,19 @@ jobs: continue fi fi + if [[ -z "${remote_output}" ]]; then + echo "::warning::No branches found for ${source_repo_url}" + continue + fi + while IFS=$'\t' read -r commit ref || [[ -n "${commit}" ]]; do + [[ -z "${commit}" || -z "${ref}" ]] && continue + branch="${ref#refs/heads/}" + SOURCE_HEADS["${source_repo}|${branch}"]="${commit}" + done <<< "${remote_output}" + done + continue + fi + fi while IFS=$'\t' read -r commit ref || [[ -n "${commit}" ]]; do [[ -z "${commit}" || -z "${ref}" ]] && continue branch="${ref#refs/heads/}" @@ -287,13 +326,19 @@ jobs: note="" target_commit="" heads_detail="src=unknown tgt=unknown" + decision_epoch="" + exec_start_epoch="" + exec_end_epoch="" source_commit="${SOURCE_HEADS[${source_repo}|${branch_name}]:-}" if [[ -z "${source_commit}" ]]; then - note="source branch 없음/조회 실패" - echo -e "${source_repo}\t${branch_name}\t${alias_name}\t${repo_name}\t\t\t건너뜀\t${note}" >> "${DECISIONS_FILE}" + decision="사전 스캔 실패" + decision_epoch=$(date +%s) + note="source branch 없음/조회 실패 (repo=${source_repo}, branch=${branch_name})" + append_decision_row "${note}" heads_detail="src=none tgt=unknown" notify_status "error" "${repo_name}" "${branch_name}" "사전 스캔 실패" "${start_epoch}" "${note}" "${heads_detail}" + append_timing_row "error" "${note}" echo "Skipping ${branch_name} (${repo_name}) - ${note}" continue fi @@ -319,11 +364,14 @@ jobs: elif [[ "${http_status}" == "404" ]]; then repo_exists=false else + decision="사전 조회 실패" + decision_epoch=$(date +%s) note="repo 조회 실패 (HTTP ${http_status})" echo "::warning::${note}" - echo -e "${source_repo}\t${branch_name}\t${alias_name}\t${repo_name}\t${source_commit}\t\t건너뜀\t${note}" >> "${DECISIONS_FILE}" heads_detail="src=${source_commit} tgt=unknown" notify_status "error" "${repo_name}" "${branch_name}" "사전 조회 실패" "${start_epoch}" "${note}" "${heads_detail}" + append_decision_row "${note}" + append_timing_row "error" "${note}" continue fi @@ -351,11 +399,13 @@ jobs: note="커밋 상이" fi - echo -e "${source_repo}\t${branch_name}\t${alias_name}\t${repo_name}\t${source_commit}\t${target_commit}\t${decision}\t${note}" >> "${DECISIONS_FILE}" + decision_epoch=$(date +%s) + append_decision_row "${note}" if [[ "${decision}" == "동일 커밋 - 건너뜀" ]]; then notify_status "skip" "${repo_name}" "${branch_name}" "${decision}" "${start_epoch}" "" "${heads_detail}" echo "Pre-scan marked ${branch_name} (${repo_name}) as skip (same commit)." + append_timing_row "skip" "${note}" continue fi @@ -378,6 +428,8 @@ jobs: if [[ "${create_status}" != "201" ]]; then echo "::error::Failed to create repository. HTTP ${create_status}" notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "repo 생성 실패 (HTTP ${create_status})" "${heads_detail}" + exec_end_epoch=$(date +%s) + append_timing_row "error" "repo 생성 실패 (HTTP ${create_status})" continue fi echo "Repository created successfully." @@ -391,6 +443,7 @@ jobs: notify_status "start" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "" "${heads_detail}" + exec_start_epoch=$(date +%s) shallow_exclude_args=() if ${repo_exists} && [[ -n "${target_commit:-}" ]]; then shallow_exclude_args=(--shallow-exclude="${target_commit}") @@ -404,6 +457,8 @@ jobs: if ! git clone --bare --no-tags --single-branch --branch "${branch_name}" "${SOURCE_FETCH_REMOTE}" "${CLONE_DIR}"; then echo "::error::Failed to clone source repository ${SOURCE_FETCH_REMOTE}" notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "source clone 실패" "${heads_detail}" + exec_end_epoch=$(date +%s) + append_timing_row "error" "source clone 실패" rm -rf "${CLONE_DIR}" continue fi @@ -415,6 +470,8 @@ jobs: if ! git init --bare; then echo "::error::Failed to init bare repository" notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "bare init 실패" "${heads_detail}" + exec_end_epoch=$(date +%s) + append_timing_row "error" "bare init 실패" cd "${ROOT_DIR}" rm -rf "${CLONE_DIR}" continue @@ -451,6 +508,8 @@ jobs: fi echo "::error::Failed to fetch branch '${branch_name}' from source repo (fallback without shallow-exclude)" notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "source fetch 오류(폴백)" "${heads_detail}" + exec_end_epoch=$(date +%s) + append_timing_row "error" "source fetch 오류(폴백)" cd "${ROOT_DIR}" rm -rf "${CLONE_DIR}" continue @@ -458,6 +517,8 @@ jobs: else echo "::error::Failed to fetch branch '${branch_name}' from source repo" notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "source fetch 오류" "${heads_detail}" + exec_end_epoch=$(date +%s) + append_timing_row "error" "source fetch 오류" cd "${ROOT_DIR}" rm -rf "${CLONE_DIR}" continue @@ -469,6 +530,8 @@ jobs: if ! git fetch --progress --prune --prune-tags --no-tags source "refs/tags/*:refs/tags/*"; then echo "::error::Failed to fetch tags from source repo" notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "tag fetch 오류" "${heads_detail}" + exec_end_epoch=$(date +%s) + append_timing_row "error" "tag fetch 오류" cd "${ROOT_DIR}" rm -rf "${CLONE_DIR}" continue @@ -479,6 +542,8 @@ jobs: if ! git push --progress --force origin "refs/heads/${branch_name}:refs/heads/main"; then echo "::error::Failed to push branch '${branch_name}' to target repository" notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "push 오류 (-> main)" "${heads_detail}" + exec_end_epoch=$(date +%s) + append_timing_row "error" "push 오류 (-> main)" cd "${ROOT_DIR}" rm -rf "${CLONE_DIR}" continue @@ -488,6 +553,8 @@ jobs: if ! git push --force --prune origin "refs/tags/*:refs/tags/*"; then echo "::warning::Failed to push tags to target repository" notify_status "error" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "tag push 오류" "${heads_detail}" + exec_end_epoch=$(date +%s) + append_timing_row "error" "tag push 오류" fi fi @@ -497,6 +564,8 @@ jobs: echo "Successfully mirrored ${branch_name} to center_dev/${repo_name}" set_default_branch_main "${repo_name}" final_heads_detail="src=${source_commit} tgt=${source_commit}" + exec_end_epoch=$(date +%s) + append_timing_row "success" "${backup_mode}" notify_status "success" "${repo_name}" "${branch_name}" "${backup_mode}" "${start_epoch}" "" "${final_heads_detail}" echo "=================================================" echo "" @@ -506,9 +575,8 @@ jobs: SUMMARY_TS=$(TZ=Asia/Seoul date '+%Y-%m-%d %H:%M:%S %Z') SUMMARY_TEXT="브랜치 동기화 완료: 총 ${TOTAL_PROCESSED}개 (성공 ${TOTAL_SUCCESS}, 동일로 건너뜀 ${TOTAL_SKIP}, 오류 ${TOTAL_ERROR}) - ${SUMMARY_TS}" echo "${SUMMARY_TEXT}" - echo "Source head 기록: ${SOURCE_HEADS_FILE}" - echo "Target head 기록: ${TARGET_HEADS_FILE}" - echo "판정 테이블: ${DECISIONS_FILE}" + echo "보고서(마크다운)는 이후 스텝에서 생성됩니다." + echo "${TOTAL_ERROR}" > "${REPORT_DIR}/exit_code" if [[ -n "${NOTIFY_WEBHOOK}" ]]; then SUMMARY_PAYLOAD=${SUMMARY_TEXT//\"/\\\"} curl -sS -i -X POST \ @@ -521,3 +589,86 @@ jobs: echo "::warning::One or more branches failed (${TOTAL_ERROR})." exit 1 fi + + - name: Build markdown report + if: always() + run: | + set -euo pipefail + REPORT_DIR="${REPORT_DIR:-${{ github.workspace }}/backup_reports}" + TS="${REPORT_TS:-}" + if [[ -z "${TS}" ]]; then + TS=$(ls -t ${REPORT_DIR}/decisions_*.log 2>/dev/null | head -1 | sed -E 's/.*decisions_([0-9_]+)\\.log/\\1/') + fi + if [[ -z "${TS}" ]]; then + echo "::error::No report timestamp found; cannot build report." + exit 1 + fi + DECISIONS_LOG="${REPORT_DIR}/decisions_${TS}.log" + TIMINGS_LOG="${REPORT_DIR}/timings_${TS}.log" + REPORT_MD="${REPORT_DIR}/report_${TS}.md" + SOURCE_SSH_HOST="${SOURCE_SSH_HOST:-${{ env.SOURCE_SSH_HOST }}}" + + if [[ ! -f "${DECISIONS_LOG}" || ! -f "${TIMINGS_LOG}" ]]; then + echo "::error::Missing decisions or timings log." + exit 1 + fi + + total=0; success=0; skip=0; error=0 + while IFS='|' read -r sr br al rr sc tc dc st se de ee xe dt dexe nt; do + [[ -z "${sr}" ]] && continue + ((total++)) + case "${st}" in + success) ((success++)) ;; + skip) ((skip++)) ;; + error) ((error++)) ;; + esac + done < "${TIMINGS_LOG}" + + SUMMARY_TS=$(TZ=Asia/Seoul date '+%Y-%m-%d %H:%M:%S %Z') + { + echo "# Git Backup Report" + echo "" + echo "- Generated: ${SUMMARY_TS}" + echo "- Run ID: ${GITHUB_RUN_ID:-local}" + echo "- Source host: ${SOURCE_SSH_HOST}" + echo "- Total: ${total} (success ${success}, skip ${skip}, error ${error})" + echo "" + echo "## Decisions (all)" + echo "|source_repo|branch|alias|resolved_repo|source_commit|target_commit|decision|note|" + echo "|---|---|---|---|---|---|---|---|" + while IFS='|' read -r sr br al rr sc tc dc nt; do + [[ -z "${sr}" ]] && continue + printf "|%s|%s|%s|%s|%s|%s|%s|%s|\n" "$sr" "$br" "$al" "$rr" "$sc" "$tc" "$dc" "$nt" + done < "${DECISIONS_LOG}" + echo "" + echo "## Timings (all)" + echo "|source_repo|branch|alias|resolved_repo|source_commit|target_commit|decision|status|start_epoch|decision_epoch|exec_start_epoch|exec_end_epoch|duration_total(s)|duration_exec(s)|note|" + echo "|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|" + while IFS='|' read -r sr br al rr sc tc dc st se de ee xe dt dexe nt; do + [[ -z "${sr}" ]] && continue + printf "|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|\n" "$sr" "$br" "$al" "$rr" "$sc" "$tc" "$dc" "$st" "$se" "$de" "$ee" "$xe" "$dt" "$dexe" "$nt" + done < "${TIMINGS_LOG}" + echo "" + } > "${REPORT_MD}" + echo "Report generated at ${REPORT_MD}" + + - name: Upload backup reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: backup_reports_${{ github.run_id }} + path: backup_reports + + - name: Fail if backup had errors + if: always() + run: | + EXIT_CODE=0 + if [[ -f backup_reports/exit_code ]]; then + EXIT_CODE=$(cat backup_reports/exit_code) + fi + if [[ "${EXIT_CODE}" != "0" ]]; then + echo "::error::Backup reported ${EXIT_CODE} errors." + exit 1 + else + echo "Backup completed without errors." + fi diff --git a/.gitea/workflows/mirror.yml b/.gitea/workflows/mirror.yml index cea2548..159321f 100644 --- a/.gitea/workflows/mirror.yml +++ b/.gitea/workflows/mirror.yml @@ -29,7 +29,7 @@ jobs: run: | set -euo pipefail echo "Refreshing mirror repos on 172.16.10.191 ..." - ssh engdev@172.16.10.191 'set -euo pipefail; shopt -s nullglob; for repo in */.git; do dir="${repo%/.git}"; echo "Updating ${dir}"; (cd "${dir}" && git fetch --mirror --prune); done' + ssh engdev@172.16.10.191 'set -euo pipefail; export GIT_PROGRESS_DELAY=0 GIT_FLUSH=1; shopt -s nullglob; for repo in *.git; do [ -d "${repo}" ] || continue; echo "Updating ${repo}"; (cd "${repo}" && git fetch --progress --mirror --prune); done' - name: Mirror Branches env: @@ -41,10 +41,12 @@ jobs: SYNC_TAGS: ${{ vars.SYNC_TAGS }} # Optional, "false" to skip tag sync run: | set -euo pipefail + export GIT_PROGRESS_DELAY=0 + export GIT_FLUSH=1 CENTER_ORG="center_dev" AUTH_HEADER="Authorization: token ${BASE_GITEA_TOKEN}" - SOURCE_SSH_HOST="engdev@172.16.42.118" + SOURCE_SSH_HOST="engdev@172.16.10.191" ROOT_DIR="$(pwd)" NOTIFY_WEBHOOK="${NOTIFY_WEBHOOK:-}" SYNC_TAGS="${SYNC_TAGS:-true}" diff --git a/AGENTS.md b/AGENTS.md index 001701c..e432cb3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ - 트리거: cron `0 2 * * *` 및 `workflow_dispatch`. - 기본 데이터 소스: `branch_list` 파일을 한 줄씩 읽어 순차 처리. - 브랜치→저장소 매핑 규칙: `Develop_Net8` → `base`, `Develop_Net8_*` → `*` 부분을 저장소 이름으로 사용하며, 그 외 브랜치는 브랜치명을 그대로 저장소 이름으로 사용. -- 실행 단계: 워크플로우 리포지토리 체크아웃 → SSH 키 저장/권한 설정 후 `ssh-keyscan 172.16.10.191` → `engdev@172.16.10.191` 홈 내 `.git` 디렉터리를 가진 하위 디렉터리마다 `git fetch --mirror --prune`로 사전 동기화 → 워크플로우 내부에서 소스별 로컬 캐시를 생성/갱신해 동일 커밋의 중복 fetch를 최소화 → 각 브랜치마다 Gitea API로 `center_dev` 조직에 저장소 존재 여부 확인(있으면 대상 저장소를 mirror clone 후 source 브랜치를 fetch/pull해 업데이트만, 없으면 저장소 생성 시 기본 브랜치를 `main`으로 설정) → 소스 브랜치를 대상 저장소 `main`으로 강제 푸시 → 푸시 후에도 기본 브랜치를 `main`으로 패치 → 임시 폴더 정리. 오류가 나도 다음 브랜치로 계속 진행하도록 처리. +- 실행 단계: 워크플로우 리포지토리 체크아웃 → SSH 키 저장/권한 설정 후 `ssh-keyscan 172.16.10.191` → `engdev@172.16.10.191` 홈 내 `.git` 디렉터리를 가진 하위 디렉터리마다 `git fetch --mirror --prune`로 사전 동기화 → 워크플로우 내부에서 소스별 로컬 캐시를 생성/갱신해 동일 커밋의 중복 fetch를 최소화 → 각 브랜치마다 Gitea API로 `center_dev` 조직에 저장소 존재 여부 확인(있으면 대상 저장소를 mirror clone 후 source 브랜치를 fetch/pull해 업데이트만, 없으면 저장소 생성 시 기본 브랜치를 `main`으로 설정) → 소스 브랜치를 대상 저장소 `main`으로 강제 푸시 → 푸시 후에도 기본 브랜치를 `main`으로 패치 → 임시 폴더 정리. 오류가 나도 다음 브랜치로 계속 진행하도록 처리. 실행 후 `backup_reports/`(report_*.md에 Decisions/Timings 전체 테이블 포함)를 artifact로 업로드해 사후 확인 가능. ## 시크릿/변수 - 현재 요구: `SSH_PRIVATE_KEY`(소스 접근), `GITEA_TOKEN`(Gitea API/푸시), `GITEA_URL`(Gitea 인스턴스 URL). diff --git a/README.md b/README.md index 4276311..fe267cc 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,13 @@ ## backup.yml (프리스캔 백업) 동작 개요 1) 입력 또는 `branch_list`를 파싱해 유효 항목을 확정하고, 사전 단계에서 미러 서버(`engdev@172.16.10.191`) 홈 내 `.git` 디렉터리마다 `git fetch --mirror --prune`로 최신 상태로 맞춥니다. 2) 소스별 로컬 캐시(워크플로우 내 생성/갱신)를 이용해 브랜치 해시를 수집하고 필요한 브랜치만 fetch하므로 동일 커밋 다중 브랜치에서도 중복 전송을 줄입니다. 캐시가 없으면 원본을 직접 조회합니다. -2) Gitea에 해당 저장소가 있는지 조회 후 `main` 해시를 확인합니다. -3) 사전 판정: 타겟이 없거나 `main`이 없으면 “신규 백업”, 해시가 같으면 “건너뜀”, 다르면 “증분 백업”으로 결정합니다. -4) 해시 스냅샷과 판정 테이블을 `backup_reports/` 폴더에 `source_heads_*.tsv`, `target_heads_*.tsv`, `decisions_*.tsv`로 기록합니다. -5) “건너뜀”은 바로 알림 후 종료, “신규/증분”만 fetch→push 실행합니다. -6) 실행 단계: 저장소 존재 확인/생성(`default_branch=main`) → shallow-exclude 기반 fetch(미지원 시 타겟 main 얕은 시드 후 일반 fetch) → `main`으로 강제 푸시 → 태그 동기화(옵션) → 기본 브랜치 `main` 패치 → 임시 폴더 정리. -7) 알림은 시작/성공/실패/건너뜀에 대해 KST 타임스탬프, 모드, 소요시간을 포함해 전송합니다. +3) Gitea에 해당 저장소가 있는지 조회 후 `main` 해시를 확인합니다. +4) 사전 판정: 타겟이 없거나 `main`이 없으면 “신규 백업”, 해시가 같으면 “건너뜀”, 다르면 “증분 백업”으로 결정합니다. +5) 판정/타이밍 정보를 로그 파일(decisions/timings)로 남기고, 이후 스텝에서 `report_.md`로 전체 테이블을 생성합니다. +6) “건너뜀”은 바로 알림 후 종료, “신규/증분”만 fetch→push 실행합니다. +7) 실행 단계: 저장소 존재 확인/생성(`default_branch=main`) → shallow-exclude 기반 fetch(미지원 시 타겟 main 얕은 시드 후 일반 fetch) → `main`으로 강제 푸시 → 태그 동기화(옵션) → 기본 브랜치 `main` 패치 → 임시 폴더 정리. +8) 알림은 시작/성공/실패/건너뜀에 대해 KST 타임스탬프, 모드, 소요시간을 포함해 전송합니다. +9) 실행 후 `backup_reports/` 전체를 `backup_reports_` 이름으로 artifact 업로드합니다. `report_*.md` 하나에 Decisions/Timings 전체 테이블이 포함됩니다. ## mirror.yml (즉시 복제) 동작 개요 1) 브랜치 존재 여부 확인 (`git ls-remote`) diff --git a/branch_list b/branch_list index 250bbac..2165592 100644 --- a/branch_list +++ b/branch_list @@ -1,4 +1,4 @@ -dev_Net8.git/Develop_Net8 +dev_Net8.git/Develop_Net8, base dev_Net8.git/Develop_Net8_bridge dev_Net8.git/Develop_Net8_Graphics dev_Net8.git/Develop_Net8_Graphics_B