git bisect: 며칠이 아니라 몇 분 만에 프로덕션을 망가뜨린 커밋 찾기
출처: Dev.to
CI가 지난 금요일에 초록색이었어요. 그런데 오늘은 결제 테스트가 실패하고 있네요. 금요일에 머지된 이후 main에 47개의 커밋이 들어갔어요. 어느 커밋이 문제를 일으켰을까요?
대부분의 개발자는 이 질문에 잘못된 방식으로 답합니다. git log를 스크롤하면서 의심스러운 커밋을 하나씩 체크아웃하고, 테스트를 직접 실행해 보는 것이죠. 한 시간이 지나도 여전히 추측에 머물러 있습니다.
이 정확한 문제를 위해 만든 명령이 있습니다. 바로 git bisect이며, 한 번 배우면 예전 방식으로 회귀 버그를 디버깅하지 않게 됩니다.
bisect 작동 방식
git bisect는 커밋 히스토리를 이진 탐색합니다. Git에 두 가지 정보를 알려주면 됩니다.
- 좋은(good) 커밋 (버그가 없던 시점)
- 나쁜(bad) 커밋 (버그가 존재하는 시점 – 보통
HEAD)
Git은 두 커밋 사이의 중간 커밋을 체크아웃합니다. 테스트를 실행하고, 그 결과를 good 혹은 bad으로 표시합니다. 그러면 Git은 탐색 범위를 절반으로 좁히고, 이 과정을 반복합니다.

“좋음”과 “나쁨” 사이에 47개의 커밋이 있다면, 6단계(log₂ 47) 안에 버그를 도입한 정확한 커밋을 찾을 수 있습니다. 모든 커밋을 일일이 확인하는 것과 비교하면, 5분과 1시간의 차이죠.
수동 워크플로우
# bisect 세션 시작
$ git bisect start
# 현재 상태(HEAD)를 나쁨으로 표시
$ git bisect bad
# 알려진 좋은 커밋 지정
$ git bisect good a3f1d22
# Bisecting: 이번 이후 테스트할 커밋이 23개 남음(대략 5단계)
# [7e4b9c1] refactor: extract payment validator
# Git이 중간 커밋을 체크아웃했습니다. 테스트를 실행하세요.
$ npm test -- --grep "payments"
# 테스트 통과 — 이 커밋을 좋은 것으로 표시
$ git bisect good
# Bisecting: 이번 이후 테스트할 커밋이 11개 남음(대략 4단계)
# [b2d8e11] feat: add retry logic to payment API
# 테스트 실패 — 이 커밋을 나쁨으로 표시
$ git bisect bad
# ... Git이 첫 번째 나쁜 커밋을 찾을 때까지 계속 진행:
# b2d8e11이 첫 번째 나쁜 커밋입니다
# commit b2d8e11
# Author: leo@company.com
# Date: Tue Apr 15 11:42:03
# feat: add retry logic to payment API
# 완료 — 시작했던 상태로 되돌리기
$ git bisect reset
전체 6개의 명령만으로 어떤 커밋이 테스트를 깨뜨렸는지 정확히 알 수 있습니다. 추측도, git log를 뒤지는 고고학도 필요 없습니다.
스크립트로 bisect 자동화하기
여기서부터가 진짜 강력합니다. 버그를 확실히 감지할 수 있는 테스트가 있다면, 전체 bisect 과정을 Git에게 맡길 수 있습니다.
$ git bisect start
$ git bisect bad
$ git bisect good a3f1d22
# 이제 Git이 각 단계마다 테스트를 자동으로 실행합니다
$ git bisect run npm test -- --grep "payments"
git bisect run이 기대하는 명령은 다음과 같습니다.
- 0을 반환하면 커밋이 좋음(테스트 통과)
- **0이 아닌 값(1‑124, 126‑127)**을 반환하면 커밋이 나쁨(테스트 실패)
- 125를 반환하면 해당 커밋을 테스트할 수 없음(예: 빌드 실패 — Git이 건너뜁니다)
Git은 각 bisect 단계마다 명령을 실행하고 반환 코드를 해석해 범위를 자동으로 좁힙니다. 당신은 자리를 떠났다가 5분 뒤에 돌아와서, 어느 커밋이 문제였는지 알려주는 결과만 받으면 됩니다.
커밋이 50개 이상이고 전체 테스트 스위트가 실행당 2~3분 걸린다면, 이건 실질적인 삶의 질 향상이라 할 수 있습니다.
bisect 친화적인 테스트 스크립트 작성하기
테스트가 단순 명령이 아니라면, 셸 스크립트로 감싸세요.
#!/bin/bash
# bisect-test.sh — 검색 기능 검증
# 의존성 설치(커밋마다 달라질 수 있음)
npm install > /dev/null 2>&1 || exit 125 # 125 = 이 커밋 건너뛰기
# 빌드 실행
npm run build > /dev/null 2>&1 || exit 125
# 특정 테스트 실행
npm test -- --grep "search returns correct results"
# npm test는 성공 시 0, 실패 시 1을 반환하므로 bisect에 딱 맞습니다
이제 bisect를 실행합니다.
$ git bisect start
$ git bisect bad HEAD
$ git bisect good v2.4.0
$ git bisect run ./bisect-test.sh
빌드 실패 시 exit 125를 반환하는 것이 중요합니다. 커밋이 전혀 빌드되지 않는 경우, Git이 그 커밋을 나쁨으로 표시하지 않고 건너뛰게 해야 합니다.
불안정한(flaky) 테스트 다루기
테스트가 가끔씩 통과하고 가끔씩 실패한다면(bug이 같은 커밋에서도), bisect는 잘못된 결과를 내게 됩니다. 해결책은 스크립트 안에서 재시도하는 것입니다.
#!/bin/bash
# 최대 3번 시도, 하나라도 통과하면 성공
for i in 1 2 3; do
npm test -- --grep "flaky test" && exit 0
done
exit 1
이건 근본적인 해결책은 아니며, 결국 테스트를 결정론적으로 만들어야 합니다. 하지만 오늘 회귀를 찾는 데는 충분히 동작합니다.
bisect가 코드베이스에 가르쳐 주는 점
bisect를 몇 번 사용하면, 앞으로 스스로를 위해 선택해야 할 사항들이 보이기 시작합니다.
- 원자성(Atomic) 커밋이 중요합니다. 하나의 커밋에 기능, 버그 수정, 리팩터링이 섞여 있으면 “이 커밋이 문제다”는 결과만 나오고 어느 부분이 문제인지 알 수 없습니다. 작고 집중된 커밋이 bisect를 정확하게 만들어 줍니다.
- 테스트는 투자입니다. 테스트 커버리지가 좋은 코드베이스는 bisect를 완전 자동화된 도구로 바꿔 줍니다. 테스트가 없으면 bisect는 여전히 동작하지만 매 단계마다 수동 테스트가 필요합니다.
- main은 항상 초록색이어야 합니다.
main에 자주 깨지는 빌드가 있다면git bisect run이exit 125를 계속 만나게 되고