Git— 스냅샷으로 이해하는 버전 관리
5장

원격 저장소 — fetch · push · pull

원격 저장소는 특별한 Git이 아니다 — URL로 접근할 수 있는 또 하나의 보통 저장소일 뿐이며, fetch · push · pull은 두 저장소 사이의 스냅샷을 동기화하는 세 가지 방식이다.

원격 저장소란 무엇인가

Git의 "원격(remote)"은 네트워크 어딘가에 있는 신비로운 서버가 아니다. 그것은 단지 URL을 가진 또 다른 Git 저장소다.

로컬 저장소가 .git/ 디렉터리에 모든 것을 담고 있듯이, GitHub · GitLab · Bitbucket의 저장소도 내부 구조는 정확히 같다. 다른 점은 접근 방법 — HTTPS나 SSH를 통해 연결한다는 것 — 뿐이다. 로컬 머신의 다른 경로에 있는 bare 저장소도 원격으로 쓸 수 있다.

원격을 등록한다는 것은 긴 URL에 짧은 별칭을 붙이는 일이다. 관례적으로 첫 번째 원격은 origin이라고 부르지만, 이름 자체에는 아무런 마법도 없다. git remote add upstream https://... 처럼 여러 원격을 등록해서 쓸 수도 있다.

원격(remote)git remote add <이름> <URL>로 등록한 URL 별칭. .git/config에 저장된다.
원격 관리
git remote -v                  # 등록된 원격 목록 + URL
git remote add origin <URL>    # 원격 추가
git remote set-url origin <URL> # URL 변경
git remote remove upstream     # 원격 삭제

원격 추적 브랜치 — 로컬에 있는 읽기 전용 거울

origin/main은 브랜치가 아니다. 정확히는 원격 추적 브랜치(remote-tracking branch)다 — 마지막으로 네트워크 통신을 했을 때 원격의 main이 어디를 가리켰는지 기록해 두는 읽기 전용 책갈피다.

이 구분이 왜 중요한가? 원격 저장소는 당신이 작업하는 동안에도 계속 변한다. 다른 팀원이 커밋을 올릴 수도 있고, CI 봇이 자동 커밋을 추가할 수도 있다. origin/main은 "내가 마지막으로 통신했을 때의 원격 상태"를 보여주는 스냅샷이다. 실제 원격의 현재 상태를 보려면 네트워크 호출이 필요하다.

원격 추적 브랜치는 refs/remotes/origin/main에 저장되며, 체크아웃하면 분리 HEAD(detached HEAD) 상태가 된다. 그 위에 커밋을 쌓으려면 먼저 로컬 브랜치를 만들어야 한다.

직관: origin/main은 마치 어제 찍어 둔 사진과 같다. 원본 장소(원격)는 그 사이에 달라졌을 수 있지만, 사진은 내가 직접 가서 확인하기 전까지 그대로다. fetch는 "지금 가서 새 사진을 찍어오는" 행위다.
원격 추적 브랜치 확인
git branch -r               # 원격 추적 브랜치만 표시
git branch -a               # 로컬 + 원격 추적 브랜치 모두
git log origin/main         # 원격의 마지막 상태 기준 로그

git fetch — 가져오되 합치지 않는다

git fetch는 원격 저장소의 새 커밋·트리·블롭 객체를 모두 다운로드하고 원격 추적 브랜치를 갱신하지만, 로컬 브랜치에는 손을 대지 않는다.

이것이 fetch의 핵심 미덕이다. fetch는 결코 당신의 작업을 방해하지 않는다. main에서 작업 중이라도, feature/login을 활발히 수정하고 있어도, fetch는 그 상태를 전혀 바꾸지 않는다. 단지 origin/main이라는 책갈피만 앞으로 이동시킨다.

fetch 후에는 git log HEAD..origin/main으로 "원격에는 있지만 나에게는 없는 커밋"을 조회할 수 있다. 통합하고 싶다면 git merge origin/main이나 git rebase origin/main을 별도로 실행한다.

특정 원격, 특정 브랜치만 fetch할 수도 있다:

fetch 명령
git fetch                      # 모든 원격의 모든 브랜치
git fetch origin               # origin의 모든 브랜치
git fetch origin main          # origin의 main만
git fetch --prune              # 원격에서 삭제된 브랜치 정리
git log HEAD..origin/main      # 원격에만 있는 커밋 확인
git diff origin/main           # 원격과의 diff
핵심: fetch는 항상 안전하다. 언제든, 몇 번이든 실행해도 로컬 브랜치는 바뀌지 않는다. 불확실할 때는 pull보다 fetch를 먼저 쓰고 상황을 파악하라.

git push — 업로드와 브랜치 이동 요청

git push는 로컬의 새 커밋 객체를 원격으로 업로드하고, 원격 브랜치 포인터를 이동시켜 달라고 요청한다.

Git은 push가 fast-forward일 때만 허용한다. 즉, 원격의 현재 브랜치 끝이 로컬 브랜치의 조상일 때만 이동을 받아들인다. 그렇지 않으면 "거부됨(rejected)" 오류가 발생한다. 이는 데이터 보호 장치다 — 당신이 모르는 다른 커밋을 덮어쓰는 일을 막는다.

거부됐다면 먼저 git fetch로 원격 변경사항을 가져온 다음, 로컬에서 merge 또는 rebase로 통합하고 다시 push하면 된다.

--force-with-lease는 "내가 마지막으로 fetch한 이후 원격에 변화가 없을 때만 강제 push"를 의미하며, 단순 --force보다 훨씬 안전한 선택지다. 그래도 공유 브랜치에서는 사용을 자제해야 한다.

push 명령
git push origin main              # origin의 main으로 push
git push -u origin feature/login  # 업스트림 설정 + push
git push origin :old-branch       # 원격 브랜치 삭제
git push --force-with-lease       # 안전한 강제 push
git push --tags                   # 태그도 함께 push
함정: --force는 원격의 히스토리를 덮어쓴다. 혼자 쓰는 브랜치라면 괜찮지만, 팀원과 공유하는 브랜치에 force push하면 그들의 로컬 히스토리와 충돌이 생기고 커밋을 잃을 수 있다.

git pull — fetch + merge의 편의 조합

git pullgit fetchgit merge를 자동으로 이어 실행하는 단축 명령이다.

이 사실을 알면 pull의 동작이 예측 가능해진다. pull은 fetch와 merge 두 단계를 합친 것이므로, merge가 생성하는 모든 부작용 — 머지 커밋 생성, 충돌 발생 가능성 — 이 그대로 적용된다.

git pull --rebase를 사용하면 merge 대신 rebase를 수행한다. 이렇게 하면 머지 커밋 없이 선형 히스토리를 유지할 수 있다. 많은 팀이 기본 전략으로 pull.rebase=true를 설정해 사용한다.

pull을 사용할 때 현재 브랜치에 uncommitted 변경사항이 있으면 merge나 rebase가 실패할 수 있다. 이럴 때 git stash로 변경사항을 잠시 치워두거나, 아니면 fetch만 먼저 하고 상황을 파악하는 것이 더 명시적이다.

pull 명령과 설정
git pull                       # fetch + merge (기본)
git pull --rebase              # fetch + rebase
git pull --ff-only             # fast-forward만 허용, 아니면 실패
git config pull.rebase true    # 기본 전략을 rebase로 변경
git config pull.ff only        # fast-forward 전용으로 설정
직관: pull = fetch + merge라고 기억하면 된다. "pull이 왜 이렇게 동작하지?"라는 질문에 대한 답은 항상 "fetch하면 어떻게 되고, 그다음 merge하면 어떻게 될까?"를 각각 생각해 보는 것이다.

fetch → merge 흐름 시각화

아래 애니메이션은 세 단계 — 로컬 작업 후 원격과 diverge된 상태, fetch로 원격 추적 브랜치 갱신, merge로 통합 — 를 보여준다.

fetch 후 merge — 원격과 로컬 동기화 1 / 4
SPACE 재생 · ← → 이동 · R 리셋
← 리베이스되돌리기 →