diff --git a/book/07-git-tools/sections/advanced-merging.asc b/book/07-git-tools/sections/advanced-merging.asc index b186643f..43961924 100644 --- a/book/07-git-tools/sections/advanced-merging.asc +++ b/book/07-git-tools/sections/advanced-merging.asc @@ -1,9 +1,13 @@ [[_advanced_merging]] === 高级合并 -在 Git 中合并是相当容易的。因为 Git 使多次合并另一个分支变得很容易,这意味着你可以有一个始终保持最新的长期分支,经常解决小的冲突,比在一系列提交后解决一个巨大的冲突要好。 +在 Git 中合并是相当容易的。 +因为 Git 使多次合并另一个分支变得很容易,这意味着你可以有一个始终保持最新的长期分支,经常解决小的冲突,比在一系列提交后解决一个巨大的冲突要好。 -然而,有时也会有棘手的冲突。不像其他的版本控制系统,Git 并不会尝试过于聪明的合并冲突解决方案。Git 的哲学是聪明地决定无歧义的合并方案,但是如果有冲突,它不会尝试智能地自动解决它。因此,如果很久之后才合并两个分叉的分支,你可能会撞上一些问题。 +然而,有时也会有棘手的冲突。 +不像其他的版本控制系统,Git 并不会尝试过于聪明的合并冲突解决方案。 +Git 的哲学是聪明地决定无歧义的合并方案,但是如果有冲突,它不会尝试智能地自动解决它。 +因此,如果很久之后才合并两个分叉的分支,你可能会撞上一些问题。 在本节中,我们将会仔细查看那些问题是什么以及 Git 给了我们什么工具来帮助我们处理这些更难办的情形。我们也会了解你可以做的不同的、非标准类型的合并,也会看到如何后退到合并之前。 @@ -11,9 +15,13 @@ 我们在 <<_basic_merge_conflicts>> 介绍了解决合并冲突的一些基础知识,对于更复杂的冲突,Git 提供了几个工具来帮助你指出将会发生什么以及如何更好地处理冲突。 -首先,在做一次可能有冲突的合并前尽可能保证工作目录是干净的。如果你有正在做的工作,要么提交到一个临时分支要么储藏它。这使你可以撤消在这里尝试做的*任何事情*。如果在你尝试一次合并时工作目录中有未保存的改动,下面的这些技巧可能会使你丢失那些工作。 +首先,在做一次可能有冲突的合并前尽可能保证工作目录是干净的。 +如果你有正在做的工作,要么提交到一个临时分支要么储藏它。 +这使你可以撤消在这里尝试做的*任何事情*。 +如果在你尝试一次合并时工作目录中有未保存的改动,下面的这些技巧可能会使你丢失那些工作。 -让我们通过一个非常简单的例子来了解一下。我们有一个超级简单的打印 'hello world' 的 Ruby 文件。 +让我们通过一个非常简单的例子来了解一下。 +我们有一个超级简单的打印 'hello world' 的 Ruby 文件。 [source,ruby] ---- @@ -26,7 +34,8 @@ end hello() ---- -在我们的仓库中,创建一个名为 `whitespace` 的新分支并将所有 Unix 换行符修改为 DOS 换行符,实质上虽然改变了文件的每一行,但改变的都只是空白字符。然后我们修改行 ``hello world'' 为 ``hello mundo''。 +在我们的仓库中,创建一个名为 `whitespace` 的新分支并将所有 Unix 换行符修改为 DOS 换行符,实质上虽然改变了文件的每一行,但改变的都只是空白字符。 +然后我们修改行 ``hello world'' 为 ``hello mundo''。 [source,console] ---- @@ -40,7 +49,7 @@ $ git commit -am 'converted hello.rb to DOS' 1 file changed, 7 insertions(+), 7 deletions(-) $ vim hello.rb -$ git diff -w +$ git diff -b diff --git a/hello.rb b/hello.rb index ac51efd..e85207e 100755 --- a/hello.rb @@ -99,7 +108,9 @@ Automatic merge failed; fix conflicts and then commit the result. [[_abort_merge]] ===== 中断一次合并 -我们现在有几个选项。首先,让我们介绍如何摆脱这个情况。你可能不想处理冲突这种情况,完全可以通过 `git merge --abort` 来简单地退出合并。 +我们现在有几个选项。 +首先,让我们介绍如何摆脱这个情况。 +你可能不想处理冲突这种情况,完全可以通过 `git merge --abort` 来简单地退出合并。 [source,console] ---- @@ -113,19 +124,25 @@ $ git status -sb ## master ---- -`git merge --abort` 选项会尝试恢复到你运行合并前的状态。但当运行命令前,在工作目录中有未储藏、未提交的修改时它不能完美处理,除此之外它都工作地很好。 +`git merge --abort` 选项会尝试恢复到你运行合并前的状态。 +但当运行命令前,在工作目录中有未储藏、未提交的修改时它不能完美处理,除此之外它都工作地很好。 -如果因为某些原因你发现自己处在一个混乱的状态中然后只是想要重来一次,也可以运行 `git reset --hard HEAD` 回到之前的状态或其他你想要恢复的状态。请牢记这会将清除工作目录中的所有内容,所以确保你不需要保存这里的任意改动。 +如果因为某些原因你发现自己处在一个混乱的状态中然后只是想要重来一次,也可以运行 `git reset --hard HEAD` 回到之前的状态或其他你想要恢复的状态。 +请牢记这会将清除工作目录中的所有内容,所以确保你不需要保存这里的任意改动。 ===== 忽略空白 -在这个特定的例子中,冲突与空白有关。我们知道这点是因为这个例子很简单,但是在实际的例子中发现这样的冲突也很容易,因为每一行都被移除而在另一边每一行又被加回来了。默认情况下,Git 认为所有这些行都改动了,所以它不会合并文件。 +在这个特定的例子中,冲突与空白有关。 +我们知道这点是因为这个例子很简单,但是在实际的例子中发现这样的冲突也很容易,因为每一行都被移除而在另一边每一行又被加回来了。 +默认情况下,Git 认为所有这些行都改动了,所以它不会合并文件。 -默认合并策略可以带有参数,其中的几个正好是关于忽略空白改动的。如果你看到在一次合并中有大量的空白问题,你可以简单地中止它并重做一次,这次使用 `-Xignore-all-space` 或 `-Xignore-space-change` 选项。第一个选项忽略任意 **数量** 的已有空白的修改,第二个选项忽略所有空白修改。 +默认合并策略可以带有参数,其中的几个正好是关于忽略空白改动的。 +如果你看到在一次合并中有大量的空白问题,你可以简单地中止它并重做一次,这次使用 `-Xignore-all-space` 或 `-Xignore-space-change` 选项。 +第一个选项忽略任意 **数量** 的已有空白的修改,第二个选项忽略所有空白修改。 [source,console] ---- -$ git merge -Xignore-all-space whitespace +$ git merge -Xignore-space-change whitespace Auto-merging hello.rb Merge made by the 'recursive' strategy. hello.rb | 2 +- @@ -139,13 +156,19 @@ Merge made by the 'recursive' strategy. [[_manual_remerge]] ===== 手动文件再合并 -虽然 Git 对空白的预处理做得很好,还有很多其他类型的修改,Git 也许无法自动处理,但是脚本可以处理它们。例如,假设 Git 无法处理空白修改因此我们需要手动处理。 +虽然 Git 对空白的预处理做得很好,还有很多其他类型的修改,Git 也许无法自动处理,但是脚本可以处理它们。 +例如,假设 Git 无法处理空白修改因此我们需要手动处理。 -我们真正想要做的是对将要合并入的文件在真正合并前运行 `dos2unix` 程序。所以如果那样的话,我们该如何做? +我们真正想要做的是对将要合并入的文件在真正合并前运行 `dos2unix` 程序。 +所以如果那样的话,我们该如何做? -首先,我们进入到了合并冲突状态。然后我们想要我的版本的文件,他们的版本的文件(从我们将要合并入的分支)和共同的版本的文件(从分支叉开时的位置)的拷贝。然后我们想要修复任何一边的文件,并且为这个单独的文件重试一次合并。 +首先,我们进入到了合并冲突状态。 +然后我们想要我的版本的文件,他们的版本的文件(从我们将要合并入的分支)和共同的版本的文件(从分支叉开时的位置)的拷贝。 +然后我们想要修复任何一边的文件,并且为这个单独的文件重试一次合并。 -获得这三个文件版本实际上相当容易。Git 在索引中存储了所有这些版本,在 ``stages'' 下每一个都有一个数字与它们关联。Stage 1 是它们共同的祖先版本,stage 2 是你的版本,stage 3 来自于 `MERGE_HEAD`,即你将要合并入的版本(``theirs'')。 +获得这三个文件版本实际上相当容易。 +Git 在索引中存储了所有这些版本,在 ``stages'' 下每一个都有一个数字与它们关联。 +Stage 1 是它们共同的祖先版本,stage 2 是你的版本,stage 3 来自于 `MERGE_HEAD`,即你将要合并入的版本(``theirs'')。 通过 `git show` 命令与一个特别的语法,你可以将冲突文件的这些版本释放出一份拷贝。 @@ -156,7 +179,7 @@ $ git show :2:hello.rb > hello.ours.rb $ git show :3:hello.rb > hello.theirs.rb ---- -如果你想要更专业一点,也可以使用 `ls-files -u` 底层命令来得到这些文件的 Git blob 对象的实际 SHA 值。 +如果你想要更专业一点,也可以使用 `ls-files -u` 底层命令来得到这些文件的 Git blob 对象的实际 SHA-1 值。 [source,console] ---- @@ -166,7 +189,7 @@ $ git ls-files -u 100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb ---- -`:1:hello.rb` 只是查找那个 blob 对象 SHA 值的简写。 +`:1:hello.rb` 只是查找那个 blob 对象 SHA-1 值的简写。 既然在我们的工作目录中已经有这所有三个阶段的内容,我们可以手工修复它们来修复空白问题,然后使用鲜为人知的 `git merge-file` 命令来重新合并那个文件。 @@ -178,7 +201,7 @@ dos2unix: converting file hello.theirs.rb to Unix format ... $ git merge-file -p \ hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb -$ git diff -w +$ git diff -b diff --cc hello.rb index 36c06c8,e85207e..0000000 --- a/hello.rb @@ -195,9 +218,12 @@ index 36c06c8,e85207e..0000000 hello() ---- -在这时我们已经漂亮地合并了那个文件。实际上,这比使用 `ignore-all-space` 选项要更好,因为在合并前真正地修复了空白修改而不是简单地忽略它们。在使用 `ignore-all-space` 进行合并操作后,我们最终得到了有几行是 DOS 行尾的文件,从而使提交内容混乱了。 +在这时我们已经漂亮地合并了那个文件。 +实际上,这比使用 `ignore-space-change` 选项要更好,因为在合并前真正地修复了空白修改而不是简单地忽略它们。 +在使用 `ignore-space-change` 进行合并操作后,我们最终得到了有几行是 DOS 行尾的文件,从而使提交内容混乱了。 -如果你想要在最终提交前看一下我们这边与另一边之间实际的修改,你可以使用 `git diff` 来比较将要提交作为合并结果的工作目录与其中任意一个阶段的文件差异。让我们看看它们。 +如果你想要在最终提交前看一下我们这边与另一边之间实际的修改,你可以使用 `git diff` 来比较将要提交作为合并结果的工作目录与其中任意一个阶段的文件差异。 +让我们看看它们。 要在合并前比较结果与在你的分支上的内容,换一句话说,看看合并引入了什么,可以运行 `git diff --ours` @@ -222,11 +248,12 @@ index 36c06c8..44d0a25 100755 这里我们可以很容易地看到在我们的分支上发生了什么,在这次合并中我们实际引入到这个文件的改动,是修改了其中一行。 -如果我们想要查看合并的结果与他们那边有什么不同,可以运行 `git diff --theirs`。在本例及后续的例子中,我们会使用 `-w` 来去除空白,因为我们将它与 Git 中的,而不是我们清理过的 `hello.theirs.rb` 文件比较。 +如果我们想要查看合并的结果与他们那边有什么不同,可以运行 `git diff --theirs`。 +在本例及后续的例子中,我们会使用 `-b` 来去除空白,因为我们将它与 Git 中的,而不是我们清理过的 `hello.theirs.rb` 文件比较。 [source,console] ---- -$ git diff --theirs -w +$ git diff --theirs -b * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index e85207e..44d0a25 100755 @@ -245,7 +272,7 @@ index e85207e..44d0a25 100755 [source,console] ---- -$ git diff --base -w +$ git diff --base -b * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index ac51efd..44d0a25 100755 @@ -278,7 +305,8 @@ Removing hello.theirs.rb 也许有时我们并不满意这样的解决方案,或许有时还要手动编辑一边或者两边的冲突,但还是依旧无法正常工作,这时我们需要更多的上下文关联来解决这些冲突。 -让我们来稍微改动下例子。对于本例,我们有两个长期分支,每一个分支都有几个提交,但是在合并时却创建了一个合理的冲突。 +让我们来稍微改动下例子。 +对于本例,我们有两个长期分支,每一个分支都有几个提交,但是在合并时却创建了一个合理的冲突。 [source,console] ---- @@ -293,7 +321,8 @@ $ git log --graph --oneline --decorate --all * b7dcc89 initial hello world code ---- -现在有只在 `master` 分支上的三次单独提交,还有其他三次提交在 `mundo` 分支上。如果我们尝试将 `mundo` 分支合并入 `master` 分支,我们得到一个冲突。 +现在有只在 `master` 分支上的三次单独提交,还有其他三次提交在 `mundo` 分支上。 +如果我们尝试将 `mundo` 分支合并入 `master` 分支,我们得到一个冲突。 [source,console] ---- @@ -303,7 +332,8 @@ CONFLICT (content): Merge conflict in hello.rb Automatic merge failed; fix conflicts and then commit the result. ---- -我们想要看一下合并冲突是什么。如果我们打开这个文件,我们将会看到类似下面的内容: +我们想要看一下合并冲突是什么。 +如果我们打开这个文件,我们将会看到类似下面的内容: [source,ruby] ---- @@ -322,11 +352,16 @@ hello() 合并的两边都向这个文件增加了内容,但是导致冲突的原因是其中一些提交修改了文件的同一个地方。 -让我们探索一下现在你手边可用来查明这个冲突是如何产生的工具。应该如何修复这个冲突看起来或许并不明显。这时你需要更多上下文。 +让我们探索一下现在你手边可用来查明这个冲突是如何产生的工具。 +应该如何修复这个冲突看起来或许并不明显。 +这时你需要更多上下文。 -一个很有用的工具是带 `--conflict` 选项的 `git checkout`。这会重新检出文件并替换合并冲突标记。如果想要重置标记并尝试再次解决它们的话这会很有用。 +一个很有用的工具是带 `--conflict` 选项的 `git checkout`。 +这会重新检出文件并替换合并冲突标记。 +如果想要重置标记并尝试再次解决它们的话这会很有用。 -可以传递给 `--conflict` 参数 `diff3` 或 `merge`(默认选项)。如果传给它 `diff3`,Git 会使用一个略微不同版本的冲突标记:不仅仅只给你 ``ours'' 和 ``theirs'' 版本,同时也会有 ``base'' 版本在中间来给你更多的上下文。 +可以传递给 `--conflict` 参数 `diff3` 或 `merge`(默认选项)。 +如果传给它 `diff3`,Git 会使用一个略微不同版本的冲突标记:不仅仅只给你 ``ours'' 和 ``theirs'' 版本,同时也会有 ``base'' 版本在中间来给你更多的上下文。 [source,console] ---- @@ -366,7 +401,9 @@ $ git config --global merge.conflictstyle diff3 [[_merge_log]] ===== 合并日志 -另一个解决合并冲突有用的工具是 `git log`。这可以帮助你得到那些对冲突有影响的上下文。回顾一点历史来记起为什么两条线上的开发会触碰同一片代码有时会很有用。 +另一个解决合并冲突有用的工具是 `git log`。 +这可以帮助你得到那些对冲突有影响的上下文。 +回顾一点历史来记起为什么两条线上的开发会触碰同一片代码有时会很有用。 为了得到此次合并中包含的每一个分支的所有独立提交的列表,我们可以使用之前在 <<_triple_dot>> 学习的 ``三点'' 语法。 @@ -383,7 +420,8 @@ $ git log --oneline --left-right HEAD...MERGE_HEAD 这个漂亮的列表包含 6 个提交和每一个提交所在的不同开发路径。 -我们可以通过更加特定的上下文来进一步简化这个列表。如果我们添加 `--merge` 选项到 `git log` 中,它会只显示任何一边接触了合并冲突文件的提交。 +我们可以通过更加特定的上下文来进一步简化这个列表。 +如果我们添加 `--merge` 选项到 `git log` 中,它会只显示任何一边接触了合并冲突文件的提交。 [source,console] ---- @@ -392,11 +430,13 @@ $ git log --oneline --left-right --merge > c3ffff1 changed text to hello mundo ---- -如果你运行命令时用 `-p` 选项代替,你会得到所有冲突文件的区别。快速获得你需要帮助理解为什么发生冲突的上下文,以及如何聪明地解决它,这会 **非常** 有用。 +如果你运行命令时用 `-p` 选项代替,你会得到所有冲突文件的区别。 +快速获得你需要帮助理解为什么发生冲突的上下文,以及如何聪明地解决它,这会 **非常** 有用。 ===== 组合式差异格式 -因为 Git 暂存合并成功的结果,当你在合并冲突状态下运行 `git diff` 时,只会得到现在还在冲突状态的区别。当需要查看你还需要解决哪些冲突时这很有用。 +因为 Git 暂存合并成功的结果,当你在合并冲突状态下运行 `git diff` 时,只会得到现在还在冲突状态的区别。 +当需要查看你还需要解决哪些冲突时这很有用。 在合并冲突后直接运行的 `git diff` 会给你一个相当独特的输出格式。 @@ -421,9 +461,11 @@ index 0399cd5,59727f0..0000000 hello() ---- -这种叫作 ``组合式差异'' 的格式会在每一行给你两列数据。第一列为你显示 ``ours'' 分支与工作目录的文件区别(添加或删除),第二列显示 ``theirs'' 分支与工作目录的拷贝区别。 +这种叫作 ``组合式差异'' 的格式会在每一行给你两列数据。 +第一列为你显示 ``ours'' 分支与工作目录的文件区别(添加或删除),第二列显示 ``theirs'' 分支与工作目录的拷贝区别。 -所以在上面的例子中可以看到 `<<<<<<<` 与 `>>>>>>>` 行在工作拷贝中但是并不在合并的任意一边中。这很有意义,合并工具因为我们的上下文被困住了,它期望我们去移除它们。 +所以在上面的例子中可以看到 `<<<<<<<` 与 `>>>>>>>` 行在工作拷贝中但是并不在合并的任意一边中。 +这很有意义,合并工具因为我们的上下文被困住了,它期望我们去移除它们。 如果我们解决冲突再次运行 `git diff`,我们将会看到同样的事情,但是它有一点帮助。 @@ -447,9 +489,11 @@ index 0399cd5,59727f0..0000000 hello() ---- -这里显示出 ``hola world'' 在我们这边但不在工作拷贝中,那个 ``hello mundo'' 在他们那边但不在工作拷贝中,最终 ``hola mundo'' 不在任何一边但是现在在工作拷贝中。在提交解决方案前这对审核很有用。 +这里显示出 ``hola world'' 在我们这边但不在工作拷贝中,那个 ``hello mundo'' 在他们那边但不在工作拷贝中,最终 ``hola mundo'' 不在任何一边但是现在在工作拷贝中。 +在提交解决方案前这对审核很有用。 -也可以在合并后通过 `git log` 来获取相同信息,并查看冲突是如何解决的。如果你对一个合并提交运行 `git show` 命令 Git 将会输出这种格式。 +也可以在合并后通过 `git log` 来获取相同信息,并查看冲突是如何解决的。 +如果你对一个合并提交运行 `git show` 命令 Git 将会输出这种格式,或者你也可以在 `git log -p`(默认情况下该命令只会展示还没有合并的补丁)命令之后加上 `--cc` 选项。 [source,console] ---- @@ -567,15 +611,21 @@ image::images/undomerge-revert3.png[在重新合并一个还原合并后的历 ==== 其他类型的合并 -到目前为止我们介绍的都是通过一个叫作 ``recursive'' 的合并策略来正常处理的两个分支的正常合并。然而还有其他方式来合并两个分支到一起。让我们来快速介绍其中的几个。 +到目前为止我们介绍的都是通过一个叫作 ``recursive'' 的合并策略来正常处理的两个分支的正常合并。 +然而还有其他方式来合并两个分支到一起。 +让我们来快速介绍其中的几个。 ===== 我们的或他们的偏好 -首先,有另一种我们可以通过 ``recursive'' 合并模式做的有用工作。我们之前已经看到传递给 `-X` 的 `ignore-all-space` 与 `ignore-space-change` 选项,但是我们也可以告诉 Git 当它看见一个冲突时直接选择一边。 +首先,有另一种我们可以通过 ``recursive'' 合并模式做的有用工作。 +我们之前已经看到传递给 `-X` 的 `ignore-all-space` 与 `ignore-space-change` 选项,但是我们也可以告诉 Git 当它看见一个冲突时直接选择一边。 -默认情况下,当 Git 看到两个分支合并中的冲突时,它会将合并冲突标记添加到你的代码中并标记文件为冲突状态来让你解决。如果你希望 Git 简单地选择特定的一边并忽略另外一边而不是让你手动合并冲突,你可以传递给 `merge` 命令一个 `-Xours` 或 `-Xtheirs` 参数。 +默认情况下,当 Git 看到两个分支合并中的冲突时,它会将合并冲突标记添加到你的代码中并标记文件为冲突状态来让你解决。 +如果你希望 Git 简单地选择特定的一边并忽略另外一边而不是让你手动合并冲突,你可以传递给 `merge` 命令一个 `-Xours` 或 `-Xtheirs` 参数。 -如果 Git 看到这个,它并不会增加冲突标记。任何可以合并的区别,它会直接合并。任何有冲突的区别,它会简单地选择你全局指定的一边,包括二进制文件。 +如果 Git 看到这个,它并不会增加冲突标记。 +任何可以合并的区别,它会直接合并。 +任何有冲突的区别,它会简单地选择你全局指定的一边,包括二进制文件。 如果我们回到之前我们使用的 ``hello world'' 例子中,我们可以看到合并入我们的分支时引发了冲突。 @@ -601,13 +651,17 @@ Merge made by the 'recursive' strategy. create mode 100644 test.sh ---- -在上例中,它并不会为 ``hello mundo'' 与 ``hola world'' 标记合并冲突,它只会简单地选取 ``hola world''。然而,在那个分支上所有其他非冲突的改动都可以被成功地合并入。 +在上例中,它并不会为 ``hello mundo'' 与 ``hola world'' 标记合并冲突,它只会简单地选取 ``hola world''。 +然而,在那个分支上所有其他非冲突的改动都可以被成功地合并入。 这个选项也可以传递给我们之前看到的 `git merge-file` 命令,通过运行类似 `git merge-file --ours` 的命令来合并单个文件。 -如果想要做类似的事情但是甚至并不想让 Git 尝试合并另外一边的修改,有一个更严格的选项,它是 ``ours'' 合并 _策略_。这与 ``ours'' recursive 合并 _选项_ 不同。 +如果想要做类似的事情但是甚至并不想让 Git 尝试合并另外一边的修改,有一个更严格的选项,它是 ``ours'' 合并 _策略_。 +这与 ``ours'' recursive 合并 _选项_ 不同。 -这本质上会做一次假的合并。它会记录一个以两边分支作为父结点的新合并提交,但是它甚至根本不关注你正合并入的分支。它只会简单地把当前分支的代码当作合并结果记录下来。 +这本质上会做一次假的合并。 +它会记录一个以两边分支作为父结点的新合并提交,但是它甚至根本不关注你正合并入的分支。 +它只会简单地把当前分支的代码当作合并结果记录下来。 [source,console] ---- @@ -619,6 +673,9 @@ $ 你可以看到合并后与合并前我们的分支并没有任何区别。 -当再次合并时从本质上欺骗 Git 认为那个分支已经合并过经常是很有用的。例如,假设你有一个分叉的 `release` 分支并且在上面做了一些你想要在未来某个时候合并回 `master` 的工作。与此同时 `master` 分支上的某些 bugfix 需要向后移植回 `release` 分支。你可以合并 bugfix 分支进入 `release` 分支同时也 `merge -s ours` 合并进入你的 `master` 分支(即使那个修复已经在那儿了)这样当你之后再次合并 `release` 分支时,就不会有来自 bugfix 的冲突。 +当再次合并时从本质上欺骗 Git 认为那个分支已经合并过经常是很有用的。 +例如,假设你有一个分叉的 `release` 分支并且在上面做了一些你想要在未来某个时候合并回 `master` 的工作。 +与此同时 `master` 分支上的某些 bugfix 需要向后移植回 `release` 分支。 +你可以合并 bugfix 分支进入 `release` 分支同时也 `merge -s ours` 合并进入你的 `master` 分支(即使那个修复已经在那儿了)这样当你之后再次合并 `release` 分支时,就不会有来自 bugfix 的冲突。 include::subtree-merges.asc[] diff --git a/book/07-git-tools/sections/bundling.asc b/book/07-git-tools/sections/bundling.asc index b9ba7eb6..a839b9af 100644 --- a/book/07-git-tools/sections/bundling.asc +++ b/book/07-git-tools/sections/bundling.asc @@ -3,11 +3,18 @@ 虽然我们已经了解了网络传输 Git 数据的常用方法(如 HTTP,SSH 等),但还有另外一种不太常见却又十分有用的方式。 -Git 可以将它的数据 ``打包'' 到一个文件中。这在许多场景中都很有用。有可能你的网络中断了,但你又希望将你的提交传给你的合作者们。可能你不在办公网中并且出于安全考虑没有给你接入内网的权限。可能你的无线、有线网卡坏掉了。可能你现在没有共享服务器的权限,你又希望通过邮件将更新发送给别人,却不希望通过 `format-patch` 的方式传输 40 个提交。 +Git 可以将它的数据 ``打包'' 到一个文件中。 +这在许多场景中都很有用。 +有可能你的网络中断了,但你又希望将你的提交传给你的合作者们。 +可能你不在办公网中并且出于安全考虑没有给你接入内网的权限。 +可能你的无线、有线网卡坏掉了。 +可能你现在没有共享服务器的权限,你又希望通过邮件将更新发送给别人,却不希望通过 `format-patch` 的方式传输 40 个提交。 -这些情况下 `git bundle` 就会很有用。`bundle` 命令会将 `git push` 命令所传输的所有内容打包成一个二进制文件,你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。 +这些情况下 `git bundle` 就会很有用。 +`bundle` 命令会将 `git push` 命令所传输的所有内容打包成一个二进制文件,你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。 -来看看一个简单的例子。假设你有一个包含两个提交的仓库: +来看看一个简单的例子。 +假设你有一个包含两个提交的仓库: [source,console] ---- @@ -37,11 +44,14 @@ Writing objects: 100% (6/6), 441 bytes, done. Total 6 (delta 0), reused 0 (delta 0) ---- -然后你就会有一个名为 `repo.bundle` 的文件,该文件包含了所有重建该仓库 `master` 分支所需的数据。在使用 `bundle` 命令时,你需要列出所有你希望打包的引用或者提交的区间。如果你希望这个仓库可以在别处被克隆,你应该像例子中那样增加一个 HEAD 引用。 +然后你就会有一个名为 `repo.bundle` 的文件,该文件包含了所有重建该仓库 `master` 分支所需的数据。 +在使用 `bundle` 命令时,你需要列出所有你希望打包的引用或者提交的区间。 +如果你希望这个仓库可以在别处被克隆,你应该像例子中那样增加一个 HEAD 引用。 你可以将这个 `repo.bundle` 文件通过邮件或者U盘传给别人。 -另一方面,假设别人传给你一个 `repo.bundle` 文件并希望你在这个项目上工作。你可以从这个二进制文件中克隆出一个目录,就像从一个 URL 克隆一样。 +另一方面,假设别人传给你一个 `repo.bundle` 文件并希望你在这个项目上工作。 +你可以从这个二进制文件中克隆出一个目录,就像从一个 URL 克隆一样。 [source,console] ---- @@ -67,9 +77,14 @@ c99cf5b fourth commit - second repo b1ec324 first commit ---- -首先我们需要确认我们希望被打包的提交区间。和网络协议不太一样,网络协议会自动计算出所需传输的最小数据集,而我们需要手动计算。当然你可以像上面那样将整个仓库打包,但最好仅仅打包变更的部分 —— 就是我们刚刚在本地做的 3 个提交。 +首先我们需要确认我们希望被打包的提交区间。 +和网络协议不太一样,网络协议会自动计算出所需传输的最小数据集,而我们需要手动计算。 +当然你可以像上面那样将整个仓库打包,但最好仅仅打包变更的部分 —— 就是我们刚刚在本地做的 3 个提交。 -为了实现这个目标,你需要计算出差别。就像我们在 <<_commit_ranges>> 介绍的,你有很多种方式去指明一个提交区间。我们可以使用 `origin/master..master` 或者 `master ^origin/master` 之类的方法来获取那 3 个在我们的 master 分支而不在原始仓库中的提交。你可以用 `log` 命令来测试。 +为了实现这个目标,你需要计算出差别。 +就像我们在 <<_commit_ranges>> 介绍的,你有很多种方式去指明一个提交区间。 +我们可以使用 `origin/master..master` 或者 `master ^origin/master` 之类的方法来获取那 3 个在我们的 master 分支而不在原始仓库中的提交。 +你可以用 `log` 命令来测试。 [source,console] ---- @@ -79,7 +94,8 @@ c99cf5b fourth commit - second repo 7011d3d third commit - second repo ---- -这样就获取到我们希望被打包的提交列表,让我们将这些提交打包。我们可以用 `git bundle create` 命令,加上我们想用的文件名,以及要打包的提交区间。 +这样就获取到我们希望被打包的提交列表,让我们将这些提交打包。 +我们可以用 `git bundle create` 命令,加上我们想用的文件名,以及要打包的提交区间。 [source,console] ---- @@ -91,9 +107,11 @@ Writing objects: 100% (9/9), 775 bytes, done. Total 9 (delta 0), reused 0 (delta 0) ---- -现在在我们的目录下会有一个 `commits.bundle` 文件。如果我们把这个文件发送给我们的合作者,她可以将这个文件导入到原始的仓库中,即使在这期间已经有其他的工作提交到这个仓库中。 +现在在我们的目录下会有一个 `commits.bundle` 文件。 +如果我们把这个文件发送给我们的合作者,她可以将这个文件导入到原始的仓库中,即使在这期间已经有其他的工作提交到这个仓库中。 -当她拿到这个包时,她可以在导入到仓库之前查看这个包里包含了什么内容。`bundle verify` 命令可以检查这个文件是否是一个合法的 Git 包,是否拥有共同的祖先来导入。 +当她拿到这个包时,她可以在导入到仓库之前查看这个包里包含了什么内容。 +`bundle verify` 命令可以检查这个文件是否是一个合法的 Git 包,是否拥有共同的祖先来导入。 [source,console] ---- @@ -114,7 +132,8 @@ error: Repository lacks these prerequisite commits: error: 7011d3d8fc200abe0ad561c011c3852a4b7bbe95 third commit - second repo ---- -而我们的第一个包是合法的,所以我们可以从这个包里提取出提交。如果你想查看这边包里可以导入哪些分支,同样有一个命令可以列出这些顶端: +而我们的第一个包是合法的,所以我们可以从这个包里提取出提交。 +如果你想查看这边包里可以导入哪些分支,同样有一个命令可以列出这些顶端: [source,console] ---- @@ -122,7 +141,9 @@ $ git bundle list-heads ../commits.bundle 71b84daaf49abed142a373b6e5c59a22dc6560dc refs/heads/master ---- -`verify` 子命令同样可以告诉你有哪些顶端。该功能的目的是查看哪些是可以被拉入的,所以你可以使用 `fetch` 或者 `pull` 命令从包中导入提交。这里我们要从包中取出 `master` 分支到我们仓库中的 'other-master' 分支: +`verify` 子命令同样可以告诉你有哪些顶端。 +该功能的目的是查看哪些是可以被拉入的,所以你可以使用 `fetch` 或者 `pull` 命令从包中导入提交。 +这里我们要从包中取出 `master` 分支到我们仓库中的 'other-master' 分支: [source,console] ---- diff --git a/book/07-git-tools/sections/debugging.asc b/book/07-git-tools/sections/debugging.asc index e9eea827..c0ddc359 100644 --- a/book/07-git-tools/sections/debugging.asc +++ b/book/07-git-tools/sections/debugging.asc @@ -32,7 +32,7 @@ $ git blame -L 12,22 simplegit.rb 接下来就是行号和文件内容。 注意一下 `^4832fe2` 这个提交的那些行,这些指的是这个文件第一次提交的那些行。 这个提交是这个文件第一次加入到这个项目时的提交,并且这些行从未被修改过。 -这会带来小小的困惑,因为你已经至少看到三种 Git 使用 `^` 来修饰一个提交的 SHA 值的不同含义,但这里确实就是这个意思。 +这会带来小小的困惑,因为你已经至少看到三种 Git 使用 `^` 来修饰一个提交的 SHA-1 值的不同含义,但这里确实就是这个意思。 另一件比较酷的事情是 Git 不会显式地记录文件的重命名。 它会记录快照,然后在事后尝试计算出重命名的动作。 diff --git a/book/07-git-tools/sections/interactive-staging.asc b/book/07-git-tools/sections/interactive-staging.asc index 0ac7de33..a005313e 100644 --- a/book/07-git-tools/sections/interactive-staging.asc +++ b/book/07-git-tools/sections/interactive-staging.asc @@ -201,4 +201,5 @@ simplegit.rb 文件的状态很有趣。 也可以不必在交互式添加模式中做部分文件暂存 - 可以在命令行中使用 `git add -p` 或 `git add --patch` 来启动同样的脚本。 -更进一步地,可以使用 `reset --patch` 命令的补丁模式来部分重置文件。通过 `checkout --patch` 命令来部分检出文件与 `stash save --patch` 命令来部分暂存文件。我们将会在接触这些命令的高级使用方法时了解更多详细信息。 +更进一步地,可以使用 `reset --patch` 命令的补丁模式来部分重置文件,通过 `checkout --patch` 命令来部分检出文件与 `stash save --patch` 命令来部分暂存文件。 +我们将会在接触这些命令的高级使用方法时了解更多详细信息。 diff --git a/book/07-git-tools/sections/replace.asc b/book/07-git-tools/sections/replace.asc index 07065fa1..d86a7436 100644 --- a/book/07-git-tools/sections/replace.asc +++ b/book/07-git-tools/sections/replace.asc @@ -3,11 +3,15 @@ Git 对象是不可改变的,但它提供一种有趣的方式来用其他对象假装替换数据库中的 Git 对象。 -`replace` 命令可以让你在 Git 中指定一个对象并可以声称“每次你遇到这个 Git 对象时,假装它是其他的东西”。在你用一个不同的提交替换历史中的一个提交时,这会非常有用。 +`replace` 命令可以让你在 Git 中指定一个对象并可以声称“每次你遇到这个 Git 对象时,假装它是其他的东西”。 +在你用一个不同的提交替换历史中的一个提交时,这会非常有用。 -例如,你有一个大型的代码历史并想把自己的仓库分成一个短的历史和一个更大更长久的历史,短历史供新的开发者使用,后者给喜欢数据挖掘的人使用。 你可以通过用新仓库中最早的提交 `replace` 老仓库中最新的提交来连接历史,这种方式可以把一条历史移植到其他历史上。这意味着你不用在新历史中真正替换每一个提交(因为历史来源会影响 SHA 的值),你可以加入他们。 +例如,你有一个大型的代码历史并想把自己的仓库分成一个短的历史和一个更大更长久的历史,短历史供新的开发者使用,后者给喜欢数据挖掘的人使用。 +你可以通过用新仓库中最早的提交 `replace` 老仓库中最新的提交来连接历史,这种方式可以把一条历史移植到其他历史上。 +这意味着你不用在新历史中真正替换每一个提交(因为历史来源会影响 SHA 的值),你可以加入他们。 -让我们来试试吧!首先获取一个已经存在的仓库,并将其分成两个仓库,一个是最近的仓库,一个是历史版本的仓库。然后我们将看到如何在不更改仓库 SHA 值的情况下通过 `replace` 命令来合并他们。 +让我们来试试吧。 +首先获取一个已经存在的仓库,并将其分成两个仓库,一个是最近的仓库,一个是历史版本的仓库,然后我们将看到如何在不更改仓库 SHA 值的情况下通过 `replace` 命令来合并他们。 我们将使用一个拥有 5 个提交的简单仓库: @@ -21,7 +25,9 @@ c6e1e95 fourth commit c1822cf first commit ---- -我们想将其分成拆分成两条历史。第一个到第四个提交的作为第一个历史版本,第四、第五个提交的作为最近的第二个历史版本。 +我们想将其分成拆分成两条历史。 +第一个到第四个提交的作为第一个历史版本。 +第四、第五个提交的作为最近的第二个历史版本。 image::images/replace1.png[] @@ -56,7 +62,9 @@ To git@github.com:schacon/project-history.git * [new branch] history -> master ---- -这样一来,我们的历史版本就发布了。稍难的部分则是删减我们最近的历史来让它变得更小。我们需要一个重叠以便于用一个相等的提交来替换另一个提交,这样一来,我们将截断到第四、五个提交。 +这样一来,我们的历史版本就发布了。 +稍难的部分则是删减我们最近的历史来让它变得更小。 +我们需要一个重叠以便于用一个相等的提交来替换另一个提交,这样一来,我们将截断到第四、五个提交。 [source,console] ---- @@ -68,7 +76,8 @@ c6e1e95 (history) fourth commit c1822cf first commit ---- -在这种情况下,创建一个能够指导扩展历史的基础提交是很有用的。这样一来,如果其他的开发者想要修改第一次提交或者其他操作时就知道要做些什么。 因此,接下来我们要做的是用命令创建一个最初的提交对象,然后将剩下的提交(第四、第五个提交)变基到它的上面。 +在这种情况下,创建一个能够指导扩展历史的基础提交是很有用的。 +这样一来,如果其他的开发者想要修改第一次提交或者其他操作时就知道要做些什么,因此,接下来我们要做的是用命令创建一个最初的提交对象,然后将剩下的提交(第四、第五个提交)变基到它的上面。 为了这么做,我们需要选择一个点去拆分,对于我们而言是第三个提交(SHA 是 `9c68fdc`)。因此我们的提交将基于此提交树。我们可以使用 `commit-tree` 命令来创建基础提交,这样我们就有了一个树,并返回一个全新的、无父节点的 SHA 提交对象。 @@ -97,7 +106,8 @@ Applying: fifth commit image::images/replace4.png[] -我们已经用基础提交重写了最近的历史,基础提交包括如何重新组成整个历史的说明。我们可以将新历史推送到新项目中,当其他人克隆这个仓库时,他们仅能看到最近两次提交以及一个包含上述说明的基础提交。 +我们已经用基础提交重写了最近的历史,基础提交包括如何重新组成整个历史的说明。 +我们可以将新历史推送到新项目中,当其他人克隆这个仓库时,他们仅能看到最近两次提交以及一个包含上述说明的基础提交。 现在我们将以想获得整个历史的人的身份来初次克隆这个项目。 在克隆这个截断后的仓库后为了得到历史数据,需要添加第二个远程的历史版本库并对其做获取操作: @@ -134,7 +144,8 @@ c6e1e95 fourth commit c1822cf first commit ---- -为了合并它们,你可以使用 `git replace` 命令加上你想替换的提交信息来进行替换。这样一来,我们就可以将 master 分支中的第四个提交替换为 `project-history/master` 分支中的“第四个”提交。 +为了合并它们,你可以使用 `git replace` 命令加上你想替换的提交信息来进行替换。 +这样一来,我们就可以将 master 分支中的第四个提交替换为 `project-history/master` 分支中的“第四个”提交。 [source,console] ---- @@ -153,11 +164,12 @@ e146b5f fifth commit c1822cf first commit ---- -很酷,是不是?不用改变上游的 SHA 我们就能用一个提交来替换历史中的所有不同的提交,并且所有的工具(`bisect`,`blame` 等)也都奏效。 +很酷,是不是?不用改变上游的 SHA-1 我们就能用一个提交来替换历史中的所有不同的提交,并且所有的工具(`bisect`,`blame` 等)也都奏效。 image::images/replace5.png[] -有趣的是,即使是使用了 `c6e1e95` 提交数据来进行替换,它的 SHA 仍显示为 `81a708d`。即使你运行了 `cat-file` 命令,它仍会显示你替换的数据: +有趣的是,即使是使用了 `c6e1e95` 提交数据来进行替换,它的 SHA-1 仍显示为 `81a708d`。 +即使你运行了 `cat-file` 命令,它仍会显示你替换的数据: [source,console] ---- @@ -184,4 +196,5 @@ e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040 ---- -这意味着我们可以轻而易举的和其他人分享替换,因为我们可以将替换推送到服务器中并且其他人可以轻松地下载。也许在历史移植情况下不是很有用(既然每个人都乐意下载最新版本和历史版本,为何还要拆分他们呢?),但在其他情况下仍然很有用。 +这意味着我们可以轻而易举的和其他人分享替换,因为我们可以将替换推送到服务器中并且其他人可以轻松地下载。 +也许在历史移植情况下不是很有用(既然每个人都乐意下载最新版本和历史版本,为何还要拆分他们呢?),但在其他情况下仍然很有用。 diff --git a/book/07-git-tools/sections/rerere.asc b/book/07-git-tools/sections/rerere.asc index a4386f7f..3b30093d 100644 --- a/book/07-git-tools/sections/rerere.asc +++ b/book/07-git-tools/sections/rerere.asc @@ -1,13 +1,19 @@ [[_rerere]] === Rerere -`git rerere` 功能是一个隐藏的功能。正如它的名字 ``reuse recorded resolution'' 所指,它允许你让 Git 记住解决一个块冲突的方法,这样在下一次看到相同冲突时,Git 可以为你自动地解决它。 +`git rerere` 功能是一个隐藏的功能。 +正如它的名字 ``reuse recorded resolution'' 所指,它允许你让 Git 记住解决一个块冲突的方法,这样在下一次看到相同冲突时,Git 可以为你自动地解决它。 -有几种情形下这个功能会非常有用。在文档中提到的一个例子是如果你想要保证一个长期分支会干净地合并,但是又不想要一串中间的合并提交。将 `rerere` 功能打开后偶尔合并,解决冲突,然后返回到合并前。如果你持续这样做,那么最终的合并会很容易,因为 `rerere` 可以为你自动做所有的事情。 +有几种情形下这个功能会非常有用。 +在文档中提到的一个例子是如果你想要保证一个长期分支会干净地合并,但是又不想要一串中间的合并提交。 +将 `rerere` 功能打开后偶尔合并,解决冲突,然后返回到合并前。 +如果你持续这样做,那么最终的合并会很容易,因为 `rerere` 可以为你自动做所有的事情。 -可以将同样的策略用在维持一个变基的分支时,这样就不用每次解决同样的变基冲突了。或者你将一个分支合并并修复了一堆冲突后想要用变基来替代合并 - 你可能并不想要再次解决相同的冲突。 +可以将同样的策略用在维持一个变基的分支时,这样就不用每次解决同样的变基冲突了。 +或者你将一个分支合并并修复了一堆冲突后想要用变基来替代合并 - 你可能并不想要再次解决相同的冲突。 -另一个情形是当你偶尔将一堆正在改进的特性分支合并到一个可测试的头时,就像 Git 项目自身经常做的。如果测试失败,你可以倒回合并之前然后在去除导致测试失败的那个特性分支后重做合并,而不用再次重新解决所有的冲突。 +另一个情形是当你偶尔将一堆正在改进的特性分支合并到一个可测试的头时,就像 Git 项目自身经常做的。 +如果测试失败,你可以倒回合并之前然后在去除导致测试失败的那个特性分支后重做合并,而不用再次重新解决所有的冲突。 为了启用 `rerere` 功能,仅仅需要运行这个配置选项: @@ -18,7 +24,8 @@ $ git config --global rerere.enabled true 也通过在特定的仓库中创建 `.git/rr-cache` 目录来开启它,但是设置选项更干净并且可以应用到全局。 -现在我们看一个简单的例子,类似之前的那个。假设有一个像这样的文件: +现在我们看一个简单的例子,类似之前的那个。 +假设有一个像这样的文件: [source,console] ---- @@ -44,7 +51,10 @@ Recorded preimage for 'hello.rb' Automatic merge failed; fix conflicts and then commit the result. ---- -你会注意到那个新行 `Recorded preimage for FILE`。除此之外它应该看起来就像一个普通的合并冲突。在这个时候,`rerere` 可以告诉我们几件事。和往常一样,在这个时候你可以运行 `git status` 来查看所有冲突的内容: +你会注意到那个新行 `Recorded preimage for FILE`。 +除此之外它应该看起来就像一个普通的合并冲突。 +在这个时候,`rerere` 可以告诉我们几件事。 +和往常一样,在这个时候你可以运行 `git status` 来查看所有冲突的内容: [source,console] ---- @@ -143,7 +153,8 @@ $ git reset --hard HEAD^ HEAD is now at ad63f15 i18n the hello ---- -我们的合并被撤消了。现在让我们变基特性分支。 +我们的合并被撤消了。 +现在让我们变基特性分支。 [source,console] ---- @@ -162,7 +173,8 @@ Failed to merge in the changes. Patch failed at 0001 i18n one word ---- -现在,正像我们期望的一样,得到了相同的合并冲突,但是看一下 `Resolved FILE using previous resolution` 这行。如果我们看这个文件,会发现它已经被解决了,而且在它里面没有合并冲突标记。 +现在,正像我们期望的一样,得到了相同的合并冲突,但是看一下 `Resolved FILE using previous resolution` 这行。 +如果我们看这个文件,会发现它已经被解决了,而且在它里面没有合并冲突标记。 [source,console] ---- @@ -227,7 +239,8 @@ def hello end ---- -我们通过 `rerere` 缓存的解决方案来自动重新解决了文件冲突。现在可以添加并继续变基来完成它。 +我们通过 `rerere` 缓存的解决方案来自动重新解决了文件冲突。 +现在可以添加并继续变基来完成它。 [source,console] ---- diff --git a/book/07-git-tools/sections/reset.asc b/book/07-git-tools/sections/reset.asc index 1500d2cf..a24bac6d 100644 --- a/book/07-git-tools/sections/reset.asc +++ b/book/07-git-tools/sections/reset.asc @@ -29,7 +29,7 @@ HEAD 是当前分支引用的指针,它总是指向该分支上的最后一次 通常,理解 HEAD 的最简方式,就是将它看做 *你的上一次提交* 的快照。 其实,查看快照的样子很容易。 -下例就显示了 HEAD 快照实际的目录列表,以及其中每个文件的 SHA 校验和: +下例就显示了 HEAD 快照实际的目录列表,以及其中每个文件的 SHA-1 校验和: [source,console] ---- @@ -51,7 +51,8 @@ $ git ls-tree -r HEAD [[_the_index]] ===== 索引 -索引是你的 *预期的下一次提交*。我们也会将这个概念引用为 Git 的 ``暂存区域'',这就是当你运行 `git commit` 时 Git 看起来的样子。 +索引是你的 *预期的下一次提交*。 +我们也会将这个概念引用为 Git 的 ``暂存区域'',这就是当你运行 `git commit` 时 Git 看起来的样子。 Git 将上一次检出到工作目录中的所有文件填充到索引区,它们看起来就像最初被检出时的样子。 之后你会将其中一些文件替换为新版本,接着通过 `git commit` 将它们转换为树来用作新的提交。 @@ -137,11 +138,13 @@ image::images/reset-ex6.png[] 在以下情景中观察 `reset` 命令会更有意义。 -为了演示这些例子,假设我们再次修改了 `file.txt` 文件并第三次提交它。现在的历史看起来是这样的: +为了演示这些例子,假设我们再次修改了 `file.txt` 文件并第三次提交它。 +现在的历史看起来是这样的: image::images/reset-start.png[] -让我们跟着 `reset` 看看它都做了什么。它以一种简单可预见的方式直接操纵这三棵树。 +让我们跟着 `reset` 看看它都做了什么。 +它以一种简单可预见的方式直接操纵这三棵树。 它做了三个基本操作。 ===== 第 1 步:移动 HEAD @@ -204,7 +207,7 @@ image::images/reset-hard.png[] 不过索引和工作目录 _可以部分更新_,所以重置会继续进行第 2、3 步。 现在,假如我们运行 `git reset file.txt` -(这其实是 `git reset --mixed HEAD file.txt` 的简写形式,因为你既没有指定一个提交的 SHA 或分支,也没有指定 `--soft` 或 `--hard`),它会: +(这其实是 `git reset --mixed HEAD file.txt` 的简写形式,因为你既没有指定一个提交的 SHA-1 或分支,也没有指定 `--soft` 或 `--hard`),它会: 1. 移动 HEAD 分支的指向 _(已跳过)_ 2. 让索引看起来像 HEAD _(到此处停止)_ @@ -253,7 +256,8 @@ image::images/reset-squash-r2.png[] image::images/reset-squash-r3.png[] -现在你可以查看可到达的历史,即将会推送的历史。现在看起来有个 v1 版 `file-a.txt` 的提交,接着第二个提交将 `file-a.txt` 修改成了 v3 版并增加了 `file-b.txt`。包含 v2 版本的文件已经不在历史中了。 +现在你可以查看可到达的历史,即将会推送的历史,现在看起来有个 v1 版 `file-a.txt` 的提交,接着第二个提交将 `file-a.txt` 修改成了 v3 版并增加了 `file-b.txt`。 +包含 v2 版本的文件已经不在历史中了。 ==== 检出 diff --git a/book/07-git-tools/sections/revision-selection.asc b/book/07-git-tools/sections/revision-selection.asc index 593f6325..7a8c9f7d 100644 --- a/book/07-git-tools/sections/revision-selection.asc +++ b/book/07-git-tools/sections/revision-selection.asc @@ -9,7 +9,7 @@ Git 允许你通过几种方法来指明特定的或者一定范围内的提交 你可以通过 Git 给出的 SHA-1 值来获取一次提交,不过还有很多更人性化的方式来做同样的事情。 本节将会介绍获取单个提交的多种方法。 -==== 简短的 SHA +==== 简短的 SHA-1 Git 十分智能,你只需要提供 SHA-1 的前几个字符就可以获得对应的那次提交,当然你提供的 SHA-1 字符数量不得少于 4 个,并且没有歧义——也就是说,当前仓库中只有一个对象以这段 SHA-1 开头。 @@ -72,10 +72,13 @@ a11bef0 first commit 如果你真的向仓库里提交了一个跟之前的某个对象具有相同 SHA-1 值的对象,Git 发现仓库里已经存在了拥有相同 HASH 值的对象,就会认为这个新的提交是已经被写入仓库的。 如果之后你想检出那个对象时,你将得到先前那个对象的数据。 -但是这种情况发生的概率十分渺小。SHA-1 摘要长度是 20 字节,也就是 160 位。2^80 个随机哈希对象才有 50% 的概率出现一次冲突 +但是这种情况发生的概率十分渺小。 +SHA-1 摘要长度是 20 字节,也就是 160 位。 +2^80 个随机哈希对象才有 50% 的概率出现一次冲突 (计算冲突机率的公式是 `p = (n(n-1)/2) * (1/2^160))` )。2^80 是 1.2 x 10^24 -也就是一亿亿亿,那是地球上沙粒总数的 1200 倍。 +也就是一亿亿亿。 +那是地球上沙粒总数的 1200 倍。 举例说一下怎样才能产生一次 SHA-1 冲突。 如果地球上 65 亿个人类都在编程,每人每秒都在产生等价于整个 Linux 内核历史(360 万个 Git 对象)的代码,并将之提交到一个巨大的 Git 仓库里面,这样持续两年的时间才会产生足够的对象,使其拥有 50% 的概率产生一次 SHA-1 对象冲突。 @@ -95,7 +98,7 @@ $ git show ca82a6dff817ec66f44342007202690a93763949 $ git show topic1 ---- -如果你想知道某个分支指向哪个特定的 SHA ,或者想看任何一个例子中被简写的 SHA-1 ,你可以使用一个叫做 `rev-parse` 的 Git 探测工具。 +如果你想知道某个分支指向哪个特定的 SHA-1,或者想看任何一个例子中被简写的 SHA-1 ,你可以使用一个叫做 `rev-parse` 的 Git 探测工具。 你可以在 <<_git_internals>> 中查看更多关于探测工具的信息。简单来说,`rev-parse` 是为了底层操作而不是日常操作设计的。 不过,有时你想看 Git 现在到底处于什么状态时,它可能会很有用。 你可以在你的分支上执行 `rev-parse` diff --git a/book/07-git-tools/sections/rewriting-history.asc b/book/07-git-tools/sections/rewriting-history.asc index c77b343e..5b137239 100644 --- a/book/07-git-tools/sections/rewriting-history.asc +++ b/book/07-git-tools/sections/rewriting-history.asc @@ -258,7 +258,7 @@ $ git log -4 --pretty=format:"%h %s" f3cc40e changed my name a bit ---- -再一次,这些改动了所有在列表中的提交的 SHA 校验和,所以要确保列表中的提交还没有推送到共享仓库中。 +再一次,这些改动了所有在列表中的提交的 SHA-1 校验和,所以要确保列表中的提交还没有推送到共享仓库中。 ==== 核武器级选项:filter-branch @@ -326,4 +326,4 @@ $ git filter-branch --commit-filter ' ---- 这会遍历并重写每一个提交来包含你的新邮箱地址。 -因为提交包含了它们父提交的 SHA-1 校验和,这个命令会修改你的历史中的每一个提交的 SHA 校验和,而不仅仅只是那些匹配邮箱地址的提交。 +因为提交包含了它们父提交的 SHA-1 校验和,这个命令会修改你的历史中的每一个提交的 SHA-1 校验和,而不仅仅只是那些匹配邮箱地址的提交。 diff --git a/book/07-git-tools/sections/searching.asc b/book/07-git-tools/sections/searching.asc index cc1846b7..b7bd7e6a 100644 --- a/book/07-git-tools/sections/searching.asc +++ b/book/07-git-tools/sections/searching.asc @@ -1,14 +1,18 @@ [[_searching]] === 搜索 -无论仓库里的代码量有多少,你经常需要查找一个函数是在哪里调用或者定义的,或者一个方法的变更历史。Git 提供了两个有用的工具来快速地从它的数据库中浏览代码和提交。我们来简单的看一下。 +无论仓库里的代码量有多少,你经常需要查找一个函数是在哪里调用或者定义的,或者一个方法的变更历史。 +Git 提供了两个有用的工具来快速地从它的数据库中浏览代码和提交。 +我们来简单的看一下。 [[_git_grep]] ==== Git Grep -Git 提供了一个 `grep` 命令,你可以很方便地从提交历史或者工作目录中查找一个字符串或者正则表达式。我们用 Git 本身源代码的查找作为例子。 +Git 提供了一个 `grep` 命令,你可以很方便地从提交历史或者工作目录中查找一个字符串或者正则表达式。 +我们用 Git 本身源代码的查找作为例子。 -默认情况下 Git 会查找你工作目录的文件。你可以传入 `-n` 参数来输出 Git 所找到的匹配行行号。 +默认情况下 Git 会查找你工作目录的文件。 +你可以传入 `-n` 参数来输出 Git 所找到的匹配行行号。 [source,console] ---- @@ -52,7 +56,8 @@ date.c: if (gmtime_r(&time, tm)) { 在这里我们可以看到在 date.c 文件中有 `match_multi_number` 和 `match_digit` 两个函数调用了 `gmtime_r`。 -你还可以使用 `--and` 标志来查看复杂的字符串组合,也就是在同一行同时包含多个匹配。比如,我们要查看在旧版本 1.8.0 的 Git 代码库中定义了常量名包含 ``LINK'' 或者 ``BUF_MAX'' 这两个字符串所在的行。 +你还可以使用 `--and` 标志来查看复杂的字符串组合,也就是在同一行同时包含多个匹配。 +比如,我们要查看在旧版本 1.8.0 的 Git 代码库中定义了常量名包含 ``LINK'' 或者 ``BUF_MAX'' 这两个字符串所在的行。 这里我们也用到了 `--break` 和 `--heading` 选项来使输出更加容易阅读。 @@ -81,11 +86,14 @@ v1.8.0:zlib.c 31:#define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */ ---- -相比于一些常用的搜索命令比如 `grep` 和 `ack`,`git grep` 命令有一些的优点。第一就是速度非常快,第二是你不仅仅可以可以搜索工作目录,还可以搜索任意的 Git 树。在上一个例子中,我们在一个旧版本的 Git 源代码中查找,而不是当前检出的版本。 +相比于一些常用的搜索命令比如 `grep` 和 `ack`,`git grep` 命令有一些的优点。 +第一就是速度非常快,第二是你不仅仅可以可以搜索工作目录,还可以搜索任意的 Git 树。 +在上一个例子中,我们在一个旧版本的 Git 源代码中查找,而不是当前检出的版本。 ==== Git 日志搜索 -或许你不想知道某一项在 **哪里** ,而是想知道是什么 **时候** 存在或者引入的。`git log` 命令有许多强大的工具可以通过提交信息甚至是 diff 的内容来找到某个特定的提交。 +或许你不想知道某一项在 **哪里** ,而是想知道是什么 **时候** 存在或者引入的。 +`git log` 命令有许多强大的工具可以通过提交信息甚至是 diff 的内容来找到某个特定的提交。 例如,如果我们想找到 `ZLIB_BUF_MAX` 常量是什么时候引入的,我们可以使用 `-S` 选项来显示新增和删除该字符串的提交。 @@ -102,9 +110,12 @@ ef49a7a zlib: zlib can only process 4GB at a time ===== 行日志搜索 -行日志搜索是另一个相当高级并且有用的日志搜索功能。这是一个最近新增的不太知名的功能,但却是十分有用。在 `git log` 后加上 `-L` 选项即可调用,它可以展示代码中一行或者一个函数的历史。 +行日志搜索是另一个相当高级并且有用的日志搜索功能。 +这是一个最近新增的不太知名的功能,但却是十分有用。 +在 `git log` 后加上 `-L` 选项即可调用,它可以展示代码中一行或者一个函数的历史。 -例如,假设我们想查看 `zlib.c` 文件中`git_deflate_bound` 函数的每一次变更,我们可以执行 `git log -L :git_deflate_bound:zlib.c`。Git 会尝试找出这个函数的范围,然后查找历史记录,并且显示从函数创建之后一系列变更对应的补丁。 +例如,假设我们想查看 `zlib.c` 文件中`git_deflate_bound` 函数的每一次变更,我们可以执行 `git log -L :git_deflate_bound:zlib.c`。 +Git 会尝试找出这个函数的范围,然后查找历史记录,并且显示从函数创建之后一系列变更对应的补丁。 [source,console] ---- @@ -144,4 +155,6 @@ diff --git a/zlib.c b/zlib.c + ---- -如果 Git 无法计算出如何匹配你代码中的函数或者方法,你可以提供一个正则表达式。例如,这个命令和上面的是等同的:`git log -L '/unsigned long git_deflate_bound/',/^}/:zlib.c`。你也可以提供单行或者一个范围的行号来获得相同的输出。 +如果 Git 无法计算出如何匹配你代码中的函数或者方法,你可以提供一个正则表达式。 +例如,这个命令和上面的是等同的:`git log -L '/unsigned long git_deflate_bound/',/^}/:zlib.c`。 +你也可以提供单行或者一个范围的行号来获得相同的输出。 diff --git a/book/07-git-tools/sections/signing.asc b/book/07-git-tools/sections/signing.asc index 1d0c5461..481223c6 100644 --- a/book/07-git-tools/sections/signing.asc +++ b/book/07-git-tools/sections/signing.asc @@ -1,7 +1,8 @@ [[_signing]] === 签署工作 -Git 虽然是密码级安全的,但它不是万无一失的。如果你从因特网上的其他人那里拿取工作,并且想要验证提交是不是真正地来自于可信来源,Git 提供了几种通过 GPG 来签署和验证工作的方式。 +Git 虽然是密码级安全的,但它不是万无一失的。 +如果你从因特网上的其他人那里拿取工作,并且想要验证提交是不是真正地来自于可信来源,Git 提供了几种通过 GPG 来签署和验证工作的方式。 ==== GPG 介绍 @@ -178,7 +179,8 @@ Fast-forward 1 file changed, 2 insertions(+) ---- -也可以给 `git merge` 命令附加 `-S` 选项来签署自己生成的合并提交。下面的例子演示了验证将要合并的分支的每一个提交都是签名的并且签署最后生成的合并提交。 +也可以给 `git merge` 命令附加 `-S` 选项来签署自己生成的合并提交。 +下面的例子演示了验证将要合并的分支的每一个提交都是签名的并且签署最后生成的合并提交。 [source,console] ---- @@ -196,4 +198,6 @@ Merge made by the 'recursive' strategy. ==== 每个人必须签署 -签署标签与提交很棒,但是如果决定在正常的工作流程中使用它,你必须确保团队中的每一个人都理解如何这样做。如果没有,你将会花费大量时间帮助其他人找出并用签名的版本重写提交。在采用签署成为标准工作流程的一部分前,确保你完全理解 GPG 及签署带来的好处。 +签署标签与提交很棒,但是如果决定在正常的工作流程中使用它,你必须确保团队中的每一个人都理解如何这样做。 +如果没有,你将会花费大量时间帮助其他人找出并用签名的版本重写提交。 +在采用签署成为标准工作流程的一部分前,确保你完全理解 GPG 及签署带来的好处。 diff --git a/book/07-git-tools/sections/stashing-cleaning.asc b/book/07-git-tools/sections/stashing-cleaning.asc index 16cd646f..3268eceb 100644 --- a/book/07-git-tools/sections/stashing-cleaning.asc +++ b/book/07-git-tools/sections/stashing-cleaning.asc @@ -118,7 +118,9 @@ Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43) ==== 创造性的储藏 -有几个储藏的变种可能也很有用。第一个非常流行的选项是 `stash save` 命令的 `--keep-index` 选项。它告诉 Git 不要储藏任何你通过 git add 命令已暂存的东西。 +有几个储藏的变种可能也很有用。 +第一个非常流行的选项是 `stash save` 命令的 `--keep-index` 选项。 +它告诉 Git 不要储藏任何你通过 git add 命令已暂存的东西。 当你做了几个改动并只想提交其中的一部分,过一会儿再回来处理剩余改动时,这个功能会很有用。 @@ -136,7 +138,9 @@ $ git status -s M index.html ---- -另一个经常使用储藏来做的事情是像储藏跟踪文件一样储藏未跟踪文件。默认情况下,`git stash` 只会储藏已经在索引中的文件。如果指定 `--include-untracked` 或 `-u` 标记,Git 也会储藏任何创建的未跟踪文件。 +另一个经常使用储藏来做的事情是像储藏跟踪文件一样储藏未跟踪文件。 +默认情况下,`git stash` 只会储藏已经在索引中的文件。 +如果指定 `--include-untracked` 或 `-u` 标记,Git 也会储藏任何创建的未跟踪文件。 [source,console] ---- @@ -208,13 +212,18 @@ Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359) [[_git_clean]] ==== 清理工作目录 -对于工作目录中一些工作或文件,你想做的也许不是储藏而是移除。`git clean` 命令会帮你做这些事。 +对于工作目录中一些工作或文件,你想做的也许不是储藏而是移除。 +`git clean` 命令会帮你做这些事。 有一些通用的原因比如说为了移除由合并或外部工具生成的东西,或是为了运行一个干净的构建而移除之前构建的残留。 -你需要谨慎地使用这个命令,因为它被设计为从工作目录中移除未被追踪的文件。如果你改变主意了,你也不一定能找回来那些文件的内容。一个更安全的选项是运行 `git stash --all` 来移除每一样东西并存放在栈中。 +你需要谨慎地使用这个命令,因为它被设计为从工作目录中移除未被追踪的文件。 +如果你改变主意了,你也不一定能找回来那些文件的内容。 +一个更安全的选项是运行 `git stash --all` 来移除每一样东西并存放在栈中。 -你可以使用`git clean`命令去除冗余文件或者清理工作目录。 使用`git clean -f -d`命令来移除工作目录中所有未追踪的文件以及空的子目录。`-f` 意味着 '强制' 或 “确定移除”。 +你可以使用`git clean`命令去除冗余文件或者清理工作目录。 +使用`git clean -f -d`命令来移除工作目录中所有未追踪的文件以及空的子目录。 +`-f` 意味着 '强制' 或 “确定移除”。 如果只是想要看看它会做什么,可以使用 `-n` 选项来运行命令,这意味着 ``做一次演习然后告诉你 _将要_ 移除什么''。 @@ -225,7 +234,9 @@ Would remove test.o Would remove tmp/ ---- -默认情况下,`git clean` 命令只会移除没有忽略的未跟踪文件。任何与 `.gitiignore` 或其他忽略文件中的模式匹配的文件都不会被移除。如果你也想要移除那些文件,例如为了做一次完全干净的构建而移除所有由构建生成的 `.o` 文件,可以给 clean 命令增加一个 `-x` 选项。 +默认情况下,`git clean` 命令只会移除没有忽略的未跟踪文件。 +任何与 `.gitiignore` 或其他忽略文件中的模式匹配的文件都不会被移除。 +如果你也想要移除那些文件,例如为了做一次完全干净的构建而移除所有由构建生成的 `.o` 文件,可以给 clean 命令增加一个 `-x` 选项。 [source,console] ---- @@ -244,7 +255,8 @@ Would remove test.o Would remove tmp/ ---- -如果不知道 `git clean` 命令将会做什么,在将 `-n` 改为 `-f` 来真正做之前总是先用 `-n` 来运行它做双重检查。另一个小心处理过程的方式是使用 `-i` 或 ``interactive'' 标记来运行它。 +如果不知道 `git clean` 命令将会做什么,在将 `-n` 改为 `-f` 来真正做之前总是先用 `-n` 来运行它做双重检查。 +另一个小心处理过程的方式是使用 `-i` 或 ``interactive'' 标记来运行它。 这将会以交互模式运行 clean 命令。 diff --git a/book/07-git-tools/sections/submodules.asc b/book/07-git-tools/sections/submodules.asc index 3d5679ab..46df6c66 100644 --- a/book/07-git-tools/sections/submodules.asc +++ b/book/07-git-tools/sections/submodules.asc @@ -21,7 +21,9 @@ Git 通过子模块来解决这个问题。 我们将要演示如何在一个被分成一个主项目与几个子项目的项目上开发。 -我们首先将一个已存在的 Git 仓库添加为正在工作的仓库的子模块。你可以通过在 `git submodule add` 命令后面加上想要跟踪的项目 URL 来添加新的子模块 。在本例中,我们将会添加一个名为 ``DbConnector'' 的库。 +我们首先将一个已存在的 Git 仓库添加为正在工作的仓库的子模块。 +你可以通过在 `git submodule add` 命令后面加上想要跟踪的项目 URL 来添加新的子模块。 +在本例中,我们将会添加一个名为 ``DbConnector'' 的库。 [source,console] ---- @@ -34,7 +36,8 @@ Unpacking objects: 100% (11/11), done. Checking connectivity... done. ---- -默认情况下,子模块会将子项目放到一个与仓库同名的目录中,本例中是 ``DbConnector''。如果你想要放到其他地方,那么可以在命令结尾添加一个不同的路径。 +默认情况下,子模块会将子项目放到一个与仓库同名的目录中,本例中是 ``DbConnector''。 +如果你想要放到其他地方,那么可以在命令结尾添加一个不同的路径。 如果这时运行 `git status`,你会注意到几件事。 @@ -69,7 +72,10 @@ $ cat .gitmodules [NOTE] ===== -由于 .gitmodules 文件中的 URL 是人们首先尝试克隆/拉取的地方,因此请尽可能确保你使用的URL 大家都能访问。例如,若你要使用的推送 URL 与他人的拉取 URL 不同,那么请使用他人能访问到的 URL。你也可以根据自己的需要,通过在本地执行 `git config submodule.DbConnector.url <私有URL>` 来覆盖这个选项的值。 +由于 .gitmodules 文件中的 URL 是人们首先尝试克隆/拉取的地方,因此请尽可能确保你使用的URL 大家都能访问。 +例如,若你要使用的推送 URL 与他人的拉取 URL 不同,那么请使用他人能访问到的 URL。 +你也可以根据自己的需要,通过在本地执行 `git config submodule.DbConnector.url <私有URL>` 来覆盖这个选项的值。 +如果可行的话,一个相对路径会很有帮助。 ===== 在 `git status` 输出中列出的另一个是项目文件夹记录。 @@ -172,7 +178,8 @@ Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b2 现在 `DbConnector` 子目录是处在和之前提交时相同的状态了。 -不过还有更简单一点的方式。如果给 `git clone` 命令传递 `--recursive` 选项,它就会自动初始化并更新仓库中的每一个子模块。 +不过还有更简单一点的方式。 +如果给 `git clone` 命令传递 `--recursive` 选项,它就会自动初始化并更新仓库中的每一个子模块。 [source,console] ---- @@ -199,7 +206,8 @@ Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b2 ===== 拉取上游修改 -在项目中使用子模块的最简模型,就是只使用子项目并不时地获取更新,而并不在你的检出中进行任何更改。我们来看一个简单的例子。 +在项目中使用子模块的最简模型,就是只使用子项目并不时地获取更新,而并不在你的检出中进行任何更改。 +我们来看一个简单的例子。 如果想要在子模块中查看新工作,可以进入到目录中运行 `git fetch` 与 `git merge`,合并上游分支来更新本地代码。 @@ -216,7 +224,8 @@ Fast-forward 2 files changed, 2 insertions(+) ---- -如果你现在返回到主项目并运行 `git diff --submodule`,就会看到子模块被更新的同时获得了一个包含新添加提交的列表。如果你不想每次运行 `git diff` 时都输入 `--submodle`,那么可以将 `diff.submodule` 设置为 ``log'' 来将其作为默认行为。 +如果你现在返回到主项目并运行 `git diff --submodule`,就会看到子模块被更新的同时获得了一个包含新添加提交的列表。 +如果你不想每次运行 `git diff` 时都输入 `--submodle`,那么可以将 `diff.submodule` 设置为 ``log'' 来将其作为默认行为。 [source,console] ---- @@ -229,7 +238,8 @@ Submodule DbConnector c3f01dc..d0354fc: 如果在此时提交,那么你会将子模块锁定为其他人更新时的新代码。 -如果你不想在子目录中手动抓取与合并,那么还有种更容易的方式。运行 `git submodule update --remote`,Git 将会进入子模块然后抓取并更新。 +如果你不想在子目录中手动抓取与合并,那么还有种更容易的方式。 +运行 `git submodule update --remote`,Git 将会进入子模块然后抓取并更新。 [source,console] ---- @@ -243,7 +253,10 @@ From https://github.com/chaconinc/DbConnector Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644' ---- -此命令默认会假定你想要更新并检出子模块仓库的 `master` 分支,不过你也可以设置为想要的其他分支。例如,你想要 DbConnector 子模块跟踪仓库的 ``stable'' 分支,那么既可以在 `.gitmodules` 文件中设置(这样其他人也可以跟踪它),也可以只在本地的 `.git/config` 文件中设置。让我们在 `.gitmodules` 文件中设置它: +此命令默认会假定你想要更新并检出子模块仓库的 `master` 分支。 +不过你也可以设置为想要的其他分支。 +例如,你想要 DbConnector 子模块跟踪仓库的 ``stable'' 分支,那么既可以在 `.gitmodules` 文件中设置(这样其他人也可以跟踪它),也可以只在本地的 `.git/config` 文件中设置。 +让我们在 `.gitmodules` 文件中设置它: [source,console] ---- @@ -323,7 +336,8 @@ index 6fc0b3d..fd1cc29 100644 > better connection routine ---- -这非常有趣,因为我们可以直接看到将要提交到子模块中的提交日志。提交之后,你也可以运行 `git log -p` 查看这个信息。 +这非常有趣,因为我们可以直接看到将要提交到子模块中的提交日志。 +提交之后,你也可以运行 `git log -p` 查看这个信息。 [source,console] ---- @@ -354,13 +368,19 @@ Submodule DbConnector c3f01dc..c87d55d: ===== 在子模块上工作 -你很有可能正在使用子模块,因为你确实想在子模块中编写代码的同时,还想在主项目上编写代码(或者跨子模块工作)。否则你大概只能用简单的依赖管理系统(如 Maven 或 Rubygems)来替代了。 +你很有可能正在使用子模块,因为你确实想在子模块中编写代码的同时,还想在主项目上编写代码(或者跨子模块工作)。 +否则你大概只能用简单的依赖管理系统(如 Maven 或 Rubygems)来替代了。 现在我们将通过一个例子来演示如何在子模块与主项目中同时做修改,以及如何同时提交与发布那些修改。 -到目前为止,当我们运行 `git submodule update` 从子模块仓库中抓取修改时,Git 将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作 ``游离的 HEAD'' 的状态。这意味着没有本地工作分支(例如 ``master'')跟踪改动。所以你做的任何改动都不会被跟踪。 +到目前为止,当我们运行 `git submodule update` 从子模块仓库中抓取修改时,Git 将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作 ``游离的 HEAD'' 的状态。 +这意味着没有本地工作分支(例如 ``master'')跟踪改动。 +所以你做的任何改动都不会被跟踪。 -为了将子模块设置得更容易进入并修改,你需要做两件事。首先,进入每个子模块并检出其相应的工作分支。接着,若你做了更改就需要告诉 Git 它该做什么,然后运行 `git submodule update --remote` 来从上游拉取新工作。你可以选择将它们合并到你的本地工作中,也可以尝试将你的工作变基到新的更改上。 +为了将子模块设置得更容易进入并修改,你需要做两件事。 +首先,进入每个子模块并检出其相应的工作分支。 +接着,若你做了更改就需要告诉 Git 它该做什么,然后运行 `git submodule update --remote` 来从上游拉取新工作。 +你可以选择将它们合并到你的本地工作中,也可以尝试将你的工作变基到新的更改上。 首先,让我们进入子模块目录然后检出一个分支。 @@ -370,7 +390,9 @@ $ git checkout stable Switched to branch 'stable' ---- -然后尝试用 ``merge'' 选项。为了手动指定它,我们只需给 `update` 添加 `--merge` 选项即可。这时我们将会看到服务器上的这个子模块有一个改动并且它被合并了进来。 +然后尝试用 ``merge'' 选项。 +为了手动指定它,我们只需给 `update` 添加 `--merge` 选项即可。 +这时我们将会看到服务器上的这个子模块有一个改动并且它被合并了进来。 [source,console] ---- @@ -388,7 +410,8 @@ Fast-forward Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea' ---- -如果我们进入 DbConnector 目录,可以发现新的改动已经合并入本地 `stable` 分支。现在让我们看看当我们对库做一些本地的改动而同时其他人推送另外一个修改到上游时会发生什么。 +如果我们进入 DbConnector 目录,可以发现新的改动已经合并入本地 `stable` 分支。 +现在让我们看看当我们对库做一些本地的改动而同时其他人推送另外一个修改到上游时会发生什么。 [source,console] ---- @@ -454,7 +477,8 @@ Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'Db [[_publishing_submodules]] ===== 发布子模块改动 -现在我们的子模块目录中有一些改动。其中有一些是我们通过更新从上游引入的,而另一些是本地生成的,由于我们还没有推送它们,所以对任何其他人都不可用。 +现在我们的子模块目录中有一些改动。 +其中有一些是我们通过更新从上游引入的,而另一些是本地生成的,由于我们还没有推送它们,所以对任何其他人都不可用。 [source,console] ---- @@ -467,9 +491,12 @@ Submodule DbConnector c87d55d..82d2ad3: > add new option for conn pooling ---- -如果我们在主项目中提交并推送但并不推送子模块上的改动,其他尝试检出我们修改的人会遇到麻烦,因为他们无法得到依赖的子模块改动。那些改动只存在于我们本地的拷贝中。 +如果我们在主项目中提交并推送但并不推送子模块上的改动,其他尝试检出我们修改的人会遇到麻烦,因为他们无法得到依赖的子模块改动。 +那些改动只存在于我们本地的拷贝中。 -为了确保这不会发生,你可以让 Git 在推送到主项目前检查所有子模块是否已推送。`git push` 命令接受可以设置为 ``check'' 或 ``on-demand'' 的 `--recurse-submodules` 参数。如果任何提交的子模块改动没有推送那么 ``check'' 选项会直接使 `push` 操作失败。 +为了确保这不会发生,你可以让 Git 在推送到主项目前检查所有子模块是否已推送。 +`git push` 命令接受可以设置为 ``check'' 或 ``on-demand'' 的 `--recurse-submodules` 参数。 +如果任何提交的子模块改动没有推送那么 ``check'' 选项会直接使 `push` 操作失败。 [source,console] ---- @@ -489,7 +516,8 @@ or cd to the path and use to push them to a remote. ---- -如你所见,它也给我们了一些有用的建议,指导接下来该如何做。最简单的选项是进入每一个子模块中然后手动推送到远程仓库,确保它们能被外部访问到,之后再次尝试这次推送。 +如你所见,它也给我们了一些有用的建议,指导接下来该如何做。 +最简单的选项是进入每一个子模块中然后手动推送到远程仓库,确保它们能被外部访问到,之后再次尝试这次推送。 另一个选项是使用 ``on-demand'' 值,它会尝试为你这样做。 @@ -513,15 +541,18 @@ To https://github.com/chaconinc/MainProject 3d6d338..9a377d1 master -> master ---- -如你所见,Git 进入到 DbConnector 模块中然后在推送主项目前推送了它。如果那个子模块因为某些原因推送失败,主项目也会推送失败。 +如你所见,Git 进入到 DbConnector 模块中然后在推送主项目前推送了它。 +如果那个子模块因为某些原因推送失败,主项目也会推送失败。 ===== 合并子模块改动 -如果你其他人同时改动了一个子模块引用,那么可能会遇到一些问题。也就是说,如果子模块的历史已经分叉并且在父项目中分别提交到了分叉的分支上,那么你需要做一些工作来修复它。 +如果你其他人同时改动了一个子模块引用,那么可能会遇到一些问题。 +也就是说,如果子模块的历史已经分叉并且在父项目中分别提交到了分叉的分支上,那么你需要做一些工作来修复它。 如果一个提交是另一个的直接祖先(一个快进式合并),那么 Git 会简单地选择之后的提交来合并,这样没什么问题。 -不过,Git 甚至不会尝试去进行一次简单的合并。如果子模块提交已经分叉且需要合并,那你会得到类似下面的信息: +不过,Git 甚至不会尝试去进行一次简单的合并。 +如果子模块提交已经分叉且需要合并,那你会得到类似下面的信息: [source,console] ---- @@ -539,9 +570,13 @@ CONFLICT (submodule): Merge conflict in DbConnector Automatic merge failed; fix conflicts and then commit the result. ---- -所以本质上 Git 在这里指出了子模块历史中的两个分支记录点已经分叉并且需要合并。它将其解释为 ``merge following commits not found''(未找到接下来需要合并的提交),虽然这有点令人困惑,不过之后我们会解释为什么是这样。 +所以本质上 Git 在这里指出了子模块历史中的两个分支记录点已经分叉并且需要合并。 +它将其解释为 ``merge following commits not found''(未找到接下来需要合并的提交),虽然这有点令人困惑,不过之后我们会解释为什么是这样。 -奇怪的是,Git 并不会给你多少能帮你摆脱困境的信息,甚至连两边提交历史中的 SHA 值都没有。幸运的是,这很容易解决。如果你运行 `git diff`,就会得到试图合并的两个分支中记录的提交的 SHA 值。 +为了解决这个问题,你需要弄清楚子模块应该处于哪种状态。 +奇怪的是,Git 并不会给你多少能帮你摆脱困境的信息,甚至连两边提交历史中的 SHA-1 值都没有。 +幸运的是,这很容易解决。 +如果你运行 `git diff`,就会得到试图合并的两个分支中记录的提交的 SHA-1 值。 [source,console] ---- @@ -552,9 +587,14 @@ index eb41d76,c771610..0000000 +++ b/DbConnector ---- -所以,在本例中,`eb41d76` 是我们的子模块中**大家共有**的提交,而 `c771610` 是上游拥有的提交。如果我们进入子模块目录中,它应该已经在 `eb41d76` 上了,因为合并没有动过它。如果不是的话,无论什么原因,你都可以简单地创建并检出一个指向它的分支。 +所以,在本例中,`eb41d76` 是我们的子模块中**大家共有**的提交,而 `c771610` 是上游拥有的提交。 +如果我们进入子模块目录中,它应该已经在 `eb41d76` 上了,因为合并没有动过它。 +如果不是的话,无论什么原因,你都可以简单地创建并检出一个指向它的分支。 -来自另一边的提交的 SHA 值比较重要,它是需要你来合并解决的。你可以尝试直接通过 SHA 合并,也可以为它创建一个分支然后尝试合并。我们建议后者,哪怕只是为了一个更漂亮的合并提交信息。 +来自另一边的提交的 SHA-1 值比较重要。 +它是需要你来合并解决的。 +你可以尝试直接通过 SHA-1 合并,也可以为它创建一个分支然后尝试合并。 +我们建议后者,哪怕只是为了一个更漂亮的合并提交信息。 所以,我们将会进入子模块目录,基于 `git diff` 的第二个 SHA 创建一个分支然后手动合并。 @@ -601,16 +641,18 @@ $ git commit -m "Merge Tom's Changes" <5> <1> 首先解决冲突 <2> 然后返回到主项目目录中 -<3> 再次检查 SHA 值 +<3> 再次检查 SHA-1 值 <4> 解决冲突的子模块记录 <5> 提交我们的合并 这可能会让你有点儿困惑,但它确实不难。 有趣的是,Git 还能处理另一种情况。 -如果子模块目录中存在着这样一个合并提交,它的历史中包含了的**两边**的提交,那么 Git 会建议你将它作为一个可行的解决方案。它看到有人在子模块项目的某一点上合并了包含这两次提交的分支,所以你可能想要那个。 +如果子模块目录中存在着这样一个合并提交,它的历史中包含了的**两边**的提交,那么 Git 会建议你将它作为一个可行的解决方案。 +它看到有人在子模块项目的某一点上合并了包含这两次提交的分支,所以你可能想要那个。 -这就是为什么前面的错误信息是 ``merge following commits not found'',因为它不能 *这样* 做。它让人困惑是因为**谁能想到它会尝试这样做?** +这就是为什么前面的错误信息是 ``merge following commits not found'',因为它不能 *这样* 做。 +它让人困惑是因为**谁能想到它会尝试这样做?** 如果它找到了一个可以接受的合并提交,你会看到类似下面的信息: @@ -654,9 +696,11 @@ $ git commit -am 'Fast forwarded to a common submodule child' ===== 子模块遍历 -有一个 `foreach` 子模块命令,它能在每一个子模块中运行任意命令。如果项目中包含了大量子模块,这会非常有用。 +有一个 `foreach` 子模块命令,它能在每一个子模块中运行任意命令。 +如果项目中包含了大量子模块,这会非常有用。 -例如,假设我们想要开始开发一项新功能或者修复一些错误,并且需要在几个子模块内工作。我们可以轻松地保存所有子模块的工作进度。 +例如,假设我们想要开始开发一项新功能或者修复一些错误,并且需要在几个子模块内工作。 +我们可以轻松地保存所有子模块的工作进度。 [source,console] ---- @@ -679,7 +723,8 @@ Entering 'DbConnector' Switched to a new branch 'featureA' ---- -你应该明白,能够生成一个主项目与所有子项目的改动的统一差异是非常有用的。 +你应该明白。 +能够生成一个主项目与所有子项目的改动的统一差异是非常有用的。 [source,console] ---- @@ -717,11 +762,13 @@ index 1aaefb6..5297645 100644 struct strbuf out = STRBUF_INIT; ---- -在这里,我们看到子模块中定义了一个函数并在主项目中调用了它。这明显是个简化了的例子,但是希望它能让你明白这种方法的用处。 +在这里,我们看到子模块中定义了一个函数并在主项目中调用了它。 +这明显是个简化了的例子,但是希望它能让你明白这种方法的用处。 ===== 有用的别名 -你可能想为其中一些命令设置别名,因为它们可能会非常长而你又不能设置选项作为它们的默认选项。我们在 <<_git_aliases>> 介绍了设置 Git 别名,但是如果你计划在 Git 中大量使用子模块的话,这里有一些例子。 +你可能想为其中一些命令设置别名,因为它们可能会非常长而你又不能设置选项作为它们的默认选项。 +我们在 <<_git_aliases>> 介绍了设置 Git 别名,但是如果你计划在 Git 中大量使用子模块的话,这里有一些例子。 [source,console] ---- @@ -770,7 +817,8 @@ Untracked files: nothing added to commit but untracked files present (use "git add" to track) ---- -移除那个目录并不困难,但是有一个目录在那儿会让人有一点困惑。如果你移除它然后切换回有那个子模块的分支,需要运行 `submodule update --init` 来重新建立和填充。 +移除那个目录并不困难,但是有一个目录在那儿会让人有一点困惑。 +如果你移除它然后切换回有那个子模块的分支,需要运行 `submodule update --init` 来重新建立和填充。 [source,console] ---- @@ -841,7 +889,9 @@ warning: unable to rmdir CryptoLibrary: Directory not empty Switched to branch 'master' ---- -当你切换回来之后,因为某些原因你得到了一个空的 `CryptoLibrary` 目录,并且 `git submodule update` 也无法修复它。你需要进入到子模块目录中运行 `git checkout .` 来找回所有的文件。你也可以通过 `submodule foreach` 脚本来为多个子模块运行它。 +当你切换回来之后,因为某些原因你得到了一个空的 `CryptoLibrary` 目录,并且 `git submodule update` 也无法修复它。 +你需要进入到子模块目录中运行 `git checkout .` 来找回所有的文件。 +你也可以通过 `submodule foreach` 脚本来为多个子模块运行它。 要特别注意的是,近来子模块会将它们的所有 Git 数据保存在顶级项目的 `.git` 目录中,所以不像旧版本的 Git,摧毁一个子模块目录并不会丢失任何提交或分支。 diff --git a/book/07-git-tools/sections/subtree-merges.asc b/book/07-git-tools/sections/subtree-merges.asc index c7b374a6..8474456c 100644 --- a/book/07-git-tools/sections/subtree-merges.asc +++ b/book/07-git-tools/sections/subtree-merges.asc @@ -43,12 +43,14 @@ $ ls README ---- -这个是一个比较奇怪的概念。并不是仓库中的所有分支都是必须属于同一个项目的分支,这并不常见,因为没啥用,但是却是在不同分支里包含两条完全不同提交历史的最简单的方法。 +这个是一个比较奇怪的概念。 +并不是仓库中的所有分支都是必须属于同一个项目的分支. +这并不常见,因为没啥用,但是却是在不同分支里包含两条完全不同提交历史的最简单的方法。 在这个例子中,我们希望将 Rack 项目拉到 `master` 项目中作为一个子目录。 我们可以在 Git 中执行 `git read-tree` 来实现。 你可以在 <<_git_internals>> 中查看更多 `read-tree` 的相关信息,现在你只需要知道它会读取一个分支的根目录树到当前的暂存区和工作目录里。 -先切回你的 `master` 分支,将 `rack` 分支拉取到我们项目的 `master` 分支中的 `rack` 子目录。 +先切回你的 `master` 分支,将 `rack_back` 分支拉取到我们项目的 `master` 分支中的 `rack` 子目录。 [source,console] ---- @@ -66,31 +68,34 @@ $ git pull ---- 接着,我们可以将这些变更合并回我们的 `master` 分支。 -使用 `git merge -s subtree` 不会有问题;但是 Git 也会将历史记录合并起来,这可能不是我们想要的。 -在 `-s subtree` 选项后面加上 `--squash` 和 `--no-commit` 选项就可以拉取变更并且预填充提交信息。 +使用 `--squash` 选项和使用 `-Xsubtree` 选项(它采用递归合并策略),都可以用来可以拉取变更并且预填充提交信息。 +(递归策略在这里是默认的,提到它是为了让读者有个清晰的概念。) [source,console] ---- $ git checkout master -$ git merge --squash -s subtree --no-commit rack_branch +$ git merge --squash -s recursive -Xsubtree=rack rack_branch Squash commit -- not updating HEAD Automatic merge went well; stopped before committing as requested ---- -All the changes from the Rack project are merged in and ready to be committed locally. -You can also do the opposite – make changes in the `rack` subdirectory of your master branch and then merge them into your `rack_branch` branch later to submit them to the maintainers or push them upstream. +Rack 项目中所有的改动都被合并了,等待被提交到本地。 +你也可以用相反的方法——在 master 分支上的 `rack` 子目录中做改动然后将它们合并入你的 `rack_branch` 分支中,之后你可能将其提交给项目维护着或者将它们推送到上游。 -This gives us a way to have a workflow somewhat similar to the submodule workflow without using submodules (which we will cover in <<_git_submodules>>). We can keep branches with other related projects in our repository and subtree merge them into our project occasionally. It is nice in some ways, for example all the code is committed to a single place. However, it has other drawbacks in that it's a bit more complex and easier to make mistakes in reintegrating changes or accidentally pushing a branch into an unrelated repository. +这给我们提供了一种类似子模块工作流的工作方式,但是它并不需要用到子模块(有关子模块的内容我们会在 <<_git_submodules>> 中介绍)。 +我们可以在自己的仓库中保持一些和其他项目相关的分支,偶尔使用子树合并将它们合并到我们的项目中。 +某些时候这种方式很有用,例如当所有的代码都提交到一个地方的时候。 +然而,它同时也有缺点,它更加复杂且更容易让人犯错,例如重复合并改动或者不小心将分支提交到一个无关的仓库上去。 -Another slightly weird thing is that to get a diff between what you have in your `rack` subdirectory and the code in your `rack_branch` branch – to see if you need to merge them – you can’t use the normal `diff` command. -Instead, you must run `git diff-tree` with the branch you want to compare to: +另外一个有点奇怪的地方是,当你想查看 `rack` 子目录和 `rack_branch` 分支的差异——来确定你是否需要合并它们——你不能使用普通的 `diff` 命令。 +取而代之的是,你必须使用 `git diff-tree` 来和你的目标分支做比较: [source,console] ---- $ git diff-tree -p rack_branch ---- -Or, to compare what is in your `rack` subdirectory with what the `master` branch on the server was the last time you fetched, you can run +或者,将你的 `rack` 子目和最近一次从服务器上抓取的 `master` 分支进行比较,你可以运行: [source,console] ----