TL;DR

  • git fetch origin master의 origin master는 원격 저장소와 가져올 브랜치를 가리키며, git switch -c my-feature origin/master의 origin/master는 로컬에 저장된 원격 브랜치의 상태를 가리킴.
  • Git은 불변 콘텐츠 주소 지정 데이터베이스뿐 아니라, 문자열 키가 콘텐츠 주소 지정 객체를 가리키는 변경 가능한 키-값 저장소도 갖춤.
  • 브랜치와 태그, Git 노트는 모두 refs이며, refs/heads/나 refs/remotes/ 같은 이름 공간으로 구분됨.
  • 원격 추적 브랜치는 마지막 동기화 시점에 파악한 원격 상태의 로컬 기록이며, 로컬 브랜치는 로컬 커밋에 따라 별도로 움직임.
  • fetch는 원격 브랜치 정보를 로컬 원격 추적 ref에 저장하고, switch -c는 그 ref를 시작점으로 새 로컬 브랜치를 만듦.

Git의 커밋과 refs

  • Git의 커밋은 콘텐츠 해시로 식별되며, 주로 특정 시점 코드베이스의 메모리 없는 스냅샷과 부모 커밋 해시 목록으로 구성됨.
  • 이 모델만으로도 git log 출력을 이해하고, 저장소를 다시 복제하지 않고도 잘못된 리베이스에서 복구할 수 있음. 경력의 대략 절반 동안 저장소를 다시 복제했으며, Git 학습은 유용하지만 일을 시작할 때 배워야 할 최우선 과제는 아니라는 관점임.
  • Git은 추가만 가능한 불변 콘텐츠 주소 지정 데이터베이스와 함께, 변경 가능한 키-값 저장소도 제공함.
  • 이 저장소는 문자열 키와 콘텐츠 주소 지정 객체를 연결하는 변경 가능한 맵이며, 키는 관례적으로 파일 시스템 경로 형태임. 맵의 상태는 대체로 .git/refs를 나열해 확인할 수 있음.
  • 예를 들어 .git/refs/heads/master 파일에는 해당 브랜치가 가리키는 커밋 해시가 저장되며, git show-ref refs/heads/master로 같은 ref와 해시를 확인할 수 있음.

refs가 혼란스러운 이유

  • refs는 사용자에게 보이는 여러 Git 기능을 작동시키지만, 그 자체는 구현 세부 사항임.
  • 일상적인 사용에서는 refs가 거의 보이지 않아 추상화가 일부 새어 나옴.
  • Git 명령줄 인터페이스(CLI)는 refs의 축약 표기와 기본 인수를 사용하므로, 특정 인수가 ref라는 사실이 명확하지 않을 때가 있음.
  • Git은 하나의 대상에 여러 이름을 붙이고, 같은 이름을 서로 다른 대상에 재사용하기도 함.
  • 브랜치와 태그, Git 노트는 모두 refs임.

`git fetch origin master`의 의미

  • 단축 표기를 모두 풀면 git fetch origin master는 원격 저장소 URL https://github.com/matklad/matklad.github.io와 소스·대상 ref 쌍 refs/heads/master:refs/remotes/origin/master를 지정하는 명령임.
  • fetch의 첫 번째 인수는 원격 저장소 위치이며, Git은 해당 주소에 접속해 데이터를 네트워크를 통해 로컬로 전송함.
  • 두 번째 인수는 문자열 키(ref)의 소스·대상 쌍임. 소스는 원격 저장소의 키이고 대상은 로컬 키의 이름이며, fetch는 원격 값을 읽어 로컬의 다른 이름으로 저장함.
  • 저장소 URL을 매번 입력하지 않도록 Git은 URL에 기호 이름을 부여하며, origin은 주 원격 저장소에 흔히 쓰이는 이름임. 따라서 git fetch origin refs/heads/master:refs/remotes/origin/master처럼 쓸 수 있음.
  • refs/heads/master는 원격 저장소의 브랜치를 완전히 표기한 이름임. my-feature 브랜치도 실제로는 refs/heads/my-feature라는 ref이며, refs/branch/my-feature일 수도 있었지만 그렇게 정해져 있지는 않음.
  • 구체적인 축약 규칙은 알지 못하지만, 일반적으로 Git은 ref의 접미부만 입력하는 표기를 허용함. 따라서 소스는 master로 줄여 쓸 수 있음.
  • refs/remotes/origin/master는 가져온 결과를 저장할 로컬 ref임. 원격과 같은 이름을 로컬에서도 그대로 쓰면 원격 저장소가 하나일 때만 문제가 없으며, 포크한 저장소와 원본 저장소처럼 업스트림이 둘이면 ref 이름이 충돌함.
  • 원격 저장소 foo의 refs를 refs/remotes/foo 아래에 두는 이유는 원격별 이름 공간을 만들기 위함이며, 단순한 설정에서 origin은 관례적인 원격 이름임.
  • 로컬에서 원격 ref 구조를 그대로 복제하는 방식이라면 refs/heads/my-branch가 refs/remotes/origin/heads/my-branch로 이어지겠지만, Git은 중복되는 heads 구성 요소를 제거함.
  • heads에서 remotes/$remote로 이어지는 매핑이 내장되어 있어 명령을 git fetch origin master로 줄일 수 있음.

원격 브랜치와 로컬 브랜치

  • Git은 오프라인 우선 모델을 따르며, 동기화 시점을 명시적으로 지정한다고 가정함. 연결이 있을 때 원격 저장소와 백그라운드 동기화를 하는 대신, 데이터를 네트워크로 전송하려면 명시적으로 fetch와 push를 실행해야 함.
  • 이 방식에서는 마지막으로 원격과 통신한 순간의 원격 상태를 모델링하는 것이 유용함. 이는 상대방의 상태를 추정해 기억하는 것과 같음.
  • 예를 들어 main 브랜치는 origin 원격에 refs/heads/main으로 존재함. 로컬 저장소를 origin과 동기화하면 refs/remotes/origin/main이 생기며, 이는 origin의 main 상태에 대한 현재 로컬 지식임.
  • 로컬 refs/heads/main은 보통 처음에는 refs/remotes/origin/main과 같은 커밋을 가리킴. 하지만 커밋을 만들면 refs/heads/main은 앞으로 이동하고 refs/remotes/origin/main은 그대로임.
  • 로컬 커밋을 origin에 푸시하려 할 때 그사이 원격 main이 앞으로 이동했다면 충돌이 발생함. 이때 Git은 원격 상태의 로컬 미러인 refs/remotes/origin/main을 자동으로 갱신하지만, refs/heads/main을 갱신하고 다시 푸시하는 일은 직접 해야 함.

전체 명령 예시

  • git fetch origin master는 .git/config에서 origin 원격의 URL을 찾고 해당 기기에 네트워크 요청을 보냄. 그 결과 로컬 refs/remotes/origin/master가 원격의 refs/heads/master와 같은 커밋을 가리키도록 갱신되며, 해당 커밋과 조상 커밋도 로컬로 전송됨.
  • git switch -c my-feature origin/master는 refs/remotes/origin/master를 시작점으로 삼아 새 브랜치인 refs/heads/my-feature를 만듦. 두 번째 인수는 축약된 ref이므로 refs/remotes/origin/master로 완전히 적을 수 있음.
  • 첫 번째 인수인 my-feature는 ref를 만들 이름이지만, 그 자체가 ref는 아님. 이를 refs/heads/my-feature로 완전히 적으면 Git은 refs/heads/refs/heads/my-feature를 만들게 됨.
  • 이 설명이 특별히 유용하지 않을 수도 있지만 흥미로운 내용일 수 있음.