你什么时候使用 Git rebase 而不是 Git merge?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/804115/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 06:23:53  来源:igfitidea点击:

When do you use Git rebase instead of Git merge?

gitversion-controlgit-mergegit-rebase

提问by Coocoo4Cocoa

When is it recommended to use Git rebase vs. Git merge?

什么时候推荐使用 Git rebase 和 Git merge?

Do I still need to merge after a successful rebase?

成功 rebase 后我还需要合并吗?

回答by Rob Di Marco

Short Version

精简版

  • Merge takes all the changes in one branch and merges them into another branch in one commit.
  • Rebase says I want the point at which I branched to move to a new starting point
  • 合并获取一个分支中的所有更改,并在一次提交中将它们合并到另一个分支中。
  • Rebase 说我希望我分支的点移动到一个新的起点

So when do you use either one?

那么你什么时候使用任何一种呢?

Merge

合并

  • Let's say you have created a branch for the purpose of developing a single feature. When you want to bring those changes back to master, you probably want merge(you don't care about maintaining all of the interim commits).
  • 假设您为了开发单个功能而创建了一个分支。当您想将这些更改带回 master 时,您可能需要合并(您不关心维护所有临时提交)。

Rebase

变基

  • A second scenario would be if you started doing some development and then another developer made an unrelated change. You probably want to pull and then rebaseto base your changes from the current version from the repository.
  • 第二种情况是,如果您开始进行一些开发,然后另一个开发人员进行了不相关的更改。你可能想拉,然后重订到从资源库中的当前版本为基础修改。

回答by Aldo 'xoen' Giambelluca

It's simple. With rebase you say to use another branch as the new basefor your work.

这很简单。使用 rebase 你说使用另一个分支作为你工作的新基础

If you have, for example, a branch master, you create a branch to implement a new feature, and say you name it cool-feature, of course the master branch is the base for your new feature.

例如,如果你有一个分支master,你创建一个分支来实现一个新功能,并说你给它命名cool-feature,当然主分支是你新功能的基础。

Now at a certain point you want to add the new feature you implemented in the masterbranch. You could just switch to masterand merge the cool-featurebranch:

现在,您希望添加在master分支中实现的新功能。您可以切换到master并合并cool-feature分支:

$ git checkout master
$ git merge cool-feature

But this way a new dummy commit is added. If you want to avoid spaghetti-history you can rebase:

但是这样会添加一个新的虚拟提交。如果你想避免意大利面的历史,你可以rebase

$ git checkout cool-feature
$ git rebase master

And then merge it in master:

然后将其合并master

$ git checkout master
$ git merge cool-feature

This time, since the topic branch has the same commits of master plus the commits with the new feature, the merge will be just a fast-forward.

这一次,由于主题分支具有相同的 master 提交加上具有新功能的提交,因此合并将只是一个快进。

回答by VonC

To complement my own answermentioned by TSamper,

为了补充TSamper提到我自己的答案

  • a rebase is quite often a good idea to do before a merge, because the idea is that you integrate in your branch Ythe work of the branch Bupon which you will merge.
    But again, before merging, you resolve any conflict in yourbranch (i.e.: "rebase", as in "replay my work in my branch starting from a recent point from the branch B).
    If done correctly, the subsequent merge from your branch to branch Bcan be fast-forward.

  • a merge directly impacts the destination branch B, which means the merges better be trivial, otherwise that branch Bcan be long to get back to a stable state (time for you solve all the conflicts)

  • 在合并之前,rebase 通常是一个好主意,因为这个想法是您将要合并的分支Y的工作集成到您的分支中B
    但同样,合并之前,你解决任何冲突分支(即:“重订”,如“重播我在我的分支工作从分支最近的点开始B)。
    如果做得正确的话,以后的合并从你的分支分支B可以快进。

  • 合并直接影响目标分支B,这意味着合并最好是微不足道的,否则该分支B可能很长才能恢复到稳定状态(是时候解决所有冲突了)



the point of merging after a rebase?

变基后合并的意义是什么?

In the case that I describe, I rebase Bonto my branch, just to have the opportunity to replay my work from a more recent point from B, but while staying into my branch.
In this case, a merge is still needed to bring my "replayed" work onto B.

在我描述的情况下,我重新定位B到我的分支,只是为了有机会从最近的点重播我的工作B,但同时留在我的分支中。
在这种情况下,仍然需要合并才能将我的“重播”工作带到B.

The other scenario (described in Git Readyfor instance), is to bring your work directly in Bthrough a rebase (which does conserve all your nice commits, or even give you the opportunity to re-order them through an interactive rebase).
In that case (where you rebase while being in the B branch), you are right: no further merge is needed:

另一种情况(例如在 Git Ready 中描述)是B通过 rebase直接引入你的工作(这确实保存了你所有好的提交,甚至让你有机会通过交互式 rebase 重新排序它们)。
在这种情况下(您在 B 分支中变基),您是对的:不需要进一步合并:

A Git tree at default when we have not merged nor rebased

当我们没有合并或重新定位时,默认的 Git 树

rebase1

变基1

we get by rebasing:

我们通过变基得到:

rebase3

变基3

That second scenario is all about: how do I get new-feature back into master.

第二个场景是关于:我如何将新功能恢复到 master 状态。

My point, by describing the first rebase scenario, is to remind everyone that a rebase can also be used as a preliminary step to that (that being "get new-feature back into master").
You can use rebase to first bring master "in" the new-feature branch: the rebase will replay new-feature commits from the HEAD master, but still in the new-feature branch, effectively moving your branch starting point from an old master commit to HEAD-master.
That allows you to resolve any conflicts in yourbranch (meaning, in isolation, while allowing master to continue to evolve in parallel if your conflict resolution stage takes too long).
Then you can switch to master and merge new-feature(or rebase new-featureonto masterif you want to preserve commits done in your new-featurebranch).

我的观点,通过描述第一个 rebase 场景,是为了提醒大家,rebase 也可以用作它的初步步骤(即“将新功能恢复为 master”)。
您可以使用 rebase 首先将 master “引入”新功能分支:rebase 将重播来自 的新功能提交HEAD master,但仍在新功能分支中,有效地将您的分支起点从旧的 master 提交移动到HEAD-master.
这让你解决任何冲突分支(意思是,在隔离,同时允许主继续同步发展,如果你的解决冲突的阶段时间过长)。
然后,你可以切换到主和合并new-feature(或重订new-featuremaster,如果你想保留做你的提交new-feature分支)。

So:

所以:

  • "rebase vs. merge" can be viewed as two ways to import a work on, say, master.
  • But "rebase then merge" can be a valid workflow to first resolve conflict in isolation, then bring back your work.
  • “rebase vs. merge”可以被看作是导入作品的两种方式,比如,master
  • 但是“rebase 然后合并”可以是一个有效的工作流程,首先单独解决冲突,然后恢复您的工作。

回答by Pace

TL;DR

TL; 博士

If you have any doubt, use merge.

如果您有任何疑问,请使用合并。

Short Answer

简答

The only differences between a rebase and a merge are:

变基和合并之间的唯一区别是:

  • The resulting tree structure of the history (generally only noticeable when looking at a commit graph) is different (one will have branches, the other won't).
  • Merge will generally create an extra commit (e.g. node in the tree).
  • Merge and rebase will handle conflicts differently. Rebase will present conflicts one commit at a time where merge will present them all at once.
  • 生成的历史树结构(通常只有在查看提交图时才明显)是不同的(一个会有分支,另一个没有)。
  • 合并通常会创建一个额外的提交(例如树中的节点)。
  • 合并和变基将以不同的方式处理冲突。Rebase 将一次提交一次提交冲突,而合并将一次提交所有冲突。

So the short answer is to pick rebase or merge based on what you want your history to look like.

所以简短的回答是根据你希望你的历史看起来像什么选择变基或合并

Long Answer

长答案

There are a few factors you should consider when choosing which operation to use.

在选择要使用的操作时,您应该考虑几个因素。

Is the branch you are getting changes from shared with other developers outside your team (e.g. open source, public)?

您正在获取更改的分支是否与团队以外的其他开发人员共享(例如开源、公共)?

If so, don't rebase. Rebase destroys the branch and those developers will have broken/inconsistent repositories unless they use git pull --rebase. This is a good way to upset other developers quickly.

如果是这样,不要rebase。Rebase 会破坏分支,除非他们使用git pull --rebase. 这是快速打乱其他开发人员的好方法。

How skilled is your development team?

你的开发团队有多熟练?

Rebase is a destructive operation. That means, if you do not apply it correctly, you could lose committed work and/or break the consistency of other developer's repositories.

Rebase 是一种破坏性操作。这意味着,如果您没有正确应用它,您可能会丢失已提交的工作和/或破坏其他开发人员存储库的一致性。

I've worked on teams where the developers all came from a time when companies could afford dedicated staff to deal with branching and merging. Those developers don't know much about Git and don't want to know much. In these teams I wouldn't risk recommending rebasing for any reason.

我曾在团队中工作过,这些团队的开发人员都来自公司可以负担得起专职人员来处理分支和合并的时代。那些开发人员对 Git 了解不多,也不想了解太多。在这些团队中,我不会出于任何原因冒险推荐变基。

Does the branch itself represent useful information

分支本身是否代表有用的信息

Some teams use the branch-per-feature model where each branch represents a feature (or bugfix, or sub-feature, etc.) In this model the branch helps identify sets of related commits. For example, one can quickly revert a feature by reverting the merge of that branch (to be fair, this is a rare operation). Or diff a feature by comparing two branches (more common). Rebase would destroy the branch and this would not be straightforward.

一些团队使用每个功能分支模型,其中每个分支代表一个功能(或错误修复,或子功能等)。在此模型中,分支有助于识别相关提交集。例如,可以通过恢复该分支的合并来快速恢复特征(公平地说,这是一种罕见的操作)。或者通过比较两个分支(更常见)来区分特征。Rebase 会破坏分支,这并不简单。

I've also worked on teams that used the branch-per-developer model (we've all been there). In this case the branch itself doesn't convey any additional information (the commit already has the author). There would be no harm in rebasing.

我还曾在使用每个开发者分支模型的团队工作过(我们都去过那里)。在这种情况下,分支本身不会传达任何附加信息(提交已经有了作者)。重新定位不会有什么坏处。

Might you want to revert the merge for any reason?

您可能出于任何原因想要恢复合并吗?

Reverting (as in undoing) a rebase is considerably difficult and/or impossible (if the rebase had conflicts) compared to reverting a merge. If you think there is a chance you will want to revert then use merge.

与恢复合并相比,恢复(如撤消)rebase 是相当困难和/或不可能的(如果 rebase 有冲突)。如果您认为有机会恢复,请使用合并。

Do you work on a team? If so, are you willing to take an all or nothing approach on this branch?

你在团队中工作吗?如果是这样,您是否愿意在这个分支上采取全有或全无的方法?

Rebase operations need to be pulled with a corresponding git pull --rebase. If you are working by yourself you may be able to remember which you should use at the appropriate time. If you are working on a team this will be very difficult to coordinate. This is why most rebase workflows recommend using rebase for all merges (and git pull --rebasefor all pulls).

Rebase 操作需要使用相应的git pull --rebase. 如果您自己工作,您可能会记得在适当的时候应该使用哪个。如果你在一个团队中工作,这将很难协调。这就是为什么大多数 rebase 工作流建议对所有合并(以及git pull --rebase所有拉取)使用 rebase 。

Common Myths

常见的神话

Merge destroys history (squashes commits)

合并破坏历史(壁球提交)

Assuming you have the following merge:

假设您有以下合并:

    B -- C
   /      \
  A--------D

Some people will state that the merge "destroys" the commit history because if you were to look at the log of only the master branch (A -- D) you would miss the important commit messages contained in B and C.

有些人会说合并会“破坏”提交历史,因为如果您只查看主分支 (A - D) 的日志,您将错过 B 和 C 中包含的重要提交消息。

If this were true we wouldn't have questions like this. Basically, you will see B and C unless you explicitly ask not to see them (using --first-parent). This is very easy to try for yourself.

如果这是真的,我们就不会有这样的问题了。基本上,除非您明确要求不要看到它们(使用 --first-parent),否则您将看到 B 和 C。这很容易自己尝试。

Rebase allows for safer/simpler merges

Rebase 允许更安全/更简单的合并

The two approaches merge differently, but it is not clear that one is always better than the other and it may depend on the developer workflow. For example, if a developer tends to commit regularly (e.g. maybe they commit twice a day as they transition from work to home) then there could be a lot of commits for a given branch. Many of those commits might not look anything like the final product (I tend to refactor my approach once or twice per feature). If someone else was working on a related area of code and they tried to rebase my changes it could be a fairly tedious operation.

这两种方法的合并方式不同,但尚不清楚一种总是优于另一种,这可能取决于开发人员的工作流程。例如,如果开发人员倾向于定期提交(例如,当他们从工作过渡到家时,他们可能每天提交两次),那么给定分支可能会有很多提交。许多提交可能看起来不像最终产品(我倾向于对每个功能重构我的方法一两次)。如果其他人正在处理相关的代码区域,并且他们试图重新调整我的更改,这可能是一个相当乏味的操作。

Rebase is cooler / sexier / more professional

Rebase 更酷/更性感/更专业

If you like to alias rmto rm -rfto "save time" then maybe rebase is for you.

如果您喜欢别名rmrm -rf“节省时间”,那么 rebase 可能适合您。

My Two Cents

我的两分钱

I always think that someday I will come across a scenario where Git rebase is the awesome tool that solves the problem. Much like I think I will come across a scenario where Git reflog is an awesome tool that solves my problem. I have worked with Git for over five years now. It hasn't happened.

我一直认为有一天我会遇到一个场景,Git rebase 是解决问题的绝佳工具。就像我想我会遇到一个场景,Git reflog 是一个很棒的工具,可以解决我的问题。我使用 Git 已经五年多了。它没有发生。

Messy histories have never really been a problem for me. I don't ever just read my commit history like an exciting novel. A majority of the time I need a history I am going to use Git blame or Git bisect anyway. In that case, having the merge commit is actually useful to me, because if the merge introduced the issue, that is meaningful information to me.

凌乱的历史对我来说从来都不是问题。我从来没有像阅读一部激动人心的小说一样阅读我的提交历史。大多数情况下,我需要历史记录,无论如何我都会使用 Git 指责或 Git bisect。在这种情况下,合并提交实际上对我有用,因为如果合并引入了问题,那对我来说是有意义的信息。

Update (4/2017)

更新 (4/2017)

I feel obligated to mention that I have personally softened on using rebase although my general advice still stands. I have recently been interacting a lot with the Angular 2 Materialproject. They have used rebase to keep a very clean commit history. This has allowed me to very easily see what commit fixed a given defect and whether or not that commit was included in a release. It serves as a great example of using rebase correctly.

我觉得有必要提到我个人对使用 rebase 的态度软化了,尽管我的一般建议仍然有效。我最近与Angular 2 Material项目进行了很多互动。他们使用 rebase 来保持非常干净的提交历史。这让我可以很容易地看到哪些提交修复了给定的缺陷,以及该提交是否包含在版本中。它是正确使用 rebase 的一个很好的例子。

回答by Andrew Arnott

A lot of answers here say that merging turns all your commits into one, and therefore suggest to use rebase to preserve your commits. This is incorrect. And a bad idea if you have pushed your commits already.

这里的很多答案都说合并会将您的所有提交合二为一,因此建议使用 rebase 来保留您的提交。这是不正确的。如果你已经推送了你的提交,这是一个坏主意

Merge does notobliterate your commits. Merge preserves history! (just look at gitk) Rebase rewrites history, which is a Bad Thing after you've pushedit.

合并并不能抹杀你的提交。合并保留历史!(看看 gitk)Rebase 重写了历史,这在你推送之后是一件坏事。

Use merge -- not rebasewhenever you've already pushed.

使用合并——不要在你已经推送的时候变基

Here is Linus' (author of Git) take on it(now hosted on my own blog, as recovered by the Wayback Machine). It's a really good read.

这是 Linus(Git 的作者)对此的看法(现在托管在我自己的博客上,由 Wayback Machine 恢复)。这是一个很好的阅读。

Or you can read my own version of the same idea below.

或者你可以在下面阅读我自己的相同想法的版本。

Rebasing a branch on master:

在 master 上重新建立分支:

  • provides an incorrect idea of how commits were created
  • pollutes master with a bunch of intermediate commits that may not have been well tested
  • could actually introduce build breaks on these intermediate commits because of changes that were made to master between when the original topic branch was created and when it was rebased.
  • makes finding good places in master to checkout difficult.
  • Causes the timestamps on commits to not align with their chronological order in the tree. So you would see that commit A precedes commit B in master, but commit B was authored first. (What?!)
  • Produces more conflicts, because individual commits in the topic branch can each involve merge conflicts which must be individually resolved (further lying in history about what happened in each commit).
  • is a rewrite of history. If the branch being rebased has been pushed anywhere (shared with anyone other than yourself) then you've screwed up everyone else who has that branch since you've rewritten history.
  • 提供了关于如何创建提交的错误想法
  • 用一堆可能没有经过良好测试的中间提交污染 master
  • 实际上可能会在这些中间提交上引入构建中断,因为在创建原始主题分支和重新定位之间对 master 进行了更改。
  • 很难在 master 中找到好地方结账。
  • 导致提交的时间戳与它们在树中的时间顺序不一致。所以你会看到在 master 中提交 A 先于提交 B,但提交 B 是最先创作的。(什么?!)
  • 产生更多冲突,因为主题分支中的各个提交都可能涉及必须单独解决的合并冲突(进一步记录在每个提交中发生的事情的历史)。
  • 是对历史的改写。如果重新建立的分支被推送到任何地方(与除您以外的任何人共享),那么自从您重写历史以来,您就已经搞砸了拥有该分支的其他所有人。

In contrast, merging a topic branch into master:

相比之下,将主题分支合并到 master 中:

  • preserves history of where topic branches were created, including any merges from master to the topic branch to help keep it current. You really get an accurate idea of what code the developer was working with when they were building.
  • master is a branch made up mostly of merges, and each of those merge commits are typically 'good points' in history that are safe to check out, because that's where the topic branch was ready to be integrated.
  • all the individual commits of the topic branch are preserved, including the fact that they were in a topic branch, so isolating those changes is natural and you can drill in where required.
  • merge conflicts only have to be resolved once (at the point of the merge), so intermediate commit changes made in the topic branch don't have to be resolved independently.
  • can be done multiple times smoothly. If you integrate your topic branch to master periodically, folks can keep building on the topic branch, and it can keep being merged independently.
  • 保留主题分支创建位置的历史记录,包括从 master 到主题分支的任何合并,以帮助使其保持最新状态。您确实可以准确了解开发人员在构建时使用的代码。
  • master 是一个主要由合并组成的分支,每个合并提交通常都是历史上的“好点”,可以安全检查,因为这是主题分支准备集成的地方。
  • 主题分支的所有单独提交都被保留,包括它们在主题分支中的事实,因此隔离这些更改是很自然的,您可以在需要的地方进行钻取。
  • 合并冲突只需解决一次(在合并点),因此不必独立解决在主题分支中所做的中间提交更改。
  • 可以多次顺利完成。如果您定期将主题分支集成到 master,人们可以继续在主题分支上构建,并且它可以继续独立合并。

回答by Carl

TLDR: It depends on what is most important - a tidy history or a true representation of the sequence of development

TLDR:这取决于什么是最重要的 - 整洁的历史或发展顺序的真实表现

If a tidy history is the most important, then you would rebase first and then merge your changes, so it is clear exactly what the new code is. If you have already pushed your branch, don't rebase unless you can deal with the consequences.

如果整洁的历史记录是最重要的,那么您将首先变基,然后合并您的更改,这样新代码到底是什么就很清楚了。如果你已经推送了你的分支,除非你能处理后果,否则不要重新设置。

If true representation of sequence is the most important, you would merge without rebasing.

如果序列的真实表示是最重要的,那么您将合并而不进行变基。

Merge means: Create a single new commit that merges my changes into the destination. Note:This new commit will have two parents - the latest commit from your string of commits and the latest commit of the other branch you're merging.

合并意味着:创建一个新提交,将我的更改合并到目标中。注意:这个新提交将有两个父项 - 您的提交字符串中的最新提交和您正在合并的另一个分支的最新提交。

Rebase means: Create a whole new series of commits, using my current set of commits as hints. In other words, calculate what my changes would have looked like if I had started making them from the point I'm rebasing on to. After the rebase, therefore, you might need to re-test your changes and during the rebase, you would possibly have a few conflicts.

Rebase 意味着:创建一系列全新的提交,使用我当前的一组提交作为提示。换句话说,计算如果我从我要重新建立的点开始进行更改,我的更改会是什么样子。因此,在 rebase 之后,您可能需要重新测试您的更改,并且在 rebase 期间,您可能会遇到一些冲突。

Given this, why would you rebase? Just to keep the development history clear. Let's say you're working on feature X and when you're done, you merge your changes in. The destination will now have a single commit that would say something along the lines of "Added feature X". Now, instead of merging, if you rebased and then merged, the destination development history would contain all the individual commits in a single logical progression. This makes reviewing changes later on much easier. Imagine how hard you'd find it to review the development history if 50 developers were merging various features all the time.

鉴于此,你为什么要变基?只是为了保持发展历史清晰。假设您正在处理功能 X,当您完成后,您将更改合并到其中。目标现在将有一个单一的提交,它会说一些类似于“添加的功能 X”的内容。现在,如果您重新定位然后合并,而不是合并,目标开发历史将包含单个逻辑进程中的所有单独提交。这使得以后更容易查看更改。想象一下,如果 50 名开发人员一直在合并各种功能,您会发现回顾开发历史是多么困难。

That said, if you have already pushed the branch you're working on upstream, you should not rebase, but merge instead. For branches that have not been pushed upstream, rebase, test and merge.

也就是说,如果您已经将正在处理的分支推送到上游,则不应 rebase,而是应合并。对于没有被推到上游的分支,rebase、test 和 merge。

Another time you might want to rebase is when you want to get rid of commits from your branch before pushing upstream. For example: Commits that introduce some debugging code early on and other commits further on that clean that code up. The only way to do this is by performing an interactive rebase: git rebase -i <branch/commit/tag>

另一个你可能想要 rebase 的时候是当你想要在推送上游之前从你的分支中删除提交时。例如: 早期引入一些调试代码的提交和进一步清理代码的其他提交。做到这一点的唯一方法是执行交互式 rebase:git rebase -i <branch/commit/tag>

UPDATE: You also want to use rebase when you're using Git to interface to a version control system that doesn't support non-linear history (Subversionfor example). When using the git-svn bridge, it is very important that the changes you merge back into Subversion are a sequential list of changes on top of the most recent changes in trunk. There are only two ways to do that: (1) Manually re-create the changes and (2) Using the rebase command, which is a lot faster.

更新:当您使用 Git 与不支持非线性历史记录(例如Subversion)的版本控制系统接口时,您还想使用 rebase 。使用 git-svn 桥时,合并回 Subversion 的更改是在主干中最新更改之上的更改顺序列表,这一点非常重要。只有两种方法可以做到这一点:(1) 手动重新创建更改和 (2) 使用 rebase 命令,这要快得多。

UPDATE 2: One additional way to think of a rebase is that it enables a sort of mapping from your development style to the style accepted in the repository you're committing to. Let's say you like to commit in small, tiny chunks. You have one commit to fix a typo, one commit to get rid of unused code and so on. By the time you've finished what you need to do, you have a long series of commits. Now let's say the repository you're committing to encourages large commits, so for the work you're doing, one would expect one or maybe two commits. How do you take your string of commits and compress them to what is expected? You would use an interactive rebase and squash your tiny commits into fewer larger chunks. The same is true if the reverse was needed - if your style was a few large commits, but the repository demanded long strings of small commits. You would use a rebase to do that as well. If you had merged instead, you have now grafted your commit style onto the main repository. If there are a lot of developers, you can imagine how hard it would be to follow a history with several different commit styles after some time.

更新 2:考虑 rebase 的另一种方法是,它支持从您的开发风格到您提交的存储库中接受的风格的某种映射。假设您喜欢以小块的形式进行提交。你有一次修复错字的提交,一次删除未使用的代码等等。当你完成你需要做的事情时,你有一系列的提交。现在假设您要提交的存储库鼓励大量提交,因此对于您正在做的工作,人们会期望进行一次或两次提交。你如何获取你的提交字符串并将它们压缩到预期的?您将使用交互式变基并将您的微小提交压缩成更少的大块。如果需要相反的情况也是如此——如果你的风格是一些大的提交,但是存储库需要长串的小提交。你也可以使用 rebase 来做到这一点。如果您改为合并,那么您现在已经将提交样式移植到主存储库中。如果有很多开发人员,您可以想象一段时间后跟踪具有几种不同提交样式的历史记录是多么困难。

UPDATE 3: Does one still need to merge after a successful rebase?Yes, you do. The reason is that a rebase essentially involves a "shifting" of commits. As I've said above, these commits are calculated, but if you had 14 commits from the point of branching, then assuming nothing goes wrong with your rebase, you will be 14 commits ahead (of the point you're rebasing onto) after the rebase is done. You had a branch before a rebase. You will have a branch of the same length after. You still need to merge before you publish your changes. In other words, rebase as many times as you want (again, only if you have not pushed your changes upstream). Merge only after you rebase.

更新 3:Does one still need to merge after a successful rebase?是的,你知道。原因是 rebase 本质上涉及提交的“转移”。正如我上面所说,这些提交是计算出来的,但是如果你从分支点开始有 14 次提交,那么假设你的 rebase 没有任何问题,那么你将提前 14 次提交(在你rebase 的点上)之后变基完成了。在变基之前你有一个分支。之后你将有一个相同长度的分支。在发布更改之前,您仍然需要合并。换句话说,可以根据需要多次变基(同样,仅当您没有将更改推送到上游时)。仅在变基后合并。

回答by Abdullah Khan

While merging is definitely the easiest and most common way to integrate changes, it's not the only one: Rebaseis an alternative means of integration.

虽然合并绝对是集成更改的最简单和最常见的方法,但它不是唯一的方法:Rebase是另一种集成方法。

Understanding Merge a Little Better

更好地理解合并

When Git performs a merge, it looks for three commits:

当 Git 执行合并时,它会查找三个提交:

  • (1) Common ancestor commit. If you follow the history of two branches in a project, they always have at least one commit in common: at this point in time, both branches had the same content and then evolved differently.
  • (2) + (3) Endpoints of each branch. The goal of an integration is to combine the current states of two branches. Therefore, their respective latest revisions are of special interest. Combining these three commits will result in the integration we're aiming for.
  • (1) 共同祖先提交。如果你跟踪一个项目中两个分支的历史,它们总是至少有一个共同的提交:在这个时间点,两个分支都有相同的内容,然后又发生了不同的演变。
  • (2) + (3) 每个分支的端点。集成的目标是结合两个分支的当前状态。因此,它们各自的最新修订特别令人感兴趣。结合这三个提交将导致我们的目标集成。

Fast-Forward or Merge Commit

快进或合并提交

In very simple cases, one of the two branches doesn't have any new commits since the branching happened - its latest commit is still the common ancestor.

在非常简单的情况下,自分支发生以来,两个分支之一没有任何新的提交——它的最新提交仍然是共同的祖先。

Enter image description here

在此处输入图片说明

In this case, performing the integration is dead simple: Git can just add all the commits of the other branch on top of the common ancestor commit. In Git, this simplest form of integration is called a "fast-forward" merge. Both branches then share the exact same history.

在这种情况下,执行集成非常简单:Git 只需将另一个分支的所有提交添加到共同祖先提交之上。在 Git 中,这种最简单的集成形式称为“快进”合并。然后两个分支共享完全相同的历史记录。

Enter image description here

在此处输入图片说明

In a lot of cases, however, both branches moved forward individually.

然而,在很多情况下,两个分支都各自向前发展。

Enter image description here

在此处输入图片说明

To make an integration, Git will have to create a new commit that contains the differences between them - the merge commit.

为了进行集成,Git 必须创建一个包含它们之间差异的新提交——合并提交。

Enter image description here

在此处输入图片说明

Human Commits & Merge Commits

人工提交和合并提交

Normally, a commit is carefully created by a human being. It's a meaningful unit that wraps only related changes and annotates them with a comment.

通常,提交是由人精心创建的。这是一个有意义的单元,它只包装相关的更改并用注释对其进行注释。

A merge commit is a bit different: instead of being created by a developer, it gets created automatically by Git. And instead of wrapping a set of related changes, its purpose is to connect two branches, just like a knot. If you want to understand a merge operation later, you need to take a look at the history of both branches and the corresponding commit graph.

合并提交有点不同:它不是由开发人员创建,而是由 Git 自动创建。而不是包装一组相关的变化,它的目的是连接两个分支,就像一个结一样。如果以后想了解一个合并操作,就需要看一下两个分支的历史和对应的提交图。

Integrating with Rebase

与 Rebase 集成

Some people prefer to go without such automatic merge commits. Instead, they want the project's history to look as if it had evolved in a single, straight line.No indication remains that it had been split into multiple branches at some point.

有些人更喜欢没有这种自动合并提交。相反,他们希望项目的历史看起来好像是在一条直线上发展的。没有迹象表明它在某个时候被分成了多个分支。

Enter image description here

在此处输入图片说明

Let's walk through a rebase operation step by step. The scenario is the same as in the previous examples: we want to integrate the changes from branch-B into branch-A, but now by using rebase.

让我们一步一步地完成一个 rebase 操作。该场景与前面的示例相同:我们希望将分支 B 的更改集成到分支 A 中,但现在使用 rebase。

Enter image description here

在此处输入图片说明

We will do this in three steps

我们将分三步完成

  1. git rebase branch-A // Synchronises the history with branch-A
  2. git checkout branch-A // Change the current branch to branch-A
  3. git merge branch-B // Merge/take the changes from branch-B to branch-A
  1. git rebase branch-A // Synchronises the history with branch-A
  2. git checkout branch-A // Change the current branch to branch-A
  3. git merge branch-B // Merge/take the changes from branch-B to branch-A

First, Git will "undo" all commits on branch-A that happened after the lines began to branch out (after the common ancestor commit). However, of course, it won't discard them: instead you can think of those commits as being "saved away temporarily".

首先,Git 将“撤消”在分支 A 上发生的所有在行开始分支之后(在共同祖先提交之后)的提交。但是,当然,它不会丢弃它们:相反,您可以将这些提交视为“暂时保存”。

Enter image description here

在此处输入图片说明

Next, it applies the commits from branch-B that we want to integrate. At this point, both branches look exactly the same.

接下来,它应用来自我们想要集成的分支 B 的提交。此时,两个分支看起来完全一样。

Enter image description here

在此处输入图片说明

In the final step, the new commits on branch-A are now reapplied - but on a new position, on top of the integrated commits from branch-B (they are re-based).

在最后一步,分支 A 上的新提交现在被重新应用 - 但在一个新位置上,在分支 B 的集成提交之上(它们是重新基于的)。

The result looks like development had happened in a straight line. Instead of a merge commit that contains all the combined changes, the original commit structure was preserved.

结果看起来像是直线发展。保留了原始提交结构,而不是包含所有组合更改的合并提交。

Enter image description here

在此处输入图片说明

Finally, you get a clean branch branch-Awith no unwanted and auto generated commits.

最后,您将获得一个干净的分支branch-A,没有不需要的和自动生成的提交。

Note:Taken from the awesome postby git-tower. The disadvantagesof rebaseis also a good read in the same post.

注:从真棒摘自通过git-tower。该缺点rebase也是在同一职位读好。

回答by guybrush

Before merge/rebase:

合并/变基之前:

A <- B <- C    [master]
^
 \
  D <- E       [branch]

After git merge master:

之后git merge master

A <- B <- C
^         ^
 \         \
  D <- E <- F

After git rebase master:

之后git rebase master

A <- B <- C <- D' <- E'

(A, B, C, D, E and F are commits)

(A、B、C、D、E 和 F 是提交)

This example and much more well illustrated information about Git can be found in Git The Basics Tutorial.

可以在Git The Basics Tutorial 中找到此示例以及有关 Git 的更多说明性信息。

回答by Joaquin Sargiotto

This sentence gets it:

这句话明白了:

In general, the way to get the best of both worlds is to rebase local changes you've made, but haven't shared yet, before you push them in order to clean up your story, but never rebase anything you've pushed somewhere.

一般来说,获得两全其美的方法是在推送之前重新设置您所做但尚未共享的本地更改以清理您的故事,但永远不要重新设置您在某处推送的任何内容.

Source: 3.6 Git Branching - Rebasing, Rebase vs. Merge

资料来源:3.6 Git 分支 - 变基、变基与合并

回答by sp00m

This answer is widely oriented around Git Flow. The tables have been generated with the nice ASCII Table Generator, and the history trees with this wonderful command (aliasedas git lg):

这个答案广泛地针对Git Flow。这些表是用漂亮的ASCII Table Generator 生成的,历史树是用这个奇妙的命令(别名git lg):

git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

Tables are in reverse chronological order to be more consistent with the history trees. See also the difference between git mergeand git merge --no-fffirst (you usually want to use git merge --no-ffas it makes your history look closer to the reality):

表格按时间倒序排列,以便与历史树更一致。另请参阅git mergegit merge --no-fffirst之间的区别(您通常希望使用git merge --no-ff它,因为它使您的历史看起来更接近现实):

git merge

git merge

Commands:

命令:

Time          Branch "develop"             Branch "features/foo"
------- ------------------------------ -------------------------------
15:04   git merge features/foo
15:03                                  git commit -m "Third commit"
15:02                                  git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

结果:

* 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo)
|           Third commit - Christophe
* 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago)
|           Second commit - Christophe
* 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge --no-ff

git merge --no-ff

Commands:

命令:

Time           Branch "develop"              Branch "features/foo"
------- -------------------------------- -------------------------------
15:04   git merge --no-ff features/foo
15:03                                    git commit -m "Third commit"
15:02                                    git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

结果:

*   1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/foo' - Christophe
| * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago)
|/            Second commit - Christophe
* c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe


git mergevs git rebase

git merge对比 git rebase

First point: always merge features into develop, never rebase develop from features. This is a consequence of the Golden Rule of Rebasing:

第一点:始终将 features 合并到 develop 中,永远不要从 features 中 rebase develop。这是Rebase 黄金法则的结果

The golden rule of git rebaseis to never use it on publicbranches.

的黄金法则git rebase是永远不要在公共分支上使用它。

In other words:

换句话说

Never rebase anything you've pushed somewhere.

永远不要重新调整你在某处推送的任何东西。

I would personally add: unless it's a feature branch AND you and your team are aware of the consequences.

我个人会补充说:除非它是一个功能分支并且你和你的团队知道后果

So the question of git mergevs git rebaseapplies almost only to the feature branches (in the following examples, --no-ffhas always been used when merging). Note that since I'm not sure there's one better solution (a debate exists), I'll only provide how both commands behave. In my case, I prefer using git rebaseas it produces a nicer history tree :)

所以git mergevs的问题git rebase几乎只适用于特征分支(在下面的例子中,--no-ff一直在合并时使用)。请注意,由于我不确定是否有更好的解决方案(存在争论),因此我将仅提供这两个命令的行为方式。就我而言,我更喜欢使用,git rebase因为它会产生更好的历史树:)

Between feature branches

特征分支之间

git merge

git merge

Commands:

命令:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- --------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

结果:

*   c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago)
| |\            Merge branch 'features/foo' into features/bar - Christophe
| * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | |           Fifth commit - Christophe
| * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | |           Fourth commit - Christophe
* | |   98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \ \            Merge branch 'features/foo' - Christophe
| |/ /
|/| /
| |/
| * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

git rebase

Commands:

命令:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git rebase features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

结果:

*   7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
| * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

From developto a feature branch

develop到功能分支

git merge

git merge

Commands:

命令:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git merge --no-ff develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

结果:

*   9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago)
| |\            Merge branch 'develop' into features/bar - Christophe
| |/
|/|
* |   5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | |           Third commit - Christophe
| * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ /            Second commit - Christophe
| * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
* 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

git rebase

Commands:

命令:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git rebase develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

结果:

*   b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
*   856433e - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\            Merge branch 'features/foo' - Christophe
| * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe


Side notes

旁注

git cherry-pick

git cherry-pick

When you just need one specific commit, git cherry-pickis a nice solution (the -xoption appends a line that says "(cherry picked from commit...)" to the original commit message body, so it's usually a good idea to use it - git log <commit_sha1>to see it):

当您只需要一个特定的提交时,这git cherry-pick是一个不错的解决方案(该-x选项会在原始提交消息正文中附加一行表示“ (从提交中挑选的樱桃...)”,因此使用它通常是一个好主意 -git log <commit_sha1>看看它):

Commands:

命令:

Time           Branch "develop"              Branch "features/foo"                Branch "features/bar"
------- -------------------------------- ------------------------------- -----------------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git cherry-pick -x <second_commit_sha1>
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Result:

结果:

*   50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago)
| |           Second commit - Christophe
| * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
|/|
| * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git pull --rebase

git pull --rebase

I am not sure I can explain it better than Derek Gourlay... Basically, use git pull --rebaseinstead of git pull:) What's missing in the article though, is that you can enable it by default:

我不确定我能比Derek Gourlay更好地解释它......基本上,使用git pull --rebase而不是git pull:) 文章中缺少的是,您可以默认启用它

git config --global pull.rebase true

git rerere

git rerere

Again, nicely explained here. But put simply, if you enable it, you won't have to resolve the same conflict multiple times anymore.

再次,在这里很好地解释。但简而言之,如果您启用它,您将不再需要多次解决相同的冲突。