개발 도구/Git・협업

git bisect 사용법, 회귀 커밋 30분 만에 찾는 실전 워크플로

stackD 2026. 7. 27. 18:00
반응형

 

예전에 380개 커밋 중에 회귀 버그 하나 찾느라 반나절을 날린 적이 있어요. 그날 옆자리 선배가 알려준 git bisect 한 줄로 같은 작업이 30분으로 줄었습니다.

 

그때 제가 했던 짓이 가관이었는데요. git checkout 으로 커밋 SHA를 하나씩 찍으면서 앱을 다시 빌드하고, 로그인 시나리오를 손으로 돌려보고, 안 되면 또 다음 커밋으로 넘어가는 식이었습니다. 이걸 380번 중 60번쯤 반복하다가 점심을 거의 굶었던 기억이 있어요. 선형 탐색이 얼마나 비효율인지 그날 몸으로 배웠습니다.

 

git bisect 이진 탐색 원리, 380개를 9~10번으로

git bisect 는 이진 탐색으로 회귀 커밋을 좁히는 도구입니다. 커밋 1,000개여도 약 10번의 테스트만에 범인을 특정할 수 있어요. 380개라면 9번 안팎이면 끝납니다.

 

쉽게 설명하자면, 사전에서 단어 찾을 때 가운데 펼쳐서 앞뒤 비교하는 그 동작을 깃이 자동으로 해주는 거예요. 한 번 테스트할 때마다 후보 커밋이 절반씩 잘려나가니까 380개가 190 → 95 → 47 → 23 식으로 빠르게 줄어드는 구조거든요.

 

bisect 를 시작하기 전에 한 가지는 꼭 챙기셔야 하는데요, 버그를 1분 안에 재현할 수 있는 테스트 명령어를 미리 정해두는 것입니다. pytest tests/test_login.py::test_oauth_redirect 처럼 한 줄로 끝나는 명령이요. 이게 흐릿하면 9번 테스트가 90분으로 늘어나는 일이 많습니다.

 

git bisect 수동 탐색, start·bad·good 세 줄로 시작

세션을 여는 흐름은 이렇게 잡으시면 됩니다.

 

# 1. 세션 시작
git bisect start

# 2. 현재 커밋에 버그가 있다고 알림
git bisect bad HEAD

# 3. 정상 동작했던 과거 시점 지정 (태그 권장)
git bisect good v1.4.2

 

이러면 깃이 알아서 중간 커밋으로 체크아웃해줍니다. 테스트 돌려보고 결과에 따라 셋 중 하나만 입력하시면 됩니다.

 

  1. 버그가 재현되면 → git bisect bad
  2. 버그가 없으면 → git bisect good
  3. 빌드가 깨져서 판단 불가면 → git bisect skip

 

여기서 자주 헷갈리는 자리가 있는데, bad 는 "이 코드가 못 짠 코드"라는 뜻이 아니라 "버그가 존재하는 상태" 라는 뜻이지요. "지금 깨져 있으니까 bad" 라고 받아들이는 게 직관적이더라고요.

 

원인 커밋을 찾으면 깃이 그 SHA와 diff 요약을 출력해줍니다. 그리고 반드시, 정말 반드시 git bisect reset 으로 세션을 닫아주세요. 안 닫으면 detached HEAD 상태가 그대로 남아서 IDE에서 다음 커밋을 쌓을 때 이상하게 꼬이는 경우가 많습니다. 저도 한 번 이걸 잊고 다음 작업 브랜치를 잘못 판 적이 있어요.

 

git bisect run 자동화, 종료 코드 125가 핵심

테스트가 명확히 스크립트로 표현되는 경우라면 bisect run 으로 전 과정을 깃에게 맡길 수 있습니다.

 

#!/usr/bin/env bash
# check.sh
make build || exit 125          # 빌드 실패는 skip 처리
pytest tests/test_login.py::test_oauth_redirect
# pytest 결과가 그대로 종료 코드 (1~127 중 125 제외면 bad 로 처리됨)

 

git bisect start HEAD v1.4.2
git bisect run ./check.sh

 

종료 코드 규칙은 정확히 외워두시는 게 좋습니다.

 

  1. 0 → 테스트 성공, good 으로 처리
  2. 1~127 (단, 125 제외) → 테스트 실패, bad 로 처리
  3. 125 → 테스트 불가, skip 으로 처리 (공식 예약 코드)
  4. 128~255 → 스크립트 자체 실행 오류, bisect 중단 (abort)

 

125 가 핵심입니다. 빌드 자체가 안 되는 커밋을 만나면 make build || exit 125 한 줄로 그 커밋만 건너뛰고 다음 후보로 넘어가게 할 수 있어요. 이거 안 깔아두면 중간에 빌드가 깨진 커밋 하나 때문에 전체 탐색이 무너지는 일이 생기거든요.

 

진행 기록을 남기고 싶으면 git bisect log > bisect.log 로 저장하시면 됩니다. 동료가 같은 작업을 이어받아야 할 때는 git bisect replay bisect.log 한 줄이면 똑같이 재현돼요. PR 리뷰어한테 "여기까지 좁혔다" 보여줄 때 진짜 유용하다고 체감했어요.

 

 

머지 커밋 많은 저장소는 --first-parent 옵션으로

피처 브랜치 안의 WIP 커밋이 잔뜩 머지되어 있는 저장소에서 bisect 를 그냥 돌리면, 브랜치 내부의 "잠깐 깨진 커밋들" 때문에 결과가 노이즈로 가득 차는 일이 많습니다. 메인 브랜치만 깔끔하고 안에는 난리법석인 그 흔한 패턴이지요.

 

이럴 때는 --first-parent 옵션으로 메인에 합쳐진 머지 커밋만 탐색하게 좁히는 게 정답입니다.

 

git bisect start --first-parent
git bisect bad HEAD
git bisect good v1.4.2

 

이러면 후보가 "PR 머지 단위"로만 압축돼요. 380개 개별 커밋이 30개 PR로 줄어드니 탐색 횟수도 4~5번이면 충분합니다. PR 단위로 원인을 좁혀놓고 그 PR 안에서만 다시 들여다보는 2단계 접근이 제일 깔끔하다고 봅니다.

 

그리고 good 기준점은 가능하면 태그된 릴리스로 잡으시는 게 안전합니다. 리베이스로 히스토리가 한 번 다시 쓰인 적이 있는 저장소라면 6개월 전 커밋 SHA가 실제로 그 시점 상태가 아닐 수 있거든요. v1.4.2 같은 릴리스 태그는 그 위험에서 자유롭습니다.

 

 

회귀 커밋 diff를 클로드 코드에 넘기는 마지막 단계

bisect 가 좋은 또 하나의 이유는 결과물이 작고 명확한 단일 커밋의 diff 라는 점입니다. 평균 한 PR이 수십 파일 변경이라면, 그 안의 진짜 회귀 원인 커밋은 보통 한두 파일 수십 줄인 경우가 많아요.

 

저는 bisect 가 커밋을 찍어주면 git show <SHA> 결과를 그대로 클로드 코드(Claude Code) 에 붙여 넣고 "이 diff 중 OAuth 리다이렉트가 깨질 만한 변경 한 줄 짚어줘" 라고 묻고 있습니다. PR 전체를 통째로 주는 것보다 토큰도 훨씬 적게 들고, AI가 헛다리 짚을 여지도 줄어들더라고요.

 

회귀 버그 리포트가 들어왔을 때 제가 가장 먼저 떠올리는 루틴은 이 세 단계입니다.

 

  1. 1분 안에 돌릴 수 있는 재현 테스트 명령어 정리하기
  2. git bisect start --first-parent 로 PR 단위 좁히기
  3. 좁혀진 단일 커밋 diff를 LLM에게 분석 요청하고 git bisect reset 으로 세션 닫기

 

새벽 두 시에 "지난주엔 분명 됐는데" 로 시작하는 슬랙 메시지를 받게 되시면, 그날 밤은 이 세 줄짜리 루틴 하나로 넘어갈 수 있습니다. 익숙해지는 데 한 시간도 안 걸리는데 디버깅 시간은 분기 단위로 줄여주는 도구, 개인적으로 이만한 가성비를 가진 깃 명령은 잘 떠오르지 않네요.

 

반응형
LIST