이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
기초개념 › 구조와 운영
Git 커밋과 브랜치
커밋은 무엇이고, 브랜치는 무엇을 가리키는가
Git에서 커밋은 그 시점의 파일 전체 모습을 저장한 것이고, 바로 앞 커밋을 가리킵니다. 브랜치는 커밋을 따로 복사해 둔 것이 아니라 커밋 하나를 가리키는 이름표입니다. 브랜치에서 커밋하면 그 이름표만 새 커밋으로 옮겨 가고, 다른 브랜치는 그대로입니다. git switch로 다른 브랜치로 옮기면 폴더의 파일이 그 브랜치가 가리키는 커밋의 모습으로 바뀝니다. 이 실습은 코드 칸의 명령을 실제 Git으로 새 빈 폴더에서 한 줄씩 실행한 결과를 보여 줍니다. 둘째 커밋 줄과 main으로 돌아가는 줄을 하나씩 지워 가며, 이름표가 가리키는 커밋과 폴더의 memo.txt가 어떻게 달라지는지를 봅니다. 커밋 번호는 실행할 때마다 달라지는 긴 글자라서, 만들어진 차례대로 커밋 1, 커밋 2로 적었습니다. 코드 칸의 #로 시작하는 줄은 설명이고, echo는 파일에 글을 쓰는 명령, cat은 파일 내용을 보이는 명령입니다.
실행으로 대조 · git 2.55.0으로 새 빈 폴더에서 네 가지 조합을 줄마다 실제로 실행해 상태를 대조
이 실습에 나오는 개념
- Git 커밋(commit)
- Git의 커밋은 저장소에 남기는 한 번의 저장이다. Git은 변경된 부분의 차이가 아니라 그 시점의 파일 모습(스냅숏)을 저장하고, 커밋은 그 모습을 가리킨다. 커밋은 바로 앞 커밋(부모)도 가리키는데, 맨 처음 커밋은 부모가 없고 보통 커밋은 부모가 하나다. 그래서 커밋들은 앞 커밋을 가리키는 줄로 이어진다(Pro Git 3.1절). git commit은 올려 둔(git add 한) 내용으로 새 커밋을 만들고, 그 커밋은 지금 있는 브랜치가 가리키던 커밋의 자식이 되며 브랜치가 새 커밋을 가리키도록 갱신된다(git-commit 문서). 고치기만 하고 커밋하지 않은 변경은 어느 커밋에도 들어 있지 않다.
- Git 브랜치(branch)
- Git의 브랜치는 커밋 하나를 가리키는 가볍고 움직이는 포인터(이름표)다. 커밋을 복사해 둔 것이 아니다. 브랜치는 가리키는 커밋의 SHA-1 체크섬 40글자를 담은 단순한 파일이라서 만들고 지우는 비용이 작다(Pro Git 3.1절). 브랜치에서 커밋하면 그 브랜치의 이름표만 새 커밋으로 옮겨 가고, 같은 커밋을 가리키던 다른 브랜치는 그대로다. 브랜치를 만들기만 하고 커밋하지 않으면 커밋은 늘지 않는다.
- git switch
- git switch는 지금 있는 브랜치를 다른 브랜치로 바꾸는 명령이다(Git 2.23부터). git switch main처럼 쓰면 main으로 옮겨 가고, 폴더의 파일이 그 브랜치가 가리키는 커밋의 모습으로 바뀐다(Pro Git 3.1절). git switch -c feature는 feature 브랜치를 새로 만든 뒤 그쪽으로 옮긴다(git-switch 문서의 -c). 폴더가 깨끗하지 않아도(커밋하지 않은 변경이 있어도) 옮길 수 있지만, 옮기면서 그 변경을 잃게 되는 경우에는 중단한다(git-switch 문서 DESCRIPTION). 이 PC의 git 2.55.0에서는 옮기는 두 브랜치의 그 파일이 같을 때 고친 내용이 그대로 따라왔고, 두 브랜치의 그 파일이 서로 다를 때는 옮기기를 막았다.
- HEAD(Git)
- HEAD는 Git이 지금 어느 브랜치에 있는지를 기억하는 특별한 포인터로, 보통 지금 있는 브랜치를 가리키며 커밋을 직접 가리키게 할 수도 있다(Pro Git 3.1절). 그래서 git commit은 HEAD가 가리키는 브랜치의 이름표를 새 커밋으로 옮기고, git switch는 HEAD를 다른 브랜치로 바꾼다. 이 PC의 git 2.55.0에서 새 저장소의 .git/HEAD 파일에는 지금 있는 브랜치 이름이 ref: refs/heads/main 꼴로 적혀 있었다.
이렇게 바꿔 보세요
- 처음 설정으로 — feature에서 커밋하면 feature 이름표만 새 커밋으로 가고, main으로 돌아오면 폴더가 main의 커밋 모습(v1)으로 바뀝니다.
- main으로 돌아가지 않으면 — feature에 그대로 있어서 폴더의 memo.txt는 둘째 커밋의 모습(v2)입니다. main은 커밋 1에 그대로 있습니다.
- 둘째 커밋을 하지 않고 main으로 옮기면 — 고친 memo.txt가 어느 커밋에도 들어 있지 않은 채 main으로 따라옵니다. 두 브랜치는 같은 커밋 1을 가리킵니다.
- 둘 다 지우면 — 커밋은 1개뿐이고 두 브랜치는 같은 커밋 1을 가리킵니다. 고친 v2는 폴더에만 있습니다.
처음 설정으로 실행하면
- git init -b main: 이 폴더를 Git 저장소로 만듭니다. 폴더 안에 .git이라는 폴더가 생기고, 앞으로 커밋이 여기에 쌓입니다. -b main은 처음 쓸 브랜치의 이름을 main으로 하라는 뜻이고, 브랜치가 무엇인지는 뒤에서 봅니다. 아직 커밋이 하나도 없어서, main이라는 이름이 가리킬 커밋도 없습니다.
- echo v1 > memo.txt: 폴더에 memo.txt 파일을 만들고 안에 v1이라고 씁니다. 이 줄은 Git 명령이 아니라 폴더의 파일을 쓰는 명령입니다. Git은 이 파일을 아직 저장소에 올리지 않은 새 파일(??)로만 알려 줍니다.
- git add memo.txt: memo.txt의 지금 모습(v1)을 다음 커밋에 넣을 것으로 올려 둡니다. 아직 커밋은 아닙니다. Git이 이 파일을 커밋에 넣을 것으로 알려 줍니다(A).
- git commit -m "첫 커밋": 올려 둔 것을 커밋으로 저장합니다. 커밋 1이 만들어졌고, 그 안에는 이 순간의 memo.txt 모습(v1)이 들어 있습니다. 앞에 다른 커밋이 없는 맨 처음 커밋입니다. 이렇게 커밋 하나를 가리키는 이름표를 브랜치라고 하는데, main 브랜치가 커밋 1을 가리키게 되었습니다. 폴더의 파일과 커밋의 모습이 같아서, 저장하지 않은 변경은 없습니다.
- git switch -c feature: feature라는 새 이름표를 만들어 지금 있는 커밋 1을 가리키게 하고, 지금 있는 브랜치를 feature로 옮깁니다. 브랜치를 만들었는데 커밋은 늘지 않고 1개 그대로입니다. 브랜치는 커밋을 복사해 둔 것이 아니라 커밋 하나를 가리킬 뿐이라서, main과 feature가 같은 커밋 1을 함께 가리킵니다.
- echo v2 > memo.txt: 폴더의 memo.txt 안을 v2로 바꿉니다. 폴더의 파일만 바뀌었고 커밋 1 안의 memo.txt는 그대로 v1입니다. Git은 고쳤지만 커밋하지 않은 변경(M)으로 알려 줍니다. 이 v2는 아직 어느 커밋에도 들어 있지 않습니다.
- git commit -am "둘째 커밋": 바뀐 memo.txt를 올려서(-a) 커밋 2로 저장합니다. 커밋 2는 바로 앞 커밋인 커밋 1을 가리키고, 그 안에는 이 순간의 memo.txt 모습(v2)이 들어 있습니다. 지금 있는 브랜치인 feature 이름표가 커밋 2로 옮겨 갔습니다. main 이름표는 움직이지 않아서 커밋 1을 가리킨 채이고, 커밋 1 안의 memo.txt도 v1 그대로입니다.
- git switch main: 지금 있는 브랜치를 main으로 옮기고, 폴더의 파일을 main이 가리키는 커밋 1의 모습으로 바꿉니다. 그래서 폴더의 memo.txt가 v1이 되었습니다. 커밋 2는 사라지지 않았고, feature 이름표가 그대로 가리키고 있습니다.
- cat memo.txt: 폴더의 memo.txt를 화면에 보입니다. v1입니다. feature에서 v2로 고쳐 커밋했지만 그 모습은 커밋 2 안에 있고, 지금 폴더에는 main이 가리키는 커밋 1의 모습이 나와 있습니다.
실행 결과
- 커밋은 그 시점의 파일 전체 모습을 저장한 것이고 바로 앞 커밋을 가리킵니다. 브랜치(main, feature)는 커밋을 복사해 둔 것이 아니라 커밋 하나를 가리키는 이름표입니다. feature에서 커밋하면 feature 이름표만 커밋 2로 옮겨 가고 main은 커밋 1에 그대로 있어서, main으로 돌아오면 memo.txt는 v1입니다.
- git switch main은 이름표를 옮기고 폴더의 파일을 main이 가리키는 커밋의 모습으로 바꿨을 뿐입니다. 커밋 2는 지워지지 않고 feature 이름표가 가리키고 있습니다.
코드
# 이 폴더를 Git 저장소로 만든다 (첫 브랜치 이름은 main)
git init -b main
# memo.txt 파일을 만들고 v1이라고 쓴다
echo v1 > memo.txt
# memo.txt를 다음 커밋에 넣을 것으로 올린다
git add memo.txt
# 올려 둔 것을 커밋(저장)한다
git commit -m "첫 커밋"
# feature 브랜치를 새로 만들고 그쪽으로 옮겨 간다
git switch -c feature
# memo.txt의 내용을 v2로 바꾼다
echo v2 > memo.txt
# 바뀐 파일을 올리고(-a) 곧바로 커밋한다
git commit -am "둘째 커밋"
# main 브랜치로 옮겨 간다
git switch main
# memo.txt의 내용을 화면에 보인다
cat memo.txt
이 모형의 전제
git 2.55.0(Windows)로 실제 실행해 대조했습니다. 네 가지 조합을 모두, 조합마다 새 빈 폴더에서 화면의 코드(# 설명 줄은 실행하지 않음)를 한 줄씩 그대로 실행했습니다. git 명령은 git.exe에 인자 그대로 주었고(사용자 이름과 메일은 그 명령에만 -c로 주고 전역 설정은 읽지 않게 함), echo와 cat은 Git Bash가 실행했습니다. 줄마다 실행한 직후에 지금 있는 브랜치, main과 feature 브랜치가 가리키는 커밋, 만들어진 커밋 수, 폴더의 memo.txt, git status --short, 각 브랜치의 커밋 안에 든 memo.txt를 재서 화면 단계의 상태와 대조했고, 화면의 코드가 실행한 글과 같은 글인지도 조합마다 확인했습니다. 커밋 번호(해시)는 실행마다 달라지므로 만들어진 차례대로 커밋 1, 커밋 2로 적었습니다. 처음 브랜치를 main으로 하려고 git init -b main을 썼습니다. 이 실습의 둘째 커밋 줄이 없는 조합에서는 main과 feature가 같은 커밋 1을 가리켜, 커밋하지 않은 변경이 막히지 않고 main으로 따라왔습니다. 이것은 두 브랜치의 memo.txt가 같기 때문이며, 화면 조합 밖에서 따로 확인한 결과 두 브랜치의 memo.txt가 서로 다를 때(main은 v1, feature는 v2인 상태에서 main에서 memo.txt를 v3로 고치고 feature로 옮기려 할 때)는 Git이 옮기기를 막고 "Your local changes to the following files would be overwritten by checkout"이라는 오류를 냈습니다. 이 막힘은 화면 조합에 넣지 않았고, 이 Git 버전에서 본 동작입니다. 새 저장소의 .git/HEAD 파일은 지금 있는 브랜치 이름을("ref: refs/heads/main" 꼴) 적고, 브랜치 파일(.git/refs/heads/main 등)은 커밋 번호 하나만 적은 41바이트 파일임을 확인했습니다. 문서로 확인한 것은 Pro Git 2판 3.1절 Branches in a Nutshell(커밋은 스냅숏을 가리키고 부모를 가리킴, 브랜치는 커밋을 가리키는 가벼운 움직이는 포인터이자 SHA-1 40자를 담은 단순한 파일, HEAD는 지금 있는 브랜치를 가리키는 포인터, 브랜치를 바꾸면 작업 폴더의 파일이 바뀌고 깔끔하게 할 수 없으면 옮기지 못함), git-switch 문서(깨끗하지 않은 작업 폴더로도 옮길 수 있고 변경을 잃게 되면 중단함, -c는 새 브랜치를 만들고 옮김), git-commit 문서(-a는 이미 아는 파일의 변경을 자동으로 올림, -m은 메시지, 새 커밋은 HEAD의 자식이고 브랜치가 그 커밋을 가리키도록 갱신됨), git-init 문서(-b는 처음 브랜치의 이름)입니다. 커밋 하나가 "그 시점의 파일 전체 모습"이라는 설명은 문서의 스냅숏 서술을 쉽게 푼 것으로, 커밋 1과 커밋 2의 memo.txt 내용을 git show로 직접 확인했습니다(파일이 하나뿐인 저장소라서 전체 모습과 한 파일이 같습니다). 병합(merge, rebase), 원격 저장소, 충돌, 커밋 안에 파일이 어떤 형식으로 저장되는지(객체)는 다루지 않았습니다. 같은 주제의 세부 동작은 "세부 동작 실습: merge와 rebase"에서 다룹니다.
앞 실습: 배포