본문 바로가기
카테고리 없음

[Git] .git 디렉토리 내부 변화를 살펴보면서 Git 내부 동작 이해하기

by 웅대 2024. 8. 2.
728x90
반응형

https://growth-coder.tistory.com/129

 

[Git & GitHub] 깃 버전 관리 기초 사용법

작업을 하다보면 예상치 못한 오류가 발생했을 때 이전에 정상적으로 작동했던 코드를 불러오고 싶은 마음이 있을 것이다. 여러 버전으로 나누어 이러한 정보들을 보관해두고 불러온다면 편리

growth-coder.tistory.com

 

이전 포스팅에서 깃으로 버전 관리하는 방법을 알아보았다.

 

사실 아래 과정만 알고 있어도 깃으로 버전 관리 하는 것에 있어서 큰 어려움은 없을 것이다.

 

  1. git init으로 저장소를 생성한다.
  2. git add로 파일을 stage area에 올린다.
  3. git commit으로 stage area에 올라온 파일들을 하나의 커밋으로 만든다.

그래도 깃을 사용할 때 내부 동작을 알고 있다면 사용에 더 좋을 것 같아서 이번 포스팅에서는 git이 정확히 어떤 방식으로 동작하는지 알아보려고 한다.

Repository

Git Repositoroy는 Git으로 관리하는 프로젝트의 저장소이다.

 

주로 자신의 로컬 컴퓨터에 저장하는 저장소인 Local Repository와 github와 같은 깃 호스팅 서비스의 외부 저장소에 저장하는 Remote Repository로 나뉜다.

파일 상태

git 파일들은 총 4가지 상태를 가지고 있다.

  1. Untracked : 깃에서 버전 관리를 한 적이 없기 때문에 수정 여부를 추적할 수 없는 상태
  2. Unmodified : 수정되지 않은 파일
  3. Modified : 수정된 파일
  4. Staged : Stage 영역에 올라간 파일

어떠한 파일을 처음 만들었다면 버전 관리를 한 적이 없기 때문에 Untracked 상태가 된다.

 

새로 만든 파일을 git add 명령어를 통해 stage 영역으로 올리면 Untracked 상태에서 벗어나 staged 상태가 되고 그 때부터 수정 여부를 추적하기 시작한다.

 

Untracked 상태에서 벗어나 수정 여부를 추적하는 상태에서 해당 파일을 변경하면 Modifed 상태가 된다.

 

 

 

https://inpa.tistory.com/entry/GIT-⚡️-개념-원리-쉽게이해

 

깃 객체 (Objects)

깃 객체에 알아보기에 앞서 git objects들의 변화를 그림과 함께 상세하게 설명한 영상이 있어서 가져왔다.

 

https://www.youtube.com/watch?v=MyvyqdQ3OjI&t=159s

 

위 영상을 한 번 보면 이해가 훨씬 쉬울 것이다.

 

깃 객체의 종류는 크게 blob, tree, commit 3가지로 나뉜다.

 

git init 명령어를 사용하여 저장소를 생성할 때 .git 디렉토리가 생기게되고 그 안의 objects 디렉토리 내부에 git 객체들이 저장된다.

 

아래 사진에서 git 객체들이 저장된 모습을 볼 수 있다.

blob

깃이 데이터를 저장할 때는 key-value 형태로 저장소에 저장한다.

 

저장할 때는 고정된 40자 길이의 해시 값을 생성하여 앞 2 글자는 디렉토리 이름으로 사용하고 뒤 38자는 파일 이름으로 사용하여 저장한다.

./git/objeccts 디렉토리 구조

 

위 40자 길이의 해시를 사용하여 파일을 복원할 수 있다.

 

위와 같이 저장되는 파일 안의 값은 단지 파일 내용만 저장하고 blob 객체라고 한다.

 

파일 내용만을 저장하기 때문에 내용이 똑같은 파일이 여러 개 있어도 단 하나의 blob만 존재한다.

 

하지만 blob만 있다면 파일을 식별할 수 없기 때문에 tree가 필요하다.

tree

tree에는 blob과 다르게 파일 이름과 이 파일의 해시 값을 함께 저장한다.

 

tree 하나는 여러 개의 항목을 가질 수 있고 그 항목은 blob이나 tree가 될 수 있다.

위 트리 구조는 디렉토리 구조를 생각하면 쉽게 이해할 수 있다.

 

위와 같은 형태의 working directory는 아래와 같은 구조일 것이다.

commit

commit은 아래와 같은 정보를 포함한다.

 

commit 객체는 tree 객체의 해시 값을 가지고 있고 이는 곧 commit이 생성되었을 시점의 working directory 정보이다.

 

그리고 주의깊게 봐야할 부분은 parent이다.

 

parent는 자신의 부모 커밋이고 이는 이전 커밋을 의미한다.

 

commit은 자식 commit 해시 값을 가지고 있지 않고 부모 commit 해시 값만을 가지고 있다.

 

이를 이해한다면 아래 그림 또한 이해할 수 있다.

 

git의 branch를 공부할 때 아래처럼 커밋들이 화살표로 이어진 형태를 많이 보았을 것이다.

https://darekkay.com/blog/git-commit-ranges/

 

화살표가 가장 마지막에 가리키는 A 커밋이 HEAD라고 생각할 수 있지만 commit 객체가 오직 부모 commit의 해시 값 즉 이전 commit의 해시 값을 가지고 있다는 것을 생각하면 A는 첫 번째 커밋이라는 것을 알 수 있다.

git add

hello.txt를 생성하고 안에 hello라는 문자열을 입력한다.

 

<hello.txt>

hello

 

git add hello.txt로 hello.txt 파일을 stage area로 올려보자.

git add hello.txt

 

hello.txt의 파일 내용에 해쉬 알고리즘을 적용하여 40자 길이의 고정 해쉬 값을 얻어낸다.

 

그 후 앞 2자는 폴더 이름으로 나머지 38자는 파일 이름으로 사용한다.

 

참고로 stage area에 올라온 파일의 정보는 .git/index 파일에 존재한다.

 

이를 확인하려면 git ls-files 명령어를 사용하면 된다.

git ls-files --stage 

 

 

stage area에 존재하는 파일들의 목록을 확인할 수 있다.

 

그러면 이번에는 hello2.txt를 만들고 똑같이 hello라는 문자열을 입력한다.

 

<hello2.txt>

hello

 

git add hello2.txt로 hello2.txt 파일을 stage area로 올려보자.

 

 

objects 내부에 파일이 새로 생기지 않았다.

 

파일 내용을 대상으로 해시 알고리즘을 적용하기 때문에 내용이 똑같다면 당연히 해시 함수도 똑같기 때문에 새로 생기지 않은 것이다.

 

git은 이러한 방식으로 중복된 값을 줄일 수 있는 것이다.

 

이번에는 stage area를 확인해보자.

 

stage area에는 hello.txt.와 hello2.txt가 존재하고 둘이 가리키는 blob 객체가 동일한 것을 확인할 수 있다.

 

그렇다면 이번에는 hello.txt의 내용을 hello1으로 바꾸고 hello2.txt의 내용을 hello2로 바꾸고 두 파일 모두 stage area 영역으로 올려보자.

 

<hello.txt>

hello1

<hello2.txt>

hello2
git add hello1.txt
git add hello2.txt

 

이전의 hello라는 내용을 가진 파일은 없어졌지만 여전히 objects에는 남아있고 hello1과 hello2에 대한 새로운 해쉬 값이 두 개 생성되어 추가된 모습을 확인할 수 있다.

 

즉 git add [파일]의 결과는 다음과 같다.

  1. 파일 내용을 해시 값으로 만들어서 .git/objects에 존재하지 않으면 추가한다.
  2. .git/index 파일(stage area)에 파일 정보를 추가한다.

git commit

이제 다시 stage area에 두 파일을 올리고 git commit 명령어를 통해 stage area에 올라온 파일들로 커밋을 만들어보자.

git add *
git commit -m "첫 번째 커밋"

 

 

이번에는 두 개의 파일이 생성되었다.

 

하나는 tree 객체이고 하나는 commit 객체이다.

 

tree 안에는 blob 객체 혹은 tree 객체들이 담겨있다.

 

기존에 stage area에 올라온 파일은 hello.txt와 hello1.txt가 있었기 때문에 tree 객체에는 이 파일들에 대한 정보가 담겨있다.

 

git cat-file 명령어를 사용하면 해시 값에 해당하는 객체의 정보를 확인할 수 있다.

git cat-file -p [해시 값]

 

위 사진은 tree 객체에 대한 정보이다.

 

commit 객체도 확인해보자.

commit 객체는 같이 생성된 tree 객체를 가리키고 있고 author, commiter, 커밋 메시지를 확인할 수 있다.

 

즉 git commit의 결과는 다음과 같다.

  1. 현재 stage area에 올라온 파일들의 정보를 모두 담아서 tree 객체를 만든다.
  2. 위에서 만든 tree 객체를 가리키고 커밋에 대한 정보를 담은 commit 객체를 만든다.

참고로 tree 객체는 내부에 blob 객체만 저장하지 않고 하위 tree 객체 또한 저장할 수 있다.

 

이렇게 tree 객체 안에 tree 객체가 존재하는 형식은 디렉토리 안에 하위 디렉토리가 있는 구조를 생각하면 편하다.

 

이번에는 하위 디렉토리 안에 hello3.txt 파일을 만들어보자.

 

dir 디렉토리 안에 hello3라는 문자열을 가진 hello3.txt 파일을 만들었다.

 

<hello.txt>

hello3

 

git add 명령어를 통해 stage area로 올려보자.

git add ./dird/hello3.txt

 

hello3를 내용으로 가지는 파일을 stage area에 올린 적이 없기 때문에 objects 아래에 새로운 blob 객체가 생성된다.

두 번째 커밋을 생성해보자.

git commit -m "두 번째 커밋" 

 

이번에는 첫 번째 커밋과는 다르게 세 개의 객체가 생성되었다.

첫 번째 커밋과 다르게 하위 디렉토리가 생겼기 때문이다.

 

즉 새로 만든 dir 디렉토리가 하나의 tree 객체가 된다고 생각하면 된다.

 

git cat-file 명령어로 세 객체의 값을 확인해보자.

git cat-file -p 96e8610d4da1c4c12a76d54418676666c428aa21
git cat-file -p 2fe8610d4da1c4c12a76d54418676666c428aa21
git cat-file -p c62f1784e84c3c40709854f027051417d2c5477e

 

96으로 시작하는 객체는 tree 객체임을 확인할 수 있고 이는 dir 디렉토리가 담고 있는 정보이다.

 

2f로 시작하는 객체도 tree 객체임을 확인할 수 있고 이는 현재 디렉토리가 담고 있는 정보이다.

 

위 사진을 보면 tree 객체는 blob 객체 뿐만 아니라 하위 tree 객체도 담을 수 있다는 것을 확인할 수 있다.

 

c6으로 시작하는 객체는 commit 객체임을 확인할 수 있다.

여기서 중요한 점은 두 번째 커밋을 생성할 때이다.

 

두 번째 커밋을 생성할 때 첫 번째 커밋과 달라진 점은 하위 dir 디렉토리가 생겼고 그 안에 hello3.txt.라는 새로운 파일이 생겼다는 점이다.

 

기존에 존재하던 tree 객체에는 hello.txt.와 hello2.txt만 담겨있었는데 새로운 디렉토리와 파일이 추가되었기 때문에 tree 객체의 내용은 달라진다.

 

내용이 달라진다는 것은 해시 값이 달라진다는 것을 의미하고 결국 objects 디렉토리 내부에 새로운 tree 객체가 생성된다는 뜻이다.

 

tree 객체의 중요한 특징을 정리해보자.

 

  1. blob 객체 뿐만 아니라 하위 tree 객체도 담을 수 있다.
  2. tree 객체가 하위 tree 객체를 담고 있는 구조는 디렉토리 구조를 생각하면 이해하기 쉽다.
  3. tree 객체 안의 내용이 바뀌면 tree의 해시 값이 변경되기 때문에 새로운 tree 객체가 objects 디렉토리 내부에 추가된다.

commit 객체의 중요한 특징을 정리해보자.

  1. commit 객체는 tree 객체를 가리키고 있다.
  2. 변경된 사항만을 가지고 있지 않고 모든 사항을 가지고 있다.

이전 커밋으로

이제 이전 커밋으로 돌아가보자.

 

우리가 첫 번째 커밋을 만들었을 때 objects 디렉토리에 생성된 커밋 객체의 해쉬 값은 c73e6d14f1f58de8f09e66311dacfcbd2c08d63b임을 확인했다.

 

이 해시 값을 그대로 사용해도 되고 앞 6글자만 사용해서 해당 커밋으로 되돌아갈 수 있다.

 

첫 번째 커밋으로 돌아가보자.

 

git reset --hard c73e6d

 

파일들은 첫 번째 커밋 당시의 hello.txt와 hello2.txt만 있는 상태로 돌아왔지만 objects 디렉토리 내부의 파일들은 변경되지 않았다.

 

여기서 중요한 점은 git 객체들은 불변성을 지니고 있다는 것이다.

 

한 번 생성된 git 객체들은 삭제되지 않는다.

 

그런데 이러한 방식은 공간이 낭비될 수 있어서 가비지 컬렉터 방식을 사용해서 주기적으로 참조되지 않는 git 객체를 삭제한다고 한다.

출처

전체적인 깃 설명

https://inpa.tistory.com/entry/GIT-⚡️-개념-원리-쉽게이해

tracked, untracked

https://my-develop-note.tistory.com/44

깃 레포지토리

https://dev-jacob.tistory.com/entry/Git-Repository란

깃 내부 객체

https://git-scm.com/book/ko/v2/Git의-내부-Git-개체

깃 객체 불변성

https://git-scm.com/book/ko/v2/Git의-내부-운영-및-데이터-복구#_git_gc

 

728x90
반응형

댓글