git bisect run은 "언제부턴가 깨진" 버그를 이진 탐색으로 자동 추적해, 문제를 처음 만든 커밋 하나를 몇 초 만에 짚어주는 기능이다. 좋다/나쁘다를 판정하는 스크립트만 넘겨주면 Git이 커밋 범위를 반씩 접어가며 알아서 checkout하고 테스트를 돌린다.
손으로 git bisect good/bad를 반복해 본 사람은 많다. 하지만 run 서브커맨드까지 쓰는 사람은 의외로 적더라. 나는 이걸 알기 전엔 커밋 200개를 눈으로 훑으며 하루를 날린 적이 있다. 그 삽질 이후로는 회귀 버그를 만나면 반사적으로 이 명령부터 친다.
어쩌다 여기까지 왔나
상황은 늘 비슷하다. 2주 전 릴리스에선 멀쩡하던 결제 합계 계산이 지금은 틀린다. 그동안 커밋은 180개쯤 쌓였고, "누가 언제 뭘 바꿨는지" 아무도 정확히 기억 못 한다. PR 제목만 봐선 범인을 못 찾는다.
이럴 때 무작정 git log를 노려보는 건 최악의 선택이다. 커밋이 N개면 하나씩 확인할 때 최대 N번을 테스트해야 한다. 이진 탐색은 log₂(N)번이면 끝난다. 180개면 손으로도 8번이면 되지만, 그 8번을 매번 정확한 커밋으로 checkout하고 빌드하고 판정하는 게 사람에겐 지겹고 실수가 잦다. 한 번 good/bad를 반대로 누르면 전체가 엉킨다.
먼저 수동 bisect의 뼈대
자동화를 이해하려면 수동 흐름을 알아야 한다. 시작은 "지금은 나쁘다, 그때는 좋았다"를 알려주는 것이다.
git bisect start
git bisect bad # 현재(HEAD)는 버그 있음
git bisect good v2.4.0 # 이 태그에선 정상이었음
# Git이 중간 커밋으로 자동 checkout → 확인 후 판정
git bisect good # 또는 git bisect bad
# ... 반복 ...
git bisect reset # 끝나면 원래 브랜치로 복귀
여기서 판정 부분을 사람이 아니라 스크립트가 대신하게 만드는 게 핵심이다.
핵심: run에 판정 스크립트를 넘긴다
규칙은 단순하다. Git은 스크립트의 종료 코드로 좋고 나쁨을 판단한다.
| 종료 코드 | 의미 |
|---|---|
| 0 | good (정상) |
| 1~124, 126, 127 | bad (버그 있음) |
| 125 | skip (판정 불가, 빌드 실패 등) |
그래서 특정 테스트 하나만 돌리면 된다.
git bisect start HEAD v2.4.0 # bad good 을 한 줄로
git bisect run npm test -- billing.spec.ts
이러면 Git이 알아서 커밋을 옮겨 다니며 npm test를 돌리고, 마지막에 <커밋해시> is the first bad commit을 뱉는다. 나는 이 줄을 보는 순간이 늘 통쾌하다.
skip(125)를 반드시 챙겨라
실전에서 가장 자주 데이는 지점이다. 이진 탐색 도중 빌드 자체가 안 되는 커밋을 만날 수 있다. 이걸 bad로 처리하면 결과가 오염된다. 그래서 판정 전에 빌드를 먼저 시도하고, 실패하면 125로 빠지게 짠다.
#!/bin/bash
# check.sh — 빌드 실패 커밋은 건너뛴다
npm ci --silent || exit 125
npm run build || exit 125
npm test -- billing.spec.ts
chmod +x check.sh
git bisect run ./check.sh
알아두면 덜 데이는 것들
- 스크립트는 저장소 밖에 두거나 stash: bisect가 커밋을 갈아끼우면 작업트리도 그 시점으로 바뀐다. 판정 스크립트가 저장소 안에 있으면 checkout하는 커밋에 그 파일이 없어서 사라진다.
/tmp에 두거나 절대경로로 부르자. - 테스트 반대로 붙이기: "버그가 고쳐진 커밋"을 찾고 싶을 땐 good/bad 의미가 뒤집힌다.
git bisect start후 용어를--term-old/--term-new로 바꿀 수 있다. - 로그 남기기:
git bisect log > bisect.log로 과정을 저장해두면, 판정을 잘못했을 때git bisect replay bisect.log로 되돌려 재개할 수 있다.
개인적으로는, 회귀 테스트를 하나라도 재현 스크립트로 만들 수 있으면 bisect의 값어치가 폭발한다고 본다. "재현 안 되는 버그"는 bisect도 못 잡는다. 그러니 먼저 최소 재현부터.
자주 묻는 질문
커밋 히스토리가 병합(merge)투성이인데 괜찮나요?
됩니다. bisect는 두 지점 사이의 그래프를 위상적으로 탐색하기 때문에 병합 커밋이 섞여 있어도 동작합니다. 다만 병합 커밋에서 빌드가 깨지는 경우가 있어, exit 125로 skip 처리하는 방어 코드를 넣어두는 편이 안전합니다.
good 지점을 모르면 어떻게 하나요?
확실히 정상이었던 과거 태그나 릴리스 커밋을 good으로 잡으면 됩니다. 너무 옛날로 잡아도 상관없습니다. 탐색 횟수가 log₂로 늘 뿐이라, 범위가 두 배여도 판정은 한 번만 더 필요합니다.
테스트가 없는 프로젝트는 못 쓰나요?
테스트 프레임워크가 필수는 아닙니다. curl로 응답을 확인하거나 grep으로 로그를 검사해 종료 코드만 맞게 반환하는 한 줄 스크립트면 충분합니다. 판정 기준을 코드로 표현할 수만 있으면 됩니다.
bisect 도중 실수로 good/bad를 잘못 눌렀어요.
git bisect log로 기록을 파일에 저장한 뒤, 그 파일을 텍스트 편집기로 열어 잘못된 줄을 지우고 git bisect replay로 다시 재생하면 됩니다. 처음부터 다시 할 필요는 없습니다.

댓글 0