【摘】Git- 从零单排 03 期
前言
书接上回,02期里大概的介绍了 Git 部分操作的底层原理。这一期呢,笔者想谈谈 指针 。在 Git 里, 指针 这个概念太重要了。如果理解不到位,你可能根本不敢碰 reset 、 rebase 等指令,就算用了,也是一知半解。相反,如果你对 指针 理解深刻,遇到任何情况,你都会在第一时间想到多种解决办法,你将不再被局限在 add、commit、pull、push 这四个指令里。
指针
我们先不聊指针,我们先来看看 Git 里常见的 5 个事务,它们到底是什么?
首先是 tag ,项目功能上线,我们需要打 tag 。具体的命令如下:
我们实际操作一下,我们先进入到项目位置,这里就继续使用之前文章里操作过的项目目录。
看到这里,有没有发现一个现象, tag 的行为跟我们以前查看 commit 是不是超级像?我们暂且,按下不表。
我们接着看一下 branch 。平时,开发中,我们接到一个新的需求,一般都会重新开一个新的分支,用于开发,等开发完成,再合并到主线分支。 branch 命令如下:
来,实际操作一波。
这里我们就大概演示一下 branch 的查看、创建、删除、更名操作。并且,我们也发现,newTest 分支跟 tag-1 完全一样,也在后续统一说明。切换分支后面讲。
接下来,我们再看看 HEAD ,这个我们一般称它为头指针。
HEAD 这个是一个特殊的指针,笔者对它的理解就是像一个指挥官,控制着你的大部分对历史记录的操作。
好了,到这里,我们来分析一下。当我们输入查看历史记录的时候,你会看到几个熟悉的字眼, 1fce6d3 (HEAD -> master, tag: tag-1, newTest) chore: tag 1
1fce6d3 是一个提交对象的 hashId , HEAD -> master 的意思是头指针当前在 master 分支上,这也就是当前分支,查看分支时,分支前面的那个 * 号就是代码头指针在那,同时 master 分支指向 1fce6d3 这个提交对象。 tag: tag-1 的意思是,这个提交对象上有一个标签,名称是 tag-1 。 newTest 的意思是 newTest 分支也指向 1fce6d3 这个提交对象。 chore: tag 1 是 1fce6d3 这个提交对象的提交说明。
上面描述的是现象,笔者现在来针对这个现象,表达自己的理解。
首先是 HEAD ,这是一个不管你怎么操作,都指向当前提交对象的指针,大家都叫它 头指针 。这个指针的能带着 branch 一起移动,当你进行 commit 提交时。
然后是 branch ,大家都叫它分支,但是,我自己的理解里,我更愿意叫它 游标 ,因为 分支 这个词在记忆理解的时候,会让我联想到 树木的树枝,河流的支流,然后会把分支想成一个 有头有尾的连续的 线条状的事务。其实,这是大错特错的,如果你把分支理解成线条状的事务,你就会无法理解切分支的原理,(我以前就是把切分支理解成,把一根根线条状事务移来移去,把删除分支理解成把某一整条历史记录都删除)对于分支操作出现问题时,你可能会无法理解。其实,分支就是一个指向提交对象的指针,删除分支就只是把一个指针删除而已,删除分支并不会影响到任何一个提交对象,也不影响到任何一个历史记录(其它操作分支的行为也都是一样)。对应 HEAD 来理解,头指针是一个永远指向当前提交对象的指针,而 branch 是一个指向当前‘业务’所在的提交对象的指针。
解释一下这里说的‘业务’。比如我们开发一个项目,同事 A 开发订单模块,她就切一个名为 order 的 分支 ,那么一般正常情况下,这个 order 分支,一定是指向订单模块当下所在的那个提交对象的。然后同事 B 呢,她开发门店模块,她就切一个名为 shop 的 分支 ,同理,这个 shop 的分支,一定是指向门店模块当下所在的提交对象。<br />我们使用 git log order
来看订单分支的历史记录,然后出来的就是 order 所指向的 提交对象 的 parent 对象形成的链表,这是一个以当前提交对象为‘起始结点’,第一个提交对象为‘终端结点’的链表。再次强调,这个链表不是分支,分支只是一个指向这个链表‘起始结点’的指针,所以你删除分支,用 reset 移动分支时,并不会影响这个已经形成的链表记录。<br />所以呢, 分支 就是一个提交记录的别名,因为 order 这种跟业务相关,语义化强的字符,肯定比 40 位的无规律的 hashId 好记,对应着 ip 与 域名记忆哈(你就说你记得 www.baidu.com, 还是记得 180.101.49.11)。还有要强调的一点, 分支 指向的提交对象是可以变化的。所以我更愿意叫它 游标。
接下来,就该是 tag 了,可以叫它 标记。功能上来说,它就是一个指向特定提交对象的指针,或者说,它也是一个特定提交对象的别名。为什么有了分支的别名,还要再搞一个 tag 的别名呢?这是因为,分支是可以移动的,今天分支指向的是 A 提交对象,明天可能就指向 B 了。tag 呢,是一个永远都不能移动的别名,不管什么时候使用它,它指向的提交对象绝对是同一个。所以,在项目上线了新功能时,会打个 tag 。如果后面需要让这个新功能下架,我们只要操作这个 tag 就行。tag 相对于分支来说,它更特殊一点,如果 tag 与 分支重名(假设是 abc)了,除了在 git branch
命令后,其它地方,使用 abc ,拿到的一定是 tag 指针。所以遇到 tag 和 分支重名,一定要记得用 git branch -m abc abcBranch
命令把分支的名称修改。
就如前面让看官记下的,tag-1 、 newTest 、HEAD 的内容都是一样的,为什么呢?因为它们在那个时候都指向同一个提交对象 1fce6d3 。在 Git 里,指针还有很多,比如,stash@{0}, origin/master, HEAD^ , HEAD~1 等。好了,我们现在都理解了指针,那看看我们在工作中,可以怎么操作吧
示例
新建分支。操作如下:
新建 tag。操作如下:
查看以前版本代码。操作如下:
后语
最后,再来说说, git checkout
这个命令叫签出,它的作用是移动 HEAD 头指针。比如,你使用 git checkout branchName
那就是把头指针挂在 branchName 这个分支上,下一次提交时,头指针往前移,同时会带着 branchName 分支一起移动。如果你使用 git reset
命令移动头指针,头指针也会带着 branchName 一起走。你就想象分支身上是有一个洞的,头指针有一个钩。头指针钩住了分支,就会带着一起走。 git checkout
呢,就是把头指针的钩从分支身上取下,并移动另一个地方,新地方有洞就钩住,没洞的话,Git 会提示你,现在头指针是分离状态。 git checkout -b name master
这个命令是,先从 master 分支所指向的提交对象上,新建一个 name 分支,然后把头指针从当下的位置拿下来,挂到 name 这个分支上,也就新建加切换分支。基于以上观点,笔者认为 tag 呢,它是被焊死在提交对象上的,永远在那。
还有一个,那就是,在操作 Git 的时候,不要担心,不管你怎么玩,只要你不搞 .git 里的东西,你就不可以把项目搞坏,你想要的东西,通过 git reflog
命令就能全部找到。所以,如果你理解了指针,那就大胆的操作吧,玩不坏的啦!
————世界上任何值得去的地方,都没有捷径!
版权声明: 本文为 InfoQ 作者【卡尔】的原创文章。
原文链接:【http://xie.infoq.cn/article/f7f9310681a60f34e1e0ab028】。文章转载请联系作者。
评论