Git worktree로 수정 중인 파일을 두고 다른 브랜치 작업하기
기능을 반쯤 만들었는데 긴급 수정 요청이 들어왔다. 커밋하기 애매한 변경은 그대로 둔 채 수정 브랜치를 편집기에 나란히 열 수는 없을까?

- git worktree add는 같은 저장소에 별도 작업 폴더를 만들어 두 브랜치를 동시에 열게 한다.
- 새 폴더에는 지정한 커밋의 파일이 생기며, 기존 폴더의 미커밋 변경과 스테이징 상태가 자동으로 옮겨가지는 않는다.
- 작업한 커밋은 공유 이력에 남고, 폴더 제거·브랜치 통합·브랜치 삭제는 각각 별도 명령으로 처리한다.
작업을 치워 둘 때와 두 폴더를 함께 쓸 때
기능을 만들던 중 긴급 수정 요청이 들어오면 현재 파일을 어디에 둘지부터 고민하게 된다. git worktree는 그 파일을 원래 위치에 남겨 둔 채 다른 브랜치를 새 폴더에 펼친다. 편집기에서 기존 개발 화면을 닫지 않고, 새 폴더에서 수정할 코드와 테스트를 열 수 있다.
잠깐 다른 브랜치만 확인할 일이라면 git stash가 간단하다. 현재 작업 디렉터리와 스테이징 영역의 변경을 저장해 두고 폴더를 HEAD 커밋 상태로 되돌린 다음, 나중에 변경을 다시 적용한다. 두 작업을 동시에 펼칠 필요가 없는 경우에 맞는다.
worktree는 변경을 넣었다 꺼내는 대신 폴더를 추가한다. 긴급 수정 코드를 보다가 원래 기능으로 돌아가도 브랜치를 전환할 필요가 없다. 원격 저장소를 다시 clone하는 방법도 두 폴더를 만들지만, 이 경우에는 별도 저장소의 브랜치와 원격 설정을 관리하게 된다.
| 방법 | 원래 변경의 위치 | 추가 작업 | 맞는 상황 |
|---|---|---|---|
| stash다. | stash에 보관했다가 다시 적용한다. | 기존 폴더의 브랜치를 바꾼다. | 한 번에 한 작업만 열어 두면 된다. |
| worktree다. | 기존 폴더에 그대로 남는다. | 이력을 공유하는 새 폴더에서 한다. | 개발·긴급 수정·검토를 나란히 진행한다. |
| 별도 clone이다. | 원래 저장소에 남는다. | 새 저장소에서 한다. | 브랜치와 저장소 설정까지 따로 관리한다. |
worktree에서는 한 폴더에서 만든 커밋을 다른 폴더가 같은 저장소의 커밋으로 볼 수 있다. 별도 clone 사이처럼 커밋을 전달하려고 원격에 올렸다 다시 받을 필요가 없다. 반대로 브랜치 이름과 기본 저장소 설정도 공유하므로, 원격 연결이나 설정까지 독립시키려는 목적에는 별도 clone이 더 잘 맞는다.
최신 원격 main에서 긴급 수정 폴더 만들기
아래 예는 project 폴더의 기능 브랜치에서 작업 중이며, 원격 이름이 origin이고 origin/main이 있는 일반 저장소를 가정한다. 긴급 수정 브랜치 fix/login과 형제 폴더 ../project-login은 아직 없다고 가정한다. 명령은 계속 원래 project 폴더에서 실행한다.
git status --short --branch
git fetch origin &&
git worktree add --no-track -b fix/login ../project-login origin/main &&
git worktree list 첫 명령은 현재 브랜치와 변경을 보여 준다. fetch는 일반적인 원격 추적 설정에서 원격 이력을 받아 origin/main을 갱신한다. &&로 연결한 다음 명령은 fetch가 성공했을 때만 실행되며, 그 커밋에서 새 브랜치와 작업 폴더를 만든다. 원격 접속이 실패하면 기존 참조로 작업을 시작하지 않고 멈춘다. 기존 폴더의 수정 내용과 스테이징 상태는 그 자리에 남는다.
출발점을 main 대신 origin/main으로 적은 이유는 로컬 main이 더 오래된 커밋일 수 있기 때문이다. Git fetch 문서가 설명하듯 일반적인 fetch는 원격 추적 참조를 갱신하며, 현재 브랜치에 자동 병합하지 않는다. ‘방금 fetch했으니 로컬 main도 최신이다’라고 생각하면 예상보다 오래된 코드에서 긴급 수정을 시작할 수 있다.
--no-track은 새 수정 브랜치의 upstream을 자동으로 origin/main에 연결하지 않도록 한다. upstream은 인자 없이 pull하거나 앞섬·뒤처짐을 표시할 때 참고하는 브랜치다. 수정용 브랜치가 main을 그대로 추적하게 만들기보다, 나중에 실제 수정 브랜치를 원격으로 보낼 때 연결을 정하는 구성이다.
이미 만들어 둔 브랜치를 열 때는 -b를 빼고 git worktree add ../project-login fix/login처럼 쓴다. 같은 브랜치가 다른 worktree에 체크아웃돼 있으면 Git은 기본적으로 거부한다. git worktree list에서 해당 폴더를 찾아 열거나 새 브랜치를 만들면, 한 브랜치를 두 폴더에서 동시에 움직이는 충돌을 피할 수 있다.
수정 없이 특정 커밋만 검토하려면 git worktree add --detach ../project-review origin/main도 가능하다. 이 폴더의 HEAD는 브랜치 이름에 연결되지 않는다. 검토 중 수정 사항을 커밋해 계속 보관할 생각이 생겼다면 먼저 그 폴더에서 새 브랜치를 만들어 커밋의 이름을 확보하는 편이 다음 작업을 이어 가기 쉽다.
파일과 스테이징은 폴더마다, 커밋 이력은 함께
Git worktree 문서는 각 worktree에 HEAD와 index 같은 개별 상태가 있고, 객체와 일반 브랜치 참조는 공유한다고 설명한다. HEAD는 그 폴더가 현재 어느 커밋이나 브랜치를 보고 있는지 나타낸다. index는 다음 커밋에 넣을 내용을 준비하는 스테이징 영역이다.
따라서 원래 폴더에서 일부 파일만 git add해 둔 상태가 새 폴더의 커밋에 섞이지 않는다. 새 폴더에서 파일을 수정해도 원래 폴더의 같은 경로 파일이 즉시 바뀌지 않는다. 두 폴더는 서로 다른 체크아웃이며, 변경을 합치려면 나중에 커밋을 대상으로 병합하는 과정이 필요하다.
| 대상 | worktree에서의 동작 |
|---|---|
| 체크아웃된 파일·미커밋 변경이다. | 각 폴더에 따로 존재한다. |
| HEAD와 스테이징 영역이다. | 각 폴더가 따로 가진다. |
| 커밋 객체·일반 브랜치 이름이다. | 같은 저장소에서 공유한다. |
| 기본 저장소 설정·원격 정보다. | 기본적으로 공유하며 별도 worktree 설정 기능은 따로 활성화한다. |
| stash 목록이다. | 저장소에서 공유하므로 다른 worktree에서도 같은 항목을 볼 수 있다. |
연결된 폴더의 .git은 저장소 전체를 복사한 디렉터리 대신 관리 위치를 가리키는 파일일 수 있다. Git 저장소 구조 문서에 나오는 이 연결 때문에 파일 폴더는 추가돼도 모든 이력을 별도 저장소로 복제할 필요는 없다. 다만 실제 체크아웃 파일과 각 폴더의 빌드 결과는 디스크 공간을 사용한다.
이 구조가 프로그램 실행 환경까지 나눠 주는 것은 아니다. Git에 기록하지 않은 .env, 의존성 폴더, 로컬 데이터 파일은 새 체크아웃에 자동으로 생기지 않는다. 필요한 설정을 프로젝트 절차에 맞춰 준비한 뒤 실행해야 한다. 두 앱이 같은 포트나 같은 외부 데이터베이스를 사용하면 작업 폴더가 달라도 그 자원에서 충돌하거나 같은 데이터를 바꿀 수 있다.
새 폴더의 수정 내용을 커밋하고 main에 합치기
이제 편집기에서 ../project-login을 열어 긴급 수정을 한다. 아래의 src/login.js는 예시 파일명이며 실제 고친 파일로 바꾼다. git -C는 셸 위치를 옮기지 않고 지정한 폴더에서 Git 명령을 실행하므로, 원래 기능 브랜치와 수정 브랜치를 혼동하기 어렵다.
git -C ../project-login status --short --branch
git -C ../project-login diff
git -C ../project-login add src/login.js
git -C ../project-login diff --cached
git -C ../project-login commit -m "Fix login handling" diff로 수정 내용을 읽고, add로 선택한 파일을 스테이징한 뒤 diff --cached로 실제 커밋할 변경을 확인하는 순서다. 프로젝트가 정한 테스트도 이 수정 폴더에서 실행한다. 커밋이 만들어지면 fix/login이 앞으로 이동하지만 main과 원래 기능 브랜치의 파일이 자동으로 바뀌지는 않는다.
팀이 pull request로 통합한다면 이 브랜치를 원격에 올리고 기존 검토 절차를 따르면 된다. 로컬에서 합치는 경우에는 main을 사용할 폴더의 작업부터 끝내거나 보관해 깨끗한 상태로 만든다. Git merge 문서는 미커밋 변경이 있는 상태에서 병합을 시작하면 충돌 후 원상 복구가 어려울 수 있다고 설명한다.
원래 폴더의 기능 작업을 안전하게 커밋하거나 보관했고, main이 다른 worktree에서 사용 중이지 않다면 다음 순서로 통합할 수 있다. 첫 병합의 --ff-only는 로컬 main을 원격 이력으로 단순히 앞으로 이동할 수 있을 때만 진행한다.
git switch main &&
git fetch origin &&
git merge --ff-only origin/main &&
git merge fix/login &&로 연결했으므로 앞 명령이 성공해야 다음 명령을 실행한다. 로컬 main과 원격 main이 이미 갈라졌다면 --ff-only 단계에서 멈춘다. 이 경우 두 이력을 살펴보고 프로젝트의 병합 또는 리베이스 방식을 적용한다. 이어지는 fix/login 병합도 같은 줄이 양쪽에서 달라졌다면 충돌할 수 있다. worktree는 수정할 공간을 나눠 주지만 서로 다른 변경을 합치는 Git의 규칙을 없애지는 않는다.
기존 수정본을 옮기는 일은 새 작업을 여는 일과 다르다
Stack Overflow의 실제 질문에는 새 worktree를 만들었는데 수정 중인 파일이 원래 폴더에 남는다는 상황이 나온다. 질문자는 미해결 충돌이 있는 병합을 진행 중이었고, 그 상태까지 다른 폴더에 옮기고 싶어 했다. 새 worktree는 지정한 커밋을 체크아웃하므로 작업 중인 파일과 충돌 상태를 복제하지 않는다.
목적이 병합 작업을 그대로 둔 채 다른 일을 시작하는 것이라면 처음의 긴급 수정 절차로 충분하다. 원래 폴더에서 진행하던 병합은 그곳에 남고 새 폴더는 별도 브랜치에서 출발한다. 반면 기존 변경 자체를 다른 폴더에서 계속 편집하려면 전송 작업을 따로 해야 한다.
미해결 충돌이 없는 일반 변경이라면 stash를 통해 옮길 수 있다. 아래는 앞의 긴급 수정 예와 별개인 이동 예다. 원래 변경을 가진 폴더에서 시작하며 새 이름과 경로는 비어 있다고 가정한다. 먼저 목록을 출력하는 줄까지 실행한다. 저장과 폴더 생성이 성공했고, stash@{0}의 메시지가 방금 지정한 문구인지 확인한 뒤 아래쪽 적용 명령을 실행한다. 저장할 변경이 없다고 나오거나 어느 단계가 실패하면 적용 단계로 넘어가지 않는다.
git stash push -u -m "Move unfinished work" &&
git worktree add -b feature/moved ../project-moved HEAD &&
git stash list 새 저장본의 메시지와 폴더 생성을 확인했다면 해당 항목을 적용한다.
git -C ../project-moved stash apply --index 'stash@{0}' &&
git -C ../project-moved status --short -u는 추적하지 않은 파일까지 저장하지만 ignore된 파일은 포함하지 않는다. apply --index는 파일 변경과 스테이징 상태의 복원을 시도한다. 적용 과정에서 충돌이 생기면 그 복원이 완성되지 않을 수 있다. apply는 적용 뒤 stash 항목을 남기므로 새 폴더의 결과를 확인할 때까지 저장본을 유지할 수 있다.
stash 목록은 공유되므로 확인부터 적용까지 다른 worktree에서 stash를 추가하거나 지우지 않는 상태에서 진행한다. 중간에 목록이 바뀌었다면 메시지로 항목을 다시 찾고 적용 명령의 번호도 바꾼다. 복원된 변경을 확인하고 저장본이 더 필요 없을 때 해당 항목을 git stash drop으로 지운다. 이 예를 미해결 병합 충돌의 이동 절차로 사용할 수는 없다. 질문자의 경우처럼 unmerged 파일이 남아 있다면 기존 병합을 원래 폴더에 두고 다른 작업을 추가하는 편이 맞는다.
폴더 제거, 브랜치 삭제, 관리 정보 정리
수정 폴더의 역할이 끝나면 Git의 제거 명령으로 정리할 수 있다. 먼저 변경과 개인 파일을 확인한다. status --ignored는 일반 상태 출력에서 숨겨지는 ignore 대상도 보여 주므로, 재생성할 수 없는 로컬 설정이나 데이터가 그 폴더에 남았는지 살펴보는 데 도움이 된다.
git -C ../project-login status --short --ignored
git worktree remove ../project-login
git worktree list remove는 작업 폴더와 연결된 관리 정보를 정리한다. 커밋하지 않은 변경이나 추적하지 않은 파일 때문에 거부되면 먼저 그 내용을 커밋하거나 다른 곳에 보관한다. 계속 사용할 로컬 설정과 데이터도 폴더를 없애기 전에 다른 위치로 옮긴다.
폴더를 없애도 fix/login 브랜치와 커밋은 남는다. 앞에서 main에 통합했고 더 사용할 일이 없다면 main에서 git branch -d fix/login으로 브랜치를 별도로 지운다. branch 문서에 따르면 -d는 설정된 upstream, 없으면 HEAD에 완전히 병합됐는지 검사한다. 이 예는 처음부터 upstream을 자동 설정하지 않았으므로 main으로 통합한 결과를 기준으로 정리할 수 있다.
파일 탐색기로 폴더만 지워 목록에 연결 정보가 남았다면 git worktree prune --dry-run --verbose로 정리 대상을 먼저 보고 git worktree prune을 실행한다. prune은 살아 있는 작업 폴더를 제거하는 명령이 아니라 남은 관리 정보를 청소하는 명령이다. 외장 드라이브처럼 잠시 접근할 수 없는 폴더는 git worktree lock --reason "External drive" 경로로 보호할 수 있으며, 잠금을 풀 때는 unlock을 쓴다.
폴더 위치를 바꾸려면 지원되는 경우 git worktree move를 사용하고, 수동 이동 뒤 연결이 어긋났다면 git worktree repair로 복구할 수 있다. 서브모듈을 사용하는 여러 체크아웃은 공식 문서에 지원이 불완전하다고 명시돼 있다. 이 가이드는 일반 저장소의 흐름이며, 서브모듈 프로젝트는 그 제약을 반영한 별도 작업 절차가 필요하다.