声明:本文译自Lydia Hallie在dev网站发表的文章CS Visualized: Useful Git Commands。文中的每个git命令都配以动画演示其工作过程,可以供大家直观学习git命令。
尽管 Git 是一个非常强大的工具,但同时对于初学者来说也很难。我一直觉得在脑海中想象使用 Git 时发生的事情非常有用:当我执行某个命令时,分支如何交互,它将如何影响历史记录?
以下配以生动的图画来演示在执行这些命令的时候发生了什么。
merge
拥有多个分支是很方便的,这样可以将不同的新修改互相隔离开,而且还能确保你不会意外地向生产代码推送未经许可或破损的代码修改.但一旦这些修改得到了批准许可,我们就需要将其部署到我们的生产分支中。
将更改从一个分支转移到另一个分支的一种方法是执行 "git merge" 。Git 可以执行两种类型的合并:no-fast-forward(快进) 或 fast-forward(非快进)。
Fast-forward (--ff)
在当前分支相比于我们要合并的分支没有额外的提交时,可以执行 fast-forward 合并。Git 很懒,首先会尝试执行最简单的选项:fast-forward!这类合并不会创建新的提交,而是会将我们正在合并的分支上的提交直接合并到当前分支。
No-fast-foward (--no-ff)
如果你的当前分支相比于你想要合并的分支没有任何提交,那当然很好,但很遗憾现实情况很少如此!如果我们在当前分支上提交我们想要合并的分支不具备的改变,那么 git 将会执行 no-fast-forward 合并。
使用 no-fast-forward 合并时,Git 会在当前活动分支上创建新的 merging commit .这个提交的父提交既指向这个活动分支,也指向我们想要合并的分支!
没什么大不了的,完美的合并!现在,我们在 dev 分支上所做的所有改变都合并到了 master 分支上. master 分支包含我们在 dev 分支上所做的所有改变.
Merge Conflicts
尽管 Git 能够很好地决定如何合并分支以及如何向文件添加修改,但它并不总是能完全自己做决定.当我们想要合并的两个分支的同一文件中的同一行代码上有不同的修改,或者一个分支删除了一个文件而另一个分支修改了这个文件时,Git 就不知道如何取舍了.
在这样的情况下,Git 会询问你想要保留哪种选择?假设在这两个分支中,我们都编辑了 README.md 的第一行.
如果我们想把 dev 合并到 master,就会出现一个合并冲突:你想要标题是 Hello! 还是 Hey!?
当尝试合并这些分支时,Git 会向你展示冲突出现的位置.我们可以手动移除我们不想保留的修改,保存这些修改,再次添加这个已修改的文件,然后提交这些修改.
Rebase
我们刚刚了解了如何通过执行 将更改从一个分支应用到另一个分支git merge。将更改从一个分支添加到另一个分支的另一种方法是执行git rebase。
A从当前分支git rebase 复制提交,并将这些复制的提交放在指定分支的顶部。
太好了,现在我们在master分支上所做的所有更改都已保存在dev分支上!🎊
与合并相比,一个很大的区别是 Git 不会尝试找出哪些文件要保留,哪些文件不保留。我们重新定基的分支始终具有我们想要保留的最新更改!这样您就不会遇到任何合并冲突,并保留良好的线性 Git 历史记录。
此示例显示了在master分支上进行rebase 。然而,在较大的项目中,您通常不想这样做。随着为复制的提交创建新的哈希值, Agit rebase 会更改项目的历史记录!
当你在功能分支上工作,并且主分支已更新时,rebase 非常有用。你可以获取分支上的所有更新,这将防止将来发生合并冲突!😄
Interactive Rebase
在rebase提交之前,我们可以修改它们!😃 我们可以使用Interactive rebase来实现这一点。Interactive rebase在你当前正在处理的分支上也很有用,并且想要修改一些提交。
我们可以对正在rebase的提交执行 6 种操作:
- reword:更改提交信息
- edit:修改此提交
- squash:将提交合并到上一次提交中
- fixup:将提交合并到前一个提交中,但不保留提交的日志消息
- exec:在我们要重新定基的每个提交上运行一个命令
- drop:删除提交
太棒了!这样,我们就可以完全控制我们的提交。如果我们想删除提交,我们可以直接drop删除。
或者如果我们想将多个提交压缩在一起以获得更清晰的历史记录,没问题!
Interactive Rebase使您可以对尝试变基的提交进行很多控制,即使在当前活动分支上也是如此!
Resetting
可能会发生我们提交了后来不想要的更改的情况。可能是一次WIP提交,也可能是一次引入错误的提交!在这种情况下,我们可以执行git reset.
Git reset 删除所有当前暂存的文件并让我们可以控制HEAD指向何处。
1、Soft reset
Soft reset移动到HEAD指定的提交(或与 相比的提交的索引HEAD),而不会摆脱之后提交中引入的更改!
假设我们不想保留9e78i添加style.css文件的提交,也不想保留035cc添加文件的提交index.js。但是,我们确实想保留新添加的文件!Soft reset的完美用例。
输入 时git status,你会看到我们仍然可以访问之前提交的所有更改。这很棒,因为这意味着我们可以修复这些文件的内容并在稍后再次提交它们!
1、Hard reset
有时,我们不想保留某些提交引入的更改。与Soft reset不同,我们不再需要访问它们。Git 应该简单地将其状态重置回指定提交时的状态:这甚至包括工作目录和暂存文件中的更改!💣
9e78iGit 已丢弃在和上引入的更改035cc,并将其状态重置为提交时的状态ec5be。
回退
撤消更改的另一种方法是执行git revert。通过还原某个提交,我们可以创建一个包含还原更改的新提交!
假设ec5be添加了一个index.js文件。后来,我们意识到我们不再需要这个提交带来的更改了!让我们恢复提交ec5be。
完美!提交9e78i撤销了提交引入的更改ec5be。执行此操作git revert非常有用,可以撤消某个提交,而无需修改分支的历史记录。
Cherry-picking
当某个分支包含一个提交,该提交引入了我们在活动分支上需要的更改时,我们可以执行cherry-pick该命令!通过cherry-picking 提交,我们在活动分支上创建一个新的提交,其中包含由 ed 提交引入的更改cherry-pick。
假设master分支上的提交76d12对我们dev分支中想要的index.js文件进行了更改。我们不需要全部,我们只关心这一个提交!
太棒了,主分支现在包含引入的更改76d12!
Fetching
如果我们有一个远程 Git 分支,例如 Github 上的分支,则可能会发生远程分支具有当前分支没有的提交的情况!也许另一个分支被合并了,你的同事推送了一个快速修复,等等。
我们可以在远程分支上执行 来在本地获取这些更改git fetch!它不会以任何方式影响您的本地分支:fetch只是下载新数据。
如果我们有一个远程 Git 分支,例如 Github 上的分支,则可能会发生远程分支具有当前分支没有的提交的情况!也许另一个分支被合并了,你的同事推送了一个快速修复,等等。
我们可以在远程分支上执行 来在本地获取这些更改git fetch!它不会以任何方式影响您的本地分支:fetch只是下载新数据。
Pull
尽管 git fetch 可用于获取某个分支的远程信息,但我们也可以执行 git pull.git pull 实际上是两个命令合成了一个:git fetch 和git merge.当我们从来源拉取修改时,我们首先是像 git fetch 那样取回所有数据,然后最新的修改会自动合并到本地分支中.
很好,我们现在与远程分支完美同步了,并且也有了所有最新的修改!
Reflog
每个人都会犯错,但犯错其实没啥!有时候你可能感觉你把 git repo 完全搞坏了,让你想完全删了了事.
git reflog 是一个非常有用的命令,可以展示已经执行过的所有动作的日志.包括合并、重置、还原,基本上包含你对你的分支所做的任何修改.
如果你犯了错,你可以根据 reflog 提供的信息通过重置 HEAD 来轻松地重做!
假设我们实际上并不需要合并原有分支.当我们执行 git reflog 命令时,我们可以看到这个 repo 的状态在合并前位于 HEAD@{1}.那我们就执行一次 git reset,将 HEAD 重新指向在 HEAD@{1} 的位置.
我们可以看到最新的动作已被推送给 reflog.
这个网站可以帮助大家可视化学习gity命令 learngitbranching