티스토리 뷰
Git Branch
모든 버전 관리 시스템은 브랜치(Branch)를 지원한다. 개발을 하다 보면 코드를 여러 개로 복사해야 하는 일이 자주 생긴다. 코드를 통째로 복사하고 나서 원래 코드와는 상관없이 독립적으로 개발을 진행할 수 있는데, 이렇게 독립적으로 개발하는 것이 브랜치다.
Git의 브랜치 모델은 다른 VCS들과 다르며, 최고의 장점으로 손꼽힌다. Git의 브랜치는 매우 가볍다. 순식간에 브랜치를 새로 만들고 브랜치 사이를 이동할 수 있다. 때문에 이전의 코드를 그대로 가져와 독립적인 개발을 진행할 수 있다는 브랜치의 생성 목적에 맞게 활용할 수 있으면서도 리스크를 줄일 수 있었다.
브랜치(Branch)란 무엇인가
Git이 브랜치를 다루는 방법을 알아보기 위해 예시를 들어 보겠다.
➜ branch-ex git:(master) vim file1.txt
➜ branch-ex git:(master) ✗ git add file1.txt
➜ branch-ex git:(master) ✗ git commit -m "commit 1"
[master (최상위-커밋) 7633576] commit 1
1 file changed, 1 insertion(+)
create mode 100644 file1.txt
➜ branch-ex git:(master) vim file2.txt
➜ branch-ex git:(master) ✗ git add file2.txt
➜ branch-ex git:(master) ✗ git commit -m "commit 2"
[master 4f8bbe5] commit 2
1 file changed, 1 insertion(+)
create mode 100644 file2.txt
➜ branch-ex git:(master) mkdir directory1
➜ branch-ex git:(master) vim directory1/file3.txt
➜ branch-ex git:(master) ✗ git add directory1/file3.txt
➜ branch-ex git:(master) ✗ git commit -m "commit 3"
[master cb76639] commit 3
1 file changed, 1 insertion(+)
create mode 100644 directory1/file3.txt
위 예시 코드는 총 3개의 커밋을 생성하는 작업이다. 그 결과 로그를 보면 아래와 같다.
* cb76639 (HEAD -> master) commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
Git은 커밋을 객체로 관리한다는 것을 이전 포스팅에서 배웠다. 그리고 이 커밋 객체를 나타내는 링크 주소인 sha-1 체크섬 값을 가진다는 것도 알고 있다. 때문에 각 커밋 객체는 이전 커밋을 나타내는 체크섬 값을 저장할 수 있고, 그로 인해 우리는 언제든지 원하는 커밋으로 되돌아갈 수 있다. 40개의 16진수로 이루어진 체크섬 값을 알고 있다면 말이다. 하지만 당연히 그건 쉽지 않다.
개발을 할 때도 직접 메모리 위치로 데이터를 접근하는 것이 아니라 변수를 생성한다. 브라우저에 IP 주소 값을 직접 입력하지 않고 도메인 주소를 입력한다. 마찬가지로 우리에게 좀 더 친숙한 언어로 커밋을 가리키고 싶을 것이다. 그게 바로 브랜치의 역할이다.
브랜치(Branch) : 커밋 사이를 가볍게 이동할 수 있는 포인터 역할로 별칭을 가질 수 있음
실제로 Git의 브랜치는 어떤 한 커밋을 가리키는 40글자의 SHA-1 체크섬 파일에 불과하기 때문에 만들기도 쉽고 지우기도 쉽다. 새로 브랜치를 하나 만드는 것은 41바이트 크기의 파일을(40자와 줄 바꿈 문자) 하나 만드는 것에 불과하다.
git init
명령으로 초기화할 때 자동으로 master
브랜치가 생성된다. 처음 커밋하면 이 master
브랜치가 생성된 커밋을 가리킨다. 이후 커밋을 새로 생성할 때마다 master
브랜치는 자동으로 가장 마지막 커밋을 가리킨다.
따라서 우리는 master
라는 이름의 브랜치로 최신의 커밋인 cb76639
를 접근할 수 있고, 해당 커밋 객체의 Index(최상위 디렉토리)를 가져와 워킹 디렉토리에서 마치 이전 코드를 전부 복사한 것처럼 그대로 이어서 작업할 수 있다.
HEAD Refs
만약 새로운 브랜치를 하나 더 생성하면 어떻게 될까? 당연히 그 브랜치도 최신의 커밋을 가리키게 된다.
➜ branch-ex git:(master) git branch testing
➜ branch-ex git:(master) git log --oneline --decorate --graph --all
* cb76639 (HEAD -> master, testing) commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
testing
이란 이름의 브랜치를 새로 생성했다. 그리고 로그를 보면 cb76639
커밋을 2개의 브랜치가 모두 가리키고 있는 것을 확인할 수 있다. 지금 작업 중인 브랜치가 무엇인지 Git은 어떻게 파악할까. 눈치 챘겠지만, master
를 HEAD
라는 녀석이 화살표로 가리키고 있다. 다른 버전 관리 시스템과는 달리 Git은 HEAD
라는 특수한 포인터가 있다. 이 포인터는 지금 작업하는 로컬 브랜치를 가리킨다.
HEAD : 현재 작업 중인 브랜치나 커밋을 가리키고 있는 포인터(Refs)
브랜치 이동하기 : checkout vs switch
위 상황에서 testing
브랜치로 이동하고 싶으면 어떻게 해야 할까? 이전에는 checkout
명령어만을 사용해야 했지만, 이제는 2가지 방법이 있다.
checkout
checkout
은 브랜치를 이동할 때 주로 사용하지만, 사실 특정 커밋으로 워킹 디렉터리를 변경할 수 있는 다목적 명령어다. 예를 들어 단순히 브랜치를 이동하고 싶으면 아래와 같이 사용하면 된다.
➜ branch-ex git:(master) git checkout testing
'testing' 브랜치로 전환합니다
➜ branch-ex git:(testing) git log --oneline --decorate --graph --all
* cb76639 (HEAD -> testing, master) commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
HEAD
가 testing
브랜치를 가리키게 변경됐다. 이런 단순한 기능도 있지만, 특정한 커밋에 있는 파일을 가져와 복원하거나 커밋으로 이동할 수도 있다. 이때 이동하고 싶은 커밋의 체크섬을 업력해 주면 된다.
➜ branch-ex git:(testing) git checkout 4f8bbe54cbc2bdd5386a17099ef0116b06aaa76d
Note: switching to '4f8bbe54cbc2bdd5386a17099ef0116b06aaa76d'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD의 현재 위치는 4f8bbe5 commit 2
➜ branch-ex git:(4f8bbe5) ls -al
total 16
drwxr-xr-x 5 on1ystar staff 160 10 30 19:18 .
drwxr-xr-x 9 on1ystar staff 288 10 30 17:58 ..
drwxr-xr-x 12 on1ystar staff 384 10 30 19:18 .git
-rw-r--r-- 1 on1ystar staff 15 10 30 17:59 file1.txt
-rw-r--r-- 1 on1ystar staff 15 10 30 18:01 file2.txt
➜ branch-ex git:(4f8bbe5) git log --oneline --decorate --graph --all
* cb76639 (testing, master) commit 3
* 4f8bbe5 (HEAD) commit 2
* 7633576 commit 1
위 예시의 경우, 2번째 커밋으로 이동했다. 때문에 워킹 디렉토리를 확인해 보면, directory1/test3.txt
는 존재하지 않는다. 로그를 확인해 보면 재밌는 점을 발견할 수 있는데, HEAD
가 브랜치를 가리키는 것이 아니라 단독으로 2번째 커밋(4f8bbe5
)을 가리키고 있다. 이 상태를 detached HEAD라고 한다.
💡 detached HEAD 상태란 HEAD가 특정 브랜치를 가리키는 것이 아니라, 특정 커밋이나 태그를 직접 가리키고 있는 상태를 말한다. 이 상태에서 작업 후 커밋을 하면 그 커밋은 아무 브랜치에도 연결되지 않은 고립된 상태가 된다. 그래서 보통 과거의 커밋을 일시적으로 점검하거나, 임시 테스트 같은 상황에서 실험적인 코드를 작성할 때 사용된다.
이처럼 checkout은 너무 다양한 기능을 가지고 있기 때문에 잘못 사용될 우려도 크다. 그래서 Git은 단순한 브랜치 이동을 위한 switch
명령어를 추가했다.
switch
switch
는 브랜치 전환에만 집중한 명령어로, checkout의 브랜치 이동 기능을 대체하기 위해 도입됐다.
➜ branch-ex git:(testing) git switch master
'master' 브랜치로 전환합니다
➜ branch-ex git:(master) git switch testing
'testing' 브랜치로 전환합니다
Git Merge
브랜치 분기
브랜치를 변경한 상태에서 새로운 커밋을 추가하게 되면 각각의 브랜치는 서로 다른 커밋을 가리키게 된다. 약간의 시나리오를 가정해 보겠다.
master
: 현재 서비스 중인 브랜치feature2
: 새로운 기능을 위한 브랜치
현재 서비스는 master
브랜치의 최신 커밋 상태에서 서비스 중이다. 때문에 새로운 기능을 위해 master
브랜치에 새로운 커밋을 추가하는게 아닌 feature2
브랜치에 추가 개발을 진행함으로써, 실 서비스 환경과 간단하게 분리시킬 수 있다(물론 상당히 간단한 시나리오다).
➜ branch-ex git:(master) git switch feature2
'feature2' 브랜치로 전환합니다
➜ branch-ex git:(feature2) vim test4.txt
➜ branch-ex git:(feature2) ✗ git add --all
➜ branch-ex git:(feature2) ✗ git commit -m "commit 4"
[feature2 bc531ae] commit 4
1 file changed, 1 insertion(+)
create mode 100644 file4.txt
➜ branch-ex git:(feature2) git log --oneline --decorate --graph --all
* bc531ae (HEAD -> feature2) commit 4
* cb76639 (master) commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
feature2
브랜치는 최신 커밋인 bc531ae
를 가리키게 되고, master
브랜치는 여전히 이전 3번째 커밋을 가리키고 있다.
위 상황에서 서비스에 문제가 생겨 급히 수정할 사항이 생겼다고 가정해 보자. 실 서비스 환경이 필요한데, 간단하게 master 브랜치로 돌아가면 된다. 이전에 작업했던 브랜치로 이동하면 워킹 디렉토리의 파일은 그 브랜치에서 가장 마지막으로 했던 작업 내용으로 변경된다. 커밋이 그 때 당시의 스냅샷을 가지고 있기 때문이다. 그 다음 버그를 해결할 때까지 사용할 hotfix 브랜치가 새로 필요하다.
💡 아직 커밋하지 않은 파일이 Checkout 할 브랜치와 충돌 나면 브랜치를 변경할 수 없다. 브랜치를 변경할 때는 워킹 디렉토리를 정리하는 것이 좋다.
➜ branch-ex git:(testing) git switch master
'master' 브랜치로 전환합니다
➜ branch-ex git:(master) git switch -c hotfix
새로 만든 'hotfix' 브랜치로 전환합니다
➜ branch-ex git:(hotfix) vim file1.txt
➜ branch-ex git:(hotfix) ✗ git add file1.txt
➜ branch-ex git:(hotfix) ✗ git commit -m "commit 5 for hotfix"
[hotfix 6de8f2a] commit 5 for hotfix
1 file changed, 1 insertion(+)
➜ branch-ex git:(hotfix) git log --oneline --decorate --graph --all
* 6de8f2a (HEAD -> hotfix) commit 5 for hotfix
| * bc531ae (feature2) commit 4
|/
* cb76639 (master) commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
master
브랜치에서 hotfix
브랜치를 새로 생성한 뒤, 브랜치를 전환한다. 그 다음, file1.txt
를 수정 후 커밋한다.
이걸로 급한 버그가 수정됐다. 이제 hotfix된 버전으로 서비스를 배포하기 위해 master
브랜치를 hotfix
브랜치와 병합해야 한다.
브랜치 병합 : Merge
Fast-forward
브랜치 병합에는 merge
명령어가 사용되는데, 병합에 기준이 되는 브랜치로 전환한 후 사용해야 한다. 위 상황같은 경우 master
브랜치로 전환한 후 hotfix
브랜치를 병합시킨다.
➜ branch-ex git:(hotfix) git checkout master
'master' 브랜치로 전환합니다
➜ branch-ex git:(master) git merge hotfix
업데이트 중 cb76639..6de8f2a
Fast-forward
file1.txt | 1 +
1 file changed, 1 insertion(+)
➜ branch-ex git:(master) git log --oneline --decorate --graph --all
* 6de8f2a (HEAD -> master, hotfix) commit 5 for hotfix
| * bc531ae (feature2) commit 4
|/
* cb76639 commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
Merge 메시지에서 “Fast-forward” 라고 표시해 준다. hotfix
브랜치가 가리키는 6de8f2a
커밋이 cb76639
커밋에 기반한 브랜치이기 때문에 사실 브랜치 포인터는 Merge 과정 없이 그저 최신 커밋으로 이동한다. 이런 Merge 방식을 Fast forward 라고 부른다.
Fast-forward : A 브랜치에서 다른 B 브랜치를 Merge 할 때, B 브랜치가 A 브랜치 이후의 커밋을 가리키고 있으면, 그저 A 브랜치가 B 브랜치와 동일한 커밋을 가리키도록 이동
그림으로 확인해 보면, 단순히 master 브랜치 포인터가 가리키는 커밋이 바뀌었을 뿐이다.
이후 필요 없어진 hotfix
브랜치는 삭제해 주면 된다.
➜ branch-ex git:(master) git branch -d hotfix
hotfix 브랜치 삭제 (과거 6de8f2a).
3-way Merge
이제 급한 버그가 수정됐으니, 새로운 기능 개발을 마저 하기 위해 feature2
브랜치로 돌아가면 된다.
➜ branch-ex git:(feature2) vim file4.txt
➜ branch-ex git:(feature2) ✗ git add --all
➜ branch-ex git:(feature2) ✗ git commit -m "feature 2 complete"
[feature2 b623610] feature 2 complete
1 file changed, 1 insertion(+)
➜ branch-ex git:(feature2) git log --oneline --decorate --graph --all
* b623610 (HEAD -> feature2) feature 2 complete
* bc531ae commit 4
| * 6de8f2a (master) commit 5 for hotfix
|/
* cb76639 commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
이후 기능이 다 완성되면, 이제 feature2
를 master
브랜치에 병합해야 할 것이다. 이 때는 이전의 Fast-forward 상황과는 다르다. master
브랜치가 가리키는 커밋이 feature2
브랜치가 가리키고 있는 커밋의 이전 커밋이 아니기 때문이다.
위와 같은 경우에는 각 브랜치가 가리키는 커밋 두 개와 공통 조상 하나를 사용하여 3-way Merge를 한다.
즉, 각 브랜치가 가리키고 있는 6de8f2a
, b62361O
커밋과 두 브랜치의 공통 조상인 cb76639
커밋이 해당된다. 병합을 한 뒤, 결과를 확인해 보자.
➜ branch-ex git:(feature2) git switch master
'master' 브랜치로 전환합니다
➜ branch-ex git:(master) git merge feature2
Merge made by the 'recursive' strategy.
file4.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 file4.txt
➜ branch-ex git:(master) git log --oneline --decorate --graph --all
* f7cb86a (HEAD -> master) Merge branch 'feature2'
|\
| * b623610 (feature2) feature 2 complete
| * bc531ae commit 4
* | 6de8f2a commit 5 for hotfix
|/
* cb76639 commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
단순히 브랜치 포인터를 최신 커밋으로 옮기는 게 아니라, 3-way Merge 의 결과를 별도의 커밋으로 만들고 나서 해당 브랜치가 그 커밋을 가리키도록 이동시킨다. 그래서 이런 커밋은 부모가 여러 개고 Merge 커밋이라고 부른다.
이런 식으로 3개의 커밋을 참조하는 이유는 두 브랜치가 같은 조상 커밋에서 어떻게 발전했는지 비교하기 위함이다. 어떤 부분이 다른지 알아야 병합할 때 빠진 부분 없이 반영할 수 있고, 특히나 같이 작성한 파일은 병합 과정에서 충돌이 발생할 수 있다. 위 예시에서는 feature2
브랜치에서 새로 생성한 파일에서만 작업했기 때문에 충돌 없이 바로 병합됐다. 하지만 만약 master
브랜치에서 작업하고 있던 파일을 feature2 브랜치에서도 작업했다면, 충돌 확률이 매우 높을 것이다.
충돌(Conflict)
가끔씩 3-way Merge가 실패할 때도 있다. Merge 하는 두 브랜치에서 같은 파일의 한 부분을 동시에 수정하고 Merge 하면 Git은 해당 부분을 Merge 하지 못한다. feature2
브랜치에서 hotfix 당시 수정헀던 file1.txt
파일을 다른 방식으로 수정한 뒤, 다시 병합을 시도해 충돌하는 상황을 만들어 보자.
➜ branch-ex git:(master) git merge feature2
자동 병합: file1.txt
충돌 (내용): file1.txt에 병합 충돌
자동 병합이 실패했습니다. 충돌을 바로잡고 결과물을 커밋하십시오.
메세지를 보면 file1.txt
에 병합 충돌이 일어나 자동 병합에 실패했다고 표시된다. Git은 자동으로 Merge 하지 못해서 새 커밋이 생기지 않는다. 변경사항의 충돌을 개발자가 해결하지 않는 한 Merge 과정을 진행할 수 없다. Merge 충돌이 일어났을 때 Git이 어떤 파일을 Merge 할 수 없었는지 살펴보려면 git status
명령을 이용하면 된다.
➜ branch-ex git:(master) ✗ git status
현재 브랜치 master
병합하지 않은 경로가 있습니다.
(충돌을 바로잡고 "git commit"을 실행하십시오)
(병합을 중단하려면 "git merge --abort"를 사용하십시오)
병합하지 않은 경로:
(해결했다고 표시하려면 "git add <파일>..."을 사용하십시오)
양쪽에서 수정: file1.txt
커밋할 변경 사항을 추가하지 않았습니다 ("git add" 및/또는 "git commit -a"를
사용하십시오
충돌이 일어난 파일은 unmerged
상태로 표시된다. Git은 충돌이 난 부분을 표준 형식에 따라 표시해준다. 그러면 개발자는 해당 부분을 수동으로 해결한다. 충돌 난 부분은 아래와 같이 표시된다.
➜ branch-ex git:(master) ✗ vim file1.txt
This is file 1
<<<<<<< HEAD
update for hotfix
=======
hihihihi~ update for conflict test in feature2 branch~~~~
>>>>>>> feature2
=======
위쪽의 내용은 HEAD
버전(merge 명령을 실행할 때 작업하던 master
브랜치)의 내용이고 아래쪽은 feature2
브랜치의 내용이다. 충돌을 해결하려면 위쪽이나 아래쪽 내용 중에서 고르거나 새로 작성하여 Merge 한다. 즉, 개발자가 해당 파일을 열어보고 직접 수정해 줘야 한다는 것이다(간단한 것들은 Merge 도구를 사용해 해결할 수도 있다).
파일을 수정한 뒤 git add
후 커밋하면 중단됐던 병합이 마무리된다.
➜ branch-ex git:(master) ✗ git add file1.txt
➜ branch-ex git:(master) git status
➜ branch-ex git:(master) git commit
[master 43ef359] Merge branch 'feature2'
이때 아래와 같이 커밋 메세지를 작성해 주는데, 어떻게 충돌을 해결했고 좀 더 확인해야 하는 부분은 무엇이고 왜 그렇게 해결했는지에 대해서 자세하게 기록하면, 나중에 이 Merge 커밋을 이해하는데 도움을 준다.
Merge branch 'feature2'
Conflicts:
file1.txt
#
# It looks like you may be committing a merge.
# If this is not correct, please run
# git update-ref -d MERGE_HEAD
# and try again.
# 변경 사항에 대한 커밋 메시지를 입력하십시오. '#' 문자로 시작하는
# 줄은 무시되고, 메시지를 입력하지 않으면 커밋이 중지됩니다.
#
# 현재 브랜치 master
# 모든 충돌을 바로잡았지만 아직 병합하는 중입니다.
#
사실 위 예제는 충돌 상황 중에서도 지극히 간단한 예제다. 일반적으로 충돌이 발생하게 되면 더 복잡하고 귀찮아 진다. 특히나 오랫동안 병합하지 않은 두 브랜치를 병합할 때는 더 위험하다. 때문에 자주 병합을 진행하면서 조금씩 충돌들을 해결해 나가야 하고, 프로젝트에 맞는 브랜치 워크플로우를 선택해 활용하는 것도 방법이다.
Git Remote Branch
리모트 Refs는 리모트 저장소에 있는 포인터인 레퍼런스다. 리모트 저장소에 있는 브랜치, 태그, 등등을 의미한다. git ls-remote [remote]
명령으로 모든 리모트 Refs를 조회할 수 있다. git remote show [remote]
명령은 모든 리모트 브랜치와 그 정보를 보여준다. 리모트 Refs가 있지만 보통은 리모트 트래킹 브랜치를 사용한다.
리모트 트래킹 브랜치
리모트 트래킹 브랜치는 리모트 브랜치를 추적하는 레퍼런스이며 브랜치다. 리모트 트래킹 브랜치는 로컬에 있지만 임의로 움직일 수 없다. 리모트 서버에 연결할 때마다 리모트의 브랜치 업데이트 내용에 따라서 자동으로 갱신될 뿐이다. 리모트 트래킹 브랜치는 일종의 북마크라고 할 수 있다. 리모트 저장소에 마지막으로 연결했던 순간에 브랜치가 무슨 커밋을 가리키고 있었는지를 나타낸다.
리모트 트래킹 브랜치(remote tracking branch) : 리모트 브랜치를 추적하고 있는 로컬 브랜치
리모트 트래킹 브랜치의 이름은 <remote>/<branch>
형식으로 되어 있다. 예를 들어 리모트 저장소 origin
의 master
브랜치를 보고 싶다면 origin/master
라는 이름으로 브랜치를 확인하면 된다.
💡 origin이란 이름은 git clone 명령이 자동으로 만들어주는 리모트 이름이다. git clone -o [원하는 리모트 이름] 라고 옵션을 주고 명령을 실행하면 사용자가 정한 대로 리모트 이름을 생성해 준다.
예제를 살펴보자. git.ourcompany.com
이라는 Git 서버가 있고 이 서버의 저장소를 하나 Clone 하면 Git은 자동으로 origin
이라는 이름을 붙인다. origin
으로부터 저장소 데이터를 모두 내려받고 master
브랜치를 가리키는 포인터를 만든다. 이 포인터는 origin/master
라고 부르고 멋대로 조종할 수 없다. 그리고 Git은 로컬의 master
브랜치가 origin/master
를 가리키게 한다. 이제 이 master
브랜치에서 작업을 시작할 수 있다.
로컬 저장소에서 어떤 작업을 하고 있는데 동시에 다른 팀원이 git.ourcompany.com
서버에 Push 하고 master
브랜치를 업데이트한다. 그러면 이제 팀원 간의 히스토리는 서로 달라진다. 서버 저장소로부터 어떤 데이터도 주고받지 않아서 origin/master
포인터는 그대로다.
이런 경우에는 작업을 중단하고 적당한 시기에 동기화를 시켜줘야 한다.
리모트 저장소를 동기화 시키기 : Fetch
git fetch
는 Git에서 원격 저장소의 변경 사항을 로컬로 가져오는 명령어다. 이 명령어는 현재 작업 중인 로컬 브랜치에 영향을 주지 않고, 원격 저장소에서 변경된 커밋, 브랜치, 태그 등의 메타데이터를 업데이트하는 데 사용된다. 위 예제에서는 git fetch origin
명령을 사용한다.
명령이 수행되면 리모트 트래킹 브랜치인 origin/master
가 리모트 저장소의 master
브랜치와 같이 최신 커밋을 가리키게 된다.
로컬 브랜치를 서버로 전송하기 : Push
로컬의 브랜치를 서버로 전송하려면 쓰기 권한이 있는 리모트 저장소에 Push 해야 한다. 로컬 저장소의 브랜치는 자동으로 리모트 저장소로 전송되지 않는다. 명시적으로 브랜치를 Push 해야 정보가 전송된다.
아래와 같이 git push <remote> <branch>
명령을 사용한다.
➜ branch-ex git:(master) git push origin master
오브젝트 나열하는 중: 25, 완료.
오브젝트 개수 세는 중: 100% (25/25), 완료.
Delta compression using up to 8 threads
오브젝트 압축하는 중: 100% (17/17), 완료.
오브젝트 쓰는 중: 100% (25/25), 2.05 KiB | 2.05 MiB/s, 완료.
Total 25 (delta 6), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (6/6), done.
To https://github.com/on1ystar/git-study.git
* [new branch] master -> master
git push origin master:master
라고 Push 하는 것도 같은 의미인데 이것은 “로컬의 master 브랜치를 리모트 저장소의 master 브랜치로 Push 하라” 라는 뜻이다. 로컬 브랜치의 이름과 리모트 서버의 브랜치 이름이 다를 때 필요하다. 리모트 저장소에 master
라는 이름 대신 다른 이름을 사용하려면 git push origin master:my-master-branch
처럼 사용한다.
이 상황에서 누군가가 serverfix
브랜치를 만들어 작업을 한 뒤, 서버에 Push 했다고 해보자. 그럼 우리는 리모트 저장소를 Fetch 한 뒤, origin/serverfix
라는 이름으로 접근할 수 있다.
➜ branch-ex git:(master) git branch -a
feature2
* master
remotes/origin/master
remotes/origin/serverfix
이제 새로 받은 브랜치를 병합해서 사용하거나, 새로운 브랜치를 만들어서 이어가도 된다. 단, origin/serverfix
라는 브랜치 포인터는 수정할 수 없다는 것만 기억하면 된다.
트래킹 브랜치
리모트 트래킹 브랜치를 로컬 브랜치로 Checkout 하면 자동으로 트래킹(Tracking) 브랜치가 만들어진다. 이때 트래킹 하는 대상 브랜치를 Upstream 브랜치 라고 부른다.
- 트래킹 브랜치 : 리모트 브랜치와 직접적인 연결고리가 있는 로컬 브랜치
- Upstream 브랜치 : 트래킹 브랜치가 트래킹하는 대상 브랜치(리모트 브랜치)
서버로부터 저장소를 Clone 하면 Git은 자동으로 master
브랜치를 origin/master
브랜치의 트래킹 브랜치로 만든다. 예를 들어 새로운 디렉토리에서 git clone
명령을 수행하면 다음과 같아진다.
➜ remote-ex git:(master) git clone https://github.com/on1ystar/git-study.git
'git-study'에 복제합니다...
remote: Enumerating objects: 28, done.
remote: Counting objects: 100% (28/28), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 28 (delta 7), reused 28 (delta 7), pack-reused 0 (from 0)
오브젝트를 받는 중: 100% (28/28), 완료.
델타를 알아내는 중: 100% (7/7), 완료.
➜ git-study git:(master) git log --oneline --decorate --graph --all
* 27ace59 (origin/serverfix) server fix
* 43ef359 (HEAD -> master, origin/master, origin/HEAD) Merge branch 'feature2'
|\
| * 91c9c82 update for conflict test
* | f7cb86a Merge branch 'feature2'
|\|
| * b623610 feature 2 complete
| * bc531ae commit 4
* | 6de8f2a commit 5 for hotfix
|/
* cb76639 commit 3
* 4f8bbe5 commit 2
* 7633576 commit 1
master 브랜치가 origin/master 브랜치의 최신 커밋을 가리키고 있는 걸 확인할 수 있다. 이게 리모트 저장소가 그랬으니까 똑같이 복제된거 아니냐고 생각할 수 있는데, 트래킹 브랜치를 직접 만들 수도 있다.
git checkout -b <branch> <remote>/<branch>
명령으로 간단히 트래킹 브랜치를 만들 수 있다. --track
옵션을 사용하여 로컬 브랜치 이름을 자동으로 생성할 수도 있다.
➜ git-study git:(serverfix) git checkout -b my-master origin/master
'my-master' 브랜치가 리모트의 'master' 브랜치를 ('origin'에서) 따라가도록 설정되었습니다.
새로 만든 'my-master' 브랜치로 전환합니다
➜ git-study git:(master) git checkout --track origin/serverfix
'serverfix' 브랜치가 리모트의 'serverfix' 브랜치를 ('origin'에서) 따라가도록 설정되었습니다.
새로 만든 'serverfix' 브랜치로 전환합니다
➜ git-study git:(my-master) git log --oneline --decorate --graph --all
* 27ace59 (origin/serverfix, serverfix) server fix
* 43ef359 (HEAD -> my-master, origin/master, origin/HEAD, master) Merge branch 'feature2'
|\
| * 91c9c82 update for conflict test
* | f7cb86a Merge branch 'feature2'
...
그리고 이미 로컬에 존재하는 브랜치가 리모트의 특정 브랜치를 추적하게 하려면 git branch
명령에 -u
나 --set-upstream-to
옵션을 붙여서 설정하면 된다.
➜ git-study git:(my-master) git branch sf
➜ git-study git:(my-master) git switch sf
'sf' 브랜치로 전환합니다
➜ git-study git:(sf) git branch -u origin/serverfix
'sf' 브랜치가 리모트의 'serverfix' 브랜치를 ('origin'에서) 따라가도록 설정되었습니다.
추적 브랜치가 현재 어떻게 설정되어 있는지 확인하려면 git branch
명령에 -vv
옵션을 더한다. 이 명령을 실행하면 로컬 브랜치 목록과 로컬 브랜치가 추적하고 있는 리모트 브랜치도 함께 보여준다. 게다가, 로컬 브랜치가 앞서가는지 뒤쳐지는지에 대한 내용도 보여준다.
➜ git-study git:(sf) git branch -vv
master 43ef359 [origin/master] Merge branch 'feature2'
my-master 43ef359 [origin/master] Merge branch 'feature2'
serverfix 27ace59 [origin/serverfix] server fix
* sf 43ef359 [origin/serverfix: 1개 뒤] Merge branch 'feature2'
위 결과를 보면, 우리가 생성한 트래킹 브랜치 4개가 있다. 리모트 브랜치마다 각각 2개씩 생성됐는데, sf
브랜치를 보면 origin/serverfix
브랜치보다 “1개 뒤”라고 표시된다. 이는 서버의 브랜치에서 아직 로컬 브랜치로 병합하지 않은 커밋이 1개 있다는 말이다.
로그를 확인해 보자.
* 27ace59 (origin/serverfix, serverfix) server fix
* 43ef359 (HEAD -> sf, origin/master, origin/HEAD, my-master, master) Merge branch 'feature2'
|\
| * 91c9c82 update for conflict test
...
sf
브랜치를 만들 당시, master
브랜치로 Checkout 된 상태였기 때문에, 43ef359
커밋을 가리키고 있는 상태로 sf
브랜치가 만들어져 1개의 커밋이 뒤쳐지게 된 것이다.
💡 여기서 중요한 점은 명령을 실행했을 때 나타나는 결과는 모두 마지막으로 서버에서 데이터를 가져온(fetch) 시점을 바탕으로 계산한다는 점이다. 단순히 이 명령만으로는 서버의 최신 데이터를 반영하지는 않으며 로컬에 저장된 서버의 캐시 데이터를 사용한다. 따라서 아래처럼 두 명령을 이어서 사용하는 것이 좋다.$ git fetch --all; git branch -vv
그럼 이렇게 트래킹 브랜치를 만드는 이유가 뭘까?
트래킹 브랜치를 사용하면 원격 브랜치의 최신 상태를 확인하고 필요한 경우 간편하게 변경 사항을 가져오거나(push), 병합(pull) 할 수 있다.
서버의 브랜치를 로컬 브랜치로 가져와 병합하기 : Pull
트래킹 브랜치에서 git pull
명령을 내리면 리모트 저장소로부터 데이터를 내려받아 연결된 리모트 브랜치와 자동으로 병합한다.
➜ git-study git:(sf) git pull
업데이트 중 43ef359..27ace59
Fast-forward
file6.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 file6.txt
➜ git-study git:(sf) git log --oneline --decorate --graph --all
* 27ace59 (HEAD -> sf, origin/serverfix, serverfix) server fix
* 43ef359 (origin/master, origin/HEAD, my-master, master) Merge branch 'feature2'
|\
| * 91c9c82 update for conflict test
git pull
로그를 보면 Fast-forward
로 병합된 것을 확인할 수 있다. 커밋 로그에서도 sf
브랜치가 origin/serverfix
브랜치와 같은 커밋을 가리키고 있다.
➜ git-study git:(sf) git branch -vv
master 43ef359 [origin/master] Merge branch 'feature2'
my-master 43ef359 [origin/master] Merge branch 'feature2'
serverfix 27ace59 [origin/serverfix] server fix
* sf 27ace59 [origin/serverfix] server fix
이제 모든 트래킹 브랜치가 리모트 브랜치와의 차이가 없어졌다.
💡 만약 트래킹 브랜치가 설정되어 있지 않았다면, git pull 이 아니라 git pull origin/serverfix sf 라고 해야 한다.
git fetch
명령을 실행하면 서버에는 존재하지만, 로컬에는 아직 없는 데이터를 받아와서 저장한다. 이 때 워킹 디렉토리의 파일 내용은 변경되지 않고 그대로 남는다. 서버로부터 데이터를 가져와서 저장해두고 사용자가 Merge 하도록 준비만 해둔다. 간단히 말하면 git pull
명령은 대부분 git fetch
명령을 실행하고 나서 자동으로 git merge
명령을 수행하는 것 뿐이다.
만약 작업하던 것이 있었는데 git pull
명령을 실행해 버리면, 자동 병합을 진행하기 때문에 작업하던 것들이 모두 손실될 수 있다. 따라서 일반적으로 fetch
와 merge
명령을 명시적으로 사용하는 것이 pull
명령으로 한번에 두 작업을 하는 것보다 낫다.
Reference
'Git 공부' 카테고리의 다른 글
Git의 원리를 이해하고 사용하기 - 2 (파일의 라이프 사이클, 되돌리기) (1) | 2024.10.24 |
---|---|
Git의 원리를 이해하고 시작하기 - 1 (Git의 핵심, 데이터 모델) (1) | 2024.10.22 |
Git Github 사용법 (0) | 2019.03.18 |
Git fatal: refusing to merge unrelated histories (0) | 2019.03.15 |
Git 병합 충돌 (0) | 2019.03.11 |
- Total
- Today
- Yesterday
- 파이썬 for Beginner 솔루션
- 파이썬 for Beginner 연습문제
- 스프링
- 쉽게 배우는 운영체제
- Computer_Networking_A_Top-Down_Approach
- 스프링 테스트
- 운영체제 반효경
- 스프링 컨테이너
- 지옥에서 온 git
- git branch
- shell code
- Gradle
- JPA
- git
- Python Cookbook
- 프로그래머스
- Thymeleaf
- 패킷 스위칭
- Do it! 정직하게 코딩하며 배우는 딥러닝 입문
- Spring
- spring mvc
- 생활코딩 javascript
- jsp
- 방명록 프로젝트
- 쉘 코드
- 김영환
- Spring Data JPA
- git merge
- Spring Boot
- 선형 회귀
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |