CtrlK
BlogDocsLog inGet started
Tessl Logo

yes-or-no/gitlab-commitflow

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

Quality

82%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

monthly.shscripts/

#!/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}"

scripts

add-member.sh

cleanup-merged.sh

install-hooks.sh

monthly.sh

post-commit.sh

SKILL.md

tile.json