[GitHub] ProGit 요약 - 3. Git 브랜치
챕터 개요
- 브랜치란 코드를 복사해서 원래 코드와는 상관없이 독립적으로 개발할 수 있는 것
- 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 커밋은 여러 개 이전 커밋 포인터가 있음
- 파일을 Staging Area에 저장하면 Git 저장소에 파일 저장(Blob, Binary large object), Staging Area에 파일 체크섬 저장
- 커밋하면 루트 디렉토리의 트리 개체 체크섬과 함께 저장소에 저장, 커밋 개체 생성, 커밋 개체에 메타데이터와 루트 트리 개체 포인터 저장
- 커밋을 마치면 Git 저장소에는 각 파일의 Blob n개, 트리 개체 1개, 커밋 개체 1개가 생김
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'
다시 파일을 수정하고 커밋하면 이전 커밋이 무엇인지도 저장한다.
- Git의 브랜치는 커밋 사이를 가볍게 이동할 수 있는 포인터 같은 것
- Git은 기본적으로 main 브랜치 생성
- main 브랜치는 처음 커밋할 때 생성된 커밋 가리킴. 이후 커밋 만들면 자동으로 가장 마지막 커밋 가리킴
- "main" 브랜치는 Git 버전 관리 시스템에서 특별한 의미 없음. git init 명령으로 초기화할 때 자동으로 생성되는 것이기 때문
새 브랜치 생성하기
- git branch 명령으로 testing 브랜치를 생성
- HEAD 포인터는 지금 작업하고 있는 로컬 브랜치를 가리킴
- git branch 명령은 새로운 브랜치 생성만 하고 커밋을 옮기지 않음
- git log 명령에 --decorate 옵션 사용시 브랜치가 어떤 커밋을 가리키는지 확인 가능
$ git branch testing
$ 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
- 새로운 커밋 생성
$ vim test.rb
$ git commit -a -m 'made a change'
- 새로 커밋해서 testing 브랜치는 앞으로 이동됨
- master 브랜치는 여전히 이전 커밋 가리킴
- git checkout master 명령으로 master 브랜치로 이동
$ git checkout master
위 명령이 실행한 일
- 이동 시 HEAD와 워킹 디렉토리의 파일이 이전 시점으로 되돌려짐
- 이후 커밋은 새로운 브랜치와 별개로 진행됨
노트 - Git 브랜치 이동 시 주의점
- 브랜치 이동 시 워킹 디렉토리의 파일이 변경됨
- 파일 변경으로 인해 브랜치 이동이 어려울 경우 Git은 이동 명령을 수행하지 않음
- 파일 수정 후 다시 커밋
$ vim test.rb
$ git commit -a -m 'made other changes'
- 프로젝트 히스토리는 분리되어 진행됨
- 두 작업 내용은 서로 독립적으로 각 브랜치에 존재
- git log 명령으로 확인 가능
- git log --oneline --decorate --graph --all 실행 시 히스토리 출력
$ 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는 보통 이런 식으로 진행한다.
- 웹사이트가 있고 뭔가 작업을 진행하고 있다.
- 새로운 이슈를 처리할 새 Branch를 하나 생성한다.
- 새로 만든 Branch에서 작업을 진행한다.
이때 중요한 문제가 생겨서 그것을 해결하는 Hotfix를 먼저 만들어야 한다. 그러면 아래와 같이 할 수 있다.
- 새로운 이슈를 처리하기 이전의 운영(Production) 브랜치로 이동한다.
- Hotfix 브랜치를 새로 하나 생성한다.
- 수정한 Hotfix 테스트를 마치고 운영 브랜치로 Merge 한다.
- 다시 작업하던 브랜치로 옮겨가서 하던 일 진행한다.
브랜치의 기초
- 현재 작업하는 프로젝트에서 이전 master 브랜치에 커밋 몇 번 했다고 가정
- 이슈 관리 시스템에 등록된 53번 이슈 처리한다고 가정
- 해당 이슈에 집중할 수 있는 브랜치 생성 후 처리
- git checkout -b 옵션 추가: 브랜치 생성과 동시에 checkout 진행
$ git checkout -b iss53
Switched to a new branch "iss53"
# 아래 두 명령을 줄여놓은 것
$ git branch iss53
$ git checkout iss53
$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
- 만드는 사이트에 문제가 생겨서 즉시 고쳐야 하는 상황 가정 -> 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(+)
- 운영 환경에 적용 전 테스트 필요
- 테스트 후 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 브랜치에 포함되었고 운영 환경에 적용할 수 있는 상태가 되었다고 가정
- 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(+)
- 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를 수행
- 3-way Merge 의 결과를 새로운 커밋으로 생성 후 해당 브랜치가 새로운 커밋을 가리키도록 이동
- 이 커밋은 부모가 여러 개인 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 브랜치
- Git은 3-way Merge를 사용해서 장기간에 걸친 한 브랜치와 다른 브랜치를 여러 번 Merge 하는 것이 쉬움.
- 개발 과정에서 필요한 용도에 따라 브랜치를 만들어 정기적으로 Merge.
- 이런 접근법에 따라서 Git 개발자들이 선호하는 워크플로우: master브랜치에는 배포할 코드만 Merge 하며, develop or next브랜치로 개발하고 안정화 한다.
- 테스트 거쳐 안정적이면 master 브랜치에 Merge 하고, 토픽브랜치도 적용 가능.
- 이는 커밋 포인터의 생성/수정/분리/병합에 대한 것
- 개발 브랜치: 공격적으로 히스토리 생성
- 안정 브랜치: 이미 만든 히스토리 뒤따르며 나아감
실험실에서 충분히 테스트하고 실전에 배치하는 것으로 이해하면 쉬움
- 프로젝트 규모가 클 경우 proposed/pu(proposed updates)라는 브랜치에 안정 브랜치(next/master)에 적용 어려운 것 일단 merge
- 충분한 안정화 이후 안정 브랜치로 merge
- 이는 브랜치가 여러 개일 필요 없지만 유용
토픽 브랜치
- 토픽 브랜치: 어떤 한 가지 주제나 작업을 위해 만든 짧은 수명의 브랜치
- 각 작업 유지 후 merge 시점에 순서 관계 없이 merge 가능
iss91v2 및 dumbidea를 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라는 사용자가 정한 리모트 이름 생성
- 로컬 저장소에서 작업 중 동시에 다른 팀원이 git.ourcompany.com 서버에 push 후 master 브랜치 업데이트
- 팀원 간 히스토리가 서로 달라짐
- origin/master 포인터는 그대로. 로컬에서 서버로부터 데이터를 주고받지 않기 때문
- git fetch origin : remote repository 정보를 동기화하기 위한 명령
- "origin" 서버의 주소 정보 ( git.ourcompany.com)를 찾음
- 로컬 저장소에 없는 새로운 정보 내려받음
- 내려받은 데이터를 로컬 저장소에 업데이트
- origin/master 포인터를 최신 커밋으로 이동
- 리포 여러 개 운영하는 상황 이해할 수 있도록 개발용으로 사용할 Git 저장소 추가
- 저장소 주소는 git.team1.ourcompany.com
- git remote add 명령을 사용하여 현재 작업중인 프로젝트에 팀 저장소 추가
- 이름을 teamone으로 지정
- 긴 서버 주소 대신 사용할 수 있도록 지정
- 리모트 저장소를 여러개 운영하는 상황에서 유용함
- 서버 추가 후 git fetch teamone 명령 실행
- teamone 서버의 데이터 내려받음
- teamone 서버의 데이터는 origin 서버에도 있어 내려받지 않음
- 하지만, remote tracking branch teamone/master를 teamone 서버의 master 브랜치가 가리키는 커밋을 가리키게 함
- 즉, git fetch 명령으로 remote서버에 있는 데이터를 로컬저장소에 받아오며, 리모트 트래킹 브랜치를 이동시켜줌
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 명령
- 추적 브랜치 설정 확인 가능
- 로컬 브랜치와 추적하는 리모트 브랜치 확인 가능
- 로컬 브랜치가 앞서가는지 뒤쳐지는지 확인 가능
$ 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
- iss53 브랜치는 origin/iss53 브랜치를 추적하고, 로컬에만 있는 커밋 2개가 있음
- master 브랜치는 origin/master 브랜치를 추적하고, 두 브랜치가 같은 상태
- serverfix 브랜치는 teamone/server-fix-good 브랜치를 추적하고, 앞서가는 커밋 3개, 뒤쳐지는 커밋 1개
- 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 의 기초
merge: 두 브랜치의 마지막 커밋 두 개(C3, C4)와 공통 조상(C2)을 사용하는 3-way 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
- 두 브랜치가 나뉘기 전인 공통 커밋으로 이동
- 지금 Checkout 한 브랜치가 가리키는 커밋까지 diff를 차례로 만들어 어딘가에 임시로 저장
- Rebase 할 브랜치(역주 - experiment)가 합칠 브랜치(역주 - master)가 가리키는 커밋을 가리키게 하고 저장한 변경사항을 차례대로 적용
master 브랜치를 Fast-forward
$ git checkout master
$ git merge experiment
- C4 커밋의 내용은 C5 커밋의 내용과 같을 것
- Merge와 Rebase는 합치는 관점에서는 서로 다를 게 없으나 Rebase가 깨끗한 히스토리를 만듦
- Rebase는 리모트 브랜치에 커밋을 깔끔하게 적용하고 싶을 때 사용, 히스토리는 선형적이며 병렬 작업을 수행하여도 차례대로 수행된 것처럼 보임
- Rebase는 메인 프로젝트에 Patch를 보낼 준비를 할때 사용, 프로젝트 관리자가 통합 작업을 할 필요 없이 master브랜치를 fast-forward 시켜서 합칠 수 있음
- 최종 결과물은 Merge, Rebase 모두 같으며 커밋 히스토리만 다름.
Rebase 활용
- Rebase의 다른 용도 예시
- 다른 토픽 브랜치에서 갈라져 나온 토픽 브랜치 같은 히스토리가 있다고 가정
- server 브랜치를 만들어서 서버 기능을 추가
- server 브랜치에서 client 브랜치를 만들어 클라이언트 기능을 추가
- 마지막으로 server 브랜치로 돌아가서 몇 가지 기능을 더 추가
- 상황 : server 브랜치는 그대로 두고, client 브랜치만 master로 합치려는 상황
- client 브랜치에는 server 와는 관련없는 C8,C9 커밋
- --onto 옵션 사용하여 git rebase --onto master server client 명령 실행
- 이 명령은 master브랜치부터 server브랜치와 client브랜치의 공통 조상까지의 커밋을 client브랜치에서 없애고, client브랜치에서만 변경된 패치를 만들어 master브랜치에서 client브랜치를 기반으로 새로 만들어 적용
$ git rebase --onto master server client
- master 브랜치로 이동하여 git merge client 명령 실행하여 Fast-forward 시킴
$ git checkout master
$ git merge client
서버 일이 다 끝난 후
- git rebase <basebranch> <topicbranch> 명령으로 Checkout 하지 않고 바로 topic 브랜치를 base 브랜치로 Rebase
- 예시 : git rebase master server
- 이 명령은 토픽(server) 브랜치를 베이스(master) 브랜치에 Rebase 하여, server 브랜치의 수정 사항을 master 브랜치에 적용
- master 브랜치를 Fast-forward
- 삭제 가능 : client 나 server 브랜치는 삭제해도 됨, 커밋 히스토리는 여전히 남아 있음
$ git checkout master
$ git merge server
$ git branch -d client
$ git branch -d server
Rebase 의 위험성
이미 공개 저장소에 Push 한 커밋을 Rebase 하지 마라
- Rebase는 기존 커밋을 새로운 커밋으로 바꾸므로, 중앙 저장소에서 Clone한 후 일부 수정한 커밋 히스토리는 변경됨
- 상황: 새 커밋을 서버에 Push 하고 동료 중 누군가가 그 커밋을 Pull 해서 작업 중 기존에 Push한 커밋을 git rebase로 push한 경우
- 동료가 다시 push 했을 때 동료는 다시 merge 해야 함
- 동료가 다시 merge한 내용을 pull 하면 코드는 엉망이 됨
- Rebase를 할 경우, 다른 사람들이 이미 Pull 한 커밋이 엉망이 될 수 있음
- 이제 팀원 중 누군가 커밋, Merge 하고 나서 서버에 Push
- 이 리모트 브랜치를 Fetch, Merge 하면 히스토리는 아래와 같이 된다.
- 그런데 Push 했던 팀원은 Merge 한 일을 되돌리고 다시 Rebase
- 서버의 히스토리를 새로 덮어씌우려면 git push --force 명령을 사용해야 함
- 이후에 저장소에서 Fetch 하고 나면 아래 그림과 같은 상태
git pull 로 서버의 내용을 가져와서 Merge 하면 같은 내용의 수정사항을 포함한 Merge 커밋이 아래와 같이 생성
- git log로 히스토리를 확인할 경우 같은 저자, 날짜, 메시지의 커밋이 두 개 있음(C4, C6)
- 이 히스토리를 서버에 Push 할 경우, 다른 사람들도 혼란스러워할 수 있음
- 애초에 서버로 데이터를 보내기 전에 Rebase로 커밋을 정리하면 히스토리를 이해하는데 도움이 될 것
Rebase 한 것을 다시 Rebase 하기
- 팀원이 강제로 내가 한 일을 덮어썼을 때, 내가 했던 일과 덮어쓴 내용을 알아내는 것이 필요
- Git은 커밋 SHA 체크섬 외에도 커밋에 patch 할 내용으로 patch-id라는 값을 구함
- patch-id는 커밋의 내용에 대한 SHA-1 체크섬
- 이 patch-id를 이용해서, 덮어쓴 커밋을 기준으로 Rebase 할 때, Git은 원래 누가 작성한 코드인지 잘 찾아내고 patch를 원래대로 잘 적용할 수 있음
위 상황에서 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 하기 같은 결과를 얻을 수 있다.
- 위 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 같은 도구로 프로젝트의 진행 이야기를 다듬을 수 있음
- Merge는 두 브랜치의 커밋을 합치는 기능
- Rebase는 브랜치의 커밋을 재배치하는 기능
- Merge는 리모트에 Push 한 커밋에 대해서도 사용할 수 있음
- Rebase는 리모트에 Push 한 커밋에 대해서는 사용하면 안 된다.
- 어떤 기능을 사용할지는 각자의 상황과 팀 문화, 업무 방식에 따라 결정되며, 각자의 판단으로 사용해야 한다.
- 일반적으로 로컬 브랜치에서는 Rebase를 사용하여 히스토리를 정리할 수 있음
- 리모트 등 어딘가에 Push로 내보낸 커밋에 대해서는 절대 Rebase 하면 안 됨
3.7 요약
요약
- Git으로 브랜치를 만들고 Merge 의 기본적인 사용법을 다루었음
- 브랜치를 만들고 옮겨다니고 Merge 하는 것
- 브랜치를 Rebase 해서 Push 해서 공유하는 것
- 다음 장에서는 Git 저장소 서버를 직접 운영하는 방법을 설명