Tuyệt vời! Đây là một mô phỏng rất thực tế. Dưới đây là các kịch bản làm việc chi tiết với Git & GitHub, bao gồm cả luồng công việc lý tưởng và các xung đột có thể xảy ra cho từng vai trò trong đội của bạn. --- ### **Phần 1: Thiết lập ban đầu & Quy tắc chung** Trước khi bắt đầu, để dự án chạy trơn tru, MrPm (với sự tư vấn của MrDev kinh nghiệm nhất) sẽ thiết lập nền tảng: 1. **Tạo Repository:** MrPm tạo một repository mới trên GitHub, ví dụ: `my-company-website`. 2. **Chiến lược phân nhánh (Branching Strategy):** Công ty sẽ áp dụng một phiên bản đơn giản của **GitFlow**. * `main`: Nhánh chính, chứa code đã được kiểm thử và sẵn sàng để triển khai (deploy) lên production. Nhánh này được **bảo vệ (protected)**, chỉ MrPm mới có quyền merge vào. * `develop`: Nhánh phát triển. Tất cả các tính năng sau khi hoàn thành sẽ được hợp nhất (merge) vào đây. Đây là nhánh "sống" của đội dev. Nhánh này cũng nên được bảo vệ, cấm push trực tiếp. * `feature/ten-tinh-nang`: Mỗi tính năng mới sẽ được phát triển trên một nhánh riêng, tách ra từ `develop`. Ví dụ: `feature/user-login`, `feature/payment-gateway`. * `hotfix/ten-loi-khan-cap`: Dùng để sửa các lỗi nghiêm trọng trên `main`. Tách ra từ `main` và sau đó merge lại vào cả `main` và `develop`. 3. **Quy tắc làm việc:** * **Không bao giờ commit trực tiếp lên `main` hoặc `develop`**. * Mọi thay đổi phải được thực hiện thông qua **Pull Request (PR)**. * Mỗi PR cần ít nhất **1 người review** (ví dụ: Dev này review cho Dev kia). * Commit message phải rõ ràng, theo một quy chuẩn (ví dụ: `feat: add login button`, `fix: correct typo in footer`). * Luôn `git pull` để cập nhật nhánh `develop` trước khi tạo nhánh `feature` mới. --- ### **Phần 2: Vai trò và luồng công việc của mỗi thành viên** #### **1. MrCEO (Giám đốc điều hành)** * **Vai trò trên GitHub:** Người quan sát. Ông không code, không commit. * **Luồng công việc:** * Xem tiến độ dự án qua **Projects (Kanban Board)** trên GitHub mà MrPm đã tạo. * Theo dõi các **Milestones** (cột mốc) như "Hoàn thành Giai đoạn 1". * Đọc mô tả của các Pull Request lớn để hiểu tính năng mới là gì, nhưng không cần đọc code. * **Không có quyền ghi (write access)** vào repository để tránh tai nạn. #### **2. MrPm (Quản lý dự án)** * **Vai trò trên GitHub:** Người điều phối. * **Luồng công việc:** * Tạo và quản lý các **Issues** (tác vụ) trên GitHub. Gán task cho MrDev1, MrDev2... * Review các Pull Request về mặt logic, đảm bảo tính năng đúng với yêu cầu. * Là người cuối cùng bấm nút **Merge Pull Request** vào nhánh `develop`. * Khi đến kỳ release, tạo một PR từ `develop` vào `main`, review lần cuối và merge. * Thực hiện deploy từ nhánh `main`. #### **3. MrDev (Lập trình viên)** * **Vai trò trên GitHub:** Người thực thi chính. * **Luồng công việc ("Happy Path" - Kịch bản lý tưởng):** 1. MrDev1 nhận task "Xây dựng trang đăng nhập" từ MrPm. 2. Mở terminal: ```bash # Chuyển về nhánh develop và cập nhật code mới nhất git checkout develop git pull origin develop # Tạo nhánh mới cho tính năng của mình git checkout -b feature/user-login-page ``` 3. MrDev1 code xong phần giao diện form đăng nhập. ```bash # Thêm các file đã thay đổi git add . # Viết commit message rõ ràng git commit -m "feat: create UI for login form" # Đẩy nhánh của mình lên GitHub git push origin feature/user-login-page ``` 4. Lên GitHub, MrDev1 tạo một **Pull Request** từ nhánh `feature/user-login-page` vào nhánh `develop`. 5. Trong PR, anh ấy tag `@MrPm` để thông báo và tag `@MrDev2` để nhờ review code. 6. MrDev2 vào xem code, để lại comment: "Cần thêm validation cho email". 7. MrDev1 thấy comment, code thêm, rồi commit và push lên tiếp. PR tự động được cập nhật. 8. MrDev2 xem lại và **Approve** (phê duyệt). 9. MrPm thấy PR đã được Dev approve, kiểm tra lại lần cuối và **Merge** vào `develop`. #### **4. Intern (Thực tập sinh)** * **Vai trò trên GitHub:** Người học hỏi, thực hiện các task nhỏ. * **Luồng công việc:** Tương tự như Dev, nhưng thường nhận các task nhỏ hơn như "Sửa lỗi chính tả ở footer", "Đổi màu nút bấm". PR của Intern sẽ được các MrDev hoặc MrPm review kỹ hơn. --- ### **Phần 3: Các kịch bản xung đột (Conflicts) và cách giải quyết** Đây là phần thú vị nhất, nơi các vấn đề thực tế phát sinh. #### **Kịch bản 1: Xung đột Merge cổ điển (Classic Merge Conflict)** * **Tình huống:** * MrPm giao cho **MrDev1** sửa màu header và **MrDev2** thêm nút "Thông báo" cũng trên header. * Cả hai cùng chỉnh sửa file `styles/header.css`. * MrDev1 hoàn thành trước và PR của anh được merge vào `develop`. * Khi MrDev2 tạo PR, GitHub thông báo: **"Can't automatically merge"**. Hoặc khi MrDev2 chạy `git pull origin develop` để cập nhật nhánh của mình, terminal báo **CONFLICT**. * **Nguyên nhân:** Cả hai đã thay đổi cùng một dòng code (hoặc các dòng gần nhau) trong cùng một file. Git không biết phải chọn phiên bản của ai. * **Cách giải quyết (phía MrDev2):** 1. Cập nhật nhánh `develop` về máy: `git pull origin develop`. 2. Mở file `styles/header.css` đang bị conflict. Sẽ thấy các dấu `<<<<<<<`, `=======`, `>>>>>>>`. ```css /* styles/header.css */ <<<<<<< HEAD (Code của MrDev2) .header { background-color: #000; /* Thay đổi của MrDev2 */ display: flex; align-items: center; } ======= .header { background-color: #3498db; /* Thay đổi của MrDev1 đã có trên develop */ } >>>>>>> develop ``` 3. MrDev2 phải nói chuyện với MrDev1 để quyết định giữ lại code nào. Trong trường hợp này, họ cần cả hai: màu nền của MrDev1 và các thuộc tính của MrDev2. Anh ấy sẽ sửa file lại thành: ```css .header { background-color: #3498db; /* Giữ lại màu của MrDev1 */ display: flex; /* Giữ lại code của mình */ align-items: center; /* Giữ lại code của mình */ } ``` 4. Lưu file lại và chạy các lệnh sau để hoàn tất việc giải quyết xung đột: ```bash git add styles/header.css git commit -m "fix: resolve merge conflict for header styles" git push origin feature/add-notification-button ``` 5. Bây giờ Pull Request trên GitHub sẽ không còn báo conflict nữa. #### **Kịch bản 2: "Intern năng nổ" và nhánh bị lỗi thời** * **Tình huống:** * **Intern1** được giao sửa một bug nhỏ trong hàm `calculatePrice()`. * Tuần trước, cậu đã `pull` nhánh `develop` về máy. * Trong tuần này, **MrDev3** đã tái cấu trúc (refactor) toàn bộ file chứa hàm `calculatePrice()`, đổi tên hàm thành `calculateFinalPrice()`. Thay đổi này đã được merge vào `develop`. * Intern1 không biết điều này. Cậu tạo nhánh mới từ nhánh `develop` cũ trên máy mình và sửa hàm `calculatePrice()` (nay đã không còn tồn tại trên `develop` mới nhất). * Khi Intern1 tạo PR, nó chứa rất nhiều thay đổi "kỳ lạ" và xung đột lớn vì nó cố gắng thêm lại code cũ đã bị xóa. * **Nguyên nhân:** Làm việc trên một phiên bản cũ của nhánh `develop`. * **Cách giải quyết:** 1. MrDev3 (hoặc MrPm) sẽ review và giải thích cho Intern1. 2. Intern1 cần cập nhật lại nhánh feature của mình theo `develop` mới nhất. Cách tốt nhất là dùng `rebase`: ```bash # Trên nhánh feature của Intern1 git checkout feature/fix-calculate-price-bug # Lấy code mới nhất về git fetch origin # Áp dụng lại các commit của mình trên nền của develop mới nhất git rebase origin/develop ``` 3. Lệnh `rebase` có thể gây conflict. Intern1 sẽ phải sửa conflict (tương tự Kịch bản 1), nhưng lần này là nhận ra hàm cũ đã bị đổi tên và áp dụng sửa lỗi của mình vào hàm mới. 4. Sau khi giải quyết xong, Intern1 sẽ phải **force push** lên nhánh feature của mình (an toàn vì đây là nhánh của riêng cậu): `git push --force-with-lease origin feature/fix-calculate-price-bug`. #### **Kịch bản 3: Thảm họa `git push --force`** * **Tình huống:** * **MrDev4** làm việc trên nhánh `feature/user-profile` và có nhiều commit lộn xộn (`"fix"`, `"oops"`, `"final fix"`). * Anh ta muốn dọn dẹp lịch sử commit trước khi tạo PR nên đã dùng `git rebase -i` để gộp các commit lại. * Sau khi rebase, lịch sử commit trên máy anh ta đã khác với lịch sử trên GitHub. Lệnh `git push` thông thường bị từ chối. * Không suy nghĩ nhiều, anh ta dùng lệnh cấm: `git push --force origin develop`. **ANH TA PUSH NHẦM LÊN `develop` THAY VÌ NHÁNH FEATURE CỦA MÌNH!** * **Hậu quả:** Toàn bộ lịch sử của nhánh `develop` trên GitHub bị ghi đè bằng phiên bản của MrDev4. Các commit của những người khác (MrDev1, MrDev2...) có thể bị "biến mất". Ai `pull` nhánh `develop` về sẽ gây ra lỗi lớn. * **Cách giải quyết (Cần MrPm hoặc Dev cứng tay):** 1. **Hành động ngay lập tức:** MrPm thông báo toàn đội **"NGỪNG MỌI HOẠT ĐỘNG TRÊN NHÁNH `develop`"**. 2. Tìm một người (ví dụ MrDev5) có phiên bản `develop` đúng **trước khi** bị force push. 3. Người này sẽ phải thực hiện một `force push` khác để khôi phục lại nhánh `develop` đúng: ```bash # Trên máy MrDev5, đảm bảo nhánh develop đang đúng git checkout develop git push --force origin develop ``` 4. **Phòng ngừa cho tương lai:** MrPm vào ngay cài đặt của Repository trên GitHub -> Branches -> Add branch protection rule cho `develop` và `main`. Bật tùy chọn **"Prevent force pushes"**. #### **Kịch bản 4: Hotfix và sự lãng quên** * **Tình huống:** * Website trên production (nhánh `main`) gặp lỗi nghiêm trọng về thanh toán. * MrPm yêu cầu **MrDev5** sửa gấp. * MrDev5 làm đúng quy trình: `git checkout main`, `git pull`, `git checkout -b hotfix/payment-gateway-error`. * Anh sửa lỗi, tạo PR vào `main`. MrPm review vội và merge. Website hoạt động trở lại. Mọi người thở phào. * **VẤN ĐỀ:** Mọi người quên merge bản vá này ngược lại vào `develop`. * **Hậu quả:** Nhánh `develop` vẫn chứa lỗi thanh toán. Vài tuần sau, khi release phiên bản mới từ `develop` lên `main`, lỗi cũ quay trở lại. * **Cách giải quyết:** 1. Luôn ghi nhớ quy trình hotfix: **Merge vào `main` xong thì phải tạo ngay một PR khác để merge `main` vào `develop`**. 2. Để giải quyết hậu quả, MrPm tạo một PR từ `main` vào `develop` để đồng bộ hóa bản vá. Có thể sẽ có conflict nếu phần code đó cũng đã được thay đổi trên `develop`. Nếu có, đội Dev sẽ phải giải quyết. #### **Kịch bản 5: Xung đột về quy trình (CEO vào cuộc)** * **Tình huống:** * **MrCEO** đang xem trang web staging và thấy một lỗi chính tả. Ông không muốn phiền phức, liền chat trực tiếp cho **Intern2**: "Cháu sửa giúp chú chữ 'Chào mứng' thành 'Chào mừng' ở trang chủ nhé, sửa thẳng luôn cho nhanh." * Intern2, vì nể nang và muốn thể hiện, đã dùng giao diện editor của GitHub để sửa trực tiếp file trên nhánh `main` (giả sử do cấu hình sai nên Intern có quyền). * **Hậu quả:** * Thay đổi này bỏ qua toàn bộ quy trình: không có Issue, không có PR, không có review, không có kiểm thử tự động. * Rủi ro: Intern2 có thể vô tình xóa một dấu ngoặc, làm sập cả trang web production mà không ai hay biết. * **Cách giải quyết:** 1. **Hành động khắc phục:** Nếu có sự cố, revert ngay commit đó trên `main`. 2. **Hành động về quy trình:** MrPm phải nói chuyện với cả MrCEO và Intern2. * Với **MrCEO**: Giải thích rằng mọi thay đổi, dù nhỏ nhất, đều cần tuân thủ quy trình để đảm bảo sự ổn định của sản phẩm. Yêu cầu của ông nên được tạo thành một Issue trên GitHub. * Với **Intern2**: Nhắc nhở về quy trình làm việc, giải thích tầm quan trọng của việc review code và sự nguy hiểm của việc sửa code trực tiếp. Những kịch bản này bao quát từ lỗi kỹ thuật thuần túy đến lỗi quy trình và giao tiếp, phản ánh rất sát với thực tế làm việc trong một đội phát triển phần mềm. ================================ Chắc chắn rồi! Dưới đây là 5 kịch bản nâng cao và khác biệt, tập trung vào các vấn đề tinh vi hơn về bảo mật, hiệu suất và quy trình làm việc phức tạp. --- #### **Kịch bản 6: Thảm họa lộ thông tin nhạy cảm (Credential Leak)** * **Tình huống:** * **MrDev4** đang tích hợp một dịch vụ của bên thứ ba. Để tiện cho việc thử nghiệm trên máy local, anh ta đã đặt API key và mật khẩu database vào một file `config.js`. * Khi commit, anh ta đã quên không đưa file `config.js` vào `.gitignore`. Anh ta commit và đẩy code lên nhánh `feature/third-party-integration`. * Ngay sau đó, anh ta nhận ra sai lầm, xóa API key khỏi file, rồi tạo một commit mới với thông điệp "remove sensitive data". Anh ta nghĩ rằng mọi thứ đã ổn. * **Hậu quả nghiêm trọng:** * Mặc dù commit mới nhất không còn chứa thông tin nhạy cảm, nhưng **toàn bộ lịch sử của Git vẫn lưu lại commit cũ**. Bất kỳ ai có quyền truy cập vào repository đều có thể xem lịch sử, tìm đến commit cũ và lấy được API key. * Nếu repository là public, các bot tự động quét GitHub sẽ tìm thấy key này trong vòng vài phút và lạm dụng nó. * **Cách giải quyết (Yêu cầu hành động khẩn cấp):** 1. **Bước 1: Vô hiệu hóa ngay lập tức.** MrPm/MrDev4 phải ngay lập tức truy cập vào dịch vụ của bên thứ ba và **thu hồi/thay đổi (rotate)** API key đã bị lộ. Đây là bước quan trọng nhất và phải làm đầu tiên. Tương tự với mật khẩu database. 2. **Bước 2: Xóa hoàn toàn khỏi lịch sử Git.** Một commit mới không đủ. Phải dùng các công cụ mạnh hơn để viết lại lịch sử. * **MrDev cứng tay nhất (MrDev5)** sẽ sử dụng công cụ như `git filter-repo` (công cụ được khuyên dùng hiện nay) hoặc BFG Repo-Cleaner. * Lệnh ví dụ với `git filter-repo`: ```bash # Cài đặt git-filter-repo nếu chưa có # Xóa file config.js khỏi toàn bộ lịch sử git filter-repo --path config.js --invert-paths ``` 3. **Bước 3: Force push để cập nhật lịch sử mới.** Sau khi lịch sử đã được làm sạch, MrDev5 phải force push lên tất cả các nhánh bị ảnh hưởng: ```bash git push origin --force --all ``` 4. **Bước 4: Thông báo toàn đội.** Tất cả thành viên khác phải fetch lại và reset repository của họ để đồng bộ với lịch sử mới, tránh vô tình đẩy lại commit cũ lên. 5. **Phòng ngừa:** Đưa ra quy định: Luôn sử dụng biến môi trường (environment variables) và file `.env` (và file `.env` phải có trong `.gitignore`) để quản lý thông tin nhạy cảm. --- #### **Kịch bản 7: "Repo phình to" do file lớn** * **Tình huống:** * **Intern2** được giao nhiệm vụ thêm một video hướng dẫn sử dụng (dung lượng 200MB) và vài file thiết kế Photoshop (mỗi file 80MB) vào website. * Không biết về Git LFS (Large File Storage), cậu đã `git add` và commit các file này như bình thường. * **Hậu quả:** * Kích thước của repository tăng vọt. Mỗi khi có thành viên mới `git clone`, họ phải tải xuống toàn bộ 280MB này, ngay cả khi họ không cần đến chúng. * Mỗi lần `git pull` cũng trở nên chậm chạp hơn. GitHub có giới hạn về kích thước file và repo, có thể gây ra cảnh báo. * **Cách giải quyết:** 1. **Giải thích:** MrDev giải thích cho Intern2 rằng Git được thiết kế cho code (text file), không phải cho file nhị phân lớn. 2. **Gỡ bỏ file khỏi lịch sử:** Tương tự Kịch bản 6, phải dùng `git filter-repo` để xóa các file này khỏi lịch sử. ```bash git filter-repo --path path/to/video.mp4 --invert-paths git filter-repo --path path/to/design.psd --invert-paths ``` 3. **Thiết lập Git LFS:** * MrPm/MrDev cài đặt Git LFS cho repository. * Họ cấu hình để Git LFS theo dõi các loại file lớn: ```bash git lfs install git lfs track "*.mp4" "*.psd" # Đảm bảo file .gitattributes được commit git add .gitattributes ``` 4. **Commit lại file lớn theo cách đúng:** Intern2 bây giờ có thể add lại các file video và psd. Git sẽ chỉ lưu một con trỏ nhỏ, còn file thực tế sẽ được tải lên dịch vụ LFS. Repository sẽ nhẹ nhàng trở lại. --- #### **Kịch bản 8: Xung đột "Nó chạy trên máy tôi!"** * **Tình huống:** * **MrDev1** thêm một thư viện mới (ví dụ: `moment.js`) vào dự án nhưng quên không chạy `npm install moment --save` (thiếu flag `--save`). Do đó, file `package.json` không được cập nhật. * Tính năng trên máy MrDev1 chạy hoàn hảo vì thư viện đã được cài trong thư mục `node_modules` của anh. Anh ta tạo PR. * **MrPm** merge PR này vào `develop`. * **MrDev2** `pull` nhánh `develop` về, chạy ứng dụng và nó bị sập với lỗi `Error: Cannot find module 'moment'`. Anh ta mất nửa tiếng để tìm ra nguyên nhân. * **Nguyên nhân:** Sự không đồng nhất về môi trường và dependencies giữa các máy phát triển. * **Cách giải quyết và phòng ngừa:** 1. **Sửa lỗi:** MrDev1 hoặc MrDev2 phải chạy `npm install moment --save` để cập nhật `package.json` và `package-lock.json`, sau đó commit các file này lên. 2. **Thiết lập quy trình CI (Continuous Integration):** * MrPm thiết lập một workflow trên **GitHub Actions**. * Mỗi khi một Pull Request được tạo, GitHub Actions sẽ tự động: * Checkout code. * Chạy `npm install` (hoặc `npm ci` để đảm bảo dùng đúng phiên bản trong lock file). * Chạy các bài test tự động (`npm test`). * Nếu bước `npm install` hoặc `npm test` thất bại, PR sẽ bị đánh dấu là "failed" và không thể merge. * Điều này sẽ bắt được lỗi của MrDev1 ngay lập tức, trước khi nó được merge và ảnh hưởng đến người khác. 3. **Sử dụng `.gitignore` triệt để:** Đảm bảo các file của môi trường local (như `node_modules`, `*.log`, file cấu hình của IDE) được đưa vào `.gitignore` để tránh commit nhầm. --- #### **Kịch bản 9: Rebase trên nhánh chung và sự phân kỳ lịch sử** * **Tình huống:** * Một tính năng phức tạp yêu cầu **MrDev3** và **MrDev5** cùng làm việc trên một nhánh là `feature/realtime-chat`. * Cả hai đều push commit lên nhánh này. * MrDev3, một người yêu thích lịch sử commit thẳng hàng và sạch sẽ, đã `pull` code của MrDev5 về. Thay vì `merge`, anh ta đã chạy `git rebase` để đặt các commit của mình lên trên các commit của MrDev5. * Sau khi rebase, anh ta `git push --force` lên nhánh `feature/realtime-chat`. * **Hậu quả:** * Đối với MrDev3, lịch sử trông rất đẹp. * Nhưng đối với MrDev5, khi anh ta cố gắng `git pull` hoặc `git push`, Git báo lỗi vì lịch sử trên máy anh ta và trên remote đã **phân kỳ (diverged)**. Lịch sử cũ mà anh ta đang có không còn tồn tại trên remote nữa. Điều này gây ra sự bối rối cực độ. * **Cách giải quyết:** 1. **Quy tắc vàng:** **Không bao giờ rebase một nhánh đã được chia sẻ và có người khác làm việc trên đó.** Rebase chỉ nên dùng cho nhánh cá nhân trước khi tạo PR vào nhánh chung (`develop`). 2. **Sửa chữa:** MrDev5 bây giờ phải thực hiện một thao tác phức tạp để đồng bộ lại: ```bash # Lấy lịch sử mới nhất từ remote git fetch origin # Reset nhánh local của mình về đúng trạng thái của remote git reset --hard origin/feature/realtime-chat ``` * **Rủi ro:** Lệnh `reset --hard` sẽ xóa mọi commit local chưa được push của MrDev5. Anh ta phải chắc chắn đã sao lưu chúng (ví dụ dùng `git cherry-pick`) trước khi thực hiện. 3. **Thống nhất cách làm:** MrPm tổ chức một cuộc họp và quyết định: Đối với các nhánh feature có nhiều người làm, chỉ sử dụng `git merge` để tích hợp thay đổi của nhau. --- #### **Kịch bản 10: Pull Request "Mồ côi" (Stale/Orphaned PR)** * **Tình huống:** * **MrDev1** tạo một PR cho một tính năng "nice-to-have" (có thì tốt). * **MrPm** bận với các ưu tiên cao hơn và không review PR này. * PR bị bỏ quên trong 3 tuần. Trong thời gian đó, nhánh `develop` đã có hàng chục commit mới được merge, bao gồm cả việc tái cấu trúc một số file mà PR của MrDev1 đang đụng tới. * **Hậu quả:** * Khi MrPm quay lại xem PR, nó có **xung đột merge (merge conflicts) rất lớn** với `develop`. * Quan trọng hơn, logic trong PR của MrDev1 có thể không còn phù hợp hoặc thậm chí gây lỗi với cấu trúc code mới trên `develop`. Việc review và sửa chữa bây giờ tốn nhiều công sức hơn cả việc làm lại từ đầu. * **Cách giải quyết và phòng ngừa:** 1. **Quy trình cho PR:** MrPm đặt ra quy tắc: * Không có PR nào được để quá 3 ngày làm việc mà không có ít nhất một bình luận hoặc review. * Sử dụng tính năng **"Draft Pull Request"** của GitHub cho các công việc đang dang dở để mọi người biết nó chưa sẵn sàng để review. * Sử dụng **bot tự động** (ví dụ Stale bot) để ping các PR cũ và tự động đóng chúng nếu không có hoạt động. 2. **Trách nhiệm của người tạo PR:** MrDev1 có trách nhiệm giữ cho PR của mình luôn "tươi mới". Trước khi yêu cầu review, anh ta phải chủ động cập nhật nhánh của mình với `develop` mới nhất. ```bash # Trên nhánh feature/nice-to-have-feature git fetch origin git rebase origin/develop # Dùng rebase để giữ lịch sử sạch # Giải quyết conflict nếu có git push --force-with-lease origin feature/nice-to-have-feature ``` 3. Bằng cách này, khi MrPm review, anh ta biết chắc rằng code này là mới nhất và có thể merge được ngay lập tức nếu được duyệt. ============================= Tuyệt vời! Chúng ta hãy đi sâu vào các tình huống phức tạp hơn, nơi vấn đề không chỉ nằm ở code mà còn ở công cụ, quy trình và tâm lý đội nhóm. --- #### **Kịch bản 11: Sai lầm trong việc gắn Tag phiên bản (The Phantom Hotfix)** * **Tình huống:** * Website `v1.0.0` đang chạy trên production (`main`). Một lỗi nghiêm trọng được phát hiện. * **MrDev5** nhanh chóng sửa lỗi và merge một commit hotfix vào nhánh `main`. * **MrPm**, trong lúc vội vã, cần phải deploy phiên bản vá lỗi `v1.0.1`. Ông chạy lệnh `git tag v1.0.1` và `git push --tags`. * **Sai lầm chí mạng:** MrPm đã quên `git pull` trước khi gắn tag. Ông đã gắn tag `v1.0.1` vào commit *trước* khi commit hotfix của MrDev5 được kéo về máy ông. * **Hậu quả:** * Hệ thống triển khai tự động (CI/CD) nhận diện tag `v1.0.1` mới và tiến hành build. * Nó build từ commit mà MrPm đã gắn tag – tức là phiên bản **vẫn còn lỗi**. * Phiên bản lỗi được deploy ra production một lần nữa. Cả đội hoang mang vì họ "thấy" code đã được merge nhưng lỗi vẫn còn đó. * **Cách giải quyết:** 1. **Điều tra:** MrDev5 dùng lệnh `git show v1.0.1` và phát hiện tag này không chứa commit sửa lỗi của mình. 2. **Xóa tag sai:** MrPm phải xóa tag sai trên cả local và remote: ```bash # Xóa tag ở local git tag -d v1.0.1 # Xóa tag trên remote (cú pháp hơi lạ) git push --delete origin v1.0.1 ``` 3. **Gắn lại tag đúng:** MrPm tìm đúng hash của commit hotfix (ví dụ: `f8a3d1b`), cập nhật nhánh `main` của mình, và gắn lại tag: ```bash git pull origin main git tag v1.0.1 f8a3d1b git push origin v1.0.1 ``` 4. Hệ thống CI/CD sẽ nhận diện tag mới (cùng tên nhưng trỏ đến commit khác) và deploy đúng phiên bản. * **Phòng ngừa:** Luôn có một bước kiểm tra trong checklist release: "Xác minh tag trỏ đến đúng commit trước khi push." --- #### **Kịch bản 12: Sự hỗn loạn của Git Submodules** * **Tình huống:** * Dự án sử dụng một repository riêng cho thư viện UI chung, được nhúng vào dự án chính dưới dạng **Git Submodule**. * **Intern1** được giao cập nhật thư viện UI này (thêm một icon mới). Cậu push thay đổi lên repository của thư viện UI. * **MrDev4**, làm việc trên dự án chính, cần sử dụng icon mới. Anh vào thư mục submodule, chạy `git pull` để lấy code mới của thư viện. Thấy icon đã hiện ra, anh vui mừng. * **Sai lầm:** Anh ta chỉ commit thay đổi trên dự án chính mà **quên không tạo một commit để "khóa" phiên bản mới của submodule**. * **Hậu quả:** * Trên máy của MrDev4, mọi thứ hoạt động. Nhưng khi CI/CD server hoặc một Dev khác (`MrDev2`) clone dự án chính, Git sẽ checkout submodule ở phiên bản cũ (phiên bản được commit lần cuối trong dự án chính). * Build thất bại với lỗi "không tìm thấy icon mới". MrDev2 bối rối vì trên repo thư viện UI, anh thấy rõ ràng icon đã có. * **Cách giải quyết:** 1. MrDev4 phải hiểu rằng cập nhật submodule là một quy trình 2 bước. 2. Anh ta quay lại máy mình, vào thư mục dự án chính và chạy `git status`. Terminal sẽ báo: `modified: path/to/ui-library (new commits)`. 3. Anh ta phải tạo một commit mới trong dự án chính để ghi lại sự thay đổi này: ```bash git add path/to/ui-library git commit -m "feat: Update UI library to include new icon" git push ``` 4. Bây giờ, dự án chính đã "biết" phải dùng phiên bản mới nhất của submodule. Khi người khác clone, họ sẽ nhận được đúng phiên bản. * **Phòng ngừa:** Tổ chức buổi training về cách làm việc với Submodule. Tạo tài liệu hướng dẫn các bước cập nhật submodule để không ai quên bước cuối cùng. --- #### **Kịch bản 13: "Git Blame" và văn hóa đổ lỗi** * **Tình huống:** * Một bug nghiêm trọng về logic tính toán đã tồn tại trong code suốt 6 tháng mà không ai phát hiện. * **MrPm** đang chịu áp lực từ **MrCEO**, ông liền dùng `git blame` để xem ai đã viết dòng code lỗi. Kết quả chỉ thẳng vào một commit của **Intern2** từ hồi mới vào công ty. * Trong cuộc họp, MrPm nói: "Chúng ta đã tìm ra nguyên nhân, nó đến từ code của Intern2." * **Hậu quả & Xung đột ngầm:** * Intern2 cảm thấy bị tấn công, mất mặt và mất động lực. * **MrDev1** (người đã review code cho Intern2 lúc đó) im lặng nhưng biết rằng: tại thời điểm Intern2 viết, code đó là hoàn toàn đúng. Một thay đổi ở một module khác 3 tháng sau do chính MrDev1 thực hiện đã vô tình làm cho logic của Intern2 chạy sai. * Văn hóa đội nhóm bị rạn nứt. Mọi người trở nên sợ hãi, ngại thử nghiệm và chỉ dám viết code "an toàn", kìm hãm sự sáng tạo. * **Cách giải quyết:** 1. **Thay đổi tư duy:** MrDev1 nên lên tiếng, giải thích toàn bộ bối cảnh. `git blame` không phải để "đổ lỗi" mà là để "tìm người có bối cảnh" (find context). Người viết code đó là người tốt nhất để hỏi "Tại sao bạn lại viết như thế này lúc đó?". 2. **Tổ chức buổi họp "Blameless Postmortem" (Mổ xẻ không đổ lỗi):** Trọng tâm của buổi họp là phân tích "Quy trình nào đã thất bại?" chứ không phải "Ai đã thất bại?". Lẽ ra đã phải có test case nào để bắt được lỗi tương tác này? * **Phòng ngừa:** Xây dựng văn hóa an toàn tâm lý. MrPm và các leader phải làm gương, nhấn mạnh rằng lỗi là một phần của quá trình phát triển và là cơ hội để cải tiến hệ thống. --- #### **Kịch bản 14: Cái bẫy "Revert một Revert"** * **Tình huống:** 1. **Commit A:** MrDev3 merge một tính năng mới vào `develop`. 2. **Commit B (Revert A):** MrPm phát hiện tính năng gây lỗi và quyết định `revert` commit của MrDev3. Một commit mới được tạo ra, xóa bỏ các thay đổi của Commit A. 3. Vài ngày sau, bug đã được hiểu rõ. Họ muốn đưa tính năng đó trở lại. 4. **Sai lầm:** MrDev3 nghĩ rằng cách đơn giản nhất là "revert lại commit revert". Anh ta chạy `git revert <Commit_B_hash>`. * **Hậu quả:** * Về mặt code, các thay đổi của Commit A đã quay trở lại nhánh `develop`. * Tuy nhiên, khi MrDev3 cố gắng merge lại nhánh `feature` gốc của mình vào `develop`, Git thông báo "Already up-to-date" hoặc "No changes". * Lịch sử Git bây giờ trở nên rất khó hiểu. Nhánh `feature` gốc dường như đã bị "vô hiệu hóa" vì tất cả các thay đổi của nó đã được thêm vào rồi lại bị xóa đi trong lịch sử của `develop`. * **Cách giải quyết:** 1. Chấp nhận rằng nhánh `feature` cũ đã "chết". 2. Cách sạch sẽ nhất: MrDev3 tạo một nhánh **hoàn toàn mới** từ `develop` hiện tại. 3. Anh ta dùng `git cherry-pick` để lấy lại các commit cần thiết từ nhánh `feature` cũ sang nhánh mới này. 4. Tạo một Pull Request mới từ nhánh mới này. Lịch sử sẽ rõ ràng và dễ theo dõi. * **Phòng ngừa:** Khi một PR bị revert, hãy coi như PR đó đã hoàn thành nhiệm vụ. Nếu muốn làm lại tính năng đó, hãy bắt đầu bằng một nhánh mới. Đừng cố gắng "hồi sinh" nhánh cũ. --- #### **Kịch bản 15: Xung đột do Cache của hệ thống CI/CD** * **Tình huống:** * Để tăng tốc độ build, **MrPm** đã cấu hình GitHub Actions để cache lại thư mục `node_modules`. * **MrDev2** cập nhật một dependency phụ thuộc (ví dụ: từ `lodash@4.17.20` lên `lodash@4.17.21`) để vá một lỗ hổng bảo mật. Anh ta cập nhật file `package-lock.json` và tạo PR. * **Sai lầm của hệ thống:** Cơ chế cache của CI/CD không đủ thông minh. Nó thấy file `package.json` không đổi nên đã dùng lại cache `node_modules` cũ, thay vì chạy `npm ci` để cài đặt lại theo `package-lock.json` mới. * **Hậu quả:** * CI/CD báo "build thành công" với dấu tick xanh. * PR được merge. * Code được deploy ra production với phiên bản `lodash` cũ và **lỗ hổng bảo mật vẫn còn đó**. Đây là một lỗi "tàng hình", rất khó phát hiện vì mọi thứ có vẻ đều hoạt động bình thường. * **Cách giải quyết:** 1. **Điều tra:** Sau khi một công cụ quét bảo mật báo động, cả đội mới tá hỏa đi tìm nguyên nhân. Họ phải kiểm tra log build của CI/CD để thấy rằng bước `npm install` đã bị bỏ qua do cache. 2. **Sửa cấu hình CI/CD:** MrPm phải sửa lại file workflow của GitHub Actions. Chìa khóa (key) của cache phải phụ thuộc vào hash của file lock, không chỉ file manifest. ```yaml # Ví dụ trong workflow.yml - name: Cache dependencies uses: actions/cache@v3 with: path: ~/.npm # Key thay đổi khi package-lock.json thay đổi key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-npm- ``` 3. **Kích hoạt lại build:** Chạy lại pipeline cho commit mới nhất trên `main` để deploy đúng phiên bản đã được vá. * **Phòng ngừa:** Hiểu rõ và cấu hình cơ chế cache một cách cẩn thận. Luôn làm cho key của cache phụ thuộc vào các file định nghĩa phiên bản chính xác (như `package-lock.json`, `Gemfile.lock`, `yarn.lock`).