깃의 핵심 기능 브랜치
😃 오늘 알아볼 내용
오늘 알아볼 내용은 협업, 그리고 안전한 버전 관리 등에 유용한 브랜치 개념을 알아보려고 한다.
이전 글에서도 브랜치, 병합 등의 용어가 나왔어서 조금은 궁금하셨을 분들도 있었을 것이고, 그러한 궁금증이 없었다 해도 이 개념은 개인적인 생각으로는 매우 중요해 보인다.
더 말할 것 없이 바로 들어가보자.
🌲 브랜치란?
브랜치(branch)는 나뭇가지, 지사, 분점 등 줄기에서 뻗어나온 갈림길을 말한다.
큰 나무 줄기에서 작은 줄기가 뻗어나오는 것처럼 가상의 또 다른 저장 공간을 만드는 것이라 생각하면 좋다.
commit의 경우는 파일의 수정 이력을 관리하는 데 유용하다고 하면, 브랜치는 프로젝트를 독립적으로 관리하는 데 유용하다. 다른 가상 공간을 만드는 것으로 기존의 코드는 항상 안정된 상태를 유지할 수 있게 하며, 본인은 생성한 가상 공간에서 다양한 것을 테스트 해볼 수 있다.
🧐 브랜치의 특징은?
우리가 가상의 공간을 만든다고 하면 어떠한 생각이 들까? 우선 가장 간단하게 머리 속에 들어올 수 있는 것은 바로 복사, 붙여넣기로 원본을 아에 복사하는 것이다.
만약 깃이 이렇게 동작한다면 용량이나 관리 측면에서 비효율적일 것이다.
그러면 깃은 어떻게 이 가상의 공간들을 관리할까?
가상 폴더
깃의 브랜치 작업 폴더를 실제로 복사하지 않고 가상 폴더로 생성한다. 이로 인해서 우리는 브랜치를 늘린다고 해서 실제로 외부에 폴더나 파일이 많아지는 경우는 없다.
이렇게 가상 폴더를 이용하면서 물리적인 구조보다 유연하게 이동이 가능하고, 개발자는 쉽게 가상 폴더인 브랜치를 이동하면서 프로젝트를 진행할 수 있다.
독립적인 동작
브랜치를 이용하면 원본 폴더와 분리하여 독립적으로 개발 작업을 수행할 수 있다.
분리된 브랜치에서 소스 코드를 각자 수정한 후 원본 코드에 병합하는 명령어만 수행하면 각 개발자들이 독립적으로 코드를 개선할 수 있다.
빠른 동작
깃의 브랜치 기능은 다른 버전 관리 도구보다 가볍고, 브랜치 전환이 빠른 것이 특징이다.
깃은 Blob 개념을 도입해서 내부를 구조화한다. Blob은 포인터와 유사한 객체로 깃은 브랜치를 변경할 때 포인터를 이동하듯 빠르게 전환한다.
또한 브랜치 명령을 사용하면 내부적으로 commit을 하나 생성하여 브랜치로 할당하게 되는데, 이 commit이란건 41바이트 해시 파일이기 때문에 브랜치를 만드는 비용도 적다.
🖋 브랜치를 만들어보자
일단 폴더를 하나 만들고 init 작업을 해보자.
만들면 아래와 같이 나올 것이다.
우리가 자주 했던 git init을 한 경우 master라는 브랜치가 현재 브랜치로 설정되게 된다.
이를 master라는 맨 아래 프롬포트 창 글을 통해서 알 수 있다.
(본인은 iterm을 사용 중이고 사용자에 따라 다르게 보이거나 할 수 있다.)
이 master라는 브랜치는 태초의 브랜치 뭐 세계수 같은 것이라 보면 좋다.
모든 commit과 이력은 기본적으로 브랜치에 저장된다. 이때 최소한 한 개 이상의 브랜치가 필요할 것인데 이를 git init시 master라는 이름으로 깃이 생성해주는 것이라고 생각하면 된다.
git status 명령을 통해서도 현 브랜치가 무엇인지 위와 같이 확인할 수 있다.
이제 우리는 이 master 브랜치에서 가지를 뻗은 새로운 브랜치를 만들어볼 것이다.
브랜치는 공통된 커밋을 가리키는 지점을 의미한다. 그리고 이 브랜치는 commit처럼 해시키를 가리킨다. 그러나 우리가 언제나 해시키를 암기하고 다닐 수 없다. 이를 위해서 우리는 별명을 지어줄 필요가 있다.
우리가 새 브랜치를 생성하면 포인터만 있는 브랜치가 생성이 된다. 현재 commit을 가리키는 HEAD를 기준으로 생성이 되는 것이 일반적이며 HEAD는 마지막 커밋을 가리킨다.
이 말은 브랜치가 마지막 commit 위치를 가리키는 역할만 한다는 것인데 위 그림처럼 실제 브랜치가 되려면 어떻게 해야하는가? 바로 실제 커밋이 추가될 경우이다.
한 번 실제로 만들어보자
git branch 브랜치이름 commitID
branch 명령어 뒤에 브랜치 이름을 인자 값으로 추가한다. 브랜치 이름만 입력하면 현재 HEAD 포인터를 기준으로 새로운 브랜치를 생성한다.
만약 직접 commitID 인자를 지정한다면 해당 ID를 기준으로 브랜치를 생성한다.
새로운 파일 하나를 만들고 이를 commit한 상태로 만들어보자.
이후 git branch 명령어를 통해서 브랜치를 만들어보자.
이후 git branch 명령어에 인자를 붙이지 않고 명령을 해보면 이때까지 존재하는 브랜치 목록을 볼 수 있다. 나온 창에서 우리가 만든 브랜치가 있는지 확인해보자.
잘 생성된 것으로 보인다.
본인은 test라는 이름으로 했지만 여러분은 어떤 이름으로 했는가?
이 브랜치 이름으로 가능한 조건을 몇 개 잠시 알아보면 아래와 같다.
- 기호(-)로 시작할 수 없다.
- 마침표(.)로 시작할 수 없다.
- 연속적인 마침표(..)를 포함할 수 없다.
- 빈칸, 공백 문자, 물결(~), 캐럿(^), 물음표(?), 별표(*), 대괄호([]) 등은 포함될 수 없다.
- 아스키 제어 문자는 포함할 수 없다.
- 브랜치 이름은 중복해서 사용할 수 없다.
- 슬래시(/)를 이용해서 계층적인 구조로 만들 수 있다.
🏨 체크아웃을 해보자
우리는 지금 성공적으로 브랜치를 만들었다.
우리는 지금 해당 브랜치일까? 그건 아니다.
우리는 브랜치간 작업 영역을 이동하는 방법을 알아볼 것이다. 바로 체크아웃 이다.
체크아웃이란 현재 브랜치를 떠나 새로운 브랜치로 들어간다는 의미이다.
git checkout 브랜치이름
이 명령어는 중요하다. 생각을 해보면 깃이라는 것은 하나의 워킹 디렉터리만 가지고 있다.
이 워킹 디렉터리는 선택한 브랜치 하나만 연결이 되어 있다. 즉 내가 작업과 commit을 할 브랜치가 맞는지 잘 확인하고 옮기는 것이 중요하다.
한 번 우리가 새로 만든 브랜치로 이동해보자
전환이 된다는 문구와 함께 master에서 test로 변한 것이 보인다.
이 과정은 어떻게 되는 것일까?
우선 HEAD는 항상 변경된 브랜치의 마지막 commit을 가리킨다. 이처럼 HEAD는 브랜치의 마지막 commit을 의미하기 때문에 브랜치가 이동하면 HEAD 포인터도 함께 이동한다.
이후 변경된 브랜치로 새로운 작업을 할 수 있도록 워킹 디렉터리를 변경한다.
이러한 과정을 거쳐서 이동한 브랜치에서 작업을 시작할 준비를 하는 것이다.
이러한 과정에서 충돌을 방지하기 위한 깃의 규칙 같은 것이 존재하는데, 워킹 디렉터리에 작업이 남아있는 경우 checkout을 막는다.
왜 그럴까? 우리가 작업을 하는 공간을 옮기려고 하는데 commit을 하지 않은 작업이 존재한다면 그 내역은 지금 작업을 하는 브랜치에 저장되지 않을 것이다. 이러한 정보가 날아갈 것을 경고하는 것이기도 하며, 더 나아가 충돌을 방지하기 위해서이다.
따라서 작업을 commit을 하거나 스태시라는 명령을 할 수 있는데 이 스태시는 차후 알아볼 것이다.
우리는 브랜치를 만들면서 독립적인 공간으로 만들고 작업하는 것이 장점이라 언급하고 시작했다. 과연 그런지 확인해보자.
현 브랜치에서 새로운 내용을 추가하고 commit 그리고 다시 master로 돌아가보자
우리는 test 브랜치에서 commit을 하고 돌아왔지만 master의 경우는 이전과 그대로다.
이것이 바로 브랜치의 장점인 것이다.
🤯 HEAD 포인터 더 알아보기
앞선 글에서도 HEAD 포인터가 어떠한 역할을 하는지 언급을 한 적이 많았다.
하지만 이번에는 이 HEAD 포인터가 중요하기 때문에 정리를 하고 넘어가려 한다.
마지막 커밋
깃은 항상 마지막 commit 정보가 중요하다. 왜냐하면 새로운 commit의 부모 역할을 하는 commit이 무엇인지 확인해야 하기 때문이다.
만약 매번 마지막 commit을 찾는다고 하면 이는 시스템에 부하가 있을 것이다. 그래서 깃은 마지막 commit을 쉽게 찾을 수 있도록 특수 포인터를 제공하는데 이것이 HEAD다.
HEAD는 브랜치의 마지막 commit ID를 가리키는 참조 포인터이다.
깃은 마지막 commit을 가리키는 이 HEAD 포인터를 부모 commit으로 대체하여 사용한다.
HEAD 포인터가 있기 때문에 우리는 빠르게 부모 commit을 찾고, 이를 스테이지와 비교하고 스냅샷을 생성할 수 있는 것이다.
브랜치 HEAD
브랜치를 이동하면 HEAD 포인터도 이동된다.
브랜치가 여러 개면 HEAD 포인터도 여러개이다. 각각의 브랜치마다 마지막 commit이 다르기 때문이다. 브랜치마다 마지막 commit을 가리키는 HEAD 포인터가 하나씩 있다.
이는 .git 폴더의 ref폴더를 확인해보면 알 수 있다.
위와 같이 우리가 만든 혹은 깃이 만든 브랜치인 master, test가 있는 것을 볼 수 있고, 두 파일을 가지고 있는 폴더의 이름은 heads 이다.
그렇다면 우리는 HEAD가 실제로 여러개가 존재하는 것은 확인했고, 이 HEAD가 브랜치 이동에 따라서 변경되는 것은 어떻게 확인할 수 있을까?
git log --graph --all
위 명령어를 사용해서 확인을 해보자, master에 있을 경우와 test에 있을 경우 두 경우를 모두 확인해보자.
위의 경우가 master 브랜치에 있던 경우, 아래가 test 브랜치에 있던 경우에 결과 값이다.
정리를 하자면 깃은 브랜치 마다 HEAD 값을 저장하고 있다가 우리가 브랜치를 이동하는 것에 따라서 HEAD가 가리키는 포인터 값을 바꿔주는 것이라 생각하면 된다.
💻 원격 브랜치를 알아보자
깃의 원격 저장소에도 브랜치를 나누어서 운영할 수 있다.
저장소는 각자의 고유한 브랜치를 생성하고 관리하는데, 원격 저장소에 생성한 브랜치를 우리는 리모트 브랜치라고 한다.
이 리모트 브랜치는 원격 저장소와 연결된 로컬 저장소에서 새로운 브랜치를 생성한다고 해서 자동으로 원격 저장소에도 브랜치가 생성되는 것은 아니다. 별도의 명령을 통해서 저장소를 동기화 해야한다.
한 번 같이 만들어보자.
우선 이전에 했던 것과 유사하게 새로운 레포지토리를 GitHub에서 생성해주자.
위처럼 새 레포지토리를 만들었다면 이번에는 2번째 방식을 이용해서 연결을 해볼 예정이다. git remote add 적혀있는 라인을 실행시키고 연결이 잘 되었는지 확인해보자.
연결이 잘 된 것 같다.
우리는 이제 등록된 원격 저장소의 리모트 브랜치가 어떤게 있는지 확인해보자.
git remote show origin 을 통해서 리보트 브랜치를 확인해보자.
원격 저장소를 생성하고 등록만 했기 때문에 리모트 브랜치는 아직 없는 상태이다.
로컬 저장소의 브랜치를 원격 저장소에 동기화하려면 push 작업을 해줘야 한다.
git push 원격저장소이름 브랜치이름
로컬 브랜치를 push하면 원격 저장소는 로컬 저장소와 동일한 브랜치를 생성한다.
한 번 master 브랜치를 전송해보자.
이때 -u 옵션을 이용하면 앞으로 해당 원격 저장소와 브랜치를 연결해준다는 의미로 이후 git push나 git pull시 인자를 넘겨주지 않아도 이전 정보로 자동으로 매치해준다.
잘 연결된 것으로 보인다. 연결이 되면서 원격 저장소에 master 브랜치가 생긴 것을 볼 수 있다.
그러면 다른 것도 등록해보자. 우리가 이전에 로컬 저장소에서 만들었던 브랜치인 test 브랜치도 넘겨보자
자동으로 test 브랜치가 원격 서버에 생성된 것을 볼 수 있다.
지금 우리는 원격과 로컬을 연결해둔 상태이다.
이러한 매칭을 업스트림 트래킹이라고 한다.
트래킹 브랜치라고 불리는 업스트림은 리모트 브랜치와 로컬 브랜치를 연결해주는 중간 다리 역할을 한다.
우리는 지금 로컬에서 브랜치를 만든 후 원격으로 올리는 작업을 했는데 반대로 하는 것을 한 번 해보자.
우선 GitHub 레포지토리에서 브랜치를 생성해보자.
브랜치 목록에서 새로운 이름을 생성하고 아래 Create branch를 통해서 만들 수 있다.
해당 버튼을 누르면 기존에 위치한 브랜치에서 새로운 브랜치를 생성하는 것이다.
본인은 master에서 진행을 했다.
이 브랜치를 내려받아보자.
우선 fetch를 통해서 브랜치 커밋을 가져오고 원격 브랜치가 생겼는지 확인해본다.
브랜치를 생성한 commit을 가져왔고 원격 브랜치 목록이 생긴 것을 볼 수 있다.
이후 git checkout -b new origin/new 를 통해서 로컬 저장소에 new 라는 브랜치를 생성한다.
생성된 브랜치가 트래킹을 잘 하고 있는지 확인을 해보자면 git branch -vv 를 통해 확인할 수 있다.
다음과 같이 잘 생성되고 트래킹되는 것을 볼 수 있다.
🙇🏻♂️ 다음에는 무엇을?
브랜치라는 개념에서 학습할 내용들이 정말 많지만 그 중에서 핵심을 나름 알아보았다.
이 브랜치를 이용해서 이제는 병합이라는 내용을 학습해보려고 한다.
사실 이 글을 이렇게 마무리해도 되는가에 대한 생각이 있기는 하다.
글이 잘 마무리 된 것인지도 모르겠고, 필요한 만큼 정리를 한 것인지도 모르겠다. 추가할 내용이 생긴다면 새로운 브랜치 관련 글로 돌아올 것 같다.
다음 내용 병합도 학습하고 정리되는데로 빠르게 올려볼 수 있도록 하겠다.
오늘도 긴 글을 읽어주신 분이 계시다면 감사하다는 말을 드리고 물러나보겠다.