8. HEAD와 브랜치의 관계
main 브랜치에서 커밋 히스토리를 살펴보면 HEAD가 main을 가리키고 있다.(HEAD -> main)
앞서 HEAD는 어떤 커밋 하나를 가리키고, branch는 하나의 코드 관리 흐름이라고 했다. 둘은 무슨 관계길래 HEAD가 브랜치를 가리키는 형상을 띠고 있을까?
먼저 branch는 코드를 관리하는 하나의 흐름인데, HEAD처럼 어떤 커밋을 가리키는 존재이다. 앞으로는 포인터라고 설명하겠다. 우리가 커밋을 처음으로 하면 main 브랜치가 첫번째 커밋부터 시작해서 매번 새롭게 생기는 커밋을 가리킨다. 깃에서 커밋은 이전 커밋에 대한 정보를 가지고 있으므로 main 포인터가 가장 최신의 커밋을 가리키고 있다고 해도 결국 그 이전 커밋으로 하나씩 거슬러 올라갈 수 있기 때문에 어떻게 프로젝트가 변해 왔는지 추적할 수 있다. 이것이 코드 관리 흐름이라 불리는 이유이다.
HEAD 역시 어떤 커밋을 가리키는 포인터이다. HEAD가 가리키는 커밋의 내용대로 working directory의 내용물이 바뀐다. but 정확하게 말하자면 HEAD는 커밋을 직접적으로 가리키지는 않는다. HEAD는 보통 브랜치를 가리킨다. 즉, 브랜치를 통해 커밋을 간접적으로 가리킨다고 볼 수 있다.
여기서 premium 브랜치를 생성하면? HEAD가 가리키던 커밋을 premium 브랜치가 가리키게 된다.
그리고 git checkout premium 커맨드를 통해 premium 브랜치로 이동하면, HEAD가 premium을 가리키게 될 것이다. 여기서 checkout 커맨드는 HEAD가 가리키는 브랜치를 변경하는 작업이었음을 알 수 있다.
이 상태에서 새로운 커밋을 추가하면, HEAD가 가리키는 premium 브랜치가 새로운 커밋을 가리키게 된다.
그리고 다시 git checkout main 을 입력하면, HEAD는 main 브랜치를 가리키게 된다. 따라서 HEAD는 다섯 번째 커밋을 가리키게 되고, 해당 커밋의 내용대로 working directory의 내용도 바뀌게 된다.
여기서 또다시 커밋을 하면??!! 사진처럼 커밋 히스토리의 흐름이 갈라진다. '분기한다' 라고도 한다.
main 브랜치와 premium 브랜치에서 각각 여러번 커밋을 하면 사진처럼 될 것이다.
그리고 premium 브랜치를 main 브랜치에 merge하면??! main 브랜치가 가리키던 커밋의 내용에 premium 브랜치가 가리키던 내용을 합쳐서 새로운 커밋을 만들게 된다. 이때의 커밋을 머지 커밋이라고 한다.
즉, merge는 HEAD가 가리키던 커밋에 다른 브랜치가 가리키던 커밋을 합쳐서 새로운 커밋을 만드는 작업이다.
< 브랜치 배울 때 참고하면 좋은 사이트 https://learngitbranching.js.org/?locale=ko >
9. git reset의 비밀
git reset을 할 때 head의 변화
전에 배운 git reset 커맨드의 동작 원리를 더울 정확하게 알아보겠다.
git reset 커맨드를 사용하면
- HEAD는 여전히 같은 브랜치를 가리키고,
- HEAD가 가리키는 브랜치가 다른 특정 커밋을 가리키게 된다.
- 이 때문에 결국 HEAD가 간접적으로 가리키던 커밋도 바뀌게 되는 것이다.
git reset을 한다고 이후의 커밋이 사라지는 것이 아니다
git reset을 한다고 하면 그 이후의 커밋이 삭제되는 것으로 착각하기 쉽다. 사실은 전혀 그렇지 않다.
그리고 git reset은 꼭 과거의 커밋으로만 할 수 있는 것도 아니다. 현재 HEAD가 가리키고 있는 커밋 이후의 커밋으로도 리셋할 수 있다.
10. git reset과 git checkout의 차이점
HEAD는 직접 커밋을 가리키는 게 아니라 브랜치를 통해서 간접적으로 커밋을 가리킨다고 했다.
하지만 git checkout 커맨드를 사용해 HEAD 자체가 가리키던 것을 바꿀 수도 있다.
git checkout 커맨드를 사용하면 HEAD가 브랜치를 가리키는 게 아니라 본인이 직접 커밋을 가리키게 된다.
이렇게 브랜치를 통해서 커밋을 가리키는 게 아니라 본인이 직접 커밋을 가리키고 있는 상태의 HEAD를 Detached HEAD 라고 부른다.
이렇게 HEAD가 특정 커밋을 직접 가리키게 하는 주된 이유 한 가지는 바로 과거의 특정 커밋에서 새로운 브랜치를 만들고 싶을 때이다. 예를 들어 Detached HEAD인 상태에서 git branch premium 으로 premium 브랜치를 새로 만들면 사진과 같은 결과가 된다.
정리해보면 지금 premium이라는 브랜치가 새로 생성되었고, premium 브랜치는 HEAD가 가리키던 커밋을 똑같이 가리키게 된다.
또한 git checkout 커맨드로는
- HEAD가 커밋을 직접적으로 가리키게 할 수도 있을 뿐만 아니라
- 브랜치를 직접 가리키게 만들 수도 있다.
여기서 git checkout premium 커맨드를 사용하면 HEAD가 premium 브랜치를 가리키게 된다. 그리고 이것은 곧 Detached HEAD 상태에서 벗어나 HEAD가 브랜치를 가리키는 정상적인 상태로 돌아오는 것이다.
그리고 이렇게 HEAD가 premium 브랜치를 가리키는 상태일 때 새 커밋을 하면??
이제 premium 브랜치로 main 브랜치와 다른 새로운 코드 관리 흐름을 가져갈 수 있게 된다.
방금 한 것처럼 특정 커밋을 시작점으로 하는 새로운 브랜치를 만들고 싶을 때 HEAD를 잠시 Detached HEAD 상태로 두는 경우가 많다.
정리해보면 git checkout 뒤에는 커밋 아이디 또는 브랜치의 이름을 줘서 HEAD가 직접 커밋을 가리키거나, 브랜치를 가리키도록 할 수 있다는 뜻이다.
또한 여기서 git checkout master 를 실행하면
이렇게 HEAD가 master 브랜치를 가리키게 된다. 바로 이게 우리가 이전에 git checkout 커맨드를 사용해서 다른 브랜치로 이동할 때 벌어지는 일이었던 것이다.
이렇게
- HEAD가 다른 브랜치가 가리키던 커밋을 가리키게 되면
- 그에 맞게 working directory 내부도 바뀌게 되고,
- 그 결과 우리는 브랜치가 변경되었다는 걸 실감할 수 있었던 것이다.
즉 git checkout main 이 커맨드의 뜻은 다음과 같이 해석된다.
= main 브랜치로 이동하라
= HEAD가 main 브랜치를 가리키도록 하라
= HEAD가 main 브랜치가 가리키던 커밋을 간접적으로 가리키게 됨으로써
= working directory의 내부도 그 커밋에 맞게 변함으로써
= main 브랜치로 이동한 것을 사용자는 실감하게 됨
마지막으로 git reset과 git checkout 을 비교해보면!!
git reset | git checkout |
HEAD가 가리키던 브랜치가 다른 커밋을 가리키게 함 | HEAD 자체가 다른 커밋이나 브랜치를 가리키도록 한다 |
HEAD도 결국 간접적으로 다른 커밋을 가리키게되는 효과가 생김 | 브랜치를 통하지 않고, 커밋을 직접적으로 가리키는 HEAD를 Detached HEAD라고 한다 |
11. 새로운 커밋을 만들지 않는 merge도 있다
머지를 한다고 항상 새로운 커밋(머지 커밋)이 생기는 것은 아니다.
예를 들어 아래와 같이 HEAD가 main을 가리키고 있는 상황일 때, git merge premium 을 입력하면 어떻게 될까??
premium 브랜치가 가리키던 커밋을, master 커밋도 똑같이 가리키게 된다. 지금 총 커밋 수도 그대로다.
이렇게 새로운 커밋이 생기는 게 아니라 단지 브랜치가 이동하게 되는 머지를 Fast-forward 머지라고 한다.
어떤 경우에 이렇게 되는 걸까??
커밋 히스토리에서 같은 선(line) 상에 있는 브랜치를 머지할 때 Fast-forward 머지가 이루어진다. 방금 전에는 master 브랜치와 premium 브랜치가 둘다 같은 선 상에 있었다.
아래 그림처럼 두 브랜치가, 커밋 히스토리 상에서 분리된 2개의 선에 각각 존재할 때 머지를 하면 머지 커밋이 새롭게 생기게 된다.
그리고 이런 머지는 3-way merge라고 한다. 이름이 3-way인 이유는 지금 1, 2, 3 표시한 3가지 커밋을 고려해서 머지를 하기 때문이다.
- (1)번 : 두 갈래로 갈라지기 전 공통 조상이 되는커밋
- (2)번 : 한 브랜치가 가리키는 커밋
- (3)번 : 다른 브랜치가 가리키는 커밋
3-way merge는 자신만의 방식을 갖고 이 3가지 커밋을 기준으로 머지 커밋을 자동으로 만들어낸다.
방식에 대해 간단히 알아보겠다. 모든 커밋에 smaple.txt 파일이 있다고 가정하자.
- base : 두 브랜치의 공통 부모 커밋의 sample.txt 파일의 내용 중 일부 = 위 그림 (1)번
- master : 마스터 브랜치의 최신 커밋의 sample.txt 파일의 내용 중 일부 = 위 그림 (2)번
- premium : 프리미엄 브랜치의 sample.txt 파일의 내용 중 일부 = 위 그림 (3)번
- 머지 결과 : master 브랜치에서 premium 브랜치를 머지했을 때의 최종 결과
case1
지금 base가 A이고, master는 A, premium은 B이다. 그럼 base를 기준으로 볼 때, master에서는 변화가 없었지만, premium에서는 A가 B로 변경된 상태이다. 3-way merge는 base에서 변화가 발생한 것을 우선 채택한다. 그래서 머지 결과는 'B'가 된다.
case2
지금 base가 1이고, master는 2, premium은 1이다. 이 경우에도 base에서 변화가 발생한 2가 머지 결과가 된다.
case3
지금 base가 "hello"이고, master는 "hello"를 삭제한 공백 상태, premium은 "hello"이다. "hello"를 삭제해서 공백 상태가 된 것이 변화가 더 발생한 것이기 때문에 머지 결과는 공백이 된다.
case4
지금 base가 "bye", master가 "fighting", premium이 "please" 인데, 둘 다 base 때와는 다른 변화가 일어났다. 이렇게 두 브랜치에서 다 변화가 있을 때 Conflict가 발생합니다.
정리해보면 3-way merge는
- base때의 내용과 비교했을 때 달라진 부분이 있는 것이 우선시되고,
- 두 브랜치에서 둘다 변화가 일어났을 때는 Conflict를 발생시켜서 사용자가 스스로 선택하게끔 한다.
앞서 merge할 때 이것저것 해보고 이해불가 판정내렸던 파트의 해설이.. 여기 나오네... ㅎㅎㅎ
12. 브랜치 정리
- git branch [새 브랜치 이름] : 새로운 브랜치를 생성
- git checkout -b [새 브랜치 이름] : 새로운 브랜치를 생성하고 그 브랜치로 바로 이동
- git branch -d [기존 브랜치 이름] : 브랜치 삭제
- git checkout [기존 브랜치 이름] : 그 브랜치로 이동
- git merge [기존 브랜치 이름] : 현재 브랜치에 다른 브랜치를 머지
- git merge --abort : 머지를 하다가 conflict가 발생했을 때, 일단은 머지 작업을 취소하고 이전 상태로 돌아감
'1. 프로그래밍 > 1-1. Git' 카테고리의 다른 글
7. Git 자유자재로 활용하기 (0) | 2021.09.06 |
---|---|
6. Git 협업하기 (0) | 2021.09.05 |
5. 브랜치 사용하기 - [ branch / merge ] (0) | 2021.09.05 |
4. 커밋 다루기 - [ commit history / 커밋 메세지 / HEAD / git reset ] (0) | 2021.08.31 |
3. GitHub 시작하기 - [ Git hub / Remote Repository / 사용자 초대 / Readme.md ] (0) | 2021.08.24 |