Rebase: 커밋 기반(base)을 옮겨 히스토리를 한 줄로 정리하는 방법
Rebase는 브랜치의 커밋들을 다른 커밋 위에 다시 적용해 브랜치의 시작점(base)을 옮기는 Git 명령이다. 병합 커밋 없이 선형 히스토리를 만들 수 있지만, 커밋을 새로 생성하므로 해시가 바뀐다. 이 글에서는 동작 원리, 기본 사용법, 대화형 rebase, 그리고 공유 브랜치에서 지켜야 할 주의점을 다룬다.
By 송민성
1. 개념
리베이스(rebase)는 한 브랜치에서 갈라져 나온 커밋들을 다른 커밋 위에 다시 적용(replay) 하여 브랜치의 기준점(base)을 바꾸는 Git 작업이다.
예를 들어 main에서 feature가 갈라진 뒤 main에 새 커밋이 쌓였다고 하자.
A---B---C feature
/
D---E---F---G mainfeature 브랜치에서 git rebase main을 실행하면 다음처럼 바뀐다.
A'--B'--C' feature
/
D---E---F---G mainA', B', C'는 A, B, C와 변경 내용은 같지만 부모 커밋이 다르므로 별개의 커밋이다. 따라서 커밋 해시(hash)도 달라진다.
2. 왜 사용하는가
- 선형 히스토리(linear history): 병합 커밋(merge commit)이 생기지 않아
git log가 한 줄로 읽힌다. - 최신 변경 반영: 작업 브랜치를 최신
main위로 옮겨, 통합 전에 충돌을 미리 해결할 수 있다. - 커밋 정리: 대화형 리베이스(interactive rebase)로 지저분한 커밋을 합치거나(squash) 메시지를 고치거나 순서를 바꿀 수 있다.
3. 동작 원리
git rebase main을 feature에서 실행하면 Git은 대략 다음 순서로 동작한다.
feature와main의 공통 조상(merge base)을 찾는다. 위 예시에서는E다.- 공통 조상 이후
feature에만 있는 커밋(A,B,C)의 변경분(patch)을 임시로 저장한다. feature가 가리키는 위치를main의 끝(G)으로 옮긴다.- 저장해 둔 변경분을
A,B,C순서대로 하나씩 적용해 새 커밋A',B',C'를 만든다. - 브랜치 포인터를 마지막 새 커밋(
C')으로 이동한다.
기존 커밋 A, B, C는 즉시 삭제되지 않고 어떤 브랜치도 가리키지 않는 상태가 된다. git reflog로 한동안 되돌릴 수 있다.
적용 중 같은 부분을 양쪽에서 수정했다면 충돌(conflict)이 발생하며, 커밋 단위로 한 번씩 해결해야 한다.
4. 코드 예제
아래 스크립트는 빈 디렉터리에서 실행하면 rebase 전후 히스토리를 직접 확인할 수 있다.
#!/bin/zsh
set -e
mkdir rebase-demo && cd rebase-demo
git init -b main
git config user.name "demo"
git config user.email "demo@example.com"
echo "base" > app.txt
git add app.txt && git commit -m "E: 초기 커밋"
# feature 브랜치에서 작업
git switch -c feature
echo "feature 1" > feature1.txt
git add feature1.txt && git commit -m "A: feature 작업 1"
echo "feature 2" > feature2.txt
git add feature2.txt && git commit -m "B: feature 작업 2"
# main에도 새 커밋이 쌓임
git switch main
echo "main update" > main.txt
git add main.txt && git commit -m "F: main 업데이트"
# feature를 main 위로 rebase
git switch feature
git rebase main
git log --oneline --graph --all실행 결과 feature의 두 커밋이 F 뒤에 한 줄로 이어진 것을 볼 수 있다.
충돌이 났을 때의 흐름은 다음과 같다.
git rebase main
# 충돌 발생 → 파일 수정 후
git add <충돌을 해결한 파일>
git rebase --continue
# 중단하고 원래 상태로 복귀
git rebase --abort최근 커밋 3개를 정리하는 대화형 리베이스는 다음과 같다.
git rebase -i HEAD~3
# 에디터에서 각 커밋 앞의 pick을 바꾼다
# reword : 커밋 메시지 수정
# squash : 이전 커밋에 합치기(메시지 병합)
# fixup : 이전 커밋에 합치기(메시지 버림)
# drop : 커밋 삭제5. 시간 복잡도 또는 성능 특성
rebase는 알고리즘 복잡도보다 적용할 커밋 수가 비용을 좌우한다. 옮길 커밋이 n개이면 패치를 n번 적용하므로 작업량이 대체로 n에 비례하고, 충돌이 나면 그만큼 사람이 개입해야 한다. 반면 merge는 충돌을 한 번에 해결한다. 커밋이 많고 충돌이 잦은 브랜치에서는 rebase가 더 번거로울 수 있다.
6. 실무 사용 사례
- PR 올리기 전 동기화: 작업 브랜치를
git rebase main으로 최신 상태로 맞춘 뒤 리뷰를 요청한다. - 커밋 정리: "fix typo", "WIP" 같은 임시 커밋을
squash/fixup으로 의미 있는 단위로 합친다. - Rebase and merge / Squash and merge: GitHub 등 호스팅 서비스는 PR을 이 방식으로 통합하는 옵션을 제공해 선형 히스토리를 유지한다.
- pull 시 rebase:
git pull --rebase는 원격 변경을 가져온 뒤 내 로컬 커밋을 그 위에 다시 적용한다. 불필요한 병합 커밋을 줄여 준다.
7. 주의할 점
- 이미 공유한(push한) 커밋은 rebase하지 않는다. 커밋 해시가 바뀌므로 다른 사람의 히스토리와 어긋난다. 이는 가장 흔한 사고 원인이다.
- 이미 push한 브랜치를 rebase했다면 push할 때 강제 갱신이 필요하다. 이때는
--force보다 다른 사람의 작업을 덮어쓰지 않도록 확인해 주는git push --force-with-lease를 쓴다. - 꼬였다면
git rebase --abort로 중단하거나, 완료 후라면git reflog에서 이전HEAD를 찾아git reset --hard <해시>로 복구할 수 있다. - rebase는 히스토리를 "깔끔하게" 만들지만, 실제로 어떤 순서로 작업했는지에 대한 기록은 사라진다.
8. 핵심 정리
- rebase는 브랜치의 커밋을 다른 커밋 위에 다시 적용해 base를 옮긴다.
- 결과 커밋은 내용이 같아도 새 커밋(새 해시) 이다.
- merge와 달리 병합 커밋이 없어 선형 히스토리가 된다.
git rebase -i로 커밋을 합치거나 수정할 수 있다.- 공유된 브랜치는 rebase하지 않는다. 내 로컬 작업 브랜치에만 사용한다.
- 문제가 생기면
--abort와reflog로 되돌릴 수 있다.