/개발/git push 거부 non-fast-forward 'remote contains work' 해결법
개발git push 거부non-fast-forward

git push 거부 non-fast-forward 'remote contains work' 해결법

git push 시 non-fast-forward와 'remote contains work' 거부 에러를 원인별 분류표로 진단하고, pull --rebase와 force-with-lease로 데이터 손실 없이 해결하는 법, reflog 복구까지 정리했습니다.

git push 거부 non-fast-forward 'remote contains work' 해결법

git push 거부 "remote contains work" non-fast-forward 완벽 해결법

"fetch first"라는 빨간 메시지 앞에서 멈춘 당신에게

작업을 마치고 자신 있게 git push를 눌렀는데 터미널이 빨간 글씨로 이렇게 답합니다.

CODE
! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. ... (fetch first)

먼저 안심하세요. 당신의 커밋은 아직 한 줄도 날아가지 않았습니다. 이 에러는 "거부"이지 "삭제"가 아닙니다. 원격 저장소가 당신의 로컬보다 앞서 있으니, 합치고 나서 다시 보내라는 정중한 경고일 뿐입니다.

여기서 가장 위험한 행동은 검색해서 나온 git push --force를 무작정 복사해 붙이는 것입니다. 그 순간 동료의 커밋이 사라질 수 있습니다. 30초만 투자해서 원인부터 진단합시다.

에러 메시지 한 줄로 원인 구분하기

같은 "rejected"라도 메시지의 디테일이 원인을 알려줍니다. 아래 표로 내 상황을 먼저 찾으세요.

상황실제 출력 메시지원인 한 줄권장 조치
원격이 앞섬! [rejected] main -> main (fetch first) / Updates were rejected because the remote contains work내가 작업하는 동안 누군가 같은 브랜치에 push 함git pull --rebase 후 push
로컬·원격 분기! [rejected] main -> main (non-fast-forward) / failed to push some refs to 'origin'로컬과 원격이 서로 다른 커밋으로 갈라짐fetch → 충돌 해결 → push
누군가 강제푸시함(non-fast-forward) 인데 pull 해도 히스토리가 꼬임동료가 --force로 히스토리를 갈아엎음팀 확인 후 --force-with-lease
태그 충돌! [rejected] v1.2.0 -> v1.2.0 (would clobber existing tag)원격에 이미 같은 이름 태그 존재태그 삭제 후 재생성 또는 --force 태그

대부분의 일상적인 협업 상황은 1~2번입니다. 3번은 신중하게, 4번은 태그 전용 처리가 필요합니다.

안전한 해결 흐름: rebase vs merge

원격을 로컬로 가져와 합치는 방법은 두 가지입니다. 결과 히스토리가 다릅니다.

git pull(merge 방식) — 머지 커밋이 생깁니다.

CODE
*   a1b2c3 Merge branch 'origin/main'   ← 불필요한 머지 커밋
|\
| * 9f8e7d 동료의 커밋 (원격)
* | 4d5c6b 내 커밋 (로컬)
|/
* 0a1b2c 공통 조상

git pull --rebase — 내 커밋을 원격 위로 옮겨 선형으로 만듭니다.

CODE
* 4d5c6b' 내 커밋 (재배치됨)   ← 깔끔한 직선
* 9f8e7d 동료의 커밋
* 0a1b2c 공통 조상
구분mergerebase
히스토리머지 커밋 생김선형 유지
선호 상황협업 흔적을 그대로 남기고 싶을 때깔끔한 히스토리, PR 리뷰가 쉬운 트렁크 기반 개발
주의머지 커밋이 누적됨공유 브랜치에 이미 push한 커밋은 rebase 금지

2025~2026년 들어 트렁크 기반 개발과 PR squash/rebase 머지가 표준으로 자리잡으면서, 개인 브랜치에서는 pull --rebase를 기본으로 두는 팀이 늘었습니다. git config --global pull.rebase true로 기본값을 잡아두면 편합니다.

복붙 명령어 세트

일반 케이스 (90%는 이걸로 끝납니다)

Bash
git fetch origin
git pull --rebase origin main
# 충돌이 나면 파일 수정 후
git add <충돌_해결한_파일>
git rebase --continue
git push origin main

rebase 중 도저히 안 되겠다 싶을 때 (원상복구)

Bash
git rebase --abort   # rebase 시작 전 상태로 안전하게 복귀

안전한 강제 푸시 (정말 필요한 경우만)

Bash
git push --force-with-lease origin main

날아간 것 같을 때 복구 — reflog

Bash
git reflog                    # HEAD가 이동한 모든 기록 확인
# 예: 4d5c6b HEAD@{2}: commit: 살리고 싶은 작업
git reset --hard HEAD@{2}     # 그 시점으로 되돌리기

reflog는 약 90일간 모든 HEAD 이동을 기록합니다. rebase나 reset으로 커밋이 "사라졌다"고 느껴져도 대부분 여기서 되살릴 수 있습니다. 이게 바로 "데이터는 아직 안 날아갔다"의 근거입니다.

--force vs --force-with-lease: 무엇이 다른가

둘의 차이가 팀원 커밋의 생사를 가릅니다.

--force는 무조건 덮어씁니다. 시나리오를 봅시다.

  1. 나와 동료가 같은 시점에서 출발
  2. 동료가 커밋 푸시 → 원격이 앞섬
  3. 내가 원격 변화를 모른 채 git push --force 실행
  4. 동료의 커밋이 원격에서 통째로 사라짐 😱

--force-with-lease는 한 번 더 확인합니다. 내가 마지막으로 fetch한 원격 ref 상태와 현재 원격 상태를 비교해서, 그 사이 누군가 새 커밋을 올렸다면 push를 거부합니다.

CODE
$ git push --force-with-lease
! [rejected] main -> main (stale info)   ← 원격이 바뀌었으니 거부됨

--force-with-lease는 "내가 본 그 상태 그대로일 때만 덮어써라"는 안전장치입니다. 강제 푸시가 불가피하다면 항상 이 옵션을 쓰세요.

⚠️ 경고

  • 공유 브랜치(main/develop)에 --force를 쓰면 팀원 커밋이 영구 삭제될 수 있습니다.
  • 강제 푸시 전에는 반드시 git fetch로 최신 상태를 먼저 확인하세요. --force-with-lease도 fetch 직후에 써야 의미가 있습니다.

실무 한마디

저는 신입 때 충돌이 무서워서 --force로 밀어버렸다가 동료의 반나절 작업을 날린 적이 있습니다. 다행히 동료의 로컬 reflog로 복구했지만, 그 뒤로는 팀에 두 가지를 정착시켰습니다. 첫째, GitHub/GitLab의 protected branch 규칙으로 main에 force push 자체를 차단. 둘째, 모든 force push는 --with-lease만 허용. 이 두 가지만으로 "커밋이 사라졌어요" 사고가 사라졌습니다.

재발 방지 체크리스트

오늘 바로 적용할 행동입니다.

  • git config --global pull.rebase true 로 pull 기본값을 rebase로
  • main/develop브랜치 보호 규칙 활성화 (force push·직접 push 차단)
  • push 전 습관적으로 git fetch 먼저 실행
  • 강제 푸시는 무조건 --force-with-lease만 사용
  • 작업은 개인 브랜치 → PR(squash/rebase 머지) 흐름으로

빨간 에러 메시지는 사고가 아니라 git이 당신을 보호하는 신호입니다. 메시지를 읽고, 분류표로 원인을 찾고, rebase로 합친 뒤, 정말 필요할 때만 --force-with-lease. 이 순서만 지키면 push 거부는 더 이상 무서운 일이 아닙니다.

자주 묻는 질문 (FAQ)

Q. git pull --rebase 도중 충돌이 너무 많이 나는데 그냥 처음으로 돌아가고 싶어요. A. git rebase --abort를 실행하면 rebase 시작 전 상태로 완전히 안전하게 복귀합니다. 데이터 손실이 없으니 부담 없이 사용하세요.

Q. --force-with-lease로 푸시했는데 stale info라며 또 거부됩니다. A. 마지막 fetch 이후 원격에 새 커밋이 올라왔다는 뜻입니다. 의도된 안전장치입니다. git fetch 후 변경 내용을 확인하고, 정말 덮어써도 되는지 판단한 뒤 다시 시도하세요.

Q. reset --hard로 되돌렸는데 필요한 커밋이 사라졌어요. 복구 가능한가요? A. 네. git reflog를 실행해 HEAD 이동 기록에서 원하는 시점(HEAD@{n})을 찾아 git reset --hard HEAD@{n}으로 되살릴 수 있습니다. reflog는 기본 약 90일간 보존됩니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·
관련 공식 문서Git 공식 문서

댓글

첫 번째 댓글을 남겨보세요.