본문 바로가기

Hub Development/git

[Git] git의 내부 구조

728x90

🍁 깃의 내부 구조


🔹 처음에는 깃의 작동 방식을 알아봤다. 평소에 git을 이용했지만 내부 구조까지는 파악하지 않았고, add와 commit을 할 때 어떤 식으로 영역이 할당되는지만 숙지하고 있었다. git의 명령어만 사용했을 뿐 git의 내부 동작이 어떻게 돌아가는지 알지 못했는데 이번에 알아봐야겠다는 생각을 했다.

 

가장 먼저 알아야 할 부분은 git의 내부 구조이기 때문에 .git의 파일 내부에는 어떤 내용이 들어있는지 살펴봤다. refs 폴더 안에 remotes라는 폴더가 있는데 이곳에 연결해 왔던 원격 저장소의 remote 내역이 들어있어서 놀랐다. cmd를 통해 써오던 명령어들이 이런 파일의 형태로 내 폴더 어딘가에 저장되고 있겠다는 생각을 확인해 보지 못했는데 이번 기회를 통해 눈으로 직접 확인할 수 있었다.

 

📑 .git 파일이 생기는 과정


🔹 init 명령어를 사용하거나 특정 저장소를 clone 명령어를 활용해 로컬 저장소로 복제하면 내부에 .git 이라는 디렉터리가 생성된다. 아무런 커밋도 이루어지지 않은 초기의 .git 디렉터리는 아래와 같다.

├── HEAD         # 현재 체크아웃(Checkout)한 브랜치(Branch)를 가리키는 파일
├── config       # 이 프로젝트에만 적용되는 설정 옵션
├── description  # GitWeb 프로그램에서 사용하는 파일
├── hooks        # 클라이언트 훅 또는 서버 훅 스크립트 파일이 존재하는 디렉터리
├── info         # .gitignore 파일처럼 무시할 파일의 패턴을 적어두는 디렉터리
├── objects      # 모든 파일의 내용을 저장하는 일종의 데이터베이스 역할의 디렉터리
└── refs         # 커밋 개체의 포인터를 저장하는 디렉터리

이중에서도 중요한 건 HEAD 파일과 objects , 그리고 refs 디렉터리이다. 여기서 objects 폴더와 index폴더가 생성되는 과정이 나온다.

 

👻  Objects 디렉터리


🔹 파일을 add하게 되면 objects 디렉터리에 새로운 디렉터리가 생기고 index 파일이 생긴다. add 한 파일은 objects 내부에서 일종의 데이터베이스처럼 저장되고 깃은 40자 길이의 체크섬 해시를 생성해서 파일의 이름을 지정하는데 해시의 첫 두 글자를 디렉터리의 이름으로 쓰고 나머지 38글자를 파일 이름으로 사용한다.

├── HEAD
├── index
├── objects
│   └── e3
│       └── 9e3dfc2b608453846cbf0e74aa69a1c0b7f311
└── refs

이후 cat-file 명령어에 -p 옵션을 준 뒤 내용을 보기 원하는 파일의 40자 길이의 해시값을 전달하면 파일 내용이 출력된다. cat-file 은 저장소 개체의 파일 내용이나 타입, 그리고 크기 등을 제공하는 명령어고 -p 옵션은 pretty-print 의 준말로 해시값을 전달하면 파일의 내용을 출력해 주는 것을 의미한다.

 

추가적인 설명을 하자면, 깃은 파일을 저장할 때 Tree 또는 Blob 개체로 저장하며 이때 Tree는 쉽게 디렉터리를 의미하고 Blob는 일반 파일을 의미한다고 생각하면 된다. 만약 -p 옵션을 주지 않고 파일의 내용을 출력하고 싶다면 타입과 함께 해시값을 전달하면 된다! Tree 또는 Blob 외에도 Commit과 Tag 개체가 존재한다.

 

✋ cat-file 과 같이 깃 자체의 내부 작동과 관련된 명령어를 Plumbing 명령어라 부르며, 기타 사용자의 편의성을 위해 존재하는 add , commit 같은 명령어를 Porcelain 명령어라 부른다.

 

궁금해서 실제로 사용해봤다. 0a라는 디렉터리 안에 3개의 파일이 들어있길래 각자 파일이 뭘 의미하는 건지 궁금했다. 첫 번째 파일은 commit 개체로 커밋의 대상이 되는 Tree 개체와 함께 커밋과 관련된 메타 데이터인 사용자 정보와 타임스탬프, 그리고 커밋 메시지가 저장되어 있다. (+ 부모 노드 {이전 파일}) 여러 파일을 한 번에 스테이징 영역에 올리고 커밋할 경우 해당 파일이 모두 이 Tree 개체의 노드로 연결된다. tree를 자세히 보면 d7df1f~ 라고 나와있다. d7파일을 찾아가 보니 똑같은 해쉬값을 가진 파일이 있었고 이를 통해 이 0a6f0d~ 라는 해쉬값의 파일이 d7df1f~의 값을 가지고 있다고 이해했다.

두 번째 파일은 tree 개체이며, 개체의 종류, 해시값, 내 파일의 이름인 README.md가 보인다.

세 번째 파일은 blob 개체로 파일 안의 내용이 나온다. 눈으로 보면서 공부해서 더 이해가 잘 된 것 같다.

 

여기서 디렉터리안에 파일이 1개 있는 경우도 2개, 3개 있는 경우도 있었다. 이 부분에서 안의 내용들은 다 공통점이 보일 때도 안 보일 때도 있어서 같은 버전의 다른 정보값을 가지고 있는 것이 아니라 해쉬값의 앞부분이 같으면 같은 디렉터리에 저장된다고 이해했다.

 

 index 파일


 

🔹 index 파일은 스테이징 영역(Staging Area)의 정보를 저장하는 파일이다. 깃은 기본적으로 파일을 Committed, Modified, Staged라는 세 가지 상태로 관리한다. 이 중에서 Staged 상태는 커밋하기 직전의 상태를 의미하며, 다시 말해 스테이징 영역에 있는 파일들은 모두 커밋하기 직전의 파일들이고 Staged 상태이다.

 

🧪 깃에 대한 추가적인 정보


❓git은 기존에는 SHA-1을 사용했다고 한다. 하지만 현재는 SHA-256을 쓰는데 이유가 뭘까? 아래의 이유와 더불어 심지어는 계산도 빠르다고 한다!

 

      SHA-1의 보안상의 문제

      SHA-256의 256비트 해시(일반적인 보안 관행과 일치할 만큼 충분히 길며 성능 및 디스크 사용량을 저하시킬 만큼 지나치게 길지 않음).

      고품질의 구현이 가능한 SHA-256

 

또 다른 정보로는 Git은 update사항이 있다면 예를들어 100Mb 파일에 1Mb의 내용을 추가했을 때 100Mb와 1Mb로 두 개의 파일이 생기는 것이 아니라 100Mb와 101Mb의 파일을 만든다는 사실이다. 이 편이 더 효율적이라고 한다. 누가 봐도 전자가 효율적이지만 왜 후자가 더 효율적일까?

 

→ 나는 이유를 순회에 있다고 생각했다. 전자와 같이 저장을 하게된다면 업데이트 내역과 본문이 따로 해시값을 갖고 저장이 되는 것인데 그렇게 된다면 업데이트의 수가 많을 때 업데이트 내용과 본문을 찾기 위해 수많은 노드를 순회해야 할 것이다. 하지만 후자의 경우 가장 마지막 노드가 내용과 업데이트 내용을 둘 다 갖고 있는 최종파일이기 때문에 후자가 더 효율적이지 않을까라는 생각을 했다.

 

📸 참조


https://medium.com/daangn/%EC%95%97-%EB%AA%A8%EB%A5%B4%EA%B3%A0-%EA%B9%83%ED%97%99-github-%EC%97%90-%EC%98%AC%EB%A0%B8%EC%96%B4%EC%9A%94-50d48b343f0f