ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • cherry-pick과 revert — 브랜치 전체가 아니라 커밋 하나만 다루고 싶을 때
    IT 2026. 8. 6. 23:00
    cherry-pick과 revert — 브랜치 전체가 아니라 커밋 하나만 다루고 싶을 때

    merge와 rebase를 익힌 지인이 다음 상황에서 다시 막혔다. "운영 서버에서 버그가 터졌어. 수정 커밋은 개발 브랜치에 이미 있는데, 개발 브랜치에는 아직 배포하면 안 되는 미완성 기능이 잔뜩 섞여 있어. merge를 하면 그것까지 다 딸려 가잖아?" 정확한 딜레마다. merge와 rebase는 브랜치 단위 도구라서, 브랜치에 쌓인 커밋을 골라 담을 수 없다. 이럴 때 필요한 것이 커밋 단위 도구 — 원하는 커밋 하나만 가져오는 cherry-pick과, 원하는 커밋 하나만 취소하는 revert다.

    둘은 방향만 반대일 뿐 같은 원리로 움직인다. 커밋을 "스냅샷"이 아니라 "변경분(diff)"으로 읽어서, 그 변경분을 다른 곳에 순방향/역방향으로 적용한다.

    cherry-pick — 케이크 위의 체리만 집어 온다

    # 개발 브랜치에서 버그 수정 커밋의 해시를 확인
    git log dev --oneline
    # f3a92c1 fix: 결제 금액 반올림 버그 수정   ← 이것만 필요
    # 8b1d4e7 wip: 신규 대시보드 (미완성)
    # 2c9f0a3 wip: 알림 기능 (미완성)
    
    # 운영 브랜치로 가서 그 커밋 하나만 적용
    git checkout release
    git cherry-pick f3a92c1
    

    코드 설명. cherry-pick은 지정한 커밋이 부모 대비 만든 변경분을 계산해서 현재 브랜치에 새 커밋으로 다시 적용한다. 케이크(브랜치)를 통째로 가져오지 않고 위에 얹힌 체리(커밋)만 집어 온다는 비유 그대로다. 미완성 기능 커밋들은 dev에 그대로 남고, release에는 버그 수정만 도착한다.

    diagram

    다이어그램 설명. 점선이 cherry-pick의 동작이다 — "fix: 반올림 버그" 커밋의 변경분만 release 쪽으로 건너가 "복제본" 커밋이 됐다. 여기서 rebase 글을 읽었다면 익숙한 패턴이 보일 것이다: 이것도 이동이 아니라 복제다. 부모가 다르므로 해시도 다르다. 사실 rebase의 내부 동작이 "커밋들을 순서대로 cherry-pick하는 것"과 원리상 같다 — cherry-pick을 이해하면 rebase가 공짜로 이해되는 이유다. 함정도 복제에서 나온다: 같은 변경이 dev와 release 양쪽에 서로 다른 해시로 존재하게 되므로, 나중에 dev를 release에 merge할 때 git이 같은 변경을 두 번 적용하려다 잔충돌을 낼 수 있다. cherry-pick은 정규 배포 경로가 아니라 긴급 수송용 예외 도구로 아껴 쓰는 게 좋다.

    revert — 삭제가 아니라 "반대 방향 커밋 추가"

    이번엔 반대 상황. 배포까지 나간 커밋 하나가 사고를 쳤다. 초심자의 본능은 "그 커밋을 지우고 싶다"인데, 이미 push된 커밋을 지우는 것(reset 후 force push)은 rebase 글에서 본 "공유된 역사 다시 쓰기" 사고를 그대로 재현한다. revert는 다르게 접근한다 — 역사는 건드리지 않고, 문제 커밋의 변경을 정확히 반대로 뒤집는 새 커밋을 위에 얹는다.

    # 문제 커밋의 변경을 반대로 적용하는 새 커밋을 만든다
    git revert f3a92c1
    # → "Revert 'fix: 결제 금액 반올림 버그 수정'" 커밋이 생성됨
    # 기존 역사는 그대로. force push 불필요. 동료에게 무해.
    

    코드 설명. f3a92c1이 어떤 줄을 추가했다면 revert 커밋은 그 줄을 삭제하고, 삭제했다면 되살린다. 결과적으로 코드 상태는 그 커밋이 없던 때로 돌아가지만, 역사에는 "적용했다"와 "되돌렸다"가 둘 다 남는다. 회계 장부에서 잘못 기입한 줄을 지우개로 지우지 않고 상쇄 분개를 새로 적는 것과 같은 원리다 — 장부(역사)의 무결성이 유지되므로 이미 그 역사를 받아 간 동료들에게 아무 피해가 없다.

    diagram

    다이어그램 설명. "C2 — 사고 친 커밋"은 그 자리에 그대로 있다. 맨 뒤에 "Revert C2" 커밋이 추가돼 코드만 원상 복구됐다. 역사가 앞으로만 자라므로(append-only) 이미 pull해 간 동료들과 어긋날 일이 없다 — 공유된 브랜치에서 되돌리기의 유일한 정답이 revert인 이유다. 참고로 "지운 듯 되돌리기"(reset)는 아직 push하지 않은 내 로컬 커밋에만 안전한 도구로, 경계선은 rebase 때와 완전히 같다: 그 커밋을 나만 아는가.

    둘을 나란히 놓으면 — 방향만 반대인 쌍둥이

      cherry-pick revert
    하는 일 다른 브랜치의 커밋 변경분을 순방향 적용 내 브랜치의 커밋 변경분을 역방향 적용
    결과물 새 커밋 (원본의 복제) 새 커밋 (원본의 반전)
    기존 역사 안 건드림 안 건드림
    대표 시나리오 hotfix를 릴리스 브랜치로 긴급 수송 배포 나간 문제 커밋을 안전하게 취소
    충돌 가능성 있음 (대상 브랜치가 그 줄을 이미 고쳤다면) 있음 (이후 커밋이 그 줄을 또 고쳤다면)

    표의 마지막 행처럼 둘 다 충돌이 날 수 있다 — 변경분을 다른 맥락에 적용하는 작업이므로 merge·rebase와 같은 규칙의 충돌이 발생하고, 해결 방법도 같다(--continue / --abort). 한편 merge commit을 revert할 때는 "어느 부모 쪽으로 되돌릴지"를 -m 옵션으로 지정해야 하는 심화 케이스가 있는데, 처음에는 "merge commit의 revert는 일반 revert보다 까다롭다"는 사실만 알아 둬도 충분하다.

    정리 — 브랜치의 문법과 커밋의 문법

    merge·rebase가 브랜치라는 문장을 통째로 다루는 문법이라면, cherry-pick·revert는 커밋이라는 단어 하나를 집어 옮기고 지우는 문법이다. 그리고 넷을 관통하는 원리는 하나로 수렴한다 — git에서 안전한 조작은 언제나 "새 커밋을 추가"하는 것이고, 위험한 조작은 언제나 "공유된 역사를 다시 쓰는" 것이다. cherry-pick과 revert가 실무에서 사랑받는 이유는 정확히 전자에 속하기 때문이다.


    참고한 공개 자료:

    • git-cherry-pick 공식 문서: https://git-scm.com/docs/git-cherry-pick
    • git-revert 공식 문서 (-m 옵션): https://git-scm.com/docs/git-revert
    • Pro Git Book — Undoing Things: https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things
    • Atlassian Git Tutorial — Cherry Picking: https://www.atlassian.com/git/tutorials/cherry-pick

    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.