GitHub 새 저장소에 첫 push를 했더니 rejected와 unrelated histories가 연달아 난 이유
문제 발생
로컬에서 커밋까지 해 둔 사이드 프로젝트를 GitHub에 올리려고 새 저장소를 만들었습니다. 만들 때 "Add a README file"에 체크한 게 전부였는데, 첫 push까지 에러를 세 번 만났습니다.
GitHub 안내대로 git push -u origin main을 쳤더니 처음엔 이렇게 나왔습니다.
error: src refspec main does not match any
error: failed to push some refs to 'github.com:me/my-app.git'브랜치 이름을 main으로 바꾸고 다시 push하자 이번엔 거절당했습니다.
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:me/my-app.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.시키는 대로 git pull을 하니 방식을 정하라는 안내가 나와서 git pull --no-rebase origin main으로 다시 했고, 마지막으로 이게 떴습니다.
fatal: refusing to merge unrelated histories혼자 쓰는 빈 저장소에 처음 올리는 건데 남의 작업과 충돌한다는 말이 영 이해가 안 됐습니다. 이 글의 메시지는 로컬 bare 저장소를 GitHub 대신 원격으로 두고 Git 2.43에서 같은 순서로 재현한 것이고, 주소만 바꿔 적었습니다.
원인 분석
첫 번째 에러는 로컬에 main 브랜치가 없어서 났습니다. git init 문서에 따르면 브랜치 이름을 따로 주지 않으면 기본 이름을 쓰는데, 지금 그 기본값은 master이고 Git 3.0이 나오면 main으로 바뀔 예정이라고 적혀 있습니다. 실제로 git init을 하니 Using 'master' as the name for the initial branch라는 안내와 함께 master가 만들어졌습니다. 반면 GitHub 안내는 main 기준이라 git push -u origin main을 그대로 따라 치면, 보낼 main이 로컬에 없다는 뜻의 src refspec main does not match any가 납니다.
두 번째 에러는 원격에 내가 모르는 커밋이 있어서 났습니다. README에 체크하는 순간 GitHub가 저장소에 Initial commit을 하나 만듭니다. 로컬에는 이 커밋이 없으니, 내 main으로 원격 main을 덮으면 그 커밋이 사라집니다. git push 문서의 fast-forward 설명이 이 경우입니다. 브랜치를 A에서 B로 바꾸는 게 fast-forward가 되려면 B가 A의 후손이어야 하고, 그렇지 않은 업데이트는 이력을 잃기 때문에 기본으로 허용하지 않는다고요. 참고로 같은 상황에서 git fetch를 먼저 해 두면 메시지가 (non-fast-forward)로 바뀝니다. 재현해 보니 원격 커밋을 아직 받아 오지 않았을 때는 fetch first, 받아 온 뒤에는 non-fast-forward였습니다. 표현만 다를 뿐 같은 이유입니다.
세 번째 에러는 두 이력에 공통 조상이 아예 없어서 났습니다. 보통의 충돌은 같은 커밋에서 갈라진 두 브랜치 사이에서 생깁니다. 이번엔 로컬의 first commit과 GitHub의 Initial commit이 서로 아무 관계 없는 두 개의 시작점이었습니다. git merge-base HEAD origin/main을 돌려 보니 아무것도 출력하지 않고 종료 코드 1로 끝났습니다. 공통 조상이 없다는 뜻입니다. git merge 문서에 따르면 Git은 기본적으로 공통 조상이 없는 이력의 병합을 거부합니다. 이걸 넘는 옵션이 --allow-unrelated-histories인데, 문서는 따로 시작한 두 프로젝트를 합치는 아주 드문 경우를 위한 것이라 기본으로 켜는 설정은 없고 앞으로도 추가하지 않을 거라고까지 적어 둡니다.
해결 방안
-
다음부터는 GitHub 저장소를 빈 상태로 만듭니다. 세 에러 중 두 개가 README 체크 하나에서 시작됐습니다. GitHub 문서도 이미 있는 프로젝트를 올릴 때는 에러를 피하려면 README, 라이선스, gitignore 파일로 새 저장소를 초기화하지 말고, 프로젝트를 올린 뒤에 추가하라고 안내합니다. 브랜치 이름도 처음부터 맞춰 둡니다.
git init -b main으로 만들거나,git config --global init.defaultBranch main을 한 번 해 두면 이후 새 저장소는 모두main으로 시작합니다. 이미master로 만든 저장소는git branch -M main으로 이름을 바꾸면 됩니다. -
이미 README를 만들었다면
--allow-unrelated-histories로 한 번만 합칩니다. 지금 상황은 문서가 말하는 "따로 시작한 두 이력을 합치는" 경우가 맞으니 이 옵션을 쓸 자리입니다.
git pull --no-rebase --allow-unrelated-histories origin main로컬에도 README.md가 있었다면 여기서 한 번 더 멈춥니다. 재현에서는 CONFLICT (add/add): Merge conflict in README.md가 났습니다. 양쪽이 같은 이름의 파일을 각자 새로 만들었기 때문입니다. 남길 내용으로 파일을 정리한 뒤 git add README.md, git commit을 하면 병합 커밋이 생기고, 그다음 push는 fast-forward라 통과합니다. 이력을 그려 보면 시작점이 두 개인 모양이 됩니다.
* 3fd0150 Merge branch 'main' of github.com:me/my-app
|\
| * cca7c4b Initial commit
* 5ffc33a first commit-
이력을 한 줄로 두고 싶다면
--rebase로 받아도 됩니다.git pull --rebase origin main은 내 커밋을 GitHub의Initial commit위로 옮겨 붙입니다. 재현해 보니 rebase 쪽은--allow-unrelated-histories없이도 바로 성공했고, 결과는Initial commit다음에first commit이 오는 한 줄짜리 이력이었습니다. 이렇게 만든 브랜치도 push가 그대로 통과했습니다. 양쪽에 같은 파일이 있으면 rebase 중에도 충돌은 날 수 있으니 그때는 똑같이 정리하고git rebase --continue로 이어 갑니다. 어떤 방식으로 받을지 매번 묻는 안내는 예전 글 "git pull이 갑자기 거부하며 방식을 정하라고 한 이유"에 정리해 두었습니다. -
원격의 README가 정말 필요 없다면
--force-with-lease로 덮어씁니다. 방금 만든 저장소이고 아무도 아직 쓰지 않았다면 GitHub가 만든 커밋을 버려도 됩니다. 이때도--force보다--force-with-lease가 안전한데, 순서가 하나 있습니다. 재현에서git fetch없이 바로git push --force-with-lease origin main을 하니! [rejected] main -> main (stale info)로 거절됐습니다.git push문서에 따르면 이 옵션은 원격 브랜치의 현재 값이 내가 가진 원격 추적 브랜치와 같을 때만 덮어쓰게 하는데, 아직 받아 온 적이 없으니 비교할 기준이 없었던 겁니다.git fetch origin을 먼저 하고 다시 실행하자(forced update)로 원격이 내 커밋 하나로 바뀌었습니다. 이 옵션이 왜 필요한지는 "git push --force로 동료 커밋을 날린 뒤 --force-with-lease를 쓰게 된 이유"에 자세히 적어 두었습니다. -
첫 push가 아닌데 unrelated histories가 나면 옵션부터 붙이지 않습니다. 문서 표현대로 이 옵션은 아주 드문 경우를 위한 것입니다. 오래 쓰던 프로젝트에서 이 에러를 만났다면 대개 원격 주소를 잘못 넣었거나, 누가 원격 저장소를 지우고 새로 만든 경우입니다. 그 상태에서 옵션을 붙이면 전혀 다른 프로젝트의 이력이 내 브랜치에 섞여 들어옵니다.
git remote -v로 주소가 맞는지,git log --oneline origin/main으로 원격에 내가 아는 커밋이 있는지부터 확인합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.