Chuyện thường ngày: bạn đang code dở feature/cart-refactor, webpack dev server chạy ngon lành, vài file đang sửa nửa chừng. Rồi Slack hiện lên: “Prod lỗi 500 ở /api/login, fix gấp nhé.” Bạn thở dài, git stash, git checkout main, chờ npm install chạy lại, node_modules rebuild… mất 5 phút trước khi bắt đầu nhìn bug. Fix xong, quay lại feature branch, git stash pop, conflict. Một buổi sáng bay theo gió.

Nhiều người làm điều đó vài lần một tuần cho đến khi phát hiện git worktree, một tính năng có sẵn trong Git mà không hiểu sao ít người dùng. Nó cho phép bạn mở nhiều nhánh cùng lúc, mỗi nhánh một thư mục riêng, mà không cần clone thêm repo. Nghe đơn giản, nhưng ảnh hưởng đến workflow hằng ngày nhiều hơn bạn nghĩ.


Worktree là gì và tại sao nó khác clone

Khi bạn git clone, Git tạo cho bạn một working tree, thư mục chứa file mã nguồn tương ứng với nhánh đang checkout. Phía sau là thư mục .git/ chứa toàn bộ object database (blob, tree, commit), refs, config. Mối quan hệ là 1:1, một .git/, một thư mục code.

git worktree phá vỡ mối quan hệ 1:1 đó. Nó cho phép bạn tạo nhiều working tree cùng chia sẻ một object database. Nghĩa là bạn có thể có feature/cart ở thư mục ~/work/proj, đồng thời main~/work/proj-hotfix, và staging~/work/proj-staging, tất cả dùng chung lịch sử commit, remote, stash. Không phải ba bản clone tách biệt. Fetch một lần ở bất kỳ worktree nào thì cả ba đều thấy commit mới.

Worktree đầu tiên (thư mục chứa .git/) gọi là primary, các worktree thêm vào gọi là linked worktree. Mỗi linked worktree có metadata riêng trong .git/worktrees/<name>/, nhưng object database vẫn chung.

Có một luật quan trọng: một nhánh local chỉ được checkout ở đúng một worktree tại một thời điểm. Git chặn để tránh hai nơi cùng sửa working copy của một nhánh. Và khi xoá thư mục worktree bằng rm -rf thay vì git worktree remove, metadata “ma” vẫn còn, bạn cần chạy git worktree prune để dọn.

So sánh nhanh ba cách tiếp cận:

CáchƯuNhược
git stash + git checkoutKhông cần thư mục khácMất context, build cache reset, dễ conflict khi pop
Clone nhiều lầnĐộc lập hoàn toànTốn gấp đôi disk, fetch nhiều lần, refs không chia sẻ
git worktreeShare object DB, nhiều nhánh sống cùng lúcCần kỷ luật đặt thư mục, vài cạm bẫy nhỏ

Các lệnh thường dùng

Liệt kê worktree hiện có:

git worktree list
# /home/me/proj            abc1234 [feature/api]
# /home/me/proj-hotfix     def5678 [main]
# /home/me/proj-review     9876abc (detached HEAD)

Thêm worktree cho nhánh có sẵn, Git tạo thư mục mới và checkout nhánh đó vào:

git worktree add ../proj-hotfix main

Tạo nhánh mới đồng thời với worktree, rất tiện khi cần hotfix branch từ main:

git worktree add -b fix/login-500 ../proj-fix main

Khi muốn xem lại một tag cũ mà không ảnh hưởng gì, dùng detached mode:

git worktree add --detach ../proj-v1.8.0 v1.8.0

Xoá worktree đúng cách (không phải rm -rf):

git worktree remove ../proj-hotfix
# Nếu còn uncommitted changes, Git báo lỗi, thêm --force nếu bạn chắc chắn

Và nếu lỡ xoá thư mục bằng tay, chạy prune để dọn metadata mồ côi:

git worktree prune

prune an toàn, chỉ xoá entry trỏ tới thư mục không còn tồn tại, có thể chạy định kỳ hoặc kèm theo git gc.


Đặt thư mục thế nào cho gọn

Sau vài tháng dùng worktree, cách đặt thư mục ảnh hưởng nhiều đến trải nghiệm. Pattern thoải mái nhất là sibling, các worktree nằm cạnh nhau cùng cấp:

~/work/
├── proj/              ← worktree chính (feature đang làm)
├── proj-hotfix/       ← checkout main để fix nhanh
├── proj-review/       ← dùng review PR
└── proj-benchmark/    ← chạy benchmark bản cũ

Mỗi thư mục là một cửa sổ IDE riêng. cd ../proj-hotfix là đủ. Nếu bạn làm nhiều repo cùng lúc, có thể gom thêm một cấp: ~/work/proj/worktrees/hotfix/, nhưng pattern này ít cần thiết trừ khi bạn có hơn 5 repo active.

Một pattern khác là đặt worktree bên trong repo chính (ví dụ proj/worktrees/hotfix/). Nghe gọn nhưng trên thực tế hay gặp rắc rối: linter, formatter, hay Docker build context sẽ quét đệ quy vào thư mục con và xử lý file hai lần. Không khuyến nghị cách này.

Về tên thư mục, nên đặt theo mục đích thay vì tên nhánh. proj-hotfix hay hơn proj-fix-login-500-abc, vì bạn có thể checkout nhánh khác trong worktree đó sau này, metadata worktree không gắn chặt với branch name. Worktree proj-review có thể dùng đi dùng lại cho nhiều PR khác nhau, chỉ cần git checkout sang nhánh PR mới.


Kịch bản thực tế thường gặp

Hotfix trong khi đang dở feature

Đây là tình huống mở đầu bài viết. Thay vì stash rồi checkout, tạo worktree mới từ main:

cd ~/work/proj
git worktree add -b hotfix/login-500 ../proj-hotfix origin/main
cd ../proj-hotfix
# sửa bug, chạy test, tạo PR, merge
cd ~/work/proj   # quay lại, build cache, node_modules, dev server vẫn nguyên

Điều giá trị nhất ở đây không phải tiết kiệm vài lệnh git, mà là build cache và state của dev server không bị reset. Với mono-repo lớn dùng Gradle hay webpack, mỗi lần checkout branch khác có thể phải rebuild 2-3 phút. Worktree giúp bạn bỏ qua toàn bộ chi phí đó.

Review PR mà không phá môi trường hiện tại

Khi cần review kỹ một PR (chạy test, debug thử), fetch branch rồi tạo worktree riêng:

git fetch origin pull/1234/head:pr-1234
git worktree add ../proj-pr-1234 pr-1234
cd ../proj-pr-1234
npm install && npm test

Review xong thì dọn sạch:

cd ~/work/proj
git worktree remove ../proj-pr-1234
git branch -D pr-1234

Cách này đặc biệt hữu ích khi PR đổi dependencies hoặc config, bạn không muốn npm install của PR đè lên node_modules đang chạy ổn của feature branch.

So sánh hiệu năng giữa hai branch

Đôi khi cần benchmark trước/sau khi optimize. Với worktree, bạn có thể chạy cả hai phiên bản song song trên hai port khác nhau, giảm biến thiên do thời gian giữa hai lần đo. Hoặc ít nhất, cả hai thư mục đều có warm cache sẵn.

Bisect mà không mất context

git bisect thay đổi HEAD liên tục khi nhị phân tìm commit gây lỗi. Nếu đang dở thay đổi lớn, tạo worktree phụ để bisect:

git worktree add ../proj-bisect main
cd ../proj-bisect
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
# lặp cho đến khi tìm ra commit xấu
git bisect reset

Worktree chính hoàn toàn không bị đụng vào.


Worktree sống chung với IDE, CI và submodule

IDE và editor

VSCode, JetBrains đều coi mỗi worktree như một repo riêng khi bạn mở thư mục đó, Git panel, commit, branch list hoạt động bình thường. Có điều, một số thông tin chia sẻ giữa các worktree (như refs mới sau khi fetch) cần IDE refresh mới thấy. Và tuyệt đối không mở cùng một thư mục worktree trong hai cửa sổ IDE, lock file sẽ conflict.

Một tip nhỏ: trong VSCode, nên lưu mỗi worktree thành một workspace riêng (.code-workspace), đặt ở thư mục sibling. Mở workspace nào là nhảy sang ngữ cảnh đó.

CI cục bộ

Nếu bạn chạy CI local bằng act hay container runner, mỗi worktree nên có thư mục cache riêng (.ci/ hay .cache/) để tránh đè nhau. Cũng đừng symlink node_modules giữa hai worktree, race condition khi cài package là chuyện có thật, và debug mất thời gian hơn cài lại từ đầu.

Submodule

Worktree phụ có working copy submodule riêng, nhưng object của submodule vẫn nằm trong primary (.git/modules/ chung). Khi tạo worktree mới mà repo dùng submodule, bạn cần:

cd ../proj-hotfix
git submodule update --init --recursive

Cẩn thận với git submodule deinit, chạy ở một worktree có thể ảnh hưởng các worktree khác nếu config share. Nếu dự án phụ thuộc nhiều submodule, nên đọc kỹ tài liệu về mối quan hệ giữa submodule và worktree trước khi dùng.

Hooks

.git/hooks/ nằm ở primary worktree. Tất cả linked worktree dùng chung hooks (trừ khi bạn đặt core.hooksPath khác cho từng worktree). Thường thì đây là điều tốt, pre-commit, pre-push chạy đồng nhất ở mọi worktree.

Rebase, merge, pull

Các thao tác này chạy trên worktree hiện tại và không ảnh hưởng nhánh đang checkout ở worktree khác, miễn bạn giữ luật “một nhánh một worktree”. Tuy nhiên, git push thì luôn push ref chứ không quan tâm worktree nào, có thể push nhánh đang checkout ở worktree khác. Cẩn thận với push --force.


Những rắc rối hay gặp

“fatal: … is already checked out at …”

Lỗi này xuất hiện khi bạn thử tạo worktree cho nhánh đang được checkout ở nơi khác. Giải pháp đơn giản nhất là tạo nhánh tạm:

git worktree add -b feature/foo-review ../proj-x feature/foo

Hoặc giải phóng worktree kia bằng cách commit/stash rồi checkout sang nhánh khác, hoặc remove nó.

Worktree “ma” sau khi xoá thư mục bằng tay

Nếu bạn lỡ rm -rf ../proj-hotfix thay vì dùng git worktree remove, Git vẫn nghĩ worktree đó tồn tại:

git worktree list
# /home/me/proj-hotfix  (prunable)
git worktree prune     # dọn sạch

Lỗi này hay gặp trong thời gian đầu dùng worktree. Nên tập thói quen: luôn dùng git worktree remove, coi như rm -rf worktree là cấm.

Branch xoá rồi mà ref vẫn còn

git worktree remove xoá worktree nhưng không tự xoá branch. Nếu muốn dọn sạch:

git worktree remove ../proj-fix
git branch -d fix/login-500   # -D nếu branch chưa merge

Bare repo + worktree, pattern nâng cao

Một cách dùng khá hay: clone bare repo làm “gốc refs”, rồi mỗi nhánh là một worktree:

git clone --bare git@github.com:org/proj.git ~/work/proj.git
cd ~/work/proj.git
git worktree add ../proj-main main
git worktree add ../proj-develop develop

Pattern này gọn khi bạn cần nhiều nhánh luôn sẵn sàng, ví dụ maintain nhiều phiên bản song song. Nhưng ban đầu dễ nhầm lẫn vì thư mục primary chỉ chứa bare git objects, không có file mã nguồn nào.

Worktree trên filesystem khác

Git vẫn hoạt động nếu worktree nằm trên disk khác (ổ ngoài, mount point), nhưng một số thao tác dựa vào hardlink cho pack files sẽ fall back sang copy, performance thấp hơn. Với SSD nội bộ thì không phải lo, nhưng nếu đặt trên NFS hay network drive, bạn sẽ thấy fetch/checkout chậm đáng kể.


Khi nào không nên dùng worktree

Worktree không phải lúc nào cũng là lựa chọn tốt. Nếu dự án nhỏ và bạn hiếm khi phải nhảy nhánh, stash + checkout hoàn toàn đủ, thêm worktree chỉ tạo loạn thư mục. Nếu bạn chưa quen Git lắm, worktree thêm một tầng mental model mà chưa cần thiết, nắm vững checkout, branch, stash trước đã.

Trong môi trường CI hoặc remote dev container, mỗi task thường chạy trong container riêng rồi, worktree không mang lại giá trị gì thêm. Và nếu repo có tooling cứng đường dẫn tuyệt đối trong script build, worktree phụ ở thư mục khác có thể phá vỡ assumption đó.


Tóm tắt

Worktree giải quyết một vấn đề cụ thể: giảm chi phí context switch khi bạn cần làm việc trên nhiều nhánh song song. Không phải clone thêm repo, không phải stash/checkout liên tục, không mất build cache. Bạn có nhiều thư mục, mỗi thư mục một nhánh, tất cả chia sẻ chung lịch sử Git.

Vài điều cần nhớ: mỗi nhánh chỉ checkout ở một worktree tại một thời điểm; xoá worktree bằng git worktree remove chứ không rm -rf; đặt thư mục sibling với tên mô tả mục đích thay vì tên nhánh; và chạy git worktree prune khi nghi có metadata mồ côi.

Trong một tuần làm việc bình thường, worktree tiết kiệm vài chục phút context switch, tính ra cả tháng cũng kha khá. Quan trọng hơn, nó giữ được flow: không phải dừng dev server, không phải rebuild, không phải nhớ stash ở đâu. Một khi đã quen, rất khó quay lại cách cũ.


Tham khảo