0

Git Worktree vào hệ sinh thái Microservices Bài 2: Giải phẫu Git Worktree: Một thư mục .git, đa không gian làm việc

Để sử dụng một công cụ một cách làm chủ hoàn toàn trong hệ thống lớn, chúng ta không chỉ học thuộc câu lệnh mà cần hiểu rõ cơ chế hoạt động bên dưới (Under the hood). Tại sao Git Worktree có thể tạo ra một thư mục code mới chỉ trong tích tắc mà không tốn thêm dung lượng lưu trữ lịch sử commit? Câu trả lời nằm ở kiến trúc của thư mục .git.

1. Ôn lại cấu trúc của một Git Repository truyền thống

Khi bạn chạy lệnh git clone hoặc git init cho một Microservice (ví dụ payment-service), Git sẽ tạo ra một thư mục ẩn .git nằm ngay bên trong thư mục dự án. Lúc này, dự án của bạn gồm 2 thành phần gắn chặt với nhau:

  1. Working Tree (Không gian làm việc): Chính là các thư mục app/, src/, config/... nơi bạn trực tiếp mở IDE lên để đọc và sửa code.

  2. Git Directory (Thư mục .git): Là "bộ não" và "nhà kho" lưu trữ toàn bộ lịch sử dự án.

Bên trong thư mục .git truyền thống có hai nhóm dữ liệu cực kỳ quan trọng mà bạn cần phân biệt:

Plaintext

payment-service/.git/
├── objects/       <-- [NẶNG] Kho chứa toàn bộ lịch sử code, các bản ghi commit (nặng hàng trăm MB - vài GB)
├── refs/          <-- [NHẸ] Danh sách các con trỏ nhánh (branches) và tags
├── config         <-- [NHẸ] Cấu hình remote (origin), user...
│
├── HEAD           <-- [TRẠNG THÁI RIÊNG] Đang trỏ tới nhánh nào? (VD: ref: refs/heads/feature/loyalty-v2)
└── index          <-- [TRẠNG THÁI RIÊNG] Staging Area - Những file nào đang được `git add`?

Điểm mấu chốt nằm ở đây: Trong mô hình truyền thống, mỗi thư mục .git chỉ có duy nhất 1 file HEAD và 1 file index. Đó là lý do tại một thời điểm, thư mục payment-service chỉ có thể đứng ở một nhánh duy nhất. Muốn sang nhánh khác, bạn buộc phải ghi đè lên HEAD và index hiện tại (thông qua git checkout).

2. Cơ chế của Git Worktree: Tách "Trạng thái riêng" khỏi "Nhà kho chung"

Các kỹ sư thiết kế Git đã đặt ra câu hỏi: "Tại sao phải nhân bản cả kho objects/ nặng hàng Gigabyte chỉ để có thêm một cặp file HEAD và index mới?"

Từ phiên bản Git 2.5, tính năng Multiple Worktrees ra đời với cơ chế cực kỳ thông minh: Giữ nguyên "nhà kho" (objects, refs, config) ở thư mục gốc, và chỉ tạo thêm các bộ (HEAD, index) mới cho từng không gian làm việc phụ.

Khi bạn đứng ở thư mục gốc payment-service và tạo thêm một worktree mới tên là payment-hotfix, cấu trúc trên ổ cứng sẽ biến đổi như sau:

Plaintext

workspace/
│
├── payment-service/                    <-- [MAIN WORKTREE] (Đang ở nhánh feature/loyalty-v2)
│   ├── src/
│   └── .git/                           <-- Thư mục .git thật sự (Chứa toàn bộ dữ liệu)
│       ├── objects/                    (Dùng chung cho mọi worktree)
│       ├── refs/                       (Dùng chung cho mọi worktree)
│       ├── HEAD                        (Trỏ tới: feature/loyalty-v2)
│       ├── index                       (Staging của feature/loyalty-v2)
│       └── worktrees/                  <-- [MỚI] Nơi quản lý các worktree phụ!
│           └── payment-hotfix/
│               ├── HEAD                (Trỏ tới: hotfix/payment-stuck)
│               ├── index               (Staging riêng của hotfix/payment-stuck)
│               └── gitdir              (Lưu đường dẫn ngược về thư mục payment-hotfix)
│
└── payment-hotfix/                     <-- [LINKED WORKTREE] (Đang ở nhánh hotfix/payment-stuck)
    ├── src/
    └── .git                            <-- CHÚ Ý: Đây là 1 FILE TEXT (không phải thư mục!)

3. Bất ngờ lớn nhất: .git trong Worktree phụ chỉ là một File Text!

Nếu bạn đi vào thư mục payment-hotfix vừa tạo bằng Worktree và kiểm tra dung lượng của .git, bạn sẽ thấy nó chỉ nặng đúng... vài chục Bytes!

Bởi vì trong các Worktree phụ (Linked Worktrees), .git không phải là một thư mục, mà chỉ là một file văn bản thuần túy đóng vai trò như một đường dẫn chỉ đường (pointer). Nếu bạn mở file .git này ra xem bằng lệnh cat .git, bạn sẽ thấy nội dung duy nhất bên trong là:

Plaintext

gitdir: /Users/username/workspace/payment-service/.git/worktrees/payment-hotfix

Luồng hoạt động khi bạn gõ lệnh Git ở thư mục payment-hotfix:

  1. Bạn gõ git status hoặc git commit bên trong payment-hotfix.

  2. Git đọc file .git (nặng vài chục bytes) để biết trạng thái HEAD và index của nhánh hotfix này đang nằm ở .../payment-service/.git/worktrees/payment-hotfix.

  3. Khi cần lưu commit mới hoặc tra cứu lịch sử code, Git tự động đi ngược lên một cấp để truy cập vào kho objects/ và refs/ chung của payment-service/.git.

4. Ba lợi ích "vàng" từ kiến trúc này đối với hệ thống Microservices

Nhờ cơ chế "Một nhà kho, nhiều con trỏ trạng thái", việc vận hành hàng chục Microservices ở local nhận được 3 lợi ích trực tiếp:

  1. Chia sẻ tức thì mọi Commit và Branch (Shared Object Database):

    • Giả sử bạn đang đứng ở worktree payment-hotfix, bạn vừa sửa xong bug và tạo một commit mới, hoặc bạn gõ git fetch origin.

    • Ngay lập tức, ở cửa sổ VS Code bên kia đang mở payment-service (nhánh feature), bạn đã có thể nhìn thấy commit hotfix đó để cherry-pick hoặc rebase vào nhánh feature mà không cần phải push lên mạng rồi pull về lại. Mọi thứ nằm chung trong một kho objects/ dưới ổ cứng của bạn!

  2. Tiết kiệm 99% dung lượng Git và tốc độ khởi tạo cực nhanh:

    • Vì không phải tải lại dữ liệu từ Github/Gitlab/Bitbucket và không phải copy thư mục .git/objects, việc tạo một worktree mới chỉ tốn thời gian hệ điều hành copy các file source code (thường mất 0.5 đến 2 giây trên ổ cứng SSD NVMe).
  3. Cơ chế khóa nhánh an toàn (Branch Checkout Lock):

    • Một câu hỏi rất hay gặp: "Điều gì xảy ra nếu cả 2 worktree cùng checkout vào chung nhánh main và cùng sửa code?"

    • Nếu điều đó xảy ra, 2 thư mục sẽ làm loạn lịch sử của cùng một nhánh. Vì vậy, Git thiết kế một hàng rào bảo vệ sẵn ở tầng .git/worktrees: Một nhánh chỉ được phép mở (checkout) trên tối đa 1 Worktree tại một thời điểm. Nếu nhánh feature/loyalty-v2 đang mở ở worktree A, và bạn cố tình gõ git checkout feature/loyalty-v2 ở worktree B, Git sẽ chặn lại ngay lập tức với thông báo lỗi fatal: 'feature/loyalty-v2' is already checked out at .... Điều này giúp bạn không bao giờ sợ sửa nhầm hoặc ghi đè code của chính mình.

Tóm tắt Bài 2 & Chuẩn bị cho Bài 3

Hiểu đơn giản, Git Worktree biến thư mục .git gốc thành một Database trung tâm, còn mỗi thư mục Worktree bạn tạo ra giống như một Client kết nối vào Database đó với phiên làm việc (HEAD, index) hoàn toàn độc lập.

Khi đã nắm vững bản chất bên dưới lớp vỏ này, ở Bài 3: Các lệnh Git Worktree cốt lõi dành cho Backend Developer, chúng ta sẽ bắt tay vào thực hành trực tiếp trên Terminal bộ lệnh quản lý vòng đời của Worktree (tạo mới, liệt kê, khóa bảo vệ và dọn dẹp sạch sẽ sau khi merge PR).


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí