ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 브랜치를 합치는 세 가지 방법 — merge, rebase, squash는 히스토리 모양이 다르다
    IT 2026. 8. 6. 21:00
    브랜치를 합치는 세 가지 방법 — merge, rebase, squash는 히스토리 모양이 다르다

    git을 처음 배우는 지인이 물었다. "GitHub에서 PR(Pull Request, 브랜치를 합쳐 달라고 요청하는 기능) 머지 버튼을 누르려는데 옵션이 세 개나 있어. Merge commit, Squash and merge, Rebase and merge. 뭘 눌러야 해?" 나도 처음엔 아무거나 눌렀다. 셋 다 "브랜치가 합쳐진다"는 결과는 같아 보이기 때문이다. 그런데 이 세 버튼은 합쳐진 뒤 커밋 히스토리의 모양이 완전히 다르다. 그리고 그 모양의 차이가 몇 달 뒤 "이 버그가 언제 들어왔지?"를 추적할 때 체감 난이도를 가른다.

    이 글은 세 방식이 커밋 그래프에서 실제로 무슨 일을 하는지를 그림으로 비교한다. 결론부터 말하면 셋의 차이는 단 하나의 질문으로 요약된다 — "작업 과정의 역사를 보존할 것인가, 다시 쓸 것인가."

    출발 상황 — 모든 비교의 공통 전제

    세 방식을 비교하려면 같은 출발점이 필요하다. main 브랜치에서 feature 브랜치를 따서 커밋 두 개를 쌓았는데, 그 사이에 main에도 다른 사람의 커밋이 하나 들어온 상황이다. 실무에서 가장 흔한 형태다.

    diagram

    다이어그램 설명. 화살표는 시간 순서(부모 커밋 → 자식 커밋)다. "C2" 커밋에서 역사가 두 갈래로 갈라졌다 — 한 갈래는 동료가 추가한 "C3"로, 다른 갈래는 내가 쌓은 "F1"과 "F2"로 이어진다. 이렇게 두 브랜치가 서로 모르는 커밋을 각자 갖고 있는 상태가 "합치기"가 필요한 순간이다. 이제 이 갈래를 하나로 만드는 세 가지 방법을 차례로 본다.

    방법 1 — merge commit: 역사를 그대로 두고 매듭을 하나 만든다

    # main으로 이동해서 feature를 병합
    git checkout main
    git merge feature
    # → "Merge branch 'feature'" 라는 새 커밋(M)이 자동 생성된다
    

    코드 설명. git merge feature는 기존 커밋을 하나도 건드리지 않는다. 대신 부모를 두 개 가진 특별한 커밋(merge commit)을 새로 만들어 갈라진 두 갈래를 매듭짓는다. 옵션도 플래그도 없는 git의 기본 동작이다.

    diagram

    다이어그램 설명. "M — merge commit"으로 들어오는 화살표가 두 개인 것이 핵심이다 — "C3"에서 하나, "F2"에서 하나. 갈라졌던 역사가 그대로 남아 있고, 매듭 커밋이 두 갈래를 봉합했다. 장점은 정직함이다. "이 기능은 이 시점에 분기해서, 이 커밋들을 거쳐, 이 시점에 합류했다"가 그래프에 그대로 기록된다. 단점은 브랜치가 수십 개 오가는 저장소에서 그래프가 철길 조차장처럼 얽혀서, git log --graph로 봤을 때 읽기 힘들어진다는 것이다.

    방법 2 — rebase: 내 커밋을 상대 끝으로 옮겨 심는다

    # feature 브랜치에서, main 끝으로 내 커밋들을 옮겨 심기
    git checkout feature
    git rebase main
    # → F1, F2가 C3 뒤에 다시 만들어진다 (F1', F2')
    git checkout main
    git merge feature   # 일직선이 됐으므로 매듭 없이 그냥 따라잡기만 한다
    

    코드 설명. rebase는 이름 그대로 "base(출발점)를 다시(re) 잡는" 명령이다. 내 커밋 "F1", "F2"를 잠시 들어냈다가, main의 최신 커밋 "C3" 뒤에 순서대로 다시 적용한다. 중요한 건 이게 이동이 아니라 복제라는 점이다 — 다시 만들어진 커밋은 내용이 같아도 완전히 새로운 커밋(F1', F2')이고, 커밋을 식별하는 해시(commit hash, 커밋 내용으로 계산되는 고유 ID)가 바뀐다.

    diagram

    다이어그램 설명. 갈래가 사라지고 역사가 일직선이 됐다. "C3" 뒤에 "F1'의 복제본"과 "F2'의 복제본"이 이어 붙어, 마치 처음부터 순서대로 작업한 것처럼 보인다. 장점은 이 깔끔함이다 — 일직선 히스토리는 읽기 쉽고, 버그가 들어온 커밋을 이진 탐색으로 찾는 git bisect 같은 도구도 잘 든다. 대신 "실제로는 병렬로 작업했다"는 사실이 역사에서 지워진다. 역사를 다시 쓴 것이다. 이 "다시 쓰기"가 공유 브랜치에서 왜 사고로 이어지는지는 분량이 커서 별도 글에서 다룬다.

    방법 3 — squash merge: 여러 커밋을 한 덩어리로 뭉쳐서 얹는다

    git checkout main
    git merge --squash feature
    # → F1 + F2의 변경 내용이 스테이징만 된 상태. 커밋은 아직 없다
    git commit -m "feat: 로그인 기능 추가"
    # → 변경 전체가 새 커밋 S 하나로 기록된다
    

    코드 설명. --squash(짓누르다)는 feature의 커밋 여러 개를 변경 내용만 합산해서 커밋 하나로 뭉친다. GitHub의 "Squash and merge" 버튼이 하는 일이 정확히 이것이다. 뭉쳐진 새 커밋 S는 merge commit이 아니라 부모가 하나인 평범한 커밋이다.

    diagram

    다이어그램 설명. 히스토리는 rebase처럼 일직선인데, 내 커밋 두 개가 "F1+F2를 뭉친 커밋 하나"로 압축됐다. "오타 수정", "리뷰 반영" 같은 자잘한 중간 커밋들이 최종 역사에서 사라지고 "기능 하나 = 커밋 하나"만 남는다. main의 역사가 기능 단위 목차처럼 읽히는 게 장점이다. 함정도 여기에 있다 — 중간 과정이 통째로 사라지므로, 뭉쳐진 커밋 안에서 "둘째 수정이 버그 원인이었다"를 나중에 분리해 볼 수 없다. 그리고 원래 feature 브랜치의 커밋들과 S는 git 입장에서 아무 관계가 없는 남남이라, 합친 뒤 그 브랜치를 지우지 않고 계속 쓰면 다음 병합에서 같은 변경을 두 번 만나는 혼란이 생긴다. squash 후 브랜치는 지운다가 세트 규칙이다.

    세 방식을 한 축에서 비교하면

      merge commit rebase squash merge
    히스토리 모양 갈래 + 매듭 보존 일직선 (커밋 개수 유지) 일직선 (커밋 1개로 압축)
    원래 커밋 그대로 보존 복제 후 해시 변경 사라지고 새 커밋 1개
    "언제 분기했나" 추적 가능 불가능 불가능
    중간 커밋 단위 추적 가능 가능 불가능
    어울리는 곳 릴리스 브랜치 통합처럼 "합류 사실" 자체가 기록 가치인 곳 혼자 쓰는 브랜치를 main에 얹기 전 정돈 PR 단위로 main을 목차처럼 유지하고 싶은 팀

    표에서 보이듯 정답은 없고 무엇을 기록으로 남기고 싶은가의 선택이다. 실제로 많은 팀이 셋을 섞어 쓴다. 예를 들어 "PR은 squash로 합쳐 main을 깔끔하게 유지하되, 릴리스 브랜치를 main에 되돌릴 때는 merge commit으로 합류 지점을 남긴다" 같은 식이다. GitHub이 저장소 설정에서 세 버튼을 개별적으로 켜고 끌 수 있게 해 둔 것도 이 때문이다.

    정리 — 버튼이 아니라 역사관을 고르는 것이다

    merge는 역사를 보존하고, rebase는 역사를 다시 쓰고, squash는 역사를 요약한다. PR 머지 버튼 세 개는 기능 버튼이 아니라 "우리 팀은 커밋 히스토리를 무엇으로 취급할 것인가"라는 질문에 대한 세 가지 답이다. 나는 개인 저장소에서는 과정이 남는 merge commit을, 여러 자잘한 커밋이 쌓인 실험 브랜치는 squash를 쓴다. 어느 쪽이든 팀 전체가 하나의 답으로 합의하고 일관되게 쓰는 것이 세 방식의 우열보다 중요하다.


    참고한 공개 자료:

    • Pro Git Book — Basic Branching and Merging: https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
    • Pro Git Book — Rebasing: https://git-scm.com/book/en/v2/Git-Branching-Rebasing
    • GitHub Docs — About pull request merges: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges
    • Atlassian Git Tutorial — Merging vs. Rebasing: https://www.atlassian.com/git/tutorials/merging-vs-rebasing

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

Designed by Tistory.