개념 #
Git은 소스 코드의 버전을 관리하게 해주는 소스코드 버전 관리 시스템이다. 데이터를 저장할 공간만 있다면 어디서나 사용할 수 있다. 개인 컴퓨터, USB, 클라우드 등등.
Git으로 관리하는 프로젝트를 올릴 수 있는 호스팅 사이트 중 하나가 GitHub이다. 깃랩 등 다른 것도 당연히 있다. 아무튼 데이터를 대신 클라우드로 저장해 주는 GitHub을 쓰면 시공간 제약 없이 Git을 사용해 협업할 수 있다.
설치 #
git 공식 사이트의 안내를 따라하면 된다. homebrew로 간단히 설치 가능.
brew install git
그리고 터미널에서 git
명령어를 치면 설치가 잘 되었는지 확인할 수 있다.
기본 #
터미널의 폴더 경로에서 git init
으로 git 로컬 저장소를 초기화하고 버전 관리 시작 가능. git 버전들의 정보와 원격 저장소 주소 등이 담긴 .git
폴더가 생성된다. 이 .git
폴더를 로컬 저장소라 부른다. 이렇게 한 다음부터 이 폴더에서 버전 관리를 할 수 있다.
정보 등록 #
git config --global user.name "사용자 이름"
으로 사용자 이름을 등록한다. git config --global user.email "사용자 이메일"
로 이메일을 등록한다. GitHub를 쓸 거니까 거기의 계정 정보를 등록하면 된다.
커밋 만들고 되돌리기 #
Git에서는 생성된 각 버전을 커밋이라고 부른다.
git add 파일명 # 커밋에 추가할 파일 선택
git commit -m "커밋 메시지" # 커밋에 대한 설명을 하는 메시지와 함께 커밋 만들기
# -m은 message 약자
git log
로 커밋 목록을 볼 수 있다. 여기서 특정 커밋으로 돌아가려면 로그의 커밋 아이디를 써서 git checkout 커밋아이디
를 입력한다.
다시 최신 커밋으로 돌아가려면 이전에 git log에 있었던 최신 커밋 아이디를 써도 되지만 git checkout -
를 입력하면 된다. git checkout
을 쓰면 원하는 시점으로 파일을 되돌릴 수 있다.
원격 저장소 #
GitHub에 새로운 저장소를 만들고 로컬 저장소와 연결하려면 다음과 같이 한다. 새 저장소 만드는 건 그냥 GitHub에서 만들면 된다. 로컬 저장소에 GitHub 원격 저장소 주소를 등록하면 된다.
git remote add origin https://github.com/witch-factory/itshirt.git
이제 git push origin 원격저장소브랜치명
으로 로컬 저장소의 커밋을 원격 저장소로 올릴 수 있다. 예를 들어 git push origin main
로 하면 된다.
내려받기 #
내려받는 건 git clone
을 쓰면 된다. GitHub에서 내려받을 저장소의 주소를 복사해서 터미널에 붙여넣으면 된다.
git clone 저장소주소
그리고 뒤에 .
을 찍으면 현재 폴더에 저장소를 내려받는다.
git clone 저장소주소 .
이렇게 하면 이전 버전들과 원격저장소 주소 등을 그대로 받아온다. Download ZIP을 통해 받으면 이전 버전들과 원격 저장소 정보가 없고 그냥 파일만 받아온다.
이렇게 받아온 로컬 저장소에서도 add, commit, push 등을 통해 원격 저장소에 반영이 가능하다.
이런 변경 사항을 다른 저장소에서 받아오려면 git pull
을 쓰면 된다.
git pull origin main
push는 로컬 저장소의 커밋을 원격 저장소에 올리는 것이고 pull은 원격 저장소의 커밋을 로컬 저장소로 받아오는 것이다. 이런 변경 내역들은 git log
로 확인할 수 있다.
커밋은 버전 관리를 통해 생성된 파일 혹은 그렇게 파일을 생성하는 것을 의미한다.
레포지토리 생성 #
레포지토리를 GitHub에서 생성시 이름, 설명, 공개여부뿐 아니라 README, .gitignore, 라이센스도 템플릿 등으로 자동 생성하도록 할 수 있다.
소스트리 #
소스트리는 Git을 GUI로 사용할 수 있게 해주는 프로그램이다. 터미널로 Git을 사용하는 것보다 편리하다. 소스트리를 설치하고 GitHub 계정을 등록하면 GitHub의 저장소를 소스트리로 관리할 수 있다. 소스트리 설치는 공식 사이트에서. https://www.sourcetreeapp.com/
설치하고 나서는 로컬 저장소를 소스트리에 불러온 후 커밋해 볼 수 있다.
로컬 저장소를 추가하면 커밋을 시각화해서 볼 수 있는데 이는 .git
폴더에 저장된 정보를 읽어와서 그려주는 것이다. git init
명령어나 소스트리의 '로컬 저장소 생성'으로 생성할 수 있다. 당연히 기존에 로컬 저장소가 없는 폴더를 선택해야 함.
소스트리 커밋 히스토리에서 원격저장소이름/브랜치명
은 원격 저장소 버전을 나타내고 브랜치명
은 로컬 저장소 버전을 나타낸다. 원격 저장소 버전을 로컬 저장소로 받아오려면 브랜치명
을 더블클릭해서 체크아웃하면 된다.
Git 간단원리 #
새 버전에 반영할 변경사항을 선택하는 게 add, 이런 변경사항들을 묶어 버전으로 만든 게 커밋이라고 했다. 하지만 커밋으로 만든 버전에는 변경사항뿐 아니라 변경된 파일의 스냅샷 통째로 저장된다.
변경사항만 저장할 시 버전을 보여주려면 맨 처음까지 변경사항을 추적하면서 바뀐 점을 모두 반영하는 계산을 해야 한다. 반면 Git은 이전 커밋의 스냅샷을 그대로 불러오면 된다.
팀 개발 #
2명이 협업한다면 하나의 커밋에서 거기 연결된 2개의 커밋이 생길 수 있다. 이렇게 갈래가 생기는 것이다.
commit1 -> commit2 -> commit3
\
-> commit4
이런 갈래를 나누어 작업할 수 있는 기능을 브랜치라 한다. 이 브랜치는 커밋 그 자체가 아니라 어떤 커밋을 가리키고 있는 포인터와 같다. feature 브랜치가 commit2를 가리키고 있고 master 브랜치가 commit3을 가리키고 있고, 그런 식이다.
브랜치에서 push를 하면 새로 커밋이 올라간 후 해당 브랜치가 그 커밋의 스냅샷을 가리키게 바뀐다.
그럼 우리는 브랜치 간을 어떻게 이동할까? 브랜치 혹은 커밋을 가리키는 HEAD라는 특수 포인터가 있다. 이는 현재 브랜치에서 내가 보고 있는 커밋을 가리킨다. git checkout을 통해 HEAD를 이동시킬 수 있다. 어떤 브랜치의 어떤 버전을 볼지를 HEAD 포인터가 결정하고 있는 것이다.
다른 사람과 함께 작업하면서 내 브랜치를 만들 때, 남의 수정본이 내 푸시에 반영되지 않도록 하려면 base 브랜치를 잘 설정해야 한다.
브랜치 이동 #
브랜치 이동을 위해선 checkout, switch 명령을 쓸 수 있다.
이제는 switch를 쓰는 게 정석이 되었다고 한다.
https://blog.outsider.ne.kr/1505
브랜치 병합 #
merge는 말 그대로 두 버전의 합집합을 하는 것이다. 만약 두 브랜치간 작업한 내용 간에 충돌하는 내용이 없다면 merge commit, 한쪽 커밋이 다른 쪽에 포함되어서 한쪽으로 이동하기만 하면 되면 fast-forward merge가 된다. 추가나 충돌 없이 앞으로 이동하기만 하면 되어서 빨리감기라고 한다.
작업 내용이 충돌한다면 충돌(merge conflict)이 발생한다.
병합할 때 만들어지는 새로운 병합 커밋을 어디에 올릴지는 선택할 수 있다. A, B 브랜치의 최신 버전을 병합한 AB 커밋을 A에 올릴지 B에 올릴지 결정하는 것이다. 소스트리에서는 현재 브랜치에서 다른 브랜치를 선택하고 병합을 누르면 된다. 그럼 현재 브랜치로 해당 브랜치가 병합된다.
병합 충돌 시 현재 내가 보고 있는 HEAD, 그리고 머지되고자 하는 브랜치 이름의 버전 내용을 자동으로 git에서 정리해준다. 이를 정리하고 저장하면 충돌이 해결된다.
PR 같은 경우, PR 내용을 병합하고 나면 당장 로컬 저장소에 반영되지는 않는다. fetch를 해야 한다. fetch는 pull과 달리 코드와 상관없이 git 그래프만 업데이트한다. 즉 원격 저장소에서 변경된 사항이 "있다는 사실"만 가져오는 것이다. 실제로 변경 사항의 코드를 가져오려면 pull을 해야 한다.(책 124쪽 참고)
릴리즈 #
특정 커밋의 버전을 릴리즈할 수 있다. 이를 위해서 브랜치를 서버에 올려서 배포하고 코드 상태를 v1.0.0 등으로 태그를 붙여서 릴리즈(즉 푸시)하면 된다. 소스트리에서는 특정 커밋에 오른쪽 클릭 후 "태그"를 붙여 추가할 수 있다.
먼저 브랜치에 태그를 붙이고 push하면 자동으로 원격 저장소에 태그와 릴리즈가 생성된다.
GitHub의 레포지토리 릴리즈에 가보면 각 태그별로 압축 파일을 다운로드할 수도 있다.
fork해서 작업하기 #
레포지토리의 콜라보레이터를 추가하면 그 사람이 원본 저장소에 커밋을 직접 반영할 수 있도록 할 수 있다. 콜라보레이터가 아닌 사람이 원본 저장소에 푸시하면 에러가 나기 때문에, 콜라보레이터가 아닌 사람이 커밋을 반영하려면 fork를 한 후 원본 저장소로 풀 리퀘스트를 보내야 한다.
협업자가 많다면 이런 식으로 fork 후 풀 리퀘스트를 보내는 게 편리하다. 이렇게 하면 원본 저장소에 반영되기 전에 코드 리뷰를 받을 수도 있고 각자의 작업을 더 분리해서 관리할 수 있다.
rebase #
코드 충돌을 해결하였다. 그런데 PR을 날리려고 하는 상황 등에서, 변경->충돌->충돌해결 과정을 거치면서 충돌을 해결하느라 생긴 병합 커밋 등이 남아있을 수 있다. 이런 병합 커밋을 없애고 깔끔하게 변경사항만 남기고 싶을 때 rebase를 사용한다.
rebase하면 원격 저장소에는 꼭 force push 해야 한다.
기타 명령어 #
amend #
방금 만든 커밋에 추가하고 싶은 파일이 있을 때 쓴다. 굳이 추가적인 커밋을 안 해도 된다. amend를 쓰면 방금 했던 커밋을 수정할 수 있다. 커밋을 이미 원격 저장소에 푸시했더라도.
소스트리의 경우 커밋시 오른쪽에 있는 "커밋 옵션"의 "마지막 커밋 수정"을 누르면 된다.
이미 원격 저장소에 반영된 커밋일 경우 force push를 해서 amend를 통한 수정을 반영할 수 있다.
cherry-pick #
해당 브랜치의 모든 커밋이 아니라(이 경우 merge를 쓰면 된다) 특정 커밋만을 다른 브랜치로 가져오고 싶을 때 쓴다. cherry-pick은 특정 커밋의 내용을 다른 브랜치로 가져오는 것이다.
reset #
커밋을 취소하고 싶을 때 쓴다. reset은 커밋을 취소하고 해당 커밋 이후의 변경사항을 워킹 디렉토리로 가져온다. 이 때 옵션에 따라 워킹 디렉토리의 변경사항을 남기거나 버릴 수 있다.
mixed는 기본값으로 커밋을 취소하고 해당 커밋 이후의 변경사항을 아직 add되지 않은 상태로 워킹 디렉토리로 가져온다. soft는 커밋을 취소하고 해당 커밋 이후의 변경사항을 스테이징 영역에 올린다(add). hard는 커밋을 취소하고 해당 커밋 이후의 변경사항을 아예 버린다.
revert #
revert는 커밋의 변경사항을 되돌리는 새 커밋을 만든다. reset은 커밋을 취소하고 해당 커밋 이후의 변경사항을 워킹 디렉토리로 가져오는 것이고 revert는 커밋의 변경사항을 되돌리는 새 커밋을 만드는 것이다.
stash #
작업 중인 파일을 임시로 저장하고 다른 브랜치로 이동하고 싶을 때 쓴다. stash는 작업 중인 파일을 임시로 저장하고 워킹 디렉토리를 깨끗하게 만든다. 이후 다시 원래 브랜치로 돌아와서 stash pop을 하면 작업 중인 파일을 다시 가져올 수 있다.
소스트리에서는 상단의 "스태시" 버튼을 누르면 된다. 불러올 때는 왼쪽의 "치워두기" 탭을 누르면 된다.
터미널로 실습하기 #
git status로 현재 상태를 확인할 수 있는데 git status -s로 간단하게 확인할 수 있다. git status -s는 변경된 파일을 한 줄로 보여준다.
git 로컬 저장소 없애는 법 : 그냥 .git 폴더를 지우면 된다.
git config로 설정 변경 가능. --local
, --global
, --system
옵션으로 설정을 변경할 수 있다. --local
은 현재 저장소, --global
은 사용자, --system
은 시스템 전체에 적용된다. 또한 git config --list
로 전체 설정을 확인할 수 있다.
다음 명령으로 파일을 스테이징에서 내릴 수 있다. git restore 가 이런 기능을 하고, 이 방식으로 스테이징 안된 영역의 변경사항을 되돌릴 수도 있다. --staged
옵션을 붙이면 스테이징 영역에서 내릴 수 있다.
https://kks2140501.tistory.com/16
git restore --staged <file>...
이렇게 특정 파일을 add 상태에서 벗어나게 하는(스테이징 영역에서 내리는) 건 git reset 파일명
으로도 가능하다.
git commit -m
이 아니라 git commit -a
는 모든 변경사항을 한 번에 커밋한다. 이 때 add를 하지 않아도 된다. 이는 add와 commit을 한 번에 하는 것이다. 단 이때 한번도 add 안된 파일은 따로 add를 해줘야 한다.
또한 git commit
만 입력할 시 텍스트 에디터가 열리는데 여기서 커밋 메시지를 입력할 수 있다. 여기서 커밋 메시지를 입력하고 저장하면 커밋이 된다.
git log 예쁘게 보기
git log --oneline --graph --all --decorate
git log: 현재 브랜치 커밋 이력
git log -n<숫자>
: 표시한 숫자개 만큼의 커밋 이력. git log -n1
은 최근 커밋 한 개만 보여준다.
git log --oneline: 각 커밋을 한 줄로 표시
git log --graph: 브랜치와 머지 관계를 그래프로 표시
git log --all: HEAD 이외에도 모든 브랜치의 커밋 이력을 표시
git log --decorate: 브랜치와 태그 등 참조명을 표시
git help 명령어
로 명령어의 사용법을 볼 수 있다. 예를 들어 git help log
로 log 명령어의 사용법을 볼 수 있다.
git remote -v
로 현재 원격 저장소 목록 확인.
다음 명령어로 push를 하면서 업스트림 브랜치를 설정할 수 있다. 둘은 같은 기능을 한다.
git push -u origin main
git push --set-upstream origin main
git clone을 할 때 저장소 주소 다음에 .
을 찍으면 현재 폴더에 저장소를 내려받는다. 다른 새로운 폴더명을 지정할 수도 있다.
git clone 저장소주소 .
git clone 저장소주소 새폴더명
커밋에는 부모 커밋 정보는 들어 있지만 자식 커밋에 대한 정보는 없다. 병합 0-커밋 같은 경우 2개의 부모가 있다. 커밋 객체에는 부모 커밋 참조와 실제 커밋을 구성하는 파일 객체가 있다.
브랜치 관련 명령 #
git branch
: 브랜치 목록을 보여준다. 현재 브랜치는 앞에 *
로 표시된다. -v
를 붙이면 마지막 커밋도 같이 표시된다. -r
을 붙이면 원격 저장소 브랜치 목록을 볼 수 있다. -a
를 붙이면 로컬 저장소와 원격 저장소 브랜치 목록을 모두 볼 수 있다.
git branch 브랜치명
: 브랜치를 생성한다. 명령어 다음에 특정 커밋의 체크섬 값을 주면 해당 커밋을 가리키는 브랜치를 생성한다. 이미 있는 브랜치를 다른 커밋으로 옮기고 싶을 경우 -f
옵션을 사용한다.
git switch 브랜치명
: 브랜치로 이동한다. git checkout
대신 사용한다. git switch -c 브랜치명
으로 브랜치를 생성하고 바로 이동할 수 있다.
git branch -d 브랜치명
: 브랜치를 삭제한다. -D
를 붙이면 브랜치를 강제로 삭제한다.
git merge <대상 브랜치명>
: 현재 브랜치에 대상 브랜치를 병합한다. 충돌이 발생하면 충돌을 해결하고 다시 커밋해야 한다.
git rebase <대상 브랜치명>
: 현재 브랜치에 대상 브랜치를 rebase한다. 충돌이 발생하면 충돌을 해결하고 git rebase --continue
로 계속 진행한다. git rebase --abort
로 rebase를 중단할 수 있다.
이때 git switch -c
에서 원본이 될 브랜치명을 명시할 수 있다.
git switch -c hotfix main # main 브랜치를 원본으로 하는 hotfix 브랜치를 만들고 이동
HEAD는 현재 작업 중인 브랜치의 최근 커밋을 가리킨다.
git revert 커밋해시
으로 해당 커밋의 작업 내용을 되돌리는 새로운 커밋을 만들 수 있다. 최신 커밋부터 취소하는 것이 좋고, 이걸 이용하면 특정 기능을 되돌리는 데에도 사용할 수 있다.
만약 git checkout 커밋해시
로 특정 커밋으로 돌아가게 되면 HEAD와 브랜치가 분리된 상태가 된다. 이 상태에서 다른 브랜치로 이동하면 detached HEAD 이후 커밋들은 다 사라지게 된다. 물론 로컬에 해당 커밋이 남아있기에 git reflog로 찾을 수는 있다. 하지만 권장하지 않기에 브랜치를 이용해서 체크아웃하는 것이 좋다.
git reset --hard <커밋 체크섬>
으로 현재 브랜치를 지정한 커밋으로 옮길 수 있다. 이때 커밋 체크섬을 git log
로 알아내는 건 번거로울 수 있기에 HEAD~n
으로 n개 전의 커밋으로 이동할 수 있다. HEAD~1
은 바로 이전 커밋을 가리키고 HEAD~2
는 그 이전 커밋 즉 할아버지 커밋을 말한다.
HEAD^
는 HEAD~1
과 같은 의미이다. 그런데 HEAD^2
는 머지 커밋의 두 번째 부모를 가리킨다. 머지 커밋처럼 부모가 둘 이상인 커밋에서 의미가 있다.
그런데 git reset --hard HEAD^2
와 git checkout HEAD^2
는 뭐가 다를까?
git checkout을 쓰면 main 브랜치는 그대로 있고 HEAD만 두 번째 부모로 이동한다. detached head 상태가 되는 것이다. git reset --hard
는 거기에 더해 HEAD의 현 위치로 main 브랜치를 옮기는 것까지 수행하는 것이다. 즉 다음과 같다.
git checkout HEAD^2
git branch -f main
git checkout main
전자는 HEAD를 두 번째 부모로 옮기는 것이고 후자는 HEAD를 두 번째 부모의 커밋으로 이동하는 것이다.
태그 #
git tag -a -m "태그 메시지" 태그명 브랜치||커밋 체크섬
로 태그를 만들 수 있다. -a
는 태그를 만들 때 주석을 추가할 수 있게 하는 옵션이다. 그리고 마지막에 브랜치명 등을 생략하면 HEAD에 태그를 붙인다.
git push <원격저장소명> <태그명>
으로 태그를 원격 저장소로 푸시할 수 있다. git push origin v1.0.0
처럼 하면 된다.
rebase #
git rebase 명령은 다음과 같이 쓴다.
git rebase <base 브랜치명>
이렇게 하면 rebase에 명시한 브랜치를 base로 해서 현재 브랜치를 이어붙인다. 현재 브랜치가 feat/a
이고 git rebase main
을 하면 main을 base로 해서 feat/a
를 이어붙인다.
이 때 충돌이 발생하면 충돌을 해결하고 git rebase --continue
로 계속 rebase를 진행한다. git rebase --abort
로 rebase를 중단할 수 있다.
이렇게 rebase한 내역을 다른 브랜치로 머지할 수 있다.
git checkout main # main 브랜치로 이동
git merge feat/a # 현재 브랜치로 feat/a 내용(아까의 rebase로 main을 기반으로 뒤에 feat/a가 붙어 있는 형태)을 머지
rebase로 뻗은 가지 없애기 #
자동으로 병합 브랜치가 생긴 다음과 같은 상황이 있을 수 있다.
A0 - A1 - A2(병합 브랜치)
\ /
B1 - B2
A0을 기반으로 2명이 작업을 하고 각자가 서로의 작업 내역을 pull받아 작업하지 않았을 경우 이렇게 자동으로 3-way merge가 되어 병합 브랜치가 생긴다.
이 상황을 해결하고 히스토리를 깔끔하게 하려면 reset --hard
로 병합 브랜치를 되돌리고 다시 rebase를 하면 된다.
git reset --hard HEAD~ # 병합 브랜치(HEAD)를 한 단계 되돌린다.
git rebase origin/B2 # origin/B2을 base로 해서 현재 브랜치(main)를 이어붙인다.
# 물론 origin/A1을 base로 해서 B1, B2를 이어붙여도 된다.
# 이 경우 feat/B 브랜치로 체크아웃한 후 git rebase origin/A1을 하면 된다.
git push
주의사항 #
원격 저장소에 push한 브랜치는 rebase하지 않는 것이 좋다. rebase로 다른 브랜치에 붙게 된 커밋은 원래 커밋과는 다른 커밋이기 때문이다.
예를 들어 위의 그림에서 B1, B2를 rebase로 A1을 base로 해서 이어붙인다면 B1, B2는 원래의 B1, B2와 같은 내용을 담고 있지만 다른 버전이 되어서 A1의 뒤에 붙는다. 이때 만약 B1, B2가 이미 원격 저장소에 push되어 있다면 원격 저장소에는 원래 B1, B2도 있고 rebase를 통해 새로 만들어진 B1', B2'도 있다. 이때 누군가 B1, B2에서 작업하고 있었다면...
이런 식으로 동일 커밋 사본이 여러 개 생기고 충돌도 더 잘 발생하게 되고 히스토리도 꼬이기 때문에 원격 저장소에 push한 브랜치는 되도록 rebase하지 않는 것이 좋다.
임시 브랜치 만들기 #
merge, rebase 등을 할 때 커밋 히스토리가 꼬이거나 작업에 실수할 걱정이 들 수 있다. 그러면 임시 브랜치를 만들어서 작업하고 실패하면 그냥 버리면 된다. 브랜치를 삭제하면 모든 내용이 원상복구된다.
git branch -D 임시브랜치명 # 임시 브랜치 삭제(강제)
git 내부 원리 #
저수준 명령어들(실제로 잘 쓰지는 않음)
- git hash-object: 파일의 해시 체크섬 확인(같은 내용 파일은 값 똑같음)
- git show <체크섬>: 체크섬을 가진 객체 내용 표시
- git ls-files --stage: 스테이징 영역에 있는 파일 내용 표시. .git/index가 스티이징 영역이다.
- git cat-file -t <체크섬>: 해당 체크섬을 갖는 객체의 타입 표시
- git cat-file <객체 타입> <체크섬>: 해당 객체 타입의 체크섬을 갖는 객체 내용 표시
git status 명령은 작업 영역과 스테이징 영역, HEAD 커밋의 차이를 비교해 보여준다. 만약 작업 영역과 스테이징 영역, HEAD 커밋 내용이 모두 같다면 "working tree clean"이라는 메시지가 나온다. 각 영역에 있는 내용이 없어진 게 절대 아니다!
git add는 스테이징 영역에 추가되는 파일의 체크섬값과 동일한 이름을 갖는 blob 객체를 생성하여 .git/objects/
에 저장하고, 스테이지 내용은 .git/index
(스테이지에 들어가야 할 파일의 체크섬과 상태만 저장)에 기록한다.
커밋을 하면 스테이지 객체를 이용해 트리 객체가 만들어진다. 커밋으로 만든 버전에는 이렇게 만든 트리 객체와 커밋 메시지가 포함된다. git ls-tree 명령으로 트리 객체 내용을 볼 수도 있다.
작업 영역에 파일 만듬 -> add 시 스테이징 영역에 파일 추가 -> commit 시 스테이징 영역의 파일을 트리 객체로 만들어 커밋(스테이징 영역의 파일은 여전히 blob으로 존재함)
git hash-object 파일명 # 파일의 해시 체크섬 확인
git ls-files --stage # 스테이징 영역에 있는 파일 내용 표시. add시 파일이 여기에 추가됨
git ls-tree HEAD # HEAD 커밋의 트리 객체 내용 표시. commit 시 스테이징 영역의 파일을 트리 객체로 만들어 커밋하고 스테이징 영역의 해시가 여기에 반영됨
직접 커밋해보기 #
git write-tree # 스테이지 영역의 파일을 트리 객체로 만들어 반환
3536cd63c18b76528a28bf7783e0da7701b2301e
git ls-tree 3536cd # 방금 생성된 트리 객체 내용 확인
echo "commit by tree" | git commit-tree 3536cd -p HEAD
# echo로 커밋 메시지 입력하고 HEAD를 부모로 하는 커밋 생성
e1bf8dfa821cefc0418d45781e92ecc5d6003891 # -> 생성된 커밋 객체 해시
git cat-file commit e1bf8d # 생성된 커밋 확인
tree 3536cd63c18b76528a28bf7783e0da7701b2301e
parent 3b0944e746edc32d1caf91b00c5f895162b21506
author witch-factory <[email protected]> 1715048850 +0900
committer witch-factory <[email protected]> 1715048850 +0900
# 방금 생성된 커밋 객체의 parent는 HEAD 커밋. git log --oneline -n1으로 현재 HEAD 커밋의 체크섬 확인 가능
그런데 이렇게 하면 아직 HEAD를 새 커밋으로 옮기지 않았기 때문에 로그가 업데이트되지 않는다. HEAD를 새 커밋으로 옮기려면 다음과 같이 update-ref
명령을 쓴다.
$ cat .git/HEAD
ref: refs/heads/main
git update-ref refs/heads/main e1bf8d # 아까 생성한 커밋 객체의 해시로 main 브랜치 HEAD(refs/heads/main) 업데이트
cat .git/refs/heads/main # 값 바뀐 것 확인
git log --oneline -n3 # 로그 확인. 이제 아까 생성한 "commit by tree" 커밋이 생성됨
git에서 파일은 blob 객체로 관리된다. 이 blob 객체들은 제목, 생성 날짜 등과 관계없이 내용만 같으면 같은 체크섬을 가진다. 따라서 새로운 커밋을 여러 개 만들거나 같은 파일을 cp
명령 등으로 복사해 만들어도 같은 내용이라면 하나의 blob 객체만 만들어진다. 따라서 git은 좀더 효율적으로 동작할 수 있게 된다.
아무튼 내부를 따진다면 git add는 작업 영역 내용을 스테이지에 반영한다. 그리고 git commit은 스테이지 내용으로 트리 객체를 만든 후(write-tree) 트리 객체를 기반으로 기존 HEAD 커밋을 부모로 하는 새 커밋을 만든다(commit-tree). HEAD를 새 커밋으로 옮기면(update-ref) 로그가 업데이트된다.
브랜치 만들기 #
브랜치를 만들고 생성된 브랜치의 파일 내용을 확인해보자.
git branch test
ls .git/refs/heads # 브랜치 목록 확인
cat .git/refs/heads/test # test 브랜치의 체크섬 확인
-> e1bf8dfa821cefc0418d45781e92ecc5d6003891 # 방금 생성한 커밋 객체의 해시와 똑같다!
즉 브랜치는 커밋 객체를 참조하고 있을 뿐이다. 특정 커밋에서 브랜치를 만들면 해당 브랜치 체크섬을 내용으로 갖는 텍스트 파일을 하나 생성하는 것과 같다. 브랜치는 그게 전부다. 브랜치를 삭제하는 명령(git branch -d
)은 이 파일을 삭제하는 것이다.(rm .git/refs/heads/브랜치명
)
브랜치를 이동하는 동작도 간단하다. HEAD
가 현재 보고 있는 브랜치의 최신 커밋을 가리키는 거니까, 먼저 HEAD를 이동할 브랜치로 바꾸고 해당 브랜치의 최신 커밋 내용으로 "작업 영역, 스테이징 영역"을 HEAD가 가리키는 커밋으로 업데이트하면 된다.
이를 수동으로 할 수도 있다.
cat .git/HEAD # HEAD가 가리키는 브랜치 확인
-> ref: refs/heads/test
echo "ref: refs/heads/main" > .git/HEAD # HEAD를 main 브랜치로 변경
물론 이렇게 하면 HEAD만 바꾼 것이기에 스테이지, 작업 영역은 바뀌지 않는다.
추가 특별판 #
원격 저장소 명령 #
한 프로젝트를 여러 원격 저장소에 연결 가능
- git remote add <원격 저장소명> <저장소 주소>: 원격 저장소 추가
- git remote rename <기존 원격 저장소명> <새 원격 저장소명>: 원격 저장소 이름 변경
- git remote remove <원격 저장소명>: 원격 저장소 삭제
일반적으로 첫 번째 원격 저장소는 origin, 포크한 저장소 원본에는 upstream이라는 이름을 사용한다. git remote -v
로 현재 원격 저장소 목록을 확인할 수 있다. 이렇게 하면 포크된 원본 저장소의 변경 사항을 쉽게 pull 할 수 있다. 꼭 작업 영역을 변경시키고 싶지 않을 경우 fetch
를 통해 원격 저장소 변경사항만 가져올 수 있다. pull은 fetch+merge이다. 이때 원격 저장소명을 생략할 경우 origin
을 사용한다.
단 upstream에는 push할 수 없다. upstream에 코드를 반영하기 위해서는 origin에 push하고 PR을 보내야 한다.
git pull upstream main
push에 다음과 같은 옵션도 있다.
- git push --all: 로컬 저장소의 모든 브랜치 커밋을 원격 저장소로 푸시
- git push --tags: 로컬 저장소의 모든 태그를 원격 저장소로 푸시
- 옵션 생략시 현재 브랜치 커밋들만 원격 저장소로 푸시
백업하기 #
깃헙이 다운되어도 다른 git 호스팅 서비스를 이용하면 백업을 만들어둘 수 있다. 이렇게 백업 저장소를 두면 깃헙이 다운되어도 백업에서 작업하다가 깃헙이 복구되면 거기 푸시하면 된다. 저장소를 만들고 원격 저장소로 추가하면 된다.
git remote add backup <백업 저장소 주소>
git push backup main # 메인 브랜치 푸시
git push backup --all # 모든 브랜치 푸시
# 이런 식으로 저장소를 백업하려면 로컬 브랜치가 생성된 상태여야 한다
git remote -rv # 저장소 상태 확인. 로컬에 없는 브랜치는 백업에도 없다
아쉽게도 origin 원격 저장소의 브랜치를 그대로 백업 저장소로 푸시할 수는 없다. 로컬 브랜치를 먼저 만든 후 백업에 푸시해야 한다.
clean, hard reset #
워킹트리 내용을 변경하는 명령으로 switch, restore, reset이 있다. 그런데 git 명령은 대부분 추적 중이 아닌 파일은 건드리지 않는다.(untracked) 그런데 이런 추적 중이 아닌 파일을 삭제하고 싶을 때는 git clean
명령을 사용한다.
git clean -f -d # 추적 중이 아닌 파일과 디렉토리 삭제
git clean -f -d <파일/폴더명>
-f
옵션은 강제로 삭제하라는 의미이고 -d
옵션은 디렉토리도 삭제하라는 의미이다. 이 명령은 조심해서 사용해야 한다. 추적 중이 아닌 파일을 삭제하면 복구가 어렵기 때문이다. 그래도 개발하다 보면 추적중이 아닌 파일을 삭제해야 할 때가 있기 때문에 유용하다.
아무튼 이렇게 삭제 후 git status
로 상태를 보면 추적중이 아닌 파일이 삭제된 것을 확인할 수 있다.
hard reset #
reset의 여러 옵션들
- git reset --soft: 커밋을 취소하고 해당 커밋 이후의 변경사항은 스테이징 영역에 남아있다. 즉 HEAD가 가리키는 커밋만 바뀐다.
- git reset --mixed: 커밋을 취소하고 해당 커밋 이후의 변경사항은 워킹 디렉토리에 남아있다. 또한 변경사항은 스테이징 영역에 올라가지 않은 상태이다.
- git reset --hard: 커밋을 취소하고 해당 커밋 이후의 변경사항은 모두 사라진다. 즉 워킹 디렉토리와 스테이징 영역이 모두 해당 커밋으로 돌아간다.
옵션 | 워킹 디렉토리 | 스테이징 영역 | HEAD |
---|---|---|---|
--soft | 변경사항 남음 | 변경사항 남음 | 변경사항 삭제 |
--mixed | 변경사항 남음 | 변경사항 삭제 | 변경사항 삭제 |
--hard | 변경사항 삭제 | 변경사항 삭제 | 변경사항 삭제 |
단 이 모든 reset 명령은 untracked 파일은 건드리지 않는다.
그런데 이렇게 hard reset을 하고 나면 되돌릴 수 없나? 만약 reset --hard로 날아간 커밋의 체크섬을 알고 있다면 되돌릴 수 있다.
git reset --hard <커밋 체크섬> # hard reset으로 날아간 커밋이라도 로컬의 커밋은 없어지지 않았기 때문에 살릴 수 있다
그런데 날아간 커밋의 체크섬을 기록해 두지 않았다면? git reflog
를 사용할 수 있다. 이 명령은 로컬 저장소의 커밋, 브랜치 이력등을 보여준다.
git reflog [-n숫자] # 로컬 저장소의 최근 커밋 이력을 보여준다. -n 옵션으로 숫자만큼의 이력을 보여준다. -n5면 이력 5개만 보여준다.
reflog는 로컬 저장소의 이력만 보여주고 원격 저장소의 로그는 확인할 수 없다. 그래도 로컬에만 남아있는 커밋을 복구할 때 유용하다.
reset은 신중히 해야 한다. 원격 저장소에 올라간 커밋에 reset을 하게 되면 force push를 하게 되는 일도 있을 수 있고. 따라서 rebase처럼 reset도 되도록이면 로컬에서만 사용하는 것이 좋다.
중급 git 명령어 2 #
commit --amend #
commit --amend 명령을 쓰면 이전 커밋을 수정할 수 있다. 단 이렇게 커밋 수정시 해시 체크섬 값도 바뀐다. 실제로는 새로운 커밋이 하나 생성되는 것이다.
원격 저장소 커밋을 수정했을 경우 원격 저장소 커밋과 로컬에서 amend로 수정된 커밋이 달라지기 때문에 force push 해야 한다. 포스 푸시가 좋은 건 아닌데 이런 경우에는 어쩔 수 없다.
git commit --amend
그럼 오래된 커밋, HEAD가 아닌 커밋도 수정 가능할까? 기본적으로는 브랜치의 최신 커밋만 수정할 수 있다. 하지만 명령어를 여러 개 쓰면 가능하다. 먼저 reset으로 이전 커밋으로 돌아가고 amend로 수정하면 된다.
git reset HEAD^n # n개 전 커밋, 즉 내가 수정하고 싶은 커밋으로 돌아간다
git commit --amend
# 이 다음 다시 원래 위치로 돌아가는 명령어를 해야 한다
오래된 커밋 수정은 git rebase -i
로도 할 수 있는데 이후 살펴본다.
git diff #
git diff # 아직 스테이지에 올라가지 않은 변경사항 확인. untracked 파일은 확인되지 않는다.
git diff [a] [b] # a를 토대로 b에 추가된 내용의 차이를 보여줌. a, b는 커밋 체크섬, 브랜치명, HEAD 등이 될 수 있다.
여러 옵션들도 있다.
git diff --cached # 스테이징 영역과 HEAD의 차이 확인. HEAD를 기반으로 스테이징 영역에 추가된 내용을 보여주는 것.
git diff HEAD~ HEAD # 이전 커밋과 새 커밋 비교
git diff origin/브랜치명 브랜치명 # 원격 저장소 브랜치와 로컬 브랜치 비교
git diff A B # A, B 브랜치 비교
git diff --check # 공백 문자 확인
ours, theirs #
충돌을 해결할 때 -X
와 ours, theirs 옵션을 사용시 충돌을 좀더 쉽게 지나갈 수 있다.
예를 들어 feature 브랜치에서 작업하고 이를 main에 머지/리베이스를 하다가 충돌이 발생했다. 그럼 보통은 feature 브랜치 내용을 택할 것이다. 이 경우 theirs 옵션을 사용하면 충돌을 해결할 때 feature 브랜치 내용을 택한다. -X
옵션이 대문자 X임에 주의한다.
참고로 -X
는 준말이며 --strategy-option=theirs
로 쓸 수도 있다.
git merge feature/xxx # 충돌 발생하는 머지 시도
git merge --abort # 머지 취소
git merge -X theirs feature/xxx # 충돌 발생 시 feature 브랜치 내용을 택함
# main 입장에서 feature 브랜치는 theirs이다.
git merge -X ours feature/xxx # 충돌 발생 시 main 브랜치 내용을 택함
rebase에서도 ours, theirs를 사용할 수 있다.
git switch feature1
git rebase main -X theirs
# main을 base로 해서 feature1 브랜치를 rebase하되 충돌 발생 시 feature1 브랜치 내용을 택함
# rebase에서는 base 브랜치(여기선 main)를 기준으로 ours, theirs가 정해진다.
rebase -i #
rebase는 현재 브랜치 커밋들을 base가 되는 브랜치 위에 재배치하는 방식이었다. rebase -i는 이 과정을 인터랙티브하게 할 수 있다. 즉 git의 히스토리를 편집할 수 있는 것이다. 참고로 원래 rebase
와는 아예 별개라고 생각하는 게 편하다.
git rebase -i <수정하려는 커밋들의 부모커밋>
git rebase -i HEAD~3 # HEAD로부터 3개 전 커밋까지 편집
지정한 커밋과 HEAD 사이 커밋들의 히스토리를 수정한다. 에디터에 커밋들의 목록이 보이고 각 커밋에 대해 아래 옵션들 중 하나를 선택 편집 가능하다.
- pick: 커밋을 그대로 사용. 기본 옵션임
- reword: 커밋 메시지만 수정
- edit: 커밋 내용 수정
- squash: 커밋을 사용하지만 내용을 부모 커밋에 합침, 커밋 메시지도 합쳐진다.
- fixup: squash와 비슷하지만 커밋 메시지는 버리고 부모 커밋의 커밋 메시지만 사용
- drop: 커밋을 버림
예를 들어 커밋이 init, commit2, commit1 순서대로 되어 있다고 하자. 그럼 commit2와 commit1의 순서를 바꾸고 싶을 것이다.
git rebase -i HEAD~3
이렇게 하면 에디터가 뜬다. 단 git log와 반대로 여기서는 최신 커밋이 아래쪽에 위치한다. 그럼 이렇게 보일 것이다.
pick 2c0e887 init
pick 7965aee commit2
pick d67fef9 commit1
그냥 두 줄의 위치를 바꾸고 저장한다.
pick 2c0e887 init
pick d67fef9 commit1
pick 7965aee commit2
이러면 커밋 순서가 바뀐다. 단 커밋의 해시 체크섬도 바뀜에 주의한다.
커밋 메시지 변경 같은 경우 git rebase -i
이후 나오는 에디터에서 커밋 앞의 글자를 reword
로 바꾸면 된다. 그러면 나오는 에디터에서 커밋 메시지를 수정할 수 있다.
커밋 내용을 수정하고 싶으면 이렇게 edit을 쓴다.
edit a05b92f commit1.5 (메시지 수정)
pick 6085488 commit2
이렇게 하면 commit1.5 커밋을 수정할 수 있다. 수정 후 git commit --amend
로 커밋을 수정하고 git rebase --continue
로 rebase를 계속 진행한다. 이 내용은 위에서 edit
을 한 후 저장하면 터미널에서 친절하게 메시지로 알려준다.
특정 커밋을 '이전 커밋'에 합치고 싶으면 squash를 쓴다. 예를 들어 이렇게 하면 "commit2" 커밋이 "commit1.5" 커밋에 합쳐진다. 또한 이 상태로 저장시 에디터 창이 뜨는데 거기서 커밋 메시지를 편집 가능하다.
pick a05b92f commit1.5 (메시지 수정)
squash 6085488 commit2
물론 이 경우 최신 커밋을 수정하므로 git commit --amend
를 쓸 수도 있지만 오래된 커밋들을 합치는 경우 이렇게 git rebase -i
와 squash
를 쓰는 것이 편하다.
이렇게 git rebase -i
를 쓰면 커밋 히스토리를 깔끔하게 정리할 수 있다.
cherry-pick #
cherry-pick 명령은 다른 브랜치의 특정 커밋을 골라서 "현재 브랜치"에 합칠 때 사용한다.
git cherry-pick <커밋 체크섬>
# 해당 커밋 체크섬의 커밋을 현재 브랜치에 합친다.
git stash #
stash를 쓰면 변경 내용을 stash 스택(.git/refs/stash
폴더에 있다)에 저장할 수 있다. 여기 있는 내용은 언제든 다시 꺼내올 수 있음. stash는 작업 중인 내용을 잠시 저장해두는 용도로 쓰이고 원격 저장소에 푸시할 수는 없다. 임시 로컬 저장소라고 생각하면 된다.
변경사항 커밋도 삭제도 모호한 상황에 쓰이는데, 작업하다가 급하게 다른 사람의 브랜치 작업을 보기 위해 브랜치를 변경한다던가 하는 상황에 쓸 수 있다.
- git stash: 변경사항을 스택에 저장한다. -u 옵션 사용시 untracked 파일도 저장
- git stash list: stash 스택 목록 확인
- git stash pop: stash 스택에서 가장 최근에 저장한 내용을 꺼내고 스택에서 삭제한다. 뒤에 stash 객체 숫자를 붙이면 해당 stash를 꺼내고 삭제한다.
- git stash apply: stash 스택에서 가장 최근에 저장한 내용을 꺼낸다. pop과 달리 스택에서 삭제하지 않는다. 뒤에 stash 객체 숫자를 붙이면 해당 stash를 꺼낸다.
- git stash drop <객체>: 저장한 stash 객체 삭제
- git stash clear: 저장한 stash 객체 모두 삭제
- git stash branch <객체>: 마지막 커밋에 해당 stash 객체 내용 반영해서 새로운 브랜치 생성. 테스트용 브랜치를 만들 때 유용하다.
또다른 용도도 있다. 개발하며 흔히 하는 실수로 다른 브랜치에서 작업하는 경우가 있다. feature 브랜치에서 작업해야 하는데 main 브랜치에서 작업하는 식이다. 이 경우 stash를 이용하면 현재 작업 내용을 다른 브랜치로 옮길 수 있다.
# main 브랜치에서 작업 중이라고 가정
git stash -u # 변경사항 stash. untracked 파일도 저장
git switch feature # feature 브랜치로 이동
git stash pop # stash 내용을 feature 브랜치로 가져옴
main 브랜치에서 커밋을 해버렸다면 cherry-pick을 써서 가져올 수 있다.
또는 stash의 내용을 적용한 브랜치를 바로 새로 만들 수도 있다.
# main 브랜치에서 작업 중이라고 가정
git stash -u # 변경사항 stash. untracked 파일도 저장
git stash branch new-idea # stash 내용을 적용한 새로운 브랜치 생성. 새 브랜치 만들고 stash를 해당 브랜치에 적용하고 stash 객체는 삭제한다.
add로 스테이지 작업 #
우리는 기존에 add를 이용해서 파일 전체의 수정사항을 올렸다. 그런데 파일 전체가 아니라 일부만 커밋을 하고 싶으면 어떻게 할까? -p
혹은 --patch
옵션을 사용하면 된다.
untracked가 아닌 modified 파일에 대해서만 사용할 수 있다. untracked 파일의 일부만 커밋하려면 약간 트릭이 필요하다.
git add -p <파일명>
이렇게 하면 대화형 명령창이 나오는데 git에서 변경사항을 hunk라는 단위로 쪼개서 보여준다. 각 hunk의 스테이징 여부를 y/n으로 선택하거나 e로 직접 수정할 수 있다. 이렇게 하면 일부만 스테이징할 수 있다.
e를 선택해서 직접 수정하게 되면 편집 내역을 수정하게 되는데 여기서 아직 스테이징하고 싶지 않은 내용을 삭제하고 저장하면 된다.
untracked 파일의 일부만 스테이징하려면 git add -N
을 사용한다. 이 명령은 untracked 상태 파일을 스테이지에 올리는데 내용은 올리지 않고 파일 상태만 변경한다. 기존의 git add -p
같은 경우 untracked 파일에는 적용이 안된다. 따라서 git add -N
으로 파일 상태만 변경해 주는 것이다.
git add -i
는 git add -p
의 상위 명령으로 사용자가 직접 터미널과 상호작용하면서 변경 사항 add를 세부적으로 조정할 수 있다. 다만 사용할 일이 많지는 않으므로 나중에 help를 보고 해보면 되겠다.
히스토리에서 파일 삭제 #
올리면 안 되는 파일(.env 등)을 github에 올렸을 때 삭제할 수 있다. 삭제 사항을 커밋하는 건 아무 의미가 없다. 이전 히스토리에 남아 있기 때문이다. 따라서 커밋 히스토리 전체에서 해당 파일을 삭제해야 한다.
파일을 히스토리에서 제거하기 위해서는 먼저 어떤 커밋이 이 파일을 편집했는지 알아야 한다. git blame을 사용한다.
# 해당 파일의 커밋 히스토리를 보여준다.
#-L 옵션 사용시 변경 사항을 특정 범위(시작줄, 끝줄 사이)만 볼 수 있다.
git blame [-L <시작 줄, 끝 줄>] <파일명>
# 예시
git blame file1.txt
히스토리 파일 삭제를 위해서는 filter-branch
명령을 쓴다. 이 명령에는 다른 기능도 많지만 --tree-filter
를 써서 히스토리에서 파일을 삭제하는 기능을 쓰겠다.
git filter-branch --tree-filter 'rm -f <파일명>' HEAD
# 예시. 히스토리에서 file1.txt를 완전히 삭제한다.
git filter-branch --tree-filter 'rm -f file1.txt' HEAD
이렇게 히스토리를 변경하고 나면 원격 저장소에 푸시할 때는 이미 올라간 커밋을 변경하는 것이므로 git push -f
를 써야 한다. 또한 다른 브랜치에서는 여전히 file1.txt가 남아 있을 수 있으므로 모든 브랜치에서 이 작업을 해야 한다.
BFG라는 repo-cleaner 프로그램이 있기는 하다. 이 프로그램을 쓰면 좀더 쉽게 히스토리에서 파일을 삭제할 수 있다.
팁 #
단축 명령 #
git 명령어는 매우 길다. 그래서 단축 명령을 사용하면 편리하다. 이를 위해 git config에서 alias를 만들 수 있다. 보통 --global
과 함께 쓴다.
# 전역에서 st를 status의 단축 명령으로 만든다
git config --global alias.st status
# 그래프를 보여주는 명령은 기니까 단축 명령어를 만든다
# git graph로 그래프를 볼 수 있게 된다.
git config --global alias.graph "log --oneline --graph --all"
git 저장소 삭제 #
git status
로 현재 디렉토리가 git 저장소인지 확인할 수 있다. 만약 특정 폴더를 실수로 git 저장소로 만들었는데 삭제하고 싶다면 그냥 디렉토리의 .git
폴더를 삭제하면 된다. 이 폴더는 숨김 폴더이므로 rm -rf .git
으로 삭제한다.
만약 이렇게 했는데도 git status가 정상적으로 수행된다면 내 현재 폴더 또는 상위폴더에 .git
폴더가 있는 것이다. 이런 상위 폴더의 저장소도 삭제하면 된다.
전역 gitignore #
특정 프로젝트에서만 쓰이는 gitignore 파일을 만들어서 사용할 수 있다. 하지만 모든 프로젝트에서 git에 올라가면 안되는 파일이 있다면 전역 gitignore 파일을 만들어서 사용하는 것도 번거로움을 줄여줄 수 있다.
먼저 홈 폴더에 global_gitignore
파일을 생성한다. 사실 파일명은 상관 없다.
# 해당 파일을 전역 gitignore로 설정
git config --global core.excludesfile ~/.global_gitignore
사용하는 프레임워크, 빌드 도구 등에 따라 gitignore
를 생성해 주는 사이트가 있다. gitignore.io
이다.
예를 들어 macos 운영체제, node를 넣으면 해당 환경에서 필요 없는 파일들을 적은 페이지로 이동하고 이를 복사해서 gitignore 파일에 붙여넣으면 된다.
pro git이라는 책도 읽으면 좋다.
git worktree에 대하여 #
여러 개의 브랜치를 오가면서 작업할 때가 많다. git stash
같은 명령으로 하던 작업을 저장해 두고 이동할 수 있다. 근데 이렇게 하면 번거로운 점들이 있다. 새로운 패키지를 설치했다면 node_modules
를 다시 설치해 줘야 할 수도 있고 나중에 다시 컨텍스트 스위칭하기 힘들고 등등.
git worktree
를 쓰면 지금까지의 작업 내역이 똑같이 저장된 브랜치를 별도의 워크트리로 물리적으로 분리할 수 있다.
예를 들어 나는 projects/user-web
에서 작업하고 있었다고 하자. 근데 여기서 user-web
에서 변경해야 할 사항이 생겼다. git stash
하고 나서 다른 브랜치로 이동(git switch -c
)할 수 있다. 그러나 앞서 말했듯 그렇게 하면 패키지 버전이 꼬이는 등 문제가 생길 수 있다. 거럴 때 git worktree로 물리적으로 분리 가능.
/projects/user-web
> git worktree add ../hotfix main
# main 브랜치를 ../hotfix 폴더에 새로 만듦
즉 형태는 다음과 같다.
git worktree add [워크트리 디렉토리 생성 경로] [워크트리를 만들 브랜치]
이제 이 두 워크트리는 분리되어 있고 user-web
과 hotfix
의 작업 내역도 분리된다. 브랜치를 실제로 분리해서 만들어 주는 느낌이라고 이해하면 된다. 이제 git stash
나 임시 커밋 없이도 그대로 다른 작업을 하고 돌아올 수 있다.
혹은 커밋 해시를 이용해 해당 커밋으로부터 워크트리를 만들 수 있다.
git worktree add [워크트리 디렉토리 생성 경로] [커밋 해시]
다른 명령어
git worktree list # 당연히 워크트리 목록
git worktree remove [워크트리명]
주의사항
- 워크트리들은 git 상태를 공유한다. 따라서 한쪽에서 새로운 커밋을 생성하면 다른 쪽에서도 그 커밋을 읽을 수 있다. 애초에
git worktree
로 만든 워크트리는 원본 워크트리를 참조하도록 되어 있다! - 하나의 브랜치는 하나의 워크트리만 사용할 수 있다.