use*_*670 -1 svn git version-control github
我正在尝试理解git,我想知道你是否可以在我在下面的例子中做出的任何错误的观点上纠正我.
举个简单的例子,假设我初始化一个包含以下2个简单文件的存储库.
main.cpp中
#include <stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
algorithms.cpp
#include <string.h>
void reverse_string ( char * s )
{
char * offend = *(s + strlen(s));
while(offend != s && --offend != s)
{
char temp = *s;
*s = *offend;
*offend = temp;
++s;
}
}
Run Code Online (Sandbox Code Playgroud)
当我commit在add所有文件之后创建第一个文件时,2个文件的内容存储在git中,然后我返回一个根据2个文件的内容计算的哈希值(加上一些元数据,例如它们在目录树中的位置和等等).该散列仅仅是一个标识符,但也可以用于检查不同的提交是否包括相同的文件集(假设散列中没有冲突).我们假设哈希是firstcommit5njn2n34n.
现在我已经进行了第一次提交,base除非我rebase在某个时刻,否则它将用于所有后续提交.
假设我对第二个文件稍作修改.
algorithms.cpp
#include <string.h>
void reverse_string ( char * s )
{
char * offend = *(s + strlen(s));
while(offend != s && --offend != s)
{
char temp = *s;
*s++ = *offend;
*offend = temp;
}
}
Run Code Online (Sandbox Code Playgroud)
再做一个commit.它的哈希值仅commit来自algorithms.cpp.完全没有参与main.cpp文件commit.(我相信,当人们通过说git是"你的存储库的快照"来教git时,这会产生误导.更确切地说,a commit只是你改变的文件的快照.)让我们假设提交的哈希是secondcommit5njn2n34n.
此时的实际物理存储库看起来像原始的main.cpp和我最后一次的algorithms.cpp版本commit.最后commit有一个名为的别名HEAD.因此,考虑在给定时间点物理存储库如何在基本提交之上按顺序应用的提交数量的方式.
假设我做了另一个改变
main.cpp中
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
然后commit.第3个commit包含上述文件的快照.让我们假装它的哈希是thirdcommit9123b1nb31n.
现在我的提交历史看起来像
firstcommit5njn2n34n 又名 base
----> secondcommit5njn2n34n
----> thirdcommit9123b1nb31n又名HEAD
假设我现在跑了git checkout secondcommit5njn2n34n.在引擎盖下,git运行一些逻辑
"好吧,我拿了他添加的那两个原始文件,main.cpp和algorithms.cpp,然后我将commit.cpp替换为commit secondcommit5njn2n34n中的版本."
现在我的工作目录看起来像
main.cpp中
#include <stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
algorithms.cpp
#include <string.h>
void reverse_string ( char * s )
{
char * offend = *(s + strlen(s));
while(offend != s && --offend != s)
{
char temp = *s;
*s = *offend;
*s++ = *offend;
*offend = temp;
}
}
Run Code Online (Sandbox Code Playgroud)
将HEAD仍然是thirdcommit9123b1nb31n.那是对的吗?如果我现在改变了一个文件又做了另一个文件commit会怎么样?这是不允许的,还是会让commit历史上有两条路?
当我在添加所有文件后进行第一次提交时,2个文件的内容存储在git中,我得到一个根据2个文件的内容计算的哈希值(加上一些元数据,例如它们在目录树中的位置)等等).该散列仅仅是一个标识符,但也可以用于检查不同的提交是否包括相同的文件集(假设散列中没有冲突).我们假设哈希是
firstcommit5njn2n34n.
是的,虽然最好将这个哈希看作是(或"在这种情况下")提交的"真实名称" .(在SVN术语中,它就像-r数字一样.)
现在我已经进行了第一次提交,它是所有后续提交的基础,除非我在某个时候重新提交.
不:这里Git和SVN已经分道扬.. 第一次提交是当前的提交,并且为了绘制所有提交的图表,你可以绘制一个图形,但是现在图形很无聊,因为它有一个没有边的节点(一个顶点)和一个指向一个节点的标签:
o <-- master
Run Code Online (Sandbox Code Playgroud)
标签是master,它指向图中的一个提交(包含哈希ID).
......再做一次提交.该提交的哈希仅从algorithms.cpp计算.
不,这是错的.
新哈希是新提交的校验和.该新提交包含五个项目:四个项目,并提交信息.让我们来看看两个实际的提交,还有一个树,具体来说.我没有你的存储库,所以在这里我将使用我的Git本身的存储库副本.1 我们可以git cat-file用来查看存储库中存储的任何对象.有了-p它,它将以文本形式"漂亮地打印"它们(实际上,大多数已经是文本,尽管树不是;不是这对使用它们很重要).这是我目前的提交:
$ git cat-file -p HEAD | sed 's/@/ /g'
tree 4a87650eeb8ca29bd76be9275a246e35af37904b
parent cf4c2cfe52be5bd973a4838f73a35d3959ce2f43
author Chris Torek <chris.torek gmail.com> 1467720923 -0700
committer Chris Torek <chris.torek gmail.com> 1467720923 -0700
git diff: improve A...B merge-base handling
When git diff is given a symmetric difference A...B, it chooses
some merge base between the two specified commits.
[snip]
Run Code Online (Sandbox Code Playgroud)
特别注意tree和parent线条.这五个项目是:
将此与您查看"root"(无父级)提交时所看到的内容(例如此提交)相比较(找到的ID git rev-list HEAD | tail -1):
$ git cat-file -p e83c5163316f89bfbde7d9ab23ca2e25604af290 | sed 's/@/ /g'
tree 2b5bfdf7798569e0b59b16eb9602d5fa572d6038
author Linus Torvalds <torvalds ppc970.osdl.org> 1112911993 -0700
committer Linus Torvalds <torvalds ppc970.osdl.org> 1112911993 -0700
Initial revision of "git", the information manager from hell
Run Code Online (Sandbox Code Playgroud)
现在只有四个项目:没有parent线.(如果我们查看一些合并提交,我们会发现它们至少有两 parent行,每个父提交节点一行.)
tree这个存储库中的s(对于Git本身)非常大,但我可以git cat-file再次显示它们(从二进制转换为文本).这是一个片段:
$ git cat-file -p HEAD:
[snip]
100644 blob 536e55524db72bd2acf175208aef4f3dfc148d42 COPYING
040000 tree 1eb510854c64576991212dc139de0813ff9f2140 Documentation
100755 blob 0fe02a6ce28212200cada0cdce2b19cbc3937cff GIT-VERSION-GEN
[snip]
Run Code Online (Sandbox Code Playgroud)
现在让我们回到您的存储库,因为它更简单.
让我们假装提交的哈希值
secondcommit5njn2n34n.
好.同时,第二次提交tree中行的ID 由以下公式计算:
algorithms.cpp; 2main.cpp;100644毫无疑问)写入"树"对象;我们现在可以将您的提交图绘制为两个节点:
o <- o <-- master
Run Code Online (Sandbox Code Playgroud)
现在名称master包含secondcommit...哪个是右侧o节点.该提交包含parent一行包含ID firstcommit...,因此ID 指向左侧o节点.第一个提交是根提交 - 没有父母; 是一个死胡同 - 所以当从提交级别查看时,图形停在那里.
但是,从较低(树和文件/"blob")级别查看时,每个提交都有一个树对象,每个树对象都有两个blob对象.为了告诉提交,树和blob,让我们用字母标记它们:
o C1 <- o C2 <-- master
| |
v v
o T1 o T2
|\ /|
| \ / |
| \ / |
v v v
o o o
B2 B1 B3
Run Code Online (Sandbox Code Playgroud)
我把blob B1放在中间,因为它是(未更改的)main.cpp文件.该blob在两个树中都具有相同的哈希值,因此两个树都指向同一个文件.Blob B2和B3这里是两个不同的版本algorithms.cpp.两棵树T1和T2是不同的,因为T1列出B2和B1以名称algorithms.cpp和main.cpp,而T2列出B3和B1的那些相同的两个名字.该名称是相同的,但BLOB的哈希值不同,所以树木不同.(当然parent,C1和C2中的行不同 - C1根本没有parent行,C2有一个列表C1.同样,两tree行,时间戳,可能是提交消息,在两个不同的提交之间都不同. )
此时的实际物理存储库看起来像原始的main.cpp和我最后一次的algorithms.cpp版本
commit.
事实上,它看起来像上面的"完整图形":现在有三个blob对象,两个树对象和两个提交对象.该名称master包含两个提交长线性提交链的提示提交的ID,链本身是Git必须通过读取ID在命令运行时构造的.
最后一次提交有一个名为的别名
HEAD.
是的,有点.实际上,HEAD是一个普通文件(cat .git/HEAD看它),通常包含一个分支的名称:
$ cat .git/HEAD
ref: refs/heads/diff-merge-base
Run Code Online (Sandbox Code Playgroud)
(在这种情况下,我在分支机构diff-merge-base,我创建以保持我的修复git diff).它来自引用本身 - 分支名称 - Git找到(单个)提交的哈希ID.
因此,考虑在给定时间点物理存储库如何在基本提交之上按顺序应用的提交数量的方式.
这是其他版本控制系统工作的数量,甚至大多数.Mercurial是面向图形的(因此没有单独的"基础"版本),但其他方面的确如此.SVN,CVS和RCS共享很多开发历史,除此之外(至少在CVS和RCS中,我没有看过SVN)"trunk"存储反向增量而不是前向增量.除了将文件的整个内容(一旦唯一标识)存储为交错增量之外,SCCS的工作方式与此类似.(Clearcase使用了一个对象数据库,一旦你超越了外部名称到OID的映射,我就不知道它的内部结构是什么样的.)
Git非常非常不同.它存储一个带有名称的树的提交图,其底层对象完全由content-hash-ID存储.每次运行Git命令时,都会动态计算提交,树和文件之间的关系.从一个提交到另一个提交的任何增量也是按需计算的.(包文件确实使用修改后的xdelta压缩形式,但是对象是通过ID压缩其他对象的,而不是针对以前版本的文件的文件.3)
当您进行新提交时,新提交将获得tree(通常来自git write-tree),零个或多个父提交ID,作者和提交者名称和日期戳以及消息.如果您(或Git)然后将新提交的ID写入分支名称,则分支现在指向新提交.这是新提交的工作,指向任何以前的提交,提供任何历史记录.让父母正确,你将有一个明智的承诺历史.选择一些任意的父母,你会得到一个奇怪的历史.
1 Mine与官方版略有不同,因为我有一些错误修复,没有人收回.也许将它们发布到官方邮件列表中是不是将修复程序放入Git的正确方法?
2实际上,当您git add使用文件时,会更早地计算哈希值.通常,Git在将对象添加到存储库数据库的同时计算哈希值.这是最好的时间,因为散列实际上是内容的校验和(包括对象的类型和大小的头),并且Git必须扫描这些内容以便将它们写入压缩(zlib-deflated)"松散的对象"文件.
Git将缓存存储在称为(不同地)"索引","登台区域"或"缓存"的缓存文件中.实际文件.git/index的格式并没有很好的记录.它相当于Git将为下一次提交写入的扁平形式的树,除了对于每个文件名,最多有四个"插槽",其中一次最多使用三个:插槽0保存普通文件哈希,如果合并因冲突而停止,则插槽1-3保留未合并的文件.
该命令git write-tree将索引转换为一个或多个tree对象,并打印出顶级树的ID.这是git commit命令如何知道tree要在新提交的行中放入什么.请注意,在将索引写入树对象之前和之后,索引仍然具有相同的展平树.添加新版本algorithms.cpp替换旧的slot-zero条目algorithms.cpp,但对任何条目都不做任何操作main.cpp.
3增量压缩代码确实使用启发式方法来选择要相互压缩的对象,并使用对象类型,从树对象中提取的文件名以及文件大小和各种使用模式,以对对象进行压缩.因此,Git经常会压缩algorithms.cpp彼此不同的版本.但至少在原则上,它可以压缩blob以防止提交和树木.这很隐蔽,除非你看到"压缩对象"消息,Git正在重新进行压缩以加速网络传输,理论上算子是无限且自由的,而网络有点慢.