A-2: 브랜치로 평행 우주 열고 다시 합치기
목차 32
다시 만났어요, 홍순구 튜터입니다. 지난 시간 우리는 빈 폴더를 저장소로 만들고, 첫 커밋을 찍고, git log 로 걸어온 길을 읽는 것까지 했어요. 개발자가 매일 반복하는 가장 기본적인 흐름을 익혔죠. 잘 따라오셨어요.
그때 우리 일기의 역사는 한 줄기로 곧게 뻗어 있었어요. 첫 커밋 → 둘째 커밋 → 셋째 커밋, 이렇게 일직선으로요. 그런데 살다 보면 이런 순간이 와요. "원래 일기는 그대로 두고, 완전히 새로운 형식을 한번 실험해보고 싶다. 근데 잘못되면 원래대로 돌아가고 싶어." 한 줄기 역사 위에서 막 고치자니, 실험이 망하면 되돌리기가 번거롭죠.
그래서 오늘 우리는 가지를 칩니다. 본체(main)는 안전하게 그대로 두고, 옆에 평행 우주를 하나 열어서 거기서 마음껏 실험하는 거예요. 이게 바로 브랜치(branch) 입니다. 실험이 마음에 들면 본체에 합치고(merge), 마음에 안 들면 그 우주를 통째로 지워버리면 돼요. 본체는 손도 안 댔으니까요.
그리고 오늘, 현업 신입들이 가장 식은땀 흘리는 그것을 정면으로 만납니다. 바로 충돌(conflict) 이에요. 미리 한마디 할게요 — 충돌은 무서운 게 아니에요. Git 이 고장 난 것도, 여러분이 뭘 잘못한 것도 아니에요. "여기는 둘이 다르게 고쳤으니 네가 직접 골라줘" 하고 Git 이 정중하게 물어보는 것뿐이에요. 오늘 끝나면 "아, 이거였어? 별거 아니네" 하게 될 거예요. 같이 천천히 가요.
오늘의 여정 — 가지를 치고, 다시 합치기
① 한 줄기였던 역사에 가지를 친다 — 브랜치란
② 가지를 만들고 옮겨 타기 — git branch · switch
③ 가지에서 일하고, 본체와 따로 노는지 확인
④ 가장 단순한 합치기 — fast-forward
⑤ 갈래가 벌어졌을 때 — 3-way 병합
⑥ 신입이 무서워하는 그것 — 충돌은 왜 생기나
⑦ 충돌을 손으로 풀기
⑧ 하던 일을 잠깐 접어두기 — git stash
①②③에서 브랜치를 만들고 옮겨 타며 "정말 따로 노는구나" 를 손으로 확인해요. ④⑤에서 합치는 두 가지 방법을 배우고, ⑥⑦에서 충돌을 만들고 직접 풀어봅니다. ⑧에서 "하던 일을 잠깐 접어두는" 편리한 도구로 가볍게 마무리해요.
💡 오늘 수업의 핵심 — "브랜치는 본체(main)를 건드리지 않고 평행 우주에서 마음껏 실험하게 해주고, 실험이 끝나면 git merge 로 다시 본체에 합친다. 두 갈래가 같은 곳을 고쳐 충돌이 나도, 사람이 직접 골라 안전하게 합치면 그만이다."
🎯 학습 목표
- 브랜치가 왜 필요한지(본체 보호·평행 실험)를 이해하고,
git branch·git switch로 가지를 만들고 옮겨 탑니다. - 가지에서 작업한 변경을
git merge로 본체에 합치고, fast-forward 와 3-way 병합이 어떻게 다른지 그래프로 구분합니다. - 병합 충돌이 왜 생기는지 알고, 충돌 표시를 읽어 직접 해결하며,
git stash로 하던 일을 잠깐 접어둡니다.
Step 1: "한 줄기였던 역사에 가지를 친다 — 브랜치란"
먼저 그림으로 시작할게요. 지난 시간 우리가 쌓은 역사는 이렇게 한 줄기였어요. 커밋이 시간 순으로 이어진 점들이었죠.
지난 시간까지 — 한 줄기 역사
A───B───C ← main
여기서 A, B, C 는 우리가 찍은 커밋이에요. 그리고 맨 오른쪽 ← main 이 중요해요. main 은 가장 최신 커밋 C 를 가리키는 이름표예요. 브랜치라는 게 사실 별게 아니에요. "어느 커밋을 가리키는 이름표" 일 뿐이에요. 지금까지 우리는 main 이라는 이름표 하나만 쓰고 있었던 거죠.
한 가지만 더 짚을게요. 이름표만 화살표를 갖는 게 아니에요. 커밋끼리도 서로를 가리켜요. 각 커밋은 자기 바로 앞(부모) 커밋을 가리키는 화살표를 속에 품고 있어서, git 은 최신 커밋 하나만 알면 거기서 부모를 거슬러 올라가며 전체 역사를 읽어내요. 우리가 그리는 A───B───C 화살표는 보기 좋으라고 시간 순으로 그린 거고, git 내부의 진짜 화살표는 그 반대로 자식이 부모를 가리키는 방향이에요.
이제 여기에 가지를 쳐볼게요. 본체는 그대로 두고, 새 이름표를 하나 더 만들어 실험용 우주를 여는 거예요.
이번 시간 — 가지를 친다
A───B───C ← main (안정된 본체, 손 안 댐)
\
D───E ← new-format (실험 중인 가지)
main 은 여전히 C 를 가리키며 안전하게 있고, new-format 이라는 새 가지에서 D, E 를 실험해요. 실험이 마음에 들면 main 에 합치고, 망하면 new-format 만 지우면 끝이에요. 본체는 처음부터 끝까지 안전했죠.
현업에선 이게 기본 중의 기본
현업에서 일하는 모습을 잠깐 그려볼게요. 팀의 main 브랜치는 항상 동작하는, 배포 가능한 상태로 지켜져요. 새 기능을 만들 땐 누구도 main 에서 바로 고치지 않아요. 대신 login-feature 같은 가지를 따서 거기서 만들고, 다 되면 합쳐요. 이렇게 하면 내가 기능을 만들다 코드가 반쯤 깨져 있어도, 본체 main 은 멀쩡하니 다른 동료들 작업에 지장이 없어요.
지금은 혼자 일기 저장소로 연습하지만, 이 "본체는 지키고 가지에서 실험한다" 는 감각이 나중에 팀으로 일할 때 그대로 쓰여요. 오늘 그 첫 단추를 끼우는 거예요.
💡 한 줄 정리
브랜치는 "어느 커밋을 가리키는 이름표" 일 뿐이고, 가지를 치면 본체(main)를 안전하게 둔 채 평행 우주에서 마음껏 실험한 뒤 마음에 들 때만 합칠 수 있다.
🙋 학생 질문 — "튜터님, 그냥 main 에서 바로 고치면 안 되나요? 브랜치가 꼭 필요한가요?"
혼자 연습할 땐 사실 main 에서 바로 고쳐도 당장은 큰일 안 나요. 그래서 처음엔 브랜치가 왜 필요한지 잘 안 와닿죠.
진짜 차이는 두 가지 상황에서 드러나요. 첫째, 실험이 망했을 때예요. main 에서 막 고치다 망치면 일일이 되돌려야 하는데, 가지에서 했다면 그 가지만 버리면 끝이에요. 본체는 아예 안 건드렸으니까요.
둘째, 그리고 이게 핵심인데 — 팀으로 일할 때예요. 여러 명이 같은 main 에서 동시에 고치면 서로의 반쯤 만든 코드가 뒤엉켜요. 그래서 현업에선 "각자 자기 가지에서 만들고, 다 된 것만 본체에 합친다" 가 철칙이에요. 지금 혼자 연습으로 이 습관을 들여두면, 팀에 들어갔을 때 자연스럽게 몸에 배어 있어요.
Step 2: "가지를 만들고 옮겨 타기 — git branch · git switch"
그림을 머리에 넣었으니 직접 가지를 쳐봐요. 지난 시간 만든 my-diary 저장소에서 이어가요. 먼저 지금 어떤 브랜치들이 있는지 물어봐요.
git branch
# 출력 예시:
# * main
* main 에서 별표(*)는 "지금 내가 여기 서 있다" 는 표시예요. 아직 가지가 main 하나뿐이죠. 이제 새 가지를 만들어요.
git branch new-format
조용히 만들어졌어요. 다시 목록을 보면 둘이 됐어요.
git branch
# 출력 예시:
# new-format
# * main
가지는 생겼는데, 별표는 아직 main 에 있죠? 만든 것과 그리로 옮겨 타는 건 다른 일이에요. new-format 으로 갈아타볼게요.
git switch new-format
# 출력 예시:
# Switched to branch 'new-format'
이제 별표가 옮겨갔어요.
git branch
# 출력 예시:
# * new-format
# main
방금 무슨 일이 일어난 걸까요? 그림으로 보면 이래요. 아직 새 커밋을 안 찍었으니 두 이름표는 같은 커밋을 가리켜요. 달라진 건 "지금 내가 선 곳" 뿐이에요.
switch 전: 지금 위치 → main
switch 후: 지금 위치 → new-format
A───B───C ← main, new-format 이 둘 다 여기를 가리킴
(아직 안 갈라짐. '지금 위치' 만 new-format 으로 옮겨감)
한 줄로 줄이는 단축법, 그리고 checkout 이야기
"만들고 → 옮겨 타기" 두 번이 번거롭죠? -c 옵션(create)을 쓰면 만들면서 곧장 갈아타기 가 한 줄로 돼요. 현업에서 가장 많이 쓰는 형태예요.
git switch -c new-format
# 출력 예시:
# Switched to a new branch 'new-format'
여기서 한 가지 짚고 갈게요. 오래된 자료나 동료의 화면을 보면 git checkout 이라는 명령을 자주 볼 거예요. 예전엔 하나의 checkout 명령이 너무 많은 일(브랜치 이동 + 파일 되돌리기)을 했어요.
헷갈리기 쉬워서, 요즘 Git 은 이걸 둘로 쪼갰어요 — 브랜치를 옮겨 타는 건 git switch, 파일을 되돌리는 건 git restore(다음 시간에 배워요). 그래서 우리는 더 또렷한 switch 를 1순위로 쓸게요.
| 요즘 권장 🌟 | 예전 방식 (지금도 동작함) |
|---|---|
git switch new-format |
git checkout new-format |
git switch -c new-format |
git checkout -b new-format |
checkout 도 여전히 잘 동작하니, 동료가 쓰는 걸 봐도 "아, switch 와 같은 거구나" 하면 돼요.
GUI 로는 — GitHub Desktop 이나 VS Code 화면 왼쪽 아래(또는 위쪽)에 현재 브랜치 이름이 늘 떠 있어요. 그걸 클릭하면 브랜치 목록이 뜨고, "New Branch" 로 만들거나 목록에서 골라 갈아탈 수 있어요. 방금 우리가 친 git branch + git switch 와 똑같은 일이에요.
💡 한 줄 정리
git branch 이름 으로 가지를 만들고 git switch 이름 으로 옮겨 타며(git switch -c 로 한 번에), 옮겨 타기 전까지는 새 가지도 같은 커밋을 가리킬 뿐 "지금 내가 선 곳" 만 바뀐다.
🙋 학생 질문 — "튜터님, 브랜치 이름은 아무렇게나 지어도 되나요?"
네, 규칙은 자유로워요. 다만 나중에 봐도 무슨 가지인지 알 수 있게 짓는 게 좋아요. 커밋 메시지를 잘 쓰는 거랑 똑같은 이치예요.
현업에선 보통 "무엇을 하는 가지인지" 가 드러나게 지어요. login-feature(로그인 기능), fix-typo(오타 수정), 2026-redesign(개편) 처럼요.
팀에 따라 feature/login, fix/login-error 같이 슬래시로 분류를 붙이는 약속을 두기도 하고요. 띄어쓰기는 피하고(- 나 / 로 연결) 영어로 짧게 짓는 게 무난해요. 지금 연습 단계에선 new-format 처럼 알아보기 쉬운 이름이면 충분해요.
Step 3: "가지에서 일하고, 본체와 따로 노는지 확인"
가지를 만들었으니 거기서 진짜로 일을 해봐요. 정말 본체와 따로 노는지 두 눈으로 확인하는 게 이번 Step 의 목표예요. new-format 가지에 서 있는 상태에서, 일기에 새 형식을 한 줄 더해 커밋해요.
git switch new-format
echo "[새 형식] 2026-06-15 / 날씨: 맑음 / 기분: 좋음" >> diary.txt
git add diary.txt
git commit -m "일기에 새로운 형식(날짜/날씨/기분) 시험 적용"
자, 이제 가지에서 커밋을 하나 찍었어요. 그림으로는 이렇게 갈라진 거예요.
A───B───C ← main (그대로 C 에 머물러 있음)
\
D ← new-format ← 지금 위치 (방금 찍은 커밋)
여기서 가장 중요한 확인을 해봐요. 본체(main)로 돌아가면, 방금 더한 줄이 정말 사라져 있을까요?
git switch main
cat diary.txt
# 출력 예시:
# 오늘 Git 을 처음 배웠다.
# 둘째 날, git add 와 commit 을 익혔다.
# (방금 더한 '[새 형식]' 줄이 안 보임!)
신기하죠? main 으로 오니 diary.txt 가 새 형식 줄을 더하기 전 모습으로 돌아와 있어요. 파일을 누가 지운 게 아니에요. new-format 가지의 변경은 그 우주에만 있고, main 우주는 그 일을 모르는 거예요. 진짜 평행 우주처럼 따로 노는 거죠.
다시 new-format 으로 가면 그 줄이 되살아나요.
git switch new-format
cat diary.txt
# 출력 예시:
# 오늘 Git 을 처음 배웠다.
# 둘째 날, git add 와 commit 을 익혔다.
# [새 형식] 2026-06-15 / 날씨: 맑음 / 기분: 좋음 ← 돌아옴!
git switch 로 가지를 옮길 때마다 작업 폴더의 파일 내용이 그 가지의 모습으로 통째로 바뀌어요. 이게 브랜치의 핵심이에요. 한 폴더 안에서 여러 평행 우주를 오가며 일하는 거죠.
💡 한 줄 정리
가지에서 커밋한 변경은 그 가지에만 존재하고, git switch 로 본체에 돌아가면 작업 폴더가 본체의 모습으로 통째로 바뀌어 두 브랜치가 서로 독립적으로 따로 논다.
🙋 학생 질문 — "튜터님, 브랜치를 옮길 때마다 파일이 바뀌면, 지금 하던 작업은 어떻게 되나요?"
아주 좋은 질문이에요. 핵심을 찔렀어요. 결론부터 말하면, 커밋해둔 변경은 안전하게 그 가지에 남아요. 옮겨 다녀도 사라지지 않아요. 그래서 방금 본 것처럼 다시 돌아오면 그대로 되살아나죠.
문제는 아직 커밋 안 한, 고치던 중인 변경이에요. 이게 남아 있으면 Git 이 "이거 정리 안 하고 가면 헷갈리는데?" 하며 브랜치 이동을 막거나 경고할 때가 있어요. 그럴 땐 두 가지 길이 있어요 — 지금 하던 걸 커밋해서 마무리하거나, 아니면 잠깐 접어두는 방법이 있어요. 이 "잠깐 접어두기" 가 바로 오늘 Step 8 에서 배울 git stash 예요. 조금만 기다려 주세요.
Step 4: "가장 단순한 합치기 — fast-forward"
실험이 마음에 들었다고 해봐요. 이제 new-format 의 변경을 본체 main 에 합칠(merge) 차례예요. 합치기엔 두 가지 모양이 있는데, 오늘은 가장 단순한 것부터 봐요.
규칙은 간단해요. 합쳐 받을 쪽(본체)으로 먼저 가서, 가져올 가지를 merge 한다. 우리는 main 에 new-format 을 합칠 거니까, main 으로 가서 merge 해요.
git switch main
git merge new-format
# 출력 예시:
# Updating c3d4e5f..3c4d5e6
# Fast-forward
# diary.txt | 1 +
# 1 file changed, 1 insertion(+)
출력에 Fast-forward 라는 말이 보이죠? 이게 가장 단순한 병합이에요. 왜 "빨리 감기(fast-forward)" 라고 부를까요? 그림을 보면 단번에 이해돼요.
병합 전 — main 은 C 에 그대로, 가지에 D 가 있음
A───B───C ← main
\
D ← new-format
git merge new-format (fast-forward) — main 이름표가 D 로 미끄러져 따라감
A───B───C───D ← main, new-format
(새 커밋 안 생김. main 이름표만 앞으로 이동)
main 이 가지를 친 뒤로 한 발짝도 안 움직였기 때문에, 합칠 게 따로 없어요. Git 은 그냥 main 이름표를 D 로 쓱 옮기기만 하면 돼요. 마치 비디오를 빨리 감듯이요. 그래서 새 병합 커밋도 안 생기고, 역사가 한 줄로 깔끔하게 이어져요.
합쳤으니 이제 new-format 가지는 역할을 다했어요. 가지를 정리해요.
git branch -d new-format
# 출력 예시:
# Deleted branch new-format (was 3c4d5e6).
-d 는 delete 예요. "어, 가지를 지워도 괜찮나요? 작업이 사라지는 거 아니에요?" 걱정 마세요. new-format 의 커밋 D 는 이미 main 에 합쳐졌으니, 이름표만 떼는 거예요. 내용은 main 에 멀쩡히 남아 있어요.
혹시 아직 안 합친 가지를 -d 로 지우려 하면, Git 이 "이거 합친 적 없는데 정말 지울 거야?" 하고 막아줘요. 그만큼 안전장치가 있다는 뜻이에요. (정말 버리고 싶을 때 강제로 지우는 법, 그리고 실수로 지운 걸 되살리는 법은 다음 시간에 배워요.)
GUI 로는 — GitHub Desktop 의 "Branch" 메뉴에서 "Merge into current branch" 를 고르면 방금 한 merge 와 똑같아요. 합친 뒤 브랜치 목록에서 가지를 우클릭해 "Delete" 하면 git branch -d 고요.
💡 한 줄 정리
합쳐 받을 본체로 가서 git merge 가지 를 하면 합쳐지고, 본체가 가지를 친 뒤 움직이지 않았다면 이름표만 앞으로 옮기는 fast-forward 로 새 커밋 없이 한 줄로 이어진다.
🙋 학생 질문 — "튜터님, 합친 가지를 꼭 지워야 하나요? 그냥 둬도 되지 않나요?"
안 지워도 큰일은 안 나요. 다만 안 지우고 쌓이면 나중에 지저분해져요.
브랜치는 이름표라 거의 공짜예요. 그런데 다 쓴 가지를 계속 두면, git branch 를 쳤을 때 old-test, temp, new-format... 안 쓰는 이름표가 잔뜩 떠서 "지금 살아있는 가지가 뭐지?" 하고 헷갈려요. 현업 팀에선 "합친 기능 브랜치는 바로 지운다" 가 보통의 약속이에요. 깔끔한 작업대를 유지하는 거죠. 합쳤으면 내용은 본체에 다 있으니, 빈 이름표는 미련 없이 떼어내는 게 좋아요.
Step 5: "갈래가 벌어졌을 때 — 3-way 병합"
방금 fast-forward 는 본체가 안 움직였을 때의 이야기였어요. 그런데 현업에선 더 흔한 상황이 있어요. 내가 가지에서 일하는 동안, 본체도 따로 전진하는 경우예요. 팀에선 내가 기능을 만드는 사이 동료가 main 에 다른 걸 합치니까요.
직접 그 상황을 만들어봐요. main 에서 새 가지 add-weather 를 따서 날씨 파일을 추가해요.
git switch -c add-weather
echo "맑음 / 22도" > weather.txt
git add weather.txt
git commit -m "weather.txt 추가: 날씨 기록 파일 분리"
이제 본체도 전진시켜요. main 으로 돌아가 다른 파일을 고쳐 커밋해요. (가지에서 만진 weather.txt 가 아니라 diary.txt 를 건드려요 — 일부러 서로 다른 파일을.)
git switch main
echo "둘째 줄: 오늘은 브랜치를 배웠다." >> diary.txt
git add diary.txt
git commit -m "diary: 브랜치 학습 기록 추가"
이제 양쪽이 각자 다른 방향으로 한 발짝씩 갔어요. 이 상태에서 합치면 어떻게 될까요?
git merge add-weather
# (편집기가 열리며 병합 커밋 메시지를 묻는다 — 기본값 "Merge branch 'add-weather'" 그대로 저장)
# 출력 예시:
# Merge made by the 'ort' strategy.
# weather.txt | 1 +
# 1 file changed, 1 insertion(+)
이번엔 Fast-forward 가 아니라 Merge made by ... 가 떴어요. 그리고 fast-forward 때와 달리, 병합 커밋(merge commit) 이라는 새 커밋이 하나 생겼어요. 왜 그럴까요? 그림으로 두 경우를 나란히 봐요.
① fast-forward — 본체가 안 움직였을 때
가지를 친 뒤 main 이 그대로면 이름표만 앞으로.
A───B───C───D ← 한 줄로 깔끔
② 3-way 병합 — 양쪽이 각자 전진했을 때
main 도 F 로 가고, 가지도 E 로 갔다면 합류점 M 이 생긴다.
A───B───C───F───M ← main
\ /
E───┘ (M = 병합 커밋)
①은 본체가 안 움직여서 그냥 따라가면 됐죠. 그런데 ②는 main(F)과 가지(E)가 갈라진 두 갈래예요. 이 둘을 하나로 모으려면, "양쪽을 다 품은 새 합류 지점" 이 필요해요. 그게 병합 커밋 M 이에요. Git 은 갈라지기 전 공통 조상(C)과 양쪽 끝(F, E) 세 지점을 비교(three-way) 해서 합치기 때문에 3-way 병합이라고 불러요.
그리고 현업 팀에선 이 3-way 병합이 사실 더 흔해요. 내가 가지에서 기능을 만드는 동안 동료들도 main 에 자기 작업을 계속 합치니까, 합칠 때쯤이면 본체가 이미 저만치 전진해 있거든요. 그래서 양쪽이 갈라진 3-way 병합이 자연스럽게 일어나요. 다음 과목들에서 코드를 GitHub 에 올려 협업할 때 병합 커밋이 자주 보이는 건 그래서예요.
겁먹을 거 없어요. 서로 다른 파일을 고쳤다면(우리처럼 weather.txt vs diary.txt), Git 이 알아서 둘 다 살려서 깔끔하게 합쳐줘요. 병합 커밋 메시지는 기본값을 그냥 써도 충분하고요. 합친 역사를 한번 볼까요?
git log --oneline --graph
# 출력 예시:
# * 8a9b0c1 Merge branch 'add-weather'
# |\
# | * 5e6f7a8 weather.txt 추가: 날씨 기록 파일 분리
# * | 2b3c4d5 diary: 브랜치 학습 기록 추가
# |/
# * 3c4d5e6 일기에 새로운 형식(날짜/날씨/기분) 시험 적용
--graph 를 붙이면 갈래가 갈라졌다 합쳐지는 모양이 글자로 그려져요. 왼쪽의 * 와 선들이 아까 그림이랑 똑 닮았죠?
💡 한 줄 정리
본체와 가지가 각자 전진해 갈라졌을 땐 양쪽을 품는 병합 커밋(M) 이 새로 생기는 3-way 병합이 일어나고, 서로 다른 곳을 고쳤다면 Git 이 알아서 둘 다 살려 합쳐준다.
🙋 학생 질문 — "튜터님, 병합 커밋이 자꾸 생기면 역사가 지저분해지지 않나요?"
날카로운 지적이에요. 맞아요, 갈래가 많은 프로젝트에선 병합 커밋이 잔뜩 쌓여 역사가 복잡해 보일 수 있어요.
그런데 관점을 바꾸면, 병합 커밋은 "이 시점에 이 가지가 본체로 합쳐졌다" 는 사실을 그대로 기록하는 거예요. 거짓말 안 하고 있었던 일을 남기는 거죠. 그래서 "언제 무엇이 합쳐졌나" 를 추적하기엔 오히려 좋아요.
반대로, 이 갈래 흔적을 안 남기고 역사를 한 줄로 펴서 더 깔끔하게 만드는 방법도 있어요. 바로 rebase 인데, 장단점이 뚜렷해서 통째로 따로 배울 만큼 중요해요(A-7 에서 깊게 다뤄요). 입문 단계에선 사실 그대로 남기는 merge 만으로 충분하니, 지금은 "갈라졌으면 병합 커밋이 생긴다" 만 알면 돼요.
Step 6: "신입이 무서워하는 그것 — 충돌은 왜 생기나"
자, 드디어 오늘의 클라이맥스예요. 현업 신입들이 첫 주에 가장 식은땀 흘리는 그것, 충돌(conflict) 입니다. 먼저 안심부터 시키고 갈게요. 충돌은 에러가 아니에요. 여러분이 뭘 잘못한 것도 아니에요. 그냥 Git 이 혼자 판단하기 곤란한 상황에서 "이건 사람인 네가 골라줘" 하고 정중히 멈춰 서는 것뿐이에요.
언제 생길까요? Step 5 에선 양쪽이 서로 다른 파일을 고쳐서 Git 이 알아서 합쳤죠. 충돌은 그 반대예요. 두 갈래가 같은 파일의 같은 줄을 서로 다르게 고쳤을 때 생겨요. Git 입장에선 "둘 중 누구 말을 들어야 하지?" 알 수가 없는 거죠.
직접 만들어봐요. 먼저 본체에 제목 파일을 하나 만들어 공통 출발점을 깔아요.
git switch main
echo "제목: 나의 일기" > title.txt
git add title.txt
git commit -m "title.txt 추가: 일기 제목"
이제 가지를 따서, 가지에서는 제목을 "성장 일기" 로 바꿔요.
git switch -c edit-title
echo "제목: 나의 성장 일기" > title.txt
git add title.txt
git commit -m "제목을 '성장 일기'로 변경"
그리고 본체로 돌아와, 같은 줄을 "코딩 일기" 로 다르게 바꿔요.
git switch main
echo "제목: 나의 코딩 일기" > title.txt
git add title.txt
git commit -m "제목을 '코딩 일기'로 변경"
같은 title.txt 의 같은 줄을, 한쪽은 "성장 일기", 한쪽은 "코딩 일기" 로 고친 거예요. 이제 합쳐봐요.
git merge edit-title
# 출력 예시:
# Auto-merging title.txt
# CONFLICT (content): Merge conflict in title.txt
# Automatic merge failed; fix conflicts and then commit the result.
CONFLICT 가 떴어요! 처음 보면 가슴이 철렁하죠. 그런데 마지막 줄을 읽어보세요. "fix conflicts and then commit the result" — "충돌을 고치고 나서 커밋해" 라고 Git 이 친절하게 다음 할 일을 알려주고 있어요. 무서운 게 아니라 안내문이에요.
파일을 열어보면 Git 이 표시를 남겨놨어요.
cat title.txt
# 출력 예시:
# <<<<<<< HEAD
# 제목: 나의 코딩 일기
# =======
# 제목: 나의 성장 일기
# >>>>>>> edit-title
처음엔 외계어 같지만, 읽는 법은 정말 간단해요.
<<<<<<< HEAD
제목: 나의 코딩 일기 ← 지금 내 브랜치(main, HEAD)의 내용
=======
제목: 나의 성장 일기 ← 합치려는 브랜치(edit-title)의 내용
>>>>>>> edit-title
─── 가운데 ======= 를 경계로, 윗칸은 '나', 아랫칸은 '상대' ───
─── 둘 중 무엇을 남길지 사람이 직접 고른다 ───
<<<<<<< 부터 ======= 까지가 현재 내 브랜치(HEAD, 여기선 main) 의 내용, ======= 부터 >>>>>>> 까지가 합치려는 가지(edit-title) 의 내용이에요. Git 이 "이 두 개가 부딪혔어. 어느 걸 남길래? 아니면 둘을 섞을래?" 하고 양쪽을 다 보여주는 거예요. 결정은 사람 몫이고요.
이번 Step 에선 충돌이 왜, 어떻게 생기는지, 그리고 이 표시를 읽는 법까지 익혔어요. 실제로 고르고 마무리하는 건 바로 다음 Step 에서 해요.
💡 한 줄 정리
충돌은 에러가 아니라 두 갈래가 같은 파일의 같은 줄을 다르게 고쳐 Git 이 사람에게 결정을 넘기는 상황이고, <<<<<<< / ======= / >>>>>>> 표시로 내 쪽과 상대 쪽 내용을 나란히 보여준다.
🙋 학생 질문 — "튜터님, 충돌이 나면 제가 뭔가 크게 잘못한 건가요? 너무 무서워요."
전혀 아니에요. 이 말을 꼭 해주고 싶었어요 — 충돌은 협업하다 보면 누구에게나, 자연스럽게 생기는 일이에요. 오히려 충돌이 났다는 건 "두 사람이 같은 곳을 열심히 고쳤다" 는 증거예요.
생각해보면 충돌은 Git 이 우리를 지켜주는 거예요. 지난 시간에 얘기했죠? 구글 드라이브 같은 클라우드는 둘이 같은 파일을 고치면 한쪽을 조용히 덮어써서, 누군가의 작업이 소리 없이 사라진다고요.
Git 은 그걸 안 해요. 대신 "여기 둘이 부딪혔으니 네가 직접 확인하고 골라" 하고 멈춰서 양쪽을 다 보여줘요. 덕분에 누구의 작업도 함부로 사라지지 않죠. 그러니 충돌을 만나면 "아 Git 이 나 대신 사고를 막아줬구나" 하고 차분히 다음 Step 처럼 풀면 돼요.
Step 7: "충돌을 손으로 풀기"
충돌 표시를 읽었으니, 이제 풀 차례예요. 푸는 건 생각보다 싱거워요. 딱 두 단계예요 — ① 파일을 열어 원하는 모습으로 고치고(표시 지우기), ② add 하고 commit 한다.
지금 title.txt 는 이 상태죠.
<<<<<<< HEAD
제목: 나의 코딩 일기
=======
제목: 나의 성장 일기
>>>>>>> edit-title
여기서 우리가 할 일은, <<<<<<<, =======, >>>>>>> 세 줄을 모두 지우고, 최종적으로 남기고 싶은 내용만 남기는 거예요. "코딩 일기" 를 고를 수도, "성장 일기" 를 고를 수도, 아니면 둘을 합쳐 새로 쓸 수도 있어요. 우리는 둘을 합쳐볼게요. 편집기로 파일을 이렇게 고쳐요.
제목: 나의 코딩 성장 일기
표시 세 줄이 싹 사라지고, 깔끔한 한 줄만 남았죠? 이제 충돌이 풀렸는지 상태를 물어봐요.
git status
# 출력 예시:
# On branch main
# You have unmerged paths.
# (fix conflicts and run "git commit")
# Unmerged paths:
# both modified: title.txt
both modified — "양쪽이 다 고친 파일" 이라는 표시예요. 이제 해결한 파일을 add 로 담아 "이거 다 풀었어" 라고 Git 에게 알려주고, 커밋으로 마무리해요.
git add title.txt
git commit -m "제목 충돌 해결: '코딩 성장 일기'로 통합"
끝났어요! 방금 그 커밋이 바로 병합 커밋이에요. 충돌을 직접 풀어 합류 지점을 만든 거죠. git log --oneline --graph 로 보면 갈래가 다시 하나로 모인 게 보여요. 어때요, 막상 해보니 "별거 아니네" 싶죠?
⚠️ 참고: 커밋 메시지에
-m을 안 붙이고 그냥git commit만 쳐도 돼요. 그러면 Git 이Merge branch 'edit-title'같은 기본 메시지를 미리 채운 편집기를 띄워줘요. 그대로 저장해도 되고요.
도망칠 구멍도 있어요 — git merge --abort
충돌을 보자마자 "아, 지금 이거 풀 상황이 아닌데" 싶을 때도 있어요. 그럴 땐 병합을 시작하기 전으로 통째로 되돌릴 수 있어요.
git merge --abort
# (병합을 취소하고, merge 치기 직전의 깨끗한 상태로 되돌아감)
이 한 줄이면 충돌 표시고 뭐고 싹 사라지고, merge 를 누르기 전 상태로 돌아가요. 그러니 충돌이 나도 절대 갇히는 게 아니에요. 언제든 빠져나와 마음을 가다듬고 다시 시도하면 돼요. 이렇게 "잘못돼도 되돌릴 수 있다" 는 안전망이 있다는 게 중요해요 — 사실 다음 시간 전체가 이 "되돌리기" 이야기예요. 실수한 변경, 잘못 찍은 커밋, 심지어 지워버린 것까지 되살리는 구조대를 배울 거예요.
GUI 로는 — VS Code 는 충돌 난 파일을 열면 Accept Current(내 쪽) · Accept Incoming(상대 쪽) · Accept Both(둘 다) 버튼을 충돌 위에 직접 띄워줘요. 클릭 한 번으로 고르고, 저장한 뒤 커밋하면 끝이에요. 명령으로 원리를 알아두면 이 버튼들이 무슨 일을 하는지 바로 읽혀요.
💡 한 줄 정리
충돌은 <<<<<<< ======= >>>>>>> 표시를 지우고 원하는 내용만 남긴 뒤 git add → git commit 으로 풀고, 풀기 싫으면 git merge --abort 로 병합 직전 상태로 안전하게 되돌릴 수 있다.
🙋 학생 질문 — "튜터님, 표시(<<<<<< 같은 거)를 깜빡하고 안 지운 채 커밋하면 어떻게 되나요?"
흔한 실수예요. 그리고 다행히 큰 사고는 아니에요. 다만 그 <<<<<<< 같은 표시가 파일 안에 글자로 그대로 남아버려요. 일기 파일이면 그냥 이상한 줄이 남는 정도지만, 코드 파일이라면 그 줄 때문에 프로그램이 안 돌아가요.
그래서 충돌을 풀 땐 표시 세 줄을 빠짐없이 지웠는지 꼭 확인하는 습관이 중요해요. 좋은 점검법이 있어요 — 커밋하기 전에 지난 시간 배운 git diff 로 한번 훑어보거나, 파일에서 <<<<<<< 를 검색해보는 거예요. 하나도 안 걸리면 깨끗하게 푼 거예요. VS Code 같은 편집기는 이 표시가 남아 있으면 눈에 띄게 색칠해줘서 놓치기 어렵게 도와주기도 해요.
Step 8: "하던 일을 잠깐 접어두기 — git stash"
마지막은 현업에서 정말 자주 쓰는 편리한 도구예요. 이런 상황을 상상해봐요. 일기 새 기능을 한창 만드는 중인데, 아직 커밋하긴 일러요(반쯤 했거든요). 그런데 갑자기 "급한 거 하나만 봐줘" 하고 다른 일이 끼어들어요. 다른 브랜치로 옮겨가야 하는데, 하던 작업은 어쩌죠? 커밋하자니 반쪽짜리고, 버리자니 아깝고.
이럴 때 쓰는 게 git stash 예요. "하던 일을 잠깐 서랍에 넣어두기" 라고 생각하면 돼요. 고치던 중인 변경을 일단 보관함에 치워서 작업 폴더를 깨끗하게 만드는 거죠.
# diary.txt 를 한창 고치는 중... 아직 커밋하긴 이른 상태
git stash
# 출력 예시:
# Saved working directory and index state WIP on main: 8a9b0c1 ...
이제 git status 를 보면 작업 폴더가 깨끗해요. 고치던 변경이 보관함으로 들어가 잠시 사라진 거예요. 이 상태로 자유롭게 다른 브랜치로 옮겨 급한 일을 처리하면 돼요. 보관함에 뭐가 들었는지는 이렇게 봐요.
git stash list
# 출력 예시:
# stash@{0}: WIP on main: 8a9b0c1 ...
급한 일을 다 끝냈으면, 접어뒀던 작업을 다시 꺼내요.
git stash pop
# (보관함에 넣어둔 변경이 작업 폴더로 되살아나고, 보관함에선 빠짐)
pop 하면 아까 고치던 그 모습 그대로 돌아와요. 하던 일을 이어서 하면 되죠. 흐름을 그림으로 보면 이래요.
git stash : 고치던 것을 보관함에 잠깐 치워두기 (작업 폴더 깨끗해짐)
git stash pop : 보관함에서 다시 꺼내 작업 폴더로
[ 작업 폴더 ] ---- stash ----> [ 보관함 ]
[ 작업 폴더 ] <---- pop ------ [ 보관함 ]
한 가지 알아둘 점. git stash pop 으로 꺼낼 때 그새 그 파일이 바뀌어 있으면 충돌이 날 수 있어요. 이땐 앞에서 배운 병합 충돌과 똑같이 <<<<<<< 표시를 정리하면 돼요. 그리고 보관함은 여러 개를 쌓을 수 있어서, git stash list 로 목록을 보고 특정 항목만 꺼낼 땐 git stash apply stash@{1} 처럼 번호로 골라요.
GUI 로는 — GitHub Desktop 은 "Branch > Stash changes" 로 접어두고, "Stashed changes" 에서 다시 꺼낼 수 있어요. VS Code 도 Source Control 메뉴에 Stash 항목이 있고요.
💡 한 줄 정리
git stash 는 아직 커밋하기 이른 변경을 잠깐 보관함에 치워 작업 폴더를 깨끗이 비우고, git stash pop 으로 다시 꺼내 이어서 작업하게 해주는 "임시 서랍" 이다.
🙋 학생 질문 — "튜터님, stash 랑 커밋이랑 뭐가 달라요? 그냥 커밋해두면 안 되나요?"
좋은 비교예요. 둘 다 "지금 변경을 어딘가 보관한다" 는 점은 비슷해요. 차이는 "역사에 남느냐" 예요.
커밋은 저장소의 영구 역사에 한 줄로 남아요. 그래서 "기능 A 완성" 처럼 의미가 매듭지어진 변경에 어울려요.
반면 stash 는 역사에 안 남는 임시 서랍이에요. "아직 반쯤 했고 커밋할 만큼 정리되진 않았는데, 잠깐 치워둬야 하는" 어정쩡한 변경에 딱이죠. 만약 이걸 어설프게 커밋하면 "WIP", "작업중" 같은 지저분한 커밋이 역사에 남아버려요(지난 시간 배운 좋은 커밋 메시지 정신에 어긋나죠). 그래서 "잠깐 치워둘 거면 stash, 매듭지어 남길 거면 commit" 으로 구분하면 돼요.
마무리
오늘 정말 큰 산을 넘었어요. 한 줄기였던 역사에 가지를 치고, 평행 우주에서 실험하고, 다시 본체에 합치고, 그리고 신입들이 가장 무서워한다는 충돌까지 직접 만들어 풀어봤어요. 어때요, 막상 해보니 충돌도 "별거 아니네" 싶죠? 그 감각이 오늘의 가장 큰 수확이에요.
오늘 배운 핵심 세 가지
- 💡 하나 — 브랜치는 평행 우주다.
git switch -c로 가지를 따면 본체(main)를 안전하게 둔 채 실험할 수 있고, 가지를 옮길 때마다 작업 폴더가 그 가지의 모습으로 바뀐다. - 💡 둘 — 합치기엔 두 모양이 있다. 본체가 안 움직였으면 이름표만 옮기는 fast-forward, 양쪽이 갈라졌으면 합류점(병합 커밋)을 만드는 3-way 병합이 일어난다.
- 💡 셋 — 충돌은 에러가 아니라 안내다. 같은 줄을 다르게 고치면 충돌이 나고,
<<<<<<< ======= >>>>>>>표시를 지워 원하는 내용만 남긴 뒤add·commit으로 풀면 된다.git merge --abort라는 도망칠 구멍도 늘 있다.
다음 시간 예고
오늘 git merge --abort 로 "병합을 통째로 되돌리는" 안전망을 살짝 맛봤죠? 사실 그건 시작일 뿐이에요. 다음 시간엔 "되돌리기" 를 통째로 배웁니다. 방금 고친 걸 커밋 전으로 돌리는 git restore, 잘못 찍은 커밋을 되감는 git reset, 이미 공유한 커밋을 안전하게 취소하는 git revert — 상황에 맞는 되돌리기를 골라 쓰는 법을 익혀요.
그리고 git 의 진짜 안전망을 만나요. 실수로 가지를 지웠거나 커밋을 날린 것 같아도, git reflog 라는 구조대가 잃어버린 커밋을 되살려줘요. "되돌릴 수 있다" 는 이 믿음이 있어야 과감하게 실험할 수 있거든요. 거기에 비밀번호 같은 건 아예 추적에서 빼버리는 .gitignore 까지 — 다음 시간은 "실수해도 괜찮은 개발자" 가 되는 시간이에요.
과제
오늘 배운 건 눈으로만 보면 절대 안 익어요. 꼭 여러분 컴퓨터에서 직접 가지를 따고, 합치고, 충돌을 내보세요. 충돌은 일부러 만들어 풀어봐야 무서움이 사라져요.
[기초] 가지를 따서 작업하고 fast-forward 로 합치기
연습 저장소에서 git switch -c practice-1 로 가지를 만들고, 거기서 파일을 고쳐 커밋 두 개를 쌓으세요. (이 동안 main 은 건드리지 않습니다.) 그다음 git switch main 으로 본체에 돌아가 git merge practice-1 로 합치세요.
출력에 Fast-forward 가 떴는지 확인하고, 왜 이번엔 병합 커밋이 안 생겼는지 한 문장으로 설명해보세요. 마지막으로 git branch -d practice-1 로 가지를 정리하세요.
[응용] 일부러 충돌을 내고 손으로 풀기
같은 파일(note.txt)의 같은 줄을, main 과 새 가지 practice-2 에서 서로 다르게 고쳐 커밋하세요. 그리고 main 에서 git merge practice-2 를 해서 충돌을 일부러 일으키세요. 충돌 표시(<<<<<<< 등)를 읽고, 표시를 모두 지워 최종 내용을 정한 뒤 git add → git commit 으로 해결하세요.
해결을 마쳤다면, 한 번 더 충돌을 만든 다음 이번엔 git merge --abort 로 병합을 취소해보세요. 작업 폴더가 충돌 전 깨끗한 상태로 돌아오는 걸 눈으로 확인하고, "충돌이 나도 갇히지 않는다" 는 게 어떤 안심을 주는지 한두 문장으로 적어보세요.
[심화] 팀 상황을 상상하며 stash 써보기
이런 팀 상황을 떠올려보세요. "기능을 한창 만드는 중인데, 갑자기 '급한 버그 하나만 먼저 고쳐달라' 는 요청이 왔다. 지금 작업은 반쯤 됐다." 이 상황을 git stash 로 처리해보세요 — 하던 작업을 stash 로 접어두고, main 에서 급한 수정을 한 커밋으로 처리한 뒤, git stash pop 으로 원래 작업을 되살려 이어가세요.
그리고 한 가지 생각해보세요. 여럿이 같은 main 을 공유하며 일하는 팀이라면, 합칠 때 fast-forward 와 3-way 병합 중 어느 쪽이 더 자주 생길까요? 왜 그럴지 한두 문장으로 적어보세요. (힌트: 내가 가지에서 일하는 동안 동료들도 main 에 무언가를 합치고 있어요.)
생각해볼 주제
1. 왜 main 에서 바로 안 고치고 굳이 가지를 따나
혼자 연습할 땐 main 에서 바로 고쳐도 당장은 불편하지 않아요. 그런데 거의 모든 개발팀이 "새 작업은 반드시 가지를 따서 한다" 는 규칙을 둬요. 만약 팀원 다섯 명이 모두 같은 main 에서 동시에 코드를 고친다면 어떤 일이 벌어질까요? 본체를 "항상 동작하는 상태" 로 지키는 게 왜 협업에서 그렇게 중요한지, 가지를 따로 두는 것이 그걸 어떻게 가능하게 하는지 생각해보세요.
2. 충돌은 왜 "에러" 가 아니라 "정상" 일까
많은 입문자가 충돌을 보면 "내가 뭘 잘못했나" 하고 겁부터 먹어요. 그런데 충돌은 사실 Git 이 일부러 멈춰서 사람에게 결정을 넘기는 거예요. 지난 시간 배운 클라우드 자동 동기화(둘이 같은 파일을 고치면 한쪽을 조용히 덮어쓰는 것)와 비교해보세요. Git 이 충돌을 "숨기지 않고 드러내는" 선택을 한 덕분에 우리가 얻는 게 무엇인지, 왜 그게 팀 작업에서 더 안전한 길인지 풀어보세요.
3. "되돌릴 수 있다" 는 믿음이 주는 힘
오늘 우리는 git merge --abort 로 병합을 취소하고, 합친 가지를 git branch -d 로 지워도 내용은 본체에 남는 걸 봤어요. 이렇게 "잘못해도 되돌릴 수 있다" 는 안전망이 있을 때와 없을 때, 사람이 실험에 임하는 태도는 어떻게 달라질까요? 안전망이 오히려 더 과감하고 자유로운 시도를 가능하게 한다는 점을, 여러분이 코드를 짤 때(혹은 다른 어떤 일을 할 때)의 경험과 엮어 생각해보세요.
✅ 예시 답안정답 보기
이 문서는 A-2 「브랜치로 평행 우주 열고 다시 합치기」의 과제와 생각해볼 주제에 대한 예시답안입니다. 정답을 외우는 용도가 아니라, 직접 손으로 쳐본 결과를 어떻게 정리하고 설명하는지 흐름을 참고하는 용도로 보세요. 특히 브랜치와 충돌은 눈으로 읽기보다 손으로 만들어봐야 무서움이 사라집니다. 답안을 보기 전에 꼭 먼저 터미널에서 직접 가지를 따고, 합치고, 충돌을 내보세요.
과제 예시답안
🎯 [과제 1 예시답안] 가지를 따서 작업하고 fast-forward 로 합치기
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| 가지 생성 + 작업 | 35% | git switch -c 로 가지를 만들고 그 위에서 커밋 두 개를 쌓았는가 (main 은 안 건드림) |
| fast-forward 병합 | 40% | main 으로 돌아가 git merge 했고 출력에 Fast-forward 가 떴는가 |
| 이유 설명 + 정리 | 25% | 병합 커밋이 왜 안 생겼는지 설명하고 git branch -d 로 가지를 정리했는가 |
풀이 예시
가지를 따서 커밋 두 개를 쌓고, 본체로 돌아와 합치는 흐름이에요.
git switch -c practice-1
# Switched to a new branch 'practice-1'
echo "첫 번째 줄" >> base.txt
git add base.txt
git commit -m "practice: 첫 번째 줄 추가"
echo "두 번째 줄" >> base.txt
git add base.txt
git commit -m "practice: 두 번째 줄 추가"
git switch main
# Switched to branch 'main'
git merge practice-1
# Updating a1b2c3d..f4e5d6c
# Fast-forward
# base.txt | 2 ++
# 1 file changed, 2 insertions(+)
git branch -d practice-1
# Deleted branch practice-1 (was f4e5d6c).
왜 병합 커밋이 안 생겼나: 가지를 친 뒤 main 이 한 번도 움직이지 않았기 때문이에요. 합칠 게 따로 없으니 Git 은 main 이름표를 가지 끝으로 쓱 옮기기만 하면 됐어요. 그래서 새 커밋 없이 한 줄로 깔끔하게 이어졌죠.
💡 튜터의 한마디 — fast-forward 가 가능한 건 "본체가 가만히 있었다" 는 특수한 상황 덕분이에요. 혼자 연습할 땐 이게 자주 나오지만, 팀에선 내가 가지에서 일하는 동안 동료들이 main 을 계속 전진시켜서 fast-forward 보다 3-way 병합이 훨씬 흔해져요. 그러니 "fast-forward 는 운 좋게 깔끔한 경우" 정도로 기억해두면 좋아요.
🎯 [과제 2 예시답안] 일부러 충돌을 내고 손으로 풀기
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| 충돌 유발 | 30% | 같은 파일의 같은 줄을 양쪽에서 다르게 고쳐 CONFLICT 를 일으켰는가 |
| 충돌 해결 | 40% | 표시(<<<<<<< 등)를 모두 지우고 git add → git commit 으로 풀었는가 |
| abort 체험 + 설명 | 30% | git merge --abort 로 병합 직전 상태로 되돌리고, 그 안심을 한두 문장으로 적었는가 |
풀이 예시
같은 note.txt 의 같은 줄을 본체와 가지에서 다르게 고쳐 충돌을 만들어요.
git switch -c practice-2
echo "가지에서 고친 줄" > note.txt
git add note.txt
git commit -m "note: 가지 쪽 내용으로 변경"
git switch main
echo "본체에서 고친 줄" > note.txt
git add note.txt
git commit -m "note: 본체 쪽 내용으로 변경"
git merge practice-2
# Auto-merging note.txt
# CONFLICT (content): Merge conflict in note.txt
# Automatic merge failed; fix conflicts and then commit the result.
파일을 열면 표시가 남아 있어요. 세 줄(<<<<<<<, =======, >>>>>>>)을 지우고 최종 내용만 남겨요.
cat note.txt
# <<<<<<< HEAD
# 본체에서 고친 줄
# =======
# 가지에서 고친 줄
# >>>>>>> practice-2
# (편집기로 아래 한 줄만 남기게 고침)
# 본체와 가지를 합친 최종 줄
git add note.txt
git commit -m "note 충돌 해결: 양쪽 내용 통합"
이번엔 같은 방식으로 충돌을 한 번 더 만든 뒤, 풀지 말고 도망쳐 봐요.
git merge practice-2
# CONFLICT (content): Merge conflict in note.txt
git merge --abort
git status
# On branch main
# nothing to commit, working tree clean ← 충돌 전으로 깨끗하게 되돌아옴
안심에 대해: 충돌이 떠도 --abort 한 줄이면 merge 누르기 직전으로 통째로 돌아간다는 걸 직접 보니, "충돌 나면 갇히는 게 아니구나" 하는 안심이 생겼어요. 잘못돼도 빠져나올 구멍이 있으니 충돌을 두려워하지 않고 시도하게 돼요.
💡 튜터의 한마디 — 충돌 해결에서 가장 흔한 실수가 표시 세 줄 중 하나를 깜빡 남기는 거예요. 커밋 전에 git diff 로 한 번 훑거나 파일에서 <<<<<<< 를 검색해 하나도 안 걸리는지 확인하는 습관을 들이면 사고를 막아요. 그리고 --abort 는 "잘 안 되면 처음부터 다시" 를 가능하게 해주는 든든한 뒷문이에요. 충돌이 무섭게 느껴질수록 이 뒷문이 있다는 걸 기억하세요.
🎯 [과제 3 예시답안] 팀 상황을 상상하며 stash 써보기
채점 포인트
| 항목 | 배점 | 기준 |
|---|---|---|
| stash 로 접어두기 | 35% | 작업 중인 변경을 git stash 로 치우고 작업 폴더가 깨끗해진 걸 확인했는가 |
| 급한 일 처리 + 복원 | 35% | 다른 작업을 한 커밋으로 처리한 뒤 git stash pop 으로 원래 작업을 되살렸는가 |
| 병합 모양 추론 | 30% | 팀에선 fast-forward 와 3-way 중 무엇이 흔할지 이유와 함께 적었는가 |
풀이 예시
기능을 만드는 중에 급한 일이 끼어든 상황을 stash 로 처리해요.
# 기능을 한창 만드는 중 (아직 커밋하긴 이른 상태)
echo "새 기능 (작업 중...)" >> feature.txt
git stash
# Saved working directory and index state WIP on main: 8a9b0c1 ...
git status
# nothing to commit, working tree clean ← 작업 폴더가 깨끗해짐
# 급한 버그 수정을 한 커밋으로 끝냄
echo "버그 수정 한 줄" >> bugfix.txt
git add bugfix.txt
git commit -m "긴급: 잘못된 출력 한 줄 수정"
# 접어뒀던 원래 작업을 다시 꺼냄
git stash pop
# (feature.txt 의 '작업 중' 변경이 되살아남)
fast-forward vs 3-way 추론: 팀에선 3-way 병합이 훨씬 자주 생길 거예요. 내가 기능 가지에서 일하는 동안, 동료들도 쉴 새 없이 main 에 자기 작업을 합치거든요. 그러면 내가 가지를 친 뒤 main 이 계속 전진하니, 합칠 때쯤이면 본체와 내 가지가 이미 갈라져 있어요. 양쪽이 다 움직였으니 fast-forward 가 안 되고, 합류점(병합 커밋)을 만드는 3-way 병합이 일어나죠.
💡 튜터의 한마디 — stash 는 "잠깐 치워두는 임시 서랍" 이라 가볍게 쓰기 좋지만, 너무 오래 묵혀두면 뭘 넣었는지 잊어버려요(git stash list 에 잔뜩 쌓이죠). "급한 일 끝나면 바로 pop 한다" 는 리듬으로 쓰는 게 좋아요. 그리고 팀 협업에서 3-way 병합이 기본이라는 감각은 중요해요 — 그래서 다음 과목들에서 코드를 GitHub 에 올리고 협업할 때, 병합 커밋이 생기는 걸 당연하게 받아들이게 될 거예요.
생각해볼 주제 예시답안
🤔 [생각해볼 주제 1] 왜 main 에서 바로 안 고치고 굳이 가지를 따나
문제 상황 요약
혼자 연습할 땐 main 에서 바로 고쳐도 불편하지 않다. 그런데 거의 모든 개발팀이 "새 작업은 반드시 가지를 따서 한다" 는 규칙을 둔다. 팀원 다섯이 같은 main 에서 동시에 고치면 어떤 일이 벌어지는지, 본체를 "항상 동작하는 상태" 로 지키는 게 왜 중요한지 풀어보는 주제다.
튜터의 가이드 및 해설
핵심은 main 이 팀 모두가 딛고 서는 "공용 바닥" 이라는 점이다. 이 바닥이 흔들리면 모두가 함께 넘어진다.
다섯 명이 같은 main 에서 동시에 고치는 장면을 그려보자. A 가 로그인 기능을 만들다 코드가 반쯤 깨진 상태로 main 에 두면, 그 순간 B·C·D·E 가 받는 main 도 깨져 있다. 동료들은 "내가 뭘 잘못했나" 하며 남의 미완성 코드에 발이 묶인다. 누구의 작업이 문제인지 가려내기도 어렵다. 다섯 사람의 반쪽짜리 변경이 한 바닥에서 뒤엉키니까.
가지를 따로 두면 이 문제가 사라진다. 각자 자기 가지에서 코드를 만들고, 그게 완성되어 동작하는 것만 본체에 합친다. 내 가지에서 코드가 아무리 깨져 있어도 main 은 멀쩡하니, 동료들은 늘 "동작하는 본체" 위에서 안심하고 일한다. 본체를 항상 배포 가능한 상태로 지킨다는 건, 곧 "언제든 여기서 출발해도 안전하다" 는 공동의 약속이다.
그래서 브랜치는 단순한 편의 기능이 아니라 협업의 전제 조건이다. 가지가 있어서 여러 명이 같은 코드베이스를 동시에, 서로를 방해하지 않고 고칠 수 있다. 이 약속이 다음 과목들에서 배울 Pull Request·코드 리뷰의 바탕이 된다.
💡 핵심을 한마디로
main은 팀 전체가 딛고 서는 공용 바닥이라, 거기서 직접 고치면 한 사람의 미완성 코드가 모두의 발을 묶는다. 각자 가지에서 만들고 완성된 것만 합치면 본체는 늘 동작하는 상태로 지켜지고, 그래서 여럿이 서로를 방해하지 않고 함께 일할 수 있다.
🤔 [생각해볼 주제 2] 충돌은 왜 "에러" 가 아니라 "정상" 일까
문제 상황 요약
많은 입문자가 충돌을 보면 "내가 뭘 잘못했나" 하고 겁부터 먹는다. 그런데 충돌은 Git 이 일부러 멈춰 사람에게 결정을 넘기는 것이다. 지난 시간 배운 클라우드 자동 동기화와 비교해, Git 이 충돌을 "드러내는" 선택으로 우리가 얻는 게 무엇인지 풀어보는 주제다.
튜터의 가이드 및 해설
충돌을 이해하는 열쇠는 "Git 이 멈춘 건 못 해서가 아니라, 안 하기로 한 것" 이라는 점이다.
두 사람이 같은 줄을 다르게 고쳤을 때, 컴퓨터가 둘 중 하나를 자동으로 고를 수도 있다. 실제로 구글 드라이브 같은 클라우드는 그렇게 한다 — 나중에 저장한 쪽으로 조용히 덮어쓰거나, 사본을 따로 만들어버린다. 편해 보이지만 무서운 일이다. 누군가의 작업이 본인도 모르게 사라질 수 있기 때문이다. 어느 쪽이 옳은지는 사람만 아는데, 기계가 멋대로 정해버리는 셈이다.
Git 은 정반대의 선택을 한다. 같은 줄이 부딪히면 자동으로 정하지 않고 멈춰서 양쪽을 다 보여준다. "여기 둘이 달라. 어느 게 맞는지는 네가 알지. 직접 골라." 이것이 충돌이다. 즉 충돌은 고장이 아니라, 어느 작업도 함부로 버리지 않겠다는 Git 의 안전장치다. 충돌이 났다는 건 "두 사람이 같은 곳을 의미 있게 고쳤다" 는 신호이고, 그 결정을 사람에게 정중히 넘긴 것뿐이다.
그래서 충돌을 만나면 겁먹을 게 아니라 오히려 안심해도 된다. Git 이 나 대신 "덮어쓰기 사고" 를 막아준 것이니까. 우리가 할 일은 표시를 읽고 어느 쪽을(혹은 둘을 합쳐) 남길지 차분히 고르는 것뿐이다. 자동화가 편하긴 해도, 정말 중요한 결정은 사람이 보게 하는 것 — 그게 팀 작업에서 더 안전한 길이다.
💡 핵심을 한마디로
충돌은 Git 이 못 해서 난 에러가 아니라, 같은 줄이 부딪혔을 때 멋대로 한쪽을 덮어쓰지 않고 사람에게 결정을 넘기려 일부러 멈춘 것이다. 덕분에 누구의 작업도 소리 없이 사라지지 않는다. 충돌은 사고가 아니라 안전장치다.
🤔 [생각해볼 주제 3] "되돌릴 수 있다" 는 믿음이 주는 힘
문제 상황 요약
오늘 우리는 git merge --abort 로 병합을 취소하고, 합친 가지를 git branch -d 로 지워도 내용은 본체에 남는 걸 봤다. "잘못해도 되돌릴 수 있다" 는 안전망이 있을 때와 없을 때, 사람이 실험에 임하는 태도가 어떻게 달라지는지 생각해보는 주제다.
튜터의 가이드 및 해설
핵심은 "되돌릴 수 있다는 믿음이 오히려 더 과감한 시도를 가능하게 한다" 는 역설이다.
안전망이 없다고 상상해보자. 한 번 고치면 되돌릴 수 없다면, 우리는 매 순간 조심스러워진다. "이거 바꿨다가 망하면 끝장인데…" 하는 두려움에 새로운 시도를 미루고, 익숙하고 안전한 길만 가게 된다. 실험 자체가 무서워지는 것이다. 마치 지우개 없는 연필로 그림을 그릴 때처럼, 선 하나하나에 잔뜩 긴장하게 된다.
반대로 Git 처럼 든든한 안전망이 있으면 태도가 달라진다. 가지를 따서 마음껏 실험하고, 망하면 그 가지를 통째로 버리면 그만이다. 본체는 안 건드렸으니까. 병합이 꼬여도 --abort 로 빠져나오면 된다. "잘못돼도 처음으로 돌아갈 수 있다" 는 믿음이 있으면, 우리는 두려움 없이 과감하게 시도하게 된다. 더 대담한 실험, 더 큰 변화를 시도할 용기가 거기서 나온다.
이건 코드뿐 아니라 어떤 일에든 통한다. 초안을 따로 저장해두면 본문을 과감히 고쳐 쓸 수 있고, 게임에 세이브가 있으면 어려운 길에 도전한다. 안전망은 사람을 게으르게 만드는 게 아니라, 두려움이라는 족쇄를 풀어 더 멀리 나아가게 한다.
그래서 다음 시간에 배울 git reset·git revert·git reflog 같은 "되돌리기" 도구들이 그렇게 중요한 것이다. 그것들은 단순한 복구 기능이 아니라, 우리가 겁 없이 실험할 수 있게 해주는 자유의 토대다.
💡 핵심을 한마디로
되돌릴 수 없으면 매 시도가 두려워 안전한 길만 가게 되지만, "잘못해도 처음으로 돌아갈 수 있다" 는 믿음이 있으면 과감하게 실험하게 된다. Git 의 안전망은 사람을 게으르게 만드는 게 아니라, 두려움을 걷어내 더 멀리 나아가게 하는 자유의 토대다.