Cách đóng gói code thay đổi bằng git diff và git archive để triển khai Incremental Deployment

Git tutorial - IT technology blog
Git tutorial - IT technology blog

Bối cảnh: Khi việc deploy toàn bộ trở thành cơn ác mộng

Hồi mới vào nghề, mình từng quản lý một dự án web PHP “cổ đại” nặng hơn 5GB. Đống mã nguồn này chứa vô vàn ảnh cũ và thư viện rác nhưng không ai dám xóa. Mỗi lần cần cập nhật code qua FTP hay RSYNC là một lần thót tim vì thời gian chờ đợi quá lâu.

Nỗi đau nằm ở chỗ: mình chỉ sửa đúng 2 dòng trong file auth.py. Vậy mà hệ thống vẫn phải quét hàng chục nghìn file để so sánh và upload. Một lần mạng chập chờn giữa chừng đã khiến trang web sập mất 15 phút. Đó là lúc mình nhận ra Incremental Deployment (triển khai tịnh tiến) chính là chìa khóa để giải quyết vấn đề này.

Dù hiện nay Docker hay CI/CD rất phổ biến, việc đẩy toàn bộ source code đôi khi vẫn quá tốn kém. Đặc biệt là khi băng thông giữa server build và server deploy bị giới hạn ở mức vài Mbps. Kỹ thuật kết hợp git diffgit archive sẽ giúp bạn tạo ra gói cập nhật cực nhẹ, chỉ chứa đúng những gì vừa thay đổi.

Công cụ cần thiết: Hai “trợ thủ” có sẵn trong Git

Thay vì cài đặt thêm phần mềm phức tạp, chúng ta sẽ tận dụng tối đa hai lệnh cơ bản của Git.

1. git diff: Lập danh sách file cần cập nhật

Bình thường chúng ta dùng git diff để xem code thay đổi thế nào. Tuy nhiên, để đóng gói, mình chỉ cần lấy tên file.

# Liệt kê các file thay đổi giữa commit hiện tại và commit ngay trước đó
git diff --name-only HEAD~1 HEAD

Trong thực tế, mình thường so sánh giữa hai phiên bản, ví dụ từ v1.0.2 lên v1.1.0. Lệnh này trả về danh sách đường dẫn file, đóng vai trò là “danh sách mua hàng” cho bước tiếp theo.

2. git archive: Bộ máy đóng gói chuyên nghiệp

Lệnh git archive giúp nén các file trong commit thành định dạng zip hoặc tar. Ưu điểm lớn nhất là nó tự động giữ nguyên cấu trúc thư mục và loại bỏ hoàn toàn các file rác không được Git quản lý.

# Đóng gói toàn bộ source code hiện tại thành file update.zip
git archive -o update.zip HEAD

Kết hợp để tự động hóa quy trình

Mục tiêu của chúng ta là bắt git archive chỉ đóng gói những file mà git diff tìm thấy.

Câu lệnh rút gọn cho hiệu quả tức thì

Bạn có thể lồng hai lệnh này bằng cú pháp sub-shell của Linux. Ví dụ, để lấy mọi thay đổi từ commit abc1234 đến bản mới nhất:

git archive -o changes.zip HEAD $(git diff --name-only abc1234 HEAD)

Nhưng hãy cẩn thận. Nếu trong danh sách có file đã bị xóa, lệnh trên sẽ vấp lỗi ngay vì git archive không tìm thấy file đó trong commit hiện tại để đóng gói.

Lọc trạng thái file bằng –diff-filter

Kinh nghiệm của mình là luôn thêm cờ --diff-filter. Mình chỉ quan tâm đến các file được thêm mới (A), copy (C), sửa đổi (M), hoặc đổi tên (R). Các file bị xóa (D) sẽ được xử lý riêng bằng một script xóa file trên server.

# Chỉ lấy các file thực sự đang tồn tại
git archive -o patch_v2.zip HEAD $(git diff --name-only --diff-filter=ACMR v1.0 HEAD)

Cách làm này đảm bảo gói patch_v2.zip luôn sạch và sẵn sàng để giải nén đè lên code cũ.

Tối ưu bằng Script Bash

Để anh em trong team không phải nhớ câu lệnh dài dòng, mình thường viết một file bundle.sh đơn giản:

#!/bin/bash

OLD_COMMIT=$1
NEW_COMMIT=${2:-HEAD}
OUTPUT="deploy_$(date +%Y%m%d_%H%M%S).zip"

if [ -z "$OLD_COMMIT" ]; then
    echo "Lỗi: Cần cung cấp mã commit cũ!"
    exit 1
fi

FILES=$(git diff --name-only --diff-filter=ACMR $OLD_COMMIT $NEW_COMMIT)

if [ -z "$FILES" ]; then
    echo "Không phát hiện thay đổi nào."
    exit 0
fi

git archive -o $OUTPUT $NEW_COMMIT $FILES
echo "Đã tạo gói: $OUTPUT với $(echo "$FILES" | wc -l) files."

Mình từng áp dụng script này vào GitLab CI để tự động tạo Artifact. Kết quả là giảm được dung lượng file gửi lên server từ 200MB xuống còn chưa tới 1MB cho mỗi lần fix bug nhỏ.

Kiểm soát chất lượng trước khi triển khai

Đừng vội vàng đẩy file lên server ngay. Hãy dành 5 giây để kiểm tra lại gói hàng của bạn.

  • Soi nội dung zip: Chạy unzip -l changes.zip để xem danh sách file. Hãy đảm bảo các file cấu hình nhạy cảm như .env không nằm trong đó.
  • Xử lý file rác: Gói zip chỉ có tác dụng ghi đè hoặc thêm mới. Để xóa file cũ trên server, mình thường xuất danh sách file bị xóa ra một file text: git diff --name-only --diff-filter=D $OLD $NEW > deleted.txt.
  • Lưu vết phiên bản: Luôn ghi mã commit vừa deploy vào file VERSION trên server. Lần sau, script sẽ tự đọc file này để biết cần so sánh từ đâu, tránh việc deploy nhầm hoặc thiếu.

Kỹ thuật này cực kỳ hữu ích cho VPS cấu hình thấp hoặc môi trường Shared Hosting. Mình đã từng rút ngắn thời gian deploy một dự án Legacy từ 15 phút xuống dưới 30 giây chỉ nhờ thay đổi nhỏ này.

Share: