Bối cảnh: Sự cố lúc 2 giờ sáng và cái giá của sự đãng trí
2 giờ 15 sáng. Cổng thanh toán dính lỗi timeout khiến hơn 200 giao dịch bị treo. Cả team nháo nhào mở máy làm hotfix. Mắt díp lại, tay mỏi nhừ, mình gõ nhầm lệnh cherry-pick rồi tiện tay bấm luôn git push --force. Kết quả: 4 commit quan trọng trên nhánh chung bốc hơi sạch. Team mất thêm gần 3 tiếng để revert và khôi phục dữ liệu. Sau đêm đó, mình rút ra một chân lý: đừng bao giờ phó mặc an toàn hệ thống vào sự tỉnh táo của con người lúc nửa đêm.
Nhiều anh em có thói quen dùng git alias trong ~/.gitconfig. Cách này tiện với thao tác đơn giản như co = checkout hay st = status. Nhưng khi bài toán phức tạp dần lên, alias lộ rõ nhược điểm. Bạn không thể parse cờ linh hoạt, không validate được dữ liệu đầu vào, thiếu màn hình trợ giúp --help, và việc nhồi nhét cả chục dòng logic shell vào một file cấu hình thực sự là ác mộng bảo trì.
Git đã giải quyết bài toán này từ lâu bằng cơ chế Custom Git Subcommands. Nguyên lý rất gọn gàng: bất kỳ file thực thi nào nằm trong biến môi trường PATH và có tên dạng git-<tên_lệnh> đều trở thành một lệnh con hợp lệ. Khi bạn gõ git safe-push, Git tự động triệu hồi file git-safe-push và chuyển toàn bộ tham số sang đó.
Cài đặt: Nguyên lý phân giải và subcommand đầu tiên
Git phân giải lệnh rất trực quan. Nếu bạn nhập một lệnh không có sẵn trong nhân (như git nuke), Git sẽ rà soát các thư mục trong biến $PATH để tìm file thực thi mang tên git-nuke.
Bước 1: Chuẩn bị thư mục chứa script cá nhân
Tránh vứt script tự viết vào /usr/bin hay /bin của hệ thống. Bạn nên tách riêng vào thư mục home của user để tiện backup và chuyển đổi máy:
mkdir -p ~/.local/bin
# Thêm đường dẫn vào ~/.bashrc hoặc ~/.zshrc
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Bước 2: Viết script chặn force push nhầm branch
Tạo file ~/.local/bin/git-safe-push. Script này dùng cờ --force-with-lease an toàn hơn và chặn hoàn toàn thao tác push đè lên các nhánh quan trọng:
cat << 'EOF' > ~/.local/bin/git-safe-push
#!/usr/bin/env bash
set -euo pipefail
CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || true)
if [ -z "$CURRENT_BRANCH" ]; then
echo "[ERROR] Đang ở trạng thái Detached HEAD. Dừng thao tác." >&2
exit 1
fi
# Danh sách các nhánh cấm push đè
PROTECTED_BRANCHES=("main" "master" "production" "release")
for b in "${PROTECTED_BRANCHES[@]}"; do
if [ "$CURRENT_BRANCH" = "$b" ]; then
echo "[CHẶN] Bạn đang đứng ở nhánh được bảo vệ: '$CURRENT_BRANCH'!" >&2
echo "Không được phép dùng safe-push lên nhánh này." >&2
exit 2
fi
done
echo "=> Đang đẩy an toàn nhánh: $CURRENT_BRANCH"
git push origin "$CURRENT_BRANCH" --force-with-lease
EOF
chmod +x ~/.local/bin/git-safe-push
Giờ đây, bạn có thể gõ git safe-push ở bất kỳ repo nào trên máy.
Nâng cao: Xử lý logic phức tạp bằng Python
Shell script rất nhanh cho tác vụ đơn giản. Tuy nhiên, khi cần parse JSON log, kiểm tra regex nhiều tầng hoặc gọi API bên ngoài, Python sẽ dễ đọc và bảo trì hơn nhiều nhờ thư viện chuẩn argparse và subprocess.
Xây dựng công cụ kiểm tra release: git-audit
Tạo file ~/.local/bin/git-audit. Script này tự động quét các commit gần nhất để bắt lỗi commit thiếu Jira Ticket ID, đồng thời cảnh báo nếu lỡ add nhầm file credential:
#!/usr/bin/env python3
import argparse
import subprocess
import sys
import re
def run_cmd(cmd):
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print(f"[ERROR] Lỗi khi chạy lệnh: {cmd}\n{result.stderr}", file=sys.stderr)
sys.exit(result.returncode)
return result.stdout.strip()
def main():
parser = argparse.ArgumentParser(
prog="git audit",
description="Kiểm tra format commit và rà soát file nhạy cảm trước khi deploy."
)
parser.add_argument("-n", "--count", type=int, default=5, help="Số lượng commit gần nhất cần kiểm tra (mặc định: 5)")
parser.add_argument("--strict", action="store_true", help="Chặn quy trình nếu commit thiếu Jira Ticket ID")
args = parser.parse_args()
print(f"[*] Đang kiểm tra {args.count} commit gần nhất...")
log_output = run_cmd(f"git log -n {args.count} --pretty=format:'%h|%an|%s'")
if not log_output:
print("[!] Không tìm thấy commit nào.")
return
has_warning = False
for line in log_output.splitlines():
sha, author, msg = line.split("|", 2)
# Kiểm tra Ticket ID ở đầu message (ví dụ: PROJ-1024)
ticket_pattern = r"^[A-Z]{2,10}-[0-9]+"
if not re.search(ticket_pattern, msg):
print(f" [CẢNH BÁO] Commit {sha} ({author}) thiếu Ticket ID: '{msg}'")
has_warning = True
else:
print(f" [OK] {sha} - {msg}")
# Kiểm tra file secret vô tình nằm trong commit HEAD
changed_files = run_cmd("git diff-tree --no-commit-id --name-only -r HEAD")
sensitive_patterns = [".env", "credentials.json", "id_rsa", ".pem"]
for f in changed_files.splitlines():
if any(pat in f for pat in sensitive_patterns):
print(f"\n[RỦI RO CAO] Phát hiện file nhạy cảm trong commit HEAD: {f}", file=sys.stderr)
sys.exit(3)
if has_warning and args.strict:
print("\n[FAILED] Chế độ strict bật: Commit message chưa chuẩn format.", file=sys.stderr)
sys.exit(1)
print("\n=> Kiểm tra hoàn tất, mã nguồn sẵn sàng!")
if __name__ == "__main__":
main()
Đừng quên cấp quyền thực thi cho file:
chmod +x ~/.local/bin/git-audit
Kiểm tra & Debug: Đảm bảo script vận hành trơn tru
Viết xong script là một chuyện, kiểm thử độ ổn định trước khi đưa vào workflow hàng ngày lại là chuyện khác.
1. Xác nhận Git đã nhận diện subcommand
Bạn không cần mở thư mục ra xem. Hãy dùng cờ tích hợp sẵn của Git để lọc toàn bộ subcommand ngoại vi:
git --list-cmds=others | grep -E "(safe-push|audit)"
2. Kiểm tra Exit Code
Một subcommand tốt bắt buộc phải trả về exit code chuẩn. Điều này giúp shell script khác hoặc pipeline CI/CD phát hiện lỗi chính xác để dừng luồng chạy kịp thời:
# Kiểm tra hiển thị help của script Python
git audit --help
# Chạy thử chế độ strict và kiểm tra mã thoát
git audit -n 3 --strict
echo "Exit Code: $?"
Mã thoát khác 0 đồng nghĩa với việc script đã bắt đúng vi phạm.
3. Khắc phục lỗi thường gặp khi Git báo “is not a git command”
- Quên cấp cờ execute: Chạy
ls -l $(which git-safe-push)để chắc chắn file có quyền-rwxr-xr-x. - Sai dòng Shebang: Luôn dùng
#!/usr/bin/env bashhoặc#!/usr/bin/env python3. Cách này giúp script chạy đúng trên cả Linux và macOS, tránh hardcode cố định đường dẫn như#!/bin/bash. - Shell chưa cập nhật cache PATH: Nếu vừa tạo script mà gõ Git chưa nhận diện, hãy chạy
hash -r(với Bash) hoặcrehash(với Zsh) để shell làm mới danh mục lệnh.
