Git 실무 워크플로우 #6 stash·cherry-pick·bisect — 일상 도구 셋
지금까지 다룬 브랜치 전략, rebase, 커밋 정리, 충돌 해결, PR 운영이 워크플로우의 뼈대라면, 이번 글의 세 도구는 그 사이사이를 메우는 보조 도구입니다. 매일 쓰지는 않지만 필요한 순간에 모르면 곤란해집니다. 작업 도중 급한 일이 끼어들 때, 커밋 하나만 다른 브랜치로 옮겨야 할 때, 언제부터 버그가 생겼는지 아무도 모를 때가 그런 순간이고, 각각 stash, cherry-pick, bisect가 답하는 상황입니다.
총 7편으로 구성됩니다.
- #1 브랜치 전략 — GitHub Flow와 trunk-based
- #2 rebase vs merge — 결정 기준과 금기
- #3 interactive rebase — squash·fixup으로 커밋 정리
- #4 충돌 해결 — 충돌의 구조와 mergetool·rerere
- #5 PR 운영 — 리뷰 단위·커밋 메시지·draft PR
- #6 stash·cherry-pick·bisect — 일상 도구 셋 ← 이번 글
- #7 모노레포와 Git — sparse-checkout·서브모듈·LFS
세 도구를 상황, 기본 사용법, 흔한 함정 순으로 하나씩 정리하겠습니다.
git stash — 작업을 잠시 치워 두기 #
기능 브랜치에서 한창 작업 중인데 급한 리뷰 요청이나 핫픽스가 끼어듭니다. 커밋하기에는 어중간하고, 버리기에는 아까운 수정이 작업 디렉터리에 널려 있습니다. stash는 이 어중간한 수정을 임시 보관함에 밀어 넣고 작업 디렉터리를 깨끗하게 만들어 줍니다.
git stash push -m "장바구니 검증 로직 작업 중"
# 작업 디렉터리가 깨끗해짐 — 브랜치 전환, 핫픽스 자유
git stash list
# stash@{0}: On feature/cart: 장바구니 검증 로직 작업 중
git stash pop # 복원하면서 보관함에서 제거메시지 없이 git stash만 실행해도 되지만, 보관함에 두 개 이상 쌓이는 순간 이름 없는 stash는 서로 구분이 되지 않습니다. -m으로 메시지를 붙이는 습관을 처음부터 들여 두는 편이 좋습니다.
복원 명령은 두 가지입니다. pop은 적용과 동시에 보관함에서 제거하고, apply는 적용만 하고 보관함에 남겨 둡니다. 같은 수정을 여러 브랜치에 적용해 볼 때는 apply가 맞습니다. 한 가지 주의할 점은 pop 도중 충돌이 나면 stash가 자동으로 삭제되지 않는다는 것입니다. 충돌을 해결한 뒤 git stash drop으로 직접 정리해야 보관함이 깨끗하게 유지됩니다.
기본 stash는 추적 중인 파일만 담습니다. 새로 만든 untracked 파일까지 함께 치우려면 -u 옵션을 붙입니다. stash한 내용이 커져서 원래 브랜치와 많이 어긋났다면, git stash branch <브랜치명>으로 stash 시점 기준의 새 브랜치를 만들어 안전하게 복원하는 길도 있습니다.
git worktree를 검토할 만합니다. git worktree add ../hotfix main처럼 실행하면 같은 저장소의 다른 브랜치를 별도 디렉터리에 체크아웃해 줍니다. 작업 중인 디렉터리를 건드리지 않고 옆 폴더에서 핫픽스를 처리하는 방식이라, 치웠다가 복원하는 절차 자체가 사라집니다.git cherry-pick — 커밋 하나만 옮기기 #
머지가 브랜치의 히스토리 전체를 합치는 도구라면, cherry-pick은 특정 커밋 하나의 변경만 골라 현재 브랜치에 새 커밋으로 재적용하는 도구입니다.
대표 시나리오는 릴리스 브랜치로의 핫픽스 백포트입니다. main에서 고친 버그가 이미 배포된 릴리스 브랜치에도 필요할 때, main의 다른 변경까지 통째로 머지할 수는 없습니다. 수정 커밋 하나만 집어 옵니다.
git switch release/2.3
git cherry-pick -x 4f9a2c1
# [release/2.3 8d3e5b7] fix(auth): 토큰 만료 검증 오류 수정
# (cherry picked from commit 4f9a2c1...)-x 옵션은 커밋 메시지에 원본 커밋 참조를 자동으로 남깁니다. 이 한 줄이 있어야 나중에 “이 수정이 어느 브랜치들에 들어갔는지"를 추적할 수 있으므로, 공개 브랜치로 백포트할 때는 붙이는 것을 기본으로 삼는 편이 좋습니다.
연속한 커밋 여러 개는 범위로 집어 올 수 있습니다. git cherry-pick A..B는 A 다음 커밋부터 B까지를 순서대로 적용합니다. 도중에 충돌이 나면 이전 글에서 다룬 절차대로 해결한 뒤 git cherry-pick --continue로 진행하고, 전체를 그만두려면 --abort로 시작 전 상태로 돌아갑니다.
주의할 점은 남용입니다. cherry-pick은 같은 변경을 서로 다른 해시의 커밋으로 두 히스토리에 복제합니다. 브랜치 사이에 cherry-pick이 일상이 되면 어느 브랜치에 무엇이 반영됐는지 추적이 흐려지고, 이후 머지에서 같은 변경이 두 번 등장해 혼란을 만들기도 합니다. 정상 경로는 어디까지나 머지나 rebase이고, cherry-pick은 백포트 같은 예외 상황의 도구로 남겨 두는 것이 안전합니다.
git bisect — 버그가 유입된 커밋 찾기 #
“지난주까지는 됐는데 지금은 안 됩니다"라는 버그가 있습니다. 그 사이 커밋은 수십 개이고, 어느 커밋이 범인인지 아무도 모릅니다. 커밋을 하나씩 확인하면 수십 번을 확인해야 하지만, bisect는 이진 탐색으로 이 문제를 풉니다. 정상과 고장 사이의 중간 커밋을 확인하고, 결과에 따라 범위를 절반씩 좁힙니다.
git bisect start
git bisect bad # 현재 커밋은 고장
git bisect good v2.3.0 # 이 태그 시점은 정상
# Bisecting: 10 revisions left to test after this (roughly 4 steps)
# Git이 중간 커밋을 체크아웃해 줌 — 동작 확인 후 판정 입력
git bisect good # 이 커밋은 정상
# Bisecting: 5 revisions left to test after this (roughly 3 steps)
git bisect bad # 이 커밋은 고장
# ...반복...
# 4f9a2c1 is the first bad commit커밋 20개 사이에 숨은 범인도 판정 다섯 번 이내로 특정됩니다. 커밋이 수백 개라도 확인 횟수는 열 번 남짓입니다. 여기서 이전 글들에서 쌓은 원칙의 효과가 그대로 나타납니다. 커밋이 작고 각각 빌드 가능한 상태로 유지됐다면, 범인 커밋을 특정하는 순간 diff가 작아서 원인 코드까지 바로 보입니다.
판정을 명령어로 자동화할 수도 있습니다. 성공하면 0, 실패하면 0이 아닌 값을 반환하는 스크립트가 있다면 전 과정이 무인으로 돌아갑니다.
git bisect start HEAD v2.3.0
git bisect run pytest tests/test_auth.py
# Git이 알아서 체크아웃과 판정을 반복한 뒤 첫 bad 커밋을 보고탐색이 끝나면 git bisect reset으로 원래 브랜치로 돌아옵니다. bisect 도중에는 HEAD가 과거 커밋으로 이동해 있는 상태이므로, reset을 잊으면 엉뚱한 시점에서 작업을 이어 가는 사고로 이어집니다.
세 도구를 관통하는 공통점 #
셋을 나란히 놓고 보면 공통점이 하나 보입니다. 히스토리가 깨끗할수록 도구의 정밀도가 올라간다는 점입니다.
- cherry-pick은 커밋이 한 가지 변경만 담고 있을 때 깔끔하게 옮겨집니다. 여러 변경이 섞인 커밋은 필요 없는 수정까지 함께 딸려 갑니다.
- bisect는 모든 커밋이 빌드 가능할 때 매끄럽게 돌아갑니다. “작업 중” 커밋이 섞여 있으면 판정 불가능한 지점에서 탐색이 자꾸 멈춥니다.
- stash조차 커밋 단위가 작은 팀에서는 등장 빈도가 줄어듭니다. 어중간한 수정의 양 자체가 적기 때문입니다.
커밋을 의미 단위로 정리하고 작은 PR로 흘려보내는 습관은 그 자체로 끝나지 않고, 이런 도구들의 성능으로 되돌아옵니다.
마무리 #
이번 글의 핵심은 세 가지입니다.
- stash는 어중간한 수정을 치워 두는 임시 보관함입니다. 메시지를 붙이고, pop 충돌 시 수동 정리를 기억해 둡니다. 전환이 잦다면 worktree가 대안입니다.
- cherry-pick은 특정 커밋만 재적용하는 백포트 도구입니다.
-x로 출처를 남기고, 정상 경로의 머지를 대체하지 않게 절제해서 씁니다. - bisect는 이진 탐색으로 버그 유입 커밋을 특정합니다. 스크립트가 있다면
git bisect run으로 전 과정이 자동화됩니다.
다음 글인 “Git 실무 워크플로우 #7 모노레포와 Git — sparse-checkout·서브모듈·LFS"는 시리즈의 마지막 편입니다. 저장소가 커질 때 만나는 문제들, 곧 거대 저장소의 부분 체크아웃, 저장소 안의 저장소, 대용량 파일 관리를 정리하며 시리즈를 마치겠습니다.