前言
你是否在一次提交后,想要撤回对应的内容迷茫无措...
你是否在多个分支来回切换开发中,对需要合并散落在不同分支的commit而束手无策...
你是否经常听到同事在说使用rebase命令而根据自己多年的add/commit经验不知道rebase是个什么...
你是否每次跟远端同步都在害怕产生冲突...
你是否对一堆修改想要分步提交而望而却步...
以上的种种,我都有过,在团队开发中,版本管理工具是必不可少的,git也是一个非常常用的版本管理工具,往往速成上手,在网上学习过了怎么提交,推送,拉取,合并操作后,就可以在项目中进行合作了,一般情况下也都适用了,但是git的强大远不止于此,除了递增式的去向前推进commit记录,还可以针对已经提交过的进行选择性的修改,这样妈妈再也不用担心提交后想要修改,还得将文件对着之前的版本,一行一行的改回去了
从这里开始进入正文
分支切换
在使用中,我们知道git鼓励是使用多分支进行开发,通过简单的checkout -b指令就可以新建一个分支,并将当前分支指向新的分支,那么什么是当前分支,新建一个分支是将当前分支的所有内容全部拷贝了一遍么
实际上git的版本控制就是在文件内的./git文件中,里面没有没有对实际需要管理的文件进行一次复制,不同的分支也仅仅是一个引用的指向
简单的新建了一个本地仓库,新建了一个文件和master分支,进行一次提交,再切换出develop分支,修改文件后,在develop分支进行一次修改提交,通过git log 查看记录
//每一次的提交都会有一个hash值作为索引标识
commit 034c5c1db2b4af576e48d6b5f0bad91e599c117e (HEAD -> develop) //括号内的就是分支引用的信息
Date: Wed Sep 7 14:02:55 2022 +0800
develop commit 1 //每次commit需要写入本次提交的修改简介
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255 (master)
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
从这简单的操作可以看出,当前master分支是指向了f1f90f这一次提交,而当前的分支通过HEAD指向,可以看出当前的分支是在develop分支上,并且develop比master分支有一个更新的提交
这个时候切回master分支
//HEAD引用指向了master,并且master还是在f1f90f这次提交上
//log查看的是历史,develop分支由于超前于当前位置,所以就看不到对应的信息了
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255 (HEAD -> master)
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
这样看来,checkout+分支名就是将HEAD的引用指向了对应的分支,这时候查看./git文件夹中的内容可以看出
HEAD : ref: refs/heads/master
develop : 034c5c1db2b4af576e48d6b5f0bad91e599c117e
master :f1f90fef4ba31a240a81adf0c3774ab4bcdc0255
所以具体的分支就是指向了一次具体的commit,而git管理的主要修改内容还是一个个的commit
再将master分支做一次改动提交,后切换到develop分支,分别查看log
//master新增一次commit记录
commit 6e4d4de115cb81750ad1ff943d9efebb9374c064 (HEAD -> master)
Date: Wed Sep 7 14:55:03 2022 +0800
master change 1
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
//切换回到develop分支
commit 034c5c1db2b4af576e48d6b5f0bad91e599c117e (HEAD -> develop)
Date: Wed Sep 7 14:02:55 2022 +0800
develop commit 1
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
master指向的commit记录不存在在develop的历史commit中,所以在develop分支中查看log是找不到master记录的,log查看当前指针位置到根节点的提交记录
--detach
可以看到在checkout指令后跟具体的分支名,HEAD的引用是指向了具体的一个分支,而具体的分支是指向在了一条具体的commit记录,通过checkout --detach指令,可以将HEAD的引用从具体的分支脱离下来,跟分支一样指向具体的一次提交
当前在develop分支上,执行git checkout --detach指令
//可以看到从之前的(HEAD -> develop)变为了(HEAD, develop)
commit 034c5c1db2b4af576e48d6b5f0bad91e599c117e (HEAD, develop)
Date: Wed Sep 7 14:02:55 2022 +0800
develop commit 1
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
可以看到HEAD指针的引用从指向develop分支变成了指向对应的commit
这会查看./git文件夹中的HEAD文件内容也就变成了 “034c5c1db2b4af576e48d6b5f0bad91e599c117e”
除了detach可以直接从分支脱离,也可以通过git checkout + commitId 去直接指向一个具体的commit
直接将checkout 到之前master的那一次提交 git checkout 6e4d4d
这样的查看log
//HEAD指针就指向了之前master所在分支的最新提交,并且没有引用master分支
commit 6e4d4de115cb81750ad1ff943d9efebb9374c064 (HEAD, master)
Date: Wed Sep 7 14:55:03 2022 +0800
master change 1
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
通过这一些了的checkout操作,就可以看出git分支的本质就是一个指向具体commit的指针引用,对应的分支信息就是存储的当前分支所在的commit的哈希值,而checkout可以操作HEAD当前工作指针,如果HEAD指向了分支,就会commit提交后跟着分支一起移动,脱钩--detach后,就可以很灵活的从任意一个commit去切换分支,而保持之前的分支引用不变
举个例子
在当前情况下,切回develop分支,并产生一次新的提交记录,然后将HEAD指向前一个commit
//step1 git checkout develop 后新增一次新的commit
commit 739ec5bbd9c0ff66a6ba4997ea944bdac4feb7ea (HEAD -> develop)
Date: Wed Sep 7 22:26:18 2022 +0800
develop commit 2
commit 034c5c1db2b4af576e48d6b5f0bad91e599c117e
Date: Wed Sep 7 14:02:55 2022 +0800
develop commit 1
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
//step2 git checkout 034c5c 将当前的HEAD指针指向前一个commit提交
commit 034c5c1db2b4af576e48d6b5f0bad91e599c117e (HEAD)
Date: Wed Sep 7 14:02:55 2022 +0800
develop commit 1
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
这样就可以从develop分支中的任意一次提交的位置,切出新的分支,进行开发
从HEAD指针的位置切出一个新的分支feature-1,并进行一次提交
//这样就从develop分支的历史记录中牵发出了一个新的分支
commit 4b465a7a3514b65917bbb432902afa5e062a4261 (HEAD -> feature-1)
Date: Wed Sep 7 22:27:39 2022 +0800
feature1 commit 1
commit 034c5c1db2b4af576e48d6b5f0bad91e599c117e
Date: Wed Sep 7 14:02:55 2022 +0800
develop commit 1
commit f1f90fef4ba31a240a81adf0c3774ab4bcdc0255
Date: Wed Sep 7 14:00:42 2022 +0800
first-commit
最终会得到这样的形态的git当前版本示意图
小结
通过上述一些列的操作,可以理解到git管理的本质就是一次一次的commit提交记录,commit里面有具体的文件的修改记录,分支就是一个指向具体某条commit提交记录的指针,分支的内容也就是对应commit记录的哈希值引用,而HEAD指针可以指向分支随分支一起移动,也可以很灵活的切换到任意的commit
checkout指令的本质就是在移动HEAD指针,并不是在新的分支将文件内容进行一份复制,只是在对应的commit提交节点,增加一个分支的引用关系,这样就让分支的管理变得非常的轻量级,只需要在一个文本文件,记录下一次commit的哈希值,也让操作分支更加的灵活
需要注意的是,本地仓库没有对接远端,如果有远端仓库存在的情况下,远端的分支如 origin/master 也会指向在一个具体的commit提交,如果在本地通过checkout origin/master,跟切换本地分支让HEAD指向对应的分支不同,checkout到远端分支,会让HEAD指针直接指向对应的commit