Git钩子脚本如何更新版本号文件

wen 实用脚本 1

本文目录导读:

Git钩子脚本如何更新版本号文件

  1. 目录导读
  2. 为什么需要Git钩子更新版本号
  3. Git钩子基础与版本号文件结构
  4. 核心实现:pre-commit钩子脚本详解
  5. 进阶方案:post-commit与tag触发更新
  6. 多语言项目适配(JavaScript/Python/Java)
  7. 常见问题与调试技巧
  8. 总结与最佳实践

目录导读

  1. 为什么需要Git钩子更新版本号
  2. Git钩子基础与版本号文件结构
  3. 核心实现:pre-commit钩子脚本详解
  4. 进阶方案:post-commit与tag触发更新
  5. 多语言项目适配(JavaScript/Python/Java)
  6. 常见问题与调试技巧
  7. 总结与最佳实践

问答环节

  • Q1:如何避免多人协作时版本号冲突?
  • Q2:能否同时更新多个版本号文件?
  • Q3:钩子脚本在Windows和macOS上兼容吗?

为什么需要Git钩子更新版本号

在团队开发中,版本号管理是一个高频痛点,手动修改版本号文件(如package.jsonversion字段、VERSION.txtgradle.properties)容易导致:

  • 遗漏修改:提交代码后忘记更新版本号。
  • 格式不一致:不同开发者使用不同的递增规则(如0.1 vs 0.1-beta)。
  • 构建失败:CI/CD流水线依赖版本号,但文件未同步更新。

解决方案:利用Git钩子(Git Hook)在特定事件(如pre-commitpost-commitpost-merge)自动解析提交信息(Commit Message),按语义化版本规则(SemVer)更新文件,并自动添加到暂存区。


Git钩子基础与版本号文件结构

1 Git钩子是什么?

Git钩子是存储在.git/hooks/目录下的脚本文件,当特定Git事件触发时自动执行,常用钩子包括:

  • pre-commit:提交前运行(适合检查代码、更新版本号)。
  • prepare-commit-msg:生成默认提交信息前执行。
  • post-commit:提交成功后执行(适合生成标签或通知)。

2 版本号文件典型结构

不同项目类型采用不同文件存储版本号:

项目类型 文件路径示例 内容格式
Node.js package.json "version": "1.2.3"
Python setup.cfg / __version__ version = 2.0.1
Java/Gradle gradle.properties version=3.4.0
通用文本文件 VERSION 0.0(纯文本)

关键点:脚本需支持正则匹配不同格式,并写入更新后的版本号。


核心实现:pre-commit钩子脚本详解

1 脚本逻辑流程图

用户执行 git commit  →  pre-commit 钩子触发  
  │  
  ├─ 读取提交信息(如 “feat: add login #minor”)  
  │  
  ├─ 解析版本增量类型(major/minor/patch)  
  │  
  ├─ 读取当前版本文件(如 package.json)  
  │  
  ├─ 计算新版本号(如 1.2.3 → 1.3.0)  
  │  
  ├─ 写入新版本号到文件  
  │  
  ├─ 将修改的文件添加到暂存区(git add)  
  │  
  └─ 返回 0(允许继续提交)  

2 完整Shell脚本示例(适用于任意项目)

#!/bin/bash
# .git/hooks/pre-commit
# 功能:根据提交信息自动更新版本号文件(支持SemVer)
# 配置区:版本号文件名和正则模式
VERSION_FILES=("package.json" "VERSION" "version.txt")
VERSION_PATTERN='"version": "[0-9]+\.[0-9]+\.[0-9]+"'  # JSON格式
PLAIN_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+$'               # 纯文本格式
# 解析提交信息中的增量标记(如 [patch]、[minor]、[major])
commit_msg=$(cat "$1")  # $1 是临时提交信息文件路径
if [[ $commit_msg =~ \[patch\] ]]; then
    increment="patch"
elif [[ $commit_msg =~ \[minor\] ]]; then
    increment="minor"
elif [[ $commit_msg =~ \[major\] ]]; then
    increment="major"
else
    increment="patch"  # 默认patch
fi
# 遍历并更新版本文件
for file in "${VERSION_FILES[@]}"; do
    if [ ! -f "$file" ]; then
        continue
    fi
    # 读取当前版本(支持JSON和纯文本)
    if grep -qP "$VERSION_PATTERN" "$file" 2>/dev/null; then
        current_version=$(grep -oP '"version": "\K[0-9]+\.[0-9]+\.[0-9]+' "$file")
    elif grep -qP "$PLAIN_PATTERN" "$file"; then
        current_version=$(grep -oP '^\K[0-9]+\.[0-9]+\.[0-9]+' "$file")
    else
        echo "Warning: No version pattern found in $file" >&2
        continue
    fi
    # 分割版本号并递增
    IFS='.' read -r major minor patch <<< "$current_version"
    case $increment in
        major) ((major++)); minor=0; patch=0 ;;
        minor) ((minor++)); patch=0 ;;
        patch) ((patch++)) ;;
    esac
    new_version="$major.$minor.$patch"
    # 替换版本号(兼容JSON和纯文本)
    sed -i "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/" "$file"
    sed -i "s/$current_version/$new_version/" "$file"  # 纯文本全局替换
    # 将修改添加至暂存区
    git add "$file"
done
# 写入更新后的版本号到提交信息(可选)
echo "[版本更新: v$new_version]" >> "$1"

3 关键点说明

  • 正则匹配:使用grep -P增强兼容性(macOS需安装grep的GNU版本)。
  • 增量标记:推荐在提交信息中加入[patch][minor][major]明确语义。
  • 错误处理:若版本文件不存在或格式不匹配,脚本优雅跳过并输出警告。
  • 权限设置chmod +x .git/hooks/pre-commit确保脚本可执行。

进阶方案:post-commit与tag触发更新

1 post-commit钩子:自动创建Git标签

当版本号更新后,通常需要创建对应标签(Tag)以便CI/CD触发构建。

# .git/hooks/post-commit
#!/bin/bash
# 从最新提交的版本文件中提取版本号并打标签
version=$(grep -oP '"version": "\K[0-9]+\.[0-9]+\.[0-9]+' package.json)
git tag -a "v$version" -m "Release version $version"

注意:防止重复标签,可先检查是否存在:if git tag -l "v$version" | grep -q .; then ... fi

2 基于Git标签触发的版本号同步

某些项目希望仅在打标签时更新版本号文件(避免每次提交都递增),可以结合post-mergeprepare-commit-msg钩子,在检测到合并标签分支后自动更新。


多语言项目适配(JavaScript/Python/Java)

1 JavaScript (Node.js) 项目

使用npm version命令可自动更新package.json并创建tag:

# 在pre-commit中调用
increment_type=${1:-patch}
npm version $increment_type --no-git-tag-version  # 仅更新文件,不自动git tag

2 Python项目

修改setup.cfg时需注意INI格式:

# 可用Python脚本解析
import configparser
config = configparser.ConfigParser()
config.read('setup.cfg')
version = config['metadata']['version']

3 Java (Gradle) 项目

gradle.properties中的版本号按Key-Value更新:

sed -i "s/^version=.*/version=$new_version/" gradle.properties

常见问题与调试技巧

1 问题排查

  • 钩子未执行:检查.git/hooks/下文件名是否正确(pre-commit无后缀),权限是否为755
  • 版本号格式错误:使用git stash暂存修改后,手动运行钩子脚本测试:bash .git/hooks/pre-commit
  • Windows兼容:使用.ps1(PowerShell)脚本,或安装Git for Windows + Bash环境。

2 多人协作注意事项

  • 共享钩子:将钩子脚本放在项目根目录的hooks/文件夹下,通过git config core.hooksPath hooks/指向共享路径。
  • 避免冲突:约定所有开发者使用统一的提交信息前缀(如[minor]),并定期同步钩子脚本更新。

3 问答环节

Q1:如何避免多人协作时版本号冲突?
A1:建议采用以下策略:

  • 分支隔离:每个功能分支独立递增patch(如2.3-feature-01),仅合并到主分支时通过[minor]标记升级。
  • 本地自增:钩子只在本地修改文件,提交后由CI/CD统一验证最终版本号。
  • 使用唯一标记:如[ci-skip]跳过钩子,避免对已存在的版本号二次修改。

Q2:能否同时更新多个版本号文件?
A2:是的,脚本中的VERSION_FILES数组可包含任意数量的文件,如同时更新package.jsonVERSIONREADME.md中的版本号,只需确保每个文件的正则匹配规则正确。

Q3:钩子脚本在Windows和macOS上兼容吗?
A3:原生Shell脚本(Bash)在Windows上需安装Git Bash或WSL,macOS自带的BSD sed用法可能与Linux不同,推荐使用以下兼容方案:

  • 使用perl -i -pe替代sed -i(跨平台)。
  • 或用Node.js脚本(自带跨平台优势),在package.jsonscripts中调用:"precommit": "node scripts/update-version.js"

总结与最佳实践

1 核心要点

  • 自动化:减少手动错误,让版本号与提交历史严格对应。
  • 灵活性:支持SemVer、自定义字段、多文件同步。
  • 可追溯:通过提交信息中的标记和Git Tag,清晰记录版本变更原因。

2 生产环境建议

  1. 使用共享钩子:在项目根目录创建scripts/hooks/pre-commit,通过.gitconfigsetup.sh自动部署到开发者本地。
  2. 结合CI/CD:在GitHub Actions或Jenkins中额外验证版本号是否与上次发布一致,避免重复递增。
  3. 保留版本历史:在版本号文件中同时记录上一次版本和变更日志,如CHANGELOG.md的自动生成。
  4. 测试先行:在项目仓库中加入test_hooks/目录,用git reset --hard验证脚本的幂等性。

3 未来扩展方向

  • 集成conventional-changelog规范自动生成版本注释。
  • 使用Husky等开源工具统一管理钩子脚本(参考husky文档)。
  • pre-commit钩子中增加版本号正则校验,防止非标准版本提交。

抱歉,评论功能暂时关闭!