git push 거부 "remote contains work" non-fast-forward 완벽 해결법
"fetch first"라는 빨간 메시지 앞에서 멈춘 당신에게
작업을 마치고 자신 있게 git push를 눌렀는데 터미널이 빨간 글씨로 이렇게 답합니다.
! [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 방식) — 머지 커밋이 생깁니다.
* a1b2c3 Merge branch 'origin/main' ← 불필요한 머지 커밋
|\
| * 9f8e7d 동료의 커밋 (원격)
* | 4d5c6b 내 커밋 (로컬)
|/
* 0a1b2c 공통 조상git pull --rebase — 내 커밋을 원격 위로 옮겨 선형으로 만듭니다.
* 4d5c6b' 내 커밋 (재배치됨) ← 깔끔한 직선
* 9f8e7d 동료의 커밋
* 0a1b2c 공통 조상| 구분 | merge | rebase |
|---|---|---|
| 히스토리 | 머지 커밋 생김 | 선형 유지 |
| 선호 상황 | 협업 흔적을 그대로 남기고 싶을 때 | 깔끔한 히스토리, PR 리뷰가 쉬운 트렁크 기반 개발 |
| 주의 | 머지 커밋이 누적됨 | 공유 브랜치에 이미 push한 커밋은 rebase 금지 |
2025~2026년 들어 트렁크 기반 개발과 PR squash/rebase 머지가 표준으로 자리잡으면서, 개인 브랜치에서는 pull --rebase를 기본으로 두는 팀이 늘었습니다. git config --global pull.rebase true로 기본값을 잡아두면 편합니다.
복붙 명령어 세트
일반 케이스 (90%는 이걸로 끝납니다)
git fetch origin
git pull --rebase origin main
# 충돌이 나면 파일 수정 후
git add <충돌_해결한_파일>
git rebase --continue
git push origin mainrebase 중 도저히 안 되겠다 싶을 때 (원상복구)
git rebase --abort # rebase 시작 전 상태로 안전하게 복귀안전한 강제 푸시 (정말 필요한 경우만)
git push --force-with-lease origin main날아간 것 같을 때 복구 — reflog
git reflog # HEAD가 이동한 모든 기록 확인
# 예: 4d5c6b HEAD@{2}: commit: 살리고 싶은 작업
git reset --hard HEAD@{2} # 그 시점으로 되돌리기reflog는 약 90일간 모든 HEAD 이동을 기록합니다. rebase나 reset으로 커밋이 "사라졌다"고 느껴져도 대부분 여기서 되살릴 수 있습니다. 이게 바로 "데이터는 아직 안 날아갔다"의 근거입니다.
--force vs --force-with-lease: 무엇이 다른가
둘의 차이가 팀원 커밋의 생사를 가릅니다.
--force는 무조건 덮어씁니다. 시나리오를 봅시다.
- 나와 동료가 같은 시점에서 출발
- 동료가 커밋 푸시 → 원격이 앞섬
- 내가 원격 변화를 모른 채
git push --force실행 - 동료의 커밋이 원격에서 통째로 사라짐 😱
--force-with-lease는 한 번 더 확인합니다. 내가 마지막으로 fetch한 원격 ref 상태와 현재 원격 상태를 비교해서, 그 사이 누군가 새 커밋을 올렸다면 push를 거부합니다.
$ 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일간 보존됩니다.
이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
댓글
첫 번째 댓글을 남겨보세요.