Git 브랜치: git branch로 만들기, 전환, 삭제하기
Git 브랜치는 하나의 커밋을 가리키는 가벼운 포인터이고, HEAD는 지금 어떤 브랜치에 있는지를 나타냅니다. git branch는 브랜치를 조회, 생성, 이름 변경, 삭제하고, git switch는 브랜치 사이를 전환합니다. git switch -c 이름을 쓰면 브랜치를 만들고 바로 그쪽으로 이동합니다.
Git 브랜치란 무엇인가
Git 브랜치는 프로젝트의 복사본이 아니라, 커밋 하나의 해시가 적힌 작은 파일입니다. 브랜치를 만드는 데는 눈 깜짝할 사이가 걸리고 공간도 거의 쓰지 않습니다. Git은 아무것도 복제하지 않고, 모든 브랜치가 저장소 안의 같은 객체들을 가리키기 때문입니다. 커밋을 하면 현재 브랜치의 포인터만 새 커밋으로 옮겨집니다.
이 때문에 브랜치를 아껴 쓸 필요가 없습니다. 작업, 수정, 실험마다 브랜치를 따로 만드는 것이 일반적인 방법입니다. 실험이 실패하면 브랜치를 지우면 되고, 메인 히스토리는 깨끗하게 유지됩니다.
브랜치가 파일이라는 것은 직접 확인할 수 있습니다.
$ cat .git/refs/heads/main
8b7d4e2a9f1c0d3e5b6a7c8d9e0f1a2b3c4d5e6f
(큰 저장소에서는 일부 참조가 .git/packed-refs에 묶여 있을 수 있어서 파일이 없을 수도 있습니다. 원리는 같습니다.)
Git의 HEAD: 지금 어디에 있는가
HEAD는 지금 작업 폴더에 "체크아웃"되어 있는 대상을 가리키는 포인터입니다. 보통 HEAD는 커밋이 아니라 브랜치를 가리킵니다.
$ cat .git/HEAD
ref: refs/heads/main
연결은 이렇게 이어집니다: HEAD → main → 커밋. 새 커밋은 HEAD가 가리키는 커밋을 부모로 삼고, 그다음 Git은 HEAD가 가리키는 브랜치를 앞으로 옮깁니다. 그래서 커밋은 현재 브랜치에 "들어갑니다".
커밋이나 태그로 바로 전환하면(git switch --detach v1.0 또는 git checkout 3f2a1c9), HEAD가 커밋을 직접 가리킵니다. 이 상태를 detached HEAD라고 합니다. 여기서 코드를 보는 것은 안전하지만, 새 커밋은 어떤 브랜치에도 묶이지 않으므로 다른 곳으로 옮기면 쉽게 잃어버립니다. 이미 무언가를 고치기 시작했다면 그 자리에서 브랜치를 만드세요: git switch -c fix-from-old-version.
식에서 HEAD는 기준점으로 쓰기 좋습니다. HEAD~1은 바로 이전 커밋, HEAD~3은 세 커밋 전, HEAD^2는 병합 커밋의 두 번째 부모입니다.
브랜치 목록 보기: git branch
git branch # 로컬 브랜치 목록, 현재 브랜치에 *
git branch -v # 각 브랜치의 마지막 커밋 포함
git branch -vv # upstream과 앞뒤 차이(ahead/behind) 포함
git branch -r # 원격 브랜치(origin/...)
git branch -a # 모든 브랜치
git branch --show-current # 현재 브랜치 이름만
git branch -vv 출력 예:
feature/login 4c1e2d7 [origin/feature/login: ahead 2] 로그인 폼
* main 8b7d4e2 [origin/main] README 업데이트
old-spike 1a9b3c0 캐시 실험
이 결과를 보면 feature/login에는 아직 push하지 않은 커밋이 두 개 있고, main은 서버와 같으며, old-spike에는 서버에 짝이 되는 브랜치가 없습니다.
브랜치를 걸러 보려면 git branch --merged(현재 브랜치에 이미 병합된 것, 삭제 후보), git branch --no-merged(아직 병합되지 않은 것), git branch --list 'feature/*'(패턴 검색)를 사용하세요.
브랜치 만들기
두 가지 방법이 있습니다. git branch는 브랜치를 만들지만 현재 위치는 그대로 둡니다.
git branch feature/login # 현재 커밋을 기준으로
git branch hotfix v1.2.0 # 태그를 기준으로
git branch review 3f2a1c9 # 특정 커밋을 기준으로
대부분은 브랜치를 만들고 바로 그쪽으로 이동하고 싶을 때입니다. 이때는 git switch -c(Git 2.23에서 추가)나 예전 방식인 git checkout -b를 씁니다.
git switch -c feature/login
git switch -c hotfix origin/main # 서버의 최신 상태에서 새 브랜치 만들기
작업용 브랜치를 만들기 전에 기준을 최신으로 맞추는 습관을 들이세요. git fetch를 실행한 뒤 git switch -c feature/x origin/main을 실행합니다. 그러면 오랫동안 갱신하지 않은 로컬 main이 아니라 최신 코드에서 시작할 수 있습니다.
브랜치 이름 짓기
팀에서 많이 쓰는 규칙은 작업 유형을 접두사로 붙이고, 짧은 설명을 하이픈으로 잇는 것입니다. 예: feature/user-avatar, fix/login-timeout, chore/update-deps. 이슈 번호를 함께 넣기도 합니다: feature/SHOP-142-cart. 공백, ~ ^ : ? * [, 연속된 점 두 개, .lock으로 끝나는 이름은 사용할 수 없습니다.
브랜치 전환하기: git switch
git switch main # 기존 브랜치로 이동
git switch - # 직전 브랜치로 돌아가기
git switch feature/login # 로컬 브랜치가 없고 origin/feature/login만 있으면,
# Git이 로컬 브랜치를 만들고 원격과 연결합니다
전환하면 Git은 작업 폴더의 파일을 목표 브랜치의 버전으로 바꿉니다. 커밋하지 않은 변경은 목표 브랜치와 충돌하지 않는 한 그대로 따라옵니다. 충돌한다면 Git은 전환을 거부합니다.
error: Your local changes to the following files would be overwritten by checkout:
app.js
Please commit your changes or stash them before you switch branches.
해결 방법은 세 가지입니다. 커밋하거나, git stash로 임시 보관한 뒤 나중에 git stash pop으로 되돌리거나, git switch -m main으로 병합을 시도하며 변경을 가지고 가는 것입니다. --discard-changes 옵션은 변경을 버리므로, 정말 필요 없을 때만 쓰세요.
왜 checkout이 아니라 git switch일까요? checkout은 브랜치를 바꾸는 것도, 파일을 덮어쓰는 것도 할 수 있어서 실수하기 쉽습니다. switch는 브랜치 작업만 하므로 더 안전합니다. checkout, switch, restore를 자세히 비교한 내용은 별도의 글에서 다룹니다.
브랜치 이름 바꾸기
git branch -m new-name # 현재 브랜치 이름 바꾸기
git branch -m old-name new-name # 임의의 브랜치 이름 바꾸기
git branch -M main # 이미 있는 브랜치를 덮어쓰며 이름 바꾸기
브랜치가 이미 서버에 올라가 있었다면, 이름 변경은 로컬에서만 일어납니다. 새 이름을 push하고 예전 이름을 삭제하세요.
git push -u origin new-name
git push origin --delete old-name
브랜치 삭제하기
git branch -d feature/login # 병합된 경우에만 삭제
git branch -D old-spike # 강제로 삭제
git push origin --delete feature/login # 서버에서 삭제
-d는 작업을 잃지 않도록 막아 줍니다. 브랜치에 현재 브랜치(또는 그 upstream)에 없는 커밋이 있으면 "The branch 'x' is not fully merged" 메시지와 함께 거부합니다. -D는 묻지 않고 지웁니다. 현재 브랜치는 삭제할 수 없으므로 먼저 다른 브랜치로 이동하세요.
실수로 지웠나요? 커밋은 바로 사라지지 않습니다. git reflog에서 브랜치 마지막 커밋의 해시를 찾아 복구하세요: git branch old-spike 1a9b3c0.
서버에서 이미 사라진 원격 브랜치의 흔적을 지우려면 git fetch --prune을 실행하세요.
로컬 브랜치와 원격 브랜치 연결하기
로컬 브랜치는 upstream, 즉 비교하고 동기화할 원격 브랜치를 가질 수 있습니다. 이 연결이 있으면 git push와 git pull을 인수 없이 쓸 수 있고, git status가 "ahead/behind" 정보를 보여 줍니다.
git push -u origin feature/login # 처음 보낼 때
git branch --set-upstream-to=origin/main main # 기존 브랜치에 설정
git branch --unset-upstream # 연결 해제
브랜치 병합 간단 정리
브랜치는 나중에 합치기 위해 만듭니다. 작업이 끝나면 대상 브랜치로 이동한 뒤 내 브랜치를 병합하세요.
git switch main
git merge feature/login
브랜치를 만든 뒤로 main이 앞으로 나아가지 않았다면 Git은 fast-forward를 합니다. 병합 커밋 없이 main 포인터를 브랜치의 마지막 커밋으로 옮기기만 합니다. 양쪽 브랜치 모두 새 커밋이 있다면 3-way 병합이 일어납니다. Git은 공통 조상을 찾고, 양쪽 변경을 비교해서 부모가 두 개인 병합 커밋을 만듭니다. 팀에서는 보통 서버의 pull request로 병합하고, 로컬에서는 최신 main을 받아 오기만 합니다.
브랜치 치트시트
| 작업 | 명령 |
|---|---|
| 브랜치 목록 | git branch -vv |
| 브랜치 만들기 | git branch 이름 |
| 만들고 이동 | git switch -c 이름 |
| 전환 | git switch 이름 |
| 직전 브랜치로 | git switch - |
| 이름 바꾸기 | git branch -m 예전이름 새이름 |
| 병합된 브랜치 삭제 | git branch -d 이름 |
| 강제 삭제 | git branch -D 이름 |
| 서버에서 삭제 | git push origin --delete 이름 |
| 삭제해도 되는 브랜치 보기 | git branch --merged |
| 삭제한 브랜치 복구 | git reflog 확인 후 git branch 이름 해시 |
초보자가 자주 하는 실수
- main에서 바로 작업하기. 커밋은 했지만 push하지 않았다면, 현재 위치에 브랜치를 만든 뒤(
git branch feature/x)main을 서버 상태로 되돌립니다:git reset --hard origin/main(먼저 모든 것을 커밋했는지 확인하세요). - 오래된 main에서 브랜치 만들기. 먼저
git fetch를 실행하고,origin/main을 기준으로 브랜치를 만드세요. - detached HEAD에서 커밋하기. 전환하기 전에 브랜치를 만드세요. 이미 전환했다면
git reflog에서 커밋을 찾으세요. - 오래 사는 브랜치. 브랜치가 따로 오래 살수록 병합은 더 고통스럽습니다. main을 자주 받아 오고, 작업을 작은 단위로 병합하세요.
팀 작업에서의 브랜치
팀마다 프로세스는 다르지만, 대부분 두 가지 방식 중 하나를 씁니다. GitHub Flow에서는 언제든 배포 가능한 장기 브랜치 main 하나와, 리뷰와 CI 검사를 거쳐 pull request로 합쳐지는 짧은 기능 브랜치를 씁니다. Git Flow에는 develop, 릴리스 브랜치, hotfix 브랜치가 추가됩니다. 구조가 더 무거우며, 드물게 버전 단위로 배포하는 제품에 적합합니다.
두 방식 모두 공통 규칙이 있습니다. 브랜치 하나는 작업 하나를 위해 만들고, 며칠 안에 끝내며, pull request 전에 최신 main을 반영하고, 병합 후에는 로컬과 서버 모두에서 삭제합니다. 메인 브랜치는 저장소 설정으로 보호합니다. 직접 push와 force push를 막고, 리뷰를 거친 병합만 허용합니다.
브랜치 이력 보기
브랜치끼리의 관계는 그래프로 보는 것이 가장 쉽습니다. git log --oneline --graph --all은 모든 브랜치와 분기점을 터미널에 그려 줍니다. 브랜치에는 있지만 아직 main에 없는 커밋은 git log main..feature/login으로 보고, 파일 차이는 git diff main...feature/login으로 보세요. 세 개의 점은 공통 조상과 비교하므로 브랜치 자체의 변경만 보여 줍니다.
정리
Git 브랜치는 커밋을 가리키는 값싼 포인터이고, HEAD는 현재 브랜치를 가리키는 포인터입니다. git branch는 브랜치를 관리하고(목록, 생성, 이름 변경, 삭제), git switch는 브랜치 사이를 전환하며, git switch -c는 두 작업을 한 번에 합니다. 브랜치, HEAD, fast-forward, 3-way 병합은 Git 강의에서 실제 예제로 다룹니다.
자주 묻는 질문
Git에서 브랜치는 몇 개까지 만들 수 있나요?
사실상 제한이 없습니다. 브랜치 하나는 수십 바이트 크기의 참조이므로 용량 제한이 거의 문제가 되지 않습니다.
브랜치를 지우면 커밋도 함께 사라지나요?
바로 사라지지는 않습니다. 어디에서도 참조되지 않는 커밋은 가비지 컬렉터가 지우기 전까지 남아 있으며, git reflog로 찾을 수 있습니다. 도달할 수 없는 reflog 항목은 기본적으로 30일 이후에 정리됩니다.
현재 브랜치가 어느 브랜치에서 만들어졌는지 알 수 있나요?
Git은 이 정보를 저장하지 않습니다. 브랜치는 자신이 가리키는 커밋만 기억합니다. main과의 공통 조상은 git merge-base HEAD main으로 찾을 수 있습니다.
브랜치와 태그는 어떻게 다른가요?
브랜치는 새 커밋이 생길 때마다 앞으로 움직이고, 태그는 한 커밋에 영원히 고정되어 릴리스를 표시하는 데 씁니다.
왜 master가 아니라 main인가요?
기본 브랜치의 이름일 뿐입니다. 많은 서비스가 main으로 옮겨 갔으며, 새 저장소의 기본 이름은 git config --global init.defaultBranch main으로 설정할 수 있습니다.