Git常用指令及其可视化

Posted on 2020年4月2日

🌳🚀 CS Visualized: Useful Git Commands

Lydia Hallie

此为原作者,本文为中文翻译。
原文地址

Although Git is a very powerful tool, I think most people would agree when I say it can also be… a total nightmare 😐 I’ve always found it very useful to visualize in my head what’s happening when working with Git: how are the branches interacting when I perform a certain command, and how will it affect the history? Why did my coworker cry when I did a hard reset on master, force pushed to origin and rimraf’d the .git folder?

虽然 Git 是一个非常强大的工具,但是我认为,当我说 Git 也是一个巨大的噩梦的时候大家也都会默认我说的是对的。我认为当我在使用 Git 的时候,如果我脑中能可视化出具体发生了什么的话,我就不会觉得 Git 难用:比如当我敲一个指令的时候,各个分支会怎么做,整个提交的历史记录会发生什么变化?为什么当我强制 Reset 主线,强制推送到远程或者当我删除了 .git 文件夹的时候我的同事会问候我?

I thought it would be the perfect use case to create some visualized examples of the most common and useful commands! 🥳 Many of the commands I’m covering have optional arguments that you can use in order to change their behavior. In my examples, I’ll cover the default behavior of the commands without adding (too many) config options! 😄

我想如果我能举一些可视化的例子用来解释一些常用的、强大的指令的话,那简直不要太完美!以下介绍的指令中很多都带有可选的参数,你可以灵活运用他们以达到你的目的,但是我就没有介绍太多了,我还是以介绍默认参数为主!


Merging

Having multiple branches is extremely convenient to keep new changes separated from each other, and to make sure you don’t accidentally push unapproved or broken changes to production. Once the changes have been approved, we want to get these changes in our production branch!

在开发过程中,我们一般会针对一个新的需求建立一个分支,然后在这个新分支上开发,以免不同需求的开发相互影响,或者意外将垃圾代码提交到主线,导致产品出问题。而一旦这个需求开发完成了,我们需要将这个分支所做的修改合并到产品的主线去!

One way to get the changes from one branch to another is by performing a git merge! There are two types of merges Git can perform: a fast-forward, or a no-fast-forward 🐢

分支合并的一种方式就是使用 git merge!Git有两种方式来做合并:fast-forwardno-fast-forward

This may not make a lot of sense right now, so let’s look at the differences!

现在说这些可能没多大意义,所以简单说一下区别!

Fast-forward (--ff)

A fast-forward merge can happen when the current branch has no extra commits compared to the branch we’re merging. Git is… lazy and will first try to perform the easiest option: the fast-forward! This type of merge doesn’t create a new commit, but rather merges the commit(s) on the branch we’re merging right in the current branch 🥳

这种模式一般发生在主线没有其他提交记录,分支的提交紧跟当前主线 HEAD 后面(可以对比 no-ff 的情况),Git 有点懒,所以首选就是这种模式,就像下图所示,master 和 dev 之间找不同,直接把不同的地方合并到 master,然后 HEAD、master、dev 都指向最新的提交,因此这种合并并不会产生新的提交。

Perfect! We now have all the changes that were made on the dev branch available on the master branch. So, what’s the no-fast-forward all about?

完美,现在我们在分支上做的所有的修改在主线上就都可以看到了,那 no-ff 模式是什么呢?

No-fast-foward (--no-ff)

It’s great if your current branch doesn’t have any extra commits compared to the branch that you want to merge, but unfortunately that’s rarely the case! If we committed changes on the current branch that the branch we want to merge doesn’t have, git will perform a no-fast-forward merge.

你要合并的分支上没有额外的提交记录固然是好的,但这种情况基本不可能(除非项目只有你一个人开发),如果有这些额外的提交的话,git就会采用 no-ff 模式来合并分支

With a no-fast-forward merge, Git creates a new merging commit on the active branch. The commit’s parent commits point to both the active branch and the branch that we want to merge!

在 no-ff 模式的合并下,git会创建一个新的提交,然后将主线的HEAD和待合并的分支节点合并起来,这个最新的提交就在最新的主线基础上合并了分支的修改(当然很有可能需要处理冲突)!


No big deal, a perfect merge! 🎉 The master branch now contains all the changes that we’ve made on the dev branch.

但是多大点事,这不就又完美了么!现在主线又包含了所有分支做的修改。

Merge Conflicts

Although Git is good at deciding how to merge branches and add changes to files, it cannot always make this decision all by itself 🙂 This can happen when the two branches we’re trying to merge have changes on the same line in the same file, or if one branch deleted a file that another branch modified, and so on.

虽然 Git 可以轻松地合并分支,或者将代码的改动应用的对应的文件上,但是他也不是全能的。尤其当两个分支出现同一个文件中修改居然不相同,或者一个分支修改了文件,但另一个分支直接删除了这个文件,或者还有很多所谓的冲突时,Git 就懵逼了,到底改采用哪个分支的修改呢?

In that case, Git will ask you to help decide which of the two options we want to keep! Let’s say that on both branches, we edited the first line in the README.md.

这种情况下,Git 就会请求你去帮助他决定,到底哪边的修改需要保留,直到所有的冲突都被解决,才能成功合并代码!让我们来看下面这个例子:在两个分支下,我们都修改了这个 README 文件的第一行。


If we want to merge dev into master, this will end up in a merge conflict: would you like the title to be Hello! or Hey!?

如果我们想要将 dev 分支合并到 master,冲突就此产生:你到底想要这个标题是 Hey 还是 Hello?

When trying to merge the branches, Git will show you where the conflict happens. We can manually remove the changes we don’t want to keep, save the changes, add the changed file again, and commit the changes 🥳

当我们尝试合并分支时,Git 就会提示你哪里有冲突(如果有的话),这时候我们就需要手动去处理一个个冲突,留下那个你想要的修改,重新保存修改再提交。

在这里插入图片描述
Yay! Although merge conflicts are often quite annoying, it makes total sense: Git shouldn’t just assume which change we want to keep.

芜湖!虽然合并冲突使人暴躁,但这也是没办法的事情,Git 不能私自决定使用哪个修改,贿赂是没有用的。


Rebasing

We just saw how we could apply changes from one branch to another by performing a git merge. Another way of adding changes from one branch to another is by performing a git rebase.

刚才说了分支合并的一种方式就是使用 git merge,当然还有另一种方式 git rebase

A git rebase copies the commits from the current branch, and puts these copied commits on top of the specified branch.

git rebase 会将当前分支上所有的提交记录都复制到另一个分支上,他不会创建新的提交节点,比如一个新的合并节点之类(no-ff 做的那样),但是复制过去的提交记录会拥有新的hash值,也就是说,合并过去的 commit id 会和原来的不同。


Perfect, we now have all the changes that were made on the master branch available on the dev branch! 🎊

完美,现在 master 上新做的修改也可以在 dev 上看到啦!

A big difference compared to merging, is that Git won’t try to find out which files to keep and not keep. The branch that we’re rebasing always has the latest changes that we want to keep! You won’t run into any merging conflicts this way, and keeps a nice linear Git history.

merge 相比,Git 不再纠结如何处理冲突,我们正在 rebase 的分支就是最新的修改,你不会陷入各种冲突合并当中,并且提交历史将会是漂亮的直线(存疑,实测 rebase 也需要手动处理冲突,相对于 mergerebase 即所谓的变基地址,上图中 dev 以 i8fe5 为基地址新增修改,变基后改为 ec5be,这使得提交记录将显示为一条直线,显然 merge 产生的提交记录将会出现交叉合并线,两种方式都合并了修改,但 rebase 操作改变了实际的 commit 时间线,但它使得提交记录更容易查看,而 merge 操作完全记录了原始的 commit 时间线,但提交记录可能看起来很复杂)!

This example shows rebasing on the master branch. In bigger projects, however, you usually don’t want to do that. A git rebase changes the history of the project as new hashes are created for the copied commits!

这个例子展示了变基到 master 的过程,然而在大的项目中一般不会这样做,因为 reabse 操作将会改变当前项目的提交历史,并且那些变基的提交将会使用完全不同的 commit id!

Rebasing is great whenever you’re working on a feature branch, and the master branch has been updated. You can get all the updates on your branch, which would prevent future merging conflicts! 😄

当你在开发feather 分支,并且主线分支又出现更新的时候,变基操作则非常有用,你能在你的分支上获得全部更新,并免于冲突合并的困扰(存疑同上)!

Interactive Rebase

Before rebasing the commits, we can modify them! 😃 We can do so with an interactive rebase. An interactive rebase can also be useful on the branch you’re currently working on, and want to modify some commits.

在变基之前,我们甚至可以对将要变基的提交进行修改!我们可以通过交互式变基来完成这件事。当我们需要修改当前分支的提交记录时,交互式变基也非常有用。

There are 6 actions we can perform on the commits we’re rebasing:

以下是我们在对 commit 进行变基时能做的 6 个操作

  • reword: Change the commit message 修改 commit 的说明
  • edit: Amend this commit 修改本次 commit
  • squash: Meld commit into the previous commit 压缩本次 commit 到上一次 commit 中,两次 commit 说明叠加,也可以修改
  • fixup: Meld commit into the previous commit, without keeping the commit’s log message 压缩 commit,但不保留本次 commit 的说明,仅保留上次的
  • exec: Run a command on each commit we want to rebase 对需要 rebase 的所有 commit 执行一条指令
  • drop: Remove the commit 去掉该条 commit 记录(若去掉一系列提交中间的某个 commit,可能会出现冲突,需要手动处理)

Awesome! This way, we can have full control over our commits. If we want to remove a commit, we can just drop it.

绝了,这样我们就可以完全控制我们所有的 commit了。如果我们想要删除某次提交,我们就可以直接使用 drop,像下图一样。

在这里插入图片描述
Or if we want to squash multiple commits together to get a cleaner history, no problem!

或者如果我们想要将多个 commit 压缩到一起,以获得一个干净的历史提交,也没问题!像下图一样操作。

在这里插入图片描述

Interactive rebasing gives you a lot of control over the commits you’re trying to rebase, even on the current active branch!

交互式变基让我们可以完全控制需要调整的这些 commit,甚至在当前分支上进行调整也没问题!


Resetting

It can happen that we committed changes that we didn’t want later on. Maybe it’s a WIP commit, or maybe a commit that introduced bugs! 🐛 In that case, we can perform a git reset.

reset 可以帮助我们处理很多我们之后不会再想要的提交。可能是工作中提交的代码(Working In Progress),或某段有代码的 bug!这种情况下,我们可以使用 git reset

A git reset gets rid of all the current staged files and gives us control over where HEAD should point to.

git reset 会清理所有目前暂存的修改,如果后面可能还会需要的话,一定要 commit 再 reset!并且控制我们的 HEAD 移动到任何地方。

Soft reset

A soft reset moves HEAD to the specified commit (or the index of the commit compared to HEAD), without getting rid of the changes that were introduced on the commits afterward!

soft reset 将 HEAD 移动到一个指定的 commit 上,如 ec5be(或者某个相对于 HEAD 回退 n 步的 commit 上,如 HEAD~2,两者等价),但是并不会清除后续的 commit 所代表的修改,比如下图中的 035cc9e78i 仍然存在,HEAD 依然可以回到这些 commit 上,别担心!

Let’s say that we don’t want to keep the commit 9e78i which added a style.css file, and we also don’t want to keep the commit 035cc which added an index.js file. However, we do want to keep the newly added style.css and index.js file! A perfect use case for a soft reset.

这就好比,我们并不需要后面这俩 commits,一个添加了css文件,一个添加了js文件,但是我们又需要保留这两个源文件,这种时候我们就需要使用 soft reset


When typing git status, you’ll see that we still have access to all the changes that were made on the previous commits. This is great, as this means that we can fix the contents of these files and commit them again later on!

这时候当我们再使用 git status,你会发现我们之前退回的提交所修改的内容又出现了。这太棒了,因为这意味着我们仍然可以保存这些修改,然后重新做一个更正式的提交!

Hard reset

Sometimes, we don’t want to keep the changes that were introduced by certain commits. Unlike a soft reset, we shouldn’t need to have access to them any more. Git should simply reset its state back to where it was on the specified commit: this even includes the changes in your working directory and staged files! 💣

但有些时候,我们既不想要提交记录,也不想要保留这些修改,我只想完全恢复到某个节点的状态。和 soft reset 不同的是,我们不再需要这些 commits 了。Git 会直接回到某个指定的 commit,所有的修改都会回到那个状态,注意,所有没有暂存的修改将会永远被删除,无法找到!

Alt Text
Git has discarded the changes that were introduced on 9e78i and 035cc, and reset its state to where it was on commit ec5be.

Git 已经完全不管 9e78i035cc 所引入的修改,目前的所有文件都恢复到了 ec5be 时的状态了。


Reverting

Another way of undoing changes is by performing a git revert. By reverting a certain commit, we create a new commit that contains the reverted changes!

另一种撤回修改的方式是使用 git revert,通过恢复某个指定的 commit,Git 将会在 HEAD 后创建一个新的 commit,这个提交中将会包含所有撤回需要做的修改!

Let’s say that ec5be added an index.js file. Later on, we actually realize we didn’t want this change introduced by this commit anymore! Let’s revert the ec5be commit.

比方说 ec5be 增加了一个 js 文件,然后我们意识到我们并不需要引入这个修改了,领导还是觉得第一个版本最好!我直接一个 revert 回到 ec5be

Alt Text
Perfect! Commit 9e78i reverted the changes that were introduced by the ec5be commit. Performing a git revert is very useful in order to undo a certain commit, without modifying the history of the branch.

完美!9e78i 中退回了 ec5be 所引入的所有修改。当我们需要撤回某个提交中包含的修改时,我们可以使用 revert,这个指令不会修改任何提交历史,他只会在后面增加一个节点去表示我撤回了什么内容。


Cherry-picking

When a certain branch contains a commit that introduced changes we need on our active branch, we can cherry-pick that command! By cherry-picking a commit, we create a new commit on our active branch that contains the changes that were introduced by the cherry-picked commit.

有时候会出现这样一种情况,同事在某个分支上开发的修改对我来说很有用,我想单纯地借鉴这个提交,并且一不小心合并到我现在的分支上,我们就可以用 cherry-pick 来实现!这简直是程序员的福音,只需要使用这个指令,指定一个 commit,Git 就会将这个提交所做的修改引入到当前的分支上,然后将这些修改重新做一个提交。

Say that commit 76d12 on the dev branch added a change to the index.js file that we want in our master branch. We don’t want the entire we just care about this one single commit!

还是这个 js 文件,同事在 dev 分支上提交了,我们直接挑选出这个修改,合并到主线上了!

Alt Text

Cool, the master branch now contains the changes that 76d12 introduced!

泰裤辣,现在主线已经包含了 76d12 所做的修改了,注意这是一个新的提交,拥有新的 commit id 哦!


Fetching

If we have a remote Git branch, for example a branch on Github, it can happen that the remote branch has commits that the current branch doesn’t have! Maybe another branch got merged, your colleague pushed a quick fix, and so on.

如果我们在 Github 上(或者其他部署了 Git 服务器的远端)管理一个仓库,那就意味着本地和远程之间可能会存在不同步的情况,远程很可能有别人提交了新的修改,而我们本地没有!比如某个分支被合并了,或者某个同事悄悄修改了一个bug等。

We can get these changes locally, by performing a git fetch on the remote branch! It doesn’t affect your local branch in any way: a fetch simply downloads new data.

这时候我们就需要使用 git fetch 来将这些远程的修改同步到本地,但是它仅仅下载新的修改,并不会修改你当前的 HEAD 节点,也就是说,文件夹里的代码还是原来的样子,并不会直接修改他们到最新的状态。

Alt Text

We can now see all the changes that have been made since we last pushed! We can decide what we want to do with the new data now that we have it locally.

我们现在可以看到,上次我们推动到远程的修改已经下载到本地了!我们后面可以自行决定如何利用这些修改。


Pulling

Although a git fetch is very useful in order to get the remote information of a branch, we can also perform a git pull. A git pull is actually two commands in one: a git fetch, and a git merge. When we’re pulling changes from the origin, we’re first fetching all the data like we did with a git fetch, after which the latest changes are automatically merged into the local branch.

你可能奇怪,为什么同步下来修改,还要我自行决定怎么利用,直接本地代码一并更新不就好了,别急,git pull 来帮您,这条指令其实就是做了两件事情,一个是 fetch 下载修改到本地,一个是 merge 合并这些修改到最新的分支上,当我们从远程拉取修改的时候,Git 首先会从远程下载修改的数据到本地,然后 Git 会自动合并这些修改到当前的分支上。

在这里插入图片描述

Awesome, we’re now perfectly in sync with the remote branch and have all the latest changes! 🤩

牛逼克拉斯,现在我们的本地和远程已经同步了!不用担心少了什么代码了!


Reflog

Everyone makes mistakes, and that’s totally okay! Sometimes it may feel like you’ve screwed up your git repo so badly that you just want to delete it entirely.

每个人都会犯错,这当然是没问题的!但有些时候你可能会觉得 Git 仓库被搞得乱七八糟的,真的想删库跑路。

git reflog is a very useful command in order to show a log of all the actions that have been taken! This includes merges, resets, reverts: basically any alteration to your branch.

reflog 是一个非常有用的指令,它可以打印出本地仓库所做的所有事情,比如合并分支、复位节点、恢复节点等任何分支上的改动。

Alt Text
If you made a mistake, you can easily redo this by resetting HEAD based on the information that reflog gives us!

如果你不小心搞错了,你可以通过这个指令显示的信息,结合之前的指令,轻易地将仓库恢复到某个节点!

Say that we actually didn’t want to merge the origin branch. When we execute the git reflog command, we see that the state of the repo before the merge is at HEAD@{1}. Let’s perform a git reset to point HEAD back to where it was on HEAD@{1}!

多说无用,看下面这个例子,假如我们不小心在主线上合并了最新的修改,但这完全是意外,是我家的猫干的!别急着狡辩,通过 reflog 打印各个节点记录,我们可以看到合并修改之前的记录HEAD@{1} 或者 035cc 也可以,然后使用 reset 恢复到之前的状态,至于是使用 soft 还是 hard 就看你了,反正所有的修改仍然会被记录在案,不信再次用 reflog 看看!

在这里插入图片描述

We can see that the latest action has been pushed to the reflog!

看吧,哪怕你偷偷撤回了,Git 也会诚实的记录你做的好事!


Git has so many useful porcelain and plumbing commands, I wish I could cover them all! 😄 I know there are many other commands or alterations that I didn’t have time for to cover right now - let me know what your favorite/most useful commands are, and I may cover them in another post!

一键三连,马上更新!

And as always, feel free to reach out to me! 😊

有问题随时私信(原作者)!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值