这里不从0开始介绍什么是git、怎么使用git,而是来关注一些日后使用git时可能遇到的常见场景需要怎么进行处理
Rebase
我所参与的社区项目的工作流里明确指出了将新的修改提交pr前需要rebase主分支,我们先来看一下什么是rebase,官方给的解释是这样的:当执行rebase操作时,git会从两个分支的共同祖先开始提取待变基分支上的修改,然后将待变基分支指向基分支的最新提交,最后将刚才提取的修改应用到基分支的最新提交的后面
看着有点抽象,结合例子来解释一下:
css
A---B---M master
|
----C---D local
在这个例子里,你基于主分支的 commit B 建立了一个新分支local并在local分支上创建了commit C D,你的同事张三向主分支提交了commit M ,现在你需要把你的local分支的修改提交到主分支,但你不能直接提交pr,因为你的local分支缺少主分支的M commit修改,直接提交会发生conflict,在local分支进行操作:先 git fetch upstream
拉取主仓库最新提交之后,再使用 git rebase upstream/master
(假设主分支所在仓库在你本地仓库的remote关联名叫upstream)后,你的local分支的commit历史会变成:
css
A---B---M---C---D local
这样你的local分支就同步了主分支的最新提交,并在此基础上合并了你的新修改(值得注意的是,在rebase master分支的时候,如果M commit对代码的修改和你的C D commit对代码的修改存在交集,那么rebase会发生conflict,需要你对commit历史进行重写提交,处理冲突),这个时候你就可以把你的local分支向主仓库master分支提交pr了
(如果在rebase时发生conflict需要解决冲突重写commit历史时,出于某些原因,你需要放弃这次rebase,使用命令 git rebase --abort
,注意这只有在rebase发生冲突未完成时可以使用,如果rebase成功将无法丢弃)
总结起来就是两个字:变基,即改变你的本地分支创建时的基底,你的commit都会被接到这个新基底上
【这是rebase的主要常见用法,还有些用法(例如git rebase HEAD~1
之类的)感兴趣可以自行了解】
rebase的好处是显而易见的:主分支的commit历史是一条非常干净的直线,每个commit节点都是有意义的修改,可读性高,例如这是我参与的一个社区项目的commit历史记录:
编辑
缺点也是有的:rebase之后,你的本地分支是基于主分支的哪一个commit节点创建出来的新分支在commit历史上就不可见了,但是在多人协作开发项目中,更加注重多人的开发成果能否稳定的合并进同一个分支,个人提交顺序先后反而不是特别重要
Merge
同样作为合并代码的命令,merge对commit历史的修改是这样的:
css
A---B---C---D master
|
----E---F local
在local分支下执行命令git merge master
后:
css
A---B---C---D master
|
----E---F---G local
Merge之后,master分支的commit C D会合并为一个提交G,拼接到local分支的历史上(注意在merge的时候同样有可能需要处理冲突)
这样做的好处是,对于个人开发者而言,他的commit历史是足够清楚的,缺点是commit历史上会多出许多不必要的commit,当上游主分支的更新足够频繁时,这种commit历史会变得极其冗杂,可读性极低
这只是merge的简单用法,详细的Fast - forward、non - Fast - forward和squash三种合并模式,还有许多其他参数可以自行了解
Reset
reset的作用是进行版本回退,原理是通过reset命令将HEAD指针指向指定的commit节点,指定节点可以是形如aabbcc
的节点哈希值,也可以形如HEAD~2
(将HEAD指针指向当前节点的前两个,即倒数第三个)
一共有三个模式:soft,mixed,hard,分别来介绍
-
hard:
git reset --hard HEAD~1
:cssA---B (HEAD)
- 该命令会将HEAD指针从B指向前一个的A,并且完全抹除A之后的所有内容修改(无法找回),这意味着你会完全丢失commit B的所有修改内容,慎用!!
-
mixed:
git reset (--mixed) HEAD~1
:加括号是因为reset不加参数时默认的模式就是mixedcssA---B (HEAD)
- 该命令会将HEAD从指向B改为指向A,但是会保留从A到B的修改在工作目录,这意味着你可以重新对从A到B的修改进行add 和commit,总结:撤销指定节点之后的所有提交,但保留修改
- 对于保留了的修改,可能的场景就比较多了,你可能想把一个大的commit拆分成几个小的commit来提交,提升commit的原子性;也可能是原本的修改存在一些问题,需要加以一定的修正后重新提交等
-
soft:
git reset --soft HEAD~1
:cssA---B (HEAD)
-
该命令会将HEAD从指向B改为指向A,但是会保留从A到B的修改在暂存区(add之后commit之前),这意味着你可以对这个commit进行补充
-
由于soft将回退的修改内容保存在暂存区,这意味着原本的修改内容不会变动,因此在如下场景你可能会用到这个模式:
- 1.合并commit,例如
git reset --soft HEAD~2
,这会将最近的两个commit的修改内容都保存在暂存区,然后重新commit就可以把这两个commit合并为一个commit - 2.旧的commit有缺漏,在不改变原本的commit修改内容的前提下,你可以添加新的修改文件到这个commit
-
- 等等
- 1.合并commit,例如
-
Cherry-pick
功能是将一个分支的部分修改代码合并到另一个分支,而不必把整个分支的修改合并进去
css
A---B---C---D master
|
----E---F---G feature
在master分支执行git cherry-pick F
后:
css
A---B---C---D---F master
|
----E---F---G feature
以上是基本用法,用于转移一个commit的内容;cherry-pick也支持多个commit的批量转移(示例在master分支下执行):
git cherry-pick E..G 将(E,G]的commit合并到master 效果:
css
A---B---C---D---F---G master
|
----E---F---G feature
git cherry-pick E^..G 将[E,G]的commit合并到master 效果:
css
A---B---C---D---E---F---G master
|
----E---F---G feature
(注意以上的字母都是对应commit的hash值)
其他参数可以自行了解
Stash
考虑这么一个场景:你在A分支开发时由于某些原因,需要暂停,你想切换到B分支,但是git版本管理不支持在有未提交的修改内容的情况下进行分支切换,你无法切换到B分支,这时候可以使用git stash
来暂存A分支的开发内容,然后切换到B分支进行开发,等B分支任务结束后切换回A分支后,使用git stash pop
来弹出stash栈顶的上一次存储的暂存内容,继续A分支上的开发
这是stash的基础用法,详细用法和原理可以自行了解