在git中没有真正的共同祖先的情况下合并复制的存储库的更改 [英] Merge changes of copied repository without true common ancestor in git

查看:34
本文介绍了在git中没有真正的共同祖先的情况下合并复制的存储库的更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个基于Demo仓库Project1构建的DemoA项目.

不幸的是,DemoA最初只是来自Project1的文件的副本,然后才变成一个实际的长期项目.现在,我想使Project1成为DemoA的子模块,但更重要的是,要在DemoA中合并对从Project1派生的代码所做的更改.

我在DemoA上完成了一个子树拆分,以创建一个分支P1,该分支对DemoA中的Project1代码库进行了所有更改.

在实例化为存储库之前,我还设法添加了对Project1对DemoA所做的更改.

  Project1A-B-C-D-E演示1/P1(未跟踪的更改)F-G-H-IE中的文件与F相同 

我想要什么:

 项目1A-B-C-D-E-G-H-I 

显然,E和F的哈希值不同,因此当我将Demo1/P1添加为Project1的远程对象并尝试合并时,它抱怨没有共同的祖先.

我尝试了> rebase到另一个分支上,,这样做:

  git rebase -s递归-X subtree = project1dir --onto(E-hash)(F-hash)空分支 

但是我显然不明白这实际上是在做什么,因为它似乎并没有真正任何事情.

有没有一种干净的方法可以做到这一点?我不介意该过程有些手动性,但我想保留历史记录.

解决方案

这都是中等难度的(实际难度级别视情况和您对Git的熟悉程度而异).

如果 E F 中的文件确实相同,那么(或一种简单的方法)将放入嫁接(使用git replace 或grafts文件),以便Git假装 G 的父提交是 E 提交.也就是说,您有:

  A--B--C--D--E<-主F ------------- G--H--I<-refs/remotes/rem/P1 

git diff master rem/P1〜4 完全不输出( master 名称提交 E rem/P1〜4 个名称提交 F ,并且 E F 的两棵树完全匹配).

希望,至少作为一种中间产品,您可能拥有以下内容:

  A--B--C--D--E<-主\F G--H--I<-refs/remotes/rem/P1 

也就是说,至少在某些目的和一段时间内,您希望Git假装提交 G 具有提交 E 作为其父级./p>

使用 git replace 模仿旧的恐怖黑客入侵

Git移植程序正是这样做的:它们告诉Git假装某个提交的父代是其他一些提交.但是不推荐使用这些方法,而推荐使用更通用的 git replace .您可以使用 git replace 来进行新的提交 G',该提交类似于(但至少对于大多数Git命令来说是这样的) G ,一个区别是 G' E 作为其父级.

然后,您可以使用 git filter-branch 重新复制存储库中的提交,以便这种替换成为真实且永久的,而不仅仅是副本.当然,您将为新提交获得新的提交哈希( G'可以保留其哈希,但是您必须获得新的 H' I').请参见 JakubNarębski的答案,然后using format-patch, but git am has complained

error: file.xyz: already exists in index

and I was trying to rebase onto a different branch, doing:

git rebase -s recursive -X subtree=project1dir --onto (E-hash) (F-hash) emptybranch

but I clearly don't understand what that is actually doing, as it didn't seem to actually do anything.

Is there a clean way to do this? I don't mind some manualness to the process, but I would like to preserve the history.

解决方案

This is all moderately difficult (actual difficulty level varies depending on circumstances and your familiarity with Git).

If the files in E and F are truly identical, the (or an) easy way to do this would be to put in a graft (with git replace or the grafts file) so that Git pretends that G's parent commit is commit E. That is, you have:

A--B--C--D--E   <-- master

F-------------G--H--I   <-- refs/remotes/rem/P1

and git diff master rem/P1~4 produces no output at all (master names commit E, rem/P1~4 names commit F, and the two trees for E and F match exactly).

You wish, at least as an intermediate product perhaps, that you had this:

A--B--C--D--E   <-- master
             \
F             G--H--I   <-- refs/remotes/rem/P1

That is, you'd like Git to pretend, at least for some purposes and some period of time, that commit G has commit E as its parent.

Using git replace to emulate the old horrible-hack grafts

Git grafts do precisely that: they tell Git to pretend that the parent(s) of some commit is some other commit(s). But these have been deprecated in favor of the more generic git replace. You can use git replace to make a new commit G' that resembles (but supersedes, at least, for most Git commands) G, with the one difference being that G' has E as its parent.

You can then use git filter-branch to re-copy commits in the repository so that this replacement becomes real and permanent, rather than just a copy. You will, of course, get new commit hashes for the new commits (G' can keep its hash but you must get a new H' and I'). See this answer by Jakub Narębski, and then How do git grafts and replace differ? (Are grafts now deprecated?), where VonC links to Jakub's answers.

(Git grafts do still work, and you can just put the hash for commits G and E into .git/info/grafts: echo $(git rev-parse rem/P1~3) $(git rev-parse master) > .git/info/grafts, for instance. But they are a horrible hack and if you do this sort of trick it's best to just run your filter-branch immediately afterward, as Jakub notes.)

Using git rebase

You can also use git rebase --onto, as you were attempting, but you must start this rebase using an existing (ordinary, local) branch name (I'm not sure where emptybranch came from here) that points to commit I. I think maybe the step you are missing might be making this regular ordinary local branch name:

git checkout -b rewrite rem/P1

for instance, assuming the name rem/P1 resolves to commit I. Or git checkout -b rewrite <hash-of-I>, if you have that hash in front of you for easy cut/paste. At that point you will have this:

A--B--C--D--E   <-- master

F-------------G--H--I   <-- HEAD -> rewrite, rem/P1

That is, you're now on this new rewrite branch, which points to commit I. Now you can git rebase --onto master HEAD~3 to copy the most recent 3 commits on the current branch—G, H, and I. The copies will be G', H', and I', with the parent of G' being E—the commit to which master points—and the parent of H' being G' and so on:

              G'-H'-I'   <-- HEAD -> rewrite
             /
A--B--C--D--E   <-- master

F-------------G--H--I   <-- rem/P1

Now you can delete the remote and its remote-tracking branch since you have the commit chain you want. You can also fast-forward master to point to commit I' at any time, if that's what you want.

这篇关于在git中没有真正的共同祖先的情况下合并复制的存储库的更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆