Git-秘籍-四-

56 阅读48分钟

Git 秘籍(四)

原文:Git Recipes

协议:CC BY-NC-SA 4.0

九、解决冲突

到目前为止,我们主要关注修订图的结构。我们通常使用别名$ git simple-commit提交;因此,我们生成的文件非常简单。事实上,到目前为止我们创建的几乎每个文件都只包含一个单词。一旦创建,这些文件很少被修改。此外,秘籍是以这样一种方式编写的,我们通常在不同的分支中使用不同的文件名。这种简化的提交过程是学习修订图上各种操作的非常有效的方式,例如合并和重新建立基础。然而,它们并没有为您在团队中工作做好充分的准备,在团队中,您的同事正在对同一个文件进行更改。在这一章中,我们将填补这个空白。您将学习如何控制文件的内容,直到解决问题。

在实际项目中工作,你迟早会遇到冲突。当您合并包含对文件的完全相同行的不同修改的分支时,就会发生这种情况。如果在某个文件中,例如readme.txt,一个开发人员键入第一行如下:

Lorem ipsum

和其他开发人员类型:

Dolor sit amet

git 将不能自动合并两个版本。您必须在Lorem ipsumDolor sit amet之间手动选择。因为冲突通常会引起很多关注和恐慌——它们被认为是令人害怕的事情——四个秘籍将准确地解释如何在文本和二进制文件的合并和重定基础期间处理冲突。一旦你熟悉了冲突,好好看看冲突是如何解决的。在 git 中,当你暂存一个文件时,冲突就解决了。这意味着在所有四个关于冲突的方案中,最重要的工作是由一个著名的$ git add指挥完成的。

9-1.在文本文件 中创建冲突的更改

问题

您想要创建一个包含两个分支的存储库,这两个分支在合并或改变基础时会在文本文件中产生冲突。您想要创建的存储库如图 9-1 所示。

9781430261032_Fig09-01.jpg

图 9-1 。带有分支 en 和 fr 的存储库将在文本文件中产生冲突

解决办法

创建新的存储库:

$ cd git-recipes

$ git init 09-01

$ cd 09-01

然后按照以下步骤操作:

  1. Create the file numbers.txt with the contents shown in Listing 9-1

    清单 9-1。 在主控分支中创建 numbers.txt 文件

    1
    
    2
    
    3
    
  2. $ git snapshot Numbers: 1, 2, 3提交numbers.txt文件

  3. $ git branch en创建名为en的分支

  4. $ git branch fr创建名为fr的分支

  5. en分支中创建新的提交

    • a.用$ git checkout en切换到en分支

    • b.  Change the contents of numbers.txt. Replace 2 with two as shown in Listing 9-2

      清单 9-2。 恩分公司提交的 numbers.txt 文件

      1
      
      two
      
      3
      
    • c.用$ git snapshot Numbers: two提交更改

  6. fr分支中创建新的提交

    • a.用$ git checkout fr切换到fr分支

    • b.  Change the contents of numbers.txt. Replace 2 with deux as shown in Listing 9-3

      ***清单 9-3。***fr 分公司提交的 numbers.txt 文件

      1
      
      deux
      
      3
      
    • c.用$ git snapshot Numbers: deux提交更改

$ git checkout en完成秘籍。

它是如何工作的

要创建一个冲突的变更,你必须在两个不同的分支中修改一个文件的完全相同的行。在这个配方中,我们修改了包含数字2的一行。在名为en的第一个分支中,我们用two替换2,在名为fr的第二个分支中,我们用deux替换2

在这种情况下,git 不能自动地合并或者重置分支。正如您将看到的,合并和重置基础将被暂停,您将不得不手动解决冲突。

9-2.合并后解决文本冲突

问题

你和你的同事正在处理同一个项目。你们都很欣赏 git 分支模型提供的独立性。因此你们每个人都创造了一个分支。不幸的是,在不同的分支工作时,你们都编辑了同一个文件,插入了重叠的更改。现在,您想将自己的工作与同事的工作合并。在这次行动中,你将面临冲突。您希望以这样一种方式解决冲突,即您和您的同事在文件中键入的所有内容都得到保留。

这个真实的场景简化为合并我们在方法 9-1 中创建的存储库中的分支enfr。您希望使用在配方 9-1 中创建的存储库作为起点。你想要达到的结果如图 9-2 所示。numbers.txt文件的第二行应该包含清单 9-4 中显示的内容。你想保留两个互相冲突的单词twodeux

9781430261032_Fig09-02.jpg

图 9-2 。来自配方 9-1 的储存库,具有合并的分支 en 和 fr

清单 9-4。 合并 en 和 fr 分支后的 numbers.txt 文件

1
two - deux
3

解决办法

从配方 9-1 克隆存储库:

$ cd git-recipes

$ git clone-with-branches 09-01 09-02

$ cd 09-02

$ git checkout en

你现在的部门是en。用$ git merge frfr分支合并成en。这一次,自动合并失败,并显示以下消息:

Auto-merging numbers.txt
CONFLICT (content): Merge conflict in numbers.txt
Automatic merge failed; fix conflicts and then commit the result.

如您所见,合并被暂停。您必须解决冲突,然后提交结果。$ git status -s的输出是:

UU numbers.txt

冲突文件标有UU,根据$ git status --help表示已更新但未合并$ git merge fr之后的numbers.txt的内容如清单 9-5 所示。

***清单 9-5。***numbers . txt 文件的内容紧跟在$ git merge fr 命令之后

1
<<<<<<< HEAD
two
=======
deux
>>>>>>> fr
3

现在,您必须编辑文件并准备您认为是冲突的适当解决方案的内容。你可以使用任何你喜欢的编辑器,你可以插入任何你喜欢的内容。打开文件numbers.txt,根据清单 9-6 进行更改。

***清单 9-6。***numbers . txt 文件的内容带有手动编辑的内容

1
two - deux
3

文件保存后,您可以验证其状态没有改变。命令$ git status -s返回与之前相同的输出:UU numbers.txt

一旦您手动解决了冲突,您可以将文件的状态从UU更改为M_。这是通过$ git add numbers.txt命令完成的。用$ git commit --no-edit命令提交更改,你将完成配方。

它是如何工作的

$ git merge命令产生冲突时,合并暂停。留给您的是一个存储库,其中的一些文件被标记为UU。这些文件包含必须手动解决的冲突。

每个冲突都用特殊的标记表示:

<<<<<<<
=======
>>>>>>>

冲突的第一部分来自你当前的分支,存储在 HEAD 中。Git 通知您,您当前的分支(在我们的菜谱中是en)包含单词two:

<<<<<<< HEAD
two
=======

冲突的第二部分来自于fr分支。冲突的词是deux。该信息显示为:

=======
deux
>>>>>>> fr

如何解决 git 中的冲突?这是通过一个命令$ git add 完成的。从现在起,你需要记住一个简单的规则:暂存文件解决冲突。就状态而言,我们可以说暂存文件会将其状态从UU更改为M_。起初,文件的内容并不重要,这可能令人惊讶。如果您愿意,您可以保留清单 9-5 中所示的文件,并使用<<<<<<<, =======, >>>>>>>标记提交它。当您编辑文件并删除这些标记时,并不意味着您已经解决了冲突。只有当你暂存一个文件时才这样做(例如,用$ git add命令)。

有时,您需要通过使用一个分支中引入的内容并忽略另一个分支中的更改来解决冲突。这对二进制文件尤其重要。您可以使用两个命令来实现这一点:

$ git checkout --ours [filename]
$ git checkout --theirs [filename]

--ours标志表示当前分支。这就是菜谱里的en--theirs标志表示传递给$ git merge命令的分支。在这个秘籍中,它是fr分支。换句话说,命令$ git checkout --ours numbers.txt将在工作目录中产生如列表 9-2 所示的文件,而命令$ git checkout --theirs numbers.txt—如列表 9-3 所示的文件。请注意,这些命令不能解决冲突。它们只恢复文件的内容,而不改变其状态。恢复的文件保持在UU状态。

如果你想生成清单 9-5 中所示的文件,你可以使用:

$ git checkout --merge [filename]

使用上面的命令,您将得到一个文件,其中的冲突用<<<<<<< ours>>>>>>> theirs标签表示,如:

1
<<<<<<< ours
two
=======
deux
>>>>>>> theirs
3

上面的输出不包含分支enfr分叉之前的原始行。如果这对您很重要,请使用以下命令:

$ git checkout --conflict=diff3 numbers.txt

它将创建如清单 9-7 所示内容的文件。这一次,该文件包含另一个标记为base的部分。base部分显示存储在合并库中的版本——由$ git merge-base en fr命令返回的提交。

清单 9-7。 冲突以 diff3 格式呈现

<<<<<<< ours
two
||||||| base
2
=======
deux
>>>>>>> theirs
3

image 提示在配方 5-12 中,我们讨论了从任意版本恢复任意文件的命令:$ git checkout [REVISION] [filename]。它可以代替使用--ours--theirs参数。在该配方中,命令$ git checkout en numbers.txt相当于$ git checkout --ours numbers.txt$ git checkout fr numbers.txt相当于$ git checkout --theirs numbers.txt

解决所有冲突后,您可以使用$ git commit --no-edit命令恢复暂停的合并。选项--no-edit不是强制性的,您可以跳过它。但是它减轻了您输入或检查提交消息的负担。

如有疑问,您可以随时使用$ git merge --abort中止正在进行的合并。撤销合并的方法在第六章中讨论。

image 提示解决合并冲突包括三个步骤:1)编辑文件;2)用$ git add命令暂存文件;3)用$ git commit --no-edit命令完成合并。

9-3.重置基础后解决文本冲突

问题

对于在配方 9-1 中创建的存储库,您想要将分支en重置到fr上。你的目标是产生如图 9-3 所示的库。numbers.txt文件的冲突行应该包含清单 9-8 中显示的内容。

9781430261032_Fig09-03.jpg

图 9-3 。配方 9-1 中的存储库,其中分支基于 fr

这个方法是一个简化的场景,其中两个开发人员在不同的分支工作,并在其中一个文件中产生重叠的变更。该秘籍提供了一种方法,将你的工作重新建立在你同事的工作基础上。

清单 9-8。 将 en 分支重置到 fr 后要保留的 numbers.txt 文件

1
deux - two
3

解决办法

从配方 9-1 克隆存储库:

$ cd git-recipes

$ git clone-with-branches 09-01 09-03

$ cd 09-03

$ git rebase fr en命令将en分支复位到fr分支上。重置基础将失败,并显示以下消息:

First, rewinding head to replay your work on top of it...
Applying: Numbers: two
Using index info to reconstruct a base tree...
M       numbers.txt
Falling back to patching base and 3-way merge...
Auto-merging numbers.txt
CONFLICT (content): Merge conflict in numbers.txt
Failed to merge in the changes.
Patch failed at 0001 Numbers: two
The copy of the patch that failed is found in:
   /git-recipes/09-03/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

重设基础以与配方 8-7 相同的方式暂停。你必须解决冲突,然后你可以继续重新基础。

$ git status -s 的输出是:

UU numbers.txt

冲突文件的标记方式与使用UU合并时完全相同。然而,当您打开numbers.txt文件时,您会看到文件的内容发生了变化。$ git rebase fr en之后的numbers.txt文件如清单 9-9 所示。

***清单 9-9。***numbers . txt 文件的内容紧跟在$ git rebase fr en 命令之后

1
<<<<<<< HEAD
deux
=======
two
>>>>>>> Numbers: two
3

重置基础从检验fr分支的 tip 提交开始。因此,HEAD 部分中呈现的内容来自于fr分支:

<<<<<<< HEAD
deux
=======

在 rebase 期间应用的第一个补丁来自标记为Numbers: two的提交。因此,冲突的第二部分被格式化为:

=======
two
>>>>>>> Numbers: two

现在,您必须编辑文件并输入清单 9-8 中的内容。

最后,用$ git add numbers.txt命令暂存文件。该命令会将文件的状态从UU更改为M_。用暂停的复位:$ git rebase --continue 完成配方继续。

它是如何工作的

如果在重设基准期间有冲突,操作暂停。您必须手动解决冲突。该过程与合并冲突的情况完全相同:编辑文件,然后登台它。同样的规则也适用于此:暂存文件 解决冲突

清单 9-2、9-3、9-7 和 9-9 中所示的numbers.txt文件的四种状态可以用以下命令检索:

$ git checkout --ours numbers.txt
$ git checkout --theirs numbers.txt
$ git checkout --merge numbers.txt
$ git checkout --conflict=diff3 numbers.txt

但是要小心:这次--ours--theirs的意思颠倒了:--ours是指fr分支,--theirs是指en分支。这是因为在fr分支中,重定基础是从最近一次提交的检验开始的。

当所有冲突都解决后,您可以使用$ git rebase --continue继续重置基准,或者使用$ git rebase --abort中止操作。这个操作的撤销在第七章中讨论过。

image 提示解决 rebase 冲突包括三个步骤:1)编辑文件;2)用$ git add命令暂存文件;3)用$ git rebase --continue命令完成复位。

9-4 在二进制文件中创建冲突的更改

问题

您想要创建一个包含两个分支的存储库,这两个分支在合并或重置时会在二进制文件中产生冲突。您想要创建的存储库如图 9-4 所示。

9781430261032_Fig09-04.jpg

图 9-4 。带有分支 a 和 b 的存储库将产生二元冲突

解决办法

创建新的存储库:

$ cd git-recipes

$ git init 09-04

$ cd 09-04

然后按照以下步骤操作:

  1. $ git commit --allow-empty --allow-empty-message -m " "命令创建第一个版本。这就是你如何产生一个带有空注释的空提交。
  2. $ git branch a创建名为a的分支
  3. $ git branch b创建名为b的分支
  4. a分支中创建新的提交
    • a.用$ git checkout a切换到a分支
    • b.创建显示一只猫的图像,并将其保存在名为picture.jpg的文件中
    • c.用$ git snapshot Cat提交更改
  5. b分支中创建新的提交
    • a.用$ git checkout b切换到b分支
    • b.创建显示一只狗的图像,并将其保存在名为picture.jpg的文件中
    • c.用$ git snapshot Dog提交更改

$ git checkout a命令完成配方。

它是如何工作的

这一次,存储库包含以相同名称保存在文件中的两个不同的图像。存储在名为a的分支中的文件显示一只猫,存储在b分支中的文件显示一只狗。

注意,命令$ git commit --allow-empty --allow-empty-message -m " "产生了带有空消息的空提交。

9-5.合并期间解决二元冲突

问题

你想用$ git merge命令合并配方 9-4 中创建的分支ab。您想要获得图 9-5 中所示的库。合并后的存储库应该包含来自b分支的picture.jpg文件。

9781430261032_Fig09-05.jpg

图 9-5 。来自配方 9-4 的储存库,具有合并的分支 a 和 b

解决办法

从配方 9-4 克隆存储库:

$ cd git-recipes

$ git clone-with-branches 09-04 09-05

$ cd 09-05

$ git checkout a

你现在的部门是a。用$ git merge b命令将b分支合并成a。正如你所猜测的,合并失败了。该消息通知您在picture.jpg文件中有二进制冲突:

warning: Cannot merge binary files: picture.jpg (HEAD vs. b)
Auto-merging picture.jpg
CONFLICT (add/add): Merge conflict in picture.jpg
Automatic merge failed; fix conflicts and then commit the result.

合并暂停,冲突的二进制文件用以下符号表示:

AA picture.jpg

通过$ git status -s命令。您必须选择文件的一个版本。使用$ git checkout --theirs picture.jpg命令选择显示狗的图像。

一旦您签出了文件的适当版本,您可以将文件的状态从AA更改为M_。这是通过$ git add picture.jpg命令完成的。用$ git commit --no-edit提交更改,您将完成合并。当合并完成后,打开你最喜欢的图像编辑器,确认picture.jpg显示一只狗。

它是如何工作的

二进制文件比文本文件引起更多的无故障冲突。这是因为 git 不能将两个不同的二进制文件合并成一个文件。没有方法产生图 9-6 中所示的合并文件。你只能使用图像编辑器,比如 Gimp,git 不能帮助你。对于二进制文件,您只能请求文件的第一个或第二个版本。这是通过两个命令完成的:

$ git checkout --ours [filename]
$ git checkout --theirs [filename]

9781430261032_Fig09-06.jpg

图 9-6 。Git 不能将两个独立的二进制文件合并成一个文件

或者用:

$ git checkout a [filename]
$ git checkout b [filename]

冲突的二进制文件用AA表示。这是另一个区别,因为文本冲突用UU表示。

其余的,即解决冲突和完成合并,如前面使用$ git add$ git commit --no-edit命令所做的那样执行。

9-6.在重置基期间解决二进制冲突

问题

当在配方 9-4 中创建的存储库中工作时,您可能希望将分支a重置到b上。您想要获得图 9-7 中所示的存储库。当你完成重置基础时,分支a应该包含一张显示一只猫的图片。

9781430261032_Fig09-07.jpg

图 9-7 。您希望在配方 9-6 中生成的存储库

解决办法

从配方 9-4 克隆存储库:

$ cd git-recipes

$ git clone-with-branches 09-04 09-06

$ cd 09-06

$ git rebase b a命令将分支a复位到b上。重置基础将失败,并显示以下消息:

First, rewinding head to replay your work on top of it...
Applying: Cat
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
warning: Cannot merge binary files: picture.jpg (HEAD vs. Cat)
Auto-merging picture.jpg
CONFLICT (add/add): Merge conflict in picture.jpg
Failed to merge in the changes.
Patch failed at 0001 Cat
The copy of the patch that failed is found in:
   c:/git-recipes/09-06/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

重置基础已暂停,您必须解决冲突。$ git status -s的输出是:

AA picture.jpg

要从分支a恢复显示猫的图像,使用$ git checkout --theirs picture.jpg命令。这次--ours是分支b--theirs是分支a

$ git add picture.jpg$ git rebase --continue命令完成配方。最后打开你最喜欢的图片编辑器,确认picture.jpg显示的是一只猫。

它是如何工作的

重定基过程中的二进制冲突的处理几乎与配方 9-5 相同。合并和重定基础的唯一区别是--ours--theirs的角色颠倒了。这在表 9-1 中进行了总结。

表 9-1 。在合并和重组过程中我们和他们的角色

|

命令

|

-我们的

|

-他们的

| | --- | --- | --- | | $ git checkout a | a | b | | $ git checkout a | b | a |

9-7.合并期间强制二进制模式

问题

该配方的起点是在配方 9-1 中创建的存储库。你想要合并两个分支enfr,以二进制模式合并numbers.txt文件的两个版本。

解决办法

从配方 9-1 克隆存储库:

$ cd git-recipes

$ git clone-with-branches 09-01 09-07

$ cd 09-07

$ git checkout en

现在创建一个名为.gitattributes的文件,包含单行numbers.txt binary。您可以使用一个命令来完成:

$ echo "numbers.txt binary" > .gitattributes

提交这个新文件

$ git snapshot .gitattributes rule to force binary type of numbers.txt

最后,用$ git merge fr命令将fr分支合并成en。这一次你会得到这样的信息:

warning: Cannot merge binary files: numbers.txt (HEAD vs. fr)
Auto-merging numbers.txt
CONFLICT (content): Merge conflict in numbers.txt
Automatic merge failed; fix conflicts and then commit the result.

如您所见,numbers.txt文件被视为二进制文件。命令$ cat numbers.txt打印:

1
two
3

因此,文件的两个版本没有合并。工作目录包含来自en分支的版本(目前是--ours)。

它是如何工作的

git 如何知道哪些文件是二进制的,哪些文件有文本内容?它检查文件的前 8,000 个字节是否出现空字节。如果文件包含代码为 0 的字节,则认为它是二进制文件。否则,它将被视为文本文件。

要强制将文件视为二进制文件,可以使用:

filename binary

.gitattributes文件中的规则。同样,您也可以使用以下规则强制将二进制文件视为文本文件:

filename text

在这两种情况下,filename都可以用一个模式代替。这里有两个例子:第一个强制将所有在bindir/下的文件视为二进制文件,另一个强制将所有文件名以.xyz结尾的文件视为文本文件:

bindir/ binary
*.xyz text

一般来说,.gitattributes文件的语法允许您为每个模式定义一个属性列表:

pattern attribute-1 attribute-2 attribute-3 ...

模式定义了规则应该影响哪些文件。以下是四个模式示例:

*             # all files
*.txt         # all files ending with .txt
somedir/      # all files under somedir/ directory
readme.txt    # one file readme.txt

对于每个模式,您可以应用任意数量的属性,如下所示:

*.txt         text -merge eol=crlf

这一行定义了一个规则,将用于所有匹配*.txt模式的文件。该规则由三个条目组成:

text
-merge
eol=crlf

第一个条目由一个单词text组成,为所有匹配的文件设置text属性。因此,所有的*.txt文件都将被视为文本文件,因此 git 将对它们执行行尾规范化。

第二个条目由一个单词merge组成,前面有一个破折号,它取消了merge属性。这意味着所有的*.txt文件将被合并为二进制文件。

最后一个规则设置了在结帐过程中应该使用的行尾字符。

所有可用属性的列表汇总在表 9-2 中。

表 9-2 。可用属性的列表

|

属性

|

描述

| | --- | --- | | binary | 关闭三个属性:diff, merge, text | | conflict-marker-size | 定义冲突标记的长度。 | | delta | 对于由属性增量设置为false的路径生成的 blobs,不会尝试增量压缩。 | | diff | 该属性影响 git 对给定文件执行$ git diff操作的方式。 | | encoding | 该属性的值指定 GUI 工具(例如 gitk 和 git-gui)应该使用的字符编码,以显示相关文件的内容。 | | eol | 该属性定义了在签出过程中使用的行尾。 | | export-ignore | 从使用$ git archive命令生成的档案中排除文件。 | | export-subst | 在执行$ git archive命令时,用其他文件替换文件。 | | filter | 此属性可用于在签出和签入期间执行附加处理。 | | ident | 该属性允许在文件中嵌入$Id$变量。这些变量在签入和签出期间进行处理。 | | merge | 定义文件是否可以合并为带有标记<<<<<<<, =======, >>>>>>>的文本文件,或者是否应该被视为二进制文件。 | | text | 该属性控制行尾规范化。 | | white-space | 此属性允许您定制空白错误的控制。 |

所有属性的完整描述可以通过$ git attributes --help命令获得。

摘要

这一章是与其他开发者合作之前的最后一步,也是必要的一步。它为您提供以下问题的准确答案:

  • 两个人修改一个文本文件的完全相同的一行会发生什么?
  • 当两个开发人员更改存储不同内容的同一个二进制文件时会发生什么?
  • git 如何决定哪些文件是二进制的,哪些不是?

第一个问题是文本文件中的重叠更改,这将导致合并或重设基础操作期间的文本冲突。在这两种情况下,操作都会暂停,您必须解决所有冲突。冲突解决后,您可以使用$ git commit命令完成合并。使用$ git rebase --continue命令恢复暂停的重置基础。

冲突的文本文件由$ git status –s命令表示为UU。重叠部分标有<<<<<<<, =======, >>>>>>>。这些标记的长度可以通过表 9-2 中的conflict-marker-size属性进行调整。您必须手动编辑文件,并为每个冲突决定您认为合适的内容。请记住,移除标记<<<<<<<=======>>>>>>>并不能解决冲突。即使您删除所有标记并保存文件,它仍然处于UU状态。要将文件的状态从UU更改为M_,您必须暂存文件。这可以通过$ git add [filename]命令来完成。

二进制文件也会导致冲突,但是在这种情况下,git 不能将两个不同的二进制文件合并成一个文件。你将会看到一个文件的第一个或第二个版本,这取决于你是否使用了合并或重建基础。冲突的二进制文件表示为AA。通过使用$ git add [filename]命令暂存文件,可以完全像在文本情况下一样解决冲突。

在文本冲突的情况下,以下四个命令可用于生成四个版本的冲突文件:

$ git checkout --ours [filename]
$ git checkout --theirs [filename]
$ git checkout --merge [filename]
$ git checkout --conflict=diff3 [filename]

对于二元冲突,只能使用前两个命令。

git 如何决定哪些文件是二进制,哪些是文本?它浏览每个文件的前 8,000 个字节。如果它发现一个空字节,文件被认为是二进制的。否则,该文件被视为文本文件。您也可以使用一个.gitattributes文件来详细指定每个路径的类型。您可以将该文件视为在每个模式级别上指定各种文件属性的一种方式。

十、远程存储库和同步

所有 VCS 系统背后的内在原因是使一组开发人员之间的协作尽可能无缝。最后,我们已经到了可以讨论如何使用 git 进行小组工作的时候了。我们将从最简单的设置开始,所有的存储库都可以通过本地存储获得。

首先你必须学会如何使用遥控器。我们将在一个菜谱中讨论这个问题,这个菜谱将向您展示当您克隆一个存储库时会发生什么。然后,我们将深入介绍两个或更多开发人员如何合作的分步方法。我们将考虑两种重要情况:

  • 第一种:所有成员共享和访问一个空存储库
  • 第二种:两个开发者直接合作,不需要任何额外的存储库

您可以将第一个视为集中式客户机/服务器解决方案,而将另一个视为对等解决方案。Git 是一个分布式系统,允许您混合两种方法。

从中央存储库下载新贡献的最简单方法是使用$ git pull命令。当使用默认设置时,这会导致项目的历史非常复杂。我会告诉你什么时候,为什么你会遇到麻烦。这将引导我们改进秘籍,永远为你提供一个干净的历史。

我在配方 5-2 中提到的不同类型分支的主题将再次出现。这一次,我们将坚持到底。您将了解关于远程分支机构、本地跟踪分支机构和远程跟踪分支机构的所有信息。我不仅将向您展示如何列出、创建、销毁和同步它们,还将展示不同的命令,如$ git commit$ git fetch,如何影响它们的状态。本章将让您对远程分支机构、远程跟踪分支机构和本地跟踪分支机构有一个全面而完整的了解。通过获得这些技能,你将准备好加入任何使用 git 的团队。

10-1.手动克隆

问题

你想对克隆有更深入的了解。实现这一点的一种方法是手动克隆一个存储库。您想要执行一个手动克隆,在这个过程中,每一个内部 git 操作,比如新存储库的初始化和从远端获取修订,都是用一个更专门的命令来执行的。继续这个配方克隆https://github.com/creationix/js-git库。js-git 项目是 git 的初步 JavaScript 实现。

解决办法

使用以下内容创建一个新目录:

$ cd git-recipes

$ mkdir 10-01

$ cd 10-01

然后按照以下步骤操作:

  1. 使用$ git init初始化新的存储库
  2. 添加远端的网址:$ git remote add origin https://github.com/creationix/js-git.git
  3. 从远端获取 git 数据库和远程跟踪分支:$ git fetch --no-tags origin master:refs/remotes/origin/master
  4. 创建一个本地的master分支,它将指向与origin/master远程跟踪分支相同的版本:$ git rev-parse origin/master > .git/refs/heads/master
  5. $ git branch --set-upstream-to=origin/master设置master分支为远程跟踪分支origin/master的本地跟踪分支
  6. 将默认分支的信息存储在本地的远程存储库中:$ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/master
  7. 签出工作目录中的文件:$ git checkout

它是如何工作的

这个秘籍揭开了克隆操作的神秘面纱。它将克隆分为:

  • 初始化
  • 遥控器的定义
  • 下载 git 数据库和远程跟踪分支
  • 创建适当的分支

Git 启动一个克隆,用$ git init命令初始化一个新的空存储库。在这个命令之后,存储库是空的——它不包含任何分支。$ git branch的输出为空。

要从外部来源复制修订,我们需要一个 URL。这个 URL 是用$ remote add [alias] [URL]命令设置的。第一个参数是短别名;第二个参数是一个 URL。一旦你定义了一个远程 origin用:

$ git remote add origin https://github.com/creationix/jz-git.git

您可以使用短别名origin来代替完整的 URL。命令:

$ git fetch --no-tags origin master:refs/remotes/origin/master

相当于:

$ git fetch --no-tags https://github.com/creationix/js-git.git master:refs/remotes/origin/master

可以用$ git remote命令列出遥控器。默认情况下,该命令打印定义的别名。附加参数-v打开详细输出。命令$ git remote -v打印所有别名的名称和 URL。可以使用$ git remote rm [alias]命令移除遥控器。

所有遥控器都存储在.git/config文件中。当您执行:$ git remote add foo https://example.comnet/bar.git时,git 会在.git/config文件中添加以下条目:

[remote "foo"]
    url = https://example.comnet/bar.git
    fetch = +refs/heads/*:refs/remotes/foo/*

该行:

url = https://example.comnet/bar.git

存储 URL。第二行:

fetch = +refs/heads/*:refs/remotes/foo/*

定义了所谓的 refspec 。Refspec 指定了远程分支(即远程存储库中的分支)映射到远程跟踪分支(即存储在refs/remotes/foo目录中的本地分支)的方式。远程存储库在其.git/refs/heads目录中包含分支。我们希望以这样一种方式复制它们,使得它们不会与我们在.git/refs/heads本地存储的普通本地分支相冲突。因此,我们将远程跟踪分支放在一个名为.git/refs/remotes/foo的单独目录中。只要用于命名远程的别名是唯一的,我们就可以确保来自不同远程的分支不会相互冲突,也不会与我们的本地分支冲突。

您可以治疗:

fetch = +refs/heads/*:refs/remotes/foo/*

两个目录之间的 1:1 映射:一个在远程存储库中,另一个在本地存储库中。上面说明了在远端的.git/refs/heads目录中别名为foo的所有文件都被映射到本地名为.git/refs/remotes/foo的目录中。该映射在$ git fetch$ git push操作期间使用。放置在 refspec 最开始的字符+允许您推送修订,这些修订将覆盖存储在远程存储库中的历史。

值得注意的是,除了存储在.git/config文件的[remote "foo"]部分中的配置之外,两个存储库之间没有其他依赖关系。遥控器的名称只是一个别名,使您的命令更短。您可以使用遥控器的名称,而不是键入完整的 URL。此外,远程别名只存储在本地存储库中。远程终端不会在其配置中存储任何有人使用其 URL 的信息。

image 提示 Origin 是 git 在克隆操作期间为远程存储库使用的标准名称。这没有什么神奇的:你可以用$ git remote rm origin删除一个原始遥控器。你可以用$ git remote add origin [URL]创建一个新的原点遥控器。

一旦定义了远程,我们就可以将 git 数据库从远程复制到本地存储库中。这是通过$ git fetch --no-tags origin master:refs/remotes/origin/master命令完成的。在这个命令之后,存储库包含一个远程跟踪分支。命令$ git branch -a -vv输出类似于:

remotes/origin/master 60478cc Bump version to 0.3.1

参数--no-tags 确保包含在远程储存库中的标签不被复制。下一个参数origin给出了我们想要从中复制修订的遥控器的名称。最后一个参数master:refs/remotes/origin/master是一个 refspec。它由冒号分隔的两个名称组成:

  • master—远程存储库中的远程分支的名称,别名为origin
  • refs/remotes/origin/master—远程跟踪分支的名称(它是本地存储库中的本地分支)

refspec 确保远程master分支将被复制到本地.git/refs/remotes/origin/master文件中。在 fetch 命令之后,本地存储库中的.git/objects目录包含了从远程存储库中复制的对象。您应该注意到,$ git fetch命令创建了一个本地文件.git/refs/remotes/origin/master。这是远程分支的副本。副本作为远程跟踪分支存储在本地存储库中。回到配方 5-2,我强调了远程跟踪分支是本地分支。这就是远程跟踪分支是如何创建的:它们在$ git fetch命令之后出现在您的存储库中。您可以通过在$ git fetch之后立即发出的$ git branch -a -vv命令来验证这一点。即使你用$ git branch -d -r命令删除了一个远程跟踪分支,它也会在下一个$ git fetch命令后被重新创建。

下一步就是成立当地的master分公司。正如您已经知道的,一个普通的本地分支只是一个存储适当的 SHA-1 名称的文本文件。我们希望我们的分支指向与获取操作期间创建的.git/refs/remotes/origin/master分支相同的修订。存放在.git/refs/remotes/origin/master的分支可以简称为origin/master。你如何找到由一些象征性的参考origin/master所指向的修订版的 SHA-1 名字?为此,我们可以使用gitrevparse命令。运行命令 `git rev-parse`命令。运行命令` git rev-parse origin/master。它将打印出由.git/refs/remotes/origin/master`分支指向的修订的 SHA-1 名称。要创建一个指向相同版本的普通本地分支,将 SHA-1 存储在一个文本文件中就足够了:

$ git rev-parse origin/master > .git/refs/heads/master

image 提示$ git rev-parse origin/master > .git/refs/heads/master命令的结果也可以用$ cp .git/refs/remotes/origin/master .git/refs/heads/master命令来实现。

上面的命令创建了一个名为master的普通本地分支。$ git branch -a -vv命令的输出应该类似于:

* master                60478cc Bump version to 0.3.1
  remotes/origin/master 60478cc Bump version to 0.3.1

现在我们把一个普通的本地分支master变成一个本地跟踪分支,用于远程跟踪origin/master分支。这通过以下方式完成:

$ git branch --set-upstream-to=origin/master

在此之后,$ git branch -a -vv版画:

* master                60478cc [origin/master] Bump version to 0.3.1
remotes/origin/master 60478cc Bump version to 0.3.1

感谢第一线的[origin/master],我们知道master是远程跟踪分支origin/master的本地跟踪分支。

命令$ branch --set-upstream-to=origin/master.git/config文件中创建以下条目:

[branch "master"]
    remote = origin
    merge = refs/heads/master

这表示您的本地master分支被设置为跟踪存储在由origin URL 指向的存储库中的refs/heads/master中的远程分支。通过对本地和远程分支使用不同名称的示例,更容易理解上面命令的含义。假设您的本地存储库包含一个名为foo的普通本地分支。您希望foo分支跟踪远程存储库中的bar分支。如果您发出以下命令:

$ git branch --set-upstream-to=origin/bar foo

然后将创建以下配置条目:

[branch "foo"]
    remote = origin
    merge = refs/heads/bar

origin指向的远程存储库包含一个文件.git/refs/heads/bar。这是bar的远程分支。本地存储库包含.git/refs/remotes/origin/bar.git/refs/heads/foo。第一文件.git/refs/remotes/origin/bar是远程跟踪分支,第二文件.git/refs/heads/foo是本地跟踪分支。本地跟踪支路foo与远程跟踪支路origin/bar相连。

image 提示你也可以用$ git branch master origin/master命令创建指向origin/mastermaster分支。我避免了上面的命令,因为它不仅创建了一个本地的master分支,还设置了跟踪。我更喜欢把两项业务分开。因此我用$ git rev-parse$ git branch --set-upstream-to来分别执行这两个动作。

这个过程的最后一步是在本地远程存储库中存储关于默认分支的信息:

$ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/master

该命令将创建一个本地文件.git/refs/remotes/origin/HEAD。该文件将包含一个指向refs/remotes/origin/master的符号引用。这就是我们如何知道在远端哪个分支被认为是默认的。

Git 允许使用$ git config命令直接操作其配置。因此在foo分支发出的命令$ git branch --set-upstream-to=origin/bar相当于两个命令:

$ git config branch.foo.remote origin

$ git config branch.foo.merge refs/heads/bar

使用一个额外的--unset参数你也可以取消设置任意选项。在配方 10-5 中,我们将使用:

$ git config --unset branch.foo.remote
$ git config --unset branch.foo.merge

取消跟踪。

10-2.与中央存储库 合作

问题

您希望使用一个中央存储库来模拟两个开发人员 John 和 Sarah 的合作。在这种情况下,协作 将由三个库组织:

  • 第一个开发者的非裸存储库
  • 10-02/sarahs-repo—第二个开发者的非裸库
  • 一个用来同步约翰和莎拉工作的空仓库

在这个配方中,两个开发者将只使用master分支 来工作。您希望分析在以下情况下会发生什么:

  • 每个开发人员在master分支继续他或她的工作
  • 一个开发人员将他或她的修改发送到共享的回购协议中
  • 另一个开发人员获取新的版本

image 提示这个菜谱展示了如何围绕一个中心库来组织团队的工作。这个工作流程类似于集中式系统使用的客户端/服务器方法,如 CVS 或 SVN。

解决办法

使用以下内容创建新目录:

$ cd git-recipes

$ mkdir 10-02

$ cd 10-02

工作由开发人员之一初始化 。我们假设是约翰开始了整个项目:

# the command issued in git-recipes/10-02 directory
$ git init --bare shared-repo

这个存储库将用于同步 John 和 Sarah 的工作。

接下来,约翰创建了自己的知识库:

# john's commands in git-recipes/10-02 directory
$ git init johns-repo

然后他设置他的个人数据并定义origin遥控器 ??:

# john's commands
$ cd johns-repo
$ git config --local user.name john
$ git config --local user.email john@example.net
$ git remote add origin ../shared-repo

这两个库现在看起来像图 10-1 。图中未示出元信息user.nameuser.emailremote.origin

9781430261032_Fig10-01.jpg

图 10-1 。初始化后的两个存储库

然后,John 在他的私有存储库 中创建了一些修订:

# john's command
$ git simple-commit a1 a2 a3

现在,John 的存储库中包含一个名为master的普通分支。$ git branch -a -vv的输出类似于:

* master dc30648 a3

存储库看起来像图 10-2 。

9781430261032_Fig10-02.jpg

图 10-2 。约翰的 a1、a2、a3 提交后的储存库状态

约翰将他的a1a2a3修订发送到shared-repo存储库 中:

# john's command
$ git push -u origin master

该命令在远程shared-repo存储库中创建一个新的分支。新的远程分支被命名为master。由于有了-u参数,上面的命令在johns-repo中创建了一个远程跟踪分支origin/master。您可以使用$ git branch -a -vv命令进行验证。输出将类似于:

* master                dc30648 [origin/master] a3
  remotes/origin/master dc30648 a3

如您所见,John 的存储库包含一个本地跟踪分支master和一个远程跟踪分支origin/master。我们可以说,-u参数将名为master的普通本地分支转换为本地跟踪分支。存储库现在看起来像图 10-3 。

9781430261032_Fig10-03.jpg

图 10-3 。约翰发布的$ git push -u origin master 的效果

接下来莎拉加入了这个项目。她克隆了:

# sarah's commands
# executed in git-recipes/10-02
$ git clone shared-repo sarahs-repo
$ cd sarahs-repo

因为莎拉使用了$ git clone命令 ??,她的master分支被设置为跟踪远程master分支。这三个存储库现在看起来如图 10-4 所示。

9781430261032_Fig10-04.jpg

图 10-4 。莎拉发行的$ git 克隆共享回购 sarahs-repo 的效果

现在轮到莎拉为这个项目做贡献了。她创建了两个版本b1b2:

# sarah's command
$ git simple-commit b1 b2

存储库现在看起来像图 10-5 。

9781430261032_Fig10-05.jpg

图 10-5 。在 Sara 已经创建了 b1 和 b2 提交 ?? 之后仓库的状态

下一步,Sarah 将她的修订发送给shared-repo,内容如下:

# sarah's command
$ git push origin master

注意 Sarah 不需要使用-u。她用$ git clone命令初始化了她的存储库,因此自动初始化了对master分支的跟踪。John 用$ git init初始化了他的存储库。这就是为什么他第一次推的时候需要用-u。Sarah 的 push 命令的结果如图 10-6 所示。

9781430261032_Fig10-06.jpg

图 10-6 。萨拉的$ git push -u origin master 命令之后的仓库

现在轮到约翰下载莎拉的修改。他跑着说:

# john's command
$ git pull origin master

这导致了图 10-7 中所示的状态。

9781430261032_Fig10-07.jpg

图 10-7 。后一库约翰的$ git 拉原点主控命令

上述模式可以重复任意多次。在约翰和莎拉的主分支分开之前,没有什么特别的事情发生。我们来分析一个这样的案例。

约翰和莎拉的作品

这一次,John 和 Sarah 都在他们的存储库中独立工作。约翰创建修订版a4a5,而莎拉创建修订版b3:

# john's command

$ git simple-commit a4 a5


# sarah's command

$ git simple-commit b3

图 10-8 中描述了您将获得的存储库。

9781430261032_Fig10-08.jpg

图 10-8 。约翰和莎拉的分支分叉的存储库

现在约翰和莎拉都想把他们的修改推进shared-repo。我们假设 Sarah 是第一个执行$ git push命令的人。之后:

# sarah's command
$ git push origin master

存储库看起来像图 10-9 。

9781430261032_Fig10-09.jpg

图 10-9 。Sarah 成功的$ git push origin master 命令之后的存储库

现在约翰想把他的作品寄给shared-repo:

# john's command
$ git push origin master

Git 拒绝提交,因为johns-repo已经过时。上述命令的输出包含以下消息:

! [rejected]        master -> master (fetch first)

Git 通知 John,他的推送被拒绝,他必须首先获取缺失的修订。为了更新他的本地master分支,John 运行以下命令:

# john's command
$ git pull origin master

pull 命令获取 Sarah 的b3修订版并执行合并操作。现在存储库看起来像图 10-10 。

9781430261032_Fig10-10.jpg

图 10-10 。John 的$ git pull origin master 命令之后的存储库

John 的$ git pull origin master命令使用 Sarah 的最新版本更新了他的存储库,因此 John 现在可以使用以下命令将他的工作推送到shared-repo:

# john's command
$ git push origin master

注意,John 不再需要参数-u了,因为跟踪已经被 John 对$ git push -u的第一次调用所定义。存储库看起来像图 10-11 。

9781430261032_Fig10-11.jpg

图 10-11 。John 成功的$ git push origin master 命令之后的存储库

最后,Sarah 使用以下方法完成了 John 的工作:

# sarah's command
$ git pull origin master

产生的储存库如图 10-12 所示。

9781430261032_Fig10-12.jpg

图 10-12 。配方 10-2 中存储库的最终状态

它是如何工作的

在我们深入研究用于更新三个存储库的命令之前,让我们从分析图 10-12 中所示的所有三个存储库的内容开始。它们都包含完全相同的提交。您可以使用$ git log命令来验证这一点。进入git-recipes/10-02/johns-repo目录,运行下面的$ git log命令:

$ cd git-recipes/10-02/johns-repo

$ git log --oneline

您将获得类似于清单 10-1 中所示的输出。

清单 10-1。 图 10-12 中的在 johns-repo 中执行的$ git log-one line 的输出

515710e Merge branch 'master' of ../shared-repo
596e379 b3
5d11316 a5
2f46c63 a4
82d0a6b b2
6075835 b1
73e4416 a3
44fc529 a2
c8e56d1 a1

对 Sarah 的存储库重复类似的命令:

$ cd git-recipes/10-02/sarahs-repo

$ git log --oneline

上述命令将打印清单 10-2 中的输出。

清单 10-2。$ git log 的输出-one line run in sarahs-repo from 图 10-12

515710e Merge branch 'master' of ../shared-repo
596e379 b3
5d11316 a5
2f46c63 a4
82d0a6b b2
6075835 b1
73e4416 a3
44fc529 a2
c8e56d1 a1

尽管实际打印在屏幕上的 SHA-1 会有所不同,但您应该注意到两个输出是完全相同的。从图 10-12 中可以猜出shared-repo中执行的$ git log的输出:

$ cd git-recipes/10-02/shared-repo

$ git log --oneline

也会一模一样。存储在:johns-repo/.git/objectssarahs-repo/.git/objectsshared-repo/.git/objects中的三个数据库包含完全相同的对象。这由以下事实来证明:在所有三个存储库中由$ git log返回的 SHA-1 名称是相同的。

换句话说,当您使用$ git push$ git pull发送或接收修订时,git 会在存储库之间复制数据库条目。这与$ git rebase$ git cherry-pick$ git commit --amend命令相反。当您在本地存储库中工作时,没有复制现有修订的方法—您所能做的就是用新的 SHA-1 名称创建一个新的数据库对象。另一方面,在$ git push$ git pull命令期间,修改被复制而不是重新提交。复制的对象与原始对象具有相同的 SHA-1。

在这个配方中,我们使用了两个命令来发送和接收远程存储库的修订:

  • $ git push—将修订从本地发送到远程
  • $ git pull—将修订从远程下载到本地,然后与适当的分支合并

这些命令带有两个参数:

$ git push origin master
$ git pull origin master

在这两种情况下,origin是远程的名称,而master是本地分支的名称。

第一个命令$ git push origin master,将本地分支发送到远程存储库。更准确地说,我们可以说该命令将丢失的修订从本地存储库发送到远程端,然后更新分支(远程分支和远程跟踪分支——我们将很快讨论这一点)。

当 John 对图 10-2 中的仓库执行$ git push origin master时,首先 git 将三个版本a1a2a3johns-repo的数据库复制到shared-repo的数据库,然后更新分支。

默认情况下,git 将$ git push操作限制为快进情况。这意味着只有当远程分支可以快速推进您的工作时,$ git push才会成功。这是约翰将图 10-2 中的状态变为图 10-3 中的状态的情况。图 10-2 中库shared-repo为空;因此它可以安全地接收三个提交a1a2a3。当 Sara 将存储库从图 10-5 中的更改为图 10-6 中的所示的存储库时,也会发生同样的情况。图 10-5 中所示的库shared-repo包含了a1a2a3三个版本。不包含sarahs-repoa3之前的b1b2修订。shared-repo中的master分支可以用b1b2版本快进,因此操作成功。

图 10-9 所示的情况要复杂得多。共享存储库中的master分支 和 John 的存储库中的master分支已经分离。约翰的包含a4a5,共享存储库包含b3。因此约翰在图 10-9 所示的库中执行的$ git push origin master失败。Git 打印了 John 首先从远程存储库获取修订所需的信息。

当与-u参数一起使用时,$ git push命令存储关于跟踪的信息。命令:

$ git push -u origin master

当约翰将他的a1a2a3修订版本推送到约翰的存储库中的远程跟踪分支remotes/origin/master时,由约翰执行。master分行被设置为origin/master分行的本地跟踪分行。只有在第一次调用$ git push时才需要这个参数。

该库中使用的新命令是$ git pull origin master。这个命令从origin指向的远程存储库中复制修订。修订从远程的master分支复制,然后与本地存储库中当前的master分支合并。如果操作可以作为快进执行,则不存在合并提交。这是将图 10-6 所示的状态变为图 10-7 所示的状态。当本地和远程分支出现分歧时,该命令会生成一个合并提交。这就是为什么我们在清单 10-1 和 10-2 的顶部显示了合并提交。当您将图 10-9 所示的状态更改为图 10-10 所示的状态时,合并提交出现在johns-repo中。

提交如何影响跟踪分支

我们再次从 Sarah 的回购的角度分析跟踪分支如何在提交 期间改变。在$ git clone shared-repo sarahs-repo之后,仓库shared-reposarahs-repo看起来像图 10-13 。

9781430261032_Fig10-13.jpg

图 10-13 。克隆后立即共享回购和 sarahs 回购

我们从莎拉的角度来看这个情况。因此sarahs-repo是本地存储库,shared-repo是远程存储库,如图 10-13 中的所示。该图显示了三种类型的分支:远程分支、本地跟踪分支和远程跟踪分支。Sarah 的存储库不包含任何普通的本地分支。当 Sarah 克隆存储库时,$ git clone命令会自动为她创建两个本地分支:masterorigin/master。第一个是本地跟踪分支;第二个是远程跟踪分支。您可以使用$ git branch -a -vv命令验证这一点。输出将包含两行重要的内容:

* master                36c7205 [origin/master] a3
  remotes/origin/master 36c7205 a3

第一行表示masterorigin/master分支的本地跟踪分支。第二行表示origin/master是一个远程分支。

当 Sarah 用$ git simple-commit b1 b2提交时,她将她的master分支(即本地跟踪分支)向前移动。$ git simple-commit b1 b2后的分支状态如图图 10-14 所示。

9781430261032_Fig10-14.jpg

图 10-14 。在 Sarah-repo 中执行$ git simple-commit b1 b2 后分支的状态如图 10-13 所示

您可以使用$ git log --oneline --decorate命令来验证 Sarah 的存储库的状态。输出:

b019 (HEAD, master) b2
978f b1
66ad (origin/master, origin/HEAD) a3
91d5 a2
b189 a1

包含指向适当修订的标签masterorigin/master。关于分支状态的简短信息也可以通过$ git status -sb命令获得。当在图 10-14 的sarahs-repo中执行时,该命令将产生如下输出:

## master...origin/master [ahead 2]

这将通知您,您的本地跟踪分支包含两个未包含在远程跟踪分支中的修订。换句话说:本地跟踪分支master比远程跟踪分支origin/master领先两个版本。

如您所见,提交操作仅向前移动本地跟踪分支。远程跟踪分支和远程分支保持不变。

推动如何影响跟踪分支

我们如何更新shared-repo中的远程分支主机和 Sarah 的存储库中的远程跟踪分支origin/master?这是在莎拉用$ git push origin master命令提交时完成的。该命令改变两个储存库的状态,如图图 10-15 所示。图 10-15 展示了在图 10-14 所示的状态下,执行sarahs-repo$ git push origin master的结果。

9781430261032_Fig10-15.jpg

图 10-15 。在 Sarah-repo 中执行$ git push origin master 命令后,图 10-14 中的存储库的状态

您应该注意到,为了决定应该发送哪些修订,git 只需找到 Sarah 的存储库中两个分支masterorigin/master之间的差异。由于配方 7-2 中讨论的两点,这可以用$ git log --oneline origin/master..master命令来完成。该命令打印包含在master中但不包含在origin/master中的修订列表。

image 提示请记住,远程跟踪分支,比如origin/master,可以像普通本地分支一样在 git 提交中使用。例如,命令$ git branch foo origin/master∼3创建一个名为foo的新的普通本地分支,它指向与origin/master的曾祖父相同的修订。

总而言之,推送以下列方式更新分支:

  • 在本地存储库远程—跟踪分支被更新为本地跟踪分支中的最新版本
  • 在远程存储库中—远程分支被更新为本地跟踪分支中的最新版本

如果推送操作失败会怎么样?如果约翰在他的存储库中执行$ git push origin master,如图 10-9 中的所示,那么推送将被拒绝,并显示以下消息:

! [rejected]        master -> master (fetch first)

所有分支保持不变。

拉动如何影响跟踪分支

这一次我们是从约翰的视角观察变化。因此,johns-repo是本地存储库,shared-repo是远程存储库。

当您从远程存储库提取时,您的本地跟踪分支和远程跟踪分支将会更新。远程分支保持不变。第一种情况(没有合并)在图 10-16 和图 10-17 中描述。图 10-16 显示了拉动操作之前的状态。共享存储库包含不包含在johns-repo中的两个修订b1b2。拉操作将这些修订带入johns-repo,并将分支更新到图 10-17 所示的状态。

9781430261032_Fig10-16.jpg

图 10-16 。在$ git pull origin master 命令之前的 John 的存储库

9781430261032_Fig10-17.jpg

图 10-17 。约翰的仓库在$ git pull origin master 命令后在约翰-repo 中执行如图图 10-16 所示

图 10-18 和 10-19 说明了第二种情况。这是一个快进操作。如果在$ git pull origin master期间发生合并,本地跟踪分支master和远程跟踪分支origin/master以完全相同的方式改变。它们将指向自动生成的合并提交,如图图 10-18 和图 10-19 所示。

9781430261032_Fig10-18.jpg

图 10-18 。共享回购和约翰回购的主要分支出现分化

9781430261032_Fig10-19.jpg

图 10-19 。在图 10-18 中显示的约翰回购中发出的$ git 拉式来源主数据的结果

总而言之,拉操作更新本地跟踪分支和远程跟踪分支,而保持远程分支不变。

image 提示记住:远程分支是远程存储库中的一个分支。远程跟踪分支是本地分支,用作您的工作和远程存储库内容之间的链接。图 5-7 清楚地显示了所有类型的分支。

10-3.为一个提交生成(n-1)个合并提交

问题

如果项目是由 n 个开发人员使用配方 10-2 实现的,您想要检查历史看起来像什么。为此,您需要模拟三个开发人员的工作:John、Sarah 和 Peter。你必须在三个不同的存储库中并行提交:johns-reposarahs-repopeters-repo。然后,您需要同步所有的存储库。正如您将看到的,由$ git pull命令生成的历史将包含大量多余的合并提交。在最坏的情况下,一个提交可以生成多达 n-1 个合并提交,其中 n 是所涉及的开发人员的数量。

解决办法

使用以下内容创建新目录:

$ cd git-recipes

$ mkdir 10-03

$ cd 10-03

然后用以下内容初始化项目:

# the command issued in git-recipes/10-03 directory
$ git init --bare shared-repo

接下来,John 初始化他的存储库,创建一个初始提交,并将其推送到共享存储库:

# commands issued in git-recipes/10-03 directory

$ git clone shared-repo johns-repo

$ cd johns-repo

$ git simple-commit "Initial commit"

$ git push -u origin master

然后,另外两个开发人员使用以下内容创建他们的存储库:

# commands issued in git-recipes/10-03 directory

$ git clone shared-repo sarahs-repo

$ git clone shared-repo peters-repo

现在,所有的开发人员都准备好提交了。在这个配方中,所有的开发人员并行工作。他们每个人都创建了自己的提交:

# command issued in johns-repo
$ git simple-commit "The first commit by John"

# command issued in sarahs-repo
$ git simple-commit "The first commit by Sara"

# command issued in peters-repo
$ git simple-commit "The first commit by Peter"

现在,他们想分享他们的工作。

John 是第一个将他的更改推送到中央存储库的人:

# command issued in johns-repo
$ git push origin master

然后莎拉和彼得拉他们的工作:

# command issued in sarahs-repo
$ git pull --edit origin master

# command issued in peters-repo
$ git pull --edit origin master

他们都输入合并消息。莎拉打字"Sarah merges...",彼得打字"Peter merges..."

现在,莎拉推进她的工作:

# command issued in sarahs-repo
$ git push origin master

然后彼得试着推:

# command issued in peters-repo
$ git push origin master

该推送被拒绝,因此 Peter 合并了shared-repo中的最新更改:

# command issued in peters-repo
$ git pull --edit origin master

Peter 将合并提交的消息键入为"Peter merges again...",然后他用:

# command issued in peters-repo
$ git push origin master

当 John 和 Sara 都接受了 Peter 所做的更改后,菜谱就完成了:

# command issued in johns-repo
$ git pull origin master

# command issued in sarahs-repo
$ git pull origin master

现在,所有四个存储库都包含了清单 10-3 中的历史。清单显示了在四个存储库中执行的$ git log --graph --oneline命令的输出。

清单 10-3。 在菜谱 10-3 中创建的历史

*   901f9b1 Peter merges again...
|\
| *   70984f8 Sarah merges...
| |\
| * | ebf6fff The first commit by Sarah
* | |   192af3a Peter merges...
|\ \ \
| | |/
| |/|
| * | 4721211 The first commit by John
| |/
* | 4314f0a The first commit by Peter
|/
* ebb21d1 Initial commit

它是如何工作的

这个秘籍的目的很简单:我想让你相信秘籍 10-2 中提出的解决方案不是你应该遵循的模式。用$ git pull origin master命令创建的历史将很难阅读。配方 10-3 向您展示了如果一组 n 个开发人员并行工作,并且每个开发人员创建一个提交,那么第一个开发人员所做的提交将生成 n-1 个合并提交。

在我们的配方中,提交"The first commit by John"生成了两个合并:

70984f8 Sarah merges...
192af3a Peter merges...

很容易认识到,如果团队由 n 个开发人员组成,我们将得到 n-1 个合并提交。

看看清单 10-3 中的。显示的历史仅由三次提交生成:每个开发人员一次。如果您的团队由大量定期提交的开发人员组成,那么命令$ git pull origin master将产生一个非常复杂的修订图,其中包含大量多余的合并提交。

image 提示如果你认为清白的历史很重要,你应该把秘籍 10-2 当作一个不可遵循的模式。

10-4.保持历史的线性

问题

您希望组织由任意数量的开发人员组成的团队的工作。每个开发人员都将使用自己的存储库。他们将使用一个中央存储库来共享他们的工作。该设置与制作方法 10-2 相同。

这一次您想要定义一个工作流,它将保证所有存储库中主分支的线性结构。不允许合并提交,并且不应出现在任何存储库中。

为了保持历史的线性,所有团队成员都需要在更新的远程跟踪分支之上重新构建他们的工作。

解决办法

使用以下内容创建新目录:

$ cd git-recipes

$ mkdir 10-04

$ cd 10-04

然后用以下内容初始化一个项目:

# the command issued in git-recipes/10-04 directory
$ git init --bare shared-repo

现在,John 初始化了他的存储库,创建了一个初始提交,并将其推送到共享回购:

# commands issued in git-recipes/10-04 directory

$ git clone shared-repo johns-repo

$ cd johns-repo

$ git simple-commit i1 i2

$ git push -u origin master

然后下一个开发人员 Mark 加入了团队:

# commands issued in git-recipes/10-04 directory
$ git clone shared-repo marks-repo

现在johns-reposhared-repomarks-repo包含相同的提交i1i2$ git status -sb命令只打印当前分支的名称master。所有的存储库都是干净的,并且分支是同步的。所有储存库的状态如图 10-20 所示。

9781430261032_Fig10-20.jpg

图 10-20 。配方 10-4 中所有三个储存库的初始状态

约翰和马克平行工作

约翰和马克同时工作。约翰创建了三个提交j1j2j3,马克创建了两个提交m1m2:

# command issued in johns-repo
$ git simple-commit j1 j2 j3

# command issued in marks-repo
$ git simple-commit m1 m2

现在,在约翰的报告中执行的命令:

# command issued in johns-repo
$ git status -sb

打印以下信息:

## master...origin/master [ahead 3]

这意味着 John 的master分支包含三个修订,这三个修订没有包含在他的origin/master跟踪分支中。在 Mark 的存储库中执行的相同命令:

# command issued in marks-repo
$ git status -sb

产出:

## master...origin/master [ahead 2]

marks-repo中的master分支包含两个修订,这两个修订不包括在马克的origin/master跟踪分支中。

所有三个储存库的状态如图 10-21 所示。

9781430261032_Fig10-21.jpg

图 10-21 。约翰的主分支领先三个,马克的主分支领先两个的状态

约翰成功地将他的作品上传到共享存储库

现在开发人员想要分享他们的工作。John 是第一个将他的更改推送到中央存储库的人:

# command issued in johns-repo
$ git push origin master

约翰的命令成功了。当 John 再次运行$ git status -sb时,输出不再包含[ahead: 3]。他的master分支现在与他的origin/master分支完全同步。

Mark 解决了分叉的问题

共享存储库已经更改,因为 John 已经上传了他的修订。但是马克不知道这件事。他的命令$ git status -sb返回与之前完全相同的信息:[ahead 2]。请记住,此信息仅涉及 Mark 的本地分支机构。他们还没有更新。Mark 的存储库和共享存储库现在看起来像图 10-22 。马克的知识库中没有约翰的修订版j1j2j3

9781430261032_Fig10-22.jpg

图 10-22 。John 已成功将其 j1、j2 和 j3 版本推送到共享回购,因此 Mark 无法推送到 m1 和 m2 版本

马克想用以下方式推进他的工作:

# command issued in marks-repo
$ git push -u origin master

但是这个操作被拒绝,因为推送不是快进。

马克需要更新他的master分支。他首先从共享存储库中获取最新的修订:

# command issued in marks-repo
$ git fetch origin

为了检查他的存储库的状态,Mark 运行了$ git status -sb命令。它打印:

## master...origin/master [ahead 2, behind 3]

现在马克知道他的masterorigin/master主分支已经分叉。[ahead 2]通知他,Marks 的master分支包含两个不在他的origin/master分支中的修订。打印出[behind 3]是因为 Mark 的master分支遗漏了他的origin/master分支中包含的三个修订。

使用$ git log --graph --all --oneline --decorate马克可以想象他的master分支和他的origin/master分支分叉。图 10-23 中的显示了 Mark 的存储库和共享存储库的状态。

9781430261032_Fig10-23.jpg

图 10-23 。$ git fetch origin 后 Mark 的回购状态

为了保持历史的线性,Mark 将他的master分支放在从共享存储库中获取的origin/master分支之上:

# command issued in marks-repo

$ git rebase origin/master master

现在马克的master分支的历史是线性的,他的基础修订m1'm2'm3'在顶部。马克想确保万无一失,所以他运行了$ git log --graph --oneline --decorate$ gitk --all &命令。储存库的状态如图 10-24 中所示。

9781430261032_Fig10-24.jpg

图 10-24 。$ git rebase origin/master master 后的马克回购

marks-repo中执行的命令$ git status -sb如图图 10-24 打印:

## master...origin/master [ahead 2]

后面的消息消失了,因为 Mark 的master分支现在包含了来自他的origin/master分支的所有修订。

当处于[ahead 3]状态时,Mark 可以将其工作推送到共享存储库:

# command issued in marks-repo
$ git push -u origin master

这次接受推送。它转换共享存储库,如图图 10-25 所示。

9781430261032_Fig10-25.jpg

图 10-25 。Mark 成功执行$ git push -u origin master 后的 Mark-repo 和 shared-repo

马克的工作完成了。$ git status -sb的输出中没有超前或滞后信息。这意味着马克回购中的masterorigin/master分支是同步的。

约翰下载马克的作品

现在约翰的回购和共享回购看起来像图 10-26 。

9781430261032_Fig10-26.jpg

图 10-26 。在 John 的$ git fetch 命令之前的 Shared-repo 和 johns-repo

约翰可以用下面的命令获取马克的工作:

# command issued in johns-repo
$ git fetch

该命令将把图 10-26 中所示的存储库转换成图 10-27 中所示的形式。

9781430261032_Fig10-27.jpg

图 10-27 。John 的$ git fetch 命令后的 repo

当约翰的存储库处于图 10-27 中所示的状态时,那么$ git status -sb命令打印:

## master...origin/master [behind 2]

因此,John 的master分支遗漏了他的origin/master分支中包含的两个修订。John 可以使用以下命令快进他的分支:

# command issued in johns-repo
$ git rebase

图 10-27 中所示的存储库随着 John 的$ git rebase命令转换成图 10-28 中所示的状态。所有存储库的历史都是线性的。

9781430261032_Fig10-28.jpg

图 10-28 。约翰的回购后,他的$ git rebase 命令

它是如何工作的

Git 获取命令执行两个操作:

  • 它将对象从远程数据库复制到本地数据库。
  • 它更新远程跟踪分支。

但是,它不会更新本地跟踪分支。有两个有趣的例子:

  • 当命令$ git fetch运行后,可以快进的方式更新本地分支。
  • 当命令$ git fetch运行后,本地分支分叉。

在图 10-26 中所示的库johns-repo中执行时,$ git fetch命令会产生图 10-27 中所示的效果。这是一个快进的案例。在这种情况下,$ git status -sb命令打印:

## master...origin/master [behind 2]

这意味着本地跟踪分支master遗漏了包含在远程跟踪分支origin/master中的两个修订。

分支分叉的情况如图图 10-23 所示。图 10-22 中marks-repo执行的命令$ git fetch产生图 10-23 中所示的结果。这一次$ git status -sb的输出将是:

## master...origin/master [ahead 2, behind 3]

因此,本地跟踪分支比远程跟踪分支提前两次修订,落后三次修订。

将本地跟踪分支重置为远程跟踪分支

远程跟踪分支,比如origin/master,就是本地分支。它们可以像普通的本地分支一样使用。因此,为了保持历史线性,我们可以使用$ git rebase命令,如配方 7-1 所述。图 10-23 中marks-repo发出的命令$ git rebase origin/master master会产生图 10-24 中所示的效果。

如果设置主分支的跟踪,那么命令$ git rebase origin/master master可以简化为:

$ git rebase

它在其远程跟踪分支之上执行当前本地跟踪分支的重置。

你也可以使用$ git pull命令来达到同样的效果。默认情况下,命令$ git pull被执行为$ git fetch,后跟$ git merge。虽然改变这种行为,你可以使用-r标志或配置设置。命令$ git pull -r相当于$ git fetch后跟$ git rebase

记住,$ git fetch命令更新您的本地数据库和远程跟踪分支。它不会影响您的本地跟踪分支机构或您的普通本地分支机构。因此,您可以随时安全运行$ git fetch。它不会给你带来任何麻烦。

消息[前方 x]和[后方 y]

正如您已经看到的,可以使用$ git status -sb命令检查本地跟踪分支和远程跟踪分支之间的关系。在本地跟踪分支和远程跟踪分支指向不同修订的情况下,该命令的输出包含部分[ahead N, behind M],如:

## master...origin/master [ahead 2]

## master...origin/master [behind 2]

## master...origin/master [ahead 2, behind 1]

[ahead 2]消息表明本地跟踪分支比远程跟踪分支领先两个版本。这是您在本地跟踪分支中提交后将获得的状态。

[behind 2]消息表明您的本地跟踪分支在远程跟踪分支之后。您的本地跟踪分支缺少远程跟踪分支中可用的两个修订。当您团队中的某个成员将他或她的提交推送到共享存储库,并且您使用$ git fetch命令将它们下载到您的存储库时,您就处于这种状态。

最后的消息[ahead 2, behind 1]意味着本地跟踪分支和远程跟踪分支已经分离。本地跟踪分支包含两个不在远程跟踪分支中的修订,同时它丢失了远程跟踪分支中包含的一个修订。在执行了$ git commit$ git fetch命令之后,您获得了这个状态,假设有人推送到共享存储库。

访问远程分支

请记住,$ git status -sb$ git branch -a -vv命令只适用于您的本地分支。它们是:普通本地分支、本地跟踪分支和远程跟踪分支。命令$ git status$ git branch不访问远程存储库中的远程分支。远程分支仅在$ git fetch期间转移到您的存储库。因此,如果您想检查远端,您需要运行$ git fetch,然后运行$ git status -sb$ git branch -a -vv。但是无论您的速度有多快,$ git status -sb$ git branch -a -vv的结果可能已经过时,因为有人可能已经在您获取之后和您执行$ git status -sb$ git branch -a -vv之前将其推送到远程存储库。

为什么跟踪分支很重要

你应该注意到了$ git status -sb 总是比较本地跟踪分支和远程跟踪分支。这种比较是为您当前的分支执行的。为了获得$ git status -sb[ahead N, behind M]输出,您需要为您当前的分支定义跟踪。如果您的配置文件.git/config中没有[branch"..."]条目,例如:

[branch "master"]
    remote = origin
    merge = refs/heads/master

然后 git 就不知道比较哪个分支了。$ git status -sb的输出将不包含[ahead N, behind M]信息。

当您克隆一个存储库时,git 会自动为您的master分支配置跟踪。Git 还会在您向 checkout 命令传递远程分支的名称时设置跟踪—如配方 5-2 中的$ git checkout doc 。否则,您必须手动设置跟踪。以下是一些不同的解决方案:

  • $ git branch --set-upstream-to=origin/master命令(如配方 10-1 中的提示所示)
  • $ git push -u origin master命令(如配方 10-2 中的提示所示)
  • $ git config branch命令(如配方 10-1 中的提示所示)
  • 手动编辑.git/config文件

一旦设置,跟踪信息可用于简化许多命令。如果您当前在被设置为跟踪origin/master分支的master分支上,那么三个命令$ git rebase$ git rebase origin/master$ git rebase origin/master master 是等效的。

基本上,定义跟踪分支有两个原因:

  • $ git status -sb命令可以与$ git fetch一起使用,以确定您的分支是否与远程分支同步。
  • 许多命令可以简化为缺少参数——如在$ git rebase中——以默认跟踪分支。

10-5.没有中央存储库的协同工作

问题

您想要模拟两个开发人员 John 和 Sarah 在没有中央存储库的情况下的合作。在这种情况下,协同工作将由两个存储库组成:

  • 第一个开发者的非裸存储库
  • 10-05/sarahs-repo—第二个开发者的非裸库

两个开发人员都将在他们的master分支内提交。约翰将使用sarah分支获取莎拉的工作,莎拉将使用john分支获取约翰的工作。

提示这个秘籍强调了 git 的分布式本质,每个人都可以相互合作。

解决办法

使用以下内容创建新目录:

$ cd git-recipes

$ mkdir 10-05

$ cd 10-05

这项工作是由一个开发人员初始化的。让它成为约翰。他初始化了他的知识库:

# john's commands in git-recipes/10-05 directory
$ git init johns-repo

然后他设置他的个人数据:

# john's commands
$ cd johns-repo
$ git config --local user.name john
$ git config --local user.email john@example.net

并提交:

# john's command
$ git simple-commit j1 j2 j3

现在莎拉进入了这个项目。她克隆了johns-repo并配置了她的个人信息:

# sarah's commands
# executed in git-recipes/10-05
$ git clone johns-repo sarahs-repo
$ cd sarahs-repo
$ git config --local user.name sarah
$ git config --local user.email sarah@example.net

接下来,Sarah 对项目进行了两次修订s1s2:

# sarah's command
$ git simple-commit s1 s2

当提交准备好被获取时,Sarah 给 John 发电子邮件告知她的s1 s2修订。为了获得它们,John 需要建立远程存储库和本地跟踪分支。约翰跑了:

# john's command
$ git remote add origin ../sarahs-repo

然后,他用$ git fetch从 Sarah 的存储库中取出远程分支,作为到他的存储库中的远程跟踪分支。该命令创建了origin/master远程跟踪分支,该分支与 John 存储库中的本地master分支没有任何关系。可以用$ git branch -a -vv查一下。关于他的master分行的行不包含[origin/master]部分。看起来像是:

* master                abc123f s2

它证明它仍然是一个普通分支,因为本地跟踪分支包含[origin/master],如:

* master                abc123f [origin/master] s2

接下来,John 创建了一个名为sarah的普通本地分支:

# john's command run in 10-05/johns-repo
$ git branch sarah

并且他将他的sarah分支配置为origin/master分支的本地跟踪分支:

# john's command
$ git branch --set-upstream-to=origin/master sarah

为了检查莎拉在s1s2版本中编写的代码,约翰带着:

# john's command
$ git checkout sarah

该分支还不包含s1s2修订,证明如下:

# john's command
$ git status -sb

输出通知您,他的当前分支(sarah)比它的远程跟踪分支(origin/master)晚两次提交。John 更新了他的sarah分支:

# john's command
$ git rebase

现在约翰的存储库中的sarah分支包含了s1s2修订。约翰可以分析莎拉的贡献。当他认为这些修改没问题时,他可以将它们合并到自己的工作中:

# john's commands
$ git checkout master
$ git rebase sarah

现在是约翰进行更多修改的时候了。他跑$ git simple-commit j4 j5 j6。然后他给莎拉发电子邮件询问他的工作。

轮到莎拉下载约翰在修订版j4j5j6中贡献的代码。她用$ git clone命令开始工作,因此她的存储库中已经包含了.git/config条目,该条目将她的master分支设置为origin/master远程跟踪分支的本地跟踪分支。可以用$ git branch -a -vv命令检查。它输出:

* master                40695ac [origin/master] s2
  remotes/origin/master 604549f j3

片段[origin/master]表示master分支是origin/master远程跟踪分支的本地跟踪分支。要删除此关系,Sarah 运行:

# sarah's command
$ git config --unset branch.master.remote
$ git config --unset branch.master.merge

上述命令后,$ git branch -a -vv输出:

* master                40695ac s2
  remotes/origin/master 604549f j3

尽管她的存储库仍然包含名为origin/master的远程跟踪分支,但是她的本地master分支没有与之连接。这两个$ git config --unset命令将她的本地跟踪分支master变成了一个普通的本地分支。很好。

现在,Sarah 想要创建名为john的本地跟踪分支,以便与 John 的master分支相连接。她跑了:

# sarah's command
$ git branch john
$ git branch --set-upstream-to=origin/master john

然后她用$ git fetch从约翰的仓库里取东西。该命令从约翰的回购协议中的master分支以及一个新的远程跟踪分支remotes/origin/sarah获取所有丢失的修订。莎拉不需要约翰的origin/sarah分支,但是没有办法避开。

接下来,Sarah 切换到john分支并检查其状态:

# sarah's command
$ git checkout john
$ git status -sb

输出表明当前分支(john)比远程跟踪分支(origin/master)晚三次提交。她更新了她的john分支:

$ git rebase

她检查约翰的修改j4j5j6。然后,她将约翰的工作并入她的master分支:

$ git rebase john master

约翰如何更新他的项目?

为了下载并合并 Sarah 的所有代码,John 运行以下命令:

$ git fetch
$ git checkout sarah
$ git rebase
$ git rebase sarah master

开发人员如何对项目做出贡献?

每个开发人员通过在他或她的master分支中承诺:

$ git checkout master
$ git simple-commit x y z

莎拉如何下载约翰的稿件?

她遵循程序*约翰如何更新他的项目?*使用john分支名称代替sarah:

$ git fetch
$ git checkout john
$ git rebase
$ git rebase john master

它是如何工作的

这个秘籍强调了 git 的分布式本质。如您所见,每个存储库都可以用作修订源。您可以从您有权访问的每个存储库中获取数据。Git 不限制对一些特殊的中央存储库的获取,就像 Recipe 10-2 中的shared-repo一样,存储在服务器上。默认情况下,推送操作仅限于裸存储库,但是我们也可以避开这一限制。这将在配方 10-10 中完成。

如果您仔细遵循这个方法,您会注意到$ git fetch操作为远程存储库中的所有分支创建了远程跟踪分支。当您完成菜谱时,John 的存储库包含以下分支(由$ git branch -a -vv返回):

* master                542d21a z
  sarah                 bacddd0 [origin/master] j6
  remotes/origin/john   bacddd0 j6
  remotes/origin/master bacddd0 j6

有两个远程分支origin/masterorigin/john。第二个是 Sarah 在sarahs-repo中创建的远程john分支的远程跟踪分支。约翰不需要也不使用它,但它还是被创造出来了。最后,记住$ git fetch获取所有的远程分支,并将它们存储为远程跟踪分支。

这个过程还向您展示了您可以使用以下命令将名为some-branch的本地跟踪分支转换成普通的本地分支:

$ git config --unset branch.some-branch.remote
$ git config --unset branch.some-branch.merge

10-6.使用远程分支

问题

您和您的同事想要使用一个包含许多分支 的共享存储库,而不仅仅是一个主分支。这将让你有机会将团队重组为从事不同功能的小组。要处理项目中一个名为 foo 的特定特性,您需要创建一个名为 foo 的远程分支,存储在共享存储库中。使用 foo 特性的开发人员应该使用 foo 远程分支来共享他们的工作。

这个菜谱为您提供了操作远程分支所需的所有命令。您将学习如何:

  • 创建一个与本地分支同名的远程分支
  • 使用不同于本地分支的名称创建远程分支
  • 删除远程分支
  • 更新您的存储库以反映远程分支中的更改

解决办法

创建一个新目录并初始化和一个共享存储库:

$ cd git-recipes

$ mkdir 10-06

$ cd 10-06

$ git init --bare shared-repo

接下来为 John 创建一个存储库,创建一个初始版本 ,并将其发送给shared-repo:

$ git clone shared-repo johns-repo

$ cd johns-repo

$ git simple-commit "Initial commit"

$ git push -u origin master

然后为 Sarah 创建一个存储库:

$ git clone shared-repo sarahs-repo

让我们假设约翰是领导。他负责为所有团队成员设定任务。他决定团队的一些成员(包括 Sarah)将从事文档工作。这项工作将在一个名为doc的分支中完成。为此,John 在他的存储库中创建了一个新的普通本地分支,名为doc :

# john's command
$ git branch doc

然后,John 将该分支提供给其他开发人员。他将他的 doc 分支推入shared-repo中:

# john's command
$ git push -u origin doc

上述命令在johns-repo中为doc分支设置跟踪。在上面的命令之后,shared-repo包含了doc分支。您可以验证它正在运行:

# shared-repo's command
$ git branch

接下来,John 想要开始一个特定的单元测试工作。他创建了一个名为test的普通本地分支:

# john's command
$ git branch test

他意识到这个名字可能已经被许多团队成员用于他们自己的私人工作,这些工作与单元测试没有任何关系。因此,约翰决定将远程分支命名为special-unit-tests 。他用下面的命令将他的本地test分支推到special-unit-tests名下:

# john's command
$ git push -u origin test:special-unit-test

上面的命令在别名为origin的远程存储库中创建了一个名为special-unit-test的远程分支。用以下方式检查:

# shared-repo's command
$ git branch

让我们假设 Sarah 被分配从事文档和测试工作。她从遥控器里取出:

# sarah's command
$ git fetch

该命令在sarahs-repo中创建远程跟踪分支origin/docorigin/special-unit-tests

一段时间过去了,docspecial-unit-tests异地分公司的工作顺利通过。成员们使用配方 10-4 来同步他们的工作。

约翰从doc分公司下载了最新版本。他决定小组工作结束。远程doc分支应该被删除。约翰删除远程doc分支:

# john's command
$ git push origin :doc

上述命令执行了以下操作:

  • 删除了shared-repo.中的doc分支
  • 它删除了远程跟踪分支johns-repo.中的origin/doc
  • 然而,它没有将johns-repo中的本地跟踪分支doc转换成普通的本地分支。johns-repo中的doc分公司还在跟踪一个不存在的origin/doc远程跟踪分公司。您可以使用$ git branch -avv命令验证这一点。

image 提示本地跟踪分公司和普通本地分公司有什么区别?当然,你可以在这两种情况下都做出承诺。但是本地跟踪分支连接到远程跟踪分支;因此,您可以使用$ git status -sb来检查本地跟踪分支是在相应的远程跟踪分支之前还是之后。

因此,shared-repo不再包含doc分支。在shared-repo中执行的$ git branch 命令打印两个分支:masterspecial-unit-tests。然而,在 John 的存储库中,分支doc仍然存在——它是一个普通的本地分支。

Sarah 更新了她的远程分支机构:

# sarah's command
$ git fetch

然而,上面的命令不会删除shared-repo中不存在的远程分支doc的远程跟踪分支origin/doc。如果 Sarah 想要更新她的存储库,以反映已删除的远程分支,她需要以下命令:

# sarah's command
$ git remote prune origin

上面的命令删除了 Sarah 存储库中的远程跟踪分支origin/doc。该操作可在提取过程中通过附加的-p标志自动执行:

# sarah's command
$ git fetch -p

现在doc分支的工作已经完成。在共享存储库中或者在除 John 之外的任何其他成员的存储库中没有远程分支doc。约翰是领导,他负责将doc分公司整合到master分公司。他可以使用第六章和第七章中讨论的任何方法。例如,他可以将doc分支合并到master分支中,从而形成一个灯泡。配方 7-6 中给出了确切的程序。一旦 John 将doc分支集成到master分支中,他就会将包含doc分支的主分支推送到共享存储库中。这完全按照配方 10-4 来做,也许(如果约翰需要给灯泡换底)按照配方 7-9 来做。

约翰可以使用 gitolite 限制对远程分支机构的访问,如配方 11-10 中所述。

它是如何工作的

命令:

$ git push [remote-name] [branch-name]

在别名为remote-name的存储库中创建名为branch-name 的远程分支。为了成功,分支branch-name必须存在于您正在工作的存储库中。然而,它不一定是你现在的分行。如果您想推动当前分支,您可以使用:

$ git push [remote-name] HEAD

用于一个没有-u 的普通本地分支,如:

$ git push origin doc

该命令执行三个操作:

  • 它在本地存储库中创建一个远程跟踪分支origin/doc
  • 它在远程存储库中创建一个远程分支doc
  • 它将所需的修订从本地数据库发送到远程数据库。

如果您使用-u标志,那么普通本地分支doc将被转换为远程跟踪分支origin/doc的本地跟踪分支。

这就是如何创建一个与本地分支同名的远程分支。如果要创建不同名称的远程分支,则本地分支使用以下语法:

$ git push [remote-name] [local-branch-name]:[remote-branch-name]

命令:

$ git push origin foo:bar

发送名为foo的本地分支,并将其存储在远端的bar名下。上面的命令:

  • 在远程存储库中创建远程分支bar
  • 在本地存储库中创建远程跟踪分支origin/foo
  • 将本地分支foo设置为远程跟踪分支origin/foo的本地跟踪分支

如果要删除远程分支,请使用:

$ git push [remote-name] :[remote-branch-to-remove]

就像:

$ git push origin :foo

上面的命令:

  • 删除origin中的远程分支foo
  • 删除本地存储库中的远程跟踪分支origin/foo
  • 它不会删除您的本地跟踪分支foo.
  • 它不会将本地跟踪分支foo转换成普通的本地分支;您当地的foo分支仍然跟踪一个不存在的origin/foo分支。

您可以通过以下方式删除本地分支:

$ git branch -d foo

或者您可以将其更改为普通的本地分支机构,方法是:

$ git config --unset branch.foo.remote
$ git config --unset branch.foo.merge

每次运行$ git fetch 时,所有的远程分支都被复制到您的存储库中作为远程跟踪分支。但是如果您删除了一个远程分支,那么默认情况下,这个变更不会在其他开发人员中传播。每个开发人员都可以通过以下方式删除过时的远程跟踪分支:

$ git remote prune origin

以上命令删除不存在的远程分支的所有远程跟踪分支。在提取过程中,可以使用以下方法完成同样的操作:

$ git fetch -p

上述命令执行两项操作:

$ git fetch
$ git remote prune

10-7.使用远程分支机构捐款

问题

你是一个大型项目的开发人员之一。为了同步工作,整个团队使用带有远程分支的共享存储库。在本秘籍中,我们将使用以下设置:

  • 10-07/leaders-repo——一个项目领导者的非裸露知识库
  • 10-07/johns-repo—您的非裸存储库
  • 10-07/shared-repo—用于同步的空存储库

你负责新网页界面的工作。您计划使用名为new-web-interface的分支与团队共享您的工作。特别是,您希望:

  • 创建一个名为new-web-interface的本地分支
  • 在您的本地分支机构提交new-web-interface
  • 将您的本地new-web-interface推送到一个共享存储库中,由项目负责人进行审查

解决办法

创建新目录并初始化领导者的存储库:

$ cd git-recipes

$ mkdir 10-07

$ cd 10-07

$ git init leaders-repo

$ cd leaders-repo

$ git simple-commit "Initial commit"

接下来创建一个共享存储库:

# command issued in git-recipes/10-07 directory

$ git clone --bare leaders-repo shared-repo

领导者需要在他或她的存储库中添加源别名:

# command issued by leader in 10-07/leaders-repo directory

$ git remote add origin ../shared-repo

此时,您加入了团队:

# command issued in git-recipes/10-07 directory

$ git clone shared-repo johns-repo

因为我们将复制在这个菜谱中创建的三个存储库,John 需要重新定义他的原点以使用相对路径:

# command issued in 10-07/johns-repo directory

$ git remote rm origin

$ git remote add origin ../shared-repo

为了对项目有所贡献,请遵循以下程序(所有命令都应在10-07/johns-repo中发出):

  1. 为您的贡献创建分支:$ git checkout -b new-web-interface
  2. 在您的new-web-interface分支中提交:$ git simple-commit a b c
  3. 将分支发送到共享存储库:$ git push -u origin new-web-interface

现在,您的贡献被存储在远程存储库shared-repo的远程分支new-web-interface中。

它是如何工作的

这个方法为组织项目成员的合作提供了一个更加方便的解决方案。通过为任务使用专用的远程分支,您可以在团队内部设置组时获得更大的灵活性。您还可以在将代码合并到主分支之前检查它。

10-8.接受捐款

问题

你是一个项目的领导者。您团队中的一名成员使用new-web-interface分支将一些代码推送到共享存储库中。你要检查贡献的代码。在这个秘籍中,我们假设代码是正确的,你(记住,你是领导者)接受它。

在这个秘籍中,我们使用了秘籍 10-7 中的场景。你在leaders-repo担任领导工作。

解决办法

复制配方 10-7 中的所有储存库:

$ cd git-recipes

$ cp -R 10-07 10-08

$ cd 10-08

现在你是一个领导者,你检查new-web-interface分支中的贡献(所有的命令都应该在10-08/leaders-repo中发出):

  1. 您获取投稿:$ git fetch
  2. 您签出远程分支$ git checkout new-web-interface
  3. 你用任意的命令和工具检查文件,例如,$ ls$ cat a.txtvi b.txt
  4. 您可以使用任意命令检查修订,例如,$ git log --oneline$ git log --oneline --name-only HEAD∼3..HEAD
  5. 你决定代码是正确的,应该合并到master分支。
  6. 你检查master分支:$ git checkout master
  7. 你把工作合并到master$ git merge new-web-interface
  8. 您将作品发布给所有其他团队成员:$ git push origin master

它是如何工作的

new-web-interface分支整合到主分支的主开发线包括两个步骤。首先,领导者必须获取在new-web-interface中完成的工作。这是通过$ git fetch$ git checkout new-web-interface命令完成的。在这两个命令之后,领导者有了一个名为new-web-interface的地方分支。

因为new-web-interface是一个本地分支,所以可以用第六章和第七章中讨论的任意方法进行整合。这里我们使用了简单的$ git merge命令。也可以用$ git merge --no-ff$ git rebase来完成。

一旦new-web-interface分支被集成到master中,它就可以被公开。为此,领导者将master分支推入shared-repo

10-9.向远程分支追加提交

问题

你是一个已经将自己的工作推到共享存储库中的new-web-interface分支的开发人员。领导让你做一些改进。您将在new-web-interface分支中进行一些新的提交。

在这个秘籍中,我们使用了秘籍 10-7 中的场景。你作为一名开发人员在johns-repo工作。

解决办法

复制配方 10-7 中的所有储存库:

$ cd git-recipes

$ cp -R 10-07 10-09

$ cd 10-09

领导怎么下载你作品的第一版?

为了下载你的工作的第一个版本,领导者更新他的存储库(命令应该在10-09/leaders-repo中运行):

  • 他取你的修改:$ git fetch.
  • 他去了new-web-interface分支:$ git checkout new-web-interface

开发人员如何向远程分支追加提交?

要添加新的提交,请遵循以下步骤(命令应该在10-09/johns-repo中运行):

  1. 转到new-web-interface分支:$ git checkout new-web-interface
  2. 创建新的提交:$ git simple-commit n1 n2 n3 n4 n5
  3. 发布您的作品:$ git push origin new-web-interface

领导如何从远程分支机构下载最新版本?

领导者更新他的存储库(命令应该在10-09/leaders-repo中运行):

  1. 他取你的修改:$ git fetch
  2. 他去了new-web-interface分支:$ git checkout new-web-interface
  3. 他更新了new-web-interface分支:$ git rebase origin/new-web-interface

现在他可以检查你的新修改并接受它们(如秘籍 10-8)或要求新的改进(如秘籍 10-9)。因为领导者用$ git checkout new-web-interface命令创建了他的本地new-web-interface分支,所以为该分支设置了跟踪。因此他可以在new-web-interface上使用$ git rebase来更新这个分支。

它是如何工作的

您用于投稿的分支可以由您和您团队的其他成员在更长的时间内使用。您可以迭代地提交并请求代码评审。这样可以重复很多次。菜谱解释了领导者如何用new-web-interface分支中的最新变化来更新他的存储库。

当然这些相同的程序:

  • 向远程分支追加新提交
  • 从远程分支下载最新提交

can be performed by every member. Thus you can use the new-web-interface branch as a way to collaborate with others while working on a given feature.

10-10.用$ git push -f 重写历史

问题

你是团队的一员。你把工作推到了一个叫new-web-interface的远程分支。你的作品被拒绝了很多次。你被一次又一次地要求改正。因此,远程分支new-web-interface包含大量提交。你负责new-web-interface远程分部。领导者要求您将该分支中的所有提交压缩成一个提交,然后他才能最终合并它。

在这个秘籍中,我们使用了秘籍 10-7 中的场景。你作为一名开发人员在johns-repo工作。

解决办法

复制配方 10-7 中的所有储存库:

$ cd git-recipes

$ cp -R 10-07 10-10

$ cd 10-10

现在你是一名在10-10/johns-reponew-web-interface工作的开发者:

您的本地new-web-interface分支包含三个版本abc。您希望压缩它们并更新远程分支。

以下是您必须遵循的程序(所有命令都将在10-10/johns-repo中执行):

  1. 转到new-web-interface分支:$ git checkout new-web-interface

  2. 您的new-web-interface分支包含三个版本abc。你可以和$ git log --oneline核实一下。

  3. 修订abc还没有合并到master分支。你可以用$ git log --oneline master..new-web-interface查一下

  4. Squash your three commits with: $ git rebase -i HEAD∼3. Use the following interactive rebasing subcommands:

    reword XXXXXXX a
    fixup  XXXXXXX b
    fixup  XXXXXXX c
    

    将新版本的注释设置为abc。交互式重置基础的细节在配方 8-3 中描述。

  5. 您的new-web-interface分支包含一个新的修订abc。你可以和$ git log --oneline核实一下。

  6. 版本abc还没有合并到master分支中。你可以用$ git log --oneline master..new-web-interface查一下

  7. 使用$ git push -f origin new-web-interface重新发布您的作品

它是如何工作的

命令$ git push -f origin new-web-interface强制 git 更新远程分支new-web-interface,即使这会导致历史被重写。默认情况下,$ git push只对快速更新成功。如果你知道你在做什么,你可以使用-f标志来强制转移。

Git 允许您配置一个拒绝所有使用$ git push的非快进更新的存储库,即使使用了-f标志。您可以通过将receive.denyNonFastForwards设置为true来实现这一点。如果你跑步:

$ git config receive.denyNonFastForwards true

shared-repo中,你将禁止所有改变历史的推送。

image 提示其他关于推送的选项可以在$ git config --help手册中找到。很多都是以receive开头的前缀。

10-11.完成远程分支的工作

问题

你是团队的一员。你把你的工作推到了名为new-web-interface的远程分支。该分支被整合到master分支中,不再使用。领导要求您移除 远程分支new-web-interface。您还想删除您的本地分支。

在这个秘籍中,我们使用了秘籍 10-8 中的场景。你作为一名开发人员在johns-repo工作。

解决办法

复制配方 10-8 中的所有储存库:

$ cd git-recipes

$ cp -R 10-08 10-11

$ cd 10-11

现在你是约翰。所有命令都应该在10-11/johns-repo中运行:

  1. 您用$ git fetch更新您的项目。
  2. 转到master分支:$ git checkout master
  3. $ git rebase origin/master更新您的master分支
  4. 检查可以安全移除的分支:$ git branch --merged。输出应包括—除其他外— new-web-interface分支。这意味着可以安全地删除new-web-interface分支。命令$ git branch --merged是一个安全检查:如果分支new-web-interface没有被打印,那么删除分支是不安全的。
  5. 移除shared-repo中的远程分支new-web-interface以及带有$ git push origin :new-web-interface的本地跟踪分支origin/new-web-interface
  6. 最后用$ git branch -d new-web-interface删除你的new-web-interface分支

它是如何工作的

秘籍中发生的一件奇怪的事情在秘籍 10-6 中已经提到过了。在步骤 5 中使用以下命令删除远程分支后:

$ git push origin :new-web-interface

$ git branch -a -vv命令打印:

* master                59de3b0 [origin/master] z
  new-web-interface            59de3b0 [origin/new-web-interface] z
  remotes/origin/HEAD   -> origin/master
  remotes/origin/master 59de3b0 z

这意味着new-web-interface分支仍然是本地跟踪分支。它跟踪不再存在的origin/new-web-interface分支。我们通过在步骤 6 中完全删除new-web-interface分支来解决这个矛盾。

10-12.推送到非空存储库

问题

您在一个从非裸存储库克隆而来的存储库中工作。即使原始存储库不是一个空存储库,您也希望将其推送到原始存储库。在这个秘籍中,我们将使用两个储存库:

  • johns-repo—您提交的非裸存储库
  • public-repo—一个你推送到的非裸库

解决办法

使用以下内容创建新目录:

$ cd git-recipes

$ mkdir 10-12

$ cd 10-12

用以下内容初始化 johns-repository:

# commands issued in git-recipes/10-12 directory
$ git init johns-repo
$ cd johns-repo
$ git simple-commit "Initial commit"

接下来,克隆johns-repo以获得public-repo:

# command issued in git-recipes/10-12 directory
$ git clone johns-repo public-repo

要允许推入到非裸存储库public-repo中,请使用以下命令更改其配置:

# command issued in public-repo directory
$ git config receive.denyCurrentBranch ignore
$ git config core.worktree ../

然后将文件public-repo/.git/hooks/post-update.sample重命名为public-repo/.git/hooks/post-update。您可以通过以下方式实现这一点:

# command issued in public-repo directory

$ mv .git/hooks/post-update.sample .git/hooks/post-update

更改public-repo/.git/hooks/post-update的内容,如清单 10-4 所示。

清单 10-4。 公开-回购/的内容。git/hooks/更新后

#!/bin/sh
exec git reset --hard

public-repo的配置完成。现在转到johns-repo并添加遥控器:

# command issued in johns-repo
$ git remote add origin ../public-repo

johns-repo中创建三个提交,使用:

# command issued in johns-repo
$ git simple-commit one two three

并用$ git push origin master将它们推向公开回购。

如果你现在用$ ls列出public-repo中的文件,你会注意到它的工作目录包含了one.txttwo.txtthree.txt文件。这证明了推送操作将johns-repo的最新状态转移到了public-repo

它是如何工作的

由于工作目录的原因,推送到非空的远程存储库会导致问题。假设您和您的同事在主分支机构工作,并且你们都创建了一个名为lorem.txt的文件。如果您提交文件并将其推送到同事的存储库,那么他的工作目录会发生什么变化?是否应该执行检验?如果是这样,你的朋友可能会丢失他在lorem.txt中完成的工作。

第一步是允许以更新远程分支的方式进行推送。它是通过以下方式完成的:

$ git config receive.denyCurrentBranch ignore

此命令允许您推送至远程存储库。推送会将必要的对象从您的存储库上传到远程数据库,然后更新您正在推送的远程分支。远程存储库的工作目录不会受到影响。

要在远程存储库中执行签出,我们必须配置工作目录的路径。它是通过以下方式完成的:

$ git config core.worktree ../

最后一步是当有人推到public-repo时强制结账。这是用post-update钩子完成的。要使用钩子,你必须创建一个名为public-repo/.git/hooks/post-update的 shell 脚本。该脚本应该包含一个单独的$ git reset --hard命令,如清单 10-4 所示。

image 提示这个配方可以当作一个部署工具。public-repo是无人工作的仓库。这是一个可通过 HTTP 协议访问的只读存储库。秘籍展示了如何通过简单的$ git push命令在网络上发布你的作品。

摘要

当我们讨论同步时,我们总是考虑两个存储库:本地(发出命令的那个)和远程(通过 URL 可用的那个)。为了避免反复输入 URL 的麻烦,git 可以将它存储在本地的.git/config文件中。使用$ git remote命令管理远程 URL。

git 存储库的同步是在修订图的基础上实现的。$ git push命令将修订从本地存储库复制到远端。当您获取时,修订会从远程存储库复制到本地存储库。在这两种情况下,数据库条目在传输过程中不会更改,它们的 SHA-1 保持不变。您可以将一组 git 存储库视为分布式数据库,其中 SHA-1 充当主键。因为 SHA-1 散列是唯一的,所以我们可以在任意存储库之间复制项目,而没有密钥冲突的风险。如果该键存在于目标数据库中,它总是被视为同一个对象。

为了解释这一点,我在我的一个存储库中创建了以下版本:

6c69fa3372f7099836176c8d0f123895adea58f1 Unique commit by gajdaw

该修订版的名称是:

6c69fa3372f7099836176c8d0f123895adea58f1

从 git 的角度来看这个名字在整个宇宙中是独一无二的——在所有已知的 git 仓库中。这是一个非常强有力的假设,使得存储库的同步变得容易。每个想要同步他或她的工作和我的工作的人都需要这个提交的副本。当我按下时,远端将接收到以下对象:

6c69fa3372f7099836176c8d0f123895adea58f1 Unique commit by gajdaw

这将是我的修订版的精确副本,有一个相同的 SHA-1 名字。

我们可以用另一种方式来表达同样的事实。每当您分析任何存储库中的历史时,都要查找这个:6c69fa3372f7099836176c8d0f123895adea58f1 name。一旦你找到它,你总是可以说:哦,我的存储库中有沃齐米耶兹·加伊达在 2013 年 9 月 6 日提交的修订,为了他的书第十章的“总结”。没有其他同名的修订版。

一旦您理解了如何添加和删除遥控器以及如何复制修订,掌握组工作的下一步就是分支。直到现在,我们都集中在普通的地方分行。这些是你工作中使用的个人本地分支。没人知道他们。你不必向任何人咨询你在这些部门的工作。您可以创建、修改和销毁它们。

同样的规则适用于所有的存储库——不仅仅是您的。因此,我们需要既允许独立又允许合作的规则。这些规则非常简单:您的本地分支存储在以远程名称命名的单独目录中的其他存储库中。

当处于松散格式时,您的普通本地分支ab存储在:

.git/refs/heads/a
.git/refs/heads/b

如果有人将您的存储库命名为foo,并带有:

$ git remote add foo [URL]

并使用$ git fetch foo从您的存储库中获取数据,那么您的本地分支ab将被存储在他或她的存储库中:

.git/refs/remotes/foo/a
.git/refs/remotes/foo/b

它们不会与存储在.git/refs/heads中的本地分支冲突。这就是全部的诀窍。

存储在.git/refs/remotes中的遥控跟踪分支,如.git/refs/remotes/foo/a,可以简称为foo/a。您可以将它们用作普通的修订指针。每当你需要一个修订版的 SHA-1 时,你可以使用foo/a,就像你使用任何其他方法来引用提交、HEADmaster∼5doc²等等。

当您考虑同步时,三种重要的分支类型是:

  • 远程分支机构
  • 本地跟踪分支
  • 远程跟踪分支

它们之间的关系以及$ git commit$ git fetch$ git push对它们的影响方式如图 10-29 所示。

9781430261032_Fig10-29.jpg

图 10-29 。gitcommit git commit、 git fetch 和$ git push 对三种类型的分支的影响

下面是图 10-29 的总结:

  • 当您使用$ git commit提交时,您将本地跟踪分支向前移动。
  • 当你用$ git fetch抓取时,你向前移动你的远程追踪分支。
  • 当您按下$ git push时,您向前移动远程分支和远程跟踪分支。

始终可以使用$ git branch -a -vv命令检查跟踪。输出列表:

  • 普通地方分行为:

    lorem    a1b2c3f Some commend
    
  • 本地跟踪分支机构为:

    ipsum    a1b2c3f [origin/ipsum] Some comment
    
  • 远程跟踪分支为:

    remotes/origin/dolor    a1b2c3f Some comment
    

$ git branch -a -vv命令没有列出远程分支。要列出远程分支,您必须首先用$ git fetch获取它们。我们可以说$ git branch是一个本地命令——它不执行本地和远程存储库之间的网络传输。

你将在配方 10-4 和 10-5 中找到设置和取消跟踪的具体步骤。如果你理解了每一类分支的作用,它们就非常简单。

git 的分布式角色在有和没有中央存储库的情况下展示团队工作的方法中得到了强调。为了更好地理解这个概念,请尝试在任一存储库中运行以下命令:

$ git fetch --no-tags https://github.com/github/GitPad master:refs/remotes/xyz/pqr

该命令从库https://github.com/github/GitPad中获取master分支,并将其存储在.git/refs/remotes/xyz/pqr文件中。该操作从 GitPad 存储库中复制修订,并将它们存储在您的.git/objects数据库中。如果您不想为远程存储库定义别名,您不必这样做。Git 不需要。即使没有定义远程的别名,git 也能够从一些 URL 可以访问的任何存储库中下载修订版到您的存储库中。你的仓库和 Github 上 GitPad 的仓库之间没有任何联系。远程分支主机(在 GitPad 的存储库中)和远程跟踪分支xyz/pqr(在您的存储库中)之间的映射由 refspec 设置:

master:refs/remotes/xyz/pqr

冒号前的部分是远程分支的名称,冒号后的部分是远程跟踪分支的名称。通过使用 URL 和 refspec,您可以从任何想要的存储库中获取任意分支。

有时候有人问我,为什么有时我们写origin master用空格分开,而有时我们用/作为分隔符。就像:

$ git pull origin master

$ git rebase origin/master

在第一个命令中,origin是遥控器的名称,master是分支的名称。这是一个普通的本地分支或本地跟踪分支。

在第二个命令中origin/master是远程跟踪分支的名称。上述命令的语法可以描述为:

$ git pull [remote] [branch]
$ git rebase [branch]

在第二个命令中,我们使用远程跟踪分支origin/master作为[branch]参数。