JUNSEOK
06 · SW 공학·18분·4개 레슨

버전 관리 Git

Git 내부 구조, 브랜치 전략, rebase vs merge.

학습 목표

  • Git 내부 모델(Object DAG, refs, index)을 이해한다
  • merge / rebase / cherry-pick의 차이와 선택 기준을 안다
  • Conventional Commits·브랜칭 전략·PR 리뷰 문화를 실무 레벨로 익힌다
  • 잘못된 상태 복구(reflog, reset, revert)에 자신감을 갖는다

0. Git이 뭔지 다시 짚기 — 초심자용

0-1. 버전 관리 = "코드의 타임머신"

워드 파일로 작업한다고 상상해보자.

  • "문서.docx" → "문서_수정.docx" → "문서_최종.docx" → "문서_진짜최종.docx" → "문서_진짜진짜최종.docx"

누구나 한 번쯤 해본 실수. 무엇이 필요한가?

  • 각 버전을 자동으로 저장
  • 원하는 버전으로 되돌리기 가능
  • 여러 명이 동시에 편집하고 합치기
  • 누가 언제 무엇을 바꿨는지 기록

이걸 해주는 도구가 버전 관리 시스템 (VCS). Git이 사실상 표준.

0-2. Git의 핵심 아이디어

Git은 파일을 "파일"로 저장하지 않는다. 대신:

  • 파일 내용의 스냅샷을 SHA-1 해시로 식별
  • 커밋 = "이 시점의 모든 파일 스냅샷 + 부모 커밋 + 메시지"
  • 커밋들이 체인 (또는 그래프) 를 형성
[커밋1] ← [커밋2] ← [커밋3] ← main

                        [커밋4] ← feature/login

각 커밋은 부모를 가리킨다. 이게 Git 의 전부. 모든 명령은 이 그래프를 조작하는 것.

0-3. 왜 이 장을 "깊게" 가르치나

많은 개발자가 Git을 "외운 명령어 10개" 로 쓴다. 평소엔 괜찮지만:

  • 충돌 발생 → 패닉
  • 실수로 force push → 복구 못 함
  • 브랜치 꼬임 → 누가 풀어줘야 함
  • "이 커밋만 다른 브랜치에 가져오기" → 불가능하다고 생각

내부 모델을 이해하면 어떤 상황이든 같은 원리로 해결할 수 있다. 이 장이 Git을 "외우지 말고 이해" 하게 만드는 목표.

0-4. 이 장에서 다루는 주제

주제한 줄
객체 모델blob, tree, commit, tag — Git의 4가지 핵심 객체
refs브랜치, 태그, HEAD — 커밋을 가리키는 이름표
index (스테이징 영역)작업 디렉터리와 저장소 사이의 "대기실"
merge vs rebase두 브랜치 합치는 두 가지 방법
cherry-pick"이 커밋 하나만" 가져오기
reflog삭제한 브랜치·커밋도 복구할 수 있는 "진짜 로그"
reset vs revert되돌리기의 두 가지 철학
Conventional Commits커밋 메시지 표준
브랜칭 전략GitFlow, GitHub Flow, Trunk-based

0-5. Git = 분산 버전 관리

"분산(distributed)" = 모든 개발자가 전체 저장소 사본을 들고 있음. GitHub 같은 중앙 서버가 없어도 작업 가능. 이 설계 덕분에:

  • 오프라인에서 커밋 가능
  • 서버가 날아가도 누군가의 로컬에 전체 이력 존재
  • 여러 저장소를 자유롭게 연결

0-6. 초보자가 놓치는 것

  • git pullgit fetch + git merge 라는 사실
  • rebase커밋을 "이동"하는 게 아니라 "다시 만드는" 것 — 그래서 해시가 바뀜
  • force push가 동료의 작업을 덮어쓸 수 있다는 점
  • .git 폴더를 열어보면 사람이 읽을 수 있는 구조라는 것

0-7. 이 장을 읽은 뒤 할 수 있는 것

  • 충돌을 침착하게 해결
  • 복잡한 브랜치 상황에서 명확한 계획 세우기
  • 실수 발생 시 reflog 로 복구
  • 팀에 일관된 워크플로우 제안
  • Git 내부를 다른 이에게 설명 가능

1. Git을 "명령어 모음"으로 외우지 말 것

Git 초심자는 git commit -m "fix", git push 같은 명령을 외우는 것으로 시작한다. 하지만 복잡한 상황(충돌, 브랜치 꼬임, 강제 push)에서 길을 잃는 이유는 내부 모델을 모르기 때문이다.

Git은 본질적으로 **"내용 기반 파일 시스템 + 변경 이력의 DAG"**이다. 이 모델을 이해하면 어떤 상황도 일관되게 다룰 수 있다.


2. Git 내부 모델

2-1. 4가지 객체 (Object)

모든 것이 .git/objects/SHA-1 해시로 저장된다. (현대는 SHA-256 지원 중)

객체역할
Blob파일 내용
Tree디렉터리 구조 (blob과 tree 참조)
Commit트리 루트 + 부모 커밋 + 메타 (author, message, timestamp)
Tag특정 커밋에 대한 영구 이름표
commit (SHA: abc123)
├── tree (SHA: def456)
│   ├── blob: README.md
│   └── tree: src/
│       └── blob: index.ts
├── parent: xyz789
├── author: Alice
└── message: "feat: add login"

2-2. Reference (ref)

커밋 SHA를 가리키는 이름표.

  • HEAD: 현재 작업 중인 커밋
  • refs/heads/main: 로컬 브랜치
  • refs/remotes/origin/main: 리모트 추적 브랜치
  • refs/tags/v1.0.0: 태그

git branch는 SHA 이름표를 하나 만드는 것, git checkout은 HEAD를 옮기는 것뿐이다.

2-3. 세 영역 (Three Areas)

[Working Directory]  ←→  [Staging Area (Index)]  ←→  [Repository (.git/objects)]
        ↑                        ↑                         ↑
      edit               git add              git commit
  • Working Directory: 실제 파일
  • Staging (Index): 다음 커밋에 포함할 스냅샷
  • Repository: 커밋된 히스토리

git add내용 기반으로 동작하므로 같은 파일을 add → 수정 → 다시 add 하지 않으면 이전 스냅샷이 커밋된다.

2-4. DAG (Directed Acyclic Graph)

커밋은 부모 커밋을 참조하는 DAG. 브랜치는 "이 노드를 가리키는 이름표"일 뿐.

ABCD (main)

           E (feature)

3. 일상 명령 — 모델로 이해하기

3-1. git add

작업 파일의 blob 생성 + index 갱신. 파일 전체 해시를 계산해 저장한다.

3-2. git commit

index 상태를 기준으로 tree + commit 객체 생성 → HEAD가 새 커밋을 가리키도록 이동.

3-3. git checkout <branch>

해당 브랜치가 가리키는 커밋의 tree를 Working Directory에 복원하고 HEAD 이동.

⚠️ 변경된 파일이 있으면 덮어쓰기 위험. git stash 또는 커밋 필요.

3-4. git switch / git restore (권장)

checkout이 여러 역할을 겸해 헷갈렸기에 Git 2.23(2019)에 분리됐다.

git switch main             # 브랜치 이동
git switch -c feat/login    # 새 브랜치 생성 + 이동
git restore file.ts         # 작업 디렉터리에서 파일 되돌리기
git restore --staged file.ts # staging 취소

3-5. git log

HEAD부터 부모를 따라가며 커밋 메타를 출력.

git log --oneline --graph --all
git log -p <file>           # 파일 변경 이력과 diff
git log -S "functionName"   # 해당 문자열이 추가/제거된 커밋
git log --author="Alice"
git log main..feature       # main에 없는 feature의 커밋

4. merge vs rebase

4-1. merge

두 브랜치 히스토리를 병합 커밋으로 합친다.

Before:                After merge:
ABC (main)       ABCM (main)
         ↖                      ↗
           DE (feat)        DE

특성:

  • 원 히스토리 보존 (분기/합류 흔적 남음)
  • 협업 흐름이 그대로 드러남
  • 브랜치가 많으면 그래프가 복잡

4-2. rebase

feature 브랜치의 커밋들을 main의 최신 위에 다시 쌓는다.

Before:                After rebase feat onto main:
ABC (main)       ABCD' ← E' (feat)

           DE (feat)

특성:

  • 선형 히스토리 (병합 커밋 없음)
  • 깨끗한 로그
  • 커밋 SHA가 바뀐다 → 이미 push된 공용 브랜치에 하면 위험
  • 커밋 하나씩 충돌 해결 필요

4-3. 실무 규칙

상황권장
내 로컬 feature 브랜치를 최신 main 위로rebase
PR 머지 (main에 feature 합칠 때)merge (또는 squash merge)
이미 공유된 브랜치rebase 금지 → merge

4-4. Squash Merge

여러 feature 커밋을 하나로 합쳐 main에 얹는다. 대부분의 GitHub 조직이 기본값.

  • 장점: main 히스토리가 PR 단위로 깔끔
  • 단점: PR 내부 커밋 이력 손실 (중간 실패 이력 포함)

4-5. Rebase Merge

PR 커밋들을 그대로 main 위로 rebase. 각 커밋이 main에 선형으로 들어간다. Conventional Commits를 엄격히 따르는 팀에서 선호.


5. cherry-pick

특정 커밋 하나를 다른 브랜치에 가져다 붙임.

git cherry-pick abc123
git cherry-pick main~3..main  # 범위

용도:

  • 핫픽스 — main에서 고친 커밋을 release 브랜치로
  • 브랜치 전환 중 특정 커밋만 가져오기

주의: cherry-pick은 새 SHA를 만든다. 같은 변경이 두 브랜치에 다른 커밋으로 존재하게 됨 → 이후 merge 시 충돌 가능.


6. Conflict 해결

6-1. 충돌 마커

<<<<<<< HEAD
const config = { env: 'prod' };
=======
const config = { env: 'development' };
>>>>>>> feature

6-2. 해결 절차

  1. 마커를 보고 어느 쪽이 맞는지 의식적으로 결정 (둘 다 반영이 필요한 경우도 있음)
  2. 마커 제거
  3. git add <file>
  4. git commit (merge) 또는 git rebase --continue (rebase)

6-3. 도구

  • VS Code: Merge Editor 내장 (Accept Current / Incoming / Both)
  • git mergetool: 외부 도구 호출
  • 복잡한 충돌은 작성자 둘이 함께 풀기 — 의사소통이 가장 빠른 해결

6-4. rerere

같은 충돌을 자주 겪는 긴 작업이면:

git config --global rerere.enabled true

Git이 이전 해결을 기억해 자동 재적용.


7. 브랜칭 전략

7-1. Git Flow

main     ────●─────────●───
              \       /
release       ●─────●
               \   /
develop  ──●──●──●──●───
            \ /    \
feature     ●       ●
  • main, develop, feature/*, release/*, hotfix/*
  • 복잡. 웹 서비스는 거의 쓰지 않는다. 패키지·대형 릴리스에 적합.

7-2. GitHub Flow (가장 보편)

main ──●──●──●──●──●──
        \/    \/
     feature1 feature2
  • main은 항상 배포 가능
  • 모든 변경은 feature/* 브랜치 + PR + 리뷰 + 머지
  • **지속 배포(CI/CD)**와 궁합 최고

7-3. Trunk-Based Development

  • 모든 개발자가 main에 직접 작은 커밋을 자주 푸시
  • 미완 기능은 Feature Flag로 숨김
  • Google, Facebook 같은 대규모 조직

7-4. 선택 기준

조직/프로덕트권장
SaaS, 웹 서비스 (지속 배포)GitHub Flow
모바일 앱, 라이브러리 (릴리스 주기 명확)Git Flow 또는 Release Branching
초대형 조직 + 강한 테스트 자동화Trunk-Based

8. Commit Message — Conventional Commits

8-1. 포맷

<type>(<scope>): <subject>

<body>

<footer>

8-2. type

type용도
feat새 기능
fix버그 수정
docs문서
style포맷·세미콜론 등
refactor동작 변화 없는 구조 개선
perf성능 개선
test테스트
build빌드 시스템
ciCI 설정
chore기타 잡무
revert이전 커밋 되돌림

8-3. 예

feat(auth): add Google OAuth login

Users can now sign in with Google. Uses PKCE flow for native apps.

Closes #123

Breaking change:

feat(api)!: change response format of /users

BREAKING CHANGE: response now returns { data, meta } instead of raw array.

8-4. 얻는 것

  • 자동 CHANGELOG 생성 (semantic-release, changesets)
  • 자동 버전 번호 — feat → minor, fix → patch, ! 또는 BREAKING → major
  • PR 리뷰 시 의도 명확
  • 검색·필터링 쉬움

8-5. Commit 단위 원칙

  • 한 커밋에 한 의도 — feat + fix + refactor 섞지 않음
  • 빌드 통과 상태 유지 (bisect 가능)
  • 50자 제목 + 72자 줄바꿈 본문 관례

9. PR 문화

9-1. PR 크기

  • 작을수록 좋다 (200줄 이하 권장, 400줄 이상은 리뷰 품질 급락)
  • 큰 기능은 여러 PR로 쪼개기 — 구조 먼저, 기능 나중
  • Breaking change는 반드시 독립 PR

9-2. PR 설명

## Summary
- 어떤 문제를 해결하는가
- 어떻게 접근했는가

## Screenshots / Demo
(UI 변경 시)

## Test Plan
- [ ] 단위 테스트 추가됨
- [ ] Storybook 확인
- [ ] 로컬에서 수동 검증

## Related
Closes #123

9-3. 코드 리뷰 가이드

제출자:

  • 자기 리뷰 먼저 (diff를 처음부터 훑으며 코멘트)
  • Draft PR로 먼저 대화 시작 가능
  • 리뷰어 지정 시 컨텍스트 포함

리뷰어:

  • 빠르게 (같은 날 이내): 블로킹이 심함
  • 코드 냄새와 원칙 이름으로 코멘트 (SRP 위반, N+1 등)
  • 의견(nit) vs 필수(blocker) 구분 — 접두사로 명시

9-4. 자동화

  • CODEOWNERS로 특정 경로 필수 리뷰어 지정
  • Required checks: 테스트, 린트, 타입, 빌드 통과 필수
  • Auto-merge: approve + check 통과 시 자동 머지
  • semantic-pr-title 액션으로 Conventional Commits 강제

10. .gitignore와 첨부 파일

10-1. 실무 기본

# dependencies
node_modules/
.pnp/
.pnp.js

# build
dist/
build/
.next/
.vite/

# env
.env
.env.local
.env*.local

# IDE
.vscode/
.idea/
.DS_Store

# logs
*.log

# test
coverage/
.nyc_output/

# cache
.cache/
.turbo/

10-2. 이미 커밋된 파일 제거

git rm --cached .env
echo ".env" >> .gitignore
git commit -m "chore: stop tracking .env"

⚠️ 이미 누설된 비밀은 rotate가 필수. 히스토리에서 지워도 GitHub 캐시·포크에 남아 있을 수 있다.

10-3. Git LFS

큰 바이너리(모델, 영상, 디자인 파일)는 Git LFS로. 저장소 크기·clone 시간 폭증 방지.


11. 잘못된 상태 복구

11-1. git reflog — 생명 구조선

HEAD의 이동 이력 전체를 보여준다. reset --hard로 날린 커밋도 보통 복구 가능.

git reflog
# c3d4e5f HEAD@{0}: reset: moving to HEAD~3
# a1b2c3d HEAD@{1}: commit: feat: add login

git reset --hard a1b2c3d

Git은 가비지 컬렉션 전까지 객체를 유지(기본 30일). 당황하지 말고 reflog부터.

11-2. Reset 3종

명령HEADIndexWorking Dir
reset --soft <commit>이동유지유지
reset --mixed <commit> (기본)이동이동유지
reset --hard <commit>이동이동덮어씀 ⚠️
  • --soft: 여러 커밋을 합쳐 다시 커밋하고 싶을 때
  • --mixed: git add 취소
  • --hard: Working Directory 변경까지 날림 → 확신 없으면 금지

11-3. Revert

공개된 커밋은 reset 대신 revert. 취소하는 새 커밋을 추가 (히스토리 보존).

git revert abc123         # abc123의 변경을 되돌리는 커밋 생성
git revert -m 1 <merge>   # 머지 커밋 revert

11-4. 잘못 커밋한 파일 제거

# 직전 커밋 메시지만 수정
git commit --amend -m "new message"

# 직전 커밋에 파일 추가
git add missing.ts
git commit --amend --no-edit

# push 이전에만 안전. push 후라면 push --force-with-lease 신중히

11-5. git bisect

언제 버그가 들어왔는지 이분 탐색.

git bisect start
git bisect bad              # 현재 버그 있음
git bisect good v1.2.0       # v1.2.0엔 없었음
# Git이 중간 커밋으로 체크아웃 → 테스트 후 good/bad 반복
git bisect reset

git bisect run <테스트 스크립트>로 자동화도 가능.


12. 고급 주제

12-1. Hooks

.git/hooks/ 또는 Husky + lint-staged로 커밋/푸시 전 자동 검사.

// package.json
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"]
  }
}

12-2. Worktree

한 저장소의 여러 브랜치를 다른 디렉터리에 동시에 체크아웃.

git worktree add ../proj-hotfix hotfix/x
# 메인 작업 디렉터리는 그대로 두고 hotfix 따로 작업

12-3. Submodule vs Subtree

방식특징
Submodule외부 저장소를 pin해 가져옴. SHA 고정. 업데이트 수동.
Subtree외부 저장소 내용을 병합해 포함. 일반 디렉터리처럼 동작.

요즘은 모노레포 또는 npm 패키지로 대체되는 추세.

12-4. Signed Commits

git commit -S -m "feat: important"

GPG/SSH 서명으로 커밋 작성자 검증. 보안 민감 프로젝트에서 중요.


13. 실무 체크리스트

  • 커밋 메시지가 Conventional Commits를 따르는가
  • 하나의 커밋이 하나의 의도를 담는가
  • PR이 리뷰 가능한 크기(~200줄)인가
  • 공유 브랜치에 rebase/force push를 피하는가 (또는 --force-with-lease)
  • .env, 비밀 키가 커밋에 포함되지 않도록 .gitignore를 유지하는가
  • husky + lint-staged로 커밋 전 자동 검사가 도는가
  • 잘못된 reset/머지 복구에 reflog를 쓸 줄 아는가
  • 버그 추적에 git bisect를 시도할 수 있는가

14. 연습 문제

Q1. Git의 Blob, Tree, Commit 객체의 관계를 그림 없이 설명하라.

정답
  • Blob은 파일 내용 자체를 SHA 해시로 저장한다. 파일명이 없고 순수 내용만.
  • Tree는 디렉터리 스냅샷이다. 자식 blob/tree의 이름·권한·해시 목록을 가진다.
  • Commit은 tree 루트 한 개를 참조하고, 부모 커밋(들) + 저자·메시지·타임스탬프 같은 메타를 가진다.

Commit이 tree를 참조하고, tree가 재귀적으로 하위 tree + blob을 참조하는 구조. 커밋들은 부모 포인터로 DAG를 이룬다. 같은 내용의 파일은 어디서든 같은 blob 해시 → 중복 저장 없이 구조 공유.

Q2. merge와 rebase의 결과 그래프 차이를 설명하고, 어느 상황에 어느 것을 써야 하는지 설명하라.

정답
  • merge: 두 브랜치 끝을 묶는 **머지 커밋(M)**이 생겨 이력이 Y자로 남는다. 원래의 분기·합류 흔적이 보존.
  • rebase: feature의 커밋들을 main 최신 위에 재적용해 선형(직선)으로 만든다. 머지 커밋 없음. 단 커밋 SHA가 바뀐다.

선택:

  • 로컬 feature 브랜치를 최신 main에 맞춰 업데이트할 때 → rebase (깔끔).
  • main에 PR을 합칠 때 → merge 또는 squash merge (리뷰 단위 보존).
  • 이미 공유된 브랜치는 절대 rebase 하지 않는다 → 다른 사람의 로컬 히스토리가 꼬인다.

Q3. 실수로 git reset --hard HEAD~3을 실행해 최근 3개 커밋을 날렸다. 복구 방법을 설명하라.

정답

git reflogHEAD의 이동 이력을 본다. reset 직전 커밋 SHA(예: HEAD@{1}이 직전 위치)를 찾아 git reset --hard <SHA> 또는 git reset --hard HEAD@{1}로 되돌린다.

Git은 객체를 기본 30일 이상 유지(GC 전)하므로 reflog에 남아 있으면 거의 복구 가능. 단, reset 이후 새 커밋·작업을 많이 했다면 Working Directory 변경은 덮어쓰일 수 있으니 먼저 git stash 또는 백업.

교훈: reset --hard는 Working Dir까지 날리므로 확신 없을 땐 reset --keep 또는 새 브랜치로 옮기고 확인 후 삭제.

Q4. Conventional Commits가 주는 실무 이점 3가지를 들어라.

정답
  1. 자동 버전 관리 + CHANGELOG: semantic-release 등이 feat → minor, fix → patch, BREAKING CHANGE → major로 SemVer 자동 bump하고 릴리스 노트 생성. 수동 작업 제거.
  2. 리뷰·히스토리 가독성: 메시지만 봐도 "기능인지 버그 수정인지 리팩토링인지" 즉시 구분. git log --grep="^feat"으로 기능 커밋만 추출 가능.
  3. PR 규칙·CI 통합: PR 제목에 prefix 강제(semantic-pr-title 액션), 브랜치 이름 규칙과 자동 linking, ChatOps 자동화.

부가적으로, 팀 신규 멤버가 의도 있는 커밋 작성을 강제로 배우게 됨 → 전반적 품질 상승.

Q5. "PR을 작게 유지하라"는 지침이 중요한 이유를 3가지 들어라.

정답
  1. 리뷰 품질: 400줄 넘는 diff는 리뷰어가 세밀히 못 읽는다. "LGTM"만 받고 버그가 통과. 200줄 이하는 집중해 읽힌다.
  2. 머지 충돌 최소화: 오래 열려 있는 큰 PR은 main 변경을 따라가느라 rebase·merge 지옥. 작게 쪼개 자주 머지.
  3. 롤백·원인 추적 용이: 버그 발생 시 작은 PR 하나만 revert하면 됨. 큰 PR은 다른 변경까지 휩쓸려 되돌아감.
  4. 심리적 부담: 작고 자주 머지하면 팀 리듬이 살고, 큰 PR은 리뷰어가 미루는 블로커가 된다.

Q6. force push가 위험한 이유와, 상대적으로 안전한 대안을 설명하라.

정답

위험: git push --force는 리모트 브랜치 끝을 로컬이 가리키는 커밋으로 강제 교체한다. 다른 팀원이 같은 브랜치에서 작업 중이었다면 그들의 커밋이 리모트에서 사라진다 (로컬엔 남아 있으나 다음 pull이 꼬임).

상대적으로 안전한 대안: git push --force-with-lease

  • 리모트 브랜치가 내가 마지막으로 본 상태 그대로일 때만 덮어쓰기 허용.
  • 다른 사람이 그 사이에 푸시했다면 실패 → 충돌 확인 후 대응.

원칙:

  • main/develop 같은 공유 브랜치에는 force push 금지 (브랜치 보호 설정으로 차단).
  • 개인 feature 브랜치에서 rebase 후 업데이트할 때만 --force-with-lease.

Q7. git bisect가 어떻게 "언제 버그가 들어왔는가"를 효율적으로 찾는지 설명하라.

정답

커밋 이력을 이분 탐색한다. "good" 커밋(버그 없음)과 "bad" 커밋(버그 있음)의 중간을 체크아웃 → 그 시점을 테스트해 good/bad를 표시 → 범위가 반으로 줄어든다. 반복하면 O(log N) 단계만에 원인 커밋을 찾는다.

예: 1024 커밋 사이에서 최대 10번이면 특정. 수동으로 일일이 되돌리는 것보다 훨씬 빠르다.

git bisect run <test-script>로 자동화하면 Git이 good/bad를 스크립트 exit code(0 good, 1 bad)로 판단해 완전 자동 탐색. CI 회귀 추적에 강력.


15. 정리

  • Git은 **내용 기반 객체 + DAG + 이름표(ref)**로 이해해야 혼란스럽지 않다.
  • merge는 이력 보존, rebase는 선형 이력. 로컬 feature는 rebase, PR은 merge/squash.
  • Conventional Commits는 자동 버전 관리·리뷰·검색을 모두 개선한다.
  • GitHub Flow가 웹 서비스의 사실상 표준이다.
  • PR은 작게, 리뷰는 빠르게, 리뷰 코멘트는 원칙 이름으로.
  • reflogbisect를 알면 대부분의 사고가 복구 가능하고 원인이 빠르게 드러난다.
  • 공유 브랜치에 force push / rebase 금지. 개인 브랜치에만 --force-with-lease.

← 6-5. 테스팅 | 6-7. 배포와 CI/CD →

진도 체크시작 전
NEXT · 6-7

배포와 CI/CD

CI/CD 파이프라인, 블루-그린, 카나리 배포.

이어서 학습하기 →