Git
은 데이터를 Change Set이나 변경사항으로 기록하지 않고 일련의 스냅샷으로 기록한다.
커밋하면 Git
은 현재 Staging Area
에 있는 데이터의 스냅샷에 대한 포인터, 저자나 커밋 메시지 같은 메타데이터, 이전 커밋에 대한 포인터 등을 포함하는 커밋 객체를 저장
이전 커밋 포인터가 존재하기 때문에 현재 커밋이 무엇을 기준으로 바뀌었는지 알 수 있다.
🎲 파일이 3개 있는 디렉토리를 커밋하려고 한다.
커밋하면 먼저 루트 디렉토리와 각 하위 디렉토리의 트리 객체를 체크섬과 함께 저장소에 저장
그 다음에 커밋 객체를 만들고 메타데이터와 루트 디렉토리 트리 객체를 가리키는 포인터 정보를 커밋 객체에 넣어 저장
[커밋과 트리 데이터]
[커밋과 이전 커밋]
[브랜치와 커밋 히스토리]
$ git branch <브랜치명>
[한 커밋 히스토리를 가리키는 두 브랜치]
Git
은 HEAD
라는 지금 작업하는 로컬 브랜치를 가리키는 특수한 포인터가 있다.[현재 작업 중인 브랜치를 가리키는 HEAD]
git log --decorate
를 사용하면 브랜치가 어떤 커밋을 가리키는지 확인 가능$ git log --oneline --decorate
git checkout
으로 다른 브랜치로 이동할 수 있다.$ git checkout <브랜치명>
▶️ HEAD
는 지정한 브랜치를 가리킨다.
커밋을 새로 하면 HEAD
가 가리키고 있는 브랜치는 새로운 커밋을 가리킨다.
이전 커밋을 가리키고 있는 HEAD
가 가리키는 브랜치에서 파일을 수정한 후 커밋하면 기존의 커밋 객체와 합쳐지는 것이 아니라 별도의 커밋 객체가 생긴다.
Merge
🎲 지금 작업하는 프로젝트에서 이전에 master 브랜치에 커밋을 몇 번 했다고 가정
[현재 커밋 히스토리]
이슈 관리 시스템에 등록된 53번 이슈를 처리하려고 한다.
▶️ 이 이슈에 집중할 수 있는 브랜치를 새로 하나 만든다.
git checkout -b
명령은 브랜치를 만들면서 Checkout까지 한번에 처리
$ git checkout -b <브랜치이름>
# 예시
$ git checkout -b iss53
[브랜치 포인터를 새로 만듦]
▶️ iss53 브랜치를 Checkout 했기 때문에 작업을 하고 커밋하면 iss53 브랜치가 앞으로 나아간다.
[진행 중인 iss53 브랜치]
🎲 만들고 있는 사이트가 문제가 생겨서 즉시 고쳐야 한다.
버그를 해결한 Hotfix에서 iss53이 섞이는 것을 방지하기 위해 iss53과 관련된 코드를 어딘가에 저장해두고 원래 운영 소스로 복구해야 한다.
▶️ Git
을 사용하면 그냥 master
브랜치로 돌아가면 된다.
하지만, 아직 커밋하지 않은 파일이 Checkout할 브랜치와 충돌 나면 브랜치를 변경할 수 없다.
작업하던 것을 모두 커밋하고 master
브랜치로 옮긴다.
▶️ 이때 워킹 디렉토리는 53번 이슈를 시작하기 이전 모습으로 되돌려지기 때문에 새로운 문제에 집중할 수 있는 환경이 만들어진다.
해결해야 할 Hotfix가 생겼을 때 Hotfix 브랜치를 만들고 새로운 이슈를 해결할 때까지 사용한다.
[master 브랜치에서 갈라져 나온 hotfix 브랜치]
$ git merge hotfix
Merge
할 때 B 브랜치가 A 브랜치 이후의 커밋을 가리키고 있으면 A 브랜치가 B 브랜치와 동일한 커밋을 가리키도록 이동Fast forward
🎲 hotfix는 master
브랜치에 포함됐고 운영 환경에 적용할 수 있는 상태가 되었다고 가정
[Merge 후 hotfix와 같은 것을 가리키는 master 브랜치]
급한 문제를 해결하고 master
브랜치에 적용하고 나면 다시 일하던 브랜치로 돌아가야 한다.
더 이상 필요없는 hotfix 브랜치 삭제
$ git branch -d hotfix
[master와 별개로 진행하는 iss53 브랜치]
🎲 53번 이슈를 다 구현하고 master
브랜치에 Merge
하려고 한다.
git merge
명령으로 합칠 브랜치에서 합쳐질 브랜치를 Merge
$ git checkout master
$ git merge iss53
Merge
할 브랜치의 조상이 아니므로 Fast forward
로 Merge
하지 않는다.3-way Merge
를 한다.[커밋 3개를 Merge]
Merge
의 결과를 별도의 커밋으로 만들고 해당 브랜치가 그 커밋을 가리키도록 이동[Merge 커밋]
master
에 Merge
하고 나면 더는 iss53 브랜치가 필요 없기 때문에 브랜치를 삭제하고 이슈 상태를 처리 완료로 표시Merge
하는 두 브랜치에서 같은 파일의 한 부분을 동시에 수정하고 Merge
하면 Git
은 해당 부분을 Merge
하지 못한다.
Merge
충돌이 일어났을 때 Git
이 어떤 파일을 Merge
할 수 없었는지 살펴보려면 git status
명령 이용
▶️ 충돌이 일어난 파일은 unmerged
상태로 표시
충돌을 해결하려면 둘의 내용 중에서 고르거나 새로 작성하여 Merge
한다.
git mergetool
이라는 Merge 도구로 충돌 해결 가능
Merge 도구를 종료하면 Git
은 잘 Merge
했는지 물어본다.
▶️ 잘 마쳤다고 입력하면 자동으로 git add
가 수행되고 해당 파일이 Staging Area
에 저장
▶️ 잘 저장됐는지 확인했으면 git commit
으로 Merge
한 것을 커밋
git branch
를 실행하면 브랜치의 목록을 보여준다.$ git branch
iss53
* master
testing
▶️ *
기호가 붙어 있는 master
브랜치는 현재 Checkout 해서 작업하는 브랜치를 나타낸다.
git branch -v
를 실행하면 브랜치마다 마지막 커밋 메시지도 함께 보여준다.$ git branch -v
iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes
git branch --merged
로 이미 Merge
한 브랜치 목록을 확인한다.$ git branch --merged
iss53
* master
▶️ *
기호가 붙어 있지 않은 브랜치는 git branch -d
로 삭제해도 되는 브랜치다.
▶️ 이미 다른 브랜치와 Merge
했기 때문에 삭제해도 정보를 잃지 않는다.
Merge
하지 않은 브랜치를 살펴보려면 git branch --no-merged
사용$ git branch --no-merged
testing
▶️ 아직 Merge
하지 않은 커밋을 담고 있기 때문에 git branch -d
로 삭제되지 않는다.
Merge
하지 않은 브랜치를 강제로 삭제하려면 -D
옵션으로 삭제master
브랜치에 Merge
해서 안정 버전의 코드만 master
브랜치에 둔다.develop
나 next
라는 이름으로 추가로 만들어 사용한다.Merge
한다.[안정적인 브랜치일수록 커밋 히스토리가 뒤쳐짐]
[각 브랜치를 하나의 "실험실"로 생각]
proposed
혹은 pu
(proposed updates)라는 이름의 브랜치를 만들고 next
나 master
브랜치에 아직 Merge
할 준비가 되지 않은 것을 일단 Merge
시킨다.🎲 master
브랜치를 checkout 한 상태에서 어떤 작업을 한다고 가정
master
브랜치로 되돌아가서 dumbidea
브랜치를 하나 더 만든다.[토픽 브랜치가 많음]
🎲 iss91v2 브랜치가 괜찮아서 적용하기로 결정했다.
dumbidea 브랜치도 괜찮아 보인다.
Merge
한다.[dumbidea 와 iss91v2 브랜치를 Merge 하고 난 후의 모습]
리모트 트래킹 브랜치 : 리모트 브랜치를 추적하는 레퍼런스이며 브랜치
▶️ 로컬에 있지만 임의로 움직일 수 없다.
▶️ 리모트 서버에 연결할 때마다 리모트의 브랜치 업데이트 내용에 따라서 자동으로 갱신된다.
▶️ 리모트 저장소에 마지막으로 연결했던 순간에 브랜치가 무슨 커밋을 가리키고 있었는지를 나타낸다.
리모트 트래킹 브랜치의 이름은 <리모트저장소명>/<브랜치이름>
형식
🎲 git.ourcompany.com
이라는 Git 서버가 있고 이 서버의 저장소를 하나 clone
하면 Git은 자동으로 origin
이라는 이름을 붙인다.
origin
으로부터 저장소 데이터를 모두 내려받고 master
브랜치를 가리키는 포인터를 만든다.master
브랜치가 origin/master
를 가리키게 한다.master
브랜치에서 작업을 시작할 수 있다.[Clone 이후 서버와 로컬의 master 브랜치]
git.ourcompany.com
서버에 push
하고 master
브랜치를 업데이트한다.origin/master
포인터는 그대로다.[로컬과 서버의 커밋 히스토리는 독립적임]
git fetch origin
명령을 사용한다.origin
서버의 주소 정보(이 예에서는 git.ourcompany.com
)를 찾아서, 현재 로컬의 저장소가 갖고 있지 않은 새로운 정보가 있으면 모두 내려받고, 받은 데이터를 로컬 저장소에 업데이트하고 나서, origin/master
포인터의 위치를 최신 커밋으로 이동시킨다.[git fetch 명령은 리모트 브랜치 정보를 업데이트]
🎲 리모트 저장소를 여러 개 운영하는 상황을 이해할 수 있도록 개발용으로 사용할 Git 저장소를 팀 내부에 하나 추가해보자.
git.team1.ourcompany.com
이며 git remote add
으로 현재 작업 중인 프로젝트에 팀의 저장소를 추가한다.[서버를 리모트 저장소로 추가]
git fetch teamone
으로 teamone 서버의 데이터를 내려받는다.origin
서버에도 있는 것들이라서 아무것도 내려받지 않는다.teamone/master
가 teamone 서버의 master
브랜치가 가리키는 커밋을 가리키게 한다.[teamone/master의 리모트 트래킹 브랜치]
push
해야 한다.🎲 serverfix 라는 브랜치를 다른 사람과 공유할 때도 브랜치를 처음 push
하는 것과 같은 방법으로 push
$ git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2), reused 0 (delta 0)
To https://github.com/schacon/simplegit
* [new branch] serverfix -> serverfix
fetch
하고 나서 서버에 있는 serverfix 브랜치에 접근할 때 origin/serverfix
라는 이름으로 접근할 수 있다.$ git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
* [new branch] serverfix -> origin/serverfix
fetch
명령으로 리모트 트래킹 브랜치를 내려받는다고 해서 로컬 저장소에 수정할 수 있는 브랜치가 새로 생기는 것이 아니다.origin/serverfix
브랜치 포인터가 생기는 것Merge
하려면 git merge origin/serverfix
사용Merge
하지 않고 리모트 트래킹 브랜치에서 시작하는 새 브랜치를 만들려면 아래와 같은 명령 사용$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
▶️ 그러면 origin/serverfix
에서 시작하고 수정할 수 있는 serverfix 로컬 브랜치가 만들어진다.
리모트 트래킹 브랜치를 로컬 브랜치로 Checkout 하면 자동으로 트래킹 브랜치가 만들어진다.
▶️ 트래킹 하는 대상 브랜치를 Upstream 브랜치라고 부른다.
트래킹 브랜치에서 git pull
를 하면 리모트 저장소로부터 데이터를 내려받아 연결된 리모트 브랜치와 자동으로 Merge
한다.
서버로부터 저장소를 clone
하면 Git은 자동으로 master
브랜치를 origin/master
브랜치의 트래킹 브랜치로 만든다.
트래킹 브랜치를 직접 만들 수 있는데 리모트를 origin
이 아닌 다른 리모트로 할 수 도 있고, 브랜치도 master
가 아닌 다른 브랜치로 추적하게 할 수 있다.
git checkout -b <branch> <remote>/<branch>
로 간단히 트래킹 브랜치를 만들 수 있다.
▶️ --track
옵션을 사용하여 로컬 브랜치 이름을 자동으로 생성할 수 있다.
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
$ git checkout serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'
git branch
에 -u
나 --set-upstream-to
옵션을 붙여서 설정$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
git branch -vv
사용$ git branch -vv
iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets
master 1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
testing 5ea463a trying something new
fetch
) 시점을 바탕으로 계산한다는 점이다.git pull
은 대부분 git fetch
를 실행하고 나서 자동으로 git merge
를 수행하는 것 뿐이다.fetch
와 merge
로 명시적으로 사용하는 것이 좋다.git push --delete
를 사용하여 리모트 브랜치를 삭제$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
- [deleted] serverfix
[두 개의 브랜치로 나누어진 커밋 히스토리]
merge
를 사용하는 것이지만 다른 방식으로 rebase
가 있다.$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Rebase
할 브랜치가 합칠 브랜치가 가리키는 커밋을 가리키게 하고 아까 저장해 놓았던 변경사항을 차례대로 적용[C4의 변경사항을 C3에 적용하는 Rebase 과정]
master
브랜치를 Fast-forward
시킨다.$ git checkout master
$ git merge experiment
[master 브랜치를 Fast-forward시키기]
Merge
와 Rebase
둘 다 합치는 관점에서는 서로 다를게 없지만 Rebase
가 좀 더 깨끗한 히스토리를 만든다.Rebase
하는 리모트 브랜치는 직접 관리하는 것이 아니라 그냥 참여하는 브랜치일 것이다.🎲 다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치 같은 히스토리가 있다고 가정
[다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치]
🎲 이 때 테스트가 덜 된 server 브랜치는 그대로 두고 client 브랜치만 master
로 합치려는 상황
$ git rebase --onto master server client
master
브랜치로부터 server 브랜치와 client 브랜치의 공통 조상까지의 커밋을 client 브랜치에서 없애고 싶을 때 사용master
브랜치에서 client 브랜치를 기반으로 새로 만들어 적용[다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치를 Rebase 하기]
master
브랜치로 돌아가서 Fast-forward
시킬 수 있다.$ git checkout master
$ git merge client
[master 브랜치를 client 브랜치 위치로 진행 시키기]
git rebase <basebranch> <topicbranch>
로 checkout 하지 않고 바로 server 브랜치를 master
브랜치로 Rebase
할 수 있다.$ git rebase master server
[master 브랜치에 server 브랜치의 수정 사항을 적용]
master
브랜치를 Fast-forward
시킨다.$ git checkout master
$ git merge server
master
브랜치에 통합됐기 때문에 더 필요하지 않으면 client 나 server 브랜치는 삭제해도 된다.$ git branch -d client
$ git branch -d server
[최종 커밋 히스토리]
🎲 새 커밋을 서버에 Push
하고 동료 중 누군가가 그 커밋을 Pull
해서 작업을 한다고 가정
git rebase
로 바꿔서 Push
해버리면 동료가 다시 Push
했을 때는 동료는 다시 Merge
해야 한다.Merge
한 내용을 Pull
하면 내 코드는 엉망이 된다.[저장소를 Clone 하고 일부 수정함]
Merge
하고 나서 서버에서 Push
Fetch
, Merge
한다.[Fetch 한 후 Merge 함]
Push
했던 팀원은 Merge
한 일을 되돌리고 다시 Rebase
한다.Fetch
하고 나면 아래와 같은 상태가 된다.[한 팀원이 다른 팀원이 의존하는 커밋을 없애고 Rebase 한 커밋을 다시 Push 함]
git pull
로 서버의 내용을 가져와서 Merge
하면 같은 내용의 수정사항을 포함한 Merge
커밋이 아래와 같이 만들어진다.[같은 Merge를 다시 한다]
git log
로 히스토리를 확인해보면 같은 커밋이 두 개 있다. (C4, C4')Rebase
로 커밋을 정리했어야 했다.🎲 어떤 팀원이 강제로 내가 한 일을 덮어썼다고 가정
[한 팀원이 다른 팀원이 의존하는 커밋을 없애고 Rebase 한 커밋을 다시 Push 함]
Merge
하는 대신 git rebase teamone/master
를 실행하면 Git은 아래와 같은 작업을 한다.Merge
커밋이 아닌 것을 결정Merge
할 브랜치에 덮어쓰이지 않은 커밋 결정teamone/master
브랜치에 적용[강제로 덮어쓴 브랜치에 Rebase 하기]
동료가 생성했던 C4와 C4' 커밋 내용이 완전히 같은 때만 이렇게 동작된다.
▶️ 커밋 내용이 아예 다르거나 비슷하다면 커밋이 두개 생긴다.
git pull --rebase
로 Rebase
할 수도 있다.
Push
하기 전에 정리하려고 Rebase
하는 것과 절대 공개하지 않고 혼자 Rebase
하는 경우도 괜찮다.
▶️ 하지만, 이미 공개하여 사람들이 사용하는 커밋을 Rebase
하면 틀림없이 문제가 생긴다.
Rebase
할 수도 있지만, 리모트 등 어딘가에 Push
로 내보낸 커밋에 대해서는 절대 Rebase
하지 말아야 한다.