本文翻译自:What exactly does git's “rebase --preserve-merges” do (and why?)
Git's documentation for the rebase
command is 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
? 那么,当您使用--preserve-merges
时,实际发生了什么? How does it differ from the default behavior (without that flag)? 它与默认行为(没有该标志)有何不同? What does it mean to "recreate" a merge, etc. “重新创建”合并是什么意思,等等。
参考:https://stackoom.com/question/14mKU/git的-rebase-preserve-merges-到底做了什么-为什么
As with a normal git rebase, git with --preserve-merges
first identifies a list of commits made in one part of the commit graph, and then replays those commits on top of another part. 与普通的git rebase一样,带有--preserve-merges
git首先标识在提交图的一部分中进行的提交的列表,然后在另一部分的顶部重播这些提交。 The differences with --preserve-merges
concern which commits are selected for replay and how that replaying works for merge commits. 与--preserve-merges
的区别涉及选择要重播的提交以及重播对合并提交的工作方式。
To be more explicit about the main differences between normal and merge-preserving rebase: 要更明确地了解普通和保留合并基准之间的主要区别:
git checkout
), whereas normal rebase doesn't have to worry about that. 重播合并提交还需要显式签出特定的提交( git checkout
),而正常的重新设置不必为此担心。 First I will try to describe "sufficiently exactly" what rebase --preserve-merges
does, and then there will be some examples. 首先,我将尝试“足够准确地”描述rebase --preserve-merges
所做的工作,然后将提供一些示例。 One can of course start with the examples, if that seems more useful. 如果看起来更有用,那么当然可以从示例开始。
The Algorithm in "Brief" “简要”中的算法
If you want to really get into the weeds, download the git source and explore the file git-rebase--interactive.sh
. 如果您真的想深入研究杂草,请下载git源并浏览文件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".) (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. 为了减少需要考虑的事情,我采取了一些自由措施。 (eg I don't try to capture with 100% accuracy the precise order in which computations take place, and ignore some less central-seeming topics, eg 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: 或多或少:
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-merges
is comparatively complicated. Rebase --preserve-merges
比较复杂。 Here's as simple as I've been able to make it without losing things that seem pretty important: 这就像我能够做到的一样简单,而且不会丢失看起来很重要的东西:
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 C
argument should be very similar. 用--onto C
参数重新设置应该非常相似。 Just instead of starting commit playback at the HEAD of B, you start commit playback at the HEAD of C instead. 与其在B的HEAD上开始提交回放,不如在C的HEAD上开始提交回放。 (And use C_new instead of B_new.) (并使用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. 假设我们使用正常的不保留合并的基准在主数据库(C)之上重新建立主题(H)。 (For example, checkout topic; rebase master .) In that case, git would select the following commits for replay: (例如, checkout主题; rebase master 。)在这种情况下,git将选择以下提交进行重播:
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-merges
rebase 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: 如果我们改为在C的基础上对H进行--preserve-merges
merges重新设置(例如, 检出主题; rebase --preserve-merges master 。)在这种新情况下,git将选择以下提交进行重播:
Now m was chosen for replay. 现在m 被选中进行重放。 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 (ie recreated) version of D. Same for E', etc.. Every commit not on master has been replayed. 同样,D'是D的精选版本(即重新创建的版本)。E'相同,依此类推。每一次不在master上的提交都被重播。 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). 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不同,保留合并的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: 如果我们在H(主题)的基础上将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 如果我们将主题重新设置为主主题(保留合并),则重播的提交将为
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. 在某些“空提交”情况下,保留合并的rebase可能会造成混乱。 At least this is true only some older versions of git (eg 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-merges
of H (topic) onto D (master), then the following commits are chosen for replay: 如果我们尝试git rebase --preserve-merges
H(topic)的git rebase --preserve-merges
到D(master)上,则选择以下提交进行重播:
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 (ie one where the diff between successive revisions is empty). 请注意,在m1中合并的更改(B,F)应该已经合并到D中。(这些更改应该已经合并到m2中,因为m2将B和F的子级合并在一起。)因此,从概念上讲,在m1的顶部重播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不喜欢创建空的提交。
Git 2.18 (Q2 2018) will improve considerably the --preserve-merge
option 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,Q2 2019,实际上不推荐使用 --preserve-merge
,而Git 2.25,Q1 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
) . 见提交25cff9f , 提交7543f6f , 提交1131ec9 , 提交7ccdf65 , 提交537e7d6 , 提交a9be29c , 提交8f6aed7 , 提交1644c73 , 提交d1e8b01 , 提交4c68e7d , 提交9055e40 , 提交cb5206e , 提交a01c2a5 , 提交2f6b1d1 , 提交bf5c057 (2018年4月25日)由Johannes Schindelin( dscho
) 。
See commit f431d73 (25 Apr 2018) by Stefan Beller ( stefanbeller
) . 参见Stefan Beller( stefanbeller
)的 commit f431d73 (25 Apr 2018 ) 。
See commit 2429335 (25 Apr 2018) by Phillip Wood ( phillipwood
) . 参见Phillip Wood( phillipwood
)的 commit 2429335 (2018年4月25日) 。
(Merged by Junio C Hamano -- gitster
-- in commit 2c18e6a , 23 May 2018) (由Junio C gitster
- gitster
在commit 2c18e6a中合并 ,2018年5月23日)
pull
: accept--rebase-merges
to recreate the branch topologypull
:accept--rebase-merges
重新创建分支拓扑Similar to the
preserve
mode simply passing the--preserve-merges
option to therebase
command, themerges
mode simply passes the--rebase-merges
option. 类似于preserve
模式,只是将--preserve-merges
--rebase-merges
选项传递给rebase
命令,merges
模式仅传递--rebase-merges
选项。This will allow users to conveniently rebase non-trivial commit topologies when pulling new commits, without flattening them. 这将使用户在拉动新提交时可以方便地为非平凡的提交拓扑建立基础,而无需对其进行展平。
git rebase
man 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 ofgit log --graph --format=%s -5
may look like this: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
The developer might want to rebase those commits to a newer
master
while keeping the branch topology, for example when the first topic branch is expected to be integrated intomaster
much earlier than the second one, say, to resolve merge conflicts with changes to theDownloadButton
class that made it intomaster
. 开发人员可能希望在保留分支拓扑的同时将这些提交重新分配到较新的master
,例如,当第一个主题分支要比第二个主题更早地集成到master
,例如,解决合并冲突和对DownloadButton
更改成为master
班级。This rebase can be performed using the
--rebase-merges
option. 可以使用--rebase-merges
选项执行此变基。
See commit 1644c73 for a small example: 参见commit 1644c73的一个小例子:
rebase-helper
--make-script
: introduce a flag to rebase mergesrebase-helper
--make-script
:引入一个标志来重新合并The sequencer just learned new commands intended to recreate branch structure ( similar in spirit to
--preserve-merges
, but with a substantially less-broken design ). 排序器刚刚学习了旨在重新创建分支结构的新命令( 本质上与--preserve-merges
类似,但设计基本没有中断 )。Let's allow the
rebase--helper
to generate todo lists making use of these commands, triggered by the new--rebase-merges
option. 让我们让rebase--helper
通过新的--rebase-merges
选项触发的命令,使用这些命令生成待办事项列表。
For a commit topology like this (where the HEAD points to C): 对于这样的提交拓扑(HEAD指向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
What is the difference with --preserve-merge
? --preserve-merge
什么区别?
Commit 8f6aed7 explains: 提交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? 曾几何时,这名开发人员曾想过:如果说,如果将Git for Windows的核心Git之上的补丁表示为分支的灌木丛,并在核心Git之上重新建立基础,那不是很好吗?维护一套可挑选的补丁系列?
The original attempt to answer this was:
git rebase --preserve-merges
. 回答这个问题的最初尝试是:git rebase --preserve-merges
。However, that experiment was never intended as an interactive option, and it only piggy-backed on
git rebase --interactive
because that command's implementation looked already very, very familiar: it was designed by the same person who designed--preserve-merges
: yours truly. 但是,该实验从未打算用作交互选项,它只是背负在git rebase --interactive
因为该命令的实现看起来已经非常非常熟悉:它是由设计--preserve-merges
的同一人设计--preserve-merges
:敬上。
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 ). 并以“真正的您”来dscho
自己: 约翰内斯· dscho
( dscho
) ,这是我们拥有Windows的Git(甚至还有其他英雄,包括汉尼斯,史蒂芬,塞巴斯蒂安等)的主要原因(甚至尽管回溯到2009年的今天-这并不容易 )。
He is working at Microsoft since Sept. 2015 , which makes sense considering Microsoft now heavily uses Git and needs his services. 自2015年9月以来 ,他一直在Microsoft工作,考虑到Microsoft现在大量使用Git并需要他的服务,这很有意义。
That trend started in 2013 actually, with TFS . 这种趋势实际上始于2013年的TFS 。 Since then, Microsoft manages the largest Git repository on the planet ! 从那时起,Microsoft管理着地球上最大的Git存储库 ! And, since Oct. 2018, Microsoft acquired GitHub . 而且, 自2018年10月以来,微软收购了GitHub 。
You can see Johannes speak in this video for 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-merges
to 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-merges
design started to fall apart rather quickly and unglamorously. 一段时间后,另一位开发人员(我在看着你,安德里亚斯!;-))决定,将--preserve-merges
与--interactive
(带有警告!)和Git结合起来是一个好主意。维护者(嗯,就是在Junio不在期间的临时Git维护者)表示同意,这就是--preserve-merges
设计的魅力开始迅速而毫不掩饰地瓦解了。
Here Jonathan is talking about Andreas Schwab from Suse. 乔纳森(Jonathan)在这里谈论苏斯 (Suse)的安德里亚斯·施瓦布 ( Andreas Schwab) 。
You can see some of their discussions back in 2012 . 您可以在2012年看到他们的一些讨论 。
The reason? 原因? In
--preserve-merges
mode, the parents of a merge commit (or for that matter, of any commit) were not stated explicitly, but were implied by the commit name passed to thepick
command . 在--preserve-merges
模式下,未明确说明合并提交(或就此而言, 任何提交)的父项,但由传递给pick
命令的提交名隐含了 。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. las,这些缺点也阻止了该模式(其最初目的是为Windows的Git服务,另外希望它也可能对其他人有用)无法为Windows的Git服务。
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-hg
series 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 theexec
command to "implement" the missing todo list commands) and finally recreate the patch series on top of the new base commit. 五年后,当在Git for Windows中拥有一个笨拙,大杂烩的补丁系列变得不切实际的时候,部分补丁,部分无关的补丁会不时地基于核心Git的标签(引起开发者的愤怒)不幸的git-remote-hg
系列最初使Git for Windows的竞争方法过时,后来被弃置,而无需维护人员)确实站不住脚,“ Git花园剪 ” 诞生了 :一个脚本, 背负着小本子交互基础,首先确定要重新基础补丁的分支拓扑,创建伪待办事项列表以进行进一步编辑,将结果转换为实际待办事项列表(大量使用exec
命令来“实现”缺少的内容todo list命令),最后在新的基本提交之上重新创建补丁系列。
(The Git garden shears script is referenced in this patch in commit 9055e40 ) (此补丁在提交9055e40中引用了Git花园剪脚本)
That was in 2013. 那是在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 -i
itself . 有了这个补丁,git rebase -i
本身就可以使用Git园林剪了 。
Passing the--rebase-merges
option will generate a todo list that can be understood readily, and where it is obvious how to reorder commits . 传递--rebase-merges
选项将生成一个待办事项列表,该列表很容易理解,而且很明显如何重新排列提交 。
New branches can be introduced by insertinglabel
commands and callingmerge
. 可以通过插入label
命令并调用merge
来引入新的分支。
And once this mode will have become stable and universally accepted, we can deprecate the design mistake that was--preserve-merges
. 一旦此模式变得稳定并被普遍接受,我们就可以弃用--preserve-merges
的设计错误 。
Git 2.19 (Q3 2018) improves the new --rebase-merges
option by making it work with --exec
. Git 2.19(Q3 2018)通过与--exec
一起使用改进了--rebase-merges
选项。
The " --exec
" option to " git rebase --rebase-merges
" placed the exec commands at wrong places, which has been corrected. git rebase --rebase-merges
的“ --exec
”选项git rebase --rebase-merges
exec命令放在错误的位置,该位置已得到纠正。
See commit 1ace63b (09 Aug 2018), and commit f0880f7 (06 Aug 2018) by Johannes Schindelin ( dscho
) . 见提交1ace63b (2018年8月9日),并提交f0880f7 (2018年8月6日)由约翰内斯Schindelin( dscho
) 。
(Merged by Junio C Hamano -- gitster
-- in commit 750eb11 , 20 Aug 2018) (由Junio C gitster
- gitster
在commit 750eb11中合并 ,2018年8月20日)
rebase --exec
: make it work with--rebase-merges
rebase --exec
:与--rebase-merges
一起使用The idea of
--exec
is to append anexec
call after eachpick
.--exec
的想法是在每次pick
之后追加exec
调用。Since the introduction of
fixup!
自引入fixup!
/squash!
/ squash!
commits, this idea was extended to apply to "pick, possibly followed by a fixup/squash chain", ie an exec would not be inserted between apick
and any of its correspondingfixup
orsquash
lines. 提交,这个想法被扩展到适用于“拾取,可能后跟一个修正/壁球链”,即一个执行者不会被插入在一个pick
与它的任何对应的fixup
或squash
线之间。The current implementation uses a dirty trick to achieve that: it assumes that there are only pick/fixup/squash commands, and then inserts the
exec
lines before anypick
but the first, and appends a final one. 当前的实现使用了一个肮脏的技巧来实现该目标:它假定仅存在pick / fixup / squash命令,然后在除第一个pick
之外的任何pick
之前插入exec
行,并附加最后一个。With the todo lists generated by
git rebase --rebase-merges
, this simple implementation shows its problems: it produces the exact wrong thing when there arelabel
,reset
andmerge
commands. 使用git rebase --rebase-merges
生成的待办事项列表,此简单的实现显示了它的问题:当存在label
,reset
和merge
命令时,它会产生完全错误的事情。Let's change the implementation to do exactly what we want: look for
pick
lines, skip any fixup/squash chains, and then insert theexec
line . 让我们更改实现以完全实现我们想要的功能: 查找pick
线,跳过所有修正/压扁链,然后插入exec
行 。 Lather, rinse, repeat. 泡沫,冲洗,重复。Note: we take pains to insert before comment lines whenever possible, as empty commits are represented by commented-out pick lines (and we want to insert a preceding pick's exec line before such a line, not afterward). 注意:我们会尽量在注释行之前插入,因为空提交由注释掉的选择行表示(并且我们希望在该行之前而不是之后插入之前的选择的exec行)。
While at it, also add
exec
lines aftermerge
commands, because they are similar in spirit topick
commands: they add new commits. 同时,还要在merge
命令之后添加exec
行,因为它们在本质上与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年第二季度)修复了使用refs / rewrite /层次结构来存储变基中间状态的问题,这固有地使每个工作树成为层次结构。
See commit b9317d5 , commit 90d31ff , commit 09e6564 (07 Mar 2019) by Nguyễn Thái Ngọc Duy ( pclouds
) . 请参阅NguyễnTháiNgọcDuy( pclouds
)的 commit b9317d5 , commit 90d31ff和commit 09e6564 (2019年3月7日) 。
(Merged by Junio C Hamano -- gitster
-- in commit 917f2cd , 09 Apr 2019) (由Junio C gitster
- gitster
在commit 917f2cd中合并 ,2019年4月9日)
Make sure refs/rewritten/ is per-worktree 确保ref / rewrite /是每个工作树
a9be29c (sequencer: make refs generated by the label
command worktree-local, 2018-04-25, Git 2.19) adds refs/rewritten/
as per-worktree reference space. a9be29c (序列器:由label
命令worktree-local生成的ref,2018-04-25,Git 2.19)将refs/rewritten/
rewrite refs/rewritten/
作为每个工作树的参考空间。
Unfortunately (my bad) there are a couple places that need update to make sure it's really per-worktree. 不幸的是(我不好),有几个地方需要更新以确保它确实是每个工作树。
- add_per_worktree_entries_to_dir()
is updated to make sure ref listing look at per-worktreerefs/rewritten/
instead of per-repo one.- add_per_worktree_entries_to_dir()
更新了- add_per_worktree_entries_to_dir()
,以确保引用列表查看每个工作树的refs/rewritten/
而不是每个回购。
common_list[]
is updated so thatgit_path()
returns the correct location.common_list[]
已更新,以便git_path()
返回正确的位置。 This includes "rev-parse --git-path
". 这包括“rev-parse --git-path
”。This mess is created by me. 这个烂摊子是我创造的。
I started trying to fix it with the introduction ofrefs/worktree,
where all refs will be per-worktree without special treatments. 我开始尝试通过引入refs/worktree,
来修复它refs/worktree,
其中所有refs将是每个工作树,无需特殊处理。
Unfortunate refs/rewritten came before refs/worktree so this is all we can do. 不幸的ref / rewrite早于ref / 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
) . 参见Elijah Newren( newren
)的 commit 476998d (2019年9月4日) 。
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
) . 见提交e1fac53 , 提交a63f990 , 提交5dcdd74 , 提交e145d99 , 提交4e6023b , 提交f67336d , 提交a9c7107 , 提交b8c6f24 , 提交d51b771 , 提交c248d32 , 提交8c1e240 , 提交5efed0e , 提交68b54f6 , 提交2e7bbac , 提交6180b20 , 提交d5b581f (31 Johannes Schindelin( dscho
)于 2019年7月dscho
。
(Merged by Junio C Hamano -- gitster
-- in commit 917a319 , 18 Sep 2019) (由Junio C gitster
- gitster
在commit 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年第1季度)中,用于区分工作树本地引用和存储库全局引用的逻辑是固定的,以促进保留合并。
See commit f45f88b , commit c72fc40 , commit 8a64881 , commit 7cb8c92 , commit e536b1f (21 Oct 2019) by SZEDER Gábor ( szeder
) . 见提交f45f88b , 提交c72fc40 , 提交8a64881 , 提交7cb8c92 , 提交e536b1f通过(2019年10月21日) SZEDER的Gabor( szeder
) 。
(Merged by Junio C Hamano -- gitster
-- in commit db806d7 , 10 Nov 2019) (由Junio C gitster
- gitster
在commit db806d7中合并 ,2019年11月10日)
path.c
: don't call thematch
function without value intrie_find()
path.c
:不要在trie_find()
没有值的情况下调用match
函数Signed-off-by: SZEDER Gábor 签名人:SZEDERGá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: 'logs / refs'不是工作树特定的路径,但是由于提交b9317d55a3 (确保refs / rewrite /是每个工作树,2019-03-07,v2.22.0-rc0)'git rev-parse --git-path
如果存在尾随的'/
',则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/
We use a
trie
data structure to efficiently decide whether a path belongs to the common dir or is working tree-specific. 我们使用trie
数据结构来有效地确定路径是属于公共目录还是特定于工作树的路径。As it happens b9317d55a3 triggered a bug that is as old as the
trie
implementation itself, added in 4e09cf2acf ("path
: optimize common dir checking", 2015-08-31, Git v2.7.0-rc0 -- merge listed in batch #2 ). 发生这种情况时, b9317d55a3触发了一个与trie
实现本身一样古老的错误,已在4e09cf2acf中添加(“path
:优化通用目录检查”,2015-08-31,Git v2.7.0-rc0- 合并在批次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". 根据描述trie_find()
的注释,它只应为“包含trie的值的键的/-或-\\ 0终止的前缀”调用给定的匹配函数'fn'。
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. 这是不正确的:trie_find()在三个地方调用match函数,但是其中之一缺少对值是否存在的检查。b9317d55a3 added two new keys to the
trie
: b9317d55a3向trie
添加了两个新键:
- '
logs/refs/rewritten
', and 'logs/refs/rewritten
',以及- '
logs/refs/worktree
', next to the already existing 'logs/refs/bisect
'. “logs/refs/worktree
”,在已存在的“logs/refs/bisect
”旁边。
This resulted in atrie
node with the path 'logs/refs/
', which didn't exist before, and which doesn't have a value attached. 这导致一个trie
节点的路径为'logs/refs/
',该节点以前不存在,并且没有附加值。
A query for 'logs/refs/
' finds this node and then hits that one callsite of thematch
function which doesn't check for the value's existence, and thus invokes thematch
function withNULL
as value. 对'logs/refs/
'的查询找到了该节点,然后命中了不检查该值是否存在的match
函数的一个调用站点,因此调用了以NULL
作为value的match
函数。When the
match
functioncheck_common()
is invoked with aNULL
value, it returns 0, which indicates that the queried path doesn't belong to the common directory, ultimately resulting the bogus path shown above. 当使用NULL
值调用match
函数check_common()
,它返回0,这表明查询的路径不属于公共目录,最终导致上面显示的虚假路径。Add the missing condition to
trie_find()
so it will never invoke the match function with a non-existing value. 将缺少的条件添加到trie_find()
这样它将永远不会调用不存在值的match函数。
check_common()
will then no longer have to check that it got a non-NULL value, so remove that condition. 然后,check_common()
将不再需要检查它是否具有非NULL值,因此删除该条件。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
NULL
value is 'co
' (because of the keys 'common
' and 'config
'). AFAICT导致以NULL
值调用match函数的唯一其他键是'co
'(因为键为'common
'和'config
')。However, as they are not in a directory that belongs to the common directory the resulting working tree-specific path is expected. 但是,由于它们不在属于公共目录的目录中,因此预期会出现生成的工作树特定路径。