介绍
本文内容,主要来自Learn Git Branching的关卡教程。
包括commit,branch,branch -f,checkout checkout -b,HEAD分离,merge,rebase,reset,HEAD和^~符号,cherry-up,rebase -i,commit --amend,tag,describe,clone,fetch,pull,push,pull source:goal,push source:goal,fetch source:goal,branch -u。
并且结合Learn Git Branching提供的UI,提供实验加以理解。
1 git rebase
git rebase介绍三种语法,git rebase <tear>,git rebase <tear> <head>,,git rebase -i。
1.1 git rebase <tear>
先用单参数的git rebase来理解rebase的本质操作。
命令解释:
将一个[提交系列]移植到另一个[提交系列]。
下面通过实验来理解rebase的作用:
- 实验内容和步骤:
- 每次提交,新建一个【分支名】-【提交编号】.txt的文件,形成两条分支。
- 使用rebase命令,将节点合并,观察最后的节点内容来理解rebase。
- 实验过程:
- 铺垫:
- 新建一个文件夹,git_test,使用git init 初始化仓库。
- 新建文件main-0.txt,然后git add .然后提交git commit,这样最初的节点我们就创建好了。
- 在此节点上,分支出bugFix节点,git checkout -b bugFix,然后工作区中新建bugFix-1.txt,git add .提交git commit。
- 现在bugFix分支的最新节点,已经和main分支的最新节点不一样了。我们再切换回main分支git switch main,加入一个新的文件main-1.txt,然后提交。
- 现在分支结构如下:
- 探究1:
- 现在通过rebase 将bugfix-1.txt所在节点,移动到main分支最新结点后。如果使用单参数,就要先切换回bugFix分支,然后git rebase main。
- 变成这样。
- 如果最后的bugFix分支最新节点中,不包含上一个节点main-1.txt,那么说明,rebase后不会改变节点中的内容,否则说明节点中的内容和分支中的先辈节点有关。
- 结果:
- 观察得到,rebase后的bugFix节点中包含main-1.txt文件
- 总结1:
- 原来节点中的内容和节点分支中的先辈节点有关。
- 探究2:
- 我们将rebase还原,通过git reflog 和git reset --hard。(也就是bugFix-1.txt和main-1.txt作为分支最新提交的状态)
- 然后将bugFix的节点延长,加入一个bugFix-2.txt,然后再次rebase
- 添加后
- rebase后在UI中是这样
- 看起来是将和main分支有分歧的节点都加入rebase了,以此来保证节点不丢失原来的内容。
- 如果在最后bugFix最新的节点中,不包含bugFix-1.txt,那么就是单纯将节点rebase到别的分支,如果包含,那么rebase就是将节点原所有先辈节点都rebase。
- 结果:
- 包含bugFix-1.txt
- 总结2:
- 实际上rebase就是对于分支的操作,因为每个节点的内容都由节点自身和节点在分支上所有的先辈节点组成的,要想保证节点的内容不丢失,就必须将分支中所有的先辈节点rebase加入。
- 后来发现,git rebase <节点> 也可以,实际上就是将当前head指针指向节点,以及其先辈节点,以后面参数 (无论是分支指向还是指明的节点)节点为起点,这个就是rebase。
- 所以可以间接得到结论,每个节点中存储的都只是对上一个节点的修改,而不是提交的内容。
- 铺垫:
1.2 git rebase <tear> <head>
也就是接受两个参数的情况。第二个参数实际上就是单参数情况下的隐藏参数HEAD所指向节点。
对于第一个参数<tear>的理解,可认为是【尾】,也就是要将HEAD续接到哪个地方。第二个参数看做是补充参数,指明要取哪一个【头】。
看下面的示例:
我们有两个分支,现在想要将bug分支续接到main分支下。当前分支所在main,如果使用单参数git rebase就只能将main续接到其他分支。但是使用两参数git rebase可以指明方向。
git rebase main bug
可在Learn Git Branching中,输入一下命令进行上述演示。
1.3 git rebase -i
-i参数的意思是交互式interactive的意思,具备让用户通过交互式页面控制提交顺序的能力。
在Learn Git Branching中,使用git rebase -i 会提供一个下图所示页面,来模拟git的实际交互页面。
git rebase -i 同样可以选择一个参数,或者两个参数,用法和git rebase一样。
只不过使用 -i 参数会提供一个交互式页面,可以以此来调整提交的顺序以及选择移除特定的提交。
通过一下示例来演示:
在Learn Git Branching中,创建如下结构。
通过git rebase -i bug main可将main系列移植到bug分支。不过在此之前,会弹出交互式页面,我们可以拖动来改变顺序,点击Omit按钮来选择显示或者移除提交。
提交之后就会得到结果。
2 HEAD分离状态
在git中,我们的命令往往都有一个默认参数,就是HEAD指针所指向的提交节点。
在git中有很多指针,指针的终点就是提交的哈希值。
HEAD指针,作为我们操作最多的指针,随时关注HEAD指针的指向。
分支指针,例如文章上述涉及到的main指针,始终指向分支的最新提交。
tag指针,也叫做标签,不会随着提交而改变,相当于锚点一样的存在,帮助我们快速定位一些重要的提交节点。
我们可以通过git checkout 或者 git switch 来改变HEAD指针的指向。
我们可以让HEAD指向某一个特定的提交,或者指向分支指针,例如 git switch 9f54cf 切换HEAD指向哈希值为9f54cf的提交节点,git switch main 将HEAD指向main分支。
2.1 指针自动更新
当我们使用git commit 更新仓库,或者某些改变仓库提交结构的时候,直接指向的指针会更新。
HEAD直接指向节点,那么HEAD指针更新。
HEAD指向分支指针,分支指针直接指向更新节点。那么分支节点更新指向最新提交。HEAD指针就间接指向了最新提交。
让HEAD不直接指向分支指针,这就叫做HEAD分离状态。
3 分支、切换、相对定位
分支branch
创建分支git branch <branch name> ,以HEAD指向的当前节点为起点,创建新的分支。
创建分支并切换到新的分支 git checkout -b <branch name>
以上两种是创建分支的常用方法。还可通过git pull 来创建分支,不做介绍。
切换switch和checkout
切换分支一般通过
git switch <branch name>
git checkout <branch name> 因为git checkout 具备太多的功能,所以现在git官方才提供git switch来替代git checkout 的切换功能。
切换到特定节点
git switch <commit HashCode>
git checkout <commit HashCode>
要查看提交历史,查看提交节点的哈希值,可通过以下命令查看。
git log
git reflog
相对定位
当我们在切换到提交节点的时候,通过查找提交节点的哈希值往往是很慢很头疼的。而基于特定节点进行相对定位,能够在我们脑中更清楚的展示节点的位置。
git提供了两种符号来帮助进行相对定位。
波浪号~,尖角号^
波浪号~
git switch HEAD~2
他的意思就是切换到 HEAD指针指向的节点的往前第二个节点。
或者git switch HEAD~~
尖角号^
^符号也表示往前,往上。但是有一点区别,^号后接数字,表示横向选择,而不是纵向次数。
看下图,你就知道什么是横向选择了。
当前HEAD所在c6,往上是c5还是c4呢?c6的两个父节点,一个离他近一个离他远是什么意思呢?
当我们使用git merge合并分支的时候,HEAD指向分支就是默认1号分支,合并得到的新节点1号默认分支就是原HEAD所在分支,而git merge参数所在分支是默认2号分支。
这样就能对两个父节点进行区分,一个1号父节点,一个2号父节点。
所以横向选择 git switch HEAD^2 表示选择2号父节点。git switch HEAD^就是默认选择1号的父节点
组合使用
git switch main~~^2^^
表示选择main指针指向节点的往上第二个节点的二号父节点的往上第二个节点。
4 更改原有节点
想要更改已经提交的节点,从而影响整个下流分支节点。这种操作是可行的。
但是我们必须要让目标节点处于分支的最末端,才能进行修改。也就是说git允许我们更改已经提交的最新节点,而不产生新的节点。
那么我们早就提交的节点如何更改呢?
还记得git rebase -i吗,可以通过拖动来改变提交节点的顺序。
来看下面的示例:
想要修改c1节点的内容,从而影响c2c3c4c5以及后续所有节点。
先使用git rebase -i 将c1提到前面来。
git rebase -i c0
通过交互式界面将c1放到最下面。
结构发生如下变化
此时使用git commit --amend即可修改c1节点的提交内容。
git commit --amend
然后将c1通过rebase -i还原原来的顺序。
注意,通过此方法重新重新提交之后是一个新的节点,只会影响新提交所在的分支。而原提交节点,仍然不会受到影响,其他分支并没得到更新。
撤销提交
git reset 以及 git revert
git reset 作用于本地,在本地随便我们怎么修改,所以git reset是比较好理解好使用的。
git revert是作用于远程仓库的,因为远程仓库不仅仅只是一个人修改,为了让别人知道你的修改,我们只能将撤销这个修改作为更新提交到远程仓库,这样别人才能兼容你的修改。
5 git merge合并
合并操作是非常关键的操作,也是很复杂的操作,特别是对于冲突问题的解决。
当两个修改方案都对同一个文件做出修改,并且修改了同一处内容(修改不同行内容允许),会导致冲突。
6 远程仓库操作
git clone,git fetch,git pull,git push,git remote
git clone
和git init是相似的作用,不过git clone是从远程仓库,将仓库下载到本地。
而git init 是创建本地仓库。也就是先有本地还是先有远程仓库的区别。
git fetch
用于将远程仓库的更新下载到本地,但不会影响本地分支,本地工作。
git pull
结合了git fetch 和 git merge不仅将更新下载到本地,还将和本地分支合并。
所以git pull遇到的错,大多都是因为merge产生的错误。
单独使用git pull,git会先取HEAD指针,并试图分析出【要拉取的远程分支名】,【merge的1号父节点】。
如果此时HEAD并不是指向的分支指针,而是处于HEAD分离状态直接指向某个节点,那么使用git pull会报错,找不到远程分支。
此时我们可以直接指明git pull origin <remote branch name>
git pull --rebase
默认的git pull是git fetch 和git merge orign/branch 的结合,使用--rebase参数,就是git fetch和git rebase的结合。
这个命令可以快速的将远程仓库的更新添加到本地使得现有本地节点兼容最新更新,可以随时提交到远程仓库。
git pull origin source:goal
三参数,第一个参数指明远程仓库的位置,通过git remote可以添加远程仓库。
第二个参数,从那个分支拉取。
第三个参数,推送到那个分支。
7 关联追踪远程仓库
git checkout -b <local branch> <remote branch>
这个命令1个参数,就是创建并切换本地分支。
而此处2个参数,就是让本地某个分支(自动创建),关联或者追踪远程仓库的某个分支。
这样当HEAD指向这个分支的时候,pull和push就能自动追踪到远程仓库的特定分支。
git branch -u <remote branch> [local branch]
或者branch命令。
如果不指定本地分支,那么就是默认当前分支。