Zoom-in: Git Worktree

git checkout sang một branch khác để sửa lỗi nóng khi thư mục làm việc đang dang dở thường là một trải nghiệm phiền toái. Lựa chọn thường thấy là tạo một commit tạm bợ với message kiểu "temp" hay "wip". Cách khác là dùng git stash để cất đi phần code chưa hoàn thành — nhưng thứ này rất dễ bị bỏ quên hoặc gây xung đột khi áp dụng ngược trở lại.
Có một cách sạch sẽ hơn rất nhiều: tạo một thư mục làm việc mới trỏ cùng vào repository hiện tại bằng git worktree. Cách này đặc biệt hữu ích trong kỷ nguyên các AI coding agent tự trị (như Claude Code, Antigravity) ngày càng tham gia sâu vào workflow hàng ngày. Kỹ sư có thể dễ dàng giao cho agent một task chạy kiểm thử, build code trên một branch riêng trong một worktree độc lập chạy ngầm, trong khi bản thân vẫn tiếp tục làm việc trên thư mục chính mà không sợ bị gián đoạn hay xung đột IDE.
Ví dụ cụ thể: đang code dở tính năng A ở thư mục myapp (nhánh main), nhưng cần Claude Code chạy full test suite cho một pull request khác ở nhánh feature/checkout-flow. Thay vì stash tính năng A đang dở, chỉ cần tạo một worktree riêng cho agent:
git worktree add ../myapp-agent-review feature/checkout-flow
cd ../myapp-agent-review && claude-code run-tests
Agent đọc ghi file thoải mái trong ../myapp-agent-review mà không đụng đến staging area hay HEAD của myapp chính. Khi agent chạy xong, dọn dẹp bằng git worktree remove ../myapp-agent-review.
Phóng to dần vào đó.
Layer 1 — Working tree độc lập và file text .git
Một repository bình thường chỉ có một thư mục làm việc duy nhất liên kết chặt chẽ với cơ sở dữ liệu Git nằm trong thư mục con .git. Để làm việc trên nhiều branch song song mà không làm ảnh hưởng đến thư mục hiện tại, Git cần một cơ chế tách biệt mã nguồn ở một thư mục khác nhưng vẫn giữ được mối liên kết với cơ sở dữ liệu gốc.
Khi chạy lệnh git worktree add ../hotfix-branch hotfix, Git tạo ra một thư mục mới tại ../hotfix-branch và tự động check out branch hotfix vào đó. Thư mục mới này là một working tree độc lập.
Thay vì chứa một thư mục con .git khổng lồ, thư mục mới này chỉ chứa một file text tên là .git. Nội dung file này cực kỳ đơn giản, chỉ có một dòng duy nhất trỏ đến đường dẫn của thư mục metadata thực sự nằm sâu bên trong repository gốc:
gitdir: /path/to/main-repo/.git/worktrees/hotfix-branch
File text .git này đóng vai trò như một liên kết mềm (symlink) đặc biệt của Git, định hướng cho mọi lệnh git chạy bên trong thư mục này biết nơi tìm cơ sở dữ liệu thực sự.
graph LR
WT["📁 Working Tree con\n(hotfix-branch)"] -->|".git file"| M["📂 Main Repo metadata\n(.git/worktrees/hotfix-branch)"]
style WT fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style M fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
Layer 2 — Thư mục metadata riêng trong .git/worktrees/
Mỗi working tree cần phải có staging area (chỉ số index) riêng, branch đang hoạt động (HEAD) riêng, và trạng thái tạm thời của các file (như BISECT_LOG hay rebase state) riêng để tránh gây xung đột trực tiếp với thư mục làm việc chính.
Để giải quyết vấn đề này, trong thư mục .git của repository gốc, Git tự động tạo ra một cấu trúc thư mục con tại .git/worktrees/<name>/.
.git/
└── worktrees/
└── hotfix-branch/
├── HEAD # Trỏ đến branch 'hotfix' cục bộ
├── index # Staging area riêng của worktree con
├── gitdir # Trỏ ngược lại file text .git của worktree con
└── commondir # Trỏ về các folder dùng chung của repo chính
Khi chạy git status hay git add ở thư mục con, Git sẽ đọc và ghi vào file index và HEAD nằm trong .git/worktrees/hotfix-branch/. Do đó, trạng thái staging area của các thư mục hoàn toàn độc lập với nhau. Người dùng có thể thoải mái thêm sửa file ở thư mục con mà không sợ làm bẩn index của thư mục chính.
graph TD
WT_Main["📁 Main Working Tree"] -->|dùng| M_HEAD["HEAD chính"]
WT_Child["📁 Child Working Tree"] -->|dùng| C_HEAD["HEAD phụ\n(.git/worktrees/child/HEAD)"]
style WT_Main fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style WT_Child fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style M_HEAD fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
style C_HEAD fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
Layer 3 — Cơ chế chia sẻ Commondir
Dữ liệu lịch sử commit (objects) và danh sách các branch (refs) là tài sản chung của toàn bộ repository. Nếu mỗi worktree phải tự lưu trữ một bản sao của chúng, việc tạo thêm worktree sẽ rất chậm và tốn dung lượng ổ đĩa tương tự như việc clone một repo mới.
Git giải quyết bài toán này thông qua file commondir nằm trong thư mục metadata riêng của từng worktree. File này chỉ chứa một dòng chữ đơn giản:
../..
Dòng chữ này hướng dẫn Git hiểu rằng, đối với các thư mục dữ liệu dùng chung như objects, refs, info, hooks, và file cấu hình config, nó phải tìm ở thư mục gốc của .git (tức là đi ngược lên 2 tầng thư mục từ .git/worktrees/hotfix-branch/).
Nhờ cơ chế này, bất kỳ commit mới nào được tạo ra ở thư mục con sẽ ngay lập tức được ghi vào cơ sở dữ liệu objects dùng chung của repo chính. Khi quay lại thư mục làm việc chính, commit đó đã có sẵn ở đó mà không cần qua bất cứ bước đồng bộ nào. Đồng thời, Git ngăn cản việc checkout cùng một branch trên hai working tree khác nhau để tránh làm hỏng cấu trúc lịch sử commit.
graph LR
subgraph "Main Repo (.git/)"
CommonDB["🗃️ Common Database\n(objects, refs)"]
end
subgraph "Worktree child metadata"
C_Meta["📁 .git/worktrees/child/"] -->|commondir| CommonDB
end
style CommonDB fill:#1a3a2a,stroke:#22c55e,color:#86efac
style C_Meta fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
Full picture
Dưới đây là sơ đồ tổng quan mô tả cách Git phối hợp các thành phần để duy trì nhiều working tree hoạt động song song trên cùng một cơ sở dữ liệu:
graph TD
subgraph "Working Trees (Thư mục mã nguồn)"
MainWT["📁 Thư mục dự án chính\n(/projects/my-app/)"]
ChildWT["📁 Thư mục Worktree con\n(/projects/my-app-hotfix/)"]
end
subgraph "Main Repository (.git/)"
MainMeta["📂 Thư mục gốc .git/"]
CommonDB["🗃️ Shared Database\n(objects/, refs/, config)"]
subgraph "Worktrees Directory"
ChildMeta["📂 .git/worktrees/my-app-hotfix/"]
ChildHEAD["HEAD cục bộ"]
ChildIndex["index cục bộ"]
end
end
MainWT -->|chứa| MainMeta
ChildWT -->|chứa file text .git| ChildMeta
ChildMeta -->|commondir| CommonDB
MainMeta -->|chứa| CommonDB
ChildMeta -->|chứa| ChildHEAD
ChildMeta -->|chứa| ChildIndex
style MainWT fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style ChildWT fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style MainMeta fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
style ChildMeta fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
style CommonDB fill:#1a3a2a,stroke:#22c55e,color:#86efac
Takeaway
Bản chất của git worktree không phải là một phép thuật phức tạp, mà là một thiết kế tách biệt dữ liệu thông minh (decoupling). Bằng cách phân chia trạng thái làm việc (HEAD, index) thành các phần riêng biệt và giữ chung cơ sở dữ liệu lịch sử (objects, refs) qua cơ chế commondir, Git cho phép làm việc đa nhiệm với hiệu năng tối đa.
Hiểu được cấu trúc này giúp các bạn tự tin hơn khi dọn dẹp các worktree bị hỏng (chỉ cần xóa thư mục làm việc và chạy git worktree prune), hoặc thậm chí tự tay sửa các liên kết trong file .git/worktrees nếu vô tình di chuyển thư mục dự án sang vị trí khác. Nền tảng cấu trúc dữ liệu tốt luôn mang lại sự tự tin khi đối mặt với các tình huống debug phức tạp nhất.
Related Posts
Zoom-in: Git Commit
git commit -m 'fix bug' — một lệnh quen thuộc. Bên dưới là một cấu trúc dữ liệu content-addressed, bất biến, giải thích tại sao rebase, cherry-pick, và merge hoạt động như chúng vốn có.
7 Git Alias Giúp Bạn Tiết Kiệm 1 Giờ Mỗi Ngày
Bạn gõ `git status` rồi `git add .` rồi `git commit` mỗi lần? Có cách nhanh hơn nhiều. Đây là 7 git alias và config được sử dụng hàng ngày để tiết kiệm cả tiếng mỗi ngày.
Zoom-in: Rate Limiter
Gửi quá nhiều request liên tiếp lên API và nhận về mã lỗi 429 Too Many Requests. Người gác cổng Rate Limiter bảo vệ hệ thống như thế nào?