前言
工作中使用git是从两年前开始的,之前一直add -> commit ->push
常规操作,真正在工作中使用之后才渐渐理解了git的强大,这种理解是建立在不断解决问题的基础上的,不断的处理遇到的问题,就像升级打怪一样,对git的理解也越来越全面。因为在使用git之前一直用svn作为版本控制工具,所以对git和svn的区别也有了自己的认识,关于两者的区别网上的文章一搜一大把,我就不重复了,我仅仅从自己的理解来描述下两者的不同。
git和svn
关于git和svn的区别,网上的文章确实很多,大多数会提到分布式、存储方式、版本号、完整性等方面,而我今天要写的区别是两者提交记录的结构。
既然作为版本控制工具,那么每次历史提交都必须可以追溯和回退,在svn中提交记录时线性的,以时间轴为参考基准,所有提交按照时间先后排列,因为svn记录必须提交到服务器才能生效,所有服务器相当于各个svn客户端的总控,各个svn提交到服务器时线性排列,且必须将本地文件状态更新成和服务器相同时才能修改提交。
正因为在svn中有服务器负责总控操作,所以能保证时间最新的提交记录就是整个svn最新的状态,提交记录不依赖客户端时间,完全由服务器时间进行排序。
在git中没有这样的总控服务器,虽然一般情况下每个代码库都会有统一的托管服务器,但是它的作用任何一个git客户端都能代替,因为git是可以离线提交的,托管服务器只是我们用来存储代码的地方,与svn服务器按时间排序的做法大不相同。
git的提交记录通常是一个树形结构,个别时候会变成有“起点”和“终点”的网状结构,在git中时间只具备参考意义,并不能决定提交记录的先后,如果你对这一点还心存怀疑,可能你是个svn的重度用户,一时还没理解git操作原理。
对于这个问题可以举个例子,操作同一个文件,在svn中2月13日修改一次,2月14日修改一次,那么2月15日看这个文件一定是2月14日修改后的状态;而在git中,同样是那个文件分别在2月13日和2月14日修改一次,2月15日文件的状态取决了两次修改是否在同一分支,以及合并时是怎样处理的,这种错位随着时间的延长和多分支的合并,往往对时间的依赖“微乎其微”,此时再也不能用时间来衡量提交的先后了。
如果一开始就是git,上面提到的这个问题还不太明显,但是用惯了svn再使用git,处理历史回溯问题时往往容易找错方向,经常通过时间过滤出来的内容并不是自己想要的,这一点在实际操作中需要注意。
git最重要的是什么
相信这个问题每个人都有自己的答案,有人认为是分布式,有人认为是切换分支很方便,而我的答案是 commit 的设计哲学
,我觉得这是git中的精髓,git中的commit就像一个链表中的元素,用来将自身和其他的commit串联到一起,形成branch
、tag
、HEAD
等等。
我们可以通过 git log
命令来看一条 commit:
1 | $ git log -1 |
这条commit id 为 7bf665f125a4771db095c83a7ad6ed46692cd314
,这在整个库是惟一的,通过 git log
可以看到这次提交的时间、作者、简要说明等信息,那么这次提交和库是什么关系呢?
通过括号中的内容可以知道当前提交是这个库的6.0
分支,同时为标签6.0.6
,也与远端的6.0
分支同步。
使用 git cat-file
命令可以进一步查询这个commit的组织形式:
1 | $ git cat-file -p HEAD |
可以发现这次提交包含了 tree c3d4b2bcd934be7e4ed98edac5aa7e9c054503c3
,同时它的父提交就是 parent a5696bdf4f2687ab45f633ccb7cdc4ee9c2f957d
,有了这两个id就可以递推出当前版本内容和这个历史记录。
通过 tree c3d4b2bcd934be7e4ed98edac5aa7e9c054503c3
可以递归找出当前版本中的所有文件:
1 | $ git cat-file -p c3d4b2bcd934be7e4ed98edac5aa7e9c054503c3 |
通过 parent a5696bdf4f2687ab45f633ccb7cdc4ee9c2f957d
可以找出上一次提交,进而递归找出所有的提交,要注意有些commit的parent不止一个:
1 | $ git cat-file -p a5696bdf4f2687ab45f633ccb7cdc4ee9c2f957d |
这个commit的设计真的很神奇,一个个commit串起来就是一个branch,本质来讲branch
只是commit的一个别名,包括HEAD
也是,而 tag
也是对commit的一个描述,在不加描述信息时和commit也是一样的。
1 | $ git cat-file -p 6.0.6 |
所以理解了commit的定位以后,所有切换分支、切换tag、操作HEAD,本质上都是在对commit进行操作,这些操作的参数完全可以用commit id来替换HEAD、branch name、tag name等等。
总结
- svn的提交记录是一个按时间排序的线性结构,git的提交记录是一个参考时间的树状结构
- git记录中时间先后不能代表commit修改的先后,回溯查找时要注意这一点才能解释很多疑惑
- git中的commit我认为是它的精髓,通过commit的串联和别名,形成分支、标签、HEAD等多种元素,隐藏了细节,方便了操作
什么才是精彩的人生?扬在脸上的自信、长在心底的善良、融进血里的骨气、刻进生命里的坚强~
2022-2-13 23:19:05