A-4: GitHub와 원격 다루기
목차 27
또 만났네요, 홍순구 튜터입니다. 지난 시간 우리는 되돌리기의 모든 것을 손에 넣었어요. 고치던 변경을 버리고, 잘못 찍은 커밋을 되감고, 공유한 커밋을 안전하게 취소하고, 날린 줄 알았던 커밋까지 되살려봤죠. 그리고 마지막에 .gitignore 로 비밀번호 같은 건 애초에 안 들이는 법까지 챙겼어요. 그 .gitignore 를 왜 인터넷에 올리기 직전에 배웠는지, 오늘 그 이유가 드러나요.
지금까지 우리가 다룬 git 은 전부 내 컴퓨터 안에서만 돌았어요. 인터넷이 한 번도 필요 없었죠. 커밋도, 브랜치도, 되돌리기도 전부 내 노트북 한 대 안에서 벌어진 일이었어요. 그런데 개발은 결국 여럿이 함께 하는 일이에요. 내 코드를 동료에게 보여주고, 동료의 코드를 받아오고, 같은 프로젝트를 여러 사람이 나눠 고치죠.
오늘은 드디어 그 문을 엽니다. 내 저장소를 인터넷(GitHub)에 올려 어디서든 접근하고, 여럿이 함께 다루는 법이요. 처음 GitHub 를 만나면 계정, 2FA, SSH 키, 토큰 같은 낯선 단어가 우르르 쏟아져서 겁날 수 있어요. 괜찮아요. 오늘 하나씩 천천히, 막히지 않게 같이 넘어가요. 끝나면 "내 코드가 인터넷에 안전하게 올라가 있고, 언제든 받아올 수 있다" 는 든든함이 생길 거예요.
오늘의 여정 — 내 저장소를 인터넷에 올려 함께 다루기
① 내 저장소를 인터넷에 — GitHub와 원격이라는 개념
② GitHub 계정과 2FA — 선택이 아니라 필수
③ 내 컴퓨터와 GitHub가 서로를 믿게 — 인증(토큰 vs SSH 키)
④ 로컬 저장소를 원격에 올리기 — git remote와 첫 push
⑤ 원격 저장소를 통째로 받아오기 — git clone
⑥ 원격의 변화를 가져오기 — fetch, pull, 그리고 origin/main
⑦ 로컬과 원격이 어긋나지 않게 — 동기화 한 장과 push가 거부될 때
①에서 원격이라는 개념과 GitHub 가 무엇인지 깔고, ②③에서 계정을 만들고 내 컴퓨터가 GitHub 와 안전하게 연결되게 준비해요. ④⑤에서 올리고(push) 받아오는(clone) 기본을 익히고, ⑥에서 원격의 변화를 가져오는 두 방법(fetch·pull)을 배워요. ⑦에서 로컬과 원격이 어긋날 때 어떻게 맞추는지로 마무리합니다.
💡 오늘 수업의 핵심 — "원격(GitHub)은 모두가 공유하는 중심 저장소이고, 그 이름은 보통 origin 이다. 내 커밋을 올리는 건 git push, 받아오는 건 git pull. push 가 거부되면 혼나는 게 아니라 '먼저 pull 해서 동료 작업과 합쳐라' 는 안전장치다."
🎯 학습 목표
- GitHub 계정을 만들고 2FA(2단계 인증) 를 설정하며, HTTPS 토큰 또는 SSH 키로 내 컴퓨터를 GitHub 에 인증합니다.
- 로컬 저장소를
git remote로 원격에 연결하고git push로 올리며, 원격 저장소를git clone으로 받아옵니다. git fetch와git pull의 차이를 구분하고, 원격 추적 브랜치origin/main을 이해해 로컬과 원격을 어긋나지 않게 동기화합니다.
Step 1: "내 저장소를 인터넷에 — GitHub와 원격이라는 개념"
본격적으로 명령을 치기 전에, 그림 한 장으로 오늘의 무대를 깔게요. 지난 시간까지 우리 git 은 전부 내 컴퓨터 한 대 안에서만 돌았어요. 이걸 로컬(local, 내 컴퓨터) 저장소라고 불러요. 그런데 이 저장소를 인터넷 어딘가에 똑같이 하나 더 두면 어떨까요? 그게 바로 원격(remote) 저장소예요.
원격 저장소는 비유하자면 여럿이 함께 쓰는 공유 드라이브예요. 내 노트북이 고장 나도 코드가 인터넷에 안전하게 남아 있고, 회사 데스크탑에서도 똑같이 받아 쓸 수 있고, 무엇보다 동료가 같은 저장소를 받아 함께 고칠 수 있죠. 혼자 다루던 git 이 비로소 협업 도구가 되는 순간이에요.
그럼 GitHub 는 뭘까요? GitHub 는 그 원격 저장소를 인터넷에 올려두고 호스팅해주는 웹 서비스예요. A-1 에서 한 번 짚었죠 — Git 은 내 컴퓨터에서 도는 프로그램, GitHub 는 그 저장소를 인터넷에서 공유하게 해주는 서비스. 비슷한 서비스로 GitLab·Bitbucket 도 있지만, 가장 널리 쓰이는 GitHub 를 기준으로 배워요.
내 컴퓨터(로컬)의 .git 저장소를 인터넷 위 GitHub(원격)로 올린다.
원격 저장소의 기본 이름은 보통 'origin' 이라고 부른다.
로컬 .git ──────── git push ───────▶ origin (GitHub)
로컬 .git ◀─────── git pull ──────── origin (GitHub)
push = 내 커밋을 원격으로 올리기 / pull = 원격의 새 커밋을 받아오기
여기서 한 가지, 왜 우리가 지난 시간 마지막에 .gitignore 를 챙겼는지 이제 보여요. 인터넷에 올린다는 건 누군가 볼 수도 있다는 뜻이에요. 그래서 비밀번호나 API 키 같은 건 올리기 전에 .gitignore 로 막아둬야 해요. "혼자 다룰 땐 괜찮았지만, 인터넷에 올리는 순간 비밀은 새면 끝" 이라서, 그 안전장치를 먼저 익혀둔 거였어요.
⚠️ GitHub 에 올린 건 되돌려도 흔적이 남을 수 있어요. 한 번 push 한 비밀번호는 누군가 이미 받아갔을 수 있어요. 그러니 "올리기 전에
.gitignore로 막기" 를 습관으로 두세요. 사고를 수습하는 것보다 처음부터 예방하는 게 훨씬 쉬워요.
💡 한 줄 정리
원격(remote)은 인터넷에 둔 저장소 사본이고, GitHub 는 그 원격을 호스팅해주는 웹 서비스다. 보통 원격의 이름은 origin, 올리는 건 push, 받아오는 건 pull 이다.
🙋 학생 질문 — "튜터님, 그냥 구글 드라이브에 폴더째 올리면 안 되나요?"
좋은 질문이에요. 실제로 많은 분이 그렇게 시작해요. 그런데 구글 드라이브는 파일을 통째로 동기화할 뿐, "누가 무엇을 왜 바꿨는지" 의 역사를 모르잖아요.
둘이 같은 파일을 동시에 고치면 드라이브는 보통 한쪽을 "사본"으로 만들거나 한쪽을 덮어써요. 충돌을 사람이 직접 보고 합칠 수가 없죠. 우리가 A-2 에서 배운 그 충돌 해결을, 드라이브는 못 해줘요.
GitHub 원격은 달라요. 커밋 단위의 역사를 그대로 들고 올라가고, 둘이 같은 곳을 고치면 "여기 충돌났으니 사람이 정해라" 하고 명시적으로 멈춰줘요. 그래서 코드 협업은 드라이브가 아니라 git 원격으로 하는 거예요.
Step 2: "GitHub 계정과 2FA — 선택이 아니라 필수"
이제 GitHub 에 가입할 차례예요. github.com 에 가서 이메일·사용자 이름·비밀번호로 가입하면 돼요. 사용자 이름은 앞으로 내 저장소 주소에 들어가니까(github.com/사용자이름/저장소), 너무 장난스럽지 않은 걸로 정하는 걸 권해요.
가입하면 GitHub 가 곧바로 한 가지를 요구해요. 바로 2FA(Two-Factor Authentication, 2단계 인증) 예요. 비밀번호 하나만으로는 부족하니, 로그인할 때 비밀번호 + 두 번째 증거(휴대폰 앱이 만드는 6자리 숫자 등)를 함께 요구하는 보안 장치죠.
여기서 꼭 짚고 갈게요. 2FA 는 이제 선택이 아니라 필수예요. GitHub 는 2026년 5월부터 코드를 다루는 모든 계정에 2FA 를 의무화했어요. 설정 안 하면 일정 유예 기간 뒤에 로그인이 막혀요. 그러니 "나중에 해야지" 가 아니라 가입 직후 바로 설정하는 게 맞아요.
2FA 로그인 — 두 가지 증거를 함께
① 아는 것 비밀번호 (내가 외운 것)
+
② 가진 것 인증 앱의 6자리 숫자 / 하드웨어 보안 키 (내가 가진 기기)
=
둘 다 맞아야 로그인 → 비밀번호가 새도 두 번째가 없으면 못 들어옴
설정 방법은 간단해요. 휴대폰에 인증 앱(authenticator) 을 하나 깔고(예: 구글 Authenticator, Microsoft Authenticator 등), GitHub 의 보안 설정에서 뜨는 QR 코드를 그 앱으로 찍으면 끝이에요. 그러면 앱이 30초마다 바뀌는 6자리 숫자를 보여주는데, 로그인할 때 그 숫자를 같이 넣어요. 더 단단하게 하려면 하드웨어 보안 키(물리적 USB 키)를 쓸 수도 있어요.
겁먹지 마세요. 처음 한 번만 설정하면 그다음부턴 자동이에요. 그리고 이건 여러분 코드를 지키는 일이에요 — 비밀번호가 어디선가 새더라도, 내 휴대폰이 없으면 남이 못 들어오니까요. 현업에선 이 2FA 가 당연한 기본기라, 지금 익혀두면 어디 가서도 막히지 않아요.
💡 한 줄 정리
GitHub 계정은 가입 직후 2FA(2단계 인증) 설정이 필수다(2026 의무화). 인증 앱으로 QR 을 찍어두면, 로그인 때 비밀번호 + 6자리 숫자를 함께 넣어 계정을 지킨다.
🙋 학생 질문 — "튜터님, 휴대폰을 잃어버리면 영영 로그인 못 하나요?"
가장 많이 걱정하는 부분이에요. 그래서 GitHub 는 2FA 를 켜는 순간 복구 코드(recovery codes) 를 여러 개 보여줘요.
이건 휴대폰이 없을 때 대신 쓸 수 있는 일회용 비밀 코드예요. 화면에 뜨면 꼭 안전한 곳에 따로 저장해두세요(인쇄하거나, 비밀번호 관리 앱에). 휴대폰을 잃어버려도 이 코드 하나로 들어가서 2FA 를 다시 설정할 수 있어요.
그러니 2FA 를 켤 때 "복구 코드 저장" 단계를 절대 건너뛰지 마세요. 이거 하나면 "휴대폰 잃어버리면 어쩌지" 걱정은 안 해도 돼요.
Step 3: "내 컴퓨터와 GitHub가 서로를 믿게 — 인증(토큰 vs SSH 키)"
계정을 만들었으니, 이제 내 컴퓨터가 GitHub 에 "나 이 계정 주인 맞아요" 라고 증명할 차례예요. 안 그러면 아무 컴퓨터에서나 내 저장소에 막 올릴 수 있을 테니까요.
먼저 옛날 방식 하나를 정리하고 갈게요. 예전엔 push 할 때 GitHub 비밀번호를 그냥 입력했어요. 그런데 이 방식은 폐지됐어요. 비밀번호는 너무 위험하거든요. 지금은 두 가지 방법 중 하나만 고르면 돼요. 둘 다 할 필요 없어요.
| 방법 | 한 줄 설명 | 이런 분께 |
|---|---|---|
| HTTPS + 토큰 | 비밀번호 대신 GitHub 가 발급한 긴 비밀 문자열(토큰)을 씀 | 설정이 가장 간단. 일단 빨리 시작하고 싶다면 🌟 |
| SSH 키 | 내 컴퓨터에 열쇠 한 쌍(공개키·비밀키)을 만들어 GitHub 에 등록 | 한 번 설정하면 매번 입력 없이 편함. 자주 push 한다면 |
HTTPS + 토큰 부터 볼게요. GitHub 의 설정(Settings → Developer settings → Personal access tokens)에서 토큰을 하나 발급받아요. 이게 비밀번호를 대신하는 긴 문자열이에요. push 할 때 비밀번호 칸에 이 토큰을 넣으면 돼요. 토큰은 권한과 유효기간을 정할 수 있어서, 새더라도 만료시키거나 폐기하기 쉬워요. 그래서 그냥 비밀번호보다 훨씬 안전하죠.
SSH 키 는 열쇠 한 쌍을 쓰는 방식이에요. 터미널에서 한 번만 만들면 돼요.
# SSH 키 한 쌍 만들기 (한 번만, 엔터 몇 번이면 끝)
ssh-keygen -t ed25519 -C "your_email@example.com"
# 만들어진 '공개키' 내용 보기 (이걸 GitHub 에 등록한다)
cat ~/.ssh/id_ed25519.pub
# ssh-ed25519 AAAAC3Nza... your_email@example.com
cat 으로 나온 공개키(ssh-ed25519 ... 한 줄)를 복사해서, GitHub 의 Settings → SSH and GPG keys 에 붙여 넣어 등록해요. 공개키(.pub)만 올리고, 짝꿍인 비밀키는 절대 안 올려요 — 비밀키는 내 컴퓨터에만 있어야 하는 진짜 열쇠니까요. 등록했으면 연결이 되는지 확인해봐요.
# GitHub 와 SSH 연결이 잘 되는지 테스트
ssh -T git@github.com
# Hi USERNAME! You've successfully authenticated, but GitHub does not provide shell access.
Hi USERNAME! 가 뜨면 성공이에요. 이제 내 컴퓨터와 GitHub 가 서로를 믿게 됐어요. 둘 중 뭘 골라도 좋아요 — 처음엔 토큰이 간단하고, 자주 쓰다 보면 SSH 키가 편해요. 현업에선 둘 다 흔히 쓰니 이름만이라도 익혀두세요.
한 가지, 신입들이 자주 묻는 걸 미리 풀어둘게요. "그럼 push 할 때마다 토큰을 매번 쳐야 하나요?" 아니에요. 토큰을 처음 한 번만 넣으면 credential helper 가 그걸 안전하게 기억해줘요. 맥은 키체인, 윈도우는 자격증명 관리자가 보통 기본으로 깔려 있어서 그다음부터는 안 물어봐요.
혹시 매번 물어본다면 git config --global credential.helper 로 한 번 켜두면 돼요. 그러니 토큰 방식도 "처음 한 번만 귀찮은" 거지, 매번 복붙하는 게 아니에요.
🌟 GUI 로는 — SSH 키는 GitHub 웹의 Settings → SSH and GPG keys 페이지에서
New SSH key버튼으로 공개키를 붙여 넣어 등록해요. GitHub Desktop 을 쓰면 토큰·SSH 설정 없이 로그인 한 번으로 인증이 끝나서 가장 간단해요.
💡 한 줄 정리
비밀번호 인증은 폐지됐고, 이제 HTTPS 토큰(간단)이나 SSH 키(한 번 설정하면 편함) 중 하나로 인증한다. SSH 는 ssh-keygen 으로 키 쌍을 만들어 공개키만 GitHub 에 등록한다.
🙋 학생 질문 — "튜터님, 공개키랑 비밀키가 뭐가 다른 거예요? 헷갈려요."
비유로 잡아볼게요. 비밀키는 내 도장(인감), 공개키는 그 도장이 찍힌 모양을 등록해둔 인감증명이라고 생각하면 돼요.
내 컴퓨터에만 있는 비밀키로 "서명" 을 하면, GitHub 는 등록된 공개키로 "이 서명이 그 도장에서 나온 게 맞네" 하고 확인해요. 공개키는 남이 봐도 위조할 수 없어요 — 도장 모양만으로 진짜 도장을 깎아낼 순 없으니까요.
그래서 공개키(.pub)는 GitHub 에 올려도 안전하고, 비밀키는 절대 내 컴퓨터 밖으로 내보내면 안 돼요. 비밀키가 새면 누가 내 도장을 들고 다니는 거랑 같거든요. "공개키만 올린다" 만 기억하면 돼요.
Step 4: "로컬 저장소를 원격에 올리기 — git remote와 첫 push"
준비가 끝났어요. 이제 내 로컬 저장소(지난 시간까지 만든 그 연습 저장소)를 GitHub 에 올려봐요. 순서는 두 단계예요. ① GitHub 에 빈 저장소를 하나 만들고, ② 내 로컬을 거기에 연결해서 올린다.
먼저 GitHub 웹에서 우측 상단 + → New repository 로 빈 저장소를 만들어요. 이름은 로컬 폴더와 같게(예: my-diary) 두면 헷갈리지 않아요. 이때 "README 추가" 같은 옵션은 체크하지 말고 정말 빈 저장소로 만드세요 — 안 그러면 곧 충돌이 나거든요(그 이유는 Step 7 에서 만나요).
저장소를 만들면 GitHub 가 주소를 하나 줘요(git@github.com:USERNAME/my-diary.git 같은 SSH 주소, 또는 https://... 주소). 그 주소를 내 로컬 저장소에 원격으로 등록해요.
# GitHub 가 준 주소를 'origin' 이라는 이름의 원격으로 등록
git remote add origin git@github.com:USERNAME/my-diary.git
# 잘 등록됐는지 확인
git remote -v
# origin git@github.com:USERNAME/my-diary.git (fetch)
# origin git@github.com:USERNAME/my-diary.git (push)
git remote add origin ... 에서 origin 은 그 원격에 붙인 별명이에요. 매번 긴 주소를 치는 대신 origin 이라고 부르겠다는 거죠. 관습적으로 "내 주된 원격" 의 이름을 origin 으로 둬요. 이제 올려봐요.
# 로컬 main 을 원격 origin 으로 처음 올리기
git push -u origin main
# Enumerating objects: 5, done.
# ...
# To github.com:USERNAME/my-diary.git
# * [new branch] main -> main
# branch 'main' set up to track 'origin/main'.
처음엔 원격(origin)이 비어 있다. git push 로 로컬 역사를 통째로 올린다.
올리기 전 올린 뒤
로컬 : A───B───C (main) 로컬 : A───B───C (main)
원격 : (비어 있음) 원격 : A───B───C (origin/main)
→ push 후 로컬과 원격이 똑같아진다.
-u 옵션이 한 일을 봐요. 마지막 줄 set up to track 'origin/main' 이 그거예요. 로컬 main 과 원격 origin/main 을 짝지어 추적하게 묶어준 거죠. 이 -u 는 처음 한 번만 붙이면 돼요. 그다음부턴 그냥 git push 만 쳐도 git 이 "아, main 은 origin 으로 올리는 거였지" 하고 알아들어요.
🌟 GUI 로는 — GitHub Desktop 에서 로컬 저장소를 열고 "Publish repository" 버튼을 누르면, 위의
remote add+push -u를 한 번에 해줘요. VS Code 라면 소스 제어 패널의 "Publish Branch" 버튼이 같은 역할이에요.
💡 한 줄 정리
로컬을 원격에 올리려면 git remote add origin <주소> 로 원격을 등록하고, git push -u origin main 으로 올린다. -u 는 로컬 main 과 origin/main 을 추적 연결해, 다음부턴 git push 만 쳐도 되게 한다.
🙋 학생 질문 — "튜터님, 왜 하필 이름이 'origin' 이에요? 다른 이름이면 안 돼요?"
origin 은 법으로 정해진 이름이 아니라 그냥 관습이에요. 원하면 git remote add 회사서버 ... 처럼 아무 이름이나 붙일 수 있어요.
그런데 거의 모든 사람이 "내 주된 원격" 을 origin 이라고 불러요. git clone(Step 5)으로 저장소를 받아오면 git 이 자동으로 그 원격을 origin 이라고 이름 붙여주기도 하고요. 그래서 origin 이라고 하면 다들 "아, 주된 원격이구나" 하고 알아들어요.
나중에 원격이 둘 이상 필요해지면(예: 오픈소스 기여 때) upstream 같은 두 번째 이름도 등장해요. 그건 A-6 에서 만나요. 지금은 "주된 원격 = origin" 만 기억하면 충분해요.
Step 5: "원격 저장소를 통째로 받아오기 — git clone"
올리는 걸 배웠으니, 이번엔 반대로 받아오는 걸 배워요. 인터넷에 있는 원격 저장소를 통째로 내 컴퓨터로 복제하는 명령이 git clone 이에요.
이게 필요한 상황은 두 가지예요. 하나는 내가 올린 저장소를 다른 컴퓨터에서 받을 때(예: 집 데스크탑에서 회사 노트북으로). 또 하나는 남이 공개한 저장소를 받아올 때(예: 오픈소스 프로젝트, 동료의 저장소). 둘 다 명령은 똑같아요.
# 원격 저장소를 통째로 내 컴퓨터에 복제 (SSH 주소)
git clone git@github.com:USERNAME/my-diary.git
# HTTPS 주소로도 똑같이 받을 수 있다
# git clone https://github.com/USERNAME/my-diary.git
# 실행하면 'my-diary' 폴더가 생기고 그 안에 모든 게 들어온다
# Cloning into 'my-diary'...
# remote: Enumerating objects: 5, done.
# Receiving objects: 100% (5/5), done.
git clone 한 방이 사실 꽤 많은 일을 한 번에 해줘요. ① my-diary 폴더를 새로 만들고, ② 그 안에 git init 하고, ③ 원격을 origin 으로 등록하고, ④ 원격의 모든 커밋·브랜치 역사를 받아오고, ⑤ 최신 파일들을 작업 폴더에 펼쳐줘요. Step 4 에서 우리가 손으로 했던 remote add 까지 자동으로 다 해주는 거죠.
그래서 clone 받은 폴더는 받자마자 바로 쓸 수 있어요. cd my-diary 로 들어가서 git log 를 쳐보면, 원격에 있던 커밋 역사가 그대로 들어와 있는 걸 볼 수 있어요. 마치 그 저장소를 처음부터 내 컴퓨터에서 만든 것처럼요.
git 을 '분산' 버전 관리라고 부르는 이유가 바로 여기 있어요. clone 은 원격의 일부가 아니라 전체 역사를 통째로 내 컴퓨터에 복제해요. 그래서 모두의 컴퓨터에 완전한 저장소가 하나씩 있는 셈이고, GitHub 가 잠깐 멈춰도 각자 작업은 계속 굴러가요. 역사가 중앙 서버 한 곳에만 있던 옛 방식과 다른 점이죠.
🌟 GUI 로는 — GitHub 저장소 페이지의 초록색 "Code" 버튼을 누르면 주소가 나와요. GitHub Desktop 의 "Clone repository", VS Code 의 "Git: Clone" 도 같은 주소를 넣으면 똑같이 받아와요.
💡 한 줄 정리
git clone <주소> 는 원격 저장소를 통째로 복제한다. 폴더 생성 + init + origin 등록 + 역사 전체 받아오기 + 파일 펼치기를 한 번에 해줘서, 받자마자 바로 쓸 수 있다.
🙋 학생 질문 — "튜터님, clone 이랑 그냥 'Download ZIP' 받는 거랑 뭐가 달라요?"
GitHub 페이지에 보면 ZIP 으로 다운로드하는 버튼도 있죠. 그런데 그건 지금 이 순간의 파일들만 받는 거예요. git 의 역사가 빠져 있어요.
ZIP 으로 받으면 .git 폴더가 없어서, git log 도 안 되고 커밋도 push 도 못 해요. 그냥 "현재 파일 묶음" 일 뿐이죠. 반면 git clone 은 .git 저장소를 통째로 받아오니까, 받은 순간부터 커밋하고 브랜치 따고 push 할 수 있어요.
그래서 "코드를 구경만 할 거면 ZIP, 함께 작업할 거면 clone" 이에요. 우리가 협업하려는 거니까 당연히 clone 이죠.
Step 6: "원격의 변화를 가져오기 — fetch, pull, 그리고 origin/main"
이제 핵심으로 들어가요. 협업을 하면 원격이 나보다 앞서 가는 일이 생겨요. 동료가 자기 작업을 원격에 올리면, 원격엔 내가 아직 모르는 새 커밋이 쌓이죠. 그 변화를 내 컴퓨터로 가져오는 방법이 두 가지예요 — git fetch 와 git pull. 이 둘의 차이가 오늘 가장 중요한 대목이에요.
먼저 한 가지 개념을 잡고 가요. 원격 추적 브랜치(origin/main) 라는 거예요. 내 컴퓨터에는 사실 main 이 두 개처럼 존재해요. 하나는 내가 직접 커밋하는 로컬 main, 또 하나는 "내가 마지막으로 확인한 원격의 main 은 여기까지였다" 를 기억하는 origin/main 이에요. origin/main 은 원격의 상태를 비춰주는 거울 같은 거예요.
동료가 원격에 새 커밋 D 를 올렸다. 내 로컬은 아직 C 까지만 안다.
원격 : A───B───C───D (origin 의 진짜 최신)
로컬 : A───B───C (내 main = origin/main, 아직 C 까지만 앎)
git fetch → origin/main 만 D 까지 갱신 (내 작업 폴더는 그대로)
git pull → fetch + 내 로컬 main 까지 D 로 합치기 (한 번에)
이제 두 명령을 봐요.
# ① fetch — 원격에 무슨 변화가 있는지 '가져오기만' 한다 (합치진 않음)
git fetch origin
# 원격 추적 브랜치 목록을 보면 origin/main 이 갱신돼 있다
git branch -r
# origin/main
# ② pull — 가져온 변화를 내 현재 브랜치에 '합치기까지' 한 번에
git pull
# Updating c3a1f9e..d4b2c01
# Fast-forward
# diary.txt | 2 ++
# 1 file changed, 2 insertions(+)
핵심은 이거예요. git fetch 는 "원격에 뭐 새로 올라왔나 구경만" 하고, git pull 은 "구경하고 내 코드에 합치기까지" 해요. 사실 git pull 은 속을 까보면 git fetch 다음에 git merge(A-2 에서 배운 그 병합!)를 자동으로 이어 붙인 거예요. 즉 pull = fetch + merge 죠.
그럼 언제 뭘 쓸까요? 동료가 뭘 바꿨는지 먼저 확인하고 신중하게 합치고 싶으면 fetch 로 보고 나서 merge, 그냥 바로 받아 합쳐도 되면 pull 한 방이에요. 입문 단계에선 pull 만으로 충분하지만, "pull 은 사실 fetch + merge 다" 라는 걸 알아두면 나중에 충돌이 났을 때 당황하지 않아요.
🌟 GUI 로는 — GitHub Desktop 의
Fetch origin버튼이git fetch, 원격이 앞서 있을 때 라벨이 바뀌는Pull origin버튼이git pull이에요. VS Code 도 소스 제어 패널의 동기화 아이콘으로 같은 일을 해요.
💡 한 줄 정리
origin/main 은 "내가 마지막으로 본 원격 상태" 를 기억하는 원격 추적 브랜치다. git fetch 는 원격 변화를 가져오기만 하고, git pull 은 fetch + merge 로 내 브랜치에 합치기까지 한다.
🙋 학생 질문 — "튜터님, 그럼 그냥 항상 pull 쓰면 편하지 않아요? fetch 는 왜 따로 있어요?"
맞아요, 평소엔 pull 한 방이 편해요. 그런데 pull 은 받자마자 자동으로 내 코드에 합쳐버려요. 가끔은 이게 곤란해요.
예를 들어 동료가 원격에 큰 변경을 올렸는데, 나도 마침 같은 파일을 고치는 중이라고 해봐요. 바로 pull 하면 곧바로 충돌이 터지면서 작업이 엉킬 수 있어요. 이럴 땐 fetch 로 "동료가 뭘 바꿨는지" 먼저 구경하고, 마음의 준비를 한 뒤에 합치는 게 안전하죠.
그래서 fetch 는 "합치기 전에 먼저 확인하는" 신중한 길이에요. 처음엔 pull 로 충분하고, 협업이 익숙해지면 "큰 변경 전엔 fetch 로 먼저 본다" 를 자연스럽게 쓰게 될 거예요.
Step 7: "로컬과 원격이 어긋나지 않게 — 동기화 한 장과 push가 거부될 때"
마지막으로, 협업하다 가장 자주 만나는 순간 하나를 짚고 갈게요. 신입들이 여기서 제일 식은땀 흘려요. 바로 내 push 가 거부될 때예요. 미리 말해둘게요 — 이건 git 이 여러분을 혼내는 게 아니라, 지켜주는 거예요.
상황은 이래요. 내가 커밋을 만드는 사이, 동료가 원격에 자기 커밋을 먼저 올렸어요. 그래서 원격이 내 로컬보다 앞서 있죠. 이 상태에서 내가 그냥 git push 를 하면 git 이 막아요.
내가 X 를 만드는 사이, 동료가 원격에 D 를 먼저 올렸다.
이 상태에서 내가 X 를 바로 push 하면?
원격 : A───B───C───D (동료의 D 가 이미 올라가 있음)
로컬 : A───B───C───X (내 새 커밋 X)
git push ──▶ ! [rejected] (원격이 더 앞서 있어 거부됨)
→ pull 로 D 를 먼저 받아 합친 뒤, 다시 push 하면 안전하게 올라간다.
# 동료의 D 를 모른 채 그냥 올리면
git push
# ! [rejected] main -> main (fetch first)
# error: failed to push some refs to 'github.com:USERNAME/my-diary.git'
# hint: Updates were rejected because the remote contains work that you do
# hint: not have locally. ... integrate the remote changes (e.g. 'git pull')
# 안내대로, 먼저 원격 변화를 받아 합치고
git pull
# 다시 올리면 이번엔 성공
git push
왜 막았을까요? 만약 git 이 그냥 올리게 뒀다면, 내 X 가 동료의 D 를 덮어써서 동료 작업이 통째로 사라질 수도 있어요. 신입이 현업에서 제일 무서워하는 사고가 바로 이거예요 — "내가 동료 코드를 날렸다." git 은 그걸 막으려고 "잠깐, 원격에 네가 모르는 게 있어. 먼저 받아서 합쳐(pull) 와" 하고 멈춰준 거예요.
그러니 ! [rejected] 메시지를 보면 당황하지 마세요. 거의 항상 답은 "git pull 먼저, 그다음 git push" 예요. pull 로 동료의 D 를 받아 내 X 와 합치면(필요하면 충돌 해결하고), 그다음 push 는 깔끔하게 올라가요. 이 리듬 하나만 익혀두면 협업에서 사고 칠 일이 확 줄어요.
협업의 기본 리듬 — 어긋나면 받아서 합치고, 그다음 올린다
작업 시작 전 : git pull (원격 최신부터 받기)
작업 후 올리기 : git push (성공)
혹시 거부되면 : git pull → git push (받아 합치고 다시 올리기)
⚠️
git pull했더니 또 멈출 때가 있어요. 나도 로컬에 새 커밋이 있고 원격에도 동료의 새 커밋이 있어 양쪽이 갈라진 상황이면,git pull이 "divergent branches" 어쩌고 하며 멈추거나, 자동으로 병합 커밋(Merge branch ...)을 만들고 편집기를 띄우기도 해요. 혼나는 게 아니라 정상이에요 — git 이 "받아온 걸 merge 로 합칠까, 한 줄로 펴서 합칠까" 를 묻는 거예요. 입문 땐git config --global pull.rebase false(merge 방식) 로 한 번 정해두면 그다음부턴 안 물어봐요. 역사를 직선으로 유지하고 싶을 땐git pull --rebase라는 길도 있는데, 그 "rebase" 가 뭔지는 A-7 에서 차근차근 배워요.
💡 한 줄 정리
push 가 ! [rejected] 로 거부되는 건 원격에 내가 모르는 동료 작업이 있어서다. 동료 작업을 덮어쓰지 말라는 안전장치이니, git pull 로 먼저 받아 합친 뒤 다시 git push 하면 된다.
🙋 학생 질문 — "튜터님, 그냥 강제로 밀어붙이는 방법은 없어요? 거부되면 답답한데요."
있어요. git push --force 라는 게 있어서 거부를 무시하고 밀어붙일 수 있어요. 그런데 이건 입문 단계에서 절대 쓰면 안 되는 명령이에요.
--force 로 밀면 방금 말한 그 사고가 진짜로 나요 — 원격에 있던 동료의 D 가 흔적도 없이 사라지고 내 X 로 덮여요. 동료가 한참 작업한 게 통째로 날아가는 거죠. 현업에서 신입이 --force 잘못 썼다가 팀 전체가 멈추는 사고가 실제로 종종 생겨요.
그래서 거부됐을 땐 강제로 미는 게 아니라 pull 로 받아 합치는 게 정답이에요. (force 를 그나마 안전하게 쓰는 방법과 "황금률" 은 한참 뒤 A-7 에서 따로 다뤄요. 지금은 "force 는 위험하니 pull 로 푼다" 만 기억하세요.)
마무리
오늘 우리는 git 의 세계를 내 컴퓨터 밖으로 넓혔어요. GitHub 에 계정을 만들고 2FA 로 지키고, 내 컴퓨터를 인증으로 연결하고, 저장소를 올리고(push) 받아오고(clone), 원격의 변화를 가져오고(fetch·pull), push 가 거부될 때 침착하게 푸는 법까지요. 이제 여러분 코드는 인터넷에 안전하게 올라가 있고, 어디서든 받아 쓸 수 있어요. 혼자 다루던 git 이 비로소 함께 쓰는 도구가 된 거예요.
오늘 배운 핵심 세 가지
- 💡 하나 — 원격은 공유 저장소, 이름은 origin. GitHub 는 그 원격을 호스팅하는 서비스다. 계정은 2FA 필수, 인증은 HTTPS 토큰 또는 SSH 키 중 하나로 한다.
- 💡 둘 — 올리는 push, 받는 clone·pull.
git remote add origin+git push -u origin main으로 올리고,git clone으로 통째로 받는다.git pull은fetch + merge다. - 💡 셋 — 거부는 안전장치다. push 가
! [rejected]로 막히는 건 동료 작업을 덮어쓰지 말라는 뜻이다.git pull로 받아 합친 뒤 다시git push가 협업의 기본 리듬이다.
다음 시간 예고
오늘 우리는 원격에 자유롭게 올리고 받았어요. 그런데 한 가지, 계속 마음에 걸리는 게 있죠 — 우리는 main 에 곧바로 push 했어요. 혼자 쓰는 연습 저장소라 괜찮았지만, 여럿이 일하는 진짜 프로젝트에서도 다들 main 에 직접 밀어 넣으면 어떻게 될까요? 누구 코드가 맞는지 검토도 없이 섞이고, 오늘 본 그 거부와 충돌이 매일 터질 거예요.
그래서 팀은 main 에 바로 올리지 않아요. 대신 "내가 이렇게 바꿨는데, 합쳐도 될까요?" 하고 제안서를 내고 동료의 리뷰를 받은 뒤 합쳐요. 그 제안서가 바로 Pull Request(PR) 예요. 다음 시간엔 이 PR 을 중심으로, 브랜치 → 리뷰 → 병합으로 이어지는 진짜 협업의 안전장치를 배워요. 오늘 익힌 push·pull 이 그 바탕이 돼요.
과제
오늘 배운 건 눈으로만 보면 절대 안 익어요. 꼭 여러분 GitHub 계정과 연습 저장소에서 직접 올리고 받아보세요. 특히 일부러 push 를 거부당해보고 pull 로 푸는 걸 손으로 해보면, "아 이래서 막아준 거구나" 하는 게 몸에 새겨져요.
[기초] 내 연습 저장소를 GitHub에 처음 올리기
GitHub 에 가입하고 2FA 를 설정한 뒤(복구 코드는 꼭 따로 저장!), 빈 저장소를 하나 만드세요(README 추가 옵션은 체크하지 마세요). 그리고 지난 시간까지 만든 로컬 연습 저장소를 그 원격에 연결해 올려보세요.
git remote add origin <주소> 로 원격을 등록하고, git remote -v 로 잘 등록됐는지 확인한 뒤, git push -u origin main 으로 올리세요. GitHub 페이지를 새로고침해서 내 커밋들이 인터넷에 올라온 걸 직접 확인하세요. 마지막으로, -u 옵션이 무슨 일을 했는지 push 결과 메시지에서 찾아 한 문장으로 적어보세요.
[응용] 다른 폴더에 clone 해서 양쪽을 오가기
방금 올린 저장소를 다른 폴더에 git clone 으로 받아보세요(마치 다른 컴퓨터인 것처럼). 받은 폴더에서 git log --oneline 을 쳐서, 원래 저장소의 커밋 역사가 그대로 들어왔는지 확인하세요.
이제 clone 받은 쪽에서 파일을 한 줄 고쳐 커밋하고 git push 하세요. 그다음 원래 저장소 폴더로 가서 git pull 로 그 변경을 받아오세요. 양쪽 폴더의 git log 가 똑같아지는 걸 확인하고, "clone 받은 쪽과 원래 쪽이 같은 원격을 통해 어떻게 이어졌는지" 를 한두 문장으로 적어보세요.
[심화] 팀 상황을 상상하며 push 거부를 직접 만들어 풀기
이런 팀 상황을 떠올려보세요. "내가 작업하는 동안 동료가 원격에 먼저 커밋을 올렸다. 그걸 모르고 내가 push 하면 거부당한다." 이 상황을 일부러 만들어볼게요.
위 [응용]의 두 폴더(원래 폴더 = 나, clone 폴더 = 동료라고 치고)를 이용하세요. 먼저 "동료" 폴더에서 한 줄 고쳐 커밋하고 push 하세요. 그다음 "나" 폴더에서는 그 변경을 받지 않은 채 다른 줄을 고쳐 커밋하고 git push 를 시도하세요. ! [rejected] 가 뜨면 성공이에요(=거부를 재현했어요). 이제 git pull 로 동료 변경을 받아 합친 뒤 다시 git push 해서 풀어내세요. 그리고 "git 이 왜 처음 push 를 거부했는지, 만약 거부 없이 그냥 올라갔다면 무슨 일이 생겼을지" 를 한두 문장으로 적어보세요.
생각해볼 주제
1. fetch와 pull — 왜 굳이 둘로 나눠놨을까
오늘 우리는 git pull 이 사실 git fetch + git merge 라는 걸 봤어요. 그러면 그냥 pull 하나만 있어도 될 텐데, git 은 왜 fetch 를 따로 떼어 뒀을까요? "가져오는 것" 과 "합치는 것" 을 분리할 수 있다는 게 협업에서 어떤 안전을 주는지 생각해보세요. 동료가 큰 변경을 올린 걸 모르고 무작정 합쳤을 때 벌어질 수 있는 일과, fetch 로 먼저 들여다본 뒤 합칠 때의 차이를 떠올리며 풀어보세요.
2. push가 거부되는 게 왜 "고마운" 일일까
! [rejected] 메시지는 처음 보면 당황스럽고 답답해요. 그런데 오늘 우리는 이게 사실 안전장치라고 배웠죠. 만약 git 이 거부 없이 항상 push 를 받아준다면, 여럿이 같은 저장소를 쓸 때 무슨 일이 반복해서 벌어질까요? "덮어쓰기를 막는다" 는 이 거부의 본질을, A-2 에서 배운 충돌(conflict)과 엮어 생각해보세요. 둘 다 "사람이 직접 확인하고 합쳐라" 라고 멈춰주는 장치라는 공통점이 보일 거예요.
3. 비밀번호 인증은 왜 사라졌을까
예전엔 push 할 때 GitHub 비밀번호를 그냥 입력했는데, 지금은 그게 폐지되고 토큰이나 SSH 키를 써요. 단순히 "더 안전해서" 를 넘어, 비밀번호 자체가 가진 약점이 뭔지 생각해보세요. 비밀번호 하나가 새면 어디까지 뚫리는지, 반면 토큰은 "권한과 유효기간을 정할 수 있다" 는 게 왜 그렇게 큰 차이를 만드는지 풀어보세요. 오늘 배운 2FA 가 "아는 것 + 가진 것" 두 가지를 요구한 것과도 통하는 이야기예요.
✅ 예시 답안정답 보기
이 문서는 A-4 「GitHub와 원격 다루기」의 과제와 생각해볼 주제에 대한 예시답안입니다. 정답을 외우는 용도가 아니라, 직접 손으로 올리고 받아본 결과를 어떻게 정리하고 설명하는지 흐름을 참고하는 용도로 보세요. 특히 원격은 직접 올리고, 받고, 일부러 거부당해봐야 감이 잡힙니다. 답안을 보기 전에 꼭 먼저 여러분 GitHub 계정과 연습 저장소에서 직접 push 하고 pull 해보세요.
과제 예시답안
🎯 [과제 1 예시답안] 내 연습 저장소를 GitHub에 처음 올리기
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| 계정 + 2FA | 25% | 가입 후 2FA 를 설정하고 복구 코드를 따로 저장했는가 |
| 원격 등록 | 30% | git remote add origin <주소> + git remote -v 로 등록을 확인했는가 |
| 첫 push | 25% | git push -u origin main 으로 올리고 GitHub 페이지에서 커밋을 확인했는가 |
| -u 설명 | 20% | -u 가 한 일(추적 연결)을 결과 메시지에서 찾아 설명했는가 |
풀이 예시
GitHub 에서 빈 저장소를 만든 뒤(README 추가 옵션은 체크하지 않음), 로컬에서 원격을 등록해요.
git remote add origin git@github.com:USERNAME/my-diary.git
git remote -v
# origin git@github.com:USERNAME/my-diary.git (fetch)
# origin git@github.com:USERNAME/my-diary.git (push)
이제 처음으로 올려요.
git push -u origin main
# Enumerating objects: 8, done.
# Counting objects: 100% (8/8), done.
# Writing objects: 100% (8/8), 720 bytes | 720.00 KiB/s, done.
# To github.com:USERNAME/my-diary.git
# * [new branch] main -> main
# branch 'main' set up to track 'origin/main'. ← -u 가 한 일
올린 뒤 GitHub 저장소 페이지를 새로고침하면, 그동안 로컬에서 쌓은 커밋들이 인터넷에 올라온 게 보여요.
-u 옵션에 대해: 마지막 줄 branch 'main' set up to track 'origin/main' 이 -u 가 한 일이에요. 로컬 main 과 원격 origin/main 을 짝지어 추적하게 묶어준 거죠. 덕분에 다음부터는 git push / git pull 만 쳐도 git 이 알아서 origin 으로 올리고 받아요.
💡 튜터의 한마디 — 첫 push 에서 -u 를 한 번 붙여두면 그다음이 편해져요. 처음엔 이 옵션이 뭔지 안 보이다가, 두 번째 push 부터 git push 만 쳐도 동작하는 걸 보면 "아, 그때 그 -u 덕분이구나" 하고 와닿을 거예요. 그리고 2FA 복구 코드 저장을 건너뛰지 않은 분, 잘하셨어요 — 그 1분이 휴대폰 잃어버렸을 때 여러분을 구해줘요.
🎯 [과제 2 예시답안] 다른 폴더에 clone 해서 양쪽을 오가기
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| clone 받기 | 30% | 다른 폴더에 git clone 으로 받고 git log 로 역사가 그대로 온 걸 확인했는가 |
| 복제본에서 push | 35% | clone 받은 쪽에서 고쳐 커밋하고 git push 했는가 |
| 원래 쪽에서 pull | 35% | 원래 폴더에서 git pull 로 받아 양쪽 git log 가 같아진 걸 확인했는가 |
풀이 예시
원격 저장소를 다른 폴더에 통째로 받아와요(마치 다른 컴퓨터인 것처럼).
cd ~/practice
git clone git@github.com:USERNAME/my-diary.git my-diary-clone
# Cloning into 'my-diary-clone'...
# remote: Enumerating objects: 8, done.
# Receiving objects: 100% (8/8), done.
cd my-diary-clone
git log --oneline
# def5678 (HEAD -> main, origin/main) 셋째 줄 추가 ← 원래 역사가 그대로
# abc1234 둘째 줄 추가
# 0f9e8d7 첫 줄 추가
복제본에서 한 줄 고쳐 커밋하고 올려요.
echo "복제본에서 추가한 줄" >> diary.txt
git add diary.txt
git commit -m "복제본에서 한 줄 추가"
git push
# To github.com:USERNAME/my-diary.git
# def5678..a1b2c3d main -> main
이제 원래 폴더로 가서 그 변경을 받아와요.
cd ~/my-diary
git pull
# Updating def5678..a1b2c3d
# Fast-forward
# diary.txt | 1 +
# 1 file changed, 1 insertion(+)
git log --oneline
# a1b2c3d (HEAD -> main, origin/main) 복제본에서 한 줄 추가 ← 양쪽이 같아짐
# def5678 셋째 줄 추가
양쪽이 이어진 방식에 대해: clone 받은 쪽과 원래 쪽은 서로를 직접 모르지만, 둘 다 같은 원격(origin)을 바라보고 있어요. 한쪽이 origin 에 push 하면, 다른 쪽은 origin 에서 pull 로 받아오죠. 원격이 둘 사이의 만남의 장소가 되는 거예요.
💡 튜터의 한마디 — 이 과제가 사실 협업의 축소판이에요. "복제본 = 동료 컴퓨터" 라고 바꿔 읽으면, 동료가 push 한 걸 내가 pull 로 받는 그림 그대로거든요. 혼자서 두 폴더를 오가며 push·pull 의 리듬을 익혀두면, 진짜 동료와 일할 때 똑같이 하면 돼요. 원격을 가운데 두고 양쪽이 만난다는 그림을 머릿속에 그려두세요.
🎯 [과제 3 예시답안] 팀 상황을 상상하며 push 거부를 직접 만들어 풀기
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| 거부 재현 | 35% | 동료 변경을 안 받은 채 push 해 ! [rejected] 를 띄웠는가 |
| pull 로 합치기 | 35% | git pull 로 동료 변경을 받아 합치고 다시 git push 로 성공했는가 |
| 거부 이유 설명 | 30% | 왜 거부됐는지, 거부 없이 올라갔다면 무슨 일이 생길지 적었는가 |
풀이 예시
두 폴더를 "동료(clone 폴더)" 와 "나(원래 폴더)" 로 나눠 상황을 만들어요. 먼저 동료가 원격에 먼저 올려요.
# [동료 역할] clone 폴더에서 먼저 올리기
cd ~/practice/my-diary-clone
echo "동료가 추가한 줄" >> diary.txt
git add diary.txt && git commit -m "동료: 한 줄 추가"
git push
# a1b2c3d..e4f5a6b main -> main ← 동료의 커밋이 원격에 올라감
이제 나는 그 변경을 모른 채 다른 줄을 고쳐 올려봐요.
# [나 역할] 원래 폴더에서 (동료 변경을 안 받은 상태)
cd ~/my-diary
echo "내가 추가한 다른 줄" >> diary.txt
git add diary.txt && git commit -m "나: 다른 줄 추가"
git push
# ! [rejected] main -> main (fetch first)
# error: failed to push some refs to 'github.com:USERNAME/my-diary.git'
# hint: Updates were rejected because the remote contains work that you do
# hint: not have locally. ... integrate the remote changes (e.g. 'git pull')
! [rejected] 가 떴으면 거부를 제대로 재현한 거예요. 안내대로 먼저 받아 합치고 다시 올려요.
git pull
# Merge made by the 'ort' strategy.
# diary.txt | 1 +
# (서로 다른 줄을 고쳤다면 자동 병합 / 같은 줄이면 충돌 → A-2 방식으로 해결 후 커밋)
git push
# e4f5a6b..f7a8b9c main -> main ← 이번엔 성공
거부 이유에 대해: 내가 push 하려는 순간, 원격엔 내가 모르는 동료의 커밋(e4f5a6b)이 이미 있었어요. git 이 그냥 올리게 뒀다면 내 커밋이 동료의 커밋을 덮어써서 동료 작업이 통째로 사라질 수 있었죠. git 은 그걸 막으려고 "원격에 네가 모르는 게 있으니 먼저 받아 합쳐라" 하고 거부한 거예요.
💡 튜터의 한마디 — 이 과제의 진짜 목적은 ! [rejected] 를 무섭지 않게 만드는 거예요. 처음 이 빨간 메시지를 보면 다들 당황하는데, 직접 만들어보고 pull → push 로 풀어보면 "아, 이건 혼나는 게 아니라 지켜주는 거구나" 하고 몸으로 알게 돼요. 현업에서 이 리듬 하나면 "동료 코드 날렸다" 는 최악의 사고를 거의 다 피할 수 있어요. 참고로 거부됐을 때 --force 로 밀어붙이고 싶은 유혹이 들 텐데, 그건 동료 커밋을 진짜로 날리는 길이라 지금은 절대 쓰지 마세요. 답은 언제나 pull 먼저예요.
생각해볼 주제 예시답안
🤔 [생각해볼 주제 1] fetch와 pull — 왜 굳이 둘로 나눠놨을까
문제 상황 요약
git pull 은 사실 git fetch + git merge 다. 그러면 pull 하나로 충분할 텐데, git 은 왜 fetch 를 따로 떼어 뒀을까. "가져오는 것" 과 "합치는 것" 을 분리할 수 있다는 게 협업에서 어떤 안전을 주는지, 무작정 합쳤을 때와 먼저 들여다본 뒤 합칠 때의 차이를 풀어보는 주제다.
튜터의 가이드 및 해설
핵심은 "합치는 결정을 내 손에 남겨두느냐" 의 차이다.
git pull 은 편하다. 원격의 변화를 받아 곧바로 내 코드에 합쳐주니, 평소엔 이 한 방이면 충분하다. 그런데 "곧바로 합친다" 는 바로 그 점이 가끔 위험해진다. 내가 합칠 준비가 됐는지, 원격에 뭐가 올라왔는지 확인하기도 전에 합병이 끝나버리기 때문이다.
git fetch 는 이 과정을 둘로 쪼갠다. 먼저 원격의 새 커밋을 origin/main 으로 가져오기만 하고, 내 작업 폴더는 건드리지 않는다. 그러면 나는 git log origin/main 으로 "동료가 무엇을 바꿨는지" 를 차분히 들여다볼 수 있다. 큰 변경이 와 있으면 마음의 준비를 하고, 내가 고치던 걸 먼저 커밋해 안전하게 만든 뒤, 그때 직접 merge 한다. 합치는 시점과 방식을 내가 정하는 것이다.
이 분리가 빛나는 상황이 있다. 내가 마침 같은 파일을 한참 고치는 중인데 동료도 그 파일을 크게 바꿔 올렸다고 해보자. 무심코 pull 하면 곧바로 충돌이 터지며 작업이 엉킨다. 반면 fetch 로 먼저 봤다면, "어 이 파일이 바뀌었네, 내 작업부터 정리하고 합치자" 하고 순서를 조절할 수 있다. 사고가 터진 뒤 수습하는 것과, 터지기 전에 길을 고르는 것의 차이다.
그래서 둘로 나뉜 건 불편이 아니라 선택지다. 평소엔 pull 의 편함을 쓰고, 신중해야 할 땐 fetch 로 먼저 보는 길을 연다. 도구가 "빠른 길" 과 "신중한 길" 을 모두 열어두는 건, 쓰는 사람이 상황에 맞게 고르라는 배려다.
💡 핵심을 한마디로
pull은 받아서 곧바로 합치는 빠른 길,fetch는 받아만 두고 합치는 시점을 내가 정하는 신중한 길이다. 같은 파일을 동시에 고치는 상황에선 fetch 로 먼저 들여다본 뒤 합치는 게 충돌 사고를 막는다. 둘로 나뉜 건 불편이 아니라, 빠름과 신중함을 골라 쓰라는 선택지다.
🤔 [생각해볼 주제 2] push가 거부되는 게 왜 "고마운" 일일까
문제 상황 요약
! [rejected] 메시지는 처음 보면 당황스럽고 답답하다. 그런데 이건 사실 안전장치다. git 이 거부 없이 항상 push 를 받아준다면 여럿이 같은 저장소를 쓸 때 무슨 일이 반복될지, A-2 의 충돌(conflict)과 엮어 이 거부의 본질을 풀어보는 주제다.
튜터의 가이드 및 해설
핵심은 "거부는 덮어쓰기를 막는 마지막 방어선" 이라는 점이다.
상상해보자. git 이 거부를 안 하고 push 를 무조건 받아준다면 어떻게 될까. 내가 커밋을 만드는 사이 동료가 원격에 먼저 자기 작업을 올렸는데, 내가 그걸 모르고 push 하면 내 커밋이 동료의 커밋을 그대로 덮어쓴다. 동료가 한 시간 작업한 게 흔적도 없이 사라지고, 동료는 다음에 pull 할 때까지 그 사실조차 모른다. 이게 거부가 없는 세상이다. 여럿이 쓸수록 "누군가의 작업이 조용히 사라지는" 사고가 끝없이 반복된다.
git 의 거부는 이걸 정면으로 막는다. "원격에 네가 모르는 작업이 있다. 그걸 덮어쓰기 전에 먼저 받아서 합쳐라(pull)" 하고 멈춰 세운다. 덕분에 나는 동료의 작업을 받아 내 것과 나란히 합친 뒤에야 올릴 수 있다. 아무도 모르게 사라지는 작업이 없어지는 것이다.
여기서 A-2 의 충돌과 묶어보면 둘의 공통점이 선명해진다. 충돌도, 거부도 모두 "기계가 함부로 결정하지 않고, 사람에게 판단을 넘기는" 장치다. 충돌은 "같은 줄을 둘이 다르게 고쳤으니 어느 쪽이 맞는지 네가 정해라" 하고 멈추고, 거부는 "원격이 앞서 있으니 어떻게 합칠지 네가 정해라" 하고 멈춘다. 둘 다 git 이 똑똑한 척 자동으로 밀어붙이는 대신, 정보를 잃을 위험이 있는 순간에 사람을 부른다.
그래서 ! [rejected] 는 답답한 훼방이 아니라 고마운 신호다. 그 빨간 메시지가 뜬 덕분에, 나는 동료의 작업을 날리는 최악을 피하고 안전하게 합칠 기회를 얻는다. 거부를 만났을 때 짜증 대신 "막아줘서 다행" 이라고 읽을 수 있다면, 협업이 한결 단단해진다.
💡 핵심을 한마디로
push 거부가 없다면 내 커밋이 동료의 커밋을 조용히 덮어써 작업이 사라진다. 거부는 그 덮어쓰기를 막는 마지막 방어선이다. 충돌과 마찬가지로 "정보를 잃을 위험이 있는 순간 기계가 멈추고 사람에게 판단을 넘기는" 장치라, 답답한 훼방이 아니라 고마운 신호다.
🤔 [생각해볼 주제 3] 비밀번호 인증은 왜 사라졌을까
문제 상황 요약
예전엔 push 할 때 GitHub 비밀번호를 그냥 입력했는데, 지금은 폐지되고 토큰이나 SSH 키를 쓴다. 단순히 "더 안전해서" 를 넘어 비밀번호 자체가 가진 약점이 무엇인지, 토큰의 "권한과 유효기간" 이 왜 큰 차이를 만드는지, 2FA 의 "아는 것 + 가진 것" 과 엮어 풀어보는 주제다.
튜터의 가이드 및 해설
핵심은 "비밀번호는 전부 아니면 전무(all-or-nothing)인 열쇠" 라는 점이다.
비밀번호의 약점은 세 가지로 모인다. 첫째, 권한을 쪼갤 수 없다. 비밀번호 하나는 내 계정의 모든 것을 연다. 저장소 읽기만 시키고 싶어도, 비밀번호를 넘기는 순간 삭제·설정 변경까지 전부 열린다. 둘째, 유효기간이 없다. 한 번 샌 비밀번호는 내가 바꾸기 전까지 영원히 유효하다. 셋째, 여기저기 재사용된다. 사람은 같은 비밀번호를 여러 서비스에 쓰는 경향이 있어서, 한 곳이 뚫리면 GitHub 까지 함께 뚫린다.
토큰은 이 약점들을 정면으로 고친다. 토큰은 권한을 좁게 정할 수 있다 — "이 토큰은 이 저장소 읽기만" 처럼. 또 유효기간을 붙일 수 있어서, 90일 뒤 자동 만료되게 하거나 의심되면 그 토큰만 폐기할 수 있다. 비밀번호처럼 "새면 계정 전체가 영원히 위험" 한 게 아니라, "새도 권한이 좁고 곧 만료되는" 피해 범위가 한정된 열쇠인 것이다. SSH 키도 비슷하게, 비밀키가 내 컴퓨터를 떠나지 않으니 네트워크로 비밀번호를 흘릴 일 자체가 없다.
이걸 오늘 배운 2FA 와 엮으면 한 줄기로 이어진다. 2FA 가 "아는 것(비밀번호) + 가진 것(휴대폰)" 두 가지를 요구한 이유도, 하나가 새도 전부가 뚫리지 않게 하려는 것이었다. 비밀번호 폐지도, 토큰의 좁은 권한도, 2FA 도 모두 같은 철학 위에 있다 — "단일 비밀 하나에 모든 걸 걸지 마라." 보안은 결국 "한 군데가 뚫려도 피해를 좁게 가두는" 설계의 문제다.
그래서 비밀번호 인증의 폐지는 불편을 주려는 게 아니라, 위험을 한 점에 몰아두지 않으려는 방향이다. 토큰을 발급하고 SSH 키를 만드는 약간의 수고는, "새도 적게 잃는" 안전을 사는 값이다.
💡 핵심을 한마디로
비밀번호는 권한을 쪼갤 수도, 유효기간을 둘 수도 없는 전부-아니면-전무의 열쇠라, 한 번 새면 계정 전체가 영원히 위험해진다. 토큰은 권한을 좁히고 만료시킬 수 있어 피해를 가둔다. 2FA 의 "아는 것 + 가진 것" 과 같은 철학 — "단일 비밀 하나에 모든 걸 걸지 마라" 의 실천이다.