From bcb7daa62ef1dd88e1dc9780d7f0db34ccf64bd8 Mon Sep 17 00:00:00 2001 From: Silicon Date: Mon, 30 Mar 2026 11:21:30 +0800 Subject: [PATCH] Add LFS config and pre-push hook --- .gitattributes | 27 +++++ .githooks/post-checkout | 4 + .githooks/post-commit | 4 + .githooks/post-merge | 4 + .githooks/pre-push | 233 ++++++++++++++++++++++++++++++++++++++++ .lfsconfig | 2 + 6 files changed, 274 insertions(+) create mode 100644 .gitattributes create mode 100755 .githooks/post-checkout create mode 100755 .githooks/post-commit create mode 100755 .githooks/post-merge create mode 100755 .githooks/pre-push create mode 100644 .lfsconfig diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..aedb131 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,27 @@ +# Git LFS 跟踪规则 +# 提交到每个启用 LFS 的 GitHub 仓库根目录 +# 策略:按文件类型全覆盖(含小图,可接受此开销) +# Issue: OS-GBL-F-002 + +# 视频 +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.mov filter=lfs diff=lfs merge=lfs -text +*.avi filter=lfs diff=lfs merge=lfs -text +*.wmv filter=lfs diff=lfs merge=lfs -text + +# 设计稿 +*.psd filter=lfs diff=lfs merge=lfs -text +*.ai filter=lfs diff=lfs merge=lfs -text +*.sketch filter=lfs diff=lfs merge=lfs -text +*.fig filter=lfs diff=lfs merge=lfs -text + +# 图片 +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text +*.tiff filter=lfs diff=lfs merge=lfs -text + +# 如特定仓库需要某类型不走 LFS,在该仓库的 .gitattributes 追加覆盖规则: +# *.png -filter -diff -merge diff --git a/.githooks/post-checkout b/.githooks/post-checkout new file mode 100755 index 0000000..9068dcf --- /dev/null +++ b/.githooks/post-checkout @@ -0,0 +1,4 @@ +#!/bin/sh +# LFS post-checkout passthrough (required when core.hooksPath = .githooks) +command -v git-lfs >/dev/null 2>&1 || { git lfs version >/dev/null 2>&1 || exit 0; } +git lfs post-checkout "$@" diff --git a/.githooks/post-commit b/.githooks/post-commit new file mode 100755 index 0000000..b6cd30f --- /dev/null +++ b/.githooks/post-commit @@ -0,0 +1,4 @@ +#!/bin/sh +# LFS post-commit passthrough (required when core.hooksPath = .githooks) +command -v git-lfs >/dev/null 2>&1 || { git lfs version >/dev/null 2>&1 || exit 0; } +git lfs post-commit "$@" diff --git a/.githooks/post-merge b/.githooks/post-merge new file mode 100755 index 0000000..4c397b2 --- /dev/null +++ b/.githooks/post-merge @@ -0,0 +1,4 @@ +#!/bin/sh +# LFS post-merge passthrough (required when core.hooksPath = .githooks) +command -v git-lfs >/dev/null 2>&1 || { git lfs version >/dev/null 2>&1 || exit 0; } +git lfs post-merge "$@" diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..ca43665 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,233 @@ +#!/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 diff --git a/.lfsconfig b/.lfsconfig new file mode 100644 index 0000000..fd0d2fb --- /dev/null +++ b/.lfsconfig @@ -0,0 +1,2 @@ +[lfs] + url = http://silicon:5e2aa6ff8dd2042a65eaaac0e8e5028bf3c99968@100.64.144.118:3000/silicon/lfs-hook-test.git/info/lfs