Git을 사용하다 보면 지금 작업을 하던 코드를 멈추고 다른 작업을 해야 하는 경우가 있다. 한창 코드를 작성하던 중에 버그나 장애가 발생해서 핫픽스를 해야 하는 상황도 있고 갑자기 다른 작업을 해야 할 수도 있고 지금 하던 접근방식과 다른 접근 방식을 테스트해보고 싶은 경우도 있다.
이런 경우 현재 작업 중인 워킹 디렉터리(혹은 워킹 트리, 아래에서는 워킹 트리와 워킹 디렉토리를 같은 의미로 사용한다.)에는 아직 작업 중이라 커밋하지 않은 다수의 변경사항이 존재할 수 있다. 이런 경우 git stash를 이용해서 변경사항을 임시 보관해 두고 브랜치를 바꿔서 작업을 할 수 있다. 나는 git stash
도 애용하는 편이지만 stash
가 너무 많아지거나 stash
해놓고 긴 시간이 지나면 오히려 관리하기가 어려워져서 임시 커밋과 섞어서 사용하는 편이다. 간단히 rebase
등을 위해서 잠시 stash
해서 rebase
를 하고 돌아오는 경우가 아니라면 커밋으로 올려두는 편이다. 커밋으로 하는 경우 브랜치에 정확히 묶여있고 커밋 메시지도 있어서 보기가 쉬워서 이쪽을 선호하고 나중에 돌아왔을 때 reset
으로 커밋을 되돌리면 다시 원래 상태의 변경사항 위에서 작업할 수 있다.
Git에 어느 정도 익숙해 진 다음에는 크게 불만도 없기 때문에 새로운 명령어를 찾아보려고 하지 않는 탓에 worktree
명령어는 비교적 최근에 알게 되었다.
$ git worktree
사용법
git-worktree
는 다수의 워킹 디렉터리(혹은 워킹 트리)를 관리할 수 있게 해주는 명령어다.
Git 저장소에서 새로운 워킹 트리를 만들려면 git worktree add <path>
를 사용하고 이때 <path>
의 마지막 부분이 브랜치 명이 된다.
~/demo-worktree on main@b5e68abe$ git status -s
M README.md
?? new-file
데모를 위해 간단한 Git 저장소를 만들었다. 저장소 내용은 중요하지 않고 수정한 파일과 새로운 파일을 하나 추가해 두었다. ~/demo-worktree on main@b5e68abe
부분은 현재 디렉터리와 브랜치, 현재 커밋을 의미한다.
이제 여기서 git worktree add
로 새로운 워킹 트리를 추가해보자.
~/demo-worktree on main@b5e68abe$ git worktree add ../new-worktree
작업 트리 준비 중 (새 브랜치 'new-worktree')
HEAD의 현재 위치는 b5e68ab입니다 fix typo
~/demo-worktree on main@b5e68abe$ git status -s
M README.md
?? new-file
git worktree add
에 경로를 ../new-worktree
로 주었으므로 new-worktree
라는 브랜치가 생겼다. 마지막을 보면 알겠지만 새로운 워킹 트리와 브랜치가 생겼지만, 현재 워킹 트리는 그대로 main
에 있고 워킹 트리에 있던 변경사항도 달라진 것이 없다.
~/demo-worktree on main@b5e68abe$ git branch
fix-bug
* main
+ new-worktree
브랜치를 살펴보면 new-worktree
브랜치가 생긴 걸 확인할 수 있고 현재 브랜치를 의미하는 *
외에 워킹 트리로 new-worktree
브랜치에는 +
표시가 있는 걸 알 수 있다. 이는 해당 브랜치가 새로운 워킹 트리와 연결되었음을 의미한다.
상위 디렉터리에 가면 원래의 demo-worktree
옆에 new-worktree
디렉터리가 생긴 걸 알 수 있다. 앞에서 ../new-worktree
로 경로를 지정했으므로 그 위치에 디렉터리가 만들어지면서 워킹 트리가 생긴 것이다.
../
├── demo-worktree
└── new-worktree
이제 새로 만든 new-worktree
에 가보면 new-worktree
브랜치를 보고 있고 변경사항은 없는 것을 알 수 있다.(Git에 포함된 파일은 전부 있다.)
~/new-worktree on new-worktree@b5e68abe$ git status -s
일어나는 과정을 간단히 설명하면 현재 디렉터리에서 하던 작업이 있어서 new-worktree
라는 디렉터리에서 새로 저장소를 클론 받은 뒤에 new-worktree
브랜치를 만들어서 체크아웃한 것과 같은 결과이다. 그래서 새로 만든 워킹 트리에는(위에서 new-worktree
디렉터리) .gitignore
된 파일은 존재하지 않는다. 예를 들어 빌드 결과 파일이라거나 Node.js의 node_modules
파일은 이쪽에는 존재하지 않으므로 다시 설치해 주어야 한다. Git에 추가하지 않은 파일이므로 워킹 트리에는 복사되지 않는다.
직접 클론해서 만드는 거와의 차이점이라면 편리하다는 점과 서로 연결되어 있다는 점이다.(위에서 +
표시)
~/new-worktree on new-worktree@994a43b7$ git branch
fix-bug
+ main
* new-worktree
new-worktree
디렉터리에서 브랜치를 확인해 보면 이번엔 반대로 main
저장소가 워킹 트리로 연결된 것을 알 수 있다.(Git의 분산 저장소 특성을 생각하면 당연하다고 생각한다.)
* b5e68ab (HEAD -> main, new-worktree, fix-bug) fix typo— Outsider
* ec2eb93 test commit— Outsider
* 2d408f1 initial commit— Outsider
워킹 트리를 만들었을 때는 위처럼 되어있던 히스토리가 new-worktree
워킹 트리에서 추가 커밋을 올리면 어느 쪽에서 히스토리를 조회하던 아래쪽으로 바뀐다. 두 워킹 트리는 디렉터리는 분리되어 있지만 연결되어 있기 때문에 커밋을 추가하거나 브랜치를 추가하더라도 양쪽 모두에서 볼 수 있다. 실제 클론을 별로로 한 것처럼 분리된 것이 아니라 연결되어 있다는 의미이다.
* 7305910 (new-worktree) commit from new working-tree— Outsider
* b5e68ab (HEAD -> main, fix-bug) fix typo— Outsider
* ec2eb93 test commit— Outsider
* 2d408f1 initial commit— Outsider
현재 작업 중인 워킹 트리를 그대로 둔 채 다른 브랜치로 작업을 할 때 아주 유용한 기능이고 연결되어 있기 때문에 필요에 따라서는 임시가 아니라 계속 사용하는 것도 가능하다. 이런 경우 외에도 나 같은 경우는 Git에서 관리되지 않는 의존성에 대해 테스트를 할 때도 유용하게 쓰고 있다. 예를 들어 특정 의존성 모듈을 버전업하는 작업을 하는 경우 의존성 모듈 자체는 Git으로 관리하지 않으므로 작업 중에 다른 작업을 하거나 Pull Request를 리뷰해야 할 때 의존성도 재설치해야 하는 불편함이 있는데 이럴 때 워킹 트리를 추가로 만들어서 관리하면 편하다.
$ git worktree list
/Users/outsider/demo-worktree b5e68ab [main]
/Users/outsider/new-worktree 7305910 [new-worktree]
워킹 트리 목록은 git worktree list
명령어로 볼 수 있고 더는 필요 없는 워킹 트리는 git worktree remove <worktree>
로 제거할 수 있다.
$ git worktree remove new-worktree
이때 위에서 만들어진 new-worktree
디렉터리는 지워지지만 new-worktree
브랜치는 사라지지 않는다.(그래서 커밋을 했다면 워킹트리를 지워도 히스토리에 남는다.)
앞에서 워킹 트리를 추가할 때 git worktree add <path>
명령어를 사용했고 이때 브랜치 명이 <path>
의 마지막 부분으로 자동으로 만들어졌는데 디렉터리는 만들면서 이미 있는 브랜치나 커밋과 연결하고 싶다면 git worktree add <path> <branch|commit>
로 지정할 수 있고 새로운 브랜치 명을 지정하고 싶다면 git worktree add -b <new-branch> <path>
와 같이 사용하면 된다.
Comments