> ## 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.

# 브랜치를 만들면 폴더가 복사될까요?

> 브랜치와 HEAD가 가리키는 곳을 확인하고, switch 전후의 파일과 커밋을 비교해요. 미커밋 변경이 따라오는 경우와 전환이 거절되는 경우도 직접 확인해봐요.

`index.js`의 인사말을 다른 방식으로 고쳐보고 싶어요. 지금 코드는 `main`에 남겨두고 새 브랜치를 만들었는데, 폴더도 파일도 늘어나지 않아요. 새 작업 공간은 어디에 생긴 걸까요?

[앞편](https://blog.nvim.me/git/learn/tracked-files-and-gitignore.md)에서는 기록할 파일을 골랐어요. 이번에는 **어느 브랜치에 다음 커밋을 쌓을지** 골라봐요. 같은 폴더에서 브랜치를 만들고 이동하면서 파일 내용과 이력을 함께 확인해요.

## 브랜치는 커밋을 가리키는 이름이에요

브랜치(branch)는 커밋을 가리키는 참조예요. 새 브랜치를 만들면 처음에는 기존 브랜치와 같은 커밋을 가리킬 수 있어요. 프로젝트 폴더를 하나 더 복사하는 동작은 아니에요.

`HEAD`는 일반적으로 **현재 작업 중인 브랜치**를 가리켜요. `main`에서 커밋하면 `main`이 새 커밋으로 이동하고, `HEAD`는 계속 `main`을 가리켜요. 다른 브랜치의 위치는 그대로예요. 이 연결은 [Pro Git의 브랜치 설명](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)을 기준으로 해요.

| 확인할 대상 | 답하는 질문 |
| - | - |
| 브랜치 이름 | 이 작업 흐름의 끝 커밋은 무엇일까요? |
| `HEAD` | 지금 어느 브랜치 또는 커밋을 기준으로 작업하나요? |
| 작업 트리와 인덱스 | 지금 편집한 내용과 다음 커밋에 담을 내용은 무엇인가요? |

브랜치와 [작업 트리·인덱스](https://blog.nvim.me/git/learn/working-tree-index-and-commit.md)를 구분해야 해요. **브랜치마다 미커밋 파일을 따로 보관하는 것은 아니에요.** 이 글에서는 하나의 작업 트리에서 두 브랜치를 번갈아 사용해요.

## 시작 상태를 맞춰요

명령은 **Bash** 기준이에요. 코드를 실행하지 않으므로 Node.js는 필요하지 않아요. 앞편을 마친 연습 저장소의 루트에서 시작해요.

| 항목 | 시작 상태 |
| - | - |
| 브랜치와 이력 | `main`만 있고, 앞편까지 만든 다섯 커밋이 있어요 |
| `index.js` | `console.log("hello world");` 한 줄 |
| `README.md` | `# Git practice` 한 줄 |
| `.gitignore` | `*.log`와 `/local-notes.txt` 두 줄 |
| 제외된 파일 | `debug.log` 한 줄과 `local-notes.txt` 두 줄이 작업 폴더에 남아 있어요 |
| 미커밋 변경과 원격 | 제외된 두 파일 외에는 변경이 없고, 원격도 없어요 |

실습은 별도 hook이나 커밋 서명 설정이 없고, 전역 제외 규칙이 예제 파일명에 영향을 주지 않는 환경을 기준으로 해요. 다른 작업이 섞였거나 이미 `feature/greeting` 브랜치가 있다면 새 연습 저장소를 준비해요.

> [accordion] 이 글부터 시작한다면: 앞편과 같은 다섯 커밋 준비하기
>
> Git 설치는 [첫 커밋 실습](https://blog.nvim.me/git/learn/setup-and-first-commit.md)을 참고해요. `switch`와 `branch --show-current`는 Git 2.23부터 사용할 수 있어요. 아래 준비 명령의 `git init -b main`에는 Git 2.28 이상이 필요해요.
>
> 기존 Git 저장소 밖에서 실행해요. 같은 이름의 폴더가 있어 `mkdir`가 실패하면 멈추고 새 이름을 골라요.
>
> ```bash theme={null}
> mkdir git-practice-branches && cd git-practice-branches && git init -b main
> ```
>
> 아래 `>`는 파일을 덮어써요. 방금 만든 빈 연습 폴더에서만 실행해요. 작성자 설정은 이 저장소에만 적용하고 원격은 연결하지 않아요.
>
> 준비 과정의 `git rm --cached`는 연습 메모를 인덱스에서만 제거해요. 작업 파일과 과거 기록은 남고, 다음 커밋에는 해당 경로의 삭제를 기록해요.
>
> ```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"
> printf 'console.log("hello git");\n' > index.js
> git add index.js
> git commit -m "Change greeting to hello git"
> printf 'console.log("hello world");\n' > index.js
> git add index.js
> git commit -m "Change greeting to hello world"
> printf 'Practice note: inspect greeting\n' > local-notes.txt
> printf 'Debug: application started\n' > debug.log
> git add local-notes.txt
> git commit -m "Record practice local note"
> printf '*.log\n/local-notes.txt\n' > .gitignore
> printf 'Practice note: inspect diff\n' >> local-notes.txt
> git rm --cached -- local-notes.txt
> git add .gitignore
> git commit -m "Ignore logs and stop tracking local note"
> ```
>
> 여기서 `rm --cached`는 연습 메모를 인덱스에서만 제거해요. 작업 파일은 남고 다음 커밋에는 삭제를 기록해요. 동작이 낯설면 [앞편의 추적 해제 설명](https://blog.nvim.me/git/learn/tracked-files-and-gitignore.md)을 먼저 읽어봐요.

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

```bash theme={null}
git status
git branch --show-current
git log --oneline
cat index.js README.md .gitignore
git status --short --ignored
git remote -v
```

현재 브랜치는 `main`이고 일반 `status`는 `working tree clean`이어야 해요. 이력은 다섯 커밋이고, `--ignored`에는 `!! debug.log`와 `!! local-notes.txt`가 나와요. 원격 출력은 없어요. 이력 조회가 별도 화면으로 열리면 `q`로 나와요.

## 브랜치를 만들고 변경을 기록해봐요

**1. 브랜치 이름만 만들기**

실행 전에 예상해봐요. 새 브랜치를 만들면 현재 브랜치도 바뀔까요?

```bash theme={null}
git branch feature/greeting
git branch --show-current
git branch -v
git rev-parse main feature/greeting
git symbolic-ref --short HEAD
```

현재 브랜치와 마지막 출력은 여전히 `main`이에요. `branch -v`의 `*`도 `main` 앞에 있어요. `rev-parse`가 출력하는 두 커밋 ID는 같아요. **이름은 둘이지만 아직 같은 기록을 가리켜요.**

`git branch feature/greeting`은 생성만 하고 전환하지 않아요. `symbolic-ref --short HEAD`는 `HEAD`가 가리키는 참조 이름을 읽어요. [git branch](https://git-scm.com/docs/git-branch)와 [git symbolic-ref](https://git-scm.com/docs/git-symbolic-ref) 공식 문서에서 두 역할을 확인할 수 있어요.

**2. 파일을 고친 뒤 새 브랜치로 이동하기**

아직 `main`에 있을 때 연습 파일 `index.js`를 덮어써요. 이번 변경은 새 브랜치에 기록할 거예요.

```bash theme={null}
printf 'console.log("hello branch");\n' > index.js
git status --short
git diff -- index.js
git switch feature/greeting
git branch --show-current
git status --short
cat index.js
git diff --cached
```

전환 전후 모두 ` M index.js`가 보여요. 파일에는 `console.log("hello branch");`가 남아 있고, 현재 브랜치만 `feature/greeting`으로 바뀌어요. 스테이징은 하지 않았으므로 `diff --cached`는 비어 있어요.

두 브랜치가 같은 커밋을 가리키는 이 상황에서는 미커밋 변경을 유지하며 이동할 수 있어요. `main`에서 편집을 시작했다고 해서 변경이 `main` 전용으로 저장된 것은 아니에요.

**3. 현재 브랜치에 커밋 쌓기**

```bash theme={null}
git add index.js
git diff --cached -- index.js
git commit -m "Change greeting to hello branch"
git log --graph --oneline --decorate --all
git show main:index.js
git show feature/greeting:index.js
```

커밋 뒤 `feature/greeting`만 새 커밋을 가리켜요. 로그의 `HEAD -> feature/greeting`은 현재 브랜치가 그곳이라는 뜻이에요. `main`은 여전히 앞편의 마지막 커밋을 가리켜요.

두 `show` 출력은 차례로 `hello world`, `hello branch` 인사말이에요. 여기서 `브랜치:경로`는 그 브랜치가 가리키는 커밋의 파일을 읽는 표현이에요. `show`로 파일을 조회해도 현재 브랜치나 작업 파일은 바뀌지 않아요.

**4. main으로 돌아와 파일 비교하기**

미커밋 변경이 없는 상태에서 돌아가요. 실행 전 `index.js`에 어떤 인사말이 나타날지 예상해봐요.

```bash theme={null}
git switch main
git status
cat index.js
git show :index.js
git log --oneline -1
git log --graph --oneline --decorate --all
```

작업 파일과 인덱스의 파일은 모두 `console.log("hello world");`예요. `git show :index.js`는 인덱스의 내용을 읽어요. 깨끗한 상태의 `switch`는 이동할 브랜치에 맞춰 작업 트리와 인덱스를 갱신해요. 폴더 경로는 그대로예요.

기본 `log`는 현재 `HEAD`에서 부모를 따라 읽으므로 새 인사말 커밋이 보이지 않아요. `--all`을 붙인 로그에는 `feature/greeting`의 커밋도 보여요. 기록을 지운 것이 아니라 조회 출발점이 달라졌어요. 옵션은 [git log 공식 문서](https://git-scm.com/docs/git-log)를 참고해요.

## 미커밋 변경이 있으면 항상 이동할 수 있나요?

앞에서는 같은 커밋 사이를 이동해서 변경을 유지할 수 있었어요. 이번에는 두 브랜치의 `index.js`가 달라요. `main`에서 같은 파일을 다시 고치고 전환을 시도해봐요. 아래 `>`는 연습 저장소의 `index.js`만 덮어써요.

```bash theme={null}
printf 'console.log("hello main");\n' > index.js
git diff -- index.js
git switch feature/greeting
```

마지막 명령은 **실패하는 것이 이번 실습의 예상 결과**예요. 전환으로 현재 수정 내용을 잃을 수 있어 Git이 거절해요. 영어 메시지 환경에서 확인한 출력은 다음과 같아요. 경로나 언어에 따라 표현은 달라질 수 있어요.

```text theme={null}
error: Your local changes to the following files would be overwritten by checkout:
        index.js
Please commit your changes or stash them before you switch branches.
Aborting
```

실패 뒤에도 브랜치와 파일은 유지돼요. 다음 블록은 위의 거절을 확인한 다음 실행해요.

```bash theme={null}
git branch --show-current
git status --short
cat index.js
git diff --cached
```

출력은 `main`, `M index.js`, `console.log("hello main");` 순서예요. 스테이징된 변경은 없어요. 전환이 거절됐으므로 `feature/greeting`에서 작업 중이라고 생각하고 커밋하면 안 돼요.

> [!WARNING]
>
> `git switch --discard-changes`나 `-f`는 미커밋 변경을 버릴 수 있어요.
> 거절 메시지를 없애려고 붙이지 않아요.
> 먼저 현재 브랜치와 두 diff를 읽고, 변경을 어느 브랜치에 보존할지 정해요.

이 예제의 `hello main`은 `main`에 남기려던 작업이에요. 내용을 확인하고 그곳에 커밋한 뒤 이동해요. 실제 작업이 미완성이거나 다른 브랜치용이라면 이 예제를 따라 현재 브랜치에 바로 커밋하지 말고, 보존할 위치와 방법부터 정해요.

```bash theme={null}
git add index.js
git diff --cached -- index.js
git commit -m "Change greeting to hello main"
git switch feature/greeting
cat index.js
git switch main
cat index.js
git log --graph --oneline --decorate --all
```

이제 전환이 성공하고, 인사말도 `hello branch`와 `hello main`으로 바뀌어요. 아래는 **커밋 ID를 생략한 설명용 도식**이에요. 커밋 사이 화살표는 새 커밋에서 부모 커밋을 향해요. 앞편의 더 오래된 네 커밋은 생략했어요.

```mermaid theme={null}
flowchart LR
  H["HEAD"] --> M["main"]
  M --> CM["hello main 커밋"]
  F["feature/greeting"] --> CF["hello branch 커밋"]
  CM --> B["앞편의 마지막 커밋"]
  CF --> B
```

두 새 커밋의 부모는 같지만 어느 쪽도 다른 쪽의 새 커밋을 포함하지 않아요. `switch`는 작업 위치를 바꿨을 뿐, 두 변경을 합치지 않았어요.

미커밋 변경이 있다는 이유만으로 전환이 항상 실패하지는 않아요. 손실 없이 유지할 수 있으면 이동할 수 있고, 덮어쓸 위험이 있으면 기본 동작은 거절이에요. 범위와 강제 옵션은 [git switch 공식 문서](https://git-scm.com/docs/git-switch)를 참고해요.

## HEAD가 브랜치를 가리키지 않을 때도 있어요

이제 `main`의 변경은 모두 커밋했어요. 이번에는 브랜치 이름 대신 부모 커밋으로 이동해봐요. `HEAD~1`은 현재 커밋의 첫 번째 부모예요. 여기서는 두 브랜치가 갈라지기 직전의 커밋이에요.

```bash theme={null}
git status
git switch --detach HEAD~1
git branch --show-current
git rev-parse --short HEAD
cat index.js
git log --oneline -1
```

`branch --show-current`는 아무것도 출력하지 않지만, `rev-parse`는 커밋 ID를 출력해요. 파일에는 `hello world` 인사말이 있어요. **HEAD가 브랜치를 거치지 않고 커밋을 직접 가리키는 detached HEAD 상태**예요. `main`과 `feature/greeting`의 위치는 움직이지 않았어요.

이번에는 조회만 하고 돌아와요.

```bash theme={null}
git switch main
git branch --show-current
cat index.js
```

다시 `main`과 `hello main`이 보여요. detached 상태에서도 커밋은 만들 수 있지만 기존 브랜치는 그 커밋을 따라가지 않아요. 그곳에서 계속 작업을 남기려면 먼저 새 브랜치를 만들어 연결하는 편이 좋아요. 이 글에서는 detached 상태에 새 커밋을 만들지 않았어요. 자세한 모델은 [git checkout의 detached HEAD 설명](https://git-scm.com/docs/git-checkout#_detached_head)을 참고해요.

## 명령의 역할을 구분해요

아래 표는 이번 실습을 정리한 참고예요. 새 브랜치를 다시 만들라는 실행 순서는 아니에요.

| 명령 | 바뀌거나 확인되는 것 |
| - | - |
| `git branch feature/greeting` | 현재 커밋을 가리키는 브랜치를 만들어요. 전환하지 않아요 |
| `git switch feature/greeting` | 기존 브랜치로 이동해요 |
| `git switch -c feature/greeting` | 새 브랜치를 만들고 그곳으로 이동해요. 같은 이름이 이미 있으면 실패해요 |
| `git branch --show-current` | 현재 브랜치 이름을 읽어요. detached HEAD에서는 출력이 없어요 |
| `git rev-parse --short HEAD` | 현재 기준 커밋의 축약 ID를 읽어요 |

`switch -c`는 앞의 생성과 전환을 한 번에 수행할 때 쓰는 명령이에요. 이 실습에서는 두 동작의 차이를 보려고 나누어 실행했어요. `-c`와 달리 `-C`는 기존 브랜치가 있어도 위치를 다시 지정할 수 있으므로 같은 옵션으로 취급하지 않아요.

## 끝난 상태를 확인해요

```bash theme={null}
git status
git branch -v
git log --graph --oneline --decorate --all
git show main:index.js
git show feature/greeting:index.js
git diff main feature/greeting -- index.js
git diff
git diff --cached
git status --short --ignored
git remote -v
```

* 현재 브랜치는 `main`이고 작업 파일의 인사말은 `hello main`이에요. 일반 `status`는 clean이에요.

* `feature/greeting`의 인사말은 `hello branch`예요. 브랜치 간 diff에는 두 인사말의 차이가 나와요.

* 공통 다섯 커밋 뒤에 브랜치마다 하나씩 새 커밋이 있어요. 각 브랜치의 이력은 여섯 커밋이고, 두 브랜치 전체에서 서로 다른 커밋은 일곱 개예요.

* 작업 트리와 인덱스의 두 미커밋 diff는 비어 있어요. README.md와 .gitignore는 시작할 때와 같아요.

* 제외된 메모와 로그는 남아 있고 원격은 없어요. 두 브랜치는 아직 병합하지 않았어요.

브랜치를 만들었는데 폴더가 늘지 않은 이유를 이제 설명할 수 있어요. 브랜치는 기록의 위치를 가리키고, `HEAD`는 현재 작업 위치를 알려줘요. `switch`를 실행하면 그 위치에 맞춰 같은 작업 폴더의 내용을 바꿔볼 수 있어요.

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

| 상황 | 확인과 대응 |
| - | - |
| 브랜치를 만들었는데 계속 `main`이에요 | `branch`는 생성만 해요. 이동은 `switch`로 해요 |
| `feature/greeting`이 이미 있다고 해요 | `git branch -v`로 기존 작업인지 확인해요. 강제 재생성하지 말고 새 연습 저장소를 준비해요 |
| 브랜치로 이동해도 미커밋 변경이 남아요 | 유지 가능한 변경은 따라올 수 있어요. `status`와 두 diff를 확인한 뒤 커밋할 브랜치를 골라요 |
| 브랜치 전환이 거절돼요 | 덮어쓸 위험이 있는 파일과 현재 브랜치를 확인해요. 미추적 파일도 대상 브랜치의 파일과 경로가 겹치면 전환을 막을 수 있어요 |
| 현재 브랜치 이름이 비어 있어요 | `status`와 `rev-parse HEAD`로 detached HEAD인지 확인해요. 새 작업이 없다면 `git switch main`으로 돌아와요 |
| 준비 단계의 커밋이 실패했어요 | [첫 커밋 실습의 오류 대응](https://blog.nvim.me/git/learn/setup-and-first-commit.md)을 참고해요. 실패한 커밋을 완료한 뒤 다음 단계로 진행해요 |

이제 `main`과 `feature/greeting`에 각각 다른 변경이 있어요. 어느 한쪽으로 이동하는 것과 두 작업을 합치는 것은 별개의 일이에요. 현재 위치가 헷갈리면 먼저 `status`와 브랜치 이름을 읽고, [이력과 diff](https://blog.nvim.me/git/learn/read-history-and-diff.md)로 실제 차이를 확인해보세요.

> [card] [두 갈래 작업은 어떻게 합칠까요?](https://blog.nvim.me/git/learn/merge-and-commit-graph.md)
>
> 새 연습 저장소에서 fast-forward와 merge commit을 비교해요. 합친 파일과 부모 커밋으로 결과를 확인해봐요.


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