우리가 깃헙을 사용하면서, 자주 브랜치라는 것을 보았을 거다. 대충 이름만 보면 나뭇가지란 뜻인데..
뭔가 데이터구조를 공부하면서 배운 트리구조가 생각나기도 하고...
과연 이 브랜치가 정확히 무엇이고 어떻게 사용하는지 알아보도록 하자!
브랜치
branch는 하나의 코드 관리 흐름이다.
우리가 처음 커밋을 했을 때 나왔던 root commit이 뿌리가 되고, 이 커밋으로부터 가지를 쳐가면서 커밋들이 스냅샷으로 저장되는데 이 갈라지는 가지를 브랜치라고한다.
나무의 기둥처럼 기본적인 브랜치가 존재하는데 이를 master branch라고 한다
master branch는 레포지토리를 만들고 커밋을 하면 자동으로 생성된다.
위 사진에서 볼 수 있듯이 각자의 브랜치에서 작업한 내용은 특별한 작업을 거치지 않는 한 서로 영향을 미치지 않는다.
- 브랜치 만들기
git branch [branchName]
- 브랜치 만들고 그 브랜치로 이동하기
git checkout [branchName]
- 브랜치 조회하기
git branch
* master
premium
* : 현재 사용중인 브랜치
- 브랜치 삭제
git branch -d [branchName]
- 브랜치 만들고 바로 이동
git checkout -b [branchName]
브랜치 Merge
자, 위의 사진을 기준으로 무료 버전에 무언가를 추가했다고 해보자.
그리고 그걸 커밋하자.
근데 동업자가 유료버전에는 무료버전이 반드시 포함되어있어야 한다고 한다.
그럴려면 무료버전 브랜치를 유료버전 브랜치에 가져와야하는데...
하나하나 파일을 찾아가며 이를 복사해 수정하고 한다면... 과연 깃을 쓰는 이유가 있을까?
따라서 이러한 행위를 가능하게 해주는 것을 branch merge라고 한다.
git merge [branchName]
- 현재 위치인 브랜치에 [branchName]을 합치겠다.
Merge를 할 때 Conflict 발생
두개의 브랜치에서 모두 수정이 일어나면 git은 둘 중에 무엇을 반영해야 하는지 결정하지 못한다.
이를 해결하기 위해선 두가지 방법이 있다.
1. conflict가 발생한 파일을 열고, merge의 결과가 되었음 하는 모습대로 코드를 수정하고 커밋하기.
conflict가 발생한 빨간 박스 부분을 삭제후 원하는 모습으로 수정한 뒤 커밋을 한다.
2. merge 자체를 취소하는 것
git merge --abort
conflict가 발생한 파일이 여러개일 때
원리는 똑같다.
1. conflict를 해결하고 git add [파일 이름] 커맨드로 하나씩 staging area에 올리기
(중간중간에 git status 커맨드로 현재 상태 확인하면서)
2. 모든 파일들의 conflict를 다 해결하고, git add . 커맨드로 한번에 staging area에 올리기
1 or 2 하고 커밋을 하기
Merge를 한다고 꼭 커밋이 생성되는 건 아니다
지금 HEAD는 master를 가리키고있다. 이 상태에서 git merge premium을 실행하면????
premium 브랜치가 가리키던 커밋을, master 커밋도 똑같이 가리키게 되는,. 지금 총 커밋 수는 그대로다!
이렇게 새로운 커밋이 생기는 게 아니라 단지 브랜치가 이동하게 되는 merge를 Fast-forward merge라고 합니다.
어떤 경우에 이렇게 되는 걸까?
커밋 히스토리에서 같은 선(line) 상에 있는 브랜치를 merge할 때 Fast-forward merge가 이루어집니다.
방금 전에는 master 브랜치와 premium 브랜치가 둘다 같은 선 상에 있었기 때문에 Fast-forward merge가 이루어진 것!
이 사진처럼 두 브랜치가, 커밋 히스토리 상에서 분리된 2개의 선에 각각 존재할 때 merge를 하면 merge-commit이
새롭게 생기는 것! 이러한 merge를 3-way merge라고 한다!
이름이 3-way merge인 이유는
- 두 갈래로 갈라지기 전 공통 조상이 되는커밋
- 한 브랜치가 가리키는 커밋
- 다른 브랜치가 가리키는 커밋
이렇게 1개의 분기점과 2개의 지시점이 있기 때문이다.
3-way merge 방식
위의 표는 master 브랜치와 premium 브랜치를 merge했을 때 상황별 결과를 정리해 놓은 것이다.
모든 커밋에 sample.txt란 파일이 있다고 해보자.
base : 두 브랜치의 공통 부모 커밋의 sample.txt 파일의 내용 중 일부
master : 마스터 브랜치의 최신 커밋의 sample.txt 파일의 내용 중 일부
premium : 프리미엄 브랜치의 sample.txt 파일의 내용 중 일부
머지 결과 : 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" 인데, 두 브랜치에서 다 변화가 있을 때 Git은 뭘 선택할 지모른다. 사실, 바로 이런 경우에 Conflict가 발생한다.
이전에 Conflict가 발생했을 때 그것을 해결하고 머지를 마무리했던 경우가 이런 경우다.
Remote Repository 브랜치
GitHub에서 Math_Box라는 리모트 레포지토리를 만들고 로컬 레포지토리의 내용을 그 리모트 레포지토리에 보내기위해 아래와 같은 커맨드 2개를 실행한 적이 있다.
git remote add origin https://github.com/kyuri-dev/Math_Box.git
git push -u origin master
1. Origin
git remote add origin https://github.com/kyuri-dev/Math_Box.git
위 커맨드에서
remote : 리모트 레포지토리에 관한 작업을 할 때 사용하는 커맨드
add : 새로운 리모트 레포지토리를 등록하겠다는 뜻
origin https://github.com/kyuri-dev/Math_Box.git : 뒤의 주소의 레포지토리를 origin이라는 이름으로 등록하겠다.
즉, 주소를 간단히 변수 origin에 대입한다고 생각하면 된다.
왜 하필 origin일까?
그럼 왜 하필 origin이라고 하는 걸까? origin이 아닌 다른 단어를 입력해도 큰 상관은 없다.
하지만 Git에서는 리모트 레포지토리를 최초로 추가할 때 origin이라는 이름으로 가리키는 것이 관례화되어 있다!
origin은 ‘근원’, ‘기원’이라는 뜻을 가진다. 아마도 다른 사람의 리모트 레포지토리를 자신의 컴퓨터로 가져와서 작업을 하는 사람의 입장에서는 리모트 레포지토리가 프로젝트의 근원이 되는 존재이기 때문에 그런 관습이 생긴 것으로 추측된다.
git remote add hello https://github.com/kyuri-dev/Math_Box.git
처럼 origin 대신 우리가 원하는 단어(hello)를 써도 상관은 없지만, 되도록 관례에 따라 origin을 써주는 게 좋겠지?
2. Remote Repository에 있는 브랜치
git push -u origin master
위 커맨드에서
현재 로컬 레포지토리에 있는 master 브랜치의 내용을 origin이라는 리모트 레포지토리로 보낸다는 뜻.
이때 같은 이름의 브랜치로 전송하게 되는데 만약 origin이라는 리모트 레포지토리에 master 브랜치가 없으면 master 브랜치를 새로 생성하고 푸시.
여기서 옵션 -u에 대해 알아보자
--set-upstream (-u) : 로컬레포지토리에 있는 master 브랜치가 origin에 있는 master 브랜치를 tracking 한다.
tracking
로컬 레포지토리의 한 브랜치가 리모트 레포지토리의 한 브랜치와 연결되어 그것을 계속 바라보는 상태가 되는 것.
이렇게 맺어진 연결 상태를 tracking connection이라고 한다.
만약에...!
로컬 레포지토리에 A라는 브랜치가 있고, 리모트 레포지토리에 B라는 브랜치가 있을 때
이런 tracking connection이 서로 맺어진 경우, B 브랜치를 A 브랜치의 upstream branch라고 한다.
(지금은 구별하기 위해서 A와 B라고 표현했지만 보통은 같은 이름인 경우가 대부분!)
이렇게 tracking connection이 한번 설정되고 나면,
사용자가 현재 master 브랜치에 위치해있을 때,
git push 라고만 써도 자동으로 리모트 레포지토리의 master 브랜치를 대상으로 git push가 동작하고,
git pull 라고만 써도 리모트 레포지토리의 master 브랜치를 대상으로 git pull이 동작한다.
사실 옵션을 안줘도 push pull 할 수 있다.
하지만 맨 처음에 이 옵션을 주지 않으면 tracking connection이 없기 때문에 나중에 git push를 하고 싶을 때
git push origin master:master
이런 식으로 적어줘야 한다.
origin은 리모트 레포지토리를 나타내고,
master:master에서 더 먼저 나오는 master는 로컬 레포지토리의 master 브랜치이고, 더 뒤에 나오는 master는 리모트 레포지토리의 master 브랜치이다.
그러니까 tracking connection이 없으면 매번 이런 식으로 git push를 해줘야 한다.
git pull도 마찬가지이다.
그러니까 그냥 처음부터 tracking connection을 설정하고 그 이후부터는 git push, git pull이라고만 써서 편하게 푸시와 풀을 하는 게 좋겠지??
3. origin / master의 의미
master가 로컬 레포지토리의 master 브랜치를 나타내고
origin/master가 리모트 레포지토리의 master 브랜치를 나타낸다.
위 사진에서는, 커밋을 로컬레포지토리의 master브랜치에서 여러번 했지만 그 이후에 git push를 해준 적이 없기 때문에
위 그림처럼 origin/master가 master보다 이전의 커밋을 가리키고 있는 것이다.
4. master 브랜치와 premium 브랜치 둘다 push하기
현재 리모트 레포지토리에는 premium 브랜치가 없으므로
--set-upstream 옵션을 설정하지 않은 상태이기 때문에 tracking이 되지 않음.
그래서...
git push --set-upstream origin premium 을 해주면 브랜치가 생기면서 푸쉬가 됨.
HEAD와 브랜치
(HEAD -> branch)
커밋 히스토리를 보면 위와 같은 로그내용이 있다.
우리는 이전에 HEAD가 포인터 개념으로 구현되어 있다고 했다.
여기서, 이제 우리는 다시 branch의 개념을 정립할 필요가 있다.
- branch : 작업의 흐름이기도 하나 어떤 커밋을 가리키는 존재이기도 함!!
- HEAD : 어떤 커밋을 가리키는 포인터!!라 했지만 사실 branch를 가리킨다.
브랜치를 바꾼다는 것은 HEAD가 가리키는 브랜치가 바뀌는 것이다.
git reset의 비밀
git reset 커맨드를 사용하면 HEAD는 여전히 같은 브랜치를 가리키고, 브랜치가 다른 특정 커밋을 가리키게 된다.
이 때문에 결국 HEAD가 간접적으로 가리키던 커밋도 바뀌게 되는 것!
즉!!!!
과거의 커밋으로 git reset을 한다고 그 이후의 커밋들이 삭제되는 게 절대 아니다. 계속 남아있다.
git reset은 과거의 커밋뿐만 아니라 현재 HEAD가 가리키는 커밋 이후의 커밋으로도 할 수 있다.
git reset vs git checkout
git reset은 브랜치가 다른 특정 커밋을 가리키게 하는 것
git checkout은 HEAD가 다른 브랜치를 가리키게 하는 것
HEAD가 다른 브랜치가 가리키던 커밋을 가리키게 되면 그에 맞게 working directory 내부도 바뀌게 되고,
그 결과 우리는 브랜치가 변경되었다는 걸 실감할 수 있었던 것이다!
다시 말해보면
git checkout master의 뜻은 다음과 같이 해석됩니다.
- master 브랜치로 이동하라
- HEAD가 master 브랜치를 가리키도록 하라
- HEAD가 master 브랜치가 가리키던 커밋을 간접적으로 가리키게 됨으로써
- working directory의 내부도 그 커밋에 맞게 변함으로써
- master 브랜치로 이동한 것을 사용자는 실감하게 됨
커맨드 정리
git branch [새 브랜치 이름] : 새로운 브랜치를 생성
git checkout -b [새 브랜치 이름] : 새로운 브랜치를 생성하고 그 브랜치로 바로 이동
git branch -d [기존 브랜치 이름] : 브랜치 삭제
git checkout [기존 브랜치 이름] : 그 브랜치로 이동
git merge [기존 브랜치 이름] : 현재 브랜치에 다른 브랜치를 머지
git merge --abort : 머지를 하다가 conflict가 발생했을 때, 일단은 머지 작업을 취소하고
양이 진짜 많다...! 저장소를 가리키는 포인터 개념도 나오고 살짝 복잡할 뻔 했지만 다 정리하고 나니까 머리속에 많이 남았다. 오늘도 수고했다!
2021-09-26
'DevOps > Git' 카테고리의 다른 글
3. GIT 실무 맛보기 (0) | 2021.10.01 |
---|---|
4. 커맨드 모음 (0) | 2021.10.01 |
1. Commit 다루기 (0) | 2021.09.21 |
GIT 오픈소스 (0) | 2021.09.11 |
0. GIT & GIT_HUB 시작하기 (0) | 2021.09.11 |