【保姆级】Git第六课:嵌入式专项实战——LFS、换行符、自动发布与容灾备份
【保姆级】Git第六课:嵌入式专项实战——LFS、换行符、自动发布与容灾备份
【摘要/导读】
本文专为已掌握Git基础与协作流程的嵌入式团队设计,聚焦生产级工作流落地。针对"仓库被.hex文件撑爆导致clone耗时半小时"、“Windows/Mac/Linux开发者混用导致交叉编译失败”、“固件版本与Git标签脱节导致产线烧录错误”、"单点故障导致代码库丢失"四大工程化痛点,提供可直接落地的配置方案与自动化脚本。
核心内容包括:
- 大文件管控实战:Git LFS深度配置管理固件bin、PDF手册、Altium工程文件;LFS vs 子模块 vs Artifactory的决策矩阵,配合
.gitattributes自动拦截策略与CI体积监控 - 跨平台换行符治理:
core.eol、core.autocrlf与.gitattributes三层配置模型,解决Windows(Keil)/Linux(交叉编译服务器)/Mac混用场景下的^M错误与Shell脚本崩溃问题 - 发布管理自动化:Git标签触发GitLab CI自动生成Release包,固件版本号与Git标签严格绑定(
v2.1.3→APP_v213.hex),配合git log --pretty=format自动生成含硬件BOM关联的Release Note - 备份容灾架构:裸仓库(bare repo)作为中央权威副本,多远程策略(GitLab主仓+Gitee镜像+本地NAS)实现"三地两中心"备份,含自动同步脚本与容灾切换手册
适合负责团队Git基础设施建设的技术主管/固件组长建立生产级规范,掌握"自动拦截大文件"、“跨平台一致性”、“标签即发布”、“多副本容灾"等工程化技能,避开"源码仓传HEX”、“手动改版本号”、"单点仓库"等量产阶段致命错误。
关键词:Git LFS;跨平台换行符;自动发布;固件版本绑定;Release Note生成;裸仓库备份;多远程容灾;嵌入式工程化;Keil Linux混用;Altium版本管理
📑 文章目录
一、大文件与二进制管理:源码仓的"体重控制"
1.1 Git LFS深度配置
问题量化:一个STM32项目,每次编译生成128KB的.hex和3MB的.map。如果10个开发者每天提交2次,30天后仓库增加 (128KB+3MB) × 10 × 2 × 30 ≈ 1.9GB,而实际代码可能只有5MB。
LFS原理回顾:Git仓库只存储指针文件(约130字节),实际内容存储在LFS服务器(可以是GitLab自带、GitHub或自建)。
嵌入式专用LFS配置:
# 1. 初始化LFS(项目根目录)
git lfs install
# 2. 追踪嵌入式常见大文件
git lfs track "*.hex" # 固件
git lfs track "*.bin" # 二进制
git lfs track "*.map" # 地图文件
git lfs track "*.pdf" # 手册
git lfs track "*.PcbDoc" # Altium PCB
git lfs track "*.SchDoc" # Altium原理图
git lfs track "*.PrjPcb" # Altium工程
git lfs track "*.step" # 3D模型
git lfs track "*.stp" # STEP模型
git lfs track "*.zip" # 压缩包(如第三方库)
# 3. 提交追踪规则
git add .gitattributes
git commit -m "chore: setup LFS tracking for embedded artifacts"
# 4. 验证
git lfs track
git lfs ls-files
.gitattributes完整模板(与.gitignore配合使用):
# ===== 强制LFS管理 =====
*.hex filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.map filter=lfs diff=lfs merge=lfs -text
*.elf filter=lfs diff=lfs merge=lfs -text
*.axf filter=lfs diff=lfs merge=lfs -text
# 文档与硬件设计
*.pdf filter=lfs diff=lfs merge=lfs -text
*.doc filter=lfs diff=lfs merge=lfs -text
*.docx filter=lfs diff=lfs merge=lfs -text
*.PcbDoc filter=lfs diff=lfs merge=lfs -text
*.SchDoc filter=lfs diff=lfs merge=lfs -text
*.PrjPcb filter=lfs diff=lfs merge=lfs -text
*.step filter=lfs diff=lfs merge=lfs -text
*.stp filter=lfs diff=lfs merge=lfs -text
# 合并策略:二进制文件不尝试文本合并,直接标记冲突
*.hex merge=binary
*.bin merge=binary
*.pdf merge=binary
*.PcbDoc merge=binary
# ===== 文本文件强制LF(跨平台) =====
*.c text eol=lf
*.h text eol=lf
*.cpp text eol=lf
*.hpp text eol=lf
*.md text eol=lf
*.txt text eol=lf
*.json text eol=lf
*.xml text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
Makefile text eol=lf
*.mk text eol=lf
# Keil/IDE配置文件(根据团队统一策略)
*.uvprojx text eol=lf
*.uvoptx text eol=lf
*.ioc text eol=lf
# 脚本文件强制LF(防止Windows开发者提交CRLF导致Linux CI失败)
*.sh text eol=lf
*.bash text eol=lf
*.py text eol=lf
LFS指针文件内容(存储在Git仓库中的实际内容):
version https://git-lfs.github.com/spec/v1
oid sha256:6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8
size 3276800
克隆含LFS的仓库:
# 方式1:克隆并拉取所有LFS文件(耗时较长,适合需要完整资料的新人)
git lfs clone git@gitlab.company.com:firmware/imu.git
# 或新版:git clone ... 后自动处理
# 方式2:克隆但不拉取LFS文件(仅下载指针,快速开始编码)
GIT_LFS_SKIP_SMUDGE=1 git clone git@gitlab.company.com:firmware/imu.git
# 后续按需拉取特定文件
git lfs pull --include="Datasheets/BMI323.pdf"
1.2 LFS vs 子模块 vs Artifactory决策矩阵
嵌入式团队常面临三种大文件管理方案的选择:
| 维度 | Git LFS | Git Submodule | Artifactory/Nexus |
|---|---|---|---|
| 存储位置 | Git服务器(GitLab/GitHub) | 外部Git仓库 | 独立制品库服务器 |
| 版本关联 | 与代码commit强绑定 | 与代码commit弱绑定(需手动更新指针) | 与代码无直接绑定,需手动上传下载 |
| 访问权限 | 继承Git权限 | 继承子仓库权限 | 独立权限体系 |
| CI集成 | 原生支持 | 需--recursive或手动初始化 |
需API调用或CLI工具 |
| 离线可用 | 需LFS服务器在线 | 需子仓库服务器在线 | 需制品库在线 |
| 适用场景 | 与代码版本强相关的文件(固件hex、手册PDF) | 独立演进的公共库(CMSIS、FreeRTOS) | 海量历史版本、大型二进制(编译工具链、Docker镜像) |
| 成本 | GitLab自带(免费) | 免费 | 需额外部署(企业级) |
嵌入式团队建议:
- 源码仓:代码 +
.gitattributes(LFS管理hex/pdf) - 子模块:第三方SDK(CMSIS、HAL库)
- Artifactory:编译产物长期归档(Release.hex历史版本)、编译工具链(ARM GCC安装包)
混合策略示例:
imu-project/
├── Core/ # 源码(普通Git)
├── Drivers/
│ ├── CMSIS/ # submodule(ARM官方库)
│ └── STM32G4xx_HAL_Driver/ # submodule(ST官方HAL)
├── Datasheets/
│ ├── BMI323.pdf # LFS(与代码版本同步)
│ └── TJA1051.pdf # LFS
├── Hardware/
│ ├── PCB_v2.1.PcbDoc # LFS(硬件版本关联)
│ └── SCH_v2.1.SchDoc # LFS
├── Release/ # Artifactory(仅CI上传,不在Git仓)
│ └── v1.0.0/
│ └── IMU_v100.hex # 由CI编译后上传到Artifactory
└── .gitattributes # LFS规则
1.3 自动拦截与CI体积监控
pre-commit钩子自动拦截(本地防护):
# .git/hooks/pre-commit(或项目共享hooks)
#!/bin/bash
# 检查是否有超过1MB且未被LFS追踪的文件
THRESHOLD=1048576 # 1MB
git diff --cached --name-only | while read file; do
if [ -f "$file" ]; then
size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
if [ "$size" -gt "$THRESHOLD" ]; then
# 检查是否被LFS追踪
if ! git check-attr filter -- "$file" | grep -q "lfs"; then
echo "ERROR: $file ($(($size/1024))KB) is large but NOT tracked by Git LFS!"
echo "Run: git lfs track \"$file\""
exit 1
fi
fi
fi
done
GitLab CI体积监控(远程防护):
# .gitlab-ci.yml
check_repo_size:
stage: test
image: alpine:latest
script:
- |
# 计算仓库体积(不含LFS对象)
REPO_SIZE=$(du -sb .git | cut -f1)
LIMIT=52428800 # 50MB
if [ "$REPO_SIZE" -gt "$LIMIT" ]; then
echo "ERROR: Git repo size ($(($REPO_SIZE/1024/1024))MB) exceeds limit (50MB)"
echo "Large files should use Git LFS or Artifactory"
exit 1
fi
# 检查是否有非LFS的大文件在最新提交中
git ls-tree -r -l HEAD | awk '$4 > 1048576 {print $5, $4}' | while read file size; do
if ! git check-attr filter -- "$file" | grep -q "lfs"; then
echo "ERROR: Large file detected in repo (not LFS): $file ($(($size/1024))KB)"
exit 1
fi
done
allow_failure: false
二、跨平台换行符治理
2.1 换行符问题的本质
换行符(Line Ending)历史遗留问题:
- Windows(CRLF):
\r\n,记事本、Keil MDK、IAR默认 - Linux/macOS(LF):
\n,GCC、VS Code、Vim默认 - 旧版Mac(CR):
\r,已罕见
嵌入式场景的典型故障:
- Shell脚本崩溃:Windows开发者编辑了
build.sh,提交后Linux CI执行报错/bin/bash^M: bad interpreter - Makefile解析失败:
make命令在Linux下无法识别Windows换行符的目标规则 - 宏定义诡异错误:
#define CAN_ID 0x100\r导致编译器将\r视为宏的一部分(某些老版编译器) - Git diff满屏
^M:代码审查时无法聚焦实质变更
2.2 三层配置模型
解决换行符问题需要三层防御:
| 层级 | 配置位置 | 作用范围 | 优先级 | 说明 |
|---|---|---|---|---|
| 第一层 | .gitattributes |
仓库级,所有开发者 | 最高 | 强制指定文件类型的换行符,覆盖个人设置 |
| 第二层 | core.autocrlf |
个人Git配置 | 中 | 全局默认行为(input/true/false) |
| 第三层 | 编辑器设置 | IDE/编辑器 | 低 | Keil/VS Code保存时的换行符选择 |
铁律:.gitattributes必须存在且规则完整,绝不依赖个人core.autocrlf设置(因为无法控制每个开发者的Git配置)。
2.3 嵌入式团队实战配置
.gitattributes跨平台方案(承接1.1节模板,补充说明):
# ===== 强制LF(适合以Linux交叉编译为主的团队) =====
*.c text eol=lf
*.h text eol=lf
*.s text eol=lf # 汇编文件
*.ld text eol=lf # 链接脚本
# Makefile必须LF,否则make解析失败
Makefile text eol=lf
*.mk text eol=lf
# Shell脚本必须LF
*.sh text eol=lf
*.bash text eol=lf
# Python脚本(如果有上位机工具)
*.py text eol=lf
# ===== 自动处理(让Git自行判断文本/二进制) =====
*.txt text=auto
*.md text=auto
*.json text=auto
*.xml text=auto
# ===== 明确标记为二进制(防止Git误判换行符) =====
*.hex binary
*.bin binary
*.axf binary
*.elf binary
*.lib binary
*.a binary
*.o binary
*.obj binary
*.jpg binary
*.png binary
Keil MDK换行符设置:
# Keil中设置:Edit → Configuration → EOL Encoding → Unix (LF)
# 或在项目根目录放置.editorconfig
# .editorconfig
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{c,h}]
indent_style = space
indent_size = 4
[Makefile]
indent_style = tab
全局Git配置建议(配合.gitattributes):
# 全局设置(作为fallback,但不应依赖)
git config --global core.autocrlf input # 提交时转LF,检出时不转(适合Windows开发Linux编译)
# 或
git config --global core.autocrlf false # 完全不动,依赖.gitattributes(推荐)
# 拒绝提交混合换行符
git config --global core.safecrlf true
# 设置safecrlf警告(不阻止提交,仅警告)
git config --global core.safecrlf warn
验证换行符:
# 查看文件的换行符类型
file Core/Src/main.c
# 输出:Core/Src/main.c: ASCII text, with very long lines
# 查看是否有CRLF
cat -A Core/Src/main.c | head -5
# 如果看到^M(即\r),说明有CRLF
# 批量转换(在提交前)
find . -name "*.c" -o -name "*.h" | xargs dos2unix
# 或Git内置
git add --renormalize . # 根据.gitattributes重新规范化所有文件
三、发布管理:标签即发布
3.1 标签触发自动发布
目标:工程师打标签v1.2.3后,GitLab CI自动编译生成IMU_v123.hex,上传到Release页面,并通知测试团队。
GitLab CI配置:
# .gitlab-ci.yml
stages:
- build
- release
variables:
GIT_SUBMODULE_STRATEGY: recursive
build_release:
stage: build
image: gcc-arm-none-eabi:latest
script:
- make clean
- make VERSION=$CI_COMMIT_TAG
- arm-none-eabi-size build/IMU.elf
- ls -la build/*.hex build/*.bin
artifacts:
name: "IMU_Firmware_$CI_COMMIT_TAG"
paths:
- build/*.hex
- build/*.bin
- build/*.map
expire_in: 1 year # Release产物长期保存
only:
- tags # 仅在打标签时触发
create_release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
script:
- |
release-cli create \
--name "IMU Firmware $CI_COMMIT_TAG" \
--tag-name "$CI_COMMIT_TAG" \
--description "Release Note auto-generated. See below." \
--assets-link "{\"name\":\"Firmware HEX\",\"url\":\"$CI_JOB_URL/artifacts/download\",\"link_type\":\"package\"}"
only:
- tags
dependencies:
- build_release
打标签与触发:
# 本地打附注标签(必须附注标签才能触发Release)
git tag -a v1.2.3 -m "Release v1.2.3 for HW-v2.1 MP
- Fix CAN FD bus-off recovery
- Add BMI270 temperature compensation
- Pass EMI pre-test"
# 推送到远程(触发CI)
git push origin v1.2.3
# 查看CI流水线
# GitLab Project → CI/CD → Pipelines → 看到v1.2.3标签触发的任务
3.2 固件版本与Git标签绑定
版本号生成策略:
# Makefile
# 获取Git标签(如v1.2.3),去掉v前缀
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "unknown")
VERSION_NUM := $(shell echo $(VERSION) | sed 's/^v//;s/-.*//')
VERSION_MAJOR := $(shell echo $(VERSION_NUM) | cut -d. -f1)
VERSION_MINOR := $(shell echo $(VERSION_NUM) | cut -d. -f2)
VERSION_PATCH := $(shell echo $(VERSION_NUM) | cut -d. -f3)
# 编译时注入宏
CFLAGS += -DFW_VERSION=\"$(VERSION)\"
CFLAGS += -DFW_VERSION_MAJOR=$(VERSION_MAJOR)
CFLAGS += -DFW_VERSION_MINOR=$(VERSION_MINOR)
CFLAGS += -DFW_VERSION_PATCH=$(VERSION_PATCH)
# 生成带版本号的HEX文件名
OUTPUT_NAME := IMU_v$(VERSION_MAJOR)$(VERSION_MINOR)$(VERSION_PATCH)
// version.h(由Makefile注入,或自动生成)
#ifndef VERSION_H
#define VERSION_H
#define FW_VERSION "v1.2.3"
#define FW_VERSION_MAJOR 1
#define FW_VERSION_MINOR 2
#define FW_VERSION_PATCH 3
// 硬件版本关联(手动定义,与Git标签分开)
#define HW_VERSION "v2.1"
#define HW_VERSION_CODE 0x0201
#endif
// main.c 启动时打印
printf("========================================\r\n");
printf(" IMU Firmware %s\r\n", FW_VERSION);
printf(" Hardware %s\r\n", HW_VERSION);
printf(" Build: %s %s\r\n", __DATE__, __TIME__);
printf(" Git: %s\r\n", GIT_COMMIT_HASH); // 由Makefile注入
printf("========================================\r\n");
产线烧录核对清单:
# 烧录前读取固件版本号(通过UART或SWD)
# 预期:IMU_v123.hex 对应 Git标签 v1.2.3,硬件版本 HW-v2.1
# 自动核对脚本(产线工装)
EXPECTED_FW="v1.2.3"
EXPECTED_HW="v2.1"
ACTUAL_FW=$(python read_fw_version.py) # 通过UART读取
ACTUAL_HW=$(python read_hw_version.py) # 通过GPIO或EEPROM读取
if [ "$ACTUAL_FW" != "$EXPECTED_FW" ]; then
echo "FATAL: FW mismatch! Expected $EXPECTED_FW, got $ACTUAL_FW"
exit 1
fi
if [ "$ACTUAL_HW" != "$EXPECTED_HW" ]; then
echo "FATAL: HW mismatch! Expected $EXPECTED_HW, got $ACTUAL_HW"
exit 1
fi
echo "PASS: FW $ACTUAL_FW + HW $ACTUAL_HW verified"
3.3 Release Note自动生成
基于Git日志生成结构化Release Note:
# 生成从上次标签到当前标签的变更日志
git log $(git describe --tags --abbrev=0 HEAD~1)..HEAD --pretty=format:"- %s (%h)" --no-merges
# 更完整的脚本(save as generate_changelog.sh)
#!/bin/bash
LAST_TAG=$(git describe --tags --abbrev=0 HEAD~1)
CURRENT_TAG=$(git describe --tags --abbrev=0)
echo "# Release Note: $CURRENT_TAG"
echo ""
echo "## 硬件兼容性"
echo "- 支持硬件版本: HW-v2.1 (BOM: BOM_IMU_v2.1_20240415.xlsx)"
echo "- 最小Bootloader版本: v1.0.0"
echo ""
echo "## 变更摘要"
git log ${LAST_TAG}..${CURRENT_TAG} --pretty=format:"- [%h] %s (%an)" --reverse --no-merges
echo ""
echo "## 缺陷修复 (fix)"
git log ${LAST_TAG}..${CURRENT_TAG} --pretty=format:"- %s (%h)" --grep="^fix" --no-merges
echo ""
echo "## 新功能 (feat)"
git log ${LAST_TAG}..${CURRENT_TAG} --pretty=format:"- %s (%h)" --grep="^feat" --no-merges
echo ""
echo "## 破坏性变更 (BREAKING CHANGE)"
git log ${LAST_TAG}..${CURRENT_TAG} --pretty=format:"- %b" --grep="BREAKING CHANGE" --no-merges
echo ""
echo "## 校验值"
echo "- Git Commit: $(git rev-parse HEAD)"
echo "- 编译器: arm-none-eabi-gcc $(arm-none-eabi-gcc --version | head -1)"
echo "- 编译日期: $(date '+%Y-%m-%d %H:%M:%S')"
echo "- MD5(IMU.hex): $(md5sum build/IMU.hex | cut -d' ' -f1)"
输出示例:
# Release Note: v1.2.3
## 硬件兼容性
- 支持硬件版本: HW-v2.1 (BOM: BOM_IMU_v2.1_20240415.xlsx)
- 最小Bootloader版本: v1.0.0
## 变更摘要
- [a1b2c3d] feat(imu): add temperature compensation (Li Si)
- [b2c3d4e] fix(can): correct FD bus-off recovery (Zhang San)
- [c3d4e5f] docs: update calibration procedure (Wang Wu)
## 缺陷修复 (fix)
- fix(can): correct FD bus-off recovery (b2c3d4e)
## 新功能 (feat)
- feat(imu): add temperature compensation (a1b2c3d)
## 破坏性变更 (BREAKING CHANGE)
- CAN FD data rate changed from 2Mbps to 5Mbps. Requires HW-v2.1 PCB.
## 校验值
- Git Commit: c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2
- 编译器: arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10.3-2021.10)
- 编译日期: 2024-04-22 14:30:00
- MD5(IMU.hex): a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
四、备份与容灾:代码库的"两地三中心"
4.1 裸仓库作为中央权威副本
**裸仓库(Bare Repository)**是没有工作区的纯Git数据库,适合作为"黄金副本"。
公司内网部署(Linux服务器/NAS):
# 在NAS上创建裸仓库目录
mkdir -p /volume1/git/imu.git
cd /volume1/git/imu.git
git init --bare --shared=group # 允许组内成员协作
# 设置hook限制(禁止直接push到main,强制MR流程)
cat > hooks/pre-receive << 'EOF'
#!/bin/bash
while read oldrev newrev refname; do
if [ "$refname" = "refs/heads/main" ]; then
echo "ERROR: Direct push to main is forbidden. Use Merge Request."
exit 1
fi
done
EOF
chmod +x hooks/pre-receive
开发者关联裸仓库:
# 添加裸仓库为远程(命名为nas或backup)
git remote add nas ssh://git@nas.local/volume1/git/imu.git
git remote add gitlab git@gitlab.company.com:firmware/imu.git
# 同时推送到两个远程
git push gitlab main
git push nas main
4.2 多远程策略与自动同步
三地备份架构:
GitLab主仓(生产环境)
↓ 自动同步(GitLab Mirror)
Gitee镜像(国内加速/备用)
↓ 定时任务(crontab/rsync)
本地NAS裸仓库(内网灾备)
自动同步脚本(部署在NAS或跳板机):
#!/bin/bash
# /opt/scripts/git_backup.sh
REPOS=("imu" "flight-control" "gps-module")
GITLAB_HOST="gitlab.company.com"
GITEA_HOST="gitea.company.com"
NAS_PATH="/volume1/git"
for repo in "${REPOS[@]}"; do
echo "Syncing $repo..."
# 确保裸仓库存在
if [ ! -d "$NAS_PATH/${repo}.git" ]; then
git clone --bare "git@${GITLAB_HOST}:firmware/${repo}.git" "$NAS_PATH/${repo}.git"
fi
cd "$NAS_PATH/${repo}.git"
# 拉取GitLab最新更新
git fetch origin
# 推送到Gitea镜像
git push gitea --all
git push gitea --tags
echo "$repo synced at $(date)"
done
# 日志记录
echo "Backup completed at $(date)" >> /var/log/git_backup.log
crontab定时任务:
# 每6小时同步一次
0 */6 * * * /opt/scripts/git_backup.sh >> /var/log/git_backup.log 2>&1
4.3 容灾切换手册
场景A:GitLab主仓宕机
# 1. 确认NAS备份最新
ssh nas.local "cd /volume1/git/imu.git && git log --oneline -1"
# 确认commit与本地一致
# 2. 切换远程到NAS
git remote set-url origin ssh://git@nas.local/volume1/git/imu.git
# 或临时添加
git remote add emergency ssh://git@nas.local/volume1/git/imu.git
# 3. 继续开发(推送到NAS)
git push emergency feature/can-fd
# 4. GitLab恢复后切回
git remote set-url origin git@gitlab.company.com:firmware/imu.git
git push origin feature/can-fd
场景B:笔记本损坏,新电脑恢复
# 从任意远程克隆(GitLab或NAS)
git clone --recursive git@gitlab.company.com:firmware/imu.git
# 或
git clone --recursive ssh://git@nas.local/volume1/git/imu.git
# 恢复个人分支(如果本地有未push的分支,只能从reflog找,所以日常要push!)
git fetch origin
git checkout -b feature/can-fd origin/feature/can-fd
五、阶段实战:建立生产级工作流
目标:为10人嵌入式团队建立完整的Git工程化规范。
Step 1:项目初始化
# 技术主管操作
git init IMU_Project
cd IMU_Project
# 创建核心配置文件
touch .gitignore
touch .gitattributes
touch .editorconfig
mkdir -p .gitlab/merge_request_templates
# 配置LFS
git lfs install
git lfs track "*.hex" "*.bin" "*.pdf" "*.PcbDoc" "*.SchDoc"
# 提交基础设施
git add .gitattributes .gitignore .editorconfig
git commit -m "chore: initialize project infrastructure
- Add .gitignore for Keil/STM32CubeIDE
- Add .gitattributes with LFS rules and LF enforcement
- Add .editorconfig for cross-platform consistency"
# 推送到GitLab
git remote add origin git@gitlab.company.com:firmware/imu.git
git push -u origin main
Step 2:保护分支设置
GitLab Settings → Repository → Protected Branches:
- main: Maintainer can merge, No one can push
- develop: Maintainer can merge, Developer can push (或也禁止,强制MR)
- release/*: Maintainer can merge, No one can push
GitLab Settings → CI/CD → General Pipelines:
- 开启Auto DevOps: 关闭(使用自定义.gitlab-ci.yml)
Step 3:CI/CD配置
# .gitlab-ci.yml(完整版)
stages:
- check
- build
- test
- release
variables:
GIT_SUBMODULE_STRATEGY: recursive
GIT_LFS_SKIP_SMUDGE: "false"
# 阶段1:代码规范检查
check_lint:
stage: check
image: alpine:latest
script:
- |
# 检查换行符违规
find . -name "*.c" -o -name "*.h" | xargs file | grep CRLF && exit 1 || true
- |
# 检查大文件入侵
git ls-tree -r -l HEAD | awk '$4 > 1048576 {print}' | grep -v "filter=lfs" && exit 1 || true
allow_failure: false
# 阶段2:编译
build:
stage: build
image: gcc-arm-none-eabi:latest
script:
- make VERSION=$CI_COMMIT_REF_NAME
artifacts:
paths:
- build/*.hex
- build/*.bin
- build/*.map
expire_in: 1 week
# 阶段3:静态分析(MISRA-C)
static_analysis:
stage: test
image: cppcheck:latest
script:
- cppcheck --enable=all --std=c99 --suppress=missingIncludeSystem Core/Src/
allow_failure: true
# 阶段4:发布(仅标签)
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
script:
- ./scripts/generate_changelog.sh > CHANGELOG.md
- release-cli create --name "IMU $CI_COMMIT_TAG" --tag-name "$CI_COMMIT_TAG" --description "$(cat CHANGELOG.md)"
only:
- tags
dependencies:
- build
Step 4:团队培训清单
- 安装Git并配置SSH
- 配置全局
core.autocrlf=false(依赖.gitattributes) - 安装Git LFS
- 克隆项目时使用
--recursive - 理解MR流程(禁止直接push到main/develop)
- 理解提交规范(
type(scope): subject+[HW-vX.X])
六、工程化安全守则
| 红线 | 后果 | 正确做法 |
|---|---|---|
| 源码仓(非LFS)提交.hex/.bin | 仓库体积指数膨胀,clone耗时,CI缓存失效 | 开发阶段:不提交编译产物;发布阶段:LFS或Artifactory |
依赖个人core.autocrlf设置 |
Windows开发者提交CRLF,Linux CI编译失败 | 仓库根目录强制.gitattributes,全局设置autocrlf=false |
手动修改version.h中的版本号 |
版本号与Git标签不一致,产线烧录错误固件 | Makefile自动生成版本号,从Git标签注入 |
| 单点仓库(仅GitLab,无备份) | 服务器宕机/误删项目导致开发停滞 | NAS裸仓库 + Gitee镜像,定时自动同步 |
| 打标签后未触发CI验证直接发布 | 标签对应的代码实际编译失败 | 仅允许CI通过后的标签作为Release,禁止本地手动编译发布 |
| Release Note手动编写 | 遗漏关键变更,硬件兼容性说明缺失 | generate_changelog.sh自动生成,人工审核补充 |
总结
第六阶段完成了Git从协作工具到工程基础设施的升级:
- LFS管控:
.gitattributes自动拦截大文件,配合CI体积监控防止仓库膨胀,LFS/子模块/Artifactory三层架构各司其职 - 跨平台一致性:
.gitattributes+.editorconfig+core.autocrlf=false三层防御,根治Windows/Linux混用的换行符灾难 - 标签即发布:Git标签触发CI自动生成
IMU_vXXX.hex,版本号从标签注入,Release Note从commit自动生成,实现"不可手动发布" - 多地容灾:GitLab主仓 + Gitee镜像 + NAS裸仓库的自动同步架构,确保单点故障时5分钟内切换
自检问题:
- 如果团队有成员使用MacBook(macOS)开发,另一位使用Windows(Keil),第三位在Linux服务器上跑CI,
.gitattributes应该如何配置才能确保三方换行符一致且Shell脚本不会崩溃? - Git LFS指针文件存储在Git仓库中,如果LFS服务器(如GitLab的LFS存储)损坏,仅保留Git仓库(含指针文件)是否足够恢复大文件内容?为什么?
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
分类:嵌入式开发 > 版本控制 > Git
标签:Git LFS;跨平台换行符;自动发布;固件版本管理;裸仓库备份;多远程容灾;嵌入式工程化;Release Note生成;CI/CD
更多推荐
所有评论(0)