[이제와서 시작하는 GitHub 마스터하기 - 심화편 #1] Git Submodules & Subtree: 대규모 프로젝트 관리 전략
[이제와서 시작하는 GitHub 마스터하기 - 심화편 #1] Git Submodules & Subtree: 대규모 프로젝트 관리 전략
들어가며
GitHub 마스터하기 시리즈의 심화편을 시작합니다. 첫 번째 주제는 많은 개발자들이 어려워하지만 대규모 프로젝트에서는 필수적인 Git Submodules와 Subtree입니다. 여러 저장소를 효율적으로 관리하고, 코드 재사용성을 높이며, 의존성을 체계적으로 관리하는 방법을 깊이 있게 다뤄보겠습니다.
1. Monorepo vs Polyrepo 전략
아키텍처 비교
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Monorepo (단일 저장소):
장점:
- 통합된 버전 관리
- 쉬운 리팩토링
- 일관된 도구 사용
- 원자적 커밋
단점:
- 거대한 저장소 크기
- 복잡한 권한 관리
- CI/CD 복잡성
- 성능 이슈 가능성
사용 사례:
- Google (86TB, 20억 라인)
- Facebook
- Twitter
Polyrepo (다중 저장소):
장점:
- 독립적인 개발
- 세밀한 권한 관리
- 작은 저장소 크기
- 명확한 경계
단점:
- 의존성 관리 복잡
- 버전 동기화 어려움
- 중복 코드 가능성
- 교차 저장소 변경 어려움
사용 사례:
- Netflix
- Amazon (대부분)
하이브리드 접근법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 프로젝트 구조 예시
company/
├── frontend-monorepo/ # 프론트엔드 Monorepo
│ ├── packages/
│ │ ├── web-app/
│ │ ├── mobile-app/
│ │ └── shared-components/
│ └── package.json
│
├── backend-services/ # 백엔드 Polyrepo
│ ├── auth-service/ (별도 저장소)
│ ├── payment-service/ (별도 저장소)
│ └── notification-service/ (별도 저장소)
│
└── shared-libraries/ # 공유 라이브러리
├── utils/ (submodule)
└── config/ (submodule)
2. Git Submodules 심화
Submodule 기본 개념
1
2
3
4
5
6
7
8
9
# Submodule 추가
git submodule add https://github.com/company/shared-lib libs/shared
git commit -m "Add shared library as submodule"
# .gitmodules 파일 생성됨
[submodule "libs/shared"]
path = libs/shared
url = https://github.com/company/shared-lib
branch = main # 특정 브랜치 추적
고급 Submodule 관리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 1. 재귀적 클론
git clone --recurse-submodules https://github.com/company/main-project
# 또는 이미 클론한 경우
git submodule update --init --recursive
# 2. 모든 서브모듈 업데이트
git submodule update --remote --merge
# 3. 서브모듈에서 작업하기
cd libs/shared
git checkout -b feature/new-feature
# ... 변경사항 작업 ...
git add .
git commit -m "Add new feature"
git push origin feature/new-feature
# 4. 메인 프로젝트에서 서브모듈 커밋 참조 업데이트
cd ../..
git add libs/shared
git commit -m "Update shared library reference"
# 5. 서브모듈 브랜치 추적 설정
git config -f .gitmodules submodule.libs/shared.branch develop
git submodule update --remote
# 6. 서브모듈 foreach 명령
git submodule foreach 'git pull origin main'
git submodule foreach 'git checkout -b feature/update'
Submodule 워크플로우 자동화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# .github/workflows/submodule-update.yml
name: Update Submodules
on:
schedule:
- cron: '0 0 * * 1' # 매주 월요일
workflow_dispatch:
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
token: $
- name: Update submodules
run: |
git config user.name github-actions
git config user.email github-actions@github.com
# 모든 서브모듈 최신 버전으로 업데이트
git submodule update --init --recursive
git submodule foreach git pull origin main
# 변경사항 확인
if [[ `git status --porcelain` ]]; then
git add .
git commit -m "chore: update submodules to latest version"
git push
fi
Submodule 문제 해결
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 서브모듈 충돌 해결
git submodule update --init --merge
# 충돌 발생 시
cd path/to/submodule
git status # 충돌 확인
# 충돌 해결 후
git add .
git commit -m "Resolve conflicts"
cd ../..
git add path/to/submodule
git commit -m "Update submodule after conflict resolution"
# 2. 서브모듈 제거
git submodule deinit -f path/to/submodule
rm -rf .git/modules/path/to/submodule
git rm -f path/to/submodule
# 3. 깨진 서브모듈 복구
git submodule sync --recursive
git submodule update --init --recursive --force
# 4. 서브모듈 URL 변경
git submodule set-url path/to/submodule https://new-url.git
git submodule sync --recursive
3. Git Subtree 전략
Subtree vs Submodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Subtree 특징:
장점:
- 저장소에 코드가 직접 포함
- 클론 시 추가 명령 불필요
- 하위 프로젝트 수정 용이
- 히스토리 통합 가능
단점:
- 업스트림 기여가 복잡
- 히스토리가 복잡해질 수 있음
- 대용량 프로젝트에 부적합
Submodule 특징:
장점:
- 명확한 프로젝트 경계
- 독립적인 버전 관리
- 작은 메인 저장소 크기
단점:
- 추가 명령어 필요
- 학습 곡선 높음
- 동기화 이슈 가능
Subtree 실전 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. Subtree 추가
git subtree add --prefix=libs/shared \
https://github.com/company/shared-lib main --squash
# 2. Subtree 업데이트 (pull)
git subtree pull --prefix=libs/shared \
https://github.com/company/shared-lib main --squash
# 3. Subtree로 변경사항 푸시 (contribute back)
git subtree push --prefix=libs/shared \
https://github.com/company/shared-lib feature/update
# 4. 별칭 설정으로 간소화
git config alias.shared-pull 'subtree pull --prefix=libs/shared https://github.com/company/shared-lib main --squash'
git config alias.shared-push 'subtree push --prefix=libs/shared https://github.com/company/shared-lib'
# 이제 간단하게 사용
git shared-pull
git shared-push feature/update
고급 Subtree 전략
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/bin/bash
# subtree-manager.sh - Subtree 관리 스크립트
# 설정
declare -A SUBTREES
SUBTREES["libs/ui"]="https://github.com/company/ui-library"
SUBTREES["libs/auth"]="https://github.com/company/auth-library"
SUBTREES["libs/utils"]="https://github.com/company/utils"
# 모든 subtree 업데이트
update_all_subtrees() {
for prefix in "${!SUBTREES[@]}"; do
repo="${SUBTREES[$prefix]}"
echo "Updating $prefix from $repo..."
git subtree pull --prefix="$prefix" "$repo" main --squash
done
}
# 특정 subtree에 기여
contribute_to_subtree() {
local prefix=$1
local branch=$2
if [[ -z "${SUBTREES[$prefix]}" ]]; then
echo "Unknown subtree: $prefix"
exit 1
fi
repo="${SUBTREES[$prefix]}"
echo "Pushing $prefix to $repo branch $branch..."
git subtree push --prefix="$prefix" "$repo" "$branch"
}
# Split subtree (히스토리 분리)
split_subtree() {
local prefix=$1
local new_branch=$2
echo "Splitting $prefix into branch $new_branch..."
git subtree split --prefix="$prefix" -b "$new_branch"
}
# 사용 예
case "$1" in
update)
update_all_subtrees
;;
contribute)
contribute_to_subtree "$2" "$3"
;;
split)
split_subtree "$2" "$3"
;;
*)
echo "Usage: $0 {update|contribute <prefix> <branch>|split <prefix> <branch>}"
exit 1
;;
esac
4. 실전 프로젝트 구조
마이크로서비스 아키텍처
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# docker-compose.yml - 개발 환경
version: '3.8'
services:
frontend:
build: ./frontend # subtree
ports:
- "3000:3000"
depends_on:
- api-gateway
api-gateway:
build: ./services/gateway # submodule
ports:
- "8080:8080"
environment:
- AUTH_SERVICE_URL=http://auth:3001
- USER_SERVICE_URL=http://user:3002
auth:
build: ./services/auth # submodule
ports:
- "3001:3001"
user:
build: ./services/user # submodule
ports:
- "3002:3002"
shared-db:
image: postgres:15
environment:
POSTGRES_DB: microservices
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
프로젝트 초기화 스크립트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/bin/bash
# init-project.sh
echo "🚀 Initializing microservices project..."
# 메인 저장소 생성
git init microservices-platform
cd microservices-platform
# 디렉토리 구조 생성
mkdir -p services docs scripts
# Submodules 추가 (독립적인 서비스)
echo "📦 Adding service submodules..."
git submodule add https://github.com/company/auth-service services/auth
git submodule add https://github.com/company/user-service services/user
git submodule add https://github.com/company/gateway-service services/gateway
# Subtrees 추가 (통합 관리가 필요한 컴포넌트)
echo "🌳 Adding subtrees..."
git subtree add --prefix=frontend \
https://github.com/company/frontend-app main --squash
git subtree add --prefix=shared/config \
https://github.com/company/config-templates main --squash
# 개발 환경 설정
echo "⚙️ Setting up development environment..."
cat > docker-compose.yml << 'EOF'
# ... docker-compose 내용 ...
EOF
# Git hooks 설정
echo "🔗 Setting up git hooks..."
cat > .git/hooks/post-checkout << 'EOF'
#!/bin/bash
# Submodule 자동 업데이트
git submodule update --init --recursive
EOF
chmod +x .git/hooks/post-checkout
# Makefile 생성
cat > Makefile << 'EOF'
.PHONY: help init update-all dev test deploy
help:
@echo "Available commands:"
@echo " make init - Initialize all submodules and subtrees"
@echo " make update-all - Update all dependencies"
@echo " make dev - Start development environment"
@echo " make test - Run all tests"
@echo " make deploy - Deploy to production"
init:
git submodule update --init --recursive
npm install
update-all:
git submodule foreach git pull origin main
./scripts/update-subtrees.sh
dev:
docker-compose up -d
test:
docker-compose run --rm test-runner
deploy:
./scripts/deploy.sh production
EOF
echo "✅ Project initialized successfully!"
5. 의존성 관리 고급 전략
버전 고정 vs 유동적 추적
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 1. 특정 태그/릴리스 고정
cd services/auth
git checkout v1.2.3
cd ../..
git add services/auth
git commit -m "Pin auth service to v1.2.3"
# 2. 브랜치 추적 설정
git config -f .gitmodules submodule.services/auth.branch release-1.2
git submodule update --remote services/auth
# 3. 의존성 매트릭스 관리
cat > dependency-matrix.yml << 'EOF'
dependencies:
services:
auth:
version: v1.2.3
branch: release-1.2
critical: true
user:
version: v2.0.1
branch: main
critical: true
gateway:
version: v1.5.0
branch: main
critical: true
libraries:
shared-ui:
version: v3.1.0
type: subtree
config:
version: latest
type: subtree
compatibility:
auth:
requires:
- user: ">=2.0.0"
- gateway: ">=1.5.0"
EOF
자동화된 의존성 검증
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// verify-dependencies.js
const yaml = require('js-yaml');
const { execSync } = require('child_process');
const fs = require('fs');
class DependencyVerifier {
constructor(configPath = 'dependency-matrix.yml') {
this.config = yaml.load(fs.readFileSync(configPath, 'utf8'));
}
async verifyAll() {
console.log('🔍 Verifying dependencies...\n');
const results = {
passed: [],
failed: [],
warnings: []
};
// Submodules 검증
for (const [name, spec] of Object.entries(this.config.dependencies.services)) {
const result = await this.verifySubmodule(name, spec);
if (result.status === 'pass') {
results.passed.push(result);
} else if (result.status === 'fail') {
results.failed.push(result);
} else {
results.warnings.push(result);
}
}
// 호환성 검증
for (const [service, requirements] of Object.entries(this.config.compatibility)) {
const compatResult = await this.verifyCompatibility(service, requirements);
if (!compatResult.compatible) {
results.failed.push(compatResult);
}
}
// 결과 출력
this.printResults(results);
return results.failed.length === 0;
}
async verifySubmodule(name, spec) {
try {
const path = `services/${name}`;
const currentCommit = execSync(
`git -C ${path} rev-parse HEAD`
).toString().trim();
const expectedTag = spec.version;
const expectedCommit = execSync(
`git -C ${path} rev-list -n 1 ${expectedTag}`
).toString().trim();
if (currentCommit === expectedCommit) {
return {
status: 'pass',
service: name,
message: `✅ ${name} is at expected version ${expectedTag}`
};
} else {
return {
status: 'fail',
service: name,
message: `❌ ${name} version mismatch. Expected: ${expectedTag}, Current: ${currentCommit.substring(0, 7)}`
};
}
} catch (error) {
return {
status: 'fail',
service: name,
message: `❌ Error verifying ${name}: ${error.message}`
};
}
}
async verifyCompatibility(service, requirements) {
// 실제로는 각 서비스의 package.json이나 버전 파일을 확인
console.log(`Checking compatibility for ${service}...`);
for (const [dep, versionReq] of Object.entries(requirements.requires || {})) {
// 버전 비교 로직
console.log(` - ${dep}: ${versionReq}`);
}
return { compatible: true, service };
}
printResults(results) {
console.log('\n📊 Verification Results:\n');
if (results.passed.length > 0) {
console.log('✅ Passed:');
results.passed.forEach(r => console.log(` ${r.message}`));
}
if (results.warnings.length > 0) {
console.log('\n⚠️ Warnings:');
results.warnings.forEach(r => console.log(` ${r.message}`));
}
if (results.failed.length > 0) {
console.log('\n❌ Failed:');
results.failed.forEach(r => console.log(` ${r.message}`));
}
console.log(`\nTotal: ${results.passed.length} passed, ${results.warnings.length} warnings, ${results.failed.length} failed`);
}
}
// 실행
const verifier = new DependencyVerifier();
verifier.verifyAll().then(success => {
process.exit(success ? 0 : 1);
});
6. CI/CD 파이프라인 통합
GitHub Actions 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# .github/workflows/monorepo-ci.yml
name: Monorepo CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
services: $
frontend: $
shared: $
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
frontend:
- 'frontend/**'
auth:
- 'services/auth/**'
user:
- 'services/user/**'
gateway:
- 'services/gateway/**'
shared:
- 'shared/**'
- 'libs/**'
verify-dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Verify dependency versions
run: |
node scripts/verify-dependencies.js
- name: Check for security vulnerabilities
run: |
git submodule foreach 'npm audit --production'
build-services:
needs: [detect-changes, verify-dependencies]
if: ${{ needs.detect-changes.outputs.services != '[]' }}
strategy:
matrix:
service: ${{ fromJson(needs.detect-changes.outputs.services) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Build $
run: |
cd services/$
docker build -t company/$:$ .
- name: Run tests
run: |
cd services/$
npm test
- name: Push to registry
if: github.ref == 'refs/heads/main'
run: |
echo $ | docker login -u $ --password-stdin
docker push company/$:$
update-deployment-manifest:
needs: build-services
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Update K8s manifests
run: |
# Update image tags in deployment manifests
for service in ${{ join(fromJson(needs.detect-changes.outputs.services), ' ') }}; do
sed -i "s|company/${service}:.*|company/${service}:$|g" \
k8s/deployments/${service}.yaml
done
- name: Commit and push changes
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git add k8s/deployments/
git commit -m "Update service images to $"
git push
7. 문제 해결과 디버깅
일반적인 문제들
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 1. Submodule 포인터 불일치
# 증상: "Changes not staged for commit" in submodule
git submodule update --init --recursive
# 또는 강제 리셋
git submodule foreach --recursive git reset --hard
# 2. Subtree 병합 충돌
# 증상: Merge conflict in subtree pull
git status # 충돌 파일 확인
# 수동으로 충돌 해결 후
git add .
git commit -m "Resolve subtree merge conflicts"
# 3. 권한 문제
# 증상: Permission denied when updating submodule
# SSH 키 확인
ssh -T git@github.com
# HTTPS 인증 정보 갱신
git config --global credential.helper cache
# 4. 히스토리 크기 문제
# 대용량 저장소 최적화
git gc --aggressive --prune=now
git repack -a -d -f --depth=250 --window=250
# 5. 순환 의존성
# dependency-check.sh
#!/bin/bash
check_circular_deps() {
local visited=()
local stack=()
# DFS로 순환 의존성 검사
# ... 구현 ...
}
성능 최적화
1
2
3
4
5
6
7
8
9
10
11
12
# 1. Shallow 클론 사용
git clone --depth 1 --recurse-submodules --shallow-submodules URL
# 2. 병렬 서브모듈 페치
git config submodule.fetchJobs 8
# 3. 부분 클론 (Git 2.25+)
git clone --filter=blob:none --recurse-submodules URL
# 4. 스파스 체크아웃
git sparse-checkout init --cone
git sparse-checkout set services/auth services/user
8. 마이그레이션 전략
Monorepo로 마이그레이션
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/bin/bash
# migrate-to-monorepo.sh
# 1. 새 monorepo 생성
git init company-monorepo
cd company-monorepo
# 2. 각 저장소를 subtree로 추가
repos=(
"frontend-app"
"auth-service"
"user-service"
"shared-utils"
)
for repo in "${repos[@]}"; do
echo "Migrating $repo..."
# 히스토리 보존하며 추가
git subtree add --prefix="packages/$repo" \
"https://github.com/company/$repo.git" main
# 또는 스쿼시로 깔끔하게
# git subtree add --prefix="packages/$repo" \
# "https://github.com/company/$repo.git" main --squash
done
# 3. 루트 설정 파일 생성
cat > package.json << 'EOF'
{
"name": "company-monorepo",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"bootstrap": "lerna bootstrap",
"build": "lerna run build",
"test": "lerna run test",
"release": "lerna version"
}
}
EOF
# 4. Lerna 설정
cat > lerna.json << 'EOF'
{
"version": "independent",
"npmClient": "npm",
"command": {
"publish": {
"conventionalCommits": true,
"message": "chore(release): publish"
}
},
"packages": ["packages/*"]
}
EOF
# 5. CI/CD 업데이트
mkdir -p .github/workflows
# ... workflow 파일 생성 ...
echo "✅ Migration complete!"
9. 베스트 프랙티스
선택 가이드라인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Submodule 사용 시:
✅ 서드파티 라이브러리 통합
✅ 독립적으로 버전 관리되는 컴포넌트
✅ 여러 프로젝트에서 공유되는 코드
✅ 엄격한 버전 관리가 필요한 경우
❌ 자주 수정되는 내부 코드
❌ 팀원 대부분이 Git 초보자
Subtree 사용 시:
✅ 프로젝트에 통합하여 관리할 코드
✅ 업스트림 기여가 적은 경우
✅ 간단한 워크플로우 선호
❌ 독립적인 릴리스 사이클 필요
❌ 대용량 히스토리 보존 필요
Monorepo 사용 시:
✅ 긴밀하게 연결된 프로젝트들
✅ 공유 코드가 많은 경우
✅ 통합 테스트가 중요한 경우
❌ 팀/프로젝트가 독립적
❌ 권한 관리가 복잡한 경우
팀 가이드라인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## Submodule/Subtree 관리 규칙
### 1. 커밋 메시지 규칙
- Submodule 업데이트: `chore(deps): update [submodule-name] to [version/commit]`
- Subtree 업데이트: `chore(deps): sync [subtree-name] from upstream`
### 2. 업데이트 주기
- Submodules: 매주 보안 업데이트 확인
- Subtrees: 월 1회 upstream 동기화
### 3. 리뷰 프로세스
- Submodule 포인터 변경은 반드시 리뷰 필요
- 변경 내용 요약을 PR 설명에 포함
### 4. 비상 대응
- Rollback 절차 문서화
- 의존성 버전 고정 전략
마무리
Git Submodules와 Subtree는 복잡하지만 강력한 도구입니다. 프로젝트의 규모와 팀의 요구사항에 맞는 전략을 선택하는 것이 중요합니다.
핵심 포인트:
- Monorepo vs Polyrepo는 정답이 없음
- Submodule은 엄격한 경계와 버전 관리에 적합
- Subtree는 통합과 간편함을 우선시
- 자동화와 검증이 성공의 열쇠
- 팀의 Git 숙련도를 고려한 선택
대규모 프로젝트를 효율적으로 관리하고, 코드 재사용성을 높이며, 팀 협업을 개선하는 데 이 가이드가 도움이 되기를 바랍니다.
다음 심화편에서는 GitHub GraphQL API 마스터하기에 대해 다루겠습니다.
📚 GitHub 마스터하기 시리즈
🌱 기초편 (입문자)
💼 실전편 (중급자)
🚀 고급편 (전문가)
- GitHub Actions 입문
- Actions 고급 활용
- Webhooks와 API
- GitHub Apps 개발
- 보안 기능
- GitHub Packages
- Codespaces
- GitHub CLI
- 통계와 인사이트
🏆 심화편 (전문가+)
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.