Git 뜯어먹기
![thumbnail.png](/_next/image?url=%2Fimages%2Finsight%2Fgit%2Fthumbnail.png&w=1920&q=75)
Git을 뜯어먹어보자
내게 git이라면 푸시, 풀, 머지, 커밋만 있었다. 그러니 이번 기회에 git을 한번 뜯어먹어보자.
git이 실제로 어떻게 관리되는지, 어떻게 동작하는지 알아보겠다.
Git의 구성 요소
Git은 크게 세 가지 구성 요소로 이루어져 있다.
- Working Directory: 실제 파일이 저장되는 디렉토리
- Index: 스테이징 영역으로도 불리며, 변경 사항을 저장하는 공간으로, 커밋을 준비하는 단계
- Repository: 변경 사항이 저장되는 공간으로, 실제로 버전 관리가 이루어지는 공간
git을 사용하면서 git add
, git commit
, git push
등의 명령어를 사용하는데, 이 명령어들은 각각 Index, Repository에 영향을 미치는 명령어이다.
개발자는 Working Directory에서 파일을 수정하고, 수정한 파일을 Index에 추가한 후, Index에 있는 변경 사항을 Repository에 커밋한다.
이런 내용들은 .git
디렉토리에 저장되어 있다.
.git
.git 디렉토리는 git 저장소의 모든 정보를 담고 있는 디렉토리이다. 이 디렉토리는 다음과 같은 파일과 디렉토리로 이루어져 있다.
- HEAD
- config
- description
- index
HEAD
HEAD 파일은 현재 작업 중인 브랜치를 가리키는 파일이다. HEAD 파일을 열어보면 다음과 같은 내용을 볼 수 있다.
ref: refs/heads/main
위 내용은 현재 작업 중인 브랜치가 main 브랜치라는 것을 의미한다. 만약 특정 브랜치가 아닌 특정 커밋을 가리키고 있다면, 다음과 같은 커밋의 해시값을 볼 수 있다.
1e2b4b3d4e5b6b7c8d9e0f1a2b3c4d5e6f7a8
objects
objects 디렉토리는 git의 객체를 담고 있는 디렉토리이다. git의 객체는 blob, tree, commit 등이 있다.
-
blob
파일의 내용을 담고 있는 객체로, 파일의 내용을 압축(zlib)해서 SHA-1 해시값을 구한 후, 해당 해시값의 파일로 저장한다. 파일의 메타데이터는 저장하지 않고 파일의 내용만 저장한다.
따라서, 동일한 내용의 파일이 여러개 있더라도 한 번만 저장하게 된다. -
tree
디렉토리의 내용을 담고 있는 객체이다. blob과는 다르게 식별자, 파일 데이터의 해시값, 파일 이름을 저장한다. 이름에서 알 수 있듯이 tree는 blob과 또 다른 tree로 이루어져 있다. -
commit
커밋을 담고 있는 객체로, 각 커밋별로 하나의 커밋 객체가 생성된다. tree의 해시값, author, committer, 직전 커밋 등의 정보를 담고 있다.
커밋 객체는 Linked List 형태로 이루어져 있어, 직전 커밋을 가리키는 포인터를 가지고 있다.
refs
refs 디렉토리는 브랜치, 태그 등을 담고 있는 디렉토리이다.
로컬에서 작업 중인 브랜치는 refs/heads 디렉토리에 저장되고, 원격 저장소의 브랜치는 refs/remotes 디렉토리에 저장된다.
다음과 같은 형태를 띌 수 있다.
- main
- HEAD
- main
hooks
hooks 디렉토리는 git의 훅 스크립트를 담고 있는 디렉토리이다.
훅 스크립트는 git의 특정 이벤트가 발생할 때 실행되는 스크립트로, pre-commit, post-commit 등의 훅이 있다.
husky, lint-staged 등의 라이브러리를 사용하면 이 훅 스크립트를 쉽게 작성할 수 있다.
자주 쓰는 명령어
이제 git의 구성 요소와 .git 디렉토리에 대해 알아보았으니, git을 사용하면서 자주 사용하는 명령어와 그 동작 방식을 알아보자.
원격 저장소에 변경 사항 반영하기
git add
git add
명령어는 Working Directory에서 변경된 파일을 Index에 추가하는 명령어이다.
즉, 내 작업 내용을 staged
상태로 만드는 것이다.
다만, .gitignore
파일에 등록된 파일은 git add
명령어를 사용해도 Index에 추가되지 않는다.
git commit
git commit
명령어는 Index에 있는 변경 사항을 Repository에 커밋하는 명령어이다.
커밋을 하면, Repository에 커밋 객체가 생성되고, Index에 있는 변경 사항이 Repository에 저장된다.
옵션 | 설명 |
---|---|
-m | 커밋 메시지를 작성하는 옵션으로, 커밋 메시지를 인라인으로 작성할 수 있다. |
-a | 변경된 파일을 자동으로 Index에 추가하는 옵션으로, git add 명령어를 생략할 수 있다. |
-amend | 직전 커밋을 수정하는 옵션이다. |
--no-verify | 훅 스크립트를 실행하지 않는 옵션으로, 훅 스크립트를 실행하지 않고 커밋할 수 있다. |
--allow-empty | 변경 사항이 없어도 커밋할 수 있는 옵션으로, 커밋 메시지만 작성하면 커밋할 수 있다. |
--no-edit | 직전 커밋 메시지를 그대로 사용하는 옵션으로, 커밋 메시지를 수정하지 않고 커밋할 수 있다. |
git push
git push
명령어는 Repository에 있는 변경 사항을 원격 저장소에 업로드하는 명령어이다.
원격 저장소에는 브랜치가 있고, git push
명령어를 사용하면 해당 브랜치에 변경 사항이 업로드된다.
옵션 | 설명 |
---|---|
-u | 원격 저장소와 로컬 저장소를 연결하는 옵션으로, 원격 저장소를 설정할 수 있다. |
원격 저장소에서 변경 사항 가져오기
git fetch
git fetch
명령어는 원격 저장소의 변경 사항을 가져오는 명령어이다.
git fetch
명령어를 사용하면, 원격 저장소의 변경 사항을 로컬 저장소로 가져올 수 있다.
병합과 재배치
3-way merge
3-way merge는 merge의 기본 동작 방식으로, 두 브랜치에 신규 commit이 한개 이상 있을 때 사용된다.
3-way merge는 새로운 병합 커밋을 생성한다.
아래와 같은 명령어로 브랜치를 병합할 수 있다.
git merge feature
fast-forward
fast-forward는 브랜치가 한쪽으로만 이동할 때 사용된다.
즉, 기준 브랜치에는 새로운 커밋이 없고, 다른 브랜치에만 새로운 커밋이 있을 때 사용된다.
기준이 되는 브랜치에 새로운 커밋이 없다면 자동으로 fast-forward 병합이 된다.
만약 강제로 fast-forward 병합이 아닌 병합을 하고 싶다면, --no-ff
옵션을 사용하면 된다.
git merge --no-ff feature
rebase and merge
rebase and merge는 우선 rebase를 진행하고 나서 merge를 진행하는 방식이다.
rebase는 이름에서 유추할 수 있듯이 브랜치의 base를 옮기는 작업이다.
그리고 이걸 fast-forward 병합하는 것이 rebase and merge이다.
git switch feature
git rebase main
git switch main
git merge feature
이렇게 하면 커밋 이력이 깔끔하게 유지된다.
squash merge
squash merge는 여러 커밋을 하나로 합치는 방식이다.
여러 커밋을 하나로 합치면, 커밋 이력이 깔끔해지고, 커밋 메시지도 한 번만 작성하면 된다.
git switch main
git merge --squash feature
git commit -m "Merge"
이렇게 하면 여러 커밋을 하나로 합칠 수 있다.
병합 충돌
git merge 에서의 ours, theirs
ours
와 theirs
는 병합 충돌 시 사용되는 키워드이다.
ours
: 현재 브랜치의 변경 사항theirs
: 병합 대상 브랜치의 변경 사항
git merge feature
git checkout --ours file.txt
git checkout --theirs file.txt
git checkout --ours
명령어는 현재 브랜치의 변경 사항을 적용하고, git checkout --theirs
명령어는 병합 대상 브랜치의 변경 사항을 적용한다.
git rebase 에서의 ours, theirs
그러나 rebase 상황에서 발생한 충돌은 ours
와 theirs
가 반대로 적용된다.
ours
: HEAD가 가리키고 있는 것theirs
: rebase 대상 브랜치가 가리키고 있는 것
이렇게 되는 이유는, rebase시에 먼저 HEAD를 옮기고, 그 다음에 rebase 대상 브랜치를 옮기기 때문이다.
기타 명령어
git checkout
git checkout
명령어는 브랜치를 변경하거나, 파일을 복구하는 명령어이다.
브랜치를 변경할 때는 git checkout -b
명령어를 사용하고, 파일을 복구할 때는 git checkout
명령어를 사용한다.
옵션 | 설명 |
---|---|
-b | 새로운 브랜치를 생성하는 옵션으로, 브랜치를 생성하고 변경할 수 있다. |
-f | 변경 사항을 무시하고 브랜치를 변경하는 옵션으로, 변경 사항을 덮어쓸 수 있다. |
git reset
git reset
명령어는 커밋을 초기화하는 명령어이다.
옵션 | 설명 |
---|---|
--soft | 커밋을 초기화하고, 변경 사항을 Index에 추가하는 옵션으로, 커밋을 취소할 수 있다. |
--mixed | 커밋을 초기화하고, 변경 사항을 Working Directory에 추가하는 옵션으로, 커밋을 취소할 수 있다. |
--hard | 커밋을 초기화하고, 변경 사항을 모두 삭제하는 옵션으로, 커밋을 취소할 수 있다. |
git cherry-pick
git cherry-pick
명령어는 특정 커밋을 가져오는 명령어이다.
옵션 | 설명 |
---|---|
-n | 커밋을 가져오고, 커밋을 적용하지 않는 옵션으로, 커밋을 가져올 수 있다. |
git stash
git stash
명령어는 변경 사항을 임시로 저장하는 명령어이다.
옵션 | 설명 |
---|---|
save | 변경 사항을 임시로 저장하는 옵션으로, 변경 사항을 저장할 수 있다. |
pop | 변경 사항을 복구하는 옵션으로, 변경 사항을 복구할 수 있다. |
apply | 변경 사항을 적용하는 옵션으로, 변경 사항을 적용할 수 있다. |
drop | 변경 사항을 삭제하는 옵션으로, 변경 사항을 삭제할 수 있다. |
git log
git log
명령어는 커밋 로그를 확인하는 명령어이다.
옵션 | 설명 |
---|---|
--oneline | 간략하게 커밋 로그를 확인하는 옵션으로, 한 줄로 커밋 로그를 확인할 수 있다. |
--graph | 그래프 형태로 커밋 로그를 확인하는 옵션으로, 그래프 형태로 커밋 로그를 확인할 수 있다. |
--decorate | 브랜치, 태그 등을 표시하는 옵션으로, 브랜치, 태그 등을 표시할 수 있다. |
git reflog
git reflog
명령어는 HEAD의 이동 기록을 확인하는 명령어이다.
이것을 이용해서 git reset --hard
명령어로 되돌린 커밋을 다시 복구할 수 있다.