233 lines
6.3 KiB
Bash
Executable file
233 lines
6.3 KiB
Bash
Executable file
#!/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 输出格式: <oid> <indicator> <path> (<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
|