Learn Git Branching 전 레벨 공략과 실무 대응표

Git이 어려운 진짜 이유는 명령을 쳐도 무슨 일이 벌어졌는지 눈에 보이지 않기 때문이다. rebase를 치면 커밋 그래프가 어떻게 바뀌는지, git push origin main^:foo가 정확히 무엇을 어디로 보내는지 터미널만 봐서는 알 수 없다.

Learn Git Branching은 그 그래프를 실시간으로 그려주는 학습 사이트다. 한국어를 지원하고, 설치할 것이 없고, 전부 브라우저 안에서 돌아간다. 이 글은 36개 레벨 전체의 답과 왜 그 답인지, 그리고 각 레벨이 실무의 어떤 상황에 대응하는지를 정리한다.

답만 보고 넘기면 남는 것이 없다. 각 레벨의 답 아래에 그 명령이 그래프에 무슨 짓을 하는지 한 줄씩 붙였다. 그쪽이 본론이다.

시작 전에 알아둘 것

주소는 learngitbranching.js.org이고 뒤에 ?locale=ko를 붙이면 한국어로 열린다. 명령창에 치는 것은 실제 Git 명령이고, 몇 가지 도우미 명령이 더 있다.

명령 하는 일
levels 레벨 목록을 연다
objective 이 레벨의 목표 설명을 다시 본다
show goal / hide goal 목표 그래프를 띄우고 숨긴다
hint 힌트
undo 방금 명령 한 개 취소
reset 레벨을 처음 상태로
show solution 정답을 보여준다
sandbox 아무 목표 없는 연습장으로 나간다
git fakeTeamwork [n] 원격에 다른 사람이 커밋한 척한다

show goal을 띄워 놓고 시작하는 습관이 좋다. 목표 그래프와 현재 그래프의 차이를 먼저 말로 설명할 수 있으면 명령은 대개 저절로 나온다.

Learn Git Branching 화면에서 커밋 원, 부모 화살표, 브랜치 이름표, HEAD 표시를 읽는 법
원이 커밋, 화살표가 부모 방향이다. 브랜치는 이름표일 뿐이고 HEAD는 지금 서 있는 자리를 가리킨다

읽는 법은 네 가지만 알면 된다.

  • 은 커밋이다. C0, C1 같은 이름은 실제 해시 자리에 붙은 별명이다.
  • 화살표는 부모를 가리킨다. 최신 커밋에서 시작해 과거로 향한다.
  • 네모 이름표는 브랜치다. 커밋이 아니라 커밋을 가리키는 포인터다.
  • HEAD는 내가 지금 서 있는 자리다. 보통 브랜치를 가리키고, 커밋을 직접 가리키면 분리된(detached) 상태다.

기본편

1. Git 커밋 소개

git commit
git commit

커밋은 스냅샷이다. 새 원이 생기고 화살표가 직전 커밋을 가리키며, 브랜치 이름표와 HEAD가 함께 앞으로 따라간다. 커밋할 때마다 이름표가 같이 움직인다는 것이 뒤의 모든 레벨의 바탕이다.

2. Git에서 브랜치 쓰기

git branch bugFix
git checkout bugFix

git branch는 이름표를 하나 더 만들 뿐 아무것도 옮기지 않는다. checkout을 해야 HEAD가 그 이름표로 옮겨가고, 그때부터 커밋이 그 브랜치를 밀고 나간다. 요즘 Git이라면 git switch -c bugFix 한 줄로 같은 일을 한다.

3. Git에서 브랜치 합치기(Merge)

git checkout -b bugFix
git commit
git checkout main
git commit
git merge bugFix

양쪽에 커밋이 하나씩 쌓인 뒤 머지하면 부모가 둘인 커밋이 생긴다. 그래프에서 화살표 두 개가 나가는 원이 그것이다. 머지 후 main은 양쪽 작업을 모두 포함하지만 bugFix는 여전히 옛 자리에 있다는 점을 확인해 둔다.

4. 리베이스(rebase)의 기본

git checkout -b bugFix
git commit
git checkout main
git commit
git checkout bugFix
git rebase main

머지와 결과는 같아 보이지만 그래프가 다르다. bugFix의 커밋이 main 끝으로 복사되어 한 줄이 된다. 원래 자리의 커밋은 흐릿하게 남는다. 그 흐릿한 원이 "해시가 바뀌었다"는 사실을 보여주는 것이고, 여기서 리베이스의 황금률(남이 가진 커밋은 리베이스하지 않는다)이 나온다.

커밋 트리 이동하기

5. HEAD 분리하기

git checkout C4

브랜치가 아니라 커밋을 직접 checkout하면 HEAD가 그 커밋에 붙는다. 실무에서 "detached HEAD 상태입니다"라는 경고가 이것이다. 이 상태에서 만든 커밋은 어떤 브랜치에도 속하지 않으므로, 작업을 남기려면 git switch -c <이름>으로 이름표를 붙여야 한다.

6. 상대 참조 (^)

git checkout bugFix^

해시를 외우지 않고 위치로 지정하는 방법이다. ^는 부모 하나 위, bugFix^는 "bugFix가 가리키는 커밋의 부모"다. HEAD^, main^^처럼 붙여 쓸 수 있다.

7. 상대 참조 #2 (~)

git branch -f main C6
git checkout HEAD~1
git branch -f bugFix HEAD~1

~n은 부모를 n번 거슬러 올라간다. HEAD~3HEAD^^^와 같다.

여기서 진짜 배울 것은 git branch -f다. 이름표를 원하는 커밋으로 강제로 옮긴다. 브랜치가 포인터일 뿐이라는 사실이 이 명령에서 가장 분명해진다. 실무에서는 로컬 브랜치를 원격 상태로 맞출 때 git branch -f main origin/main 꼴로 쓴다. 체크아웃 중인 브랜치에는 쓸 수 없으니 그때는 git reset --hard를 쓴다.

8. Git에서 작업 되돌리기

git reset HEAD~1
git checkout pushed
git revert HEAD

한 레벨 안에서 되돌리기 두 가지를 비교시킨다. local 브랜치는 아직 아무도 안 본 로컬 브랜치라 reset으로 커밋을 없애도 되고, pushed는 이미 공유된 브랜치라 revert뒤집는 커밋을 쌓아야 한다. 그래프에서 reset은 이름표가 뒤로 물러나고, revert는 새 원이 앞에 생기는 것으로 구분된다.

실무 기준도 정확히 이것이다. push 전이면 reset, push 후면 revert.

작업 재배치

9. Cherry-pick 소개

git cherry-pick C3 C4 C7

다른 브랜치의 커밋을 골라 지금 HEAD 위로 복사한다. 순서대로 여러 개를 줄 수 있다. 복사이므로 새 원에는 C3'처럼 작은따옴표가 붙는다. 해시가 달라진다는 뜻이다.

실무에서는 릴리스 브랜치에 핫픽스만 옮길 때 쓴다.

10. 인터랙티브 리베이스 소개

git rebase -i overHere

대화창이 열리면 커밋을 드래그로 재배치하고 필요 없는 것의 체크를 푼다. 이 레벨의 목표 순서는 C3, C5, C4다.

실제 Git에서는 편집기로 todo 목록이 열리고, 줄 순서를 바꾸거나 pickdrop, squash, fixup으로 고쳐 같은 일을 한다. 사이트의 드래그가 곧 줄 순서 바꾸기, 체크 해제가 drop이다.

11. The Staging Area

git add app.js
git commit
git add styles.css
git commit

비교적 최근에 추가된 레벨로, 작업 트리와 스테이지를 시각적으로 보여준다. 파일을 골라 담아 커밋 두 개로 나누는 것이 목표다. 실무에서 git add -p로 하는 일의 축소판이다.

12. Undoing with git restore

git restore --staged secret.env
git restore experiment.js
git commit

restore의 두 가지 용법이 한 화면에 나온다. --staged스테이지에서만 내리고 파일 내용은 남긴다. 옵션 없는 restore작업 트리 변경을 버린다. 이 둘이 각각 옛 git reset HEAD <파일>git checkout -- <파일>을 대신한다.

버리는 쪽은 되살릴 수 없다는 점을 기억해 둔다.

잡동사니

13. 딱 한 개의 커밋만 가져오기

git rebase -i main
git rebase bugFix main

디버그 커밋이 섞인 브랜치에서 필요한 커밋 하나만 main으로 옮기는 문제다. 대화창에서 C4만 남기고 나머지를 뺀 뒤, git rebase bugFix main으로 main 이름표를 그 자리까지 끌어온다.

git rebase <목적지> <옮길 것>은 인자가 두 개일 때 checkout까지 겸한다. 여기서는 "main을 bugFix 위로 옮긴다"는 뜻이 된다.

cherry-pick으로 푸는 방법도 있다.

git checkout main
git cherry-pick C4

이 방식이 더 직관적이고 실무에서도 더 자주 쓴다.

14. 커밋들 갖고 놀기

git rebase -i HEAD~2
git commit --amend
git rebase -i HEAD~2
git rebase caption main

과거 커밋의 내용을 고치는 문제다. 요령은 이렇다. 고칠 커밋을 리베이스로 맨 위에 올리고, commit --amend로 고치고, 다시 리베이스해서 원래 순서로 되돌린다. 각 리베이스의 대화창에서 두 커밋의 순서를 바꿔주면 된다.

실제 Git이라면 git rebase -i HEAD~2에서 그 커밋을 edit으로 표시하는 편이 훨씬 간단하다.

git rebase -i HEAD~2
# 고칠 커밋 줄을 edit으로 바꾸고 저장
git commit --amend
git rebase --continue

15. 커밋 갖고 놀기 #2

git checkout main
git cherry-pick C2
git commit --amend
git cherry-pick C3

같은 문제를 cherry-pick으로 푼다. 필요한 커밋을 하나씩 가져오면서 중간에 고치는 방식이다. 리베이스보다 순서 꼬임이 적어서, 옮길 커밋이 두세 개라면 이쪽이 안전하다.

16. Git 태그

git tag v1 side~1
git tag v0 main~2
git checkout v1

태그는 움직이지 않는 이름표다. 브랜치는 커밋할 때마다 따라오지만 태그는 그 자리에 못 박힌다. 릴리스 지점을 표시하는 용도다. 태그를 checkout하면 detached HEAD가 되는데, 그 지점이 고정된 자리라 당연한 결과다.

실무에서는 주석 태그를 쓰고 push까지 해야 남는다.

git tag -a v1.2.0 -m "결제 모듈 릴리스"
git push origin v1.2.0

17. Git 설명

git commit

git describe <ref>는 가장 가까운 태그를 기준으로 지금 위치를 <태그>_<태그 이후 커밋 수>_g<해시> 꼴로 알려준다. 빌드 산출물에 버전을 새길 때 실제로 많이 쓴다. 이 레벨은 명령을 몇 번 쳐보고 커밋 하나만 하면 통과한다.

git describe --tags --always --dirty
# v1.2.0-3-gb7c8d9e-dirty

고급

18. 9천번이 넘는 리베이스

git rebase main bugFix
git rebase bugFix side
git rebase side another
git rebase another main

브랜치 네 개를 한 줄로 세우는 문제다. 요령은 아래에서 위로 차례차례 얹는 것이다. 각 명령의 두 인자는 git rebase <바탕> <올릴 브랜치>로 읽는다.

마지막 줄이 핵심이다. 앞의 세 줄로 커밋은 이미 한 줄에 다 놓였고, 남은 것은 main 이름표를 맨 끝으로 옮기는 일뿐이다. git branch -f main another로도 같은 결과가 나온다.

19. 다수의 부모

git branch bugWork main^^2^

머지 커밋은 부모가 둘이다. ^에 숫자를 붙이면 몇 번째 부모로 갈지 고를 수 있다. main^은 첫 번째 부모, main^2는 두 번째 부모다.

읽는 법은 왼쪽부터 차례로다. main^ 한 칸, ^2 두 번째 부모로, ^ 다시 한 칸.

실무에서 이 문법이 필요한 자리는 머지 커밋을 되돌릴 때다. git revert -m 1 <머지커밋>-m 1이 "첫 번째 부모 쪽을 남긴다"는 같은 개념이다.

20. 브랜치 스파게티

git checkout one
git cherry-pick C4 C3 C2
git checkout two
git cherry-pick C5 C4 C3 C2
git branch -f three C2

이 코스에서 가장 손이 많이 가는 문제다. 새 기법은 없고, cherry-pick과 branch -f를 목표 그래프에 맞춰 조합하기만 하면 된다. show goal을 띄워 놓고 브랜치별로 필요한 커밋을 순서대로 적어본 다음 치는 것이 요령이다.

원격편

원격 레벨부터는 화면에 트리가 둘로 나온다. 왼쪽이 내 저장소, 오른쪽이 원격이다.

로컬 저장소와 원격 저장소가 나란히 있고 그 사이에 원격 추적 브랜치 o/main이 놓인 구조
o/main은 원격 저장소가 아니라 내 저장소 안에 있다. 마지막으로 통신했을 때 원격이 어디였는지를 기억하는 표시일 뿐이다

o/main이 이 편의 핵심이다. 실제 Git의 origin/main에 해당하며, 내 컴퓨터 안에 있는 표시다. 내가 마지막으로 fetch했을 때 원격의 main이 어디였는지를 기억한다. 그래서 인터넷이 끊겨도 git log origin/main이 동작하고, 그 값이 실제 원격과 다를 수 있다.

21. Clone 소개

git clone

원격 저장소가 생기고, 내 저장소에 o/main 이름표가 함께 생긴다.

22. 원격 브랜치(remote branch)

git commit
git checkout o/main
git commit

o/main을 checkout하면 detached HEAD가 된다. 원격 추적 브랜치는 내가 직접 커밋을 쌓을 수 있는 자리가 아니기 때문이다. 이 이름표는 오직 fetch와 push로만 움직인다.

23. Git Fetch

git fetch

원격의 커밋을 내 저장소로 내려받고 o/main을 원격 위치로 옮긴다. main은 건드리지 않는다. 화면에서 로컬 main 이름표가 제자리에 있는 것을 확인해 두면 fetch와 pull의 차이가 분명해진다.

24. Git pull

git pull

git fetch 뒤에 git merge o/main을 한 것과 같다. 그래서 머지 커밋이 생긴다.

25. 가짜 팀워크

git clone
git fakeTeamwork 2
git commit
git pull

git fakeTeamwork은 사이트 전용 명령으로, 원격에 남이 커밋한 상황을 만든다. 내 커밋과 남의 커밋이 갈라진 뒤 pull로 합치는 흐름을 확인하는 레벨이다.

26. Git push

git commit
git commit
git push

내 커밋을 원격으로 올리고, o/main도 함께 원격의 새 위치로 옮겨간다. 세 이름표(로컬 main, o/main, 원격 main)가 한 자리에 모이면 성공이다.

27. 엇갈린 히스토리

git clone
git fakeTeamwork
git commit
git pull --rebase
git push

원격이 앞서 나간 상태에서 그냥 push하면 거부당한다. Git은 남의 커밋을 지우는 push를 허용하지 않는다. 해결은 먼저 받아서 내 작업을 그 위에 얹고 다시 올리는 것이다.

git pull --rebase는 머지 커밋 없이 내 커밋만 원격 끝으로 옮긴다. git pull(머지)로 풀어도 목표는 통과되지만 그래프가 갈라졌다 합쳐진 모양으로 남는다. 실무에서 pull.rebase=true를 권하는 이유가 이 그림에 그대로 나온다.

28. 잠겨버린 main 브랜치

git branch -f main o/main
git checkout -b feature C2
git push origin feature

main에 직접 push가 막힌 상황이다. 대부분의 회사 저장소가 이렇게 설정되어 있다.

풀이 순서를 읽어보면 실무 절차 그대로다. 내 커밋 C2를 새 브랜치 feature로 붙잡고, main은 원격 상태로 되돌리고, feature만 push해서 PR을 연다. 커밋을 붙잡는 것이 먼저라는 순서가 여기서도 같다.

원격 고급편

29. Push Main!

git fetch
git rebase o/main side1
git rebase side1 side2
git rebase side2 side3
git rebase side3 main
git push

여러 기능 브랜치를 순서대로 쌓아 한 줄로 만든 뒤 한 번에 올린다. 18번 레벨의 원격 버전이다. 첫 줄의 git fetch를 빼먹으면 바탕이 될 o/main이 낡은 자리에 있어 전부 어긋난다.

30. 원격 작업과 merge하기

git checkout main
git pull
git merge side1
git merge side2
git merge side3
git push

같은 상황을 merge로 푼다. 리베이스 판과 나란히 놓고 그래프를 비교해 보면 좋다. 머지는 각 브랜치가 갈라졌던 흔적이 남고, 리베이스는 한 줄로 펴진다. 어느 쪽이 옳은 것이 아니라 팀이 무엇을 기록으로 남기고 싶은가의 문제다.

31. 원격 저장소 추적하기

git checkout -b side o/main
git commit
git pull --rebase
git push

브랜치가 어느 원격 브랜치를 따라가는지(추적 관계)를 직접 정하는 레벨이다. git checkout -b <새 브랜치> o/main은 새 브랜치를 만들면서 추적 대상까지 지정한다. 그래서 뒤의 pullpush가 인자 없이 동작한다.

실제 Git에서는 이 두 가지가 같은 일을 한다.

git switch -c side --track origin/main
git branch -u origin/main side     # 이미 있는 브랜치의 추적 대상 변경

32. git push의 인자들

git push origin main
git push origin foo

git push <원격> <브랜치>는 추적 관계를 무시하고 어느 브랜치를 올릴지 직접 지정한다. 지금 어느 브랜치에 서 있든 상관없이 동작한다는 점이 요점이다.

33. git push 인자 확장판

git push origin main^:foo
git push origin foo:main

<source>:<destination> 꼴이다. 콜론 앞이 내 저장소에서 보낼 자리, 뒤가 원격에서 받을 자리다. main^처럼 상대 참조도 쓸 수 있고, 양쪽 이름이 달라도 된다.

실무에서 이 문법이 필요한 대표적인 자리가 배포다.

git push origin feature/login:staging   # 이 브랜치를 staging으로 배포

34. Fetch의 인자들

git fetch origin c3:foo
git fetch origin c6:main
git checkout foo
git merge main

fetch는 방향이 반대다. 콜론 앞이 원격에서 가져올 자리, 뒤가 내 저장소에서 받을 자리다.

여기서 놀라운 점은 fetch가 main 같은 로컬 브랜치에도 직접 쓸 수 있다는 것이다. 다만 지금 체크아웃 중인 브랜치에는 안 된다. 작업 트리와 어긋나기 때문이다.

35. Source가 없다

git push origin :foo
git fetch origin :bar

콜론 앞을 비우면 "아무것도 보내지 않는다"가 된다.

  • git push origin :foo → 원격의 foo지운다. 원격 브랜치를 삭제하는 정식 문법이 이것이다. 요즘은 git push origin --delete foo가 더 읽기 쉽다.
  • git fetch origin :bar → 아무것도 안 받고 로컬에 빈 브랜치 bar를 만든다. 쓸 일은 거의 없다.

36. pull 인자들

git pull origin c3:foo
git pull origin c2:side

git pullfetch 뒤에 merge이므로, 인자도 fetch의 인자를 그대로 받고 받은 자리를 현재 브랜치에 머지한다. 콜론 문법이 fetch와 같다는 것만 확인하면 끝난다.

사이트와 실무의 차이

전부 풀고 나서 터미널로 돌아가면 몇 가지가 다르다. 미리 알아두면 덜 당황한다.

사이트 실제 Git
C2 같은 짧은 이름 40자 해시. git log --oneline으로 앞 7자만 본다
o/main origin/main
충돌이 없다 리베이스와 머지에서 충돌이 자주 난다. --abort로 언제든 되돌린다
git fakeTeamwork 없다. 동료가 실제로 push한다
강제 push가 자유롭다 --force-with-lease를 쓴다. main은 대개 잠겨 있다
undo, reset이 있다 없다. 대신 git reflog가 그 역할을 한다
인터랙티브 리베이스가 드래그 편집기에서 todo 목록의 줄을 고친다

특히 충돌이 가장 큰 차이다. 사이트에서는 리베이스가 항상 깔끔하게 끝나지만 실제로는 커밋마다 충돌을 풀어야 할 수 있다. 이때 쓸 도구는 이 시리즈의 앞선 글들에서 다뤘다. 판단이 안 서면 git rebase --abort로 시작 자리로 돌아가면 된다.

어떻게 풀면 남는 게 있나

세 가지만 지키면 통과가 아니라 이해가 남는다.

하나. 명령을 치기 전에 결과를 말로 예측한다. "이 명령을 치면 bugFix 이름표가 C4로 옮겨가고 HEAD도 따라간다"까지 말한 뒤에 엔터를 친다. 예측이 빗나가면 그 자리가 바로 내가 모르던 곳이다.

둘. 답을 본 레벨은 reset 후 다시 푼다. show solution으로 본 답은 손이 기억하지 못한다.

셋. 다 풀고 나면 sandbox에서 내 저장소 상황을 재현해 본다. 지금 회사 저장소의 브랜치 구조를 그대로 만들어 놓고 리베이스해 보면, 실제로 치기 전에 결과를 확인할 수 있다. 위험한 명령을 처음 쓸 때 특히 쓸모 있다.