- 前言
- Git的基本术语
- Git文件存储方式与OS的文件系统
- Git的一次提交
- Git是Commit的世界
- commit的引用方法
- merge和rebase的区别
- Git 中的index
- Git中的reset
- 参考文章
Git是目前最流行的版本管理工具,但是由于其命令比较复杂,有时候无法很顺利的使用,但是,一旦了解Git的底层原理,那么Git的命令也就变得简单起来。本文是Git From the Bottom Up的总结与拓展
前言
Git是目前最流行的版本管理工具,但是由于其命令比较复杂,有时候无法很顺利的使用,但是,一旦了解Git的底层原理,那么Git的命令也就变得简单起来。本文是Git From the Bottom Up的总结与拓展
Git的基本术语
repository
: Git仓库,是commit的集合,仅此而已working tree
: 当前文件系统的工作目录,不包括.gitindex
: 暂存区,即将成为一个新的commit的临时节点commit
: 可以理解为working tree在某个时间点的一个快照(snapshot),当然只记录了增量的文件修改branch
: Git分支,实质是指向某个commit的指针,有一个别名如dev,随着这个分支的提交,这个分支对应指针会指向新的HEADtag
: tag和branch都是指向某个commit的指针,也有一个别名,唯一区别是,tag指向一个commit,指针不会再移动。master
: 提交主要分支,系统建立的第一个分支HEAD
: 重要概念,一个commit指针,用于记录工作区当前checkout的分支状态- 若checkout的是一个branch,那么HEAD指针直接指向该branch
- 若checkout的是一个tag或是一个commit的hash id,那么HEAD处于detatch状态
-
tree
: Git中的的树形结构,类似于目录,tree可以包含多个tree,也可以包含blob,blob是叶子节点,tree是非叶子节点一个典型的工作流的示意图:
Git文件存储方式与OS的文件系统
文件在Git中存储是使用blob的方式存储,与Linux操作系统中的inode号类似,一个文件对应一个inode号,不同的inode号代表不同的文件,如果两个文件共享一个inode号,那么这两个文件是一个硬链接。
在Git中,每个文件是一个blob,使用SHA1算法计算出一个hash id,作为这个文件的唯一标识,在Git中,hash id相同的两个文件内容一定相同。与文件系统不同的是,Git中,相同内容的文件使用同一个blog表示,因为Git中的文件不记录原信息(如文件修改时间等等信息)。
在Linux中,两个文件如果两个文件内容相同,但是创建于不同的时间,那么可以被OS认为是不同的文件,但是在Git中,由于不存储原信息,那么只要两个文件内容相同,那么他们就是同一个文件。有这种区别的原因是,操作系统保存的文件是常常可变的,而Git管理的文件,一旦进入commit集合,那么就是不可变的
Git的一次提交
Git的典型提交如下:
$ mkdir sample; cd sample
$ echo "Hello, world!" > greeting
$ git init
$ git add greeting
$ git commit -m "Added my greeting"
实质上,内部过程是这样的:
- 编辑greeting文件
- greeting被计算为blob,添加进入index
- Git将index的所有文件组织成tree
- 新建一个commit,指向这个tree,这个commit的hashid使用commit的日期,commit的用户计算出来
- 把当前分支(master)的HEAD指向这个commit
所以是head -> commit -> tree -> blob Git中,能计算出hashid的东西只有三种
- blob: 代表单个文件,使用文件内容计算hash
- tree: 包含多个blob组成的树形结构,使用目录内文件的内容计算hash
- commit: 指向一个tree,记录commit的metadata,使用这些内容计算hash
- 提交时间
- 提交人
- 提交的comment
- parent,一个commit可能有多个parent
Git是Commit的世界
Git的世界是由commit组成的一个大图,每个commit是图中一个节点(圆形节点),这个图是一个有向无环图,对于每个节点,可能有一个或多个的祖先,一个或多个的孩子,在Git中,节点只记录它的parent(s):
- 含有多个祖先: 这是一个merge节点
- 含有多个孩子: 这是一个branch节点
每个commit都对应着一个tree,每个tree里面还有tree(三角形节点)或是blob(方形节点)
在理解了Git的Commit的世界以后,不需要再纠结于tag,branch,
Say it with me: A branch is nothing more than a named reference to a commit :分支知识一个commit的引用,其他啥也不是
commit的引用方法
你有一千种方法来引用某个commit~不过最常用的的是这些:
- branchname: 使用分支名来引用commit,引用的是这个分支指向的commit
- tagname:和branchname一样,引用的是这个tag指向的commit
- HEAD:使用HEAD引用的是当前正在使用的commit,也就是使用git commit命令产生新的commit后新commit的父节点
- 93a88bc922dbd…:完整的sha1,代表具体的一个commit
- 93a88b:在本repo中的可以唯一确定的sha1码,只需要前几位即可,表示具体的一个commit
- name^:表示name的父节点,name表示任何合法的commit表示方法,如果一个commit有多个父节点(Merge节点)那么取第一个,比如HEAD^
- name^^:表示name的祖父节点(父亲的父亲)如果是祖父节点的父节点用name^^^表示
- name^2:表示name的第二个父节点,当name有多个父节点的时候使用,如果还有更多父节点,可以使用name^n
- name~n:表示name的父亲n代,比如name~5相当于name^^^^^
- name:path:表示name这个commit快照中的path路径比如在比较两个文件diff的时候可以使用:
$ git diff HEAD^1:Makefile HEAD^2:Makefile # 注意git diff两个参数的关系,git diff old new看变化
- name^{tree}:引用该commit对应的tree,而不是name本身 其余的引用方式可以参考原文
merge和rebase的区别
merge发生在两个commit之间,当多个commit有着共同的branch base的时候,可以使用git show-branch
来查看效果,下面图片产生的是
$ git show-branch
! [Z] Z
* [D] D
--
* [D] D
* [D^] C
* [D~2] B
+ [Z]Z
+ [Z^]Y
+ [Z~2] X
+ [Z~3] W
+* [D~3] A
注意:git merge XX命令是将XX分支合并到当前的分支,而不对XX分支改变,git merge命令会在当前分支生成一个新的节点,这个节点的前驱是XX分支的指向的commit和HEAD指向的commit
git rebase的作用: git rebase XX命令是把XX分支与当前分支进行衍合,过程是将当前分支“嫁接”在XX分支上,把W作为D的改变提交,在这个过程中,每一步都有可能出现冲突,可能需要修改多次冲突,rebase的结果是两个分支变为一个分支,称为线性结构
Git 中的index
index就是中文的暂存区,用来生成下一个commit的时候使用,index的唯一作用就是暂时存储下一个commit的blobs,用来构成下一个commit对应的tree。当使用git commit的时候,会将当前暂存区内所有的blob打包成tree,然后形成新的commit节点。
有一个方法可以几乎忽略暂存区的存在
$ git commit -a
这句话相当于把当前的修改全部加入暂存区后commit,不过注意commit -a会将新生成的文件全部忽略,只有在原来基础上修改的文件会被commit,除此之外,commit -a命令几乎可以理解为使svn中的svn commit
index存在的一个必要是,可以通过git add很好的控制一次commit中添加的文件 比如一次working tree中修改的了两个不相关的文件,可能是两个功能,可以分两次commit,增加commit的原子性
Git中的reset
reset是Git版本回退的主要方法,其主要功能是改变HEAD指针指向的commit,当HEAD指向HEAD^的时候,版本将退回上一个版本。不过要注意的是,reset的三种参数:
--soft
:纯粹改变HEAD指针,而对index和working tree视而不见--hard
:这个参数除了移动指针,强制将index和working tree和HEAD进行同步,因为这个时候你的working tree还没有提交到commit上,所以这种舍弃可能是破坏性的,是Git中一个很危险的参数--mix
:默认参数,soft和hard的混合,改变HEAD指针后,把Index和HEAD同步,但不改变working tree
参考文章
- Git From Bottom Up:篇文章是打开我Git新大门的文章,非常有insight,有时间考虑翻译这篇文章
- 图解Git:比较生动的图解Git,便于记忆