git이 뭔지 이해하기

재엽님이 실습하신거에 내용을 추가하면서 제가 아는 것 까지 넣어서 길어졌습니다

이해가 안 되시면 여러번 읽고 실습도 해보세요

# 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`을 사용하므로 둘 다 알아두는 것이 좋다.

1개의 답글

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요