Use when setting up or installing the automated GitLab git workflow in a project, including branch versioning, monthly tags, auto-merge to develop, and branch cleanup.
65
82%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
#!/bin/bash
# =============================================================================
# monthly.sh - 月度自动化任务(每月1号由 GitLab CI 调用)
# 作用:
# 1. 读取 master 最新 tag,计算下一个版本号
# 2. 在 master 上打新 tag(发布快照)
# 3. 为 .gitflow-members 中每个成员创建新 base 分支
# 4. 删除上个月的 base 分支(跨月子版本分支不删)
# =============================================================================
set -e
REPO_ROOT=$(git rev-parse --show-toplevel)
MEMBERS_FILE="${REPO_ROOT}/.gitflow-members"
# ── 防止月中误执行(非1号拒绝运行,除非加 --force 或 --init)───────────────────
ARG="${1:-}"
TODAY=$(date +%d)
if [ "$TODAY" != "01" ] && [ "$ARG" != "--force" ] && [ "$ARG" != "--init" ]; then
echo "错误:monthly.sh 只应在每月1号执行(今天是 $(date +%Y-%m-%d))"
echo "如需月中新增成员,请使用:bash scripts/gitflow/add-member.sh <username>"
echo "如需强制执行,请加 --force 参数(慎用)"
echo "首次初始化(使用当前 tag 不升版本),请加 --init 参数"
exit 1
fi
# ── 工具函数 ─────────────────────────────────────────────────────────────────
# 计算下一个版本号(patch+1,满9进位)
# 参数:当前版本字符串,如 V2.0.9
# 输出:V2.1.0
next_version() {
local current="$1"
# 去掉前缀 V,分割为数组
local ver="${current#V}"
local major minor patch
IFS='.' read -r major minor patch <<< "$ver"
patch=$((patch + 1))
if [ "$patch" -gt 9 ]; then
patch=0
minor=$((minor + 1))
fi
if [ "$minor" -gt 9 ]; then
minor=0
major=$((major + 1))
fi
echo "V${major}.${minor}.${patch}"
}
# ── 1. ���取 master 最新 tag ───────────────────────────────────────────────────
git fetch --tags --force origin
LATEST_TAG=$(git tag --list 'V[0-9]*.[0-9]*.[0-9]*' \
| grep -E '^V[0-9]+\.[0-9]+\.[0-9]+$' \
| sort -V \
| tail -1)
if [ -z "$LATEST_TAG" ]; then
echo "[monthly] 未找到已有 tag,使用初始版本 V1.0.0"
LATEST_TAG="V1.0.0"
NEW_TAG="V1.0.0"
elif [ "$ARG" = "--init" ]; then
# 首次初始化:直接使用当前 tag,不升版本
NEW_TAG="$LATEST_TAG"
echo "[monthly] 初始化模式:使用当前版本 $NEW_TAG(不升版本)"
else
NEW_TAG=$(next_version "$LATEST_TAG")
fi
echo "[monthly] 当前版本:$LATEST_TAG → 新版本:$NEW_TAG"
# ── 2. 在 master HEAD 打新 tag ────────────────────────────────────────────────
git fetch origin master
MASTER_SHA=$(git rev-parse origin/master)
if [ "$ARG" != "--init" ]; then
git tag "$NEW_TAG" "$MASTER_SHA"
git push origin "$NEW_TAG"
echo "[monthly] 已打 tag:$NEW_TAG"
else
echo "[monthly] 初始化模式:跳过打 tag($NEW_TAG 已存在)"
fi
# ── 3. 读取成员列表 ───────────────────────────────────────────────────────────
if [ ! -f "$MEMBERS_FILE" ]; then
echo "[monthly] 错误:未找到 $MEMBERS_FILE"
exit 1
fi
# 读取非空、非注释行
MEMBERS=()
while IFS= read -r line; do
line="${line%%#*}" # 去掉注释
line="${line// /}" # 去掉空格
[ -n "$line" ] && MEMBERS+=("$line")
done < "$MEMBERS_FILE"
echo "[monthly] 成员列表:${MEMBERS[*]}"
# ── 4. 为每个成员创建新 base 分支 ─────────────────────────────────────────────
for MEMBER in "${MEMBERS[@]}"; do
NEW_BASE="${NEW_TAG}_base_${MEMBER}"
echo "[monthly] 创建 base 分支:$NEW_BASE"
git push origin "${MASTER_SHA}:refs/heads/${NEW_BASE}"
done
# ── 5. 删除上月 base 分支 ─────────────────────────────────────────────────────
# 查找所有远端 base 分支,排除本月刚创建的
echo "[monthly] 清理旧 base 分支..."
git fetch --prune origin
git branch -r \
| grep -E 'origin/V[0-9]+\.[0-9]+\.[0-9]+_base_' \
| grep -v "origin/${NEW_TAG}_base_" \
| sed 's|origin/||' \
| while read -r OLD_BASE; do
echo "[monthly] 删除旧 base 分支:$OLD_BASE"
git push origin --delete "$OLD_BASE" || echo "[monthly] 警告:删除 $OLD_BASE 失败,跳过"
done
echo "[monthly] 月度任务完成 ✓"
echo "[monthly] 新版本:$NEW_TAG"
echo "[monthly] 新 base 分支:${NEW_TAG}_base_{username}"