- 原文地址: CS Visualized: Useful Git Commands
- 原文作者:Lydia Hallie
尽管Git
是一个非常强力的工具,但如果我说这也可能是一场噩梦 ,我想大多数人也会同意。在使用Git
的时候,我发现在我的大脑中想象发生了什么是非常有用的:在我执行特定命令的时候,分支之间是如何相互影响的,以及它会如何影响历史日志?当我在master
分支做了硬重置(hard reset
),强制推送(force push
)到master
分支并且rimraf
.git
目录之后,我的同事为什么会哭?
我认为为最常用和最有用的命令创建一些可视化的图例将会成为完美的使用示例! 我涉及到的许多命令拥有可选的参数,您可以使用这些参数来更改命令的行为。在示例中,我将会涉及没有添加(太多)配置项命令的默认行为!
Merging(合并)
拥有多个分支是极其方便的,他可以使新的更新彼此分离,也可以确保您不会意外的推送未经批准或破坏性更改的代码到生产环境。一旦更改被批准,我们便想在生产环境获得这些更改。
从一个分支获得另一个分支更改的一种方式是执行git merge
命令。Git
可以执行俩种类型的合并:
fast-forward
non-fast-forward
现在说这些可能没有太大意义,我们先看一下它们之间的区别。
Fast-forward(--ff
)
与我们正在合并的分支相比,当当前分支没有额外提交的时候,会发生fast-forward-merge
。Git
是懒惰的,它会首先尝试最简单的选项:fast-forward
!这种类型的合并不会创建一个新的提交,而是在当前分支上合并我们正在合并分支上的提交 。
完美!我们现在在master
分支上可以找到所有在dev
分支上做出的更改。那么,non-fast-forward
又是什么意思呢?
No-fast-forward(--no-ff
)
相比于您想要合并的分支,当前分支没有任何额外的提交是极好的,但不幸的是那是很罕见的情况!如果在当前分支上提交的更改在我们想要合并的分支上不存在,Git
将会执行一次no-fast-forward
合并。
随着一次no-fast-forward
合并,Git
在活动分支(master
)上创建一个新的合并提交。该提交的父提交同时指向活动分支和我们想要合并的分支(dev
)。
没什么大不了的,一次完美的合并! master
分支现在包含了所有我们在dev
分支上做出的所有更改。
Merge Conflicts(合并冲突)
尽管Git
擅长如何去合并分支以及向文件中添加更改,但是它不能总是依靠它自己来做出所有的决定 。当我们尝试合并的俩个分支在同一个文件的同一行上有不同的更改,或者如果一个分支删除了一个在另一个分支被编辑过的文件等情况发生的时候,Git
将不能自己决定该如何合并代码。
在这种情况下,Git
将会询问您来帮助决定我们想要保留俩个选项中的哪一个。比如说在俩个分支上,我们都编辑了README.MD
中的第一行。
如果您想把dev
合并到master
,这将会导致合并冲突:您希望标题是Hello!
还是Hey!
?
在尝试合并分支的时候,Git
将会为您显示冲突发生的位置。我们可以手动移除我们不想保留的更改,保存剩余的更改,再次添加文件到暂存区,然后提交所有更改的文件 。
好极了!尽管解决冲突十分烦人,但是它完全有意义:Git
不应该只是假设我们想要保留哪些更改。
Rebasing
我们刚刚已经看过如何通过执行一个git merge
命令,将来自于一个分支的更改应用到另一个分支。从一个分支添加更改到另一个分支的另一个方法是执行git rebase
命令。
git rebase
会从当前分支拷贝提交,并且将这些拷贝的提交放到指定分支的顶部。
完美,我们现在在dev
分支上可以找到所有在master
分支上做出的更改!
rebase
和merge
命令一个最大的不同是:Git
不会尝试去找出哪些文件要保留,哪些文件不需要保留。我们正在rebase
的分支总是拥有我们想要保留的最新更改!这种方式在之后合并过程中不会遇到任何冲突,并且可以保持一个很好的直线Git
历史记录。
“ 译者注:这里指的不会遇到任何冲突的情况如下(还是以上图为例):
- dev$
git rebase master
将dev
的提交拷贝一份复制到master
的顶部 - dev$
git checkout master
- master$
git merge dev
此时master
相当于没有做任何额外的提交,会进行fast-forward-merge
,保持直线提交记录
这个例子展示了在master
分支上进行rebase
。然而在更大的项目中,通常不希望这样做。由于拷贝的提交创建了新的哈希值,所以git rebase
改变了项目的历史记录。
无论何时您在一个特性分支上工作,并且master
分支已经被更新,rebase
都是一种很好的做法。您可以在自己的分支上获取到所有的更新,这将阻止未来的合并冲突!
交互式的 rebase
在rebase
提交之前,我们可以编辑它们! 我们可以使用一个交互式的rebase
来做这件事。交互式rebase
对于您当前正在工作的分支以及想要修改的某些提交也是很有用处的。
在我们正在rebase
的提交上有 6 个操作可以执行:
-
reword
: 更改提交信息 -
edit
: 修改这次提交 -
squash
: 合并提交到之前的提交,会用新的提交信息覆盖之前的提交信息 -
fixup
: 合并提交到之前的提交,不保留当前修改提交的日志消息 -
exec
: 在我们想要rebase
的每一个分支上运行一个命令 -
drop
: 移除一个提交
棒极了!通过这种方式,我们可以完全控制在分支上做出的提交。如果我们想要移除一个提交,我们只需要对它执行drop
命令。
或者如果您想将多个提交压缩到一起来获得一个干净的历史记录,完全没有问题!
交互式rebase
对于您正在尝试rebase
的提交甚至是当前活动分支都给予了极大的控制权!
Resetting
这个命会在已经提交的更改在之后不想要的时候会被用到。可能它是一个还在工作进度中的半成品提交,或者可能提交引入了bug
! 在这种情况,我们可以使用git reset
命令。
git reset
会删除当前暂存的所有文件,并且让我们控制HEAD
应该指向的位置。
Soft reset
soft reset
移动HEAD
到指定的提交(或者提交相对于HEAD
的索引),不会删除在提交之后引入的更改!
比如说我们不想保留添加了一个style.css
文件的提交9478i
,也不想保留添加了一个index.js
文件的提交035cc
。但是我们想要保留新添加的style.css
和index.js
文件!这是soft reset
一个完美的使用示例。
当输入git status
时,您将会发现我们仍然可以访问在之前的提交做出的所有更改。这很好,因为这意味着我们可以继续处理这些文件,并且在之后再次提交它们!
Hard reset
有些时候,我们不想保留通过特定分支引入的更改。不同于soft reset
,我们不再需要访问这些不想被保留的更改。Git
应该只是重置它的状态回到指定提交的位置:被重置的状态甚至包括您在工作目录以及暂存文件中做出的更改!
Git
已经丢弃了在9e78i
和035cc
上引入的更改,并且重置它的状态到提交ec5be
所在的位置
Reverting
撤销更改的另一个方式是执行git revert
命令。通过恢复一个特定的提交,我们会创建一个包含被恢复更改的新提交!
比如说ec5be
添加了一个index.js
文件。之后,我们真正的意识到我们不再想要这次提交引入的更改!让我们恢复ec5be
对应的提交。
完美!提交9e78i
恢复了ec5be
提交引入的更改。在不修改分支历史的情况下,为了撤销一次特定的提交,执行git revert
命令是特别有用的。
Cherry-picking
当一个特定分支中包含的一个提交,需要在我们的活动分支上引入它的更改时,我们可以使用cherry-pick
命令!对一次提交进行cherry-pick
,我们会在活动分支上创建一个新的提交,该提交会包含被cherry-pick
的提交引入的更改。
比如说在dev
分支上的提交76d12
为index.js
文件添加了一个改动,我们在master
分支上也想要拥有这次改动。我们不想要所有的提交,我们只关心这一个单独的提交!
很酷,master
分支现在包含了76d12
引入的更改!
Fetching
如果我们有一个远程Git
分支,比如在GitHub
上的一个分支,远程分支已经拥有了提交,但是当前分支却没有,这时会用到fetch
命令!可能是另一个分支被合并到了远程分支,也可能是您的同事推送了一个快速修复等等情况。
通过在远程分支上执行git fetch
命令,我们可以在本地获得这些更改!它不会以任何方式影响您的本地分支:fetch
只是下载了新的数据。
现在我们可以看到从最后一次推送之后做出的所有更改!现在我们在本地拥有了新数据,我们可以决定想要用新数据做什么。
Pulling
尽管为了获取一个分支的远程信息,git fetch
是及其有用的,但是我们也可以执行git pull
命令。git pull
实际上是将俩个命令合为了一个命令:git fetch
+ git merge
。当我们从origin
分支拉取更改的时候,首先会像执行git fetch
命令一样fetch
所有的数据,在这之后自动地将最新的更改合并到本地分支。
太好了,我们现在完美的同步了远程分支的数据,并且拥有了所有最新的更改!
Reflog
每个人都会犯错,这完全可以理解!有些时候您可能会感觉已经搞砸了您的Git
仓库,以至于您特别想整个删掉它。
git reflog
是一个非常有用的命令,目的是用来显示已经被采取的所有操作的日志!日志中包括merge
,reset
,rervert
:基本上会显示任何对您分支做出更改的操作日志。
如果您不小心犯了一个错误,您可以轻易的基于reflog
给我们提供的信息重置HEAD
到之前的位置,然后重做我们之前做错的事情!
比如说实际上我们不想合并origin
分支。当我们执行git reflog
命令时,将会看到在合并之前仓库的状态是在HEAD@{1}
。让我们执行git reset
将HEAD
指回到HEAD@{1}
的位置!
我们可以看到最新的操作已经被推到了reflog
中!
译者注:
到这里文章就结束了,笔者通过思维导图整理了一下对应的知识点,希望能对阅读文章的小伙伴有所帮助