Merge
多分支模式可以将每个人所做的新的更改分离开来,极大地方便了项目管理,避免了未经评审或坏的更改进入项目。一旦评审通过,则可以将这些更改合并到项目中。
将一个分支的更改添加到另一个分支的一种方式是 git merge。Git 有两种合并的执行方式,分别是 fast-forward 和 no-fast-forward。
现在你可能还不能理解这些,所以让我们一起来看看它们的差异。
Fast-forward (--ff)
如果当前分支与即将合并的分支之间没有额外的提交,将采用 fast-forward 的合并方式。Git 很懒,所以默认使用 fast-forward 方式处理分支合并。这种合并方式不会创建新的提交,而是直接在当前分支中合并提交,且当旧分支被移除后,其分支信息也会被一并删除。
完美!现在 dev 分支上的所有更改都被合并到 master 分支。那么,no-fast-forward 有什么不同呢?
No-fast-foward (--no-ff)
如果当前分支与即将合并的分支之间没有额外的提交,那是相当的好。然而,这种情况很少。如果当前分支有待合并分支上所没有的提交更改,Git 将使用 no-fast-forward 合并方式。
执行 no-fast-forward 合并,Git 将在活动分支上创建一个新的提交,这个新的提交会同时指向活动分支和我们要合并的分支。
没什么大不了的,又是一个完美的合并!现在 master 分支已经包含了 dev 分支的所有更改了。
Merge Conflicts
尽管 Git 擅长决定如何合并分支并向文件中添加更改,但也不是所有情况都能作出决定。比如我们想合并的两个分支都修改了同一个文件的相同行,或者一个分支删除了文件,但另一个分支却修改这个文件。
这种情况,Git 会询问你来决定应该保留哪个修改。举个例子,假如我们在两个分支中同时修改了 README.md 文件中的第一行。
现在你想将 dev 分支合并到 master 分支,将会出现合并冲突:would you like the title to be Hello! or Hey!?
Git 会找出冲突发生的位置,为了解决合并冲突,我们需要手动删除不想保留的更改,保存并重新添加文件,然后再提交更改。
耶!尽管解决合并冲突很烦人,但这是有意义的,因为 Git 不会帮我们去假设需要保留哪些更改。
Rebase
我们知道使用 git merge 可以将一个分支的更改添加到另一个分支。除了这种方式,我们还可以使用 git rebase 命令来完成这个工作。
git rebase 命令将复制当前分支的所有提交,并将它们移动到指定分支上。
完美!我们现在可以在 dev 分支上使用 master 分支上进行的所有更改了。
与 merge 最大不同的是,rebase 总是保留当前分支的最新更改,不会尝试寻找哪些文件需要保留,哪些不需要。因此你不需要处理合并冲突,同时也会保持线性的 git 提交历史。
上面的例子展示了在 rebase master 分支的动作,但这里有一个大问题,导致我们通常不想这么处理。因为 git rebase 复制提交时会产生新的哈希值,从而影响了项目的提交历史。
当你在 feature 分支上工作时,并且 master 分支已经更新了,那么使用 rebase 是非常合适的。因为你可以在分支上获取所有更新,防止将来合并时发生冲突。
交互式 Rebase
在 rebase 提交之前,我们还可以通过交互式 rebase 对提交进行修改。当你想修改正在使用的分支上的某些提交时,交互式 rebase 就非常有用。
我们可以对 rebase 的提交进行六种操作:
-
reword: 修改提交附带消息 -
edit: 修改提交 -
squash: 将提交合并到上一个提交中 -
fixup: 将提交合并到上一个提交中,并且不保留提交的日志消息 -
exec: 在我们 rebase 的每一个提交的基础上运行命令 -
drop: 删除提交
太棒了!这样,我们就可以完全控制提交。如果想删除提交,就可以 drop 它。
或者,我们要压缩多个提交以获得更清晰的历史记录,那也没问题!
使用交互式 rebase,你可以对要 rebase 的提交有更多控制,即使是在当前活动分支。
Reset
有时候我们提交了更改,但是因为一些原因不想要了。比如这是一个 WIP(Work In Process)提交,或者这个提交有 Bug。那么,我们就可以使用 git reset 对分支进行重置。
git reset 会删除所有当前暂存的文件,并让我们控制 HEAD 指向的位置。
Soft reset
soft reset 将 HEAD 移动到指定的提交,不会丢弃该提交之后引入的更改。
假如我们不想保留添加了 styles.css 文件的 9e78i 提交,也不想保留添加 index.js 文件的 035cc 提交。但是,我们想保留 styles.css 和 index.js 文件,那么使用 soft reset 就非常合适。
执行 git status 可以看到我们仍然可以访问先前提交所做的更改,即 styles.css 和 index.js 文件。这很棒,意味着我们可以修复这些文件的内容,后续再次提交。
Hard reset
有时候,我们不想保留某个提交引入的更改,不像 soft reset,我们不再需要访问它们了。Git 允许将其 reset 到指定的提交状态,包括工作目录和暂存文件都会被重置。
比如这里 Git 丢弃了 9e78i 和 035cc 提交引入的更改,并将其状态重置为 ec5be 提交时的状态。
Revert
撤销更改的另一种方法是执行 git revert。通过还原某个提交,我们创建一个包含还原更改的新提交。
假如 ec5be 添加了 index.js 文件,后来我们意识到我们不再希望此提交引入此更改!于是,我们需要还原 ec5bc 提交。
完美!9e78i 提交还原了 ec5be 提交引入的更改。执行 git revert 对于撤销特定提交而不修改分支的历史记录非常有用。
Cherry-Pick
当某个分支中的提交包含了当前活动分支需要更改时,可以使用 cherry-pick 命令。cherry-pick 一个提交时,会在活动分支上创建一个新的提交,其中包含了 cherry-pick 提交引入的更改。
假如 dev 分支的 76d12 提交在 index.js 文件添加了更改,而 master 分支刚好需要这些更改。但是 master 关心的只是这个提交,并不关心整个 dev 分支。
酷!现在 master 分支就包含了 76d12 提交的更改了。
Fetch
如果我么有一个远程 Git 分支,例如 GitHub 上的一个分支,则可能该远程分支具有当前分支所没有的提交!这也许是由于另一个分支被合并到远程分支了,或者你的小伙伴提出了一个解决方案。
我们可以通过 git fetch 将远程分支的更改拉取到本地。fetch 操作只会下载新数据,并不会影响本地分支。
现在,我们可以看到自上次推送以来远程分支上所做的更改。本地已经拥有新数据了,接下来我们可以决定要如何处理这些数据。
Pull
尽管 git fetch 对于获取分支的远程信息非常有用,但是我们也可以执行 git pull。git pull 实际上是 git fetch 和 git merge 两个命令的组合。当我们从远程分支 pull 更改时,实际上首先需要像 git fetch 那样拉取数据,之后将最新的更改自动合并到本地分支中。
太棒了!我们现在可以与远程分支完美同步,并具有所有最新的更改!
Reflog
每个人都会犯错,这是 OK 的!有时候,你可能觉得自己已经把 git 仓库搞砸了,以致于只想完全删除它。
git reflog 是一个非常有用的命令,用于显示所有已执行动作的日志。包括 merge、reset、revert 等等对分支的任何更改操作。
如果你输入有误,可以先重置 HEAD,然后再根据 reflog 的信息轻松地重做。
假如,我们不想合并 origin 分支。我们执行 git reflog 命令,看到合并前的状态是 [email protected]{1},于是我们通过 git reset 将 HEAD 指向 [email protected]{1}。
可以看到,最新的操作已经被记录到日志 ????