#!/bin/sh # LFS Pre-Push Hook — push 前显示 LFS 文件清单并要求确认 # Issue: OS-GBL-F-002 # # 功能: # 1. 检测本次 push 涉及的 LFS 文件,显示清单和总大小 # 2. >500MB 的文件标记警告 # 3. 用户确认后执行 git lfs pre-push 上传 # # 环境变量: # LFS_PREPUSH_SKIP=1 跳过确认(CI/CD 用) # # 兼容性:POSIX sh + BSD awk(macOS 原生环境) REMOTE="$1" URL="$2" # --- 捕获 stdin(ref 数据),后续 replay 给 git lfs pre-push --- INPUT="" while IFS= read -r line; do if [ -z "$INPUT" ]; then INPUT="$line" else INPUT="$INPUT $line" fi done # --- 检查 git-lfs 是否可用 --- if ! command -v git-lfs >/dev/null 2>&1 && ! git lfs version >/dev/null 2>&1; then echo "pre-push: git-lfs not found, skipping LFS check" exit 0 fi # --- 收集所有 ref 涉及的 LFS 文件 --- LFS_LIST="" process_ref() { local_ref="$1" local_sha="$2" remote_ref="$3" remote_sha="$4" ZERO="0000000000000000000000000000000000000000" # 删除分支 → 跳过 if [ "$local_sha" = "$ZERO" ]; then return fi # 确定比较基准 if [ "$remote_sha" = "$ZERO" ]; then # 新分支:找与默认分支的 merge-base base="" for candidate in main master develop; do if git rev-parse --verify "refs/remotes/origin/$candidate" >/dev/null 2>&1; then base=$(git merge-base "refs/remotes/origin/$candidate" "$local_sha" 2>/dev/null) break fi done if [ -z "$base" ]; then # 无法找到基准,列出该分支所有 LFS 文件 base="" fi else base="$remote_sha" fi # 获取 LFS 文件列表(带大小) if [ -n "$base" ]; then files=$(git lfs ls-files --size "$base" "$local_sha" 2>/dev/null) else files=$(git lfs ls-files --size "$local_sha" 2>/dev/null) fi if [ -n "$files" ]; then LFS_LIST="$LFS_LIST $files" fi } # 解析每一行 ref 数据 if [ -n "$INPUT" ]; then echo "$INPUT" | while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do if [ -n "$local_ref" ]; then process_ref "$local_ref" "$local_sha" "$remote_ref" "$remote_sha" fi done fi # 因为上面的 while 在子 shell 中,LFS_LIST 不会传递回来 # 重新在当前 shell 处理 LFS_LIST="" if [ -n "$INPUT" ]; then set -f # 禁用 glob OLD_IFS="$IFS" IFS=' ' for line in $INPUT; do IFS="$OLD_IFS" local_ref=$(echo "$line" | awk '{print $1}') local_sha=$(echo "$line" | awk '{print $2}') remote_ref=$(echo "$line" | awk '{print $3}') remote_sha=$(echo "$line" | awk '{print $4}') ZERO="0000000000000000000000000000000000000000" # 删除分支 → 跳过 if [ "$local_sha" = "$ZERO" ]; then continue fi # 确定比较基准 base="" if [ "$remote_sha" = "$ZERO" ]; then for candidate in main master develop; do if git rev-parse --verify "refs/remotes/origin/$candidate" >/dev/null 2>&1; then base=$(git merge-base "refs/remotes/origin/$candidate" "$local_sha" 2>/dev/null) break fi done else base="$remote_sha" fi # 获取 LFS 文件列表 if [ -n "$base" ]; then files=$(git lfs ls-files --size "$base" "$local_sha" 2>/dev/null) else files=$(git lfs ls-files --size "$local_sha" 2>/dev/null) fi if [ -n "$files" ]; then LFS_LIST="$LFS_LIST $files" fi IFS=' ' done IFS="$OLD_IFS" set +f fi # --- 去重并解析 --- # git lfs ls-files --size 输出格式: () # 示例: abc1234567 * assets/logo.png (1.2 MB) if [ -z "$LFS_LIST" ] || [ "$(echo "$LFS_LIST" | sed '/^$/d' | wc -l)" -eq 0 ]; then # 无 LFS 文件 → 静默执行 git lfs pre-push 并放行 echo "$INPUT" | git lfs pre-push "$@" exit $? fi # 按文件路径去重 DEDUPED=$(echo "$LFS_LIST" | sed '/^$/d' | awk '!seen[$3]++') FILE_COUNT=$(echo "$DEDUPED" | wc -l | awk '{print $1}') # --- 非 TTY 或 CI 模式 → 自动放行 --- if [ "${LFS_PREPUSH_SKIP:-0}" = "1" ]; then echo "$INPUT" | git lfs pre-push "$@" exit $? fi if [ ! -t 0 ] && [ ! -t 1 ]; then # 非交互环境,自动放行 echo "$INPUT" | git lfs pre-push "$@" exit $? fi # --- 显示 LFS 文件清单 --- echo "" echo "==========================================" echo " LFS Pre-Push: $FILE_COUNT file(s) detected" echo "==========================================" echo "" TOTAL_BYTES=0 HAS_LARGE=0 echo "$DEDUPED" | while IFS= read -r entry; do # 提取路径和大小 path=$(echo "$entry" | awk '{print $3}') size_str=$(echo "$entry" | sed 's/.*(\(.*\))/\1/') # 解析大小为字节数(用于判断 >500MB) size_num=$(echo "$size_str" | awk '{print $1}') size_unit=$(echo "$size_str" | awk '{print $2}') bytes=0 case "$size_unit" in B) bytes=$(echo "$size_num" | awk '{printf "%.0f", $1}') ;; KB) bytes=$(echo "$size_num" | awk '{printf "%.0f", $1 * 1024}') ;; MB) bytes=$(echo "$size_num" | awk '{printf "%.0f", $1 * 1048576}') ;; GB) bytes=$(echo "$size_num" | awk '{printf "%.0f", $1 * 1073741824}') ;; esac # 检查是否超过 500MB (524288000 bytes) is_large=$(echo "$bytes" | awk '{if ($1 > 524288000) print 1; else print 0}') if [ "$is_large" = "1" ]; then printf " %-50s %s %s\n" "$path" "$size_str" "[!] >500MB" else printf " %-50s %s\n" "$path" "$size_str" fi done echo "" echo "------------------------------------------" echo " Total: $FILE_COUNT file(s)" echo "------------------------------------------" echo "" # --- 确认提示 --- printf "Proceed with LFS upload? [y/N] " # 从 /dev/tty 读取用户输入(因为 stdin 已被 git 占用) if read -r answer < /dev/tty 2>/dev/null; then case "$answer" in [yY]|[yY][eE][sS]) echo "Uploading LFS files..." echo "$INPUT" | git lfs pre-push "$@" exit $? ;; *) echo "Push aborted by user." exit 1 ;; esac else echo "Cannot read from terminal, aborting." exit 1 fi