빌드노트
git switch 뒤에도 수정 중인 파일이 남는 이유
git switch 뒤에도 수정이 남는 이유를 작업 파일과 커밋 내용으로 구별한다. 독립 저장소에서 전환 성공·거부와 HEAD 보존을 확인하고, 이동 전에 commit 또는 stash로 변경을 보존할 기준을 설명한다.
git switch main으로 브랜치를 바꿨는데 수정하던 파일이 그대로 보일 수 있다. 커밋하지 않은 변경은 브랜치마다 자동 보관되지 않는다. 같은 작업 폴더에서 브랜치를 전환할 때, Git이 그 변경을 보존할 수 있으면 남겨 둔다. 특정 브랜치의 기록으로 남기려면 그 브랜치에서 커밋하고, 잠시 치워 두려면 stash를 선택한다.
공식 git switch 문서 (새 탭에서 열림)는 작업 트리와 인덱스가 깨끗해야만 전환할 수 있는 것은 아니며, 로컬 변경을 잃게 되는 전환은 중단한다고 설명한다. 여기서는 별도 옵션 없는 git switch main의 성공과 거부를 비교한다. 아래 출력은 Windows 11, Git 2.45.1.windows.1, Windows PowerShell 5.1에서 새 독립 저장소 두 개로 직접 확인한 결과다.
현재 파일과 커밋 속 파일을 나눠 본다
이미 브랜치를 바꿨다면 먼저 현재 위치와 남은 수정을 확인한다. 저장소 최상위 폴더에서 실행하고, notes.txt는 확인하려는 추적 파일의 경로로 바꾼다. 하위 파일이라면 docs/notes.txt처럼 저장소 기준 경로를 쓴다.
다음 네 명령은 현재 브랜치, 커밋과 다른 파일 내용, 커밋에 저장된 원문, 현재 커밋 식별자를 차례로 보여 준다.
git status --short --branch
git diff HEAD -- notes.txt
git show HEAD:notes.txt
git rev-parse --short HEAD
HEAD는 여기서 현재 브랜치의 마지막 커밋을 가리킨다. git diff HEAD는 그 커밋과 현재 작업 파일을 비교하므로, 스테이징 여부와 관계없이 남은 내용 차이를 볼 수 있다. git show HEAD:notes.txt는 디스크 파일을 다시 읽는 대신 그 커밋에 들어 있는 파일을 보여 준다. diff의 커밋 비교 (새 탭에서 열림), 커밋:경로 표기 (새 탭에서 열림)
재현에서는 feature-notes에서 notes.txt의 한 줄을 title=base에서 title=draft로 고치고, git add와 커밋은 하지 않았다. 위 네 명령의 출력은 다음과 같았다.
## feature-notes
M notes.txt
diff --git a/notes.txt b/notes.txt
index 76ac36c..fbd54cc 100644
--- a/notes.txt
+++ b/notes.txt
@@ -1 +1 @@
-title=base
+title=draft
title=base
dc2c7d5
M notes.txt에서 두 상태 칸 중 뒤쪽의 M은 작업 파일에 스테이징하지 않은 수정이 있다는 뜻이다. 마지막의 title=base는 git show의 출력이고, dc2c7d5는 HEAD의 짧은 식별자다. 파일을 title=draft로 고쳤어도 현재 커밋 속 내용은 여전히 title=base다. status의 짧은 출력 형식 (새 탭에서 열림)
이 조회는 이미 추적 중인 파일을 대상으로 한다. 새 파일이 ??로 표시되면 아직 커밋 속 원문이 없으므로 git show HEAD:파일경로가 실패할 수 있다. 그 실패를 파일이 사라졌다는 뜻으로 읽지 않는다.
전환이 성공해도 수정은 커밋에 들어가지 않는다
첫 저장소의 main과 feature-notes에는 모두 title=base인 notes.txt가 들어 있었다. 두 브랜치는 다른 파일인 page.txt의 내용만 달라 서로 다른 커밋을 가리켰다. 즉, main으로 갈 때 notes.txt를 다른 내용으로 교체할 필요가 없는 조건이다.
미커밋 수정이 남은 상태에서 다음 전환을 실행했다.
git switch main
종료 코드는 0이었고 Switched to branch 'main'이 나왔다. 앞의 네 조회를 다시 실행했을 때 읽을 부분은 다음과 같았다. diff의 파일 헤더만 생략했다.
## main
M notes.txt
@@ -1 +1 @@
-title=base
+title=draft
title=base
fd0a684
브랜치는 main, HEAD는 fd0a684로 바뀌었다. page.txt도 main의 내용으로 바뀌었다. 그런데 notes.txt의 수정은 그대로이고, 새 HEAD 안의 원문도 title=base다. 전환 전후 두 브랜치가 가리키는 커밋 식별자도 각각 그대로였다. 전환으로 현재 커밋은 바뀌었지만, 수정 내용을 담는 새 커밋은 만들어지지 않았다.
따라서 여기서 main에 커밋하면 그 수정은 main의 다음 커밋에 들어간다. 원래 feature-notes에서 시작한 수정이라는 이유로 Git이 저장할 브랜치를 골라 주지는 않는다. 브랜치와 현재 작업 폴더의 관계는 Git 용어집의 branch 설명 (새 탭에서 열림)에서도 확인할 수 있다.
두 브랜치가 같은 커밋을 가리키면 전환해도 HEAD 식별자가 같을 수 있다. 실제 이동 여부는 식별자 하나보다 status의 브랜치 이름과 함께 판단한다.
덮어써야 하는 파일이면 전환을 거부한다
두 번째 저장소에서는 조건 하나를 바꿨다. feature-notes의 커밋에는 title=base가 있고, 이동할 main의 커밋에는 title=main-edited가 있도록 준비했다. feature-notes에서 작업 파일만 title=draft로 수정한 상태는 앞과 같다.
전환하기 전 git show main:notes.txt로 이동할 브랜치의 원문을 조회하니 title=main-edited가 나왔다. 이 상태에서 같은 git switch main을 실행하면 다음 오류가 나왔다. 파일 목록과 중단 문구를 남기고 중간 안내 한 줄은 생략했다.
error: Your local changes to the following files would be overwritten by checkout:
notes.txt
Aborting
명령 이름은 switch인데 오류에는 checkout이라고 나왔다. 이 실행에서 확인한 메시지 그대로다. 종료 코드는 1이었다. 다시 조회하니 브랜치는 feature-notes, HEAD는 전환 전과 같은 b90c7fe였고, diff도 title=base에서 title=draft로 바뀐 상태 그대로였다. 두 브랜치의 커밋과 작업 파일 내용 모두 보존됐다.
이 경우에는 이동할 커밋의 notes.txt를 꺼내려면 현재 수정을 덮어써야 한다. Git은 전환을 중단해 그 수정을 보호했다. 거부 메시지를 만나면 먼저 커밋하거나 임시 보관할 변경을 정한다. 이 재현은 한 줄짜리 추적 파일과 스테이징하지 않은 수정으로 확인했으며, 같은 파일의 서로 다른 줄을 수정한 경우나 스테이징된 변경의 모든 전환 조건까지 검사한 것은 아니다.
이동 전에 어디에 보존할지 정한다
수정이 feature-notes의 기록으로 남아야 하고 커밋할 준비가 됐다면 그 브랜치에서 커밋한다. 이미 첫 사례처럼 main으로 이동했다면 현재 변경을 확인한 뒤 먼저 feature-notes로 돌아간다. 돌아가는 전환도 거부되면 아래 커밋 단계로 진행하지 않고 stash로 보관한다.
다음은 돌아가는 전환에 성공했고, 커밋할 수정이 notes.txt 하나뿐인 조건에서 실행한 순서다. 명령을 하나씩 실행하며 실패하면 다음 단계로 넘어가지 않는다. git diff --cached에서는 커밋할 전체 내용을 읽고, 다른 파일이 섞여 있으면 커밋 전에 대상을 정리한다.
git switch feature-notes
git add -- notes.txt
git diff --cached
git commit -m "Draft notes"
git switch main
실제 실행에서는 feature-notes에 새 커밋이 생겼다. 다시 main으로 가자 status에는 ## main만 남았고 notes.txt도 title=base였다. feature-notes의 새 커밋에는 title=draft가 저장됐다. git commit은 인덱스의 내용으로 커밋을 만들고 현재 브랜치를 갱신한다. 공식 git commit 설명 (새 탭에서 열림)
아직 커밋할 단계가 아니라면 현재 브랜치에서 stash로 잠시 보관한다. 다음은 두 번째 사례처럼 feature-notes에서 전환을 거부당한 상태에 적용한 명령이다. 파일 경로를 제한하지 않았으므로 다른 추적 파일의 변경도 함께 보관한다. 기본 stash로 ??인 새 파일이나 ignored 파일까지 모두 치웠다고 가정하지 않는다.
git stash push -m "notes before switch"
git stash list -1
git status --short --branch
git switch main
실행 결과 stash@{0}: On feature-notes: notes before switch가 생겼고, 전환 전 status에는 ## feature-notes만 남았다. 그다음 switch가 성공해 main의 파일은 title=main-edited가 됐다. 실제 작업에서도 보관 명령의 성공, 목록의 새 항목, 남은 변경을 확인한 뒤 이동한다. 공식 stash 설명 (새 탭에서 열림)
작업을 이어갈 때는 원래 브랜치로 돌아간 뒤 보관한 항목을 적용한다. 아래의 stash@{0}는 위에서 만든 항목이 계속 최신이라는 조건이다. 그사이 stash를 더 만들었다면 목록에서 메시지를 확인해 해당 항목을 고른다.
git switch feature-notes
git stash apply 'stash@{0}'
git status --short --branch
git diff HEAD -- notes.txt
이 실행에서는 다시 feature-notes에 M notes.txt와 title=draft 수정이 나타났고, HEAD는 보관 전 커밋 그대로였다. apply는 stash 항목을 목록에 남긴다. 원래 브랜치가 그사이 바뀌면 적용 중 충돌이 날 수 있으므로, 적용 실패 시에는 현재 상태를 확인하고 반복 적용하지 않는다. 충돌 복원은 이번 실험에서 실행하지 않았다. stash apply의 동작 (새 탭에서 열림)
특히 브랜치를 바꾼 뒤 처음 커밋하기 직전에는 status의 브랜치 이름과 diff의 변경 대상을 다시 확인한다. 전환에 성공했어도 이전 작업의 수정이 함께 남아 있을 수 있다.