포스트

브랜치 통합 함정 — 로컬 ref ≠ 원격 ref, orphan main과 non-ff

로컬 ref만 보고 ff로 판단했다가 원격에서 non-fast-forward로 거부되는 orphan 브랜치 함정과 해법.

브랜치 통합 함정 — 로컬 ref ≠ 원격 ref, orphan main과 non-ff

main·master가 뒤섞인 repo를 하나로 정리할 때, 로컬 ref만 보고 “선형이니 ff로 끝”이라 판단하면 원격에서 non-fast-forward로 거부될 수 있다. 로컬 mainorigin/main이 같은 이름·같은 커밋 메시지여도 실체(히스토리)가 다를 수 있기 때문. 파괴적 작업 전엔 반드시 git fetch원격 ref 기준으로 merge-base를 확인한다.

선형성/무관성 검증 순서

1
2
3
4
5
git fetch origin
# A에 있고 B에 없는 커밋
git log --oneline B..origin/A
# 공통 조상 — 비어 있으면 "완전 무관한 히스토리"(unrelated)
git merge-base origin/A B
  • merge-base비어 있음 → 두 브랜치는 공통 뿌리가 없는 orphan/unrelated 관계. ff 불가, merge도 --allow-unrelated-histories 필요.
  • merge-base == 한쪽 tip → 그쪽이 다른쪽의 순수 조상 → ff 가능(충돌·머지커밋 0).

실제로 밟은 함정

  • GitHub repo에 버려진 초기 main(자동 생성된 Initial commit 3개짜리)이 진짜 작업 라인(master 계열 450커밋)과 뿌리부터 무관하게 존재.
  • 로컬 main(진짜 라인의 조상)을 작업본으로 ff → git push origin mainnon-fast-forward로 거부. 원인은 origin/main이 그 고아 브랜치였기 때문.
  • 무관한 두 히스토리는 ff가 원천적으로 불가 → 교체하려면 원격을 --force 하거나 삭제 후 재push.
1
2
3
# 고아 원격 브랜치를 실제 작업본으로 교체 (force 대신 삭제+push가 의도 명확)
git push origin --delete main
git push origin main            # 로컬 main(진짜 작업본)으로 새로 생성

default 브랜치 삭제 순서

원격 기본 브랜치(default branch)는 삭제 불가. mastermain 전환 시 순서를 지켜야 한다:

1
2
3
git push origin main                     # 1. 새 default 후보를 먼저 push
gh repo edit --default-branch main       # 2. GitHub default 전환
git push origin --delete master          # 3. 그 다음에야 구 default 삭제

교훈

  • 로컬 브랜치 그래프(git log --all --graph)는 원격 실체를 반영 안 할 수 있다. 통합·삭제 전 git fetch 필수.
  • non-fast-forward 거부는 대개 “원격이 앞서 있다”지만, 공통 조상이 아예 없는 orphan일 때도 뜬다 — merge-base로 구분.
  • 파괴적 push 실패는 오히려 안전장치다. 강제로 밀지 말고 원격 상태부터 조사.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.