git 的“rebase --preserve-merges”究竟做了什么(为什么?)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15915430/
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 15:56:28  来源:igfitidea点击:

What exactly does git's "rebase --preserve-merges" do (and why?)

gitgit-rebase

提问by Chris

Git's documentation for the rebasecommandis quite brief:

Git 的命令文档rebase非常简短:

--preserve-merges
    Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).

So what actually happens when you use --preserve-merges? How does it differ from the default behavior (without that flag)? What does it mean to "recreate" a merge, etc.

那么当你使用时实际发生了什么--preserve-merges?它与默认行为(没有该标志)有何不同?“重新创建”合并等是什么意思?

回答by Chris

As with a normal git rebase, git with --preserve-mergesfirst identifies a list of commits made in one part of the commit graph, and then replays those commits on top of another part. The differences with --preserve-mergesconcern which commits are selected for replay and how that replaying works for merge commits.

与普通的 git rebase 一样,git with--preserve-merges首先标识在提交图的一个部分中进行的提交列表,然后在另一部分之上重放这些提交。关于--preserve-merges选择哪些提交进行重放以及重放如何用于合并提交的差异。

To be more explicit about the main differences between normal and merge-preserving rebase:

更明确地了解正常和合并保留变基之间的主要区别:

  • Merge-preserving rebase is willing to replay (some) merge commits, whereas normal rebase completely ignores merge commits.
  • Because it's willing to replay merge commits, merge-preserving rebase has to define what it meansto replay a merge commit, and deal with some extra wrinkles
    • The most interesting part, conceptually, is perhaps in picking what the new commit's merge parents should be.
    • Replaying merge commits also require explicitly checking out particular commits (git checkout <desired first parent>), whereas normal rebase doesn't have to worry about that.
  • Merge-preserving rebase considers a shallower set of commits for replay:
    • In particular, it will only consider replaying commits made since the most recent merge base(s) -- i.e. the most recenttime the two branches diverged --, whereas normal rebase might replay commits going back to the firsttime the two branches diverged.
    • To be provisional and unclear, I believe this is ultimately a means to screen out replaying "old commits" that have already been "incorporated into" a merge commit.
  • 保留合并的 rebase 愿意重放(一些)合并提交,而正常的 rebase 完全忽略合并提交。
  • 因为它愿意重放合并提交,合并保留 rebase 必须定义重放合并提交的含义,并处理一些额外的皱纹
    • 从概念上讲,最有趣的部分可能是选择新提交的合并父项应该是什么。
    • 重放合并提交还需要明确检查特定的提交 ( git checkout <desired first parent>),而普通的 rebase 不必担心这一点。
  • 保留合并的 rebase 考虑了一组较浅的用于重放的提交:
    • 特别是,它只会考虑重放自最近的合并基础(即两个分支分歧的最近时间)以来所做的提交,而正常的 rebase 可能会重播回到两个分支分歧的一次的提交。
    • 暂时且不清楚,我相信这最终是一种筛选已经“合并到”合并提交中的“旧提交”的重放方式。

First I will try to describe "sufficiently exactly" what rebase --preserve-mergesdoes, and then there will be some examples. One can of course start with the examples, if that seems more useful.

首先,我将尝试“完全准确地”描述 rebase 的--preserve-merges作用,然后会有一些示例。如果这看起来更有用的话,当然可以从示例开始。

The Algorithm in "Brief"

“Brief”中的算法

If you want to really get into the weeds, download the git source and explore the file git-rebase--interactive.sh. (Rebase is not part of Git's C core, but rather is written in bash. And, behind the scenes, it shares code with "interactive rebase".)

如果您想真正深入研究,请下载 git 源并浏览文件git-rebase--interactive.sh. (Rebase 不是 Git 的 C 核心的一部分,而是用 bash 编写的。而且,在幕后,它与“交互式 rebase”共享代码。)

But here I will sketch what I think is the essence of it. In order to reduce the number of things to think about, I have taken a few liberties. (e.g. I don't try to capture with 100% accuracy the precise order in which computations take place, and ignore some less central-seeming topics, e.g. what to do about commits that have already been cherry-picked between branches).

但在这里我将勾勒出我认为它的本质。为了减少要考虑的事情的数量,我采取了一些自由。(例如,我不会尝试 100% 准确地捕获计算发生的精确顺序,并忽略一些不太中心化的主题,例如如何处理已经在分支之间挑选出来的提交)。

First, note that a non-merge-preserving rebase is rather simple. It's more or less:

首先,请注意非合并保留 rebase 相当简单。它或多或少:

Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A") 
Replay all those commits onto B one at a time in order.

Rebase --preserve-mergesis comparatively complicated. Here's as simple as I've been able to make it without losing things that seem pretty important:

Rebase--preserve-merges比较复杂。这就像我能够做到的那样简单而不会丢失看起来很重要的东西:

Find the commits to replay:
  First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
    This (these) merge base(s) will serve as a root/boundary for the rebase.
    In particular, we'll take its (their) descendants and replay them on top of new parents
  Now we can define C, the set of commits to replay. In particular, it's those commits:
    1) reachable from B but not A (as in a normal rebase), and ALSO
    2) descendants of the merge base(s)
  If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
    git log A..B --not $(git merge-base --all A B)
Replay the commits:
  Create a branch B_new, on which to replay our commits.
  Switch to B_new (i.e. "git checkout B_new")
  Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
    If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
    Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
      To create a merge commit, its parents must exist and we must know what they are.
      So first, figure out which parents to use for c', by reference to the parents of c:
        For each parent p_i in parents_of(c):
          If p_i is one of the merge bases mentioned above:
            # p_i is one of the "boundary commits" that we no longer want to use as parents
            For the new commit's ith parent (p_i'), use the HEAD of B_new.
          Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
            # Note: Because we're moving parents-before-children, a rewritten version
            # of p_i must already exist. So reuse it:
            For the new commit's ith parent (p_i'), use the rewritten version of p_i.
          Otherwise:
            # p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
            For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
      Second, actually create the new commit c':
        Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
        Merge in the other parent(s):
          For a typical two-parent merge, it's just "git merge p_2'".
          For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
        Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
  Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

Rebase with an --onto Cargument should be very similar. Just instead of starting commit playback at the HEAD of B, you start commit playback at the HEAD of C instead. (And use C_new instead of B_new.)

带有--onto C参数的Rebase应该非常相似。只是不是在 B 的 HEAD 处开始提交回放,而是在 C 的 HEAD 处开始提交回放。(并使用 C_new 而不是 B_new。)

Example 1

示例 1

For example, take commit graph

以提交图为例

  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G

m is a merge commit with parents E and G.

m 是与父 E 和 G 的合并提交。

Suppose we rebased topic (H) on top of master (C) using a normal, non-merge-preserving rebase. (For example, checkout topic; rebase master.) In that case, git would select the following commits for replay:

假设我们使用正常的非合并保留 rebase 将主题 (H) 重新定位在主 (C) 之上。(例如,checkout topic; rebase master。)在这种情况下,git 将选择以下提交进行重播:

  • pick D
  • pick E
  • pick F
  • pick G
  • pick H
  • 选D
  • 选E
  • 选F
  • 挑G
  • 挑H

and then update the commit graph like so:

然后像这样更新提交图:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic

(D' is the replayed equivalent of D, etc..)

(D' 是 D 等重放的等价物。)

Note that merge commit m is not selected for replay.

请注意,未选择合并提交 m 进行重放。

If we instead did a --preserve-mergesrebase of H on top of C. (For example, checkout topic; rebase --preserve-merges master.) In this new case, git would select the following commits for replay:

如果我们改为--preserve-merges在 C 之上对 H 进行 rebase。(例如,checkout topic; rebase --preserve-merges master。)在这种新情况下,git 将选择以下提交进行重放:

  • pick D
  • pick E
  • pick F (onto D' in the 'subtopic' branch)
  • pick G (onto F' in the 'subtopic' branch)
  • pick Merge branch 'subtopic' into topic
  • pick H
  • 选D
  • 选E
  • 选择 F(到“副主题”分支中的 D')
  • 选择 G(到“副主题”分支中的 F')
  • 选择将分支“子主题”合并到主题中
  • 挑H

Now m waschosen for replay. Also note that merge parents E and G were picked for inclusion before merge commit m.

现在米选择用于重放。另请注意,在合并提交 m 之前选择合并父 E 和 G 以包含在内。

Here is the resulting commit graph:

这是生成的提交图:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'

Again, D' is a cherry-picked (i.e. recreated) version of D. Same for E', etc.. Every commit not on master has been replayed. Both E and G (the merge parents of m) have been recreated as E' and G' to serve as the parents of m' (after rebase, the tree history still remains the same).

同样,D' 是 D 的精选(即重新创建)版本。 E' 等也是如此。每个不在 master 上的提交都已重播。E 和 G(m 的合并父代)都被重新创建为 E' 和 G' 以作为 m' 的父代(在变基后,树历史仍然保持不变)。

Example 2

示例 2

Unlike with normal rebase, merge-preserving rebase can create multiple children of the upstream head.

与普通 rebase 不同,merge-preserving rebase 可以创建上游头的多个子级。

For example, consider:

例如,考虑:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 

If we rebase H (topic) on top of C (master), then the commits chosen for rebase are:

如果我们在 C(主)之上重新设置 H(主题),那么为重新设置选择的提交是:

  • pick D
  • pick E
  • pick F
  • pick G
  • pick m
  • pick H
  • 选D
  • 选E
  • 选F
  • 挑G
  • 挑米
  • 挑H

And the result is like so:

结果是这样的:

  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/

Example 3

示例 3

In the above examples, both the merge commit and its two parents are replayed commits, rather than the original parents that the original merge commit have. However, in other rebases a replayed merge commit can end up with parents that were already in the commit graph before the merge.

在上面的示例中,合并提交及其两个父项都是重放提交,而不是原始合并提交所具有的原始父项。但是,在其他变基中,重放的合并提交可能会以合并之前已经在提交图中的父项结束。

For example, consider:

例如,考虑:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic

If we rebase topic onto master (preserving merges), then the commits to replay will be

如果我们将主题变基到主节点(保留合并),那么重放的提交将是

  • pick merge commit m
  • pick F
  • 选择合并提交 m
  • 选F

The rewritten commit graph will look like so:

重写后的提交图如下所示:

                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic

Here replayed merge commit m' gets parents that pre-existed in the commit graph, namely D (the HEAD of master) and E (one of the parents of the original merge commit m).

这里重放的合并提交 m' 获取提交图中预先存在的父项,即 D(主节点的 HEAD)和 E(原始合并提交 m 的父项之一)。

Example 4

示例 4

Merge-preserving rebase can get confused in certain "empty commit" cases. At least this is true only some older versions of git (e.g. 1.7.8.)

在某些“空提交”情况下,保留合并的变基可能会混淆。至少这仅适用于某些旧版本的 git(例如 1.7.8。)

Take this commit graph:

拿这个提交图:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic

Note that both commit m1 and m2 should have incorporated all the changes from B and F.

请注意,提交 m1 和 m2 都应该包含来自 B 和 F 的所有更改。

If we try to do git rebase --preserve-mergesof H (topic) onto D (master), then the following commits are chosen for replay:

如果我们尝试git rebase --preserve-merges在 D(主)上执行H(主题),则选择以下提交进行重放:

  • pick m1
  • pick H
  • 选择 m1
  • 挑H

Note that the changes (B, F) united in m1 should already be incorporated into D. (Those changes should already be incorporated into m2, because m2 merges together the children of B and F.) Therefore, conceptually, replaying m1 on top of D should probably either be a no-op or create an empty commit (i.e. one where the diff between successive revisions is empty).

请注意,合并在 m1 中的更改 (B, F) 应该已经合并到 D 中。(这些更改应该已经合并到 m2 中,因为 m2 将 B 和 F 的孩子合并在一起。)因此,从概念上讲,在上面重放 m1 D 可能应该是一个无操作或创建一个空提交(即连续修订之间的差异为空的提交)。

Instead, however, git may reject the attempt to replay m1 on top of D. You can get an error like so:

但是,git 可能会拒绝在 D 之上重放 m1 的尝试。您可能会收到如下错误:

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

It looks like one forgot to pass a flag to git, but the underlying problem is that git dislikes creating empty commits.

看起来好像忘记将标志传递给 git,但潜在的问题是 git 不喜欢创建空提交。

回答by VonC

Git 2.18 (Q2 2018) will improve considerably the --preserve-mergeoption by adding a new option.

Git 2.18(2018 年第二季度)将--preserve-merge通过添加一个新选项来显着改进该选项。

"git rebase" learned "--rebase-merges" to transplant the whole topology of commit graph elsewhere.

git rebase学会” --rebase-merges“将提交图的整个拓扑移植到别处

(Note: Git 2.22, Q2 2019, actually deprecates--preserve-merge, and Git 2.25, Q1 2020, stops advertising it in the "git rebase --help" output)

(注意:Git 2.22,2019 年第二季度,实际上已弃用--preserve-merge,而 Git 2.25,2020 年第一季度,停止在“ git rebase --help”输出中宣传它

See commit 25cff9f, commit 7543f6f, commit 1131ec9, commit 7ccdf65, commit 537e7d6, commit a9be29c, commit 8f6aed7, commit 1644c73, commit d1e8b01, commit 4c68e7d, commit 9055e40, commit cb5206e, commit a01c2a5, commit 2f6b1d1, commit bf5c057(25 Apr 2018) by Johannes Schindelin (dscho).
See commit f431d73(25 Apr 2018) by Stefan Beller (stefanbeller).
See commit 2429335(25 Apr 2018) by Phillip Wood (phillipwood).
(Merged by Junio C Hamano -- gitster--in commit 2c18e6a, 23 May 2018)

提交25cff9f提交7543f6f提交1131ec9提交7ccdf65提交537e7d6提交a9be29c提交8f6aed7提交1644c73提交d1e8b01提交4c68e7d提交9055e40提交cb5206e提交a01c2a5提交2f6b1d1提交bf5c057(2018年4月25日)作者:约翰内斯·辛德林 ( dscho)
请参阅Stefan Beller ( ) 的提交 f431d73(2018 年 4 月 25 日stefanbeller
请参阅Phillip Wood ( ) 的commit 2429335(2018 年 4 月 25 日(由Junio C Hamano合并-- --commit 2c18e6a,2018 年 5 月 23 日)phillipwood
gitster

pull: accept --rebase-mergesto recreate the branch topology

Similar to the preservemode simply passing the --preserve-mergesoption to the rebasecommand, the mergesmode simply passes the --rebase-mergesoption.

This will allow users to conveniently rebase non-trivial commit topologies when pulling new commits, without flattening them.

pull:接受--rebase-merges重新创建分支拓扑

类似于preserve简单地将--preserve-merges选项传递给rebase命令的merges模式,模式只是简单地传递 --rebase-merges选项。

这将允许用户在拉新提交时方便地重新设置非平凡的提交拓扑,而不会扁平化它们。



git rebaseman page now has a full section dedicated to rebasing history with merges.

git rebase手册页现在有一个完整的部分专门用于使用合并重新定位历史记录

Extract:

提炼:

There are legitimate reasons why a developer may want to recreate merge commits: to keep the branch structure (or "commit topology") when working on multiple, inter-related branches.

In the following example, the developer works on a topic branch that refactors the way buttons are defined, and on another topic branch that uses that refactoring to implement a "Report a bug" button.
The output of git log --graph --format=%s -5may look like this:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

The developer might want to rebase those commits to a newer masterwhile keeping the branch topology, for example when the first topic branch is expected to be integrated into mastermuch earlier than the second one, say, to resolve merge conflicts with changes to the DownloadButtonclass that made it into master.

This rebase can be performed using the --rebase-mergesoption.

开发人员可能想要重新创建合并提交的合理原因是:在处理多个相互关联的分支时保持分支结构(或“提交拓扑”)。

在以下示例中,开发人员在重构按钮定义方式的主题分支上工作,并在使用该重构实现“报告错误”按钮的另一个主题分支上工作。
的输出git log --graph --format=%s -5可能如下所示:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

开发人员可能希望master在保持分支拓扑的同时将这些提交重新设置为更新的提交,例如,当第一个主题分支预计master比第二个更早地被集成到时,例如,解决与更改的DownloadButton类的合并冲突 它变成master.

可以使用该--rebase-merges选项执行此变基。



See commit 1644c73for a small example:

有关一个小示例,请参阅提交 1644c73

rebase-helper--make-script: introduce a flag to rebase merges

The sequencer just learned new commands intended to recreate branch structure (similar in spirit to --preserve-merges, but with a substantially less-broken design).

Let's allow the rebase--helperto generate todo lists making use of these commands, triggered by the new --rebase-mergesoption.
For a commit topology like this (where the HEAD points to C):

- A - B - C (HEAD)
    \   /
      D

the generated todo list would look like this:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C

rebase-helper--make-script: 引入一个标志来rebase合并

音序器刚刚学习了旨在重新创建分支结构的新命令(在精神上与 类似--preserve-merges,但具有明显较少破坏的设计)。

让我们允许rebase--helper使用这些命令生成待办事项列表,由新--rebase-merges选项触发。
对于这样的提交拓扑(HEAD 指向 C):

- A - B - C (HEAD)
    \   /
      D

生成的待办事项列表如下所示:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C


What is the difference with --preserve-merge?
Commit 8f6aed7explains:

与 有--preserve-merge什么区别?
提交 8f6aed7解释说:

Once upon a time, this here developer thought: wouldn't it be nice if, say, Git for Windows' patches on top of core Git could be represented as a thicket of branches, and be rebased on top of core Git in order to maintain a cherry-pick'able set of patch series?

The original attempt to answer this was: git rebase --preserve-merges.

However, that experiment was never intended as an interactive option, and it only piggy-backed on git rebase --interactivebecause that command's implementation looked already very, very familiar: it was designed by the same person who designed --preserve-merges: yours truly.

曾几何时,这里的开发人员想:如果比方说,Git for Windows 在核心 Git 之上的补丁可以表示为分支的灌木丛,并在核心 Git 之上重新建立,这样不是很好吗?维护一组可挑选的补丁系列?

回答这个问题的最初尝试是:git rebase --preserve-merges

然而,那个实验从来没有打算作为一个交互式选项,它只是捎带,git rebase --interactive因为该命令的实现看起来已经非常非常熟悉了:它是由设计--preserve-merges: yours trues的同一个人设计的。

And by "yours truly", the author refers to himself: Johannes Schindelin (dscho), who is the main reason (with a few other heroes -- Hannes, Steffen, Sebastian, ...) that we have Git For Windows (even though back in the day -- 2009 -- that was not easy).
He is working at Microsoft since Sept. 2015, which makes sense considering Microsoft now heavily uses Git and needs his services.
That trend started in 2013 actually, with TFS. Since then, Microsoft manages the largest Git repository on the planet! And, since Oct. 2018, Microsoft acquired GitHub.

而“你的真实”,作者指的是他自己: Johannes Schindelin ( dscho),他是我们拥有 Git For Windows 的主要原因(以及其他一些英雄——Hannes、Steffen、Sebastian 等)(尽管回到当天 - 2009 - 这并不容易)。
从 2015 年 9 月开始在微软工作,考虑到微软现在大量使用 Git 并需要他的服务,这是有道理的。
这种趋势实际上始于 2013 年,即 TFS。从那时起,Microsoft 管理着地球上最大的 Git 存储库!而且,自 2018 年 10 月以来,微软收购了 GitHub

You can see Johannes speak in this videofor Git Merge 2018 in April 2018.

您可以2018 年 4 月的 Git Merge 2018视频中看到Johannes 的发言

Some time later, some other developer (I am looking at you, Andreas! ;-)) decided that it would be a good idea to allow --preserve-mergesto be combined with --interactive(with caveats!) and the Git maintainer (well, the interim Git maintainer during Junio's absence, that is) agreed, and that is when the glamor of the --preserve-mergesdesign started to fall apart rather quickly and unglamorously.

一段时间后,其他一些开发人员(我在看着你,安德烈亚斯!;-))决定允许--preserve-merges--interactive(有警告!)和 Git 维护者(嗯,临时 Git 维护者)结合是一个好主意在 Junio 缺席期间,也就是)同意了,那是--preserve-merges设计的魅力开始迅速而乏味地分崩离析的时候。

Here Jonathan is talking about Andreas Schwabfrom Suse.
You can see some of their discussions back in 2012.

乔纳森在这里谈论的是来自 Suse 的Andreas Schwab
您可以在 2012 年看到他们的一些讨论

The reason? In --preserve-mergesmode, the parents of a merge commit (or for that matter, of anycommit) were not stated explicitly, but were impliedby the commit name passed to the pickcommand.

This made it impossible, for example, to reorder commits.
Not to mention to move commits between branches or, deity forbid, to split topic branches into two.

Alas, these shortcomings also prevented that mode (whose original purpose was to serve Git for Windows' needs, with the additional hope that it may be useful to others, too) from serving Git for Windows' needs.

Five years later, when it became really untenable to have one unwieldy, big hodge-podge patch series of partly related, partly unrelated patches in Git for Windows that was rebased onto core Git's tags from time to time (earning the undeserved wrath of the developer of the ill-fated git-remote-hgseries that first obsoleted Git for Windows' competing approach, only to be abandoned without maintainer later) was really untenable, the "Git garden shears" were born: a script, piggy-backing on top of the interactive rebase, that would first determine the branch topology of the patches to be rebased, create a pseudo todo list for further editing, transform the result into a real todo list (making heavy use of the execcommand to "implement" the missing todo list commands) and finally recreate the patch series on top of the new base commit.

原因?--preserve-mergesmode 中,合并提交(或就此而言,任何提交)的父项没有明确说明,而是 由传递给command的提交名称暗示pick

例如,这使得重新排序 commits 变得不可能
更不用说在分支之间移动提交,或者,神禁止,将主题分支分成两个。

唉,这些缺点也阻止了这种模式(其最初的目的是为 Windows 的需求提供 Git,另外希望它也可能对其他人有用)为 Windows 的需求提供 Git。

五年后,当在 Windows 版 Git 中拥有一个笨拙的大杂烩补丁系列部分相关,部分不相关的补丁时,它变得真的站不住脚了,这些补丁不时重新基于核心 Git 的标签(招致开发人员不应有的愤怒)命运多舛的 git-remote-hg系列首先淘汰了 Git 用于 Windows 的竞争方法,后来在没有维护者的情况下被放弃)真的站不住脚,“ Git 花园剪刀诞生了:一个脚本,捎带在交互式 rebase 之上,这将首先确定要重新定位的补丁的分支拓扑,创建一个伪待办事项列表以供进一步编辑,将结果转换为真正的待办事项列表(大量使用exec命令“实现”缺少的待办事项列表命令),最后在新的基础提交之上重新创建补丁系列。

(The Git garden shears script is referenced in this patch in commit 9055e40)

(此补丁中的提交 9055e40引用了 Git 花园剪刀脚本)

That was in 2013.
And it took about three weeks to come up with the design and implement it as an out-of-tree script. Needless to say, the implementation needed quite a few years to stabilize, all the while the design itself proved itself sound.

With this patch, the goodness of the Git garden shears comes to git rebase -iitself.
Passing the --rebase-mergesoption will generate a todo list that can be understood readily, and where it is obvious how to reorder commits.
New branches can be introduced by inserting labelcommands and calling merge <label>.
And once this mode will have become stable and universally accepted, we can deprecate the design mistake that was --preserve-merges.

那是在 2013 年。
大约花了三周的时间来提出设计并将其实现为树外脚本。毋庸置疑,实施需要好几年才能稳定下来,同时设计本身证明了自己的合理性。

有了这个补丁,Git 园艺剪的优点就git rebase -i显现出来了
传递该--rebase-merges选项将生成一个易于理解的待办事项列表,并且很明显如何重新排序提交
可以通过插入label命令和调用来引入新的分支merge <label>
一旦这种模式变得稳定并被普遍接受,我们就可以弃用--preserve-merges.



Git 2.19 (Q3 2018) improves the new --rebase-mergesoption by making it work with --exec.

Git 2.19(2018 年第三季度)--rebase-merges通过使其与--exec.

The "--exec" option to "git rebase --rebase-merges" placed the exec commands at wrong places, which has been corrected.

" --exec"的 " " 选项git rebase --rebase-merges将 exec 命令放在错误的位置,已更正。

See commit 1ace63b(09 Aug 2018), and commit f0880f7(06 Aug 2018) by Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster--in commit 750eb11, 20 Aug 2018)

提交1ace63b(2018年8月9日),并提交f0880f7通过(2018年8月6日)约翰内斯Schindelin( )dscho
(由Junio C gitsterHamano合并-- --commit 750eb11,2018 年 8 月 20 日)

rebase --exec: make it work with --rebase-merges

The idea of --execis to append an execcall after each pick.

Since the introduction of fixup!/squash!commits, this idea was extended to apply to "pick, possibly followed by a fixup/squash chain", i.e. an exec would not be inserted between a pickand any of its corresponding fixupor squashlines.

The current implementation uses a dirty trick to achieve that: it assumes that there are only pick/fixup/squash commands, and then insertsthe execlines before any pickbut the first, and appends a final one.

With the todo lists generated by git rebase --rebase-merges, this simple implementation shows its problems: it produces the exact wrong thing when there are label, resetand mergecommands.

Let's change the implementation to do exactly what we want: look for picklines, skip any fixup/squash chains, and then insert the execline. Lather, rinse, repeat.

Note: we take pains to insert beforecomment lines whenever possible, as empty commits are represented by commented-out pick lines (and we want to insert a preceding pick's exec line beforesuch a line, not afterward).

While at it, also add execlines after mergecommands, because they are similar in spirit to pickcommands: they add new commits.

rebase --exec: 让它与 --rebase-merges

的想法--execexec在每个pick.

由于fixup!/squash!提交的引入,这个想法被扩展到适用于“pick,可能后跟一个 fixup/squash 链”,即一个 exec 不会被插入到 apick和它的任何对应的 fixuporsquash行之间。

当前实现使用一个卑鄙的手段来实现这一目标:它假设有只挑选/修正/壁球命令,然后 插入exec之前的任何线pick,但第一,并附加最后一节。

使用由 生成的待办事项列表git rebase --rebase-merges,这个简单的实现显示了它的问题:当有label,resetmerge命令时,它会产生完全错误的东西。

让我们更改实现以完全满足我们的要求:查找 pick行,跳过任何修复/压缩链,然后插入exec。起泡,冲洗,重复。

注意:我们尽可能注释行之前插入,因为空提交由注释掉的选择行表示(并且我们希望这样的行之前而不是之后插入前一个选择的 exec 行)。

同时,execmerge命令之后添加行,因为它们在精神上与pick命令相似:它们添加新的提交。



Git 2.22 (Q2 2019) fixes the usage of the refs/rewritten/ hierarchy to store a rebase intermediate states, which inherently makes the hierarchy per worktree.

Git 2.22(2019 年第 2 季度)修复了使用 refs/rewritten/hierarchy 来存储 rebase 中间状态的问题,这在本质上是使每个工作树的层次结构。

See commit b9317d5, commit 90d31ff, commit 09e6564(07 Mar 2019) by Nguy?n Thái Ng?c Duy (pclouds).
(Merged by Junio C Hamano -- gitster--in commit 917f2cd, 09 Apr 2019)

请参阅Nguy?n Thái Ng?c Duy ( ) 的commit b9317d5commit 90d31ffcommit 09e6564(2019 年 3 月 7 日(由Junio C Hamano合并-- --提交 917f2cd,2019 年 4 月 9 日)pclouds
gitster

Make sure refs/rewritten/ is per-worktree

确保 refs/rewritten/ 是每个工作树

a9be29c(sequencer: make refs generated by the labelcommand worktree-local, 2018-04-25, Git 2.19) adds refs/rewritten/as per-worktree reference space.
Unfortunately (my bad) there are a couple places that need update to make sure it's really per-worktree.

a9be29c(sequencer: make refs generated by the labelcommand worktree-local, 2018-04-25, Git 2.19) 添加refs/rewritten/为每个工作树的参考空间。
不幸的是(我的错)有几个地方需要更新以确保它真的是每个工作树。

- add_per_worktree_entries_to_dir()is updated to make sure ref listing look at per-worktree refs/rewritten/instead of per-repo one.

  • common_list[]is updated so that git_path()returns the correct location. This includes "rev-parse --git-path".

This mess is created by me.
I started trying to fix it with the introduction of refs/worktree,where all refs will be per-worktree without special treatments.
Unfortunate refs/rewritten came before refs/worktree so this is all we can do.

- add_per_worktree_entries_to_dir()更新以确保参考列表查看每个工作树refs/rewritten/而不是每个存储库。

  • common_list[]已更新,以便git_path()返回正确的位置。这包括“ rev-parse --git-path”。

这个烂摊子是我造成的。
我开始尝试通过引入refs/worktree,所有 refs 在没有特殊处理的情况下每个工作树的位置来修复它。
不幸的 refs/rewritten 出现在 refs/worktree 之前,所以这就是我们所能做的。



With Git 2.24 (Q4 2019), "git rebase --rebase-merges" learned to drive different merge strategies and pass strategy specific options to them.

在 Git 2.24(2019 年第四季度)中,“ git rebase --rebase-merges”学会了驱动不同的合并策略并将特定于策略的选项传递给它们。

See commit 476998d(04 Sep 2019) by Elijah Newren (newren).
See commit e1fac53, commit a63f990, commit 5dcdd74, commit e145d99, commit 4e6023b, commit f67336d, commit a9c7107, commit b8c6f24, commit d51b771, commit c248d32, commit 8c1e240, commit 5efed0e, commit 68b54f6, commit 2e7bbac, commit 6180b20, commit d5b581f(31 Jul 2019) by Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster--in commit 917a319, 18 Sep 2019)

请参阅Elijah Newren ( ) 的提交 476998d(2019 年 9 月 4 日。 见提交e1fac53提交a63f990提交5dcdd74提交e145d99提交4e6023b提交f67336d提交a9c7107提交b8c6f24提交d51b771提交c248d32提交8c1e240提交5efed0e提交68b54f6提交2e7bbac提交6180b20提交d5b581f(31 2019 年 7 月)由newren
约翰内斯·辛德林 ( dscho)
(由Junio C gitsterHamano合并-- --提交 917a319 中,2019 年 9 月 18 日)



With Git 2.25 (Q1 2020), the logic used to tell worktree local and repository global refs apart is fixed, to facilitate the preserve-merge.

在 Git 2.25(2020 年第一季度)中,用于区分工作树本地引用和存储库全局引用的逻辑是固定的,以促进保留合并。

See commit f45f88b, commit c72fc40, commit 8a64881, commit 7cb8c92, commit e536b1f(21 Oct 2019) by SZEDER Gábor (szeder).
(Merged by Junio C Hamano -- gitster--in commit db806d7, 10 Nov 2019)

请参阅SZEDER Gábor ( ) 的提交 f45f88b提交 c72fc40提交 8a64881提交 7cb8c92提交 e536b1f(2019 年 10 月 21 日(由Junio C Hamano合并-- --提交 db806d7 中,2019 年 11 月 10 日)szeder
gitster

path.c: don't call the matchfunction without value in trie_find()

Signed-off-by: SZEDER Gábor

'logs/refs' is not a working tree-specific path, but since commit b9317d55a3(Make sure refs/rewritten/ is per-worktree, 2019-03-07, v2.22.0-rc0) 'git rev-parse --git-path' has been returning a bogus path if a trailing '/' is present:

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

We use a triedata structure to efficiently decide whether a path belongs to the common dir or is working tree-specific.

As it happens b9317d55a3triggered a bug that is as old as the trieimplementation itself, added in 4e09cf2acf("path: optimize common dir checking", 2015-08-31, Git v2.7.0-rc0 -- mergelisted in batch #2).

  • According to the comment describing trie_find(), it should only call the given match function 'fn' for a "/-or-\0-terminated prefix of the key for which the trie contains a value".
    This is not true: there are three places where trie_find() calls the match function, but one of them is missing the check for value's existence.

  • b9317d55a3added two new keys to the trie:

    • 'logs/refs/rewritten', and
    • 'logs/refs/worktree', next to the already existing 'logs/refs/bisect'.
      This resulted in a trienode with the path 'logs/refs/', which didn't exist before, and which doesn't have a value attached.
      A query for 'logs/refs/' finds this node and then hits that one callsite of the matchfunction which doesn't check for the value's existence, and thus invokes the matchfunction with NULLas value.
  • When the matchfunction check_common()is invoked with a NULLvalue, it returns 0, which indicates that the queried path doesn't belong to the common directory, ultimately resulting the bogus path shown above.

Add the missing condition to trie_find()so it will never invoke the match function with a non-existing value.

check_common()will then no longer have to check that it got a non-NULL value, so remove that condition.

I believe that there are no other paths that could cause similar bogus output.

AFAICT the only other key resulting in the match function being called with a NULLvalue is 'co' (because of the keys 'common' and 'config').

However, as they are not in a directory that belongs to the common directory the resulting working tree-specific path is expected.

path.c: 不要在match没有值的情况下调用函数trie_find()

签字人:SZEDER Gábor

'logs/refs' 不是特定于工作树的路径,但自从提交 b9317d55a3(确保 refs/rewritten/ 是每个工作树,2019-03-07,v2.22.0-rc0)以来,' git rev-parse --git-path' 一直在返回假路径如果尾随 ' /' 存在:

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

我们使用trie数据结构来有效地确定路径是属于公共目录还是特定于工作树。

碰巧b9317d55a3触发了一个与trie实现本身一样古老的错误,在4e09cf2acf 中添加(“ path:优化公共目录检查”,2015 年 8 月 31 日,Git v2.7.0-rc0 --合并第 2 批中列出)。

  • 根据描述的注释trie_find(),它应该只为“trie 包含值的键的/-或-\0 终止前缀”调用给定的匹配函数'fn'。
    这不是真的:trie_find() 在三个地方调用 match 函数,但其​​中之一缺少对值是否存在的检查。

  • b9317d55a3向 中添加了两个新密钥trie

    • ' logs/refs/rewritten',和
    • ' logs/refs/worktree',在已经存在的 ' logs/refs/bisect'旁边。
      这导致了trie具有路径“ logs/refs/”的节点,该节点以前不存在,并且没有附加值。
      对“ logs/refs/”的查询找到此节点,然后命中该match函数的一个调用站点,该调用站点不检查该值是否存在,从而match使用NULLas 值调用该函数。
  • match函数check_common()被一个NULL值调用时,它返回0,这表明查询的路径不属于公共目录,最终导致如上所示的伪造路径。

添加缺少的条件,trie_find()以便它永远不会使用不存在的值调用匹配函数。

check_common()然后将不再需要检查它是否具有非 NULL 值,因此删除该条件。

我相信没有其他路径会导致类似的虚假输出。

AFAICT 导致使用NULL值调用匹配函数的唯一其他键是“ co”(因为键“ common”和“ config”)。

但是,由于它们不在属于公共目录的目录中,因此需要生成特定于工作树的路径。