跨多个 FPGA 仓库创建、管理和清理 Git 标签的标准实践。本文档定义了统一的标签命名规范,并提供了批量操作的自动化脚本。
snapshot-YYYYMMDDHHmmss
| 字段 | 含义 | 示例 |
|---|---|---|
snapshot- |
固定前缀,标识快照标签 | snapshot- |
YYYY |
4 位年份 | 2026 |
MM |
2 位月份(01-12) | 07 |
DD |
2 位日期(01-31) | 24 |
HH |
2 位小时(00-23) | 15 |
mm |
2 位分钟(00-59) | 30 |
ss |
2 位秒(00-59) | 45 |
snapshot-20260724153045
ls -1 即按时间排序)git tag -l "snapshot-202607*" 可检索 2026 年 7 月所有快照
$ git tag -l "snapshot-*" | tail -5
snapshot-20260720140022
snapshot-20260721101503
snapshot-20260722184530
snapshot-20260723113015
snapshot-20260724153045
| 特性 | 轻量标签 (git tag <name>) |
附注标签 (git tag -a <name>) |
|---|---|---|
| 存储方式 | 指向提交的指针(ref) | 完整的 Git 对象,拥有独立 SHA |
| 元数据 | 无 | 标签者姓名、邮箱、日期、消息 |
git describe |
仅返回最近的附注标签 | 正常使用 |
git show <tag> |
显示提交内容 | 显示标签消息 + 提交内容 |
| 推送到远程 | git push --tags 或显式推送 |
git push --tags 或显式推送 |
| GPG 签名 | 不支持 | git tag -s 签名标签 |
| 适用场景 | 临时本地标记 | 永久性快照 |
# 正确(附注标签)
git tag -a snapshot-20260724153045 -m "FPGA Top: added ILA debug hub, 8 probe ports"
# 错误(轻量标签 — 不要使用)
git tag snapshot-20260724153045
消息应简洁但信息充分。包含受影响的模块或文件区域:
<Area>: <short description of changes since last tag>
示例:
- "fcam: fixed Bayer-to-RGB pipeline stall on frame boundary"
- "top: integrated soft_ila_top v2.3, added 4 trigger ports"
- "axi_interconnect: increased outstanding transaction limit to 16"
- "All repos: pre-release baseline for v1.4.0-rc1"
# 显示标签元数据 + 指向的提交
git show snapshot-20260724153045
# 列出所有标签及其消息(每行一个)
git tag -l "snapshot-*" --format='%(refname:short) %(taggerdate:short) %(subject)'
# 显示两个快照之间的提交
git log --oneline snapshot-20260723113015..snapshot-20260724153045
# 显示两个快照之间变更的文件
git diff --stat snapshot-20260723113015..snapshot-20260724153045
# 默认 FPGA 仓库列表
FPGA_REPOS=(
"[email protected]:BuckHuang/fpga_top.git"
"[email protected]:BuckHuang/fcam.git"
"[email protected]:BuckHuang/axi_interconnect.git"
"[email protected]:BuckHuang/video_pipeline.git"
"[email protected]:BuckHuang/ip_common.git"
)
tag-all.sh
#!/bin/bash
# /home/user/tools/tag-all.sh
# 在所有 5 个 FPGA 仓库中创建附注快照标签。
#
# 用法:
# tag-all.sh # 使用自动生成的时间戳打标签
# tag-all.sh "fixed CDC bug" # 使用自定义消息打标签
# tag-all.sh -d # 干运行(仅打印将执行的操作)
# tag-all.sh -r # 同时将标签推送到远程
set -euo pipefail
# --- 配置 ---
REPOS_BASE="/home/user/work"
REPOS=(
"fpga_top"
"fcam"
"axi_interconnect"
"video_pipeline"
"ip_common"
)
REMOTE_PUSH=false
DRY_RUN=false
TAG_MESSAGE=""
# --- 解析参数 ---
while getopts "dr" opt; do
case $opt in
d) DRY_RUN=true ;;
r) REMOTE_PUSH=true ;;
*) echo "Usage: $0 [-d] [-r] [tag message]" ; exit 1 ;;
esac
done
shift $((OPTIND - 1))
TAG_MESSAGE="${*:-routine snapshot}"
# --- 标签名称 ---
TIMESTAMP=$(date +%Y%m%d%H%M%S)
TAG_NAME="snapshot-${TIMESTAMP}"
echo "=== Snapshot Tag: ${TAG_NAME} ==="
echo "=== Message: ${TAG_MESSAGE} ==="
echo ""
SUCCESS=()
FAILED=()
for repo in "${REPOS[@]}"; do
REPO_PATH="${REPOS_BASE}/${repo}"
echo "--- [${repo}] ---"
if [ ! -d "${REPO_PATH}/.git" ]; then
echo " SKIP: not a git repository (${REPO_PATH})"
continue
fi
pushd "${REPO_PATH}" > /dev/null
# 确保工作树干净
if [ -n "$(git status --porcelain)" ]; then
echo " SKIP: working tree is dirty"
FAILED+=("${repo} (dirty)")
popd > /dev/null
continue
fi
# 获取最新代码,确保标签落在最新远程状态上
if $REMOTE_PUSH; then
git fetch origin 2>/dev/null || true
fi
CURRENT_HASH=$(git rev-parse --short HEAD)
echo " Commit: ${CURRENT_HASH}"
if $DRY_RUN; then
echo " [DRY RUN] Would create: git tag -a ${TAG_NAME} -m \"${TAG_MESSAGE}\""
else
git tag -a "${TAG_NAME}" -m "${TAG_MESSAGE}"
echo " Created tag: ${TAG_NAME}"
if $REMOTE_PUSH; then
git push origin "${TAG_NAME}"
echo " Pushed to remote"
fi
fi
SUCCESS+=("${repo}")
popd > /dev/null
echo ""
done
# --- 摘要 ---
echo "=== Summary ==="
echo "Tag: ${TAG_NAME}"
echo "Message: ${TAG_MESSAGE}"
echo "Success: ${#SUCCESS[@]} repos"
for r in "${SUCCESS[@]}"; do
echo " + ${r}"
done
if [ ${#FAILED[@]} -gt 0 ]; then
echo "Failed: ${#FAILED[@]} repos"
for r in "${FAILED[@]}"; do
echo " - ${r}"
done
fi
# 干运行,查看将打哪些标签
./tag-all.sh -d
# 本地创建标签
./tag-all.sh "Pre-release baseline: all modules pass sim"
# 创建并推送到 GitHub
./tag-all.sh -r "Fixed AXI deadlock in interconnect; added ILA to top"
# 使用自动生成的消息
./tag-all.sh
list-tags.sh
#!/bin/bash
# /home/user/tools/list-tags.sh
# 列出所有 FPGA 仓库中最近的快照标签。
REPOS_BASE="/home/user/work"
REPOS=("fpga_top" "fcam" "axi_interconnect" "video_pipeline" "ip_common")
for repo in "${REPOS[@]}"; do
REPO_PATH="${REPOS_BASE}/${repo}"
echo "=== ${repo} ==="
if [ -d "${REPO_PATH}/.git" ]; then
git -C "${REPO_PATH}" tag -l "snapshot-*" --sort=-creatordate --format=' %(refname:short) %(creatordate:short) %(subject)' | head -5
else
echo " (not found)"
fi
echo ""
done
# 使用 gh CLI(安装:sudo apt install gh)
gh release create snapshot-20260724153045 \
--repo BuckHuang/fpga_top \
--title "Snapshot 2026-07-24 15:30" \
--notes "## Changes
- Added ILA debug hub with 8 probe ports
- Fixed Bayer-to-RGB pipeline stall
- Updated timing constraints for 200 MHz
### Affected repos
| Repo | Commit |
|------|--------|
| fpga_top | a1b2c3d |
| fcam | e4f5g6h |
| ip_common | i7j8k9l |" \
--prerelease
将构建输出附加到 Release 以保持可追溯性:
# 附加 FPGA 比特流和构建日志
gh release upload snapshot-20260724153045 \
--repo BuckHuang/fpga_top \
build/fpga_top.bit \
build/timing_summary.rpt \
build/vivado.log
# 在 GitHub 上:https://github.com/BuckHuang/fpga_top/releases/tag/snapshot-20260724153045
# 通过 CLI:
gh release view snapshot-20260724153045 --repo BuckHuang/fpga_top
| 场景 | 操作 |
|---|---|
| 标签创建在错误的提交上 | 删除本地和远程标签,重新创建 |
| 此快照的 CI 构建失败 | 删除标签(该标签从未有效) |
| 标签消息包含敏感信息 | 删除并强制推送修正后的标签 |
| 旧快照(> 90 天),无关联 Release | 可以安全清理 |
# 1. 检查是否有 Release 使用此标签
gh release view snapshot-20260724153045 --repo BuckHuang/fpga_top
# 如果存在 Release,先删除 Release:
gh release delete snapshot-20260724153045 --repo BuckHuang/fpga_top --yes
# 2. 本地删除
git tag -d snapshot-20260724153045
# 3. 从远程删除
git push origin --delete snapshot-20260724153045
# 或:git push origin :refs/tags/snapshot-20260724153045
# 4. 验证删除
git ls-remote --tags origin | grep snapshot-20260724153045
# (应无输出)
#!/bin/bash
# /home/user/tools/prune-tags.sh
# 删除所有仓库中超过 N 天的快照标签。
# 用法:prune-tags.sh 90 (删除超过 90 天的标签)
DAYS_OLD="${1:-90}"
CUTOFF_DATE=$(date -d "${DAYS_OLD} days ago" +%Y%m%d%H%M%S)
REPOS=("fpga_top" "fcam" "axi_interconnect" "video_pipeline" "ip_common")
REPOS_BASE="/home/user/work"
echo "Pruning snapshot tags older than ${DAYS_OLD} days (before ${CUTOFF_DATE})"
echo ""
for repo in "${REPOS[@]}"; do
REPO_PATH="${REPOS_BASE}/${repo}"
[ -d "${REPO_PATH}/.git" ] || continue
OLD_TAGS=$(git -C "${REPO_PATH}" tag -l "snapshot-*" | while read tag; do
TAG_DATE=${tag#snapshot-}
if [[ "${TAG_DATE}" < "${CUTOFF_DATE}" ]]; then
echo "$tag"
fi
done)
if [ -z "$OLD_TAGS" ]; then
echo "${repo}: nothing to prune"
continue
fi
echo "${repo}: found $(echo "$OLD_TAGS" | wc -l) old tags"
for tag in $OLD_TAGS; do
echo " Deleting: ${tag}"
git -C "${REPO_PATH}" tag -d "${tag}" 2>/dev/null || true
git -C "${REPO_PATH}" push origin --delete "${tag}" 2>/dev/null || true
done
done
# 如果知道标签指向的提交哈希:
git tag -a snapshot-20260724153045 <commit-hash> -m "Recovered tag"
# 如果不知道哈希,检查 reflog(仅限本地,90 天内):
git reflog | grep snapshot-20260724153045
# 检查是否有其他开发者的本地副本:
# 请团队成员执行:git push origin snapshot-20260724153045
| 规则 | 详细说明 |
|---|---|
| 标签类型 | 始终使用附注标签(-a),绝不使用轻量标签 |
| 标签消息 | 包含 <Area>: <description> |
| 创建频率 | 每个工作日至少一次;任何有风险的重构之前 |
| 推送策略 | 创建后立即推送标签(使用 -r 标志) |
| 删除策略 | 仅在标签创建错误时删除;绝不删除已发布的标签 |
| 保留期限 | 无限期保留所有标签(磁盘成本可忽略不计) |
| 清理 | 可选:清理超过 180 天且无关联 Release 的标签 |
# 创建快照标签(单个仓库)
git tag -a snapshot-$(date +%Y%m%d%H%M%S) -m "fcam: description"
# 推送标签到远程
git push origin snapshot-20260724153045
# 列出最近的快照标签
git tag -l "snapshot-*" --sort=-creatordate | head -10
# 查看自上次标签以来的变更
git log --oneline $(git describe --abbrev=0)..HEAD
# 批量打标签(全部仓库,含推送)
/home/user/tools/tag-all.sh -r "message"
# 批量列出最近标签
/home/user/tools/list-tags.sh
# 清理旧标签
/home/user/tools/prune-tags.sh 180