재엽님이 실습하신거에 내용을 추가하면서 제가 아는 것 까지 넣어서 길어졌습니다
이해가 안 되시면 여러번 읽고 실습도 해보세요
# Git 가이드
## 1. Git이란?
Git은 **분산 버전 관리 시스템(DVCS)**이다.
파일의 변경 이력을 추적하고, 여러 사람이 협업할 수 있게 해준다.
### 버전 관리가 왜 필요한가?
버전 관리 없이 작업하면 이런 상황이 발생한다:
```
보고서_최종.docx
보고서_최종_수정.docx
보고서_진짜최종.docx
보고서_진짜최종_v2.docx
```
Git을 사용하면 **하나의 파일**만 유지하면서, 모든 변경 이력을 기록하고 언제든 과거로 돌아갈 수 있다.
### 중앙집중식 vs 분산식
```
[중앙집중식 (SVN)] [분산식 (Git)]
서버 서버
| / | \
클라이언트 로컬저장소 로컬저장소 로컬저장소
(이력 없음) (전체 이력) (전체 이력) (전체 이력)
```
- **중앙집중식**: 서버가 다운되면 아무도 작업 불가
- **분산식 (Git)**: 각자 전체 이력을 가지고 있어서 서버 없이도 작업 가능. 네트워크 없이 커밋, 브랜치 생성, 이력 조회가 모두 가능하다.
### Git의 핵심 개념: 스냅샷
Git은 파일의 **변경 차이(diff)**가 아니라 **스냅샷(snapshot)**을 저장한다.
```
커밋1: [파일A v1] [파일B v1] [파일C v1]
커밋2: [파일A v2] [파일B v1] [파일C v1] ← 파일A만 변경됨
커밋3: [파일A v2] [파일B v2] [파일C v2] ← 파일B, C 변경됨
```
변경되지 않은 파일은 이전 스냅샷에 대한 링크만 저장하므로 공간 낭비가 없다.
---
## 2. Git 초기 설정
Git을 처음 설치하면 사용자 정보를 설정해야 한다. 이 정보는 모든 커밋에 기록된다.
```bash
# 필수 설정 (커밋에 기록되는 이름과 이메일)
git config --global user.name "홍길동"
git config --global user.email "[email protected]"
# 기본 브랜치 이름을 main으로 설정 (최신 관례)
git config --global init.defaultBranch main
# 설정 확인
git config --list
```
### --global vs --local
| 옵션 | 범위 | 설정 파일 위치 |
|------|------|---------------|
| `--global` | 현재 사용자의 모든 저장소 | `~/.gitconfig` |
| `--local` | 현재 저장소만 | `.git/config` |
| (없음) | `--local`과 동일 | `.git/config` |
`--local`이 `--global`보다 우선한다. 회사 프로젝트에서 다른 이메일을 쓰고 싶을 때 유용하다.
---
## 3. Git의 3가지 영역
Git에는 파일이 거치는 3가지 영역이 있다. 이것을 이해하면 대부분의 명령어를 자연스럽게 이해할 수 있다.
```
Working Directory ──git add──▶ Staging Area ──git commit──▶ Repository
(작업 디렉토리) (스테이징) (저장소)
```
### Working Directory (작업 디렉토리)
- 실제로 파일을 편집하는 공간
- VS Code에서 보이는 파일들이 바로 여기에 있다
- Git이 추적하는 파일과 추적하지 않는 파일(Untracked)이 섞여 있다
### Staging Area (스테이징 영역, Index)
- 다음 커밋에 포함시킬 파일을 **선택적으로** 올려놓는 공간
- `git add`로 파일을 이곳에 올린다
- 왜 필요한가? → 작업 중인 10개 파일 중 3개만 골라서 커밋할 수 있게 해준다
```bash
# 예시: 파일 3개를 수정했지만, 2개만 커밋하고 싶을 때
git add login.js signup.js # 이 2개만 스테이징
git commit -m "인증 기능 추가" # 스테이징된 2개만 커밋됨
# profile.js는 아직 Working Directory에 남아있음
```
### Repository (저장소, .git 디렉토리)
- 커밋된 스냅샷이 영구적으로 저장되는 공간
- `.git/` 폴더 안에 모든 커밋 이력이 들어있다
- `git commit`을 하면 스테이징 영역의 내용이 여기에 저장된다
### 파일의 상태 흐름
```
Untracked ──git add──▶ Staged ──git commit──▶ Committed
▲ |
| |
(파일 수정) |
| |
Modified ◀──────────────────┘
```
| 상태 | 의미 |
|------|------|
| **Untracked** | Git이 아직 추적하지 않는 새 파일 |
| **Staged** | 스테이징 영역에 올라간 상태 (다음 커밋에 포함 예정) |
| **Committed** | 저장소에 안전하게 저장된 상태 |
| **Modified** | 커밋된 파일이 수정되었지만 아직 스테이징하지 않은 상태 |
---
## 4. 기본 명령어
### 저장소 생성
```bash
# 방법 1: 새 저장소 만들기
git init
# → 현재 폴더에 .git/ 디렉토리가 생성된다
# → 이미 파일이 있는 폴더에서 해도 된다 (기존 파일은 Untracked 상태)
# 방법 2: 기존 원격 저장소 복제하기
git clone https://github.com/user/repo.git
# → repo/ 폴더가 생성되고, 모든 파일과 커밋 이력이 다운로드된다
# → 자동으로 원격 저장소(origin)가 연결된다
# 다른 이름으로 복제
git clone https://github.com/user/repo.git my-project
# → my-project/ 폴더로 복제된다
```
### 상태 확인
```bash
# 현재 상태 확인 (가장 자주 쓰는 명령어)
git status
# → 어떤 파일이 수정되었는지, 스테이징 되었는지 한눈에 보여준다
# 출력 예시:
# On branch main
# Changes not staged for commit:
# modified: index.html ← 수정되었지만 스테이징 안 됨
# Untracked files:
# style.css ← 새 파일 (Git이 아직 모름)
# 짧은 형태로 보기
git status -s
# 출력 예시:
# M index.html ← M: Modified
# ?? style.css ← ??: Untracked
# 커밋 히스토리 확인
git log
# → 커밋 해시, 작성자, 날짜, 메시지를 보여준다
# 한 줄로 요약
git log --oneline
# 출력 예시:
# a1b2c3d 로그인 기능 추가
# e4f5g6h 초기 프로젝트 설정
# 그래프로 브랜치 흐름 보기
git log --oneline --graph --all
# 출력 예시:
# * a1b2c3d (HEAD -> feature) 로그인 기능 추가
# | * b2c3d4e (main) README 수정
# |/
# * e4f5g6h 초기 설정
```
### 변경 내용 비교 (diff)
```bash
# Working Directory의 변경사항 (스테이징 전)
git diff
# → 마지막 스테이징(또는 커밋)과 현재 파일의 차이를 보여준다
# 스테이징된 변경사항 (커밋 전)
git diff --staged
# → 스테이징 영역과 마지막 커밋의 차이를 보여준다
# 두 커밋 사이의 차이
git diff 커밋해시1 커밋해시2
# 특정 파일만 비교
git diff index.html
```
diff 출력 읽는 법:
```diff
- 이 줄은 삭제되었다 (빨간색)
+ 이 줄은 추가되었다 (초록색)
```
### 스테이징 & 커밋
```bash
# 특정 파일 스테이징
git add index.html
# 여러 파일 한번에
git add index.html style.css app.js
# 현재 디렉토리의 모든 변경 파일
git add .
# 특정 확장자만
git add *.js
# 대화형으로 부분 스테이징 (한 파일의 일부 변경만 스테이징)
git add -p index.html
# → 변경사항을 덩어리(hunk)별로 보여주며 y/n으로 선택
# 스테이징 취소 (파일 내용은 유지)
git restore --staged index.html
```
```bash
# 커밋 (스테이징된 파일을 저장소에 기록)
git commit -m "로그인 페이지 추가"
# 여러 줄 커밋 메시지
git commit -m "로그인 기능 구현
- 이메일/비밀번호 입력 폼 추가
- 유효성 검사 로직 구현
- 로그인 API 연동"
# add + commit 동시에 (이미 추적 중인 파일만, 새 파일은 안 됨)
git commit -am "빠른 수정"
```
### 좋은 커밋 메시지 작성법
```
# 형식
<타입>: <제목> (50자 이내)
<본문> (선택사항, 72자 줄바꿈)
# 타입 예시
feat: 새 기능 추가
fix: 버그 수정
docs: 문서 수정
style: 코드 포맷팅 (동작 변경 없음)
refactor: 리팩토링 (기능 변경 없음)
test: 테스트 추가/수정
# 좋은 예
feat: 소셜 로그인 기능 추가
fix: 장바구니 수량 음수 입력 방지
# 나쁜 예
수정함
update
asdf
```
### 커밋 되돌리기
```bash
# 1. 마지막 커밋 메시지 수정 (내용은 유지)
git commit --amend -m "새 메시지"
# 2. 마지막 커밋에 파일 추가 (깜빡 잊은 파일이 있을 때)
git add 빠뜨린파일.js
git commit --amend --no-edit # 메시지는 그대로, 파일만 추가
# 3. reset - 커밋 자체를 되돌리기
git reset --soft HEAD~1 # 커밋만 취소 (변경사항은 스테이징에 유지)
git reset --mixed HEAD~1 # 커밋+스테이징 취소 (변경사항은 Working에 유지) ← 기본값
git reset --hard HEAD~1 # 커밋+스테이징+파일 변경 모두 삭제 (⚠️ 복구 어려움!)
# HEAD~1 = 1개 전 커밋, HEAD~3 = 3개 전 커밋
# 4. revert - 되돌리는 새 커밋을 생성 (이미 push한 커밋에 사용)
git revert HEAD # 마지막 커밋을 되돌리는 새 커밋 생성
git revert a1b2c3d # 특정 커밋을 되돌리기
```
### reset vs revert 차이
```
[reset] 히스토리를 지움 → 혼자 작업할 때 사용
A → B → C → D → A → B → C (D가 사라짐)
[revert] 되돌리는 새 커밋 추가 → 협업 시 사용 (안전함)
A → B → C → D → A → B → C → D → D' (D를 취소하는 D' 추가)
```
---
## 5. 브랜치 (Branch)
### 브랜치란?
브랜치는 **커밋을 가리키는 포인터**이다. 새 브랜치를 만드는 것은 포인터를 하나 더 만드는 것이므로 비용이 거의 없다.
```
main
↓
A → B → C → D
\
E → F
↑
feature
```
### HEAD란?
**HEAD**는 "현재 내가 어떤 브랜치에 있는지"를 가리키는 특수 포인터이다.
```
HEAD → main → 커밋D (main 브랜치에 있을 때)
HEAD → feature → 커밋F (feature 브랜치에 있을 때)
```
### 브랜치 명령어
```bash
# 브랜치 목록 확인 (* 표시가 현재 브랜치)
git branch
# 출력 예시:
# * main
# feature/login
# fix/bug-123
# 원격 브랜치 포함 모두 보기
git branch -a
# 새 브랜치 생성 (전환은 안 함)
git branch feature/login
# 브랜치 생성 + 전환
git switch -c feature/login
# 브랜치 전환
git switch main
# ⚠️ 전환 전에 변경사항을 커밋하거나 stash해야 한다
# 브랜치 삭제 (병합 완료된 브랜치만)
git branch -d feature/login
# 강제 삭제 (병합 안 된 브랜치도 삭제 - 주의!)
git branch -D feature/login
# 브랜치 이름 변경
git branch -m old-name new-name
```
### 브랜치 네이밍 컨벤션
```
feature/기능이름 → 새 기능 개발 (feature/login, feature/cart)
fix/버그설명 → 버그 수정 (fix/null-error, fix/login-crash)
hotfix/긴급수정 → 긴급 배포 수정
release/버전 → 릴리스 준비 (release/1.0.0)
```
### 병합 (Merge)
```bash
# feature 브랜치를 main에 병합하려면:
git switch main # 1. 먼저 main으로 이동
git merge feature/login # 2. feature/login을 main에 병합
```
### 병합의 두 가지 방식
**1. Fast-Forward Merge** (빨리 감기)
main에 새 커밋이 없을 때, 포인터만 앞으로 이동한다.
```
병합 전:
main: A → B → C
\
feature: D → E
병합 후 (fast-forward):
main: A → B → C → D → E
↑
main, feature
```
**2. 3-Way Merge** (삼방향 병합)
main에도 새 커밋이 있을 때, 병합 커밋이 생성된다.
```
병합 전:
main: A → B → C → F
\
feature: D → E
병합 후:
main: A → B → C → F → G (G = 병합 커밋)
\ /
feature: D → E
```
### Rebase (리베이스)
병합의 대안. 브랜치의 시작점을 최신 커밋으로 옮긴다.
```bash
git switch feature
git rebase main
```
```
리베이스 전:
main: A → B → C → F
\
feature: D → E
리베이스 후:
main: A → B → C → F
\
feature: D' → E' (커밋이 재생성됨)
```
**merge vs rebase:**
| | Merge | Rebase |
|---|---|---|
| 히스토리 | 분기 이력이 남음 | 일직선으로 깔끔함 |
| 안전성 | 기존 커밋 변경 없음 (안전) | 커밋이 재생성됨 (주의 필요) |
| 사용 시점 | 공유 브랜치에서 | 개인 브랜치에서 |
> **주의:** 이미 push한 브랜치에서 rebase하면 안 된다! 다른 사람의 작업과 충돌할 수 있다.
---
## 6. 브랜치 vs 워크트리 (Worktree)
둘 다 "독립적인 작업 공간"이지만 본질이 다르다.
### 브랜치는 여러 개 만들 수 있다
작업을 섞지 않기 위해 브랜치를 여러 개 만들어 쓴다.
```
main (안정 버전)
├── feature/login ← 로그인 작업
├── fix/bug-123 ← 버그 수정
└── design/new-header ← 디자인 변경
```
각 브랜치가 독립적이므로:
- 로그인 잘 됨 → main에 merge
- 버그 수정 완료 → main에 merge
- 디자인 별로임 → 브랜치 삭제 (다른 작업에 영향 없음!)
팀으로 일할 때는 더 중요하다:
```
main
├── feature/login ← 나
├── feature/cart ← 동료 A
└── feature/payment ← 동료 B
→ 각자 자기 브랜치에서 작업하니까 서로 코드가 안 섞인다
```
### 쉬운 비유로 이해하기
**브랜치 = 책갈피**
하나의 책(폴더)에 책갈피(브랜치)를 여러 개 꽂는 것이다.
```
📖 책 (내 프로젝트 폴더)
🔖 main 책갈피 → 100페이지
🔖 feature 책갈피 → 150페이지
```
- 책은 **하나**뿐이다
- 책갈피를 바꾸면(`git switch`) 책이 해당 페이지로 넘어간다
- **동시에 두 페이지를 펼칠 수 없다** — 한 번에 하나만 볼 수 있음
```
git switch main → 책이 100페이지로 바뀜
git switch feature → 책이 150페이지로 바뀜
```
**워크트리 = 책을 한 권 더 복사하기**
```
📖 책1 (21_study/) → feature 150페이지 펼쳐놓고 작업 중
📖 책2 (21_study-hotfix/) → main 100페이지 펼쳐놓고 작업 중
```
- 책이 **두 권**이다 (폴더가 2개)
- 각각 다른 페이지를 펼쳐놓을 수 있다
- **동시에 두 작업을 할 수 있다**
**다른 비유: 책상**
```
브랜치만 쓰기 = 책상 1개에서 서류를 바꿔가며 작업
→ 로그인 서류 치우고 → 버그 서류 꺼내고 → 다시 치우고...
→ 서류를 계속 왔다갔다
워크트리 쓰기 = 책상 여러 개에 서류를 펼쳐놓기
→ 책상1: 로그인 서류 펼쳐놓은 채로
→ 책상2: 버그 서류 펼쳐놓은 채로
→ 필요한 책상으로 가서 바로 작업
```
---
### 브랜치 (Branch) — 상세
**커밋을 가리키는 포인터**이다. 논리적 개념.
```
main → 커밋C
feature → 커밋E
main: A → B → C
\
feature: D → E
```
- 브랜치를 전환(`git switch`)하면 **같은 폴더**의 파일 내용이 바뀜
- 한 번에 하나의 브랜치에서만 작업 가능
- 전환 전에 변경사항을 커밋하거나 stash해야 함
### 워크트리 (Worktree)
**같은 저장소의 별도 작업 디렉토리**이다. 물리적으로 폴더가 하나 더 생긴다.
```bash
git worktree add ../hotfix main # 다른 폴더에 main 브랜치를 체크아웃
```
```
21_study/ ← feature 브랜치 작업 중
21_study-hotfix/ ← main 브랜치 작업 중 (동시에!)
```
- 브랜치 전환 없이 **여러 브랜치를 동시에** 열어둘 수 있음
- 각 폴더가 독립적이라 빌드/테스트도 따로 가능
- `.git` 정보를 공유하므로 커밋 이력은 하나로 유지
### 워크트리 실전 예시
```
상황: feature 브랜치에서 작업 중인데 긴급 버그 수정 요청이 옴
```
**방법 1: 브랜치 전환 (책갈피만 사용)**
```bash
# 📖 지금 150페이지(feature) 보는 중인데 100페이지(main)를 봐야 함
git stash # "잠깐 여기 접어두고..."
git switch main # 100페이지로 넘김
# ... 버그 수정 ...
git commit -m "긴급 버그 수정"
git switch feature # 150페이지로 다시 넘김
git stash pop # "아까 접어둔 거 다시 펼치고..."
```
**방법 2: 워크트리 (책을 한 권 더 만들기)**
```bash
# 📖 책1은 그대로 두고, 📖 책2를 만든다
git worktree add ../hotfix main # main 브랜치를 새 폴더에 열기
cd ../hotfix # 📖 책2로 이동
# ... 버그 수정 ...
git commit -m "긴급 버그 수정"
cd ../21_study # 📖 책1로 돌아옴 (feature 작업 그대로!)
git worktree remove ../hotfix # 📖 책2 치우기
```
### 핵심 비교
| | 브랜치 | 워크트리 |
|---|---|---|
| **본질** | 커밋을 가리키는 포인터 | 물리적 작업 폴더 |
| **동시 작업** | 불가 (전환 필요) | 가능 (폴더가 별개) |
| **디스크 사용** | 추가 공간 거의 없음 | 폴더만큼 공간 사용 |
| **용도** | 기능별 작업 분리 | 브랜치 전환 없이 병렬 작업 |
| **관계** | 워크트리 없이 존재 가능 | 반드시 브랜치가 필요 |
### 그럼 왜 항상 워크트리를 안 쓰는가?
| | 브랜치만 | 워크트리 |
|---|---|---|
| **디스크 공간** | 추가 없음 | 폴더마다 파일 복사 (용량 차지) |
| **단순함** | 간단 | 관리할 폴더가 늘어남 |
| **적합한 상황** | 순서대로 하나씩 작업 | 여러 작업을 동시에 진행 |
| **IDE** | 창 1개 | 폴더마다 창을 따로 열어야 함 |
```
보통은 브랜치만으로 충분하다
→ 작업을 순서대로 하나씩 하면 되니까
워크트리가 필요한 순간:
→ 작업 도중에 급한 다른 작업이 끼어들 때
→ 두 브랜치를 비교하면서 작업해야 할 때
→ 전환 비용이 클 때 (빌드 시간이 긴 프로젝트)
```
> **브랜치 = 작업을 분리**, **워크트리 = 분리된 작업을 동시에 진행**
### 워크트리 명령어
```bash
git worktree add <경로> <브랜치> # 워크트리 생성
git worktree add -b <새브랜치> <경로> # 새 브랜치를 만들면서 워크트리 생성
git worktree list # 워크트리 목록 확인
git worktree remove <경로> # 워크트리 삭제
git worktree prune # 삭제된 폴더의 워크트리 정보 정리
```
---
## 7. 원격 저장소 (Remote)
### 원격 저장소란?
GitHub, GitLab 등 인터넷에 있는 Git 저장소이다. 협업과 백업을 위해 사용한다.
```
[내 컴퓨터] [GitHub]
로컬 저장소 ──── push ────▶ 원격 저장소
◀──── pull ────
```
### 원격 저장소 연결
```bash
# 원격 저장소 추가 (origin은 관례적인 이름)
git remote add origin https://github.com/user/repo.git
# 연결된 원격 저장소 확인
git remote -v
# 출력 예시:
# origin https://github.com/user/repo.git (fetch)
# origin https://github.com/user/repo.git (push)
# 원격 저장소 이름 변경
git remote rename origin upstream
# 원격 저장소 연결 제거
git remote remove origin
```
### push (로컬 → 원격)
```bash
# 기본 push
git push origin main
# → 로컬의 main 브랜치를 origin(원격)의 main에 업로드
# 처음 push할 때 (-u로 추적 관계 설정)
git push -u origin main
# → 이후부터는 git push만으로 가능
# 새 브랜치를 원격에 올리기
git push -u origin feature/login
# 원격 브랜치 삭제
git push origin --delete feature/login
```
### pull (원격 → 로컬)
```bash
# 원격 변경사항을 가져와서 병합
git pull origin main
# → git fetch + git merge를 한번에 수행
# 리베이스로 가져오기 (히스토리가 깔끔해짐)
git pull --rebase origin main
```
### fetch vs pull
```bash
# fetch: 가져오기만 (병합 안 함)
git fetch origin
# → 원격 변경사항을 확인만 하고 싶을 때
# → origin/main이 업데이트되지만 내 main은 변경 안 됨
# pull: 가져오기 + 병합
git pull origin main
# → fetch + merge를 한번에
```
```
fetch만 한 경우:
내 main: A → B → C (그대로)
origin/main: A → B → C → D → E (업데이트됨)
pull한 경우:
내 main: A → B → C → D → E (병합됨)
origin/main: A → B → C → D → E
```
### 원격 브랜치 추적
```bash
# 원격 브랜치 목록 확인
git branch -r
# 출력:
# origin/main
# origin/feature/login
# 원격 브랜치를 로컬에 가져와서 작업
git switch -c feature/login origin/feature/login
# 또는 간단하게
git switch feature/login # 원격에 같은 이름이 있으면 자동 추적
```
---
## 8. 충돌 (Conflict) 해결
### 충돌이 발생하는 상황
두 브랜치(또는 두 사람)가 **같은 파일의 같은 부분**을 다르게 수정하면 Git이 자동 병합하지 못하고 충돌이 발생한다.
```
main에서: "Hello World" → "Hello Korea"
feature에서: "Hello World" → "Hello Japan"
→ Git: "어떤 걸 택해야 할지 모르겠어!" → 충돌 발생
```
### 충돌 파일 모습
```
<<<<<<< HEAD
Hello Korea
=======
Hello Japan
>>>>>>> feature
```
| 마커 | 의미 |
|------|------|
| `<<<<<<< HEAD` | 현재 브랜치(내 코드)의 시작 |
| `=======` | 구분선 |
| `>>>>>>> feature` | 병합하려는 브랜치(상대 코드)의 끝 |
### 해결 방법
```bash
# 1. 충돌 발생 확인
git status
# 출력: both modified: index.html
# 2. 파일을 열어서 수동으로 해결
# - 원하는 내용만 남기고 마커(<<<, ===, >>>)를 삭제
# 방법 A: 내 코드만 남기기
Hello Korea
# 방법 B: 상대 코드만 남기기
Hello Japan
# 방법 C: 둘 다 합치기
Hello Korea and Japan
# 3. 해결한 파일을 스테이징
git add index.html
# 4. 커밋 (충돌 해결 완료)
git commit -m "merge: main과 feature 충돌 해결"
```
### 충돌 예방 팁
1. **자주 pull 받기** — 변경사항이 쌓이면 충돌도 커진다
2. **작은 단위로 커밋** — 충돌 범위가 작아진다
3. **같은 파일을 동시에 수정하지 않기** — 작업 분배를 잘 하기
4. **merge 전에 최신 상태 확인** — `git fetch` 후 상태 확인
### 병합 취소
```bash
# 충돌이 너무 복잡하면 병합 자체를 취소할 수 있다
git merge --abort # 병합 전 상태로 돌아감
```
---
## 9. .gitignore
### .gitignore란?
Git이 **추적하지 않을 파일/폴더**를 지정하는 설정 파일이다. 프로젝트 루트에 `.gitignore` 파일을 만들면 된다.
### 무엇을 무시해야 하는가?
| 종류 | 예시 | 이유 |
|------|------|------|
| 의존성 | `node_modules/`, `venv/` | 용량이 크고 다시 설치 가능 |
| 환경 변수 | `.env` | 비밀번호, API 키 등 민감 정보 |
| 빌드 결과물 | `dist/`, `build/` | 소스에서 다시 생성 가능 |
| OS 파일 | `.DS_Store`, `Thumbs.db` | 운영체제가 자동 생성 |
| IDE 설정 | `.idea/`, `.vscode/` | 개인 환경 설정 |
| 로그 | `*.log` | 불필요한 이력 |
### .gitignore 문법
```gitignore
# 주석은 #으로 시작
# 특정 파일
.env
secret.json
# 특정 확장자 (와일드카드)
*.log
*.tmp
# 특정 폴더 (슬래시로 끝남)
node_modules/
dist/
build/
# 폴더 내 특정 파일
logs/*.log
# 모든 하위 디렉토리의 특정 파일
**/*.pyc
# 예외 (! 느낌표로 무시 취소)
*.log
!important.log # 이 파일만 추적함
# 루트의 파일만 (하위 폴더의 같은 이름은 무시 안 함)
/config.json
```
### 이미 추적 중인 파일을 .gitignore에 추가한 경우
```bash
# .gitignore에 추가해도 이미 추적 중인 파일은 계속 추적된다!
# 추적을 중단하려면:
git rm --cached .env # 파일은 유지하고 Git 추적만 중단
git rm --cached -r node_modules/ # 폴더의 경우
git commit -m "불필요한 파일 추적 제거"
```
---
## 10. Stash (임시 저장)
### Stash란?
작업 중인 변경사항을 **임시로 저장**하고, Working Directory를 깨끗한 상태로 되돌리는 기능이다.
### 언제 쓰는가?
```
상황: feature 브랜치에서 작업 중인데, 갑자기 main의 버그를 수정해야 함
문제: 아직 커밋하기엔 작업이 불완전함
해결: stash로 임시 저장 → main에서 버그 수정 → stash에서 복원
```
### 사용법
```bash
# 현재 변경사항 임시 저장
git stash
# → Working Directory가 마지막 커밋 상태로 깨끗해짐
# 메시지와 함께 저장 (여러 stash를 구분할 때 유용)
git stash -m "로그인 폼 작업 중"
# Untracked 파일도 포함해서 저장
git stash -u
# 임시 저장 목록 보기
git stash list
# 출력:
# stash@{0}: On feature: 로그인 폼 작업 중
# stash@{1}: WIP on main: a1b2c3d 초기 설정
# 가장 최근 stash 복원 (stash에서 제거됨)
git stash pop
# 가장 최근 stash 복원 (stash에 유지됨)
git stash apply
# 특정 stash 복원
git stash apply stash@{1}
# stash 삭제
git stash drop stash@{0} # 특정 stash 삭제
git stash clear # 모든 stash 삭제
```
---
## 11. 자주 쓰는 워크플로우
### 혼자 작업할 때
```bash
# 1. 작업 후 저장
git add .
git commit -m "feat: 메인 페이지 추가"
git push origin main
```
### 팀으로 협업할 때 (GitHub Flow)
```bash
# 1. 최신 상태 동기화
git switch main
git pull origin main
# 2. 기능 브랜치 생성
git switch -c feature/login
# 3. 작업 & 커밋 (여러 번 반복)
git add .
git commit -m "feat: 로그인 폼 UI 추가"
git add .
git commit -m "feat: 로그인 API 연동"
# 4. 원격에 push
git push -u origin feature/login
# 5. GitHub에서 Pull Request 생성
# 6. 코드 리뷰 후 main에 병합 (GitHub에서)
# 7. 로컬 정리
git switch main
git pull origin main
git branch -d feature/login
```
### Pull Request(PR)란?
```
"내가 만든 브랜치를 main에 병합해주세요" 라는 요청이다.
PR의 장점:
1. 코드 리뷰 — 다른 사람이 코드를 검토할 수 있다
2. 토론 — 코드에 대해 댓글로 의견을 나눌 수 있다
3. CI/CD — 자동 테스트를 실행하여 문제를 미리 발견할 수 있다
4. 이력 — 왜 이 변경을 했는지 기록이 남는다
```
---
## 12. 유용한 명령어 모음
```bash
# 임시 저장
git stash # 변경사항 임시 저장
git stash pop # 복원
# 히스토리 탐색
git log --oneline --graph --all # 전체 브랜치 그래프
git blame <파일> # 각 줄을 누가, 언제 수정했는지
git show <커밋해시> # 특정 커밋의 상세 내용
# 실수 복구
git reflog # 모든 HEAD 이동 기록
git checkout <reflog해시> # 과거 상태로 이동
# → reflog는 실수로 삭제한 커밋도 찾을 수 있는 "최후의 보루"
# 태그 (버전 표시)
git tag v1.0.0 # 현재 커밋에 태그 생성
git tag -a v1.0.0 -m "첫 릴리스" # 메시지 포함 태그
git push origin v1.0.0 # 태그를 원격에 push
# Cherry Pick (다른 브랜치의 특정 커밋만 가져오기)
git cherry-pick <커밋해시>
# 파일 복원
git restore <파일> # Working Directory의 변경 취소
git restore --staged <파일> # 스테이징 취소
```
---
## 13. Git 내부 구조 (심화)
Git이 내부적으로 어떻게 동작하는지 이해하면 더 효과적으로 사용할 수 있다.
### Git의 4가지 객체
```
[Blob] → 파일 내용 저장
[Tree] → 디렉토리 구조 저장 (Blob과 Tree의 목록)
[Commit] → 스냅샷 + 메타데이터 (작성자, 날짜, 부모 커밋, Tree)
[Tag] → 특정 커밋에 이름 붙이기
```
### 커밋의 구조
```
커밋 객체:
├── tree: abc123 ← 이 커밋의 파일/폴더 구조
├── parent: def456 ← 이전 커밋 (첫 커밋이면 없음)
├── author: 홍길동
├── date: 2026-03-18
└── message: "로그인 기능 추가"
```
### SHA-1 해시
모든 Git 객체는 **40자리 SHA-1 해시**로 식별된다.
```
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
```
같은 내용은 항상 같은 해시를 가지므로, 데이터 무결성이 보장된다.
---
## 14. 자주 겪는 문제와 해결법
### "Changes not staged for commit"
```bash
# 파일을 수정했지만 git add를 안 한 상태
git add <파일> # 스테이징 하기
```
### "Your branch is behind"
```bash
# 원격에 새 커밋이 있는 상태
git pull origin main
```
### "CONFLICT (content): Merge conflict"
```bash
# 충돌 발생 → 파일을 열어서 수동 해결
# 해결 후:
git add <충돌파일>
git commit
```
### "fatal: not a git repository"
```bash
# .git이 없는 폴더에서 git 명령어 실행
git init # 저장소 초기화
# 또는 올바른 폴더로 이동
```
### 실수로 커밋한 파일 삭제하기
```bash
# 예: .env 파일을 실수로 커밋한 경우
echo ".env" >> .gitignore # .gitignore에 추가
git rm --cached .env # Git 추적 중단 (파일은 유지)
git commit -m "remove: .env 파일 추적 제거"
```
### 실수로 reset --hard 한 경우
```bash
# reflog로 복구 가능!
git reflog # 이전 상태의 해시 찾기
git reset --hard <해시> # 해당 상태로 복구
```
---
## 15. 파일 복원 심화 (restore vs checkout)
### git restore 기본 동작
`git restore`는 **파일의 변경사항을 되돌리는** 명령어이다.
```bash
# 1. Working Directory의 변경 취소 (수정한 파일을 마지막 커밋 상태로)
git restore git-guide.md
# 2. Staging 취소 (add한 걸 다시 빼기)
git restore --staged git-guide.md
```
흐름으로 이해하기:
```
Working Directory Staging Area Repository
(파일 수정하는 곳) (커밋 대기 공간) (저장된 커밋)
◀── git restore ── ◀── git restore --staged ──
(수정 취소) (스테이징 취소)
```
| 명령어 | 하는 일 | 비유 |
|--------|--------|------|
| `git restore 파일` | 수정 취소 → 마지막 커밋 상태로 | 문서에서 Ctrl+Z |
| `git restore --staged 파일` | add 취소 → Staging에서 빼기 | 제출함에서 다시 꺼내기 |
---
### --source로 특정 커밋에서 복원
커밋 ID를 지정하면 **특정 시점의 파일로 복원**할 수 있다.
```bash
# 기본 (HEAD에서 복원)
git restore git-guide.md
# → 마지막 커밋 상태로 복원
# 특정 커밋에서 복원
git restore --source 7504415 git-guide.md
# → 7504415(한글 버전) 시점의 파일로 복원
```
---
### 커밋된 실수는 단순 restore로 안 된다
```
상황:
a1b2c3d 한글 버전 작성 ← 정상
b2c3d4e 영어 버전으로 교체 ← 정상
e5f6a7b 실수: 파일 내용 삭제 ← HEAD (망가진 상태)
```
```bash
git restore ai-intro.txt
# → HEAD(e5f6a7b)로 복원
# → 이미 망가진 커밋이 HEAD니까 → 망가진 상태로 "복원"됨
# → 의미 없음!
```
해결 방법:
```bash
# 방법 1: --source로 망가지기 전 커밋에서 복원
git restore --source b2c3d4e ai-intro.txt
git add ai-intro.txt
git commit -m "파일 복구"
# 방법 2: revert (실수 커밋을 되돌리는 새 커밋 생성) → 협업 시
git revert e5f6a7b
# 방법 3: reset (실수 커밋 자체를 삭제) → 혼자 작업 시
git restore --hard b2c3d4e
```
---
### checkout vs restore --source 비교
```bash
# checkout: 파일이 Staged 상태로 복원 (자동으로 add됨)
git checkout 7504415 -- git-guide.md
# restore: 파일이 Modified 상태로 복원 (add 안 됨)
git restore --source 7504415 git-guide.md
```
```
checkout 후:
Working Directory Staging Area Repository
한글 버전 ✅ 한글 버전 일본어 버전
(add 됨!)
→ 바로 git commit 가능
restore --source 후:
Working Directory Staging Area Repository
한글 버전 일본어 버전 일본어 버전
(add 안 됨)
→ git add 먼저 해야 커밋 가능
```
---
### 과거 파일을 보기만 하기 (git show)
파일을 변경하지 않고 **구경만** 할 때는 `git show`를 사용한다.
```bash
git show 7504415:git-guide.md
# → 터미널에 한글 버전 내용을 출력만 함
# → 현재 파일은 변경되지 않음!
```
| 목적 | 명령어 | 파일 변경? |
|------|--------|-----------|
| **보기만** | `git show 커밋ID:파일명` | X (안 바뀜) |
| **파일 복원** | `git restore --source 커밋ID 파일` | O (Modified) |
| **파일 복원** | `git checkout 커밋ID -- 파일` | O (Staged) |
---
### checkout은 왜 다시 Staging에 올리는가?
과거에 한글을 `add` + `commit`한 것은 이미 끝난 일이다. `checkout`으로 한글을 다시 가져오면 **현재 HEAD 기준으로 새로운 변경**이 된다.
```
커밋1: 📄 한글 제출 완료 ✓ (끝)
커밋2: 📄 영어 제출 완료 ✓ (끝)
커밋3: 📄 일본어 제출 완료 ✓ (현재 HEAD)
checkout으로 한글 가져오기
→ "일본어를 한글로 바꾸겠다"는 새로운 변경
→ Staging에 올라감
```
Git은 **현재 HEAD 기준**으로 변경 여부를 판단한다. 과거에 커밋했던 내용이라도, 현재 HEAD와 다르면 "새로운 변경"으로 취급한다.
---
### 내용이 같으면 Git이 헷갈리지 않는가?
헷갈리지 않는다. Git은 파일 내용의 **SHA-1 해시**로 비교한다.
```bash
# HEAD와 같은 커밋에서 checkout → 변경 없음
git checkout 7ec67ae -- git-guide.md
# → "현재랑 내용이 똑같네? 변경사항 없음!"
# HEAD와 다른 커밋에서 checkout → 변경 있음
git checkout 7504415 -- git-guide.md
# → "현재(일본어)와 다르네(한글)? Staging에 올림!"
```
사람처럼 글자를 하나하나 읽는 게 아니라, 내용 전체를 해시값으로 변환해서 같은지 다른지 즉시 판별한다. 파일이 아무리 커도 정확하게 비교할 수 있다.
---
### --staged vs --source 차이
이 두 옵션은 완전히 다른 역할이다.
```bash
# --staged: Staging에서 빼기 (add 취소)
git restore --staged git-guide.md
# --source: 어디서 가져올지 지정 (특정 커밋에서 복원)
git restore --source 7504415 git-guide.md
```
흐름으로 보기:
```
Working Directory Staging Area Repository
┌─────────┐
│ 커밋 ffe │ (HEAD)
│ 커밋 7ec │
◀── --staged ── │ 커밋 892 │
(add 취소) │ 커밋 750 │ ◀── --source 750
└─────────┘
```
구체적인 예시:
```bash
# 상황: git-guide.md를 수정하고 git add까지 한 상태
# --staged: "add한 거 취소할래"
git restore --staged git-guide.md
# → Staging에서 빼기 (파일 내용은 그대로)
# --source: "7504415 커밋 버전으로 바꿀래"
git restore --source 7504415 git-guide.md
# → 파일 내용이 한글 버전으로 바뀜
```
| 옵션 | 비유 | 하는 일 |
|------|------|--------|
| `--staged` | 제출함에서 **꺼내기** | add 취소 (내용은 안 바뀜) |
| `--source` | 어떤 **서랍에서** 가져올지 선택 | 특정 커밋의 파일로 교체 |
같이 쓸 수도 있다:
```bash
git restore --source 7504415 --staged git-guide.md
# → 7504415 버전을 Staging에 올리기
# (checkout과 비슷한 동작)
```
정리:
- `--staged` = **어디로** 복원할지 (Staging에서 빼기)
- `--source` = **어디에서** 가져올지 (특정 커밋 지정)
- 기본 `git restore 파일`은 `--source HEAD`가 생략된 것이다
---
## 16. 브랜치 실전 예제: 실험 브랜치 활용하기
브랜치의 핵심은 **main을 안전하게 유지하면서 마음껏 실험**할 수 있다는 것이다.
### 1단계: 새 브랜치 만들고 이동하기
```bash
git checkout -b experiment-languages
# 또는
git switch -c experiment-languages
```
main 브랜치를 그대로 두고, 복사본인 `experiment-languages` 브랜치로 이동한다. **여기서 아무리 바꿔도 main은 안전하다.**
```
main: A → B → C → D
\
experiment-languages: (여기서 작업 시작)
```
### 2단계: 브랜치에서 실험하기
마음대로 파일을 수정하고 커밋한다.
```bash
# 파일 수정 후
git add ai-intro.txt
git commit -m "스페인어, 아랍어 실험 추가"
```
```
main: A → B → C → D
\
experiment-languages: E (실험 커밋)
```
### 3단계: 실험이 마음에 안 들면 — main으로 돌아가기
```bash
git switch main
```
파일을 열어보면 실험한 내용이 없다. **main은 건드리지 않았으니까.**
```bash
# 실험 브랜치 삭제 (실험 폐기)
git branch -d experiment-languages
```
```
main: A → B → C → D (깨끗한 그대로!)
experiment-languages: (삭제됨, 실험은 사라짐)
```
### 3단계 (대안): 실험이 마음에 들면 — main에 합치기
```bash
# main 브랜치에 있는 상태에서
git merge experiment-languages
```
```
main: A → B → C → D → E (실험 내용이 main에 합쳐짐!)
experiment-languages: E (삭제해도 됨)
```
```bash
# 합친 후 실험 브랜치 정리
git branch -d experiment-languages
```
### 전체 흐름 요약
```
실험하고 싶다!
│
├── git switch -c 실험브랜치 ← 1. 브랜치 만들기
│
├── (마음대로 수정 & 커밋) ← 2. 실험하기
│
├── git switch main ← 3. main으로 돌아오기
│
├── 마음에 든다?
│ ├── YES → git merge 실험브랜치 ← main에 합치기
│ └── NO → (아무것도 안 해도 됨)
│
└── git branch -d 실험브랜치 ← 4. 정리
```
### 실전 활용 예시
```bash
# 예시 1: 새 디자인 실험
git switch -c experiment/new-design
# ... 디자인 변경 ...
# 마음에 안 들면: git switch main (끝!)
# 마음에 들면: git switch main → git merge experiment/new-design
# 예시 2: 위험한 리팩토링 시도
git switch -c experiment/refactor-auth
# ... 코드 대폭 수정 ...
# 망하면: git switch main (원본 무사!)
# 잘 되면: git switch main → git merge experiment/refactor-auth
# 예시 3: 라이브러리 버전 업그레이드 테스트
git switch -c experiment/upgrade-react
# ... 업그레이드 시도 ...
# 호환 안 되면: git switch main
# 잘 되면: git merge experiment/upgrade-react
```
브랜치는 **실패해도 되는 안전한 놀이터**다. 부담 없이 만들고, 필요 없으면 버리면 된다.
---
## 17. 브랜치 전환: checkout vs switch
### checkout의 문제점
`git checkout`은 원래 **두 가지 역할**을 동시에 했다.
```bash
# 역할 1: 브랜치 전환
git checkout main
# 역할 2: 파일 복원
git checkout -- file.txt
git checkout abc1234 -- file.txt
```
하나의 명령어가 너무 많은 일을 하다 보니 헷갈리는 경우가 많았다. 그래서 Git 2.23부터 **역할별로 명령어가 분리**되었다.
```
checkout (만능 도구)
├── 브랜치 전환 → git switch (새 명령어)
└── 파일 복원 → git restore (새 명령어)
```
### switch vs checkout 비교
| 목적 | checkout (예전) | switch (새로운) |
|------|----------------|----------------|
| 브랜치 전환 | `git checkout main` | `git switch main` |
| 브랜치 생성+전환 | `git checkout -b new` | `git switch -c new` |
| 파일 복원 | `git checkout -- file.txt` | ❌ (이건 restore가 담당) |
### switch가 더 안전한 이유
`checkout`은 실수로 파일을 덮어쓸 수 있다.
```bash
# 의도: feature 브랜치로 전환
git checkout feature
# 실수: feature라는 파일이 있으면 파일을 덮어씀!
# → checkout은 브랜치인지 파일인지 헷갈릴 수 있다
# switch는 브랜치 전환만 하니까 이런 실수가 없다
git switch feature
# → 브랜치가 없으면 에러 (안전!)
```
### 현재 권장 사용법
```bash
# 브랜치 전환 → switch
git switch main
git switch -c new-feature # 생성 + 전환
# 파일 복원 → restore
git restore file.txt # 수정 취소
git restore --staged file.txt # add 취소
git restore --source abc1234 file.txt # 특정 커밋에서 복원
# checkout은 아직 쓸 수 있지만, 새 명령어가 더 명확하다
```
### 정리
```
Git 2.23 이전: checkout = 브랜치 전환 + 파일 복원 (만능)
Git 2.23 이후: switch = 브랜치 전환 전용
restore = 파일 복원 전용
checkout = 여전히 사용 가능 (하위 호환)
```
> **결론:** `switch`와 `restore`를 쓰는 게 더 안전하고 명확하다. 하지만 인터넷의 많은 자료가 `checkout`을 사용하므로 둘 다 알아두는 것이 좋다.