- content_hashes_custom_precommit 필드 추가 (pre-commit 제외 해시) - push/mr/release Step 0.5: custom_pre_commit 플래그에 따라 해시 비교 분기 - update-hash.sh: HASH_NO_PC_FILE 초기화 버그 수정 + 두 세트 해시 동시 계산
234 lines
8.7 KiB
Markdown
234 lines
8.7 KiB
Markdown
---
|
|
name: release
|
|
description: develop에서 main으로 릴리즈 MR을 생성합니다
|
|
user-invokable: true
|
|
---
|
|
|
|
develop 브랜치와 원격 동기화를 확인하고, 릴리즈 노트를 정리하고, develop → main 릴리즈 MR을 생성합니다.
|
|
사용자 승인 시 claude-bot으로 PR 승인 + 머지까지 자동 처리합니다.
|
|
|
|
## 수행 단계
|
|
|
|
### 0. 권한 확인
|
|
|
|
```bash
|
|
# GITEA_TOKEN 확인
|
|
if [ -z "$GITEA_TOKEN" ]; then
|
|
echo "GITEA_TOKEN이 필요합니다. Gitea → Settings → Applications에서 생성하세요"
|
|
exit 1
|
|
fi
|
|
|
|
# Gitea API로 리포 권한 조회
|
|
REMOTE_URL=$(git remote get-url origin)
|
|
GITEA_HOST=$(echo "$REMOTE_URL" | sed -E 's|^https?://([^/]+)/.*|\1|')
|
|
REPO_PATH=$(echo "$REMOTE_URL" | grep -oE '[^/]+/[^/]+\.git$' | sed 's/.git$//')
|
|
PERMISSIONS=$(curl -sf "https://${GITEA_HOST}/api/v1/repos/${REPO_PATH}" \
|
|
-H "Authorization: token ${GITEA_TOKEN}")
|
|
IS_ADMIN=$(echo "$PERMISSIONS" | python3 -c "import sys,json; print(json.load(sys.stdin)['permissions']['admin'])")
|
|
```
|
|
|
|
- `IS_ADMIN`이 `False`이면: "릴리즈는 프로젝트 관리자만 실행할 수 있습니다." 안내 후 종료
|
|
|
|
### 0.5. 팀 워크플로우 최신화 확인
|
|
|
|
`.claude/workflow-version.json`이 존재하지 않으면 이 단계를 건너뛴다 (팀 프로젝트가 아닌 경우).
|
|
|
|
```bash
|
|
# 로컬 설정 읽기
|
|
GITEA_URL=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('gitea_url', 'https://gitea.gc-si.dev'))" 2>/dev/null)
|
|
PROJECT_TYPE=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('project_type', ''))" 2>/dev/null)
|
|
CUSTOM_PRECOMMIT=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('custom_pre_commit', False))" 2>/dev/null)
|
|
|
|
# 서버 해시 조회 (custom_pre_commit이면 pre-commit 제외 해시 사용)
|
|
SERVER_VER=$(curl -sf --max-time 5 "${GITEA_URL}/gc/template-common/raw/branch/develop/workflow-version.json")
|
|
if [ "$CUSTOM_PRECOMMIT" = "True" ]; then
|
|
SERVER_HASH=$(echo "$SERVER_VER" | python3 -c "import sys,json; print(json.load(sys.stdin).get('content_hashes_custom_precommit',{}).get('${PROJECT_TYPE}',''))" 2>/dev/null)
|
|
else
|
|
SERVER_HASH=$(echo "$SERVER_VER" | python3 -c "import sys,json; print(json.load(sys.stdin).get('content_hashes',{}).get('${PROJECT_TYPE}',''))" 2>/dev/null)
|
|
fi
|
|
|
|
# 로컬 해시 계산 (custom_pre_commit이면 .githooks/pre-commit 제외)
|
|
if [ "$CUSTOM_PRECOMMIT" = "True" ]; then
|
|
LOCAL_HASH=$(find .claude/rules .claude/agents .claude/scripts .githooks \
|
|
.claude/skills/push .claude/skills/mr .claude/skills/create-mr \
|
|
.claude/skills/release .claude/skills/version .claude/skills/fix-issue \
|
|
-type f ! -path '.githooks/pre-commit' 2>/dev/null | sort | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
|
|
else
|
|
LOCAL_HASH=$(find .claude/rules .claude/agents .claude/scripts .githooks \
|
|
.claude/skills/push .claude/skills/mr .claude/skills/create-mr \
|
|
.claude/skills/release .claude/skills/version .claude/skills/fix-issue \
|
|
-type f 2>/dev/null | sort | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
|
|
fi
|
|
```
|
|
|
|
**비교 결과 처리**:
|
|
- **서버 조회 실패** (`SERVER_HASH` 비어있음): "⚠️ 서버 연결 불가, 워크플로우 체크를 건너뜁니다" 경고 후 다음 단계 진행
|
|
- **일치** (`LOCAL_HASH == SERVER_HASH`): 다음 단계 진행
|
|
- **불일치**: "⚠️ 팀 워크플로우가 최신이 아닙니다. 동기화를 실행합니다..." 출력 → **sync-team-workflow 절차를 자동 실행** → 완료 후 원래 작업 계속
|
|
|
|
### 1. 사전 검증
|
|
|
|
- 커밋되지 않은 변경 사항이 있으면 경고 ("먼저 /push로 커밋하세요")
|
|
|
|
### 2. develop 브랜치 동기화 확인
|
|
|
|
```bash
|
|
git fetch origin
|
|
LOCAL=$(git rev-parse develop 2>/dev/null)
|
|
REMOTE=$(git rev-parse origin/develop 2>/dev/null)
|
|
BASE=$(git merge-base develop origin/develop 2>/dev/null)
|
|
```
|
|
|
|
| 상태 | 조건 | 행동 |
|
|
|------|------|------|
|
|
| 동일 | LOCAL == REMOTE | 바로 진행 |
|
|
| 로컬 뒤처짐 | LOCAL == BASE, LOCAL != REMOTE | "origin/develop에 새 커밋이 있습니다. `git pull origin develop` 후 다시 시도하세요" |
|
|
| 로컬 앞섬 | REMOTE == BASE, LOCAL != REMOTE | "push되지 않은 커밋이 있습니다" → 사용자 확인 후 push |
|
|
| 분기됨 | 그 외 | "분기되었습니다. 수동으로 해결해주세요" |
|
|
|
|
### 3. develop → main 차이 분석
|
|
|
|
```bash
|
|
git log main..origin/develop --oneline
|
|
git diff main..origin/develop --stat
|
|
git rev-list --count main..origin/develop
|
|
```
|
|
|
|
차이가 없으면 "릴리즈할 변경이 없습니다" 출력 후 종료.
|
|
|
|
### 4. 미기록 커밋 감지
|
|
|
|
`docs/RELEASE-NOTES.md`에 기록되지 않은 커밋이 있는지 확인:
|
|
|
|
```bash
|
|
# develop의 커밋 중 RELEASE-NOTES.md에 언급되지 않은 feat/fix 커밋 추출
|
|
git log main..origin/develop --format="%s" | grep -E "^(feat|fix|refactor|perf|docs|test|style|chore|ci)"
|
|
```
|
|
|
|
- 릴리즈 노트의 [Unreleased] + 날짜 릴리즈 항목과 비교
|
|
- 미기록 커밋이 있으면 경고 표시 + 보충 기회 제공
|
|
- 사용자에게 미기록 커밋 목록 표시
|
|
- [Unreleased]에 추가할지 확인
|
|
|
|
### 5. 이전 날짜 넘버링 릴리즈 압축
|
|
|
|
`docs/RELEASE-NOTES.md`에서 **오늘 이전 날짜에 넘버링된 릴리즈**가 있으면 압축:
|
|
|
|
```
|
|
[2026-02-28.1] + [2026-02-28.2] → [2026-02-28]
|
|
```
|
|
|
|
압축 규칙:
|
|
- 같은 기능의 반복 수정 → 최종 상태만 유지
|
|
- 추가 후 수정된 항목 → "추가" 섹션에 최종 상태로 합침
|
|
- 추가 후 제거된 항목 → 양쪽 모두에서 삭제
|
|
- 오늘 날짜의 넘버링은 유지 (압축 대상 아님)
|
|
|
|
### 6. [Unreleased] → 날짜 버전 전환
|
|
|
|
```
|
|
## [Unreleased] → ## [2026-03-01] 또는 ## [2026-03-01.2]
|
|
```
|
|
|
|
- 같은 날 이전 릴리즈가 있으면 순번 부여: `.1`, `.2`, `.3`
|
|
- [Unreleased]는 빈 섹션으로 다시 생성
|
|
|
|
### 7. 사용자 첨삭 확인
|
|
|
|
최종 릴리즈 노트를 표시하고 **AskUserQuestion**으로 확인:
|
|
- **질문**: "릴리즈 노트 최종 초안입니다. 수정할 내용이 있으면 알려주세요."
|
|
- 옵션 1: 이대로 진행 (추천)
|
|
- 옵션 2: 수정 (Other 입력)
|
|
|
|
### 8. develop 커밋 + 푸시
|
|
|
|
```bash
|
|
git checkout develop
|
|
git add docs/RELEASE-NOTES.md
|
|
git commit -m "docs: 릴리즈 노트 정리 ($(date +%Y-%m-%d))"
|
|
git push origin develop
|
|
```
|
|
|
|
### 9. MR 정보 구성 + 생성
|
|
|
|
**제목**: `release: YYYY-MM-DD (N건 커밋)`
|
|
|
|
**본문**: 릴리즈 노트 내용 포함 (커밋 type별 그룹핑)
|
|
|
|
```bash
|
|
curl -X POST "https://{host}/api/v1/repos/{owner}/{repo}/pulls" \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"title": "release: 2026-03-01 (12건 커밋)",
|
|
"body": "릴리즈 본문 (RELEASE-NOTES.md 내용 포함)",
|
|
"head": "develop",
|
|
"base": "main"
|
|
}'
|
|
```
|
|
|
|
### 10. PR 승인 + 머지 (선택)
|
|
|
|
**사용자 확인** (AskUserQuestion):
|
|
- **질문**: "MR을 승인하고 머지하시겠습니까?"
|
|
- 옵션 1: 승인 + 머지 (추천)
|
|
- 옵션 2: MR만 생성 (수동 리뷰/머지)
|
|
|
|
**승인 + 머지 선택 시**:
|
|
|
|
```bash
|
|
# CLAUDE_BOT_TOKEN 확인
|
|
if [ -z "$CLAUDE_BOT_TOKEN" ]; then
|
|
echo "CLAUDE_BOT_TOKEN이 설정되지 않아 자동 승인이 불가합니다. MR만 생성합니다."
|
|
# MR URL 출력 후 종료
|
|
fi
|
|
|
|
# 1. claude-bot이 PR 리뷰 승인
|
|
curl -X POST "https://{host}/api/v1/repos/{owner}/{repo}/pulls/{pr_number}/reviews" \
|
|
-H "Authorization: token ${CLAUDE_BOT_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"event": "APPROVED", "body": "릴리즈 승인 (via /release skill)"}'
|
|
|
|
# 2. 머지 실행
|
|
curl -X POST "https://{host}/api/v1/repos/{owner}/{repo}/pulls/{pr_number}/merge" \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"Do": "merge", "merge_message_field": "release: YYYY-MM-DD", "delete_branch_after_merge": false}'
|
|
```
|
|
|
|
⚠️ `delete_branch_after_merge: false` — develop 브랜치는 삭제하지 않는다.
|
|
|
|
### 11. 결과 출력
|
|
|
|
```
|
|
✅ 릴리즈 완료
|
|
브랜치: develop → main
|
|
MR: https://gitea.gc-si.dev/gc/my-project/pulls/50
|
|
커밋: 12건, 파일: 28개 변경
|
|
릴리즈 노트: [2026-03-01] 생성됨
|
|
PR 승인: claude-bot ✅
|
|
머지: 완료 ✅
|
|
CI/CD: 자동 배포 시작됨
|
|
|
|
다음 단계: CI/CD 배포 결과 확인
|
|
```
|
|
|
|
또는 MR만 생성한 경우:
|
|
|
|
```
|
|
✅ 릴리즈 MR 생성 완료
|
|
브랜치: develop → main
|
|
MR: https://gitea.gc-si.dev/gc/my-project/pulls/50
|
|
릴리즈 노트: [2026-03-01] 생성됨
|
|
|
|
다음 단계:
|
|
1. 리뷰어 지정 (main 브랜치는 1명 이상 리뷰 필수)
|
|
2. 승인 후 머지
|
|
3. CI/CD 자동 배포 확인
|
|
```
|
|
|
|
## 필요 환경변수
|
|
|
|
- `GITEA_TOKEN`: Gitea API 접근 토큰 (필수)
|
|
- `CLAUDE_BOT_TOKEN`: claude-bot PR 승인용 토큰 (선택, 없으면 자동 승인 건너뜀)
|