Todo 앱의 feature/todo-filter 브랜치에서 필터 기능을 절반쯤 작업하던 중, 운영 빌드에서 크래시 리포트가 올라왔습니다. 급한 핫픽스가 필요했는데, 하던 작업을 커밋하기엔 애매한 상태였습니다. 이때 브랜치를 어떻게 오갈지 고민하다가 git worktree를 쓰게 된 과정을 정리합니다.
문제 상황
원래 하던 방식은 단순했습니다.
git stash
git checkout main
git checkout -b hotfix/fix-crash
작업 중이던 변경사항을 스태시에 넣고 브랜치를 옮기는 방식입니다. 그런데 이 과정에서 두 가지가 반복적으로 걸렸습니다.
첫째, 브랜치를 전환할 때마다 Android Studio가 프로젝트를 다시 인덱싱하고 Gradle 싱크를 새로 돌렸습니다. commonMain/androidMain/iosMain 구조라 소스셋이 많다 보니 싱크에 1~2분씩 걸렸습니다.
둘째, 핫픽스 작업 중 필터 기능 관련 파일을 하나 건드리게 됐는데, 원래 스태시에 있던 변경사항과 겹치는 부분이 있어서 나중에 stash pop할 때 이런 에러를 만났습니다.
$ git stash pop
Auto-merging composeApp/src/commonMain/kotlin/todo/TodoViewModel.kt
CONFLICT (content): Merge conflict in composeApp/src/commonMain/kotlin/todo/TodoViewModel.kt
핫픽스 커밋과 스태시가 같은 파일을 건드리면서 병합 충돌이 난 겁니다. 스태시는 원래 "잠깐 치워두는" 용도인데, 그 사이에 같은 작업 디렉토리에서 다른 브랜치 작업을 하면 언제든 이런 충돌이 날 수 있다는 걸 알게 됐습니다.
원인 분석
근본 원인은 하나의 작업 디렉토리(worktree)에 브랜치를 하나씩만 체크아웃할 수 있다는 git의 기본 구조입니다. 브랜치를 바꾼다는 건 파일 시스템 전체를 그 브랜치 상태로 덮어쓴다는 뜻이고, 그러면
- IDE 입장에서는 프로젝트가 통째로 바뀐 것과 같아서 재인덱싱이 발생하고
- 진행 중이던 변경사항은 스태시나 별도 커밋으로 밀어두는 수밖에 없어서 관리 부담이 생깁니다
두 브랜치를 동시에 "열어두고" 오가려면 작업 디렉토리 자체를 분리해야 한다는 결론이 나왔습니다.
해결 과정
git worktree는 하나의 저장소(.git)를 공유하면서 브랜치별로 별도의 작업 디렉토리를 만들어주는 기능입니다.
$ git worktree add ../todo-hotfix hotfix/fix-crash
Preparing worktree (new branch 'hotfix/fix-crash')
HEAD is now at 8f3a1c2 릴리즈 1.4.0
이렇게 하면 원래 프로젝트 폴더(todo/)는 feature/todo-filter 상태 그대로 두고, todo-hotfix/라는 새 디렉토리에 hotfix/fix-crash 브랜치가 체크아웃됩니다. 두 디렉토리 모두 같은 .git 히스토리를 공유하지만 워킹 디렉토리는 완전히 분리됩니다.
Android Studio로 todo-hotfix/ 디렉토리를 새 창으로 열면 별도 프로젝트처럼 인식합니다. ~/.gradle 캐시는 공유되기 때문에 의존성을 다시 받지는 않지만, 프로젝트별 빌드 캐시(build/, .gradle/)는 새로 생성되므로 첫 빌드는 다소 걸립니다.
$ cd ../todo-hotfix
$ git status
On branch hotfix/fix-crash
nothing to commit, working tree clean
여기서 크래시를 고치고 커밋, 푸시까지 마친 뒤 원래 작업하던 todo/ 디렉토리로 돌아가면 feature/todo-filter 브랜치와 스태시 상태가 그대로 남아 있습니다. 스태시를 건드릴 필요가 없어졌습니다.
핫픽스가 끝나면 worktree를 정리합니다.
$ git worktree remove ../todo-hotfix
브랜치가 이미 다른 worktree에서 체크아웃된 상태로 또 체크아웃을 시도하면 이런 에러가 납니다.
$ git checkout hotfix/fix-crash
fatal: 'hotfix/fix-crash' is already used by worktree at '/Users/dev/todo-hotfix'
같은 브랜치를 두 워킹 디렉토리에서 동시에 열 수는 없다는 뜻입니다. worktree를 먼저 지우거나(git worktree remove), 다른 브랜치명을 써야 합니다.
결론
브랜치를 자주 오가야 하는 상황에서는 git stash + checkout보다 git worktree가 더 안전합니다. 진행 중인 변경사항을 건드리지 않고도 다른 브랜치 작업을 병행할 수 있고, IDE 재인덱싱 비용도 워크트리별로 한 번만 치르면 됩니다. 다만 워크트리마다 빌드 캐시가 따로 생기기 때문에 디스크 용량은 그만큼 더 씁니다. 급한 핫픽스나 리뷰 대응처럼 "지금 하던 작업은 그대로 두고 잠깐 다른 브랜치를 봐야 하는" 상황에서는 꽤 유용합니다.