Git版本控制进阶:掌握高级技巧与工作流¶
概述¶
在掌握了Git基础操作后,进一步学习Git的高级功能将大幅提升你的开发效率和团队协作能力。本教程将深入探讨Git的高级特性,包括变基、交互式操作、高级分支策略、子模块管理等内容。
为什么需要学习Git进阶?¶
基础操作的局限: - ❌ 提交历史混乱,难以追踪 - ❌ 分支管理不规范,冲突频繁 - ❌ 无法高效处理复杂的合并场景 - ❌ 团队协作效率低下 - ❌ 缺乏自动化和工作流优化
进阶技能的优势: - ✅ 保持清晰的提交历史 - ✅ 灵活处理分支和合并 - ✅ 高效解决复杂问题 - ✅ 优化团队协作流程 - ✅ 自动化重复性任务 - ✅ 深入理解Git内部机制
学习目标¶
完成本教程后,你将能够:
- 熟练使用rebase进行分支整理
- 掌握交互式rebase修改历史
- 使用cherry-pick精确选择提交
- 理解并应用高级分支策略
- 管理Git子模块和子树
- 使用Git钩子自动化任务
- 优化Git性能和仓库大小
- 处理复杂的合并和冲突场景
Git Rebase详解¶
Rebase vs Merge¶
理解rebase和merge的区别是掌握Git进阶的关键。
Merge合并:
gitGraph
commit id: "A"
commit id: "B"
branch feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
merge feature id: "M"
Rebase变基:
gitGraph
commit id: "A"
commit id: "B"
commit id: "E"
commit id: "C'"
commit id: "D'"
对比分析:
| 特性 | Merge | Rebase |
|---|---|---|
| 历史记录 | 保留完整历史 | 线性历史 |
| 提交图 | 有分支合并点 | 直线型 |
| 冲突处理 | 一次性解决 | 逐个提交解决 |
| 安全性 | 安全,不改写历史 | 改写历史,需谨慎 |
| 适用场景 | 公共分支 | 本地分支整理 |
基本Rebase操作¶
场景: 将feature分支变基到main分支
执行过程: 1. Git找到feature和main的共同祖先 2. 提取feature分支上的所有提交 3. 将feature分支指向main的最新提交 4. 依次应用之前提取的提交
示例:
# 初始状态
# main: A - B - E
# feature: A - B - C - D
git checkout feature
git rebase main
# 结果
# main: A - B - E
# feature: A - B - E - C' - D'
处理Rebase冲突¶
在rebase过程中遇到冲突时:
# 1. Git会暂停并提示冲突
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
error: could not apply a1b2c3d... feat: 添加新功能
# 2. 查看冲突文件
git status
# 3. 编辑冲突文件,解决冲突
# 编辑src/main.c,删除冲突标记
# 4. 标记冲突已解决
git add src/main.c
# 5. 继续rebase
git rebase --continue
# 如果想放弃rebase
git rebase --abort
# 如果想跳过当前提交
git rebase --skip
Rebase最佳实践¶
黄金法则: 永远不要rebase已经推送到公共仓库的提交!
推荐使用场景: 1. ✅ 整理本地分支的提交历史 2. ✅ 在推送前清理提交 3. ✅ 保持feature分支与main同步 4. ✅ 创建清晰的线性历史
避免使用场景: 1. ❌ 已推送到远程的分支 2. ❌ 多人协作的公共分支 3. ❌ 已被其他分支依赖的提交 4. ❌ 不熟悉rebase时的重要分支
实用技巧:
# 在pull时使用rebase
git pull --rebase origin main
# 配置默认使用rebase
git config --global pull.rebase true
# 保留合并提交的rebase
git rebase --preserve-merges main
# 交互式rebase(后面详细介绍)
git rebase -i HEAD~3
交互式Rebase¶
交互式rebase是Git最强大的功能之一,允许你修改、重排、合并、删除提交。
启动交互式Rebase¶
# 修改最近3次提交
git rebase -i HEAD~3
# 修改从某个提交开始的所有提交
git rebase -i <commit-hash>
# 修改到根提交
git rebase -i --root
交互式Rebase命令¶
执行后会打开编辑器,显示类似内容:
pick a1b2c3d feat: 添加GPIO驱动
pick d4e5f6g fix: 修复时钟配置
pick h7i8j9k docs: 更新README
# Rebase commands:
# p, pick = 使用提交
# r, reword = 使用提交,但修改提交信息
# e, edit = 使用提交,但停下来修改
# s, squash = 使用提交,但合并到前一个提交
# f, fixup = 类似squash,但丢弃提交信息
# x, exec = 运行shell命令
# d, drop = 删除提交
命令说明:
| 命令 | 简写 | 作用 |
|---|---|---|
| pick | p | 保留提交 |
| reword | r | 修改提交信息 |
| edit | e | 修改提交内容 |
| squash | s | 合并到前一个提交,保留信息 |
| fixup | f | 合并到前一个提交,丢弃信息 |
| exec | x | 执行shell命令 |
| drop | d | 删除提交 |
实用场景¶
场景1: 修改提交信息¶
# 启动交互式rebase
git rebase -i HEAD~3
# 将pick改为reword
reword a1b2c3d feat: 添加GPIO驱动
pick d4e5f6g fix: 修复时钟配置
pick h7i8j9k docs: 更新README
# 保存后会打开编辑器让你修改提交信息
场景2: 合并多个提交¶
# 启动交互式rebase
git rebase -i HEAD~3
# 将后续提交合并到第一个
pick a1b2c3d feat: 添加GPIO驱动
squash d4e5f6g feat: 完善GPIO驱动
squash h7i8j9k feat: 添加GPIO测试
# 保存后会打开编辑器让你编辑合并后的提交信息
结果: 三个提交合并为一个
场景3: 拆分提交¶
# 启动交互式rebase
git rebase -i HEAD~3
# 将要拆分的提交标记为edit
pick a1b2c3d feat: 添加GPIO驱动
edit d4e5f6g feat: 添加多个功能 # 这个提交包含太多内容
pick h7i8j9k docs: 更新README
# 保存后Git会停在标记为edit的提交
# 撤销这个提交但保留修改
git reset HEAD~1
# 分别提交不同的修改
git add src/gpio.c
git commit -m "feat: 添加GPIO驱动"
git add src/uart.c
git commit -m "feat: 添加UART驱动"
# 继续rebase
git rebase --continue
场景4: 重排提交顺序¶
# 启动交互式rebase
git rebase -i HEAD~3
# 调整提交顺序
pick h7i8j9k docs: 更新README
pick a1b2c3d feat: 添加GPIO驱动
pick d4e5f6g fix: 修复时钟配置
# 保存即可
场景5: 删除提交¶
# 启动交互式rebase
git rebase -i HEAD~3
# 删除不需要的提交
pick a1b2c3d feat: 添加GPIO驱动
drop d4e5f6g test: 临时测试代码 # 删除这个提交
pick h7i8j9k docs: 更新README
# 或者直接删除该行
场景6: 在每个提交后执行测试¶
# 启动交互式rebase
git rebase -i HEAD~3
# 在每个提交后运行测试
pick a1b2c3d feat: 添加GPIO驱动
exec make test
pick d4e5f6g fix: 修复时钟配置
exec make test
pick h7i8j9k docs: 更新README
exec make test
# 如果测试失败,rebase会停止
交互式Rebase注意事项¶
安全提示:
1. 只修改本地未推送的提交
2. 修改前创建备份分支: git branch backup
3. 如果出错,使用reflog恢复: git reflog
4. 团队协作时谨慎使用
常见错误:
# 错误: 修改了已推送的提交
# 解决: 使用git push -f(仅限个人分支)
git push -f origin feature-branch
# 错误: rebase过程中迷失方向
# 解决: 放弃rebase重新开始
git rebase --abort
# 错误: 不小心删除了重要提交
# 解决: 使用reflog找回
git reflog
git cherry-pick <commit-hash>
Cherry-Pick精确选择¶
Cherry-pick允许你选择特定的提交应用到当前分支。
基本用法¶
# 应用单个提交
git cherry-pick <commit-hash>
# 应用多个提交
git cherry-pick <commit1> <commit2> <commit3>
# 应用提交范围(不包含start)
git cherry-pick <start-commit>..<end-commit>
# 应用提交范围(包含start)
git cherry-pick <start-commit>^..<end-commit>
实用场景¶
场景1: 将bug修复应用到多个分支¶
# 在develop分支修复了bug
git checkout develop
git commit -m "fix: 修复内存泄漏问题" # commit: a1b2c3d
# 将修复应用到release分支
git checkout release/1.0
git cherry-pick a1b2c3d
# 将修复应用到main分支
git checkout main
git cherry-pick a1b2c3d
场景2: 从错误的分支提取提交¶
# 不小心在main分支开发了新功能
git checkout main
git commit -m "feat: 新功能" # commit: d4e5f6g
# 创建正确的feature分支
git checkout -b feature/new-feature main~1
# 应用那个提交
git cherry-pick d4e5f6g
# 回到main分支撤销错误的提交
git checkout main
git reset --hard HEAD~1
场景3: 选择性合并¶
Cherry-Pick选项¶
# 应用提交但不自动提交(可以修改)
git cherry-pick -n <commit-hash>
git cherry-pick --no-commit <commit-hash>
# 仅应用提交,不保留原作者信息
git cherry-pick -x <commit-hash>
# 编辑提交信息
git cherry-pick -e <commit-hash>
git cherry-pick --edit <commit-hash>
# 保留原提交的作者日期
git cherry-pick --keep-redundant-commits <commit-hash>
处理Cherry-Pick冲突¶
# 1. 执行cherry-pick
git cherry-pick a1b2c3d
# 2. 如果有冲突,Git会提示
error: could not apply a1b2c3d... feat: 添加新功能
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
# 3. 解决冲突
# 编辑冲突文件
# 4. 标记已解决
git add <resolved-files>
# 5. 继续cherry-pick
git cherry-pick --continue
# 或者放弃
git cherry-pick --abort
Cherry-Pick最佳实践¶
适用场景: - ✅ 将bug修复应用到多个分支 - ✅ 从废弃分支提取有用的提交 - ✅ 选择性合并部分功能 - ✅ 紧急修复需要快速部署
注意事项: - ⚠️ Cherry-pick会创建新的提交(不同的hash) - ⚠️ 可能导致重复的修改 - ⚠️ 不适合大量提交的迁移 - ⚠️ 注意依赖关系,可能需要多个提交
高级分支策略¶
Git Flow详解¶
Git Flow是一套完整的分支管理规范。
分支类型:
- main: 生产环境代码
- develop: 开发主分支
- feature/*: 功能开发分支
- release/*: 发布准备分支
- hotfix/*: 紧急修复分支
完整流程:
gitGraph
commit id: "初始"
branch develop
checkout develop
commit id: "开发1"
branch feature/uart
checkout feature/uart
commit id: "UART开发"
commit id: "UART完成"
checkout develop
merge feature/uart
commit id: "集成"
branch release/1.0
checkout release/1.0
commit id: "准备发布"
commit id: "修复bug"
checkout main
merge release/1.0 tag: "v1.0"
checkout develop
merge release/1.0
checkout main
branch hotfix/1.0.1
commit id: "紧急修复"
checkout main
merge hotfix/1.0.1 tag: "v1.0.1"
checkout develop
merge hotfix/1.0.1
详细操作:
# 1. 初始化Git Flow
git flow init
# 2. 开始新功能
git flow feature start uart-driver
# 等价于
git checkout -b feature/uart-driver develop
# 3. 完成功能
git flow feature finish uart-driver
# 等价于
git checkout develop
git merge --no-ff feature/uart-driver
git branch -d feature/uart-driver
# 4. 开始发布
git flow release start 1.0.0
# 等价于
git checkout -b release/1.0.0 develop
# 5. 完成发布
git flow release finish 1.0.0
# 等价于
git checkout main
git merge --no-ff release/1.0.0
git tag -a v1.0.0
git checkout develop
git merge --no-ff release/1.0.0
git branch -d release/1.0.0
# 6. 开始热修复
git flow hotfix start 1.0.1
# 等价于
git checkout -b hotfix/1.0.1 main
# 7. 完成热修复
git flow hotfix finish 1.0.1
# 等价于
git checkout main
git merge --no-ff hotfix/1.0.1
git tag -a v1.0.1
git checkout develop
git merge --no-ff hotfix/1.0.1
git branch -d hotfix/1.0.1
GitHub Flow¶
GitHub Flow是一种更简单的工作流,适合持续部署。
核心原则: 1. main分支始终可部署 2. 从main创建描述性分支 3. 定期推送到远程 4. 通过Pull Request合并 5. 合并后立即部署
操作流程:
# 1. 创建功能分支
git checkout -b feature/add-sensor main
# 2. 开发并提交
git add .
git commit -m "feat: 添加温度传感器"
# 3. 推送到远程
git push -u origin feature/add-sensor
# 4. 在GitHub上创建Pull Request
# 5. 代码审查和讨论
# 6. 合并到main(在GitHub上点击Merge)
# 7. 部署到生产环境
# 8. 删除功能分支
git branch -d feature/add-sensor
git push origin --delete feature/add-sensor
Trunk-Based Development¶
主干开发是一种快速迭代的开发模式。
核心思想: - 所有开发者在main分支上工作 - 使用短生命周期的分支(1-2天) - 频繁集成到main - 使用特性开关控制功能发布
操作方式:
# 1. 从main创建短期分支
git checkout -b task/add-feature main
# 2. 快速开发(1-2天内完成)
git add .
git commit -m "feat: 添加新功能"
# 3. 及时合并回main
git checkout main
git pull --rebase origin main
git merge task/add-feature
git push origin main
# 4. 删除短期分支
git branch -d task/add-feature
特性开关示例:
// feature_flags.h
#define FEATURE_NEW_SENSOR_ENABLED 0
// main.c
#if FEATURE_NEW_SENSOR_ENABLED
init_new_sensor();
#endif
分支策略对比¶
| 策略 | 复杂度 | 适用场景 | 发布频率 |
|---|---|---|---|
| Git Flow | 高 | 定期发布的产品 | 周/月 |
| GitHub Flow | 中 | 持续部署的Web应用 | 天 |
| Trunk-Based | 低 | 快速迭代的项目 | 小时/天 |
选择建议: - 嵌入式固件: Git Flow(需要严格的版本管理) - Web后端: GitHub Flow(持续部署) - 敏捷团队: Trunk-Based(快速迭代)
Git子模块管理¶
子模块允许你在Git仓库中包含其他Git仓库。
添加子模块¶
# 添加子模块
git submodule add https://github.com/user/library.git libs/library
# 添加到指定目录
git submodule add https://github.com/user/hal.git drivers/hal
# 添加特定分支
git submodule add -b develop https://github.com/user/lib.git libs/lib
结果:
- 创建.gitmodules文件
- 在指定目录创建子模块
- 子模块作为特殊的提交记录
.gitmodules文件:
[submodule "libs/library"]
path = libs/library
url = https://github.com/user/library.git
branch = main
克隆包含子模块的仓库¶
# 方法1: 克隆时初始化子模块
git clone --recursive https://github.com/user/project.git
# 方法2: 克隆后初始化子模块
git clone https://github.com/user/project.git
cd project
git submodule init
git submodule update
# 或者合并为一条命令
git submodule update --init --recursive
更新子模块¶
# 更新所有子模块到最新提交
git submodule update --remote
# 更新特定子模块
git submodule update --remote libs/library
# 更新并合并
git submodule update --remote --merge
# 更新并变基
git submodule update --remote --rebase
在子模块中工作¶
# 进入子模块目录
cd libs/library
# 子模块是一个完整的Git仓库
git checkout main
git pull origin main
# 进行修改
git add .
git commit -m "fix: 修复bug"
git push origin main
# 返回主项目
cd ../..
# 提交子模块的更新
git add libs/library
git commit -m "chore: 更新library子模块"
git push origin main
删除子模块¶
# 1. 删除子模块配置
git submodule deinit libs/library
# 2. 删除子模块目录
git rm libs/library
# 3. 删除.git/modules中的子模块
rm -rf .git/modules/libs/library
# 4. 提交更改
git commit -m "chore: 移除library子模块"
子模块最佳实践¶
优点: - ✅ 明确的依赖版本 - ✅ 独立的版本控制 - ✅ 可以单独更新
缺点: - ❌ 操作复杂 - ❌ 容易出错 - ❌ 团队成员需要理解子模块
建议: 1. 为子模块创建文档 2. 使用自动化脚本初始化 3. 定期更新子模块 4. 考虑使用Git Subtree作为替代
Git Subtree¶
Git Subtree是子模块的替代方案,更简单但有不同的权衡。
添加Subtree¶
# 添加远程仓库
git remote add library-remote https://github.com/user/library.git
# 添加subtree
git subtree add --prefix=libs/library library-remote main --squash
更新Subtree¶
推送到Subtree¶
Subtree vs Submodule¶
| 特性 | Submodule | Subtree |
|---|---|---|
| 复杂度 | 高 | 低 |
| 历史记录 | 独立 | 合并 |
| 克隆 | 需要额外步骤 | 自动包含 |
| 更新 | 复杂 | 简单 |
| 推送修改 | 简单 | 复杂 |
| 仓库大小 | 小 | 大 |
选择建议: - 需要频繁修改依赖: Submodule - 只读依赖: Subtree - 团队不熟悉Git: Subtree
Git钩子(Hooks)¶
Git钩子是在特定Git事件发生时自动执行的脚本。
钩子类型¶
客户端钩子:
- pre-commit: 提交前执行
- prepare-commit-msg: 准备提交信息时
- commit-msg: 提交信息编辑后
- post-commit: 提交后执行
- pre-push: 推送前执行
- pre-rebase: 变基前执行
服务器端钩子:
- pre-receive: 接收推送前
- update: 更新引用前
- post-receive: 接收推送后
创建钩子¶
钩子脚本位于.git/hooks/目录。
示例1: pre-commit检查代码格式¶
#!/bin/bash
# .git/hooks/pre-commit
echo "运行代码格式检查..."
# 检查C代码格式
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.c$')$')
if [ -n "$files" ]; then
for file in $files; do
# 使用clang-format检查
clang-format -style=file "$file" | diff -u "$file" - > /dev/null
if [ $? -ne 0 ]; then
echo "错误: $file 格式不正确"
echo "请运行: clang-format -i $file"
exit 1
fi
done
fi
echo "代码格式检查通过"
exit 0
使钩子可执行:
示例2: commit-msg检查提交信息¶
#!/bin/bash
# .git/hooks/commit-msg
commit_msg_file=$1
commit_msg=$(cat "$commit_msg_file")
# 检查提交信息格式: type(scope): subject
pattern="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}"
if ! echo "$commit_msg" | grep -qE "$pattern"; then
echo "错误: 提交信息格式不正确"
echo "格式应为: type(scope): subject"
echo "类型: feat, fix, docs, style, refactor, test, chore"
echo "示例: feat(gpio): 添加GPIO驱动"
exit 1
fi
echo "提交信息格式正确"
exit 0
示例3: pre-push运行测试¶
#!/bin/bash
# .git/hooks/pre-push
echo "运行测试..."
# 运行单元测试
make test
if [ $? -ne 0 ]; then
echo "错误: 测试失败,推送被阻止"
exit 1
fi
echo "测试通过,允许推送"
exit 0
共享钩子¶
由于.git/hooks不被Git跟踪,需要其他方式共享钩子。
方法1: 使用脚本目录
# 项目结构
project/
├── .git/
├── scripts/
│ └── hooks/
│ ├── pre-commit
│ └── commit-msg
└── setup-hooks.sh
# setup-hooks.sh
#!/bin/bash
ln -sf ../../scripts/hooks/pre-commit .git/hooks/pre-commit
ln -sf ../../scripts/hooks/commit-msg .git/hooks/commit-msg
chmod +x .git/hooks/*
方法2: 使用Git配置
钩子最佳实践¶
设计原则: 1. 快速执行(< 5秒) 2. 提供清晰的错误信息 3. 可以跳过(使用--no-verify) 4. 幂等性(多次执行结果相同)
实用技巧:
Git Reflog恢复¶
Reflog记录了HEAD和分支引用的变化历史,是恢复丢失提交的救命稻草。
查看Reflog¶
输出示例:
a1b2c3d HEAD@{0}: commit: feat: 添加新功能
d4e5f6g HEAD@{1}: rebase finished: returning to refs/heads/feature
h7i8j9k HEAD@{2}: rebase: feat: 修改功能
k1l2m3n HEAD@{3}: checkout: moving from main to feature
恢复场景¶
场景1: 恢复被删除的分支¶
# 不小心删除了分支
git branch -D feature-important
# 查找分支最后的提交
git reflog
# 恢复分支
git branch feature-important a1b2c3d
场景2: 恢复reset丢失的提交¶
场景3: 恢复rebase前的状态¶
Reflog配置¶
# 设置reflog保留时间(默认90天)
git config gc.reflogExpire 180.days
# 设置未引用对象的reflog保留时间(默认30天)
git config gc.reflogExpireUnreachable 60.days
Git性能优化¶
仓库维护¶
浅克隆¶
# 只克隆最近的提交
git clone --depth 1 https://github.com/user/repo.git
# 克隆最近10次提交
git clone --depth 10 https://github.com/user/repo.git
# 获取完整历史
git fetch --unshallow
部分克隆¶
# 只克隆特定分支
git clone --single-branch --branch main https://github.com/user/repo.git
# 稀疏检出(只检出部分文件)
git clone --filter=blob:none --sparse https://github.com/user/repo.git
cd repo
git sparse-checkout init --cone
git sparse-checkout set src/ docs/
大文件处理¶
使用Git LFS:
# 安装Git LFS
git lfs install
# 跟踪大文件
git lfs track "*.bin"
git lfs track "*.hex"
# 查看跟踪的文件
git lfs ls-files
# 提交
git add .gitattributes
git add firmware.bin
git commit -m "添加固件文件"
.gitattributes示例:
*.bin filter=lfs diff=lfs merge=lfs -text
*.hex filter=lfs diff=lfs merge=lfs -text
*.elf filter=lfs diff=lfs merge=lfs -text
清理历史中的大文件¶
# 使用BFG Repo-Cleaner
# 下载: https://rtyley.github.io/bfg-repo-cleaner/
# 删除大于10MB的文件
bfg --strip-blobs-bigger-than 10M repo.git
# 删除特定文件
bfg --delete-files firmware.bin repo.git
# 清理
cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
高级合并策略¶
合并策略选项¶
# 使用ours策略(保留当前分支)
git merge -s ours feature-branch
# 使用theirs策略(Git 2.20+)
git merge -X theirs feature-branch
# 递归策略(默认)
git merge -s recursive feature-branch
# 忽略空白字符
git merge -Xignore-space-change feature-branch
# 耐心合并算法
git merge -Xpatience feature-branch
三方合并¶
合并冲突高级处理¶
# 查看冲突的不同版本
git show :1:file.c # 共同祖先版本
git show :2:file.c # 当前分支版本
git show :3:file.c # 要合并的分支版本
# 选择特定版本
git checkout --ours file.c # 使用当前分支版本
git checkout --theirs file.c # 使用要合并的分支版本
# 使用合并工具
git mergetool
# 配置合并工具
git config --global merge.tool vimdiff
git config --global mergetool.prompt false
Git别名和配置优化¶
实用别名¶
# 添加到~/.gitconfig
[alias]
# 简短命令
st = status -sb
co = checkout
br = branch
ci = commit
unstage = reset HEAD
# 日志相关
lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
last = log -1 HEAD
hist = log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
# 差异相关
df = diff --color --color-words --abbrev
dfs = diff --staged
# 分支相关
branches = branch -a
tags = tag -l
remotes = remote -v
# 撤销相关
undo = reset --soft HEAD~1
amend = commit --amend --no-edit
# 清理相关
cleanup = "!git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d"
# 查找相关
find = "!git ls-files | grep -i"
grep = grep -Ii
# 贡献统计
contributors = shortlog -sn
# 工作进度
save = stash save
pop = stash pop
# 同步
sync = !git fetch --all && git pull --rebase
全局配置优化¶
# 用户信息
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
# 编辑器
git config --global core.editor "vim"
# 差异工具
git config --global diff.tool vimdiff
# 合并工具
git config --global merge.tool vimdiff
# 颜色输出
git config --global color.ui auto
# 默认分支名
git config --global init.defaultBranch main
# Pull策略
git config --global pull.rebase true
# 推送策略
git config --global push.default current
# 自动修正拼写错误
git config --global help.autocorrect 1
# 忽略文件权限变化
git config --global core.fileMode false
# 处理换行符(Windows)
git config --global core.autocrlf true
# 处理换行符(Linux/Mac)
git config --global core.autocrlf input
Git工作流最佳实践¶
提交信息规范¶
Conventional Commits格式:
类型(type):
- feat: 新功能
- fix: Bug修复
- docs: 文档更新
- style: 代码格式(不影响功能)
- refactor: 重构
- perf: 性能优化
- test: 测试相关
- chore: 构建/工具相关
示例:
分支命名规范¶
feature/功能名称 # 新功能
bugfix/问题描述 # Bug修复
hotfix/紧急修复 # 紧急修复
release/版本号 # 发布分支
docs/文档更新 # 文档
refactor/重构内容 # 重构
test/测试内容 # 测试
示例:
feature/add-uart-driver
bugfix/fix-memory-leak
hotfix/critical-security-fix
release/v1.2.0
docs/update-api-reference
提交频率建议¶
推荐: - ✅ 完成一个逻辑单元就提交 - ✅ 每个提交都能通过编译 - ✅ 每个提交都有清晰的目的 - ✅ 一天至少提交一次
避免: - ❌ 一次提交包含多个不相关的修改 - ❌ 提交无法编译的代码 - ❌ 提交信息含糊不清 - ❌ 长时间不提交积累大量修改
代码审查流程¶
Pull Request最佳实践:
- 创建PR前:
- 确保代码通过所有测试
- 更新相关文档
- 自我审查代码
-
保持PR小而专注
-
PR描述:
-
审查清单:
- 代码符合编码规范
- 有适当的注释
- 有相应的测试
- 文档已更新
- 没有明显的性能问题
- 没有安全隐患
常见问题与解决方案¶
问题1: 如何撤销已推送的提交?¶
场景: 不小心推送了错误的提交到远程仓库。
解决方案:
# 方法1: 使用revert(推荐,安全)
git revert <commit-hash>
git push origin main
# 方法2: 使用reset(仅限个人分支)
git reset --hard HEAD~1
git push -f origin feature-branch
# 方法3: 使用rebase(仅限个人分支)
git rebase -i HEAD~3
# 删除错误的提交
git push -f origin feature-branch
问题2: 如何合并多个提交?¶
场景: 本地有很多小提交,想合并后再推送。
解决方案:
# 方法1: 交互式rebase
git rebase -i HEAD~5
# 将后续提交标记为squash
# 方法2: 软重置后重新提交
git reset --soft HEAD~5
git commit -m "feat: 完整功能实现"
# 方法3: 合并时使用squash
git merge --squash feature-branch
git commit -m "feat: 合并功能分支"
问题3: 如何处理大量冲突?¶
场景: 合并时遇到大量冲突文件。
解决方案:
# 1. 使用合并工具
git mergetool
# 2. 分步解决
git merge --no-commit feature-branch
# 逐个解决冲突文件
git add <resolved-file>
# 继续解决下一个
# 3. 选择一方的修改
git checkout --ours . # 保留当前分支
git checkout --theirs . # 使用要合并的分支
# 4. 如果冲突太多,考虑重新规划合并策略
git merge --abort
# 尝试先合并部分提交
问题4: 如何找回删除的文件?¶
场景: 不小心删除了重要文件。
解决方案:
# 1. 如果还没提交
git checkout HEAD <file>
# 2. 如果已经提交
git log --all --full-history -- <file>
git checkout <commit-hash> -- <file>
# 3. 使用reflog
git reflog
git checkout <commit-hash> -- <file>
# 4. 恢复整个目录
git checkout <commit-hash> -- <directory>/
问题5: 如何清理本地分支?¶
场景: 本地有很多已合并的分支。
解决方案:
# 查看已合并的分支
git branch --merged
# 删除已合并的分支(排除main和develop)
git branch --merged | grep -v "\*\|main\|develop" | xargs -n 1 git branch -d
# 删除远程已删除的本地跟踪分支
git fetch --prune
# 查看远程分支状态
git remote prune origin --dry-run
# 清理远程分支
git remote prune origin
问题6: 如何处理detached HEAD?¶
场景: 检出了特定提交,处于detached HEAD状态。
解决方案:
# 1. 如果想保留修改
git branch temp-branch
git checkout main
git merge temp-branch
# 2. 如果不想保留修改
git checkout main
# 3. 如果已经提交了修改
git branch new-feature
git checkout new-feature
实战演练¶
演练1: 完整的功能开发流程¶
# 1. 更新main分支
git checkout main
git pull origin main
# 2. 创建功能分支
git checkout -b feature/add-spi-driver
# 3. 开发功能
# 编辑文件...
git add src/spi.c src/spi.h
git commit -m "feat(spi): 添加SPI基础驱动"
# 继续开发...
git add src/spi.c
git commit -m "feat(spi): 添加DMA支持"
# 4. 整理提交历史
git rebase -i HEAD~2
# 如果需要,合并提交
# 5. 同步main分支的更新
git fetch origin main
git rebase origin/main
# 6. 推送到远程
git push -u origin feature/add-spi-driver
# 7. 创建Pull Request(在GitHub/GitLab上)
# 8. 代码审查通过后,合并到main
# 9. 清理本地分支
git checkout main
git pull origin main
git branch -d feature/add-spi-driver
演练2: 紧急修复流程¶
# 1. 从main创建hotfix分支
git checkout main
git pull origin main
git checkout -b hotfix/fix-critical-bug
# 2. 修复bug
# 编辑文件...
git add src/critical.c
git commit -m "fix: 修复严重内存泄漏"
# 3. 测试修复
make test
# 4. 推送并合并到main
git push -u origin hotfix/fix-critical-bug
# 创建PR并快速审查
# 5. 合并到main后,也合并到develop
git checkout develop
git pull origin develop
git merge hotfix/fix-critical-bug
git push origin develop
# 6. 清理分支
git branch -d hotfix/fix-critical-bug
git push origin --delete hotfix/fix-critical-bug
演练3: 处理复杂冲突¶
# 1. 尝试合并
git checkout main
git merge feature/complex-feature
# 2. 发现冲突
# CONFLICT (content): Merge conflict in src/main.c
# 3. 查看冲突
git status
git diff
# 4. 使用合并工具
git mergetool
# 5. 或手动解决
# 编辑src/main.c,删除冲突标记
# <<<<<<< HEAD
# 当前分支的代码
# =======
# 要合并分支的代码
# >>>>>>> feature/complex-feature
# 6. 标记已解决
git add src/main.c
# 7. 完成合并
git commit
# 8. 验证
make test
进阶技巧总结¶
Git命令速查表¶
基础操作:
git init # 初始化仓库
git clone <url> # 克隆仓库
git add <file> # 添加到暂存区
git commit -m "message" # 提交
git push origin main # 推送
git pull origin main # 拉取
分支操作:
git branch # 查看分支
git branch <name> # 创建分支
git checkout <name> # 切换分支
git checkout -b <name> # 创建并切换
git merge <branch> # 合并分支
git branch -d <name> # 删除分支
历史操作:
git log # 查看历史
git log --graph # 图形化历史
git reflog # 查看引用日志
git reset --hard <commit> # 重置到指定提交
git revert <commit> # 撤销提交
git cherry-pick <commit> # 应用提交
高级操作:
git rebase main # 变基
git rebase -i HEAD~3 # 交互式变基
git stash # 暂存修改
git stash pop # 恢复暂存
git submodule add <url> # 添加子模块
git submodule update # 更新子模块
学习资源¶
交互式学习: - Learn Git Branching - Git Immersion
视频教程: - Git and GitHub for Beginners - Advanced Git Tutorials
实用工具: - GitKraken - Git GUI客户端 - SourceTree - 免费Git GUI - Git Extensions - Windows Git工具
总结¶
通过本教程,你已经学习了Git的高级功能:
核心技能¶
- ✅ Rebase: 整理提交历史,保持线性
- ✅ 交互式Rebase: 修改、合并、重排提交
- ✅ Cherry-Pick: 精确选择提交
- ✅ 分支策略: Git Flow、GitHub Flow、Trunk-Based
- ✅ 子模块: 管理依赖仓库
- ✅ 钩子: 自动化工作流
- ✅ Reflog: 恢复丢失的提交
- ✅ 性能优化: 维护和优化仓库
最佳实践¶
- 保持提交历史清晰
- 使用规范的提交信息
- 频繁提交,小步前进
- 定期同步远程分支
- 谨慎使用改写历史的操作
- 使用分支策略规范团队协作
- 自动化重复性任务
下一步¶
- 在实际项目中应用这些技巧
- 学习团队的Git工作流
- 探索CI/CD集成
- 深入理解Git内部原理
记住:Git是一个强大的工具,但也需要谨慎使用。在修改历史或使用强制推送前,务必确保理解其影响。
练习题¶
练习1: Rebase实践¶
- 创建一个feature分支
- 在feature分支上提交3次
- 在main分支上提交2次
- 将feature分支rebase到main
- 解决可能的冲突
练习2: 交互式Rebase¶
- 创建5个提交
- 使用交互式rebase合并其中3个
- 修改一个提交信息
- 重排提交顺序
练习3: Cherry-Pick¶
- 在分支A上提交3次
- 在分支B上cherry-pick其中2个提交
- 解决可能的冲突
练习4: 子模块管理¶
- 创建一个主项目
- 添加一个子模块
- 在子模块中进行修改
- 更新主项目中的子模块引用
练习5: Git钩子¶
- 创建pre-commit钩子检查代码格式
- 创建commit-msg钩子验证提交信息
- 创建pre-push钩子运行测试
练习6: 恢复操作¶
- 创建一些提交
- 使用reset删除提交
- 使用reflog找回删除的提交
- 恢复删除的分支
参考资料¶
命令参考¶
git rebase --help- Rebase帮助git cherry-pick --help- Cherry-pick帮助git submodule --help- 子模块帮助git reflog --help- Reflog帮助
配置文件¶
~/.gitconfig- 全局配置.git/config- 仓库配置.gitignore- 忽略文件.gitattributes- 文件属性.gitmodules- 子模块配置
相关文章¶
作者: 嵌入式知识平台
最后更新: 2024-01-15
版本: 1.0
许可: CC BY-NC-SA 4.0