Giới thiệu
Trong thời đại phát triển phần mềm hiện nay, Claude Code đã mở ra một kỷ nguyên mới cho các lập trình viên. Claude Code cho phép chỉnh sửa nhiều phần của dự án cùng một lúc để đáp ứng các truy vấn phức tạp, đồng thời xem xét toàn bộ mã nguồn. Tuy nhiên, tính năng này cũng có thể dẫn đến những thay đổi không mong muốn, khiến dự án bị hỏng. Trong tình huống này, người dùng thường không có cách nào rõ ràng để phục hồi. Thay vì thực hiện nhiều commit cho từng tác vụ, điều này sẽ làm lộn xộn lịch sử Git của bạn. Một giải pháp tốt hơn là sử dụng hệ thống checkpoint tự động với git stash
và Hooks của Claude.
1. Hiểu Về Hooks
Hooks là những trình theo dõi sự kiện cho Claude Code, cho phép bạn chạy một lệnh hoặc script khi một sự kiện nhất định xảy ra (chẳng hạn như khi Claude bắt đầu hoặc dừng). Để tìm hiểu thêm, hãy xem tài liệu chính thức tại đây.
2. Thiết lập Cấu trúc Dự án
Trước khi bắt đầu, bạn cần thiết lập cấu trúc dự án như sau:
├── .claude/
│ ├── settings.local.json
│ └── logs/
├── ...
└── checkpoint.sh
3. Cấu hình Tập tin settings.local.json
Đảm bảo thay thế YOUR_PROJECT_DIR
bằng đường dẫn thực tế đến dự án của bạn.
json
{
"permissions": {
"deny": [
]
},
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "YOUR_PROJECT_DIR/checkpoint.sh",
"timeout": 30000
}
]
}
]
}
}
4. Thiết lập Tập tin checkpoint.sh
Bạn có thể tùy chỉnh các quy tắc checkpoint (số lượng stash tối đa hoặc hành vi ghi log). Nếu gặp phải vấn đề về quyền truy cập, bạn có thể cần chạy chmod +x checkpoint.sh
để làm cho tập tin có thể thực thi.
bash
#!/bin/bash
# Claude Code stop hook: checkpoint manager
# Thư mục và tập tin log
LOG_DIR=".claude/logs"
LOG_FILE="$LOG_DIR/checkpoint.log"
mkdir -p "$LOG_DIR"
# Mức độ ghi log qua biến môi trường
CHECKPOINT_DEBUG=${CHECKPOINT_DEBUG:-false}
CHECKPOINT_VERBOSE=${CHECKPOINT_VERBOSE:-true}
# Xoay vòng log: >50KB, giữ lại hiện tại + .old
rotate_logs() {
if [ -f "$LOG_FILE" ] && [ $(wc -c < "$LOG_FILE" 2>/dev/null || echo 0) -gt 51200 ]; then
# Xóa tệp cũ, di chuyển tệp hiện tại đến .old
[ -f "$LOG_FILE.old" ] && rm -f "$LOG_FILE.old"
mv "$LOG_FILE" "$LOG_FILE.old"
fi
}
# Hàm hỗ trợ ghi log (có điều kiện)
log_debug() {
[ "$CHECKPOINT_DEBUG" = "true" ] && echo "[$(date '+%H:%M:%S')] DEBUG: $1" >> "$LOG_FILE"
}
log_info() {
[ "$CHECKPOINT_VERBOSE" = "true" ] && echo "[$(date '+%H:%M:%S')] INFO: $1" >> "$LOG_FILE"
}
log_error() {
echo "[$(date '+%H:%M:%S')] ERROR: $1" >> "$LOG_FILE"
}
# Xoay vòng log
rotate_logs
# Bắt đầu ghi debug
log_debug "Stop hook started with args: $*"
# Đọc dữ liệu JSON đầu vào của Claude Code
HOOK_DATA=""
if [ -t 0 ]; then
log_debug "No stdin data detected (terminal mode)"
else
log_debug "Reading JSON data from stdin"
HOOK_DATA=$(cat)
log_debug "Received JSON: $HOOK_DATA"
fi
# Guard: stop_hook_active true => exit (ngăn chặn vòng lặp)
if echo "$HOOK_DATA" | grep -q '"stop_hook_active":\s*true'; then
log_info "Stop hook already active, exiting to prevent loop"
exit 0
fi
# Xác minh repo git
if ! git rev-parse --git-dir >/dev/null 2>&1; then
log_debug "Not a git repository, exiting"
exit 1
fi
# Kiểm tra thay đổi trong cây làm việc
GIT_STATUS=$(git status --porcelain 2>/dev/null)
if [ -z "$GIT_STATUS" ]; then
log_debug "No changes detected, exiting"
exit 0
fi
log_info "Changes detected: $(echo "$GIT_STATUS" | wc -l) files"
# Xây dựng thông điệp stash (arg hoặc trạng thái git)
if [ -n "$1" ]; then
MESSAGE="$1"
log_debug "Using command line message: $MESSAGE"
else
# Từ trạng thái, lấy tên tệp được sắp xếp theo thời gian sửa đổi (tối đa 3)
FILES_WITH_TIME=""
while IFS= read -r line; do
if [ -n "$line" ]; then
FILE_PATH=$(echo "$line" | cut -c4-)
if [ -f "$FILE_PATH" ]; then
# Lưu với thời gian sửa đổi tính bằng giây
MOD_TIME=$(stat -f "%m" "$FILE_PATH" 2>/dev/null || echo "0")
FILES_WITH_TIME="$FILES_WITH_TIME$MOD_TIME:$FILE_PATH\n"
fi
fi
done <<< "$GIT_STATUS"
# Sắp xếp theo thời gian sửa đổi desc, giữ lại tên
SORTED_FILES=$(echo -e "$FILES_WITH_TIME" | grep -v "^$" | sort -rn -t: -k1 | cut -d: -f2- | head -3)
# Soạn thảo thông điệp
CHANGED_FILES=$(echo "$SORTED_FILES" | tr '\n' ' ' | sed 's/ $//')
TOTAL_COUNT=$(echo "$GIT_STATUS" | wc -l)
if [ $TOTAL_COUNT -gt 3 ]; then
MESSAGE="$CHANGED_FILES (and $(($TOTAL_COUNT - 3)) more)"
else
MESSAGE="$CHANGED_FILES"
fi
log_debug "Generated message from git status (sorted by modification time): $MESSAGE"
fi
# Tạo nhãn checkpoint
TIMESTAMP=$(date '+%y%m%d:%H:%M:%S')
STASH_MESSAGE="agent: $TIMESTAMP $MESSAGE"
log_info "Creating checkpoint: $STASH_MESSAGE"
# Thực hiện git stash
if git stash push --include-untracked -m "$STASH_MESSAGE" >/dev/null 2>&1; then
log_info "Stash created successfully"
# Áp dụng lại stash
if git stash apply stash@{0} >/dev/null 2>&1; then
log_info "Stash reapplied successfully"
else
log_debug "Warning: Failed to reapply stash"
fi
else
log_debug "Error: Failed to create stash"
exit 1
fi
# Vòng đời stash của agent (tối đa 10)
AGENT_STASH_COUNT=$(git stash list 2>/dev/null | grep "agent:" | wc -l)
log_debug "Current agent stash count: $AGENT_STASH_COUNT"
while [ $AGENT_STASH_COUNT -gt 10 ]; do
OLDEST_AGENT_STASH=$(git stash list 2>/dev/null | grep "agent:" | tail -1 | cut -d: -f1)
if [ -n "$OLDEST_AGENT_STASH" ]; then
log_info "Removing oldest agent stash: $OLDEST_AGENT_STASH"
git stash drop "$OLDEST_AGENT_STASH" >/dev/null 2>&1
AGENT_STASH_COUNT=$((AGENT_STASH_COUNT - 1))
else
break
fi
done
# Thông điệp thành công
FINAL_MESSAGE="Checkpoint saved: stash@{0} - $STASH_MESSAGE"
echo "$FINAL_MESSAGE"
log_info "$FINAL_MESSAGE"
5. Khôi phục từ một stash
Để xem danh sách các checkpoint:
git stash list
Ví dụ:
Khôi phục một stash:
git stash apply stash@{0}
Thực hành tốt nhất
- Thực hiện kiểm tra thường xuyên: Đảm bảo rằng bạn kiểm tra các checkpoint thường xuyên để không bỏ lỡ các thay đổi quan trọng.
- Ghi chú lại lý do cho mỗi stash: Điều này giúp bạn dễ dàng tìm ra lý do cho việc lưu trữ khi cần khôi phục.
Những cạm bẫy thường gặp
- Quá nhiều stash: Nếu bạn không kiểm soát số lượng stash, bạn có thể gặp khó khăn trong việc tìm kiếm và khôi phục các thay đổi quan trọng.
- Không kiểm tra trước khi áp dụng stash: Đảm bảo rằng bạn đã kiểm tra các thay đổi trước khi áp dụng lại stash để tránh xung đột.
Mẹo Hiệu suất
- Tối ưu hóa kích thước stash: Chỉ lưu trữ những thay đổi cần thiết, tránh lưu trữ các tệp không quan trọng.
- Sử dụng các hook để tự động hóa: Tận dụng các hook để tự động hóa quy trình lưu trữ và khôi phục, tiết kiệm thời gian cho bạn.
Kết luận
Việc sử dụng git stash
kết hợp với Claude Code giúp bạn duy trì một lịch sử mã sạch sẽ và hiệu quả. Hệ thống checkpoint tự động giúp bạn dễ dàng phục hồi các thay đổi mà không làm lộn xộn lịch sử Git. Hãy bắt đầu áp dụng ngay hôm nay để nâng cao hiệu suất làm việc của bạn! Nếu bạn có bất kỳ câu hỏi nào, hãy để lại câu hỏi dưới đây.
Câu hỏi thường gặp (FAQ)
1. Tôi nên sử dụng git stash
khi nào?
Trả lời: Nên sử dụng khi bạn cần tạm thời lưu trữ các thay đổi mà không muốn commit ngay lập tức.
2. Có cách nào để xem các thay đổi trong stash không?
Trả lời: Có, bạn có thể sử dụng lệnh git stash show -p stash@{0}
để xem chi tiết các thay đổi.
3. Làm thế nào để xóa một stash?
Trả lời: Sử dụng lệnh git stash drop stash@{0}
để xóa stash cụ thể.
4. Có giới hạn nào cho số lượng stash không?
Trả lời: Mặc định, Git không giới hạn số lượng stash, nhưng bạn nên quản lý chúng để tránh lộn xộn.
5. Tôi có thể khôi phục nhiều stash cùng một lúc không?
Trả lời: Không, bạn cần khôi phục từng stash một.
Hãy thử áp dụng những kiến thức trên vào dự án của bạn và cảm nhận sự khác biệt!