一、Lefthook 简介
Lefthook 是一个高性能的 Git Hooks 管理工具。
它用于统一管理:
-
pre-commit
-
commit-msg
-
pre-push
-
post-merge
-
post-checkout
等 Git Hook 生命周期。
Lefthook 的主要目标:
-
自动化代码检查
-
提交前质量控制
-
提高团队开发规范一致性
-
提升 Git Hook 执行性能
-
简化 Hook 配置与维护
相比传统 shell 脚本 Hook:
-
配置更统一
-
执行速度更快
-
支持并行任务
-
更适合多人协作
-
更容易接入 CI/CD
二、为什么使用 Lefthook
传统 Git Hook 存在的问题:
难以统一
每个人本地 .git/hooks 不一致。
容易出现:
-
有人执行 Hook
-
有人未执行 Hook
-
团队规范不统一
不方便维护
传统 Hook 通常:
.git/hooks/pre-commit
属于本地文件。
无法方便进行:
-
版本管理
-
团队共享
-
统一升级
执行效率低
多个脚本串行执行。
大型项目中:
-
commit 很慢
-
开发体验差
配置分散
例如:
-
SwiftLint
-
ESLint
-
ktlint
-
单元测试
往往分散在多个 shell 脚本中。
维护成本较高。
三、Lefthook 的优势
配置统一
通过:
lefthook.yml
统一管理所有 Hook。
支持版本管理
配置文件可提交到 Git。
团队成员自动同步。
执行速度快
Lefthook 使用 Go 编写。
特点:
-
启动快
-
并行执行
-
更低资源占用
多语言支持
适用于:
-
Swift
-
JavaScript
-
Kotlin
-
Dart
-
Python
-
Go
等项目。
支持并行执行
例如:
-
SwiftLint
-
单元测试
-
格式化
可同时运行。
提高开发效率。
四、安装 Lefthook
Homebrew 安装(推荐)
brew install lefthook
验证安装
lefthook version
初始化项目
在项目根目录执行:
lefthook install
执行后会:
-
自动生成 Git Hook
-
关联
.git/hooks -
接管 Hook 生命周期
也可以直接使用touch lefthook.yml命令直接
五、基础配置
各模块添加lefthook.yml配置
1、创建 lefthook.yml
项目根目录:touch lefthook.yml
这里采用统一代码规范模式所以使用统一一份.swiftlint.yml和.swiftlint-baseline.json 并放在壳工程下
壳工程下 lefthook.yml配置
pre-commit:
commands:
swiftlint:
run: bash "$(git rev-parse --show-toplevel)/swiftlint_diff.sh"
fail_text: "❌ SwiftLint 检测不通过,请修复后重新提交
子工程下 lefthook.yml配置
re-commit:
commands:
swiftlint:
run: bash $(git rev-parse --show-toplevel)/../../swiftlint_diff.sh
fail_text: "❌ SwiftLint 检测不通过,请修复后重新提交"
这里的配置都指向根目录下的swiftlint 、.swiftlint.yml和.swiftlint-baseline.json
添加diff脚本 swiftlint_dif.sh
#!/bin/bash
# ── 动态查找主项目根目录 ──
find_main_root() {
local dir=$(pwd)
for i in 1 2 3; do
if [ -f "$dir/.swiftlint.yml" ] && [ -f "$dir/.swiftlint-baseline.json" ]; then
echo "$dir"
return
fi
dir=$(dirname "$dir")
done
echo ""
}
MAIN_ROOT=$(find_main_root)
if [ -z "$MAIN_ROOT" ]; then
echo "⚠️ 未找到 .swiftlint.yml,跳过检测"
exit 0
fi
SWIFTLINT="$MAIN_ROOT/Pods/SwiftLint/swiftlint"
CONFIG="$MAIN_ROOT/.swiftlint.yml"
BASELINE="$MAIN_ROOT/.swiftlint-baseline.json"
FOUND=0
echo "📋 配置路径: $CONFIG"
# ── 检查 SwiftLint 是否存在 ──
if [ ! -f "$SWIFTLINT" ]; then
echo "⚠️ SwiftLint 未找到: $SWIFTLINT"
exit 0
fi
# ── 获取暂存的 Swift 文件 ──
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep "\.swift$")
[ -z "$STAGED" ] && echo "✅ 无 Swift 变更" && exit 0
for FILE in $STAGED; do
echo "🔍 检测: $FILE"
# ── 兼容 macOS awk,提取真正新增的行号 ──
ADDED_LINE_NUMS=$(git diff --cached -U0 "$FILE" | \
grep "^@@" | \
sed 's/.*+\([0-9]*\),\{0,1\}\([0-9]*\).*/\1 \2/' | \
while read start count; do
[ -z "$start" ] && continue
count=${count:-1}
[ "$count" -eq 0 ] && continue
for i in $(seq "$start" $((start + count - 1))); do
echo "$i"
done
done)
[ -z "$ADDED_LINE_NUMS" ] && continue
# ── 对文件跑 SwiftLint ──
LINT_OUTPUT=$("$SWIFTLINT" lint \
--config "$CONFIG" \
--baseline "$BASELINE" \
--quiet \
"$FILE" 2>&1)
[ -z "$LINT_OUTPUT" ] && continue
# ── 只输出新增行的报错 ──
while IFS= read -r lint_line; do
LINE_NUM=$(echo "$lint_line" | \
grep -oE ':[0-9]+:[0-9]+:' | \
head -1 | cut -d':' -f2)
[ -z "$LINE_NUM" ] && continue
if echo "$ADDED_LINE_NUMS" | grep -q "^${LINE_NUM}$"; then
echo "$lint_line"
echo "$lint_line" | grep -q " error:" && FOUND=$((FOUND + 1))
fi
done <<< "$LINT_OUTPUT"
done
if [ $FOUND -gt 0 ]; then
echo ""
echo "❌ 发现 $FOUND 个新增错误,提交被拦截"
exit 1
fi
echo "✅ 检测通过"
exit 0
六、创建团队初始化脚本 init_dev.sh
touch init_dev.sh chmod +x init_dev.sh
init_dev.sh 是项目开发环境初始化脚本。
主要用于:
-
初始化本地开发环境
-
安装项目依赖
-
配置 Git Hook
-
安装代码规范工具
-
检查开发环境
-
提高团队环境一致性
通过统一脚本:
可以降低:
-
新成员接入成本
-
环境配置错误
-
工具版本不一致
-
手动初始化步骤遗漏
通常项目成员只需要执行一次:
./init_dev.sh
即可完成开发环境初始化。
init_dev.sh 脚步内容
#!/bin/bash
echo "========================================="
echo " 开发环境初始化"
echo "========================================="
# ── 检查 Lefthook ──
if ! which lefthook > /dev/null 2>&1; then
echo "📦 安装 Lefthook..."
brew install lefthook
else
echo "✅ Lefthook: $(lefthook version)"
fi
# ── 查找 SwiftLint ──
echo ""
echo "🔍 查找 SwiftLint..."
SWIFTLINT=""
# 优先找 Pods 里的
if [ -f "Pods/SwiftLint/swiftlint" ]; then
SWIFTLINT="Pods/SwiftLint/swiftlint"
echo "✅ 找到 SwiftLint (Pods): $SWIFTLINT"
# 其次找系统安装的
elif which swiftlint > /dev/null 2>&1; then
SWIFTLINT=$(which swiftlint)
echo "✅ 找到 SwiftLint (系统): $SWIFTLINT"
else
echo "⚠️ SwiftLint 未找到,请先执行 pod install"
echo " 执行完 pod install 后重新运行 ./init_dev.sh"
exit 1
fi
# ── 检查 baseline 是否存在 ──
echo ""
echo "📋 检查 baseline..."
if [ ! -f ".swiftlint-baseline.json" ]; then
echo " 生成 baseline 中(首次耗时较长)..."
"$SWIFTLINT" \
--config .swiftlint.yml \
--baseline .swiftlint-baseline.json \
. gitsubmodules/
git add .swiftlint-baseline.json
git commit -m "feat: 生成 SwiftLint baseline"
echo "✅ baseline 已生成并提交"
else
echo "✅ baseline 已存在,跳过"
fi
# ── 主项目安装 hook ──
echo ""
echo "🔧 安装主项目 Git hook..."
lefthook install
echo "✅ 主项目完成"
# ── 子模块安装 hook ──
echo ""
echo "🔧 安装子模块 Git hook..."
for module in gitsubmodules/*/; do
echo " → $module"
if [ -f "${module}lefthook.yml" ]; then
cd "$module"
lefthook install
cd - > /dev/null
echo " ✅ 完成"
else
echo " ⚠️ 跳过(无 lefthook.yml): $module"
fi
done
echo ""
echo "========================================="
echo " ✅ 初始化完成!"
echo " SwiftLint: $SWIFTLINT"
echo " 规则文件: .swiftlint.yml"
echo " 基线文件: .swiftlint-baseline.json"
echo "========================================="
各模块提交所用配置到 Git
七、新同事初始化项目(clone后)
git clone 主项目地址
进入项目cd 主项目
执行初始化脚本(只需一次)./init_dev.sh
本地已有项目 记得更新主工程 和 所有子工程
pod install 安装 swiftlint ./init_dev.sh 脚步 添加 pre-commit hook
验证效果