Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Join history of older version of the same repository

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.

like image 630
Teodoro Avatar asked Oct 16 '22 02:10

Teodoro


2 Answers

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.

like image 153
Teodoro Avatar answered Oct 20 '22 20:10

Teodoro


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
like image 34
root Avatar answered Oct 20 '22 21:10

root