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

Git 동작 원리 — Git init → add → commit → checkout → merge 완전 추적기

by 뜨거운 개발자 2025. 5. 2.
728x90

0. 프롤로그

오소소 과제랍니다.

그동안 Git을 많이 사용했지만 내부 원리는 정확히 몰랐다. Git에 대한 개요를 설명하고 원리부터 명령 흐름까지 Git 전 과정( init → add → commit → checkout → merge )과 .git/index, .git/objects 구조를 단계별로 확인해보겠다.

 

1. 탄생 배경과 설계 목표

Linux 커널은 1991년부터 2002년까지 패치와 tarball로만 관리됐다. 2002년에는 BitKeeper라는 상용 DVCS를 도입했지만 2005년에 무료 라이선스가 철회됐고, 그때 Linus Torvalds가 직접 만든 도구가 Git이다. Git은 아래 다섯 가지 목표를 세웠다.

고성능 수 초 내에 수만 개 파일 히스토리 탐색
단순성 변경 이력 = 스냅샷 체인
완전 분산 모든 클라이언트가 전체 히스토리 보존
브랜치 대량 처리 수천 개 동시 브랜치에서도 O(1) 수준 전환
대형 코드베이스 지원 Linux 커널(수십 GB)도 무리 없이 처리

2005년 첫 릴리스 이후 지금까지 이 목표는 그대로다. 성숙해졌지만 철학은 변하지 않았다.

2. Git? 그 전에 버전관리

버전 관리 시스템(VCS - Version Control System)은 파일 변화를 시간에 따라 기록했다가 나중에 특정 시점의 버전을 다시 꺼내올 수 있는 시스템이다. 버전 관리는 소프트웨어 소스 코드 뿐만 아니라 실제로 거의 모든 컴퓨터 파일의 버전을 관리할 수 있다.

VCS를 사용하면 각 파일을 이전 상태로 되돌릴 수 있고, 프로젝트를 통째로 이전 상태로 되돌릴 수 있고, 시간에 따라 수정 내용을 비교해 볼 수 있고, 누가 문제를 일으켰는지도 추적할 수 있고, 누가 언제 만들어낸 이슈인지도 알 수 있다. VCS를 사용하면 파일을 잃어버리거나 잘못 고쳤을 때도 쉽게 복구할 수 있다.

많은 사람은 버전을 관리하기 위해 디렉토리로 파일을 복사해서 사용한다. 하지만 이 방법은 단점이 많다. 실수로 폴더를 지우거나 파일을 잘못 복사하거나 잘못 고칠수도 있다. 또한 모든걸 복사해야하기 때문에 용량도 많이 차지한다. 이러한 이유로 사람들은 로컬 VCS를 만들었다.

2.1 로컬 VCS

local vcs

로컬 VCS는 아주 간단한 데이터베이스를 사용해서 파일의 변경 정보를 관리했다. 많이 쓰는 VCS 도구 중에 RCS(Revision Control System)라고 부르는 것이 있는데 오늘날까지도 아직 많은 회사가 사용하고 있다. RCS는 기본적으로 Patch Set(파일에서 변경되는 부분)을 관리한다. 이 Patch Set은 특별한 형식의 파일로 저장한다. 그리고 일련의 Patch Set을 적용해서 모든 파일을 특정 시점으로 되돌릴 수 있다.

2.2 중앙집중식 버전 관리(CVCS)

프로젝트를 진행하다 보면 다른 개발자와 함께 작업해야 하는 경우가 많다. 이럴 때 생기는 문제를 해결하기 위해 CVCS(중앙집중식 VCS)가 개발됐다. CVS, Subversion, Perforce 같은 시스템은 파일을 관리하는 서버가 별도로 있고 클라이언트가 중앙 서버에서 파일을 받아서 사용(Checkout)한다. 수년 동안 이러한 시스템들이 많은 사랑을 받았다.

CVCS 환경은 로컬 VCS에 비해 장점이 많다. 모두 누가 무엇을 하고 있는지 알 수 있다. 관리자는 누가 무엇을 할지 꼼꼼하게 관리할 수 있다. 모든 클라이언트의 로컬 데이터베이스를 관리하는 것보다 VCS 하나를 관리하기가 훨씬 쉽다. 하지만 이 방식은 중앙 서버에 문제가 생기면 협업에 치명적인 문제가 생긴다. 그래서 등장한 방법이 분산버전 관리 시스템이다.

2.3 분산 버전 관리 시스템(DVCS)

Git, Mecurial, Bazaar, Darcs 같은 DVCS에서의 클라이언트는 단순히 파일의 마지막 스냅샷만 Checkout 하지 않는다. 그냥 저장소를 히스토리와 전부 복제한다. 서버에 문제가 생기면 이 복제물로 다시 작업을 시작할 수 있다. 클라이언트 중에서 아무거나 골라도 서버를 복원할 수 있다. 즉 Clone은 모든 데이터를 가진 진정한 백업이다.

대부분의 DVCS 환경에서는 리모트 저장소가 존재하고 리모트가 많을 수도 있다. 그래서 사람들은 동시에 다양한 그룹과 협업할 수 있다. 계층 모델 같은 중앙집중식 시스템으로는 할 수 없는 워크플로를 다양하게 사용할 수 있다.

3. Git 과 다른 VCS의 차이점

3‑1. 델타가 아닌 스냅샷 스트림

대부분 VCS 시스템은 관리하는 정보가 파일들의 목록이다. CVS, Subversion, Perforce, Bazaar 등의 시스템은 각 파일의 변화를 시간순으로 관리하면서 파일들의 집합을 관리한다. 이를 델타 기반 버전관리 시스템이라 한다.

각 파일의 변화를 저장하는 시스템들

다른 VCS는 파일 변경분(델타)를 저장하지만 Git은 프로젝트 스냅샷을 통째로 저장한다. 달라지지 않은 파일은 이전 스냅샷을 가리키는 링크만 남긴다. 즉 Git은 파일의 변화를 시간순으로 관리하지 않고 스냅샷의 연속으로 관리하기 때문에 과거 사용하던 VCS와는 구분된다. Git은 강력한 도구를 지원하는 작은 파일시스템으로 이해하자.

3‑2. 로컬 실행 능력

Git은 거의 모든 명령을 로컬에서 실행하기 때문에 네트워크에 있는 다른 컴퓨터는 필요 없다. 대부분의 명령어가 네트워크의 속도에 영향을 받는 CVCS와의 차이점이다. 그것이 가능한 이유는 프로젝트의 모든 히스토리가 로컬 디스크에 있기 때문이다.

3‑3. 무결성 보증

Git은 객체를 쓰기 전에 SHA‑1(옵션으로 SHA‑256) 해시를 계산한다. 40 글자 16진수 값(예: 24b9da6552…)이 곧 파일 이름이다. Git이 아니면 내용을 바꿔도 해시가 달라져서 히스토리가 끊긴다.

Git은 모든 것을 해시로 식별하기 때문에 이런 값은 여기저기서 보인다. 즉 Git은 파일을 이름으로 저장하지 않고 해당 파일의 해시로 저장한다. Git에 특정 작업을 하면 Git 데이터베이스에 데이터가 추가된다. 한번 커밋하고 나면 데이터를 쉽게 잃어버리지 않는다는 것이 특징이다. (git log 명령중 git log -p 는 꽤 유용하다. git diff를 같이 해주기 때문이다. — stat 옵션도 사용해보자)

4. Git 파일 상태와 세 공간

git 상태

공간 파일 상태 설명
Working Directory Untracked / Modified 실제 파일 작업 공간
Staging Area(index) Staged 곧 커밋할 스냅샷 목록
Repository(.git/objects) Committed 영구 스냅샷 DB

git status가 이 세 상태를 컬러로 보여 준다.

파일의 라이프 사이클

5. Git 설정 필수 명령

다음으로 git 필수 설정에 대해 알아보자.

git config --global user.name  "haward"
git config --global user.email haward@example.com
git config --global core.editor vim   # 원하면 변경
git config --list                     # 설정 확인

설정 파일 우선순위는 .git/config > ~/.gitconfig > /etc/gitconfig이다.

6. Git 명령 흐름과 내부 구조

Git 명령어 (init → add → commit → checkout → merge) 를 따라가면서 Git 내부 구조가 어떻게 변하는지 이해해보겠다.

아래의 parser를 활용해서 내부 동작을 확인한다.

https://github.com/charsyam/git-parser

 

GitHub - charsyam/git-parser

Contribute to charsyam/git-parser development by creating an account on GitHub.

github.com

 

6‑1. git init

git init
ls -aR .git

.git/ 폴더가 생긴다. objects, refs, HEAD 등 기본 디렉터리가 초기화된다.

6.2 git add

git add 명령은 현재 파일을 staging area 에 올리는 명령이다.

Git에서는 staging area 를 .git/index 에서 관리한다.

파일을 생성하고 git status 를 통해, 확인해보면 현재 파일의 상태는 untracked 이다.

새로운 파일을 생성하면 untracked file로 a 가 나오는 걸 볼 수가 있다.

echo "hello" > a.txt
git status        # a.txt = Untracked
git add a.txt
git status        # a.txt = Staged

확인해보면 git add를 진행했을 때 새로운 파일이 tracked상태로 변경됐고 git 에 objects에 해시값 파일이 생긴 걸 볼 수가 있다.

Git에서는 staging area 를 .git/index 에서 관리하기 때문에 이전에 없던 index 파일도 생겼다.

Index파일을 확인하려면 cat 으로만으로는 볼 수 없다.

따라서 parser를 이용해 확인했다.

parser로 index를 확인해보면 아래와 같은 형식이 된다.

mode hash값 파일크기 파일경로

여기서 mode 의 경우, 리눅스 파일 저장 시스템의 inode 의 mode 값과 같다. 100644 는 100(일반 파일을 의미) + 644(권한: rw-r--r--) 을 의미한다.

실제 index는 더 많은 정보를 담고 있지만 다음과 같은 순서로 파싱되었다.

Object 확인해보기

b.txt 파일을 추가해주고

오프젝트 파일을 열어주면 blob이라는 정보를 볼 수가 있다.

여기서 blob이 무엇인지 알려면 Commit 객체의 종류에 대해 알아야한다.

6.3 Commit 객체의 종류

object의 타입 즉 커밋 객체의 타입은 크게 blob, commit, tree, tag 로 나뉜다.

처음으로 파일을 생성하면 blob객체가 생성된다.

위 그림은 다음과 같은 명령을 실행하면 생기는 커밋 객체들의 모양이다.

$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

 

즉 파일의 내용이 바뀌면 저장되는 blob과 파일 시스템을 다루는 tree 그리고 commit 객체가 추가가 된다.

여기서 blob과 tree commit 객체를 직접 실습해보며 확인해보자.

6.4 COMMIT

이렇게 확인해보면 위에서 봤듯이 4개의 object가 생성된 걸 볼 수가 있다.

올려보기 귀찮은 사람들이 있을테니 바로 비교해보겠다 왼쪽이 add만 했을때 오른쪽이 commit 까지 한 지금이다.

전/후

 

object 폴더는 9d 62 로 2개의 blob object 밖에 없었는데 2개의 오브젝트가 더 생긴 걸 볼 수가 있다.

또 자세히 보면 COMMIT_EDITMSG와 logs description이 추가가 된 걸 볼 수가 있다. 

전/후

COMMIT object를 먼저 확인해보자.

커밋 오브젝트 객체 는 treeparent 그리고 커밋 관련 정보 (authorcomittercommit message) 로 구성되어 있다.

Tree 객체를 확인해보면 blob객체의 위치를 가리기고 있는 것이 보인다. (폴더명 + hash값)

6.5 Tree 객체 더 살펴보기

Tree객체에 대해 더 설명하려면 tree 객체는 리눅스 파일 시스템과 같이 디렉토리 구조를 트리 형태로 추상화 한 것이다.

폴더와 파일을 한번 add 하고 commit 해보면 다음과 같다.

다음과 같이 계층 구조를 add 해보자.

갓 파일의 blob위치를 index를 통해 파악하고 object를 살펴보자.

만든 blob 들의 위치는 잘 들어갔다.

 

이제 Tree를 추가하려면 COMMIT을 진행한다.

commit 객체를 살펴보면 Parent는 이전 COMMIT 객체를 가리키고 있는걸 볼 수가 있다.

commit에 해당하는 tree를 확인해보면 다음과 같이 파일시스템을 계층 구조로 표현한 걸 볼 수가 있다.

 

6.6 파일 수정하기

b.txt를 직접 수정하고 index와 blob 이 어떻게 변하는지 확인해보자.

이후 인덱스를 확인해보면 달라진걸 확인할 수 있다.

전/후

blob object를 가리키는 위치가 달라졌다. 다만 이렇게 변경되더라도 새로운 blob을 생성하고 이전의 blob을 지우지는 않는다.

 

6.7 git checkout -b feature/test1

- refs/heads/feature/test1 생성(현재 commit SHA‑1).
- HEAD가 feature/test1을 가리킨다.

 

해당 명령을 통해 브랜치를 생성하고 전환할 수 있다.

브랜치는 커밋 객체를 가리키는 파일 한 줄이다. 포인터 이동만 한다.

다음과 같이 브랜치를 변경하면 HEAD 파일에 현재 작업중인 브랜치를 알려준다.

브랜치의 저장 위치는 위와 같다.

브랜치를 제대로 이해하기 위해서는 브랜치가 포인터와 같은 느낌이라는 것을 이해하면 좋다.

브랜치에 대한 이해가 부족하다면 아래 링크를 꼭 읽고 오자.

https://git-scm.com/book/ko/v2/Git-%eb%b8%8c%eb%9e%9c%ec%b9%98-%eb%b8%8c%eb%9e%9c%ec%b9%98%eb%9e%80-%eb%ac%b4%ec%97%87%ec%9d%b8%ea%b0%80

 

Git - 브랜치란 무엇인가

3.1 Git 브랜치 - 브랜치란 무엇인가 모든 버전 관리 시스템은 브랜치를 지원한다. 개발을 하다 보면 코드를 여러 개로 복사해야 하는 일이 자주 생긴다. 코드를 통째로 복사하고 나서 원래 코드와

git-scm.com

즉  각 branch 를 옮겨다닌다는 것의 의미는 HEAD 포인터를 옮기고자 하는 브랜치로 이동을 한다.

브랜치는 커밋 객체를 가리키는 상태라고 했으니 다음과 같이 브랜치는 커밋 객체를 옮겨다니는 모습과 같다.

즉 브랜치라는 포인터 개념을 도입해 우리는 객체를 직접 복사해서 버전을 관리하는게 아니라 포인터의 위치만 기억해주면 되는 것이다.

 

6.8 git merge

git merge 를 통해 feature/test1 과 main 을 머지해보자.

병합에는 크게 2가지 종류가 있는데 fast-forward 와 3-way merge가 존재한다.

이것 역시 아래 링크를 참고하자.

https://git-scm.com/book/ko/v2/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%B8%8C%EB%9E%9C%EC%B9%98%EC%99%80-Merge-%EC%9D%98-%EA%B8%B0%EC%B4%88

 

Git - 브랜치와 Merge 의 기초

Merge 시에 발생한 충돌을 다루는 더 어렵고 요상한 내용은 뒤에 고급 Merge 에서 다루기로 한다.

git-scm.com

merge는 크게 2가지 종류가 있는데 Fast‑Forward와 3‑Way 머지가 있다.

6.8.1 Fast‑Forward

git checkout main
git merge feature/test1   # 다른 커밋이 없을 때

main 포인터를 feature/test1 위치로 이동만 한다. 새 커밋이 생기지 않는다.

그림은 다르지만 이 그림에서 master가 main이고 iss53이 test1 브랜치라고 이해하자.

6.8.2 3‑Way

두 브랜치 모두 추가 커밋이 있으면 3‑way 머지가 일어난다.

  1. 공통 조상, 브랜치 A, 브랜치 B 추출.
  2. diff 병합 → 충돌 해결.
  3. 머지 커밋 M 생성(부모 두 개).
  4. 현재 브랜치 포인터가 M을 가리킨다.

여기선 merge를 위해 새로운 commit 객체가 생성된다.

7. 실수 복구 레시피

만약 실수 했을 때 사용할만한 명령이다. 

다만 주의해서 사용하도록 하자.

스테이징 취소 git reset HEAD <file> index → Modified
워킹 트리 변경 취소 git checkout -- <file> Modified → 마지막 커밋
커밋 메시지 등 덮어쓰기 git commit --amend 최신 커밋 교체
임시 대피 git stash push -m "WIP" 워킹 트리+index를 스택에 저장

8. 결론

  1. Git은 스냅샷 DB다.
  2. 모든 데이터는 SHA‑1 검증으로 무결성을 가진다.
  3. 브랜치·체크아웃·머지는 포인터 이동이므로 빠르다.
  4. 로컬에 전체 히스토리가 있어서 네트워크가 느려도 상관없다.
  5. 실수는 대부분 복구할 수 있지만 커밋하지 않은 내용은 복구 못 한다는 점만 기억하면 된다.

오늘은 Git 에 대해 알아보았다. 사실 더 많은 기능이 있고 모든 부분을 알고 싶어져서 다음에는 오늘 못 다룬 cherry-pick, reset 등은 어떻게 동작하는지와 stash의 동작원리 등에 대해서 더 알아보도록 하겠다.

728x90