Git 실전 · 핫픽스가 왔는데 하던 일이 반쯤 열려 있다 · 이론
하던 일을 밀어 넣지 않고 자리를 하나 더 만든다
한 줄 요약
git worktree 는 저장소 하나(.git 하나)에 작업 디렉터리를 여러 개 붙이는 기능입니다.
커밋·브랜치·객체는 전부 공유하고 체크아웃한 자리만 달라집니다. 그래서 하던 일을
어디로도 밀어 넣지 않은 채 다른 브랜치를 열 수 있습니다.
왜 이게 필요했나
금요일 오후에 이런 일이 납니다. 기능 브랜치에서 파일 열두 개를 반쯤 고쳐 놓았는데,
운영에서 장애가 나고 릴리스 브랜치에 핫픽스를 넣어야 합니다. 보통은 이렇게 합니다.
git stashgit switch release# 고치고 커밋하고 올리고git switch featuregit stash pop이 흐름에는 함정이 셋 있습니다. 첫째, git stash 는 **추적되지 않는 파일을 기본으로
담지 않습니다**. 새로 만든 파일은 브랜치를 바꿔도 그 자리에 남아 있다가 엉뚱한 브랜치에
섞입니다. 둘째, stash 는 이름이 없는 스택이라 두세 개가 쌓이면 무엇이 무엇인지 알 수
없고, stash pop 이 충돌하면 그 자리에서 복구를 해야 합니다. 셋째, 브랜치를 바꾸는
순간 빌드 산출물과 의존성 디렉터리가 통째로 무효가 됩니다. 큰 프로젝트에서는 이
재빌드가 핫픽스 자체보다 오래 걸립니다.
두 번째 사본을 git clone 으로 만드는 방법도 있습니다. 그러면 위 세 가지는 해결되지만
이력을 통째로 한 벌 더 받아야 하고, 두 사본의 브랜치가 따로 놀기 시작합니다. 한쪽에서
만든 커밋이 다른 쪽에 없어서 결국 둘 사이에 또 push 와 fetch 를 합니다.
git worktree 는 그 가운데를 정확히 메웁니다. 객체 저장소는 하나라 이력을 다시 받지
않고, 디렉터리는 둘이라 빌드 산출물이 섞이지 않습니다.
어떻게 동작하나
git worktree add <경로> <브랜치> 를 하면 두 가지가 생깁니다.
- 새 디렉터리에 그 브랜치가 체크아웃되고, 그 안의
.git은 디렉터리가 아니라 파일 - 원래 저장소의
.git/worktrees/<이름>/아래에 그 작업 사본만의HEAD·index·
입니다. 내용은 gitdir: /원래저장소/.git/worktrees/<이름> 한 줄뿐입니다.
gitdir 같은 관리 파일이 생깁니다.
여기서 중요한 사실이 하나 나옵니다. **HEAD 와 인덱스는 작업 사본마다 따로지만, 브랜치
참조(refs/heads/*)는 저장소 전체가 공유합니다.** 그래서 같은 브랜치를 두 자리에서
체크아웃하려고 하면 git 이 거절합니다.
$ git worktree add /tmp/dup mainfatal: 'main' is already used by worktree at '/home/me/repo'이것은 불친절이 아니라 보호입니다. 브랜치 참조가 하나인데 작업 트리가 둘이면, 한쪽에서
커밋하는 순간 다른 쪽은 자기가 만들지 않은 커밋 위에 앉게 되고 인덱스와 실제 파일이
어긋납니다. 정말 같은 커밋을 두 자리에서 보고 싶다면 --detach 로 브랜치 없이 붙입니다.
정리는 세 가지 명령으로 갈립니다.
| 상황 | 명령 | 하는 일 |
| --- | --- | --- |
| 작업이 끝나 깔끔히 접는다 | git worktree remove <경로> | 디렉터리와 관리 파일을 함께 지운다 |
| 디렉터리를 이미 rm -rf 했다 | git worktree prune | 남은 관리 파일만 지운다 |
| 외장 디스크처럼 잠시 안 보인다 | git worktree lock | prune 이 건드리지 못하게 잠근다 |
rm -rf 로 디렉터리만 지우면 git worktree list 에 그 자리가 prunable 이라고 계속
남습니다. 그 상태에서 같은 이름으로 다시 add 하면 "이미 있다" 는 소리를 듣습니다.
그리고 prune 은 관리 파일만 지웁니다 — 그 자리에서 만들었던 브랜치는 그대로
남으므로, 브랜치까지 정리하려면 git branch -d 를 따로 해야 합니다.
현장에서 만나는 모습
- 릴리스 브랜치를 상시로 하나 붙여 둡니다. 핫픽스 요청이 오면 그 디렉터리로
cd만 - 리뷰할 때 동료의 브랜치를 별도 자리에 붙여 두고, 내 브랜치와 나란히 열어 둡니다.
- 오래 걸리는 테스트를 한 자리에서 돌리는 동안 다른 자리에서 계속 작업합니다.
- CI 러너가 같은 저장소를 여러 브랜치로 동시에 빌드할 때도 clone 대신 worktree 를
하면 되고, 기능 브랜치의 반쯤 고친 파일은 손대지 않습니다.
에디터 창 두 개가 서로 다른 브랜치를 보고 있으면 비교가 훨씬 쉽습니다.
객체 저장소는 공유하지만 인덱스가 따로라 서로 방해하지 않습니다.
씁니다. 디스크와 네트워크를 이력 한 벌로 아낍니다.
주의할 것도 있습니다. 작업 사본 안에서 git gc 가 돌 때 다른 작업 사본이 참조하는
객체를 살려 두려면 git 이 그 자리들을 알아야 하는데, rm -rf 로 지워 놓고 prune 을
안 한 자리가 있으면 그 정보가 낡습니다. 정리를 미루지 않는 편이 안전합니다.
다음 실습에서 할 것
작업 사본 셋을 붙여 놓고, 같은 브랜치를 두 번 열려다 거절당하는 것을 직접 봅니다.
저장하지 않은 변경을 그대로 둔 채 핫픽스를 따로 커밋하고, 디렉터리를 rm -rf 했을 때prunable 이 뜨는 것과 prune 뒤에도 브랜치는 남는 것을 확인합니다. 마지막으로
잠긴 작업 사본이 remove 를 어떻게 거절하는지까지 봅니다.
공식 문서는 [git-worktree](https://git-scm.com/docs/git-worktree) 입니다.