基础
- 参见另一篇Git基础
一、数据结构
- Git的底层存储时以文件快照实现的,对版本的存储是通过文件快照整体进行存储,重复的文件通过指针索引源文件,不重复存储;而SVN/CVS存储的是各版本之间的差异。
文件快照(snapshot):概念上的复制+物理上的引用
- git会把出现变更的文件直接拷贝(但不是复制粘贴这么简单,git会处理、压缩),形成新的blob,而不是与上一个版本的差异diff;(切换速度快, 其他版本控制需要做merge,空间换时间)
(快照的作用就相当于将旧文件所占的空间保留下来,并且保存一个引用,而新文件中会继续使用与旧文件内容相同部分的磁盘空间,不同部分则写入新的磁盘空间。总的来说git其实也算是保存diff的方式,只不过是在文件系统层次上实现的。) - 如果没有改变,快照其实是链接上一个版本。
- git gc是一个打包指令,打包之前变动的文件,会保存当前最新版本的整个文件,之前的版本只保存diff(因为最新版本用得多)。这个指令可以主动触发,也可以在git push,或者变动文件过多的时候自动触发。通过git的底层指令能够还原出打包之前的信息。
- git会把出现变更的文件直接拷贝(但不是复制粘贴这么简单,git会处理、压缩),形成新的blob,而不是与上一个版本的差异diff;(切换速度快, 其他版本控制需要做merge,空间换时间)
git object
-
Git是一个内容寻址文件系统,Git的核心部分是一个键值对数据库;向 Git 仓库中插入任意类型的内容,它会返回一个唯一的键,通过该键可以在任意时刻再次取回该内容。
-
.git/object
目录即对象数据库。右键打开Git Bash:
初始化一个版本库,Git会对object目录初始化,创建pack,info子目录,均为空。
1. blob对象
echo "contents" > a.txt
会在工作目录创建a.txt,写入contents。即在数据结构中创建blob对象节点。
echo "222" | git hash-object -w --stdin
会返回存储在Git库中的唯一键。(-w 选项会指示该命令不要只返回键,还要将该对象写入数据库中。)
git会根据文件内容生成校验和(SHA-1哈希值
,40位),以校验和为索引。
将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和。
SHA-1算法(Secure Hash Algorithm 1):一种密码散列函数。
git cat-file -t 前六位哈希
可以让 Git 告诉我们其内部存储的任何对象类型,只要给定该对象的 SHA-1 值。
git cat-file -p 前六位哈希
查看文件内容
blob数据对象中,并没有保存文件名,仅保存了文件内容。
为什么要把文件的权限和文件名储存在Tree object 里面而不是Blob object呢?
节省空间,因为相同的文件内容在Git仓库里面只有一个Blob Object, 不管有多少个指针指向他。
2. tree对象
- tree object可以解决文件名保存的问题,一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息。
- 通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记录一个对应的树对象, 如此重复便可依次记录(某个时间段内)一系列的树对象。
3. commit对象
- commit对象存储的是提交信息,可以保存作者信息,快照时间,已经为什么保存快照等信息。
commit对象内容
每次commit的时候,都生成一个commit的对象。它先指定一个顶层树对象,代表当前项目快照; 然后是可能存在的父提交(前面描述的提交对象并不存在任何父提交); 之后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个时间戳); 留空一行,最后是提交注释。
tree对象保存当前暂存区所有文件的blob对象,每个blob对象都通过SHA1生成一个索引作为key,指向该文件的压缩(保存整个文件)。
也可以通过git命令创建blob,tree,commit对象,建立他们的指向关系,不需要借助上层命令(git add/commit),同样可以提交文件。
总结,blob类型只是存储文件的内容,不包括文件名等其他信息。将这些信息经过SHA1哈希算法得到对应的哈希值,作为这个object在Git仓库的唯一标识。tree对象
二、执行命令时的结构变化
新建文件后,Git会创建blob object:
git add
- 将新添加的文件或者改动放入Git的暂存区(Index)。
git commit
git commit -am '[+] init'
后,会多出来两个object。- tree object,从它存储的内容来看可以发现它存储了一个目录结构(类型文件夹),以及每一个文件(或者子文件夹)的权限、类型、对应身份证(SHA1值)以及文件名。
- 提交后会生成commit object,存储提交信息,包括对应目录结构的快照tree的哈希值,上一个提交的哈希值,提交的作者以及提交的具体时间,该提交的信息。
- 此时Git仓库是这样的:
- 在Git仓库中,HEAD、分支、普通的Tag可以理解成一个指针,指向对应commit的SHA1值。
HEAD:当前分支版本顶端的别名,也就是在当前分支最近的一个提交
本质上是一个key-value的数据库加上默克尔树形成的有向无环图(DAG)。
整体的仓库状态:
修改本地文件
- 将本地文件a.txt由111改为333。
git add a.txt
,在git仓库中新建了一个blob object,并且更新索引指向新建的blob object。
git commit -m 'update'
提交修改,Git首先根据当前索引生成一个tree object。然后创建新的commit object,将这次的提交信息存储,父提交只想上一个commit,组成一条链记录变更历史。最后将master分支的指针移到新的commit。
常用命令及结构变化
-
git checkout
:移动HEAD指针git checkout -b dev
:创建分支dev并切换到这个分支(HEAD指向这个分支)
git checkout branchname
:切换到某个分支(HEAD指针指向该分支的commit节点,索引指向也相应更新,然后由索引内容更新到本地工作目录)。
-
git reset:回滚,会移动HEAD和master。
git reset --soft HEAD~
: 将Git仓库的提交回滚到前一个(移动HEAD和master),暂存区和工作目录不变;HEAD~是前一个,HEAD~2是第二个祖先提交…HEAD~n;
git reset HEAD~
:等价于git reset --mixed HEAD~
默认选项,暂存区和Git仓库回滚到前一个提交,工作目录不变。(将你当前的改动从缓存区中移除,但是这些改动还留在工作目录中。)
git reset --hard HEAD~
:工作目录也回到指定的提交。
-
checkout和reset区别:
-
git revert
:撤销一个提交的同时会创建一个新的提交。这是一个安全的方法,因为它不会重写提交历史。比如,git revert HEAD~2
会找出倒数第二个提交,然后创建一个新的提交来撤销这些更改,然后把这个提交加入项目中。
其他命令
git status
git log
git cat-file -t xxxxxx
git cat-file -p xxxxxx
:查看二进制压缩
head指针的变化
三、分支合并
- 三向合并
- 合并策略:fast-forward、recursive递归、ours&therirs、octopus
四、实用技巧
-
误删分支:
git rebase 节点哈希值
() -
获得干净的工作目录:
git stash push [-u]
,能用git stash pop恢复
。git reset --hard HEAD,git checkout -f都会丢失文件。
-
git log
:可以显示所有提交过的版本信息 -
git reflog
:可以查看所有分支的所有操作记录(包括已经被删除的 commit 记录和 reset 的操作)