掌握强大的 Git 变基命令
Git 变基
![](/data/attachment/album/202212/07/133637yq2526zsp7f1t7a2.jpg)



> 
> 学习如何使用 Git 来压扁、变基和精选。
> 
> 
> 


当我与别人谈到 Git 时，几乎每个人都对 [git rebase 命令](https://opensource.com/article/20/4/git-rebase-i) 有强烈的印象，这个命令让许多人遇到了问题，而不得不更改目录、删除仓库、然后再重新克隆一个仓库。我认为这是因为他们误解了分支是如何工作，遇到了一个非常糟糕的默认界面，还有一些合并冲突把事情搞得一团糟。


### 怎么找不到 git squash 命令？


如果你曾在本地的仓库提交过很多次，并希望能把这些提交都合并为一个提交，接下来，我们就来介绍能用什么 Git 命令达到这个目的。Git 称这个概念为 “<ruby> 压扁提交 <rt>  squash commits </rt></ruby>”。我在编写文档时发现了这个概念：我花了十几个提交才修改好我的 Markdown 文档，但是仓库的维护者不想看到我的所有尝试，以免扰乱了该项目的历史，所以我被告知“需要压扁你的提交”。


**压扁提交**听起来是一个很有用的方法。但是只有一个问题：我不知道该怎么做。作为 Git 的新手，我做了任何人会做的事情：我去查阅 `git-squash` 的手册，但我立即遇到了阻碍：



```
$ man git-squash
> No manual entry for git-squash

```

我发现没有一个名为 `squash` 的 Git 命令，而是被要求 [运行一个完全独立的命令：git rebase 命令](https://opensource.com/article/22/4/manage-git-commits-rebase-i-command)，该命令能将我的所有提交最终合并为一个提交。


我知道我碰到一个常见的情形：已经使用工具一段时间的人使用了**行话**或引用了一个概念，这个概念对他们来说是非常清楚的，但对新手来说就不能明白了。从概念上讲，这个情况看起来是这样的：


![Image of 6 bowls of different colored spices, and an arrow pointing to the second image of all the spices blended into one bowl.](/data/attachment/album/202212/07/133811dxvzpbh3by43twxl.jpg)


我这样说是为了鼓励你，你绝对不是第一个或最后一个 *被 Git 或谈论 Git 的人* 弄糊涂的人。你可以要求对方说明白他的意见，并帮助你应该使用的正确命令。仓库的维护者实际上的意思是，“使用 `git rebase` 命令\*\*，将很多提交压扁成一个提交”。


### 现在就来学习 git rebase 命令吧


`git rebase` 命令会将一个提交链从其第一个父级中删除，并将其放置在另一个提交链的末尾，将两个提交链组合成一个长链，而不是两个并行链。我意识到这是一个很复杂的定义。


回想一下 Git 的提交是如何链接在一起的，你可以看到，除了初始的 `main`（或 `master`）分支外，任何分支都有一个 <ruby> 父提交 <rt>  parent commit </rt></ruby> 作为该链的 “<ruby> 基础 <rt>  base </rt></ruby>”。“<ruby> 变基 <rt>  rebase </rt></ruby>” 能使另一个链中的最后一个提交成为指定分支的新 “<ruby> 基础提交 <rt>  base commit </rt></ruby>”。


在 Git 中整合来自不同分支的修改主要有两种方法：<ruby> 合并 <rt>  merge </rt></ruby> 以及 <ruby> 变基 <rt>  rebase </rt></ruby>，你可能更熟悉 `git merge` 命令。接下来，就来看看 [[git-scm.com](http://git-scm.com)] 是如何解释 `git merge` 和 `git rebase` 的差异：


![Image of Git merge versus git rebase shown as numbered bubbles.](/data/attachment/album/202212/07/133643mjrq1jjq2ir1ssm7.png)


在合并示例中，它会把两个分支的最新快照（`C3` 和 `C4`）以及二者最近的共同祖先（`C2`）进行三方合并，合并的结果是生成一个新的快照（`C5`）。`experiment` 的分支指针仍然存在，仍然指向 `C4`。


在变基示例中，它提取在 `C4` 中引入的补丁和修改，然后在 `C3` 的基础上应用一次，使 `C3` 成为 `C4` 的新父级，并产生了一个名为 `C4'` 的新提交。


（LCTT 译注：具体的命令如下：



```
$ git checkout experiment
$ git rebase main
First, rewinding head to replay your work on top of it...
Applying: added staged command

```

它的原理是首先找到这两个分支 —— 即当前分支 `experiment`、变基操作的目标基底分支 `main` —— 的最近共同祖先 `C2`，然后对比当前分支相对于该祖先的历次提交，提取相应的修改并存为临时文件，然后将当前分支指向目标基底 `C3`，最后以此将之前另存为临时文件的修改依序应用。）


值得注意的是，分支指针 `main` 没有移动。要让 Git 将指针移动到链的末尾（由`experiment` 指向），你还需要执行合并。


（LCTT 译注：具体的命令如下：



```
$ git checkout main
$ git merge experiment

```

![master 分支的快进合并](/data/attachment/album/202212/07/133647lby1cjnc5nidid5c.png)


此时，`C4'` 指向的快照就和上面使用 `merge` 命令的例子中 `C5` 指向的快照一模一样了。）


`git rebase` 并不能替代 `git merge`。`git rebase` 是一种用于制作更清晰的历史记录，以与 `git merge` 结合使用的工具。


（LCTT 译注：使用 `git rebase` 命令将提交到某一分支上的所有修改都移至另一分支上，就好像“重新播放”一样。）


#### 交互式变基能给你一个更友好的界面！


从命令行执行 `git rebase` 命令，最可怕的地方在于它**糟糕的默认界面**。运行命令 `git rebase <target-refr>` 要么有效，要么会变得一团糟，因为它没有太多的反馈或方法来确保它做你想做的事情。幸运的是，`git rebase` 命令和许多其他 Git 命令一样，具有 <ruby> 交互模式 <rt>  interactive mode </rt></ruby>，你可以使用参数 `-i` 或者 `-interactive` 来使用交互模式。


![Image of the Git lens interactive Rebase tool in VS Code.](/data/attachment/album/202212/07/133647b5hathna7w25qnz5.png)


在使用交互式模式时，`git rebase` 会从一个糟糕的黑框界面转换为一个**选项菜单**，允许你选择对正在变基的提交链所做的事。对于每个提交，你可以**选择**：


* <ruby> 选用 <rt>  pick </rt></ruby>：按原样包含
* <ruby> 重写 <rt>  reword </rt></ruby>：重写提交消息
* <ruby> 编写 <rt>  edit </rt></ruby>：在变基完成之前对提交中的文件进行进一步更改
* <ruby> 压扁 <rt>  squash </rt></ruby>：将多个提交压缩成一个提交，保留所有提交消息
* <ruby> 修理 <rt>  fixup </rt></ruby>：将多个提交压缩成一个提交，但只保留最后一个提交消息
* <ruby> 丢弃 <rt>  drop </rt></ruby>：丢弃此提交


就我个人而言，我更喜欢 [VS Code 的开源 GitLens 扩展](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) 使用下拉选择列表布局选项的方式，但 Git 允许你使用任何编辑器选择这些选项。对于 Emacs 或 Vim 等纯文本工具，你需要键入选择，而不是从菜单中选择，但最终结果仍然是相同的。


#### 何时做变基


知道 *何时* 做变基与知道 *如何* 做变基同样重要。事实上，如果你不在乎你的仓库历史提交消息有点混乱的话，那么你可以永远都不使用 `git rebase` 命令。但是，如果你想要更干净的历史提交消息，并且想要更少扰乱你的图形视图的提交，那么当你使用 `git rebase` 命令时，有一个重要的**经验法则**需要时刻记住：



> 
> “不要变基你存储库以外的的提交，那些提交可能是别人工作的基础。”
> 
> 
> 


如果你遵循该准则，不会发生什么大问题的。


简而言之，如果你让一个**本地分支**来完成你的工作，变基是没有问题的。但一旦该分支被 <ruby> 推送 <rt>  push </rt></ruby> 了，就不要再变基该分支了。当然，你想要怎么做完全取决于你自己。


希望你会认为上述内容有助于你理解 `git rebase` 命令的工作原理，并能让你更有信心地使用它。与任何 Git 命令一样，**练习**是学习和理解怎么做的唯一方法。我鼓励你勇敢地尝试 <ruby> 交互式变基 <rt>  interactive rebase </rt></ruby> `git rebase -i <branch name>`！


### 接下来学习 Git cherry-pick 命令吧


大多数开发人员将修改提交到某一分支上，但是之后发现他们一直**提交到了错误的分支上**。理想情况下，他们可以拿走那个提交，然后把它移到正确的分支，这正是 `git cherry-pick` 命令的作用。


`git cherry-pick` 命令利用了变基单个提交的方法。这一用法非常常见，以至于有了它自己的命令。


![Image of a woman picking a cherry from one tree and putting on another tree.](/data/attachment/album/202212/07/133821chbpnu55pqp1pbbp.jpg)


要使用 `git cherry-pick`，你只需告诉 Git 你要移动到“那个分支”的提交 ID（由 `HEAD` 指向）：



```
$ git cherry-pick <target-ref>

```

如果出现问题，你可以根据 Git 提供的错误消息，来进行恢复：



```
$ git cherry-pick -i 2bc01cd
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
error: could not apply 2bc01cd… added EOF lines
hint: After resolving the conflicts, mark them with
hint: "git add/rm ", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
$ git cherry-pick --abort

```

### 让 Git 更强大


`git rebase` 命令是 Git 实用程序强大的地方之一。你最好在测试仓库中先练习一下怎么使用，一旦你熟悉了它的概念和工作流程，你就可以给仓库一个清晰历史消息记录了。




---


via: <https://opensource.com/article/22/11/advanced-git-commands>


作者：[Dwayne McDaniel](https://opensource.com/users/dwaynemcdaniel) 选题：[lkxed](https://github.com/lkxed) 译者：[chai001125](https://github.com/chai001125) 校对：[wxy](https://github.com/wxy)


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


OK
When I talk to people about Git, almost everyone has a strong reaction to [the git rebase command](https://opensource.com/article/20/4/git-rebase-i). This command has caused many people to change directory, remove the repository, and just re-clone to start over. I think this comes from misconceptions about how branching actually works, a pretty terrible default interface, and some merge conflicts mucking things up.

## Git squash

If you've ever made a lot of commits locally and wish there was a way to smash them all down into a single commit, you're in luck. Git calls this concept "squashing commits." I discovered the concept while working on documentation. It took me over a dozen commits to finally get a bit of markdown just right. The repo maintainer didn't want to see all my attempts cluttering up the project's history, so I was told to "just git squash your commits."

Squashing sounded like a solid plan. There was just one issue. I didn't know how to do it. As someone new to Git, I did what anyone would do. I consulted the manual for `squash`

and immediately hit a snag:

```
``````
$ man git-squash
> No manual entry for git-squash
```


It turns out I wasn't being told to run a Git command called `squash`

, I was being asked to [run an entirely separate command](https://opensource.com/article/22/4/manage-git-commits-rebase-i-command) that would, in the end, combine all my commits into one. This is a common scenario: someone who has been using a tool for a while uses jargon or refers to a concept, which to them is absolutely clear, but isn't obvious to someone new to the tech.

Conceptually it would look like this:

![Image of 6 bowls of different colored spices, and an arrow pointing to the second image of all the spices blended into one bowl.](https://opensource.com/sites/default/files/2022-11/gitbeyond2.spices.png)

Photos by [Dan Burton](https://unsplash.com/@dan__burton?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/indian-chef-cooks?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)

I'm laying it out this way to encourage you that you are definitely not the first or last person that would be confused by Git or someone talking about Git. It's OK to ask for clarification and for help finding the right documentation. What that docs maintainer actually meant was, "use Git rebase to squash the commits into one."


## Git rebase

The git rebase command removes a chain of commits away from its first parent and places it at the end of another chain of commits, combining both chains of commits into one long chain, instead of two parallel chains. I realize that's a dense statement.

If you think back to how Git commits are chained together, you can see that any branch aside from your initial `main`

branch has a parent commit that serves as the "base" of that chain. Rebasing is the act of making the last commit in another chain the new "base" commit for a specified branch.

You might already be more familiar with Git merge. Take a look at how the [git-scm.com](http://git-scm.com) site explains the difference:

![Image of Git merge versus git rebase shown as numbered bubbles.](https://opensource.com/sites/default/files/2022-11/gitbeyond2.gitmerger.png)

(Git-scm.com, CC BY-SA 3.0)

In this example merge, Git preserves the chain of commits shown in the image as C4, which has a parent of C2, when combining the changes in C3 to make a whole new commit, C5. The branch pointer for "experiment" still exists and still points at C4.

The rebase in this example shows a similar situation of C4 first existing as a separate branch with a parent of C2. But then, instead of merging with the code of C3, it makes C3 the new parent of C4, resulting in a new commit called C4. Notably, the branch pointer "main" has not moved yet. To make Git move the pointer to the end of the chain, currently pointed at by "experiment", you also need to perform a merge.

Rebase is not a replacement for merge. It's a tool for making cleaner histories to be used in conjunction with merge.

### Interactive rebase is your best friend!

One of the scariest parts of performing a rebase from the command line is the horrifying interface. Running the command `git rebase <target-refr>`

either works or blows up. There's not a lot of feedback or way to ensure it is doing what you precisely want. Fortunately, the rebase command and many other Git commands have an interactive mode, which you can invoke with the parameter `-i' or `

–interactive`.

![Image of the Git lens interactive Rebase tool in VS Code.](https://opensource.com/sites/default/files/2022-11/gitbeyond2.GitLens%20Interactive%20Rebase%20tool%20in%20VS%20Code.png)

(Dwayne McDaniel, CC BY-SA 4.0)

When invoking interactive mode, rebase transforms from a terrifying black box into a menu of options that let you do several things to the chain of commits you are rebasing. For every commit, you can choose to:

-
Pick: Include it as is

-
Reword: Rewrite the commit message

-
Edit: Make further changes to the files in the commit before the rebase finishes

-
Squash: Smash multiple commits into one commit, keeping all commit messages

-
Fixup: Smash multiple commits into one commit, but just keep the last commit message

-
Drop: Discard this commit


I personally like the way that the open source [GitLens extension for VS Code](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) lays out the options with dropdown picklists, but Git lets you assign these options using any editor. For text-only tools like Emacs or Vim, you need to type out the selection rather than pick from a menu, but the end result is still the same.

### When to rebase

Knowing *when* to rebase is as important as knowing *how* to rebase. In truth, if you don't care about your repos histories being a bit messy, then you might never perform a rebase. But if you do want to make cleaner histories and have fewer commits cluttering up your graph view, then there is one clear rule of thumb to always keep in mind:

"Do not rebase commits that exist outside your repository and that people may have based work on."


If you follow that guideline, you'll be fine.

Simply put, if you make a local branch to do your work, feel free to rebase it all you want. But as soon as that branch is pushed, do not rebase it. It is really up to you.

Hopefully you found this helpful in understanding how the git rebase command works and can use it with more confidence. As with any Git command, practice is the only real way to learn and understand what is going on. I encourage you to brave and experiment with interactive rebase!

## Git cherry-pick

Most developers have committed work only to realize they have been working on the wrong branch. Ideally, they could just pick up that one commit and move it over to the right branch. That is exactly what `git cherry-pick`

does.

Cherry-picking is the art of rebasing single commits. This was so common of a pattern that they gave it its own command.

![Image of a woman picking a cherry from one tree and putting on another tree.](https://opensource.com/sites/default/files/2022-11/gitbeyond2.cherrypicking.png)

(Crossroadsphotototeam, CC BY-SA 2.0)

To perform a cherry pick, you simply tell Git the ID of the commit you want to move to "here", where HEAD is pointing:

```
````$ git cherry-pick <target-ref>`


Should something go wrong, it's straightforward to recover, thanks to the error messages that Git provides:

```
``````
$ git cherry-pick -i 2bc01cd
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
error: could not apply 2bc01cd… added EOF lines
hint: After resolving the conflicts, mark them with
hint: "git add/rm ", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
$ git cherry-pick --abort
```


## Git more power

The `git rebase`

command is a powerful part of the Git utility. It's probably best to practice using it with a test repo before you have to use it under stress, but once you're familiar with its concepts and workflow, you can help provide a clear history of a repository's development.

## Comments are closed.