0/7 完成 (0%)

分支管理

Git的分支功能是其最强大的特性之一,它允许开发者在不影响主分支的情况下进行并行开发。理解和掌握分支操作是成为Git高级用户的关键。

分支的概念

在Git中,分支本质上是指向提交对象的可移动指针。当你创建一个新分支时,Git会创建一个新的指针,但并不会复制代码库。这使得Git的分支操作非常轻量和快速。

Git分支结构示意图
图1: Git分支结构示意图

分支操作基础

查看分支

git branch           # 列出本地分支
git branch -r        # 列出远程分支
git branch -a        # 列出所有本地和远程分支
git branch -v        # 查看每个分支的最后一次提交

创建与切换分支

创建新分支:

git branch feature-x  # 创建一个名为feature-x的分支

切换到指定分支:

git checkout feature-x  # 切换到feature-x分支

创建并切换到新分支:

git checkout -b feature-y  # 创建并切换到feature-y分支

使用Git 2.23版本后的新命令:

git switch feature-x    # 切换到feature-x分支
git switch -c feature-z  # 创建并切换到feature-z分支

删除分支

git branch -d feature-x  # 删除已合并的分支
git branch -D feature-y  # 强制删除未合并的分支

重命名分支

git branch -m old-name new-name  # 重命名分支

远程分支操作

推送分支到远程

git push origin feature-x  # 推送本地feature-x分支到远程

设置上游分支

git push -u origin feature-x  # 推送并设置上游分支
git branch --set-upstream-to=origin/feature-x feature-x  # 手动设置上游分支

跟踪远程分支

git checkout --track origin/feature-x  # 创建本地分支并跟踪远程分支
git switch -c feature-x origin/feature-x  # 使用switch命令

删除远程分支

git push origin --delete feature-x  # 删除远程分支

分支命名最佳实践

良好的分支命名规范有助于团队协作:

  • feature/ - 用于开发新功能,如 feature/user-authentication
  • bugfix/ - 用于修复bug,如 bugfix/login-error
  • hotfix/ - 用于紧急修复生产环境问题,如 hotfix/security-breach
  • release/ - 用于发布准备,如 release/v1.2.0

合并策略

在Git中,合并是将两个或多个开发历史集成在一起的过程。Git提供了多种合并方式,以适应不同的工作流和需求。

基本合并

常规合并

git checkout main       # 切换到目标分支
git merge feature-x      # 将feature-x合并到当前分支

如果没有冲突,Git将自动创建一个新的合并提交。

快进合并(Fast-forward)

当目标分支是源分支的直接上游时,Git默认执行"快进"合并:

git checkout main
git merge feature-x      # 如果可能,执行快进合并

要强制创建合并提交,即使可以快进:

git merge --no-ff feature-x
Git合并类型对比
图2: 快进合并与常规合并对比

压缩合并(Squash)

将源分支的所有提交压缩为一个提交,然后合并到目标分支:

git checkout main
git merge --squash feature-x
git commit -m "合并feature-x的所有更改"

解决合并冲突

当Git无法自动合并更改时,会产生冲突。解决冲突是Git用户必须掌握的技能。

冲突标记解释

<<<<<<< HEAD
当前分支的代码
=======
被合并分支的代码
>>>>>>> feature-x

解决冲突步骤

  1. 使用 git status 查看冲突文件
  2. 编辑冲突文件,手动解决冲突
  3. 使用 git add 标记冲突已解决
  4. 使用 git commit 完成合并

使用合并工具

git mergetool  # 启动配置的可视化合并工具

中止合并

如果合并过程中遇到问题,想要中止合并:

git merge --abort

合并前的最佳实践

在进行重要的合并操作前:

  1. 确保工作目录干净(无未提交的更改)
  2. 拉取最新的目标分支代码
  3. 考虑先在本地测试分支合并
  4. 对于复杂的合并,考虑创建临时分支

重写历史

Git允许你修改提交历史,这使得你可以保持一个干净、线性的项目历史。但是,重写已推送的历史可能会导致团队协作问题,因此需要谨慎使用。

重写共享历史的风险

黄金法则:永远不要重写已经推送到公共仓库的历史!这会导致其他开发者的本地历史与远程不一致,造成混乱。

修改最近的提交

修改提交信息

git commit --amend -m "新的提交信息"

修改提交内容

git add forgotten-file.txt
git commit --amend --no-edit  # 不修改提交信息

变基(Rebase)

变基是将一系列提交重新应用到另一个基础提交上的过程,可以用来保持线性历史。

基本变基

git checkout feature
git rebase main  # 将feature分支的提交变基到main分支上
Git变基操作示意图
图3: Git变基操作示意图

交互式变基

git rebase -i HEAD~3  # 交互式变基最近的3个提交

交互式变基可以让你:

  • 重新排列提交顺序
  • 删除提交(drop
  • 合并提交(squash
  • 编辑提交(edit
  • 修改提交信息(reword

处理变基冲突

变基过程中可能会遇到冲突,处理步骤:

  1. 解决冲突文件
  2. 使用 git add 标记为已解决
  3. 使用 git rebase --continue 继续变基
  4. 若要放弃,使用 git rebase --abort

其他历史重写技巧

重置(Reset)

git reset --soft HEAD~1  # 撤销最近的提交,但保留更改
git reset --mixed HEAD~1  # 撤销提交和暂存,但保留工作目录更改
git reset --hard HEAD~1  # 撤销提交和所有更改(危险操作!)

还原(Revert)

安全地撤销提交,通过创建新的提交来抵消之前的更改:

git revert 9fceb02  # 撤销指定提交
git revert HEAD~3..HEAD  # 撤销最近的3个提交

过滤分支(Filter-branch)

用于批量修改历史,如从所有提交中删除敏感文件:

git filter-branch --tree-filter 'rm -f passwords.txt' HEAD

注意:Git现在推荐使用 git-filter-repo 工具代替 filter-branch

历史重写的最佳实践

如果确实需要重写已推送的历史,请遵循以下步骤:

  1. 通知所有团队成员
  2. 确保每个人提交或暂存他们的更改
  3. 执行历史重写操作
  4. 强制推送更改 git push --force-with-lease
  5. 指导团队成员如何同步他们的本地仓库

暂存工作

Git的stash功能允许你临时保存工作目录的修改,而无需提交,这对于需要快速切换分支或应用紧急修复时非常有用。

基本操作

暂存修改

git stash  # 暂存所有修改
git stash save "正在开发登录功能"  # 添加描述性信息

查看暂存列表

git stash list  # 显示所有暂存的工作

应用暂存

git stash apply  # 应用最近的暂存,但保留在暂存列表中
git stash apply stash@{2}  # 应用特定的暂存
git stash pop  # 应用最近的暂存,并从暂存列表中删除

删除暂存

git stash drop stash@{1}  # 删除特定暂存
git stash clear  # 删除所有暂存

高级暂存技巧

暂存未跟踪文件

git stash -u  # 包括未跟踪的文件
git stash --all  # 包括未跟踪和忽略的文件

暂存部分更改

git stash -p  # 交互式选择要暂存的更改

创建分支从暂存

直接从暂存创建并切换到新分支:

git stash branch new-feature stash@{1}

查看暂存内容

git stash show  # 显示最近暂存的文件变更统计
git stash show -p  # 显示详细的差异
git stash show -p stash@{2}  # 显示特定暂存的详细差异
Git Stash操作示意图
图4: Git Stash操作示意图

暂存工作流

一个典型的工作流可能如下:

  1. 在feature-x分支工作
  2. 发现紧急bug需要修复
  3. git stash save "feature-x进行中的工作"
  4. git checkout main
  5. git checkout -b hotfix-bug
  6. 修复bug并提交
  7. git checkout feature-x
  8. git stash pop
  9. 继续feature-x的工作

挑选提交

Cherry-pick是Git中一个强大的功能,允许你选择性地应用其他分支上的提交,而不需要合并整个分支。

基本用法

挑选单个提交

git cherry-pick 2c3a8b  # 将指定提交应用到当前分支

这将在当前分支上创建一个新的提交,包含相同的更改但有不同的提交哈希。

挑选多个提交

git cherry-pick a123b..c456d  # 应用一系列提交(不包括a123b)
git cherry-pick a123b^..c456d  # 应用一系列提交(包括a123b)

保留原始作者信息

git cherry-pick -x 2c3a8b  # 在提交信息中包含原始提交的引用

处理cherry-pick冲突

解决冲突

如果cherry-pick遇到冲突:

  1. 解决冲突文件
  2. git add 标记为已解决
  3. git cherry-pick --continue 继续操作

中止操作

git cherry-pick --abort  # 取消cherry-pick操作

跳过当前提交

git cherry-pick --skip  # 跳过当前冲突的提交

Cherry-pick的应用场景

  • 向稳定分支回移修复:将bug修复从开发分支移至生产分支
  • 选择性合并功能:只取其他分支上的特定功能
  • 恢复意外丢失的更改:从已删除的分支恢复提交
  • 同步分叉仓库:将上游仓库的特定提交应用到你的分叉
Git Cherry-pick操作示意图
图5: Cherry-pick操作示意图

使用Cherry-pick的注意事项

尽管cherry-pick很有用,但也有一些潜在问题需要注意:

  • 重复应用相同的更改可能导致冲突
  • 可能会丢失提交之间的关系和上下文
  • 难以追踪更改的来源和历史

在可能的情况下,优先考虑使用合并或变基来保持完整的提交历史。

二分查找(Bisect)

Git bisect是一个强大的调试工具,它使用二分查找算法来帮助你快速找出引入bug的提交。当你发现一个bug,但不知道它是何时引入的,bisect可以帮你在众多提交中定位问题。

基本用法

开始二分查找

git bisect start  # 启动二分查找过程

标记好坏提交

git bisect bad  # 标记当前提交有bug
git bisect good v1.0  # 标记已知没有bug的提交

此时Git会自动检出一个中间的提交,让你测试。

测试并标记

# 测试代码后,标记当前检出的提交
git bisect good  # 如果当前提交没有bug
# 或
git bisect bad  # 如果当前提交有bug

Git会继续检出下一个要测试的提交,重复此过程直到找到第一个引入bug的提交。

结束二分查找

git bisect reset  # 结束查找,返回到原始分支
Git Bisect二分查找操作示意图
图6: Git Bisect二分查找操作示意图

高级Bisect技巧

自动化测试

git bisect start HEAD v1.0
git bisect run npm test  # 自动运行测试脚本

这会自动运行测试脚本,并根据退出状态码(0表示成功,非0表示失败)标记提交。

查看日志

git bisect log  # 显示当前二分查找的日志

跳过提交

git bisect skip  # 跳过当前提交(如果无法测试)

Bisect的最佳实践

  • 确保测试过程一致可重复
  • 尽可能使用自动化测试脚本
  • 选择足够远的"good"提交,确保它在bug引入之前
  • 记录bisect的结果,便于后续分析

子模块(Submodules)

Git子模块允许你将一个Git仓库作为另一个Git仓库的子目录。这使得你可以在一个项目中包含和使用其他项目的代码,同时保持提交的独立性。

基本操作

添加子模块

git submodule add https://github.com/example/library.git lib/library

这会将指定的仓库克隆到lib/library目录,并创建.gitmodules文件记录子模块信息。

克隆包含子模块的项目

git clone https://github.com/example/main-project.git
cd main-project
git submodule init    # 初始化子模块
git submodule update  # 检出子模块的内容

或者一步完成:

git clone --recurse-submodules https://github.com/example/main-project.git

更新子模块

cd lib/library
git fetch
git merge origin/master  # 或checkout特定标签/提交

# 回到主项目
cd ../..
git add lib/library
git commit -m "更新library子模块到最新版本"

或者从主项目一次性更新所有子模块:

git submodule update --remote
Git子模块示意图
图7: Git子模块示意图

高级子模块技巧

查看子模块状态

git submodule status

在子模块中工作

子模块是独立的Git仓库,你可以在其中正常工作:

cd lib/library
git checkout -b new-feature
# 做出修改并提交
git push origin new-feature

删除子模块

# 1. 删除相关部分
git submodule deinit -f lib/library
rm -rf .git/modules/lib/library
git rm -f lib/library

# 2. 提交更改
git commit -m "移除library子模块"

使用子模块的注意事项

  • 子模块可能增加项目的复杂性
  • 团队成员需要了解子模块的工作方式
  • 子模块更新需要额外的步骤
  • 考虑使用包管理器作为替代方案
上一章
Git基础
下一章
Git工作流