Git 기초: init·add·commit과 .gitignore 직접 해 보기
첫 커밋까지 5분. 작업 트리·스테이징·저장소의 차이, status/diff/log 읽는 법, .gitignore가 안 먹는 이유, 비밀번호를 커밋했을 때의 진짜 대처법을 실제 출력으로 설명합니다.
시리즈: Git 강좌 1/6 (초급). Git을 처음 쓰는 분, 또는 팀 위키의 명령어를 복사해서 감으로 쓰고 있는 분을 위한 글입니다. 다 읽으면 저장소를 만들고, 커밋하고, status·diff·log를 읽고, 비밀 정보와 빌드 산출물이 히스토리에 들어가지 않게 관리할 수 있습니다.
입사 첫 주에 "일단 레포에 올려 주세요"라는 말을 듣고 막막했던 신입 개발자를 위한 글입니다. 초보자가 헷갈리는 이유는 대부분 내 변경이 지금 어디에 있는지(디스크에만 있는지, 스테이징됐는지, 히스토리에 저장됐는지) 모르기 때문입니다. git status를 보고 이것을 읽어 낼 수 있으면 Git의 혼란은 대부분 사라집니다.
아래 내용은 모두 Git 2.54.0(Apple Git-157), Apple Silicon Mac(M1 Pro)의 임시 폴더에서 데모용 신원(demo <demo@example.com>)으로 실행했습니다. 출력은 찍힌 그대로 붙였고, 홈 폴더 경로만 ~로 줄였습니다.
변경이 머무는 세 곳
Git 오류 메시지에는 거의 항상 아래 세 단어가 나옵니다. 이것부터 익혀 둡시다.
- 작업 트리(working tree): 프로젝트 폴더에 있는 평범한 파일들. 여러분이 편집하는 곳입니다.
- 스테이징 영역(인덱스라고도 함): 다음 커밋에 들어갈 내용의 목록.
git add는 파일의 현재 내용을 여기에 복사합니다. - 저장소(repository): 숨김 폴더
.git. 모든 커밋이 저장됩니다. 커밋은 스테이징 영역의 스냅숏에 작성자, 날짜, 메시지, 이전 커밋을 가리키는 포인터를 붙인 것입니다.
처음에 낯선 것은 스테이징 영역입니다. 이게 있어서 변경의 일부만 커밋할 수 있습니다. 예를 들어 버그 수정과 오타 수정을 커밋 두 개로 나눌 수 있습니다.
5분 만에 첫 커밋
터미널을 열고 한 줄씩 실행하세요. 먼저 거의 모두가 한 번은 하는 실수, 저장소 밖에서 Git 명령을 실행해 봅니다.
git status
fatal: not a git repository (or any of the parent directories): .git
fatal:은 Git이 작업을 중단했다는 뜻이고, 종료 코드는 128이었습니다. 저장소를 만들거나 그 안으로 cd하면 해결됩니다.
mkdir shop && cd shop
git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: will change to "main" in Git 3.0. To configure the initial branch name
hint: to use in all of your new repositories, which will suppress this warning,
hint: call:
hint:
hint: git config --global init.defaultBranch <name>
...
Initialized empty Git repository in ~/demo/shop/.git/
hint: 줄은 조언이지 오류가 아닙니다. 요즘 팀은 대부분 main을 쓰고, git init -b main이라고 하면 그 저장소만 main으로 시작합니다. 파일을 하나 만들고 git status로 세 곳을 옮겨 다니는 모습을 봅시다.
printf 'print("hello, shop")\n' > app.py
git status
On branch master
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)
Untracked(추적 안 함)는 "보이긴 하지만 한 번도 저장한 적 없는 파일"이라는 뜻입니다. 스테이징하고 커밋합니다.
git add app.py
git commit -m "Add app.py"
git log
[master (root-commit) 8d02147] Add app.py
1 file changed, 1 insertion(+)
create mode 100644 app.py
commit 8d02147eba3b707bfb452bed66288443ef8f808b
Author: demo <demo@example.com>
Date: Thu Nov 12 01:00:00 2026 +0000
Add app.py
8d02147…은 커밋 ID로, 내용으로 계산한 해시값입니다. 앞 7글자만으로 가리킬 수 있습니다. root-commit은 부모가 없는 첫 커밋이라는 뜻입니다.
이름과 이메일을 먼저 설정하세요
실제 첫 커밋 전에 git config --global user.name "이름", git config --global user.email "<회사 이메일>"을 한 번만 설정합니다. 아무 설정이 없는 macOS에서 커밋해 보니 오류가 나지 않았습니다. Mac 계정 이름과 호스트 이름으로 만든 .local로 끝나는 주소를 Git이 알아서 만들어 커밋한 뒤에야 Your name and email address were configured automatically…라고 알려 줍니다. 이 반쯤 틀린 신원은 이미 커밋 안에 들어가 있습니다. 작업 전에 팀에서 쓰는 주소로 설정해 두세요.
status·diff·log 읽는 법
파일을 고치고 Git에게 무슨 일이 있었는지 물어봅니다.
printf 'print("hello, shop")\nprint("total:", 3 * 1200)\n' > app.py
git status
git diff
On branch master
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/or "git commit -a")
diff --git a/app.py b/app.py
index d0b0cc7..fb97e2d 100644
--- a/app.py
+++ b/app.py
@@ -1 +1,2 @@
print("hello, shop")
+print("total:", 3 * 1200)
+로 시작하는 줄은 추가, -는 삭제입니다. 이제 스테이징한 뒤 두 가지 diff를 실행합니다.
git add app.py
git diff # 아무것도 안 나옴
git diff --staged # 변경이 나옴
이 강좌에서 가장 쓸모 있는 사실이 이것입니다. git diff는 아직 스테이징하지 않은 변경을, git diff --staged는 다음 커밋에 들어갈 변경을 보여 줍니다. git add 직후 git diff가 텅 비어서 "변경이 날아갔다"고 놀라는 분이 많은데, 사라진 게 아니라 스테이징 영역으로 옮겨 간 것입니다.
git commit -m "Print total"
git log --oneline
292126a Print total
8d02147 Add app.py
| 알고 싶은 것 | 명령 |
|---|---|
| 어떤 파일이 바뀌었고 각각 어디에 있는지 | git status (짧게는 -s) |
| 스테이징 전 변경을 줄 단위로 | git diff |
| 다음 커밋에 들어갈 내용 | git diff --staged |
| 히스토리를 한 줄씩 | git log --oneline |
| 커밋마다 바뀐 파일 | git log --stat |
| 예전 시점의 파일 하나 | git show <커밋>:<경로> |
.gitignore로 쓰레기와 비밀 정보 막기
빌드 산출물, 캐시, 비밀번호가 든 .env는 절대 커밋하면 안 됩니다. 일부러 전형적인 실수를 해 봅시다. .env와 파이썬 캐시 폴더를 만들고 "전부 추가"인 git add .을 실행합니다.
printf 'DB_PASSWORD=demo-secret-123\n' > .env
mkdir __pycache__; printf 'x' > __pycache__/app.cpython-39.pyc
git add .
git status --short
git commit -m "Add config"
A .env
A __pycache__/app.cpython-39.pyc
[master 0d2cbec] Add config
2 files changed, 2 insertions(+)
create mode 100644 .env
create mode 100644 __pycache__/app.cpython-39.pyc
대부분 처음 시도하는 "해결책"은 .gitignore를 쓰는 것입니다.
printf '.env\n__pycache__/\n' > .gitignore
git status --short
git check-ignore -v .env; echo "exit=$?"
?? .gitignore
exit=1
git check-ignore는 아무것도 출력하지 않고 종료 코드 1, 즉 "무시되지 않음"이라고 답했습니다. .gitignore는 추적하지 않는 파일에만 적용됩니다. .env는 이미 추적 중이라 Git이 계속 지켜봅니다. --no-index를 붙이면 규칙 자체는 맞는다(.gitignore:1:.env .env)고 나오지만, 추적 중인 파일에는 쓰이지 않습니다. 디스크의 파일은 두고 추적만 끊으려면 이렇게 합니다.
git rm --cached .env __pycache__/app.cpython-39.pyc
git add .gitignore
git commit -m "Stop tracking .env and pycache"
rm '.env'
rm '__pycache__/app.cpython-39.pyc'
[master ba6ab87] Stop tracking .env and pycache
3 files changed, 2 insertions(+), 2 deletions(-)
delete mode 100644 .env
create mode 100644 .gitignore
delete mode 100644 __pycache__/app.cpython-39.pyc
ls -a로 보면 .env는 폴더에 그대로 있으니 로컬 설정은 계속 동작합니다.
흔한 실수: "지웠으니까 비밀번호도 없어졌다"
실제로 일어나는 일. 커밋 0d2cbec은 그대로 있고, 파일도 그 안에 있습니다.
git show HEAD~1:.env
git log --oneline -- .env
DB_PASSWORD=demo-secret-123
ba6ab87 Stop tracking .env and pycache
0d2cbec Add config
저장소를 clone한 사람은 누구나 같은 명령으로 볼 수 있습니다. 파일을 지우면 "그 파일이 없는 새 스냅숏"이 하나 늘어날 뿐, 히스토리는 바뀌지 않습니다.
그럼 아직 푸시하지 않은 커밋이라면? 흔히 쓰는 git commit --amend(마지막 커밋 다시 만들기)를 해 봤습니다.
git rm --cached .env
printf '.env\n' > .gitignore
git add .gitignore
git commit --amend -m "Add settings (without .env)"
git reflog
git show 'HEAD@{1}:.env'
75ed8d6 HEAD@{0}: commit (amend): Add settings (without .env)
88dcb5b HEAD@{1}: commit: Add settings
7b51afc HEAD@{2}: commit (initial): Add app.py
DB_PASSWORD=demo-secret-123
브랜치에서는 비밀번호가 사라졌지만, 예전 커밋은 로컬 .git에 남아 있고 reflog(HEAD가 어디를 가리켰는지 기록하는 Git의 로컬 일기장)로 찾아갈 수 있습니다. 어떤 브랜치도 가리키지 않으니 푸시로 전송되지는 않고, 나중에 만료되어 지워집니다. reflog는 5강에서 자세히 다룹니다.
해결 순서:
- 유출된 것으로 보고 비밀번호를 바꾸거나 키를 폐기하는 것이 먼저입니다. 이미 푸시했다면 실제로 나를 지켜 주는 건 이것뿐입니다.
- 푸시 전:
git rm --cached,.gitignore에 추가,git commit --amend. - 푸시 후: 히스토리에서 지우려면 공유 히스토리를 다시 써야 하고(
git filter-repo같은 별도 도구. Git에 기본 포함되어 있지 않고 여기서는 실행하지 않았습니다), 팀원 모두가 다시 clone해야 합니다. 그래도 1번이 먼저입니다. - 예방: 첫
git add .전에.gitignore를 만들고, 익숙해질 때까지는git add <파일명>으로 하나씩 추가하세요.
실측: 빌드 폴더를 실수로 git add . 하면
실무에서 자주 나는 사고가 큰 빌드 폴더가 있는 프로젝트에서 git add .을 치는 것입니다. 커밋된 파일이 하나뿐인 저장소에 작은 파일 2만 개(개당 16~20바이트, build/d0…d199/f0…f99.o)를 만들고 Apple Silicon Mac(M1 Pro), Git 2.54.0에서 쟀습니다. 시간은 각 7회 실행의 중앙값이고, git add .은 한 번만 실행했습니다.
| 상태 | git status 중앙값 |
.git 디스크 사용량 |
|---|---|---|
build/ 추적 안 함(추가 전) |
16.6 ms | 112 KB |
git add . 후 (소요 15.2초) |
55.0 ms | 82,284 KB |
git reset으로 스테이징 해제 후 |
— | 80,116 KB |
git gc --prune=now 후 |
— | 128 KB |
build/를 .gitignore에 추가 |
13.3 ms | — |
교훈은 두 가지입니다. 첫째, git add는 그 자리에서 모든 파일을 객체로 .git에 씁니다. git reset으로 내려도 객체는 지워지지 않고, 가비지 컬렉션 전까지 아무도 가리키지 않는 데이터로 남습니다. 둘째, 표의 숫자는 디스크 사용량입니다. APFS에서는 작은 객체도 하나에 4KiB 블록을 차지해서, 20바이트도 안 되는 객체 2만 개가 약 80MB가 됐습니다. 큰 바이너리가 있는 실제 프로젝트에서는 더 심각하고, 커밋까지 하면 히스토리에 영원히 남습니다.
솔직한 한계와 버전 메모
- 실행 환경은 Git 2.54.0(Apple Git-157), macOS, Apple Silicon(M1 Pro)입니다.
master/Git 3.0 힌트와git config set advice…문구는 이 버전의 것이고, 예전 Git은 더 짧은 힌트를 냅니다. git status가 안내하는git restore는git switch와 함께 Git 2.23(2019년)에 추가됐습니다. 예전 자료에서는 같은 일을git checkout -- <파일>로 합니다.- macOS가 신원을 자동으로 만들어 넣는 동작은 전역 설정이 없는 이 Mac에서 본 것입니다. 다른 환경에서는
Author identity unknown으로 커밋 자체를 거부하기도 합니다. - 시간은 디스크와 파일 수에 따라 달라지는 한 대의 기계에서 잰 숫자이고, 벤치마크가 아닙니다.
- 히스토리 재작성 도구는 설명만 했고 실행하지 않았습니다.
연습 문제
- 새 저장소에
a.txt와b.txt를 만들고a.txt만 커밋하세요.b.txt가 아직 추적되지 않음을 확인하는 명령은? app.py를 고치고git add app.py를 했습니다.git diff가 아무것도 출력하지 않습니다. 변경을 보려면?notes.log는 이미 커밋돼 있습니다..gitignore에*.log를 넣었는데도git status에 수정됨으로 나옵니다. 디스크에서 지우지 않고 해결하세요.
정답
git add a.txt && git commit -m "Add a"다음git status(git status -s라면?? b.txt가 나옵니다).git diff --staged. 스테이징된 변경은 그냥git diff에 나오지 않습니다.git rm --cached notes.log후git commit -m "Stop tracking notes.log".git check-ignore -v notes.log가 일치한 규칙을 출력하면 성공입니다.
다음 강좌: Git 강좌 2: 브랜치·병합·충돌 · 시리즈: 원격 저장소와 push·pull·fetch · 신입 개발자 학습 로드맵으로 돌아가기. 명령줄 자체가 처음이라면 셸 기초부터 보세요.
자주 묻는 질문
.gitignore에 적었는데 파일 변경이 계속 보입니다. 왜 그런가요?
.gitignore는 아직 추적하지 않는 파일에만 적용됩니다. 이미 커밋한 파일은 계속 추적됩니다. git rm --cached <파일>로 추적만 끊고(디스크에는 남습니다) 커밋하세요. 추적 중인 파일에 git check-ignore -v를 실행하면 아무것도 출력되지 않아서 바로 알 수 있습니다.
비밀번호를 커밋했습니다. 다음 커밋에서 파일을 지우면 되나요?
안 됩니다. 예전 커밋에 그대로 남아 있고, 실험에서도 git show HEAD~1:.env로 비밀번호가 그대로 나왔습니다. 먼저 유출된 것으로 보고 비밀번호나 키를 바꾸세요. 푸시 전이라면 git commit --amend로 브랜치에서는 없앨 수 있지만, 그래도 예전 커밋은 만료될 때까지 reflog로 찾아갈 수 있습니다.
git diff와 git diff --staged는 무엇이 다른가요?
git diff는 작업 트리와 스테이징 영역의 차이(아직 add하지 않은 변경), git diff --staged는 스테이징 영역과 마지막 커밋의 차이(다음 커밋에 들어갈 내용)를 보여 줍니다. 실험에서 git add 직후 git diff는 아무것도 출력하지 않았고, git diff --staged에만 변경이 나왔습니다.