핫픽스가 오면 스태시 대신 깃 워크트리로 형제 폴더를 깐다. 객체는 공유하고 체크아웃만 따로 둔다. 같은 브랜치는 두 폴더에 동시에 못 깐다. 노드 모듈과 환경 파일, 도커 포트는 워크트리마다 다시 준비한다. 개발자, 깃허브, Next.js, 백엔드, 데브옵스, 커서, API 기준으로 리무브와 프룬, 리페어 순서만 남긴다.
핫픽스가 오면 스태시를 쓰지 않습니다. 형제 폴더에 깃 워크트리를 하나 더 깔면, 반쯤 고친 파일은 그대로 두고 다른 브랜치를 동시에 엽니다. 객체 칸은 같은 저장소를 씁니다. 헤드와 인덱스만 폴더마다 다릅니다. 같은 브랜치는 두 폴더에 동시에 깔리지 않습니다. 노드 모듈과 환경 파일은 워크트리마다 다시 준비합니다. 저는 12개 사이트 핫픽스를 이렇게 빼고, 끝나면 리무브와 프룬으로 폴더를 지웁니다.
커서 3 에이전트 창이 만든 워크트리와 터미널 명령은 같은 칸이 아닙니다. 에이전트 창은 커서 3 분석에 두고, 여기는 깃 명령만 봅니다. 네이버 클라우드에 올리는 저장소도 명령은 같습니다. 숫자는 2026년 8월 깃 워크트리 공식 문서 기준입니다.
스태시가 깨지는 자리
파일 이름만 바뀐 작업 중이면 스태시가 편합니다. 새 파일과 삭제, 이름 변경이 섞이면 꺼낼 때 충돌이 납니다.
공식 문서 예시도 같은 장면입니다. 리팩터 도중에 급한 수정이 오면, 지저분한 작업 트리를 건드리지 말고 임시 워크트리를 만들라고 적혀 있습니다. 스태시는 한 폴더의 변경을 선반에 올립니다. 워크트리는 폴더를 하나 더 엽니다. 클론을 하나 더 받으면 객체 칸이 두 벌이 됩니다. 워크트리는 객체 칸을 공유합니다.
공식 예시의 장면 | 문서 예시는 git worktree add -b emergency-fix ../temp master로 임시 폴더를 만듭니다. 고치고 커밋한 뒤 git worktree remove ../temp로 지웁니다. 메인 폴더의 리팩터는 손대지 않습니다.
형제 폴더에 핫픽스 워크트리를 만든다
메인 저장소의 부모 폴더에 새 경로를 줍니다. 브랜치 이름을 경로 끝으로 두면 깃이 그 이름으로 새 브랜치를 만듭니다.
지금 있는 브랜치를 열려면 경로 뒤에 브랜치 이름을 적습니다. 새 이름을 강제로 쓰려면 -b입니다. 이미 있는 이름을 덮어쓰려면 -B입니다. 커밋만 보고 버리려면 -d입니다. 오리진에만 있는 이름이면, 리모트가 하나일 때 추적 브랜치를 만들어 줍니다. worktree.guessRemote를 켜면 경로 끝 이름과 같은 리모트 추적을 먼저 찾습니다.
핫픽스 워크트리를 형제 폴더에 깐다
# 메인 저장소: ~/work/tech
cd ~/work/tech
git fetch origin
# 경로 끝 이름(hotfix-login)으로 새 브랜치를 만들고 체크아웃
git worktree add -b hotfix-login ../tech-hotfix-login origin/main
# 이미 있는 릴리스 브랜치를 다른 폴더에서 연다
git worktree add ../tech-release-2026 release/2026-08
# 버릴 실험. 브랜치를 만들지 않는다
git worktree add --detach ../tech-try HEAD
git worktree list
목록의 첫 줄은 메인 워크트리입니다. 그 아래가 연결 워크트리입니다. 잠겨 있으면 locked, 폴더가 사라져 지울 수 있으면 prunable이 붙습니다. 스크립트는 --porcelain과 -z를 같이 씁니다. 경로에 한글이나 공백이 있어도 줄이 안 갈립니다.
목록을 스크립트가 읽게 찍는다
git worktree list --verbose
git worktree list --porcelain -z | tr '\0' '\n'
핫픽스는 스태시가 아니라 형제 폴더의 워크트리로 뺀다
같은 브랜치는 두 폴더에 못 깐다
이미 체크아웃된 브랜치를 다른 워크트리에 다시 열면 명령이 거절합니다. --force가 아니면 두 폴더가 같은 브랜치를 동시에 잡지 않습니다.
에러 문장은 버전마다 조금 다릅니다. 뜻은 같습니다. 그 브랜치는 이미 다른 작업 트리가 잡고 있다. 핫픽스를 메인에서 딴 새 이름으로 빼면 이 거절을 피합니다. 정말 같은 브랜치를 두 곳에서 봐야 하면 --force입니다. 인덱스가 서로 덮일 수 있어서, 리뷰용 읽기에만 씁니다.
경로가 이미 등록돼 있는데 폴더만 지워진 경우도 거절합니다. 그때는 --force 한 번입니다. 잠긴 경로를 다시 쓰려면 --force를 두 번 줍니다.
이미 깔린 브랜치를 새 이름으로 뺀다
# 거절 예: main 이 메인 폴더에 이미 깔려 있다
# git worktree add ../tech-main-copy main
# 새 이름으로 같은 커밋에서 시작한다
git worktree add -b hotfix-login ../tech-hotfix-login origin/main
# 누가 그 브랜치를 잡고 있는지 본다
git worktree list
git branch -vv
참조는 공유, 헤드는 폴더마다 | refs/heads는 저장소가 같이 씁니다. 헤드와 인덱스, refs/bisect 같은 칸만 워크트리마다 다릅니다. 한 폴더에서 브랜치를 지우면 다른 폴더의 그 이름도 사라집니다. 커밋은 객체 칸에 남습니다.
노드 모듈과 환경 파일은 워크트리마다 따로다
워크트리는 소스만 깔립니다. node_modules와 .env.local은 따라오지 않습니다. 넥스트 앱이면 설치와 환경 복사를 한 번 더 합니다.
심볼릭 링크로 노드 모듈을 공유하면 락파일이 다른 브랜치에서 깨집니다. 핫픽스 폴더에서 다시 설치하는 편이 안전합니다. 환경 파일은 메인에서 복사하되, 포트와 데이터베이스 이름을 바꿉니다. 같은 포트로 두 넥스트를 띄우면 한쪽이 죽습니다. 시크릿을 커밋하지 않는 칸은 환경변수 체크리스트와 같습니다.
핫픽스 폴더에서 설치하고 포트를 나눈다
cd ../tech-hotfix-login
# 락파일이 있는 패키지 매니저를 그대로 쓴다
npm ci
# 또는: pnpm install --frozen-lockfile
# 메인 환경은 복사만 하고, 로컬 포트는 덮어쓴다
cp ../tech/.env.local .env.local
printf '\nPORT=3002\n' >> .env.local
npm run dev
도커 컴포즈를 쓰는 저장소는 프로젝트 이름과 볼륨이 폴더 이름을 따릅니다. 형제 폴더면 컨테이너 이름이 갈립니다. 고정 컨테이너 이름을 컴포즈에 박아 두면 두 워크트리가 한 컨테이너를 뺏습니다. 컴포즈 파일의 container_name을 빼거나, 워크트리마다 오버라이드 파일을 둡니다. 이미지 줄이기는 멀티스테이지 빌드에 있습니다.
연결 워크트리의 맨 위 .git은 디렉터리가 아닙니다. 메인 저장소의 .git/worktrees/이름을 가리키는 파일입니다. 메인을 옮기면 연결 쪽이 메인을 못 찾습니다. 연결만 옮기면 메인이 연결을 못 찾습니다. 서브모듈이 있는 워크트리는 move가 거절합니다. 문서도 슈퍼프로젝트의 다중 체크아웃을 권하지 않습니다.
옮긴 뒤 연결을 다시 붙인다
# 정석: 깃이 양쪽 경로를 같이 고친다
git worktree move ../tech-hotfix-login ../hotfix/login
# 이미 mv 로 옮긴 뒤. 새 폴더 안에서 고친다
cd ../hotfix/login
git worktree repair
# 메인까지 같이 옮겼으면 메인에서 연결 경로를 넘긴다
cd ~/work/tech
git worktree repair ../hotfix/login ../hotfix/pay
git worktree list
잠금과 프룬으로 죽은 칸을 지운다
외장 디스크나 네트워크 공유에 둔 워크트리는 잠급니다. 폴더가 안 보일 때 깃이 관리 칸을 지우지 못하게 막습니다. 다 쓴 폴더는 리무브입니다. 폴더만 휴지통에 넣었으면 프룬입니다.
리무브는 깨끗한 워크트리만 받습니다. 추적 파일 변경이나 추적 안 된 파일이 있으면 --force입니다. 잠긴 워크트리를 지우려면 --force를 두 번 줍니다. 메인 워크트리는 리무브로 지울 수 없습니다. 프룬의 -n은 지울 목록만 보여 줍니다. 오래된 것만 지우려면 --expire입니다. 자동 정리는 gc.worktreePruneExpire가 맡습니다.
잠그고, 지우고, 죽은 칸만 프룬한다
# 외장 디스크. 이유 문자열은 잠금 파일에 남는다
git worktree lock --reason 'SSD not always mounted' ../tech-usb
git worktree unlock ../tech-usb
# 핫픽스가 머지된 뒤
cd ~/work/tech
git worktree remove ../tech-hotfix-login
# 폴더만 지운 뒤 관리 칸을 청소
git worktree prune -n -v
git worktree prune --expire 2.weeks.ago
매주 한 번 목록을 보고 프룬하는 습관이 디스크보다 머리를 아낍니다. 의존성 봇이 연 피알은 디펜더봇 체크에 두고, 여기는 로컬 폴더만 비웁니다.
항상 안 붙는 디스크의 워크트리는 잠근다. 다 쓰면 리무브, 폴더만 지웠으면 프룬
커서 창과 깃 명령을 한 칸에 두지 않는다
커서 3 에이전트 창도 워크트리라는 단어를 씁니다. 그건 아이디이 창이 에이전트마다 폴더를 나누는 기능입니다. 터미널의 git worktree와 설정 칸이 다릅니다.
에이전트가 만든 폴더를 깃 리무브로 지우려다 창이 빈 경로를 가리키는 경우가 있습니다. 반대로 터미널에서 깐 워크트리를 커서 창이 모릅니다. 아이디이를 쓰기 전에 git worktree list로 실제 경로를 봅니다. 액션에서 체크아웃 한 줄만 쓰는 파이프라인은 재사용 워크플로와 액션 오아이디씨에 있습니다. 러너는 보통 워크트리 하나가 맞습니다.
핫픽스 한 줄을 스크립트로 고정한다
#!/bin/sh
# usage: ./scripts/wt-hotfix.sh login
set -eu
name="$1"
root="$(git rev-parse --show-toplevel)"
parent="$(dirname "$root")"
dest="$parent/$(basename "$root")-hotfix-$name"
git fetch origin
git worktree add -b "hotfix-$name" "$dest" origin/main
cd "$dest"
if [ -f package-lock.json ]; then
npm ci
elif [ -f pnpm-lock.yaml ]; then
pnpm install --frozen-lockfile
fi
if [ -f "$root/.env.local" ]; then
cp "$root/.env.local" .env.local
fi
echo "worktree: $dest"
git worktree list
상대 경로를 켜는 자리 | 저장소와 워크트리를 통째로 옮길 일이 많으면 worktree.useRelativePaths=true입니다. 이 값은 extensions.relativeWorktrees를 같이 켭니다. 옛 깃은 그 확장이 있는 저장소를 거절할 수 있습니다. 팀 최저 깃 버전을 먼저 맞춥니다. 정본은 깃 설정 문서입니다.
참고 자료
git-worktree 공식 문서 - add·list·lock·move·prune·remove·repair, 2026년 8월 current