Git
Git rebase 중 충돌을 줄인 세 가지 커밋 분리 기준
기능 코드와 포맷 변경을 한 커밋에 섞어 충돌을 키웠던 경험을 돌아보고, 초보자도 적용할 수 있는 커밋 분리 기준을 정리했습니다.
충돌보다 더 어려웠던 것은 판단이었다
기능 브랜치를 최신 main에 rebase하던 날, 같은 파일에서 충돌이 여섯 군데 났다. 실제 변경은 버튼 하나와 API 호출 한 곳뿐인데 파일 전체가 바뀐 것처럼 보였다. 원인을 찾아보니 기능 작업 중 자동 포맷을 한 번 실행했고, import 정렬과 줄바꿈 변경이 기능 커밋에 함께 들어가 있었다.
git show --stat --oneline HEAD
git diff main...HEAD -- src/components/SaveForm.tsx
diff의 절반이 동작 변경이 아니라 공백이었다. 충돌 해결 화면에서 어느 줄을 살릴지 판단하기 어려운 이유가 여기에 있었다.
커밋을 나누는 실제 기준
나는 이후 다음 세 가지를 기준으로 커밋을 나눴다.
- 동작 변경과 정리 변경을 분리한다. 포맷·이름 변경은 별도 커밋으로 둔다.
- 한 커밋의 검증 명령을 한 문장으로 말할 수 있어야 한다. “저장 요청이 중복되지 않는지 확인”처럼 범위를 좁힌다.
- 되돌릴 단위가 같으면 합치고, 리뷰 질문이 다르면 나눈다. 타입 수정과 그 타입을 쓰는 기능은 한 리뷰 질문으로 묶을 수 있다.
이미 섞였다면 새 브랜치에서 파일 일부를 되돌리는 것보다 git add -p로 변경 덩어리를 나눠 새 커밋을 만드는 편이 안전했다.
git reset HEAD~1
git add -p src/components/SaveForm.tsx
git commit -m "feat: save form request handling"
git add -p src/components/SaveForm.tsx
git commit -m "style: format save form"
명령을 실행하기 전 git diff --cached를 확인해야 한다. 첫 시도에서 실수했던 부분도 여기였다. staged diff를 보지 않고 커밋해서 정리 변경이 다시 섞였다.
다음 rebase 전에 보는 표
| 변경 | 같은 커밋에 둘까? | 이유 |
|---|---|---|
| API 호출과 그 응답 타입 | 대체로 예 | 같은 동작을 완성함 |
| 기능 코드와 전체 파일 포맷 | 아니오 | 충돌과 리뷰 범위가 커짐 |
| 테스트와 테스트 대상 수정 | 보통 예 | 검증 단위가 명확함 |
| unrelated한 README 수정 | 아니오 | 되돌림 이유가 다름 |
회고
작은 커밋은 커밋 개수를 늘리는 규칙이 아니다. 충돌이 생겼을 때 “이 변경을 왜 선택했는가”를 복원할 수 있게 만드는 기록이다. 지금은 push 전에 staged diff와 git diff --check를 확인하고, 포맷터를 돌렸다면 기능 커밋과 분리한다.