Keil MDK工程使用Git实战:从.gitignore到分支管理

发布时间:2026/10/1 3:33:06
Keil MDK工程使用Git实战:从.gitignore到分支管理 Keil MDK 环境下用 Git其实没有你想的那么别扭先讲一个我亲眼见过很多次的场景项目文件夹里躺着一排最终版压缩包从project_v1.0.rar一路排到project_v12.3_backup_final_真最终版.rar。问就是怕改坏改一段代码先复制一份整个工程。这种玩法在裸机程序里勉强能撑一旦开始上 RTOS、加功能模块、或者两个人同时动一个工程用不了两个星期你连哪个压缩包是最新的都不敢确定。我第一次在 Keil MDK 工程里引入 Git 的时候想法很简单就想把代码恢复到三天前还能跑的版本不想再翻十几个压缩包了。后来用顺手了才发现 Git 带来的远不只是后悔药它还能告诉你这次功能是谁改的、改了什么、为什么这么改这些信息对嵌入式项目来说价值不亚于代码本身。这篇东西不打算从 Git 的基本概念开始啰嗦重点放在在 Keil MDK 这种环境下Git 到底怎么落地。包括哪些文件该入库、哪些文件死活不该入库、怎么配 Git 才能不天天处理垃圾 diff、多人协作时 MDK 工程文件冲突怎么解决。你跟着操作一遍基本就能把压缩包备份的习惯扔掉了。1. Keil 工程目录里那些动不动就变的文件到底都是干什么的很多人在 Keil 里用 Git 的第一反应是把整个工程文件夹全部提交进去一了百了。结果用了一天就崩溃了——每次编译完Git 状态里全是乱七八糟的改动到处都是红的绿的根本分不清哪些是真正改过的代码。这个问题的根子在于Keil MDK 的工程目录里天然混着三类完全不同的文件源码、工程配置、编译产物。你不把这三类分开Git 就没办法正常工作。1.1 工程文件全家福uvprojx、uvoptx、uvguix 和散落的输出目录一个典型的 Keil MDK 5 工程文件夹里通常会有这几类文件文件类型文件后缀作用是否适合入库主工程文件.uvprojx记录源文件列表、编译选项、芯片型号、宏定义适合这是工程的核心用户选项文件.uvoptx记录断点、窗口布局、仿真器设置不适合个人偏好太强GUI 布局文件.uvguix界面布局、上次打开的文件完全不适合编译输出目录Objects/Listings.o、.axf、.hex、.map、.lst绝对不适合分散加载文件.sct内存布局配置适合属于源码配置启动文件.s/.S芯片启动代码适合第三方库.lib/.a预编译库视情况而定这里要重点说下.uvoptx和.uvprojx的区别。.uvprojx是工程的核心配置哪个文件参与编译、用的什么芯片、宏定义是什么都记录在里面。这个文件必须入库否则别人克隆下来根本打不开工程。.uvoptx记录的则是你个人的调试习惯——哪里有断点、哪个文件在编辑器中是打开的、窗口怎么排布它对构建结果没有任何影响但对每个开发者来说又是不一样的。你今天设了个断点提交上去同事拉下来发现被打断在别人的断点上体验非常糟糕。我在实际配置 Git 的时候习惯把.uvoptx和.uvguix加进忽略列表只跟踪.uvprojx这样从仓库克隆下来的工程干干净净打开就是默认布局不会莫名奇妙多出一堆和代码无关的改动。1.2 所有的编译产物请一律拒之门外Objects 目录放的是编译过程中生成的.o文件、链接生成的.axf、.hex、.bin以及调试用的.map文件。这堆东西最大的特点是每一次编译哪怕只是改了文件里的一个字符串它们全都会变。如果你把这堆东西放进 Git每次编译完Git 都会给你显示几百个文件变更。你根本没法从 diff 里看出代码到底改了什么因为 99% 的变更来自编译产物。更麻烦的是这些二进制文件不管怎么提交Git 也没法帮你做合并或者语义化对比纯粹是往仓库里堆垃圾。仓库体积会迅速膨胀克隆速度变慢每个人都痛苦。所以数据输出的目录比如 Objects、Listings 这种直接从源头就别入库。有人说那我需要给别人发 hex 文件怎么办发编译产物的正确姿势是走发布渠道比如做成 release 附件或者单独的 firmware 仓库而不是混在源码仓库里。源码仓库只保存怎么从零把这个东西编译出来的信息这本身就是一个很好的团队约束——能从一个干净的环境把工程编译出来说明依赖配置是完整的、文档是够用的。2. 动手之前先花三分钟把 .gitignore 写对.gitignore 是 Git 的安检名单你告诉它哪些路径、哪些文件不要管它会自动忽略。对于 Keil 工程来说写一份好的 .gitignore 比 Git 十来个基础命令加起来还要重要。因为 Keil 这个 IDE 跟 Visual Studio 不一样它的用户配置文件跟工程文件是放在同一个目录下的不像 VS 有单独的 .vs 目录那么清晰分离。你不主动忽略它就会无孔不入地出现在你的提交记录里。2.1 一份我实测用了一整年的 Keil .gitignore 模板# Keil MDK 编译输出目录 Objects/ Listings/ **/Objects/ **/Listings/ # Keil 用户配置文件个人偏好不入库 *.uvguix *.uvoptx *.bak *.dep # J-Link / 调试器配置文件 *.jlink *.jscope *.svd # 编译过程中产生的临时文件 *.crf *.d *.o *.axf *.hex *.bin *.htm *.sct *.lnp *.clang # 自己项目里的临时目录 Debug/ Release/ build/细心的同学可能会问为什么把.sct也忽略掉这里必须说清楚如果你的分散加载文件是手写的、放在工程根目录下且参与了版本管理那它是源码不该忽略。但 Keil 5 默认生成的.sct经常是编译器自动生成的内容随芯片型号和内存配置变化属于工程生成的配置产物这时候忽略它是合理的。两种说法都见过我自己的原则是手写且需要多个工程复用的.sct入 Git编译器自动生成的那份忽略。你只要把上面这份模板放到工程根目录命名成.gitignore就行。Windows 下如果系统提示你需要提供文件名直接另存为.gitignore.多点一个点也能存下来存完后文件名会自动变成.gitignore。2.2 校验忽略规则的一个快速方法写完了 .gitignore最好当场验证一下别等到提交完才后悔。在 Git Bash 里执行git status --ignored --short这条命令会把当前目录下的未跟踪文件、已跟踪文件、被忽略文件的列表都列出来。你重点看有没有!!开头的条目比如!! Objects/、!! Listings/那就说明忽略规则生效了。如果某个文件本该出现在!!里却没有赶紧去改 .gitignore。这里还有一个常见坑如果某个文件在 .gitignore 规则写进去之前就已经被提交进仓库了Git 会优先尊重已跟踪状态哪怕你后来加了忽略规则这个文件照样会继续被跟踪。遇到这种情况要用下面的命令把它从仓库里拿掉但保留本地文件git rm -r --cached Objects/ git rm -r --cached Listings/加--cached的意思是只从 Git 的索引里删除不动磁盘上的实际文件。执行完后提交一次之后 .gitignore 的规则就会接管了。3. 本地仓库开张从 git init 到第一次提交写好了 .gitignore下一步就是正式初始化仓库。这里我就不从头讲安装 Git 了Windows 下 Git 的安装包直接去官网下就行。重点讲两件事安装选项里哪些值得注意以及初始化 Keil 工程仓库时的正确姿势。3.1 Windows 下 Git 安装最容易踩的两个选项Git for Windows 的安装向导里有几个选项对 Windows 用户影响很大。一个是默认编辑器默认可能是 Vim很多人进git commit不熟悉 Vim 就直接卡死满屏都是 please enter the commit message怎么退出都不知道。我的建议是在安装的时候默认编辑器直接选 Notepad 或者 VS Code如果装完了发现已经选了 Vim也不慌全局改一下就行git config --global core.editor code --wait另一个选项是行尾换行符的处理。Git 默认在 Windows 上做 checkout 的时候会把 LF 转成 CRLF提交时再转回 LF。这个机制对纯文本项目还算友好但 Keil MDK 的工程文件.uvprojx、.uvoptx是 XML 格式的换行符来回转换有时候会引起莫名其妙的 diff尤其是你用不同版本 MDK 打开过工程之后。我个人的习惯是把自动转换关掉统一用 LFgit config --global core.autocrlf false如果你跟同事协作这一步最好统一约定。否则一个人用默认的 CRLF 转换另一个人关了自动转换两个人改同一段代码Git 会给你整行整行地标红。为了这种系统层面的差异去浪费时间太不值了。3.2 Keil 工程仓库的初始化与第一次提交进入工程根目录也就是.uvprojx所在的那一层打开 Git Bash按顺序执行git init git add . git status我不建议一上来就git add . git commit一条龙。因为如果 .gitignore 没写对git status会立刻暴露问题——你会看到一堆 Objects 下的文件被加了进来。先git add .再git status看一遍到底加了什么确认没有混入编译产物再提交。第一次提交的信息我用的是这种格式git commit -m chore: 初始化项目仓库配置gitignore规则提交信息不要求多华丽但最好约定一个团队统一的风格。很多人在git commit -m里写得乱七八糟什么修改、更新、aaa过三个月回来看完全想不起来当时改了什么。我自己用约定式提交的格式前缀就几个feat新功能、fix修bug、docs文档、chore杂务Keil 工程的细节就写在正文里。不用太复杂但至少保证你在git log --oneline里扫一眼能看懂每次提交的意图。第一次提交完后这个仓库就在本地跑起来了。接下来你可以正常用 Git 的基本操作了改代码 →git add→git commit→git log看历史。但真正体现 Git 优势的还是要结合远程仓库来用。4. 远程仓库加进来GitHub、Gitee 与 SSH 密钥配置本地仓库能让你自己回退版本但人终究是社会性动物项目做大了总要把代码分享出去、备份出去、协作起来。这一步我们讲讲怎么把本地仓库推到远程。4.1 怎么选远程仓库选远程仓库这件事主要看你的代码能不能公开、以及团队习惯。搭私有仓库的话Gitee 和 GitHub 都有免费额度GitHub 的私有仓库个人用完全够但如果你在国内网络环境下推代码GitHub 偶尔会给你点颜色看看。Gitee 在国内的速度和稳定性要友好一些而且支持一键导入 GitHub 仓库我现在的嵌入式小项目基本都在 Gitee 上。不管选哪个流程都一样先在他们网页上新建一个空仓库不要勾选初始化 README因为那样会生成一个初始提交跟本地仓库的历史就对不上了然后把本地仓库跟远程关联起来。git remote add origin https://gitee.com/yourname/yourproject.git git push -u origin master如果你的默认分支是 main 而不是 masterpush 的时候对应调整一下。4.2 SSH 密钥配置省掉每次输密码的麻烦用 HTTPS 协议推送代码每次都要输账号密码很烦人。我强烈建议配一个 SSH 密钥。步骤非常简单ssh-keygen -t rsa -b 4096 -C your_emailexample.com一路回车密钥默认生成在用户目录的.ssh文件夹下一个私钥id_rsa一个公钥id_rsa.pub。然后把公钥内容复制到 Gitee 或 GitHub 的SSH 公钥设置里。之后把远程地址换成 SSH 格式的git remote set-url origin gitgitee.com:yourname/yourproject.git再执行git push就不需要每次输入账号密码了。我在多台电脑上配了同一份公钥换机器开发也没什么障碍。有一个小细节如果你公司或学校网络环境下 SSH 端口 22 被禁了GitHub 和 Gitee 都支持走 443 端口的 SSH 连接。GitHub 的配置方法是改~/.ssh/configHost github.com Hostname ssh.github.com Port 443 User gitGitee 也有类似的配置说明真遇到端口被禁的问题去官方文档查一下就能解决。别慌。5. Keil Git 日常操作流提交、分支、合并的实际姿势远程仓库配好之后最核心的问题就来了在日常 Keil 开发中Git 到底该怎么用才能既享受它的好处又不被它拖慢节奏5.1 一个推荐的单人开发工作流如果你是一个人开发嵌入式项目或者团队还处在各写各的模块阶段最简单高效的流程是这样的拿到需求从主分支开一个功能分支git checkout -b feature/add-sensor-driver。在 Keil 里正常写代码、编译、调试。每次编译通过、验证了一个关键节点就提交一次git add相关源文件git commit -m feat: 添加温度传感器驱动初始化。功能全部完成合并回主分支git checkout master git merge feature/add-sensor-driver。推送远程git push。有人会嫌开分支这件事麻烦觉得一个人开发不需要分支。但我的经验是哪怕是自己一个人写的裸机程序分支也是保护伞。你可以在feature分支上大胆尝试一个数据采集方案改的一塌糊涂也没关系最后觉得不行直接git branch -D feature/add-sensor-driver删掉分支主分支纹丝不动。这种安全感是复制一份工程再改完全给不了的。5.2 多人协作时Keil 工程文件的冲突处理多人协作的场景下最头疼的是.uvprojx这个文件。它是 XML 格式Keil 在保存工程文件的时候有自己的一套书写习惯节点排列顺序并不完全按照 ASCII 排序而是跟着工程树走的。这意味着两个人同时往工程里添加不同的源文件在.uvprojx里修改的位置可能挨得很近Git 合并的时候极容易冲突。一旦真冲突了你会看到类似这样的提示Auto-merging xxx.uvprojx CONFLICT (content): Merge conflict in xxx.uvprojx这时候不要慌分三步处理用文本编辑器打开冲突的.uvprojx文件找到所有 HEAD、、标记的地方。看清两边各自加了什么文件手动保留两边的内容。因为.uvprojx里一个文件节点通常就几行格式类似File FileNamegpio.c/FileName FileType1/FileType FilePath../Src/gpio.c/FilePath /File直接把两边冲突的File节点都拼进去就完成了合并。保存文件用 Keil 打开工程确认一下源文件列表正常然后提交合并结果。这里有个小技巧如果在合并前先看 Git 的输出你会发现.uvprojx的具体冲突位置。真的不想手工处理这种 XML 冲突备选方案是干脆约定好每次改动工程文件的人单独提交其他人在这个时间窗口内不要改工程文件或者改完手动同步一下。团队规则越清晰这类冲突越少。5.3 Keil 里也能直接操作 Git 的几种姿势嫌命令行不够方便Keil MDK 本身不内置 Git 客户端但有几种替代方案让 Git 操作贴近 Keil 的使用习惯TortoiseGit小乌龟在 Windows 资源管理器里右键就能操作 Git。Keil 里添加文件后切到文件夹里右键看变更特别直观。图标覆盖还能让你一眼看出哪个文件改过、哪个文件没提交。对刚接触 Git 的嵌入式工程师来说这个方案最容易上手。VS Code 的 Git 面板VS Code 打开 Keil 工程目录左边的源代码管理面板里能看 diff、暂存、提交、推送。我已经用这个组合很久了Keil 写代码VS Code 管 Git各干各擅长的事。Git Bash 命令行最原生最稳定但初期学习成本高一点。不管你用哪种姿势底层逻辑是一样的先用 Keil 写代码然后切到 Git 界面确认改动、提交、推送。选择哪个客户端纯看个人习惯不用在工具选择上纠结太久。6. 版本管理落地阶段的疑难杂症我踩过的坑和解决方案这一节专门讲我在 Keil Git 实际使用过程中踩过的、以及平时帮同事排查时遇到的各种问题。每一个都是真实场景每一个都值得你提前知道。6.1 fatal: not a git repository 的真相新手最常见的报错就是fatal: not a git repository (or any of the parent directories): .git遇到这个别急大概率不是你搞坏了什么而是你站错了目录。Git 命令必须在仓库的工作目录里执行也就是你执行过git init的那个目录层级或者它的子目录。如果你在工程文件夹my_project/里初始化了仓库但当前终端却停在了my_project/../上一层目录Git 找不到.git文件就会报这个错。解决方案简单粗暴先看一眼当前路径pwd然后cd进入仓库目录再执行命令。还有一种情况是仓库目录嵌套错了。有人把git init执行在了my_project的上层目录导致整个上级目录成了一个仓库。这时候你在my_project里面操作感觉一切正常但一旦在上级目录执行 Git 命令会意外把所有兄弟工程都纳入版本管理。排查方法是看git rev-parse --show-toplevel输出的仓库根目录是不是你期望的那个。6.2 Git 提交信息没写好怎么改不止一次有同事问我刚才 commit 信息写错了但已经提交了怎么改如果你只是最后一次提交的信息写错了且这个提交还没推送到远程直接git commit --amend -m 正确的提交信息这个命令会把当前暂存区的改动合并进上一次提交同时修改提交信息。但有几个前提必须确保这个提交还没被别人拉取过否则amend重写了历史别人再次拉取的时候会出现分叉麻烦很大。如果已经推送了就老老实实再提交一个修复别硬改历史。6.3 中文文件名乱码和路径显示问题Keil 工程里很多人习惯用中文给文件命名比如主程序.c这在 Git 里会遇到两个问题一是提交后文件名变成一串转义的八进制编码二是 Windows 默认的文件名编码跟 Git 期望的 UTF-8 不一致。第一个问题好解决设置一下 Git 关闭路径转义git config --global core.quotepath false这样git status和git log显示中文文件名就是正常的了。第二个问题稍微复杂一点。Windows 下的 Git 对中文文件名的支持跟文件系统编码有关。我的建议是如果工程还没有大量中文文件尽量改用英文字母命名源文件。不是崇洋媚外而是 Keil 本身对中文路径的支持就时好时坏编译器、链接器、调试器对中文路径的兼容性也参差不齐。如果团队确实需要中文命名至少统一编码别有人用 GBK、有人用 UTF-8那就彻底乱了。6.4 仓库体积越来越大克隆速度慢Keil 工程的仓库体积容易膨胀除了没忽略编译产物之外还有一个常见原因把第三方库比如 HAL 库、协议栈、GUI 库整个塞进了仓库。第三方库要不要入库这是个经典问题。我的看法是如果你的项目依赖的是某个固定版本的第三方库而这个库在网络上可以稳定下载到那就不入库在 README 里写清版本号用脚本去拉取特定版本。如果这个库很特殊或者你们改了库里的代码那就必须入库但要用 Git 的submodule机制单独管理别直接混在工程代码里。如果仓库已经膨胀了可以用git filter-branch或者 BFG Repo-Cleaner 把历史中的大文件剔除。对于还在用 Git 来存编译产物、后来想清理的人这条路走得通但过程比较绕也有点风险建议在操作之前先完完整整备份整个仓库目录。6.5 Keil 工程中调试配置与版本管理的边界Keil 的调试器配置分散在.uvoptx和 Keil 的全局配置里。我们已经把.uvoptx忽略了但团队协作中经常会遇到一个问题A 用 J-LinkB 用 ST-Link调试器的选择会不会跟工程配置冲突不会。.uvoptx被忽略后每个人用 Keil 打开工程调试器配置用的是本机当前的状态。你用自己的 J-Link 调试配置会写在你自己的.uvoptx里不会影响别人。只有工程级的调试选项比如 Debug Info 是否生成、优化等级这些写在.uvprojx里这部分全团队应该统一。所以版本管理的边界很清楚.uvprojx管的是构建配置必须统一.uvoptx管的是个人调试偏好应该私有。把边界划清了团队协作才不会为这些琐事吵架。7. 给嵌入式团队的两个额外建议tag 与 Git LFS最后再聊两个能明显提升体验但很多教程不会强调的功能。第一个是 tag。Keil 项目经常有发布固件版本这个动作比如给客户出个样机固件、送检版本、量产版本。用 Git 的 tag 来记录这些关键节点比压缩包命名可靠得多git tag -a v1.0.0 -m 量产版本支持南向设备2.0协议 git push origin v1.0.0以后任何一个时间点想回到这个版本的完整状态一行命令就搞定git checkout v1.0.0第二个是 Git LFSLarge File Storage。如果你的工程里涉及字体文件、音频资源、加密固件、图形素材这些二进制文件特别大而且每次修改都会产生巨大的 diff普通 Git 仓库会越来越难维护。Git LFS 是专门的解决方案它把大文件替换成一个小指针文件提交进仓库真正的文件内容存到 LFS 服务器上。Keil 工程里的第三方 UI 素材库、字库文件都适合用 LFS 管理。git lfs install git lfs track *.bin *.wav *.ttf执行之后.gitattributes文件里会自动记录这些规则提交后大文件就走 LFS 通道了。Git LFS 的代价是它依赖远程平台的支持GitHub 对 LFS 免费额度有 1GB 的限制Gitee 也有付费机制。如果只是偶尔传一两个固件包不用上 LFS普通 Git 也能接受但要是工程里高频更新编译产物或资源文件还是要认真评估一下。我在实际使用中的体会是Keil MDK 和 Git 的配合最大的阻力其实不是技术而是习惯。只要迈过不要整个文件夹全部提交不要把工程配置和个人偏好混在一起这两道坎后面就顺了。你可以在任何时间点回到之前任何一个能编译、能运行的里程碑团队里也没有人会用我明明改了当借口来掩盖版本丢失的问题。对这些常年工作在 Keil 工程里的工程师来说Git 带来的可能不是惊喜而是一种早就该这么干的自在感。