사전 병합 커밋
개념

병합 커밋

gabury1고친 사람 github-actions[bot]

병합 커밋은 갈라져 따로 자란 두 갈래의 작업을 한 줄기로 다시 잇는 커밋입니다. 앞선 커밋을 하나가 아니라 둘 이상 가리켜서, 어느 두 갈래가 어디서 만났는지를 이력에 남깁니다. 합쳐 놓은 결과도 이 커밋이 담고 있습니다.

쉽고 빠른 이해

병합 커밋은 갈라진 두 갈래를 하나로 잇는 커밋입니다. 기능을 따로 만들던 갈래를 본 줄기에 합칠 때 그 이음매로 하나 생깁니다.

이런 커밋이 없으면 합친 결과를 둘 데가 없습니다. 커밋 하나는 앞 커밋을 하나만 가리키므로, 두 갈래를 모두 앞에 두려면 앞을 둘 가리키는 커밋이 있어야 합니다.

  1. 두 갈래가 갈라진 지점을 찾습니다
  2. 그 지점과 두 갈래의 끝, 셋을 견줘 합친 파일 한 벌을 만듭니다
  3. 그 파일 한 벌을 담고 두 끝을 앞으로 가리키는 커밋을 하나 얹습니다

대가가 있습니다. 이력이 한 줄이 아니라 갈라졌다 붙는 그물이 됩니다. 합치는 일이 잦으면 이음매만 쌓여서 사람이 쓴 커밋을 골라 읽기가 번거로워집니다.

상세

같은 원고를 두 사람이 각자 복사해 가서 따로 고쳐 왔다고 해 봅시다. 두 원고를 한 벌로 합친 새 원고를 만들면, 거기에 어느 둘에서 나왔는지를 적어 둬야 나중에 되짚을 수 있습니다. 병합 커밋은 그 합친 원고이면서 동시에 그 쪽지입니다.

Git 같은 버전 관리 도구에서 커밋은 어느 시점의 파일 전체를 한 벌로 찍어 둔 것입니다. 한 벌만 있으면 무엇이 먼저였는지 알 수 없으므로, 커밋마다 바로 앞 커밋을 가리키는 값을 함께 담습니다. 그 앞 커밋을 부모 커밋이라고 부릅니다.

보통 커밋은 부모가 하나입니다. 저장소의 첫 커밋만 앞이 없어서 부모가 없습니다. 병합 커밋은 부모가 둘 이상인 커밋입니다. 부모의 수가 곧 이 커밋에서 몇 갈래가 만났는지를 말합니다.

부모를 둘 둬야 하는 까닭

두 갈래가 각자 고친 파일을 한 벌로 잘 합쳐 놓았다고 해도, 그 결과를 담을 커밋이 없으면 합치기가 끝나지 않습니다. 부모를 하나만 가리키는 커밋으로는 두 갈래 중 한쪽만 앞에 둘 수 있습니다.

부모를 하나만 허용하는 도구라면 한쪽 커밋을 다른 쪽 뒤로 옮겨 적는 수밖에 없습니다. 이렇게 옮겨 적는 것을 리베이스라고 합니다. 그러면 이력은 한 줄로 남지만, 두 갈래가 어디서 갈라졌고 어디서 만났는지는 남지 않습니다.

병합 커밋이 생기는 때

브랜치는 같은 저장소 안에서 커밋을 따로 쌓아 가는 갈래입니다. 브랜치 이름은 그 갈래의 끝 커밋을 가리키는 이름표입니다. 커밋이 하나 쌓이면 이름표도 새 끝으로 따라 옮겨 갑니다.

두 브랜치를 합칠 때 합치기를 받는 쪽을 합쳐 받는 브랜치, 그 안으로 들어가는 쪽을 합쳐 넣는 브랜치라고 부르겠습니다. 갈라진 뒤 양쪽 모두에 새 커밋이 쌓였다면 어느 쪽도 다른 쪽의 뒤가 아닙니다. 이때 병합을 하면 병합 커밋이 하나 생깁니다.

갈라지기 직전의 커밋, 곧 양쪽이 마지막으로 같았던 커밋을 공통 조상이라고 합니다. 공통 조상과 두 브랜치의 끝, 이렇게 셋을 줄마다 견줘 합치는 방식이 세 갈래 병합입니다. 한쪽만 고친 줄은 고친 쪽을 가져옵니다. 양쪽 다 고친 줄만 사람에게 넘깁니다.

한쪽에만 커밋이 쌓였다면 이야기가 다릅니다. 합쳐 받는 main 에는 새 커밋이 없고 합쳐 넣는 login 에만 쌓였다면, main 의 끝은 login 이 지나온 커밋 중 하나입니다.

그러면 main 이름표를 login 의 끝으로 옮기기만 해도 둘이 같아집니다. 이것을 패스트 포워드라고 합니다. 이때는 병합 커밋이 만들어지지 않습니다.

아래 그림은 main 에서 갈라 낸 login 브랜치를 다시 합친 모양입니다. 병합 커밋이 가리키는 두 부모에는 순서가 있어서 첫째·둘째로 부릅니다.

flowchart TD
    M["병합 커밋 · main 의 끝"] -->|첫째 부모| A3["커밋 3 · main"]
    M -->|둘째 부모| B2["커밋 5 · login"]
    B2 --> B1["커밋 4"]
    A3 --> C2["커밋 2 · 공통 조상"]
    B1 --> C2
    C2 --> C1["커밋 1"]

화살표는 커밋이 자기 부모를 가리키는 방향입니다. main 과 login 은 커밋 2 에서 갈라져 각자 커밋을 쌓았습니다. 맨 위의 병합 커밋이 두 끝을 모두 부모로 가리킵니다. 이 커밋을 만든 뒤 main 이름표는 병합 커밋으로 옮겨 갑니다.

부모의 순서

부모 둘에는 순서가 있습니다. 첫째 부모는 합쳐 받는 브랜치의 끝입니다. 둘째 부모는 합쳐 넣는 브랜치의 끝입니다. 위 그림처럼 main 에서 login 을 합쳤다면 커밋 3 이 첫째, 커밋 5 가 둘째입니다.

이 순서 덕분에 이력을 첫째 부모만 따라 읽을 수 있습니다. 그러면 갈라져 나갔던 잔가지가 접힙니다. 본 줄기만 훑게 됩니다.

병합을 되돌릴 때에도 이 순서를 씁니다. 되돌리기는 남은 한 줄기를 본 줄기로 삼아야 하므로, 첫째·둘째 중 몇 번째 부모를 본 줄기로 칠지 사람이 지정합니다. 그 숫자가 부모 번호입니다.

사람이 골라야 하는 충돌

두 갈래가 같은 파일의 같은 줄을 서로 다르게 고쳤으면 기계는 한쪽을 고를 수 없습니다. 그러면 병합은 커밋을 만들지 못하고 중간에 멈춥니다. 사람이 충돌 해소를 끝내야 병합 커밋이 만들어집니다.

멈춰 있는 동안에도 두 갈래의 끝은 손대지 않은 채 남아 있습니다. 합치기를 그만두면 병합 커밋 없이 원래 상태로 돌아갑니다.

지금까지 본 갈림을 한데 모으면 병합 커밋이 생기는 때와 안 생기는 때가 이렇게 나뉩니다.

flowchart TD
    Q1{"합쳐 받는 브랜치가<br/>합쳐 넣는 브랜치의 조상인가"}
    Q1 -->|예| FF["패스트 포워드<br/>병합 커밋이 안 생긴다"]
    Q1 -->|아니오| Q2{"줄마다<br/>한쪽만 고쳤나"}
    Q2 -->|예| MC["세 갈래 병합이 끝나고<br/>병합 커밋이 생긴다"]
    Q2 -->|아니오| ST["충돌로 멈춘다"]
    ST -->|충돌 해소를 끝낸다| MC
    ST -->|합치기를 그만둔다| BK["원래 상태로 돌아간다"]

합치기가 병합 커밋을 남기는지는 두 물음으로 갈립니다. 합쳐 받는 브랜치가 합쳐 넣는 브랜치의 조상이면 패스트 포워드로 끝납니다. 이때는 병합 커밋이 없습니다.

조상이 아니면 세 갈래 병합이 돕니다. 줄마다 한쪽만 고쳤으면 병합 커밋이 바로 생깁니다. 양쪽 다 고친 줄이 있으면 멈췄다가, 사람이 충돌 해소를 끝내야 생깁니다. 합치기를 그만두면 안 생깁니다.

병합 커밋을 남기지 않는 합치기

갈라진 둘을 합치는 방법이 병합만 있는 것은 아닙니다. 세 방법은 무엇을 남기고 무엇을 지우는지가 다릅니다.

합치는 방법 새로 생기는 커밋 이력에 남는 모양
병합 부모 둘인 병합 커밋 하나 갈라졌다 붙은 자국이 남습니다
리베이스 옮겨 붙는 커밋마다 하나씩, 부모는 전부 하나 한 줄로 곧게 남습니다
스쿼시 여러 커밋을 뭉친 커밋 하나, 부모 하나 한 줄로 남고 갈래 안의 커밋은 사라집니다

병합은 실제로 있었던 갈라짐을 남깁니다. 나머지 둘은 읽기 쉬운 한 줄을 남깁니다. 어느 쪽을 고를지는 그 저장소가 이력에서 무엇을 찾아 읽느냐에 달려 있습니다.

세 방법이 이력에 남기는 모양은 이렇게 다릅니다. 공통 조상 하나와 본 줄기 커밋 하나, 갈래 커밋 둘을 놓고 셋을 나란히 견줬습니다.

flowchart TD
    subgraph 병합
        MB["병합 커밋"] --> M2["본 줄기 커밋"]
        MB --> M4["갈래 커밋 둘째"]
        M4 --> M3["갈래 커밋 첫째"]
        M2 --> M1["공통 조상"]
        M3 --> M1
    end
    subgraph 리베이스
        R4["갈래 커밋 둘째"] --> R3["갈래 커밋 첫째"]
        R3 --> R2["본 줄기 커밋"]
        R2 --> R1["공통 조상"]
    end
    subgraph 스쿼시
        S3["갈래를 뭉친 커밋"] --> S2["본 줄기 커밋"]
        S2 --> S1["공통 조상"]
    end

일부러 남기는 병합 커밋

패스트 포워드로 붙을 수 있는 때에도 병합 커밋을 만들라고 시킬 수 있습니다. 그러면 기능 하나가 어디서 갈라져 어디서 합쳐졌는지가 이력에서 한 덩이로 보입니다.

터미널
git switch main         # 받을 쪽으로 간다
git merge --no-ff login # 이음매를 남긴다

대신 커밋 하나짜리 갈래를 합칠 때에도 이음매가 하나씩 더 붙습니다. 이력을 그물로 두고 갈래를 드러낼지, 한 줄로 두고 커밋만 볼지를 저장소마다 미리 정해 두는 편이 나중에 덜 엇갈립니다.

관련 항목

병합 커밋을 만들어 내는 작업

병합 · 세 갈래 병합 · 풀 · 풀 리퀘스트 · 충돌 해소 · 페치

병합 커밋을 남기지 않는 합치기 방법

리베이스 · 스쿼시 · 패스트 포워드 · 체리픽 · 되돌리기

병합 커밋을 이루는 구성 요소

커밋 · 커밋 객체 · 부모 커밋 · 공통 조상 · 스냅샷 · 커밋 메시지

병합 커밋이 놓이는 이력 구조

DAG · 브랜치 · HEAD · 원격 추적 브랜치 · 커밋 그래프 · 리비전 이력

병합 커밋에서 자주 나는 문제

병합 충돌 · 십자 병합 · 이력 강제 덮어쓰기 · 분리된 HEAD

병합 커밋을 얼마나 남길지 정하는 규칙

브랜치 전략 · 기능 브랜치 · 메인라인 · 지속적 통합 · 커밋에서 배포까지

병합 커밋을 다루는 도구

Git · GitHub · GitLab · Mercurial · 버전관리

다른 이름: merge commit · 머지 커밋