반응형

챕터 개요

  • 브랜치란 코드를 복사해서 원래 코드와는 상관없이 독립적으로 개발할 수 있는 것
  • Git의 브랜치는 가볍고 순식간에 생성, 이동 가능
  • Git은 브랜치를 작업한 후 Merge하는 방법을 권장
  • Git 브랜치에 능숙해지면 개발 방식이 완전히 바뀜

 

목차

3.1 브랜치란 무엇인가
3.2 브랜치와 Merge 의 기초
3.3 브랜치 관리
3.4 브랜치 워크플로
3.5 리모트 브랜치
3.6 Rebase 하기
3.7 요약

 

 

 


3.1 브랜치란 무엇인가

브랜치란 무엇인가

  • 브랜치란 커밋 사이를 가볍게 이동할 수 있는 어떤 포인터 같은 것
  • Git은 Change Set 대신 일련의 스냅샷으로 데이터를 기록
  • 커밋하면 Git은 스테이징 영역의 스냅샷, 커밋 메타데이터, 이전 커밋 포인터를 포함하는 커밋 개체(commit object) 생성
  • 이전 커밋 포인터로 현재 커밋이 어떤 기준으로 바뀌었는지 알 수 있음
  • 일반 커밋은 적어도 1개 이전 커밋 포인터, Merge 커밋은 여러 개 이전 커밋 포인터가 있음

 

  1. 파일을 Staging Area에 저장하면 Git 저장소에 파일 저장(Blob, Binary large object), Staging Area에 파일 체크섬 저장
  2. 커밋하면 루트 디렉토리의 트리 개체 체크섬과 함께 저장소에 저장, 커밋 개체 생성, 커밋 개체에 메타데이터와 루트 트리 개체 포인터 저장
  3. 커밋을 마치면 Git 저장소에는 각 파일의 Blob n개, 트리 개체 1개, 커밋 개체 1개가 생김
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

그림 9. 커밋과 트리 데이터

다시 파일을 수정하고 커밋하면 이전 커밋이 무엇인지도 저장한다.

그림 10. 커밋과 이전 커밋

 

  • Git의 브랜치는 커밋 사이를 가볍게 이동할 수 있는 포인터 같은 것
  • Git은 기본적으로 main 브랜치 생성
  • main 브랜치는 처음 커밋할 때 생성된 커밋 가리킴. 이후 커밋 만들면 자동으로 가장 마지막 커밋 가리킴
  • "main" 브랜치는 Git 버전 관리 시스템에서 특별한 의미 없음. git init 명령으로 초기화할 때 자동으로 생성되는 것이기 때문
그림 11. 브랜치와 커밋 히스토리

 

새 브랜치 생성하기

  • git branch 명령으로 testing 브랜치를 생성
  • HEAD 포인터는 지금 작업하고 있는 로컬 브랜치를 가리킴
  • git branch 명령은 새로운 브랜치 생성만 하고 커밋을 옮기지 않음
  • git log 명령에 --decorate 옵션 사용시 브랜치가 어떤 커밋을 가리키는지 확인 가능
$ git branch testing

그림 12. 한 커밋 히스토리를 가리키는 두 브랜치
그림 13. 현재 작업 중인 브랜치를 가리키는 HEAD

$ git log --oneline --decorate
f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project

 

브랜치 이동하기

  • git checkout 명령으로 브랜치 이동 가능
$ git checkout testing
그림 14. HEAD는 testing 브랜치를 가리킴
  • 새로운 커밋 생성
$ vim test.rb
$ git commit -a -m 'made a change'
그림 15. HEAD가 가리키는 testing 브랜치가 새 커밋을 가리킴
  • 새로 커밋해서 testing 브랜치는 앞으로 이동됨
  • master 브랜치는 여전히 이전 커밋 가리킴

 

  • git checkout master 명령으로 master 브랜치로 이동
$ git checkout master
그림 16. HEAD가 Checkout 한 브랜치로 이동함

위 명령이 실행한 일

  • 이동 시 HEAD와 워킹 디렉토리의 파일이 이전 시점으로 되돌려짐
  • 이후 커밋은 새로운 브랜치와 별개로 진행됨

노트 - Git 브랜치 이동 시 주의점

  1. 브랜치 이동 시 워킹 디렉토리의 파일이 변경됨
  2. 파일 변경으로 인해 브랜치 이동이 어려울 경우 Git은 이동 명령을 수행하지 않음

 

  • 파일 수정 후 다시 커밋
$ vim test.rb
$ git commit -a -m 'made other changes'
  • 프로젝트 히스토리는 분리되어 진행됨
  • 두 작업 내용은 서로 독립적으로 각 브랜치에 존재
  • git log 명령으로 확인 가능
  • git log --oneline --decorate --graph --all 실행 시 히스토리 출력

그림 17. 갈라지는 브랜치

 

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project
  • Git의 브랜치는 어떤 한 커밋을 가리키는 40글자의 SHA-1 체크섬 파일
  • 생성/삭제 쉬움. 생성은 41바이트 크기의 파일(40글자와 줄바꿈) 하나를 만드는 것
  • Git은 다른 버전 관리 도구와 달리 브랜치 생성 빠름
  • 커밋 시 이전 커밋의 정보를 저장하기 때문에 Merge 할 때 어디서부터(Merge Base) 합쳐야 하는지 알 수 있음
  • 수시로 브랜치 생성 및 병합 가능

 


3.2 브랜치와 Merge 의 기초

브랜치와 Merge 의 기초

실제 개발과정에서 겪을 만한 예제를 하나 살펴보자. 브랜치와 Merge는 보통 이런 식으로 진행한다.

  1. 웹사이트가 있고 뭔가 작업을 진행하고 있다.
  2. 새로운 이슈를 처리할 새 Branch를 하나 생성한다.
  3. 새로 만든 Branch에서 작업을 진행한다.

이때 중요한 문제가 생겨서 그것을 해결하는 Hotfix를 먼저 만들어야 한다. 그러면 아래와 같이 할 수 있다.

  1. 새로운 이슈를 처리하기 이전의 운영(Production) 브랜치로 이동한다.
  2. Hotfix 브랜치를 새로 하나 생성한다.
  3. 수정한 Hotfix 테스트를 마치고 운영 브랜치로 Merge 한다.
  4. 다시 작업하던 브랜치로 옮겨가서 하던 일 진행한다.

 

브랜치의 기초

  • 현재 작업하는 프로젝트에서 이전 master 브랜치에 커밋 몇 번 했다고 가정

그림 18. 현재 커밋 히스토리

 

  • 이슈 관리 시스템에 등록된 53번 이슈 처리한다고 가정
  • 해당 이슈에 집중할 수 있는 브랜치 생성 후 처리
  • git checkout -b 옵션 추가: 브랜치 생성과 동시에 checkout 진행
$ git checkout -b iss53
Switched to a new branch "iss53"

# 아래 두 명령을 줄여놓은 것
$ git branch iss53
$ git checkout iss53

그림 19. 브랜치 포인터를 새로 만듦

 

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'

그림 20. 진행 중인 iss53 브랜치

  • 만드는 사이트에 문제가 생겨서 즉시 고쳐야 하는 상황 가정 -> Hotfix로 커밋
  • Hotfix에 iss53 섞이는 것 방지하기 위해 iss53 관련 코드를 어딘가에 저장하고 원래 운영 환경의 소스로 복구하고자 한다.
  • master 브랜치로 돌아가면 됨

 

  • 커밋하지 않은 파일이 있거나 충돌하면 브랜치 이동 불가
  • 워킹 디렉토리 정리 추천
  • Stashing과 Cleaning 등으로 이런 문제 해결 가능
  • 지금은 작업하던 것 모두 커밋 후 master 브랜치로 옮김.
$ git checkout master
Switched to branch 'master'
  • 워킹 디렉토리는 53번 이슈를 시작하기 이전 모습으로 되돌려져서 새로운 문제에 집중할 수 있음
  • Git은 워킹 디렉토리에 파일들을 추가/삭제/수정 후 Checkout 한 브랜치의 마지막 스냅샷으로 되돌려 놓음

 

해결해야 할 핫픽스가 생겼을 때를 살펴보자. `hotfix`라는 브랜치를 만들고 새로운 이슈를 해결할 때까지 사용한다.

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)

 

 

그림 21. master 브랜치에서 갈라져 나온 hotfix 브랜치

  • 운영 환경에 적용 전 테스트 필요
  • 테스트 후 hotfix 브랜치를 master 브랜치에 합치기
  • git merge 사용
  • Fast-forward 가 발생하면 그냥 브랜치 포인터가 최신 커밋으로 이동
  • Fast-forward란 A 브랜치에서 다른 B 브랜치를 Merge 할 때 B 브랜치가 A 브랜치 이후의 커밋을 가리키고 있으면 그저 A 브랜치가 B 브랜치와 동일한 커밋을 가리키도록 이동시키는 것
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

 

  • hotfix 가 master 브랜치에 포함되었고 운영 환경에 적용할 수 있는 상태가 되었다고 가정

그림 22. Merge 후 hotfix 같은 것을 가리키는 master 브랜치

  • git branch -d : 브랜치 삭제
  • 더 이상 필요 없는 hotfix 브랜치 삭제 후 iss53으로 이동
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)

그림 23. master와 별개로 진행하는 iss53 브랜치

  • hotfix가 iss53 브랜치에 영향을 주지 않음을 이해하는 것이 중요
  • git merge master로 master 브랜치를 iss53 브랜치에 합치면 hotfix가 적용됨
  • iss53 브랜치가 master에 Merge 가능한 수준이 될 때까지 기다렸다가 Merge하면 hotfix와 iss53 브랜치가 합쳐짐

 

Merge의 기초

  • iss53 브랜치를 master 브랜치에 Merge 하는 과정은 hotfix 브랜치를 Merge 하는 과정과 유사
  • git merge 명령으로 합칠 브랜치(master)에서 합쳐질 브랜치(iss53)를 Merge 하면 됨
$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)
  • Fast-forward로 Merge하지 않고 3-way Merge를 수행

그림 24. 커밋 3개를 Merge

 

  • 3-way Merge 의 결과를 새로운 커밋으로 생성 후 해당 브랜치가 새로운 커밋을 가리키도록 이동
  • 이 커밋은 부모가 여러 개인 Merge 커밋

그림 25. Merge 커밋

  • 다음 명령으로 브랜치 삭제 후 이슈의 상태를 처리 완료로 표시
$ git branch -d iss53

 

충돌의 기초

  • Merge 할 두 브랜치에서 같은 파일의 일부를 동시에 수정할 경우 Merge 실패
  • 새 커밋이 생기지 않고 충돌(Conflict) 메시지 출력
  • git status 명령으로 Merge 충돌이 일어난 파일 확인 가능
  •  
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")
  • 충돌이 일어난 파일은 unmerged 상태로 표시
  • 충돌이 일어난 파일 내부에 아래와 같이 충돌 내용 표시됨
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
  • ======= 위쪽의 내용: HEAD 버전(merge 명령을 실행할 때 작업하던 master 브랜치)의 내용
  • ======= 아래쪽의 내용: iss53 브랜치의 내용
  • 충돌을 해결하려면 위쪽이나 아래쪽 내용 중에서 고르거나 새로 작성하여 Merge
  • 아래는 아예 새로 작성하여 충돌을 해결하는 예제
<div id="footer">
please contact us at email.support@github.com
</div>
  • <<<<<<<, =======, >>>>>>> 제거됨
  • git add 명령으로 다시 git에 저장 시 충돌 완료

 

git mergetool로 그래픽 도구를 이용하여 해결 가능

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):
$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

충돌 해결 후 커밋 메시지는 아래와 같음

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

 


3.3 브랜치 관리

브랜치 관리

 

  • git branch : 현재 저장소의 브랜치 목록을 보여줌
  • git branch -v : 각 브랜치들의 마지막 커밋 메시지도 함께 보여줌
  • * 기호가 붙은 브랜치는 현재 checkout한 브랜치를 나타냄
  • 커밋 시 * 기호가 표시된 브랜치에 커밋되고 포인터 한 단계 나아감
$ git branch
  iss53
* master
  testing

 

$ git branch -v
  iss53   93b412c fix javascript issue
* master  7a98805 Merge branch 'iss53'
  testing 782fd34 add scott to the author list in the readmes

 

  • --merged와 --no-merged 옵션을 사용하면 merge된 상태를 확인 가능
  • git branch --merged : 현재 checkout한 브랜치와 merge된 브랜치 목록을 보여줌
  • git branch --no-merged : 현재 checkout한 브랜치와 merge되지 않은 브랜치 목록을 보여줌
  • git branch -d : 이미 merge된 브랜치를 삭제
  • git branch -D : merge되지 않은 브랜치를 강제로 삭제
$ git branch --merged
  iss53
* master

 

$ git branch --no-merged
  testing
 
 
# merge되지 않았기 때문에 삭제되지 않음
$ git branch -d testing
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.

 

힌트

  • 특정 브랜치를 기준으로 git branch --merged, --no-merged 옵션을 사용 가능
  • git branch --merged <브랜치 이름> : 특정 브랜치와 merge된 브랜치 목록 출력
  • git branch --no-merged <브랜치 이름> : 특정 브랜치와 merge되지 않은 브랜치 목록 출력
  • 예를 들어 master 브랜치에 아직 merge되지 않은 브랜치를 살펴보려면 git branch --no-merged master 입력

 

브랜치 이름 변경하기

주의

  • "마스터 브랜치 이름 변경하기" 절을 읽지 않은 상태에서 master/main/mainline과 같은 분기 이름 변경 금지
  • 공동작업자가 사용 중인 브랜치의 이름을 바꾸지 마라

 

  • git branch --move 명령을 사용하여 로컬 분기 이름 변경: $ git branch --move bad-branch-name corrected-branch-name
  • git push --set-upstream origin corrected-branch-name 명령을 사용하여 원격 저장소에 수정된 분기 이름 적용
  • git push origin --delete bad-branch-name 명령을 사용하여 원격 저장소에서 잘못된 분기 이름 삭제
$ git branch --move bad-branch-name corrected-branch-name
$ git push --set-upstream origin corrected-branch-name

$ git branch --all
* corrected-branch-name
  main
  remotes/origin/bad-branch-name
  remotes/origin/corrected-branch-name
  remotes/origin/main
  
$ git push origin --delete bad-branch-name

 

마스터 브랜치 이름 변경하기

주의 - master/main/mainline/default 분기 이름 변경 금지

  • 통합, 서비스, 도우미 유틸리티 및 빌드/릴리스 스크립트가 중단될 수 있음
  • 공동 작업자와 상의 필요
  • 코드와 스크립트에서 이전 분기 이름 참조 업데이트 필요
# master 브랜치를 main 브랜치로 이름 변경
$ git branch --move master main

# 원격에서 볼 수 있게 푸시
$ git push --set-upstream origin main

# 결과
$ git branch --all
* main
  remotes/origin/HEAD -> origin/master
  remotes/origin/main
  remotes/origin/master

# master 브랜치 삭제
$ git push origin --delete master

 

마스터 브랜치 삭제 전 추가 작업

  • 이 프로젝트에 의존하는 모든 프로젝트의 코드/설정 업데이트
  • 모든 테스트 실행 설정 파일 업데이트
  • 도큐먼테이션에서 이전 분기에 대한 레퍼런스 업데이트
  • 빌드/배포 스크립트 수정
  • 디폴트 브랜치/머지 규칙/브랜치 이름 등의 리포지토리 호스트의 설정을 리디렉션 
  • 이전 브랜치 대상 풀 요청의 클로즈 또는 머지

 

 


3.4 브랜치 워크플로

Long-Running 브랜치

  1. Git은 3-way Merge를 사용해서 장기간에 걸친 한 브랜치와 다른 브랜치를 여러 번 Merge 하는 것이 쉬움.
  2. 개발 과정에서 필요한 용도에 따라 브랜치를 만들어 정기적으로 Merge.
  3. 이런 접근법에 따라서 Git 개발자들이 선호하는 워크플로우: master브랜치에는 배포할 코드만 Merge 하며, develop or next브랜치로 개발하고 안정화 한다.
  4. 테스트 거쳐 안정적이면 master 브랜치에 Merge 하고, 토픽브랜치도 적용 가능.
  5. 이는 커밋 포인터의 생성/수정/분리/병합에 대한 것
  6. 개발 브랜치: 공격적으로 히스토리 생성
  7. 안정 브랜치: 이미 만든 히스토리 뒤따르며 나아감

 

그림 26. 안정적인 브랜치일수록 커밋 히스토리가 뒤쳐짐

 

실험실에서 충분히 테스트하고 실전에 배치하는 것으로 이해하면 쉬움

그림 27. 각 브랜치를 하나의 &ldquo;실험실&rdquo; 로 생각

 

  • 프로젝트 규모가 클 경우 proposed/pu(proposed updates)라는 브랜치에 안정 브랜치(next/master)에 적용 어려운 것 일단 merge
  • 충분한 안정화 이후 안정 브랜치로 merge
  • 이는 브랜치가 여러 개일 필요 없지만 유용

 

토픽 브랜치

  • 토픽 브랜치: 어떤 한 가지 주제나 작업을 위해 만든 짧은 수명의 브랜치
  • 각 작업 유지 후 merge 시점에 순서 관계 없이 merge 가능

 

그림 28. 토픽 브랜치가 많음

 

iss91v2 및 dumbidea를 merge한다 가정

 

그림 29.&nbsp;dumbidea&nbsp;와&nbsp;iss91v2&nbsp;브랜치를 Merge 하고 난 후의 모습

 

  • 분산 환경의 Git에서 여러 워크플로에 대해 살필 예정
  • 현재까지는 전부 로컬에서의 작업

 


3.5 리모트 브랜치

리모트 브랜치

  • 리모트 References는 리모트 저장소에 있는 포인터, 브랜치, 태그 등을 의미
  • git ls-remote 명령으로 리모트 Refs 조회 가능
  • git remote show 명령으로 리모트 브랜치와 정보 조회
  • 리모트 트래킹 브랜치는 리모트 브랜치를 추적하는 레퍼런스이자 브랜치
  • 임의로 움직일 수 없고, 리모트 서버에 연결될때마다 자동으로 갱신, 일종의 북마크

 

  • 리모트 트래킹 브랜치의 이름은 <remote>/<branch> 형식
  • 예를들어 origin/master로 리모트 저장소의 master브랜치를 확인
  • 다른 팀원과 함께 작업시 그 팀원이 서버로 push한 iss53 브랜치를 조회할때는 origin/iss53로 확인
  • git.ourcompany.com 서버에서 저장소를 clone 하면 Git은 자동으로 origin이라는 이름을 붙이고, master브랜치를 가리키는 포인터를 만들어 origin/master라고 부름
  • 그리고 Git은 로컬의 master브랜치가 origin/master를 가리키게 하고 이제 이 master브랜치에서 작업을 시작

 

노트- "origin"의 의미

  • "origin" 특별한 의미 없음
  • "master": git init 명령이 자동으로 만들어주는 이름
  • "origin": git clone 명령이 자동으로 만들어주는 리모트 이름
  • git clone -o 새_이름 : 새_이름/master라는 사용자가 정한 리모트 이름 생성

 

그림 30. Clone 이후 서버와 로컬의 master 브랜치

 

  • 로컬 저장소에서 작업 중 동시에 다른 팀원이 git.ourcompany.com 서버에 push 후 master 브랜치 업데이트
  • 팀원 간 히스토리가 서로 달라짐
  • origin/master 포인터는 그대로. 로컬에서 서버로부터 데이터를 주고받지 않기 때문
그림 31. 로컬과 서버의 커밋 히스토리는 독립적임

 

  1. git fetch origin : remote repository 정보를 동기화하기 위한 명령
  2. "origin" 서버의 주소 정보 ( git.ourcompany.com)를 찾음
  3. 로컬 저장소에 없는 새로운 정보 내려받음
  4. 내려받은 데이터를 로컬 저장소에 업데이트
  5. origin/master 포인터를 최신 커밋으로 이동
그림 32.&nbsp;git fetch&nbsp;명령은 리모트 브랜치 정보를 업데이트

 

  1. 리포 여러 개 운영하는 상황 이해할 수 있도록 개발용으로 사용할 Git 저장소 추가
  2. 저장소 주소는 git.team1.ourcompany.com
  3. git remote add 명령을 사용하여 현재 작업중인 프로젝트에 팀 저장소 추가
  4. 이름을 teamone으로 지정
  5. 긴 서버 주소 대신 사용할 수 있도록 지정
  6. 리모트 저장소를 여러개 운영하는 상황에서 유용함
그림 33. 서버를 리모트 저장소로 추가
 
  1. 서버 추가 후 git fetch teamone 명령 실행
  2. teamone 서버의 데이터 내려받음
  3. teamone 서버의 데이터는 origin 서버에도 있어 내려받지 않음
  4. 하지만, remote tracking branch teamone/master를 teamone 서버의 master 브랜치가 가리키는 커밋을 가리키게 함
  5. 즉, git fetch 명령으로 remote서버에 있는 데이터를 로컬저장소에 받아오며, 리모트 트래킹 브랜치를 이동시켜줌

그림 34.&nbsp;teamone/master&nbsp;의 리모트 트래킹 브랜치

 

Push 하기

  • Push 하려면 쓰기 권한이 있는 리모트 저장소에 전송해야 함
  • 로컬 저장소의 브랜치는 자동으로 리모트 저장소로 전송되지 않고, 명시적으로 Push 해야 정보 전송
  • 비공개 브랜치를 만들기 위해 리모트 저장소에 전송하지 않을 수 있음
  • 협업을 위해 토픽 브랜치만 전송 가능

 

  • 공유할 브랜치를 처음 Push 하는 것과 같은 방법으로 Push 하며 git push <remote> <branch> 명령을 사용
  • git push origin serverfix 라는 명령은 serverfix 라는 로컬 브랜치를 서버로 Push 하면서 remote의 serverfix 브랜치로 업데이트
  • git push origin serverfix:serverfix 라는 명령도 같은 의미로, "로컬의 serverfix 브랜치를 리모트 저장소의 serverfix 브랜치로 Push 하라"는 의미. 리모트 저장소의 브랜치 이름이 다를 때 각각의 이름을 지정해야 함
  • serverfix 대신 다른 이름 사용하려면 git push origin serverfix:awesomebranch 처럼 사용
$ 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

 

노트 - 암호를 매번 입력하지 않아도 된다.

  • Push 나 Pull을 할 때, HTTPS URL로 시작하는 리모트 저장소를 사용할 경우 인증을 위한 사용자이름과 암호를 입력해야 함
  • 매번 입력하지 않도록 “credential cache” 기능을 사용 가능
  • Git config --global credential.helper cache 명령을 사용하여 활성화
  • Credential 저장소를 참고하여 자세한 설명 확인 가능

 

  • Fetch 한 내용으로부터 서버의 serverfix 브랜치를 접근할 때 origin/serverfix 라는 이름으로 접근 가능
  • Fetch 명령으로 리모트 트래킹 브랜치를 내려받는 것은 로컬 저장소에 수정 가능한 브랜치가 생기는 것이 아니며, 수정할 수 없는 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

 

  • 리모트 트래킹 브랜치의 내용을 로컬 브랜치에 Merge 하려면 git merge origin/serverfix 명령 사용
  • 리모트 트래킹 브랜치에서 새로운 브랜치를 만들려면 git checkout -b serverfix origin/serverfix 명령 사용
  • 그렇게 하면 origin/serverfix 에서 시작하고 수정 가능한 serverfix 라는 로컬 브랜치 생성됨
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

 

브랜치 추적

  • 리모트 트래킹 브랜치를 로컬 브랜치로 Checkout 시 트래킹 브랜치 생성
  • upstream 브랜치 : 트래킹하는 대상 브랜치
  • 트래킹 브랜치는 리모트 브랜치와 직접 연결
  • git pull 시 리모트 브랜치와 자동 Merge

 

  • Clone 시 master 브랜치를 origin/master 브랜치의 트래킹 브랜치 생성
  • 리모트를 origin 이 아닌 다른 리모트로 하거나, 브랜치도 master 가 아닌 다른 브랜치 추적 가능
  • git checkout -b <branch> <remote>/<branch> 명령으로 트래킹 브랜치 생성
  • --track 옵션 사용 시 로컬 브랜치 이름 자동 생성. 자주 쓰여서 (a) 리모트가 딱 하나 있고 (b) 로컬에는 없으면 생략 가능
  • git checkout --track origin/serverfix : 트래킹 브랜치 생성 및 이동
$ 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 : 리모트와 같은 이름으로 트래킹 브랜치 생성 및 이동
$ 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 : 리모트와 다른 이름으로 트래킹 브랜치 생성 및 이동
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'

이제 sf 브랜치에서 Push 나 Pull 하면 자동으로 origin/serverfix 로 데이터를 보내거나 가져온다.

  • git branch -u origin/serverfix : 이미 로컬에 존재하는 브랜치를 리모트에서 추적 (-u, --set-upstream-to)
$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.

노트 - Upstream 별명

  • 추적 브랜치를 설정하면 @{upstream} 또는 @{u} 로 대체 사용 가능
  • master 브랜치가 origin/master 브랜치를 추적하는 경우 git merge origin/master와 git merge @{u} 명령 동일

 

  • git branch -vv 명령
    1. 추적 브랜치 설정 확인 가능
    2. 로컬 브랜치와 추적하는 리모트 브랜치 확인 가능
    3. 로컬 브랜치가 앞서가는지 뒤쳐지는지 확인 가능

 

$ 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
  1. iss53 브랜치는 origin/iss53 브랜치를 추적하고, 로컬에만 있는 커밋 2개가 있음
  2. master 브랜치는 origin/master 브랜치를 추적하고, 두 브랜치가 같은 상태
  3. serverfix 브랜치는 teamone/server-fix-good 브랜치를 추적하고, 앞서가는 커밋 3개, 뒤쳐지는 커밋 1개
  4. testing 브랜치는 추적하는 브랜치 없음



중요한 점

  • 실행 결과는 마지막으로 서버에서 데이터를 가져온(fetch) 시점을 바탕으로 계산
  • 단순히 이 명령만으로는 로컬에 저장된 서버의 캐시 데이터를 사용하며 서버의 최신 데이터를 반영하지는 않음
  • $ git fetch --all; git branch -vv :  먼저 서버로부터 최신 데이터를 받아온 후 추적 상황 확인
$ git fetch --all; git branch -vv

 

Pull 하기

  • git pull : 대부분 git fetch 명령을 실행하고 나서 자동으로 git merge 명령을 수행하는 것
  • git fetch : 서버에는 존재하지만, 로컬에는 아직 없는 데이터를 받아와서 저장. 이때, 워킹 디렉토리의 파일 내용은 유지
  • clone 이나 checkout 명령을 실행하여 추적 브랜치가 설정되면 git pull 명령은 서버로부터 데이터를 가져와서 현재 로컬 브랜치와 서버의 추적 브랜치를 Merge
  • 일반적으로 fetch 와 merge 명령을 명시적으로 사용하는 것이 pull 명령으로 한번에 두 작업을 하는 것보다 나음

 

리모트 브랜치 삭제

git push 명령에 --delete 옵션을 사용하여 리모트 브랜치를 삭제 가능

# serverfix 라는 리모트 브랜치를 삭제하려면 아래와 같이 실행한다.
$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
 - [deleted]         serverfix
  • 위 명령 실행 시 서버에서 브랜치(즉 커밋을 가리키는 포인터) 하나가 사라짐
  • 서버에서 가비지 컬렉터가 동작하지 않는 한 데이터는 사라지지 않음
  • 종종 의도치 않게 삭제한 경우에도 커밋한 데이터 살릴 수 있음

 


3.6 Rebase 하기

Rebase 하기

Git에서 한 브랜치에서 다른 브랜치로 합치는 방법 1. Merge 2. Rebase

Rebase가 무엇인지/어떻게 사용하는지/좋은 점/어떤 상황에서 사용/용하지 말아야 하는지 알아봄

 

Rebase 의 기초

 

그림 35. 두 개의 브랜치로 나누어진 커밋 히스토리

 

merge: 두 브랜치의 마지막 커밋 두 개(C3, C4)와 공통 조상(C2)을 사용하는 3-way Merge로 새로운 커밋 생성

그림 36. 나뉜 브랜치를 Merge 하기

 

Rebase: C3 에서 변경된 사항을 Patch로 만든 후 이를 C4 에 적용

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
  1. 두 브랜치가 나뉘기 전인 공통 커밋으로 이동
  2. 지금 Checkout 한 브랜치가 가리키는 커밋까지 diff를 차례로 만들어 어딘가에 임시로 저장
  3. Rebase 할 브랜치(역주 - experiment)가 합칠 브랜치(역주 - master)가 가리키는 커밋을 가리키게 하고 저장한 변경사항을 차례대로 적용

그림 37. `C4`의 변경사항을 `C3`에 적용하는 Rebase 과정

master 브랜치를 Fast-forward

$ git checkout master
$ git merge experiment

그림 38. master 브랜치를 Fast-forward시키기

  • C4 커밋의 내용은 C5 커밋의 내용과 같을 것
  • Merge와 Rebase는 합치는 관점에서는 서로 다를 게 없으나 Rebase가 깨끗한 히스토리를 만듦
  • Rebase는 리모트 브랜치에 커밋을 깔끔하게 적용하고 싶을 때 사용, 히스토리는 선형적이며 병렬 작업을 수행하여도 차례대로 수행된 것처럼 보임
  • Rebase는 메인 프로젝트에 Patch를 보낼 준비를 할때 사용, 프로젝트 관리자가 통합 작업을 할 필요 없이 master브랜치를 fast-forward 시켜서 합칠 수 있음
  • 최종 결과물은 Merge, Rebase 모두 같으며 커밋 히스토리만 다름.

 

Rebase 활용

 

  • Rebase의 다른 용도 예시
  • 다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치 같은 히스토리가 있다고 가정
    1. server 브랜치를 만들어서 서버 기능을 추가
    2. server 브랜치에서 client 브랜치를 만들어 클라이언트 기능을 추가
    3. 마지막으로 server 브랜치로 돌아가서 몇 가지 기능을 더 추가

그림 39. 다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치

 

  1. 상황 : server 브랜치는 그대로 두고, client 브랜치만 master로 합치려는 상황
  2. client 브랜치에는 server 와는 관련없는 C8,C9 커밋
  3. --onto 옵션 사용하여 git rebase --onto master server client 명령 실행
  4. 이 명령은 master브랜치부터 server브랜치와 client브랜치의 공통 조상까지의 커밋을 client브랜치에서 없애고, client브랜치에서만 변경된 패치를 만들어 master브랜치에서 client브랜치를 기반으로 새로 만들어 적용
$ git rebase --onto master server client

그림 40. 다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치를 Rebase 하기

  1. master 브랜치로 이동하여 git merge client 명령 실행하여 Fast-forward 시킴
$ git checkout master
$ git merge client

그림 41. master 브랜치를 client 브랜치 위치로 진행 시키기

서버 일이 다 끝난 후

  1. git rebase <basebranch> <topicbranch> 명령으로 Checkout 하지 않고 바로 topic 브랜치를 base 브랜치로 Rebase
  2. 예시 : git rebase master server
  3. 이 명령은 토픽(server) 브랜치를 베이스(master) 브랜치에 Rebase 하여, server 브랜치의 수정 사항을 master 브랜치에 적용

그림 42. master 브랜치에 server 브랜치의 수정 사항을 적용

 

  1. master 브랜치를 Fast-forward
  2. 삭제 가능 : client 나 server 브랜치는 삭제해도 됨, 커밋 히스토리는 여전히 남아 있음
$ git checkout master
$ git merge server
$ git branch -d client
$ git branch -d server

그림 43. 최종 커밋 히스토리

 

Rebase 의 위험성

이미 공개 저장소에 Push 한 커밋을 Rebase 하지 마라

  1. Rebase는 기존 커밋을 새로운 커밋으로 바꾸므로, 중앙 저장소에서 Clone한 후 일부 수정한 커밋 히스토리는 변경됨
  2. 상황:  새 커밋을 서버에 Push 하고 동료 중 누군가가 그 커밋을 Pull 해서 작업 중 기존에 Push한 커밋을 git rebase로 push한 경우
  3. 동료가 다시 push 했을 때 동료는 다시 merge 해야 함
  4. 동료가 다시 merge한 내용을 pull 하면 코드는 엉망이 됨
  5. Rebase를 할 경우, 다른 사람들이 이미 Pull 한 커밋이 엉망이 될 수 있음

그림 44. 저장소를 Clone 하고 일부 수정함

 

  • 이제 팀원 중 누군가 커밋, Merge 하고 나서 서버에 Push
  • 이 리모트 브랜치를 Fetch, Merge 하면 히스토리는 아래와 같이 된다.

그림 45. Fetch 한 후 Merge 함

 

  • 그런데 Push 했던 팀원은 Merge 한 일을 되돌리고 다시 Rebase
  • 서버의 히스토리를 새로 덮어씌우려면 git push --force 명령을 사용해야 함
  • 이후에 저장소에서 Fetch 하고 나면 아래 그림과 같은 상태

그림 46. 한 팀원이 다른 팀원이 의존하는 커밋을 없애고 Rebase 한 커밋을 다시 Push 함

 

git pull 로 서버의 내용을 가져와서 Merge 하면 같은 내용의 수정사항을 포함한 Merge 커밋이 아래와 같이 생성

그림 47. 같은 Merge를 다시 한다

  • git log로 히스토리를 확인할 경우 같은 저자, 날짜, 메시지의 커밋이 두 개 있음(C4, C6)
  • 이 히스토리를 서버에 Push 할 경우, 다른 사람들도 혼란스러워할 수 있음
  • 애초에 서버로 데이터를 보내기 전에 Rebase로 커밋을 정리하면 히스토리를 이해하는데 도움이 될 것

 

Rebase 한 것을 다시 Rebase 하기

  1. 팀원이 강제로 내가 한 일을 덮어썼을 때, 내가 했던 일과 덮어쓴 내용을 알아내는 것이 필요
  2. Git은 커밋 SHA 체크섬 외에도 커밋에 patch 할 내용으로 patch-id라는 값을 구함
  3. patch-id는 커밋의 내용에 대한 SHA-1 체크섬
  4. 이 patch-id를 이용해서, 덮어쓴 커밋을 기준으로 Rebase 할 때, Git은 원래 누가 작성한 코드인지 잘 찾아내고 patch를 원래대로 잘 적용할 수 있음
 
그림 46. 한 팀원이 다른 팀원이 의존하는 커밋을 없애고 Rebase 한 커밋을 다시 Push 함

위 상황에서 Merge 하는 대신 git rebase teamone/master 명령을 실행하면 Git은 아래와 같은 작업을 한다.

  • 현재 브랜치에만 포함된 커밋을 결정한다. (C2, C3, C4, C6, C7)
  • Merge 커밋이 아닌 것을 결정한다. (C2, C3, C4)
  • 이 중 merge할 브랜치에 덮어쓰이지 않은 커밋을 결정한다. (C2, C3. C4는 C4’와 동일한 Patch다)
  • 결정한 커밋을 teamone/master 브랜치에 적용한다.

결과를 확인해보면 같은 Merge를 다시 한다 같은 결과 대신 제대로 정리된 강제로 덮어쓴 브랜치에 Rebase 하기 같은 결과를 얻을 수 있다.

그림 48. 강제로 덮어쓴 브랜치에 Rebase 하기

  • 위 Rebase 가 정상적으로 동작하려면 C4, C4'커밋 내용이 완전히 같아야 함
  • git pull --rebase 명령으로 Rebase를 실행할 수 있음
  • 위 명령 대신 git fetch 와 git rebase teamone/master 명령을 순서대로 실행해도 됨
  • git config --global pull.rebase true 명령으로 git pull 명령 실행시 기본적으로 --rebase 옵션 적용 가능
  • 'push 전 rebase'/'공개 없이 혼자 rebase'는 괜찮음
  • 커밋을 이미 공개하여 사람들이 사용하는 경우에는 Rebase 시 문제 초래
  • 이슈를 미리 방지하기 위해 git pull --rebase 명령 사용과 설정을 공유하는 것이 좋음
 

Rebase vs. Merge

  • 히스토리는 작업한 내용의 기록으로도 볼 수 있음
  • 커밋 히스토리를 변경하면 역사를 부정하는 꼴이 됨
  • 역사는 후세를 위해 기록하고 보존해야 함

 

  • 히스토리를 프로젝트의 진행에 대한 이야기로도 볼 수 있음
  • 소프트웨어를 주의 깊게 편집하는 방법에 메뉴얼이나 세세한 작업내용을 초벌부터 공개하고 싶지 않을 수 있음
  • 나중에 다른 사람에게 들려주기 좋도록 Rebase나 filter-branch 같은 도구로 프로젝트의 진행 이야기를 다듬을 수 있음

 

  1. Merge는 두 브랜치의 커밋을 합치는 기능
  2. Rebase는 브랜치의 커밋을 재배치하는 기능
  3. Merge는 리모트에 Push 한 커밋에 대해서도 사용할 수 있음
  4. Rebase는 리모트에 Push 한 커밋에 대해서는 사용하면 안 된다.
  5. 어떤 기능을 사용할지는 각자의 상황과 팀 문화, 업무 방식에 따라 결정되며, 각자의 판단으로 사용해야 한다.
  6. 일반적으로 로컬 브랜치에서는 Rebase를 사용하여 히스토리를 정리할 수 있음
  7. 리모트 등 어딘가에 Push로 내보낸 커밋에 대해서는 절대 Rebase 하면 안 됨

3.7 요약

요약

  • Git으로 브랜치를 만들고 Merge 의 기본적인 사용법을 다루었음
  • 브랜치를 만들고 옮겨다니고 Merge 하는 것
  • 브랜치를 Rebase 해서 Push 해서 공유하는 것
  • 다음 장에서는 Git 저장소 서버를 직접 운영하는 방법을 설명
반응형

+ Recent posts