Git rebase interactive 사용법 | pick·squash·fixup·충돌 해결·실수 복구

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와 충돌 해결·실수 복구까지 단계별로 정리합니다.


목차

  1. 개념: 왜 interactive rebase인가
  2. 실전: 기본 명령과 에디터 토큰
  3. 고급: autosquash·부분 스테이징
  4. 비교/주의: merge와의 차이
  5. 실무 사례
  6. 트러블슈팅: 충돌·중단·복구
  7. 마무리

개념: 왜 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

저장 후 종료하면 fed9012def5678에 흡수됩니다.

예시 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/--oursrebase 중에는 의미가 뒤바뀔 수 있으므로 항상 “어느 쪽 패치인지” 확인하고 사용하세요.


고급: 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와의 차이

비교표

항목mergerebase
히스토리병합 커밋이 남음선형에 가깝게 정리 가능
공유 브랜치안전히스토리 변경 시 협업자와 충돌
충돌한 번에 병합커밋마다 순차 해결 가능
커밋 정리불가능가능 (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했다면 resetforce-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 정책을 전제로 하세요.

핵심 원칙:

  1. 개인 브랜치에서만 사용
  2. 원격에 푸시된 커밋은 변경 금지 (팀 합의 필요)
  3. 충돌 해결은 커밋마다 순차 진행
  4. reflog로 언제든 되돌릴 수 있음
  5. autosquash로 fixup 자동화

브랜치·병합 기초는 Git 브랜치와 병합, 되돌리기·리베이스 맥락은 Git revert / rebase와 함께 보면 흐름이 이어집니다.