Ref:强烈安利
https://learngitbranching.js.org/?locale=zh_CN
1 基础篇
1.1 本地提交
git commit -m "xxx"
1.2 创建分支、分支切换
git branch new_branch_name
git checkout new_branch_name
# 创建一个分支,并切换过去
git checkout -b new_branch_name
1.3 merge合并分支
# 当前在master分支,有一个dev分支
git merge dev
当前状态
合并之后,产生了一个新提交
1.4 rebase合并分支
rebase合并分支是更高级的合并,也是更推荐的一种合并。Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
Rebase 的优势就是可以创造更线性的提交历史,这听上去有些难以理解。如果只允许使用 Rebase 的话,代码库的提交历史将会变得异常清晰。
当前状态,在bugFix分支
执行
git rebase master
合并之后状态,相当于把bugFix之前所在的c3状态复制了一份放到了master分支的下面。rebase的意思像是,给当前的节点重新找一个基地(父节点)。
2 高级篇
2.1 分离HEAD
HEAD 是一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。**HEAD 总是指向当前分支上最近一次提交记录。**大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。HEAD 通常情况下是指向分支名的(如 bugFix)。在你提交时,改变了 bugFix 的状态,这一变化通过 HEAD 变得可见。
执行命令,就会把HEAD从指向master分离出来,指向一个具体的提交记录
git checkout c2
2.2 相对引用
通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,并没有像本程序中这么漂亮的可视化提交树供你参考,所以你就不得不用 git log 来查查看提交记录的哈希值
- 使用
^
向上移动 1 个提交记录 - 使用
~<num>
向上移动多个提交记录,如~3
git checkout
后面可以跟分支,hash值,HEAD,非常灵活。
git checkout 分支名/提交记录hash值/HEAD 相对引用(^/~<num>)
举例,当前状态
执行
git checkout master^
结果
2.3 撤销变更
2.3.1 reset:真正的回退
git reset
通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。
当前状态
执行
git reset HEAD^
结果,
2.3.2 revert(推荐):以进为退
虽然在你的本地分支中使用git reset
很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦!
为了撤销更改并分享给别人,我们需要使用 git revert
。来看演示:
执行
git revert HEAD^
结果
在我们要撤销的提交记录后面居然多了一个新提交!这是因为新提交记录C2'
引入了更改 —— 这些更改刚好是用来撤销C2
这个提交的。也就是说C2'
的状态与C1
是相同的。
总结:使用git revert
可以让我们一直向前!!
3 移动提交记录
到现在我们已经学习了 Git 的基础知识 —— 提交、分支以及在提交树上移动。 这些概念涵盖了 Git 90% 的功能,同样也足够满足开发者的日常需求。
然而, 剩余的 10% 在处理复杂的工作流时(或者当你陷入困惑时)可能就显得尤为重要了。接下来要讨论的这个话题是**“整理提交记录” —— 开发人员有时会说“我想要把这个提交放到这里, 那个提交放到刚才那个提交的后面”,** 而接下来就讲的就是它的实现方式,非常清晰、灵活,还很生动。
3.1 cherry-pick摘樱桃
git cherry-pick <提交号hash>
如果你想将一些提交复制到当前所在的位置(HEAD
)下面的话,Cherry-pick
是最直接的方式了。我个人非常喜欢 cherry-pick
,因为它特别简单。
当前状态
执行命令
git cherry-pick c2 c4
执行结果,可以看到像是直接把C2、C4复制了一份放到了当前节点下面
cherry-pick
的厉害之处就在于保持提交记录的线性,因为它是以复制的方式完成工作。至此,有没有一种神奇的感觉,git
命令的精髓其实就是保持提交记录的线性,因为这样的历史记录就会非常清晰。
3.2 交互式rebase
cherry-pick
需要你检查git log
看到hash值才能像摘樱桃那样去操作。更方便的是有一种交互式的rebase
,图形化摘樱桃,而且任意拖动调整顺序。
当前状态,查看git log
,提交记录按时间线由最新到最老有 test2、test1、version1.0
执行
git rebase -i HEAD~2
出现交互式vim
可以在vim编辑器中使用列出的命令,也可以调整上下顺序。常用命令pick
和drop
查看git log
,发现只剩下了v1.0
,test1和test2
就像没有存在过一样。
4 杂项
4.1 只取一个提交记录
当前状态,修改bug中间加入了printf帮助调试,在最新的c4
解决了bug,想只把c4
的内容合并到master
下面,printf
的内容并不想要,就是用cherry-pick
或者rebase -i
吧!
git checkout master
git cherry-pick c4
执行结果
或者
git rebase -i bugFix~3
git branch -f master bugFix # 把master指向bugFix,也就是把成c4加到了master中
4.2 提交的技巧
4.2.1 提交技巧1
接下来这种情况也是很常见的:你之前在 newImage
分支上进行了一次提交,然后又基于它创建了caption
分支,然后又提交了一次。
此时你想对的某个以前的提交记录进行一些小小的调整。比如设计师想修改一下newImage
中图片的分辨率,尽管那个提交记录并不是最新的了。
我们可以通过下面的方法来克服困难:
先用git rebase -i
将提交重新排序,然后把我们想要修改的提交记录挪到最前
然后用commit --amend
来进行一些小修改
接着再用git rebase -i
来将他们调回原来的顺序
最后我们把master
移到修改的最前端(用你自己喜欢的方法),就大功告成啦!
当然完成这个任务的方法不止上面提到的一种(我知道你在看 cherry-pick
啦),之后我们会多点关注这些技巧啦,但现在暂时只专注上面这种方法。 最后有必要说明一下目标状态中的那几个’ —— 我们把这个提交移动了两次,每移动一次会产生一个 ';而C2
上多出来的那个是我们在使用了amend
参数提交时产生的,所以最终结果就是这样了。
也就是说,我在对比结果的时候只会对比提交树的结构,对于'
的数量上的不同,并不纳入对比范围内。只要你的 master
分支结构与目标结构相同,我就算你通过。
初始状态
git rebase -i c1
执行后状态,把c3,c2
复制了一份,并且改变了顺序
git commit --amend # 通过创建新的提交来替换当前分支
把c3' ,c2''
复制了一份,并且改变了顺序
git rebase -i c1
git branch -f master c3'' # master指向最新的
4.2.2 提交技巧2
rebase -i
对提交记录进行重新排序,只要把我们想要的提交记录挪到最前端,我们就可以很轻松的用--amend
修改它,然后把它们重新排成我们想要的顺序。但这样做就唯一的问题就是要进行两次排序,而这有可能造成由rebase
而导致的冲突。下面还是看看 git cherry-pick
是怎么做的吧。
要在心里牢记cherry-pick
可以将提交树上任何地方的提交记录取过来追加到HEAD
上**(只要不是 HEAD 上游的提交就没问题)。**
当前状态
git cherry-pick c2
仅仅使用cherry-pick
也可以达成上一关的目的。
4.3 git tag
你可能会问了:有没有什么可以永远指向某个提交记录的标识呢,比如软件发布新的大版本,或者是修正一些重要的 Bug 或是增加了某些新特性,有没有比分支更好的可以永远指向这些提交的方法呢?
git tag v1 c1
4.4 git describe
由于标签在代码库中起着“锚点”的作用,Git 还为此专门设计了一个命令用来描述离你最近的锚点(也就是标签),它就是 git describe!
**Git Describe 能帮你在提交历史中移动了多次以后找到方向;**当你用git bisect
(一个查找产生 Bug 的提交记录的指令)找到某个提交记录时,或者是当你坐在你那刚刚度假回来的同事的电脑前时, 可能会用到这个命令。
找到某个分支的最近的一个版本号,快速恢复到那个版本!!
5 高级篇
5.1 把众多的分支rebase到master分支
初始状态,要求把众多的分支rebase到master分支,这就是项目代码管理员干的事呀!!
合并c3到master下面
git checkout bugFix
git rebase master
合并c4,c5,c6
到bugFix下面
git checkout side
git rebase -i bugFix
合并c7
到side分支下面
git checkout another
git rebase -i side
master移动到最前面,完成
git checkout master
git rebase another
完成上述步骤更快的一种方式,。。。。。还不知道。。。
...
5.2 存在多个父亲,选择父提交记录
操作符^
与 ~
符一样,后面也可以跟一个数字。
但是该操作符后面的数字与~
后面的不同,并不是用来指定向上返回几代,而是指定合并提交记录的某个父提交。还记得前面提到过的一个合并提交有两个父提交吧,所以遇到这样的节点时该选择哪条路径就不是很清晰了。
Git 默认选择合并提交的“第一个”父提交,在操作符 ^
后跟一个数字可以改变这一默认行为。
初始状态
git checkout HEAD~^2~2 # 前进一个,第二个父亲,前进2个
初始状态
git checkout HEAD~^2~ -b bugWork # 移动到指定位置,并创建分支
结果
5.3 纠缠不清的分支
初始状态
目标结果
执行过程,最优只要5条命令
git checkout one
git cherry-pick c4 c3 c2
git checkout two
git cherry-pick c5 c4' c3' c2'
git branch -f three c2