Git 기초 #2 add·commit·status — 스테이징 영역의 의미
지난 글에서 Git의 두 가지 핵심 모델을 세웠습니다. 커밋은 스냅샷이고, 파일은 working directory, staging area, repository 세 영역을 거쳐 기록됩니다. 이번 글은 그 모델을 손으로 확인하는 편입니다. 지난 글에서 만든 hello-git 저장소에 실제 파일을 만들어 status, add, commit, diff를 차례로 실행하고, 마지막에 .gitignore로 기록하지 않을 파일을 정리합니다.
총 6편으로 구성됩니다.
- #1 Git이란 — 스냅샷 모델과 세 영역
- #2 add·commit·status — 스테이징 영역의 의미 ← 이번 글
- #3 브랜치와 머지 — fast-forward와 3-way
- #4 원격 저장소 — clone·fetch·pull·push와 origin의 정체
- #5 되돌리기 — restore·reset·revert 구분
- #6 GitHub와 첫 Pull Request
이번 글을 마치면 Git의 가장 기본 루프인 고치고, 골라 담고, 기록하는 한 사이클을 혼자 돌릴 수 있게 됩니다.
첫 파일 만들기 #
hello-git 디렉터리에서 시작합니다. 파일을 하나 만듭니다.
cd hello-git
echo "print('hello git')" > app.py파일을 만들었을 뿐 Git 명령은 아직 아무것도 실행하지 않았습니다. 지금 Git이 이 상황을 어떻게 보고 있는지부터 확인합니다.
git status — 상태를 읽는 법 #
git status는 세 영역의 현재 상태를 요약해 주는 명령입니다. 앞으로 어떤 작업을 하든 가장 자주 실행하게 됩니다.
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
app.py
nothing added to commit but untracked files present (use "git add" to track)app.py가 Untracked files 아래에 있습니다. untracked는 working directory에는 존재하지만 Git이 아직 추적하지 않는 새 파일이라는 뜻입니다. Git은 지시 없이는 어떤 파일도 마음대로 기록하지 않으므로, 새 파일은 전부 untracked에서 출발합니다.
친절하게도 출력 안에 다음 명령이 적혀 있습니다. git add로 이 파일을 추적 대상에 올립니다.
git add app.py
git statusOn branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: app.py이번에는 Changes to be committed 아래로 옮겨 갔습니다. staged 상태, 즉 다음 커밋에 포함될 목록인 staging area에 올라간 상태입니다. 아직 기록된 것은 아니고, 기록될 예정인 상태입니다.
세 번째 상태인 modified는 커밋을 한 번 만든 뒤에 등장하므로, 먼저 커밋부터 만들고 돌아오겠습니다.
git commit — 첫 스냅샷 #
git commit -m "Add greeting script"
# [main (root-commit) 3f2b1a9] Add greeting script
# 1 file changed, 1 insertion(+)
# create mode 100644 app.py-m 뒤의 문자열이 커밋 메시지입니다. 이 커밋으로 staging area에 있던 내용이 스냅샷으로 repository에 영구 기록됐습니다. 출력의 3f2b1a9는 커밋을 가리키는 고유 식별자(해시)의 앞부분으로, 실행할 때마다 다른 값이 나옵니다.
커밋 메시지는 처음부터 습관을 잡아 두는 편이 좋습니다. 기본 원칙은 두 가지입니다.
- 제목 한 줄로 무엇을 했는지 요약합니다. “수정”, “작업 중” 같은 메시지는 이력을 조회할 때 아무 정보도 주지 못합니다.
- 커밋 하나에는 한 가지 변경만 담습니다. 버그 수정과 새 기능이 한 커밋에 섞이면 나중에 하나만 되돌리기 어렵습니다. #5에서 이 원칙의 가치를 그대로 체감하게 됩니다.
이제 파일을 한 번 고쳐서 세 번째 상태를 확인합니다.
echo "print('bye git')" >> app.py
git statusOn branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: app.py
no changes added to commit (use "git add" and "git commit -a" to use)Changes not staged for commit, 즉 modified 상태입니다. Git이 추적 중인 파일이 마지막 커밋 이후 달라졌지만, 아직 staging area에는 올라가지 않았다는 뜻입니다. 정리하면 파일의 상태는 이렇게 순환합니다. 새 파일은 untracked로 시작해 git add를 거치면 staged가 되고, git commit으로 기록되면 깨끗한 상태로 돌아갑니다. 파일을 다시 고치면 modified, 다시 add하면 staged가 되는 순환입니다.
스테이징 영역은 왜 존재할까요? #
고친 것을 전부 기록하면 될 텐데 왜 add라는 중간 단계가 있을까요? 답은 커밋 단위를 설계하기 위해서입니다.
실제 작업을 하다 보면 working directory에는 성격이 다른 변경이 섞이기 마련입니다. 버그를 고치다가 눈에 띈 오타를 함께 수정하고, 설정 파일도 하나 손봤다고 하면, 이 셋을 한 커밋에 넣는 순간 “한 커밋에 한 가지 변경” 원칙이 깨집니다. 스테이징 영역이 있으면 버그 수정 파일만 add해서 커밋하고, 오타 수정만 add해서 커밋하는 식으로 골라 담기가 가능합니다. 이력이 변경 단위로 깔끔하게 나뉘고, 되돌리기(#5)와 리뷰가 모두 쉬워집니다.
파일 단위보다 더 잘게 고를 수도 있습니다. git add -p는 한 파일 안의 변경을 조각(hunk) 단위로 보여 주며 하나씩 물어봅니다.
git add -p app.py
# @@ -1 +1,2 @@
# print('hello git')
# +print('bye git')
# (1/1) Stage this hunk [y,n,q,a,d,e,?]? yy는 담기, n은 건너뛰기입니다. 한 파일 안에 두 가지 작업이 섞였을 때 유용합니다.
git diff — 무엇이 달라졌는지 #
status가 어느 영역에 무엇이 있는지를 알려 준다면, diff는 내용이 어떻게 달라졌는지를 보여 줍니다. 비교 대상이 다른 두 형태를 구분해야 합니다.
| 명령 | 비교 대상 | 답하는 질문 |
|---|---|---|
git diff | working directory ↔ staging area | 아직 add하지 않은 수정이 무엇인가? |
git diff --staged | staging area ↔ 마지막 커밋 | 지금 커밋하면 무엇이 기록되는가? |
커밋 직전에 git diff --staged로 기록될 내용을 눈으로 확인하는 습관을 들이면, 의도하지 않은 변경이 섞여 들어가는 사고를 대부분 막을 수 있습니다.
남은 수정을 마저 커밋하고 이력을 확인합니다.
git add app.py
git commit -m "Add farewell line"
git log --oneline
# 8c4d2e7 (HEAD -> main) Add farewell line
# 3f2b1a9 Add greeting scriptgit log는 커밋 사슬을 최신순으로 보여 줍니다. --oneline은 한 커밋을 한 줄로 요약하는 옵션으로, 지난 글에서 그린 스냅샷의 사슬이 실제로 쌓이고 있음을 이 출력에서 확인할 수 있습니다.
.gitignore — 기록하지 않을 파일 #
프로젝트 폴더에는 이력으로 남길 필요가 없거나 남겨서는 안 되는 파일도 생깁니다. 대표적으로 세 부류입니다.
- 다시 만들 수 있는 것 — 빌드 산출물(
dist/,build/), 캐시(__pycache__/) - 내려받으면 되는 것 — 의존성 디렉터리(
node_modules/,.venv/) - 절대 공유하면 안 되는 것 — 비밀키, 토큰, 로컬 환경 설정(
.env)
프로젝트 최상위에 .gitignore라는 파일을 만들어 패턴을 적어 두면, 해당 파일은 untracked 목록에도 나타나지 않고 git add . 같은 일괄 명령에도 딸려 들어가지 않습니다.
# 의존성
node_modules/
.venv/
# 빌드 산출물과 캐시
dist/
build/
__pycache__/
# 비밀키와 로컬 환경
.env
*.pem
# OS, 에디터 부산물
.DS_Store.gitignore 자체는 커밋해서 팀 전체가 공유하는 파일입니다. 한 가지 주의할 점이 있습니다. 이미 추적 중인 파일은 나중에 .gitignore에 추가해도 계속 추적됩니다. 이 경우 git rm --cached <파일>로 추적만 해제한 뒤 커밋해야 합니다.
.gitignore에 먼저 등록하는 순서를 습관으로 만들어 두는 것이 가장 확실한 예방입니다.마무리 #
이번 글에서 Git의 기본 루프 한 사이클을 완주했습니다.
git status— untracked, staged, modified 세 상태를 읽습니다.git add— 다음 커밋에 담을 변경을 골라 staging area에 올립니다. 스테이징 영역의 존재 이유는 커밋 단위의 설계입니다.git commit— staging area의 내용으로 스냅샷을 기록합니다. 한 커밋에는 한 가지 변경만 담습니다.git diff와git diff --staged— add 전과 커밋 전에 내용을 확인합니다..gitignore— 산출물, 의존성, 비밀키를 이력에서 제외합니다.
다음 글인 “Git 기초 #3 브랜치와 머지 — fast-forward와 3-way"에서는 Git의 스냅샷 모델이 진가를 드러내는 브랜치를 다룹니다. 브랜치가 왜 포인터 하나에 불과한지, 두 갈래로 갈라진 작업이 어떻게 다시 합쳐지는지를 확인하겠습니다.