git init 명령어를 입력하면 만들어지는 " .git " 이라는 숨겨진 폴더에 파일들이 어떤 일을 하는지 살펴봄으로써, 깃의 원리에 대해 알아본다.
index와 objects/
add의 원리
깃 프로젝트 폴더에서 "hello" 라는 내용이 적혀있는 "greet.txt"라는 파일을 새로 만들었다고 가정해보자. 그런 다음 git add greet.txt 명령어를 입력하면, greet 파일은 staging area에서 commit을 대기하고 있는 상태가 된다. 이 때 .git 폴더에서는 무슨 일이 일어날까?
폴더에는 index라는 새로운 파일이 추가된다. 이 파일에는 우리가 add한 파일의 이름과 복잡하고 긴 문자열이 기록된다. 이 복잡하고 긴 문자열은 objects폴더 안에 새로 생기는 파일의 이름인데, 이 파일에는 우리가 파일에 작성한 "hello"라는 내용이 기록되어 있다. 이렇게 파일의 내용이 담겨져 있는 파일을 blob 파일이라고 한다.
이 때 파일의 이름(복잡하고 긴 문자열)은, 그 파일의 내용(hello)에 따라 정해진다. A라는 사람이든, B라는 사람이든 hello라고 적힌 파일을 add하면, hello가 저장된 파일의 이름(긴 문자열)이 같다는 뜻이다.
이 원리는 SHA1 online이라는 사이트에서 직접 확인해 볼 수 있는데, 내가 어떤 내용을 입력하면 그 내용이 저장될 파일의 이름을 반환한다. 이 이름을 해쉬코드라고 부른다.
commit의 원리
이번에는 stage에 등록된 것들을 git commit -m "Add greet file" 를 이용해 commit 해 보자.
이 때 objects 폴더에는 또 다른 해쉬코드로 이루어진 파일이 만들어진다. 이 파일은 commit 파일이다. 커밋에 대한 정보(커밋한 사람, 그 사람의 이메일, 커밋 메세지)를 저장하고 있고, 동시에 [tree] 해쉬코드 파일이라는 것을 가리키고 있다. tree 해쉬코드 파일을 따라가 보자.
tree 역시 objects 폴더에 저장되어 있는 또 다른 파일이다. 이 파일은 다른 해쉬코드를 저장하고 있는데, 이 코드는 greet.txt의 내용을 저장하고 있는 파일의 이름을 가리키고 있다.
만약 여기에서 greet 파일에 nice to meet you라는 내용을 추가한 뒤에 다시 add, commit을 하면 무슨 일이 일어날까? 다양한 변화가 생기겠지만, 우선 objects 폴더에 commit 파일이 생긴다. 이 파일은 커밋 정보, [tree]와 새로운 [parent]라는 정보를 가지고 있다. 이 때 parent가 가리키는 것은 greet 파일과 관련된 이전의 커밋 , 즉 "Add greet file" 정보를 담고 있는 그 파일이다.
정리해 보면 objects 폴더에는 commit, tree, blob 세 종류의 파일이 생성된다. commit은 tree와 parent 정보를 가지고 있고, parent는 또 다른 커밋을, tree는 파일의 이름을 가리키고 있다. tree에 저장되는 파일의 이름은, 다시 파일의 내용을 가리키는 blob을 가리키고 있다. 이 blob 파일은 바로 우리가 add할 때 생성된 파일이다.
HEAD와 refs/heads/
commit
우리가 첫번째 커밋을 하면, HEAD라는 파일 내용에 refs/heads/master라는 것이 추가된다. 폴더의 경로를 따라 파일을 열어보면 어떤 해쉬코드가 저장되어있는데, 이 코드는 우리의 첫번째 커밋, 즉 최신 커밋을 가리킨다.
branch
이번에는 feature라는 이름의 브랜치를 만들어보자. 우리가 브랜치를 만들면 refs/heads/feature라는 파일이 생기고, 이 파일 역시 우리의 최신 커밋을 가리킨다. 그런 다음 git checkout feature 명령어를 입력하면 HEAD 파일의 내용이 refs/heads/feature로 바뀐다. 다시 이 내용이 가리키는 것은 다시 어떤 해쉬코드, 우리가 등록한 최신 커밋이 된다.
reset & reflog
우리가 reset --hard 해쉬코드 명령어를 이용해서, 특정 커밋 이후의 커밋들을 삭제하고 특정 커밋으로 돌아가는 reset 명령어를 이용하는, 즉 과거의 상태로 돌아가는 것도 refs/heads/현재 브랜치가 가리키고 있는 커밋을 바꾼다. 그러고보니 Undo 챕터에서 reset을 취소하는 명령어를 정리했는데, 우리가 삭제한 커밋을은 어디로 가는 걸까?
우리가 reset 명령어를 이용하면 ORIG_HEAD라는 파일이 생성되고, 이 파일은 우리가 삭제한 커밋의 해쉬코드를 저장하고 있다. 즉, 우리가 삭제한 커밋 파일을 가리키고 있다. 즉 git reset --hard ORIG_HEAD라는 명령어를 입력하면 우리가 커밋을 삭제하기 이전 상태로 돌아갈 수 있다는 것을 의미한다.
ORIG_HEAD 뿐만 아니라 logs/refs/heads/master 파일도 생성되는데, 여기에는 더 자세한 정보들이 담겨있다. 우리가 git reflog 명령어를 작성했을 때 출력되는 내용이, 바로 이 파일에 기록된 내용들이다. 여기에 기록되어 있는 해쉬코드를 reset 명령어에서 유용하게 이용할 수 있다.
detached HEAD
우리가 어떤 브랜치가 아니라, 커밋으로 체크아웃을 하면 'you are in detached HEAD state now' 라는 도움말이 출력된다. 이 때 git status를 살펴보면 HEAD가 refs/heads/브랜치 폴더 안에 생성되는 해쉬코드가 아니라, 해당 커밋을 직접 가리키고 있는 것을 알 수 있다. 뿐만 아니라 git log를 살펴보면, 해당 커밋을 최신 커밋으로 하는 log를 보여준다.
이 상태는 브랜치에 속해 있으면서 커밋을 가리키는 것이 아니라, 커밋 그 자체를 가리킨다. 이것은 크게 쓸모가 없기 때문에 git checkout 브랜치로 돌아가서 작업을 한다.
merge와 conflict
두 개의 브랜치에서 하나의 파일의 같은 라인을 수정한 뒤에 두 브랜치의 병합(merge)을 시도하면, merge conflict이 발생한다. 이 때 index에는 우리가 수정한 파일의 이름과 그 파일의 내용을 담고있는 3개의 해쉬코드를 저장한다.
예를 들어, "안녕하세요"라고 작성되어있는 common.txt라는 파일이 있었다. 이 때 master 브랜치에서는 common.txt 파일의 첫번째 줄을 "hello"라고, exp 브랜치에서는 "bonjour"라고 수정했다면 index에는 다음 3개의 해쉬코드가 저장된다.
첫번째 해쉬코드에는 안녕하세요가 저장되어있다. 두번째, 세번째 해쉬코드에는 각각 hello와 bonjour가 저장되어 있다. 깃은 이 세개의 파일을 바탕으로 merge를 시도한다.
auto merge에 실패해서 conflict이 발생하면, 자동으로 common.txt 파일의 내용을 아래와 같이 변경하고, 이 내용과 똑같은 컨텐츠를 갖고 있는 common.txt.orig라는 파일이 생성된다. 이 이후에는 충돌을 해결하는 방법에서 정리한 내용(git mergetool)을 확인하면 된다.
<<<<<<< HEAD
hello
=======
bonjour
>>>>>>> exp
'Study > Git & Github' 카테고리의 다른 글
etc (0) | 2022.04.16 |
---|---|
Remote (0) | 2022.04.15 |
Undo (0) | 2022.04.14 |
Stash (0) | 2022.04.13 |
Branch & Merge (0) | 2022.04.11 |