7

I had a repository with this history:

A---B---C---D

Then, this repository was "split" (basically, another repository was created with it's history beginning at 'D' with the use of git-subtrees).

Now, I have this other repo with this history:

# The same D as the other
D---E---F---G

How can I join these two "parts" of the same project storyline into one repository?
The final result must be:

A---B---C---D---E---F---G

I've already tried a number of things but all of them includes merging, and that's not what I intend, because merging doesn't preserve some changes like deleted files.
Also, I tried to generate patches for all changes of this last version of the repository and apply them in the old one, but got lots of error: <file> already exists in index errors.

Update

I found this other question about re-parenting a commit and that was exactly what solved my problem, a combination of both git replace --graft and git filter-branch.

Update 2

Now my task is complete and I posted the complete, correct answer to the problem below.

Teodoro
  • 1,194
  • 8
  • 22
  • In referring to commits like `D`, are you OK with the hashes changing? When you say that the other repo starts at D, do you mean the contents of commits A to D, or just D? – root Jun 19 '20 at 04:38
  • Just the contents, I don't mind if the hashes are different. Also, it's not a problem if D gets duplicated when "glueing" those two parts. – Teodoro Jun 19 '20 at 04:40
  • Sorry, didn't quite answered the last part, I mean that the other repo started just when the first one stopped. So it has all contents from commits A to D. – Teodoro Jun 19 '20 at 04:42
  • so why not just rebase `E-F-G` onto `A-B-C-D`? – root Jun 19 '20 at 04:50
  • Can I do that with another repository? And I'm not quite sure what would be the command for that.. – Teodoro Jun 19 '20 at 04:52

2 Answers2

3

Update - The actual perfect approach:

Preparation

# Inside the older repo
$ cd old_repo

# Add the remote to newer repo with updated content
$ git remote add <remote name> <new_repo>

# Fetch the remote
$ git fetch <remote name>

# Track all branches of the remote so you have all of it's history in your older git (be aware of the remote's name in the command)
$ for b in `git branch -r | grep -v -- '->'`; do git branch --track ${b##<remote name>/} $b; done

# Delete the remote so you avoid messing up with the newer repo
$ git remote remove <remote name>

Now, I strongly suggest that you use a visual tool with this repo (like Gitkraken) since now it's kind of a mess there. You'll have two histories unattached to each other with, possibly, lots of duplicate commits.

Now, choose the commits that will be manipulated. Let's call commit with hash A the one in the older history, which will now be a parent of the commit B of the newest history. Now, you can use the script below (or run the commands manually) in order to join the trees and clean the mess left behind (trim the newer history right at the commit B, discarding all parents, since now it has a new parent).
(You must have git-replace and git-filter-repo installed)

#!/bin/sh

# Argument "$1" is commit A, and argument "$2" is commit B of the explanation above

if [ -z "$1" ] || [ -z "$2" ]
then
        echo "You must provide two commit hashes for this script";
        exit 1;
fi

git replace --graft $1 $2
result="$?"

[ "$result" = "0" ] && git filter-repo --force

Older, not important (that only serves to learn what to NOT do), answer below.

First I tried the approach with git-rebase, that didn't work out for a number of reasons, the biggest one is that it was a bit overkill for something like just changing the parent of a commit to another one, even if it's unrelated to the history.
Then I tried git cherry-pick to reapply all the history from point E..G to the old repository, also didn't work for a number of reasons but the main one was that it didn't copied other branches recursively.

Tried approach

$ git replace --graft <commit> <new parent to this commit>
Now, put the HEAD on the tip of the new history (the most recent commit in the main line you want to preserve), then:
$ git filter-branch <new parent to this commit>..HEAD

You may loose branches that are not yet merged onto the branch in which the HEAD is, and I couldn't find a way around that for now.

Teodoro
  • 1,194
  • 8
  • 22
1

You can create a new repo, add both repositories as remotes, and rebase the second onto the first:

Here's repo 1:

repo1[master]/$ git log --oneline
b3ae047 D
5c68b5e C
4a0bfe9 B
0d88f30 A
repo1[master]/$ git grep -e .
a:a
b:b
c:c
d:d

And here's repo 2:

$ cd ../repo2/
repo2[master]/ $ git log --oneline
7b05da3 G
3a72ace F
acd2388 E
5bfa6b3 D
repo2[master]/$ git grep -e .
a:a
b:b
c:c
d:d
e:e
f:f
g:g

Which starts with commit D identical to repo 1:

repo2[master]/$ git log --oneline HEAD~3
5bfa6b3 D
repo2[master]/$ git grep -e . HEAD~3
HEAD~3:a:a
HEAD~3:b:b
HEAD~3:c:c
HEAD~3:d:d

Now let's create a repo which connects them:

repo2[master]/$ mkdir ../repo3
repo2[master]/$ cd ../repo3
repo3$ git init
repo3[master]/$ git remote add r1 ../repo1
repo3[master]/$ git remote add r2 ../repo2
repo3[master]/$ git fetch r1 && git fetch r2
...boring output omitted...

Now we want to rebase from the tip of repo 2:

fat:repo3[master]/$ git reset --hard r2/master
HEAD is now at 7b05da3 G

You'll want to do git rebase -i r1/master, and remove the first commit, D, because it duplicates r1/master. If you go full command line (and don't have an editor configured in gitconfig):

repo3[master]/$ export EDITOR='sed -ibak 1d'
repo3[master]/$ git rebase -i r1/master
Successfully rebased and updated refs/heads/master.
repo3[master]/$ git log --oneline
fc2eb8e G
de5161b F
e85ce17 E
b3ae047 D
5c68b5e C
4a0bfe9 B
0d88f30 A
repo3[master]/ (INT)$ git grep -e .
a:a
b:b
c:c
d:d
e:e
f:f
g:g
root
  • 5,528
  • 1
  • 7
  • 15
  • Thanks a lot for your help and time, unfortunately this solution was not adequate for my problem because of some details. Somehow git couldn't do a proper rebase and it led me to "merge conflicts" to resolve, pretty weird. Eventually I've managed to fix this with another approach using cherry-picks. – Teodoro Jun 19 '20 at 17:07
  • Actually, forgot what I said, your answer is correct, now I've managed to do it. Maybe I've done some mistake earlier, thank you! My approach with cherry picks only works when there are no merges in between histories, otherwise it leads me to even more troubles. – Teodoro Jun 19 '20 at 17:40
  • Ok, going back again in my answer, I didn't realize that I ended up with two different histories lying in the same repository until I tried to see all commits with git log and just couldn't, because the rest of the history was in a branch completely apart. The correct approach came from another SO question using git replace --graft along with filter-branch, so I'm going to post and remove the correct answer check. But thanks again! – Teodoro Jun 19 '20 at 21:21