Git으로 범인 찾기

갑자기 build가 안됩니다. 우리의 코드는 대체 언제 망가진 걸까요? 범인을 빨리 색출해봅시다.

8 min read
views

다음 명령어들을 실행함으로써 버그 커밋을 쉽고 빠르게 찾을 수 있습니다.

git bisect start # git bisect 모드 시작
git bisect bad <commit> # 버그가 발생하고 있는 커밋
git bisect good <commit> # 버그가 없었던 커밋
git bisect run <command> # 매번 command를 자동으로 실행하여 버그 커밋 찾기
git bisect reset # git bisect 모드 종료


git bisect

git은 버전 관리 말고도 디버깅에 사용할 수 있는 git bisect라는 기능을 제공합니다. 이 git bisect는 이진 탐색 알고리즘을 사용하는데요, 꽤나 저희가 아는 알고리즘을 사용하기에 흥미롭습니다. 자동으로 버그를 찾는 모습까지 목격하면 더욱 흥미로우실 겁니다!

혹여나 이진 탐색을 잊어버리셨거나 처음 들어보신 분들을 위해 간단히 알아보고 가겠습니다.

이진 탐색 알고리즘은 정렬된 리스트에서 특정한 값의 위치를 찾는 알고리즘입니다. 범위의 중간 값을 정하고, 그 중간 값이 찾고자 하는 값보다 크다면 중간 값보다 작은 범위를 택합니다. 그렇지 않다면 중간 값보다 큰 범위를 택합니다. 이렇게 범위를 줄여나가면 중간 값이 찾고자 하는 값과 일치하면서 탐색을 멈추게 됩니다.

글로만 설명해드리니 어지러우셨을 것 같습니다. 다음 이미지를 보며 마저 얘기해보겠습니다.

이진 탐색

1부터 10까지의 숫자 중 2를 찾는다고 가정하겠습니다. 1부터 10까지의 중간 값은 5입니다. 5는 2보다 크죠. 2는 5보다 작은 범위에 위치해 있을 겁니다. 그러면 1부터 5사이로 범위를 줄입니다. 1부터 5사이의 중간 값은 3입니다. 3은 2보다 크죠. 2는 3보다 작은 범위에 위치해 있을 겁니다. 그러면 1부터 3사이로 범위를 줄입니다.

1부터 3까지의 중간 값은 2입니다. 우리가 찾고자 하는 값을 찾았습니다. 이렇게 탐색은 멈추게 되고 3번의 시도 끝에 값을 찾았습니다. 최악의 경우(같은 방법으로 1을 찾을 경우) 4번의 비교를 했어야 합니다.

하지만 이진 탐색 알고리즘을 거치지 않고 그저 단순하게 처음부터 끝까지 순서대로 탐색했다면 최악의 경우(10을 찾을 경우) 10번이 걸렸을 겁니다.

어쨌든, 커밋들은 시간 순서대로 이미 정렬되어 있다는 점을 활용해 이진 탐색 알고리즘을 적용해볼 수 있습니다.


사용 방법

git bisect 기능을 이용하기 위해서는 다음 3가지의 명령어가 필요합니다.

  • git bisect start: git bisect를 시작하기 위한 명령어로, 항상 입력해주어야 합니다.
  • git bisect bad <commit>: 버그가 발생하는 커밋을 입력합니다. 따로 commit을 입력해주지 않으면 현재 HEAD가 입력됩니다.
  • git bisect good <commit>: 버그가 발생하지 않는 커밋을 입력합니다. 따로 commit을 입력해주지 않으면 현재 HEAD가 입력됩니다.
  • git bisect reset: git bisect 모드를 종료합니다.

git bisect start로 git bisect 모드를 시작하고, git bisect bad/good을 통해 탐색할 범위를 정합니다. 이렇게 범위가 정해지고 나면 중간에 위치한 커밋으로 이동됩니다. 그리고 해당 커밋에서 버그가 있는지 살펴보고 있다면 git bisect bad를, 없다면 git bisect good을 입력합니다. 그러고 나면 좁혀진 범위의 중간 커밋으로 다시 이동하게 되고, 이 커밋에서도 버그가 있는지 살펴보고 git bisect bad 혹은 git bisect good을 입력해 범위를 좁혀나갑니다. 이 과정을 반복하면 결국 버그가 발생했던 최초의 커밋을 찾게 됩니다.

역시나 글로만 설명해드리니 혼란스러우셨을 겁니다. 간단한 예시를 살펴보겠습니다.

import { useState } from 'react';
 
export default function Home() {
  const [count, setCount] = useState(0);
 
  function handleClick() {
    setCount(count + 1);
  }
 
  return (
    <div className='flex h-screen flex-col items-center justify-center'>
      <h1 className='text-4xl font-bold'>카운터</h1>
      <button className='rounded-md bg-blue-500 px-4 py-2 text-white' onClick={handleClick}>
        증가
      </button>
    </div>
  );
}

nextjs의 app router 환경에서 작성한 코드입니다. 여기엔 아주 사소한 문제가 하나 있습니다. useState를 사용하는 클라이언트 컴포넌트임에도 use client가 선언되어 있지 않습니다. 언제부터 이렇게 되었을까요? 바로 찾아보겠습니다.


자동화

  • git bisect run <command>: 매 탐색 시 command를 실행하여 자동으로 버그 커밋을 찾아나갑니다.

위 예시를 바탕으로 이 git bisect run을 사용해보겠습니다. command로는 pnpm build를 입력해보겠습니다.



끝입니다! 커밋을 의미 있는 작은 단위로 매번 잘 쪼개 놓으면 버그 찾기는 더욱 쉬워질 것 같습니다.

*에러, 버그, 문제를 묶어서 버그라고 표현했습니다.


참고