我是否正确的基本git命令如何?

时间:2016-07-09 23:28:25

标签: git svn github version-control

我试图理解git,我想知道你是否可以在下面的例子中纠正我的错误点。

作为一个简单的例子,让我们说我初始化一个包含以下2个简单文件的存储库。

的main.cpp

#include <stdio.h>

int main()
{
  printf("Hello world\n");
  return 0;
}

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;
   }
}

当我在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;
   }
}

并制作另一个commitcommit的哈希值仅来自 algorithms.cpp 。文件 main.cpp 根本不涉及commit。 (当我们通过说出你的存储库的快照时,我相信当人们教git时会产生误导。更准确地说,commit是一个快照只有你改变的文件。)让我们假设提交的哈希是secondcommit5njn2n34n

此时的实际物理存储库看起来像我上一次commit中的原始 main.cpp algorithms.cpp 的版本。最后一个commit有一个名为HEAD的别名。因此,考虑在给定时间点物理存储库是如何在基本提交之上按顺序应用的一些提交的方式

我们说我做了另一个改变

的main.cpp

    #include <stdio.h>

    int main()
    {
      printf("Hello, world!\n");
      return 0;
    }

然后commit。第3个commit包含上述文件的快照。让我们假装它的哈希是thirdcommit9123b1nb31n

现在我的提交历史记录似乎是

firstcommit5njn2n34n a.k.a。base

----&GT; secondcommit5njn2n34n

----&GT; thirdcommit9123b1nb31n a.k.a。HEAD

假设我现在运行git checkout secondcommit5njn2n34n。在引擎盖下,git运行一些逻辑,如

  

&#34;好的,我拿了他添加的那两个原始文件,main.cpp和   algorithms.cpp,然后我用该版本替换algorithms.cpp   在提交secondcommit5njn2n34n。&#34;

现在我的工作目录看起来像

的main.cpp

#include <stdio.h>

int main()
{
  printf("Hello world\n");
  return 0;
}

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;
   }
}

HEAD仍为thirdcommit9123b1nb31n。那是对的吗?如果我现在更改了一个文件又创建了另一个commit,会发生什么?这是不允许的,还是会让commit历史记录中有两条路径?

2 个答案:

答案 0 :(得分:6)

  

当我在添加所有文件后进行第一次提交时,2个文件的内容存储在git中,我得到一个根据2个文件的内容计算的哈希值(加上一些元数据,例如它们的位置)目录树等等)。该散列仅仅是一个标识符,但也可以用于检查不同的提交是否包括相同的文件集(假设散列中没有冲突)。我们假设哈希是firstcommit5njn2n34n

是的,尽管将此哈希视为&#34;真实姓名&#34;会更好。一个(或&#34;&#34;,在这种情况下)提交。 (在SVN术语中,它与-r数字相似。)

  

现在我已经进行了第一次提交,它是所有后续提交的基础,除非我在某个时候进行了修改。

否:这里Git和SVN已经分道扬..第一次提交是当前提交,并且为了绘制所有提交的图表,你可以绘制一个图形,但是现在这个图形很无聊,因为它有一个没有边的节点(一个顶点),并且一个标签指向一个节点:

o   <-- master

标签为master,它指向图中一次提交的(包含哈希ID)。

  

...再做一次提交。该提交的哈希值仅来自 algorithms.cpp

不,这是错误的。

新哈希是新提交的校验和。 new 提交包含五个项目:四个标题项和提交消息。让我们来看看两个实际的提交,以及一个树的具体情况。我没有你的存储库,所以在这里我将使用我的Git存储库副本。 1 我们可以使用git cat-file来查看存储库中存储的任何对象。使用-p,它将会打印出来#34;它们是以文本形式出现的(事实上,大多数已经是文本,尽管树不是;不是这对使用它们很重要)。这是我目前的提交:

$ 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]

特别注意treeparent行。因此,这五个项目是:

  1. 树;
  2. 父母;
  3. 作者;
  4. 提交者;
  5. 提交消息。
  6. 将此与您在查看&#34; root&#34;时所看到的内容进行比较(无父)提交,例如这个(git rev-list HEAD | tail -1找到的ID):

    $ 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
    

    现在只有四个项目:没有parent行。 (如果我们查看一些合并提交,我们会发现它们至少有两个 parent行,每个父提交节点一行。)

    此存储库中的tree(对于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]
    

    现在让我们回到您的存储库,因为它更简单。

      

    让我们假装提交的哈希是secondcommit5njn2n34n

    行。同时, second 提交中tree行的ID由以下公式计算:

    • 散列algorithms.cpp; 2
    • 的(新)内容
    • 散列main.cpp;
    • 的内容(与之前相同)
    • 将这些哈希值以及两个文件名和文件模式(毫无疑问100644)写入&#34;树&#34;对象;
    • 计算该树对象的哈希。

    我们现在可以将您的提交图绘制为两个节点:

    o <- o   <-- master
    

    名称master现在包含secondcommit...,它是右侧o节点。该提交包含parent行,其中包含ID firstcommit...,因此指向左侧o节点。第一个提交是根提交 - 没有父母;是一个死胡同 - 所以当从提交级别查看时,图表就会停止。

    从较低层(树和文件/&#34; blob&#34;)级别查看时,每个提交都有一个对象,每个对象都有两个 blob 对象。为了区分提交,树和blob,让我们用字母标记它们:

    o C1  <-  o C2           <-- master
    |         |
    v         v
    o T1      o T2
    |\       /|
    | \     / |
    |  \   /  |
    v    v    v
    o    o    o
    B2   B1   B3
    

    我在这里放了blob B1 ,因为它是(未更改的)main.cpp文件。该blob在两个树中都具有相同的哈希值,因此两个树都指向同一个文件。 Blob B2 B3 这里是algorithms.cpp的两个不同版本。两棵树 T1 T2 不同,因为 T1 列出 B2 B1 名称algorithms.cppmain.cpp,而 T2 在相同的两个名称下列出 B3 B1 名称是相同的,但blob哈希值不同,因此树木不同。 (当然 C1 C2 中的parent行不同 - C1 根本没有parent C2 有一个列表 C1 。同样,两个tree行,时间戳和大概提交消息都在两个不同的提交之间不同。 )

      

    此时的实际物理存储库看起来像我上一次commit中的原始 main.cpp algorithms.cpp 的版本。< / p>

    事实上,它看起来像上面的&#34;完整的图形&#34;:现在有三个blob对象,两个树对象和两个提交对象。名称master包含两个提交长线性提交链的提示提交的ID,链本身是Git必须通过读取ID在命令运行时构造的。

      

    最后一次提交有一个名为HEAD的别名。

    是的,有点儿。实际上,HEAD是一个普通文件(cat .git/HEAD可以看到它),通常包含分支的名称

    $ cat .git/HEAD
    ref: refs/heads/diff-merge-base
    

    (在这种情况下,我在分支diff-merge-base上,我创建该分支以保存我对git diff的修复。它来自引用本身 - 分支名称 - Git找到(单个)提交的哈希ID。

      

    所以,考虑在给定时间点物理存储库是如何在基本提交之上按顺序应用的一些提交的方式。

    这是其他版本控制系统工作的数量,甚至大多数。 Mercurial是面向图形的(因此没有单一的区分&#34; base&#34;版本),但在其他方面的工作方式非常类似。 SVN,CVS和RCS共享很多开发历史,除此之外(至少在CVS和RCS中,我没有看过SVN)&#34; trunk&#34;存储反向三角洲而不是前向三角洲。 SCCS的工作方式与此类似,只是文件的整个内容(一旦唯一标识)存储为<​​em> interleaved 增量。 (Clearcase使用了一个对象数据库,一旦你超越了外部名称到OID的映射,我就不知道它的内部结构是什么了。)

    Git非常非常不同。它存储一个带有名称的树的提交图,其底层对象完全由content-hash-ID存储。每次运行Git命令时,都会动态计算提交,树和文件之间的关系。从一个提交到另一个提交的任何增量也是按需计算的。 (包文件确实使用了xdelta压缩的修改形式,但是对象是通过ID压缩其他对象的,而不是针对以前版本的文件的文件。 3

    当您进行新提交时,新提交将获得tree(通常来自git write-tree),零个或多个父提交ID,作者和提交者名称和日期戳,以及信息。如果您(或Git)然后将新提交的ID写入分支名称,则分支现在指向新提交。新提交的工作是指向任何先前的提交,提供任何历史记录。让父母正确,你将有一个明智的承诺历史。选择一些任意的父母,你会得到一个奇怪的历史。

    1 我的官方略有不同,因为我有一些错误修复,没有人收回。也许将它们发布到官方邮件列表不是将修复程序放入Git的正确方法吗?

    2 实际上,当你git add文件时,哈希是先前计算的。通常,Git在将对象添加到存储库数据库的同时计算哈希值。这是最好的时间,因为散列实际上是内容的校验和(包括对象的类型和大小的头),并且Git必须扫描这些内容以便将它们写入压缩(zlib) -deflated)&#34;松散的物体&#34;文件。

    Git将缓存存储在缓存文件中(不同地)&#34;索引&#34;,&#34;登台区&#34;或&#34;缓存&#34;。实际文件为.git/index,其格式记录不完整。它相当于Git将为下一次提交写入的平面形式的树,除了对于每个文件名,最多有四个&#34;插槽&#34;,其中一次最多使用三个#34; :slot 0保存正常文件哈希,如果合并因冲突而停止,则slot 1-3保留未合并文件。

    命令git write-tree将索引转换为一个或多个tree对象,并打印出顶级树的ID。这就是git commit命令知道在新提交的tree行中放置什么的方式。请注意,索引在将它们写入树对象之前和之后仍然具有相同的扁平树。添加algorithms.cpp的新版本会替换algorithms.cpp的旧版插槽条目,但对main.cpp的任何条目都不执行任何操作。

    3 delta压缩代码确实使用启发式方法来选择要相互压缩的对象,并且那些确实使用对象类型,从树对象中提取的文件名,以及文件大小和各种使用模式,将对象分组以进行压缩。因此,Git经常会相互压缩不同版本的algorithms.cpp。但至少 ,它可以压缩提交和树木的blob。这很隐蔽,除非你看到&#34;压缩对象&#34;消息,Git正在重新进行压缩以加速网络传输,理论上算子是无限且自由的,而网络有点慢。

答案 1 :(得分:0)

  

HEAD仍然是thirdcommit9123b1nb31n。这是对的吗?

是的,HEAD总是指向给定分支上的最新提交。

  

如果我现在更改文件并再次提交,会发生什么?这是不允许的,还是会让提交历史中有两条路径?

这不是推荐的东西。当您签出这样的特定哈希时,您最终会进入detached HEAD模式。您可以提交任何您喜欢的内容,但这些提交与任何分支都没有关联 - 所以除非您记住哈希,否则您将无法将其恢复。

您通常会在您感兴趣的提交中执行branch,然后切换到该分支:

git checkout -b mybranch secondcommit5njn2n34n

现在你正处于一个新的分支,谁的root是第二次提交。您可以再次提交,并且您将基本上与原始master分支并行运行。这个新分支有它自己的HEAD。

最终您可能希望将这些更改合并回master,因此您可以使用:

git checkout master
git merge mybranch 

More info on branching & merging