coredot.today
Git, 이것만 알면 협업한다 3편 — GitHub와 주고받기: push·pull·clone
블로그로 돌아가기
GitGit 입문버전 관리GitHub협업Claude Codegit pushgit pullgit clone원격 저장소GitHub CLI

Git, 이것만 알면 협업한다 3편 — GitHub와 주고받기: push·pull·clone

내 컴퓨터에만 있던 커밋을 GitHub라는 '클라우드 금고'에 올리고(push), 동료의 커밋을 받아 오는(pull) 법을 배웁니다. 빈 저장소 만들기, clone, 2026년식 인증(gh auth login), 하루 루틴, 그리고 누구나 한 번은 만나는 push 거절(rejected)을 실제 출력으로 따라가며 풀어 봅니다.

코어닷투데이2026-10-0177분

클라우드 금고를 사이에 두고 커밋을 주고받는 민지와 도윤크게 보기

지난 2편까지 민지는 자기 컴퓨터 안에서 cafe-menu 프로젝트에 세이브 포인트(커밋)를 차곡차곡 쌓았습니다. 그런데 이 기록은 아직 민지의 노트북 안에만 있습니다. 노트북을 잃어버리면 기록도 함께 사라지고, 함께 일하는 도윤은 민지가 무엇을 바꿨는지 볼 방법이 없습니다.

이번 3편에서는 그 기록을 인터넷 위의 공용 저장소로 올리고, 동료가 올린 기록을 받아 오는 법을 배웁니다. 명령은 딱 네 개가 중심입니다. clone(처음 한 번 통째로 받기), push(올리기), pull(받아서 합치기), 그리고 fetch(받아 오기만 하기). 여기에 누구나 한 번은 만나는 빨간 글씨, push 거절(rejected)을 실제 출력 그대로 재현해 보고 푸는 법까지 익힙니다.

🧪
이 글의 출력 예시에 대해 — 모든 터미널 출력은 git 2.55에서 직접 실행해 얻은 것입니다. 실제 GitHub 대신 내 컴퓨터 안에 git init --bare로 만든 "가짜 원격 저장소" 하나와, 민지·도윤 두 사람 몫의 복제본을 만들어 재현했고, 주소만 https://github.com/minji/cafe-menu.git로 바꿔 적었습니다. 실제 GitHub에 push하면 Enumerating objects…, Writing objects… 같은 진행 상황 줄이 몇 줄 더 나오는데, 뜻은 "보내는 중"이라 여기서는 생략했습니다. 터미널이 한국어로 설정되어 있으면 같은 내용이 한국어로 나옵니다.

1. 원격 저장소 = 함께 쓰는 클라우드 금고

1편에서 본 네 공간을 다시 떠올려 봅시다. 작업 폴더 → 스테이징 → 로컬 저장소까지는 모두 내 컴퓨터 안이었습니다. 네 번째 공간인 원격 저장소(remote repository)만 인터넷 저편에 있습니다.

원격 저장소를 클라우드 금고라고 생각하면 쉽습니다.

  • 금고에는 커밋(세이브 포인트)만 들어갑니다. 저장만 하고 커밋하지 않은 파일, git add만 해 둔 파일은 금고로 가지 않습니다.
  • 금고 열쇠는 여러 사람이 나눠 가질 수 있습니다. 민지와 도윤이 같은 금고를 쓰면, 서로의 커밋을 주고받을 수 있습니다.
  • 금고에 넣는 동작이 push, 금고에서 꺼내 내 것과 합치는 동작이 pull입니다.
  • 금고는 자동으로 동기화되지 않습니다. Dropbox나 iCloud처럼 저절로 맞춰지는 게 아니라, 내가 push·pull을 눌러야만 주고받습니다. 이 점이 처음엔 불편해 보이지만, 덕분에 "언제 무엇을 공개할지"를 내가 정할 수 있습니다.

클라우드 금고에 커밋 상자를 넣고 꺼내는 두 사람크게 보기

민지의 컴퓨터
로컬 저장소 (main)
push →
← pull
원격 저장소 (origin)
GitHub의 cafe-menu
← push
pull →
도윤의 컴퓨터
로컬 저장소 (main)
민지와 도윤은 서로 직접 주고받지 않습니다. 언제나 금고를 거칩니다.

이 금고를 빌려주는 서비스가 여럿 있는데, 가장 널리 쓰이는 곳이 GitHub입니다(그 밖에 GitLab, Bitbucket 등이 있고 사용법은 거의 같습니다). 이 글은 GitHub를 기준으로 설명합니다.

"origin"이라는 이름

원격 저장소는 긴 주소(https://github.com/minji/cafe-menu.git)를 갖고 있습니다. 매번 이 주소를 치기는 번거로우니, git은 주소에 짧은 별명을 붙여 둡니다. 관례적으로 첫 번째 원격 저장소의 별명은 origin(원본)입니다. 특별한 기능이 있는 이름은 아니고, 모두가 그렇게 부르기로 한 약속일 뿐입니다. 앞으로 origin이 보이면 "그 금고"라고 읽으면 됩니다.

2. GitHub에 빈 저장소 만들기

금고부터 만들어 봅시다. GitHub 계정이 없다면 github.com에서 먼저 가입합니다(무료 계정으로 비공개 저장소도 만들 수 있습니다).

1
GitHub에 로그인한 뒤, 화면 오른쪽 위의 + 버튼을 누르고 New repository를 고릅니다.
2
Repository name에 cafe-menu를 적습니다. 설명(Description)은 비워도 됩니다.
3
공개 범위를 고릅니다. Public은 누구나 볼 수 있고, Private은 나와 초대한 사람만 봅니다. 연습이라면 Private을 권합니다.
4
README, .gitignore, 라이선스 추가 옵션은 모두 끈 채로 둡니다. 이미 내 컴퓨터에 커밋이 있는 폴더를 올릴 거라면 이 단계가 중요합니다(아래 경고 참고).
5
Create repository를 누르면 빈 금고가 생기고, 그 주소(https://github.com/아이디/cafe-menu.git)와 함께 "다음에 칠 명령" 안내가 화면에 나옵니다.
⚠️
README를 체크하면 생기는 일 — GitHub에서 README를 추가하면 금고 안에 "Initial commit"이라는 첫 커밋이 생깁니다. 그런데 민지의 컴퓨터에도 따로 시작한 첫 커밋이 있으니, 뿌리가 다른 두 역사가 만나게 됩니다. 그러면 첫 push가 거절되고, pull을 하면 fatal: refusing to merge unrelated histories라는 낯선 에러가 나옵니다. 기존 폴더를 올릴 땐 완전히 빈 저장소를 만드는 것이 가장 깔끔합니다. (이미 체크했다면 FAQ에 해결법이 있습니다.)

GitHub 화면의 버튼 위치나 문구는 가끔 바뀝니다. 위 설명과 조금 달라도 "새 저장소 → 이름 → 공개 범위 → 만들기"라는 뼈대는 같습니다.

터미널을 좋아한다면: 뒤에서 설치할 GitHub CLI(gh)로 로그인한 뒤, 프로젝트 폴더 안에서 gh repo create cafe-menu --private --source=. --push를 치면 저장소 만들기·원격 등록·첫 push를 한 번에 해 줍니다. 다만 처음 배울 때는 아래 과정을 한 단계씩 직접 해 보는 편이 무엇이 일어나는지 이해하기 좋습니다.

3. 금고 열쇠: 2026년의 인증 방법

GitHub는 아무나 내 금고에 커밋을 넣게 두지 않습니다. push(비공개 저장소라면 clone·pull도)를 하려면 "이 사람이 정말 민지인가"를 증명해야 합니다. 여기서 초보자가 가장 많이 막힙니다. 먼저 꼭 알아야 할 사실 하나.

🔑
GitHub 계정 비밀번호로는 push할 수 없습니다. GitHub는 2021년 8월 13일부터 git 작업에서 비밀번호 인증을 받지 않습니다. 터미널이 Username과 Password를 물을 때 평소 로그인 비밀번호를 넣으면 인증 실패가 납니다. 옛날 블로그 글에서 "비밀번호를 입력하세요"라고 했다면 그 부분은 더 이상 맞지 않습니다.

대신 쓸 수 있는 방법은 세 가지입니다.

방법어떻게난이도추천 대상
① GitHub CLI 로그인
gh auth login
브라우저로 한 번 로그인하면, git이 쓸 인증을 gh가 대신 관리가장 쉬움처음 시작하는 대부분의 사람
② HTTPS + 개인 액세스 토큰GitHub 설정에서 토큰(긴 문자열)을 만들어 비밀번호 칸에 붙여 넣기보통gh를 설치할 수 없는 환경
③ SSH 키내 컴퓨터에 열쇠 쌍을 만들고 공개 열쇠를 GitHub에 등록조금 어려움여러 서버를 오가는 개발자

① 가장 쉬운 길: gh auth login

GitHub CLI(gh)는 GitHub가 만든 공식 명령줄 도구입니다. 먼저 설치합니다.

bash
# macOS (Homebrew)
brew install gh
bash
# Windows (winget)
winget install --id GitHub.cli

설치가 끝나면 로그인합니다.

bash
gh auth login

몇 가지 질문이 차례로 나옵니다. 처음이라면 이렇게 고르면 됩니다.

  • 어디에 로그인할지 → GitHub.com
  • git 작업에 쓸 방식 → HTTPS
  • git이 GitHub 인증을 쓰게 할지 → Yes
  • 로그인 방법 → 웹 브라우저로 로그인. 화면에 뜬 일회용 코드를 브라우저에 입력하고 승인하면 끝납니다.

gh auth login --help에 따르면 이렇게 받은 인증 토큰은 운영체제의 안전한 저장소(macOS 키체인 등)에 보관됩니다. 잘 되었는지는 다음 명령으로 확인합니다.

bash
gh auth status

이제부터 git push·git pull을 할 때 비밀번호를 묻지 않습니다. 혹시 로그인은 했는데 git이 여전히 Username을 묻는다면, gh를 git의 인증 도우미로 등록하는 명령을 한 번 실행하세요.

bash
gh auth setup-git

② HTTPS + 개인 액세스 토큰(PAT)

개인 액세스 토큰(Personal Access Token, PAT)은 비밀번호 대신 쓰는 긴 문자열입니다. 비밀번호와 다른 점은 권한과 만료일을 좁게 정할 수 있다는 것입니다. GitHub 웹에서 오른쪽 위 프로필 → Settings → Developer settings → Personal access tokens로 가서 Fine-grained token을 만들고, 이 저장소에 대한 Contents 읽기·쓰기 권한만 주는 식입니다.

push할 때 터미널이 Username과 Password를 물으면, Username에는 GitHub 아이디를, Password 칸에 토큰을 붙여 넣습니다. 토큰은 만들 때 한 번만 보여 주므로 안전한 곳에 적어 두세요. 그리고 토큰은 비밀번호와 똑같이 취급해야 합니다. 채팅방이나 코드 파일에 붙여 넣지 마세요.

③ SSH 키 (짧게)

SSH는 "내 컴퓨터에만 있는 비밀 열쇠"와 "GitHub에 맡겨 둔 자물쇠(공개 열쇠)"를 맞춰 보는 방식입니다. 한 번 설정하면 편하지만 처음엔 단계가 많습니다. 쉽게 하려면 gh auth login에서 git 작업 방식을 SSH로 고르면 됩니다. gh가 기존 SSH 키를 찾아 올려 주거나, 없으면 새로 만들지 물어봅니다. SSH를 쓰면 원격 주소가 https://github.com/… 대신 git@github.com:minji/cafe-menu.git 모양이 됩니다.

💡
어느 것을 고를지 모르겠다면 — 그냥 ①을 고르세요. HTTPS 주소 + gh auth login 조합이 설정할 것이 가장 적고, 나중에 SSH로 바꾸고 싶어지면 그때 바꿔도 늦지 않습니다.

4. 출발점 A — 내 컴퓨터의 프로젝트를 금고에 올리기

GitHub와 연결하는 출발점은 두 가지입니다.

상황쓰는 명령이 글의 인물
A. 내 컴퓨터에 이미 저장소(커밋)가 있고, 빈 금고에 처음 올린다git remote add → git push -u민지
B. 금고에 이미 프로젝트가 있고, 내 컴퓨터로 통째로 받아 온다git clone도윤

민지는 1·2편에서 만든 cafe-menu 폴더가 있으니 A입니다. 폴더 안에서 시작합니다. 지금 연결된 원격 저장소가 있는지부터 봅시다.

bash
git remote -v

아무것도 나오지 않습니다. git init으로 만든 저장소는 원격이 비어 있기 때문입니다. 이 상태에서 무작정 push를 하면 git이 "어디로 보내라는 거죠?"라고 되묻습니다.

text
$ git push
fatal: No configured push destination.
Either specify the URL from the command-line or configure a remote repository using

    git remote add <name> <url>

and then push using the remote name

    git push <name>

(출력 아래쪽의 remote group 안내는 줄였습니다.) 안내대로 금고 주소를 origin이라는 별명으로 등록합니다. 주소는 2단계에서 GitHub가 보여 준 것을 복사해 쓰세요.

bash
git remote add origin https://github.com/minji/cafe-menu.git

등록됐는지 다시 확인합니다.

bash
git remote -v
text
origin	https://github.com/minji/cafe-menu.git (fetch)
origin	https://github.com/minji/cafe-menu.git (push)

같은 주소가 두 줄 나옵니다. fetch(받아 올 때 쓰는 주소)와 push(올릴 때 쓰는 주소)입니다. 보통은 같습니다. 주소를 잘못 넣었다면 git remote set-url origin <새 주소>로 고칠 수 있습니다.

첫 push: git push -u origin main

드디어 올립니다.

bash
git push -u origin main
text
To https://github.com/minji/cafe-menu.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

한 줄씩 읽어 봅시다.

  • To https://github.com/minji/cafe-menu.git — 이 금고로 보냈습니다.
  • * [new branch] main -> main — 내 main 브랜치를 금고에 새 브랜치 main으로 만들었습니다. 금고가 비어 있었으니 "new branch"입니다.
  • branch 'main' set up to track 'origin/main'. — 이게 -u가 한 일입니다. 아래에서 자세히 봅니다.

이제 GitHub 웹에서 저장소 페이지를 새로고침하면 index.html, menu.md, README.md와 커밋 기록이 보입니다. 민지의 세이브 포인트가 처음으로 컴퓨터 밖으로 나간 순간입니다.

-u와 upstream: "짝꿍 브랜치" 정해 두기

-u는 --set-upstream의 줄임말입니다. upstream(업스트림)은 "내 브랜치가 평소에 주고받는 원격 쪽 짝꿍 브랜치"라는 뜻입니다. git push -u origin main은 "origin의 main으로 보내고, 앞으로 내 main의 짝꿍은 origin/main이라고 기억해 둬"라는 명령입니다.

짝꿍을 한 번 정해 두면 두 가지가 편해집니다.

  1. 다음부터는 git push, git pull만 쳐도 됩니다. 어디로 보낼지 git이 압니다.
  2. git status가 "금고와 비교해 내가 앞서 있는지 뒤처져 있는지"를 알려 줍니다.
bash
git status
text
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

Your branch is up to date with 'origin/main' — 내 main과 짝꿍 origin/main이 같은 상태라는 뜻입니다. 짝꿍 관계는 이 명령으로도 볼 수 있습니다.

bash
git branch -vv
text
* main b901b3a [origin/main] README에 영업시간 추가

대괄호 안의 [origin/main]이 짝꿍입니다. 만약 -u 없이 브랜치를 만들고 그냥 git push를 치면 이런 안내가 나옵니다.

text
$ git push
fatal: The current branch main has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin main

To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.

겁먹을 필요 없습니다. git이 알려 준 명령(git push --set-upstream origin main, 줄여서 git push -u origin main)을 그대로 치면 됩니다. -u는 브랜치마다 처음 한 번만 붙이면 됩니다.

🧩
origin/main은 무엇인가요? — 내 컴퓨터가 기억하는 "마지막으로 확인했을 때 금고의 main은 여기였다"는 메모입니다. 금고 그 자체가 아니라, 내 컴퓨터 안의 사본 표시입니다. 이 메모는 push·fetch·pull을 할 때만 새로 고쳐집니다. 이 차이가 6장에서 중요해집니다.

5. 출발점 B — git clone으로 통째로 받아 오기

이제 도윤 차례입니다. 금고에는 민지가 올린 프로젝트가 있고, 도윤의 컴퓨터에는 아무것도 없습니다. (비공개 저장소라면 민지가 GitHub 저장소 설정에서 도윤을 협업자(Collaborator)로 초대해야 도윤이 받고 올릴 수 있습니다.)

도윤은 프로젝트를 둘 상위 폴더에서 clone을 합니다. clone(복제)은 금고의 모든 커밋 기록과 파일을 통째로 내려받아 새 폴더를 만들어 주는 명령입니다.

bash
git clone https://github.com/minji/cafe-menu.git
text
Cloning into 'cafe-menu'...
done.

cafe-menu 폴더가 생겼습니다. 안으로 들어가 봅시다.

bash
cd cafe-menu
git remote -v
text
origin	https://github.com/minji/cafe-menu.git (fetch)
origin	https://github.com/minji/cafe-menu.git (push)

git remote add를 하지 않았는데도 origin이 이미 등록되어 있습니다. clone은 받아 온 곳을 자동으로 origin이라고 기억합니다. 기록도 모두 받아졌습니다.

bash
git log --oneline
text
b901b3a README에 영업시간 추가
a223993 카페 웹사이트 첫 버전
bash
git branch -vv
text
* main b901b3a [origin/main] README에 영업시간 추가

짝꿍(upstream)까지 자동으로 설정되어 있습니다. 그래서 clone으로 시작한 사람은 -u를 붙일 필요 없이 처음부터 git push, git pull만 쓰면 됩니다.

비교A. remote add + push -uB. clone
시작할 때 내 컴퓨터이미 커밋이 있는 폴더아무것도 없음
시작할 때 금고비어 있음이미 프로젝트가 있음
origin 등록직접 git remote add자동
upstream(짝꿍) 설정첫 push에 -u자동
몇 번 하나프로젝트당 한 번컴퓨터당 한 번

빈 금고를 clone하면? GitHub에서 방금 만든 빈 저장소를 clone해도 됩니다. 이때는 warning: You appear to have cloned an empty repository.라는 경고가 나오는데, "비어 있는 걸 받았어요"라는 알림일 뿐 에러가 아닙니다. 그 폴더에서 파일을 만들고 커밋한 뒤 push하면 됩니다. 새 프로젝트라면 이렇게 "빈 저장소 만들기 → clone → 작업"으로 시작하는 쪽이 오히려 헷갈릴 일이 적습니다.

원격(remote) 관리 명령 모음

git remote는 금고 주소록을 다루는 명령입니다. 주소록의 각 줄은 "별명 → 주소" 한 쌍이고, 지금까지 쓴 origin도 그중 하나일 뿐입니다. 한 저장소에 원격을 여러 개 등록할 수도 있습니다(6편에서 오픈소스에 기여할 때 upstream이라는 두 번째 원격을 씁니다). 자주 쓰는 명령을 한 번에 정리합니다. 아래 출력은 모두 실제로 실행한 결과입니다.

① 목록 보기 — 별명만 보려면 git remote, 주소까지 보려면 -v를 붙입니다.

bash
git remote
git remote -v
text
$ git remote
origin
$ git remote -v
origin	https://github.com/minji/cafe-menu.git (fetch)
origin	https://github.com/minji/cafe-menu.git (push)

② 추가하기 — git remote add <별명> <주소>. 이미 있는 별명으로 또 추가하면 거절됩니다. clone한 폴더에서 git remote add origin ...을 따라 치다가 자주 보는 에러입니다.

text
$ git remote add origin https://github.com/minji/cafe-menu.git
error: remote origin already exists.

이 에러가 나면 새로 추가할 게 아니라 이미 등록돼 있다는 뜻입니다. 주소가 맞는지 git remote -v로 확인하고, 다르면 아래 ④ set-url로 고치세요. 다른 별명이라면 얼마든지 추가할 수 있습니다. 예를 들어 민지가 백업용 금고를 하나 더 둔다면:

bash
git remote add backup https://gitlab.com/minji/cafe-menu.git
git remote -v
text
backup	https://gitlab.com/minji/cafe-menu.git (fetch)
backup	https://gitlab.com/minji/cafe-menu.git (push)
origin	https://github.com/minji/cafe-menu.git (fetch)
origin	https://github.com/minji/cafe-menu.git (push)

이제 git push backup main처럼 별명으로 보낼 곳을 고릅니다. 별명 없이 git push만 치면 짝꿍(upstream)이 정해진 곳, 보통 origin으로 갑니다.

③ 이름 바꾸기·지우기 — rename과 remove는 주소록만 고칩니다. 금고(GitHub 저장소) 자체나 내 커밋은 전혀 건드리지 않으니 안심하고 써도 됩니다.

bash
git remote rename backup mirror
git remote remove mirror
git remote -v
text
origin	https://github.com/minji/cafe-menu.git (fetch)
origin	https://github.com/minji/cafe-menu.git (push)

없는 별명을 지우려 하면 error: No such remote: 'mirror'가 나옵니다. 오타가 없는지 git remote로 목록부터 보세요.

④ 주소 바꾸기 — 저장소 이름을 바꿨거나, 인증을 HTTPS에서 SSH로 옮길 때 씁니다. SSH 주소는 git@github.com:아이디/저장소.git 모양입니다(SSH 키를 먼저 등록해 두어야 합니다, 3장 ③ 참고).

bash
git remote set-url origin git@github.com:minji/cafe-menu.git
git remote -v
text
origin	git@github.com:minji/cafe-menu.git (fetch)
origin	git@github.com:minji/cafe-menu.git (push)

반대로 SSH에서 HTTPS로 돌아갈 때도 같은 명령에 https://github.com/minji/cafe-menu.git을 넣으면 됩니다. 주소 하나만 보고 싶다면 git remote get-url origin을 씁니다.

⑤ 자세히 보기 — git remote show origin은 그 원격과 내 브랜치들이 어떻게 짝지어져 있는지 한눈에 보여 줍니다. 이 명령은 금고에 실제로 접속해서 확인합니다.

bash
git remote show origin
text
* remote origin
  Fetch URL: https://github.com/minji/cafe-menu.git
  Push  URL: https://github.com/minji/cafe-menu.git
  HEAD branch: main
  Remote branch:
    main tracked
  Local branch configured for 'git pull':
    main merges with remote main
  Local ref configured for 'git push':
    main pushes to main (up to date)
  • HEAD branch: main — 금고의 기본 브랜치는 main입니다.
  • main merges with remote main — 내 main에서 git pull하면 금고의 main을 받아 합칩니다.
  • main pushes to main (up to date) — 내 main에서 git push하면 금고의 main으로 가고, 지금은 둘이 같습니다.
하고 싶은 일명령금고(원격 저장소)에 영향?
별명 목록git remote / git remote -v없음
원격 추가git remote add <별명> <주소>없음 (주소록에만 추가)
별명 바꾸기git remote rename <옛> <새>없음
원격 지우기git remote remove <별명>없음 (GitHub 저장소는 그대로)
주소 바꾸기git remote set-url <별명> <새 주소>없음
주소 하나 보기git remote get-url <별명>없음
짝꿍 관계 자세히git remote show <별명>없음 (읽기만)
🗂️
기억할 한 줄 — git remote 명령은 모두 내 컴퓨터의 주소록만 바꿉니다. 금고에 무언가가 실제로 오가는 건 push·fetch·pull·clone뿐입니다. GitHub 저장소 자체를 지우거나 이름을 바꾸는 일은 GitHub 웹의 저장소 Settings에서 합니다.

6. push와 pull, 그리고 fetch

이제 두 사람이 같은 금고를 씁니다. 도윤이 메뉴에 음료를 하나 추가해 커밋하고 올립니다.

bash
git add menu.md
git commit -m "메뉴에 바닐라라떼 추가"
text
[main 137a16b] 메뉴에 바닐라라떼 추가
 1 file changed, 1 insertion(+)
bash
git push
text
To https://github.com/minji/cafe-menu.git
   b901b3a..137a16b  main -> main

b901b3a..137a16b main -> main은 "금고의 main을 b901b3a에서 137a16b로 앞으로 옮겼다"는 뜻입니다. 첫 push 때의 [new branch] 대신 이전..이후 형태로 나옵니다.

함정: git status는 금고를 들여다보지 않는다

같은 시각, 민지가 자기 컴퓨터에서 상태를 확인합니다.

bash
git status
text
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

"up to date(최신)"라고 나옵니다. 하지만 금고에는 도윤의 새 커밋이 이미 들어가 있습니다. 왜 이럴까요? 4장 끝에서 본 대로 origin/main은 마지막으로 확인했을 때의 메모이기 때문입니다. git status는 인터넷에 접속하지 않고, 이 메모와 내 main을 비교할 뿐입니다. 즉 이 문장은 "금고와 같다"가 아니라 "내가 마지막으로 본 금고와 같다"로 읽어야 합니다.

git fetch: 받아 오기만 하기

메모를 새로 고치는 명령이 git fetch입니다. 금고에 접속해 새 커밋을 내려받고 origin/main 메모를 갱신하지만, 내 main과 작업 폴더의 파일은 건드리지 않습니다.

bash
git fetch
text
From https://github.com/minji/cafe-menu
   b901b3a..137a16b  main       -> origin/main

"금고의 main이 137a16b까지 왔길래 내 메모(origin/main)를 옮겨 뒀다"는 뜻입니다. 이제 상태를 다시 보면 달라집니다.

bash
git status
text
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

behind ... by 1 commit — 금고보다 커밋 1개 뒤처져 있다는 뜻입니다. can be fast-forwarded는 "내 쪽에 새 커밋이 없으니 그냥 앞으로 당기기만 하면 된다"는 뜻인데, 이 fast-forward(빨리 감기)는 4편에서 자세히 다룹니다. git이 친절하게 다음 할 일도 알려 줍니다: use "git pull".

git pull: 받아서 합치기

bash
git pull
text
Updating b901b3a..137a16b
Fast-forward
 menu.md | 1 +
 1 file changed, 1 insertion(+)

이제 민지의 menu.md에도 바닐라라떼가 들어왔습니다.

bash
git log --oneline
text
137a16b 메뉴에 바닐라라떼 추가
b901b3a README에 영업시간 추가
a223993 카페 웹사이트 첫 버전

방금은 fetch를 먼저 하고 pull을 했지만, 사실 git pull은 안에서 fetch를 먼저 한 뒤 merge(합치기)를 하는 명령입니다. fetch를 따로 하지 않고 바로 git pull만 쳐도 결과는 같습니다.

git pull
=
① git fetch
금고의 새 커밋을 받아 오고
origin/main 메모를 갱신
+
② git merge
origin/main을 내 main에 합치고
작업 폴더 파일도 바뀜
명령금고에 접속origin/main 메모내 main·파일언제 쓰나
git status안 함그대로그대로내 상태 확인 (메모 기준)
git fetch함갱신그대로"뭐가 바뀌었나" 먼저 보고 싶을 때
git pull함갱신합쳐짐받아서 바로 내 것으로 만들 때 (평소)
git push함갱신그대로내 커밋을 금고에 올릴 때

fetch는 "택배를 문 앞까지만 받아 두기", pull은 "받아서 뜯고 정리까지 하기"라고 생각하면 됩니다. 입문 단계에서는 거의 항상 git pull을 쓰고, 합치기 전에 무엇이 들어왔는지 먼저 보고 싶을 때만 git fetch 후 git log --oneline main..origin/main처럼 확인하면 충분합니다.

7. "rejected" — 도윤이 먼저 push했을 때

이제 협업에서 가장 자주 만나는 장면입니다. 둘 다 최신 상태에서 출발해, 도윤은 menu.md에 레몬에이드를, 민지는 index.html에 영업시간을 추가했습니다. 도윤이 먼저 push합니다.

text
$ git push
To https://github.com/minji/cafe-menu.git
   137a16b..81deecf  main -> main

잠시 뒤 민지도 커밋하고 push합니다.

bash
git add index.html
git commit -m "첫 화면에 영업시간 표시"
git push
text
[main 6614321] 첫 화면에 영업시간 표시
 1 file changed, 1 insertion(+)
text
To https://github.com/minji/cafe-menu.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://github.com/minji/cafe-menu.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.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

먼저 도착한 도윤의 커밋 때문에 금고 문 앞에서 멈춘 민지크게 보기

빨간 글씨가 잔뜩 나와 놀랄 수 있지만, 번역해 보면 아주 점잖은 말입니다.

거절 메시지 번역
! [rejected] main -> main (fetch first) — main을 올리려 했지만 거절됨. 먼저 받아 오세요(fetch first).
the remote contains work that you do not have locally — 금고에 당신 컴퓨터엔 없는 작업이 들어 있습니다.
usually caused by another repository pushing to the same ref — 보통 다른 사람이 같은 브랜치에 먼저 올렸을 때 생깁니다. (바로 도윤!)
use 'git pull' before pushing again — 다시 push하기 전에 git pull 하세요.

왜 거절할까요? 금고의 main은 지금 도윤의 레몬에이드 커밋을 가리키고 있습니다. 민지의 main은 그 커밋을 모른 채 바닐라라떼 → 민지의 영업시간 커밋으로 이어집니다. 여기서 민지의 main으로 금고를 그냥 덮으면 도윤의 레몬에이드 커밋이 금고에서 사라집니다. git은 남의 작업을 몰래 지우지 않도록, 금고가 "내 커밋 뒤에 이어 붙이기만 하면 되는 경우"에만 push를 받아 줍니다. 거절은 고장이 아니라 안전장치입니다.

🚫
문제
도윤이 먼저 push해서 금고에 민지가 모르는 커밋이 생겼다. 민지의 push는 거절된다.
⬇️
해결
git pull로 도윤의 커밋을 받아 민지의 커밋과 합친다(merge).
✅
결과
민지의 main에 두 사람의 작업이 모두 들어간다. 이제 git push가 받아들여진다.

pull을 했는데 또 멈췄다: divergent branches

안내대로 git pull을 쳤더니, 최근 git에서는 이런 긴 안내와 함께 멈출 수 있습니다.

text
$ git pull
From https://github.com/minji/cafe-menu
   137a16b..81deecf  main       -> origin/main
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.

divergent branches(갈라진 브랜치)란 "내 쪽에도 새 커밋이 있고, 금고 쪽에도 새 커밋이 있어서 역사가 두 갈래로 갈라졌다"는 뜻입니다. 지금 상태가 정확히 그렇습니다.

text
$ git status
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" if you want to integrate the remote branch with yours)

갈라진 두 갈래를 하나로 만드는 방법은 크게 두 가지가 있습니다. git은 사용자가 어느 쪽을 원하는지 모르니, 한 번 정해 달라고 요청하는 것입니다. fetch는 이미 끝났고(맨 위 두 줄), 합치기 단계에서 멈춘 상태입니다.

설정하는 일결과입문자에게
pull.rebase false두 갈래를 merge 커밋으로 묶어 합침역사가 있는 그대로 남음. 그래프에 합쳐진 흔적이 보임추천 — 이해하기 쉽고, 문제가 생겨도 되돌리기 쉬움
pull.rebase true내 커밋을 금고 커밋 뒤로 옮겨 다시 쌓음역사가 한 줄로 깔끔함개념을 익힌 뒤에 (6편에서 개념 소개)
pull.ff only빨리 감기가 가능할 때만 pull, 갈라졌으면 거부합치기는 직접 해야 함조심스러운 팀용

이 시리즈는 merge 방식을 기본으로 합니다. 모든 저장소에 한 번에 적용되도록 --global을 붙여 설정합니다.

bash
git config --global pull.rebase false

설정은 한 번만 하면 됩니다. 확인은 이렇게 합니다.

bash
git config --global --get pull.rebase
text
false

이제 다시 pull합니다.

bash
git pull

이때 merge 커밋 메시지를 적는 편집기 화면이 열릴 수 있습니다. Merge branch 'main' of https://github.com/…라는 기본 메시지가 이미 적혀 있으니, 그대로 저장하고 닫으면 됩니다. (vim이 열렸다면 :wq를 치고 Enter. 편집기를 건너뛰고 싶으면 git pull --no-edit을 쓰세요.) 그러면 이렇게 끝납니다.

text
Merge made by the 'ort' strategy.
 menu.md | 1 +
 1 file changed, 1 insertion(+)

ort는 git이 기본으로 쓰는 합치기 방식의 이름이니 신경 쓰지 않아도 됩니다. 그래프로 보면 무슨 일이 있었는지 한눈에 들어옵니다.

bash
git log --oneline --graph
text
*   f3d8af5 Merge branch 'main' of https://github.com/minji/cafe-menu
|\  
| * 81deecf 메뉴에 레몬에이드 추가
* | 6614321 첫 화면에 영업시간 표시
|/  
* 137a16b 메뉴에 바닐라라떼 추가
* b901b3a README에 영업시간 추가
* a223993 카페 웹사이트 첫 버전

바닐라라떼 커밋에서 두 갈래(민지의 영업시간, 도윤의 레몬에이드)로 갈라졌다가, 맨 위 merge 커밋 f3d8af5에서 다시 만났습니다. 이제 민지의 main은 금고의 모든 커밋을 포함하므로, push가 "이어 붙이기"가 됩니다.

bash
git push
text
To https://github.com/minji/cafe-menu.git
   81deecf..f3d8af5  main -> main

성공입니다. 도윤이 나중에 pull하면 민지의 영업시간과 merge 커밋을 빨리 감기로 받아 갑니다.

text
$ git pull
From https://github.com/minji/cafe-menu
   81deecf..f3d8af5  main       -> origin/main
Updating 81deecf..f3d8af5
Fast-forward
 index.html | 1 +
 1 file changed, 1 insertion(+)
🧯
두 사람이 같은 줄을 고쳤다면? — 이번 예시는 도윤은 menu.md, 민지는 index.html을 고쳐서 git이 자동으로 합칠 수 있었습니다. 둘이 같은 파일의 같은 줄을 서로 다르게 고쳤다면 git은 어느 쪽이 맞는지 모르니 pull 도중 충돌(conflict)을 알리고 사람에게 골라 달라고 합니다. 충돌을 읽고 푸는 법은 5편에서 따로 다룹니다. 지금은 "거절 → pull → push"라는 흐름만 기억하세요.

비슷한 형제: (non-fast-forward)

거절 메시지의 괄호 안이 fetch first가 아니라 non-fast-forward인 경우도 있습니다.

text
$ git push
To https://github.com/minji/cafe-menu.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/minji/cafe-menu.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

차이는 사소합니다. fetch first는 "금고에 처음 보는 커밋이 있다", non-fast-forward는 "fetch로 받아 두기는 했는데 내 main에 합치지 않았다"입니다. 해결법은 똑같이 git pull 후 git push입니다.

직접 해 보기: 민지와 도윤 시뮬레이터

아래에서 두 사람의 컴퓨터와 금고를 버튼으로 움직여 볼 수 있습니다. 추천 순서대로 한 번 눌러 거절을 겪어 보고, 그다음엔 fetch만 하고 push해 non-fast-forward도 만들어 보세요. git status 줄이 "ahead(앞섬)", "behind(뒤처짐)", "diverged(갈라짐)"로 바뀌는 것도 눈여겨보세요.

8. 절대 push --force로 밀어붙이지 마세요

거절 메시지를 검색하다 보면 "git push --force(또는 -f)를 쓰면 된다"는 답을 보게 됩니다. 실제로 그 명령을 치면 거절 없이 올라갑니다. 문제는 어떻게 올라가느냐입니다.

⛔
강제 push는 금고의 역사를 내 것으로 덮어씁니다. 앞 장의 상황에서 민지가 git push --force를 쳤다면, 금고의 main은 민지의 커밋을 가리키게 되고 도윤의 레몬에이드 커밋은 금고에서 사라집니다. 도윤은 영문도 모른 채 다음 pull에서 이상한 상태를 만나게 됩니다. 거절 메시지를 없애는 방법이 아니라, 동료의 작업을 지우는 방법입니다.

규칙은 간단합니다.

  • 여러 사람이 함께 쓰는 브랜치(main 등)에는 절대 --force를 쓰지 않습니다. 거절되면 언제나 pull 먼저.
  • 이미 push한 커밋을 "없던 일로" 하고 싶다면, 역사를 지우는 대신 되돌리는 새 커밋을 쌓는 git revert를 씁니다. 되돌리기 방법들은 5편에서 자세히 다룹니다.
  • 나 혼자만 쓰는 브랜치에서 꼭 필요할 때도, 남의 새 커밋이 있으면 멈춰 주는 --force-with-lease가 더 안전합니다. 하지만 이 시리즈를 읽는 단계에서는 "force가 떠오르면 멈추고 물어보기"로 충분합니다.

많은 팀이 GitHub의 브랜치 보호 규칙으로 main에 강제 push를 아예 막아 둡니다. 이 설정은 6편에서 다룹니다.

9. 하루 루틴: 아침 pull, 올리기 전 pull

지금까지 배운 것을 하루 흐름으로 묶으면 이렇습니다. 이 여섯 줄만 몸에 익혀도 협업의 대부분은 문제없이 굴러갑니다.

아침 커피와 함께 pull하고, 퇴근 전에 push하는 민지의 하루크게 보기

아침
git pull — 밤사이 동료가 올린 커밋부터 받기. 오래된 파일 위에서 일하지 않기 위해.
작업
파일 고치기. 중간중간 git status·git diff로 무엇이 바뀌었는지 확인.
기록
git add → git commit -m "..." — 의미 있는 단위마다 세이브 포인트(2편). 하루에 여러 번 해도 좋습니다.
올리기 전
git pull — 작업하는 사이 누가 push했을 수 있습니다. 먼저 받아 합쳐 두면 거절되지 않습니다.
올리기
git push — 커밋이 내 컴퓨터를 떠나 금고로. 동료가 받아 볼 수 있게 됩니다.

몇 가지 요령을 덧붙이면:

  • 자주, 작게 올리세요. 일주일치를 한 번에 push하면 그사이 금고도 많이 바뀌어 합칠 게 많아집니다. 하루에 한 번 이상 push하는 습관이 충돌을 줄이는 가장 쉬운 방법입니다.
  • pull은 커밋한 뒤에. 고치는 중인(커밋하지 않은) 파일이 있을 때 pull하면, 받아 온 변경이 같은 파일을 건드릴 경우 git이 "덮어쓸 수 있으니 먼저 커밋하거나 치워 두라"며 멈춥니다. 작업을 커밋해 두고 pull하는 순서가 안전합니다(잠깐 치워 두는 git stash는 5편에서).
  • push 전에 git log --oneline -3으로 한 번 훑기. 무엇이 올라가는지 눈으로 확인하는 습관은, 나중에 AI 에이전트와 일할 때 더 중요해집니다.

순서를 섞어 놓은 카드를 올바른 순서로 눌러 보세요. 순서를 틀리면 실제로 어떤 일이 생기는지 알려 줍니다.

10. 빨간 글씨 해독기: push·pull 에러 모음

마지막으로, 이번 편에서 만났거나 곧 만날 에러 메시지를 한곳에 모았습니다. 목록에서 고르거나 터미널에 뜬 메시지를 그대로 붙여 넣으면 쉬운 설명과 해결 명령을 보여 줍니다.

그중 가장 자주 만나는 것만 표로 정리하면 다음과 같습니다.

메시지의 핵심 문구뜻해결
[rejected] ... (fetch first)금고에 내가 모르는 커밋이 있음git pull → git push
[rejected] ... (non-fast-forward)받아 두기만 하고 안 합침git pull → git push
divergent branches양쪽 다 새 커밋, 합치는 방식 미정git config --global pull.rebase false 후 다시 pull
has no upstream branch짝꿍 브랜치가 없음git push -u origin main
No configured push destination원격 주소를 등록 안 함git remote add origin <주소>
remote origin already existsorigin이 이미 등록돼 있음(clone한 폴더에서 흔함)git remote -v로 확인, 다르면 git remote set-url
src refspec main does not match any올릴 main 브랜치가 없음(커밋이 없거나 이름이 다름)커밋부터 하거나 git branch로 이름 확인
refusing to merge unrelated histories뿌리가 다른 두 역사(GitHub에서 README 체크)--allow-unrelated-histories (FAQ 참고)
인증 실패(Authentication failed)비밀번호를 넣었거나 토큰이 틀림/만료gh auth login
🤖
AI 에이전트와 함께 쓸 때 — Claude Code 같은 코딩 에이전트는 git 명령을 직접 실행할 수 있습니다. 그중 push는 작업이 내 컴퓨터를 떠나 동료에게 보이게 되는 순간이라, 되돌리기가 가장 번거로운 동작입니다. Claude Code는 기본 권한 설정에서 이런 명령을 실행하기 전에 허락을 구하는데, 그때 기계적으로 "예"를 누르지 말고 세 가지를 확인하세요. ① git log --oneline origin/main..HEAD로 무엇이 올라가는지 ② 올라가는 곳이 어느 원격·어느 브랜치인지 ③ 명령에 --force나 -f가 섞여 있지 않은지. 에이전트가 push 거절을 만났을 때 force로 밀어붙이자고 하면 거절하고 "pull해서 합친 뒤 다시 push해 줘"라고 말하면 됩니다. 에이전트에게 권한을 어디까지 줄지, 위험한 명령을 아예 막는 설정은 7편에서 다룹니다.

자주 하는 질문(FAQ)

Q1. GitHub에서 README를 체크하고 저장소를 만들어 버렸어요. 첫 push가 거절돼요.

금고에 GitHub가 만든 "Initial commit"이 있고, 내 컴퓨터엔 따로 시작한 커밋이 있어서 뿌리가 다른 상태입니다. 그냥 git pull origin main을 하면 divergent branches 안내가, 합치기 방식을 정한 뒤에는 fatal: refusing to merge unrelated histories가 나옵니다. "뿌리가 달라도 합쳐도 된다"고 명시하면 됩니다.

bash
git pull origin main --allow-unrelated-histories --no-rebase
git push -u origin main
text
From https://github.com/minji/cafe-menu
 * branch            main       -> FETCH_HEAD
Merge made by the 'ort' strategy.
 README.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

(편집기가 열리면 저장하고 닫으세요.) 양쪽 README가 모두 있었다면 충돌이 날 수 있는데, 그 해결은 5편을 보세요. 아직 아무것도 올리지 않았다면 GitHub에서 저장소를 지우고 빈 저장소로 다시 만드는 것도 방법입니다.

Q2. git pull과 git fetch 중 뭘 써야 하나요?

평소에는 git pull이면 됩니다. fetch는 "받아 오기만 하고 내 파일은 그대로 두는" 명령이라, 합치기 전에 동료가 무엇을 올렸는지 먼저 살펴보고 싶을 때 씁니다. git fetch 후 git status를 보면 몇 커밋 뒤처졌는지 알 수 있고, git log --oneline main..origin/main으로 새로 들어온 커밋 목록을 볼 수 있습니다.

Q3. git status가 "up to date"라는데, 동료는 방금 push했다고 해요.

git status는 인터넷에 접속하지 않고, 마지막으로 확인한 금고 상태(origin/main 메모)와 비교합니다. git fetch를 한 번 한 뒤 다시 git status를 보면 "behind"로 바뀝니다. 그래서 "up to date"는 "내가 마지막으로 본 금고와 같다"로 읽어야 합니다.

Q4. push하려는데 Username과 Password를 물어요. 비밀번호를 넣었더니 실패해요.

GitHub는 git 작업에서 계정 비밀번호를 받지 않습니다(2021년 8월부터). 가장 쉬운 해결은 gh auth login으로 로그인하는 것이고, 토큰을 쓰려면 GitHub 설정에서 개인 액세스 토큰을 만들어 비밀번호 칸에 붙여 넣습니다. 한 번 잘못된 값이 저장되어 계속 실패한다면, gh auth login 후 gh auth setup-git을 실행해 인증을 gh에 맡기세요.

Q5. 원격 저장소 주소를 바꾸고 싶어요 (저장소 이름을 바꿨거나, SSH로 옮기고 싶어요).

git remote set-url로 origin의 주소만 바꾸면 됩니다. 커밋 기록은 그대로입니다.

bash
git remote set-url origin https://github.com/minji/cafe-menu-v2.git
git remote -v

Q6. pull할 때마다 "Merge branch 'main' of …" 커밋이 생기는 게 지저분해 보여요.

merge 방식(pull.rebase false)의 자연스러운 결과입니다. 두 갈래를 합쳤다는 사실이 기록에 남는 것이라 문제는 아닙니다. 올리기 전 pull 습관을 들이면 갈라질 일 자체가 줄어 merge 커밋도 줄어듭니다. 한 줄짜리 깔끔한 역사를 원하는 팀은 rebase 방식을 쓰는데, 그 차이와 주의점은 6편에서 개념으로 소개합니다. 역사와 내부 구조까지 궁금하다면 Git 완전 정복도 참고하세요.

이번 편 요약 (치트시트)

하고 싶은 일명령메모
GitHub에 로그인(인증)gh auth login비밀번호로는 push 불가. 확인은 gh auth status
금고 주소 등록git remote add origin <주소>기존 폴더를 올릴 때 한 번
등록된 원격 확인git remote -vfetch·push 주소 두 줄
원격 주소 바꾸기git remote set-url origin <새 주소>저장소 이름 변경·HTTPS↔SSH 전환
원격 이름 바꾸기·지우기git remote rename · git remote remove주소록만 바뀜, GitHub 저장소는 그대로
짝꿍 관계 자세히 보기git remote show originpull·push가 어디로 가는지
첫 push + 짝꿍 설정git push -u origin main-u는 브랜치마다 처음 한 번
금고에서 통째로 받기git clone <주소>origin·짝꿍 자동 설정
올리기git push커밋만 올라감
받아서 합치기git pull= fetch + merge
받아 오기만git fetch내 파일은 그대로, origin/main만 갱신
짝꿍과 앞섬/뒤처짐 보기git status · git branch -vv기준은 마지막으로 본 금고
pull 합치기 방식 정하기git config --global pull.rebase false한 번만. 이 시리즈는 merge 방식
push 거절됐을 때git pull → git push--force 금지
하루 루틴pull → 작업 → add → commit → pull → push자주, 작게

다음 편 예고

이번 편까지 민지와 도윤은 둘 다 main 한 줄 위에서 일했습니다. 그래서 누가 먼저 push하느냐에 따라 거절과 merge가 생겼죠. 그런데 도윤이 몇 주 걸리는 새 메뉴판 디자인을 만드는 동안, 민지는 오늘 당장 가격을 고쳐야 한다면 어떻게 할까요? 4편 — 브랜치와 merge에서는 브랜치로 작업 줄기를 나눠 서로 방해하지 않고 일한 뒤, 완성된 것만 합치는 법을 배웁니다. 이번 편에서 스쳐 지나간 fast-forward와 merge 커밋의 정체도 거기서 밝혀집니다.