git plumbing 更加底层命令解析-深刻理解GIT

原文: http://rypress.com/tutorials/git/plumbinghtml

本文详细介绍GIT Plumbing--更加底层的git命令,你将会对git在内部是如何管理和呈现一个项目repo有一个深刻的理解。git

除非你想通读Git源代码,你可能永远没有必要使用下面的命令。可是经过手工的操做一个repo将会让你对于GIT如何保存数据的概念细节有个深刻理解,你也将对于git是如何工做的有更好的理解。数据库

咱们首先来检阅Git的object database,而后咱们使用git的低级命令手工建立和commit一个snapshot安全

Examine Commit Details

首先咱们经过git cat-file plumbing command来查看最近的一个commit:编辑器

git cat-file commit HEAD

commit参数告诉git咱们须要查看一个commit对象。正如咱们所知,HEAD指向最近的commit.这将输出下面的信息:工具

tree 552acd444696ccb1c3afe68a55ae8b20ece2b0e6
parent 6a1d380780a83ef5f49523777c5e8d801b7b9ba2
author Ryan <ryan.example@rypress.com> 1326496982 -0600
committer Ryan <ryan.example@rypress.com> 1326496982 -0600

Add .gitignore file

这是表明一个commit的完整信息:一个tree,一个parent,用户数据和一个commit message.用户信息和commit消息是很是容易理解的,但咱们历来没有见过tree或者parent.spa

一个tree object是git对于一个"snapshot"的表明。他们保存了一个目录在特定时刻的状态,该object没有任何关于时间或者做者的信息。为了将tree和项目一向的历史信息关联起来,GIT将每个tree对象包装在一个commit对象中,而且指定一个parent,而这个parent实际上就是另一个commit.经过遍历每个commit的parent,你就能够遍历完项目的整个历史。3d

注意每个commit refers to one and only one tree object,也就是说commit和tree是一一对应的。从git cat-file输出的内容看,咱们可使用SHA checksum来表明那个tree.这个SHA CHECKSUM对于GIT内部每个变量都是适用的。code

Examine a Tree

下面咱们使用git cat-file命令来检阅一个TREE对象。注意将相关的id更新为你的tree的id:htm

git cat-file tree 552acd4

不幸的是上述命令输出的是binary数据,没法阅读。你能够经过使用下面的git ls-tree命令来输出可阅读的内容。

git ls-tree 552acd4

该命令将输出目录的列表:

100644 blob 99ed0d431c5a19f147da3c4cb8421b5566600449    .gitignore
040000 tree ab4947cb27ef8731f7a54660655afaedaf45444d    about
100644 blob cefb5a651557e135666af4c07c7f2ab4b8124bd7    blue.html
100644 blob cb01ae23932fd9704fdc5e077bc3c1184e1af6b9    green.html
100644 blob e993e5fa85a436b2bb05b6a8018e81f8e8864a24    index.html
100644 blob 2a6deedee35cc59a83b1d978b0b8b7963e8298e9    news-1.html
100644 blob 0171687fc1b23aa56c24c54168cdebaefecf7d71    news-2.html
...

经过检查上面命令的输出内容,咱们能够假定"blobs"表明了咱们repo里面的文件,而trees表明了repo里面的folders。继续经过git ls-tree检阅about tree,咱们就能够看到是否是咱们的假定是正确的了。

因此,blob对象实际上就是git保存咱们文件内容的对象,能够简单理解为文件,tree对象组合了blob和其余的tree对象造成了目录列表,也就是说tree能够简单理解为目录。这些对象就是git最终造成咱们在git中经常使用命令所操做的惟一对象。commit,tree,blob之间的关系能够用下面的图形象展现:

Examine a Blob

咱们来看看和blue.html文件相对应的blob:

git cat-file blob cefb5a6

这会展现blue.html文件的整个内容,这也印证了blob自己就是存数据文件,blob自己是纯粹的content,它自己甚至没有任何关于文件名称的信息。也就是说文件名blue.html是保存在包含blob的tree对象中,而不是在blob对象中。

你可能知道SHA-1 checksum确保一个对象的内容永远不会在Git不知情的状况下被篡改。checksum的原理是经过使用对象内容来计算一个惟一的字符序列。这不只做为一个id,并且它保证一个对象永远不会悄无声息地被修改(而git居然不知情).当咱们来谈到blob对象时,这又有另一个好处。既然两个具备相同内容的blob永远具备相同的checksum id,那么git能够跨tree来共享一个blob对象。好比咱们的blue.html文件自从被建立后就没有被修改,那么咱们的repo将只有一个相关联的blob,而全部后续的tree对象都将引用它。经过不去建立重复的blob,Git能够大大下降repo的尺寸。有这个理念做为基础,咱们能够修正git的对象图以下:

然而,只要你更改文件的任何一行,Git都必需建立一个新的blob对象来反映这个修改,由于内容变动,SHA-1 checksum就将变动。固然,GIT也有一些增量修改的机制来保证这个尺寸增长不是很大的问题。

Examine a Tag

第四个也是最后一个git object是tag对象.咱们可使用git cat-file命令来显示tag的detail存储信息:

git cat-file tag v2.0

上面的命令将输出和v2.0这个tag相关的commitID,以及tag的名称,做者,建立时间和附加信息。下面更新最后版本的git对象图:

Inspect Git’s Branch Representation

咱们如今有了可以彻底浏览git branch representation的全部工具,使用-t选项,咱们能够得知git对branch使用哪一种对象来表示:

git cat-file -t master
将输出commit
git cat-file commit master
将输出和git cat-file commit HEAD彻底同样的信息

一个branch就是一个对一个commit对象的引用,这意味着咱们能够经过git cat-file commit master来查看master branch的详细信息。master branch和HEAD都是对一个commit对象的简单引用。

咱们打开.git/refs/heads/master文件,你能够看到该文件内容实际上就是最近的一个commit的checksumID(你能够经过git log -n 1来检查这个commitid)。这个文件就是git为了维护master分支所需的一切,全部其余的信息都是经过这个commit object根据上面讨论的关系图来外推得出的!(branch自己是否就等于branch tip呢??)

另外一方面,HEAD reference自己记录在.git/HEAD文件中,不像branch tips老是指向一个branch的顶端commit,

HEAD并非任何一个commit的直接连接。反而,HEAD老是引用一个branch,GIT使用这个branch来指出当前checkout出来的是哪个commit。记住一个detached HEAD state是在当HEAD再也不和branch的tip相一致时发生的!从机理上说,这意味着.git/HEAD并无一个Local branch。试着checkout一个老的commit:

git checkout HEAD~1

如今,.git/HEAD就包含一个commitID,而再也不是一个branch。这告诉git咱们进入了一个dtached HEAD state。不管你在什么状态,git checkout命令将老是将checkedout commit的引用记录在.git/HEAD中。咱们继续git checkout master,返回master branch,来接着作一些其余的实验:

Explore the Object Database

当咱们有一个git对象操做的基本理解,咱们能够看看git将这些对象放在哪里了。在咱们的my-git-repo库,打开文件夹.git/objects,那就是git的数据库!

每个对象,不管对象的类型是什么,都被保存为一个文件,使用他的SHA-1 checksum做为文件名。可是,和传统将全部objects存放在一个文件夹的作法不一样的是:他们经过将他们的ID前两个字符剔出来做为一个目录名,而id剩下的字符做为文件名。具体能够看看下面的objects输出目录格式:

00  10  28  33  3e  51  5c  6e  77  85  95  f7
01  11  29  34  3f  52  5e  6f  79  86  96  f8
02  16  2a  35  41  53  63  70  7a  87  98  f9
03  1c  2b  36  42  54  64  71  7c  88  99  fa
0c  26  30  3c  4e  5a  6a  75  83  91  a0  info
0e  27  31  3d  50  5b  6b  76  84  93  a2  pack

 

好比,一个具备以下ID的object,

7a52bb857229f89bffa74134ee3de48e5e146105

将会被保存在7a 文件架中,而剩下的(52bb8...)做为文件名,也就是说7a目录+52bb8...文件的组合来惟一标识该object

这样作的好处是检索迅速。知道了这一层,那么咱们就能够从新组合出来咱们的objectID,以便方便地查阅它的内容,git cat-file -t 7a52bb8 ,

   gitcat-fileblob7a52bb8

:该命令就是从新组合咱们的object,而且若是发现它是blob对象,咱们就将获得其内容,若是是tree对象,则用lstree命令

Collect the Garbage

随着repo的增加,GIT可能自动将你的object文件转换成一种被成为"pack"的压缩文件。你可使用garbage collection的命令来强制运行该压缩过程。可是要清楚:这个命令是不可回退的。若是你但愿继续explore .git/objects文件夹的内容,你就应该在执行下面的命令以前进行。

git gc

上面这条命令将会压缩各个object files造成更快更小的pack file而且删除一些再也不引用的commit(好比frrom a deleted, unmerged branch)。

固然,全部相同objectID的都将可用git cat-file来访问。该git gc命令只是更改git的存储机制--而不是更改repo的内容。

Add Files to the Index

到如今,咱们已经讨论过git的low-level representation of commited snapshots.这篇文章中,下面咱们将各个零件转动运转起来,使用更多的"plumbing"命令来手工准备和提交一个新的snapshot。这将完全解密GIT是如何管理working directory和staging area的。

在my-git-repo中建立一个新的文件,命名为news-4.html,而且增长一些html代码;而后修改Index.html文件的news section以便生成一个指向news-4.html的连接。

这时,正常状况下,咱们将git add, git commit提交咱们的变动。如今咱们来试图使用更加低级的命令来手工完成这个操做。index是git的术语,表明了staged snapshot。

git status
git update-index index.html
git update-index news-4.html

后面的命令将会抛出一个错误,由于git在你不明显告诉他news-4.html是一个新文件时,他是不容许加入他不知道的文件到stage area的。相反地,咱们少作修改,增长--add参数:

git update-index --add news-4.html
git status

咱们经过上面的update-index命令将咱们的working directory搬到了Index区,这意味着咱们已经有了一个准备好的snapshot了,剩下来要作的事情就是commit了。

Store the Index in the Database

记住:全部的commits都引用到一个tree object,而这个tree object就表明了那个commit的snapshot。因此,在建立一个commit对象以前,咱们须要将咱们的Index(staged tree)放到git的object database中。咱们能够经过下面的命令来达到目的:

git write-tree

这个命令从index建立一个tree object而且保存在.git/objects目录中。它将输出这个命令结果的tree的checksumid:

5f44809ed995e5b861acf309022ab814ceaaafd6

你能够经过git ls-tree来检查你的新创的snapshot。记住这个commit中惟一新创的blob是index.html和news-4.html,这个tree的剩余内容引用了已经存在的blobs

  gitls-tree5f44809

因此,咱们已经有了咱们的tree object了,可是咱们必需将他放到咱们项目的历史中去。

Create a Commit Object

为了commit the new tree object,咱们能够手工地找到parent commit的ID:

git log --oneline -n 1

这条命令将输出下面的内容,咱们将使用这个commitID来指定新的commit对象的parent

 Add .gitignore file3329762

git commit-tree命令建立一个commit object根据传入的tree和parentID参数,可是author信息倒是从git的一个环境变量来读取的。

git commit-tree 5f44809 -p 3329762

这条命令将须要更多的输入: commit message.就像咱们在作commit时要求输入commit message同样操做就能够了。

该命令最终输出

c51dc1b3515f9f8e80536aa7acb3d17d0400b0b5

如今你就能够在.git/objects目录下查看到这个commit了,可是不管是HEAD或者是branches都没有被自动更新而包含这个commit. 如今这就是一个dangling commit。好消息是,咱们知道git在哪里保存branch信息的:

Update HEAD

既然咱们并非在一个detached HEAD state, HEAD就是一个对一个branch的引用。因此,咱们要更新HEAD须要作的就是移动master branch,向前指向到咱们的最新的commit object.这个工做能够经过直接在文本编辑器中修改.git/refs/heads/master为咱们commit-tree命令的输出(commit objectid)来完成。

若是这个文件根本不存在,也不用烦恼,这仅仅意味着git gc命令packed up all of our branch references into single file.在这种状况下,咱们没法来更新。git/refs/heads/master,可是咱们应该打开.git/packed-refs,找到独一refs/heads/master的引用的那行,修改便可。

既然咱们的master branch指向了新的ecommit,咱们应该能够在项目历史中看到news-4.html文件了。

git log -n 2

上面的章节咱们解释了当咱们执行git commit -a -m "some message"时全部发送在背后的真实事情。你是否以为仍是不用超低级命令的好呢?

 commited, modified, staged

注意:在Git中,你的文件将有三个可能的状态存在:commited, modified, staged. Commited意味着数据已经安全地存储到了你的local database. Modified意味着你已经修改了这个文件可是尚未放到数据库中。staged意味着你已经标示了modified files,以便做为下一个commit的snapshot。这也致使了一个Git项目具备三个不一样的section: Git directory, working directory, staging area.

Git directory是Git用于保存其metadata和objects的地方所在。这个目录是git最重要的部分,这个也是当你clone一个repo时copy的部分。working directory是你的项目的一个版本的checkout.这些文件是从git directory的compress database中抽取出来的,而且将这些内容放到磁盘中供你来修改和使用。

staging area就是一个文件,一般就放在.git目录中,这个文件保存了关于下一个commit的全部信息。有时咱们又称之为index,可是更多的状况下,人们称之为staging area. 基本的GIT workflow像下面这个样子:

1.你在working directory中修改文件;

2.你stage这些修改,adding snapshots of them to your staging area;

3.你作一个commit,这个动做将把stage区中的snapshot永远存储于git directory的repo数据库中。

若是一个文件在Git directory中存在了,那么就被认为被commited了。若是文件modified,而且已经放到staging area了,那么成为it is staged.若是自从该文件被checkout出来后作了修改,可是却尚未staged,那么他就是modified状态。

.git/index这个文件实际上就是staging area,它会实时记录你准备放到下一个commit的snapshot,咱们能够用ls-files来检查他的内容(他是binary文件)

git ls-files --stage

git ls-files -stage //记住:该命令实际上查看的是.git/index文件自己
//输入以下内容:
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       anotherfile.txt
100644 303bcbd14ef854ea5ac85a4896e5f37a11dd4394 0       hotfixbranch.txt
100644 41d9b439a7c42e9f65c5382d992a0b158ce43956 0       readme.txt
100644 b64aa37a6bd9fc4f66fe86b9c1d07b821fbf3966 0       third

因为咱们已经修改了readme.txt文件而且放到staging area,那么这时咱们来检视这个文件的话,

git cat-file -t 41d9b4  必定输出blob

D:\gittest>git cat-file blob 41d9
readme.txt first version
second version readme
to check the index area

你能够看到最后一行就是最新的更改,可是上述命令却将整个readme.txt文件都做为blob来输出了@

 tracked and untracked

记住:在你的working directory中任何一个文件都有两种可能的状态:tracked or untracked. tracked文件是那些在最近的snapshot中存在的文件,他们能够被修改,反修改或者staged. Untracked文件是任何你的working directory中没有在你的last snapshot中而且未在staging area中的文件。也就是说只要是曾经执行过git add命令的,都是tracked file。

相关文章
相关标签/搜索