GitMedium#47
Git의 cherry-pick, stash, reflog 명령어를 설명해주세요.
#Git#명령어#협업
힌트
특정 커밋 선택, 임시 저장, 히스토리 복구 기능입니다.
정답 및 해설
Git의 cherry-pick, stash, reflog 명령어를 설명해주세요.
Git에는 일상적인 add/commit/push 외에도 실수를 복구하거나 유연하게 작업을 관리할 수 있는 강력한 명령어들이 있습니다. cherry-pick은 특정 커밋만 선택적으로 가져오는 기능, stash는 작업 중인 변경사항을 임시 저장하는 기능, reflog는 Git의 "타임머신"으로 실수로 삭제된 커밋도 복구할 수 있는 기능입니다.
cherry-pick
개념
cherry-pick은 다른 브랜치의 특정 커밋 하나(또는 여러 개)를 현재 브랜치에 적용하는 명령어입니다. merge나 rebase처럼 전체 브랜치를 통합하는 것이 아니라, 원하는 커밋만 골라서 가져올 수 있습니다.
# 기본 사용법
git cherry-pick <커밋_해시>
# 커밋 해시 확인
git log --oneline feature-branch
# a1b2c3d feat: 결제 버그 수정
# e4f5g6h feat: 장바구니 기능 추가
# i7j8k9l feat: 상품 목록 구현
# main 브랜치에서 특정 커밋만 가져오기
git checkout main
git cherry-pick a1b2c3d # 결제 버그 수정만 가져옴
여러 커밋 cherry-pick
# 여러 커밋을 개별적으로 지정
git cherry-pick a1b2c3d e4f5g6h
# 연속된 커밋 범위 (A는 포함 안 됨, B는 포함됨)
git cherry-pick A..B
# 연속된 커밋 범위 (A, B 모두 포함)
git cherry-pick A^..B
# 예시: develop 브랜치의 최근 3개 커밋 가져오기
git log --oneline develop
# 9f8e7d6 fix: 로그인 토큰 갱신 오류 수정
# 5c4b3a2 fix: 회원가입 이메일 검증 개선
# 1h2i3j4 feat: 소셜 로그인 추가
git cherry-pick 5c4b3a2^..9f8e7d6 # 이메일 검증 + 토큰 수정 가져오기
cherry-pick 옵션
# 커밋하지 않고 스테이징 상태로 유지
git cherry-pick --no-commit a1b2c3d
# 이후 내용 수정 후 직접 커밋 가능
# 충돌 발생 시 처리
git cherry-pick a1b2c3d
# CONFLICT (content): Merge conflict in src/payment.js
# 1. 충돌 해결 후
git add src/payment.js
git cherry-pick --continue
# 2. 취소 (cherry-pick 전 상태로)
git cherry-pick --abort
# 3. 충돌 무시하고 진행
git cherry-pick --skip
# 커밋 메시지 수정 (-e: edit)
git cherry-pick -e a1b2c3d # 에디터에서 메시지 수정 가능
cherry-pick 실전 시나리오
# 시나리오 1: 핫픽스를 여러 브랜치에 적용
# main에 긴급 버그 수정 후 release/1.0 브랜치에도 적용
git checkout main
git commit -m "fix: 결제 오류 수정" # 커밋 해시: abc123
git checkout release/1.0
git cherry-pick abc123 # release에도 동일 수정 적용
git checkout release/2.0
git cherry-pick abc123 # 다른 릴리즈에도 적용
# 시나리오 2: 잘못된 브랜치에 커밋한 경우
git checkout wrong-branch
git commit -m "feat: 새 기능" # 커밋 해시: def456
# 올바른 브랜치로 이동 후 cherry-pick
git checkout correct-branch
git cherry-pick def456
# wrong-branch에서 커밋 되돌리기
git checkout wrong-branch
git revert def456 # 또는 git reset HEAD~1 (push 전)
# 시나리오 3: develop의 특정 기능만 main에 반영 (긴급)
git log --oneline develop
# 3e4f5g6 test: 단위 테스트 추가 (아직 불완전)
# 1a2b3c4 feat: 빠른 검색 기능 (완성, 긴급 반영 필요)
# 9z8y7x6 feat: 추천 알고리즘 (개발 중)
git checkout main
git cherry-pick 1a2b3c4 # 빠른 검색만 main에 반영
stash
개념
stash는 작업 중인 변경사항(추적된 파일의 수정사항, staged 변경사항)을 임시로 저장하고 작업 디렉토리를 깨끗한 상태로 만드는 명령어입니다. 스택(stack) 구조로 동작합니다.
# 기본 사용법
git stash # 현재 변경사항 stash (추적된 파일만)
git stash pop # 가장 최근 stash 적용 후 stash 목록에서 제거
git stash apply # 가장 최근 stash 적용 (stash 목록에 유지)
git stash list # stash 목록 조회
git stash drop # 가장 최근 stash 삭제
git stash clear # 모든 stash 삭제
stash 상세 사용법
# stash 저장 시 메시지 추가
git stash push -m "로그인 폼 작업 중"
git stash push -m "결제 모듈 리팩토링"
# stash 목록 확인
git stash list
# stash@{0}: On main: 결제 모듈 리팩토링
# stash@{1}: On feature/login: 로그인 폼 작업 중
# stash@{2}: WIP on develop: 상품 페이지 수정
# 특정 stash 적용
git stash apply stash@{1} # 인덱스로 지정
git stash pop stash@{1} # 적용 후 삭제
# 특정 stash 내용 확인
git stash show stash@{0} # 변경된 파일 목록
git stash show -p stash@{0} # diff 내용까지 확인
# 추적되지 않은 새 파일도 stash
git stash push --include-untracked
git stash push -u # 축약형
# gitignore 파일도 포함
git stash push --all
git stash push -a
# 일부 파일만 stash
git stash push -m "일부 변경사항" src/components/Login.tsx src/api/auth.ts
# stash를 새 브랜치로 꺼내기
git stash branch new-feature-branch stash@{0}
# stash 내용이 새 브랜치에 적용되고 stash는 삭제됨
stash 실전 시나리오
# 시나리오 1: 브랜치 전환이 필요할 때
# A 기능 개발 중 긴급 버그 신고 접수
git status
# M src/components/ProductList.tsx ← 작업 중
# M src/api/products.ts ← 작업 중
git stash push -m "상품 목록 페이지네이션 작업 중"
# 긴급 버그 수정
git checkout hotfix/payment-error
# ... 버그 수정 ...
git commit -m "fix: 결제 금액 계산 오류 수정"
git checkout main
git merge hotfix/payment-error
# 원래 작업으로 복귀
git checkout feature/product-pagination
git stash pop
# 시나리오 2: pull 전 임시 저장
# 원격에 새 변경사항이 있을 때
git stash push -m "현재 작업 임시 저장"
git pull origin develop
git stash pop
# 충돌이 있을 수 있음 - 해결 후 작업 계속
# 시나리오 3: stash 충돌 처리
git stash pop
# CONFLICT: 충돌 발생
# 충돌 해결 후
git add .
# stash pop 중 충돌 시 stash는 자동 삭제 안 됨
git stash drop # 수동으로 stash 삭제
reflog
개념
reflog(reference log)는 HEAD와 브랜치 포인터가 이동한 모든 기록을 저장합니다. reset, rebase, checkout 등으로 커밋을 "잃어버린" 것처럼 보여도 reflog를 통해 복구할 수 있습니다. 기본적으로 90일간 보관됩니다.
# HEAD의 이동 기록 조회
git reflog
# 출력 예시:
# a1b2c3d (HEAD -> main) HEAD@{0}: commit: feat: 새 기능 추가
# e4f5g6h HEAD@{1}: reset: moving to HEAD~1
# i7j8k9l HEAD@{2}: commit: fix: 버그 수정
# m1n2o3p HEAD@{3}: checkout: moving from develop to main
# q4r5s6t HEAD@{4}: commit: docs: README 업데이트
# 특정 브랜치의 reflog
git reflog show feature/my-branch
# 날짜 기준으로 확인
git reflog --date=iso
실수 복구 시나리오
# 시나리오 1: git reset --hard로 커밋 삭제한 경우
# 실수로 최근 커밋들을 hard reset
git reset --hard HEAD~3 # 최근 3개 커밋 삭제됨!
# reflog로 삭제된 커밋 해시 확인
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# d4e5f6g HEAD@{1}: commit: feat: 세 번째 기능
# h7i8j9k HEAD@{2}: commit: feat: 두 번째 기능
# l1m2n3o HEAD@{3}: commit: feat: 첫 번째 기능 ← 이전 HEAD
# 이전 상태로 복구
git reset --hard d4e5f6g # reset 전 HEAD로 복구
# 시나리오 2: 브랜치를 실수로 삭제한 경우
git branch -D feature/important-work # 실수로 브랜치 삭제!
# reflog에서 삭제된 브랜치의 마지막 커밋 찾기
git reflog
# a1b2c3d HEAD@{0}: checkout: moving from feature/important-work to main
# b2c3d4e HEAD@{1}: commit: feat: 중요한 기능 완성
# ...
# 브랜치 복구
git checkout -b feature/important-work b2c3d4e
# 시나리오 3: 잘못된 rebase 후 복구
git rebase main # 예상치 못한 충돌 등으로 작업이 망가짐
# rebase 전 상태 찾기
git reflog
# a1b2c3d HEAD@{0}: rebase finished: refs/heads/feature onto abc
# e4f5g6h HEAD@{1}: rebase: feat: 기능 C
# i7j8k9l HEAD@{2}: rebase: feat: 기능 B
# m1n2o3p HEAD@{3}: rebase (start): checkout main
# q4r5s6t HEAD@{4}: commit: feat: 기능 C ← rebase 전 상태
# rebase 전으로 복구
git reset --hard q4r5s6t
# 시나리오 4: 특정 시간대로 복구
git checkout 'HEAD@{2 hours ago}' # 2시간 전 HEAD 상태로
git checkout 'HEAD@{yesterday}' # 어제 HEAD 상태로
reflog 활용 팁
# 분리된 HEAD 상태에서 작업 후 브랜치 잃어버린 경우
git checkout abc123 # detached HEAD에서 작업
git commit -m "작업 내용" # 커밋 해시: def456
git checkout main # 다른 브랜치로 이동 (def456 커밋이 브랜치에 없음!)
# reflog에서 찾기
git reflog
# 현재 HEAD: main
# def456 HEAD@{1}: commit: 작업 내용 ← 이 커밋!
# 새 브랜치로 복구
git branch recovered-work def456
# orphan 커밋 찾기 (어느 브랜치에도 속하지 않는)
git fsck --unreachable
# unreachable commit def456...
# reflog의 기간 설정 (기본 90일)
git config gc.reflogExpire "180 days"
git config gc.reflogExpireUnreachable "90 days"
세 명령어 조합 실전 예시
# 복잡한 시나리오: 잘못된 브랜치에서 작업 후 올바른 브랜치로 이전
# 1. main에서 실수로 직접 작업
git checkout main
# ... 여러 파일 수정 ...
git add .
git commit -m "feat: 신규 기능 구현" # 커밋 해시: abc123
git commit -m "fix: 버그 수정" # 커밋 해시: def456
# 2. 이 커밋들을 feature 브랜치로 이전
git checkout -b feature/new-feature # 새 브랜치 생성
# (이미 커밋이 포함되어 있음)
# 3. main을 이전 상태로 되돌리기
git checkout main
git reset --hard HEAD~2 # 2개 커밋 제거
# 4. 만약 reset 후 후회가 된다면 reflog로 복구
git reflog
git reset --hard <이전_HEAD_해시>
# 다른 브랜치의 특정 커밋만 급하게 가져와야 할 때
git stash push -m "현재 작업 임시 저장" # 현재 작업 stash
git cherry-pick abc123 def456 # 필요한 커밋들 cherry-pick
git stash pop # 임시 저장한 작업 복원
정리 표
| 명령어 | 주요 역할 | 핵심 명령 | 주요 사용 시나리오 |
|---|---|---|---|
| cherry-pick | 특정 커밋만 선택 적용 | git cherry-pick <hash> | 핫픽스 여러 브랜치 적용, 잘못된 브랜치 커밋 이전 |
| stash | 변경사항 임시 저장 | git stash / git stash pop | 긴급 브랜치 전환, pull 전 임시 보관 |
| reflog | HEAD 이동 이력 조회 | git reflog | hard reset 복구, 삭제된 브랜치 복구 |