0

Auto-delete source branch: thói quen nhỏ, tư duy lớn về git workflow

Có một câu hỏi tưởng như ngớ ngẩn nhưng gần như dev nào cũng từng tự hỏi ít nhất một lần: "Branch này merge xong rồi, giờ mình xóa hay giữ lại?"

Câu trả lời phổ biến của người mới là: giữ lại cho chắc, lỡ sau này cần thì sao. Nghe rất hợp lý. Và cũng chính suy nghĩ "giữ lại cho chắc" đó là thứ khiến không ít repo doanh nghiệp sau vài năm có tới hàng trăm branch chết, không ai dám đụng vào, và không ai còn nhớ branch nào an toàn để xóa.

Bài viết này không nói về cú pháp Git — git branch -d, git push --delete ai cũng tra được trong 10 giây. Nó nói về một câu hỏi lớn hơn: branch nên sống bao lâu, và điều gì xảy ra khi bạn để nó sống quá lâu.

Problem

Git cho phép branch tồn tại vĩnh viễn, miễn phí, không giới hạn số lượng. Đó chính là cái bẫy.

Vì tạo branch gần như không tốn chi phí gì, đội ngũ kỹ thuật có xu hướng coi nhẹ vòng đời của nó — tạo ra rồi để đó, merge xong không dọn, đặt tên tùy hứng vì "chỉ dùng tạm". Nhưng branch không phải là file rác nằm im một chỗ. Nó là một bối cảnh làm việc mà bất kỳ ai trong team cũng có thể vô tình bước vào nhầm.

Hậu quả thực tế không phải là lý thuyết. Nó là:

  • Một dev checkout nhầm vào branch cũ tưởng đang active, code tiếp lên đó rồi mất công tìm hiểu vì sao "code cũ" đột nhiên xuất hiện lại.
  • Hai người cùng đặt tên branch fix hoặc test, reviewer duyệt nhầm merge request của người khác.
  • Một feature branch sống 3 tháng, mỗi tuần conflict một lần với develop, tới lúc merge thì diff dài 200 file không ai review nổi.

Vấn đề không nằm ở Git. Nó nằm ở cách con người dùng Git như một cái tủ lưu trữ thay vì một công cụ đồng bộ hóa công việc.

Context

Để hiểu vì sao ngành công nghiệp dần đi tới đồng thuận "branch nên ngắn hạn", cần nhìn lại một chút lịch sử.

Khoảng 2010, mô hình Git Flow (Vincent Driessen) ra đời và trở thành chuẩn mực: main, develop, release/*, hotfix/*, feature/* — mỗi loại branch có vòng đời và mục đích riêng, được thiết kế cho các sản phẩm release theo chu kỳ cố định (vài tuần một lần), cần kiểm soát version chặt chẽ. Đây là lý do nhiều ngân hàng, doanh nghiệp lớn tại Việt Nam vẫn áp dụng mô hình này gần như nguyên bản.

Nhưng khi ngành chuyển dịch sang continuous deployment — deploy nhiều lần một ngày thay vì vài tuần một lần — mô hình branch nhiều tầng đó bắt đầu bộc lộ chi phí quản lý quá lớn so với lợi ích. Các công ty vận hành ở tốc độ cao (GitHub, GitLab, Google, Shopify, Meta, Netflix) dần hội tụ về một nguyên tắc chung, dù cách triển khai khác nhau: branch càng ngắn hạn càng tốt, và việc xóa nó sau khi hoàn thành nhiệm vụ là mặc định, không phải ngoại lệ.

Core Concept

Mental model dễ nhớ nhất: hãy coi branch như một cốc giấy dùng một lần, không phải một cái ly thủy tinh để rửa và dùng lại.

Một khi merge request đã merge, code của branch đó không "mất đi" — nó đã trở thành một phần vĩnh viễn của develop/main. Bản thân merge request cũng không biến mất, nó chỉ chuyển sang trạng thái "Merged" và giữ nguyên toàn bộ commit, diff, comment để tra cứu về sau. Cái branch chỉ là con trỏ tạm thời trỏ tới một chuỗi commit — công cụ để đưa code đi, không phải nơi lưu trữ code.

Vì vậy, xóa branch sau merge không xóa lịch sử. Nó chỉ dọn đi cái "công cụ vận chuyển" đã hết nhiệm vụ.

Deep Dive: 5 case study và bài học rút ra

1. GitHub Flow — GitHub tự dùng để build chính GitHub Không có develop riêng, mọi branch tạo trực tiếp từ main, merge qua Pull Request là auto-delete ngay. main luôn ở trạng thái deploy được. Đơn giản đến mức không cần nghĩ nhiều về vòng đời branch — vì nó vốn chỉ sống vài giờ đến vài ngày.

2. Google — Trunk-Based Development ở quy mô monorepo Google gần như không dùng long-lived feature branch. Thay vào đó, tính năng chưa hoàn thiện được ẩn sau feature flag, merge thẳng vào trunk dù chưa bật cho user. Bài học quan trọng ở đây: branch không phải là cách duy nhất để "giấu" code chưa xong. Flag làm việc đó tốt hơn, vì code vẫn được tích hợp liên tục, giảm nguy cơ conflict dồn cục.

3. Netflix — Canary release thay vì giữ branch chờ hoàn thiện Tương tự Google nhưng ở góc microservices: code merge sớm, ẩn sau flag, bật dần cho 1% traffic để test trước khi mở rộng. Branch luôn ngắn.

4. Shopify — Naming convention chống trùng lặp ở quy mô lớn Hàng trăm MR mỗi ngày buộc Shopify áp quy ước <username>/<mô tả ngắn>. Có bot tự động cảnh báo nếu branch tồn tại quá 2 tuần chưa merge — một cơ chế "hết hạn" nhân tạo để ép buộc kỷ luật dọn dẹp.

5. Case lỗi thực tế — một team fintech giữ branch sống 3 tháng Một feature lớn được code chung trên một branch duy nhất suốt 3 tháng. Conflict với develop xảy ra hàng tuần, mỗi lần tốn 1–2 ngày resolve. Khi merge cuối cùng, diff hơn 200 file khiến review chỉ mang tính hình thức — và bug lọt thẳng ra production. Đây không phải lỗi công cụ, mà là hệ quả tất yếu của việc để một branch sống quá lâu.

Branch sống càng lâu
     ↓
Càng lệch xa develop
     ↓
Conflict càng lớn khi merge
     ↓
Diff càng to, review càng hời hợt
     ↓
Bug càng dễ lọt qua

Real-world Example

Tình huống rất phổ biến: bạn từng làm một tính năng trên branch tên payment-update, đã merge, remote tự xóa branch (auto-delete on merge). Vài tuần sau, bạn có task mới liên quan cùng module đó và định bụng "hay dùng lại tên payment-update cho tiện, khỏi nghĩ tên mới".

Về mặt kỹ thuật điều này an toàn — vì branch cũ đã bị xóa trên remote, tạo lại từ develop mới nhất và push lên là một branch hoàn toàn mới, không đụng gì đến merge request cũ (MR cũ đã đóng, giữ nguyên diff lịch sử). Nhưng nó chỉ an toàn về mặt kỹ thuật, chưa chắc tốt về mặt vận hành.

Trade-offs

Khía cạnh Đặt tên theo ticket/task (feature/CDS01-123-fix-form) Đặt tên ngắn tùy ý (B, fix, payment-update)
Traceability Cao — nhìn tên branch biết ngay liên kết tới task nào Thấp — phải hỏi lại hoặc đoán
Rủi ro trùng lặp Gần như không (ticket ID là duy nhất) Cao khi nhiều dev cùng dùng tên ngắn
Tốc độ gõ Chậm hơn một chút Nhanh, gọn
Dễ audit sau này Dễ Khó, nhất là khi tên bị tái sử dụng nhiều lần
Phù hợp quy mô Team từ vài người trở lên Cá nhân làm việc độc lập, ít rủi ro nhầm lẫn

Không có lựa chọn nào đúng tuyệt đối — nhưng chi phí của naming convention tốt là gần như bằng 0, trong khi chi phí của một lần nhầm merge request là rất thật (như case Shopify từng gặp: hai dev cùng đặt tên fix, review nhầm sang MR của người khác).

Common Mistakes

  • Giữ branch "cho chắc": nghĩ rằng xóa branch = mất code. Thực tế, nếu đã merge, code vẫn còn nguyên trong lịch sử nhánh đích và trong chính MR đã đóng.
  • Để 1 feature lớn nằm trên 1 branch duy nhất suốt nhiều tuần: dẫn tới conflict dồn cục và diff khổng lồ khó review — nên chia nhỏ thành nhiều MR nối tiếp nhau.
  • Đặt tên branch không mang thông tin (B, test, fix1): dễ nhầm lẫn khi scale team, dù ban đầu tưởng "chỉ dùng tạm nên không cần đặt tên đẹp".
  • Tắt auto-delete vì sợ mất dữ liệu: dẫn tới repo tích tụ hàng trăm branch chết theo thời gian, không ai dám dọn vì không rõ cái nào an toàn.

Practical Takeaways

  1. Bật "Delete source branch when merge request is accepted" làm mặc định cho mọi feature/fix branch — chỉ giữ lại các branch dài hạn có mục đích rõ ràng như release/*, hotfix/*.
  2. Đặt tên branch có gắn ticket/task ID, kể cả khi team chỉ có vài người — chi phí gõ thêm vài ký tự nhỏ hơn nhiều so với chi phí một lần nhầm lẫn.
  3. Nếu một tính năng đủ lớn để branch phải sống quá 1–2 tuần, đó là dấu hiệu nên chia nhỏ ra thành các MR nối tiếp thay vì tiếp tục nuôi một branch duy nhất.
  4. Yên tâm tái sử dụng tên branch sau khi remote đã auto-delete — merge request cũ vẫn giữ nguyên lịch sử, không bị ảnh hưởng.

Conclusion

Git không bắt bạn phải giữ branch mãi mãi — điều đó chỉ là thói quen phòng thủ thừa thãi từ thời chưa quen với việc lịch sử commit đã được bảo toàn ngay khi merge. Một khi hiểu rằng branch chỉ là phương tiện vận chuyển tạm thời, không phải kho lưu trữ, việc xóa nó sau khi hoàn thành nhiệm vụ trở thành điều hiển nhiên — giống như cách gần như mọi công ty vận hành ở tốc độ cao, từ GitHub tới Google, Shopify hay Netflix, đều đã chọn.


Bảng giải thích thuật ngữ

Thuật ngữ Giải thích
Branch Một nhánh phát triển độc lập trong Git, cho phép code song song mà không ảnh hưởng nhánh chính.
Merge Request (MR) / Pull Request (PR) Yêu cầu đưa thay đổi từ một branch vào branch khác, kèm quy trình review trước khi merge.
Auto-delete source branch Tùy chọn tự động xóa branch nguồn ngay sau khi MR được merge thành công.
Feature flag Cơ chế bật/tắt một tính năng bằng cấu hình, cho phép merge code chưa hoàn thiện vào nhánh chính mà không hiển thị với người dùng.
Trunk-Based Development Mô hình phát triển trong đó mọi người merge thường xuyên vào một nhánh chính duy nhất (trunk), hạn chế tối đa branch sống lâu.
Canary release Kỹ thuật triển khai tính năng mới cho một phần nhỏ người dùng trước khi mở rộng toàn bộ, nhằm giảm rủi ro.
Git Flow Mô hình branching truyền thống với nhiều loại nhánh cố định (main, develop, release, hotfix, feature), phù hợp release theo chu kỳ.
Long-lived branch Branch tồn tại trong thời gian dài mà không merge, dễ dẫn tới lệch xa nhánh chính và conflict lớn.

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í