coredot.today
Git, 이것만 알면 협업한다 4편 — 브랜치와 merge: 원본을 건드리지 않고 실험하는 법
블로그로 돌아가기
GitGit 입문버전 관리GitHub협업Claude Code브랜치git switchgit mergeFast-forward머지 커밋

Git, 이것만 알면 협업한다 4편 — 브랜치와 merge: 원본을 건드리지 않고 실험하는 법

브랜치는 복사본이 아니라 커밋에 붙은 '움직이는 이름표'이고, HEAD는 '지금 내가 서 있는 곳'입니다. git switch -c로 브랜치를 만들어 실험하고, git merge로 합칠 때 왜 어떤 때는 Fast-forward가 되고 어떤 때는 merge commit이 생기는지 실제 실행 결과와 인터랙티브 그래프로 보여 줍니다. 브랜치 올리기·정리·이름 규칙, 커밋 안 한 변경이 있을 때 전환이 막히는 이유까지 정리합니다.

코어닷투데이2026-10-0262분

브랜치와 merge로 배우는 Git 4편크게 보기

들어가며: "손님이 보는 사이트는 그대로 두고, 새 메뉴판을 만들어 보고 싶어요"

동네 카페 웹사이트 cafe-menu는 이제 제법 모양을 갖췄습니다. 민지는 2편에서 커밋으로 기록을 남기는 법을, 3편에서 GitHub에 올리고 도윤과 주고받는 법을 익혔습니다. 그런데 새로운 고민이 생겼습니다.

"가을 계절 메뉴를 넣어 보고 싶은데, 아직 가격도 확정이 안 됐고 디자인도 여러 번 바꿔 볼 것 같아요. 그동안 도윤 씨가 영업시간 안내도 고친다는데… 제가 만지는 게 도윤 씨 작업이나 지금 사이트를 망가뜨리면 어떡하죠?"

이 고민에 대한 git의 답이 브랜치(branch) 입니다. 브랜치를 쓰면 지금까지의 기록(main)은 그대로 둔 채 옆길에서 마음껏 실험하고, 결과가 마음에 들면 merge(병합) 로 합치고, 마음에 안 들면 그냥 버리면 됩니다.

이번 편에서 배울 것은 다음과 같습니다.

  • 브랜치가 왜 필요한지, 그리고 브랜치의 정체가 "복사본"이 아니라 커밋에 붙은 이름표라는 것
  • HEAD가 무엇인지 ("지금 내가 서 있는 곳")
  • git branch · git switch -c · git switch로 브랜치를 만들고 옮겨 다니기
  • git log --oneline --graph --all로 갈래를 눈으로 보기
  • git merge로 합치기: Fast-forward와 3-way merge(merge commit) 가 갈리는 이유
  • 다 쓴 브랜치 지우기, 브랜치를 GitHub에 올리기, 브랜치 이름 짓는 규칙
  • 커밋하지 않은 변경이 있을 때 브랜치 전환이 막히는 이유
🖥️
이 글의 터미널 출력에 대해. 모든 출력은 git 2.55로 cafe-menu 예제를 직접 실행해서 얻은 것이고, 시리즈의 다른 편과 같이 git 메시지를 영어로 보여 줍니다(커밋 메시지는 한글 그대로). 운영체제 언어가 한국어인 컴퓨터에서는 git 메시지가 한국어로 나올 수 있습니다. 예를 들어 git switch -c feature/new-menu를 실행하면 Switched to a new branch 'feature/new-menu' 대신 새로 만든 'feature/new-menu' 브랜치로 전환합니다가 나옵니다. 뜻은 같습니다. 커밋 해시(1855273 같은 7자리)는 여러분 컴퓨터에서는 다르게 나오는 것이 정상입니다.

1. 왜 브랜치가 필요할까: 복사본 없이 평행 세계 만들기

git을 모르던 시절, 민지는 실험을 이렇게 했습니다.

  • menu.md를 복사해서 menu_가을시안.md를 만든다.
  • 시안이 두 개가 되면 menu_가을시안2.md, menu_가을시안_최종.md, menu_가을시안_진짜최종.md…
  • 마음에 드는 시안이 정해지면 원본에 손으로 옮겨 붙인다. 그 사이 원본이 바뀌었다면? 어디가 달라졌는지 눈으로 비교한다.

파일 하나라면 버틸 만하지만, index.html·menu.md·README.md를 함께 고치는 실험이라면 폴더째 복사해야 하고, 두 사람이 각자 실험하면 복사본끼리 뒤섞입니다.

🗂️
복사본 방식의 문제
파일·폴더가 계속 늘어나고, 어느 것이 최신인지 헷갈리며, 합칠 때는 사람이 직접 비교해서 옮겨 붙여야 합니다. 두 사람이 동시에 실험하면 누구의 복사본이 기준인지조차 모호해집니다.
🌿
브랜치 방식
폴더는 하나 그대로. "지금부터 나는 가을 메뉴 세계에서 작업한다"고 선언(git switch -c)하면 그 뒤의 커밋은 그 세계에만 쌓입니다. 원래 세계(main)로 돌아가면(git switch main) 폴더 안 파일이 원래 모습으로 바뀝니다.
🤝
결과
실험이 성공하면 git merge 한 줄로 합치고, 실패하면 브랜치를 지우면 끝. 민지와 도윤이 각자의 브랜치에서 동시에 일해도 서로의 작업이 섞이지 않습니다.

SF 영화의 평행 세계를 떠올리면 쉽습니다. 어느 시점까지는 역사가 같다가, 거기서 갈라져 각자 다른 일이 일어나는 세계들입니다. 다만 git의 평행 세계는 언제든 다시 하나로 합칠 수 있다는 점이 영화와 다릅니다.

한 줄기에서 갈라진 두 갈래 길 위에서 민지와 도윤이 각자 작업하는 모습크게 보기

실무에서 브랜치를 쓰는 대표적인 이유는 세 가지입니다.

  1. main을 늘 멀쩡하게 유지하기 — main은 "지금 손님에게 보여 줘도 되는 상태"로 두고, 만드는 중인 것은 브랜치에서 합니다.
  2. 여러 일을 동시에 하기 — 계절 메뉴 작업 도중 급한 가격 수정이 들어와도, 새 브랜치를 따서 가격만 고쳐 먼저 합칠 수 있습니다.
  3. 리뷰 단위 만들기 — "이 브랜치의 변경을 봐 주세요"가 곧 6편에서 배울 Pull Request의 단위가 됩니다.

2. 브랜치의 정체: 커밋에 붙은 "움직이는 이름표"

여기가 이번 편에서 가장 중요한 부분입니다. 많은 입문자가 브랜치를 "프로젝트의 복사본"으로 상상하는데, git의 브랜치는 훨씬 가볍습니다.

🏷️
브랜치 = 어떤 커밋 하나를 가리키는 이름표. 파일을 복사하지 않습니다. 새 커밋을 만들면, 지금 서 있는 브랜치의 이름표가 그 새 커밋으로 자동으로 한 칸 옮겨 붙습니다. 그래서 "움직이는 이름표"입니다.
HEAD = 지금 내가 서 있는 곳. 보통은 "지금 작업 중인 브랜치 이름표"를 가리킵니다. 다음 커밋이 어느 브랜치에 쌓일지를 HEAD가 정합니다.

커밋을 줄에 꿴 구슬이라고 생각해 봅시다. 2편에서 배운 것처럼 커밋은 각자 "내 앞 커밋(부모)이 누구인지"를 기억하고 있어서, 구슬이 한 줄로 이어집니다. 브랜치는 그 구슬 중 하나에 달아 둔 포스트잇이고, HEAD는 "지금 내가 들고 있는 포스트잇"을 가리키는 화살표입니다.

10dd434
첫 커밋
→
73d176d
바닐라라테 추가
↑ 이름표 두 개가 같은 커밋에 붙어 있음
HEAD → main
지금 서 있는 곳
feature/new-menu
방금 만든 이름표

실제로 확인해 봅시다. 지금 cafe-menu에는 커밋이 두 개 있고, 브랜치는 main 하나입니다. git branch는 브랜치 목록을 보여 주고, 별표(*)가 붙은 것이 지금 서 있는 브랜치입니다.

bash
git branch
text
* main

새 이름표를 하나 만들어 봅니다. git branch <이름>은 지금 커밋에 이름표만 붙이고, 자리는 옮기지 않습니다.

bash
git branch feature/new-menu
git branch
text
  feature/new-menu
* main

이제 커밋 그래프를 봅니다. --oneline은 한 줄 요약, --graph는 갈래를 선으로, --all은 모든 브랜치를 함께 보여 달라는 뜻입니다. 이 명령은 이번 편 내내 쓸 테니 손에 익혀 두세요.

bash
git log --oneline --graph --all
text
* 73d176d (HEAD -> main, origin/main, feature/new-menu) 메뉴에 바닐라라테 추가
* 10dd434 첫 커밋: 카페 웹사이트 뼈대

괄호 안을 읽는 법이 핵심입니다.

  • HEAD -> main — 지금 나는 main에 서 있다.
  • origin/main — GitHub(원격) 쪽 main이 마지막으로 확인했을 때 여기에 있었다(3편 내용).
  • feature/new-menu — 방금 만든 이름표도 같은 커밋 73d176d에 붙어 있다.

커밋은 하나도 늘지 않았고, 파일도 복사되지 않았습니다. 이름표만 하나 늘었습니다. 브랜치를 만드는 데 1초도 안 걸리고 용량도 거의 차지하지 않는 이유가 이것입니다. 그러니 브랜치는 아끼지 말고 필요할 때마다 만들어도 됩니다.

구슬처럼 꿰인 커밋 위에 브랜치 이름표가 붙어 있고 HEAD 화살표가 그중 하나를 가리키는 그림크게 보기


3. 브랜치 만들고 옮겨 다니기: git switch

브랜치로 이동하기: git switch <이름>

이름표를 만들었으니 그리로 옮겨 서 봅시다.

bash
git switch feature/new-menu
text
Switched to branch 'feature/new-menu'
bash
git branch
text
* feature/new-menu
  main

별표가 옮겨 갔습니다. HEAD가 feature/new-menu를 가리키게 됐다는 뜻입니다. 이제부터 만드는 커밋은 feature/new-menu 이름표를 밀고 나아갑니다. main으로 돌아갈 때도 같은 명령입니다.

bash
git switch main
text
Switched to branch 'main'
Your branch is up to date with 'origin/main'.

둘째 줄 Your branch is up to date with 'origin/main'.은 "내 main과 GitHub의 main이 같은 커밋에 있다"는 뜻입니다(3편에서 본 그 메시지).

만들면서 바로 이동하기: git switch -c <이름>

실제로는 "만들고 → 이동"을 한 번에 하는 경우가 대부분입니다. -c는 create(만들기)의 약자입니다.

bash
git switch -c feature/new-menu
text
Switched to a new branch 'feature/new-menu'

(앞에서 같은 이름의 브랜치를 만들어 두었다면 fatal: a branch named 'feature/new-menu' already exists라는 오류가 납니다. 이름표는 이름이 겹칠 수 없습니다. 이 글에서는 앞의 연습용 브랜치를 지운 뒤 다시 만들었습니다. 지우는 법은 9장에서 다룹니다.)

git status의 첫 줄도 지금 어느 브랜치에 있는지 알려 줍니다. 헷갈릴 때마다 확인하는 습관을 들이세요.

bash
git status
text
On branch feature/new-menu
nothing to commit, working tree clean
↩️
작은 요령: git switch - — 하이픈 하나만 쓰면 "바로 전에 있던 브랜치"로 돌아갑니다. 두 브랜치를 왔다 갔다 할 때 편합니다.

예전 방식: git checkout

인터넷의 오래된 글이나 동료의 메모에서는 git checkout을 자주 보게 됩니다. git 2.23(2019년)에서 git switch와 git restore가 생기기 전까지는 checkout 하나가 "브랜치 이동"과 "파일 되돌리기"를 모두 맡았습니다. 기능이 너무 많아 헷갈린다는 이유로 둘로 나뉜 것입니다. 지금도 checkout은 그대로 동작하니, 보이면 아래처럼 바꿔 읽으면 됩니다.

하고 싶은 일요즘 방식 (이 시리즈)예전 방식 (같은 결과)
브랜치 목록 보기git branchgit branch
브랜치로 이동git switch maingit checkout main
새로 만들면서 이동git switch -c feature/xgit checkout -b feature/x
직전 브랜치로 돌아가기git switch -git checkout -

4. 브랜치에서 커밋하기: 갈래가 생기는 순간

이제 feature/new-menu에 서서 가을 계절 메뉴를 추가합니다. 커밋하는 방법은 2편과 완전히 같습니다. 브랜치라고 해서 다른 명령을 쓰지 않습니다.

bash
git add menu.md
git commit -m "가을 계절 메뉴 섹션 추가"
text
[feature/new-menu 06e178d] 가을 계절 메뉴 섹션 추가
 1 file changed, 4 insertions(+)

대괄호 안에 feature/new-menu가 찍혔습니다. "이 커밋은 feature/new-menu 브랜치에 쌓였다"는 뜻입니다. 하나 더 커밋합니다.

bash
git add menu.md
git commit -m "계절 메뉴에 고구마라테 추가"
text
[feature/new-menu 1855273] 계절 메뉴에 고구마라테 추가
 1 file changed, 1 insertion(+)

그래프를 보면 이름표가 어떻게 움직였는지 드러납니다.

bash
git log --oneline --graph --all
text
* 1855273 (HEAD -> feature/new-menu) 계절 메뉴에 고구마라테 추가
* 06e178d 가을 계절 메뉴 섹션 추가
* 73d176d (origin/main, main) 메뉴에 바닐라라테 추가
* 10dd434 첫 커밋: 카페 웹사이트 뼈대

feature/new-menu 이름표는 커밋할 때마다 앞으로 나아가 1855273에 있고, main 이름표는 73d176d에 그대로 남아 있습니다. HEAD가 가리키는 브랜치만 움직인다는 규칙이 그대로 보입니다.

main으로 돌아가면 파일이 "사라진다"?

이제 main으로 돌아가서 menu.md를 열어 봅니다.

bash
git switch main
cat menu.md
text
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
# 메뉴

- 아메리카노 4,000원
- 카페라테 4,500원
- 바닐라라테 5,000원

방금 추가한 계절 메뉴가 보이지 않습니다. 처음 보면 깜짝 놀라지만 사라진 것이 아닙니다. git switch는 이름표만 바꾸는 것이 아니라, 작업 폴더의 파일을 그 브랜치가 가리키는 커밋의 모습으로 바꿔 놓습니다. main은 아직 73d176d에 있으니 계절 메뉴가 없는 게 맞습니다. git switch feature/new-menu로 돌아가면 계절 메뉴가 다시 나타납니다.

⚠️
에디터가 열려 있을 때 주의. 브랜치를 바꾸면 폴더 속 파일 내용이 바뀝니다. VS Code 같은 에디터는 보통 자동으로 새 내용을 다시 읽지만, 오래된 내용이 화면에 남아 있는 채로 저장하면 방금 바꾼 브랜치에 옛 내용을 덮어쓰게 될 수 있습니다. 브랜치를 바꾼 뒤에는 에디터 화면이 새로 고쳐졌는지 확인하세요.

직접 해 보는 것이 가장 빠릅니다. 아래 놀이터에서 버튼을 눌러 커밋을 쌓고, 브랜치를 만들고, 옮겨 다녀 보세요. 어떤 버튼이 이름표를 움직이고 어떤 버튼이 커밋을 새로 만드는지 구분해 보는 것이 목표입니다.


5. 합치기 ① Fast-forward: 이름표를 앞으로 밀기만 하면 될 때

계절 메뉴가 완성됐습니다. 이제 main에 반영할 차례입니다. merge의 규칙은 한 문장입니다.

🧲
합쳐 받을 브랜치에 서서, 가져올 브랜치 이름을 부른다. main에 합치고 싶으면 먼저 git switch main, 그다음 git merge feature/new-menu. 방향을 거꾸로 하면 feature/new-menu 쪽에 main이 합쳐집니다.
bash
git switch main
git merge feature/new-menu
text
Updating 73d176d..1855273
Fast-forward
 menu.md | 5 +++++
 1 file changed, 5 insertions(+)

Fast-forward(빨리 감기)라는 단어가 보입니다. 그래프를 봅시다.

bash
git log --oneline --graph --all
text
* 1855273 (HEAD -> main, feature/new-menu) 계절 메뉴에 고구마라테 추가
* 06e178d 가을 계절 메뉴 섹션 추가
* 73d176d (origin/main) 메뉴에 바닐라라테 추가
* 10dd434 첫 커밋: 카페 웹사이트 뼈대

새 커밋은 하나도 생기지 않았습니다. main 이름표가 73d176d에서 1855273으로 앞으로 미끄러졌을 뿐입니다. 왜 이렇게 간단할 수 있었을까요?

①민지가 브랜치를 딴 뒤, main에는 아무 커밋도 생기지 않았다. main은 여전히 73d176d.
②1855273에서 부모를 따라 거슬러 가면 73d176d를 만난다. 즉 main의 끝이 feature 브랜치 끝의 조상이다. 길이 한 줄로 이어져 있다.
③그러니 합칠 것이 없다. main 이름표를 줄을 따라 1855273까지 옮기기만 하면 두 브랜치의 내용이 같아진다. 이것이 Fast-forward.

비디오를 빨리 감기 하듯, 이미 한 줄로 찍혀 있는 역사 위에서 이름표만 앞으로 감는다고 생각하면 됩니다. Fast-forward는 혼자 작업할 때, 또는 브랜치를 따서 일하는 동안 main에 아무도 손대지 않았을 때 일어납니다.


6. 합치기 ② 3-way merge: 두 갈래가 모두 앞으로 갔을 때

현실의 협업에서는 내가 브랜치에서 일하는 동안 main도 가만히 있지 않습니다. 이번에는 그 상황을 만들어 봅니다.

민지는 영업시간 안내를 넣기 위해 feature/opening-hours 브랜치를 땁니다.

bash
git switch -c feature/opening-hours
git add index.html
git commit -m "영업시간 안내 추가"
text
Switched to a new branch 'feature/opening-hours'
[feature/opening-hours 8ae18ae] 영업시간 안내 추가
 1 file changed, 1 insertion(+)

그사이 main에서는 README에 카페 위치를 적는 작업이 먼저 커밋됩니다. (실무라면 도윤이 올린 커밋을 git pull로 받아 온 상황이라고 보면 됩니다.)

bash
git switch main
git add README.md
git commit -m "README에 카페 위치 추가"
text
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
[main c4e771b] README에 카페 위치 추가
 1 file changed, 2 insertions(+)

이제 그래프에 처음으로 갈래가 보입니다.

bash
git log --oneline --graph --all
text
* c4e771b (HEAD -> main) README에 카페 위치 추가
| * 8ae18ae (origin/feature/opening-hours, feature/opening-hours) 영업시간 안내 추가
|/  
* 1855273 (origin/main) 계절 메뉴에 고구마라테 추가
* 06e178d 가을 계절 메뉴 섹션 추가
* 73d176d 메뉴에 바닐라라테 추가
* 10dd434 첫 커밋: 카페 웹사이트 뼈대

|/ 모양이 갈라진 지점입니다. 1855273에서 두 갈래로 나뉘어, 왼쪽 줄은 main(c4e771b), 오른쪽 줄은 feature/opening-hours(8ae18ae)로 뻗었습니다. (origin/feature/opening-hours는 이 브랜치를 GitHub에도 올려 두었기 때문에 보이는 것입니다. 올리는 법은 8장에서 봅니다.)

이 상태에서 합칩니다.

bash
git merge feature/opening-hours
text
Merge made by the 'ort' strategy.
 index.html | 1 +
 1 file changed, 1 insertion(+)

이번에는 Fast-forward가 아니라 Merge made by the 'ort' strategy.가 나왔습니다(ort는 git이 기본으로 쓰는 병합 방식의 이름이니 신경 쓰지 않아도 됩니다).

bash
git log --oneline --graph --all
text
*   8f9176d (HEAD -> main) Merge branch 'feature/opening-hours'
|\  
| * 8ae18ae (origin/feature/opening-hours, feature/opening-hours) 영업시간 안내 추가
* | c4e771b README에 카페 위치 추가
|/  
* 1855273 (origin/main) 계절 메뉴에 고구마라테 추가
* 06e178d 가을 계절 메뉴 섹션 추가
* 73d176d 메뉴에 바닐라라테 추가
* 10dd434 첫 커밋: 카페 웹사이트 뼈대

맨 위에 8f9176d Merge branch 'feature/opening-hours'라는 새 커밋이 생겼고, |\ 모양으로 두 갈래가 이 커밋에서 다시 만납니다. 이것이 merge commit(병합 커밋) 입니다. 일반 커밋과 다른 점은 부모가 둘이라는 것입니다.

bash
git log -1 --format='commit %h%nMerge: %p%n%n    %s'
text
commit 8f9176d
Merge: c4e771b 8ae18ae

    Merge branch 'feature/opening-hours'

Merge: 줄에 부모 두 개(c4e771b와 8ae18ae)가 찍혀 있습니다.

왜 이번에는 이름표를 밀 수 없었을까

main 이름표를 8ae18ae로 밀어 버리면 어떻게 될까요? 8ae18ae에서 부모를 거슬러 가면 1855273으로 가지, c4e771b(README 위치 추가)를 지나지 않습니다. 즉 이름표를 밀면 README 위치 추가 커밋이 main의 역사에서 떨어져 나갑니다. 두 갈래 모두 새 작업을 갖고 있으니, 둘을 모두 담은 새 커밋이 필요합니다.

① 공통 조상
1855273
두 갈래가 갈라진 곳
② main의 끝
c4e771b
README 위치 추가
③ 가져올 브랜치의 끝
8ae18ae
영업시간 안내 추가
↓ 세 지점을 비교해 "조상 대비 각자 바뀐 것"을 모두 반영
merge commit 8f9176d
부모: c4e771b + 8ae18ae

그래서 이 방식을 3-way merge(삼자 병합) 라고 부릅니다. git은 ① 공통 조상 대비 ②에서 무엇이 바뀌었고 ③에서 무엇이 바뀌었는지 비교한 뒤, 두 변경을 모두 담은 결과를 만듭니다. 이번에는 ②가 README.md를, ③이 index.html을 고쳤기 때문에 겹치는 곳이 없어 git이 알아서 합쳐 줬습니다.

두 갈래 길이 하나의 교차로에서 다시 만나는 모습, 교차로에 새 이정표가 세워져 있음크게 보기

📝
merge 도중 편집기가 열리면? 터미널에서 3-way merge를 하면 git이 merge commit 메시지(Merge branch '…')를 확인하라며 편집기를 여는 경우가 많습니다. 내용을 그대로 두고 저장한 뒤 닫으면 merge가 끝납니다. vim이 열렸다면 Esc를 누르고 :wq를 입력한 뒤 Enter를 누르세요. 편집기 없이 기본 메시지로 끝내고 싶다면 git merge --no-edit feature/opening-hours처럼 씁니다.

아래 위젯에서 세 장면을 번갈아 보며 "git의 질문"에 직접 답해 보세요. 세 번째 장면 --no-ff는 바로 다음에 설명합니다.

한눈에 비교

구분Fast-forward3-way merge
언제 일어나나브랜치를 딴 뒤 받는 쪽(main)에 새 커밋이 없을 때양쪽 모두에 새 커밋이 있을 때
git의 판단main 끝이 가져올 브랜치 끝의 조상이다어느 쪽도 다른 쪽의 조상이 아니다
새 커밋안 생김 (이름표만 이동)merge commit 1개 생김 (부모 2개)
그래프 모양일직선갈라졌다가 다시 만나는 모양
터미널 표시Fast-forwardMerge made by the 'ort' strategy.
충돌 가능성없음양쪽이 같은 곳을 고쳤다면 있음 (5편)

일부러 merge commit을 남기는 --no-ff

Fast-forward가 가능한 상황에서도 일부러 merge commit을 만들고 싶을 때가 있습니다. "이 커밋들은 README 소개 작업이라는 한 묶음으로 들어왔다"는 흔적을 그래프에 남기고 싶을 때입니다. --no-ff(no fast-forward)를 붙이면 됩니다.

bash
git switch -c docs/readme-intro
git add README.md
git commit -m "README에 카페 소개 문장 추가"
git switch main
git merge --no-ff docs/readme-intro
text
Merge made by the 'ort' strategy.
 README.md | 1 +
 1 file changed, 1 insertion(+)
bash
git log --oneline --graph --all
text
*   0d83486 (HEAD -> main) Merge branch 'docs/readme-intro'
|\  
| * e2f5bec (docs/readme-intro) README에 카페 소개 문장 추가
|/  
*   8f9176d (origin/main) Merge branch 'feature/opening-hours'
|\  
| * 8ae18ae 영업시간 안내 추가
* | c4e771b README에 카페 위치 추가
|/  
* 1855273 계절 메뉴에 고구마라테 추가
* 06e178d 가을 계절 메뉴 섹션 추가
* 73d176d 메뉴에 바닐라라테 추가
* 10dd434 첫 커밋: 카페 웹사이트 뼈대

그냥 git merge였다면 main 이름표가 e2f5bec로 미끄러지고 끝났겠지만, --no-ff 덕분에 0d83486 merge commit이 생겨 곁가지 모양이 남았습니다. 파일 내용은 두 방식이 똑같습니다. 어느 쪽을 쓸지는 팀의 취향이고, 입문 단계에서는 "그냥 git merge"로 충분합니다. 6편에서 볼 GitHub의 Pull Request 머지 버튼도 이와 비슷한 선택지(merge / squash / rebase)를 제공합니다.

💥
양쪽이 같은 줄을 고쳤다면? 이번 예제에서는 두 갈래가 서로 다른 파일을 고쳐서 git이 알아서 합쳤습니다. 하지만 민지가 menu.md의 카페라테 가격을 4,800원으로, 도윤이 같은 줄을 5,000원으로 고쳤다면 git은 어느 쪽이 맞는지 알 수 없어 멈추고 사람에게 묻습니다. 이것이 merge 충돌(conflict) 이고, 놀랄 일도 망가진 것도 아닙니다. 충돌 표시를 읽고 해결하는 법은 5편에서 차근차근 다룹니다.

7. 커밋하지 않은 변경이 있을 때 브랜치를 바꾸면

입문자가 가장 자주 막히는 장면입니다. 민지는 가격 수정용으로 fix/price 브랜치를 따서, menu.md의 카페라테 가격을 4,800원으로 고친 커밋을 하나 만들어 두었습니다(아직 merge 전). 그런데 main으로 돌아와 같은 줄을 5,000원으로 고쳐 보다가(아직 커밋 안 함), 마음을 바꿔 다시 fix/price로 옮기려고 합니다.

bash
git status --short
git switch fix/price
text
 M menu.md
error: Your local changes to the following files would be overwritten by checkout:
	menu.md
Please commit your changes or stash them before you switch branches.
Aborting

git이 전환을 거부했습니다("로컬 변경 사항이 체크아웃으로 덮어써질 것이니, 브랜치를 바꾸기 전에 커밋하거나 stash하라. 중단함"). 이유는 4장에서 본 규칙 때문입니다. 브랜치를 바꾸면 git은 작업 폴더의 파일을 그 브랜치의 모습으로 바꿔 놓습니다. 그런데 menu.md는 두 브랜치에서 내용이 다르고, 여기에 아직 커밋하지 않은 수정까지 있습니다. 그대로 전환하면 민지가 방금 타이핑한 내용이 덮어써져 사라집니다. git은 기록되지 않은 작업을 잃게 만드는 일은 하지 않으려고 멈춘 것입니다. 오류가 났지만 아무것도 망가지지 않았습니다.

반대로, 옮겨 갈 브랜치와 충돌하지 않는 변경이라면 git은 그 변경을 그대로 들고 옮겨 갑니다. 이번에는 두 브랜치에서 내용이 같은 README.md만 고친 상태로 전환해 봤습니다.

bash
git switch fix/price
git status --short
text
Switched to branch 'fix/price'
M	README.md
 M README.md

전환 메시지 아래 M README.md가 "수정한 파일을 들고 왔다"는 뜻입니다. 커밋하지 않은 변경은 어느 브랜치의 것도 아닌 상태로 작업 폴더에 떠 있다가, 커밋하는 순간 그때 서 있던 브랜치에 기록됩니다. 엉뚱한 브랜치에 커밋하지 않도록 git status 첫 줄로 현재 브랜치를 확인하세요.

전환이 거부됐을 때 선택지는 세 가지입니다.

상황할 일명령
지금 수정이 이 브랜치에 들어가야 할 작업이다커밋하고 옮긴다git add → git commit → git switch
지금 수정은 버려도 된다수정을 되돌리고 옮긴다git restore menu.md → git switch (되돌린 내용은 복구 불가)
아직 커밋하긴 이르지만 버리기도 싫다잠깐 치워 두고 옮긴다git stash — 5편에서 자세히
✅
가장 쉬운 습관: 브랜치를 바꾸기 전에 git status. nothing to commit, working tree clean(작업 폴더 깨끗함)이 보이면 안심하고 옮기면 됩니다. 뭔가 남아 있다면 위 표에서 하나를 고릅니다.

8. 브랜치를 GitHub에 올리고 받기

지금까지 만든 브랜치는 내 컴퓨터에만 있습니다. 브랜치는 자동으로 GitHub에 올라가지 않습니다. 도윤에게 보여 주거나, 6편의 Pull Request를 열려면 브랜치를 올려야 합니다.

처음 올릴 때: git push -u origin <브랜치>

bash
git push -u origin feature/opening-hours
text
To ../remote.git
 * [new branch]      feature/opening-hours -> feature/opening-hours
branch 'feature/opening-hours' set up to track 'origin/feature/opening-hours'.

(To 뒤 주소는 연습용 가짜 원격이라 ../remote.git으로 나왔습니다. 실제로는 여러분의 GitHub 저장소 주소가 보입니다.)

  • * [new branch] — 원격에 이 이름의 브랜치가 새로 생겼습니다.
  • set up to track — -u(--set-upstream) 덕분에 "내 feature/opening-hours의 짝은 GitHub의 feature/opening-hours"라고 연결됐습니다. 3편에서 main을 처음 올릴 때 했던 것과 같습니다. 이 연결이 있으면 다음부터는 이 브랜치에서 git push, git pull만 쳐도 됩니다.

각 브랜치의 짝을 보려면 git branch -vv를, 원격 브랜치까지 모두 보려면 git branch -a를 씁니다.

bash
git branch -vv
git branch -a
text
* feature/opening-hours 8ae18ae [origin/feature/opening-hours] 영업시간 안내 추가
  main                  1855273 [origin/main] 계절 메뉴에 고구마라테 추가
* feature/opening-hours
  main
  remotes/origin/feature/opening-hours
  remotes/origin/main

(이 출력은 6장에서 브랜치를 올린 직후, main에 README 커밋을 하기 전에 실행한 것이라 main이 아직 1855273에 있습니다.) 대괄호 [origin/…]가 짝(upstream)입니다. remotes/origin/…은 "GitHub에 있는 브랜치를 내 컴퓨터가 마지막으로 본 모습"입니다.

동료의 브랜치 받아 보기

반대로 도윤이 fix/typo 브랜치를 올렸다면, 민지는 git fetch로 소식을 받아 온 뒤 같은 이름으로 git switch만 하면 됩니다. git이 원격의 같은 이름 브랜치를 찾아 짝까지 맞춰 만들어 줍니다.

bash
git fetch
git switch fix/typo
text
From ../remote
 * [new branch]      fix/typo   -> origin/fix/typo
Switched to a new branch 'fix/typo'
branch 'fix/typo' set up to track 'origin/fix/typo'.

9. 다 쓴 브랜치 정리하기

merge가 끝난 브랜치는 이름표 역할을 다 했습니다. 그대로 두면 브랜치 목록이 금세 지저분해지니 정리합니다.

합쳐진 브랜치 확인: git branch --merged

bash
git branch --merged
text
  feature/new-menu
* main

지금 서 있는 브랜치(main)에 이미 모두 들어온 브랜치 목록입니다(5장의 Fast-forward 직후에 실행한 결과). 여기 나온 것은 지워도 안전합니다.

로컬 브랜치 지우기: git branch -d

bash
git branch -d feature/new-menu
text
Deleted branch feature/new-menu (was 1855273).

"커밋이 지워지는 것 아닌가요?" 하고 걱정할 수 있는데, 지워지는 것은 이름표뿐입니다. 06e178d, 1855273 커밋은 이미 main의 역사에 들어 있으니 그대로 남습니다.

그렇다면 아직 합치지 않은 브랜치를 지우려 하면 어떻게 될까요? 7장에서 쓴 fix/price 브랜치(카페라테 4,800원 커밋)는 아직 merge하지 않았습니다. 가격은 그대로 두기로 해서 이 브랜치를 지워 봅니다.

bash
git branch -d fix/price
text
error: the branch 'fix/price' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D fix/price'
hint: Disable this message with "git config set advice.forceDeleteBranch false"

git이 거부합니다. 이 브랜치에만 있는 커밋이 있어서, 이름표를 떼면 그 커밋을 찾아갈 길이 사라지기 때문입니다. 힌트대로 대문자 -D를 쓰면 강제로 지워집니다.

bash
git branch -D fix/price
text
Deleted branch fix/price (was c29987c).
🧯
-d는 안전장치, -D는 "정말 버린다". 평소에는 소문자 -d만 쓰고, 실험이 실패해서 통째로 버릴 때만 -D를 쓰세요. 실수로 -D를 했더라도 지운 직후라면 출력에 찍힌 해시(c29987c)로 되살릴 방법이 있습니다. 이 구조 방법(reflog)은 5편에서 다룹니다.

GitHub 쪽 브랜치 지우기: git push origin --delete

로컬 브랜치를 지워도 GitHub에 올렸던 브랜치는 남아 있습니다. 원격 브랜치는 이렇게 지웁니다.

bash
git branch -d feature/opening-hours
git push origin --delete feature/opening-hours
text
Deleted branch feature/opening-hours (was 8ae18ae).
To ../remote.git
 - [deleted]         feature/opening-hours

merge 결과인 main도 올려 두고, 목록을 확인하면 깔끔해졌습니다.

bash
git push
git branch -a
text
To ../remote.git
   1855273..8f9176d  main -> main
* main
  remotes/origin/main

10. 브랜치 이름 짓는 법

브랜치 이름은 자유롭게 지어도 git은 상관하지 않지만, 팀에서는 이름만 보고 무슨 작업인지 알 수 있게 약속을 정해 두면 편합니다. 가장 널리 쓰이는 방식은 종류/짧은-설명 형태입니다.

접두사쓰임cafe-menu 예시
feature/새 기능·새 콘텐츠 추가feature/new-menu, feature/opening-hours
fix/잘못된 것 고치기 (버그·오타·가격 오류)fix/price, fix/typo
docs/문서만 고치기 (README 등)docs/readme-intro
chore/기능과 무관한 잡무 (설정·정리)chore/update-gitignore

이름을 지을 때 지키면 좋은 규칙입니다.

1띄어쓰기 대신 하이픈(-). git은 브랜치 이름에 공백을 허용하지 않습니다. new menu가 아니라 new-menu.
2영어 소문자로 짧게. 한글 이름도 만들 수는 있지만, 명령어로 입력하기 불편하고 일부 도구에서 깨져 보일 수 있습니다. 3~4 단어 이내가 좋습니다.
3작업 하나에 브랜치 하나. "계절 메뉴 + 영업시간 + 가격 수정"을 한 브랜치에 몰지 말고 나눕니다. 합치기도, 리뷰하기도, 버리기도 쉬워집니다.
4팀 규칙이 있으면 그것을 따른다. 이슈 번호를 붙이는 팀(fix/42-price), 사람 이름을 앞에 붙이는 팀도 있습니다. 접두사 자체보다 일관성이 중요합니다.

feature/new-menu의 /는 폴더처럼 보이지만 이름의 일부일 뿐입니다. 다만 이 때문에 feature라는 브랜치와 feature/new-menu라는 브랜치를 동시에 가질 수는 없으니, 접두사만으로 된 이름은 피하세요.


AI 에이전트와 함께 쓸 때

🤖 Claude Code 같은 AI 코딩 도구에게 일을 맡긴다면

① "새 브랜치에서 작업해 줘"라고 먼저 말하세요. 예: "feature/dessert-menu 브랜치를 새로 만들어서 거기서 디저트 메뉴를 추가해 줘. main은 건드리지 마." 에이전트는 이런 요청을 받으면 git switch -c feature/dessert-menu 같은 명령을 직접 실행합니다. main이 깨끗하게 남아 있으니, 결과가 마음에 안 들면 브랜치째 버리면 됩니다.

② 에이전트가 무엇을 했는지 그래프로 확인하세요. git branch로 지금 어느 브랜치인지, git log --oneline --graph --all로 커밋이 어느 갈래에 쌓였는지 봅니다. 에이전트가 브랜치를 여러 개 만들었거나 엉뚱한 브랜치에 커밋했다면 여기서 바로 드러납니다.

③ 합치기 전에 "main에 없는 것"만 골라 보세요. git log --oneline main..feature/dessert-menu는 브랜치에만 있는 커밋 목록을, git diff main...feature/dessert-menu(점 세 개)는 브랜치가 갈라진 뒤 그 브랜치에서 바뀐 내용만 보여 줍니다. 이걸 읽고 괜찮을 때 사람이 git merge를 결정하는 것이 안전한 분업입니다.

④ 커밋 안 한 변경이 남은 채로 브랜치를 바꾸라고 시키지 마세요. 7장에서 본 것처럼 git이 막아 주긴 하지만, 먼저 "지금 변경사항을 커밋하고 나서 브랜치를 바꿔 줘"처럼 순서를 분명히 말하면 헷갈릴 일이 줄어듭니다.

에이전트에게 여러 작업을 동시에 맡기는 방법(worktree)과 위험 명령을 막는 설정은 7편에서 다룹니다.


자주 하는 질문(FAQ)

Q1. 브랜치를 만들면 용량이 두 배가 되나요? 아닙니다. 브랜치는 커밋 하나를 가리키는 이름표라서, 만드는 순간에는 파일을 전혀 복사하지 않습니다. 브랜치에서 새 커밋을 만들 때 바뀐 내용만큼만 기록이 늘어나는 것은 main에서 커밋할 때와 같습니다.

Q2. main이 아니라 master라고 나와요. 예전 git의 기본 브랜치 이름이 master였습니다. 오래된 저장소나 설정에서는 여전히 master가 나옵니다. 이름만 다를 뿐 역할은 같으니, 이 글의 main을 master로 바꿔 읽으면 됩니다. 새로 만드는 저장소의 기본 브랜치 이름은 git config --global init.defaultBranch main으로 정해 둘 수 있습니다.

Q3. merge했는데 feature 브랜치가 아직 남아 있어요. 합치면 없어지는 것 아닌가요? 아닙니다. merge는 받는 쪽(main)을 앞으로 옮기거나 merge commit을 만들 뿐, 가져온 브랜치의 이름표는 건드리지 않습니다. 다 쓴 브랜치는 9장처럼 git branch -d로 직접 지웁니다.

Q4. 잘못된 방향으로 merge했어요(main을 feature에 합쳤어요). feature 브랜치에 main의 최신 내용이 들어온 것뿐이라 대개는 문제가 되지 않습니다. 오히려 실무에서는 작업이 길어질 때 일부러 main을 feature 브랜치에 합쳐 최신 상태를 따라가기도 합니다. merge 자체를 취소하고 싶다면 5편의 되돌리기(revert·reset)를 보세요.

Q5. 브랜치는 몇 개까지 만들어도 되나요? git에는 사실상 제한이 없습니다. 다만 사람이 관리할 수 있어야 하니, 작업이 끝난 브랜치는 합치고 바로 지우는 습관이 좋습니다. git branch --merged로 지워도 되는 것부터 정리하세요.

Q6. GitHub에서 브랜치를 지웠는데 내 git branch -a에는 remotes/origin/…이 계속 보여요. remotes/origin/…은 "마지막으로 본 원격의 모습"이라 자동으로 갱신되지 않습니다. git fetch --prune을 실행하면 원격에서 사라진 브랜치의 기록을 정리합니다.


이번 편 요약

하고 싶은 일명령기억할 점
브랜치 목록 / 현재 위치git branch*가 HEAD. -vv는 짝, -a는 원격까지
새 브랜치 만들고 이동git switch -c feature/이름커밋은 늘지 않고 이름표만 생김
다른 브랜치로 이동git switch main작업 폴더 파일이 그 브랜치 모습으로 바뀜. 예전 방식 git checkout
직전 브랜치로git switch -두 브랜치를 오갈 때
갈래 눈으로 보기git log --oneline --graph --all괄호 안이 이름표, HEAD ->가 현재 위치
합치기git switch main → git merge feature/이름받는 쪽에 서서 가져올 쪽을 부름
일부러 merge commit 남기기git merge --no-ff 브랜치파일 결과는 같고 그래프 모양만 다름
브랜치 올리기 (처음)git push -u origin feature/이름다음부터는 git push만
동료 브랜치 받기git fetch → git switch 이름짝이 자동으로 연결됨
합친 브랜치 지우기git branch -d 이름안 합친 브랜치는 거부됨. -D는 강제
원격 브랜치 지우기git push origin --delete 이름로컬 삭제와 별개
1브랜치는 움직이는 이름표, HEAD는 지금 서 있는 곳. 커밋하면 HEAD가 가리키는 이름표만 앞으로 간다.
2main 끝이 가져올 브랜치의 조상이면 Fast-forward(이름표만 이동), 아니면 3-way merge(부모 둘인 merge commit).
3작업 하나에 브랜치 하나, 합치면 지운다. 바꾸기 전엔 git status.

더 깊이, 예를 들어 git이 이름표를 실제로 어떤 파일에 저장하는지나 git의 탄생 배경이 궁금하다면 Git 완전 정복을 함께 읽어 보세요.


다음 편 예고: 충돌이 나도 당황하지 않기

이번 편의 예제는 운이 좋았습니다. 두 갈래가 서로 다른 파일을 고쳤으니까요. 5편 「충돌 해결과 되돌리기」 에서는 민지와 도윤이 menu.md의 같은 줄을 서로 다르게 고친 상황을 일부러 만들어 봅니다. 화면에 나타나는 <<<<<<<, =======, >>>>>>> 표시를 읽고 고치는 법, 잘못한 커밋을 restore·revert·reset으로 되돌리는 법, 작업을 잠깐 치워 두는 stash, 그리고 "지운 줄 알았던" 브랜치를 되살리는 reflog까지 다룹니다.