-
1. 起步
-
2. Git 基础
-
3. Git 分支
-
4. 服务器上的 Git
- 4.1 协议
- 4.2 在服务器上部署 Git
- 4.3 生成 SSH 公钥
- 4.4 架设服务器
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管服务
- 4.10 小结
-
5. 分布式 Git
-
A1. 附录 A: Git 在其他环境
- A1.1 图形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小结
-
A2. 附录 B: 在应用程序中嵌入 Git
-
A3. 附录 C: Git 命令
3.1 Git 分支 - 分支概览
几乎所有的版本控制系统都支持某种形式的分支。分支意味着你可以脱离主开发线,继续进行开发而不影响主线。在许多版本控制工具中,这是一个相对昂贵的过程,通常需要你创建一个源代码目录的全新副本,这对于大型项目来说可能非常耗时。
有些人将 Git 的分支模型称为其“杀手级特性”,它无疑使 Git 在版本控制社区中脱颖而出。为什么它如此特别?Git 分支的实现方式非常轻量级,使得分支操作几乎瞬时完成,而且在分支之间切换也通常一样快。与其他许多版本控制系统不同,Git 鼓励经常进行分支和合并的工作流程,甚至一天内进行多次。理解并掌握这个特性会给你一个强大而独特的工具,并可能彻底改变你的开发方式。
分支概览
为了真正理解 Git 的分支工作方式,我们需要退一步,审视 Git 是如何存储其数据的。
正如你可能还记得 Git 是什么? 中提到的,Git 存储数据的方式不是一系列变更集或差异,而是一系列快照。
当你提交时,Git 会存储一个提交对象,其中包含指向你暂存内容快照的指针。该对象还包含作者姓名和电子邮件地址、你输入的提交信息,以及指向该提交之前一个或多个提交的指针(其父提交):对于初始提交为零个父提交,对于普通提交为一个父提交,而对于合并两个或多个分支产生的提交则有多个父提交。
为了可视化这一点,我们假设你有一个包含三个文件的目录,并将它们全部暂存然后提交。暂存文件会计算每个文件的校验和(我们在 Git 是什么? 中提到的 SHA-1 哈希),将该文件的版本存储在 Git 仓库中(Git 将它们称为blob),并将该校验和添加到暂存区。
$ git add README test.rb LICENSE
$ git commit -m 'Initial commit'
当你通过运行 git commit 来创建提交时,Git 会校验每个子目录(在本例中,只有根项目目录),并将它们作为树对象存储在 Git 仓库中。然后,Git 会创建一个提交对象,该对象包含元数据以及指向根项目树的指针,以便在需要时重新创建该快照。
你的 Git 仓库现在包含五个对象:三个blob(每个代表三个文件之一的内容)、一个tree(列出目录内容并指定哪些文件名对应哪些 blob),以及一个commit(指向该根树并包含所有提交元数据)。
如果你进行一些更改并再次提交,下一个提交会存储一个指向其紧前面提交的指针。
Git 中的分支只是一个指向这些提交之一的、可移动的轻量级指针。Git 中的默认分支名称是 master。当你开始进行提交时,会有一个指向你最新提交的 master 分支。每次提交时,master 分支指针会自动向前移动。
|
注意
|
Git 中的“master”分支并非特殊分支。它与其他任何分支完全相同。几乎每个仓库都有一个的原因是 |
创建新分支
当你创建新分支时会发生什么?嗯,这样做会创建一个新的指针供你移动。假设你想创建一个名为 testing 的新分支。你可以通过 git branch 命令来实现
$ git branch testing
这会创建一个指向你当前所在提交的新指针。
Git 如何知道你当前在哪个分支上?它会维护一个特殊的指针,称为 HEAD。请注意,这与你在其他版本控制系统(如 Subversion 或 CVS)中可能习惯的 HEAD 概念大不相同。在 Git 中,它是指向你当前所在本地分支的指针。在这种情况下,你仍然在 master 分支上。git branch 命令仅仅创建了一个新分支,并没有切换到该分支。
你可以通过运行简单的 git log 命令来轻松查看这一点,该命令会显示分支指针指向何处。此选项称为 --decorate。
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit
你可以看到 master 和 testing 分支都指向 f30ab 提交。
切换分支
要切换到现有分支,请运行 git checkout 命令。让我们切换到新的 testing 分支
$ git checkout testing
这会将 HEAD 移动到指向 testing 分支。
这有什么意义呢?嗯,让我们再进行一次提交
$ vim test.rb
$ git commit -a -m 'Make a change'
这很有趣,因为现在你的 testing 分支已经向前移动了,而你的 master 分支仍然指向你在运行 git checkout 切换分支时所在的提交。让我们切换回 master 分支
$ git checkout master
|
注意
|
git log 不会总是显示所有分支如果你现在运行 分支并没有消失;Git 只是不知道你对该分支感兴趣,它试图显示它认为你感兴趣的内容。换句话说,默认情况下, 要显示所需分支的提交历史,你必须显式指定它: |
该命令执行了两项操作。它将 HEAD 指针移回指向 master 分支,并将工作目录中的文件恢复到 master 指向的快照。这也意味着你从现在开始所做的更改将与旧版本项目分道扬镳。它基本上是回滚了你在 testing 分支中所做的工作,以便你可以朝不同的方向前进。
|
注意
|
切换分支会更改工作目录中的文件
重要的是要注意,当你切换 Git 中的分支时,工作目录中的文件会发生变化。如果你切换到较旧的分支,你的工作目录将恢复到上一次在该分支提交时的样子。如果 Git 无法干净地执行此操作,它将不允许你切换。 |
让我们进行一些更改并再次提交
$ vim test.rb
$ git commit -a -m 'Make other changes'
现在你的项目历史已经分叉(请参见 分叉历史)。你创建了一个分支并切换到它,在该分支上进行了一些工作,然后切换回你的主分支并进行了其他工作。这些更改都隔离在不同的分支中:你可以在分支之间来回切换,并在准备好时将它们合并。所有这些都通过简单的 branch、checkout 和 commit 命令完成。
你也可以通过 git log 命令轻松地看到这一点。如果你运行 git log --oneline --decorate --graph --all,它将打印出你的提交历史,显示你的分支指针在哪里以及你的历史如何分叉。
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Make other changes
| * 87ab2 (testing) Make a change
|/
* f30ab Add feature #32 - ability to add new formats to the central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 Initial commit of my project
由于 Git 中的分支实际上是一个包含其指向的提交的 40 位 SHA-1 校验和的简单文件,因此创建和销毁分支的成本很低。创建一个新分支就像向一个文件写入 41 字节(40 个字符和一个换行符)一样快速简单。
这与大多数旧版本控制工具的分支方式形成了鲜明对比,它们通常涉及将项目的所有文件复制到第二个目录中。根据项目大小,这可能需要几秒钟甚至几分钟,而在 Git 中,这个过程总是瞬时的。此外,由于我们在提交时记录了父提交,因此查找合适的合并基础进行合并会自动完成,并且通常非常容易。这些特性有助于鼓励开发人员频繁创建和使用分支。
让我们看看为什么你应该这样做。
|
注意
|
同时创建新分支并切换到该分支
通常情况下,你会创建一个新分支并立即想切换到该新分支——这可以通过一个操作完成,即使用 |
|
注意
|
从 Git 版本 2.23 开始,你可以使用
|