将整个git分支重新整理到孤立分支上,同时保持提交树不变 [英] Rebase entire git branch onto orphan branch while keeping commit tree intact
问题描述
我有一个存储库,其中有两个分支, master
和 master-old
,它被创建为一个孤儿分支。
现在我想将整个 master
转换为 master-旧的
,但每个提交的树应该保持不变,即每个提交的工作副本 master
和 master-
当前状态
应该与rebase之前和之后的完全相同。 -------------
A - B - C - D < - master
E - F - G - H < - master-old
希望的状态
-------------
E'- F'- G'- H'- A'-B ' - C'-D'<--- master
我试图用 git rebase --onto master-old --root
。问题在于,对于 master
以及 master-old
的整个提交历史的初始提交,a很多相同的文件被创建,所以我得到了大量的冲突解决。
有没有一种方法来重写历史记录,以保持每棵树提交完整?
鉴于您希望保留与原始<$ c相关联的树 $ c> A - B - C - D 一系列的提交,毕竟你并不是真的想要 这就是 Git发现从给定的 如果新的提交(与其可能被改变的可能不是树,可能被改变,可能不是父,可能改变,可能不是消息等)与原始提交100%一点一点地相同,新提交的哈希ID不变。在这种情况下, next 提交的默认新父代与原始父代相同。否则,下一次提交的默认新父代就是我们刚才提交的那个。 (在实践中,因为提交图可以再次发散并合并,因为您可以跳过提交或添加新的提交,那么filter-branch真正做的是将旧提交哈希映射到新提交哈希。每次创建副本时,它都会输入一对:< old- hash,new-hash>添加到这个映射中,但对于一个简单的线性链,你可以把它想象成只记得最近一次提交的新哈希ID。) 现在,您在这里遇到的问题是,您希望更改一个特定提交的父哈希ID,即根提交。有一个特别的过滤器, - parent-filter< command> 因此,您可以测试stdin是否为空,如果是,输出 (不是你要求的,但可能更好)。 (要获得 另外两种方法可以在这里值得一提。一个是使用 最后, 这意味着您可以构建一个新的Git对象,它非常像commit 现在你有这个新的对象 - 我们称之为 (基于 code> git log 转到 view 从commit 当您使用 如果您愿意,要求3可让您查看原始(未替换)的历史记录。第2项意味着在 由于上面的第2项,您可能想要进行替换,确保它们按您喜欢的方式工作,然后然后运行 I have a repo in which I have two branches, Now I want to rebase the entirety of I tried to accomplish this using Is there a way to rewrite history in a way that keeps the tree of each commit intact? Given that you want to retain the trees associated with the original This is where Git finds every commit reachable from the given If the new commit (with its maybe-altered-maybe-not tree, maybe-altered-maybe-not parent, maybe-altered-maybe-not message, etc.) is 100% bit-for-bit identical to the original commit, the new commit's hash ID is unchanged. In that case, the default "new parent" for the next commit is the same as the original parent. Otherwise the default "new parent" for the next commit is the one we just made. (In practice, because the commit graph can diverge and merge again and because you can skip commits or add new commits, what filter-branch really does is to make a mapping of old commit hash to new commit hash. Each time it makes a copy, it enters a pair: <old-hash, new-hash> into this mapping. For a simple linear chain, though, you can think of this as just remembering the most recent commit's new hash ID.) Now, the issue you have here is that you want to change the parent hash ID of one specific commit, namely the root commit. There's a filter specifically for that, the --parent-filter <command> Hence, you could test whether stdin is empty, and if so, output (not quite what you asked for, but maybe even better). (To get the The other two ways to do this are worth mentioning here. One is to use the Finally, there's the What this means is that you can construct a new Git object that is very much like commit Now that you have this new object—let's call it (based on When When you use Requirement 3 lets you see the original (unreplaced) history, if you like. Item 2 means that on Because of item 2 above, you might want to make a replacement, make sure it all works the way you like, and then run 这篇关于将整个git分支重新整理到孤立分支上,同时保持提交树不变的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! A
添加到您的新提交 A'
,其父元素 H
,然后复制附加到 B
添加到其父$ A'
的新提交 B'
中,并且等等。
git filter-branch
很好的地方。运行时:
git filter-branch< filter-list> <分支名称>
< branch-name>
,然后拷贝每个提交。无论如何,从逻辑上讲,通过提取整个提交,在< filter-list>
中运行每个过滤器,然后创建一个新的使用生成的树和消息进行提交。它通过Git的正常顺序,即通过历史转发而不是后退来执行复制过程。
- parent-filter
。还有两种方法可以做到这一点,但我们首先描述 - parent-filter
。这来自 git filter-branch
documentation :
这是用于重写提交的父列表的过滤器。它将
接收stdin上的父字符串,并应在stdout上输出新的父
字符串。父字符串的格式为
git-commit-tree(1):对于初始提交为空,-p parent对于
正常提交以及-p parent1 -p parent2 -p parent3 ...对于
合并提交。
-p
。结果将是:
$ pre $ E $ F $ G $ H $ A'-B'-C'- D'< - master
EFGH
链复制,您必须通过 master-old
也作为一个正面参考,并且由于任何按位相同的提交都必须具有与原始相同的哈希ID,因此您必须至少进行一次更改以提交 E
,例如改变提交者tiemstamp一秒钟。)
- commit-filter
:这是实际进行新提交的命令。你可以在这里做任何事情,包括完全省略一些提交;但所有其他过滤器的原因是为了让事情更容易,所以在这种情况下根本没有理由使用提交过滤器。
使用
git replace
git replace
命令。 git replace
的作用是创建一个新的对象,它保留在存储库中,由 refs / replace / $ c $中的特殊名称引用c>名称空间。每当Git通过其哈希ID查看某个对象时,Git通常首先检查是否存在
refs / replace /< hash-id>
。如果是这样,Git会查看该引用所指向的对象。
A
,但略有不同。细微的区别在于新的提交对象有一个存储在其中的父哈希ID。父哈希ID是提交 H
的哈希ID。 (请注意,它具有与 A
相同的树。)
A'
- 你把它放到版本库中并使 refs / replace /< big-丑陋哈希>
指向它:
A - B - C - D < - 主
E - F - G - H< - master-old
\
A'< - refs / replace / deadcabf001 ...
$ / code> A
的实际散列值,这可能不是真的 deadcabf001 ...
,所以在这里使用正确的ID)
D
开始的历史记录,它会查看commit D
,然后得到 D
的父母ID C
,在提交 C
时,获取 B
的ID并继续提交 B
,得到 A
的ID和... whoa,嘿,还有一个 refs / replace /
为这一个!毕竟,我们不要看 A
!我们来看看 A'
!它将 A'
显示为 B
的父项,然后移至 A'
的父母,并显示 H
,然后显示 G
等等。 p>
git replace
时,您不必复制任何其他提交。 / em> 你有一个提交历史,其中新的更好的提交取代了旧的不太好的提交,但两者实际上并存。 Git在这些条件下使用替换:
refs / replace / 散列
在参考文献中;和
git --no-replace-objects
。
git clone
中,默认情况下,您不会 获取替换项。你必须明确地要求他们(这并不难,但没有任何好的简单前端)。
使用filter-branch替换
git filter-branch
。因为你没有运行 git --no-replace-objects filter-branch
,所以Git会看到替换 commit A '
而不是原始提交 A
。因此它会复制 A'
而不是 A
。你不需要 - parent-filter
。当它复制 E
到 H
时,新的副本将与原始文件完全相同,所以那些将保持不变。最终结果将与您使用正确的父级过滤器运行 git filter-branch
相同。master
and master-old
, which was created as an orphan branch.master
onto master-old
, but the tree of each commit should stay unchanged, i.e. the working copies of each commit on master
and master-old
should look exactly the same way before and after the rebase.Current state
-------------
A - B - C - D <--- master
E - F - G - H <--- master-old
Desired state
-------------
E'- F'- G'- H'- A'- B'- C'- D' <--- master
git rebase --onto master-old --root
. The problem is, that in both, the initial commit to master
and the entire commit history of master-old
, a lot of the same files were created, so I get a huge amount of conflicts to resolve.A--B--C--D
series of commits, you don't really want to rebase after all. Rebasing implies turning commits into diffs (changesets) and then applying those changesets, one at a time, to some existing starting point—but all you want to do is to copy the tree that's attached to A
to your new commit A'
whose parent is H
, then copy the tree attached to B
to the new commit B'
whose parent is A'
, and so on.git filter-branch
works well. When you run:git filter-branch <filter-list> <branch-name>
<branch-name>
, and then copies each of these commits. The copy is done, logically speaking anyway, by extracting the entire commit as-is, running each of the filters in your <filter-list>
, and then making a new commit using the resulting tree and message. It runs through the copying process in the reverse of Git's normal order, i.e., "forwards through history", instead of backwards.--parent-filter
. There are two more ways to do this but let's describe the --parent-filter
first. This is from the git filter-branch
documentation:
This is the filter for rewriting the commit's parent list. It will
receive the parent string on stdin and shall output the new parent
string on stdout. The parent string is in the format described in
git-commit-tree(1): empty for the initial commit, "-p parent" for a
normal commit and "-p parent1 -p parent2 -p parent3 ..." for a
merge commit.
-p <hash-of-H>
. The result would be:E--F--G--H--A'-B'-C'-D' <-- master
E-F-G-H
chain copied you'd have to pass master-old
as a positive reference as well, and since any bit-for-bit identical commit necessarily has the same hash ID as the original, you would have to make at least one change to commit E
, such as changing the committer tiemstamp by one second, for instance.)--commit-filter
: this is the command that actually makes the new commit. You can do anything here, including omit some commits entirely; but the reason for all the other filters is to make things easier, so in this case there's no reason to use the commit filter at all.Using
git replace
git replace
command. What git replace
does is to make new objects that stay in the repository, referenced by a special name in the refs/replace/
name-space. Whenever Git goes to look at some object by its hash ID, Git normally first checks to see if refs/replace/<hash-id>
exists. If so, Git looks instead at the object to which that reference points.A
, but slightly different. The slight difference is that the new commit object has one parent hash ID stored in it. The parent hash ID is that of commit H
. (Note that it has the same tree as A
.)A'
—you stick it into the repository and make refs/replace/<big-ugly-hash>
point to it:A--B--C--D <-- master
E--F--G--H <-- master-old
\
A' <-- refs/replace/deadcabf001...
A
's actual hash, which probably isn't really deadcabf001...
, so use the right ID here instead).git log
goes to view the history starting from commit D
, it will look at commit D
, then get D
's parent ID C
, look at commit C
, get B
's ID and move on to commit B
, get A
's ID and ... whoa, hey, there's a refs/replace/
for this one! Let's not look at A
after all! Let's look at A'
! It shows you A'
as B
's parent, then moves on to A'
's parent and shows you H
, and then G
, and so on.git replace
you do not have to copy any of the other commits. What you have is a commit history in which the new "better" commit supplants the old "not-so-good" one, but both actually coexist. Git uses the replacement under these conditions:
refs/replace/hash
in the references; andgit --no-replace-objects
.git clone
, you don't get replacements, by default. You must explicitly ask for them (which is not hard but does not have any nice easy front-end either).Using filter-branch with replacements
git filter-branch
. Since you are not running git --no-replace-objects filter-branch
, Git will see the replacement commit A'
instead of the original commit A
. It will therefore copy A'
instead of A
. You won't need a --parent-filter
. When it copies E
through H
, the new copies will be bit-for-bit identical to the originals, so those will stick around unchanged. The final result will be the same as if you had run git filter-branch
with the correct parent-filter.