병합 커밋
고친 사람 github-actions[bot]
병합 커밋은 갈라져 따로 자란 두 갈래의 작업을 한 줄기로 다시 잇는 커밋입니다. 앞선 커밋을 하나가 아니라 둘 이상 가리켜서, 어느 두 갈래가 어디서 만났는지를 이력에 남깁니다. 합쳐 놓은 결과도 이 커밋이 담고 있습니다.
쉽고 빠른 이해
병합 커밋은 갈라진 두 갈래를 하나로 잇는 커밋입니다. 기능을 따로 만들던 갈래를 본 줄기에 합칠 때 그 이음매로 하나 생깁니다.
이런 커밋이 없으면 합친 결과를 둘 데가 없습니다. 커밋 하나는 앞 커밋을 하나만 가리키므로, 두 갈래를 모두 앞에 두려면 앞을 둘 가리키는 커밋이 있어야 합니다.
- 두 갈래가 갈라진 지점을 찾습니다
- 그 지점과 두 갈래의 끝, 셋을 견줘 합친 파일 한 벌을 만듭니다
- 그 파일 한 벌을 담고 두 끝을 앞으로 가리키는 커밋을 하나 얹습니다
대가가 있습니다. 이력이 한 줄이 아니라 갈라졌다 붙는 그물이 됩니다. 합치는 일이 잦으면 이음매만 쌓여서 사람이 쓴 커밋을 골라 읽기가 번거로워집니다.
상세
같은 원고를 두 사람이 각자 복사해 가서 따로 고쳐 왔다고 해 봅시다. 두 원고를 한 벌로 합친 새 원고를 만들면, 거기에 어느 둘에서 나왔는지를 적어 둬야 나중에 되짚을 수 있습니다. 병합 커밋은 그 합친 원고이면서 동시에 그 쪽지입니다.
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
병합 커밋을 얼마나 남길지 정하는 규칙
브랜치 전략 · 기능 브랜치 · 메인라인 · 지속적 통합 · 커밋에서 배포까지
병합 커밋을 다루는 도구
다른 이름: merge commit · 머지 커밋