I have a project, DemoA that was built off of a git repository, Project1.
Unfortunately, DemoA started as simply a copy of the files from Project1, before itself turning into an actual long-term project. I would now like to make Project1 a submodule of DemoA, but - more importantly - want to merge in the changes done on the code derived from Project1, in DemoA.
I have done a subtree split on DemoA to create a branch P1, which has all the changes done to the Project1 codebase in DemoA.
I have also managed to add in the changes to Project1 made to DemoA before it was instantiated as a repository.
Project1
A - B - C - D - E
Demo1/P1
(untracked changes) F - G - H - I
where the files in E are identical to F
What I want:
Project 1
A - B - C - D - E - G - H - I
Obviously the hashes for E and F are different, so when I added Demo1/P1 as a remote to Project1 and tried to merge, it complained about no common ancestor.
I have tried 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.
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.)
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.