git 使用详解(7)-- 分支HEAD

本文深入探讨了Git的分支管理机制,介绍了Git如何通过保存一系列文件快照而非文件差异来进行版本控制,强调了分支的轻量级特性及其在工作流程中的重要作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有人把 Git 的分支模型称为“必杀技特性”,而正是因为它,将 Git 从版本控制系统家族里区分出来。Git 有何特别之处呢?Git 的分支可谓是难以置信的轻量级,它的新建操作几乎可以在瞬间完成,并且在不同分支间切换起来也差不多一样快。和许多其他版本控制系统不同,Git鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。理解分支的概念并熟练运用后,你才会意识到为什么 Git 是一个如此强大而独特的工具,并从此真正改变你的开发方式

1  何谓分支

为了理解 Git 分支的实现方式,我们需要回顾一下 Git 是如何储存数据的。或许你还记得第一章的内容,Git 保存的不是文件差异或者变化量,而只是一系列文件快照。

在 Git 中提交时,会保存一个提交(commit)对象,该对象包含一个指向暂存内容快照的指针,包含本次提交的作者等相关附属信息,包含零个或多个指向该提交对象的父对象指针:首次提交是没有直接祖先的,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。

为直观起见,我们假设在工作目录中有三个文件,准备将它们暂存后提交。暂存操作会对每一个文件计算校验和(即第一章中提到的 SHA-1 哈希字串),然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照),并将校验和加入暂存区域:

$ git add README test.rb LICENSE
$ git commit -m 'initial commit of my project'

当使用 git commit 新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和,然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。

现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。概念上来说,仓库中的各个对象保存的数据和相互关系看起来如图 3-1 所示:


图 3-1. 单个提交对象在仓库中的数据结构

作些修改后再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针(译注:即下图中的 parent 对象)。两次提交后,仓库历史会变成图 3-2 的样子:


图 3-2. 多个提交对象之间的链接关系

现在来谈分支。Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针Git 会使用 master 作为分支的默认名字在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动


图 3-3. 分支其实就是从某个提交对象往回看的历史

那么,Git 又是如何创建一个新的分支的呢?答案很简单,创建一个新的分支指针。比如新建一个 testing 分支,可以使用 git branch 命令:

$ git branch testing

这会在当前 commit 对象上新建一个分支指针(见图 3-4)。


图 3-4. 多个分支指向提交数据的历史

那么,Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为 HEAD 的特别指针。请注意它和你熟知的许多其他版本控制系统(比如 Subversion 或 CVS)里的 HEAD 概念大不相同。在 Git 中,它是一个指向你正在工作中的本地分支的指针(译注:将 HEAD 想象为当前分支的别名。)。运行git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,所以在这个例子中,我们依然还在 master 分支里工作(参考图 3-5)。


图 3-5. HEAD 指向当前所在的分支

要切换到其他分支,可以执行 git checkout 命令。我们现在转换到新建的 testing 分支:

$ git checkout testing

这样 HEAD 就指向了 testing 分支(见图3-6)。


图 3-6. HEAD 在你转换分支时 指向 新的分支

这样的实现方式会给我们带来什么好处呢?好吧,现在不妨再提交一次:

$ vim test.rb
$ git commit -a -m 'made a change'

图 3-7 展示了提交后的结果。


图 3-7. 每次提交后 HEAD 随着分支一起向前移动

非常有趣,现在 testing 分支向前移动了一格,而 master 分支仍然指向原先 git checkout 时所在的 commit 对象。现在我们回到 master 分支看看:

$ git checkout master

图 3-8 显示了结果。


图 3-8. HEAD 在一次 checkout 之后移动到了另一个分支

这条命令做了两件事它把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支所指向的快照内容。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。

我们作些修改后再次提交:

$ vim test.rb
$ git commit -a -m 'made other changes'

现在我们的项目提交历史产生了分叉(如图 3-9 所示),因为刚才我们创建了一个分支,转换到其中进行了一些工作,然后又回到原来的主分支进行了另外一些工作。这些改变分别孤立在不同的分支里:我们可以在不同分支里反复切换,并在时机成熟时把它们合并到一起。而所有这些工作,仅仅需要branchcheckout 这两条命令就可以完成。


图 3-9. 不同流向的分支历史

由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,当然也就很快了。

这和大多数版本控制系统形成了鲜明对比,它们管理分支大多采取备份所有项目文件到特定目录的方式,所以根据项目文件数量和大小不同,可能花费的时间也会有相当大的差别,快则几秒,慢则数分钟。而 Git 的实现与项目复杂度无关,它永远可以在几毫秒的时间内完成分支的创建和切换。同时,因为每次提交时都记录了祖先信息(译注:即parent 对象),将来要合并分支时,寻找恰当的合并基础(译注:即共同祖先)的工作其实已经自然而然地摆在那里了,所以实现起来非常容易。Git 鼓励开发者频繁使用分支,正是因为有着这些特性作保障。

接下来看看,我们为什么应该频繁使用分支。

<think>我们讨论的是`git pull --rebase`命令。首先,我们需要理解`git pull`的默认行为以及`--rebase`选项的作用。 ### 1. `git pull`的默认行为 `git pull`实际上是两个命令的组合: - `git fetch`:从远程仓库获取最新的提交历史,但不会自动合并到当前分支- `git merge`:将远程分支的更改合并到当前分支。 默认情况下,执行`git pull`会先执行`git fetch`,然后执行`git merge`,将远程分支的更改合并到当前分支,并生成一个合并提交(merge commit)。 ### 2. `git pull --rebase`的作用 使用`--rebase`选项时,`git pull`的执行步骤变为: - 执行`git fetch`,获取远程仓库的最新提交。 - 然后执行`git rebase`,而不是`git merge`。 具体来说,`git rebase`会将当前分支的提交“暂存”起来,然后将远程分支的最新提交应用到当前分支,最后再将暂存的提交依次应用到这些最新提交之后。这样,当前分支的提交历史会变成一条直线,避免了不必要的合并提交。 ### 3. 为什么使用`git pull --rebase`? 使用`git pull --rebase`的主要目的是保持提交历史的整洁和线性。在多人协作的项目中,如果每个人都使用`git pull`(默认合并),那么提交历史中会出现很多合并提交,使得历史记录变得复杂。而使用`rebase`可以避免这种情况,使得提交历史看起来更加清晰。 ### 4. 使用示例 假设我们有一个本地分支`feature`,它基于远程分支`origin/main`。当我们工作时,其他人已经向`origin/main`推送了新的提交。此时,我们想将远程分支的更新同步到本地分支。 #### 默认的`git pull`(合并方式): ```bash git checkout feature git pull origin main ``` 这会产生一个合并提交,提交历史如下: ``` * merge commit (HEAD -> feature) |\ | * remote commit (origin/main) * | local commit |/ * previous commit ``` #### 使用`git pull --rebase`: ```bash git checkout feature git pull --rebase origin main ``` 提交历史会变成线性: ``` * local commit (HEAD -> feature) * remote commit (origin/main) * previous commit ``` ### 5. 注意事项 - **冲突解决**:在`rebase`过程中可能会遇到冲突。当冲突发生时,`rebase`会暂停,让你解决冲突。解决冲突后,使用`git add <file>`标记冲突已解决,然后使用`git rebase --continue`继续剩余的`rebase`操作。如果想放弃`rebase`,可以使用`git rebase --abort`。 - **不要对已推送的提交进行变基**:如果你已经将提交推送到远程仓库,那么最好不要对这些提交进行变基(rebase),因为这会导致提交历史被重写,从而给其他人带来困扰。变基应该只用于本地尚未推送的提交。 ### 6. 配置默认使用rebase 你可以设置让当前分支在`pull`时默认使用`rebase`: ```bash git config pull.rebase true ``` 或者全局设置: ```bash git config --global pull.rebase true ``` ### 总结 `git pull --rebase`是一种将远程更改合并到本地分支的方式,它通过变基而不是合并来保持提交历史的线性。这使得提交历史更加清晰,但需要注意在已推送的提交上避免使用变基。 §§相关问题§§: 1. `git merge`和`git rebase`的主要区别是什么? 2. 在解决rebase冲突时,有哪些步骤? 3. 如何撤销一个已经完成的rebase操作? 4. 为什么不应该对已经推送到远程仓库的提交进行变基?</think>### `git pull --rebase` 详解 `git pull --rebase` 是 Git 中一个强大的工作流优化命令,它将 `git fetch` + `git rebase` 组合成原子操作。核心作用是**避免不必要的合并提交**,保持提交历史的线性整洁。 #### 工作流程对比 | 操作 | 提交历史效果 | 适用场景 | |----------------------|----------------------------------|----------------------------| | `git pull` (默认) | 生成合并提交(Merge Commit) | 需要保留分支合并信息的场景 | | `git pull --rebase` | 线性历史(无额外合并提交) | 个人分支/保持提交历史整洁 | --- ### 代码实现与解释 #### 1. 基础用法 ```bash # 拉取远程更新并变基 git pull --rebase origin main # 等效于: git fetch origin # 获取远程最新代码 git rebase origin/main # 将本地提交变基到远程分支 ``` #### 2. 冲突解决流程 ```bash # 1. 执行变基时出现冲突 >>> git pull --rebase origin main CONFLICT (content): Merge conflict in file.txt # 2. 手动解决冲突后标记文件 git add file.txt # 3. 继续变基(不要用 git commit!) git rebase --continue # 4. 若想放弃变基 git rebase --abort ``` #### 3. 配置默认行为(推荐) ```bash # 为当前仓库设置默认变基 git config pull.rebase true # 全局设置默认变基(所有仓库) git config --global pull.rebase true ``` --- ### 关键机制图解 ``` 初始状态: 本地: A--B--C (feature) 远程: A--D--E (main) 普通 pull (merge): A--B--C--M (feature) / D--E pull --rebase: A--D--E--B'--C' (feature) ``` --- ### 最佳实践 1. **个人分支必用**:在私有分支开发时始终使用 `--rebase` 2. **提交前清理**:`git pull --rebase` 后再 `git push` 3. **避免已推送提交**:不要对已推送到远程的提交进行变基 4. **可视化工具**:使用 `git log --graph --oneline` 验证历史线 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值