#!/usr/bin/env bash # GitHub upstream → monorepo submodule 최신 소스 반영 (pull only, push 없음) # # ./git_fetch_all.sh # 4개 submodule fetch + fast-forward # ./git_fetch_all.sh --init # submodule 미초기화 시 clone 포함 # ./git_fetch_all.sh --dry-run # 실행 없이 변경 예상만 출력 # # 대상 (.gitmodules): # chatbotApp-chatbot2.0-agent → KoreaExpressWayCorp/chatbotApp (chatbot2.0-agent) # chatbotApi-chatbot2.0-agent → KoreaExpressWayCorp/chatbotApi (chatbot2.0-agent) # exAiChatBot-chatbot2.0-agent → KoreaExpressWayCorp/exAiChatBot (chatbot2.0-agent) # kakaoChatbotSkill-chatbot2.0-agent → KoreaExpressWayCorp/kakaoChatbotSkill (chatbot2.0-agent) set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # bare repo 배포 시 상위에서 GIT_DIR 이 export 되면 submodule git 이 깨짐 MONOREPO_GIT_DIR="${MONOREPO_GIT_DIR:-${GIT_DIR:-}}" unset GIT_DIR GIT_WORK_TREE INIT=false DRY_RUN=false for arg in "$@"; do case "$arg" in --init) INIT=true ;; --dry-run) DRY_RUN=true ;; -h|--help) sed -n '2,14p' "$0" exit 0 ;; *) echo "알 수 없는 옵션: $arg (--help)" exit 1 ;; esac done log() { echo "[git_fetch_all] $*"; } warn() { echo "[git_fetch_all] ⚠️ $*" >&2; } cd "${ROOT_DIR}" if [[ ! -f .gitmodules ]]; then echo "ERROR: .gitmodules 없음 — submodule 미설정" exit 1 fi SUBMODULE_PATHS=() while IFS= read -r _path; do [[ -n "${_path}" ]] && SUBMODULE_PATHS+=("${_path}") done < <(git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | awk '{print $2}') if [[ ${#SUBMODULE_PATHS[@]} -eq 0 ]]; then echo "ERROR: .gitmodules 에 submodule 경로 없음" exit 1 fi if $INIT && ! $DRY_RUN; then log "submodule init (direct clone)..." fi resolve_monorepo_git_dir() { if [[ -n "${MONOREPO_GIT_DIR}" && -d "${MONOREPO_GIT_DIR}" ]]; then echo "${MONOREPO_GIT_DIR}" elif git -C "${ROOT_DIR}" rev-parse --git-dir >/dev/null 2>&1; then git -C "${ROOT_DIR}" rev-parse --git-dir fi } submodule_pinned_sha() { local path="$1" local git_dir git_dir="$(resolve_monorepo_git_dir || true)" [[ -n "${git_dir}" ]] || return 0 git --git-dir="${git_dir}" ls-tree HEAD "${path}" 2>/dev/null | awk '{print $3}' } clone_submodule_repo() { local path="$1" url="$2" branch="$3" local target="${ROOT_DIR}/${path}" local sha sha="$(submodule_pinned_sha "${path}" || true)" log " clone ${url} → ${path} (branch=${branch})" rm -rf "${target}" if ! git clone --branch "${branch}" --single-branch "${url}" "${target}"; then warn "${path} — clone 실패 (서버 GitHub 미접속 시 sync-submodules-rsync.sh 사용)" return 1 fi if [[ -n "${sha}" ]]; then git -C "${target}" checkout "${sha}" 2>/dev/null || true fi } restore_monorepo_overlays() { if [[ -f "${ROOT_DIR}/local-dev/docker-compose.rag.local.yml" ]]; then cp "${ROOT_DIR}/local-dev/docker-compose.rag.local.yml" \ "${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/docker-compose.local.yml" log "복원: exAiChatBot/docker-compose.local.yml (monorepo overlay)" fi if [[ -f "${ROOT_DIR}/server-dev/docker-compose.rag.server.yml" ]]; then cp "${ROOT_DIR}/server-dev/docker-compose.rag.server.yml" \ "${ROOT_DIR}/exAiChatBot-chatbot2.0-agent/docker-compose.server.yml" log "복원: exAiChatBot/docker-compose.server.yml (monorepo overlay)" fi } submodule_is_cloned() { local path="$1" [[ -e "${ROOT_DIR}/${path}/.git" ]] } fetch_one() { local path="$1" local url branch old new dirty url="$(git config -f .gitmodules "submodule.${path}.url")" branch="$(git config -f .gitmodules "submodule.${path}.branch" || echo main)" branch="${branch:-main}" log ">>> ${path}" log " url=${url} branch=${branch}" if ! submodule_is_cloned "${path}"; then if $DRY_RUN; then log " [dry-run] submodule clone 필요 — ./git_fetch_all.sh --init" return 0 fi if $INIT; then clone_submodule_repo "${path}" "${url}" "${branch}" || return 1 else warn "${path} — submodule 미초기화. --init 옵션 사용" return 1 fi fi ( cd "${ROOT_DIR}/${path}" if ! git diff --quiet || ! git diff --cached --quiet; then dirty=yes warn "${path} — 로컬 변경 있음 (fetch는 진행, fast-forward 실패 가능)" else dirty=no fi old="$(git rev-parse HEAD 2>/dev/null || echo none)" if $DRY_RUN; then git fetch origin "${branch}" 2>/dev/null || git fetch origin new="$(git rev-parse "origin/${branch}" 2>/dev/null || echo unknown)" if [[ "${old}" == "${new}" ]]; then log " [dry-run] 이미 최신: ${old:0:7}" else log " [dry-run] ${old:0:7} → ${new:0:7}" git log --oneline "${old}..origin/${branch}" 2>/dev/null | sed 's/^/ /' || true fi exit 0 fi git fetch origin "${branch}" git checkout "${branch}" 2>/dev/null || git checkout -B "${branch}" "origin/${branch}" if git merge-base --is-ancestor HEAD "origin/${branch}" 2>/dev/null \ && [[ "$(git rev-parse HEAD)" != "$(git rev-parse "origin/${branch}")" ]]; then git merge --ff-only "origin/${branch}" elif [[ "$(git rev-parse HEAD)" != "$(git rev-parse "origin/${branch}")" ]]; then warn "${path} — fast-forward 불가 (로컬 커밋/변경 존재). 수동 merge 필요" git status -sb | sed 's/^/ /' exit 1 fi new="$(git rev-parse HEAD)" if [[ "${old}" == "${new}" ]]; then log " ✅ 최신 유지 $(git log -1 --oneline)" else log " ✅ 갱신 ${old:0:7} → ${new:0:7} $(git log -1 --oneline)" fi ) } failures=0 for path in "${SUBMODULE_PATHS[@]}"; do if ! fetch_one "${path}"; then failures=$((failures + 1)) fi echo "" done if ! $DRY_RUN; then restore_monorepo_overlays fi log "==========================================" log "submodule 상태:" git submodule status || true log "==========================================" if [[ ${failures} -gt 0 ]]; then warn "실패 ${failures}개 — 로그 확인" exit 1 fi log "완료 — GitHub → 로컬 반영 (push 없음)" log "monorepo 커밋 갱신: git add .gitmodules *-chatbot2.0-agent && git status"