图解常用的 Git 指令含义

本文会介绍一些常用 Git 指令的图解说明。包括:

  • git merge 合并
  • git rebase 变基
  • git reset 重置
  • git revert 还原
  • git cherry-pick 检出提交
  • git fetch 获取
  • git pull 拉取
  • git reflog

合并(git merge)

当项目中包含多条功能分支时,有时就需要使用 git merge 命令,指定将某个分支的提交合并到当前分支。Git 中有两个合并策略:fast-forward 和 no-fast-forward。

fast-forward(--ff)

如果当前分支在合并分支前,没有做过额外提交。那么合并分支的过程不会产生的新的提交记录,而是直接将分支上的提交添加进来,这称为 fast-forward 合并。
图解常用的 Git 指令含义

现在 dev 分支上的修改已全部合并到主分支 master 上。那 no-fast-forward 又是什么呢?

no-fast-forward(--no-ff)

上面的场景很少遇到,基本是:在当前分支分离出子分支后,做了一些修改;而分离出的子分支也做了修改。这个时候再使用 git merge,就会触发 no-fast-forward 策略了。

在 no-fast-forward 策略下,Git 会在当前分支(active branch)额外创建一个新的 合并提交(merging commit)。这条提交记录既指向当前分支,又指向合并分支。

图解常用的 Git 指令含义

合并后,在当前主分支 master 上包含 dev 分支上的所有修改。

合并冲突

如果两个分支的修改存在冲突:比如说同时修改了某个文件的同一行;或者一个分支删除了文件,另一个分支则修改了文件——对于这种情况,Git 是无法自行决定合并策略的。这个时候,Git 就会把合并操作交给我们。举个例子,两个分支对同一个 README.md 文件做了修改。

图解常用的 Git 指令含义

如果此时将 dev 合并到 master,那么就存在合并冲突了:标题是使用 Hello! 还是 Hey! 呢?

当在主分支上执行 git merge 后,Git 会提示存在合并冲突,并把冲突的地方标记出来。我们手工处理完毕后,保存修改、添加文件、然后提交修改就可以了。

图解常用的 Git 指令含义

有一种情况或许很多新手都会遇到,在个人开发分支,提交了多次历史版本,等开发完往master合并的时候,又不想显示历史版本信息,这个怎么办呢?
使用 git merge <分支名> --squash 命令合并,squash直译为压缩多个commit。这样就不会显示分支的历史版本信息了。如果你没有操作master分支的权限,你可以先创建一个临时分支,使用这个命令将你个人的开发分支合并到临时分支,然后再将临时分支发起合并到master分支就行了。

变基(git rebase)

除了 git merge,还能使用 git rebase 来合并分支。

git rebase 指令会 复制 当前分支的所有最新提交,然后将这些提交添加到指定分支提交记录之上。

图解常用的 Git 指令含义

如图,dev 分支是从主分支上分离出去的(在 i8fe5 处),之后主分支与 dev 分支上都有相应的修改。执行 git rebase master 指令后,dev 分支将自己的最新提交记录复制出来(提交 hash 也发生了改变),拼在了主分支最后一次提交之上。这种合并分支的方式,会另 Git 提交历史看起来很清爽。

变基在开发功能(feature branch)分支时很有用——在开发功能时,主分支上可能也做了一些更新,我们可以将主分支上的最新更新通过变基合并到功能分支上来,这在未来在主分支上合并功能分支避免了冲突的发生。

交互式变基

git rebase 时,我们还能对当前分支上的提交记录做修改!采用 交互式变基(Interactive Rebase) 形式。

变基时提供了 6 种操作模式:

  • reword:修改提交信息
  • edit:修改此提交
  • squash:将当前提交合并到之前的提交中
  • fixup:将当前提交合并到之前的提交中,不保留提交日志消息
  • exec:在每一个需要变基的提交上执行一条命令
  • drop:删除提交

以 drop 为例:

图解常用的 Git 指令含义

以 squash 为例:

图解常用的 Git 指令含义

e45cb(+styles.css) 合并到 ec5be(+index.js) 提交后,两个提交重新 hash 出了 c4ec9(+styles.css、+index.js)这个提交记录。

重置(git reset)

如果因为某些原因(比如新提交导致了 BUG,或只是一个 WIP 提交),需要撤回提交,那么可以使用 git reset 指令。

git reset 可以控制当前分支回撤到某次提交时的状态。

软重置

执行软重置时,撤回到特定提交之后,已有的修改会保留。

以下图为例:9e78i 提交添加了 style.css 文件,035cc 提交添加了 index.js 文件。使用软重置,我们可以撤销提交记录,但是保留新建的 style.css 和 index.js 文件。

图解常用的 Git 指令含义

使用 git status 指令查看,发现新建的 style.css 和 index.js 的两个文件还在,不过对应的提交记录已经移除。这很好,我们可以对这些文件内容重新编辑,稍后再做提交。

硬重置

有时重置时,无需保留提交已有的修改,直接将当前分支的状态恢复到某个特定提交下,这种重置称为硬重置,需要注意的是,硬重置还会将当前工作目录(working directory)中的文件、已暂存文件(staged files)全部移除!

图解常用的 Git 指令含义

使用 git status 查看,发现当前操作空间空空如也。Git 丢弃了 9e78i 和 035cc 两次提交引入的修改,将仓库重置到 ec5be 时的状态。

还原(git revert)

另一种撤销更改的方式,是使用 git revert 指令。用于还原某次提交的修改,会创建一个包含已还原更改的 新提交记录!

举个例子,我们在 ec5be 上添加了 index.js 文件。之后发现并不需要这个文件。那么就可以使用 git revert ec5be 指令还原之前的更改。

图解常用的 Git 指令含义

新的提交记录 9e78i 还原了 ec5be 引入的更改。git revert 可以在不修改分支历史的前提下,还原某次提交引入的更改。

检出提交(git cherry-pick)

如果某个分支上的某次提交的修改正是当前分支需要的,那我们可以使用 cherry-pick 命令检出某次的提交更改作为新的提交添加到当前分支上面。(
git cherry-pick 只会合并进来某一次的提交,无需指定分支,指定commit的id即可,会自动识别分支)

举个例子(如下图所示):dev 分支上的 76d12 提交添加了 index.js 文件,我们需要将本次提交更改加入到 master 分支,那么就可以使用 git cherry-pick 76d12 单独检出这条记录修改。

图解常用的 Git 指令含义

现在 master 分支包含了 76d12 中引入的修改,并添加了一条提交记录 9e78i。

获取(git fetch)

假设,我们在一个有关联远程分支(比如:在 Github 上)的分支上工作,那么就要面临一个问题——你和你的同事都这个分支上工作,你的同事将他做的更改(比如一个 quick fix)提交到了远程分支上,而这些提交是你本地没有的。

此时,就要使用 git fetch 指令将远程分支上的最新的修改下载下来。

图解常用的 Git 指令含义

可以看见,git fetch 指令并没有影响本地分支。

拉取(git pull)

除了 git fetch,我们还能使用 git pull 获取远程分支数据。有什么不同呢?git pull 指令实际做了两件事:git fetch 和 git merge。

如下图所示:

图解常用的 Git 指令含义

译注:这里的图画的是有问题的——当前主分支并没有新的提交,因此 git merge 的结果是直接将远程分支上的提交添加到当前分支之后,而不是如图所示的产生一个合并提交。

Reflog(git reflog)

每个人都会犯错,举一个例子:假设你不小心使用 git reset 命令硬重置仓库到某个提交。后面突然想到,重置导致了一些已有的正常代码的误删!

git reflog 是一个非常有用的命令,用于显示所有已执行操作的日志!包括合并、重置、还原:基本上记录了对分支的任何更改。

图解常用的 Git 指令含义

如果你不幸犯错了,你可以使用 git reflog 的信息通过重置 HEAD 轻松地重做此操作!

假设,我们不想合并 origin/master 分支了。执行 git reflog 命令,我们看到合并之前的仓库状态位于 HEAD@{1} 这个地方,我们使用 git reset 指令将 HEAD 头指向 HEAD@{1}。

图解常用的 Git 指令含义

可以看见,最新的操作信息也已经记录到 reflog 中了!

参考
图解常用的 Git 指令含义