完美生活：git rebase -i
git 变基

> 
> 让大家觉得你一次就能写出完美的代码，并让你的补丁更容易审核和合并。
> 
> 
> 


![](/data/attachment/album/202005/18/185911fvwztwyp4lvbzkw4.jpg)


软件开发是混乱的。有很多错误的转折、有需要修复的错别字、有需要修正的错误、有需要稍后纠正的临时和粗陋的代码，还有在以后的开发过程中发现一次又一次的问题。有了版本控制，在创建“完美”的最终产品（即准备提交给上游的补丁）的过程中，你会有一个记录着每一个错误转折和修正的原始记录。就像电影中的花絮一样，它们会让人有点尴尬，有时也会让人觉得好笑。


如果你使用版本控制来定期保存你的工作线索，然后当你准备提交审核的东西时，又可以隐藏所有这些私人草稿工作，并只提交一份单一的、完美的补丁，那不是很好吗？`git rebase -i`，是重写历史记录的完美方法，可以让大家觉得你一次就写出了完美的代码！


### git rebase 的作用是什么？


如果你不熟悉 Git 的复杂性，这里简单介绍一下。在幕后，Git 将项目的不同版本与唯一标识符关联起来，这个标识符由父节点的唯一标识符的哈希以及新版本与其父节点的差异组成。这样就形成了一棵修订树，每个签出项目的人都会得到自己的副本。不同的人可以把项目往不同的方向发展，每个方向都可能从不同的分支点开始。


![Master branch vs. private branch](/data/attachment/album/202005/18/185954e6u9qgo89fm1iqus.png "Master branch vs. private branch")


*左边是 origin 版本库中的主分支，右边是你个人副本中的私有分支。*


有两种方法可以将你的工作与原始版本库中的主分支整合起来：一种是使用合并：`git merge`，另一种是使用变基：`git rebase`。它们的工作方式非常不同。


当你使用 `git merge` 时，会在主分支（`master`）上创建一个新的提交，其中包括所有来自原始位置（`origin`）的修改和所有本地的修改。如果有任何冲突（例如，如果别人修改了你也在修改的文件），则将这些冲突标记出来，并且你有机会在将这个“合并提交”提交到本地版本库之前解决这些冲突。当你将更改推送回父版本库时，所有的本地工作都会以分支的形式出现在 Git 版本库的其他用户面前。


但是 `git rebase` 的工作方式不同。它会回滚你的提交，并从主分支（`master`）的顶端再次重放这些提交。这导致了两个主要的变化。首先，由于你的提交现在从一个不同的父节点分支出来，它们的哈希值会被重新计算，并且任何克隆了你的版本库的人都可能得到该版本库的一个残破副本。第二，你没有“合并提交”，所以在将更改重放到主分支上时会识别出任何合并冲突，因此，你需要在进行<ruby> 变基 <rt>  rebase </rt></ruby>之前先修复它们。现在，当你现在推送你的修改时，你的工作不会出现在分支上，并且看起来像是你是在主分支的最新的提交上写入了所有的修改。


![Merge commits preserve history, and rebase rewrites history.](/data/attachment/album/202005/18/190001rh770g6a6r7hra0z.png "Merge commits preserve history, and rebase rewrites history.")


*合并提交（左）保留了历史，而变基（右）重写历史。*


然而，这两种方式都有一个缺点：在你准备好分享代码之前，每个人都可以看到你在本地处理问题时的所有涂鸦和编辑。这就是 `git rebase` 的 `--interactive`（或简写 `-i`）标志发挥作用的地方。


### git rebase -i 登场


`git rebase` 的最大优点是它可以重写历史。但是，为什么仅止于假装你从后面的点分支出来呢？有一种更进一步方法可以重写你是如何准备就绪这些代码的：`git rebase -i`，即交互式的 `git rebase`。


这个功能就是 Git 中的 “魔术时光机” 功能。这个标志允许你在做变基时对修订历史记录进行复杂的修改。你可以隐藏你的错误! 将许多小的修改合并到一个崭新的功能补丁中! 重新排列修改历史记录中的显示顺序！


![output of git rebase -i](/data/attachment/album/202005/18/190007un3ink2h5nyciz3p.png "output of git rebase -i")


当你运行 `git rebase -i` 时，你会进入一个编辑器会话，其中列出了所有正在被变基的提交，以及可以对其执行的操作的多个选项。默认的选择是选择（`Pick`）。


* `Pick`：会在你的历史记录中保留该提交。
* `Reword`：允许你修改提交信息，可能是修复一个错别字或添加其它注释。
* `Edit`：允许你在重放分支的过程中对提交进行修改。
* `Squash`：可以将多个提交合并为一个。
* 你可以通过在文件中移动来重新排序提交。


当你完成后，只需保存最终结果，变基操作就会执行。在你选择修改提交的每个阶段（无论是用 `reword`、`edit`、`squash` 还是发生冲突时），变基都会停止，并允许你在继续提交之前进行适当的修改。


上面这个例子的结果是 “One-liner bug fix” 和 “Integate new header everywhere” 被合并到一个提交中，而 “New header for docs website” 和 “D'oh - typo. Fixed” 合并到另一个提交中。就像变魔术一样，其他提交的工作还在你的分支中，但相关的提交已经从你的历史记录中消失了！


这使得使用 `git send-email` 或者用你新整理好的补丁集在父版本库中创建一个拉取请求，然后来提交一个干净的补丁给上游项目变得很容易。这有很多好处，包括让你的代码更容易审核，更容易接受，也更容易合并。




---


via: <https://opensource.com/article/20/4/git-rebase-i>


作者：[Dave Neary](https://opensource.com/users/dneary) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Software development is messy. So many wrong turns, typos to fix, quick hacks and kludges to correct later, off-by-one errors you find late in the process. With version control, you have a pristine record of every wrong turn and correction made during the process of creating the "perfect" final product—a patch ready to submit upstream. Like the outtakes from movies, they are a little embarrassing and sometimes amusing.

Wouldn't it be great if you could use version control to save your work regularly at waypoints, and then when you have something you are ready to submit for review, you could hide all of that private drafting work and just submit a single, perfect patch? Meet **git rebase -i**, the perfect way to rewrite history and make everyone think that you produce perfect code the first time!

## What does git rebase do?

In case you're not familiar with the intricacies of Git, here is a brief overview. Under the covers, Git associates different versions of your project with a unique identifier, which is made up of a hash of the parent node's unique identifier, and the difference between the new version and its parent node. This creates a tree of revisions, and each person who checks out the project gets their own copy. Different people can take the project in different directions, each starting from potentially different branch points.

![Master branch vs. private branch Master branch vs. private branch](https://opensource.com/sites/default/files/uploads/master-private-branches.png)

The master branch in the "origin" repo on the left and the private branch on your personal copy on the right.


There are two ways to integrate your work back with the master branch in the original repository: one is to use **git merge**, and the other is to use **git rebase**. They work in very different ways.

When you use **git merge**, a new commit is created on the master branch that includes all of the changes from origin plus all of your local changes. If there are any conflicts (for example, if someone else has changed a file you are also working with), these will be marked, and you have an opportunity to resolve the conflicts before committing this merge commit to your local repository. When you push your changes back to the parent repository, all of your local work will appear as a branch for other users of the Git repository.

But **git rebase** works differently. It rewinds your commits and replays those commits again from the tip of the master branch. This results in two main changes. First, since your commits are now branching off a different parent node, their hashes will be recalculated, and anyone who has cloned your repository may now have a broken copy of the repository. Second, you do not have a merge commit, so any merge conflicts are identified as your changes are being replayed onto the master branch, and you need to fix them before proceeding with the rebase. When you push your changes now, your work does not appear on a branch, and it looks as though you wrote all of your changes off the very latest commit to the master branch.

![Merge commits preserve history, and rebase rewrites history. Merge commits preserve history, and rebase rewrites history.](https://opensource.com/sites/default/files/uploads/merge-commit-vs-rebase.png)

Merge commits (left) preserve history, while rebase (right) rewrites history.

However, both of these options come with a downside: everyone can see all your scribbles and edits as you worked through problems locally before you were ready to share your code. This is where the **--interactive** (or **-i** for short) flag to **git rebase** comes into the picture.

## Introducing git rebase -i

The big advantage of **git rebase** is that it rewrites history. But why stop at just pretending you branched off a later point? There is a way to go even further and rewrite how you arrived at your ready-to-propose code: **git rebase -i**, an interactive **git rebase**.

This feature is the "magic time machine" function in Git. The flag allows you to make sophisticated changes to revision history while doing a rebase. You can hide your mistakes! Merge many small changes into one pristine feature patch! Reorder how things appear in revision history!

![output of git rebase -i output of git rebase -i](https://opensource.com/sites/default/files/uploads/git-rebase-i.png)

When you run **git rebase -i**, you get an editor session listing all of the commits that are being rebased and a number of options for what you can do to them. The default choice is **pick**.

**Pick**maintains the commit in your history.**Reword**allows you to change a commit message, perhaps to fix a typo or add additional commentary.**Edit**allows you to make changes to the commit while in the process of replaying the branch.**Squash**merges multiple commits into one.- You can reorder commits by moving them around in the file.

When you are finished, simply save the final result, and the rebase will execute. At each stage where you have chosen to modify a commit (either with **reword**, **edit**, **squash**, or when there is a conflict), the rebase stops and allows you to make the appropriate changes before continuing.

The example above results in "One-liner bug fix" and "Integrate new header everywhere" being merged into one commit, and "New header for docs website" and "D'oh - typo. Fixed" into another. Like magic, the work that went into the other commits is still there on your branch, but the associated commits have disappeared from your history!

This makes it easy to submit a clean patch to an upstream project using **git send-email** or by creating a pull request against the parent repository with your newly tidied up patchset. This has a number of advantages, including that it makes your code easier to review, easier to accept, and easier to merge.

## 5 Comments