← 전체 글로 돌아가기

Git

Git rebase 중 충돌을 줄인 세 가지 커밋 분리 기준

기능 코드와 포맷 변경을 한 커밋에 섞어 충돌을 키웠던 경험을 돌아보고, 초보자도 적용할 수 있는 커밋 분리 기준을 정리했습니다.

충돌보다 더 어려웠던 것은 판단이었다

기능 브랜치를 최신 main에 rebase하던 날, 같은 파일에서 충돌이 여섯 군데 났다. 실제 변경은 버튼 하나와 API 호출 한 곳뿐인데 파일 전체가 바뀐 것처럼 보였다. 원인을 찾아보니 기능 작업 중 자동 포맷을 한 번 실행했고, import 정렬과 줄바꿈 변경이 기능 커밋에 함께 들어가 있었다.

git show --stat --oneline HEAD
git diff main...HEAD -- src/components/SaveForm.tsx

diff의 절반이 동작 변경이 아니라 공백이었다. 충돌 해결 화면에서 어느 줄을 살릴지 판단하기 어려운 이유가 여기에 있었다.

커밋을 나누는 실제 기준

나는 이후 다음 세 가지를 기준으로 커밋을 나눴다.

  1. 동작 변경과 정리 변경을 분리한다. 포맷·이름 변경은 별도 커밋으로 둔다.
  2. 한 커밋의 검증 명령을 한 문장으로 말할 수 있어야 한다. “저장 요청이 중복되지 않는지 확인”처럼 범위를 좁힌다.
  3. 되돌릴 단위가 같으면 합치고, 리뷰 질문이 다르면 나눈다. 타입 수정과 그 타입을 쓰는 기능은 한 리뷰 질문으로 묶을 수 있다.

이미 섞였다면 새 브랜치에서 파일 일부를 되돌리는 것보다 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를 확인하고, 포맷터를 돌렸다면 기능 커밋과 분리한다.