Git rebase interactive 사용법 | pick·squash·fixup·충돌 해결·실수 복구
이 글의 핵심
git rebase -i로 커밋 메시지·순서·단위를 다루고, squash·fixup·edit 충돌을 해결한 뒤 reflog로 안전하게 복구하는 흐름을 한 번에 정리합니다.
들어가며
Git rebase interactive(git rebase -i)는 로컬 브랜치에서 커밋 순서·메시지·단위를 다듬을 때 쓰는 핵심 도구입니다. PR을 보내기 전에 “의미 있는 단위”로 묶거나, 오타만 고친 커밋을 fixup으로 흡수하는 패턴이 실무에서 매우 흔합니다.
다만 이미 원격에 푸시된 커밋을 바꾸면 히스토리가 달라지므로, 팀 규칙과 force push 정책을 반드시 확인해야 합니다. 이 글은 Git rebase interactive 사용법 충돌 검색 의도에 맞춰 pick, squash, fixup, reword, edit와 충돌 해결·실수 복구까지 단계별로 정리합니다.
목차
- 개념: 왜 interactive rebase인가
- 실전: 기본 명령과 에디터 토큰
- 고급: autosquash·부분 스테이징
- 비교/주의: merge와의 차이
- 실무 사례
- 트러블슈팅: 충돌·중단·복구
- 마무리
개념: 왜 interactive rebase인가
Interactive Rebase 명령어
- pick: 해당 커밋을 그대로 유지합니다.
- reword(r): 커밋은 유지하되 메시지만 다시 씁니다.
- edit(e): 그 커밋에서 멈춰 내용을 수정하거나 나눌 수 있습니다.
- squash(s): 이전 커밋과 합치고 메시지 편집 기회가 있습니다.
- fixup(f): 이전 커밋과 합치되 메시지는 버립니다(보통 “리뷰 반영” 같은 잡음 제거).
- drop(d): 커밋을 제거합니다.
실행 순서는 에디터에 나온 커밋 목록의 위에서 아래가 오래된 것→최신 것입니다. 순서를 바꾸면 적용 순서도 바뀝니다.
언제 사용하나?
- PR 전 커밋 정리: “WIP”, “fix lint” 같은 임시 커밋을 제거
- 커밋 메시지 통일: Conventional Commits 형식으로 변경
- 커밋 순서 변경: 논리적 순서로 재배치
- 큰 커밋 분할: 하나의 커밋을 여러 개로 나누기
주의사항
- 원격에 푸시된 커밋을 변경하면 히스토리가 달라짐
- 공유 브랜치에서는 사용 금지 (팀원과 충돌)
- 개인 feature 브랜치에서만 사용 권장
실전: 기본 명령과 에디터 토큰
최근 N개 커밋 정리
기본 사용법
git rebase -i HEAD~3
에디터 화면
pick abc1234 feat: 로그인 API
pick def5678 fix: typo
pick fed9012 wip
# Rebase 1234567..fed9012 onto 1234567 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# d, drop <commit> = remove commit
예시 1: fixup으로 임시 커밋 제거
pick abc1234 feat: 로그인 API
pick def5678 fix: typo
fixup fed9012 wip
저장 후 종료하면 fed9012가 def5678에 흡수됩니다.
예시 2: squash로 커밋 합치기
pick abc1234 feat: 로그인 API
squash def5678 fix: typo
squash fed9012 wip
저장 후 종료하면 메시지 편집 화면이 나타납니다:
# This is a combination of 3 commits.
# This is the 1st commit message:
feat: 로그인 API
# This is the commit message #2:
fix: typo
# This is the commit message #3:
wip
# Please enter the commit message for your changes.
불필요한 메시지를 삭제하고 최종 메시지를 작성합니다:
feat: 로그인 API 구현
- JWT 토큰 발급
- 리프레시 토큰 처리
- 에러 핸들링 추가
reword로 메시지만 변경
사용법
git rebase -i HEAD~2
에디터에서 변경
reword abc1234 feat: 로그인 API
pick def5678 fix: typo
저장 후 종료하면 메시지 편집 화면이 나타납니다:
feat: 로그인 API
# Please enter the commit message for your changes.
메시지를 수정하고 저장합니다:
feat: JWT 기반 로그인 API 구현
- 토큰 발급 로직 추가
- 리프레시 토큰 처리
edit로 커밋 내용 수정
사용법
git rebase -i HEAD~2
에디터에서 변경
edit abc1234 feat: 로그인 API
pick def5678 fix: typo
저장 후 종료하면 abc1234에서 멈춥니다:
Stopped at abc1234... feat: 로그인 API
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
파일 수정
# 파일 수정
vim src/auth.js
# 변경 사항 스테이징
git add src/auth.js
# 커밋 수정
git commit --amend --no-edit
# 또는 메시지도 변경
git commit --amend -m "feat: JWT 기반 로그인 API 구현"
# rebase 계속
git rebase --continue
커밋 분할 (edit 활용)
git rebase -i HEAD~2
# edit로 변경
# 커밋 되돌리기 (변경 사항은 유지)
git reset HEAD^
# 부분 스테이징
git add -p src/auth.js
git commit -m "feat: 로그인 API 추가"
git add -p src/token.js
git commit -m "feat: 토큰 발급 로직 추가"
# rebase 계속
git rebase --continue
충돌 해결 루프
충돌 발생 시
git rebase -i HEAD~3
Auto-merging src/auth.js
CONFLICT (content): Merge conflict in src/auth.js
error: could not apply abc1234... feat: 로그인 API
충돌 확인
git status
rebase in progress; onto 1234567
You are currently rebasing branch 'feature/login' on '1234567'.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/auth.js
충돌 해결
vim src/auth.js
<<<<<<< HEAD
function login(username, password) {
return authenticateUser(username, password);
}
=======
function login(email, password) {
return authenticateWithEmail(email, password);
}
>>>>>>> abc1234 (feat: 로그인 API)
충돌을 해결합니다:
function login(email, password) {
return authenticateWithEmail(email, password);
}
해결 표시 및 계속
git add src/auth.js
git rebase --continue
포기하고 되돌리기
git rebase --abort
실무 팁: 충돌 마커(<<<<<<<)를 지울 때 git checkout --theirs/--ours는 rebase 중에는 의미가 뒤바뀔 수 있으므로 항상 “어느 쪽 패치인지” 확인하고 사용하세요.
고급: autosquash·부분 스테이징
fixup 커밋 자동 정렬
수동 방식
git commit -m "feat: 로그인 API"
# ... 작업
git commit -m "fixup! feat: 로그인 API"
git rebase -i HEAD~2
에디터에서 수동으로 fixup을 이동해야 합니다.
자동 방식 (autosquash)
git commit -m "feat: 로그인 API"
# ... 작업
git commit --fixup=abc1234
git rebase -i --autosquash HEAD~2
에디터가 자동으로 fixup을 정렬합니다:
pick abc1234 feat: 로그인 API
fixup def5678 fixup! feat: 로그인 API
전역 설정
git config --global rebase.autoSquash true
이제 git rebase -i만 해도 자동으로 autosquash가 적용됩니다.
여러 커밋을 하나로 (squash)
방법 1: rebase -i
git rebase -i HEAD~4
pick abc1234 feat: 결제 API 추가
squash def5678 feat: 결제 검증 추가
squash fed9012 feat: 결제 이력 저장
squash 1234567 feat: 결제 알림 추가
방법 2: reset —soft
git reset --soft HEAD~4
git commit -m "feat: 결제 플로우 통합"
이 방식은 새 단일 커밋으로 덮어쓰므로, 이미 push한 브랜치면 같은 주의가 필요합니다.
부분 스테이징 (git add -p)
사용법
git add -p src/auth.js
diff --git a/src/auth.js b/src/auth.js
index 1234567..abcdefg 100644
--- a/src/auth.js
+++ b/src/auth.js
@@ -1,5 +1,10 @@
function login(email, password) {
+ if (!email || !password) {
+ throw new Error('Email and password required');
+ }
return authenticateWithEmail(email, password);
}
Stage this hunk [y,n,q,a,d,s,e,?]?
y: 이 변경 사항 스테이징n: 건너뛰기s: 더 작은 단위로 분할e: 수동 편집q: 종료
실무 예시: 두 개의 커밋으로 분할
# 첫 번째 커밋: 검증 로직
git add -p src/auth.js
# 검증 부분만 선택
git commit -m "feat: 로그인 검증 추가"
# 두 번째 커밋: 에러 핸들링
git add -p src/auth.js
# 에러 핸들링 부분만 선택
git commit -m "feat: 로그인 에러 핸들링 추가"
커밋 순서 변경
에디터에서 줄 순서 변경
git rebase -i HEAD~3
변경 전
pick abc1234 feat: 로그인 API
pick def5678 feat: 회원가입 API
pick fed9012 feat: 비밀번호 재설정 API
변경 후
pick def5678 feat: 회원가입 API
pick abc1234 feat: 로그인 API
pick fed9012 feat: 비밀번호 재설정 API
커밋 삭제 (drop)
에디터에서 drop
git rebase -i HEAD~3
pick abc1234 feat: 로그인 API
drop def5678 wip: 테스트 코드
pick fed9012 feat: 회원가입 API
또는 해당 줄을 삭제해도 동일한 효과입니다.
비교/주의: merge와의 차이
비교표
| 항목 | merge | rebase |
|---|---|---|
| 히스토리 | 병합 커밋이 남음 | 선형에 가깝게 정리 가능 |
| 공유 브랜치 | 안전 | 히스토리 변경 시 협업자와 충돌 |
| 충돌 | 한 번에 병합 | 커밋마다 순차 해결 가능 |
| 커밋 정리 | 불가능 | 가능 (squash, fixup) |
| 이력 추적 | 병합 시점 명확 | 선형이라 추적 어려움 |
merge 예시
git checkout main
git merge feature/login
* abc1234 (HEAD -> main) Merge branch 'feature/login'
|\
| * def5678 feat: 로그인 API
| * fed9012 feat: 토큰 발급
|/
* 1234567 feat: 회원가입 API
rebase 예시
git checkout feature/login
git rebase main
* abc1234 (HEAD -> feature/login) feat: 토큰 발급
* def5678 feat: 로그인 API
* 1234567 (main) feat: 회원가입 API
언제 merge를 사용하나?
- 공유 브랜치 (main, develop)
- 히스토리 보존이 중요한 경우
- 병합 시점 추적이 필요한 경우
언제 rebase를 사용하나?
- 개인 feature 브랜치
- PR 전 커밋 정리
- 선형 히스토리를 선호하는 팀
public branch에 rebase 후 push는 팀 규칙이 없으면 피하고, 개인 feature 브랜치에서 정리한 뒤 PR을 올리는 용도가 가장 무난합니다.
실무 사례
사례 1: PR 전 커밋 정리
문제: 개발 중 임시 커밋이 많음
* abc1234 feat: 로그인 API
* def5678 wip
* fed9012 fix lint
* 1234567 fix typo
* 2345678 feat: 토큰 발급
해결: fixup으로 정리
git rebase -i HEAD~5
pick abc1234 feat: 로그인 API
fixup def5678 wip
fixup fed9012 fix lint
fixup 1234567 fix typo
pick 2345678 feat: 토큰 발급
결과
* abc1234 feat: 로그인 API
* 2345678 feat: 토큰 발급
사례 2: 커밋 메시지 컨벤션 통일
문제: 메시지 형식이 일관되지 않음
* abc1234 add login api
* def5678 fix bug
* fed9012 update token logic
해결: reword로 통일
git rebase -i HEAD~3
reword abc1234 add login api
reword def5678 fix bug
reword fed9012 update token logic
각 커밋 메시지를 Conventional Commits 형식으로 변경:
feat: 로그인 API 추가
fix: 토큰 검증 버그 수정
refactor: 토큰 발급 로직 개선
사례 3: 큰 커밋 분할
문제: 하나의 커밋에 여러 기능이 섞임
* abc1234 feat: 로그인 및 회원가입 API
해결: edit로 분할
git rebase -i HEAD~1
edit abc1234 feat: 로그인 및 회원가입 API
git reset HEAD^
git add src/login.js
git commit -m "feat: 로그인 API 추가"
git add src/signup.js
git commit -m "feat: 회원가입 API 추가"
git rebase --continue
결과
* def5678 feat: 회원가입 API 추가
* abc1234 feat: 로그인 API 추가
사례 4: 리뷰 반영 커밋 정리
문제: 리뷰 반영 커밋이 많음
* abc1234 feat: 로그인 API
* def5678 리뷰 반영: 검증 추가
* fed9012 리뷰 반영: 에러 핸들링
* 1234567 리뷰 반영: 테스트 추가
해결: fixup 자동 정렬
git commit --fixup=abc1234
git commit --fixup=abc1234
git commit --fixup=abc1234
git rebase -i --autosquash HEAD~4
결과
* abc1234 feat: 로그인 API
트러블슈팅: 충돌·중단·복구
rebase가 꼬였을 때
증상: rebase 중 여러 충돌이 발생하여 진행이 어려움
해결: reflog로 되돌리기
git reflog
abc1234 (HEAD) HEAD@{0}: rebase -i (continue): feat: 로그인 API
def5678 HEAD@{1}: rebase -i (start): checkout HEAD~3
fed9012 HEAD@{2}: commit: feat: 토큰 발급
1234567 HEAD@{3}: commit: feat: 로그인 API
git reset --hard HEAD@{3}
reflog는 대부분의 실수 복구에 충분합니다.
잘못 rebase —continue를 눌렀을 때
증상: 충돌을 잘못 해결하고 continue를 눌렀음
해결: reflog로 되돌리기
git reflog
git reset --hard HEAD@{5}
원격에 이미 push했다면 reset 후 force-with-lease는 팀 합의 후에만.
rebase 중 에디터가 안 열릴 때
증상: rebase -i 실행 시 에디터가 열리지 않음
해결: 에디터 설정
git config --global core.editor "vim"
VS Code 사용
git config --global core.editor "code --wait"
환경 변수 설정
export GIT_EDITOR="vim"
충돌이 반복될 때
증상: 같은 파일에서 여러 커밋마다 충돌 발생
원인: 같은 파일을 여러 커밋이 수정
해결 1: squash로 먼저 합치기
git rebase -i HEAD~5
pick abc1234 feat: 로그인 API
squash def5678 feat: 검증 추가
squash fed9012 feat: 에러 핸들링
pick 1234567 feat: 회원가입 API
해결 2: rerere 활성화 (충돌 해결 재사용)
git config --global rerere.enabled true
이제 동일한 충돌은 자동으로 해결됩니다.
force push 후 팀원과 충돌
증상: force push 후 팀원이 pull 실패
원인: 히스토리가 달라짐
팀원 해결 방법
git fetch origin
git reset --hard origin/feature/login
주의: 로컬 변경 사항은 모두 사라집니다. 백업 후 진행하세요.
rebase 중 특정 커밋 건너뛰기
증상: 특정 커밋이 충돌을 일으키고, 해당 커밋이 불필요함
해결: skip
git rebase --skip
rebase 중 커밋 메시지 편집 실패
증상: 메시지 편집 중 에디터를 잘못 닫음
해결: 다시 편집
git commit --amend
git rebase --continue
마무리
Git rebase interactive는 로컬 이력을 읽기 좋게 만드는 도구이고, 충돌은 순차 적용의 대가입니다. 공유된 커밋을 바꿀 때는 항상 소통·force 정책을 전제로 하세요.
핵심 원칙:
- 개인 브랜치에서만 사용
- 원격에 푸시된 커밋은 변경 금지 (팀 합의 필요)
- 충돌 해결은 커밋마다 순차 진행
- reflog로 언제든 되돌릴 수 있음
- autosquash로 fixup 자동화
브랜치·병합 기초는 Git 브랜치와 병합, 되돌리기·리베이스 맥락은 Git revert / rebase와 함께 보면 흐름이 이어집니다.