Merge를 하고 Commit 히스토리를 훑어보던 중, 대상 브랜치에만 존재하는 이력들을 발견해서,
"히스토리가 다르면 Merge가 실패했어야 했던 것 아닌가?"라는 의문이 들었다.
Merge의 동작 방식
Merge는 '두 브랜치의 작업을 합친다는 커밋(merge commit)'을 만들어 히스토리를 통합하는 기능이다. 그래서 "아 이런 방식으로 두 갈래로 갈라진 커밋 이력을 하나의 흐름으로 정리하는구나" 라고 이해했었다. 근데 대상 브랜치에만 이력들이 +a 로 존재하는데도 Merge가 정상적으로 되는 경우가 있어 무언가 놓치고 있는 것 같아 확인해봤다.
일단 Merge를 '갈라진 브랜치들의 Commit들을 합치는 행위'라고만 하는 것은 단순히 결과를 설명하는 말이었다. 하지만 정확히는 Git Merge가 실제로 하는 일은 현재(대상) 브랜치에 다른 브랜치의 스냅샷(Snapshot)을 비교하여, (두 개발 라인을) 하나의 결과 스냅샷으로 통합하는 작업이다.
Git에서는 '프로젝트 전체 상태'를 '스냅샷(Snapshot)'이라 표현한다. 커밋(Commit) 또한 변경된 내용에 대한 목록?이라기보다, '그 시점의 Snapshot'를 의미한다.
어쨌든 찾아보면 Merge는 '3-way'를 기준으로 통합/결과 스냅샷을 만든다고 정의하고 있다. 3-way라는 건 쉽게 말해 3개의 Snapshot을 의미하는데, 이들을 비교해 최종 결과 스냅샷을 만들어 낸다는 것이다. 각 요소가 무엇인지 정리해 봤다.
| 3-way | 의미 |
| Base | 두 브랜치의 공통 조상(merge-base) 스냅샷 |
| Ours | 현재 브랜치(merge 받는 쪽)의 스냅샷 |
| Theirs | 합치려는 대상 브랜치의 스냅샷 |
Merge가 확인하는 것은 '커밋 히스토리의 동일 여부'가 아니라 base 대비 ours가 어떻게 바뀌었는지, base 대비 theirs가 어떻게 바뀌었는지의 관계다. 나는 왜인지 변경 이력들을 통합하는 것이라 오해하고 있었다.
- 두 브랜치의 공통 조상(merge-base) 를 찾는다.
- 공통 조상(base) ↔ 내 브랜치(ours) ↔ 상대 브랜치(theirs) 이 3개의 스냅샷을 통합
- Merge Commit 생성 및 반영
Merge의 본질은 스냅샷을 통합해 '최종 결과 스냅샷'을 만드는 것이다. 결론은 커밋 히스토리를 맞추는 게 아니라, 각 커밋이 가리키는 스냅샷(파일 내용과 구조)을 비교해 결과를 만드는 것이다. 그래서 최종 결과 파일 내용이 동일해서 추가로 바뀔 게 없다면 Merge는 충돌 없이 정상 종료된다. 이것만 기억하면 된다.
예시 (히스토리가 달라도 Merge가 되는 경우)


특정 기능을 긴급 반영해야 해서 cherry-pick을 했었다. 이때 stage 브랜치에 dev 브랜치보다 커밋 이력이 더 있었는데도, dev → stage Merge가 정상적으로 되는 이유는 다음과 같다.
- stage 브랜치가 dev에서 갈라져 나온 적이 있다면 공통 조상(base)은 존재한다.
- stage 브랜치에 cherry-pick 커밋이 추가되면 히스토리가 dev와 달라지지만, 공통 조상은 여전히 존재하므로 Merge 자체는 가능하다.
- stage에 cherry-pick으로 같은 수정이 이미 반영돼 있으면, 나중에 dev를 merge할 때 그 수정이 다시 들어오더라도 stage의 파일 내용은 '이미 같은 상태' or '이미 반영된 상태'다.
- 이 경우 실제로 바뀔 내용이 없어서 충돌 없이 Merge가 끝날 수 있다.
Merge는 히스토리 개수나 모양을 비교하는 게 아니라, 공통 조상(base) 기준으로 스냅샷 변경 내용을 비교한다.
당연한 말이지만, 반대로 공통 조상(base) 기준으로 같은 부분을 서로 다르게 수정했다면 충돌이 날 수 있다. 이건 Merge가 불가능한 게 아니라, 충돌을 어떻게 정리할지의 문제이다.
'Git' 카테고리의 다른 글
| Git 기본 개념 & 원격 저장소와 Eclipse 연동 및 Commit/push하기 (0) | 2019.08.22 |
|---|