끝내는 결국 `git bisect`를 사용하게 된다

5 days ago 6

  • 이야기의 핵심은 git bisect 명령어가 이진 탐색을 이용해 버그를 도입한 커밋을 찾는 실제 사례
  • 대규모 monorepo 환경에서 테스트가 갑자기 실패했을 때, 로그만으로는 원인을 추적하기 어려운 상황이 발생
  • 한 동료가 좋은 커밋과 나쁜 커밋을 지정해 git bisect로 자동 탐색을 수행해 문제 커밋을 정확히 찾아냄
  • 예시로 calc.py의 정상 버전과 문자열 연결 버그가 포함된 버전, 그리고 pytest 기반 테스트 스크립트가 제시됨
  • 이진 탐색 원리를 활용한 git bisect는 대규모 코드베이스에서 버그 원인을 신속히 추적하는 강력한 도구

알고리듬과 실제 사례

  • 인터뷰 준비에서 이진 탐색(binary search) 학습의 중요성을 언급하며, 실제로 이 개념이 git bisect 명령어에 적용됨
    • git bisect는 “버그를 도입한 커밋을 찾기 위해 이진 탐색을 사용하는 도구”로 소개됨
    • Leetcode의 “First Bad Version” 문제와 유사한 원리로 작동함

실제 업무 환경에서의 문제 상황

  • 작성자는 monorepo를 사용하는 환경에서 수백~수천 개의 커밋이 하루에도 발생한다고 설명
  • 어느 날 테스트가 실패하기 시작했으나, 로그만으로는 원인을 파악할 수 없었음
  • 실패 원인은 원격 호출에 필요한 토큰을 얻는 설정 파일의 문자열 변경으로, 다른 계정을 참조하게 되어 테스트가 실패함
  • 이 변경은 통합 테스트를 통과했지만 실제로는 문제를 일으켰고, 수많은 커밋 중 어느 시점에서 발생했는지 찾기 어려웠음

git bisect를 이용한 문제 해결

  • 다른 팀의 동료가 git bisect 명령어를 사용해 문제 커밋을 빠르게 식별
    • “좋은 커밋”과 “나쁜 커밋”을 지정한 후, 자동으로 중간 커밋을 테스트하며 원인을 좁혀감
    • 각 테스트 실행은 시간이 걸렸지만, 결국 정확히 문제를 도입한 커밋을 찾아냄
    • 해당 커밋을 되돌리자 테스트가 모두 정상으로 복구됨

데모 저장소 예시

  • git-bisect-demo/ 구조는 다음 세 파일로 구성됨
    • calc.py: 테스트 대상 라이브러리
    • test_calc.py: pytest 기반 테스트
    • test_script.sh: git bisect run에서 실행되는 스크립트
  • 정상 버전의 calc.py는 단순히 a + b를 반환
  • 버그 버전은 str(a) + str(b)로 문자열 결합을 수행해 오류 발생
  • test_calc.py는 calc.add(2, 3)이 5를 반환해야 한다고 검증
  • test_script.sh는 테스트 성공 시 0, 실패 시 비 0 코드를 반환

git bisect 실행 과정

  • 예시 커밋 히스토리
    • Commit 1: 초기 커밋 (정상)
    • Commit 2: 리팩터링 (정상)
    • Commit 3: 버그 도입 (오류 발생)
    • Commit 4~10: 비기능적 변경 (오류 유지)
  • 실행 명령 예시 git bisect start git bisect bad HEAD git bisect good HEAD~9 git bisect run ./test_script.sh
  • git bisect는 중간 커밋을 자동으로 체크아웃하고 테스트 스크립트를 실행하며,
    테스트 실패 시점을 기준으로 첫 번째 나쁜 커밋을 식별
  • 출력 결과에서 b982ed9373fe235fe61c74b15faf264bc7142398 커밋이 첫 번째 버그 커밋으로 확인됨

결론

  • git bisect는 이진 탐색 원리를 코드 히스토리 탐색에 적용한 실용적 도구
  • 대규모 저장소나 복잡한 변경 이력에서도 버그 도입 시점을 신속히 추적할 수 있음
  • 실제 사례와 데모를 통해 알고리듬 학습이 실무 문제 해결에 직접 연결됨을 보여줌

Read Entire Article