3-way merge 는 병합할 두 브랜치와 그 공통 조상(merge base), 딱 세 스냅샷의 줄만 비교해 충돌 여부를 판정한다. 그 사이에 쌓인 중간 커밋들은 판정에 들어가지 않아, 충돌이 안 떴다고 병합이 옳다는 뜻은 아니다.
충돌 없음, 그런데 문서가 스스로 부정한다

PR(풀 리퀘스트) 화면에 "충돌 없음 — 자동으로 병합할 수 있습니다"라는 초록 문구가 뜨면 대개 그대로 버튼을 누른다. 도구가 문제없다고 했으니 문제가 없다고 읽는 것이다. 그런데 git 공식 문서에는 그 안심을 정면으로 뒤집는 사례가 적혀 있다. 두 브랜치가 같은 줄을 똑같이 고쳤고, 그중 한쪽이 나중에 그 변경을 되돌렸다고 하자. 병합하면 되돌림은 결과에 남지 않는다. 충돌도 뜨지 않는다.
일부러 꾸며낸 상황도 아니다. 급한 수정을 두 사람이 각자 넣었다가 한쪽이 부작용을 발견해 되돌리는 일은 어느 팀에서나 생긴다. 문서는 왜 이런 일이 벌어지는지도 같은 문단에서 밝혀 둔다.
It occurs because only the heads and the merge base are considered when performing a merge, not the individual commits.
병합을 수행할 때는 두 브랜치의 끝(heads)과 공통 조상(merge base)만 고려되고 개별 커밋들은 고려되지 않기 때문에, 이런 일이 생긴다.
git 공식 문서 `merge-strategies`"충돌 없음"은 병합이 옳다는 판정이 아니다. 도구가 들여다본 세 장의 스냅샷에서 줄이 서로 부딪히지 않았다는, 그보다 훨씬 좁은 보고다. 이 글에서는 그 판정축이 무엇을 보고 무엇을 안 보는지, 그 밖에서 실제로 무엇이 깨지는지, 그리고 널리 인용되는 "33%"라는 숫자의 분모가 어디에 붙어 있는지를 차례로 확인한다.
3-way merge 가 실제로 보는 세 장
판정에 들어가는 스냅샷은 셋이다. 두 브랜치의 마지막 상태(ours·theirs), 그리고 둘이 갈라지기 직전의 공통 조상(merge base)이다. 이름의 "3-way"가 가리키는 것이 바로 이 셋이다. 조상은 기준선 노릇을 한다 — 어느 쪽이 무엇을 바꿨는지는 조상과 견줘 봐야 알 수 있기 때문이다.
규칙 자체는 줄 하나 단위로 아주 단순하다. 한쪽만 조상과 달라졌으면 달라진 쪽을 채택한다. 조상과 ours 가 같다면 그 줄을 고친 것은 theirs 뿐이니, theirs 의 값을 그대로 쓰면 된다는 판단이다. 양쪽이 똑같이 달라졌으면 같은 변경을 두 번 넣지 않고 한 번만 반영한다. 양쪽이 서로 다르게 달라졌을 때만 충돌 마커를 세우고 사람을 부른다. 그림 1에 조합별 결과를 한 판에 모아 두었다.
값을 넣어 보면 더 분명하다. 조상 파일의 어떤 줄이 timeout = 30 이라고 하자. ours 에서 그 줄이 timeout = 5 로 바뀌었고 theirs 는 그대로라면, 병합 결과는 timeout = 5 다. 반대로 theirs 만 timeout = 10 으로 바꿨다면 결과는 timeout = 10 이다.
둘 다 손댔는데 값이 5와 10으로 갈릴 때, 그때 비로소 충돌이 뜬다. 도구가 어느 쪽이 옳은지 고를 근거를 갖고 있지 않기 때문이다. 한쪽만 바뀌었을 때는 근거가 있다 — 나머지 한쪽이 조상과 같으니 그 줄을 건드리지 않았다고 읽으면 된다. 판정에 필요한 것은 이 세 값이 전부다.
여기서 빠진 것이 이 글의 주제다. 두 브랜치가 갈라진 뒤 각자 쌓은 중간 커밋들은 판정 입력이 아니다. 스무 개의 커밋을 거쳐 도착한 상태든 한 번에 도착한 상태든, 도구가 읽는 것은 도착점 하나뿐이다. 그 사이에 무엇을 넣었다 뺐는지, 무엇을 고쳤다 되돌렸는지는 판정에 흔적을 남기지 않는다. 커밋을 잘게 나눠 쌓든 한 덩어리로 쌓든 병합의 판정은 달라지지 않는다.
되돌림이 사라지는 규칙을 코드로 재현
문서의 한 문단을 그냥 믿고 넘어갈 수도 있다. 그런데 앞 절의 규칙이 워낙 단순해서 직접 돌려 보는 편이 빠르다. 세 값을 놓고 갈래를 넷으로 나누는 판정은 그대로 열 줄 남짓한 파이썬 함수가 된다. 아래 코드는 git 을 실행하지 않는다.
판정 규칙만 그대로 옮겨 놓고, 입력으로는 문서가 든 되돌림 사례를 넣었다. 두 브랜치가 접속 타임아웃을 30에서 5로 똑같이 줄였고, theirs 쪽만 나중에 30으로 되돌린 상황이다. 출력에서 확인할 것은 충돌 수와 병합 결과, 두 줄이다.
BASE = ["def connect(host):", " timeout = 30", " return open(host, timeout)"]
OURS = ["def connect(host):", " timeout = 5", " return open(host, timeout)"]
THEIRS = ["def connect(host):", " timeout = 30", " return open(host, timeout)"]
def merge3(base, ours, theirs):
merged, conflicts = [], 0
for b, o, t in zip(base, ours, theirs):
if o == t: # 양쪽이 같다
merged.append(o)
elif b == o: # theirs 만 조상과 다르다
merged.append(t)
elif b == t: # ours 만 조상과 다르다
merged.append(o)
else: # 양쪽이 서로 다르게 바뀌었다
merged.append("<<<<<<< 충돌 >>>>>>>")
conflicts += 1
return merged, conflicts
merged, conflicts = merge3(BASE, OURS, THEIRS)
print("theirs 커밋 이력 :", " -> ".join(["timeout = 30", "timeout = 5", "timeout = 30"]))
print("판정 입력 세 장 :", BASE[1].strip(), "/", OURS[1].strip(), "/", THEIRS[1].strip())
print("충돌 수 :", conflicts)
print("병합 결과 :", merged[1].strip())
print("theirs 최종 상태 :", THEIRS[1].strip())theirs 커밋 이력 : timeout = 30 -> timeout = 5 -> timeout = 30
판정 입력 세 장 : timeout = 30 / timeout = 5 / timeout = 30
충돌 수 : 0
병합 결과 : timeout = 5
theirs 최종 상태 : timeout = 30theirs 브랜치는 세 커밋을 거쳐 30으로 돌아왔지만, 판정에 들어간 것은 도착점 하나였다. 조상도 30이고 theirs 도 30이니 규칙은 "theirs 는 이 줄을 건드리지 않았다"고 읽는다. 그러면 남는 것은 ours 의 5뿐이고, 충돌은 0이다. 문서가 적어 둔 문장은 비유가 아니라 이 규칙에서 곧바로 나오는 결과다.
되돌린 사람 쪽에서 보면 사정이 나쁘다. 자기 브랜치에서 분명히 되돌렸고, 병합할 때 아무 경고도 받지 못했는데, 결과 파일에는 되돌리기 전 값이 앉아 있다. 도구는 거짓말을 하지 않았다. 다만 그 되돌림이라는 사건이 애초에 도구의 시야에 들어간 적이 없었을 뿐이다.
충돌 없이도 깨진다 — 구문 기준 실측
그러면 이런 일은 얼마나 잦을까. On the Correctness of Software Merge(2026)는 자바 오픈소스 76개 프로젝트의 병합 시나리오 43,774건을 도구별로 다시 돌렸다. git 의 기본 병합은 그중 31,533건을 충돌 없이 통과시켰다. 그 무충돌 결과 가운데 40건, 곧 31,533건의 0.13%가 "양쪽 편집을 모두, 그리고 그것만 반영해야 한다"는 기준을 어겼다. 논문의 Fig. 12 가 그 사례다 — 한쪽이 끼운 코드와 다른 쪽이 고친 코드가 같아지자, 도구는 충돌 대신 같은 코드를 두 벌 복사해 넣었다.
0.13%는 구문 기준으로도 오답인 병합의 비율이다. 코드 구조만 보고 판정하니, 문법은 멀쩡한데 의미상 서로를 망가뜨리는 편집은 세지 않는다. 40건은 상한이 아니라 하한이다.
"33%가 깨져 있다"의 분모를 다시 센다
가장 자주 인용되는 숫자는 따로 있다. "깨끗하게 병합된 것의 33%가 실은 깨져 있다"는 문장이다. 출처는 Brun 외의 2011년 논문이고, 근거인 Figure 4 를 다시 세면 분모가 어긋난다.
Figure 4 는 9개 프로젝트 3,562건을 담는다. 그중 빌드·테스트까지 되돌려 본 것은 세 프로젝트 1,694건이다. 텍스트 충돌 266건, 빌드 실패 24건, 테스트 실패 109건이고, 나머지 1,295건은 문제없이 통과했다(그림 2). 논문이 깨끗한 병합 399건이라 부른 그 399는 266+24+109, 곧 전체 충돌의 수다.
그래서 33%(133/399)는 그 399 중 텍스트로 드러나지 않은 133건의 몫이다. 그 문장이 재려던 값은 다른 쪽이다 — 깨끗하다고 통과시킨 병합은 1,694에서 266을 뺀 1,428건, 그중 깨진 133건은 9.3%(133/1,428)다. 겁주는 숫자는 3분의 1 아래로 내려가도, 조용히 깨진 133건은 그대로다.
잘못 붙은 분모는 다음 논문으로 옮겨 갔다
분모를 잘못 붙인 문장은 그 논문에서 끝나지 않았다. Kasi & Sarma 의 2013년 논문이 관련 연구를 정리하며 그 문장을 분모째 다시 인용했다. 원표인 Figure 4 가 Brun 외의 논문에 실려 있는데도 그랬다.
같은 논문 Table I 은 프로젝트별 값도 싣는다. 텍스트 충돌 7.6~19.3%, 빌드 실패 2.1~14.7%, 테스트 실패 5.6~35%로 갈린다고 보고했다(그림 3). Brun 외의 9개 프로젝트에서도 텍스트 충돌은 7~42%였다.
이런 범위는 신뢰구간이 아니다. 프로젝트 넷의 값을 최솟값과 최댓값으로 묶은 스프레드일 뿐이다. 여기 없는 다섯 번째가 이 범위에 들어온다는 보장은 없다.
구조 병합이라는 대안, 공짜는 아니다
숫자 이야기는 여기까지다. 어긋남이 줄을 판정축으로 삼아 생긴다면 축을 바꾸면 된다 — 추상 구문 트리(AST)를 비교하는 구조 병합이 그 길이다. 다만 대가가 처리 시간이다 — 같은 43,774건 실험에서 spork 의 누적 시간은 git 의 약 2,118배였다. 정확도도 한 방향으로만 좋아지지 않는다. 덜 시끄러운 도구가 더 안전한 도구는 아니다.
기준선 자체가 깨져 있을 때 — 가상 조상과 rerere
공통 조상이 하나라는 보장도 없다. 두 브랜치가 서로를 몇 번씩 주고받으면 조상 후보가 둘 이상 남는다(criss-cross). 기본 전략 ort 는 그 조상들을 먼저 병합한 가상 조상을 기준선으로 쓴다. 소스 주석은 예비 병합이 실패할 때를 이렇게 적어 둔다 — "When the merge fails, the result contains files with conflict markers. The cleanness flag is ignored (unless indicating an error) …" 에러를 알릴 때가 아니면 깨끗함 여부는 무시한다는 뜻이다(그림 4).
즉 기준선에 <<<<<<< 가 박힌 채 본 병합이 시작될 수 있다. 한 번 푼 충돌을 기억하는 rerere 도 여기서는 소용이 없다. 마커가 실제로 뜬 충돌만 기억하기 때문이다.
"충돌 없음"을 다시 읽는 법
이제 그 초록 문구를 번역할 수 있다. "이 병합은 옳다"가 아니라 "세 스냅샷의 줄이 서로 부딪히지 않았다"이다. 판정 입력은 양 끝과 공통 조상 셋이고, 판정 단위는 줄이며, 그 축 밖의 일은 보고 대상이 아니다. 되돌림이 사라지는 것도, 같은 코드가 두 벌 복사되는 것도, 기준선 안에 충돌 마커가 남는 것도 전부 그 축 밖에서 일어난다.
그렇다고 병합마다 겁을 먹으라는 말은 아니다. 무충돌 병합의 대부분은 실제로 멀쩡하고, 숫자도 그렇게 나왔다. 달라지는 것은 무엇을 어디에 기대느냐다. 초록 문구는 줄 단위 판정까지만 보증하니, 그 밖의 보증은 빌드와 테스트에서 따로 받아내야 한다. 되돌린 변경이 결과에 남았는지 같은 것은 도구가 아니라 사람이 확인할 항목으로 남는다.
숫자를 읽는 법도 같이 남는다. 33%와 9.3%는 분자가 133으로 같고 분모만 다른 두 문장이었다. 전자는 전체 충돌 399건 기준이고 후자는 깨끗 판정 1,428건 기준이다. 덧셈 세 번이면 되는 일이 논문 사이를 건너뛰며 몇 해를 살아남기도 한다.
남는 것은 판정축을 세어 보는 습관 하나다. git 의 판정축은 세 스냅샷과 그 안의 줄이고, 인용된 백분율의 판정축은 그 값이 매달린 분모다. 둘 다 눈으로 셀 수 있다. 세 스냅샷과 줄, 분모까지 세어 보면 초록 문구도 33%도 제 크기로 읽힌다.
더 읽기
- On the Correctness of Software Merge (arXiv:2607.07987) — 43,774개 병합 시나리오로 도구별 무충돌 결과의 정확성을 잰 실측.
- Brun, Holmes, Ernst, Notkin. Proactive Detection of Collaboration Conflicts (ESEC/FSE 2011) — 이 글이 재계산한 Figure 4 의 원표.
- Kasi & Sarma. Cassandra (ICSE 2013) — 프로젝트별 충돌·빌드·테스트 실패 비율(Table I).