본 포스팅은 Git – 커밋(Commit) 다루기와 이어집니다!
이번에는 브랜치에 대해 알아 보겠습니다!
브랜치의 개념
현재 필자의 커밋 히스토리는 아래와 같습니다.
(이전의 포스팅과는 살짝 다르지만 브랜치를 이해하는 것에는 지장이 없습니다.)
이번 포스팅에서 살펴볼 것은 master, origin/master 등에 해당됩니다.
master, origin/master 따위를 브랜치라고 합니다.
그리고 브랜치는 하나의 코드 관리 흐름이라고 생각하면 됩니다.
위는 브랜치를 이해하기 위한 트리 그림입니다.
커밋들은 트리의 가지 모양을 띄고 있는데 가지 하나 하나를 브랜치라고 부릅니다.
가지는 ‘곧은 가지(파란 타원 부분:C)‘, ‘곧다가 꺾인 가지(주황 타원 부분:B)‘로 나뉠 수 있으며, 후자의 가지는 곧은 가지(빨간 타원 부분:A)를 포함할 수 있습니다. (오른쪽 그림 참고)
가지의 끝에 있는 ‘커밋’을 가리키는 포인터도 브랜치라고 부르며 똑같이 A,B,C 브랜치라고 표현합니다.
아래는 필자의 트리 그림입니다.
필자의 트리는 Master브랜치와 origin/master브랜치가 같은 가지인 상태입니다.
또한 Head포인터 같은 경우 Master브랜치를 가리키고 있습니다.
명령어 제대로 이해하기
브랜치를 이해하고 나면 Head와 reset명령어를 제대로 이해할 수 있습니다.
Head는 ‘현재의 브랜치를 가리키고 있는 포인터’입니다.
reset명령어는 ‘Head가 가리키던 브랜치가 다른 커밋을 가리키도록 하는 명령어’입니다.
브랜치가 다른 커밋을 가리켜도 Head가 해당 브랜치를 가리키는 것은 변하지 않으므로 마치 Head가 다른 커밋을 가리키는 것처럼 보입니다.
또한 ‘git push –u origin master’ 명령어도 제대로 이해할 수 있습니다.
push origin master는
현재 로컬 레포지토리에 있는 master브랜치 내용을 origin이라는 리모트 레포지토리로 보낸다는 의미입니다.
-u 옵션은 –set-upstream옵션과 같으며
origin(리모트 레포지토리)에 있는 master브랜치를 추적하겠다는 의미입니다.
이 옵션은 처음으로 브랜치 내용을 전송할 때만 사용합니다. (이후에는 자동 추적 됨)
커밋을 하면 브랜치는 새로운 커밋을 가리킵니다.
기존에 없던 README.md파일을 추가해서 커밋했습니다.
README.md파일은 MarkDown파일로, 이를 커밋 후 푸시하면 깃헙 페이지에서 브랜치에 대한 소개를 볼 수 있습니다.
커밋 후 master브랜치는 ‘e2f0’커밋에서 새로 추가된 ‘e3bc’커밋을 가리키게 됩니다.
당연히 master브랜치의 가지 영역도 ‘e2f0’커밋 영역까지 확대됩니다.
‘e3bc’커밋은 ver_2라는 태그를 붙였고, master브랜치는 push 하였습니다.
git에서 origin/master를 추적한 결과, ‘e3bc’커밋을 가리키는 것을 확인할 수 있습니다.
(리모트 레포지토리도 새로운 커밋을 가리킴)
(origin/master브랜치의 가지 영역은 master브랜치의 영역과 같으므로 생략합니다. )
새로운 브랜치 만들기
새 브랜치는 git branch (브랜치) 명령어로 생성할 수 있습니다.
필자는 master의 반의어인 novice 브랜치를 생성했습니다.
이후 현재 브랜치(Head가 가리키고 있는 브랜치)를 novice로 전환하였습니다.
git checkout (전환할 브랜치) 명령어를 사용하면, Head가 가리키는 브랜치를 바꿀 수 있습니다.
(해당 브랜치의 최종 커밋 형태로 워킹 디렉토리도 바뀌게 됩니다.)
아래는 novice 브랜치 생성 후의 트리 그림입니다.
새 브랜치(novice)는 현재 브랜치(master)의 커밋들을 포함해서 생성됩니다.
새로운 브랜치에 커밋하기
novice 브랜치에 커밋을 해보겠습니다.
새 브랜치에 커밋을 하려면 먼저 git checkout명령어로 현재 브랜치를 전환해야 합니다.
(git checkout: HEAD가 다른 커밋이나 브랜치를 가리키게 하는 명령어 )
이번에는 ‘hello_novice.py’라는 파일을 추가해서 커밋하였습니다.
해당 커밋은 nver_1이라는 태그를 붙였고 novice 브랜치는 push하였습니다.
처음으로 (novice)브랜치 내용을 전송하는 것이기 때문에,
git push –u origin branch 명령어를 이용하였습니다.
아래는 새 브랜치에 커밋을 추가한 후 나타낸 트리 그림입니다.
master 브랜치 가지와는 다른 방향으로 커밋이 추가됩니다.
맨 처음 설명한 곧다가 꺾인 가지(Novice)가 생성된 것입니다!
다음은 깃허브에서 브랜치를 비교해보았습니다.
머지 커밋 하기 (merge)
서로 다른 브랜치의 최종 커밋은 git merge 명령어를 통해 합병이 가능합니다.
그러나 경우에 따라 합병이 안 될 수도 있고 합병하는 방향이 다를 수도 있습니다.
각 경우는 아래 텍스트를 참고하시기 바랍니다.
일단 필자의 트리를 기준으로 머지 커밋을 해보겠습니다.
아래 트리 그림을 보면 novice 브랜치가 master 브랜치를 포함하고 있습니다.
git merge master를 이용해서 합병을 시도해보지만 합병되지 않는 결과를 보입니다.
(1-a 경우에 해당됨)
이번에는 git checkout master로 현재 브랜치를 바꾼 뒤 git merge novice 명령을 해보았습니다.
그 결과, master브랜치의 워킹 디렉토리의 내용은 novice 브랜치의 최종 커밋과 같은 상태가 되었습니다.
또한 트리 그림 상에서 master브랜치와 novice브랜치도 같아지게 되었습니다.
브랜치 가지가 같기 때문에 novice 브랜치의 보라색 영역은 굳이 나타내지 않았습니다.
(1-b 경우에 해당됨)
충돌(Conflict)의 개념과 해결 방안
이번에는 (2-a),(2-b) 경우에 해당하는 충돌(conflict)를 알아 보겠습니다.
이를 확인하기 위해 앞에서 진행한 합병을 해제했습니다. (master브랜치에서 git reset –hard ver_2를 진행함)
이후 각각의 브랜치에서 hello.py 파일을 다르게 수정하고 커밋하였습니다.
master 브랜치에서는 커밋 후 ver_3라는 태그 이름을 붙였습니다.
nocie 브랜치에서는 커밋 후 nver_2라는 태그 이름을 붙였습니다.
이제 트리 상태는 아래와 같습니다!
원래는 novice 브랜치가 master브랜치를 포함했으나, master브랜치의 커밋이 추가되면서 포함 관계를 벗어나게 됐습니다.
이 상태에서 master 브랜치 기준으로 novice와 합병을 해보았습니다.
그러면 아래와 같이 CONFLICT(충돌)이라는 메시지가 발생합니다.
이는 novice 브랜치 기준으로 master 브랜치와 합병을 해도 마찬가지입니다.
(2-a 경우에 해당됨)
CONFLICT(충돌)의 이유는 어느 파일로 합병을 해야될지 모르기 때문입니다.
master 브랜치의 hello.py를 적용해야 될지, novice 브랜치의 hello.py를 적용해야 될지 모르는 상태입니다.
이 상태에서 hello.py를 실행한 화면은 다음과 같습니다.
이때 사용자는 위, 아래 둘 중 한 텍스트를 삭제해서 적용할 파일을 선택해야 됩니다.
필자는 novice의 hello.py파일을 적용하였습니다. (아래와 같이 수정)
이후 git add . 을 하고 다시 커밋을 진행합니다.
그러면 master브랜치의 워킹 디렉토리는 성공적으로 합병이 됩니다!
(master브랜치 최종 커밋 + novice브랜치의 최종 커밋 / 충돌된 것은 novice브랜치로 처리)
이제 트리 상태는 아래와 같습니다. (합병된 커밋은 merge1이라고 태그를 붙임)
머지 커밋을 할 때는 현재 브랜치를 기준으로 추가됩니다!
만일 CONFLICT가 발생했을 때 합병을 중단하고 싶으면 git merge –abort 옵션을 이용하면 됩니다!
Detached Head의 개념과 활용
마지막으로 Detached Head에 대해 알아 보겠습니다.
Head는 ‘현재의 브랜치를 가리키고 있는 포인터’입니다.
그런데 만약 Head가 브랜치가 아니라 커밋 자체를 가리키고 있다면,
해당 Head는 Detached Head라고 부릅니다!
이것이 가능한 이유는 git checkout 명령어 덕분입니다.
git reset명령이 Head가 다른 브랜치를 가리키도록 하는 명령어였다면
git checkout명령은 Head가 다른 브랜치 혹은 다른 커밋을 가리키도록 하는 명령어입니다!
한 번 nver_1태그의 커밋을 가리키게 해보았습니다.
이 상태에서 git branch 명령어를 사용한다면 트리의 끝 부분이 아니어도 새로운 가지를 만들 수 있는 것입니다.
다음 포스팅은 ‘유용한 명령어’에 대해 살펴보겠습니다.
감사합니다!
본 포스팅은 필자가 이해하기 편한 대로 작성하였습니다. 브랜치에 대한 정확한 설명을 원하시는 분은 Git 공식 문서를 참고하시길 바랍니다!