포스트

[이제와서 시작하는 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 마스터하기 시리즈

🌱 기초편 (입문자)

  1. GitHub 시작하기
  2. Repository 기초
  3. Git 기본 명령어
  4. Branch와 Merge
  5. Fork와 Pull Request

💼 실전편 (중급자)

  1. Issues 활용법
  2. Projects로 프로젝트 관리
  3. Code Review 잘하기
  4. GitHub Discussions
  5. Team 협업 설정
  6. GitHub Pages

🚀 고급편 (전문가)

  1. GitHub Actions 입문
  2. Actions 고급 활용
  3. Webhooks와 API
  4. GitHub Apps 개발
  5. 보안 기능
  6. GitHub Packages
  7. Codespaces
  8. GitHub CLI
  9. 통계와 인사이트

🏆 심화편 (전문가+)

  1. Git Submodules & Subtree (현재 글)
  2. Git 내부 동작 원리
  3. 고급 브랜치 전략과 릴리스 관리
  4. GitHub GraphQL API
  5. GitHub Copilot 완벽 활용
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.