Tuesday, March 12, 2024

Git 고수의 비결: Rebase, Cherry-Pick, Bisect 완벽 정복

Git은 현대 소프트웨어 개발의 핵심인 분산 버전 관리 시스템입니다. 대부분의 개발자는 git commit, git push, git pull과 같은 기본 명령어에 익숙하지만, Git의 진정한 힘은 그 너머에 있는 고급 명령어들을 자유자재로 다룰 때 발휘됩니다. 이러한 고급 명령어들은 단순히 복잡한 작업을 처리하는 것을 넘어, 프로젝트의 이력을 깔끔하게 관리하고, 팀과의 협업을 원활하게 하며, 버그 추적 시간을 획기적으로 단축시켜 줍니다.

이 글에서는 Git을 한 단계 더 깊이 있게 사용하고자 하는 개발자들을 위해, 실무에서 가장 유용하게 쓰이는 세 가지 고급 명령어를 심층적으로 다룹니다. 이 명령어들을 마스터하면 복잡한 개발 시나리오에서도 자신감 있게 대처할 수 있는 '프로 개발자'로 거듭날 수 있을 것입니다.

본문에서 다룰 핵심 명령어는 다음과 같습니다.

  • git rebase: 복잡하게 얽힌 커밋 히스토리를 깔끔한 직선 형태로 재정렬하여 가독성을 높입니다.
  • git cherry-pick: 다른 브랜치에 있는 특정 커밋 하나만 골라서 현재 브랜치에 적용합니다.
  • git bisect: 이진 탐색을 통해 수많은 커밋 중에서 버그를 유발한 단 하나의 커밋을 놀랍도록 빠르게 찾아냅니다.

이제 Git의 숨겨진 능력을 깨우고 당신의 개발 워크플로우를 혁신해 보세요.

커밋 히스토리를 예술로 만드는 `git rebase`

git rebase는 커밋 히스토리를 '재배치'하여 깨끗하고 논리적인 흐름으로 만드는 강력한 도구입니다. merge가 두 브랜치의 역사를 합치면서 'Merge commit'을 남기는 반면, rebase는 현재 브랜치의 변경사항을 대상 브랜치의 최신 커밋 위로 하나씩 옮겨 심는 것처럼 작동합니다. 그 결과, 마치 처음부터 최신 버전에서 작업을 시작한 것처럼 보이는 깔끔한 직선형 히스토리가 만들어집니다.

예를 들어, feature 브랜치에서 신규 기능 개발을 하는 동안 다른 팀원이 main 브랜치에 새로운 내용을 반영했다고 상상해 보세요. 이때 rebase를 사용하면 내 작업 내용을 main 브랜치의 최신 변경사항 위에 깔끔하게 얹을 수 있어, 나중에 코드를 리뷰하거나 히스토리를 추적하기가 훨씬 수월해집니다.

`git rebase` 기본 사용법

기본적인 사용법은 매우 간단합니다. 현재 브랜치의 커밋들을 옮겨놓을 새로운 기준(base)이 될 브랜치를 지정해주기만 하면 됩니다.

git rebase <기준_브랜치>

가장 일반적인 시나리오는 기능 브랜치를 최신 main 브랜치에 맞춰 업데이트하는 경우입니다.

# 1. 작업 중인 기능 브랜치로 이동
git checkout feature

# 2. main 브랜치를 기준으로 rebase 실행
git rebase main

이 명령을 실행하면 Git은 feature 브랜치에만 있는 커밋들을 잠시 따로 보관한 뒤, feature 브랜치를 main 브랜치의 최신 지점으로 이동시키고, 그 위에 보관했던 커밋들을 차례대로 다시 적용합니다.

Rebase의 황금률: 절대 공유된 브랜치에는 사용 금지!

rebase는 히스토리를 '수정'하는 강력한 기능인 만큼, 치명적인 문제를 일으킬 수도 있습니다. 반드시 지켜야 할 **황금률**은 바로 **"팀원들과 공유하는 원격 브랜치(예: `origin/main`)에는 절대 `rebase`를 사용해서는 안 된다"**는 것입니다. rebase는 기존 커밋을 지우고 새로운 커밋 ID를 가진 복제본을 만들기 때문에, 만약 다른 팀원이 당신이 지워버린 옛날 히스토리를 기반으로 작업하고 있었다면 팀 전체의 저장소가 뒤죽박죽 엉망이 될 수 있습니다. rebase는 아직 원격 저장소에 push하지 않은, 나 혼자 쓰는 로컬 브랜치를 정리할 때만 사용하세요.

원하는 커밋만 쏙 빼오는 `git cherry-pick`

"다른 브랜치에 있는 저 커밋 하나만 지금 당장 필요한데..." 이런 상황을 겪어본 적 있으신가요? 예를 들어, develop 브랜치에서 수정한 치명적인 버그를, 전체 브랜치를 병합하지 않고 해당 수정사항만 골라서 안정 버전인 main 브랜치에 즉시 반영해야 할 때가 있습니다. 바로 이럴 때 사용하는 명령어가 git cherry-pick입니다.

이름 그대로, 잘 익은 '체리'를 하나 '골라 따오듯이' 다른 브랜치에 있는 특정 커밋의 변경사항을 현재 브랜치에 새로운 커밋으로 복제해올 수 있습니다. 이는 불필요한 변경사항 없이 필요한 내용만 정확하게 가져올 수 있어 매우 유용합니다.

`git cherry-pick` 사용법

사용법은 매우 직관적입니다. 가져오고 싶은 커밋의 고유 ID(커밋 해시)만 알면 됩니다.

git cherry-pick <가져올_커밋_해시>

예를 들어, develop 브랜치에 있는 버그 수정 커밋(해시: `a1b2c3d`)을 긴급하게 main 브랜치에 적용해야 하는 상황을 가정해 보겠습니다.

# 1. 수정사항을 적용할 main 브랜치로 이동
git checkout main

# 2. develop 브랜치에서 원하는 커밋을 cherry-pick
git cherry-pick a1b2c3d

이 명령을 실행하면, `a1b2c3d` 커밋과 동일한 변경 내용을 가진 새로운 커밋이 main 브랜치의 맨 끝에 생성됩니다. 이를 통해 서비스 안정성에 영향을 줄 수 있는 다른 미완성 기능들을 제외하고, 오직 버그 수정사항만 안전하게 반영할 수 있습니다.

버그 탐정 `git bisect`로 범인 찾기

어느 날 갑자기 발견된 버그, "대체 이 버그는 언제부터 있었던 거지?"라며 막막했던 경험은 모든 개발자의 악몽입니다. 수백, 수천 개의 커밋을 일일이 뒤져보며 버그의 원인을 찾는 것은 엄청난 시간 낭비입니다. 이때, 명탐정처럼 범인을 찾아주는 도구가 바로 git bisect입니다.

git bisect는 버그를 유발한 최초의 커밋을 찾기 위해 '이진 탐색' 알고리즘을 사용합니다. 개발자가 "버그가 없었던 정상적인 커밋(good)"과 "버그가 확실히 존재하는 커밋(bad)"을 알려주면, Git은 그 사이의 커밋들을 절반씩 나눠가며 버그 유무를 질문합니다. 이 과정을 몇 번만 반복하면, 마법처럼 버그를 만든 단 하나의 커밋을 정확히 지목해 줍니다.

`git bisect`를 이용한 버그 추적 과정

git bisect는 Git과 대화하듯 진행됩니다. 추적을 시작하고, 범위를 알려준 뒤, Git의 질문에 답하기만 하면 됩니다.

현재 최신 버전(HEAD)에서 버그가 발생하고, 과거의 `v1.2.0` 태그 시점에서는 정상이었다는 것을 알고 있는 상황을 예로 들어보겠습니다.

# 1. bisect 모드를 시작합니다.
git bisect start

# 2. 현재 버그가 있는 커밋을 'bad'로 지정합니다.
git bisect bad HEAD

# 3. 버그가 없었던 정상 커밋을 'good'으로 지정합니다.
git bisect good v1.2.0

이제 Git은 `good`과 `bad`의 중간 지점에 있는 커밋으로 자동 체크아웃하며 "이 커밋은 괜찮은가요?"라고 묻습니다. 당신은 코드를 테스트해 본 후, 버그가 여전히 존재하면 git bisect bad, 버그가 사라졌다면 git bisect good을 입력합니다. 이 과정을 몇 번 반복하면 Git이 최종적으로 범인 커밋을 찾아내 알려줍니다.

범인을 찾았다면, 아래 명령어로 `bisect` 모드를 종료하고 원래 작업하던 브랜치로 돌아올 수 있습니다.

git bisect reset

수동으로 했다면 몇 시간이 걸렸을지도 모를 버그 추적을 단 몇 분 만에 끝낼 수 있게 해주는 매우 강력한 도구입니다.

결론

Git의 기본 명령어만으로도 개발은 가능하지만, git rebase, git cherry-pick, git bisect와 같은 고급 명령어를 능숙하게 사용하면 개발의 생산성과 코드의 품질을 한 차원 높일 수 있습니다. 깔끔한 히스토리 관리, 유연한 변경사항 적용, 신속한 버그 해결 능력은 뛰어난 개발자가 갖춰야 할 중요한 역량입니다. 오늘 배운 명령어들을 실제 프로젝트에 적극적으로 활용하여, 더 효율적이고 전문적인 개발자로 성장해 나가시길 바랍니다.


0 개의 댓글:

Post a Comment