Git版本库(实际上就是一个数据库)不仅仅提供版本库中所有文件的完整副本,还提供版本库本身的副本。
Git在每个版本库里维护一组配置值,例如版本库的用户名和email地址,在把一个版本库克隆或者复制到另一个版本库的时候配置设置是不跟着转移的。
Git对象类型:Git放在对象库只有4种类型,块(blob),目录树(tree),提交(commit)和标签(tag)。
blob:文件的每一个版本表示为一个块,一个blob保存一个文件的数据,但是不包含任何关于这个文件的元数据,甚至连文件名都没有。(也就是说,文件每修改一次就会产生一个blob,同一个文件可以拥有许多blob)
tree:一个目录树对象代表一层目录信息。它记录blob标识符,路径名和一个目录里的所有文件的一些元数据。(也就是说,一个tree目录对象包含一个文件的许多不同的blob,保存blob信息和元数据)
【注】技术上讲,一个树对象只代表版本库中的一个目录层级。它包含该目录下的直接文件和它的所有直接子目录的信息,但不包括所有子目录的完整内容。然而,因为树对象引用所有子目录的树对象,所以对应项目根目录的树对象实际上代表某个时刻的整个项目。
commit:一个提交对象保存版本库中每一次变化的元数据,包括作者,提交者,提交日期和日志消息。每一个提交对象指向一个目录树对象。(也就是说,文件提交一次就会产生一个提交对象,保存提交的信息,并将该次提交指向一个目录树对象中。)
tag:一个标签对象分配一个任意的且人类可以读懂的名字给一个特定对象,通常是一个提交对象。commit ID 很难理解,所以可以通过tag对象来制定。(也就是说,提交一次,产生一个提交ID,就可以对应一个tag对象。)
Git 同时是一个内容追踪系统,首先,Git追踪对象库中的文件内容,而不是文件名和目录名。其次,当文件从一个版本变化到下一个版本的时候,Git的内部数据库有效的存储每个文件的每个版本,而不是它们之间的差异。
.git/objects文件夹中存放这个Git版本库中的对象(包括blob,tree,commit...),用它的SHA1散列值作为文件名,例如,b3bf55b0c96720f676d55b2ceb0e3c0267a1c16e。可以通过git cat-file -p b3bf55b0c96720f676d55b2ceb0e3c0267a1c16e来展现对象中的内容。可以通过git rev-parse b3bf55b0c来获得对象的全名。
Git 通过目录树tree的对象来跟踪文件的路径名。 通过git write-tree 来捕获index当前的快照的对象,得到的对象即目录树对象。例如d0edd8a129da644ca97a5d30082db1a684779342,通过git cat-file -p d0edd8a129da644ca97a5d30082db1a684779342 可以获得该文件名和最新blob对象(当前最新的状态文件)的关联,例如,100644 blob 18d9d082662e7e2ae6bcebb683cf494b8c10c2cb first_git.txt。也可以通过 git ls-files -s 命令很容易的看到树对象的内容。
树层次结构:例子:
PERVIOUS VERSION:
hello (dir)
| (树对象:d0edd8a129da644ca97a5d30082db1a684779342)
hello.txt
CURRENT VERSION:
hello(dir)
| \ (树对象:49241326..)
hello.txt subdir(dir)
| (树对象:d0edd8a129da644ca97a5d30082db1a684779342)
hello.txt(内容一样)
git cat-file -p 4924132693
100644 blob 18d9d082662e7e2ae6bcebb683cf494b8c10c2cb hello.txt
040000 tree d0edd8a129da644ca97a5d30082db1a684779342 subdir
因为subdir与PERVIOUS VERSION的内容完全一样,所以树对象SHA1也是一样的,印证了Git追踪对象库中的文件内容,而不是文件名和目录名。因为hello.txt内容也没有变,当然blob的SHA1也不会改变。
提交
提交对象指向一个目录树对象,通过git commit-tree 49241326..提交一个tree对象,得到一个commit对象SHA1。
标签
通过 git tag -m "Tag info" [Tag_name] [commit_SHA1] 添加标签
Git 的索引不包含任何文件内容, 它仅仅追踪你想要提交的那些内容。当执行git commit命令的时候,Git 会通过检查索引而不是工作目录来找到提交的内容。任何时候,都可以通过git status命令来查询索引的状态,它会明确展现出哪些文件Git是暂存的。通过 git ls-files -s 可以看到Git库顶级树下所有的文件。
Git库内容变更分三个阶段:(1)在Git库的工作目录中修改(2)暂存 Stage (3)提交变更
(1)中可以通过 git diff 显示仍留在工作目录中但是还没有暂存的变更。(2)通过git add 将工作目录的变更添加的cache中,通过 git diff --cached 显示已经暂存并且等待提交的变更。(3)提交后,cache中为空,可以通过 git diff [commit_SHA1] \[commit_SHA1] 来查看变更。
可以通过添加一个.gitignore 文件来忽略那些不需要被追踪的文件。
.gitignore 定义规则:
(1)空行会被忽略。
(2)#开头的行用于注释。
(3)一个简单文件名将匹配任何目录中的同名文件。
(4)目录名以/ 结尾将匹配同名的目录和子目录,但不匹配文件。
(5)* 通配符匹配,但是不能跨目录匹配。
(6)!开头表示取反。
例如:
root dir cat .gitignore : *.out
root dir/subdir cat .gitignore: !driver. out
意思是除了 subdir/drvier.out 文件,其它的.out 文件都被忽略。
同一文件,当修改了文件的内容,修改前的SHA1和修改后的SHA1不一样,因为Git追踪的是内容,而非文件名和目录名。如果Git 库中已经追踪了的文件(也就是被git add),修改了这些文件后,即使没有再次git add,通过 git commit -a,Git也会递归遍历整个版本库,自动暂存(git add)它们,然后提交。而那些全新的没有在版本库中被追踪的文件或者目录,执行git commit -a也不会提交或追踪它们。
通过 git rm [file_name] 可以将文件从索引和工作目录中都删除,而 git rm --cached [file_name]会删除索引中的文件而把它保留在工作目录中。
通过 git log --following [file_name]可以实现显示指定的文件名的相关日志。
通过 git log -l --pretty=oneline HEAD可以显示到目前为止版本库中的所有提交ID,HEAD 始终指向当前分支的最近提交,当切换分支时,HEAD会更新为指向新分支的最近提交。通过 git show-branch --more=10 可以查看最近10次提交。^ 代表当前提交的不同的父提交,~ 代表当前提交的父辈提交。
通过 git log master 将以当前最近的提交开始回溯到最原始的提交,通过 git log [commit_id] 将从指定的提交开始回溯到最原始的提交。通过 git log --pretty=short master~12..master~10 可以显示回溯第10次和第11次的提交。--pretty=oneline, shore, full --abbrev-commit :简单的请求缩写SHA1 ID。 --stat:列举提交中所更改的文件以及每个更改的文件有多少行做了改变。
通过 git show HEAD~2 显示当前提交信息,并且显示版本库中文件的改变。
关于提交
提交图:有向无环图(DAG),时间轴是从左到右的,左边的提交是右边提交的父提交。通过 gitk命令可以查看该版本库的DAG图。对于合并提交(merge commit)拥有多个父提交。
识别一个文件的特定提交信息工具:git blame -L 1, [file_name] (1代表从第一行开始到最后一行)