Git 머지 충돌 해결: 개념과 전략

Merge Base, 3-way Merge, 충돌 해소 전략을 체계적으로 이해하기

Git 머지 충돌의 원리(Merge Base, 3-way Merge)를 설명하고, 충돌 해소를 위한 다양한 전략(-X ours/theirs, 파일별 선택, 날짜 기반 비교)을 체계적으로 정리한다. 실전 케이스는 별도 포스트에서 다룬다.

Engineering
Git
DevOps
저자

Kwangmin Kim

공개

2026년 04월 03일

브랜치를 병합할 때 같은 파일의 같은 부분을 두 브랜치가 각각 다르게 수정했다면, Git은 어느 쪽을 선택해야 할지 모른다. 이것이 “충돌(conflict)”이다. 충돌은 무섭지 않다 — Git이 판단을 사람에게 넘긴 것일 뿐이다. 판단 기준(브랜치 우선순위, 날짜)을 정해서 체계적으로 선택하면 된다.

1 브랜치 상태 파악

머지를 시도하기 전에, 두 브랜치가 현재 어떤 상태인지 먼저 확인한다.

git branch -a        # 로컬 + 원격 브랜치 전체 목록
git status           # 현재 브랜치 상태
  • git branch -a : -a 옵션은 로컬 브랜치와 원격 브랜치를 모두 보여준다. 원격 브랜치는 remotes/origin/ 접두사가 붙는다.
  • git status : 현재 브랜치 이름, 스테이징 영역, 작업 디렉토리의 변경 사항을 보여준다.

1.1 브랜치 간 커밋 divergence 확인

두 브랜치가 공통 조상에서 얼마나 갈라졌는지 확인한다.

git fetch origin
git log --oneline origin/feat/chatbot...origin/main --left-right

git fetch origin 을 먼저 실행하는 이유: 원격 저장소의 최신 상태를 로컬에 가져와야 정확한 비교가 가능하다. fetch 없이 비교하면 로컬에 캐시된 오래된 정보를 기준으로 비교하게 된다.

... (triple dot) 의미:

  • 두 브랜치의 공통 조상(merge base) 이후 각 브랜치에만 있는 커밋을 보여준다
  • < 기호: 왼쪽 브랜치에만 있는 커밋
  • > 기호: 오른쪽 브랜치에만 있는 커밋

Example output:

< e504cfd citation improved           <- feat/chatbot에만 있는 커밋
> c968455 Merge branch 'feat/code-analyzer'   <- main에만 있는 커밋

두 브랜치가 같은 출발점에서 각자 다른 방향으로 뻗어나간 상태임을 알 수 있다. <> 의 개수가 많을수록 두 브랜치 사이의 차이가 크고, 충돌 가능성도 높아진다.

2 Merge Base (공통 조상)

git merge-base origin/feat/chatbot origin/main
9ef1d052e8b4407c417f18e19a18edf78e83705c

Merge Base란:

  • 두 브랜치가 갈라지기 시작한 마지막 공통 커밋이다
  • Git은 머지 시 이 merge base를 기준으로 각 브랜치의 변경사항을 3-way merge한다
  • A 브랜치 변경 + B 브랜치 변경 vs 공통 조상 → 충돌 여부 판단

비유로 설명하면, 두 사람이 같은 원본 문서를 복사해서 각자 다른 부분을 수정한 상황이다. 원본 문서가 바로 merge base이다. 원본과 비교해야만 “누가 어디를 바꿨는지” 판단할 수 있다.

3 3-way Merge와 충돌의 원리

Git의 merge는 단순히 두 파일을 비교하는 것이 아니다. 세 개의 버전을 비교한다:

  1. Base (공통 조상 버전) — 두 브랜치가 갈라지기 전 원본
  2. Ours (현재 브랜치 버전) — HEAD가 가리키는 브랜치의 파일
  3. Theirs (상대 브랜치 버전) — 머지하려는 브랜치의 파일

왜 2-way가 아닌 3-way인가? 두 파일만 비교하면 “누가 바꿨는지”를 알 수 없다. 예를 들어 A 파일에는 “hello”가 있고 B 파일에는 “world”가 있다면, 어느 쪽이 원본이고 어느 쪽이 수정본인지 판단할 수 없다. 원본(Base)을 함께 보면 판단이 가능하다.

이 세 버전을 비교해서:

  • Base와 같고 Ours만 다르면 → Ours를 채택 (현재 브랜치만 수정한 것이므로)
  • Base와 같고 Theirs만 다르면 → Theirs를 채택 (상대 브랜치만 수정한 것이므로)
  • Ours와 Theirs가 모두 Base와 다르면 → CONFLICT (Git이 결정할 수 없으므로 사람에게 넘김)
3-way Merge 판단 로직 요약
Base Ours Theirs 결과
A A A 변경 없음 (A 유지)
A B A Ours 채택 (B)
A A C Theirs 채택 (C)
A B C CONFLICT (사람이 결정)
A B B 양쪽이 같은 수정 → B 채택 (충돌 아님)

4 충돌 파일 확인: 머지 시뮬레이션

실제로 머지하기 전에 어떤 파일이 충돌하는지 미리 확인할 수 있다. 이 방법을 사용하면 머지를 커밋하지 않고 충돌 상황만 파악한 뒤 원래 상태로 돌아올 수 있다.

git checkout feat/chatbot
git merge origin/main --no-commit --no-ff

Options:

  • --no-commit : 머지 후 자동 커밋하지 않는다 → 결과를 검토할 수 있다
  • --no-ff : fast-forward 방지 → 항상 머지 커밋을 생성한다. fast-forward란 한쪽 브랜치가 다른 쪽의 직계 조상인 경우 머지 커밋 없이 포인터만 이동하는 것이다. --no-ff 를 쓰면 이를 방지하여 반드시 머지 커밋이 만들어진다

실행 결과:

Auto-merging .gitignore
CONFLICT (content): Merge conflict in .gitignore
Auto-merging data/messages.json
CONFLICT (content): Merge conflict in data/messages.json
Automatic merge failed; fix conflicts and then commit the result.
  • Auto-merging 성공 = 같은 파일이지만 수정 위치가 겹치지 않아 Git이 자동으로 합침
  • CONFLICT (content) = 같은 파일의 같은 부분을 두 브랜치가 각자 다르게 수정 → Git이 어느 쪽을 선택할지 모르는 상태

4.1 충돌 마커

충돌난 파일을 열어보면 이런 형태가 된다:

<<<<<<< HEAD (현재 브랜치의 내용)
현재 브랜치에서 수정한 내용
=======
상대 브랜치에서 수정한 내용
>>>>>>> origin/main
  • <<<<<<< HEAD 부터 ======= 까지: 현재 브랜치(Ours)의 내용이다
  • ======= 부터 >>>>>>> origin/main 까지: 상대 브랜치(Theirs)의 내용이다
  • 이 마커를 수동으로 편집하여 원하는 내용만 남기고 마커를 삭제하면 충돌이 해소된다

4.2 머지 취소

시뮬레이션 후 원래 상태로 돌아가려면:

git merge --abort
Switched to branch 'feat/chatbot'

--abort 실행 후 머지 이전 브랜치 상태로 완전히 복원된다.

  • 충돌 파일들의 <<<<<<<, =======, >>>>>>> 마커 제거
  • 브랜치를 머지 시도 이전 상태로 완전 복원
  • 머지 중에 발생한 모든 변경을 되돌린다

5 충돌 해소 전략

충돌을 해소하는 방법은 상황에 따라 세 가지 전략으로 나뉜다. 어떤 전략을 선택할지는 “한쪽 브랜치가 명확히 우선인가, 아니면 파일마다 다른가”로 결정한다.

5.1 전략 A: -X ours / -X theirs (전체 파일 일괄)

한쪽 브랜치가 명확히 최신이거나 우선인 경우 사용한다. 예를 들어 main 브랜치에 이미 검증된 코드가 있고, feature 브랜치에서 실험적으로 수정한 것이라면 -X ours 로 현재 브랜치를 일괄 우선할 수 있다.

git merge origin/main -X ours -m "Merge: 현재 브랜치 우선"
Merge made by the 'ort' strategy.
 .gitignore | 3 +--
 data/messages.json | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

충돌이 있던 파일들이 자동으로 현재 브랜치 기준으로 해결되어 머지 커밋이 생성된다.

-X ours :

  • -X 는 merge strategy extension option이다
  • ours : 충돌 발생 시 현재 브랜치(HEAD)의 내용을 자동 선택한다
  • 충돌이 없는 파일은 정상적으로 양쪽 변경사항 모두 반영한다
  • 충돌이 있는 파일만 현재 브랜치 기준으로 덮어쓴다

-X theirs 는 반대로 상대 브랜치를 선택한다.

-X ours vs -s ours — 반드시 구분해야 한다
git merge -s ours origin/main    # strategy: 상대 브랜치 변경 전체 무시 (위험!)
git merge -X ours origin/main    # extension: 충돌 부분만 현재 브랜치 선택 (안전)
  • -s ours 는 상대 브랜치의 모든 변경을 무시한다. 충돌이 없는 파일의 변경도 버린다. 실수로 사용하면 상대 브랜치의 작업이 통째로 사라진다.
  • -X ours충돌이 발생한 부분에서만 현재 브랜치를 선택한다. 충돌이 없는 변경은 정상적으로 병합된다.

한 글자 차이(-s vs -X)가 결과를 완전히 바꾸므로 주의해야 한다.

5.2 전략 B: --ours / --theirs (파일별 수동 선택)

파일마다 어느 브랜치가 더 최신인지 다를 때 사용한다. 예를 들어 .gitignore 는 main이 최신이지만, data/messages.json 은 feature 브랜치가 최신인 경우이다.

먼저 --no-commit 으로 머지해서 충돌 상태에 진입한 후, 파일별로 선택한다:

# 충돌 상태 진입
git merge origin/main --no-commit --no-ff

# 현재 브랜치 버전 선택
git checkout --ours data/messages.json
git add data/messages.json

# 상대 브랜치 버전 선택
git checkout --theirs .gitignore
git add .gitignore

git add 를 반드시 해야 하는 이유:

  • git checkout --ours/--theirs 로 파일 내용을 선택하면, 파일은 수정되지만 git 내부적으로는 아직 “충돌 상태”로 표시된다
  • git add 를 해야 “이 파일의 충돌을 해소했다”고 git에게 알리는 것이다
  • git add 없이 git commit 을 시도하면 “unmerged files” 에러가 발생한다

확인:

On branch feat/standardizer
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

이 메시지가 나오면 모든 충돌이 해소된 것이다. git commit 으로 머지를 완료한다.

5.3 전략 C: 날짜 기반 파일별 선택

파일마다 어느 브랜치가 더 최신인지 판단해야 하는데, 파일 수가 많을 때 커밋 날짜로 비교하는 기법이다. 충돌 파일이 10개 이상이면 일일이 내용을 비교하기 어려우므로, 날짜를 기준으로 빠르게 판단할 수 있다.

MERGE_BASE=$(git merge-base origin/feat/standardizer origin/main)

for f in .gitignore data/messages.json poetry.lock pyproject.toml; do
  std_date=$(git log -1 --format="%ai" "$MERGE_BASE..origin/feat/standardizer" -- "$f")
  main_date=$(git log -1 --format="%ai" "$MERGE_BASE..origin/main" -- "$f")
  echo "FILE: $f"
  echo "  standardizer: $std_date"
  echo "  main:         $main_date"
done

핵심: MERGE_BASE..브랜치 범위 지정

  • merge_base..브랜치 = merge base 이후 해당 브랜치에서의 변경만 조회한다
  • merge base 이전 커밋까지 포함하면 잘못된 날짜 비교가 된다
  • “이 브랜치에서 독자적으로 수정한 마지막 날짜”를 정확히 보려면 반드시 범위를 제한해야 한다
  • git log -1 은 가장 최근 커밋 1개만 보여주고, --format="%ai" 는 날짜를 ISO 형식으로 출력한다
  • -- "$f" 는 특정 파일에 대한 커밋만 필터링한다

Example output:

FILE: .gitignore
  standardizer: 2026-03-16 14:18:54 +0900
  main:         2026-03-30 10:07:46 +0900   <- main이 더 최신

FILE: data/messages.json
  standardizer: 2026-03-06 05:53:47 +0000   <- standardizer가 더 최신
  main:         2026-02-23 08:54:39 +0900

날짜 비교 결과에 따라 파일별로 --ours 또는 --theirs 를 선택한다. 위 예시에서는 .gitignore 는 main이 더 최신이므로 --theirs (상대 브랜치)를, data/messages.json 은 standardizer가 더 최신이므로 --ours (현재 브랜치)를 선택하면 된다.

날짜가 비어있는 경우

날짜가 출력되지 않는 파일이 있을 수 있다. 이는 해당 브랜치에서 merge base 이후 그 파일을 한 번도 수정하지 않았다는 뜻이다. 이 경우 상대 브랜치의 버전을 선택하면 된다 — 한쪽만 수정했으므로 수정한 쪽이 최신이다.

6 전체 워크플로우

머지 충돌 해결의 전체 흐름을 정리한다:

[GitHub PR 충돌 발생]
        |
git log A...B --left-right   -> 두 브랜치 차이 파악
        |
git merge-base A B            -> 공통 조상 확인
        |
git merge --no-commit --no-ff -> 충돌 파일 목록 확인
        |
    +---+----+
 한 브랜치가   파일마다 다름
 명확히 최신    (날짜 비교 필요)
    |          |
-X ours 로    git log --format="%ai" MERGE_BASE..브랜치 -- 파일
한 번에 해결   로 날짜 비교 후
              파일별 --ours / --theirs 선택
              + git add
    +---+----+
        |
  git commit
        |
  git push
        |
  GitHub PR 충돌 해소 확인

7 핵심 명령어 레퍼런스

명령어/개념 설명
git merge-base A B 두 브랜치의 공통 조상 커밋 해시를 출력한다
git log A...B --left-right 두 브랜치 간 diverge된 커밋을 비교한다
git merge --no-commit --no-ff 머지 시뮬레이션 (충돌 파일 확인용)
git merge --abort 진행 중인 머지를 전체 취소한다
git merge -X ours 충돌 시 현재 브랜치를 자동 선택한다
git merge -X theirs 충돌 시 상대 브랜치를 자동 선택한다
git checkout --ours <file> 특정 파일만 현재 브랜치 버전으로 선택한다
git checkout --theirs <file> 특정 파일만 상대 브랜치 버전으로 선택한다
git log -1 --format="%ai" A..B -- <file> 특정 파일의 브랜치 내 최신 커밋 날짜를 조회한다

8 관련 주제

Subscribe

Enjoy this blog? Get notified of new posts by email: