🌿 git 기초 — 첫 커밋부터 비밀키 보호까지
study/my-board 에 .gitignore 와 첫 커밋이 만들어지고, GitHub 에 push 되어 있습니다.
왜 필요한가
git 은 작업의 세이브 파일이다. 게임에서 보스 앞에 세이브하고 죽으면 되돌아가듯, 코드도 "여기까진 됐다" 지점을 찍어두고 언제든 돌아올 수 있다.
혼자 공부하는데 왜 필요한가? 세 가지 때문이다.
- 되돌리기. 4장에서 DB 를 붙이다가 코드를 망가뜨렸을 때, 어제 커밋으로 1초 만에 돌아간다.
- 배포. 9장에서 EC2 에 코드를 올리는 방법이
git clone이다. git 이 없으면 배포 자체가 안 된다. - 비밀값 보호.
.env에 든 DB 비밀번호와 JWT 시크릿이 GitHub 에 올라가면 진짜 사고가 난다. 이걸 막는 게.gitignore다.
명세의 완료 조건 12번이 바로 이거다. ".env·비밀키가 git 에 한 번도 올라간 적 없다."
개념
git 의 3개 공간
[작업 폴더] [스테이지] [저장소(.git)]
working tree staging area repository
│ │ │
│ git add ─────────▶│ │
│ │ git commit ───────▶│
│◀──────────────────────── git checkout ── │
│ │
└──────────── git push ───────────────────▶ [GitHub]
| 공간 | 비유 | 확인 명령 |
|---|---|---|
| 작업 폴더 | 작업 중인 책상 | git status |
| 스테이지 | 사진 찍을 사람들을 모아둔 자리 | git status (초록색 목록) |
| 저장소 | 앨범 (커밋 = 사진 한 장) | git log |
| 원격(GitHub) | 클라우드 백업 앨범 | git remote -v |
왜 add 와 commit 이 나뉘어 있나? 한 번에 여러 파일을 고쳤어도 "관련 있는 것만 골라서" 한 커밋으로 묶기 위해서다. add 는 고르는 행위, commit 은 저장하는 행위다.
파일의 4가지 상태
?? file.ts = untracked(git 이 모르는 새 파일), M file.ts = modified(내용만 바뀜), M file.ts(앞칸) = staged(add 해서 커밋 대기), 아무것도 안 보이면 committed.
브랜치
브랜치는 "평행 세계"다. 실무에선 기능마다 브랜치를 따지만, 이 스터디는 혼자 하니 main 하나로 충분하다.
브랜치를 나누는 건 12주 이후에 배워도 늦지 않다. 지금은 main 에 계속 커밋하는 게 더 빨리 완주하는 길이다.
따라하기
1단계. 저장소 만들기
mkdir -p ~/Desktop/study/my-board
cd ~/Desktop/study/my-board
git init
git status
Initialized empty Git repository in /Users/me/Desktop/study/my-board/.git/
On branch main
No commits yet
On branch main 이 나와야 한다. master 가 나오면 00-환경설정.md 의 git config --global init.defaultBranch main 을 빠뜨린 것이다. 지금 고치려면:
git branch -M main
2단계. .gitignore 부터 만든다 — 순서가 중요하다
.gitignore 를 먼저 만든다. 한 번 커밋된 파일은 나중에 .gitignore 에 넣어도 계속 추적된다. 순서를 바꾸면 고생한다.
cat > .gitignore <<'EOF'
# ── 의존성 ──
node_modules/
# ── 빌드 산출물 ──
dist/
.next/
out/
build/
*.tsbuildinfo
# ── 비밀값 (가장 중요) ──
.env
.env.local
.env.development
.env.production
*.pem
*.key
# 값이 없는 템플릿은 팀이 봐야 하므로 예외로 추적한다
!.env.example
# ── 로그 ──
logs/
*.log
npm-debug.log*
# ── macOS / 에디터 ──
.DS_Store
.idea/
.vscode/*
!.vscode/extensions.json
# ── 테스트 ──
coverage/
EOF
cat .gitignore
| 패턴 | 왜 제외하나 |
|---|---|
node_modules/ | 수만 개 파일에 수백 MB. package.json 만 있으면 npm ci 로 언제든 복원된다 |
dist/ .next/ | 소스에서 다시 만들어지는 결과물. 커밋하면 충돌만 난다 |
.env | DB 비밀번호, JWT 시크릿, AWS 키가 들어 있다. 절대 올리면 안 된다 |
*.pem *.key | EC2 접속용 비밀키 (6장) |
logs/ *.log | 계속 커지는 로그 파일 |
.DS_Store | 맥이 폴더마다 자동으로 만드는 쓰레기 파일 |
!.env.example | ! 는 예외다. 값이 빈 템플릿은 팀이 봐야 하니 올린다 |
.env 는 막고 .env.example 은 올리는 이 조합이 실무 표준이다. 명세 8절의 환경변수 목록이 바로 .env.example 의 내용이 된다.
3단계. 첫 커밋
cat > README.md <<'EOF'
# my-board
풀스택 완주 프로젝트. Next.js(3100) + NestJS/Fastify(9100) + MySQL(my_board).
EOF
git status
git add .gitignore README.md
git status
git commit -m "chore: 프로젝트 초기화 (.gitignore, README)"
git log --oneline
a1b2c3d chore: 프로젝트 초기화 (.gitignore, README)
git add . 로 전부 담을 수도 있지만, 처음엔 파일 이름을 직접 적는 습관을 들이는 게 안전하다. 실수로 .env 를 담는 사고를 막아준다.
4단계. 커밋 메시지 규칙
참고 레포들처럼 앞에 종류를 붙인다. feat:(새 기능) fix:(버그) chore:(설정·잡일) refactor:(정리) docs:(문서) test:(테스트).
git commit -m "feat: posts 목록 API 추가"
한글로 써도 된다. 중요한 건 "무엇을 왜 바꿨는지"가 보이는 것이다. 수정, ㅇㅇ, asdf 같은 메시지는 3주 뒤의 자신을 괴롭힌다.
5단계. 상태 확인 3종 세트
echo "추가 줄" >> README.md
git status # 뭐가 바뀌었나
git status -sb # 짧게 (실무에서 이걸 더 많이 쓴다)
git diff # 아직 add 안 한 변경 내용
git add README.md
git diff --staged # add 한 변경 내용
git log --oneline --graph --decorate -10
| 명령 | 보여주는 것 |
|---|---|
git status | 파일 단위로 무엇이 바뀌었나 |
git diff | 줄 단위로 무엇이 바뀌었나 (add 전) |
git diff --staged | 줄 단위 (add 후, 커밋 직전 최종 점검) |
git log --oneline | 커밋 이력 한 줄씩 |
git show HEAD | 마지막 커밋의 전체 변경 내용 |
git diff --staged 를 보는 습관이 비밀값 유출을 막는 마지막 방어선이다.
6단계. GitHub 원격에 올리기
GitHub 에서 New repository → 이름 my-board → Private 선택 → README·gitignore 는 추가하지 않음(이미 우리가 만들었다) → Create.
그다음 나오는 주소를 쓴다. HTTPS 대신 SSH 방식을 권한다. 매번 비밀번호를 안 물어본다.
# SSH 키가 없으면 한 번만 만든다
ls ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519 -C "본인이메일@example.com"
cat ~/.ssh/id_ed25519.pub
출력된 ssh-ed25519 AAAA... 전체를 복사해서 GitHub → 우측 상단 프로필 → Settings → SSH and GPG keys → New SSH key 에 붙여 넣는다.
ssh -T git@github.com # "Hi 아이디! You've successfully authenticated" 가 나오면 성공
git remote add origin git@github.com:본인아이디/my-board.git
git remote -v
git push -u origin main
-u 는 "앞으로 git push 만 쳐도 여기로 보내"라는 뜻이다. 한 번만 붙이면 된다.
7단계. 실수 복구 3가지
git 의 진짜 가치는 여기 있다.
(1) 파일을 망가뜨렸다. 마지막 커밋 상태로 되돌리고 싶다.
echo "망가뜨림" > README.md
git status
git restore README.md # 최신 문법
# git checkout -- README.md # 예전 문법. 같은 동작
cat README.md
(2) 커밋을 했는데 메시지가 틀렸거나 파일을 빠뜨렸다.
git commit --amend -m "chore: 프로젝트 초기화 (.gitignore, README, 포트 메모)"
(3) 커밋을 통째로 취소하고 싶다.
git reset --soft HEAD~1 # 커밋만 취소. 변경 내용은 스테이지에 그대로 (가장 안전)
git reset --mixed HEAD~1 # 커밋+add 취소. 변경 내용은 작업 폴더에 남음 (기본값)
git reset --hard HEAD~1 # 커밋+변경 내용까지 전부 삭제 (위험)
| 옵션 | 커밋 | 스테이지 | 작업 파일 | 언제 |
|---|---|---|---|---|
--soft | 취소 | 유지 | 유지 | 메시지만 다시 쓰고 싶을 때 |
--mixed | 취소 | 취소 | 유지 | 다시 나눠서 커밋하고 싶을 때 |
--hard | 취소 | 취소 | 삭제 | 정말 다 버릴 때만 |
--hard 는 습관적으로 쓰지 않는다. 헷갈리면 --soft 를 쓴다. 손실이 없다.
(4) 최후의 보험: reflog. --hard 로 날려도 살릴 수 있다. git 은 모든 HEAD 이동을 기록한다. git reflog 로 돌아갈 지점의 해시를 찾아 git reset --hard <해시>.
8단계. 비밀키를 커밋해 버렸을 때
가장 중요한 절이다. 5장에서 JWT_SECRET, 6장에서 AWS 키를 다루니 언젠가 겪는다.
상황 A. 아직 커밋 안 했다 (git status 에만 보임)
git restore --staged .env # 스테이지에서 빼고
echo ".env" >> .gitignore # .gitignore 에 추가
git status # .env 가 목록에서 사라졌는지 확인
상황 B. 커밋은 했지만 push 는 안 했다
git reset --soft HEAD~1 # 커밋 취소
git restore --staged .env # 스테이지에서 제외
echo ".env" >> .gitignore
git add .gitignore
git commit -m "chore: .env 를 gitignore 에 추가"
git log -p | grep -c "JWT_SECRET" # 0 이 나와야 한다
상황 C. 이미 push 했다
순서를 지킨다. 1번을 먼저 한다.
-
값을 즉시 무효화한다 (가장 먼저).
JWT_SECRET→ 새 값으로 교체. 기존 토큰은 전부 무효가 된다 (정상)- AWS 키 → AWS 콘솔 → IAM → 사용자 → 보안 자격 증명 탭 → 해당 액세스 키 비활성화 후 삭제 → 새 키 발급
- DB 비밀번호 → RDS 또는 MySQL 에서 변경
-
추적에서 제외한다.
git rm --cached .env # 파일은 남기고 git 추적만 해제 echo ".env" >> .gitignore git commit -m "chore: .env 추적 제외" git push -
이력에서도 지운다 (선택이지만 권장).
brew install git-filter-repo후git filter-repo --invert-paths --path .env --force→git remote add origin ...→git push --force origin main. 모든 커밋 해시가 다시 만들어지므로, 협업 중이라면 팀 전원이 다시 clone 해야 한다. -
검증한다.
빈 결과면 끝. 이게 명세 완료 조건 12번이다.git log -p | grep -i "JWT_SECRET\|AWS_SECRET\|password" | head
예방책 — 커밋 전에 막기
.git/hooks/pre-commit 에 간단한 검사를 넣어두면 사고 자체를 막는다.
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
if git diff --cached --name-only | grep -qE '(^|/)\.env$|\.pem$|\.key$'; then
echo "중단: .env / .pem / .key 가 커밋에 포함되어 있다."
echo "git restore --staged <파일> 로 제외한 뒤 다시 커밋한다."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
이제 .env 를 실수로 add 해도 커밋이 막힌다.
확인 — 이렇게 보이면 성공
cd ~/Desktop/study/my-board
git add -A && git commit -qm "docs: README 보강" # 5단계에서 고친 것 먼저 정리
echo "SECRET=1234" > .env # 일부러 비밀 파일을 만들어 본다
git status -sb
## main...origin/main
.env 가 목록에 안 보이면 성공이다. .gitignore 가 제대로 먹고 있다는 뜻이다. (아직 push 전이라면 ## main 만 나온다.)
git check-ignore -v .env
.gitignore:12:.env .env
".gitignore 12번째 줄 때문에 무시된다"는 뜻이다. 커밋 이력도 확인한다.
git log --oneline
git remote -v
b2c3d4e docs: README 보강
a1b2c3d chore: 프로젝트 초기화 (.gitignore, README)
origin git@github.com:본인아이디/my-board.git (fetch)
origin git@github.com:본인아이디/my-board.git (push)
🔧 흔한 에러
| 증상 | 원인 | 해결 |
|---|---|---|
fatal: not a git repository | git init 을 안 했거나 다른 폴더에 있음 | pwd 확인 후 프로젝트 루트에서 git init |
Author identity unknown | user.name / user.email 미설정 | git config --global user.name "홍길동" git config --global user.email "메일" |
.gitignore 에 넣었는데 계속 추적됨 | 이미 커밋된 파일은 .gitignore 가 안 먹는다 | git rm --cached 파일명 으로 추적 해제 후 커밋 |
Permission denied (publickey) | GitHub 에 SSH 공개키를 안 등록함 | cat ~/.ssh/id_ed25519.pub 을 GitHub Settings → SSH keys 에 등록. ssh -T git@github.com 으로 확인 |
Updates were rejected ... fetch first | 원격이 로컬보다 앞서 있음 | git pull --rebase origin main 후 다시 push |
error: src refspec main does not match any | 커밋이 하나도 없음 | 먼저 git add + git commit 을 한 뒤 push |
git commit 을 쳤더니 vim 같은 편집기가 열림 | -m 을 빼먹음 | :q! 로 나간 뒤 git commit -m "메시지". 또는 core.editor 를 code --wait 로 설정 |
--hard 로 작업을 날렸다 | 되돌리기 명령을 잘못 씀 | git reflog 로 직전 해시를 찾아 git reset --hard <해시> |
node_modules 가 커밋되어 push 가 매우 느림 | .gitignore 없이 git add . 을 함 | git rm -r --cached node_modules 후 .gitignore 추가하고 커밋 |
참고 레포에서 보기
| 파일 | 무엇을 볼지 |
|---|---|
/Users/me/Desktop/plutosaju/plutosaju_server/.gitignore | 서버용 .gitignore. node_modules/, dist/, .env, logs/ 가 어떻게 적혀 있는지 본다. 우리가 만든 것과 비교 |
/Users/me/Desktop/runepluto/runepluto_web/.gitignore | Next.js 용. .env* 로 전부 막고 !.env.example 로 템플릿만 예외 처리한 부분, /.next/ *.tsbuildinfo 제외를 확인한다. 주석으로 왜 예외인지까지 적어둔 게 좋은 습관이다 |
/Users/me/Desktop/runepluto/runepluto_server/.env.example | 실제 커밋되어 있는 템플릿. 값이 전부 비어 있고 키 이름과 설명만 있다. 우리 server/.env.example 도 이렇게 만든다 (명세 8절) |
/Users/me/Desktop/runepluto/runepluto_server/ddl/CHANGELOG.md | DB 스키마 변경을 날짜순으로 누적 기록한 파일. 커밋 메시지만으로 부족한 이력을 문서로 남기는 방식이다 (4장에서 따라 한다) |
📝 과제
-
my-board저장소 완성.git init→.gitignore작성 →README.md작성 → 첫 커밋 → GitHub private 저장소에 push. 완료 조건: GitHub 웹에서 저장소를 열었을 때README.md와.gitignore두 파일만 보인다. (주차 플랜 1주차 완료 조건에 해당) -
.gitignore동작을 직접 증명하기.echo "JWT_SECRET=abcd" > .env로 파일을 만든 뒤git status에 안 나오는 것을 확인하고,git check-ignore -v .env로 몇 번째 줄 규칙 때문인지 출력한다. 완료 조건:git add .을 실행해도.env가 스테이지에 안 담긴다. -
복구 리허설.
README.md를 3번 고쳐 3개의 커밋을 만든다. 그다음 ①git restore로 저장 안 한 변경 버리기, ②git commit --amend로 메시지 고치기, ③git reset --soft HEAD~1로 커밋 취소 후 재커밋을 각각 해본다. 마지막에git reflog를 열어 지금까지의 이동 이력을 읽는다. 완료 조건: 세 가지를 전부 실행했고,git log --oneline이 의도한 커밋 3개를 보여준다.
🎯 요약 (3줄)
git add(고르기) →git commit(저장) →git push(백업) 세 박자. 커밋 전엔git diff --staged로 무엇이 담겼는지 반드시 본다..gitignore는 파일을 만들기 전에 먼저 만든다.node_modules,.next,dist,.env는 무조건 제외하고.env.example만 예외로 올린다.- 비밀키를 올렸다면 지우기보다 값 교체가 먼저다. 실수는
git restore/git commit --amend/git reset --soft로 대부분 복구되고, 최후엔git reflog가 있다.
'코딩 시작하기' 카테고리의 다른 글
| 터미널 명령어 정리 - 개발자가 진짜 쓰는 20개 (초보자용 치트시트) (0) | 2026.09.20 |
|---|




























