Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified book/03-git-branching/images/advance-master.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified book/03-git-branching/images/basic-merging-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ image::images/basic-branching-3.png[iss53 分支随着工作的进展向前推
但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。
最好的方法是,在你切换分支之前,保持好一个干净的状态。
有一些方法可以绕过这个问题(即,保存进度(stashing) 和 修补提交(commit amending)),我们会在 <<_git_stashing>> 中看到关于这两个命令的介绍。
现在,我们假设你已经把你的修改全部提交了,这时你可以切换回 master 分支了:
现在,我们假设你已经把你的修改全部提交了,这时你可以切换回 `master` 分支了:

[source,console]
----
Expand Down Expand Up @@ -91,7 +91,7 @@ $ git commit -a -m 'fixed the broken email address'
.基于 `master` 分支的紧急问题分支 `hotfix branch`
image::images/basic-branching-4.png[基于 `master` 分支的紧急问题分支(hotfix branch)。]

你可以运行你的测试,确保你的修改是正确的,然后将其合并回你的 master 分支来部署到线上。
你可以运行你的测试,确保你的修改是正确的,然后将其合并回你的 `master` 分支来部署到线上。
你可以使用 `git merge` 命令来达到上述目的:(((git commands, merge)))

[source,console]
Expand Down Expand Up @@ -146,7 +146,7 @@ image::images/basic-branching-6.png[继续在 `iss53` 分支上的工作。]

(((branches, merging)))(((merging)))
假设你已经修正了 #53 问题,并且打算将你的工作合并入 `master` 分支。
为此,你需要合并 `iss53` 分支,这和之前你合并 `hotfix` 分支所做的工作差不多。
为此,你需要合并 `iss53` 分支到 `master` 分支,这和之前你合并 `hotfix` 分支所做的工作差不多。
你只需要检出到你想合并入的分支,然后运行 `git merge` 命令:

[source,console]
Expand All @@ -155,7 +155,7 @@ $ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
README | 1 +
index.html | 1 +
1 file changed, 1 insertion(+)
----

Expand Down
25 changes: 13 additions & 12 deletions book/03-git-branching/sections/nutshell.asc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
[source,console]
----
$ git add README test.rb LICENSE
$ git commit -m 'initial commit of my project'
$ git commit -m 'The initial commit of my project'
----

当使用 `git commit` 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在 Git 仓库中这些校验和保存为树对象。
Expand All @@ -30,10 +30,9 @@ image::images/commit-and-tree.png[首次提交对象及其树结构.]
.提交对象及其父对象
image::images/commits-and-parents.png[提交对象及其父对象.]

说完了 Git 保存数据的方式,现在让我们谈回分支。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。
Git 的默认分支名字是 `master`。
在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。
在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 `master` 分支。
它会在每次的提交操作中自动向前移动。

[NOTE]
Expand All @@ -52,7 +51,8 @@ image::images/branch-and-history.png[分支及其提交历史.]
(((branches, creating)))
Git 是怎么创建新分支的呢?
很简单,它只是为你创建了一个可以移动的新的指针。
比如,创建一个 testing 分支,需要使用 `git branch` 命令:(((git commands, branch)))
比如,创建一个 testing 分支,
你需要使用 `git branch` 命令:(((git commands, branch)))

[source,console]
----
Expand All @@ -62,19 +62,20 @@ $ git branch testing
这会在当前所在的提交对象上创建一个指针。

.两个指向相同提交历史的分支
image::images/two-branches.png[两个指向相同提交历史的分支.]
image::images/two-branches.png[两个指向相同提交历史的分支]

那么,Git 又是怎么知道当前在哪一个分支上呢?
也很简单,它有一个名为 `HEAD` 的特殊指针。
请注意它和许多其它版本控制系统(如 Subversion 或 CVS)里的 `HEAD` 概念完全不同。
在 Git 中,它是一个指针,指向当前所在的本地分支(译注:将 `HEAD` 想象为当前分支的别名)。
在本例中,你仍然在 master 分支上。
在本例中,你仍然在 `master` 分支上。
因为 `git branch` 命令仅仅 _创建_ 一个新分支,并不会自动切换到新分支中去。

.HEAD 指向当前所在的分支
image::images/head-to-master.png[HEAD 指向当前所在的分支.]

你可以简单地使用 `git log` 命令查看各个分支当前所指的对象。提供这一功能的参数是 `--decorate`。
你可以简单地使用 `git log` 命令查看各个分支当前所指的对象。
提供这一功能的参数是 `--decorate`。

[source,console]
----
Expand All @@ -91,7 +92,7 @@ f30ab (HEAD, master, testing) add feature #32 - ability to add new

(((branches, switching)))
要切换到一个已存在的分支,你需要使用 `git checkout` 命令。(((git commands, checkout)))
我们现在切换到新创建的 testing 分支去:
我们现在切换到新创建的 `testing` 分支去:

[source,console]
----
Expand All @@ -115,8 +116,8 @@ $ git commit -a -m 'made a change'
.HEAD 分支随着提交操作自动向前移动
image::images/advance-testing.png[HEAD 分支随着提交操作自动向前移动.]

如图所示,你的 testing 分支向前移动了,但是 master 分支却没有,它仍然指向运行 `git checkout` 时所指的对象。
这就有意思了,现在我们切换回 master 分支看看:
如图所示,你的 `testing` 分支向前移动了,但是 `master` 分支却没有,它仍然指向运行 `git checkout` 时所指的对象。
这就有意思了,现在我们切换回 `master` 分支看看:

[source,console]
----
Expand All @@ -127,9 +128,9 @@ $ git checkout master
image::images/checkout-master.png[检出时 HEAD 随之移动.]

这条命令做了两件事。
一是使 HEAD 指回 master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。
一是使 HEAD 指回 `master` 分支,二是将工作目录恢复成 `master` 分支所指向的快照内容。
也就是说,你现在做修改的话,项目将始于一个较旧的版本。
本质上来讲,这就是忽略 testing 分支所做的修改,以便于向另一个方向进行开发。
本质上来讲,这就是忽略 `testing` 分支所做的修改,以便于向另一个方向进行开发。

[NOTE]
.分支切换会改变你工作目录中的文件
Expand Down
14 changes: 9 additions & 5 deletions book/03-git-branching/sections/rebasing.asc
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,16 @@ image::images/perils-of-rebasing-4.png[你将相同的内容又合并了一次

此时如果你执行 `git log` 命令,你会发现有两个提交的作者、日期、日志居然是一样的,这会令人感到混乱。
此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被变基抛弃的提交又找了回来,这会令人感到更加混乱。
很明显对方并不想在提交历史中看到 `C4` 和 `C6`,因为之前就是她把这两个提交通过变基丢弃的
很明显对方并不想在提交历史中看到 `C4` 和 `C6`,因为之前就是他们把这两个提交通过变基丢弃的

[[_rebase_rebase]]
==== 用变基解决变基

如果你 *真的* 遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。
如果你 *真的* 遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。
如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。

实际上,Git 除了对整个提交计算 SHA 校验和以外,也对本次提交所引入的修改计算了校验和——即 ``patch-id''。
实际上,Git 除了对整个提交计算 SHA-1 校验和以外,也对本次提交所引入的修改计算了校验和——
即 ``patch-id''。

如果你拉取被覆盖过的更新并将你手头的工作基于此进行变基的话,一般情况下 Git 都能成功分辨出哪些是你的修改,并把它们应用到新分支上。

Expand All @@ -198,9 +200,11 @@ image::images/perils-of-rebasing-4.png[你将相同的内容又合并了一次
.在一个被变基然后强制推送的分支上再次执行变基
image::images/perils-of-rebasing-5.png[在一个被变基然后强制推送的分支上再次执行变基]

要想上述方案有效,还需要对方在变基时确保 C4' 和 C4 是几乎一样的。否则变基操作将无法识别,并新建另一个类似 C4 的补丁(而这个补丁很可能无法整洁的整合入历史,因为补丁中的修改已经存在于某个地方了)。
要想上述方案有效,还需要对方在变基时确保 C4' 和 C4 是几乎一样的。
否则变基操作将无法识别,并新建另一个类似 C4 的补丁(而这个补丁很可能无法整洁的整合入历史,因为补丁中的修改已经存在于某个地方了)。

在本例中另一种简单的方法是使用 `git pull --rebase` 命令而不是直接 `git pull`。又或者你可以自己手动完成这个过程,先 `git fetch`,再 `git rebase teamone/master`。
在本例中另一种简单的方法是使用 `git pull --rebase` 命令而不是直接 `git pull`。
又或者你可以自己手动完成这个过程,先 `git fetch`,再 `git rebase teamone/master`。

如果你习惯使用 `git pull` ,同时又希望默认使用选项 `--rebase`,你可以执行这条语句 `git config --global pull.rebase true` 来更改 `pull.rebase` 的默认配置。

Expand Down
49 changes: 33 additions & 16 deletions book/03-git-branching/sections/remote-branches.asc
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
=== 远程分支

(((branches, remote)))(((references, remote)))
远程分支是对远程仓库的分支状态的引用(指针)。
你在本地不能移动远程分支,当你做任何网络通信操作时,它们会自动移动。
远程分支像是你上次连接到远程仓库时,那些分支所处状态的书签。
远程引用是对远程仓库的引用(指针),包括分支、标签等等。
你可以通过 `git ls-remote (remote)` 来显式地获得远程引用的完整列表,或者通过 `git remote show (remote)` 获得远程分支的更多信息。
然而,一个更常见的做法是利用远程跟踪分支。

远程跟踪分支是远程分支状态的引用。
它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。
远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。

它们以 `(remote)/(branch)` 形式命名。
例如,如果你想要看你最后一次与远程仓库 `origin` 通信时 `master` 分支的状态,你可以查看 `origin/master` 分支。
Expand All @@ -18,13 +22,15 @@ Git 也会给你一个与 origin 的 `master` 分支在指向同一个地方的
[NOTE]
.``origin'' 并无特殊含义
====
远程仓库名字 ``origin'' 与分支名字 ``master'' 一样,在 Git 中并没有任何特别的含义一样。同时 ``master'' 是当你运行 `git init` 时默认的起始分支名字,原因仅仅是它的广泛使用,``origin'' 是当你运行 `git clone` 时默认的远程仓库名字。如果你运行 `git clone -o booyah`,那么你默认的远程分支名字将会是 `booyah/master`。(((origin)))
远程仓库名字 ``origin'' 与分支名字 ``master'' 一样,在 Git 中并没有任何特别的含义一样。
同时 ``master'' 是当你运行 `git init` 时默认的起始分支名字,原因仅仅是它的广泛使用,``origin'' 是当你运行 `git clone` 时默认的远程仓库名字。
如果你运行 `git clone -o booyah`,那么你默认的远程分支名字将会是 `booyah/master`。(((origin)))
====

.克隆之后的服务器与本地仓库
image::images/remote-branches-1.png[克隆之后的服务器与本地仓库。]

如果你在本地的 master 分支做了一些工作,然而在同一时间,其他人推送提交到 `git.ourcompany.com` 并更新了它的 `master` 分支,那么你的提交历史将向不同的方向前进。
如果你在本地的 `master` 分支做了一些工作,然而在同一时间,其他人推送提交到 `git.ourcompany.com` 并更新了它的 `master` 分支,那么你的提交历史将向不同的方向前进。
也许,只要你不与 origin 服务器连接,你的 `origin/master` 指针就不会移动。

.本地与远程的工作可以分叉
Expand All @@ -45,7 +51,7 @@ image::images/remote-branches-3.png[`git fetch` 更新你的远程仓库引用
image::images/remote-branches-4.png[添加另一个远程仓库。]

现在,可以运行 `git fetch teamone` 来抓取远程仓库 `teamone` 有而本地没有的数据。
因为那台服务器上现有的数据是 `origin` 服务器上的一个子集,所以 Git 并不会抓取数据而是会设置远程分支 `teamone/master` 指向 `teamone` 的 `master` 分支。
因为那台服务器上现有的数据是 `origin` 服务器上的一个子集,所以 Git 并不会抓取数据而是会设置远程跟踪分支 `teamone/master` 指向 `teamone` 的 `master` 分支。

.远程跟踪分支 `teamone/master`
image::images/remote-branches-5.png[远程跟踪分支 `teamone/master`。]
Expand Down Expand Up @@ -83,9 +89,11 @@ Git 自动将 `serverfix` 分支名字展开为 `refs/heads/serverfix:refs/heads
[NOTE]
.如何避免每次输入密码
====
如果你正在使用 HTTPS URL 来推送,Git 服务器会询问用户名与密码。默认情况下它会在终端中提示服务器是否允许你进行推送。
如果你正在使用 HTTPS URL 来推送,Git 服务器会询问用户名与密码。
默认情况下它会在终端中提示服务器是否允许你进行推送。

如果不想在每一次推送时都输入用户名与密码,你可以设置一个 ``credential cache''。最简单的方式就是将其保存在内存中几分钟,可以简单地运行 `git config --global credential.helper cache` 来设置它。
如果不想在每一次推送时都输入用户名与密码,你可以设置一个 ``credential cache''。
最简单的方式就是将其保存在内存中几分钟,可以简单地运行 `git config --global credential.helper cache` 来设置它。

想要了解更多关于不同验证缓存的可用选项,查看 <<_credential_caching>>。
====
Expand All @@ -103,11 +111,11 @@ From https://github.com/schacon/simplegit
* [new branch] serverfix -> origin/serverfix
----

要特别注意的一点是当抓取到新的远程分支时,本地不会自动生成一份可编辑的副本(拷贝)。
要特别注意的一点是当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。
换一句话说,这种情况下,不会有一个新的 `serverfix` 分支 - 只有一个不可以修改的 `origin/serverfix` 指针。

可以运行 `git merge origin/serverfix` 将这些工作合并到当前所在的分支。
如果想要在自己的 `serverfix` 分支上工作,可以将其建立在远程分支之上
如果想要在自己的 `serverfix` 分支上工作,可以将其建立在远程跟踪分支之上

[source,console]
----
Expand All @@ -122,7 +130,7 @@ Switched to a new branch 'serverfix'
==== 跟踪分支

(((branches, tracking)))(((branches, upstream)))
从一个远程分支检出一个本地分支会自动创建一个叫做 ``跟踪分支''(有时候也叫做 ``上游分支'')。
从一个远程跟踪分支检出一个本地分支会自动创建一个叫做 ``跟踪分支''(有时候也叫做 ``上游分支'')。
跟踪分支是与远程分支有直接关系的本地分支。
如果在一个跟踪分支上输入 `git pull`,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。

Expand Down Expand Up @@ -160,10 +168,12 @@ Branch serverfix set up to track remote branch serverfix from origin.
[NOTE]
.上游快捷方式
====
当设置好跟踪分支后,可以通过 `@{upstream}` 或 `@{u}` 快捷方式来引用它。所以在 `master` 分支时并且它正在跟踪 `origin/master` 时,如果愿意的话可以使用 `git merge @{u}` 来取代 `git merge origin/master`。
当设置好跟踪分支后,可以通过 `@{upstream}` 或 `@{u}` 快捷方式来引用它。
所以在 `master` 分支时并且它正在跟踪 `origin/master` 时,如果愿意的话可以使用 `git merge @{u}` 来取代 `git merge origin/master`。
====

如果想要查看设置的所有跟踪分支,可以使用 `git branch` 的 `-vv` 选项。这会将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。
如果想要查看设置的所有跟踪分支,可以使用 `git branch` 的 `-vv` 选项。
这会将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。

[source,console]
----
Expand All @@ -174,9 +184,15 @@ $ git branch -vv
testing 5ea463a trying something new
----

这里可以看到 `iss53` 分支正在跟踪 `origin/iss53` 并且 ``ahead'' 是 2,意味着本地有两个提交还没有推送到服务器上。也能看到 `master` 分支正在跟踪 `origin/master` 分支并且是最新的。接下来可以看到 `serverfix` 分支正在跟踪 `teamone` 服务器上的 `server-fix-good` 分支并且领先 2 落后 1,意味着服务器上有一次提交还没有合并入同时本地有三次提交还没有推送。最后看到 `testing` 分支并没有跟踪任何远程分支。
这里可以看到 `iss53` 分支正在跟踪 `origin/iss53` 并且 ``ahead'' 是 2,意味着本地有两个提交还没有推送到服务器上。
也能看到 `master` 分支正在跟踪 `origin/master` 分支并且是最新的。
接下来可以看到 `serverfix` 分支正在跟踪 `teamone` 服务器上的 `server-fix-good` 分支并且领先 2 落后 1,意味着服务器上有一次提交还没有合并入同时本地有三次提交还没有推送。
最后看到 `testing` 分支并没有跟踪任何远程分支。

需要重点注意的一点是这些数字的值来自于你从每个服务器上最后一次抓取的数据。这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据。如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。可以像这样做:`$ git fetch --all; git branch -vv`
需要重点注意的一点是这些数字的值来自于你从每个服务器上最后一次抓取的数据。
这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据。
如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。
可以像这样做:`$ git fetch --all; git branch -vv`

==== 拉取

Expand All @@ -203,4 +219,5 @@ To https://github.com/schacon/simplegit
- [deleted] serverfix
----

基本上这个命令做的只是从服务器上移除这个指针。Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。
基本上这个命令做的只是从服务器上移除这个指针。
Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。