치춘짱베리굿나이스

Git 폴더 뜯어보기 본문

이론적인 부분들/Git Github

Git 폴더 뜯어보기

치춘 2022. 7. 25. 01:50

.git 뜯어보기

여기서부턴 .git 폴더를 직접 뜯어보았다

깃 많이 쓰긴 하지만 깃의 작동방식 원리를 이해하고 쓰는것도 괜찮을 것 같다

물론 깃의 명령어와 기능이 워낙 방대하기 때문에 전부 다 다룰 수는 없다

.git/index

아직 add하지 않은 상태에서 루트 폴더의 .git/index 파일을 열어보자

대부분의 문자가 깨진 이유는 index 파일 내용물이 SHA1 방식으로 암호화되어 있기 때문이다

 

이번에는 git add 커맨드를 사용한 뒤 .git/index 파일을 열어보았다

여전히 파일이 깨져있지만 아까와는 변화가 생겼음을 바로 알 수 있다

위에 적었듯 깃에서 이용하는 해시 키는 파일 내용과 함께 암호화되기 때문에, 파일 내용이 조금이라도 달라지면 완전히 다른 해시 키를 발급받게 된다

즉 깃에서 파일을 추적한다는 것은 커밋 시점 (또는 마지막으로 staged된 시점, 즉 index 파일에 저장되어 있는 값) 에서의 해시 값을 현재 파일의 해시 값과 비교 후, 만약에 현재 파일의 해시 값이 바뀌었다면 변화가 있는지 계속 감시하는 것이다

git add 명령어는 이 값을 index 파일에 저장하는 역할을 한다

SHA

Secure Hash Algorithm

안전한 암호화 해시 알고리즘으로, 미국 국방부 소속 국방안보국 (헉) 에 의해 설계되었다

 

SHA-0이 최초로 등장한 알고리즘이고, 그 뒤로 SHA-1이 설계되었으며, SHA-224, SHA-256, SHA-384 등… SHA-2 군 알고리즘들이 등장하였다

전세계적으로 가장 많이 사용되는 알고리즘은 SHA-1이고, 이 알고리즘의 설계 덕에 이전까지 많이 쓰이던 MD5가 거의 사장되었다

SHA1 방식은 특정한 알고리즘을 이용하여 파일의 내용물과 추가 정보를 40자리의 hex값으로 암호화하며, 내용에 따라 값이 아예 바뀌기 때문에 중복이 없는 키를 생성할 수 있다

그 말인 즉슨 파일의 내용이 조금만 바뀌면 이 파일을 기반으로 생성되는 SHA1 해시 키의 값도 아예 바뀌어버리므로, 해시 키의 변화를 통해 파일의 변화를 감지할 수 있다는 것이다

 

최대 2의 64승 비트의 메시지 (2의 61승 바이트이니까 대충… 200경 정도) 를 160비트 (20바이트) 의 해시 키로 압축시켜 버릴 수 있으며, 1바이트는 2개의 16진수 글자로 표현되므로 40글자짜리 해시 키가 생성되는 것이다 (아래에서 실제로 40글자짜리임을 확인할 수 있다) 이정도이니까 키가 겹칠래야 겹칠 수가 없다…

온라인에 존재하는 SHA1 해독 사이트들도 진짜로 그 알고리즘을 해독한 것은 아니고 어떤 키에서 어떤 값이 나오더라~ 를 대조하는 것 뿐이다

깃에서 SHA1 해시를 이용하는 것은 내용물의 변화를 고유 해시 키를 이용하여 계속 추적하고 충돌을 방지하기 위해서라고 한다

 

+

SHA1 방식에 충돌 문제점이 발견되어서 (다른 내용의 파일임에도 같은 SHA를 갖게 됨) 지금은 SHA1 대신 SHA256으로 대체했다고 한다

SHA256은 256비트를 사용하므로, 표현가능한 데이터의 범위가 훨씬 넓어졌다

Git 2.29 Introduces Experimental Support for SHA-256

.git/objects

깃에서 활용되는 데이터들이 저장되는 폴더이다

폴더명은 1바이트 hex값 (2글자) 으로 출력된다

 

각 폴더에 담긴 파일을 확인해보니 왠 외계어가.. 여기도 적혀있다

폴더명은 무조건 2글자이고, 파일명은 무조건 38자이다

이는 실제 파일을 SHA1 방식으로 해싱한 결과값이 40자이고, 이를 폴더명 2자와 파일명 38자로 분리해서 저장한 것이기 때문이다

앞서 적었듯 SHA1 방식으로 파일 내용을 암호화하면 파일 내용이 조금만 바뀌어도 완전히 별개의 해시값이 되기 때문에 파일 변화를 쉽게 알아채기 위해 사용한 것이라 볼 수 있다

왜 굳이 폴더 이름에 해시의 앞 2글자를 넣었을까

굳이 해시의 앞 두 글자를 이용해서 폴더를 만든 이유는 위와 같다

커밋이 굉장히 많은… 팀 프로젝트의 레포지토리를 뜯어보았다 (하하)

앞의 레포지토리와 다르게 폴더명이 00부터 ff까지 256종류가 다 있는 것을 볼 수 있다

 

하나의 폴더에 들어가보니 파일이 10개나 있다

만약 이 커밋들이 폴더 구분 없이 한 폴더에 다 들어가 있다면 커밋이 많은 대형 프로젝트는 파일 개수가 어마어마할 것이다

폴더를 하나의 구분자로 사용하여 어느 정도 분리해 두니 그나마 정리된 느낌을 받을 수 있다

blob

아예 새로운 git 레포지토리를 만들고 파일을 관찰해 보자

위의 이미 커밋이 여러 번 이루어져 파일이 잔뜩 쌓여있는 레포지토리와 다르게, 이 레포지토리는 커밋 내역이 하나도 없기 때문에 폴더가 아주 깨끗하다

이제 파일을 생성하고 add 해보자

 

가장 만만한 README.md 파일을 만들고 내용을 적어넣은 뒤, git add 했다

 

.git/objects 폴더로 들어가니 폴더 하나가 생겨있다

이 파일의 정체를 알아보자

 

$> git cat-file -t [폴더명][파일명 앞 4자리] # 파일명 다 적어도 상관없음

objects 폴더 안의 파일들이 무슨 파일인지 알아보고 싶다면 git의 cat-file 명령어를 이용하면 된다

폴더명과 파일명은 하나의 해시값을 두 덩어리로 분리한 것이므로, 이어서 적어줘야 한다 (단, 파일명은 4글자만 적어도 상관없다 = 총 6글자)

-t 플래그는 해당 파일이 무슨 타입인지 알려주며, 위의 출력값을 보니 blob이라고 한다

blobgit add할 때 생성되는 파일로, 깃이 감시하는 파일의 내용만을 저장하므로 메타데이터 (파일명 등) 는 알 수 없다

 

참고로 위의 명령어를 사용해서 열지 않으면 파일이… 다 깨진다 (ㅎ)

 

$> git cat-file -p [폴더명][파일명 앞 4자리] # 파일명 다 적어도 상관없음

이번에는 -p 명령어를 써보자

0f/c15f…. 파일의 내용을 볼 수 있다

정말로 README.md 파일의 내용물만을 저장했다

commit

이번에는 커밋을 한 뒤 폴더를 열어보자

이번에는 파일이 2개나 더 생겼다 (띠용~~)

위의 blob 파일을 열었을 때와 마찬가지로 git cat-file을 이용하여 파일을 열어보자

 

먼저 ad 폴더의 파일의 타입을 알아보았다

이번엔 commit이란다 커밋 명령어를 썼으니 커밋 파일이 생겼나보다

 

commit 파일에는 한 커밋의 제작자, 커밋한 사람, 커밋 메시지 그리고 트리의 해시값이 들어있다

그야말로 커밋 그 자체라고 할 수 있다

이 커밋은 깃 레포지토리가 초기화된 후 처음으로 작성하는 커밋이라 항목 하나가 빠져있지만, 원래는 parent라 해서 현재 커밋의 직전 커밋 해시값이 같이 저장된다고 한다

단방향 링크드리스트와 비슷하게 저장된다고 보면 된다

 

git log로 커밋 내역을 확인했을 때 나오는 커밋 내용은 위에서 출력된 커밋 내용과 같다

또한 커밋 아이디까지 아까의 폴더명+파일명과 같은 것을 볼 수 있다

이 파일들은 커밋 그 자체를 보관한다고 할 수 있다

트리는 아래에서 보도록 하자

tree

commit 파일을 열어봤을 때 tree라는 수상한 해시값을 볼 수 있었다

위에서 본 해시값 그대로 저장된 파일이 하나 있을 것이다

git cat-file을 이용해서 파일 타입을 체크하면 tree라고 나온다

 

treeblob과는 반대로 파일의 식별자, 파일 데이터의 해시값, 파일의 이름이 저장된다

또한 treeblob과 또 다른 tree로 구성되며, 위의 예시는 파일 개수가 많지 않기 때문에 이를 이해하긴 조금 힘들다

 

작업 파일이 많은 다른 레포지토리를 가져왔다

실제로 blobtree가 중간중간 섞여있는 것을 볼 수 있다

tree 오른쪽에 적혀있는 것은 전부 디렉토리명으로, 결국 해당 디렉토리의 tree를 나타내는 해시값이라고 볼 수 있겠다

blob 오른쪽의 이름들은 전부 한 폴더 내의 파일명에 해당하며, 따라서 blob의 해시값은 해당 파일의 내용을 담은 해시값이다

맨 왼쪽의 값 (100644) 은 파일 식별자이다

  • 100644: 읽기 파일 (blob) ⇒ 권한 644
  • 100755: 실행 파일 (blob) ⇒ 권한 755
  • 040000: 디렉토리 (tree)

깃에서 파일 경로 관리를 어떻게 하는지 엿볼 수 있는 파일이었다

또한 이 tree 파일들에서 관리되고 있지 않은 파일들이 untracked로 처리되는 것까지 알 수 있다

tag

위의 blob, tree, commitgit addgit commit만으로 만들 수 있는 반면, tag 파일은 따로 다른 명령어를 사용해야만 한다

git tag 명령어로 태그를 생성해 보자

보통 버전명 태그에 많이 사용된다고 한다

 

폴더가 하나 늘었다

바로 열어보자

 

태그가 작성된 커밋 또는 객체의 해시값, 해당 객체의 타입, 태그 이름, 태그 작성자, 태그 메시지가 표시된다

.git/refs

해당 레포지토리의 브랜치들이 파일로 정리되어 있으며, 폴더구조는 브랜치명을 따른다

브랜치를 만들 때 feat/[브랜치이름] 이나 refactor/[브랜치이름] 이렇게 폴더구조 식으로 이름을 지으면, 해당 폴더까지 함께 작성되어 모아보기 편하다

feat이냐 feature이냐 통일해야하는 이유가 여기서 나온다 (ㅋㅋㅋㅋ)

브랜치별 마지막 커밋 (HEAD) 의 해시 값을 저장하고 있다고 한다

stash

stash에는 스태시 해시들이 저장되어 있으며, 이 해시에는 위처럼 commit 객체의 형식으로 스태시 메시지, 트리나 작성자 등이 기록되어 있다

.git/hooks

특정 이벤트가 발생했을 때 특정 스크립트를 바로 실행하도록 할 수 있다

위의 파일들을 보면 확장자에 .sample이 붙어있는데, 이를 떼어버리면 쓸 수 있다고 한다

위 파일들은 git 측에서 제공하는 예제이고, 사용자가 더 추가해서 쓸 수도 있다

따라서 따로 hooks 폴더를 건드리지 않는 이상 모든 레포지토리에서 이 폴더의 파일 내용은 다 같다

.git/logs

HEAD 폴더와 .git/refs 폴더와 똑같은 폴더 구조를 한 refs 폴더가 들어있다

각 브랜치와 HEAD 별로 모든 작업 내역이 로그로 기록되는 폴더이다

.git/info

exclude라는 파일 딱 하나밖에 없는데, .gitignore 대신 사용할 수 있다고 한다

그 외 파일들

  • HEAD
    • 현재 브랜치나 커밋 등, 레포지토리의 시점을 가리킨다
    • git checkout을 통해 변경할 수 있다
    • 특정 커밋으로의 시점으로 이동했으면 해당 커밋이 표시된다
    • 브랜치의 최상단 커밋 (HEAD) 을 바라보고 있을 경우, 해당 브랜치가 표시된다
  • ORIG_HEAD
    • HEAD의 직전 값
    • 브랜치나 커밋 사이를 넘나들 때, 되돌아갈 지점이 기록된다
  • description
    • GitWeb 프로그램에서 사용되는 해당 레포지토리 정보 파일이라고 한다
  • COMMIT_EDITMSG
    • 커밋 작성하는 파일이다
    • 우리가 매번 -m 플래그 없이 커밋할 때, 에디터로 열리는 파일이 여기다
    • 여기에 메시지를 입력하고 저장하면 해당 문자열이 커밋 메시지로 들어간다
  • config
    • 해당 레포지토리의 설정값이 저장된다
    • 대소문자 구분 여부, 유니코드, origin 등 리모트 경로 등
  • packed-refs
    • 가비지 콜렉션 이후 refs 폴더 안의 값들이 압축되어 저장되는 곳

번외: 용어정리

스냅샷

흔히 커밋을 만드는 것을 “스냅샷으로 기록하는 행위" 라고들 설명한다

스냅샷이란 특정 시점 (2022년 7월 24일, 2015년 3월 3일, 어제 등…) 에서의 파일, 폴더 및 워크스페이스의 상태를 의미한다

스냅샷을 뜯어보면 해당 시점에 어떤 파일에 어떤 내용이 들어있었는지, 어떤 폴더구조가 있었는지, 어떤 파일이 존재했었는지 등 스냅샷이 관리하는 저장소의 모든 정보들을 알 수 있다

효율을 위해 변경점이 없는 파일까지 저장하진 않고, 대신 이미 저장되어 있는 버전의 링크만을 걸어준다

이 파일에서 버전 2의 파일 B는 버전 1로부터 변경점이 없기 때문에 버전 1에서의 B 링크만 걸려있다

버전 3의 파일 A 또한 버전 2 이후 변경점이 없기 때문에 버전 2에서의 A1 링크만 걸려있는 것을 볼 수 있다

이처럼 스냅샷은 저장소의 모든 파일을 저장한다곤 하지만 수정된 부분만 저장하여 효율을 높인다

스냅샷들을 이어붙이면 특정 파일이나 저장소의 변화를 연속된 사진처럼 볼 수 있다 (그래서 이름이 스냅샷이기도 하다)

델타

스냅샷은 워크스페이스의 내용 전체를 저장하기 때문에 용량이 엄청 크지 않나? 라고 느껴질 수 있지만, 이를 보완하기 위해 깃은 델타를 함께 도입하여 변경점을 저장한다

깃은 파일이 일정 수 이상으로 많아졌을 때 가비지 콜렉션 (git gc) 을 작동시킨다

가비지 콜렉션은 사용하지 않는 (어느 커밋에도 기록되지 않은) 오브젝트 파일을 삭제하거나, 가장 최신 커밋 (HEAD 커밋) 내용만을 스냅샷으로 통째로 저장한 뒤 다른 커밋들은 델타만을 남겨놓고 압축시키는 역할을 한다

델타란 앞 커밋과의 차이점 (diff) 만을 저장한 파일로, 위의 이미지에서 4번째 커밋은 해당 커밋의 시점과 5번째 커밋의 시점을 비교하여 그 차이점만을 저장한 델타로 관리된다

따라서 만약 이미지에서 rev 4 (4번째 커밋) 으로 돌아가고 싶다면, 가장 최근 스냅샷에 6→5 델타와 5→4 델타를 적용시키면 된다

우리가 가장 많이 바라보는 시점이 가장 최근 시점 (마지막 스냅샷) 이고, 아주 먼 과거로 돌아가는 일은 그렇게 많지 않기 때문에 가장 최근 스냅샷을 통째로 저장하는 것은 효율적이라고 할 수 있다

MergeRebase도 이 델타를 이용해서 이루어지며, 간단하게 diff 명령어와 patch 명령어를 합친 거라고 보면 된다 (브랜치의 차이를 diff를 이용하여 델타로 만들고, patch를 이용하여 다른 한 쪽에 합치기)

물론 델타도 한 파일에서 너무 많은 변경점이 생겼다던가 하면 용량이 엄청 커지는 등의 한계는 존재하지만, 마지막 커밋의 스냅샷만을 저장하는 등 나름대로 다른 방향에서 효율성을 높였다고 볼 수 있다


참고자료

Git 교과서: 4.8.1 SHA1

git status 원리

Git: 델타와 스냅샷

Git 교과서: 4.4.2 스냅샷

.git 내부 구조 파헤치기

SHA1 / SHA2 알고리즘

Git - Git Hooks

what should be in the git description file?

ORIG_HEAD, FETCH_HEAD, MERGE_HEAD etc

 

Comments