git rebase 사용법과 주의사항
Git으로 협업하다 보면 커밋 이력이 점점 복잡해지는 경험을 한 번쯤 해보셨을 텐데요.
git rebase는 커밋 이력을 깔끔하게 재정리해주는 명령어입니다.
git merge와 비슷하게 브랜치를 합치는 역할을 하지만, 이력을 다루는 방식에서 큰 차이가 있습니다.
merge vs. rebase
merge와 rebase의 차이를 그림으로 살펴보겠습니다.
main에서 feature 브랜치를 만든 뒤, 양쪽 모두 새 커밋이 생긴 상황입니다.
main: A --- B --- E
\
feature: C --- D
여기서 merge를 하면 두 브랜치를 합치는 병합 커밋(M)이 생깁니다.
main: A --- B --- E --- M
\ /
feature: C --- D
반면 rebase를 하면 feature의 커밋들(C, D)을 main의 최신 커밋(E) 뒤로 옮겨 놓습니다.
main: A --- B --- E
\
feature: C' --- D'
원래의 C, D 커밋이 E 이후로 다시 적용되면서 C’, D’이라는 새로운 커밋이 만들어집니다. 커밋의 내용은 같지만 커밋 해시(ID)는 달라지는 점에 유의하세요.
결과적으로 feature 브랜치의 이력이 마치 main의 최신 상태에서 작업을 시작한 것처럼 깔끔한 일직선이 됩니다.
기본 사용법
feature 브랜치에서 작업 중이고, main 브랜치의 최신 변경분을 가져오고 싶다면 다음과 같이 실행합니다.
$ git switch feature/login
$ git rebase main
Successfully rebased and updated refs/heads/feature/login.
이 명령어는 feature/login 브랜치의 커밋들을 main 브랜치의 최신 커밋 위에 하나씩 다시 적용합니다.
rebase가 끝난 뒤에 main에서 feature/login을 merge하면 fast-forward merge가 되어 이력이 깔끔하게 유지됩니다.
$ git switch main
$ git merge feature/login
대화형 rebase
git rebase -i(인터랙티브 모드)는 단순히 브랜치를 옮기는 것을 넘어 커밋 이력 자체를 편집할 수 있게 해줍니다. 커밋을 합치거나, 메시지를 고치거나, 순서를 바꾸거나, 아예 삭제할 수 있어요.
PR을 올리기 전에 지저분한 커밋 이력을 정리하는 게 가장 흔한 용도입니다. 작업하면서 “wip”, “fix typo”, “진짜 수정” 같은 커밋이 쌓였다면 이걸 의미 있는 단위로 묶어서 리뷰어가 읽기 쉽게 만드는 거죠.
기본 사용법
feature 브랜치에서 origin/main 이후의 커밋을 모두 편집하고 싶다면 이렇게 실행합니다.
$ git rebase -i origin/main
최근 몇 개만 빠르게 고치고 싶다면 HEAD~N으로 범위를 지정할 수도 있습니다.
$ git rebase -i HEAD~3
그러면 텍스트 에디터가 열리면서 대상 커밋 목록이 나타납니다.
pick a1b2c3d 사용자 인증 로직 추가
pick b2c3d4e 오타 수정
pick c3d4e5f 에러 처리 보강
# Rebase d4e5f6g..c3d4e5f onto d4e5f6g (3 commands)
#
# Commands:
# p, pick = use commit as is
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# d, drop = remove commit
각 커밋 앞의 명령어를 수정한 뒤 에디터를 저장하고 닫으면 Git이 그대로 실행합니다. 위에서 아래로, 오래된 커밋부터 최신 커밋 순서로 나열된다는 점을 기억하세요 (git log와 반대 순서입니다).
pick: 그대로 유지
pick은 해당 커밋을 변경 없이 그대로 사용한다는 뜻입니다. 기본값이라서 아무것도 안 바꾸면 원래 이력과 동일하게 유지돼요.
단, 커밋의 순서를 바꾸고 싶다면 에디터에서 줄의 순서를 변경하면 됩니다.
pick c3d4e5f 에러 처리 보강
pick a1b2c3d 사용자 인증 로직 추가
pick b2c3d4e 오타 수정
순서를 바꾸면 커밋이 적용되는 순서도 바뀌기 때문에 충돌이 발생할 수 있습니다.
reword: 커밋 메시지 수정
reword는 커밋 내용은 그대로 두고 메시지만 수정합니다.
reword a1b2c3d 사용자 인증 로직 추가
pick b2c3d4e 오타 수정
pick c3d4e5f 에러 처리 보강
저장하고 닫으면 해당 커밋의 메시지를 편집하는 에디터가 다시 열립니다. 여기서 메시지를 고치면 되죠.
에디터에서 직접 메시지를 바꾸는 게 아니라 reword라는 명령어만 써야 한다는 점에 주의하세요. pick a1b2c3d 수정된 메시지처럼 적어도 메시지가 바뀌지 않습니다.
squash: 커밋 합치기
squash는 해당 커밋을 바로 위(이전) 커밋과 합칩니다. 합쳐질 때 두 커밋의 메시지를 편집할 수 있는 에디터가 열려요.
pick a1b2c3d 사용자 인증 로직 추가
squash b2c3d4e 오타 수정
squash c3d4e5f 에러 처리 보강
이렇게 하면 세 커밋이 하나로 합쳐집니다. 에디터에서 합쳐진 메시지를 확인하고 원하는 대로 수정할 수 있어요.
# This is a combination of 3 commits.
# This is the 1st commit message:
사용자 인증 로직 추가
# This is the commit message #2:
오타 수정
# This is the commit message #3:
에러 처리 보강
전부 지우고 새 메시지를 쓰거나 필요한 부분만 남기면 됩니다.
fixup: 메시지 없이 합치기
fixup은 squash와 비슷하지만 합쳐지는 커밋의 메시지를 버립니다. 메시지 편집 에디터가 열리지 않아서 더 간편해요.
pick a1b2c3d 사용자 인증 로직 추가
fixup b2c3d4e 오타 수정
fixup c3d4e5f 에러 처리 보강
세 커밋이 합쳐지면서 메시지는 “사용자 인증 로직 추가”만 남습니다. 자잘한 수정 커밋을 정리할 때 squash보다 편하죠.
edit: 커밋 수정을 위해 멈추기
edit은 해당 커밋에서 rebase를 일시 정지시킵니다. 파일을 수정하거나 커밋을 쪼개는 등 자유롭게 작업한 뒤 계속 진행할 수 있어요.
pick a1b2c3d 사용자 인증 로직 추가
edit b2c3d4e 오타 수정
pick c3d4e5f 에러 처리 보강
Git이 b2c3d4e에서 멈추면 원하는 수정을 합니다.
# 파일 수정 후
$ git add .
$ git commit --amend
# 수정이 끝났으면 rebase 계속 진행
$ git rebase --continue
하나의 커밋을 여러 개로 쪼개고 싶을 때도 edit을 씁니다. 멈춘 상태에서 git reset HEAD~1로 커밋을 풀고 원하는 단위로 나눠서 다시 커밋하면 돼요.
# edit으로 멈춘 상태에서
$ git reset HEAD~1
# 파일별로 나눠서 커밋
$ git add src/auth.js
$ git commit -m "인증 로직 수정"
$ git add src/validation.js
$ git commit -m "유효성 검사 분리"
$ git rebase --continue
drop: 커밋 삭제
drop은 해당 커밋을 이력에서 완전히 제거합니다. 에디터에서 해당 줄을 아예 지워버려도 같은 효과예요.
pick a1b2c3d 사용자 인증 로직 추가
drop b2c3d4e 실수로 추가한 디버그 코드
pick c3d4e5f 에러 처리 보강
삭제된 커밋의 변경사항이 이후 커밋에 영향을 주면 충돌이 발생할 수 있으니 주의하세요.
autosquash로 정리 자동화
작업하다 보면 “이전 커밋에 포함됐어야 할 변경”을 뒤늦게 발견하는 일이 잦습니다. 이럴 때 fixup!이나 squash! 접두어를 붙여서 커밋하면 인터랙티브 rebase에서 자동으로 정리할 수 있어요.
# 원본 커밋이 "사용자 인증 로직 추가"라면
$ git commit -m "fixup! 사용자 인증 로직 추가"
메시지를 직접 타이핑하는 대신 git commit의 --fixup 옵션에 원본 커밋 해시를 넘기면 더 편리합니다. 원본 커밋 메시지 앞에 fixup!을 자동으로 붙여주기 때문에 오타 걱정이 없어요.
$ git commit --fixup HEAD
# → 커밋 메시지: "fixup! 사용자 인증 로직 추가"
이렇게 만든 뒤 --autosquash 옵션을 붙여서 rebase하면 Git이 자동으로 fixup! 커밋을 원본 커밋 바로 아래에 배치하고 fixup 명령어를 붙여줍니다.
$ git rebase -i --autosquash origin/main
에디터가 열렸을 때 이미 정리된 상태라서 확인만 하고 저장하면 돼요.
매번 --autosquash를 붙이기 번거롭다면 git config로 기본값을 설정할 수 있습니다.
$ git config --global rebase.autoSquash true
—onto 옵션
--onto는 커밋을 옮길 목적지를 직접 지정하는 옵션입니다. 일반 rebase로는 처리하기 까다로운 상황에서 유용해요.
예를 들어 feature 브랜치에서 sub-feature를 만들었는데, feature의 변경은 빼고 sub-feature의 커밋만 main 위로 옮기고 싶은 경우입니다.
main: A --- B
\
feature: C --- D
\
sub-feature: E --- F
$ git rebase --onto main feature sub-feature
이 명령은 “sub-feature에서 feature 이후에 추가된 커밋(E, F)을 main 위에 적용해라”라는 뜻입니다.
main: A --- B --- E' --- F' (sub-feature)
\
feature: C --- D
결과적으로 E, F만 main의 B 커밋 뒤에 다시 적용되고 feature의 C, D는 포함되지 않습니다.
실전 워크플로우
팀에서 rebase를 활용하는 가장 흔한 시나리오를 소개하겠습니다.
기능 브랜치에서 작업하는 동안 main에 다른 사람의 커밋이 추가된 상황입니다.
Pull Request를 보내기 전에 main의 최신 내용을 반영하고 싶은데요.
# main 브랜치의 최신 내용을 가져온다
$ git fetch origin
# 내 feature 브랜치를 origin/main 위에 rebase한다
$ git rebase origin/main
이렇게 하면 PR에서 충돌 없이 깔끔하게 merge할 수 있습니다.
다만, 이미 원격에 push한 브랜치를 rebase하면 커밋 해시가 바뀌기 때문에 강제 push가 필요합니다.
$ git push -f origin feature/login
혼자 사용하는 기능 브랜치라면 강제 push를 해도 괜찮지만, 여러 사람이 같은 브랜치에서 작업하고 있다면 절대 하면 안 됩니다.
PR 전 커밋 정리 워크플로우
인터랙티브 rebase가 가장 빛나는 순간은 PR을 올리기 직전입니다. 작업하면서 쌓인 커밋을 논리적인 단위로 정리하면 리뷰어가 변경 의도를 파악하기 훨씬 수월하거든요.
실제 작업 과정에서 쌓인 커밋이 이렇다고 해보겠습니다.
$ git log --oneline -6
f1a2b3c (HEAD -> feature/auth) lint 에러 수정
e1d2c3b 테스트 추가
d1c2b3a 빠뜨린 import문 추가
c1b2a3f 에러 처리 구현
b1a2f3e 로그인 API 연동
a1f2e3d 로그인 폼 UI 구현
이걸 리뷰하기 좋은 3개 커밋으로 정리해볼게요.
$ git rebase -i origin/main
에디터에서 이렇게 편집합니다.
pick a1f2e3d 로그인 폼 UI 구현
pick b1a2f3e 로그인 API 연동
fixup d1c2b3a 빠뜨린 import문 추가
pick c1b2a3f 에러 처리 구현
fixup f1a2b3c lint 에러 수정
fixup e1d2c3b 테스트 추가
저장하면 6개 커밋이 3개로 정리됩니다. “빠뜨린 import문”은 API 연동 커밋에, “lint 에러 수정”과 “테스트 추가”는 에러 처리 커밋에 합쳐지는 거죠.
충돌 해결
rebase 중에도 충돌이 발생할 수 있습니다.
커밋을 하나씩 다시 적용하는 과정에서 main의 변경 내용과 충돌하는 부분이 있으면 Git이 멈추고 알려줍니다.
$ git rebase main
CONFLICT (content): Merge conflict in src/app.js
error: could not apply c1a2b3c... 사용자 인증 로직 추가
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add <pathspec>" then run "git rebase --continue".
충돌을 해결하는 순서는 다음과 같습니다.
# 1. 충돌 파일을 열어서 수정한다
# 2. 수정한 파일을 스테이징한다
$ git add src/app.js
# 3. rebase를 계속 진행한다
$ git rebase --continue
커밋이 여러 개라면 각 커밋마다 충돌이 발생할 수 있고 그때마다 위 과정을 반복해야 합니다.
충돌이 너무 복잡해서 rebase 자체를 취소하고 싶다면 이렇게 합니다.
$ git rebase --abort
rebase 시작 전 상태로 깔끔하게 되돌아갑니다.
rebase 주의사항
rebase를 사용할 때 가장 중요한 원칙이 하나 있습니다.
다른 사람과 공유하는 브랜치의 커밋은 rebase하지 않는다.
rebase는 기존 커밋을 새로운 커밋으로 다시 만드는 작업이기 때문에, 이미 원격에 올리고 다른 사람이 내려받은 커밋을 rebase하면 이력이 꼬여서 팀 전체가 곤란해질 수 있습니다.
안전한 사용 기준을 정리하면 다음과 같습니다.
| 상황 | rebase 가능? |
|---|---|
| 아직 push하지 않은 로컬 커밋 | 자유롭게 가능 |
| 혼자 쓰는 기능 브랜치 (push 후) | 강제 push 감수하면 가능 |
main이나 develop 같은 공유 브랜치 | 절대 하면 안 됨 |
git pull —rebase
git pull에 --rebase 옵션을 붙이면 fetch 후에 merge 대신 rebase를 수행합니다.
$ git pull --rebase origin main
이 방식은 불필요한 병합 커밋을 만들지 않아서 이력을 깔끔하게 유지하고 싶을 때 유용합니다.
매번 옵션을 붙이기 번거롭다면 git config로 기본 동작을 설정할 수도 있습니다.
$ git config --global pull.rebase true
git reset과 비교: 커밋 합치기
커밋을 합치는 방법은 인터랙티브 rebase 말고도 있습니다. git reset —soft로 여러 커밋을 풀어서 하나로 다시 커밋하는 방법이죠.
# 최근 3개 커밋을 하나로 합치기 (reset 방식)
$ git reset --soft HEAD~3
$ git commit -m "합친 커밋 메시지"
간단하긴 한데, 3개 커밋을 무조건 하나로 뭉치는 것만 가능합니다. “이 커밋은 저 커밋과 합치고, 이건 따로 두고” 같은 세밀한 조작은 인터랙티브 rebase에서만 할 수 있어요.
| 기준 | git rebase -i | git reset --soft |
|---|---|---|
| 커밋 순서 변경 | 가능 | 불가 |
| 선택적으로 합치기 | 가능 | 불가 (전부 합쳐짐) |
| 커밋 메시지 수정 | 가능 (reword) | 새 메시지만 가능 |
| 커밋 삭제 | 가능 (drop) | 불가 |
| 사용 난이도 | 약간 높음 | 낮음 |
최근 2~3개 커밋을 단순히 하나로 합칠 때는 reset이 빠르고 커밋 이력을 세밀하게 재구성할 때는 인터랙티브 rebase가 적합합니다.
마치며
git rebase는 커밋 이력을 깔끔하게 관리하기 위한 강력한 도구입니다.
merge가 이력을 있는 그대로 보존하는 방식이라면 rebase는 이력을 재구성해서 읽기 쉽게 만드는 방식이에요.
둘 중 어떤 것이 “더 좋다”라고 할 수 없고 팀의 Git 워크플로우에 맞는 방식을 선택하는 게 중요합니다. 공유 브랜치에서는 merge, 개인 브랜치에서는 rebase라는 기본 원칙만 지키면 대부분의 상황에서 문제없이 쓸 수 있을 겁니다.
특히 인터랙티브 rebase는 PR 전에 커밋 이력을 정리하는 습관을 들이면 코드 리뷰의 질이 확 달라집니다. git rebase -i origin/main부터 써보세요.
Git의 커밋 참조 표기법(HEAD~1 등)이 헷갈린다면 Git에서 HEAD란 무엇인가?를 참고하세요. 커밋을 되돌리는 다른 방법이 궁금하다면 git reset 사용법도 읽어보시고, 브랜치 관리에 대해서는 git branch 사용법에서 다루고 있습니다.
더 자세한 내용은 Git 공식 문서를 참고하세요.
This work is licensed under
CC BY 4.0