Git 기초 #4 원격 저장소 — clone·fetch·pull·push와 origin의 정체

6 분 소요

“Git 기초 #3 브랜치와 머지 — fast-forward와 3-way"까지의 모든 작업은 내 컴퓨터의 .git 디렉터리 안에서 끝났습니다. 커밋도 브랜치도 머지도 네트워크 없이 동작합니다. 그런데 이 상태로는 컴퓨터가 고장 나면 히스토리 전체를 잃고, 다른 사람과 함께 작업할 방법도 없습니다. 두 문제를 한 번에 푸는 것이 원격 저장소입니다.

총 6편으로 구성됩니다.

  • #1 Git이란 — 스냅샷 모델과 세 영역
  • #2 add·commit·status — 스테이징 영역의 의미
  • #3 브랜치와 머지 — fast-forward와 3-way
  • #4 원격 저장소 — clone·fetch·pull·push와 origin의 정체 ← 이번 글
  • #5 되돌리기 — restore·reset·revert 구분
  • #6 GitHub와 첫 Pull Request

이번 글은 원격 저장소를 복제하는 clone부터 원격과 주고받는 fetch, pull, push까지의 흐름을 정리합니다. 그 과정에서 많은 분들이 이름만 알고 정체는 모른 채 쓰는 origin과 origin/main이 실제로 무엇인지 확인하겠습니다.

git clone — 히스토리 전체를 복제한다 #

원격 저장소 작업의 출발점은 대부분 clone입니다.

저장소 복제
$ git clone https://github.com/schoolofweb/sample-project.git
Cloning into 'sample-project'...
remote: Enumerating objects: 127, done.
Receiving objects: 100% (127/127), done.

clone이 가져오는 것은 최신 파일들만이 아닙니다. 모든 커밋, 모든 브랜치를 포함한 히스토리 전체가 복제됩니다. 그래서 clone이 끝난 순간부터 git log도 브랜치 생성도 네트워크 없이 동작합니다. 복제본이라고 해서 기능이 제한된 사본이 아니라, 원격에 있는 것과 동등한 완전한 저장소가 하나 더 생기는 것입니다.

origin의 정체 — 리모트의 기본 이름일 뿐 #

clone 직후 저장소 안에서 리모트 목록을 확인해 보겠습니다.

리모트 확인
$ cd sample-project
$ git remote -v
origin  https://github.com/schoolofweb/sample-project.git (fetch)
origin  https://github.com/schoolofweb/sample-project.git (push)

origin은 특별한 기능이 있는 예약어가 아닙니다. clone할 때 원본 저장소의 URL에 자동으로 붙는 기본 별명일 뿐입니다. 매번 긴 URL을 입력하는 대신 origin이라는 짧은 이름으로 부르는 것입니다. 이름이 마음에 들지 않으면 git remote rename origin upstream처럼 바꿀 수도 있고, 리모트를 여러 개 등록할 수도 있습니다. origin이라는 이름 자체에는 아무 마법이 없다는 점만 기억하면 됩니다.

origin/main — 원격을 기억하는 로컬 포인터 #

브랜치 목록을 전부 보면 낯선 이름이 하나 보입니다.

원격 추적 브랜치 확인
$ git branch -a
* main
  remotes/origin/main

origin/main은 원격 서버에 있는 브랜치가 아닙니다. 마지막으로 원격과 통신했을 때 원격의 main이 어디를 가리키고 있었는지를 기록해 둔, 내 저장소 안의 읽기 전용 포인터입니다. 원격 추적 브랜치(remote-tracking branch)라고 부릅니다.

내 main 과 origin/main 은 다른 포인터
[원격 저장소 (GitHub)]
A --- B --- C            ← main

[내 저장소]
A --- B --- C            ← origin/main (마지막 통신 시점의 기록)
              \
                D        ← main (HEAD, 내가 추가한 커밋)

내가 로컬에서 커밋 D를 만들면 내 main만 전진하고 origin/main은 C에 머뭅니다. 반대로 다른 사람이 원격에 커밋을 올려도, 내가 통신하기 전까지 내 origin/main은 그 사실을 모릅니다. 이 구조를 이해하면 fetch와 pull의 차이가 명확해집니다.

git fetch — 가져오되 건드리지 않는다 #

fetch는 원격의 새 커밋을 내려받아 원격 추적 브랜치만 갱신합니다.

fetch 로 원격 상태 가져오기
$ git fetch origin
remote: Enumerating objects: 5, done.
From https://github.com/schoolofweb/sample-project
   1a2b3c4..9f8e7d6  main       -> origin/main

출력의 마지막 줄이 핵심입니다. 갱신된 것은 origin/main뿐이고, 내 main과 작업 디렉터리의 파일들은 그대로입니다. 그래서 fetch는 언제 실행해도 안전합니다. 가져온 다음 무엇이 달라졌는지 확인하고 나서 합칠지 결정할 수 있습니다.

fetch 후 차이 확인과 머지
$ git log main..origin/main --oneline   # 원격에만 있는 커밋 확인
9f8e7d6 로그인 버그 수정

$ git merge origin/main                  # 확인 후 내 main 에 합치기

git pull — fetch와 merge를 한 번에 #

pull은 새로운 명령이 아닙니다. fetch를 실행한 뒤 곧바로 merge까지 실행하는 축약 명령입니다.

pull 은 fetch + merge
$ git pull origin main
# 아래 두 명령과 같습니다
# git fetch origin
# git merge origin/main

일상 작업에서는 pull이 편합니다. 다만 무엇이 내려와서 무엇이 합쳐질지 모른 채 실행하게 되므로, 원격에서 큰 변경이 있었던 것 같을 때나 상태가 궁금할 때는 fetch로 먼저 확인하고 merge하는 두 단계가 안전합니다. pull이 잘 이해되지 않을 때 fetch와 merge로 풀어서 생각하면 대부분의 혼란이 정리됩니다.

git push — 내 커밋을 원격에 올린다 #

방향을 반대로 돌려, 내 커밋을 원격에 올릴 때는 push를 씁니다. 새 브랜치를 처음 올릴 때만 형태가 조금 다릅니다.

처음 푸시할 때
$ git push -u origin feature-login
To https://github.com/schoolofweb/sample-project.git
 * [new branch]      feature-login -> feature-login
branch 'feature-login' set up to track 'origin/feature-login'.

-u는 upstream 설정 옵션입니다. 내 feature-login이 원격의 origin/feature-login과 짝이라는 사실을 기록해 둡니다. 이 짝을 한 번 기록하면 다음부터는 브랜치와 리모트 이름을 생략하고 git push, git pull만으로 충분합니다. 어디로 보내고 어디서 받을지 Git이 이미 알고 있기 때문입니다.

push가 거부될 때 #

push가 항상 성공하지는 않습니다. 내가 마지막으로 fetch한 뒤에 다른 사람이 먼저 원격에 커밋을 올렸다면, 원격이 나보다 앞서 있는 상태라 Git이 push를 거부합니다.

push 거부
$ git push
To https://github.com/schoolofweb/sample-project.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs
hint: Updates were rejected because the remote contains work that you do not
hint: have locally.

내 push가 원격의 커밋을 덮어쓰지 않도록 막아 주는 정상적인 보호 장치입니다. 해결 순서도 정해져 있습니다. 원격의 커밋을 먼저 받아서 합친 뒤 다시 올리는 것입니다.

거부됐을 때의 해결 흐름
$ git pull      # 원격 커밋을 받아 내 커밋과 머지
$ git push      # 합쳐진 결과를 올리기

pull 과정에서 충돌이 나면 이전 글에서 다룬 충돌 해결 절차를 그대로 따르면 됩니다.

인증 — HTTPS와 SSH #

push는 저장소를 쓰는 행위라 인증이 필요합니다. 방식은 두 가지입니다.

  • HTTPS — URL이 https://로 시작합니다. 처음 push할 때 운영체제의 credential manager가 GitHub 로그인을 요청하고, 이후에는 저장된 인증 정보를 재사용합니다. 시작이 간단해 입문 단계에 적합합니다.
  • SSH — URL이 git@github.com:으로 시작합니다. 공개키를 GitHub 계정에 등록해 두면 이후 인증 절차 없이 통신합니다. 초기 설정이 필요한 대신 한 번 해 두면 편합니다.
노트
어느 쪽이든 기능 차이는 없으므로 지금은 HTTPS로 시작해도 충분합니다. SSH 키 등록 절차는 GitHub 공식 문서의 “Connecting to GitHub with SSH” 항목에 단계별로 정리되어 있습니다.

마무리 #

이번 글의 핵심은 네 가지입니다.

  • clone은 최신 파일이 아니라 히스토리 전체를 복제하고, origin은 그 원본 URL에 붙는 기본 별명일 뿐입니다.
  • origin/main은 마지막 통신 시점의 원격 상태를 기록한 내 저장소 안의 읽기 전용 포인터입니다.
  • fetch는 원격 추적 브랜치만 갱신하는 안전한 확인 단계이고, pull은 fetch와 merge를 한 번에 실행하는 축약입니다.
  • push가 거부되면 원격이 앞서 있다는 뜻이니 pull로 합친 뒤 다시 올립니다.

되돌리기를 아직 다루지 않았습니다. 잘못 add한 파일, 방금 만든 커밋의 오타, 이미 push까지 끝난 커밋의 버그는 각각 되돌리는 방법이 다릅니다. 다음 글인 “Git 기초 #5 되돌리기 — restore·reset·revert 구분"에서 세 명령이 각각 언제 필요한지 정리하겠습니다.

X