Git工作原理 -演道网

本网站用的阿里云ECS,推荐大家用。自己搞个学习研究也不错

最近在使用git时遇到了一些问题,用google搜索到一篇很好的文章,简单的翻译了一下。(原文地址:https://codewords.recurse.com/issues/two/git-from-the-inside-out)

这篇文章解释了git的工作原理,它会使你更深入的理解git,更好的使用它来控制项目的版本。

本文重点介绍了支持Git的图形结构,以及该图形的属性指示Git行为的方式。从基础开始,同时有实例讲解,根据实例建立一个更真实的模型,让你更好地理解git做了什么。

创建项目

1
2
~ $ mkdir alpha
~ $ cd alpha

项目目录是alpha

1
2
~/alpha $ mkdir data
~/alpha $ printf 'a' > data/letter.txt

到目录alpha下创建了一个名为data的目录,在里面创建了一个名为letter.txt的文件,其中的内容是一个字符aalpha目录结构如下:

1
2
3
alpha
└── data
└── letter.txt

初始化仓库

1
2
~/alpha $ git init
Initialized empty Git repository

git init使当前目录变成了Git仓库,为此,它创建了一个.git目录并向其中写入了一些文件。这些文件定义了关于Git配置和项目历史的一切,它们只是普通文件。 用户可以使用文本编辑器或shell来读取和编辑它们。 这就是说,用户可以像他们的项目文件一样轻松地阅读和编辑他们项目的历史。

现在alpha目录的结构就像下面这样

1
2
3
4
5
6
alpha
├── data
| └── letter.txt
└── .git
├── objects
etc...

.git目录及其内容归Git系统所有,所有其他的文件统称为工作副本,归用户所有。

添加文件

1
~/alpha $ git add data/letter.txt

运行上面的命令,有两个效果。

首先,它在.git/objects/目录中创建了一个新的blob文件。

这个blob文件包含data/letter.txt的压缩内容。 它的名称通过文件内容的Hash(应该是用的sha1)得到。取一段文本的Hash值意味着运行一个程序,将其内容变成一块较小的文本,这块文本是原始内容的唯一标识。例如,Gitaes转换为2e65efe2a145dda7ee51d1741299f848e5bf752e,前两个字符用作对象数据库中的目录的名称:.git/objects/2e/。 散列的其余部分用作保存所添加文件的内容的blob文件的名称:

.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e

git add将文件添加到Git并将其内容保存到objects目录中。 如果用户从工作副本中删除data/letter.text,它的内容在Git中仍然是安全的。

其次,git add将文件添加到索引索引是一个列表,其中包含Git已被告知要跟踪的每个文件。 它作为一个文件存储在.git/index。 文件的每一行将被跟踪的文件映射到其内容的哈希。 这是运行git add命令后的索引:

data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e

创建一个包含内容1234的文件data/number.txt

1
~/alpha $ printf '1234' > data/number.txt

目录结构变成了下面这样:

1
2
3
4
alpha
└── data
└── letter.txt
└── number.txt

添加文件到Git

1
~/alpha $ git add data

git add命令创建一个包含data/number.txt内容的blob对象。 它为指向blobdata/number.txt添加一个索引条目。 这是git add命令第二次运行后的索引:

1
2
data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e
data/number.txt 274c0052dd5408f8ae2bc8440029ff67d79bc5c3

只有数据目录中的文件被列在索引中,虽然用户运行了git add data。 数据目录data不单独列出。

1
2
~/alpha $ printf '1' > data/number.txt
~/alpha $ git add data

当最初创建data/number.txt时,想要输入内容1,而不是1234.他们进行更正并将文件再次添加到索引。 此命令将使用新内容创建一个新的blob。 并且它更新data/number.txt的索引条目以指向新的blob

git commit

1
2
~/alpha $ git commit -m 'a1'
[master (root-commit) 774b54a] a

进行a1提交,Git打印了这次提交的相关信息。

commit命令有三个步骤。 创建一个树形图来表示正在提交的项目版本的内容。 创建一个提交对象。 将当前分支指向新的提交对象。

创建树形图

Git通过从索引创建树图来记录项目的当前状态。 此树图记录项目中每个文件的位置和内容。

该图由两种类型的对象组成:blob

Blob是通过git add存储的。 它们表示文件的内容。

commit时存储树。 树表示工作副本中的目录。

下面是记录新提交的data目录的内容的树对象:

1
2
100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
100664 blob 56a6051ca2b02b04ef92d5150c9ef600403cb1de number.txt

第一行记录展示了data/letter.txt文件的信息。 第一部分是文件的权限。 第二部表示此条目的内容由blob而不是表示。 第三部分描述了blobHash。 第四部分描述文件的名称。第二行当然就是文件data/number.txt文件的信息。

下面是alpha的树对象:

1
040000 tree 0eed1217a2947f4930583229987d90fe5e8e0b74 data

alpha树对象只包含了一个指向data树指针。(译著:如果alpha目录下还有一个文件, alpha树对象就还会多一行,就是指向多出文件的blob对象)

`a1` commit object pointing at its tree graph

在上面的图中,root树指向data树。 data树指向data/letter.txtdata/number.txtblob

创建一个提交对象

git commit在创建树图后创建一个提交对象提交对象只是.git/objects/中的另一个文本文件:

1
2
3
4
5
tree ffe298c3ce8bb07326f888907996eaa48d266db4
author Mary Rose Cook 1424798436 -0500
committer Mary Rose Cook 1424798436 -0500
a1

第一行指向树图。 Hash是表示工作副本的根的树对象。 也就是alpha目录。 最后一行是提交消息。

`a1` commit object pointing at its tree graph

将当前分支指向新的提交

最后,commit命令将当前分支指向新的提交对象。哪个是当前分支? .git/HEAD文件记录了当前分支:

1
ref: refs/heads/master

这说明HEAD指向master, master是主分支。
HEADmaster都是refsrefGit用来标识特定提交的标签。

表示master引用的文件不存在,因为这是对仓库的第一次提交。 Git.git/refs/heads/master下创建文件,并将其内容设置为提交对象的哈希值:

1
74ac3ad9cde0b265d2b4f1c778b283a6e2ffbafd

(如果你在阅读时输入这些Git命令,你的a1提交的哈希值将不同于我的哈希值。 内容对象(如blob和树)总是散列为相同的值。 提交不会,因为它们包括创建者的日期和名称。)

添加HEADmaster到树图:

`HEAD` pointing at `master` and `master` pointing at the `a1` commit

HEAD指向master,就像提交之前一样。 但master现在存在并指向新的提交对象a1

再一次commit

下面是a1提交后的Git结构图。 包含工作副本和索引。

`a1` commit shown with the working copy and index

工作副本,索引和a1提交都具有与data/letter.txtdata/number.txt相同的内容。 索引和HEAD提交都使用Hash来引用blob对象,但是工作副本内容作为文本存储在不同的地方。

1
~/alpha $ printf '2' > data/number.txt

data/number.txt的内容设置为2.这会更新工作副本,但索引HEAD不变。

`data/number.txt` set to `2` in the working copy

1
~/alpha $ git add data/number.txt

将文件添加到Git。 这会向objects目录添加一个包含2blob。 它指向新blobdata/number.txt的索引条目。

`data/number.txt` set to `2` in the working copy and index

1
2
~/alpha $ git commit -m 'a2'
[master f0af7e6] a2

提交的步骤与之前相同。

首先,创建一个新的树形图来表示索引的内容。

data/number.txt的索引条目已更改。 旧的数据树不再反映data目录的索引状态。 必须创建一个新的data树对象:

1
2
100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
100664 blob d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 number.txt

新数据树与旧数据树的哈希值不同。 必须创建一个新的根树以记录此Hash值:

1
040000 tree 40b0318811470aaacc577485777d7a6780e51f0b data

其次,创建一个新的提交对象。

1
2
3
4
5
6
tree ce72afb5ff229a39f6cce47b00d1b0ed60fe3556
parent 774b54a193d6cfdd081e581a007d2e11f784b9fe
author Mary Rose Cook 1424813101 -0500
committer Mary Rose Cook 1424813101 -0500
a2

提交对象的第一行指向新的根树对象。 第二行指向a1:新提交的父级。要找到父提交,要跟着HEADmaster来掌握并发现a1的提交哈希。

最后,master分支文件的内容被设置为新提交的hash值。

`a2` commit

Git graph without the working copy and index

内容存储为对象树。 这意味着只有diffs存储在对象数据库中。 看看上面的图表。 a2 commit重用了在a1提交之前创建的blob。 类似地,如果提交中整个没有变,则其树以及其下的所有blob和树可以被重用。 一般来说,提交的内容更改很少。 这意味着Git可以在小的空间中存储大的提交历史。

每个提交都有一个父级。 这意味着存储库可以存储项目的历史记录。

refs是提交历史的一部分或另一部分的入口点。 这意味着提交可以被赋予有意义的名称。 用户将他们的工作组织到对他们的项目有意义的谱系中,具体的参考如fix-for-bug-376Git使用符号引用,如HEADMERGE_HEADFETCH_HEAD来支持操作提交历史记录的命令。

objects目录中的节点是不可变的。 这意味着内容被编辑,而不是被删除。 每一次添加的内容和每次提交的对象都是在目录中.

refs是可变的。 因此,ref的含义可以改变。 master指向的提交可能是当前项目的最佳版本,但是,很快,它将被更新的更好的提交取代。

Check out a commit

1
2
~/alpha $ git checkout 37888c2
You are in 'detached HEAD' state...

使用Hashcheckout``a2的提交(如果你在运行这些git命令,这里的hash值要换成你自己的,使用git log查看)

checkout 有四个步骤:

  • 获取a2提交,并获取指向它的树图
  • 它将树形图中的文件条目写入工作副本。 这将导致没有更改。 工作副本已经具有被写入其中的树图的内容,因为HEAD已经通过master指向a2提交。
  • 将树图中的文件条目写入索引。 这也导致没有变化。 索引已经具有a2提交的内容。
  • HEAD的内容设置为a2提交的哈希:
1
f0af7e62679e144bb28c627ee3e8f7bdb235eee9

HEAD的内容设置为Hash值会使存储库处于分离的HEAD状态。 注意在下面的图表中,HEAD直接指向a2提交,而不是指向master

Detached `HEAD` on `a2` commit

1
2
3
4
~/alpha $ printf '3' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'a3'
[detached HEAD 3645a0e] a3

data/number.txt的内容设置为3,并提交更改。 GitHEAD得到a3提交的父级。 而不是找到一个分支ref,它找到并返回a2提交的哈希。

Git更新HEAD直接指向新的a3提交的哈希。 存储库仍处于分离的HEAD状态。 它不在一个分支上,因为没有提交指向a3或其一个后代。 这意味着它很容易丢失。

`a3` commit that is not on a branch

创建分支

1
~/alpha $ git branch deputy

创建一个新分支deputy。 这只是在.git/refs/heads/deputy创建一个新文件,其中包含HEAD指向的哈希, 也就是a3提交的哈希。

分支只是refs, refs只是文件。 这意味着Git分支是轻量级的。

deputy分支的创建将新的a3提交安全地放置在分支上。 HEAD仍然分离,因为它仍然直接指向一个提交。

`a3` commit now on the `deputy` branch

切换分支

1
2
~/alpha $ git checkout master
Switched to branch 'master'

切换到了master分支

  • 获取a2提交,并将master指向获取提交点的树图。
  • 树形图中的文件条目替换工作副本的文件。 这将使data/number.txt的内容设置为2。
  • 将树图中的文件条目写入索引。 这会将data/number.txt的条目更新为2个blob的散列。
  • 改变HEAD的值
1
ref: refs/heads/master

`master` checked out and pointing at the `a2` commit

切换到与工作副本不兼容(有改变)的分支

1
2
3
4
5
6
7
~/alpha $ printf '789' > data/number.txt
~/alpha $ git checkout deputy
Your changes to these files would be overwritten
by checkout:
data/number.txt
Commit your changes or stash them before you
switch branches.

data/number.txt的内容设置为789, 当checkoutdeputy时,Git报了一个错误。

HEAD指向mastermaster指向a2,其中data/number.txt的内容是2deputy指向a3,其中data/number.txt的内容是3data/number.txt在工作副本的内容为789,所有这些版本都不同,差异必须解决。

Git可以使用要切换分支中提交的版本替换掉工作副本中的版本,这样可以避免数据丢失。

Git可以合并工作副本的版本和要切换分支中的版本,但这很复杂。

所以Git报了一个错误,不能切换分支。

1
2
3
~/alpha $ printf '2' > data/number.txt
~/alpha $ git checkout deputy
Switched to branch 'deputy'

data/number.txt的内容变回2时,便切换成功了。

`deputy` checked out

合并祖先

1
2
~/alpha $ git merge master
Already up-to-date.

将主分支master和并到deputy分支。和并两个分支实际上是合并两个提交。第一个提交指向deputy,它是接收者。第二个提交指向master,它是提交者。可以理解为把master提交到deputy。对于这个合并,git什么也没有做,因为两个分支的内容是一样的。

图中的一系列的提交可以看成是对存储库的一系列更改。这也就意味着,在合并中,如果提交者(master)是接收者(deputy)的祖先,git将什么也不做,因为这些变化已经存在。

合并后代

1
2
~/alpha $ git checkout master
Switched to branch 'master'

切换到分支master

`master` checked out and pointing at the `a2` commit

1
2
~/alpha $ git merge deputy
Fast-forward

合并deputymasterGit发现接受者的a2提是提交者a3的祖先,它可以做快进合并。

获得提交者的提交a3并提供指向它的树图,将树图中的文件条目写入工作副本和索引。快进是指master指向a3

`a3` commit from `deputy` fast-forward merged into `master`

在合并中,如果提交者(deputy分支上的a3提价)是接收者(master上的a2提价)的后代,则历史记录不改变。 已经有一系列提交描述了要做出的改变(接收者和提交者之间的提交序列)。 虽然Git历史没有改变,Git图确实改变。 HEAD指向的具体引用被更新为指向提交者(master指向a3)。

合并来自两个不同谱系的分支

1
2
3
4
~/alpha $ printf '4' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'a4'
[master 7b7bd9a] a4

number.txt的内容设置为4,并提交到master

1
2
3
4
5
6
~/alpha $ git checkout deputy
Switched to branch 'deputy'
~/alpha $ printf 'b' > data/letter.txt
~/alpha $ git add data/letter.txt
~/alpha $ git commit -m 'b3'
[deputy 982dffb] b3

切换到deputy分支,把data/letter.txt的内容设置为b,并提交到deputy

`a4` committed to `master`, `b3` committed to `deputy` and `deputy` checked out

提交可以共享父级,这意味着可以在提价的历史中创建新的谱系。

提交可以有多个父级。 这意味着单独的谱系可以通过具有两个父的提交来合并:合并提交。

1
2
~/alpha $ git merge master -m 'b4'
Merge made by the 'recursive' strategy.

合并masterdeputy

Git发现接收者b3和提供者a4在不同的谱系中。 它做一个合并提交。 这个过程有八个步骤。

  1. Git将提交者的提交的哈希写入到alpha/.git/MERGE_HEAD文件。 这个文件的存在告诉Git在合并中。
  2. Git查找基本提交:接收者和提交者提交的最近的祖先的共同点。

`a3`, the base commit of `a4` and `b3`

提交有父级别。 这意味着可以找到两个谱系起始点。 Gitb3向后跟踪,找到所有的祖先,从a4向后寻找所有的祖先。 它找到两个谱系共享的最近的祖先a3。 这是基本提交。

  1. Git从接收者和提交者提交的树图生成基本的索引。
  2. Git生成一个diff,它将接收者提交和提交者提交对基础提交所做的更改合并。 此diff是指向更改的文件路径列表:添加,删除,修改或冲突。

Git获取出现在basereceivergiver索引中的所有文件的列表。 比较较索引条目以决定对文件做出的更改。 它将一个相应的条目写入diff。 在这种情况下,diff有两个条目。

第一个条目是是data/letter.txt。 文件内容在basereceiver中不同。 但是在basegiver中是一样的。 Git看到内容被reviceer修改,但是没有被giver修改。 data/letter.txtdiff条目是一个修改,而不是冲突。

diff中的第二个条目是data/number.txt。 在这种情况下,文件内容在basereceiver中是相同的,并且在giver中是不同的。 data/letter.txtdiff条目也是一个修改。

可以找到合并的base提交。 这意味着,如果一个文件只是从receiver或提giverbase改变,Git可以自动解决该文件的合并。 这减少了用户必须做的工作。

  1. diff中的条目指示的更改将应用于工作副本。 data/letter.txt的内容设置为bdata/number.txt的内容设置为4
  2. diff中的条目指示的更改将应用于索引。 data/letter.txt的条目指向b blobdata/number.txt的条目指向4 blob
  3. 更新索引:
1
2
3
4
5
6
7
tree 20294508aea3fb6f05fcc49adaecc2e6d60f7e7d
parent 982dffb20f8d6a25a8554cc8d765fb9f3ff1333b
parent 7b7bd9a5253f47360d5787095afc5ba56591bfe7
author Mary Rose Cook 1425596551 -0500
committer Mary Rose Cook 1425596551 -0500
b4

注意:这次提交有两个父级

  1. 将当前分支deputy 分支指向新的提交。

`b4`, the merge commit resulting from the recursive merge of `a4` into `b3`

合并来自不同谱系的两个提交,这两个提交都修改同一个文件

切换到master分支,并把deputy合并到master快进b4,现在masterdeputy都指向同一个提交

`deputy` merged into `master` to bring `master` up to the latest commit, `b4`

1
2
3
4
5
6
~/alpha $ git checkout deputy
Switched to branch 'deputy'
~/alpha $ printf '5' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'b5'
[deputy bd797c2] b5

切换到deputy分支,把data/number.txt的内容设置为5,并提交。

1
2
3
4
5
6
~/alpha $ git checkout master
Switched to branch 'master'
~/alpha $ printf '6' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'b6'
[master 4c3ce18] b6

切换到master分支,把data/number.txt的内容设置为6,并提交。

`b5` commit on `deputy` and `b6` commit on `master`

1
2
3
4
~/alpha $ git merge deputy
CONFLICT in data/number.txt
Automatic merge failed; fix conflicts and
commit the result.

deputy合并到master。存在冲突,并且合并已暂停。冲突合并的过程遵循与未冲突合并的过程相同的前六个步骤:设置.git/MERGE_HEAD,查找base,生成basereceivergiver的索引,创建diff,更新工作副本和更新索引。由于冲突,不采取第七提交步骤和第八更新ref步骤。让我们再次看看这些步骤,发生了什么。

  1. Gitgiver提交的哈希写入.git/MERGE_HEAD文件。

`MERGE_HEAD` written during merge of `b5` into `b6`

  1. Git找到base提交b4
  2. Git从接收者和提交者提交的树图生成基本的索引。
  3. Git生成一个diff,它将接收者提交和提交者提交对基础提交所做的更改合并。 此diff是指向更改的文件路径列表:添加,删除,修改或冲突。

在这种情况下,diff只包含一个条目:data/number.txt。 该条目被标记为冲突,因为data/number.txt的内容在接收者,提供者和base中是不同的。

  1. diff中的条目指示的更改将应用于工作副本。 对于冲突区域,Git将两个版本写入工作副本中的文件。 data/number.txt的内容设置为:
1
2
3
4
5
<<<<<<< HEAD
6
=======
5
>>>>>>> deputy
  1. diff中的条目指示的更改应用于索引。 索引中的条目通过其文件路径和阶段的组合成唯一标识。 未冲突文件的条目具有阶段0.在此合并之前,索引如下所示,其中0是阶段值:
1
2
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
0 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb

在合并diff被写入索引之后,索引如下所示:

1
2
3
4
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
1 data/number.txt bf0d87ab1b2b0ec1a11a3973d2845b42413d9767
2 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
3 data/number.txt 7813681f5b41c028345ca62a2be376bae70b7f61

在阶段0data/letter.txt的条目与在合并之前相同。 在阶段0data/number.txt的条目被去掉了。 它有三个新的条目。 阶段1的条目具有base data/number.txt内容的散列。 阶段2的条目具有receiver data/number.txt内容的散列。 阶段3的条目具有giver data/number.txt内容的散列。 这三个条目的存在告诉Git data/number.txt是冲突的。合并就暂停了。

1
2
~/alpha $ printf '11' > data/number.txt
~/alpha $ git add data/number.txt

通过将data/number.txt的内容设置为11来合成两个冲突版本的内容。他们将文件添加到索引。 Git添加一个包含11Blob。添加一个冲突的文件告诉Git冲突已解决。 Git从索引中删除阶段1,23data/number.txt条目。 在阶段0data/number.txt的条目中添加新blob的散列。 该索引现在为:

1
2
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
0 data/number.txt 9d607966b721abde8931ddd052181fae905db503
1
2
~/alpha $ git commit -m 'b11'
[master 251a513] b11
  1. 提交。 Git在存储库中看到.git/MERGE_HEAD,告诉它合并正在进行。 然后检查索引并发现没有冲突。 就创建一个新的提交b11,以记录解析的合并的内容。 z最后会删除.git/MERGE_HEAD文件。 这将完成合并。
  2. 将当前分支master指向新的提交。

`b11`, the merge commit resulting from the conflicted, recursive merge of `b5` into `b6`

移除一个文件

下面的图包括提交历史、最近提交的树和blob以及工作副本和索引:

The working copy, index, `b11` commit and its tree graph

1
2
~/alpha $ git rm data/letter.txt
rm 'data/letter.txt'

告诉Git删除data/letter.txt。 该文件从工作副本中删除。 该条目从索引中删除。

After `data/letter.txt` `rm`ed from working copy and index

1
2
~/alpha $ git commit -m '11'
[master d14c7d2] 11

提交。 作为提交的一部分,一如既往,Git构建一个表示索引内容的树形图。 data/letter.txt不包括在树图中,因为它不在索引中。

`11` commit made after `data/letter.txt` `rm`ed

复制存储库

1
2
~/alpha $ cd ..
~ $ cp -R alpha bravo

alpha/存储库的内容复制到bravo/目录。 这将产生以下目录结构:

1
2
3
4
5
6
7
~
├── alpha
| └── data
| └── number.txt
└── bravo
└── data
└── number.txt

现在bravo目录中有另一个Git图:

New graph created when `alpha` `cp`ed to `bravo`

将存储库链接到另一个存储库

1
2
~ $ cd alpha
~/alpha $ git remote add bravo ../bravo

移回到alpha存储库。 他们将bravo设置为alpha上的远程存储库。 这会在alpha/.git/config文件中添加:

1
2
[remote "bravo"]
url = ../bravo/

从远程获取分支

1
2
3
4
5
~/alpha $ cd ../bravo
~/bravo $ printf '12' > data/number.txt
~/bravo $ git add data/number.txt
~/bravo $ git commit -m '12'
[master 94cd04d] 12

进入bravo存储库。 将data/number.txt的内容设置为12,并将更改提交到bravo上的master

`12` commit on `bravo` repository

1
2
3
4
5
~/bravo $ cd ../alpha
~/alpha $ git fetch bravo master
Unpacking objects: 100%
From ../bravo
* branch master -> FETCH_HEAD

进入alpha存储库。 从bravo获取masteralpha。 这个过程有四个步骤。

  1. Git获取masterbravo上指向的提交的哈希。 这是12提交的哈希。
  2. Git提供了12提交所依赖的所有对象的列表:提交对象本身,其树图中的对象,12提交的祖先提交和它们的树图中的对象。 它从此列表中删除alpha对象数据库已有的对象。 它将其余部分复制到alpha/.git/objects/
  3. alpha/.git/refs/remotes/bravo/master下的具体ref文件的内容设置为12提交的哈希值。
  4. alpha/.git/FETCH_HEAD的内容设置为:
1
94cd04d93ae88a1f53a4646532b1e8cdfbc0977f branch 'master' of ../bravo

下图表示了fetch命令从bravo获取了master12提交

`alpha` after `bravo/master` fetched

对象是可以复制的,这意味着可以在存储库之间共享历史记录。

存储库可以存储远程分支引用,如alpha/.git/refs/remotes/bravo/master, 这意味着存储库可以在本地记录在远程存储库上分支的状态。 在获取时是正确的,但如果远程分支改变,它将过期。

合并FETCH_HEAD

1
2
3
~/alpha $ git merge FETCH_HEAD
Updating d14c7d2..94cd04d
Fast-forward

合并FETCH_HEAD, FETCH_HEAD只是另一个ref。 解析了12提交,givermaster开始指向11提交。 Git做一个快进合并,并将master指向在12提交。

`alpha` after `FETCH_HEAD` merged

从远程分支Pull

1
2
~/alpha $ git pull bravo master
Already up-to-date.

bravomaster拉到alphaPullfetch and merge FETCH_HEAD的缩写。

Clone一个存储库

1
2
3
~/alpha $ cd ..
~ $ git clone alpha charlie
Cloning into 'charlie'

移动到上面的目录。 clone alphacharlieclonecharlie具有与生成bravo存储库的cp类似的结果。 Git创建一个名为charlie的新目录。 它将charlie作为一个Git仓库,将alpha添加为远程仓库被称为origin,获取源并合并FETCH_HEAD

Push分支到远程分支

1
2
3
4
5
~ $ cd alpha
~/alpha $ printf '13' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m '13'
[master 3238468] 13

返回到alpha仓库,把data/number.txt的内容设置为13,并提交。

1
~/alpha $ git remote add charlie ../charlie

设置alpha的远程仓库为charlie

1
2
3
4
5
~/alpha $ git push charlie master
Writing objects: 100%
remote error: refusing to update checked out
branch: refs/heads/master because it will make
the index and work tree inconsistent

push mastercharlie.

13提交所需的所有对象都复制到charlie

此时,推送过程停止。 Git告诉我们出了什么问题。 它拒绝推送到远程分支。 这是有道理的, 因为推送将更新远程索引和HEAD。 这将导致混乱,如果有人正在编辑远程的工作副本。(这也有其他的解决办法,可以google一下)

此时,可以创建一个新的分支,将13提交合并到其中,并将该分支推送到charlie。但是我们想要一个类似GitHub那样的中央仓库,无论什么时候都可以push pull。(中央仓库为什么可以?因为在初始化仓库的时候使用的是git init --bare, 初始化成一个裸存储库,远程仓库应该都要这么初始化。)

Clone 一个裸仓库

1
2
3
~/alpha $ cd ..
~ $ git clone alpha delta --bare
Cloning into bare repository 'delta'

移动到上面的目录。 将delta clone为裸存储库。 这是一个有两个区别的普通clone。 配置文件指示存储库是裸的。 通常存储在.git目录中的文件存储在存储库的根目录中如下:

1
2
3
4
5
delta
├── HEAD
├── config
├── objects
└── refs

`alpha` and `delta` graphs after `alpha` cloned to `delta`

Push分支到裸存储库

1
2
~ $ cd alpha
~/alpha $ git remote add delta ../delta

返回到alpha存储库。 将delta设置为alpha上的远程存储库。

1
2
3
4
~/alpha $ printf '14' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m '14'
[master cb51da8] 14

data/number.txt的内容设置为14,并将更改提交到alpha上的master

`14` commit on `alpha`

1
2
3
4
~/alpha $ git push delta master
Writing objects: 100%
To ../delta
3238468..cb51da8 master -> master

push masterdelta,有3个步骤

  1. master分支上的14提交所需的所有对象都从alpha/.git/objects/复制到delta/objects /
  2. delta/refs/heads/master被更新为指向14提交。
  3. alpha/.git/refs/remotes/delta/master设置为指向14提交。 alpha具有delta的状态的最新记录.

`14` commit pushed from `alpha` to `delta`

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

未经允许不得转载:演道网 » Git工作原理 -演道网

赞 (0)
分享到:更多 ()
已有 0 条评论 腾讯微博