Search

Git 내부 구조와 분산 환경 운영 메커니즘

Git의 로컬 저장소 구조를 이해했다면, 이제 분산 환경에서 어떻게 동작하는지 알아볼 차례입니다. 원격 저장소와의 통신 방식, 데이터 전송 프로토콜, 그리고 실제 운영에서 필요한 관리 도구들을 살펴보면서 Git이 분산 버전 관리 시스템으로서 어떤 메커니즘을 사용하는지 이해해보겠습니다.

Refspec: 원격과 로컬을 연결하는 매핑 규칙

git remote add origin https://github.com/user/repo.git
Shell
복사
이 명령을 실행하면 .git/config 파일에 다음과 같은 설정이 추가됩니다:
[remote "origin"] url = https://github.com/user/repo.git fetch = +refs/heads/*:refs/remotes/origin/*
Plain Text
복사
여기서 fetch 라인에 있는 +refs/heads/*:refs/remotes/origin/*가 바로 Refspec입니다. 이 문법은 +<src>:<dst> 형태로 구성되며, 각 요소는 명확한 의미를 갖습니다.
+ 기호는 Fast-forward가 아닌 업데이트도 허용한다는 의미입니다. 일반적으로 Git은 안전을 위해 Fast-forward 관계가 아닌 업데이트를 거부하지만, 이 기호가 있으면 강제로 업데이트를 수행합니다.
refs/heads/*는 원격 저장소의 모든 브랜치를 의미하고, refs/remotes/origin/*는 로컬의 원격 추적 브랜치 위치를 나타냅니다. 결과적으로 원격의 master 브랜치는 로컬의 refs/remotes/origin/master로 매핑됩니다.
실제로 원격 브랜치에 접근할 때는 다음과 같은 방식들을 모두 사용할 수 있습니다:
git log origin/master git log remotes/origin/master git log refs/remotes/origin/master
Shell
복사
Git은 이 세 표현을 모두 refs/remotes/origin/master로 해석합니다.
특정 브랜치만 가져오도록 제한하려면 Refspec을 수정할 수 있습니다:
fetch = +refs/heads/master:refs/remotes/origin/master
Shell
복사
이렇게 설정하면 master 브랜치만 가져오고 다른 브랜치는 무시합니다.
명령어에서 직접 Refspec을 지정하는 것도 가능합니다:
git fetch origin master:refs/remotes/origin/mymaster
Shell
복사
이 명령은 원격의 master 브랜치를 로컬의 origin/mymaster로 가져옵니다.
여러 Refspec을 동시에 사용할 수도 있습니다:
git fetch origin master:refs/remotes/origin/mymaster \ topic:refs/remotes/origin/topic
Shell
복사
Push에서도 Refspec을 사용할 수 있습니다. 예를 들어 로컬의 master 브랜치를 원격의 qa/master로 Push하려면:
git push origin master:refs/heads/qa/master
Shell
복사
설정 파일에 Push용 Refspec을 추가하면 자동화할 수 있습니다:
[remote "origin"] url = https://github.com/user/repo.git fetch = +refs/heads/*:refs/remotes/origin/* push = refs/heads/master:refs/heads/qa/master
Shell
복사
흥미롭게도 Refspec으로 원격 브랜치를 삭제할 수도 있습니다:
git push origin :topic
Shell
복사
이는 <src>를 비워두어 <dst>를 비우라는 의미가 됩니다. Git 1.7 이상에서는 더 명확한 문법도 제공합니다:
git push origin --delete topic
Shell
복사

Git의 데이터 전송 방식: Dumb vs Smart 프로토콜

Git은 데이터를 전송할 때 두 가지 종류의 프로토콜을 사용합니다. 이 차이를 이해하면 Git의 네트워크 동작을 더 잘 파악할 수 있습니다.

Dumb 프로토콜

Dumb 프로토콜은 읽기 전용 HTTP 저장소에서 사용되는 단순한 방식입니다. "Dumb"라고 불리는 이유는 서버가 Git에 특화된 처리를 전혀 하지 않기 때문입니다. 예전에 서버 구축 설명할 때에도 멍청한 프로토콜로 소개된 적이 있습니다.
Clone 과정을 살펴보면:
git clone http://server/simplegit-progit.git
Shell
복사
1.
먼저 info/refs 파일을 다운로드합니다:
=> GET info/refs ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
Plain Text
복사
2.
HEAD가 무엇을 가리키는지 확인합니다:
=> GET HEAD ref: refs/heads/master
Plain Text
복사
3.
이제 커밋 객체를 하나씩 가져옵니다:
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
Plain Text
복사
4.
만약 객체가 Loose 형태로 없다면 Packfile에서 찾습니다:
=> GET objects/info/packs P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack => GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx => GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
Plain Text
복사
이 방식은 단순하지만 비효율적입니다. 서버는 단순히 정적 파일을 제공할 뿐이고, 클라이언트가 필요한 모든 객체를 개별적으로 요청해야 합니다.

Smart 프로토콜

Smart 프로토콜은 서버가 클라이언트의 상태를 분석하여 필요한 데이터만 전송하는 지능적인 방식입니다.
데이터 업로드 (Push)
SSH를 통한 Push 과정:
git push origin master
Shell
복사
1.
SSH 연결을 통해 git-receive-pack 실행:
ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
Shell
복사
2.
서버가 현재 상태와 지원 기능을 알려줍니다:
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master□report-status \ delete-refs side-band-64k quiet ofs-delta \ agent=git/2:2.1.1+github-607-gfba4028 0000
Plain Text
복사
3.
클라이언트가 업데이트할 정보를 전송합니다:
0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \ refs/heads/master report-status 0000
Plain Text
복사
4.
필요한 객체들을 Packfile로 압축하여 전송합니다.
데이터 다운로드 (Fetch)
SSH를 통한 Fetch 과정:
git fetch origin
Shell
복사
1.
git-upload-pack 명령 실행:
ssh -x git@server "git-upload-pack 'simplegit-progit.git'"
Shell
복사
2.
서버가 사용 가능한 참조들을 전송:
00dfca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \ side-band side-band-64k ofs-delta shallow no-progress include-tag \ multi_ack_detailed symref=HEAD:refs/heads/master 003fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master 0000
Plain Text
복사
3.
클라이언트가 원하는 객체와 가진 객체를 알려줍니다:
003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta 0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 0009done 0000
Plain Text
복사
4.
서버가 필요한 객체들만 Packfile로 압축하여 전송합니다.
HTTP를 통한 Smart 프로토콜도 비슷하지만 두 번의 HTTP 요청으로 나뉩니다:
=> GET $GIT_URL/info/refs?service=git-upload-pack => POST $GIT_URL/git-upload-pack
Plain Text
복사
Smart 프로토콜의 핵심은 서버와 클라이언트가 서로의 상태를 파악하여 실제로 필요한 데이터만 전송한다는 점입니다. 이를 통해 네트워크 사용량을 크게 줄이고 전송 속도를 향상시킵니다.

저장소 관리와 문제 해결

실제 Git 저장소를 운영하다 보면 다양한 관리 작업이 필요합니다.

Garbage Collection

Git은 자동으로 gc (Garbage Collection) 명령을 실행합니다. 이 작업은 저장소를 최적화하는 중요한 과정입니다:
git gc --auto
Shell
복사
gc 명령은 다음과 같은 작업을 수행합니다:
1.
Loose 객체 압축: 개별 파일로 저장된 객체들을 Packfile로 압축
2.
작은 Packfile 통합: 여러 작은 Packfile을 하나의 큰 Packfile로 병합
3.
참조 압축: refs 디렉토리의 파일들을 .git/packed-refs로 압축
4.
사용하지 않는 객체 삭제: 어떤 참조도 가리키지 않는 오래된 객체 제거
실행 조건은 다음과 같습니다:
Loose 객체가 7,000개 이상
Packfile이 50개 이상
gc 실행 후 참조들이 어떻게 압축되는지 확인할 수 있습니다:
# gc 실행 전 $ find .git/refs -type f .git/refs/heads/experiment .git/refs/heads/master .git/refs/tags/v1.0 # gc 실행 후 $ cat .git/packed-refs # pack-refs with: peeled fully-peeled cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0 9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1 ^1a410efbd13591db07496601ebc7a059dd55cfe9
Shell
복사
^ 기호로 시작하는 라인은 바로 위 태그가 Annotated 태그임을 나타냅니다.

데이터 복구

커밋을 잃어버렸을 때의 복구 방법을 살펴보겠습니다. Hard Reset으로 커밋을 잃어버린 상황을 가정해봅시다:
$ git log --pretty=oneline ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit 484a59275031909e19aadb7c92262719cfcdf19a added repo.rb 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit $ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9 HEAD is now at 1a410ef third commit
Shell
복사
이제 최근 두 커밋이 사라진 것처럼 보입니다. 이때 reflog를 사용할 수 있습니다:
$ git reflog 1a410ef HEAD@{0}: reset: moving to 1a410ef ab1afef HEAD@{1}: commit: modified repo.rb a bit 484a592 HEAD@{2}: commit: added repo.rb
Shell
복사
더 자세한 정보를 보려면:
$ git log -g commit 1a410efbd13591db07496601ebc7a059dd55cfe9 Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>) Reflog message: updating HEAD Author: Scott Chacon <schacon@gmail.com> Date: Fri May 22 18:22:37 2009 -0700 third commit commit ab1afef80fac8e34258ff41fc1b867c702daa24b Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>) Reflog message: updating HEAD Author: Scott Chacon <schacon@gmail.com> Date: Fri May 22 18:15:24 2009 -0700 modified repo.rb a bit
Shell
복사
잃어버린 커밋을 가리키는 브랜치를 만들어 복구합니다:
git branch recover-branch ab1afef
Shell
복사
Reflog가 삭제된 더 극한 상황에서는 fsck 명령을 사용할 수 있습니다:
$ git fsck --full Checking object directories: 100% (256/256), done. Checking objects: 100% (18/18), done. dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4 dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
Shell
복사
여기서 "dangling commit"이 잃어버린 커밋입니다.

대용량 객체 제거

실수로 큰 파일을 커밋했다가 다음 커밋에서 삭제했더라도, 히스토리에는 그 파일이 남아있어 저장소 크기를 키웁니다. 이를 완전히 제거하는 방법을 살펴보겠습니다.
먼저 큰 객체를 찾습니다:
$ git verify-pack -v .git/objects/pack/pack-29…69.idx \ | sort -k 3 -n \ | tail -3 dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 22044 5792 4977696 82c99a3e86bb1267b236a4b6eff7868d97489af1 blob 4975916 4976258 1438
Shell
복사
가장 큰 객체(5MB)가 무엇인지 확인합니다:
$ git rev-list --objects --all | grep 82c99a3 82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz
Shell
복사
이 파일을 추가한 커밋을 찾습니다:
$ git log --oneline --branches -- git.tgz dadf725 oops - removed large tarball 7b30847 add git tarball
Shell
복사
filter-branch로 히스토리에서 완전히 제거합니다:
$ git filter-branch --index-filter \ 'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..
Shell
복사
마지막으로 정리 작업을 수행합니다:
$ rm -Rf .git/refs/original $ rm -Rf .git/logs/ $ git gc $ git prune --expire now
Shell
복사
이 과정을 통해 대용량 파일을 히스토리에서 완전히 제거할 수 있습니다.

환경변수로 Git 동작 커스터마이징

Git은 다양한 환경변수를 통해 동작을 제어할 수 있습니다. 이는 특별한 환경에서 Git을 사용하거나 디버깅할 때 매우 유용합니다.

주요 환경변수

저장소 위치 관련:
GIT_DIR: .git 디렉토리 위치 지정
GIT_WORK_TREE: 워킹 디렉토리 위치 지정
GIT_INDEX_FILE: Index 파일 위치 지정
편집기 및 페이저:
GIT_EDITOR: 커밋 메시지 편집기 지정
GIT_PAGER: 출력 페이저 지정
커밋 정보:
GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL: Author 정보
GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL: Committer 정보

디버깅을 위한 Trace 옵션

Git의 내부 동작을 살펴보고 싶을 때 매우 유용한 환경변수들입니다:
일반적인 명령 실행 추적:
$ GIT_TRACE=true git lga 20:12:49.877982 git.c:554 trace: exec: 'git-lga' 20:12:49.878369 run-command.c:341 trace: run_command: 'git-lga' 20:12:49.879529 git.c:282 trace: alias expansion: lga => 'log' '--graph' '--pretty=oneline' '--abbrev-commit' '--decorate' '--all' 20:12:49.879885 git.c:349 trace: built-in: git 'log' '--graph' '--pretty=oneline' '--abbrev-commit' '--decorate' '--all'
Shell
복사
Packfile 접근 추적:
$ GIT_TRACE_PACK_ACCESS=true git status 20:10:12.081397 sha1_file.c:2088 .git/objects/pack/pack-c3fa...291e.pack 12 20:10:12.081886 sha1_file.c:2088 .git/objects/pack/pack-c3fa...291e.pack 34662
Shell
복사
네트워크 패킷 추적:
$ GIT_TRACE_PACKET=true git ls-remote origin 20:15:14.867043 pkt-line.c:46 packet: git< # service=git-upload-pack 20:15:14.867071 pkt-line.c:46 packet: git< 0000 20:15:14.867079 pkt-line.c:46 packet: git< 97b8860c071898d9e162678ea1035a8ced2f8b1f HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master agent=git/2.0.4
Shell
복사
성능 추적:
$ GIT_TRACE_PERFORMANCE=true git gc 20:18:19.499676 trace.c:414 performance: 0.374835000 s: git command: 'git' 'pack-refs' '--all' '--prune' 20:18:19.845585 trace.c:414 performance: 0.343020000 s: git command: 'git' 'reflog' 'expire' '--all'
Shell
복사
설정 정보 추적:
$ GIT_TRACE_SETUP=true git status 20:19:47.086765 trace.c:315 setup: git_dir: .git 20:19:47.087184 trace.c:316 setup: worktree: /Users/ben/src/git 20:19:47.087191 trace.c:317 setup: cwd: /Users/ben/src/git
Shell
복사

특수한 상황에서의 활용

포터블 Git 설정:
export HOME=/custom/path export GIT_CONFIG_NOSYSTEM=1
Shell
복사
CI/CD 환경에서의 자동화:
export GIT_AUTHOR_NAME="CI Bot" export GIT_AUTHOR_EMAIL="ci@company.com" export GIT_COMMITTER_NAME="CI Bot" export GIT_COMMITTER_EMAIL="ci@company.com"
Shell
복사
네트워크 문제 해결:
export GIT_CURL_VERBOSE=1 export GIT_SSL_NO_VERIFY=1 # Self-signed 인증서 환경에서
Shell
복사
이러한 환경변수들을 적절히 활용하면 다양한 상황에서 Git을 더 효과적으로 사용할 수 있습니다.

결론

Git의 분산 환경 동작 메커니즘을 이해하면 단순히 명령어를 외우는 것을 넘어서 "왜 이런 일이 일어나는지"를 파악할 수 있게 됩니다. Refspec을 통한 원격 저장소 매핑, Smart 프로토콜의 효율적인 데이터 전송, 그리고 다양한 관리 도구들의 동작 원리를 알면 문제 상황에서 적절히 대응할 수 있습니다.
물론 일상적인 개발에서는 이런 세부사항을 자주 다루지 않습니다. 하지만 저장소에 문제가 생겼을 때, 특별한 워크플로우가 필요할 때, 또는 Git의 동작을 최적화해야 할 때 이런 지식이 큰 도움이 됩니다. Git을 더 깊이 이해하고 자신감 있게 사용하는 밑바탕이 되는 것이죠.