Git 실무 워크플로우 #3 interactive rebase — squash·fixup으로 커밋 정리
“Git 실무 워크플로우 #2 rebase vs merge — 결정 기준과 금기"에서 로컬 전용 브랜치는 자유롭게 rebase해도 된다는 원칙을 세웠습니다. 이번 글은 그 자유를 가장 생산적으로 쓰는 방법입니다. 작업 중인 브랜치의 커밋은 지저분해지기 마련입니다. “기능 추가” 뒤에 “오타 수정"이 붙고, “리뷰 반영"과 “진짜 마지막 수정"이 이어집니다. 이 커밋들을 그대로 PR에 올리면 리뷰어가 의미 없는 중간 과정까지 읽어야 하고, 나중에 이력을 조회할 때도 잡음이 됩니다. PR을 열기 전에 커밋을 읽을 수 있는 단위로 다시 묶는 도구가 interactive rebase입니다.
총 7편으로 구성됩니다.
- #1 브랜치 전략 — GitHub Flow와 trunk-based
- #2 rebase vs merge — 결정 기준과 금기
- #3 interactive rebase — squash·fixup으로 커밋 정리 ← 이번 글
- #4 충돌 해결 — 충돌의 구조와 mergetool·rerere
- #5 PR 운영 — 리뷰 단위·커밋 메시지·draft PR
- #6 stash·cherry-pick·bisect — 일상 도구 셋
- #7 모노레포와 Git — sparse-checkout·서브모듈·LFS
이번 글은 interactive rebase의 todo 리스트를 읽는 법부터 시작해, 커밋 4개를 2개로 정리하는 실습, fixup과 autosquash를 결합한 워크플로우, 그리고 정리한 브랜치를 원격에 안전하게 올리는 방법까지 진행합니다.
todo 리스트 — interactive rebase의 작업 지시서 #
git rebase -i는 지정한 범위의 커밋 목록을 에디터에 띄워 주고, 각 커밋을 어떻게 처리할지 지시를 받아 한 번에 실행합니다. 최근 커밋 4개를 정리 대상으로 열어 보겠습니다.
git rebase -i HEAD~4에디터에 다음과 같은 todo 리스트가 열립니다.
pick a1b2c3d feat: 프로필 페이지 추가
pick d4e5f6a 오타 수정
pick 9f8e7d6 리뷰 반영
pick 3c2b1a0 feat: 프로필 이미지 업로드
# Rebase 7f6e5d4..3c2b1a0 onto 7f6e5d4 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the
# previous commit's log message
# ...여기서 주의할 점이 하나 있습니다. git log는 최신 커밋을 위에 보여 주지만, todo 리스트는 오래된 커밋이 위입니다. 리스트를 위에서 아래로 실행한다고 읽으면 순서가 자연스럽습니다.
각 줄 앞의 명령을 바꾸는 것이 정리 작업의 전부입니다. 자주 쓰는 명령은 여섯 가지입니다.
| 명령 | 동작 |
|---|---|
pick | 커밋을 그대로 사용합니다 (기본값) |
reword | 커밋은 유지하고 메시지만 다시 작성합니다 |
edit | 해당 커밋에서 멈춰 내용을 수정할 기회를 줍니다 |
squash | 바로 위 커밋에 합치고, 두 메시지를 합쳐 다시 작성합니다 |
fixup | 바로 위 커밋에 합치고, 메시지는 위 커밋 것만 남깁니다 |
drop | 커밋을 삭제합니다 (줄 자체를 지워도 동일) |
실습 — 커밋 4개를 2개로 #
위 시나리오에서 “오타 수정"과 “리뷰 반영"은 첫 커밋인 “프로필 페이지 추가"의 후속 손질입니다. 독립된 커밋으로 남을 이유가 없으므로 첫 커밋에 합치겠습니다. todo 리스트를 다음과 같이 고치고 저장합니다.
pick a1b2c3d feat: 프로필 페이지 추가
fixup d4e5f6a 오타 수정
fixup 9f8e7d6 리뷰 반영
pick 3c2b1a0 feat: 프로필 이미지 업로드저장하고 에디터를 닫으면 Git이 지시대로 커밋을 다시 만듭니다. 전후를 비교하면 효과가 분명합니다.
# 정리 전
$ git log --oneline
3c2b1a0 feat: 프로필 이미지 업로드
9f8e7d6 리뷰 반영
d4e5f6a 오타 수정
a1b2c3d feat: 프로필 페이지 추가
# 정리 후
$ git log --oneline
8d7c6b5 feat: 프로필 이미지 업로드
5f4e3d2 feat: 프로필 페이지 추가의미 있는 커밋 2개만 남았습니다. 해시가 전부 바뀌었다는 점도 확인해 두기 바랍니다. rebase는 커밋을 수정하는 것이 아니라 새로 만들어 교체합니다.
squash와 fixup의 차이는 메시지 처리뿐입니다. squash는 합쳐지는 두 커밋의 메시지를 에디터에 함께 띄워 새 메시지를 다시 쓰게 하고, fixup은 묻지 않고 위 커밋의 메시지만 남깁니다. “오타 수정” 같은 후속 손질은 메시지를 남길 가치가 없으므로 fixup이 맞고, 두 작업 커밋을 하나의 새 단위로 합치면서 메시지를 다시 설계하고 싶다면 squash가 맞습니다.
fixup 워크플로우 — 커밋할 때 미리 표시해 두기 #
정리를 나중에 몰아서 하는 대신, 후속 손질 커밋을 만드는 시점에 어느 커밋에 합쳐질 것인지 미리 표시해 둘 수 있습니다. --fixup 옵션에 대상 커밋을 지정하면 됩니다.
$ git commit --fixup=a1b2c3d
$ git log --oneline
f1e2d3c fixup! feat: 프로필 페이지 추가
3c2b1a0 feat: 프로필 이미지 업로드
a1b2c3d feat: 프로필 페이지 추가메시지가 자동으로 fixup! + 대상 커밋 메시지 형태가 됐습니다. 이렇게 쌓아 둔 fixup 커밋들은 --autosquash 옵션으로 한 번에 정리됩니다.
git rebase -i --autosquash HEAD~5todo 리스트가 열리는 것은 같지만, fixup 커밋이 대상 커밋 바로 아래로 이동해 fixup 명령까지 미리 적힌 상태로 열립니다. 내용을 확인하고 저장만 하면 정리가 끝납니다. 매번 옵션을 붙이는 대신 설정으로 기본값을 바꿔 둘 수도 있습니다.
git config --global rebase.autoSquash truegit commit --fixup=<원래 커밋>으로 쌓아 두면, 승인 후 rebase 한 번으로 브랜치가 처음부터 깨끗하게 작성된 모습이 됩니다.reword·edit·drop — 나머지 세 명령 #
reword— 코드가 아니라 메시지만 문제일 때 사용합니다. 해당 줄을 reword로 바꾸면 그 커밋 차례에 메시지 에디터가 열립니다.edit— 그 커밋 시점에서 rebase가 멈춥니다. 파일을 수정하고git commit --amend로 커밋을 고친 뒤git rebase --continue로 재개합니다. 커밋 하나를 둘로 쪼갤 때도 이 명령을 씁니다.drop— 커밋을 이력에서 제거합니다. 디버그용 코드만 담긴 커밋을 버릴 때 유용합니다. 해당 커밋의 변경 내용 자체가 사라진다는 점만 주의하기 바랍니다.
정리한 브랜치 올리기 — force-with-lease #
이미 원격에 올라간 브랜치를 정리했다면 push가 거부됩니다. 원격의 커밋과 로컬의 새 커밋이 해시부터 다르기 때문입니다. 이때 필요한 것이 강제 푸시인데, 두 가지 형태의 차이를 반드시 알아야 합니다.
# 원격 상태와 무관하게 무조건 덮어쓰기 — 사용하지 않기
git push --force
# 내가 아는 원격 상태일 때만 덮어쓰기 — 이것을 사용
git push --force-with-lease--force는 원격에 무엇이 있든 내 브랜치로 덮어씁니다. 내가 마지막으로 fetch한 뒤 동료가 같은 브랜치에 커밋을 올렸다면 그 커밋이 흔적 없이 사라집니다. --force-with-lease는 원격 브랜치가 내가 마지막으로 확인한 상태 그대로일 때만 덮어쓰고, 모르는 커밋이 있으면 push를 중단합니다. 강제 푸시가 필요한 상황에서는 항상 이쪽을 쓰는 습관을 들이기 바랍니다.
마무리 #
이번 글의 핵심은 세 가지입니다.
- interactive rebase는 todo 리스트로 커밋을 다시 묶는 도구이고, 실무에서 대부분의 정리는 pick과 fixup 두 명령으로 끝납니다.
- 후속 손질은
git commit --fixup으로 미리 표시해 두고--autosquash로 한 번에 정리하는 워크플로우가 효율적입니다. - 정리한 브랜치는
git push --force-with-lease로 올립니다.--force는 동료의 커밋을 지울 수 있으므로 쓰지 않습니다.
커밋을 다시 쓰는 작업이 늘어나면 그만큼 충돌을 만날 기회도 늘어납니다. 다음 글인 “Git 실무 워크플로우 #4 충돌 해결 — 충돌의 구조와 mergetool·rerere"에서는 충돌을 구조적으로 이해하고, rebase 중 충돌에서 ours와 theirs가 뒤집혀 보이는 이유부터 같은 충돌을 두 번 풀지 않게 해 주는 rerere까지 정리하겠습니다.