Git 다 써보기, (3) Branch 브랜치
브런치 먹고 싶은 지금 브랜치~,,,?
브랜치야 말로 Git의 최고 장점이자, Git만의 차이를 만들어내는 기능이다.
도대체 브랜치가 뭐길래 이런평을 받는것일까?.
0. 들어 가기전에
이 포스팅은 내가 모르는 git 을 알고자 git 공식 doc를 적어보고 추가한 기록이다. https://git-scm.com/book/ko/v2/
1. 브랜치란?
Git이 브랜치를 다루는 과정을 이해하려면 우선 Git이 데이터를 어떻게 저장하는지 알아야 한다.
Git은 데이터를 Changed Set이나 변경사항 Diff로 기록 하지 않고 일련의 스냅샷으로 기록한다
커밋하면 Git은 현 Staging Area에 있는 데이터의 스냅샷에 대한 포인터, 저자나 커밋 메세지 같은 메타데이터 이전 커밋에 대한 포인터 등을 포함하는 커밋 개체를 저장한다.
이전 커밋 포인터가 있어서 현재 커밋이 무엇을 기준으로 바뀌었는지를 알 수 있따.
최초 커밋을 제외한 나머지 커밋은 이전 커밋 포인터가 적어도 하나씩 있고 브랜치를 합친 Merge 같은 경우에는 이전 커밋 포인터가 여러개 있다.
이전에 커밋한 파일과 디렉토리는 Staging Area에 저장되고 커밋이 되어있다.
파일을 Stage 하면 Git 저장소에 파일을 저장하고 (Blob라고 한다.) Staging Area에 해당 파일의 체크섬을 저장한다. (SHA-1)을 사용
$ git add .
$ git commit -m 'The initial commit of my project'
git commit 을 하면 먼저 루트 디렉토리와 각 하위 디렉토리의 트리 개체를 체크섬과 함께 저장소에 저장한다.그 다음 커밋 개체를 만들고 메타 데이터와 루트 디렉토리 트리 개체를 가리키는 포인터 정보를 커밋 개체에 넣어서 저장한다. 그래서 필요하면 언제든 스냅샷을 다시 불러올수가 있다.
그러면 5개의 객체가 생성이 되는데
- 각파일의 Blob 세개
- 파일과 디렉토리 구조가 들어있는 트리 객체 하나
- 메타 데이터와 루트 트리를 가리키는 포인터가 담긴 커밋 객체 하나 가 생성된다.
Git의 브랜치는 커밋 사이를 가볍게 이동할 수 있는 포인터 같은 것이다.
기본적으로 Git은 master브랜치를 만드는데. 처음 커밋을 한다면 이 master 브랜치가 생성된 커밋을 가르킨다. 이후 커밋을 만들면 master 브랜치는 자동으로 가장 마지막 커밋을 가르킨다.
새 브랜치를 생성하는 git branch 명령어
git branch 명령어로 새로운 브랜치를 만들어 보자
git branch testing
새로 만든 브랜치도 지금 작업하고 있느던 마지막 커밋을 바라본다.
이렇게 두가지 브랜치가 나뉘면 지금 작업중인 브랜치가 무엇인지 Git는 어떻게 파악을 할까?. 다른 버전 관리 시스템과는 달리 Git은 HEAD 라는 특수한 포인터가 있다. 이 포인터는 지금 작업하고 있는 로컬 브랜치를 가리키고있다.. git brach 명령은 브랜치를 만들기만 하고 브랜치를 옮기지는 않는다.
현재 작업중인 브랜치 가르키는 HEAD
git log 명령어에 –decorate 옵션을 사용하면 쉽게 브랜치가 어떤 커밋을 가르키는지 확인이 가능하다.
$ git log --oneline --decorate
464d36b (HEAD -> master, testing) The initial commit of my project
b07a9b4 체인지
fdcfc36 ammend 전 commit
664daa9 commit
4603af6 git 첫 커밋
master 와 testing 이라는 브랜치가 464d36b 커밋 옆에 위치하여 브랜치가 가리키는 커밋을 확인할 수 있다.
브랜치 이동하기 git checkout
git checkout 명령으로 다른 브랜치로 이동이 가능하다. testing 브랜치로 이동하자.
$ git checkout testing
$ git log --oneline --decorate
464d36b (HEAD -> testing, master) The initial commit of my project
b07a9b4 체인지
fdcfc36 ammend 전 commit
664daa9 commit
4603af6 git 첫 커밋
이제 HEAD 가 testing 브랜치를 가리킨다,
그다음 이제 파일을 수정하고 커밋을 새로 해보자
git log --oneline --decorate
7a079c4 (HEAD -> testing) aa
464d36b (master) The initial commit of my project
b07a9b4 체인지
fdcfc36 ammend 전 commit
664daa9 commit
4603af6 git 첫 커밋
그리고 파일을 하나 생성해보자 branch_testing.txt 파일을 생성하였다.
$ dir
2021-10-09 오후 11:00 35 branch-test.txt
2021-10-09 오전 11:10 133 README.md
2021-10-09 오후 10:40 101 test.txt
이렇게 testing 브랜치에 commit 작업들을 하고 다시 master branch로 돌아가보자
$ git checkout master
$ git dir
2021-10-09 오전 11:10 133 README.md
2021-10-09 오후 11:02 98 test.txt
당연히 master working dir 로 돌아왔고 master 브랜치가 HEAD가 가리키게 되돌려졌다.
$ git log --oneline --decorate
464d36b (HEAD -> master) The initial commit of my project
b07a9b4 체인지
fdcfc36 ammend 전 commit
664daa9 commit
4603af6 git 첫 커밋
NOTE
브랜치를 이동하면 워킹 디렉토리의 파일이 변경된다. 이전에 작업했던 브랜치로 이동하면 워킹 디렉토리의 파일은 그 배린치에서 가장 마지막으로 했던 작업내용으로 변경된다.
파일 변경시 문제가 있어 브랜치를 이동시키는게 불가능한 경우엔 Git은 브랜치 이동 명령을 수행하지 않는다
2. 브랜치와 Merge의 기초
아래와 같은 상황이 있다고 생각해보자
- 웹사이트가 있고 뭔가 작업을 진행하고 있다.
- 새로운 이슈를 처리할 새 Branch를 하나 생성한다.
- 새로 만든 Branch에서 작업을 진행한다.
이때 중요한 문제가 생겨 그것을 해결하는 Hotfix를 먼저 만들어야 한다. 그러면 아래와 같이 할 수 있다.
- 새로운 이슈르 처리하기 이전의 운영
- Hotfix 브랜치를 새로 하나 생성한다.
- 수정한 Hotfix 테스트를 마치고 운영 브랜치로 Merge 한다.
- 다시 작업하던 브랜치로 옮겨가서 하던 일 진행한다.
브랜치의 기초
이전에 작업하던 프로젝트에서 이전에 master 브랜치에 커미승 했다고 가정한다.
이슈 관련 시스템에 등록된 53번 이슈를 처리 한다고 하면 이 이슈에 집중할 수 있는 브랜치를 새로 만들어 작업을 할것이다. 브랜치를 만들어 Checkout 까지 한 번에 하려면 git checkout 명령에 -b 라는 옵션을 추가한다.
$ git checkout -b iss53
위 명령어를 이용하면 아래와 같은 명령어를 출여서 사용하는것이 가능하다.
$ git branch iss53
$ git checkout iss53
브랜치 포인터를 새로 만들어 보자 iss53 브랜치를 Checkout 했기 때문에 (즉, HEAD는 ISS53 브랜치를 가리킨다.) 뭔가 일을 하고 커밋하면 브랜치가 앞으로 나아간다.
이러한 상황에서 운영 환경에서 문제가 발생해 즉시 코드를 고쳐야 하는 일이 발생했다고 하면 버그를 해결한 Hotfix에 iss53이 섞이는 것을 방지하기 위해 iss53과 관련된 코드를 어딘가에 저장해 두고 원래 운영환경이였던 소스로 복구하여 소스를 복구해야 한다. Git을 사용하면 이런 부분이 굉장히 편해진다.
그렇지만 브랜치를 이동하기 위해선 해야할 일이 있다. 아직 커밋 하지 않은 파일이 checkout 할 브랜치와 충돌 나면 브랜치를 변경할 수 없다 이부분은 나중에 알아보자
브랜치를 변경할 떄는 워킹 디렉토리를 정리하는 것이 중요하다. 이런 문제를 다루는 방법은 Stash나 Commit amend를 다룰때 알아본다. 내 git 포스팅 1에서 하긴 했는데 좀더 다루려함
지금은 작업하던 모든것을 커밋하고 master 브랜치로 옮긴다.
$ git checkout master
Switched to branch 'master'
이렇게 하면 Wordking directory는 53번 이슈를 시작하기 이전 모습으로 되돌려지기 때문에 새로운 문제에 집중할 수 있는 환경이 만들어 졌다. Git은 자동으로 워킹 디렉토리에 파일들을 추가하고, 지우고 수정해 Checkout 한 브랜치의 마지막 스냅샷으로 되돌려 놓았다.
이젠 해결해야 할 핫픽스가 생겼을 때를 살펴보자. hotfix
라는 브랜치를 만들고 새로운 이슈를 해결할 때까지 사용한다.
$ git checkout -b hotfix
// 문제였던 코드를 수정한다. 내 예제에선 test.txt 를 그대로 수정해보도록 하겠다.
$ vim test.txt
$ git status
On branch hotfix
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m "fixed the broken email address"
운영 환경에 적용하려면 문제를 제대로 고쳤는지 테스트하고 최정적으로 운영환경에 배포하기위해 hotfix 브랜치를 master 브랜치에 합쳐야 한다.
git merge 명령으를 사용하여 master branch와 hotfix 브랜치를 합쳐보자.
$ git checkout master
Switched to branch 'master'
$ git merge hotfix
Updating 464d36b..bc40cec
Fast-forward
test.txt | 2 ++
1 file changed, 2 insertions(+)
Merge 메세지에 fast-foward 라는게 보인다 hotfix 브랜치가 가리키는 커밋이 master에서 기반한 브랜치이기 떄문에 브랜치 포인터는 merge 과정없이 그저 최신 커밋 으로 이동한다. 이런 merge 방식을 fast foward 라고 부른다. 다시 말해 A 브랜치에서 다른 B브랜치를 Merge 할 때 B브랜치가 A브랜치 이후 커밋을 가리키고 있으면 그저 A 브랜치가 B브랜치와 동일한 커밋을 가리키도록 이동시킨다.
이제 hotfix 는 master 브랜치에 포함 되었고 운영환경에 적용할 수 있는 상태가 되었다.
Merge후 hotfix 같은 것을 가리키는 master 브랜치 급한 문제를 해결하고 master 브랜치에 적용하고 나면 다시 일하던 브랜치로 돌아간다. 이제 더이상 필요없는 hotfix 브랜치는 삭제하자. 브랜치 삭제 명령어는 -d 옵션으로 삭제한다.
$ git branch -d hotfix
Deleted branch hotfix (was bc40cec).
이제 다시 iss53 브랜치로 돌아가 작업을 다시 하도록 하자
$ git checkout iss53
// 다시 test.txt 파일을 수정한다.
$ vim test.txt
$ git commit -a -m "finished the new footer [issue 53]"
[iss53 a0ebc22] finished the new footer [issue 53]
1 file changed, 1 insertion(+)
위에서 작업한 hotfix가 iss53 브랜치에 영향을 끼치지 않는걸 볼수 있다. git merge master 명려으로 master 브랜치를 iss53 브랜치에 Merge 하면 iss 브랜치에 hotfix가 적용이 된다. 아니면 iss53 브랜치가 master 에 Merge 할 수 있는 수준이 될 때가지 기다렸다 Merge 하면 master 와 iss53 브랜치가 합쳐진다.
그러면 바로전에 hotfix브랜치와 master을 merge 한것처럼 iss53과 master을 merge 해보도록 하자.
어!!?? 문제가 발생했다.
충돌의 기초
이전에 master hotfix와 수정한 파일이 test.txt 로 동일하다. 이문제를 해결해야 merge 가 가능하다
충돌에 대해 좀더 알아보자.
$ git merge iss53
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Automatic merge failed; fix conflicts and then commit the result.
git status 명령어를 통해 Git이 어떤 파일을 Merge 할수 없는지 확인해보자.
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
충돌이 일어난 파일은 보는것 처름 unmerged 상태로 표시된다. Git은 충돌이 난 부분을 표준 형식에 따라 표시해준다. 개발자는 해당 부분을 수동으로 해결해야 한다.
파일을 열어보자
test.txt
<<<<<<< HEAD
git commit -a -m "fixed the broken email address"
hotfix 수정
=======
이걸 수정하면 충돌이 생기려나?
>>>>>>> iss53
이러한 부분이 있다. ======= 위쪽 내용은 HEAD 버전 merge 명령어를 실행 할 때 작업하던 master 브랜치 내용이고 아래 쪽은 iss53 브랜치 내용이다 . 충돌을 해결 하려면 위쪽이나, 아래쪽 내용중 고르거나 새로 작성하여 Merge 해야 한다.
수정 test.txt
git commit -a -m "fixed the broken email address"
hotfix 수정
나는 master 부분을 그대로 사용하기로 하였다. 그다음 git add . 명령어로 다시 Git에 저장한다.
충돌을 해결후 파일이 Staging Area에 저장 되었는지 확인 했으면 git commit 명령으로 Merge 한것을 커밋한다. 충돌을 해결하고 Merge 할때는 아래 같은 메세지가 나온다.
Merge branch 'iss53'
# Conflicts:
# test.txt
#
# It looks like you may be committing a merge.
이 merge 와 충돌에 대한건 굉장히 중요한 내용이므로 추후에 다시 자세히 다룰것이다.