학습 목표
- 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 pull이git 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. 브랜치는 "이 노드를 가리키는 이름표"일 뿐.
A ← B ← C ← D (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:
A ← B ← C (main) A ← B ← C ← M (main)
↖ ↗
D ← E (feat) D ← E
특성:
- 원 히스토리 보존 (분기/합류 흔적 남음)
- 협업 흐름이 그대로 드러남
- 브랜치가 많으면 그래프가 복잡
4-2. rebase
feature 브랜치의 커밋들을 main의 최신 위에 다시 쌓는다.
Before: After rebase feat onto main:
A ← B ← C (main) A ← B ← C ← D' ← E' (feat)
↖
D ← E (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. 해결 절차
- 마커를 보고 어느 쪽이 맞는지 의식적으로 결정 (둘 다 반영이 필요한 경우도 있음)
- 마커 제거
git add <file>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 | 빌드 시스템 |
ci | CI 설정 |
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종
| 명령 | HEAD | Index | Working 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 reflog로 HEAD의 이동 이력을 본다. 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가지를 들어라.
정답
- 자동 버전 관리 + CHANGELOG:
semantic-release등이feat→ minor,fix→ patch,BREAKING CHANGE→ major로 SemVer 자동 bump하고 릴리스 노트 생성. 수동 작업 제거. - 리뷰·히스토리 가독성: 메시지만 봐도 "기능인지 버그 수정인지 리팩토링인지" 즉시 구분.
git log --grep="^feat"으로 기능 커밋만 추출 가능. - PR 규칙·CI 통합: PR 제목에 prefix 강제(
semantic-pr-title액션), 브랜치 이름 규칙과 자동 linking, ChatOps 자동화.
부가적으로, 팀 신규 멤버가 의도 있는 커밋 작성을 강제로 배우게 됨 → 전반적 품질 상승.
Q5. "PR을 작게 유지하라"는 지침이 중요한 이유를 3가지 들어라.
정답
- 리뷰 품질: 400줄 넘는 diff는 리뷰어가 세밀히 못 읽는다. "LGTM"만 받고 버그가 통과. 200줄 이하는 집중해 읽힌다.
- 머지 충돌 최소화: 오래 열려 있는 큰 PR은 main 변경을 따라가느라 rebase·merge 지옥. 작게 쪼개 자주 머지.
- 롤백·원인 추적 용이: 버그 발생 시 작은 PR 하나만 revert하면 됨. 큰 PR은 다른 변경까지 휩쓸려 되돌아감.
- 심리적 부담: 작고 자주 머지하면 팀 리듬이 살고, 큰 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은 작게, 리뷰는 빠르게, 리뷰 코멘트는 원칙 이름으로.
reflog와bisect를 알면 대부분의 사고가 복구 가능하고 원인이 빠르게 드러난다.- 공유 브랜치에 force push / rebase 금지. 개인 브랜치에만
--force-with-lease.