引言
本文主要以动图的方式讲解 Git 中比较常用的命令,以加深对这些命令的理解。
Merging
我们通常用不同的分支开发不同的新功能。当对应的功能准备发布时,通过 git merge
命令将该分支内容合并到生产。然而该命令有两种不同的模式:fast-forward
以及 no-fast-forward
,接下来,我们看下两者的不同!
Fast-forward (–ff)
如果 master 分支没有任何新的提交(即:落后于当前分支),那么将当前分支合并到 master 时,会采用 fast-forward
方式进行合并。该方式合并时并不会产生新的 commit,而是把当前分支的 commit 合并到 master 中。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/01.gif)
No-fast-forward (–no-ff)
如果 master 上在我们检出开发分支后有了新的 commit(即:master 领先于当前分支),那么 git 将会使用 no-fast-forward
进行合并。此时会有一个新的 commit 被创建,该 commit 同时指向了 master 以及当前分支(也就是将两个分支的内容合并到了当前 commit 中)。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/02.gif)
Merge Conflicts
如果不同的分支在相同的文件、相同的行上进行修改或者一个分支删除,一个分支修改同一个文件等操作时发生合并,就会产生冲突。比如在以下例子中,我们在两个分支(master、dev)分别编辑 README.md 文件:
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/03.png)
当把 dev 合并到 master 时,就会提示合并冲突,因为同时修改了 README.md 文件的第一行。此时,git 就会展示冲突的地方,我们可以手动的选择要保留的完成合并,然后再进行 add 以及 commit,提交我们合并之后的信息。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/04.gif)
Rebasing
git merge
可以将一个分支的内容合并到另一个分支,除此之外还有另外一个命令 git rebase
也可以做到。不同的是 git rebase
会将当前分支的提交信息追加到目标分支之后(不像 git merge
会产生一个新的 commit 信息)。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/05.gif)
与 git merge
相比,当遇到冲突的时候,git 并不会检查哪些文件需要保留,哪些不需要保留。要 rebase 的分支(这里的 master)总是保留最新的更改,这会有什么问题呢?就是如果 dev 上有冲突产生,那么总会保留 master 而丢弃 dev 上的更新(所以在 rebase 之前,切记保持分支代码的更新,避免冲突出现)。该命令的好处就是可以让 git 的提交历史更线性。
📚 Tips
对于一些大型项目,rebase 并不是很推荐。本文里对 rebase 的说明过于浅显,也就造成有些陷阱的存在,但是作为命令讲解已经足够了。所以,我会单独开一篇文章详细讨论一下 rebase!
Interactive Rebase
交互式的 rebase 可以让我们在 rebase 之前修改 commit 信息。以下有六种操作:
reword
修改 commit 信息edit
修正本次 commitsquash
将本次提交合并到上一次提交中fixup
将本次提交合并到上一次提交中,丢弃 commit 信息exec
在我们想要 rebase 的每次 commit 上执行一个命令drop
丢弃本次提交
以上命令可以使我们完全掌握我们的提交记录。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/06.gif)
git rebase -i HEAD~3
命令的意思是:以交互式(interactive)打开最近的 3 次提交记录。通过 vim
修改 commit 的前缀,这里是将提交记录 ec5bef2 的前缀改为 drop(即:删除当前提交)。随后的两个提交记录便会产生新的 hash 值。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/07.gif)
以上命令则是将 e45cb9e 合并到 ec5bef2 中.
Resetting
如果我们想要丢弃已经提交的信息,那么 git reset
就派上用场了。git reset
用于将 head 指针指向我们期望的提交记录。reset 又分为 soft reset
跟 hard reset
。
soft reset
软重置将 head 指向我们期望的 commit 记录,此 commit 之后提交的信息并不会被删除。
如下图,我们并不想保留添加了一个 style.css 文件的提交记录 9378i 以及添加了一个 index.js 文件的 035cc 记录,但是我们想保留这两个文件,那么我们就可以使用软重置。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/08.gif)
通过 git status
,我们仍然可以访问之前提交的所有信息,这样的好处就是我们可以修改这些内容以便之后再次提交。
hard reset
有时你可能并不想保留之前所做的修改,而是完完全全删除它们。那么请使用硬重置。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/09.gif)
如上图所示,9e78i 跟 035cc 两次提交的信息就完全被丢弃了。
Reverting
撤销提交的另一种方式是执行 git revert
。与 git rest
不同地方是,它会产生一个新的 commit,该 commit 中包含了我们要撤销的内容。
比如下图,我们在 ec5b3 提交中添加了一个 index.js 文件,随后我们要撤销该文件。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/10.gif)
从图中我们可以看到,产生了一个新的提交记录 9e78i,而该记录就是将 index.js 文件进行删除,以达到撤销 ec5b3 提交的目的。
📚 Tips
这里有个地方需要特别注意,如果我们把之前 revert 过的记录再合并回来是比较困难的。因为此次被 revert 的内容以 commit 的方式保留在提交记录中。所以,如果只是暂时性的撤销,那么推荐 reset。revert 带来的好处就是保留了撤销内容的 commit 信息。
Cherry-picking
如果只是想把部分 commit 而不是整个分支合并到目标分支,我们可以使用 cherry-pick
。
如下图,dev 分支的 76d12 提交中添加了一个 index.js 文件,以下操作只是把 76d12 合并到了 master 分支中。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/11.gif)
Fetching
如果远程分支已经被更新,我们只是想把它下载下来而不是合并到本地分支,可以使用 git fetch
命令。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/12.gif)
此时我们可以查看自上次 push 以来,该分支上所做的修改。同时我们注意到 head 指针的位置并没有发生变化,也就是当前下载下来的内容并没有合并到当前分支。如果想把更新的内容合并到当前分支,则需要执行 git merge
。
Pulling
git pull
命令可能是我们最常用的命令,该命令可以理解为两个命令的组合:git fetch
、git merge
。也就是将远程的更新合并到当前分支中,同时我们注意到,下图的 head 指针已经发生了变化。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/13.gif)
Reflog
git reflog
用于显示当前分支所做的所有操作,以便于我们进行回滚。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/14.gif)
通过 git reflog
显示当前分支最近的操作,假如我们想回滚到 HEAD@{1} 所对应的提交。我们可以执行 git reset HEAD@{1}
。然后在通过 git reflog
查看,刚刚我们执行的 reset 操作已经记录下来了。
%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/15.gif)
以上只是对常用的命令进行了简单介绍,通过动图的方式让大家理解每个命令的区别以及背后的原理。后续的文章会对相关的命令进行深层的探讨,以防止大家在使用中遇到各种各样的问题。