原文: http://rypress.com/tutorials/git/plumbinghtml
本文详细介绍GIT Plumbing--更加底层的git命令,你将会对git在内部是如何管理和呈现一个项目repo有一个深刻的理解。git
除非你想通读Git源代码,你可能永远没有必要使用下面的命令。可是经过手工的操做一个repo将会让你对于GIT如何保存数据的概念细节有个深刻理解,你也将对于git是如何工做的有更好的理解。数据库
咱们首先来检阅Git的object database,而后咱们使用git的低级命令手工建立和commit一个snapshot安全
首先咱们经过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
下面咱们使用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之间的关系能够用下面的图形象展现:
咱们来看看和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也有一些增量修改的机制来保证这个尺寸增长不是很大的问题。
第四个也是最后一个git object是tag对象.咱们可使用git cat-file命令来显示tag的detail存储信息:
git cat-file tag v2.0
上面的命令将输出和v2.0这个tag相关的commitID,以及tag的名称,做者,建立时间和附加信息。下面更新最后版本的git对象图:
咱们如今有了可以彻底浏览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,来接着作一些其余的实验:
当咱们有一个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命令
随着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的内容。
到如今,咱们已经讨论过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了。
记住:全部的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了,可是咱们必需将他放到咱们项目的历史中去。
为了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信息的:
既然咱们并非在一个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"时全部发送在背后的真实事情。你是否以为仍是不用超低级命令的好呢?
注意:在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来输出了@
记住:在你的working directory中任何一个文件都有两种可能的状态:tracked or untracked. tracked文件是那些在最近的snapshot中存在的文件,他们能够被修改,反修改或者staged. Untracked文件是任何你的working directory中没有在你的last snapshot中而且未在staging area中的文件。也就是说只要是曾经执行过git add命令的,都是tracked file。