> ## Documentation Index
> Fetch the complete documentation index at: https://blog.nvim.me/llms.txt
> Use this file to discover all available pages before exploring further.

# 작업 트리, 스테이징 영역, 커밋은 어떻게 다를까요?

> add한 뒤 index.js를 다시 고치며 작업 트리·인덱스·커밋의 차이를 확인해요. diff와 diff --cached로 다음 커밋에 담길 내용을 예측해봐요.

`index.js`를 `console.log("hello git");`로 바꾸고 `git add`까지 했어요. 그 뒤 `console.log("hello world");`로 다시 저장했어요. 지금 커밋하면 어느 코드가 기록될까요?

[첫 커밋 실습](https://blog.nvim.me/git/learn/setup-and-first-commit.md)에서는 파일을 저장하고, 스테이징하고, 커밋했어요. 이번에는 **같은 파일의 내용이 작업 트리, 인덱스, 커밋에서 서로 다를 수 있다**는 걸 직접 확인해봐요.

## 같은 파일을 세 상태로 확인해요

| Git 용어 | 이번 예제에서 확인할 내용 |
| - | - |
| 작업 트리(working tree) | 지금 디스크에 저장된 `index.js` |
| 스테이징 영역(index) | 마지막 `git add index.js`가 반영한 내용 |
| 커밋(commit) | 이미 이력에 기록한 파일의 스냅샷 |

스테이징 영역은 인덱스라고도 불러요. **파일 이름인 `index.js`와 인덱스는 별개**예요. `HEAD`는 이 실습에서 현재 브랜치의 마지막 커밋을 가리켜요.

인덱스는 다음 커밋의 파일 상태를 준비해요. 변경된 파일만 다음 스냅샷에 들어가는 것은 아니에요. 수정하지 않은 파일도 포함돼요. 스냅샷은 논리적인 모델이며, 모든 파일을 매번 통째 복사한다는 뜻은 아니에요. [Pro Git의 세 영역 설명](https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F)을 참고해요.

## 시작 상태를 맞춰요

명령은 **Bash** 기준이에요. 준비 명령의 `git init -b main`에는 Git 2.28 이상이 필요해요. 별도 hook이나 커밋 서명 설정이 없는 로컬 연습 저장소를 기준으로 해요. 코드를 실행하지 않으므로 Node.js는 필요하지 않아요.

| 항목 | 시작 상태 | 끝낼 때의 상태 |
| - | - | - |
| 브랜치와 이력 | `main`, 첫 커밋 하나 | 코드 변경 커밋 두 개를 더해 총 세 개 |
| `index.js` | `console.log("hello");` | `console.log("hello world");` |
| `README.md` | `# Git practice` | 그대로 유지 |
| 미커밋 변경과 원격 | 둘 다 없음 | 둘 다 없음 |

앞 실습의 `git-practice`를 그대로 뒀다면 그 폴더에서 시작해요. 다른 작업이 섞여 있다면 되돌리거나 덮어쓰지 말고 별도 저장소를 만들어요.

> [accordion] 이 글부터 시작한다면: 별도 실습 저장소 준비하기
>
> 설치는 [첫 커밋 실습](https://blog.nvim.me/git/learn/setup-and-first-commit.md)을 참고해요. 기존 프로젝트나 Git 저장소 밖에서 실행해요. 같은 이름의 폴더가 있어 `mkdir`가 실패하면 멈추고 새 이름을 골라요.
>
> ```bash theme={null}
> mkdir git-practice-staging && cd git-practice-staging && git init -b main
> ```
>
> 아래 `>`는 파일을 덮어써요. 방금 만든 빈 폴더에서만 실행해요.
>
> ```bash theme={null}
> git config --local user.name "Git Learner"
> git config --local user.email "learner@example.com"
> printf 'console.log("hello");\n' > index.js
> printf '# Git practice\n' > README.md
> git add index.js README.md
> git commit -m "Add initial script and README"
> ```
>
> 작성자 설정은 이 저장소에만 적용하고 원격은 연결하지 않아요.

같은 터미널에서 시작 상태를 확인해요.

```bash theme={null}
git status
git log --oneline
cat index.js README.md
git remote -v
```

`main`과 `working tree clean`, 초기 커밋 한 개가 보여야 해요. 파일 내용은 `console.log("hello");`, `# Git practice` 순서예요. 원격 출력은 없어야 해요.

## diff는 어느 두 상태를 비교할까요?

| 명령 | 비교 방향 | 답하는 질문 |
| - | - | - |
| `git diff` | 인덱스 → 작업 트리 | 아직 스테이징하지 않은 변경은 무엇일까요? |
| `git diff --cached` | `HEAD` → 인덱스 | 지금 커밋하면 마지막 기록과 무엇이 다를까요? |
| `git diff HEAD` | `HEAD` → 작업 트리 | 마지막 기록과 현재 파일은 어떻게 다를까요? |

`--staged`는 `--cached`와 같은 뜻이에요. 뒤에 `-- index.js`를 붙이면 그 파일만 비교해요. `--`는 뒤의 값을 파일 경로로 구분해요. 출력이 별도 보기 화면으로 열렸다면 `q`로 나올 수 있어요. [git diff 공식 문서](https://git-scm.com/docs/git-diff)에 비교 대상이 설명되어 있어요.

## add한 뒤 다시 고쳐봐요

각 단계는 같은 터미널에서 순서대로 실행해요. 명령 전에 어느 상태가 바뀔지 예상해보세요.

**1. 작업 파일의 문자열 바꾸기**

아래 명령은 연습용 `index.js` 전체를 한 줄로 덮어써요. 시작 상태를 확인한 연습 저장소에서만 실행해요.

```bash theme={null}
printf 'console.log("hello git");\n' > index.js
git status --short
git diff -- index.js
git diff --cached -- index.js
```

상태에는 맨 앞에 공백이 있는 ` M index.js`가 보여요. 일반 diff의 변경 줄은 다음과 같아요. 파일명·해시·줄 위치를 나타내는 헤더는 생략했어요.

```diff title="index.js" theme={null}
-console.log("hello");
+console.log("hello git");
```

`git diff --cached`는 비어 있어요. 파일만 저장했으므로 인덱스는 마지막 커밋과 같아요.

**2. 현재 코드를 스테이징하기**

작업 파일의 내용을 인덱스에 반영해요. 기존 커밋은 바뀌지 않아요.

```bash theme={null}
git add index.js
git status --short
git diff -- index.js
git diff --cached -- index.js
```

상태는 `M  index.js`로 바뀌어요. 작업 트리와 인덱스가 같아 일반 diff는 비어 있고, cached diff에는 `"hello"` → `"hello git"` 변경이 나와요.

**diff가 비었다는 것만으로 커밋을 마쳤다고 판단할 수는 없어요.** 비교한 두 상태를 확인해야 해요.

**3. add를 다시 하지 않고 코드 고치기**

이번에는 작업 파일만 바꿔요. 인덱스에는 방금 스테이징한 코드가 남아요.

```bash theme={null}
printf 'console.log("hello world");\n' > index.js
git status --short
git diff -- index.js
git diff --cached -- index.js
git diff HEAD -- index.js
```

상태에 `MM index.js`가 보여요. 아래 표는 각 diff에서 `console.log`의 문자열만 줄여 적었어요.

| 명령 | 이전 문자열 | 이후 문자열 |
| - | - | - |
| `git diff` | `"hello git"` | `"hello world"` |
| `git diff --cached` | `"hello"` | `"hello git"` |
| `git diff HEAD` | `"hello"` | `"hello world"` |

`add`는 실행한 시점의 내용을 반영해요. 이후 편집도 담으려면 다시 스테이징해야 해요. [git add 공식 문서](https://git-scm.com/docs/git-add)를 참고해요.

**4. 준비한 내용만 커밋하기**

`add`를 다시 하지 않고 커밋해요. 인덱스의 코드와 작업 파일 중 무엇이 기록될까요?

```bash theme={null}
git commit -m "Change greeting to hello git"
git show HEAD:index.js
cat index.js
git status --short
git diff --cached -- index.js
git diff -- index.js
```

`git show HEAD:index.js`는 마지막 커밋의 파일을 읽어서 `console.log("hello git");`를 보여줘요. `cat index.js`는 현재 파일을 읽어서 `console.log("hello world");`를 보여줘요.

상태에는 ` M index.js`가 남아요. cached diff는 비어 있고 일반 diff에는 `"hello git"` → `"hello world"` 변경이 있어요. **커밋이 성공해도 미커밋 변경이 남을 수 있어요.**

인덱스의 파일이 사라진 것은 아니에요. 인덱스와 새 커밋이 같아져서 스테이징된 차이가 없어진 거예요.

**5. 남은 변경도 별도 커밋으로 기록하기**

인덱스를 갱신하고 준비된 변경을 검토한 뒤 커밋해요.

```bash theme={null}
git add index.js
git diff --cached -- index.js
git commit -m "Change greeting to hello world"
git show HEAD:index.js
git show HEAD:README.md
git status
git log --oneline
git remote -v
```

커밋 전 diff에는 `"hello git"` → `"hello world"` 변경이 나와요. 커밋 뒤의 파일 내용은 `console.log("hello world");`와 `# Git practice`예요. 수정하지 않은 README도 새 커밋의 스냅샷에 포함돼요.

`status`는 `main`과 `working tree clean`을 보여줘요. 커밋은 총 세 개이고 원격은 없어요. 커밋 ID는 실행마다 달라질 수 있으니 개수와 메시지를 확인해요.

## MM은 충돌 표시일까요?

이번 실습의 `MM`은 충돌이 아니에요. 충돌 중이 아닌 일반적인 짧은 상태 출력은 `XY 경로` 형태예요.

| 표시 | 첫 번째 열: HEAD와 인덱스 | 두 번째 열: 인덱스와 작업 트리 |
| - | - | - |
| `M` | 스테이징된 차이 없음 | 스테이징하지 않은 수정 있음 |
| `M` | 스테이징한 수정 있음 | 추가 수정 없음 |
| `MM` | 스테이징한 수정 있음 | 그 뒤에 수정한 내용도 있음 |

공백도 상태의 일부예요. 새 파일의 `??`나 병합 충돌 표시는 별도 규칙이 있으므로 이 표를 모든 상태에 적용하지 않아요. [git status 공식 문서](https://git-scm.com/docs/git-status#_short_format)를 참고해요.

## 실제 작업에서는 무엇을 확인하면 좋을까요?

커밋 직전에 `git status`로 전체 상태를 보고, `git diff --cached`로 이번에 기록할 변경을 읽어요. `git diff`에서는 현재 파일에 있지만 이번 커밋에서 빠질 변경을 확인해요. 새 미추적 파일은 일반 diff에 나오지 않으므로 status도 함께 봐야 해요.

두 번 커밋한 것은 세 상태를 관찰하기 위한 실습이에요. 처음부터 `"hello world"`로 바꾼 결과만 남기고 싶었다면 첫 변경 커밋 전에 `git add index.js`를 다시 실행하면 돼요. 커밋은 저장 횟수보다 변경 목적에 맞춰 나누는 편이 읽기 좋아요.

> [!NOTE]
>
> 이 글은 파일 경로나 `-a` 없이 실행하는 `git commit -m "..."`을 기준으로 해요.
> `git commit -a`는 이미 추적하는 파일의 수정·삭제를 자동으로 스테이징해요.
> `git commit index.js`처럼 경로를 주는 방식도 커밋할 내용을 고르는 규칙이 달라요.
> [git commit 공식 문서](https://git-scm.com/docs/git-commit)를 참고해요.

## 예상과 다른 결과가 나왔다면

| 상황 | 확인과 대응 |
| - | - |
| 두 번째 수정 뒤 MM이 안 나와요 | 저장 여부와 두 diff를 확인해요. 이미 add를 다시 했다면 인덱스도 최신 내용이에요. 재현하려면 새 연습 폴더에서 시작해요 |
| 커밋할 변경이 없다고 해요 | cached diff를 확인해요. 작업 파일만 고쳤다면 add가 필요해요 |
| 다른 파일도 변경 목록에 있어요 | 현재 위치와 전체 status를 확인하고 별도 연습 저장소를 준비해요. 다른 작업은 지우지 않아요 |
| 작성자·서명·hook 오류가 나요 | [첫 커밋 실습의 오류 대응](https://blog.nvim.me/git/learn/setup-and-first-commit.md)을 따라 원인을 해결한 뒤 같은 커밋 명령을 다시 실행해요 |

## 자, 정리해볼까요?

* add 뒤 다시 고친 시점에 마지막 커밋은 `"hello"`, 인덱스는 `"hello git"`, 작업 파일은 `"hello world"`였어요.

* 첫 변경 커밋에는 `"hello git"`이 기록됐고, 파일의 `"hello world"`는 남았어요.

* 마지막에는 세 상태가 같고 미커밋 변경이 없어요.

* README는 모든 커밋에 남아 있고 원격 저장소는 없어요.

이 세 커밋은 [커밋 이력과 변경 내용 읽기](https://blog.nvim.me/git/learn/read-history-and-diff.md)에서 이어서 살펴봐요. 기록의 ID만 필요하다면 [현재 커밋 해시 확인하기](https://blog.nvim.me/git/how-to/check-current-commit-hash.md)를 참고해요.

실무에서 조회 명령만 빠르게 찾으려면 [현재 브랜치와 변경 내용 확인하기](https://blog.nvim.me/git/how-to/inspect-working-changes.md)를 참고해요.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.