포스트

[Git] stash와 worktree로 긴급 작업 전환하기

Git stash로 untracked 파일과 staged 상태를 안전하게 보관·복원하고, worktree로 핫픽스 작업 공간을 병렬 운영하는 절차를 설명합니다.

수정 중인 파일을 잃지 않고 핫픽스로 이동하고, 돌아온 뒤 정확히 복원하는 절차

대상 환경과 실패 증상

Windows PowerShell 또는 Git Bash에서 Git 2.23 이상을 사용하고, feature/report-filter 작업 도중 긴급 핫픽스를 요청받은 상황을 가정합니다. git switch main을 실행했을 때 “local changes would be overwritten”로 중단되거나, 미완성 변경을 억지로 커밋하고 싶지 않은 것이 시작 증상입니다.

앞선 글 브랜치부터 rebase와 PR 병합까지는 브랜치를 만들고 원격과 통합하는 흐름을 설명했습니다. 다만 작업 디렉터리가 브랜치를 바꿀 수 있는 상태라고 가정했습니다. 실무에서는 저장하지 않은 변경이 많은 순간에 장애 대응이 들어옵니다. 이번 글은 그 빈틈을 짧게 보관하는 stash와 별도 폴더에서 병렬로 일하는 worktree로 메웁니다.

1. 먼저 변경의 위치를 기록합니다

전환 전에 현재 브랜치, staged 변경, unstaged 변경, untracked 파일을 한 번에 기록합니다. 이 관찰값이 복원 뒤 비교 기준입니다.

git --version
git status --short --branch
git diff --stat
git diff --cached --stat

기대 관찰값: 첫 줄에는 현재 브랜치가, 각 파일 앞의 두 글자에는 index와 working tree 상태가 표시됩니다. ??는 아직 추적하지 않는 파일입니다. 이 목록을 보지 않고 전환하면 새 파일을 stash에서 빠뜨리기 쉽습니다.

2. stash와 worktree는 해결하는 문제가 다릅니다

둘 다 급한 전환에 쓰이지만 작업 모델이 다릅니다. stash는 현재 폴더를 깨끗하게 만든 뒤 순서대로 작업하고, worktree는 같은 저장소에 연결된 새 폴더를 만들어 두 브랜치를 동시에 유지합니다.

왼쪽은 현재 폴더의 변경을 stash 선반에 보관했다가 복원하는 순차 흐름이고, 오른쪽은 공유 저장소에서 기존 기능 브랜치 폴더와 별도 핫픽스 폴더가 병렬로 연결된 구조를 비교한 그림
stash는 한 작업 공간을 비웠다가 복원하고, worktree는 브랜치마다 독립된 HEAD·index·작업 폴더를 둡니다.
판단 기준stashworktree
추천 상황몇 분 또는 몇 시간의 짧은 전환핫픽스와 기존 작업을 함께 실행·검증
작업 폴더기존 폴더 하나브랜치별 별도 폴더
주요 위험untracked 누락, 너무 이른 drop수정된 worktree 강제 삭제, 같은 브랜치 중복 checkout
안전한 종료apply·검증 후 dropclean 확인 후 worktree remove

3. 짧은 전환이면 stash를 명시적으로 만듭니다

기본 stash는 추적 중인 수정과 staged 변경을 저장하지만 untracked 파일은 포함하지 않습니다. 새 파일까지 함께 작업했다면 -u를 붙이고, 나중에 이유를 알 수 있는 메시지를 남깁니다.

git stash push -u -m "wip: report filter before login hotfix"
git stash list
git stash show --stat --include-untracked 'stash@{0}'
git status --short --branch

기대 관찰값: stash@{0}에 메시지가 보이고, 마지막 status에는 핫픽스와 무관한 변경이 남지 않아야 합니다. PowerShell에서는 중괄호 해석을 피하려고 stash 참조를 작은따옴표로 감쌉니다.

  • -u: untracked 파일까지 포함하지만 ignored 파일은 포함하지 않습니다.
  • -a: ignored 파일까지 치우므로 빌드 산출물·로컬 환경 파일이 예상 밖으로 이동할 수 있어 기본 절차로 권하지 않습니다.
  • show: 이름과 파일 목록이 맞는지 확인하기 전에는 브랜치를 바꾸지 않습니다.

핫픽스를 만들고 완료합니다

git switch main
git pull --ff-only origin main
git switch -c hotfix/login-timeout

# 수정과 테스트 후
git add src/login-timeout.ps1
git commit -m "fix: bound login request timeout"
git push -u origin hotfix/login-timeout

진단 분기: 첫 switch 뒤에도 변경이 보이면 stash 대상에서 빠진 ignored 파일인지 확인합니다. git clean -fd로 즉시 지우지 말고 먼저 git clean -nd로 삭제 후보만 봅니다. 핫픽스 브랜치가 이미 있다면 새 이름을 만들지 말고 원격·로컬 브랜치 상태를 확인합니다.

4. 복원은 pop보다 apply 후 drop이 안전합니다

핫픽스를 끝낸 뒤 원래 브랜치로 돌아와 working tree가 깨끗한지 확인하고 stash를 적용합니다. --index는 stash 전의 staged 상태까지 되살리려 시도합니다.

git switch feature/report-filter
git status --short --branch
git stash apply --index 'stash@{0}'
git status --short --branch
git diff --stat
git diff --cached --stat

처음 기록한 파일 목록과 staged 상태가 돌아왔고 테스트가 통과했을 때만 아래처럼 stash를 지웁니다.

git stash drop 'stash@{0}'

충돌 시 복구: 즉시 drop하지 말고 충돌 파일을 확인합니다. 현재 기준과 너무 멀어 적용이 복잡하다면, clean한 상태에서 git stash branch recover/report-filter 'stash@{0}'로 stash를 만들 당시 커밋에서 복구 브랜치를 만들 수 있습니다. 성공하면 Git이 해당 stash를 목록에서 제거하므로 결과 브랜치를 먼저 확인합니다.

5. 기존 작업도 실행해야 한다면 worktree를 만듭니다

긴급 수정 중에도 기존 기능 브랜치의 개발 서버나 테스트를 계속 돌려야 한다면 stash보다 worktree가 명확합니다. 아래 예시는 현재 저장소의 형제 경로에 핫픽스 폴더를 만들고 최신 origin/main에서 새 브랜치를 시작합니다.

git fetch origin
git worktree add -b hotfix/login-timeout ..\shop-hotfix origin/main
git worktree list
git -C ..\shop-hotfix status --short --branch

기대 관찰값: 목록에 기존 폴더와 ..\shop-hotfix가 서로 다른 브랜치로 표시됩니다. 두 폴더는 Git 객체와 refs를 공유하지만 각자 HEAD, index, working tree를 가집니다.

진단 분기: “already checked out” 오류가 나면 같은 로컬 브랜치가 다른 worktree에서 사용 중인 것입니다. 보호를 무시하는 --force를 붙이지 말고 git worktree list로 위치를 찾거나 새 브랜치 이름을 사용합니다.

별도 폴더에서 수정하고 push합니다

git -C ..\shop-hotfix status --short --branch
git -C ..\shop-hotfix add src/login-timeout.ps1
git -C ..\shop-hotfix commit -m "fix: bound login request timeout"
git -C ..\shop-hotfix push -u origin hotfix/login-timeout

환경 파일과 의존성 디렉터리는 working tree별로 따로 준비될 수 있습니다. 반대로 저장소 설정과 refs 일부는 공유되므로 “완전히 독립된 clone”으로 오해하면 안 됩니다. 같은 브랜치를 두 폴더에서 동시에 수정하지 않는 것이 기본 안전장치입니다.

6. worktree는 폴더 삭제가 아니라 Git 명령으로 정리합니다

PR 병합과 필요한 기록 보존을 확인한 뒤, 별도 폴더가 clean일 때 정리합니다. 수정 파일이 있으면 remove가 거부하는 것이 정상입니다.

git -C ..\shop-hotfix status --short --branch
git worktree remove ..\shop-hotfix
git branch -d hotfix/login-timeout
git worktree prune --dry-run

롤백 기준: status에 수정 또는 untracked 파일이 하나라도 보이거나, hotfix 브랜치가 원격·PR에 반영됐는지 확실하지 않으면 제거를 중단합니다. git worktree remove --force와 탐색기 수동 삭제는 기본 절차에서 제외합니다. 폴더를 수동 이동해 연결이 끊겼다면 삭제보다 git worktree repair를 먼저 검토합니다.

7. 실무 선택 규칙

  1. 전환이 짧고 현재 폴더 하나면 충분하면 stash를 사용합니다.
  2. 두 브랜치를 동시에 실행·비교해야 하면 worktree를 사용합니다.
  3. stash에는 메시지와 -u 포함 여부를 명시하고, 적용 전후 status를 비교합니다.
  4. worktree는 clean 상태를 확인하고 Git 명령으로 제거합니다.
  5. 복구 명령보다 먼저, drop·force·수동 삭제를 늦추는 것이 가장 안전합니다.

핵심은 “stash와 worktree 중 더 고급인 명령”을 고르는 것이 아닙니다. 현재 작업을 잠깐 접을 것인지, 두 작업 공간을 동시에 유지할 것인지를 먼저 결정하고, 제거는 검증 뒤에 실행하는 것입니다.

공식 출처

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.